500 ) { return null; } $expression = preg_replace_callback( '/\{([a-z][a-z0-9_-]{0,63})\}/i', static function ( $m ) use ( $values ) { $value = $values[ $m[1] ] ?? 0; return is_numeric( $value ) ? (string) $value : '0'; }, $expression ); if ( preg_match( '/[^0-9+\-*\/().\s]/', $expression ) ) { return null; } $tokens = preg_split( '/(\d+(?:\.\d+)?|[+\-*\/()])/', $expression, -1, PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_NO_EMPTY ); $output = []; $ops = []; $precedence = [ '+' => 1, '-' => 1, '*' => 2, '/' => 2 ]; foreach ( $tokens as $token ) { $token = trim( $token ); if ( '' === $token ) { continue; } if ( is_numeric( $token ) ) { $output[] = (float) $token; continue; } if ( '(' === $token ) { $ops[] = $token; continue; } if ( ')' === $token ) { while ( $ops && '(' !== end( $ops ) ) { $output[] = array_pop( $ops ); } if ( ! $ops ) { return null; } array_pop( $ops ); continue; } if ( ! isset( $precedence[ $token ] ) ) { return null; } while ( $ops && '(' !== end( $ops ) && $precedence[ end( $ops ) ] >= $precedence[ $token ] ) { $output[] = array_pop( $ops ); } $ops[] = $token; } while ( $ops ) { if ( '(' === end( $ops ) ) { return null; } $output[] = array_pop( $ops ); } $stack = []; foreach ( $output as $token ) { if ( is_numeric( $token ) ) { $stack[] = (float) $token; continue; } if ( count( $stack ) < 2 ) { return null; } $b = array_pop( $stack ); $a = array_pop( $stack ); if ( '/' === $token && 0.0 === $b ) { return null; } $stack[] = '+' === $token ? $a + $b : ( '-' === $token ? $a - $b : ( '*' === $token ? $a * $b : $a / $b ) ); } return 1 === count( $stack ) ? round( (float) $stack[0], 2 ) : null; } }