1<?php 2/** 3 * RRDGraph Plugin: Simple RPN parser for rules. 4 * 5 * @author Daniel Goß <developer@flashsystems.de> 6 * @license MIT 7 */ 8 9/** 10 * Simple RPN parser used for rrdgraph rules. 11 * @author dgoss 12 * 13 */ 14class RPNComputer { 15 /** 16 * Contains the constants that are passed to addConst. 17 * @var Array 18 */ 19 private $constants = array (); 20 21 /** 22 * Defines a constant that can be used within an RPN expression. 23 * @param String $name Name of the constant. 24 * @param Multi $value The value to use. 25 * @throws Exception 26 */ 27 public function addConst($name, $value) { 28 $name = strtolower(trim($name)); 29 30 if (strspn($name, "abcdefghijklmnopqrstuvwxyz_.") != strlen($name)) throw new Exception("Invalid variable name"); 31 32 if ($value === null) 33 unset($this->constants[$name]); 34 else 35 $this->constants[$name] = $value; 36 } 37 38 /** 39 * Checks if the given Value contains only numbers and optionally a sign. 40 * @param String $v The value to check. 41 * @return boolean Returns true if the string only contains numbers. 42 */ 43 private function is_integer($v) { 44 return (strspn($v, "0123456789-") == strlen($v)); 45 } 46 47 /** 48 * Compares any PHP variable in a sensfull manner. 49 * If a string contains only digits, it is compared as a number. If it contains anlything else, it is compared as a string. 50 * @param Multi $a The first value. 51 * @param Multi $b The second value. 52 * @return Returns 0 if $a and $b are equal. -1 if $a is less than $b and 1 if $a is more than $b. 53 */ 54 private function compare($a, $b) { 55 //-- Comapring anything to null return false 56 if (is_null($a)) return false; 57 if (is_null($b)) return false; 58 59 //-- Convert boolean values into integer values 0 and 1 60 if (is_bool($a)) $a = $a?1:0; 61 if (is_bool($b)) $b = $b?1:0; 62 63 //-- If both values are numeric, their content is compared 64 if (ctype_digit($a) && ctype_digit($b)) { 65 //-- Convert a and b to integer or float and then compare them. 66 $a = $this->is_integer($a)?intval($a):floatval($a); 67 $b = $this->is_integer($b)?intval($b):floatval($b); 68 69 if ($a < $b) 70 return - 1; 71 else if ($a > $b) 72 return 1; 73 else 74 return 0; 75 } else { 76 return strcasecmp(strval($a), strval($b)); 77 } 78 } 79 80 /** 81 * Processes a RPN expression in rrdtool style. The only supported operators are |, &, >, <, = 82 * @param String $expression RPN expression. 83 * @throws Exception An exception is thrown if the RPN expression could not be parsed. 84 * @return mixed Returns the result of the RPN computation. 85 */ 86 public function compute($expression) { 87 $stack = array (); 88 89 foreach (explode(",", $expression) as $part) { 90 switch (trim($part)) { 91 case '|' : 92 if (count($stack) < 2) throw new Exception("RPN stack underflow"); //FIXME: Position 93 94 95 $b = array_pop($stack); 96 $a = array_pop($stack); 97 $r = ($a || $b); 98 array_push($stack, $r); 99 break; 100 101 case '&' : 102 if (count($stack) < 2) throw new Exception("RPN stack underflow"); //FIXME: Position 103 104 105 $b = array_pop($stack); 106 $a = array_pop($stack); 107 $r = ($a && $b); 108 array_push($stack, $r); 109 break; 110 111 case '>' : 112 if (count($stack) < 2) throw new Exception("RPN stack underflow"); //FIXME: Position 113 114 115 $b = array_pop($stack); 116 $a = array_pop($stack); 117 $r = ($this->compare($a, $b) > 0); 118 array_push($stack, $r); 119 break; 120 121 case '<' : 122 if (count($stack) < 2) throw new Exception("RPN stack underflow"); //FIXME: Position 123 124 125 $b = array_pop($stack); 126 $a = array_pop($stack); 127 $r = ($this->compare($a, $b) < 0); 128 129 array_push($stack, $r); 130 break; 131 132 case '=' : 133 if (count($stack) < 2) throw new Exception("RPN stack underflow"); //FIXME: Position 134 135 136 $b = array_pop($stack); 137 $a = array_pop($stack); 138 $r = ($this->compare($a, $b) == 0); 139 140 array_push($stack, $r); 141 break; 142 143 //-- Variable or Value 144 default : 145 $v = strtolower(trim($part)); 146 147 if (array_key_exists($v, $this->constants)) { 148 array_push($stack, $this->constants[$v]); 149 } else { 150 array_push($stack, $v); 151 } 152 } 153 } 154 155 if (count($stack) > 1) throw new Exception("Unused parameters on RPN stack."); 156 157 return array_pop($stack); 158 } 159}