2010-07-26 4 views
9

J'ai travaillé sur un analyseur pour un langage de template simple. J'utilise Ragel.Comment analyser les langages de modèles dans Ragel?

Les exigences sont modestes. J'essaie de trouver [[tags]] qui peuvent être intégrés n'importe où dans la chaîne d'entrée. J'essaye d'analyser un langage de modèle simple, quelque chose qui peut avoir des balises telles que {{foo}} incorporées dans HTML. J'ai essayé plusieurs approches pour analyser cela, mais j'ai dû utiliser un scanner Ragel et utiliser l'approche inefficace consistant à ne faire correspondre qu'un seul caractère à un "attrape-tout". Je pense que c'est la mauvaise façon d'y arriver. J'utilise essentiellement le biais le plus long du scanner pour implémenter ma règle par défaut (il ne peut être que d'un caractère, donc ça devrait toujours être le dernier recours).

%%{ 

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

}%% 

(actions écrites en rubis, mais devraient être faciles à comprendre).

Comment voulez-vous écrire un analyseur pour un langage aussi simple? Ragel n'est peut-être pas le bon outil? Il semble que vous devez combattre Ragel dent et ongles si la syntaxe est imprévisible comme ceci.

Répondre

20

Ragel fonctionne très bien. Vous devez juste faire attention à ce que vous faites correspondre. Votre question utilise à la fois [[tag]] et {{tag}}, mais votre exemple utilise [[tag]], donc je pense que c'est ce que vous essayez de traiter comme spécial.

Ce que vous voulez faire est de manger du texte jusqu'à ce que vous frappiez un support ouvert. Si cette parenthèse est suivie d'une autre parenthèse, alors il est temps de commencer à manger des caractères minuscules jusqu'à ce que vous atteigniez un crochet. Comme le texte de la balise ne peut inclure aucune parenthèse, vous savez que le seul caractère non-erreur qui peut suivre ce close-bracket est un autre close-bracket. À ce stade, vous êtes de retour là où vous avez commencé.

Eh bien, c'est une description textuelle de cette machine:

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

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

La partie délicate est, où appelez-vous vos actions? Je ne prétends pas avoir la meilleure réponse à cette question, mais voici ce que je suis venu avec:

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

Il y a quelques choses non évidentes:

  • L'action eof est nécessaire parce que %PrintTextNode est seulement invoqué en quittant une machine. Si l'entrée se termine par du texte normal, il n'y aura pas d'entrée pour le faire quitter cet état. Comme il sera également appelé lorsque l'entrée se termine par une étiquette, et qu'il n'y a pas de nœud de texte final non imprimé, PrintTextNode teste qu'il a du texte à imprimer.
  • L'action %PrintTextNode niché après la ^'[' est nécessaire parce que, si nous avons marqué le début quand nous avons touché le [, après que nous ayons atteint un [ non, nous allons commencer à essayer d'analyser à nouveau quoi que ce soit et remarque le point de départ. Nous devons vider ces deux caractères avant que cela n'arrive, d'où cette invocation d'action.

L'analyseur complet suit.Je l'ai fait en C parce que ce que je sais, mais vous devriez être en mesure de le transformer en quelque langue que vous avez besoin assez facilement:

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

Voici quelques entrées de test:

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

Et est ici la sortie de l'analyseur:

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

le noeud texte final ne contient que la nouvelle ligne à la fin du fichier.

Questions connexes