Parent Directory
|
Revision Log
Added Parser::Number::NoDecimals() function that causes the parser to give an error message when the student types a decimal number. This means the student must enter fractions as fractions, or values like sqrt(2) or pi/4 symbolically not numerically.
1 package Parser; 2 my $pkg = "Parser"; 3 4 use strict; 5 #use Carp; 6 7 ################################################## 8 # 9 # Parse a string and create a new Parser object 10 # If the string is already a parsed object then copy the parse tree 11 # If it is a Value, make an appropriate tree for it. 12 # 13 sub new { 14 my $self = shift; my $class = ref($self) || $self; 15 my $string = shift; 16 my $math = bless { 17 string => undef, 18 tokens => [], tree => undef, 19 variables => {}, values => {}, 20 context => Parser::Context->current, 21 }, $class; 22 if (ref($string) =~ m/^(Parser|Value::Formula)/) { 23 my $tree = $string; $tree = $tree->{tree} if exists $tree->{tree}; 24 $math->{tree} = $tree->copy($math); 25 } elsif (ref($string) =~ m/^Value/) { 26 $math->{tree} = Parser::Value->new($math,$string); 27 } else { 28 $math->{string} = $string; 29 $math->tokenize; 30 $math->parse; 31 } 32 return $math; 33 } 34 35 sub copy {my $self = shift; $self->new($self)} 36 37 ################################################## 38 # 39 # Break the string into tokens based on the patterns for the various 40 # types of objects. 41 # 42 sub tokenize { 43 my $self = shift; my $space; 44 my $tokens = $self->{tokens}; my $string = $self->{string}; 45 my $tokenPattern = $self->{context}{pattern}{token}; 46 @{$tokens} = (); $self->{error} = 0; $self->{message} = ''; 47 $string =~ m/^\s*/gc; my $p0 = 0; my $p1; 48 while (pos($string) < length($string)) { 49 $p0 = pos($string); 50 if ($string =~ m/\G$tokenPattern/gc) { 51 $p1 = pos($string); 52 push(@{$tokens},['str',$1,$p0,$p1,$space]) if (defined($1)); 53 push(@{$tokens},['fn',$2,$p0,$p1,$space]) if (defined($2)); 54 push(@{$tokens},['const',$3,$p0,$p1,$space]) if (defined($3)); 55 push(@{$tokens},['num',$4,$p0,$p1,$space]) if (defined($4)); 56 push(@{$tokens},['op',$5,$p0,$p1,$space]) if (defined($5)); 57 push(@{$tokens},['open',$6,$p0,$p1,$space]) if (defined($6)); 58 push(@{$tokens},['close',$7,$p0,$p1,$space]) if (defined($7)); 59 push(@{$tokens},['var',$8,$p0,$p1,$space]) if (defined($8)); 60 } else { 61 push(@{$tokens},['error',substr($string,$p0,1),$p0,$p0+1]); 62 $self->{error} = 1; 63 last; 64 } 65 $space = ($string =~ m/\G\s+/gc); 66 } 67 } 68 69 ################################################## 70 # 71 # Parse the token list to produce the expression tree. This does syntax checks 72 # and reports "compile-time" errors. 73 # 74 # Start with a stack that has a single entry (an OPEN object for the expression) 75 # For each token, try to add that token to the tree. 76 # After all tokens have been finished, add a CLOSE object for the initial OPEN 77 # and save the complete tree 78 # 79 sub parse { 80 my $self = shift; 81 $self->{tree} = undef; $self->{error} = 0; 82 $self->{stack} = [{type => 'open', value => 'start'}]; 83 foreach my $ref (@{$self->{tokens}}) { 84 $self->{ref} = $ref; $self->{space} = $ref->[4]; 85 for ($ref->[0]) { 86 /open/ and do {$self->Open($ref->[1]); last}; 87 /close/ and do {$self->Close($ref->[1],$ref); last}; 88 /op/ and do {$self->Op($ref->[1],$ref); last}; 89 /num/ and do {$self->Num($ref->[1]); last}; 90 /const/ and do {$self->Const($ref->[1]); last}; 91 /var/ and do {$self->Var($ref->[1]); last}; 92 /fn/ and do {$self->Fn($ref->[1]); last}; 93 /str/ and do {$self->Str($ref->[1]); last}; 94 /error/ and do {$self->Error("Unexpected character '$ref->[1]'",$ref); last}; 95 } 96 return if ($self->{error}); 97 } 98 $self->Close('start'); return if ($self->{error}); 99 $self->{tree} = $self->{stack}->[0]->{value}; 100 } 101 102 103 # Get the top or previous item of the stack 104 # 105 sub top { 106 my $self = shift; my $i = shift || 0; 107 return $self->{stack}->[$i-1]; 108 } 109 sub prev {(shift)->top(-1)} 110 111 # 112 # Push or pop the top of the stack 113 # 114 sub pop {pop(@{(shift)->{stack}})} 115 sub push {push(@{(shift)->{stack}},@_)} 116 117 # 118 # Return the type of the top item 119 # 120 sub state {(shift)->top->{type}} 121 122 # 123 # Report an error at a given possition (if possible) 124 # 125 sub Error { 126 my $self = shift; my $context = $self->{context}; 127 my $message = shift; my $ref = shift; my $string; 128 if ($ref) { 129 $message .= "; see position ".($ref->[2]+1)." of formula"; 130 $string = $self->{string}; 131 $ref = [$ref->[2],$ref->[3]]; 132 } 133 $context->setError($message,$string,$ref); 134 die $message . Value::getCaller(); 135 # confess $message; 136 } 137 138 # 139 # Insert an implicit multiplication 140 # 141 sub ImplicitMult { 142 my $self = shift; 143 my $ref = $self->{ref}; 144 $self->Op(' '); 145 $self->{ref} = $ref; 146 } 147 148 # 149 # Push an operator onto the expression stack. 150 # We save the operator symbol, the precedence, etc. 151 # 152 sub pushOperator { 153 my $self = shift; 154 my ($op,$precedence,$reverse) = @_; 155 $self->push({ 156 type => 'operator', ref => $self->{ref}, 157 name => $op, precedence => $precedence, reverse => $reverse 158 }); 159 } 160 161 # 162 # Push an operand onto the expression stack. 163 # 164 sub pushOperand { 165 my $self = shift; my $value = shift; 166 $self->push({type => 'operand', ref => $self->{ref}, value => $value}); 167 } 168 169 ################################################## 170 # 171 # Handle an operator token 172 # 173 # Get the operator data from the context 174 # If the top of the stack is an operand 175 # If the operator is a left-associative unary operator 176 # Insert an implicit multiplication and save the operator 177 # Otherwise 178 # Complete any pending operations of higher precedence 179 # If the top item is still an operand 180 # If we have a (right associative) unary operator 181 # Apply it to the top operand 182 # Otherwise (binary operator) 183 # Convert the space operator to explicit multiplication 184 # Save the opertor on the stack 185 # Otherwise, (top is not an operand) 186 # If the operator is an explicit on or the top is a function 187 # Call Op again to report the error, or to apply 188 # the operator to the function (this happens when 189 # there is a function to a power, for example) 190 # Otherwise (top is not an operand) 191 # If this is a left-associative unary operator, save it on the stack 192 # Otherwise, if it is a left-associative operator that CAN be unary 193 # Save the unary version of the operator on the stack 194 # Otherwise, if the top item is a function 195 # If the operator can be applied to functions, save it on the stack 196 # Otherwise, report that the function is missing its inputs 197 # Otherwise, report the missing operand for this operator 198 # 199 sub Op { 200 my $self = shift; my $name = shift; 201 my $ref = $self->{ref} = shift; 202 my $context = $self->{context}; my $op = $context->{operators}{$name}; 203 $op = $context->{operators}{$op->{space}} if $self->{space} && defined($op->{space}); 204 if ($self->state eq 'operand') { 205 if ($op->{type} eq 'unary' && $op->{associativity} eq 'left') { 206 $self->ImplicitMult(); 207 $self->pushOperator($name,$op->{precedence}); 208 } else { 209 $self->Precedence($op->{precedence}); 210 if ($self->state eq 'operand') { 211 if ($op->{type} eq 'unary') { 212 my $top = $self->pop; 213 $self->pushOperand(Parser::UOP->new($self,$name,$top->{value},$ref)); 214 } else { 215 $name = $context->{operators}{' '}{string} 216 if $name eq ' ' or $name eq $context->{operators}{' '}{space}; 217 $self->pushOperator($name,$op->{precedence}); 218 } 219 } elsif ($ref || $self->state ne 'fn') {$self->Op($name,$ref)} 220 } 221 } else { 222 $name = 'u'.$name, $op = $context->{operators}{$name} 223 if ($op->{type} eq 'both' && defined $context->{operators}{'u'.$name}); 224 if ($op->{type} eq 'unary' && $op->{associativity} eq 'left') { 225 $self->pushOperator($name,$op->{precedence}); 226 } elsif ($self->state eq 'fn') { 227 if ($op->{leftf}) { 228 $self->pushOperator($name,$op->{precedence},1); 229 } else { 230 my $top = $self->top; 231 $self->Error("Function '$top->{name}' is missing its input(s)",$top->{ref}); 232 } 233 } else {$self->Error("Missing operand before '$name'",$ref)} 234 } 235 } 236 237 ################################################## 238 # 239 # Handle an open parenthesis 240 # 241 # If the top of the stack is an operand 242 # Check if the open paren is really a close paren (for when the open 243 # and close symbol are the same) 244 # Otherwise insert an implicit multiplication 245 # Save the open object on the stack 246 # 247 sub Open { 248 my $self = shift; my $type = shift; 249 my $paren = $self->{context}{parens}{$type}; 250 if ($self->state eq 'operand') { 251 if ($type eq $paren->{close}) 252 {$self->Close($type,$self->{ref}); return} else {$self->ImplicitMult()} 253 } 254 $self->push({type => 'open', value => $type, ref => $self->{ref}}); 255 } 256 257 ################################################## 258 # 259 # Handle a close parenthesis 260 # 261 # When the top stack object is 262 # An open parenthesis (that is empty): 263 # Get the data for the type of parentheses 264 # If the parentheses can be empty and the parentheses match 265 # Save the empty list 266 # Otherwise report a message appropriate to the type of parentheses 267 # 268 # An operand: 269 # Complete any pending operations, and stop if there was an error 270 # If the top is no longer an operand 271 # Call Close to report the error and return 272 # Get the item before the operand (an OPEN object), and its parenthesis type 273 # If the parens match 274 # Pop the operand off the stack 275 # If the parens can't be removed, or if the operand is a list 276 # Make the operand into a list object 277 # Replace the paren object with the operand 278 # If the parentheses are used for function calls and the 279 # previous stack object is a function call, do the function apply 280 # Otherwise report an appropriate error message 281 # 282 # A function: 283 # Report an error message about missing inputs 284 # 285 # An operator: 286 # Report the missing operation 287 # 288 sub Close { 289 my $self = shift; my $type = shift; 290 my $ref = $self->{ref} = shift; 291 my $parens = $self->{context}{parens}; 292 293 for ($self->state) { 294 /open/ and do { 295 my $top = $self->pop; my $paren = $parens->{$top->{value}}; 296 if ($paren->{emptyOK} && $paren->{close} eq $type) { 297 $self->pushOperand(Parser::List->new($self,[],1,$paren)) 298 } 299 elsif ($type eq 'start') {$self->Error("Missing close parenthesis for '$top->{value}'",$top->{ref})} 300 elsif ($top->{value} eq 'start') {$self->Error("Extra close parenthesis '$type'",$ref)} 301 else {$self->Error("Empty parentheses: '$top->{value} $type'",$top->{ref})} 302 last; 303 }; 304 305 /operand/ and do { 306 $self->Precedence(0); return if ($self->{error}); 307 if ($self->state ne 'operand') {$self->Close($type,$ref); return} 308 my $paren = $parens->{$self->prev->{value}}; 309 if ($paren->{close} eq $type) { 310 my $top = $self->pop; 311 if (!$paren->{removable} || ($top->{value}->type eq "Comma")) { 312 $top = $top->{value}; 313 $top = {type => 'operand', value => 314 Parser::List->new($self,[$top->makeList],$top->{isConstant},$paren, 315 ($top->type eq 'Comma') ? $top->entryType : $top->typeRef, 316 ($type ne 'start') ? ($self->top->{value},$type) : () )}; 317 } 318 $self->pop; $self->push($top); 319 $self->CloseFn() if ($paren->{function} && $self->prev->{type} eq 'fn'); 320 } elsif ($paren->{formInterval} eq $type && $self->top->{value}->length == 2) { 321 my $top = $self->pop->{value}; my $open = $self->pop->{value}; 322 $self->pushOperand( 323 Parser::List->new($self,[$top->makeList],$top->{isConstant}, 324 $paren,$top->entryType,$open,$type)); 325 } else { 326 my $prev = $self->prev; 327 if ($type eq "start") {$self->Error("Missing close parenthesis for '$prev->{value}'",$prev->{ref})} 328 elsif ($prev->{value} eq "start") {$self->Error("Extra close parenthesis '$type'",$ref)} 329 else {$self->Error("Mismatched parentheses: '$prev->{value}' and '$type'",$ref)} 330 return; 331 } 332 last; 333 }; 334 335 /fn/ and do { 336 my $top = $self->top; 337 $self->Error("Function '$top->{name}' is missing its input(s)",$top->{ref}); 338 return; 339 }; 340 341 /operator/ and do { 342 my $top = $self->top(); my $name = $top->{name}; $name =~ s/^u//; 343 $self->Error("Missing operand after '$name'",$top->{ref}); 344 return; 345 }; 346 } 347 } 348 349 ################################################## 350 # 351 # Handle any pending operations of higher precedence 352 # 353 # While the top stack item is an operand: 354 # When the preceding item is: 355 # An pending operator: 356 # Get the precedence of the operator (use the special right-hand prrecedence 357 # of there is one, otherwise use the general precedence) 358 # Stop processing if the current operator precedence is higher 359 # If the stacked operator is binary or if it is reversed (for function operators) 360 # Stop processing if the precedence is equal and we are right associative 361 # If the operand for the stacked operator is a function 362 # If the operation is ^(-1) (for inverses) 363 # Push the inverse function name 364 # Otherwise 365 # Reverse the order of the stack, so that the function can be applied 366 # to the next operand (it will be unreversed later) 367 # Otherwise (not a function, so an operand) 368 # Get the operands and binary operator off the stack 369 # If it is reversed (for functions), get the order right 370 # Save the result of the binary operation as an operand on the stack 371 # Otherwise (the stack contains a unary operator) 372 # Get the operator and operand off the stack 373 # Push the result of the unary operator as an operand on the stack 374 # 375 # A pending function call: 376 # Keep working if the precedence of the operator is higher than a function call 377 # Otherwise apply the function to the operator and continue 378 # 379 # Anything else: 380 # Return (no more pending operations) 381 # 382 # If there was an error, stop processing 383 # 384 sub Precedence { 385 my $self = shift; my $precedence = shift; 386 my $context = $self->{context}; 387 while ($self->state eq 'operand') { 388 my $prev = $self->prev; 389 for ($prev->{type}) { 390 391 /operator/ and do { 392 my $prev_prec = $context->{operators}{$prev->{name}}{rprecedence}; 393 $prev_prec = $prev->{precedence} unless $prev_prec; 394 return if ($precedence > $prev_prec); 395 if ($self->top(-2)->{type} eq 'operand' || $prev->{reverse}) { 396 return if ($precedence == $prev_prec && 397 $context->{operators}{$prev->{name}}{associativity} eq 'right'); 398 if ($self->top(-2)->{type} eq 'fn') { 399 my $top = $self->pop; my $op = $self->pop; my $fun = $self->pop; 400 if (Parser::Function::checkInverse($self,$fun,$op,$top)) { 401 $fun->{name} = $context->{functions}{$fun->{name}}{inverse}; 402 $self->push($fun); 403 } else {$self->push($top,$op,$fun)} 404 } else { 405 my $rop = $self->pop; my $op = $self->pop; my $lop = $self->pop; 406 if ($op->{reverse}) {my $tmp = $rop; $rop = $lop; $lop = $tmp} 407 $self->pushOperand(Parser::BOP->new($self,$op->{name}, 408 $lop->{value},$rop->{value},$op->{ref}),$op->{reverse}); 409 } 410 } else { 411 my $rop = $self->pop; my $op = $self->pop; 412 $self->pushOperand(Parser::UOP->new 413 ($self,$op->{name},$rop->{value},$op->{ref}),$op->{reverse}); 414 } 415 last; 416 }; 417 418 /fn/ and do { 419 return if ($precedence > $context->{operators}{fn}{precedence}); 420 $self->CloseFn(); 421 last; 422 }; 423 424 return; 425 426 } 427 return if ($self->{error}); 428 } 429 } 430 431 ################################################## 432 # 433 # Apply a function to its parameters 434 # 435 # If the operand is a list and the parens are those for function calls 436 # Use the list items as the parameters, otherwise use the top item 437 # Pop the function object, and push the result of the function call 438 # 439 sub CloseFn { 440 my $self = shift; my $context = $self->{context}; 441 my $top = $self->pop->{value}; my $fn = $self->pop; 442 my $constant = $top->{isConstant}; 443 if ($context->{parens}{$top->{open}}{function} && 444 $context->{parens}{$top->{open}}{close} eq $top->{close} && 445 !$context->{functions}{$fn->{name}}{vectorInput}) 446 {$top = $top->coords} else {$top = [$top]} 447 $self->pushOperand(Parser::Function->new 448 ($self,$fn->{name},$top,$constant,$fn->{ref})); 449 } 450 451 ################################################## 452 # 453 # Handle a numeric token 454 # 455 # Add an implicit multiplication, if needed 456 # Create the number object and check it 457 # Save the number as an operand 458 # 459 sub Num { 460 my $self = shift; 461 $self->ImplicitMult() if $self->state eq 'operand'; 462 my $num = Parser::Number->new($self,shift,$self->{ref}); 463 my $check = $self->{context}->flag('NumberCheck'); 464 &$check($num) if $check; 465 $self->pushOperand($num); 466 } 467 468 ################################################## 469 # 470 # Handle a constant token 471 # 472 # Add an implicit multiplication, if needed 473 # Save the number as an operand 474 # 475 sub Const { 476 my $self = shift; my $ref = $self->{ref}; 477 my $name = shift; my $const = $self->{context}{constants}{$name}; 478 $self->ImplicitMult() if $self->state eq 'operand'; 479 if (defined($self->{context}{variables}{$name})) { 480 $self->pushOperand(Parser::Variable->new($self,$name,$ref)); 481 } elsif ($const->{keepName}) { 482 $self->pushOperand(Parser::Constant->new($self,$name,$ref)); 483 } else { 484 $self->pushOperand(Parser::Value->new($self,[$const->{value}],$ref)); 485 } 486 } 487 488 ################################################## 489 # 490 # Handle a variable token 491 # 492 # Add an implicit multiplication, if needed 493 # Save the variable as an operand 494 # 495 sub Var { 496 my $self = shift; 497 $self->ImplicitMult() if $self->state eq 'operand'; 498 $self->pushOperand(Parser::Variable->new($self,shift,$self->{ref})); 499 } 500 501 ################################################## 502 # 503 # Handle a function token 504 # 505 # Add an implicit multiplication, if needed 506 # Save the function object on the stack 507 # 508 sub Fn { 509 my $self = shift; 510 $self->ImplicitMult() if $self->state eq 'operand'; 511 $self->push({type => 'fn', name => shift, ref => $self->{ref}}); 512 } 513 514 ################################################## 515 # 516 # Handle a string constant 517 # 518 # Add an implicit multiplication, if needed (will report an error) 519 # Save the string object on the stack 520 # 521 sub Str { 522 my $self = shift; 523 $self->ImplicitMult() if $self->state eq 'operand'; 524 $self->pushOperand(Parser::String->new($self,shift,$self->{ref})); 525 } 526 527 ################################################## 528 ################################################## 529 # 530 # Evaluate the equation using the given values 531 # 532 sub eval { 533 my $self = shift; 534 $self->setValues(@_); 535 foreach my $x (keys %{$self->{values}}) { 536 $self->Error("The value of '$x' can't be a formula") 537 if Value::isFormula($self->{values}{$x}); 538 } 539 $self->{tree}->eval; 540 } 541 542 ################################################## 543 # 544 # Removes redundent items (like x+-y, 0+x and 1*x, etc) 545 # (substituting the given values). 546 # 547 sub reduce { 548 my $self = shift; 549 $self = $self->copy($self); 550 $self->setValues(@_); 551 $self->{tree} = $self->{tree}->reduce; 552 $self->{variables} = $self->{tree}->getVariables; 553 return $self; 554 } 555 556 ################################################## 557 # 558 # Substitute values for one or more variables 559 # 560 sub substitute { 561 my $self = shift; 562 $self = $self->copy($self); 563 $self->setValues(@_); 564 foreach my $x (keys %{$self->{values}}) {delete $self->{variables}{$x}} 565 $self->{tree} = $self->{tree}->substitute; 566 return $self; 567 } 568 569 ################################################## 570 # 571 # Produces a printable string (substituting the given values). 572 # 573 sub string { 574 my $self = shift; 575 $self->setValues(@_); 576 $self->{tree}->string; 577 } 578 579 ################################################## 580 # 581 # Produces a TeX string (substituting the given values). 582 # 583 sub TeX { 584 my $self = shift; 585 $self->setValues(@_); 586 $self->{tree}->TeX; 587 } 588 589 ################################################## 590 # 591 # Produces a perl eval string (substituting the given values). 592 # 593 sub perl { 594 my $self = shift; 595 $self->setValues(@_); 596 $self->{tree}->perl; 597 } 598 599 ################################################## 600 # 601 # Produce a perl function 602 # 603 # (Parameters specify an optional name and an array reference of 604 # optional variables. If the name is not included, an anonymous 605 # code reference is returned. If the variables are not included, 606 # then the variables from the formula are used in sorted order.) 607 # 608 sub perlFunction { 609 my $self = shift; my $name = shift; my $vars = shift; 610 $vars = [sort(keys %{$self->{variables}})] unless $vars; 611 my $n = scalar(@{$vars}); my $vnames = ''; 612 if ($n > 0) { 613 my @v = (); foreach my $x (@{$vars}) {push(@v,'$'.$x)} 614 $vnames = "my (".join(',',@v).") = \@_;"; 615 } 616 my $fn = eval 617 "package main; 618 sub $name { 619 die \"Wrong number of arguments".($name?" to '$name'":'')."\" if scalar(\@_) != $n; 620 $vnames 621 return ".$self->perl."; 622 }"; 623 $self->Error($@) if $@; 624 return $fn; 625 } 626 627 628 ################################################## 629 # 630 # Sets the values of variables for evaluation purposes 631 # 632 sub setValues { 633 my $self = shift; my ($value,$type); 634 my $variables = $self->{context}{variables}; 635 $self->{values} = {@_}; 636 foreach my $x (keys %{$self->{values}}) { 637 $self->Error("Undeclared variable '$x'") unless defined $variables->{$x}; 638 $value = Value::makeValue($self->{values}{$x}); 639 $value = Value::Formula->new($value) unless Value::isValue($value); 640 ($value,$type) = Value::getValueType($self,$value); 641 $self->Error("Variable '$x' should be of type $variables->{$x}{type}{name}") 642 unless Parser::Item::typeMatch($type,$variables->{$x}{type}); 643 $self->{values}{$x} = $value; 644 } 645 } 646 647 648 ################################################## 649 ################################################## 650 # 651 # Convert a student answer to a formula, with error trapping. 652 # If the result is undef, there was an error (message is in Context()->{error} object) 653 # 654 655 sub Formula { 656 my $f = shift; 657 eval {Value::Formula->new($f)}; 658 } 659 660 # 661 # Evaluate a formula, with error trapping. 662 # If the result is undef, there was an error (message is in Context()->{error} object) 663 # If the result was a real, make it a fuzzy one. 664 # 665 sub Evaluate { 666 my $f = shift; 667 return unless defined($f); 668 my $v = eval {$f->eval(@_)}; 669 $v = Value::makeValue($v) if defined($v); 670 return $v; 671 } 672 673 674 ################################################## 675 ################################################## 676 # 677 # Produce a vector in ijk form 678 # 679 sub ijk { 680 my $self = shift; 681 $self->{tree}->ijk; 682 } 683 684 ######################################################################### 685 ######################################################################### 686 # 687 # Load the sub-classes and Value.pm 688 # 689 690 use Parser::Item; 691 use Value; 692 use Value::Formula; 693 use Parser::Context; 694 use Parser::Context::Default; 695 696 # use Parser::Differentiation; 697 698 ########################################################################### 699 700 use vars qw($installed); 701 $Parser::installed = 1; 702 703 ########################################################################### 704 ########################################################################### 705 # 706 # To Do: 707 # 708 # handle sqrt(-1) and log of negatives (make complexes) 709 # do division by zero and log of zero checks in compound functions 710 # add context flags for various reduction checks 711 # make context flag for reduction of constants 712 # make reduce have reduce patterns as parameters 713 # more reduce patterns 714 # make operator strings customizable (reduce, and other places they are used) 715 # add parens alternately as () and []? 716 # 717 ######################################################################### 718 719 1; 720
| aubreyja at gmail dot com | ViewVC Help |
| Powered by ViewVC 1.0.9 |