From 9b981411b8dc7c0ff4878fa1a98dad6e3d69c653 Mon Sep 17 00:00:00 2001 From: John Denker Date: Wed, 10 Nov 2021 17:19:23 -0700 Subject: multi-character variable names, with and without auto-Greek, with and without subscripts --- lang/en/qtype_algebra.php | 1 + parser.php | 114 ++++++++++++++-------------------------------- 2 files changed, 34 insertions(+), 81 deletions(-) diff --git a/lang/en/qtype_algebra.php b/lang/en/qtype_algebra.php index b29682e..003d0ee 100644 --- a/lang/en/qtype_algebra.php +++ b/lang/en/qtype_algebra.php @@ -25,6 +25,7 @@ // Parser lang strings. $string['deprecatedlog'] = 'log() is deprecated; use ln() or log10() instead'; $string['trivial_1'] = '{$a}'; +$string['debug_1'] = 'Debug:
{$a}'; $string['badclosebracket'] = 'Invalid close bracket found'; $string['badequivtype'] = 'Invalid type: can only compare parser terms with other parser terms'; $string['badfuncargs'] = 'Invalid arguments for the function \'{$a}\''; diff --git a/parser.php b/parser.php index fd58f4c..ad141f1 100644 --- a/parser.php +++ b/parser.php @@ -627,7 +627,7 @@ class qtype_algebra_parser_variable extends qtype_algebra_parser_term { ); /** - * Constructor for an algebraic term cass representing a variable. + * Constructor for an algebraic term class representing a variable. * * Initializes an instance of the variable term subclass. The method is given the text * in the expression corresponding to the variable name. This is then parsed to get the @@ -643,37 +643,15 @@ class qtype_algebra_parser_variable extends qtype_algebra_parser_term { $m = array(); // Set the sign of the variable to be empty. $this->_sign = ''; - // Try to match the text to a greek letter. - if (preg_match('/('.implode('|', self::$greek).')/A', $text, $m)) { - // Take the base name of the variable to be the greek letter. - $this->_base = $m[1]; - // Extract the remaining characters for use as the subscript. - $this->_subscript = substr($text, strlen($m[1])); - // If the first letter of the subscript is an underscore then remove it. - if (strlen($this->_subscript) != 0 && $this->_subscript[0] == '_') { - $this->_subscript = substr($this->_subscript, 1); - } - // Call the base class constructor with the variable text set to the combination of the - // base name and the subscript without an underscore between them. - parent::__construct(self::NARGS, self::$formats['greek'], - $this->_base.$this->_subscript); + if (preg_match('/([^_]+)_(.*)/A', $text, $m)) { + $this->_base = $m[1]; + $this->_subscript = $m[2]; } else { - // Otherwise we have a simple multi-letter variable name. Treat the fist letter as the base - // name and the rest as the subscript. - - // Get the variable's base name. - $this->_base = substr($text, 0, 1); - // Now set the subscript to the remaining letters. - $this->_subscript = substr($text, 1); - // If the first letter of the subscript is an underscore then remove it. - if (strlen($this->_subscript) != 0 && $this->_subscript[0] == '_') { - $this->_subscript = substr($this->_subscript, 1); - } - // Call the base class constructor with the variable text set to the combination of the - // base name and the subscript without an underscore between them. - parent::__construct(self::NARGS, self::$formats['std'], - $this->_base.$this->_subscript); + $this->_base = $text; + $this->_subscript = ''; } + $gmode = in_array($this->_base, self::$greek, 1) ? 'greek' : 'std'; + parent::__construct(self::NARGS, self::$formats[$gmode], $text); } /** @@ -698,7 +676,8 @@ class qtype_algebra_parser_variable extends qtype_algebra_parser_term { * @return array of the arguments that, with a format string, can be passed to sprintf */ public function print_args($method) { - return array($this->_sign, $this->_base, $this->_subscript); + $under = $this->_subscript == '' ? '' : '_'; + return array($this->_sign, $this->_base, $under, $this->_subscript); } /** @@ -745,10 +724,10 @@ class qtype_algebra_parser_variable extends qtype_algebra_parser_term { // Static class properties. const NARGS = 0; private static $formats = array( - 'greek' => array('str' => '%s%s%s', - 'tex' => '%s\%s_{%s}'), - 'std' => array('str' => '%s%s%s', - 'tex' => '%s%s_{%s}') + 'greek' => array('str' => '%s%s%s%s', + 'tex' => '%s\%s%s{%s}'), + 'std' => array('str' => '%s%s%s%s', + 'tex' => '%s%s%s{%s}') ); } @@ -1479,14 +1458,12 @@ class qtype_algebra_parser { public function __construct() { $this->_tokens = array ( array ('/(\^|\*\*)/A', 'qtype_algebra_parser_power' ) - , array ('/('.implode('|', self::$functions).')/A', 'qtype_algebra_parser_function' ) - , array ('/\//A', 'qtype_algebra_parser_divide' ) + , array ('/\//A', 'qtype_algebra_parser_divide' ) , array ('/\*/A', 'qtype_algebra_parser_multiply' ) , array ('/\+/A', 'qtype_algebra_parser_add' ) , array ('/-/A', 'qtype_algebra_parser_subtract' ) - , array ('/('.implode('|', self::$specials).')/A', 'qtype_algebra_parser_special' ) , array ('/('.self::$expnumber.'|'.self::$plainnumber.')/A', 'qtype_algebra_parser_number' ) - , array ('/[A-Za-z][A-Za-z0-9_]*/A', 'qtype_algebra_parser_variable' ) + , array ('/[A-Za-z][A-Za-z0-9_]*/A', 'qtype_algebra_parser_identifier' ) ); } @@ -1508,31 +1485,6 @@ class qtype_algebra_parser { * @return top term of the parsed expression */ public function parse($text, $variables = array(), $undecvars = false) { - // Create a regular expression to match the known variables if an array is specified. - if (!empty($variables)) { - // Create an empty array to store the list of extra regular expressions to match. - $reextra = array(); - // Loop over all the variable names we are given. - foreach ($variables as $var) { - // Create a temporary variable term using the current name. - $tmpvar = new qtype_algebra_parser_variable($var); - // If the variable name has a subscript then create a new regular expression to - // search for which includes an underscore. - if (!empty($tmpvar->_subscript)) { - $reextra[] = $tmpvar->_base.'_'.$tmpvar->_subscript; - } - } - // Merge the variable name array with the array of extra regular expressions to match. - $variables = array_merge($variables, $reextra); - // Sort the array in order of increasing variable length in order to prevent 'x1' matching - // a variable 'x' before 'x1'. Do this using a helper function, which will compare two - // strings using their length only, and use this with the usort function. - usort($variables, 'qtype_algebra_parser_strlen_sort'); - // Generate a single regular expression which will match both all known variables. - $revar = '/('.implode('|', $variables).')/A'; - } else { - $revar = ''; - } $i = 0; // Create an array to store the parse tree. $tree = array(); @@ -1594,28 +1546,28 @@ class qtype_algebra_parser { $i += strlen($m[0]); continue; } - // If a list of predefined variables was given to the method then check for them here. - if (!empty($revar) and preg_match($revar, substr($text, $i), $m)) { - // Check for a zero argument term or brackets preceding the variable and if there is one then - // add the implicit multiplication operation. - if (count($tree) > 0 and (is_array($tree[count($tree) - 1]) or $tree[count($tree) - 1]->n_args() == 0)) { - array_push($tree, new qtype_algebra_parser_multiply('*')); - } - // Increment the string index by the length of the variable's name. - $i += strlen($m[0]); - // Push a new variable term onto the parse tree. - array_push($tree, new qtype_algebra_parser_variable($m[0])); - continue; - } // Here we have not found any open or close brackets or known variables so we can // parse the string for a normal token. foreach ($this->_tokens as $token) { if (preg_match($token[0], substr($text, $i), $m)) { - // Check for a variable and throw an exception if undeclared variables are - // not allowed and a list of defined variables was passed. - if (!empty($revar) and !$undecvars and $token[1] == 'qtype_algebra_parser_variable') { - throw new parser_exception(get_string('undeclaredvar', 'qtype_algebra', $m[0])); + if ($token[1] == 'qtype_algebra_parser_identifier') { + if (in_array($m[0], self::$functions, 1)) { + $token[1] = 'qtype_algebra_parser_function'; + } + else if (in_array($m[0], self::$specials, 1)) { + $token[1] = 'qtype_algebra_parser_special'; + } + else if (in_array($m[0], $variables, 1)) { + $token[1] = 'qtype_algebra_parser_variable'; + } else { + if (!empty($variables) and !$undecvars) { + throw new parser_exception(get_string('undeclaredvar', 'qtype_algebra', $m[0])); + } + // heretofore unknown identifier, promote to variable: + $token[1] = 'qtype_algebra_parser_variable'; + } } + // Check for a zero argument term preceding a variable, function or special and then // add the implicit multiplication. if (count($tree) > 0 and ($token[1] == 'qtype_algebra_parser_variable' or -- cgit v1.2.3