line |
stmt |
bran |
cond |
sub |
pod |
time |
code |
1
|
|
|
|
|
|
|
#!/usr/bin/perl |
2
|
|
|
|
|
|
|
|
3
|
|
|
|
|
|
|
# Expr.pm - A perl parser or mathematicall expressions. |
4
|
|
|
|
|
|
|
# (c) Copyright 1998 Hakan Ardo |
5
|
|
|
|
|
|
|
# |
6
|
|
|
|
|
|
|
# This program is free software; you can redistribute it and/or modify |
7
|
|
|
|
|
|
|
# it under the terms of the GNU General Public License as published by |
8
|
|
|
|
|
|
|
# the Free Software Foundation; either version 2 of the License, or |
9
|
|
|
|
|
|
|
# any later version. |
10
|
|
|
|
|
|
|
# |
11
|
|
|
|
|
|
|
# This program is distributed in the hope that it will be useful, |
12
|
|
|
|
|
|
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of |
13
|
|
|
|
|
|
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
14
|
|
|
|
|
|
|
# GNU General Public License for more details. |
15
|
|
|
|
|
|
|
# |
16
|
|
|
|
|
|
|
# You should have received a copy of the GNU General Public License |
17
|
|
|
|
|
|
|
# along with this program; if not, write to the Free Software |
18
|
|
|
|
|
|
|
# Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. |
19
|
|
|
|
|
|
|
|
20
|
|
|
|
|
|
|
=head1 NAME |
21
|
|
|
|
|
|
|
|
22
|
|
|
|
|
|
|
Math::Expr - Parses mathematical expressions |
23
|
|
|
|
|
|
|
|
24
|
|
|
|
|
|
|
=head1 SYNOPSIS |
25
|
|
|
|
|
|
|
|
26
|
|
|
|
|
|
|
use Math::Expr; |
27
|
|
|
|
|
|
|
|
28
|
|
|
|
|
|
|
SetOppDB(new Math::Expr::OpperationDB('')); |
29
|
|
|
|
|
|
|
$e=Parse("a+4*b-d/log(s)+f(d,e)"); |
30
|
|
|
|
|
|
|
|
31
|
|
|
|
|
|
|
=head1 DESCRIPTION |
32
|
|
|
|
|
|
|
|
33
|
|
|
|
|
|
|
Parses mathematical expressions into a tree structure. The expressions |
34
|
|
|
|
|
|
|
may contain integers, real numbers, alphanumeric variable names, |
35
|
|
|
|
|
|
|
alphanumeric function names and most other characters might be used |
36
|
|
|
|
|
|
|
as operators. The operators can even be longer than one character! |
37
|
|
|
|
|
|
|
The only limitation is that a variable or function name may not start |
38
|
|
|
|
|
|
|
on a digit, and not all chars are accepted as operations. To be exact, |
39
|
|
|
|
|
|
|
here is the grammatic (in perl regexp notation): |
40
|
|
|
|
|
|
|
|
41
|
|
|
|
|
|
|
= -?()* |
42
|
|
|
|
|
|
|
= |||\(\) |
43
|
|
|
|
|
|
|
= | |
44
|
|
|
|
|
|
|
= \d+ |
45
|
|
|
|
|
|
|
= \d*\.\d+ |
46
|
|
|
|
|
|
|
= [a-zA-Z][a-zA-Z0-9]*(:[a-zA-Z][a-zA-Z0-9]*)? |
47
|
|
|
|
|
|
|
= [a-zA-Z][a-zA-Z0-9]*\((,)*\) |
48
|
|
|
|
|
|
|
= [^a-zA-Z0-9\(\)\,\.\:]+ |
49
|
|
|
|
|
|
|
|
50
|
|
|
|
|
|
|
If the - sign is present at the beginning of an Then a neg() |
51
|
|
|
|
|
|
|
function is placed around it. That is to allow constructions like |
52
|
|
|
|
|
|
|
"-a*b" or "b+3*(-7)". |
53
|
|
|
|
|
|
|
|
54
|
|
|
|
|
|
|
A variable consists of two parts separated by a ':'-char. The first |
55
|
|
|
|
|
|
|
part is the variable name, and the second optional part is its type. |
56
|
|
|
|
|
|
|
Default type is Real. |
57
|
|
|
|
|
|
|
|
58
|
|
|
|
|
|
|
=head1 METHODS |
59
|
|
|
|
|
|
|
|
60
|
|
|
|
|
|
|
=cut |
61
|
|
|
|
|
|
|
|
62
|
|
|
|
|
|
|
package Math::Expr; |
63
|
1
|
|
|
1
|
|
806
|
use strict; |
|
1
|
|
|
|
|
2
|
|
|
1
|
|
|
|
|
60
|
|
64
|
|
|
|
|
|
|
|
65
|
|
|
|
|
|
|
require Exporter; |
66
|
1
|
|
|
1
|
|
5
|
use vars qw (@ISA @EXPORT_OK @EXPORT $Pri $OppDB); |
|
1
|
|
|
|
|
1
|
|
|
1
|
|
|
|
|
1957
|
|
67
|
|
|
|
|
|
|
|
68
|
|
|
|
|
|
|
@ISA = qw (Exporter); |
69
|
|
|
|
|
|
|
@EXPORT_OK = qw($Pri $OppDB); |
70
|
|
|
|
|
|
|
@EXPORT = qw(Parse Priority SetOppDB); |
71
|
|
|
|
|
|
|
|
72
|
|
|
|
|
|
|
require Math::Expr::Opp; |
73
|
|
|
|
|
|
|
require Math::Expr::Var; |
74
|
|
|
|
|
|
|
require Math::Expr::Num; |
75
|
|
|
|
|
|
|
require Math::Expr::VarSet; |
76
|
|
|
|
|
|
|
require Math::Expr::OpperationDB; |
77
|
|
|
|
|
|
|
|
78
|
|
|
|
|
|
|
$Pri={'^'=>50, '/'=>40, '*'=>30, '-'=>20, '+'=>10, '='=>0}; |
79
|
|
|
|
|
|
|
|
80
|
|
|
|
|
|
|
=head2 $e=Parse($str) |
81
|
|
|
|
|
|
|
|
82
|
|
|
|
|
|
|
This will parse the string $str and return an expression tree, in the |
83
|
|
|
|
|
|
|
form of a Math::Expr::Opp object (or in simple cases only a |
84
|
|
|
|
|
|
|
Math::Expr::Var or Math::Expr::Num object). |
85
|
|
|
|
|
|
|
|
86
|
|
|
|
|
|
|
=cut |
87
|
|
|
|
|
|
|
|
88
|
|
|
|
|
|
|
|
89
|
|
|
|
|
|
|
=head2 $p = new Math::Expr |
90
|
|
|
|
|
|
|
|
91
|
|
|
|
|
|
|
This is the constructor, it creates an object which later can be used |
92
|
|
|
|
|
|
|
to parse the strings. |
93
|
|
|
|
|
|
|
|
94
|
|
|
|
|
|
|
=cut |
95
|
|
|
|
|
|
|
|
96
|
|
|
|
|
|
|
sub Parse { |
97
|
42
|
|
|
42
|
1
|
5588
|
my ($str) = @_; |
98
|
42
|
|
|
|
|
82
|
my $self=bless {}; |
99
|
|
|
|
|
|
|
|
100
|
42
|
50
|
|
|
|
107
|
if (ref $str) {warn "Bad param str: $str"} |
|
0
|
|
|
|
|
0
|
|
101
|
|
|
|
|
|
|
|
102
|
42
|
|
|
|
|
387
|
$str=~ s/\s*//g; |
103
|
42
|
|
|
|
|
110
|
$self->{'Str'}=$str; |
104
|
|
|
|
|
|
|
|
105
|
42
|
|
|
|
|
126
|
$self->NextToken; |
106
|
42
|
|
|
|
|
93
|
my $e=$self->Expr; |
107
|
42
|
|
|
|
|
171
|
$e; |
108
|
|
|
|
|
|
|
} |
109
|
|
|
|
|
|
|
|
110
|
|
|
|
|
|
|
=head2 Priority({'^'=>50, '/'=>40, '*'=>30, '-'=>20, '+'=>10}) |
111
|
|
|
|
|
|
|
|
112
|
|
|
|
|
|
|
This will set the priority of ALL the operands (there is currently no |
113
|
|
|
|
|
|
|
way to change only one of them). The priority decides what should be |
114
|
|
|
|
|
|
|
constructed if several operands is listed without delimiters. Eg if |
115
|
|
|
|
|
|
|
a+b*c should be treated as (a+b)*c or a+(b*c). (Default is listed in |
116
|
|
|
|
|
|
|
header). |
117
|
|
|
|
|
|
|
|
118
|
|
|
|
|
|
|
The priority is global for all parsers and all expretions, so |
119
|
|
|
|
|
|
|
changing it here will change it for all parsers and parsed objects. |
120
|
|
|
|
|
|
|
The idea is to use this method to initiate the system before using it. |
121
|
|
|
|
|
|
|
|
122
|
|
|
|
|
|
|
=cut |
123
|
|
|
|
|
|
|
|
124
|
|
|
|
|
|
|
sub Priority { |
125
|
0
|
|
|
0
|
1
|
0
|
my ($p) = @_; |
126
|
0
|
|
|
|
|
0
|
$Pri=$p; |
127
|
|
|
|
|
|
|
} |
128
|
|
|
|
|
|
|
|
129
|
|
|
|
|
|
|
=head2 SetOppDB($db) |
130
|
|
|
|
|
|
|
|
131
|
|
|
|
|
|
|
Sets the OpperationDB to be used to $db. See L |
132
|
|
|
|
|
|
|
for more info. |
133
|
|
|
|
|
|
|
|
134
|
|
|
|
|
|
|
This is a global variable afecting all parsers and all parsed structures. |
135
|
|
|
|
|
|
|
|
136
|
|
|
|
|
|
|
=cut |
137
|
|
|
|
|
|
|
|
138
|
|
|
|
|
|
|
sub SetOppDB { |
139
|
1
|
|
|
1
|
1
|
2
|
my ($db) = @_; |
140
|
|
|
|
|
|
|
|
141
|
1
|
|
|
|
|
3
|
$OppDB= $db; |
142
|
1
|
|
|
|
|
4
|
$OppDB->InitDB; |
143
|
|
|
|
|
|
|
} |
144
|
|
|
|
|
|
|
|
145
|
|
|
|
|
|
|
sub NextToken { |
146
|
266
|
|
|
266
|
0
|
289
|
my $self = shift; |
147
|
|
|
|
|
|
|
|
148
|
266
|
100
|
|
|
|
2029
|
if ($self->{'Str'} =~ s/^([a-zA-Z][a-zA-Z0-9]*)\(//) { |
|
|
100
|
|
|
|
|
|
|
|
100
|
|
|
|
|
|
|
|
100
|
|
|
|
|
|
|
|
100
|
|
|
|
|
|
149
|
12
|
|
|
|
|
23
|
$self->{'TType'}="Func"; |
150
|
|
|
|
|
|
|
} |
151
|
|
|
|
|
|
|
elsif ($self->{'Str'} =~ s/^([a-zA-Z][a-zA-Z0-9]*(:[a-zA-Z][a-zA-Z0-9]*)?)//) { |
152
|
98
|
|
|
|
|
235
|
$self->{'TType'}="Var"; |
153
|
|
|
|
|
|
|
} |
154
|
|
|
|
|
|
|
elsif ($self->{'Str'} =~ s/^(\d*\.\d+|\d+)//) { |
155
|
14
|
|
|
|
|
31
|
$self->{'TType'}="Num"; |
156
|
|
|
|
|
|
|
} |
157
|
|
|
|
|
|
|
elsif ($self->{'Str'}=~ s/^([^a-zA-Z0-9\(\)\,\.\:]+)//) { |
158
|
70
|
|
|
|
|
110
|
$self->{'TType'}="OpChr"; |
159
|
|
|
|
|
|
|
} |
160
|
|
|
|
|
|
|
elsif ($self->{'Str'}=~ s/^([\(\)\,])//){ |
161
|
30
|
|
|
|
|
55
|
$self->{'TType'}="Chr"; |
162
|
|
|
|
|
|
|
} |
163
|
|
|
|
|
|
|
else { |
164
|
42
|
50
|
|
|
|
102
|
if ($self->{'Str'} ne "") {$self->Bad} |
|
0
|
|
|
|
|
0
|
|
165
|
42
|
|
|
|
|
60
|
return 0; |
166
|
|
|
|
|
|
|
} |
167
|
224
|
|
|
|
|
479
|
$self->{'Token'}=$1; |
168
|
224
|
|
|
|
|
287
|
return 1; |
169
|
|
|
|
|
|
|
} |
170
|
|
|
|
|
|
|
|
171
|
|
|
|
|
|
|
sub Expr { |
172
|
63
|
|
|
63
|
1
|
73
|
my $self = shift; |
173
|
63
|
|
|
|
|
84
|
my $e; |
174
|
|
|
|
|
|
|
my $n; |
175
|
|
|
|
|
|
|
|
176
|
63
|
50
|
|
|
|
136
|
if ($self->{'Token'} eq '-') { |
177
|
0
|
|
|
|
|
0
|
$e= new Math::Expr::Opp('neg'); |
178
|
0
|
|
|
|
|
0
|
$self->NextToken; |
179
|
0
|
|
|
|
|
0
|
$e->SetOpp(0,$self->Elem); |
180
|
|
|
|
|
|
|
} else { |
181
|
63
|
|
|
|
|
140
|
$e=$self->Elem; |
182
|
|
|
|
|
|
|
} |
183
|
|
|
|
|
|
|
|
184
|
63
|
|
|
|
|
168
|
while ($self->{'TType'} eq 'OpChr'){ |
185
|
70
|
|
|
|
|
210
|
$n= new Math::Expr::Opp($self->{'Token'}); |
186
|
|
|
|
|
|
|
|
187
|
70
|
100
|
100
|
|
|
733
|
if ($e->isa('Math::Expr::Opp') && |
|
|
|
66
|
|
|
|
|
|
|
|
100
|
|
|
|
|
|
|
|
100
|
|
|
|
|
188
|
|
|
|
|
|
|
defined $Pri->{$e->{'Val'}} && |
189
|
|
|
|
|
|
|
defined $Pri->{$n->{'Val'}} && |
190
|
|
|
|
|
|
|
$Pri->{$e->{'Val'}} < $Pri->{$n->{'Val'}} && |
191
|
|
|
|
|
|
|
$e->Breakable |
192
|
|
|
|
|
|
|
) { |
193
|
18
|
|
|
|
|
45
|
$n->SetOpp(0,$e->Opp(1)); |
194
|
18
|
|
|
|
|
35
|
$self->NextToken; |
195
|
18
|
|
|
|
|
34
|
$n->SetOpp(1,$self->Elem); |
196
|
18
|
|
|
|
|
53
|
$n->Breakable(1); |
197
|
18
|
|
|
|
|
40
|
$n=$self->FixPri($n); |
198
|
18
|
|
|
|
|
48
|
$e->SetOpp(1,$n); |
199
|
|
|
|
|
|
|
} else { |
200
|
52
|
|
|
|
|
152
|
$n->SetOpp(0,$e); |
201
|
52
|
|
|
|
|
106
|
$self->NextToken; |
202
|
52
|
|
|
|
|
102
|
$n->SetOpp(1,$self->Elem); |
203
|
52
|
|
|
|
|
135
|
$n->Breakable(1); |
204
|
52
|
|
|
|
|
181
|
$e=$n; |
205
|
|
|
|
|
|
|
} |
206
|
|
|
|
|
|
|
} |
207
|
63
|
|
|
|
|
176
|
$e->Breakable(0); |
208
|
63
|
|
|
|
|
145
|
return $e; |
209
|
|
|
|
|
|
|
} |
210
|
|
|
|
|
|
|
|
211
|
|
|
|
|
|
|
sub FixPri { |
212
|
44
|
|
|
44
|
0
|
55
|
my ($self, $n)=@_; |
213
|
44
|
|
|
|
|
90
|
my $a=$n->Opp(0); |
214
|
44
|
|
|
|
|
57
|
my $t; |
215
|
|
|
|
|
|
|
|
216
|
44
|
100
|
66
|
|
|
988
|
if ($a->isa('Math::Expr::Opp') && |
|
|
|
100
|
|
|
|
|
|
|
|
66
|
|
|
|
|
|
|
|
100
|
|
|
|
|
217
|
|
|
|
|
|
|
defined $Pri->{$n->{'Val'}} && |
218
|
|
|
|
|
|
|
defined $Pri->{$a->{'Val'}} && |
219
|
|
|
|
|
|
|
$Pri->{$a->{'Val'}} < $Pri->{$n->{'Val'}} && |
220
|
|
|
|
|
|
|
$a->Breakable |
221
|
|
|
|
|
|
|
) { |
222
|
26
|
|
|
|
|
57
|
$n->SetOpp(0,$a->Opp(1)); |
223
|
26
|
|
|
|
|
65
|
$n=$self->FixPri($n); |
224
|
26
|
|
|
|
|
72
|
$a->SetOpp(1,$n); |
225
|
26
|
|
|
|
|
45
|
$a; |
226
|
|
|
|
|
|
|
} else { |
227
|
18
|
|
|
|
|
36
|
$n; |
228
|
|
|
|
|
|
|
} |
229
|
|
|
|
|
|
|
} |
230
|
|
|
|
|
|
|
|
231
|
|
|
|
|
|
|
sub Elem { |
232
|
133
|
|
|
133
|
0
|
155
|
my $self=shift; |
233
|
|
|
|
|
|
|
|
234
|
133
|
100
|
|
|
|
444
|
if ($self->{'TType'} eq "Var") { |
|
|
100
|
|
|
|
|
|
|
|
50
|
|
|
|
|
|
|
|
100
|
|
|
|
|
|
|
|
50
|
|
|
|
|
|
235
|
98
|
|
|
|
|
302
|
my $n = new Math::Expr::Var($self->{'Token'}); |
236
|
98
|
|
|
|
|
190
|
$self->NextToken; |
237
|
98
|
|
|
|
|
257
|
return $n; |
238
|
|
|
|
|
|
|
} |
239
|
|
|
|
|
|
|
elsif ($self->{'TType'} eq "Num") { |
240
|
14
|
|
|
|
|
58
|
my $n = new Math::Expr::Num($self->{'Token'}); |
241
|
14
|
|
|
|
|
38
|
$self->NextToken; |
242
|
14
|
|
|
|
|
31
|
return $n; |
243
|
|
|
|
|
|
|
} |
244
|
|
|
|
|
|
|
elsif ($self->{'TType'} eq "Var") { |
245
|
0
|
|
|
|
|
0
|
my $n = new Math::Expr::Var($self->{'Token'}); |
246
|
0
|
|
|
|
|
0
|
$self->NextToken; |
247
|
0
|
|
|
|
|
0
|
return $n; |
248
|
|
|
|
|
|
|
} |
249
|
|
|
|
|
|
|
elsif ($self->{'Token'} eq "(") { |
250
|
9
|
|
|
|
|
18
|
$self->NextToken; |
251
|
9
|
|
|
|
|
19
|
my $n= $self->Expr; |
252
|
9
|
50
|
|
|
|
196
|
if ($self->{'Token'} ne ")") { |
253
|
0
|
|
|
|
|
0
|
$self->Bad; |
254
|
|
|
|
|
|
|
} |
255
|
9
|
|
|
|
|
20
|
$self->NextToken; |
256
|
9
|
|
|
|
|
18
|
return $n; |
257
|
|
|
|
|
|
|
} |
258
|
|
|
|
|
|
|
elsif ($self->{'TType'} eq "Func") { |
259
|
12
|
|
|
|
|
42
|
my $n=new Math::Expr::Opp($self->{'Token'}); |
260
|
12
|
|
|
|
|
14
|
my $o=0; |
261
|
12
|
|
|
|
|
17
|
do { |
262
|
12
|
|
|
|
|
24
|
$self->NextToken; |
263
|
12
|
|
|
|
|
47
|
$n->SetOpp($o, $self->Expr); |
264
|
12
|
|
|
|
|
38
|
$o++; |
265
|
|
|
|
|
|
|
} while ($self->{'Token'} eq ","); |
266
|
12
|
50
|
|
|
|
27
|
if ($self->{'Token'} ne ")") { |
267
|
0
|
|
|
|
|
0
|
$self->Bad; |
268
|
|
|
|
|
|
|
} |
269
|
12
|
|
|
|
|
20
|
$self->NextToken; |
270
|
12
|
|
|
|
|
38
|
return $n |
271
|
|
|
|
|
|
|
} else { |
272
|
0
|
|
|
|
|
|
$self->Bad; |
273
|
|
|
|
|
|
|
} |
274
|
|
|
|
|
|
|
} |
275
|
|
|
|
|
|
|
|
276
|
|
|
|
|
|
|
sub Bad { |
277
|
0
|
|
|
0
|
0
|
|
my $self = shift; |
278
|
|
|
|
|
|
|
|
279
|
0
|
|
|
|
|
|
warn "Bad str: " . $self->{'Str'} . "\n"; |
280
|
|
|
|
|
|
|
} |
281
|
|
|
|
|
|
|
|
282
|
|
|
|
|
|
|
=head1 BUGS |
283
|
|
|
|
|
|
|
|
284
|
|
|
|
|
|
|
The parses does not handle bad strings in a decent way. If you try |
285
|
|
|
|
|
|
|
to parse a string that does not follow the specification above, all |
286
|
|
|
|
|
|
|
strange things might happen... |
287
|
|
|
|
|
|
|
|
288
|
|
|
|
|
|
|
=head1 AUTHOR |
289
|
|
|
|
|
|
|
|
290
|
|
|
|
|
|
|
Hakan Ardo |
291
|
|
|
|
|
|
|
|
292
|
|
|
|
|
|
|
=head1 SEE ALSO |
293
|
|
|
|
|
|
|
|
294
|
|
|
|
|
|
|
L |
295
|
|
|
|
|
|
|
|
296
|
|
|
|
|
|
|
=cut |