blob: b3f1eca3c04f415b164e60be69eaaf3e8c629177 [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\Node\Node;
16
17/**
18 * @author Fabien Potencier <fabien@symfony.com>
19 */
20class Compiler
21{
22 private $lastLine;
23 private $source;
24 private $indentation;
25 private $env;
26 private $debugInfo = [];
27 private $sourceOffset;
28 private $sourceLine;
29 private $varNameSalt = 0;
30
31 public function __construct(Environment $env)
32 {
33 $this->env = $env;
34 }
35
36 public function getEnvironment(): Environment
37 {
38 return $this->env;
39 }
40
41 public function getSource(): string
42 {
43 return $this->source;
44 }
45
46 /**
47 * @return $this
48 */
49 public function compile(Node $node, int $indentation = 0)
50 {
51 $this->lastLine = null;
52 $this->source = '';
53 $this->debugInfo = [];
54 $this->sourceOffset = 0;
55 // source code starts at 1 (as we then increment it when we encounter new lines)
56 $this->sourceLine = 1;
57 $this->indentation = $indentation;
58 $this->varNameSalt = 0;
59
60 $node->compile($this);
61
62 return $this;
63 }
64
65 /**
66 * @return $this
67 */
68 public function subcompile(Node $node, bool $raw = true)
69 {
70 if (false === $raw) {
71 $this->source .= str_repeat(' ', $this->indentation * 4);
72 }
73
74 $node->compile($this);
75
76 return $this;
77 }
78
79 /**
80 * Adds a raw string to the compiled code.
81 *
82 * @return $this
83 */
84 public function raw(string $string)
85 {
86 $this->source .= $string;
87
88 return $this;
89 }
90
91 /**
92 * Writes a string to the compiled code by adding indentation.
93 *
94 * @return $this
95 */
96 public function write(...$strings)
97 {
98 foreach ($strings as $string) {
99 $this->source .= str_repeat(' ', $this->indentation * 4).$string;
100 }
101
102 return $this;
103 }
104
105 /**
106 * Adds a quoted string to the compiled code.
107 *
108 * @return $this
109 */
110 public function string(string $value)
111 {
112 $this->source .= sprintf('"%s"', addcslashes($value, "\0\t\"\$\\"));
113
114 return $this;
115 }
116
117 /**
118 * Returns a PHP representation of a given value.
119 *
120 * @return $this
121 */
122 public function repr($value)
123 {
124 if (\is_int($value) || \is_float($value)) {
125 if (false !== $locale = setlocale(\LC_NUMERIC, '0')) {
126 setlocale(\LC_NUMERIC, 'C');
127 }
128
129 $this->raw(var_export($value, true));
130
131 if (false !== $locale) {
132 setlocale(\LC_NUMERIC, $locale);
133 }
134 } elseif (null === $value) {
135 $this->raw('null');
136 } elseif (\is_bool($value)) {
137 $this->raw($value ? 'true' : 'false');
138 } elseif (\is_array($value)) {
139 $this->raw('array(');
140 $first = true;
141 foreach ($value as $key => $v) {
142 if (!$first) {
143 $this->raw(', ');
144 }
145 $first = false;
146 $this->repr($key);
147 $this->raw(' => ');
148 $this->repr($v);
149 }
150 $this->raw(')');
151 } else {
152 $this->string($value);
153 }
154
155 return $this;
156 }
157
158 /**
159 * @return $this
160 */
161 public function addDebugInfo(Node $node)
162 {
163 if ($node->getTemplateLine() != $this->lastLine) {
164 $this->write(sprintf("// line %d\n", $node->getTemplateLine()));
165
166 $this->sourceLine += substr_count($this->source, "\n", $this->sourceOffset);
167 $this->sourceOffset = \strlen($this->source);
168 $this->debugInfo[$this->sourceLine] = $node->getTemplateLine();
169
170 $this->lastLine = $node->getTemplateLine();
171 }
172
173 return $this;
174 }
175
176 public function getDebugInfo(): array
177 {
178 ksort($this->debugInfo);
179
180 return $this->debugInfo;
181 }
182
183 /**
184 * @return $this
185 */
186 public function indent(int $step = 1)
187 {
188 $this->indentation += $step;
189
190 return $this;
191 }
192
193 /**
194 * @return $this
195 *
196 * @throws \LogicException When trying to outdent too much so the indentation would become negative
197 */
198 public function outdent(int $step = 1)
199 {
200 // can't outdent by more steps than the current indentation level
201 if ($this->indentation < $step) {
202 throw new \LogicException('Unable to call outdent() as the indentation would become negative.');
203 }
204
205 $this->indentation -= $step;
206
207 return $this;
208 }
209
210 public function getVarName(): string
211 {
212 return sprintf('__internal_%s', hash('sha256', __METHOD__.$this->varNameSalt++));
213 }
214}