HalloPHP

Term mit PHP parsen

Tutorials | letzte Änderung am 07. Oktober '10 um 21:00 Uhr

Motivation

Wir möchten einen einfachen mathematischen Term berechnen lassen, den uns ein User über ein Formular übermittelt hat. Das Problem: Der Term liegt als String vor und wird vom Interpreter nicht geparst. Der Term wird so, wie er übermittelt wurde, auch ausgegeben.
In diesem Tutorial untersuchen wir nun verschiedene Möglichkeiten einfache Terme, die neben den Grundoperatoren +, -, * und / auch Potenzen und runde Klammern enthalten können, auswerten zu können.

Anforderungen

Zunächst einmal wollen wir festlegen, wie unser Term, den wir interpretieren lassen wollen, überhaupt aussehen darf. Folgende Operatoren wollen wir unterstützen:

Hinzu kommen noch runde Klammern, um die Rangfolge bei der Berechnung (Punktrechnung vor Strichrechnung) beeinflussen zu können.

Als Beispielterm nehmen wir zum Beispiel

12+(2^2)^(3-6)*2+(1/4)^-0.5*(4-(3^2)) = 2.03125

um es uns nicht ganz so einfach zu machen.

Die einfache unsaubere Methode

Hier sprechen wir natürlich über die naheliegendste aller Möglichkeiten und zwar über den Einsatz der Funktion eval(). Den Grundsatz eval() is evil! ignorieren wir an dieser Stelle und schauen uns das erste Beispiel an!

<?php
$term 
'12+pow(pow(2, 2), 3-6)*2+pow(1/4, -0.5)*(4-pow(3,2))';

eval(
"\$output = $term;");

echo 
'input: ' $term;
echo 
'output: ' $output;

Dieser Code gibt

input: 12+pow(pow(2, 2), 3-6)*2+pow(1/4, -0.5)*(4-pow(3,2))
output: 2.03125

aus. Der Term ist äquivalent zu

12+(2^2)^(3-6)*2+(1/4)^-0.5*(4-(3^2))

und eine kurze Überprüfung bei unserem Freund Google bestätigt uns das berechnete Ergebnis. Ein Nachteil ist natürlich, dass der User die PHP-Funktion pow() zum Potenzieren kennen und einsetzen können muss und nicht wie gewünscht das Zirkumflex bei der Eingabe verwenden kann.
Außerdem bedeutet der Einsatz von eval() auch immer ein Sicherheitsrisiko, da auch Schadcode eingeschleust werden kann.
Schauen wir uns also eine zweite Methode an.

Term parsen mit regulären Ausdrücken

Deutlich sicherer ist dagegen das manuelle Interpretieren des Terms mit Hilfe von regulären Ausdrücken. Auch können wir nun auf den Einsatz von PHP-Funktionen verzichten und das Zirkumflex als Operator für das Potenzieren verwenden.

Für die Umsetzung unseres Parser ist etwas Vorwissen vonnöten, dass jedem durch die Schulbildung bekannt sein sollte. Stichworte sind:

Die Regel "Punktrechnung vor Strichrechnung" kennt jeder und auch den Begriff "Assoziativgesetz" sollte man schon mal gehört haben. Dieses ist vorallem beim Potenzieren wichtig, da Terme wie

2^2^3

von rechts nach links ausgewertet werden. Die Potenz ist nicht assoziativ und muss anders behandelt werden, als zum Beispiel der Additions- oder Multiplikationsoperator.

Weitere Überlegungen legen nahe, dass ein Term aus mehreren Termen bestehen kann.

(A+B)/(C-D)

Dies kann uns die Berechnung vereinfachen, da wir uns nicht gleich um den gesamten Term kümmern müssen, sondern erst kleinere Terme auswerten können. In einem ersten Schritt können wir bei diesem Beispiel also die Addition und im Anschluss die Subtraktion ausführen. Stehen bei Ergebnisse in den Klammern fest, können wir mit der Division fortfahren.
Für unseren Parser bietet sich eine rekursive Funktion an, die einen großen Term in Kleinere zerlegt und diese kleineren Terme zur weiteren Berechnung an die Funktion delegiert.

<?php
/**
 * calculate simple terms without functions or parentheses
 *
 * @param string $term
 * @return numeric
 */
function calcTerm($term) {
  if (
preg_match('/\+/'$term)) {
    
$array preg_split('/\+/'$term);

    
$result 0;
    foreach (
$array as $term) {
      
$result += calcTerm($term);
    }

    return 
$result;
  }

  if (
preg_match('/[0-9]\-/'$term)) {
    
$array preg_split('/\-/'$term);

    
$result calcTerm($array[0]);
    for (
$i 1$c count($array); $i $c; ++$i) {
      
$result -= calcTerm($array[$i]);
    }

    return 
$result;
  }

  if (
preg_match('/\//'$term)) {
    
$array preg_split('/\//'$term);

    
$result calcTerm($array[0]);
    for (
$i 1$c count($array); $i $c; ++$i) {
      
$calc calcTerm($array[$i]);
      if (
$calc != 0) {
        
$result /= $calc;
      }
      else {
        throw new 
Exception('Division by zero.');
      }
    }

    return 
$result;
  }

  if (
preg_match('/\*/'$term)) {
    
$array preg_split('/\*/'$term);

    
$result calcTerm($array[0]);
    for (
$i 1$c count($array); $i $c; ++$i) {
      
$result *= calcTerm($array[$i]);
    }

    return 
$result;
  }

  if (
preg_match('/\^/'$term)) {
    
$array preg_split('/\^/'$term);

    
$temp 1;
    for (
$i count($array)-1$i >= 0; --$i) {
      
$temp pow(calcTerm($array[$i]), $temp);
      
$result $temp;
    }

    return 
$result;
  }

  return 
$term;
}

Mit dieser Funktion können wir bereits Terme ohne Klammern lösen. Sie sucht zuerst nach den Operatoren niedriger und dann höherer Präzedenz und teilt den String am Operator auf. Die so erhaltenen Subterme werden dann zur weiteren Berechnung erneut an die Funktion übergeben und die Rückgabewerte in Abhängigkeit vom Operator verrechnet.
Um auch Klammern zu berücksichtigen, schreiben wir eine zweite Funktion, die zunächst sämtliche Klammerausdrücke selektiert und von unserer Funktion auswerten lässt.

<?php
function calcTerm($term) { 
  
// ...
}

function 
parseTerm($term) {
  
// get subterms between parentheses if exist
  
while (preg_match_all('/\(([^\)\(]+)\)/'$term$matchesPREG_SET_ORDER)) {
    foreach (
$matches as $match) {
      
// calculate subterms
      
$result calcTerm($match[1]);

      
// replace subterm with calculated result
      
$term str_replace($match[0], $result$term);
    }
  }

  
// calculate and return term
  
return calcTerm($term);
}

Nun können wir bereits wieder den oben aufgeführten Term lösen.

<?php
$term 
'10+(2^2)^(3-6)*2+36^-0.5*(4-(3^2))';

$output parseTerm($term);

echo 
'input: ' $term;
echo 
'output: ' $output;

Auch hier ist das Ergebnis natürlich wieder 2.03125.

Der Nachteil dieser Variante ist klar die Performance.
Überlegt man sich, die Funktion so zu erweitern, dass auch Variablen interpretiert und von außen gefüllt werden können sollen, bekommt man mit dieser Variante schnell Probleme, wenn beispielsweise Wertetabellen mathematischer Funktionen wie f(x) = x^2 erstellt werden sollen. Der Term muss dann nicht einmal, sondern beliebig oft - je nach Schrittweite - geparst werden.
Daher möchte ich noch eine weitere Variante, vielmehr eine andere Notation des Terms, vorstellen.

Umgekehrte Polnische Notation

Die umgekehrte polnische Notation (kurz: UPN) wird auch Postfixnotation genannt und ermöglicht es uns, den Term von links nach rechts zu interpretieren. Dabei sind Klammerausdrücke bereits aufgelöst und die Präzedenz der Operatoren sowie die Assoziativität werden ebenfalls beachtet.
Einen Term von der Infix- in die Postfixnotation zu übersetzen ist bei dieser Vorgehensweise also unsere erste Aufgabe.
Das Beispiel

(A+B)/(C-D)

sieht in der UPN-Schreibweise so aus:

A B + C D - /

Danach können wir den Term beliebig oft von links nach rechts übersetzen lassen, ohne ressourcenfressende Funktionen und Befehle wie reguläre Ausdrücke verwenden zu müssen.

Ein passender Algorithmus ist hier sehr schön beschrieben (engl.): http://www.spsu.edu/cs/faculty/bbrown/web_lectures/postfix/.
Ein wenig müssen wir allerdings noch am Algorithmus schrauben, da in dem Beispiel keine Rücksicht auf Potenzen genommen wird. Die Präzendenz des ^-Operator ist noch höher als die der *- und /-Operatoren.

Für die Umsetzung ist es sinnvoll, sich der Datenstruktur "Stapel" (engl. Stack) zu bedienen. Eine entsprechende Klasse könnte so aussehen:

<?php
class StackException extends Exception {}

class 
Stack {
  private 
$data = array();

  function 
__construct() {}

  public function 
push($element) {
    
$this->data[] = $element;
  }

  public function 
pop() {
    if (!
$this->isEmpty()) {
      return 
array_pop($this->data);
    }
    else {
      throw new 
StackException('Stapel leer');
    }
  }

  public function 
top() {
    if (!
$this->isEmpty()) {
      return 
$this->data[count($this->data)-1];
    }
    else {
      throw new 
StackException('Stapel leer');
    }
  }

  public function 
isEmpty() {
    return (
count($this->data) == 0);
  }

  public function 
clear() {
    while (!
$this->isEmpty()) {
      
$this->pop();
    }
  }
}

Da in PHP bereits ähnliche native Funktion wie array_pop() oder array_push() existieren, verzichte ich allerdings im Folgenden auf die Klasse Stack und nutze stattdessen ein simples Array. Die Handhabe ist ähnlich komfortabel und das Script ist - nach einigen Tests - performanter. Die Parserklasse, die den Term in die UPN-Schreibweise übersetzt, schaut so aus:

<?php
class UPN_Parser {
  
/**
   *
   * contains information about the operators, their precedence
   * and their associativity
   *
   * @var array
   */
  
private $operator_info = array();

  
/**
   *
   * contains the operators
   *
   * @var array
   */
  
private $operators = array();

  
/**
   *
   * the stack, operators are pushed onto
   *
   * @var array
   */
  
private $stack = array();

  public function 
__construct() {
    
$this->operator_info = array(
        
'(' => array('precedence' => 0'associativity' => 'left'),
        
')' => array('precedence' => 0'associativity' => 'left'),
        
'+' => array('precedence' => 1'associativity' => 'left'),
        
'-' => array('precedence' => 1'associativity' => 'left'),
        
'/' => array('precedence' => 2'associativity' => 'left'),
        
'*' => array('precedence' => 2'associativity' => 'left'),
        
'^' => array('precedence' => 3'associativity' => 'right'),
    );

    
$this->operators array_keys($this->operator_info);
  }

  
/**
   *
   * @param string $element
   * @return boolean
   */
  
private function isOperand($element) {
    return !
in_array($element$this->operators);
  }

  
/**
   *
   * @param string $operator
   * @return boolean
   */
  
private function isOperator($element) {
    return 
in_array($element$this->operators);
  }

  
/**
   *
   * @param string $element
   * @return boolean
   */
  
private function isLeftParenthesis($element) {
    return (
$element == '(');
  }

  
/**
   *
   * @param string $element
   * @return boolean
   */
  
private function isRightParenthesis($element) {
    return (
$element == ')');
  }

  
/**
   *
   * @param string $element
   * @param string $stack_element
   * @return boolean
   */
  
private function hasHigherPrecedence($element$stack_element) {
    if (
$this->isOperator($element) && $this->isOperator($stack_element)) {
      return (
$this->operator_info[$element]['precedence'] > $this->operator_info[$stack_element]['precedence']);
    }

    return 
false;
  }

  
/**
   *
   * @param string $element
   * @param string $stack_element
   * @return boolean
   */
  
private function hasSamePrecedence($element$stack_element) {
    if (
$this->isOperator($element) && $this->isOperator($stack_element)) {
      return (
$this->operator_info[$element]['precedence'] === $this->operator_info[$stack_element]['precedence']);
    }

    return 
false;
  }

  
/**
   *
   * @param string $element
   * @return boolean
   */
  
private function hasRightAssociativity($element) {
    if (
$this->isOperator($element)) {
      return (
$this->operator_info[$element]['associativity'] === 'right');
    }

    return 
false;
  }

  
/**
   *
   * @param string $term
   * @return string
   */
  
private function sanitiseTerm($term) {
    
// replace , with .
    
$term str_replace(',''.'$term);

    
// solve problems with algebraic signs, following a non-numeric character, except )
    
$search = array(
        
'/([^0-9\)])(\-|\+)([0-9.]+)/',
    );
    
$replace = array(
        
'\1(0\2\3)',
    );
    
$term preg_replace($search$replace$term);

    return 
$term;
  }

  
/**
   *
   * @param string $term
   * @return array
   */
  
public function parseTermToUPN($term) {
    
// clear stack
    
$this->stack = array();
    
    
// sanitise term
    
$term $this->sanitiseTerm($term);

    
// initialise output
    
$output = array();
    
$operand '';

    for (
$i 0$l strlen($term); $i $l; ++$i) {
      
// get next element
      
$element $term[$i];

      if (
$this->isOperand($element)) {
        
// concatenate elements if operand consists of more than one element
        
$operand .= $element;
      }
      else {
        if (
$operand != '') {
          
// push operand to the output and clear variable
          
$output[] = $operand;
          
$operand '';
        }
        if (
$this->isLeftParenthesis($element)) {
          
$this->stack[] = $element;
        }
        else if (
$this->isRightParenthesis($element)) {
          while (!
$this->isLeftParenthesis($this->stack[count($this->stack)-1])) {
            
$output[] = array_pop($this->stack);
          }
          
array_pop($this->stack);
        }
        else if (
count($this->stack) == 0                                                 ||
                
$this->hasHigherPrecedence($element$this->stack[count($this->stack)-1])     )
        {
          
$this->stack[] = $element;
        }
        else if (
$this->hasRightAssociativity($element)                                   &&
                
$this->hasSamePrecedence($element$this->stack[count($this->stack)-1])       )
        {
          
$this->stack[] = $element;
        }
        
// else if - condition:
        // "lower precedence" or ("same precedence" and "left associativity")
        // it should not be necessary to demand for that condition in an "else if" - block
        // because it should be always true, if the other cases are not be respected
        
else {
          
$output[] = array_pop($this->stack);

          --
$i;
        }
      }
    }

    
// push last operand to the output if exists
    
if ($operand != '') {
      
$output[] = $operand;
    }

    
// pop remaining elements from the stack to the output
    
while (count($this->stack) != 0) {
      
$output[] = array_pop($this->stack);
    }

    return 
$output;
  }

  
/**
   *
   * calculates an upn-term, represented by an array
   * 13 ^ 2 corresponds to array(13, 2, '^');
   *
   * @param array $upn
   * @return double
   */
  
public function calcUpn(array $upn) {
    
// clear stack
    
$this->stack = array();
    
    foreach (
$upn as $element) {
      if (
$this->isOperand($element)) {
        
$this->stack[] = $element;
      }
      else if (
$this->isOperator($element)) {
        
$right_operand array_pop($this->stack);
        
$left_operand array_pop($this->stack);
        
        switch(
$element) {
          case 
'+':
            
$this->stack[] = $left_operand $right_operand;
            break;
          case 
'-':
            
$this->stack[] = $left_operand $right_operand;
            break;
          case 
'*':
            
$this->stack[] = $left_operand $right_operand;
            break;
          case 
'/':
            if (
$right_operand != 0) {
              
$this->stack[] = $left_operand $right_operand;
            }
            else {
              throw new 
Exception('Division by zero.');
            }
            break;
          case 
'^':
            
$this->stack[] = pow($left_operand$right_operand);
            break;
          default:
            break;
        }
      }
      else {
        throw new 
Exception('Invalid Operator.');
      }
    }

    return (double)
array_pop($this->stack);
  }
}

Die einzigen öffentlichen Methoden sind

public function parseTermToUPN($term) {}

und

public function calcUpn(array $upn) {}

mit denen ein Term von der Infix- in die Postfixnotation übersetzt und ein Term in der UPN-Schreibweise berechnet werden kann.
Nach dem Aufruf der Funktion parseTermToUPN() mit unserem Term als Parameter erhalten wir den Term in der Postfixnotation zurück:

infix: 12+(2^2)^(3-6)*2+(1/4)^-0.5*(4-(3^2)) 

postfix: 12 2 2 ^ 3 6 - ^ 2 * + 1 4 / 0 0.5 - ^ 4 3 2 ^ - * + 

Dieser Term kann nun stumpf von links nach rechts nach den Regeln für die UPN-Schreibweise berechnet werden.
Aufgerufen werden kann die Klasse so:

<?php
require_once 'upn_parser.class.php';

// define term
$term '12+(2^2)^(3-6)*2+(1/4)^-0.5*(4-(3^2))';

$upn null;
$result null;

$err_msg '';

// converting infix to postfix notation
try {
  
// create UPN parser
  
$upn_parser = new UPN_Parser;
  
$upn $upn_parser->parseTermToUPN($term);

  
$result $upn_parser->calcUpn($upn);
} catch (
Exception $e) {
  
$err_msg $e->getMessage();
}
?>
<h1>
  Infix to postfix convertion
</h1>
<p>
  input (infix):
  <em>
    <?php echo $term?>
  </em>
</p>
<p>
  output (postfix):
  <em>
    <?php echo implode(' '$upn); ?>
  </em>
</p>
<p>
  result:
  <em>
    <?php echo $result?>
  </em>
</p>
<?php
if ($err_msg != '') {
  
?>
<h2>
  An error occured
</h2>
<p>
  error:
  <em>
    <?php echo $err_msg?>
  </em>
</p>
  <?php
}

Anmerkung: Die Fehlerbehandlung der Klasse ist nicht vollständig. Es wird nicht überprüft, ob ein Term - ob in Infix- oder Postfixnotation - valide ist, sprich auch die richtige Anzahl an Operatoren und Operanden vorhanden ist.

Unterschiede bei der Performance

Alle drei Varianten habe ich mit einem Benchmark untersucht. Selbstverständlich ist die Variante mit eval() die Performanteste, sollte aber aufgrund von großen Sicherheitsrisiken in keinem Fall verwendet werden.

Jede Berechnung desselben Terms wurde 10000 mal hintereinander ausgeführt.

Antworten

Anonym | verfasst am 09. Oktober '10 um 20:21 Uhr

#1

Und wie wäre es einfach mit einem Parser, bevor man solchen Regex-Unsinn macht?

Asipak | verfasst am 10. Oktober '10 um 11:12 Uhr

#2

Was ist denn die dritte Variante für dich?
Hast du den Artikel überhaupt bis zum Ende gelesen?

nikosch | verfasst am 06. November '10 um 19:43 Uhr

#3

Krasse Sache, da hast DU ja Zeit investiert ;)

Anmerkung:
„Ein Nachteil ist natürlich, dass der User die PHP-Funktion pow() zum Potenzieren kennen und einsetzen können muss“.
Das ist für den Circumflex aber auch gegeben. Zudem könnte man leicht mit str_replace ersetzen.

Asipak | verfasst am 07. November '10 um 12:57 Uhr

#4

Quote:
Krasse Sache, da hast DU ja Zeit investiert ;)

Es existiert sogar noch eine veraltete Version, die ich zusammengefrickelt habe, bevor ich den oben genannten Algorithmus gefunden habe. Diese war mir für so einen Artikel aber zu schäbig, auch wenn sie bereits den Umgang mit Funktionen wie exp() oder sin() beherscht. :D

Quote:
„Ein Nachteil ist natürlich, dass der User die PHP-Funktion pow() zum Potenzieren kennen und einsetzen können muss“.
Das ist für den Circumflex aber auch gegeben. Zudem könnte man leicht mit str_replace ersetzen.

Gut, das stimmt natürlich. Das hieße aber auch, dass ich bereits für die Lösung mit eval() einen kleinen Parser benötigen würde. Es sollte ja nur die kleine unsaubere Lösung bleiben. ;)
Und da der Punkt "Sicherheitsrisiko" dabei immer noch das Hauptargument bleibt, geht das so in Ordnung, denke ich.

Lars | verfasst am 30. November '11 um 23:42 Uhr

#5

Da ist mir das hier eingefallen... wenn einen mal Langeweile überkommt...

$val["date"] = date("Ymd");
$val["check"] = "TEST";
$val["x"] = 3;
$val["y"] = 2;

$evalthis = "13 == 1 + x + true * 3 + y && (((date == true || DAT == 20090411)|| date == 20080422) && check == \"TEST\")";
$evalthis = "(DATE == 2 || (3 > 2 && (6 >= 6 || 3 != 4 || !(!TEST && 4 == 4 && 2 != 2))) || 3 == 4) || 1 < 2 || 2==3";

echo is($evalthis,$val);

function is(){
//Ist $trace gesetzt, werden die Auswertungsschritte dargestellt
$trace = True;
//Übersetzungen für Bool-Werte in der Darstellungsausgabe
$bool[""] = $bool[0] = "FALSE";
$bool[1] = "TRUE";
//Übernehmen des Evaluationsstrings
$ev = func_get_arg(0);
if($trace){echo $ev."\n\n";}
//Übernehmen der Variablen aus den übergebenen Arrays
for($a=1;$a<func_num_args();$a++){$var = func_get_arg($a);}

//Zuweisen der verschiedenen Operatoren (es wir vermutlich niemand den Bedarf verspüren das Zeichen für eine Addition zu ändern?!)
$pri = "BOOL";$op[$pri]["TRUE"] = 1;$op[$pri]["FALSE"] = 0;
$pri = "QUOT";$op[$pri]["S"] = "'";$op[$pri]["D"] = "\"";
$pri = "MATH";$op[$pri]["MUL"] = "*";$op[$pri]["DIV"] = "/";$op[$pri]["ADD"] = "+";$op[$pri]["SUB"] = "-";$op[$pri]["MOD"] = "%";
$pri = "LOGI";$op[$pri]["AND"] = "&&";$op[$pri]["OR"] = "||";$op[$pri]["ANDW"] = " AND ";$op[$pri]["ORW"] = " OR ";
$pri = "COMP";$op[$pri]["EQ"] = "==";$op[$pri]["NEA"] = "<>";$op[$pri]["NE"] = "!=";$op[$pri]["GTE"] = ">=";$op[$pri]["GT"] = ">";$op[$pri]["LTE"] = "<=";$op[$pri]["LT"] = "<";
//$cd ist ein einzelnes Trennzeichen zum separieren von dynamischen Variableninformationen
//$gs und $ge repräsentieren das GruppenStart- und GruppenEndzeichen
$cd = "x";$gs="(";$ge=")";

//Im ersten Durchlauf werden alle gequoteten Zeichenketten durch Speicherrepräsentanten ersetzt
foreach($op["QUOT"] as $id => $v){
$opcode = "QUOT";
$s[$opcode.$id] = 0;
while(preg_match("#".$v."([^".$v."]*)".$v."#is",$ev,$strsign)){
$store[$opcode.$cd.$id.$cd.$s[$opcode.$id]] = $strsign[1];
$ev = preg_replace("#".preg_quote($strsign[0])."#s",$opcode.$cd.$id.$cd.$s[$opcode.$id]++,$ev,1);
}
}

foreach($op["BOOL"] as $id => $v){
$opcode = "BOOL";
$s[$opcode] = 0;
while(preg_match("#(?:.*\b)(".preg_quote($id).")(?:\b.*)#is",$ev,$consmath)){
$const[$opcode.$cd.$cd.$s[$opcode]] = $consmath[1];
$ev = preg_replace("#(.*\b)(".preg_quote($id).")(\b.*)#is","\${1}".$opcode.$cd.$id.$cd.$v."\${3}",$ev);
}
}

foreach($op["MATH"] as $id => $v){
$opcode = "MATH";
$s[$opcode.$id] = 0;
while(preg_match("#.*([a-z0-9]+) *?(".preg_quote($v)."){1} *?([a-z0-9]+)#is",$ev,$strmath)){
if(substr($strmath[1],0,5)=="BOOL".$cd){list(,,$strmath[1]) = explode($cd,$strmath[1]);}
if(substr($strmath[3],0,5)=="BOOL".$cd){list(,,$strmath[3]) = explode($cd,$strmath[3]);}

$strmath[1] = ($var[$strmath[1]]?$var[$strmath[1]]:$strmath[1]);
$strmath[3] = ($var[$strmath[3]]?$var[$strmath[3]]:$strmath[3]);
if($strmath[2] == $op[$opcode]["ADD"]) $erg = (float)$strmath[1] + (float)$strmath[3];
if($strmath[2] == $op[$opcode]["SUB"]) $erg = (float)$strmath[1] - (float)$strmath[3];
if($strmath[2] == $op[$opcode]["MUL"]) $erg = (float)$strmath[1] * (float)$strmath[3];
if($strmath[2] == $op[$opcode]["DIV"]) $erg = (float)$strmath[1] / (float)$strmath[3];
if($strmath[2] == $op[$opcode]["MOD"]) $erg = (float)$strmath[1] % (float)$strmath[3];
$ev = preg_replace("#(.*)(?:[a-z0-9]+) *?".preg_quote($v)."{1} *?(?:[a-z0-9]+)(.*)#is","\${1}".$erg."\${2}",$ev);
}
}

$opcode = "GRUP";
$s[$opcode] = 0;
while(preg_match("#".preg_quote($gs)."([^".$gs."^".$ge."]*)".preg_quote($ge)."#is",$ev,$strgrp)){
$block[$opcode.$cd.$cd.$s[$opcode]] = $strgrp[1];
$ev = preg_replace("#".preg_quote($strgrp[0])."#s",$opcode.$cd.$cd.$s[$opcode]++,$ev,1);
}
$block[$opcode.$cd.$cd.$s[$opcode]] = $ev;


$opcode = "LOGI";
foreach($block as $grpid => $subset){
$st = preg_split("/".preg_quote($op[$opcode]["OR"])."|".preg_quote($op[$opcode]["ORW"])."/is",$subset);
if(is_array($st)){$grp[$grpid] = $st;} else {$grp[$grpid][] = $st;}
foreach($grp[$grpid] as $id => $v){
$grp[$grpid][$id] = preg_split("/".preg_quote($op[$opcode]["AND"])."|".preg_quote($op[$opcode]["ANDW"])."/is",$v);
}
}

$opcode = "COMP";
foreach($op[$opcode] as $id => $v){$vm[$id] = preg_quote($v);}
$vm = implode("|",$vm);
foreach($grp as $grpid => $or){
foreach($or as $orid => $and){
foreach($and as $andid => $v){
$v = str_replace(" ","",$v);
list($val1,$vc,$val2) = preg_split("/(".$vm.")/",$v,0,PREG_SPLIT_DELIM_CAPTURE);
if($val1[0] == "!"){$val1 = substr($val1,1); $vc = $op[$opcode]["NE"]; $val2 = "BOOL".$cd."TRUE".$cd."1";}

if(!is_numeric($val1) && substr($val1,0,5) != "QUOT".$cd && substr($val1,0,5) != "GRUP".$cd && substr($val1,0,5) != "BOOL".$cd) $val1 = $var[$val1];
if(!is_numeric($val2) && substr($val2,0,5) != "QUOT".$cd && substr($val2,0,5) != "GRUP".$cd && substr($val2,0,5) != "BOOL".$cd) $val2 = $var[$val2];

if(!$vc && !$val2){$vc = $op[$opcode]["EQ"]; $val2 = "BOOL".$cd."TRUE".$cd."1";}

if(substr($val1,0,5) == "QUOT".$cd) $val1 = $store[$val1];
if(substr($val2,0,5) == "QUOT".$cd) $val2 = $store[$val2];

if(substr($val1,0,5) == "BOOL".$cd){list(,$bname,$bval) = explode($cd,$val1);$val1 = (bool) $op["BOOL"][$bname];}
if(substr($val2,0,5) == "BOOL".$cd){list(,$bname,$bval) = explode($cd,$val2);$val2 = (bool) $op["BOOL"][$bname];}

$a = FALSE;

if(substr($val1,0,5) == "GRUP".$cd){$val1 = $gl[$val1];/*$a = $gl[$val1];*/}


if($vc == $op[$opcode]["EQ"]) if($val1 == $val2) $a = TRUE;
if($vc == $op[$opcode]["NE"]) if($val1 != $val2) $a = TRUE;
if($vc == $op[$opcode]["NEA"]) if($val1 != $val2) $a = TRUE;
if($vc == $op[$opcode]["GTE"]) if($val1 >= $val2) $a = TRUE;
if($vc == $op[$opcode]["LTE"]) if($val1 <= $val2) $a = TRUE;
if($vc == $op[$opcode]["LT"]) if($val1 < $val2) $a = TRUE;
if($vc == $op[$opcode]["GT"]) if($val1 > $val2) $a = TRUE;

if($andid == 0){$al = $a;} else {$al = $al & $a;}

if($trace){if($andid > 0) echo " AND "; echo "{".trim(gettype($val1).":".$val1." ".$vc." ".gettype($val2).":".$val2)."} == ".$bool[$al];}
}
if($orid == 0){$ol = $al;} else {$ol = $al | $ol;}

if($trace){if($orid < count($or)-1){ echo " OR ";}}

}
$gl[$grpid] = (bool) $ol;

if($trace){echo "\n=> $grpid == ".$bool[$gl[$grpid]]."\n\n";}

}

if($trace){echo "Condition is ".$bool[$ol]."\n";}

return $ol;
}

Lars | verfasst am 30. November '11 um 23:53 Uhr

#6

...würde mich freuen, wenn es
a) jemandem hilft und
b) jemand der mehr Ahnung von der korrekten Auswertung von Ausdrücken die korrekte Funktion bestätigt.

Bei mir ist die Funktion nie Richtig zum Einsatz gekommen. Sie sollte in einer Seitenbeschreibungssprache eine gewisse Dynamik einbringen. (Natürlich alles ohne evil()) Leider habe ich firmenintern keine "Abnehmer" für eine geskriptete Sprache gefunden... Zu wenig Klickibunti. Zum lernen hat es mir gereicht. ;-)