00:00
好,那我们定义完语法文件以后呢,我们接下来呃,我们就来测试一下这个语法文件,对吧?呃,当然就是说我们除了在依赖里边,我们需要呃去导入这个安泰LS-runtime以外的话呢,你还需要装一个插件。对吧,你还要装一个插件,呃,因为这个插件呢,非常非常好用,对吧,我们一会儿用一下大家就知道了,呃,当然在这的话,你可以点击一下,诶点击插件市场对吧,你要安装一个按台LRV4这样的一个插件。对吧,那么有了这样的一个插件以后呢,比方说我双击这个。那么它配合idea呢,是非常用的,对吧,在这里边的话呢,当我。右键点击这个program的时候,你会发现它有一个叫做test入program。对吧,那在这里边的呢,它会。
01:00
它这个插件呢,会帮你把你输入的代码呢,转成一个抽象语法数,那这样的话呢,你通过解释呃,查看这个抽象语法数呢,你就大概能知道,呃,就说你的语法定义有没有什么问题对吧?比如说在这的话呢,我如果输入一个一加二。诶,当然我在这儿来一个回车,对吧?我们补上回车符,你可以看到它的抽象语法是什么呢?首先是一个program,也就是一个程序,然后呢,呃,然后这一行呢,它是一个,你可以看到它是一个呃,就是说表达式,然后有一个换行符对吧?也就是print expression,然后这个print呢,是由表达式和换行符构成的,那么除掉换行符以外呢,它左边的表达式呢,也是ADD的sub表达式,对吧?它是一个加减表达式,那么加减表达式的左边呢是一,右边是二,然后中间呢是加号,对吧?呃,我们在这儿起了一个别名二的,你可以看到,哎,这个别名就出现了,对吧,当然你可以。
02:04
再来看其他的,比如说A等于一。回射,哎,是吧。那我们这个A等于一,它对应了哪个?呃,就是说规则语法规则,它对应的语句的ID等于expression,然后换行符这样的一个规则,也就是它是一个赋值语句,那么在这的话,你可以看它转成的抽象语法数,就是program下面呢是一个赋值语句,然后呢,ID是A,也就是变量名是A,然后它要赋值的呢是什么呢?赋值的是一。对吧,然后还有一个换行符,这还有个等号,所以说你看看到这个插件呢,实际上可以帮助你去呃了解你这个语法文件,就是你可以看到他说他分析这个代码对吧,分析出来的结果,或者说抽象语法数是什么,当然它还有呃另一种表示方式,对吧?你如果说不看这个可视化呢,你还可以看一下这个它的层级结构对吧?啊,那这实际上也是一个树形结构,这个无非是树形结构的可视化而已。
03:04
呃,所以说我们这个插件呢,它是呃非常好用的,对吧,我们这个插件呢,是非常好用的。那么我们现在。有了这样的一个表达式以后呢,比如说我这是一个一加二对吧,A等于一,B等于二,然后A加BA对不对?呃,我们这个程序的话呢,实际上是有四行语句,你可以看到它就会有一个相对比较复杂的,呃,这个抽象语法数出来对不对?相对比较复杂的这样的一个抽象语法数出来啊,当然在这的话呢,实际上我们一加二,当我输入回车的话,它的求值结果应该是三,A等于一,它是个赋值语句对吧?它呃,其实并没有任何求值结果,或者说你可以认为它求值结果是零啊,因为它只是做了一个赋值,也就是说把A呢赋值为一,然后把B赋值为二,这个也没有任何求值结果,然后A加B的求值结果呢,应该是三,对吧?那么我们怎么来实现这个计算器的求值呢?实际上就是你要通果遍历这颗抽象语法数的方式,比方说我如利的是一个加减表达式。
04:13
式的话,那我就把它左边的表达式的求值结果和右边的表达式的求值结果加起来,那就是这个表达式的结果对吧?也就是说我们需要通过某种方式呢,把这个抽象语法数给它遍历一遍,然后边遍利呢呃边给它求值对吧?你遍历到一个节点的时候呢,你就可以对它呃做求值对吧?按照我们的呃规则去做求值,比如说我在这里边我一个最简单的一。对不对,那么这个一的话呢,它是一个呃,Print expression打印表达式语句,然后呢,它只有一个值对吧?那么它这个表达式是什么呢?它是一个int类型的表达式,对吧?那么它的字面量值呢,是一。对吧?那我们遍历这颗抽象语法数的时候呢,当我们遍历到expression冒号int,或者说遍利到这个int冒号一的时候呢,那么它的求值结果应该是一对吧,它的求值结果应该是一,或者说当你访问到这个节点的时候,对吧?我们访问这个呃,这个抽象语法数,你访问program,然后继续递归的去访问print expression,然后继续访问expression int,然后继续访问INT1,那么它最终的一个整棵数的求值结果呢,应该是一对吧,应该是一,好。
05:30
那我们有了对这个抽象语法数的一个简单的了解之后呢,因为我这里边呃,并没有时间说把这个东西彻底讲明白,对吧,因为毕竟。编译原理在计算机系,呃也属于是也属于基本上是,呃可能不说是最难的课程吧,它也是最难的课程之一,对吧?所以说我们没有办法说是在几句话之内就把它搞明白,但是我们呃基本上通过这样的一个演示的话呢,那你应该知道,就是说它是如何把代码转成抽象语法数的,对吧?呃,然后呢,我们再通过访问抽象语法数的节点的方式呢,来。
06:07
对它进行求职对吧,当然我们这里面需要求职的呢,就是这个expression in对吧,因为你这个new line,比方说你这个。呃,就是说换行符它并不需要做任何求职的动作,对吧,并不需要做任何求职的动作,好我们现在有了这个语法文件以后呢,我们就要自动生成一些代码。对吧,自动生成一个叫做什么呀,当然你可以说的,呃,就是说专业一点叫做语法分析器,对吧,我们自动生成一个语法分析器啊,那么在这的话呢,我就可以啊,就是说。右键它,然后点击一个用装上插件呢,它就可以点击按台con按R就配置按台LR,然后呢,在这儿的话呢,它自动生成。呃,就是说语法分析器对吧,然后它有两种,一种是生成监听者模式的,另一种是生成访问者模式的,对吧,我们把这个勾选掉,反选掉,我们只生成访问者模式对吧?我们只生成访问者模式,那么在这的话呢,这个访问者模式它是我们的23种设计模式之一,对吧,所以说呢。
07:20
你可以去读一下这个设计模式这本书,然后呢,去了解一下访问者模式,对吧?啊了解一下访问者模式好。然后呢,我在这里边的话呢,我点击确定。哎。对吧,我们就已经配置好了,然后呢,我再点击一个generate暗r recognize,那这个就会生成一些语法分析文件,对吧,这个就会生成一些语法分析文件,好啊,那么在这里面的话呢,你可以看到它,诶这个就是它的访问者模式,OK。然后接下来的话呢,我们把这里面的Java文件呢,都给它移动到哪里呢。
08:04
因为我们只需要这几Java文件就可以了,我们给它移动到这个案台r qorial这个包里面,然后点击重构。好,那么这样就重复完了,那你可以看到这里面其实有有这么几个文件,第一个呢是lecturer,这个是词法分析器,这个词法分析器用来干嘛呢。就是我刚才说的对吧,用来把这个代码呢,给它切割成词法标记流对吧,切割成这样的词法标记流,然后呢,除了词法分析器以外呢,还有一个叫做语法分析器,那就是对我们的。你的源代码进行语法分析,然后语法分析完以后,对吧。会把它转换成。抽象语法数,那么你如何去访问抽象语法数的一些节点呢?那这个接口对吧?Visitor,它里边就包含了访问抽象语法数的接口对吧?访问抽象语法数的呃,这个呃接口方法对吧?比如说我在这里,呃,我有些命名对不对?我有些命名我在这的话呢,比如说这个是一个expression对吧,把这个语法规则命名成了print expression,把这个语法规则命名成了sign,呃,把下面这个语法规则命名成什么ma dive等等等等,对吧?所以说你你做了命名以后呢,你在这个代码里面,你在这个接口的方法里面,你就会发现它,呃,有哪些东西呢?比如说哎,Visit multi dive对吧,包括。
09:36
Visit blank visit sign,包括visit print expression,对吧,就是你访问这些节点的时候,我们要做什么样的事情,对吧,我们要做什么样的事情,当然这个是一个,呃,就是说接口啊,那么他给你提供了一个。基本的对吧,接口实现对,就是说calculator visitor的接口实现对吧,当然这个实现呢,它其实什么都没做,对不对,比如说我们访问负值。
10:07
呃,这个抽象语法数节点的时候,他直接return visit children cx直接返回一个什么呀,访问他的孩子节点的。对吧,呃,这样的一个结果对吧,所以说实际上它其实呃。没有做什么事情,也就是说这里边的逻辑呢,是需要我们自己去填充的,如果你想实现一个计算器的话,所以呢,我们这里边我们怎么去实现呢?呃,那么在这里边的话呢,我们就会选择来实现一个,呃,就是说计算器对吧。因为这个calculator basic visitor,它其实呃,只是给你填充了一些代码而已,它没有做任何事情对吧,他没有做任何事情,呃,所以在这的话呢,我们就来新建一个什么呢?我们就来新建一个Java类,叫做calculator,对吧,这样的一个类。
11:05
那么这个类我们要继承什么呢?我们要继承calculator basic visitor。对吧,Calculator basic visitor对不对,我们要继承这个东西啊,也就是说我们把里边的其中一部分实现,我们要自己写一下,对吧,我们要自己重写一下,这样才能实现计算器的功能啊,然后呢,我们在这的话呢,Calculator basic visitor。对吧。那么他的泛型应该是什么?对不对?它的泛型是什么意思呢?也就是说你访问抽象语法数的每一个节点,你的返回值应该是什么,对不对?我们这个我们应该呃给他确定好,当然由于计算器的这个对吧,计算器的呃结果是整形,所以呢,我们所以访问抽象语法数的每个节点的对吧,结果的返回值应该也是整形好,所以在这的话呢,我们就来一个尖括号,它的泛型是引提格。
12:25
然后呢,我们在这的话呢。我们首先要初始化一个什么呀?我们首先要初始化map数据结构对吧?Map数据结构用来保存什么呢?用来保存变量和。什么呀?变量和值的映射关系对吧?用来保存变量和值的映射关系,也就是说,比如说我这边有一个赋值语句A等于一的话呢,那么它应该保存起来的话呢,那么它的变量呢,是A对吧?那么它的值是一,也就是说我们赋值语句的话,等号左边是变量,右边是值。
13:04
那你怎么样能知道说哎,哪个变量它的值是多少呢?我们需要一个map数据结构来保存变量和值的映射关系,其实就是这个意思,那么在这的话呢,就是一个map对吧?呃,当然在这你可以直接一个V,呃,我们V可能得是局部变量对吧?Map,然后呢,它的T是string,也就是变量名value呢是引格。对吧,那就是in,我们给他起个名字叫做memory吧,哈希map,对吧?也就说我的这个key是什么呢?Key是变量名,Value呢,是变量名所对应的值对吧?好,我们现在我们有了这样一个呃,全局的一个字典,然后接下来的话呢,我们就来,因为我们的这个calculator basic visitor,它实现了访问所有抽象语法数节点类型的这样的一个访问者方法,对吧?呃,但是其实对于我们来讲,我们不需要实现所有的,我们只需要实现其中一部分,就能实现我们的这个计算器,比方说我在这里边,我第一个要实现的是什么呢?我第一个要实现的是呃,这个赋值。
14:22
对吧,或者说我要重写的呢,是这个赋值操作的,呃呃,就是说访问赋值操作的抽象语法数节点的这样的一个逻辑啊,那在这的话呢,就是override visitor visit,访问a sign。对吧,访问赛,然后这个上下文里边呢,就包含了比方说这个ign节点的它的呃,所有的孩子节点啊等等等等之类的,对吧,呃之类的好。我们现在首先取出等号左边的变量名,那么就是YID等于ctx对吧,这个上下文点ID对吧,然后点get text,那这个就是变量名我们就取出来了,然后呢,取出等号右边的值对吧?当然你这个赋值操作的话呢,它有可能是这样的一个赋值操作,比方说A等于一加上二对吧,也就是说它这个等号的右边呢,有可能它是一个表达式对吧?根据我们的这个语法定义,所以说我们。
15:32
在取出等号右边的值的时候呢,我们首先应该是对先对等号右边的表达式对吧?先对等号右边的表达式进行求值啊,那么我们求值的方法是什么呢?对不对?我们来看一下Y。然后呢,Value等于visit对吧?访问右边的表达式,当然反回去就是求出结果嘛,CTX1EXPRESSION对吧?对它进行求值,然后把它取出来就可以了,这个visit呢,就是求值对吧?Visit就是求值,好啊,因为你访问它这个节点的结果呢,就是他求知结果嘛,然后。
16:19
放入,当然我们这个是一个打引号的内存啊,也就是说把这个把变量和值的映射关系放入内存中,对不对?那么怎么放呢?memory.put ID value对吧,那也就我们保存在这个KV建筑队里边了,保存在这个。呃,就是说map里边对吧,我们visit sign它的返回值呢,要求是引提,我们这里边的话呢,我们就呃将这个value,对吧,也就是求值结果呢,给它返回就可以了,不过一般来讲的话呢,其实赋值操作呃,你也可以返回一个零对吧,这也是没有任何问题的对吧,这也是没有任何问题的,因为本身赋值操作呃它的它实际上没有求职结果这么一说的对吧?好,那么第二个的话呢,我们要重写的是expression new line这样的一个规则对吧,也就是说你表达式,然后直接换行。
17:20
那么,我们要做的应该是什么呢?应该是打印出这个表达式的值来对吧?所以我们在这里面我们override visit print expression对吧?那在这的话呢,那就很简单了,首先先对表达式进行求值对吧,Value,那么我们求值的是怎么进行求值呢?那就是访问这个表达式Ctx.expression对吧?然后呢,我们再把这个求值结果打印出来,呃,然后呢,返回值是什么呢?我们来把求值结果再给他返回对吧?所以你可以看到这个还是相对来说比较简单的,OK。
18:02
呃,那么第三个我们要重写的是什么呢?第三个我们要重写的就是。对不对,你这个比如说int,然后。New life对吧,因为int它也是个表达式呀,对吧,那你访问int的时候,呃,应该怎么做呢?对不对?访问int的时候当然就呃就更简单了,对吧,我们这里边我们直接return一个integer pass int对吧,也就是说将它这个字面量字符串。给它pass成int,然后返回就行了,对吧,也就是当你访问到这个int这个节点的时候呢,我们直接把呃,因为它在词法分析转成标记流的时候呢,实际上这个那个一呢,它是变成字符串了,对吧?所以我们在这的话,我们获取它字符串,然后给它转成整形就可以了,好。那么接下来的话呢,我们完成了几个比较简单的东西以后呢,呃,当然我们再来写一个比较简单的对吧?那就是比如说我这个左括号和右括号括起来的这个表达式,那么它是visit parents。
19:13
那么它的返回值应该是什么呢?就或者说我们访问到这个节点的时候应该怎么做呢?对吧?当然这个其实就更简单了,也就是说我们直接把这个括号脱去,然后直接返回括号里边的表达式,求出结果就可以了,对吧?所以说在这的话就是直接返回。括号中的表达式的求值结果不就OK了吗?对吧?不就可以了吗?所以说这个也是非常简单的好,那么接下来的话呢,我们就来把这个加减和乘除的节点呃来给大家写一下,对吧?比方说我们第一个是expression。Op等于乘除对吧?Expression好,然后呢,也就是说我们这个乘除操作呢,那就是override visit。
20:16
Mo dive,对吧?好,那么当我们访问这个乘除的抽象语法数节点的时候,我们应该怎么做呢?对不对?那就是先对运算符左侧的表达式进行求值,对吧?那就是往left等于visit ctx点。EXPRESS0对吧?那这个就是它的第零个表达式,就是它,呃,就是说运算符左侧的表达式在对运算符右侧的表达式进行求值对吧?Y right等于visit CT x.EXPRESSION1,好,然后呢,当然因为我们这个乘数表达式,它有可能是乘号,有可能是除号,所以说我们需要判断一下呃,它这个运算符的类型对吧?那就是说如果ctx,也就是我们上下文里面的运算符的类型。
21:23
对不对,它等于什么呢?等于calculator pass点乘号对吧,也就是说它这个运算符的类型如果是一个乘号的话。对吧?那我们就直接return left乘以right对吧?Else,那如果是除号的话,那就是left除以right。对吧,没有任何问题,好,当然加减法的这个表达式呢,它的实现方式也是一样的,Expression op等于expression对吧?然后在这的话呢,是一个加减,好,Visit at sub,诶,这个加减法的话呢,其实可以仿照上面这个乘除的呃实现对吧,他们基本上是完全一样的,那就是y left等于visit CTx.EXPRESSION0对吧,然后呢,Y right等于visit ctx expression1对吧,然后呢,如果运算符的类型。
22:37
是什么呢?运算符的类型是ADD,那我们就return一个什么呢?Left加right对吧?Else return一个left减去啊。那这样的话呢,我们整个计算机就实现完了,整个计算就实现完了,呃,在这的话,你要注意呢,并不是所有方法我们都进行了重写,比方说visit program对吧,我们就没有重写,呃,比方说visit blank,对吧,访问空白服,当然你没有任何呃求职结果了对吧?呃,包括说像什么呃visit ID其实我们也没有实现对吧,实际上你这里边呃你也可以把这个visit ID呢,也可以给他实现一下对吧?呃,比如说在这里边的话呢,我们就是一个。
23:25
我们要实现的是假设这里边是一个ID,然后呢,后面是一个,呃,也就是说当我们输入一个变量名,然后回车的时候,我们应该干什么呢?诶。那当然应该做的是对吧,你这个变量名,因为我们回车的话,我们是要返回这个变量名的求值结果嘛,而这个变量名求值结果是什么呢?就是它对应的值,而它对应的值是存储在这个map里边的,所以说在这的话呢,我们就对不对,首先获取它的ID等于ctx点。
24:02
呃。id.get text,然后呢,如果memory对吧,它包含了什么呢?它包含了这个ID。我们直接return memory.get ID对吧?我们直接返回它对应的值,那如果没有呢,我们就直接返回零对吧?当然你返回那也是可以的,对不对?呃,你返回那也是没有任何问题的好。啊,那这个是对ID的一个求值好。然后我们写一个main函数来对它进行测试,对吧?我们写一个main函数来对它进行测试。首先我们在这的话呢,我们给一个string。那这个string是什么呢?那么这个string就是比如说第一个是一个一加二对吧,第二个是A等于一,第三个是B等于一。那B等于二,第四个是A加B对吧?哎,我们一个多行字符串啊,因为这都有一些换行符嘛,那么一加二求值结果应该是3A等于一求值结果我们看一下这个负值,呃,运算符。
25:12
对吧。他的求职结果,实际上这个value,但是并不会把它打印出来,对吧,并不会把它打印出来,因为我们的打印操作呢,只在这里。呃,我们打印操作只在这个print expression,就是你输入回车的时候对吧,好。A等于一呢,就把它保存在memory那个map里边,B等于二也把它保存在memory map里边,所以说A加B呢,对不对?先对A进行求值,从memory里面取出来它是一,再对B进行求值,从memory里面它取出来是二,那么一加二等于三对吧,所以说它应该输出两个三,好,我们现在的话,我们首先。啊。将字符串转换成流对吧?Y stream等于什么呢?等于。
26:03
Char streams对吧,这个是RR里面的API,然后from strings,好,然后呢,接下来。呃,这个实例化磁法分析器对吧?瓦,Lacker等于有一个calculator lecture,你可以看到这个是它自动帮我们生成的对吧,自动帮我们生成的这样的代码,然后呢,词法分析对吧?词法分析然后在这的话挖tokens,它会转成一个标记流或者说标记数组,对吧,你有一个common。我们把词法分析给它传进去,然后呢,在这里面的话呢,你其实可以把这个token打出来,看一下它词法分析的结果,对吧?然后呢,我们接下来的话呢,语法分析,这个语法分析的话呢,就是把我们的标记流呢,给它转成一个一颗抽象语法数,所以说我们在语法分析之前的首相应该是实例化语法分析器。
27:12
对吧,Y passer等于new一个calculator passer OK,然后呢,把这个标记流的给它传进去,对吧,然后呢。语法分析并转成抽象语法数,那就是y tree等于pass program。对吧,我们为什么这返回的是呃赋值的是pass掉program呢?因为我们来看一下这个语法,它是从program开始的,所以我们在这的话呢,我们就pass掉program,那这样的话呢,就会返回它的抽象语法数,对吧,然后。我们做什么呢?实例化一个计算器,对吧?哇,Calculator等于你有一个calculator,然后呢,Calcul.visit直接访问这个数就行了,对吧?只不过它是从根节点开始访问,然后访问到哪个节点呢,它就执行哪一个节点的逻辑,对吧?我们现在我们来执行一下,看一下能不能得出正确的结果。
28:30
哎,你可以看到它结果没有任何问题对吧,当然在这的话呢,呃,我这个token的这个字符流呢,呃,由于我在这里边。呃,并没有,就是说我直接print对吧,所以说它直接打出了,呃,就是说标记流的一个对象的地址对吧,标记流的对象的一个地址啊。呃,其实的话呢,那你可以说,如果你想把呃正经的打印出来呢,你可以研究一下呃它里边的语法对吧,或者说它的呃这个源码,然后呢,来把它正确的打印出来,对吧,把它正确打印出来。
29:07
啊,比方你可以在这里点点。我们可以来一个GET0吧,对,我看GET0它打印出来是什么。啊,当然这个其实并不是说特别重要啊,并不是说特别重要。啊,它GET0,它不能这么用对吧?不能这么用好我们把这个删掉吧,OK,那么到这儿的话呢,我们基本上就知道了,呃,就是说这个an lr呢,它是怎么使用的,对吧?那么接下来我们要做什么呢?我们接下来的工作,首先我们要定义一个简单的CQ语法,然后再对这个简单的CQ语法呢,对吧?我们根据这个简单CQ语法呢,写几行CQ,然后分析它的血缘关系,对吧?分析它的血缘关系好。
我来说两句