Add remaining functions, generic fixes for implementation, english comments
parent
5e90352980
commit
10d1588e88
604
template.skel.pm
604
template.skel.pm
|
@ -20,6 +20,7 @@ sub new
|
|||
{
|
||||
my $class = shift;
|
||||
$class = ref($class) || $class;
|
||||
my ($compiler) = @_;
|
||||
return bless $class->SUPER::new(
|
||||
yyversion => '<<$version>>',
|
||||
yystates =>
|
||||
|
@ -27,6 +28,8 @@ sub new
|
|||
yyrules =>
|
||||
<<$rules>>,
|
||||
#line 30 "template.skel.pm"
|
||||
compiler => $compiler,
|
||||
lexer => undef,
|
||||
@_
|
||||
), $class;
|
||||
}
|
||||
|
@ -34,22 +37,39 @@ sub new
|
|||
sub _Lexer
|
||||
{
|
||||
my ($parser) = shift;
|
||||
return $parser->{__lexer}->read_token;
|
||||
return $parser->{lexer}->read_token;
|
||||
}
|
||||
|
||||
sub _error
|
||||
{
|
||||
my ($self) = @_;
|
||||
$self->{__lexer}->warn('Unexpected ' . $self->YYCurtok . ($self->YYCurval ? ' ' . $self->YYCurval : ''));
|
||||
$self->{__lexer}->skip_error;
|
||||
$self->{lexer}->warn('Unexpected ' . $self->YYCurtok . ($self->YYCurval ? ' ' . $self->YYCurval : ''));
|
||||
$self->{lexer}->skip_error;
|
||||
}
|
||||
|
||||
sub compile
|
||||
{
|
||||
my ($self, $text) = @_;
|
||||
$self->{__lexer} ||= new VMXTemplate::Lexer($self, $self->{__options});
|
||||
$self->{__lexer}->set_code($text);
|
||||
$self->{lexer} ||= new VMXTemplate::Lexer($self, $self->{compiler}->{options});
|
||||
$self->{lexer}->set_code($text);
|
||||
$self->{functions} = {
|
||||
main => {
|
||||
name => 'main',
|
||||
args => [],
|
||||
body => '',
|
||||
line => 0,
|
||||
pos => 0,
|
||||
},
|
||||
};
|
||||
$self->YYParse(yylex => \&_Lexer, yyerror => \&_error);
|
||||
if (!$self->{functions}->{main}->{body})
|
||||
{
|
||||
# Parse error?
|
||||
delete $self->{functions}->{main};
|
||||
}
|
||||
return "use VMXTemplate::Utils;\n".
|
||||
"our \$FUNCTIONS = { ".join(", ", map { "$_ => 1" } keys %{$self->{functions}})." };\n".
|
||||
join("\n", map { $_->{body} } values %{$self->{functions}})
|
||||
}
|
||||
|
||||
package VMXTemplate::Lexer;
|
||||
|
@ -122,6 +142,19 @@ sub eat
|
|||
return $str;
|
||||
}
|
||||
|
||||
sub pos
|
||||
{
|
||||
my $self = shift;
|
||||
use bytes;
|
||||
return length $self->{eaten};
|
||||
}
|
||||
|
||||
sub line
|
||||
{
|
||||
my $self = shift;
|
||||
return $self->{lineno};
|
||||
}
|
||||
|
||||
sub skip_error
|
||||
{
|
||||
my ($self) = @_;
|
||||
|
@ -287,13 +320,8 @@ sub errorinfo
|
|||
my $lineend = index($self->{code}, "\n");
|
||||
$lineend = length($self->{code}) if $lineend < 0;
|
||||
my $line = substr($self->{eaten}, $linestart+1) . '^^^' . substr($self->{code}, 0, $lineend);
|
||||
my $charpos;
|
||||
{
|
||||
use bytes;
|
||||
$charpos = length $self->{eaten};
|
||||
}
|
||||
return ' in '.$self->{options}->{input_filename}.', line '.($self->{lineno}+1).
|
||||
', character '.$charpos.', marked by ^^^ in '.$line;
|
||||
', byte '.$self->pos.', marked by ^^^ in '.$line;
|
||||
}
|
||||
|
||||
sub warn
|
||||
|
@ -305,10 +333,15 @@ sub warn
|
|||
|
||||
package VMXTemplate::Utils;
|
||||
|
||||
use Encode;
|
||||
|
||||
use base qw(Exporter);
|
||||
our @EXPORT = qw(
|
||||
TS_UNIX TS_DB TS_DB_DATE TS_MW TS_EXIF TS_ORACLE TS_ISO_8601 TS_RFC822
|
||||
timestamp plural_ru strlimit strip_tags addcslashes requote
|
||||
timestamp plural_ru strlimit htmlspecialchars strip_tags strip_unsafe_tags
|
||||
addcslashes requote quotequote sql_quote regex_replace str_replace
|
||||
array_slice array_div encode_json trim html_pbr array_items utf8on
|
||||
exec_subst exec_pairs exec_is_array exec_get exec_cmp
|
||||
);
|
||||
|
||||
use constant {
|
||||
|
@ -578,9 +611,68 @@ sub html_pbr
|
|||
return $s;
|
||||
}
|
||||
|
||||
package VMXTemplate::Exception;
|
||||
# helper - returns array elements or just scalar, if it's not an arrayref
|
||||
sub array_items
|
||||
{
|
||||
ref($_[0]) && $_[0] =~ /ARRAY/ ? @{$_[0]} : (defined $_[0] ? ($_[0]) : ());
|
||||
}
|
||||
|
||||
VMXTemplate::Utils::import();
|
||||
# recursive utf8_on and return result
|
||||
sub utf8on
|
||||
{
|
||||
if (ref($_[0]) && $_[0] =~ /HASH/so)
|
||||
{
|
||||
utf8on($_[0]->{$_}) for keys %{$_[0]};
|
||||
}
|
||||
elsif (ref($_[0]) && $_[0] =~ /ARRAY/so)
|
||||
{
|
||||
utf8on($_) for @{$_[0]};
|
||||
}
|
||||
else
|
||||
{
|
||||
Encode::_utf8_on($_[0]);
|
||||
}
|
||||
return $_[0];
|
||||
}
|
||||
|
||||
# function subst()
|
||||
sub exec_subst
|
||||
{
|
||||
my $str = shift;
|
||||
$str =~ s/(?<!\\)((?:\\\\)*)\$(?:([1-9]\d*)|\{([1-9]\d*)\})/$_[($2||$3)-1]/gisoe;
|
||||
return $str;
|
||||
}
|
||||
|
||||
# array of sorted key-value pairs for hash: [ { key => ..., value => ... }, ... ]
|
||||
sub exec_pairs
|
||||
{
|
||||
my $hash = shift;
|
||||
return [ map { { key => $_, value => $hash->{$_} } } sort keys %{ $hash || {} } ];
|
||||
}
|
||||
|
||||
# check if the argument is an arrayref
|
||||
sub exec_is_array
|
||||
{
|
||||
return ref $_[1] && $_[1] =~ /ARRAY/;
|
||||
}
|
||||
|
||||
# get array or hash element
|
||||
sub exec_get
|
||||
{
|
||||
defined $_[1] && ref $_[0] || return $_[0];
|
||||
$_[0] =~ /ARRAY/ && return $_[0]->[$_[1]];
|
||||
return $_[0]->{$_[1]};
|
||||
}
|
||||
|
||||
# type-dependent comparison
|
||||
sub exec_cmp
|
||||
{
|
||||
my ($a, $b) = @_;
|
||||
my $n = grep /^-?\d+(\.\d+)?$/, $a, $b;
|
||||
return $n ? $a <=> $b : $a cmp $b;
|
||||
}
|
||||
|
||||
package VMXTemplate::Exception;
|
||||
|
||||
sub new
|
||||
{
|
||||
|
@ -592,6 +684,8 @@ sub new
|
|||
|
||||
package VMXTemplate::Options;
|
||||
|
||||
VMXTemplate::Utils::import();
|
||||
|
||||
sub new
|
||||
{
|
||||
my $class = shift;
|
||||
|
@ -601,9 +695,9 @@ sub new
|
|||
my $self = bless {
|
||||
begin_code => '<!--', # instruction start
|
||||
end_code => '-->', # instruction end
|
||||
begin_subst => '{', # substitution start (may be turned off via false)
|
||||
end_subst => '}', # substitution end (may be turned off via false)
|
||||
no_code_subst => 0, # do not substitute expressions in instructions
|
||||
begin_subst => '{', # substitution start (set to '' to turn off)
|
||||
end_subst => '}', # substitution end (set to '' to turn off)
|
||||
no_code_subst => 0, # only evaluate instructions, but ignore their results
|
||||
eat_code_line => 1, # remove the "extra" lines which contain instructions only
|
||||
root => '.', # directory with templates
|
||||
cache_dir => undef, # compiled templates cache directory
|
||||
|
@ -697,7 +791,6 @@ sub new
|
|||
|
||||
my $self = bless {
|
||||
tpldata => {},
|
||||
parent => undef,
|
||||
failed => {},
|
||||
function_search_path => {},
|
||||
options => new VMXTemplate::Options($options),
|
||||
|
@ -709,43 +802,6 @@ sub new
|
|||
|
||||
package VMXTemplate::Compiler;
|
||||
|
||||
# function subst()
|
||||
sub exec_subst
|
||||
{
|
||||
my $str = shift;
|
||||
$str =~ s/(?<!\\)((?:\\\\)*)\$(?:([1-9]\d*)|\{([1-9]\d*)\})/$_[($2||$3)-1]/gisoe;
|
||||
return $str;
|
||||
}
|
||||
|
||||
# array of sorted key-value pairs for hash: [ { key => ..., value => ... }, ... ]
|
||||
sub exec_pairs
|
||||
{
|
||||
my $hash = shift;
|
||||
return [ map { { key => $_, value => $hash->{$_} } } sort keys %{ $hash || {} } ];
|
||||
}
|
||||
|
||||
# check if the argument is an arrayref
|
||||
sub exec_is_array
|
||||
{
|
||||
return ref $_[1] && $_[1] =~ /ARRAY/;
|
||||
}
|
||||
|
||||
# get array or hash element
|
||||
sub exec_get
|
||||
{
|
||||
defined $_[1] && ref $_[0] || return $_[0];
|
||||
$_[0] =~ /ARRAY/ && return $_[0]->[$_[1]];
|
||||
return $_[0]->{$_[1]};
|
||||
}
|
||||
|
||||
# type-dependent comparison
|
||||
sub exec_cmp
|
||||
{
|
||||
my ($a, $b) = @_;
|
||||
my $n = grep /^-?\d+(\.\d+)?$/, $a, $b;
|
||||
return $n ? $a <=> $b : $a cmp $b;
|
||||
}
|
||||
|
||||
# Function aliases
|
||||
my $functions = {
|
||||
'i' => 'int',
|
||||
|
@ -868,182 +924,7 @@ my $functionSafeness = {
|
|||
'yesno' => Q_ALL_BUT_FIRST,
|
||||
};
|
||||
|
||||
sub fmop
|
||||
{
|
||||
my $op = shift;
|
||||
return "((" . join(") $op (", @_) . "))";
|
||||
}
|
||||
|
||||
# логические операции
|
||||
sub function_or { fmop('||', @_) }
|
||||
sub function_and { fmop('&&', @_) }
|
||||
sub function_not { "!($_[1])" }
|
||||
# арифметические операции
|
||||
sub function_add { fmop('+', @_) }
|
||||
sub function_sub { fmop('-', @_) }
|
||||
sub function_mul { fmop('*', @_) }
|
||||
sub function_div { fmop('/', @_) }
|
||||
sub function_mod { fmop('%', @_) }
|
||||
# логарифм
|
||||
sub function_log { "log($_[1])" }
|
||||
# чётный, нечётный
|
||||
sub function_even { "!(($_[1]) & 1)" }
|
||||
sub function_odd { "(($_[1]) & 1)" }
|
||||
# приведение к целому числу
|
||||
sub function_int { "int($_[1])" }
|
||||
# сравнения: = != > < >= <= (типозависимые)
|
||||
sub function_eq { "(exec_cmp($_[1], $_[2]) == 0)" }
|
||||
sub function_ne { "(exec_cmp($_[1], $_[2]) != 0)" }
|
||||
sub function_gt { "(exec_cmp($_[1], $_[2]) > 0)" }
|
||||
sub function_lt { "(exec_cmp($_[1], $_[2]) < 0)" }
|
||||
sub function_ge { "(exec_cmp($_[1], $_[2]) >= 0)" }
|
||||
sub function_le { "(exec_cmp($_[1], $_[2]) <= 0)" }
|
||||
# сравнения: = != > < >= <= (строковые)
|
||||
sub function_seq { "(($_[1]) eq ($_[2]))" }
|
||||
sub function_sne { "(($_[1]) ne ($_[2]))" }
|
||||
sub function_sgt { "(($_[1]) gt ($_[2]))" }
|
||||
sub function_slt { "(($_[1]) lt ($_[2]))" }
|
||||
sub function_sge { "(($_[1]) ge ($_[2]))" }
|
||||
sub function_sle { "(($_[1]) le ($_[2]))" }
|
||||
# сравнения: = != > < >= <= (численные)
|
||||
sub function_neq { "(($_[1]) == ($_[2]))" }
|
||||
sub function_nne { "(($_[1]) != ($_[2]))" }
|
||||
sub function_ngt { "(($_[1]) > ($_[2]))" }
|
||||
sub function_nlt { "(($_[1]) < ($_[2]))" }
|
||||
sub function_nge { "(($_[1]) >= ($_[2]))" }
|
||||
sub function_nle { "(($_[1]) <= ($_[2]))" }
|
||||
# тернарный оператор $1 ? $2 : $3
|
||||
sub function_yesno { "(($_[1]) ? ($_[2]) : ($_[3]))" }
|
||||
|
||||
## Строки
|
||||
|
||||
# нижний и верхний регистр
|
||||
sub function_lc { "lc($_[1])" }
|
||||
sub function_uc { "uc($_[1])" }
|
||||
# нижний и верхний регистр первого символа
|
||||
sub function_lcfirst { "lcfirst($_[1])" }
|
||||
sub function_ucfirst { "ucfirst($_[1])" }
|
||||
# экранировать двойные и одинарные кавычки в стиле C (добавить \)
|
||||
sub function_quote { "quotequote($_[1])" }
|
||||
# экранировать двойные кавычки в стиле SQL/CSV (удвоением)
|
||||
sub function_sql_quote { "sql_quote($_[1])" }
|
||||
# экранирование символов, специальных для регулярного выражения
|
||||
sub function_requote { "requote($_[1])" }
|
||||
# кодировать символы в стиле URL
|
||||
sub function_urlencode { shift; "URI::Escape::uri_escape(".join(",",@_).")" }
|
||||
# декодировать символы в стиле URL
|
||||
sub function_urldecode { shift; "URI::Escape::uri_unescape(".join(",",@_).")" }
|
||||
# замена регэкспов
|
||||
sub function_replace { "regex_replace($_[1], $_[2], $_[3])" }
|
||||
# замена подстрок (а не регэкспов)
|
||||
sub function_str_replace { "str_replace($_[1], $_[2], $_[3])" }
|
||||
# длина строки в символах
|
||||
sub function_strlen { "strlen($_[1])" }
|
||||
# подстрока
|
||||
sub function_substr { shift; "substr(".join(",", @_).")" }
|
||||
# обрезать пробелы из начала и конца строки
|
||||
sub function_trim { shift; "trim($_[0])" }
|
||||
# разделить строку $2 по регулярному выражению $1 опционально с лимитом $3
|
||||
sub function_split { shift; "split(".join(",", @_).")" }
|
||||
# заменить символы & < > " ' на HTML-сущности
|
||||
sub function_html { "htmlspecialchars($_[1])" }
|
||||
# удалить все HTML-теги
|
||||
sub function_strip { "strip_tags($_[1])" }
|
||||
# оставить только "безопасные" HTML-теги
|
||||
sub function_strip_unsafe { "strip_unsafe_tags($_[1])" }
|
||||
# заменить \n на <br />
|
||||
sub function_nl2br { "regex_replace(qr/\\n/s, '<br />', $_[1])" }
|
||||
# конкатенация строк
|
||||
sub function_concat { fmop('.', @_) }
|
||||
# объединяет не просто скаляры, а также все элементы массивов
|
||||
sub function_join { fearr('join', 1, @_) }
|
||||
# подставляет на места $1, $2 и т.п. в строке аргументы
|
||||
sub function_subst { fearr('exec_subst', 1, @_) }
|
||||
# sprintf
|
||||
sub function_sprintf { fearr('sprintf', 1, @_) }
|
||||
# strftime
|
||||
sub function_strftime
|
||||
{
|
||||
my $self = shift;
|
||||
my ($fmt, $date, $time) = @_;
|
||||
$date = "($date).' '.($time)" if $time;
|
||||
$date = "POSIX::strftime($date, localtime(timestamp($date)))";
|
||||
$date = "utf8on($date)" if $self->{use_utf8};
|
||||
return $date;
|
||||
}
|
||||
# ограничение длины строки $maxlen символами на границе пробелов и добавление '...', если что.
|
||||
sub function_strlimit { shift; "strlimit(".join(",", @_).")" }
|
||||
# выбор правильной формы множественного числа для русского языка
|
||||
sub function_plural_ru { shift; "plural_ru(".join(",", @_).")" }
|
||||
|
||||
## Массивы и хеши
|
||||
|
||||
# создание хеша
|
||||
sub function_hash { shift; @_ == 1 ? "{ \@{ $_[0] } }" : "{" . join(",", @_) . "}"; }
|
||||
# hash keys, values
|
||||
sub function_keys { '[ keys(%{'.$_[1].'}) ]'; }
|
||||
sub function_values { '[ values(%{'.$_[1].'}) ]'; }
|
||||
# сортировка массива
|
||||
sub function_sort { '[ '.fearr('sort', 0, @_).' ]'; }
|
||||
# пары { id => ключ, name => значение } для хеша
|
||||
sub function_pairs { "exec_pairs($_[1])" }
|
||||
# создание массива
|
||||
sub function_array { shift; "[" . join(",", @_) . "]"; }
|
||||
# диапазон значений
|
||||
sub function_range { "[ $_[1] .. $_[2] ]" }
|
||||
# проверка, аргумент - массив или не массив?
|
||||
sub function_is_array { "exec_is_array($_[1])" }
|
||||
# количество элементов _массива_ (не хеша)
|
||||
sub function_count { "(ref($_[1]) && $_[1] =~ /ARRAY/so ? scalar(\@{ $_[1] }) : 0)" }
|
||||
# подмассив по номерам элементов
|
||||
sub function_array_slice { shift; "array_slice(" . join(",", @_) . ")"; }
|
||||
# подмассив по кратности номеров элементов
|
||||
sub function_array_div { shift; "array_div(" . join(",", @_) . ")"; }
|
||||
# получить элемент хеша/массива по неконстантному ключу (например get(iteration.array, rand(5)))
|
||||
# по-моему, это лучше, чем Template Toolkit'овский ад - hash.key.${another.hash.key}.зюка.хрюка и т.п.
|
||||
sub function_get { shift; "exec_get(" . join(",", @_) . ")"; }
|
||||
# для хеша
|
||||
sub function_hget { "($_[1])->\{$_[2]}" }
|
||||
# для массива
|
||||
sub function_aget { "($_[1])->\[$_[2]]" }
|
||||
# присваивание (только lvalue)
|
||||
sub function_set { "scalar(($_[1] = $_[2]), '')" }
|
||||
# слияние массивов в один большой массив
|
||||
sub function_array_merge { shift; '[@{'.join('},@{',@_).'}]' }
|
||||
# вынуть первый элемент массива
|
||||
sub function_shift { "shift(\@{$_[1]})"; }
|
||||
# вынуть последний элемент массива
|
||||
sub function_pop { "pop(\@{$_[1]})"; }
|
||||
# вставить как первый элемент массива
|
||||
sub function_unshift { shift; "unshift(\@{".shift(@_)."}, ".join(",", @_).")"; }
|
||||
# вставить как последний элемент массива
|
||||
sub function_push { shift; "push(\@{".shift(@_)."}, ".join(",", @_).")"; }
|
||||
|
||||
## Прочее
|
||||
|
||||
# вычисление выражения и игнорирование результата, как в JS
|
||||
sub function_void { "scalar(($_[1]), '')" }
|
||||
# дамп переменной
|
||||
sub function_dump { shift; "exec_dump(" . join(",", @_) . ")" }
|
||||
# JSON-кодирование
|
||||
sub function_json { "encode_json($_[1])" }
|
||||
# return the value as is, to ignore automatic escaping of "unsafe" HTML
|
||||
sub function_raw { $_[1] }
|
||||
# apply the function to each array element
|
||||
sub function_map
|
||||
{
|
||||
my $self = shift;
|
||||
my $fn = shift;
|
||||
if ($fn =~ /^[\"\'](\w+)[\"\']$/so)
|
||||
{
|
||||
return '(map { '.$self->compile_function($1, '$_').' } (@{'.join('}, @{', @_).'}))';
|
||||
}
|
||||
else
|
||||
{
|
||||
$self->{lexer}->warn("Non-constant function: unimplemented");
|
||||
}
|
||||
}
|
||||
|
||||
# Generate semantic expression for template function call
|
||||
sub compile_function
|
||||
{
|
||||
my $self = shift;
|
||||
|
@ -1097,4 +978,265 @@ sub compile_function
|
|||
return [ $r, $q ];
|
||||
}
|
||||
|
||||
# call operator on arguments
|
||||
sub fmop
|
||||
{
|
||||
my $op = shift;
|
||||
return "((" . join(") $op (", @_) . "))";
|
||||
}
|
||||
|
||||
# call function, expanding all passed arrays
|
||||
sub fearr
|
||||
{
|
||||
my $f = shift;
|
||||
my $n = shift;
|
||||
my $self = shift;
|
||||
my $e = "$f(";
|
||||
$e .= join(", ", splice(@_, 0, $n)) if $n;
|
||||
$e .= ", " if $n && @_;
|
||||
$e .= join(", ", map { "array_items($_)" } @_);
|
||||
$e .= ")";
|
||||
return $e;
|
||||
}
|
||||
|
||||
### Function implementations
|
||||
|
||||
## Numeric/Logical
|
||||
|
||||
# logical
|
||||
sub function_or { fmop('||', @_) }
|
||||
sub function_and { fmop('&&', @_) }
|
||||
sub function_not { "!($_[1])" }
|
||||
# arithmetic
|
||||
sub function_add { fmop('+', @_) }
|
||||
sub function_sub { fmop('-', @_) }
|
||||
sub function_mul { fmop('*', @_) }
|
||||
sub function_div { fmop('/', @_) }
|
||||
sub function_mod { fmop('%', @_) }
|
||||
# logarithm
|
||||
sub function_log { "log($_[1])" }
|
||||
# is the argument even/odd?
|
||||
sub function_even { "!(($_[1]) & 1)" }
|
||||
sub function_odd { "(($_[1]) & 1)" }
|
||||
# cast to integer, throwing away the fractional part
|
||||
sub function_int { "int($_[1])" }
|
||||
# type-dependent comparisons: = != > < >= <=
|
||||
sub function_eq { "(exec_cmp($_[1], $_[2]) == 0)" }
|
||||
sub function_ne { "(exec_cmp($_[1], $_[2]) != 0)" }
|
||||
sub function_gt { "(exec_cmp($_[1], $_[2]) > 0)" }
|
||||
sub function_lt { "(exec_cmp($_[1], $_[2]) < 0)" }
|
||||
sub function_ge { "(exec_cmp($_[1], $_[2]) >= 0)" }
|
||||
sub function_le { "(exec_cmp($_[1], $_[2]) <= 0)" }
|
||||
# string comparisons: = != > < >= <=
|
||||
sub function_seq { "(($_[1]) eq ($_[2]))" }
|
||||
sub function_sne { "(($_[1]) ne ($_[2]))" }
|
||||
sub function_sgt { "(($_[1]) gt ($_[2]))" }
|
||||
sub function_slt { "(($_[1]) lt ($_[2]))" }
|
||||
sub function_sge { "(($_[1]) ge ($_[2]))" }
|
||||
sub function_sle { "(($_[1]) le ($_[2]))" }
|
||||
# numeric comparisons: = != > < >= <=
|
||||
sub function_neq { "(($_[1]) == ($_[2]))" }
|
||||
sub function_nne { "(($_[1]) != ($_[2]))" }
|
||||
sub function_ngt { "(($_[1]) > ($_[2]))" }
|
||||
sub function_nlt { "(($_[1]) < ($_[2]))" }
|
||||
sub function_nge { "(($_[1]) >= ($_[2]))" }
|
||||
sub function_nle { "(($_[1]) <= ($_[2]))" }
|
||||
# ternary operator $1 ? $2 : $3
|
||||
sub function_yesno { "(($_[1]) ? ($_[2]) : ($_[3]))" }
|
||||
|
||||
## String
|
||||
|
||||
# lowercase, uppercase
|
||||
sub function_lc { "lc($_[1])" }
|
||||
sub function_uc { "uc($_[1])" }
|
||||
# lowercase, uppercase the first letter
|
||||
sub function_lcfirst { "lcfirst($_[1])" }
|
||||
sub function_ucfirst { "ucfirst($_[1])" }
|
||||
# quote ', ", \, \n and \r in C-style, prepending \
|
||||
sub function_quote { "quotequote($_[1])" }
|
||||
# quote " in SQL/CSV style (by doubling them)
|
||||
sub function_sql_quote { "sql_quote($_[1])" }
|
||||
# escape characters special to regular expressions
|
||||
sub function_requote { "requote($_[1])" }
|
||||
# encode URL parameter
|
||||
sub function_urlencode { shift; "URI::Escape::uri_escape(".join(",",@_).")" }
|
||||
# decode URL parameter
|
||||
sub function_urldecode { shift; "URI::Escape::uri_unescape(".join(",",@_).")" }
|
||||
# replace regexp: replace(<regex>, <replacement>, <subject>)
|
||||
sub function_replace { "regex_replace($_[1], $_[2], $_[3])" }
|
||||
# replace substrings
|
||||
sub function_str_replace { "str_replace($_[1], $_[2], $_[3])" }
|
||||
# character length of string
|
||||
sub function_strlen { "strlen($_[1])" }
|
||||
# substring
|
||||
sub function_substr { shift; "substr(".join(",", @_).")" }
|
||||
# remove starting and ending whitespace
|
||||
sub function_trim { shift; "trim($_[0])" }
|
||||
# splice $2 with regexp $1, optionally maximum to $3 parts
|
||||
sub function_split { shift; "split(".join(",", @_).")" }
|
||||
# replace & < > " ' with HTML entities
|
||||
sub function_html { "htmlspecialchars($_[1])" }
|
||||
# remove HTML tags
|
||||
sub function_strip { "strip_tags($_[1])" }
|
||||
# remove "unsafe" HTML tags
|
||||
sub function_strip_unsafe { "strip_unsafe_tags($_[1])" }
|
||||
# replace \n with <br />
|
||||
sub function_nl2br { "regex_replace(qr/\\n/s, '<br />', $_[1])" }
|
||||
# concatenate strings
|
||||
sub function_concat { fmop('.', @_) }
|
||||
# join strings with delimiter specified as the first argument; expands all passed arrays
|
||||
sub function_join { fearr('join', 1, @_) }
|
||||
# replace $1, $2 etc with passed arguments
|
||||
sub function_subst { fearr('exec_subst', 1, @_) }
|
||||
# sprintf
|
||||
sub function_sprintf { fearr('sprintf', 1, @_) }
|
||||
# strftime
|
||||
sub function_strftime
|
||||
{
|
||||
my $self = shift;
|
||||
my ($fmt, $date, $time) = @_;
|
||||
$date = "($date).' '.($time)" if $time;
|
||||
$date = "POSIX::strftime($date, localtime(timestamp($date)))";
|
||||
$date = "utf8on($date)" if $self->{use_utf8};
|
||||
return $date;
|
||||
}
|
||||
# limit $1 with $2 chars on whitespace boundary and add $3 (or '...' by default) if it is longer
|
||||
sub function_strlimit { shift; "strlimit(".join(",", @_).")" }
|
||||
# select one of 3 russian plural forms based on first numeric argument: plural_ru($number, $one, $few, $many)
|
||||
sub function_plural_ru { shift; "plural_ru(".join(",", @_).")" }
|
||||
|
||||
## Arrays and hashes
|
||||
|
||||
# create a hash
|
||||
sub function_hash { shift; @_ == 1 ? "{ \@{ $_[0] } }" : "{" . join(",", @_) . "}"; }
|
||||
# hash keys
|
||||
sub function_keys { '[ keys(%{'.$_[1].'}) ]'; }
|
||||
# hash values
|
||||
sub function_values { '[ values(%{'.$_[1].'}) ]'; }
|
||||
# sort array
|
||||
sub function_sort { '[ '.fearr('sort', 0, @_).' ]'; }
|
||||
# extract [ { key => <key>, value => <value> }, ... ] pairs from first hash argument
|
||||
sub function_pairs { "exec_pairs($_[1])" }
|
||||
# create an array
|
||||
sub function_array { shift; "[" . join(",", @_) . "]"; }
|
||||
# create a numeric range array
|
||||
sub function_range { "[ $_[1] .. $_[2] ]" }
|
||||
# check if the argument is an array
|
||||
sub function_is_array { "exec_is_array($_[1])" }
|
||||
# count array (not hash) elements
|
||||
sub function_count { "(ref($_[1]) && $_[1] =~ /ARRAY/so ? scalar(\@{ $_[1] }) : 0)" }
|
||||
# extract a contiguous slice of array
|
||||
sub function_array_slice { shift; "array_slice(" . join(",", @_) . ")"; }
|
||||
# extract a regular slice of array
|
||||
sub function_array_div { shift; "array_div(" . join(",", @_) . ")"; }
|
||||
# get array or hash element using a variable key (i.e. get(iteration.array, rand(5)))
|
||||
sub function_get { shift; "exec_get(" . join(",", @_) . ")"; }
|
||||
# same only for hash
|
||||
sub function_hget { "($_[1])->\{$_[2]}" }
|
||||
# same only for array
|
||||
sub function_aget { "($_[1])->\[$_[2]]" }
|
||||
# set first argument to second (first argument must be an "lvalue")
|
||||
sub function_set { "scalar(($_[1] = $_[2]), '')" }
|
||||
# merge arrays into one
|
||||
sub function_array_merge { shift; '[@{'.join('},@{',@_).'}]' }
|
||||
# extract first argument of an array
|
||||
sub function_shift { "shift(\@{$_[1]})"; }
|
||||
# extract last argument of an array
|
||||
sub function_pop { "pop(\@{$_[1]})"; }
|
||||
# insert into beginning of an array
|
||||
sub function_unshift { shift; "unshift(\@{".shift(@_)."}, ".join(",", @_).")"; }
|
||||
# insert into end of an array
|
||||
sub function_push { shift; "push(\@{".shift(@_)."}, ".join(",", @_).")"; }
|
||||
|
||||
## Misc
|
||||
|
||||
# explicitly ignore expression result (like void() in javascript)
|
||||
sub function_void { "scalar(($_[1]), '')" }
|
||||
# dump variable
|
||||
sub function_dump { shift; "exec_dump(" . join(",", @_) . ")" }
|
||||
# encode into JSON
|
||||
sub function_json { "encode_json($_[1])" }
|
||||
# return the value as is, to ignore automatic escaping of "unsafe" HTML
|
||||
sub function_raw { $_[1] }
|
||||
# call object method using variable name and inline arguments
|
||||
sub function_call
|
||||
{
|
||||
my $self = shift;
|
||||
my $obj = shift;
|
||||
my $method = shift;
|
||||
return "map({ ($obj)->\$_(".join(",", @_).") } $method)";
|
||||
}
|
||||
# call object method using variable name and array arguments
|
||||
sub function_call_array
|
||||
{
|
||||
my ($self, $obj, $method, $args) = @_;
|
||||
return "map({ ($obj)->\$_(\@\{$args}) } $method)";
|
||||
}
|
||||
|
||||
# apply the function to each array element
|
||||
sub function_map
|
||||
{
|
||||
my $self = shift;
|
||||
my $fn = shift;
|
||||
if ($fn =~ /^[\"\'](\w+)[\"\']$/so)
|
||||
{
|
||||
return '(map { '.$self->compile_function($1, '$_').' } (@{'.join('}, @{', @_).'}))';
|
||||
}
|
||||
else
|
||||
{
|
||||
$self->{lexer}->warn("Non-constant function: unimplemented");
|
||||
}
|
||||
}
|
||||
|
||||
## Template inclusion
|
||||
|
||||
# Include another template: parse('file.tpl'[, args])
|
||||
sub function_parse
|
||||
{
|
||||
my $self = shift;
|
||||
my $file = shift;
|
||||
my $args = @_ > 1 ? { @_ } : $_[0];
|
||||
return "\$self->{template}->parse_discard($file, undef, 'main', $args)";
|
||||
}
|
||||
|
||||
# Run block from current template: exec('block'[, args])
|
||||
sub function_exec
|
||||
{
|
||||
my $self = shift;
|
||||
my $block = shift;
|
||||
my $args = @_ > 1 ? { @_ } : $_[0];
|
||||
return "\$self->{template}->parse_discard(\$FILENAME, undef, $block, $args)";
|
||||
}
|
||||
|
||||
# Run block from another template: exec_from('file.tpl', 'block'[, args])
|
||||
sub function_exec_from
|
||||
{
|
||||
my $self = shift;
|
||||
my $file = shift;
|
||||
my $block = shift;
|
||||
my $args = @_ > 1 ? { @_ } : $_[0];
|
||||
return "\$self->{template}->parse_discard($file, undef, $block, $args)";
|
||||
}
|
||||
|
||||
# (Not recommended, but possible)
|
||||
# Parse string as a template: parse('code'[, args])
|
||||
sub function_parse
|
||||
{
|
||||
my $self = shift;
|
||||
my $code = shift;
|
||||
my $args = @_ > 1 ? { @_ } : $_[0];
|
||||
return "\$self->{template}->parse_discard(undef, $code, 'main', $args)";
|
||||
}
|
||||
|
||||
# (Highly not recommended, but still possible)
|
||||
# Parse string as a template and run a named block from it: parse('code', 'block'[, args])
|
||||
sub function_parse
|
||||
{
|
||||
my $self = shift;
|
||||
my $code = shift;
|
||||
my $block = shift;
|
||||
my $args = @_ > 1 ? { @_ } : $_[0];
|
||||
return "\$self->{template}->parse_discard(undef, $code, $block, $args)";
|
||||
}
|
||||
|
||||
1;
|
||||
|
|
44
template.yp
44
template.yp
|
@ -68,7 +68,7 @@
|
|||
%%
|
||||
|
||||
template: chunks {
|
||||
$_[0]->{template}->{st}->{functions}->{main}->{body} = "sub fn_main() {\nmy \$stack = [];\nmy \$t = '';\n".$_[1]."\nreturn \$t;\n}\n";
|
||||
$_[0]->{functions}->{main}->{body} = "sub fn_main {\nmy \$stack = [];\nmy \$t = '';\n".$_[1]."\nreturn \$t;\n}\n";
|
||||
'';
|
||||
}
|
||||
;
|
||||
|
@ -86,14 +86,14 @@ chunk: literal {
|
|||
$_[2];
|
||||
}
|
||||
| '{{' exp '}}' {
|
||||
'$t .= ' . ($_[2][1] || !$_[0]->{template}->{options}->{auto_escape} ? $_[2][0] : $_[0]->{template}->compile_function($_[0]->{template}->{options}->{auto_escape}, [ $_[2] ])->[0]) . ";\n";
|
||||
'$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]->{template}->{options}->{auto_escape} ? $_[1][0] : $_[0]->{template}->compile_function($_[0]->{template}->{options}->{auto_escape}, [ $_[1] ])->[0]) . ";\n";
|
||||
'$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' {
|
||||
|
@ -127,24 +127,22 @@ c_set: 'SET' varref '=' exp {
|
|||
"push \@\$stack, \$t;\n\$t = '';\n" . $_[4] . $_[2][0] . " = \$t;\n\$t = pop(\@\$stack);\n";
|
||||
}
|
||||
;
|
||||
c_fn: fn name '(' arglist ')' '=' exp {
|
||||
$_[0]->{template}->{st}->{functions}->{$_[2]} = {
|
||||
'name' => $_[2],
|
||||
'args' => $_[4],
|
||||
'body' => 'sub fn_'.$_[2]." () {\nreturn ".$_[7].";\n}\n",
|
||||
#'line' => $line, Ой, я чо - аргументы не юзаю?
|
||||
#'pos' => $pos,
|
||||
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 name '(' arglist ')' '-->' chunks '<!--' 'END' {
|
||||
$_[0]->{template}->{st}->{functions}->{$_[2]} = {
|
||||
'name' => $_[2],
|
||||
'args' => $_[4],
|
||||
'body' => 'sub fn_'.$_[2]." () {\nmy \$stack = [];\nmy \$t = '';\n".$_[7]."\nreturn \$t;\n}\n",
|
||||
#'line' => $line,
|
||||
#'pos' => $pos,
|
||||
};
|
||||
| fn_def '-->' chunks '<!--' 'END' {
|
||||
$_[1]->{body} .= " {\nmy \$stack = [];\nmy \$t = '';\n".$_[3]."\nreturn \$t;\n}\n";
|
||||
'';
|
||||
}
|
||||
;
|
||||
|
@ -155,7 +153,7 @@ c_for: for varref '=' exp '-->' chunks '<!--' 'END' {
|
|||
#{
|
||||
my $varref_index = substr($varref[0], 0, -1) . ".'_index'}";
|
||||
"push \@\$stack, ".$varref[0].", ".$varref_index.", 0;
|
||||
foreach my \$item (array1($exp[0])) {
|
||||
foreach my \$item (array_items($exp[0])) {
|
||||
".$varref[0]." = \$item;
|
||||
".$varref_index." = \$stack[count(\$stack)-1]++;
|
||||
".$cs."}
|
||||
|
@ -249,16 +247,16 @@ nonbrace: '{' hash '}' {
|
|||
| literal
|
||||
| varref
|
||||
| name '(' ')' {
|
||||
$_[0]->{template}->compile_function($_[1], []);
|
||||
$_[0]->{compiler}->compile_function($_[1], []);
|
||||
}
|
||||
| name '(' list ')' {
|
||||
$_[0]->{template}->compile_function($_[1], $_[3]);
|
||||
$_[0]->{compiler}->compile_function($_[1], $_[3]);
|
||||
}
|
||||
| name '(' gthash ')' {
|
||||
[ "\$self->{parent}->call_block('".addcslashes($_[1], "'\\")."', { ".$_[3]." }, '".addcslashes($_[0]->{__lexer}->errorinfo(), "'\\")."')", 1 ];
|
||||
[ "\$self->{template}->call_block('".addcslashes($_[1], "'\\")."', { ".$_[3]." }, '".addcslashes($_[0]->{lexer}->errorinfo(), "'\\")."')", 1 ];
|
||||
}
|
||||
| name nonbrace {
|
||||
$_[0]->{template}->compile_function($_[1], [ $_[3] ]);
|
||||
$_[0]->{compiler}->compile_function($_[1], [ $_[3] ]);
|
||||
}
|
||||
;
|
||||
list: exp {
|
||||
|
|
Loading…
Reference in New Issue