Permalink
Browse files

MDL-27162 "localize strings in mathslib" needed to add mathslib file

to list of core language files in moodlelib.
  • Loading branch information...
jamiepratt authored and timhunt committed Apr 13, 2011
1 parent 94a67b3 commit 13264f35057d2f37374ec3e0e8ad4070f4676bd7
Showing with 138 additions and 33 deletions.
  1. +42 −0 lang/en/mathslib.php
  2. +49 −33 lib/evalmath/evalmath.class.php
  3. +2 −0 lib/moodlelib.php
  4. +45 −0 lib/simpletest/testmathslib.php
View
@@ -0,0 +1,42 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Strings for component 'mathslib', language 'en', branch 'MOODLE_19_STABLE'
*
* @package mathslib
* @copyright 1999 onwards Martin Dougiamas {@link http://moodle.com}
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
$string['anunexpectederroroccured'] = 'an unexpected error occured';
$string['cannotassigntoconstant'] = 'cannot assign to constant \'{$a}\'';
$string['cannotredefinebuiltinfunction'] = 'cannot redefine built-in function \'{$a}()\'';
$string['divisionbyzero'] = 'division by zero';
$string['expectingaclosingbracket'] = 'expecting a closing bracket';
$string['illegalcharactergeneral'] = 'illegal character \'{$a}\'';
$string['illegalcharacterunderscore'] = 'illegal character \'_\'';
$string['implicitmultiplicationnotallowed'] = 'expecting operator, implicit multiplication not allowed.';
$string['internalerror'] = 'internal error';
$string['operatorlacksoperand'] = 'operator \'{$a}\' lacks operand';
$string['undefinedvariable'] = 'undefined variable \'{$a}\'';
$string['undefinedvariableinfunctiondefinition'] = 'undefined variable \'{$a}\' in function definition';
$string['unexpectedclosingbracket'] = 'unexpected closing bracket';
$string['unexpectedcomma'] = 'unexpected comma';
$string['unexpectedoperator'] = 'unexpected operator \'{$a}\'';
$string['wrongnumberofarguments'] = 'wrong number of arguments ({$a->given} given, {$a->expected} expected)';
@@ -133,7 +133,7 @@ function evaluate($expr) {
// is it a variable assignment?
if (preg_match('/^\s*([a-z][a-z0-9]*)\s*=\s*(.+)$/', $expr, $matches)) {
if (in_array($matches[1], $this->vb)) { // make sure we're not assigning to a constant
return $this->trigger("cannot assign to constant '$matches[1]'");
return $this->trigger(get_string('cannotassigntoconstant', 'mathslib', $matches[1]));
}
if (($tmp = $this->pfx($this->nfx($matches[2]))) === false) return false; // get the result and make sure it's good
$this->v[$matches[1]] = $tmp; // if so, stick it in the variable array
@@ -143,7 +143,7 @@ function evaluate($expr) {
} elseif (preg_match('/^\s*([a-z][a-z0-9]*)\s*\(\s*([a-z][a-z0-9]*(?:\s*,\s*[a-z][a-z0-9]*)*)\s*\)\s*=\s*(.+)$/', $expr, $matches)) {
$fnn = $matches[1]; // get the function name
if (in_array($matches[1], $this->fb)) { // make sure it isn't built in
return $this->trigger("cannot redefine built-in function '$matches[1]()'");
return $this->trigger(get_string('cannotredefinebuiltinfunction', 'mathslib', $matches[1]));
}
$args = explode(",", preg_replace("/\s+/", "", $matches[2])); // get the arguments
if (($stack = $this->nfx($matches[3])) === false) return false; // see if it can be converted to postfix
@@ -153,7 +153,7 @@ function evaluate($expr) {
if (array_key_exists($token, $this->v)) {
$stack[$i] = $this->v[$token];
} else {
return $this->trigger("undefined variable '$token' in function definition");
return $this->trigger(get_string('undefinedvariableinfunctiondefinition', 'mathslib', $token));
}
}
}
@@ -194,7 +194,7 @@ function nfx($expr) {
// 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]}'");
return $this->trigger(get_string('illegalcharactergeneral', 'mathslib', $matches[0]));
}
while(1) { // 1 Infinite Loop ;)
@@ -206,12 +206,12 @@ function nfx($expr) {
$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
return $this->trigger(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("expecting operator, implicit multiplication not allowed.");
return $this->trigger(get_string('implicitmultiplicationnotallowed', 'mathslib'));
} else {// it's an implicit multiplication
$op = '*';
$index--;
@@ -228,7 +228,7 @@ function nfx($expr) {
//===============
} 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("unexpected ')'");
if (is_null($o2)) return $this->trigger(get_string('unexpectedclosingbracket', 'mathslib'));
else $output[] = $o2;
}
if (preg_match("/^([a-z][a-z0-9]*)\($/", $stack->last(2), $matches)) { // did we just close a function?
@@ -237,30 +237,42 @@ function nfx($expr) {
$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)
return $this->trigger("too many arguments ($arg_count given, 1 expected)");
if($arg_count > 1) {
$a= new stdClass();
$a->expected = 1;
$a->given = $arg_count;
return $this->trigger(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))
return $this->trigger("wrong number of arguments ($arg_count given, " . implode('/',$this->fc[$fnn]) . " expected)");
elseif (!in_array($arg_count, $counts)) {
$a= new stdClass();
$a->expected = implode('/',$this->fc[$fnn]);
$a->given = $arg_count;
return $this->trigger(get_string('wrongnumberofarguments', 'mathslib', $a));
}
} 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)");
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(get_string('wrongnumberofarguments', 'mathslib', $a));
}
} else { // did we somehow push a non-function on the stack? this should never happen
return $this->trigger("internal error");
return $this->trigger(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("unexpected ','"); // oops, never had a (
if (is_null($o2)) return $this->trigger(get_string('unexpectedcomma', 'mathslib')); // oops, never had a (
else $output[] = $o2; // pop the argument expression stuff and push onto the output
}
// make sure there was a function
if (!preg_match("/^([a-z][a-z0-9]*)\($/", $stack->last(2), $matches))
return $this->trigger("unexpected ','");
return $this->trigger(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++;
@@ -291,30 +303,34 @@ function nfx($expr) {
//===============
} 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("unexpected ')'");
if ($stack->last() != '(' or $stack->last(2) != 1) return $this->trigger(get_string('unexpectedclosingbracket', 'mathslib'));
if (preg_match("/^([a-z][a-z0-9]*)\($/", $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
$counts = $this->fc[$fnn];
if (!in_array(0, $counts))
return $this->trigger("wrong number of arguments ($arg_count given, " . implode('/',$this->fc[$fnn]) . " expected)");
if (!in_array(0, $counts)){
$a= new stdClass();
$a->expected = $this->fc[$fnn];
$a->given = $arg_count;
return $this->trigger(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("unexpected ')'");
return $this->trigger(get_string('unexpectedclosingbracket', 'mathslib'));
}
//===============
} elseif (in_array($op, $ops) and !$expecting_op) { // miscellaneous error checking
return $this->trigger("unexpected operator '$op'");
return $this->trigger(get_string('unexpectedoperator', 'mathslib', $op));
} else { // I don't even want to know what you did to get here
return $this->trigger("an unexpected error occured");
return $this->trigger(get_string('anunexpectederroroccured', 'mathslib'));
}
if ($index == strlen($expr)) {
if (in_array($op, $ops)) { // did we end with an operator? bad.
return $this->trigger("operator '$op' lacks operand");
return $this->trigger(get_string('operatorlacksoperand', 'mathslib', $op));
} else {
break;
}
@@ -325,7 +341,7 @@ function nfx($expr) {
}
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
if ($op == '(') return $this->trigger(get_string('expectingaclosingbracket', 'mathslib')); // if there are (s on the stack, ()s were unbalanced
$output[] = $op;
}
return $output;
@@ -345,33 +361,33 @@ function pfx($tokens, $vars = array()) {
$fnn = $token['fnn'];
$count = $token['argcount'];
if (in_array($fnn, $this->fb)) { // built-in function:
if (is_null($op1 = $stack->pop())) return $this->trigger("internal error");
if (is_null($op1 = $stack->pop())) return $this->trigger(get_string('internalerror', 'mathslib'));
$fnn = preg_replace("/^arc/", "a", $fnn); // for the 'arc' trig synonyms
if ($fnn == 'ln') $fnn = 'log';
eval('$stack->push(' . $fnn . '($op1));'); // perfectly safe eval()
} elseif (array_key_exists($fnn, $this->fc)) { // calc emulation function
// get args
$args = array();
for ($i = $count-1; $i >= 0; $i--) {
if (is_null($args[] = $stack->pop())) return $this->trigger("internal error");
if (is_null($args[] = $stack->pop())) return $this->trigger(get_string('internalerror', 'mathslib'));
}
$res = call_user_func(array('EvalMathCalcEmul', $fnn), $args);
if ($res === FALSE) {
return $this->trigger("internal error");
return $this->trigger(get_string('internalerror', 'mathslib'));
}
$stack->push($res);
} elseif (array_key_exists($fnn, $this->f)) { // user function
// get args
$args = array();
for ($i = count($this->f[$fnn]['args'])-1; $i >= 0; $i--) {
if (is_null($args[$this->f[$fnn]['args'][$i]] = $stack->pop())) return $this->trigger("internal error");
if (is_null($args[$this->f[$fnn]['args'][$i]] = $stack->pop())) return $this->trigger(get_string('internalerror', 'mathslib'));
}
$stack->push($this->pfx($this->f[$fnn]['func'], $args)); // yay... recursion!!!!
}
// if the token is a binary operator, pop two values off the stack, do the operation, and push the result back on
} elseif (in_array($token, array('+', '-', '*', '/', '^'), true)) {
if (is_null($op2 = $stack->pop())) return $this->trigger("internal error");
if (is_null($op1 = $stack->pop())) return $this->trigger("internal error");
if (is_null($op2 = $stack->pop())) return $this->trigger(get_string('internalerror', 'mathslib'));
if (is_null($op1 = $stack->pop())) return $this->trigger(get_string('internalerror', 'mathslib'));
switch ($token) {
case '+':
$stack->push($op1+$op2); break;
@@ -380,7 +396,7 @@ function pfx($tokens, $vars = array()) {
case '*':
$stack->push($op1*$op2); break;
case '/':
if ($op2 == 0) return $this->trigger("division by zero");
if ($op2 == 0) return $this->trigger(get_string('divisionbyzero', 'mathslib'));
$stack->push($op1/$op2); break;
case '^':
$stack->push(pow($op1, $op2)); break;
@@ -397,12 +413,12 @@ function pfx($tokens, $vars = array()) {
} elseif (array_key_exists($token, $vars)) {
$stack->push($vars[$token]);
} else {
return $this->trigger("undefined variable '$token'");
return $this->trigger(get_string('undefinedvariable', 'mathslib', $token));
}
}
}
// when we're out of tokens, the stack should have a single element, the final result
if ($stack->count != 1) return $this->trigger("internal error");
if ($stack->count != 1) return $this->trigger(get_string('internalerror', 'mathslib'));
return $stack->pop();
}
View
@@ -7049,6 +7049,8 @@ function get_core_subsystems() {
'iso6392' => NULL,
'langconfig' => NULL,
'license' => NULL,
'mathslib' => NULL,
'message' => 'message',
'message' => 'message',
'mimetypes' => NULL,
'mnet' => 'mnet',
@@ -76,6 +76,51 @@ function test__specialchars() {
$this->assertEqual($res, 60, 'sum is: %s');
}
/**
* Tests some slightly more complex expressions
*/
function test__more_complex_expressions() {
$formula = new calc_formula('=pi() + a', array('a'=>10));
$res = $formula->evaluate();
$this->assertEqual($res, pi()+10);
$formula = new calc_formula('=pi()^a', array('a'=>10));
$res = $formula->evaluate();
$this->assertEqual($res, pow(pi(),10));
$formula = new calc_formula('=-8*(5/2)^2*(1-sqrt(4))-8');
$res = $formula->evaluate();
$this->assertEqual($res, -8*pow((5/2),2)*(1-sqrt(4))-8);
}
/**
* Tests some slightly more complex expressions
*/
function test__error_handling() {
if (debugging('', DEBUG_DEVELOPER)){
$this->expectError();
}
$formula = new calc_formula('=pi( + a', array('a'=>10));
$res = $formula->evaluate();
$this->assertEqual($res, false);
$this->assertEqual($formula->get_error(), get_string('unexpectedoperator', 'mathslib', '+'));
if (debugging('', DEBUG_DEVELOPER)){
$this->expectError();
}
$formula = new calc_formula('=pi(');
$res = $formula->evaluate();
$this->assertEqual($res, false);
$this->assertEqual($formula->get_error(), get_string('expectingaclosingbracket', 'mathslib'));
if (debugging('', DEBUG_DEVELOPER)){
$this->expectError();
}
$formula = new calc_formula('=pi()^');
$res = $formula->evaluate();
$this->assertEqual($res, false);
$this->assertEqual($formula->get_error(), get_string('operatorlacksoperand', 'mathslib', '^'));
}
}

0 comments on commit 13264f3

Please sign in to comment.