VMXTemplate/VMXTemplateCompiler.php

5009 lines
121 KiB
PHP
Raw Permalink Blame History

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

<?php
/**
* Homepage: http://yourcmc.ru/wiki/VMX::Template
* License: GNU GPLv3 or later
* Author: Vitaliy Filippov, 2006-2020
* Version: V3 (LALR), 2020-01-01
*
* This file contains the implementation of VMX::Template compiler.
* It is only used when a template is compiled in runtime.
*/
/**
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
* http://www.gnu.org/copyleft/gpl.html
*/
class VMXTemplateCompiler
{
// Function aliases
static $functions = array(
'i' => 'int',
'intval' => 'int',
'lower' => 'lc',
'lowercase' => 'lc',
'upper' => 'uc',
'uppercase' => 'uc',
'addslashes' => 'quote',
'q' => 'quote',
'sq' => 'sql_quote',
're_quote' => 'requote',
'preg_quote' => 'requote',
'uri_escape' => 'urlencode',
'uriquote' => 'urlencode',
'substring' => 'substr',
'htmlspecialchars' => 'html',
's' => 'html',
'strip_tags' => 'strip',
't' => 'strip',
'h' => 'strip_unsafe',
'implode' => 'join',
'truncate' => 'strlimit',
'hash_keys' => 'keys',
'array_keys' => 'keys',
'array_slice' => 'subarray',
'hget' => 'get',
'aget' => 'get',
'var_dump' => 'dump',
'process' => 'parse',
'include' => 'parse',
'process_inline' => 'parse_inline',
'include_inline' => 'parse_inline',
);
// Functions that do escape HTML, for safe mode
const Q_ALWAYS = -1;
const Q_IF_ALL = -2;
const Q_ALL_BUT_FIRST = -3;
static $functionSafeness = array(
'int' => self::Q_ALWAYS,
'raw' => self::Q_ALWAYS,
'html' => self::Q_ALWAYS,
'strip' => self::Q_ALWAYS,
'strip_unsafe' => self::Q_ALWAYS,
'parse' => self::Q_ALWAYS,
'parse_inline' => self::Q_ALWAYS,
'exec' => self::Q_ALWAYS,
'exec_from' => self::Q_ALWAYS,
'exec_from_inline' => self::Q_ALWAYS,
'quote' => self::Q_ALWAYS,
'sql_quote' => self::Q_ALWAYS,
'requote' => self::Q_ALWAYS,
'urlencode' => self::Q_ALWAYS,
'and' => self::Q_ALWAYS,
'or' => self::Q_IF_ALL,
'not' => self::Q_ALWAYS,
'add' => self::Q_ALWAYS,
'sub' => self::Q_ALWAYS,
'mul' => self::Q_ALWAYS,
'div' => self::Q_ALWAYS,
'mod' => self::Q_ALWAYS,
'min' => self::Q_IF_ALL,
'max' => self::Q_IF_ALL,
'round' => self::Q_ALWAYS,
'log' => self::Q_ALWAYS,
'even' => self::Q_ALWAYS,
'odd' => self::Q_ALWAYS,
'eq' => self::Q_ALWAYS,
'ne' => self::Q_ALWAYS,
'gt' => self::Q_ALWAYS,
'lt' => self::Q_ALWAYS,
'ge' => self::Q_ALWAYS,
'le' => self::Q_ALWAYS,
'seq' => self::Q_ALWAYS,
'sne' => self::Q_ALWAYS,
'sgt' => self::Q_ALWAYS,
'slt' => self::Q_ALWAYS,
'sge' => self::Q_ALWAYS,
'sle' => self::Q_ALWAYS,
'neq' => self::Q_ALWAYS,
'nne' => self::Q_ALWAYS,
'ngt' => self::Q_ALWAYS,
'nlt' => self::Q_ALWAYS,
'nge' => self::Q_ALWAYS,
'nle' => self::Q_ALWAYS,
'strlen' => self::Q_ALWAYS,
'strftime' => self::Q_ALWAYS,
'str_replace' => self::Q_ALL_BUT_FIRST,
'substr' => 1, // parameter number to take safeness from
'trim' => 1,
'split' => 1,
'nl2br' => 1,
'concat' => self::Q_IF_ALL,
'join' => self::Q_IF_ALL,
'subst' => self::Q_IF_ALL,
'strlimit' => 1,
'plural_ru' => self::Q_ALL_BUT_FIRST,
'hash' => self::Q_IF_ALL,
'keys' => 1,
'values' => 1,
'sort' => 1,
'pairs' => 1,
'array' => self::Q_IF_ALL,
'range' => self::Q_ALWAYS,
'is_array' => self::Q_ALWAYS,
'count' => self::Q_ALWAYS,
'subarray' => 1,
'subarray_divmod' => 1,
'set' => 2,
'array_merge' => self::Q_IF_ALL,
'shift' => 1,
'pop' => 1,
'unshift' => self::Q_ALWAYS,
'push' => self::Q_ALWAYS,
'void' => self::Q_ALWAYS,
'json' => self::Q_ALWAYS,
'map' => self::Q_ALL_BUT_FIRST,
'yesno' => self::Q_ALL_BUT_FIRST,
);
var $options, $st, $lexer, $parser;
function __construct($options)
{
$this->options = $options;
}
/**
* Translate a template to PHP
*
* @param $code full template code
* @param $filename input filename for error reporting
* @param $func_ns suffix for class name (Template_SUFFIX)
*/
function parse_all($code, $filename, $func_ns)
{
$this->st = new VMXTemplateState();
$this->options->input_filename = $filename;
$this->st->functions['main'] = array(
'name' => 'main',
'args' => array(),
'body' => '',
);
if (!$this->lexer)
{
$this->lexer = new VMXTemplateLexer($this->options);
$this->parser = new parse_engine(new VMXTemplateParser());
$this->parser->parser->template = $this;
}
$this->lexer->set_code($code);
$this->lexer->feed($this->parser);
if ($this->st->functions['main']['body'] === '')
{
// Parse error
unset($this->st->functions['main']);
}
// Generate code for functions
$code = '';
$functions = [];
$smap = [];
$l = 9;
foreach ($this->st->functions as $n => $f)
{
$ms = [];
preg_match_all('/(?:^|\n)# line (\d+)|\n/', $f['body'], $ms, PREG_SET_ORDER);
foreach ($ms as $m)
{
$l++;
if (!empty($m[1]))
$smap[] = [ $l, $m[1] ];
}
$code .= $f['body'];
$functions[$n] = $f['args'];
}
// Assemble the class code
$functions = var_export($functions, true);
$smap = var_export($smap, true);
$rfn = addcslashes($this->options->input_filename, '\\\'');
$code = "<?php // {$this->options->input_filename}
class Template_$func_ns extends VMXTemplate {
static \$template_filename = '$rfn';
static \$version = ".VMXTemplate::CODE_VERSION.";
function __construct(\$t) {
\$this->tpldata = &\$t->tpldata;
\$this->parent = &\$t;
}
$code
static \$functions = $functions;
static \$smap = $smap;
}
";
return $code;
}
function compile_function($fn, $args)
{
$fn = strtolower($fn);
if (isset(self::$functions[$fn]))
{
// Function alias
$fn = self::$functions[$fn];
}
$argv = [];
$q = isset(self::$functionSafeness[$fn]) ? self::$functionSafeness[$fn] : false;
if ($q > 0)
{
$q = isset($args[$q-1]) ? $args[$q-1][1] : true;
}
elseif ($q == self::Q_ALWAYS)
{
$q = true;
}
elseif ($q == self::Q_IF_ALL || $q == self::Q_ALL_BUT_FIRST)
{
$q = true;
$n = count($args);
for ($i = ($q == self::Q_ALL_BUT_FIRST ? 1 : 0); $i < $n; $i++)
{
$q = $q && $args[$i][1];
}
}
foreach ($args as $a)
{
$argv[] = $a[0];
}
// Check if function is not disabled
if (!empty($this->options->compiletime_functions[$fn]) ||
!isset($this->options->compiletime_functions[$fn]) &&
method_exists($this, "function_$fn"))
{
if (!empty($this->options->compiletime_functions[$fn]))
{
// Custom compile-time function call
$r = call_user_func($this->options->compiletime_functions[$fn], $this, $argv);
}
else
{
// Builtin function call using name
$r = call_user_func_array(array($this, "function_$fn"), $argv);
}
}
else
{
// A block reference or unknown function
$r = "\$this->parent->call_block_list('$fn', array(".implode(', ', $argv)."), '".addcslashes($this->lexer->errorinfo(), "'\\")."')";
$q = true;
}
return [ $r, $q ];
}
/*** Functions ***/
/** Utilities for function parsing **/
// Code for operator-like function
static function fmop($op, $args)
{
return "((" . join(") $op (", $args) . "))";
}
/** Числа, логические операции **/
/* логические операции */
function function_or() { $a = func_get_args(); return "self::perlish_or(".join(",", $a).")"; }
function function_and() { $a = func_get_args(); return self::fmop('&&', $a); }
function function_not($e) { return "!($e)"; }
/* арифметические операции */
function function_add() { $a = func_get_args(); return self::fmop('+', $a); }
function function_sub() { $a = func_get_args(); return self::fmop('-', $a); }
function function_mul() { $a = func_get_args(); return self::fmop('*', $a); }
function function_div() { $a = func_get_args(); return self::fmop('/', $a); }
function function_mod($a,$b) { return "(($a) % ($b))"; }
/* минимум и максимум, округление */
function function_min() { $a = func_get_args(); return "min([ ".implode(', ', $a)." ])"; }
function function_max() { $a = func_get_args(); return "max([ ".implode(', ', $a)." ])"; }
function function_round($a) { return "round($a)"; }
/* логарифм */
function function_log($e) { return "log($e)"; }
/* чётный, нечётный */
function function_even($e) { return "!(($e) & 1)"; }
function function_odd($e) { return "(($e) & 1)"; }
/* приведение к целому числу */
function function_int($e) { return "intval($e)"; }
/* сравнения: == != > < >= <= (аргументов как строк если оба строки, иначе как чисел) */
function function_eq($a,$b) { return "(($a) == ($b))"; }
function function_ne($a,$b) { return "(($a) != ($b))"; }
function function_gt($a,$b) { return "(($a) > ($b))"; }
function function_lt($a,$b) { return "(($a) < ($b))"; }
function function_ge($a,$b) { return "(($a) >= ($b))"; }
function function_le($a,$b) { return "(($a) <= ($b))"; }
/* сравнения: == != > < >= <= (аргументов как строк) */
function function_seq($a,$b) { return "((\"$a\") == (\"$b\"))"; }
function function_sne($a,$b) { return "((\"$a\") != (\"$b\"))"; }
function function_sgt($a,$b) { return "((\"$a\") > (\"$b\"))"; }
function function_slt($a,$b) { return "((\"$a\") < (\"$b\"))"; }
function function_sge($a,$b) { return "((\"$a\") >= (\"$b\"))"; }
function function_sle($a,$b) { return "((\"$a\") <= (\"$b\"))"; }
/* сравнения: == != > < >= <= (аргументов как чисел) */
function function_neq($a,$b) { return "((0+$a) == ($b))"; }
function function_nne($a,$b) { return "((0+$a) != ($b))"; }
function function_ngt($a,$b) { return "((0+$a) > ($b))"; }
function function_nlt($a,$b) { return "((0+$a) < ($b))"; }
function function_nge($a,$b) { return "((0+$a) >= ($b))"; }
function function_nle($a,$b) { return "((0+$a) <= ($b))"; }
/* тернарный оператор $1 ? $2 : $3 */
function function_yesno($a,$b,$c) { return "(($a) ? ($b) : ($c))"; }
/** Строки **/
/* нижний регистр */
function function_lc($e) { return ($this->options->use_utf8 ? "mb_" : "") . "strtolower($e)"; }
/* верхний регистр */
function function_uc($e) { return ($this->options->use_utf8 ? "mb_" : "") . "strtoupper($e)"; }
/* нижний регистр первого символа */
function function_lcfirst($e) { return ($this->options->use_utf8 ? "self::mb_" : "") . "lcfirst($e)"; }
/* верхний регистр первого символа */
function function_ucfirst($e) { return ($this->options->use_utf8 ? "self::mb_" : "") . "ucfirst($e)"; }
/* экранирование кавычек */
function function_quote($e) { return "str_replace(array(\"\\n\",\"\\r\"),array(\"\\\\n\",\"\\\\r\"),addslashes($e))"; }
/* экранирование кавычек в SQL- или CSV- стиле (кавычка " превращается в двойную кавычку "") */
function function_sql_quote($e) { return "str_replace('\"','\"\"',$e)"; }
/* экранирование символов, специальных для регулярного выражения */
function function_requote($e) { return "preg_quote($e)"; }
/* экранирование в стиле URL */
function function_urlencode($e) { return "urlencode($e)"; }
/* замены - по регулярке и по подстроке */
function function_replace($re, $sub, $v)
{
return "preg_replace('#'.str_replace('#','\\\\#',$re).'#s', $sub, $v)";
}
function function_str_replace($s, $sub, $v)
{
return "str_replace($s, $sub, $v)";
}
/* длина строки */
function function_strlen($s) { return ($this->options->use_utf8 ? "mb_" : "") . "strlen($s)"; }
/* подстрока */
function function_substr($s, $start, $length = NULL)
{
return ($this->options->use_utf8 ? "mb_" : "") . "substr($s, $start" . ($length !== NULL ? ", $length" : "") . ")";
}
/* убиение пробелов в начале и конце */
function function_trim($s) { return "trim($s)"; }
/* разбиение строки по регулярному выражению */
function function_split($re, $v, $limit = -1)
{
return "preg_split('#'.str_replace('#','\\\\#',$re).'#s', $v, $limit)";
}
/* пустое преобразование, для отмены автоэкранирования HTML */
function function_raw($e) { return $e; }
/* преобразование символов <>&'" в HTML-сущности &lt; &gt; &amp; &apos; &quot; */
function function_html($e) { return "htmlspecialchars($e,ENT_QUOTES)"; }
/* удаление всех или заданных тегов */
function function_strip($e, $t='') { return "self::strip_tags($e".($t?",$t":"").")"; }
/* удаление "небезопасных" HTML-тегов */
/* TODO: м.б исправлять некорректную разметку? */
function function_strip_unsafe($e) { return "self::strip_tags($e, self::\$safe_tags)"; }
/* заменить \n на <br /> */
function function_nl2br($s) { return "nl2br($s)"; }
/* конкатенация строк */
function function_concat() { $a = func_get_args(); return self::fmop('.', $a); }
/* объединение всех скаляров и всех элементов аргументов-массивов */
function function_join()
{
$a = func_get_args();
$sep = array_shift($a);
return "call_user_func('implode', $sep, self::merge_to_array(".implode(', ', $a)."))";
}
/* подставляет на места $1, $2 и т.п. в строке аргументы */
function function_subst()
{
$a = func_get_args();
return "call_user_func_array('VMXTemplate::exec_subst', self::merge_to_array(".implode(', ', $a)."))";
}
/* sprintf */
function function_sprintf()
{
$a = func_get_args();
return "call_user_func_array('sprintf', self::merge_to_array(".implode(', ', $a)."))";
}
/* strftime */
function function_strftime($fmt, $date = NULL)
{
if ($date !== NULL)
{
$date = ", self::timestamp($date)";
}
return "strftime($fmt$date)";
}
/* ограничение длины строки $maxlen символами на границе пробелов и добавление '...', если что. */
/* strlimit(string, length, dots = '...') */
function function_strlimit($a)
{
$a = func_get_args();
return "self::" . ($this->options->use_utf8 ? "mb_" : "") . "strlimit(".join(",", $a).")";
}
/* выбор правильной формы множественного числа для русского языка */
function function_plural_ru($count, $one, $few, $many)
{
return "self::plural_ru($count, $one, $few, $many)";
}
/** Массивы и хеши **/
/* создание хеша */
function function_hash()
{
$a = func_get_args();
if (count($a) == 1)
return "self::exec_hash(".$a[0].")";
$s = "array(";
$i = 0;
$d = '';
foreach ($a as $v)
{
$s .= $d;
$s .= $v;
$i++;
if ($i & 1)
$d = '=>';
else
$d = ',';
}
$s .= ")";
return $s;
}
/* ключи хеша или массива */
function function_keys($a) { return "array_keys(is_array($a) ? $a : array())"; }
/* значения хеша или массива */
function function_values($a) { return "array_values(is_array($a) ? $a : array())"; }
/* сортировка массива/массивов */
function function_sort()
{
$a = func_get_args();
return "call_user_func('VMXTemplate::exec_sort', self::merge_to_array(".implode(', ', $a)."))";
}
/* пары key => ключ, value => значение для ассоциативного массива */
function function_pairs() { $a = func_get_args(); return "self::exec_pairs(".implode(', ', $a).")"; }
/* создание массива */
function function_array()
{
$a = func_get_args();
return "array(" . join(",", $a) . ")";
}
/* диапазон от $1 до $2 */
function function_range($a, $b) { return "range($a,$b)"; }
/* проверка, массив это или нет? */
function function_is_array($a) { return "is_array($a)"; }
/* число элементов в массиве */
function function_count($e) { return "self::array_count($e)"; }
/* подмассив по номерам элементов */
function function_subarray() { $a = func_get_args(); return "array_slice(" . join(",", $a) . ")"; }
/* подмассив по кратности номеров элементов */
function function_subarray_divmod() { $a = func_get_args(); return "self::exec_subarray_divmod(" . join(",", $a) . ")"; }
/* 0) получить "корневую" переменную по неконстантному ключу
1) получить элемент хеша/массива по неконстантному ключу (например get(iteration.array, rand(5)))
по-моему, это лучше, чем Template Toolkit'овский ад - hash.key.${another.hash.key}.зюка.хрюка и т.п.
2) получить элемент выражения-массива - в PHP < 5.4 не работает (...expression...)['key'],
к примеру не работает range(1,10)[0]
но у нас-то это поддерживается... */
function function_get($a, $k = NULL)
{
if ($k === NULL)
return "\$this->tpldata[$a]";
if (PHP_VERSION_ID > 50400)
return $a."[$k]";
return "self::exec_get($a, $k)";
}
/* присваивание (только lvalue) */
function function_set($l, $r) { return "self::void($l = $r)"; }
/* объединение массивов */
function function_array_merge() { $a = func_get_args(); return "array_merge(" . join(",", $a) . ")"; }
/* shift, unshift, pop, push */
function function_shift($a) { return "array_shift($a)"; }
function function_pop($a) { return "array_pop($a)"; }
function function_unshift($a, $v) { return "array_unshift($a, $v)"; }
function function_push($a, $v) { return "array_push($a, $v)"; }
/** Прочее **/
/* игнорирование результата (а-ля js) */
function function_void($a) { return "self::void($a)"; }
/* дамп переменной */
function function_dump($var)
{
return "var_export($var, true)";
}
/* JSON-кодирование */
function function_json($v) { return "json_encode($v, JSON_UNESCAPED_UNICODE)"; }
/* Аргументы для функций включения
аргументы ::= hash(ключ => значение, ...) | ключ => значение, ...
*/
protected function auto_hash($args)
{
if (!($n = count($args)))
$args = ', $this->tpldata, false';
elseif ($n == 1)
$args = ", ".$args[0];
else
$args = ", ".call_user_func_array(array($this, 'function_hash'), $args);
return $args;
}
/* включение другого файла: parse('файл'[, аргументы]) */
function function_parse()
{
$args = func_get_args();
$file = array_shift($args);
$p = $args ? 'parse_discard' : 'parse_real';
$args = $this->auto_hash($args);
return "\$this->parent->$p($file, NULL, 'main'$args)";
}
/* включение блока из текущего файла: exec('блок'[, аргументы]) */
function function_exec()
{
$args = func_get_args();
$block = array_shift($args);
$p = $args ? 'parse_discard' : 'parse_real';
$args = $this->auto_hash($args);
return "\$this->parent->$p(self::\$template_filename, NULL, $block$args)";
}
/* включение блока из другого файла: exec_from('файл', 'блок'[, аргументы]) */
function function_exec_from()
{
$args = func_get_args();
$file = array_shift($args);
$block = array_shift($args);
$p = $args ? 'parse_discard' : 'parse_real';
$args = $this->auto_hash($args);
return "\$this->parent->$p($file, NULL, $block$args)";
}
/* parse не из файла, хотя и не рекомендуется */
function function_parse_inline()
{
$args = func_get_args();
$code = array_shift($args);
$p = $args ? 'parse_discard' : 'parse_real';
$args = $this->auto_hash($args);
return "\$this->parent->$p(NULL, $code, 'main'$args)";
}
/* сильно не рекомендуется, но возможно:
включение блока не из файла:
exec_from_inline('код', 'блок'[, аргументы]) */
function function_exec_from_inline()
{
$args = func_get_args();
$code = array_shift($args);
$block = array_shift($args);
$p = $args ? 'parse_discard' : 'parse_real';
$args = $this->auto_hash($args);
return "\$this->parent->$p(NULL, $code, $block$args)";
}
/* вызов функции объекта по вычисляемому имени:
call(object, "method", arg1, arg2, ...) или
call_array(object, "method", array(arg1, arg2, ...)) */
function function_call()
{
$a = func_get_args();
$o = array_shift($a);
$m = array_shift($a);
return "call_user_func_array(array($o, $m), array(".implode(", ", $a)."))";
}
function function_call_array($o, $m, $a = NULL)
{
return "call_user_func_array(array($o, $m), ".($a ? $a : "array()").")";
}
/* map() */
function function_map($f)
{
if (!preg_match('/^(["\'])([a-z_]+)\1$/s', $f, $m))
{
$this->lexer->warn("Non-constant function specified for map(): $f");
return 'false';
}
$fn = $m[2];
if (isset(self::$functions[$fn]))
{
// Function alias
$fn = self::$functions[$fn];
}
if (!method_exists($this, "function_$fn"))
{
$this->lexer->warn("Unknown function specified for map(): $f");
return 'false';
}
$fn = "function_$fn";
$fn = $this->$fn('$arg');
$args = func_get_args();
array_shift($args);
return "array_map(function(\$arg) { return $fn; }, self::merge_to_array(".implode(", ", $args)."))";
}
}
/**
* State object
*/
class VMXTemplateState
{
// Functions
var $functions = array();
}
/**
* Lexical analyzer (~regexp)
*/
class VMXTemplateLexer
{
var $options;
// Code (string) and current position inside it
var $code, $codelen, $pos, $lineno;
// Last directive start position, directive and substitution start/end counters
var $last_start, $last_start_line, $in_code, $in_subst, $force_literal = 0;
// Possible tokens consisting of special characters
static $chartokens = '+ - = * / % ! ? : , . < > ( ) { } [ ] & .. || && == != <= >= =>';
// Reserved keywords
static $keywords_str = 'OR XOR AND NOT IF ELSE ELSIF ELSEIF END SET FOR FOREACH FUNCTION BLOCK MACRO';
var $nchar, $lens, $keywords;
function __construct(VMXTemplateOptions $options)
{
$this->options = $options;
$this->nchar = array();
foreach (explode(' ', self::$chartokens) as $t)
{
$this->nchar[strlen($t)][$t] = true;
}
// Add code fragment finishing tokens
$this->nchar[strlen($this->options->end_code)][$this->options->end_code] = true;
if ($this->options->end_subst)
{
$this->nchar[strlen($this->options->end_subst)][$this->options->end_subst] = true;
}
// Reverse-sort lengths
$this->lens = array_keys($this->nchar);
rsort($this->lens);
$this->keywords = array_flip(explode(' ', self::$keywords_str));
}
function feed($parser)
{
try
{
$parser->reset();
$in = false;
while ($t = $this->read_token())
{
$success = $parser->eat($t[0], $t[1]);
if (!$success)
{
// Pass $in from last step so we skip to the beginning
// of directive even if it just ended and $this->in_* == 0
$this->skip_error(end($parser->parser->errors), $in);
}
$in = $this->in_code || $this->in_subst;
}
$parser->eat_eof();
}
catch (parse_error $e)
{
$this->options->error($e->getMessage());
}
}
function set_code($code)
{
$this->code = $code;
$this->codelen = strlen($this->code);
$this->pos = $this->lineno = 0;
}
function errorinfo()
{
$linestart = strrpos($this->code, "\n", $this->pos-$this->codelen-1) ?: -1;
$lineend = strpos($this->code, "\n", $this->pos) ?: $this->codelen;
$line = substr($this->code, $linestart+1, $this->pos-$linestart-1);
$line .= '^^^';
$line .= substr($this->code, $this->pos, $lineend-$this->pos);
return " in {$this->options->input_filename}, line ".($this->lineno+1).", byte {$this->pos}, marked by ^^^ in $line";
}
function warn($text)
{
$this->options->error($text.$this->errorinfo());
}
/**
* Skip a directive
*/
function skip_error($e, $force = false)
{
if (substr($e, 0, 18) !== 'error not expected')
{
$this->warn($e);
if ($this->in_code || $this->in_subst || $force)
{
$this->in_code = $this->in_subst = 0;
$this->pos = $this->last_start;
$this->lineno = $this->last_start_line;
$this->force_literal = 1;
}
}
}
/**
* Read next token from the stream
* Returns array($token, $value) or false for EOF
*/
function read_token()
{
if ($this->pos >= $this->codelen)
{
// End of code
return false;
}
if ($this->in_code <= 0 && $this->in_subst <= 0)
{
$was_code = true;
$code_pos = strpos($this->code, $this->options->begin_code, $this->pos+$this->force_literal);
$subst_pos = strpos($this->code, $this->options->begin_subst, $this->pos+$this->force_literal);
$this->force_literal = 0;
if ($code_pos === false && $subst_pos === false)
{
$r = array('literal', "'".addcslashes(substr($this->code, $this->pos), "'\\")."'");
$this->lineno += substr_count($r[1], "\n");
$this->pos = $this->codelen;
}
elseif ($subst_pos === false || $code_pos !== false && $subst_pos > $code_pos)
{
// Code starts closer
if ($code_pos > $this->pos)
{
// We didn't yet reach the code beginning
$str = substr($this->code, $this->pos, $code_pos-$this->pos);
$this->lineno += substr_count($str, "\n");
if ($this->options->eat_code_line)
{
$str = preg_replace('/\n[ \t]*$/s', $was_code ? '' : "\n", $str);
}
$r = array('literal', "'".addcslashes($str, "'\\")."'");
$this->pos = $code_pos;
}
elseif ($code_pos !== false)
{
// We are at the code beginning ($this->pos == $code_pos)
$i = $this->pos+strlen($this->options->begin_code);
while ($i < $this->codelen && (($c = $this->code{$i}) == ' ' || $c == "\t"))
{
$i++;
}
if ($i < $this->codelen && $this->code{$i} == '#')
{
// Strip comment
$i = strpos($this->code, $this->options->end_code, $i);
$this->pos = $i ? $i+strlen($this->options->end_code) : $this->codelen;
return $this->read_token();
}
$r = array('<!--', $this->options->begin_code);
$this->last_start = $this->pos;
$this->last_start_line = $this->lineno;
$this->pos += strlen($this->options->begin_code);
$this->in_code = 1;
}
$was_code = true;
}
else
{
// Substitution is closer
if ($subst_pos > $this->pos)
{
$r = array('literal', "'".addcslashes(substr($this->code, $this->pos, $subst_pos-$this->pos), "'\\")."'");
$this->lineno += substr_count($r[1], "\n");
$this->pos = $subst_pos;
}
else
{
$r = array('{{', $this->options->begin_subst);
$this->last_start = $this->pos;
$this->last_start_line = $this->lineno;
$this->pos++;
$this->in_subst = 1;
}
$was_code = false;
}
return $r;
}
while ($this->pos < $this->codelen)
{
// Skip whitespace
$t = $this->code{$this->pos};
if ($t == "\n")
$this->lineno++;
elseif ($t != "\t" && $t != ' ')
break;
$this->pos++;
}
if ($this->pos >= $this->codelen)
{
// End of code
return false;
}
if (preg_match('#[a-z_][a-z0-9_]*#Ais', $this->code, $m, 0, $this->pos))
{
$this->pos += strlen($m[0]);
if (isset($this->keywords[$l = strtoupper($m[0])]))
{
// Keyword
return array($l, $m[0]);
}
// Identifier
return array('name', $m[0]);
}
elseif (preg_match(
'/((\")(?:[^\"\\\\]+|\\\\.)*\"|\'(?:[^\'\\\\]+|\\\\.)*\''.
'|0\d+|\d+(\.\d+)?|0x\d+)/Ais', $this->code, $m, 0, $this->pos))
{
// String or numeric non-negative literal
$t = $m[1];
if (isset($m[2]))
{
$t = str_replace('$', '\\$', $t);
}
$this->pos += strlen($m[0]);
return array('literal', $t);
}
else
{
// Special characters
foreach ($this->lens as $l)
{
$a = $this->nchar[$l];
$t = substr($this->code, $this->pos, $l);
if (isset($a[$t]))
{
$this->pos += $l;
if ($this->in_code)
{
$this->in_code += ($t === $this->options->begin_code);
$this->in_code -= ($t === $this->options->end_code);
if (!$this->in_code)
{
return array('-->', $t);
}
}
elseif ($this->in_subst)
{
$this->in_subst += ($t === $this->options->begin_subst);
$this->in_subst -= ($t === $this->options->end_subst);
if (!$this->in_subst)
{
return array('}}', $t);
}
}
return array($t, false);
}
}
// Unknown character
$this->skip_error(
"Unexpected character '".$this->code{$this->pos}."'"
);
return array('error', false);
}
}
}
/**
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Library General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
*/
define('LIME_CALL_PROTOCOL', '$tokens, &$result');
abstract class lime_parser {
}
/**
* The input doesn't match the grammar
*/
class parse_error extends Exception {
}
/**
* Bug, I made a mistake
*/
class parse_bug extends Exception {
}
class parse_unexpected_token extends parse_error {
public function __construct($type, $state) {
parent::__construct("Unexpected token of type {$type}");
$this->type = $type;
$this->state = $state;
}
}
class parse_premature_eof extends parse_error {
public function __construct(array $expect) {
parent::__construct('Premature EOF');
}
}
class parse_stack {
public $q;
public $qs = array();
/**
* Stack of semantic actions
*/
public $ss = array();
public function __construct($qi) {
$this->q = $qi;
}
public function shift($q, $semantic) {
$this->ss[] = $semantic;
$this->qs[] = $this->q;
$this->q = $q;
// echo "Shift $q -- $semantic\n";
}
public function top_n($n) {
if (!$n) {
return array();
}
return array_slice($this->ss, 0 - $n);
}
public function pop_n($n) {
if (!$n) {
return array();
}
$qq = array_splice($this->qs, 0 - $n);
$this->q = $qq[0];
return array_splice($this->ss, 0 - $n);
}
public function occupied() {
return !empty($this->ss);
}
public function index($n) {
if ($n) {
$this->q = $this->qs[count($this->qs) - $n];
}
}
public function text() {
return $this->q . ' : ' . implode(' . ', array_reverse($this->qs));
}
}
class parse_engine {
public $debug = false;
public $parser;
public $qi;
public $rule;
public $step;
public $descr;
/**
* @var boolean
*/
public $accept;
/**
* @var parse_stack
*/
public $stack;
public function __construct($parser) {
$this->parser = $parser;
$this->qi = $parser->qi;
$this->rule = $parser->a;
$this->step = $parser->i;
$this->descr = $parser->d;
$this->reset();
}
public function reset() {
$this->accept = false;
$this->stack = new parse_stack($this->qi);
$this->parser->errors = array();
}
private function enter_error_tolerant_state() {
while ($this->stack->occupied()) {
if ($this->has_step_for('error')) {
return true;
}
if ($this->debug) echo "Dropped an item from the stack, {" . implode(', ', $this->get_steps()) . "} left\n";
if ($this->debug) echo 'Currently in state ' . $this->state() . "\n";
$this->drop();
}
return false;
}
private function drop() {
$this->stack->pop_n(1);
}
/*
* So that I don't get any brilliant misguided ideas:
*
* The "accept" step happens when we try to eat a start symbol.
* That happens because the reductions up the stack at the end
* finally (and symetrically) tell the parser to eat a symbol
* representing what they've just shifted off the end of the stack
* and reduced. However, that doesn't put the parser into any
* special different state. Therefore, it's back at the start
* state.
*
* That being said, the parser is ready to reduce an EOF to the
* empty program, if given a grammar that allows them.
*
* So anyway, if you literally tell the parser to eat an EOF
* symbol, then after it's done reducing and accepting the prior
* program, it's going to think it has another symbol to deal with.
* That is the EOF symbol, which means to reduce the empty program,
* accept it, and then continue trying to eat the terminal EOF.
*
* This infinte loop quickly runs out of memory.
*
* That's why the real EOF algorithm doesn't try to pretend that
* EOF is a terminal. Like the invented start symbol, it's special.
*
* Instead, we pretend to want to eat EOF, but never actually
* try to get it into the parse stack. (It won't fit.) In short,
* we look up what reduction is indicated at each step in the
* process of rolling up the parse stack.
*
* The repetition is because one reduction is not guaranteed to
* cascade into another and clean up the entire parse stack.
* Rather, it will instead shift each partial production as it
* is forced to completion by the EOF lookahead.
*/
public function eat_eof() {
// We must reduce as if having read the EOF symbol
do {
// and we have to try at least once, because if nothing
// has ever been shifted, then the stack will be empty
// at the start.
list($opcode, $operand) = $this->step_for('#');
switch ($opcode) {
case 'r':
$this->reduce($operand);
break;
case 'e':
$this->premature_eof();
break;
default:
throw new parse_bug();
}
} while ($this->stack->occupied());
// If the sentence is well-formed according to the grammar, then
// this will eventually result in eating a start symbol, which
// causes the "accept" instruction to fire. Otherwise, the
// step('#') method will indicate an error in the syntax, which
// here means a premature EOF.
//
// Incidentally, some tremendous amount of voodoo with the parse
// stack might help find the beginning of some unfinished
// production that the sentence was cut off during, but as a
// general rule that would require deeper knowledge.
if (!$this->accept) {
throw new parse_bug();
}
return $this->semantic;
}
private function premature_eof() {
$seen = array();
$expect = $this->get_steps();
while ($this->enter_error_tolerant_state() || $this->has_step_for('error')) {
if (isset($seen[$this->state()])) {
// This means that it's pointless to try here.
// We're guaranteed that the stack is occupied.
$this->drop();
continue;
}
$seen[$this->state()] = true;
$this->eat('error', 'Premature EOF');
if ($this->has_step_for('#')) {
// Good. We can continue as normal.
return;
} else {
// That attempt to resolve the error condition
// did not work. There's no point trying to
// figure out how much to slice off the stack.
// The rest of the algorithm will make it happen.
}
}
throw new parse_premature_eof($expect);
}
private function current_row() {
return $this->step[$this->state()];
}
private function step_for($type) {
$row = $this->current_row();
if (isset($row[$type])) {
return explode(' ', $row[$type]);
}
if (isset($row[''])) {
return explode(' ', $row['']);
}
return array('e', $this->stack->q);
}
private function get_steps() {
$out = array();
foreach($this->current_row() as $type => $row) {
foreach($this->rule as $rule) {
if ($rule['symbol'] == $type) {
continue 2;
}
}
list($opcode) = explode(' ', $row, 2);
if ($opcode != 'e') {
$out[] = $type === '' ? '$default' : $type;
}
}
return $out;
}
private function has_step_for($type) {
$row = $this->current_row();
return isset($row[$type]);
}
private function state() {
return $this->stack->q;
}
function eat($type, $semantic) {
// assert('$type == trim($type)');
if ($this->debug) echo "Trying to eat a ($type)\n";
list($opcode, $operand) = $this->step_for($type);
switch ($opcode) {
case 's':
if ($this->debug) echo "shift $type to state $operand\n";
$this->stack->shift($operand, $semantic);
// echo $this->stack->text()." shift $type<br/>\n";
break;
case 'r':
if ($this->debug) echo "Reducing $type via rule $operand\n";
$this->reduce($operand);
// Yes, this is tail-recursive. It's also the simplest way.
return $this->eat($type, $semantic);
case 'a':
if ($this->stack->occupied()) {
throw new parse_bug('Accept should happen with empty stack.');
}
$this->accept = true;
if ($this->debug) echo ("Accept\n\n");
$this->semantic = $semantic;
break;
case 'e':
// This is thought to be the uncommon, exceptional path, so
// it's OK that this algorithm will cause the stack to
// flutter while the parse engine waits for an edible token.
if ($this->debug) echo "($type) causes a problem.\n";
// get these before doing anything
$expected = $this->get_steps();
$this->parser->errors[] = $this->descr($type, $semantic) . ' not expected, expected one of ' . implode(', ', $expected);
if ($this->debug) echo "Possibilities before error fixing: {" . implode(', ', $expected) . "}\n";
if ($this->enter_error_tolerant_state() || $this->has_step_for('error')) {
$this->eat('error', end($this->parser->errors));
if ($this->has_step_for($type)) {
$this->eat($type, $semantic);
}
return false;
} else {
// If that didn't work, give up:
throw new parse_error('Parse Error: ' . $this->descr($type, $semantic) . ' not expected, expected one of ' . implode(', ', $expected));
}
break;
default:
throw new parse_bug("Bad parse table instruction " . htmlspecialchars($opcode));
}
return true;
}
private function descr($type, $semantic) {
if (isset($this->descr[$type])) {
return $this->descr[$type];
} elseif ("$semantic" !== "") {
return $type . ' (' . $semantic . ')';
} else {
return $type;
}
}
private function reduce($rule_id) {
$rule = $this->rule[$rule_id];
$len = $rule['len'];
$semantic = $this->perform_action($rule_id, $this->stack->top_n($len));
//echo $semantic.br();
if ($rule['replace']) {
$this->stack->pop_n($len);
} else {
$this->stack->index($len);
}
$this->eat($rule['symbol'], $semantic);
}
private function perform_action($rule_id, $slice) {
// we have this weird calling convention....
$result = null;
$method = $this->parser->method[$rule_id];
//if ($this->debug) echo "rule $id: $method\n";
$this->parser->$method($slice, $result);
return $result;
}
}
/*
*** DON'T EDIT THIS FILE! ***
*
* This file was automatically generated by the Lime parser generator.
* The real source code you should be looking at is in one or more
* grammar files in the Lime format.
*
* THE ONLY REASON TO LOOK AT THIS FILE is to see where in the grammar
* file that your error happened, because there are enough comments to
* help you debug your grammar.
* If you ignore this warning, you're shooting yourself in the brain,
* not the foot.
*/
class VMXTemplateParser extends lime_parser {
public $qi = 0;
public $i = array(
array(
'chunks' => 's 1',
'template' => 's 191',
"'start'" => "a 'start'",
'literal' => 'r 1',
'<!--' => 'r 1',
'{{' => 'r 1',
'#' => 'r 1'
),
array(
'chunk' => 's 2',
'literal' => 's 3',
'<!--' => 's 4',
'{{' => 's 165',
'error' => 's 168',
'#' => 'r 0'
),
array(
'' => 'r 2'
),
array(
'' => 'r 3'
),
array(
'code_chunk' => 's 5',
'c_if' => 's 7',
'c_set' => 's 8',
'c_fn' => 's 9',
'c_for' => 's 10',
'exp' => 's 11',
'IF' => 's 122',
'SET' => 's 133',
'fn' => 's 141',
'for' => 's 152',
'FUNCTION' => 's 160',
'BLOCK' => 's 161',
'MACRO' => 's 162',
'FOR' => 's 163',
'FOREACH' => 's 164',
'p10' => 's 52',
'p11' => 's 53',
'-' => 's 54',
'nonbrace' => 's 56',
'(' => 's 57',
'!' => 's 66',
'NOT' => 's 68',
'{' => 's 70',
'literal' => 's 74',
'varref' => 's 75',
'name' => 's 80'
),
array(
'-->' => 's 6'
),
array(
'' => 'r 4'
),
array(
'' => 'r 7'
),
array(
'' => 'r 8'
),
array(
'' => 'r 9'
),
array(
'' => 'r 10'
),
array(
'..' => 's 12',
'||' => 's 14',
'OR' => 's 16',
'XOR' => 's 18',
'&&' => 's 20',
'AND' => 's 22',
'?' => 's 24',
'==' => 's 28',
'!=' => 's 30',
'<' => 's 32',
'>' => 's 34',
'<=' => 's 36',
'>=' => 's 38',
'+' => 's 40',
'-' => 's 42',
'&' => 's 44',
'*' => 's 46',
'/' => 's 48',
'%' => 's 50',
'-->' => 'r 11'
),
array(
'exp' => 's 13',
'p10' => 's 52',
'p11' => 's 53',
'-' => 's 54',
'nonbrace' => 's 56',
'(' => 's 57',
'!' => 's 66',
'NOT' => 's 68',
'{' => 's 70',
'literal' => 's 74',
'varref' => 's 75',
'name' => 's 80'
),
array(
'..' => 'r 31',
'||' => 's 14',
'OR' => 's 16',
'XOR' => 's 18',
'&&' => 's 20',
'AND' => 's 22',
'?' => 's 24',
'==' => 's 28',
'!=' => 's 30',
'<' => 's 32',
'>' => 's 34',
'<=' => 's 36',
'>=' => 's 38',
'+' => 's 40',
'-' => 's 42',
'&' => 's 44',
'*' => 's 46',
'/' => 's 48',
'%' => 's 50',
'-->' => 'r 31',
':' => 'r 31',
')' => 'r 31',
',' => 'r 31',
'=>' => 'r 31',
']' => 'r 31',
'}}' => 'r 31',
'}' => 'r 31'
),
array(
'exp' => 's 15',
'p10' => 's 52',
'p11' => 's 53',
'-' => 's 54',
'nonbrace' => 's 56',
'(' => 's 57',
'!' => 's 66',
'NOT' => 's 68',
'{' => 's 70',
'literal' => 's 74',
'varref' => 's 75',
'name' => 's 80'
),
array(
'..' => 'r 32',
'||' => 'r 32',
'OR' => 'r 32',
'XOR' => 'r 32',
'&&' => 's 20',
'AND' => 's 22',
'?' => 'r 32',
'==' => 's 28',
'!=' => 's 30',
'<' => 's 32',
'>' => 's 34',
'<=' => 's 36',
'>=' => 's 38',
'+' => 's 40',
'-' => 's 42',
'&' => 's 44',
'*' => 's 46',
'/' => 's 48',
'%' => 's 50',
'-->' => 'r 32',
':' => 'r 32',
')' => 'r 32',
',' => 'r 32',
'=>' => 'r 32',
']' => 'r 32',
'}}' => 'r 32',
'}' => 'r 32'
),
array(
'exp' => 's 17',
'p10' => 's 52',
'p11' => 's 53',
'-' => 's 54',
'nonbrace' => 's 56',
'(' => 's 57',
'!' => 's 66',
'NOT' => 's 68',
'{' => 's 70',
'literal' => 's 74',
'varref' => 's 75',
'name' => 's 80'
),
array(
'..' => 'r 33',
'||' => 'r 33',
'OR' => 'r 33',
'XOR' => 'r 33',
'&&' => 's 20',
'AND' => 's 22',
'?' => 'r 33',
'==' => 's 28',
'!=' => 's 30',
'<' => 's 32',
'>' => 's 34',
'<=' => 's 36',
'>=' => 's 38',
'+' => 's 40',
'-' => 's 42',
'&' => 's 44',
'*' => 's 46',
'/' => 's 48',
'%' => 's 50',
'-->' => 'r 33',
':' => 'r 33',
')' => 'r 33',
',' => 'r 33',
'=>' => 'r 33',
']' => 'r 33',
'}}' => 'r 33',
'}' => 'r 33'
),
array(
'exp' => 's 19',
'p10' => 's 52',
'p11' => 's 53',
'-' => 's 54',
'nonbrace' => 's 56',
'(' => 's 57',
'!' => 's 66',
'NOT' => 's 68',
'{' => 's 70',
'literal' => 's 74',
'varref' => 's 75',
'name' => 's 80'
),
array(
'..' => 'r 34',
'||' => 'r 34',
'OR' => 'r 34',
'XOR' => 'r 34',
'&&' => 's 20',
'AND' => 's 22',
'?' => 'r 34',
'==' => 's 28',
'!=' => 's 30',
'<' => 's 32',
'>' => 's 34',
'<=' => 's 36',
'>=' => 's 38',
'+' => 's 40',
'-' => 's 42',
'&' => 's 44',
'*' => 's 46',
'/' => 's 48',
'%' => 's 50',
'-->' => 'r 34',
':' => 'r 34',
')' => 'r 34',
',' => 'r 34',
'=>' => 'r 34',
']' => 'r 34',
'}}' => 'r 34',
'}' => 'r 34'
),
array(
'exp' => 's 21',
'p10' => 's 52',
'p11' => 's 53',
'-' => 's 54',
'nonbrace' => 's 56',
'(' => 's 57',
'!' => 's 66',
'NOT' => 's 68',
'{' => 's 70',
'literal' => 's 74',
'varref' => 's 75',
'name' => 's 80'
),
array(
'..' => 'r 35',
'||' => 'r 35',
'OR' => 'r 35',
'XOR' => 'r 35',
'&&' => 'r 35',
'AND' => 'r 35',
'?' => 'r 35',
'==' => 's 28',
'!=' => 's 30',
'<' => 's 32',
'>' => 's 34',
'<=' => 's 36',
'>=' => 's 38',
'+' => 's 40',
'-' => 's 42',
'&' => 's 44',
'*' => 's 46',
'/' => 's 48',
'%' => 's 50',
'-->' => 'r 35',
':' => 'r 35',
')' => 'r 35',
',' => 'r 35',
'=>' => 'r 35',
']' => 'r 35',
'}}' => 'r 35',
'}' => 'r 35'
),
array(
'exp' => 's 23',
'p10' => 's 52',
'p11' => 's 53',
'-' => 's 54',
'nonbrace' => 's 56',
'(' => 's 57',
'!' => 's 66',
'NOT' => 's 68',
'{' => 's 70',
'literal' => 's 74',
'varref' => 's 75',
'name' => 's 80'
),
array(
'..' => 'r 36',
'||' => 'r 36',
'OR' => 'r 36',
'XOR' => 'r 36',
'&&' => 'r 36',
'AND' => 'r 36',
'?' => 'r 36',
'==' => 's 28',
'!=' => 's 30',
'<' => 's 32',
'>' => 's 34',
'<=' => 's 36',
'>=' => 's 38',
'+' => 's 40',
'-' => 's 42',
'&' => 's 44',
'*' => 's 46',
'/' => 's 48',
'%' => 's 50',
'-->' => 'r 36',
':' => 'r 36',
')' => 'r 36',
',' => 'r 36',
'=>' => 'r 36',
']' => 'r 36',
'}}' => 'r 36',
'}' => 'r 36'
),
array(
'exp' => 's 25',
'p10' => 's 52',
'p11' => 's 53',
'-' => 's 54',
'nonbrace' => 's 56',
'(' => 's 57',
'!' => 's 66',
'NOT' => 's 68',
'{' => 's 70',
'literal' => 's 74',
'varref' => 's 75',
'name' => 's 80'
),
array(
'..' => 's 12',
'||' => 's 14',
'OR' => 's 16',
'XOR' => 's 18',
'&&' => 's 20',
'AND' => 's 22',
'?' => 's 24',
':' => 's 26',
'==' => 's 28',
'!=' => 's 30',
'<' => 's 32',
'>' => 's 34',
'<=' => 's 36',
'>=' => 's 38',
'+' => 's 40',
'-' => 's 42',
'&' => 's 44',
'*' => 's 46',
'/' => 's 48',
'%' => 's 50'
),
array(
'exp' => 's 27',
'p10' => 's 52',
'p11' => 's 53',
'-' => 's 54',
'nonbrace' => 's 56',
'(' => 's 57',
'!' => 's 66',
'NOT' => 's 68',
'{' => 's 70',
'literal' => 's 74',
'varref' => 's 75',
'name' => 's 80'
),
array(
'..' => 'r 37',
'||' => 's 14',
'OR' => 's 16',
'XOR' => 's 18',
'&&' => 's 20',
'AND' => 's 22',
'==' => 's 28',
'!=' => 's 30',
'<' => 's 32',
'>' => 's 34',
'<=' => 's 36',
'>=' => 's 38',
'+' => 's 40',
'-' => 's 42',
'&' => 's 44',
'*' => 's 46',
'/' => 's 48',
'%' => 's 50',
'-->' => 'r 37',
':' => 'r 37',
')' => 'r 37',
',' => 'r 37',
'=>' => 'r 37',
']' => 'r 37',
'}}' => 'r 37',
'}' => 'r 37'
),
array(
'exp' => 's 29',
'p10' => 's 52',
'p11' => 's 53',
'-' => 's 54',
'nonbrace' => 's 56',
'(' => 's 57',
'!' => 's 66',
'NOT' => 's 68',
'{' => 's 70',
'literal' => 's 74',
'varref' => 's 75',
'name' => 's 80'
),
array(
'..' => 'r 38',
'||' => 'r 38',
'OR' => 'r 38',
'XOR' => 'r 38',
'&&' => 'r 38',
'AND' => 'r 38',
'?' => 'r 38',
'+' => 's 40',
'-' => 's 42',
'&' => 's 44',
'*' => 's 46',
'/' => 's 48',
'%' => 's 50',
'-->' => 'r 38',
':' => 'r 38',
')' => 'r 38',
',' => 'r 38',
'=>' => 'r 38',
']' => 'r 38',
'}}' => 'r 38',
'}' => 'r 38'
),
array(
'exp' => 's 31',
'p10' => 's 52',
'p11' => 's 53',
'-' => 's 54',
'nonbrace' => 's 56',
'(' => 's 57',
'!' => 's 66',
'NOT' => 's 68',
'{' => 's 70',
'literal' => 's 74',
'varref' => 's 75',
'name' => 's 80'
),
array(
'..' => 'r 39',
'||' => 'r 39',
'OR' => 'r 39',
'XOR' => 'r 39',
'&&' => 'r 39',
'AND' => 'r 39',
'?' => 'r 39',
'+' => 's 40',
'-' => 's 42',
'&' => 's 44',
'*' => 's 46',
'/' => 's 48',
'%' => 's 50',
'-->' => 'r 39',
':' => 'r 39',
')' => 'r 39',
',' => 'r 39',
'=>' => 'r 39',
']' => 'r 39',
'}}' => 'r 39',
'}' => 'r 39'
),
array(
'exp' => 's 33',
'p10' => 's 52',
'p11' => 's 53',
'-' => 's 54',
'nonbrace' => 's 56',
'(' => 's 57',
'!' => 's 66',
'NOT' => 's 68',
'{' => 's 70',
'literal' => 's 74',
'varref' => 's 75',
'name' => 's 80'
),
array(
'..' => 'r 40',
'||' => 'r 40',
'OR' => 'r 40',
'XOR' => 'r 40',
'&&' => 'r 40',
'AND' => 'r 40',
'?' => 'r 40',
'+' => 's 40',
'-' => 's 42',
'&' => 's 44',
'*' => 's 46',
'/' => 's 48',
'%' => 's 50',
'-->' => 'r 40',
':' => 'r 40',
')' => 'r 40',
',' => 'r 40',
'=>' => 'r 40',
']' => 'r 40',
'}}' => 'r 40',
'}' => 'r 40'
),
array(
'exp' => 's 35',
'p10' => 's 52',
'p11' => 's 53',
'-' => 's 54',
'nonbrace' => 's 56',
'(' => 's 57',
'!' => 's 66',
'NOT' => 's 68',
'{' => 's 70',
'literal' => 's 74',
'varref' => 's 75',
'name' => 's 80'
),
array(
'..' => 'r 41',
'||' => 'r 41',
'OR' => 'r 41',
'XOR' => 'r 41',
'&&' => 'r 41',
'AND' => 'r 41',
'?' => 'r 41',
'+' => 's 40',
'-' => 's 42',
'&' => 's 44',
'*' => 's 46',
'/' => 's 48',
'%' => 's 50',
'-->' => 'r 41',
':' => 'r 41',
')' => 'r 41',
',' => 'r 41',
'=>' => 'r 41',
']' => 'r 41',
'}}' => 'r 41',
'}' => 'r 41'
),
array(
'exp' => 's 37',
'p10' => 's 52',
'p11' => 's 53',
'-' => 's 54',
'nonbrace' => 's 56',
'(' => 's 57',
'!' => 's 66',
'NOT' => 's 68',
'{' => 's 70',
'literal' => 's 74',
'varref' => 's 75',
'name' => 's 80'
),
array(
'..' => 'r 42',
'||' => 'r 42',
'OR' => 'r 42',
'XOR' => 'r 42',
'&&' => 'r 42',
'AND' => 'r 42',
'?' => 'r 42',
'+' => 's 40',
'-' => 's 42',
'&' => 's 44',
'*' => 's 46',
'/' => 's 48',
'%' => 's 50',
'-->' => 'r 42',
':' => 'r 42',
')' => 'r 42',
',' => 'r 42',
'=>' => 'r 42',
']' => 'r 42',
'}}' => 'r 42',
'}' => 'r 42'
),
array(
'exp' => 's 39',
'p10' => 's 52',
'p11' => 's 53',
'-' => 's 54',
'nonbrace' => 's 56',
'(' => 's 57',
'!' => 's 66',
'NOT' => 's 68',
'{' => 's 70',
'literal' => 's 74',
'varref' => 's 75',
'name' => 's 80'
),
array(
'..' => 'r 43',
'||' => 'r 43',
'OR' => 'r 43',
'XOR' => 'r 43',
'&&' => 'r 43',
'AND' => 'r 43',
'?' => 'r 43',
'+' => 's 40',
'-' => 's 42',
'&' => 's 44',
'*' => 's 46',
'/' => 's 48',
'%' => 's 50',
'-->' => 'r 43',
':' => 'r 43',
')' => 'r 43',
',' => 'r 43',
'=>' => 'r 43',
']' => 'r 43',
'}}' => 'r 43',
'}' => 'r 43'
),
array(
'exp' => 's 41',
'p10' => 's 52',
'p11' => 's 53',
'-' => 's 54',
'nonbrace' => 's 56',
'(' => 's 57',
'!' => 's 66',
'NOT' => 's 68',
'{' => 's 70',
'literal' => 's 74',
'varref' => 's 75',
'name' => 's 80'
),
array(
'..' => 'r 44',
'||' => 'r 44',
'OR' => 'r 44',
'XOR' => 'r 44',
'&&' => 'r 44',
'AND' => 'r 44',
'?' => 'r 44',
'==' => 'r 44',
'!=' => 'r 44',
'<' => 'r 44',
'>' => 'r 44',
'<=' => 'r 44',
'>=' => 'r 44',
'+' => 'r 44',
'-' => 'r 44',
'&' => 's 44',
'*' => 's 46',
'/' => 's 48',
'%' => 's 50',
'-->' => 'r 44',
':' => 'r 44',
')' => 'r 44',
',' => 'r 44',
'=>' => 'r 44',
']' => 'r 44',
'}}' => 'r 44',
'}' => 'r 44'
),
array(
'exp' => 's 43',
'p10' => 's 52',
'p11' => 's 53',
'-' => 's 54',
'nonbrace' => 's 56',
'(' => 's 57',
'!' => 's 66',
'NOT' => 's 68',
'{' => 's 70',
'literal' => 's 74',
'varref' => 's 75',
'name' => 's 80'
),
array(
'..' => 'r 45',
'||' => 'r 45',
'OR' => 'r 45',
'XOR' => 'r 45',
'&&' => 'r 45',
'AND' => 'r 45',
'?' => 'r 45',
'==' => 'r 45',
'!=' => 'r 45',
'<' => 'r 45',
'>' => 'r 45',
'<=' => 'r 45',
'>=' => 'r 45',
'+' => 'r 45',
'-' => 'r 45',
'&' => 's 44',
'*' => 's 46',
'/' => 's 48',
'%' => 's 50',
'-->' => 'r 45',
':' => 'r 45',
')' => 'r 45',
',' => 'r 45',
'=>' => 'r 45',
']' => 'r 45',
'}}' => 'r 45',
'}' => 'r 45'
),
array(
'exp' => 's 45',
'p10' => 's 52',
'p11' => 's 53',
'-' => 's 54',
'nonbrace' => 's 56',
'(' => 's 57',
'!' => 's 66',
'NOT' => 's 68',
'{' => 's 70',
'literal' => 's 74',
'varref' => 's 75',
'name' => 's 80'
),
array(
'..' => 'r 46',
'||' => 'r 46',
'OR' => 'r 46',
'XOR' => 'r 46',
'&&' => 'r 46',
'AND' => 'r 46',
'?' => 'r 46',
'==' => 'r 46',
'!=' => 'r 46',
'<' => 'r 46',
'>' => 'r 46',
'<=' => 'r 46',
'>=' => 'r 46',
'+' => 'r 46',
'-' => 'r 46',
'&' => 'r 46',
'*' => 's 46',
'/' => 's 48',
'%' => 's 50',
'-->' => 'r 46',
':' => 'r 46',
')' => 'r 46',
',' => 'r 46',
'=>' => 'r 46',
']' => 'r 46',
'}}' => 'r 46',
'}' => 'r 46'
),
array(
'exp' => 's 47',
'p10' => 's 52',
'p11' => 's 53',
'-' => 's 54',
'nonbrace' => 's 56',
'(' => 's 57',
'!' => 's 66',
'NOT' => 's 68',
'{' => 's 70',
'literal' => 's 74',
'varref' => 's 75',
'name' => 's 80'
),
array(
'' => 'r 47'
),
array(
'exp' => 's 49',
'p10' => 's 52',
'p11' => 's 53',
'-' => 's 54',
'nonbrace' => 's 56',
'(' => 's 57',
'!' => 's 66',
'NOT' => 's 68',
'{' => 's 70',
'literal' => 's 74',
'varref' => 's 75',
'name' => 's 80'
),
array(
'' => 'r 48'
),
array(
'exp' => 's 51',
'p10' => 's 52',
'p11' => 's 53',
'-' => 's 54',
'nonbrace' => 's 56',
'(' => 's 57',
'!' => 's 66',
'NOT' => 's 68',
'{' => 's 70',
'literal' => 's 74',
'varref' => 's 75',
'name' => 's 80'
),
array(
'' => 'r 49'
),
array(
'' => 'r 50'
),
array(
'' => 'r 51'
),
array(
'p11' => 's 55',
'nonbrace' => 's 56',
'(' => 's 57',
'!' => 's 66',
'NOT' => 's 68',
'{' => 's 70',
'literal' => 's 74',
'varref' => 's 75',
'name' => 's 80'
),
array(
'' => 'r 52'
),
array(
'' => 'r 53'
),
array(
'exp' => 's 58',
'p10' => 's 52',
'p11' => 's 53',
'-' => 's 54',
'nonbrace' => 's 56',
'(' => 's 57',
'!' => 's 66',
'NOT' => 's 68',
'{' => 's 70',
'literal' => 's 74',
'varref' => 's 75',
'name' => 's 80'
),
array(
'..' => 's 12',
'||' => 's 14',
'OR' => 's 16',
'XOR' => 's 18',
'&&' => 's 20',
'AND' => 's 22',
'?' => 's 24',
'==' => 's 28',
'!=' => 's 30',
'<' => 's 32',
'>' => 's 34',
'<=' => 's 36',
'>=' => 's 38',
'+' => 's 40',
'-' => 's 42',
'&' => 's 44',
'*' => 's 46',
'/' => 's 48',
'%' => 's 50',
')' => 's 59'
),
array(
'varpath' => 's 60',
'.' => 'r 83',
'[' => 'r 83',
'%' => 'r 83',
'/' => 'r 83',
'*' => 'r 83',
'&' => 'r 83',
'-' => 'r 83',
'+' => 'r 83',
'>=' => 'r 83',
'<=' => 'r 83',
'>' => 'r 83',
'<' => 'r 83',
'!=' => 'r 83',
'==' => 'r 83',
'?' => 'r 83',
'AND' => 'r 83',
'&&' => 'r 83',
'XOR' => 'r 83',
'OR' => 'r 83',
'||' => 'r 83',
'..' => 'r 83',
'-->' => 'r 83',
':' => 'r 83',
')' => 'r 83',
',' => 'r 83',
'=>' => 'r 83',
']' => 'r 83',
'}}' => 'r 83',
'}' => 'r 83'
),
array(
'.' => 's 61',
'[' => 's 77',
'varpart' => 's 121',
'%' => 'r 54',
'/' => 'r 54',
'*' => 'r 54',
'&' => 'r 54',
'-' => 'r 54',
'+' => 'r 54',
'>=' => 'r 54',
'<=' => 'r 54',
'>' => 'r 54',
'<' => 'r 54',
'!=' => 'r 54',
'==' => 'r 54',
'?' => 'r 54',
'AND' => 'r 54',
'&&' => 'r 54',
'XOR' => 'r 54',
'OR' => 'r 54',
'||' => 'r 54',
'..' => 'r 54',
'-->' => 'r 54',
':' => 'r 54',
')' => 'r 54',
',' => 'r 54',
'=>' => 'r 54',
']' => 'r 54',
'}}' => 'r 54',
'}' => 'r 54'
),
array(
'namekw' => 's 62',
'name' => 's 105',
'IF' => 's 106',
'END' => 's 107',
'ELSE' => 's 108',
'ELSIF' => 's 109',
'ELSEIF' => 's 110',
'SET' => 's 111',
'OR' => 's 112',
'XOR' => 's 113',
'AND' => 's 114',
'NOT' => 's 115',
'FUNCTION' => 's 116',
'BLOCK' => 's 117',
'MACRO' => 's 118',
'FOR' => 's 119',
'FOREACH' => 's 120'
),
array(
'(' => 's 63',
'.' => 'r 79',
'[' => 'r 79',
'%' => 'r 79',
'/' => 'r 79',
'*' => 'r 79',
'&' => 'r 79',
'-' => 'r 79',
'+' => 'r 79',
'>=' => 'r 79',
'<=' => 'r 79',
'>' => 'r 79',
'<' => 'r 79',
'!=' => 'r 79',
'==' => 'r 79',
'?' => 'r 79',
'AND' => 'r 79',
'&&' => 'r 79',
'XOR' => 'r 79',
'OR' => 'r 79',
'||' => 'r 79',
'..' => 'r 79',
'-->' => 'r 79',
':' => 'r 79',
')' => 'r 79',
',' => 'r 79',
'=>' => 'r 79',
'=' => 'r 79',
']' => 'r 79',
'}}' => 'r 79',
'}' => 'r 79'
),
array(
'exp' => 's 64',
'p10' => 's 52',
'p11' => 's 53',
'-' => 's 54',
'nonbrace' => 's 56',
'(' => 's 57',
'!' => 's 66',
'NOT' => 's 68',
'{' => 's 70',
'literal' => 's 74',
'varref' => 's 75',
'name' => 's 80',
')' => 's 102',
'list' => 's 103'
),
array(
'..' => 's 12',
'||' => 's 14',
'OR' => 's 16',
'XOR' => 's 18',
'&&' => 's 20',
'AND' => 's 22',
'?' => 's 24',
'==' => 's 28',
'!=' => 's 30',
'<' => 's 32',
'>' => 's 34',
'<=' => 's 36',
'>=' => 's 38',
'+' => 's 40',
'-' => 's 42',
'&' => 's 44',
'*' => 's 46',
'/' => 's 48',
'%' => 's 50',
',' => 's 65',
')' => 'r 64'
),
array(
'exp' => 's 64',
'p10' => 's 52',
'p11' => 's 53',
'-' => 's 54',
'nonbrace' => 's 56',
'(' => 's 57',
'!' => 's 66',
'NOT' => 's 68',
'{' => 's 70',
'literal' => 's 74',
'varref' => 's 75',
'name' => 's 80',
'list' => 's 101'
),
array(
'nonbrace' => 's 56',
'(' => 's 57',
'!' => 's 66',
'p11' => 's 67',
'NOT' => 's 68',
'{' => 's 70',
'literal' => 's 74',
'varref' => 's 75',
'name' => 's 80'
),
array(
'' => 'r 55'
),
array(
'nonbrace' => 's 56',
'(' => 's 57',
'!' => 's 66',
'NOT' => 's 68',
'p11' => 's 69',
'{' => 's 70',
'literal' => 's 74',
'varref' => 's 75',
'name' => 's 80'
),
array(
'' => 'r 56'
),
array(
'exp' => 's 71',
'p10' => 's 52',
'p11' => 's 53',
'-' => 's 54',
'nonbrace' => 's 56',
'(' => 's 57',
'!' => 's 66',
'NOT' => 's 68',
'{' => 's 70',
'hash' => 's 95',
'literal' => 's 74',
'varref' => 's 75',
'name' => 's 80',
'pair' => 's 97',
'gtpair' => 's 100',
'}' => 'r 71'
),
array(
'..' => 's 12',
'||' => 's 14',
'OR' => 's 16',
'XOR' => 's 18',
'&&' => 's 20',
'AND' => 's 22',
'?' => &#