递归下降解析器的探索与实践

递归下降解析器(Recursive Descent Parser, RDP)是一种用于解析表达式的编程技术。尽管它的概念可能在初次接触时显得有些难以理解,但通过本文的介绍,希望能够帮助逐步揭开RDP的神秘面纱。本文将通过一个简单的程序演示RDP的工作原理,帮助更好地理解这一技术。

控制台程序介绍

为了简化演示,使用Windows控制台项目。在编写本文时,作者使用的是24英寸的ASUS显示器,并将控制台窗口的宽度和高度分别设置为150和60。当然,可以根据个人喜好和显示器的不同,调整以下代码中的参数: private const int ScreenWidth = 150; private const int ScreenHeight = 60; ... Console.SetBufferSize(ScreenWidth, ScreenHeight); Console.ForegroundColor = ConsoleColor.Cyan; Console.BackgroundColor = ConsoleColor.Black;

使用代码

首先,下载并解压项目,然后使用Visual Studio打开RDPInst.sln文件。该项目包含两个子项目:

  • RDPInstLib - 递归下降解析器(RDP)。这是与第二部分相同的RDP,但进行了一些修改,稍后将进行详细描述。"Inst"是"Instrumented"的缩写。
  • RDPInst - 主控制台程序,用于运行Instrumented Parser,TestParserProgram.cs。
通过按下F6键构建解决方案。

栈帧的引入

对RDP的主要修改是增加了一个List<MyStackFrame> MyStackFrameList。MyStackFrame类(为了避免与System.Diagnostics.StackFrame混淆,给它起了这个名字)包含了在RDP评估表达式时感兴趣的项目: public class MyStackFrame { public DateTime Timestamp { get; set; } public int StackLevel { get; set; } public String Op { get; set; } public String Method { get; set; } public String Token { get; set; } public RecursiveDescentParserInstrumented.TokenType TokenType { get; set; } public Double SaveResult { get; set; } public Double PartialResult { get; set; } public Double Result { get; set; } public int ExprNdx { get; set; } } 递归下降解析器使用递归,将值推入栈中。在此过程中,会创建一个MyStackFrame对象并将其添加到myStackFrameList中。当RDP完成表达式的评估后,TestParserProgram会遍历这个列表,实际上是在“回放”方法和值。

解析入门

解析器的核心是其语法,这里以EBNF(扩展巴科斯-诺尔范式)表示法展示:

  1. <Expression> := [ "-" ] <Term> { ("+" | "-") <Term> }
  2. <Term> := <Factor> { ( "*" | "/" | "%" ) <Factor> }
  3. <Factor> := <RealNumber> | "(" <Expression> ")"
  4. <RealNumber> := <Digit>{<Digit>} | {<Digit>} "." {<Digit>}
  5. <Digit> := "0" | "1" | "2" | "3" | "4" | "5" | "6" | "7" | "8" | "9"
如果不熟悉EBNF,这个语法定义可能看起来有些令人生畏。让考虑表达式1 + 2 * 3,并将其分解为语法的符号。以第5行的语法为例,记住":="表示"定义为",竖线"|"表示"或",第5行可以读作"一个数字定义为0或1或2等等或9"。在EBNF语法中,0, 1, 2 ... 9被称为终端符号,而<Digit>和其他在<和>之间的项是非终端符号。知道EBNF中的花括号表示任意数量的项目,第4行可以读作"一个<RealNumber>定义为任意数量的<Digits>",或者"一个<RealNumber>定义为任意数量的<Digits>,一个十进制点,后面跟着任意数量的<Digits>"。因此,表达式中的1, 2和3被识别为称为<RealNumbers>的非终端符号。

运行程序

构建程序后,按CTRL+F5以“不调试方式启动”。应该会出现一个控制台窗口,提示"Enter Expression For Instrumented Parser (Enter to quit)>"(确保将控制台的高度和宽度设置为适当的大小)。输入要解析的表达式,然后连续按Enter键,直到表达式完成。当按Enter键时,程序会遍历myStackFrameList。在左侧,可以观察到方法调用以及操作的结果(加法、减法、指数等等)。右侧是栈;可以观察到数字如何被推入栈中,然后在遇到操作符如+,^或*时被弹出,显示当前结果。注意如何在左下角显示表达式,光标指向当前正在处理的标记。

沪ICP备2024098111号-1
上海秋旦网络科技中心:上海市奉贤区金大公路8218号1幢 联系电话:17898875485