ILIAS  release_8 Revision v8.19
All Data Structures Namespaces Files Functions Variables Modules Pages
EvalMath Class Reference
+ Collaboration diagram for EvalMath:

Public Member Functions

 __construct ()
 
 e (string $expr)
 
 evaluate (string $expr)
 
 vars ()
 
 funcs ()
 
 nfx ($expr)
 
 pfx ($tokens, $vars=[])
 
 trigger (string $msg)
 
 from_hexbin ($token)
 

Data Fields

bool $suppress_errors = false
 
string $last_error = null
 
array $v = array('e' => 2.71,'pi' => 3.14)
 
array $f = []
 
array $vb = array('e', 'pi')
 
array $fb
 

Detailed Description

Definition at line 89 of file class.EvalMath.php.

Constructor & Destructor Documentation

◆ __construct()

EvalMath::__construct ( )

Definition at line 103 of file class.EvalMath.php.

104  {
105  // make the variables a little more accurate
106  $this->v['pi'] = pi();
107  $this->v['exp'] = exp(1);
108  $this->v['e'] = exp(1); // different result for exp(1) and e
109  $this->fb[] = 'exp'; // usage of php exp function in formula
110  }

Member Function Documentation

◆ e()

EvalMath::e ( string  $expr)

Definition at line 112 of file class.EvalMath.php.

References evaluate().

113  {
114  return $this->evaluate($expr);
115  }
evaluate(string $expr)
+ Here is the call graph for this function:

◆ evaluate()

EvalMath::evaluate ( string  $expr)

Definition at line 117 of file class.EvalMath.php.

References $i, $token, nfx(), pfx(), and trigger().

Referenced by e().

118  {
119  // convert exponential notation
120  $expr = preg_replace_callback(
121  "/(\\d{0,1})e(-{0,1}\\d+)/is",
122  fn ($hit): string => $hit[1] . ((strlen($hit[1])) ? '*' : '') . '10^(' . $hit[2] . ')',
123  $expr
124  );
125  // standard functionality
126  $this->last_error = null;
127  $expr = trim($expr);
128  if (substr($expr, -1, 1) == ';') {
129  $expr = substr($expr, 0, strlen($expr) - 1);
130  } // strip semicolons at the end
131  //===============
132  // is it a variable assignment?
133  if (preg_match('/^\s*([a-z]\w*)\s*=\s*(.+)$/', $expr, $matches)) {
134  if (in_array($matches[1], $this->vb)) { // make sure we're not assigning to a constant
135  return $this->trigger("cannot assign to constant '$matches[1]'");
136  }
137  if (($tmp = $this->pfx($this->nfx($matches[2]))) === false) {
138  return false;
139  } // get the result and make sure it's good
140  $this->v[$matches[1]] = $tmp; // if so, stick it in the variable array
141  return $this->v[$matches[1]]; // and return the resulting value
142  //===============
143  // is it a function assignment?
144  } elseif (preg_match('/^\s*([a-z]\w*)\s*\(\s*([a-z]\w*(?:\s*,\s*[a-z]\w*)*)\s*\)\s*=\s*(.+)$/', $expr, $matches)) {
145  $fnn = $matches[1]; // get the function name
146  if (in_array($matches[1], $this->fb)) { // make sure it isn't built in
147  return $this->trigger("cannot redefine built-in function '$matches[1]()'");
148  }
149  $args = explode(",", preg_replace("/\s+/", "", $matches[2])); // get the arguments
150  if (($stack = $this->nfx($matches[3])) === false) {
151  return false;
152  } // see if it can be converted to postfix
153  for ($i = 0; $i < count($stack); $i++) { // freeze the state of the non-argument variables
154  $token = $stack[$i];
155  if (preg_match('/^[a-z]\w*$/', $token) and !in_array($token, $args)) {
156  if (array_key_exists($token, $this->v)) {
157  $stack[$i] = $this->v[$token];
158  } else {
159  return $this->trigger("undefined variable '$token' in function definition");
160  }
161  }
162  }
163  $this->f[$fnn] = array('args' => $args, 'func' => $stack);
164  return true;
165  } else {
166  return $this->pfx($this->nfx($expr)); // straight up evaluation, woo
167  }
168  }
pfx($tokens, $vars=[])
$token
Definition: xapitoken.php:70
trigger(string $msg)
$i
Definition: metadata.php:41
+ Here is the call graph for this function:
+ Here is the caller graph for this function:

◆ from_hexbin()

EvalMath::from_hexbin (   $token)

Definition at line 422 of file class.EvalMath.php.

References $token.

Referenced by pfx().

423  {
424  if (strtoupper(substr($token, -1, 1)) == 'H') {
425  return hexdec($token);
426  }
427  if (strtoupper(substr($token, -1, 1)) == 'B') {
428  return bindec($token);
429  }
430  return false;
431  }
$token
Definition: xapitoken.php:70
+ Here is the caller graph for this function:

◆ funcs()

EvalMath::funcs ( )
Returns
string[]

Definition at line 181 of file class.EvalMath.php.

181  : array
182  {
183  $output = [];
184  foreach ($this->f as $fnn => $dat) {
185  $output[] = $fnn . '(' . implode(',', $dat['args']) . ')';
186  }
187  return $output;
188  }

◆ nfx()

EvalMath::nfx (   $expr)

Definition at line 190 of file class.EvalMath.php.

References $index, and trigger().

Referenced by evaluate().

191  {
192  $index = 0;
193  $stack = new EvalMathStack();
194  $output = []; // postfix form of expression, to be passed to pfx()
195  $expr = trim(strtolower($expr));
196 
197  $ops = array('+', '-', '*', '/', '^', '_');
198  $ops_r = array('+' => 0,'-' => 0,'*' => 0,'/' => 0,'^' => 1); // right-associative operator?
199  $ops_p = array('+' => 0,'-' => 0,'*' => 1,'/' => 1,'_' => 1,'^' => 2); // operator precedence
200 
201  $expecting_op = false; // we use this in syntax-checking the expression
202  // and determining when a - is a negation
203 
204  if (preg_match("/[^\w\s+*^\/()\.,-]/", $expr, $matches)) { // make sure the characters are all good
205  return $this->trigger("illegal character '{$matches[0]}'");
206  }
207 
208  while (1) { // 1 Infinite Loop ;)
209  $op = substr($expr, $index, 1); // get the first character at the current index
210  // find out if we're currently at the beginning of a number/variable/function/parenthesis/operand
211  $ex = preg_match('/^([01]+[bB]|[\da-fA-F]+[hH]|[a-z]\w*\(?|\d+(?:\.\d*)?|\.\d+|\()/', substr($expr, $index), $match);
212  //===============
213  if ($op == '-' and !$expecting_op) { // is it a negation instead of a minus?
214  $stack->push('_'); // put a negation on the stack
215  $index++;
216  } elseif ($op == '_') { // we have to explicitly deny this, because it's legal on the stack
217  return $this->trigger("illegal character '_'"); // but not in the input expression
218  //===============
219  } elseif ((in_array($op, $ops) or $ex) and $expecting_op) { // are we putting an operator on the stack?
220  if ($ex) { // are we expecting an operator but have a number/variable/function/opening parethesis?
221  $op = '*';
222  $index--; // it's an implicit multiplication
223  }
224  // heart of the algorithm:
225  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])) {
226  $output[] = $stack->pop(); // pop stuff off the stack into the output
227  }
228  // many thanks: http://en.wikipedia.org/wiki/Reverse_Polish_notation#The_algorithm_in_detail
229  $stack->push($op); // finally put OUR operator onto the stack
230  $index++;
231  $expecting_op = false;
232  //===============
233  } elseif ($op == ')' and $expecting_op) { // ready to close a parenthesis?
234  while (($o2 = $stack->pop()) != '(') { // pop off the stack back to the last (
235  if (is_null($o2)) {
236  return $this->trigger("unexpected ')'");
237  } else {
238  $output[] = $o2;
239  }
240  }
241  if (preg_match("/^([a-z]\w*)\($/", (string) $stack->last(2), $matches)) { // did we just close a function?
242  $fnn = $matches[1]; // get the function name
243  $arg_count = $stack->pop(); // see how many arguments there were (cleverly stored on the stack, thank you)
244  $output[] = $stack->pop(); // pop the function and push onto the output
245  if (in_array($fnn, $this->fb)) { // check the argument count
246  if ($arg_count > 1) {
247  return $this->trigger("too many arguments ($arg_count given, 1 expected)");
248  }
249  } elseif (array_key_exists($fnn, $this->f)) {
250  if ($arg_count != count($this->f[$fnn]['args'])) {
251  return $this->trigger("wrong number of arguments ($arg_count given, " . count($this->f[$fnn]['args']) . " expected)");
252  }
253  } else { // did we somehow push a non-function on the stack? this should never happen
254  return $this->trigger("internal error");
255  }
256  }
257  $index++;
258  //===============
259  } elseif ($op == ',' and $expecting_op) { // did we just finish a function argument?
260  while (($o2 = $stack->pop()) != '(') {
261  if (is_null($o2)) {
262  return $this->trigger("unexpected ','");
263  } // oops, never had a (
264  else {
265  $output[] = $o2;
266  } // pop the argument expression stuff and push onto the output
267  }
268  // make sure there was a function
269  if (!preg_match("/^([a-z]\w*)\($/", $stack->last(2), $matches)) {
270  return $this->trigger("unexpected ','");
271  }
272  $stack->push($stack->pop() + 1); // increment the argument count
273  $stack->push('('); // put the ( back on, we'll need to pop back to it again
274  $index++;
275  $expecting_op = false;
276  //===============
277  } elseif ($op == '(' and !$expecting_op) {
278  $stack->push('('); // that was easy
279  $index++;
280  $allow_neg = true;
281  //===============
282  } elseif ($ex and !$expecting_op) { // do we now have a function/variable/number?
283  $expecting_op = true;
284  $val = $match[1];
285  if (preg_match("/^([a-z]\w*)\($/", $val, $matches)) { // may be func, or variable w/ implicit multiplication against parentheses...
286  if (in_array($matches[1], $this->fb) or array_key_exists($matches[1], $this->f)) { // it's a func
287  $stack->push($val);
288  $stack->push(1);
289  $stack->push('(');
290  $expecting_op = false;
291  } else { // it's a var w/ implicit multiplication
292  $val = $matches[1];
293  $output[] = $val;
294  }
295  } else { // it's a plain old var or num
296  $output[] = $val;
297  }
298  $index += strlen($val);
299  //===============
300  } elseif ($op == ')') { // miscellaneous error checking
301  return $this->trigger("unexpected ')'");
302  } elseif (in_array($op, $ops) and !$expecting_op) {
303  return $this->trigger("unexpected operator '$op'");
304  } else { // I don't even want to know what you did to get here
305  return $this->trigger("an unexpected error occured");
306  }
307  if ($index == strlen($expr)) {
308  if (in_array($op, $ops)) { // did we end with an operator? bad.
309  return $this->trigger("operator '$op' lacks operand");
310  } else {
311  break;
312  }
313  }
314  while (substr($expr, $index, 1) == ' ') { // step the index past whitespace (pretty much turns whitespace
315  $index++; // into implicit multiplication if no operator is there)
316  }
317  }
318  while (!is_null($op = $stack->pop())) { // pop everything off the stack and push onto output
319  if ($op == '(') {
320  return $this->trigger("expecting ')'");
321  } // if there are (s on the stack, ()s were unbalanced
322  $output[] = $op;
323  }
324  return $output;
325  }
$index
Definition: metadata.php:145
trigger(string $msg)
+ Here is the call graph for this function:
+ Here is the caller graph for this function:

◆ pfx()

EvalMath::pfx (   $tokens,
  $vars = [] 
)

Definition at line 328 of file class.EvalMath.php.

References $i, $token, ilMath\_add(), ilMath\_div(), ilMath\_mul(), ilMath\_pow(), ilMath\_sub(), from_hexbin(), and trigger().

Referenced by evaluate().

329  {
330  if ($tokens == false) {
331  return false;
332  }
333 
334  $stack = new EvalMathStack();
335 
336  foreach ($tokens as $token) { // nice and easy
337  // if the token is a binary operator, pop two values off the stack, do the operation, and push the result back on
338  if (in_array($token, array('+', '-', '*', '/', '^'))) {
339  if (is_null($op2 = $stack->pop())) {
340  return $this->trigger("internal error");
341  }
342  if (is_null($op1 = $stack->pop())) {
343  return $this->trigger("internal error");
344  }
345  switch ($token) {
346  case '+':
347  $stack->push(ilMath::_add($op1, $op2)); break;
348  case '-':
349  $stack->push(ilMath::_sub($op1, $op2)); break;
350  case '*':
351  $stack->push(ilMath::_mul($op1, $op2)); break;
352  case '/':
353  if ($op2 == 0) {
354  return $this->trigger("division by zero");
355  }
356  $stack->push(ilMath::_div($op1, $op2)); break;
357  case '^':
358  $stack->push(ilMath::_pow($op1, $op2)); break;
359  }
360  // if the token is a unary operator, pop one value off the stack, do the operation, and push it back on
361  } elseif ($token == "_") {
362  $stack->push(-1 * $stack->pop());
363  // if the token is a function, pop arguments off the stack, hand them to the function, and push the result back on
364  } elseif (preg_match("/^([a-z]\w*)\($/", $token, $matches)) { // it's a function!
365  $fnn = $matches[1];
366  if (in_array($fnn, $this->fb)) { // built-in function:
367  if (is_null($op1 = $stack->pop())) {
368  return $this->trigger("internal error");
369  }
370  $fnn = preg_replace("/^arc/", "a", $fnn); // for the 'arc' trig synonyms
371  if ($fnn == 'log') {
372  $fnn = 'log10';
373  } elseif ($fnn == 'ln') {
374  $fnn = 'log';
375  }
376 
377  $stack->push($fnn($op1)); // 'eval()' can be easily avoided here
378  } elseif (array_key_exists($fnn, $this->f)) { // user function
379  // get args
380  $args = [];
381  for ($i = count($this->f[$fnn]['args']) - 1; $i >= 0; $i--) {
382  if (is_null($args[$this->f[$fnn]['args'][$i]] = $stack->pop())) {
383  return $this->trigger("internal error");
384  }
385  }
386  $stack->push($this->pfx($this->f[$fnn]['func'], $args)); // yay... recursion!!!!
387  }
388  // if the token is a number or variable, push it on the stack
389  } else {
390  if (is_numeric($token)) {
391  $stack->push($token);
392  } elseif (($hex = $this->from_hexbin($token)) !== false) {
393  $stack->push($hex);
394  } elseif (array_key_exists($token, $this->v)) {
395  $stack->push($this->v[$token]);
396  } elseif (array_key_exists($token, $vars)) {
397  $stack->push($vars[$token]);
398  } else {
399  return $this->trigger("undefined variable '$token'");
400  }
401  }
402  }
403  // when we're out of tokens, the stack should have a single element, the final result
404  if ($stack->count != 1) {
405  return $this->trigger("internal error");
406  }
407  return $stack->pop();
408  }
static _add($left_operand, $right_operand, int $scale=50)
static _div($left_operand, $right_operand, int $scale=50)
static _pow($left_operand, $right_operand, int $scale=50)
from_hexbin($token)
pfx($tokens, $vars=[])
static _sub($left_operand, $right_operand, int $scale=50)
$token
Definition: xapitoken.php:70
trigger(string $msg)
static _mul($left_operand, $right_operand, int $scale=50)
$i
Definition: metadata.php:41
+ Here is the call graph for this function:
+ Here is the caller graph for this function:

◆ trigger()

EvalMath::trigger ( string  $msg)

Definition at line 411 of file class.EvalMath.php.

Referenced by evaluate(), nfx(), and pfx().

411  : bool
412  {
413  $this->last_error = $msg;
414  if (!$this->suppress_errors) {
415  trigger_error($msg, E_USER_WARNING);
416  }
417  return false;
418  }
+ Here is the caller graph for this function:

◆ vars()

EvalMath::vars ( )

Definition at line 170 of file class.EvalMath.php.

References $v.

170  : array
171  {
172  $output = $this->v;
173  unset($output['pi']);
174  unset($output['e']);
175  return $output;
176  }

Field Documentation

◆ $f

array EvalMath::$f = []

Definition at line 95 of file class.EvalMath.php.

◆ $fb

array EvalMath::$fb
Initial value:
= array(
'sin','sinh','arcsin','asin','arcsinh','asinh',
'cos','cosh','arccos','acos','arccosh','acosh',
'tan','tanh','arctan','atan','arctanh','atanh',
'sqrt','abs','ln','log')

Definition at line 97 of file class.EvalMath.php.

◆ $last_error

string EvalMath::$last_error = null

Definition at line 92 of file class.EvalMath.php.

◆ $suppress_errors

bool EvalMath::$suppress_errors = false

Definition at line 91 of file class.EvalMath.php.

◆ $v

array EvalMath::$v = array('e' => 2.71,'pi' => 3.14)

Definition at line 94 of file class.EvalMath.php.

Referenced by vars().

◆ $vb

array EvalMath::$vb = array('e', 'pi')

Definition at line 96 of file class.EvalMath.php.


The documentation for this class was generated from the following file: