File Coverage

blib/lib/Math/Formula/Context.pm
Criterion Covered Total %
statement 88 93 94.6
branch 45 64 70.3
condition 21 38 55.2
subroutine 22 22 100.0
pod 14 15 93.3
total 190 232 81.9


line stmt bran cond sub pod time code
1             # This code is part of Perl distribution Math-Formula version 0.18.
2             # The POD got stripped from this file by OODoc version 3.03.
3             # For contributors see file ChangeLog.
4              
5             # This software is copyright (c) 2023-2025 by Mark Overmeer.
6              
7             # This is free software; you can redistribute it and/or modify it under
8             # the same terms as the Perl 5 programming language system itself.
9             # SPDX-License-Identifier: Artistic-1.0-Perl OR GPL-1.0-or-later
10              
11             #oodist: *** DO NOT USE THIS VERSION FOR PRODUCTION ***
12             #oodist: This file contains OODoc-style documentation which will get stripped
13             #oodist: during its release in the distribution. You can use this file for
14             #oodist: testing, however the code of this development version may be broken!
15              
16             package Math::Formula::Context;{
17             our $VERSION = '0.18';
18             }
19              
20              
21 15     15   523778 use warnings;
  15         39  
  15         1092  
22 15     15   179 use strict;
  15         29  
  15         462  
23              
24 15     15   77 use Log::Report 'math-formula';
  15         27  
  15         163  
25 15     15   5628 use Scalar::Util qw/blessed/;
  15         50  
  15         36284  
26              
27             #--------------------
28              
29 16     16 1 1045802 sub new(%) { my $class = shift; (bless {}, $class)->init({@_}) }
  16         140  
30              
31             sub _default($$$$)
32 64     64   9752 { my ($self, $name, $type, $value, $default) = @_;
33 64 0       664 my $form
    50          
    50          
    50          
    100          
34             = ! $value ? $type->new(undef, $default)
35             : ! blessed $value ? ($value ? Math::Formula->new($name, $value) : undef)
36             : $value->isa('Math::Formula') ? $value
37             : $value->isa('Math::Formula::Type') ? $value
38             : error __x"unexpected value for '{name}' in #{context}", name => $name, context => $self->name;
39             }
40              
41             sub init($)
42 16     16 0 55 { my ($self, $args) = @_;
43 16 50       98 my $name = $args->{name} or error __x"context requires a name";
44 16 100       202 my $node = blessed $name ? $name : MF::STRING->new(undef, $name);
45 16         134 $self->{MFC_name} = $node->value;
46              
47 16         44 my $now;
48             $self->{MFC_attrs} = {
49             ctx_name => $node,
50             ctx_version => $self->_default(version => 'MF::STRING', $args->{version}, "1.00"),
51             ctx_created => $self->_default(created => 'MF::DATETIME', $args->{created}, $now = DateTime->now),
52             ctx_updated => $self->_default(updated => 'MF::DATETIME', $args->{updated}, $now //= DateTime->now),
53 16   33     99 ctx_mf_version => $self->_default(mf_version => 'MF::STRING', $args->{mf_version}, $Math::Formula::VERSION),
54             };
55              
56 16   100     175 $self->{MFC_lead} = $args->{lead_expressions} // '';
57 16         53 $self->{MFC_forms} = { };
58 16         50 $self->{MFC_frags} = { };
59 16 100       92 if(my $forms = $args->{formulas})
60 3 100       26 { $self->add(ref $forms eq 'ARRAY' ? @$forms : $forms);
61             }
62              
63 16         85 $self->{MFC_claims} = { };
64 16         46 $self->{MFC_capts} = [ ];
65 16         98 $self;
66             }
67              
68             # For save()
69             sub _index()
70 2     2   4 { my $self = shift;
71             +{ attributes => $self->{MFC_attrs},
72             formulas => $self->{MFC_forms},
73             fragments => $self->{MFC_frags},
74 2         11 };
75             }
76              
77             #--------------------
78              
79 13     13 1 1290 sub name { $_[0]->{MFC_name} }
80 20     20 1 72 sub lead_expressions { $_[0]->{MFC_lead} }
81              
82             #--------------------
83              
84             sub attribute($)
85 4     4 1 13 { my ($self, $name) = @_;
86 4 50       20 my $def = $self->{MFC_attrs}{$name} or return;
87 4         27 Math::Formula->new($name => $def);
88             }
89              
90             #--------------------
91             #XXX example with fragment
92              
93             sub add(@)
94 41     41 1 108 { my $self = shift;
95 41 100       165 unless(ref $_[0])
96 34         52 { my $name = shift;
97 34 50       115 return $name =~ s/^#// ? $self->addFragment($name, @_) : $self->addFormula($name, @_);
98             }
99              
100 7         25 foreach my $obj (@_)
101 9 100 33     49 { if(ref $obj eq 'HASH')
    50 0        
    0          
102 7         50 { $self->add($_, $obj->{$_}) for keys %$obj;
103             }
104             elsif(blessed $obj && $obj->isa('Math::Formula'))
105 2         10 { $self->{MFC_forms}{$obj->name} = $obj;
106             }
107             elsif(blessed $obj && $obj->isa('Math::Formula::Context'))
108 0         0 { $self->{MFC_frags}{$obj->name} = $obj;
109             }
110             else
111 0         0 { panic __x"formula add '{what}' not understood", what => $obj;
112             }
113             }
114              
115 7         17 undef;
116             }
117              
118              
119             sub addFormula(@)
120 45     45 1 599 { my ($self, $name) = (shift, shift);
121 45         82 my $next = $_[0];
122 45         90 my $forms = $self->{MFC_forms};
123              
124 45 100 33     227 if(ref $name)
    50          
125 1 50 33     20 { return $forms->{$name->name} = $name
      33        
126             if !@_ && blessed $name && $name->isa('Math::Formula');
127             }
128             elsif(! ref $name && @_)
129 44 100 100     323 { return $forms->{$name} = $next
      100        
130             if @_==1 && blessed $next && $next->isa('Math::Formula');
131              
132 39 100       114 return $forms->{$name} = Math::Formula->new($name, @_)
133             if ref $next eq 'CODE';
134              
135 38 100 66     155 return $forms->{$name} = Math::Formula->new($name, @_)
136             if blessed $next && $next->isa('Math::Formula::Type');
137              
138 25 100 100     148 my ($data, %attrs) = @_==1 && ref $next eq 'ARRAY' ? @$next : $next;
139 25 100       75 if(my $r = $attrs{returns})
140 5 50       67 { my $typed = $r->isa('MF::STRING') ? $r->new(undef, $data) : $data;
141 5         29 return $forms->{$name} = Math::Formula->new($name, $typed, %attrs);
142             }
143              
144 20 100       56 if(length(my $leader = $self->lead_expressions))
145 3 100       39 { my $typed = $data =~ s/^\Q$leader// ? $data : \$data;
146 3         17 return $forms->{$name} = Math::Formula->new($name, $typed, %attrs);
147             }
148              
149 17         98 return $forms->{$name} = Math::Formula->new($name, $data, %attrs);
150             }
151              
152 0         0 error __x"formula declaration '{name}' not understood", name => $name;
153             }
154              
155              
156 55     55 1 252 sub formula($) { $_[0]->{MFC_forms}{$_[1]} }
157              
158              
159             sub addFragment($;$)
160 1     1 1 9 { my $self = shift;
161 1 50       8 my ($name, $fragment) = @_==2 ? @_ : ($_[0]->name, $_[0]);
162 1         15 $self->{MFC_frags}{$name} = MF::FRAGMENT->new($name, $fragment);
163             }
164              
165              
166 7     7 1 30 sub fragment($) { $_[0]->{MFC_frags}{$_[1]} }
167              
168             #--------------------
169              
170             sub evaluate($$%)
171 32     32 1 79 { my ($self, $name) = (shift, shift);
172              
173             # Wow, I am impressed! Caused by prefix(#,.) -> infix
174 32 50       75 length $name or return $self;
175              
176 32 100       141 my $form = $name =~ /^ctx_/ ? $self->attribute($name) : $self->formula($name);
177 32 50       76 unless($form)
178 0         0 { warning __x"no formula '{name}' in {context}", name => $name, context => $self->name;
179 0         0 return undef;
180             }
181              
182 32         165 my $claims = $self->{MFC_claims};
183 32 50       105 ! $claims->{$name}++
184             or error __x"recursion in expression '{name}' at {context}", name => $name, context => $self->name;
185              
186 32         139 my $result = $form->evaluate($self, @_);
187              
188 32         69 delete $claims->{$name};
189 32         128 $result;
190             }
191              
192              
193             sub run($%)
194 21     21 1 60 { my ($self, $expr, %args) = @_;
195 21   33     181 my $name = delete $args{name} || join '#', (caller)[1,2];
196 21         135 my $result = Math::Formula->new($name, $expr)->evaluate($self, %args);
197              
198 21   66     1813 while($result && $result->isa('MF::NAME'))
199 6         29 { $result = $self->evaluate($result->token, %args);
200             }
201              
202 21         70 $result;
203             }
204              
205              
206             sub value($@)
207 12     12 1 304 { my $self = shift;
208 12         60 my $result = $self->run(@_);
209 12 50       52 $result ? $result->value : undef;
210             }
211              
212              
213 10     10 1 35 sub setCaptures($) { $_[0]{MFC_capts} = $_[1] }
214 7     7   29 sub _captures() { $_[0]{MFC_capts} }
215              
216              
217 4     4 1 12 sub capture($) { $_[0]->_captures->[$_[1]] }
218              
219             #--------------------
220              
221             1;