File Coverage

blib/lib/Math/Formula/Token.pm
Criterion Covered Total %
statement 63 66 95.4
branch 10 16 62.5
condition 4 6 66.6
subroutine 27 29 93.1
pod 0 2 0.0
total 104 119 87.3


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 28     28   437139 use warnings;
  28         87  
  28         1778  
17 28     28   146 use strict;
  28         72  
  28         5589  
18              
19             package Math::Formula::Token;{
20             our $VERSION = '0.18';
21             }
22              
23              
24             #!!! The declarations of all other packages in this file are indented to avoid
25             #!!! indexing by CPAN.
26              
27             #!!! Classes and methods which are of interest of normal users are documented
28             #!!! in ::Types, because the package set-up caused too many issues with OODoc.
29              
30             # The object is an ARRAY.
31 1789     1789 0 475938 sub new(%) { my $class = shift; bless [@_], $class }
  1789         29828  
32              
33              
34             # Returns the token in string form. This may be a piece of text as parsed
35             # from the expression string, or generated when the token is computed.
36              
37 732   100 732 0 44810 sub token { $_[0][0] //= $_[0]->_token($_[0]->value) }
38 63     63   410 sub _token { $_[1] }
39              
40             # MF::PARENS, parenthesis tokens
41             # Parser object to administer parenthesis, but disappears in the AST.
42              
43             package
44             MF::PARENS;
45              
46 28     28   219 use base 'Math::Formula::Token'; #XXX module does not load with 'use parent'
  28         75  
  28         4859  
47              
48 12     12   29 sub level { $_[0][1] }
49              
50             # MF::OPERATOR, operator of yet unknown type.
51             # In the AST upgraded to either MF::PREFIX or MF::INFIX.
52              
53             package
54             MF::OPERATOR;
55              
56 28     28   270 use base 'Math::Formula::Token';
  28         70  
  28         13269  
57 28     28   916 use Log::Report 'math-formula', import => [ 'panic' ];
  28         118639  
  28         240  
58              
59             use constant {
60             # Associativity
61 28         12378 LTR => 1, RTL => 2, NOCHAIN => 3,
62 28     28   7393 };
  28         78  
63              
64             # method operator(): Returns the operator value in this token, which
65             # "accidentally" is the same value as the M<token()> method produces.
66 269     269   759 sub operator() { $_[0][0] }
67              
68             sub compute
69 0     0   0 { my ($self, $context) = @_;
70 0         0 panic +(ref $self) . ' does not compute';
71             }
72              
73             my %table;
74             {
75             # Prefix operators and parenthesis are not needed here
76             # Keep in sync with the table in Math::Formula
77             my @order = (
78             # [ LTR, ',' ],
79             [ LTR, '?', ':' ], # ternary ?:
80             [ NOCHAIN, '->' ],
81             [ LTR, qw/or xor/, '//' ],
82             [ LTR, 'and' ],
83             [ NOCHAIN, qw/ <=> < <= == != >= > / ],
84             [ NOCHAIN, qw/ cmp lt le eq ne ge gt/ ],
85             [ LTR, qw/+ - ~/ ],
86             [ LTR, qw!* / %! ],
87             [ LTR, qw/=~ !~ like unlike/ ],
88             [ LTR, '#', '.' ],
89             );
90              
91             my $level;
92             foreach (@order)
93             { my ($assoc, @line) = @$_;
94             $level++;
95             $table{$_} = [ $level, $assoc ] for @line;
96             }
97             }
98              
99             # method find($operator)
100             # Returns a list with knowledge about a know operator.
101             # The first argument is a priority level for this operator. The actual
102             # priority numbers may change over releases of this module.
103             # The second value is a constant of associativety. Either the constant
104             # LTR (compute left to right), RTL (right to left), or NOCHAIN (non-stackable
105             # operator).
106              
107 291   33 291   471 sub find($) { @{$table{$_[1]} // panic "op $_[1]" } }
  291         2632  
108              
109             # MF::PREFIX, monadic prefix operator
110             # Prefix operators process the result of the expression which follows it.
111             # This is a specialization from the MF::OPERATOR type, hence shares its methods.
112              
113             package
114             MF::PREFIX;
115              
116 28     28   207 use base 'MF::OPERATOR';
  28         86  
  28         14883  
117              
118             # method child(): Returns the AST where this operator works on.
119 30     30   97 sub child() { $_[0][1] }
120              
121             sub compute($$)
122 30     30   52 { my ($self, $context) = @_;
123 30 50       74 my $value = $self->child->compute($context)
124             or return undef;
125              
126 30         77 $value->prefix($self->operator, $context);
127             }
128              
129             # MF::INFIX, infix (dyadic) operator
130             # Infix operators have two arguments. This is a specialization from the
131             # MF::OPERATOR type, hence shares its methods.
132              
133             package
134             MF::INFIX;
135              
136 28     28   241 use base 'MF::OPERATOR';
  28         108  
  28         21856  
137              
138             # method left(): Returns the AST left from the infix operator.
139 239     239   983 sub left() { $_[0][1] }
140              
141             # method right(): Returns the AST right from the infix operator.
142 239     239   689 sub right() { $_[0][2] }
143              
144             my %comparison = (
145             '<' => [ '<=>', sub { $_[0] < 0 } ],
146             '<=' => [ '<=>', sub { $_[0] <= 0 } ],
147             '==' => [ '<=>', sub { $_[0] == 0 } ],
148             '!=' => [ '<=>', sub { $_[0] != 0 } ],
149             '>=' => [ '<=>', sub { $_[0] >= 0 } ],
150             '>' => [ '<=>', sub { $_[0] > 0 } ],
151             'lt' => [ 'cmp', sub { $_[0] < 0 } ],
152             'le' => [ 'cmp', sub { $_[0] <= 0 } ],
153             'eq' => [ 'cmp', sub { $_[0] == 0 } ],
154             'ne' => [ 'cmp', sub { $_[0] != 0 } ],
155             'ge' => [ 'cmp', sub { $_[0] >= 0 } ],
156             'gt' => [ 'cmp', sub { $_[0] > 0 } ],
157             );
158              
159 0     0   0 sub _compare_ops { keys %comparison }
160              
161             sub compute($$)
162 239     239   522 { my ($self, $context) = @_;
163              
164 239 50       609 my $left = $self->left->compute($context)
165             or return undef;
166              
167 239 50       1881 my $right = $self->right->compute($context)
168             or return undef;
169              
170             # Comparison operators are all implemented via a space-ship, when available.
171             # Otherwise, the usual track is taken.
172              
173 239         630 my $op = $self->operator;
174 239 100       691 if(my $rewrite = $comparison{$op})
175 36         72 { my ($spaceship, $compare) = @$rewrite;
176 36 50       94 if(my $result = $left->infix($spaceship, $right, $context))
177 36         100 { return MF::BOOLEAN->new(undef, $compare->($result->value));
178             }
179             }
180              
181 203         803 $left->infix($op, $right, $context);
182             }
183              
184              
185             # MF::TERNARY, if ? then : else
186             # Ternary operators have three arguments. This is a specialization from the
187             # MF::OPERATOR type, hence shares its methods.
188              
189             package
190             MF::TERNARY;
191              
192 28     28   263 use base 'MF::OPERATOR';
  28         55  
  28         12092  
193              
194 7     7   17 sub condition() { $_[0][1] }
195 3     3   7 sub then() { $_[0][2] }
196 4     4   6 sub else() { $_[0][3] }
197              
198             sub compute($$)
199 7     7   10 { my ($self, $context) = @_;
200              
201 7 50       12 my $cond = $self->condition->compute($context)
202             or return undef;
203              
204 7 100       16 ($cond->value ? $self->then : $self->else)->compute($context)
205             }
206              
207             # When used, this returns a MF::STRING taken from the captures in the context.
208              
209             package
210             MF::CAPTURE;
211 28     28   280 use base 'Math::Formula::Token';
  28         69  
  28         7290  
212              
213 4     4   25 sub seqnr() { $_[0][0] }
214              
215             sub compute($$)
216 4     4   10 { my ($self, $context) = @_;
217 4         14 my $v = $context->capture($self->seqnr -1);
218 4 50       22 defined $v ? MF::STRING->new(undef, $v) : undef;
219             }
220              
221             1;