cass出现验证许可_从零开始开发JVM语言(九)验证有效性

目录戳这里

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

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

这一步要做的事不多:

#检查循环继承 这一步非常简单,对于“类”来说,用一个循环不停的获取父类,看会不会获取到自己。对于接口来说,可以选择用栈跑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

也就是

e4e34b267ad5a216e0106ccb5b8dd46d.png

对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

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值