Skip to content

Instantly share code, notes, and snippets.

@omaskery
Created August 28, 2024 20:00
Show Gist options
  • Save omaskery/42a67ebca36b5ad73897355b2c3ac1dc to your computer and use it in GitHub Desktop.
Save omaskery/42a67ebca36b5ad73897355b2c3ac1dc to your computer and use it in GitHub Desktop.
Bevy BSN parse test to learn the nom crate
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