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

txbleehom · September 08, 2021 · Last by EvanYa replied at September 22, 2021 · 1290 hits

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

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

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

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

Reply to txbleehom

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

Reply to SunA0

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

Reply to SunA0

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

Reply to Rei

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

哦哦,有空研究下😀

王垠已经说过了,不到必要的时候不要写 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),也就是人肉编译器。当我在自己的程序中,发现用到了模式,我觉得这就表明某个地方出错了。程序的形式,应该仅仅反映它所要解决的问题。代码中其他任何外加的形式,都是一个信号,(至少对我来说)表明我对问题的抽象还不够深,也经常提醒我,自己正在手工完成的事情,本应该写代码,通过宏的扩展自动实现。

Reply to rubyKC

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

Reply to rocLv

ruby2.7 开始已经去掉了

Reply to 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 吗?(逃

You need to Sign in before reply, if you don't have an account, please Sign up first.