Note that there are some explanatory texts on larger screens.

plurals
  1. PO
    primarykey
    data
    text
    <p>Depending on your needs, I would suggest looking into the <a href="http://en.wikipedia.org/wiki/Shunting-yard_algorithm">Shunting Yard Algorithm</a>. It's pretty easy to implement, and works quite well.</p> <p>Here's an example I whipped up a while ago: <a href="https://gist.github.com/1232629">GIST</a>.</p> <p>Here's the code copy/pasted into one block:</p> <p>Expression Definitions:</p> <pre><code>class Parenthesis extends TerminalExpression { protected $precidence = 7; public function operate(Stack $stack) { } public function getPrecidence() { return $this-&gt;precidence; } public function isNoOp() { return true; } public function isParenthesis() { return true; } public function isOpen() { return $this-&gt;value == '('; } } class Number extends TerminalExpression { public function operate(Stack $stack) { return $this-&gt;value; } } abstract class Operator extends TerminalExpression { protected $precidence = 0; protected $leftAssoc = true; public function getPrecidence() { return $this-&gt;precidence; } public function isLeftAssoc() { return $this-&gt;leftAssoc; } public function isOperator() { return true; } } class Addition extends Operator { protected $precidence = 4; public function operate(Stack $stack) { return $stack-&gt;pop()-&gt;operate($stack) + $stack-&gt;pop()-&gt;operate($stack); } } class Subtraction extends Operator { protected $precidence = 4; public function operate(Stack $stack) { $left = $stack-&gt;pop()-&gt;operate($stack); $right = $stack-&gt;pop()-&gt;operate($stack); return $right - $left; } } class Multiplication extends Operator { protected $precidence = 5; public function operate(Stack $stack) { return $stack-&gt;pop()-&gt;operate($stack) * $stack-&gt;pop()-&gt;operate($stack); } } class Division extends Operator { protected $precidence = 5; public function operate(Stack $stack) { $left = $stack-&gt;pop()-&gt;operate($stack); $right = $stack-&gt;pop()-&gt;operate($stack); return $right / $left; } } class Power extends Operator { protected $precidence=6; public function operate(Stack $stack) { $left = $stack-&gt;pop()-&gt;operate($stack); $right = $stack-&gt;pop()-&gt;operate($stack); return pow($right, $left); } } abstract class TerminalExpression { protected $value = ''; public function __construct($value) { $this-&gt;value = $value; } public static function factory($value) { if (is_object($value) &amp;&amp; $value instanceof TerminalExpression) { return $value; } elseif (is_numeric($value)) { return new Number($value); } elseif ($value == '+') { return new Addition($value); } elseif ($value == '-') { return new Subtraction($value); } elseif ($value == '*') { return new Multiplication($value); } elseif ($value == '/') { return new Division($value); } elseif ($value == '^') { return new Power($value); } elseif (in_array($value, array('(', ')'))) { return new Parenthesis($value); } throw new Exception('Undefined Value ' . $value); } abstract public function operate(Stack $stack); public function isOperator() { return false; } public function isParenthesis() { return false; } public function isNoOp() { return false; } public function render() { return $this-&gt;value; } } </code></pre> <p>The stack (really simple implementation):</p> <pre><code>class Stack { protected $data = array(); public function push($element) { $this-&gt;data[] = $element; } public function poke() { return end($this-&gt;data); } public function pop() { return array_pop($this-&gt;data); } } </code></pre> <p>And finally, the executor class:</p> <pre><code>class Math { protected $variables = array(); public function evaluate($string) { $stack = $this-&gt;parse($string); return $this-&gt;run($stack); } public function parse($string) { $tokens = $this-&gt;tokenize($string); $output = new Stack(); $operators = new Stack(); foreach ($tokens as $token) { $token = $this-&gt;extractVariables($token); $expression = TerminalExpression::factory($token); if ($expression-&gt;isOperator()) { $this-&gt;parseOperator($expression, $output, $operators); } elseif ($expression-&gt;isParenthesis()) { $this-&gt;parseParenthesis($expression, $output, $operators); } else { $output-&gt;push($expression); } } while (($op = $operators-&gt;pop())) { if ($op-&gt;isParenthesis()) { throw new RuntimeException('Mismatched Parenthesis'); } $output-&gt;push($op); } return $output; } public function registerVariable($name, $value) { $this-&gt;variables[$name] = $value; } public function run(Stack $stack) { while (($operator = $stack-&gt;pop()) &amp;&amp; $operator-&gt;isOperator()) { $value = $operator-&gt;operate($stack); if (!is_null($value)) { $stack-&gt;push(TerminalExpression::factory($value)); } } return $operator ? $operator-&gt;render() : $this-&gt;render($stack); } protected function extractVariables($token) { if ($token[0] == '$') { $key = substr($token, 1); return isset($this-&gt;variables[$key]) ? $this-&gt;variables[$key] : 0; } return $token; } protected function render(Stack $stack) { $output = ''; while (($el = $stack-&gt;pop())) { $output .= $el-&gt;render(); } if ($output) { return $output; } throw new RuntimeException('Could not render output'); } protected function parseParenthesis(TerminalExpression $expression, Stack $output, Stack $operators) { if ($expression-&gt;isOpen()) { $operators-&gt;push($expression); } else { $clean = false; while (($end = $operators-&gt;pop())) { if ($end-&gt;isParenthesis()) { $clean = true; break; } else { $output-&gt;push($end); } } if (!$clean) { throw new RuntimeException('Mismatched Parenthesis'); } } } protected function parseOperator(TerminalExpression $expression, Stack $output, Stack $operators) { $end = $operators-&gt;poke(); if (!$end) { $operators-&gt;push($expression); } elseif ($end-&gt;isOperator()) { do { if ($expression-&gt;isLeftAssoc() &amp;&amp; $expression-&gt;getPrecidence() &lt;= $end-&gt;getPrecidence()) { $output-&gt;push($operators-&gt;pop()); } elseif (!$expression-&gt;isLeftAssoc() &amp;&amp; $expression-&gt;getPrecidence() &lt; $end-&gt;getPrecidence()) { $output-&gt;push($operators-&gt;pop()); } else { break; } } while (($end = $operators-&gt;poke()) &amp;&amp; $end-&gt;isOperator()); $operators-&gt;push($expression); } else { $operators-&gt;push($expression); } } protected function tokenize($string) { $parts = preg_split('((\d+|\+|-|\(|\)|\*|/)|\s+)', $string, null, PREG_SPLIT_NO_EMPTY | PREG_SPLIT_DELIM_CAPTURE); $parts = array_map('trim', $parts); return $parts; } } </code></pre> <p>It works by first tokenizing the input (based on word boundary, and tokens). Then, it runs the Shunting Yard algorithm on it to convert the input into a RPN (Reverse Polish Notation) stack. Then, it's just a matter of executing the stack. Here's a quick example:</p> <pre><code>$math = new Math(); $answer = $math-&gt;evaluate('(2 + 3) * 4'); var_dump($answer); // int(20) $answer = $math-&gt;evaluate('1 + 2 * ((3 + 4) * 5 + 6)'); var_dump($answer); // int(83) $answer = $math-&gt;evaluate('(1 + 2) * (3 + 4) * (5 + 6)'); var_dump($answer); // int(231) $math-&gt;registerVariable('a', 4); $answer = $math-&gt;evaluate('($a + 3) * 4'); var_dump($answer); // int(28) $math-&gt;registerVariable('a', 5); $answer = $math-&gt;evaluate('($a + $a) * 4'); var_dump($answer); // int(40) </code></pre> <p>Now, this example is significantly more complex than you may need. The reason is that it also handles grouping and operator precedence. But it's a decent example of a running algorithm that doesn't use EVAL and supports variables...</p>
    singulars
    1. This table or related slice is empty.
    plurals
    1. This table or related slice is empty.
    1. This table or related slice is empty.
    1. This table or related slice is empty.
    1. VO
      singulars
      1. This table or related slice is empty.
    2. VO
      singulars
      1. This table or related slice is empty.
    3. VO
      singulars
      1. This table or related slice is empty.
 

Querying!

 
Guidance

SQuiL has stopped working due to an internal error.

If you are curious you may find further information in the browser console, which is accessible through the devtools (F12).

Reload