Skip to content

Commit

Permalink
added inner the function "if" working as the tenar operator
Browse files Browse the repository at this point in the history
solved issue: #3
  • Loading branch information
optimistex committed Jan 19, 2018
1 parent 9abeac7 commit 2d806cc
Show file tree
Hide file tree
Showing 3 changed files with 92 additions and 29 deletions.
2 changes: 1 addition & 1 deletion composer.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "optimistex/math-expression",
"version": "2.1.2",
"version": "2.1.3",
"type": "library",
"description": "Taken from http://www.phpclasses.org/browse/file/11680.html, cred to Miles Kaufmann",
"keywords": [
Expand Down
102 changes: 74 additions & 28 deletions expression.php
Original file line number Diff line number Diff line change
Expand Up @@ -105,35 +105,73 @@ functions, which are stored in the object. Try it, it's fun!

class Expression
{

public $suppress_errors = false;
public $last_error;

public $v = array('e' => 2.71, 'pi' => 3.14); // variables (and constants)
public $f = array(); // user-defined functions
public $vb = array('e', 'pi'); // constants
public $fb = array( // built-in functions
/**
* Function defined outside of Expression as closures
*
* Example:
* ```php
* $expr = new Expression();
* $expr->functions = [
* 'foo' => function ($a, $b) {
* return $a + $b;
* }
* ];
* $expr->e('2 * foo(3, 4)'); //return: 14
* ```
*
* @var array
*/
public $functions = [];

/**
* Variables (and constants)
* @var array
*/
public $v = ['e' => 2.71, 'pi' => 3.14];

private $f = []; // user-defined functions
private $vb = ['e', 'pi']; // constants
private $fb = [ // built-in functions
'sin', 'sinh', 'arcsin', 'asin', 'arcsinh', 'asinh',
'cos', 'cosh', 'arccos', 'acos', 'arccosh', 'acosh',
'tan', 'tanh', 'arctan', 'atan', 'arctanh', 'atanh',
'sqrt', 'abs', 'ln', 'log');
'sqrt', 'abs', 'ln', 'log'];

public $functions = array(); // function defined outside of Expression as closures
private $_functions = [];

public function __construct()
{
// make the variables a little more accurate
$this->v['pi'] = M_PI;
$this->v['e'] = exp(1);
$this->_functions = [
'if' => function ($condition, $valueIfTrue, $valueIfFalse) {
return $condition ? $valueIfTrue : $valueIfFalse;
}
];
}

/**
* @param string $expr
* @return bool|mixed|null
* @throws ReflectionException
*/
public function e($expr)
{
return $this->evaluate($expr);
}

/**
* @param string $expr
* @return bool|mixed|null
* @throws ReflectionException
*/
public function evaluate($expr)
{
$this->_functions = array_merge($this->_functions, $this->functions);
$this->last_error = null;
$expr = preg_replace("/\r|\n/", '', trim($expr));
if ($expr && $expr[strlen($expr) - 1] === ';') {
Expand All @@ -157,7 +195,7 @@ public function evaluate($expr)
return $this->trigger("cannot redefine built-in function '$matches[1]()'");
}

$args = array();
$args = [];
if ($matches[2] !== '') {
$args = explode(',', preg_replace("/\s+/", '', $matches[2])); // get the arguments
}
Expand All @@ -174,23 +212,29 @@ public function evaluate($expr)
}
}
}
$this->f[$fnn] = array('args' => $args, 'func' => $stack);
$this->f[$fnn] = ['args' => $args, 'func' => $stack];
return true;
//===============
}
return $this->pfx($this->nfx($expr)); // straight up evaluation, woo
}

/**
* @return array
*/
public function vars()
{
$output = $this->v;
unset($output['pi'], $output['e']);
return $output;
}

/**
* @return array
*/
public function funcs()
{
$output = array();
$output = [];
foreach ($this->f as $fnn => $dat) {
$output[] = $fnn . '(' . implode(',', $dat['args']) . ')';
}
Expand All @@ -203,20 +247,21 @@ public function funcs()
* Convert infix to postfix notation
* @param string $expr
* @return array|bool
* @throws ReflectionException
*/
public function nfx($expr)
private function nfx($expr)
{
$index = 0;
$stack = new ExpressionStack;
$output = array(); // postfix form of expression, to be passed to pfx()
$output = []; // postfix form of expression, to be passed to pfx()
$expr = trim($expr);

$ops = array('+', '-', '*', '/', '^', '_', '%', '>', '<', '>=', '<=', '==', '!=', '=~', '&&', '||', '!');
$ops_r = array('+' => 0, '-' => 0, '*' => 0, '/' => 0, '%' => 0, '^' => 1, '>' => 0,
$ops = ['+', '-', '*', '/', '^', '_', '%', '>', '<', '>=', '<=', '==', '!=', '=~', '&&', '||', '!'];
$ops_r = ['+' => 0, '-' => 0, '*' => 0, '/' => 0, '%' => 0, '^' => 1, '>' => 0,
'<' => 0, '>=' => 0, '<=' => 0, '==' => 0, '!=' => 0, '=~' => 0,
'&&' => 0, '||' => 0, '!' => 0); // right-associative operator?
$ops_p = array('+' => 3, '-' => 3, '*' => 4, '/' => 4, '_' => 4, '%' => 4, '^' => 5, '>' => 2, '<' => 2,
'>=' => 2, '<=' => 2, '==' => 2, '!=' => 2, '=~' => 2, '&&' => 1, '||' => 1, '!' => 5); // operator precedence
'&&' => 0, '||' => 0, '!' => 0]; // right-associative operator?
$ops_p = ['+' => 3, '-' => 3, '*' => 4, '/' => 4, '_' => 4, '%' => 4, '^' => 5, '>' => 2, '<' => 2,
'>=' => 2, '<=' => 2, '==' => 2, '!=' => 2, '=~' => 2, '&&' => 1, '||' => 1, '!' => 5]; // operator precedence

$expecting_op = false; // we use this in syntax-checking the expression
// and determining when a - is a negation
Expand Down Expand Up @@ -308,8 +353,8 @@ public function nfx($expr)
if ($arg_count !== count($this->f[$fnn]['args'])) {
return $this->trigger("wrong number of arguments ($arg_count given, " . count($this->f[$fnn]['args']) . ' expected) ' . json_encode($this->f[$fnn]['args']));
}
} elseif (array_key_exists($fnn, $this->functions)) {
$func_reflection = new ReflectionFunction($this->functions[$fnn]);
} elseif (array_key_exists($fnn, $this->_functions)) {
$func_reflection = new ReflectionFunction($this->_functions[$fnn]);
$count = $func_reflection->getNumberOfParameters();
if ($arg_count !== $count) {
return $this->trigger("wrong number of arguments ($arg_count given, " . $count . ' expected)');
Expand Down Expand Up @@ -355,7 +400,7 @@ public function nfx($expr)
} elseif (preg_match("/^([a-z]\w*)\($/", $val, $matches)) { // may be func, or variable w/ implicit multiplication against parentheses...
if (in_array($matches[1], $this->fb, true) ||
array_key_exists($matches[1], $this->f) ||
array_key_exists($matches[1], $this->functions)
array_key_exists($matches[1], $this->_functions)
) { // it's a func
if ($begin_argument && !$stack->incrementArgument()) {
$this->trigger("unexpected '('");
Expand Down Expand Up @@ -411,16 +456,17 @@ public function nfx($expr)
* @param array|bool $tokens
* @param array $vars
* @return bool|mixed|null
* @throws ReflectionException
*/
public function pfx($tokens, array $vars = array())
private function pfx($tokens, array $vars = [])
{
if ($tokens === false) {
return false;
}
$stack = new ExpressionStack();
foreach ((array)$tokens as $token) { // nice and easy
// if the token is a binary operator, pop two values off the stack, do the operation, and push the result back on
if (in_array($token, array('+', '-', '*', '/', '^', '<', '>', '<=', '>=', '==', '&&', '||', '!=', '=~', '%'), true)) {
if (in_array($token, ['+', '-', '*', '/', '^', '<', '>', '<=', '>=', '==', '&&', '||', '!=', '=~', '%'], true)) {
$op2 = $stack->pop();
$op1 = $stack->pop();
switch ($token) {
Expand Down Expand Up @@ -530,8 +576,8 @@ public function pfx($tokens, array $vars = array())
$args[$this->f[$fnn]['args'][$i]] = $stack->pop();
}
$stack->push($this->pfx($this->f[$fnn]['func'], $args)); // yay... recursion!!!!
} else if (array_key_exists($fnn, $this->functions)) {
$reflection = new ReflectionFunction($this->functions[$fnn]);
} else if (array_key_exists($fnn, $this->_functions)) {
$reflection = new ReflectionFunction($this->_functions[$fnn]);
$count = $reflection->getNumberOfParameters();
$args = [];
for ($i = $count - 1; $i >= 0; $i--) {
Expand Down Expand Up @@ -565,8 +611,8 @@ public function pfx($tokens, array $vars = array())
$stack->push(0 + $token);
} else if (preg_match("/^['\\\"](.*)['\\\"]$/", $token)) {
$stack->push(json_decode(preg_replace_callback("/^['\\\"](.*)['\\\"]$/", function ($matches) {
$m = array("/\\\\'/", '/(?<!\\\\)"/');
$r = array("'", '\\"');
$m = ["/\\\\'/", '/(?<!\\\\)"/'];
$r = ["'", '\\"'];
return '"' . preg_replace($m, $r, $matches[1]) . '"';
}, $token)));
} elseif (array_key_exists($token, $this->v)) {
Expand All @@ -590,7 +636,7 @@ public function pfx($tokens, array $vars = array())
* @param string $msg
* @return bool
*/
public function trigger($msg)
private function trigger($msg)
{
$this->last_error = $msg;
if (!$this->suppress_errors) {
Expand All @@ -605,7 +651,7 @@ public function trigger($msg)
*/
class ExpressionStack
{
public $stack = array();
public $stack = [];
public $count = 0;

public function push($val)
Expand Down
17 changes: 17 additions & 0 deletions tests/Expression.php
Original file line number Diff line number Diff line change
Expand Up @@ -371,9 +371,13 @@ public function testFunctionOrderParameters()
'max' => function ($v1, $v2) {
return max($v1, $v2);
},
'foo' => function ($a, $b) {
return $a + $b;
}
];

$data = [
'2*foo(3,4)' => 14,
'max(2,(2+2)*2)' => 8,
'max((2),(0))' => 2,
'max((2+2)*2,(3+2)*2)' => 10,
Expand Down Expand Up @@ -406,4 +410,17 @@ public function testRowBreaking()
2'
));
}

public function testFunctionIf()
{
$expr = new Expression();
$expr->functions = [
'fake' => function () {
return 'fake';
}
];
$this->assertEquals('fake', $expr->evaluate('fake()'));
$this->assertEquals(10, $expr->evaluate('if(2 > 3, 5, 10)'));
$this->assertEquals(5, $expr->evaluate('if(2 < 3, 5, 10)'));
}
}

2 comments on commit 2d806cc

@alekseysolo
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Спасибо.
А вот определение массивов зря переделали. Оно работает только с какой-то версии пхп, и бывают ситуации когда у клиентов стоят как раз ранние версии. Приходилось переписывать...

@optimistex
Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Рад помочь!

Про массивы. Специально сверил. Короткая запись массива начинается с PHP версии 5.4.
В composer.json как раз прописано:

  "require": {
    "php": "^5.4 || >=7.0 <7.3"
  }

Другими словами, клиент с более низкой версией PHP просто не сможет установить этот пакет.

Please sign in to comment.