packages/sql-parser/tests/Unit/Compiler/BisonGrammarReaderTest.php

1<?php
2
3declare(strict_types=1);
4
5namespace Tests\Unit\Compiler;
6
7use BisonParser\Ast\Declaration\Code;
8use BisonParser\Ast\Declaration\CodeProps;
9use BisonParser\Ast\Declaration\Declaration;
10use BisonParser\Ast\Declaration\Define;
11use BisonParser\Ast\Declaration\DefineForm;
12use BisonParser\Ast\Declaration\Expect;
13use BisonParser\Ast\Declaration\Flag;
14use BisonParser\Ast\Declaration\InitialAction;
15use BisonParser\Ast\Declaration\Option;
16use BisonParser\Ast\Declaration\Param;
17use BisonParser\Ast\Declaration\ParamKind;
18use BisonParser\Ast\Declaration\Prologue;
19use BisonParser\Ast\Declaration\Start;
20use BisonParser\Ast\Declaration\Symbols\Alias;
21use BisonParser\Ast\Declaration\Symbols\PrecedenceDeclaration;
22use BisonParser\Ast\Declaration\Symbols\SymbolClass;
23use BisonParser\Ast\Declaration\Symbols\SymbolDeclaration;
24use BisonParser\Ast\Declaration\Symbols\SymbolEntry;
25use BisonParser\Ast\Declaration\UnionDeclaration;
26use BisonParser\Ast\Epilogue;
27use BisonParser\Ast\GrammarFile;
28use BisonParser\Ast\Location;
29use BisonParser\Ast\Rule\Action;
30use BisonParser\Ast\Rule\Alternative;
31use BisonParser\Ast\Rule\DprecItem;
32use BisonParser\Ast\Rule\EmptyItem;
33use BisonParser\Ast\Rule\ExpectItem;
34use BisonParser\Ast\Rule\MergeItem;
35use BisonParser\Ast\Rule\PrecItem;
36use BisonParser\Ast\Rule\Predicate;
37use BisonParser\Ast\Rule\RhsItem;
38use BisonParser\Ast\Rule\SymbolItem;
39use BisonParser\Ast\Symbol;
40use BisonParser\Ast\SymbolKind;
41use BisonParser\Ast\Tag;
42use BisonParser\Parser;
43use BisonParser\Printer\DeclarationPrinter;
44use BisonParser\Printer\Printer;
45use BisonParser\Printer\RhsPrinter;
46use BisonParser\Printer\SymbolPrinter;
47use BisonParser\Scanner\CodeReader;
48use BisonParser\Scanner\Cursor;
49use BisonParser\Scanner\Directives;
50use BisonParser\Scanner\Escapes;
51use BisonParser\Scanner\Scanner;
52use BisonParser\Scanner\Token;
53use BisonParser\Scanner\TokenKind;
54use BisonParser\Syntax\DeclarationParser;
55use BisonParser\Syntax\GrammarParser;
56use BisonParser\Syntax\RuleParser;
57use BisonParser\Syntax\SymbolListParser;
58use BisonParser\Syntax\TokenStream;
59use BisonParser\SyntaxException;
60use PHPUnit\Framework\Attributes\CoversClass;
61use PHPUnit\Framework\Attributes\Small;
62use PHPUnit\Framework\Attributes\UsesClass;
63use PHPUnit\Framework\TestCase;
64use SqlParser\Compiler\BisonGrammarReader;
65use SqlParser\Grammar\Associativity;
66use SqlParser\Grammar\Grammar;
67use SqlParser\Grammar\GrammarBuilder;
68use SqlParser\Grammar\Precedence;
69use SqlParser\Grammar\PrecedencePolicy;
70use SqlParser\Grammar\Rule;
71use SqlParser\Grammar\SymbolTable;
72
73#[CoversClass(BisonGrammarReader::class)]
74#[UsesClass(Code::class)]
75#[UsesClass(CodeProps::class)]
76#[UsesClass(Declaration::class)]
77#[UsesClass(Define::class)]
78#[UsesClass(DefineForm::class)]
79#[UsesClass(Expect::class)]
80#[UsesClass(Flag::class)]
81#[UsesClass(InitialAction::class)]
82#[UsesClass(Option::class)]
83#[UsesClass(Param::class)]
84#[UsesClass(ParamKind::class)]
85#[UsesClass(Prologue::class)]
86#[UsesClass(Start::class)]
87#[UsesClass(Alias::class)]
88#[UsesClass(\BisonParser\Ast\Declaration\Symbols\Associativity::class)]
89#[UsesClass(PrecedenceDeclaration::class)]
90#[UsesClass(SymbolClass::class)]
91#[UsesClass(SymbolDeclaration::class)]
92#[UsesClass(SymbolEntry::class)]
93#[UsesClass(UnionDeclaration::class)]
94#[UsesClass(Epilogue::class)]
95#[UsesClass(GrammarFile::class)]
96#[UsesClass(Location::class)]
97#[UsesClass(Action::class)]
98#[UsesClass(Alternative::class)]
99#[UsesClass(DprecItem::class)]
100#[UsesClass(EmptyItem::class)]
101#[UsesClass(ExpectItem::class)]
102#[UsesClass(MergeItem::class)]
103#[UsesClass(PrecItem::class)]
104#[UsesClass(Predicate::class)]
105#[UsesClass(RhsItem::class)]
106#[UsesClass(\BisonParser\Ast\Rule\Rule::class)]
107#[UsesClass(SymbolItem::class)]
108#[UsesClass(Symbol::class)]
109#[UsesClass(SymbolKind::class)]
110#[UsesClass(Tag::class)]
111#[UsesClass(Parser::class)]
112#[UsesClass(DeclarationPrinter::class)]
113#[UsesClass(Printer::class)]
114#[UsesClass(RhsPrinter::class)]
115#[UsesClass(SymbolPrinter::class)]
116#[UsesClass(CodeReader::class)]
117#[UsesClass(Cursor::class)]
118#[UsesClass(Directives::class)]
119#[UsesClass(Escapes::class)]
120#[UsesClass(Scanner::class)]
121#[UsesClass(Token::class)]
122#[UsesClass(TokenKind::class)]
123#[UsesClass(SyntaxException::class)]
124#[UsesClass(DeclarationParser::class)]
125#[UsesClass(GrammarParser::class)]
126#[UsesClass(RuleParser::class)]
127#[UsesClass(SymbolListParser::class)]
128#[UsesClass(TokenStream::class)]
129#[UsesClass(Associativity::class)]
130#[UsesClass(Grammar::class)]
131#[UsesClass(GrammarBuilder::class)]
132#[UsesClass(Precedence::class)]
133#[UsesClass(PrecedencePolicy::class)]
134#[UsesClass(Rule::class)]
135#[UsesClass(SymbolTable::class)]
136#[Small]
137final class BisonGrammarReaderTest extends TestCase
138{
139    public function testRead(): void
140    {
141        $grammar = (new BisonGrammarReader())->read("%token NUM STR\n%left '+' '-'\n%right UMINUS\n%start expr\n%expect 1\n%%\nexpr: expr '+' expr | '-' expr %prec UMINUS | NUM { \$\$ = \$1; } | \"str\" ;\n");
142
143        self::assertSame('expr', $grammar->symbols->name($grammar->startSymbol()));
144        self::assertSame(1, $grammar->expectedConflicts);
145        self::assertSame(PrecedencePolicy::LastTerminal, $grammar->policy);
146        self::assertSame(['$accept', 'expr', 'expr', 'expr', 'expr'], array_map(static fn (Rule $rule): string => $grammar->symbols->name($rule->lhs), $grammar->rules));
147        self::assertSame(['-', 'expr'], array_map(static fn (int $id): string => $grammar->symbols->name($id), $grammar->rules[2]->rhs));
148        self::assertSame('UMINUS', $grammar->symbols->name($grammar->rules[2]->precedenceSymbol ?? -1));
149        self::assertSame('str', $grammar->symbols->name($grammar->rules[4]->rhs[0]));
150        self::assertNotNull($grammar->symbols->id('error'));
151        self::assertSame(Associativity::Right, $grammar->precedenceOf($grammar->symbols->id('UMINUS') ?? -1)?->associativity);
152    }
153
154    public function testReadNumbersMidRuleActionsBeforeTheirRule(): void
155    {
156        $grammar = (new BisonGrammarReader())->read("%token A B C\n%%\ns: A { one(); } B { two(); } C { last(); } | %empty ;\nt: s ;\n");
157
158        self::assertSame(['$accept', '$@1', '$@2', 's', 's', 't'], array_map(static fn (Rule $rule): string => $grammar->symbols->name($rule->lhs), $grammar->rules));
159        self::assertSame(['A', '$@1', 'B', '$@2', 'C'], array_map(static fn (int $id): string => $grammar->symbols->name($id), $grammar->rules[3]->rhs));
160        self::assertTrue($grammar->rules[1]->hidden);
161        self::assertSame([], $grammar->rules[4]->rhs);
162    }
163
164    public function testReadRejectsWhatBisonRejects(): void
165    {
166        $this->expectException(SyntaxException::class);
167
168        (new BisonGrammarReader())->read("%token A\nexpr: A;\n");
169    }
170
171    public function testDeclaration(): void
172    {
173        $file = (new Parser())->parse("%token <int> NUM 258 \"number\"\n%nonassoc EQ\n%precedence LOW\n%type <t> expr\n%start expr\n%expect-rr 2\n%expect 3\n%%\nexpr: NUM;\n");
174        $reader = new BisonGrammarReader();
175        $builder = new GrammarBuilder();
176        $builder->policy(PrecedencePolicy::LastTerminal);
177
178        $declarations = $file->allDeclarations();
179
180        $reader->declaration($declarations[0], $builder);
181        $reader->declaration($declarations[1], $builder);
182        $reader->declaration($declarations[2], $builder);
183        $reader->declaration($declarations[3], $builder);
184        $reader->declaration($declarations[4], $builder);
185        $reader->declaration($declarations[5], $builder);
186        $reader->declaration($declarations[6], $builder);
187        $builder->rule('other', ['NUM'], null);
188        $builder->rule('expr', ['NUM'], null);
189        $grammar = $builder->build();
190
191        self::assertTrue($builder->isTerminal('NUM'));
192        self::assertTrue($builder->isTerminal('EQ'));
193        self::assertSame(3, $grammar->expectedConflicts);
194        self::assertSame('expr', $grammar->symbols->name($grammar->startSymbol()));
195        self::assertSame(Associativity::Precedence, $grammar->precedenceOf($grammar->symbols->id('LOW') ?? -1)?->associativity);
196    }
197
198    public function testAssociativity(): void
199    {
200        $reader = new BisonGrammarReader();
201
202        self::assertSame(Associativity::Left, $reader->associativity(\BisonParser\Ast\Declaration\Symbols\Associativity::Left));
203        self::assertSame(Associativity::Right, $reader->associativity(\BisonParser\Ast\Declaration\Symbols\Associativity::Right));
204        self::assertSame(Associativity::NonAssoc, $reader->associativity(\BisonParser\Ast\Declaration\Symbols\Associativity::NonAssoc));
205        self::assertSame(Associativity::Precedence, $reader->associativity(\BisonParser\Ast\Declaration\Symbols\Associativity::Precedence));
206    }
207
208    public function testAlternative(): void
209    {
210        $file = (new Parser())->parse("%token A\n%%\ns: A[x] <int>{ mid(); } 'b' %dprec 2 %merge <m> %prec A { end(); } ;\n");
211        $reader = new BisonGrammarReader();
212        $builder = new GrammarBuilder();
213        $builder->terminal('A');
214
215        $reader->alternative('s', $file->rules()[0]->alternatives[0], $builder);
216        $grammar = $builder->build();
217
218        self::assertSame(['A', '$@1', 'b'], array_map(static fn (int $id): string => $grammar->symbols->name($id), $grammar->rules[2]->rhs));
219        self::assertSame('A', $grammar->symbols->name($grammar->rules[2]->precedenceSymbol ?? -1));
220        self::assertTrue($builder->isTerminal('b'));
221    }
222
223    public function testContinues(): void
224    {
225        $file = (new Parser())->parse("%token A\n%%\ns: { a } A { b } %prec A { c } ;\n");
226        $alternative = $file->rules()[0]->alternatives[0];
227        $reader = new BisonGrammarReader();
228
229        self::assertTrue($reader->continues($alternative, 0));
230        self::assertTrue($reader->continues($alternative, 1));
231        self::assertTrue($reader->continues($alternative, 2));
232        self::assertFalse($reader->continues($alternative, 4));
233    }
234
235    public function testContinuesLooksOnlyAtWhatFollowsTheItem(): void
236    {
237        $reader = new BisonGrammarReader();
238        $parser = new Parser();
239        $symbol = $parser->parse("%token A\n%%\ns: { a } A ;\n")->rules()[0]->alternatives[0];
240        $action = $parser->parse("%token A\n%%\ns: { a } { b } ;\n")->rules()[0]->alternatives[0];
241        $predicate = $parser->parse("%token A\n%%\ns: { a } %?{ p } ;\n")->rules()[0]->alternatives[0];
242        $modifiers = $parser->parse("%token A\n%%\ns: { a } %prec A %dprec 1 ;\n")->rules()[0]->alternatives[0];
243
244        self::assertTrue($reader->continues($symbol, 0));
245        self::assertTrue($reader->continues($action, 0));
246        self::assertTrue($reader->continues($predicate, 0));
247        self::assertFalse($reader->continues($modifiers, 0));
248        self::assertFalse($reader->continues($symbol, 1));
249    }
250
251    public function testReadNumbersMidRuleActionsFromOneForEveryFile(): void
252    {
253        $reader = new BisonGrammarReader();
254        $source = "%token A B\n%%\ns: A { act(); } B ;\n";
255
256        $reader->read($source);
257        $grammar = $reader->read($source);
258
259        self::assertSame('$@1', $grammar->symbols->name($grammar->rules[1]->lhs));
260    }
261
262    public function testMidRule(): void
263    {
264        $reader = new BisonGrammarReader();
265        $builder = new GrammarBuilder();
266
267        $first = $reader->midRule($builder);
268        $second = $reader->midRule($builder);
269        $builder->rule('s', [$first, $second], null);
270        $grammar = $builder->build();
271
272        self::assertSame(['$@1', '$@2'], [$first, $second]);
273        self::assertTrue($grammar->rules[1]->hidden);
274        self::assertSame([], $grammar->rules[1]->rhs);
275    }
276}
277