Added ability to calculate basic Math functions in SCRIPT custom field types git-svn-id: svn://192.168.202.10@3352 3d104415-ff17-0410-8863-d5cf3c621b8a
240 lines
7.2 KiB
PHP
240 lines
7.2 KiB
PHP
<?php
|
|
|
|
# Evaluator.php - part of the Matex project written by Dorin Marcoci, © 2021
|
|
# (edited by Matt Florell in 2021)
|
|
#
|
|
# CHANGES:
|
|
# 210211-1426 - Changed from original script to comment-out Exceptions in favor of returned errors
|
|
#
|
|
|
|
namespace Matex;
|
|
|
|
class Exception extends \Exception {}
|
|
|
|
class Evaluator {
|
|
|
|
private $pos;
|
|
private $text;
|
|
|
|
public $variables = [];
|
|
public $onVariable;
|
|
public $functions = []; // ARCTAN COS SIN TAN ABS EXP LN LOG SQRT SQR INT FRAC TRUNC ROUND ARCSIN ARCCOS SIGN NOT
|
|
public $onFunction;
|
|
|
|
private function getIdentity(int &$kind = null, string &$value = null): bool {
|
|
$ops = $this->pos;
|
|
$ist = ($this->text[$this->pos] ?? false) == '"';
|
|
if ($ist) {
|
|
$this->pos++;
|
|
if (($ist = strpos($this->text, '"', $this->pos)) === false) return false;
|
|
$kind = 4;
|
|
$value = substr($this->text, $this->pos, $ist - $this->pos);
|
|
$this->pos = $ist + 1;
|
|
return true;
|
|
}
|
|
while ((($char = $this->text[$this->pos] ?? false) !== false) && (ctype_alnum($char) || in_array($char, ['.', '_'])))
|
|
$this->pos++;
|
|
if (!$len = $this->pos - $ops) return false;
|
|
$str = substr($this->text, $ops, $len);
|
|
if (is_numeric($str)) $kind = 1;
|
|
else {
|
|
if (ctype_digit($str[0]) || (strpos($str, '.') !== false)) return false;
|
|
$kind = $char == '(' ? 3 : 2;
|
|
}
|
|
$value = $str;
|
|
return true;
|
|
}
|
|
|
|
private function getVariable(string $name) {
|
|
$value = $this->variables[$name] ?? null;
|
|
if (!isset($value) && isset($this->onVariable)) {
|
|
call_user_func_array($this->onVariable, [$name, &$value]);
|
|
$this->variables[$name] = $value;
|
|
}
|
|
if (!isset($value)) {return "Math Error: Unknown variable";}
|
|
// throw new Exception('Unknown variable: '.$name, 5);
|
|
return $value;
|
|
}
|
|
|
|
private function addArgument(&$arguments, $argument) {
|
|
if ($argument == '') {return "Math Error: Empty argument";}
|
|
// throw new Exception('Empty argument', 4);
|
|
$arguments[] = $argument;
|
|
}
|
|
|
|
private function getArguments(&$arguments = []): bool {
|
|
$b = 1;
|
|
$this->pos++;
|
|
$mark = $this->pos;
|
|
while ((($char = $this->text[$this->pos] ?? false) !== false) && ($b > 0)) {
|
|
if (($char == ',') && ($b == 1)) {
|
|
$this->addArgument($arguments, substr($this->text, $mark, $this->pos - $mark));
|
|
$mark = $this->pos + 1;
|
|
}
|
|
elseif ($char == ')') $b--;
|
|
elseif ($char == '(') $b++;
|
|
$this->pos++;
|
|
}
|
|
if (!in_array($char, [false, '+', '-', '/', '*', '^', '%', ')']))
|
|
return false;
|
|
$this->addArgument($arguments, substr($this->text, $mark, $this->pos - $mark - 1));
|
|
return true;
|
|
}
|
|
|
|
private function proArguments($arguments) {
|
|
$ops = $this->pos;
|
|
$otx = $this->text;
|
|
$result = [];
|
|
foreach ($arguments as $argument)
|
|
$result[] = $this->perform($argument);
|
|
$this->pos = $ops;
|
|
$this->text = $otx;
|
|
return $result;
|
|
}
|
|
|
|
private function getFunction(string $name) {
|
|
$routine = $this->functions[$name] ?? null;
|
|
if (!isset($routine) && isset($this->onFunction)) {
|
|
call_user_func_array($this->onFunction, [$name, &$routine]);
|
|
$this->functions[$name] = $routine;
|
|
}
|
|
if (!isset($routine)) {return "Math Error: Unknown function";}
|
|
// throw new Exception('Unknown function: '.$name, 6);
|
|
if (!$this->getArguments($arguments)) {return "Math Error: Syntax error";}
|
|
// throw new Exception('Syntax error', 1);
|
|
if (isset($routine['arc']) && ($routine['arc'] != count($arguments))) {return "Math Error: Invalid argument count";}
|
|
// throw new Exception('Invalid argument count', 3);
|
|
return call_user_func_array($routine['ref'], $this->proArguments($arguments));
|
|
}
|
|
|
|
private function isNumer($value): bool {
|
|
return is_float($value) || is_integer($value);
|
|
}
|
|
|
|
private function checkNumers($a, $b) {
|
|
if ($this->isNumer($a) && $this->isNumer($b)) return;
|
|
{return "Math Error: Non-numeric value";}
|
|
// throw new Exception('Non-numeric value', 8);
|
|
}
|
|
|
|
private function checkString($a) {
|
|
if (is_string($a)) return;
|
|
{return "Math Error: Non-string value";}
|
|
// throw new Exception('Non-string value', 9);
|
|
}
|
|
|
|
private function term() {
|
|
$minus = false;
|
|
while ((($char = $this->text[$this->pos] ?? false) !== false) && in_array($char, ['-', '+'])) {
|
|
$negat = $char == '-';
|
|
$minus = $minus ? ($negat ? false : true) : $negat;
|
|
$this->pos++;
|
|
}
|
|
if ($this->text[$this->pos] == '(') {
|
|
$this->pos++;
|
|
$value = $this->calculate();
|
|
$this->pos++;
|
|
if (!in_array($this->text[$this->pos] ?? false, [false, '+', '-', '/', '*', '^', '%', ')'])) {return "Math Error: Syntax error";}
|
|
// throw new Exception('Syntax error', 1);
|
|
return $minus ? - $value : $value;
|
|
}
|
|
if (!$this->getIdentity($kind, $name)) {return "Math Error: Syntax error";}
|
|
// throw new Exception('Syntax error', 1);
|
|
switch ($kind) {
|
|
case 1: $value = (float) $name; break;
|
|
case 2: $value = $this->getVariable($name); break;
|
|
case 3: $value = $this->getFunction($name); break;
|
|
case 4: $value = $name; break;
|
|
}
|
|
return $minus ? - $value : $value;;
|
|
}
|
|
|
|
private function subTerm() {
|
|
$value = $this->term();
|
|
while (in_array($char = $this->text[$this->pos] ?? false, ['*', '/', '^', '%'])) {
|
|
$this->pos++;
|
|
$term = $this->term();
|
|
$this->checkNumers($value, $term);
|
|
switch ($char) {
|
|
case '*':
|
|
if ( (!is_numeric($value)) or (strlen($value) < 1) or (!is_numeric($term)) or (strlen($term) < 1) ) {return "Math Error: Empty variable";}
|
|
// throw new Exception('Empty variable', 7);
|
|
$value *= $term;
|
|
break;
|
|
case '/':
|
|
if ($term == 0) {return "Math Error: Division by zero";}
|
|
// throw new Exception('Division by zero', 7);
|
|
$value /= $term;
|
|
break;
|
|
case '^':
|
|
$value **= $term;
|
|
break;
|
|
case '%':
|
|
$value %= $term;
|
|
break;
|
|
}
|
|
}
|
|
return $value;
|
|
}
|
|
|
|
private function calculate() {
|
|
$value = $this->subTerm();
|
|
while (in_array($char = $this->text[$this->pos] ?? false, ['+', '-'])) {
|
|
$this->pos++;
|
|
$subTerm = $this->subTerm();
|
|
if (($char == '+') && is_string($value)) {
|
|
$this->checkString($subTerm);
|
|
$value .= $subTerm;
|
|
continue;
|
|
}
|
|
$this->checkNumers($value, $subTerm);
|
|
if ( (strlen($subTerm) < 1) or (!is_numeric($subTerm)) ) {return "Math Error: Empty variable";}
|
|
// throw new Exception('Empty variable', 7);
|
|
if ($char == '-') $subTerm = -$subTerm;
|
|
$value += $subTerm;
|
|
}
|
|
return $value;
|
|
}
|
|
|
|
private function perform(string $formula) {
|
|
$this->pos = 0;
|
|
$this->text = $formula;
|
|
return $this->calculate();
|
|
}
|
|
|
|
public function execute(string $formula) {
|
|
$b = 0;
|
|
for ($i = 0; $i < strlen($formula); $i++) {
|
|
switch ($formula[$i]) {
|
|
case '(': $b++; break;
|
|
case ')': $b--; break;
|
|
}
|
|
}
|
|
if ($b != 0) {return "Math Error: Unmatched brackets";}
|
|
// throw new Exception('Unmatched brackets', 2);
|
|
$i = strpos($formula, '"');
|
|
if ($i === false)
|
|
$formula = str_replace(' ', '', strtolower($formula));
|
|
else {
|
|
$cleaned = '';
|
|
$l = strlen($formula);
|
|
$s = 0;
|
|
$b = false;
|
|
do {
|
|
if ($b) $i++;
|
|
$part = substr($formula, $s, $i - $s);
|
|
if (!$b) $part = str_replace(' ', '', strtolower($part));
|
|
$s = $i;
|
|
$b = !$b;
|
|
$cleaned .= $part;
|
|
$d = $s + 1;
|
|
if ($l < $d) break;
|
|
} while (($i = strpos($formula, '"', $d)) !== false);
|
|
if ($l != $s)
|
|
$cleaned .= str_replace(' ', '', strtolower(substr($formula, $s)));
|
|
$formula = $cleaned;
|
|
}
|
|
return $this->perform($formula);
|
|
}
|
|
|
|
} |