题
我一直在研究简单模板语言的解析器。我用的是拉格尔。
要求是适度的。我试图找到可以嵌入输入字符串中任何位置的[[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 tooth和nails作斗争。
解决方案
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(
)
最终文本节点只包含文件末尾的换行符。
不隶属于 StackOverflow