自制脚本语言[附.1] 字节码列表
目录
注意
本文最后更新于 2023-07-30,文中内容可能已过时。
本文为想法草稿,并没有真实实践过。
本文描述的是一个基于栈的字节码指令列表。
前置内容
- 栈
- 指令基本上都是基于一个栈的。
- 栈中的每一个元素叫栈帧。
- 压栈/入栈: 在栈顶处放入元素。
- 出栈: 把栈顶的元素移出。
- 刚开始执行的时候,栈应该是空的。
- 局部变量表
- 用于存放变量值的一个数组(或者其他形式的数据结构。)
- 刚开始执行的时候, 局部变量表应该也是空的。
- 字节码列表
- 使用数组或者类似结构来保存字节码。
- 每个字节码的位置在成功编译后是不会产生变化的。
- 刚开始执行的时候, 字节码列表是已经填充完毕的, 且执行索引应该是0 。
- 一些指令会存在参数。 好像这叫基于寄存器的字节码❓
本篇使用的部分指令为变量名代指, 实际上应该使用的是一个索引位置。
-
比如
seti a 0
应该为seti 0 0
a的位置实际上应该填写的是0(即一个索引)。 -
[varName/index]
参数表示这里应该是一个索引, 即index. 但是在伪代码的地方可能会给予一个变量名。 -
[label/index]
大致同上,index
表示一个指令序号。 -
[varName/localIndex]
大致和[varName/index]
一样, 只是这里的localIndex
表示一个局部变量表的索引。 -
[index/value]
表示这里应该是一个索引,以防止歧义。 但是在编写伪代码的时候,笔者可能会直接给予一个值。
常规指令
cjmp [label/index]
- 有条件的跳转指令 全写应该是:
condition jump
- 此指令存在一个参数, 表示要跳到的指令序号。
- 由于序号在本文中难以确定, 且可能会变化, 所以使用标签来代指。
- 这个指令首先获取栈顶元素, 并将该元素出栈
- 之后判断栈顶元素的值, 如果值是
false
的话, 就跳转, 否则此指令无任何效果
- 有条件的跳转指令 全写应该是:
jmp [label/index]
- 无条件的跳转指令
- 此指令存在一个参数, 表示要跳到的指令序号。
- 由于序号在本文中难以确定, 且可能会变化, 所以使用标签来代指。
- 直接跳转到指定的行数
set [varName/localIndex] [value]
- 设置一个局部变量表的元素的值
- 此指令会在计算的时候重新检测类型
get [varName/localIndex]
- 将一个局部变量表里面的值获取, 然后压栈。
dec [varName/localIndex]
- 此指令应该是不需要的。
- 声明一个变量 , 此指令的作用应该类似于
set [varName/localIndex] null
即将某个栈帧的值初始化成一个null值。
label:
- 使用冒号结尾的当做一个标签, 去掉冒号的部分即为标签的名字。
call [funcName]
- 调用一个函数
- 此指令会创建一个新的栈, 压入当前的栈顶,然后切换到新的栈。
- 按照参数顺序将参数入栈。 此时的栈已经是新的栈了。
- 此指令后面必需存在一个
execute
指令。
execute
- 执行一个函数的具体内容, 此指令必需跟在
call
后面。 - 创建新的局部变量表, 并将栈中的全部元素,按照顺序复制到局部变量表中。
- 直接复制的话, 会出现先从局部变量表中复制到栈上, 然后从栈上再复制到新的局部变量表中的事情。 这样做的好处应该是实现起来稍微简单一些。
- 不过应该会存在一些性能上的浪费, 如果想避免性能问题的话, 可以考虑将元素留在栈中,不进行复制。 如果这样的话,
[localIndex]
参数就需要能够区分是指的栈,还是局部变量表。 一般的做法可能是使用正负数。
- 清空当前栈中的全部元素。
- 切换到函数自身的字节码指令中。 从0开始执行。
- 执行一个函数的具体内容, 此指令必需跟在
return
- 当函数的全部字节码执行完毕 或者碰到
return
关键字的时候,应该放入此指令。 - 切换到之前到局部变量表。
- 将当前栈中的剩余元素视为返回值,进行暂存。 一般单返回值的情况下就只有一个元素。
- 切换到之前的栈中, 并将栈顶元素出栈。 即删除函数使用的那个栈。
- 将暂存的返回值按照顺序压栈。 这样做看起来多进行了1次复制, 可以考虑优化。
- 切换到之前的字节码列表中, 并将对应的索引恢复。
- 当函数的全部字节码执行完毕 或者碰到
逻辑判断指令
lt/lte [varName/localIndex] [index/value]
- 检测localIndex 的值是否小于/小于等于 后面所指示的值。
- 并将
true/false
的结果压栈
gt/gte [varName/localIndex] [index/value]
- 检测localIndex 的值是否大于/大于等于 后面所指示的值。
- 并将
true/false
的结果压栈
eq [varName/localIndex] [index/value]
- 检测localIndex 的值是否等于 后面所指示的值。
- 并将
true/false
的结果压栈
reverse
- 将栈顶的布尔值出栈, 反转, 然后再压栈。
类型限定指令
使用类型限定指令应该可以增加效率。
比如 seti,setf
这个i结尾就表示整形数字,f结尾就表示浮点型数字。 在解释指令的时候不进行类型判断,直接转换成目标类型。
seti [varName/localIndex] [intValue]
- 设置一个变量的值为一个整形数字
setf [varName/localIndex] [doubleValue]
- 设置一个变量的值为一个浮点型数字