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