2010-07-26 56 views
9

我一直在研究簡單模板語言的解析器。我正在使用Ragel。如何解析Ragel中的模板語言?

要求不高。我試圖找到[[tags]]可以嵌入輸入字符串中的任何位置。

我想解析一個簡單的模板語言,這個語言可以嵌入HTML中的{{foo}}標籤。我嘗試了幾種方法來解析這個問題,但不得不求助於使用Ragel掃描器,並使用低效率的方法來匹配單個字符作爲「全部捕獲」。我覺得這是錯誤的做法。我基本上濫用掃描器的最長匹配偏見來實現我的默認規則(它只能是1個字符長,所以它應該始終是最後的手段)。

%%{ 

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

}%% 

(用ruby編寫的操作,但應該很容易理解)。

你會如何去爲這樣一種簡單的語言編寫解析器? 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(
) 

最終文本節點僅包含文件末尾的換行符。