packages/sql-faker/tests/Unit/Compiler/Bison/Directive/TokenDirectiveReaderTest.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\UsesClass;
9use PHPUnit\Framework\TestCase;
10use SqlFaker\Compiler\Bison\Ast\BisonTokenDeclaration;
11use SqlFaker\Compiler\Bison\Ast\BisonTokenDefinition;
12use SqlFaker\Compiler\Bison\Directive\BisonDeclarationBoundary;
13use SqlFaker\Compiler\Bison\Directive\TokenDirectiveReader;
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(TokenDirectiveReader::class)]
29#[UsesClass(BisonDeclarationBoundary::class)]
30#[UsesClass(BisonLexeme::class)]
31#[UsesClass(BisonLexer::class)]
32#[UsesClass(BisonScannerChain::class)]
33#[UsesClass(BisonToken::class)]
34#[UsesClass(BisonTokenDeclaration::class)]
35#[UsesClass(BisonTokenDefinition::class)]
36#[UsesClass(BisonTokenStream::class)]
37#[UsesClass(BisonTrivia::class)]
38#[UsesClass(SourceCursor::class)]
39#[UsesClass(ActionScanner::class)]
40#[UsesClass(DirectiveScanner::class)]
41#[UsesClass(IdentifierScanner::class)]
42#[UsesClass(NumberScanner::class)]
43#[UsesClass(QuotedLiteralScanner::class)]
44#[UsesClass(TypeTagScanner::class)]
45#[UsesClass(\SqlFaker\Compiler\Bison\Lexer\PunctuationScanner::class)]
46#[UsesClass(\SqlFaker\Compiler\GrammarParseException::class)]
47final class TokenDirectiveReaderTest extends TestCase
48{
49 public function testHandlesClaimsOnlyTheTokenDirective(): void
50 {
51 $reader = new TokenDirectiveReader();
52
53 self::assertTrue($reader->handles('%token'));
54 self::assertFalse($reader->handles('%type'));
55 }
56
57 public function testReadTakesEveryTerminalNamed(): void
58 {
59 $declaration = (new TokenDirectiveReader())
60 ->read(BisonTokenStream::over('SELECT_SYM FROM_SYM WHERE_SYM'), '%token');
61
62 self::assertInstanceOf(BisonTokenDeclaration::class, $declaration);
63 self::assertSame(
64 ['SELECT_SYM', 'FROM_SYM', 'WHERE_SYM'],
65 array_map(static fn (BisonTokenDefinition $token): string => $token->name, $declaration->tokens),
66 );
67 }
68
69 public function testReadTakesTheOptionalTypeTag(): void
70 {
71 $declaration = (new TokenDirectiveReader())->read(BisonTokenStream::over('<lexer> IDENT'), '%token');
72
73 self::assertInstanceOf(BisonTokenDeclaration::class, $declaration);
74 self::assertSame('lexer', $declaration->typeTag);
75 }
76
77 public function testReadReportsNoTypeTagWhenNoneIsWritten(): void
78 {
79 $declaration = (new TokenDirectiveReader())->read(BisonTokenStream::over('IDENT'), '%token');
80
81 self::assertInstanceOf(BisonTokenDeclaration::class, $declaration);
82 self::assertNull($declaration->typeTag);
83 }
84
85 public function testReadStopsAtTheNextDirective(): void
86 {
87 $stream = BisonTokenStream::over('IDENT %type <num> expr');
88
89 (new TokenDirectiveReader())->read($stream, '%token');
90
91 self::assertSame('%type', $stream->next()->value);
92 }
93
94 public function testReadTakesAnEmptyDeclarationAsNoTerminals(): void
95 {
96 $declaration = (new TokenDirectiveReader())->read(BisonTokenStream::over('%%'), '%token');
97
98 self::assertInstanceOf(BisonTokenDeclaration::class, $declaration);
99 self::assertSame([], $declaration->tokens);
100 }
101
102 public function testReadTerminalTakesTheNameAlone(): void
103 {
104 $token = (new TokenDirectiveReader())->readTerminal(BisonTokenStream::over('IDENT'));
105
106 self::assertSame('IDENT', $token->name);
107 self::assertNull($token->number);
108 self::assertNull($token->alias);
109 }
110
111 public function testReadTerminalTakesTheExplicitTokenCode(): void
112 {
113 $token = (new TokenDirectiveReader())->readTerminal(BisonTokenStream::over('IDENT 258'));
114
115 self::assertSame(258, $token->number);
116 self::assertNull($token->alias);
117 }
118
119 public function testReadTerminalTakesTheAlias(): void
120 {
121 $token = (new TokenDirectiveReader())->readTerminal(BisonTokenStream::over('IDENT "identifier"'));
122
123 self::assertNull($token->number);
124 self::assertSame('identifier', $token->alias);
125 }
126
127 public function testReadTerminalTakesACodeAndAnAliasTogether(): void
128 {
129 $token = (new TokenDirectiveReader())->readTerminal(BisonTokenStream::over('IDENT 258 "identifier"'));
130
131 self::assertSame('IDENT', $token->name);
132 self::assertSame(258, $token->number);
133 self::assertSame('identifier', $token->alias);
134 }
135}
136