Domanda

Ho lavorato su un parser per un semplice linguaggio del modello. Sto usando il raggio.

I requisiti sono modesti. Sto cercando di trovare [[Tag]] che può essere incorporato ovunque nella stringa di ingresso.

Sto cercando di analizzare un semplice linguaggio modello, qualcosa che può avere tag come {{foo}} incorporato all'interno di HTML. Ho provato diversi approcci per analizzare questo, ma ho dovuto ricorrere all'utilizzo di uno scanner di raggio e utilizzare l'approccio inefficiente di abbinare solo un singolo carattere come "catch all". Sento che questo è il modo sbagliato per farlo. Sto essenzialmente abusando del bias di corrispondenza più lungo dello scanner per implementare la mia regola predefinita (può essere solo 1 caratteri, quindi dovrebbe essere sempre l'ultima risorsa).

%%{

  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;
  *|;

}%%
.

(Azioni scritte in Ruby, ma dovrebbero essere facili da capire).

Come andresti a scrivere un parser per un linguaggio così semplice? Raggel forse non è lo strumento giusto? Sembra che tu debba combattere i denti del raggel e le unghie se la sintassi è imprevedibile come questa.

È stato utile?

Soluzione

Ragel funziona bene. Hai solo bisogno di stare attento a ciò che stai combinando. La tua domanda utilizza sia [[tag]] e {{tag}}, ma il tuo esempio utilizza [[tag]], quindi immagino che sia quello che stai cercando di trattare come speciale.

Cosa vuoi fare è mangiare il testo fino a quando non colpisci una parentesi aperta. Se quella staffa è seguita da un'altra staffa, allora è il momento di iniziare a mangiare caratteri minuscoli finché non colpisci una parentesi chiusa. Dal momento che il testo nel tag non può includere qualsiasi staffa, sai che l'unico carattere non errori che può seguire quella chiusa-staffa è un'altra parentesi chiusa. A quel punto, sei tornato dove hai iniziato.

Bene, questa è una descrizione verbatim di questa macchina:

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

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

La parte difficile è, dove chiami le tue azioni? Non pretendo di avere la migliore risposta a questo, ma ecco cosa ho trovato:

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);
}%%
.

Ci sono alcune cose non ovvie:

    .
  • L'azione eof è necessaria perché %PrintTextNode viene sempre invocato solo lasciando una macchina. Se l'ingresso termina con il testo normale, non ci sarà alcun input per farti lasciare tale stato. Poiché verrà anche chiamato quando l'input termina con un tag, e non vi è alcun nodo di testo non stampato, non stampato, test PrintTextNode che ha un po 'di testo da stampare.
  • L'azione %PrintTextNode è stata immersa dopo il ^'[' perché, anche se abbiamo segnato l'inizio quando abbiamo colpito il [, dopo aver colpito un non-[, inizieremo a provare a analizzare qualsiasi cosa e osservaremo il punto iniziale. Dobbiamo scaricare quei due personaggi prima che accada, quindi quell'invocazione d'azione.

Segue il parser completo. L'ho fatto in c perché è quello che so, ma dovresti essere in grado di trasformarlo in qualsiasi lingua hai bisogno abbastanza prontamente:

/* 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;
}
.

Ecco alcuni ingressi di prova:

[[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]]
.

Ed ecco l'uscita dal parser:

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(
)
.

Il nodo del testo finale contiene solo la nuova riga alla fine del file.

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top