Subversion-Projekte lars-tiefland.laravel_shop

Revision

Zur aktuellen Revision | Details | Letzte Änderung | Log anzeigen | RSS feed

Revision Autor Zeilennr. Zeile
148 lars 1
<?php
2
 
3
declare(strict_types=1);
4
 
5
namespace Doctrine\Common\Lexer;
6
 
7
use ReflectionClass;
8
use UnitEnum;
9
 
10
use function get_class;
11
use function implode;
12
use function preg_split;
13
use function sprintf;
14
use function substr;
15
 
16
use const PREG_SPLIT_DELIM_CAPTURE;
17
use const PREG_SPLIT_NO_EMPTY;
18
use const PREG_SPLIT_OFFSET_CAPTURE;
19
 
20
/**
21
 * Base class for writing simple lexers, i.e. for creating small DSLs.
22
 *
23
 * @template T of UnitEnum|string|int
24
 * @template V of string|int
25
 */
26
abstract class AbstractLexer
27
{
28
    /**
29
     * Lexer original input string.
30
     *
31
     * @var string
32
     */
33
    private $input;
34
 
35
    /**
36
     * Array of scanned tokens.
37
     *
38
     * @var list<Token<T, V>>
39
     */
40
    private $tokens = [];
41
 
42
    /**
43
     * Current lexer position in input string.
44
     *
45
     * @var int
46
     */
47
    private $position = 0;
48
 
49
    /**
50
     * Current peek of current lexer position.
51
     *
52
     * @var int
53
     */
54
    private $peek = 0;
55
 
56
    /**
57
     * The next token in the input.
58
     *
59
     * @var mixed[]|null
60
     * @psalm-var Token<T, V>|null
61
     */
62
    public $lookahead;
63
 
64
    /**
65
     * The last matched/seen token.
66
     *
67
     * @var mixed[]|null
68
     * @psalm-var Token<T, V>|null
69
     */
70
    public $token;
71
 
72
    /**
73
     * Composed regex for input parsing.
74
     *
75
     * @var string|null
76
     */
77
    private $regex;
78
 
79
    /**
80
     * Sets the input data to be tokenized.
81
     *
82
     * The Lexer is immediately reset and the new input tokenized.
83
     * Any unprocessed tokens from any previous input are lost.
84
     *
85
     * @param string $input The input to be tokenized.
86
     *
87
     * @return void
88
     */
89
    public function setInput($input)
90
    {
91
        $this->input  = $input;
92
        $this->tokens = [];
93
 
94
        $this->reset();
95
        $this->scan($input);
96
    }
97
 
98
    /**
99
     * Resets the lexer.
100
     *
101
     * @return void
102
     */
103
    public function reset()
104
    {
105
        $this->lookahead = null;
106
        $this->token     = null;
107
        $this->peek      = 0;
108
        $this->position  = 0;
109
    }
110
 
111
    /**
112
     * Resets the peek pointer to 0.
113
     *
114
     * @return void
115
     */
116
    public function resetPeek()
117
    {
118
        $this->peek = 0;
119
    }
120
 
121
    /**
122
     * Resets the lexer position on the input to the given position.
123
     *
124
     * @param int $position Position to place the lexical scanner.
125
     *
126
     * @return void
127
     */
128
    public function resetPosition($position = 0)
129
    {
130
        $this->position = $position;
131
    }
132
 
133
    /**
134
     * Retrieve the original lexer's input until a given position.
135
     *
136
     * @param int $position
137
     *
138
     * @return string
139
     */
140
    public function getInputUntilPosition($position)
141
    {
142
        return substr($this->input, 0, $position);
143
    }
144
 
145
    /**
146
     * Checks whether a given token matches the current lookahead.
147
     *
148
     * @param T $type
149
     *
150
     * @return bool
151
     *
152
     * @psalm-assert-if-true !=null $this->lookahead
153
     */
154
    public function isNextToken($type)
155
    {
156
        return $this->lookahead !== null && $this->lookahead->isA($type);
157
    }
158
 
159
    /**
160
     * Checks whether any of the given tokens matches the current lookahead.
161
     *
162
     * @param list<T> $types
163
     *
164
     * @return bool
165
     *
166
     * @psalm-assert-if-true !=null $this->lookahead
167
     */
168
    public function isNextTokenAny(array $types)
169
    {
170
        return $this->lookahead !== null && $this->lookahead->isA(...$types);
171
    }
172
 
173
    /**
174
     * Moves to the next token in the input string.
175
     *
176
     * @return bool
177
     *
178
     * @psalm-assert-if-true !null $this->lookahead
179
     */
180
    public function moveNext()
181
    {
182
        $this->peek      = 0;
183
        $this->token     = $this->lookahead;
184
        $this->lookahead = isset($this->tokens[$this->position])
185
            ? $this->tokens[$this->position++] : null;
186
 
187
        return $this->lookahead !== null;
188
    }
189
 
190
    /**
191
     * Tells the lexer to skip input tokens until it sees a token with the given value.
192
     *
193
     * @param T $type The token type to skip until.
194
     *
195
     * @return void
196
     */
197
    public function skipUntil($type)
198
    {
199
        while ($this->lookahead !== null && ! $this->lookahead->isA($type)) {
200
            $this->moveNext();
201
        }
202
    }
203
 
204
    /**
205
     * Checks if given value is identical to the given token.
206
     *
207
     * @param string     $value
208
     * @param int|string $token
209
     *
210
     * @return bool
211
     */
212
    public function isA($value, $token)
213
    {
214
        return $this->getType($value) === $token;
215
    }
216
 
217
    /**
218
     * Moves the lookahead token forward.
219
     *
220
     * @return mixed[]|null The next token or NULL if there are no more tokens ahead.
221
     * @psalm-return Token<T, V>|null
222
     */
223
    public function peek()
224
    {
225
        if (isset($this->tokens[$this->position + $this->peek])) {
226
            return $this->tokens[$this->position + $this->peek++];
227
        }
228
 
229
        return null;
230
    }
231
 
232
    /**
233
     * Peeks at the next token, returns it and immediately resets the peek.
234
     *
235
     * @return mixed[]|null The next token or NULL if there are no more tokens ahead.
236
     * @psalm-return Token<T, V>|null
237
     */
238
    public function glimpse()
239
    {
240
        $peek       = $this->peek();
241
        $this->peek = 0;
242
 
243
        return $peek;
244
    }
245
 
246
    /**
247
     * Scans the input string for tokens.
248
     *
249
     * @param string $input A query string.
250
     *
251
     * @return void
252
     */
253
    protected function scan($input)
254
    {
255
        if (! isset($this->regex)) {
256
            $this->regex = sprintf(
257
                '/(%s)|%s/%s',
258
                implode(')|(', $this->getCatchablePatterns()),
259
                implode('|', $this->getNonCatchablePatterns()),
260
                $this->getModifiers()
261
            );
262
        }
263
 
264
        $flags   = PREG_SPLIT_NO_EMPTY | PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_OFFSET_CAPTURE;
265
        $matches = preg_split($this->regex, $input, -1, $flags);
266
 
267
        if ($matches === false) {
268
            // Work around https://bugs.php.net/78122
269
            $matches = [[$input, 0]];
270
        }
271
 
272
        foreach ($matches as $match) {
273
            // Must remain before 'value' assignment since it can change content
274
            $firstMatch = $match[0];
275
            $type       = $this->getType($firstMatch);
276
 
277
            $this->tokens[] = new Token(
278
                $firstMatch,
279
                $type,
280
                $match[1]
281
            );
282
        }
283
    }
284
 
285
    /**
286
     * Gets the literal for a given token.
287
     *
288
     * @param T $token
289
     *
290
     * @return int|string
291
     */
292
    public function getLiteral($token)
293
    {
294
        if ($token instanceof UnitEnum) {
295
            return get_class($token) . '::' . $token->name;
296
        }
297
 
298
        $className = static::class;
299
 
300
        $reflClass = new ReflectionClass($className);
301
        $constants = $reflClass->getConstants();
302
 
303
        foreach ($constants as $name => $value) {
304
            if ($value === $token) {
305
                return $className . '::' . $name;
306
            }
307
        }
308
 
309
        return $token;
310
    }
311
 
312
    /**
313
     * Regex modifiers
314
     *
315
     * @return string
316
     */
317
    protected function getModifiers()
318
    {
319
        return 'iu';
320
    }
321
 
322
    /**
323
     * Lexical catchable patterns.
324
     *
325
     * @return string[]
326
     */
327
    abstract protected function getCatchablePatterns();
328
 
329
    /**
330
     * Lexical non-catchable patterns.
331
     *
332
     * @return string[]
333
     */
334
    abstract protected function getNonCatchablePatterns();
335
 
336
    /**
337
     * Retrieve token type. Also processes the token value if necessary.
338
     *
339
     * @param string $value
340
     *
341
     * @return T|null
342
     *
343
     * @param-out V $value
344
     */
345
    abstract protected function getType(&$value);
346
}