Files
ViciDialSVN/agc_2-X/trunk/www/agc/Evaluator.php
T
mattf 05452d2883 Added SOURCESELECT Custom Field Type
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
2021-02-11 23:43:56 +00:00

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);
}
}