我正在使用avr-gcc工具链针对C语言的AVR微控制器上的练习,为一种简单的BASIC语言编写小型解释器。但是,我想知道是否有任何开源工具可以帮助我编写词法分析器和解析器。
如果我将其编写为在Linux机器上运行,则可以使用flex / bison。现在,我将自己限制在一个8位平台上,我是否必须手工完成所有工作?
我正在使用avr-gcc工具链针对C语言的AVR微控制器上的练习,为一种简单的BASIC语言编写小型解释器。但是,我想知道是否有任何开源工具可以帮助我编写词法分析器和解析器。
如果我将其编写为在Linux机器上运行,则可以使用flex / bison。现在,我将自己限制在一个8位平台上,我是否必须手工完成所有工作?
Answers:
我已经针对ATmega328p的简单命令语言实现了解析器。该芯片具有32k ROM和仅2k RAM。RAM绝对是更重要的限制-如果您尚未绑定到特定芯片,则选择一个具有尽可能多RAM的芯片。这将使您的生活更加轻松。
一开始我考虑使用flex / bison。我决定拒绝此选项的原因有两个:
拒绝Flex&Bison之后,我去寻找其他生成器工具。我考虑了以下几点:
您可能还想看看Wikipedia的比较。
最终,我最终对词法分析器和解析器进行了手工编码。
对于解析,我使用了递归下降解析器。我认为Ira Baxter已经做了足够的工作来涵盖这个主题,并且在线上有很多教程。
对于我的词法分析器,我为我所有的终端编写了正则表达式,绘制了等效的状态机,并将其实现为一个巨型函数,使用goto
来在状态之间跳转。这很乏味,但结果效果很好。顺便说goto
一句,这是一个用于实现状态机的好工具-您所有的状态都可以在相关代码的旁边带有清晰的标签,没有函数调用或状态变量的开销,并且它的速度最快。C确实没有更好的构造静态机器的构造。
需要考虑的事情:词法分析器实际上只是解析器的一种特殊化。最大的区别是常规语法通常足以进行词法分析,而大多数编程语言具有(大部分)上下文无关的语法。因此,实际上没有什么阻止您将词法分析器实现为递归下降解析器或使用解析器生成器编写词法分析器。它通常不如使用更专业的工具那样方便。
如果您想要一种简单的方法来编写解析器,或者空间有限,则应该手动编写递归下降解析器。这些本质上是LL(1)解析器。这对于像Basic这样“简单”的语言尤其有效。(我在70年代做过其中的几个!)。好消息是它们不包含任何库代码。就是你写的
如果您已经有语法,它们非常容易编码。首先,您必须摆脱左递归规则(例如X = XY)。通常这很容易做到,因此我将其保留为练习。(对于列表形成规则,您不必这样做;请参见下面的讨论)。
然后,如果您具有以下形式的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”的请求。有关此问题的详细信息,本质上讲得很详细,请参阅我的其他SO回答https://stackoverflow.com/a/25106688/120163
2015年7月:
有很多人想要编写一个简单的表达式评估器。您可以通过执行上面的“ AST构建器”链接建议的相同操作来完成此操作。只是做算术而不是建立树节点。这是一个以此方式完成的表达式评估器。
GCC可以交叉编译到各种平台,但是您可以在运行编译器的平台上运行flex和bison。他们只是吐出编译器然后生成的C代码。测试它以查看生成的可执行文件的实际大小。请注意,它们具有运行时库(libfl.a
等),您还必须将它们交叉编译到目标。
与其重新发明轮子,不如看看LUA:www.lua.org。它是一种解释性语言,旨在嵌入到其他软件中并在小型系统(例如嵌入式系统)中使用。内置的过程语法分析树,控制逻辑,数学和变量支持-无需重新发明成千上万已经调试和使用过的东西。它是可扩展的,这意味着您可以通过添加自己的C函数来添加语法。