编译实验lab4:错误分析

概述:

本次实验的内容是在前两次词法分析和语法分析的基础上,为编译器前段添加错误处理功能。由于这次实验中,参考学长的博客学到了很多思路,并对当前代码做了较大的重构修改,遂决定写下本篇博客做一梳理记录,并会在之后逐步补全完善系列实验内容。

一、问题描述:

1.1任务概览

1.2错误详情

二、实现思路

2.1准备工作:

在第二次的实验中,我们实现了Lexer相关类,其中的关键函数为Lexer.next () 能够从字符串流中提取出一个个Token并存入TokenList中

在第三次的实验中,我们使用递归下降的方法将生成了一颗包含所有终结符和非终结符的抽象语法树AST,以便后续实验使用

2.1.1代码重构

由于在第三次实验中,我偷懒并未新增各种为终结符节点类,导致我在在后续的实验中需要重复遍历语法树时变得很困难,于是在开始本次实验前,我新建了parser包下新建了包含所有非终结符的节点类,并让他们统一继承节点Node类,以便于构建出方便遍历的语法树。

我们按照功能将非终结符分成如下几个大类:

1、表达式相关:存放Exp相关的节点类

2、函数相关:存放Func相关的节点类

3、语句相关:存放Stmt相关的节点类

4、变(常)量定义相关:存放Const、Var相关的节点类

5、其他:存放入Block相关的节点类

此外我还设置了一个NodeToken节点类作为AST语法树的叶节点。他也直接继承自Node类

2.2新增内容

参考学长的博客内容,本次实验的主要实现思路如下:

1、为每个AST节点设置错误检查函数checkError () 函数,该函数在于检查本节点中可能出现的错误类型,并递归调用子节点的错误检查函数完成整棵AST树的错误检查。

2、在进行错误检查时我们需要借助符号表的帮助,因此我们可以选择在错误检查这一遍遍历的时候同时完成符号表的建立。

3、对于符号表的构建,我们选择采用课件上的方法,即栈式符号表。在我们顺序遍历AST的每个节点,如果是进入一个新的语句块,如Block,FuncDef,Loop则新建一个符号表,插入到符号表栈的栈顶。当离开该语句块时,从符号栈中弹出该符号表,并插入到有所有符号表组成的ArrayList中备用。

三、代码细节

3.1构建AST树

3.1.1Node节点

首先新建Node节点作为所以有节点的父类,为其设计四个属性:startLine,endLine,type和children。前两个属性用来记录这个语法成分的起始行和终止行,用于错误处理时输出错误位置信息。type用于记录节点类型,即该节点具体属于哪一个非终结符(或者终结符)。children则是一个存储了当前节点所有子节点的ArrayList。

    protected int startLine;
    protected int endLine;
    protected SyntaxVarType type;
    protected ArrayList<Node> children;

为Node节点设置checkError方法,该方法会递归遍历调用子节点的checkError方法,之后在不同的子节点中会重写这一方法

    public void checkError() {
        // 如果没有子节点即叶子结点
        if (children == null) return;
        // 否则,递归遍历检查子节点
        for (Node child : children) {
            child.checkError();
        }
    }

3.2符号表相关类

3.2.1符号类

Symbol类作为所有符号类的父类具有name和type两个属性,分别用于记录符号的名称和符号类型

    protected String name;
    protected SymbolType type;
3.2.2符号类派生

对于需要记录到符号表中的符号一共包括下面三类:变量、常量、函数

对于变量与常量,设置SymbolVar和SymbolConst类继承自Symbol类,并设置变量类型、变量维度、变量值、等属性

    private ValueType valueType;  // 变量类型
    private int dimension; // 变量维度
    private ArrayList<Integer> valueList; // 变量的值
    private ArrayList<Integer> lenList; // 变量各个维度的长度
    private boolean isGlobal; // 是否是全局变量

对于函数,设置SymbolFunc类继承自Symbol类,并设置函数返回类型,函数参数类型列表,函数参数维度等属性

    private ValueType returnType;  // 函数返回类型
    private ArrayList<ValueType> FParamTypes; // 函数参数类型
    private ArrayList<Integer> FParamDims; // 函数参数维度
3.2.3符号表管理类

为了便于符号表的插入删除和管理,我们使用单例模式创建了一个符号表管理类SymbolManager,它的属性包含一个临时符号表栈,一个符号表列表,当前符号表Id,上一个符号表Id以及其他一些辅助属性

    private static final SymbolManager symbolManager = new SymbolManager();
    private Stack<SymbolTable> symbolTableStack;
    private HashMap<Integer, SymbolTable> symbolTables;
    private int currentTableId;
    private int latestTableId;

    private SymbolFunc latestFunc; // 记录当前所在的函数,用于检查return语句
    private int loopDepth; // 记录当前所在的循环深度,用于检查是否出现break、continue语句
    private boolean isGlobal; // 记录当前所属位置是否为全局区域(当进入函数内部后转为false)

符号插入:在临时符号表栈的栈顶符号表中进行插入

    public boolean addSymbol(Symbol symbol) {
        SymbolTable symbolTable = symbolTableStack.peek();
        if (symbolTable.getSymbolByName(symbol.getName()) != null) {
            return false;
        }
        symbolTable.addSymbol(symbol);

        symbolTables.get(symbolTable.getId()).addSymbol(symbol);
        return true;
    }

符号查找:现在当前栈顶符号表中查找,如果找不到则继续在外层符号表中查找,直到找到或者最外层符号表返回null位置

    public Symbol getSymbolByName(String name) {
        Symbol symbol = null;
        SymbolTable symbolTable = symbolTableStack.peek();
        symbol = symbolTable.getSymbolByName(name);
        while(symbol == null && symbolTable.getFatherId() != -1) {
            symbolTable = symbolTables.get(symbolTable.getFatherId());
            symbol = symbolTable.getSymbolByName(name);
        }
        return symbol;
    }

符号表的创建:在进入一个新的语句块(block,func,loop)时,需要创建符号表,即向临时符号表栈的栈顶插入一个新的符号表

    public void enterBlock() {
        SymbolTable symbolTable = new SymbolTable();
        symbolTable.setId(++latestTableId);
        symbolTable.setFatherId(currentTableId);
        this.symbolTableStack.push(symbolTable);
        this.symbolTables.put(symbolTable.getId(), symbolTable);
        currentTableId = symbolTable.getId();
    }

弹出符号表:在离开一个语句块时,需要将符号表栈顶的符号表弹出,并插入符号表列表中备用

    public void leaveBlock() {
        SymbolTable symbolTable = this.symbolTableStack.pop();
        symbolTables.put(symbolTable.getId(), symbolTable);
        if(!symbolTableStack.isEmpty()) {
            currentTableId = symbolTableStack.peek().getId();
        }
    }

3.3Node中的错误检查(以下方法按所包含的错误顺序排序,不包含任何可能错误类型的节点的checkError方法直接继承父节点Node的checkError即可)

3.3.1 NodeStmt的checkError方法 (包含错误a、f、h1、h2、l、m1、m2)
    @Override
    public void checkError() {
        if(children.get(0) instanceof NodeLVal) {
            // 错误检查 h (不能改变常量的值)中的第一种和第二种情况 :<Stmt>→<LVal>‘=’ <Exp>‘;’    <Stmt>→<LVal>‘=’ ‘getint’ ‘(’ ‘)’ ‘;’
            NodeLVal lValExp = (NodeLVal) children.get(0);
            if (lValExp.isConst()) {
                Printer.addErrorMsg(lValExp.getEndLine(), ErrorType.h);
            }
            super.checkError();
        }
        else if(children.get(0) instanceof NodeBlock) {
            SymbolManager.getInstance().enterBlock();
            super.checkError();
            SymbolManager.getInstance().leaveBlock();
        }
        else if(children.get(0) instanceof NodeToken && children.get(0).getType() == SyntaxVarType.FORTK) {
            SymbolManager.getInstance().enterLoop();
            super.checkError();
            SymbolManager.getInstance().leaveLoop();
        }
        else if(children.get(0) instanceof NodeToken && (children.get(0).getType() == SyntaxVarType.BREAKTK || children.get(0).getType() == SyntaxVarType.CONTINUETK)) {
            // 错误检查 m (在非循环块中使用break和continue语句) 中的第一和第二种情况 :<Stmt>→‘break’‘;’    <Stmt>→‘continue’‘;’
            if (SymbolManager.getInstance().getLoopDepth() <= 0) {
                Printer.addErrorMsg(startLine, ErrorType.m);
            }
            super.checkError();
        }
        else if(children.get(0) instanceof NodeToken && (children.get(0).getType() == SyntaxVarType.RETURNTK)) {
            // 错误检查 f (无返回值的函数存在不匹配的return语句)中的唯一一种情况 :<Stmt>→‘return’ [Exp] ‘;’
            if (children.size() >= 2 && children.get(1) instanceof NodeExp) {
                SymbolFunc func = SymbolManager.getInstance().getLatestFunc();
                if (func.getReturnType() == ValueType.VOID) {
                    Printer.addErrorMsg(children.get(0).getStartLine(), ErrorType.f);
                }
            }
            super.checkError();
        }
        else if(children.get(0) instanceof NodeToken && (children.get(0).getType() == SyntaxVarType.PRINTFTK)) {
            // 错误检查 a (格式字符串中出现非法字符报错行号为 <FormatString> 所在行数。)中的唯一一种情况 :<FormatString> → ‘“‘{<Char>}’”
            String formatString = ((NodeToken)children.get(2)).getValue();
            if (! checkFormatString(formatString)) {
                Printer.addErrorMsg(children.get(2).getStartLine(), ErrorType.a);
            }
            // 错误检查 l (printf中格式字符与表达式个数不匹配)中的唯一一种情况 :<Stmt> → ‘printf’‘(’<FormatString>{‘,’<Exp>}’)’‘;’
            int numOfFormatChar = 0;
            for (int i = 0; i < formatString.length(); i++) {
                if (formatString.charAt(i) == '%' && i+1 < formatString.length() && formatString.charAt(i+1) == 'd') numOfFormatChar++;
            }
            int numOfExp = 0;
            for (int i = 0; i < children.size(); i++) {
                if (children.get(i) instanceof NodeExp) numOfExp++;
            }
            if (numOfFormatChar != numOfExp) {
                Printer.addErrorMsg(children.get(0).getStartLine(), ErrorType.l);
                super.checkError(); return;
            }
            super.checkError();
        }
        else {
            super.checkError();
        }
    }
3.3.2 NodeConstDef的checkError方法(包含错误b1)
@Override
    public void checkError() {
        this.symbol = createSymbol();
        super.checkError();
        // 错误检查 b (常量名重复定义) 中的第一个情况 :<ConstDef>→<Ident> …
        boolean res = SymbolManager.getInstance().addSymbol(symbol);
        if (! res) Printer.addErrorMsg(children.get(0).getEndLine(), ErrorType.b);
    }
3.3.3 NodeVarDef的checkError方法(包含错误b2)
    @Override
    public void checkError() {
        this.symbol = createSymbolVar();
        super.checkError();
        // 错误检查 b(名字重定义)中的第二种情况 :<VarDef>→<Ident> … <Ident> …
        boolean insertInToSymbolTable = SymbolManager.getInstance().addSymbol(symbol);
        if (! insertInToSymbolTable) Printer.addErrorMsg(children.get(0).getEndLine(), ErrorType.b);
    }
3.3.4 NodeFuncDef的checkError方法(包含错误b3、g1)
    @Override
    public void checkError() {
        SymbolManager.getInstance().setGlobal(false); // 接下来不可能出现全局变量的定义
        this.symbol = createSymbol();
        // 错误检查 b (函数名重复定义)中的第三种情况 :<FuncDef>→<FuncType><Ident> …
        boolean res = SymbolManager.getInstance().addSymbol(symbol);
        if (! res) Printer.addErrorMsg(children.get(1).getEndLine(), ErrorType.b);

        // 设置 SymbolManager 进入函数定义块
        SymbolManager.getInstance().enterFuncDef(symbol);

        // check children's error
        // 因为只有形参checkError之后,形参的symbol才能生成,然后函数symbol的参数类型、维度信息才能获得
        // 必须要保证在检查block之前将参数类型、维度信息传给函数的symbol,否则会影响函数体内对函数自调用的检查
        for (Node child : children) {
            if (child instanceof NodeBlock) { // 在检查Block之前加入参数类型、维度信息
                setParamInfo();
            }
            child.checkError();
        }

        // 设置 SymbolManager 离开函数定义块
        SymbolManager.getInstance().leaveFuncDef();

        // 错误检查 g (缺少有返回类型函数的返回语句)中的第一种情况 :<FuncDef> → <FuncType> <Ident> ‘(’ [<FuncFParams>] ‘)’ <Block>
        Node returnStmt = null;
        Node block = children.get(children.size() - 1); //  Block → '{' { BlockItem } '}'
        if(block.getChildren().size() > 2) {
            Node blockItem = block.getChildren().get(block.getChildren().size() - 2); // BlockItem → Decl | Stmt
            Node stmt = blockItem.getChildren().get(0); // Stmt → <ReturnStmt> ';'
            for(Node child : stmt.getChildren()) {
                if(child instanceof NodeToken && child.getType() == SyntaxVarType.RETURNTK) {
                    returnStmt = child;
                    break;
                }
            }
        }
        // 检查是否出现缺少return语句的情况
        if (returnStmt == null && symbol.getReturnType() != ValueType.VOID) {
            Printer.addErrorMsg(endLine, ErrorType.g);
        }
    }
3.3.5 NodeFuncParam的checkError方法(包含错误b4)
    @Override
    public void checkError() {
        this.symbol = createSymbol();
        // 错误检查 b (形参名重复定义)中的第四种情况 :<FuncFParam> → <BType> <Ident> …
        boolean res = SymbolManager.getInstance().addSymbol(symbol);
        if (! res) Printer.addErrorMsg(children.get(1).getEndLine(), ErrorType.b);
        super.checkError();
    }
3.3.6 NodeLVal的checkError方法(包含错误c1)
    @Override
    public void checkError() {
        // 错误检查 c (未定义的标识符)中的第一种情况 :<LVal>→<Ident> …
        NodeToken ident = ((NodeToken)children.get(0));
        if (SymbolManager.getInstance().getSymbolByName(ident.getValue()) == null) {
            Printer.addErrorMsg(ident.getStartLine(), ErrorType.c);
        }
        super.checkError();
    }
3.3.7 NodeUnaryExp的checkError方法(包含错误c2、d、e)
    @Override
    public void checkError() { //  UnaryExp → PrimaryExp | Ident '(' [FuncRParams] ')' | UnaryOp UnaryExp
        // 函数调用
        if (children.get(0) instanceof NodeToken) { // Ident '(' [FuncRParams] ')' (唯一包含可能错误的分支)
            NodeToken ident = ((NodeToken)children.get(0));
            SymbolFunc symbolFunc = (SymbolFunc)SymbolManager.getInstance().getSymbolByName(ident.getValue());
            // 错误检查 c (未定义的函数)中的情况二 :<UnaryExp>→<Ident> …
            if (symbolFunc == null) {
                Printer.addErrorMsg(ident.getStartLine(), ErrorType.c);
                super.checkError(); return;
            }
            // 函数调用
            ArrayList<Integer> FParamDims = symbolFunc.getFParamDims();
            ArrayList<Integer> RParamDims = new ArrayList<>();
            if (children.size() > 2 &&  children.get(2) instanceof NodeFuncRParams) {
                RParamDims = ((NodeFuncRParams) children.get(2)).getRParamDims();
            }
            // 错误检查 d (函数参数个数不匹配)中的唯一情况 : <UnaryExp>→<Ident>‘(’[<FuncRParams>]‘)’
            if (RParamDims.size() != FParamDims.size()) {
                Printer.addErrorMsg(ident.getStartLine(), ErrorType.d);
                super.checkError(); return;
            }
            // 错误检查 e (函数参数类型不匹配)中的唯一情况 : <UnaryExp>→<Ident>‘(’[<FuncRParams>]‘)’
            for (int i = 0; i < RParamDims.size(); i++) {
                if (! FParamDims.get(i).equals(RParamDims.get(i))) {
                    Printer.addErrorMsg(ident.getStartLine(), ErrorType.e);
                    super.checkError(); return; // only print once
                }
            }

        }
        super.checkError();
    }
3.3.8 错误i、j、k

由于错误i、j、k的处理涉及语法解析内容,故放在Parser类中处理

四、其他

本次实验还做了一些其他规范化处理,比如添加了utils文件夹并添加了Printer类等等,就不在本文中详细介绍了。第一次写博客,能力有限,欢迎各位给出指导和建议orz

Lab1-Lab4分别完成了词法分析,语法分析;语义分析;中间代码生成和目标代码生成. Lab1实验报告 词法分析: 能够查出C--源代码中词法未定义的字符以及任何不符合词法单元定义的字符; 识别合法的八进制,如035、072; 识别合法的十六进制数,如0x23、0X4a; 识别合法的指数形式的浮点数,如1.2、.2、1.2e+4; 语法分析: 能够查出C--源代码中的语法错误; 没有词法和语错误的情况,则打印语法树 Lab2实验报告 语义分析: 可对输入的*.cmm文件进行语义分析,并检查如下类型的错误错误类型1:变量在使用时未定义。 错误类型2:函数在调用时未经定义。 错误类型3:变量出现重复定义,或变量与前面定义过的结构体名字重复。 错误类型4:函数出现重复定义。 错误类型5:赋值号两边的表达式类型不匹配。 错误类型6:赋值号左边出现一个只有右值的表达式。 错误类型7:操作数类型不匹配或操作数类型与操作符不匹配。 错误类型8:return语句的返回类型与函数定义的返回类型不匹配。 错误类型9:函数调用时实参与形参的数目或类型不匹配。 错误类型10:对非数组型变量使用“[…]”(数组访问)操作符。 错误类型11:对普通变量使用“(…)”或“()”(函数调用)操作符。 错误类型12:数组访问操作符“[…]”中出现非整数。 错误类型13:对非结构体型变量使用“.”操作符。 错误类型14:访问结构体中未定义过的域。 错误类型15:结构体中域名重复定义(同一结构体中),或在定义时对域进行初始化。 错误类型16:结构体的名字与前面定义过的结构体或变量的名字重复。 错误类型17:直接使用未定义过的结构体来定义变量。 Lab3实验报告 中间代码生成: 在词法分析、语法分析和语义分析的基础上,可对输入的*.cmm文件进行中间代码生成。但不支持结构体类型的变量,不支持高维数组的变量以及不支持一维数组作为函数参数出现。 Lab4实验报告 目标代码生成: 在词法分析、语法分析、语义分析和中间代码生成程序的基础上,将C--源代码翻译为MIPS32指令序列(可以包含伪指令),并在SPIM Simulator上运行。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值