summaryrefslogtreecommitdiff
path: root/src/Calc.php
diff options
context:
space:
mode:
Diffstat (limited to 'src/Calc.php')
-rw-r--r--src/Calc.php138
1 files changed, 138 insertions, 0 deletions
diff --git a/src/Calc.php b/src/Calc.php
new file mode 100644
index 0000000..e8dba8d
--- /dev/null
+++ b/src/Calc.php
@@ -0,0 +1,138 @@
+<?php
+
+namespace kch42\ste;
+
+/* Class Calc contains static methods needed by <ste:calc /> */
+class Calc {
+ private function __construct() {}
+
+ /* We could also just eval() the $infix_math code, but this is much cooler :-D (Parser inception) */
+ public static function shunting_yard($infix_math) {
+ $operators = array(
+ "+" => array("l", 2),
+ "-" => array("l", 2),
+ "*" => array("l", 3),
+ "/" => array("l", 3),
+ "^" => array("r", 4),
+ "_" => array("r", 5),
+ "(" => array("", 0),
+ ")" => array("", 0)
+ );
+
+ preg_match_all("/\s*(?:(?:[+\\-\\*\\/\\^\\(\\)])|(\\d*[\\.]?\\d*))\\s*/s", $infix_math, $tokens, PREG_PATTERN_ORDER);
+ $tokens_raw = array_filter(array_map('trim', $tokens[0]), function($x) { return ($x === "0") || (!empty($x)); });
+ $output_queue = array();
+ $op_stack = array();
+
+ $lastpriority = NULL;
+ /* Make - unary, if neccessary */
+ $tokens = array();
+ foreach($tokens_raw as $token) {
+ $priority = isset($operators[$token]) ? $operators[$token][1] : -1;
+ if(($token == "-") && (($lastpriority === NULL) || ($lastpriority >= 0))) {
+ $priority = $operators["_"][1];
+ $tokens[] = "_";
+ } else {
+ $tokens[] = $token;
+ }
+ $lastpriority = $priority;
+ }
+
+ while(!empty($tokens)) {
+ $token = array_shift($tokens);
+ if(is_numeric($token)) {
+ $output_queue[] = $token;
+ } else if($token == "(") {
+ $op_stack[] = $token;
+ } else if($token == ")") {
+ $lbr_found = false;
+ while(!empty($op_stack)) {
+ $op = array_pop($op_stack);
+ if($op == "(") {
+ $lbr_found = true;
+ break;
+ }
+ $output_queue[] = $op;
+ }
+ if(!$lbr_found) {
+ throw new RuntimeError("Bracket mismatch.");
+ }
+ } else if(!isset($operators[$token])) {
+ throw new RuntimeError("Invalid token ($token): Not a number, bracket or operator. Stop.");
+ } else {
+ $priority = $operators[$token][1];
+ if($operators[$token][0] == "l") {
+ while((!empty($op_stack)) and ($priority <= $operators[$op_stack[count($op_stack)-1]][1])) {
+ $output_queue[] = array_pop($op_stack);
+ }
+ } else {
+ while((!empty($op_stack)) and ($priority < $operators[$op_stack[count($op_stack)-1]][1])) {
+ $output_queue[] = array_pop($op_stack);
+ }
+ }
+ $op_stack[] = $token;
+ }
+ }
+
+ while(!empty($op_stack)) {
+ $op = array_pop($op_stack);
+ if($op == "(") {
+ throw new RuntimeError("Bracket mismatch...");
+ }
+ $output_queue[] = $op;
+ }
+
+ return $output_queue;
+ }
+
+ public static function pop2(&$array) {
+ $rv = array(array_pop($array), array_pop($array));
+ if(array_search(NULL, $rv, true) !== false) {
+ throw new RuntimeError("Not enough numbers on stack. Invalid formula.");
+ }
+ return $rv;
+ }
+
+ public static function calc_rpn($rpn) {
+ $stack = array();
+ foreach($rpn as $token) {
+ switch($token) {
+ case "+":
+ list($b, $a) = self::pop2($stack);
+ $stack[] = $a + $b;
+ break;
+ case "-":
+ list($b, $a) = self::pop2($stack);
+ $stack[] = $a - $b;
+ break;
+ case "*":
+ list($b, $a) = self::pop2($stack);
+ $stack[] = $a * $b;
+ break;
+ case "/":
+ list($b, $a) = self::pop2($stack);
+ $stack[] = $a / $b;
+ break;
+ case "^":
+ list($b, $a) = self::pop2($stack);
+ $stack[] = pow($a, $b);
+ break;
+ case "_":
+ $a = array_pop($stack);
+ if($a === NULL) {
+ throw new RuntimeError("Not enough numbers on stack. Invalid formula.");
+ }
+ $stack[] = -$a;
+ break;
+ default:
+ $stack[] = $token;
+ break;
+ }
+ }
+ return array_pop($stack);
+ }
+
+ public static function calc($expr) {
+ return self::calc_rpn(self::shunting_yard($expr));
+ }
+} \ No newline at end of file