[system] / trunk / pg / macros / parserAssignment.pl Repository:
ViewVC logotype

View of /trunk/pg/macros/parserAssignment.pl

Parent Directory Parent Directory | Revision Log Revision Log


Revision 6058 - (download) (as text) (annotate)
Thu Jun 25 23:28:44 2009 UTC (10 years, 7 months ago) by gage
File size: 12002 byte(s)
syncing pg HEAD with pg2.4.7 on 6/25/2009

    1 ################################################################################
    2 # WeBWorK Online Homework Delivery System
    3 # Copyright  2000-2007 The WeBWorK Project, http://openwebwork.sf.net/
    4 # $CVSHeader$
    5 #
    6 # This program is free software; you can redistribute it and/or modify it under
    7 # the terms of either: (a) the GNU General Public License as published by the
    8 # Free Software Foundation; either version 2, or (at your option) any later
    9 # version, or (b) the "Artistic License" which comes with this package.
   10 #
   11 # This program is distributed in the hope that it will be useful, but WITHOUT
   12 # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
   13 # FOR A PARTICULAR PURPOSE.  See either the GNU General Public License or the
   14 # Artistic License for more details.
   15 ################################################################################
   16 
   17 =head1 NAME
   18 
   19 parserAssignment.pl - Implements assignments to variables
   20 
   21 =head1 DESCRIPTION
   22 
   23 This file implements an assignment operator that allows only a single
   24 variable reference on the left and any value on the right.  You can use
   25 this to require students to enter things like
   26 
   27   y = 3x + 1
   28 
   29 rather than making the "y = " part of the text of the question.  This
   30 also allows you to ask for lists of assignments more easily.
   31 
   32 To use it, load the macro file, select the Context you want to use,
   33 add any variables you may need, and enable the assignment operator as
   34 in the following example:
   35 
   36   loadMacros(
   37     "PGstandard.pl",
   38     "MathObjects.pl",
   39     "parserAssignment.pl",
   40   );
   41 
   42   Context("Numeric")->variables->add(y=>'Real');
   43   parser::Assignment->Allow;
   44 
   45 Now you can use the equal sign in Formula() objects to create assignments.
   46 
   47   $f = Formula("y = 3x + 1");
   48   ...
   49   ANS($f->cmp);
   50 
   51 The student will have to make an assignment to the same variable in
   52 order to get credit.  For example, he or she could enter y = 1+3x to get
   53 credit for the answer above.
   54 
   55 The left-hand side of an assignment must be a single variable, so
   56 
   57   $f = Formula("3y = 2x");
   58 
   59 will produce an error.  The right-hand side can not include the
   60 variable being assigned on the left, so
   61 
   62   $f = Formula("x = 2x+1");
   63 
   64 also is not allowed.
   65 
   66 You can produce lists of assignments just as easily:
   67 
   68   $f = Formula("y = 3x, y = 2x-1");
   69 
   70 and the assignment can be of any type of MathObject.  For example:
   71 
   72   Context("Vector")->variables->add(p=>'Vector3D');
   73   parser::Assignment->Allow;
   74 
   75   $f = Formula("p = <1,2x,1-x>");
   76 
   77 To produce a constant assignment, use Compute(), as in:
   78 
   79   $p = Compute("p = <1,2,3>");
   80 
   81 (in fact, Compute() could be used for in place of Formula() in the
   82 examples above as well, since it returns a Formula when the value is
   83 one).
   84 
   85 The left-hand side of an assignment can also be a function
   86 declaration, as in
   87 
   88   f(x) = 3x + 1
   89 
   90 To allow this, use
   91 
   92   parser::Assignment->Function("f");
   93 
   94 You can supply more than one function name if you want.  E.g.,
   95 
   96   parser::Assignment->Function("f","g");
   97 
   98 The number of variables for these functions is determined by the
   99 assignment itself, so after declaring f to be a function, you can use
  100 either
  101 
  102   f(x) = x+1
  103 or
  104   f(x,y) = x^2 + y^2
  105 
  106 provided the variables are defined in the current context.
  107 
  108 Type-checking between the student and correct answers is performed
  109 using the right-hand values of the assignment, and a warning message
  110 will be issued if the types are not compatible.  The type of the
  111 variable on the left-hand side, however, is not checked.
  112 
  113 For function declarations, the name of the function and the order
  114 of the variables must match the professor's answer; however, the
  115 names of the variables don't have to match, as long as the function
  116 returns the same results for the same inputs.  So
  117 
  118   f(x) = x + 1
  119 and
  120   f(y) = y + 1
  121 
  122 will be marked as equal.
  123 
  124 =cut
  125 
  126 #
  127 #  FIXME:  allow any variables in declaration
  128 #  FIXME:  Add more hints when variable name isn't right
  129 #          or function name or number of arguments isn't right.
  130 #
  131 
  132 sub _parserAssignment_init {parser::Assignment::Init()}
  133 
  134 ######################################################################
  135 
  136 package parser::Assignment;
  137 our @ISA = qw(Parser::BOP);
  138 
  139 sub Init {
  140   main::PG_restricted_eval('sub Assignment {parser::Assignment::List->new(@_)}');
  141 }
  142 
  143 #
  144 #  Check that the left operand is a variable and not used on the right
  145 #
  146 sub _check {
  147   my $self = shift; my $name = $self->{def}{string} || $self->{bop};
  148   $self->Error("Only one assignment is allowed in an equation")
  149     if $self->{lop}->type eq 'Assignment' || $self->{rop}->type eq 'Assignment';
  150   $self->Error("The left side of an assignment must be a variable or function",$name)
  151     unless $self->{lop}->class eq 'Variable' || $self->{lop}{isDummy} || $self->context->flag("allowBadOperands");
  152   if ($self->{lop}{isDummy}) {
  153     my $fvars = $self->{lop}->getVariables;
  154     foreach my $x (keys(%{$self->{rop}->getVariables})) {
  155       $self->Error("The formula for %s can't use the variable '%s'",$self->{lop}->string,$x)
  156   unless $fvars->{$x};
  157     }
  158   } else {
  159     $self->Error("The right side of an assignment must not include the variable being defined")
  160       if $self->{rop}->getVariables->{$self->{lop}{name}};
  161     delete $self->{equation}{variables}{$self->{lop}{name}};
  162   }
  163   $self->{type} = Value::Type('Assignment',2,$self->{rop}->typeRef,list => 1);
  164 }
  165 
  166 #
  167 #  Convert to an Assignment object
  168 #
  169 sub eval {
  170   my $self = shift; my $context = $self->context;
  171   my ($a,$b) = ($self->Package("String")->make($context,$self->{lop}->string),$self->{rop});
  172   $b = Value::makeValue($b->eval,context => $context);
  173   return parser::Assignment::List->make($context,$a,$b);
  174 }
  175 
  176 #
  177 #  Don't count the left-hand variable
  178 #
  179 sub getVariables {
  180   my $self = shift;
  181   return $self->{lop}->getVariables if $self->{lop}{isDummy};
  182   $self->{rop}->getVariables;
  183 }
  184 
  185 #
  186 #  Create an Assignment object
  187 #
  188 sub perl {
  189   my $self = shift;
  190   return "parser::Assignment::List->new('".$self->{lop}->string."',".$self->{rop}->perl.")";
  191 }
  192 
  193 #
  194 #  Add/Remove the Assignment operator to/from a context
  195 #
  196 sub Allow {
  197   my $self = shift || "Value"; my $context = shift || $self->context;
  198   my $allow = shift; $allow = 1 unless defined($allow);
  199   if ($allow) {
  200     my $prec = $context->{operators}{','}{precedence};
  201     $prec = 1 unless defined($prec);
  202     $context->operators->add(
  203       '=' => {
  204          class => 'parser::Assignment',
  205          precedence => $prec+.25,  #  just above comma
  206          associativity => 'left',  #  computed left to right
  207          type => 'bin',            #  binary operator
  208          string => ' = ',          #  output string for it
  209       }
  210     );
  211     $context->{value}{Formula} = 'parser::Assignment::Formula';
  212     $context->{value}{Assignment} = 'parser::Assignment::List';
  213   } else {$context->operators->remove('=')}
  214   return;
  215 }
  216 
  217 sub Function {
  218   my $self = shift || "Value";
  219   my $context = (Value::isContext($_[0]) ? shift : $self->context);
  220   Value->Error("You must provide a function name") unless scalar(@_) > 0;
  221   foreach my $f (@_) {
  222     Value->Error("Function name '%s' is illegal",$f) unless $f =~ m/^[a-z][a-z0-9]*$/i;
  223     my $name = $f; $name = $1.'_{'.$2.'}' if ($name =~ m/^(\D+)(\d+)$/);
  224     $context->functions->add(
  225       $f => {class => 'parser::Assignment::Function', TeX => $name, type => $Value::Type{number}}
  226     );
  227   }
  228 }
  229 
  230 ######################################################################
  231 
  232 #
  233 #  A special List object that holds a variable and a value, and
  234 #  that prints with an equal sign.
  235 #
  236 
  237 package parser::Assignment::List;
  238 our @ISA = ("Value::List");
  239 
  240 sub new {
  241   my $self = shift; my $class = ref($self) || $self;
  242   my $context = (Value::isContext($_[0]) ? shift : $self->context);
  243   Value->Error("Too many arguments") if scalar(@_) > 2;
  244   my ($x,$v) = @_;
  245   if (defined($v)) {
  246     my $context = $self->context;
  247     $v = Value::makeValue($v,context=>$context);
  248     if ($v->isFormula) {
  249       $x = $self->Package("Formula")->new($context,$x);
  250       $v->{tree} = parser::Assignment->new($v,"=",$x->{tree},$v->{tree});
  251       bless $v, $self->Package("Formula");
  252       return $v;
  253     }
  254     return $self->make($self->Package("String")->make($context,$x),$v);
  255   } else {
  256     $v = $self->Package("Formula")->new($x);
  257     Value->Error("Your formula doesn't seem to be an assignment")
  258   unless $v->{tree}->type eq "Assignment";
  259     return $v;
  260   }
  261 }
  262 
  263 sub string {
  264   my $self = shift; my ($x,$v) = $self->value;
  265   $x->string . ' = ' . $v->string;
  266 }
  267 
  268 sub TeX {
  269   my $self = shift; my ($x,$v) = $self->value;
  270   $x = $self->Package("Formula")->new($x->{data}[0]);
  271   $x->TeX . ' = ' . $v->string;
  272 }
  273 
  274 #
  275 #  Needed since these are called explicitly without an object
  276 #
  277 sub cmp_defaults {
  278   my $self = shift;
  279   $self->SUPER::cmp_defaults(@_);
  280 }
  281 
  282 #
  283 #  Class is an a variable assigned to whatever
  284 #
  285 sub cmp_class {
  286   my $self = shift;
  287   my $type = ($self->{data}[0] =~ m/\(/ ? 'Function' : 'Variable');
  288   "a $type equal to ".$self->{data}[1]->showClass;
  289 }
  290 sub showClass {cmp_class(@_)}
  291 
  292 #
  293 #  Return the proper type
  294 #
  295 sub typeRef {
  296   my $self = shift;
  297   Value::Type('Assignment',2,$self->{data}[1]->typeRef,list=>1);
  298 }
  299 
  300 ######################################################################
  301 
  302 #
  303 #  A subclass of Formula that does typematching properly for Assignments
  304 #  (the match is against the right-hand sides)
  305 #
  306 
  307 package parser::Assignment::Formula;
  308 our @ISA = ("Value::Formula");
  309 
  310 sub new {
  311   my $self = shift; $class = ref($self) || $self;
  312   my $f = $self->SUPER::new(@_);
  313   bless $f, $class if $f->type eq 'Assignment';
  314   return $f;
  315 }
  316 
  317 sub typeMatch {
  318   my $self = shift; my $other = shift; my $ans = shift;
  319   return 0 unless $self->type eq $other->type;
  320   $other = $other->Package("Formula")->new($self->context,$other) unless $other->isFormula;
  321   my $typeMatch = ($self->createRandomPoints(1))[1]->[0]{data}[1];
  322   $main::__other__ = sub {($other->createRandomPoints(1))[1]->[0]{data}[1]};
  323   $other = main::PG_restricted_eval('&$__other__()');
  324   delete $main::{__other__};
  325   return 1 unless defined($other); # can't really tell, so don't report type mismatch
  326   $typeMatch->typeMatch($other,$ans);
  327 }
  328 
  329 sub cmp_class {
  330   my $self = shift; my $value;
  331   if ($self->{tree}{rop}{isConstant}) {
  332     $value = ($self->createRandomPoints(1))[1]->[0]{data}[1];
  333   } else {
  334     $value = $self->Package("Formula")->new($self->context,$self->{tree}{rop});
  335   }
  336   my $type = ($self->{tree}{lop}{isDummy} ? "Function" : "Variable");
  337   return "a $type equal to ".$value->showClass;
  338 }
  339 sub showClass {cmp_class(@_)}
  340 
  341 #
  342 #  Convert varaible names to those used in the correct answer, if the
  343 #  student answer uses different ones
  344 #
  345 sub compare {
  346   my ($l,$r) = @_; my $self = $l;
  347   my $context = $self->context;
  348   $r = $context->Package("Formula")->new($context,$r) unless Value::isFormula($r);
  349   if ($l->{tree}{lop}{isDummy} && $r->type eq 'Assignment' && $r->{tree}{lop}{isDummy}) {
  350     my ($F,$f) = ($l->{tree}{lop}{params},$r->{tree}{lop}{params});
  351     if (scalar(@{$F}) == scalar(@{$f})) {
  352       my @subs = ();
  353       for (my $i = 0; $i < scalar(@{$F}); $i++) {
  354   push(@subs,$f->[$i]{name} => $F->[$i]{name})
  355     unless $F->[$i]{name} eq $f->[$i]{name};
  356       }
  357       $r = $r->substitute(@subs) if scalar(@subs);
  358       delete $r->{f};
  359     }
  360   }
  361   $l->SUPER::compare($r,@_);
  362 }
  363 
  364 ######################################################################
  365 
  366 #
  367 #  A dummy function that is used for assignments like f(x) = x^2
  368 #
  369 
  370 package parser::Assignment::Function;
  371 our @ISA = ("Parser::Function");
  372 
  373 sub _check {
  374   my $self = shift; my %var;
  375   foreach my $x (@{$self->{params}}) {
  376     $self->Error("The arguments of '%s' must be variables",$self->{name})
  377       unless $x->class eq 'Variable';
  378     $self->Error("The arguments of '%s' must all be different",$self->{name})
  379       if $var{$x->{name}};
  380     $var{$x->{name}} = 1;
  381   }
  382   $self->{type} = $self->{def}{type};
  383   $self->{isDummy} = 1;
  384 }
  385 
  386 sub eval {
  387   my $self = shift;
  388   $self->Error("Dummy function '%s' can not be evaluated",$self->{name});
  389 }
  390 
  391 sub call {
  392   my $self = shift;
  393   $self->Error("Dummy function '%s' can not be called",$self->{name});
  394 }
  395 
  396 1;

aubreyja at gmail dot com
ViewVC Help
Powered by ViewVC 1.0.9