File Coverage

blib/lib/Language/FormulaEngine/Compiler.pm
Criterion Covered Total %
statement 270 270 100.0
branch 27 42 64.2
condition 7 10 70.0
subroutine 90 90 100.0
pod 7 8 87.5
total 401 420 95.4


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 10     10   3271 use strict; # these apply to the contents of the eval, too.
  10         27  
  10         627  
7 9     9   68 use warnings;
  9         32  
  9         663  
8             # Arguments are ($compiler, $perl_code)
9 134     134   2134 my $default_namespace= $_[0]->namespace; # referred to by evals
10 134     5   14257 eval $_[1];
  5     5   157  
  5     1   20  
  5     1   503  
  5     1   141  
  5     1   29  
  5     1   520  
  1     1   126  
  1     1   8  
  1     1   3  
  1     1   162  
  1     1   7  
  1     1   2  
  1     1   191  
  1     1   8  
  1     1   3  
  1     1   119  
  1     1   8  
  1     1   3  
  1     1   128  
  1     1   8  
  1     1   2  
  1     1   133  
  1     1   9  
  1     1   12  
  1     1   101  
  1     1   8  
  1     1   5  
  1     1   98  
  1     1   14  
  1     1   3  
  1     1   114  
  1     1   8  
  1     1   4  
  1     1   104  
  1     1   8  
  1     1   4  
  1     1   140  
  1     1   8  
  1     1   4  
  1     1   109  
  1     1   9  
  1     1   2  
  1     1   105  
  1     1   8  
  1     1   3  
  1     1   99  
  1     1   18  
  1     1   3  
  1     1   148  
  1     1   14  
  1     1   8  
  1     1   109  
  1     1   15  
  1     1   2  
  1     1   128  
  1     1   8  
  1     1   5  
  1     1   103  
  1     1   12  
  1     1   4  
  1         112  
  1         7  
  1         25  
  1         105  
  1         10  
  1         5  
  1         116  
  1         19  
  1         3  
  1         107  
  1         7  
  1         3  
  1         107  
  1         8  
  1         3  
  1         105  
  1         7  
  1         2  
  1         115  
  1         8  
  1         2  
  1         116  
  1         8  
  1         3  
  1         119  
  1         8  
  1         3  
  1         139  
  1         8  
  1         7  
  1         100  
  1         36  
  1         5  
  1         119  
  1         10  
  1         4  
  1         130  
  1         7  
  1         3  
  1         109  
  1         12  
  1         3  
  1         156  
  1         9  
  1         5  
  1         100  
  1         9  
  1         2  
  1         110  
  1         15  
  1         2  
  1         138  
  1         13  
  1         3  
  1         105  
  1         8  
  1         2  
  1         101  
  1         7  
  1         13  
  1         106  
  1         11  
  1         2  
  1         108  
  1         7  
  1         15  
  1         101  
  1         8  
  1         3  
  1         103  
  1         7  
  1         4  
  1         111  
  1         13  
  1         4  
  1         108  
  1         8  
  1         3  
  1         102  
  1         7  
  1         3  
  1         105  
  1         9  
  1         3  
  1         101  
  1         9  
  1         3  
  1         104  
  1         10  
  1         2  
  1         103  
  1         8  
  1         3  
  1         115  
  1         9  
  1         3  
  1         133  
  1         9  
  1         3  
  1         100  
  1         15  
  1         2  
  1         114  
  1         11  
  1         2  
  1         102  
  1         10  
  1         3  
  1         98  
11             }
12              
13 9     9   73 use Moo;
  9         34  
  9         351  
14 9     9   2075 use Carp;
  9         30  
  9         744  
15 9     9   63 use Try::Tiny;
  9         22  
  9         676  
16 9     9   66 use Scalar::Util 'blessed';
  9         21  
  9         740  
17 9     9   66 use Sub::Util 'subname', 'set_subname';
  9         19  
  9         675  
18 9     9   1515 use namespace::clean;
  9         28556  
  9         370  
19              
20             # ABSTRACT: Compile a parse tree into perl code
21             our $VERSION = '0.07'; # 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 142     142   7712 my ($self, $newval)= @_;
40 142 100 100     870 $self->_clear_perl_generator_cache if $newval ne ($self->{_cur_namespace}||'');
41 142         2453 $self->_clear_optimize_var_access;
42 142         1442 $self->{_cur_namespace}= $newval;
43             }
44              
45             sub _trigger_optimize_var_access {
46 4     4   54 shift->_clear_optimize_var_access;
47             }
48              
49             sub _trigger_output_api {
50 5     5   109 shift->_clear_optimize_var_access;
51             }
52              
53             sub _build__optimize_var_access {
54 134     134   1359 my $self= shift;
55 134 100       2046 return $self->optimize_var_access if defined $self->optimize_var_access;
56 133   66     2688 return ($self->output_api =~ /^function_of_vars/)
57             && $self->namespace->can('get_value') == Language::FormulaEngine::Namespace->can('get_value');
58             }
59              
60             sub BUILD {
61 10     10 0 11550 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 10 100       211 if (defined $self->variables_via_namespace) {
66 4         233 carp "variables_via_namespace is deprecated. See 'output_api' and 'optimize_var_access'";
67 4         388 $self->output_api('function_of_namespace');
68 4         45 $self->optimize_var_access(0);
69             }
70             }
71              
72              
73             sub compile {
74 134     134 1 3908 my ($self, $parse_tree, $subname)= @_;
75 134         551 my $ret;
76 134         408 $self->reset;
77             try {
78 134     132   6185 $self->code_body($self->perlgen($parse_tree));
79 132         452 $ret= $self->generate_coderef_wrapper($self->code_body, $subname);
80             }
81             catch {
82 1 0   1   2 chomp unless ref $_;
83 1         87 $self->error($_);
84 134         811 };
85 132         6152 return $ret;
86             }
87              
88              
89             sub reset {
90 132     132 1 239 my $self= shift;
91 132         399 $self->error(undef);
92 132         300 $self->code_body(undef);
93 132         214 $self;
94             }
95              
96              
97             sub generate_coderef_wrapper {
98 132     132 1 478 my ($self, $perl, $subname)= @_;
99 132         282 $self->error(undef);
100 132 50       2376 my $wrapper_method= $self->can('_output_wrapper__'.$self->output_api)
101             or Carp::croak("Unsupported output_api='".$self->output_api."'");
102 132         1598 my $code= join "\n", $self->$wrapper_method(qq{# line 0 "compiled formula"\n$perl});
103 132         3503 my $ret;
104             {
105 132         246 local $@= undef;
  132         334  
106 132 50       378 if (defined ($ret= $self->_clean_eval($code))) {
107 132 50       2059 set_subname $subname, $ret if defined $subname;
108             } else {
109 1         97 $self->error($@);
110             }
111             }
112 132         480 return $ret;
113             }
114              
115             sub _output_wrapper__function_of_vars {
116 131     131   302 my ($self, $code)= @_;
117 131 100       2247 return $self->_optimize_var_access? (
118             '# line '.(__LINE__+1),
119             'my $namespace= $default_namespace;',
120             'sub {',
121             ' use warnings FATAL => qw( uninitialized numeric );',
122             ' my $vars= $namespace->variables;',
123             ' $vars= { %$vars, (@_ == 1? %{$_[0]} : @_) } if @_;',
124             $code,
125             '}'
126             ) : (
127             '# line '.(__LINE__+1),
128             'sub {',
129             ' use warnings FATAL => qw( uninitialized numeric );',
130             ' my $namespace= @_ == 0? $default_namespace',
131             ' : $default_namespace->clone_and_merge(variables => (@_ == 1 && ref $_[0] eq "HASH"? $_[0] : { @_ }));',
132             $code,
133             '}'
134             )
135             }
136              
137             sub _output_wrapper__function_of_vars_no_default {
138 1     1   8 my ($self, $code)= @_;
139 1 0       2 return $self->_optimize_var_access? (
140             '# line '.(__LINE__+1),
141             'my $namespace= $default_namespace;',
142             'sub {',
143             ' use warnings FATAL => qw( uninitialized numeric );',
144             ' my $vars= @_ == 1? $_[0] : { @_ };',
145             $code,
146             '}'
147             ) : (
148             '# line '.(__LINE__+1),
149             'sub {',
150             ' use warnings FATAL => qw( uninitialized numeric );',
151             ' my $namespace= ref($default_namespace)->new(variables => (@_ == 1? $_[0] : { @_ }));',
152             $code,
153             '}'
154             )
155             }
156              
157             sub _output_wrapper__function_of_namespace {
158 2     2   108 my ($self, $code)= @_;
159             return
160 2 50       23 '# line '.(__LINE__+1),
161             'sub {',
162             ' use warnings FATAL => qw( uninitialized numeric );',
163             ' my $namespace= @_ == 0? $default_namespace',
164             ' : @_ == 1 && Scalar::Util::blessed($_[0])? $_[0]',
165             ' : $default_namespace->clone_and_merge(@_);',
166             ($self->_optimize_var_access? ' my $vars= $namespace->variables;' : ()),
167             $code,
168             '}'
169             }
170              
171              
172             sub perlgen {
173 490     490 1 1032 my ($self, $node)= @_;
174 490 100       2576 if ($node->can('function_name')) {
    100          
    100          
    50          
175 189         462 my $name= $node->function_name;
176 189   66     3492 my $gen= $self->_perl_generator_cache->{$name} ||= $self->_get_perl_generator($name);
177 189         4492 return $gen->($self->namespace, $self, $node);
178             }
179             elsif ($node->can('symbol_name')) {
180 81         222 my $name= $node->symbol_name;
181 81         1440 my $x= $self->namespace->get_constant($name);
182 81 50       398 return defined $x? $self->perlgen_literal($x) : $self->perlgen_var_access($name);
183             }
184             elsif ($node->can('string_value')) {
185 57         170 return $self->perlgen_string_literal($node->string_value);
186             }
187             elsif ($node->can('number_value')) {
188 166         402 return $node->number_value+0;
189             }
190             else {
191 1         103 die "Don't know how to compile node of type '".ref($node)."'\n";
192             }
193             }
194              
195             sub _get_perl_generator {
196 71     71   765 my ($self, $name)= @_;
197 71 50       1100 my $info= $self->namespace->get_function($name)
198             or die "No such function '$name'\n";
199             # If a generator is given, nothing else to do.
200 71 100       392 return $info->{perl_generator} if $info->{perl_generator};
201            
202             # Else need to create a generator around a native perl function
203             $info->{native}
204 52 50       153 or die "Cannot compile function '$name'; no generator or native function given\n";
205 52   50     405 my $fqn= subname($info->{native}) || '';
206             # For security, make reasonably sure that perl will parse the subname as a function name.
207             # This regex is more restrictive than perl's actual allowed identifier names.
208 52 50       501 $fqn =~ /^[A-Za-z_][A-Za-z0-9_]*::([A-Za-z0-9_]+::)*\p{Word}+$/
209             or die "Can't compile function '$name'; native function does not have a valid fully qualified name '$fqn'\n";
210             # Create a generator that injects this function name
211             return sub {
212 135     135   968 $fqn . '(' . join(',', map $_[1]->perlgen($_), @{ $_[2]->parameters }) . ')'
  135         488  
213 52         371 };
214             }
215              
216              
217             sub perlgen_var_access {
218 81     81 1 167 my ($self, $varname)= @_;
219 81 100       1348 return $self->_optimize_var_access
220             ? '$vars->{'.$self->perlgen_string_literal(lc $varname).'}'
221             : '$namespace->get_value('.$self->perlgen_string_literal($varname).')';
222             }
223              
224              
225             sub perlgen_string_literal {
226 137     137 1 2301 my ($self, $string)= @_;
227 137         332 $string =~ s/([\0-\x1F\x7f"\@\$\%\\])/ sprintf("\\x%02x", ord $1) /gex;
  10         54  
228 137         941 return qq{"$string"};
229             }
230              
231              
232             sub perlgen_literal {
233 1     1 1 8 my ($self, $string)= @_;
234 9     9   18076 no warnings 'numeric';
  9         23  
  9         751  
235 1 0       3 return ($string+0) eq $string? $string+0 : $self->perlgen_string_literal($string);
236             }
237              
238             1;
239              
240             __END__
241              
242             =pod
243              
244             =encoding UTF-8
245              
246             =head1 NAME
247              
248             Language::FormulaEngine::Compiler - Compile a parse tree into perl code
249              
250             =head1 VERSION
251              
252             version 0.07
253              
254             =head1 DESCRIPTION
255              
256             The Compiler object takes a parse tree and generates perl code from it, and also evals
257             that perl code into a compiled coderef. However, most of the code generation is handled
258             by the L<Language::FormulaEngine::Namespace> object. The namespace object must be
259             available during compilation. In the default scenario, the namespace object will be
260             consulted for default constants and "global" variables, and then the generated coderef
261             will accept additional variables to overlay on those global variables, but the Namespace
262             will still get to determine how to access that pool of combined variables at execution.
263              
264             For alternative strategies, see the C<output_api> attribute below.
265              
266             =head1 ATTRIBUTES
267              
268             =head2 namespace
269              
270             Instance of L<Language::FormulaEngine::Namespace> to use for looking up functions,
271             converting functions to perl code, and symbolic constants. This namespace object will
272             be referenced by the coderefs, and can act as a "global scope".
273              
274             =head2 output_api
275              
276             Determines the function signature of the coderef that will be generated. Currently supported
277             values are:
278              
279             =over 25
280              
281             =item C<"function_of_vars">
282              
283             # generated coderef signature:
284             $value = $coderef->(%vars);
285             $value = $coderef->(\%vars);
286              
287             The list or hashref of variables will get overlaid ontop of the set of variables of the
288             namespace (by creating a derived namespace), and the namespace C<get_value> method will
289             retrieve values from them.
290              
291             =item C<"function_of_vars_no_default">
292              
293             Same signature as "function_of_vars" but current C<< $namespace->variables >> are ignored
294             and either a new empty namespace is created each time, or (if C<optimize_var_access> is
295             active) no namespace will be used at all and vars will be read directly from a hashref.
296              
297             =item C<"function_of_namespace">
298              
299             # generated coderef signature:
300             $value= $coderef->();
301             $value= $coderef->($namespace);
302             $value= $coderef->(\%namespace_attributes);
303              
304             This either uses the supplied namespace *instead* of the default, or merges attributes with
305             the default namespace via L<Language::FormulaEngine::Namespace/clone_and_merge>.
306              
307             =back
308              
309             =head2 optimize_var_access
310              
311             By default, when a formula accesses a variable it will call L<Language::FormulaEngine::Namespace/get_value>
312             but for higher performance, you can have the formula directly access the variables hashref,
313             bypassing C<get_value>.
314              
315             If this attribute is not set, the compilation will default to using the optimization if the
316             L</namespace> is using the default implementation of C<get_value> (i.e. has not been overridden
317             by a subclass) and the coderefs are a function of variables.
318              
319             =head2 error
320              
321             After a failed call to C<compile>, this attribute holds the error message.
322              
323             =head2 code_body
324              
325             After compilation, this attribute holds the perl source code that was generated prior to being
326             wrapped with the coderef boilerplate.
327              
328             =head1 METHODS
329              
330             =head2 compile( $parse_tree, $subname )
331              
332             Compile a parse tree, returning a coderef. Any references to functions will be immeditely
333             looked up within the L</namespace>. Any references to constants in the L</namespace> will be
334             inlined into the generated perl. Any other symbol is assumed to be a variable, and will be
335             looked up from the L</namespace> at the time the formula is invoked.
336              
337             See attribute C<output_api> for the signature and behavior of this coderef.
338              
339             Because the generated coderef contains a reference to the namespace, be sure never to store
340             one of the coderefs into that namespace object, else you get a memory leak.
341              
342             The second argument C<$subname> is optional, but provided to help encourage use of
343             L<Sub::Util/set_subname> for generated code.
344              
345             =head2 reset
346              
347             Clear any temporary results from the last compilation. Returns C<$self>.
348              
349             =head2 generate_coderef_wrapper
350              
351             my $coderef= $compiler->generate_coderef_wrapper($perl_code, $subname);
352              
353             Utility method used by L</compile> that wraps a bit of perl code with the relevant boilerplate
354             according to L</output_api>, and then evals the perl to create the coderef.
355              
356             On a compile failure, this returns C<undef> and puts the error message into L</error>.
357              
358             =head2 perlgen
359              
360             $perl_code= $compiler->perlgen( $parse_node );
361              
362             Generate perl source code for a parse node. This is a fragment, not a whole sub.
363             The code should always be an expression which can be combined with other
364             function calls.
365              
366             =head2 perlgen_var_access
367              
368             $compiler->perlgen_var_access($varname);
369              
370             Generate perl code to access a variable. Unless L</optimize_var_access> is true,
371             this becomes a call to C<< $namespace->get_value($varname) >>, and the namespace
372             decides how to interpret the variable at execution time. If C<optimize_var_access>
373             is enabled, this returns a reference to the C<$vars> hashref like C<< $vars->{$varname} >>.
374              
375             =head2 perlgen_string_literal
376              
377             Generate a perl string literal. This wraps the string with double-quotes and escapes control
378             characters and C<["\\\@\$]> using hex-escape notation. Hex escapes are chosen over simple
379             backslash prefixes for extra security, in case of mistakes elsewhere in the generated code.
380              
381             =head2 perlgen_literal
382              
383             If the scalar can be exactly represented by a perl numeric literal, this returns that literal,
384             else it wraps the string with qoutes using L</perlgen_string_literal>.
385              
386             =head1 AUTHOR
387              
388             Michael Conrad <mconrad@intellitree.com>
389              
390             =head1 COPYRIGHT AND LICENSE
391              
392             This software is copyright (c) 2023 by Michael Conrad, IntelliTree Solutions llc.
393              
394             This is free software; you can redistribute it and/or modify it under
395             the same terms as the Perl 5 programming language system itself.
396              
397             =cut