<?php
 
class Parser
{
    const NUM = 'd';
    private $tokenizer;
    private $error;
    private $result;
    
    function __construct($tokenizer)
    {
        $this->tokenizer = $tokenizer;
        $this->process();
    }
    
    private function parseNumb()
    {
        return new NumNode($this->tokenizer->getToken(self::NUM));
    }
    
    private function parseBracketsOrNumber()
    {
        if ($this->tokenizer->isOneOf("(")) {
            $this->tokenizer->getToken("(");
            $result = $this->parseSum();
            $this->tokenizer->getToken(")");
            return $result;
        } else {
            return $this->parseNumb();
        }
    }
    
    private function parsePow()
    {
        $first  = $this->parseBracketsOrNumber();
        $result = new PowNode($first);
        while ($this->tokenizer->isOneOf("^")) {
            $this->tokenizer->getToken("^");
            $next = $this->parseBracketsOrNumber();
            $result->append($next);
        }
        return $result;
    }
    
    private function parseNegative(){
        return new NegativeNode($this->parsePow());
    }
    
    private function ParseMult()
    {
        if($this->tokenizer->isOneOf("-")){
            $this->tokenizer->getToken("-");
            $first  = $this->parseNegative();
        } else {
            $first  = $this->parsePow();
        }
        $result = new MultNode($first);
        while ($this->tokenizer->isOneOf("*", "/")) {
            $op   = $this->tokenizer->getToken("*", "/");
            $next = $this->parsePow();
            $result->append($op, $next);
        }
        return $result;
    }
    
    private function parseSum()
    {
        $first  = $this->parseMult();
        $result = new SumNode($first);
        while ($this->tokenizer->isOneOf("+", "-")) {
            $op   = $this->tokenizer->getToken("+", "-");
            $next = $this->parseMult();
            $result->append($op, $next);
        }
        return $result;
    }
    
    private function process()
    {
        try {
            $this->result = $this->parseSum()->calc();
        }
        catch (Exception $e) {
            $this->error = $e->getMessage();
        }
    }
    
    function isError()
    {
        if (!empty($this->error)) {
            return true;
        } else {
            return false;
        }
    }
    
    function getError()
    {
        return $this->error;
    }
    
    function getResult()
    {
        return $this->result;
    }
}
 
 
class Tokenazer
{
    private $tokens = array();
    private $index = 0;
    
    function __construct($text)
    {
        $matches = array();
        preg_match_all('/(\\d[.]\\d+)|\\d+|\\+|\\*|\\/|-|\\(|\\)|\\^/', $text, $matches);
        foreach ($matches[0] as $match) {
            $this->tokens[] = $match;
        }
    }
    
    function getToken()
    {
        $result = "";
        $token  = $this->getNextToken();
        foreach (func_get_args() as $arg) {
            if($this->ifMatch($token, $arg)){
                $result=$token;
            }
        }
        if (empty($result)) {
            throw new Exception("токен не соотв. типу");
        } else {
            return $result;
        }
    }
    
    private function ifMatch($token, $type){
        return (preg_match("/\\{$type}/", $token)) ? (true) : (false);
    }
    
    private function getNextToken()
    {
        $index = $this->index;
        $this->index++;
        if (isset($this->tokens[$index])) {
            return $this->tokens[$index];
        }
    }
    
    function isOneOf(){
        $resultBool=false;
        $args=func_get_args();
        foreach($args as $arg){
            if(isset($this->tokens[$this->index])){
                if($this->tokens[$this->index] == $arg){
                    $resultBool=true; 
                }
            }
        }
        return $resultBool;
    }
    
}

abstract class abstractNode
{
    abstract function calc();
}

class NumNode extends abstractNode
{
    protected $left;
    
    function __construct($left)
    {
        $this->left = $left;
    }
    
    function calc()
    {
        return $this->left;
    }
}
 
class NegativeNode extends NumNode
{
    function calc()
    {
        return (0 - $this->left->calc());
    }
}
 
class PowNode extends NumNode
{
    protected $childrens = array();
    
    function append($right)
    {
        $this->childrens[] = $right;
    }
    
    function calc()
    {
        $result = $this->left->calc();
        if (!empty($this->childrens)) {
            array_unshift($this->childrens, $this->left);
            while (count($this->childrens) != 1) {
                $degree            = array_pop($this->childrens)->calc();
                $numb              = array_pop($this->childrens)->calc();
                $this->childrens[] = new NumNode(pow($numb, $degree));
            }
            $result = array_pop($this->childrens)->calc();
        }
        return $result;
    }
}
 
class MultNode extends NumNode
{
    protected $childrens = array();
    
    function append($op, $right)
    {
        $temp              = array();
        $temp['op']        = $op;
        $temp['node']      = $right;
        $this->childrens[] = $temp;
    }
    
    function calc()
    {
        $result = $this->left->calc();
        if (!empty($this->left)) {
            foreach ($this->childrens as $child) {
                if ($child['op'] == "*") {
                    $result *= $child['node']->calc();
                } else {
                    $result /= $child['node']->calc();
                }
            }
        }
        return $result;
    }
}
 
class SumNode extends MultNode
{
    
    function calc()
    {
        $result = $this->left->calc();
        if (!empty($this->left)) {
            foreach ($this->childrens as $child) {
                if ($child['op'] == "+") {
                    $result += $child['node']->calc();
                } else {
                    $result -= $child['node']->calc();
                }
            }
        }
        return $result;
    }
}
 
//все как у людей блеять
class UnitTest
{
    private function test($answer, $string)
    {
        $tokenazer = new Tokenazer($string);
        $parser    = new Parser($tokenazer);
        if ($parser->isError()) {
            $answerFromCode = $parser->getError();
        } else {
            $answerFromCode = $parser->getResult();
        }
        if ($answerFromCode == $answer) {
            echo "✓ OK         $string → $answerFromCode\n";
        } else {
            echo "✗ ошибка     $string должно быть равно «{$answer}», но калькулятор вернул «{$answerFromCode}»\n";
        }
    }
    
    public function run()
    {
        $this->test(5, "2+3");
        $this->test(20, "4*5");
        $this->test(6561, "3^2^3");
        $this->test("токен не соотв. типу", "3^2^+3");
        $this->test(22, "2 + 4*5");
        $this->test(301, "(2 + 4) * 5 + 6 * 45 + 1");
        $this->test(2, "-3+5");
        $this->test(5, "-(-(2 + 3))");
    }
}
 
$test = new UnitTest();
$test->run();
?>