File Coverage

blib/lib/Math/Expr/Opp.pm
Criterion Covered Total %
statement 264 301 87.7
branch 93 128 72.6
condition 24 33 72.7
subroutine 22 25 88.0
pod 7 14 50.0
total 410 501 81.8


line stmt bran cond sub pod time code
1             #!/usr/bin/perl
2              
3             # Opp.pm - A perl representation 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::Opp - Represents one operation in the parsed expression
23             tree
24              
25             =head1 SYNOPSIS
26              
27             require Math::Expr::Opp;
28             require Math::Expr::Var;
29             require Math::Expr::Num;
30            
31             # To represent the expression "x+7":
32             $n=new Math::Expr::Opp("+");
33             $n->SetOpp(0,new Math::Expr::Var("x"));
34             $n->SetOpp(1,new Math::Expr::Num(7));
35             print $n->tostr . "\n";
36              
37             =head1 DESCRIPTION
38              
39             Used by the Math::Expr to represent algebraic expressions. This class
40             represents one operation or function with a set of operands, which
41             in turn can be other Math::Expr::Opp objects. And in that way we are
42             able to represent entire expression.
43              
44             Operations like a+b and functions like sin(a) or f(a,b) are all
45             represented by this kind of objects with "+", "sin" and "f" as the
46             operation- or function names and Math::Expr::Var(a) and
47             Math::Expr::Var(b) as operands (only a in the sin example).
48              
49             =head1 METHODS
50              
51             =cut
52              
53             package Math::Expr::Opp;
54 1     1   9 use strict;
  1         2  
  1         52  
55              
56 1     1   5 use Math::Expr qw ($Pri $OppDB);
  1         2  
  1         424  
57             require Math::Expr::MatchSet;
58             require Math::Expr::Node;
59             require Math::Expr::VarSet;
60 1     1   6 use vars qw(@ISA);
  1         3  
  1         50  
61              
62 1     1   664 use Math::Expr::Node;
  1         2  
  1         4025  
63             @ISA = qw(Math::Expr::Node);
64              
65             =head2 $e=new Math::Expr::Opp($name,$db)
66              
67             Creates a new operation object with the operation- or function-name
68             $name. Using the operations defined in $db. See
69             L for more info.
70              
71             =cut
72              
73             sub new {
74 469     469 0 755 my($class, $val) = @_;
75 469         1620 my $self = bless { }, $class;
76              
77 469 50 33     1862 if (!ref $OppDB || !ref $Pri) {
78 0         0 warn "OppDB not initiated, please set it using SetOppDB(...)";
79             }
80              
81 469         1256 $self->{'Val'}=$val;
82 469         1419 $self->Breakable(0);
83              
84 469         1084 $self;
85              
86             }
87              
88             =head2 $e->SetOpp($i, $v)
89              
90             Sets operand number $i to $v.
91              
92             =cut
93              
94             sub SetOpp {
95 222     222 1 310 my ($self, $i, $val) = @_;
96              
97             # Sanity checks
98 222 50       448 defined $i || warn "Bad param i.";
99 222 50       664 $val->isa("Math::Expr::Node") || warn "Bad param val: $val";
100 222 50       495 !$self->InTable || warn "Can't edit items in the table";
101              
102 222         307 delete $self->{'Op'};
103              
104 222         637 $self->{'Opps'}[$i]=$val;
105             }
106              
107             =head2 $e->Opp($i)
108              
109             Returns operand to number $i.
110              
111             =cut
112              
113             sub Opp {
114 88     88 1 107 my ($self, $i) = @_;
115              
116             # Sanity checks
117 88 50       184 defined $i || warn "Bad param i.";
118              
119 88         295 $self->{'Opps'}[$i];
120             }
121              
122             =head2 $e->tostr
123              
124             Returns a string representation of the entire expression to be
125             used for debugging.
126              
127             =cut
128              
129             sub tostr {
130 1713     1713 1 2131 my $self = shift;
131 1713         3516 my $str=$self->{'Val'}."(";
132 1713         2617 my $i;
133              
134 1713         2049 for ($i=0; $i<=$#{$self->{'Opps'}}; $i++) {
  4391         17895  
135 2678 50       5713 if (ref $self->{'Opps'}[$i]) {
136 2678         8299 $str .= $self->{'Opps'}[$i]->tostr;
137             } else {
138 0         0 $str .= "?";
139             }
140 2678 100       5201 if ($i+1<=$#{$self->{'Opps'}}) {
  2678         7778  
141 965         1651 $str .= ",";
142             }
143             }
144 1713         4999 "$str)";
145             }
146              
147             =head2 $e->strtype
148              
149             Returns a string representation of this expressions entire type,
150             without simplifying it. In the same notation as the tostr method.
151              
152             =cut
153              
154             sub strtype {
155 0     0 1 0 my $self = shift;
156 0         0 my $str=$self->{'Val'}."(";
157 0         0 my $i;
158              
159 0         0 for ($i=0; $i<=$#{$self->{'Opps'}}; $i++) {
  0         0  
160 0         0 $str .= $self->{'Opps'}[$i]->strtype;
161 0 0       0 if ($i+1<=$#{$self->{'Opps'}}) {
  0         0  
162 0         0 $str .= ",";
163             }
164             }
165 0         0 "$str)";
166             }
167              
168             =head2 $n->Simplify
169              
170             Simplifys the expression to some normal from.
171              
172             =cut
173              
174             sub op {
175 1372     1372 0 2027 my ($self, $force)=@_;
176 1372 100 100     5739 if ($force || !$self->{'Op'}) {
177 585         1170 $self->{'Op'}=$OppDB->Find($self->DBType);
178             }
179 1372         6485 return $self->{'Op'};
180             }
181              
182             sub Simplify {
183 207     207 1 568 my ($self)=@_;
184 207         209 my $i;
185             my $op;
186              
187 207         268 for ($i=0; $i<=$#{$self->{'Opps'}}; $i++) {
  550         1586  
188 343         1037 $self->{'Opps'}[$i]=$self->{'Opps'}[$i]->Simplify;
189             }
190              
191 207         474 $op=$self->op(1);
192              
193             # Type specific simplification rules
194 207 100       556 if ($op->{'simp'}) {
195 5         26 my $vs=new Math::Expr::VarSet;
196              
197 5         9 for ($i=0; $i<=$#{$self->{'Opps'}}; $i++) {
  15         41  
198 10         91 $vs->Set(chr(97+$i), $self->{'Opps'}[$i]);
199             }
200             # print $vs->tostr . "\n";
201              
202 5         19 my $e=$op->{'simp'}->Copy;
203 5         19 $e=$e->Subs($vs);
204              
205             # print $e->tostr . "\n";
206              
207 5         22 foreach (keys %{$e}) {
  5         16  
208 15         32 $self->{$_}=$e->{$_};
209             }
210 5         16 $op=$self->op(1);
211             }
212              
213             # (a+b)+c => a+b+c
214 207 100       429 if ($op->{'ass'}) {
215 107         113 my @nopp;
216 107         158 for ($i=0; $i<=$#{$self->{'Opps'}}; $i++) {
  322         1142  
217 215 100       547 if ($self->{'Val'} eq $self->{'Opps'}[$i]{'Val'}) {
218 14         25 foreach (@{$self->{'Opps'}[$i]{'Opps'}}) {
  14         42  
219 28         66 push(@nopp, $_);
220             }
221             } else {
222 201         448 push (@nopp, $self->{'Opps'}[$i]);
223             }
224             }
225 107         253 $self->{'Opps'}=\@nopp;
226             }
227              
228             # a+c+b => a+b+c
229 207 100       491 if ($op->{'com'}) {
230 107         229 my @nopp = sort {$a->tostr cmp $b->tostr} @{$self->{'Opps'}};
  135         428  
  107         395  
231 107         267 $self->{'Opps'}=\@nopp;
232             }
233 207         398 delete $self->{'Op'};
234 207         693 return $self->IntoTable;
235             }
236              
237              
238             =head2 $n->BaseType
239              
240             Returns a string type of this expression simplifyed as much as
241             possible.
242              
243             =cut
244              
245             sub BaseType {
246 792     792 1 906 my ($self)=@_;
247 792         704 my $op;
248 792         1361 my $str=$self->DBType;
249              
250 792         1688 $op= $self->op;
251 792 100       1546 if ($op) {$str=$op->{'out'}}
  678         1138  
252              
253 792         1577 $str;
254             }
255              
256             sub DBType {
257 1377     1377 0 1544 my ($self)=@_;
258 1377         2609 my $str=$self->{'Val'}."(";
259 1377         1357 my $i;
260              
261 1377         1962 for ($i=0; $i<=$#{$self->{'Opps'}}; $i++) {
  3520         8809  
262 2143         6001 $str .= $self->{'Opps'}[$i]->BaseType;
263 2143 100       2860 if ($i+1<=$#{$self->{'Opps'}}) {
  2143         6020  
264 766         1373 $str .= ",";
265             }
266             }
267 1377         4463 "$str)";
268             }
269              
270             sub power {
271 12     12 0 20 my ($a, $b) = @_;
272 12         12 my $i;
273 12         51 my $sum=1;
274            
275 12         36 for ($i=0; $i<$b; $i++) {
276 26         57 $sum=$sum*$a
277             }
278 12         26 $sum;
279             }
280              
281             =head2 $n->SubMatch($rules,$match)
282              
283             Tries to match $rules to this expretions and adds the substitutions
284             needed to $match.Returns 1 if the match excists and the substitutions
285             needed can coexcist with those already in $match otherwise 0.
286              
287             =cut
288              
289             sub _SubMatch {
290 75     75   111 my ($self, $rule, $mset) = @_;
291 75         140 my $op=$self->op;
292              
293 75 50       198 $self->InTable || warn "self not in table!";
294 75 50       199 $rule->InTable || warn "rule not in table!";
295              
296 75 100 66     682 if ($rule->isa('Math::Expr::Var') &&
    100 100        
297             $rule->BaseType eq $self->BaseType
298             ) {
299 27         163 return $mset->SetAll($rule->{'Val'},$self);
300             }
301             elsif ($rule->isa('Math::Expr::Opp') &&
302             $rule->{'Val'} eq $self->{'Val'}) {
303 24 100       52 if ($op->{'ass'}) {
  12 50       24  
304 12 50       28 if ($op->{'com'}) {
305 12         21 my @part;
306             my @pcnt;
307 0         0 my ($i,$j,$cnt);
308 12         17 my $p=$#{$rule->{'Opps'}} + 1;
  12         32  
309 12         24 my $s=$#{$self->{'Opps'}} + 1;
  12         23  
310 12         28 my $ps=power($p,$s) - 1;
311 12         41 my $resset = new Math::Expr::MatchSet;
312 12         18 my $m;
313             my $t;
314 0         0 my $a;
315 0         0 my $ok;
316              
317 12         33 for ($i=1; $i<$ps; $i++) {
318 32         67 for ($j=0; $j<$p; $j++) {
319 64         168 $part[$j]=new Math::Expr::Opp($self->{'Val'});
320 64         166 $pcnt[$j]=0;
321             }
322 32         34 $cnt=0;
323              
324 32         34 $t=$i;
325 32         79 for ($j=0; $j<$s; $j++) {
326 76         84 $a= $t % $p;
327 76         209 $part[$a]->{'Opps'}[$pcnt[$a]]=$self->{'Opps'}[$cnt];
328 76         94 $pcnt[$a]++;
329 76         133 $cnt++;
330 76         210 $t=int($t/$p);
331             }
332              
333 32         35 $a=1;
334 32         154 for ($j=0; $j<$p; $j++) {
335             # print $part[$j]->tostr . "\t";
336 64 50       156 if (!defined $part[$j]->{'Opps'}[0]) {$a=0; last;}
  0         0  
  0         0  
337 64 100       137 if (!defined $part[$j]->{'Opps'}[1]) {
338 52         86 $part[$j]=$part[$j]->{'Opps'}[0];
339             }
340 64         741 $part[$j]=$part[$j]->IntoTable;
341             }
342             # print "\n";
343              
344 32 50       58 if ($a) {
345 32         90 $m=$mset->Copy;
346 32         166 $m->AddPos("($i)");
347             # print "m:\n" . $m->tostr . "\n";
348 32         62 $ok=1;
349 32         78 for ($j=0; $j<$p; $j++) {
350 64         218 my $t=$part[$j]->SubMatch($rule->{'Opps'}[$j],$m);
351 64 100       208 if (!$t) {
352 14         33 $ok=0;
353             }
354             }
355 32 100       69 if ($ok) {$resset->Insert($m);}
  20         62  
356             }
357             }
358              
359             # print "res:\n" . $resset->tostr . "\n";
360            
361 12         43 $mset->Clear;
362 12         45 $mset->Insert($resset);
363 12         95 return 1;
364             } else {
365             #FIXME: Handle ass only objs...
366             }
367             }
368 12         36 elsif ($#{$self->{'Opps'}} eq $#{$rule->{'Opps'}}) {
369 12         15 my $ok=1;
370 12         12 my $i;
371            
372 12         16 for ($i=0; $i<=$#{$self->{'Opps'}}; $i++) {
  24         67  
373 12 50       41 if (!$self->{'Opps'}[$i]->SubMatch($rule->{'Opps'}[$i],$mset)) {
374 0         0 $ok=0;
375 0         0 last;
376             }
377             }
378 12         64 return $ok;
379             } else {
380 0         0 return 0;
381             }
382             } else {
383 24         99 return 0;
384             }
385             }
386              
387             =head2 $n->Match($rules)
388              
389             Tries to match $rules to this expretions and to all its subexpretions.
390             Returns a MatchSet object specifying where the matches ocored and what
391             substitutions they represent.
392              
393             =cut
394              
395             sub _Match {
396 37     37   66 my ($self, $rule, $pos, $pre) = @_;
397 37         41 my $i;
398 37         1005 my $mset = new Math::Expr::MatchSet;
399 37         97 my $op=$self->op;
400              
401 37 50       120 $self->InTable || warn "self not in table!";
402 37 50       93 $rule->InTable || warn "rule not in table!";
403              
404 37 50       77 if (!defined $pos) {$pos="";}
  0         0  
405 37 100       71 if (!defined $pre) {$pre=new Math::Expr::VarSet}
  8         43  
406              
407 37         121 $mset->Set($pos, $pre->Copy);
408 37 100       137 if (!$self->SubMatch($rule, $mset)) {
409 18         54 $mset->del($pos);
410             }
411              
412 37 100       93 if ($pos ne "") {$pos .=","}
  27         38  
413              
414 37         50 for ($i=0; $i<=$#{$self->{'Opps'}}; $i++) {
  101         302  
415 64         141 my $m=$self->SubExpr($i)->IntoTable->Match($rule, "$pos$i", $pre->Copy);
416 64         258 $mset->Insert($m);
417             }
418              
419 37         170 $mset;
420             }
421              
422             sub SubOpp {
423 0     0 0 0 my ($self, $a,$b) = @_;
424 0         0 my $i;
425 0         0 my $o= new Math::Expr::Opp($self->{'Val'});
426              
427             # Sanity checks
428 0 0       0 defined $a|| warn("Bad param a.");
429 0 0       0 defined $b|| warn("Bad param b.");
430              
431 0 0       0 if ($a==$b) {return $self->{'Opps'}[$a]}
  0         0  
432              
433 0         0 for ($i=$a; $i<=$b; $i++) {
434 0         0 $o->SetOpp($i-$a,$self->{'Opps'}[$i]);
435             }
436 0         0 return $o->IntoTable;
437             }
438              
439             =head2 $n->Subs($vars)
440              
441             Substitues all variables in the expretion with there vaules in $vars.
442              
443             =cut
444              
445             sub _Subs {
446 82     82   113 my ($self, $vars) = @_;
447 82         115 my $i;
448 82         213 my $n = new Math::Expr::Opp($self->{'Val'});
449              
450 82         140 for ($i=0; $i<=$#{$self->{'Opps'}}; $i++) {
  220         766  
451 138         480 $n->{'Opps'}[$i]=$self->{'Opps'}[$i]->Subs($vars);
452             }
453 82         254 $n;
454             }
455              
456             =head2 $n->Copy
457              
458             Returns a copy of this object.
459              
460             =cut
461              
462             sub _Copy {
463 103     103   162 my $self = shift;
464 103         300 my $n = new Math::Expr::Opp($self->{'Val'});
465 103         118 my $i;
466              
467 103         140 for ($i=0; $i<=$#{$self->{'Opps'}}; $i++) {
  280         841  
468 177         550 $n->{'Opps'}[$i]=$self->{'Opps'}[$i]->Copy;
469             }
470 103         368 $n;
471             }
472              
473             =head2 $n->Breakable
474              
475             Used by the parser to indikate if this object was created using
476             parantesis or if he should break it up to preserve the rules of order
477             between the diffrent opperations.
478              
479             =cut
480              
481             sub _Breakable {
482 639     639   1117 my $self=shift;
483 639         658 my $val=shift;
484              
485 639 100       1170 if (defined $val) {$self->{'Breakable'}=$val}
  588         1167  
486 639         1287 $self->{'Breakable'}
487             }
488              
489             =head2 $n->Find($pos)
490              
491             Returns an object pointer to the subexpression represented by the
492             string $pos.
493              
494             =cut
495              
496             sub Find {
497 0     0 1 0 my ($self, $pos) = @_;
498              
499             # Sanity checks
500 0 0       0 defined $pos || warn "Bad param pos.";
501              
502 0 0       0 if ($pos =~ s/^(\d+),?//) {
503 0         0 return $self->SubExpr($1)->Find($pos);
504             } else {
505 0         0 return $self;
506             }
507             }
508              
509             sub SubExpr {
510 93     93 0 143 my ($self, $pos, $rest) = @_;
511 93         171 my $op=$self->op;
512              
513             # Sanity checks
514 93 50       201 defined $pos || warn "Bad param pos.";
515 93 100       225 if (ref $rest) {
    50          
516 29 50       120 $rest->isa("Math::Expr::Opp") || warn "Bad param rest: $rest";
517 29 50       89 !$rest->InTable || warn "Can't edit items in the table";
518             }
519             elsif(defined $rest) {
520 0         0 warn "Bad param rest: $rest";
521             }
522              
523 93 100 66     387 if ($op->{'ass'} && $op->{'com'}) {
524 80         92 my ($part, $j);
525 80         93 my $cnt=0;
526 80         79 my $rcnt=0;
527            
528 80         219 $part=new Math::Expr::Opp($self->{'Val'});
529              
530 80         191 for($j=0; $j<=$#{$self->{'Opps'}}; $j++) {
  264         640  
531 184 100       404 if ($j!=$pos) {
    100          
532 104         267 $part->{'Opps'}[$cnt]=$self->{'Opps'}[$j];
533 104         156 $cnt++;
534             }
535             elsif(ref $rest) {
536 29         139 $rest->{'Opps'}[$rcnt]=$self->{'Opps'}[$j];
537 29         45 $rcnt++;
538             }
539             }
540              
541 80 100       203 if (!defined $part->{'Opps'}[1]) {$part=$part->{'Opps'}[0];}
  56         96  
542 80         390 return $part; #->IntoTable;
543             } else {
544 13         55 return $self->{'Opps'}[$pos];
545             }
546             }
547              
548             =head2 $n->Set($pos, $val)
549              
550             Replaces the subexpression at position $pos with $val.
551              
552             =cut
553              
554             sub _Set {
555 58     58   111 my ($self, $pos, $val) = @_;
556 58         130 my $op=$self->op;
557              
558 58         219 $pos =~ s/\(\d+\)//g;
559              
560 58 100       141 if ($pos eq "") {
561 25         338 return $val;
562             } else {
563 33         155 $pos =~ s/^(\d+),?//;
564 33         108 my $i=$1;
565              
566 33 100 66     165 if ($op->{'ass'} && $op->{'com'}) {
567 29         97 my $rest=new Math::Expr::Opp($self->{'Val'});
568 29         85 my $part=$self->SubExpr($i, $rest)->Set($pos,$val);
569 29         125 my $n=new Math::Expr::Opp($self->{'Val'});
570              
571 29 50       88 if (!defined $rest->{'Opps'}[1]) {$rest=$rest->{'Opps'}[0];}
  29         58  
572              
573 29         98 $n->{'Opps'}[0]=$rest;
574 29         49 $n->{'Opps'}[1]=$part;
575 29         109 return $n;
576             } else {
577 4         22 $self->{'Opps'}[$i]=$self->{'Opps'}[$i]->Set($pos,$val);
578             }
579 4         15 return $self;
580             }
581             }
582              
583             sub _toMathML {
584 27     27   33 my $self = shift;
585 27         26 my @p;
586             my $i;
587 27         49 my $op = $self->op;
588              
589 27         37 for ($i=0; $i<=$#{$self->{'Opps'}}; $i++) {
  80         207  
590 53         159 $p[$i]=$self->{'Opps'}[$i]->toMathML;
591 53 100 66     659 if (!defined $op->{'noparammathml'} || !eval($op->{'noparammathml'})) {
592 46 100       278 if ($self->{'Opps'}[$i]->isa('Math::Expr::Opp')) {
593 19 50 66     76 if (!$op->{'ass'} || $self->{'Opps'}[$i]{'Val'} ne $self->{'Val'}) {
594 19 100 66     105 if (defined $Pri->{$self->{'Val'}} &&
595             defined $Pri->{$self->{'Opps'}[$i]{'Val'}}) {
596 18 100       67 if ($Pri->{$self->{'Val'}} >=
597             $Pri->{$self->{'Opps'}[$i]{'Val'}}) {
598 3         15 $p[$i]='('.$p[$i].
599             ')';
600             }
601             }
602             }
603             }
604             }
605             }
606              
607 27 100       61 if (defined $op->{'prmathml'}) {
608 19         1103 eval($op->{'prmathml'});
609             } else {
610 8 100       29 if ($self->{'Val'} =~ /^[^a-zA-Z0-9\(\)\,\.\:]+$/) {
611 7         47 "".join ("".$self->{'Val'}."", @p)."";
612             } else {
613 1         9 ''.$self->{'Val'}.''.
614             '('.join (", ", @p) . "".
615             ')'
616             }
617             }
618             }
619              
620             sub toText {
621 78     78 0 151 my $self = shift;
622 78         77 my @p;
623             my $i;
624 78         122 my $op = $self->op;
625              
626 78         98 for ($i=0; $i<=$#{$self->{'Opps'}}; $i++) {
  212         512  
627 134         333 $p[$i]=$self->{'Opps'}[$i]->toText;
628 134 100       692 if ($self->{'Opps'}[$i]->isa('Math::Expr::Opp')) {
629 61 50 66     231 if (!$op->{'ass'} || $self->{'Opps'}[$i]{'Val'} ne $self->{'Val'}) {
630 61 100 100     324 if (defined $Pri->{$self->{'Val'}} &&
631             defined $Pri->{$self->{'Opps'}[$i]{'Val'}}) {
632 24 100       90 if ($Pri->{$self->{'Val'}} >=
633             $Pri->{$self->{'Opps'}[$i]{'Val'}}) {
634 7         25 $p[$i]='('.$p[$i].')';
635             }
636             }
637             }
638             }
639             }
640              
641 78 100       248 if ($self->{'Val'} =~ /^[^a-zA-Z0-9\(\)\,\.\:]+$/) {
642 49         217 join ($self->{'Val'}, @p);
643             } else {
644 29         101 $self->{'Val'}.'('.join (", ", @p).')'
645             }
646             }
647              
648             =head1 AUTHOR
649              
650             Hakan Ardo
651              
652             =head1 SEE ALSO
653              
654             L
655              
656             =cut
657              
658             1;