/expression.php

https://github.com/dburkart/scurvy · PHP · 383 lines · 274 code · 50 blank · 59 comment · 64 complexity · 8b0976245e70b98d507f9ce10a1e34b7 MD5 · raw file

  1. <?php
  2. /*
  3. * expression.php
  4. *
  5. * Copyright 2010-2013 Dana Burkart <danaburkart@gmail.com>
  6. *
  7. */
  8. /**
  9. * An expression is a statement that can be evaluated to some answer. It can be
  10. * an equation comparing two statements, some kind of mathematical statement, or
  11. * whatever. Currently, these are operators accounted for:
  12. * + : of the form a + b
  13. * - : of the form a - b
  14. * * : of the form a * b
  15. * = : of the form a = b
  16. * != : of the form a != b
  17. * > : of the form a > b
  18. * < : of the form a < b
  19. * >= : of the form a >= b
  20. * <= : of the form a <= b
  21. * ! : of the form !a
  22. * () : can encapsulate any sub-expression to change order of operations.
  23. *
  24. * Valid values for a and b are:
  25. * - any statement
  26. * - any numeral
  27. * - any string (eclosed by single-quotes)
  28. * - any variable satisfying this regex: /^[a-zA-Z_][a-zA-Z0-9_-]*$/
  29. *
  30. * @author Dana Burkart
  31. */
  32. // Some defines that will be useful
  33. define("EXPR_VAR", 20); // Variable
  34. define("EXPR_POS", 2); // Addition
  35. define("EXPR_NEG", 3); // Subtraction
  36. define("EXPR_MUL", 4); // Multiplication
  37. define("EXPR_DIV", 5); // Division
  38. define("EXPR_MOD", 6); // Modulo
  39. define("EXPR_EQU", -1); // Equality test
  40. define("EXPR_NEQ", -2); // Non-equality test
  41. define("EXPR_LES", -3); // Less-than test
  42. define("EXPR_GRE", -4); // Greater-than test
  43. define("EXPR_LEQ", -5); // Less-than-or-equal test
  44. define("EXPR_GEQ", -6); // Greater-than-or-equal test
  45. define("EXPR_NOT", 13); // Not operator
  46. define("EXPR_PAR", 14); // Parenthesis
  47. define("EXPR_AND", 7); // AND operator
  48. define("EXPR_OR" , 8); // OR operator
  49. // An atom is any entity that can't be broken down further. Operators, variables,
  50. // numbers, and strings are all atoms.
  51. class Atom {
  52. public $type;
  53. public $val;
  54. public $width;
  55. public function __construct($type, $val = '') {
  56. $this->type = $type;
  57. $this->val = $val;
  58. $this->width = 1;
  59. if ($this->type == EXPR_NEQ || $this->type == EXPR_LEQ ||
  60. $this->type == EXPR_GEQ) {
  61. $this->width = 2;
  62. }
  63. }
  64. }
  65. class Expression {
  66. private $expression = '';
  67. private $atomList = array();
  68. private $eval = null;
  69. public function __construct($expr) {
  70. $this->expression = $expr;
  71. $this->atomList = $this->decompose($expr);
  72. }
  73. /**
  74. * Evaluates the decomposed expression contained in $this->atomList.
  75. *
  76. * @param registry an associative array containing relevant variables
  77. * @return the result of the evaluation.
  78. */
  79. public function evaluate(&$registry=null, $recurse=false) {
  80. // Back ourself up; this expression may need to be called more than
  81. // once.
  82. if ( !$recurse ) {
  83. $this->eval = null;
  84. $this->atomListBackup = $this->atomList;
  85. }
  86. // Get the next atom
  87. $next = array_pop($this->atomList);
  88. if (is_null($this->eval) || $recurse) {
  89. switch($next->type) {
  90. case EXPR_VAR:
  91. if (is_numeric($next->val)) {
  92. $this->eval = $next->val;
  93. if ($recurse)
  94. return $this->eval;
  95. } else if (preg_match('/\'([a-zA-Z0-9_-]*)\'/', $next->val, $matches)) {
  96. $this->eval = $matches[1];
  97. if ($recurse)
  98. return $this->eval;
  99. } else if ($next->val == 'true' || $next->val == 'false') {
  100. $this->eval = (bool)$next->val;
  101. if ($recurse)
  102. return $this->eval;
  103. } else if (isset($registry[$next->val])) {
  104. $this->eval = $registry[$next->val];
  105. if ($recurse)
  106. return $this->eval;
  107. } else {
  108. $this->eval = 0;
  109. if ($recurse)
  110. return $this->eval;
  111. }
  112. break;
  113. case EXPR_NOT:
  114. $this->eval = !($this->evaluate($registry, true));
  115. if ($recurse)
  116. return $this->eval;
  117. break;
  118. case EXPR_MUL:
  119. $b = $this->evaluate($registry, true);
  120. $this->eval = $this->evaluate($registry, true) * $b;
  121. if ($recurse)
  122. return $this->eval;
  123. break;
  124. case EXPR_DIV:
  125. $b = $this->evaluate($registry, true);
  126. $this->eval = (int)floor($this->evaluate($registry, true) / $b);
  127. if ($recurse)
  128. return $this->eval;
  129. break;
  130. case EXPR_MOD:
  131. $b = $this->evaluate($registry, true);
  132. $this->eval = $this->evaluate($registry, true) % $b;
  133. if ($recurse)
  134. return $this->eval;
  135. break;
  136. case EXPR_POS:
  137. $this->eval = $this->evaluate($registry, true) + $this->evaluate($registry, true);
  138. if ($recurse)
  139. return $this->eval;
  140. break;
  141. case EXPR_NEG:
  142. $b = $this->evaluate($registry, true);
  143. $this->eval = $this->evaluate($registry, true) - $b;
  144. if ($recurse)
  145. return $this->eval;
  146. break;
  147. case EXPR_AND:
  148. $b = $this->evaluate($registry, true);
  149. $this->eval = ($this->evaluate($registry, true) && $b);
  150. if ($recurse)
  151. return $this->eval;
  152. break;
  153. case EXPR_OR:
  154. $b = $this->evaluate($registry, true);
  155. $this->eval = ($this->evaluate($registry, true) || $b);
  156. if ($recurse)
  157. return $this->eval;
  158. break;
  159. case EXPR_EQU:
  160. $this->eval = ($this->evaluate($registry, true) == $this->evaluate($registry, true));
  161. break;
  162. case EXPR_NEQ:
  163. $this->eval = ($this->evaluate($registry, true) != $this->evaluate($registry, true));
  164. break;
  165. case EXPR_LES:
  166. $this->eval = ($this->evaluate($registry, true) > $this->evaluate($registry, true));
  167. break;
  168. case EXPR_GRE:
  169. $this->eval = ($this->evaluate($registry, true) < $this->evaluate($registry, true));
  170. break;
  171. case EXPR_LEQ:
  172. $this->eval = ($this->evaluate($registry, true) >= $this->evaluate($registry, true));
  173. break;
  174. case EXPR_GEQ:
  175. $this->eval = ($this->evaluate($registry, true) <= $this->evaluate($registry, true));
  176. break;
  177. }
  178. // Restore our stack
  179. $this->atomList = $this->atomListBackup;
  180. return $this->eval;
  181. } else {
  182. return $this->eval;
  183. }
  184. }
  185. /**
  186. * decompose() checks for an equal sign, and if found, decomposes each side
  187. * using decomposeE(), and then pushes the equality operator onto the stack.
  188. * If there is no equal sign, then decomposeE() is called on the entire
  189. * expression.
  190. *
  191. * @param expr the expression to decompose
  192. */
  193. public function decompose($expr) {
  194. $buffer = '';
  195. $atomList = array();
  196. $stack = array();
  197. $count = strlen($expr);
  198. $i = 0;
  199. while ($i < $count) {
  200. switch ($expr[$i]) {
  201. case '<':
  202. $this->pushVar($buffer, $atomList);
  203. $a = EXPR_LES;
  204. if ($expr[$i+1] == '=') {
  205. $a = EXPR_LEQ;
  206. $i += 1;
  207. }
  208. $this->addOperator( $atomList, $stack, $a );
  209. break;
  210. case '>':
  211. $this->pushVar($buffer, $atomList);
  212. $a = EXPR_GRE;
  213. if ($expr[$i+1] == '=') {
  214. $a = EXPR_GEQ;
  215. $i += 1;
  216. }
  217. $this->addOperator( $atomList, $stack, $a );
  218. break;
  219. case '!':
  220. $this->pushVar($buffer, $atomList);
  221. $a = EXPR_NOT;
  222. if ($expr[$i+1] == '=') {
  223. $a = EXPR_NEQ;
  224. $i += 1;
  225. }
  226. $this->addOperator( $atomList, $stack, $a );
  227. break;
  228. case '&':
  229. $this->pushVar($buffer, $atomList);
  230. $a = EXPR_AND;
  231. if ($expr[$i+1] == '&') {
  232. $i += 1;
  233. }
  234. $this->addOperator( $atomList, $stack, $a );
  235. break;
  236. case '|':
  237. $this->pushVar($buffer, $atomList);
  238. $a = EXPR_OR;
  239. if ($expr[$i+1] == '|') {
  240. $i += 1;
  241. }
  242. $this->addOperator( $atomList, $stack, $a );
  243. break;
  244. case ')':
  245. $this->pushVar($buffer, $atomList);
  246. $top = array_pop($stack);
  247. while($top->type != EXPR_PAR && $top != NULL) {
  248. $atomList[] = $top;
  249. $top = array_pop($stack);
  250. }
  251. break;
  252. case '(':
  253. $this->pushVar($buffer, $atomList);
  254. array_push($stack, new Atom(EXPR_PAR));
  255. break;
  256. case '=':
  257. $this->pushVar($buffer, $atomList);
  258. $this->addOperator( $atomList, $stack, EXPR_EQU );
  259. break;
  260. case '*':
  261. $this->pushVar($buffer, $atomList);
  262. $this->addOperator( $atomList, $stack, EXPR_MUL );
  263. break;
  264. case '/':
  265. $this->pushVar($buffer, $atomList);
  266. $this->addOperator( $atomList, $stack, EXPR_DIV );
  267. break;
  268. case '%':
  269. $this->pushVar($buffer, $atomList);
  270. $this->addOperator( $atomList, $stack, EXPR_MOD );
  271. break;
  272. case '+':
  273. $this->pushVar($buffer, $atomList);
  274. $this->addOperator( $atomList, $stack, EXPR_POS );
  275. break;
  276. case '-':
  277. $this->pushVar($buffer, $atomList);
  278. $this->addOperator( $atomList, $stack, EXPR_NEG );
  279. break;
  280. default:
  281. $buffer = $buffer . $expr[$i];
  282. break;
  283. }
  284. $i++;
  285. }
  286. $this->pushVar($buffer, $atomList);
  287. while (($top = array_pop($stack)) == true) {
  288. $atomList[] = $top;
  289. }
  290. $this->atomList = $atomList;
  291. return $this->atomList;
  292. }
  293. /**
  294. * Creates a new variable atom, subsequent to validating that the string
  295. * is correct for a variable
  296. *
  297. * @param str the name of the variable
  298. * @return the newly created atom
  299. */
  300. public function newVar($str) {
  301. $str = trim($str);
  302. // Try to make it a variable
  303. if (preg_match('/^[a-zA-Z0-9\'_][a-zA-Z0-9\'_-]*$/', $str))
  304. return new Atom(EXPR_VAR, $str);
  305. else {
  306. return false;
  307. }
  308. }
  309. public function pushVar(&$str, &$atomList) {
  310. $str = trim($str);
  311. if (strlen($str) > 0) {
  312. $atomList[] = $this->newVar($str);
  313. $str = '';
  314. }
  315. }
  316. public function getExpressionId() {
  317. return preg_replace('/\s/', '', $this->expression);
  318. }
  319. public function getExpression() {
  320. return $this->expression;
  321. }
  322. private function addOperator(&$list, &$stack, $atom) {
  323. while (($top = array_pop($stack)) == true) {
  324. if ($atom <= $top->type && $top->type != EXPR_PAR)
  325. $list[] = $top;
  326. else {
  327. array_push($stack, $top);
  328. break;
  329. }
  330. }
  331. array_push($stack, new Atom($atom));
  332. }
  333. }