[system] / trunk / pg / lib / Value.pm Repository:
ViewVC logotype

Annotation of /trunk/pg/lib/Value.pm

Parent Directory Parent Directory | Revision Log Revision Log


Revision 2622 - (view) (download) (as text)

1 : sh002i 2558 package Value;
2 :     my $pkg = 'Value';
3 : dpvc 2579 use vars qw($context $defaultContext %Type);
4 : sh002i 2558 use strict;
5 :    
6 : dpvc 2579 #############################################################
7 : sh002i 2558 #
8 : dpvc 2579 # Initialize the context
9 :     #
10 : sh002i 2558
11 : dpvc 2579 use Value::Context;
12 :    
13 :     $defaultContext = Value::Context->new(
14 :     lists => {
15 :     'Point' => {open => '(', close => ')'},
16 :     'Vector' => {open => '<', close => '>'},
17 :     'Matrix' => {open => '[', close => ']'},
18 :     'List' => {open => '(', close => ')'},
19 :     },
20 :     flags => {
21 :     #
22 :     # For vectors:
23 :     #
24 :     ijk => 0, # print vectors as <...>
25 :     #
26 :     # For fuzzy reals:
27 :     #
28 :     useFuzzyReals => 1,
29 :     tolerance => 1E-6,
30 :     tolType => 'relative',
31 :     zeroLevel => 1E-14,
32 :     zeroLevelTol => 1E-12,
33 : dpvc 2596 #
34 :     # word to use for infinity
35 :     #
36 :     infiniteWord => 'infinity',
37 : dpvc 2622 #
38 :     # For functions
39 :     #
40 :     limits => [-2,2],
41 :     num_points => 5,
42 : dpvc 2579 },
43 :     );
44 :    
45 :     $context = \$defaultContext;
46 :    
47 :    
48 : sh002i 2558 #
49 :     # Precedence of the various types
50 :     # (They will be promoted upward automatically when needed)
51 :     #
52 : dpvc 2579 $$context->{precedence} = {
53 : dpvc 2603 'Number' => 0,
54 :     'Real' => 1,
55 :     'Infinity' => 2,
56 :     'Complex' => 3,
57 :     'Point' => 4,
58 :     'Vector' => 5,
59 :     'Matrix' => 6,
60 :     'List' => 7,
61 :     'Interval' => 8,
62 :     'Union' => 9,
63 : dpvc 2609 'String' => 10,
64 :     'Formula' => 11,
65 : dpvc 2579 };
66 : sh002i 2558
67 :     #
68 :     # Binding of perl operator to class method
69 :     #
70 : dpvc 2579 $$context->{method} = {
71 : sh002i 2558 '+' => 'add',
72 :     '-' => 'sub',
73 :     '*' => 'mult',
74 :     '/' => 'div',
75 :     '**' => 'power',
76 :     '.' => '_dot', # see _dot below
77 :     'x' => 'cross',
78 :     '<=>' => 'compare',
79 : dpvc 2579 };
80 : sh002i 2558
81 : dpvc 2601 $$context->{pattern}{infinite} = '[-+]?inf(?:inity)?';
82 :     $$context->{pattern}{infinity} = '\+?inf(?:inity)?';
83 :     $$context->{pattern}{-infinity} = '-inf(?:inity)?';
84 : dpvc 2596
85 : dpvc 2579 push(@{$$context->{data}{values}},'method','precedence');
86 : sh002i 2558
87 : dpvc 2579 #############################################################
88 :    
89 : sh002i 2558 #
90 :     # Check if a value is a number, complex, etc.
91 :     #
92 : dpvc 2603 sub matchNumber {my $n = shift; $n =~ m/^$$context->{pattern}{signedNumber}$/i}
93 :     sub matchInfinite {my $n = shift; $n =~ m/^$$context->{pattern}{infinite}$/i}
94 : dpvc 2579 sub isReal {class(shift) eq 'Real'}
95 :     sub isComplex {class(shift) eq 'Complex'}
96 :     sub isFormula {class(shift) eq 'Formula'}
97 : dpvc 2621 sub isValue {my $v = shift; return (ref($v) || $v) =~ m/^Value::/}
98 : sh002i 2558
99 :     sub isNumber {
100 :     my $n = shift;
101 : dpvc 2579 return $n->{tree}->isNumber if isFormula($n);
102 :     return isReal($n) || isComplex($n) || matchNumber($n);
103 : sh002i 2558 }
104 :    
105 :     sub isRealNumber {
106 :     my $n = shift;
107 : dpvc 2579 return $n->{tree}->isRealNumber if isFormula($n);
108 :     return isReal($n) || matchNumber($n);
109 : sh002i 2558 }
110 :    
111 :     #
112 : dpvc 2603 # Convert non-Value objects to Values, if possible
113 :     #
114 :     sub makeValue {
115 :     my $x = shift;
116 :     return $x if ref($x);
117 :     return Value::Real->make($x) if matchNumber($x);
118 : dpvc 2609 if (matchInfinite($x)) {
119 :     my $I = Value::Infinity->new();
120 :     $I = $I->neg if $x =~ m/^$$Value::context->{pattern}{-infinity}$/;
121 :     return $I;
122 :     }
123 :     if ($Parser::installed) {return $x unless $$Value::context->{strings}{$x}}
124 :     return Value::String->make($x);
125 : dpvc 2603 }
126 :    
127 :     #
128 : sh002i 2558 # Get a printable version of the class of an object
129 :     #
130 :     sub showClass {
131 : dpvc 2621 my $value = makeValue(shift); my $showFormula = shift;
132 :     return "'".$value."'" unless Value::isValue($value);
133 : sh002i 2558 my $class = class($value);
134 : dpvc 2621 return showType($value) if ($class eq 'List');
135 : dpvc 2592 $class .= ' Number' if $class =~ m/^(Real|Complex)$/;
136 :     $class .= ' of Intervals' if $class eq 'Union';
137 : dpvc 2609 $class = 'Word' if $class eq 'String';
138 : dpvc 2621 return ($showFormula ? 'a Formula that returns ' : '') . showType($value->{tree})
139 :     if ($class eq 'Formula');
140 : dpvc 2603 return 'an '.$class if $class =~ m/^[aeio]/i;
141 : sh002i 2558 return 'a '.$class;
142 :     }
143 :    
144 :     #
145 :     # Get a printable version of the type of an object
146 :     #
147 :     sub showType {
148 :     my $value = shift;
149 :     my $type = $value->type;
150 : dpvc 2621 if ($type eq 'List') {
151 :     my $ltype = $value->typeRef->{entryType}{name};
152 :     if ($ltype && $ltype ne 'unknown') {
153 :     $ltype =~ s/y$/ie/;
154 :     $type .= ' of '.$ltype.'s';
155 :     }
156 :     }
157 : dpvc 2609 return 'a Word' if $type eq 'String';
158 : sh002i 2558 return 'a Complex Number' if $value->isComplex;
159 : dpvc 2603 return 'an '.$type if $type =~ m/^[aeio]/i;
160 : sh002i 2558 return 'a '.$type;
161 :     }
162 :    
163 :     #
164 : dpvc 2603 # Return a string describing a value's type
165 : sh002i 2558 #
166 :     sub getType {
167 :     my $equation = shift; my $value = shift;
168 :     my $strings = $equation->{context}{strings};
169 :     if (ref($value) eq 'ARRAY') {
170 :     return 'Interval' if ($value->[0] =~ m/^[(\[]$/ && $value->[-1] =~ m/^[)\]]$/);
171 :     my ($type,$ltype);
172 :     foreach my $x (@{$value}) {
173 :     $type = getType($equation,$x);
174 :     if ($type eq 'value') {
175 :     $type = $x->type if $x->class eq 'Formula';
176 :     $type = 'Number' if $x->class eq 'Complex' || $type eq 'Complex';
177 :     }
178 :     $ltype = $type if $ltype eq '';
179 :     return 'List' if $type ne $ltype;
180 :     }
181 :     return 'Point' if $ltype eq 'Number';
182 :     return 'Matrix' if $ltype =~ m/Point|Matrix/;
183 :     return 'List';
184 :     }
185 :     elsif (Value::isFormula($value)) {return 'Formula'}
186 : dpvc 2605 elsif (Value::class($value) eq 'Infinity') {return 'String'}
187 : dpvc 2612 elsif (Value::isReal($value)) {return 'Number'}
188 : sh002i 2558 elsif (Value::isValue($value)) {return 'value'}
189 :     elsif (ref($value)) {return 'unknown'}
190 :     elsif (defined($strings->{$value})) {return 'String'}
191 :     elsif (Value::isNumber($value)) {return 'Number'}
192 :     return 'unknown';
193 :     }
194 :    
195 :     #
196 :     # Get a string describing a value's type,
197 :     # and convert the value to a Value object (if needed)
198 :     #
199 :     sub getValueType {
200 :     my $equation = shift; my $value = shift;
201 :     my $type = Value::getType($equation,$value);
202 :     if ($type eq 'String') {$type = $Value::Type{string}}
203 :     elsif ($type eq 'Number') {$type = $Value::Type{number}}
204 :     elsif ($type eq 'value') {$type = $value->typeRef}
205 :     elsif ($type =~ m/unknown|Formula/) {
206 :     $equation->Error("Can't convert ".Value::showClass($value)." to a constant");
207 :     } else {
208 :     $type = 'Value::'.$type, $value = $type->new(@{$value}) unless $type eq 'value';
209 :     $type = $value->typeRef;
210 :     }
211 :     return ($value,$type);
212 :     }
213 :    
214 :     #
215 :     # Convert a list of values to a list of formulas (called by Parser::Value)
216 :     #
217 :     sub toFormula {
218 :     my $formula = shift;
219 :     my $processed = 0;
220 :     my @f = (); my $vars = {};
221 :     foreach my $x (@_) {
222 :     if (isFormula($x)) {
223 :     $formula->{context} = $x->{context}, $processed = 1 unless $processed;
224 :     $formula->{variables} = {%{$formula->{variables}},%{$x->{variables}}};
225 :     push(@f,$x->{tree}->copy($formula));
226 :     } else {
227 :     push(@f,Parser::Value->new($formula,$x));
228 :     }
229 :     }
230 :     return (@f);
231 :     }
232 :    
233 :     #
234 :     # Convert a list of values (and open and close parens)
235 :     # to a formula whose type is the list type associated with
236 :     # the parens. If the formula is constant, evaluate it.
237 :     #
238 :     sub formula {
239 :     my $self = shift; my $values = shift;
240 :     my $class = $self->class;
241 : dpvc 2579 my $list = $$context->lists->get($class);
242 :     my $open = $list->{'open'};
243 :     my $close = $list->{'close'};
244 : sh002i 2558 my $formula = Value::Formula->blank;
245 :     my @coords = Value::toFormula($formula,@{$values});
246 :     $formula->{tree} = Parser::List->new($formula,[@coords],0,
247 :     $formula->{context}{parens}{$open},$coords[0]->typeRef,$open,$close);
248 : dpvc 2579 # return $formula->eval if scalar(%{$formula->{variables}}) == 0;
249 : sh002i 2558 return $formula;
250 :     }
251 :    
252 :     #
253 :     # A shortcut for new() that creates an instance of the object,
254 :     # but doesn't do the error checking. We assume the data are already
255 :     # known to be good.
256 :     #
257 :     sub make {
258 :     my $self = shift; my $class = ref($self) || $self;
259 :     bless {data => [@_]}, $class;
260 :     }
261 :    
262 :     #
263 :     # Return a type structure for the item
264 :     # (includes name, length of vectors, and so on)
265 :     #
266 :     sub Type {
267 :     my $name = shift; my $length = shift; my $entryType = shift;
268 :     $length = 1 unless defined $length;
269 :     return {name => $name, length => $length, entryType => $entryType,
270 :     list => (defined $entryType), @_};
271 :     }
272 :    
273 :     #
274 :     # Some predefined types
275 :     #
276 :     %Type = (
277 :     number => Value::Type('Number',1),
278 :     complex => Value::Type('Number',2),
279 :     string => Value::Type('String',1),
280 :     unknown => Value::Type('unknown',0,undef,list => 1)
281 :     );
282 :    
283 :     #
284 :     # Return various information about the object
285 :     #
286 :     sub value {return @{(shift)->{data}}} # the value of the object (as an array)
287 :     sub data {return (shift)->{data}} # the reference to the value
288 :     sub length {return (shift)->typeRef->{length}} # the number of coordinates
289 :     sub type {return (shift)->typeRef->{name}} # the object type
290 :     sub entryType {return (shift)->typeRef->{entryType}} # the coordinate type
291 :     #
292 :     # The the full type-hash for the item
293 :     #
294 :     sub typeRef {
295 :     my $self = shift;
296 :     return Value::Type($self->class, $self->length, $Value::Type{number});
297 :     }
298 :     #
299 :     # The Value.pm object class
300 :     #
301 :     sub class {
302 :     my $self = shift; my $class = ref($self) || $self;
303 :     $class =~ s/Value:://;
304 :     return $class;
305 :     }
306 :    
307 :     #
308 :     # Get an element from a point, vector, matrix, or list
309 :     #
310 :     sub extract {
311 :     my $M = shift; my $i;
312 :     while (scalar(@_) > 0) {
313 :     return unless Value::isValue($M);
314 :     $i = shift; $i-- if $i > 0;
315 :     Value::Error("Can't extract element number '$i' (index must be an integer)")
316 :     unless $i =~ m/^-?\d+$/;
317 :     $M = $M->data->[$i];
318 :     }
319 :     return $M;
320 :     }
321 :    
322 :    
323 :     #
324 :     # Promote an operand to the same precedence as the current object
325 :     #
326 :     sub promotePrecedence {
327 :     my $self = shift; my $other = shift;
328 : dpvc 2579 my $sprec = $$context->{precedence}{class($self)};
329 :     my $oprec = $$context->{precedence}{class($other)};
330 : sh002i 2558 return defined($oprec) && $sprec < $oprec;
331 :     }
332 :    
333 : dpvc 2592 sub promote {shift}
334 :    
335 : sh002i 2558 #
336 :     # Default stub to call when no function is defined for an operation
337 :     #
338 :     sub nomethod {
339 :     my ($l,$r,$flag,$op) = @_;
340 : dpvc 2579 my $call = $$context->{method}{$op};
341 : sh002i 2558 if (defined($call) && $l->promotePrecedence($r)) {return $r->$call($l,!$flag)}
342 :     my $error = "Can't use '$op' with ".$l->class."-valued operands";
343 :     $error .= " (use '**' for exponentiation)" if $op eq '^';
344 :     Value::Error($error);
345 :     }
346 :    
347 :     #
348 :     # Stubs for the sub-classes
349 :     #
350 :     sub add {nomethod(@_,'+')}
351 :     sub sub {nomethod(@_,'-')}
352 :     sub mult {nomethod(@_,'*')}
353 :     sub div {nomethod(@_,'/')}
354 :     sub power {nomethod(@_,'**')}
355 :     sub cross {nomethod(@_,'x')}
356 :    
357 :     #
358 :     # If the right operand is higher precedence, we switch the order.
359 :     #
360 :     # If the right operand is also a Value object, we do the object's
361 :     # dot method to combine the two objects of the same class.
362 :     #
363 :     # Otherwise, since . is used for string concatenation, we want to retain
364 :     # that. Since the resulting string is often used in Formula and will be
365 : dpvc 2579 # parsed again, we put parentheses around the values to guarantee that
366 : sh002i 2558 # the values will be treated as one mathematical unit. For example, if
367 :     # $f = Formula("1+x") and $g = Formula("y") then Formula("$f/$g") will be
368 :     # (1+x)/y not 1+(x/y), as it would be without the implicit parentheses.
369 :     #
370 :     sub _dot {
371 :     my ($l,$r,$flag) = @_;
372 :     return Value::_dot($r,$l,!$flag) if ($l->promotePrecedence($r));
373 :     return $l->dot($r,$flag) if (Value::isValue($r));
374 : dpvc 2606 $l = $l->stringify; $l = '('.$l.')' unless $$Value::context->flag('StringifyAsTeX');
375 : sh002i 2558 return ($flag)? ($r.$l): ($l.$r);
376 :     }
377 :     #
378 :     # Some classes override this
379 :     #
380 : dpvc 2603 sub dot {
381 : sh002i 2558 my ($l,$r,$flag) = @_;
382 : dpvc 2606 my $tex = $$Value::context->flag('StringifyAsTeX');
383 :     $l = $l->stringify; $l = '('.$l.')' if $tex;
384 :     if (ref($r)) {$r = $r->stringify; $r = '('.$l.')' if $tex}
385 : sh002i 2558 return ($flag)? ($r.$l): ($l.$r);
386 :     }
387 :    
388 :     #
389 :     # Compare the values of the objects
390 :     # (list classes should replace this)
391 :     #
392 :     sub compare {
393 :     my ($l,$r,$flag) = @_;
394 :     if ($l->promotePrecedence($r)) {return $r->compare($l,!$flag)}
395 :     return $l->value <=> $r->value;
396 :     }
397 :    
398 :     #
399 :     # Generate the various output formats
400 : dpvc 2606 # (can be replaced by sub-classes)
401 : sh002i 2558 #
402 : dpvc 2606 sub stringify {
403 :     my $self = shift;
404 :     return $self->TeX() if $$Value::context->flag('StringifyAsTeX');
405 : dpvc 2612 $self->string;
406 : dpvc 2606 }
407 :     sub string {shift->value}
408 :     sub TeX {shift->string(@_)}
409 : sh002i 2558 #
410 :     # For perl, call the appropriate constructor around the objects data
411 :     #
412 :     sub perl {
413 :     my $self = shift; my $parens = shift; my $matrix = shift;
414 :     my $class = $self->class; my $mtype = $class eq 'Matrix';
415 :     my $perl; my @p = ();
416 :     foreach my $x (@{$self->data}) {
417 :     if (Value::isValue($x)) {push(@p,$x->perl(0,$mtype))} else {push(@p,$x)}
418 :     }
419 :     @p = ("'".$self->{open}."'",@p,"'".$self->{close}."'") if $class eq 'Interval';
420 :     if ($matrix) {
421 :     $perl = '['.join(',',@p).']';
422 :     } else {
423 :     $perl = $class.'('.join(',',@p).')';
424 :     $perl = '('.$perl.')' if $parens == 1;
425 :     }
426 :     return $perl;
427 :     }
428 :    
429 :     #
430 :     # Stubs for when called by Parser
431 :     #
432 :     sub eval {shift}
433 :     sub reduce {shift}
434 :    
435 : dpvc 2579 sub ijk {
436 :     Value::Error("Can't use method 'ijk' with objects of type '".(shift)->class."'");
437 :     }
438 :    
439 : sh002i 2558 #
440 :     # Report an error
441 :     #
442 :     sub Error {
443 :     my $message = shift;
444 : dpvc 2579 $$context->setError($message,'');
445 : dpvc 2592 # die $message . traceback();
446 :     die $message . getCaller();
447 : sh002i 2558 }
448 :    
449 :     #
450 :     # Try to locate the line and file where the error occurred
451 :     #
452 :     sub getCaller {
453 :     my $frame = 2;
454 :     while (my ($pkg,$file,$line,$subname) = caller($frame++)) {
455 :     return " at line $line of $file\n"
456 :     unless $pkg =~ /^(Value|Parser)/ ||
457 :     $subname =~ m/^(Value|Parser).*(new|call)$/;
458 :     }
459 :     return "";
460 :     }
461 :    
462 : dpvc 2579 #
463 :     # For debugging
464 :     #
465 :     sub traceback {
466 :     my $frame = 2;
467 :     my $trace = '';
468 :     while (my ($pkg,$file,$line,$subname) = caller($frame++))
469 :     {$trace .= " in $subname at line $line of $file\n"}
470 :     return $trace;
471 :     }
472 :    
473 : sh002i 2558 ###########################################################################
474 :     #
475 :     # Load the sub-classes.
476 :     #
477 :    
478 : dpvc 2579 use Value::Real;
479 : sh002i 2558 use Value::Complex;
480 : dpvc 2603 use Value::Infinity;
481 : sh002i 2558 use Value::Point;
482 :     use Value::Vector;
483 :     use Value::Matrix;
484 :     use Value::List;
485 :     use Value::Interval;
486 :     use Value::Union;
487 : dpvc 2609 use Value::String;
488 : sh002i 2558 # use Value::Formula;
489 :    
490 : dpvc 2592 use Value::AnswerChecker; # for WeBWorK
491 :    
492 : sh002i 2558 ###########################################################################
493 : dpvc 2592
494 :     use vars qw($installed);
495 :     $Value::installed = 1;
496 :    
497 :     ###########################################################################
498 :     ###########################################################################
499 : dpvc 2576 #
500 :     # To Do:
501 :     #
502 : dpvc 2579 # Make Complex class include more of Complex1.pm
503 :     # Make better interval comparison
504 : dpvc 2576 #
505 :     ###########################################################################
506 : sh002i 2558
507 : dpvc 2592 1;

aubreyja at gmail dot com
ViewVC Help
Powered by ViewVC 1.0.9