VMXTemplate/template.yp

330 lines
7.9 KiB
Plaintext
Raw 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.

# Контекстно-свободная Parse::Yapp-грамматика шаблонизатора
#
# Компилировать так: yapp -o VMXTemplate.pm -t template.skel.pm template.yp
#
# {{ двойные скобки }} нужно исключительно чтобы маркеры начала и конца подстановки
# были уникальны в грамматике. Вместо них обычно используются { одинарные }, а
# выбор корректной лексемы - скобки или маркера - делает лексический анализатор.
# Но зато вместо { фигурных скобок } можно выбрать себе любые другие маркеры!
#
# Все выражения представляются массивом из двух значений: [ код выражения, флаг экранирования ]
# Флаг экранирования == true, если это выражение HTML-безопасно. При включённом auto_escape
# небезопасные выражения прогоняются через экранирование.
#
# Кстати:
# * Олдстайл BEGIN .. END ликвидирован
# * Возможно, нужно добавить в каком-то виде foreach ... as key => value
#
# P.S: Комментарии типа "#{" и "#}" служат, чтобы у тупого Parse::Yapp'а число скобок сходилось
%start template
%token literal
%token name
%token '..'
%token '||'
%token 'OR'
%token 'XOR'
%token 'AND'
%token '&&'
%token '&'
%token '=='
%token '!='
%token '<'
%token '>'
%token '<='
%token '>='
%token '+'
%token '-'
%token '*'
%token '/'
%token '%'
%token '('
%token ')'
%token '!'
%token 'NOT'
%token '{'
%token '}'
%token ','
%token '=>'
%token '['
%token ']'
%token '<!--'
%token '-->'
%token '{{'
%token '}}'
%left '..'
%left '||' 'OR' 'XOR'
%left '&&' 'AND'
%nonassoc '==' '!=' '<' '>' '<=' '>='
%left '+' '-'
%left '&'
%left '*' '/' '%'
# Директивы
%%
template: chunks {
$_[0]->{functions}->{main}->{body} = "sub fn_main {\nmy \$stack = [];\nmy \$t = '';\n".$_[1]."\nreturn \$t;\n}\n";
'';
}
;
chunks: {
'';
}
| chunks chunk {
$_[1] . $_[2];
}
;
chunk: literal {
'$t .= ' . $_[1][0] . ";\n";
}
| '<!--' code_chunk '-->' {
$_[2];
}
| '{{' exp '}}' {
'$t .= ' . ($_[2][1] || !$_[0]->{compiler}->{options}->{auto_escape} ? $_[2][0] : $_[0]->{compiler}->compile_function($_[0]->{compiler}->{options}->{auto_escape}, [ $_[2] ])->[0]) . ";\n";
}
| error {
'';
}
;
code_chunk: c_if | c_set | c_fn | c_for | exp {
'$t .= ' . ($_[1][1] || !$_[0]->{compiler}->{options}->{auto_escape} ? $_[1][0] : $_[0]->{compiler}->compile_function($_[0]->{compiler}->{options}->{auto_escape}, [ $_[1] ])->[0]) . ";\n";
}
;
c_if: 'IF' exp '-->' chunks '<!--' 'END' {
"if (" . $_[2][0] . ") {\n" . $_[4] . "}\n";
}
| 'IF' exp '-->' chunks '<!--' 'ELSE' '-->' chunks '<!--' 'END' {
"if (" . $_[2][0] . ") {\n" . $_[4] . "} else {\n" . $_[8] . "}\n";
}
| 'IF' exp '-->' chunks c_elseifs chunks '<!--' 'END' {
"if (" . $_[2][0] . ") {\n" . $_[4] . $_[5] . $_[6] . "}\n";
}
| 'IF' exp '-->' chunks c_elseifs chunks '<!--' 'ELSE' '-->' chunks '<!--' 'END' {
"if (" . $_[2][0] . ") {\n" . $_[4] . $_[5] . $_[6] . "} else {\n" . $_[10] . "}\n";
}
;
c_elseifs: '<!--' elseif exp '-->' {
#{
"} elsif (" . $_[3][0] . ") {\n";
#}
}
| c_elseifs chunks '<!--' elseif exp '-->' {
#{
$_[1] . $_[2] . "} elsif (" . $_[5][0] . ") {\n";
#}
}
;
c_set: 'SET' varref '=' exp {
$_[2][0] . ' = ' . $_[4][0] . ";\n";
}
| 'SET' varref '-->' chunks '<!--' 'END' {
"push \@\$stack, \$t;\n\$t = '';\n" . $_[4] . $_[2][0] . " = \$t;\n\$t = pop(\@\$stack);\n";
}
;
fn_def: fn name '(' arglist ')' {
$_[0]->{functions}->{$_[2]} = {
name => $_[2],
args => $_[4],
line => $_[0]->{lexer}->line,
pos => $_[0]->{lexer}->pos,
body => 'sub fn_'.$_[2],
};
}
;
c_fn: fn_def '=' exp {
$_[1]->{body} .= " {\nreturn ".$_[3].";\n}\n";
'';
}
| fn_def '-->' chunks '<!--' 'END' {
$_[1]->{body} .= " {\nmy \$stack = [];\nmy \$t = '';\n".$_[3]."\nreturn \$t;\n}\n";
'';
}
;
c_for: for varref '=' exp '-->' chunks '<!--' 'END' {
my @varref = @{$_[2]};
my @exp = @_{$_[4]};
my $cs = $_[6];
#{
my $varref_index = substr($varref[0], 0, -1) . ".'_index'}";
"push \@\$stack, ".$varref[0].", ".$varref_index.", 0;
foreach my \$item (array_items($exp[0])) {
".$varref[0]." = \$item;
".$varref_index." = \$stack[count(\$stack)-1]++;
".$cs."}
pop \@\$stack;
".$varref_index." = pop(\@\$stack);
".$varref[0]." = pop(\@\$stack);
";
}
;
fn: 'FUNCTION' | 'BLOCK' | 'MACRO' ;
for: 'FOR' | 'FOREACH' ;
elseif: 'ELSE' 'IF' | 'ELSIF' | 'ELSEIF' ;
# Выражения
exp: exp '..' exp {
[ '(' . $_[1][0] . ' . ' . $_[3][0] . ')', $_[1][1] && $_[3][1] ];
}
| exp '||' exp {
[ '(' . $_[1][0] . ' || ' . $_[3][0] . ')', $_[1][1] && $_[3][1] ];
}
| exp 'OR' exp {
[ '(' . $_[1][0] . ' || ' . $_[3][0] . ')', $_[1][1] && $_[3][1] ];
}
| exp 'XOR' exp {
[ '(' . $_[1][0] . ' XOR ' . $_[3][0] . ')', 1 ];
}
| exp '&&' exp {
[ '(' . $_[1][0] . ' && ' . $_[3][0] . ')', 1 ];
}
| exp 'AND' exp {
[ '(' . $_[1][0] . ' && ' . $_[3][0] . ')', 1 ];
}
| exp '==' exp {
[ '$self->eq(' . $_[1][0] . ', ' . $_[3][0] . ')', 1 ];
}
| exp '!=' exp {
[ '!$self->eq(' . $_[1][0] . ', ' . $_[3][0] . ')', 1 ];
}
| exp '<' exp {
[ '$self->lt(' . $_[1][0] . ', ' . $_[3][0] . ')', 1 ];
}
| exp '>' exp {
[ '$self->gt(' . $_[1][0] . ', ' . $_[3][0] . ')', 1 ];
}
| exp '<=' exp {
[ '!$self->gt(' . $_[1][0] . ', ' . $_[3][0] . ')', 1 ];
}
| exp '>=' exp {
[ '!$self->lt(' . $_[1][0] . ', ' . $_[3][0] . ')', 1 ];
}
| exp '+' exp {
[ '(' . $_[1][0] . ' + ' . $_[3][0] . ')', 'i' ];
}
| exp '-' exp {
[ '(' . $_[1][0] . ' - ' . $_[3][0] . ')', 'i' ];
}
| exp '&' exp {
[ '(' . $_[1][0] . ' & ' . $_[3][0] . ')', 'i' ];
}
| exp '*' exp {
[ '(' . $_[1][0] . ' * ' . $_[3][0] . ')', 'i' ];
}
| exp '/' exp {
[ '(' . $_[1][0] . ' / ' . $_[3][0] . ')', 'i' ];
}
| exp '%' exp {
[ '(' . $_[1][0] . ' % ' . $_[3][0] . ')', 'i' ];
}
| p10
;
p10: p11
| '-' p11 {
[ '(-'.$_[2][0].')', 1 ];
}
;
p11: nonbrace
| '(' exp ')' varpath {
[ '('.$_[2][0].')'.$_[4], 0 ];
}
| '!' p11 {
[ '(!'.$_[2][0].')', 1 ];
}
| 'NOT' p11 {
[ '(!'.$_[2][0].')', 1 ];
}
;
nonbrace: '{' hash '}' {
[ "{ " . $_[2] . " }", 1 ];
}
| literal
| varref
| name '(' ')' {
$_[0]->{compiler}->compile_function($_[1], []);
}
| name '(' list ')' {
$_[0]->{compiler}->compile_function($_[1], $_[3]);
}
| name '(' gthash ')' {
[ "\$self->{template}->call_block('".addcslashes($_[1], "'\\")."', { ".$_[3]." }, '".addcslashes($_[0]->{lexer}->errorinfo(), "'\\")."')", 1 ];
}
| name nonbrace {
$_[0]->{compiler}->compile_function($_[1], [ $_[3] ]);
}
;
list: exp {
[ $_[1] ];
}
| exp ',' list {
[ $_[1], @{$_[3]} ];
}
;
arglist: name {
[ $_[1] ];
}
| name ',' arglist {
[ $_[1], @{$_[3]} ];
}
| {
[];
}
;
hash: pair
| pair ',' hash {
$_[1] . ', ' . $_[3];
}
| {
'';
}
;
gthash: gtpair
| gtpair ',' gthash {
$_[1] . ', ' . $_[3];
}
;
pair: exp ',' exp {
$_[1][0] . ' => ' . $_[3][0];
}
| gtpair
;
gtpair: exp '=>' exp {
$_[1][0] . ' => ' . $_[3][0];
}
;
varref: name {
[ "\$self->{tpldata}{'".addcslashes($_[1], "\\\'")."'}", 0 ];
}
| varref varpart {
[ $_[1][0] . $_[2], 0 ];
}
;
varpart: '.' name {
"{'".addcslashes($_[1], "\\\'")."'}";
}
| '[' exp ']' {
($_[2][1] eq 'i' ? '['.$_[2][0].']' : "{".$_[2][0]."}");
}
| '.' name '(' ')' {
'->'.$_[2].'()';
}
| '.' name '(' list ')' {
'->'.$_[2].'('.join(', ', map { $_->[0] } @{$_[4]}).')';
}
;
varpath: {
'';
}
| varpath varpart {
$_[1] . $_[2];
}
;
%%