比lex / yacc更好的解决方案,用于在C中解析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 ;
代替
kill client
的错误消息,例如
Unexpected $undefined, expected $end or ;
)。最后,yacc的C API很残酷(外部定义了整个地方)。 我并不是要您解决所有上述问题(如果无法解决lex / yacc,我将用更具体的描述和代码打开单独的线程)。相反,我对lex / yacc的替代品感兴趣。 我的标准如下: 输入是一个字符串(const char *),没有输出,但是应该为每个不同的关键字调用一些代码。 我想将此与C(C99)一起使用。 该软件应该已经包含在主要的Linux发行版中,或者至少易于捆绑/打包。 应该有据可查。 描述我的语言的语法应该很简单。 在解析错误时,它应该输出有意义的错误消息。 性能并不是那么重要(当然应该很快,但是典型的用例是交互式用法,而不是处理大量MB的命令)。     
已邀请:
至于一个非常简单而又小的语法,我会考虑手工编写词法分析器/解析器-通常不需要那么多的工作。 几乎所有的Linux发行版都附带lex / yacc的变体。除此之外,其他两个广泛使用的解析器生成器是Lemon和antlr。     
由于您的语言看起来非常简单,因此我建议实现一个有限状态机,该状态机会标记和解析输入。 只需一次读取输入的一个字符,并在空白处分词(而不是用引号引起来的字符串)。每个\“ command \”会将计算机解析为命令参数时处于不同的状态。 \“; \”或\“ \\ n \”将机器重置为启动状态。     
我非常喜欢ANTLR,已经在生产系统中使用了两次。 奇怪的是,在版本2中,它支持生成C ++代码,但不支持C;而在版本3中,它支持生成C代码,但不支持C ++。我喜欢C ++,因此仍然使用ANTLR v2,但您可能会喜欢v3。如此对您更好。 许多发行版具有ANTLR v2软件包,有些发行版也具有v3。它有充分的文档记录(请注意,我使用v2;希望在这方面v3不会更糟)。 ANTLR不会“开箱即用”地生成超赞的解析错误消息。这似乎是大多数通用解析器系统的共同点,从根本上讲,这不是一个容易解决的问题。但是,通过一些工作,我已经看到了来自基于ANTLR的系统的一些体面的诊断输出(该应用程序具有一些逻辑来帮助弄清楚对用户说些什么-ANTLR在这里没有太多的魔力) 。     
Lemon解析器是Lex&Yacc的一个有趣替代品。它有很多用途,但是我没有认真使用它,因此我不确定它的实际效果如何。它由SQLite使用。     
您可能需要考虑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>
。     
您需要一个无词法分析器(例如PEG的实现)。当您使用C并且已经熟悉yacc时,类似这样的操作可能值得尝试。 而且,如果您的语法足够简单,则可以改为实施临时递归下降解析器。     

要回复问题请先登录注册