编译技术-错误处理

这篇具有很好参考价值的文章主要介绍了编译技术-错误处理。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

错误处理

一、总论

​ 错误处理和解析生成中间代码的过程是类似的,都可以看做是一个语法制导翻译的过程。对于遍历出的节点,触发不同的动作符号,这些动作符号在错误处理中用于检测错误,而在生成中间代码生成中,这些动作符号用于生成中间代码。

​ 错误处理会遍历语法生成树,CST 的每个节点都实现了 check 方法用于检查有没有错误。


二、错误的分类

​ 并不是所有的错误都是语义错误,常见的语义错误有“变量重命名,变量未定义“等,这些错误是可以通过词法分析和语法分析的,不会造成语法分析的错误。但是有些错误,是没有办法通过词法分析和语法分析的,有如下这些类

编号 类型 原因
a 非法字符串符号 无法通过词法分析
i 缺少分号 无法通过语法分析
j 缺少右小括号 无法通过语法分析
k 缺少右中括号 无法通过语法分析

​ 对于这样的错误,必须要做的就是当发生这些错误的时候,让词法分析和语法分析正常进行,必须坚持到进行错误处理的步骤。然后有两种思路,一种是在进行词法分析和语法分析的时候就抛出异常并收集,然后在进行错误处理的时候再抛出一些异常,将异常汇总,但是这种方法比较零散。我采用的是在词法分析和语法分析的时候“包容并记录”这种错误,然后在错误处理的时候一并发现。

​ 具体的实现思路就是,对于 a 类错误,并不在词法分析过程中检查双引号间有什么,而是直接存储在 token 中,当 TokenNode 进行 check 的时候,再对其中包含的字符串进行检查。同理,对于三类符号的缺失,可以考虑增强语法分析的功能,使其能够自动补全缺失的符号,然后产生相应的 TokenNode ,但是给这些补全的 TokenNode 打上缺失标记 miss。最终在 TokenNodecheck 中一并检出,即下面的逻辑:

/**
 * 用于缺失的时候产生具体的 error
 * 包括三种缺失错误
 * 字符串错误
 * 名字错误本来就不行,这是因为名字错误包括未定义和重复定义两种,没有办法确定要使用哪种
 */
@Override
public void check(SymbolTable symbolTable)
{
    // 检测缺失情况
    if (token.isSameType(SyntaxType.SEMICN))
    {
        if (miss)
        {
            errors.add(new PansyException(ErrorType.MISS_SEMICOLON, token.getLine()));
        }

    }
    else if (token.isSameType(SyntaxType.RBRACK))
    {
        if (miss)
        {
            errors.add(new PansyException(ErrorType.MISS_RBRACK, token.getLine()));
        }
    }
    else if (token.isSameType(SyntaxType.RPARENT))
    {
        if (miss)
        {
            errors.add(new PansyException(ErrorType.MISS_RPARENT, token.getLine()));
        }
    }
    // 检测是否是合法字符串
    else if (token.isSameType(SyntaxType.STRCON))
    {
        String content = token.getContent();
        // 不算两个双引号
        for (int i = 1; i < content.length() - 1; ++i)
        {
            int ascii = content.charAt(i);
            if (ascii == 32 || ascii == 33 || ascii >= 40 && ascii <= 126)
            {
                if (ascii == 92 && content.charAt(i + 1) != 'n')
                {
                    errors.add(new PansyException(ILLEGAL_SYMBOL, token.getLine()));
                    return;
                }
            }
            else if (ascii == 37)
            {
                if (content.charAt(i + 1) != 'd')
                {
                    errors.add(new PansyException(ILLEGAL_SYMBOL, token.getLine()));
                    return;
                }

            }
            else
            {
                errors.add(new PansyException(ILLEGAL_SYMBOL, token.getLine()));
                return;
            }
        }
    }
}

三、缺失符号处理

​ 正如上文所言,导致语法分析进行不下去的错误一定需要被“包容”,否则就坚持不到错误处理流程了。这种包容设置还有一种“补充”的意味在里面。但是最难的其实是缺失符号对于正常语法解析的影响,缺失符号会让原本就很困难的语法分析变得更加困难,这是因为语法分析本就需要依靠符号确定解析的分支,缺少符号会让这个过程变得模糊不清,这也是想 i, j, k 这类错误并不太多的原因,因为一旦多了,就会导致解析困难。

​ 在三种缺失中,以缺少右中括号最为好处理,中括号意味着维度信息,只要有左中括号,那么右中括号一定是可以补上的,基本上无脑就可以了。

​ 但是对于缺失分号和小括号,有可能造成解析分支的判断不当。所以需要进行回退处理(如果不回退的话就要改写文法,而且文法改的毫无语义意义)。

​ 对于缺失分号,有无法区分赋值语句和表达式的问题。

// 缺失前
i + 1;
a = b + c;

// 缺失后
i + 1
a = b + c;

​ 这就意味着没法通过前瞻(即“在分号前有一个等于号“)来确定是否是赋值语句。所以只能通过“尝试解析-判断 LAST”的方法进行。

​ 对于 return 语句,可能也有这种现象,虽然应该被语义约束了

// 缺失前
return;
a;

// 缺失后
return 
a;

​ 对于 Callee 语句,也是这样(应该也被约束了),这里应该是同时缺失了小括号和分号

// 缺失前
f();
a;

// 缺失后
f(
a;

四、动作属性

​ 因为是语法制导翻译,所以肯定会涉及动作属性,包括继承属性和综合属性,按照理论来讲,可以将这两个东西分别打包成两个类,然后将继承属性当做动作的参数,将综合属性当做动作的返回值,这样是最正统的写法。但是考虑到我设计的时候没有想到,所以我将其全部处理成了全局变量,具体的方法是将其都作为语法树节点基类的保护静态属性,然后这样所有的语法树节点就都可以共享这些变量了。

​ 对于错误处理,一共有以下变量

/**
 * 用来存储语义分析中产生的错误
 */
protecttatic final ArrayList<PansyException> errors = Checker.errors;
/**
 * 用来存储检测日志
 */
protected static final ArrayList<String> checkLog = Checker.checkLog;
/**
 * 当前函数信息
 */
protected static FuncInfo curFuncInfo = null;
/**
 * 是否在循环中
 */
protected static int inLoop = 0;

​ 还有一个符号表,我当成参数了。

public void check(SymbolTable symbolTable)

五、错误检测的可重入性

​ 可能是由于设计的问题,我会对同一个错误进行多次检测,比如说在 AssignStmtNodeLValNode 中都检测了符号是否存在,只不过 AssignStmtNode 还检查了是否更改了常量的值(大概率是设计问题)。

​ 但是没有关系,这样依然是可以保证正确性的,只要我们在输出前需要利用一个 TreeSet 进行去重和排序,然后再输出。

​ 这个涉及一个很好玩的事情,就是对于任何一棵语法子树,从它的根节点调用 check 方法,无论调用多少次,其生成的结果都是一样的。


六、符号表

​ 错误处理同样需要符号表,构建一个栈式符号表即可。可以解决的问题有

编号 内容
b 名字重定义
c 未定义的名字

​ 我在实现符号表的时候,没有将函数符号表和变量符号表分开处理,是因为担心有变量和函数重名(其实就是本质就不应该分开)。符号表的表项称为 SymInfo ,有两个子类,分别是 VarInfoFuncInfoVarInfo 又分为“常量,变量,形参”三种类型。

​ 对于 VarInfo ,需要记录维度信息,因为错误处理对于维度信息的要求较低,所以利用 CSTNode 去记录维度信息

类型 dim1 dim2 CheckDataType
单变量 ZERO ZERO INT
一维数组 ConstExp ZERO DIM1
二维数组 ConstExp ConstExp DIM2
一维指针 INF ZERO DIM1
二维指针 INF ConstExp DIM2

​ 可以看到记录的信息相当的“粗糙”。

​ 实现这两个错误的检测都是很容易的,就不赘述了。


七、类型

​ 错误处理中涉及的类型问题只有函数的实参和形参不匹配的问题,也就是编号 e。类型不匹配只涉及一维和二维和单变量之间的不匹配,并不涉及具体的维度信息,也就是说:

void f(int a[][2])
{}

int main()
{
    int b[1][5] = {{1, 3, 5, 7, 8, 9}};
    f(b)
    return 0;
}
这种是不会报错的。根据这种语义约束,涉及类型有这样 4 种即可:
public enum CheckDataType
{
    // 无返回值
    VOID,
    // 单变量
    INT,
    // 一维指针,比如说 int a[2] 中的 a,就是 DIM1, int a[] 也是 DIM1(本质是传入 a)
    DIM1,
    // 二维指针,比如说 int a[][2] 中的 a,就是 DIM2
    DIM2
}

​ 而其实涉及的运算,只有中括号造成的指针升维运算而已。也就是下面的过程

/**
 * 进行指针运算,获得左值真正的类型信息
 * @param rawType 符号表示的类型信息
 * @return 实际类型信息
 */
private CheckDataType calcDataType(CheckDataType rawType)
{
    int calDim = exps.size();
    int rawDim;
    if (rawType.equals(CheckDataType.INT))
    {
        rawDim = 0;
    }
    else if (rawType.equals(CheckDataType.DIM1))
    {
        rawDim = 1;
    }
    else if (rawType.equals(CheckDataType.DIM2))
    {
        rawDim = 2;
    }
    else
    {
        rawDim = -1;
    }
    int trueDim = rawDim - calDim;
    if (trueDim == 0)
    {
        return CheckDataType.INT;
    }
    else if (trueDim == 1)
    {
        return CheckDataType.DIM1;
    }
    else if (trueDim == 2)
    {
        return CheckDataType.DIM2;
    }
    else
    {
        return CheckDataType.VOID;
    }
}

​ 比如说原本的 a[1][2] 这个数组,在符号表存着的时候,符号表指示他是一个 DIM2 类型,当我们真的使用他的时候,一般是使用 a[0][0] ,那么就是进行了两次升维运算,变成了 INT ,如果将 a 用于传参,那么它就是一个 DIM2 型,而 a[0] 则是 DIM1 型。

​ 对于一个表达式,比如说 1 + 3 + 2 ,他是 INT 型的,究其原因,是 1, 2, 3 都是 INT 型,这就启发我们,计算一个 Exp 的类型,其实要根据它的孩子节点的类型判断,只需要一步步递归,就可以最终溯源到 UnaryExp,然后就会发现最底层只有 3 个东西 Number, LVal, Callee。这三种的类型如下所示

底层节点 类型
Number INT
LVal 查表运算后获得
callee 查表查返回值

​ 对于结合,我采用了一个更加让人自我满足的方式,就是选择所有孩子的类型中最大的作为当前节点的类型,所谓“最大”,就是 DIM2 > DIM1 > INT > VOID 。这是因为这更符合 C 语言特性,比如说 a + 1 ,如果 a 是一个指针,那么 a + 1 也是一个指针,但是指针和指针运算、INTVOID 运算,就没有那么多道理可讲了,所以只能说是自我安慰。这么做不会导致错误,是因为我们也不允许不同类型的元素做算术。


八、Return

​ 与 return 有关的错误有两类,这两类看似相似,但是处理方式却不同。如下所示

编号 类型
f 无返回值的函数存在不匹配的 return 语句
g 有返回值的函数缺少 return 语句,只需要考虑函数最后一句即可

​ 通过记录当前正在处理哪个函数 curFuncInfo,是很容易在进行 ReturnNode 的时候完成对于 f 类错误的检验的,但是对于 g 类错误,却只能在 FuncDefNode 中处理,因为不能在 ReturnNode 中处理,ReturnNode 并不知道自己是函数的最后一条指令,所以没有办法在这里处理。


九、循环

​ 只需要用一个 int 来对当前的循环深度进行计数,如果循环深度为 0 的时候发生了 breakcontinue 就会报错。即如下操作文章来源地址https://www.toymoban.com/news/detail-409517.html

@Override
public void check(SymbolTable symbolTable)
{
    addCheckLog();

    inLoop++;
    for (CSTNode child : children)
    {
        child.check(symbolTable);
    }
    inLoop--;
}

到了这里,关于编译技术-错误处理的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 【C语言】从预处理到机器代码的C语言编译过程解析

    当我们编写一个C语言程序时,我们需要经历一个编译的过程,将源代码转换为可执行的机器代码。这个过程涉及到多个阶段和环节,每个阶段都有其特定的任务和功能。在本篇博客中,我们将详细介绍C语言的编译过程。 目录 一、预处理阶段(Preprocessing) 二、词法分析阶段

    2024年02月13日
    浏览(34)
  • JVM——类加载与字节码技术—编译期处理+类加载阶段

    编译期优化称为语法糖 java基本类型和包装类型之间的自动转换。     在字节码中可以看见,泛型擦除就是字节码中的执行代码不区分是String还是Integer了,统一用Object.  对于取出的Object,这个checkcast用于转换Object为Integer类型。  可以看见 局部变量类型表 里面包含了方法参

    2024年02月11日
    浏览(35)
  • Spring Boot实现统一异常处理的技术解析

    引言 在软件开发过程中,异常处理是非常重要的一环。一个好的异常处理机制可以帮助我们更好地定位问题,提高代码的可维护性和稳定性。Spring Boot作为一款轻量级的Java开发框架,提供了一种简单而高效的方式来实现统一异常处理。本文将详细介绍如何使用Spring Boot实现统

    2024年01月21日
    浏览(41)
  • 微信小程序 ,[JS 文件编译错误] 以下文件体积超过 500KB,已跳过压缩以及 ES6 转 ES5 的处理。

    导入全部图表的echarts.js非常大,如果你的项目较大可能会导致项目上传失败, 在你把整个echarts.js导入项目时开发者工具也会有如下提示: [JS 文件编译错误] 以下文件体积超过 500KB,已跳过压缩以及 ES6 转 ES5 的处理。ec-canvas/echarts.js 我们可以通过图表在线定制来替换 echarts.js 文

    2024年02月13日
    浏览(43)
  • 自然语言处理技术:NLP句法解析树与可视化方法

    自然语言处理(Natural Language Processing,NLP)句法解析树是一种表示自然语言句子结构的图形化方式。它帮助将句子中的每个词汇和短语按照语法规则连接起来,形成一个树状结构,以便更好地理解句子的语法结构和含义。句法解析树对于理解句子的句法关系、依存关系以及语

    2024年02月12日
    浏览(41)
  • PHP表单传值和文件上传:深入解析数据交互与文件处理技术

    目录 表单传值 为什么要表单传值? 表单传值的方式 GET传值 POST传值 GET和POST两种传参方式的不同: PHP接受数据的三种方式 PHP处理复选框数据 复选框表单的命名方式 复选框数据的接受形式 复选框数据的常见处理 复选框细节: 文件上传 原理 表单写法 $_FILES变量详解 移动临

    2024年02月12日
    浏览(81)
  • WIFI 安全总论

    安全策略=认证机制 + 加密机制 为了增强无线网络安全性,至少需要提供认证和加密两个安全机制: 1、 认证机制:认证机制用来对用户的身份进行验证,以限定特定的用户(授权的用户)可以使用网络资源。 2、 加密机制:加密机制用来对无线链路的数据进行加密,以保证

    2024年02月03日
    浏览(25)
  • 可视化高级绘图技巧100篇-总论

    优秀的数据可视化作品可以用三个概括:准确、清晰、优雅。 准确:精准地反馈数据的特征信息(既不遗漏也不冗余,不造成读者疏漏误读细节) 清晰:获取图表特征信息的时间越短越好 优雅:美观(颜色搭配)、协调(相同场景的图表遵循统一规范)。 想要准确、

    2024年02月13日
    浏览(30)
  • 微信小程序编译.wxml文件编译错误

    [ WXML 文件编译错误] ./pages/mine/wallet.wxml Bad value with message 1 | view 刚开始找了半天都没发现错误,后面才发现原来是我写页面的时候,插值语法忘记换成后端传过来的数据了, 无语子- - 。 把插值语法中的中文改掉就不会报错了,不过不同原因都会报这个错误,错误原因大部分

    2024年02月15日
    浏览(55)
  • SSL连接错误导致的编译错误——详细解决方案

    SSL连接错误导致的编译错误——详细解决方案 在处理大数据时,我们经常会遇到与SSL(Secure Sockets Layer)连接相关的问题。其中之一是\\\"SSL peer shut down incorrectly\\\"错误,这个错误提示表明SSL连接的对等方(peer)未正确关闭连接,导致编译错误。本文将介绍如何解决这个问题,并

    2024年02月03日
    浏览(38)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包