2010-07-26 15 views
9

He estado trabajando en un analizador para el lenguaje de plantilla simple. Estoy usando Ragel.¿Cómo se analizan los lenguajes de plantillas en Ragel?

Los requisitos son modestos. Estoy tratando de encontrar [[tags]] que se pueden incrustar en cualquier lugar de la cadena de entrada.

Estoy tratando de analizar un lenguaje de plantilla simple, algo que puede 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 solo emparejar un solo carácter como "atrapar todo". Siento que esta es la forma incorrecta de hacerlo. Básicamente, estoy abusando del sesgo de partido más largo del escáner para implementar mi regla predeterminada (solo puede ser de 1 char de largo, por lo que siempre debe 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? ¿Es posible que Ragel no sea la herramienta adecuada? Parece que tienes que luchar contra el diente y las uñas de Ragel si la sintaxis es impredecible como esta.

Respuesta

20

Ragel funciona bien. Solo debes tener cuidado con lo que estás buscando. Su pregunta usa [[tag]] y {{tag}}, pero su ejemplo usa [[tag]], por lo que supongo que eso es lo que trata de tratar como especial.

Lo que quiere hacer es comer texto hasta que llegue a un corchete abierto. Si ese corchete es seguido por otro corchete, entonces es hora de comenzar a comer caracteres en minúscula hasta que llegue a un corchete. Debido a que el texto en la etiqueta no puede incluir ningún corchete, usted sabe que el único carácter sin errores que puede seguir a ese corchete es otro corchete. En ese punto, estás de vuelta donde comenzaste.

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

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

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

La parte difícil es, ¿de dónde llama a sus acciones? No dicen que tienen la mejor respuesta a eso, pero esto es lo que 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 no obvias:

  • La acción eof es necesario porque %PrintTextNode solo se invoca al salir de una máquina. Si la entrada finaliza con texto normal, no habrá entrada para que salga de ese estado. Como también se llamará cuando la entrada finalice con una etiqueta, y no haya un nodo de texto final no impreso, PrintTextNode prueba que tiene algo de texto para imprimir.
  • La acción %PrintTextNode enclavado en después de la ^'[' es necesaria porque, a pesar de que marcamos el principio, cuando llegamos a la [, después llegamos a un no [, vamos a empezar a intentar analizar nada más y comente el punto de inicio. Necesitamos eliminar esos dos personajes antes de que eso ocurra, de ahí esa invocación de acción.

Sigue el analizador completo.Lo hice en C porque eso es lo que sé, pero usted debería ser capaz de convertirlo en cualquier idioma que necesita bastante fácilmente:

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

He aquí alguna entrada 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á la salida 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.

Cuestiones relacionadas