File Coverage

blib/lib/Language/FormulaEngine/Compiler.pm
Criterion Covered Total %
statement 270 270 100.0
branch 29 42 69.0
condition 7 10 70.0
subroutine 90 90 100.0
pod 7 8 87.5
total 403 420 95.9


line stmt bran cond sub pod time code
1             package Language::FormulaEngine::Compiler;
2              
3             # This is at the top of the file to make sure the eval namespace is as clean as possible
4             # Need a second package to avoid getting clobbered by namespace::clean
5             sub Language::FormulaEngine::Compiler::_CleanEval::_clean_eval {
6 6     6   2969 use strict; # these apply to the contents of the eval, too.
  6         16  
  6         308  
7 6     6   36 use warnings;
  6         15  
  6         400  
8             # Arguments are ($compiler, $perl_code)
9 130     130   2365 my $default_namespace= shift->namespace;
10 130     4   13550 eval shift;
  4     4   30  
  4     4   9  
  4     3   1048  
  4     3   42  
  4     3   12  
  4     3   536  
  4     3   29  
  4     3   11  
  4     3   535  
  3     3   25  
  3     3   9  
  3     3   673  
  3     3   25  
  3     3   9  
  3     3   446  
  3     3   26  
  3     3   8  
  3     1   459  
  3     1   25  
  3     1   9  
  3     1   444  
  3     1   22  
  3     1   8  
  3     1   434  
  3     1   23  
  3     1   18  
  3     1   465  
  3     1   25  
  3     1   9  
  3     1   430  
  3     1   29  
  3     1   8  
  3     1   448  
  3     1   24  
  3     1   8  
  3     1   554  
  3     1   26  
  3     1   8  
  3     1   404  
  3     1   24  
  3     1   9  
  3     1   428  
  3     1   26  
  3     1   7  
  3     1   408  
  3     1   27  
  3     1   8  
  3     1   455  
  3     1   28  
  3     1   8  
  3     1   492  
  3     1   24  
  3     1   8  
  3     1   465  
  1     1   9  
  1     1   3  
  1     1   135  
  1     1   8  
  1     1   4  
  1     1   140  
  1     1   9  
  1     1   3  
  1     1   142  
  1         10  
  1         2  
  1         137  
  1         8  
  1         2  
  1         136  
  1         8  
  1         3  
  1         149  
  1         8  
  1         2  
  1         148  
  1         9  
  1         3  
  1         130  
  1         8  
  1         3  
  1         126  
  1         7  
  1         3  
  1         123  
  1         8  
  1         3  
  1         135  
  1         9  
  1         3  
  1         129  
  1         8  
  1         3  
  1         141  
  1         8  
  1         3  
  1         152  
  1         9  
  1         4  
  1         139  
  1         11  
  1         4  
  1         209  
  1         8  
  1         3  
  1         189  
  1         8  
  1         4  
  1         132  
  1         8  
  1         2  
  1         140  
  1         9  
  1         2  
  1         132  
  1         41  
  1         4  
  1         137  
  1         10  
  1         3  
  1         145  
  1         8  
  1         2  
  1         166  
  1         8  
  1         3  
  1         178  
  1         8  
  1         3  
  1         174  
  1         7  
  1         3  
  1         166  
  1         8  
  1         2  
  1         148  
  1         8  
  1         3  
  1         156  
  1         9  
  1         3  
  1         151  
  1         8  
  1         2  
  1         129  
  1         7  
  1         3  
  1         132  
  1         8  
  1         2  
  1         139  
  1         8  
  1         3  
  1         132  
  1         8  
  1         2  
  1         163  
  1         8  
  1         3  
  1         137  
  1         9  
  1         3  
  1         146  
  1         8  
  1         3  
  1         127  
  1         10  
  1         3  
  1         199  
11             }
12              
13 6     6   37 use Moo;
  6         13  
  6         179  
14 6     6   1973 use Carp;
  6         24  
  6         503  
15 6     6   79 use Try::Tiny;
  6         17  
  6         424  
16 6     6   40 use Scalar::Util 'blessed';
  6         13  
  6         423  
17 6     6   40 use Sub::Util 'subname', 'set_subname';
  6         13  
  6         467  
18 6     6   1115 use namespace::clean;
  6         19881  
  6         169  
19              
20             # ABSTRACT: Compile a parse tree into perl code
21             our $VERSION = '0.06'; # VERSION
22              
23             *_clean_eval= *Language::FormulaEngine::Compiler::_CleanEval::_clean_eval;
24              
25              
26             has namespace => ( is => 'rw', trigger => 1 );
27             has optimize_var_access => ( is => 'rw', trigger => 1 );
28             has output_api => ( is => 'rw', trigger => 1, default => 'function_of_vars' );
29             has _optimize_var_access => ( is => 'lazy', clearer => 1 ); # holds the effective value of optimize_var_access
30              
31             has variables_via_namespace => ( is => 'rw' ); # Deprecated
32              
33             has error => ( is => 'rw' );
34             has code_body => ( is => 'rw' );
35              
36             has _perl_generator_cache => ( is => 'lazy', clearer => 1, default => sub { {} } );
37              
38             sub _trigger_namespace {
39 139     139   7575 my ($self, $newval)= @_;
40 139 100 100     793 $self->_clear_perl_generator_cache if $newval ne ($self->{_cur_namespace}||'');
41 139         2521 $self->_clear_optimize_var_access;
42 139         1303 $self->{_cur_namespace}= $newval;
43             }
44              
45             sub _trigger_optimize_var_access {
46 2     2   36 shift->_clear_optimize_var_access;
47             }
48              
49             sub _trigger_output_api {
50 2     2   32 shift->_clear_optimize_var_access;
51             }
52              
53             sub _build__optimize_var_access {
54 131     131   1147 my $self= shift;
55 131 100       2107 return $self->optimize_var_access if defined $self->optimize_var_access;
56 130   66     2744 return $self->output_api eq 'function_of_vars'
57             && $self->namespace->can('get_value') == Language::FormulaEngine::Namespace->can('get_value');
58             }
59              
60             sub BUILD {
61 7     7 0 9028 my ($self, $args)= @_;
62             # Handle back-compat for initial broken version of this feature.
63             # There is no longer any reason to set variables_via_namespace to true, because true is the default.
64             # So if a user does that, they might be asking for the 'output_api => "function_of_namespace"'
65 7 100       167 if ($self->variables_via_namespace) {
66 2         231 carp "variables_via_namespace is deprecated. See 'output_api' and 'optimize_var_access'";
67 2         199 $self->output_api('function_of_namespace');
68 2         32 $self->optimize_var_access(0);
69             }
70             }
71              
72              
73             sub compile {
74 130     130 1 3696 my ($self, $parse_tree, $subname)= @_;
75 130         343 my $ret;
76 130         396 $self->reset;
77             try {
78 130     130   5853 $self->code_body($self->perlgen($parse_tree));
79 130         414 $ret= $self->generate_coderef_wrapper($self->code_body);
80             }
81             catch {
82 1 0   1   2 chomp unless ref $_;
83 1         134 $self->error($_);
84 130         889 };
85 130         6334 return $ret;
86             }
87              
88              
89             sub reset {
90 130     130 1 209 my $self= shift;
91 130         459 $self->error(undef);
92 130         304 $self->code_body(undef);
93 130         218 $self;
94             }
95              
96              
97             sub generate_coderef_wrapper {
98 130     130 1 532 my ($self, $perl, $subname)= @_;
99 130         304 $self->error(undef);
100 130         373 my @code= (
101             '# line '.(__LINE__+1),
102             'sub {',
103             ' use warnings FATAL => qw( uninitialized numeric );',
104             );
105 130 100       2653 if ($self->output_api eq 'function_of_vars') {
    50          
106 129 100       2745 if ($self->_optimize_var_access) {
107 128         3024 push @code,
108             '# line '.(__LINE__+1),
109             ' my $namespace= $default_namespace;',
110             ' my $vars= $namespace->variables;',
111             ' $vars= { %$vars, (@_ == 1 && ref $_[0] eq "HASH"? %{$_[0]} : @_) } if @_;'
112             }
113             else {
114 2         141 push @code,
115             '# line '.(__LINE__+1),
116             ' my $namespace= @_ == 0? $default_namespace',
117             ' : $default_namespace->clone_and_merge(variables => (@_ == 1 && ref $_[0] eq "HASH"? $_[0] : { @_ }));';
118             }
119             } elsif ($self->output_api eq 'function_of_namespace') {
120 2         36 push @code,
121             '# line '.(__LINE__+1),
122             ' my $namespace= @_ == 0? $default_namespace',
123             ' : @_ == 1 && Scalar::Util::blessed($_[0])? $_[0]',
124             ' : $default_namespace->clone_and_merge(@_);';
125 2 50       20 push @code,
126             ' my $vars= $namespace->variables;'
127             if $self->_optimize_var_access;
128             }
129             else {
130 1         123 croak "Unhandled output_api = '".$self->output_api."'";
131             }
132              
133 130         600 my $code= join "\n", @code, '# line 0 "compiled formula"', $perl, '}';
134 130         241 my $ret;
135             {
136 130         323 local $@= undef;
  130         259  
137 130 50       414 if (defined ($ret= $self->_clean_eval($code))) {
138 130 50       2656 set_subname $subname, $ret if defined $subname;
139             } else {
140 1         9 $self->error($@);
141             }
142             }
143 130         587 return $ret;
144             }
145              
146              
147             sub perlgen {
148 478     478 1 1104 my ($self, $node)= @_;
149 478 100       2575 if ($node->can('function_name')) {
    100          
    100          
    50          
150 184         460 my $name= $node->function_name;
151 184   66     3731 my $gen= $self->_perl_generator_cache->{$name} ||= $self->_get_perl_generator($name);
152 184         4101 return $gen->($self->namespace, $self, $node);
153             }
154             elsif ($node->can('symbol_name')) {
155 77         225 my $name= $node->symbol_name;
156 77         1402 my $x= $self->namespace->get_constant($name);
157 77 50       284 return defined $x? $self->perlgen_literal($x) : $self->perlgen_var_access($name);
158             }
159             elsif ($node->can('string_value')) {
160 57         158 return $self->perlgen_string_literal($node->string_value);
161             }
162             elsif ($node->can('number_value')) {
163 163         614 return $node->number_value+0;
164             }
165             else {
166 1         7 die "Don't know how to compile node of type '".ref($node)."'\n";
167             }
168             }
169              
170             sub _get_perl_generator {
171 67     67   737 my ($self, $name)= @_;
172 67 50       1293 my $info= $self->namespace->get_function($name)
173             or die "No such function '$name'\n";
174             # If a generator is given, nothing else to do.
175 67 100       253 return $info->{perl_generator} if $info->{perl_generator};
176            
177             # Else need to create a generator around a native perl function
178             $info->{native}
179 49 50       143 or die "Cannot compile function '$name'; no generator or native function given\n";
180 49   50     499 my $fqn= subname($info->{native}) || '';
181             # For security, make reasonably sure that perl will parse the subname as a function name.
182             # This regex is more restrictive than perl's actual allowed identifier names.
183 49 50       379 $fqn =~ /^[A-Za-z_][A-Za-z0-9_]*::([A-Za-z0-9_]+::)*\p{Word}+$/
184             or die "Can't compile function '$name'; native function does not have a valid fully qualified name '$fqn'\n";
185             # Create a generator that injects this function name
186             return sub {
187 132     132   1091 $fqn . '(' . join(',', map $_[1]->perlgen($_), @{ $_[2]->parameters }) . ')'
  132         412  
188 49         367 };
189             }
190              
191              
192             sub perlgen_var_access {
193 77     77 1 163 my ($self, $varname)= @_;
194 77 100       1518 return $self->_optimize_var_access
195             ? '$vars->{'.$self->perlgen_string_literal(lc $varname).'}'
196             : '$namespace->get_value('.$self->perlgen_string_literal($varname).')';
197             }
198              
199              
200             sub perlgen_string_literal {
201 133     133 1 2255 my ($self, $string)= @_;
202 133         323 $string =~ s/([\0-\x1F\x7f"\@\$\\])/ sprintf("\\x%02x", ord $1) /gex;
  9         152  
203 133         861 return qq{"$string"};
204             }
205              
206              
207             sub perlgen_literal {
208 1     1 1 77 my ($self, $string)= @_;
209 6     6   13710 no warnings 'numeric';
  6         18  
  6         518  
210 1 0       114 return ($string+0) eq $string? $string+0 : $self->perlgen_string_literal($string);
211             }
212              
213             1;
214              
215             __END__
216              
217             =pod
218              
219             =encoding UTF-8
220              
221             =head1 NAME
222              
223             Language::FormulaEngine::Compiler - Compile a parse tree into perl code
224              
225             =head1 VERSION
226              
227             version 0.06
228              
229             =head1 DESCRIPTION
230              
231             =head1 ATTRIBUTES
232              
233             =head2 namespace
234              
235             Namespace to use for looking up functions, converting functions to perl code, and symbolic
236             constants. The namespace will also be bound into the coderefs which get compiled, so any
237             change to the variables (not constants) of the namespace will be visible to compiled formulas.
238              
239             =head2 output_api
240              
241             Determines the function signature of the coderef that will be generated. Currently supported
242             values are
243              
244             =over 25
245              
246             =item C<"function_of_vars">
247              
248             Compiles as C<< $return = my_formula(%vars) >> or C<< $return = my_formula(\%vars) >>
249              
250             =item C<"function_of_namespace">
251              
252             Compiles as C<< $return = my_formula($namespace) >> or C<< $return = my_formula(%namespace_attrs) >>
253             where C<%namespace_attrs> get passed to C<clone_and_merge> of L</namespace>.
254              
255             =back
256              
257             =head2 optimize_var_access
258              
259             By default, when a formula accesses a variable it will call L<Language::FormulaEngine::Namespace/get_value>
260             but for higher performance, you can have the formula directly access the variables hashref,
261             bypassing C<get_value>.
262              
263             If this attribute is not set, the compilation will default to using the optimization if the
264             L</namespace> is using the default implementation of C<get_value> (i.e. has not been overridden
265             by a subclass) and if output_api is C<"function_of_vars">.
266              
267             =head2 error
268              
269             After a failed call to C<compile>, this attribute holds the error message.
270              
271             =head2 code_body
272              
273             After compilation, this attribute holds the perl source code that was generated prior to being
274             wrapped with the coderef boilerplate.
275              
276             =head1 METHODS
277              
278             =head2 compile( $parse_tree, $subname )
279              
280             Compile a parse tree, returning a coderef. Any references to functions will be immeditely
281             looked up within the L</namespace>. Any references to constants in the L</namespace> will be
282             inlined into the generated perl. Any other symbol is assumed to be a variable, and will be
283             looked up from the L</namespace> at the time the formula is invoked. The generated coderef
284             takes parameters of overrides for the set of variables in the namespace:
285              
286             $value= $compiled_sub->(%vars); # vars are optional
287              
288             Because the generated coderef contains a reference to the namespace, be sure never to store
289             one of the coderefs into that namespace object, else you get a memory leak.
290              
291             The second argument C<$subname> is optional, but provided to help encourage use of
292             L<Sub::Util/set_subname> for generated code.
293              
294             =head2 reset
295              
296             Clear any temporary results from the last compilation. Returns C<$self>.
297              
298             =head2 generate_coderef_wrapper
299              
300             my $coderef= $compiler->generate_coderef_wrapper($perl_code, $subname);
301              
302             Utility method used by L</compile> that wraps a bit of perl code with the relevant boilerplate
303             according to L</output_api>, and then evals the perl to create the coderef.
304              
305             On a compile failure, this returns C<undef> and puts the error message into L</error>.
306              
307             =head2 perlgen( $parse_node )
308              
309             Generate perl source code for a parse node.
310              
311             =head2 perlgen_var_access
312              
313             $compiler->perlgen_var_access($varname);
314              
315             Generate perl code to access a variable. If L</variables_via_namespace> is true, this becomes
316             a call to C<< $namespace->get_value($varname) >>. Else it becomes a reference to the variables
317             hashref C<< $vars->{$varname} >>.
318              
319             =head2 perlgen_string_literal
320              
321             Generate a perl string literal. This wraps the string with double-quotes and escapes control
322             characters and C<["\\\@\$]> using hex-escape notation.
323              
324             =head2 perlgen_literal
325              
326             If the scalar can be exactly represented by a perl numeric literal, this returns that literal,
327             else it wraps the string with qoutes using L</perlgen_string_literal>.
328              
329             =head1 AUTHOR
330              
331             Michael Conrad <mconrad@intellitree.com>
332              
333             =head1 COPYRIGHT AND LICENSE
334              
335             This software is copyright (c) 2021 by Michael Conrad, IntelliTree Solutions llc.
336              
337             This is free software; you can redistribute it and/or modify it under
338             the same terms as the Perl 5 programming language system itself.
339              
340             =cut