parse整个program
终结符要么是var_decl中的enum、type
要么是func_decl中的type
所以看上图代码
如果是enum的话 它可能有名字的
比如enum myenum{"A","B","C"}
如果没有名字 则接着就是{了
然后使用parse_enum解析A,B,C这些内容
如果不是枚举 那么就是类型开头的 要么是Int要么是Char
要么是指针类型 比如Int* 或 Int**(指针的指针)
这里的代码是先解析最基础的Int和char
先解析获得基础类型
如果token没有遇到;或}
就持续的解析
如果是变量的话 它的终结是;
如果是函数的话 它的终结是}
接着看代码
如果token是*号的话
就把类型转换为指针
如果后面还是* 即有很多个*
因为有可能是指向指针的指针
每多一个指针 就把这个类型多加一个指针的标识符PTR
有多少个PTR就有多少个指针
这样的话 类型就解析完了 可能是int、char、enum指针、指向指针的指针
接着检查token是否是一个identifier
且全局定义 不能重名
所以检查token是一个新的identifier
此时知道了它的名字 但还不知道是个变量还是个函数
如果接下来是一个{ 左括号的话
那么就解析函数
符号表中的class就可以明确定义为Fun
函数的地址就是目前code区的地址+1
然后把左括号处理掉 然后解析参数 然后处理右括号 然后左边大括号
然后就是函数体了
否则就解析这个变量
符号表中的class是全局变量类型
全局变量的value是data区的地址
局部变量的value是在栈的相对地址
地址赋了值之后 得往后移动一位
假设是64的空间 地址移动每次都会加8 即8个byte共64个bit
如果定义中有多个变量的话 比如int * a,b,c
用逗号处理掉
a是一个指向int的指针
b是一个变量
c也是int变量
通过逗号的方式 一层一层去处理
以上就是解析整个代码题program的过程
接着看下解析函数的分支
代码中有一个ibp 就是bp基栈的位置
通过ibp这个全局变量来保存它
因为在解析fun的过程中 会相应的生成vm指令
vm指令是根据栈的目前的位置来生成的
ibp就是目前这个函数base point基栈的地址
去解析0个或多个局部变量[local_var_decl]
然后解析代码体 {statement}
局部变量必须在函数的上面 因为是one pass过程
while循环里面是解析base类型 int|char
里面是处理局部变量
如果有同名的全局变量的话 需要hide下
class是局部变量
value是相对于bp的位置
bp的位置是ibp 所以value的值是ibp++
第一个就是bp+1
第二个就是bp+2
解析完了局部变量之后 就需要生成相应的代码了
需要为局部变量new stack frame
i-ibp 就表示有多少个局部变量
相当于是while循环执行的次数
代码区中就会有一个NVAR 2
在遇到}之前 就调用parse_stat函数不断的解析语句
解析完所有语句之后 进行return
但有可能语句本身是一个return语句
那么在语句里面就解析完了
如果这个函数返回是void
没有return语句 需要补充一个return语句
最后把前面遮蔽的全局变量恢复
解析语句的代码
语句一共有6种可能:
if语句、while语句、return语句、{语句、一个分号;的空语句、纯粹的表达式只有个分号(比如a=3这是一个表达式只是不返回任何东西 所以就认为a=3是终结的语句)
表达式求值与优先级爬上
- • 语句(statement)
if else;while,return
语句是告诉计算机到底做什么事情
不返回值
- • 表达式(expression)
2+3,4*5
表达式的目的是求值 有返回值
有一种情况很难说是语句还是表达式
a=3 把3赋值给a 即赋值语句
c语言语法: if (a=3;!=0){ xxx }else{ xxx }
这个时候发现a=3其实是返回值了
返回的值是a变量本身的值
3!=0满足条件 会执行else中的代码
a=3这个赋值语句其实是一个赋值表达式 有返回值即a本身
操作符优先级
3+4*3-5
在编译器做表达式求值的时候需要知道优先级以及通过优先级来实现求值
操作符分为3种
- • 一元(优先级最高)
1 ++(前置++) --
2 + -
3 ~ !
4 & *
5 ()
前4个优先级是一样的 第5个优先级最高
例如 *++a
对于一元的操作符来说 哪个最贴近符合所修饰的变量
就会先处理它即++先处理 再处理*号
即一元操作符按出现顺序的逆顺序来的
第2个 + - 不是二元中的+ - 而是正负号的意思
比如-++a 先对a进行++ 然后取负号
- • 二元(优先级次之)
1 = 优先级最低
2 ?: || (逻辑运算)
3 | & ^ (位运算)
4 == > < >= <= << >>
5 + -
6 * / %
7 [] 求值 比如a[3] 优先级最高
这7种情况 优先级依次递增
比如赋值表达式(优先级最低)的求值过程
-*&++a++ = xxxxx(比如这个是很复杂的表达式)
先求前面的表达式-*&++a++ 它是一个地址空间
= 赋值符号后面是表达式会求出一个值
地址空间的类型和值的类型需一样
整个表达式的最终结果就是这个地址空间中的值
- • 三元
?:条件操作符 类似简写的if else
优先级爬山算法
3+3*4-5
在解析表达式的时候会把遇到的数据和操作符缓存下来
直到遇到的操作符比缓存下来的操作符优先级更低
比如已经缓存了操作符+ *和数据3、3、4
遇到一个-(减号) 这个优先级比*号低
就把目前缓存中的操作符比目前-号更高的即*
拿出来处理掉即 4和3的操作符是*乘号 结果是12
12这个数据又被放入栈中
此时栈中的数据是3和12
符号是+
-和+号的优先级在同一个层级
但-号比+号的优先级更高
所以把-号放入缓存 此时操作符就有了+-
然后把5放入数据栈
此时表达式结束了 就把操作符做出栈处理
12和5 和 - 操作符 出栈 相减是7
此时数据是7和3 操作符是+
再把7、3数出栈 操作符+出栈
最后的结果10在放入栈中
再举一个复杂的例子
++a++=i==0?2+3:45
看优先级爬上算法怎么处理这个表达式
第一个操作符是一元操作符根据顺序的反方向处理
先把*放入操作符栈中
++也是一元操作符也入栈了
第一个num是a变量 放入数据栈中
此时数据栈中是a ,操作符栈中是*和++
假设这个a变量是int *a
a指针
假设i==0
a后面的操作符是++
比缓存即操作符栈中的操作符优先级更低
所以在这操作符入栈之前需要把操作符栈中的优先级更高的
操作符出栈 即操作符栈中的++出栈
数据栈中只有一个a 对于一元操作符来说只需要从数据栈中出一个
所以先处理++a
假设a在内存中的位置是230230
假设地址是8个bytes 64 bit的地址空间
a+1其实就是加8 变成了230238
a的位置从230230变成了230238
此时数据栈中是新地址的a、符号栈中是*
后置++ 比*的优先级还要低
然后把*拿出来(pop)
对a的地址求值
此是操作符栈中就空了
数据栈中就是a这个地址代表的内存空间
然后后置++进入操作符栈
然后遇到赋值操作符=
比操作符栈中的++优先级低
先把操作符栈中已有的优先级更高的操作符出栈
然后对内存地址230231中的值由0变成1
所以此时数据栈是*a这个地址中的值变成了1
然后赋值操作符入栈 此时操作符栈中就只有一个赋值操作符
然后下个是数据i 放入数据栈
然后是相等操作符==
它的优先级比赋值=更高 可以放入操作符栈
接下来数据0放入数据栈
然后是?: 三元操作符
它的优先级比=更低
不能直接入操作符栈
需要先把0和i数据出栈
==操作符出栈 比较
此时结果是true 入数据栈
三元操作符入栈
那此时数据栈中还剩a和true
操作符栈中还剩=和?:
此时true和?:出栈
那么结果是2+3表达式
将2 入数据栈
+优先级比=高 入栈
3入数据栈
此时整个表达式就结束了
把2、3和+出栈计算
将5入栈
此时数据栈还剩5和a
操作符栈是=
把a地址中的值由1变成5
此时数据和操作的缓存栈都是空的
整个表达式求解完了
接下来看代码实际怎么实现的这个过程