Created
August 28, 2024 20:00
-
-
Save omaskery/42a67ebca36b5ad73897355b2c3ac1dc to your computer and use it in GitHub Desktop.
Bevy BSN parse test to learn the nom crate
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| use nom::{InputLength, IResult, Parser}; | |
| use nom::branch::alt; | |
| use nom::bytes::complete::escaped; | |
| use nom::character::complete::{alpha1, alphanumeric1, anychar, digit1, multispace0, one_of}; | |
| use nom::combinator::{cut, map, not, opt, peek, recognize}; | |
| use nom::error::{context, ContextError, ParseError}; | |
| use nom::multi::{many0, many0_count, separated_list0}; | |
| use nom::number::complete::double; | |
| use nom::sequence::{delimited, pair, preceded, terminated, tuple}; | |
| use nom_locate::LocatedSpan; | |
| use nom_supreme::error::ErrorTree; | |
| use nom_supreme::tag::complete::tag; | |
| fn main() { | |
| println!("Hello, world!"); | |
| } | |
| #[derive(Debug, PartialEq, Clone)] | |
| pub struct Ast { | |
| root: Entity, | |
| } | |
| #[derive(Debug, PartialEq, Clone)] | |
| pub enum LiteralExpression { | |
| Character(char), | |
| String(String), | |
| Integer(isize), | |
| Float(f64), | |
| Boolean(bool), | |
| } | |
| #[derive(Debug, PartialEq, Clone)] | |
| pub struct PathExpressionSegment(String); | |
| #[derive(Debug, PartialEq, Clone)] | |
| pub struct QualifiedPathType(String); | |
| #[derive(Debug, PartialEq, Clone)] | |
| pub struct PathExpression { | |
| qualified: Option<QualifiedPathType>, | |
| segments: Vec<PathExpressionSegment>, | |
| } | |
| #[derive(Debug, PartialEq, Clone)] | |
| pub enum ArrayExpression { | |
| Literal(Vec<Expression>), | |
| Repeat(Box<Expression>, Box<Expression>), | |
| } | |
| #[derive(Debug, PartialEq, Clone)] | |
| pub struct TupleExpression(Vec<Expression>); | |
| #[derive(Debug, PartialEq, Clone)] | |
| pub enum StructExpressionField { | |
| ByName(String, Box<Expression>), | |
| ByIndex(usize, Box<Expression>), | |
| } | |
| #[derive(Debug, PartialEq, Clone)] | |
| pub enum StructExpression { | |
| Struct(PathExpression, Vec<StructExpressionField>), | |
| Tuple(PathExpression, Vec<Expression>), | |
| Unit(PathExpression), | |
| } | |
| #[derive(Debug, PartialEq, Clone)] | |
| pub enum Expression { | |
| Literal(LiteralExpression), | |
| Path(PathExpression), | |
| Array(ArrayExpression), | |
| Tuple(TupleExpression), | |
| Struct(StructExpression), | |
| ConstructProp(Box<Expression>), | |
| } | |
| #[derive(Debug, PartialEq, Clone)] | |
| pub struct ComponentField { | |
| name: String, | |
| value: Expression, | |
| } | |
| #[derive(Debug, PartialEq, Clone)] | |
| pub struct TypeName { | |
| name: String, | |
| } | |
| #[derive(Debug, PartialEq, Clone)] | |
| pub struct Component { | |
| type_name: TypeName, | |
| fields: Vec<ComponentField>, | |
| } | |
| #[derive(Debug, PartialEq, Clone)] | |
| pub struct Entity { | |
| components: Vec<Component>, | |
| inherits: Vec<String>, | |
| children: Vec<Entity>, | |
| } | |
| type ParseResult<I, O, E = I> = IResult<I, O, ErrorTree<E>>; | |
| type Span<'a> = LocatedSpan<&'a str>; | |
| fn success_default<I, O, E>(input: I) -> IResult<I, O, E> | |
| where | |
| O: Default, | |
| E: ParseError<I>, | |
| { | |
| Ok((input, O::default())) | |
| } | |
| /// A combinator that takes a parser `inner` and produces a parser that also consumes both leading and | |
| /// trailing whitespace, returning the output of `inner`. | |
| pub fn ws<'a, O, E: ParseError<Span<'a>> + ContextError<Span<'a>>, F>( | |
| inner: F, | |
| ) -> impl FnMut(Span<'a>) -> IResult<Span<'a>, O, E> | |
| where | |
| F: Parser<Span<'a>, O, E>, | |
| { | |
| delimited(multispace0, inner, multispace0) | |
| } | |
| macro_rules! define_tag { | |
| ($func:ident, $tag:literal) => { | |
| fn $func(input: Span) -> ParseResult<Span, Span> { | |
| ws(context(stringify!($func), tag($tag)))(input) | |
| } | |
| } | |
| } | |
| define_tag!(component_tuple_start, "("); | |
| define_tag!(component_separator, ","); | |
| define_tag!(component_tuple_end, ")"); | |
| fn identifier(input: Span) -> ParseResult<Span, Span> { | |
| ws(context("identifier", recognize( | |
| pair( | |
| alt((alpha1, tag("_"))), | |
| many0_count(alt((alphanumeric1, tag("_")))), | |
| ) | |
| )))(input) | |
| } | |
| pub fn separated_list0_trailing_allowed<I, O, O2, E, F, S>( | |
| sep: S, | |
| f: F, | |
| ) -> impl FnMut(I) -> IResult<I, Vec<O>, E> | |
| where | |
| I: Clone + InputLength, | |
| F: Parser<I, O, E>, | |
| S: Parser<I, O2, E> + Clone, | |
| E: ParseError<I> + ContextError<I>, | |
| { | |
| context( | |
| "separated_list0_trailing_allowed", | |
| terminated(separated_list0(sep.clone(), f), opt(sep)), | |
| ) | |
| } | |
| fn type_name(input: Span) -> ParseResult<Span, TypeName> { | |
| let (input, name) = context("type_name", identifier)(input)?; | |
| Ok((input, TypeName { | |
| name: name.fragment().to_string(), | |
| })) | |
| } | |
| fn component_type_name(input: Span) -> ParseResult<Span, TypeName> { | |
| context("component_type_name", type_name)(input) | |
| } | |
| define_tag!(component_body_start, "{"); | |
| define_tag!(component_body_end, "}"); | |
| define_tag!(component_field_separator, ","); | |
| fn field_name(input: Span) -> ParseResult<Span, Span> { | |
| context("field_name", identifier)(input) | |
| } | |
| define_tag!(field_value_separator, ":"); | |
| fn string_literal_expr(input: Span) -> ParseResult<Span, Span> { | |
| let (input, _) = tag("\"")(input)?; | |
| let (input, string) = escaped( | |
| tuple((not(tag("\"")), anychar)), | |
| '\\', | |
| one_of("\\arntf\"") | |
| )(input)?; | |
| let (input, _) = cut(tag("\""))(input)?; | |
| Ok((input, string)) | |
| } | |
| fn literal_expr(input: Span) -> ParseResult<Span, LiteralExpression> { | |
| let char_literal_expr = ws(delimited( | |
| tag("'"), | |
| cut(recognize(escaped(alphanumeric1, '\\', one_of("\\arntf\'")))), | |
| cut(tag("'")), | |
| )); | |
| let boolean_literal_expr = ws(alt(( | |
| map(tag("true"), |_| true), | |
| map(tag("false"), |_| false), | |
| ))); | |
| let number_literal_expr = ws(recognize(double)); | |
| context("literal", alt(( | |
| map(char_literal_expr, |x| LiteralExpression::Character(x.fragment().chars().nth(0).unwrap())), | |
| map(string_literal_expr, |x| LiteralExpression::String(x.fragment().to_string())), | |
| map(boolean_literal_expr, |x| LiteralExpression::Boolean(x)), | |
| map(number_literal_expr, |x| { | |
| // TODO: do better than this | |
| if x.contains('.') { | |
| LiteralExpression::Float(x.fragment().parse::<f64>().unwrap()) | |
| } else { | |
| LiteralExpression::Integer(x.fragment().parse::<isize>().unwrap()) | |
| } | |
| }), | |
| )))(input) | |
| } | |
| define_tag!(path_segment_expr_separator, "::"); | |
| fn path_segment_expr(input: Span) -> ParseResult<Span, PathExpressionSegment> { | |
| context( | |
| "path_segment_expr", | |
| map(identifier, |x| PathExpressionSegment(x.fragment().to_string())), | |
| )(input) | |
| } | |
| fn path_expr(input: Span) -> ParseResult<Span, PathExpression> { | |
| fn _impl(input: Span) -> ParseResult<Span, PathExpression> { | |
| let (input, _) = ws(opt(path_segment_expr_separator))(input)?; | |
| let (input, first_segment) = path_segment_expr(input)?; | |
| let (input, additional_segments) = many0(preceded( | |
| path_segment_expr_separator, | |
| path_segment_expr, | |
| ))(input)?; | |
| let segments = [first_segment].into_iter() | |
| .chain(additional_segments.into_iter()) | |
| .collect(); | |
| Ok((input, PathExpression { | |
| qualified: None, // TODO | |
| segments, | |
| })) | |
| } | |
| context("path_expr", _impl)(input) | |
| } | |
| define_tag!(array_start, "["); | |
| define_tag!(array_end, "]"); | |
| define_tag!(array_separator, ","); | |
| define_tag!(array_repeat_separator, ";"); | |
| fn array_expr(input: Span) -> ParseResult<Span, ArrayExpression> { | |
| let normal_array = context( | |
| "normal_array", | |
| map(delimited( | |
| array_start, | |
| cut(separated_list0_trailing_allowed(array_separator, expression)), | |
| cut(array_end), | |
| ), |values| ArrayExpression::Literal(values)), | |
| ); | |
| let repeat_array = context( | |
| "repeat_array", | |
| map(tuple(( | |
| array_start, | |
| cut(expression), | |
| cut(array_repeat_separator), | |
| cut(expression), | |
| cut(array_end), | |
| )), |(_, repeat, _, length, _)| ArrayExpression::Repeat(Box::new(repeat), Box::new(length))), | |
| ); | |
| context("array_expr", alt((normal_array, repeat_array)))(input) | |
| } | |
| define_tag!(tuple_start, "("); | |
| define_tag!(tuple_separator, ","); | |
| define_tag!(tuple_end, ")"); | |
| fn tuple_expr(input: Span) -> ParseResult<Span, TupleExpression> { | |
| context( | |
| "tuple_expr", | |
| map(delimited( | |
| tuple_start, | |
| cut(separated_list0_trailing_allowed(tuple_separator, expression)), | |
| cut(tuple_end), | |
| ), |values| TupleExpression(values)), | |
| )(input) | |
| } | |
| define_tag!(struct_start, "{"); | |
| define_tag!(struct_field_separator, ","); | |
| define_tag!(struct_end, "}"); | |
| define_tag!(struct_tuple_start, "("); | |
| define_tag!(struct_tuple_separator, ","); | |
| define_tag!(struct_tuple_end, ")"); | |
| fn struct_field(input: Span) -> ParseResult<Span, StructExpressionField> { | |
| let by_name = map(tuple(( | |
| identifier, | |
| field_value_separator, | |
| expression, | |
| )), |(name, _, value)| StructExpressionField::ByName(name.fragment().to_string(), Box::new(value))); | |
| let by_index = map(tuple(( | |
| map(ws(digit1), |x| x.fragment().parse::<usize>().unwrap()), | |
| field_value_separator, | |
| expression, | |
| )), |(index, _, value)| StructExpressionField::ByIndex(index, Box::new(value))); | |
| context("struct_field", alt(( | |
| context("by_name", by_name), | |
| context("by_index", by_index), | |
| )))(input) | |
| } | |
| fn struct_expr(input: Span) -> ParseResult<Span, StructExpression> { | |
| let struct_struct = map( | |
| tuple(( | |
| path_expr, | |
| struct_start, | |
| separated_list0_trailing_allowed(struct_field_separator, struct_field), | |
| struct_end, | |
| )), | |
| |(path, _, fields, _)| StructExpression::Struct(path, fields), | |
| ); | |
| let tuple_struct = map( | |
| tuple(( | |
| path_expr, | |
| struct_tuple_start, | |
| separated_list0_trailing_allowed(struct_tuple_separator, expression), | |
| struct_tuple_end, | |
| )), | |
| |(path, _, values, _)| StructExpression::Tuple(path, values), | |
| ); | |
| let unit_struct = map(path_expr, |path| StructExpression::Unit(path)); | |
| context("struct_expr", alt(( | |
| context("struct_struct", struct_struct), | |
| context("tuple_struct", tuple_struct), | |
| context("unit_struct", unit_struct), | |
| )))(input) | |
| } | |
| define_tag!(construct_prop_sigil, "@"); | |
| fn construct_prop_expr(input: Span) -> ParseResult<Span, Expression> { | |
| context("construct_prop_expr", preceded(construct_prop_sigil, expression))(input) | |
| } | |
| fn expression(input: Span) -> ParseResult<Span, Expression> { | |
| context("expression", alt(( | |
| map(literal_expr, |x| Expression::Literal(x)), | |
| map(struct_expr, |x| Expression::Struct(x)), | |
| map(path_expr, |x| Expression::Path(x)), | |
| map(construct_prop_expr, |x| Expression::ConstructProp(Box::new(x))), | |
| map(array_expr, |x| Expression::Array(x)), | |
| map(tuple_expr, |x| Expression::Tuple(x)), | |
| )))(input) | |
| } | |
| fn component_field(input: Span) -> ParseResult<Span, ComponentField> { | |
| let (input, (name, _, value)) = context( | |
| "component_field", | |
| tuple((field_name, cut(field_value_separator), cut(expression))), | |
| )(input)?; | |
| Ok((input, ComponentField { | |
| name: name.fragment().to_string(), | |
| value, | |
| })) | |
| } | |
| fn component_body(input: Span) -> ParseResult<Span, Vec<ComponentField>> { | |
| context( | |
| "component_body", | |
| delimited( | |
| component_body_start, | |
| cut(context( | |
| "component_body list", | |
| separated_list0_trailing_allowed(component_field_separator, component_field), | |
| )), | |
| cut(component_body_end), | |
| ), | |
| )(input) | |
| } | |
| define_tag!(entity_children_start, "["); | |
| define_tag!(entity_children_separator, ","); | |
| define_tag!(entity_children_end, "]"); | |
| fn entity_children(input: Span) -> ParseResult<Span, Vec<Entity>> { | |
| context( | |
| "entity_children", | |
| delimited( | |
| entity_children_start, | |
| cut(context( | |
| "entity_children list", | |
| separated_list0_trailing_allowed(entity_children_separator, entity), | |
| )), | |
| cut(entity_children_end), | |
| ), | |
| )(input) | |
| } | |
| fn component(input: Span) -> ParseResult<Span, Component> { | |
| fn _impl(input: Span) -> ParseResult<Span, Component> { | |
| let (input, type_name) = component_type_name(input)?; | |
| let (input, fields) = parse_if( | |
| input, | |
| ws(component_body_start), | |
| cut(component_body), | |
| success_default, | |
| )?; | |
| Ok((input, Component { | |
| type_name, | |
| fields, | |
| })) | |
| } | |
| context("component", _impl)(input) | |
| } | |
| fn parse_if<I, C, OC, T, F, O, E>(input: I, cond: C, mut true_path: T, mut false_path: F) -> IResult<I, O, E> | |
| where | |
| I: Clone, | |
| C: Parser<I, OC, E>, | |
| T: Parser<I, O, E>, | |
| F: Parser<I, O, E>, | |
| E: ParseError<I>, | |
| { | |
| if let Ok(_) = peek(cond)(input.clone()) { | |
| true_path.parse(input) | |
| } else { | |
| false_path.parse(input) | |
| } | |
| } | |
| define_tag!(inherit_start, ":"); | |
| define_tag!(inherit_separator, ","); | |
| fn inheritance_name(input: Span) -> ParseResult<Span, String> { | |
| map(identifier, |i| i.fragment().to_string())(input) | |
| } | |
| fn component_inheritance(input: Span) -> ParseResult<Span, Vec<String>> { | |
| context( | |
| "component_inheritance", | |
| preceded( | |
| inherit_start, | |
| cut(separated_list0_trailing_allowed(inherit_separator, inheritance_name)), | |
| ) | |
| )(input) | |
| } | |
| fn entity(input: Span) -> ParseResult<Span, Entity> { | |
| struct ComponentData { | |
| components: Vec<Component>, | |
| inherits: Vec<String>, | |
| } | |
| fn component_tuple(input: Span) -> ParseResult<Span, ComponentData> { | |
| let (input, (components, inherits)) = context( | |
| "component_tuple", | |
| delimited( | |
| component_tuple_start, | |
| cut(context( | |
| "component_tuple list", | |
| tuple(( | |
| separated_list0_trailing_allowed(component_separator, component), | |
| opt(component_inheritance), | |
| )), | |
| )), | |
| cut(component_tuple_end), | |
| ), | |
| )(input)?; | |
| Ok((input, ComponentData { | |
| components, | |
| inherits: inherits.unwrap_or_default(), | |
| })) | |
| } | |
| fn component_single(input: Span) -> ParseResult<Span, ComponentData> { | |
| let (input, components) = context( | |
| "component_single", | |
| map(component, |c| vec![c]) | |
| )(input)?; | |
| Ok((input, ComponentData { | |
| components, | |
| inherits: vec![], | |
| })) | |
| } | |
| fn _impl(input: Span) -> ParseResult<Span, Entity> { | |
| let (input, components) = parse_if( | |
| input, | |
| ws(component_tuple_start), | |
| cut(component_tuple), | |
| component_single, | |
| )?; | |
| let (input, children) = parse_if( | |
| input, | |
| ws(entity_children_start), | |
| cut(entity_children), | |
| success_default, | |
| )?; | |
| Ok((input, Entity { | |
| components: components.components, | |
| inherits: components.inherits, | |
| children, | |
| })) | |
| } | |
| context("entity", _impl)(input) | |
| } | |
| fn parse_bsn(input: Span) -> ParseResult<Span, Ast> { | |
| let (input, node) = context("parse_bsn", entity)(input)?; | |
| return Ok((input, Ast { | |
| root: node, | |
| })); | |
| } | |
| #[cfg(test)] | |
| mod test { | |
| use super::*; | |
| fn test_fn(input: &str, expected: Ast) { | |
| let input = Span::new(input); | |
| let output = parse_bsn(input); | |
| match output { | |
| Ok((remainder, value)) => { | |
| let expected_str = format!("{:#?}", expected); | |
| let value_str = format!("{:#?}", value); | |
| assert_eq!(value_str, expected_str); | |
| assert_eq!(remainder.len(), 0); | |
| }, | |
| Err(nom::Err::Error(e) | nom::Err::Failure(e)) => { | |
| panic!("parse failed, details:\n{:#}", e); | |
| } | |
| Err(e) => { | |
| panic!("parse failed: {:?}", e); | |
| } | |
| } | |
| } | |
| #[test] | |
| fn test_node() { | |
| test_fn("Node", Ast { | |
| root: Entity { | |
| components: vec![Component { | |
| type_name: TypeName { name: "Node".into() }, | |
| fields: vec![], | |
| }], | |
| children: vec![], | |
| inherits: vec![], | |
| }, | |
| }); | |
| } | |
| #[test] | |
| fn test_node_style() { | |
| test_fn("(Node, Style)", Ast { | |
| root: Entity { | |
| components: vec![ | |
| Component { | |
| type_name: TypeName { name: "Node".into() }, | |
| fields: vec![], | |
| }, | |
| Component { | |
| type_name: TypeName { name: "Style".into() }, | |
| fields: vec![], | |
| }, | |
| ], | |
| children: vec![], | |
| inherits: vec![], | |
| }, | |
| }); | |
| } | |
| #[test] | |
| fn test_style_fields() { | |
| test_fn(r#" | |
| ( | |
| Node, | |
| Style { | |
| width: Val::Px(100.), | |
| height: Val::Px(100.), | |
| display: Display::Flex, | |
| }, | |
| ) | |
| "#, | |
| Ast { | |
| root: Entity { | |
| components: vec![ | |
| Component { | |
| type_name: TypeName { name: "Node".into() }, | |
| fields: vec![], | |
| }, | |
| Component { | |
| type_name: TypeName { name: "Style".into() }, | |
| fields: vec![ | |
| ComponentField { | |
| name: "width".into(), | |
| value: Expression::Struct(StructExpression::Tuple( | |
| PathExpression { | |
| qualified: None, | |
| segments: vec![ | |
| PathExpressionSegment("Val".into()), | |
| PathExpressionSegment("Px".into()), | |
| ], | |
| }, | |
| vec![ | |
| Expression::Literal(LiteralExpression::Float(100.)), | |
| ], | |
| )), | |
| }, | |
| ComponentField { | |
| name: "height".into(), | |
| value: Expression::Struct(StructExpression::Tuple( | |
| PathExpression { | |
| qualified: None, | |
| segments: vec![ | |
| PathExpressionSegment("Val".into()), | |
| PathExpressionSegment("Px".into()), | |
| ], | |
| }, | |
| vec![ | |
| Expression::Literal(LiteralExpression::Float(100.)), | |
| ], | |
| )), | |
| }, | |
| ComponentField { | |
| name: "display".into(), | |
| value: Expression::Struct(StructExpression::Unit(PathExpression { | |
| qualified: None, | |
| segments: vec![ | |
| PathExpressionSegment("Display".into()), | |
| PathExpressionSegment("Flex".into()), | |
| ], | |
| })), | |
| }, | |
| ], | |
| }, | |
| ], | |
| children: vec![], | |
| inherits: vec![], | |
| }, | |
| }); | |
| } | |
| #[test] | |
| fn test_player() { | |
| test_fn(r#" | |
| ( | |
| Player { | |
| config: @"path_to_config.ron", | |
| team: Team::Blue, | |
| }, | |
| Transform { | |
| translation: Vec3 { x: 10.0 }, | |
| }, | |
| ) | |
| "#, | |
| Ast { | |
| root: Entity { | |
| components: vec![ | |
| Component { | |
| type_name: TypeName { name: "Player".into() }, | |
| fields: vec![ | |
| ComponentField { | |
| name: "config".into(), | |
| value: Expression::ConstructProp(Box::new(Expression::Literal( | |
| LiteralExpression::String("path_to_config.ron".into())))), | |
| }, | |
| ComponentField { | |
| name: "team".into(), | |
| value: Expression::Struct(StructExpression::Unit(PathExpression { | |
| qualified: None, | |
| segments: vec![ | |
| PathExpressionSegment("Team".into()), | |
| PathExpressionSegment("Blue".into()), | |
| ], | |
| })), | |
| }, | |
| ], | |
| }, | |
| Component { | |
| type_name: TypeName { name: "Transform".into() }, | |
| fields: vec![ | |
| ComponentField { | |
| name: "translation".into(), | |
| value: Expression::Struct(StructExpression::Struct( | |
| PathExpression { | |
| qualified: None, | |
| segments: vec![ | |
| PathExpressionSegment("Vec3".into()), | |
| ], | |
| }, | |
| vec![ | |
| StructExpressionField::ByName( | |
| "x".into(), | |
| Box::new(Expression::Literal(LiteralExpression::Float(10.)))), | |
| ], | |
| )), | |
| }, | |
| ], | |
| }, | |
| ], | |
| children: vec![], | |
| inherits: vec![], | |
| }, | |
| }); | |
| } | |
| #[test] | |
| fn test_children() { | |
| test_fn(r#" | |
| Node [ | |
| (Node, Style { width: px(100.), height: px(100.), }, Other), | |
| MyWidget { color: rgb8(255, 10, 10), }, | |
| ] | |
| "#, | |
| Ast { | |
| root: Entity { | |
| components: vec![ | |
| Component { | |
| type_name: TypeName { name: "Node".into() }, | |
| fields: vec![], | |
| }, | |
| ], | |
| children: vec![ | |
| Entity { | |
| components: vec![ | |
| Component { | |
| type_name: TypeName { name: "Node".into() }, | |
| fields: vec![], | |
| }, | |
| Component { | |
| type_name: TypeName { name: "Style".into() }, | |
| fields: vec![ | |
| ComponentField { | |
| name: "width".into(), | |
| value: Expression::Struct(StructExpression::Tuple( | |
| PathExpression { | |
| qualified: None, | |
| segments: vec![ | |
| PathExpressionSegment("px".into()), | |
| ], | |
| }, | |
| vec![ | |
| Expression::Literal(LiteralExpression::Float(100.)), | |
| ], | |
| )), | |
| }, | |
| ComponentField { | |
| name: "height".into(), | |
| value: Expression::Struct(StructExpression::Tuple( | |
| PathExpression { | |
| qualified: None, | |
| segments: vec![ | |
| PathExpressionSegment("px".into()), | |
| ], | |
| }, | |
| vec![ | |
| Expression::Literal(LiteralExpression::Float(100.)), | |
| ], | |
| )), | |
| }, | |
| ], | |
| }, | |
| Component { | |
| type_name: TypeName { name: "Other".into() }, | |
| fields: vec![], | |
| }, | |
| ], | |
| children: vec![], | |
| inherits: vec![], | |
| }, | |
| Entity { | |
| components: vec![ | |
| Component { | |
| type_name: TypeName { name: "MyWidget".into() }, | |
| fields: vec![ | |
| ComponentField { | |
| name: "color".into(), | |
| value: Expression::Struct(StructExpression::Tuple( | |
| PathExpression { | |
| qualified: None, | |
| segments: vec![ | |
| PathExpressionSegment("rgb8".into()), | |
| ], | |
| }, | |
| vec![ | |
| Expression::Literal(LiteralExpression::Integer(255)), | |
| Expression::Literal(LiteralExpression::Integer(10)), | |
| Expression::Literal(LiteralExpression::Integer(10)), | |
| ], | |
| )), | |
| }, | |
| ], | |
| }, | |
| ], | |
| children: vec![], | |
| inherits: vec![], | |
| }, | |
| ], | |
| inherits: vec![], | |
| }, | |
| }); | |
| } | |
| #[test] | |
| fn test_inheritance() { | |
| test_fn("(Blue :player)", Ast { | |
| root: Entity { | |
| components: vec![ | |
| Component { | |
| type_name: TypeName { name: "Blue".into() }, | |
| fields: vec![], | |
| }, | |
| ], | |
| children: vec![], | |
| inherits: vec!["player".into()], | |
| }, | |
| }); | |
| } | |
| #[test] | |
| fn test_expressions() { | |
| test_fn( | |
| "Style { x: 1 }", Ast { | |
| root: Entity { | |
| components: vec![ | |
| Component { | |
| type_name: TypeName { name: "Style".into() }, | |
| fields: vec![ | |
| ComponentField { | |
| name: "x".into(), | |
| value: Expression::Literal(LiteralExpression::Integer(1)), | |
| }, | |
| ], | |
| }, | |
| ], | |
| children: vec![], | |
| inherits: vec![], | |
| }, | |
| }); | |
| test_fn("Style { x: 24.3 }", Ast { | |
| root: Entity { | |
| components: vec![ | |
| Component { | |
| type_name: TypeName { name: "Style".into() }, | |
| fields: vec![ | |
| ComponentField { | |
| name: "x".into(), | |
| value: Expression::Literal(LiteralExpression::Float(24.3)), | |
| }, | |
| ], | |
| }, | |
| ], | |
| children: vec![], | |
| inherits: vec![], | |
| }, | |
| }); | |
| test_fn("Style { x: 'a' }", Ast { | |
| root: Entity { | |
| components: vec![ | |
| Component { | |
| type_name: TypeName { name: "Style".into() }, | |
| fields: vec![ | |
| ComponentField { | |
| name: "x".into(), | |
| value: Expression::Literal(LiteralExpression::Character('a')), | |
| }, | |
| ], | |
| }, | |
| ], | |
| children: vec![], | |
| inherits: vec![], | |
| }, | |
| }); | |
| test_fn("Style { x: \"hello\" }", Ast { | |
| root: Entity { | |
| components: vec![ | |
| Component { | |
| type_name: TypeName { name: "Style".into() }, | |
| fields: vec![ | |
| ComponentField { | |
| name: "x".into(), | |
| value: Expression::Literal(LiteralExpression::String("hello".into())), | |
| }, | |
| ], | |
| }, | |
| ], | |
| children: vec![], | |
| inherits: vec![], | |
| }, | |
| }); | |
| test_fn("Style { x: true }", Ast { | |
| root: Entity { | |
| components: vec![ | |
| Component { | |
| type_name: TypeName { name: "Style".into() }, | |
| fields: vec![ | |
| ComponentField { | |
| name: "x".into(), | |
| value: Expression::Literal(LiteralExpression::Boolean(true)), | |
| }, | |
| ], | |
| }, | |
| ], | |
| children: vec![], | |
| inherits: vec![], | |
| }, | |
| }); | |
| test_fn("Style { x: false }", Ast { | |
| root: Entity { | |
| components: vec![ | |
| Component { | |
| type_name: TypeName { name: "Style".into() }, | |
| fields: vec![ | |
| ComponentField { | |
| name: "x".into(), | |
| value: Expression::Literal(LiteralExpression::Boolean(false)), | |
| }, | |
| ], | |
| }, | |
| ], | |
| children: vec![], | |
| inherits: vec![], | |
| }, | |
| }); | |
| test_fn("Style { x: [1, 2, 3] }", Ast { | |
| root: Entity { | |
| components: vec![ | |
| Component { | |
| type_name: TypeName { name: "Style".into() }, | |
| fields: vec![ | |
| ComponentField { | |
| name: "x".into(), | |
| value: Expression::Array(ArrayExpression::Literal(vec![ | |
| Expression::Literal(LiteralExpression::Integer(1)), | |
| Expression::Literal(LiteralExpression::Integer(2)), | |
| Expression::Literal(LiteralExpression::Integer(3)), | |
| ])), | |
| }, | |
| ], | |
| }, | |
| ], | |
| children: vec![], | |
| inherits: vec![], | |
| }, | |
| }); | |
| test_fn("Style { x: (1, 2, 3) }", Ast { | |
| root: Entity { | |
| components: vec![ | |
| Component { | |
| type_name: TypeName { name: "Style".into() }, | |
| fields: vec![ | |
| ComponentField { | |
| name: "x".into(), | |
| value: Expression::Tuple(TupleExpression(vec![ | |
| Expression::Literal(LiteralExpression::Integer(1)), | |
| Expression::Literal(LiteralExpression::Integer(2)), | |
| Expression::Literal(LiteralExpression::Integer(3)), | |
| ])), | |
| }, | |
| ], | |
| }, | |
| ], | |
| children: vec![], | |
| inherits: vec![], | |
| }, | |
| }); | |
| } | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment