File Coverage

blib/lib/Math/Formula/Token.pm
Criterion Covered Total %
statement 59 62 95.1
branch 9 14 64.2
condition 4 6 66.6
subroutine 25 27 92.5
pod 0 2 0.0
total 97 111 87.3


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 26     26   1133 use warnings;
  26         62  
  26         881  
6 26     26   166 use strict;
  26         70  
  26         812  
7              
8             package Math::Formula::Token;
9 26     26   158 use vars '$VERSION';
  26         64  
  26         3702  
10             $VERSION = '0.14';
11              
12              
13             #!!! The declarations of all other packages in this file are indented to avoid
14             #!!! indexing by CPAN.
15              
16             #!!! Classes and methods which are of interest of normal users are documented
17             #!!! in ::Types, because the package set-up caused too many issues with OODoc.
18              
19             # The object is an ARRAY.
20 1558     1558 0 39925 sub new(%) { my $class = shift; bless [@_], $class }
  1558         8235  
21              
22              
23             # Returns the token in string form. This may be a piece of text as parsed
24             # from the expression string, or generated when the token is computed.
25              
26 640   100 640 0 33358 sub token { $_[0][0] //= $_[0]->_token($_[0]->value) }
27 50     50   276 sub _token { $_[1] }
28              
29             #-------------------
30             # MF::PARENS, parenthesis tokens
31             # Parser object to administer parenthesis, but disappears in the AST.
32              
33             package
34             MF::PARENS;
35              
36 26     26   182 use base 'Math::Formula::Token';
  26         80  
  26         3599  
37              
38 12     12   39 sub level { $_[0][1] }
39              
40             #-------------------
41             # MF::OPERATOR, operator of yet unknown type.
42             # In the AST upgraded to either MF::PREFIX or MF::INFIX.
43              
44             package
45             MF::OPERATOR;
46              
47 26     26   186 use base 'Math::Formula::Token';
  26         59  
  26         2360  
48 26     26   716 use Log::Report 'math-formula', import => [ 'panic' ];
  26         106310  
  26         164  
49              
50             use constant {
51             # Associativity
52 26         9081 LTR => 1, RTL => 2, NOCHAIN => 3,
53 26     26   3732 };
  26         73  
54              
55             # method operator(): Returns the operator value in this token, which
56             # "accidentally" is the same value as the M<token()> method produces.
57 232     232   512 sub operator() { $_[0][0] }
58              
59             sub _compute
60 0     0   0 { my ($self, $context, $expr) = @_;
61 0         0 panic +(ref $self) . ' does not compute';
62             }
63              
64             my %table;
65             {
66             # Prefix operators and parenthesis are not needed here
67             # Keep in sync with the table in Math::Formula
68             my @order = (
69             # [ LTR, ',' ],
70             [ LTR, '?', ':' ], # ternary ?:
71             [ LTR, qw/or xor/, '//' ],
72             [ LTR, 'and' ],
73             [ NOCHAIN, qw/ <=> < <= == != >= > / ],
74             [ NOCHAIN, qw/ cmp lt le eq ne ge gt/ ],
75             [ LTR, qw/+ - ~/ ],
76             [ LTR, qw!* / %! ],
77             [ LTR, qw/=~ !~ like unlike/ ],
78             [ LTR, '#', '.' ],
79             );
80              
81             my $level;
82             foreach (@order)
83             { my ($assoc, @line) = @$_;
84             $level++;
85             $table{$_} = [ $level, $assoc ] for @line;
86             }
87             }
88              
89             # method find($operator)
90             # Returns a list with knowledge about a know operator.
91             # The first argument is a priority level for this operator. The actual
92             # priority numbers may change over releases of this module.
93             # The second value is a constant of associativety. Either the constant
94             # LTR (compute left to right), RTL (right to left), or NOCHAIN (non-stackable
95             # operator).
96              
97 250   33 250   371 sub find($) { @{$table{$_[1]} // panic "op $_[1]" } }
  250         1123  
98              
99             #-------------------
100             # MF::PREFIX, monadic prefix operator
101             # Prefix operators process the result of the expression which follows it.
102             # This is a specialization from the MF::OPERATOR type, hence shares its methods.
103              
104             package
105             MF::PREFIX;
106              
107 26     26   213 use base 'MF::OPERATOR';
  26         62  
  26         10966  
108              
109             # method child(): Returns the AST where this operator works on.
110 28     28   99 sub child() { $_[0][1] }
111              
112             sub _compute($$)
113 28     28   59 { my ($self, $context, $expr) = @_;
114 28 50       63 my $value = $self->child->_compute($context, $expr)
115             or return undef;
116              
117 28         86 $value->prefix($self->operator, $context);
118             }
119              
120             #-------------------
121             # MF::INFIX, infix (dyadic) operator
122             # Infix operators have two arguments. This is a specialization from the
123             # MF::OPERATOR type, hence shares its methods.
124              
125             package
126             MF::INFIX;
127              
128 26     26   211 use base 'MF::OPERATOR';
  26         58  
  26         18923  
129              
130             # method left(): Returns the AST left from the infix operator.
131 204     204   638 sub left() { $_[0][1] }
132              
133             # method right(): Returns the AST right from the infix operator.
134 204     204   498 sub right() { $_[0][2] }
135              
136             my %comparison = (
137             '<' => [ '<=>', sub { $_[0] < 0 } ],
138             '<=' => [ '<=>', sub { $_[0] <= 0 } ],
139             '==' => [ '<=>', sub { $_[0] == 0 } ],
140             '!=' => [ '<=>', sub { $_[0] != 0 } ],
141             '>=' => [ '<=>', sub { $_[0] >= 0 } ],
142             '>' => [ '<=>', sub { $_[0] > 0 } ],
143             'lt' => [ 'cmp', sub { $_[0] < 0 } ],
144             'le' => [ 'cmp', sub { $_[0] <= 0 } ],
145             'eq' => [ 'cmp', sub { $_[0] == 0 } ],
146             'ne' => [ 'cmp', sub { $_[0] != 0 } ],
147             'ge' => [ 'cmp', sub { $_[0] >= 0 } ],
148             'gt' => [ 'cmp', sub { $_[0] > 0 } ],
149             );
150              
151 0     0   0 sub _compare_ops { keys %comparison }
152              
153             sub _compute($$)
154 204     204   416 { my ($self, $context, $expr) = @_;
155              
156 204 50       493 my $left = $self->left->_compute($context, $expr)
157             or return undef;
158              
159 204 50       487 my $right = $self->right->_compute($context, $expr)
160             or return undef;
161              
162             # Comparison operators are all implemented via a space-ship, when available.
163             # Otherwise, the usual track is taken.
164              
165 204         479 my $op = $self->operator;
166 204 100       533 if(my $rewrite = $comparison{$op})
167 36         82 { my ($spaceship, $compare) = @$rewrite;
168 36 50       91 if(my $result = $left->infix($spaceship, $right, $context, $expr))
169 36         94 { return MF::BOOLEAN->new(undef, $compare->($result->value));
170             }
171             }
172              
173 168         478 $left->infix($op, $right, $context, $expr);
174             }
175              
176              
177             #-------------------
178             # MF::TERNARY, if ? then : else
179             # Ternary operators have three arguments. This is a specialization from the
180             # MF::OPERATOR type, hence shares its methods.
181              
182             package
183             MF::TERNARY;
184              
185 26     26   198 use base 'MF::OPERATOR';
  26         61  
  26         10980  
186              
187 7     7   26 sub condition() { $_[0][1] }
188 3     3   10 sub then() { $_[0][2] }
189 4     4   10 sub else() { $_[0][3] }
190              
191             sub _compute($$)
192 7     7   16 { my ($self, $context, $expr) = @_;
193              
194 7 50       22 my $cond = $self->condition->_compute($context, $expr)
195             or return undef;
196              
197 7 100       28 ($cond->value ? $self->then : $self->else)->_compute($context, $expr)
198             }
199              
200             1;