Diagnostic

诊断信息是个非常重要的功能,它可以帮助我们在编写代码的时候发现错误和可能有问题的地方,从而提高我们的编码效率。

为了让用户体验尽可能的好,我们的lsp分析需要尽量容忍用户的错误输入,尽可能多的分析出用户代码中的问题

Fault Tolerance

错误容忍是生成好的诊断信息的前提。在pivot-lang的中,我们分别在两个层面上实现了错误容忍:

  • parser
  • ast

Parser的错误容忍

nom parser架构中,如果出现了一个无法被识别的语句,整个分析器就会终止分析输出错误。这对于错误容忍的要求来说是无法被接受的。所以 我们的编译器不使用nom parser的默认错误处理机制,任何parser阶段产生的nom error都应该被视作bug,我们应该尽可能的避免这种情况。

在parse过程中,如果一些错误语句能非常明显的被识别为一个语法的未完成项(且没有歧义),我们应该将它识别为该语法类型的Node,并且在Node上加一个 flag标识它不完整(常常是is_complete),这样在ast阶段我们就能输出对应的诊断信息。

对于最常见的基础语法单位statementtop_statement,parser提供了一个helper函数except,能够在遇到不可被识别的错误语句的时候 将该“块”语句识别为ErrNode,以方便后续的分析正常进行。

!!!注意:ErrNode虽然很好用,但是它只能输出很宽泛的诊断信息(比如无法识别该语句),它是最后的错误容忍手段,应该尽量避免使用它

AST的错误容忍

由于parse阶段的时候能够容忍错误的语句,对于一些语法错误,ast节点只需要检查自身的完整性就能够输出诊断信息了。而对于语义错误(例如类型不匹配),我们需要在ast阶段 进行分析并获取结果。这些操作目前是在各个节点的emit函数里进行的。所有的emit函数都返回NodeResult类型,如果该节点的emit中出现了错误, 分析将会中断,其对应的错误信息会被添加到ctx(编译上下文)中并且作为error返回。上层函数如果遇到自己依赖的函数报错一定不能重复添加该错误至ctx, 否则会导致错误信息重复输出。上层函数处理自己的依赖报错有两种情况:

  • 直接停止分析并将该错误传递给自己的上级
  • 忽略该错误继续进行分析

一般来说,大部分的expression和statement都会采用第一种方案,而statement block则会采用第二种方案