[system] / trunk / pg / lib / Parser / Legacy / NumberWithUnits.pm Repository:
ViewVC logotype

View of /trunk/pg/lib/Parser/Legacy/NumberWithUnits.pm

Parent Directory Parent Directory | Revision Log Revision Log


Revision 5014 - (download) (as text) (annotate)
Fri Jun 22 00:26:47 2007 UTC (12 years, 8 months ago) by dpvc
File size: 7003 byte(s)
Fixed context issues and a typo (must have been interrupted when
editing this file earlier).  Also modified the units pattern to allow
trailing spaces.

    1 ######################################################################
    2 #
    3 #  This is a Parser class that implements a number or formula
    4 #  with units.  It is a temporary version until the Parser can
    5 #  handle it directly.
    6 #
    7 
    8 package Parser::Legacy::ObjectWithUnits;
    9 
   10 sub name {'object'};
   11 sub cmp_class {'an Object with Units'};
   12 sub makeValue {
   13   my $self = shift; my $value = shift;
   14   my %options = (context=>$self->context,@_);
   15   Value::makeValue($value,%options);
   16 }
   17 
   18 sub new {
   19   my $self = shift; my $class = ref($self) || $self;
   20   my $context = (Value::isContext($_[0]) ? shift : $self->context);
   21   my $num = shift; my $units = shift;
   22   Value::Error("You must provide a ".$self->name) unless defined($num);
   23   ($num,$units) = splitUnits($num) unless $units;
   24   Value::Error("You must provide units for your ".$self->name) unless $units;
   25   Value::Error("Your units can only contain one division") if $units =~ m!/.*/!;
   26   $num = $self->makeValue($num,context=>$context);
   27   my %Units = getUnits($units);
   28   Value::Error($Units{ERROR}) if ($Units{ERROR});
   29   $num->{units} = $units;
   30   $num->{units_ref} = \%Units;
   31   $num->{isValue} = 1;
   32   bless $num, $class;
   33 }
   34 
   35 ##################################################
   36 
   37 #
   38 #  Find the units for the formula and split that off
   39 #
   40 my $aUnit = '(?:'.getUnitNames().')(?:\s*(?:\^|\*\*)\s*[-+]?\d+)?';
   41 my $unitPattern = $aUnit.'(?:\s*[/* ]\s*'.$aUnit.')*';
   42 my $unitSpace = "($aUnit) +($aUnit)";
   43 sub splitUnits {
   44   my $string = shift;
   45   my ($num,$units) = $string =~ m!^(.*?(?:[)}\]0-9a-z]|\d\.))\s*($unitPattern)\s*$!o;
   46   if ($units) {
   47     while ($units =~ s/$unitSpace/$1*$2/) {};
   48     $units =~ s/ //g;
   49     $units =~ s/\*\*/^/g;
   50   }
   51   return ($num,$units);
   52 }
   53 
   54 #
   55 #  Sort names so that longest ones are first, and then alphabetically
   56 #  (so we match longest names before shorter ones).
   57 #
   58 sub getUnitNames {
   59   local ($a,$b);
   60   join('|',sort {
   61     return length($b) <=> length($a) if length($a) != length($b);
   62     return $a cmp $b;
   63   } keys(%Units::known_units));
   64 }
   65 
   66 #
   67 #  Get the units hash and fix up the errors
   68 #
   69 sub getUnits {
   70   my $units = shift;
   71   my %Units = Units::evaluate_units($units);
   72   if ($Units{ERROR}) {
   73     $Units{ERROR} =~ s/ at ([^ ]+) line \d+(\n|.)*//;
   74     $Units{ERROR} =~ s/^UNIT ERROR:? *//;
   75   }
   76   return %Units;
   77 }
   78 
   79 #
   80 #  Convert units to TeX format
   81 #  (fix superscripts, put terms in \rm,
   82 #   and make a \frac out of fractions)
   83 #
   84 sub TeXunits {
   85   my $units = shift;
   86   $units =~ s/\^\(?([-+]?\d+)\)?/^{$1}/g;
   87   $units =~ s/\*/\\,/g;
   88   return '{\rm '.$units.'}' unless $units =~ m!^(.*)/(.*)$!;
   89   my $displayMode = WeBWorK::PG::Translator::PG_restricted_eval(q!$main::displayMode!);
   90   return '\frac{'.$1.'}{'.$2.'}' if ($displayMode eq 'HTML_tth');
   91   return '\frac{\rm\mathstrut '.$1.'}{\rm\mathstrut '.$2.'}';
   92 }
   93 
   94 ##################################################
   95 
   96 #
   97 #  Replace the cmp_parse with one that removes the units
   98 #  from the student answer and checks them.  The answer
   99 #  value is adjusted by the factors, and then checked.
  100 #  Finally, the units themselves are checked.
  101 #
  102 sub cmp_parse {
  103   my $self = shift; my $ans = shift;
  104   #
  105   #  Check that the units are defined and legal
  106   #
  107   my ($num,$units) = splitUnits($ans->{student_ans});
  108   unless (defined($num) && defined($units) && $units ne '') {
  109     $self->cmp_Error($ans,"Your answer doesn't look like ".lc($self->cmp_class));
  110     return $ans;
  111   }
  112   if ($units =~ m!/.*/!) {
  113     $self->cmp_Error($ans,"Your units can only contain one division");
  114     return $ans;
  115   }
  116   my %Units = getUnits($units);
  117   if ($Units{ERROR}) {$self->cmp_Error($ans,$Units{ERROR}); return $ans}
  118   #
  119   #  Check the numeric part of the answer
  120   #   and adjust the answer strings
  121   #
  122   $self->adjustCorrectValue($ans,$self->{units_ref}{factor}/$Units{factor});
  123   $ans->{student_ans} = $num;
  124   $ans = $self->cmp_reparse($ans);
  125   $ans->{student_ans} .= " " . $units;
  126   $ans->{preview_text_string}  .= " ".$units;
  127   $ans->{preview_latex_string} .= '\ '.TeXunits($units);
  128   #
  129   return $ans unless $ans->{ans_message} eq '';
  130   #
  131   #  Check that we have an actual number, and check the units
  132   #
  133   if (!defined($ans->{student_value}) || $self->checkStudentValue($ans->{student_value})) {
  134     $ans->{student_value} = undef; $ans->score(0);
  135     $self->cmp_Error($ans,"Your answer doesn't look like a number with units");
  136   } else {
  137     $ans->{student_value} = $self->new($num,$units);
  138     foreach my $funit (keys %{$self->{units_ref}}) {
  139       next if $funit eq 'factor';
  140       next if $self->{units_ref}{$funit} == $Units{$funit};
  141       $self->cmp_Error($ans,"The units for your answer are not correct")
  142         unless $ans->{isPreview};
  143       $ans->score(0); last;
  144     }
  145   }
  146   return $ans;
  147 }
  148 
  149 #
  150 #  Fix the correct answer so that the value matches the student's units
  151 #
  152 sub adjustCorrectValue {
  153   my $self = shift; my $ans = shift;
  154   my $factor = shift;
  155   $ans->{correct_value} *= $factor;
  156 }
  157 
  158 sub cmp_reparse {Value::cmp_parse(@_)}
  159 
  160 sub _compare {Value::binOp(@_,'compare')}
  161 
  162 ######################################################################
  163 
  164 #
  165 #  Customize for NumberWithUnits
  166 #
  167 
  168 package Parser::Legacy::NumberWithUnits;
  169 our @ISA = qw(Parser::Legacy::ObjectWithUnits Value::Real);
  170 
  171 sub name {'number'};
  172 sub cmp_class {'a Number with Units'};
  173 
  174 sub makeValue {
  175   my $self = shift; my $value = shift;
  176   my %options = (context => $self->context,@_);
  177   my $num = Value::makeValue(shift,%options);
  178   Value::Error("A number with units must be a constant, not %s",lc(Value::showClass($num)))
  179     unless Value::isReal($num);
  180   return $num;
  181 }
  182 
  183 sub checkStudentValue {
  184   my $self = shift; my $student = shift;
  185   return $student->class ne 'Real';
  186 }
  187 
  188 sub string {
  189   my $self = shift;
  190   Value::Real::string($self,@_) . ' ' . $self->{units};
  191 }
  192 
  193 sub TeX {
  194   my $self = shift;
  195   my $n = Value::Real::string($self,@_);
  196   $n =~ s/E\+?(-?)0*([^)]*)/\\times 10^{$1$2}/i; # convert E notation to x10^(...)
  197   return $n . '\ ' . Parser::Legacy::ObjectWithUnits::TeXunits($self->{units});
  198 }
  199 
  200 
  201 ######################################################################
  202 
  203 #
  204 #  Customize for FormulaWithUnits
  205 #
  206 
  207 package Parser::Legacy::FormulaWithUnits;
  208 our @ISA = qw(Parser::Legacy::ObjectWithUnits Value::Formula);
  209 
  210 sub name {'formula'};
  211 sub cmp_class {'a Formula with Units'};
  212 
  213 sub makeValue {
  214   my $self = shift; my $value = shift;
  215   my %options = (context => $self->context,@_);
  216   $self->Package("Formula")->new($options{context},$value);
  217 }
  218 
  219 sub checkStudentValue {
  220   my $self = shift; my $student = shift;
  221   return $student->type ne 'Number';
  222 }
  223 
  224 sub adjustCorrectValue {
  225   my $self = shift; my $ans = shift;
  226   my $factor = shift;
  227   my $f = $ans->{correct_value};
  228   $f->{tree} = $f->Item("BOP")->new($f,'*',$f->{tree},$f->Item("Value")->new($f,$factor));
  229 }
  230 
  231 sub string {
  232   my $self = shift;
  233   Parser::string($self,@_) . ' ' . $self->{units};
  234 }
  235 
  236 sub TeX {
  237   my $self = shift;
  238   Parser::TeX($self,@_) . '\ ' . Parser::Legacy::ObjectWithUnits::TeXunits($self->{units});
  239 }
  240 
  241 ######################################################################
  242 
  243 1;

aubreyja at gmail dot com
ViewVC Help
Powered by ViewVC 1.0.9