1unitedpower: Im Sting alles mit (xxx) ersetzten

Beitrag lesen

Spannend wird es vor dann, wenn im zu verarbeitenden Text die Klammerschachtelung falsch ist. Also zum Beispiel: Der Mann (bekleidet mit einer [nicht löchrigen) Jeans] steht hinter dem Haus.

Mein Ansatz wäre, nach geschlossenen Klammern zu suchen (z.B. mit p = strcspn($sting, ")}]") und von dort aus rückwärts nach der passenden öffnenden Klammer (z.B. mit strrpos). Hat man auf diese Weise ein geklammertes Fragment gefunden, muss man noch prüfen ob es einer der öffnenden Klammern enthält; wenn ja, liegt ein Schachtelungsfehler vor. Wenn nicht, kann man mit dem Fragment tun, was immer nötig ist, und zwar so, dass das gefundene Klammerpaar nachher nicht mehr im Sting steht. Und danach fängt man von vorn an, solange, wie etwas gefunden wird.

Ich würde das anders lösen, hier mein (ungetester) Entwurf eines fehlertoleranten Parsers. Wie üblich, habe ich den Parser aufgeteilt in eine lexikalische und eine syntaktische Phase. Der Lexer zerlegt den Eingabestring in einen Token-Stream: Ein Token ist entweder eine runde oder eckige Klammer, oder ein Text ohne Vorkommen von Klammern:

<?php

function tokenize(string $input) : Generator
{
    $buffer = '';
    for ($i = 0; $i < mb_strlen($input); $i++) {
        $char = mb_substr($input, $i, 1);
        switch ($char) {
            case '(':
            case ')':
            case '[':
            case ']':
                yield $buffer;
                $buffer = '';
                yield $char;
                break;
            default:
                $buffer .= $char;
        }
    }
    yield $buffer;
}

Der Parser bekommt einen Token-Stream und baut einen Stack auf: Immer wenn eine öffnende Klammer gelesen wird, wird sie auf den Stack gelegt. Wenn eine schließende Klammer gelesen wird, wird überprüft ob oben auf dem Stack eine passende öffnende Klammer liegt. Falls ja, wird das Element vom Stack genommen. Falls der Stack leer ist oder die schließende Klammer nicht zur öffnenden Klammer passt, wird ein Parser-Fehler generiert. Wenn ein Textfetzen ohne Klammern gelesen wird, wird er zusammen mit seiner Verschachtelungstiefe ausgegeben. Wenn alle Token durchlaufen wurden und der Stack noch ungeschlossende Klammern enthält, wird für jede der Klammern ein Parser-Fehler produziert.

function parse(Generator $tokens) : Generator
{
    $stack = new SplStack();
    for ($tokens as $token) {
        switch ($token) {
            case '(':
            case '[':
                $stack->push($token);
                break;
            case ')':
                if ($stack->count() === 0) {
                    yield new Expcetion("Unmatched closing round bracket.")
                } elseif ($stack->top() !== '(') {
                    yield new Exception("Expected closing square bracket, but found closing round bracket.")
                } else {
                    $stack->pop();
                }
                break;
            case ']':
                if ($stack->count() === 0) {
                    yield new Expcetion("Unmatched closing square bracket.")
                } elseif ($stack->top() !== '[') {
                    yield new Exception("Expected closing round bracket, but found closing square bracket.")
                } else {
                    $stack->pop();
                }
                break;
            default:
                yield [$token, $stack->count()]
        }
    }
    while ($item = $stack->pop()) {
        switch ($item) {
            case '(':
                yield new Expection("Unmatched opening round bracket.");
                break;
            case '[':
                yield new Expection("Unmatched opening quare bracket.");
                break;
        }
    }
}

Mit fehlertolerant meine ich, dass der Parser auch nach einem Parser-Fehler weitermacht so gut er kann.