Macros 如何使用rust中的宏生成复杂的枚举变量

Macros 如何使用rust中的宏生成复杂的枚举变量,macros,rust,Macros,Rust,我正在编写一个用于解析OVPN配置文件的小库。OVPN配置文件具有此格式 command arg1 arg2 othercommand arg1 arg2 有一组固定的命令,其中一些命令有可选参数。我想将解析后的命令表示为枚举。因此,上述情况可能最终表现为: enum ConfigDirective{ Command{arg1: String}, OtherCommand{arg1: String, optinal_arg1: Option<String>}, }

我正在编写一个用于解析OVPN配置文件的小库。OVPN配置文件具有此格式

command arg1 arg2
othercommand arg1 arg2
有一组固定的命令,其中一些命令有可选参数。我想将解析后的命令表示为枚举。因此,上述情况可能最终表现为:

enum ConfigDirective{
    Command{arg1: String},
    OtherCommand{arg1: String, optinal_arg1: Option<String>},
}

fn parse_line(command: String, args: Vec<String>) -> ConfigDirective {
    match command {
        "command" => ConfigDirective::Command{arg1: args[0]},
        "other_command" => ConfigDirective:OtherCommand{arg1: args[0], optional_arg1: args.get(1),
    }
}
到目前为止,我能得到的最接近的结果是:

macro_rules! define_config_directives {
    ($({
        rust_name => $rust_name:ident,
        required => [$($required:ident),*],
        optional => [$($optional:ident),*]
    }),*) => {
        #[derive(PartialEq, Eq, Debug)]
        pub enum ConfigDirective {
            $($rust_name{
                $($required: String),*,
                $($optional: Option<String>),*,
            }),*
        }
    };
}
macro\u规则!定义配置指令{
($({
rust_name=>$rust_name:ident,
必需=>[$($required:ident),*],
可选=>[$($optional:ident),*]
}),*) => {
#[导出(PartialEq、Eq、Debug)]
发布枚举配置指令{
$($rust_名称{
$($必需:字符串),*,
$($可选:选项),*,
}),*
}
};
}
所以我有几个问题:

  • 我不知道如何在这个宏中实现
    parse_line
    函数,我需要迭代每个必需的参数,以便编写一些代码将相应的参数从行中拉出,对于可选参数也是如此
  • 我不知道如何处理完全没有参数的情况,理想情况下是一个没有字段的简单枚举变量

  • 有人知道有没有办法解决这个问题吗?或者我应该使用python脚本生成代码吗?

    这是一种有点病态的情况。首先,您希望以不同的方式处理部分输入,这是宏所不擅长的。更糟糕的是,您希望在生成枚举变量的同时执行此操作,而宏也不擅长于生成枚举变量。综上所述,就我所见,只剩下一种方法:完全按下推生成

    简短的版本是:将其分解为简单的匹配步骤,每个步骤处理一件事情,并将该事情的输出添加到累加器中(在本例中,
    $eout
    $pout
    )。输入不足时,将累加器转储到输出中

    macro_rules! define_config_directive {
        // Start rule.
        // Note: `$(,)*` is a trick to eat any number of trailing commas.
        ( $( {$($cmd:tt)*} ),* $(,)*) => {
            // This starts the parse, giving the initial state of the output
            // (i.e. empty).  Note that the commands come after the semicolon.
            define_config_directive! { @parse {}, (args){}; $({$($cmd)*},)* }
        };
    
        // Termination rule: no more input.
        (
            @parse
            // $eout will be the body of the enum.
            {$($eout:tt)*},
            // $pout will be the body of the `parse_line` match.
            // We pass `args` explicitly to make sure all stages are using the
            // *same* `args` (due to identifier hygiene).
            ($args:ident){$($pout:tt)*};
            // See, nothing here?
        ) => {
            #[derive(PartialEq, Eq, Debug)]
            enum ConfigDirective {
                $($eout)*
            }
    
            fn parse_line(command: &str, $args: &[&str]) -> ConfigDirective {
                match command {
                    $($pout)*
                    _ => panic!("unknown command: {:?}", command)
                }
            }
        };
    
        // Rule for command with no arguments.
        (
            @parse {$($eout:tt)*}, ($pargs:ident){$($pout:tt)*};
            {
                command: $sname:expr,
                rust_name: $rname:ident,
                args: [],
                optional_args: [] $(,)*
            },
            $($tail:tt)*
        ) => {
            define_config_directive! {
                @parse
                {
                    $($eout)*
                    $rname,
                },
                ($pargs){
                    $($pout)*
                    $sname => ConfigDirective::$rname,
                };
                $($tail)*
            }
        };
    
        // Rule for other commands.
        (
            @parse {$($eout:tt)*}, ($pargs:ident){$($pout:tt)*};
            {
                command: $sname:expr,
                rust_name: $rname:ident,
                args: [$($args:ident),* $(,)*],
                optional_args: [$($oargs:ident),* $(,)*] $(,)*
            },
            $($tail:tt)*
        ) => {
            define_config_directive! {
                @parse
                {
                    $($eout)*
                    $rname { $( $args: String, )* $( $oargs: Option<String>, )* },
                },
                ($pargs){
                    $($pout)*
                    $sname => {
                        // This trickery is because macros can't count with
                        // regular integers.  We'll just use a mutable index
                        // instead.
                        let mut i = 0;
                        $(let $args = $pargs[i].into(); i += 1;)*
                        $(let $oargs = $pargs.get(i).map(|&s| s.into()); i += 1;)*
                        let _ = i; // avoid unused assignment warnings.
    
                        ConfigDirective::$rname {
                            $($args: $args,)*
                            $($oargs: $oargs,)*
                        }
                    },
                };
                $($tail)*
            }
        };
    }
    
    define_config_directive! {
        {command: "command1", rust_name: CommandOne, args: [arg1], optional_args: []},    
        {command: "other_command", rust_name: OtherCommand, args: [arg1], optional_args: [optional_arg1]},
    }
    
    fn main() {
        println!("{:?}", parse_line("command1", &["foo"]));
        println!("{:?}", parse_line("other_command", &["foo"]));
        println!("{:?}", parse_line("other_command", &["foo", "bar"]));
    }
    
    macro\u规则!定义配置指令{
    //开始规则。
    //注意:`$(,)*`是一个可以吃任意数量尾随逗号的技巧。
    ($({$($cmd:tt)*}),*$(,)*)=>{
    //这将启动解析,给出输出的初始状态
    //(即空)。请注意,命令位于分号之后。
    定义配置指令!{@parse{},(args){};$({$($cmd)*},)*}
    };
    //终止规则:不再输入。
    (
    @解析
    //$eout将是枚举的主体。
    {$($eout:tt)*},
    //$pout将是'parse_line'匹配的主体。
    //我们显式地传递'args',以确保所有阶段都在使用
    //*相同*`args`(由于标识符卫生)。
    ($args:ident){$($pout:tt)*};
    //看,这里什么都没有?
    ) => {
    #[导出(PartialEq、Eq、Debug)]
    枚举配置指令{
    $($eout)*
    }
    fn parse_行(命令:&str,$args:&[&str])->ConfigDirective{
    匹配命令{
    $($撅嘴)*
    _=>死机!(“未知命令:{:?}”,命令)
    }
    }
    };
    //不带参数的命令的规则。
    (
    @解析{$($OUT:tt)*},($pargs:ident){$($pout:tt)*};
    {
    命令:$sname:expr,
    rust_name:$rname:ident,
    args:[],
    可选参数:[]$(,)*
    },
    $($tail:tt)*
    ) => {
    定义配置指令{
    @解析
    {
    $($eout)*
    $rname,
    },
    ($pargs){
    $($撅嘴)*
    $sname=>ConfigDirective::$rname,
    };
    $($尾)*
    }
    };
    //其他命令的规则。
    (
    @解析{$($OUT:tt)*},($pargs:ident){$($pout:tt)*};
    {
    命令:$sname:expr,
    rust_name:$rname:ident,
    参数:[$($args:ident),*$(,)*],
    可选参数:[$($oargs:ident),*$(,)*]$(,)*
    },
    $($tail:tt)*
    ) => {
    定义配置指令{
    @解析
    {
    $($eout)*
    $rname{$($args:String,)*$($oargs:Option,)*},
    },
    ($pargs){
    $($撅嘴)*
    $sname=>{
    //这种诡计是因为宏不能与
    //正则整数。我们只使用可变索引
    //相反。
    设muti=0;
    $(设$args=$pargs[i].into();i+=1;)*
    $(让$oargs=$pargs.get(i).map(|&s | s.into());i+=1;)*
    让=i;//避免未使用的分配警告。
    ConfigDirective::$rname{
    $($args:$args,)*
    $($桨:$桨,)*
    }
    },
    };
    $($尾)*
    }
    };
    }
    定义配置指令!{
    {command:“command1”,rust_name:CommandOne,args:[arg1],可选的_args:[]},
    {command:“other_command”,rust_name:OtherCommand,args:[arg1],optional_args:[optional_arg1]},
    }
    fn main(){
    println!(“{:?}”,解析行(“command1”和[“foo”);
    println!(“{:?}”,parse_行(“其他_命令”&[“foo”]);
    println!(“{:?}”,parse_行(“其他_命令”和[“foo”,“bar”));
    }
    

    不,不能避免累加器,因为宏不能直接扩展到枚举变量。因此,您必须在一个步骤中扩展到整个枚举定义。

    这是一种有点病态的情况。首先,您希望以不同的方式处理部分输入,这是宏所不擅长的。更糟糕的是,您希望在生成枚举变量的同时执行此操作,而宏也不擅长于生成枚举变量。综上所述,就我所见,只剩下一种方法:完全按下推生成

    简短的版本是:将其分解为简单的匹配步骤,其中每个步骤处理一件事,并添加输出
    macro_rules! define_config_directive {
        // Start rule.
        // Note: `$(,)*` is a trick to eat any number of trailing commas.
        ( $( {$($cmd:tt)*} ),* $(,)*) => {
            // This starts the parse, giving the initial state of the output
            // (i.e. empty).  Note that the commands come after the semicolon.
            define_config_directive! { @parse {}, (args){}; $({$($cmd)*},)* }
        };
    
        // Termination rule: no more input.
        (
            @parse
            // $eout will be the body of the enum.
            {$($eout:tt)*},
            // $pout will be the body of the `parse_line` match.
            // We pass `args` explicitly to make sure all stages are using the
            // *same* `args` (due to identifier hygiene).
            ($args:ident){$($pout:tt)*};
            // See, nothing here?
        ) => {
            #[derive(PartialEq, Eq, Debug)]
            enum ConfigDirective {
                $($eout)*
            }
    
            fn parse_line(command: &str, $args: &[&str]) -> ConfigDirective {
                match command {
                    $($pout)*
                    _ => panic!("unknown command: {:?}", command)
                }
            }
        };
    
        // Rule for command with no arguments.
        (
            @parse {$($eout:tt)*}, ($pargs:ident){$($pout:tt)*};
            {
                command: $sname:expr,
                rust_name: $rname:ident,
                args: [],
                optional_args: [] $(,)*
            },
            $($tail:tt)*
        ) => {
            define_config_directive! {
                @parse
                {
                    $($eout)*
                    $rname,
                },
                ($pargs){
                    $($pout)*
                    $sname => ConfigDirective::$rname,
                };
                $($tail)*
            }
        };
    
        // Rule for other commands.
        (
            @parse {$($eout:tt)*}, ($pargs:ident){$($pout:tt)*};
            {
                command: $sname:expr,
                rust_name: $rname:ident,
                args: [$($args:ident),* $(,)*],
                optional_args: [$($oargs:ident),* $(,)*] $(,)*
            },
            $($tail:tt)*
        ) => {
            define_config_directive! {
                @parse
                {
                    $($eout)*
                    $rname { $( $args: String, )* $( $oargs: Option<String>, )* },
                },
                ($pargs){
                    $($pout)*
                    $sname => {
                        // This trickery is because macros can't count with
                        // regular integers.  We'll just use a mutable index
                        // instead.
                        let mut i = 0;
                        $(let $args = $pargs[i].into(); i += 1;)*
                        $(let $oargs = $pargs.get(i).map(|&s| s.into()); i += 1;)*
                        let _ = i; // avoid unused assignment warnings.
    
                        ConfigDirective::$rname {
                            $($args: $args,)*
                            $($oargs: $oargs,)*
                        }
                    },
                };
                $($tail)*
            }
        };
    }
    
    define_config_directive! {
        {command: "command1", rust_name: CommandOne, args: [arg1], optional_args: []},    
        {command: "other_command", rust_name: OtherCommand, args: [arg1], optional_args: [optional_arg1]},
    }
    
    fn main() {
        println!("{:?}", parse_line("command1", &["foo"]));
        println!("{:?}", parse_line("other_command", &["foo"]));
        println!("{:?}", parse_line("other_command", &["foo", "bar"]));
    }