Macro nar_dev_utils::pipe

source ·
macro_rules! pipe {
    { @CALL [ $($f:tt)* ] [ $($value:expr),* ] } => { ... };
    {
        @CALL
        $f:tt
        $value:tt => $args:tt
    } => { ... };
    { // 终态:插入完成
        @INSERT
        [ $($f:tt)* ]
        $_value:tt =>
        [
            $( $values:expr ),*
            $(,)?
        ]
    } => { ... };
    { // 中态:不断插入
        @INSERT
        $f:tt
        [ $value:expr ] =>
        [
            $( $value_past:expr, )*
            _
            $( $tail:tt )*
        ]
    } => { ... };
    { $value:expr } => { ... };
    {
        $value:expr =>
        #{ $($prefix:tt)* }
        $( => $($tail:tt)*)?
    } => { ... };
    {
        $value:expr =>
        { $($suffix:tt)* }#
        $( => $($tail:tt)*)?
    } => { ... };
    {
        $value:expr =>
        . $key:tt $( ( $($param:tt)* ) )?
        $( => $($tail:tt)*)?
    } => { ... };
    {
        $value:expr =>
        [ $($dot_path:tt).+ ] $( ( $($param:tt)* ) )?
        $( => $($tail:tt)*)?
    } => { ... };
    {
        $value:expr =>
        $($p:tt)::+           $( ( $($param:tt)* ) )?
        $( => $($tail:tt)*)?
    } => { ... };
    {
        $value:expr =>
        ($f:expr)             $( ( $($param:tt)* ) )?
        $( => $($tail:tt)*)?
    } => { ... };
}
Expand description

§pipe!

一个实用、强大而高效的「管道」宏,允许带任意数量插值的任意函数调用

  • 🎯用以实现类似Julia @pipe的「管道处理」效果
  • 📌使用占位符_进行插值
    • ✅允许多重插值——但会复制整个表达式
  • ✨部分灵感来自Julia的Pipe.jl
    • 📄其中的宏@pipe有类似的效果
  • ⚠️【2024-03-26 00:16:36】目前对「完全限定语法」尚未有良好兼容
    • 📄Vec::<usize>::new会因「大于/小于 符号」失效
    • ✅可使用「代码后缀」语法模拟,但其中不提供插值
      • 📄如=> {.collect::<Vec<_>>()}#

§📄示例 Examples

use nar_dev_utils::{pipe, asserts};
fn f1(x: i32) -> i32 { x + 1 }
fn f2(x: i32, y: i32) -> i32 { x + y }
fn f3(x: i32, y: i32) -> i32 { x - y }

let v = 1;
let x = 2;
let y = 3;
// (((x + 1) + 2) - 3)
let piped = pipe! { v => f1 => f2(x, _) => (f3)(_, y) => f2(_, _) };
let normal = f2(f3(f2(x, f1(v)), y), f3(f2(x, f1(v)), y));
asserts!{
    piped => normal,
    piped => 2,
};

§🚩内部实现

  • 📌递归模型:标签树撕咬机 + 多分派状态机
    • 总体流程:用户输入 ⇒ 内部输入 ⇒ 单次管道返回值 ⇒ 尾递归回代
  • 📌对「被管道的函数」的语法支持
    • 标识符
    • #{前缀}
    • .属性
    • .方法(..参数)
    • (表达式)
    • 模块::函数
    • [对象.方法]

§✅规模化测试

use nar_dev_utils::{pipe, asserts};
mod m {
    pub fn add_one(x: i32) -> i32 {
        x + 1
    }
    pub fn tri_add(x: i32, y: i32, z: i32) -> i32 {
        x + y + z
    }
}
use m::add_one;

asserts! {

    // 内部情形
    pipe! {@CALL [add_one] [1]} => 2,
    pipe! {@CALL [i32::checked_add] [1] => [_, 2]} => Some(3),
    pipe! {@CALL [m::tri_add] [1] => [_, 2, 3]} => 6,
    pipe! {@CALL [m::tri_add] [2] => [1, _, 3]} => 6,
    pipe! {@CALL [m::tri_add] [3] => [1, 2, _]} => 6,

    // 平凡情况:单个值 //
    pipe! { 1 } => 1,
    pipe! { 1 + 1 } => 2,
    pipe! { add_one(1) } => 2,
    pipe! { match 1 { 1 => 2, _ => 0 } } => 2,

    // 最简单的情况:单函数 //

    // 直接使用标识符
    pipe! {1 => add_one} => 2,
    // 模块路径
    pipe! {1 => m::add_one} => 2,
    // 关联函数
    pipe! {&vec![1] => Vec::len} => 1,
    // 内部使用闭包的表达式
    pipe! {1 => (|x| x + 1)} => 2,
    // 对象的方法
    pipe! {1 => [0_i32.min]} => 0,

    // 实用辅助:借用、访问 //

    // 测试`self.method`
    pipe! {
        "I can be turned into a &str!"
        => String::from // 转换成字符串
        => #{&} // 加上前缀`&`转换为`&String`
        => .as_str() // 转换为`&str`
        => .to_lowercase() // 全小写
    } => "i can be turned into a &str!",
    // 测试`self.field`、`&mut`
    {
        let mut s_0 = ("Hello, ".to_string(), 0);
        pipe! {
            s_0
            => .0 // 获取内部的`String`
            => #{&mut} // 获取`&mut String`
            => .push_str("pipe!") // 追加字符串
        };
        s_0.0
    } => "Hello, pipe!",

    // 实用辅助:数组索引、上抛、后缀运算 //

    // 测试`self[i]`
    pipe! {
        ["this", "is", "an", "array"]
        => {[3]}# // 索引第三个元素
        => .to_uppercase() // 全大写
    } => "ARRAY",
    // 测试`self?`、`self+1`
    {
        fn parse_and_add_one(input: impl AsRef<str>) -> Result<usize, std::num::ParseIntError> {
            pipe! {
                input
                => .as_ref() // 先转成字符串引用
                => (str::parse::<usize>) // 再调用「解析」方法
                => {?}# // 解析失败时,上抛错误
                => {+1}# // 后缀`+1`
                => Result::Ok // 包装进Result中
            }
        }
        // 检验处理过程
        (parse_and_add_one("1").unwrap(), parse_and_add_one("err"))
    } => (2, "err".parse::<usize>()),

    // 复杂情况:函数插值 //

    // 单重插值语法
    pipe! {1 => i32::checked_add(_, 1)} => Some(2),
    pipe! {1 => i32::min(0, _)} => 0,
    pipe! {1 => (|x, y| x + y)(_, 1)} => 2,
    pipe! {1 => (|x, y, z| x * y * z)(1, _, 2)} => 2,

    // 多重插值语法 | 直接拷贝表达式
    pipe! { @INSERT [usize::checked_add] [1] => [2, 3] } => Some(5)
    pipe! { @INSERT [m::tri_add] [1] => [_, _, 3] } => 5
    pipe! { @INSERT [m::tri_add] [2] => [1, _, _] } => 5
    pipe! { @INSERT [m::tri_add] [3] => [_, 2, _] } => 8
    pipe! { @INSERT [usize::checked_add] [0] => [_, _] } => Some(0)
    pipe! { @CALL [usize::checked_add] [0] => [_, _] } => Some(0)

    // 复杂情况:多函数链路 //

    // 直接使用标识符
    pipe! {1 => add_one => add_one} => 3,
    // 模块路径
    pipe! {1 => m::add_one => m::add_one} => 3,
    // 关联函数
    pipe! {&vec![1] => Vec::len => usize::checked_add(_, 1)} => Some(2),
    // 内部使用闭包的表达式
    pipe! {1 => (|x| x + 1) => (|x| x + 1)} => 3,
    // 对象的方法
    pipe! {1 => [0_i32.min] => [1_i32.max]} => 1,

    // 大 杂 烩 //
    pipe! {
        // 最初的值
        &vec![1]
        // 关联函数
        => Vec::len // 1
        // 内部使用闭包的表达式
        => (|x:usize| x as i32) // 转换类型
        => (|x| x + 1) // 2
        // 内部使用闭包的表达式(带参数)
        => (|x, y| x - y)(_, 1) // 1
        // 直接使用标识符
        => add_one // 2
        // 模块路径
        => m::add_one // 3
        // 关联函数(带参数)
        => i32::checked_sub(_, 1) // Some(2)
        => Option::unwrap // 2
        // 对象的方法
        => [1_i32.min] // 1
        => [(-1_i32).max] // 1
    } => 1
}