|
@@ -2,6 +2,8 @@
|
|
|
|
|
|
> http://gogs.mkyr.fun:99/myuan/elang
|
|
|
|
|
|
+## 目标
|
|
|
+
|
|
|
本节的目标是实现包含一些初等函数的计算器, 允许输入的例子如下:
|
|
|
```
|
|
|
# 允许以#开始写注释, #到行尾的将被忽略
|
|
@@ -12,6 +14,8 @@
|
|
|
4 / (2^3 / 4^2) # 当然仍然还有括号可以用
|
|
|
```
|
|
|
|
|
|
+## 代码流程
|
|
|
+
|
|
|
上一节中实现功能时边读入边解析和计算, 如果按照这样的逻辑完成本节的目标, 代码将会冗长又固执, 难以阅读和修改. 本节的代码清晰地分为了两个部分:
|
|
|
|
|
|
1. 词法分析, 将文本拆成不同的词单元, 这里靠正则表达式拆
|
|
@@ -21,20 +25,107 @@
|
|
|
2.3 a * (b + c) / (f(a) + 2) 带括号的前二者
|
|
|
3. 解释执行语法树中的内容
|
|
|
|
|
|
-更详细的内容都在代码的注释里.
|
|
|
+## 词法分析
|
|
|
|
|
|
-本节代码仍然使用纯的易语言做文本分析, 但是如果你详细读了这份代码, 会发现大量的重复, 之后我将使用 JavaScript 的 [peggyjs](https://peggyjs.org/online) 库来重新做这件事, 同时, 如果你认真读了本节代码, 你也可以轻松读懂 peggyjs 的大部分东西.
|
|
|
+此处的「词」是一个泛指, 意为某些同类字的集合, 如「12334.235」是「数字和小数点」的集合是词, 「sin」是「字母」的集合是词, 「# 注释文本」也是词.
|
|
|
|
|
|
-> *不过严格来说, 正则表达式也是 DSL, 因此用了正则表达式就不算是纯易语言了.*
|
|
|
+这里定义了一些词类型
|
|
|
+```
|
|
|
+.版本 2
|
|
|
|
|
|
-本节代码使用 git 组织, 每一个里程碑我都会创建一个 commit, 你可以通过 git 回溯来分步查看我的编码过程. 如果你不会 git, 可以下载一个 github desktop, 可以直接在 GUI 上看到各个 commit 的内容.
|
|
|
+.常量 词类型枚举
|
|
|
+.常量 词类_数字, "1"
|
|
|
+.常量 词类_整数部分, "2"
|
|
|
+.常量 词类_小数部分, "3"
|
|
|
+.常量 词类_标识符, "4"
|
|
|
+.常量 词类_算符, "5"
|
|
|
+.常量 词类_括号, "6"
|
|
|
+.常量 词类_注释, "7"
|
|
|
+.常量 词类_空格, "8"
|
|
|
+.常量 词类型枚举数量, "8"
|
|
|
+
|
|
|
+```
|
|
|
+词类_整数部分和词类_小数部分是为了迁就小数匹配, 词法分析的时候是跳过这两个的.
|
|
|
+词法分析的时候逻辑很简单, 伪代码大约是:
|
|
|
+```
|
|
|
+对于每一个词类
|
|
|
+ 如果 是整数部分 或 小数部分
|
|
|
+ 到循环尾
|
|
|
+ end
|
|
|
+
|
|
|
+ 如果 正则匹配对当前剩余文本匹配成功
|
|
|
+ 记录匹配结果和类型
|
|
|
+ 文本游标往右走匹配结果那么长
|
|
|
+ 跳出循环
|
|
|
+ end
|
|
|
+end
|
|
|
+```
|
|
|
+
|
|
|
+这样一趟后就得到了这样的结果
|
|
|
|
|
|
+```
|
|
|
+> 词法分析 sin(x) + cos(pi * y) - ln(x) * 123.456 # 注释
|
|
|
+用时 0ms
|
|
|
+当前类型: 4, 内容: sin
|
|
|
+当前类型: 6, 内容: (
|
|
|
+当前类型: 4, 内容: x
|
|
|
+当前类型: 6, 内容: )
|
|
|
+当前类型: 5, 内容: +
|
|
|
+当前类型: 4, 内容: cos
|
|
|
+当前类型: 6, 内容: (
|
|
|
+当前类型: 4, 内容: pi
|
|
|
+当前类型: 5, 内容: *
|
|
|
+当前类型: 4, 内容: y
|
|
|
+当前类型: 6, 内容: )
|
|
|
+当前类型: 5, 内容: -
|
|
|
+当前类型: 4, 内容: ln
|
|
|
+当前类型: 6, 内容: (
|
|
|
+当前类型: 4, 内容: x
|
|
|
+当前类型: 6, 内容: )
|
|
|
+当前类型: 5, 内容: *
|
|
|
+当前类型: 1, 内容: 123.456
|
|
|
+当前类型: 7, 内容: # 注释
|
|
|
+----
|
|
|
+>
|
|
|
+```
|
|
|
|
|
|
|
|
|
-额外阅读:
|
|
|
+## 语法树设计
|
|
|
+
|
|
|
+语法树通常没有定型, 需因地制宜设计. 数学表达式通常可以视为一个纯的无求值顺序问题的语言. 可以做如下规约:
|
|
|
+```
|
|
|
+0 ± x -> ±(0, x)
|
|
|
+x ± 0 -> ±(x, 0)
|
|
|
+a + b -> +(a, b)
|
|
|
+12345 -> +(12345, 0)
|
|
|
+```
|
|
|
+这样的话所有的计算表达式都可以写作 函(函甲, 函乙, 函丙, 函丁...)
|
|
|
+> 函: 指函数
|
|
|
+
|
|
|
+那么语法树的每一个节点应当包含如下信息
|
|
|
+
|
|
|
+- 原始文本, debug用
|
|
|
+- 函数名
|
|
|
+- 参数数组, 类型为节点
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+## 其他
|
|
|
+
|
|
|
+细微之处的东西在代码的注释里.
|
|
|
+
|
|
|
+本节代码使用 git 组织, 每一个里程碑我都会创建一个 commit, 你可以通过 git 回溯来分步查看我的编码过程. 如果你不会 git, 可以下载一个 github desktop, 可以直接在 GUI 上看到各个 commit 的内容.
|
|
|
+
|
|
|
+### 额外阅读
|
|
|
- 正则表达式
|
|
|
- - https://regex101.com/
|
|
|
- - https://www.runoob.com/regexp/regexp-syntax.html
|
|
|
- 递归下降器
|
|
|
- - https://zhuanlan.zhihu.com/p/31271879
|
|
|
-- - https://zh.wikipedia.org/wiki/%E9%80%92%E5%BD%92%E4%B8%8B%E9%99%8D%E8%A7%A3%E6%9E%90%E5%99%A8
|
|
|
+- - https://zh.wikipedia.org/wiki/%E9%80%92%E5%BD%92%E4%B8%8B%E9%99%8D%E8%A7%A3%E6%9E%90%E5%99%A8
|
|
|
+
|
|
|
+## 下节预告
|
|
|
+
|
|
|
+本节代码仍然使用纯的易语言做文本分析, 但是如果你详细读了这份代码, 会发现大量的重复, 之后我将使用 JavaScript 的 [peggyjs](https://peggyjs.org/online) 库来重新做这件事, 同时, 如果你认真读了本节代码, 你也可以轻松读懂 peggyjs 的大部分东西.
|
|
|
+
|
|
|
+> *不过严格来说, 正则表达式也是 DSL, 因此用了正则表达式就不算是纯易语言了.*
|