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