手写一个PrattParser基本运算解析器1: 编译原理概述

点击查看 基于Swift的PrattParser项目


编译原理概述

编译原理是我们每一个程序猿必须要了解的技能, 编译原理实际上并没有啥高深的技术, 我们如果在做业务开发, 也很少会用到编译开发的知识, 但是编译原理又是我们必备的基础知识之一. 所以我们需要对编译原理的内容有一个大概的了解.

其实我自己写这一个系列的起因, 一个是我恶补编译原理的相关内容, 另外一个就是看到了B站熊爷的技术去魅篇- 手写一个普拉特解析器, 感觉这个对我辅助理解遍历原理的一个很好的方案. 所以才会有这一个系列的博客.

首先就是编译过程中的基本组成结构单元. 如下图所示.

基本组成结构主要有 词法分析, 语法分析, 语义分析, 中间代码生成, 代码优化, 目标代码生成, 符号表管理, 错误管理.

下面, 我们就以下运算代码(以 Clang 作为编译器为例)为示例, 写一些伪过程来了解整个编译过程.

1 + 3 * 2
  • 词法分析(Lexical Analysis)

    词法分析主要是将源代码分解为词法单元(tokens),如关键字标识符运算符等。词法分析器负责读取字符流,根据语法规则识别和分类词法单元。

    使用下面的命令(需要安装Mac xcode, 以提供 Clang 编译前端环境)

    clang -Xclang -dump-tokens helloworld.c
    

    上面的运算代码会被划分成如下的词法单元. (会报错, 但不影响)

    numeric_constant '1'	 [StartOfLine]	Loc=<helloworld.c:1:1>
    
    plus '+'	 [LeadingSpace]	Loc=<helloworld.c:1:3>
    
    numeric_constant '3'	 [LeadingSpace]	Loc=<helloworld.c:1:5>
    
    star '*'	 [LeadingSpace]	Loc=<helloworld.c:1:7>
    
    numeric_constant '2'	 [LeadingSpace]	Loc=<helloworld.c:1:9>
    
    eof ''		Loc=<helloworld.c:1:11>
    

    Loc 代表着词法单元在源文件中的位置, 所以实际上 1 + 3 * 2 被划分成以下的词法单元.


  • 语法分析(Syntax Analysis)

    将词法单元流转换为抽象语法树(Abstract Syntax Tree,AST)。语法分析器负责检查和验证语法结构,根据语法规则构建语法树,以便后续的语义分析和代码生成。

    上一个步骤我们已经把源文件划分成了一个个的词法单元, 但是执行顺序呢? 因为在数学运算过程中 乘法 要比 加法 优先级高, 所以我们在 1 + 3 * 2的运算过程中. 需要先计算 3 * 2 而不是 1 + 3, 我们由于有数学基础, 一眼就知道应该先计算谁, 但程序是如何知道应该先计算谁呢? 所以我们需要构建AST语法树, 为什么要构建AST语法树, 我们实际操作一下你就明白了.

    注: 由于 1 + 3 * 2 构建语法树不是很直观, 我们把 helloworld.c 中的内容改为 int a = 1 + 3 * 2;

    使用下面的命令来构建AST语法树.

    clang -Xclang -ast-dump -fsyntax-only helloworld.c
    

    抛去一些别的构建树节点, 我们会发现如下的结构.

    `-VarDecl 0x7fb69188e200 <helloworld.c:1:1, col:17> col:5 a 'int' cinit`-BinaryOperator 0x7fb69188e330 <col:9, col:17> 'int' '+'|-IntegerLiteral 0x7fb69188e2b0 <col:9> 'int' 1`-BinaryOperator 0x7fb69188e310 <col:13, col:17> 'int' '*'|-IntegerLiteral 0x7fb69188e2d0 <col:13> 'int' 3`-IntegerLiteral 0x7fb69188e2f0 <col:17> 'int' 2
    

    上面看着很杂乱, 我们稍微改造一下, 再看一下, 整体如下图所示.

    通过上图, 我们就知道AST语法树实际上是类似于二叉树的结构. 我们可以通过生成的AST语法树知道, 如果想要执行 图中的加法运算, 就必须先执行 图中的乘法运算, 这也就是为什么计算机能通过AST语法树知道操作的优先级.


  • 语义分析(Semantic Analysis)

    对语法树进行语义检查,包括类型检查、作用域检查等。语义分析器负责分析语句和表达式的含义和关联,确保程序在运行时不会出现语义错误。

    在iOS开发过程中, 语义分析也叫做静态分析. 计算机做的操作也是包括类型检查实现检查(某个类是否存在某个方法)变量使用,还会有一些 复杂的检查.

    例如在 Objective-C 中,给某一个对象发送消息(调用某个方法),检查这个对象的类是否声明这个方法(但并不会去检查这个方法是否实现,这个错误是在运行时进行检查的),如果有什么错误就会进行提示。


  • 中间代码生成(Intermediate Code Generation)

    将语法树转换为中间表示形式,如三地址码、虚拟指令等。中间代码生成器负责将高级语言的语法结构转换成较低级的表示形式,以便后续的优化和目标代码生成。

    在Clang编译前端编译代码过程中, 中间代码的形式为 LLVM IR的形式输出的.

    我们使用如下终端指令, 来生成 LLVM IR 形式的中间代码.

    clang -S -emit-llvm helloworld.c -o helloworld.ll
    

    生成的中间代码如下所示.

    ; ModuleID = 'helloworld.c'
    source_filename = "helloworld.c"
    target datalayout = "e-m:o-p270:32:32-p271:32:32-p272:64:64-i64:64-f80:128-n8:16:32:64-S128"
    target triple = "x86_64-apple-macosx13.0.0"@a = global i32 7, align 4!llvm.module.flags = !{!0, !1, !2, !3, !4}
    !llvm.ident = !{!5}!0 = !{i32 2, !"SDK Version", [2 x i32] [i32 13, i32 3]}
    !1 = !{i32 1, !"wchar_size", i32 4}
    !2 = !{i32 7, !"PIC Level", i32 2}
    !3 = !{i32 7, !"uwtable", i32 2}
    !4 = !{i32 7, !"frame-pointer", i32 2}
    !5 = !{!"Apple clang version 14.0.3 (clang-1403.0.22.14.1)"}
    

    我们发现全局变量 int a 的结果已经被计算出来了, 如下所示.

    @a = global i32 7, align 4
    

  • 代码优化(Code Optimization)

    对中间代码进行优化,以提高程序的性能和效率。代码优化器负责分析和优化中间代码,如常量折叠、循环展开、函数内联等优化技术,以减少执行时间和内存占用。

    我们可以通过 -O 参数来优化生成的中间代码.

    clang -O3 -S -emit-llvm helloworld.c -o helloworld.ll
    

    这时候, 我们会得到如下的中间代码.

    ; ModuleID = 'helloworld.c'
    source_filename = "helloworld.c"
    target datalayout = "e-m:o-p270:32:32-p271:32:32-p272:64:64-i64:64-f80:128-n8:16:32:64-S128"
    target triple = "x86_64-apple-macosx13.0.0"@a = local_unnamed_addr global i32 7, align 4!llvm.module.flags = !{!0, !1, !2, !3, !4}
    !llvm.ident = !{!5}!0 = !{i32 2, !"SDK Version", [2 x i32] [i32 13, i32 3]}
    !1 = !{i32 1, !"wchar_size", i32 4}
    !2 = !{i32 7, !"PIC Level", i32 2}
    !3 = !{i32 7, !"uwtable", i32 2}
    !4 = !{i32 7, !"frame-pointer", i32 2}
    !5 = !{!"Apple clang version 14.0.3 (clang-1403.0.22.14.1)"}
    

    那么 -O3 是什么含义呢? 下面, 我整理了 Clang 遍历过程中所有的优化参数的具体定义.

    • 优化级别 O0 :这是最低级别的优化,默认情况下会开启。在这个级别下,编译器将进行最少的优化,以保持代码的简单性和可读性。

    • 优化级别 O1 :这个级别会应用一些轻量级的优化,以提高代码的执行速度和空间效率。编译时间通常较短,但优化效果有限。

    • 优化级别 O2 :这是默认的优化级别。它会开启更多的优化选项,包括内联函数、循环展开、变量替代等,以提高代码的执行速度。编译时间可能会比 O1 长一些。

    • 优化级别 O3 :这个级别会比 O2 应用更多的优化,但可能会导致编译时间显著增加。它会更加注重代码的性能优化,可能会牺牲一些可读性和可维护性。

    • 优化级别 Os :这个级别的优化旨在减小生成的可执行文件的大小,而不是提高执行速度。它可以通过减小代码的体积来节省内存和存储空间。

    • 优化级别 Oz :这是一个综合了优化级别 O2 和优化级别 Os 的级别。它会尽量减小代码的体积,并使用更多的性能优化。


    同时, 在 Xcode 编译项目工程时, 我们也是可以通过 Build SettingsOptimization Level 调整中间代码的优化级别.


    上述步骤就是编译前端做的工作, 下面来说一下编译后端都做了哪些工作.


  • 目标代码生成(Code Generation)

    将优化后的中间代码转换为目标机器代码(可执行代码)。代码生成器负责将中间代码转换为目标机器的指令序列,包括寄存器分配、指令选择和指令调度等。

    对于iOS来说, 我们主要根据不同架构的CPU转换成汇编代码, 然后再生成对应的可执行文件. 这样CPU就可以执行了.

    生成汇编代码, 我们主要利用到以下指令

    clang -S -o - helloworld.c | open -f 
    

    这时候, 汇编代码生成结果如下所示.

        .section	__TEXT,__text,regular,pure_instructions.build_version macos, 13, 0	sdk_version 13, 3.section	__DATA,__data.globl	_a                              ## @a.p2align	2
    _a:.long	7                               ## 0x7.subsections_via_symbols
    

  • 可执行文件的生成(Create Mach-O File)

    这一个步骤应该算在 目标代码生成 的一部分, 在生成目标平台的机器代码后,Clang 还需要进行链接操作,将生成的机器代码与所需的库文件和其他依赖项进行链接,以生成最终的 Mach-O 可执行文件。

    注: 由于 helloworld.c 中的内容为 int a = 1 + 3 * 2; , 并不能执行, 所以我们要在 helloworld.c 写一个完整的C语言main函数. 如下所示.

    #include <stdio.h>
    int main(int argc, char *argv[])
    {printf("Hello World!\n");return 0;
    }
    

    我们可以通过下面终端指令生成 Mach-O 文件.

    clang helloworld.c -o helloworld.out
    

    然后, 我们可以在终端使用 ./helloworld.out 正常调用Mach-O文件了.

    shenjingsaodong@Mac Desktop % ./helloworld.out 
    
    Hello World!
    

    然后, 我们可以通过 otool 工具可以查看生成的可执行文件的 sectionsegment, 这里我就不懂了… GG

    Segment __PAGEZERO: 0x100000000 (zero fill)  (vmaddr 0x0 fileoff 0)
    Segment __TEXT: 0x4000 (vmaddr 0x100000000 fileoff 0)Section __text: 0x2c (addr 0x100003f70 offset 16240)Section __stubs: 0x6 (addr 0x100003f9c offset 16284)Section __cstring: 0xe (addr 0x100003fa2 offset 16290)Section __unwind_info: 0x48 (addr 0x100003fb0 offset 16304)total 0x88
    Segment __DATA_CONST: 0x4000 (vmaddr 0x100004000 fileoff 16384)Section __got: 0x8 (addr 0x100004000 offset 16384)total 0x8
    Segment __LINKEDIT: 0x4000 (vmaddr 0x100008000 fileoff 32768)
    total 0x10000c000
    

  • 符号表管理(Symbol Table Management)

    维护符号表,记录程序中定义的变量、函数、常量等符号的信息。符号表包括符号名称、类型、作用域等信息,用于语义分析和代码生成阶段的符号查找和类型检查。

    当在编译过程中发生错误时, 我们是可以利用 符号表 来定位到报错位置在源文件中的位置的.

    通过, 我们在项目上线后, 当线上发生事故异常时, 我们也是可以利用 符号表 来确定问题所在的.

    基于Clang编译器,符号表管理的过程如下:

    • 符号表的组织

      Clang使用哈希表、树等数据结构来实现符号表。哈希表可以提供快速的查找和插入操作,树结构可以支持符号作用域的嵌套和查找。

    • 符号的添加

      在词法分析和语法分析过程中,当遇到变量、函数、类型定义等符号的声明和定义时,Clang将根据符号的属性创建一个符号表项,并将其添加到符号表中。符号表项包括符号名称、类型、作用域、存储位置等信息。

    • 符号的查找

      在进行语义分析和代码生成时,Clang需要查找符号表来获取符号的属性信息,如类型、存储位置等。通过符号名称和作用域,Clang可以在符号表中快速定位到符号表项,以提供相应的属性信息。

    • 符号的作用域

      符号表管理也涉及符号的作用域管理。Clang会维护一个作用域栈来跟踪当前有效的作用域。当进入一个新的作用域时,例如函数、循环或块作用域,Clang会将该作用域压入作用域栈中,并将内部的符号添加到符号表中。当离开作用域时,Clang会将该作用域从作用域栈中弹出。

    • 符号的重定义和重复定义检查

      Clang会检查符号表中是否存在重复定义或重定义的符号。如果发现重定义或重复定义的符号,Clang会生成相应的错误信息。


  • 错误管理(Error Management)

    在编译过程难免会遇到编译错误, 这时候, 出错管理体现着无与伦比的作用, 它帮助程序员发现和解决代码中的错误. 提高代码的可靠性和质量。在编译器的实现中,错误管理需要综合考虑准确性、恢复能力和用户友好性,以提供有效的错误处理和提示信息。

    以下是编译过程中的错误管理部分:

    • 错误检测(Error Detection)

      编译器在进行词法分析、语法分析、语义分析等阶段会检测代码中的语法错误、类型错误和其他语义错误。一旦检测到错误,编译器会生成相应的错误消息。

    • 错误报告(Error Reporting)

      编译器在检测到错误后,会将错误信息记录下来,并向用户报告。错误报告通常包括错误类型、错误位置和错误描述等信息,以帮助程序员找出和解决错误。

    • 错误恢复(Error Recovery)

      当编译器遇到错误后,可以尝试进行错误恢复来继续编译。错误恢复策略可以包括跳过错误部分、补全缺失部分、重新同步语法等,以尽可能提供更多的错误信息和继续编译的机会。


总结

这一篇博客还是比较偏向于理论知识的, 下一篇博客, 我们就着 词法分析 语法分析 语义分析 中间代码生成 这几个前端过程来构建我们的 PrattParser 解释器. 如果有任何问题, 欢迎留言. 欢迎持续关注骚栋.


点击查看 基于Swift的PrattParser项目


本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/110497.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【PXIE301-211】基于PXIE总线的16路并行LVDS数据采集、4路低速、2路隔离RS422数据处理平台

板卡概述 PXIE301-211A是一款基于PXIE总线架构的16路高速LVDS、4路低速LVDS采集、2路隔离RS422数据处理平台&#xff0c;该平台板卡采用Xilinx的高性能Kintex 7系列FPGA XC7K325T作为实时处理器&#xff0c;实现各个接口之间的互联。板载1组64位的DDR3 SDRAM用作数据缓存。板卡…

[Hive] explode

在 Hive 中&#xff0c;explode 函数用于将数组&#xff08;Array&#xff09;或者Map类型的列拆分成多行&#xff0c; 每个元素或键值对为一行。这允许我们在查询中对数组或 Map 进行扁平化操作。 下面是使用 explode 函数的示例&#xff1a; 假设我们有一个包含数组字段的表…

pycharm操作git

pycharm操作git 之前用命令做的所有操作&#xff0c;使用pychrm点点就可以完成 克隆代码 上方工具栏Git ⇢ \dashrightarrow ⇢ Clone ⇢ \dashrightarrow ⇢ 填写地址&#xff08;http、ssh&#xff09; 提交到暂存区&#xff0c;提交到版本库&#xff0c;推送到远程 直接…

HTTP和HTTPS

目录 HTTP协议 1.HTTP协议 2.HTTP请求 URL 方法 GET请求 post请求 header报头 请求正文&#xff08;body&#xff09; 3.HTTP响应 结构 常见的状态码 4.form表单构造HTTP请求 5.通过ajax构造HTTP请求 6.使用工具postman ​编辑 HTTPS 1.对称加密 2.非对称加密…

填充颜色游戏

无语死了这题。 题目描述 小明最近迷上下面一款游戏。游戏开始时&#xff0c; 系统将随机生成一个 N N 的 正方形棋盘&#xff0c; 棋盘的每个格子都由六种颜色中的一种绘制。在每个步骤中&#xff0c; 玩家选择一种颜色&#xff0c; 并将与左上角连接的所有网格更改为该特…

jenkins 安装与使用、用户权限划分

jenkins 安装与使用 安装插件&#xff1a; 开启该插件功能 验证用户管理 创建web01~02 使用web01登录 用户权限划分 安装 Role-Based Strategy 插件后&#xff0c;系统管理 中多了如图下所示的一个功能&#xff0c;用户权限的划分就是靠他来做的 创建角色 重新访问 创建项目…

Zabbix“专家坐诊”第207期问答汇总

问题一 Q&#xff1a;不小心把host表删除了&#xff0c;怎么处理&#xff1f;现在使用的zabbix 4.0.3的server&#xff0c;agent是4.2.1&#xff0c;能不能不动agent的情况下升级server版本&#xff0c;重新部署&#xff1f; A&#xff1a;数据库有备份话恢复即可&#xff0c;…

SSTI模板注入(flask) 学习总结

文章目录 Flask-jinja2 SSTI 一般利用姿势SSTI 中常用的魔术方法内建函数 利用 SSTI 读取文件Python 2Python 3 利用 SSTI 执行命令寻找内建函数 eval 执行命令寻找 os 模块执行命令寻找 popen 函数执行命令寻找 importlib 类执行命令寻找 linecache 函数执行命令寻找 subproce…

windows中elasticsearch7中添加用户名密码验证

1.找到elsatic的bin目录输入cmd 2.生成ca证书 输入 elasticsearch-certutil ca 在es7根目录生成ca证书&#xff0c;输入密码时直接回车即可&#xff0c;否则后面会报错 Please enter the desired output file [elastic-stack-ca.p12]: #这里直接回车即可 Enter password for…

JAVA学习日记1——JAVA简介及第一个java程序

简单记忆 JAVA SE &#xff1a;标准版&#xff0c;核心基础 JAVA EE&#xff1a;企业版&#xff0c;进阶 JDK&#xff1a;Java Development Kit&#xff0c;Java开发工具包&#xff0c;包含JRE JRE&#xff1a;Java Runtime Environment&#xff0c;Java运行时环境&#xff…

手撕 视觉slam14讲 ch7 / pose_estimation_3d2d.cpp (2)

上一篇文章中: 手撕ch7/pose_estimation_3d2d&#xff08;1&#xff09;&#xff0c;我们调用了epnp的方法进行位姿估计&#xff0c;这里我们使用非线性优化的方法来求解位姿&#xff0c;使用g2o进行BA优化 首先介绍g2o&#xff1a;可参考&#xff1a;g2o详细介绍 1.构建g2o图…

加权平均、EMD、小波等方法去噪效果对比

加权平均、EMD、小波等方法去噪效果对比 代码 整体代码如下 %% clear all; clc;load(data_filter120Hz.mat); %可自己生成随机噪声 fs1000;%采样频率是1000Hz %% %生成正弦波信号 tlinspace(0, length(data)/fs-1/fs, length(data)); y1 15*sin(2*pi* 2.8 *t);%生成频率为2.…

Android之使用QBadgeView给TabLayout顶部栏设置数量角标,数值可更新

TabLayout搭配ViewPager、Fragement使用可看另一篇文章&#xff1a; Android中TabLayoutViewPagerFragment实现顶部导航栏 本文主要描述给TabLayout的某一栏添加角标&#xff0c;数值可更新&#xff1a; 一、效果 二、TabLayout使用 1、xml文件中 <com.google.android.m…

通讯协议学习之路:QSPI协议理论

通讯协议之路主要分为两部分&#xff0c;第一部分从理论上面讲解各类协议的通讯原理以及通讯格式&#xff0c;第二部分从具体运用上讲解各类通讯协议的具体应用方法。 后续文章会同时发表在个人博客(jason1016.club)、CSDN&#xff1b;视频会发布在bilibili(UID:399951374) 一、…

【Django 01】环境搭配与项目配置

1. 介绍 https://github.com/Joe-2002/sweettalk-django4.2#readme Django 是一个使用 Python 编写的开源 Web 应用程序框架&#xff0c;它提供了一套用于快速开发安全、 可扩展和高效的 Web 应用程序的工具和功能。Django 基于 MVC&#xff08;Model-View-Controller&#xf…

Windows11家庭版没有本地组策略编辑器解决

1. 新建一个文本文件将下面代码粘到里面&#xff0c;保存后修改后缀为.cmd或者.bat echo off pushd "%~dp0"dir /b C:\Windows\servicing\Packages\Microsoft-Windows-GroupPolicy-ClientExtensions-Package~3*.mum >List.txt dir /b C:\Windows\servicing\Packa…

redis(普通连接和连接池、字符串类型、hash类型、列表类型)

1 redis普通连接和连接池 1.1 普通连接 1.2 连接池 2 redis字符串类型 3 redis hash类型 4 redis列表类型 1 redis普通连接和连接池 #1 python 代码作为客户端---》连接# 2 安装模块&#xff1a;pip install redis1.1 普通连接 from redis import Redisconn Redis(host&qu…

Selenium浏览器自动化怎么上传文件

Selenium 封装了现成的文件上传操作。但是随着现代前端框架的发展&#xff0c;文件上传的方式越来越多样。而有一些文件上传的控件&#xff0c;要做自动化控制会更复杂一些&#xff0c;这篇文章主要讨论在复杂情况下&#xff0c;如何通过自动化完成文件上传。 1. input 元素上传…

蓝桥杯每日一题2023.10.17

迷宫 - 蓝桥云课 (lanqiao.cn) 题目描述 样例&#xff1a; 01010101001011001001010110010110100100001000101010 00001000100000101010010000100000001001100110100101 01111011010010001000001101001011100011000000010000 0100000000101010001101000010100000101010101100…

Ubuntu18中的连接网络图标恢复

上图的图标不存在&#xff0c;也连不上网。 输入命令停止网络管理 service NetworkManager stop删除网络管理缓存文件 sudo rm /var/lib/NetworkManager/NetworkManager.state重启网络管理 service NetworkManager start修改网络管理文件 将‘managedfalse’修改为‘man…