比lex / yacc更好的解决方案来解析C中的DSL?

时间:2011-05-15 21:18:55

标签: c parsing dsl

我的一个程序在运行时接受命令(如kill foo)。可以把它想象成一种特定领域的语言。以下是一些例子:

kill
kill client
exit

但是,允许使用链式命令,并且在命令之前和之后空格不重要,因此以下示例也是有效的:

kill ; say "that was fun"
  kill  ;  kill      ; kill;

我目前使用lex / yacc(flex / bison具体)来实现这一点,这引起了很多麻烦。词法分析器在很大程度上取决于上下文(例如,通常不返回空格标记,除非例如在kill关键字之后)并且具有许多不同的状态。以前的语法有冲突,我真的不喜欢它必须指定的格式(特别是$ 1,$ 2,$ 3,......使用非终端的参数)。此外,bison提供的错误消息(在分析时)有时是准确的,但通常不是(带有可选参数的kill命令会导致Unexpected $undefined, expected $end or ;的{​​{1}}等错误消息,而不是kill clont)。最后,yacc的C API是残酷的(外部定义了所有地方)。

我不是要求你解决所有上述问题(如果没有办法绕过lex / yacc,我会打开单独的线程,其中包含更具体的描述和代码)。相反,我对lex / yacc的替代品感兴趣。

我的标准如下:

  • 输入是一个字符串(const char *),没有输出,但是应该为每个不同的关键字调用一些代码。
  • 我想用C(C99)。
  • 该软件应该已经包含在主要的Linux发行版中,或者至少易于捆绑/打包。
  • 应该有详细记录。
  • 描述我的语言的语法应该很简单。
  • 解析错误时应输出有意义的错误消息。
  • 性能并不重要(当然它应该很快,但典型的用例是交互式使用,而不是处理大量的MB命令)。

6 个答案:

答案 0 :(得分:3)

至于一个非常简单和小的语法,我会考虑手工编写词法分析器/解析器 - 它通常没那么多工作。

几乎所有的Linux发行版都提供了lex / yacc的变体。除此之外,另外两个广泛使用的解析器生成器是lemonantlr

答案 1 :(得分:3)

由于您的语言看起来很简单,我建议实现一个标记和解析输入的有限状态机。

一次只读输入一个字符,在空白处进行标记(而不是在带引号的字符串中)。每个“命令”使机器处于一个不同的状态,它解析命令参数。 “;”或“\ n”将机器重置为启动状态。

答案 2 :(得分:1)

我非常喜欢ANTLR,在生产系统中使用过几次。

有些奇怪的是,它在版本2中支持生成C ++代码但不支持C,而在版本3中它支持生成C代码而不支持C ++。我喜欢C ++,所以仍然使用ANTLR v2,但你可能会喜欢v3。对你来说好多了。

许多发行版都有ANTLR v2包,有些还有v3。它的记录相当充分(注意我使用v2;我希望v3在这方面不会更差)。

ANTLR不会“开箱即用”生成超级精彩的解析错误消息。这似乎是大多数通用解析器系统的常见问题,从根本上说并不是一个容易解决的问题。然而,通过一些工作,我看到一些不错的诊断输出来自基于ANTLR的系统(该应用程序有一些逻辑来帮助弄清楚对用户说什么 - 这里ANTLR没有多少魔法)。 / p>

答案 3 :(得分:1)

Lex& amp; amp; amp; Yacc是Lemon解析器。它有相当多的用途,但我没有认真使用它,所以我不能完全确定它的确如此有效。它由SQLite使用。

答案 4 :(得分:1)

您可能需要考虑Ragel。我最近开始使用它,并且一旦你加快速度,我很高兴能与你合作。在您的示例中,您可能会执行类似的操作(注意:未经过测试!):

#include <stdio.h>
#include <string.h>

%%{
    machine my_cmd_lang;

    action pk { printf("Killing %.*s\n", fpc-mark, mark); }
    action mk { mark = fpc; }

    k = 'kill'; # creates a machine that doesn't do anything
    x = 'exit' @{ printf("Exiting\n"); };
    arg = alpha+ >mk; # arg to kill is built in machine 'alpha' 1 or more times
    cmd = ((k space arg) @pk space* ';'?) | x;
    main := cmd* ;
}%%

%% write data;

int main(int argc, char* argv[]) {
    int cs;
    char* p = "kill client";
    char* pe = p + strlen(p);
    char* mark;

    %% write init;
    %% write exec;

    return 0;
}

使用ragel <filename.rl>通过Ragel运行它,它会吐出<filename.c>

答案 5 :(得分:0)

您需要一个无法解析器(例如,PEG的实现)。当你使用C并且已经熟悉yacc时,像this这样的东西可能值得尝试。

如果你的语法足够简单,你可以实现一个特殊的递归下降解析器。