25 个版本
0.8.1 | 2022 年 3 月 5 日 |
---|---|
0.8.0 | 2021 年 7 月 28 日 |
0.7.1 | 2021 年 5 月 25 日 |
0.7.0 | 2021 年 1 月 18 日 |
0.2.0 | 2019 年 7 月 18 日 |
#7 in #可嵌入
56 每月下载量
1.5MB
24K SLoC
WLambda - Rust 的可嵌入脚本语言
WLambda 是 Rust 的动态脚本语言,其中每个值都可以调用,语法是 Perl、Lua、JavaScript 和 Lisp/Scheme/Clojure 的混合。它可以作为嵌入式脚本语言或使用提供的 REPL 作为独立使用。
以下是它的一些特性
- 语法简单但独特。参考WLambda 语言参考。
- 由于 API 简单,因此易于嵌入 Rust 程序的脚本语言。
- 该语言的重点是快速完成任务,因此性能不是主要考虑因素。当前性能大致与 (C)Python 或 Perl 相当,这意味着在速度是重点的情况下,该语言可能太慢,但如果在 Rust 中进行任何重型操作,则足够快。
- 主要数据结构是 Vectors 和 Maps。
- 内建的数据结构模式匹配器和选择器,这导致了非常强大的
match
操作。 - 没有垃圾收集器。内存和资源管理仅依赖于引用计数和 RAII。您可以创建自己的析构函数。
- 通过不使用
unsafe
来保留 Rust 的安全性。 - WLambda 不保证在执行错误代码时不会崩溃或崩溃您的应用程序。需要更多的加固才能在应用侧运行不可信代码(资源限制(ram/cpu)、捕获 panic unwinding、限制文件系统访问等)。
- 没有异常,除了 WLambda 级别的 panic。错误处理通过专用数据类型完成。它可以被视为 Rust 的 Result 类型的动态对应物。
- 原型对象导向。
- 实现易于维护和可修改。
- 使用 VValUserData 实现自定义用户数据。
- 支持线程,具有共享原子和消息队列。
- 基于寄存器的 VM 解释器和代码生成器。
- 内建的模式匹配和结构选择器 模式选择语法。
- 有一个可测试的 wasm32 版本: WASM WLambda 解释器。
嵌入 API 和所有内部操作都依赖于由 VVal 节点组成的数据结构。
您可以在此找到 WLambda 语言参考。
编译WLambda
如果您想编译具有所有功能的WLambda,您需要运行
cargo build --features mqtt,http
或者只需
cargo build --features all
API Hello World
use wlambda::*;
match wlambda::eval("40 + 2") {
Ok(v) => { println!("Output: {}", v.s()); },
Err(e) => { eprintln!("Error: {}", e); },
}
更多API使用示例请见下方!
WLambda语言指南
现在就可以在WASM WLambda评估器中尝试WLambda。
变量
!x = 10; # Variable definition
.x = 20; # Variable assignment
运算符
!x = (1 + 2) * (8 - 4) / 2;
std:assert_eq x 6;
如果
if $true {
std:displayln "It's true!";
} {
std:displayln "It's false!";
};
!x = 10 / 2;
if x == 5 {
std:displayln "x == 5";
};
当...
!x = 10;
while x > 0 {
std:displayln x;
(x == 5) {
break[];
};
.x = x - 1;
};
!x = 10;
while x > 0 {
std:displayln x;
if x == 5 {
# break is a function, first arg
# is the return value for `while`:
break[];
};
.x = x - 1;
};
std:assert_eq x 5;
计数循环
!sum = 0;
iter i 0 => 10 {
.sum = sum + i;
};
std:assert_eq sum 1 + 2 + 3 + 4 + 5 + 6 + 7 + 8 + 9;
无限循环
!x = 10;
while $true {
std:displayln x;
.x = x - 1;
if x == 0 break[];
};
函数
!add = { _ + _1 }; # argument names _, _1, _2, ...
!result = add 2 3;
std:assert_eq result 5;
不同的函数调用语法
!add = {!(x, y) = @; # named variables, @ evals to list of all args
x + y
};
std:displayln[add[2, 3]]; # [] parenthesis calling syntax
std:displayln add[2, 3]; # less parenthesis
std:displayln (add 2 3); # explicit expression delimiting with `( ... )`
std:displayln ~ add 2 3; # `~` means: evaluate rest as one expression
!add5 = { _ + 5 };
std:displayln 3 &> add5; # '&>' is an argument pipe operator
std:displayln add5 <& 3; # '<&' is the reverse argument pipe operator
从嵌套函数返回
!test = \:ret_label_a {!(x) = @;
# an `if` is actually a call to another function, so we need to
# dynamically jump upwards the call stack to the given label:
if x > 10 {
return :ret_label_a x * 2;
};
};
std:assert_eq (test 11) 22;
向量
!v = $[1, 2, 3];
v.1 = 5;
std:assert_eq v.1 5;
std:assert_eq (std:pop v) 3;
std:assert_eq (std:pop v) 5;
std:assert_eq (std:pop v) 1;
遍历向量
!sum = 0;
iter i $[1, 2, 3, 4] { .sum = sum + i; };
std:assert_eq sum 10;
在向量中累积值
!new_vec =
$@vec iter i $i(0, 4) {
$+ i;
};
std:assert_eq (str new_vec) (str $[0,1,2,3]);
累积总和
!sum =
$@int iter i $i(0, 4) {
$+ i;
};
std:assert_eq sum 1 + 2 + 3;
哈希表/映射
!m = ${ a = 10, c = 2 };
m.b = m.a + m.c;
std:assert_eq m.b 12;
字符串
!name = "Mr. X";
std:assert_eq name.4 'X'; # index a character
std:assert_eq (name 0 3) "Mr."; # substring
!stuff = "日本人";
std:assert_eq stuff.0 '日'; # Unicode support
Unicode标识符
!人 = "jin";
std:assert_eq 人 "jin";
错误处理
!some_fun = {
if _ == :fail {
$error :FAIL_HAVING_FUN
} {
:ok
}
};
!res1 =
match some_fun[:ok]
($error :FAIL_HAVING_FUN) => :failed
? => :ok;
std:assert_eq res1 :ok;
!res1 =
match some_fun[:fail]
($error :FAIL_HAVING_FUN) => :failed
? => :ok;
std:assert_eq res1 :failed;
内置结构选择器
选择器的工作方式类似于XPath:$S( *:{a=10} /b/1 )
首先从向量中选择所有映射,检查它们是否有匹配的键值对key=a
和value=10
。对于匹配的映射,将遍历选择器路径并选择b
键。接下来选择索引为1
的元素并捕获。
!struct = $[
${ a = 10, b = $[ 1, 2, 3 ] },
${ a = 10, b = $[ 4, 5, 6 ] },
${ a = 20, b = $[ 8, 9, 20 ] },
${ a = 20, b = $[ 8, 10, 30 ] },
${ x = 99 },
${ y = 99 },
];
if struct &> $S( *:{a=10} /b/1 ) {
std:assert_str_eq $\ $[2,5];
} {
panic "Should've matched!";
};
内置结构匹配器
与结构选择器略有不同,但类似的是$S ...
的是$M ...
或match
结构匹配器
!struct = $[
${ a = 10, b = $[ 1, 2, 3 ] },
${ a = 10, b = $[ 4, 5, 6 ] },
${ a = 20, b = $[ 8, 9, 20 ] },
${ a = 20, b = $[ 8, 10, 30 ] },
${ x = 99 },
${ y = 99 },
];
!res = $@vec iter elem struct {
$+ ~
match elem
${ a = 10, b = childs } => $[:childs_10, $\.childs]
${ a = 20, b = childs } => $[:childs_20, $\.childs]
:other;
};
std:assert_str_eq res $[
$[:childs_10,$[1, 2, 3]],
$[:childs_10,$[4, 5, 6]],
$[:childs_20,$[8, 9, 20]],
$[:childs_20,$[8, 10, 30]],
:other,
:other,
];
内置(正则表达式)模式匹配
!some_url = "http://crates.io/crates/wlambda";
!crate = $none;
!domain = $none;
if some_url &> $r{$^ (^$+[^:]) :// (^$*[^/]) /crates/ (^$+[a-z]) } {
.domain = $\.2;
.crate = $\.3;
};
std:assert_eq domain "crates.io";
std:assert_eq crate "wlambda";
使用原型的面向对象编程
!MyClass = ${
new = {
${
_proto = $self,
_data = ${ balance = 0, }
}
},
deposit = {
$data.balance = $data.balance + _;
},
};
!account1 = MyClass.new[];
account1.deposit 100;
account1.deposit 50;
std:assert_eq account1._data.balance 150;
使用闭包的面向对象编程
!MyClass = {
!self = ${ balance = 0, };
self.deposit = { self.balance = self.balance + _; };
$:self
};
!account1 = MyClass[];
account1.deposit 100;
account1.deposit 50;
std:assert_eq account1.balance 150;
WLambda模块
# util.wl:
!@import std std;
!@wlambda;
!@export print_ten = { std:displayln ~ str 10; };
对于导入,您这样做
!@import u util;
u:print_ten[]
示例WLambda代码
这只是对WLambda语法和语义的快速浏览。
有关语法和提供的全局函数的更多详细信息,请参阅WLambda语言参考。
目前,在tests/language.rs
的测试用例中有更多示例。
API使用示例
基本API使用
以下是快速评估一段WLambda代码的方法
let s = "$[1,2,3]";
let r = wlambda::eval(&s).unwrap();
println!("Res: {}", r.s());
更高级的API使用
如果您想快速添加一些自己的函数,可以使用GlobalEnv的add_func
方法
use wlambda::vval::{VVal, VValFun, Env};
let global_env = wlambda::GlobalEnv::new_default();
global_env.borrow_mut().add_func(
"my_crazy_add",
|env: &mut Env, _argc: usize| {
Ok(VVal::Int(
env.arg(0).i() * 11
+ env.arg(1).i() * 13
))
}, Some(2), Some(2));
let mut ctx = wlambda::compiler::EvalContext::new(global_env);
// Please note, you can also add functions later on,
// but this time directly to the EvalContext:
ctx.set_global_var(
"my_crazy_mul",
&VValFun::new_fun(|env: &mut Env, _argc: usize| {
Ok(VVal::Int(
(env.arg(0).i() + 11)
* (env.arg(1).i() + 13)))
}, Some(2), Some(2), false));
let res_add : VVal = ctx.eval("my_crazy_add 2 4").unwrap();
assert_eq!(res_add.i(), 74);
let res_mul : VVal = ctx.eval("my_crazy_mul 2 4").unwrap();
assert_eq!(res_mul.i(), 221);
维护状态
use wlambda::*;
let mut ctx = EvalContext::new_default();
ctx.eval("!x = 10").unwrap();
ctx.set_global_var("y", &VVal::Int(32));
let r = ctx.eval("x + y").unwrap();
assert_eq!(r.s(), "42");
可能的路线图
WLambda当前剩余的目标是
- 修复剩余的bug。
- 已完成:添加缺失的标准库函数,而不引入更多依赖。
- 改进并进一步记录与WLambda交互的VVal API。
- 已完成:改进WLambda语言参考文档。
- 已完成:在WLambda语言参考中完成函数参考文档。
- 已完成:添加通过
!@import
和!@export
的模块支持。 - 已完成:添加基于原型的面向对象范式的继承。
- 已完成:向语言添加数据结构匹配/解构/选择原语。
- 已完成:用虚拟机和更智能的代码生成器替换编译器和基于闭包的评估器。
许可证
本项目受GNU通用公共许可证版本3或更高版本的许可。
为什么是GPL?
长期以来,我为代码选择许可证一直让我感到困扰。我阅读了许多关于这个主题的讨论。阅读了许可证说明。还与其他开发者讨论了这个问题。
首先谈谈我为什么免费写代码,原因如下
- 编写计算机程序是我的热情。在空闲时间,我可以按照自己的想法、时间和方式编写代码。我可以自由地分配我的时间,自由地选择我想工作的项目。
- 帮助朋友或家人。
- 解决我遇到的问题。
这些都是我免费写代码的原因。现在谈谈我发布代码的原因,尽管我可以自己保留
- 以便它可能为用户和自由软件社区带来价值。
- 展示我的作品作为艺术家。
- 与其他开发者取得联系。
- 并且让我的私人项目更加完善是个不错的选择。
大多数这些原因还不能证明GPL的合理性。GPL的主要观点,就我所理解:GPL确保软件永远保持自由软件。软件的最终用户始终处于控制之中。用户有手段将软件适应新的平台或用例。即使原始作者不再维护该软件。最终防止“供应商锁定”。我非常讨厌供应商锁定,尤其是作为开发者。尤其是作为开发者,我希望并且需要保持对我使用的计算机和软件的控制。
另一个观点是,我的工作(以及任何其他开发者的工作)都有价值。如果我将我的工作免费赠送,我实际上是在免费工作。这降低了(我以及可能的其他开发者)对技能、人力和时间可以要求的价格。
这使我选择了GPL的两个原因
- 我不想免费支持供应商锁定场景。当我有选择时,当我在个人时间里投资以向最终用户带来价值时,我想阻止这些。
- 我不想通过免费赠送我花费宝贵个人时间完成的工作来降低自己的工资和价格。这样,公司才能将其用于封闭源代码项目。
转换为MIT / Apache-2.0
(WeirdConstructor)我承诺,如果你在Rust(和WLambda)编写的开源/免费软件游戏(许可在MIT和/或Apache-2.0下)中使用WLambda,并且有一个可玩性测试版本,内容丰富,游戏玩法足够让我至少沉迷2小时,那么你可以在发布中使用WLambda,就像它是在MIT和/或Apache-2.0下发布的一样。按照MIT和/或Apache-2.0的要求进行适当署名。
如果你现在需要许可(MIT)
如果你需要不同的许可证并想使用我的代码,请与我联系。只要我是唯一的作者,我可以改变我自己编写的代码的许可证。我们可能会找到涉及金钱或其他方面的协议。就你的价格估计而言:截至2020年5月,我在这项项目中投入了大约6个月的个人时间。
贡献
除非你明确表示,否则,你提交给WLambda以供包含的任何贡献,都应按GPLv3或更高版本许可,不得附加任何额外条款或条件。
作者
- Weird Constructor [email protected] (GitHub上的WeirdConstructor) (你可以在Rust Discord上找到我,作为
WeirdConstructor
.)
贡献者
- Cedric Hutchings [email protected] (GitHub上的cedric-h)
依赖关系
~9–27MB
~387K SLoC