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