summaryrefslogblamecommitdiffstats
path: root/lib/Text/Handlebars/Compiler.pm
blob: 0282fffc513e135892ee0c3d1501afca589f2fd6 (plain) (tree)
1
2
3
4
5
6
7
8
9
10
11




                                   

              



                            

                                                      
                          


                     





                                                                     

 





                                                
                                                         







                                               
                                                           

 















                                                      
                                           
 
                                   
 
                                     





                                                  




                                               



                       
                              

                                                                   


                                   
                                                                         
              
                                       



          










                                                  


















                                                       
                                     




                                         
                                      
                                                     
                            
                                              
                         
                                                         






                                                                                   
                                          











































                                                         































                                                                                       

















                                                                

















































                                                                   










                                                          









                                             









                                             
































                                                          


                                  









                  
  
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;