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