parsing - 如何解析 Ragel 中的模板语言?

标签 parsing lexer fsm 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;
  *|;

}%%

(用 ruby​​ 编写的操作,但应该易于理解)。

您将如何为如此简单的语言编写解析器? Ragel 可能不是正确的工具吗?如果语法像这样不可预测,那么你似乎必须与 Ragel 拼尽全力。

最佳答案

拉格尔工作正常。您只需要注意您所匹配的内容即可。您的问题同时使用 [[tag]]{{tag}},但您的示例使用 [[tag]],所以我想这就是您想要视为特殊的东西。

您想要做的是吃文本,直到您遇到开括号。如果该括号后面跟着另一个括号,那么就该开始吃小写字符,直到遇到右括号为止。由于标记中的文本不能包含任何括号,因此您知道可以跟在该右括号后面的唯一非错误字符是另一个右括号。那时,您就回到了起点。

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

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

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

棘手的部分是,你在哪里调用你的 Action ?我并不声称对此有最好的答案,但这是我想到的:

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

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

关于parsing - 如何解析 Ragel 中的模板语言?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/3331731/

相关文章:

javascript - 如何从符合特定条件的 HTML 中找到 CSS 选择器?

c# - 使用状态模式的分层状态机的最佳实践是什么?

c++ - 将整个程序构建为 FSM 的良好设计?

c++ - Boost fsm 和 Boost 状态图路径位置

c - 设置/获取可重入 Flex 扫描仪的列号

c++ - 仅查找第一个 std::regex 有效匹配

parsing - 抽象语法树和具体语法树有什么区别?

c# - 我可以扩展 int.TryParse 方法吗?

java - 解析可变行数

我们可以将预处理器指令视为 token 吗?