< 返回版块

duan-zhou 发表于 2024-11-16 09:23

如下,要实现{}与后面的参数数量编译期检查

tokens!("SELECT {}, COUNT(*) OVER() AS total, ROW_NUMBER OVER () AS rid FROM {} WHERE {}", select, table, where_)

目前比较笨的方法借助format!宏,这样写到差不多16

#[macro_export]
macro_rules! tokens {
    ($fmt:expr, $a1:expr) => {{
        let text = format!($fmt, "{}");
        let tokens: Vec<$crate::Token> = vec![$a1.into()];
        $crate::tokens_compile!(text, tokens)
    }};
    ($fmt:expr, $a1:expr, $a2:expr) => {{
        let text = format!($fmt, "{}", "{}");
        let tokens: Vec<$crate::Token> = vec![$a1.into(), $a2.into()];
        $crate::tokens_compile!(text, tokens)
    }};
    ($fmt:expr, $a1:expr, $a2:expr, $a3:expr) => {{
        let text = format!($fmt, "{}", "{}", "{}");
        let tokens: Vec<$crate::Token> = vec![$a1.into(), $a2.into(), $a3.into()];
        $crate::tokens_compile!(text, tokens)
    }};
    ($fmt:expr, $a1:expr, $a2:expr, $a3:expr, $a4:expr) => {{
        let text = format!($fmt, "{}", "{}", "{}", "{}");
        let tokens: Vec<$crate::Token> = vec![$a1.into(), $a2.into(), $a3.into(), $a4.into()];
        $crate::tokens_compile!(text, tokens)
    }};
    ($fmt:expr, $a1:expr, $a2:expr, $a3:expr, $a4:expr, $a5:expr) => {{
        let text = format!($fmt, "{}", "{}", "{}", "{}", "{}");
        let tokens: Vec<$crate::Token> =
            vec![$a1.into(), $a2.into(), $a3.into(), $a4.into(), $a5.into()];
        $crate::tokens_compile!(text, tokens)
    }}; 
    ($fmt:expr, $a1:expr, $a2:expr, $a3:expr, $a4:expr, $a5:expr, $a6:expr) => {{
        let text = format!($fmt, "{}", "{}", "{}", "{}", "{}", "{}");
        let tokens: Vec<$crate::Token> =
            vec![$a1.into(), $a2.into(), $a3.into(), $a4.into(), $a5.into(), $a6.into()];
        $crate::tokens_compile!(text, tokens)
    }}; 
    // todo
}

评论区

写评论
small-white0-0 2024-11-20 17:40

你这个可以看看宏小册,里面有个叫什么撕咬机的写法。可以实现你这个思路。

我简单写了例子,不怎么常写宏,仅供参考。

#[macro_export]
macro_rules! tokens_impl {
    // 所有参数已经解析完成
    ($fmt:literal; $($brack:expr),* ;$($args:expr),*;) => {
        let text = format!($fmt, $($brack),*);
        let tokens: Vec<Token> = vec![$($args.into()),*];
        $crate::tokens_compile!(text, tokens)
    };
    // 剩余一个待解析参数
    ($fmt:literal; $($brack:expr),* ;$($args:expr),* ;$arg:expr) => {
        $crate::tokens_impl!($fmt; "{}" $(,$brack)* ; $arg $(,$args)* ;)
    };
    // 剩余1个且带多余逗号,或多个待解析参数
    ($fmt:literal; $($brack:literal),* ;$($args:expr),*; $arg:expr, $($remain_args:expr),*) => {
           $crate::tokens_impl!($fmt; "{}" $(,$brack)* ; $arg $(,$args)* ; $($remain_args),*)
    };
}

macro_rules! tokens {
    // 无参匹配
    ($fmt:literal) => {
        $crate::tokens_impl!($fmt;;;)
    };
    // 任意参匹配
    ($fmt:literal, $($args:expr),*) => {
        $crate::tokens_impl!($fmt;;;$($args),*)
    };
}
作者 duan-zhou 2024-11-18 14:13

我主要就希望能够检查{}和后面参数数量是否对等

--
👇
Bai-Jinlin: sqlx已经搞了编译期检测sql语句了,用的过程宏,并且编译期检测sql会搞得编译速度很慢。

作者 duan-zhou 2024-11-18 14:12

我还是先简单的从0写道16匹配吧,后面再看看过程宏

--
👇
LazyBoy: macro_rules应该是没法直接满足你的需求,应该它是定义声明宏的,即只能通过一些简单的匹配声明来实现宏。对于你需求的复杂解析需求,需要使用使用过程宏里的类函数宏,即类似函数在编译时运行,可以实现非常复杂的解析需求,如同常规程序编写函数一样。

此外,对于你提供的基于format宏的写法,也有一种优化手段(简写,但是需要特性支持):

#![feature(macro_metavar_expr)] // 元变量表达式目前只能用于nightly版本,且手动需要开启特性

macro_rules! tokens {
    // 无参匹配
    ($fmt:literal) => {
        $crate::tokens_compile!($fmt)
    };
    // 任意参匹配
    ($fmt:literal, $($args:expr),*) => {
        // ${ignore($args)}是元变量表达式,调用了ignore表示此处使用了$args但是忽略该元变量的内容,基此`"{}"`可以重复展开与参数同等的数量
        let text = format!($fmt, $(${ignore($args)}"{}"),*);
        let tokens: Vec<$crate::Token> = vec![$($args.into()),*];
        $crate::tokens_compile!(text, tokens)
    };
}
SleepyBoy 2024-11-18 12:10

macro_rules应该是没法直接满足你的需求,应该它是定义声明宏的,即只能通过一些简单的匹配声明来实现宏。对于你需求的复杂解析需求,需要使用使用过程宏里的类函数宏,即类似函数在编译时运行,可以实现非常复杂的解析需求,如同常规程序编写函数一样。

此外,对于你提供的基于format宏的写法,也有一种优化手段(简写,但是需要特性支持):

#![feature(macro_metavar_expr)] // 元变量表达式目前只能用于nightly版本,且手动需要开启特性

macro_rules! tokens {
    // 无参匹配
    ($fmt:literal) => {
        $crate::tokens_compile!($fmt)
    };
    // 任意参匹配
    ($fmt:literal, $($args:expr),*) => {
        // ${ignore($args)}是元变量表达式,调用了ignore表示此处使用了$args但是忽略该元变量的内容,基此`"{}"`可以重复展开与参数同等的数量
        let text = format!($fmt, $(${ignore($args)}"{}"),*);
        let tokens: Vec<$crate::Token> = vec![$($args.into()),*];
        $crate::tokens_compile!(text, tokens)
    };
}
nan_mu 2024-11-16 20:10

不太熟悉数据库,但现在的参数化查询有什么问题吗对比贴主的做法

Bai-Jinlin 2024-11-16 11:09

sqlx已经搞了编译期检测sql语句了,用的过程宏,并且编译期检测sql会搞得编译速度很慢。

1 共 6 条评论, 1 页