spatialos-codegen 0.2.2

Codegen tool used with spatialos-macro and spatialos-sdk
Documentation
use crate::{
    ast::{Command, Component, Enum, Event, Member, Type},
    parser::{
        command::parse_command,
        event::parse_event,
        member::parse_member,
        r#enum::parse_enum,
        r#type::parse_type,
        utils::camel_case as parse_component_name,
        utils::{parse_comments, parse_u32, ws0, ws1},
    },
};

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

#[derive(Debug)]
enum ComponentProperty {
    ID(u32),
    Member(Member),
    Command(Command),
    Event(Event),
    Type(Type),
    Enum(Enum),
}

#[derive(Default)]
struct ComponentBuilder {
    pub name: Option<String>,
    pub id: Option<u32>,
    pub members: Vec<Member>,
    pub commands: Vec<Command>,
    pub events: Vec<Event>,
    pub comments: Vec<String>,
    pub types: Vec<Type>,
    pub enums: Vec<Enum>,
}

impl ComponentBuilder {
    pub fn with_property(mut self, property: ComponentProperty) -> Self {
        match property {
            ComponentProperty::ID(id) => self.id = Some(id),
            ComponentProperty::Member(m) => self.members.push(m),
            ComponentProperty::Event(e) => self.events.push(e),
            ComponentProperty::Command(c) => self.commands.push(c),
            ComponentProperty::Type(t) => self.types.push(t),
            ComponentProperty::Enum(e) => self.enums.push(e),
        };
        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<Component, &'static str> {
        let name = self.name.ok_or("Name could not be found")?;
        let id = self.id.ok_or("ID could not be found")?;
        Ok(Component {
            name,
            id,
            members: self.members,
            commands: self.commands,
            events: self.events,
            comments: self.comments,
            enums: self.enums,
            types: self.types,
        })
    }
}

fn parse_id(input: &[u8]) -> IResult<&[u8], u32> {
    preceded(tag("id"), preceded(ws0(char('=')), parse_u32))(input)
}

fn parse_direct_property(input: &[u8]) -> IResult<&[u8], ComponentProperty> {
    terminated(
        ws0(alt((
            map(parse_id, ComponentProperty::ID),
            map(parse_member, ComponentProperty::Member),
            map(parse_command, ComponentProperty::Command),
            map(parse_event, ComponentProperty::Event),
        ))),
        char(';'),
    )(input)
}

fn parse_property(input: &[u8]) -> IResult<&[u8], ComponentProperty> {
    ws0(alt((
        parse_direct_property,
        map(parse_type, ComponentProperty::Type),
        map(parse_enum, ComponentProperty::Enum),
    )))(input)
}

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

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

pub fn parse_component(input: &[u8]) -> IResult<&[u8], Component> {
    map_res(
        map(
            tuple((
                ws0(parse_comments),
                preceded(tag("component"), ws1(parse_component_name)),
                parse_component_body,
            )),
            |(comments, name, properties)| {
                properties
                    .into_iter()
                    .fold(ComponentBuilder::default(), |acc, val| {
                        acc.with_property(val)
                    })
                    .with_name(name)
                    .with_comments(comments)
            },
        ),
        |builder| builder.build(),
    )(input)
}

#[cfg(test)]
mod tests {

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

    use super::*;

    const SIMPLE_COMPONENT: &str = "
        component AnimalCounter {
            id = 1001;
            uint32 rabbits = 1;
            double platypus = 2;
            command uint32 count_platypus(Field);
            event Rabbit new_rabbit;
        }";

    const COMMENTED_COMPONENT: &str = "
        // This is used to count animals
        component AnimalCounter {
            id = 1001;
            
            // This is used to count rabbits
            uint32 rabbits = 1;
            
            // This is used to count platypus
            double platypus = 2;

            command uint32 count_platypus(Field);
            event Rabbit new_rabbit;
        }";

    const NESTED_COMPONENT: &str = "
        // This is used to count animals
        component AnimalCounter {
            id = 1001;
            
            // This is used to count rabbits
            uint32 rabbits = 1;
            
            // This is used to count platypus
            double platypus = 2;

            enum LifeState {
                ALIVE = 0;
                DEAD = 1;
            }

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

            }

            command uint32 count_platypus(Field);
            event Rabbit new_rabbit;
        }";

    #[test]
    fn test_parse_component() {
        assert_eq!(
            parse_component(SIMPLE_COMPONENT.as_bytes()),
            Ok((
                &b""[..],
                Component {
                    id: 1001,
                    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![]
                        },
                    ],
                    events: vec![Event {
                        name: "new_rabbit".to_owned(),
                        r_type: crate::ast::DataType::UserDefined(UserDefinedType::Unresolved(
                            "Rabbit".to_owned()
                        ))
                    }],
                    commands: vec![Command {
                        name: "count_platypus".to_owned(),
                        r_type: crate::ast::DataType::Uint32,
                        args: vec![crate::ast::DataType::UserDefined(
                            UserDefinedType::Unresolved("Field".to_owned())
                        )]
                    }],
                    enums: vec![],
                    types: vec![]
                }
            ))
        );
        assert_eq!(
            parse_component(COMMENTED_COMPONENT.as_bytes()),
            Ok((
                &b""[..],
                Component {
                    id: 1001,
                    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()]
                        },
                    ],
                    events: vec![Event {
                        name: "new_rabbit".to_owned(),
                        r_type: crate::ast::DataType::UserDefined(UserDefinedType::Unresolved(
                            "Rabbit".to_owned()
                        ))
                    }],
                    commands: vec![Command {
                        name: "count_platypus".to_owned(),
                        r_type: crate::ast::DataType::Uint32,
                        args: vec![crate::ast::DataType::UserDefined(
                            UserDefinedType::Unresolved("Field".to_owned())
                        )]
                    }],
                    enums: vec![],
                    types: vec![]
                }
            ))
        );
        assert_eq!(
            parse_component(NESTED_COMPONENT.as_bytes()),
            Ok((
                &b""[..],
                Component {
                    comments: vec![" This is used to count animals".to_owned()],
                    name: "AnimalCounter".to_string(),
                    id: 1001,
                    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()]
                        },
                    ],
                    events: vec![Event {
                        name: "new_rabbit".to_owned(),
                        r_type: crate::ast::DataType::UserDefined(UserDefinedType::Unresolved(
                            "Rabbit".to_owned()
                        ))
                    }],
                    commands: vec![Command {
                        name: "count_platypus".to_owned(),
                        r_type: crate::ast::DataType::Uint32,
                        args: vec![crate::ast::DataType::UserDefined(
                            UserDefinedType::Unresolved("Field".to_owned())
                        )]
                    }],
                    enums: vec![Enum {
                        comments: vec![],
                        name: "LifeState".to_owned(),
                        variants: vec![
                            Variant {
                                comments: vec![],
                                name: "ALIVE".to_owned(),
                                id: 0
                            },
                            Variant {
                                comments: vec![],
                                name: "DEAD".to_owned(),
                                id: 1
                            }
                        ]
                    }],
                    types: vec![Type {
                        comments: vec![],
                        name: "Rabbit".to_owned(),
                        members: vec![
                            Member {
                                comments: vec![],
                                m_type: crate::ast::DataType::String,
                                name: "name".to_owned(),
                                id: 1
                            },
                            Member {
                                comments: vec![],
                                m_type: crate::ast::DataType::UserDefined(
                                    UserDefinedType::Unresolved("LifeState".to_owned())
                                ),
                                name: "life_state".to_owned(),
                                id: 2
                            },
                            Member {
                                comments: vec![],
                                m_type: crate::ast::DataType::UserDefined(
                                    UserDefinedType::Unresolved("Gender".to_owned())
                                ),
                                name: "gender".to_owned(),
                                id: 3
                            }
                        ],
                        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
                                }
                            ]
                        }],
                        types: vec![]
                    }]
                }
            ))
        );
    }
}