File Coverage

blib/lib/Language/FormulaEngine/Namespace.pm
Criterion Covered Total %
statement 55 79 69.6
branch 16 30 53.3
condition 3 19 15.7
subroutine 13 15 86.6
pod 7 7 100.0
total 94 150 62.6


line stmt bran cond sub pod time code
1             package Language::FormulaEngine::Namespace;
2 5     5   2788 use Moo;
  5         12  
  5         35  
3 5     5   1655 use Carp;
  5         22  
  5         350  
4 5     5   34 use Try::Tiny;
  5         10  
  5         394  
5             require MRO::Compat if $] lt '5.009005';
6 5     5   1906 use Language::FormulaEngine::Error ':all';
  5         16  
  5         859  
7 5     5   570 use namespace::clean;
  5         9829  
  5         36  
8              
9             # ABSTRACT: Object holding function and variable names
10             our $VERSION = '0.06'; # VERSION
11              
12              
13             has variables => ( is => 'rw', default => sub { +{} } );
14             has constants => ( is => 'rw', default => sub { +{} } );
15             has die_on_unknown_value => ( is => 'rw' );
16              
17              
18             sub clone {
19 0     0 1 0 my $self= shift;
20 0 0 0     0 my %attrs= @_==1 && ref $_[0] eq 'HASH'? %{$_[0]} : @_;
  0         0  
21 0   0     0 $attrs{variables} ||= { %{ $self->variables } };
  0         0  
22 0   0     0 $attrs{constants} ||= { %{ $self->constants } };
  0         0  
23 0         0 $self->new( %$self, %attrs );
24             }
25              
26             # potentially hot method
27             sub clone_and_merge {
28 128     128 1 297 my $self= shift;
29 128 50 33     602 my %attrs= @_==1 && ref $_[0] eq 'HASH'? %{$_[0]} : @_;
  0         0  
30 128 50       215 $attrs{variables}= { %{ $self->variables }, ($attrs{variables}? %{ $attrs{variables} } : () ) };
  128         511  
  128         625  
31 128 50       262 $attrs{constants}= { %{ $self->constants }, ($attrs{constants}? %{ $attrs{constants} } : () ) };
  128         442  
  0         0  
32 128         2648 $self->new( %$self, %attrs );
33             }
34              
35              
36             sub get_constant {
37 76     76 1 583 my ($self, $name)= @_;
38 76         158 $name= lc $name;
39 76         280 $self->constants->{$name};
40             }
41              
42             sub get_value {
43 64     64 1 243 my ($self, $name)= @_;
44 64         126 $name= lc $name;
45 64         148 my $set= $self->variables;
46 64 50       291 return $set->{$name} if exists $set->{$name};
47 0         0 $set= $self->constants;
48 0 0       0 return $set->{$name} if exists $set->{$name};
49 0 0       0 die ErrREF("Unknown variable or constant '$_[1]'")
50             if $self->die_on_unknown_value;
51 0         0 return undef;
52             }
53              
54             sub get_function {
55 230     230 1 878 my ($self, $name)= @_;
56 230         511 $name= lc $name;
57             # The value 0E0 is a placeholder for "no such function"
58 230   66     813 my $info= $self->{_function_cache}{$name} ||= do {
59 219         550 my %tmp= $self->_collect_function_info($name);
60 219 50       3460 keys %tmp? \%tmp : '0E0';
61             };
62 230 50       864 return ref $info? $info : undef;
63             }
64              
65             sub _collect_function_info {
66 219     219   390 my ($self, $name)= @_;
67 219         978 my $fn= $self->can("fn_$name");
68 219         1020 my $ev= $self->can("nodeval_$name");
69 219         800 my $pl= $self->can("perlgen_$name");
70             return
71 219 100       1239 ($fn? ( native => $fn ) : ()),
    100          
    100          
72             ($ev? ( evaluator => $ev ) : ()),
73             ($pl? ( perl_generator => $pl ) : ()),
74             $self->maybe::next::method($name);
75             }
76              
77              
78             sub evaluate_call {
79 164     164 1 366 my ($self, $call)= @_;
80 164         395 my $name= $call->function_name;
81 164 50       495 my $info= $self->get_function($name)
82             or die ErrNAME("Unknown function '$name'");
83             # If the namespace supplies a special evaluator method, use that
84 164 100       546 if (my $eval= $info->{evaluator}) {
    50          
85 14         58 return $self->$eval($call);
86             }
87             # Else if the namespace supplies a native plain-old-function, convert the parameters
88             # from parse nodes to plain values and then call the function.
89             elsif (my $fn= $info->{native}) {
90             # The function might be a perl builtin, so need to activate the same
91             # warning flags that would be used by the compiled version.
92 5     5   6116 use warnings FATAL => 'numeric', 'uninitialized';
  5         13  
  5         1106  
93 150         243 my @args= map $_->evaluate($self), @{ $call->parameters };
  150         392  
94 150         7761 return $fn->(@args);
95             }
96             # Else the definition of the function is incomplete.
97 0           die ErrNAME("Incomplete function '$name' cannot be evaluated");
98             }
99              
100              
101             sub find_methods {
102 0     0 1   my ($self, $pattern)= @_;
103 0   0       my $todo= mro::get_linear_isa(ref $self || $self);
104 0           my (%seen, @ret);
105 0           for my $pkg (@$todo) {
106 5     5   40 my $stash= do { no strict 'refs'; \%{$pkg.'::'} };
  5         12  
  5         665  
  0            
  0            
  0            
107 0   0       push @ret, grep +($_ =~ $pattern and defined $stash->{$_}{CODE} and !$seen{$_}++), keys %$stash;
108             }
109 0           \@ret;
110             }
111              
112              
113              
114             1;
115              
116             __END__
117              
118             =pod
119              
120             =encoding UTF-8
121              
122             =head1 NAME
123              
124             Language::FormulaEngine::Namespace - Object holding function and variable names
125              
126             =head1 VERSION
127              
128             version 0.06
129              
130             =head1 SYNOPSIS
131              
132             my $ns= Language::FormulaEngine::Namespace->new( values => \%val_by_name );
133              
134             =head1 DESCRIPTION
135              
136             A FormulaEngine Namespace is an object that provides a set of functions and named values.
137             It can also affect language semantics through it's implementation of those functions.
138              
139             The default implementation provides all functions of its own namespace which begin with
140             the prefix "fn_" or "eval_", and provides them case-insensitive. Named values are provided
141             from hashrefs of L</constants> and L</variables>, also case-insensitive.
142              
143             You can subclass this (or just write a class with the same interface) to provide more advanced
144             lookup for the functions or values.
145              
146             =head1 ATTRIBUTES
147              
148             =head2 variables
149              
150             A hashref of C<< name => value >> which formulas may reference. The keys should be lowercase,
151             and incoming variable requests will be converted to lowercase before checking this hash.
152             Variables will not be "compiled" into perl coderefs, and will be looked up from the namespace
153             every time a formula is evaluated.
154              
155             =head2 constants
156              
157             Same as L</variables>, but these may be compiled into coderefs.
158              
159             =head2 die_on_unknown_value
160              
161             Controls behavior of L</get_value>. If false (the default) unknown symbol names will resolve
162             as perl C<undef> values. If true, unknown symbol names will throw an
163             L<ErrREF exception|Language::FormulaEngine::Error/ErrREF>.
164              
165             =head1 METHODS
166              
167             =head2 clone
168              
169             my $ns2= $ns1->clone(variables => \%different_vars);
170              
171             Return a copy of the namespace, optionally with some attributes overridden.
172              
173             =head2 clone_and_merge
174              
175             my $ns2= $ns1->clone_and_merge(variables => \%override_some_vars);
176              
177             Return a copy of the namespace, with any new attributes merged into the existing ones.
178              
179             =head2 get_constant
180              
181             my $val= $ns->get_constant( $symbolic_name );
182              
183             Mehod to check for availability of a named constant, before assuming that a name is a variable.
184             This never throws an exception; it returns C<undef> if no constant exists by that name.
185              
186             =head2 get_value
187              
188             my $val= $ns->get_value( $symbolic_name );
189              
190             Lowercases C<$symbolic_name> and then looks in C<variables> or C<constants>. May die depending
191             on setting of L</die_on_unknown_value>.
192              
193             =head2 get_function
194              
195             $ns->get_function( $symbolic_name );
196            
197             # Returns:
198             # {
199             # native => $coderef,
200             # evaluator => $method,
201             # perl_generator => $method,
202             # }
203              
204             If a function by this name is available in the namespace, ths method returns a hashref of
205             information about it. It may include some or all of the following:
206              
207             =over
208              
209             =item native
210              
211             A native perl implementation of this function. Speficially, a non-method plain old function
212             that takes a list of values (not parse nodes) and returns the computed value.
213              
214             Note that if C<< Sub::Util::subname($native) >> returns a name with colons in it, the compiler
215             will assume it is safe to inline this function name into the generated perl code. (but this
216             only happens if C<perl_generator> was not available)
217              
218             =item evaluator
219              
220             A coderef or method name which will be called on the namespace to evaluate a parse tree for
221             this function.
222              
223             $value= $namespace->$evaluator( $parse_node );
224              
225             =item perl_generator
226              
227             A coderef or method name which will be called on the namespace to convert a parse tree into
228             perl source code.
229              
230             $perl= $namespace->$generator( $compiler, $parse_node );
231              
232             =back
233              
234             The default implementation lowercases the C<$symbolic_name> and then checks for three method
235             names: C<< $self->can("fn_$name") >>, C<< $self->can("nodeval_$name") >> and
236             C<< $self->can("perlgen_$name") >>.
237              
238             =head2 evaluate_call
239              
240             my $value= $namespace->evaluate_call( $Call_parse_node );
241              
242             Evaluate a function call, passing it either to a specialized evaluator or performing a more
243             generic evaluation of the arguments followed by calling a native perl function.
244              
245             =head2 find_methods
246              
247             Find methods on this object that match a regex.
248              
249             my $method_name_arrayref= $ns->find_methods(qr/^fn_/);
250              
251             =head1 FUNCTION LIBRARY
252              
253             Theis base Namespace class does not contain any user-visible functions; those are found within
254             the sub-classes such L<Language::FormulaEngine::Namespace::Default>.
255              
256             =head1 AUTHOR
257              
258             Michael Conrad <mconrad@intellitree.com>
259              
260             =head1 COPYRIGHT AND LICENSE
261              
262             This software is copyright (c) 2021 by Michael Conrad, IntelliTree Solutions llc.
263              
264             This is free software; you can redistribute it and/or modify it under
265             the same terms as the Perl 5 programming language system itself.
266              
267             =cut