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

项目的完整代码在 C2j-Compilerjava

写在前面

这个系列算做为我本身在学习写一个编译器的过程的一些记录,算法之类的都没有记录原理性的东西,想知道原理的在龙书里都写得很是清楚,可是我本身一开始是不怎么看得下来,到如今都尚未完整的看完,它像是一本给已经有基础的人写的书。git

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

  • 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 多个变量名称声明能够推导是一个变量名称声明或者多个变量名称声明 + 逗号 + 变量名称声明ui

VAR_DECL 则能够是一个标识符或者一个多重指针this

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

/* * 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是这个产生式对应编号,这个编号是在初始化语法的时候给定的

小结

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

相关文章
相关标签/搜索