1<?php
2
3include dirname(__FILE__) . '/Compilation/Exception.php';
4
5/**
6 * default dwoo compiler class, compiles dwoo templates into php
7 *
8 * This software is provided 'as-is', without any express or implied warranty.
9 * In no event will the authors be held liable for any damages arising from the use of this software.
10 *
11 * @author     Jordi Boggiano <j.boggiano@seld.be>
12 * @copyright  Copyright (c) 2008, Jordi Boggiano
13 * @license    http://dwoo.org/LICENSE   Modified BSD License
14 * @link       http://dwoo.org/
15 * @version    1.1.0
16 * @date       2009-07-18
17 * @package    Dwoo
18 */
19class Dwoo_Compiler implements Dwoo_ICompiler
20{
21	/**
22	 * constant that represents a php opening tag
23	 *
24	 * use it in case it needs to be adjusted
25	 *
26	 * @var string
27	 */
28	const PHP_OPEN = "<?php ";
29
30	/**
31	 * constant that represents a php closing tag
32	 *
33	 * use it in case it needs to be adjusted
34	 *
35	 * @var string
36	 */
37	const PHP_CLOSE = "?>";
38
39	/**
40	 * boolean flag to enable or disable debugging output
41	 *
42	 * @var bool
43	 */
44	public $debug = false;
45
46	/**
47	 * left script delimiter
48	 *
49	 * @var string
50	 */
51	protected $ld = '{';
52
53	/**
54	 * left script delimiter with escaped regex meta characters
55	 *
56	 * @var string
57	 */
58	protected $ldr = '\\{';
59
60	/**
61	 * right script delimiter
62	 *
63	 * @var string
64	 */
65	protected $rd = '}';
66
67	/**
68	 * right script delimiter with escaped regex meta characters
69	 *
70	 * @var string
71	 */
72	protected $rdr = '\\}';
73
74	/**
75	 * defines whether the nested comments should be parsed as nested or not
76	 *
77	 * defaults to false (classic block comment parsing as in all languages)
78	 *
79	 * @var bool
80	 */
81	protected $allowNestedComments = false;
82
83	/**
84	 * defines whether opening and closing tags can contain spaces before valid data or not
85	 *
86	 * turn to true if you want to be sloppy with the syntax, but when set to false it allows
87	 * to skip javascript and css tags as long as they are in the form "{ something", which is
88	 * nice. default is false.
89	 *
90	 * @var bool
91	 */
92	protected $allowLooseOpenings = false;
93
94	/**
95	 * defines whether the compiler will automatically html-escape variables or not
96	 *
97	 * default is false
98	 *
99	 * @var bool
100	 */
101	protected $autoEscape = false;
102
103	/**
104	 * security policy object
105	 *
106	 * @var Dwoo_Security_Policy
107	 */
108	protected $securityPolicy;
109
110	/**
111	 * stores the custom plugins registered with this compiler
112	 *
113	 * @var array
114	 */
115	protected $customPlugins = array();
116
117	/**
118	 * stores the template plugins registered with this compiler
119	 *
120	 * @var array
121	 */
122	protected $templatePlugins = array();
123
124	/**
125	 * stores the pre- and post-processors callbacks
126	 *
127	 * @var array
128	 */
129	protected $processors = array('pre'=>array(), 'post'=>array());
130
131	/**
132	 * stores a list of plugins that are used in the currently compiled
133	 * template, and that are not compilable. these plugins will be loaded
134	 * during the template's runtime if required.
135	 *
136	 * it is a 1D array formatted as key:pluginName value:pluginType
137	 *
138	 * @var array
139	 */
140	protected $usedPlugins;
141
142	/**
143	 * stores the template undergoing compilation
144	 *
145	 * @var string
146	 */
147	protected $template;
148
149	/**
150	 * stores the current pointer position inside the template
151	 *
152	 * @var int
153	 */
154	protected $pointer;
155
156	/**
157	 * stores the current line count inside the template for debugging purposes
158	 *
159	 * @var int
160	 */
161	protected $line;
162
163	/**
164	 * stores the current template source while compiling it
165	 *
166	 * @var string
167	 */
168	protected $templateSource;
169
170	/**
171	 * stores the data within which the scope moves
172	 *
173	 * @var array
174	 */
175	protected $data;
176
177	/**
178	 * variable scope of the compiler, set to null if
179	 * it can not be resolved to a static string (i.e. if some
180	 * plugin defines a new scope based on a variable array key)
181	 *
182	 * @var mixed
183	 */
184	protected $scope;
185
186	/**
187	 * variable scope tree, that allows to rebuild the current
188	 * scope if required, i.e. when going to a parent level
189	 *
190	 * @var array
191	 */
192	protected $scopeTree;
193
194	/**
195	 * block plugins stack, accessible through some methods
196	 *
197	 * @see findBlock
198	 * @see getCurrentBlock
199	 * @see addBlock
200	 * @see addCustomBlock
201	 * @see injectBlock
202	 * @see removeBlock
203	 * @see removeTopBlock
204	 *
205	 * @var array
206	 */
207	protected $stack = array();
208
209	/**
210	 * current block at the top of the block plugins stack,
211	 * accessible through getCurrentBlock
212	 *
213	 * @see getCurrentBlock
214	 *
215	 * @var Dwoo_Block_Plugin
216	 */
217	protected $curBlock;
218
219	/**
220	 * current dwoo object that uses this compiler, or null
221	 *
222	 * @var Dwoo
223	 */
224	protected $dwoo;
225
226	/**
227	 * holds an instance of this class, used by getInstance when you don't
228	 * provide a custom compiler in order to save resources
229	 *
230	 * @var Dwoo_Compiler
231	 */
232	protected static $instance;
233
234	/**
235	 * sets the delimiters to use in the templates
236	 *
237	 * delimiters can be multi-character strings but should not be one of those as they will
238	 * make it very hard to work with templates or might even break the compiler entirely : "\", "$", "|", ":" and finally "#" only if you intend to use config-vars with the #var# syntax.
239	 *
240	 * @param string $left left delimiter
241	 * @param string $right right delimiter
242	 */
243	public function setDelimiters($left, $right)
244	{
245		$this->ld = $left;
246		$this->rd = $right;
247		$this->ldr = preg_quote($left, '/');
248		$this->rdr = preg_quote($right, '/');
249	}
250
251	/**
252	 * returns the left and right template delimiters
253	 *
254	 * @return array containing the left and the right delimiters
255	 */
256	public function getDelimiters()
257	{
258		return array($this->ld, $this->rd);
259	}
260
261	/**
262	 * sets the way to handle nested comments, if set to true
263	 * {* foo {* some other *} comment *} will be stripped correctly.
264	 *
265	 * if false it will remove {* foo {* some other *} and leave "comment *}" alone,
266	 * this is the default behavior
267	 *
268	 * @param bool $allow allow nested comments or not, defaults to true (but the default internal value is false)
269	 */
270	public function setNestedCommentsHandling($allow = true) {
271		$this->allowNestedComments = (bool) $allow;
272	}
273
274	/**
275	 * returns the nested comments handling setting
276	 *
277	 * @see setNestedCommentsHandling
278	 * @return bool true if nested comments are allowed
279	 */
280	public function getNestedCommentsHandling() {
281		return $this->allowNestedComments;
282	}
283
284	/**
285	 * sets the tag openings handling strictness, if set to true, template tags can
286	 * contain spaces before the first function/string/variable such as { $foo} is valid.
287	 *
288	 * if set to false (default setting), { $foo} is invalid but that is however a good thing
289	 * as it allows css (i.e. #foo { color:red; }) to be parsed silently without triggering
290	 * an error, same goes for javascript.
291	 *
292	 * @param bool $allow true to allow loose handling, false to restore default setting
293	 */
294	public function setLooseOpeningHandling($allow = false)
295	{
296		$this->allowLooseOpenings = (bool) $allow;
297	}
298
299	/**
300	 * returns the tag openings handling strictness setting
301	 *
302	 * @see setLooseOpeningHandling
303	 * @return bool true if loose tags are allowed
304	 */
305	public function getLooseOpeningHandling()
306	{
307		return $this->allowLooseOpenings;
308	}
309
310	/**
311	 * changes the auto escape setting
312	 *
313	 * if enabled, the compiler will automatically html-escape variables,
314	 * unless they are passed through the safe function such as {$var|safe}
315	 * or {safe $var}
316	 *
317	 * default setting is disabled/false
318	 *
319	 * @param bool $enabled set to true to enable, false to disable
320	 */
321	public function setAutoEscape($enabled)
322	{
323		$this->autoEscape = (bool) $enabled;
324	}
325
326	/**
327	 * returns the auto escape setting
328	 *
329	 * default setting is disabled/false
330	 *
331	 * @return bool
332	 */
333	public function getAutoEscape()
334	{
335		return $this->autoEscape;
336	}
337
338	/**
339	 * adds a preprocessor to the compiler, it will be called
340	 * before the template is compiled
341	 *
342	 * @param mixed $callback either a valid callback to the preprocessor or a simple name if the autoload is set to true
343	 * @param bool $autoload if set to true, the preprocessor is auto-loaded from one of the plugin directories, else you must provide a valid callback
344	 */
345	public function addPreProcessor($callback, $autoload = false)
346	{
347		if ($autoload) {
348			$name = str_replace('Dwoo_Processor_', '', $callback);
349			$class = 'Dwoo_Processor_'.$name;
350
351			if (class_exists($class, false)) {
352				$callback = array(new $class($this), 'process');
353			} elseif (function_exists($class)) {
354				$callback = $class;
355			} else {
356				$callback = array('autoload'=>true, 'class'=>$class, 'name'=>$name);
357			}
358
359			$this->processors['pre'][] = $callback;
360		} else {
361			$this->processors['pre'][] = $callback;
362		}
363	}
364
365	/**
366	 * removes a preprocessor from the compiler
367	 *
368	 * @param mixed $callback either a valid callback to the preprocessor or a simple name if it was autoloaded
369	 */
370	public function removePreProcessor($callback)
371	{
372		if (($index = array_search($callback, $this->processors['pre'], true)) !== false) {
373			unset($this->processors['pre'][$index]);
374		} elseif (($index = array_search('Dwoo_Processor_'.str_replace('Dwoo_Processor_', '', $callback), $this->processors['pre'], true)) !== false) {
375			unset($this->processors['pre'][$index]);
376		} else {
377			$class = 'Dwoo_Processor_' . str_replace('Dwoo_Processor_', '', $callback);
378			foreach ($this->processors['pre'] as $index=>$proc) {
379				if (is_array($proc) && ($proc[0] instanceof $class) || (isset($proc['class']) && $proc['class'] == $class)) {
380					unset($this->processors['pre'][$index]);
381					break;
382				}
383			}
384		}
385	}
386
387	/**
388	 * adds a postprocessor to the compiler, it will be called
389	 * before the template is compiled
390	 *
391	 * @param mixed $callback either a valid callback to the postprocessor or a simple name if the autoload is set to true
392	 * @param bool $autoload if set to true, the postprocessor is auto-loaded from one of the plugin directories, else you must provide a valid callback
393	 */
394	public function addPostProcessor($callback, $autoload = false)
395	{
396		if ($autoload) {
397			$name = str_replace('Dwoo_Processor_', '', $callback);
398			$class = 'Dwoo_Processor_'.$name;
399
400			if (class_exists($class, false)) {
401				$callback = array(new $class($this), 'process');
402			} elseif (function_exists($class)) {
403				$callback = $class;
404			} else {
405				$callback = array('autoload'=>true, 'class'=>$class, 'name'=>$name);
406			}
407
408			$this->processors['post'][] = $callback;
409		} else {
410			$this->processors['post'][] = $callback;
411		}
412	}
413
414	/**
415	 * removes a postprocessor from the compiler
416	 *
417	 * @param mixed $callback either a valid callback to the postprocessor or a simple name if it was autoloaded
418	 */
419	public function removePostProcessor($callback)
420	{
421		if (($index = array_search($callback, $this->processors['post'], true)) !== false) {
422			unset($this->processors['post'][$index]);
423		} elseif (($index = array_search('Dwoo_Processor_'.str_replace('Dwoo_Processor_', '', $callback), $this->processors['post'], true)) !== false) {
424			unset($this->processors['post'][$index]);
425		} else	{
426			$class = 'Dwoo_Processor_' . str_replace('Dwoo_Processor_', '', $callback);
427			foreach ($this->processors['post'] as $index=>$proc) {
428				if (is_array($proc) && ($proc[0] instanceof $class) || (isset($proc['class']) && $proc['class'] == $class)) {
429					unset($this->processors['post'][$index]);
430					break;
431				}
432			}
433		}
434	}
435
436	/**
437	 * internal function to autoload processors at runtime if required
438	 *
439	 * @param string $class the class/function name
440	 * @param string $name the plugin name (without Dwoo_Plugin_ prefix)
441	 */
442	protected function loadProcessor($class, $name)
443	{
444		if (!class_exists($class, false) && !function_exists($class)) {
445			try {
446				$this->dwoo->getLoader()->loadPlugin($name);
447			} catch (Dwoo_Exception $e) {
448				throw new Dwoo_Exception('Processor '.$name.' could not be found in your plugin directories, please ensure it is in a file named '.$name.'.php in the plugin directory');
449			}
450		}
451
452		if (class_exists($class, false)) {
453			return array(new $class($this), 'process');
454		}
455
456		if (function_exists($class)) {
457			return $class;
458		}
459
460		throw new Dwoo_Exception('Wrong processor name, when using autoload the processor must be in one of your plugin dir as "name.php" containg a class or function named "Dwoo_Processor_name"');
461	}
462
463	/**
464	 * adds a template plugin, this is reserved for use by the {function} plugin
465	 *
466	 * this is required because the template functions are not declared yet
467	 * during compilation, so we must have a way of validating their argument
468	 * signature without using the reflection api
469	 *
470	 * @private
471	 * @param string $name function name
472	 * @param array $params parameter array to help validate the function call
473	 * @param string $uuid unique id of the function
474	 * @param string $body function php code
475	 */
476	public function addTemplatePlugin($name, array $params, $uuid, $body = null)
477	{
478		$this->templatePlugins[$name] = array('params'=> $params, 'body' => $body, 'uuid' => $uuid);
479	}
480
481	/**
482	 * returns all the parsed sub-templates
483	 *
484	 * @private
485	 * @return array the parsed sub-templates
486	 */
487	public function getTemplatePlugins()
488	{
489		return $this->templatePlugins;
490	}
491
492	/**
493	 * marks a template plugin as being called, which means its source must be included in the compiled template
494	 *
495	 * @param string $name function name
496	 */
497	public function useTemplatePlugin($name)
498	{
499		$this->templatePlugins[$name]['called'] = true;
500	}
501
502	/**
503	 * adds the custom plugins loaded into Dwoo to the compiler so it can load them
504	 *
505	 * @see Dwoo::addPlugin
506	 * @param array $customPlugins an array of custom plugins
507	 */
508	public function setCustomPlugins(array $customPlugins)
509	{
510		$this->customPlugins = $customPlugins;
511	}
512
513	/**
514	 * sets the security policy object to enforce some php security settings
515	 *
516	 * use this if untrusted persons can modify templates,
517	 * set it on the Dwoo object as it will be passed onto the compiler automatically
518	 *
519	 * @param Dwoo_Security_Policy $policy the security policy object
520	 */
521	public function setSecurityPolicy(Dwoo_Security_Policy $policy = null)
522	{
523		$this->securityPolicy = $policy;
524	}
525
526	/**
527	 * returns the current security policy object or null by default
528	 *
529	 * @return Dwoo_Security_Policy|null the security policy object if any
530	 */
531	public function getSecurityPolicy()
532	{
533		return $this->securityPolicy;
534	}
535
536	/**
537	 * sets the pointer position
538	 *
539	 * @param int $position the new pointer position
540	 * @param bool $isOffset if set to true, the position acts as an offset and not an absolute position
541	 */
542	public function setPointer($position, $isOffset = false)
543	{
544		if ($isOffset) {
545			$this->pointer += $position;
546		} else {
547			$this->pointer = $position;
548		}
549	}
550
551	/**
552	 * returns the current pointer position, only available during compilation of a template
553	 *
554	 * @return int
555	 */
556	public function getPointer()
557	{
558		return $this->pointer;
559	}
560
561	/**
562	 * sets the line number
563	 *
564	 * @param int $number the new line number
565	 * @param bool $isOffset if set to true, the position acts as an offset and not an absolute position
566	 */
567	public function setLine($number, $isOffset = false)
568	{
569		if ($isOffset) {
570			$this->line += $number;
571		} else {
572			$this->line = $number;
573		}
574	}
575
576	/**
577	 * returns the current line number, only available during compilation of a template
578	 *
579	 * @return int
580	 */
581	public function getLine()
582	{
583		return $this->line;
584	}
585
586	/**
587	 * returns the dwoo object that initiated this template compilation, only available during compilation of a template
588	 *
589	 * @return Dwoo
590	 */
591	public function getDwoo()
592	{
593		return $this->dwoo;
594	}
595
596	/**
597	 * overwrites the template that is being compiled
598	 *
599	 * @param string $newSource the template source that must replace the current one
600	 * @param bool $fromPointer if set to true, only the source from the current pointer position is replaced
601	 * @return string the template or partial template
602	 */
603	public function setTemplateSource($newSource, $fromPointer = false)
604	{
605		if ($fromPointer === true) {
606			$this->templateSource = substr($this->templateSource, 0, $this->pointer) . $newSource;
607		} else {
608			$this->templateSource = $newSource;
609		}
610	}
611
612	/**
613	 * returns the template that is being compiled
614	 *
615	 * @param mixed $fromPointer if set to true, only the source from the current pointer
616	 * 							  position is returned, if a number is given it overrides the current pointer
617	 * @return string the template or partial template
618	 */
619	public function getTemplateSource($fromPointer = false)
620	{
621		if ($fromPointer === true) {
622			return substr($this->templateSource, $this->pointer);
623		} elseif (is_numeric($fromPointer)) {
624			return substr($this->templateSource, $fromPointer);
625		} else {
626			return $this->templateSource;
627		}
628	}
629
630	/**
631	 * resets the compilation pointer, effectively restarting the compilation process
632	 *
633	 * this is useful if a plugin modifies the template source since it might need to be recompiled
634	 */
635	public function recompile()
636	{
637		$this->setPointer(0);
638	}
639
640	/**
641	 * compiles the provided string down to php code
642	 *
643	 * @param string $tpl the template to compile
644	 * @return string a compiled php string
645	 */
646	public function compile(Dwoo $dwoo, Dwoo_ITemplate $template)
647	{
648		// init vars
649		$tpl = $template->getSource();
650		$ptr = 0;
651		$this->dwoo = $dwoo;
652		$this->template = $template;
653		$this->templateSource =& $tpl;
654		$this->pointer =& $ptr;
655
656		while (true) {
657			// if pointer is at the beginning, reset everything, that allows a plugin to externally reset the compiler if everything must be reparsed
658			if ($ptr===0) {
659				// resets variables
660				$this->usedPlugins = array();
661				$this->data = array();
662				$this->scope =& $this->data;
663				$this->scopeTree = array();
664				$this->stack = array();
665				$this->line = 1;
666				$this->templatePlugins = array();
667				// add top level block
668				$compiled = $this->addBlock('topLevelBlock', array(), 0);
669				$this->stack[0]['buffer'] = '';
670
671				if ($this->debug) echo 'COMPILER INIT<br />';
672
673				if ($this->debug) echo 'PROCESSING PREPROCESSORS ('.count($this->processors['pre']).')<br>';
674
675				// runs preprocessors
676				foreach ($this->processors['pre'] as $preProc) {
677					if (is_array($preProc) && isset($preProc['autoload'])) {
678						$preProc = $this->loadProcessor($preProc['class'], $preProc['name']);
679					}
680					if (is_array($preProc) && $preProc[0] instanceof Dwoo_Processor) {
681						$tpl = call_user_func($preProc, $tpl);
682					} else {
683						$tpl = call_user_func($preProc, $this, $tpl);
684					}
685				}
686				unset($preProc);
687
688				// show template source if debug
689				if ($this->debug) echo '<pre>'.print_r(htmlentities($tpl), true).'</pre><hr />';
690
691				// strips php tags if required by the security policy
692				if ($this->securityPolicy !== null) {
693					$search = array('{<\?php.*?\?>}');
694					if (ini_get('short_open_tags')) {
695						$search = array('{<\?.*?\?>}', '{<%.*?%>}');
696					}
697					switch($this->securityPolicy->getPhpHandling()) {
698
699					case Dwoo_Security_Policy::PHP_ALLOW:
700						break;
701					case Dwoo_Security_Policy::PHP_ENCODE:
702						$tpl = preg_replace_callback($search, array($this, 'phpTagEncodingHelper'), $tpl);
703						break;
704					case Dwoo_Security_Policy::PHP_REMOVE:
705						$tpl = preg_replace($search, '', $tpl);
706
707					}
708				}
709			}
710
711			$pos = strpos($tpl, $this->ld, $ptr);
712
713			if ($pos === false) {
714				$this->push(substr($tpl, $ptr), 0);
715				break;
716			} elseif (substr($tpl, $pos-1, 1) === '\\' && substr($tpl, $pos-2, 1) !== '\\') {
717				$this->push(substr($tpl, $ptr, $pos-$ptr-1) . $this->ld);
718				$ptr = $pos+strlen($this->ld);
719			} elseif (preg_match('/^'.$this->ldr . ($this->allowLooseOpenings ? '\s*' : '') . 'literal' . ($this->allowLooseOpenings ? '\s*' : '') . $this->rdr.'/s', substr($tpl, $pos), $litOpen)) {
720				if (!preg_match('/'.$this->ldr . ($this->allowLooseOpenings ? '\s*' : '') . '\/literal' . ($this->allowLooseOpenings ? '\s*' : '') . $this->rdr.'/s', $tpl, $litClose, PREG_OFFSET_CAPTURE, $pos)) {
721					throw new Dwoo_Compilation_Exception($this, 'The {literal} blocks must be closed explicitly with {/literal}');
722				}
723				$endpos = $litClose[0][1];
724				$this->push(substr($tpl, $ptr, $pos-$ptr) . substr($tpl, $pos + strlen($litOpen[0]), $endpos-$pos-strlen($litOpen[0])));
725				$ptr = $endpos+strlen($litClose[0][0]);
726			} else {
727				if (substr($tpl, $pos-2, 1) === '\\' && substr($tpl, $pos-1, 1) === '\\') {
728					$this->push(substr($tpl, $ptr, $pos-$ptr-1));
729					$ptr = $pos;
730				}
731
732				$this->push(substr($tpl, $ptr, $pos-$ptr));
733				$ptr = $pos;
734
735				$pos += strlen($this->ld);
736				if ($this->allowLooseOpenings) {
737					while (substr($tpl, $pos, 1) === ' ') {
738						$pos+=1;
739					}
740				} else {
741					if (substr($tpl, $pos, 1) === ' ' || substr($tpl, $pos, 1) === "\r" || substr($tpl, $pos, 1) === "\n" || substr($tpl, $pos, 1) === "\t") {
742						$ptr = $pos;
743						$this->push($this->ld);
744						continue;
745					}
746				}
747
748				// check that there is an end tag present
749				if (strpos($tpl, $this->rd, $pos) === false) {
750					throw new Dwoo_Compilation_Exception($this, 'A template tag was not closed, started with "'.substr($tpl, $ptr, 30).'"');
751				}
752
753
754				$ptr += strlen($this->ld);
755				$subptr = $ptr;
756
757				while (true) {
758					$parsed = $this->parse($tpl, $subptr, null, false, 'root', $subptr);
759
760					// reload loop if the compiler was reset
761					if ($ptr === 0) {
762						continue 2;
763					}
764
765					$len = $subptr - $ptr;
766					$this->push($parsed, substr_count(substr($tpl, $ptr, $len), "\n"));
767					$ptr += $len;
768
769					if ($parsed === false) {
770						break;
771					}
772				}
773
774				// adds additional line breaks between php closing and opening tags because the php parser removes those if there is just a single line break
775				if (substr($this->curBlock['buffer'], -2) === '?>' && preg_match('{^(([\r\n])([\r\n]?))}', substr($tpl, $ptr, 3), $m)) {
776					if ($m[3] === '') {
777						$ptr+=1;
778						$this->push($m[1].$m[1], 1);
779					} else {
780						$ptr+=2;
781						$this->push($m[1]."\n", 2);
782					}
783				}
784			}
785		}
786
787		$compiled .= $this->removeBlock('topLevelBlock');
788
789		if ($this->debug) echo 'PROCESSING POSTPROCESSORS<br>';
790
791		foreach ($this->processors['post'] as $postProc) {
792			if (is_array($postProc) && isset($postProc['autoload'])) {
793				$postProc = $this->loadProcessor($postProc['class'], $postProc['name']);
794			}
795			if (is_array($postProc) && $postProc[0] instanceof Dwoo_Processor) {
796				$compiled = call_user_func($postProc, $compiled);
797			} else {
798				$compiled = call_user_func($postProc, $this, $compiled);
799			}
800		}
801		unset($postProc);
802
803		if ($this->debug) echo 'COMPILATION COMPLETE : MEM USAGE : '.memory_get_usage().'<br>';
804
805		$output = "<?php\n";
806
807		// build plugin preloader
808		foreach ($this->usedPlugins as $plugin=>$type) {
809			if ($type & Dwoo::CUSTOM_PLUGIN) {
810				continue;
811			}
812
813			switch($type) {
814
815			case Dwoo::BLOCK_PLUGIN:
816			case Dwoo::CLASS_PLUGIN:
817				$output .= "if (class_exists('Dwoo_Plugin_$plugin', false)===false)\n\t\$this->getLoader()->loadPlugin('$plugin');\n";
818				break;
819			case Dwoo::FUNC_PLUGIN:
820				$output .= "if (function_exists('Dwoo_Plugin_$plugin')===false)\n\t\$this->getLoader()->loadPlugin('$plugin');\n";
821				break;
822			case Dwoo::SMARTY_MODIFIER:
823				$output .= "if (function_exists('smarty_modifier_$plugin')===false)\n\t\$this->getLoader()->loadPlugin('$plugin');\n";
824				break;
825			case Dwoo::SMARTY_FUNCTION:
826				$output .= "if (function_exists('smarty_function_$plugin')===false)\n\t\$this->getLoader()->loadPlugin('$plugin');\n";
827				break;
828			case Dwoo::SMARTY_BLOCK:
829				$output .= "if (function_exists('smarty_block_$plugin')===false)\n\t\$this->getLoader()->loadPlugin('$plugin');\n";
830				break;
831			case Dwoo::PROXY_PLUGIN:
832				$output .= $this->getDwoo()->getPluginProxy()->getPreloader($plugin);
833				break;
834			default:
835				throw new Dwoo_Compilation_Exception($this, 'Type error for '.$plugin.' with type'.$type);
836
837			}
838		}
839
840		foreach ($this->templatePlugins as $function => $attr) {
841			if (isset($attr['called']) && $attr['called'] === true && !isset($attr['checked'])) {
842				$this->resolveSubTemplateDependencies($function);
843			}
844		}
845		foreach ($this->templatePlugins as $function) {
846			if (isset($function['called']) && $function['called'] === true) {
847				$output .= $function['body'].PHP_EOL;
848			}
849		}
850
851		$output .= $compiled."\n?>";
852
853		$output = preg_replace('/(?<!;|\}|\*\/|\n|\{)(\s*'.preg_quote(self::PHP_CLOSE, '/') . preg_quote(self::PHP_OPEN, '/').')/', ";\n", $output);
854		$output = str_replace(self::PHP_CLOSE . self::PHP_OPEN, "\n", $output);
855
856		// handle <?xml tag at the beginning
857		$output = preg_replace('#(/\* template body \*/ \?>\s*)<\?xml#is', '$1<?php echo \'<?xml\'; ?>', $output);
858
859		if ($this->debug) {
860			echo '<hr><pre>';
861			$lines = preg_split('{\r\n|\n|<br />}', highlight_string(($output), true));
862			array_shift($lines);
863			foreach ($lines as $i=>$line) {
864				echo ($i+1).'. '.$line."\r\n";
865			}
866		}
867		if ($this->debug) echo '<hr></pre></pre>';
868
869		$this->template = $this->dwoo = null;
870		$tpl = null;
871
872		return $output;
873	}
874
875	/**
876	 * checks what sub-templates are used in every sub-template so that we're sure they are all compiled
877	 *
878	 * @param string $function the sub-template name
879	 */
880	protected function resolveSubTemplateDependencies($function)
881	{
882		$body = $this->templatePlugins[$function]['body'];
883		foreach ($this->templatePlugins as $func => $attr) {
884			if ($func !== $function && !isset($attr['called']) && strpos($body, 'Dwoo_Plugin_'.$func) !== false) {
885				$this->templatePlugins[$func]['called'] = true;
886				$this->resolveSubTemplateDependencies($func);
887			}
888		}
889		$this->templatePlugins[$function]['checked'] = true;
890	}
891
892	/**
893	 * adds compiled content to the current block
894	 *
895	 * @param string $content the content to push
896	 * @param int $lineCount newlines count in content, optional
897	 */
898	public function push($content, $lineCount = null)
899	{
900		if ($lineCount === null) {
901			$lineCount = substr_count($content, "\n");
902		}
903
904		if ($this->curBlock['buffer'] === null && count($this->stack) > 1) {
905			// buffer is not initialized yet (the block has just been created)
906			$this->stack[count($this->stack)-2]['buffer'] .= (string) $content;
907			$this->curBlock['buffer'] = '';
908		} else {
909			if (!isset($this->curBlock['buffer'])) {
910				throw new Dwoo_Compilation_Exception($this, 'The template has been closed too early, you probably have an extra block-closing tag somewhere');
911			}
912			// append current content to current block's buffer
913			$this->curBlock['buffer'] .= (string) $content;
914		}
915		$this->line += $lineCount;
916	}
917
918	/**
919	 * sets the scope
920	 *
921	 * set to null if the scope becomes "unstable" (i.e. too variable or unknown) so that
922	 * variables are compiled in a more evaluative way than just $this->scope['key']
923	 *
924	 * @param mixed $scope a string i.e. "level1.level2" or an array i.e. array("level1", "level2")
925	 * @param bool $absolute if true, the scope is set from the top level scope and not from the current scope
926	 * @return array the current scope tree
927	 */
928	public function setScope($scope, $absolute = false)
929	{
930		$old = $this->scopeTree;
931
932		if ($scope===null) {
933			unset($this->scope);
934			$this->scope = null;
935		}
936
937		if (is_array($scope)===false) {
938			$scope = explode('.', $scope);
939		}
940
941		if ($absolute===true) {
942			$this->scope =& $this->data;
943			$this->scopeTree = array();
944		}
945
946		while (($bit = array_shift($scope)) !== null) {
947			if ($bit === '_parent' || $bit === '_') {
948				array_pop($this->scopeTree);
949				reset($this->scopeTree);
950				$this->scope =& $this->data;
951				$cnt = count($this->scopeTree);
952				for ($i=0;$i<$cnt;$i++)
953					$this->scope =& $this->scope[$this->scopeTree[$i]];
954			} elseif ($bit === '_root' || $bit === '__') {
955				$this->scope =& $this->data;
956				$this->scopeTree = array();
957			} elseif (isset($this->scope[$bit])) {
958				$this->scope =& $this->scope[$bit];
959				$this->scopeTree[] = $bit;
960			} else {
961				$this->scope[$bit] = array();
962				$this->scope =& $this->scope[$bit];
963				$this->scopeTree[] = $bit;
964			}
965		}
966
967		return $old;
968	}
969
970	/**
971	 * adds a block to the top of the block stack
972	 *
973	 * @param string $type block type (name)
974	 * @param array $params the parameters array
975	 * @param int $paramtype the parameters type (see mapParams), 0, 1 or 2
976	 * @return string the preProcessing() method's output
977	 */
978	public function addBlock($type, array $params, $paramtype)
979	{
980		$class = 'Dwoo_Plugin_'.$type;
981		if (class_exists($class, false) === false) {
982			$this->dwoo->getLoader()->loadPlugin($type);
983		}
984
985		$params = $this->mapParams($params, array($class, 'init'), $paramtype);
986
987		$this->stack[] = array('type' => $type, 'params' => $params, 'custom' => false, 'class' => $class, 'buffer' => null);
988		$this->curBlock =& $this->stack[count($this->stack)-1];
989		return call_user_func(array($class,'preProcessing'), $this, $params, '', '', $type);
990	}
991
992	/**
993	 * adds a custom block to the top of the block stack
994	 *
995	 * @param string $type block type (name)
996	 * @param array $params the parameters array
997	 * @param int $paramtype the parameters type (see mapParams), 0, 1 or 2
998	 * @return string the preProcessing() method's output
999	 */
1000	public function addCustomBlock($type, array $params, $paramtype)
1001	{
1002		$callback = $this->customPlugins[$type]['callback'];
1003		if (is_array($callback)) {
1004			$class = is_object($callback[0]) ? get_class($callback[0]) : $callback[0];
1005		} else {
1006			$class = $callback;
1007		}
1008
1009		$params = $this->mapParams($params, array($class, 'init'), $paramtype);
1010
1011		$this->stack[] = array('type' => $type, 'params' => $params, 'custom' => true, 'class' => $class, 'buffer' => null);
1012		$this->curBlock =& $this->stack[count($this->stack)-1];
1013		return call_user_func(array($class,'preProcessing'), $this, $params, '', '', $type);
1014	}
1015
1016	/**
1017	 * injects a block at the top of the plugin stack without calling its preProcessing method
1018	 *
1019	 * used by {else} blocks to re-add themselves after having closed everything up to their parent
1020	 *
1021	 * @param string $type block type (name)
1022	 * @param array $params parameters array
1023	 */
1024	public function injectBlock($type, array $params)
1025	{
1026		$class = 'Dwoo_Plugin_'.$type;
1027		if (class_exists($class, false) === false) {
1028			$this->dwoo->getLoader()->loadPlugin($type);
1029		}
1030		$this->stack[] = array('type' => $type, 'params' => $params, 'custom' => false, 'class' => $class, 'buffer' => null);
1031		$this->curBlock =& $this->stack[count($this->stack)-1];
1032	}
1033
1034	/**
1035	 * removes the closest-to-top block of the given type and all other
1036	 * blocks encountered while going down the block stack
1037	 *
1038	 * @param string $type block type (name)
1039	 * @return string the output of all postProcessing() method's return values of the closed blocks
1040	 */
1041	public function removeBlock($type)
1042	{
1043		$output = '';
1044
1045		$pluginType = $this->getPluginType($type);
1046		if ($pluginType & Dwoo::SMARTY_BLOCK) {
1047			$type = 'smartyinterface';
1048		}
1049		while (true) {
1050			while ($top = array_pop($this->stack)) {
1051				if ($top['custom']) {
1052					$class = $top['class'];
1053				} else {
1054					$class = 'Dwoo_Plugin_'.$top['type'];
1055				}
1056				if (count($this->stack)) {
1057					$this->curBlock =& $this->stack[count($this->stack)-1];
1058					$this->push(call_user_func(array($class, 'postProcessing'), $this, $top['params'], '', '', $top['buffer']), 0);
1059				} else {
1060					$null = null;
1061					$this->curBlock =& $null;
1062					$output = call_user_func(array($class, 'postProcessing'), $this, $top['params'], '', '', $top['buffer']);
1063				}
1064
1065				if ($top['type'] === $type) {
1066					break 2;
1067				}
1068			}
1069
1070			throw new Dwoo_Compilation_Exception($this, 'Syntax malformation, a block of type "'.$type.'" was closed but was not opened');
1071			break;
1072		}
1073
1074		return $output;
1075	}
1076
1077	/**
1078	 * returns a reference to the first block of the given type encountered and
1079	 * optionally closes all blocks until it finds it
1080	 *
1081	 * this is mainly used by {else} plugins to close everything that was opened
1082	 * between their parent and themselves
1083	 *
1084	 * @param string $type the block type (name)
1085	 * @param bool $closeAlong whether to close all blocks encountered while going down the block stack or not
1086	 * @return &array the array is as such: array('type'=>pluginName, 'params'=>parameter array,
1087	 * 				  'custom'=>bool defining whether it's a custom plugin or not, for internal use)
1088	 */
1089	public function &findBlock($type, $closeAlong = false)
1090	{
1091		if ($closeAlong===true) {
1092			while ($b = end($this->stack)) {
1093				if ($b['type']===$type) {
1094					return $this->stack[key($this->stack)];
1095				}
1096				$this->push($this->removeTopBlock(), 0);
1097			}
1098		} else {
1099			end($this->stack);
1100			while ($b = current($this->stack)) {
1101				if ($b['type']===$type) {
1102					return $this->stack[key($this->stack)];
1103				}
1104				prev($this->stack);
1105			}
1106		}
1107
1108		throw new Dwoo_Compilation_Exception($this, 'A parent block of type "'.$type.'" is required and can not be found');
1109	}
1110
1111	/**
1112	 * returns a reference to the current block array
1113	 *
1114	 * @return &array the array is as such: array('type'=>pluginName, 'params'=>parameter array,
1115	 * 				  'custom'=>bool defining whether it's a custom plugin or not, for internal use)
1116	 */
1117	public function &getCurrentBlock()
1118	{
1119		return $this->curBlock;
1120	}
1121
1122	/**
1123	 * removes the block at the top of the stack and calls its postProcessing() method
1124	 *
1125	 * @return string the postProcessing() method's output
1126	 */
1127	public function removeTopBlock()
1128	{
1129		$o = array_pop($this->stack);
1130		if ($o === null) {
1131			throw new Dwoo_Compilation_Exception($this, 'Syntax malformation, a block of unknown type was closed but was not opened.');
1132		}
1133		if ($o['custom']) {
1134			$class = $o['class'];
1135		} else {
1136			$class = 'Dwoo_Plugin_'.$o['type'];
1137		}
1138
1139		$this->curBlock =& $this->stack[count($this->stack)-1];
1140
1141		return call_user_func(array($class, 'postProcessing'), $this, $o['params'], '', '', $o['buffer']);
1142	}
1143
1144	/**
1145	 * returns the compiled parameters (for example a variable's compiled parameter will be "$this->scope['key']") out of the given parameter array
1146	 *
1147	 * @param array $params parameter array
1148	 * @return array filtered parameters
1149	 */
1150	public function getCompiledParams(array $params)
1151	{
1152		foreach ($params as $k=>$p) {
1153			if (is_array($p)) {
1154				$params[$k] = $p[0];
1155			}
1156		}
1157		return $params;
1158	}
1159
1160	/**
1161	 * returns the real parameters (for example a variable's real parameter will be its key, etc) out of the given parameter array
1162	 *
1163	 * @param array $params parameter array
1164	 * @return array filtered parameters
1165	 */
1166	public function getRealParams(array $params)
1167	{
1168		foreach ($params as $k=>$p) {
1169			if (is_array($p)) {
1170				$params[$k] = $p[1];
1171			}
1172		}
1173		return $params;
1174	}
1175
1176	/**
1177	 * entry point of the parser, it redirects calls to other parse* functions
1178	 *
1179	 * @param string $in the string within which we must parse something
1180	 * @param int $from the starting offset of the parsed area
1181	 * @param int $to the ending offset of the parsed area
1182	 * @param mixed $parsingParams must be an array if we are parsing a function or modifier's parameters, or false by default
1183	 * @param string $curBlock the current parser-block being processed
1184	 * @param mixed $pointer a reference to a pointer that will be increased by the amount of characters parsed, or null by default
1185	 * @return string parsed values
1186	 */
1187	protected function parse($in, $from, $to, $parsingParams = false, $curBlock='', &$pointer = null)
1188	{
1189		if ($to === null) {
1190			$to = strlen($in);
1191		}
1192		$first = substr($in, $from, 1);
1193
1194		if ($first === false) {
1195			throw new Dwoo_Compilation_Exception($this, 'Unexpected EOF, a template tag was not closed');
1196		}
1197
1198		while ($first===" " || $first==="\n" || $first==="\t" || $first==="\r") {
1199			if ($curBlock === 'root' && substr($in, $from, strlen($this->rd)) === $this->rd) {
1200				// end template tag
1201				$pointer += strlen($this->rd);
1202				if ($this->debug) echo 'TEMPLATE PARSING ENDED<br />';
1203				return false;
1204			}
1205			$from++;
1206			if ($pointer !== null) {
1207				$pointer++;
1208			}
1209			if ($from >= $to) {
1210				if (is_array($parsingParams)) {
1211					return $parsingParams;
1212				} else {
1213					return '';
1214				}
1215			}
1216			$first = $in[$from];
1217		}
1218
1219		$substr = substr($in, $from, $to-$from);
1220
1221		if ($this->debug) echo '<br />PARSE CALL : PARSING "<b>'.htmlentities(substr($in, $from, min($to-$from, 50))).(($to-$from) > 50 ? '...':'').'</b>" @ '.$from.':'.$to.' in '.$curBlock.' : pointer='.$pointer.'<br/>';
1222		$parsed = "";
1223
1224		if ($curBlock === 'root' && $first === '*') {
1225			$src = $this->getTemplateSource();
1226			$startpos = $this->getPointer() - strlen($this->ld);
1227			if (substr($src, $startpos, strlen($this->ld)) === $this->ld) {
1228				if ($startpos > 0) {
1229					do {
1230						$char = substr($src, --$startpos, 1);
1231						if ($char == "\n") {
1232							$startpos++;
1233							$whitespaceStart = true;
1234							break;
1235						}
1236					} while ($startpos > 0 && ($char == ' ' || $char == "\t"));
1237				}
1238
1239				if (!isset($whitespaceStart)) {
1240					$startpos = $this->getPointer();
1241				} else {
1242					$pointer -= $this->getPointer() - $startpos;
1243				}
1244
1245				if ($this->allowNestedComments && strpos($src, $this->ld.'*', $this->getPointer()) !== false) {
1246					$comOpen = $this->ld.'*';
1247					$comClose = '*'.$this->rd;
1248					$level = 1;
1249					$start = $startpos;
1250					$ptr = $this->getPointer() + '*';
1251
1252					while ($level > 0 && $ptr < strlen($src)) {
1253						$open = strpos($src, $comOpen, $ptr);
1254						$close = strpos($src, $comClose, $ptr);
1255
1256						if ($open !== false && $close !== false) {
1257							if ($open < $close) {
1258								$ptr = $open + strlen($comOpen);
1259								$level++;
1260							} else {
1261								$ptr = $close + strlen($comClose);
1262								$level--;
1263							}
1264						} elseif ($open !== false) {
1265							$ptr = $open + strlen($comOpen);
1266							$level++;
1267						} elseif ($close !== false) {
1268							$ptr = $close + strlen($comClose);
1269							$level--;
1270						} else {
1271							$ptr = strlen($src);
1272						}
1273					}
1274					$endpos = $ptr - strlen('*'.$this->rd);
1275				} else {
1276					$endpos = strpos($src, '*'.$this->rd, $startpos);
1277					if ($endpos == false) {
1278						throw new Dwoo_Compilation_Exception($this, 'Un-ended comment');
1279					}
1280				}
1281				$pointer += $endpos - $startpos + strlen('*'.$this->rd);
1282				if (isset($whitespaceStart) && preg_match('#^[\t ]*\r?\n#', substr($src, $endpos+strlen('*'.$this->rd)), $m)) {
1283					$pointer += strlen($m[0]);
1284					$this->curBlock['buffer'] = substr($this->curBlock['buffer'], 0, strlen($this->curBlock['buffer']) - ($this->getPointer() - $startpos - strlen($this->ld)));
1285				}
1286				return false;
1287			}
1288		}
1289
1290		if ($first==='$') {
1291			// var
1292			$out = $this->parseVar($in, $from, $to, $parsingParams, $curBlock, $pointer);
1293			$parsed = 'var';
1294		} elseif ($first==='%' && preg_match('#^%[a-z]#i', $substr)) {
1295			// const
1296			$out = $this->parseConst($in, $from, $to, $parsingParams, $curBlock, $pointer);
1297		} elseif ($first==='"' || $first==="'") {
1298			// string
1299			$out = $this->parseString($in, $from, $to, $parsingParams, $curBlock, $pointer);
1300		} elseif (preg_match('/^[a-z][a-z0-9_]*(?:::[a-z][a-z0-9_]*)?('.(is_array($parsingParams)||$curBlock!='root'?'':'\s+[^(]|').'\s*\(|\s*'.$this->rdr.'|\s*;)/i', $substr)) {
1301			// func
1302			$out = $this->parseFunction($in, $from, $to, $parsingParams, $curBlock, $pointer);
1303			$parsed = 'func';
1304		} elseif ($first === ';') {
1305			// instruction end
1306			if ($this->debug) echo 'END OF INSTRUCTION<br />';
1307			if ($pointer !== null) {
1308				$pointer++;
1309			}
1310			return $this->parse($in, $from+1, $to, false, 'root', $pointer);
1311		} elseif ($curBlock === 'root' && preg_match('#^/([a-z][a-z0-9_]*)?#i', $substr, $match)) {
1312			// close block
1313			if (!empty($match[1]) && $match[1] == 'else') {
1314				throw new Dwoo_Compilation_Exception($this, 'Else blocks must not be closed explicitly, they are automatically closed when their parent block is closed');
1315			}
1316			if (!empty($match[1]) && $match[1] == 'elseif') {
1317				throw new Dwoo_Compilation_Exception($this, 'Elseif blocks must not be closed explicitly, they are automatically closed when their parent block is closed or a new else/elseif block is declared after them');
1318			}
1319			if ($pointer !== null) {
1320				$pointer += strlen($match[0]);
1321			}
1322			if (empty($match[1])) {
1323				if ($this->curBlock['type'] == 'else' || $this->curBlock['type'] == 'elseif') {
1324					$pointer -= strlen($match[0]);
1325				}
1326				if ($this->debug) echo 'TOP BLOCK CLOSED<br />';
1327				return $this->removeTopBlock();
1328			} else {
1329				if ($this->debug) echo 'BLOCK OF TYPE '.$match[1].' CLOSED<br />';
1330				return $this->removeBlock($match[1]);
1331			}
1332		} elseif ($curBlock === 'root' && substr($substr, 0, strlen($this->rd)) === $this->rd) {
1333			// end template tag
1334			if ($this->debug) echo 'TAG PARSING ENDED<br />';
1335			$pointer += strlen($this->rd);
1336			return false;
1337		} elseif (is_array($parsingParams) && preg_match('#^([a-z0-9_]+\s*=)(?:\s+|[^=]).*#i', $substr, $match)) {
1338			// named parameter
1339			if ($this->debug) echo 'NAMED PARAM FOUND<br />';
1340			$len = strlen($match[1]);
1341			while (substr($in, $from+$len, 1)===' ') {
1342				$len++;
1343			}
1344			if ($pointer !== null) {
1345				$pointer += $len;
1346			}
1347
1348			$output = array(trim(substr(trim($match[1]), 0, -1)), $this->parse($in, $from+$len, $to, false, 'namedparam', $pointer));
1349
1350			$parsingParams[] = $output;
1351			return $parsingParams;
1352		} elseif (preg_match('#^([a-z0-9_]+::\$[a-z0-9_]+)#i', $substr, $match)) {
1353			// static member access
1354			$parsed = 'var';
1355			if (is_array($parsingParams)) {
1356				$parsingParams[] = array($match[1], $match[1]);
1357				$out = $parsingParams;
1358			} else {
1359				$out = $match[1];
1360			}
1361			$pointer += strlen($match[1]);
1362		} elseif ($substr!=='' && (is_array($parsingParams) || $curBlock === 'namedparam' || $curBlock === 'condition' || $curBlock === 'expression')) {
1363			// unquoted string, bool or number
1364			$out = $this->parseOthers($in, $from, $to, $parsingParams, $curBlock, $pointer);
1365		} else {
1366			// parse error
1367			throw new Dwoo_Compilation_Exception($this, 'Parse error in "'.substr($in, $from, $to-$from).'"');
1368		}
1369
1370		if (empty($out)) {
1371			return '';
1372		}
1373
1374		$substr = substr($in, $pointer, $to-$pointer);
1375
1376		// var parsed, check if any var-extension applies
1377		if ($parsed==='var') {
1378			if (preg_match('#^\s*([/%+*-])\s*([a-z0-9]|\$)#i', $substr, $match)) {
1379				if($this->debug) echo 'PARSING POST-VAR EXPRESSION '.$substr.'<br />';
1380				// parse expressions
1381				$pointer += strlen($match[0]) - 1;
1382				if (is_array($parsingParams)) {
1383					if ($match[2] == '$') {
1384						$expr = $this->parseVar($in, $pointer, $to, array(), $curBlock, $pointer);
1385					} else {
1386						$expr = $this->parse($in, $pointer, $to, array(), 'expression', $pointer);
1387					}
1388					$out[count($out)-1][0] .= $match[1] . $expr[0][0];
1389					$out[count($out)-1][1] .= $match[1] . $expr[0][1];
1390				} else {
1391					if ($match[2] == '$') {
1392						$expr = $this->parseVar($in, $pointer, $to, false, $curBlock, $pointer);
1393					} else {
1394						$expr = $this->parse($in, $pointer, $to, false, 'expression', $pointer);
1395					}
1396					if (is_array($out) && is_array($expr)) {
1397						$out[0] .= $match[1] . $expr[0];
1398						$out[1] .= $match[1] . $expr[1];
1399					} elseif (is_array($out)) {
1400						$out[0] .= $match[1] . $expr;
1401						$out[1] .= $match[1] . $expr;
1402					} elseif (is_array($expr)) {
1403						$out .= $match[1] . $expr[0];
1404					} else {
1405						$out .= $match[1] . $expr;
1406					}
1407				}
1408			} else if ($curBlock === 'root' && preg_match('#^(\s*(?:[+/*%-.]=|=|\+\+|--)\s*)(.*)#s', $substr, $match)) {
1409				if($this->debug) echo 'PARSING POST-VAR ASSIGNMENT '.$substr.'<br />';
1410				// parse assignment
1411				$value = $match[2];
1412				$operator = trim($match[1]);
1413				if (substr($value, 0, 1) == '=') {
1414					throw new Dwoo_Compilation_Exception($this, 'Unexpected "=" in <em>'.$substr.'</em>');
1415				}
1416
1417				if ($pointer !== null) {
1418					$pointer += strlen($match[1]);
1419				}
1420
1421				if ($operator !== '++' && $operator !== '--') {
1422					$parts = array();
1423					$ptr = 0;
1424					$parts = $this->parse($value, 0, strlen($value), $parts, 'condition', $ptr);
1425					$pointer += $ptr;
1426
1427					// load if plugin
1428					try {
1429						$this->getPluginType('if');
1430					} catch (Dwoo_Exception $e) {
1431						throw new Dwoo_Compilation_Exception($this, 'Assignments require the "if" plugin to be accessible');
1432					}
1433
1434					$parts = $this->mapParams($parts, array('Dwoo_Plugin_if', 'init'), 1);
1435					$parts = $this->getCompiledParams($parts);
1436
1437					$value = Dwoo_Plugin_if::replaceKeywords($parts['*'], $this);
1438					$echo = '';
1439				} else {
1440					$value = array();
1441					$echo = 'echo ';
1442				}
1443
1444				if ($this->autoEscape) {
1445					$out = preg_replace('#\(is_string\(\$tmp=(.+?)\) \? htmlspecialchars\(\$tmp, ENT_QUOTES, \$this->charset\) : \$tmp\)#', '$1', $out);
1446				}
1447				$out = Dwoo_Compiler::PHP_OPEN. $echo . $out . $operator . implode(' ', $value) . Dwoo_Compiler::PHP_CLOSE;
1448			}
1449		}
1450
1451		if ($curBlock !== 'modifier' && ($parsed === 'func' || $parsed === 'var') && preg_match('#^\|@?[a-z0-9_]+(:.*)?#i', $substr, $match)) {
1452			// parse modifier on funcs or vars
1453			$srcPointer = $pointer;
1454			if (is_array($parsingParams)) {
1455				$tmp = $this->replaceModifiers(array(null, null, $out[count($out)-1][0], $match[0]), 'var', $pointer);
1456				$out[count($out)-1][0] = $tmp;
1457				$out[count($out)-1][1] .= substr($substr, $srcPointer, $srcPointer - $pointer);
1458			} else {
1459				$out = $this->replaceModifiers(array(null, null, $out, $match[0]), 'var', $pointer);
1460			}
1461		}
1462
1463		// func parsed, check if any func-extension applies
1464		if ($parsed==='func' && preg_match('#^->[a-z0-9_]+(\s*\(.+|->[a-z].*)?#is', $substr, $match)) {
1465			// parse method call or property read
1466			$ptr = 0;
1467
1468			if (is_array($parsingParams)) {
1469				$output = $this->parseMethodCall($out[count($out)-1][1], $match[0], $curBlock, $ptr);
1470
1471				$out[count($out)-1][0] .= substr($match[0], 0, $ptr);
1472				$out[count($out)-1][1] .= $output;
1473			} else {
1474				$out = $this->parseMethodCall($out, $match[0], $curBlock, $ptr);
1475			}
1476
1477			$pointer += $ptr;
1478		}
1479
1480		if ($curBlock === 'root' && substr($out, 0, strlen(self::PHP_OPEN)) !== self::PHP_OPEN) {
1481			return self::PHP_OPEN .'echo '.$out.';'. self::PHP_CLOSE;
1482		} else {
1483			return $out;
1484		}
1485	}
1486
1487	/**
1488	 * parses a function call
1489	 *
1490	 * @param string $in the string within which we must parse something
1491	 * @param int $from the starting offset of the parsed area
1492	 * @param int $to the ending offset of the parsed area
1493	 * @param mixed $parsingParams must be an array if we are parsing a function or modifier's parameters, or false by default
1494	 * @param string $curBlock the current parser-block being processed
1495	 * @param mixed $pointer a reference to a pointer that will be increased by the amount of characters parsed, or null by default
1496	 * @return string parsed values
1497	 */
1498	protected function parseFunction($in, $from, $to, $parsingParams = false, $curBlock='', &$pointer = null)
1499	{
1500		$cmdstr = substr($in, $from, $to-$from);
1501		preg_match('/^([a-z][a-z0-9_]*(?:::[a-z][a-z0-9_]*)?)(\s*'.$this->rdr.'|\s*;)?/i', $cmdstr, $match);
1502
1503		if (empty($match[1])) {
1504			throw new Dwoo_Compilation_Exception($this, 'Parse error, invalid function name : '.substr($cmdstr, 0, 15));
1505		}
1506
1507		$func = $match[1];
1508
1509		if (!empty($match[2])) {
1510			$cmdstr = $match[1];
1511		}
1512
1513		if ($this->debug) echo 'FUNC FOUND ('.$func.')<br />';
1514
1515		$paramsep = '';
1516
1517		if (is_array($parsingParams) || $curBlock != 'root') {
1518			$paramspos = strpos($cmdstr, '(');
1519			$paramsep = ')';
1520		} elseif(preg_match_all('#[a-z0-9_]+(\s*\(|\s+[^(])#i', $cmdstr, $match, PREG_OFFSET_CAPTURE)) {
1521			$paramspos = $match[1][0][1];
1522			$paramsep = substr($match[1][0][0], -1) === '(' ? ')':'';
1523			if($paramsep === ')') {
1524				$paramspos += strlen($match[1][0][0]) - 1;
1525				if(substr($cmdstr, 0, 2) === 'if' || substr($cmdstr, 0, 6) === 'elseif') {
1526					$paramsep = '';
1527					if(strlen($match[1][0][0]) > 1) {
1528						$paramspos--;
1529					}
1530				}
1531			}
1532		} else {
1533			$paramspos = false;
1534		}
1535
1536		$state = 0;
1537
1538		if ($paramspos === false) {
1539			$params = array();
1540
1541			if ($curBlock !== 'root') {
1542				return $this->parseOthers($in, $from, $to, $parsingParams, $curBlock, $pointer);
1543			}
1544		} else {
1545			if ($curBlock === 'condition') {
1546				// load if plugin
1547				$this->getPluginType('if');
1548				if (Dwoo_Plugin_if::replaceKeywords(array($func), $this) !== array($func)) {
1549					return $this->parseOthers($in, $from, $to, $parsingParams, $curBlock, $pointer);
1550				}
1551			}
1552			$whitespace = strlen(substr($cmdstr, strlen($func), $paramspos-strlen($func)));
1553			$paramstr = substr($cmdstr, $paramspos+1);
1554			if (substr($paramstr, -1, 1) === $paramsep) {
1555				$paramstr = substr($paramstr, 0, -1);
1556			}
1557
1558			if (strlen($paramstr)===0) {
1559				$params = array();
1560				$paramstr = '';
1561			} else {
1562				$ptr = 0;
1563				$params = array();
1564				if ($func === 'empty') {
1565					$params = $this->parseVar($paramstr, $ptr, strlen($paramstr), $params, 'root', $ptr);
1566				} else {
1567					while ($ptr < strlen($paramstr)) {
1568						while (true) {
1569							if ($ptr >= strlen($paramstr)) {
1570								break 2;
1571							}
1572
1573							if ($func !== 'if' && $func !== 'elseif' && $paramstr[$ptr] === ')') {
1574								if ($this->debug) echo 'PARAM PARSING ENDED, ")" FOUND, POINTER AT '.$ptr.'<br/>';
1575								break 2;
1576							} elseif ($paramstr[$ptr] === ';') {
1577								$ptr++;
1578								if ($this->debug) echo 'PARAM PARSING ENDED, ";" FOUND, POINTER AT '.$ptr.'<br/>';
1579								break 2;
1580							} elseif ($func !== 'if' && $func !== 'elseif' && $paramstr[$ptr] === '/') {
1581								if ($this->debug) echo 'PARAM PARSING ENDED, "/" FOUND, POINTER AT '.$ptr.'<br/>';
1582								break 2;
1583							} elseif (substr($paramstr, $ptr, strlen($this->rd)) === $this->rd) {
1584								if ($this->debug) echo 'PARAM PARSING ENDED, RIGHT DELIMITER FOUND, POINTER AT '.$ptr.'<br/>';
1585								break 2;
1586							}
1587
1588							if ($paramstr[$ptr] === ' ' || $paramstr[$ptr] === ',' || $paramstr[$ptr] === "\r" || $paramstr[$ptr] === "\n" || $paramstr[$ptr] === "\t") {
1589								$ptr++;
1590							} else {
1591								break;
1592							}
1593						}
1594
1595						if ($this->debug) echo 'FUNC START PARAM PARSING WITH POINTER AT '.$ptr.'<br/>';
1596
1597						if ($func === 'if' || $func === 'elseif' || $func === 'tif') {
1598							$params = $this->parse($paramstr, $ptr, strlen($paramstr), $params, 'condition', $ptr);
1599						} else {
1600							$params = $this->parse($paramstr, $ptr, strlen($paramstr), $params, 'function', $ptr);
1601						}
1602
1603						if ($this->debug) echo 'PARAM PARSED, POINTER AT '.$ptr.' ('.substr($paramstr, $ptr-1, 3).')<br/>';
1604					}
1605				}
1606				$paramstr = substr($paramstr, 0, $ptr);
1607				$state = 0;
1608				foreach ($params as $k=>$p) {
1609					if (is_array($p) && is_array($p[1])) {
1610						$state |= 2;
1611					} else {
1612						if (($state & 2) && preg_match('#^(["\'])(.+?)\1$#', $p[0], $m)) {
1613							$params[$k] = array($m[2], array('true', 'true'));
1614						} else {
1615							if ($state & 2) {
1616								throw new Dwoo_Compilation_Exception($this, 'You can not use an unnamed parameter after a named one');
1617							}
1618							$state |= 1;
1619						}
1620					}
1621				}
1622			}
1623		}
1624
1625		if ($pointer !== null) {
1626			$pointer += (isset($paramstr) ? strlen($paramstr) : 0) + (')' === $paramsep ? 2 : ($paramspos === false ? 0 : 1)) + strlen($func) + (isset($whitespace) ? $whitespace : 0);
1627			if ($this->debug) echo 'FUNC ADDS '.((isset($paramstr) ? strlen($paramstr) : 0) + (')' === $paramsep ? 2 : ($paramspos === false ? 0 : 1)) + strlen($func)).' TO POINTER<br/>';
1628		}
1629
1630		if ($curBlock === 'method' || $func === 'do' || strstr($func, '::') !== false) {
1631			$pluginType = Dwoo::NATIVE_PLUGIN;
1632		} else {
1633			$pluginType = $this->getPluginType($func);
1634		}
1635
1636		// blocks
1637		if ($pluginType & Dwoo::BLOCK_PLUGIN) {
1638			if ($curBlock !== 'root' || is_array($parsingParams)) {
1639				throw new Dwoo_Compilation_Exception($this, 'Block plugins can not be used as other plugin\'s arguments');
1640			}
1641			if ($pluginType & Dwoo::CUSTOM_PLUGIN) {
1642				return $this->addCustomBlock($func, $params, $state);
1643			} else {
1644				return $this->addBlock($func, $params, $state);
1645			}
1646		} elseif ($pluginType & Dwoo::SMARTY_BLOCK) {
1647			if ($curBlock !== 'root' || is_array($parsingParams)) {
1648				throw new Dwoo_Compilation_Exception($this, 'Block plugins can not be used as other plugin\'s arguments');
1649			}
1650
1651			if ($state & 2) {
1652				array_unshift($params, array('__functype', array($pluginType, $pluginType)));
1653				array_unshift($params, array('__funcname', array($func, $func)));
1654			} else {
1655				array_unshift($params, array($pluginType, $pluginType));
1656				array_unshift($params, array($func, $func));
1657			}
1658
1659			return $this->addBlock('smartyinterface', $params, $state);
1660		}
1661
1662		// funcs
1663		if ($pluginType & Dwoo::NATIVE_PLUGIN || $pluginType & Dwoo::SMARTY_FUNCTION || $pluginType & Dwoo::SMARTY_BLOCK) {
1664			$params = $this->mapParams($params, null, $state);
1665		} elseif ($pluginType & Dwoo::CLASS_PLUGIN) {
1666			if ($pluginType & Dwoo::CUSTOM_PLUGIN) {
1667				$params = $this->mapParams($params, array($this->customPlugins[$func]['class'], $this->customPlugins[$func]['function']), $state);
1668			} else {
1669				$params = $this->mapParams($params, array('Dwoo_Plugin_'.$func, ($pluginType & Dwoo::COMPILABLE_PLUGIN) ? 'compile' : 'process'), $state);
1670			}
1671		} elseif ($pluginType & Dwoo::FUNC_PLUGIN) {
1672			if ($pluginType & Dwoo::CUSTOM_PLUGIN) {
1673				$params = $this->mapParams($params, $this->customPlugins[$func]['callback'], $state);
1674			} else {
1675				$params = $this->mapParams($params, 'Dwoo_Plugin_'.$func.(($pluginType & Dwoo::COMPILABLE_PLUGIN) ? '_compile' : ''), $state);
1676			}
1677		} elseif ($pluginType & Dwoo::SMARTY_MODIFIER) {
1678			$output = 'smarty_modifier_'.$func.'('.implode(', ', $params).')';
1679		} elseif ($pluginType & Dwoo::PROXY_PLUGIN) {
1680			$params = $this->mapParams($params, $this->getDwoo()->getPluginProxy()->getCallback($func), $state);
1681		} elseif ($pluginType & Dwoo::TEMPLATE_PLUGIN) {
1682			// transforms the parameter array from (x=>array('paramname'=>array(values))) to (paramname=>array(values))
1683			$map = array();
1684			foreach ($this->templatePlugins[$func]['params'] as $param=>$defValue) {
1685				if ($param == 'rest') {
1686					$param = '*';
1687				}
1688				$hasDefault = $defValue !== null;
1689				if ($defValue === 'null') {
1690					$defValue = null;
1691				} elseif ($defValue === 'false') {
1692					$defValue = false;
1693				} elseif ($defValue === 'true') {
1694					$defValue = true;
1695				} elseif (preg_match('#^([\'"]).*?\1$#', $defValue)) {
1696					$defValue = substr($defValue, 1, -1);
1697				}
1698				$map[] = array($param, $hasDefault, $defValue);
1699			}
1700
1701			$params = $this->mapParams($params, null, $state, $map);
1702		}
1703
1704		// only keep php-syntax-safe values for non-block plugins
1705		foreach ($params as &$p) {
1706			$p = $p[0];
1707		}
1708		if ($pluginType & Dwoo::NATIVE_PLUGIN) {
1709			if ($func === 'do') {
1710				if (isset($params['*'])) {
1711					$output = implode(';', $params['*']).';';
1712				} else {
1713					$output = '';
1714				}
1715
1716				if (is_array($parsingParams) || $curBlock !== 'root') {
1717					throw new Dwoo_Compilation_Exception($this, 'Do can not be used inside another function or block');
1718				} else {
1719					return self::PHP_OPEN.$output.self::PHP_CLOSE;
1720				}
1721			} else {
1722				if (isset($params['*'])) {
1723					$output = $func.'('.implode(', ', $params['*']).')';
1724				} else {
1725					$output = $func.'()';
1726				}
1727			}
1728		} elseif ($pluginType & Dwoo::FUNC_PLUGIN) {
1729			if ($pluginType & Dwoo::COMPILABLE_PLUGIN) {
1730				if ($pluginType & Dwoo::CUSTOM_PLUGIN) {
1731					$funcCompiler = $this->customPlugins[$func]['callback'];
1732				} else {
1733					$funcCompiler = 'Dwoo_Plugin_'.$func.'_compile';
1734				}
1735				array_unshift($params, $this);
1736				$output = call_user_func_array($funcCompiler, $params);
1737			} else {
1738				array_unshift($params, '$this');
1739				$params = self::implode_r($params);
1740
1741				if ($pluginType & Dwoo::CUSTOM_PLUGIN) {
1742					$callback = $this->customPlugins[$func]['callback'];
1743					$output = 'call_user_func(\''.$callback.'\', '.$params.')';
1744				} else {
1745					$output = 'Dwoo_Plugin_'.$func.'('.$params.')';
1746				}
1747			}
1748		} elseif ($pluginType & Dwoo::CLASS_PLUGIN) {
1749			if ($pluginType & Dwoo::COMPILABLE_PLUGIN) {
1750				if ($pluginType & Dwoo::CUSTOM_PLUGIN) {
1751					$callback = $this->customPlugins[$func]['callback'];
1752					if (!is_array($callback)) {
1753						if (!method_exists($callback, 'compile')) {
1754							throw new Dwoo_Exception('Custom plugin '.$func.' must implement the "compile" method to be compilable, or you should provide a full callback to the method to use');
1755						}
1756						if (($ref = new ReflectionMethod($callback, 'compile')) && $ref->isStatic()) {
1757							$funcCompiler = array($callback, 'compile');
1758						} else {
1759							$funcCompiler = array(new $callback, 'compile');
1760						}
1761					} else {
1762						$funcCompiler = $callback;
1763					}
1764				} else {
1765					$funcCompiler = array('Dwoo_Plugin_'.$func, 'compile');
1766					array_unshift($params, $this);
1767				}
1768				$output = call_user_func_array($funcCompiler, $params);
1769			} else {
1770				$params = self::implode_r($params);
1771				if ($pluginType & Dwoo::CUSTOM_PLUGIN) {
1772					$callback = $this->customPlugins[$func]['callback'];
1773					if (!is_array($callback)) {
1774						if (!method_exists($callback, 'process')) {
1775							throw new Dwoo_Exception('Custom plugin '.$func.' must implement the "process" method to be usable, or you should provide a full callback to the method to use');
1776						}
1777						if (($ref = new ReflectionMethod($callback, 'process')) && $ref->isStatic()) {
1778							$output = 'call_user_func(array(\''.$callback.'\', \'process\'), '.$params.')';
1779						} else {
1780							$output = 'call_user_func(array($this->getObjectPlugin(\''.$callback.'\'), \'process\'), '.$params.')';
1781						}
1782					} elseif (is_object($callback[0])) {
1783						$output = 'call_user_func(array($this->plugins[\''.$func.'\'][\'callback\'][0], \''.$callback[1].'\'), '.$params.')';
1784					} elseif (($ref = new ReflectionMethod($callback[0], $callback[1])) && $ref->isStatic()) {
1785						$output = 'call_user_func(array(\''.$callback[0].'\', \''.$callback[1].'\'), '.$params.')';
1786					} else {
1787						$output = 'call_user_func(array($this->getObjectPlugin(\''.$callback[0].'\'), \''.$callback[1].'\'), '.$params.')';
1788					}
1789					if (empty($params)) {
1790						$output = substr($output, 0, -3).')';
1791					}
1792				} else {
1793					$output = '$this->classCall(\''.$func.'\', array('.$params.'))';
1794				}
1795			}
1796		} elseif ($pluginType & Dwoo::PROXY_PLUGIN) {
1797			$output = call_user_func(array($this->dwoo->getPluginProxy(), 'getCode'), $func, $params);
1798		} elseif ($pluginType & Dwoo::SMARTY_FUNCTION) {
1799			if (isset($params['*'])) {
1800				$params = self::implode_r($params['*'], true);
1801			} else {
1802				$params = '';
1803			}
1804
1805			if ($pluginType & Dwoo::CUSTOM_PLUGIN) {
1806				$callback = $this->customPlugins[$func]['callback'];
1807				if (is_array($callback)) {
1808					if (is_object($callback[0])) {
1809						$output = 'call_user_func_array(array($this->plugins[\''.$func.'\'][\'callback\'][0], \''.$callback[1].'\'), array(array('.$params.'), $this))';
1810					} else {
1811						$output = 'call_user_func_array(array(\''.$callback[0].'\', \''.$callback[1].'\'), array(array('.$params.'), $this))';
1812					}
1813				} else {
1814					$output = $callback.'(array('.$params.'), $this)';
1815				}
1816			} else {
1817				$output = 'smarty_function_'.$func.'(array('.$params.'), $this)';
1818			}
1819		} elseif ($pluginType & Dwoo::TEMPLATE_PLUGIN) {
1820			array_unshift($params, '$this');
1821			$params = self::implode_r($params);
1822			$output = 'Dwoo_Plugin_'.$func.'_'.$this->templatePlugins[$func]['uuid'].'('.$params.')';
1823			$this->templatePlugins[$func]['called'] = true;
1824		}
1825
1826		if (is_array($parsingParams)) {
1827			$parsingParams[] = array($output, $output);
1828			return $parsingParams;
1829		} elseif ($curBlock === 'namedparam') {
1830			return array($output, $output);
1831		} else {
1832			return $output;
1833		}
1834	}
1835
1836	/**
1837	 * parses a string
1838	 *
1839	 * @param string $in the string within which we must parse something
1840	 * @param int $from the starting offset of the parsed area
1841	 * @param int $to the ending offset of the parsed area
1842	 * @param mixed $parsingParams must be an array if we are parsing a function or modifier's parameters, or false by default
1843	 * @param string $curBlock the current parser-block being processed
1844	 * @param mixed $pointer a reference to a pointer that will be increased by the amount of characters parsed, or null by default
1845	 * @return string parsed values
1846	 */
1847	protected function parseString($in, $from, $to, $parsingParams = false, $curBlock='', &$pointer = null)
1848	{
1849		$substr = substr($in, $from, $to-$from);
1850		$first = $substr[0];
1851
1852		if ($this->debug) echo 'STRING FOUND (in '.htmlentities(substr($in, $from, min($to-$from, 50))).(($to-$from) > 50 ? '...':'').')<br />';
1853		$strend = false;
1854		$o = $from+1;
1855		while ($strend === false) {
1856			$strend = strpos($in, $first, $o);
1857			if ($strend === false) {
1858				throw new Dwoo_Compilation_Exception($this, 'Unfinished string, started with '.substr($in, $from, $to-$from));
1859			}
1860			if (substr($in, $strend-1, 1) === '\\') {
1861				$o = $strend+1;
1862				$strend = false;
1863			}
1864		}
1865		if ($this->debug) echo 'STRING DELIMITED: '.substr($in, $from, $strend+1-$from).'<br/>';
1866
1867		$srcOutput = substr($in, $from, $strend+1-$from);
1868
1869		if ($pointer !== null) {
1870			$pointer += strlen($srcOutput);
1871		}
1872
1873		$output = $this->replaceStringVars($srcOutput, $first);
1874
1875		// handle modifiers
1876		if ($curBlock !== 'modifier' && preg_match('#^((?:\|(?:@?[a-z0-9_]+(?::.*)*))+)#i', substr($substr, $strend+1-$from), $match)) {
1877			$modstr = $match[1];
1878
1879			if ($curBlock === 'root' && substr($modstr, -1) === '}') {
1880				$modstr = substr($modstr, 0, -1);
1881			}
1882			$modstr = str_replace('\\'.$first, $first, $modstr);
1883			$ptr = 0;
1884			$output = $this->replaceModifiers(array(null, null, $output, $modstr), 'string', $ptr);
1885
1886			$strend += $ptr;
1887			if ($pointer !== null) {
1888				$pointer += $ptr;
1889			}
1890			$srcOutput .= substr($substr, $strend+1-$from, $ptr);
1891		}
1892
1893		if (is_array($parsingParams)) {
1894			$parsingParams[] = array($output, substr($srcOutput, 1, -1));
1895			return $parsingParams;
1896		} elseif ($curBlock === 'namedparam') {
1897			return array($output, substr($srcOutput, 1, -1));
1898		} else {
1899			return $output;
1900		}
1901	}
1902
1903	/**
1904	 * parses a constant
1905	 *
1906	 * @param string $in the string within which we must parse something
1907	 * @param int $from the starting offset of the parsed area
1908	 * @param int $to the ending offset of the parsed area
1909	 * @param mixed $parsingParams must be an array if we are parsing a function or modifier's parameters, or false by default
1910	 * @param string $curBlock the current parser-block being processed
1911	 * @param mixed $pointer a reference to a pointer that will be increased by the amount of characters parsed, or null by default
1912	 * @return string parsed values
1913	 */
1914	protected function parseConst($in, $from, $to, $parsingParams = false, $curBlock='', &$pointer = null)
1915	{
1916		$substr = substr($in, $from, $to-$from);
1917
1918		if ($this->debug) {
1919			echo 'CONST FOUND : '.$substr.'<br />';
1920		}
1921
1922		if (!preg_match('#^%([a-z0-9_:]+)#i', $substr, $m)) {
1923			throw new Dwoo_Compilation_Exception($this, 'Invalid constant');
1924		}
1925
1926		if ($pointer !== null) {
1927			$pointer += strlen($m[0]);
1928		}
1929
1930		$output = $this->parseConstKey($m[1], $curBlock);
1931
1932		if (is_array($parsingParams)) {
1933			$parsingParams[] = array($output, $m[1]);
1934			return $parsingParams;
1935		} elseif ($curBlock === 'namedparam') {
1936			return array($output, $m[1]);
1937		} else {
1938			return $output;
1939		}
1940	}
1941
1942	/**
1943	 * parses a constant
1944	 *
1945	 * @param string $key the constant to parse
1946	 * @param string $curBlock the current parser-block being processed
1947	 * @return string parsed constant
1948	 */
1949	protected function parseConstKey($key, $curBlock)
1950	{
1951		if ($this->securityPolicy !== null && $this->securityPolicy->getConstantHandling() === Dwoo_Security_Policy::CONST_DISALLOW) {
1952			return 'null';
1953		}
1954
1955		if ($curBlock !== 'root') {
1956			$output = '(defined("'.$key.'") ? '.$key.' : null)';
1957		} else {
1958			$output = $key;
1959		}
1960
1961		return $output;
1962	}
1963
1964	/**
1965	 * parses a variable
1966	 *
1967	 * @param string $in the string within which we must parse something
1968	 * @param int $from the starting offset of the parsed area
1969	 * @param int $to the ending offset of the parsed area
1970	 * @param mixed $parsingParams must be an array if we are parsing a function or modifier's parameters, or false by default
1971	 * @param string $curBlock the current parser-block being processed
1972	 * @param mixed $pointer a reference to a pointer that will be increased by the amount of characters parsed, or null by default
1973	 * @return string parsed values
1974	 */
1975	protected function parseVar($in, $from, $to, $parsingParams = false, $curBlock='', &$pointer = null)
1976	{
1977		$substr = substr($in, $from, $to-$from);
1978
1979		if (preg_match('#(\$?\.?[a-z0-9_:]*(?:(?:(?:\.|->)(?:[a-z0-9_:]+|(?R))|\[(?:[a-z0-9_:]+|(?R)|(["\'])[^\2]*?\2)\]))*)' . // var key
1980			($curBlock==='root' || $curBlock==='function' || $curBlock==='namedparam' || $curBlock==='condition' || $curBlock==='variable' || $curBlock==='expression' ? '(\(.*)?' : '()') . // method call
1981			($curBlock==='root' || $curBlock==='function' || $curBlock==='namedparam' || $curBlock==='condition' || $curBlock==='variable' || $curBlock==='delimited_string' ? '((?:(?:[+/*%=-])(?:(?<!=)=?-?[$%][a-z0-9.[\]>_:-]+(?:\([^)]*\))?|(?<!=)=?-?[0-9.,]*|[+-]))*)':'()') . // simple math expressions
1982			($curBlock!=='modifier' ? '((?:\|(?:@?[a-z0-9_]+(?:(?::("|\').*?\5|:[^`]*))*))+)?':'(())') . // modifiers
1983			'#i', $substr, $match)) {
1984			$key = substr($match[1], 1);
1985
1986			$matchedLength = strlen($match[0]);
1987			$hasModifiers = !empty($match[5]);
1988			$hasExpression = !empty($match[4]);
1989			$hasMethodCall = !empty($match[3]);
1990
1991			if (substr($key, -1) == ".") {
1992				$key = substr($key, 0, -1);
1993				$matchedLength--;
1994			}
1995
1996			if ($hasMethodCall) {
1997				$matchedLength -= strlen($match[3]) + strlen(substr($match[1], strrpos($match[1], '->')));
1998				$key = substr($match[1], 1, strrpos($match[1], '->')-1);
1999				$methodCall = substr($match[1], strrpos($match[1], '->')) . $match[3];
2000			}
2001
2002			if ($hasModifiers) {
2003				$matchedLength -= strlen($match[5]);
2004			}
2005
2006			if ($pointer !== null) {
2007				$pointer += $matchedLength;
2008			}
2009
2010			// replace useless brackets by dot accessed vars
2011			$key = preg_replace('#\[([^$%\[.>-]+)\]#', '.$1', $key);
2012
2013			// prevent $foo->$bar calls because it doesn't seem worth the trouble
2014			if (strpos($key, '->$') !== false) {
2015				throw new Dwoo_Compilation_Exception($this, 'You can not access an object\'s property using a variable name.');
2016			}
2017
2018			if ($this->debug) {
2019				if ($hasMethodCall) {
2020					echo 'METHOD CALL FOUND : $'.$key.substr($methodCall, 0, 30).'<br />';
2021				} else {
2022					echo 'VAR FOUND : $'.$key.'<br />';
2023				}
2024			}
2025
2026			$key = str_replace('"', '\\"', $key);
2027
2028			$cnt=substr_count($key, '$');
2029			if ($cnt > 0) {
2030				$uid = 0;
2031				$parsed = array($uid => '');
2032				$current =& $parsed;
2033				$curTxt =& $parsed[$uid++];
2034				$tree = array();
2035				$chars = str_split($key, 1);
2036				$inSplittedVar = false;
2037				$bracketCount = 0;
2038
2039				while (($char = array_shift($chars)) !== null) {
2040					if ($char === '[') {
2041						if (count($tree) > 0) {
2042							$bracketCount++;
2043						} else {
2044							$tree[] =& $current;
2045							$current[$uid] = array($uid+1 => '');
2046							$current =& $current[$uid++];
2047							$curTxt =& $current[$uid++];
2048							continue;
2049						}
2050					} elseif ($char === ']') {
2051						if ($bracketCount > 0) {
2052							$bracketCount--;
2053						} else {
2054							$current =& $tree[count($tree)-1];
2055							array_pop($tree);
2056							if (current($chars) !== '[' && current($chars) !== false && current($chars) !== ']') {
2057								$current[$uid] = '';
2058								$curTxt =& $current[$uid++];
2059							}
2060							continue;
2061						}
2062					} elseif ($char === '$') {
2063						if (count($tree) == 0) {
2064							$curTxt =& $current[$uid++];
2065							$inSplittedVar = true;
2066						}
2067					} elseif (($char === '.' || $char === '-') && count($tree) == 0 && $inSplittedVar) {
2068						$curTxt =& $current[$uid++];
2069						$inSplittedVar = false;
2070					}
2071
2072					$curTxt .= $char;
2073				}
2074				unset($uid, $current, $curTxt, $tree, $chars);
2075
2076				if ($this->debug) echo 'RECURSIVE VAR REPLACEMENT : '.$key.'<br>';
2077
2078				$key = $this->flattenVarTree($parsed);
2079
2080				if ($this->debug) echo 'RECURSIVE VAR REPLACEMENT DONE : '.$key.'<br>';
2081
2082				$output = preg_replace('#(^""\.|""\.|\.""$|(\()""\.|\.""(\)))#', '$2$3', '$this->readVar("'.$key.'")');
2083			} else {
2084				$output = $this->parseVarKey($key, $hasModifiers ? 'modifier' : $curBlock);
2085			}
2086
2087			// methods
2088			if ($hasMethodCall) {
2089				$ptr = 0;
2090
2091				$output = $this->parseMethodCall($output, $methodCall, $curBlock, $ptr);
2092
2093				if ($pointer !== null) {
2094					$pointer += $ptr;
2095				}
2096				$matchedLength += $ptr;
2097			}
2098
2099			if ($hasExpression) {
2100				// expressions
2101				preg_match_all('#(?:([+/*%=-])(=?-?[%$][a-z0-9.[\]>_:-]+(?:\([^)]*\))?|=?-?[0-9.,]+|\1))#i', $match[4], $expMatch);
2102
2103				foreach ($expMatch[1] as $k=>$operator) {
2104					if (substr($expMatch[2][$k], 0, 1)==='=') {
2105						$assign = true;
2106						if ($operator === '=') {
2107							throw new Dwoo_Compilation_Exception($this, 'Invalid expression <em>'.$substr.'</em>, can not use "==" in expressions');
2108						}
2109						if ($curBlock !== 'root') {
2110							throw new Dwoo_Compilation_Exception($this, 'Invalid expression <em>'.$substr.'</em>, assignments can only be used in top level expressions like {$foo+=3} or {$foo="bar"}');
2111						}
2112						$operator .= '=';
2113						$expMatch[2][$k] = substr($expMatch[2][$k], 1);
2114					}
2115
2116					if (substr($expMatch[2][$k], 0, 1)==='-' && strlen($expMatch[2][$k]) > 1) {
2117						$operator .= '-';
2118						$expMatch[2][$k] = substr($expMatch[2][$k], 1);
2119					}
2120					if (($operator==='+'||$operator==='-') && $expMatch[2][$k]===$operator) {
2121						$output = '('.$output.$operator.$operator.')';
2122						break;
2123					} elseif (substr($expMatch[2][$k], 0, 1) === '$') {
2124						$output = '('.$output.' '.$operator.' '.$this->parseVar($expMatch[2][$k], 0, strlen($expMatch[2][$k]), false, 'expression').')';
2125					} elseif (substr($expMatch[2][$k], 0, 1) === '%') {
2126						$output = '('.$output.' '.$operator.' '.$this->parseConst($expMatch[2][$k], 0, strlen($expMatch[2][$k]), false, 'expression').')';
2127					} elseif (!empty($expMatch[2][$k])) {
2128						$output = '('.$output.' '.$operator.' '.str_replace(',', '.', $expMatch[2][$k]).')';
2129					} else {
2130						throw new Dwoo_Compilation_Exception($this, 'Unfinished expression <em>'.$substr.'</em>, missing var or number after math operator');
2131					}
2132				}
2133			}
2134
2135			if ($this->autoEscape === true) {
2136				$output = '(is_string($tmp='.$output.') ? htmlspecialchars($tmp, ENT_QUOTES, $this->charset) : $tmp)';
2137			}
2138
2139			// handle modifiers
2140			if ($curBlock !== 'modifier' && $hasModifiers) {
2141				$ptr = 0;
2142				$output = $this->replaceModifiers(array(null, null, $output, $match[5]), 'var', $ptr);
2143				if ($pointer !== null) {
2144					$pointer += $ptr;
2145				}
2146				$matchedLength += $ptr;
2147			}
2148
2149			if (is_array($parsingParams)) {
2150				$parsingParams[] = array($output, $key);
2151				return $parsingParams;
2152			} elseif ($curBlock === 'namedparam') {
2153				return array($output, $key);
2154			} elseif ($curBlock === 'string' || $curBlock === 'delimited_string') {
2155				return array($matchedLength, $output);
2156			} elseif ($curBlock === 'expression' || $curBlock === 'variable') {
2157				return $output;
2158			} elseif (isset($assign)) {
2159				return self::PHP_OPEN.$output.';'.self::PHP_CLOSE;
2160			} else {
2161				return $output;
2162			}
2163		} else {
2164			if ($curBlock === 'string' || $curBlock === 'delimited_string') {
2165				return array(0, '');
2166			} else {
2167				throw new Dwoo_Compilation_Exception($this, 'Invalid variable name <em>'.$substr.'</em>');
2168			}
2169		}
2170	}
2171
2172	/**
2173	 * parses any number of chained method calls/property reads
2174	 *
2175	 * @param string $output the variable or whatever upon which the method are called
2176	 * @param string $methodCall method call source, starting at "->"
2177	 * @param string $curBlock the current parser-block being processed
2178	 * @param int $pointer a reference to a pointer that will be increased by the amount of characters parsed
2179	 * @return string parsed call(s)/read(s)
2180	 */
2181	protected function parseMethodCall($output, $methodCall, $curBlock, &$pointer)
2182	{
2183		$ptr = 0;
2184		$len = strlen($methodCall);
2185
2186		while ($ptr < $len) {
2187			if (strpos($methodCall, '->', $ptr) === $ptr) {
2188				$ptr += 2;
2189			}
2190
2191			if (in_array($methodCall[$ptr], array(';', '/', ' ', "\t", "\r", "\n", ')', '+', '*', '%', '=', '-', '|')) || substr($methodCall, $ptr, strlen($this->rd)) === $this->rd) {
2192				// break char found
2193				break;
2194			}
2195
2196			if(!preg_match('/^([a-z0-9_]+)(\(.*?\))?/i', substr($methodCall, $ptr), $methMatch)) {
2197				throw new Dwoo_Compilation_Exception($this, 'Invalid method name : '.substr($methodCall, $ptr, 20));
2198			}
2199
2200			if (empty($methMatch[2])) {
2201				// property
2202				if ($curBlock === 'root') {
2203					$output .= '->'.$methMatch[1];
2204				} else {
2205					$output = '(($tmp = '.$output.') ? $tmp->'.$methMatch[1].' : null)';
2206				}
2207				$ptr += strlen($methMatch[1]);
2208			} else {
2209				// method
2210				if (substr($methMatch[2], 0, 2) === '()') {
2211					$parsedCall = '->'.$methMatch[1].'()';
2212					$ptr += strlen($methMatch[1]) + 2;
2213				} else {
2214					$parsedCall = '->'.$this->parseFunction($methodCall, $ptr, strlen($methodCall), false, 'method', $ptr);
2215				}
2216				if ($curBlock === 'root') {
2217					$output .= $parsedCall;
2218				} else {
2219					$output = '(($tmp = '.$output.') ? $tmp'.$parsedCall.' : null)';
2220				}
2221			}
2222		}
2223
2224		$pointer += $ptr;
2225		return $output;
2226	}
2227
2228	/**
2229	 * parses a constant variable (a variable that doesn't contain another variable) and preprocesses it to save runtime processing time
2230	 *
2231	 * @param string $key the variable to parse
2232	 * @param string $curBlock the current parser-block being processed
2233	 * @return string parsed variable
2234	 */
2235	protected function parseVarKey($key, $curBlock)
2236	{
2237		if ($key === '') {
2238			return '$this->scope';
2239		}
2240		if (substr($key, 0, 1) === '.') {
2241			$key = 'dwoo'.$key;
2242		}
2243		if (preg_match('#dwoo\.(get|post|server|cookies|session|env|request)((?:\.[a-z0-9_-]+)+)#i', $key, $m)) {
2244			$global = strtoupper($m[1]);
2245			if ($global === 'COOKIES') {
2246				$global = 'COOKIE';
2247			}
2248			$key = '$_'.$global;
2249			foreach (explode('.', ltrim($m[2], '.')) as $part)
2250				$key .= '['.var_export($part, true).']';
2251			if ($curBlock === 'root') {
2252				$output = $key;
2253			} else {
2254				$output = '(isset('.$key.')?'.$key.':null)';
2255			}
2256		} elseif (preg_match('#dwoo\.const\.([a-z0-9_:]+)#i', $key, $m)) {
2257			return $this->parseConstKey($m[1], $curBlock);
2258		} elseif ($this->scope !== null) {
2259			if (strstr($key, '.') === false && strstr($key, '[') === false && strstr($key, '->') === false) {
2260				if ($key === 'dwoo') {
2261					$output = '$this->globals';
2262				} elseif ($key === '_root' || $key === '__') {
2263					$output = '$this->data';
2264				} elseif ($key === '_parent' || $key === '_') {
2265					$output = '$this->readParentVar(1)';
2266				} elseif ($key === '_key') {
2267					$output = '$tmp_key';
2268				} else {
2269					if ($curBlock === 'root') {
2270						$output = '$this->scope["'.$key.'"]';
2271					} else {
2272						$output = '(isset($this->scope["'.$key.'"]) ? $this->scope["'.$key.'"] : null)';
2273					}
2274				}
2275			} else {
2276				preg_match_all('#(\[|->|\.)?([a-z0-9_]+|(\\\?[\'"])[^\3]*?\3)\]?#i', $key, $m);
2277
2278				$i = $m[2][0];
2279				if ($i === '_parent' || $i === '_') {
2280					$parentCnt = 0;
2281
2282					while (true) {
2283						$parentCnt++;
2284						array_shift($m[2]);
2285						array_shift($m[1]);
2286						if (current($m[2]) === '_parent') {
2287							continue;
2288						}
2289						break;
2290					}
2291
2292					$output = '$this->readParentVar('.$parentCnt.')';
2293				} else {
2294					if ($i === 'dwoo') {
2295						$output = '$this->globals';
2296						array_shift($m[2]);
2297						array_shift($m[1]);
2298					} elseif ($i === '_root' || $i === '__') {
2299						$output = '$this->data';
2300						array_shift($m[2]);
2301						array_shift($m[1]);
2302					} elseif ($i === '_key') {
2303						$output = '$tmp_key';
2304					} else {
2305						$output = '$this->scope';
2306					}
2307
2308					while (count($m[1]) && $m[1][0] !== '->') {
2309						$m[2][0] = preg_replace('/(^\\\([\'"])|\\\([\'"])$)/x', '$2$3', $m[2][0]);
2310						if(substr($m[2][0], 0, 1) == '"' || substr($m[2][0], 0, 1) == "'") {
2311							$output .= '['.$m[2][0].']';
2312						} else {
2313							$output .= '["'.$m[2][0].'"]';
2314						}
2315						array_shift($m[2]);
2316						array_shift($m[1]);
2317					}
2318
2319					if ($curBlock !== 'root') {
2320						$output = '(isset('.$output.') ? '.$output.':null)';
2321					}
2322				}
2323
2324				if (count($m[2])) {
2325					unset($m[0]);
2326					$output = '$this->readVarInto('.str_replace("\n", '', var_export($m, true)).', '.$output.', '.($curBlock == 'root' ? 'false': 'true').')';
2327				}
2328			}
2329		} else {
2330			preg_match_all('#(\[|->|\.)?([a-z0-9_]+)\]?#i', $key, $m);
2331			unset($m[0]);
2332			$output = '$this->readVar('.str_replace("\n", '', var_export($m, true)).')';
2333		}
2334
2335		return $output;
2336	}
2337
2338	/**
2339	 * flattens a variable tree, this helps in parsing very complex variables such as $var.foo[$foo.bar->baz].baz,
2340	 * it computes the contents of the brackets first and works out from there
2341	 *
2342	 * @param array $tree the variable tree parsed by he parseVar() method that must be flattened
2343	 * @param bool $recursed leave that to false by default, it is only for internal use
2344	 * @return string flattened tree
2345	 */
2346	protected function flattenVarTree(array $tree, $recursed=false)
2347	{
2348		$out = $recursed ?  '".$this->readVarInto(' : '';
2349		foreach ($tree as $bit) {
2350			if (is_array($bit)) {
2351				$out.='.'.$this->flattenVarTree($bit, false);
2352			} else {
2353				$key = str_replace('"', '\\"', $bit);
2354
2355				if (substr($key, 0, 1)==='$') {
2356					$out .= '".'.$this->parseVar($key, 0, strlen($key), false, 'variable').'."';
2357				} else {
2358					$cnt = substr_count($key, '$');
2359
2360					if ($this->debug) echo 'PARSING SUBVARS IN : '.$key.'<br>';
2361					if ($cnt > 0) {
2362						while (--$cnt >= 0) {
2363							if (isset($last)) {
2364								$last = strrpos($key, '$', - (strlen($key) - $last + 1));
2365							} else {
2366								$last = strrpos($key, '$');
2367							}
2368							preg_match('#\$[a-z0-9_]+((?:(?:\.|->)(?:[a-z0-9_]+|(?R))|\[(?:[a-z0-9_]+|(?R))\]))*'.
2369									  '((?:(?:[+/*%-])(?:\$[a-z0-9.[\]>_:-]+(?:\([^)]*\))?|[0-9.,]*))*)#i', substr($key, $last), $submatch);
2370
2371							$len = strlen($submatch[0]);
2372							$key = substr_replace(
2373								$key,
2374								preg_replace_callback(
2375									'#(\$[a-z0-9_]+((?:(?:\.|->)(?:[a-z0-9_]+|(?R))|\[(?:[a-z0-9_]+|(?R))\]))*)'.
2376									'((?:(?:[+/*%-])(?:\$[a-z0-9.[\]>_:-]+(?:\([^)]*\))?|[0-9.,]*))*)#i',
2377									array($this, 'replaceVarKeyHelper'), substr($key, $last, $len)
2378								),
2379								$last,
2380								$len
2381							);
2382							if ($this->debug) echo 'RECURSIVE VAR REPLACEMENT DONE : '.$key.'<br>';
2383						}
2384						unset($last);
2385
2386						$out .= $key;
2387					} else {
2388						$out .= $key;
2389					}
2390				}
2391			}
2392		}
2393		$out .= $recursed ? ', true)."' : '';
2394		return $out;
2395	}
2396
2397	/**
2398	 * helper function that parses a variable
2399	 *
2400	 * @param array $match the matched variable, array(1=>"string match")
2401	 * @return string parsed variable
2402	 */
2403	protected function replaceVarKeyHelper($match)
2404	{
2405		return '".'.$this->parseVar($match[0], 0, strlen($match[0]), false, 'variable').'."';
2406	}
2407
2408	/**
2409	 * parses various constants, operators or non-quoted strings
2410	 *
2411	 * @param string $in the string within which we must parse something
2412	 * @param int $from the starting offset of the parsed area
2413	 * @param int $to the ending offset of the parsed area
2414	 * @param mixed $parsingParams must be an array if we are parsing a function or modifier's parameters, or false by default
2415	 * @param string $curBlock the current parser-block being processed
2416	 * @param mixed $pointer a reference to a pointer that will be increased by the amount of characters parsed, or null by default
2417	 * @return string parsed values
2418	 */
2419	protected function parseOthers($in, $from, $to, $parsingParams = false, $curBlock='', &$pointer = null)
2420	{
2421		$first = $in[$from];
2422		$substr = substr($in, $from, $to-$from);
2423
2424		$end = strlen($substr);
2425
2426		if ($curBlock === 'condition') {
2427			$breakChars = array('(', ')', ' ', '||', '&&', '|', '&', '>=', '<=', '===', '==', '=', '!==', '!=', '<<', '<', '>>', '>', '^', '~', ',', '+', '-', '*', '/', '%', '!', '?', ':', $this->rd, ';');
2428		} elseif ($curBlock === 'modifier') {
2429			$breakChars = array(' ', ',', ')', ':', '|', "\r", "\n", "\t", ";", $this->rd);
2430		} elseif ($curBlock === 'expression') {
2431			$breakChars = array('/', '%', '+', '-', '*', ' ', ',', ')', "\r", "\n", "\t", ";", $this->rd);
2432		} else {
2433			$breakChars = array(' ', ',', ')', "\r", "\n", "\t", ";", $this->rd);
2434		}
2435
2436		$breaker = false;
2437		foreach ($breakChars as $k => $char) {
2438			$test = strpos($substr, $char);
2439			if ($test !== false && $test < $end) {
2440				$end = $test;
2441				$breaker = $k;
2442			}
2443		}
2444
2445		if ($curBlock === 'condition') {
2446			if ($end === 0 && $breaker !== false) {
2447				$end = strlen($breakChars[$breaker]);
2448			}
2449		}
2450
2451		if ($end !== false) {
2452			$substr = substr($substr, 0, $end);
2453		}
2454
2455		if ($pointer !== null) {
2456			$pointer += strlen($substr);
2457		}
2458
2459		$src = $substr;
2460
2461		if (strtolower($substr) === 'false' || strtolower($substr) === 'no' || strtolower($substr) === 'off') {
2462			if ($this->debug) echo 'BOOLEAN(FALSE) PARSED<br />';
2463			$substr = 'false';
2464		} elseif (strtolower($substr) === 'true' || strtolower($substr) === 'yes' || strtolower($substr) === 'on') {
2465			if ($this->debug) echo 'BOOLEAN(TRUE) PARSED<br />';
2466			$substr = 'true';
2467		} elseif ($substr === 'null' || $substr === 'NULL') {
2468			if ($this->debug) echo 'NULL PARSED<br />';
2469			$substr = 'null';
2470		} elseif (is_numeric($substr)) {
2471			$substr = (float) $substr;
2472			if ((int) $substr == $substr) {
2473				$substr = (int) $substr;
2474			}
2475			if ($this->debug) echo 'NUMBER ('.$substr.') PARSED<br />';
2476		} elseif (preg_match('{^-?(\d+|\d*(\.\d+))\s*([/*%+-]\s*-?(\d+|\d*(\.\d+)))+$}', $substr)) {
2477			if ($this->debug) echo 'SIMPLE MATH PARSED<br />';
2478			$substr = '('.$substr.')';
2479		} elseif ($curBlock === 'condition' && array_search($substr, $breakChars, true) !== false) {
2480			if ($this->debug) echo 'BREAKCHAR ('.$substr.') PARSED<br />';
2481			//$substr = '"'.$substr.'"';
2482		} else {
2483			$substr = $this->replaceStringVars('\''.str_replace('\'', '\\\'', $substr).'\'', '\'', $curBlock);
2484
2485			if ($this->debug) echo 'BLABBER ('.$substr.') CASTED AS STRING<br />';
2486		}
2487
2488		if (is_array($parsingParams)) {
2489			$parsingParams[] = array($substr, $src);
2490			return $parsingParams;
2491		} elseif ($curBlock === 'namedparam') {
2492			return array($substr, $src);
2493		} elseif ($curBlock === 'expression') {
2494			return $substr;
2495		} else {
2496			throw new Exception('Something went wrong');
2497		}
2498	}
2499
2500	/**
2501	 * replaces variables within a parsed string
2502	 *
2503	 * @param string $string the parsed string
2504	 * @param string $first the first character parsed in the string, which is the string delimiter (' or ")
2505	 * @param string $curBlock the current parser-block being processed
2506	 * @return string the original string with variables replaced
2507	 */
2508	protected function replaceStringVars($string, $first, $curBlock='')
2509	{
2510		$pos = 0;
2511		if ($this->debug) echo 'STRING VAR REPLACEMENT : '.$string.'<br>';
2512		// replace vars
2513		while (($pos = strpos($string, '$', $pos)) !== false) {
2514			$prev = substr($string, $pos-1, 1);
2515			if ($prev === '\\') {
2516				$pos++;
2517				continue;
2518			}
2519
2520			$var = $this->parse($string, $pos, null, false, ($curBlock === 'modifier' ? 'modifier' : ($prev === '`' ? 'delimited_string':'string')));
2521			$len = $var[0];
2522			$var = $this->parse(str_replace('\\'.$first, $first, $string), $pos, null, false, ($curBlock === 'modifier' ? 'modifier' : ($prev === '`' ? 'delimited_string':'string')));
2523
2524			if ($prev === '`' && substr($string, $pos+$len, 1) === '`') {
2525				$string = substr_replace($string, $first.'.'.$var[1].'.'.$first, $pos-1, $len+2);
2526			} else {
2527				$string = substr_replace($string, $first.'.'.$var[1].'.'.$first, $pos, $len);
2528			}
2529			$pos += strlen($var[1]) + 2;
2530			if ($this->debug) echo 'STRING VAR REPLACEMENT DONE : '.$string.'<br>';
2531		}
2532
2533		// handle modifiers
2534		// TODO Obsolete?
2535		$string = preg_replace_callback('#("|\')\.(.+?)\.\1((?:\|(?:@?[a-z0-9_]+(?:(?::("|\').+?\4|:[^`]*))*))+)#i', array($this, 'replaceModifiers'), $string);
2536
2537		// replace escaped dollar operators by unescaped ones if required
2538		if ($first==="'") {
2539			$string = str_replace('\\$', '$', $string);
2540		}
2541
2542		return $string;
2543	}
2544
2545	/**
2546	 * replaces the modifiers applied to a string or a variable
2547	 *
2548	 * @param array $m the regex matches that must be array(1=>"double or single quotes enclosing a string, when applicable", 2=>"the string or var", 3=>"the modifiers matched")
2549	 * @param string $curBlock the current parser-block being processed
2550	 * @return string the input enclosed with various function calls according to the modifiers found
2551	 */
2552	protected function replaceModifiers(array $m, $curBlock = null, &$pointer = null)
2553	{
2554		if ($this->debug) echo 'PARSING MODIFIERS : '.$m[3].'<br />';
2555
2556		if ($pointer !== null) {
2557			$pointer += strlen($m[3]);
2558		}
2559		// remove first pipe
2560		$cmdstrsrc = substr($m[3], 1);
2561		// remove last quote if present
2562		if (substr($cmdstrsrc, -1, 1) === $m[1]) {
2563			$cmdstrsrc = substr($cmdstrsrc, 0, -1);
2564			$add = $m[1];
2565		}
2566
2567		$output = $m[2];
2568
2569		$continue = true;
2570		while (strlen($cmdstrsrc) > 0 && $continue) {
2571			if ($cmdstrsrc[0] === '|') {
2572				$cmdstrsrc = substr($cmdstrsrc, 1);
2573				continue;
2574			}
2575			if ($cmdstrsrc[0] === ' ' || $cmdstrsrc[0] === ';' || substr($cmdstrsrc, 0, strlen($this->rd)) === $this->rd) {
2576				if ($this->debug) echo 'MODIFIER PARSING ENDED, RIGHT DELIMITER or ";" FOUND<br/>';
2577				$continue = false;
2578				if ($pointer !== null) {
2579					$pointer -= strlen($cmdstrsrc);
2580				}
2581				break;
2582			}
2583			$cmdstr = $cmdstrsrc;
2584			$paramsep = ':';
2585			if (!preg_match('/^(@{0,2}[a-z][a-z0-9_]*)(:)?/i', $cmdstr, $match)) {
2586				throw new Dwoo_Compilation_Exception($this, 'Invalid modifier name, started with : '.substr($cmdstr, 0, 10));
2587			}
2588			$paramspos = !empty($match[2]) ? strlen($match[1]) : false;
2589			$func = $match[1];
2590
2591			$state = 0;
2592			if ($paramspos === false) {
2593				$cmdstrsrc = substr($cmdstrsrc, strlen($func));
2594				$params = array();
2595				if ($this->debug) echo 'MODIFIER ('.$func.') CALLED WITH NO PARAMS<br/>';
2596			} else {
2597				$paramstr = substr($cmdstr, $paramspos+1);
2598				if (substr($paramstr, -1, 1) === $paramsep) {
2599					$paramstr = substr($paramstr, 0, -1);
2600				}
2601
2602				$ptr = 0;
2603				$params = array();
2604				while ($ptr < strlen($paramstr)) {
2605					if ($this->debug) echo 'MODIFIER ('.$func.') START PARAM PARSING WITH POINTER AT '.$ptr.'<br/>';
2606					if ($this->debug) echo $paramstr.'--'.$ptr.'--'.strlen($paramstr).'--modifier<br/>';
2607					$params = $this->parse($paramstr, $ptr, strlen($paramstr), $params, 'modifier', $ptr);
2608					if ($this->debug) echo 'PARAM PARSED, POINTER AT '.$ptr.'<br/>';
2609
2610					if ($ptr >= strlen($paramstr)) {
2611						if ($this->debug) echo 'PARAM PARSING ENDED, PARAM STRING CONSUMED<br/>';
2612						break;
2613					}
2614
2615					if ($paramstr[$ptr] === ' ' || $paramstr[$ptr] === '|' || $paramstr[$ptr] === ';' || substr($paramstr, $ptr, strlen($this->rd)) === $this->rd) {
2616						if ($this->debug) echo 'PARAM PARSING ENDED, " ", "|", RIGHT DELIMITER or ";" FOUND, POINTER AT '.$ptr.'<br/>';
2617						if ($paramstr[$ptr] !== '|') {
2618							$continue = false;
2619							if ($pointer !== null) {
2620								$pointer -= strlen($paramstr) - $ptr;
2621							}
2622						}
2623						$ptr++;
2624						break;
2625					}
2626					if ($ptr < strlen($paramstr) && $paramstr[$ptr] === ':') {
2627						$ptr++;
2628					}
2629				}
2630				$cmdstrsrc = substr($cmdstrsrc, strlen($func)+1+$ptr);
2631				$paramstr = substr($paramstr, 0, $ptr);
2632				foreach ($params as $k=>$p) {
2633					if (is_array($p) && is_array($p[1])) {
2634						$state |= 2;
2635					} else {
2636						if (($state & 2) && preg_match('#^(["\'])(.+?)\1$#', $p[0], $m)) {
2637							$params[$k] = array($m[2], array('true', 'true'));
2638						} else {
2639							if ($state & 2) {
2640								throw new Dwoo_Compilation_Exception($this, 'You can not use an unnamed parameter after a named one');
2641							}
2642							$state |= 1;
2643						}
2644					}
2645				}
2646			}
2647
2648			// check if we must use array_map with this plugin or not
2649			$mapped = false;
2650			if (substr($func, 0, 1) === '@') {
2651				$func = substr($func, 1);
2652				$mapped = true;
2653			}
2654
2655			$pluginType = $this->getPluginType($func);
2656
2657			if ($state & 2) {
2658				array_unshift($params, array('value', array($output, $output)));
2659			} else {
2660				array_unshift($params, array($output, $output));
2661			}
2662
2663			if ($pluginType & Dwoo::NATIVE_PLUGIN) {
2664				$params = $this->mapParams($params, null, $state);
2665
2666				$params = $params['*'][0];
2667
2668				$params = self::implode_r($params);
2669
2670				if ($mapped) {
2671					$output = '$this->arrayMap(\''.$func.'\', array('.$params.'))';
2672				} else {
2673					$output = $func.'('.$params.')';
2674				}
2675			} elseif ($pluginType & Dwoo::PROXY_PLUGIN) {
2676				$params = $this->mapParams($params, $this->getDwoo()->getPluginProxy()->getCallback($func), $state);
2677				foreach ($params as &$p)
2678					$p = $p[0];
2679				$output = call_user_func(array($this->dwoo->getPluginProxy(), 'getCode'), $func, $params);
2680			} elseif ($pluginType & Dwoo::SMARTY_MODIFIER) {
2681				$params = $this->mapParams($params, null, $state);
2682				$params = $params['*'][0];
2683
2684				$params = self::implode_r($params);
2685
2686				if ($pluginType & Dwoo::CUSTOM_PLUGIN) {
2687					$callback = $this->customPlugins[$func]['callback'];
2688					if (is_array($callback)) {
2689						if (is_object($callback[0])) {
2690							$output = ($mapped ? '$this->arrayMap' : 'call_user_func_array').'(array($this->plugins[\''.$func.'\'][\'callback\'][0], \''.$callback[1].'\'), array('.$params.'))';
2691						} else {
2692							$output = ($mapped ? '$this->arrayMap' : 'call_user_func_array').'(array(\''.$callback[0].'\', \''.$callback[1].'\'), array('.$params.'))';
2693						}
2694					} elseif ($mapped) {
2695						$output = '$this->arrayMap(\''.$callback.'\', array('.$params.'))';
2696					} else {
2697						$output = $callback.'('.$params.')';
2698					}
2699				} elseif ($mapped) {
2700					$output = '$this->arrayMap(\'smarty_modifier_'.$func.'\', array('.$params.'))';
2701				} else {
2702					$output = 'smarty_modifier_'.$func.'('.$params.')';
2703				}
2704			} else {
2705				if ($pluginType & Dwoo::CUSTOM_PLUGIN) {
2706					$callback = $this->customPlugins[$func]['callback'];
2707					$pluginName = $callback;
2708				} else {
2709					$pluginName = 'Dwoo_Plugin_'.$func;
2710
2711					if ($pluginType & Dwoo::CLASS_PLUGIN) {
2712						$callback = array($pluginName, ($pluginType & Dwoo::COMPILABLE_PLUGIN) ? 'compile' : 'process');
2713					} else {
2714						$callback = $pluginName . (($pluginType & Dwoo::COMPILABLE_PLUGIN) ? '_compile' : '');
2715					}
2716				}
2717
2718				$params = $this->mapParams($params, $callback, $state);
2719
2720				foreach ($params as &$p)
2721					$p = $p[0];
2722
2723				if ($pluginType & Dwoo::FUNC_PLUGIN) {
2724					if ($pluginType & Dwoo::COMPILABLE_PLUGIN) {
2725						if ($mapped) {
2726							throw new Dwoo_Compilation_Exception($this, 'The @ operator can not be used on compiled plugins.');
2727						}
2728						if ($pluginType & Dwoo::CUSTOM_PLUGIN) {
2729							$funcCompiler = $this->customPlugins[$func]['callback'];
2730						} else {
2731							$funcCompiler = 'Dwoo_Plugin_'.$func.'_compile';
2732						}
2733						array_unshift($params, $this);
2734						$output = call_user_func_array($funcCompiler, $params);
2735					} else {
2736						array_unshift($params, '$this');
2737
2738						$params = self::implode_r($params);
2739						if ($mapped) {
2740							$output = '$this->arrayMap(\''.$pluginName.'\', array('.$params.'))';
2741						} else {
2742							$output = $pluginName.'('.$params.')';
2743						}
2744					}
2745				} else {
2746					if ($pluginType & Dwoo::COMPILABLE_PLUGIN) {
2747						if ($mapped) {
2748							throw new Dwoo_Compilation_Exception($this, 'The @ operator can not be used on compiled plugins.');
2749						}
2750						if ($pluginType & Dwoo::CUSTOM_PLUGIN) {
2751							$callback = $this->customPlugins[$func]['callback'];
2752							if (!is_array($callback)) {
2753								if (!method_exists($callback, 'compile')) {
2754									throw new Dwoo_Exception('Custom plugin '.$func.' must implement the "compile" method to be compilable, or you should provide a full callback to the method to use');
2755								}
2756								if (($ref = new ReflectionMethod($callback, 'compile')) && $ref->isStatic()) {
2757									$funcCompiler = array($callback, 'compile');
2758								} else {
2759									$funcCompiler = array(new $callback, 'compile');
2760								}
2761							} else {
2762								$funcCompiler = $callback;
2763							}
2764						} else {
2765							$funcCompiler = array('Dwoo_Plugin_'.$func, 'compile');
2766							array_unshift($params, $this);
2767						}
2768						$output = call_user_func_array($funcCompiler, $params);
2769					} else {
2770						$params = self::implode_r($params);
2771
2772						if ($pluginType & Dwoo::CUSTOM_PLUGIN) {
2773							if (is_object($callback[0])) {
2774								$output = ($mapped ? '$this->arrayMap' : 'call_user_func_array').'(array($this->plugins[\''.$func.'\'][\'callback\'][0], \''.$callback[1].'\'), array('.$params.'))';
2775							} else {
2776								$output = ($mapped ? '$this->arrayMap' : 'call_user_func_array').'(array(\''.$callback[0].'\', \''.$callback[1].'\'), array('.$params.'))';
2777							}
2778						} elseif ($mapped) {
2779							$output = '$this->arrayMap(array($this->getObjectPlugin(\'Dwoo_Plugin_'.$func.'\'), \'process\'), array('.$params.'))';
2780						} else {
2781							$output = '$this->classCall(\''.$func.'\', array('.$params.'))';
2782						}
2783					}
2784				}
2785			}
2786		}
2787
2788		if ($curBlock === 'var' || $m[1] === null) {
2789			return $output;
2790		} elseif ($curBlock === 'string' || $curBlock === 'root') {
2791			return $m[1].'.'.$output.'.'.$m[1].(isset($add)?$add:null);
2792		}
2793	}
2794
2795	/**
2796	 * recursively implodes an array in a similar manner as var_export() does but with some tweaks
2797	 * to handle pre-compiled values and the fact that we do not need to enclose everything with
2798	 * "array" and do not require top-level keys to be displayed
2799	 *
2800	 * @param array $params the array to implode
2801	 * @param bool $recursiveCall if set to true, the function outputs key names for the top level
2802	 * @return string the imploded array
2803	 */
2804	public static function implode_r(array $params, $recursiveCall = false)
2805	{
2806		$out = '';
2807		foreach ($params as $k=>$p) {
2808			if (is_array($p)) {
2809				$out2 = 'array(';
2810				foreach ($p as $k2=>$v)
2811					$out2 .= var_export($k2, true).' => '.(is_array($v) ? 'array('.self::implode_r($v, true).')' : $v).', ';
2812				$p = rtrim($out2, ', ').')';
2813			}
2814			if ($recursiveCall) {
2815				$out .= var_export($k, true).' => '.$p.', ';
2816			} else {
2817				$out .= $p.', ';
2818			}
2819		}
2820		return rtrim($out, ', ');
2821	}
2822
2823	/**
2824	 * returns the plugin type of a plugin and adds it to the used plugins array if required
2825	 *
2826	 * @param string $name plugin name, as found in the template
2827	 * @return int type as a multi bit flag composed of the Dwoo plugin types constants
2828	 */
2829	protected function getPluginType($name)
2830	{
2831		$pluginType = -1;
2832
2833		if (($this->securityPolicy === null && (function_exists($name) || strtolower($name) === 'isset' || strtolower($name) === 'empty')) ||
2834			($this->securityPolicy !== null && in_array(strtolower($name), $this->securityPolicy->getAllowedPhpFunctions()) !== false)) {
2835			$phpFunc = true;
2836		}
2837
2838		while ($pluginType <= 0) {
2839			if (isset($this->templatePlugins[$name])) {
2840				$pluginType = Dwoo::TEMPLATE_PLUGIN | Dwoo::COMPILABLE_PLUGIN;
2841			} elseif (isset($this->customPlugins[$name])) {
2842				$pluginType = $this->customPlugins[$name]['type'] | Dwoo::CUSTOM_PLUGIN;
2843			} elseif (class_exists('Dwoo_Plugin_'.$name, false) !== false) {
2844				if (is_subclass_of('Dwoo_Plugin_'.$name, 'Dwoo_Block_Plugin')) {
2845					$pluginType = Dwoo::BLOCK_PLUGIN;
2846				} else {
2847					$pluginType = Dwoo::CLASS_PLUGIN;
2848				}
2849				$interfaces = class_implements('Dwoo_Plugin_'.$name, false);
2850				if (in_array('Dwoo_ICompilable', $interfaces) !== false || in_array('Dwoo_ICompilable_Block', $interfaces) !== false) {
2851					$pluginType |= Dwoo::COMPILABLE_PLUGIN;
2852				}
2853			} elseif (function_exists('Dwoo_Plugin_'.$name) !== false) {
2854				$pluginType = Dwoo::FUNC_PLUGIN;
2855			} elseif (function_exists('Dwoo_Plugin_'.$name.'_compile')) {
2856				$pluginType = Dwoo::FUNC_PLUGIN | Dwoo::COMPILABLE_PLUGIN;
2857			} elseif (function_exists('smarty_modifier_'.$name) !== false) {
2858				$pluginType = Dwoo::SMARTY_MODIFIER;
2859			} elseif (function_exists('smarty_function_'.$name) !== false) {
2860				$pluginType = Dwoo::SMARTY_FUNCTION;
2861			} elseif (function_exists('smarty_block_'.$name) !== false) {
2862				$pluginType = Dwoo::SMARTY_BLOCK;
2863			} else {
2864				if ($pluginType===-1) {
2865					try {
2866						$this->dwoo->getLoader()->loadPlugin($name, isset($phpFunc)===false);
2867					} catch (Exception $e) {
2868						if (isset($phpFunc)) {
2869							$pluginType = Dwoo::NATIVE_PLUGIN;
2870						} elseif (is_object($this->dwoo->getPluginProxy()) && $this->dwoo->getPluginProxy()->handles($name)) {
2871							$pluginType = Dwoo::PROXY_PLUGIN;
2872							break;
2873						} else {
2874							throw $e;
2875						}
2876					}
2877				} else {
2878					throw new Dwoo_Exception('Plugin "'.$name.'" could not be found');
2879				}
2880				$pluginType++;
2881			}
2882		}
2883
2884		if (($pluginType & Dwoo::COMPILABLE_PLUGIN) === 0 && ($pluginType & Dwoo::NATIVE_PLUGIN) === 0 && ($pluginType & Dwoo::PROXY_PLUGIN) === 0) {
2885			$this->usedPlugins[$name] = $pluginType;
2886		}
2887
2888		return $pluginType;
2889	}
2890
2891	/**
2892	 * allows a plugin to load another one at compile time, this will also mark
2893	 * it as used by this template so it will be loaded at runtime (which can be
2894	 * useful for compiled plugins that rely on another plugin when their compiled
2895	 * code runs)
2896	 *
2897	 * @param string $name the plugin name
2898	 */
2899	public function loadPlugin($name) {
2900		$this->getPluginType($name);
2901	}
2902
2903	/**
2904	 * runs htmlentities over the matched <?php ?> blocks when the security policy enforces that
2905	 *
2906	 * @param array $match matched php block
2907	 * @return string the htmlentities-converted string
2908	 */
2909	protected function phpTagEncodingHelper($match)
2910	{
2911		return htmlspecialchars($match[0]);
2912	}
2913
2914	/**
2915	 * maps the parameters received from the template onto the parameters required by the given callback
2916	 *
2917	 * @param array $params the array of parameters
2918	 * @param callback $callback the function or method to reflect on to find out the required parameters
2919	 * @param int $callType the type of call in the template, 0 = no params, 1 = php-style call, 2 = named parameters call
2920	 * @param array $map the parameter map to use, if not provided it will be built from the callback
2921	 * @return array parameters sorted in the correct order with missing optional parameters filled
2922	 */
2923	protected function mapParams(array $params, $callback, $callType=2, $map = null)
2924	{
2925		if (!$map) {
2926			$map = $this->getParamMap($callback);
2927		}
2928
2929		$paramlist = array();
2930
2931		// transforms the parameter array from (x=>array('paramname'=>array(values))) to (paramname=>array(values))
2932		$ps = array();
2933		foreach ($params as $p) {
2934			if (is_array($p[1])) {
2935				$ps[$p[0]] = $p[1];
2936			} else {
2937				$ps[] = $p;
2938			}
2939		}
2940
2941		// loops over the param map and assigns values from the template or default value for unset optional params
2942		foreach ($map as $k => $v) {
2943			if ($v[0] === '*') {
2944				// "rest" array parameter, fill every remaining params in it and then break
2945				if (count($ps) === 0) {
2946					if ($v[1]===false) {
2947						throw new Dwoo_Compilation_Exception($this, 'Rest argument missing for '.str_replace(array('Dwoo_Plugin_', '_compile'), '', (is_array($callback) ? $callback[0] : $callback)));
2948					} else {
2949						break;
2950					}
2951				}
2952				$tmp = array();
2953				$tmp2 = array();
2954				foreach ($ps as $i=>$p) {
2955					$tmp[$i] = $p[0];
2956					$tmp2[$i] = $p[1];
2957					unset($ps[$i]);
2958				}
2959				$paramlist[$v[0]] = array($tmp, $tmp2);
2960				unset($tmp, $tmp2, $i, $p);
2961				break;
2962			} elseif (isset($ps[$v[0]])) {
2963				// parameter is defined as named param
2964				$paramlist[$v[0]] = $ps[$v[0]];
2965				unset($ps[$v[0]]);
2966			} elseif (isset($ps[$k])) {
2967				// parameter is defined as ordered param
2968				$paramlist[$v[0]] = $ps[$k];
2969				unset($ps[$k]);
2970			} elseif ($v[1]===false) {
2971				// parameter is not defined and not optional, throw error
2972				throw new Dwoo_Compilation_Exception($this, 'Argument '.$k.'/'.$v[0].' missing for '.str_replace(array('Dwoo_Plugin_', '_compile'), '', (is_array($callback) ? $callback[0] : $callback)));
2973			} elseif ($v[2]===null) {
2974				// enforce lowercased null if default value is null (php outputs NULL with var export)
2975				$paramlist[$v[0]] = array('null', null);
2976			} else {
2977				// outputs default value with var_export
2978				$paramlist[$v[0]] = array(var_export($v[2], true), $v[2]);
2979			}
2980		}
2981
2982		if (count($ps)) {
2983			foreach ($ps as $i=>$p) {
2984				array_push($paramlist, $p);
2985			}
2986		}
2987
2988		return $paramlist;
2989	}
2990
2991	/**
2992	 * returns the parameter map of the given callback, it filters out entries typed as Dwoo and Dwoo_Compiler and turns the rest parameter into a "*"
2993	 *
2994	 * @param callback $callback the function/method to reflect on
2995	 * @return array processed parameter map
2996	 */
2997	protected function getParamMap($callback)
2998	{
2999		if (is_null($callback)) {
3000			return array(array('*', true));
3001		}
3002		if (is_array($callback)) {
3003			$ref = new ReflectionMethod($callback[0], $callback[1]);
3004		} else {
3005			$ref = new ReflectionFunction($callback);
3006		}
3007
3008		$out = array();
3009
3010		if (PHP_VERSION_ID < 70100) {
3011			foreach ($ref->getParameters() as $param) {
3012				if (($class = $param->getClass()) !== null && $class->name === 'Dwoo') {
3013					continue;
3014				}
3015				if (($class = $param->getClass()) !== null && $class->name === 'Dwoo_Compiler') {
3016					continue;
3017				}
3018				if ($param->getName() === 'rest' && $param->isArray() === true) {
3019					$out[] = array('*', $param->isOptional(), null);
3020				}
3021				$out[] = array($param->getName(), $param->isOptional(), $param->isOptional() ? $param->getDefaultValue() : null);
3022			}
3023		} else {
3024			foreach ($ref->getParameters() as $param) {
3025				if ($param->getType() && !$param->getType()->isBuiltin() && $param->getType()->getName() === 'Dwoo') {
3026					continue;
3027				}
3028				if ($param->getType() && !$param->getType()->isBuiltin() && $param->getType()->getName() === 'Dwoo_Compiler') {
3029					continue;
3030				}
3031				if ($param->getName() === 'rest' && $param->getType() && $param->getType()->getName() === 'array') {
3032					$out[] = array('*', $param->isOptional(), null);
3033				}
3034				$out[] = array($param->getName(), $param->isOptional(), $param->isOptional() ? $param->getDefaultValue() : null);
3035			}
3036		}
3037
3038		return $out;
3039	}
3040
3041	/**
3042	 * returns a default instance of this compiler, used by default by all Dwoo templates that do not have a
3043	 * specific compiler assigned and when you do not override the default compiler factory function
3044	 *
3045	 * @see Dwoo::setDefaultCompilerFactory()
3046	 * @return Dwoo_Compiler
3047	 */
3048	public static function compilerFactory()
3049	{
3050		if (self::$instance === null) {
3051			self::$instance = new self;
3052		}
3053		return self::$instance;
3054	}
3055}
3056