File Coverage

blib/lib/Math/Expr.pm
Criterion Covered Total %
statement 79 94 84.0
branch 27 34 79.4
condition 21 24 87.5
subroutine 8 10 80.0
pod 4 8 50.0
total 139 170 81.7


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