自制脚本语言[附.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]
    • 设置一个变量的值为一个浮点型数字
0%