下你所需,载你所想!
汇集开发技术源码资料

简单的数值语言及其虚拟机

:17.713KB :1 :2021-11-15 10:53:18

部分简介

本节的目标是给上一节的「语言」加上一个函数定义, 然后运行起来. 不过, 我打算在这里面玩一点花哨的, 不提供可变量, 让我们看看能走到哪一步吧.

最初函数空间中只给出 个函数:

output(x, y, z...) 输出括号里的东西
dir() 展示当前有哪些函数定义。
新增的词法分析部分
本节引入了不可变变量和函数声明, 另外要加上之前忘记写的注释的词法分析. 那新的词法定义如下:

语句 ::= 注释 | 函数定义 | 表达式 # 新增了统筹的语句
函数定义 ::= 函数 "=" 表达式 # 新增了函数定义
表达式 ::= 加后表达式 (("+" | "-") 加后表达式)*
加后表达式 ::= 因子 (("*" | "/") 因子)*
因子 ::= 左括号 表达式 右括号 | 函数 | 数字 | 标识符 # 之前的语法中最简因子只能是数字, 现在可以是标识符了
函数 ::= 标识符 左括号 (表达式 (逗号 表达式)*){0,1} 右括号
没什么新东西, 学会了上一节的东西之后, 就是体力活了. 唯一需要注意的是之前假设了标识符后面一点是左括号, 否则是语法错误, 现在不是了.

即时解释式虚拟机
第一个实现照例先做一个最朴素的, 就像第一节的四则运算器一样, 之后再用业界常用手段来做.

这样的一个虚拟机实现起来非常简单, 只需要记录当前已定义的函数. 由于没有可变量, 不必去记录状态, 一切都是纯的.

在最大的环境里, 包含了前面说过的output和dir两个函数, 这两个函数是用易语言实现的, 默认的, 之后的其他函数都将在这门语言内部实现.

最复杂的部分大概是匹配函数调用, 调用时候要进行重载决议(从C++偷来的名词), 决议过程大致如下:

# 以如下举例

f(0) = 0 # 1
f(x) = x # 2
f(0, 0) = 0 # 3
f(x, 0) = f(x) # 4
f(x, y) = f(x + 1, y - 1) # 5
首先按函数参数数量匹配, 比如 f(2, 1) 应当只匹配到函数345, 如果匹配失败直接报错.
之后尝试按尽量多常量相容匹配, 比如 f(2, 1) 会匹配到函数5, 然后调用 f(3, 0), 这个时候就应该去匹配到公式4, 而不应该继续使用公式5, 因为公式4有一个常量相容, 公式5没有. f(3, 0) 则将转到 f(3), 然后求值到3.

数据结构
之前忘记在语法树节点上记录词类型了, 现在从函数名的40字节里分出来4个当做词类, 再分出来4字节用于语法结构类型, 当前语法类型为:

.常量 语类_未定义, "0"
.常量 语类_定义, "1"
.常量 语类_函数调用, "2"
.常量 语类_函数原型, "4"
.常量 语类_形式参数, "8"
.常量 语类_实际参数, "16"
.常量 语类_根, "32"
.常量 语类_nop, "64"
由于一个节点可能同时是一个参数, 也是一个表达式, 因此使用这种位技巧同时保存多个类型, 这个技巧在Windows编程里常见.

为了演示真正的在语法树上爬, 我没有额外定义什么数据结构来保存函数, 还是原来的那个抽象语法树上的函数节点. 至于函数环境, 应当用哈希表的, 但是手写一个哈希表在这个当下的易语言上有点傻, 就暴力数组查找了, 所以函数环境就是一个抽象语法树节点数组说, 没了. 函数返回值的话, 就学一下 JavaScript 吧, 都用双精度小数偷懒, 没有整数。

简单的数值语言及其虚拟机

热门推荐

相关文章