首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >如何在Ragel中解析模板语言?

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

Stack Overflow用户
提问于 2010-07-26 09:24:03
回答 1查看 2.5K关注 0票数 10

我一直在开发一个简单模板语言的解析器。我用的是Ragel

要求是适中的。我正在尝试寻找可以嵌入到输入字符串中任何位置的[标签]。

我正在尝试解析一种简单的模板语言,它可以在HTML中嵌入{{foo}}这样的标记。我尝试了几种方法来解析它,但不得不求助于使用Ragel扫描器,并使用效率低下的方法,即只匹配单个字符作为“全部捕获”。我觉得这是错误的做法。我基本上是在滥用扫描器的最长匹配偏差来实现我的默认规则(它只能是1个字符的长度,所以它应该始终是最后的选择)。

代码语言:javascript
复制
%%{

  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的牙齿和指甲作斗争。

EN

回答 1

Stack Overflow用户

回答已采纳

发布于 2010-11-24 10:19:00

Ragel工作得很好。你只需要小心你要匹配的东西。您的问题同时使用了[[tag]]{{tag}},但是您的示例使用了[[tag]],所以我认为这就是您试图将其视为特殊之处的原因。

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

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

代码语言:javascript
复制
tag = '[[' lower+ ']]';

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

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

代码语言:javascript
复制
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语言写的,因为这是我所知道的,但你应该能够很容易地把它转换成你需要的任何语言:

代码语言:javascript
复制
/* 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;
}

下面是一些测试输入:

代码语言:javascript
复制
[[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]]

下面是解析器的输出:

代码语言:javascript
复制
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(
)

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

票数 22
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/3331731

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档