Pregunta

He estado trabajando en un analizador de lenguaje de plantilla simple.Estoy usando Ragel.

Los requisitos son modestos.Estoy intentando encontrar [[etiquetas]] que puedan incrustarse en cualquier lugar de la cadena de entrada.

Estoy intentando analizar un lenguaje de plantilla simple, algo que pueda tener etiquetas como {{foo}} incrustadas en HTML.Probé varios enfoques para analizar esto, pero tuve que recurrir al uso de un escáner Ragel y utilizar el enfoque ineficiente de hacer coincidir solo un carácter como "capturar todo".Siento que esta es la forma incorrecta de abordar esto.Básicamente, estoy abusando del sesgo de coincidencia más larga del escáner para implementar mi regla predeterminada (solo puede tener 1 carácter, por lo que siempre debería ser el último recurso).

%%{

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

}%%

(acciones escritas en Ruby, pero deben ser fáciles de entender).

¿Cómo harías para escribir un analizador para un lenguaje tan simple?¿Quizás Ragel no sea la herramienta adecuada?Parece que tienes que luchar con uñas y dientes contra Ragel si la sintaxis es tan impredecible como esta.

¿Fue útil?

Solución

Ragel funciona bien.Sólo debes tener cuidado con lo que estás combinando.Tu pregunta usa ambos [[tag]] y {{tag}}, pero tu ejemplo usa [[tag]], así que supongo que eso es lo que intentas tratar como especial.

Lo que quieres hacer es comer texto hasta llegar a un corchete abierto.Si ese paréntesis va seguido de otro paréntesis, entonces es hora de empezar a comer caracteres en minúsculas hasta llegar a un paréntesis cercano.Dado que el texto de la etiqueta no puede incluir ningún corchete, sabrá que el único carácter sin error que puede seguir a ese corchete es otro corchete.En ese punto, estás de vuelta donde empezaste.

Bueno, esa es una descripción textual de esta máquina:

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

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

La parte complicada es, ¿cómo llamas a tus acciones?No pretendo tener la mejor respuesta para eso, pero esto es lo que se me ocurrió:

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

Hay algunas cosas que no son obvias:

  • El eof es necesario actuar porque %PrintTextNode solo se invoca al salir de una máquina.Si la entrada termina con texto normal, no habrá ninguna entrada para que salga de ese estado.Debido a que también se llamará cuando la entrada termine con una etiqueta y no haya un nodo de texto final sin imprimir, PrintTextNode prueba que tiene algo de texto para imprimir.
  • El %PrintTextNode acción enclavada después de la ^'[' es necesario porque, aunque marcamos el inicio cuando llegamos al [, después de que golpeamos un no-[, comenzaremos a intentar analizar cualquier cosa nuevamente y comentaremos el punto de inicio.Necesitamos eliminar esos dos caracteres antes de que eso suceda, de ahí esa invocación de acción.

A continuación se muestra el analizador completo.Lo hice en C porque eso es lo que sé, pero deberías poder convertirlo al lenguaje que necesites con bastante facilidad:

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

Aquí hay algunas entradas de prueba:

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

Y aquí está el resultado del analizador:

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

El nodo de texto final contiene solo la nueva línea al final del archivo.

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top