File Coverage

blib/lib/Math/Formula/Context.pm
Criterion Covered Total %
statement 89 96 92.7
branch 39 64 60.9
condition 19 38 50.0
subroutine 22 23 95.6
pod 14 15 93.3
total 183 236 77.5


line stmt bran cond sub pod time code
1             # Copyrights 2023 by [Mark Overmeer <markov@cpan.org>].
2             # For other contributors see ChangeLog.
3             # See the manual pages for details on the licensing terms.
4             # Pod stripped from pm file by OODoc 2.03.
5              
6             package Math::Formula::Context;
7 15     15   6973 use vars '$VERSION';
  15         30  
  15         753  
8             $VERSION = '0.16';
9              
10              
11 15     15   77 use warnings;
  15         23  
  15         314  
12 15     15   62 use strict;
  15         43  
  15         303  
13              
14 15     15   70 use Log::Report 'math-formula';
  15         27  
  15         130  
15 15     15   4391 use Scalar::Util qw/blessed/;
  15         43  
  15         21034  
16              
17              
18 13     13 1 898 sub new(%) { my $class = shift; (bless {}, $class)->init({@_}) }
  13         75  
19              
20             sub _default($$$$)
21 52     52   5062 { my ($self, $name, $type, $value, $default) = @_;
22 52 0       224 my $form
    0          
    0          
    0          
    50          
23             = ! $value ? $type->new(undef, $default)
24             : ! blessed $value ? ($value ? Math::Formula->new($name, $value) : undef)
25             : $value->isa('Math::Formula') ? $value
26             : $value->isa('Math::Formula::Type') ? $value
27             : error __x"unexpected value for '{name}' in #{context}", name => $name, context => $self->name;
28             }
29              
30             sub init($)
31 13     13 0 36 { my ($self, $args) = @_;
32 13 50       47 my $name = $args->{name} or error __x"context requires a name";
33 13 50       124 my $node = blessed $name ? $name : MF::STRING->new(undef, $name);
34 13         68 $self->{MFC_name} = $node->value;
35              
36 13         24 my $now;
37             $self->{MFC_attrs} = {
38             ctx_name => $node,
39             ctx_version => $self->_default(version => 'MF::STRING', $args->{version}, "1.00"),
40             ctx_created => $self->_default(created => 'MF::DATETIME', $args->{created}, $now = DateTime->now),
41             ctx_updated => $self->_default(updated => 'MF::DATETIME', $args->{updated}, $now //= DateTime->now),
42 13   33     61 ctx_mf_version => $self->_default(mf_version => 'MF::STRING', $args->{mf_version}, $Math::Formula::VERSION),
43             };
44              
45 13   100     81 $self->{MFC_lead} = $args->{lead_expressions} // '';
46 13         37 $self->{MFC_forms} = { };
47 13         41 $self->{MFC_frags} = { };
48 13 100       38 if(my $forms = $args->{formulas})
49 2 100       12 { $self->add(ref $forms eq 'ARRAY' ? @$forms : $forms);
50             }
51              
52 13         28 $self->{MFC_claims} = { };
53 13         34 $self->{MFC_capts} = [ ];
54 13         42 $self;
55             }
56              
57             # For save()
58             sub _index()
59 0     0   0 { my $self = shift;
60             +{ attributes => $self->{MFC_attrs},
61             formulas => $self->{MFC_forms},
62             fragments => $self->{MFC_frags},
63 0         0 };
64             }
65              
66             #--------------
67              
68 11     11 1 1385 sub name { $_[0]->{MFC_name} }
69 14     14 1 38 sub lead_expressions { $_[0]->{MFC_lead} }
70              
71             #--------------
72              
73             sub attribute($)
74 4     4 1 8 { my ($self, $name) = @_;
75 4 50       13 my $def = $self->{MFC_attrs}{$name} or return;
76 4         17 Math::Formula->new($name => $def);
77             }
78              
79             #--------------
80             #XXX example with fragment
81              
82             sub add(@)
83 17     17 1 37 { my $self = shift;
84 17 100       42 unless(ref $_[0])
85 13         16 { my $name = shift;
86 13 50       40 return $name =~ s/^#// ? $self->addFragment($name, @_) : $self->addFormula($name, @_);
87             }
88              
89 4         9 foreach my $obj (@_)
90 6 100 33     25 { if(ref $obj eq 'HASH')
    50 0        
    0          
91 4         25 { $self->add($_, $obj->{$_}) for keys %$obj;
92             }
93             elsif(blessed $obj && $obj->isa('Math::Formula'))
94 2         7 { $self->{MFC_forms}{$obj->name} = $obj;
95             }
96             elsif(blessed $obj && $obj->isa('Math::Formula::Context'))
97 0         0 { $self->{MFC_frags}{$obj->name} = $obj;
98             }
99             else
100 0         0 { panic __x"formula add '{what}' not understood", what => $obj;
101             }
102             }
103              
104 4         7 undef;
105             }
106              
107              
108             sub addFormula(@)
109 23     23 1 410 { my ($self, $name) = (shift, shift);
110 23         34 my $next = $_[0];
111 23         32 my $forms = $self->{MFC_forms};
112              
113 23 100 33     103 if(ref $name)
    50          
114 1 50 33     14 { return $forms->{$name->name} = $name
      33        
115             if !@_ && blessed $name && $name->isa('Math::Formula');
116             }
117             elsif(! ref $name && @_)
118 22 100 100     106 { return $forms->{$name} = $next
      66        
119             if @_==1 && blessed $next && $next->isa('Math::Formula');
120              
121 19 100       45 return $forms->{$name} = Math::Formula->new($name, @_)
122             if ref $next eq 'CODE';
123              
124 18 50 33     47 return $forms->{$name} = Math::Formula->new($name, @_)
125             if blessed $next && $next->isa('Math::Formula::Type');
126              
127 18 100 100     74 my ($data, %attrs) = @_==1 && ref $next eq 'ARRAY' ? @$next : $next;
128 18 100       40 if(my $r = $attrs{returns})
129 4 50       30 { my $typed = $r->isa('MF::STRING') ? $r->new(undef, $data) : $data;
130 4         20 return $forms->{$name} = Math::Formula->new($name, $typed, %attrs);
131             }
132              
133 14 100       44 if(length(my $leader = $self->lead_expressions))
134 3 100       32 { my $typed = $data =~ s/^\Q$leader// ? $data : \$data;
135 3         11 return $forms->{$name} = Math::Formula->new($name, $typed, %attrs);
136             }
137              
138 11         44 return $forms->{$name} = Math::Formula->new($name, $data, %attrs);
139             }
140              
141 0         0 error __x"formula declaration '{name}' not understood", name => $name;
142             }
143              
144              
145 49     49 1 142 sub formula($) { $_[0]->{MFC_forms}{$_[1]} }
146              
147              
148             sub addFragment($;$)
149 1     1 1 5 { my $self = shift;
150 1 50       4 my ($name, $fragment) = @_==2 ? @_ : ($_[0]->name, $_[0]);
151 1         10 $self->{MFC_frags}{$name} = MF::FRAGMENT->new($name, $fragment);
152             }
153              
154              
155 7     7 1 15 sub fragment($) { $_[0]->{MFC_frags}{$_[1]} }
156              
157             #-------------------
158              
159             sub evaluate($$%)
160 28     28 1 51 { my ($self, $name) = (shift, shift);
161              
162             # Wow, I am impressed! Caused by prefix(#,.) -> infix
163 28 50       54 length $name or return $self;
164              
165 28 100       78 my $form = $name =~ /^ctx_/ ? $self->attribute($name) : $self->formula($name);
166 28 50       53 unless($form)
167 0         0 { warning __x"no formula '{name}' in {context}", name => $name, context => $self->name;
168 0         0 return undef;
169             }
170              
171 28         37 my $claims = $self->{MFC_claims};
172 28 50       65 ! $claims->{$name}++
173             or error __x"recursion in expression '{name}' at {context}",
174             name => $name, context => $self->name;
175              
176 28         85 my $result = $form->evaluate($self, @_);
177              
178 28         50 delete $claims->{$name};
179 28         82 $result;
180             }
181              
182              
183             sub run($%)
184 21     21 1 46 { my ($self, $expr, %args) = @_;
185 21   33     125 my $name = delete $args{name} || join '#', (caller)[1,2];
186 21         74 my $result = Math::Formula->new($name, $expr)->evaluate($self, %args);
187              
188 21   66     168 while($result && $result->isa('MF::NAME'))
189 6         18 { $result = $self->evaluate($result->token, %args);
190             }
191              
192 21         54 $result;
193             }
194              
195              
196             sub value($@)
197 12     12 1 399 { my $self = shift;
198 12         25 my $result = $self->run(@_);
199 12 50       48 $result ? $result->value : undef;
200             }
201              
202              
203 10     10 1 24 sub setCaptures($) { $_[0]{MFC_capts} = $_[1] }
204 7     7   21 sub _captures() { $_[0]{MFC_capts} }
205              
206              
207 4     4 1 20 sub capture($) { $_[0]->_captures->[$_[1]] }
208              
209             #--------------
210              
211             1;