packages/sql-parser/tests/Unit/Grammar/GrammarTest.php

1<?php
2
3declare(strict_types=1);
4
5namespace Tests\Unit\Grammar;
6
7use PHPUnit\Framework\Attributes\CoversClass;
8use PHPUnit\Framework\Attributes\Small;
9use PHPUnit\Framework\Attributes\UsesClass;
10use PHPUnit\Framework\TestCase;
11use SqlParser\Grammar\Associativity;
12use SqlParser\Grammar\Grammar;
13use SqlParser\Grammar\GrammarException;
14use SqlParser\Grammar\Precedence;
15use SqlParser\Grammar\PrecedencePolicy;
16use SqlParser\Grammar\Rule;
17use SqlParser\Grammar\SymbolTable;
18
19#[CoversClass(Grammar::class)]
20#[UsesClass(GrammarException::class)]
21#[UsesClass(Precedence::class)]
22#[UsesClass(Rule::class)]
23#[UsesClass(SymbolTable::class)]
24#[Small]
25final class GrammarTest extends TestCase
26{
27    public function testRulesOf(): void
28    {
29        $symbols = new SymbolTable(['$end', 'NUM', '+'], ['$accept', 'expr']);
30        $grammar = new Grammar($symbols, [new Rule(0, 3, [4, 0], 0), new Rule(1, 4, [4, 2, 4], 0), new Rule(2, 4, [1], 1)]);
31
32        self::assertSame([1, 2], $grammar->rulesOf(4));
33        self::assertSame([], $grammar->rulesOf(9));
34    }
35
36    public function testPrecedenceOf(): void
37    {
38        $symbols = new SymbolTable(['$end', 'NUM', '+'], ['$accept', 'expr']);
39        $plus = new Precedence(1, Associativity::Left);
40        $grammar = new Grammar($symbols, [new Rule(0, 3, [4, 0], 0)], [2 => $plus]);
41
42        self::assertSame($plus, $grammar->precedenceOf(2));
43        self::assertNull($grammar->precedenceOf(1));
44    }
45
46    public function testRulePrecedenceTakesTheLastTerminalUnderBisonsPolicy(): void
47    {
48        $symbols = new SymbolTable(['$end', 'NUM', '+', '*'], ['$accept', 'expr']);
49        $plus = new Precedence(1, Associativity::Left);
50        $grammar = new Grammar($symbols, [new Rule(0, 4, [5, 0], 0), new Rule(1, 5, [5, 2, 5, 3], 0), new Rule(2, 5, [3, 5, 2], 1)], [2 => $plus]);
51
52        self::assertNull($grammar->rulePrecedence($grammar->rules[1]));
53        self::assertSame($plus, $grammar->rulePrecedence($grammar->rules[2]));
54    }
55
56    public function testRulePrecedenceTakesTheFirstRankedTerminalUnderLemonsPolicy(): void
57    {
58        $symbols = new SymbolTable(['$end', 'NUM', '+', '*'], ['$accept', 'expr']);
59        $plus = new Precedence(1, Associativity::Left);
60        $grammar = new Grammar($symbols, [new Rule(0, 4, [5, 0], 0), new Rule(1, 5, [5, 2, 5, 3], 0)], [2 => $plus], PrecedencePolicy::FirstRankedTerminal);
61
62        self::assertSame($plus, $grammar->rulePrecedence($grammar->rules[1]));
63    }
64
65    public function testRulePrecedencePrefersTheNamedTerminal(): void
66    {
67        $symbols = new SymbolTable(['$end', 'NUM', '+', 'NEG'], ['$accept', 'expr']);
68        $neg = new Precedence(2, Associativity::Right);
69        $grammar = new Grammar($symbols, [new Rule(0, 4, [5, 0], 0), new Rule(1, 5, [2, 5], 0, false, 3)], [2 => new Precedence(1, Associativity::Left), 3 => $neg]);
70
71        self::assertSame($neg, $grammar->rulePrecedence($grammar->rules[1]));
72    }
73
74    public function testMemberPrecedenceLooksThroughATokenClass(): void
75    {
76        $symbols = new SymbolTable(['$end', 'ID', 'KW', 'class'], ['$accept', 'expr']);
77        $kw = new Precedence(1, Associativity::Left);
78        $grammar = new Grammar($symbols, [new Rule(0, 4, [5, 0], 0)], [2 => $kw], PrecedencePolicy::FirstRankedTerminal, null, [3 => [1, 2]]);
79
80        self::assertSame($kw, $grammar->memberPrecedence(3));
81        self::assertNull($grammar->memberPrecedence(1));
82    }
83
84    public function testStartSymbol(): void
85    {
86        $symbols = new SymbolTable(['$end'], ['$accept', 'stmt']);
87
88        self::assertSame(2, (new Grammar($symbols, [new Rule(0, 1, [2, 0], 0)]))->startSymbol());
89    }
90
91    public function testRulesMustBeNumberedInOrder(): void
92    {
93        $this->expectException(GrammarException::class);
94
95        new Grammar(new SymbolTable(['$end'], ['$accept']), [new Rule(1, 1, [], 0)]);
96    }
97}
98