1
|
|
|
<?php |
2
|
|
|
/** |
3
|
|
|
* This file is part of PHP-Yacc package. |
4
|
|
|
* |
5
|
|
|
* For the full copyright and license information, please view the LICENSE |
6
|
|
|
* file that was distributed with this source code. |
7
|
|
|
*/ |
8
|
|
|
declare(strict_types=1); |
9
|
|
|
|
10
|
|
|
namespace PhpYacc\Lalr; |
11
|
|
|
|
12
|
|
|
use PhpYacc\Exception\LogicException; |
13
|
|
|
use PhpYacc\Grammar\Context; |
14
|
|
|
use PhpYacc\Grammar\State; |
15
|
|
|
use PhpYacc\Grammar\Symbol; |
16
|
|
|
use PhpYacc\Lalr\Conflict\ReduceReduce; |
17
|
|
|
use PhpYacc\Lalr\Conflict\ShiftReduce; |
18
|
|
|
use PhpYacc\Support\Utils; |
19
|
|
|
use PhpYacc\Yacc\Production; |
20
|
|
|
|
21
|
|
|
/** |
22
|
|
|
* Class Generator. |
23
|
|
|
*/ |
24
|
|
|
class Generator |
25
|
|
|
{ |
26
|
|
|
const NON_ASSOC = -32768; |
27
|
|
|
|
28
|
|
|
/** |
29
|
|
|
* @var Context |
30
|
|
|
*/ |
31
|
|
|
protected $context; |
32
|
|
|
|
33
|
|
|
protected $nullable; |
34
|
|
|
protected $blank; |
35
|
|
|
|
36
|
|
|
/** |
37
|
|
|
* @var array|State[][] |
38
|
|
|
*/ |
39
|
|
|
protected $statesThrough = []; |
40
|
|
|
|
41
|
|
|
/** |
42
|
|
|
* @var array |
43
|
|
|
*/ |
44
|
|
|
protected $visited = []; |
45
|
|
|
|
46
|
|
|
/** |
47
|
|
|
* @var BitSet[] |
48
|
|
|
*/ |
49
|
|
|
protected $first; |
50
|
|
|
|
51
|
|
|
/** |
52
|
|
|
* @var BitSet[] |
53
|
|
|
*/ |
54
|
|
|
protected $follow; |
55
|
|
|
|
56
|
|
|
/** |
57
|
|
|
* @var State[] |
58
|
|
|
*/ |
59
|
|
|
protected $states; |
60
|
|
|
|
61
|
|
|
/** |
62
|
|
|
* @var int |
63
|
|
|
*/ |
64
|
|
|
protected $countLooks; |
65
|
|
|
|
66
|
|
|
/** |
67
|
|
|
* @var int |
68
|
|
|
*/ |
69
|
|
|
protected $countStates; |
70
|
|
|
|
71
|
|
|
/** |
72
|
|
|
* @var int |
73
|
|
|
*/ |
74
|
|
|
protected $countActions; |
75
|
|
|
|
76
|
|
|
/** |
77
|
|
|
* @var int |
78
|
|
|
*/ |
79
|
|
|
protected $countActions2; |
80
|
|
|
|
81
|
|
|
/** |
82
|
|
|
* @var int |
83
|
|
|
*/ |
84
|
|
|
protected $countNonLeafStates; |
85
|
|
|
protected $nsrerr; |
86
|
|
|
protected $nrrerr; |
87
|
|
|
|
88
|
|
|
/** |
89
|
|
|
* @param Context $context |
90
|
|
|
* |
91
|
|
|
* @throws LogicException |
92
|
|
|
* |
93
|
|
|
* @return void |
94
|
|
|
*/ |
95
|
|
|
public function compute(Context $context) |
96
|
|
|
{ |
97
|
|
|
$this->context = $context; |
98
|
|
|
// Ensure nil symbol is part of nSymbols |
99
|
|
|
$this->context->nilSymbol(); |
100
|
|
|
$this->context->finish(); |
101
|
|
|
$countSymbols = $this->context->countSymbols; |
102
|
|
|
$this->nullable = \array_fill(0, $countSymbols, false); |
103
|
|
|
|
104
|
|
|
$this->blank = new ArrayBitSet($countSymbols); |
105
|
|
|
$this->states = []; |
106
|
|
|
$this->countLooks = $this->countStates = $this->countActions = $this->countActions2 = 0; |
107
|
|
|
$this->countNonLeafStates = 0; |
108
|
|
|
$this->nsrerr = $this->nrrerr = 0; |
109
|
|
|
|
110
|
|
|
$this->statesThrough = []; |
111
|
|
|
$this->first = []; |
112
|
|
|
$this->follow = []; |
113
|
|
|
|
114
|
|
|
foreach ($this->context->symbols as $s) { |
115
|
|
|
$this->first[$s->code] = clone $this->blank; |
116
|
|
|
$this->follow[$s->code] = clone $this->blank; |
117
|
|
|
$this->statesThrough[$s->code] = []; |
118
|
|
|
} |
119
|
|
|
|
120
|
|
|
$this->computeEmpty(); |
121
|
|
|
$this->firstNullablePrecomp(); |
122
|
|
|
$this->computeKernels(); |
123
|
|
|
$this->computeLookaheads(); |
124
|
|
|
$this->fillReduce(); |
125
|
|
|
$this->printDiagnostics(); |
126
|
|
|
$this->printStatistics(); |
127
|
|
|
|
128
|
|
|
$this->context->states = $this->states; |
129
|
|
|
$this->context->countNonLeafStates = $this->countNonLeafStates; |
130
|
|
|
} |
131
|
|
|
|
132
|
|
|
/** |
133
|
|
|
* @return void |
134
|
|
|
*/ |
135
|
|
|
protected function computeKernels() |
136
|
|
|
{ |
137
|
|
|
$tmpList = new Lr1(null, clone $this->blank, new Item($this->context->gram(0), 1)); |
138
|
|
|
$this->findOrCreateState($this->context->nilsymbol, $tmpList); |
139
|
|
|
|
140
|
|
|
// foreach by ref so that new additions to $this->states are also picked up |
141
|
|
|
foreach ($this->states as &$p) { |
142
|
|
|
// Collect direct GOTO's (come from kernel items) |
143
|
|
|
|
144
|
|
|
/** @var Lr1|null $tmpList */ |
145
|
|
|
/** @var Lr1|null $tmpTail */ |
146
|
|
|
$tmpList = $tmpTail = null; |
147
|
|
|
|
148
|
|
|
/** @var Lr1 $x */ |
149
|
|
|
for ($x = $p->items; $x !== null; $x = $x->next) { |
150
|
|
|
if (!$x->isTailItem()) { |
151
|
|
|
$wp = new Lr1(null, clone $this->blank, $x->item->slice(1)); |
152
|
|
|
if ($tmpTail !== null) { |
153
|
|
|
$tmpTail->next = $wp; |
154
|
|
|
} else { |
155
|
|
|
$tmpList = $wp; |
156
|
|
|
} |
157
|
|
|
$tmpTail = $wp; |
158
|
|
|
} |
159
|
|
|
} |
160
|
|
|
|
161
|
|
|
// Collect indirect GOTO's (come from nonkernel items) |
162
|
|
|
$this->clearVisited(); |
163
|
|
|
for ($tp = $tmpList; $tp != null; $tp = $tp->next) { |
164
|
|
|
/** @var Symbol $g */ |
165
|
|
|
$g = $tp->item[-1]; |
166
|
|
|
if ($g !== null && !$g->isTerminal && !$this->visited[$g->code]) { |
167
|
|
|
$this->visited[$g->code] = true; |
168
|
|
|
/** @var Production $gram */ |
169
|
|
|
for ($gram = $g->value; $gram != null; $gram = $gram->link) { |
170
|
|
|
if (isset($gram->body[1])) { |
171
|
|
|
$wp = new Lr1($g, clone $this->blank, new Item($gram, 2)); |
172
|
|
|
$tmpTail->next = $wp; |
173
|
|
|
$tmpTail = $wp; |
174
|
|
|
} |
175
|
|
|
} |
176
|
|
|
} |
177
|
|
|
} |
178
|
|
|
|
179
|
|
|
$tmpList = $this->sortList($tmpList, function (Lr1 $x, Lr1 $y) { |
180
|
|
|
$gx = $x->item[-1]->code; |
181
|
|
|
$gy = $y->item[-1]->code; |
182
|
|
|
if ($gx !== $gy) { |
183
|
|
|
return $gx - $gy; |
184
|
|
|
} |
185
|
|
|
$px = $x->item->getProduction(); |
186
|
|
|
$py = $y->item->getProduction(); |
187
|
|
|
if ($px !== $py) { |
188
|
|
|
return $px->num - $py->num; |
189
|
|
|
} |
190
|
|
|
|
191
|
|
|
return $x->item->getPos() - $y->item->getPos(); |
192
|
|
|
}); |
193
|
|
|
|
194
|
|
|
// Compute next states |
195
|
|
|
$nextStates = []; |
196
|
|
|
for ($tp = $tmpList; $tp !== null;) { |
197
|
|
|
$sp = null; |
198
|
|
|
|
199
|
|
|
$g = $tp->item[-1]; |
200
|
|
|
$sublist = $tp; |
201
|
|
|
while ($tp != null && $tp->item[-1] === $g) { |
202
|
|
|
$sp = $tp; |
203
|
|
|
$tp = $tp->next; |
204
|
|
|
} |
205
|
|
|
$sp->next = null; |
206
|
|
|
|
207
|
|
|
$nextStates[] = $this->findOrCreateState($g, $sublist); |
208
|
|
|
} |
209
|
|
|
|
210
|
|
|
$p->shifts = $nextStates; |
211
|
|
|
$this->countActions += \count($nextStates); |
212
|
|
|
} |
213
|
|
|
} |
214
|
|
|
|
215
|
|
|
/** |
216
|
|
|
* @return void |
217
|
|
|
*/ |
218
|
|
|
protected function computeLookaheads() |
219
|
|
|
{ |
220
|
|
|
$this->states[0]->items->look->setBit(0); |
221
|
|
|
do { |
222
|
|
|
$changed = false; |
223
|
|
|
foreach ($this->states as $p) { |
224
|
|
|
$this->computeFollow($p); |
225
|
|
|
for ($x = $p->items; $x !== null; $x = $x->next) { |
226
|
|
|
$g = $x->item[0] ?? null; |
227
|
|
|
if (null !== $g) { |
228
|
|
|
$s = $x->item->slice(1); |
229
|
|
|
$t = null; |
230
|
|
|
foreach ($p->shifts as $t) { |
231
|
|
|
if ($t->through === $g) { |
232
|
|
|
break; |
233
|
|
|
} |
234
|
|
|
} |
235
|
|
|
\assert($t->through === $g); |
236
|
|
|
for ($y = $t->items; $y !== null; $y = $y->next) { |
237
|
|
|
if ($y->item == $s) { |
238
|
|
|
break; |
239
|
|
|
} |
240
|
|
|
} |
241
|
|
|
\assert($y->item == $s); |
242
|
|
|
$changed |= $y->look->or($x->look); |
243
|
|
|
} |
244
|
|
|
} |
245
|
|
|
|
246
|
|
|
foreach ($p->shifts as $t) { |
247
|
|
|
for ($x = $t->items; $x !== null; $x = $x->next) { |
248
|
|
View Code Duplication |
if ($x->left !== null) { |
|
|
|
|
249
|
|
|
$changed |= $x->look->or($this->follow[$x->left->code]); |
250
|
|
|
} |
251
|
|
|
} |
252
|
|
|
} |
253
|
|
|
|
254
|
|
View Code Duplication |
for ($x = $p->items; $x !== null; $x = $x->next) { |
|
|
|
|
255
|
|
|
if ($x->isTailItem() && $x->isHeadItem()) { |
256
|
|
|
$x->look->or($this->follow[$x->item[-1]->code]); |
|
|
|
|
257
|
|
|
} |
258
|
|
|
} |
259
|
|
|
} |
260
|
|
|
} while ($changed); |
261
|
|
|
|
262
|
|
|
if ($this->context->debug) { |
263
|
|
|
foreach ($this->states as $p) { |
264
|
|
|
$this->context->debug("state unknown:\n"); |
265
|
|
|
for ($x = $p->items; $x != null; $x = $x->next) { |
266
|
|
|
$this->context->debug("\t".\trim((string) $x->item)."\n"); |
267
|
|
|
$this->context->debug("\t\t[ "); |
268
|
|
|
$this->context->debug(Utils::dumpSet($this->context, $x->look)); |
269
|
|
|
$this->context->debug("]\n"); |
270
|
|
|
} |
271
|
|
|
} |
272
|
|
|
} |
273
|
|
|
} |
274
|
|
|
|
275
|
|
|
/** |
276
|
|
|
* @throws LogicException |
277
|
|
|
* |
278
|
|
|
* @return void |
279
|
|
|
*/ |
280
|
|
|
protected function fillReduce() |
281
|
|
|
{ |
282
|
|
|
$this->clearVisited(); |
283
|
|
|
|
284
|
|
|
foreach ($this->states as $p) { |
285
|
|
|
/** @var Reduce[] $tmpr */ |
286
|
|
|
$tmpr = []; |
287
|
|
|
|
288
|
|
|
$tdefact = 0; |
289
|
|
|
foreach ($p->shifts as $t) { |
290
|
|
|
if ($t->through === $this->context->errorToken) { |
291
|
|
|
// shifting error |
292
|
|
|
$tdefact = -1; |
293
|
|
|
} |
294
|
|
|
} |
295
|
|
|
|
296
|
|
|
// Pick up reduce entries |
297
|
|
|
for ($x = $p->items; $x !== null; $x = $x->next) { |
298
|
|
|
if (!$x->isTailItem()) { |
299
|
|
|
continue; |
300
|
|
|
} |
301
|
|
|
|
302
|
|
|
$alook = clone $x->look; |
303
|
|
|
$gram = $x->item->getProduction(); |
304
|
|
|
|
305
|
|
|
// find shift/reduce conflict |
306
|
|
|
foreach ($p->shifts as $m => $t) { |
307
|
|
|
$e = $t->through; |
308
|
|
|
if (!$e->isTerminal) { |
309
|
|
|
break; |
310
|
|
|
} |
311
|
|
|
if ($alook->testBit($e->code)) { |
312
|
|
|
$rel = $this->comparePrecedence($gram, $e); |
313
|
|
|
if ($rel === self::NON_ASSOC) { |
314
|
|
|
$alook->clearBit($e->code); |
315
|
|
|
unset($p->shifts[$m]); |
316
|
|
|
$tmpr[] = new Reduce($e, -1); |
317
|
|
|
} elseif ($rel < 0) { |
318
|
|
|
// reduce |
319
|
|
|
unset($p->shifts[$m]); |
320
|
|
|
} elseif ($rel > 0) { |
321
|
|
|
// shift |
322
|
|
|
$alook->clearBit($e->code); |
323
|
|
|
} elseif ($rel == 0) { |
324
|
|
|
// conflict |
325
|
|
|
$alook->clearBit($e->code); |
326
|
|
|
$this->nsrerr++; |
327
|
|
|
$p->conflict = new Conflict\ShiftReduce($t, $gram->num, $e, $p->conflict); |
328
|
|
|
} |
329
|
|
|
} |
330
|
|
|
} |
331
|
|
|
|
332
|
|
|
foreach ($tmpr as $reduce) { |
333
|
|
|
if ($alook->testBit($reduce->symbol->code)) { |
334
|
|
|
// reduce/reduce conflict |
335
|
|
|
$this->nrrerr++; |
336
|
|
|
$p->conflict = new Conflict\ReduceReduce( |
337
|
|
|
$reduce->number, |
338
|
|
|
$gram->num, |
339
|
|
|
$reduce->symbol, |
340
|
|
|
$p->conflict |
341
|
|
|
); |
342
|
|
|
|
343
|
|
|
if ($gram->num < $reduce->number) { |
344
|
|
|
$reduce->number = $gram->num; |
345
|
|
|
} |
346
|
|
|
$alook->clearBit($reduce->symbol->code); |
347
|
|
|
} |
348
|
|
|
} |
349
|
|
|
|
350
|
|
|
foreach ($alook as $e) { |
351
|
|
|
$sym = $this->context->symbols[$e]; |
352
|
|
|
$tmpr[] = new Reduce($sym, $gram->num); |
353
|
|
|
} |
354
|
|
|
} |
355
|
|
|
|
356
|
|
|
// Decide default action |
357
|
|
|
if (!$tdefact) { |
358
|
|
|
$tdefact = -1; |
359
|
|
|
|
360
|
|
View Code Duplication |
Utils::stableSort($tmpr, function (Reduce $x, Reduce $y) { |
|
|
|
|
361
|
|
|
if ($x->number != $y->number) { |
362
|
|
|
return $y->number - $x->number; |
363
|
|
|
} |
364
|
|
|
|
365
|
|
|
return $x->symbol->code - $y->symbol->code; |
366
|
|
|
}); |
367
|
|
|
|
368
|
|
|
$maxn = 0; |
369
|
|
|
$nr = \count($tmpr); |
370
|
|
|
for ($j = 0; $j < $nr;) { |
371
|
|
|
for ($k = $j; $j < $nr; $j++) { |
372
|
|
|
if ($tmpr[$j]->number != $tmpr[$k]->number) { |
373
|
|
|
break; |
374
|
|
|
} |
375
|
|
|
} |
376
|
|
|
if ($j - $k > $maxn && $tmpr[$k]->number > 0) { |
377
|
|
|
$maxn = $j - $k; |
378
|
|
|
$tdefact = $tmpr[$k]->number; |
379
|
|
|
} |
380
|
|
|
} |
381
|
|
|
} |
382
|
|
|
|
383
|
|
|
// Squeeze tmpr |
384
|
|
|
$tmpr = \array_filter($tmpr, function (Reduce $reduce) use ($tdefact) { |
385
|
|
|
return $reduce->number !== $tdefact; |
386
|
|
|
}); |
387
|
|
|
|
388
|
|
View Code Duplication |
Utils::stableSort($tmpr, function (Reduce $x, Reduce $y) { |
|
|
|
|
389
|
|
|
if ($x->symbol !== $y->symbol) { |
390
|
|
|
return $x->symbol->code - $y->symbol->code; |
391
|
|
|
} |
392
|
|
|
|
393
|
|
|
return $x->number - $y->number; |
394
|
|
|
}); |
395
|
|
|
$tmpr[] = new Reduce($this->context->nilsymbol, $tdefact); |
396
|
|
|
|
397
|
|
|
// Squeeze shift actions (we deleted some keys) |
398
|
|
|
$p->shifts = \array_values($p->shifts); |
399
|
|
|
|
400
|
|
|
foreach ($tmpr as $reduce) { |
401
|
|
|
if ($reduce->number >= 0) { |
402
|
|
|
$this->visited[$reduce->number] = true; |
403
|
|
|
} |
404
|
|
|
} |
405
|
|
|
|
406
|
|
|
// Register tmpr |
407
|
|
|
$p->reduce = $tmpr; |
408
|
|
|
$this->countActions2 += \count($tmpr); |
409
|
|
|
} |
410
|
|
|
|
411
|
|
|
$k = 0; |
412
|
|
|
foreach ($this->context->grams as $gram) { |
413
|
|
|
if (!$this->visited[$gram->num]) { |
414
|
|
|
$k++; |
415
|
|
|
$this->context->debug("Never reduced: \n"); // TODO |
416
|
|
|
} |
417
|
|
|
} |
418
|
|
|
|
419
|
|
|
if ($k) { |
420
|
|
|
$this->context->debug($k." rule(s) never reduced\n"); |
421
|
|
|
} |
422
|
|
|
|
423
|
|
|
// Sort states in decreasing order of entries |
424
|
|
|
// do not move initial state |
425
|
|
|
$initState = \array_shift($this->states); |
426
|
|
|
|
427
|
|
|
Utils::stableSort($this->states, function (State $p, State $q) { |
428
|
|
|
$numReduces = count($p->reduce) - 1; // -1 for default action |
429
|
|
|
$pt = $numReduces; |
430
|
|
|
$pn = count($p->shifts) + $numReduces; |
431
|
|
|
foreach ($p->shifts as $x) { |
432
|
|
|
if ($x->through->isTerminal) { |
433
|
|
|
$pt++; |
434
|
|
|
} |
435
|
|
|
} |
436
|
|
|
|
437
|
|
|
$numReduces = \count($q->reduce) - 1; // -1 for default action |
438
|
|
|
$qt = $numReduces; |
439
|
|
|
$qn = \count($q->shifts) + $numReduces; |
440
|
|
|
foreach ($q->shifts as $x) { |
441
|
|
|
if ($x->through->isTerminal) { |
442
|
|
|
$qt++; |
443
|
|
|
} |
444
|
|
|
} |
445
|
|
|
|
446
|
|
|
if ($pt !== $qt) { |
447
|
|
|
return $qt - $pt; |
448
|
|
|
} |
449
|
|
|
|
450
|
|
|
return $qn - $pn; |
451
|
|
|
}); |
452
|
|
|
|
453
|
|
|
array_unshift($this->states, $initState); |
454
|
|
|
|
455
|
|
|
foreach ($this->states as $i => $p) { |
456
|
|
|
$p->number = $i; |
457
|
|
|
if (!empty($p->shifts) || !$p->reduce[0]->symbol->isNilSymbol()) { |
458
|
|
|
$this->countNonLeafStates = $i + 1; |
|
|
|
|
459
|
|
|
} |
460
|
|
|
} |
461
|
|
|
|
462
|
|
|
foreach ($this->states as $state) { |
463
|
|
|
$this->printState($state); |
464
|
|
|
} |
465
|
|
|
} |
466
|
|
|
|
467
|
|
|
/** |
468
|
|
|
* @param Production $gram |
469
|
|
|
* @param Symbol $x |
470
|
|
|
* |
471
|
|
|
* @throws LogicException |
472
|
|
|
* |
473
|
|
|
* @return int|mixed |
474
|
|
|
*/ |
475
|
|
|
protected function comparePrecedence(Production $gram, Symbol $x) |
476
|
|
|
{ |
477
|
|
|
if ($gram->associativity === Symbol::UNDEF |
478
|
|
|
|| ($x->associativity & Symbol::MASK) === Symbol::UNDEF |
479
|
|
|
) { |
480
|
|
|
return 0; |
481
|
|
|
} |
482
|
|
|
|
483
|
|
|
$v = $x->precedence - $gram->precedence; |
484
|
|
|
if ($v !== 0) { |
485
|
|
|
return $v; |
486
|
|
|
} |
487
|
|
|
|
488
|
|
|
switch ($gram->associativity) { |
489
|
|
|
case Symbol::LEFT: |
|
|
|
|
490
|
|
|
return -1; |
491
|
|
|
case Symbol::RIGHT: |
|
|
|
|
492
|
|
|
return 1; |
493
|
|
|
case Symbol::NON: |
494
|
|
|
return self::NON_ASSOC; |
495
|
|
|
} |
496
|
|
|
|
497
|
|
|
throw new LogicException('Gram has associativity other than LEFT/RIGHT/NON. This should never happen'); |
498
|
|
|
} |
499
|
|
|
|
500
|
|
|
/** |
501
|
|
|
* @param State $st |
502
|
|
|
*/ |
503
|
|
|
protected function computeFollow(State $st) |
504
|
|
|
{ |
505
|
|
|
foreach ($st->shifts as $t) { |
506
|
|
|
if (!$t->through->isTerminal) { |
507
|
|
|
$this->follow[$t->through->code] = clone $this->blank; |
508
|
|
View Code Duplication |
for ($x = $t->items; $x !== null && !$x->isHeadItem(); $x = $x->next) { |
|
|
|
|
509
|
|
|
$this->computeFirst($this->follow[$t->through->code], $x->item); |
510
|
|
|
} |
511
|
|
|
} |
512
|
|
|
} |
513
|
|
|
for ($x = $st->items; $x !== null; $x = $x->next) { |
514
|
|
|
/** @var Symbol $g */ |
515
|
|
|
$g = $x->item[0] ?? null; |
516
|
|
|
if ($g !== null && !$g->isTerminal && $this->isSeqNullable($x->item->slice(1))) { |
517
|
|
|
$this->follow[$g->code]->or($x->look); |
|
|
|
|
518
|
|
|
} |
519
|
|
|
} |
520
|
|
|
do { |
521
|
|
|
$changed = false; |
522
|
|
|
foreach ($st->shifts as $t) { |
523
|
|
|
if (!$t->through->isTerminal) { |
524
|
|
|
$p = $this->follow[$t->through->code]; |
525
|
|
|
for ($x = $t->items; $x !== null && !$x->isHeadItem(); $x = $x->next) { |
526
|
|
View Code Duplication |
if ($this->isSeqNullable($x->item) && $x->left != null) { |
|
|
|
|
527
|
|
|
$changed |= $p->or($this->follow[$x->left->code]); |
528
|
|
|
} |
529
|
|
|
} |
530
|
|
|
} |
531
|
|
|
} |
532
|
|
|
} while ($changed); |
533
|
|
|
} |
534
|
|
|
|
535
|
|
|
/** |
536
|
|
|
* @param BitSet $p |
537
|
|
|
* @param Item $item |
538
|
|
|
*/ |
539
|
|
|
protected function computeFirst(BitSet $p, Item $item) |
540
|
|
|
{ |
541
|
|
|
/** @var Symbol $g */ |
542
|
|
|
foreach ($item as $g) { |
543
|
|
|
if ($g->isTerminal) { |
544
|
|
|
$p->setBit($g->code); |
545
|
|
|
|
546
|
|
|
return; |
547
|
|
|
} |
548
|
|
|
$p->or($this->first[$g->code]); |
|
|
|
|
549
|
|
|
if (!$this->nullable[$g->code]) { |
550
|
|
|
return; |
551
|
|
|
} |
552
|
|
|
} |
553
|
|
|
} |
554
|
|
|
|
555
|
|
|
/** |
556
|
|
|
* @param Item $item |
557
|
|
|
* |
558
|
|
|
* @return bool |
559
|
|
|
*/ |
560
|
|
|
protected function isSeqNullable(Item $item) |
561
|
|
|
{ |
562
|
|
|
/** @var Symbol $g */ |
563
|
|
|
foreach ($item as $g) { |
564
|
|
|
if ($g->isTerminal || !$this->nullable[$g->code]) { |
565
|
|
|
return false; |
566
|
|
|
} |
567
|
|
|
} |
568
|
|
|
|
569
|
|
|
return true; |
570
|
|
|
} |
571
|
|
|
|
572
|
|
|
/** |
573
|
|
|
* @param Symbol $through |
574
|
|
|
* @param Lr1 $sublist |
575
|
|
|
* |
576
|
|
|
* @return State |
577
|
|
|
*/ |
578
|
|
|
protected function findOrCreateState(Symbol $through, Lr1 $sublist) |
579
|
|
|
{ |
580
|
|
|
foreach ($this->statesThrough[$through->code] as $state) { |
581
|
|
|
if (Utils::isSameSet($state->items, $sublist)) { |
582
|
|
|
return $state; |
583
|
|
|
} |
584
|
|
|
} |
585
|
|
|
|
586
|
|
|
$state = new State($through, $this->makeState($sublist)); |
587
|
|
|
$this->states[] = $state; |
588
|
|
|
$this->statesThrough[$through->code][] = $state; |
589
|
|
|
$this->countStates++; |
590
|
|
|
|
591
|
|
|
return $state; |
592
|
|
|
} |
593
|
|
|
|
594
|
|
|
/** |
595
|
|
|
* @return void |
596
|
|
|
*/ |
597
|
|
|
protected function computeEmpty() |
598
|
|
|
{ |
599
|
|
|
do { |
600
|
|
|
$changed = false; |
601
|
|
|
foreach ($this->context->grams as $gram) { |
602
|
|
|
|
603
|
|
|
/** @var Symbol $left */ |
604
|
|
|
$left = $gram->body[0]; |
605
|
|
|
$right = $gram->body[1] ?? null; |
606
|
|
|
if (($right === null || ($right->associativity & Production::EMPTY)) && !($left->associativity & Production::EMPTY)) { |
607
|
|
|
$left->setAssociativityFlag(Production::EMPTY); |
608
|
|
|
$changed = true; |
609
|
|
|
} |
610
|
|
|
} |
611
|
|
|
} while ($changed); |
612
|
|
|
|
613
|
|
|
if ($this->context->debug) { |
614
|
|
|
$this->context->debug('EMPTY nonterminals: '); |
615
|
|
|
foreach ($this->context->nonterminals as $symbol) { |
616
|
|
|
if ($symbol->associativity & Production::EMPTY) { |
617
|
|
|
$this->context->debug(' '.$symbol->name); |
618
|
|
|
} |
619
|
|
|
} |
620
|
|
|
$this->context->debug("\n"); |
621
|
|
|
} |
622
|
|
|
} |
623
|
|
|
|
624
|
|
|
/** |
625
|
|
|
* @return void |
626
|
|
|
*/ |
627
|
|
|
protected function firstNullablePrecomp() |
628
|
|
|
{ |
629
|
|
|
do { |
630
|
|
|
$changed = false; |
631
|
|
|
foreach ($this->context->grams as $gram) { |
632
|
|
|
$h = $gram->body[0]; |
633
|
|
|
for ($s = 1, $l = \count($gram->body); $s < $l; $s++) { |
634
|
|
|
$g = $gram->body[$s]; |
635
|
|
|
if ($g->isTerminal) { |
636
|
|
|
if (!$this->first[$h->code]->testBit($g->code)) { |
637
|
|
|
$changed = true; |
638
|
|
|
$this->first[$h->code]->setBit($g->code); |
639
|
|
|
} |
640
|
|
|
continue 2; |
641
|
|
|
} |
642
|
|
|
|
643
|
|
|
$changed |= $this->first[$h->code]->or($this->first[$g->code]); |
644
|
|
|
if (!$this->nullable[$g->code]) { |
645
|
|
|
continue 2; |
646
|
|
|
} |
647
|
|
|
} |
648
|
|
|
|
649
|
|
|
if (!$this->nullable[$h->code]) { |
650
|
|
|
$this->nullable[$h->code] = true; |
651
|
|
|
$changed = true; |
652
|
|
|
} |
653
|
|
|
} |
654
|
|
|
} while ($changed); |
655
|
|
|
|
656
|
|
|
if ($this->context->debug) { |
657
|
|
|
$this->context->debug("First:\n"); |
658
|
|
|
foreach ($this->context->nonterminals as $symbol) { |
659
|
|
|
$this->context->debug("{$symbol->name}\t[ "); |
660
|
|
|
$this->context->debug(Utils::dumpSet($this->context, $this->first[$symbol->code])); |
661
|
|
|
if ($this->nullable[$symbol->code]) { |
662
|
|
|
$this->context->debug('@ '); |
663
|
|
|
} |
664
|
|
|
$this->context->debug("]\n"); |
665
|
|
|
} |
666
|
|
|
} |
667
|
|
|
} |
668
|
|
|
|
669
|
|
|
/** |
670
|
|
|
* @param Lr1 $items |
671
|
|
|
* |
672
|
|
|
* @return Lr1 |
673
|
|
|
*/ |
674
|
|
|
protected function makeState(Lr1 $items): Lr1 |
675
|
|
|
{ |
676
|
|
|
$tail = null; |
677
|
|
|
for ($p = $items; $p !== null; $p = $p->next) { |
678
|
|
|
$p->look = null; |
679
|
|
|
if ($p->left !== null) { |
680
|
|
|
for ($q = $items; $q !== $p; $q = $q->next) { |
681
|
|
|
if ($q->left === $p->left) { |
682
|
|
|
$p->look = $q->look; |
683
|
|
|
break; |
684
|
|
|
} |
685
|
|
|
} |
686
|
|
|
} |
687
|
|
|
if ($p->look === null) { |
688
|
|
|
$p->look = clone $this->blank; |
689
|
|
|
$this->countLooks++; |
690
|
|
|
} |
691
|
|
|
$tail = $p; |
692
|
|
|
} |
693
|
|
|
$this->clearVisited(); |
694
|
|
|
for ($p = $items; $p !== null; $p = $p->next) { |
695
|
|
|
/** @var Symbol $g */ |
696
|
|
|
$g = $p->item[0] ?? null; |
697
|
|
|
if ($g !== null && !$g->isTerminal) { |
698
|
|
|
$tail = $this->findEmpty($tail, $g); |
|
|
|
|
699
|
|
|
} |
700
|
|
|
} |
701
|
|
|
|
702
|
|
|
return $items; |
703
|
|
|
} |
704
|
|
|
|
705
|
|
|
/** |
706
|
|
|
* @return void |
707
|
|
|
*/ |
708
|
|
|
protected function clearVisited() |
709
|
|
|
{ |
710
|
|
|
$nSymbols = $this->context->countSymbols; |
711
|
|
|
$nGrams = $this->context->countGrams; |
712
|
|
|
$this->visited = array_fill(0, max($nSymbols, $nGrams), false); |
713
|
|
|
} |
714
|
|
|
|
715
|
|
|
/** |
716
|
|
|
* @param Lr1 $tail |
717
|
|
|
* @param Symbol $x |
718
|
|
|
* |
719
|
|
|
* @return Lr1 |
720
|
|
|
*/ |
721
|
|
|
protected function findEmpty(Lr1 $tail, Symbol $x): Lr1 |
722
|
|
|
{ |
723
|
|
|
if (!$this->visited[$x->code] && ($x->associativity & Production::EMPTY)) { |
724
|
|
|
$this->visited[$x->code] = true; |
725
|
|
|
|
726
|
|
|
/** @var Production $gram */ |
727
|
|
|
for ($gram = $x->value; $gram !== null; $gram = $gram->link) { |
728
|
|
|
if ($gram->isEmpty()) { |
729
|
|
|
$p = new Lr1(null, clone $this->blank, new Item($gram, 1)); |
730
|
|
|
$tail->next = $p; |
731
|
|
|
$tail = $p; |
732
|
|
|
$this->countLooks++; |
733
|
|
|
} elseif (!$gram->body[1]->isTerminal) { |
734
|
|
|
$tail = $this->findEmpty($tail, $gram->body[1]); |
735
|
|
|
} |
736
|
|
|
} |
737
|
|
|
} |
738
|
|
|
|
739
|
|
|
return $tail; |
740
|
|
|
} |
741
|
|
|
|
742
|
|
|
/** |
743
|
|
|
* @param Lr1|null $list |
744
|
|
|
* @param callable $cmp |
745
|
|
|
* |
746
|
|
|
* @return mixed|null|Lr1 |
747
|
|
|
*/ |
748
|
|
|
protected function sortList(Lr1 $list = null, callable $cmp) |
749
|
|
|
{ |
750
|
|
|
$array = []; |
751
|
|
|
for ($x = $list; $x !== null; $x = $x->next) { |
752
|
|
|
$array[] = $x; |
753
|
|
|
} |
754
|
|
|
|
755
|
|
|
Utils::stableSort($array, $cmp); |
756
|
|
|
|
757
|
|
|
$list = null; |
758
|
|
|
/** @var Lr1 $tail */ |
759
|
|
|
$tail = null; |
760
|
|
|
foreach ($array as $x) { |
761
|
|
|
if ($list == null) { |
762
|
|
|
$list = $x; |
763
|
|
|
} else { |
764
|
|
|
$tail->next = $x; |
765
|
|
|
} |
766
|
|
|
$tail = $x; |
767
|
|
|
$x->next = null; |
768
|
|
|
} |
769
|
|
|
|
770
|
|
|
return $list; |
771
|
|
|
} |
772
|
|
|
|
773
|
|
|
/** |
774
|
|
|
* @param State $state |
775
|
|
|
*/ |
776
|
|
|
protected function printState(State $state) |
777
|
|
|
{ |
778
|
|
|
$this->context->debug('state '.$state->number."\n"); |
779
|
|
|
for ($conf = $state->conflict; $conf !== null; $conf = $conf->next()) { |
780
|
|
|
if ($conf instanceof ShiftReduce) { |
781
|
|
|
$this->context->debug(sprintf( |
782
|
|
|
"%d: shift/reduce conflict (shift %d, reduce %d) on %s\n", |
783
|
|
|
$state->number, |
784
|
|
|
$conf->state()->number, |
785
|
|
|
$conf->reduce(), |
786
|
|
|
$conf->symbol()->name |
787
|
|
|
)); |
788
|
|
|
} elseif ($conf instanceof ReduceReduce) { |
789
|
|
|
$this->context->debug(sprintf( |
790
|
|
|
"%d: reduce/reduce conflict (reduce %d, reduce %d) on %s\n", |
791
|
|
|
$state->number, |
792
|
|
|
$conf->reduce1(), |
793
|
|
|
$conf->reduce2(), |
794
|
|
|
$conf->symbol()->name |
795
|
|
|
)); |
796
|
|
|
} |
797
|
|
|
} |
798
|
|
|
|
799
|
|
|
for ($x = $state->items; $x !== null; $x = $x->next) { |
800
|
|
|
$this->context->debug("\t".\trim((string) $x->item)."\n"); |
801
|
|
|
} |
802
|
|
|
$this->context->debug("\n"); |
803
|
|
|
|
804
|
|
|
$i = $j = 0; |
805
|
|
|
while (true) { |
806
|
|
|
$s = $state->shifts[$i] ?? null; |
807
|
|
|
$r = $state->reduce[$j] ?? null; |
808
|
|
|
if ($s === null && $r === null) { |
809
|
|
|
break; |
810
|
|
|
} |
811
|
|
|
|
812
|
|
|
if ($s !== null && ($r === null || $s->through->code < $r->symbol->code)) { |
813
|
|
|
$str = $s->through->name; |
814
|
|
|
$this->context->debug(strlen($str) < 8 ? "\t$str\t\t" : "\t$str\t"); |
815
|
|
|
$this->context->debug($s->through->isTerminal ? 'shift' : 'goto'); |
816
|
|
|
$this->context->debug(' '.$s->number); |
817
|
|
|
if ($s->isReduceOnly()) { |
818
|
|
|
$this->context->debug(' and reduce ('.$s->reduce[0]->number.')'); |
819
|
|
|
} |
820
|
|
|
$this->context->debug("\n"); |
821
|
|
|
$i++; |
822
|
|
|
} else { |
823
|
|
|
$str = $r->symbol->isNilSymbol() ? '.' : $r->symbol->name; |
824
|
|
|
$this->context->debug(strlen($str) < 8 ? "\t$str\t\t" : "\t$str\t"); |
825
|
|
|
if ($r->number === 0) { |
826
|
|
|
$this->context->debug("accept\n"); |
827
|
|
|
} elseif ($r->number < 0) { |
828
|
|
|
$this->context->debug("error\n"); |
829
|
|
|
} else { |
830
|
|
|
$this->context->debug("reduce ($r->number)\n"); |
831
|
|
|
} |
832
|
|
|
$j++; |
833
|
|
|
} |
834
|
|
|
} |
835
|
|
|
$this->context->debug("\n"); |
836
|
|
|
} |
837
|
|
|
|
838
|
|
|
/** |
839
|
|
|
* @return void |
840
|
|
|
*/ |
841
|
|
|
protected function printDiagnostics() |
842
|
|
|
{ |
843
|
|
|
// TODO check expected_srconf |
844
|
|
|
$expected_srconf = 0; |
845
|
|
|
if ($this->nsrerr !== $expected_srconf || $this->nrrerr !== 0) { |
846
|
|
|
$this->context->debug("{$this->context->filename}: there are "); |
847
|
|
|
if ($this->nsrerr !== $expected_srconf) { |
848
|
|
|
$this->context->debug(" $this->nsrerr shift/reduce"); |
849
|
|
|
if ($this->nrrerr !== 0) { |
850
|
|
|
$this->context->debug(' and'); |
851
|
|
|
} |
852
|
|
|
} |
853
|
|
|
if ($this->nrrerr !== 0) { |
854
|
|
|
$this->context->debug(" $this->nrrerr reduce/reduce"); |
855
|
|
|
} |
856
|
|
|
$this->context->debug(" conflicts\n"); |
857
|
|
|
} |
858
|
|
|
} |
859
|
|
|
|
860
|
|
|
/** |
861
|
|
|
* @return void |
862
|
|
|
*/ |
863
|
|
|
protected function printStatistics() |
864
|
|
|
{ |
865
|
|
|
if (!$this->context->debug) { |
866
|
|
|
return; |
867
|
|
|
} |
868
|
|
|
|
869
|
|
|
$nterms = iterator_count($this->context->terminals); |
870
|
|
|
$nnonts = iterator_count($this->context->nonterminals); |
871
|
|
|
|
872
|
|
|
$nprods = $this->context->countGrams; |
873
|
|
|
$totalActs = $this->countActions + $this->countActions2; |
874
|
|
|
|
875
|
|
|
$this->context->debug("\nStatistics for {$this->context->filename}:\n"); |
876
|
|
|
$this->context->debug("\t$nterms terminal symbols\n"); |
877
|
|
|
$this->context->debug("\t$nnonts nonterminal symbols\n"); |
878
|
|
|
$this->context->debug("\t$nprods productions\n"); |
879
|
|
|
$this->context->debug("\t$this->countStates states\n"); |
880
|
|
|
$this->context->debug("\t$this->countNonLeafStates non leaf states\n"); |
881
|
|
|
$this->context->debug("\t$this->nsrerr shift/reduce, $this->nrrerr reduce/reduce conflicts\n"); |
882
|
|
|
// items? |
883
|
|
|
$this->context->debug("\t$this->countLooks lookahead sets used\n"); |
884
|
|
|
$this->context->debug("\t$this->countActions+$this->countActions2=$totalActs action entries\n"); |
885
|
|
|
// bytes used? |
886
|
|
|
} |
887
|
|
|
} |
888
|
|
|
|
Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.
You can also find more detailed suggestions in the “Code” section of your repository.