10

私は単純なテンプレート言語のパーサーに取り組んできました。ラジェルを使用しています。

要件は控えめです。入力文字列のどこにでも埋め込むことができる [[tags]] を見つけようとしています。

{{foo}} などのタグを HTML に埋め込むことができる単純なテンプレート言語を解析しようとしています。これを解析するためにいくつかのアプローチを試みましたが、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;
  *|;

}%%

(アクションはルビで書かれていますが、理解しやすいはずです)。

このような単純な言語のパーサーを作成するにはどうすればよいでしょうか? Ragel は適切なツールではないでしょうか? このような構文が予測できない場合は、Ragel の歯と爪と戦わなければならないようです。

4

1 に答える 1

22

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

最後のテキスト ノードには、ファイルの末尾にある改行のみが含まれます。

于 2010-11-24T02:18:59.507 に答える