package Text::Handlebars::Compiler;
use Any::Moose;
extends 'Text::Xslate::Compiler';
use Try::Tiny;
has '+syntax' => (
default => 'Handlebars',
);
sub define_helper { shift->parser->define_helper(@_) }
sub _generate_block_body {
my $self = shift;
my ($node) = @_;
my @compiled = map { $self->compile_ast($_) } @{ $node->second };
unshift @compiled, $self->_localize_vars($node->first)
if $node->first;
return @compiled;
}
sub _generate_key {
my $self = shift;
my ($node) = @_;
my $var = $node->clone(arity => 'variable');
return $self->compile_ast($self->check_lambda($var));
}
sub _generate_key_field {
my $self = shift;
my ($node) = @_;
my $field = $node->clone(arity => 'field');
return $self->compile_ast($self->check_lambda($field));
}
sub _generate_call {
my $self = shift;
my ($node) = @_;
if ($node->is_helper) {
my @args;
my @hash;
for my $arg (@{ $node->second }) {
if ($arg->arity eq 'pair') {
push @hash, $arg->first, $arg->second;
}
else {
push @args, $arg;
}
}
my $hash = $self->make_hash(@hash);
unshift @args, $self->vars;
if ($node->is_block_helper) {
push @{ $node->first->second }, $hash;
$node->second(\@args);
}
else {
$node->second([ @args, $hash ]);
}
}
return $self->SUPER::_generate_call($node);
}
sub _generate_partial {
my $self = shift;
my ($node) = @_;
return $self->compile_ast(
$self->make_ternary(
$self->call($node, '(find_file)', $node->first->clone),
$node->clone(
arity => 'include',
id => 'include',
first => $self->call($node, '(find_file)', $node->first),
),
$self->parser->literal(''),
),
);
}
sub _generate_for {
my $self = shift;
my ($node) = @_;
my @opcodes = $self->SUPER::_generate_for(@_);
return (
@opcodes,
$self->opcode('nil'),
);
}
sub _generate_block {
my $self = shift;
my ($node) = @_;
my $name = $node->first;
my %block = %{ $node->second };
if ($name->arity eq 'call') {
return $self->compile_ast(
$name->clone(
first => $self->call(
$node,
'(make_block_helper)',
$name->first,
$block{if}{raw_text}->clone,
($block{else}
? $block{else}{raw_text}->clone
: $self->parser->literal('')),
),
is_block_helper => 1,
),
);
}
my $iterations = $self->make_ternary(
$self->is_falsy($name->clone),
$self->make_array($self->parser->literal(1)),
$self->make_ternary(
$self->is_array_ref($name->clone),
$name->clone,
$self->make_array($self->parser->literal(1)),
),
);
my $loop_var = $self->parser->symbol('(loop_var)')->clone(arity => 'variable');
my $body_block = [
$self->make_ternary(
$self->is_falsy($name->clone),
$name->clone(
arity => 'block_body',
first => undef,
second => [ $block{else}{body} ],
),
$name->clone(
arity => 'block_body',
first => [
$self->call(
$node,
'(new_vars_for)',
$self->vars,
$name->clone,
$self->iterator_index,
),
],
second => [ $block{if}{body} ],
),
),
];
my $var = $name->clone(arity => 'variable');
return $self->compile_ast(
$self->make_ternary(
$self->call($node, '(is_code)', $var->clone),
$self->call(
$node,
'(run_code)',
$var->clone,
$self->vars,
$block{if}{open_tag}->clone,
$block{if}{close_tag}->clone,
$block{if}{raw_text}->clone,
),
$self->parser->symbol('(for)')->clone(
arity => 'for',
first => $iterations,
second => [$loop_var],
third => $body_block,
),
),
);
}
sub _generate_unary {
my $self = shift;
my ($node) = @_;
# XXX copied from Text::Xslate::Compiler because it uses a hardcoded list
# of unary ops
if ($self->is_unary($node->id)) {
my @code = (
$self->compile_ast($node->first),
$self->opcode($node->id)
);
if( $Text::Xslate::Compiler::OPTIMIZE and $self->_code_is_literal($code[0]) ) {
$self->_fold_constants(\@code);
}
return @code;
}
else {
return $self->SUPER::_generate_unary(@_);
}
}
sub is_unary {
my $self = shift;
my ($id) = @_;
my %unary = (
map { $_ => 1 } qw(builtin_is_array_ref)
);
return $unary{$id};
}
sub _generate_array_length {
my $self = shift;
my ($node) = @_;
my $max_index = $self->parser->symbol('(max_index)')->clone(
id => 'max_index',
arity => 'unary',
first => $node->first,
);
return (
$self->compile_ast($max_index),
$self->opcode('move_to_sb'),
$self->opcode('literal', 1),
$self->opcode('add'),
);
}
sub call {
my $self = shift;
my ($node, $name, @args) = @_;
my $code = $self->parser->symbol('(name)')->clone(
arity => 'name',
id => $name,
line => $node->line,
);
return $self->parser->call($code, @args);
}
sub make_ternary {
my $self = shift;
my ($if, $then, $else) = @_;
return $self->parser->symbol('?:')->clone(
arity => 'if',
first => $if,
second => $then,
third => $else,
);
}
sub vars {
my $self = shift;
return $self->parser->symbol('(vars)')->clone(arity => 'vars');
}
sub iterator_index {
my $self = shift;
return $self->parser->symbol('(iterator)')->clone(
arity => 'iterator',
id => '$~(loop_var)',
first => $self->parser->symbol('(loop_var)')->clone,
),
}
sub check_lambda {
my $self = shift;
my ($var) = @_;
return $self->make_ternary(
$self->call($var, '(is_code)', $var->clone),
$self->call($var, '(run_code)', $var->clone, $self->vars),
$var,
);
}
sub is_array_ref {
my $self = shift;
my ($var) = @_;
return $self->parser->symbol('(is_array_ref)')->clone(
id => 'builtin_is_array_ref',
arity => 'unary',
first => $var,
);
}
sub make_array {
my $self = shift;
my (@contents) = @_;
return $self->parser->symbol('[')->clone(
arity => 'composer',
first => \@contents,
);
}
sub make_hash {
my $self = shift;
my (@contents) = @_;
return $self->parser->symbol('{')->clone(
arity => 'composer',
first => \@contents,
);
}
sub is_falsy {
my $self = shift;
my ($node) = @_;
return $self->not(
$self->make_ternary(
$self->is_array_ref($node->clone),
$self->array_length($node->clone),
$node
)
);
}
sub not {
my $self = shift;
my ($node) = @_;
return $self->parser->symbol('!')->clone(
arity => 'unary',
first => $node,
);
}
sub array_length {
my $self = shift;
my ($node) = @_;
return $self->parser->symbol('(array_length)')->clone(
arity => 'array_length',
first => $node,
);
}
__PACKAGE__->meta->make_immutable;
no Any::Moose;
=for Pod::Coverage
call
check_lambda
define_helper
iterator_index
make_ternary
vars
=cut
1;