#decl #lustre #embedding #proc-macro #deep #chandeliers #language

nightly macro chandeliers-lus

Chandeliers项目中的进程宏,实现了Lustre在Rust中的深度嵌入

21个版本 (1个稳定版)

1.0.0 2024年1月5日
0.6.1 2024年1月4日
0.5.2 2023年12月29日
0.5.0 2023年11月24日
0.2.6 2023年10月27日

#311进程宏

Download history 67/week @ 2024-03-30 21/week @ 2024-04-06

每月251次下载

MIT/Apache

495KB
10K SLoC

Chandeliers-Lus

Chandeliers套件的客户端。


该crate提供了定义Lustre在Rust中深度嵌入的decl宏。

用法

在本节中,我们假设读者熟悉Lustre语言。

声明常量和节点

一个自包含的Lustre程序通常可以仅通过少量的修改,封装在decl宏中。以下是一些示例

chandeliers_lus::decl! {
    const ZERO: int = 0;
    const ONE: int = 1;

    node fibonacci() returns (n : int);
    let
        n = ZERO -> ONE -> (pre n + pre pre n);
    tel;

    node fibsum() returns (s : int);
    let
        s = fibonacci() + (0 fby s);
    tel;
}
chandeliers_lus::decl! {
    node selector(left, right : float; switch : bool) returns (out : float);
    let
        out = if switch then left else right end;
    tel;
}

导入外部定义

Chandeliers不提供任何内置函数。这是故意的。

而是提供了一个灵活且健壮的方法来声明外部定义。任何extern定义的对象将被Chandeliers的静态分析器假设为已定义,如果在代码生成时缺失,则Rustc将发出错误。

extern包括

  • 另一个decl块中的声明(以下示例),
  • 标准库中的节点(见crate chandeliers-std)或另一个crate中的节点,
  • 直接在Candle中编写的自定义节点(更多信息请参阅chandeliers-sem,示例请参阅tests/pass/std和特别地tests/pass/std/rand.rs)。

使用另一个decl块中的定义

chandeliers_lus::decl! {
    node counter() returns (n : int);
    let n = 0 fby (n + 1); tel;
}

// `decl` is local and cannot know that `counter` exists in another block.
// If we want to use it here, we need to redeclare it as an `extern node`.
chandeliers_lus::decl! {
    // any signature inconsistencies will produce type errors.
    extern node counter() returns (n : int);

    node cumul() returns (n : int);
    let n = counter() + (0 fby n); tel;
}

同样,一个

chandeliers_lus::decl! {
    extern const PI: float;
    ...

makes PI 可用于其余的块。

使用标准库中的定义

use chandeliers_std::float_of_int;

chandeliers_lus::decl! {
    extern node float_of_int(i : int) returns (f : float);
    ...
}

如何编写main函数

decl宏永远不会插入main,相反,应该手动定义以与环境交互。这是由于decl块在其定义下公开提供相同名称的事实。

main可能看起来像这样

use chandeliers_sem::traits::*;

chandeliers_lus::decl! {
    node cumul(i : int) returns (s : int);
    let s = i + (0 fby s); tel;
}

fn main() {
    let mut cumul = cumul::default();
    for i in 0..100 {
        let s = cumul.step(i.embed()).trusted();
        println!("{s}");
    }
}

上述功能由特征 StepDefaultEmbedTrusted 提供,这些特征在相关对象上自动实现,要么是通过 decl 宏本身实现的,要么是在 chandeliers-sem 中通用定义的。

decl 生成的节点在内部操作不同类型的值(同构于 Option<T>),而特征 EmbedTrusted 在所有元组类型上产生等价的 map(Some)map(Option::unwrap)

更具体地说,元组类型 (T, U, V) 实现了 Embed,输出类型为 (Nillable<T>, Nillable<U>, Nillable<V>),而 Trusted 产生其逆。使用 Embed 总是安全的,但您应该只在非 Nil 的值上使用 Trusted。在 decl 内部执行的静态分析保证,所有输入都不是 Nil 的节点将具有非 Nil 的输出,因此只需要验证不是由 decl 块生成的 extern 节点 的实现。

与标准 Lustre 的显著局限性和差异

由于各种原因——包括对 Rust 语法有限的控制和解析效率——一些 Lustre 程序可能无法直接支持,并需要微小的语法调整。以下是在您急于复制粘贴 Lustre 代码时可能会遇到的一些情况:

  • node 定义必须以分号 ; 结尾,
  • 元组解包 (x, y, z) = foo() 需要周围的 (...)
  • 使用 Rust 习惯用法来确定允许尾随逗号的上下文:1 = (1) <> (1,)(1, 2) = (1, 2,)
  • 一些Rust保留关键字(如selfsupermove等)不能用作标识符,
  • 只支持Rust风格的注释,即行注释使用//,块注释使用/* ... */
  • f((a, b, c))可以隐式转换为f(a, b, c),使得当函数的输出元组与另一个函数的输入元组的阶数相同时,可以轻松地进行函数组合。

此外,关于语义,还做了以下选择

  • 延迟操作符->具有特殊的结合规则,使得a -> b -> c(a1, b2, c3, c4, c5, c6, ...)既不等于a -> (b -> c)(a1, c2, c3, c4, c5, c6, ...),也不等于(a -> b) -> c(a1, c2, c3, c4, c5, c6, ...)。
  • 时间操作符_ -> _pre __ fby _仅适用于具有节点隐式时钟的表达式。使用注解#[universal_pre]可以取消此限制,该注解使用与时钟表达式兼容的时间操作符的另一种编码,但可能会带来轻微的性能损失。

依赖项

~0.4–0.8MB
~19K SLoC