#code-generation #scripting-language #plugin #rust

nightly rust_mixin

Yo dawg, use Rust to generate Rust, right in your Rust. (See external_mixin to use scripting languages.)

1 个不稳定版本

使用旧的 Rust 2015

0.0.1 2015年2月27日

#77 in #code-generation


external_mixin_umbrella 中使用

MIT/Apache

3KB

macro_rules! with realer programming languages

Build Status

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

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_mixinrust_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中还不存在。)

依赖项