packages/lemon-parser/tests/Unit/Preprocessor/ConditionTest.php
1<?php
2
3declare(strict_types=1);
4
5namespace Tests\Unit\Preprocessor;
6
7use LemonParser\Ast\Location;
8use LemonParser\Preprocessor\Condition;
9use LemonParser\SyntaxException;
10use PHPUnit\Framework\Attributes\CoversClass;
11use PHPUnit\Framework\Attributes\Small;
12use PHPUnit\Framework\Attributes\UsesClass;
13use PHPUnit\Framework\TestCase;
14
15#[CoversClass(Condition::class)]
16#[UsesClass(Location::class)]
17#[UsesClass(SyntaxException::class)]
18#[Small]
19final class ConditionTest extends TestCase
20{
21 public function testEvaluate(): void
22 {
23 $condition = new Condition(['A', 'B_2']);
24 $at = new Location(1, 1);
25
26 self::assertTrue($condition->evaluate('A', $at));
27 self::assertFalse($condition->evaluate('C', $at));
28 self::assertTrue($condition->evaluate(' !C ', $at));
29 self::assertTrue($condition->evaluate('A || C', $at));
30 self::assertFalse($condition->evaluate('A && C', $at));
31 self::assertTrue($condition->evaluate('C || B_2 && A', $at));
32 self::assertFalse($condition->evaluate('C || (B_2 && !A)', $at));
33 self::assertTrue($condition->evaluate('!(C || D)', $at));
34 self::assertFalse($condition->evaluate('', $at));
35 }
36
37 public function testEvaluateReadsLeftToRightWithoutPrecedence(): void
38 {
39 $condition = new Condition(['A', 'B']);
40
41 self::assertFalse($condition->evaluate('C && A || B', new Location(1, 1)));
42 }
43
44 public function testEvaluateRejectsAnOperatorWithoutALeftSide(): void
45 {
46 $this->expectException(SyntaxException::class);
47 $this->expectExceptionMessage('%if syntax error: | <-- syntax error here at 2:1');
48
49 (new Condition([]))->evaluate('|| A', new Location(2, 1));
50 }
51
52 public function testEvaluateRejectsASingleBar(): void
53 {
54 $this->expectException(SyntaxException::class);
55 $this->expectExceptionMessage('%if syntax error: A | <-- syntax error here at 2:1');
56
57 (new Condition(['A']))->evaluate('A | B', new Location(2, 1));
58 }
59
60 public function testEvaluateRejectsASingleAmpersand(): void
61 {
62 $this->expectException(SyntaxException::class);
63 $this->expectExceptionMessage('%if syntax error: A & <-- syntax error here at 2:1');
64
65 (new Condition(['A']))->evaluate('A & B', new Location(2, 1));
66 }
67
68 public function testEvaluateRejectsANegationAfterATerm(): void
69 {
70 $this->expectException(SyntaxException::class);
71 $this->expectExceptionMessage('%if syntax error: A ! <-- syntax error here at 2:1');
72
73 (new Condition(['A']))->evaluate('A !B', new Location(2, 1));
74 }
75
76 public function testEvaluateRejectsAnAndWithoutALeftSide(): void
77 {
78 $this->expectException(SyntaxException::class);
79 $this->expectExceptionMessage('%if syntax error: & <-- syntax error here at 2:1');
80
81 (new Condition(['A']))->evaluate('&& A', new Location(2, 1));
82 }
83
84 public function testEvaluateRejectsTwoTermsInARow(): void
85 {
86 $this->expectException(SyntaxException::class);
87 $this->expectExceptionMessage('%if syntax error: A B <-- syntax error here at 2:1');
88
89 (new Condition([]))->evaluate('A B', new Location(2, 1));
90 }
91
92 public function testTerm(): void
93 {
94 $condition = new Condition(['A']);
95
96 self::assertSame([true, 0], $condition->term('A', 0, new Location(1, 1)));
97 self::assertSame([false, 6], $condition->term('x Bcd_1', 2, new Location(1, 1)));
98 self::assertSame([true, 3], $condition->term('(!B)', 0, new Location(1, 1)));
99 }
100
101 public function testTermRejectsAnUnclosedGroup(): void
102 {
103 $this->expectException(SyntaxException::class);
104 $this->expectExceptionMessage('%if syntax error: (A <-- syntax error here at 1:1');
105
106 (new Condition([]))->term('(A', 0, new Location(1, 1));
107 }
108
109 public function testTermRejectsANameStartingWithADigit(): void
110 {
111 $this->expectException(SyntaxException::class);
112 $this->expectExceptionMessage('%if syntax error: 1 <-- syntax error here at 1:1');
113
114 (new Condition([]))->term('1A', 0, new Location(1, 1));
115 }
116
117 public function testGuard(): void
118 {
119 $this->expectException(SyntaxException::class);
120 $this->expectExceptionMessage('%if syntax error: A & <-- syntax error here at 1:1');
121
122 (new Condition([]))->guard(false, 'A && B', 2, new Location(1, 1));
123 }
124
125 public function testError(): void
126 {
127 $error = (new Condition([]))->error('A ? B', 2, new Location(5, 1));
128
129 self::assertSame('%if syntax error: A ? <-- syntax error here at 5:1', $error->getMessage());
130 }
131}
132