вычислить выражение записанное в виде строки символов
От: system.console  
Дата: 31.01.26 15:42
Оценка:
hi all,
подскажите, как в PHP сделать сабж.
Функции передается строка, содержащая арифметическое выражение.
Функция должна вернуть результат.
Выражение несложное — без всяких там функций и переменных, только +,-,*,/ и скобки.

вот типа такого, только без eval()

function string_calculator($str){
    eval("\$str = $str;");
    return $str;
}

echo string_calculator("(10+60/2)*2");
Отредактировано 31.01.2026 15:43 system.console . Предыдущая версия .
Re: вычислить выражение записанное в виде строки символов
От: Marty Пират https://www.youtube.com/channel/UChp5PpQ6T4-93HbNF-8vSYg
Дата: 31.01.26 15:54
Оценка: +1
Здравствуйте, system.console, Вы писали:

SC>hi all,

SC>подскажите, как в PHP сделать сабж.
SC>Функции передается строка, содержащая арифметическое выражение.
SC>Функция должна вернуть результат.
SC>Выражение несложное — без всяких там функций и переменных, только +,-,*,/ и скобки.

SC>вот типа такого, только без eval()


Опиши, какие операторы будешь использовать, и их приоритет. Напиши, что хочешь использовать группирующие скобки. Скорми это дипсику, и попроси его написать парсер рекурсивного спуска. Он справится. Я так делал. Единственно, у меня уже был лексер, и на выходе лексера (на входе парсера) уже были токены — оператор/литерал/идентификатор/скобка. Он мне на питоне написал, но я без проблем переделал под себя на плюсиках. Возможно, на PHP он сразу сможет написать. Ну, и пусть тебе лексер напишет, или совместит его с парсером.

Да, не забудь указать, что +/- могут быть унарными.

Скажи ему, что значения хочешь складывать на стек, вызывать операторную функцию, и помещать результат обратно на стек. А меня был кейс посложнее — я использовал идентификаторы/переменные, вызов своих функций, и тернарный оператор.
Маньяк Робокряк колесит по городу
Re: вычислить выражение записанное в виде строки символов
От: kov_serg Россия  
Дата: 01.02.26 07:49
Оценка: +2
Здравствуйте, system.console, Вы писали:

SC>hi all,

SC>подскажите, как в PHP сделать сабж.
SC>Функции передается строка, содержащая арифметическое выражение.
SC>Функция должна вернуть результат.
SC>Выражение несложное — без всяких там функций и переменных, только +,-,*,/ и скобки.

SC>вот типа такого, только без eval()


SC>
SC>function string_calculator($str){
SC>    eval("\$str = $str;");
SC>    return $str;
SC>}

SC>echo string_calculator("(10+60/2)*2");
SC>


Например так:
  function expr
<?php

class Expr {
    var $s, $p, $n;
    function calc($text) {
        $this->s=$text; $this->p=0; $this->n=strlen($text);
        $r=$this->L0();
        $this->skipspace(); if (!$this->eos()) $this->wtf();
        return $r;
    }
    function L0() {
        $r=$this->L1();
        for(;;) {
            if ($this->chk('+')) $r=$r+$this->L1();
            elseif ($this->chk('-')) $r=$r-$this->L1();
            else break;
        }
        return $r;
    }
    function L1() {
        $r=$this->L2();
        for(;;) {
            if ($this->chk('*')) $r=$r*$this->L2();
            elseif ($this->chk('/')) $r=$r/$this->L2();
            else break;
        }
        return $r;
    }
    function L2() {
        if ($this->chk('(')) {
            $r=$this->L0();
            if (!$this->chk(')')) $this->error("no closing braket");
            return $r;
        }
        return $this->L3();
    }
    function L3() {
        if ($this->chk('-')) return -$this->L2();
        if ($this->chk('+')) return $this->L2();
        return $this->L4();
    }
    function L4() {
        $this->skipspace(); $c=$this->peek();
        if (self::is_digit($c) || $c=='-') return $this->number();
        elseif (self::is_letter($c)) return $this->name();
        else $this->wtf();
    }
    function number() {
        $r=0; $sign=$this->chk('-');
        $c=$this->peek(); if (!self::is_digit($c)) $this->error("invalid number");
        while(self::is_digit($c)) {
            $r=$r*10 + ($c-'0');
            $this->p++; $c=$this->peek();
        }
        if ($this->chk('.')) {
            $np=1.0; $c=$this->peek();
            while(self::is_digit($c)) {
                $np/=10;
                $r+=($c-'0')*$np;
                $this->p++; $c=$this->peek();
            }
        }
        if ($this->chk('e') || $this->chk('E')) {
            $ne=0; $ns=false;
            $c=$this->peek(); 
            if ($c=='-') { $ns=true; $this->p++; $c=$this->peek(); }
            elseif($c=='+') { $this->p++; $c=$this->peek(); }
            if (!self::is_digit($c)) $this->error("invalid number");
            while(self::is_digit($c)) {
                $ne=$ne*10 + ($c-'0');
                $this->p++; $c=$this->peek();
            }
            if ($ns) $ne=-$ne;
            $r*=pow(10.0,$ne);
        }
        return $sign ? -$r : $r;
    }
    var $names=[
        'pi'=>3.1415926535897932,
        'e' =>2.7182818284590452,
    ];
    function init() {
        $this->names['sin']=function($x) { return sin($x); };
        $this->names['cos']=function($x) { return cos($x); };
        $this->names['exp']=function($x) { return exp($x); };
        $this->names['ln']=function($x) { return log($x); };
        $this->names['pow']=function($x,$n) { return pow($x,$n); };
    }
    function name() {
        if (preg_match('/\b([A-Za-z_][A-Za-z0-9_]*)/',$this->s,$m,0,$this->p)) {
            $name=$m[1]; $len=strlen($name);
            if (!isset($this->names[$name])) $this->error("undefined name $name",$len);
            $this->p+=$len;
            if ($this->chk('(')) return $this->func($name);
            return $this->names[$name];
        }
        $this->error("name expected");
    }
    function func($name) {
        $args=[];
        if (!$this->chk(')')) for(;;) {
            if ($this->eos()) $this->error("unexpected end");
            array_push($args,$this->L0());
            if ($this->chk(')')) break;
            if (!$this->chk(',')) $this->wtf();
        }
        $fn=$this->names[$name];
        $r=call_user_func_array($fn,$args);
        return $r;
    }
    static function is_space($c) { return ord($c)>0 && $c<=' '; }
    static function is_digit($c) { return $c>='0' && $c<='9'; }
    static function is_letter($c) { return ($c>='a' && $c<='z') || ($c>='A' && $c<='Z') || ($c=='_'); }
    function eos() { return $this->p>=$this->n; }
    function peek() { return $this->p<$this->n ? $this->s[$this->p] : '\0'; }
    function get() { return $this->p<$this->n ? $this->s[$this->p++] : '\0'; }
    function skipspace() { while(self::is_space($this->peek())) $this->p++; }
    function chk($c) {
        $this->skipspace();
        if ($this->peek()!=$c) return false;
        $this->p++; return true;
    }
    function wtf() { $this->error("unexpected symbol ".$this->peek()); }
    function error($msg,$len=1) {
        $p=$this->p; $n=$this->n; $w=20;
        $q=[ $p-$w, $p, $p+$len, $p+$len+$w ]; $qn=count($q);
        foreach($q as &$x) if ($x<0) $x=0; elseif ($x>$n) $x=$n;
        $ss=$q[0]==0 ? "" : "...";
        for($i=1;$i<$qn;$i++) {
            if ($i==2) $ss.='[??';
            $ss.=substr($this->s,$q[$i-1],$q[$i]-$q[$i-1]);
            if ($i==2) $ss.='??]';
        }
        if ($q[$qn-1]!=$n) $ss.="...";
        throw new Exception($msg.": $ss\n"); 
    }
}

function expr($s) {
    $e=new Expr(); $e->init();
    return $e->calc($s);
}
$s="2+2";
$r=expr($s);
print("$s=$r\n");
2+2=4
Отредактировано 01.02.2026 8:04 kov_serg . Предыдущая версия .
Re[2]: вычислить выражение записанное в виде строки символов
От: system.console  
Дата: 01.02.26 10:21
Оценка:
SC>>echo string_calculator("(10+60/2)*2");
SC>>[/php]

_>Например так:

);
_>[/php]
_>2+2=4
_>


спасибо !
а покороче никак нельзя ?
уж слишком многа букаф.
Можа есть либа какА с нужной функцией ?
Re[2]: вычислить выражение записанное в виде строки символов
От: system.console  
Дата: 01.02.26 10:26
Оценка:
M>Опиши, какие операторы будешь использовать, и их приоритет. Напиши, что хочешь использовать группирующие скобки. Скорми это дипсику, и попроси его написать парсер рекурсивного спуска. Он справится. Я так делал. Единственно, у меня уже был лексер, и на выходе лексера (на входе парсера) уже были токены — оператор/литерал/идентификатор/скобка. Он мне на питоне написал, но я без проблем переделал под себя на плюсиках. Возможно, на PHP он сразу сможет написать. Ну, и пусть тебе лексер напишет, или совместит его с парсером.
M>Да, не забудь указать, что +/- могут быть унарными.
M>Скажи ему, что значения хочешь складывать на стек, вызывать операторную функцию, и помещать результат обратно на стек.
я попробую конечно чисто заради любопытства посмотреть, на что он способен этот ваш гигачат, но мне хотелось бы что-то совсем коротко чтобы.
Может какую-то библиотечку подключить, где есть нужная мне функция.
А то все эти грамматики-парсеры-лексеры я уже давно забыл, если и знал когда-то.
На худой конец оставлю eval, только предварительно проверку сделаю, чтобы там в качестве параметра ничего кроме 0..9,+,-,*,/,(,) не могло пролезть

ЗЫ. Ниже результат работы gigachat

// Функция для обработки арифметического выражения
function calculateExpression($expression)
{
    // Очищаем пробелы
    $expression = str_replace(' ', '', $expression);
    
    try {
        // Используем встроенную функцию eval() осторожно
        return eval("return ($expression);");
    } catch (\\ParseError | \\DivisionByZeroError $e) {
        throw new Exception("Ошибка вычисления выражения: {$e->getMessage()}");
    }
}


или без использования eval()
class Calculator {
    private function isOperator($char) {
        return in_array($char, ['+', '-', '*', '/']);
    }

    private function precedence($op) {
        if ($op === '+' || $op === '-') return 1;
        if ($op === '*' || $op === '/') return 2;
        return 0;
    }

    private function applyOperation(&$stackValues, &$stackOperators) {
        $right = array_pop($stackValues);
        $left = array_pop($stackValues);
        $operator = array_pop($stackOperators);
        
        switch ($operator) {
            case '+':
                array_push($stackValues, $left + $right);
                break;
            case '-':
                array_push($stackValues, $left - $right);
                break;
            case '*':
                array_push($stackValues, $left * $right);
                break;
            case '/':
                if ($right != 0) {
                    array_push($stackValues, $left / $right);
                } else {
                    throw new Exception("Деление на ноль!");
                }
                break;
        }
    }

    public function evaluate($expression) {
        $tokens = preg_split('/(\\d+(?:\\.\\d+)?|\\(|\\)|[+\\-*\\/])/', trim($expression), -1, PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_NO_EMPTY);
        $stackValues = [];
        $stackOperators = [];

        foreach ($tokens as $token) {
            if (is_numeric($token)) { // Число
                array_push($stackValues, floatval($token));
            } elseif ($this->isOperator($token)) { // Операция
                while (!empty($stackOperators) && $this->precedence(end($stackOperators)) >= $this->precedence($token)) {
                    $this->applyOperation($stackValues, $stackOperators);
                }
                array_push($stackOperators, $token);
            } elseif ($token === '(') { // Открывающая скобка
                array_push($stackOperators, $token);
            } elseif ($token === ')') { // Закрывающая скобка
                while (end($stackOperators) !== '(') {
                    $this->applyOperation($stackValues, $stackOperators);
                }
                array_pop($stackOperators); // Убираем открывающую скобку
            }
        }

        while (!empty($stackOperators)) {
            $this->applyOperation($stackValues, $stackOperators);
        }

        return end($stackValues);
    }
}
Отредактировано 01.02.2026 11:45 system.console . Предыдущая версия . Еще …
Отредактировано 01.02.2026 11:37 system.console . Предыдущая версия .
Re[3]: вычислить выражение записанное в виде строки символов
От: kov_serg Россия  
Дата: 01.02.26 12:35
Оценка:
Здравствуйте, system.console, Вы писали:

SC>а покороче никак нельзя ?

SC>уж слишком многа букаф.
require_once("mongabukaf.php");
Re[2]: вычислить выражение записанное в виде строки символов
От: Pavel Dvorkin Россия  
Дата: 01.02.26 12:49
Оценка:
Здравствуйте, Marty, Вы писали:

M>Опиши, какие операторы будешь использовать, и их приоритет. Напиши, что хочешь использовать группирующие скобки. Скорми это дипсику, и попроси его написать парсер рекурсивного спуска. Он справится. Я так делал. Единственно, у меня уже был лексер, и на выходе лексера (на входе парсера) уже были токены — оператор/литерал/идентификатор/скобка. Он мне на питоне написал, но я без проблем переделал под себя на плюсиках. Возможно, на PHP он сразу сможет написать. Ну, и пусть тебе лексер напишет, или совместит его с парсером.


У нас эту задачу на 1 курсе давали. Я ее давал хорошим школьникам, когда вел у них факультатив.

M>Да, не забудь указать, что +/- могут быть унарными.


Ну и для разнообразия можно приоритеты операций давать произвольные, таблицей

2+2*4 получится 16, если приоритет сложения выше, чем умножения.

M>Скажи ему, что значения хочешь складывать на стек, вызывать операторную функцию, и помещать результат обратно на стек. А меня был кейс посложнее — я использовал идентификаторы/переменные, вызов своих функций, и тернарный оператор.


Вот тернарный не давал. Хотя принципиально ничего нового, просто из стека надо 3 элемента брать.
With best regards
Pavel Dvorkin
Re[4]: вычислить выражение записанное в виде строки символов
От: system.console  
Дата: 01.02.26 12:49
Оценка:
SC>>а покороче никак нельзя ?
SC>>уж слишком многа букаф.
_>
_>require_once("mongabukaf.php");
_>

да не, я не в этом смысле ...
вообще многа букаф
я-то думал, мож есть какая-то встроенная волшебная функция для моей цели
Но и так сойдет.
Большое спасибо.
Вопрос закрыт
Re[3]: вычислить выражение записанное в виде строки символов
От: Marty Пират https://www.youtube.com/channel/UChp5PpQ6T4-93HbNF-8vSYg
Дата: 01.02.26 13:05
Оценка:
Здравствуйте, Pavel Dvorkin, Вы писали:

M>>Да, не забудь указать, что +/- могут быть унарными.


PD>Ну и для разнообразия можно приоритеты операций давать произвольные, таблицей


Я тут затеял либу сделать для себя, для того, чтобы можно было задавать произвольные операторы, их приоритет, ассоциативность. Чота забуксовал


M>>Скажи ему, что значения хочешь складывать на стек, вызывать операторную функцию, и помещать результат обратно на стек. А меня был кейс посложнее — я использовал идентификаторы/переменные, вызов своих функций, и тернарный оператор.


PD>Вот тернарный не давал. Хотя принципиально ничего нового, просто из стека надо 3 элемента брать.


С тернарным оператором поинтереснее — хочется, чтобы одна часть могла не выполняться вообще — например, мы проверяем переменную а на ноль — и если ноль, то та часть, где мы делим на a — вообще не должна выполняться
Маньяк Робокряк колесит по городу
Re[3]: вычислить выражение записанное в виде строки символов
От: Anton Batenev Россия https://github.com/abbat
Дата: 01.02.26 14:42
Оценка: +1
Здравствуйте, system.console, Вы писали:

s> Можа есть либа какА с нужной функцией ?


А чем не устраивают chriskonnertz/string-calc и им подобные (mossadal/math-parser, xylemical/php-expressions, langleyfoxall/math_eval)?
Re[4]: вычислить выражение записанное в виде строки символов
От: Pavel Dvorkin Россия  
Дата: 01.02.26 14:44
Оценка:
Здравствуйте, Marty, Вы писали:

M>С тернарным оператором поинтереснее — хочется, чтобы одна часть могла не выполняться вообще — например, мы проверяем переменную а на ноль — и если ноль, то та часть, где мы делим на a — вообще не должна выполняться


То есть на переводе в обратную польскую запись не выполнялась ? На втором этапе вроде проблемы нет
With best regards
Pavel Dvorkin
Re[4]: вычислить выражение записанное в виде строки символов
От: Pavel Dvorkin Россия  
Дата: 01.02.26 14:53
Оценка:
Здравствуйте, Marty, Вы писали:

M>С тернарным оператором поинтереснее — хочется, чтобы одна часть могла не выполняться вообще — например, мы проверяем переменную а на ноль — и если ноль, то та часть, где мы делим на a — вообще не должна выполняться


То есть на переводе в обратную польскую запись не выполнялась ? На втором этапе вроде проблемы нет
With best regards
Pavel Dvorkin
Re[4]: вычислить выражение записанное в виде строки символов
От: system.console  
Дата: 01.02.26 15:25
Оценка:
AB>А чем не устраивают chriskonnertz/string-calc и им подобные (mossadal/math-parser, xylemical/php-expressions, langleyfoxall/math_eval)?
да может и всем устраивают, но надо же мне было сперва узнать о их существовании ...
ну а теперь уже наверное и поздно — разных вариантов нашлось уже 3-4 шт
чо уж там
 
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.