1<?php
2
3declare(strict_types=1);
4
5namespace Antlr\Antlr4\Runtime;
6
7use Antlr\Antlr4\Runtime\Utils\StringUtils;
8
9/**
10 * Vacuum all input from a string and then treat it like a buffer.
11 */
12final class InputStream implements CharStream
13{
14    /** @var int */
15    protected $index = 0;
16
17    /** @var int */
18    protected $size = 0;
19
20    /** @var string */
21    public $name = '<empty>';
22
23    /** @var string */
24    public $input;
25
26    /** @var array<string> */
27    public $characters = [];
28
29    /**
30     * @param array<string> $characters
31     */
32    private function __construct(string $input, array $characters)
33    {
34        $this->input = $input;
35        $this->characters = $characters;
36        $this->size = \count($this->characters);
37    }
38
39    public static function fromString(string $input) : InputStream
40    {
41        $chars = \preg_split('//u', $input, -1, \PREG_SPLIT_NO_EMPTY);
42
43        return new self($input, $chars === false ? [] : $chars);
44    }
45
46    public static function fromPath(string $path) : InputStream
47    {
48        $content = \file_get_contents($path);
49
50        if ($content === false) {
51            throw new \InvalidArgumentException(\sprintf('File not found at %s.', $path));
52        }
53
54        return self::fromString($content);
55    }
56
57    public function getIndex() : int
58    {
59        return $this->index;
60    }
61
62    public function getLength() : int
63    {
64        return $this->size;
65    }
66
67    public function consume() : void
68    {
69        if ($this->index >= $this->size) {
70            // assert this.LA(1) == Token.EOF
71            throw new \RuntimeException('Cannot consume EOF.');
72        }
73
74        $this->index++;
75    }
76
77    public function LA(int $offset) : int
78    {
79        if ($offset === 0) {
80            return 0;// undefined
81        }
82
83        if ($offset < 0) {
84            // e.g., translate LA(-1) to use offset=0
85            $offset++;
86        }
87
88        $pos = $this->index + $offset - 1;
89
90        if ($pos < 0 || $pos >= $this->size) {
91            // invalid
92            return Token::EOF;
93        }
94
95        return StringUtils::codePoint($this->characters[$pos]);
96    }
97
98    public function LT(int $offset) : int
99    {
100        return $this->LA($offset);
101    }
102
103    /**
104     * Mark/release do nothing; we have entire buffer
105     */
106    public function mark() : int
107    {
108        return -1;
109    }
110
111    public function release(int $marker) : void
112    {
113    }
114
115    /**
116     * {@see self::consume()} ahead until `$p === $this->index`; Can't just set
117     * `$p = $this->index` as we must update line and column. If we seek
118     * backwards, just set `$p`.
119     */
120    public function seek(int $index) : void
121    {
122        if ($index <= $this->index) {
123            $this->index = $index; // just jump; don't update stream state (line, ...)
124
125            return;
126        }
127
128        // seek forward
129        $this->index = \min($index, $this->size);
130    }
131
132    public function getText(int $start, int $stop) : string
133    {
134        if ($stop >= $this->size) {
135            $stop = $this->size - 1;
136        }
137
138        if ($start >= $this->size) {
139            return '';
140        }
141
142        return \implode(\array_slice($this->characters, $start, $stop - $start + 1));
143    }
144
145    public function getSourceName() : string
146    {
147        return '';
148    }
149
150    public function __toString() : string
151    {
152        return $this->input;
153    }
154}
155