从零写一个编译器(三):语法分析之几个基础数据结构

项目的完整代码在 C2j-Compiler

写在前面

这个系列算作为我自己在学习写一个编译器的过程的一些记录,算法之类的都没有记录原理性的东西,想知道原理的在龙书里都写得非常清楚,但是我自己一开始是不怎么看得下来,到现在都还没有完整的看完,它像是一本给已经有基础的人写的书。

在parse包里一共有8个文件,就是语法分析阶段写的所有东西啦

  • Symbols.java
  • Production.java
  • SyntaxProductionInit.java
  • FirstSetBuilder.java
  • ProductionManager.java
  • ProductionsStateNode.java
  • StateNodeManager.java
  • LRStateTableParser.java

项目的完整代码在 C2j-Compiler

SyntaxProductionInit 语法初始化

在上一篇说了,竟然要验证句子正确与否,自然就需要语法,也就是给出相应的语法推导式

所有语法的初始化工作都在SyntaxProductionInit里完成

///EXT_DECL_LIST ->EXT_DECL_LIST COMMA EXT_DECL
right = getProductionRight(new int[]{Token.EXT_DECL_LIST.ordinal(), Token.COMMA.ordinal(), Token.EXT_DECL.ordinal()});
production = new Production(productionNum, Token.EXT_DECL_LIST.ordinal(), 0, right);
productionNum++;
addProduction(production, false);

比如下面这个就对应C语言的变量声明语句的推导式,PROGRAM是整个推导式的开始符号,EXT_DEF_LIST就是声明列表,这里的EXT_DEF_LIST -> EXT_DEF_LIST EXT_DEF需要注意一下是左递归情况,LR语法是可以处理的,关于这个可以看之前的博文。

比如EXT_DECL_LIST -> EXT_DECL -> VAR_DECL 多个变量名称声明可以推导是一个变量名称声明或者多个变量名称声明 + 逗号 + 变量名称声明

VAR_DECL 则可以是一个标识符或者一个多重指针

即一个从叶子节点不断的推导,读入终结符,最后推导到开始符号,且输入流也已经读完

/*
*   PROGRAM -> EXT_DEF_LIST
*
*  EXT_DEF_LIST -> EXT_DEF_LIST EXT_DEF
*
*  EXT_DEF -> OPT_SPECIFIERS EXT_DECL_LIST  SEMI
*             | OPT_SPECIFIERS SEMI
*
*
*  EXT_DECL_LIST ->   EXT_DECL
*                   | EXT_DECL_LIST COMMA EXT_DECL
*
*  EXT_DECL -> VAR_DECL
*
*  OPT_SPECIFIERS -> CLASS TTYPE
*                   | TTYPE
*                   | SPECIFIERS
*                   | EMPTY?
*
*  SPECIFIERS -> TYPE_OR_CLASS
*                | SPECIFIERS TYPE_OR_CLASS
*
*
*  TYPE_OR_CLASS -> TYPE_SPECIFIER
*                   | CLASS
*
*  TYPE_SPECIFIER ->  TYPE
*
*  NEW_NAME -> NAME
*
*  NAME_NT -> NAME
*
*  VAR_DECL -> | NEW_NAME
*
*              | START VAR_DECL
*
*/

在语法推导式初始化的过程一共要构建三个数据结构

private HashMap<Integer, ArrayList<Production>> productionMap = new HashMap<>();
private HashMap<Integer, Symbols> symbolMap = new HashMap<>();
private ArrayList<Symbols> symbolArray = new ArrayList<>();
  • ProductionMap的key就是推导式的左边,value即对应的一个或多个产生式
  • SymbolMap类似ProductionMap,key是推导式的左边,value是一个或者多个产生式的右边

Symbol类似Production,也是用来表示产生式的,但是稍有点不同,也还包括终结符,在后面也会有不同的作用,

//Symbols
public int value;
public ArrayList<int[]> productions;
public ArrayList<Integer> firstSet = new ArrayList<>();
public boolean isNullable;

如果一个非终结符,它可以推导出空集,那么这样的非终结符我们称之为nullable的非终结符

  • symbolArray存储着每一个Symbols对象,在后面也会有不一样的作用

Production 产生式类

在上一篇里说到验证语法的过程就是在一堆对应语法产生式中推导出答案,Production类就是来表示一个产生式

private int dotPos = 0;
    private int left;
    private ArrayList<Integer> right;
    private ArrayList<Integer> lookAhead = new ArrayList<>();
    private int productionNum = -1;

    public Production(int productionNum, int left, int dot, ArrayList<Integer> right) {
        this.left = left;
        this.right = right;
        this.productionNum = productionNum;
        lookAhead.add(Token.SEMI.ordinal());

        if (dot >= right.size()) {
            dot = right.size();
        }
        this.dotPos = dot;
}
  1. left和right就是产生式的左边和右边,都是用之前Token的值来表示
  2. lookahead向前看集合,后面用到再提
  3. dos就像构造这个自动机的辅助工具,之后用到详细说
  4. productionNum是这个产生式对应编号,这个编号是在初始化语法的时候给定的

小结

这一篇主要介绍了几个数据结构,这几个数据结构都是之后构建有限状态自动机的基础。本来想把状态机的构建也写在这一篇,但是如果加上去之后,篇幅太长,加一部分会感觉不成模块,有点分散,所以自动机的构建就写在下一篇。

另外我的github博客:https://dejavudwh.cn/

原文地址:https://www.cnblogs.com/secoding/p/11367530.html

时间: 2024-10-07 07:57:00

从零写一个编译器(三):语法分析之几个基础数据结构的相关文章

从零写一个编译器(二):语法分析之前置知识

前言 在之前完成了词法分析之后,得到了Token流,那么接下来就是实现语法分析器来输入Token流得到抽象语法树 (Abstract Syntax Tree,AST).但是在完成这个语法分析器不像词法分析器,直接手撸就好了,还是需要一些前置的知识. 这些前置知识在之前的博文都有提起过 之前的博文目录 项目的完整代码在 C2j-Compiler 什么是语法分析? 如果我们把词法分析看成是组合单词,输出单词流,那么语法分析就可以看作是检查这些单词是不是符合语法的过程.在词法分析的时候用正则或者手工比

从零写一个编译器(四):语法分析之构造有限状态自动机

项目的完整代码在 C2j-Compiler 通过上一篇对几个构造自动机的基础数据结构的描述,现在就可以正式来构造有限状态自动机 我们先用一个小一点的语法推导式来描述这个过程 s -> e e -> e + t e -> t t -> t * f t -> f f -> ( e ) f -> NUM 初始化 状态0是状态机的初始状态,它包含着语法表达式中的起始表达式,也就是编号为0的表达式: 0: s -> . e 这里的点也就是之前Production类中的

从零写一个编译器(六):语法分析之表驱动语法分析

项目的完整代码在 C2j-Compiler 前言 上一篇已经正式的完成了有限状态自动机的构建和足够判断reduce的信息,接下来的任务就是根据这个有限状态自动机来完成语法分析表和根据这个表来实现语法分析 reduce信息 在完成语法分析表之前,还差最后一个任务,那就是描述reduce信息,来指导自动机是否该进行reduce操作 reduce信息在ProductionsStateNode各自的节点里完成,只要遍历节点里的产生式,如果符号"."位于表达式的末尾,那么该节点即可根据该表达式以

从零写一个编译器(五):语法分析之自动机的缺陷和改进

项目的完整代码在 C2j-Compiler 前言 在上一篇,已经成功的构建了有限状态自动机,但是这个自动机还存在两个问题: 无法处理shift/reduce矛盾 状态节点太多,导致自动机过大,效率较低 这一节就要解决这两个问题 shift/reduce矛盾 看上一节那个例子的一个节点 e -> t . t -> t . * f 这时候通过状态节点0输入t跳转到这个节点,但是这时候状态机无法分清是根据推导式1做reduce还是根据推导式2做shift操作,这种情况就称之为shift / redu

从零写一个编译器(十):编译前传之直接解释执行

项目的完整代码在 C2j-Compiler 前言 这一篇不看也不会影响后面代码生成部分 现在经过词法分析语法分析语义分析,终于可以进入最核心的部分了.前面那部分可以称作编译器的前端,代码生成代码优化都是属于编译器后端,如今有关编译器的工作岗位主要都是对后端的研究.当然现在写的这个编译器因为水平有限,并没有优化部分. 在进行代码生成部分之前,我们先来根据AST来直接解释执行,其实就是对AST的遍历.现代解释器一般都是生成一个比较低级的指令然后跑在虚拟机上,但是简单起见我们就直接根据AST解释执行的

从零写一个编译器(七):语义分析之符号表的数据结构

项目的完整代码在 C2j-Compiler 前言 有关符号表的文件都在symboltable包里 前面我们通过完成一个LALR(1)有限状态自动机和一个reduce信息来构建了一个语法解析表,正式完成了C语言的语法解析.接下来就是进入语义分析部分,和在第二篇提到的一样,语义分析的主要任务就是生成符号表来记录变量和变量的类型,并且发现不符合语义的语句 描述变量 在C语言里对变量声明定义里,主要有两种描述 说明符(Specifier) 说明符也就是对应C语言的一些描述变量类型或者像static,ex

从零写一个编译器(十三):代码生成之遍历AST

项目的完整代码在 C2j-Compiler 前言 在上一篇完成对JVM指令的生成,下面就可以真正进入代码生成部分了.通常现代编译器都是先把生成IR,再经过代码优化等等,最后才编译成目标平台代码.但是时间水平有限,我们没有IR也没有代码优化,就直接利用AST生成Java字节码 入口 进行代码生成的入口在CodeGen,和之前解释器一样:先获取main函数的头节点,从这个节点开始,先进入函数定义,再进入代码块 函数定义节点 在进入函数定义节点的时候,就要生成一个函数定义对应的Java字节码,即一个静

从零写一个编译器(十一):代码生成之Java字节码基础

项目的完整代码在 C2j-Compiler 前言 第十一篇,终于要进入代码生成部分了,但是但是在此之前,因为我们要做的是C语言到字节码的编译,所以自然要了解一些字节码,但是由于C语言比较简单,所以只需要了解一些字节码基础 JVM的基本机制 JVM有一个执行环境叫做stack frame 这个环境有两个基本数据结构 执行堆栈:指令的执行,都会围绕这个堆栈来进行 局部变量数组,参数和局部变量就存储在这个数组. 还有一个PC指针,它指向下一条要执行的指令. 举一个例子 int f(int a, int

学了编译原理能否用 Java 写一个编译器或解释器?

16 个回答 默认排序? RednaxelaFX JavaScript.编译原理.编程 等 7 个话题的优秀回答者 282 人赞同了该回答 能.我一开始学编译原理的时候就是用Java写了好多小编译器和解释器.其实用什么语言来实现编译器并不是最重要的部分(虽然Java也不是实现编译器最方便的语言),最初用啥语言都可以. 我在大学的时候,我们的软件工程和计算机科学的编译原理课的作业好像都是可以用Java来写的.反正我印象中我给这两门课写的作业都是用的Java. ===================