packages/bison-parser/tests/Unit/Syntax/TokenStreamTest.php

1<?php
2
3declare(strict_types=1);
4
5namespace Tests\Unit\Syntax;
6
7use BisonParser\Ast\Location;
8use BisonParser\Scanner\Token;
9use BisonParser\Scanner\TokenKind;
10use BisonParser\Syntax\TokenStream;
11use BisonParser\SyntaxException;
12use PHPUnit\Framework\Attributes\CoversClass;
13use PHPUnit\Framework\Attributes\Small;
14use PHPUnit\Framework\Attributes\UsesClass;
15use PHPUnit\Framework\TestCase;
16
17#[CoversClass(TokenStream::class)]
18#[UsesClass(Location::class)]
19#[UsesClass(SyntaxException::class)]
20#[UsesClass(Token::class)]
21#[UsesClass(TokenKind::class)]
22#[Small]
23final class TokenStreamTest extends TestCase
24{
25    public function testPeek(): void
26    {
27        $stream = new TokenStream([new Token(TokenKind::Colon, ':', new Location(1, 1)), new Token(TokenKind::End, '', new Location(1, 2))]);
28
29        self::assertSame(TokenKind::Colon, $stream->peek()->kind);
30        self::assertSame(TokenKind::End, $stream->peek(1)->kind);
31        self::assertSame(TokenKind::End, $stream->peek(5)->kind);
32    }
33
34    public function testNext(): void
35    {
36        $stream = new TokenStream([new Token(TokenKind::Colon, ':', new Location(1, 1)), new Token(TokenKind::End, '', new Location(1, 2))]);
37
38        self::assertSame(TokenKind::Colon, $stream->next()->kind);
39        self::assertSame(TokenKind::End, $stream->next()->kind);
40        self::assertSame(TokenKind::End, $stream->next()->kind);
41    }
42
43    public function testIs(): void
44    {
45        $stream = new TokenStream([new Token(TokenKind::Pipe, '|', new Location(1, 1)), new Token(TokenKind::End, '', new Location(1, 2))]);
46
47        self::assertTrue($stream->is(TokenKind::Pipe));
48        self::assertFalse($stream->is(TokenKind::End));
49    }
50
51    public function testIsDirective(): void
52    {
53        $stream = new TokenStream([new Token(TokenKind::Directive, 'left', new Location(1, 1), '%left'), new Token(TokenKind::End, '', new Location(1, 6))]);
54
55        self::assertTrue($stream->isDirective('left'));
56        self::assertFalse($stream->isDirective('right'));
57    }
58
59    public function testAccept(): void
60    {
61        $stream = new TokenStream([new Token(TokenKind::Pipe, '|', new Location(1, 1)), new Token(TokenKind::End, '', new Location(1, 2))]);
62
63        self::assertNull($stream->accept(TokenKind::Colon));
64        self::assertSame('|', $stream->accept(TokenKind::Pipe)?->text);
65        self::assertTrue($stream->is(TokenKind::End));
66    }
67
68    public function testExpect(): void
69    {
70        $stream = new TokenStream([new Token(TokenKind::Integer, '3', new Location(1, 1)), new Token(TokenKind::End, '', new Location(1, 2))]);
71
72        self::assertSame('3', $stream->expect(TokenKind::Integer, 'a number')->text);
73        self::assertTrue($stream->is(TokenKind::End));
74    }
75
76    public function testExpectRejectsAnotherToken(): void
77    {
78        $stream = new TokenStream([new Token(TokenKind::Identifier, 'x', new Location(2, 5)), new Token(TokenKind::End, '', new Location(2, 6))]);
79
80        $this->expectException(SyntaxException::class);
81        $this->expectExceptionMessage("Expected a number but found 'x' at 2:5");
82
83        $stream->expect(TokenKind::Integer, 'a number');
84    }
85}
86