# 简单的数值语言及其虚拟机 > 上一节: https://bbs.125.la/forum.php?mod=viewthread&tid=14705938 > 源代码 git: http://gogs.mkyr.fun:99/myuan/elang ## 目标 本节的目标是给上一节的「语言」加上一个函数定义, 然后运行起来. 不过, 我打算在这里面玩一点花哨的, 不提供可变量, 让我们看看能走到哪一步吧. 最初函数空间中只给出 个函数: - output(x, y, z...) 输出括号里的东西 - dir() 展示当前有哪些函数定义 允许输入的例子如下: ``` # 先热一下身 f(x) = x * x output(f(15)) # 都是编程初学者的内容啦 fib(1) = 1 fib(2) = 1 fib(x) = fib(x - 1) + fib(x - 2) output(fib(20)) factorial(0) = 1 factorial(x) = x * factorial(x-1) output(factorial(15)) exp(x, 0) = 1 exp(x, y) = exp(x, y - 1) * x square(x) = exp(x, 2) output(exp(2, square(2, 2))) sqrt(x) = sqrt(x, x / 2, 1, 10) sqrt(x, s, y, 0) = s sqrt(x, s, y, n) = sqrt(x, (s + y) / 2, x / ((s + y) / 2), n - 1) output(sqrt(2), square(sqrt(2))) ``` ## 新增的词法分析部分 本节引入了不可变变量和函数声明, 那个新的词法定义如下: ``` 语句 ::= 函数定义 | 表达式 # 新增了统筹的语句 函数定义 ::= 函数 "=" 表达式 # 新增了函数定义 表达式 ::= 加后表达式 (("+" | "-") 加后表达式)* 加后表达式 ::= 因子 (("*" | "/") 因子)* 因子 ::= 左括号 表达式 右括号 | 函数 | 数字 | 标识符 # 之前的语法中最简因子只能是数字, 现在可以是标识符了 函数 ::= 标识符 左括号 (表达式 (逗号 表达式)*){0,1} 右括号 ``` 没什么新东西, 学会了上一节的东西之后, 就是体力活了. 唯一需要注意的是之前假设了标识符后面一点是左括号, 否则是语法错误, 现在不是了. ## 即时解释式虚拟机 第一个实现照例先做一个最朴素的, 就像第一节的四则运算器一样, 之后再用业界常用手段来做. 这样的一个虚拟机实现起来非常简单, 只需要记录当前已定义的函数, 就是全部的运行环境了, 函数调用时先看参数里有没有指定的函数, 然后找到就用, 找不到就再往上看, 都找不到就报错. 上面的语法里, 虽然有函数参数, 但是函数参数是不可变的, 也是无类型的, 因此这样的代码也并没有超出框架 ``` add_one(x) = x + 1 g(f, x) = f(f(x)) add_two(x) = g(add_one, x) output(add_two(1)) ``` ## 鸣谢 感谢 e2txt 工具, 使得我可以按纯文本保存代码, 添入版本控制 ## 其他闲聊 目前仍然没有合适的语法错误提示, 错了程序就挂掉, 以及保持了一个编译器界经典的操作, 内存只申请不释放, 等程序跑完一下子丢给操作系统处理, 很多编译原理书里都是这样做的! 上节写的「在JavaScript里实现的同样功能的解析器和虚拟机」先叉掉 上面的语言非常函数式, 以及同样有对应的几乎一比一等价的 C++ 模板表示, 当然现在有更简化的 constexpr 了, 这种奇技淫巧能用的地方减少了很多, 以前用起来模板, 起手一个递归式2333. ## 下节预告 - 基于栈或者基于寄存器的虚拟机 - 虚拟栈或者虚拟寄存器的监视器 ### 可以从本节到达的其他方向 - 将 AST 编译到 LLVM/CLI IR/WASM/binaryen IR, 不清楚未来会选哪个 - 这一节我已经大量地在语法树上乱窜了, 并且在output时从树节点重新拼装了文本, 事实上这就是一个转换器了, 想想JavaScript混淆或者格式化, 都是基于这个原理完成的