|
@@ -3,6 +3,57 @@
|
|
> 上一节: https://bbs.125.la/forum.php?mod=viewthread&tid=14705938
|
|
> 上一节: https://bbs.125.la/forum.php?mod=viewthread&tid=14705938
|
|
> 源代码 git: http://gogs.mkyr.fun:99/myuan/elang
|
|
> 源代码 git: http://gogs.mkyr.fun:99/myuan/elang
|
|
|
|
|
|
|
|
+## 效果演示
|
|
|
|
+
|
|
|
|
+先上效果演示, 再看怎么实现
|
|
|
|
+
|
|
|
|
+```
|
|
|
|
+>>> output(output(1 + 2 * 3 / 4)) # 首先输出`1+2*3/4`为2.5, 之后再输出`2.5`作为输出的长度为3
|
|
|
|
+2.5
|
|
|
|
+3
|
|
|
|
+1
|
|
|
|
+>>>
|
|
|
|
+>>> f(0) = 0 # 定义函数
|
|
|
|
+>>> f(x) = x + f(x - 1) # 函数可以同名, 之后会自动重载决议
|
|
|
|
+>>> f(x, y) = x + y # 多个参数也没问题
|
|
|
|
+>>>
|
|
|
|
+>>> output(f(10)) # f(10) = 10 + f(9) = 10 + 9 + f(8) = ... = 10+9+8+7+...+1+0 = 55
|
|
|
|
+55
|
|
|
|
+2
|
|
|
|
+>>> output(f(1, 2)) # f(1, 2) = 1 + 2 = 3
|
|
|
|
+3
|
|
|
|
+1
|
|
|
|
+>>>
|
|
|
|
+>>>
|
|
|
|
+>>> fib(1) = 1 # 经典斐波那契数列定义
|
|
|
|
+>>> fib(2) = 1 # 经典斐波那契数列定义
|
|
|
|
+>>> fib(x) = fib(x - 1) + fib(x - 2) # 经典斐波那契数列定义
|
|
|
|
+>>> output(fib(20)) # 不用查表了, 这个值是正确的
|
|
|
|
+6765
|
|
|
|
+4
|
|
|
|
+>>>
|
|
|
|
+>>> exp(x, 0) = 1 # 递归求幂
|
|
|
|
+>>> exp(x, y) = exp(x, y - 1) * x # 递归求幂
|
|
|
|
+>>> square(x) = exp(x, 2) # 嵌套函数调用, 定义平方函数
|
|
|
|
+>>>
|
|
|
|
+>>> output(exp(2, square(2))) # 2^(2^2)
|
|
|
|
+16
|
|
|
|
+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)))
|
|
|
|
+1.414213562373, 2
|
|
|
|
+17
|
|
|
|
+>>>
|
|
|
|
+
|
|
|
|
+```
|
|
|
|
+
|
|
|
|
+大致就是这样了, 如果内部像 aardio 一样链接到 `msvc.dll`, 已经算是一个勉强可用的语言了, 而且就重载这一块儿, 应该比大部分现存常用语言用起来都舒服.
|
|
|
|
+
|
|
## 目标
|
|
## 目标
|
|
|
|
|
|
本节的目标是给上一节的「语言」加上一个函数定义, 然后运行起来. 不过, 我打算在这里面玩一点花哨的, 不提供可变量, 让我们看看能走到哪一步吧.
|
|
本节的目标是给上一节的「语言」加上一个函数定义, 然后运行起来. 不过, 我打算在这里面玩一点花哨的, 不提供可变量, 让我们看看能走到哪一步吧.
|
|
@@ -35,7 +86,7 @@ exp(x, 0) = 1
|
|
exp(x, y) = exp(x, y - 1) * x
|
|
exp(x, y) = exp(x, y - 1) * x
|
|
square(x) = exp(x, 2)
|
|
square(x) = exp(x, 2)
|
|
|
|
|
|
-output(exp(2, square(2, 2)))
|
|
|
|
|
|
+output(exp(2, square(2)))
|
|
|
|
|
|
|
|
|
|
sqrt(x) = sqrt(x, x / 2, 1, 10)
|
|
sqrt(x) = sqrt(x, x / 2, 1, 10)
|
|
@@ -47,10 +98,10 @@ output(sqrt(2), square(sqrt(2)))
|
|
|
|
|
|
## 新增的词法分析部分
|
|
## 新增的词法分析部分
|
|
|
|
|
|
-本节引入了不可变变量和函数声明, 那个新的词法定义如下:
|
|
|
|
|
|
+本节引入了不可变变量和函数声明, 另外要加上之前忘记写的注释的词法分析. 那新的词法定义如下:
|
|
|
|
|
|
```
|
|
```
|
|
-语句 ::= 函数定义 | 表达式 # 新增了统筹的语句
|
|
|
|
|
|
+语句 ::= 注释 | 函数定义 | 表达式 # 新增了统筹的语句
|
|
函数定义 ::= 函数 "=" 表达式 # 新增了函数定义
|
|
函数定义 ::= 函数 "=" 表达式 # 新增了函数定义
|
|
表达式 ::= 加后表达式 (("+" | "-") 加后表达式)*
|
|
表达式 ::= 加后表达式 (("+" | "-") 加后表达式)*
|
|
加后表达式 ::= 因子 (("*" | "/") 因子)*
|
|
加后表达式 ::= 因子 (("*" | "/") 因子)*
|
|
@@ -64,15 +115,7 @@ 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)) # 2
|
|
|
|
-```
|
|
|
|
|
|
+这样的一个虚拟机实现起来非常简单, 只需要记录当前已定义的函数. 由于没有可变量, 不必去记录状态, 一切都是纯的.
|
|
|
|
|
|
在最大的环境里, 包含了前面说过的`output`和`dir`两个函数, 这两个函数是用易语言实现的, 默认的, 之后的其他函数都将在这门语言内部实现.
|
|
在最大的环境里, 包含了前面说过的`output`和`dir`两个函数, 这两个函数是用易语言实现的, 默认的, 之后的其他函数都将在这门语言内部实现.
|
|
|
|
|
|
@@ -100,13 +143,16 @@ f(x, y) = f(x + 1, y - 1) # 5
|
|
|
|
|
|
.常量 语类_未定义, "0"
|
|
.常量 语类_未定义, "0"
|
|
.常量 语类_定义, "1"
|
|
.常量 语类_定义, "1"
|
|
-.常量 语类_函数, "2"
|
|
|
|
-.常量 语类_常量, "3"
|
|
|
|
-.常量 语类_参数名, "4"
|
|
|
|
|
|
+.常量 语类_函数调用, "2"
|
|
|
|
+.常量 语类_函数原型, "4"
|
|
|
|
+.常量 语类_形式参数, "8"
|
|
|
|
+.常量 语类_实际参数, "16"
|
|
|
|
+.常量 语类_根, "32"
|
|
|
|
+.常量 语类_nop, "64"
|
|
```
|
|
```
|
|
|
|
+由于一个节点可能同时是一个参数, 也是一个表达式, 因此使用这种位技巧同时保存多个类型, 这个技巧在Windows编程里常见.
|
|
|
|
|
|
-
|
|
|
|
-为了演示真正的在语法树上爬, 我没有额外定义什么数据结构来保存函数, 还是原来的那个抽象语法树上的函数节点. 至于函数环境, 应当用哈希表的, 但是手写一个哈希表在这个当下的易语言上有点傻, 就暴力数组查找了, 所以函数环境就是一个抽象语法树节点数组说, 没了.
|
|
|
|
|
|
+为了演示真正的在语法树上爬, 我没有额外定义什么数据结构来保存函数, 还是原来的那个抽象语法树上的函数节点. 至于函数环境, 应当用哈希表的, 但是手写一个哈希表在这个当下的易语言上有点傻, 就暴力数组查找了, 所以函数环境就是一个抽象语法树节点数组说, 没了. 函数返回值的话, 就学一下 JavaScript 吧, 都用双精度小数偷懒, 没有整数.
|
|
|
|
|
|
## 鸣谢
|
|
## 鸣谢
|
|
|
|
|
|
@@ -114,6 +160,8 @@ f(x, y) = f(x + 1, y - 1) # 5
|
|
|
|
|
|
## 其他闲聊
|
|
## 其他闲聊
|
|
|
|
|
|
|
|
+仔细阅读代码会发现, 分成「语法分析」「词法分析」两个部分是卓有成效的, 在虚拟机这一层, 更多依赖语法分析结果, 而不关心词到底是什么, 在语法分析这一层, 更关注词的类型是什么, 而不关注每一个字是什么.
|
|
|
|
+
|
|
目前仍然没有合适的语法错误提示, 错了程序就挂掉, 以及保持了一个编译器界经典的操作, 内存只申请不释放, 等程序跑完一下子丢给操作系统处理, 很多编译原理书里都是这样做的!
|
|
目前仍然没有合适的语法错误提示, 错了程序就挂掉, 以及保持了一个编译器界经典的操作, 内存只申请不释放, 等程序跑完一下子丢给操作系统处理, 很多编译原理书里都是这样做的!
|
|
|
|
|
|
上节写的「在JavaScript里实现的同样功能的解析器和虚拟机」先叉掉
|
|
上节写的「在JavaScript里实现的同样功能的解析器和虚拟机」先叉掉
|
|
@@ -125,8 +173,12 @@ f(x, y) = f(x + 1, y - 1) # 5
|
|
- 虚拟栈或者虚拟寄存器的监视器
|
|
- 虚拟栈或者虚拟寄存器的监视器
|
|
- 将 AST 编译到 LLVM/CLI IR/WASM/binaryen IR
|
|
- 将 AST 编译到 LLVM/CLI IR/WASM/binaryen IR
|
|
|
|
|
|
-以上三者, 我可能会根据大家的兴趣和实现难度挑一个做.
|
|
|
|
|
|
+以上三者, 我可能会根据兴趣和实现难度挑一个做.
|
|
|
|
+
|
|
|
|
+- 语言测试框架
|
|
|
|
+
|
|
|
|
+必做, 以便控制在移植到其他平台时的表现.
|
|
|
|
|
|
### 可以从本节到达的其他方向
|
|
### 可以从本节到达的其他方向
|
|
|
|
|
|
-这一节我已经大量地在语法树上乱窜了, 并且在output时从树节点重新拼装了文本, 事实上这就是一个转换器了, 想想JavaScript混淆或者格式化, 都是基于这个原理完成的
|
|
|
|
|
|
+这一节我已经大量地在语法树上乱窜了, 事实上也可以从语法树节点重新拼装文本, 展示成文本代码. 事实上这就是一个转译器了, 想想 JavaScript 混淆或者格式化, 都是基于这个原理完成的
|