Skip to content

Commit

Permalink
add merge_hash opcode
Browse files Browse the repository at this point in the history
  • Loading branch information
doy committed Oct 24, 2012
1 parent 58815c0 commit 56b1c71
Show file tree
Hide file tree
Showing 9 changed files with 175 additions and 34 deletions.
4 changes: 1 addition & 3 deletions lib/Text/Xslate.pm
Expand Up @@ -230,9 +230,7 @@ sub new {

sub _merge_hash {
my($self, $base, $add) = @_;
foreach my $name(keys %{$add}) {
$base->{$name} = $add->{$name};
}
%$base = %{ Text::Xslate::Util::merge_hash($base, $add || {}) };
return;
}

Expand Down
2 changes: 2 additions & 0 deletions lib/Text/Xslate/PP.pm
Expand Up @@ -348,6 +348,8 @@ sub _assemble {
sub is_array_ref { ref($_[0]) eq 'ARRAY' }
sub is_hash_ref { ref($_[0]) eq 'HASH' }
sub is_code_ref { ref($_[0]) eq 'CODE' }

sub merge_hash { +{ %{ $_[0] }, %{ $_[1] } } }
}

#
Expand Down
5 changes: 5 additions & 0 deletions lib/Text/Xslate/PP/Opcode.pm
Expand Up @@ -542,6 +542,11 @@ sub op_make_hash {
goto $_[0]->{ code }->[ ++$_[0]->{ pc } ]->{ exec_code };
}

sub op_merge_hash {
$_[0]->{sa} = Text::Xslate::Util::merge_hash($_[0]->{sa}, $_[0]->{sb});
goto $_[0]->{ code }->[ ++$_[0]->{ pc } ]->{ exec_code };
}


sub op_enter {
push @{$_[0]->{save_local_stack} ||= []}, delete $_[0]->{local_stack};
Expand Down
1 change: 1 addition & 0 deletions lib/Text/Xslate/Util.pm
Expand Up @@ -55,6 +55,7 @@ sub unmark_raw; # XS
sub html_escape; # XS
sub uri_escape; # XS
sub escaped_string; *escaped_string = \&mark_raw;
sub merge_hash; # XS

sub html_builder (&){
my($code_ref) = @_;
Expand Down
48 changes: 48 additions & 0 deletions src/Text-Xslate.xs
Expand Up @@ -137,6 +137,47 @@ tx_sv_is_code_ref(pTHX_ SV* const sv) {
return SvROK(sv) && SvTYPE(SvRV(sv)) == SVt_PVCV && !SvOBJECT(SvRV(sv));
}

SV*
tx_merge_hash(pTHX_ tx_state_t* const st, SV* base, SV* value) {
SV* const retval = newSV(0);
HV* const hv = (HV*)SvRV(base);
HV* const result = newHVhv(hv);
SV* const resultref = newRV_noinc((SV*)result);
HE* he;
HV* m;

if(!tx_sv_is_hash_ref(aTHX_ value)) {
if (st) {
tx_error(aTHX_ st, "Merging value is not a HASH reference");
}
else {
die("Merging value is not a HASH reference");
}
SvREFCNT_dec(resultref);
return retval;
}

m = (HV*)SvRV(value);

ENTER;
SAVETMPS;
sv_2mortal(resultref);

hv_iterinit(m);
while((he = hv_iternext(m))) {
(void)hv_store_ent(result,
hv_iterkeysv(he),
newSVsv(hv_iterval(hv, he)),
0U);
}

sv_setsv(retval, resultref);
FREETMPS;
LEAVE;

return retval;
}

STATIC_INLINE bool
tx_str_is_raw(pTHX_ pMY_CXT_ SV* const sv); /* doesn't handle magics */

Expand Down Expand Up @@ -1766,6 +1807,13 @@ CODE:
ST(0) = boolSV( tx_sv_is_code_ref(aTHX_ sv));
}

void
merge_hash(SV* base, SV* value)
CODE:
{
ST(0) = sv_2mortal(tx_merge_hash(aTHX_ NULL, base, value));
}

MODULE = Text::Xslate PACKAGE = Text::Xslate::Type::Raw

BOOT:
Expand Down
32 changes: 1 addition & 31 deletions src/xslate_methods.xs
Expand Up @@ -373,37 +373,7 @@ TXBM(hash, kv) {
}

TXBM(hash, merge) {
HV* const hv = (HV*)SvRV(*MARK);
SV* const value = *(++MARK);
HV* const result = newHVhv(hv);
SV* const resultref = newRV_noinc((SV*)result);
HE* he;
HV* m;

if(!tx_sv_is_hash_ref(aTHX_ value)) {
tx_error(aTHX_ st, "Merging value is not a HASH reference");
sv_setsv(retval, &PL_sv_undef);
SvREFCNT_dec(resultref);
return;
}

m = (HV*)SvRV(value);

ENTER;
SAVETMPS;
sv_2mortal(resultref);

hv_iterinit(m);
while((he = hv_iternext(m))) {
(void)hv_store_ent(result,
hv_iterkeysv(he),
newSVsv(hv_iterval(hv, he)),
0U);
}

sv_setsv(retval, resultref);
FREETMPS;
LEAVE;
sv_setsv(retval, tx_merge_hash(st, *MARK, *(MARK + 1)));
}

static const tx_builtin_method_t tx_builtin_method[] = {
Expand Down
9 changes: 9 additions & 0 deletions src/xslate_opcode.inc
Expand Up @@ -478,6 +478,15 @@ TXC(is_code_ref) {
TX_RETURN_NEXT();
}

TXC(merge_hash) {
SV* const base = TX_st_sa;
SV* const value = TX_st_sb;
SvGETMAGIC(base);
SvGETMAGIC(value);
TX_st_sa = tx_merge_hash(TX_st, base, value);
TX_RETURN_NEXT();
}

TXC(match) {
TX_st_sa = boolSV( tx_sv_match(aTHX_ TX_st_sb, TX_st_sa) );
TX_RETURN_NEXT();
Expand Down
105 changes: 105 additions & 0 deletions t/010_internals/036_merge_hash.t
@@ -0,0 +1,105 @@
#!perl
use strict;
use warnings;
use Test::More;

use Text::Xslate;

BEGIN {
package Text::Xslate::Syntax::Custom;
use Any::Moose;
extends 'Text::Xslate::Parser';

sub init_symbols {
my $self = shift;

$self->SUPER::init_symbols(@_);

$self->symbol('merge_hash')->set_nud($self->can('nud_merge_hash'));
}

sub nud_merge_hash {
my $self = shift;
my ($symbol) = @_;

$self->advance('(');
my $base = $self->expression(0);
$self->advance(',');
my $value = $self->expression(0);
$self->advance(')');

return $symbol->clone(
arity => 'merge_hash',
first => $base,
second => $value,
);
}

package Text::Xslate::Compiler::Custom;
use Any::Moose;
extends 'Text::Xslate::Compiler';

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'),
);
}

package Text::Xslate::Custom;
use base 'Text::Xslate';

sub options {
my $class = shift;

my $options = $class->SUPER::options(@_);

$options->{compiler} = 'Text::Xslate::Compiler::Custom';
$options->{syntax} = 'Custom';

return $options;
}
}

my $tx = Text::Xslate::Custom->new;

my @tests = (
[ ': [ merge_hash($a, $b).foo, $a.foo, $b.foo ].map(-> $elem { defined($elem) ? $elem : "undef" }).join(" ")', 'FOO FOO undef' ],
[ ': [ merge_hash($b, $a).foo, $a.foo, $b.foo ].map(-> $elem { defined($elem) ? $elem : "undef" }).join(" ")', 'FOO FOO undef' ],
[ ': [ merge_hash($a, $b).bar, $a.bar, $b.bar ].map(-> $elem { defined($elem) ? $elem : "undef" }).join(" ")', 'RAB BAR RAB' ],
[ ': [ merge_hash($b, $a).bar, $a.bar, $b.bar ].map(-> $elem { defined($elem) ? $elem : "undef" }).join(" ")', 'BAR BAR RAB' ],
[ ': [ merge_hash($a, $b).baz, $a.baz, $b.baz ].map(-> $elem { defined($elem) ? $elem : "undef" }).join(" ")', 'ZAB undef ZAB' ],
[ ': [ merge_hash($b, $a).baz, $a.baz, $b.baz ].map(-> $elem { defined($elem) ? $elem : "undef" }).join(" ")', 'ZAB undef ZAB' ],
);

for my $test (@tests) {
is(
$tx->render_string(
$test->[0],
{
a => {
foo => 'FOO',
bar => 'BAR',
},
b => {
bar => 'RAB',
baz => 'ZAB',
},
}
),
$test->[1],
$test->[0]
);
}

done_testing;
3 changes: 3 additions & 0 deletions xslate.h
Expand Up @@ -210,5 +210,8 @@ tx_sv_is_code_ref(pTHX_ SV* const sv);
SV*
tx_methodcall(pTHX_ tx_state_t* const st, SV* const method);

SV*
tx_merge_hash(pTHX_ tx_state_t* const st, SV* base, SV* value);

void
tx_register_builtin_methods(pTHX_ HV* const hv);

0 comments on commit 56b1c71

Please sign in to comment.