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