packages/sql-faker/tests/Unit/Compiler/Bison/Directive/PrecedenceDirectiveReaderTest.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\BisonPrecedenceDeclaration;
12use SqlFaker\Compiler\Bison\Directive\BisonDeclarationBoundary;
13use SqlFaker\Compiler\Bison\Directive\PrecedenceDirectiveReader;
14use SqlFaker\Compiler\Bison\Lexer\ActionScanner;
15use SqlFaker\Compiler\Bison\Lexer\BisonLexeme;
16use SqlFaker\Compiler\Bison\Lexer\BisonLexer;
17use SqlFaker\Compiler\Bison\Lexer\BisonScannerChain;
18use SqlFaker\Compiler\Bison\Lexer\BisonToken;
19use SqlFaker\Compiler\Bison\Lexer\BisonTokenStream;
20use SqlFaker\Compiler\Bison\Lexer\BisonTrivia;
21use SqlFaker\Compiler\Bison\Lexer\DirectiveScanner;
22use SqlFaker\Compiler\Bison\Lexer\IdentifierScanner;
23use SqlFaker\Compiler\Bison\Lexer\NumberScanner;
24use SqlFaker\Compiler\Bison\Lexer\QuotedLiteralScanner;
25use SqlFaker\Compiler\Bison\Lexer\SourceCursor;
26use SqlFaker\Compiler\Bison\Lexer\TypeTagScanner;
27
28#[CoversClass(PrecedenceDirectiveReader::class)]
29#[UsesClass(BisonDeclarationBoundary::class)]
30#[UsesClass(BisonLexeme::class)]
31#[UsesClass(BisonLexer::class)]
32#[UsesClass(BisonPrecedenceDeclaration::class)]
33#[UsesClass(BisonScannerChain::class)]
34#[UsesClass(BisonToken::class)]
35#[UsesClass(BisonTokenStream::class)]
36#[UsesClass(BisonTrivia::class)]
37#[UsesClass(SourceCursor::class)]
38#[UsesClass(ActionScanner::class)]
39#[UsesClass(DirectiveScanner::class)]
40#[UsesClass(IdentifierScanner::class)]
41#[UsesClass(NumberScanner::class)]
42#[UsesClass(QuotedLiteralScanner::class)]
43#[UsesClass(TypeTagScanner::class)]
44#[UsesClass(\SqlFaker\Compiler\Bison\Lexer\PunctuationScanner::class)]
45#[UsesClass(\SqlFaker\Compiler\GrammarParseException::class)]
46final class PrecedenceDirectiveReaderTest extends TestCase
47{
48    #[DataProvider('providerAssociativityDirective')]
49    public function testHandlesClaimsEveryAssociativityDirective(string $directive): void
50    {
51        self::assertTrue((new PrecedenceDirectiveReader())->handles($directive));
52    }
53
54    /**
55     * @return iterable<string, array{string}>
56     */
57    public static function providerAssociativityDirective(): iterable
58    {
59        yield 'left' => ['%left'];
60        yield 'right' => ['%right'];
61        yield 'nonassoc' => ['%nonassoc'];
62        yield 'precedence' => ['%precedence'];
63    }
64
65    public function testHandlesRejectsADirectiveThatDeclaresNoAssociativity(): void
66    {
67        self::assertFalse((new PrecedenceDirectiveReader())->handles('%token'));
68    }
69
70    #[DataProvider('providerAssociativityDirective')]
71    public function testReadTakesTheAssociativityFromTheDirectiveName(string $directive): void
72    {
73        $declaration = (new PrecedenceDirectiveReader())->read(BisonTokenStream::over('OR_SYM'), $directive);
74
75        self::assertInstanceOf(BisonPrecedenceDeclaration::class, $declaration);
76        self::assertSame(substr($directive, 1), $declaration->associativity);
77    }
78
79    public function testReadTakesOperatorsWrittenAsNamesOrAsCharacters(): void
80    {
81        $declaration = (new PrecedenceDirectiveReader())->read(BisonTokenStream::over("OR_SYM '+'"), '%left');
82
83        self::assertInstanceOf(BisonPrecedenceDeclaration::class, $declaration);
84        self::assertSame(['OR_SYM', '+'], $declaration->symbols);
85    }
86
87    public function testReadTakesTheOptionalTypeTag(): void
88    {
89        $declaration = (new PrecedenceDirectiveReader())->read(BisonTokenStream::over('<num> OR_SYM'), '%left');
90
91        self::assertInstanceOf(BisonPrecedenceDeclaration::class, $declaration);
92        self::assertSame('num', $declaration->typeTag);
93    }
94
95    public function testReadSkipsWhatIsNotAnOperator(): void
96    {
97        $declaration = (new PrecedenceDirectiveReader())->read(BisonTokenStream::over('OR_SYM 7 AND_SYM'), '%left');
98
99        self::assertInstanceOf(BisonPrecedenceDeclaration::class, $declaration);
100        self::assertSame(['OR_SYM', 'AND_SYM'], $declaration->symbols);
101    }
102
103    public function testReadStopsAtTheSectionSeparator(): void
104    {
105        $stream = BisonTokenStream::over('OR_SYM %% rules');
106
107        (new PrecedenceDirectiveReader())->read($stream, '%left');
108
109        self::assertSame('%%', $stream->next()->value);
110    }
111}
112