Frage

Ich habe an einem Parser für eine einfache Vorlagensprache gearbeitet.Ich verwende Ragel.

Die Anforderungen sind bescheiden.Ich versuche [[Tags]] zu finden, die an einer beliebigen Stelle in der Eingabezeichenfolge eingebettet werden können.

Ich versuche, eine einfache Vorlagensprache zu analysieren, in die Tags wie {{foo}} in HTML eingebettet sein können.Ich habe mehrere Ansätze ausprobiert, um dies zu analysieren, musste aber auf die Verwendung eines Ragel-Scanners zurückgreifen und den ineffizienten Ansatz verwenden, nur ein einziges Zeichen als „Alles aufzufangen“ zu finden.Ich glaube, das ist der falsche Weg.Ich missbrauche im Wesentlichen die Voreingenommenheit des Scanners für die längste Übereinstimmung, um meine Standardregel zu implementieren (sie kann nur 1 Zeichen lang sein, daher sollte sie 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, sollten aber leicht zu verstehen sein).

Wie würden Sie einen Parser für eine so einfache Sprache schreiben?Ist Ragel vielleicht nicht das richtige Werkzeug?Es scheint, dass man Ragel mit allen Mitteln bekämpfen muss, wenn die Syntax so unvorhersehbar ist.

War es hilfreich?

Lösung

Ragel funktioniert gut.Sie müssen nur vorsichtig sein, was Sie zusammenbringen.Ihre Frage verwendet beides [[tag]] Und {{tag}}, aber Ihr Beispiel verwendet [[tag]], also denke ich, dass Sie das als etwas Besonderes betrachten wollen.

Was Sie tun möchten, ist Text zu essen, bis Sie auf eine offene Klammer treffen.Wenn auf diese Klammer eine weitere Klammer folgt, ist es an der Zeit, mit Kleinbuchstaben zu beginnen, bis Sie auf eine schließende Klammer stoßen.Da der Text im Tag keine eckigen Klammern enthalten darf, wissen Sie, dass das einzige Nichtfehlerzeichen, das auf die schließende Klammer folgen kann, eine weitere schließende Klammer ist.An diesem Punkt sind Sie wieder da, wo Sie angefangen haben.

Nun, das ist eine wörtliche Beschreibung dieser Maschine:

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

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

Der schwierige Teil ist: Wo nennen Sie Ihre Handlungen?Ich behaupte nicht, die beste Antwort darauf zu haben, aber hier ist, was ich herausgefunden habe:

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:

  • Der eof Es besteht Handlungsbedarf, weil %PrintTextNode wird immer nur beim Verlassen einer Maschine aufgerufen.Wenn die Eingabe mit normalem Text endet, erfolgt keine Eingabe, um diesen Zustand zu verlassen.Da es auch aufgerufen wird, wenn die Eingabe mit einem Tag endet und kein endgültiger, nicht gedruckter Textknoten vorhanden ist, PrintTextNode testet, ob Text zum Drucken vorhanden ist.
  • Der %PrintTextNode Aktion eingebettet nach dem ^'[' wird benötigt, weil wir den Anfang markiert haben, als wir den erreicht haben [, nachdem wir ein nicht- getroffen haben[, werden wir erneut versuchen, etwas zu analysieren und den Startpunkt notieren.Wir müssen diese beiden Zeichen löschen, bevor das passiert, daher dieser Aktionsaufruf.

Der vollständige Parser folgt.Ich habe es in C gemacht, weil ich das weiß, aber Sie sollten es ziemlich leicht in jede Sprache umwandeln können, die Sie brauchen:

/* 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 einige Testeingaben:

[[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 die Ausgabe des Parsers:

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

Der letzte Textknoten enthält nur die neue Zeile am Ende der Datei.

Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top