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