packages/sql-faker/tests/Unit/Compiler/Bison/Rule/BisonAlternativeReaderTest.php
1<?php
2
3declare(strict_types=1);
4
5namespace Tests\Unit\SqlFaker\Compiler\Bison\Rule;
6
7use PHPUnit\Framework\Attributes\CoversClass;
8use PHPUnit\Framework\Attributes\UsesClass;
9use PHPUnit\Framework\TestCase;
10use SqlFaker\Compiler\Bison\Ast\BisonAlternativeNode;
11use SqlFaker\Compiler\Bison\Ast\BisonSymbolForm;
12use SqlFaker\Compiler\Bison\Ast\BisonSymbolNode;
13use SqlFaker\Compiler\Bison\Lexer\ActionScanner;
14use SqlFaker\Compiler\Bison\Lexer\BisonLexeme;
15use SqlFaker\Compiler\Bison\Lexer\BisonLexer;
16use SqlFaker\Compiler\Bison\Lexer\BisonScannerChain;
17use SqlFaker\Compiler\Bison\Lexer\BisonToken;
18use SqlFaker\Compiler\Bison\Lexer\BisonTokenStream;
19use SqlFaker\Compiler\Bison\Lexer\BisonTrivia;
20use SqlFaker\Compiler\Bison\Lexer\DirectiveScanner;
21use SqlFaker\Compiler\Bison\Lexer\IdentifierScanner;
22use SqlFaker\Compiler\Bison\Lexer\NumberScanner;
23use SqlFaker\Compiler\Bison\Lexer\PunctuationScanner;
24use SqlFaker\Compiler\Bison\Lexer\QuotedLiteralScanner;
25use SqlFaker\Compiler\Bison\Lexer\SourceCursor;
26use SqlFaker\Compiler\Bison\Lexer\TypeTagScanner;
27use SqlFaker\Compiler\Bison\Rule\BisonAlternativeDraft;
28use SqlFaker\Compiler\Bison\Rule\BisonAlternativeReader;
29
30#[CoversClass(BisonAlternativeReader::class)]
31#[UsesClass(BisonAlternativeDraft::class)]
32#[UsesClass(BisonAlternativeNode::class)]
33#[UsesClass(BisonLexeme::class)]
34#[UsesClass(BisonLexer::class)]
35#[UsesClass(BisonScannerChain::class)]
36#[UsesClass(BisonSymbolNode::class)]
37#[UsesClass(BisonToken::class)]
38#[UsesClass(BisonTokenStream::class)]
39#[UsesClass(BisonTrivia::class)]
40#[UsesClass(SourceCursor::class)]
41#[UsesClass(ActionScanner::class)]
42#[UsesClass(DirectiveScanner::class)]
43#[UsesClass(IdentifierScanner::class)]
44#[UsesClass(NumberScanner::class)]
45#[UsesClass(PunctuationScanner::class)]
46#[UsesClass(QuotedLiteralScanner::class)]
47#[UsesClass(TypeTagScanner::class)]
48#[UsesClass(BisonSymbolForm::class)]
49#[UsesClass(\SqlFaker\Compiler\GrammarParseException::class)]
50final class BisonAlternativeReaderTest extends TestCase
51{
52 public function testReadAllTakesTheSymbolsUpToTheSemicolon(): void
53 {
54 $alternatives = (new BisonAlternativeReader())->readAll(BisonTokenStream::over('a b ;'));
55
56 self::assertCount(1, $alternatives);
57 self::assertSame(
58 ['a', 'b'],
59 array_map(static fn (BisonSymbolNode $symbol): string => $symbol->value, $alternatives[0]->symbols),
60 );
61 }
62
63 public function testReadAllSplitsAlternativesOnThePipe(): void
64 {
65 $alternatives = (new BisonAlternativeReader())->readAll(BisonTokenStream::over('a | b | ;'));
66
67 self::assertCount(3, $alternatives);
68 self::assertCount(1, $alternatives[0]->symbols);
69 self::assertCount(1, $alternatives[1]->symbols);
70 self::assertSame([], $alternatives[2]->symbols);
71 }
72
73 public function testReadAllStopsWhereTheNextRuleBegins(): void
74 {
75 $stream = BisonTokenStream::over('a b next_rule : c ;');
76
77 $alternatives = (new BisonAlternativeReader())->readAll($stream);
78
79 self::assertCount(1, $alternatives);
80 self::assertCount(2, $alternatives[0]->symbols);
81 self::assertSame('next_rule', $stream->next()->value);
82 }
83
84 public function testReadAllStopsAtTheEndOfTheSection(): void
85 {
86 $alternatives = (new BisonAlternativeReader())->readAll(BisonTokenStream::over('a b'));
87
88 self::assertCount(1, $alternatives);
89 }
90
91 public function testReadAllKeepsEachAlternativesOwnAction(): void
92 {
93 $alternatives = (new BisonAlternativeReader())
94 ->readAll(BisonTokenStream::over('a { first } | b { second } ;'));
95
96 self::assertSame(' first ', $alternatives[0]->action);
97 self::assertSame(' second ', $alternatives[1]->action);
98 }
99
100 public function testReadAllDoesNotCarryAnActionIntoTheNextAlternative(): void
101 {
102 $alternatives = (new BisonAlternativeReader())->readAll(BisonTokenStream::over('a { only } | b ;'));
103
104 self::assertSame(' only ', $alternatives[0]->action);
105 self::assertNull($alternatives[1]->action);
106 }
107
108 public function testEndsTheRuleReportsTheEndOfTheSection(): void
109 {
110 self::assertTrue((new BisonAlternativeReader())->endsTheRule(BisonTokenStream::over('')));
111 self::assertTrue((new BisonAlternativeReader())->endsTheRule(BisonTokenStream::over('%%')));
112 }
113
114 public function testEndsTheRuleReportsTheStartOfTheNextRule(): void
115 {
116 self::assertTrue((new BisonAlternativeReader())->endsTheRule(BisonTokenStream::over('next :')));
117 }
118
119 public function testEndsTheRuleTreatsALoneNameAsPartOfTheAlternative(): void
120 {
121 self::assertFalse((new BisonAlternativeReader())->endsTheRule(BisonTokenStream::over('a b')));
122 }
123
124 public function testEndsTheRuleConsumesNothing(): void
125 {
126 $stream = BisonTokenStream::over('next :');
127
128 (new BisonAlternativeReader())->endsTheRule($stream);
129
130 self::assertSame('next', $stream->next()->value);
131 }
132
133 public function testReadPartAddsANamedSymbol(): void
134 {
135 $draft = new BisonAlternativeDraft();
136
137 (new BisonAlternativeReader())->readPart(BisonTokenStream::over('expr'), $draft);
138
139 self::assertSame(BisonSymbolForm::Identifier, $draft->complete()->symbols[0]->type);
140 }
141
142 public function testReadPartAddsACharacterSymbol(): void
143 {
144 $draft = new BisonAlternativeDraft();
145
146 (new BisonAlternativeReader())->readPart(BisonTokenStream::over("'+'"), $draft);
147
148 self::assertSame(BisonSymbolForm::CharLiteral, $draft->complete()->symbols[0]->type);
149 }
150
151 public function testReadPartSkipsWhatBelongsToNoAlternative(): void
152 {
153 $draft = new BisonAlternativeDraft();
154 $stream = BisonTokenStream::over('42 expr');
155
156 (new BisonAlternativeReader())->readPart($stream, $draft);
157
158 self::assertSame([], $draft->complete()->symbols);
159 self::assertSame('expr', $stream->next()->value);
160 }
161
162 public function testReadInlineDirectiveAppliesPrecedence(): void
163 {
164 $draft = new BisonAlternativeDraft();
165
166 (new BisonAlternativeReader())->readInlineDirective(BisonTokenStream::over('%prec UMINUS'), $draft);
167
168 self::assertSame('UMINUS', $draft->complete()->prec);
169 }
170
171 public function testReadInlineDirectiveIgnoresADirectiveItDoesNotModel(): void
172 {
173 $draft = new BisonAlternativeDraft();
174
175 (new BisonAlternativeReader())->readInlineDirective(BisonTokenStream::over('%empty'), $draft);
176
177 self::assertNull($draft->complete()->prec);
178 }
179
180 public function testReadPrecedenceSymbolTakesANameOrACharacter(): void
181 {
182 $reader = new BisonAlternativeReader();
183
184 self::assertSame('UMINUS', $reader->readPrecedenceSymbol(BisonTokenStream::over('UMINUS')));
185 self::assertSame('+', $reader->readPrecedenceSymbol(BisonTokenStream::over("'+'")));
186 }
187
188 public function testReadPrecedenceSymbolReportsNothingWhenNoneIsNamed(): void
189 {
190 self::assertNull((new BisonAlternativeReader())->readPrecedenceSymbol(BisonTokenStream::over('42')));
191 }
192
193 public function testReadDynamicPrecedenceTakesTheRank(): void
194 {
195 self::assertSame(2, (new BisonAlternativeReader())->readDynamicPrecedence(BisonTokenStream::over('2')));
196 }
197
198 public function testReadDynamicPrecedenceReportsNothingWhenNoneIsNamed(): void
199 {
200 self::assertNull((new BisonAlternativeReader())->readDynamicPrecedence(BisonTokenStream::over('x')));
201 }
202
203 public function testReadMergeFunctionTakesTheTaggedName(): void
204 {
205 self::assertSame('merge', (new BisonAlternativeReader())->readMergeFunction(BisonTokenStream::over('<merge>')));
206 }
207
208 public function testReadMergeFunctionReportsNothingWhenNoneIsNamed(): void
209 {
210 self::assertNull((new BisonAlternativeReader())->readMergeFunction(BisonTokenStream::over('merge')));
211 }
212}
213