1<?php
2
3declare(strict_types=1);
4
5namespace Antlr\Antlr4\Runtime;
6
7use Antlr\Antlr4\Runtime\Utils\Pair;
8use Antlr\Antlr4\Runtime\Utils\StringUtils;
9
10final class CommonToken implements WritableToken
11{
12    /**
13     * This is the backing field for {@see CommonToken::getType()} and
14     * {@see CommonToken::setType()}.
15     *
16     * @var int
17     */
18    protected $type;
19
20    /**
21     * This is the backing field for {@see CommonToken::getLine()} and
22     * {@see CommonToken::setLine()}.
23     *
24     * @var int
25     */
26    protected $line = 0;
27
28    /**
29     * This is the backing field for {@see CommonToken::getCharPositionInLine()}
30     * and {@see CommonToken::setCharPositionInLine()}.
31     *
32     * @var int
33     */
34    protected $charPositionInLine = -1;
35
36    /**
37     * This is the backing field for {@see CommonToken::getChannel()} and
38     * {@see CommonToken::setChannel()}.
39     *
40     * @var int
41     */
42    protected $channel = Token::DEFAULT_CHANNEL;
43
44    /**
45     * This is the backing field for {@see CommonToken::getTokenSource()} and
46     * {@see CommonToken::getInputStream()}.
47     *
48     *
49     * These properties share a field to reduce the memory footprint of
50     * {@see CommonToken}. Tokens created by a {@see CommonTokenFactory} from
51     * the same source and input stream share a reference to the same
52     * {@see Pair} containing these values.
53     *
54     * @var Pair
55     */
56    protected $source;
57
58    /**
59     * This is the backing field for {@see CommonToken::getText()} when the token
60     * text is explicitly set in the constructor or via {@see CommonToken::setText()}.
61     *
62     * @see CommonToken::getText()
63     *
64     * @var string|null
65     */
66    protected $text;
67
68    /**
69     * This is the backing field for {@see CommonToken::getTokenIndex()} and
70     * {@see CommonToken::setTokenIndex()}.
71     *
72     * @var int
73     */
74    protected $index = -1;
75
76    /**
77     * This is the backing field for {@see CommonToken::getStartIndex()} and
78     * {@see CommonToken::setStartIndex()}.
79     *
80     * @var int
81     */
82    protected $start;
83
84    /**
85     * This is the backing field for {@see CommonToken::getStopIndex()} and
86     * {@see CommonToken::setStopIndex()}.
87     *
88     * @var int
89     */
90    protected $stop;
91
92    public function __construct(
93        int $type,
94        ?Pair $source = null,
95        ?int $channel = null,
96        int $start = -1,
97        int $stop = -1
98    ) {
99        if ($source !== null && !$source->a instanceof TokenSource) {
100            throw new \RuntimeException('Unexpected token source type.');
101        }
102
103        if ($source !== null && !$source->b instanceof CharStream) {
104            throw new \RuntimeException('Unexpected stream type.');
105        }
106
107        $this->source = $source ?? self::emptySource();
108        $this->type = $type;
109        $this->channel = $channel ?? Token::DEFAULT_CHANNEL;
110        $this->start = $start;
111        $this->stop = $stop;
112
113        $tokenSource = $this->source->a;
114
115        if ($tokenSource instanceof TokenSource) {
116            $this->line = $tokenSource->getLine();
117            $this->charPositionInLine = $tokenSource->getCharPositionInLine();
118        }
119    }
120
121    /**
122     * An empty {@see Pair}, which is used as the default value of
123     * {@see CommonToken::source()} for tokens that do not have a source.
124     */
125    public static function emptySource() : Pair
126    {
127        static $source;
128
129        return $source = $source ?? new Pair(null, null);
130    }
131
132    /**
133     * Constructs a new {@see CommonToken} as a copy of another {@see Token}.
134     *
135     * If `oldToken` is also a {@see CommonToken} instance, the newly constructed
136     * token will share a reference to the {@see CommonToken::text()} field and
137     * the {@see Pair} stored in {@see CommonToken::source()}. Otherwise,
138     * {@see CommonToken::text()} will be assigned the result of calling
139     * {@see CommonToken::getText()}, and {@see CommonToken::source()} will be
140     * constructed from the result of {@see Token::getTokenSource()} and
141     * {@see Token::getInputStream()}.
142     */
143    public function clone() : CommonToken
144    {
145        $token = new self($this->type, $this->source, $this->channel, $this->start, $this->stop);
146
147        $token->index = $this->index;
148        $token->line = $this->line;
149        $token->charPositionInLine = $this->charPositionInLine;
150
151        $token->setText($this->text);
152
153        return $token;
154    }
155
156    public function getType() : int
157    {
158        return $this->type;
159    }
160
161    public function setType(int $type) : void
162    {
163        $this->type = $type;
164    }
165
166    public function getLine() : int
167    {
168        return $this->line;
169    }
170
171    public function setLine(int $line) : void
172    {
173        $this->line = $line;
174    }
175
176    public function getText() : ?string
177    {
178        if ($this->text !== null) {
179            return $this->text;
180        }
181
182        $input = $this->getInputStream();
183
184        if ($input === null) {
185            return null;
186        }
187
188        $n = $input->getLength();
189
190        if ($this->start < $n && $this->stop < $n) {
191            return $input->getText($this->start, $this->stop);
192        }
193
194        return '<EOF>';
195    }
196
197    /**
198     * Explicitly set the text for this token. If `text` is not `null`, then
199     * {@see CommonToken::getText()} will return this value rather than
200     * extracting the text from the input.
201     *
202     * @param string $text The explicit text of the token, or `null`
203     *                     if the text should be obtained from the input
204     *                     along with the start and stop indexes of the token.
205     */
206    public function setText(?string $text) : void
207    {
208        $this->text = $text;
209    }
210
211    public function getCharPositionInLine() : int
212    {
213        return $this->charPositionInLine;
214    }
215
216    public function setCharPositionInLine(int $charPositionInLine) : void
217    {
218        $this->charPositionInLine = $charPositionInLine;
219    }
220
221    public function getChannel() : int
222    {
223        return $this->channel;
224    }
225
226    public function setChannel(int $channel) : void
227    {
228        $this->channel = $channel;
229    }
230
231    public function getStartIndex() : int
232    {
233        return $this->start;
234    }
235
236    public function setStartIndex(int $index) : void
237    {
238        $this->start = $index;
239    }
240
241    public function getStopIndex() : int
242    {
243        return $this->stop;
244    }
245
246    public function setStopIndex(int $index) : void
247    {
248        $this->stop = $index;
249    }
250
251    public function getTokenIndex() : int
252    {
253        return $this->index;
254    }
255
256    public function setTokenIndex(int $tokenIndex) : void
257    {
258        $this->index = $tokenIndex;
259    }
260
261    public function getTokenSource() : ?TokenSource
262    {
263        $source = $this->source->a;
264
265        if ($source !== null && !$source instanceof TokenSource) {
266            throw new \RuntimeException('Unexpected token source type.');
267        }
268
269        return $source;
270    }
271
272    public function getInputStream() : ?CharStream
273    {
274        $stream = $this->source->b;
275
276        if ($stream !== null && !$stream instanceof CharStream) {
277            throw new \RuntimeException('Unexpected token source type.');
278        }
279
280        return $stream;
281    }
282
283    public function getSource() : Pair
284    {
285        return $this->source;
286    }
287
288    public function __toString() : string
289    {
290        return \sprintf(
291            '[@%d,%d:%d=\'%s\',<%d>%s,%d:%d]',
292            $this->getTokenIndex(),
293            $this->start,
294            $this->stop,
295            StringUtils::escapeWhitespace($this->getText() ?? ''),
296            $this->type,
297            $this->channel > 0 ? ',channel=' . $this->channel : '',
298            $this->line,
299            $this->charPositionInLine
300        );
301    }
302}
303