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