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(
)
Окончательный текстовый узел содержит только новую строку в конце файла.