$DEBUG
value_to_literal
is_int any_in
make_error
p
)
;
_DUMP_ASM
=>
scalar
(
$DEBUG
=~ /\b
dump
=asm \b/xms),
_DUMP_AST
=>
scalar
(
$DEBUG
=~ /\b
dump
=ast \b/xms),
_DUMP_GEN
=>
scalar
(
$DEBUG
=~ /\b
dump
=gen \b/xms),
_DUMP_CAS
=>
scalar
(
$DEBUG
=~ /\b
dump
=cascade \b/xms),
_OP_NAME
=> 0,
_OP_ARG
=> 1,
_OP_LINE
=> 2,
_OP_FILE
=> 3,
_OP_LABEL
=> 4,
_OP_COMMENT
=> 5,
_FOR_LOOP
=> 1,
_WHILE_LOOP
=> 2,
};
our
$OPTIMIZE
=
scalar
((
$DEBUG
=~ /\b optimize=(\d+) \b/xms)[0]);
if
(not
defined
$OPTIMIZE
) {
$OPTIMIZE
= 1;
}
our
@CARP_NOT
=
qw(Text::Xslate Text::Xslate::Parser)
;
{
our
%OPS
;
}
my
%binary
= (
'=='
=>
'eq'
,
'!='
=>
'ne'
,
'<'
=>
'lt'
,
'<='
=>
'le'
,
'>'
=>
'gt'
,
'>='
=>
'ge'
,
'~~'
=>
'match'
,
'<=>'
=>
'ncmp'
,
'cmp'
=>
'scmp'
,
'+'
=>
'add'
,
'-'
=>
'sub'
,
'*'
=>
'mul'
,
'/'
=>
'div'
,
'%'
=>
'mod'
,
'~'
=>
'concat'
,
'x'
=>
'repeat'
,
'+|'
=>
'bitor'
,
'+&'
=>
'bitand'
,
'+^'
=>
'bitxor'
,
'min'
=>
'lt'
,
'max'
=>
'gt'
,
'['
=>
'fetch_field'
,
);
my
%logical_binary
= (
'&&'
=>
'and'
,
'||'
=>
'or'
,
'//'
=>
'dor'
,
);
my
%unary
= (
'!'
=>
'not'
,
'+'
=>
'noop'
,
'-'
=>
'minus'
,
'+^'
=>
'bitneg'
,
'max_index'
=>
'max_index'
,
);
my
%goto_family
=
map
{
$_
=>
undef
}
qw(
for_iter
and
dand
or
dor
goto
)
;
my
%builtin
= (
'html_escape'
=> [
'builtin_html_escape'
,
\
&Text::Xslate::Util::html_escape
],
'uri_escape'
=> [
'builtin_uri_escape'
,
\
&Text::Xslate::Util::uri_escape
],
'mark_raw'
=> [
'builtin_mark_raw'
,
\
&Text::Xslate::Util::mark_raw
],
'unmark_raw'
=> [
'builtin_unmark_raw'
,
\
&Text::Xslate::Util::unmark_raw
],
'raw'
=> [
'builtin_mark_raw'
,
\
&Text::Xslate::Util::mark_raw
],
'html'
=> [
'builtin_html_escape'
,
\
&Text::Xslate::Util::html_escape
],
'uri'
=> [
'builtin_uri_escape'
,
\
&Text::Xslate::Util::uri_escape
],
'is_array_ref'
=> [
'builtin_is_array_ref'
,
\
&Text::Xslate::Util::is_array_ref
],
'is_hash_ref'
=> [
'builtin_is_hash_ref'
,
\
&Text::Xslate::Util::is_hash_ref
],
);
has
lvar_id
=> (
is
=>
'rw'
,
isa
=>
'Int'
,
init_arg
=>
undef
,
);
has
lvar
=> (
is
=>
'rw'
,
isa
=>
'HashRef[Int]'
,
init_arg
=>
undef
,
);
has
const
=> (
is
=>
'rw'
,
isa
=>
'ArrayRef'
,
init_arg
=>
undef
,
);
has
macro_table
=> (
is
=>
'rw'
,
isa
=>
'HashRef'
,
predicate
=>
'has_macro_table'
,
init_arg
=>
undef
,
);
has
engine
=> (
is
=>
'ro'
,
isa
=>
'Object'
,
required
=> 0,
weak_ref
=> 1,
);
has
dependencies
=> (
is
=>
'ro'
,
isa
=>
'ArrayRef'
,
init_arg
=>
undef
,
);
has
type
=> (
is
=>
'rw'
,
isa
=> enum([
qw(html xml text)
]),
default
=>
'html'
,
);
has
syntax
=> (
is
=>
'rw'
,
default
=>
'Kolon'
,
);
has
parser_option
=> (
is
=>
'rw'
,
isa
=>
'HashRef'
,
default
=>
sub
{ {} },
);
has
parser
=> (
is
=>
'rw'
,
isa
=>
'Object'
,
handles
=> [
qw(define_function)
],
lazy
=> 1,
builder
=>
'_build_parser'
,
init_arg
=>
undef
,
);
has
input_layer
=> (
is
=>
'ro'
,
default
=>
':utf8'
,
);
sub
_build_parser {
my
(
$self
) =
@_
;
my
$syntax
=
$self
->syntax;
if
(
ref
(
$syntax
)) {
return
$syntax
;
}
else
{
my
$parser_class
= Mouse::Util::load_first_existing_class(
"Text::Xslate::Syntax::"
.
$syntax
,
$syntax
,
);
return
$parser_class
->new(
%{
$self
->parser_option},
engine
=>
$self
->engine,
compiler
=>
$self
,
);
}
}
has
cascade
=> (
is
=>
'rw'
,
init_arg
=>
undef
,
);
has
[
qw(header footer macro)
] => (
is
=>
'rw'
,
isa
=>
'ArrayRef'
,
);
has
current_file
=> (
is
=>
'rw'
,
init_arg
=>
undef
,
);
has
file
=> (
is
=>
'rw'
,
init_arg
=>
undef
,
);
has
overridden_builtin
=> (
is
=>
'ro'
,
isa
=>
'HashRef'
,
default
=>
sub
{ +{} },
);
sub
lvar_use {
my
(
$self
,
$n
) =
@_
;
return
$self
->lvar_id +
$n
;
}
sub
filename {
my
(
$self
) =
@_
;
my
$file
=
$self
->file;
return
ref
(
$file
) ?
'<string>'
:
$file
;
}
sub
compile {
my
(
$self
,
$input
,
%args
) =
@_
;
local
$self
->{macro_table} = {};
local
$self
->{lvar_id } = 0;
local
$self
->{lvar} = {};
local
$self
->{const} = [];
local
$self
->{in_loop} = 0;
local
$self
->{dependencies} = [];
local
$self
->{cascade};
local
$self
->{header} =
$self
->{header};
local
$self
->{footer} =
$self
->{footer};
local
$self
->{macro} =
$self
->{macro};
local
$self
->{current_file} =
'<string>'
;
local
$self
->{file} =
$args
{file} || \
$input
;
if
(
my
$engine
=
$self
->engine) {
my
$ob
=
$self
->overridden_builtin;
Internals::SvREADONLY(
$ob
, 0);
foreach
my
$name
(
keys
%builtin
) {
my
$f
=
$engine
->{function}{
$name
};
$ob
->{
$name
} = (
$builtin
{
$name
}[1] !=
$f
) + 0;
}
Internals::SvREADONLY(
$ob
, 1);
}
my
$parser
=
$self
->parser;
my
$header
=
delete
$self
->{header};
my
$footer
=
delete
$self
->{footer};
my
$macro
=
delete
$self
->{macro};
if
(!
$args
{omit_augment}) {
if
(
$header
) {
substr
$input
, 0, 0,
$self
->_cat_files(
$header
);
}
if
(
$footer
) {
$input
.=
$self
->_cat_files(
$footer
);
}
}
if
(
$macro
) {
if
(!
grep
{
$_
eq
$self
->current_file }
@$macro
) {
substr
$input
, 0, 0,
$self
->_cat_files(
$macro
);
}
}
my
@code
;
{
my
$ast
=
$parser
->parse(
$input
,
%args
);
print
STDERR p(
$ast
)
if
_DUMP_AST;
@code
= (
$self
->opcode(
set_opinfo
=>
undef
,
file
=>
$self
->current_file,
line
=> 1),
$self
->compile_ast(
$ast
),
$self
->opcode(
'end'
),
);
}
my
$cascade
=
$self
->cascade;
if
(
defined
$cascade
) {
$self
->_process_cascade(
$cascade
, \
%args
, \
@code
);
}
push
@code
,
$self
->_flush_macro_table()
if
$self
->has_macro_table;
if
(
$OPTIMIZE
) {
$self
->_optimize_vmcode(\
@code
)
for
1 .. 3;
}
print
STDERR
"// "
,
$self
->filename,
"\n"
,
$self
->as_assembly(\
@code
,
scalar
(
$DEBUG
=~ /\b ix \b/xms))
if
_DUMP_ASM;
{
my
%uniq
;
push
@code
,
map
{ [
depend
=>
$_
] }
grep
{ !
ref
(
$_
) and !
$uniq
{
$_
}++ } @{
$self
->dependencies};
}
return
\
@code
;
}
sub
opcode {
my
(
$self
,
$name
,
$arg
,
%args
) =
@_
;
my
$symbol
=
$args
{symbol};
my
$file
=
$args
{file};
my
$label
=
$args
{label};
if
(not
defined
$file
) {
$file
=
$self
->filename;
if
(
defined
$file
and
$file
ne
$self
->current_file) {
$self
->current_file(
$file
);
}
else
{
$file
=
undef
;
}
}
return
[
$name
=>
$arg
,
$args
{line} || (
ref
$symbol
?
$symbol
->line :
undef
),
$file
,
$label
,
$args
{comment},
];
}
sub
push_expr {
my
(
$self
,
$node
) =
@_
;
my
$list_op
=
$node
->arity eq
'range'
;
my
@code
= (
$self
->compile_ast(
$node
));
if
(not
$list_op
) {
push
@code
,
$self
->opcode(
'push'
);
}
return
@code
;
}
sub
_cat_files {
my
(
$self
,
$files
) =
@_
;
my
$engine
=
$self
->engine ||
$self
->_error(
"No Xslate engine which header/footer requires"
);
my
$s
=
''
;
foreach
my
$file
(@{
$files
}) {
my
$fullpath
=
$engine
->find_file(
$file
)->{fullpath};
$s
.=
$engine
->slurp_template(
$self
->input_layer,
$fullpath
);
$self
->requires(
$fullpath
);
}
return
$s
;
}
our
$_lv
= -1;
sub
compile_ast {
my
(
$self
,
$ast
) =
@_
;
return
if
not
defined
$ast
;
local
$_lv
=
$_lv
+ 1
if
_DUMP_GEN;
my
@code
;
foreach
my
$node
(
ref
(
$ast
) eq
'ARRAY'
? @{
$ast
} :
$ast
) {
Scalar::Util::blessed(
$node
) or Carp::confess(
"[BUG] Not a node object: "
. p(
$node
));
printf
STDERR
"%s"
.
"generate %s (%s)\n"
,
"."
x
$_lv
,
$node
->arity,
$node
->id
if
_DUMP_GEN;
my
$generator
=
$self
->can(
'_generate_'
.
$node
->arity)
|| Carp::confess(
"[BUG] Unexpected node: "
. p(
$node
));
push
@code
,
$self
->
$generator
(
$node
);
}
return
@code
;
}
sub
_process_cascade {
my
(
$self
,
$cascade
,
$args
,
$main_code
) =
@_
;
printf
STDERR
"# cascade %s %s"
,
$self
->file,
$cascade
->
dump
if
_DUMP_CAS;
my
$engine
=
$self
->engine
||
$self
->_error(
"Cannot cascade templates without Xslate engine"
,
$cascade
);
my
(
$base_file
,
$base_code
);
my
$base
=
$cascade
->first;
my
@components
=
$cascade
->second
? (
map
{
$self
->_bare_to_file(
$_
) } @{
$cascade
->second})
: ();
my
$vars
=
$cascade
->third;
if
(
defined
$base
) {
$base_file
=
$self
->_bare_to_file(
$base
);
$base_code
=
$engine
->load_file(
$base_file
);
$self
->requires(
$engine
->find_file(
$base_file
)->{fullpath} );
}
else
{
$base_file
=
$args
->{file};
$base_code
=
$main_code
;
if
(
defined
$args
->{fullpath}) {
$self
->requires(
$args
->{fullpath} );
}
push
@{
$main_code
},
$self
->_flush_macro_table();
}
foreach
my
$cfile
(
@components
) {
my
$code
=
$engine
->load_file(
$cfile
);
my
$fullpath
=
$engine
->find_file(
$cfile
)->{fullpath};
my
$mtable
=
$self
->macro_table;
my
$macro
;
foreach
my
$c
(@{
$code
}) {
if
(
$c
->[_OP_NAME] eq
'macro_begin'
..
$c
->[_OP_NAME] eq
'macro_end'
) {
if
(
$c
->[_OP_NAME] eq
'macro_begin'
) {
$macro
= [];
$macro
= {
name
=>
$c
->[_OP_ARG],
line
=>
$c
->[_OP_LINE],
file
=>
$c
->[_OP_FILE],
body
=> [],
};
push
@{
$mtable
->{
$c
->[_OP_ARG]} ||= [] },
$macro
;
}
elsif
(
$c
->[_OP_NAME] eq
'macro_nargs'
) {
$macro
->{nargs} =
$c
->[_OP_ARG];
}
elsif
(
$c
->[_OP_NAME] eq
'macro_outer'
) {
$macro
->{outer} =
$c
->[_OP_ARG];
}
elsif
(
$c
->[_OP_NAME] eq
'macro_end'
) {
}
else
{
push
@{
$macro
->{body}},
$c
;
}
}
elsif
(
$c
->[_OP_NAME] eq
'depend'
) {
$self
->requires(
$c
->[_OP_ARG]);
}
}
$self
->requires(
$fullpath
);
$self
->_process_cascade_file(
$cfile
,
$base_code
);
}
if
(
defined
$base
) {
$self
->_process_cascade_file(
$base_file
,
$base_code
);
if
(
defined
$vars
) {
unshift
@{
$base_code
},
$self
->_localize_vars(
$vars
);
}
foreach
my
$c
(@{
$main_code
}) {
if
(
$c
->[_OP_NAME] eq
'print_raw_s'
&&
$c
->[_OP_ARG] =~ m{ [^ \t\r\n] }xms) {
Carp::carp(
"Xslate: Useless use of text '$c->[1]'"
);
}
}
@{
$main_code
} = @{
$base_code
};
}
else
{
return
;
}
}
sub
_process_cascade_file {
my
(
$self
,
$file
,
$base_code
) =
@_
;
printf
STDERR
"# cascade file %s\n"
, p(
$file
)
if
_DUMP_CAS;
my
$mtable
=
$self
->macro_table;
for
(
my
$i
= 0;
$i
< @{
$base_code
};
$i
++) {
my
$c
=
$base_code
->[
$i
];
if
(
$c
->[_OP_NAME] ne
'macro_begin'
) {
next
;
}
my
$name
=
$c
->[_OP_ARG];
$name
=~ s/\@.+$//;
printf
STDERR
"# macro %s\n"
,
$name
if
_DUMP_CAS;
if
(
exists
$mtable
->{
$name
}) {
my
$m
=
$mtable
->{
$name
};
if
(
ref
(
$m
) ne
'HASH'
) {
$self
->_error(
'[BUG] Unexpected macro structure: '
. p(
$m
) );
}
$self
->_error(
"Redefinition of macro/block $name in "
.
$file
.
" (you must use block modifiers to override macros/blocks)"
,
$m
->{line}
);
}
my
$before
=
delete
$mtable
->{
$name
.
'@before'
};
my
$around
=
delete
$mtable
->{
$name
.
'@around'
};
my
$after
=
delete
$mtable
->{
$name
.
'@after'
};
if
(
defined
$before
) {
my
$n
=
scalar
@{
$base_code
};
foreach
my
$m
(@{
$before
}) {
splice
@{
$base_code
},
$i
+1, 0, @{
$m
->{body}};
}
$i
+=
scalar
(@{
$base_code
}) -
$n
;
}
my
$macro_start
=
$i
+1;
$i
++
while
(
$base_code
->[
$i
][_OP_NAME] ne
'macro_end'
);
if
(
defined
$around
) {
my
@original
=
splice
@{
$base_code
},
$macro_start
, (
$i
-
$macro_start
);
$i
=
$macro_start
;
my
@body
;
foreach
my
$m
(@{
$around
}) {
push
@body
, @{
$m
->{body}};
}
for
(
my
$j
= 0;
$j
<
@body
;
$j
++) {
if
(
$body
[
$j
][_OP_NAME] eq
'super'
) {
splice
@body
,
$j
, 1,
@original
;
}
}
splice
@{
$base_code
},
$macro_start
, 0,
@body
;
$i
+=
scalar
(
@body
);
}
if
(
defined
$after
) {
foreach
my
$m
(@{
$after
}) {
splice
@{
$base_code
},
$i
, 0, @{
$m
->{body}};
}
}
}
return
;
}
sub
_flush_macro_table {
my
(
$self
) =
@_
;
my
$mtable
=
$self
->macro_table;
my
@code
;
foreach
my
$macros
(
values
%{
$mtable
}) {
foreach
my
$macro
(
ref
(
$macros
) eq
'ARRAY'
? @{
$macros
} :
$macros
) {
push
@code
,
$self
->opcode(
macro_begin
=>
$macro
->{name},
file
=>
$macro
->{file},
line
=>
$macro
->{line} );
push
@code
,
$self
->opcode(
macro_nargs
=>
$macro
->{nargs} )
if
$macro
->{nargs};
push
@code
,
$self
->opcode(
macro_outer
=>
$macro
->{outer} )
if
$macro
->{outer};
push
@code
, @{
$macro
->{body} },
$self
->opcode(
'macro_end'
);
}
}
%{
$mtable
} = ();
return
@code
;
}
sub
_generate_name {
my
(
$self
,
$node
) =
@_
;
my
$id
=
$node
->value;
if
(
defined
(
my
$lvar_id
=
$self
->lvar->{
$id
})) {
my
$code
=
$self
->const->[
$lvar_id
];
if
(
defined
$code
) {
return
@{
$code
};
}
else
{
return
$self
->opcode(
load_lvar
=>
$lvar_id
,
symbol
=>
$node
);
}
}
return
$self
->opcode(
fetch_symbol
=>
$id
,
line
=>
$node
->line );
}
sub
_generate_operator {
my
(
$self
,
$node
) =
@_
;
$self
->_error(
"Invalid expression"
,
$node
);
}
sub
_can_optimize_print {
my
(
$self
,
$name
,
$node
) =
@_
;
return
0
if
!
$OPTIMIZE
;
return
0
if
!(
$name
eq
'print'
or
$name
eq
'print_raw'
);
my
$maybe_name
=
$node
->first;
return
$node
->arity eq
'call'
&&
$maybe_name
->arity eq
'name'
&& @{
$node
->second} == 1
&& any_in(
$maybe_name
->id,
qw(raw mark_raw html)
)
&& !
$self
->overridden_builtin->{
$maybe_name
->id};
}
sub
_generate_print {
my
(
$self
,
$node
) =
@_
;
my
@code
;
my
$proc
=
$node
->id;
if
(
$proc
eq
'print'
and
$self
->type eq
'text'
) {
$proc
=
'print_raw'
;
}
foreach
my
$arg
(@{
$node
->first }){
if
(
$proc
eq
'print'
&&
$self
->overridden_builtin->{html_escape} ) {
push
@code
,
$self
->opcode(
'pushmark'
),
$self
->compile_ast(
$arg
),
$self
->opcode(
'push'
),
$self
->opcode(
'fetch_symbol'
=>
'html_escape'
),
$self
->opcode(
'funcall'
),
$self
->opcode(
'print_raw'
);
}
elsif
(
exists
$Text::Xslate::OPS
{
$proc
.
'_s'
}
&&
$arg
->arity eq
'literal'
){
push
@code
,
$self
->opcode(
$proc
.
'_s'
=>
$arg
->value,
line
=>
$arg
->line );
}
elsif
(
$self
->_can_optimize_print(
$proc
,
$arg
)){
my
$filter
=
$arg
->first;
my
$filter_name
=
$filter
->id;
my
$command
=
$builtin
{
$filter_name
}[0] eq
'builtin_mark_raw'
?
'print_raw'
:
'print'
;
push
@code
,
$self
->compile_ast(
$arg
->second->[0]),
$self
->opcode(
$command
=>
undef
,
symbol
=>
$filter
);
}
else
{
push
@code
,
$self
->compile_ast(
$arg
),
$self
->opcode(
$proc
=>
undef
,
line
=>
$node
->line );
}
}
if
(!
@code
) {
$self
->_error(
"$node requires at least one argument"
,
$node
);
}
return
@code
;
}
sub
_generate_include {
my
(
$self
,
$node
) =
@_
;
my
$file
=
$node
->first;
my
@code
= (
(
ref
(
$file
) eq
'ARRAY'
?
$self
->opcode(
literal
=>
$self
->_bare_to_file(
$file
) )
:
$self
->compile_ast(
$file
) ),
$self
->opcode(
$node
->
id
=>
undef
,
line
=>
$node
->line ),
);
if
(
defined
(
my
$vars
=
$node
->second)) {
@code
= (
$self
->opcode(
'enter'
),
$self
->_localize_vars(
$vars
),
@code
,
$self
->opcode(
'leave'
),
);
}
return
@code
;
}
sub
_bare_to_file {
my
(
$self
,
$file
) =
@_
;
if
(
ref
(
$file
) eq
'ARRAY'
) {
return
join
(
'/'
,
map
{
$_
->value } @{
$file
}) .
$self
->{engine}->{suffix};
}
elsif
(
$file
->arity eq
'literal'
) {
return
$file
->value;
}
else
{
$self
->_error(
"Expected a name or string literal"
,
$file
);
}
}
sub
_generate_cascade {
my
(
$self
,
$node
) =
@_
;
if
(
defined
$self
->cascade) {
$self
->_error(
"Cannot cascade twice in a template"
,
$node
);
}
$self
->cascade(
$node
);
return
;
}
sub
_compile_loop_block {
my
(
$self
,
$block
) =
@_
;
my
@block_code
=
$self
->compile_ast(
$block
);
foreach
my
$op
(
@block_code
) {
if
(any_in(
$op
->[_OP_NAME],
qw(pushmark loop_control)
)) {
unshift
@block_code
,
$self
->opcode(
'enter'
);
push
@block_code
,
$self
->opcode(
'leave'
);
last
;
}
}
foreach
my
$i
(1 .. (
@block_code
-1)) {
my
$op
=
$block_code
[
$i
];
if
(
$op
->[_OP_NAME] eq
'loop_control'
) {
my
$type
=
$op
->[_OP_ARG];
$op
->[_OP_NAME] =
'goto'
;
$op
->[_OP_ARG] = (
@block_code
-
$i
);
$op
->[_OP_ARG] += 1
if
$type
eq
'last'
;
}
}
return
@block_code
;
}
sub
_generate_for {
my
(
$self
,
$node
) =
@_
;
my
$expr
=
$node
->first;
my
$vars
=
$node
->second;
my
$block
=
$node
->third;
if
(@{
$vars
} != 1) {
$self
->_error(
"A for-loop requires single variable for each item"
,
$node
);
}
local
$self
->{lvar} = { %{
$self
->lvar} };
local
$self
->{const} = [ @{
$self
->const} ];
local
$self
->{in_loop} = _FOR_LOOP;
my
@code
=
$self
->compile_ast(
$expr
);
my
(
$iter_var
) = @{
$vars
};
my
$lvar_id
=
$self
->lvar_id;
my
$lvar_name
=
$iter_var
->id;
$self
->lvar->{
$lvar_name
} =
$lvar_id
;
$self
->lvar->{
'($_)'
} =
$lvar_id
;
push
@code
,
$self
->opcode(
for_start
=>
$lvar_id
,
symbol
=>
$iter_var
);
local
$self
->{lvar_id} =
$self
->lvar_use(3);
my
@block_code
=
$self
->_compile_loop_block(
$block
);
push
@code
,
$self
->opcode(
literal_i
=>
$lvar_id
,
symbol
=>
$iter_var
),
$self
->opcode(
for_iter
=>
scalar
(
@block_code
) + 2 ),
@block_code
,
$self
->opcode(
goto
=> -(
scalar
(
@block_code
) + 2),
comment
=>
"end for"
);
return
@code
;
}
sub
_generate_for_else {
my
(
$self
,
$node
) =
@_
;
my
$for_block
=
$node
->first;
my
$else_block
=
$node
->second;
my
@code
= (
$self
->compile_ast(
$for_block
),
);
my
@else
=
$self
->compile_ast(
$else_block
);
push
@code
, (
$self
->opcode(
or
=>
scalar
(
@else
) + 1,
comment
=>
'for-else'
),
@else
,
);
return
@code
;
}
sub
_generate_while {
my
(
$self
,
$node
) =
@_
;
my
$expr
=
$node
->first;
my
$vars
=
$node
->second;
my
$block
=
$node
->third;
if
(@{
$vars
} > 1) {
$self
->_error(
"A while-loop requires one or zero variable for each items"
,
$node
);
}
(
my
$cond_op
,
undef
,
$expr
) =
$self
->_prepare_cond_expr(
$expr
);
local
$self
->{lvar} = { %{
$self
->lvar} };
local
$self
->{const} = [ @{
$self
->const} ];
local
$self
->{in_loop} = _WHILE_LOOP;
my
@code
=
$self
->compile_ast(
$expr
);
my
(
$iter_var
) = @{
$vars
};
my
(
$lvar_id
,
$lvar_name
);
if
(@{
$vars
}) {
$lvar_id
=
$self
->lvar_id;
$lvar_name
=
$iter_var
->id;
$self
->lvar->{
$lvar_name
} =
$lvar_id
;
push
@code
,
$self
->opcode(
save_to_lvar
=>
$lvar_id
,
symbol
=>
$iter_var
);
}
local
$self
->{lvar_id} =
$self
->lvar_use(
scalar
@{
$vars
});
my
@block_code
=
$self
->_compile_loop_block(
$block
);
return
@code
,
$self
->opcode(
$cond_op
=>
scalar
(
@block_code
) + 2,
symbol
=>
$node
),
@block_code
,
$self
->opcode(
goto
=> -(
scalar
(
@block_code
) +
scalar
(
@code
) + 1),
comment
=>
"end while"
);
return
@code
;
}
sub
_generate_loop_control {
my
(
$self
,
$node
) =
@_
;
my
$type
=
$node
->id;
any_in(
$type
,
qw(last next)
)
or
$self
->_error(
"[BUG] Unknown loop control statement '$type'"
);
if
(not
$self
->{in_loop}) {
$self
->_error(
"Use of loop control statement ($type) outside of loops"
);
}
my
@cleanup
;
if
(
$self
->{in_loop} == _FOR_LOOP &&
$type
eq
'last'
) {
my
$lvar_id
=
$self
->lvar->{
'($_)'
};
defined
(
$lvar_id
)
or
$self
->_error(
'[BUG] Undefined loop iterator'
);
@cleanup
= (
$self
->opcode(
'nil'
,
undef
,
comment
=>
'to clean the loop context'
),
$self
->opcode(
save_to_lvar
=>
$lvar_id
+ 0),
$self
->opcode(
save_to_lvar
=>
$lvar_id
+ 1),
$self
->opcode(
save_to_lvar
=>
$lvar_id
+ 2),
$self
->opcode(
literal_i
=> 1 ),
);
}
return
$self
->opcode(
'leave'
),
@cleanup
,
$self
->opcode(
'loop_control'
=>
$type
,
comment
=>
$type
);
}
sub
_generate_proc {
my
(
$self
,
$node
) =
@_
;
my
$type
=
$node
->id;
my
$name
=
$node
->first->id;
my
@args
=
map
{
$_
->id } @{
$node
->second};
my
$block
=
$node
->third;
local
$self
->{lvar} = { %{
$self
->lvar} };
local
$self
->{const} = [ @{
$self
->const} ];
my
$lvar_used
=
$self
->lvar_id;
my
$arg_ix
= 0;
foreach
my
$arg
(
@args
) {
$self
->lvar->{
$arg
} =
$lvar_used
+
$arg_ix
++;
}
local
$self
->{lvar_id} =
$self
->lvar_use(
$arg_ix
);
my
$opinfo
=
$self
->opcode(
set_opinfo
=>
undef
,
file
=>
$self
->filename,
line
=>
$node
->line);
my
%macro
= (
name
=>
$name
,
nargs
=>
$arg_ix
,
body
=> [
$opinfo
,
$self
->compile_ast(
$block
) ],
line
=>
$opinfo
->[2],
file
=>
$opinfo
->[3],
outer
=>
$lvar_used
,
);
if
(any_in(
$type
,
qw(macro block)
)) {
if
(
exists
$self
->macro_table->{
$name
}) {
my
$m
=
$self
->macro_table->{
$name
};
if
(p(\
%macro
) ne p(
$m
)) {
$self
->_error(
"Redefinition of $type $name is forbidden"
,
$node
);
}
}
$self
->macro_table->{
$name
} = \
%macro
;
}
else
{
my
$fq_name
=
sprintf
'%s@%s'
,
$name
,
$type
;
$macro
{name} =
$fq_name
;
push
@{
$self
->macro_table->{
$fq_name
} ||= [] }, \
%macro
;
}
return
;
}
sub
_generate_lambda {
my
(
$self
,
$node
) =
@_
;
my
$macro
=
$node
->first;
$self
->compile_ast(
$macro
);
return
$self
->opcode(
fetch_symbol
=>
$macro
->first->id,
line
=>
$node
->line );
}
sub
_prepare_cond_expr {
my
(
$self
,
$expr
) =
@_
;
my
$t
=
"and"
;
my
$f
=
"or"
;
while
(
$expr
->id eq
'!'
) {
$expr
=
$expr
->first;
(
$t
,
$f
) = (
$f
,
$t
);
}
if
(
$expr
->is_logical and any_in(
$expr
->id,
qw(== !=)
)) {
my
$rhs
=
$expr
->second;
if
(
$rhs
->arity eq
"nil"
) {
substr
$t
, 0, 0,
'd'
;
substr
$f
, 0, 0,
'd'
;
if
(
$expr
->id eq
"=="
) {
(
$t
,
$f
) = (
$f
,
$t
);
}
$expr
=
$expr
->first;
}
}
return
(
$t
,
$f
,
$expr
);
}
sub
_generate_if {
my
(
$self
,
$node
) =
@_
;
my
$first
=
$node
->first;
my
$second
=
$node
->second;
my
$third
=
$node
->third;
my
(
$cond_true
,
$cond_false
,
$expr
) =
$self
->_prepare_cond_expr(
$first
);
local
$self
->{lvar} = { %{
$self
->lvar} };
local
$self
->{const} = [ @{
$self
->const} ];
my
@cond
=
$self
->compile_ast(
$expr
);
my
@then
=
do
{
local
$self
->{lvar} = { %{
$self
->lvar} };
local
$self
->{const} = [ @{
$self
->const} ];
$self
->compile_ast(
$second
);
};
my
@else
=
do
{
local
$self
->{lvar} = { %{
$self
->lvar} };
local
$self
->{const} = [ @{
$self
->const} ];
$self
->compile_ast(
$third
);
};
if
(
$OPTIMIZE
) {
if
(
$self
->_code_is_literal(
@cond
)) {
my
$value
=
$cond
[0][_OP_ARG];
if
(
$cond_true
eq
'and'
?
$value
: !
$value
) {
return
@then
;
}
else
{
return
@else
;
}
}
}
if
( (
@then
and
@else
) or !
$OPTIMIZE
) {
return
(
@cond
,
$self
->opcode(
$cond_true
=>
scalar
(
@then
) + 2,
comment
=>
$node
->id .
' (then)'
),
@then
,
$self
->opcode(
goto
=>
scalar
(
@else
) + 1,
comment
=>
$node
->id .
' (else)'
),
@else
,
);
}
elsif
(!
@else
) {
return
(
@cond
,
$self
->opcode(
$cond_true
=>
scalar
(
@then
) + 1,
comment
=>
$node
->id .
' (then/no-else)'
),
@then
,
);
}
else
{
return
(
@cond
,
$self
->opcode(
$cond_false
=>
scalar
(
@else
) + 1,
comment
=>
$node
->id .
' (else/no-then)'
),
@else
,
);
}
}
sub
_generate_given {
my
(
$self
,
$node
) =
@_
;
my
$expr
=
$node
->first;
my
$vars
=
$node
->second;
my
$block
=
$node
->third;
if
(@{
$vars
} > 1) {
$self
->_error(
"A given block requires one or zero variables"
,
$node
);
}
local
$self
->{lvar} = { %{
$self
->lvar} };
local
$self
->{const} = [ @{
$self
->const} ];
my
@code
=
$self
->compile_ast(
$expr
);
my
(
$lvar
) = @{
$vars
};
my
$lvar_id
=
$self
->lvar_id;
my
$lvar_name
=
$lvar
->id;
$self
->lvar->{
$lvar_name
} =
$lvar_id
;
local
$self
->{lvar_id} =
$self
->lvar_use(1);
push
@code
,
$self
->opcode(
save_to_lvar
=>
$lvar_id
,
symbol
=>
$lvar
),
$self
->compile_ast(
$block
);
return
@code
;
}
sub
_generate_variable {
my
(
$self
,
$node
) =
@_
;
if
(
defined
(
my
$lvar_id
=
$self
->lvar->{
$node
->value})) {
return
$self
->opcode(
load_lvar
=>
$lvar_id
,
symbol
=>
$node
);
}
else
{
my
$name
=
$self
->_variable_to_value(
$node
);
if
(
$name
=~ /~/) {
$self
->_error(
"Undefined iterator variable $node"
,
$node
);
}
return
$self
->opcode(
fetch_s
=>
$name
,
line
=>
$node
->line );
}
}
sub
_generate_super {
my
(
$self
,
$node
) =
@_
;
return
return
$self
->opcode(
super
=>
undef
,
symbol
=>
$node
);
}
sub
_generate_literal {
my
(
$self
,
$node
) =
@_
;
return
$self
->opcode(
literal
=>
$node
->value );
}
sub
_generate_nil {
my
(
$self
) =
@_
;
return
$self
->opcode(
'nil'
);
}
sub
_generate_vars {
my
(
$self
) =
@_
;
return
$self
->opcode(
'vars'
);
}
sub
_generate_composer {
my
(
$self
,
$node
) =
@_
;
my
$list
=
$node
->first;
my
$type
=
$node
->id eq
'{'
?
'make_hash'
:
'make_array'
;
return
$self
->opcode(
pushmark
=>
undef
,
comment
=>
$type
),
(
map
{
$self
->push_expr(
$_
) } @{
$list
}),
$self
->opcode(
$type
),
;
}
sub
_generate_unary {
my
(
$self
,
$node
) =
@_
;
my
$id
=
$node
->id;
if
(
exists
$unary
{
$id
}) {
my
@operand
=
$self
->compile_ast(
$node
->first);
my
@code
= (
@operand
,
$self
->opcode(
$unary
{
$id
} )
);
if
(
$OPTIMIZE
and
$self
->_code_is_literal(
@operand
) ) {
$self
->_fold_constants(\
@code
);
}
return
@code
;
}
else
{
$self
->_error(
"Unary operator $id is not implemented"
,
$node
);
}
}
sub
_generate_field {
my
(
$self
,
$node
) =
@_
;
my
@lhs
=
$self
->compile_ast(
$node
->first);
my
$field
=
$node
->second;
if
(
$field
->arity eq
"literal"
) {
return
@lhs
,
$self
->opcode(
fetch_field_s
=>
$field
->value );
}
else
{
local
$self
->{lvar_id} =
$self
->lvar_use(1);
my
@rhs
=
$self
->compile_ast(
$field
);
if
(
$OPTIMIZE
and
$self
->_code_is_literal(
@rhs
)) {
return
@lhs
,
$self
->opcode(
fetch_field_s
=>
$rhs
[0][1] );
}
return
@lhs
,
$self
->opcode(
save_to_lvar
=>
$self
->lvar_id ),
@rhs
,
$self
->opcode(
load_lvar_to_sb
=>
$self
->lvar_id ),
$self
->opcode(
'fetch_field'
),
;
}
}
sub
_generate_binary {
my
(
$self
,
$node
) =
@_
;
my
@lhs
=
$self
->compile_ast(
$node
->first);
my
$id
=
$node
->id;
if
(
exists
$binary
{
$id
}) {
local
$self
->{lvar_id} =
$self
->lvar_use(1);
my
@rhs
=
$self
->compile_ast(
$node
->second);
my
@code
= (
@lhs
,
$self
->opcode(
save_to_lvar
=>
$self
->lvar_id ),
@rhs
,
$self
->opcode(
load_lvar_to_sb
=>
$self
->lvar_id ),
$self
->opcode(
$binary
{
$id
} ),
);
if
(any_in(
$id
,
qw(min max)
)) {
local
$self
->{lvar_id} =
$self
->lvar_use(1);
splice
@code
, -1, 0,
$self
->opcode(
save_to_lvar
=>
$self
->lvar_id );
push
@code
,
$self
->opcode(
or
=> +2 ,
symbol
=>
$node
),
$self
->opcode(
load_lvar_to_sb
=>
$self
->lvar_id ),
$self
->opcode(
'move_from_sb'
),
}
if
(
$OPTIMIZE
) {
if
(
$self
->_code_is_literal(
@lhs
) and
$self
->_code_is_literal(
@rhs
) ){
$self
->_fold_constants(\
@code
);
}
}
return
@code
;
}
elsif
(
exists
$logical_binary
{
$id
}) {
my
@rhs
=
$self
->compile_ast(
$node
->second);
return
@lhs
,
$self
->opcode(
$logical_binary
{
$id
} =>
scalar
(
@rhs
) + 1,
symbol
=>
$node
),
@rhs
;
}
$self
->_error(
"Binary operator $id is not implemented"
,
$node
);
}
sub
_generate_range {
my
(
$self
,
$node
) =
@_
;
$self
->can_be_in_list_context
or
$self
->_error(
"Range operator must be in list context"
);
my
@lhs
=
$self
->compile_ast(
$node
->first);
local
$self
->{lvar_id} =
$self
->lvar_use(1);
my
@rhs
=
$self
->compile_ast(
$node
->second);
return
(
@lhs
,
$self
->opcode(
save_to_lvar
=>
$self
->lvar_id ),
@rhs
,
$self
->opcode(
load_lvar_to_sb
=>
$self
->lvar_id ),
$self
->opcode(
'range'
),
);
}
sub
_generate_methodcall {
my
(
$self
,
$node
) =
@_
;
my
$args
=
$node
->third;
my
$method
=
$node
->second->value;
return
(
$self
->opcode(
pushmark
=>
undef
,
comment
=>
$method
),
$self
->push_expr(
$node
->first),
(
map
{
$self
->push_expr(
$_
) } @{
$args
}),
$self
->opcode(
methodcall_s
=>
$method
,
line
=>
$node
->line ),
);
}
sub
_generate_call {
my
(
$self
,
$node
) =
@_
;
my
$callable
=
$node
->first;
my
$args
=
$node
->second;
if
(
my
$intern
=
$builtin
{
$callable
->id} and !
$self
->overridden_builtin->{
$callable
->id}) {
if
(@{
$args
} != 1) {
$self
->_error(
"Wrong number of arguments for $callable"
,
$node
);
}
return
$self
->compile_ast(
$args
->[0]),
[
$intern
->[0] =>
undef
,
$node
->line ];
}
return
(
$self
->opcode(
pushmark
=>
undef
,
comment
=>
$callable
->id ),
(
map
{
$self
->push_expr(
$_
) } @{
$args
}),
$self
->compile_ast(
$callable
),
$self
->opcode(
'funcall'
)
);
}
sub
_generate_iterator {
my
(
$self
,
$node
) =
@_
;
my
$item_var
=
$node
->first;
my
$lvar_id
=
$self
->lvar->{
$item_var
};
if
(!
defined
(
$lvar_id
)) {
$self
->_error(
"Refer to iterator $node, but $item_var is not defined"
,
$node
);
}
return
$self
->opcode(
load_lvar
=>
$lvar_id
+ 1,
symbol
=>
$node
,
);
}
sub
_generate_iterator_body {
my
(
$self
,
$node
) =
@_
;
my
$item_var
=
$node
->first;
my
$lvar_id
=
$self
->lvar->{
$item_var
};
if
(!
defined
(
$lvar_id
)) {
$self
->_error(
"Refer to iterator $node.body, but $item_var is not defined"
,
$node
);
}
return
$self
->opcode(
load_lvar
=>
$lvar_id
+ 2,
symbol
=>
$node
,
);
}
sub
_generate_assign {
my
(
$self
,
$node
) =
@_
;
my
$lhs
=
$node
->first;
my
$rhs
=
$node
->second;
my
$is_decl
=
$node
->third;
my
$lvar
=
$self
->lvar;
my
$lvar_name
=
$lhs
->id;
if
(
$node
->id ne
"="
) {
$self
->_error(
"Assignment ($node) is not supported"
,
$node
);
}
my
@expr
=
$self
->compile_ast(
$rhs
);
if
(
$is_decl
) {
$lvar
->{
$lvar_name
} =
$self
->lvar_id;
$self
->{lvar_id} =
$self
->lvar_use(1);
}
if
(!
exists
$lvar
->{
$lvar_name
} or
$lhs
->arity ne
"variable"
) {
$self
->_error(
"Cannot modify $lhs, which is not a lexical variable"
,
$node
);
}
return
@expr
,
$self
->opcode(
save_to_lvar
=>
$lvar
->{
$lvar_name
},
symbol
=>
$lhs
,
comment
=>
$node
->id);
}
sub
_generate_constant {
my
(
$self
,
$node
) =
@_
;
my
$lhs
=
$node
->first;
my
$rhs
=
$node
->second;
my
@expr
=
$self
->compile_ast(
$rhs
);
my
$lvar
=
$self
->lvar;
my
$lvar_id
=
$self
->lvar_id;
my
$lvar_name
=
$lhs
->id;
$lvar
->{
$lvar_name
} =
$lvar_id
;
$self
->{lvar_id} =
$self
->lvar_use(1);
if
(
$OPTIMIZE
) {
if
(
@expr
== 1
&& any_in(
$expr
[0][_OP_NAME],
qw(literal load_lvar)
)) {
$expr
[0][_OP_COMMENT] =
"constant $lvar_name"
;
$self
->const->[
$lvar_id
] = \
@expr
;
return
@expr
;
}
}
return
@expr
,
$self
->opcode(
save_to_lvar
=>
$lvar_id
,
symbol
=>
$lhs
,
comment
=>
$node
->id);
}
sub
_localize_vars {
my
(
$self
,
$vars
) =
@_
;
my
@localize
;
my
@pairs
= @{
$vars
};
if
( (
@pairs
% 2) != 0 ) {
if
(
@pairs
== 1) {
return
$self
->compile_ast(
@pairs
),
$self
->opcode(
'localize_vars'
);
}
else
{
$self
->_error(
"You must pass pairs of expressions to include"
);
}
}
while
(
my
(
$key
,
$expr
) =
splice
@pairs
, 0, 2) {
if
(!any_in(
$key
->arity,
qw(literal variable)
)) {
$self
->_error(
"You must pass a simple name to localize variables"
,
$key
);
}
push
@localize
,
$self
->compile_ast(
$expr
),
$self
->opcode(
localize_s
=>
$key
->value,
symbol
=>
$key
);
}
return
@localize
;
}
sub
_variable_to_value {
my
(
$self
,
$arg
) =
@_
;
my
$name
=
$arg
->value;
$name
=~ s/\$//;
return
$name
;
}
sub
requires {
my
(
$self
,
@files
) =
@_
;
push
@{
$self
->dependencies },
@files
;
return
;
}
sub
can_be_in_list_context {
my
$i
= 2;
while
(
my
$funcname
= (
caller
++
$i
)[3]) {
if
(
$funcname
=~ /::_generate_(\w+) \z/xms) {
return
any_in($1,
qw(
methodcall
call
composer
)
);
}
}
return
0;
}
sub
_code_is_literal {
my
(
$self
,
@code
) =
@_
;
return
@code
== 1
&& (
$code
[0][_OP_NAME] eq
'literal'
||
$code
[0][_OP_NAME] eq
'literal_i'
);
}
sub
_fold_constants {
my
(
$self
,
$code
) =
@_
;
my
$engine
=
$self
->engine or
return
0;
local
$engine
->{warn_handler} = \
&Carp::croak
;
local
$engine
->{die_handler} = \
&Carp::croak
;
local
$engine
->{verbose} = 1;
my
$result
=
eval
{
my
@tmp_code
= (@{
$code
},
$self
->opcode(
'print_raw'
),
$self
->opcode(
'end'
));
$engine
->_assemble(\
@tmp_code
,
'<string>'
,
undef
,
undef
,
undef
);
$engine
->render(
'<string>'
);
};
if
($@) {
Carp::carp(
"[BUG] Constant folding failed (ignored): $@"
);
return
0;
}
@{
$code
} = (
$self
->opcode(
literal
=>
$result
,
comment
=>
"optimized by constant folding"
));
return
1;
}
sub
_noop {
my
(
$self
,
$op
) =
@_
;
@{
$op
} = @{
$self
->opcode(
noop
=>
undef
,
comment
=>
"ex-$op->[0]"
) };
return
;
}
sub
_optimize_vmcode {
my
(
$self
,
$c
) =
@_
;
my
@goto_addr
;
for
(
my
$i
= 0;
$i
< @{
$c
};
$i
++) {
if
(
exists
$goto_family
{
$c
->[
$i
][_OP_NAME] }) {
my
$addr
=
$c
->[
$i
][_OP_ARG];
my
@range
=
$addr
> 0
? (
$i
.. (
$i
+
$addr
-1))
: ((
$i
+
$addr
) ..
$i
);
foreach
my
$j
(
@range
) {
push
@{
$goto_addr
[
$j
] ||= []},
$c
->[
$i
];
}
}
}
for
(
my
$i
= 0;
$i
< @{
$c
};
$i
++) {
my
$name
=
$c
->[
$i
][_OP_NAME];
if
(
$name
eq
'print_raw_s'
) {
my
$j
=
$i
+ 1;
while
(
$j
< @{
$c
}
&&
$c
->[
$j
][_OP_NAME] eq
'print_raw_s'
&&
"@{$goto_addr[$i] || []}"
eq
"@{$goto_addr[$j] || []}"
) {
$c
->[
$i
][_OP_ARG] .=
$c
->[
$j
][_OP_ARG];
$self
->_noop(
$c
->[
$j
]);
$j
++;
}
}
elsif
(
$name
eq
'save_to_lvar'
) {
my
$it
=
$c
->[
$i
];
my
$nn
=
$c
->[
$i
+2];
if
(
defined
(
$nn
)
&&
$nn
->[_OP_NAME] eq
'load_lvar_to_sb'
&&
$nn
->[_OP_ARG] ==
$it
->[_OP_ARG]) {
@{
$it
} = @{
$self
->opcode(
move_to_sb
=>
undef
,
comment
=>
"ex-$it->[0]"
)};
$self
->_noop(
$nn
);
}
}
elsif
(
$name
eq
'literal'
) {
if
(is_int(
$c
->[
$i
][_OP_ARG])) {
$c
->[
$i
][_OP_NAME] =
'literal_i'
;
$c
->[
$i
][_OP_ARG] =
int
(
$c
->[
$i
][_OP_ARG]);
}
}
elsif
(
$name
eq
'fetch_field'
) {
my
$prev
=
$c
->[
$i
-1];
if
(
$prev
->[_OP_NAME] =~ /^literal/) {
$c
->[
$i
][_OP_NAME] =
'fetch_field_s'
;
$c
->[
$i
][_OP_ARG] =
$prev
->[_OP_ARG];
$self
->_noop(
$prev
);
}
}
}
for
(
my
$i
= 0;
$i
< @{
$c
};
$i
++) {
if
(
$c
->[
$i
][_OP_NAME] eq
'noop'
) {
if
(
defined
$goto_addr
[
$i
]) {
foreach
my
$goto
(@{
$goto_addr
[
$i
] }) {
$goto
->[1] > 0
?
$goto
->[1]--
:
$goto
->[1]++;
}
}
splice
@{
$c
},
$i
, 1;
splice
@goto_addr
,
$i
, 1
if
@goto_addr
>
$i
;
}
}
return
;
}
sub
as_assembly {
my
(
$self
,
$code_ref
,
$addix
) =
@_
;
my
$asm
=
""
;
foreach
my
$ix
(0 .. (@{
$code_ref
}-1)) {
my
(
$name
,
$arg
,
$line
,
$file
,
$label
,
$comment
) = @{
$code_ref
->[
$ix
]};
$asm
.=
"$ix:"
if
$addix
;
ref
(
$name
) and
die
"Oops: "
. p(
$code_ref
->[
$ix
]);
$asm
.=
$name
;
if
(
defined
$arg
) {
$asm
.=
" "
. value_to_literal(
$arg
);
}
if
(
defined
$line
) {
$asm
.=
" #$line"
;
if
(
defined
$file
) {
$asm
.=
":"
. value_to_literal(
$file
);
}
}
if
(
defined
$label
) {
$asm
.=
" "
. value_to_literal(
$label
);
}
if
(
defined
$comment
) {
$asm
.=
" // $comment"
;
}
$asm
.=
"\n"
;
}
return
$asm
;
}
sub
_error {
my
(
$self
,
$message
,
$node
) =
@_
;
my
$line
=
ref
(
$node
) ?
$node
->line :
$node
;
die
$self
->make_error(
$message
,
$self
->file,
$line
);
}
no
Mouse;
no
Mouse::Util::TypeConstraints;
__PACKAGE__->meta->make_immutable;