packages/sql-faker/tests/Unit/Compiler/Bison/Directive/BisonDirectiveReaderChainTest.php

1<?php
2
3declare(strict_types=1);
4
5namespace Tests\Unit\SqlFaker\Compiler\Bison\Directive;
6
7use PHPUnit\Framework\Attributes\CoversClass;
8use PHPUnit\Framework\Attributes\DataProvider;
9use PHPUnit\Framework\Attributes\UsesClass;
10use PHPUnit\Framework\TestCase;
11use SqlFaker\Compiler\Bison\Ast\BisonDeclaration;
12use SqlFaker\Compiler\Bison\Ast\BisonDefineDeclaration;
13use SqlFaker\Compiler\Bison\Ast\BisonExpectDeclaration;
14use SqlFaker\Compiler\Bison\Ast\BisonParamDeclaration;
15use SqlFaker\Compiler\Bison\Ast\BisonPrecedenceDeclaration;
16use SqlFaker\Compiler\Bison\Ast\BisonStartDeclaration;
17use SqlFaker\Compiler\Bison\Ast\BisonTokenDeclaration;
18use SqlFaker\Compiler\Bison\Ast\BisonTokenDefinition;
19use SqlFaker\Compiler\Bison\Ast\BisonTypeDeclaration;
20use SqlFaker\Compiler\Bison\Ast\BisonUnknownDeclaration;
21use SqlFaker\Compiler\Bison\Directive\BisonDeclarationBoundary;
22use SqlFaker\Compiler\Bison\Directive\BisonDirectiveReaderChain;
23use SqlFaker\Compiler\Bison\Directive\DefineDirectiveReader;
24use SqlFaker\Compiler\Bison\Directive\ExpectDirectiveReader;
25use SqlFaker\Compiler\Bison\Directive\ParamDirectiveReader;
26use SqlFaker\Compiler\Bison\Directive\PrecedenceDirectiveReader;
27use SqlFaker\Compiler\Bison\Directive\StartDirectiveReader;
28use SqlFaker\Compiler\Bison\Directive\TokenDirectiveReader;
29use SqlFaker\Compiler\Bison\Directive\TypeDirectiveReader;
30use SqlFaker\Compiler\Bison\Directive\UnknownDirectiveReader;
31use SqlFaker\Compiler\Bison\Lexer\ActionScanner;
32use SqlFaker\Compiler\Bison\Lexer\BisonLexeme;
33use SqlFaker\Compiler\Bison\Lexer\BisonLexer;
34use SqlFaker\Compiler\Bison\Lexer\BisonScannerChain;
35use SqlFaker\Compiler\Bison\Lexer\BisonToken;
36use SqlFaker\Compiler\Bison\Lexer\BisonTokenStream;
37use SqlFaker\Compiler\Bison\Lexer\BisonTrivia;
38use SqlFaker\Compiler\Bison\Lexer\DirectiveScanner;
39use SqlFaker\Compiler\Bison\Lexer\IdentifierScanner;
40use SqlFaker\Compiler\Bison\Lexer\NumberScanner;
41use SqlFaker\Compiler\Bison\Lexer\QuotedLiteralScanner;
42use SqlFaker\Compiler\Bison\Lexer\SourceCursor;
43use SqlFaker\Compiler\Bison\Lexer\TypeTagScanner;
44
45#[CoversClass(BisonDirectiveReaderChain::class)]
46#[UsesClass(ActionScanner::class)]
47#[UsesClass(BisonDeclarationBoundary::class)]
48#[UsesClass(BisonDefineDeclaration::class)]
49#[UsesClass(BisonExpectDeclaration::class)]
50#[UsesClass(BisonLexeme::class)]
51#[UsesClass(BisonLexer::class)]
52#[UsesClass(BisonParamDeclaration::class)]
53#[UsesClass(BisonPrecedenceDeclaration::class)]
54#[UsesClass(BisonScannerChain::class)]
55#[UsesClass(BisonStartDeclaration::class)]
56#[UsesClass(BisonToken::class)]
57#[UsesClass(BisonTokenDeclaration::class)]
58#[UsesClass(BisonTokenDefinition::class)]
59#[UsesClass(BisonTokenStream::class)]
60#[UsesClass(BisonTrivia::class)]
61#[UsesClass(BisonTypeDeclaration::class)]
62#[UsesClass(BisonUnknownDeclaration::class)]
63#[UsesClass(DefineDirectiveReader::class)]
64#[UsesClass(ExpectDirectiveReader::class)]
65#[UsesClass(ParamDirectiveReader::class)]
66#[UsesClass(PrecedenceDirectiveReader::class)]
67#[UsesClass(SourceCursor::class)]
68#[UsesClass(StartDirectiveReader::class)]
69#[UsesClass(TokenDirectiveReader::class)]
70#[UsesClass(TypeDirectiveReader::class)]
71#[UsesClass(UnknownDirectiveReader::class)]
72#[UsesClass(DirectiveScanner::class)]
73#[UsesClass(IdentifierScanner::class)]
74#[UsesClass(NumberScanner::class)]
75#[UsesClass(QuotedLiteralScanner::class)]
76#[UsesClass(TypeTagScanner::class)]
77#[UsesClass(\SqlFaker\Compiler\Bison\Lexer\PunctuationScanner::class)]
78#[UsesClass(\SqlFaker\Compiler\GrammarParseException::class)]
79final class BisonDirectiveReaderChainTest extends TestCase
80{
81    /**
82     * @param class-string<BisonDeclaration> $expected
83     */
84    #[DataProvider('providerDirective')]
85    public function testReadRoutesEachDirectiveToTheReaderThatKnowsIt(
86        string $directive,
87        string $arguments,
88        string $expected,
89    ): void {
90        $declaration = BisonDirectiveReaderChain::forBisonGrammar()
91            ->read(BisonTokenStream::over($arguments), $directive);
92
93        self::assertInstanceOf($expected, $declaration);
94    }
95
96    /**
97     * @return iterable<string, array{string, string, class-string<BisonDeclaration>}>
98     */
99    public static function providerDirective(): iterable
100    {
101        yield 'start' => ['%start', 'statement', BisonStartDeclaration::class];
102        yield 'token' => ['%token', 'IDENT', BisonTokenDeclaration::class];
103        yield 'type' => ['%type', '<num> expr', BisonTypeDeclaration::class];
104        yield 'left' => ['%left', 'OR_SYM', BisonPrecedenceDeclaration::class];
105        yield 'right' => ['%right', 'NOT_SYM', BisonPrecedenceDeclaration::class];
106        yield 'parse-param' => ['%parse-param', '{ THD *thd }', BisonParamDeclaration::class];
107        yield 'expect' => ['%expect', '3', BisonExpectDeclaration::class];
108        yield 'define' => ['%define', 'api.pure', BisonDefineDeclaration::class];
109        yield 'anything else' => ['%glr-parser', '', BisonUnknownDeclaration::class];
110    }
111
112    public function testReadFallsBackToTheUnknownReaderRatherThanDroppingADirective(): void
113    {
114        $declaration = BisonDirectiveReaderChain::forBisonGrammar()
115            ->read(BisonTokenStream::over('a b'), '%no-such-directive');
116
117        self::assertInstanceOf(BisonUnknownDeclaration::class, $declaration);
118        self::assertSame('a b', $declaration->content);
119    }
120
121    public function testReadDeclaresNothingWhenNoReaderClaimsTheDirective(): void
122    {
123        self::assertNull(
124            (new BisonDirectiveReaderChain([]))->read(BisonTokenStream::over('x'), '%start'),
125        );
126    }
127
128    public function testForBisonGrammarConsultsTheUnknownReaderLast(): void
129    {
130        $declaration = BisonDirectiveReaderChain::forBisonGrammar()
131            ->read(BisonTokenStream::over('statement'), '%start');
132
133        self::assertInstanceOf(BisonStartDeclaration::class, $declaration);
134    }
135}
136