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

1<?php
2
3declare(strict_types=1);
4
5namespace Tests\Unit\Compiler;
6
7use LemonParser\Ast\CodeBlock;
8use LemonParser\Ast\Declaration\ArgumentForm;
9use LemonParser\Ast\Declaration\Declaration;
10use LemonParser\Ast\Declaration\Destructor;
11use LemonParser\Ast\Declaration\Directive;
12use LemonParser\Ast\Declaration\DirectiveKeyword;
13use LemonParser\Ast\Declaration\Fallback;
14use LemonParser\Ast\Declaration\PrecedenceDeclaration;
15use LemonParser\Ast\Declaration\TokenClass;
16use LemonParser\Ast\Declaration\TokenDeclaration;
17use LemonParser\Ast\Declaration\TypeDeclaration;
18use LemonParser\Ast\Declaration\Wildcard;
19use LemonParser\Ast\GrammarFile;
20use LemonParser\Ast\Location;
21use LemonParser\Ast\RhsItem;
22use LemonParser\Ast\Symbol;
23use LemonParser\Parser;
24use LemonParser\Preprocessor\Condition;
25use LemonParser\Preprocessor\Exclusion;
26use LemonParser\Preprocessor\Preprocessor;
27use LemonParser\Printer\DeclarationPrinter;
28use LemonParser\Printer\Printer;
29use LemonParser\Printer\RulePrinter;
30use LemonParser\Scanner\CodeReader;
31use LemonParser\Scanner\Cursor;
32use LemonParser\Scanner\Scanner;
33use LemonParser\Scanner\Token;
34use LemonParser\Scanner\TokenKind;
35use LemonParser\Syntax\DeclarationReader;
36use LemonParser\Syntax\GrammarReader;
37use LemonParser\Syntax\RuleReader;
38use LemonParser\Syntax\SymbolListReader;
39use LemonParser\Syntax\SymbolRegistry;
40use LemonParser\Syntax\TokenStream;
41use LemonParser\SyntaxException;
42use PHPUnit\Framework\Attributes\CoversClass;
43use PHPUnit\Framework\Attributes\Small;
44use PHPUnit\Framework\Attributes\UsesClass;
45use PHPUnit\Framework\TestCase;
46use SqlParser\Compiler\LemonGrammarReader;
47use SqlParser\Grammar\Associativity;
48use SqlParser\Grammar\Grammar;
49use SqlParser\Grammar\GrammarBuilder;
50use SqlParser\Grammar\Precedence;
51use SqlParser\Grammar\PrecedencePolicy;
52use SqlParser\Grammar\Rule;
53use SqlParser\Grammar\SymbolTable;
54
55#[CoversClass(LemonGrammarReader::class)]
56#[UsesClass(CodeBlock::class)]
57#[UsesClass(ArgumentForm::class)]
58#[UsesClass(\LemonParser\Ast\Declaration\Associativity::class)]
59#[UsesClass(Declaration::class)]
60#[UsesClass(Destructor::class)]
61#[UsesClass(Directive::class)]
62#[UsesClass(DirectiveKeyword::class)]
63#[UsesClass(Fallback::class)]
64#[UsesClass(PrecedenceDeclaration::class)]
65#[UsesClass(TokenClass::class)]
66#[UsesClass(TokenDeclaration::class)]
67#[UsesClass(TypeDeclaration::class)]
68#[UsesClass(Wildcard::class)]
69#[UsesClass(GrammarFile::class)]
70#[UsesClass(Location::class)]
71#[UsesClass(RhsItem::class)]
72#[UsesClass(\LemonParser\Ast\Rule::class)]
73#[UsesClass(Symbol::class)]
74#[UsesClass(Parser::class)]
75#[UsesClass(Condition::class)]
76#[UsesClass(Exclusion::class)]
77#[UsesClass(Preprocessor::class)]
78#[UsesClass(DeclarationPrinter::class)]
79#[UsesClass(Printer::class)]
80#[UsesClass(RulePrinter::class)]
81#[UsesClass(CodeReader::class)]
82#[UsesClass(Cursor::class)]
83#[UsesClass(Scanner::class)]
84#[UsesClass(Token::class)]
85#[UsesClass(TokenKind::class)]
86#[UsesClass(SyntaxException::class)]
87#[UsesClass(DeclarationReader::class)]
88#[UsesClass(GrammarReader::class)]
89#[UsesClass(RuleReader::class)]
90#[UsesClass(SymbolListReader::class)]
91#[UsesClass(SymbolRegistry::class)]
92#[UsesClass(TokenStream::class)]
93#[UsesClass(Associativity::class)]
94#[UsesClass(Grammar::class)]
95#[UsesClass(GrammarBuilder::class)]
96#[UsesClass(Precedence::class)]
97#[UsesClass(PrecedencePolicy::class)]
98#[UsesClass(Rule::class)]
99#[UsesClass(SymbolTable::class)]
100#[Small]
101final class LemonGrammarReaderTest extends TestCase
102{
103    public function testRead(): void
104    {
105        $grammar = (new LemonGrammarReader())->read("%token_prefix TK_\n%token SEMI.\n%left PLUS MINUS.\n%fallback ID ABORT AFTER.\n%wildcard ANY.\n%token_class id ID|INDEXED.\n%start_symbol input\nexpr(A) ::= expr(B) PLUS|MINUS expr(C). [PLUS] { A = B + C; }\nexpr ::= NUM.\ninput ::= expr SEMI.\n");
106
107        self::assertSame('input', $grammar->symbols->name($grammar->startSymbol()));
108        self::assertSame(PrecedencePolicy::FirstRankedTerminal, $grammar->policy);
109        self::assertSame(['$accept', 'expr', 'expr', 'input'], array_map(static fn (Rule $rule): string => $grammar->symbols->name($rule->lhs), $grammar->rules));
110        self::assertSame(['expr', 'PLUS|MINUS', 'expr'], array_map(static fn (int $id): string => $grammar->symbols->name($id), $grammar->rules[1]->rhs));
111        self::assertSame('PLUS', $grammar->symbols->name($grammar->rules[1]->precedenceSymbol ?? -1));
112        self::assertSame('ANY', $grammar->symbols->name($grammar->wildcard ?? -1));
113        self::assertCount(2, $grammar->tokenClasses);
114        self::assertCount(2, $grammar->fallbacks);
115        self::assertSame(Associativity::Left, $grammar->precedenceOf($grammar->symbols->id('MINUS') ?? -1)?->associativity);
116    }
117
118    public function testReadSettlesConditionalRegions(): void
119    {
120        $source = "%ifndef OMIT\ncmd ::= EXTRA.\n%endif\ncmd ::= SELECT.\n";
121        $reader = new LemonGrammarReader();
122
123        self::assertCount(3, $reader->read($source)->rules);
124        self::assertCount(2, $reader->read($source, ['OMIT'])->rules);
125    }
126
127    public function testReadRejectsWhatLemonRejects(): void
128    {
129        $this->expectException(SyntaxException::class);
130
131        (new LemonGrammarReader())->read("expr ::= expr ? expr.\n");
132    }
133
134    public function testDeclaration(): void
135    {
136        $file = (new Parser())->parse("%token A.\n%right B.\n%fallback.\n%wildcard.\n%name Calc\n%start_symbol s\ns ::= A B.\n");
137        $reader = new LemonGrammarReader();
138        $builder = new GrammarBuilder();
139
140        $declarations = $file->declarations();
141
142        $reader->declaration($declarations[0], $builder);
143        $reader->declaration($declarations[1], $builder);
144        $reader->declaration($declarations[2], $builder);
145        $reader->declaration($declarations[3], $builder);
146        $reader->declaration($declarations[4], $builder);
147        $reader->declaration($declarations[5], $builder);
148        $builder->rule('s', ['A', 'B'], null);
149        $grammar = $builder->build();
150
151        self::assertTrue($builder->isTerminal('A'));
152        self::assertSame(Associativity::Right, $grammar->precedenceOf($grammar->symbols->id('B') ?? -1)?->associativity);
153        self::assertSame([], $grammar->fallbacks);
154        self::assertNull($grammar->wildcard);
155        self::assertSame('s', $grammar->symbols->name($grammar->startSymbol()));
156    }
157
158    public function testAssociativity(): void
159    {
160        $reader = new LemonGrammarReader();
161
162        self::assertSame(Associativity::Left, $reader->associativity(\LemonParser\Ast\Declaration\Associativity::Left));
163        self::assertSame(Associativity::Right, $reader->associativity(\LemonParser\Ast\Declaration\Associativity::Right));
164        self::assertSame(Associativity::NonAssoc, $reader->associativity(\LemonParser\Ast\Declaration\Associativity::NonAssoc));
165    }
166
167    public function testRule(): void
168    {
169        $file = (new Parser())->parse("s ::= A|B c A|B. [A]\nc ::= .\n");
170        $reader = new LemonGrammarReader();
171        $builder = new GrammarBuilder();
172
173        $reader->rule($file->rules()[0], $builder);
174        $reader->rule($file->rules()[1], $builder);
175        $grammar = $builder->build();
176
177        self::assertSame(['A|B', 'c', 'A|B'], array_map(static fn (int $id): string => $grammar->symbols->name($id), $grammar->rules[1]->rhs));
178        self::assertCount(1, $grammar->tokenClasses);
179        self::assertSame('A', $grammar->symbols->name($grammar->rules[1]->precedenceSymbol ?? -1));
180        self::assertSame([], $grammar->rules[2]->rhs);
181    }
182
183    public function testNames(): void
184    {
185        $symbols = [new Symbol('A', new Location(1, 1)), new Symbol('b', new Location(1, 3))];
186
187        self::assertSame(['A', 'b'], (new LemonGrammarReader())->names($symbols));
188    }
189}
190