2010-07-26 10 views
9

Ich habe an einem Parser für einfache Template-Sprache gearbeitet. Ich benutze Ragel.Wie man Vorlagensprachen in Ragel analysiert?

Die Anforderungen sind bescheiden. Ich versuche, [[Tags]] zu finden, die irgendwo in die Eingabezeichenfolge eingebettet werden können.

Ich versuche, eine einfache Template-Sprache zu parsen, etwas, das Tags wie {{foo}} in HTML eingebettet haben kann. Ich habe mehrere Ansätze ausprobiert, um das zu analysieren, musste aber auf einen Ragel-Scanner zurückgreifen und den ineffizienten Ansatz verwenden, nur einen einzelnen Buchstaben als "alles fangen" zu bezeichnen. Ich denke, das ist der falsche Weg. Ich missbrauche im Wesentlichen die längste Übereinstimmung des Scanners, um meine Standardregel zu implementieren (es kann nur 1 Zeichen lang sein, also sollte es immer der letzte Ausweg sein).

%%{ 

    machine parser; 

    action start  { tokstart = p; }   
    action on_tag  { results << [:tag, data[tokstart..p]] }    
    action on_static { results << [:static, data[p..p]] }    

    tag = ('[[' lower+ ']]') >start @on_tag; 

    main := |* 
    tag; 
    any  => on_static; 
    *|; 

}%% 

(Aktionen in Ruby geschrieben, sollte aber leicht zu verstehen sein).

Wie würden Sie einen Parser für solch eine einfache Sprache schreiben? Ist Ragel vielleicht nicht das richtige Werkzeug? Es scheint, dass Sie Ragel Zahn und Nägel bekämpfen müssen, wenn die Syntax so unvorhersehbar ist.

Antwort

20

Ragel funktioniert gut. Sie müssen nur vorsichtig sein, was Sie suchen. Ihre Frage verwendet beide [[tag]] und {{tag}}, aber Ihr Beispiel verwendet [[tag]], also ich denke, das ist, was Sie als besonders behandeln möchten.

Was Sie tun möchten, ist essen Text, bis Sie eine offene Klammer treffen. Wenn dieser Klammer eine andere Klammer folgt, dann ist es an der Zeit, Kleinbuchstaben zu essen, bis Sie eine enge Klammer treffen. Da der Text im Tag keine Klammer enthalten kann, wissen Sie, dass das einzige Nicht-Fehlerzeichen, das dieser Klammer folgt, eine andere Klammer ist. An diesem Punkt bist du wieder da, wo du angefangen hast.

Nun, das ist eine wortgetreue Beschreibung dieser Maschine:

tag = '[[' lower+ ']]'; 

main := (
    (any - '[')* # eat text 
    ('[' ^'[' | tag) # try to eat a tag 
)*; 

Der schwierige Teil ist, wo rufen Sie Ihre Aktionen? Ich behaupte nicht, die beste Antwort darauf haben, aber hier ist, was ich kam mit:

static char *text_start; 

%%{ 
    machine parser; 

    action MarkStart { text_start = fpc; } 
    action PrintTextNode { 
    int text_len = fpc - text_start; 
    if (text_len > 0) { 
     printf("TEXT(%.*s)\n", text_len, text_start); 
    } 
    } 
    action PrintTagNode { 
    int text_len = fpc - text_start - 1; /* drop closing bracket */ 
    printf("TAG(%.*s)\n", text_len, text_start); 
    } 

    tag = '[[' (lower+ >MarkStart) ']]' @PrintTagNode; 

    main := (
    (any - '[')* >MarkStart %PrintTextNode 
    ('[' ^'[' %PrintTextNode | tag) >MarkStart 
)* @eof(PrintTextNode); 
}%% 

Es gibt ein paar nicht-offensichtliche Dinge:

  • Die eof Aktion ist erforderlich, weil %PrintTextNode wird immer nur beim Verlassen einer Maschine aufgerufen. Wenn die Eingabe mit normalem Text endet, gibt es keine Eingabe, um diesen Status zu verlassen. Da es auch aufgerufen wird, wenn die Eingabe mit einem Tag endet und kein endgültiger unbedruckter Textknoten vorhanden ist, testet PrintTextNode, ob Text vorhanden ist.
  • Die %PrintTextNode Aktion eingebettet in nach dem ^'[', weil erforderlich, obwohl wir den Anfang markiert, wenn wir die [ getroffen, nachdem wir eine nicht [ treffen, werden wir versuchen, beginnen wieder etwas zu analysieren und den Startpunkt bemerken. Wir müssen diese beiden Zeichen löschen, bevor dies geschieht, daher dieser Aktionsaufruf.

Der vollständige Parser folgt.Ich habe es in C, weil das ist, was ich weiß, aber man sollte es wiederum in der Lage sein, welche Sprache Sie ziemlich leicht benötigen:

/* ragel so_tag.rl && gcc so_tag.c -o so_tag */ 
#include <stdio.h> 
#include <string.h> 

static char *text_start; 

%%{ 
    machine parser; 

    action MarkStart { text_start = fpc; } 
    action PrintTextNode { 
    int text_len = fpc - text_start; 
    if (text_len > 0) { 
     printf("TEXT(%.*s)\n", text_len, text_start); 
    } 
    } 
    action PrintTagNode { 
    int text_len = fpc - text_start - 1; /* drop closing bracket */ 
    printf("TAG(%.*s)\n", text_len, text_start); 
    } 

    tag = '[[' (lower+ >MarkStart) ']]' @PrintTagNode; 

    main := (
    (any - '[')* >MarkStart %PrintTextNode 
    ('[' ^'[' %PrintTextNode | tag) >MarkStart 
)* @eof(PrintTextNode); 
}%% 

%% write data; 

int 
main(void) { 
    char buffer[4096]; 
    int cs; 
    char *p = NULL; 
    char *pe = NULL; 
    char *eof = NULL; 

    %% write init; 

    do { 
    size_t nread = fread(buffer, 1, sizeof(buffer), stdin); 
    p = buffer; 
    pe = p + nread; 
    if (nread < sizeof(buffer) && feof(stdin)) eof = pe; 

    %% write exec; 

    if (eof || cs == %%{ write error; }%%) break; 
    } while (1); 
    return 0; 
} 

Hier einiger Testeingang:

[[header]] 
<html> 
<head><title>title</title></head> 
<body> 
<h1>[[headertext]]</h1> 
<p>I am feeling very [[emotion]].</p> 
<p>I like brackets: [ is cool. ] is cool. [] are cool. But [[tag]] is special.</p> 
</body> 
</html> 
[[footer]] 

Und hier ist der Ausgang aus dem Parser:

Der letzte Textknoten enthält nur den Zeilenumbruch am Ende der Datei.