File Coverage

blib/lib/Math/Formula/Context.pm
Criterion Covered Total %
statement 85 92 92.3
branch 39 64 60.9
condition 19 38 50.0
subroutine 19 20 95.0
pod 12 13 92.3
total 174 227 76.6


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 13     13   7603 use vars '$VERSION';
  13         43  
  13         816  
8             $VERSION = '0.14';
9              
10              
11 13     13   92 use warnings;
  13         27  
  13         373  
12 13     13   66 use strict;
  13         25  
  13         430  
13              
14 13     13   89 use Log::Report 'math-formula';
  13         41  
  13         119  
15 13     13   4897 use Scalar::Util qw/blessed/;
  13         34  
  13         20913  
16              
17              
18 11     11 1 1002 sub new(%) { my $class = shift; (bless {}, $class)->init({@_}) }
  11         81  
19              
20             sub _default($$$$)
21 44     44   5165 { my ($self, $name, $type, $value, $default) = @_;
22 44 0       241 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 11     11 0 34 { my ($self, $args) = @_;
32 11 50       53 my $name = $args->{name} or error __x"context requires a name";
33 11 50       125 my $node = blessed $name ? $name : MF::STRING->new(undef, $name);
34 11         61 $self->{MFC_name} = $node->value;
35              
36 11         30 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 11   33     61 ctx_mf_version => $self->_default(mf_version => 'MF::STRING', $args->{mf_version}, $Math::Formula::VERSION),
43             };
44              
45 11   100     93 $self->{MFC_lead} = $args->{lead_expressions} // '';
46 11         36 $self->{MFC_forms} = { };
47 11         58 $self->{MFC_frags} = { };
48 11 100       51 if(my $forms = $args->{formulas})
49 2 100       13 { $self->add(ref $forms eq 'ARRAY' ? @$forms : $forms);
50             }
51              
52 11         29 $self->{MFC_claims} = { };
53 11         46 $self;
54             }
55              
56             # For save()
57             sub _index()
58 0     0   0 { my $self = shift;
59             +{ attributes => $self->{MFC_attrs},
60             formulas => $self->{MFC_forms},
61             fragments => $self->{MFC_frags},
62 0         0 };
63             }
64              
65             #--------------
66              
67 11     11 1 1018 sub name { $_[0]->{MFC_name} }
68 14     14 1 47 sub lead_expressions { $_[0]->{MFC_lead} }
69              
70             #--------------
71              
72             sub attribute($)
73 4     4 1 15 { my ($self, $name) = @_;
74 4 50       16 my $def = $self->{MFC_attrs}{$name} or return;
75 4         21 Math::Formula->new($name => $def);
76             }
77              
78             #--------------
79             #XXX example with fragment
80              
81             sub add(@)
82 17     17 1 47 { my $self = shift;
83 17 100       40 unless(ref $_[0])
84 13         22 { my $name = shift;
85 13 50       46 return $name =~ s/^#// ? $self->addFragment($name, @_) : $self->addFormula($name, @_);
86             }
87              
88 4         15 foreach my $obj (@_)
89 6 100 33     34 { if(ref $obj eq 'HASH')
    50 0        
    0          
90 4         29 { $self->add($_, $obj->{$_}) for keys %$obj;
91             }
92             elsif(blessed $obj && $obj->isa('Math::Formula'))
93 2         8 { $self->{MFC_forms}{$obj->name} = $obj;
94             }
95             elsif(blessed $obj && $obj->isa('Math::Formula::Context'))
96 0         0 { $self->{MFC_frags}{$obj->name} = $obj;
97             }
98             else
99 0         0 { panic __x"formula add '{what}' not understood", what => $obj;
100             }
101             }
102              
103 4         11 undef;
104             }
105              
106              
107             sub addFormula(@)
108 23     23 1 383 { my ($self, $name) = (shift, shift);
109 23         42 my $next = $_[0];
110 23         36 my $forms = $self->{MFC_forms};
111              
112 23 100 33     118 if(ref $name)
    50          
113 1 50 33     26 { return $forms->{$name->name} = $name
      33        
114             if !@_ && blessed $name && $name->isa('Math::Formula');
115             }
116             elsif(! ref $name && @_)
117 22 100 100     122 { return $forms->{$name} = $next
      66        
118             if @_==1 && blessed $next && $next->isa('Math::Formula');
119              
120 19 100       60 return $forms->{$name} = Math::Formula->new($name, @_)
121             if ref $next eq 'CODE';
122              
123 18 50 33     56 return $forms->{$name} = Math::Formula->new($name, @_)
124             if blessed $next && $next->isa('Math::Formula::Type');
125              
126 18 100 100     100 my ($data, %attrs) = @_==1 && ref $next eq 'ARRAY' ? @$next : $next;
127 18 100       53 if(my $r = $attrs{returns})
128 4 50       36 { my $typed = $r->isa('MF::STRING') ? $r->new(undef, $data) : $data;
129 4         19 return $forms->{$name} = Math::Formula->new($name, $typed, %attrs);
130             }
131              
132 14 100       36 if(length(my $leader = $self->lead_expressions))
133 3 100       32 { my $typed = $data =~ s/^\Q$leader// ? $data : \$data;
134 3         19 return $forms->{$name} = Math::Formula->new($name, $typed, %attrs);
135             }
136              
137 11         56 return $forms->{$name} = Math::Formula->new($name, $data, %attrs);
138             }
139              
140 0         0 error __x"formula declaration '{name}' not understood", name => $name;
141             }
142              
143              
144 49     49 1 204 sub formula($) { $_[0]->{MFC_forms}{$_[1]} }
145              
146              
147             sub addFragment($;$)
148 1     1 1 8 { my $self = shift;
149 1 50       7 my ($name, $fragment) = @_==2 ? @_ : ($_[0]->name, $_[0]);
150 1         12 $self->{MFC_frags}{$name} = MF::FRAGMENT->new($name, $fragment);
151             }
152              
153              
154 7     7 1 21 sub fragment($) { $_[0]->{MFC_frags}{$_[1]} }
155              
156              
157             sub evaluate($$%)
158 28     28 1 79 { my ($self, $name) = (shift, shift);
159              
160             # Wow, I am impressed! Caused by prefix(#,.) -> infix
161 28 50       69 length $name or return $self;
162              
163 28 100       119 my $form = $name =~ /^ctx_/ ? $self->attribute($name) : $self->formula($name);
164 28 50       65 unless($form)
165 0         0 { warning __x"no formula '{name}' in {context}", name => $name, context => $self->name;
166 0         0 return undef;
167             }
168              
169 28         52 my $claims = $self->{MFC_claims};
170 28 50       80 ! $claims->{$name}++
171             or error __x"recursion in expression '{name}' at {context}",
172             name => $name, context => $self->name;
173              
174 28         95 my $result = $form->evaluate($self, @_);
175              
176 28         66 delete $claims->{$name};
177 28         106 $result;
178             }
179              
180              
181             sub run($%)
182 21     21 1 81 { my ($self, $expr, %args) = @_;
183 21   33     150 my $name = delete $args{name} || join '#', (caller)[1,2];
184 21         102 my $result = Math::Formula->new($name, $expr)->evaluate($self, %args);
185              
186 21   66     217 while($result && $result->isa('MF::NAME'))
187 6         32 { $result = $self->evaluate($result->token, %args);
188             }
189              
190 21         63 $result;
191             }
192              
193              
194             sub value($@)
195 12     12 1 356 { my $self = shift;
196 12         42 my $result = $self->run(@_);
197 12 50       41 $result ? $result->value : undef;
198             }
199              
200             #--------------
201              
202             1;