moonlander_gp/
impl_astnode.rs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
/// Implement AstNode and RandNode trait for a node type.
///
/// Implementing this code for the various variants of an enum
/// is a bit of a bother and quite repetitive, so this macro
/// implements them for you.
///
/// # Example
///
/// ```
/// # #[macro_use] extern crate moonlander_gp;
/// # extern crate rand;
///
/// #[derive(Clone,PartialEq,Eq,Debug)]
/// enum Tree {
///     Leaf(i32),
///     Node(Box<Tree>, Box<Tree>)
/// }
///
/// // Notice sub-trees are indicated by a name, but data fields are
/// // indicated by (data name).
///
/// impl_astnode!(Tree, 666,
///               leaf Leaf((data value |rng: &mut ::rand::Rng| (rng.next_u32() % 100) as i32)),
///               int Node(left, right));
///
/// # fn main() { }
/// ```
#[macro_export]
macro_rules! impl_astnode {
    (@asref $i:ident) => { $i.as_ref() };
    // Field matchers for returning children
    (@retcap (data $i:ident $($gen:expr)*)) => { _ };
    (@retcap $i:ident) => { ref $i };

    // Make vector for all non-data fields
    (@mkvec () ($($acc:expr)*)) => { vec![$($acc),*] };
    (@mkvec ((data $field:ident $($gen:expr)*) $($fields:tt)*) ($($acc:tt)*)) => {
        impl_astnode!(@mkvec ($($fields)*) ($($acc)*))
    };
    (@mkvec ($field:ident $($fields:tt)*) ($($acc:tt)*)) => {
        impl_astnode!(@mkvec ($($fields)*) ($($acc)* impl_astnode!(@asref $field)))
    };

    // Matching pattern for returning children, for enum variants with and without parameters
    (@retpat $enum_name:ident $case_name:ident ()) => { $enum_name::$case_name };
    (@retpat $enum_name:ident $case_name:ident ($($fields:tt),+)) => { $enum_name::$case_name($( impl_astnode!(@retcap $fields) ),+) };
    (@retcrea $enum_name:ident $case_name:ident ($($fields:tt),*)) => { impl_astnode!(@mkvec ($($fields)*) ()) };

    // Field matchers for replacing children
    (@repcap (data $i:ident $($gen:expr)*)) => { ref $i };
    (@repcap $i:ident) => { ref $i };
    (@repret $old_child:ident $new_child:ident (data $i:ident $($gen:expr)*)) => { $i.clone() };
    (@repret $old_child:ident $new_child:ident $i:ident) => { $crate::clone_or_replace($i, $old_child, $new_child) };

    // Matching pattern for replacing children, for enum variants with and without parameters
    (@reppat $enum_name:ident $case_name:ident ()) => { $enum_name::$case_name };
    (@reppat $enum_name:ident $case_name:ident ($($fields:tt),+)) => { $enum_name::$case_name($( impl_astnode!(@repcap $fields) ),+) };

    // Constructor call, for enum variants with and without parameters
    (@repcrea $old_child:ident $new_child:ident $enum_name:ident $case_name:ident ()) => { $enum_name::$case_name };
    (@repcrea $old_child:ident $new_child:ident $enum_name:ident $case_name:ident ($($fields:tt),+)) => { $enum_name::$case_name($( impl_astnode!(@repret $old_child $new_child $fields) ),+) };

    // Constructor call, for random variants with and without parameters
    (@randcrea $weights:ident $rng:ident $enum_name:ident $case_name:ident ()) => { $enum_name::$case_name };
    (@randcrea $weights:ident $rng:ident $enum_name:ident $case_name:ident ($($fields:tt),+)) => {
        $enum_name::$case_name($( impl_astnode!(@randchild $weights $rng $fields) ),+)
    };

    // Details for RandNode implementation
    (@callgen $rng:ident) => { "You should pass a random-generating function to a 'data' field" };
    (@callgen $rng:ident $gen:expr) => { $gen($rng) };
    (@randchild $weights:ident $rng:ident (data $field:ident $($gen:expr)*)) => { impl_astnode!(@callgen $rng $($gen)*) };
    (@randchild $weights:ident $rng:ident $field:ident) => { $weights.gen_child($rng) };
    (@weight leaf $weights:expr) => { $weights.leaf() };
    (@weight int $weights:expr) => { $weights.internal() };

    // Entry point
    ($enum_name:ident, $type_id:expr, $( $case_type:ident $case_name:ident ($($fields:tt),*) ),* ) => {
        impl $crate::AstNode for $enum_name {
            fn node_type(&self) -> usize { $type_id }

            fn children(&self) -> Vec<&$crate::AstNode> {
                match *self {
                    $(
                        impl_astnode!(@retpat $enum_name $case_name($($fields),*))
                            =>
                        impl_astnode!(@retcrea $enum_name $case_name($($fields),*))
                    ),*
                }
            }

            fn replace_child(&self, _old_child: &$crate::AstNode, _new_child: &mut Option<Box<$crate::AstNode>>) -> Box<$crate::AstNode> {
                Box::new(match *self {
                    $(
                        impl_astnode!(@reppat $enum_name $case_name($($fields),*))
                            =>
                        impl_astnode!(@repcrea _old_child _new_child $enum_name $case_name($($fields),*))
                    ),*
                })
            }
        }

        impl $crate::RandNode for $enum_name {
            fn rand(weights: $crate::NodeWeights, rng: &mut ::rand::Rng) -> $enum_name {
                pick![rng,
                    $(
                        impl_astnode!(@weight $case_type weights),
                        impl_astnode!(@randcrea weights rng $enum_name $case_name( $( $fields ),* ))
                    ),*
                    ]
            }
        }
    };
}

#[cfg(test)]
mod tests {
    use super::super::ast::*;

    #[derive(Clone,PartialEq,Eq,Debug)]
    enum Tree {
        Leaf(i32),
        Node(Box<Tree>, Box<Tree>)
    }

    impl_astnode!(Tree, 666,
                  leaf Leaf((data d |rng: &mut ::rand::Rng| (rng.next_u32() % 100) as i32)),
                  int Node(left, right));

    #[test]
    fn test_children() {
        let node = Tree::Node(Box::new(Tree::Leaf(1)), Box::new(Tree::Leaf(2)));
        assert_eq!(2, node.children().len());
    }

    #[test]
    fn copy_data() {
        let node = Tree::Leaf(1);

        // This will not actually replace anything, but test data propagation
        let mut replacement : Option<Box<AstNode>> = Some(Box::new(Tree::Leaf(2)));
        let new_node = node.replace_child(&node, &mut replacement).downcast::<Tree>().ok().unwrap();
        assert_eq!(node, *new_node);
    }

}