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   3220 use strict; # these apply to the contents of the eval, too.
  10         31  
  10         610  
7 9     9   63 use warnings;
  9         24  
  9         697  
8             # Arguments are ($compiler, $perl_code)
9 134     134   2222 my $default_namespace= $_[0]->namespace; # referred to by evals
10 134     5   14045 eval $_[1];
  5     5   187  
  5     1   21  
  5     1   509  
  5     1   132  
  5     1   23  
  5     1   475  
  1     1   128  
  1     1   7  
  1     1   3  
  1     1   138  
  1     1   9  
  1     1   2  
  1     1   170  
  1     1   7  
  1     1   3  
  1     1   112  
  1     1   8  
  1     1   3  
  1     1   124  
  1     1   7  
  1     1   3  
  1     1   133  
  1     1   8  
  1     1   3  
  1     1   96  
  1     1   8  
  1     1   4  
  1     1   98  
  1     1   8  
  1     1   2  
  1     1   96  
  1     1   8  
  1     1   2  
  1     1   97  
  1     1   7  
  1     1   3  
  1     1   108  
  1     1   8  
  1     1   4  
  1     1   105  
  1     1   8  
  1     1   3  
  1     1   81  
  1     1   10  
  1     1   2  
  1     1   96  
  1     1   12  
  1     1   3  
  1     1   138  
  1     1   7  
  1     1   2  
  1     1   100  
  1     1   8  
  1     1   4  
  1     1   108  
  1     1   8  
  1     1   2  
  1     1   104  
  1     1   8  
  1     1   2  
  1         109  
  1         8  
  1         3  
  1         106  
  1         8  
  1         3  
  1         110  
  1         7  
  1         2  
  1         105  
  1         10  
  1         3  
  1         106  
  1         8  
  1         3  
  1         102  
  1         8  
  1         2  
  1         101  
  1         9  
  1         3  
  1         97  
  1         8  
  1         3  
  1         119  
  1         9  
  1         2  
  1         102  
  1         8  
  1         3  
  1         96  
  1         8  
  1         2  
  1         111  
  1         9  
  1         2  
  1         125  
  1         7  
  1         3  
  1         97  
  1         11  
  1         3  
  1         158  
  1         7  
  1         5  
  1         104  
  1         8  
  1         4  
  1         105  
  1         9  
  1         3  
  1         104  
  1         9  
  1         2  
  1         135  
  1         8  
  1         4  
  1         112  
  1         8  
  1         18  
  1         117  
  1         8  
  1         3  
  1         463  
  1         8  
  1         3  
  1         114  
  1         8  
  1         3  
  1         105  
  1         8  
  1         3  
  1         131  
  1         9  
  1         2  
  1         106  
  1         10  
  1         2  
  1         117  
  1         9  
  1         3  
  1         116  
  1         8  
  1         3  
  1         113  
  1         9  
  1         2  
  1         113  
  1         8  
  1         4  
  1         105  
  1         8  
  1         3  
  1         104  
  1         7  
  1         4  
  1         105  
  1         11  
  1         3  
  1         111  
  1         9  
  1         3  
  1         106  
  1         8  
  1         4  
  1         108  
  1         9  
  1         2  
  1         102  
11             }
12              
13 9     9   72 use Moo;
  9         23  
  9         358  
14 9     9   2172 use Carp;
  9         40  
  9         758  
15 9     9   85 use Try::Tiny;
  9         22  
  9         721  
16 9     9   67 use Scalar::Util 'blessed';
  9         34  
  9         723  
17 9     9   75 use Sub::Util 'subname', 'set_subname';
  9         57  
  9         743  
18 9     9   1554 use namespace::clean;
  9         28814  
  9         399  
19              
20             # ABSTRACT: Compile a parse tree into perl code
21             our $VERSION = '0.08'; # 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   7674 my ($self, $newval)= @_;
40 142 100 100     839 $self->_clear_perl_generator_cache if $newval ne ($self->{_cur_namespace}||'');
41 142         2510 $self->_clear_optimize_var_access;
42 142         1419 $self->{_cur_namespace}= $newval;
43             }
44              
45             sub _trigger_optimize_var_access {
46 4     4   49 shift->_clear_optimize_var_access;
47             }
48              
49             sub _trigger_output_api {
50 5     5   126 shift->_clear_optimize_var_access;
51             }
52              
53             sub _build__optimize_var_access {
54 134     134   1483 my $self= shift;
55 134 100       2100 return $self->optimize_var_access if defined $self->optimize_var_access;
56 133   66     2695 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 11445 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       186 if (defined $self->variables_via_namespace) {
66 4         240 carp "variables_via_namespace is deprecated. See 'output_api' and 'optimize_var_access'";
67 4         388 $self->output_api('function_of_namespace');
68 4         48 $self->optimize_var_access(0);
69             }
70             }
71              
72              
73             sub compile {
74 134     134 1 3495 my ($self, $parse_tree, $subname)= @_;
75 134         535 my $ret;
76 134         494 $self->reset;
77             try {
78 134     132   6183 $self->code_body($self->perlgen($parse_tree));
79 132         422 $ret= $self->generate_coderef_wrapper($self->code_body, $subname);
80             }
81             catch {
82 1 0   1   3 chomp unless ref $_;
83 1         84 $self->error($_);
84 134         867 };
85 132         6039 return $ret;
86             }
87              
88              
89             sub reset {
90 132     132 1 219 my $self= shift;
91 132         389 $self->error(undef);
92 132         321 $self->code_body(undef);
93 132         230 $self;
94             }
95              
96              
97             sub generate_coderef_wrapper {
98 132     132 1 456 my ($self, $perl, $subname)= @_;
99 132         280 $self->error(undef);
100 132 50       2357 my $wrapper_method= $self->can('_output_wrapper__'.$self->output_api)
101             or Carp::croak("Unsupported output_api='".$self->output_api."'");
102 132         1500 my $code= join "\n", $self->$wrapper_method(qq{# line 0 "compiled formula"\n$perl});
103 132         3533 my $ret;
104             {
105 132         205 local $@= undef;
  132         367  
106 132 50       400 if (defined ($ret= $self->_clean_eval($code))) {
107 132 50       2080 set_subname $subname, $ret if defined $subname;
108             } else {
109 1         95 $self->error($@);
110             }
111             }
112 132         508 return $ret;
113             }
114              
115             sub _output_wrapper__function_of_vars {
116 131     131   271 my ($self, $code)= @_;
117 131 100       2186 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       3 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   112 my ($self, $code)= @_;
159             return
160 2 50       30 '# 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 1006 my ($self, $node)= @_;
174 490 100       2542 if ($node->can('function_name')) {
    100          
    100          
    50          
175 189         495 my $name= $node->function_name;
176 189   66     3650 my $gen= $self->_perl_generator_cache->{$name} ||= $self->_get_perl_generator($name);
177 189         4150 return $gen->($self->namespace, $self, $node);
178             }
179             elsif ($node->can('symbol_name')) {
180 81         204 my $name= $node->symbol_name;
181 81         1382 my $x= $self->namespace->get_constant($name);
182 81 50       382 return defined $x? $self->perlgen_literal($x) : $self->perlgen_var_access($name);
183             }
184             elsif ($node->can('string_value')) {
185 57         176 return $self->perlgen_string_literal($node->string_value);
186             }
187             elsif ($node->can('number_value')) {
188 166         416 return $node->number_value+0;
189             }
190             else {
191 1         109 die "Don't know how to compile node of type '".ref($node)."'\n";
192             }
193             }
194              
195             sub _get_perl_generator {
196 71     71   738 my ($self, $name)= @_;
197 71 50       1159 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       339 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       198 or die "Cannot compile function '$name'; no generator or native function given\n";
205 52   50     374 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       503 $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   972 $fqn . '(' . join(',', map $_[1]->perlgen($_), @{ $_[2]->parameters }) . ')'
  135         472  
213 52         354 };
214             }
215              
216              
217             sub perlgen_var_access {
218 81     81 1 189 my ($self, $varname)= @_;
219 81 100       1352 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 2380 my ($self, $string)= @_;
227 137         354 $string =~ s/([\0-\x1F\x7f"\@\$\%\\])/ sprintf("\\x%02x", ord $1) /gex;
  10         58  
228 137         913 return qq{"$string"};
229             }
230              
231              
232             sub perlgen_literal {
233 1     1 1 9 my ($self, $string)= @_;
234 9     9   18055 no warnings 'numeric';
  9         24  
  9         834  
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.08
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