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) = @_; my $lvar_id = $self->lvar_id; local $self->{lvar_id} = $self->lvar_use(1); my $lvar = $node->clone(arity => 'lvar', id => $lvar_id); return ( $self->compile_ast( $self->save_lvar($lvar_id, $self->find_file($node->first->clone)), ), $self->compile_ast( $self->make_ternary( $lvar->clone, $node->clone( arity => 'include', id => 'include', first => $lvar->clone, ), $self->literal(''), ), ), ); } sub find_file { my $self = shift; my ($filename) = @_; return $filename->clone( arity => 'find_file', first => $filename, ); } sub _generate_find_file { my $self = shift; my ($node) = @_; my $filename = $node->first; my $with_suffix = $self->parser->symbol('~')->clone( arity => 'binary', first => $filename->clone, second => $self->call($node, '(suffix)'), ); return ( $self->compile_ast( $self->make_ternary( $self->call($node, '(find_file)', $filename->clone), $filename->clone, $self->make_ternary( $self->call($node, '(find_file)', $with_suffix->clone), $with_suffix->clone, $self->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->literal('')), ), is_block_helper => 1, ), ); } my $iterations = $self->make_ternary( $self->is_falsy($name->clone), $self->make_array($self->literal(1)), $self->make_ternary( $self->is_array_ref($name->clone), $name->clone, $self->make_array($self->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->new_vars($name->clone, $self->iterator_index), ], second => [ $block{if}{body} ], ), ), ]; my $var = $name->clone(arity => 'variable'); return $self->compile_ast( $self->make_ternary( $self->is_code_ref($var->clone), $self->run_code( $var->clone, $block{if}{raw_text}->clone, $block{if}{open_tag}->clone, $block{if}{close_tag}->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) ); # render_string can't be constant folded, because it depends on the # current vars if ($Text::Xslate::Compiler::OPTIMIZE and $self->_code_is_literal(@code) && $node->id ne 'render_string') { $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 builtin_is_hash_ref is_code_ref render_string) ); 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 _generate_run_code { my $self = shift; my ($node) = @_; my $to_render = $node->clone(arity => 'call'); if ($node->third) { my ($open_tag, $close_tag) = @{ $node->third }; $to_render = $self->make_ternary( $self->parser->symbol('==')->clone( arity => 'binary', first => $close_tag->clone, second => $self->literal('}}'), ), $to_render, $self->join('{{= ', $open_tag, ' ', $close_tag, ' =}}', $to_render) ); } my $render_string = $self->parser->symbol('(render_string)')->clone( id => 'render_string', arity => 'unary', first => $to_render, ); return $self->compile_ast($render_string); } sub _generate_new_vars { my $self = shift; my ($node) = @_; my ($vars, $value, $i) = ($node->first, $node->second, $node->third); my $value_at_index = $value->clone( arity => 'field', first => $value->clone, second => $i->clone, ); my $lvar_id = $self->lvar_id; local $self->{lvar_id} = $self->lvar_use(1); my @code; push @code, $self->compile_ast($value); push @code, $self->opcode('save_to_lvar', $lvar_id); my $lvar_value = $value->clone(arity => 'lvar', id => $lvar_id); push @code, $self->compile_ast( $self->make_ternary( $self->is_array_ref($lvar_value->clone), $self->save_lvar( $lvar_id, $self->make_ternary( $self->is_hash_ref($value_at_index->clone), $self->merge_hash( $self->make_hash( $self->literal('.'), $value_at_index->clone, ), $value_at_index->clone, ), $self->make_hash( $self->literal('.'), $value_at_index->clone, ), ), ), ), ); push @code, $self->compile_ast( $self->save_lvar( $lvar_id, $self->make_ternary( $self->is_hash_ref($lvar_value->clone), $self->merge_hash( $self->make_hash( $self->literal('@index'), $i->clone, ), $vars->clone, $lvar_value->clone, $self->make_hash( $self->literal('..'), $vars->clone, ), ), $vars->clone, ), ), ); push @code, $self->opcode('load_lvar', $lvar_id); return @code; } sub _generate_lvar { my $self = shift; my ($node) = @_; return ( $self->opcode('load_lvar', $node->id), ); } sub _generate_save_lvar { my $self = shift; my ($node) = @_; return ( $self->compile_ast($node->first), $self->opcode('save_to_lvar', $node->id), ); } sub _generate_merge_hash { my $self = shift; my ($node) = @_; my $lvar_id = $self->lvar_id; local $self->{lvar_id} = $self->lvar_use(1); return ( $self->compile_ast($node->first), $self->opcode('save_to_lvar', $lvar_id), $self->compile_ast($node->second), $self->opcode('move_to_sb'), $self->opcode('load_lvar', $lvar_id), $self->opcode('merge_hash'), ); } sub join { my $self = shift; my (@args) = @_; @args = map { $self->literalize($_) } @args; my $joined = shift @args; for my $arg (@args) { $joined = $self->parser->symbol('~')->clone( arity => 'binary', first => $joined, second => $arg, ); } return $joined; } sub literalize { my $self = shift; my ($val) = @_; return $val->clone if blessed($val); return $self->literal($val); } 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->is_code_ref($var->clone), $self->run_code($var->clone), $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 is_hash_ref { my $self = shift; my ($var) = @_; return $self->parser->symbol('(is_hash_ref)')->clone( id => 'builtin_is_hash_ref', arity => 'unary', first => $var, ); } sub is_code_ref { my $self = shift; my ($var) = @_; return $self->parser->symbol('(is_code_ref)')->clone( id => 'is_code_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, ); } sub run_code { my $self = shift; my ($code, $raw_text, $open_tag, $close_tag) = @_; return $self->parser->symbol('(run_code)')->clone( arity => 'run_code', first => $code, (@_ > 1 ? (second => [ $raw_text ], third => [ $open_tag, $close_tag ]) : (second => [])), ); } sub new_vars { my $self = shift; my ($value, $i) = @_; return $value->clone( arity => 'new_vars', first => $self->vars, second => $value, third => $i, ); } sub save_lvar { my $self = shift; my ($id, $value) = @_; return $value->clone( arity => 'save_lvar', id => $id, first => $value, ); } sub merge_hash { my $self = shift; my (@hashes) = @_; my $merged = shift @hashes; for my $hash (@hashes) { $merged = $self->merge_single_hash($merged, $hash); } return $merged; } sub merge_single_hash { my $self = shift; my ($left, $right) = @_; return $left->clone( arity => 'merge_hash', first => $left, second => $right, ); } sub literal { shift->parser->literal(@_) } __PACKAGE__->meta->make_immutable; no Any::Moose; =for Pod::Coverage call check_lambda define_helper iterator_index make_ternary vars =cut 1;