使用标记列表构建抽象语法树

2023-11-22

我想从令牌列表构建 AST。我正在制作一种脚本语言,并且已经完成了词法分析部分,但我不知道如何创建 AST。所以问题是,我该如何采取这样的事情:

WORD, int
WORD, x
SYMBOL, =
NUMBER, 5
SYMBOL, ;

并将其转换为抽象语法树?最好,我愿意这样做without像 ANTLR 这样的库或其他库,我宁愿自己尝试从头开始做。但是,如果这是一项非常复杂的任务,我不介意使用库:)谢谢


基本技巧是要认识到,无论解析如何完成,解析都是以增量步骤进行的,包括一一读取标记。

在每个增量步骤中,都有机会通过组合其他增量步骤构建的 AST 片段来构建部分 AST。这是一个递归的想法,它在扫描令牌时为令牌构建 AST 叶节点时触底反弹。这个基本思想几乎出现在所有 AST 构建解析器中。

如果构建一个递归下降解析器,那么实际上构建了一个递归过程的协作系统,其中每个递归过程都识别正在实现的任何语法中的非终结符。对于纯解析,每个过程仅返回一个“非终结符(未)识别”的布尔值。

为了使用递归下降解析器构建 AST,我们将这些过程设计为返回两个值:“已识别”布尔值,以及(如果已识别)为非终结符构造(以某种方式)的 AST。 (常见的 hack 是返回一个指针,如果“无法识别”则该指针无效,或者如果“已识别”则指向构造的 AST)。构建单个过程的 AST 的方式是组合来自它调用的子过程的 AST。对于叶子过程来说,这是非常简单的,叶子过程读取输入标记并可以立即构建一棵树。

所有这一切的缺点是必须手动编码递归下降,并通过树构建步骤对其进行增强。从总体上看,对于小型语法来说,这实际上很容易编写。

对于OP的例子,假设我们有这个语法:

GOAL = ASSIGNMENT 
ASSIGNMENT = LHS '=' RHS ';' 
LHS = IDENTIFIER 
RHS = IDENTIFIER | NUMBER

好的,我们的递归下降解析器:

boolean parse_Goal()
{  if parse_Assignement()
   then return true
   else return false
}

boolean parse_Assignment()
{  if not Parse_LHS()
   then return false
   if not Parse_equalsign()
   then throw SyntaxError // because there are no viable alternatives from here
   if not Parse_RHS()
   then throw SyntaxError
   if not Parse_semicolon()
   the throw SyntaxError
   return true
}

boolean parse_LHS()
{  if parse_IDENTIFIER()
   then return true
   else return false
}

boolean parse_RHS()
{  if parse_IDENTIFIER()
   then return true
   if parse_NUMBER()
   then return true
   else return false
}

boolean parse_equalsign()
{  if TestInputAndAdvance("=")  // this can check for token instead
   then return true
   else return false
}

boolean parse_semicolon()
{  if TestInputAndAdvance(";")
   then return true
   else return false
}

boolean parse_IDENTIFIER()
{  if TestInputForIdentifier()
   then return true
   else return false
}

boolean parse_NUMBER()
{  if TestInputForNumber()
   then return true
   else return false
}

现在,我们来修改它,构建一个抽象语法树:

AST* parse_Goal() // note: we choose to return a null pointer for "false"
{  node = parse_Assignment()
   if node != NULL
   then return node
   else return NULL
}

AST* parse_Assignment()
{  LHSnode = Parse_LHS()
   if LHSnode == NULL
   then return NULL
   EqualNode = Parse_equalsign()
   if EqualNode == NULL
   then throw SyntaxError // because there are no viable alternatives from here
   RHSnode = Parse_RHS()
   if RHSnode == NULL
   then throw SyntaxError
   SemicolonNode = Parse_semicolon()
   if SemicolonNode == NULL
   the throw SyntaxError
   return makeASTNode(ASSIGNMENT,LHSNode,RHSNode)
}

AST* parse_LHS()
{  IdentifierNode = parse_IDENTIFIER()
   if node != NULL
   then return IdentifierNode
   else return NULL
}

AST* parse_RHS()
{  RHSnode = parse_IDENTIFIER()
   if RHSnode != null
   then return RHSnode
   RHSnode = parse_NUMBER()
   if RHSnode != null
   then return RHSnode
   else return NULL
}

AST* parse_equalsign()
{  if TestInputAndAdvance("=")  // this can check for token instead
   then return makeASTNode("=")
   else return NULL
}

AST* parse_semicolon()
{  if TestInputAndAdvance(";")
   then return makeASTNode(";")
   else return NULL
}

AST* parse_IDENTIFIER()
{  text = TestInputForIdentifier()
   if text != NULL
   then return makeASTNode("IDENTIFIER",text)
   else return NULL
}

AST* parse_NUMBER()
{  text = TestInputForNumber()
   if text != NULL
   then return makeASTNode("NUMBER",text)
   else return NULL
}

我显然掩盖了一些细节,但我认为读者填写它们不会有任何困难。

像 JavaCC 和 ANTLR 这样的解析器生成器工具基本上生成递归下降解析器,并且具有用于构造与此非常相似的树的工具。

构建自下而上解析器的解析器生成器工具(YACC、Bison、GLR...)也以相同的风格构建 AST 节点。然而,不存在递归函数集;相反,这些工具管理看到并简化为非终结符的一堆标记。 AST节点构建在并行堆栈上;当发生归约时,被归约覆盖的堆栈部分上的 AST 节点将被组合起来,生成一个非终结 AST 节点来替换它们。这种情况发生在语法规则的“零大小”堆栈段上,这些堆栈段也为空,导致 AST 节点(通常用于“空列表”或“缺少选项”)似乎不知从何而来。

使用小型语言,编写构建树的递归下降解析器非常实用。

真实语言(无论是像 COBOL 这样古老而陈旧的语言,还是像 Scala 这样炙手可热的语言)的一个问题是,语法规则的数量相当大,并且由于语言的复杂性以及对负责它的语言委员会的坚持而变得复杂。不断添加其他语言提供的新功能(“语言羡慕”,请参阅 Java、C# 和 C++ 之间的进化竞赛)。现在,编写递归下降解析器变得一发不可收拾,人们倾向于使用解析器生成器。但即使使用解析器生成器,编写所有自定义代码来构建 AST 节点也是一场大战(我们还没有讨论如何设计一个好的“抽象”语法与首先想到的事情)。随着规模的扩大和不断的发展,维护语法规则和 AST 构建变得越来越困难。 (如果你的语言很成功,一年之内你就会想改变它)。因此,即使编写 AST 构建规则也会变得很尴尬。

理想情况下,人们只想编写一种语法,并获得一个解析器和树。你可以使用一些最新的解析器生成器来做到这一点:我们的 DMS 软件重新工程工具包接受完整的上下文无关语法,并自动构建 AST,语法工程师不做任何工作;自 1995 年以来一直在这样做。ANTLR 人员终于在 2014 年解决了这个问题,ANTLR4 现在提供了这样的选项。

最后一点:拥有一个解析器(即使使用 AST)很难解决您要解决的实际问题,无论它是什么。它只是一个基础部分,令大多数解析器新手感到震惊的是,它是smallest操作代码的工具的一部分。谷歌我关于解析后的生活的文章(或查看我的简历)以获取更多详细信息。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

使用标记列表构建抽象语法树 的相关文章

  • 如何默认将 Maven 插件附加到阶段?

    我有一个 Maven 插件应该在编译阶段运行 所以在项目中consumes我的插件 我必须做这样的事情
  • 制作一个交互式Windows服务

    我希望我的 Java 应用程序成为交互式 Windows 服务 用户登录时具有 GUI 的 Windows 服务 我搜索了这个 我发现这样做的方法是有两个程序 第一个是服务 第二个是 GUI 程序并使它们进行通信 服务将从 GUI 程序获取
  • Final字段的线程安全

    假设我有一个 JavaBeanUser这是从另一个线程更新的 如下所示 public class A private final User user public A User user this user user public void
  • 多个 Maven 配置文件激活多个 Spring 配置文件

    我想在 Maven 中构建一个环境 在其中我想根据哪些 Maven 配置文件处于活动状态来累积激活多个 spring 配置文件 目前我的 pom xml 的相关部分如下所示
  • Spark 1.3.1 上的 Apache Phoenix(4.3.1 和 4.4.0-HBase-0.98)ClassNotFoundException

    我正在尝试通过 Spark 连接到 Phoenix 并且在通过 JDBC 驱动程序打开连接时不断收到以下异常 为简洁起见 下面是完整的堆栈跟踪 Caused by java lang ClassNotFoundException org a
  • 控制Android的前置LED灯

    我试图在用户按下某个按钮时在前面的 LED 上实现 1 秒红色闪烁 但我很难找到有关如何访问和使用前置 LED 的文档 教程甚至代码示例 我的意思是位于 自拍 相机和触摸屏附近的 LED 我已经看到了使用手电筒和相机类 已弃用 的示例 但我
  • Liferay ClassNotFoundException:DLFileEntryImpl

    在我的 6 1 0 Portal 实例上 带有使用 ServiceBuilder 和 DL Api 的 6 1 0 SDK Portlet 这一行 DynamicQuery query DynamicQueryFactoryUtil for
  • Spring Data JPA 应用排序、分页以及 where 子句

    我目前正在使用 Spring JPA 并利用此处所述的排序和分页 如何通过Spring data JPA通过排序和可分页查询数据 https stackoverflow com questions 10527124 how to query
  • 磁模拟

    假设我在 n m 像素的 2D 表面上有 p 个节点 我希望这些节点相互吸引 使得它们相距越远吸引力就越强 但是 如果两个节点之间的距离 比如 d A B 小于某个阈值 比如 k 那么它们就会开始排斥 谁能让我开始编写一些关于如何随时间更新
  • Spring @RequestMapping 带有可选参数

    我的控制器在请求映射中存在可选参数的问题 请查看下面的控制器 GetMapping produces MediaType APPLICATION JSON VALUE public ResponseEntity
  • 如何为俚语和表情符号构建正则表达式 (regex)

    我需要构建一个正则表达式来匹配俚语 即 lol lmao imo 等 和表情符号 即 P 等 我按照以下示例进行操作http www coderanch com t 497238 java java Regular Expression D
  • 如何将 pfx 文件转换为 jks,然后通过使用 wsdl 生成的类来使用它来签署传出的肥皂请求

    我正在寻找一个代码示例 该示例演示如何使用 PFX 证书通过 SSL 访问安全 Web 服务 我有证书及其密码 我首先使用下面提到的命令创建一个 KeyStore 实例 keytool importkeystore destkeystore
  • 使用Caliper时如何指定命令行?

    我发现 Google 的微型基准测试项目 Caliper 非常有趣 但文档仍然 除了一些示例 完全不存在 我有两种不同的情况 需要影响 JVM Caliper 启动的命令行 我需要设置一些固定 最好在几个固定值之间交替 D 参数 我需要指定
  • 仅将 char[] 的一部分复制到 String 中

    我有一个数组 char ch 我的问题如下 如何将 ch 2 到 ch 7 的值合并到字符串中 我想在不循环 char 数组的情况下实现这一点 有什么建议么 感谢您花时间回答我的问题 Use new String value offset
  • 如何从终端运行处理应用程序

    我目前正在使用加工 http processing org对于一个小项目 但是我不喜欢它附带的文本编辑器 我使用 vim 编写所有代码 我找到了 pde 文件的位置 并且我一直在从 vim 中编辑它们 然后重新打开它们并运行它们 重新加载脚
  • simpleframework,将空元素反序列化为空字符串而不是 null

    我使用简单框架 http simple sourceforge net http simple sourceforge net 在一个项目中满足我的序列化 反序列化需求 但在处理空 空字符串值时它不能按预期工作 好吧 至少不是我所期望的 如
  • 捕获的图像分辨率太大

    我在做什么 我允许用户捕获图像 将其存储到 SD 卡中并上传到服务器 但捕获图像的分辨率为宽度 4608 像素和高度 2592 像素 现在我想要什么 如何在不影响质量的情况下获得小分辨率图像 例如我可以获取或设置捕获的图像分辨率为原始图像分
  • 有没有办法为Java的字符集名称添加别名

    我收到一个异常 埋藏在第 3 方库中 消息如下 java io UnsupportedEncodingException BIG 5 我认为发生这种情况是因为 Java 没有定义这个名称java nio charset Charset Ch
  • 使用 JMF 创建 RTP 流时出现问题

    我正处于一个项目的早期阶段 需要使用 RTP 广播DataStream创建自MediaLocation 我正在遵循一些示例代码 该代码目前在rptManager initalize localAddress 出现错误 无法打开本地数据端口
  • Spring Boot @ConfigurationProperties 不从环境中检索属性

    我正在使用 Spring Boot 1 2 1 并尝试创建一个 ConfigurationProperties带有验证的bean 如下所示 package com sampleapp import java net URL import j

随机推荐

  • backbone.js ajax 调用

    我正在为我正在构建的新应用程序学习 Backbone js 我需要执行 AJAX 调用 REST SERVICE 进行身份验证 此调用的正确位置在哪里 在模型 视图还是其他地方 特别与 Backbone js MVC 模型相关
  • 禁用单页应用程序的浏览器后退按钮

    我需要在单页应用程序中禁用浏览器的后退按钮 我尝试使用类似的方法哈希变化 or window history forward但它们不起作用 原因可能是 url 没有在这里更改 我在 AngularJS 中工作 构建一个单页应用程序 我想禁用
  • Tkinter 打包方法混乱

    我不明白为什么包管理器不允许您将左和右打包在顶部打包的小部件下方 我的以下代码的预期输出是 A B C D E 但它只显示 A B C D import tkinter as tk root tk Tk w h root winfo scr
  • 在没有 UAC 提示的情况下从 .NET 应用程序执行进程

    我有一个场景 我需要从 NET 应用程序启动 EXE 但我无法绕过弹出的 UAC 提示 甚至在另一个 EXE 启动之前就会触发提示 可能是在调用时Process Start 我使用此代码来启动应用程序 var info new Proces
  • CSS(也许带有Compass):跨浏览器渐变

    我想在 CSS 中获得渐变 也许通过Compass 适用于所有主要浏览器 包括 IE7 有没有一种简单的方法可以做到这一点 无需编写大量代码 也无需自定义图像文件 我看了指南针的梯度混合 但它不适用于 Internet Explorer 有
  • 网址中可以输入中文吗?

    URL中可以输入中文吗 经测试 URL中可以输入中文 并且会转换为punycode并发出请求 到达相关页面 但目前是否还有其他人会验证网站 URL 是否也允许使用中文字符 Punycode 的存在是为了能够在不受支持的软件中使用非拉丁脚本
  • 使用 jQuery Ajax 获取另一个页面的 div 内容

    我希望 page html 通过 ajax 请求 side html 的内容并提取其两个 div 的内容 但尽管我尝试了一切 但我无法找到解析响应的正确方法 这是 side html div ContentA div div Content
  • 如果不等待任务怎么办?

    这是我的代码 private static Stopwatch stopwatch static void PrintException Exception ex Console WriteLine stopwatch Elapsed Co
  • Facebook 广告 API 错误 - 未启用使用

    因此 我一直在尝试创建一个使用 Facebook Ads API 来获取广告费用和统计数据的应用程序 我已经创建了一个应用程序并将我的广告帐户 ID 添加到应用程序设置中 但仍然收到错误 error message 274 The ad a
  • 为什么使用 C++ Typedef?

    我对 typedef 的理解是给一个声明一个别名 这样的 int 声明现在将被称为 Integer 但为什么 为什么有人会使用 typedef 更有利的原因是什么 typedef int Integer Integer blamo 50 c
  • C++ 中的粗体输出

    我正在构建一本字典 当我打印 输出 单词定义时 我想以粗体打印单词本身 当我打印时 cout lt
  • 您可以通过 Flex 使用 Amazon S3 吗?

    由于缺少 clientaccesspolicy xml 通过 Flex 使用 Amazon S3 似乎存在问题 有什么解决办法吗 Edit 下面的两个答案都很棒并且有效 我都赞成 我不会为这个问题指定答案 因为它们都有效 您可以通过 Fle
  • 在 VB 中键入时,如何使智能感知上的 Enter 键的反应方式与 Visual Studio 中的 C# 中的反应方式相同?

    我使用的是 Visual Studio 2008 并且习惯了 C 当智能感知弹出时 我通过按 Enter 键选择我想要的内容 它不会跳到下一行 在 VB 中 当我在智能感知上按下回车键时 我会跳转到下一行 有谁知道这个智能感知选项的设置可能
  • 是否可以在 Emacs 中用文本替换边缘位图?

    我很想用简单 雅致的文本 甚至可能是一个很好的 unicode 字符 例如 u2026省略 这可能吗 不它不是 边缘 位图 实际上是位图 即覆盖在边缘上的 0 1 位向量 没有办法直接将任意 unicode 字符渲染到边缘上 您可以做的就是
  • IN 子句中的通配符

    SQL 我想在 IN 子句中使用通配符 但没有得到我期望的结果 我的查询是这样的 SELECT DISTINCT ID FROM INST WHERE TYPE in IP International 请帮助解决这个问题 解决方案应该是使用
  • Python for 循环中的“pass”和“continue”有区别吗?

    两个Python关键字之间有什么显着差异吗continue and pass就像例子中一样 for element in some list if not element pass and for element in some list
  • 可以改变JButton的形状吗?

    是否可以将 JButton 的形状从矩形更改为圆形 Sean Cogan 提供的链接就是您所需要的 如果您想要简短 请设置一个图像 圆形或任何您希望按钮看起来相似的形状 使用setIcon然后在 JButton button1 上设置这些值
  • 获取类转换异常,其中两个类完全相同

    我正在做一个 JBoss SEAM 项目 当我查看表单时 我收到此错误 java lang ClassCastException it cogitoweb csi entity csiorelav CsiTipoLav cannot be
  • Unix 域:connect():没有这样的文件或目录

    如标题所述 我的连接 调用具有相应地址的 unix 域类型套接字会导致错误ENOENT 没有这样的文件或目录 两个套接字已正确初始化 并且相应地创建并绑定了套接字文件 服务器和客户端套接字在不同的进程中运行 尽管客户端进程是 fork 和
  • 使用标记列表构建抽象语法树

    我想从令牌列表构建 AST 我正在制作一种脚本语言 并且已经完成了词法分析部分 但我不知道如何创建 AST 所以问题是 我该如何采取这样的事情 WORD int WORD x SYMBOL NUMBER 5 SYMBOL 并将其转换为抽象语