function nfx($expr) { $index = 0; $stack = new EvalMathStack(); $output = array(); // postfix form of expression, to be passed to pfx() $expr = trim(strtolower($expr)); $ops = array('+', '-', '*', '/', '^', '_'); $ops_r = array('+' => 0, '-' => 0, '*' => 0, '/' => 0, '^' => 1); // right-associative operator? $ops_p = array('+' => 0, '-' => 0, '*' => 1, '/' => 1, '_' => 1, '^' => 2); // operator precedence $expecting_op = false; // we use this in syntax-checking the expression // and determining when a - is a negation if (preg_match("/[^\\w\\s+*^\\/()\\.,-]/", $expr, $matches)) { // make sure the characters are all good return $this->trigger("illegal character '{$matches[0]}'"); } while (1) { // 1 Infinite Loop ;) $op = substr($expr, $index, 1); // get the first character at the current index // find out if we're currently at the beginning of a number/variable/function/parenthesis/operand $ex = preg_match('/^([a-z]\\w*\\(?|\\d+(?:\\.\\d*)?|\\.\\d+|\\()/', substr($expr, $index), $match); //=============== if ($op == '-' and !$expecting_op) { // is it a negation instead of a minus? $stack->push('_'); // put a negation on the stack $index++; } elseif ($op == '_') { // we have to explicitly deny this, because it's legal on the stack return $this->trigger("illegal character '_'"); // but not in the input expression //=============== } elseif ((in_array($op, $ops) or $ex) and $expecting_op) { // are we putting an operator on the stack? if ($ex) { // are we expecting an operator but have a number/variable/function/opening parethesis? $op = '*'; $index--; // it's an implicit multiplication } // heart of the algorithm: while ($stack->count > 0 and $o2 = $stack->last() and in_array($o2, $ops) and $ops_r[$op] ? $ops_p[$op] < $ops_p[$o2] : $ops_p[$op] <= $ops_p[$o2]) { $output[] = $stack->pop(); // pop stuff off the stack into the output } // many thanks: http://en.wikipedia.org/wiki/Reverse_Polish_notation#The_algorithm_in_detail $stack->push($op); // finally put OUR operator onto the stack $index++; $expecting_op = false; //=============== } elseif ($op == ')' and $expecting_op) { // ready to close a parenthesis? $expectingOperand = false; while (($o2 = $stack->pop()) != '(') { // pop off the stack back to the last ( if (is_null($o2)) { return $this->trigger("unexpected ')'"); } else { $output[] = $o2; } } if (preg_match("/^([a-z]\\w*)\\(\$/", $stack->last(2), $matches)) { // did we just close a function? $fnn = $matches[1]; // get the function name $arg_count = $stack->pop(); // see how many arguments there were (cleverly stored on the stack, thank you) $output[] = $stack->pop(); // pop the function and push onto the output if (in_array($fnn, $this->fb)) { // check the argument count if ($arg_count > 1) { return $this->trigger("too many arguments ({$arg_count} given, 1 expected)"); } } elseif (array_key_exists($fnn, $this->f)) { if ($arg_count != count($this->f[$fnn]['args'])) { return $this->trigger("wrong number of arguments ({$arg_count} given, " . count($this->f[$fnn]['args']) . " expected)"); } } else { // did we somehow push a non-function on the stack? this should never happen return $this->trigger("internal error"); } } $index++; //=============== } elseif ($op == ',' and $expecting_op) { // did we just finish a function argument? while (($o2 = $stack->pop()) != '(') { if (is_null($o2)) { return $this->trigger("unexpected ','"); } else { $output[] = $o2; } // pop the argument expression stuff and push onto the output } // make sure there was a function if (!preg_match("/^([a-z]\\w*)\\(\$/", $stack->last(2), $matches)) { return $this->trigger("unexpected ','"); } $stack->push($stack->pop() + 1); // increment the argument count $stack->push('('); // put the ( back on, we'll need to pop back to it again $index++; $expecting_op = false; //=============== } elseif ($op == '(' and !$expecting_op) { $stack->push('('); // that was easy $index++; $allow_neg = true; //=============== } elseif ($ex and !$expecting_op) { // do we now have a function/variable/number? $expecting_op = true; $val = $match[1]; if (preg_match("/^([a-z]\\w*)\\(\$/", $val, $matches)) { // may be func, or variable w/ implicit multiplication against parentheses... if (in_array($matches[1], $this->fb) or array_key_exists($matches[1], $this->f)) { // it's a func $stack->push($val); $stack->push(1); $stack->push('('); $expecting_op = false; } else { // it's a var w/ implicit multiplication $val = $matches[1]; $output[] = $val; } } else { // it's a plain old var or num $output[] = $val; } $index += strlen($val); //=============== } elseif ($op == ')') { // miscellaneous error checking return $this->trigger("unexpected ')'"); } elseif (in_array($op, $ops) and !$expecting_op) { return $this->trigger("unexpected operator '{$op}'"); } else { // I don't even want to know what you did to get here return $this->trigger("an unexpected error occured"); } if ($index == strlen($expr)) { if (in_array($op, $ops)) { // did we end with an operator? bad. return $this->trigger("operator '{$op}' lacks operand"); } else { break; } } while (substr($expr, $index, 1) == ' ') { // step the index past whitespace (pretty much turns whitespace $index++; // into implicit multiplication if no operator is there) } } while (!is_null($op = $stack->pop())) { // pop everything off the stack and push onto output if ($op == '(') { return $this->trigger("expecting ')'"); } // if there are (s on the stack, ()s were unbalanced $output[] = $op; } return $output; }
function nfx($expr) { $index = 0; $stack = new EvalMathStack(); $output = array(); // postfix form of expression, to be passed to pfx() $expr = trim(strtolower($expr)); $ops = array('+', '-', '*', '/', '^', '_', '>', '<', '='); $ops_r = array('+' => 0, '-' => 0, '*' => 0, '/' => 0, '^' => 1, '>' => 0, '<' => 0, '=' => 0); // right-associative operator? $ops_p = array('+' => 0, '-' => 0, '*' => 1, '/' => 1, '_' => 1, '^' => 2, '>' => 0, '<' => 0, '=' => 0); // operator precedence $expecting_op = false; // we use this in syntax-checking the expression // and determining when a - is a negation if (preg_match("/[^\\w\\s+*^\\/()\\.,-<>=]/", $expr, $matches)) { // make sure the characters are all good return $this->trigger(MoodleTranslations::get_string('illegalcharactergeneral', 'mathslib', $matches[0])); } while (1) { // 1 Infinite Loop ;) $op = substr($expr, $index, 1); // get the first character at the current index // find out if we're currently at the beginning of a number/variable/function/parenthesis/operand $ex = preg_match('/^(' . self::$namepat . '\\(?|\\d+(?:\\.\\d*)?(?:(e[+-]?)\\d*)?|\\.\\d+|\\()/', substr($expr, $index), $match); //=============== if ($op == '-' and !$expecting_op) { // is it a negation instead of a minus? $stack->push('_'); // put a negation on the stack $index++; } elseif ($op == '_') { // we have to explicitly deny this, because it's legal on the stack return $this->trigger(MoodleTranslations::get_string('illegalcharacterunderscore', 'mathslib')); // but not in the input expression //=============== } elseif ((in_array($op, $ops) or $ex) and $expecting_op) { // are we putting an operator on the stack? if ($ex) { // are we expecting an operator but have a number/variable/function/opening parethesis? if (!$this->allowimplicitmultiplication) { return $this->trigger(MoodleTranslations::get_string('implicitmultiplicationnotallowed', 'mathslib')); } else { // it's an implicit multiplication $op = '*'; $index--; } } // heart of the algorithm: while ($stack->count > 0 and $o2 = $stack->last() and in_array($o2, $ops) and $ops_r[$op] ? $ops_p[$op] < $ops_p[$o2] : $ops_p[$op] <= $ops_p[$o2]) { $output[] = $stack->pop(); // pop stuff off the stack into the output } // many thanks: http://en.wikipedia.org/wiki/Reverse_Polish_notation#The_algorithm_in_detail $stack->push($op); // finally put OUR operator onto the stack $index++; $expecting_op = false; //=============== } elseif ($op == ')' and $expecting_op) { // ready to close a parenthesis? while (($o2 = $stack->pop()) != '(') { // pop off the stack back to the last ( if (is_null($o2)) { return $this->trigger(MoodleTranslations::get_string('unexpectedclosingbracket', 'mathslib')); } else { $output[] = $o2; } } if (preg_match('/^(' . self::$namepat . ')\\($/', $stack->last(2), $matches)) { // did we just close a function? $fnn = $matches[1]; // get the function name $arg_count = $stack->pop(); // see how many arguments there were (cleverly stored on the stack, thank you) $fn = $stack->pop(); $output[] = array('fn' => $fn, 'fnn' => $fnn, 'argcount' => $arg_count); // send function to output if (in_array($fnn, $this->fb)) { // check the argument count if ($arg_count > 1) { $a = new stdClass(); $a->expected = 1; $a->given = $arg_count; return $this->trigger(MoodleTranslations::get_string('wrongnumberofarguments', 'mathslib', $a)); } } elseif (array_key_exists($fnn, $this->fc)) { $counts = $this->fc[$fnn]; if (in_array(-1, $counts) and $arg_count > 0) { } elseif (!in_array($arg_count, $counts)) { $a = new stdClass(); $a->expected = implode('/', $this->fc[$fnn]); $a->given = $arg_count; return $this->trigger(MoodleTranslations::get_string('wrongnumberofarguments', 'mathslib', $a)); } } elseif (array_key_exists($fnn, $this->f)) { if ($arg_count != count($this->f[$fnn]['args'])) { $a = new stdClass(); $a->expected = count($this->f[$fnn]['args']); $a->given = $arg_count; return $this->trigger(MoodleTranslations::get_string('wrongnumberofarguments', 'mathslib', $a)); } } else { // did we somehow push a non-function on the stack? this should never happen return $this->trigger(MoodleTranslations::get_string('internalerror', 'mathslib')); } } $index++; //=============== } elseif ($op == ',' and $expecting_op) { // did we just finish a function argument? while (($o2 = $stack->pop()) != '(') { if (is_null($o2)) { return $this->trigger(MoodleTranslations::get_string('unexpectedcomma', 'mathslib')); } else { $output[] = $o2; } // pop the argument expression stuff and push onto the output } // make sure there was a function if (!preg_match('/^(' . self::$namepat . ')\\($/', $stack->last(2), $matches)) { return $this->trigger(MoodleTranslations::get_string('unexpectedcomma', 'mathslib')); } $stack->push($stack->pop() + 1); // increment the argument count $stack->push('('); // put the ( back on, we'll need to pop back to it again $index++; $expecting_op = false; //=============== } elseif ($op == '(' and !$expecting_op) { $stack->push('('); // that was easy $index++; $allow_neg = true; //=============== } elseif ($ex and !$expecting_op) { // do we now have a function/variable/number? $expecting_op = true; $val = $match[1]; if (preg_match('/^(' . self::$namepat . ')\\($/', $val, $matches)) { // may be func, or variable w/ implicit multiplication against parentheses... if (in_array($matches[1], $this->fb) or array_key_exists($matches[1], $this->f) or array_key_exists($matches[1], $this->fc)) { // it's a func $stack->push($val); $stack->push(1); $stack->push('('); $expecting_op = false; } else { // it's a var w/ implicit multiplication $val = $matches[1]; $output[] = $val; } } else { // it's a plain old var or num $output[] = $val; } $index += strlen($val); //=============== } elseif ($op == ')') { //it could be only custom function with no params or general error if ($stack->last() != '(' or $stack->last(2) != 1) { return $this->trigger(MoodleTranslations::get_string('unexpectedclosingbracket', 'mathslib')); } if (preg_match('/^(' . self::$namepat . ')\\($/', $stack->last(3), $matches)) { // did we just close a function? $stack->pop(); // ( $stack->pop(); // 1 $fn = $stack->pop(); $fnn = $matches[1]; // get the function name if (isset($this->fc[$fnn])) { $counts = $this->fc[$fnn]; } else { $counts = array(1); } // default count for built-in functions if (!in_array(0, $counts)) { $a = new stdClass(); $a->expected = $counts; $a->given = 0; return $this->trigger(MoodleTranslations::get_string('wrongnumberofarguments', 'mathslib', $a)); } $output[] = array('fn' => $fn, 'fnn' => $fnn, 'argcount' => 0); // send function to output $index++; $expecting_op = true; } else { return $this->trigger(MoodleTranslations::get_string('unexpectedclosingbracket', 'mathslib')); } //=============== } elseif (in_array($op, $ops) and !$expecting_op) { // miscellaneous error checking return $this->trigger(MoodleTranslations::get_string('unexpectedoperator', 'mathslib', $op)); } else { // I don't even want to know what you did to get here return $this->trigger(MoodleTranslations::get_string('anunexpectederroroccured', 'mathslib')); } if ($index == strlen($expr)) { if (in_array($op, $ops)) { // did we end with an operator? bad. return $this->trigger(MoodleTranslations::get_string('operatorlacksoperand', 'mathslib', $op)); } else { break; } } while (substr($expr, $index, 1) == ' ') { // step the index past whitespace (pretty much turns whitespace $index++; // into implicit multiplication if no operator is there) } } while (!is_null($op = $stack->pop())) { // pop everything off the stack and push onto output if ($op == '(') { return $this->trigger(MoodleTranslations::get_string('expectingaclosingbracket', 'mathslib')); } // if there are (s on the stack, ()s were unbalanced $output[] = $op; } return $output; }
function nfx($expr) { $index = 0; $stack = new EvalMathStack(); $output = array(); $expr = trim(strtolower($expr)); $ops = array('+', '-', '*', '/', '^', '_'); $ops_r = array('+' => 0, '-' => 0, '*' => 0, '/' => 0, '^' => 1); $ops_p = array('+' => 0, '-' => 0, '*' => 1, '/' => 1, '_' => 1, '^' => 2); $expecting_op = false; if (preg_match("/[^\\w\\s+*^\\/()\\.,-]/", $expr, $matches)) { return $this->trigger("illegal character '{$matches[0]}'"); } while (1) { $op = substr($expr, $index, 1); $ex = preg_match('/^([a-z]\\w*\\(?|\\d+(?:\\.\\d*)?|\\.\\d+|\\()/', substr($expr, $index), $match); if ($op == '-' and !$expecting_op) { $stack->push('_'); $index++; } elseif ($op == '_') { return $this->trigger("illegal character '_'"); } elseif ((in_array($op, $ops) or $ex) and $expecting_op) { if ($ex) { $op = '*'; $index--; } while ($stack->count > 0 and $o2 = $stack->last() and in_array($o2, $ops) and $ops_r[$op] ? $ops_p[$op] < $ops_p[$o2] : $ops_p[$op] <= $ops_p[$o2]) { $output[] = $stack->pop(); } $stack->push($op); $index++; $expecting_op = false; } elseif ($op == ')' and $expecting_op) { while (($o2 = $stack->pop()) != '(') { if (is_null($o2)) { return $this->trigger("unexpected ')'"); } else { $output[] = $o2; } } if (preg_match("/^([a-z]\\w*)\\(\$/", $stack->last(2), $matches)) { $fnn = $matches[1]; $arg_count = $stack->pop(); $output[] = $stack->pop(); if (in_array($fnn, $this->fb)) { if ($arg_count > 1) { return $this->trigger("too many arguments ({$arg_count} given, 1 expected)"); } } elseif (array_key_exists($fnn, $this->f)) { if ($arg_count != count($this->f[$fnn]['args'])) { return $this->trigger("wrong number of arguments ({$arg_count} given, " . count($this->f[$fnn]['args']) . " expected)"); } } else { return $this->trigger("internal error"); } } $index++; } elseif ($op == ',' and $expecting_op) { while (($o2 = $stack->pop()) != '(') { if (is_null($o2)) { return $this->trigger("unexpected ','"); } else { $output[] = $o2; } } if (!preg_match("/^([a-z]\\w*)\\(\$/", $stack->last(2), $matches)) { return $this->trigger("unexpected ','"); } $stack->push($stack->pop() + 1); $stack->push('('); $index++; $expecting_op = false; } elseif ($op == '(' and !$expecting_op) { $stack->push('('); $index++; $allow_neg = true; } elseif ($ex and !$expecting_op) { $expecting_op = true; $val = $match[1]; if (preg_match("/^([a-z]\\w*)\\(\$/", $val, $matches)) { if (in_array($matches[1], $this->fb) or array_key_exists($matches[1], $this->f)) { $stack->push($val); $stack->push(1); $stack->push('('); $expecting_op = false; } else { $val = $matches[1]; $output[] = $val; } } else { $output[] = $val; } $index += strlen($val); } elseif ($op == ')') { return $this->trigger("unexpected ')'"); } elseif (in_array($op, $ops) and !$expecting_op) { return $this->trigger("unexpected operator '{$op}'"); } else { return $this->trigger("an unexpected error occured"); } if ($index == strlen($expr)) { if (in_array($op, $ops)) { return $this->trigger("operator '{$op}' lacks operand"); } else { break; } } while (substr($expr, $index, 1) == ' ') { $index++; } } while (!is_null($op = $stack->pop())) { if ($op == '(') { return $this->trigger("expecting ')'"); } $output[] = $op; } return $output; }
/** * Convert infix to postfix notation * * @param string $expr * The expression to convert * * @return array * An array with the elements of the expression */ private function infixToPostfix($expr) { $index = 0; $stack = new EvalMathStack(); $output = array(); $ops = array('+', '-', '*', '/', '^', '_'); $opsRight = array('+' => 0, '-' => 0, '*' => 0, '/' => 0, '^' => 1); $opsPrecedence = array('+' => 0, '-' => 0, '*' => 1, '/' => 1, '_' => 1, '^' => 2); $expectingOp = FALSE; $inFunction = 0; $funcHasArg = array(); if (preg_match("/[^\\w\\s+*^\\/()\\.,-]/", $expr, $matches)) { return $this->trigger(t('illegal character "%c"', array('%c' => $matches[0]))); } while (1) { $op = drupal_substr($expr, $index, 1); $ex = preg_match('/^([a-z]\\w*\\(?|\\d+(?:\\.\\d*)?(?:[Ee][+-]?\\d*)?|\\.\\d+|\\()/', drupal_substr($expr, $index), $match); if ($op == '-' and !$expectingOp) { $stack->push('_'); $index++; } elseif ($op == '_') { return $this->trigger(t('illegal character "_"')); } elseif ((in_array($op, $ops) || $ex) && $expectingOp) { if ($ex) { $op = '*'; $index--; } while ($stack->getCount() > 0 && ($o2 = $stack->last()) && in_array($o2, $ops) && ($opsRight[$op] ? $opsPrecedence[$op] < $opsPrecedence[$o2] : $opsPrecedence[$op] <= $opsPrecedence[$o2])) { $output[] = $stack->pop(); } $stack->push($op); $index++; $expectingOp = FALSE; } elseif ($op == ')' && ($expectingOp || $inFunction)) { while (($o2 = $stack->pop()) != '(') { if (is_NULL($o2)) { return $this->trigger(t('unexpected ) found')); } else { $output[] = $o2; } } if (preg_match("/^([a-z]\\w*)\\(\$/", $stack->last(2), $matches)) { $fnn = $matches[1]; $argCount = $stack->pop(); if ($funcHasArg[$inFunction]) { $argCount++; } $output[] = $stack->pop(); // pop the function and push onto the output if (array_key_exists($fnn, $this->funcAliases)) { // Check if the function is an alias $fnn = $this->funcAliases[$fnn]; } if (array_key_exists($fnn, $this->funcBuildIn)) { // check the argument count if ($argCount != $this->funcBuildIn[$fnn]) { return $this->trigger(t('wrong number of arguments (@gc given, @ec expected)', array('@gc' => $argCount, '@ec' => $this->funcBuildIn[$fnn]))); } } elseif (array_key_exists($fnn, $this->funcUser)) { if ($argCount != count($this->funcUser[$fnn]['args'])) { return $this->trigger(t('wrong number of arguments (@gc given, @ec expected)', array('@gc' => $argCount, '@ec' => count($this->funcUser[$fnn]['args'])))); } } else { return $this->trigger(t('internal error, not a function')); } $inFunction--; } $index++; } elseif ($op == ',' and $expectingOp) { while (($o2 = $stack->pop()) != '(') { if (is_NULL($o2)) { return $this->trigger(t('unexpected , found')); } else { $output[] = $o2; } } if (!preg_match("/^([a-z]\\w*)\\(\$/", $stack->last(2), $matches)) { return $this->trigger(t('unexpected , found')); } $stack->push($stack->pop() + 1); $stack->push('('); $index++; $expectingOp = FALSE; } elseif ($op == '(' and !$expectingOp) { $stack->push('('); $index++; $allow_neg = TRUE; } elseif ($ex and !$expectingOp) { $expectingOp = TRUE; $val = $match[1]; if (preg_match("/^([a-z]\\w*)\\(\$/", $val, $matches)) { if (array_key_exists($matches[1], $this->funcAliases) || array_key_exists($matches[1], $this->funcBuildIn) || array_key_exists($matches[1], $this->funcUser)) { $stack->push($val); $stack->push(0); $stack->push('('); $expectingOp = FALSE; // If we are in a function, it'll have at least one argument, this one. if ($inFunction) { $funcHasArg[$inFunction] = TRUE; } $inFunction++; $funcHasArg[$inFunction] = FALSE; } else { if (!array_key_exists($matches[1], $this->variables)) { return $this->trigger(t('unknown variable or function "%f"', array('%f' => $matches[1]))); } $val = $matches[1]; $output[] = $val; // If we are in a function, it'll have at least one argument, this one. if ($inFunction) { $funcHasArg[$inFunction] = TRUE; } } } else { $output[] = $val; // If we are in a function, it'll have at least one argument, this one. if ($inFunction) { $funcHasArg[$inFunction] = TRUE; } } $index += drupal_strlen($val); } elseif ($op == ')') { return $this->trigger(t('unexpected ) found')); } elseif (in_array($op, $ops) and !$expectingOp) { return $this->trigger(t('unexpected operator "%op"', array('%op' => $op))); } else { return $this->trigger(t('an unexpected error occured')); } if ($index == drupal_strlen($expr)) { if (in_array($op, $ops)) { return $this->trigger(t('operator "%op" lacks operand', array('%op' => $op))); } else { break; } } while (drupal_substr($expr, $index, 1) == ' ') { $index++; } } while (!is_NULL($op = $stack->pop())) { if ($op == '(') { return $this->trigger(t('expecting ) but none found')); } $output[] = $op; } return $output; }