fork download
  1. <?php
  2.  
  3. class Parser
  4. {
  5. const NUM = 'd';
  6. private $tokenizer;
  7. private $error;
  8. private $result;
  9.  
  10. function __construct($tokenizer)
  11. {
  12. $this->tokenizer = $tokenizer;
  13. $this->process();
  14. }
  15.  
  16. private function parseNumb()
  17. {
  18. return new NumNode($this->tokenizer->getToken(self::NUM));
  19. }
  20.  
  21. private function parseBracketsOrNumber()
  22. {
  23. if ($this->tokenizer->isOneOf("(")) {
  24. $this->tokenizer->getToken("(");
  25. $result = $this->parseSum();
  26. $this->tokenizer->getToken(")");
  27. return $result;
  28. } else {
  29. return $this->parseNumb();
  30. }
  31. }
  32.  
  33. private function parsePow()
  34. {
  35. $first = $this->parseBracketsOrNumber();
  36. $result = new PowNode($first);
  37. while ($this->tokenizer->isOneOf("^")) {
  38. $this->tokenizer->getToken("^");
  39. $next = $this->parseBracketsOrNumber();
  40. $result->append($next);
  41. }
  42. return $result;
  43. }
  44.  
  45. private function parseNegative(){
  46. return new NegativeNode($this->parsePow());
  47. }
  48.  
  49. private function ParseMult()
  50. {
  51. if($this->tokenizer->isOneOf("-")){
  52. $this->tokenizer->getToken("-");
  53. $first = $this->parseNegative();
  54. } else {
  55. $first = $this->parsePow();
  56. }
  57. $result = new MultNode($first);
  58. while ($this->tokenizer->isOneOf("*", "/")) {
  59. $op = $this->tokenizer->getToken("*", "/");
  60. $next = $this->parsePow();
  61. $result->append($op, $next);
  62. }
  63. return $result;
  64. }
  65.  
  66. private function parseSum()
  67. {
  68. $first = $this->parseMult();
  69. $result = new SumNode($first);
  70. while ($this->tokenizer->isOneOf("+", "-")) {
  71. $op = $this->tokenizer->getToken("+", "-");
  72. $next = $this->parseMult();
  73. $result->append($op, $next);
  74. }
  75. return $result;
  76. }
  77.  
  78. private function process()
  79. {
  80. try {
  81. $this->result = $this->parseSum()->calc();
  82. }
  83. catch (Exception $e) {
  84. $this->error = $e->getMessage();
  85. }
  86. }
  87.  
  88. function isError()
  89. {
  90. if (!empty($this->error)) {
  91. return true;
  92. } else {
  93. return false;
  94. }
  95. }
  96.  
  97. function getError()
  98. {
  99. return $this->error;
  100. }
  101.  
  102. function getResult()
  103. {
  104. return $this->result;
  105. }
  106. }
  107.  
  108.  
  109. class Tokenazer
  110. {
  111. private $tokens = array();
  112. private $index = 0;
  113.  
  114. function __construct($text)
  115. {
  116. $matches = array();
  117. preg_match_all('/(\\d[.]\\d+)|\\d+|\\+|\\*|\\/|-|\\(|\\)|\\^/', $text, $matches);
  118. foreach ($matches[0] as $match) {
  119. $this->tokens[] = $match;
  120. }
  121. }
  122.  
  123. function getToken()
  124. {
  125. $result = "";
  126. $token = $this->getNextToken();
  127. foreach (func_get_args() as $arg) {
  128. if($this->ifMatch($token, $arg)){
  129. $result=$token;
  130. }
  131. }
  132. if (empty($result)) {
  133. throw new Exception("токен не соотв. типу");
  134. } else {
  135. return $result;
  136. }
  137. }
  138.  
  139. private function ifMatch($token, $type){
  140. return (preg_match("/\\{$type}/", $token)) ? (true) : (false);
  141. }
  142.  
  143. private function getNextToken()
  144. {
  145. $index = $this->index;
  146. $this->index++;
  147. if (isset($this->tokens[$index])) {
  148. return $this->tokens[$index];
  149. }
  150. }
  151.  
  152. function isOneOf(){
  153. $resultBool=false;
  154. $args=func_get_args();
  155. foreach($args as $arg){
  156. if(isset($this->tokens[$this->index])){
  157. if($this->tokens[$this->index] == $arg){
  158. $resultBool=true;
  159. }
  160. }
  161. }
  162. return $resultBool;
  163. }
  164.  
  165. }
  166.  
  167. abstract class abstractNode
  168. {
  169. abstract function calc();
  170. }
  171.  
  172. class NumNode extends abstractNode
  173. {
  174. protected $left;
  175.  
  176. function __construct($left)
  177. {
  178. $this->left = $left;
  179. }
  180.  
  181. function calc()
  182. {
  183. return $this->left;
  184. }
  185. }
  186.  
  187. class NegativeNode extends NumNode
  188. {
  189. function calc()
  190. {
  191. return (0 - $this->left->calc());
  192. }
  193. }
  194.  
  195. class PowNode extends NumNode
  196. {
  197. protected $childrens = array();
  198.  
  199. function append($right)
  200. {
  201. $this->childrens[] = $right;
  202. }
  203.  
  204. function calc()
  205. {
  206. $result = $this->left->calc();
  207. if (!empty($this->childrens)) {
  208. array_unshift($this->childrens, $this->left);
  209. while (count($this->childrens) != 1) {
  210. $degree = array_pop($this->childrens)->calc();
  211. $numb = array_pop($this->childrens)->calc();
  212. $this->childrens[] = new NumNode(pow($numb, $degree));
  213. }
  214. $result = array_pop($this->childrens)->calc();
  215. }
  216. return $result;
  217. }
  218. }
  219.  
  220. class MultNode extends NumNode
  221. {
  222. protected $childrens = array();
  223.  
  224. function append($op, $right)
  225. {
  226. $temp = array();
  227. $temp['op'] = $op;
  228. $temp['node'] = $right;
  229. $this->childrens[] = $temp;
  230. }
  231.  
  232. function calc()
  233. {
  234. $result = $this->left->calc();
  235. if (!empty($this->left)) {
  236. foreach ($this->childrens as $child) {
  237. if ($child['op'] == "*") {
  238. $result *= $child['node']->calc();
  239. } else {
  240. $result /= $child['node']->calc();
  241. }
  242. }
  243. }
  244. return $result;
  245. }
  246. }
  247.  
  248. class SumNode extends MultNode
  249. {
  250.  
  251. function calc()
  252. {
  253. $result = $this->left->calc();
  254. if (!empty($this->left)) {
  255. foreach ($this->childrens as $child) {
  256. if ($child['op'] == "+") {
  257. $result += $child['node']->calc();
  258. } else {
  259. $result -= $child['node']->calc();
  260. }
  261. }
  262. }
  263. return $result;
  264. }
  265. }
  266.  
  267. //все как у людей блеять
  268. class UnitTest
  269. {
  270. private function test($answer, $string)
  271. {
  272. $tokenazer = new Tokenazer($string);
  273. $parser = new Parser($tokenazer);
  274. if ($parser->isError()) {
  275. $answerFromCode = $parser->getError();
  276. } else {
  277. $answerFromCode = $parser->getResult();
  278. }
  279. if ($answerFromCode == $answer) {
  280. echo "✓ OK $string$answerFromCode\n";
  281. } else {
  282. echo "✗ ошибка $string должно быть равно «{$answer}», но калькулятор вернул «{$answerFromCode}»\n";
  283. }
  284. }
  285.  
  286. public function run()
  287. {
  288. $this->test(5, "2+3");
  289. $this->test(20, "4*5");
  290. $this->test(6561, "3^2^3");
  291. $this->test("токен не соотв. типу", "3^2^+3");
  292. $this->test(22, "2 + 4*5");
  293. $this->test(301, "(2 + 4) * 5 + 6 * 45 + 1");
  294. $this->test(2, "-3+5");
  295. $this->test(5, "-(-(2 + 3))");
  296. }
  297. }
  298.  
  299. $test = new UnitTest();
  300. $test->run();
  301. ?>
Success #stdin #stdout 0.01s 20520KB
stdin
Standard input is empty
stdout
✓ OK         2+3 → 5
✓ OK         4*5 → 20
✓ OK         3^2^3 → 6561
✓ OK         3^2^+3 → токен не соотв. типу
✓ OK         2 + 4*5 → 22
✓ OK         (2 + 4) * 5 + 6 * 45 + 1 → 301
✓ OK         -3+5 → 2
✓ OK         -(-(2 + 3)) → 5