Ragel でテンプレート言語を解析するにはどうすればよいですか?
質問
私は単純なテンプレート言語のパーサーに取り組んでいます。ラジェル使ってます。
要件は控えめです。入力文字列のどこにでも埋め込むことができる [[tags]] を見つけようとしています。
HTML 内に {{foo}} などのタグを埋め込むことができる単純なテンプレート言語を解析しようとしています。これを解析するためにいくつかのアプローチを試しましたが、Ragel スキャナーを使用し、「すべてをキャッチ」として 1 つの文字のみを照合するという非効率的なアプローチを使用する必要がありました。これは間違ったやり方だと思います。私は基本的にスキャナーの最長一致バイアスを悪用してデフォルトのルールを実装しています(長さは 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;
*|;
}%%
(アクションはルビで書かれていますが、理解しやすいはずです)。
このような単純な言語のパーサーを作成するにはどうすればよいでしょうか?Ragel は適切なツールではないのでしょうか?このように構文が予測できない場合は、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
の後にあるアクション^'['
が必要なのは、打ったときにスタートをマークしましたが、[
, 、非を打った後、[
, 、もう一度何かを解析して開始点を確認してみます。それが起こる前に、これらの 2 文字をフラッシュする必要があるため、そのアクションが呼び出されます。
完全なパーサーが続きます。私が知っていることなので 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(
)
最後のテキスト ノードには、ファイルの末尾の改行のみが含まれます。