1 个不稳定版本
使用旧的 Rust 2015
0.0.1 | 2015年2月27日 |
---|
#77 in #code-generation
在 external_mixin_umbrella 中使用
3KB
macro_rules!
with realer programming languages
Using an arbitrary programming language, generate Rust code directly into your crate.
#![feature(plugin)]
#![plugin(external_mixin)]
#![plugin(rust_mixin)]
// creates a `get_x` function
python_mixin! {"
x = 1 + 2
print('fn get_x() -> u64 { %d }' % x)
"}
fn main() {
let value = get_x();
// should evaluate to 7.
let other_value = rust_mixin! {r#"
fn main() {
println!("{}", 3 + 4);
}
"#};
assert_eq!(value, 3);
assert_eq!(other_value, 7);
}
It can even be used to build other macros
#![feature(plugin)]
#![plugin(external_mixin)]
// converts the whole input code to lowercase, if you think Rust is
// missing that vital SQL/COBOL feel
macro_rules! downcase {
($($args: tt)*) => {
python_mixin! {
concat!("input = \"",
stringify!($($args)*),
r#""
print(input.lower())
"#)
}
}
}
fn main() {
downcase!(FN FOO() -> I32 { 1 });
println!("{}", foo()); // 1
}
This comes in three libraries
rust_mixin
— use Rust to generate your code.external_mixin
— use scripting languages like Python or Ruby to generate your code.external_mixin_umbrella
— support library, to keep the above DRY.
Don't miss the Q&A session at the bottom.
安装
Both plugin crates are available on crates.io: rust_mixin
, external_mixin
. Hence, you can add any subset of
[dependencies]
rust_mixin = "*"
external_mixin = "*"
to your Cargo.toml
.
rust_mixin
Write Rust to generate your Rust, right in your Rust (yo dawg). The plugin compiles and runs its argument as a Rust program at compile time, and then inserts the output into the main crate, similar to a macro_rules!
macro.
The rust_mixin
plugin takes a single string, containing a Rust program to be compiled with rustc
. This program should print valid Rust to stdout. Each rust_mixin
invocation is independent of all others (no stored state). The string argument is macro-expanded before being used, so constructing an invocation with concat!()
is legitimate.
The macro supports an optional { ... }
block before the string literal, to specify options. The only option supported currently is arg
: it can be specified multiple times, and the arguments are passed to rustc
in the order given.
This doesn't currently support using any dependencies via cargo
.
示例
Compute Fibonacci numbers in the best way possible, by making Rust print a function to compute each number
#![feature(plugin)]
#![plugin(rust_mixin)]
rust_mixin! {r#"
fn main() {
println!("fn fib_0() -> i32 {{ 0 }}");
println!("fn fib_1() -> i32 {{ 1 }}");
for i in 2..(40 + 1) {
println!("fn fib_{}() -> i32 {{ fib_{}() + fib_{}() }}",
i, i - 1, i - 2);
}
}
"#}
fn main() {
println!("the 30th fibonacci number is {}", fib_30());
}
Do the Fibonacci computation at compile time, naively, so we want some optimisations
#![feature(plugin)]
#![plugin(rust_mixin)]
fn main() {
let fib_30 = rust_mixin! {
{ arg = "-C", arg = "opt-level=3" }
r#"
fn fib(n: u64) -> u64 {
if n <= 1 { n } else { fib(n - 1) + fib(n - 2) }
}
fn main() {
println!("{}", fib(30))
}
"#};
println!("the 30th fibonacci number is {}", fib_30);
}
external_mixin
在编译时使用各种脚本语言生成Rust代码。这有一个external_mixin!
宏,它支持任意解释器,以及为几种语言提供专门支持:python_mixin!
、ruby_mixin!
、sh_mixin!
、perl_mixin!
。
与rust_mixin!
类似,这些宏将程序作为字符串(进行宏展开)处理。程序应该输出有效的Rust代码到stdout,并且每次调用都是独立的,没有存储状态。可以使用可选的{ ... }
块指定选项,该块位于字符串字面量之前。
external_mixin!
宏是最灵活的形式,它需要一个必填的interpreter
参数:该程序通过包含代码片段的文件的路径作为最后一个参数调用。
external_mixin!
和特定语言的宏都支持arg
选项,可以指定多次,并按顺序传递给解释器(在文件参数之前),顺序不变。
可移植性?
这些宏依赖于调用解释器,依赖于用户路径中存在正确命名的可执行文件(希望它确实是正确的版本……)。因此,这不是可移植的或可靠的。至少一个rust_mixin!
的用户可以保证有一个rustc
可用,这里没有这样的保证。
示例
计算位于(Unix)文件系统顶部的文件/文件夹数量。
#![feature(plugin)]
#![plugin(external_mixin)]
fn main() {
let file_count = sh_mixin!("ls / | wc -l");
println!("there are {} files in /", file_count);
}
通过Ruby计算程序构建时的Unix时间。
#![feature(plugin)]
#![plugin(external_mixin)]
fn main() {
let build_time = ruby_mixin!("puts Time.now.to_i");
println!("this was built {} seconds after 1970-01-01 00:00:00", build_time);
}
使用Python 2的裸露打印语句和Python 3的除法语义(并猜测由python_mixin!
使用的python
二进制版本)
#![feature(plugin)]
#![plugin(external_mixin)]
fn main() {
let value2 = external_mixin! {
{ interpreter = "python2" }
"print 1 / 2"
};
let value3 = external_mixin! {
{ interpreter = "python3" }
"print(1 / 2)"
};
let value_unknown = python_mixin!("print(1 / 2)");
if value_unknown as f64 == value3 {
println!("`python_mixin!` is Python 3");
} else {
println!("`python_mixin!` is Python 2");
}
}
external_mixin_umbrella
此存储库的最高级项是一个库,旨在最大化external_mixin
和rust_mixin
之间的代码共享,以便它们的实现分别只有100行和50行。
所有你的问题...都得到了解答
我实际上应该使用这些吗?
可能不是,我正在试验更多语言插件。进行此类代码生成的更可移植/可用的方法是使用Cargo
构建脚本加上include!
宏。
一些缺点(不全面)
-
类似于
python_mixin!
的混入依赖于用户路径中存在正确命名的二进制文件,例如,“python
”有时是Python 2,有时是Python 3。此外,它意味着要求用户在Windows上安装Python。(构建脚本只需要Cargo和Rust编译器,如果用户尝试构建你的Rust代码,则可以保证他们有这些。) -
生成的代码中的错误很难调试,尽管宏试图尽可能提供有用的错误消息,例如,尽可能接近包含源字符串的相关部分的文件/行号。然而,解析的Rust实际上并没有出现在磁盘上或其他任何地方,因此无法轻松地看到编译器抱怨时的完整上下文(相比之下,构建脚本只是在你的文件系统中生成一个正常的文件)。
为什么不使用标记树,而不是字符串?
对于空格敏感的语言,这种方法表现并不太好。与external_mixin
一起使用的其他语言可能有与Rust完全不同的语法,例如,许多脚本语言中'foo'
是一个有效的字符串,但在Rust中它是一个无效的字符字面量。Rust中用#
分隔的原始字符串意味着无需转义(只需添加足够的#
,通常一个就足够了)。
对于rust_mixin!
,我认为一致性很好,它通过语法高亮提供了对真实程序和子程序的区分(一个文件中有两个main
会让人困惑)。
将rust_mixin
作为编译时函数评估不是更好吗?
是的,可能吧...但它既类型安全又合理,并不像字符串操作那样有趣。(它目前在Rust中还不存在。)