是否有可用于8位嵌入式系统的flex / bison的替代方案?

时间:2010-02-11 16:38:03

标签: parsing embedded bison flex-lexer avr-gcc

我正在使用avr-gcc工具链为C语言中的AVR微控制器编写一个简单的基本类似语言的小型解释器。但是,我想知道是否有任何开源工具可以帮助我编写词法分析器和解析器。

如果我写这个在我的Linux机器上运行,我可以使用flex / bison。现在我把自己限制在一个8位平台上,我必须手工完成,或者不是吗?

6 个答案:

答案 0 :(得分:190)

如果你想要一种简单的方法来编码解析器,或者你的空间很紧,你应该手工编写一个递归下降解析器;这些基本上是LL(1)解析器。这对于像Basic一样“简单”的语言尤其有效。 (我在70年代做过其中几个!)。好消息是这些不包含任何库代码;就是你写的。

如果你已经有语法,它们很容易编码。 首先,你必须摆脱左递归规则(例如,X = X Y)。 这通常很容易做到,所以我把它留作练习。 (您不必为列表形成规则执行此操作; 见下面的讨论。)

然后,如果你有以下形式的BNF规则:

 X = A B C ;

为规则(X,A,B,C)中返回布尔值的每个项创建一个子例程 说“我看到了相应的语法结构”。对于X,代码:

subroutine X()
     if ~(A()) return false;
     if ~(B()) { error(); return false; }
     if ~(C()) { error(); return false; }
     // insert semantic action here: generate code, do the work, ....
     return true;
end X;

与A,B,C类似。

如果令牌是终端,请编写检查代码 组成终端的字符串的输入流。 例如,对于一个数字,检查输入流是否包含数字并前进 输入流游标经过数字。如果你这个特别容易 正在解析缓冲区(对于BASIC,你往往会得到一行) 通过简单地前进或不前进缓冲区扫描指针。 这段代码本质上是解析器的词法分析器部分。

如果您的BNF规则是递归的......请不要担心。只需对递归调用进行编码即可。 它处理语法规则,如:

T  =  '('  T  ')' ;

这可以编码为:

subroutine T()
     if ~(left_paren()) return false;
     if ~(T()) { error(); return false; }
     if ~(right_paren()) { error(); return false; }
     // insert semantic action here: generate code, do the work, ....
     return true;
end T;

如果您的BNF规则有替代方案:

 P = Q | R ;

然后使用其他选择代码P:

subroutine P()
    if ~(Q())
        {if ~(R()) return false;
         return true;
        }
    return true;
end P;

有时您会遇到列表形成规则。 这些往往是递归的,这种情况很容易处理。基本思想是使用迭代而不是递归,这避免了无限递归,你将以“明显”的方式做到这一点。 例如:

L  =  A |  L A ;

您可以使用迭代将其编码为:

subroutine L()
    if ~(A()) then return false;
    while (A()) do { /* loop */ }
    return true;
end L;

您可以通过这种方式在一两天内编写数百条语法规则。 还有更多细节需要填写,但这里的基础知识应该足够了。

如果您真的紧张空间,您可以构建一个实现这些想法的虚拟机。这就是我在70年代所做的事情,那时你可以获得8K 16位的话。


如果您不想手动编写代码,可以使用生成基本相同的元编译器Meta II)自动执行此操作。这些都是令人兴奋的技术乐趣,并且即使对于大型语法,也可以完成所有工作。

2014年8月:

我收到很多关于“如何使用解析器构建AST”的请求。有关这方面的详细信息,基本上详细说明了这个答案,请参阅我的其他答案https://stackoverflow.com/a/25106688/120163

2015年7月:

有很多人想写一个简单的表达式评估器。您可以通过执行上面“AST builder”链接建议的相同类型的操作来完成此操作;只是做算术而不是构建树节点。 这是an expression evaluator done this way

答案 1 :(得分:52)

我为一个针对ATmega328p的简单命令语言实现了一个解析器。该芯片具有32k ROM和仅2k RAM。 RAM肯定是更重要的限制 - 如果你还没有绑定特定的芯片,那么选择一个尽可能多的RAM。这将使您的生活更轻松。

起初我考虑过使用flex / bison。我决定反对这个选项有两个主要原因:

  • 默认情况下,Flex& Bison依赖于某些标准库函数(尤其是I / O),它们在avr-libc中不可用或不起作用。我非常确定有一些支持的解决方法,但这需要您考虑一些额外的工作。
  • AVR有Harvard Architecture。 C不是为此而设计的,因此默认情况下甚至常量变量都会加载到RAM中。您必须使用特殊的宏/函数来存储和访问flashEEPROM中的数据。 Flex& Bison创建了一些相对大的查找表,这些会很快占用你的RAM。除非我弄错了(这很可能),否则你必须编辑输出源才能利用特殊的闪存和放大器。 EEPROM接口。

拒绝Flex&野牛,我去寻找其他发电机工具。以下是我考虑过的一些内容:

您可能还想查看Wikipedia's comparison

最终,我最终手动编码词法分析器和解析器。

对于解析,我使用了递归下降解析器。我认为Ira Baxter已经完成了覆盖这个主题的足够工作,并且有很多在线教程。

对于我的词法分析器,我为所有终端编写了正则表达式,绘制了等效的状态机,并将其实现为一个巨大的函数,使用goto来跳转状态。这很乏味,但结果很好。顺便说一句,goto是实现状态机的一个很好的工具 - 所有状态都可以在相关代码旁边有明确的标签,没有函数调用或状态变量开销,并且它的速度和你可以得到。 C确实没有更好的构建静态机器的构造。

需要考虑的事项:词法分析器实际上只是解析器的一种特殊化。最大的区别是常规语法通常足以进行词法分析,而大多数编程语言(大多数)都有无上下文语法。因此,实际上没有什么可以阻止你将lexer实现为递归下降解析器或使用解析器生成器来编写词法分析器。它通常不像使用更专业的工具那样方便。

答案 2 :(得分:11)

你可以在Linux上使用flex / bison和它的原生gcc来生成代码,然后用你的AVR gcc交叉编译嵌入式目标。

答案 3 :(得分:2)

GCC可以交叉编译到各种平台,但是您在运行编译器的平台上运行flex和bison。他们只是吐出编译器然后构建的C代码。测试它以查看生成的可执行文件有多大。请注意,它们具有运行时库(libfl.a等),您还必须将它们交叉编译到目标。

答案 4 :(得分:-1)

尝试Boost :: Spirit。它是一个只有头文件的库,您可以使用C ++完全构建一个非常快速,干净的解析器。使用C ++中的重载运算符而不是特殊的语法文件。

答案 5 :(得分:-5)

不要重新发明轮子,而是看看LUA: www.lua.org。它是一种解释性语言,旨在嵌入其他软件中,并用于小规模系统,如嵌入式系统。内置的过程语法解析树,控制逻辑,数学和变量支持 - 无需重新发明成千上万已经调试和使用过的东西。它是可扩展的,这意味着您可以通过添加自己的C函数来添加语法。