riff

Calculator

Apr 25th, 2013
170
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
PHP 12.93 KB | None | 0 0
  1. <?php
  2.  
  3. class Calculator
  4. {
  5.     const Error_Parse = 'parse_error';
  6.     const Error_Calc = 'calc_error';
  7.     public $owner;
  8.  
  9.     public function __construct($owner)
  10.     {
  11.         $this->owner = $owner;
  12.     }
  13.  
  14.     /**
  15.      * Пользовательские функции
  16.      * @virtual
  17.      * @param string $value
  18.      * @return mixed
  19.      */
  20.     public function cb_var($value)
  21.     {
  22.         return '';
  23.     }
  24.  
  25.     /**
  26.      * Пользовательские функции
  27.      * @virtual
  28.      * @param string $name
  29.      * @param array $args
  30.      * @return mixed
  31.      */
  32.     public function cb_func($name, $args)
  33.     {
  34.         switch ($name)
  35.         {
  36.             case 'replace':
  37.                 return str_replace($args[0], $args[1], $args[2]);
  38.  
  39.             default:
  40.                 return '';
  41.         }
  42.     }
  43.  
  44.     private function parse($expression, &$APos, $AValue)
  45.     {
  46.         //если ещё не начинали вычислений, то первое слагаемое ещё не определено
  47.         if ($AValue === null)
  48.             //получаем первое "слагаемое"
  49.             if (($AValue = $this->token($expression, $APos)) === self::Error_Parse)
  50.                 return self::Error_Parse;
  51.  
  52.         //получаем оператор (или, в случае если не поставили "*" между множителями, то второй множитель)
  53.         if (($Symbol = $this->symbol($expression, $APos)) === self::Error_Parse)
  54.             //если не удалось получить, возвращаем извесное слагаемое
  55.             return $this->value($AValue);
  56.  
  57.         //пытаемся получить второе слагаемое
  58.         if (($Value = $this->token($expression, $APos)) === self::Error_Parse)
  59.             //если не удалось его получить, то не будем сообщать об ошибке
  60.             //в выражении, а заполним переменную пустой строкой.
  61.             $Value = "''";
  62.         //если парсим тернарное выражение
  63.         elseif ($Symbol === '?')
  64.         {
  65.             //получаем обе резулитирующие части тернарного выр.
  66.             $v = $this->token($expression, $APos);
  67.             //и в зависимости от результата сравнения оставляем одну из
  68.             $Value = ($AValue ? $Value : $v);
  69.         }
  70.  
  71.         if (in_array($Symbol, array('*', '/'), true))
  72.             $arr = array('^');
  73.         elseif (in_array($Symbol, array('+', '-'), true))
  74.             $arr = array('^', '*', '/');
  75.         elseif (in_array($Symbol, array('=', '!=', '<', '>'), true))
  76.             $arr = array('^', '*', '/', '+', '-');
  77.         else
  78.             $arr = false;
  79.  
  80.         if ($arr)
  81.         {
  82.             $pos = $APos;
  83.             while ($APos < strlen($expression))
  84.             {
  85.                 if (($v = $this->symbol($expression, $pos)) === self::Error_Parse)
  86.                     break;
  87.                 elseif (in_array($v, $arr, true))
  88.                 {
  89.                     if (($v = $this->parse($expression, $APos, $Value)) === self::Error_Parse)
  90.                         break;
  91.                     $Value = $v;
  92.                     $pos = $APos;
  93.                 }
  94.                 else
  95.                     break;
  96.             }
  97.         }
  98.  
  99.         $AValue = $this->value($AValue);
  100.         $Value = $this->value($Value);
  101.  
  102.         switch ($Symbol)
  103.         {
  104.             case '^':
  105.                 return $this->pow($AValue, $Value);
  106.  
  107.             case '*':
  108.                 return $this->umn($AValue, $Value);
  109.  
  110.             case '/':
  111.                 return $this->del($AValue, $Value);
  112.  
  113.             case '+':
  114.                 return $this->plus($AValue, $Value);
  115.  
  116.             case '-':
  117.                 return $this->minus($AValue, $Value);
  118.  
  119.             case '?':
  120.                 return $Value;
  121.  
  122.             case '=':
  123.             case '!=':
  124.             case '<':
  125.             case '>':
  126.                 return $this->compare($Symbol, $AValue, $Value);
  127.  
  128.             default:
  129.                 return self::Error_Parse;
  130.         }
  131.     }
  132.  
  133.     private function symbol($expression, &$pos)
  134.     {
  135.         $length = strlen($expression);
  136.         while (true)
  137.         {
  138.             if ($pos >= $length) return self::Error_Parse;
  139.             elseif ($expression[$pos] === ' ') $pos++;
  140.             else break;
  141.         }
  142.         switch ($expression[$pos])
  143.         {
  144.             case '^':
  145.             case '*':
  146.             case '/':
  147.             case '+':
  148.             case '-':
  149.             case '=':
  150.             case '<':
  151.             case '>':
  152.                 $len = 1;
  153.                 break;
  154.  
  155.             case '!':
  156.                 $len = 2;
  157.                 break;
  158.  
  159.             case '?':
  160.                 return '?';
  161.  
  162.             default:
  163.                 //если это ни один из известных символов, значит это знак "*",
  164.                 //который не поставили между множителями.
  165.                 return '*';
  166.         }
  167.         $pos += $len;
  168.         return substr($expression, $pos - $len, $len);
  169.     }
  170.  
  171.     private function token($expression, &$pos)
  172.     {
  173.         for ($length = strlen($expression); true;)
  174.         {
  175.             if ($pos >= $length) return -1;
  176.             if ($expression[$pos] === ' ') $pos++;
  177.             else break;
  178.         }
  179.         $from = $pos;
  180.         $len = $this->token_len($expression, $pos);
  181.         if ($len === -1)
  182.             return self::Error_Parse;
  183.         $pos += $len;
  184.         return substr($expression, $from, $pos - $from);
  185.     }
  186.  
  187.     private function token_len($expression, $pos)
  188.     {
  189.         $length = strlen($expression);
  190.         if ($pos >= $length) return -1;
  191.         $nums = array('.', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9');
  192.  
  193.         if (in_array($expression[$pos], $nums, true))
  194.         {
  195.             $from = $pos;
  196.             while (($pos < $length) && in_array($expression[$pos], $nums, true))
  197.                 $pos++;
  198.             return $pos - $from;
  199.         }
  200.         else
  201.         switch ($expression[$pos])
  202.         {
  203.             case '[':
  204.                 $from = $pos;
  205.                 while ($pos < $length)
  206.                 {
  207.                     $pos++;
  208.                     if ($expression[$pos] === ']') break;
  209.                 }
  210.                 $pos++;
  211.                 return $pos - $from;
  212.  
  213.             case '`': //устаревшее
  214.                 $from = $pos;
  215.                 while ($pos < $length)
  216.                 {
  217.                     $pos++;
  218.                     if ($expression[$pos] === '`') break;
  219.                 }
  220.                 $pos++;
  221.                 return $pos - $from;
  222.  
  223.             case '@':
  224.                 $from = $pos;
  225.                 $sc = -1;
  226.                 while ($pos < $length)
  227.                 {
  228.                     if ($expression[$pos] === '(') $sc++;
  229.                     elseif ($expression[$pos] === ')') $sc--;
  230.                     $pos++;
  231.                     if (($sc === 0) && ($expression[$pos] === ')')) break;
  232.                 }
  233.                 $pos++;
  234.                 return $pos - $from;
  235.  
  236.             case '?':
  237.                 $from = $pos;
  238.                 $sc = 0;
  239.                 while ($pos < $length)
  240.                 {
  241.                     if ($expression[$pos] === '(') $sc++;
  242.                     elseif ($expression[$pos] === ')') $sc--;
  243.                     $pos++;
  244.                     if (($sc === 0) && ($expression[$pos] === ':')) break;
  245.                 }
  246.                 return $pos - $from;
  247.  
  248.             case ':':
  249.                 return $length - $pos;
  250.  
  251.             case '(':
  252.                 $from = $pos;
  253.                 $sc = -1;
  254.                 while ($pos < $length)
  255.                 {
  256.                     if ($expression[$pos] === '(') $sc++;
  257.                     elseif ($expression[$pos] === ')') $sc--;
  258.                     $pos++;
  259.                     if (($sc === 0) && ($expression[$pos] === ')')) break;
  260.                 }
  261.                 $pos++;
  262.                 return $pos - $from;
  263.  
  264.             case '\'':
  265.                 $from = $pos;
  266.                 while ($pos < $length)
  267.                 {
  268.                     $pos++;
  269.                     while ($expression[$pos] === '\'')
  270.                     {
  271.                         if ($pos + 1 === $length) break;
  272.                         elseif ($expression[$pos + 1] === '\'')
  273.                             $pos++;
  274.                         else
  275.                             break;
  276.                         $pos++;
  277.                     }
  278.                     if ($expression[$pos] === '\'') break;
  279.                 }
  280.                 $pos++;
  281.                 return $pos - $from;
  282.  
  283.             default:
  284.                 return 0;
  285.         }
  286.     }
  287.  
  288.     private function value($value)
  289.     {
  290.         if (strlen($value) === 0) return "''";
  291.         switch ($value[0])
  292.         {
  293.             case '@':
  294.                 return $this->funct($value);
  295.             case '[':
  296.             case '`':
  297.                 return $this->variable($value);
  298.             case '(':
  299.                 return $this->subexp($value);
  300.             case '?':
  301.             case ':':
  302.                 return $this->ternval($value);
  303.             default:
  304.                 return $value;
  305.         }
  306. //      return (is_numeric($value) ? $value : "'$value'");
  307.     }
  308.  
  309.     private function variable($value)
  310.     {
  311.         $value = substr($value, 1, -1);
  312.         $value = $this->cb_var($value);
  313.         return (is_float($value) || is_int($value) ? $value : "'$value'");
  314.     }
  315.  
  316.     private function funct($value)
  317.     {
  318.         $pos = 0;
  319.         $args = array();
  320.         $flength = strlen($value);
  321.         while ($pos < $flength)
  322.         {
  323.             if ($value[$pos] === '(') break;
  324.             $pos++;
  325.         }
  326.         $fname = substr($value, 1, $pos - 1);
  327.         $pos++;
  328.         $from = $pos;
  329.         while ($pos < $flength)
  330.         {
  331.             switch ($value[$pos])
  332.             {
  333.                 case '(':
  334.                 case '\'':
  335.                 case '@':
  336.                 case '[':
  337.                 case '`':
  338.                     $pos += $this->token_len($value, $pos);
  339.                     break;
  340.  
  341.                 case ',':
  342.                     $args[] = substr($value, $from, $pos - $from);
  343.                     $pos++;
  344.                     $from = $pos;
  345.                     break;
  346.  
  347.                 case ')':
  348.                     $args[] = substr($value, $from, $pos - $from);
  349.                     $pos++;
  350.                     $flength = $pos;
  351.                     break;
  352.  
  353.                 default:
  354.                     $pos++;
  355.             }
  356.         }
  357.         for ($pos=0; $pos<count($args); $pos++)
  358.         {
  359.             $args[$pos] = trim($args[$pos]);
  360.             $args[$pos] = $this->Calc($args[$pos]);
  361.         }
  362.  
  363.         $value = $this->cb_func($fname, $args);
  364.         return (is_float($value) || is_int($value) ? $value : "'$value'");
  365.     }
  366.  
  367.     /**
  368.      * Выражение в скобках
  369.      * @param string $value
  370.      * @return string
  371.      */
  372.     private function subexp($value)
  373.     {
  374.         $value = substr($value, 1, -1);
  375.         return $this->DoCalc($value);
  376.     }
  377.  
  378.     /**
  379.      * Члены тернарного выражения
  380.      * @param string $value
  381.      * @return string
  382.      */
  383.     private function ternval($value)
  384.     {
  385.         $value = substr($value, 1);
  386.         $pos = 0;
  387.         $length = strlen($value);
  388.         while (true)
  389.         {
  390.             if ($pos >= $length) return "''";
  391.             elseif ($value[$pos] === ' ') $pos++;
  392.             else break;
  393.         }
  394.         $value = substr($value, $pos);
  395.         return $this->DoCalc($value);
  396.     }
  397.  
  398.     /**
  399.      * тернарное выражение. Операция сравнения.
  400.      * @param string $symbol
  401.      * @param string $value1
  402.      * @param string $value2
  403.      * @return boolean
  404.      */
  405.     private function compare($symbol, $value1, $value2)
  406.     {
  407.         if (is_numeric($value1) && is_numeric($value2))
  408.         {
  409.             $value1 = floatval($value1);
  410.             $value2 = floatval($value2);
  411.         }
  412.         switch ($symbol)
  413.         {
  414.             case '=':
  415.                 return ($value1 == $value2);
  416.             case '!=':
  417.                 return ($value1 != $value2);
  418.             case '<':
  419.                 return ($value1 < $value2);
  420.             case '>':
  421.                 return ($value1 > $value2);
  422.             default:
  423.                 return self::Error_Calc;
  424.         }
  425.     }
  426.  
  427.     private function pow($value1, $value2)
  428.     {
  429.         if (is_numeric($value1) && is_numeric($value2))
  430.             return pow((float)$value1, (float)$value2);
  431.         else
  432.             return self::Error_Calc;
  433.     }
  434.  
  435.     private function umn($value1, $value2)
  436.     {
  437.         if (is_numeric($value1) && is_numeric($value2))
  438.             return floatval($value1) * floatval($value2);
  439.         else
  440.             return self::Error_Calc;
  441.     }
  442.  
  443.     private function del($value1, $value2)
  444.     {
  445.         if (is_numeric($value1) && is_numeric($value2))
  446.             return floatval($value1) / floatval($value2);
  447.         else
  448.             return self::Error_Calc;
  449.     }
  450.  
  451.     private function plus($value1, $value2)
  452.     {
  453.         if (is_numeric($value1) && is_numeric($value2))
  454.             return floatval($value1) + floatval($value2);
  455.         else
  456.         {
  457.             $value1 = (is_numeric($value1) ? $value1 : substr($value1, 1, -1));
  458.             $value2 = (is_numeric($value2) ? $value2 : substr($value2, 1, -1));
  459.             return "'$value1$value2'";
  460.         }
  461.     }
  462.  
  463.     private function minus($value1, $value2)
  464.     {
  465.         if (is_numeric($value1) && is_numeric($value2))
  466.             return floatval($value1) - floatval($value2);
  467.         else
  468.             return self::Error_Calc;
  469.     }
  470.  
  471.     public function DoCalc($expression)
  472.     {
  473.         $pos = 0;
  474.         $value = null;
  475.         $result = '';
  476.         while ($pos < strlen($expression))
  477.         {
  478.             if (($value = $this->parse($expression, $pos, $value)) === self::Error_Parse)
  479.                 break;
  480.             $result = $value;
  481.         }
  482.         return $result;
  483.     }
  484.  
  485.     public function Calc($expression)
  486.     {
  487.         $value = $this->DoCalc($expression);
  488.         if (strlen($value) === 0) return '';
  489.         elseif (is_numeric($value)) return $value;
  490.         else return substr($value, 1, -1);
  491.     }
  492. }
  493.  
  494. //------------------- example -----------------------
  495.  
  496. //class MyCalc extends Calculator
  497. //{
  498. //  /**
  499. //   * определяем пользовательские переменные
  500. //   * @override
  501. //   */
  502. //  public function cb_var($value)
  503. //  {
  504. //      switch ($value)
  505. //      {
  506. //          case 'my_var':
  507. //              return 'abc';
  508. //          default:
  509. //              return parent::cb_var($value);
  510. //      }
  511. //  }
  512. //
  513. //  /**
  514. //   * определяем пользовательские функции
  515. //   * @override
  516. //   */
  517. //  public function cb_func($name, $args)
  518. //  {
  519. //      switch ($name)
  520. //      {
  521. //          case 'my_zzz':
  522. //              return 'zzzzzzz';
  523. //          case 'my_plus':
  524. //              return (int)$args[0] + (int)$args[1];
  525. //          default:
  526. //              return parent::cb_func($name, $args);
  527. //      }
  528. //  }
  529. //}
  530. //
  531. ////если достаточно стандартного калькулятора
  532. ////$calc = new Calculator(null);
  533. //
  534. ////создаём свой калькулятор с расширенным набором функций
  535. //$calc = new MyCalc(null);
  536. //
  537. //echo $calc->Calc('(1 + 2) / 3 +  9 * 5');
  538. ////echo $calc->Calc('2+2(6^(1+1)*8-4)+7/4'); //степень
  539. ////echo $calc->Calc("(1 + 2)^3 * 5 + ' Текст'"); //вычисления + текст
  540. ////echo $calc->Calc("@replace('дороге', 'шоссе', 'Шла Саша по дороге')"); //замена текста
  541. ////echo $calc->Calc('(1+2)/3+9+@my_zzz()'); //вызов пользовательской функции
  542. ////echo $calc->Calc('(1+2)/3+9+@my_plus(100, 25)'); //вызов пользовательской функции с параметрами
  543. //
  544. ////вызов пользовательской переменной, а так же тернарное выражение
  545. ////echo $calc->Calc("'Какой-то текст ' + ([my_var]='abc' ? 'true' : 'false')"); //= (равно), != (не равно), < (меньше), > (больше)
Advertisement
Add Comment
Please, Sign In to add comment