summaryrefslogtreecommitdiff
path: root/oracles/calc.php
blob: 833749e3d74d07c49026482b8c3244a7d46d7ad1 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
<?php
include_once("oracles/base.php");
class calculator extends oracle {
	public $info = [
		"name" => "calculator"
	];
	public function check_query($q) {
		// straight numerics should go to that oracle
		if (is_numeric($q)) {
			return false;
		}
		// all chars should be number-y or operator-y
		$char_whitelist = str_split("1234567890.+-/*^%() ");
		foreach (str_split($q) as $char) {
			if (!in_array($char, $char_whitelist)) {
				return false;
			}
		}
		return true;
	}
	// a custom parser and calculator because FUCK YUO, libraries are
	//  gay.
	public function generate_response($q)
	{
		$nums = str_split("1234567890.");
		$ops = str_split("+-/*^%;");
		$grouping = str_split("()");

		$q = str_replace(" ", "", $q);

		// backstop for the parser so it catches the last
		//  numeric token
		$q .= ";"; 

		// the following comments refer to this example input:
		//  21+9*(3+2^9)+1

		// 2-length lists of the following patterns:
		//  ["n" (umeric), <some number>]
		//  ["o" (perator), "<some operator>"]
		//  ["g" (roup explicit), <"(" or ")">]
		// e.g. [["n", 21], ["o", "+"], ["n", 9], ["o", *],
		//       ["g", "("], ["n", 3], ["o", "+"], ["n", 2],
		//       ["o", "^"], ["n", 9], ["g", ")"], ["o", "+"],
		//       ["n", "1"]]
		$tokens = array();
		$dragline = 0;
		foreach(str_split($q) as $i=>$char) {
			if (in_array($char, $nums)) {
				continue;
			}
			elseif (in_array($char, $ops) || in_array($char, $grouping)) {
				// hitting a non-numeric implies everything since the
				//  last hit has been part of a number
				$capture = substr($q, $dragline, $i - $dragline);
				// prevent the int cast from creating imaginary
				//  ["n", 0] tokens
				if ($capture != "") {
					if (substr_count($capture, ".") > 1) {
						return "";
					}
					array_push($tokens, ["n", (float)$capture]);
				}
				// reset to one past the current (non-numeric) char
				$dragline = $i + 1; 
				// the `;' backstop is not a real token and this should
				//  never be present in the token list
				if ($char != ";") {
					array_push($tokens, [
						($char == "(" || $char == ")") ? "g" : "o",
						$char
					]);
				}
			}
			else {
				return "";
			}
		}

		// two operators back to back should fail
		for ($i = 1; $i < count($tokens); $i++) {
			if ($tokens[$i][0] == "o" && $tokens[$i-1][0] == "o") {
				return "";
			}
		}

		// no implicit multiplication
		for ($i = 0; $i < count($tokens) - 1; $i++) {
			if ($tokens[$i][0] == "n" && $tokens[$i+1] == ["g", "("]) {
				return "";
			}
		}

		//strategy:
		// traverse to group open (if there is one)
		//  - return to start with the internals
		// traverse to ^, attack token previous and after
		// same but for *, then / then + then -
		// poppers all teh way down
		try {
			return [
				substr($q, 0, strlen($q)-1)." = " => $this->executeBlock($tokens)[0][1]
			];
		}   
		catch (\Throwable $e) {
			if (get_class($e) == "DivisionByZeroError") {
				return [
					$q." = " => "Division by Zero Error!!"
				];
			}
			return "";
		}
	}
	public function executeBlock($tokens) {
		if (count($tokens) >= 2 && $tokens[0][0] == "o" && $tokens[0][1] == "-" && $tokens[1][0] == "n") {
			array_splice($tokens, 0, 2, [["n", -1 * (float)$tokens[1][1]]]);
		}
		if (count($tokens) > 0 && $tokens[0][0] == "o" || $tokens[count($tokens)-1][0] == "o") {
			throw new Exception("Error Processing Request", 1);
		}
		while (in_array(["g", "("], $tokens)) {
			$first_open = array_search(["g", "("], $tokens);
			$enclosedality = 1;
			for ($i = $first_open+1; $i < count($tokens); $i++) {
				if ($tokens[$i][0] == "g") {
					$enclosedality += ($tokens[$i][1] == "(") ? 1 : -1;
				}
				if ($enclosedality == 0) {
					array_splice($tokens, 
						$first_open, 
						$i+1 - $first_open, 
						$this->executeBlock(
							array_slice($tokens, $first_open+1, $i-1 - $first_open)
						)
					);
					break;
				}
			}
		}
		$operators_in_pemdas_order = [
			"^" => (fn($x, $y) => $x ** $y),
			"*" => (fn($x, $y) => $x * $y),
			"/" => (fn($x, $y) => $x / $y), 
			"%" => (fn($x, $y) => $x % $y),
			"+" => (fn($x, $y) => $x + $y), 
			"-" => (fn($x, $y) => $x - $y)
		];
		foreach ($operators_in_pemdas_order as $op=>$func) {
			while (in_array(["o", $op], $tokens)) {
				for ($i = 0; $i < count($tokens); $i++) {
					if ($tokens[$i] == ["o", $op]) {
						array_splice(
							$tokens,
							$i-1,
							3,
							[["n", (string)($func((float)$tokens[$i-1][1], (float)$tokens[$i+1][1]))]]
						);
					}
				}
			}
		}
		return $tokens;
	}
}
?>