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

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

Parent Directory Parent Directory | Revision Log Revision Log


Revision 3652 - (download) (as text) (annotate)
Sat Sep 24 00:47:30 2005 UTC (14 years, 5 months ago) by dpvc
File size: 18870 byte(s)
Added ability to have answers that are empty strings.  String("") now
will produce a valid string object regardless of the Context's defined
string values.  (You can prevent this using

       Context()->flags->set(allowEmptyStrings=>0);

if you wish).  String("")->cmp will produce an answer checker for an
empty string (it removes the blank checker that WW installs).

    1 package Value;
    2 my $pkg = 'Value';
    3 use vars qw($context $defaultContext %Type);
    4 use strict;
    5 
    6 #############################################################
    7 #
    8 #  Initialize the context
    9 #
   10 
   11 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     'Set'    => {open => '{', close => '}'},
   20   },
   21   flags => {
   22     #
   23     #  For vectors:
   24     #
   25     ijk => 0,  # print vectors as <...>
   26     #
   27     #  For strings:
   28     #
   29     allowEmptyStrings => 1,
   30     infiniteWord => 'infinity',
   31     #
   32     #  For intervals and unions:
   33     #
   34     ignoreEndpointTypes => 0,
   35     reduceSets => 1,
   36     reduceSetsForComparison => 1,
   37     reduceUnions => 1,
   38     reduceUnionsForComparison => 1,
   39     #
   40     #  For fuzzy reals:
   41     #
   42     useFuzzyReals => 1,
   43     tolerance    => 1E-4,
   44     tolType      => 'relative',
   45     zeroLevel    => 1E-14,
   46     zeroLevelTol => 1E-12,
   47     #
   48     #  For Formulas:
   49     #
   50     limits       => [-2,2],
   51     num_points   => 5,
   52     granularity  => 1000,
   53     resolution   => undef,
   54     max_adapt    => 1E8,
   55     checkUndefinedPoints => 0,
   56     max_undefined => undef,
   57   },
   58 );
   59 
   60 $context = \$defaultContext;
   61 
   62 
   63 #
   64 #  Precedence of the various types
   65 #    (They will be promoted upward automatically when needed)
   66 #
   67 $$context->{precedence} = {
   68    'Number'   =>  0,
   69    'Real'     =>  1,
   70    'Infinity' =>  2,
   71    'Complex'  =>  3,
   72    'Point'    =>  4,
   73    'Vector'   =>  5,
   74    'Matrix'   =>  6,
   75    'List'     =>  7,
   76    'Interval' =>  8,
   77    'Set'      =>  9,
   78    'Union'    => 10,
   79    'String'   => 11,
   80    'Formula'  => 12,
   81    'special'  => 20,
   82 };
   83 
   84 #
   85 #  Binding of perl operator to class method
   86 #
   87 $$context->{method} = {
   88    '+'   => 'add',
   89    '-'   => 'sub',
   90    '*'   => 'mult',
   91    '/'   => 'div',
   92    '**'  => 'power',
   93    '.'   => '_dot',  # see _dot below
   94    'x'   => 'cross',
   95    '<=>' => 'compare',
   96    'cmp' => 'compare_string',
   97 };
   98 
   99 $$context->{pattern}{infinite} = '[-+]?inf(?:inity)?';
  100 $$context->{pattern}{infinity} = '\+?inf(?:inity)?';
  101 $$context->{pattern}{-infinity} = '-inf(?:inity)?';
  102 
  103 push(@{$$context->{data}{values}},'method','precedence');
  104 
  105 #
  106 #  Get the value of a flag from the object itself,
  107 #  or from the context, or from the default context
  108 #  or from the given default, whichever is found first.
  109 #
  110 sub getFlag {
  111   my $self = shift; my $name = shift;
  112   return $self->{$name} if ref($self) && defined($self->{$name});
  113   return $self->{context}{flags}{$name} if ref($self) && defined($self->{context}{flags}{$name});
  114   return $$Value::context->{flags}{$name} if defined($$Value::context->{flags}{$name});
  115   return shift;
  116 }
  117 
  118 sub copy {
  119   my $self = shift;
  120   my $copy = {%{$self}}; $copy->{data} = [@{$self->{data}}];
  121   foreach my $x (@{$copy->{data}}) {$x = $x->copy if Value::isValue($x)}
  122   return bless $copy, ref($self);
  123 }
  124 
  125 #############################################################
  126 
  127 #
  128 #  Check if a value is a number, complex, etc.
  129 #
  130 sub matchNumber   {my $n = shift; $n =~ m/^$$context->{pattern}{signedNumber}$/i}
  131 sub matchInfinite {my $n = shift; $n =~ m/^$$context->{pattern}{infinite}$/i}
  132 sub isReal    {class(shift) eq 'Real'}
  133 sub isComplex {class(shift) eq 'Complex'}
  134 sub isFormula {
  135   my $v = shift;
  136   return class($v) eq 'Formula' ||
  137          (ref($v) && ref($v) ne 'ARRAY' && $v->{isFormula});
  138 }
  139 sub isValue   {
  140   my $v = shift;
  141   return (ref($v) || $v) =~ m/^Value::/ ||
  142          (ref($v) && ref($v) ne 'ARRAY' && $v->{isValue});
  143 }
  144 
  145 sub isNumber {
  146   my $n = shift;
  147   return $n->{tree}->isNumber if isFormula($n);
  148   return isReal($n) || isComplex($n) || matchNumber($n);
  149 }
  150 
  151 sub isRealNumber {
  152   my $n = shift;
  153   return $n->{tree}->isRealNumber if isFormula($n);
  154   return isReal($n) || matchNumber($n);
  155 }
  156 
  157 sub isZero {
  158   my $self = shift;
  159   return 0 if scalar(@{$self->{data}}) == 0;
  160   foreach my $x (@{$self->{data}}) {return 0 unless $x eq "0"}
  161   return 1;
  162 }
  163 
  164 sub isOne {0}
  165 
  166 sub isSetOfReals {0}
  167 sub canBeInUnion {
  168   my $self = shift;
  169   return $self->length == 2 && $self->typeRef->{entryType}{name} eq 'Number' &&
  170     $self->{open} =~ m/^[\(\[]$/ && $self->{close} =~ m/^[\)\]]$/;
  171 }
  172 
  173 #
  174 #  Convert non-Value objects to Values, if possible
  175 #
  176 sub makeValue {
  177   my $x = shift; my %params = (showError => 0, makeFormula => 1, @_);
  178   return $x if ref($x) && ref($x) ne 'ARRAY';
  179   return Value::Real->make($x) if matchNumber($x);
  180   if (matchInfinite($x)) {
  181     my $I = Value::Infinity->new();
  182     $I = $I->neg if $x =~ m/^$$Value::context->{pattern}{-infinity}$/;
  183     return $I;
  184   }
  185   return Value::String->make($x)
  186     if !$Parser::installed || $$Value::context->{strings}{$x} ||
  187        ($x eq '' && $$Value::context->{flags}{allowEmptyStrings});
  188   return $x if !$params{makeFormula};
  189   Value::Error("String constant '%s' is not defined in this context",$x)
  190     if $params{showError};
  191   $x = Value::Formula->new($x);
  192   $x = $x->eval if $x->isConstant;
  193   return $x;
  194 }
  195 
  196 #
  197 #  Get a printable version of the class of an object
  198 #
  199 sub showClass {
  200   my $value = makeValue(shift,makeFormula=>0);
  201   return "'".$value."'" unless Value::isValue($value);
  202   my $class = class($value);
  203   return showType($value) if ($class eq 'List');
  204   $class .= ' Number' if $class =~ m/^(Real|Complex)$/;
  205   $class .= ' of Intervals' if $class eq 'Union';
  206   $class = 'Word' if $class eq 'String';
  207   return 'a Formula that returns '.showType($value->{tree}) if ($class eq 'Formula');
  208   return 'an '.$class if $class =~ m/^[aeio]/i;
  209   return 'a '.$class;
  210 }
  211 
  212 #
  213 #  Get a printable version of the type of an object
  214 #
  215 sub showType {
  216   my $value = shift;
  217   my $type = $value->type;
  218   if ($type eq 'List') {
  219     my $ltype = $value->typeRef->{entryType}{name};
  220     if ($ltype && $ltype ne 'unknown') {
  221       $ltype =~ s/y$/ie/;
  222       $type .= ' of '.$ltype.'s';
  223     }
  224   }
  225   return 'an Infinity' if $type eq 'String' && $value->{isInfinite};
  226   return 'a Word' if $type eq 'String';
  227   return 'a Complex Number' if $value->isComplex;
  228   return 'an '.$type if $type =~ m/^[aeio]/i;
  229   return 'a '.$type;
  230 }
  231 
  232 #
  233 #  Return a string describing a value's type
  234 #
  235 sub getType {
  236   my $equation = shift; my $value = shift;
  237   my $strings = $equation->{context}{strings};
  238   if (ref($value) eq 'ARRAY') {
  239     return 'Interval' if ($value->[0] =~ m/^[(\[]$/ && $value->[-1] =~ m/^[)\]]$/);
  240     my ($type,$ltype);
  241     foreach my $x (@{$value}) {
  242       $type = getType($equation,$x);
  243       if ($type eq 'value') {
  244         $type = $x->type if $x->class eq 'Formula';
  245         $type = 'Number' if $x->class eq 'Complex' || $type eq 'Complex';
  246       }
  247       $ltype = $type if $ltype eq '';
  248       return 'List' if $type ne $ltype;
  249     }
  250     return 'Point' if $ltype eq 'Number';
  251     return 'Matrix' if $ltype =~ m/Point|Matrix/;
  252     return 'List';
  253   }
  254   elsif (Value::isFormula($value)) {return 'Formula'}
  255   elsif (Value::class($value) eq 'Infinity') {return 'Infinity'}
  256   elsif (Value::isReal($value)) {return 'Number'}
  257   elsif (Value::isValue($value)) {return 'value'}
  258   elsif (ref($value)) {return 'unknown'}
  259   elsif (defined($strings->{$value})) {return 'String'}
  260   elsif (Value::isNumber($value)) {return 'Number'}
  261   elsif ($value eq '' && $equation->{context}{flags}{allowEmptyStrings}) {return 'String'}
  262   return 'unknown';
  263 }
  264 
  265 #
  266 #  Get a string describing a value's type,
  267 #    and convert the value to a Value object (if needed)
  268 #
  269 sub getValueType {
  270   my $equation = shift; my $value = shift;
  271   my $type = Value::getType($equation,$value);
  272   if ($type eq 'String') {$type = $Value::Type{string}}
  273   elsif ($type eq 'Number') {$type = $Value::Type{number}}
  274   elsif ($type eq 'Infinity') {$type = $Value::Type{infinity}}
  275   elsif ($type eq 'value' || $type eq 'Formula') {$type = $value->typeRef}
  276   elsif ($type eq 'unknown') {
  277     $equation->Error(["Can't convert %s to a constant",Value::showClass($value)]);
  278   } else {
  279     $type = 'Value::'.$type, $value = $type->new(@{$value});
  280     $type = $value->typeRef;
  281   }
  282   return ($value,$type);
  283 }
  284 
  285 #
  286 #  Convert a list of values to a list of formulas (called by Parser::Value)
  287 #
  288 sub toFormula {
  289   my $formula = shift;
  290   my $processed = 0;
  291   my @f = (); my $vars = {};
  292   foreach my $x (@_) {
  293     if (isFormula($x)) {
  294       $formula->{context} = $x->{context}, $processed = 1 unless $processed;
  295       $formula->{variables} = {%{$formula->{variables}},%{$x->{variables}}};
  296       push(@f,$x->{tree}->copy($formula));
  297     } else {
  298       push(@f,$formula->{context}{parser}{Value}->new($formula,$x));
  299     }
  300   }
  301   return (@f);
  302 }
  303 
  304 #
  305 #  Convert a list of values (and open and close parens)
  306 #    to a formula whose type is the list type associated with
  307 #    the parens.
  308 #
  309 sub formula {
  310   my $self = shift; my $values = shift;
  311   my $class = $self->class;
  312   my $list = $$context->lists->get($class);
  313   my $open = $list->{'open'};
  314   my $close = $list->{'close'};
  315   my $paren = $open; $paren = 'list' if $class eq 'List';
  316   my $formula = Value::Formula->blank;
  317   my @coords = Value::toFormula($formula,@{$values});
  318   $formula->{tree} = $formula->{context}{parser}{List}->new($formula,[@coords],0,
  319      $formula->{context}{parens}{$paren},$coords[0]->typeRef,$open,$close);
  320   $formula->{autoFormula} = 1;  # mark that this was generated automatically
  321   return $formula;
  322 }
  323 
  324 #
  325 #  A shortcut for new() that creates an instance of the object,
  326 #    but doesn't do the error checking.  We assume the data are already
  327 #    known to be good.
  328 #
  329 sub make {
  330   my $self = shift; my $class = ref($self) || $self;
  331   bless {data => [@_]}, $class;
  332 }
  333 
  334 #
  335 #  Easy method for setting parameters of an object
  336 #
  337 sub with {
  338   my $self = shift; my %hash = @_;
  339   foreach my $id (keys(%hash)) {$self->{$id} = $hash{$id}}
  340   return $self;
  341 }
  342 
  343 #
  344 #  Return a type structure for the item
  345 #    (includes name, length of vectors, and so on)
  346 #
  347 sub Type {
  348   my $name = shift; my $length = shift; my $entryType = shift;
  349   $length = 1 unless defined $length;
  350   return {name => $name, length => $length, entryType => $entryType,
  351           list => (defined $entryType), @_};
  352 }
  353 
  354 #
  355 #  Some predefined types
  356 #
  357 %Type = (
  358   number   => Value::Type('Number',1),
  359   complex  => Value::Type('Number',2),
  360   string   => Value::Type('String',1),
  361   infinity => Value::Type('Infinity',1),
  362   unknown  => Value::Type('unknown',0,undef,list => 1)
  363 );
  364 
  365 #
  366 #  Return various information about the object
  367 #
  368 sub value {return @{(shift)->{data}}}                  # the value of the object (as an array)
  369 sub data {return (shift)->{data}}                      # the reference to the value
  370 sub length {return scalar(@{(shift)->{data}})}         # the number of coordinates
  371 sub type {return (shift)->typeRef->{name}}             # the object type
  372 sub entryType {return (shift)->typeRef->{entryType}}   # the coordinate type
  373 #
  374 #  The the full type-hash for the item
  375 #
  376 sub typeRef {
  377   my $self = shift;
  378   return Value::Type($self->class, $self->length, $Value::Type{number});
  379 }
  380 #
  381 #  The Value.pm object class
  382 #
  383 sub class {
  384   my $self = shift; my $class = ref($self) || $self;
  385   $class =~ s/.*:://;
  386   return $class;
  387 }
  388 
  389 #
  390 #  Get an element from a point, vector, matrix, or list
  391 #
  392 sub extract {
  393   my $M = shift; my $i; my @indices = @_;
  394   return unless Value::isValue($M);
  395   @indices = $_[0]->value if scalar(@_) == 1 && Value::isValue($_[0]);
  396   while (scalar(@indices) > 0) {
  397     $i = shift @indices; $i-- if $i > 0; $i = $i->value if Value::isValue($i);
  398     Value::Error("Can't extract element number '%s' (index must be an integer)",$i)
  399       unless $i =~ m/^-?\d+$/;
  400     $M = $M->data->[$i];
  401   }
  402   return $M;
  403 }
  404 
  405 
  406 #
  407 #  Promote an operand to the same precedence as the current object
  408 #
  409 sub promotePrecedence {
  410   my $self = shift; my $other = shift;
  411   return 0 unless Value::isValue($other);
  412   my $sprec = $$context->{precedence}{class($self)};
  413   my $oprec = $$context->{precedence}{class($other)};
  414   return (defined($oprec) && $sprec < $oprec);
  415 }
  416 
  417 sub promote {shift}
  418 
  419 #
  420 #  Default stub to call when no function is defined for an operation
  421 #
  422 sub nomethod {
  423   my ($l,$r,$flag,$op) = @_;
  424   my $call = $$context->{method}{$op};
  425   if (defined($call) && $l->promotePrecedence($r)) {return $r->$call($l,!$flag)}
  426   my $error = "Can't use '%s' with %s-valued operands";
  427   $error .= " (use '**' for exponentiation)" if $op eq '^';
  428   Value::Error($error,$op,$l->class);
  429 }
  430 
  431 #
  432 #  Stubs for the sub-classes
  433 #
  434 sub add   {nomethod(@_,'+')}
  435 sub sub   {nomethod(@_,'-')}
  436 sub mult  {nomethod(@_,'*')}
  437 sub div   {nomethod(@_,'/')}
  438 sub power {nomethod(@_,'**')}
  439 sub cross {nomethod(@_,'x')}
  440 
  441 #
  442 #  If the right operand is higher precedence, we switch the order.
  443 #
  444 #  If the right operand is also a Value object, we do the object's
  445 #  dot method to combine the two objects of the same class.
  446 #
  447 #  Otherwise, since . is used for string concatenation, we want to retain
  448 #  that.  Since the resulting string is often used in Formula and will be
  449 #  parsed again, we put parentheses around the values to guarantee that
  450 #  the values will be treated as one mathematical unit.  For example, if
  451 #  $f = Formula("1+x") and $g = Formula("y") then Formula("$f/$g") will be
  452 #  (1+x)/y not 1+(x/y), as it would be without the implicit parentheses.
  453 #
  454 sub _dot {
  455   my ($l,$r,$flag) = @_;
  456   return Value::_dot($r,$l,!$flag) if ($l->promotePrecedence($r));
  457   return $l->dot($r,$flag) if (Value::isValue($r));
  458   $l = $l->stringify; $l = '('.$l.')' unless $$Value::context->flag('StringifyAsTeX');
  459   return ($flag)? ($r.$l): ($l.$r);
  460 }
  461 #
  462 #  Some classes override this
  463 #
  464 sub dot {
  465   my ($l,$r,$flag) = @_;
  466   my $tex = $$Value::context->flag('StringifyAsTeX');
  467   $l = $l->stringify; $l = '('.$l.')' if $tex;
  468   if (ref($r)) {$r = $r->stringify; $r = '('.$l.')' if $tex}
  469   return ($flag)? ($r.$l): ($l.$r);
  470 }
  471 
  472 #
  473 #  Compare the values of the objects
  474 #    (list classes should replace this)
  475 #
  476 sub compare {
  477   my ($l,$r,$flag) = @_;
  478   if ($l->promotePrecedence($r)) {return $r->compare($l,!$flag)}
  479   return $l->value <=> $r->value;
  480 }
  481 
  482 #
  483 #  Compare the values as strings
  484 #
  485 sub compare_string {
  486   my ($l,$r,$flag) = @_;
  487   if ($l->promotePrecedence($r)) {return $r->compare_string($l,!$flag)}
  488   $l = $l->stringify; $r = $r->stringify if Value::isValue($r);
  489   if ($flag) {my $tmp = $l; $l = $r; $r = $tmp}
  490   return $l cmp $r;
  491 }
  492 
  493 #
  494 #  Generate the various output formats
  495 #  (can be replaced by sub-classes)
  496 #
  497 sub stringify {
  498   my $self = shift;
  499   return $self->TeX() if $$Value::context->flag('StringifyAsTeX');
  500   my $def = $$Value::context->lists->get($self->class);
  501   return $self->string unless $def;
  502   my $open = $self->{open};   $open  = $def->{open}  unless defined($open);
  503   my $close = $self->{close}; $close = $def->{close} unless defined($close);
  504   $open.join($def->{separator},@{$self->data}).$close;
  505 }
  506 
  507 sub string {
  508   my $self = shift; my $equation = shift;
  509   my $def = ($equation->{context} || $$Value::context)->lists->get($self->class);
  510   return $self->value unless $def;
  511   my $open = shift; my $close = shift;
  512   $open  = $self->{open}  unless defined($open);
  513   $open  = $def->{open}   unless defined($open);
  514   $close = $self->{close} unless defined($close);
  515   $close = $def->{close}  unless defined($close);
  516   my @coords = ();
  517   foreach my $x (@{$self->data}) {
  518     if (Value::isValue($x))
  519       {push(@coords,$x->string($equation))} else {push(@coords,$x)}
  520   }
  521   return $open.join($def->{separator},@coords).$close;
  522 }
  523 
  524 sub TeX {
  525   my $self = shift; my $equation = shift;
  526   my $context = $equation->{context} || $$Value::context;
  527   my $def = $context->lists->get($self->class);
  528   return $self->string(@_) unless $def;
  529   my $open = shift; my $close = shift;
  530   $open  = $self->{open}  unless defined($open);
  531   $open  = $def->{open}   unless defined($open);
  532   $close = $self->{close} unless defined($close);
  533   $close = $def->{close}  unless defined($close);
  534   $open =~ s/([{}])/\\$1/g; $close =~ s/([{}])/\\$1/g;
  535   $open = '\left'.$open if $open; $close = '\right'.$close if $close;
  536   my @coords = (); my $str = $context->{strings};
  537   foreach my $x (@{$self->data}) {
  538     if (Value::isValue($x)) {push(@coords,$x->TeX($equation))}
  539     elsif (defined($str->{$x}) && $str->{$x}{TeX}) {push(@coords,$str->{$x}{TeX})}
  540     else {push(@coords,$x)}
  541   }
  542   return $open.join(',',@coords).$close;
  543 }
  544 
  545 #
  546 #  For perl, call the appropriate constructor around the object's data
  547 #
  548 sub perl {
  549   my $self = shift; my $parens = shift; my $matrix = shift;
  550   my $class = $self->class;
  551   my $mtype = $class eq 'Matrix'; $mtype = -1 if $mtype & !$matrix;
  552   my $perl; my @p = ();
  553   foreach my $x (@{$self->data}) {
  554     if (Value::isValue($x)) {push(@p,$x->perl(0,$mtype))} else {push(@p,$x)}
  555   }
  556   @p = ("'".$self->{open}."'",@p,"'".$self->{close}."'") if $class eq 'Interval';
  557   if ($matrix) {
  558     $perl = join(',',@p);
  559     $perl = '['.$perl.']' if $mtype > 0;
  560   } else {
  561     $perl = 'new '.ref($self).'('.join(',',@p).')';
  562     $perl = "($perl)->with(open=>'$self->{open}',close=>'$self->{close}')"
  563       if $class eq 'List' && $self->{open}.$self->{close} ne '()';
  564     $perl = '('.$perl.')' if $parens == 1;
  565   }
  566   return $perl;
  567 }
  568 
  569 #
  570 #  Stubs for when called by Parser
  571 #
  572 sub eval {shift}
  573 sub reduce {shift}
  574 
  575 sub ijk {
  576   Value::Error("Can't use method 'ijk' with objects of type '%s'",(shift)->class);
  577 }
  578 
  579 #
  580 #  Report an error
  581 #
  582 sub Error {
  583   my $message = shift;
  584   $message = [$message,@_] if scalar(@_) > 0;
  585   $$context->setError($message,'');
  586   $message = $$context->{error}{message};
  587   die $message . traceback() if $$context->{debug};
  588   die $message . getCaller();
  589 }
  590 
  591 #
  592 #  Try to locate the line and file where the error occurred
  593 #
  594 sub getCaller {
  595   my $frame = 2;
  596   while (my ($pkg,$file,$line,$subname) = caller($frame++)) {
  597     return " at line $line of $file\n"
  598       unless $pkg =~ /^(Value|Parser)/ ||
  599              $subname =~ m/^(Value|Parser).*(new|call)$/;
  600   }
  601   return "";
  602 }
  603 
  604 #
  605 #  For debugging
  606 #
  607 sub traceback {
  608   my $frame = shift; $frame = 2 unless defined($frame);
  609   my $trace = '';
  610   while (my ($pkg,$file,$line,$subname) = caller($frame++))
  611     {$trace .= " in $subname at line $line of $file\n"}
  612   return $trace;
  613 }
  614 
  615 ###########################################################################
  616 #
  617 #  Load the sub-classes.
  618 #
  619 
  620 use Value::Real;
  621 use Value::Complex;
  622 use Value::Infinity;
  623 use Value::Point;
  624 use Value::Vector;
  625 use Value::Matrix;
  626 use Value::List;
  627 use Value::Interval;
  628 use Value::Set;
  629 use Value::Union;
  630 use Value::String;
  631 use Value::Formula;
  632 
  633 use Value::WeBWorK;  # stuff specific to WeBWorK
  634 
  635 ###########################################################################
  636 
  637 use vars qw($installed);
  638 $Value::installed = 1;
  639 
  640 ###########################################################################
  641 ###########################################################################
  642 #
  643 #    To Do:
  644 #
  645 #  Make Complex class include more of Complex1.pm
  646 #  Make better interval comparison
  647 #  Include context in objects within new() calls.
  648 #
  649 ###########################################################################
  650 
  651 1;

aubreyja at gmail dot com
ViewVC Help
Powered by ViewVC 1.0.9