1<?php
2
3/*
4 * This file is part of Twig.
5 *
6 * (c) Fabien Potencier
7 * (c) Armin Ronacher
8 *
9 * For the full copyright and license information, please view the LICENSE
10 * file that was distributed with this source code.
11 */
12
13namespace Twig;
14
15use Twig\Error\SyntaxError;
16
17/**
18 * Represents a token stream.
19 *
20 * @final
21 *
22 * @author Fabien Potencier <fabien@symfony.com>
23 */
24class TokenStream
25{
26    protected $tokens;
27    protected $current = 0;
28    protected $filename;
29
30    private $source;
31
32    /**
33     * @param array       $tokens An array of tokens
34     * @param string|null $name   The name of the template which tokens are associated with
35     * @param string|null $source The source code associated with the tokens
36     */
37    public function __construct(array $tokens, $name = null, $source = null)
38    {
39        if (!$name instanceof Source) {
40            if (null !== $name || null !== $source) {
41                @trigger_error(sprintf('Passing a string as the $name argument of %s() is deprecated since version 1.27. Pass a \Twig\Source instance instead.', __METHOD__), E_USER_DEPRECATED);
42            }
43            $this->source = new Source($source, $name);
44        } else {
45            $this->source = $name;
46        }
47
48        $this->tokens = $tokens;
49
50        // deprecated, not used anymore, to be removed in 2.0
51        $this->filename = $this->source->getName();
52    }
53
54    public function __toString()
55    {
56        return implode("\n", $this->tokens);
57    }
58
59    public function injectTokens(array $tokens)
60    {
61        $this->tokens = array_merge(\array_slice($this->tokens, 0, $this->current), $tokens, \array_slice($this->tokens, $this->current));
62    }
63
64    /**
65     * Sets the pointer to the next token and returns the old one.
66     *
67     * @return Token
68     */
69    public function next()
70    {
71        if (!isset($this->tokens[++$this->current])) {
72            throw new SyntaxError('Unexpected end of template.', $this->tokens[$this->current - 1]->getLine(), $this->source);
73        }
74
75        return $this->tokens[$this->current - 1];
76    }
77
78    /**
79     * Tests a token, sets the pointer to the next one and returns it or throws a syntax error.
80     *
81     * @return Token|null The next token if the condition is true, null otherwise
82     */
83    public function nextIf($primary, $secondary = null)
84    {
85        if ($this->tokens[$this->current]->test($primary, $secondary)) {
86            return $this->next();
87        }
88    }
89
90    /**
91     * Tests a token and returns it or throws a syntax error.
92     *
93     * @return Token
94     */
95    public function expect($type, $value = null, $message = null)
96    {
97        $token = $this->tokens[$this->current];
98        if (!$token->test($type, $value)) {
99            $line = $token->getLine();
100            throw new SyntaxError(sprintf('%sUnexpected token "%s" of value "%s" ("%s" expected%s).',
101                $message ? $message.'. ' : '',
102                Token::typeToEnglish($token->getType()), $token->getValue(),
103                Token::typeToEnglish($type), $value ? sprintf(' with value "%s"', $value) : ''),
104                $line,
105                $this->source
106            );
107        }
108        $this->next();
109
110        return $token;
111    }
112
113    /**
114     * Looks at the next token.
115     *
116     * @param int $number
117     *
118     * @return Token
119     */
120    public function look($number = 1)
121    {
122        if (!isset($this->tokens[$this->current + $number])) {
123            throw new SyntaxError('Unexpected end of template.', $this->tokens[$this->current + $number - 1]->getLine(), $this->source);
124        }
125
126        return $this->tokens[$this->current + $number];
127    }
128
129    /**
130     * Tests the current token.
131     *
132     * @return bool
133     */
134    public function test($primary, $secondary = null)
135    {
136        return $this->tokens[$this->current]->test($primary, $secondary);
137    }
138
139    /**
140     * Checks if end of stream was reached.
141     *
142     * @return bool
143     */
144    public function isEOF()
145    {
146        return Token::EOF_TYPE === $this->tokens[$this->current]->getType();
147    }
148
149    /**
150     * @return Token
151     */
152    public function getCurrent()
153    {
154        return $this->tokens[$this->current];
155    }
156
157    /**
158     * Gets the name associated with this stream (null if not defined).
159     *
160     * @return string|null
161     *
162     * @deprecated since 1.27 (to be removed in 2.0)
163     */
164    public function getFilename()
165    {
166        @trigger_error(sprintf('The %s() method is deprecated since version 1.27 and will be removed in 2.0. Use getSourceContext() instead.', __METHOD__), E_USER_DEPRECATED);
167
168        return $this->source->getName();
169    }
170
171    /**
172     * Gets the source code associated with this stream.
173     *
174     * @return string
175     *
176     * @internal Don't use this as it might be empty depending on the environment configuration
177     *
178     * @deprecated since 1.27 (to be removed in 2.0)
179     */
180    public function getSource()
181    {
182        @trigger_error(sprintf('The %s() method is deprecated since version 1.27 and will be removed in 2.0. Use getSourceContext() instead.', __METHOD__), E_USER_DEPRECATED);
183
184        return $this->source->getCode();
185    }
186
187    /**
188     * Gets the source associated with this stream.
189     *
190     * @return Source
191     *
192     * @internal
193     */
194    public function getSourceContext()
195    {
196        return $this->source;
197    }
198}
199
200class_alias('Twig\TokenStream', 'Twig_TokenStream');
201