开启辅助访问 切换到宽版

精易论坛

 找回密码
 注册

QQ登录

只需一步,快速开始

用微信号发送消息登录论坛

新人指南 邀请好友注册 - 我关注人的新帖 教你赚取精币 - 每日签到


求职/招聘- 论坛接单- 开发者大厅

论坛版规 总版规 - 建议/投诉 - 应聘版主 - 精华帖总集 积分说明 - 禁言标准 - 有奖举报

查看: 4419|回复: 15
收起左侧

[2021开源大赛(第六届)] [用易语言做一门语言. 3] 数值语言及其虚拟机

[复制链接]
发表于 2021-11-13 19:33:25 | 显示全部楼层 |阅读模式   上海市上海市

数值语言及其虚拟机

上一节: https://bbs.125.la/forum.php?mod=viewthread&tid=14705938
源代码 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, 已经算是一个勉强可用的语言了, 而且就重载这一块儿, 应该比大部分现存常用语言用起来都舒服.

目标

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

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

  • 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)))

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} 右括号

没什么新东西, 学会了上一节的东西之后, 就是体力活了. 唯一需要注意的是之前假设了标识符后面一点是左括号, 否则是语法错误, 现在不是了.

即时解释式虚拟机

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

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

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

最复杂的部分大概是匹配函数调用, 调用时候要进行重载决议(从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"
.常量 语类_函数原型, "4"
.常量 语类_形式参数, "8"
.常量 语类_实际参数, "16"
.常量 语类_根, "32"
.常量 语类_nop, "64"

由于一个节点可能同时是一个参数, 也是一个表达式, 因此使用这种位技巧同时保存多个类型, 这个技巧在Windows编程里常见.

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

鸣谢

感谢 e2txt 工具, 使得我可以按纯文本保存代码, 添入版本控制

其他闲聊

仔细阅读代码会发现, 分成「语法分析」「词法分析」两个部分是卓有成效的, 在虚拟机这一层, 更多依赖语法分析结果, 而不关心词到底是什么, 在语法分析这一层, 更关注词的类型是什么, 而不关注每一个字是什么.

目前仍然没有合适的语法错误提示, 错了程序就挂掉, 以及保持了一个编译器界经典的操作, 内存只申请不释放, 等程序跑完一下子丢给操作系统处理, 很多编译原理书里都是这样做的!

上节写的「在JavaScript里实现的同样功能的解析器和虚拟机」先叉掉

上面的语言非常函数式, 以及同样有对应的几乎一比一等价的 C++ 模板表示, 当然现在有更简化的 constexpr 了, 这种奇技淫巧能用的地方减少了很多, 以前用起来模板, 起手一个递归式2333.

下节预告

  • 基于栈或者基于寄存器的虚拟机
  • 虚拟栈或者虚拟寄存器的监视器
  • 将 AST 编译到 LLVM/CLI IR/WASM/binaryen IR

以上三者, 我可能会根据兴趣和实现难度挑一个做.

  • 语言测试框架

必做, 以便控制在移植到其他平台时的表现.

可以从本节到达的其他方向

这一节我已经大量地在语法树上乱窜了, 事实上也可以从语法树节点重新拼装文本, 展示成文本代码. 事实上这就是一个转译器了, 想想 JavaScript 混淆或者格式化, 都是基于这个原理完成的

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

64.88 KB, 下载次数: 44, 下载积分: 精币 -2 枚

截图.png

评分

参与人数 6好评 +5 精币 +13 收起 理由
ghost12 + 1 支持开源~!感谢分享
易语言资源网 + 1 + 5 支持开源~!感谢分享
亡胧 + 1 + 2 大神,请收下我的膝盖
zainex + 1 支持开源~!感谢分享
已注销541904 + 1 + 2 感谢发布原创作品,一定好好学习,天天向上
冰点 + 1 + 3 感谢分享,很给力!~

查看全部评分

 楼主| 发表于 2022-6-11 19:55:22 | 显示全部楼层   上海市上海市
陈雷 发表于 2022-5-29 09:48
0+(160-0)*((8-4)/(20-4))^0.5    这种计算不支持

这篇里的代码没有处理「^」, 但是你可以把「^0.5」换成「sqrt」
回复 支持 反对

使用道具 举报

结帖率:0% (0/4)
发表于 2022-5-29 09:48:05 | 显示全部楼层   江苏省盐城市
0+(160-0)*((8-4)/(20-4))^0.5    这种计算不支持
回复 支持 反对

使用道具 举报

结帖率:82% (27/33)

签到天数: 3 天

发表于 2022-5-19 20:00:28 | 显示全部楼层   上海市上海市
这么厉害!必须给个好评鼓励下~
回复 支持 反对

使用道具 举报

结帖率:0% (0/1)

签到天数: 12 天

发表于 2022-2-17 07:37:37 | 显示全部楼层   河南省洛阳市
谢谢分享。。。
回复 支持 反对

使用道具 举报

签到天数: 17 天

发表于 2021-12-17 14:46:42 | 显示全部楼层   陕西省榆林市
要写一个新语言真繁琐啊。
回复 支持 反对

使用道具 举报

发表于 2021-11-19 15:32:54 | 显示全部楼层   广东省广州市
66666666666666666
回复 支持 反对

使用道具 举报

结帖率:100% (1/1)
发表于 2021-11-16 15:50:31 | 显示全部楼层   四川省成都市
学习一下
学习一下
回复 支持 反对

使用道具 举报

结帖率:100% (7/7)

签到天数: 20 天

发表于 2021-11-15 08:29:44 | 显示全部楼层   广东省佛山市
        支持开源~!感谢分享
回复 支持 反对

使用道具 举报

结帖率:76% (22/29)

签到天数: 9 天

发表于 2021-11-14 14:15:36 | 显示全部楼层   广东省中山市
支持开源原创,互相分享、互相学习,共同提升,减少重复造轮子的研发精力
回复 支持 反对

使用道具 举报

结帖率:100% (7/7)

签到天数: 17 天

发表于 2021-11-14 09:07:44 | 显示全部楼层   湖北省武汉市
感谢分享,很给力!~
回复 支持 反对

使用道具 举报

您需要登录后才可以回帖 登录 | 注册

本版积分规则 致发广告者

发布主题 收藏帖子 返回列表

sitemap| 易语言源码| 易语言教程| 易语言论坛| 易语言模块| 手机版| 广告投放| 精易论坛
拒绝任何人以任何形式在本论坛发表与中华人民共和国法律相抵触的言论,本站内容均为会员发表,并不代表精易立场!
论坛帖子内容仅用于技术交流学习和研究的目的,严禁用于非法目的,否则造成一切后果自负!如帖子内容侵害到你的权益,请联系我们!
防范网络诈骗,远离网络犯罪 违法和不良信息举报电话0663-3422125,QQ: 793400750,邮箱:wp@125.la
Powered by Discuz! X3.4 揭阳市揭东区精易科技有限公司 ( 粤ICP备12094385号-1) 粤公网安备 44522102000125 增值电信业务经营许可证 粤B2-20192173

快速回复 返回顶部 返回列表