eqEOS

Equation Operating System (EOS) Parser.

Defined (1)

The class is defined in the following location(s).

/deprecated/includes/eos.class.php  
  1. class eqEOS { 
  2. /**#@+ 
  3. *Private variables 
  4. */ 
  5. private $postFix; 
  6. private $inFix; 
  7. /**#@-*/ 
  8. /**#@+ 
  9. * Protected variables 
  10. */ 
  11. //What are opening and closing selectors 
  12. protected $SEP = array('open' => array('(', '['), 'close' => array(')', ']')); 
  13. //Top presedence following operator - not in use 
  14. protected $SGL = array('!'); 
  15. //Order of operations arrays follow 
  16. protected $ST = array('^'); 
  17. protected $ST1 = array('/', '*', '%'); 
  18. protected $ST2 = array('+', '-'); 
  19. //Allowed functions 
  20. protected $FNC = array('sin', 'cos', 'tan', 'csc', 'sec', 'cot'); 
  21. /**#@-*/ 
  22. /** 
  23. * Construct method 
  24. * Will initiate the class. If variable given, will assign to 
  25. * internal variable to solve with this::solveIF() without needing 
  26. * additional input. Initializing with a variable is not suggested. 
  27. * @see eqEOS::solveIF() 
  28. * @param String $inFix Standard format equation 
  29. */ 
  30. public function __construct($inFix = null) { 
  31. $this->inFix = (isset($inFix)) ? $inFix : null; 
  32. $this->postFix = array(); 
  33.  
  34. /** 
  35. * Check Infix for opening closing pair matches. 
  36. * This function is meant to solely check to make sure every opening 
  37. * statement has a matching closing one, and throws an exception if 
  38. * it doesn't. 
  39. * @param String $infix Equation to check 
  40. * @throws Exception if malformed. 
  41. * @return Bool true if passes - throws an exception if not. 
  42. */ 
  43. private function checkInfix($infix) { 
  44. if(trim($infix) == "") { 
  45. throw new Exception("No Equation given", EQEOS_E_NO_EQ); 
  46. return false; 
  47. //Make sure we have the same number of '(' as we do ')' 
  48. // and the same # of '[' as we do ']' 
  49. if(substr_count($infix, '(') != substr_count($infix, ')')) { 
  50. throw new Exception("Mismatched parenthesis in '{$infix}'", EQEOS_E_NO_SET); 
  51. return false; 
  52. } elseif(substr_count($infix, '[') != substr_count($infix, ']')) { 
  53. throw new Exception("Mismatched brackets in '{$infix}'", EQEOS_E_NO_SET); 
  54. return false; 
  55. $this->inFix = $infix; 
  56. return true; 
  57.  
  58. /** 
  59. * Infix to Postfix 
  60. * Converts an infix (standard) equation to postfix (RPN) notation. 
  61. * Sets the internal variable $this->postFix for the eqEOS::solvePF() 
  62. * function to use. 
  63. * @link http://en.wikipedia.org/wiki/Infix_notation Infix Notation 
  64. * @link http://en.wikipedia.org/wiki/Reverse_Polish_notation Reverse Polish Notation 
  65. * @param String $infix A standard notation equation 
  66. * @return Array Fully formed RPN Stack 
  67. */ 
  68. public function in2post($infix = null) { 
  69. // if an equation was not passed, use the one that was passed in the constructor 
  70. $infix = (isset($infix)) ? $infix : $this->inFix; 
  71.  
  72. //check to make sure 'valid' equation 
  73. $this->checkInfix($infix); 
  74. $pf = array(); 
  75. $ops = new phpStack(); 
  76. $vars = new phpStack(); 
  77.  
  78. // remove all white-space 
  79. preg_replace("/\s/", "", $infix); 
  80.  
  81. // Create postfix array index 
  82. $pfIndex = 0; 
  83.  
  84. //what was the last character? (useful for decerning between a sign for negation and subtraction) 
  85. $lChar = ''; 
  86.  
  87. //loop through all the characters and start doing stuff ^^ 
  88. for($i=0;$i<strlen($infix);$i++) { 
  89. // pull out 1 character from the string 
  90. $chr = substr($infix, $i, 1); 
  91.  
  92. // if the character is numerical 
  93. if(preg_match('/[0-9.]/i', $chr)) { 
  94. // if the previous character was not a '-' or a number 
  95. if((!preg_match('/[0-9.]/i', $lChar) && ($lChar != "")) && (@$pf[$pfIndex]!="-")) 
  96. $pfIndex++; // increase the index so as not to overlap anything 
  97. // Add the number character to the array 
  98. @$pf[$pfIndex] .= $chr; 
  99. // If the character opens a set e.g. '(' or '[' 
  100. elseif(in_array($chr, $this->SEP['open'])) { 
  101. // if the last character was a number, place an assumed '*' on the stack 
  102. if(preg_match('/[0-9.]/i', $lChar)) 
  103. $ops->push('*'); 
  104.  
  105. $ops->push($chr); 
  106. // if the character closes a set e.g. ')' or ']' 
  107. elseif(in_array($chr, $this->SEP['close'])) { 
  108. // find what set it was i.e. matches ')' with '(' or ']' with '[' 
  109. $key = array_search($chr, $this->SEP['close']); 
  110. // while the operator on the stack isn't the matching pair...pop it off 
  111. while($ops->peek() != $this->SEP['open'][$key]) { 
  112. $nchr = $ops->pop(); 
  113. if($nchr) 
  114. $pf[++$pfIndex] = $nchr; 
  115. else { 
  116. throw new Exception("Error while searching for '". $this->SEP['open'][$key] ."' in '{$infix}'.", EQEOS_E_NO_SET); 
  117. return false; 
  118. $ops->pop(); 
  119. // If a special operator that has precedence over everything else 
  120. elseif(in_array($chr, $this->ST)) { 
  121. $ops->push($chr); 
  122. $pfIndex++; 
  123. // Any other operator other than '+' and '-' 
  124. elseif(in_array($chr, $this->ST1)) { 
  125. while(in_array($ops->peek(), $this->ST1) || in_array($ops->peek(), $this->ST)) 
  126. $pf[++$pfIndex] = $ops->pop(); 
  127.  
  128. $ops->push($chr); 
  129. $pfIndex++; 
  130. // if a '+' or '-' 
  131. elseif(in_array($chr, $this->ST2)) { 
  132. // if it is a '-' and the character before it was an operator or nothingness (e.g. it negates a number) 
  133. if((in_array($lChar, array_merge($this->ST1, $this->ST2, $this->ST, $this->SEP['open'])) || $lChar=="") && $chr=="-") { 
  134. // increase the index because there is no reason that it shouldn't.. 
  135. $pfIndex++; 
  136. $pf[$pfIndex] = $chr;  
  137. // Otherwise it will function like a normal operator 
  138. else { 
  139. while(in_array($ops->peek(), array_merge($this->ST1, $this->ST2, $this->ST))) 
  140. $pf[++$pfIndex] = $ops->pop(); 
  141. $ops->push($chr); 
  142. $pfIndex++; 
  143. // make sure we record this character to be refered to by the next one 
  144. $lChar = $chr; 
  145. // if there is anything on the stack after we are done...add it to the back of the RPN array 
  146. while(($tmp = $ops->pop()) !== false) 
  147. $pf[++$pfIndex] = $tmp; 
  148.  
  149. // re-index the array at 0 
  150. $pf = array_values($pf); 
  151.  
  152. // set the private variable for later use if needed 
  153. $this->postFix = $pf; 
  154.  
  155. // return the RPN array in case developer wants to use it fro some insane reason (bug testing ;] 
  156. return $pf; 
  157. } //end function in2post 
  158.  
  159. /** 
  160. * Solve Postfix (RPN) 
  161. *  
  162. * This function will solve a RPN array. Default action is to solve 
  163. * the RPN array stored in the class from eqEOS::in2post(), can take 
  164. * an array input to solve as well, though default action is prefered. 
  165. * @link http://en.wikipedia.org/wiki/Reverse_Polish_notation Postix Notation 
  166. * @param Array $pfArray RPN formatted array. Optional. 
  167. * @return Float Result of the operation. 
  168. */ 
  169. public function solvePF($pfArray = null) { 
  170. // if no RPN array is passed - use the one stored in the private var 
  171. $pf = (!is_array($pfArray)) ? $this->postFix : $pfArray; 
  172.  
  173. // create our temporary function variables 
  174. $temp = array(); 
  175. $tot = 0; 
  176. $hold = 0; 
  177.  
  178. // Loop through each number/operator  
  179. for($i=0;$i<count($pf); $i++) { 
  180. // If the string isn't an operator, add it to the temp var as a holding place 
  181. if(!in_array($pf[$i], array_merge($this->ST, $this->ST1, $this->ST2))) { 
  182. $temp[$hold++] = $pf[$i]; 
  183. // ...Otherwise perform the operator on the last two numbers  
  184. else { 
  185. switch ($pf[$i]) { 
  186. case '+': 
  187. @$temp[$hold-2] = $temp[$hold-2] + $temp[$hold-1]; 
  188. break; 
  189. case '-': 
  190. @$temp[$hold-2] = $temp[$hold-2] - $temp[$hold-1]; 
  191. break; 
  192. case '*': 
  193. @$temp[$hold-2] = $temp[$hold-2] * $temp[$hold-1]; 
  194. break; 
  195. case '/': 
  196. if($temp[$hold-1] == 0) { 
  197. throw new Exception("Division by 0 on: '{$temp[$hold-2]} / {$temp[$hold-1]}' in {$this->inFix}", EQEOS_E_DIV_ZERO); 
  198. return false; 
  199. @$temp[$hold-2] = $temp[$hold-2] / $temp[$hold-1]; 
  200. break; 
  201. case '^': 
  202. @$temp[$hold-2] = pow($temp[$hold-2], $temp[$hold-1]); 
  203. break; 
  204. case '%': 
  205. if($temp[$hold-1] == 0) { 
  206. throw new Exception("Division by 0 on: '{$temp[$hold-2]} % {$temp[$hold-1]}' in {$this->inFix}", EQEOS_E_DIV_ZERO); 
  207. return false; 
  208. @$temp[$hold-2] = bcmod($temp[$hold-2], $temp[$hold-1]); 
  209. break; 
  210. // Decrease the hold var to one above where the last number is  
  211. $hold = $hold-1; 
  212. // return the last number in the array  
  213. return $temp[$hold-1]; 
  214.  
  215. } //end function solvePF 
  216.  
  217.  
  218. /** 
  219. * Solve Infix (Standard) Notation Equation 
  220. * Will take a standard equation with optional variables and solve it. Variables 
  221. * must begin with '&' will expand to allow variables to begin with '$' (TODO) 
  222. * The variable array must be in the format of 'variable' => value. If 
  223. * variable array is scalar (ie 5), all variables will be replaced with it. 
  224. * @param String $infix Standard Equation to solve 
  225. * @param String|Array $vArray Variable replacement 
  226. * @return Float Solved equation 
  227. */ 
  228. function solveIF($infix, $vArray = null) { 
  229. $infix = ($infix != "") ? $infix : $this->inFix; 
  230.  
  231. //Check to make sure a 'valid' expression 
  232. $this->checkInfix($infix); 
  233.  
  234. $ops = new phpStack(); 
  235. $vars = new phpStack(); 
  236.  
  237. //remove all white-space 
  238. preg_replace("/\s/", "", $infix); 
  239. if(DEBUG) 
  240. $hand=fopen("eq.txt", "a"); 
  241.  
  242. //Find all the variables that were passed and replaces them 
  243. while((preg_match('/(.) {0, 1}[&$]([a-zA-Z]+)(.) {0, 1}/', $infix, $match)) != 0) { 
  244.  
  245. //remove notices by defining if undefined. 
  246. if(!isset($match[3])) { 
  247. $match[3] = ""; 
  248.  
  249. if(DEBUG) 
  250. fwrite($hand, "{$match[1]} || {$match[3]}\n"); 
  251. // Ensure that the variable has an operator or something of that sort in front and back - if it doesn't, add an implied '*' 
  252. if((!in_array($match[1], array_merge($this->ST, $this->ST1, $this->ST2, $this->SEP['open'])) && $match[1] != "") || is_numeric($match[1])) //$this->SEP['close'] removed 
  253. $front = "*"; 
  254. else 
  255. $front = ""; 
  256.  
  257. if((!in_array($match[3], array_merge($this->ST, $this->ST1, $this->ST2, $this->SEP['close'])) && $match[3] != "") || is_numeric($match[3])) //$this->SEP['open'] removed 
  258. $back = "*"; 
  259. else 
  260. $back = ""; 
  261.  
  262. //Make sure that the variable does have a replacement 
  263. if(!isset($vArray[$match[2]]) && (!is_array($vArray != "") && !is_numeric($vArray))) { 
  264. throw new Exception("Variable replacement does not exist for '". substr($match[0], 1, -1) ."' in {$this->inFix}", EQEOS_E_NO_VAR); 
  265. return false; 
  266. } elseif(!isset($vArray[$match[2]]) && (!is_array($vArray != "") && is_numeric($vArray))) { 
  267. $infix = str_replace($match[0], $match[1] . $front. $vArray. $back . $match[3], $infix); 
  268. } elseif(isset($vArray[$match[2]])) { 
  269. $infix = str_replace($match[0], $match[1] . $front. $vArray[$match[2]]. $back . $match[3], $infix); 
  270.  
  271. if(DEBUG) 
  272. fwrite($hand, "$infix\n"); 
  273.  
  274. // Finds all the 'functions' within the equation and calculates them  
  275. // NOTE - when using function, only 1 set of paranthesis will be found, instead use brackets for sets within functions!!  
  276. while((preg_match("/(". implode("|", $this->FNC) . ")\(([^\)\(]*(\([^\)]*\)[^\(\)]*)*[^\)\(]*)\)/", $infix, $match)) != 0) { 
  277. $func = $this->solveIF($match[2]); 
  278. switch($match[1]) { 
  279. case "cos": 
  280. $ans = cos($func); 
  281. break; 
  282. case "sin": 
  283. $ans = sin($func); 
  284. break; 
  285. case "tan": 
  286. $ans = tan($func); 
  287. break; 
  288. case "sec": 
  289. $tmp = cos($func); 
  290. if($tmp == 0) { 
  291. throw new Exception("Division by 0 on: 'sec({$func}) = 1/cos({$func})' in {$this->inFix}", EQEOS_E_DIV_ZERO); 
  292. return false; 
  293. $ans = 1/$tmp; 
  294. break; 
  295. case "csc": 
  296. $tmp = sin($func); 
  297. if($tmp == 0) { 
  298. throw new Exception("Division by 0 on: 'csc({$func}) = 1/sin({$func})' in {$this->inFix}", EQEOS_E_DIV_ZERO); 
  299. return false; 
  300. $ans = 1/$tmp; 
  301. break; 
  302. case "cot": 
  303. $tmp = tan($func); 
  304. if($tmp == 0) { 
  305. throw new Exception("Division by 0 on: 'cot({$func}) = 1/tan({$func})' in {$this->inFix}", EQEOS_E_DIV_ZERO); 
  306. return false; 
  307. $ans = 1/$tmp; 
  308. break; 
  309. default: 
  310. break; 
  311. $infix = str_replace($match[0], $ans, $infix); 
  312. if(DEBUG) 
  313. fclose($hand); 
  314. return $this->solvePF($this->in2post($infix)); 
  315.  
  316.  
  317. } //end function solveIF 
  318. } //end class 'eqEOS'