myuan 3 роки тому
батько
коміт
b13a2923d9

+ 40 - 4
3. 简单的数值语言及其虚拟机/readme.md

@@ -64,16 +64,50 @@ output(sqrt(2), square(sqrt(2)))
 
 第一个实现照例先做一个最朴素的, 就像第一节的四则运算器一样, 之后再用业界常用手段来做. 
 
-这样的一个虚拟机实现起来非常简单, 只需要记录当前已定义的函数, 就是全部的运行环境了, 函数调用时先看参数里有没有指定的函数, 然后找到就用, 找不到就再往上看, 都找不到就报错. 上面的语法里, 虽然有函数参数, 但是函数参数是不可变的, 也是无类型的, 因此这样的代码也并没有超出框架
+这样的一个虚拟机实现起来非常简单, 只需要记录当前已定义的函数. 由于没有可变量, 不必去记录状态, 一切都是纯的. 在常规命令式语言有点麻烦的函数作为参数也很容易做, 因为根本就没区分函数和量, 如下: 
 
 ```
 add_one(x) = x + 1
 g(f, x) = f(f(x))
 add_two(x) = g(add_one, x)
 
-output(add_two(1))
+output(add_two(1)) # 2
 ```
 
+在最大的环境里, 包含了前面说过的`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字节用于语法结构类型, 当前语法类型为: 
+
+```
+.版本 2
+
+.常量 语类_未定义, "0"
+.常量 语类_定义, "1"
+.常量 语类_函数, "2"
+.常量 语类_常量, "3"
+.常量 语类_参数名, "4"
+```
+
+
+为了演示真正的在语法树上爬, 我没有额外定义什么数据结构来保存函数, 还是原来的那个抽象语法树上的函数节点. 至于函数环境, 应当用哈希表的, 但是手写一个哈希表在这个当下的易语言上有点傻, 就暴力数组查找了, 所以函数环境就是一个抽象语法树节点数组说, 没了.  
+
 ## 鸣谢
 
 感谢 e2txt 工具, 使得我可以按纯文本保存代码, 添入版本控制
@@ -89,8 +123,10 @@ output(add_two(1))
 
 - 基于栈或者基于寄存器的虚拟机
 - 虚拟栈或者虚拟寄存器的监视器
+- 将 AST 编译到 LLVM/CLI IR/WASM/binaryen IR
+
+以上三者, 我可能会根据大家的兴趣和实现难度挑一个做. 
 
 ### 可以从本节到达的其他方向
 
-- 将 AST 编译到 LLVM/CLI IR/WASM/binaryen IR, 不清楚未来会选哪个
-- 这一节我已经大量地在语法树上乱窜了, 并且在output时从树节点重新拼装了文本, 事实上这就是一个转换器了, 想想JavaScript混淆或者格式化, 都是基于这个原理完成的
+这一节我已经大量地在语法树上乱窜了, 并且在output时从树节点重新拼装了文本, 事实上这就是一个转换器了, 想想JavaScript混淆或者格式化, 都是基于这个原理完成的

BIN
3. 简单的数值语言及其虚拟机/简单的数值语言及其虚拟机.e