spatialos-codegen 0.2.2

Codegen tool used with spatialos-macro and spatialos-sdk
Documentation
use crate::{
    ast::{Enum, Member, Type},
    parser::{
        member::parse_member,
        r#enum::parse_enum,
        utils::{camel_case as parse_type_name, parse_comments, ws0, ws1},
    },
};

use nom::{
    branch::alt,
    bytes::complete::tag,
    character::complete::char,
    combinator::{map, map_res},
    multi::many0,
    sequence::{delimited, preceded, terminated, tuple},
    IResult,
};

#[derive(Debug)]
enum TypeProperty {
    Member(Member),
    Type(Type),
    Enum(Enum),
}

#[derive(Default)]
struct TypeBuilder {
    pub name: Option<String>,
    pub members: Vec<Member>,
    pub comments: Vec<String>,
    pub types: Vec<Type>,
    pub enums: Vec<Enum>,
}

impl TypeBuilder {
    pub fn with_property(mut self, property: TypeProperty) -> Self {
        match property {
            TypeProperty::Member(m) => self.members.push(m),
            TypeProperty::Enum(e) => self.enums.push(e),
            TypeProperty::Type(t) => self.types.push(t),
        };
        self
    }

    pub fn with_name(mut self, name: String) -> Self {
        self.name = Some(name);
        self
    }

    pub fn with_comments(mut self, comments: Vec<String>) -> Self {
        self.comments = comments;
        self
    }

    pub fn build(self) -> Result<Type, &'static str> {
        let name = self.name.ok_or("Name could not be found")?;
        Ok(Type {
            name,
            members: self.members,
            comments: self.comments,
            types: self.types,
            enums: self.enums,
        })
    }
}

fn parse_property(input: &[u8]) -> IResult<&[u8], TypeProperty> {
    ws0(alt((
        terminated(ws0(map(parse_member, TypeProperty::Member)), char(';')),
        map(parse_type, TypeProperty::Type),
        map(parse_enum, TypeProperty::Enum),
    )))(input)
}

fn parse_properties(input: &[u8]) -> IResult<&[u8], Vec<TypeProperty>> {
    many0(ws0(parse_property))(input)
}

fn parse_type_body(input: &[u8]) -> IResult<&[u8], Vec<TypeProperty>> {
    delimited(char('{'), ws0(parse_properties), char('}'))(input)
}

pub fn parse_type(input: &[u8]) -> IResult<&[u8], Type> {
    map_res(
        map(
            ws0(tuple((
                parse_comments,
                preceded(tag("type"), ws1(parse_type_name)),
                parse_type_body,
            ))),
            |(comments, name, properties)| {
                properties
                    .into_iter()
                    .fold(TypeBuilder::default(), |acc, val| acc.with_property(val))
                    .with_name(name)
                    .with_comments(comments)
            },
        ),
        |tb| tb.build(),
    )(input)
}

#[cfg(test)]
mod tests {

    use crate::ast::{UserDefinedType, Variant};

    use super::*;

    const EMPTY_TYPE: &str = "type AnimalCounter { }";

    const SIMPLE_TYPE: &str = "
        type AnimalCounter {
            uint32 rabbits = 1;
            double platypus = 2;
        }";

    const COMMENTED_TYPE: &str = "
        // This is used to count animals
        type AnimalCounter {
            // This is used to count rabbits
            uint32 rabbits = 1 ;
            // This is used to count platypus
            double platypus = 2;
        }";

    const NESTED_TYPE: &str = "
        type Rabbit {

            type LifeState {
                uint32 health = 1;
            }

            enum Gender {
                MALE = 1;
                FEMALE = 2;
            }
            
            string name = 1;
            LifeState life_state = 2;
            Gender gender = 3;
        }";

    #[test]
    fn test_parse_type() {
        assert_eq!(
            parse_type(EMPTY_TYPE.as_bytes()),
            Ok((
                &b""[..],
                Type {
                    comments: Vec::new(),
                    name: "AnimalCounter".to_string(),
                    members: vec![],
                    types: vec![],
                    enums: vec![]
                }
            ))
        );
        assert_eq!(
            parse_type(SIMPLE_TYPE.as_bytes()),
            Ok((
                &b""[..],
                Type {
                    comments: Vec::new(),
                    name: "AnimalCounter".to_string(),
                    members: vec![
                        Member {
                            m_type: crate::ast::DataType::Uint32,
                            name: "rabbits".to_owned(),
                            id: 1,
                            comments: vec![]
                        },
                        Member {
                            m_type: crate::ast::DataType::Double,
                            name: "platypus".to_owned(),
                            id: 2,
                            comments: vec![]
                        },
                    ],
                    types: vec![],
                    enums: vec![]
                }
            ))
        );
        assert_eq!(
            parse_type(COMMENTED_TYPE.as_bytes()),
            Ok((
                &b""[..],
                Type {
                    comments: vec![" This is used to count animals".to_owned()],
                    name: "AnimalCounter".to_string(),
                    members: vec![
                        Member {
                            m_type: crate::ast::DataType::Uint32,
                            name: "rabbits".to_owned(),
                            id: 1,
                            comments: vec![" This is used to count rabbits".to_owned()]
                        },
                        Member {
                            m_type: crate::ast::DataType::Double,
                            name: "platypus".to_owned(),
                            id: 2,
                            comments: vec![" This is used to count platypus".to_owned()]
                        },
                    ],
                    types: vec![],
                    enums: vec![]
                }
            ))
        );
        assert_eq!(
            parse_type(NESTED_TYPE.as_bytes()),
            Ok((
                &b""[..],
                Type {
                    comments: vec![],
                    name: "Rabbit".to_string(),
                    members: vec![
                        Member {
                            m_type: crate::ast::DataType::String,
                            name: "name".to_owned(),
                            id: 1,
                            comments: vec![]
                        },
                        Member {
                            m_type: crate::ast::DataType::UserDefined(UserDefinedType::Unresolved(
                                "LifeState".to_owned()
                            )),
                            name: "life_state".to_owned(),
                            id: 2,
                            comments: vec![]
                        },
                        Member {
                            m_type: crate::ast::DataType::UserDefined(UserDefinedType::Unresolved(
                                "Gender".to_owned()
                            )),
                            name: "gender".to_owned(),
                            id: 3,
                            comments: vec![]
                        },
                    ],
                    types: vec![Type {
                        comments: vec![],
                        name: "LifeState".to_owned(),
                        members: vec![Member {
                            m_type: crate::ast::DataType::Uint32,
                            name: "health".to_owned(),
                            id: 1,
                            comments: vec![],
                        },],
                        types: vec![],
                        enums: vec![]
                    }],
                    enums: vec![Enum {
                        comments: vec![],
                        name: "Gender".to_owned(),
                        variants: vec![
                            Variant {
                                comments: vec![],
                                name: "MALE".to_owned(),
                                id: 1
                            },
                            Variant {
                                comments: vec![],
                                name: "FEMALE".to_owned(),
                                id: 2
                            }
                        ]
                    }]
                }
            ))
        );
    }
}