Subversion-Projekte lars-tiefland.laravel_shop

Revision

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