1 Parsing token
scanner 不会report错误,但是会返回一个error token.
每次只scan一个token scan on need。与jlox 不同,之前是需要token都scan完,然后loop tokens。
这个到parser处会处理。
2 parser是个全局变量
3 compile 阶段都包括啥?
scan,parse
4 加了panic mode
当发现一个error之后,不会把后面连锁的一堆error都报出来。
让他进入panic mode。之后的错误暂时不报。
再后面再设置一个同步点,到了这个点后,再把这个flag清空。
6 emitting code
这里会有个compilingChunk。这个和另一个全局关系是啥不知道?
就是临时拿进来的
7 这里还有个临时的操作。
在compile结束的时候,emitReturn.为了
“在本章中,我们的虚拟机只处理表达式。运行 clox 时,它会解析、编译并执行一个表达式,然后打印结果。为了打印这个值,我们暂时使用OP_RETURN
指令。因此,我们让编译器在代码块的末尾添加一条 OP_RETURN 指令。”
一会儿看下这个是为了什么?
// 这张主要在讨论 parser********************************************************************
8 还是先针对数字常量为例子,将source scan成token,再变成parser。最后生成bytecode
9 回顾下上面的 emitByte
这个函数很重要。byte就是要写入到chunk的byte。
line是为了再runtime出错时,找到哪里出了问题。
10 parsing token的方法
我们将每种标记类型映射为不同类型的表达式。我们为每个表达式定义一个函数,输出相应的字节码。然后,我们建立一个函数指针数组。数组中的索引与令牌
类型枚举值相对应,每个索引中的函数就是编译该令牌类型表达式的代码。
10.5 数字是这样parse的
为什么是 parser.previous??advance了两次。
所以后面都是
为什么是previous.start。这里要把字符传进去。转成数字。
10.6 我觉得目前的代码会跳过第一个字符呢?
单步运行看2*3 这个过程
运行到这里时parser了第一个数字
之后的运行 在run以前都在这里了。是个递归的parser。
11 Prefix expressions的定义(这包括,负号,”(“)
许多表达式都以一个特定的标记开始。我们称之为前缀表达式。例如,当我们解析一个表达式时,如果当前标记是(
,我们就知道我们看到的一定是一个括号分组表达式。
12 parse grouping
就后端而言,分组表达式实际上没有任何作用。它的唯一功能是语法上的--它可以让你在需要较高优先级的地方插入一个较低优先级的表达式。因此,它本身没有运行时语义,也就不会产生任何字节码。对expression()
的内部调用负责为括号内的表达式生成字节码。
13 pratt parser 也是递归的
14 unary这样处理 ,unary也是 previous
注意
将否定指令写在操作数的字节码之后似乎有点奇怪,因为“-”
出现在左边,但请从执行顺序的角度考虑一下:
- 我们先评估操作数,将其值留在堆栈中。
- 然后,我们弹出该值,对其进行否定,并推送结果。
15 优先级 在parser中
这个例子会出问题。
会先拿走-,然后expression会处理(a.b+c)。这样的话,就视+的优先级比单个-优先级高了。这这有问题。以下为解决方案。
写出优先级别的顺序。这个越往下越高。
然后把上面的expression改为
(这里用了PREC_UNARY是为了允许 nested UNARY 操作 比如!!flag
)
这意味着只处理 高于等于 unary优先级 的expression。
16
compiler的重要功能
这是编译器工作的一部分--按照程序在源代码中出现的顺序进行解析,并将其重新排列为执行顺序。
就是scaner + parser
17 Parsing Inflix Expression
binary operator运算符与前面的表达式不同,因为它们是inflix。对于其他表达式,我们从第一个标记开始就知道我们在解析什么。而对于二进制表达式,我们只有在解析了左边的操作数,然后偶然发现了中间的运算符标记后,才知道我们正处于binary operator的中间位置。
18 考虑等式1+2
让我们根据目前掌握的知识尝试编译它:
- 我们调用
expression()
。然后调用parsePrecedence(PREC_ASSIGNMENT)
。 - 该函数(一旦我们实现了它)会看到前导数字标记,并识别出它正在解析一个数字literal。它将控制权移交给
number()
。 number()
创建一个常量,发出OP_CONSTANT
并返回parsePrecedence()
。
接下来这么干
对parsePrecedence() 的
调用应该消耗掉整个加法表达式,因此它需要以某种方式继续运行。幸运的是,解析器就在我们需要的地方。现在我们已经编译了前导数字表达式,下一个标记是+
。这正是parsePrecedence()
所需要的标记,它可以检测到我们正处于一个 infix 表达式的中间,并意识到我们已经编译的表达式实际上是该表达式的一个操作数。
18.5 为什么这里也是parser.previous?
因为current已经是数字了
比如 1+2 这时 ,“+”已经consumed了 。看20 的解释。
至于unary 同理。
-1,这时1已经consumed的了。
19 函数指针数组 为什么是个table?
因此,这个假设的函数指针数组并不只是列出解析以给定标记开头的表达式的函数。相反,它是一个函数指针表。其中一列将前缀解析器函数与标记类型相关联。第二列则将后缀解析器函数与标记类型相关联。
20 在binary operator中,左操作数的parsing 并没有和符号一起parse。而是依赖在栈中的结构。和这个binary operator产生了结合。(并没有像jlox一样 可以在一个函数中parse。)
调用prefix解析器函数时,前导标记已被使用。inflix operator的情况就更糟糕了--整个左操作数表达式已经编译完毕,随后的infix运算符也已消耗完毕。
左操作数先被编译这一事实很好。这意味着在运行时,代码会首先被执行。运行时,它产生的值将会出现在堆栈中。而这正是 infix 运算符所需要的。
然后,我们使用binary()
来处理其余的算术运算符。该函数编译右操作数,就像unary()
编译其尾部操作数一样。最后,它会发出执行binary operator的字节码指令。
运行时,虚拟机将按顺序执行左操作数和右操作数代码,并将它们的值留在堆栈中。然后执行运算符指令。这将弹出两个值,计算运算并推送结果。
21 对于binary operator的right operand
我们需要考虑right operand的优先级。这个优先级我们调用时,parsePrecedence()中参数要比当前的operator高一级。这样就可以确保
binary()就可以搞定。
当我们解析*
表达式的右操作数时,我们只需要捕捉3
,而不是3 + 4
,因为+
的优先级比*
低。我们可以为每个二进制运算符定义一个单独的函数。每个操作数都会调用parsePrecedence()
,并为其操作数传递正确的优先级。
但这有点繁琐。每个二进制运算符右侧操作数的优先级都比其自身的优先级高一级。我们可以通过getRule()
来动态查询,很快就会讲到。通过它,我们可以调用比该操作符优先级高一级的parsePrecedence()
。
22
由于二元运算符是 left-associative. ,因此右操作数的parsePrecedence的参数
优先级要高一级。给定一系列相同的运算符,如
1 + 2 + 3 + 4
我们要这样解析
((1 + 2) + 3) + 4
因此,在解析第一个+
的右侧操作数时,我们希望使用2
,而不是其余的操作数,所以我们使用了比+
优先一级的优先级。但如果我们的运算符是右关联运算符,这就错了。假设
a = b = c = d
由于赋值是右关联运算,我们要将其解析为
a = (b = (c = d))
为此,我们需要调用与当前操作符具有相同优先级的parsePrecedence()
。
23 Pratt Parser
现在,编译器的各个部分都已就绪。我们已经为每种语法生成了一个函数:number()
、grouping()
、unary()
和binary()
。我们还需要实现parsePrecedence()
和getRule(
)
。我们还需要一个表格,在给定标记类型的情况下,让我们找到
- 编译以该类型标记为开头的前缀表达式的函数、
- 编译左操作数后跟有该类型标记的inflix的函数,以及
- 使用该标记作为运算符的 infix 表达式的优先级
24 把上面的放在一个struct中。
ParseFn是个函数
25 然后这个table是。[]= 这个左边是index
这个表格的含义不太理解。为什么又两列。
为什么-减号那行帐那个样子?
26 优先级都是这么搞的
相反,我们将查询封装在一个函数中。这样,我们就可以在定义binary()
之前向前声明getRule(
)
,然后在表格之后定义 getRule()
。我们还需要一些其他的前向声明来处理我们的语法是递归的这一事实,所以让我们先把它们都写出来吧。
27 Parsing With Precedence
先处理Prefix的函数
然后再搞infix
我需要个例子去理解这里。