lince-lingua 0.7.0

Lingua - LINce proGramming langUAge.
Documentation
#[rust_sitter::grammar("lingua")]
pub mod grammar {
    #[rust_sitter::language]
    #[derive(Debug, Clone, PartialEq, Eq)]
    pub struct Program {
        pub statements: Vec<Statement>,
    }

    #[derive(Debug, Clone, PartialEq, Eq)]
    pub enum Statement {
        Function(Function),
        Expression(Expression),
    }

    #[derive(Debug, Clone, PartialEq, Eq)]
    pub struct Function {
        #[rust_sitter::leaf(text = "fn")]
        _fn: (),
        pub name: Identifier,
        #[rust_sitter::leaf(text = "(")]
        _open_parameters: (),
        #[rust_sitter::leaf(text = ")")]
        _close_parameters: (),
        pub body: Block,
    }

    #[derive(Debug, Clone, PartialEq, Eq)]
    pub struct Block {
        #[rust_sitter::leaf(text = "{")]
        _open: (),
        pub statements: Vec<Statement>,
        #[rust_sitter::leaf(text = "}")]
        _close: (),
    }

    #[derive(Debug, Clone, PartialEq, Eq)]
    pub enum Expression {
        Number(#[rust_sitter::leaf(pattern = r"[0-9]+", transform = ToOwned::to_owned)] String),
        String(#[rust_sitter::leaf(pattern = r#""[^"]*""#, transform = ToOwned::to_owned)] String),
        Boolean(Boolean),
    }

    #[derive(Debug, Clone, PartialEq, Eq)]
    pub enum Boolean {
        #[rust_sitter::leaf(text = "true")]
        True,
        #[rust_sitter::leaf(text = "false")]
        False,
    }

    #[derive(Debug, Clone, PartialEq, Eq)]
    pub struct Identifier(
        #[rust_sitter::leaf(
            pattern = r"[a-zA-Z_][a-zA-Z0-9_]*",
            transform = ToOwned::to_owned
        )]
        pub String,
    );

    #[rust_sitter::extra]
    #[allow(dead_code)]
    struct Whitespace {
        #[rust_sitter::leaf(pattern = r"\s")]
        _whitespace: (),
    }
}

pub use grammar::{Block, Boolean, Expression, Function, Identifier, Program, Statement};

pub fn parse(source: &str) -> Result<Program, Vec<rust_sitter::errors::ParseError>> {
    grammar::parse(source)
}

pub fn format(source: &str) -> Result<String, Vec<rust_sitter::errors::ParseError>> {
    parse(source).map(|program| format_program(&program))
}

pub fn format_program(program: &Program) -> String {
    let mut output = String::new();
    write_statements(&mut output, &program.statements, 0);
    output
}

fn write_statements(output: &mut String, statements: &[Statement], indent: usize) {
    for (index, statement) in statements.iter().enumerate() {
        if index > 0 {
            output.push('\n');
        }

        write_statement(output, statement, indent);
    }
}

fn write_statement(output: &mut String, statement: &Statement, indent: usize) {
    output.push_str(&" ".repeat(indent));

    match statement {
        Statement::Function(function) => write_function(output, function, indent),
        Statement::Expression(expression) => write_expression(output, expression),
    }
}

fn write_function(output: &mut String, function: &Function, indent: usize) {
    output.push_str("fn ");
    output.push_str(&function.name.0);
    output.push_str("() {\n");
    write_statements(output, &function.body.statements, indent + 4);
    output.push('\n');
    output.push_str(&" ".repeat(indent));
    output.push('}');
}

fn write_expression(output: &mut String, expression: &Expression) {
    match expression {
        Expression::Number(number) => output.push_str(number),
        Expression::String(string) => output.push_str(string),
        Expression::Boolean(Boolean::True) => output.push_str("true"),
        Expression::Boolean(Boolean::False) => output.push_str("false"),
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn parses_primitives() {
        assert_eq!(
            parse("true\n6174\n\"hello world\"\n").unwrap(),
            Program {
                statements: vec![
                    Statement::Expression(Expression::Boolean(Boolean::True)),
                    Statement::Expression(Expression::Number("6174".to_owned())),
                    Statement::Expression(Expression::String("\"hello world\"".to_owned())),
                ],
            }
        );
    }

    #[test]
    fn parses_functions() {
        let program = parse("fn test_function() {\n    true\n}\n").unwrap();

        let Statement::Function(function) = &program.statements[0] else {
            panic!("expected a function");
        };

        assert_eq!(function.name.0, "test_function");
        assert_eq!(
            function.body.statements,
            vec![Statement::Expression(Expression::Boolean(Boolean::True))]
        );
    }

    #[test]
    fn formats_functions() {
        assert_eq!(
            format("fn test( ) {\ntrue\n}").unwrap(),
            "fn test() {\n    true\n}"
        );
    }

    #[test]
    fn rejects_syntax_errors() {
        assert!(parse("fn broken(").is_err());
    }

    #[test]
    fn parses_example_files() {
        for entry in std::fs::read_dir("test/examples").unwrap() {
            let path = entry.unwrap().path();
            if path.extension().and_then(|extension| extension.to_str()) != Some("lingua") {
                continue;
            }

            let source = std::fs::read_to_string(&path).unwrap();
            parse(&source).unwrap_or_else(|err| {
                panic!("failed to parse {}: {err:?}", path.display());
            });
        }
    }
}