如何使用生锈中的宏生成复杂的枚举变体

时间:2017-05-24 14:04:16

标签: macros rust

我正在编写一个用于解析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),
    }
}

我喜欢这种结构但是有很多可能的命令(在280的区域内)。所以我想编写一个宏来生成大部分样板文件。理想情况下,我会写以下内容:

define_config_directive!{
    {command => "command1", rust_name => CommandOne, args => [arg1], optional_args => []},    
    {command => "other_command", rust_name => OtherCommand, args => [arg1], optional_args => [optional_arg1]},
}

我能够到目前为止最接近的是:

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>),*,
            }),*
        }
    };
}

所以我有一些问题:

  1. 我不知道如何在这个宏中实现parse_line函数,我需要迭代每个必需的参数,以便编写一些代码来拉出相应的参数,并且相同的可选参数
  2. 我不知道如何处理根本没有参数的情况,理想情况下这是一个没有字段的简单枚举变体。
  3. 有没有人知道是否有办法在稳定的生锈中解决这个问题?或者我应该使用python脚本生成代码吗?

1 个答案:

答案 0 :(得分:3)

这是一个有点病态的案例。首先,您希望以不同方式处理输入的部分,哪些宏不擅长。更糟糕的是,您希望与生成枚举变体一起执行此操作,这些宏也很糟糕。总而言之,我只能看到一种方法:完全按下一代。

简短版本是:将其分解为简单的匹配步骤,其中每个步骤处理一件事,并将该事物的输出添加到累加器(在这种情况下,$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"]));
}

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