blob: 1eac11a02d65eea94ef48846af825df105633c57 [file] [log] [blame]
Matthias Andreas Benkard12a57352021-12-28 18:02:04 +01001<?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 * @author Fabien Potencier <fabien@symfony.com>
21 */
22final class TokenStream
23{
24 private $tokens;
25 private $current = 0;
26 private $source;
27
28 public function __construct(array $tokens, Source $source = null)
29 {
30 $this->tokens = $tokens;
31 $this->source = $source ?: new Source('', '');
32 }
33
34 public function __toString()
35 {
36 return implode("\n", $this->tokens);
37 }
38
39 public function injectTokens(array $tokens)
40 {
41 $this->tokens = array_merge(\array_slice($this->tokens, 0, $this->current), $tokens, \array_slice($this->tokens, $this->current));
42 }
43
44 /**
45 * Sets the pointer to the next token and returns the old one.
46 */
47 public function next(): Token
48 {
49 if (!isset($this->tokens[++$this->current])) {
50 throw new SyntaxError('Unexpected end of template.', $this->tokens[$this->current - 1]->getLine(), $this->source);
51 }
52
53 return $this->tokens[$this->current - 1];
54 }
55
56 /**
57 * Tests a token, sets the pointer to the next one and returns it or throws a syntax error.
58 *
59 * @return Token|null The next token if the condition is true, null otherwise
60 */
61 public function nextIf($primary, $secondary = null)
62 {
63 if ($this->tokens[$this->current]->test($primary, $secondary)) {
64 return $this->next();
65 }
66 }
67
68 /**
69 * Tests a token and returns it or throws a syntax error.
70 */
71 public function expect($type, $value = null, string $message = null): Token
72 {
73 $token = $this->tokens[$this->current];
74 if (!$token->test($type, $value)) {
75 $line = $token->getLine();
76 throw new SyntaxError(sprintf('%sUnexpected token "%s"%s ("%s" expected%s).',
77 $message ? $message.'. ' : '',
78 Token::typeToEnglish($token->getType()),
79 $token->getValue() ? sprintf(' of value "%s"', $token->getValue()) : '',
80 Token::typeToEnglish($type), $value ? sprintf(' with value "%s"', $value) : ''),
81 $line,
82 $this->source
83 );
84 }
85 $this->next();
86
87 return $token;
88 }
89
90 /**
91 * Looks at the next token.
92 */
93 public function look(int $number = 1): Token
94 {
95 if (!isset($this->tokens[$this->current + $number])) {
96 throw new SyntaxError('Unexpected end of template.', $this->tokens[$this->current + $number - 1]->getLine(), $this->source);
97 }
98
99 return $this->tokens[$this->current + $number];
100 }
101
102 /**
103 * Tests the current token.
104 */
105 public function test($primary, $secondary = null): bool
106 {
107 return $this->tokens[$this->current]->test($primary, $secondary);
108 }
109
110 /**
111 * Checks if end of stream was reached.
112 */
113 public function isEOF(): bool
114 {
115 return /* Token::EOF_TYPE */ -1 === $this->tokens[$this->current]->getType();
116 }
117
118 public function getCurrent(): Token
119 {
120 return $this->tokens[$this->current];
121 }
122
123 /**
124 * Gets the source associated with this stream.
125 *
126 * @internal
127 */
128 public function getSourceContext(): Source
129 {
130 return $this->source;
131 }
132}