rimu-parse 0.2.0

A data structure template system.
Documentation
use rimu_ast::{SpannedBlock, SpannedExpression};
use rimu_meta::{SourceId, Span};

mod compiler;
mod error;
mod lexer;
mod token;

pub use crate::error::Error;
pub(crate) use compiler::{compile_block, compile_expression};
pub(crate) use lexer::{tokenize_block, tokenize_expression};
pub(crate) use token::{SpannedToken, Token};

pub fn parse_expression(code: &str, source: SourceId) -> (Option<SpannedExpression>, Vec<Error>) {
    let mut errors = Vec::new();

    let len = code.chars().count();
    let eoi = Span::new(source.clone(), len, len);

    let (tokens, lex_errors) = tokenize_expression(code, source.clone());
    errors.append(&mut lex_errors.into_iter().map(Error::Lexer).collect());

    let Some(tokens) = tokens else {
        return (None, errors);
    };

    let (output, compile_errors) = compile_expression(tokens, eoi);
    errors.append(&mut compile_errors.into_iter().map(Error::Compiler).collect());

    (output, errors)
}

pub fn parse_block(code: &str, source: SourceId) -> (Option<SpannedBlock>, Vec<Error>) {
    let mut errors = Vec::new();

    let len = code.chars().count();
    let eoi = Span::new(source.clone(), len, len);

    let (tokens, lex_errors) = tokenize_block(code, source.clone());
    errors.append(&mut lex_errors.into_iter().map(Error::Lexer).collect());

    let Some(tokens) = tokens else {
        return (None, errors);
    };

    let (output, compile_errors) = compile_block(tokens, eoi);
    errors.append(&mut compile_errors.into_iter().map(Error::Compiler).collect());

    (output, errors)
}

#[cfg(test)]
mod tests {
    use std::ops::Range;

    use pretty_assertions::assert_eq;
    use rimu_ast::{BinaryOperator, Block, Expression, SpannedBlock, SpannedExpression};
    use rimu_meta::{SourceId, Span, Spanned};

    use crate::{parse_block, parse_expression, Error};

    fn span(range: Range<usize>) -> Span {
        Span::new(SourceId::empty(), range.start, range.end)
    }

    fn test_block(code: &str) -> (Option<SpannedBlock>, Vec<Error>) {
        parse_block(code, SourceId::empty())
    }

    fn test_expression(code: &str) -> (Option<SpannedExpression>, Vec<Error>) {
        parse_expression(code, SourceId::empty())
    }

    #[test]
    fn expr_arithmetic() {
        let (actual_expr, errors) = test_expression("x + y * (z / w)");

        let expected_expr = Some(Spanned::new(
            Expression::Binary {
                left: Box::new(Spanned::new(Expression::Identifier("x".into()), span(0..1))),
                right: Box::new(Spanned::new(
                    Expression::Binary {
                        left: Box::new(Spanned::new(
                            Expression::Identifier("y".into()),
                            span(4..5),
                        )),
                        right: Box::new(Spanned::new(
                            Expression::Binary {
                                left: Box::new(Spanned::new(
                                    Expression::Identifier("z".into()),
                                    span(9..10),
                                )),
                                right: Box::new(Spanned::new(
                                    Expression::Identifier("w".into()),
                                    span(13..14),
                                )),
                                operator: BinaryOperator::Divide,
                            },
                            span(8..15),
                        )),
                        operator: BinaryOperator::Multiply,
                    },
                    span(4..15),
                )),
                operator: BinaryOperator::Add,
            },
            span(0..15),
        ));

        assert_eq!(actual_expr, expected_expr);
        assert_eq!(errors.len(), 0);
    }

    #[test]
    fn block_misc() {
        let (actual_block, errors) = test_block(
            "
a:
  b:
    - c + d
    - e: f
  g: h
",
        );

        let expected_block = Some(Spanned::new(
            Block::Object(vec![(
                Spanned::new("a".into(), span(1..2)),
                Spanned::new(
                    Block::Object(vec![
                        (
                            Spanned::new("b".into(), span(6..7)),
                            Spanned::new(
                                Block::List(vec![
                                    Spanned::new(
                                        Block::Expression(Expression::Binary {
                                            left: Box::new(Spanned::new(
                                                Expression::Identifier("c".into()),
                                                span(15..16),
                                            )),
                                            right: Box::new(Spanned::new(
                                                Expression::Identifier("d".into()),
                                                span(19..20),
                                            )),
                                            operator: BinaryOperator::Add,
                                        }),
                                        span(15..20),
                                    ),
                                    Spanned::new(
                                        Block::Object(vec![(
                                            Spanned::new("e".into(), span(27..28)),
                                            Spanned::new(
                                                Block::Expression(Expression::Identifier(
                                                    "f".into(),
                                                )),
                                                span(30..31),
                                            ),
                                        )]),
                                        span(27..32),
                                    ),
                                ]),
                                span(13..34),
                            ),
                        ),
                        (
                            Spanned::new("g".into(), span(34..35)),
                            Spanned::new(
                                Block::Expression(Expression::Identifier("h".into())),
                                span(37..38),
                            ),
                        ),
                    ]),
                    span(6..39),
                ),
            )]),
            span(1..39),
        ));

        assert_eq!(actual_block, expected_block);
        assert_eq!(errors.len(), 0);
    }
}