1
2/*
3 +------------------------------------------------------------------------+
4 | Phalcon Framework                                                      |
5 +------------------------------------------------------------------------+
6 | Copyright (c) 2011-2017 Phalcon Team (https://phalconphp.com)          |
7 +------------------------------------------------------------------------+
8 | This source file is subject to the New BSD License that is bundled     |
9 | with this package in the file LICENSE.txt.                             |
10 |                                                                        |
11 | If you did not receive a copy of the license and are unable to         |
12 | obtain it through the world-wide-web, please send an email             |
13 | to license@phalconphp.com so we can send you a copy immediately.       |
14 +------------------------------------------------------------------------+
15 | Authors: Andres Gutierrez <andres@phalconphp.com>                      |
16 |          Eduar Carvajal <eduar@phalconphp.com>                         |
17 +------------------------------------------------------------------------+
18 */
19
20namespace Phalcon\Mvc\View\Engine\Volt;
21
22use Phalcon\DiInterface;
23use Phalcon\Mvc\ViewBaseInterface;
24use Phalcon\Di\InjectionAwareInterface;
25use Phalcon\Mvc\View\Engine\Volt\Exception;
26
27/**
28 * Phalcon\Mvc\View\Engine\Volt\Compiler
29 *
30 * This class reads and compiles Volt templates into PHP plain code
31 *
32 *<code>
33 * $compiler = new \Phalcon\Mvc\View\Engine\Volt\Compiler();
34 *
35 * $compiler->compile("views/partials/header.volt");
36 *
37 * require $compiler->getCompiledTemplatePath();
38 *</code>
39 */
40class Compiler implements InjectionAwareInterface
41{
42
43	protected _dependencyInjector;
44
45	protected _view;
46
47	protected _options;
48
49	protected _arrayHelpers;
50
51	protected _level = 0;
52
53	protected _foreachLevel = 0;
54
55	protected _blockLevel = 0;
56
57	protected _exprLevel = 0;
58
59	protected _extended = false;
60
61	protected _autoescape = false;
62
63	protected _extendedBlocks;
64
65	protected _currentBlock;
66
67	protected _blocks;
68
69	protected _forElsePointers;
70
71	protected _loopPointers;
72
73	protected _extensions;
74
75	protected _functions;
76
77	protected _filters;
78
79	protected _macros;
80
81	protected _prefix;
82
83	protected _currentPath;
84
85	protected _compiledTemplatePath;
86
87	/**
88	 * Phalcon\Mvc\View\Engine\Volt\Compiler
89	 */
90	public function __construct(<ViewBaseInterface> view = null)
91	{
92		if typeof view == "object" {
93			let this->_view = view;
94		}
95	}
96
97	/**
98	 * Sets the dependency injector
99	 */
100	public function setDI(<DiInterface> dependencyInjector)
101	{
102		let this->_dependencyInjector = dependencyInjector;
103	}
104
105	/**
106	 * Returns the internal dependency injector
107	 */
108	public function getDI() -> <DiInterface>
109	{
110		return this->_dependencyInjector;
111	}
112
113	/**
114	 * Sets the compiler options
115	 */
116	public function setOptions(array! options)
117	{
118		let this->_options = options;
119	}
120
121	/**
122	 * Sets a single compiler option
123	 *
124	 * @param string option
125	 * @param mixed value
126	 */
127	public function setOption(string! option, value)
128	{
129		let this->_options[option] = value;
130	}
131
132	/**
133	 * Returns a compiler's option
134	 *
135	 * @param string option
136	 * @return string
137	 */
138	public function getOption(string! option)
139	{
140		var value;
141		if fetch value, this->_options[option] {
142			return value;
143		}
144		return null;
145	}
146
147	/**
148	 * Returns the compiler options
149	 */
150	public function getOptions() -> array
151	{
152		return this->_options;
153	}
154
155	/**
156	 * Fires an event to registered extensions
157	 *
158	 * @param string name
159	 * @param array arguments
160	 * @return mixed
161	 */
162	public final function fireExtensionEvent(string! name, arguments = null)
163	{
164		var extensions, extension, status;
165
166		let extensions = this->_extensions;
167		if typeof extensions == "array" {
168			for extension in extensions {
169
170				/**
171				 * Check if the extension implements the required event name
172				 */
173				if method_exists(extension, name) {
174
175					if typeof arguments == "array" {
176						let status = call_user_func_array([extension, name], arguments);
177					} else {
178						let status = call_user_func([extension, name]);
179					}
180
181					/**
182					 * Only string statuses means the extension process something
183					 */
184					if typeof status == "string" {
185						return status;
186					}
187				}
188			}
189		}
190	}
191
192	/**
193	 * Registers a Volt's extension
194	 */
195	public function addExtension(extension) -> <Compiler>
196	{
197		if typeof extension != "object" {
198			throw new Exception("The extension is not valid");
199		}
200
201		/**
202		 * Initialize the extension
203		 */
204		if method_exists(extension, "initialize") {
205			extension->initialize(this);
206		}
207
208		let this->_extensions[] = extension;
209		return this;
210	}
211
212	/**
213	 * Returns the list of extensions registered in Volt
214	 */
215	public function getExtensions() -> array
216	{
217		return this->_extensions;
218	}
219
220	/**
221	 * Register a new function in the compiler
222	 */
223	public function addFunction(string! name, var definition) -> <Compiler>
224	{
225		let this->_functions[name] = definition;
226		return this;
227	}
228
229	/**
230	 * Register the user registered functions
231	 */
232	public function getFunctions() -> array
233	{
234		return this->_functions;
235	}
236
237	/**
238	 * Register a new filter in the compiler
239	 */
240	public function addFilter(string! name, var definition) -> <Compiler>
241	{
242		let this->_filters[name] = definition;
243		return this;
244	}
245
246	/**
247	 * Register the user registered filters
248	 */
249	public function getFilters() -> array
250	{
251		return this->_filters;
252	}
253
254	/**
255	 * Set a unique prefix to be used as prefix for compiled variables
256	 */
257	public function setUniquePrefix(string! prefix) -> <Compiler>
258	{
259		let this->_prefix = prefix;
260		return this;
261	}
262
263	/**
264	 * Return a unique prefix to be used as prefix for compiled variables and contexts
265	 */
266	public function getUniquePrefix() -> string
267	{
268		/**
269		 * If the unique prefix is not set we use a hash using the modified Berstein algotithm
270		 */
271		if !this->_prefix {
272			let this->_prefix = unique_path_key(this->_currentPath);
273		}
274
275		/**
276		 * The user could use a closure generator
277		 */
278		if typeof this->_prefix == "object" {
279			if this->_prefix instanceof \Closure {
280				let this->_prefix = call_user_func_array(this->_prefix, [this]);
281			}
282		}
283
284		if typeof this->_prefix != "string" {
285			throw new Exception("The unique compilation prefix is invalid");
286		}
287
288		return this->_prefix;
289	}
290
291	/**
292	 * Resolves attribute reading
293	 */
294	public function attributeReader(array! expr) -> string
295	{
296		var exprCode, left, leftType, variable,
297			level, dependencyInjector, leftCode, right;
298
299		let exprCode = null;
300
301		let left = expr["left"];
302
303		if left["type"] == PHVOLT_T_IDENTIFIER {
304
305			let variable = left["value"];
306
307			/**
308			 * Check if the variable is the loop context
309			 */
310			if variable == "loop" {
311				let level = this->_foreachLevel,
312					exprCode .= "$" . this->getUniquePrefix() . level . "loop",
313					this->_loopPointers[level] = level;
314			} else {
315
316				/**
317				 * Services registered in the dependency injector container are available always
318				 */
319				let dependencyInjector = this->_dependencyInjector;
320				if typeof dependencyInjector == "object" && dependencyInjector->has(variable) {
321					let exprCode .= "$this->" . variable;
322				} else {
323					let exprCode .= "$" . variable;
324				}
325			}
326
327		} else {
328			let leftCode = this->expression(left), leftType = left["type"];
329			if leftType != PHVOLT_T_DOT && leftType != PHVOLT_T_FCALL {
330				let exprCode .= leftCode;
331			} else {
332				let exprCode .= leftCode;
333			}
334		}
335
336		let exprCode .= "->";
337
338		let right = expr["right"];
339
340		if right["type"] == PHVOLT_T_IDENTIFIER {
341			let exprCode .= right["value"];
342		} else {
343			let exprCode .= this->expression(right);
344		}
345
346		return exprCode;
347	}
348
349	/**
350	 * Resolves function intermediate code into PHP function calls
351	 */
352	public function functionCall(array! expr) -> string
353	{
354		var code, funcArguments, arguments, nameExpr,
355			nameType, name, extensions, functions, definition,
356			extendedBlocks, block, currentBlock, exprLevel, escapedCode,
357			method, arrayHelpers, className;
358
359		let code = null;
360
361		let funcArguments = null;
362		if fetch funcArguments, expr["arguments"] {
363			let arguments = this->expression(funcArguments);
364		} else {
365			let arguments = "";
366		}
367
368		let nameExpr = expr["name"], nameType = nameExpr["type"];
369
370		/**
371		 * Check if it's a single function
372		 */
373		if nameType == PHVOLT_T_IDENTIFIER {
374
375			let name = nameExpr["value"];
376
377			/**
378			 * Check if any of the registered extensions provide compilation for this function
379			 */
380			let extensions = this->_extensions;
381			if typeof extensions == "array" {
382
383				/**
384				 * Notify the extensions about being compiling a function
385				 */
386				let code = this->fireExtensionEvent("compileFunction", [name, arguments, funcArguments]);
387				if typeof code == "string" {
388					return code;
389				}
390			}
391
392			/**
393			 * Check if it's a user defined function
394			 */
395			let functions = this->_functions;
396			if typeof functions == "array" {
397				if fetch definition, functions[name] {
398
399					/**
400					 * Use the string as function
401					 */
402					if typeof definition == "string" {
403						return definition . "(" . arguments . ")";
404					}
405
406					/**
407					 * Execute the function closure returning the compiled definition
408					 */
409					if typeof definition == "object" {
410
411						if definition instanceof \Closure {
412							return call_user_func_array(definition, [arguments, funcArguments]);
413						}
414					}
415
416					throw new Exception(
417						"Invalid definition for user function '" . name . "' in " . expr["file"] . " on line " . expr["line"]
418					);
419				}
420			}
421
422			/**
423			 * This function includes the previous rendering stage
424			 */
425			if name == "get_content" || name == "content" {
426				return "$this->getContent()";
427			}
428
429			/**
430			 * This function includes views of volt or others template engines dynamically
431			 */
432			if name == "partial" {
433				return "$this->partial(" . arguments . ")";
434			}
435
436			/**
437			 * This function embeds the parent block in the current block
438			 */
439			if name == "super" {
440				let extendedBlocks = this->_extendedBlocks;
441				if typeof extendedBlocks == "array" {
442
443					let currentBlock = this->_currentBlock;
444					if fetch block, extendedBlocks[currentBlock] {
445
446						let exprLevel = this->_exprLevel;
447						if typeof block == "array" {
448							let code = this->_statementListOrExtends(block);
449							if exprLevel == 1 {
450								let escapedCode = code;
451							} else {
452								let escapedCode = addslashes(code);
453							}
454						} else {
455							if exprLevel == 1 {
456								let escapedCode = block;
457							} else {
458								let escapedCode = addslashes(block);
459							}
460						}
461
462						/**
463						 * If the super() is the first level we don't escape it
464						 */
465						if exprLevel == 1 {
466							return escapedCode;
467						}
468						return "'" . escapedCode . "'";
469					}
470				}
471				return "''";
472			}
473
474			let method = lcfirst(camelize(name)),
475				className = "Phalcon\\Tag";
476
477			/**
478			 * Check if it's a method in Phalcon\Tag
479			 */
480			if method_exists(className, method) {
481
482				let arrayHelpers = this->_arrayHelpers;
483				if typeof arrayHelpers != "array" {
484					let arrayHelpers = [
485						"check_field": true,
486						"color_field": true,
487						"date_field": true,
488						"date_time_field": true,
489						"date_time_local_field": true,
490						"email_field": true,
491						"file_field": true,
492						"form": true,
493						"hidden_field": true,
494						"image": true,
495						"image_input": true,
496						"link_to": true,
497						"month_field": true,
498						"numeric_field": true,
499						"password_field": true,
500						"radio_field": true,
501						"range_field": true,
502						"search_field": true,
503						"select": true,
504						"select_static": true,
505						"submit_button": true,
506						"tel_field": true,
507						"text_area": true,
508						"text_field": true,
509						"time_field": true,
510						"url_field": true,
511						"week_field": true
512					];
513					let this->_arrayHelpers = arrayHelpers;
514				}
515
516				if isset arrayHelpers[name] {
517					return "$this->tag->" . method . "([" . arguments . "])";
518				}
519				return "$this->tag->" . method . "(" . arguments . ")";
520			}
521
522			/**
523			 * Get a dynamic URL
524			 */
525			if name == "url" {
526				return "$this->url->get(" . arguments . ")";
527			}
528
529			/**
530			 * Get a static URL
531			 */
532			if name == "static_url" {
533				return "$this->url->getStatic(" . arguments . ")";
534			}
535
536			if name == "date" {
537				return "date(" . arguments . ")";
538			}
539
540			if name == "time" {
541				return "time()";
542			}
543
544			if name == "dump" {
545				return "var_dump(" . arguments . ")";
546			}
547
548			if name == "version" {
549				return "Phalcon\\Version::get()";
550			}
551
552			if name == "version_id" {
553				return "Phalcon\\Version::getId()";
554			}
555
556			/**
557			 * Read PHP constants in templates
558			 */
559			if name == "constant" {
560				return "constant(" . arguments . ")";
561			}
562
563			/**
564			 * By default it tries to call a macro
565			 */
566			return "$this->callMacro('" . name . "', [" . arguments . "])";
567		}
568
569		return this->expression(nameExpr) . "(" . arguments . ")";
570	}
571
572	/**
573	 * Resolves filter intermediate code into a valid PHP expression
574	 */
575	public function resolveTest(array! test, string left) -> string
576	{
577		var type, name, testName;
578
579		let type = test["type"];
580
581		/**
582		 * Check if right part is a single identifier
583		 */
584		if type == PHVOLT_T_IDENTIFIER {
585
586			let name = test["value"];
587
588			/**
589			 * Empty uses the PHP's empty operator
590			 */
591			if name == "empty" {
592				return "empty(" . left . ")";
593			}
594
595			/**
596			 * Check if a value is even
597			 */
598			if name == "even" {
599				return "(((" . left . ") % 2) == 0)";
600			}
601
602			/**
603			 * Check if a value is odd
604			 */
605			if name == "odd" {
606				return "(((" . left . ") % 2) != 0)";
607			}
608
609			/**
610			 * Check if a value is numeric
611			 */
612			if name == "numeric" {
613				return "is_numeric(" . left . ")";
614			}
615
616			/**
617			 * Check if a value is scalar
618			 */
619			if name == "scalar" {
620				return "is_scalar(" . left . ")";
621			}
622
623			/**
624			 * Check if a value is iterable
625			 */
626			if name == "iterable" {
627				return "(is_array(" . left . ") || (" . left . ") instanceof Traversable)";
628			}
629
630		}
631
632		/**
633		 * Check if right part is a function call
634		 */
635		if type == PHVOLT_T_FCALL {
636
637			let testName = test["name"];
638			if fetch name, testName["value"] {
639
640				if name == "divisibleby" {
641					return "(((" . left . ") % (" . this->expression(test["arguments"]) . ")) == 0)";
642				}
643
644				/**
645				 * Checks if a value is equals to other
646				 */
647				if name == "sameas" {
648					return "(" . left . ") === (" . this->expression(test["arguments"]) . ")";
649				}
650
651				/**
652				 * Checks if a variable match a type
653				 */
654				if name == "type" {
655					return "gettype(" . left . ") === (" . this->expression(test["arguments"]) . ")";
656				}
657			}
658		}
659
660		/**
661		 * Fall back to the equals operator
662		 */
663		return left . " == " . this->expression(test);
664	}
665
666	/**
667	 * Resolves filter intermediate code into PHP function calls
668	 */
669	final protected function resolveFilter(array! filter, string left) -> string
670	{
671		var code, type, functionName, name, file, line,
672			extensions, filters, funcArguments, arguments, definition;
673
674		let code = null, type = filter["type"];
675
676		/**
677		 * Check if the filter is a single identifier
678		 */
679		if type == PHVOLT_T_IDENTIFIER {
680			let name = filter["value"];
681		} else {
682
683			if type != PHVOLT_T_FCALL {
684
685				/**
686				 * Unknown filter throw an exception
687				 */
688				throw new Exception("Unknown filter type in " . filter["file"] . " on line " . filter["line"]);
689			}
690
691			let functionName = filter["name"],
692				name = functionName["value"];
693		}
694
695		let funcArguments = null, arguments = null;
696
697		/**
698		 * Resolve arguments
699		 */
700		if fetch funcArguments, filter["arguments"] {
701
702			/**
703			 * "default" filter is not the first argument, improve this!
704			 */
705			if name != "default" {
706
707				let file = filter["file"], line = filter["line"];
708
709				/**
710				 * TODO: Implement this function directly
711				 */
712				array_unshift(funcArguments, [
713					"expr": [
714						"type":  364,
715						"value": left,
716						"file": file,
717						"line": line
718					],
719					"file": file,
720					"line": line
721				]);
722			}
723
724			let arguments = this->expression(funcArguments);
725		} else {
726			let arguments = left;
727		}
728
729		/**
730		 * Check if any of the registered extensions provide compilation for this filter
731		 */
732		let extensions = this->_extensions;
733		if typeof extensions == "array" {
734
735			/**
736			 * Notify the extensions about being compiling a function
737			 */
738			let code = this->fireExtensionEvent("compileFilter", [name, arguments, funcArguments]);
739			if typeof code == "string" {
740				return code;
741			}
742		}
743
744		/**
745		 * Check if it's a user defined filter
746		 */
747		let filters = this->_filters;
748		if typeof filters == "array" {
749			if fetch definition, filters[name] {
750
751				/**
752				 * The definition is a string
753				 */
754				if typeof definition == "string" {
755					return definition . "(" . arguments . ")";
756				}
757
758				/**
759				 * The definition is a closure
760				 */
761				if typeof definition == "object" {
762					if definition instanceof \Closure {
763						return call_user_func_array(definition, [arguments, funcArguments]);
764					}
765				}
766
767				/**
768				 * Invalid filter definition throw an exception
769				 */
770				throw new Exception(
771					"Invalid definition for user filter '" . name . "' in " . filter["file"] . " on line " . filter["line"]
772				);
773			}
774		}
775
776		/**
777		 * "length" uses the length method implemented in the Volt adapter
778		 */
779		if name == "length" {
780			return "$this->length(" . arguments . ")";
781		}
782
783		/**
784		 * "e"/"escape" filter uses the escaper component
785		 */
786		if name == "e" || name == "escape" {
787			return "$this->escaper->escapeHtml(" . arguments . ")";
788		}
789
790		/**
791		 * "escape_css" filter uses the escaper component to filter css
792		 */
793		if name == "escape_css" {
794			return "$this->escaper->escapeCss(" . arguments . ")";
795		}
796
797		/**
798		 * "escape_js" filter uses the escaper component to escape javascript
799		 */
800		if name == "escape_js" {
801			return "$this->escaper->escapeJs(" . arguments . ")";
802		}
803
804		/**
805		 * "escape_attr" filter uses the escaper component to escape html attributes
806		 */
807		if name == "escape_attr" {
808			return "$this->escaper->escapeHtmlAttr(" . arguments . ")";
809		}
810
811		/**
812		 * "trim" calls the "trim" function in the PHP userland
813		 */
814		if name == "trim" {
815			return "trim(" . arguments . ")";
816		}
817
818		/**
819		 * "left_trim" calls the "ltrim" function in the PHP userland
820		 */
821		if name == "left_trim" {
822			return "ltrim(" . arguments . ")";
823		}
824
825		/**
826		 * "right_trim" calls the "rtrim" function in the PHP userland
827		 */
828		if name == "right_trim" {
829			return "rtrim(" . arguments . ")";
830		}
831
832		/**
833		 * "striptags" calls the "strip_tags" function in the PHP userland
834		 */
835		if name == "striptags" {
836			return "strip_tags(" . arguments . ")";
837		}
838
839		/**
840		 * "url_encode" calls the "urlencode" function in the PHP userland
841		 */
842		if name == "url_encode" {
843			return "urlencode(" . arguments . ")";
844		}
845
846		/**
847		 * "slashes" calls the "addslashes" function in the PHP userland
848		 */
849		if name == "slashes" {
850			return "addslashes(" . arguments . ")";
851		}
852
853		/**
854		 * "stripslashes" calls the "stripslashes" function in the PHP userland
855		 */
856		if name == "stripslashes" {
857			return "stripslashes(" . arguments . ")";
858		}
859
860		/**
861		 * "nl2br" calls the "nl2br" function in the PHP userland
862		 */
863		if name == "nl2br" {
864			return "nl2br(" . arguments . ")";
865		}
866
867		/**
868		 * "keys" uses calls the "array_keys" function in the PHP userland
869		 */
870		if name == "keys" {
871			return "array_keys(" . arguments . ")";
872		}
873
874		/**
875		 * "join" uses calls the "join" function in the PHP userland
876		 */
877		if name == "join" {
878			return "join(" . arguments . ")";
879		}
880
881		/**
882		 * "lower"/"lowercase" calls the "strtolower" function or "mb_strtolower" if the mbstring extension is loaded
883		 */
884		if name == "lower" || name == "lowercase" {
885			return "Phalcon\\Text::lower(" . arguments . ")";
886		}
887
888		/**
889		 * "upper"/"uppercase" calls the "strtoupper" function or "mb_strtoupper" if the mbstring extension is loaded
890		 */
891		if name == "upper" || name == "uppercase" {
892			return "Phalcon\\Text::upper(" . arguments . ")";
893		}
894
895		/**
896		 * "capitalize" filter calls "ucwords"
897		 */
898		if name == "capitalize" {
899			return "ucwords(" . arguments . ")";
900		}
901
902		/**
903		 * "sort" calls "sort" method in the engine adapter
904		 */
905		if name == "sort" {
906			return "$this->sort(" . arguments . ")";
907		}
908
909		/**
910		 * "json_encode" calls the "json_encode" function in the PHP userland
911		 */
912		if name == "json_encode" {
913			return "json_encode(" . arguments . ")";
914		}
915
916		/**
917		 * "json_decode" calls the "json_decode" function in the PHP userland
918		 */
919		if name == "json_decode" {
920			return "json_decode(" . arguments . ")";
921		}
922
923		/**
924		 * "format" calls the "sprintf" function in the PHP userland
925		 */
926		if name == "format" {
927			return "sprintf(" . arguments . ")";
928		}
929
930		/**
931		 * "abs" calls the "abs" function in the PHP userland
932		 */
933		if name == "abs" {
934			return "abs(" . arguments . ")";
935		}
936
937		/**
938		 * "slice" slices string/arrays/traversable objects
939		 */
940		if name == "slice" {
941			return "$this->slice(" . arguments . ")";
942		}
943
944		/**
945		 * "default" checks if a variable is empty
946		 */
947		if name == "default" {
948			return "(empty(" . left . ") ? (" . arguments . ") : (" . left . "))";
949		}
950
951		/**
952		 * This function uses mbstring or iconv to convert strings from one charset to another
953		 */
954		if name == "convert_encoding" {
955			return "$this->convertEncoding(" . arguments . ")";
956		}
957
958		/**
959		 * Unknown filter throw an exception
960		 */
961		throw new Exception("Unknown filter \"" . name . "\" in " . filter["file"] . " on line " . filter["line"]);
962	}
963
964	/**
965	 * Resolves an expression node in an AST volt tree
966	 */
967	final public function expression(array! expr) -> string
968	{
969		var exprCode, extensions, items, singleExpr, singleExprCode, name,
970			left, leftCode, right, rightCode, type, startCode, endCode, start, end;
971
972		let exprCode = null, this->_exprLevel++;
973
974		/**
975		 * Check if any of the registered extensions provide compilation for this expression
976		 */
977		let extensions = this->_extensions;
978
979		loop {
980
981			if typeof extensions == "array" {
982
983				/**
984				 * Notify the extensions about being resolving an expression
985				 */
986				let exprCode = this->fireExtensionEvent("resolveExpression", [expr]);
987				if typeof exprCode == "string" {
988					break;
989				}
990			}
991
992			if !fetch type, expr["type"] {
993				let items = [];
994				for singleExpr in expr {
995					let singleExprCode = this->expression(singleExpr["expr"]);
996					if fetch name, singleExpr["name"] {
997						let items[] = "'" . name . "' => " . singleExprCode;
998					} else {
999						let items[] = singleExprCode;
1000					}
1001				}
1002				let exprCode = join(", ", items);
1003				break;
1004			}
1005
1006			/**
1007			 * Attribute reading needs special handling
1008			 */
1009			if type == PHVOLT_T_DOT {
1010				let exprCode = this->attributeReader(expr);
1011				break;
1012			}
1013
1014			/**
1015			 * Left part of expression is always resolved
1016			 */
1017			if fetch left, expr["left"] {
1018				let leftCode = this->expression(left);
1019			}
1020
1021			/**
1022			 * Operator "is" also needs special handling
1023			 */
1024			if type == PHVOLT_T_IS {
1025				let exprCode = this->resolveTest(expr["right"], leftCode);
1026				break;
1027			}
1028
1029			/**
1030			 * We don't resolve the right expression for filters
1031			 */
1032			if type == 124 {
1033				let exprCode = this->resolveFilter(expr["right"], leftCode);
1034				break;
1035			}
1036
1037			/**
1038			 * From here, right part of expression is always resolved
1039			 */
1040			if fetch right, expr["right"] {
1041				let rightCode = this->expression(right);
1042			}
1043
1044			let exprCode = null;
1045			switch type {
1046
1047				case PHVOLT_T_NOT:
1048					let exprCode = "!" . rightCode;
1049					break;
1050
1051				case PHVOLT_T_MUL:
1052					let exprCode = leftCode . " * " . rightCode;
1053					break;
1054
1055				case PHVOLT_T_ADD:
1056					let exprCode = leftCode . " + " . rightCode;
1057					break;
1058
1059				case PHVOLT_T_SUB:
1060					let exprCode = leftCode . " - " . rightCode;
1061					break;
1062
1063				case PHVOLT_T_DIV:
1064					let exprCode = leftCode . " / " . rightCode;
1065					break;
1066
1067				case 37:
1068					let exprCode = leftCode . " % " . rightCode;
1069					break;
1070
1071				case PHVOLT_T_LESS:
1072					let exprCode = leftCode . " < " . rightCode;
1073					break;
1074
1075				case 61:
1076					let exprCode = leftCode . " > " . rightCode;
1077					break;
1078
1079				case 62:
1080					let exprCode = leftCode . " > " . rightCode;
1081					break;
1082
1083				case 126:
1084					let exprCode = leftCode . " . " . rightCode;
1085					break;
1086
1087				case 278:
1088					let exprCode = "pow(" . leftCode . ", " . rightCode . ")";
1089					break;
1090
1091				case PHVOLT_T_ARRAY:
1092					if isset expr["left"] {
1093						let exprCode = "[" . leftCode . "]";
1094					} else {
1095						let exprCode = "[]";
1096					}
1097					break;
1098
1099				case 258:
1100					let exprCode = expr["value"];
1101					break;
1102
1103				case 259:
1104					let exprCode = expr["value"];
1105					break;
1106
1107				case PHVOLT_T_STRING:
1108					let exprCode = "'" . str_replace("'", "\\'", expr["value"]) . "'";
1109					break;
1110
1111				case PHVOLT_T_NULL:
1112					let exprCode = "null";
1113					break;
1114
1115				case PHVOLT_T_FALSE:
1116					let exprCode = "false";
1117					break;
1118
1119				case PHVOLT_T_TRUE:
1120					let exprCode = "true";
1121					break;
1122
1123				case PHVOLT_T_IDENTIFIER:
1124					let exprCode = "$" . expr["value"];
1125					break;
1126
1127				case PHVOLT_T_AND:
1128					let exprCode = leftCode . " && " . rightCode;
1129					break;
1130
1131				case 267:
1132					let exprCode = leftCode . " || " . rightCode;
1133					break;
1134
1135				case PHVOLT_T_LESSEQUAL:
1136					let exprCode = leftCode . " <= " . rightCode;
1137					break;
1138
1139				case 271:
1140					let exprCode = leftCode . " >= " . rightCode;
1141					break;
1142
1143				case 272:
1144					let exprCode = leftCode . " == " . rightCode;
1145					break;
1146
1147				case 273:
1148					let exprCode = leftCode . " != " . rightCode;
1149					break;
1150
1151				case 274:
1152					let exprCode = leftCode . " === " . rightCode;
1153					break;
1154
1155				case 275:
1156					let exprCode = leftCode . " !== " . rightCode;
1157					break;
1158
1159				case PHVOLT_T_RANGE:
1160					let exprCode = "range(" . leftCode . ", " . rightCode . ")";
1161					break;
1162
1163				case PHVOLT_T_FCALL:
1164					let exprCode = this->functionCall(expr);
1165					break;
1166
1167				case PHVOLT_T_ENCLOSED:
1168					let exprCode = "(" . leftCode . ")";
1169					break;
1170
1171				case PHVOLT_T_ARRAYACCESS:
1172					let exprCode = leftCode . "[" . rightCode . "]";
1173					break;
1174
1175				case PHVOLT_T_SLICE:
1176
1177					/**
1178					 * Evaluate the start part of the slice
1179					 */
1180					if fetch start, expr["start"] {
1181						let startCode = this->expression(start);
1182					} else {
1183						let startCode = "null";
1184					}
1185
1186					/**
1187					 * Evaluate the end part of the slice
1188					 */
1189					if fetch end, expr["end"] {
1190						let endCode = this->expression(end);
1191					} else {
1192						let endCode = "null";
1193					}
1194
1195					let exprCode = "$this->slice(" . leftCode . ", " . startCode . ", " . endCode . ")";
1196					break;
1197
1198				case PHVOLT_T_NOT_ISSET:
1199					let exprCode = "!isset(" . leftCode . ")";
1200					break;
1201
1202				case PHVOLT_T_ISSET:
1203					let exprCode = "isset(" . leftCode . ")";
1204					break;
1205
1206				case PHVOLT_T_NOT_ISEMPTY:
1207					let exprCode = "!empty(" . leftCode . ")";
1208					break;
1209
1210				case PHVOLT_T_ISEMPTY:
1211					let exprCode = "empty(" . leftCode . ")";
1212					break;
1213
1214				case PHVOLT_T_NOT_ISEVEN:
1215					let exprCode = "!(((" . leftCode . ") % 2) == 0)";
1216					break;
1217
1218				case PHVOLT_T_ISEVEN:
1219					let exprCode = "(((" . leftCode . ") % 2) == 0)";
1220					break;
1221
1222				case PHVOLT_T_NOT_ISODD:
1223					let exprCode = "!(((" . leftCode . ") % 2) != 0)";
1224					break;
1225
1226				case PHVOLT_T_ISODD:
1227					let exprCode = "(((" . leftCode . ") % 2) != 0)";
1228					break;
1229
1230				case PHVOLT_T_NOT_ISNUMERIC:
1231					let exprCode = "!is_numeric(" . leftCode . ")";
1232					break;
1233
1234				case PHVOLT_T_ISNUMERIC:
1235					let exprCode = "is_numeric(" . leftCode . ")";
1236					break;
1237
1238				case PHVOLT_T_NOT_ISSCALAR:
1239					let exprCode = "!is_scalar(" . leftCode . ")";
1240					break;
1241
1242				case PHVOLT_T_ISSCALAR:
1243					let exprCode = "is_scalar(" . leftCode . ")";
1244					break;
1245
1246				case PHVOLT_T_NOT_ISITERABLE:
1247					let exprCode = "!(is_array(" . leftCode . ") || (" . leftCode . ") instanceof Traversable)";
1248					break;
1249
1250				case PHVOLT_T_ISITERABLE:
1251					let exprCode = "(is_array(" . leftCode . ") || (" . leftCode . ") instanceof Traversable)";
1252					break;
1253
1254				case PHVOLT_T_IN:
1255					let exprCode = "$this->isIncluded(" . leftCode . ", " . rightCode . ")";
1256					break;
1257
1258				case PHVOLT_T_NOT_IN:
1259					let exprCode = "!$this->isIncluded(" . leftCode . ", " . rightCode . ")";
1260					break;
1261
1262				case PHVOLT_T_TERNARY:
1263					let exprCode = "(" . this->expression(expr["ternary"]) . " ? " . leftCode . " : " . rightCode . ")";
1264					break;
1265
1266				case PHVOLT_T_MINUS:
1267					let exprCode = "-" . rightCode;
1268					break;
1269
1270				case PHVOLT_T_PLUS:
1271					let exprCode = "+" . rightCode;
1272					break;
1273
1274				case PHVOLT_T_RESOLVED_EXPR:
1275					let exprCode = expr["value"];
1276					break;
1277
1278				default:
1279					throw new Exception("Unknown expression " . type . " in " . expr["file"] . " on line " . expr["line"]);
1280			}
1281
1282			break;
1283		}
1284
1285		let this->_exprLevel--;
1286
1287		return exprCode;
1288	}
1289
1290	/**
1291	 * Compiles a block of statements
1292	 *
1293	 * @param array statements
1294	 * @return string|array
1295	 */
1296	final protected function _statementListOrExtends(var statements)
1297	{
1298		var statement;
1299		boolean isStatementList;
1300
1301		/**
1302		 * Resolve the statement list as normal
1303		 */
1304		if typeof statements != "array" {
1305			return statements;
1306		}
1307
1308		/**
1309		 * If all elements in the statement list are arrays we resolve this as a statementList
1310		 */
1311		let isStatementList = true;
1312		if !isset statements["type"] {
1313			for statement in statements {
1314				if typeof statement != "array" {
1315					let isStatementList = false;
1316					break;
1317				}
1318			}
1319		}
1320
1321		/**
1322		 * Resolve the statement list as normal
1323		 */
1324		if isStatementList === true {
1325			return this->_statementList(statements);
1326		}
1327
1328		/**
1329		 * Is an array but not a statement list?
1330		 */
1331		return statements;
1332	}
1333
1334	/**
1335	 * Compiles a "foreach" intermediate code representation into plain PHP code
1336	 */
1337	public function compileForeach(array! statement, boolean extendsMode = false) -> string
1338	{
1339		var compilation, prefix, level, prefixLevel, expr,
1340			exprCode, bstatement, type, blockStatements, forElse, code,
1341			loopContext, iterator, key, ifExpr, variable;
1342
1343		/**
1344		 * A valid expression is required
1345		 */
1346		if !isset statement["expr"] {
1347			throw new Exception("Corrupted statement");
1348		}
1349
1350		let compilation = "", forElse = null;
1351
1352		let this->_foreachLevel++;
1353
1354		let prefix = this->getUniquePrefix();
1355		let level = this->_foreachLevel;
1356
1357		/**
1358		 * prefixLevel is used to prefix every temporal variable
1359		 */
1360		let prefixLevel = prefix . level;
1361
1362		/**
1363		 * Evaluate common expressions
1364		 */
1365		let expr = statement["expr"];
1366		let exprCode = this->expression(expr);
1367
1368		/**
1369		 * Process the block statements
1370		 */
1371		let blockStatements = statement["block_statements"];
1372
1373		let forElse = false;
1374		if typeof blockStatements == "array" {
1375
1376			for bstatement in blockStatements {
1377
1378				if typeof bstatement != "array" {
1379					break;
1380				}
1381
1382				/**
1383				 * Check if the statement is valid
1384				 */
1385				if !fetch type, bstatement["type"] {
1386					break;
1387				}
1388
1389				if type == PHVOLT_T_ELSEFOR {
1390					let compilation .= "<?php $" . prefixLevel . "iterated = false; ?>";
1391					let forElse = prefixLevel;
1392					let this->_forElsePointers[level] = forElse;
1393					break;
1394				}
1395
1396			}
1397		}
1398
1399		/**
1400		 * Process statements block
1401		 */
1402		let code = this->_statementList(blockStatements, extendsMode);
1403
1404		let loopContext = this->_loopPointers;
1405
1406		/**
1407		 * Generate the loop context for the "foreach"
1408		 */
1409		if isset loopContext[level] {
1410			let compilation .= "<?php $" . prefixLevel . "iterator = " . exprCode . "; ";
1411			let compilation .= "$" . prefixLevel . "incr = 0; ";
1412			let compilation .= "$" . prefixLevel . "loop = new stdClass(); ";
1413			let compilation .= "$" . prefixLevel . "loop->self = &$" . prefixLevel . "loop; ";
1414			let compilation .= "$" . prefixLevel . "loop->length = count($" . prefixLevel . "iterator); ";
1415			let compilation .= "$" . prefixLevel . "loop->index = 1; ";
1416			let compilation .= "$" . prefixLevel . "loop->index0 = 1; ";
1417			let compilation .= "$" . prefixLevel . "loop->revindex = $" . prefixLevel . "loop->length; ";
1418			let compilation .= "$" . prefixLevel . "loop->revindex0 = $" . prefixLevel . "loop->length - 1; ?>";
1419			let iterator = "$" . prefixLevel . "iterator";
1420		} else {
1421			let iterator = exprCode;
1422		}
1423
1424		/**
1425		 * Foreach statement
1426		 */
1427		let variable = statement["variable"];
1428
1429		/**
1430		 * Check if a "key" variable needs to be calculated
1431		 */
1432		if fetch key, statement["key"] {
1433			let compilation .= "<?php foreach (" . iterator . " as $" . key . " => $" . variable . ") { ";
1434		} else {
1435			let compilation .= "<?php foreach (" . iterator . " as $" . variable . ") { ";
1436		}
1437
1438		/**
1439		 * Check for an "if" expr in the block
1440		 */
1441		if fetch ifExpr, statement["if_expr"] {
1442			let compilation .= "if (" . this->expression(ifExpr) . ") { ?>";
1443		} else {
1444			let compilation .= "?>";
1445		}
1446
1447		/**
1448		 * Generate the loop context inside the cycle
1449		 */
1450		if isset loopContext[level] {
1451			let compilation .= "<?php $" . prefixLevel . "loop->first = ($" . prefixLevel . "incr == 0); ";
1452			let compilation .= "$" . prefixLevel . "loop->index = $" . prefixLevel . "incr + 1; ";
1453			let compilation .= "$" . prefixLevel . "loop->index0 = $" . prefixLevel . "incr; ";
1454			let compilation .= "$" . prefixLevel . "loop->revindex = $" . prefixLevel . "loop->length - $" . prefixLevel . "incr; ";
1455			let compilation .= "$" . prefixLevel . "loop->revindex0 = $" . prefixLevel . "loop->length - ($" . prefixLevel . "incr + 1); ";
1456			let compilation .= "$" . prefixLevel . "loop->last = ($" . prefixLevel . "incr == ($" . prefixLevel . "loop->length - 1)); ?>";
1457		}
1458
1459		/**
1460		 * Update the forelse var if it's iterated at least one time
1461		 */
1462		if typeof forElse == "string" {
1463			let compilation .= "<?php $" . forElse . "iterated = true; ?>";
1464		}
1465
1466		/**
1467		 * Append the internal block compilation
1468		 */
1469		let compilation .= code;
1470
1471		if isset statement["if_expr"] {
1472			let compilation .= "<?php } ?>";
1473		}
1474
1475		if typeof forElse == "string" {
1476			let compilation .= "<?php } ?>";
1477		} else {
1478			if isset loopContext[level] {
1479				let compilation .= "<?php $" . prefixLevel . "incr++; } ?>";
1480			} else {
1481				let compilation .= "<?php } ?>";
1482			}
1483		}
1484
1485		let this->_foreachLevel--;
1486
1487		return compilation;
1488	}
1489
1490	/**
1491	 * Generates a 'forelse' PHP code
1492	 */
1493	public function compileForElse() -> string
1494	{
1495		var level, prefix;
1496
1497		let level = this->_foreachLevel;
1498		if fetch prefix, this->_forElsePointers[level] {
1499			if isset this->_loopPointers[level] {
1500				return "<?php $" . prefix . "incr++; } if (!$" . prefix . "iterated) { ?>";
1501			}
1502			return "<?php } if (!$" . prefix . "iterated) { ?>";
1503		}
1504		return "";
1505	}
1506
1507	/**
1508	 * Compiles a 'if' statement returning PHP code
1509	 */
1510	public function compileIf(array! statement, boolean extendsMode = false) -> string
1511	{
1512		var compilation, blockStatements, expr;
1513
1514		/**
1515		 * A valid expression is required
1516		 */
1517		if !fetch expr, statement["expr"] {
1518			throw new Exception("Corrupt statement", statement);
1519		}
1520
1521		/**
1522		 * Process statements in the "true" block
1523		 */
1524		let compilation = "<?php if (" . this->expression(expr) . ") { ?>" . this->_statementList(statement["true_statements"], extendsMode);
1525
1526		/**
1527		 * Check for a "else"/"elseif" block
1528		 */
1529		if fetch blockStatements, statement["false_statements"] {
1530
1531			/**
1532			 * Process statements in the "false" block
1533			 */
1534			let compilation .= "<?php } else { ?>" . this->_statementList(blockStatements, extendsMode);
1535		}
1536
1537		let compilation .= "<?php } ?>";
1538
1539		return compilation;
1540	}
1541
1542	/**
1543	 * Compiles a 'switch' statement returning PHP code
1544	 */
1545	public function compileSwitch(array! statement, boolean extendsMode = false) -> string
1546	{
1547		var compilation, caseClauses, expr, lines;
1548
1549		/**
1550		 * A valid expression is required
1551		 */
1552		if !fetch expr, statement["expr"] {
1553			throw new Exception("Corrupt statement", statement);
1554		}
1555
1556		/**
1557		 * Process statements in the "true" block
1558		 */
1559		let compilation = "<?php switch (" . this->expression(expr) . "): ?>";
1560
1561		/**
1562		 * Check for a "case"/"default" blocks
1563		 */
1564		if fetch caseClauses, statement["case_clauses"] {
1565			let lines = this->_statementList(caseClauses, extendsMode);
1566
1567			/**
1568			 * Any output (including whitespace) between a switch statement and the first case will result in
1569			 * a syntax error. This is the responsibility of the user. However, we can clear empty lines
1570			 * and whitespaces here to reduce the number of errors.
1571			 *
1572			 * http://php.net/control-structures.alternative-syntax
1573			 */
1574			 if strlen(lines) !== 0 {
1575				/**
1576				 * (*ANYCRLF) - specifies a newline convention: (*CR), (*LF) or (*CRLF)
1577				 * \h+ - 1+ horizontal whitespace chars
1578				 * $ - end of line (now, before CR or LF)
1579				 * m - multiline mode on ($ matches at the end of a line).
1580				 * u - unicode
1581				 *
1582				 * g - global search, - is implicit with preg_replace(), you don't need to include it.
1583				 */
1584				let lines = preg_replace("/(*ANYCRLF)^\h+|\h+$|(\h){2,}/mu", "", lines);
1585			 }
1586
1587			let compilation .= lines;
1588		}
1589
1590		let compilation .= "<?php endswitch ?>";
1591
1592		return compilation;
1593	}
1594
1595	/**
1596	 * Compiles a "case"/"default" clause returning PHP code
1597	 */
1598	public function compileCase(array! statement, bool caseClause = true) -> string
1599	{
1600		var expr;
1601
1602		if unlikely caseClause === false {
1603			/**
1604			 * "default" statement
1605			 */
1606			return "<?php default: ?>";
1607		}
1608
1609		/**
1610		 * A valid expression is required
1611		 */
1612		if !fetch expr, statement["expr"] {
1613			throw new Exception("Corrupt statement", statement);
1614		}
1615
1616		/**
1617		 * "case" statement
1618		 */
1619		return "<?php case " . this->expression(expr) . ": ?>";
1620	}
1621
1622	/**
1623	 * Compiles a "elseif" statement returning PHP code
1624	 */
1625	public function compileElseIf(array! statement) -> string
1626	{
1627		var expr;
1628
1629		/**
1630		 * A valid expression is required
1631		 */
1632		if !fetch expr, statement["expr"] {
1633			throw new Exception("Corrupt statement", statement);
1634		}
1635
1636		/**
1637		 * "elseif" statement
1638		 */
1639		return "<?php } elseif (" . this->expression(expr) . ") { ?>";
1640	}
1641
1642	/**
1643	 * Compiles a "cache" statement returning PHP code
1644	 */
1645	public function compileCache(array! statement, boolean extendsMode = false) -> string
1646	{
1647		var compilation, expr, exprCode, lifetime;
1648
1649		/**
1650		 * A valid expression is required
1651		 */
1652		if !fetch expr, statement["expr"] {
1653			throw new Exception("Corrupt statement", statement);
1654		}
1655
1656		/**
1657		 * Cache statement
1658		 */
1659		let exprCode = this->expression(expr);
1660		let compilation = "<?php $_cache[" . this->expression(expr) . "] = $this->di->get('viewCache'); ";
1661		if fetch lifetime, statement["lifetime"] {
1662			let compilation .= "$_cacheKey[" . exprCode . "]";
1663			if lifetime["type"] == PHVOLT_T_IDENTIFIER {
1664				let compilation .= " = $_cache[" . exprCode . "]->start(" . exprCode . ", $" . lifetime["value"] . "); ";
1665			} else {
1666				let compilation .= " = $_cache[" . exprCode . "]->start(" . exprCode . ", " . lifetime["value"] . "); ";
1667			}
1668		} else {
1669			let compilation .= "$_cacheKey[" . exprCode . "] = $_cache[" . exprCode."]->start(" . exprCode . "); ";
1670		}
1671		let compilation .= "if ($_cacheKey[" . exprCode . "] === null) { ?>";
1672
1673		/**
1674		 * Get the code in the block
1675		 */
1676		let compilation .= this->_statementList(statement["block_statements"], extendsMode);
1677
1678		/**
1679		 * Check if the cache has a lifetime
1680		 */
1681		if fetch lifetime, statement["lifetime"] {
1682			if lifetime["type"] == PHVOLT_T_IDENTIFIER {
1683				let compilation .= "<?php $_cache[" . exprCode . "]->save(" . exprCode . ", null, $" . lifetime["value"] . "); ";
1684			} else {
1685				let compilation .= "<?php $_cache[" . exprCode . "]->save(" . exprCode . ", null, " . lifetime["value"] . "); ";
1686			}
1687			let compilation .= "} else { echo $_cacheKey[" . exprCode . "]; } ?>";
1688		} else {
1689			let compilation .= "<?php $_cache[" . exprCode . "]->save(" . exprCode . "); } else { echo $_cacheKey[" . exprCode . "]; } ?>";
1690		}
1691
1692		return compilation;
1693	}
1694
1695	/**
1696	 * Compiles a "set" statement returning PHP code
1697	 */
1698	public function compileSet(array! statement) -> string
1699	{
1700		var assignments, assignment, exprCode, target, compilation;
1701
1702		/**
1703		 * A valid assignment list is required
1704		 */
1705		if !fetch assignments, statement["assignments"] {
1706			throw new Exception("Corrupted statement");
1707		}
1708
1709		let compilation = "<?php";
1710
1711		/**
1712		 * A single set can have several assignments
1713		 */
1714		for assignment in assignments {
1715
1716			let exprCode = this->expression(assignment["expr"]);
1717
1718			/**
1719			 * Resolve the expression assigned
1720			 */
1721			let target = this->expression(assignment["variable"]);
1722
1723			/**
1724			 * Assignment operator
1725			 * Generate the right operator
1726			 */
1727			switch assignment["op"] {
1728
1729				case PHVOLT_T_ADD_ASSIGN:
1730					let compilation .= " " . target . " += " . exprCode . ";";
1731					break;
1732
1733				case PHVOLT_T_SUB_ASSIGN:
1734					let compilation .= " " . target . " -= " . exprCode . ";";
1735					break;
1736
1737				case PHVOLT_T_MUL_ASSIGN:
1738					let compilation .= " " . target . " *= " . exprCode . ";";
1739					break;
1740
1741				case PHVOLT_T_DIV_ASSIGN:
1742					let compilation .= " " . target . " /= " . exprCode . ";";
1743					break;
1744
1745				default:
1746					let compilation .= " " . target . " = " . exprCode . ";";
1747					break;
1748			}
1749
1750		}
1751
1752		let compilation .= " ?>";
1753		return compilation;
1754	}
1755
1756	/**
1757	 * Compiles a "do" statement returning PHP code
1758	 */
1759	public function compileDo(array! statement) -> string
1760	{
1761		var expr;
1762
1763		/**
1764		 * A valid expression is required
1765		 */
1766		if !fetch expr, statement["expr"] {
1767			throw new Exception("Corrupted statement");
1768		}
1769
1770		/**
1771		 * "Do" statement
1772		 */
1773		return "<?php " . this->expression(expr) . "; ?>";
1774	}
1775
1776	/**
1777	 * Compiles a "return" statement returning PHP code
1778	 */
1779	public function compileReturn(array! statement) -> string
1780	{
1781		var expr;
1782
1783		/**
1784		 * A valid expression is required
1785		 */
1786		if !fetch expr, statement["expr"] {
1787			throw new Exception("Corrupted statement");
1788		}
1789
1790		/**
1791		 * "Return" statement
1792		 */
1793		return "<?php return " . this->expression(expr) . "; ?>";
1794	}
1795
1796	/**
1797	 * Compiles a "autoescape" statement returning PHP code
1798	 */
1799	public function compileAutoEscape(array! statement, boolean extendsMode) -> string
1800	{
1801		var autoescape, oldAutoescape, compilation;
1802
1803		/**
1804		 * A valid option is required
1805		 */
1806		if !fetch autoescape, statement["enable"] {
1807			throw new Exception("Corrupted statement");
1808		}
1809
1810		/**
1811		 * "autoescape" mode
1812		 */
1813		let oldAutoescape = this->_autoescape,
1814			this->_autoescape = autoescape;
1815
1816		let compilation = this->_statementList(statement["block_statements"], extendsMode),
1817			this->_autoescape = oldAutoescape;
1818
1819		return compilation;
1820	}
1821
1822	/**
1823	 * Compiles a '{{' '}}' statement returning PHP code
1824	 *
1825	 * @param array   statement
1826	 * @param boolean extendsMode
1827	 * @return string
1828	 */
1829	public function compileEcho(array! statement) -> string
1830	{
1831		var expr, exprCode, name;
1832
1833		/**
1834		 * A valid expression is required
1835		 */
1836		if !fetch expr, statement["expr"] {
1837			throw new Exception("Corrupt statement", statement);
1838		}
1839
1840		/**
1841		 * Evaluate common expressions
1842		 */
1843		let exprCode = this->expression(expr);
1844
1845		if expr["type"] == PHVOLT_T_FCALL  {
1846
1847			let name = expr["name"];
1848
1849			if name["type"] == PHVOLT_T_IDENTIFIER {
1850
1851				/**
1852				 * super() is a function however the return of this function must be output as it is
1853				 */
1854				if name["value"] == "super" {
1855					return exprCode;
1856				}
1857			}
1858		}
1859
1860		/**
1861		 * Echo statement
1862		 */
1863		if this->_autoescape {
1864			return "<?= $this->escaper->escapeHtml(" . exprCode . ") ?>";
1865		}
1866
1867		return "<?= " . exprCode . " ?>";
1868	}
1869
1870	/**
1871	 * Compiles a 'include' statement returning PHP code
1872	 */
1873	public function compileInclude(array! statement) -> string
1874	{
1875		var pathExpr, path, subCompiler, finalPath, compilation, params;
1876
1877		/**
1878		 * Include statement
1879		 * A valid expression is required
1880		 */
1881		if !fetch pathExpr, statement["path"] {
1882			throw new Exception("Corrupted statement");
1883		}
1884
1885		/**
1886		 * Check if the expression is a string
1887		 * If the path is an string try to make an static compilation
1888		 */
1889		if pathExpr["type"] == 260 {
1890
1891			/**
1892			 * Static compilation cannot be performed if the user passed extra parameters
1893			 */
1894			if !isset statement["params"]  {
1895
1896				/**
1897				 * Get the static path
1898				 */
1899				let path = pathExpr["value"];
1900
1901				let finalPath = this->getFinalPath(path);
1902
1903				/**
1904				 * Clone the original compiler
1905				 * Perform a sub-compilation of the included file
1906				 * If the compilation doesn't return anything we include the compiled path
1907				 */
1908				let subCompiler = clone this;
1909				let compilation = subCompiler->compile(finalPath, false);
1910				if typeof compilation == "null" {
1911
1912					/**
1913					 * Use file-get-contents to respect the openbase_dir directive
1914					 */
1915					let compilation = file_get_contents(subCompiler->getCompiledTemplatePath());
1916				}
1917
1918				return compilation;
1919			}
1920
1921		}
1922
1923		/**
1924		 * Resolve the path's expression
1925		 */
1926		let path = this->expression(pathExpr);
1927
1928		/**
1929		 * Use partial
1930		 */
1931		if !fetch params, statement["params"] {
1932			return "<?php $this->partial(" . path . "); ?>";
1933		}
1934
1935		return "<?php $this->partial(" . path . ", " . this->expression(params) . "); ?>";
1936	}
1937
1938	/**
1939	 * Compiles macros
1940	 */
1941	public function compileMacro(array! statement, boolean extendsMode) -> string
1942	{
1943		var code, name, defaultValue, macroName, parameters, position, parameter, variableName, blockStatements;
1944
1945		/**
1946		 * A valid name is required
1947		 */
1948		if !fetch name, statement["name"] {
1949			throw new Exception("Corrupted statement");
1950		}
1951
1952		/**
1953		 * Check if the macro is already defined
1954		 */
1955		if isset this->_macros[name] {
1956			throw new Exception("Macro '" . name . "' is already defined");
1957		}
1958
1959		/**
1960		 * Register the macro
1961		 */
1962		let this->_macros[name] = name;
1963
1964		let macroName = "$this->_macros['" . name . "']";
1965
1966		let code = "<?php ";
1967
1968		if !fetch parameters, statement["parameters"] {
1969			let code .= macroName . " = function() { ?>";
1970		} else {
1971
1972			/**
1973			 * Parameters are always received as an array
1974			 */
1975			let code .= macroName . " = function($__p = null) { ";
1976			for position, parameter in parameters {
1977
1978				let variableName = parameter["variable"];
1979
1980				let code .= "if (isset($__p[" . position . "])) { ";
1981				let code .= "$" . variableName . " = $__p[" . position ."];";
1982				let code .= " } else { ";
1983				let code .= "if (isset($__p[\"" . variableName."\"])) { ";
1984				let code .= "$" . variableName . " = $__p[\"" . variableName ."\"];";
1985				let code .= " } else { ";
1986				if fetch defaultValue, parameter["default"] {
1987					let code .= "$" . variableName . " = " . this->expression(defaultValue) . ";";
1988				} else {
1989					let code .= " throw new \\Phalcon\\Mvc\\View\\Exception(\"Macro '" . name . "' was called without parameter: " . variableName . "\"); ";
1990				}
1991				let code .= " } } ";
1992			}
1993
1994			let code .= " ?>";
1995		}
1996
1997		/**
1998		 * Block statements are allowed
1999		 */
2000		if fetch blockStatements, statement["block_statements"] {
2001
2002			/**
2003			 * Process statements block
2004			 */
2005			let code .= this->_statementList(blockStatements, extendsMode) . "<?php }; ";
2006		}  else {
2007			let code .= "<?php }; ";
2008		}
2009
2010		/**
2011		 * Bind the closure to the $this object allowing to call services
2012		 */
2013		let code .= macroName . " = \\Closure::bind(" . macroName . ", $this); ?>";
2014
2015		return code;
2016	}
2017
2018	/**
2019	 * Compiles calls to macros
2020	 *
2021	 * @param array    statement
2022	 * @param boolean  extendsMode
2023	 * @return string
2024	 */
2025	public function compileCall(array! statement, boolean extendsMode)
2026	{
2027
2028	}
2029
2030	/**
2031	 * Traverses a statement list compiling each of its nodes
2032	 */
2033	final protected function _statementList(array! statements, boolean extendsMode = false) -> string
2034	{
2035		var extended, blockMode, compilation, extensions,
2036			statement, tempCompilation, type, blockName, blockStatements,
2037			blocks, path, finalPath, subCompiler, level;
2038
2039		/**
2040		 * Nothing to compile
2041		 */
2042		if !count(statements) {
2043			return "";
2044		}
2045
2046		/**
2047		 * Increase the statement recursion level in extends mode
2048		 */
2049		let extended = this->_extended;
2050		let blockMode = extended || extendsMode;
2051		if blockMode === true {
2052			let this->_blockLevel++;
2053		}
2054
2055		let this->_level++;
2056
2057		let compilation = null;
2058
2059		let extensions = this->_extensions;
2060		for statement in statements {
2061
2062			/**
2063			 * All statements must be arrays
2064			 */
2065			if typeof statement != "array" {
2066				throw new Exception("Corrupted statement");
2067			}
2068
2069			/**
2070			 * Check if the statement is valid
2071			 */
2072			if !isset statement["type"] {
2073				throw new Exception("Invalid statement in " . statement["file"] . " on line " . statement["line"], statement);
2074			}
2075
2076			/**
2077			 * Check if extensions have implemented custom compilation for this statement
2078			 */
2079			if typeof extensions == "array" {
2080
2081				/**
2082				 * Notify the extensions about being resolving a statement
2083				 */
2084				let tempCompilation = this->fireExtensionEvent("compileStatement", [statement]);
2085				if typeof tempCompilation == "string" {
2086					let compilation .= tempCompilation;
2087					continue;
2088				}
2089			}
2090
2091			/**
2092			 * Get the statement type
2093			 */
2094			let type = statement["type"];
2095
2096			/**
2097			 * Compile the statement according to the statement's type
2098			 */
2099			switch type {
2100
2101				case PHVOLT_T_RAW_FRAGMENT:
2102					let compilation .= statement["value"];
2103					break;
2104
2105				case PHVOLT_T_IF:
2106					let compilation .= this->compileIf(statement, extendsMode);
2107					break;
2108
2109				case PHVOLT_T_ELSEIF:
2110					let compilation .= this->compileElseIf(statement);
2111					break;
2112
2113				case PHVOLT_T_SWITCH:
2114					let compilation .= this->compileSwitch(statement, extendsMode);
2115					break;
2116
2117				case PHVOLT_T_CASE:
2118					let compilation .= this->compileCase(statement);
2119					break;
2120
2121				case PHVOLT_T_DEFAULT:
2122					let compilation .= this->compileCase(statement, false);
2123					break;
2124
2125				case PHVOLT_T_FOR:
2126					let compilation .= this->compileForeach(statement, extendsMode);
2127					break;
2128
2129				case PHVOLT_T_SET:
2130					let compilation .= this->compileSet(statement);
2131					break;
2132
2133				case PHVOLT_T_ECHO:
2134					let compilation .= this->compileEcho(statement);
2135					break;
2136
2137				case PHVOLT_T_BLOCK:
2138
2139					/**
2140					 * Block statement
2141					 */
2142					let blockName = statement["name"];
2143
2144					fetch blockStatements, statement["block_statements"];
2145
2146					let blocks = this->_blocks;
2147					if blockMode {
2148
2149						if typeof blocks != "array" {
2150							let blocks = [];
2151						}
2152
2153						/**
2154						 * Create a unamed block
2155						 */
2156						if typeof compilation != "null" {
2157							let blocks[] = compilation;
2158							let compilation = null;
2159						}
2160
2161						/**
2162						 * In extends mode we add the block statements to the blocks variable
2163						 */
2164						let blocks[blockName] = blockStatements;
2165						let this->_blocks = blocks;
2166
2167					} else {
2168						if typeof blockStatements == "array" {
2169							let compilation .= this->_statementList(blockStatements, extendsMode);
2170						}
2171					}
2172					break;
2173
2174				case PHVOLT_T_EXTENDS:
2175
2176					/**
2177					 * Extends statement
2178					 */
2179					let path = statement["path"];
2180
2181					let finalPath = this->getFinalPath(path["value"]);
2182
2183					let extended = true;
2184
2185					/**
2186					 * Perform a sub-compilation of the extended file
2187					 */
2188					let subCompiler = clone this;
2189					let tempCompilation = subCompiler->compile(finalPath, extended);
2190
2191					/**
2192					 * If the compilation doesn't return anything we include the compiled path
2193					 */
2194					if typeof tempCompilation == "null" {
2195						let tempCompilation = file_get_contents(subCompiler->getCompiledTemplatePath());
2196					}
2197
2198					let this->_extended = true;
2199					let this->_extendedBlocks = tempCompilation;
2200					let blockMode = extended;
2201					break;
2202
2203				case PHVOLT_T_INCLUDE:
2204					let compilation .= this->compileInclude(statement);
2205					break;
2206
2207				case PHVOLT_T_CACHE:
2208					let compilation .= this->compileCache(statement, extendsMode);
2209					break;
2210
2211				case PHVOLT_T_DO:
2212					let compilation .= this->compileDo(statement);
2213					break;
2214
2215				case PHVOLT_T_RETURN:
2216					let compilation .= this->compileReturn(statement);
2217					break;
2218
2219				case PHVOLT_T_AUTOESCAPE:
2220					let compilation .= this->compileAutoEscape(statement, extendsMode);
2221					break;
2222
2223				case PHVOLT_T_CONTINUE:
2224					/**
2225					 * "Continue" statement
2226					 */
2227					let compilation .= "<?php continue; ?>";
2228					break;
2229
2230				case PHVOLT_T_BREAK:
2231					/**
2232					 * "Break" statement
2233					 */
2234					let compilation .= "<?php break; ?>";
2235					break;
2236
2237				case 321:
2238					/**
2239					 * "Forelse" condition
2240					 */
2241					let compilation .= this->compileForElse();
2242					break;
2243
2244				case PHVOLT_T_MACRO:
2245					/**
2246					 * Define a macro
2247					 */
2248					let compilation .= this->compileMacro(statement, extendsMode);
2249					break;
2250
2251				case 325:
2252					/**
2253					 * "Call" statement
2254					 */
2255					let compilation .= this->compileCall(statement, extendsMode);
2256					break;
2257
2258				case 358:
2259					/**
2260					 * Empty statement
2261					 */
2262					break;
2263
2264				default:
2265					throw new Exception("Unknown statement " . type . " in " . statement["file"] . " on line " . statement["line"]);
2266
2267			}
2268		}
2269
2270		/**
2271		 * Reduce the statement level nesting
2272		 */
2273		if blockMode === true {
2274			let level = this->_blockLevel;
2275			if level == 1 {
2276				if typeof compilation != "null" {
2277					let this->_blocks[] = compilation;
2278				}
2279			}
2280			let this->_blockLevel--;
2281		}
2282
2283		let this->_level--;
2284
2285		return compilation;
2286	}
2287
2288	/**
2289	 * Compiles a Volt source code returning a PHP plain version
2290	 */
2291	protected function _compileSource(string! viewCode, boolean extendsMode = false) -> string
2292	{
2293		var currentPath, intermediate, extended,
2294			finalCompilation, blocks, extendedBlocks, name, block,
2295			blockCompilation, localBlock, compilation, options, autoescape;
2296
2297		let currentPath = this->_currentPath;
2298
2299		/**
2300		 * Check for compilation options
2301		 */
2302		let options = this->_options;
2303		if typeof options == "array" {
2304
2305			/**
2306			 * Enable autoescape globally
2307			 */
2308			if fetch autoescape, options["autoescape"] {
2309				if typeof autoescape != "bool" {
2310					throw new Exception("'autoescape' must be boolean");
2311				}
2312				let this->_autoescape = autoescape;
2313			}
2314		}
2315
2316		let intermediate = phvolt_parse_view(viewCode, currentPath);
2317
2318		/**
2319		 * The parsing must return a valid array
2320		 */
2321		if typeof intermediate != "array" {
2322			throw new Exception("Invalid intermediate representation");
2323		}
2324
2325		let compilation = this->_statementList(intermediate, extendsMode);
2326
2327		/**
2328		 * Check if the template is extending another
2329		 */
2330		let extended = this->_extended;
2331		if extended === true {
2332
2333			/**
2334			 * Multiple-Inheritance is allowed
2335			 */
2336			if extendsMode === true {
2337				let finalCompilation = [];
2338			} else {
2339				let finalCompilation = null;
2340			}
2341
2342			let blocks = this->_blocks;
2343			let extendedBlocks = this->_extendedBlocks;
2344
2345			for name, block in extendedBlocks {
2346
2347				/**
2348				 * If name is a string then is a block name
2349				 */
2350				if typeof name == "string" {
2351
2352					if isset blocks[name] {
2353						/**
2354						 * The block is set in the local template
2355						 */
2356						let localBlock = blocks[name],
2357							this->_currentBlock = name,
2358							blockCompilation = this->_statementList(localBlock);
2359					} else {
2360						if typeof block == "array" {
2361							/**
2362							 * The block is not set local only in the extended template
2363							 */
2364							let blockCompilation = this->_statementList(block);
2365						} else {
2366							let blockCompilation = block;
2367						}
2368					}
2369
2370					if extendsMode === true {
2371						let finalCompilation[name] = blockCompilation;
2372					} else {
2373						let finalCompilation .= blockCompilation;
2374					}
2375				} else {
2376
2377					/**
2378					 * Here the block is an already compiled text
2379					 */
2380					if extendsMode === true {
2381						let finalCompilation[] = block;
2382					} else {
2383						let finalCompilation .= block;
2384					}
2385				}
2386			}
2387
2388			return finalCompilation;
2389		}
2390
2391		if extendsMode === true {
2392			/**
2393			 * In extends mode we return the template blocks instead of the compilation
2394			 */
2395			return this->_blocks;
2396		}
2397		return compilation;
2398	}
2399
2400	/**
2401	 * Compiles a template into a string
2402	 *
2403	 *<code>
2404	 * echo $compiler->compileString('{{ "hello world" }}');
2405	 *</code>
2406	 */
2407	public function compileString(string! viewCode, boolean extendsMode = false) -> string
2408	{
2409		let this->_currentPath = "eval code";
2410		return this->_compileSource(viewCode, extendsMode);
2411	}
2412
2413	/**
2414	 * Compiles a template into a file forcing the destination path
2415	 *
2416	 *<code>
2417	 * $compiler->compileFile("views/layouts/main.volt", "views/layouts/main.volt.php");
2418	 *</code>
2419	 *
2420	 * @param string path
2421	 * @param string compiledPath
2422	 * @param boolean extendsMode
2423	 * @return string|array
2424	 */
2425	public function compileFile(string! path, string! compiledPath, boolean extendsMode = false)
2426	{
2427		var viewCode, compilation, finalCompilation;
2428
2429		if path == compiledPath {
2430			throw new Exception("Template path and compilation template path cannot be the same");
2431		}
2432
2433		/**
2434		 * Check if the template does exist
2435		 */
2436		if !file_exists(path) {
2437			throw new Exception("Template file " . path . " does not exist");
2438		}
2439
2440		/**
2441		 * Always use file_get_contents instead of read the file directly, this respect the open_basedir directive
2442		 */
2443		let viewCode = file_get_contents(path);
2444		if viewCode === false {
2445			throw new Exception("Template file " . path . " could not be opened");
2446		}
2447
2448		let this->_currentPath = path;
2449		let compilation = this->_compileSource(viewCode, extendsMode);
2450
2451		/**
2452		 * We store the file serialized if it's an array of blocks
2453		 */
2454		if typeof compilation == "array" {
2455			let finalCompilation = serialize(compilation);
2456		} else {
2457			let finalCompilation = compilation;
2458		}
2459
2460		/**
2461		 * Always use file_put_contents to write files instead of write the file
2462		 * directly, this respect the open_basedir directive
2463		 */
2464		if file_put_contents(compiledPath, finalCompilation) === false {
2465			throw new Exception("Volt directory can't be written");
2466		}
2467
2468		return compilation;
2469	}
2470
2471	/**
2472	 * Compiles a template into a file applying the compiler options
2473	 * This method does not return the compiled path if the template was not compiled
2474	 *
2475	 *<code>
2476	 * $compiler->compile("views/layouts/main.volt");
2477	 *
2478	 * require $compiler->getCompiledTemplatePath();
2479	 *</code>
2480	 */
2481	public function compile(string! templatePath, boolean extendsMode = false)
2482	{
2483		var stat, compileAlways, prefix, compiledPath, compiledSeparator, blocksCode,
2484			compiledExtension, compilation, options, realCompiledPath,
2485			compiledTemplatePath, templateSepPath;
2486
2487		/**
2488		 * Re-initialize some properties already initialized when the object is cloned
2489		 */
2490		let this->_extended = false;
2491		let this->_extendedBlocks = false;
2492		let this->_blocks = null;
2493		let this->_level = 0;
2494		let this->_foreachLevel = 0;
2495		let this->_blockLevel = 0;
2496		let this->_exprLevel = 0;
2497
2498		let stat = true;
2499		let compileAlways = false;
2500		let compiledPath = "";
2501		let prefix = null;
2502		let compiledSeparator = "%%";
2503		let compiledExtension = ".php";
2504		let compilation = null;
2505
2506		let options = this->_options;
2507		if typeof options == "array" {
2508
2509			/**
2510			 * This makes that templates will be compiled always
2511			 */
2512			if isset options["compileAlways"] {
2513				let compileAlways = options["compileAlways"];
2514				if typeof compileAlways != "boolean" {
2515					throw new Exception("'compileAlways' must be a bool value");
2516				}
2517			}
2518
2519			/**
2520			 * Prefix is prepended to the template name
2521			 */
2522			if isset options["prefix"] {
2523				let prefix = options["prefix"];
2524				if typeof prefix != "string" {
2525					throw new Exception("'prefix' must be a string");
2526				}
2527			}
2528
2529			/**
2530			 * Compiled path is a directory where the compiled templates will be located
2531			 */
2532			if isset options["compiledPath"] {
2533				let compiledPath = options["compiledPath"];
2534				if typeof compiledPath != "string" {
2535					if typeof compiledPath != "object" {
2536						throw new Exception("'compiledPath' must be a string or a closure");
2537					}
2538				}
2539			}
2540
2541			/**
2542			 * There is no compiled separator by default
2543			 */
2544			if isset options["compiledSeparator"] {
2545				let compiledSeparator = options["compiledSeparator"];
2546				if typeof compiledSeparator != "string" {
2547					throw new Exception("'compiledSeparator' must be a string");
2548				}
2549			}
2550
2551			/**
2552			 * By default the compile extension is .php
2553			 */
2554			if isset options["compiledExtension"] {
2555				let compiledExtension = options["compiledExtension"];
2556				if typeof compiledExtension != "string" {
2557					throw new Exception("'compiledExtension' must be a string");
2558				}
2559			}
2560
2561			/**
2562			 * Stat option assumes the compilation of the file
2563			 */
2564			if isset options["stat"] {
2565				let stat = options["stat"];
2566			}
2567		}
2568
2569		/**
2570		 * Check if there is a compiled path
2571		 */
2572		if typeof compiledPath == "string" {
2573
2574			/**
2575			 * Calculate the template realpath's
2576			 */
2577			if !empty compiledPath {
2578				/**
2579				 * Create the virtual path replacing the directory separator by the compiled separator
2580				 */
2581				let templateSepPath = prepare_virtual_path(realpath(templatePath), compiledSeparator);
2582			} else {
2583				let templateSepPath = templatePath;
2584			}
2585
2586			/**
2587			 * In extends mode we add an additional 'e' suffix to the file
2588			 */
2589			if extendsMode === true {
2590				let compiledTemplatePath = compiledPath . prefix . templateSepPath . compiledSeparator . "e" . compiledSeparator . compiledExtension;
2591			} else {
2592				let compiledTemplatePath = compiledPath . prefix . templateSepPath . compiledExtension;
2593			}
2594
2595		} else {
2596
2597			/**
2598			 * A closure can dynamically compile the path
2599			 */
2600			if typeof compiledPath == "object" {
2601
2602				if compiledPath instanceof \Closure {
2603
2604					let compiledTemplatePath = call_user_func_array(compiledPath, [templatePath, options, extendsMode]);
2605
2606					/**
2607					 * The closure must return a valid path
2608					 */
2609					if typeof compiledTemplatePath != "string" {
2610						throw new Exception("compiledPath closure didn't return a valid string");
2611					}
2612				} else {
2613					throw new Exception("compiledPath must be a string or a closure");
2614				}
2615			}
2616		}
2617
2618		/**
2619		 * Use the real path to avoid collisions
2620		 */
2621		let realCompiledPath = compiledTemplatePath;
2622
2623		if compileAlways {
2624
2625			/**
2626			 * Compile always must be used only in the development stage
2627			 */
2628			let compilation = this->compileFile(templatePath, realCompiledPath, extendsMode);
2629		} else {
2630			if stat === true {
2631				if file_exists(compiledTemplatePath) {
2632
2633					/**
2634					 * Compare modification timestamps to check if the file needs to be recompiled
2635					 */
2636					if compare_mtime(templatePath, realCompiledPath) {
2637						let compilation = this->compileFile(templatePath, realCompiledPath, extendsMode);
2638					} else {
2639
2640						if extendsMode === true {
2641
2642							/**
2643							 * In extends mode we read the file that must contains a serialized array of blocks
2644							 */
2645							let blocksCode = file_get_contents(realCompiledPath);
2646							if blocksCode === false {
2647								throw new Exception("Extends compilation file " . realCompiledPath . " could not be opened");
2648							}
2649
2650							/**
2651							 * Unserialize the array blocks code
2652							 */
2653							if blocksCode {
2654								let compilation = unserialize(blocksCode);
2655							} else {
2656								let compilation = [];
2657							}
2658						}
2659					}
2660				} else {
2661
2662					/**
2663					 * The file doesn't exist so we compile the php version for the first time
2664					 */
2665					let compilation = this->compileFile(templatePath, realCompiledPath, extendsMode);
2666				}
2667			} else {
2668
2669				/**
2670				 * Stat is off but the compiled file doesn't exist
2671				 */
2672				if !file_exists(realCompiledPath) {
2673					/**
2674					 * The file doesn't exist so we compile the php version for the first time
2675					 */
2676					let compilation = this->compileFile(templatePath, realCompiledPath, extendsMode);
2677				}
2678
2679			}
2680		}
2681
2682		let this->_compiledTemplatePath = realCompiledPath;
2683
2684		return compilation;
2685	}
2686
2687	/**
2688	 * Returns the path that is currently being compiled
2689	 */
2690	public function getTemplatePath() -> string
2691	{
2692		return this->_currentPath;
2693	}
2694
2695	/**
2696	 * Returns the path to the last compiled template
2697	 */
2698	public function getCompiledTemplatePath() -> string
2699	{
2700		return this->_compiledTemplatePath;
2701	}
2702
2703	/**
2704	 * Parses a Volt template returning its intermediate representation
2705	 *
2706	 *<code>
2707	 * print_r(
2708	 *     $compiler->parse("{{ 3 + 2 }}")
2709	 * );
2710	 *</code>
2711	 *
2712	 * @param string viewCode
2713	 * @return array
2714	 */
2715	public function parse(string! viewCode)
2716	{
2717		var currentPath = "eval code";
2718		return phvolt_parse_view(viewCode, currentPath);
2719	}
2720
2721	/**
2722	 * Gets the final path with VIEW
2723	 */
2724	protected function getFinalPath(string path)
2725	{
2726		var view, viewsDirs, viewsDir;
2727		let view = this->_view;
2728
2729		if typeof view == "object" {
2730			let viewsDirs = view->getViewsDir();
2731
2732			if typeof viewsDirs == "array" {
2733				for viewsDir in viewsDirs {
2734					if file_exists(viewsDir . path) {
2735						return viewsDir . path;
2736					}
2737				}
2738
2739				// Otherwise, take the last viewsDir
2740				return viewsDir . path;
2741
2742			} else {
2743				return viewsDirs . path;
2744			}
2745		}
2746
2747		return path;
2748	}
2749}
2750