2010-07-26 6 views
9

Я работаю над парсером для простого языка шаблонов. Я использую Ragel.Как анализировать языки шаблонов в Ragel?

Требования являются скромными. Я пытаюсь найти [[теги]], которые могут быть внедрены где угодно во входной строке.

Я пытаюсь проанализировать простой язык шаблонов, который может содержать теги, такие как {{foo}}, встроенные в HTML. Я попробовал несколько подходов к синтаксическому анализу, но мне пришлось прибегнуть к использованию сканера Ragel и использовать неэффективный подход только для соответствия одному символу как «поймать все». Я чувствую, что это неправильный путь. Я по сути злоупотребляю самым длинным смещением сканера для реализации моего правила по умолчанию (он может быть только 1 char long, поэтому он всегда должен быть последним средством).

%%{ 

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

}%% 

(действия, написанные на рубине, но должны быть понятны).

Как вы собираетесь писать парсер для такого простого языка? Возможно, Ragel - не лучший инструмент? Кажется, вам нужно бороться с зубами Ragel и гвоздями, если синтаксис непредсказуем, например.

ответ

20

Ragel отлично работает. Вам просто нужно быть осторожным в том, что вы соответствуете. Ваш вопрос использует как [[tag]], так и {{tag}}, но в вашем примере используется [[tag]], поэтому я полагаю, что вы пытаетесь рассматривать как специальные.

Что вы хотите сделать, это съесть текст, пока не нажмете открытую скобку. Если за этой скобкой следует еще одна скобка, тогда пришло время начать употреблять символы нижнего регистра, пока вы не нажмете скобку. Поскольку текст в теге не может содержать никакой скобки, вы знаете, что единственным символом без ошибок, который может следовать за этой скобкой, является еще одна скобка. В этот момент вы вернулись туда, где вы начали.

Ну, это дословное описание этой машины:

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

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

Хитрость в том, где вы называете ваши действия? Я не утверждаю, что есть лучший ответ на этот вопрос, но вот что я придумал:

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

Есть несколько неочевидных вещей:

  • eof действия необходимы, поскольку %PrintTextNode когда-либо вызывается при выходе из машины. Если вход заканчивается обычным текстом, вход для его выхода не будет. Поскольку он также будет вызываться, когда вход заканчивается тегом, и нет окончательного, непечатаемого текстового узла, PrintTextNode проверяет, что у него есть текст для печати.
  • %PrintTextNode действия, расположенное в после ^'[' необходимо, потому что, хотя мы отметили начало, когда мы попали в [, после того, как мы попали в не- [, мы начнем пытаться разобрать что-нибудь снова и замечают начальную точку. Нам нужно сбросить эти два символа до того, как это произойдет, и, следовательно, вызвать действие.

Полный парсер следует.Я сделал это в C, потому что это то, что я знаю, но вы должны быть в состоянии превратить его в любом языке, вам нужно довольно легко:

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

Вот некоторые испытания вход:

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

А вот выход от парсера:

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

Окончательный текстовый узел содержит только новую строку в конце файла.