我正在使用avr-gcc工具链为C语言中的AVR微控制器编写一个简单的基本类似语言的小型解释器。但是,我想知道是否有任何开源工具可以帮助我编写词法分析器和解析器。
如果我写这个在我的Linux机器上运行,我可以使用flex / bison。现在我把自己限制在一个8位平台上,我必须手工完成,或者不是吗?
答案 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&野牛,我去寻找其他发电机工具。以下是我考虑过的一些内容:
您可能还想查看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函数来添加语法。