Ruby 有没有可能基于 Ruby 封装一个自己的语言

txbleehom · 2021年09月08日 · 最后由 EvanYa 回复于 2021年09月22日 · 1288 次阅读

项目有个需求,系统提供一些固定接口和基本的语法,然后用户可以自己写逻辑执行结果,目前我是直接用 ruby 语法写的公式,然后后台用 eval 执行结果,这样安全性肯定有问题,但是又想不出有什么好的方法可以避免这种安全问题,但又希望复用 ruby 的语法,各位有没有什么好的解决方案。 如果能达到 https://deluge.zoho.com/tryout 这个效果就更好了,这个平台定义了自己语言,提供基本的语法给用户自己去调用执行,比如写个九九乘法表效果如图:

意思是自己实现一个受限的 Ruby 解释器/编译器?

misakamikoto 回复

其实也没打算做太复杂,如果能解决 eval 安全问题也行,毕竟 eval 什么都可以执,安全隐患很大,或者说有什么沙盒模式随便怎么折腾不影响主程序,而且能接收住程序提供的特定接口

贴子里提到的“这样的效果”做的这种功能,他是怎么实现检查语法正确性的,我随便写个2=1能给我错误反馈。 我还挺好奇的。

txbleehom 回复

菜鸟教程那种好像是开一个容器或者 vm 跑的,把目标代码写到一个.rb/.js 文件...,然后用一个 shell 脚本去调用这个文件,你可以试试在菜鸟教程的实践块里敲 system('ls') 看看

SunA0 回复

这个大致看了下,好像还不错,可以研究下

SunA0 回复

他这个是有自己的一套语法规则的,即使少个分号结尾都会报错,必须按照他的语法规则来,有自己完整的文档,也算是一套比较成熟的脚本语言了,他这个语言只服务于他自己的产品,内部提供很多接口可以调用自己内部产品相关数据

Rei 回复

不错,可以研究下,现在研究的方向多了几条选择👍

PaulChan1995 回复

哦哦,有空研究下😀

王垠已经说过了,不到必要的时候不要写 dsl

首先 Ruby 提供了一个叫做$SAFE 的变量,提供了 5 个级别的运行环境:

$SAFE 描述
0 我们平常运行代码的默认值
1 不允许使用潜在威胁的操作污染的代码
2 禁止加载全局可写的文件
3 所有新创建的对象认为被污染,所以不能运行
4 沙盒模式

另外,就是 DSL。比如FactoryBot 就是一种 DSL。

再者,可以去类似于实现一门编程语言。比如tinyrb

这样做听上去好像开玩笑,但是在大型编程项目中,却不同程度地广泛存在。因此,有人把它总结出来,起名为"格林斯潘第十定律"(Greenspun's Tenth Rule):

"任何 C 或 Fortran 程序复杂到一定程度之后,都会包含一个临时开发的、只有一半功能的、不完全符合规格的、到处都是 bug 的、运行速度很慢的 Common Lisp 实现。"

如果你想解决一个困难的问题,关键不是你使用的语言是否强大,而是好几个因素同时发挥作用(a)使用一种强大的语言,(b)为这个难题写一个事实上的解释器,或者(c)你自己变成这个难题的人肉编译器。在 Python 的例子中,这样的处理方法已经开始出现了,我们实际上就是自己写代码,模拟出编译器实现局部变量的功能。

这种实践不仅很普遍,而且已经制度化了。举例来说,在面向对象编程的世界中,我们大量听到"模式"(pattern)这个词,我觉得那些"模式"就是现实中的因素(c),也就是人肉编译器。当我在自己的程序中,发现用到了模式,我觉得这就表明某个地方出错了。程序的形式,应该仅仅反映它所要解决的问题。代码中其他任何外加的形式,都是一个信号,(至少对我来说)表明我对问题的抽象还不够深,也经常提醒我,自己正在手工完成的事情,本应该写代码,通过宏的扩展自动实现。

rubyKC 回复

这段引用 这个出自哪里,想学习下

rocLv 回复

ruby2.7 开始已经去掉了

SunA0 回复

它这种只是输出了期望得到的 token 的,成熟的 lexer/parser generator 都有的吧,不涉及语义因此其实提示性的用处不太大且时常有迷惑性。加上一个错误 case 就行,然后在此处打印行号列号和期望得到的 token/value 和当前读到的 token。

由于打印期望得到的 token 可能会迷惑使用者,我在做 shell lab 实验的时候就没打印,只是打印列号和当前的错误 token 而已(加上 shell 其实没有什么期望得到的 token,管道、重定向和布尔符号都是可选的,只是做了必须要有一个 WORD 作为命令的特判而已)

如果是希望手动写 Parser,那确实需要自己处理。但实在是太累了,做 HTTP 报文解析这种用一个大 Switch 当状态机还好,还可以进行统计概率上的预测优化。但语言的 Parser 又不追求速度,用 lexer/parser generator 就可以了还严谨好维护。

当然了写 shell 只是为了学习系统调用,完全没有支持函数和控制流命令之类的,复杂度不可相提并论。不过即使是要用 lexer/parser generator 来写一个 DSL 的话我也是不太支持的,因为几乎所有的独立 DSL 都失败了,eDSL 才有鲜活的生命力。对于不可信来源的代码,既要保证图灵完备还要用即有语法和生态,那还是 Shopify/ess 叭。

另一种思路就是写一个受限环境的源代码变换器或是元循环解释器,就是先用 Ruby 写一个 Ruby 的 parser,输出 AST,把 AST 里所有能够动态生成调用的分支干掉(最显著就是 eval)或者一经发现有问题直接报错。如果写元循环解释器这个时候你拿到干净的 AST 就能在树上游走并调用 Ruby 进行解释了(不知道为啥我觉得这样更安全,可能是因为黑名单不如白名单。但考虑到 Ruby 没有好用的模式匹配,处理 Ruby 那么多 case 可能要脱层皮),如果是写源代码变换器的话就是再把 AST 转换回 Ruby,然后 eval。第一步和最后一步已经做好了,叫 ruby_parser 和 ruby2ruby。这俩 Gem 是一个人写的,所以 AST 格式一样。熟悉前端的话,这个套路约等于 JavaScript 界的 Babel。

我记得 JavaScript 界就有人实现了用 JavaScript(运行时)写的受限 JavaScript 解释器,大抵是面向 ES5 的,因为去掉了 eval Function 等等没法完全通过测试用例,但据说生产能用。可惜基本上等于是假开源,没放出来编译器的源码。

如果能做到这一点的话,恭喜你把 Lisp 的宏模型引入了 Ruby 原有的字符串宏的体系中。叫什么好呢,Elixir 吗?(逃

需要 登录 后方可回复, 如果你还没有账号请 注册新账号