packages/sql-parser/tests/Unit/Parser/LrParserTest.php

1<?php
2
3declare(strict_types=1);
4
5namespace Tests\Unit\Parser;
6
7use PHPUnit\Framework\Attributes\CoversClass;
8use PHPUnit\Framework\Attributes\Small;
9use PHPUnit\Framework\Attributes\UsesClass;
10use PHPUnit\Framework\TestCase;
11use SqlParser\Automaton\ParseTableBuilder;
12use SqlParser\Grammar\Associativity;
13use SqlParser\Grammar\GrammarBuilder;
14use SqlParser\Lexer\Token;
15use SqlParser\Parser\LrParser;
16use SqlParser\Parser\Node;
17use SqlParser\Parser\SyntaxException;
18
19#[CoversClass(LrParser::class)]
20#[UsesClass(Node::class)]
21#[UsesClass(SyntaxException::class)]
22#[UsesClass(Token::class)]
23#[UsesClass(GrammarBuilder::class)]
24#[UsesClass(ParseTableBuilder::class)]
25#[UsesClass(\SqlParser\Automaton\Bitset::class)]
26#[UsesClass(\SqlParser\Automaton\BuildResult::class)]
27#[UsesClass(\SqlParser\Automaton\ClosureIndex::class)]
28#[UsesClass(\SqlParser\Automaton\ConflictResolver::class)]
29#[UsesClass(\SqlParser\Automaton\ConflictSummary::class)]
30#[UsesClass(\SqlParser\Automaton\Digraph::class)]
31#[UsesClass(\SqlParser\Automaton\LookaheadSets::class)]
32#[UsesClass(\SqlParser\Automaton\Lr0Automaton::class)]
33#[UsesClass(\SqlParser\Automaton\Lr0Builder::class)]
34#[UsesClass(\SqlParser\Automaton\NullableSet::class)]
35#[UsesClass(\SqlParser\Automaton\ResolvedState::class)]
36#[UsesClass(\SqlParser\Grammar\Grammar::class)]
37#[UsesClass(\SqlParser\Grammar\Precedence::class)]
38#[UsesClass(\SqlParser\Grammar\Rule::class)]
39#[UsesClass(\SqlParser\Grammar\SymbolTable::class)]
40#[UsesClass(\SqlParser\Lexer\SourcePosition::class)]
41#[UsesClass(\SqlParser\Table\ActionCode::class)]
42#[UsesClass(\SqlParser\Table\ArrayRows::class)]
43#[UsesClass(\SqlParser\Table\ParseTable::class)]
44#[UsesClass(\SqlParser\Table\TableRule::class)]
45#[UsesClass(\SqlParser\Lexer\SourceException::class)]
46#[Small]
47final class LrParserTest extends TestCase
48{
49    public function testParseBuildsATreeNamedAfterTheRules(): void
50    {
51        $builder = new GrammarBuilder();
52        $builder->precedence(['+'], Associativity::Left);
53        $builder->precedence(['*'], Associativity::Left);
54        $builder->terminal('NUM');
55        $builder->rule('expr', ['expr', '+', 'expr']);
56        $builder->rule('expr', ['expr', '*', 'expr']);
57        $builder->rule('expr', ['NUM']);
58        $table = (new ParseTableBuilder())->build($builder->build())->table;
59        $symbols = $table->symbols;
60        $num = $symbols->id('NUM') ?? -1;
61        $sql = '1+2*3';
62        $tokens = [new Token($num, 'NUM', '1', 0), new Token($symbols->id('+') ?? -1, '+', '+', 1), new Token($num, 'NUM', '2', 2), new Token($symbols->id('*') ?? -1, '*', '*', 3), new Token($num, 'NUM', '3', 4), new Token(0, '$end', '', 5)];
63        $tree = (new LrParser($table))->parse($tokens, $sql);
64
65        self::assertSame('expr', $tree->name);
66        self::assertSame(0, $tree->ordinal);
67        self::assertSame('1', $tree->find('expr')[1]->text($sql));
68        self::assertSame('2*3', $tree->find('expr')[2]->text($sql));
69        self::assertSame($sql, $tree->text($sql));
70    }
71
72    public function testParseLeavesMidRuleActionsOutOfTheTree(): void
73    {
74        $builder = new GrammarBuilder();
75        $builder->terminal('A');
76        $builder->rule('$@1', [], null, true);
77        $builder->rule('s', ['A', '$@1', 'A']);
78        $table = (new ParseTableBuilder())->build($builder->build())->table;
79        $a = $table->symbols->id('A') ?? -1;
80        $tree = (new LrParser($table))->parse([new Token($a, 'A', 'a', 0), new Token($a, 'A', 'a', 1), new Token(0, '$end', '', 2)], 'aa');
81
82        self::assertCount(2, $tree->children);
83        self::assertSame('aa', $tree->text('aa'));
84    }
85
86    public function testParseGivesTheTreeTheTextThatFollowsTheLastToken(): void
87    {
88        $builder = new GrammarBuilder();
89        $builder->terminal('NUM');
90        $builder->rule('expr', ['NUM']);
91        $table = (new ParseTableBuilder())->build($builder->build())->table;
92        $sql = " 1  -- done\n";
93        $tokens = [new Token($table->symbols->id('NUM') ?? -1, 'NUM', '1', 1, ' '), new Token(0, '$end', '', 12, "  -- done\n")];
94        $tree = (new LrParser($table))->parse($tokens, $sql);
95
96        self::assertSame("  -- done\n", $tree->trailing);
97        self::assertSame($sql, $tree->toString());
98    }
99
100    public function testParseRejectsAnUnexpectedToken(): void
101    {
102        $builder = new GrammarBuilder();
103        $builder->terminal('A');
104        $builder->terminal('B');
105        $builder->rule('s', ['A', 'B']);
106        $table = (new ParseTableBuilder())->build($builder->build())->table;
107        $a = $table->symbols->id('A') ?? -1;
108
109        $this->expectException(SyntaxException::class);
110        $this->expectExceptionMessage("Unexpected 'a' at line 1, column 2, expected B");
111
112        (new LrParser($table))->parse([new Token($a, 'A', 'a', 0), new Token($a, 'A', 'a', 1), new Token(0, '$end', '', 2)], 'aa');
113    }
114
115    public function testParseSuppliesTheEndMarkerWhenTheStreamLacksIt(): void
116    {
117        $builder = new GrammarBuilder();
118        $builder->terminal('A');
119        $builder->rule('s', ['A']);
120        $builder->rule('s', []);
121        $table = (new ParseTableBuilder())->build($builder->build())->table;
122
123        self::assertTrue((new LrParser($table))->parse([], '')->isEmpty());
124        self::assertSame(1, (new LrParser($table))->parse([new Token($table->symbols->id('A') ?? -1, 'A', 'a', 0)], 'a')->children === [] ? 0 : 1);
125    }
126
127    public function testExpected(): void
128    {
129        $builder = new GrammarBuilder();
130        $builder->terminal('A');
131        $builder->terminal('B');
132        $builder->rule('s', ['A']);
133        $builder->rule('s', ['B']);
134        $table = (new ParseTableBuilder())->build($builder->build())->table;
135
136        self::assertSame(['A', 'B'], (new LrParser($table))->expected(0));
137    }
138}
139