如何在Ragel中解析模板语言?

Tob*_*tke 9 parsing fsm lexer 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;
  *|;

}%%
Run Code Online (Sandbox Code Playgroud)

(用红宝石写的动作,但应该很容易理解).

How would you go about writing a parser for such a simple language? Is Ragel maybe not the right tool? It seems you have to fight Ragel tooth and nails if the syntax is unpredictable such as this.

Jer*_*man 20

Ragel工作得很好.你只需要小心你的匹配.你的问题使用了两个[[tag]]{{tag}},但是你的例子使用了[[tag]],所以我认为这是你想要的特殊处理.

你想要做的就是吃文字直到你打开括号.如果该括号后跟另一个括号,那么是时候开始吃小写字符,直到你达到一个近距离.由于标记中的文本不能包含任何括号,因此您知道可以跟随该括号的唯一非错误字符是另一个近括号.那时,你回到了你的起点.

嗯,这是对这台机器的逐字描述:

tag = '[[' lower+ ']]';

main := (
  (any - '[')*  # eat text
  ('[' ^'[' | tag)  # try to eat a tag
)*;
Run Code Online (Sandbox Code Playgroud)

棘手的部分是,你在哪里称呼你的行为?我没有声称对此有最好的答案,但这就是我想出的:

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);
}%%
Run Code Online (Sandbox Code Playgroud)

有一些不明显的事情:

  • 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;
}
Run Code Online (Sandbox Code Playgroud)

这是一些测试输入:

[[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]]
Run Code Online (Sandbox Code Playgroud)

这是解析器的输出:

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(
)
Run Code Online (Sandbox Code Playgroud)

最终文本节点仅包含文件末尾的换行符.