packages/sql-faker/tests/Unit/Compiler/Bison/Lexer/DirectiveScannerTest.php
1<?php
2
3declare(strict_types=1);
4
5namespace Tests\Unit\SqlFaker\Compiler\Bison\Lexer;
6
7use PHPUnit\Framework\Attributes\CoversClass;
8use PHPUnit\Framework\Attributes\UsesClass;
9use PHPUnit\Framework\TestCase;
10use SqlFaker\Compiler\Bison\Lexer\BisonLexeme;
11use SqlFaker\Compiler\Bison\Lexer\BisonToken;
12use SqlFaker\Compiler\Bison\Lexer\DirectiveScanner;
13use SqlFaker\Compiler\Bison\Lexer\SourceCursor;
14use SqlFaker\Compiler\GrammarParseException;
15
16#[CoversClass(DirectiveScanner::class)]
17#[UsesClass(BisonLexeme::class)]
18#[UsesClass(BisonToken::class)]
19#[UsesClass(GrammarParseException::class)]
20#[UsesClass(SourceCursor::class)]
21final class DirectiveScannerTest extends TestCase
22{
23 public function testHandlesClaimsOnlyAPercentSign(): void
24 {
25 $scanner = new DirectiveScanner();
26
27 self::assertTrue($scanner->handles('%'));
28 self::assertFalse($scanner->handles('{'));
29 }
30
31 public function testScanSectionSeparatorReadsTwoPercentSigns(): void
32 {
33 $cursor = new SourceCursor('%% rules');
34
35 $token = (new DirectiveScanner())->scan($cursor);
36
37 self::assertSame(BisonLexeme::PercentPercent, $token->type);
38 self::assertSame('%%', $token->value);
39 self::assertSame(0, $token->offset);
40 self::assertSame(' rules', $cursor->takeRest());
41 }
42
43 public function testScanPrologueReadsTheCodeBetweenItsDelimiters(): void
44 {
45 $cursor = new SourceCursor('%{ int x; %} rest');
46
47 $token = (new DirectiveScanner())->scan($cursor);
48
49 self::assertSame(BisonLexeme::Prologue, $token->type);
50 self::assertSame(' int x; ', $token->value);
51 self::assertSame(' rest', $cursor->takeRest());
52 }
53
54 public function testScanPrologueRunsAnUnterminatedBodyToTheEndOfTheFile(): void
55 {
56 $cursor = new SourceCursor('%{ int x;');
57
58 $token = (new DirectiveScanner())->scan($cursor);
59
60 self::assertSame(BisonLexeme::Prologue, $token->type);
61 self::assertSame(' int x;', $token->value);
62 self::assertTrue($cursor->atEnd());
63 }
64
65 public function testScanDirectiveNameKeepsTheLeadingPercentSign(): void
66 {
67 $cursor = new SourceCursor('%parse-param { x }');
68
69 $token = (new DirectiveScanner())->scan($cursor);
70
71 self::assertSame(BisonLexeme::Directive, $token->type);
72 self::assertSame('%parse-param', $token->value);
73 self::assertSame(' { x }', $cursor->takeRest());
74 }
75
76 public function testScanDirectiveNameReportsAPercentSignWithoutAName(): void
77 {
78 $cursor = new SourceCursor('% ');
79
80 $this->expectException(GrammarParseException::class);
81 $this->expectExceptionMessage("Unexpected '%' at offset 0");
82
83 (new DirectiveScanner())->scan($cursor);
84 }
85
86 public function testScanDispatchesOnTheCharacterAfterThePercentSign(): void
87 {
88 $scanner = new DirectiveScanner();
89
90 self::assertSame(BisonLexeme::PercentPercent, $scanner->scan(new SourceCursor('%%'))->type);
91 self::assertSame(BisonLexeme::Prologue, $scanner->scan(new SourceCursor('%{ x %}'))->type);
92 self::assertSame(BisonLexeme::Directive, $scanner->scan(new SourceCursor('%token'))->type);
93 }
94
95 public function testScanReportsTheOffsetTheDirectiveStartedAt(): void
96 {
97 $cursor = new SourceCursor('ab%token');
98 $cursor->advance(2);
99
100 self::assertSame(2, (new DirectiveScanner())->scan($cursor)->offset);
101 }
102}
103