为什么我要使用词法分析器而不是直接解析代码?

Why would I use a lexer and not directly parse code?

我正在尝试从头开始创建一种简单的编程语言(解释器),但我想知道为什么我应该使用词法分析器。 对我来说,看起来创建一个直接解析代码的解析器会更容易。我忽略了什么?

我想你会同意大多数语言(可能包括你正在实现的语言)都有概念标记:

  • 运算符,例如 *(通常是乘法)、'('、')'、;
  • 关键字,例如“IF”、“GOTO”
  • 标识符,例如FOO,计数,...
  • 数字,例如0, -527.23E-41
  • 评论,例如,/* 此文本在您的文件中被忽略 */
  • 白色space,例如被忽略的空格、制表符和换行符序列

作为一个实际问题,它需要一段特定的代码来扫描 for/collect 构成每个单独标记的字符。对于您的语言所具有的每种类型的标记,您都需要这样一个代码块。

如果你编写一个没有词法分析器的解析器,在你的解析器试图决定接下来会发生什么的每个点,你都必须有 ALL 代码来识别在解析中的那个点可能出现的标记。在下一个解析器点,您将需要所有代码来识别那里可能存在的标记。这给你带来了大量的代码重复;您希望空白代码在解析器中出现多少次?

如果您认为这不是一个好方法,明显的解决方法是删除所有重复项:将每个标记的代码放在该标记的子例程中,并在每个解析器位置为标记调用子例程。此时,从某种意义上说,您已经有了一个词法分析器:一个用于识别标记的独立代码集合。 You can code perfectly fine recursive descent parsers this way.

接下来您会发现,您会在每个解析器点为许多标记调用标记子例程。即便如此,这似乎也是大量的工作和重复工作。因此,将所有调用替换为单个“GetNextToken”调用,该调用本身调用 all 令牌的令牌识别代码,以及 returns 识别遇到的特定令牌的枚举。现在您的解析器开始看起来合理了:在每个解析器点,它对 GetNextToken 进行一次调用,然后对返回的枚举进行分支。这基本上是人们标准化为“词法分析器”的接口。

你会发现的一件事是,token-lexers 有时会遇到重叠问题;关键字和标识符通常有这个麻烦。将所有标记识别器合并到一个有限状态机中实际上更容易,这样就可以更容易地区分标记。在处理编程语言源文本时,这也证明速度非常快。你的玩具语言可能永远不会解析超过 100 行,但真正的编译器每天处理数百万行代码,其中大部分时间都花在做标记识别(“lexing”)尤其是。白space压制.

您可以手动编写此状态机的代码。这并不难,但相当乏味。或者,您可以使用 FLEX 之类的工具为您完成,这只是一个方便的问题。随着您的语言中不同种类的标记数量的增加,FLEX 解决方案变得越来越有吸引力。

TLDR:如果你使用词法分析器,你的解析器更容易编写,而且体积更小。此外,如果您将单个词素编译到状态机中(手动或使用“词法分析器生成器”),它将 运行 更快,这很重要。

好吧,对于智能简化的编程语言,您可以在没有词法分析器或解析器的情况下逃脱 :-) 不是开玩笑。查找 Forth。您可以从 tags here on SO (gforth is GNU's) and then go to the Standard's site which has pointers to a few interpreters, sites and its Glossary.

开始

然后您可以查看 Win32Forth,这应该会让您忙上一段时间:-)

解释器也编译(当你调用将系统切换到编译上下文的词时)。所有这些都没有独特的解析器。前瞻实际上是后视 :-) - 不是开玩笑。它很少吸收后面的一个词(== lookahead 最多为 1)。 “单词”(又名标记)同时是关键字和变量名称,它们都位于该站点的 Dictionary. There's a whole online book 中(加上 pdf)。

控制结构也只是单词(它们会编译一些地址并即时跳转)。

您也可以在那里找到旧的 Journals,涵盖从机器代码生成到面向对象扩展的广泛范围。是的,仍然没有解析器 - 信不信由你。

曾经有更复杂的(商业)Forth 系统,它们将单词减少为具有立即寻址功能的机器调用指令(使引擎 运行 快 2-4 倍),但即使是普通的解释器也总是被认为是要快。一个显然仍然活跃 - SwiftForth,但不要指望那里有任何免费赠品。

GitHub CiForth 上有一个 Forth,它非常简朴,但有针对 Win、Linux 和 Mac、32 和 64 的构建和发布,因此您可以下载并 运行。声称也有 16 位构建 :-) 我想对于嵌入式系统。