VMXTemplate/template.lime

356 lines
11 KiB
Plaintext
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.

# Контекстно-свободная LIME-грамматика шаблонизатора
#
# Для корректной работы нужен патченый LIME со следующими изменениями:
# (*) Подменой лексемы 'lit' на 'str' в метаграмматике.
# Это нужно, чтобы можно было юзать строковые лексемы типа '<!--'.
# (*) Для корректной обработки ошибок нужно, чтобы метод eat() возвращал
# false при ошибке и true при успехе. Т.к. подразумевается, что лексический
# анализатор зависим от работы синтаксического, знает о его состоянии и
# соответственно выдаёт либо лексемы "внутри" блоков кода, либо литералы
# "вне" оных.
# Взять таковой можно здесь: https://github.com/vitalif/lime
# Компилить так: php -d xdebug.max_nesting_level=200 lime.php template.lime > template.class
#
# {{ двойные скобки }} нужно исключительно чтобы маркеры начала и конца подстановки
# были уникальны в грамматике. Вместо них обычно используются { одинарные }, а
# выбор корректной лексемы - скобки или маркера - делает лексический анализатор.
# Но зато вместо { фигурных скобок } можно выбрать себе любые другие маркеры!
#
# Все выражения представляются массивом из двух значений: [ код выражения, флаг экранирования ]
# Флаг экранирования == true, если это выражение HTML-безопасно. При включённом auto_escape
# небезопасные выражения прогоняются через экранирование.
#
# Кстати:
# * Олдстайл BEGIN .. END ликвидирован
# * Возможно, нужно добавить в каком-то виде foreach ... as key => value
#
# PHP старее 5.4 не поддерживается из-за следующих причин:
# * используется $a ?: $b в выражении {a || b}
# * используется короткий синтаксис массивов [ $a, $b ]
# * используется синтаксис ($array_expression)[$key]
%class VMXTemplateParser
%start template
%token literal
%token incorrect
%token name
%token comment
%token ".." "concatenation operator '..'"
%token "||" "OR operator '||'"
%token "OR" "OR operator 'OR'"
%token "XOR" "XOR operator 'XOR'"
%token "AND" "AND operator 'AND'"
%token "&&" "AND operator '&&'"
%token "&" "bitwise AND operator '&'"
%token "==" "equality operator '=='"
%token "!=" "non-equality operator '!='"
%token "<" "less than operator '<'"
%token ">" "greater than operator '>'"
%token "<=" "less or equal operator '<='"
%token ">=" "greater or equal operator '>='"
%token "?" "ternary operator '? :'"
%token ":" "ternary operator '? :'"
%token "+" "plus operator '+'"
%token "-" "minus operator '-'"
%token "*" "multiply operator '*'"
%token "/" "divide operator '/'"
%token "%" "mod operator '%'"
%token "(" "left round brace '('"
%token ")" "right round brace '('"
%token "!" "NOT operator '!'"
%token "NOT" "NOT operator 'NOT'"
%token "{" "left curly brace '{'"
%token "}" "right curly brace '}'"
%token "," "comma ','"
%token "=>" "hash item operator '=>'"
%token "[" "left square brace '['"
%token "]" "right square brace ']'"
%token "<!--" "directive begin"
%token "-->" "directive end"
%token "{{" "substitution begin"
%token "}}" "substitution end"
%left ".."
%nonassoc "?" ":"
%left "||" "OR" "XOR"
%left "&&" "AND"
%nonassoc "==" "!=" "<" ">" "<=" ">="
%left "+" "-"
%left "&"
%left "*" "/" "%"
# Директивы
template = chunks {
$this->template->st->functions['main']['body'] = "function fn_main() {\$stack = array();\n\$t = '';\n".$1."\nreturn \$t;\n}\n";
$$ = '';
}
.
chunks = {
$$ = '';
}
| chunks chunk {
$$ = $1 . "# line ".$this->template->lexer->lineno."\n" . $2;
}
.
chunk = literal {
$$ = ($1 != "''" && $1 != '""' ? '$t .= ' . $1 . ";\n" : '');
}
| "<!--" code_chunk/c "-->" {
$$ = $c;
}
| "{{" exp/e "}}" {
$$ = '$t .= ' . ($e[1] || !$this->template->options->auto_escape ? $e[0] : $this->template->compile_function($this->template->options->auto_escape, [ $e ])[0]) . ";\n";
}
| error/e {
$$ = '';
}
.
code_chunk = c_if/$ | c_set/$ | c_fn/$ | c_for/$ | exp/e {
$$ = '$t .= ' . ($e[1] || !$this->template->options->auto_escape ? $e[0] : $this->template->compile_function($this->template->options->auto_escape, [ $e ])[0]) . ";\n";
}
.
c_if = "IF" exp/e "-->" chunks/if "<!--" "END" {
$$ = "if (" . $e[0] . ") {\n" . $if . "}\n";
}
| "IF" exp/e "-->" chunks/if "<!--" "ELSE" "-->" chunks/else "<!--" "END" {
$$ = "if (" . $e[0] . ") {\n" . $if . "} else {\n" . $else . "}\n";
}
| "IF" exp/e "-->" chunks/if c_elseifs/ei chunks/ec "<!--" "END" {
$$ = "if (" . $e[0] . ") {\n" . $if . $ei . $ec . "}\n";
}
| "IF" exp/e "-->" chunks/if c_elseifs/ei chunks/ec "<!--" "ELSE" "-->" chunks/else "<!--" "END" {
$$ = "if (" . $e[0] . ") {\n" . $if . $ei . $ec . "} else {\n" . $else . "}\n";
}
.
c_elseifs = "<!--" elseif exp/e "-->" {
$$ = "} elseif (" . $e[0] . ") {\n";
}
| c_elseifs/p chunks/cs "<!--" elseif exp/e "-->" {
$$ = $p . $cs . "} elseif (" . $e[0] . ") {\n";
}
.
c_set = "SET" varref/v "=" exp/e {
$$ = $v[0] . ' = ' . $e[0] . ";\n";
}
| "SET" varref/v "-->" chunks/cs "<!--" "END" {
$$ = "\$stack[] = \$t;\n\$t = '';\n" . $cs . $v[0] . " = \$t;\n\$t = array_pop(\$stack);\n";
}
.
c_fn = fn name/name "(" arglist/args ")" "=" exp/exp {
$this->template->st->functions[$name] = array(
'name' => $name,
'args' => $args,
'body' => 'function fn_'.$name." () {\nreturn ".$exp[0].";\n}\n",
//'line' => $line, Ой, я чо - аргументы не юзаю?
//'pos' => $pos,
);
$$ = '';
}
| fn name/name "(" arglist/args ")" "-->" chunks/cs "<!--" "END" {
$this->template->st->functions[$name] = array(
'name' => $name,
'args' => $args,
'body' => 'function fn_'.$name." () {\$stack = array();\n\$t = '';\n".$cs."\nreturn \$t;\n}\n",
//'line' => $line,
//'pos' => $pos,
);
$$ = '';
}
.
c_for = for varref/varref "=" exp/exp "-->" chunks/cs "<!--" "END" {
$varref_index = substr($varref[0], 0, -1) . ".'_index']";
$$ = "\$stack[] = ".$varref[0].";
\$stack[] = ".$varref_index.";
\$stack[] = 0;
foreach ((array)($exp[0]) as \$item) {
".$varref[0]." = \$item;
".$varref_index." = \$stack[count(\$stack)-1]++;
".$cs."}
array_pop(\$stack);
".$varref_index." = array_pop(\$stack);
".$varref[0]." = array_pop(\$stack);
";
}
.
fn = "FUNCTION" | "BLOCK" | "MACRO" .
for = "FOR" | "FOREACH" .
elseif = "ELSE" "IF" | "ELSIF" | "ELSEIF" .
# Выражения
exp: exp/a ".." exp/b {
$$ = [ '(' . $a[0] . ' . ' . $b[0] . ')', $a[1] && $b[1] ];
}
| exp/a "||" exp/b {
$$ = [ '(' . $a[0] . ' ?: ' . $b[0] . ')', $a[1] && $b[1] ];
}
| exp/a "OR" exp/b {
$$ = [ '(' . $a[0] . ' ?: ' . $b[0] . ')', $a[1] && $b[1] ];
}
| exp/a "XOR" exp/b {
$$ = [ '(' . $a[0] . ' XOR ' . $b[0] . ')', true ];
}
| exp/a "&&" exp/b {
$$ = [ '(' . $a[0] . ' && ' . $b[0] . ')', true ];
}
| exp/a "AND" exp/b {
$$ = [ '(' . $a[0] . ' && ' . $b[0] . ')', true ];
}
| exp/a "?" exp/b ":" exp/c {
$$ = [ '(' . $a[0] . ' ? ' . $b[0] . ' : ' . $c[0] . ')', $b[1] && $c[1] ];
}
| exp/a "==" exp/b {
$$ = [ '(' . $a[0] . ' == ' . $b[0] . ')', true ];
}
| exp/a "!=" exp/b {
$$ = [ '(' . $a[0] . ' != ' . $b[0] . ')', true ];
}
| exp/a "<" exp/b {
$$ = [ '(' . $a[0] . ' < ' . $b[0] . ')', true ];
}
| exp/a ">" exp/b {
$$ = [ '(' . $a[0] . ' > ' . $b[0] . ')', true ];
}
| exp/a "<=" exp/b {
$$ = [ '(' . $a[0] . ' <= ' . $b[0] . ')', true ];
}
| exp/a ">=" exp/b {
$$ = [ '(' . $a[0] . ' >= ' . $b[0] . ')', true ];
}
| exp/a "+" exp/b {
$$ = [ '(' . $a[0] . ' + ' . $b[0] . ')', true ];
}
| exp/a "-" exp/b {
$$ = [ '(' . $a[0] . ' - ' . $b[0] . ')', true ];
}
| exp/a "&" exp/b {
$$ = [ '(' . $a[0] . ' & ' . $b[0] . ')', true ];
}
| exp/a "*" exp/b {
$$ = [ '(' . $a[0] . ' * ' . $b[0] . ')', true ];
}
| exp/a "/" exp/b {
$$ = [ '(' . $a[0] . ' / ' . $b[0] . ')', true ];
}
| exp/a "%" exp/b {
$$ = [ '(' . $a[0] . ' % ' . $b[0] . ')', true ];
}
| p10/$
.
p10: p11/$
| '-' p11/a {
$$ = [ '(-'.$a[0].')', true ];
}
.
p11: nonbrace
| '(' exp/e ')' varpath/p {
$$ = [ ($p !== '' ? 'self::noop('.$e[0].')'.$p : '('.$e[0].')'), false ];
}
| '!' p11/a {
$$ = [ '(!'.$a[0].')', true ];
}
| "NOT" p11/a {
$$ = [ '(!'.$a[0].')', true ];
}
.
nonbrace: '{' hash/h '}' {
$$ = [ 'array(' . $h . ')', true ];
}
| literal {
$$ = [ $1, true ];
}
| varref/$
| name/f '(' ')' {
$$ = $this->template->compile_function($f, []);
}
| name/f '(' list/args ')' {
$$ = $this->template->compile_function($f, $args);
}
| name/f '(' gthash/args ')' {
$$ = [ "\$this->parent->call_block('".addcslashes($f, "'\\")."', array(".$args."), '".addcslashes($this->template->lexer->errorinfo(), "'\\")."')", true ];
}
| name/f nonbrace/arg {
$$ = $this->template->compile_function($f, [ $arg ]);
}
.
list: exp/e {
$$ = [ $e ];
}
| exp/e ',' list/l {
$$ = $l;
array_unshift($$, $e);
}
.
arglist: name/n {
$$ = [ $n ];
}
| name/n ',' arglist/args {
$$ = $args;
array_unshift($$, $n);
}
| {
$$ = [];
}
.
hash: pair/$
| pair/p ',' hash/h {
$$ = $p . ', ' . $h;
}
| {
$$ = '';
}
.
gthash: gtpair/$
| gtpair/p ',' gthash/h {
$$ = $p . ', ' . $h;
}
.
pair: exp/k ',' exp/v {
$$ = $k[0] . ' => ' . $v[0];
}
| gtpair/$
.
gtpair: exp/k "=>" exp/v {
$$ = $k[0] . ' => ' . $v[0];
}
.
varref: name/n {
$$ = [ "\$this->tpldata['".addcslashes($n, "\\\'")."']", false ];
}
| varref/v varpart/p {
$$ = [ $v[0] . $p, false ];
}
.
varpart: '.' namekw/n {
$$ = "['".addcslashes($n, "\\\'")."']";
}
| '[' exp/e ']' {
$$ = '['.$e[0].']';
}
| '.' namekw/n '(' ')' {
$$ = '->'.$n.'()';
}
| '.' namekw/n '(' list/l ')' {
$argv = [];
foreach ($l as $a) {
$argv[] = $a[0];
}
$$ = '->'.$n.'('.implode(', ', $argv).')';
}
.
varpath: {
$$ = '';
}
| varpath/a varpart/p {
$$ = $a . $p;
}
.
namekw: name
| "IF" | "END" | "ELSE" | "ELSIF" | "ELSEIF"
| "SET" | "OR" | "XOR" | "AND" | "NOT"
| "FUNCTION" | "BLOCK" | "MACRO" | "FOR" | "FOREACH"
.