您的位置:首页 > 其它

从零开始开发JVM语言(九)验证有效性

2016-06-13 00:00 218 查看
摘要: 编译器 语义分析

目录戳这里

语义分析分为4步。1.记录类型;2.签名;3.验证有效性;4.解析。

上一篇描述了1,2两步。这一篇说说第三步。

这一步要做的事不多:

检查循环继承

方法重写

abstract方法是否全部被实现

编译相关的注解

data class
生成方法


#检查循环继承
这一步非常简单,对于“类”来说,用一个循环不停的获取父类,看会不会获取到自己。对于接口来说,可以选择用栈跑dfs(可以不显式的用栈,单独开个方法递归一下就行了),或者用队列跑bfs,都没问题。

#方法重写检查
这一步非常重要。在步骤3和步骤4都将会用到它记录的内容。
这一步会记录“子类方法重写了哪些方法”以及“父类方法被哪些方法重写”。这里的“类”也包括接口。这一步实现目标可以很多,因为它并不是实际的操作,而是为了检查以及辅助后续步骤。我实现的目标是“找出每个方法
往每一条路走
的最近的被重写的方法”(好像很难用一句话说清楚,不过我准备了一个例子)

这里的“路线”是说由“继承”构成的有向图的边。不过“近”并不是说边权之和,因为很难说两个完全不同的继承关系哪个更“近”。所以只要是不同的继承路线,即使最终汇聚到一起,它们也是一样“近”的。
另外说明一下如下算法不是之前那个tag里的内容,请在新tag里找(后面有链接,点进去就是新tag的内容)

例如这么一个继承关系:

interface A         ; 接口A
interface B:A       ; 接口B继承A
abstract class C:A  ; 类C实现A
class D:C,B         ; 类D继承C实现B

也就是



对D中每一个方法

路线1
,路线1可以找到C。

如果在C中有签名相同的方法,那么很明显,这是
路线1
上最近的被重写的方法,那么记录下来,路线1就算走完了,1.1不会去考虑,因为即使找到,它也不会比C中找到的近。

如果C中没有找到,那么继续走“C往外能够走的路线”,在这里只有
路线1.1
。这条路指向A,于是在A中寻找签名相同的方法。如果找到,那么记录下来。否则丢弃。由于A之上没有其他类型,也就是无路可走,所以
路线1.1
结束。由于
C
的所有路线都走完了,所以
路线1
结束。

路线2
,路线2可以找到B。

如果在B中有签名相同的方法,那么记录之,之后便不再往外走。

如果在B中没有找到,那么继续走
路线2.1
。由于它指向的A之前已经走过,所以此处不再重复走。由于B没有其他路可走,所以
路线2
结束。

所有可走的路线都走完了,算法结束。

从算法就能看出,它应该使用递归实现。而且应该设置两个方法。一种针对类,一种针对接口。这样可以很方便的实现。(戳这里看源码)

此外,记得检查子类方法的返回值是否是父类方法返回值的子类。

#Abstract实现检查
一个非abstract类应当实现父类型的所有abstract方法。
有了上一步的铺垫,这一步就好做很多了。但是也不能毫无技巧。
第一步要得到所有“需要检查”的方法。什么叫“需要检查”呢?首先必须是abstract方法,其次,它们需要是继承树(图)上最近的。

例如

abstract class A
abstract m1()=...
abstract m2()=...
abstract class B:A
m1()=1 ; 实现了方法m1()
class C:B
m2()=2 ; 实现了方法m2()

其中,
A#m1()
A#m2()
都是abstract方法,但是,在检查C类的时候,只有
A#m2()
是“需要检查”的。因为
A#m1()
已经在B中实现了。如果从“远近”的角度讲,
B#m1()
A#m1()
离C更近。

那么如何设计算法来找出这些方法呢?
上一步是“根据子类方法找签名相同的方法”,这一步是“在父类中找离子类最近的abstract方法”。如果前者找到的包括了所有后者找到的,那么不就说明所有abstract方法都被重载了吗~
所以,只需要用“和上一步相同的路线进行寻找”并记录即可。上一步骤中的例子中是这么走的:
1 -> 1.1 -> 2 -> 2.2
。那么这一步还是这么走(还是跑两个递归)。
那么怎么确定方法“是否最近”呢?由于走的路线是从近往远走,所以若是存在
当前方法
已找到的方法中的某个方法
签名相同,那么
当前方法
就不应当被记录,因为它一定是更远的。

#编译相关的注解
java有两个注解是可能引起编译错误的。
@Override
@FunctionalInterface
。前者要求方法要重写,后者要求类型是函数式接口。而
Latte-lang
额外加了一个
@FunctionalAbstractClass
注解。这个注解用来描述“函数式抽象类”(Latte-lang中规定的,具体是啥我这里就不说了,知道函数式接口的大致也能猜出来什么是函数式抽象类)。

由于有了之前的步骤,
@Override
很容易解析出结果。

函数式接口和函数式抽象类要求的是“未实现的方法只有1个”。实际上也很简单。不停的获取父类和父接口,然后对比是否这个方法已经被实现了。如果没有那么“未实现的方法计数”+1。如果计数超过1,或者全跑完了计数还是0,那么就报错。

#data class
这是
Latte-lang
的特性,并不一定对类java的JVM语言适用,所以。。放代码跑= =

不过得说一下,如果使用我这种实现方式,建议在其他特性都做完的情况下再添加之。不过在做其他特性之前就做也没有什么“硬伤”,只是它不属于“基础”特性。(什么是“基础”特性后续章节再描述吧)

到此,语义分析第三步结束。即将开始漫长且代码量奇多的解析步骤了。。

最后,希望看官能够关注我的编译器哦~Latte
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  编译器 语义分析