Ankündigung

Einklappen
Keine Ankündigung bisher.

StringParser bauen, wie setz ich den Lexer um

Einklappen
X
  • Filter
  • Zeit
  • Anzeigen
Alles löschen
neue Beiträge

  • StringParser bauen, wie setz ich den Lexer um

    Hallo,

    ich bin gerade dabei einen SQL-Lexer(Scanner?)/Parser zu bauen, eigentlich nur, weil ich einen OR-Mapper (für mich) baue, der auch DDL lesen können soll. Und zwar richtig und nicht (ausschließlich/hauptsächlich) per RegExp.

    Kurzer Quellcodeausschnitt:

    Anti_StringParser übernimmt das Parsen (von einem einfachen CREATE TABLE-Statement) anhand der Token-Definition von Anti_StringParser_Mysql. Als Identifier habe ich u.a. einfach mal eine RegExp-Klasse benutzt, die bestimmte Zeichenketten als zusammengehörig erkennt. Die Problematik besteht jetzt eigentlich wenn variable Codeteile (SQL-Identifier, also Tabellennamen oder sogar Strings) kommen:
    PHP-Code:
    <?php
    class Anti_StringParser
    {
        
    // ..

        
    public function parse($source// z.B. = "CREATE TABLE .."
        
    {
            
    $position 0;
            
    $source   = (string)$source;
            
    $length   mb_strlen($source); // Länge, damit ich weiß wann zu Ende geparset ist
            
    $tokens   = array(); // meine Liste der erzeugten Tokens
            
    $maxLoop  10000// infinite loop Schutz ;)
            
    do {
                
    $current mb_substr($source$position); // relevanter Quellcodeabschnitt
                
    $found   false;
                foreach (
    $this->_identifiers as $name => $identifier) {
                    
    // durchlaufe alle Identifier (siehe nächstes Quellcodebeispiel für die Definitionen)
                    
    list ($match$offset$token) = $identifier->matches($current$source$position); // hat ein Identifier einen Token gefunden?
                    
    if (!$match) {
                        continue; 
    // nicht? dann frag den nächsten
                    
    }
                    
    $found     true;
                    
    $position += $offset// OK offset auf die aktuelle Position rechnen, damit das nächste Stück Code gelesen werden kann
                    
    $tokens[]  = $token;
                    break;
                }
                if (!
    $found) {
                    
    // keiner Identifier hat was gefunden, Quellcode wurde nicht erkannt/ist nicht gültig
                    
    throw new Exception("unknown identifier at position [$position]:\n<var>$current</var>");
                }
                
                
    $parsed = ($position == $length); // sind wir fertig?
                
                
    if (--$maxLoop <= 0) {
                    throw new 
    Exception("infinite loop at position [$position]: $current");
                }
            } while (!
    $parsed);
            
            
    Debug::stop($tokens); // ~ var_dump
        
    }
    }
    ?>
    PHP-Code:
    <?php
    class Anti_StringParser_Mysql extends Anti_StringParser
    {
        public function 
    init()
        {
            
    $this->_addIdentifier('regexp''whitespace''\s+'); // whitespace Erkennung per RegExp
            
    $this->_addIdentifier('regexp''keyword',    '\b[A-Za-z][A-Za-z0-9\_]*\b'); // Keyword erkennung
            
    $this->_addIdentifier('char',   '(');
            
    $this->_addIdentifier('char',   ')');
            
    $this->_addIdentifier('char',   '=');
            
    $this->_addIdentifier('char',   ';');
            
    $this->_addIdentifier('char',   ',');
            
    $this->_addIdentifier('regexp''string',     "'[^']*'"); // Strings, erstmal ganz einfach
            
    $this->_addIdentifier('regexp''number',     "\b[0-9]+\b"); // Zahlen (für z.B. "int(10)")
            
    $this->_addIdentifier('regexp''identifier'"`[^`]*`"); // DB-Identifier (nicht zu verwechseln mit dem Klassentyp, der die Tokens erstellt), doofe Namenswahl vlt.
        
    }
    }
    ?>
    PHP-Code:
    <?php
    class Anti_StringParser_Identifier_RegExp extends Anti_StringParser_Identifier_Abstract
    {
        
    // ..
        
        
    public function matches($current$source$position)
        {
            try {
                
    $pattern $this->_delimiter '^' $this->_regExp $this->_delimiter $this->_modifiers// regexp bauen
                
    $success = (bool)preg_match($pattern$current$matches);
                if (!
    $success) {
                    return array(
    falsenullnull); // nix gefunden
                
    }
                
    $match   $matches[0];
                
    $offset  mb_strlen($matches[0]); // offset berechnen
                
    $token   = new Anti_StringParser_Token($this->_name$match); // token bauen
                
    $result  = array(true$offset$token); 
                
                return array(
    true$offset$token); // Treffer zurückliefern
            
    } catch (Exception $e) {
                throw new 
    Exception("match with regular expression failed"0$e);
            }
        }
    }
    ?>
    Als Ergebnis bekomme ich relativ fix eine halbwegs brauchbare Ausgabe:
    Code:
    CREATE TABLE `benchmark` (
      `id` int(10) unsigned NOT NULL AUTO_INCREMENT,
      `title` varchar(255) COLLATE utf8_bin NOT NULL,
      `description` text COLLATE utf8_bin NOT NULL,
      `last_modified` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
      `created` timestamp NOT NULL DEFAULT '0000-00-00 00:00:00',
      PRIMARY KEY (`id`)
    ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin
    Code:
    DEBUG ARG 1:
    
    array(104) {
      [0]=>
      object(Anti_StringParser_Token)#52 (2) {
        ["_name"]=>
        string(10) "whitespace"
        ["_value"]=>
        string(29) "        
            
            		
    "
      }
      [1]=>
      object(Anti_StringParser_Token)#53 (2) {
        ["_name"]=>
        string(7) "keyword"
        ["_value"]=>
        string(6) "CREATE"
      }
      [2]=>
      object(Anti_StringParser_Token)#54 (2) {
        ["_name"]=>
        string(10) "whitespace"
        ["_value"]=>
        string(1) " "
      }
      [3]=>
      object(Anti_StringParser_Token)#55 (2) {
        ["_name"]=>
        string(7) "keyword"
        ["_value"]=>
        string(5) "TABLE"
      }
      [4]=>
      object(Anti_StringParser_Token)#56 (2) {
        ["_name"]=>
        string(10) "whitespace"
        ["_value"]=>
        string(1) " "
      }
      [5]=>
      object(Anti_StringParser_Token)#57 (2) {
        ["_name"]=>
        string(10) "identifier"
        ["_value"]=>
        string(11) "`benchmark`"
      }
      [6]=>
      object(Anti_StringParser_Token)#58 (2) {
        ["_name"]=>
        string(10) "whitespace"
        ["_value"]=>
        string(1) " "
      }
      [7]=>
      object(Anti_StringParser_Token)#59 (2) {
        ["_name"]=>
        string(1) "("
        ["_value"]=>
        string(1) "("
      }
    (naja usw.).

    Leider nur halbwegs brauchbar. Siehe object#57. Schön und gut, dass der Identifier erkannt wurde, allerdings wären es ja richtiger Token "`" + Token String "benchmark" + Token "`".

    Ich denke hier habe ich das Vorgehen von einem Scanner (oder heißt es Lexer?) nicht verstanden. Er muss ja, sobald er ein "`" findet auf einen anderen Modus schalten (lies alles ein, (fast) egal was kommt bis zum nächsten "`"). Ursprünglich dachte ich, dass ein Lexer erstmal alles ganz dumm zerlegt und erst der Parser dann sagt, he da ist ein Token "`" das niemals geschlossen wird. Wenn das der Fall wäre, könnte mein Lexer ja aber nicht wissen, dass nach dem öffnenden "`" nur stupide alles bis zum nächsten "`" einlesen muss. Hier ist mein Lexer offenbar noch zu blöd für, also vermutlich falsch konzipiert.

    Im Internet konnte ich leider keine gute Anleitung für den Lexer/Parserbau finden. Der Wikiartikel dazu ist auch sehr knapp.

    Oder bin ich jetz schon auf einem guten Weg und muss nurnoch die "identifier" oder "string" Tokens intern auf mehrere verteilen (Aggregation)?
    "Mein Name ist Lohse, ich kaufe hier ein."


  • #2
    Scheisse mit der Aggregation, Eventhandler, ArrayAccess-Interface und Closures klappts nahezu perfekt:
    PHP-Code:
            $this->_addIdentifier('regexp''ml-comment'"\/\*.+\*\/")
                 ->
    addModifier('s')
                 ->
    bind('onSuccess', function($success$offset$token) {
                    
    $aggregate   = new Anti_StringParser_Token_Aggregate();
                    
    $aggregate[] = new Anti_StringParser_Token('ml-comment-open''/*');
                    
    $aggregate[] = new Anti_StringParser_Token('comment'mb_substr($token->getValue(), 2, -2));
                    
    $aggregate[] = new Anti_StringParser_Token('ml-comment-close''*/');
                    return array(
    $success$offset$aggregate);
                }); 
    Macht haargenau was es soll. Falls euch trotzdem was am Konzept nicht gefällt oder ihr es besser wisst, nur her damit!
    Code:
      object(Anti_StringParser_Token_Aggregate)#67 (1) {
        ["_elements":protected]=>
        array(3) {
          [0]=>
          object(Anti_StringParser_Token)#68 (2) {
            ["_name":protected]=>
            string(15) "ml-comment-open"
            ["_value":protected]=>
            string(2) "/*"
          }
          [1]=>
          object(Anti_StringParser_Token)#69 (2) {
            ["_name":protected]=>
            string(7) "comment"
            ["_value":protected]=>
            string(31) " scho
    
    
    
    -- queeeek
    wieder oins"
          }
          [2]=>
          object(Anti_StringParser_Token)#70 (2) {
            ["_name":protected]=>
            string(16) "ml-comment-close"
            ["_value":protected]=>
            string(2) "*/"
          }
        }
      }
    Evtl. noch Fliegengewichtspattern einbauen xD



    Edit:

    So, nach dem ich gemerkt hab wie rüpelhaft man in PHP mit Strings umgehen kann hab ich das ganze umgemodelt auf Streams, ist jetzt zwar immer noch lahm wie ne Krücke, aber kann immerhin 500 KB mal unterm Timeout/Memory Limit verarbeiten. Klingt für mich aber immer noch voll wenig, weiß aber nicht wo ich noch an der Performance schrauben könnte. Prüfe bereits Identifier auf maximal 256 Zeichen, wie im Handbuch von MySQL erläutert, bei Strings gehe ich momentan von 2^16 Bytes aus (entspricht TEXT), lässt sich aber anpassen. Das lustige ist, ich hatte bei fets() anfangs als $length Parameter PHP_INT_MAX genommen, weil mir strlen(stream_get_contents()) für die Restlänge doch etwas langsam vorkam. Wurde sehr schnell eines besseren belehrt, als PHP 2 GB Speicher allokieren wollte

    Das komische ist, dass jetzt feof() nicht mehr funktioniert und ich einen (von mir geworfenen) Syntaxerror bekomme, wenn das Dateiende erreicht ist. Die Position (per ftell() überprüft) entspricht jetzt genau dem Dateiende, auch wenn Notepad++ und Windows das anders sehen. In Eclipse wird scheinbar die wahre Bytegröße (über Properties) ermittelt und das ist genau der Wert. Die Hinweise im feof()-Handbuch treffen auch meinen Fall nicht zu, habe zumindest den Server durchgestartet. Der erste Usernote hat allerdings funktioniert, allerdings etwas schade, weil das so vermutlich etwas langsamer ist als feof() selbst. Schaffe jetzt etwa 5500 Tokens pro Sekunde zu erzeugen, wobei ich die Tokens in Arrays umwandel, ich glaub bei ein paar tausend Objekten geht PHP dann doch in die Knie. Fliegengewichtspattern ist also Pflicht.
    "Mein Name ist Lohse, ich kaufe hier ein."

    Kommentar


    • #3
      Gibt es den Code irgendwo in form eines repositories oder so dass man mal damit spielen/testen kann ?

      Hast du dir mal den uralten PEAR-SQL-Parser oder die PEAR-Systeme zum Thema Parsen/Lexen angeschaut/evaluiert ?
      robo47.net - Blog, Codeschnipsel und mehr
      | Caching-Klassen und Opcode Caches in php | Robo47 Components - PHP Library extending Zend Framework

      Kommentar


      • #4
        Nein, PEAR ist mir dabei noch nicht über den Weg gelaufen, lohnt es sich den mal anzuschauen? Hab ihn mir mal in die Bookmarks geschoben und schau heut nachmittag drauf.

        Hier ist mal der aktuelle Stand von DB-Reader fürs spätere ORM und der Scanner/Parser. Habs so hingebogen dass dus grad irgendwo ins localhost kopieren kannst. Wenn du Zeit findest wärs toll wenn du nen Blick riskieren könntest, vor allem soviel Code ist es garnicht, eigentlich nur Anti_StringParser_Myql für die Definition der Tokenmatcher und Anti_StringParser::_scan() für das Auslesen (25 Zeilen).
        Angehängte Dateien
        "Mein Name ist Lohse, ich kaufe hier ein."

        Kommentar


        • #5
          Ob sich PEAR lohnt kann ich dir nicht sagen, hab ich noch nicht genutzt, nur weis ich dass es da was in der richtung gibt

          btw entwickelst du sowas übern browser und ohne tests ?
          robo47.net - Blog, Codeschnipsel und mehr
          | Caching-Klassen und Opcode Caches in php | Robo47 Components - PHP Library extending Zend Framework

          Kommentar


          • #6
            Äh ja, ich bin da ganz oldschool. Ist ja wie gesagt nur für mich, weil ich meistens nach dem selben Schema arbeite. Datenbank erstellen, abstrakte Data-Models generieren (mit setter/getter) und dann muss ich nicht mehr soviel tippen. Erspart unglaublich viel Arbeit. Nächster Schritt wäre dann, dass ich mir auch die Zugriffe auf die DB generieren lasse (CRUD). Die Information ist ja da und ich muss wieder etwas weniger tippen. Hast du denn Erfahrung mit dem Parserbau? Zufrieden wäre ich ja, wenn man damit auch einen BBCode-Parser oder ähnliches konfigurieren könnte. Nur wie es mit Freitexten aussieht weiß ich gerade noch nicht. Ich glaub ich muss einfach nochmal die richtige Lektüre finden.
            "Mein Name ist Lohse, ich kaufe hier ein."

            Kommentar


            • #7
              oldschool ?

              Nein mit Parserbau hab ich leider keine Erfahrung, wollte mir das nur mal am anschauen weil es mich ein bißchen interessiert hat.

              Langsam würde ich es auch nicht unbedingt bezeichnen, ist ja auch zumindest für klassengeneration nichts was sooooo relevant wäre, bei crud und ddl-query-parsing dann wohl schon eher.

              bei mir braucht er für die scanner.php 0.03s:

              php -f scanner.php > /dev/null
              0.03s user 0.02s system 97% cpu 0.053 total

              für die db-reader 0.34 (13 datenbanken mit insgesamt ~ 190 tabellen)
              php -f db-reader.php > /dev/null
              0.34s user 0.10s system 90% cpu 0.482 total


              Was das auslesen von Datenbankstrukturen angeht fällt mir noch Doctrine1 ein, das kann einem models auch aus einer db erstellen, daraus yaml erzeugen und jeweils in die andere richtung und das für verschiedene DB-systeme, vielleicht bringt dich ein blick in die srouces weiter wie die das alles machen weil ne DDL haben die ja auch für queries.
              robo47.net - Blog, Codeschnipsel und mehr
              | Caching-Klassen und Opcode Caches in php | Robo47 Components - PHP Library extending Zend Framework

              Kommentar


              • #8
                Hi,

                also meine CRUD-Klassen sollen dann selbst den Parser nicht benutzen, eher umgekehrt soll der Parser dann benutzt werden um die (abstrakten) Klassen einmalig zu generieren. Wenn sich was ändert brauch ich dann nur das Skript anwerfen und sie werden erneut erzeugt.

                Der Parser klappt übrigens unglaublich gut, baue grade den Parse-Tree für CREATE TABLE-Statements auf, eigentlich wie die formale Sprache hier:
                http://dev.mysql.com/doc/refman/5.1/...ate-table.html

                Also mit Platzhalter (ich benutz Events), um z.B. für "tbl_name" einen generierten Parse-Tree zu erzeugen. Klappt sogar so gut, dass er genauer ist als ich. TEMPORARY ist kein Keyword! Bis ich das endlich gemerkt hab ... Hier mal ein kurzer Ausschnitt (wens interessiert):

                PHP-Code:
                <?php
                        $this
                ->_bind("onParse", function($tokens) use ($p$b) {
                            return 
                $p->parseTokenTree(
                                
                $tokens,
                                
                $b->listing// eine Abfolge von Befehlen
                                    
                $b->keyword("CREATE"), // muss das Keyword CREATE treffen
                                    
                $b->optional(
                                        
                $b->token("T_STRING""TEMPORARY"// optional der String (!) TEMPORARY
                                    
                ),
                                    
                $b->keyword("TABLE"),
                                    
                $b->optional(
                                        
                $b->keywords("IF""NOT""EXISTS")
                                    ),
                                    
                $b->trigger('tbl_name'), // triggeret einen Event an, siehe weiter unten
                                    
                $b->optional(
                                        
                $b->listing(
                                            
                $b->token("T_BRACKET_ROUND_OPEN"), // runde Klammer erwartet
                                            
                $b->trigger("create_definition"),
                                            
                $b->loop// komma-separierte Liste simulieren
                                                
                $b->token("T_COMMA"),
                                                
                $b->trigger("create_definition")
                                            ),
                                            
                $b->token("T_BRACKET_ROUND_CLOSE")
                                        )
                                    ),
                                    
                $b->optional(
                                        
                $b->trigger("table_options")
                                    ),
                                    
                $b->optional(
                                        
                $b->trigger("select_statement")
                                    )
                                )
                            );
                        });
                        
                $this->_bind("tbl_name", function() use ($b) {
                            return 
                $b->listing(
                                
                $b->trigger("identifier"), // table name allein, oder als db-name
                                
                $b->optional(
                                    
                $b->listing(
                                        
                $b->token("T_DOT"),
                                        
                $b->trigger("identifier"// oder dann hier erst table name
                                    
                )
                                )
                            );
                        });
                        
                $this->_bind("identifier", function() use ($b) {
                            return 
                $b->combineOr(
                                
                $b->listing(
                                    
                $b->token("T_QUOTE_BACKTICK"),
                                    
                $b->token("T_STRING"),
                                    
                $b->token("T_QUOTE_BACKTICK")
                                ),
                                
                $b->token("T_STRING")
                           );
                        });
                ?>
                Erzeugt einen riesigen Array mit Befehlen, den mein Parser dann durchgeht:
                PHP-Code:
                <?php
                    
                public function parseTokenTree(array &$tokens, array $tree$optional false)
                    {
                        
                $token current($tokens);
                        
                $key   key($tree);
                        
                $value current($tree);
                        
                        switch (
                $key) {
                            case 
                "listing":
                                
                $success true;
                                foreach (
                $value as $node) {
                                    
                $success  $this->parseTokenTree($tokens$node$optional);
                                    if (
                $optional && !$success) {
                                        return 
                true;
                                    }
                                    
                                    
                $success  $success || $optional;
                                    
                $optional $optional && !$success;
                                }
                                return 
                $success;
                            case 
                "token":
                                
                $match $value === $token || ($value[1] === null && $token[0] == $value[0]);
                                if (
                $match) {
                                    echo 
                "matching token: " $token[1] . " ";
                                    
                next($tokens);
                                    return 
                true;
                                }
                                if (
                $optional) {
                                    return 
                null;
                                }
                                
                Debug::stop($token$key$value);
                                throw new 
                Exception("unexpected token #1: " $this->_getTokenName($token[0]) . "(" $token[1] . ")");
                            case 
                "optional":
                                return 
                $this->parseTokenTree($tokens$valuetrue);
                            case 
                "trigger":
                                return 
                $this->parseTokenTree($tokens$this->_trigger($value), $optional);
                            case 
                "or":
                                foreach (
                $value as $node) {
                                    if (
                $this->parseTokenTree($tokens$nodetrue)) {
                                        return 
                true;
                                    }
                                }
                                throw new 
                Exception("unexpected token #2: " $this->_getTokenName($token[0]) . "(" $token[1] . ")");
                            default:
                                
                Debug::stop($token$key$value);
                        }
                    }
                ?>
                Wird noch etwas entschlakt, "listing" usw. wird durch numerische Konstanten ersetzt usw., aber anders kann ich den Array sonst manuell nicht mehr lesen.

                Danke übrigens fürs Testen, die Zeit klingt gut für mich, reicht wie du schon gesagt hast für Einmal-Vorgänge völlig aus.
                "Mein Name ist Lohse, ich kaufe hier ein."

                Kommentar


                • #9
                  Warum gehst du denn für das Generieren der Modelle über die DDL? Wenn du das Modell aus einer existierenden Tabelle generieren willst (backwards engineering) dann kann man dem DBMS doch bestimmt per Query die Definition entlocken?!

                  Oder versteh ich gerade deine Intention falsch?

                  Kommentar


                  • #10
                    Soweit ich weiß bekomme ich anders nicht die Fremdschlüsselbeziehungen. Außerdem kann es sein, dass die Tabelle eben nur als DDL vorliegt und nicht als existierende DB.
                    "Mein Name ist Lohse, ich kaufe hier ein."

                    Kommentar


                    • #11
                      Zitat von Chriz Beitrag anzeigen
                      Soweit ich weiß bekomme ich anders nicht die Fremdschlüsselbeziehungen.
                      Für MySQL gibts es diese INFO in information_schema.KEY_COLUMN_USAGE

                      Kommentar


                      • #12
                        Wo kommt die DDL denn her? Bei mir mache ich es andersrum (forward engineering). Mein Modell liegt in XML Dateien, die vom ORM gelesen werden. Falls das Schema in der DB nicht existiert generiere ich die DDL-Statements und lege sie an. Ist einfacher

                        Wenn die DDL-Statements aber bereits gegeben sind, bleibt nur das Parsen oder das Auslesen des Schemas aus der DB. Ich würde trotzdem zu letzterem tendieren Aber wo du schon mal angefangen hast

                        Kommentar


                        • #13
                          Die DDL kommt aus der Datenbank, mit SHOW CREATE TABLE. Danke hts übrigens für die Info. Ich bin halt nicht sicher ob jeder Zugriff auf die Tabelle hat. Deshalb versuche ich es gerade so. Im übrigen machts grad voll Spass, wollt schon immer nen Parser bauen Und es klappt auch, nur dass ich mit optionalen Treeelementen noch nicht optimal umgehen kann. Aber von diesem "kleinen" Problem abgesehen, kann ich CREATE TABLE-Statements parsen. Für alle neuen DDLs (ALTER TABLE usw.) brauch ich nur noch den ParseTree zu erzeugen. Verschachtelungen kann er auch lesen, also z.B. Sub-SELECTs gehen theoretisch auch. Ich kann ja mal eine Version in den nächsten Tagen online stellen. Danach hab ich auch schon des nächste Einsatzgebiet für den Parser: Ich will deutsche Zeitangaben parsen, in etwa wie strtotime() nur auf deutsch. Bräuchte das als Feature für meinen Kalender, ist etwas mühselig Termine (vor allem Wiederholungstermine) anzugeben.
                          "Mein Name ist Lohse, ich kaufe hier ein."

                          Kommentar


                          • #14
                            Wie definierst du denn deinen Syntax-Tree? ASN1?

                            Kommentar


                            • #15
                              ASN1? Habe ich noch nichts von gehört und habs auch irgendwann aufgegeben ne sinnvolle Anleitung im Netz zu finden. Hab mich dunkel an die Vorlesung zum Parserbau erinnert und das Problem sitzt wie immer zwischen Stuhl und Keyboard. Soviele K***fehler weil ich den Parserbaum falsch aufgebaut habe. Ich denk dieses WE krieg ich ne fertige Vorabversion für CREATE-TABLE-Statements hin (hoffentlich) und stells mal hier vor.

                              Edit: hab mir mal den Wikiartikel angeschaut. Also mein Baum sieht so aus, dass ich Schlüssel/Wertepaare (ohne Objekte, nur Arrays, der Performance wegen) habe. Der Schlüssel stellt die Art der Verarbeitung da, der Wert eben das Subjekt bzw. den weiteren Parserbaum. Als Schlüssel habe ich
                              • P_PARSER: lädt einen Parser, könnte z.B. für HTML mit Inline-JavaScript sinnvoll sein
                              • P_LISTING: nach ASN1 vlt. SEQUENCE ?
                              • P_TOKEN: Vergleicht Quellcodetoken mit Parserbaumtoken
                              • P_OPTIONAL: macht den Parserbaum optional
                              • P_ALIAS: lädt einen Parserbaumabschnitt nach
                              • P_OR: prüft ob einer der Parserbaumabschnitte positiv ist
                              • P_LOOP: Schleife von Befehlen, die mindestens einmal ausgeführt werden können, werd ich ersetzen durch ein P_REPEAT, für das man genaue, maximale, minimale oder unbegrenzte Wiederholung festlegen kann
                              • P_DEBUG: um an einer bestimmten Stelle im Parserbaum eine Funktion auszuführen, meistens nur var_dump() für die Ausgabe


                              Muss zugeben ich weiß nicht, wie das in ASN1 aussehen würde, oder ob das überhaupt geht. Mir fehlt da wirklich die Lektüre zu. Evtl. geh ich mal in die Bücherei.
                              "Mein Name ist Lohse, ich kaufe hier ein."

                              Kommentar

                              Lädt...
                              X