calc.php 4.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165
  1. <?php
  2. include_once("oracles/base.php");
  3. class calculator extends oracle {
  4. public $info = [
  5. "name" => "calculator"
  6. ];
  7. public function check_query($q) {
  8. // straight numerics should go to that oracle
  9. if (is_numeric($q)) {
  10. return false;
  11. }
  12. // all chars should be number-y or operator-y
  13. $char_whitelist = str_split("1234567890.+-/*^%() ");
  14. foreach (str_split($q) as $char) {
  15. if (!in_array($char, $char_whitelist)) {
  16. return false;
  17. }
  18. }
  19. return true;
  20. }
  21. // a custom parser and calculator because FUCK YUO, libraries are
  22. // gay.
  23. public function generate_response($q)
  24. {
  25. $nums = str_split("1234567890.");
  26. $ops = str_split("+-/*^%;");
  27. $grouping = str_split("()");
  28. $q = str_replace(" ", "", $q);
  29. // backstop for the parser so it catches the last
  30. // numeric token
  31. $q .= ";";
  32. // the following comments refer to this example input:
  33. // 21+9*(3+2^9)+1
  34. // 2-length lists of the following patterns:
  35. // ["n" (umeric), <some number>]
  36. // ["o" (perator), "<some operator>"]
  37. // ["g" (roup explicit), <"(" or ")">]
  38. // e.g. [["n", 21], ["o", "+"], ["n", 9], ["o", *],
  39. // ["g", "("], ["n", 3], ["o", "+"], ["n", 2],
  40. // ["o", "^"], ["n", 9], ["g", ")"], ["o", "+"],
  41. // ["n", "1"]]
  42. $tokens = array();
  43. $dragline = 0;
  44. foreach(str_split($q) as $i=>$char) {
  45. if (in_array($char, $nums)) {
  46. continue;
  47. }
  48. elseif (in_array($char, $ops) || in_array($char, $grouping)) {
  49. // hitting a non-numeric implies everything since the
  50. // last hit has been part of a number
  51. $capture = substr($q, $dragline, $i - $dragline);
  52. // prevent the int cast from creating imaginary
  53. // ["n", 0] tokens
  54. if ($capture != "") {
  55. if (substr_count($capture, ".") > 1) {
  56. return "";
  57. }
  58. array_push($tokens, ["n", (float)$capture]);
  59. }
  60. // reset to one past the current (non-numeric) char
  61. $dragline = $i + 1;
  62. // the `;' backstop is not a real token and this should
  63. // never be present in the token list
  64. if ($char != ";") {
  65. array_push($tokens, [
  66. ($char == "(" || $char == ")") ? "g" : "o",
  67. $char
  68. ]);
  69. }
  70. }
  71. else {
  72. return "";
  73. }
  74. }
  75. // two operators back to back should fail
  76. for ($i = 1; $i < count($tokens); $i++) {
  77. if ($tokens[$i][0] == "o" && $tokens[$i-1][0] == "o") {
  78. return "";
  79. }
  80. }
  81. // no implicit multiplication
  82. for ($i = 0; $i < count($tokens) - 1; $i++) {
  83. if ($tokens[$i][0] == "n" && $tokens[$i+1] == ["g", "("]) {
  84. return "";
  85. }
  86. }
  87. //strategy:
  88. // traverse to group open (if there is one)
  89. // - return to start with the internals
  90. // traverse to ^, attack token previous and after
  91. // same but for *, then / then + then -
  92. // poppers all teh way down
  93. try {
  94. return [
  95. substr($q, 0, strlen($q)-1)." = " => $this->executeBlock($tokens)[0][1]
  96. ];
  97. }
  98. catch (\Throwable $e) {
  99. if (get_class($e) == "DivisionByZeroError") {
  100. return [
  101. $q." = " => "Division by Zero Error!!"
  102. ];
  103. }
  104. return "";
  105. }
  106. }
  107. public function executeBlock($tokens) {
  108. if (count($tokens) >= 2 && $tokens[0][0] == "o" && $tokens[0][1] == "-" && $tokens[1][0] == "n") {
  109. array_splice($tokens, 0, 2, [["n", -1 * (float)$tokens[1][1]]]);
  110. }
  111. if (count($tokens) > 0 && $tokens[0][0] == "o" || $tokens[count($tokens)-1][0] == "o") {
  112. throw new Exception("Error Processing Request", 1);
  113. }
  114. while (in_array(["g", "("], $tokens)) {
  115. $first_open = array_search(["g", "("], $tokens);
  116. $enclosedality = 1;
  117. for ($i = $first_open+1; $i < count($tokens); $i++) {
  118. if ($tokens[$i][0] == "g") {
  119. $enclosedality += ($tokens[$i][1] == "(") ? 1 : -1;
  120. }
  121. if ($enclosedality == 0) {
  122. array_splice($tokens,
  123. $first_open,
  124. $i+1 - $first_open,
  125. $this->executeBlock(
  126. array_slice($tokens, $first_open+1, $i-1 - $first_open)
  127. )
  128. );
  129. break;
  130. }
  131. }
  132. }
  133. $operators_in_pemdas_order = [
  134. "^" => (fn($x, $y) => $x ** $y),
  135. "*" => (fn($x, $y) => $x * $y),
  136. "/" => (fn($x, $y) => $x / $y),
  137. "%" => (fn($x, $y) => $x % $y),
  138. "+" => (fn($x, $y) => $x + $y),
  139. "-" => (fn($x, $y) => $x - $y)
  140. ];
  141. foreach ($operators_in_pemdas_order as $op=>$func) {
  142. while (in_array(["o", $op], $tokens)) {
  143. for ($i = 0; $i < count($tokens); $i++) {
  144. if ($tokens[$i] == ["o", $op]) {
  145. array_splice(
  146. $tokens,
  147. $i-1,
  148. 3,
  149. [["n", (string)($func((float)$tokens[$i-1][1], (float)$tokens[$i+1][1]))]]
  150. );
  151. }
  152. }
  153. }
  154. }
  155. return $tokens;
  156. }
  157. }
  158. ?>