mamba 0.3.6

A transpiler which converts Mamba files to Python 3 files
Documentation
use crate::parse::ast::AST;
use crate::parse::ast::Node;
use crate::parse::expr_or_stmt::parse_expr_or_stmt;
use crate::parse::iterator::LexIterator;
use crate::parse::lex::token::Token;
use crate::parse::operation::parse_expression;
use crate::parse::result::expected_one_of;
use crate::parse::result::ParseResult;
use crate::parse::ty::parse_type;

pub fn parse_cntrl_flow_expr(it: &mut LexIterator) -> ParseResult {
    it.peek_or_err(
        &|it, lex| match lex.token {
            Token::If => parse_if(it),
            Token::Match => parse_match(it),
            _ => Err(Box::from(expected_one_of(&[Token::If, Token::Match], lex, "control flow expression")))
        },
        &[Token::If, Token::Match],
        "control flow expression",
    )
}

fn parse_if(it: &mut LexIterator) -> ParseResult {
    let start = it.start_pos("if expression")?;
    it.eat(&Token::If, "if expressions")?;
    let cond = it.parse(&parse_expression, "if expression", start)?;
    it.eat(&Token::Then, "if expression")?;
    let then = it.parse(&parse_expr_or_stmt, "if expression", start)?;

    let el = if it.peek_if(&|lex| lex.token == Token::Else) {
        it.parse_if(&Token::Else, &parse_expr_or_stmt, "if else branch", start)?
    } else if it.peek_if_followed_by(&Token::NL, &Token::Else) {
        it.eat(&Token::NL, "if else branch")?;
        it.parse_if(&Token::Else, &parse_expr_or_stmt, "if else branch", start)?
    } else {
        None
    };

    let pos = if let Some(el) = &el { start.union(el.pos) } else { start.union(then.pos) };
    let node = Node::IfElse { cond, then, el };

    Ok(Box::from(AST::new(pos, node)))
}

fn parse_match(it: &mut LexIterator) -> ParseResult {
    let start = it.start_pos("match")?;
    it.eat(&Token::Match, "match")?;
    let cond = it.parse(&parse_expression, "match", start)?;
    it.eat(&Token::NL, "match")?;
    let cases = it.parse_vec(&parse_match_cases, "match", start)?;
    let end = cases.last().cloned().map_or(cond.pos, |case| case.pos);

    let node = Node::Match { cond, cases };
    Ok(Box::from(AST::new(start.union(end), node)))
}

pub fn parse_match_cases(it: &mut LexIterator) -> ParseResult<Vec<AST>> {
    let start = it.eat(&Token::Indent, "match cases")?;
    let mut cases = vec![];
    it.peek_while_not_token(&Token::Dedent, &mut |it, _| {
        cases.push(*it.parse(&parse_match_case, "match case", start)?);
        it.eat_if(&Token::NL);
        Ok(())
    })?;

    it.eat(&Token::Dedent, "match cases")?;
    Ok(cases)
}

fn parse_match_case(it: &mut LexIterator) -> ParseResult {
    let start = it.start_pos("match case")?;
    let cond = it.parse(&parse_expression_maybe_type, "match case", start)?;
    it.eat(&Token::BTo, "match case")?;
    let body = it.parse(&parse_expr_or_stmt, "match case", start)?;

    let node = Node::Case { cond, body: body.clone() };
    Ok(Box::from(AST::new(start.union(body.pos), node)))
}

fn parse_expression_maybe_type(it: &mut LexIterator) -> ParseResult {
    let start = it.start_pos("expression maybe type")?;
    let mutable = it.eat_if(&Token::Fin).is_none();

    let expr = it.parse(&parse_expression, "expression maybe type", start)?;
    let ty = it.parse_if(&Token::DoublePoint, &parse_type, "expression maybe type", start)?;
    let end = ty.clone().map_or(expr.pos, |t| t.pos);

    let node = Node::ExpressionType { expr, mutable, ty };
    Ok(Box::from(AST::new(start.union(end), node)))
}

#[cfg(test)]
mod test {
    use crate::parse::{parse, parse_direct};
    use crate::parse::ast::{AST, Node};
    use crate::parse::result::ParseResult;
    use crate::test_util::resource_content;

    #[test]
    fn if_else_verify() {
        let source = String::from("if a then c else d");
        let statements = parse_direct(&source).unwrap();

        let Node::IfElse { cond, then, el } = &statements.first().expect("script empty.").node else {
            panic!("first element script was not if.")
        };

        assert_eq!(cond.node, Node::Id { lit: String::from("a") });
        assert_eq!(then.node, Node::Id { lit: String::from("c") });
        assert_eq!(el.as_ref().unwrap().node, Node::Id { lit: String::from("d") });
    }

    #[test]
    fn match_verify() {
        let source = String::from("match a\n    a => b\n    c => d");
        let statements = parse_direct(&source).unwrap();

        let Node::Match { cond, cases } = &statements.first().expect("script empty.").node else {
            panic!("first element script was not match.")
        };

        assert_eq!(cond.node, Node::Id { lit: String::from("a") });

        assert_eq!(cases.len(), 2);
        let (cond1, expr1, cond2, expr2) = match (&cases[0], &cases[1]) {
            (
                AST { node: Node::Case { cond: cond1, body: expr1 }, .. },
                AST { node: Node::Case { cond: cond2, body: expr2 }, .. }
            ) => match (&cond1.node, &cond2.node) {
                (
                    Node::ExpressionType { expr: cond1, .. },
                    Node::ExpressionType { expr: cond2, .. }
                ) => (cond1, expr1, cond2, expr2),
                other => panic!("expected expression type: {:?}", other)
            },
            _ => panic!("Cases incorrect.")
        };

        assert_eq!(cond1.node, Node::Id { lit: String::from("a") });
        assert_eq!(expr1.node, Node::Id { lit: String::from("b") });
        assert_eq!(cond2.node, Node::Id { lit: String::from("c") });
        assert_eq!(expr2.node, Node::Id { lit: String::from("d") });
    }

    #[test]
    fn if_expression() -> ParseResult<()> {
        let source = String::from("if a then\n    b\n");
        let ast = parse_direct(&source)?;

        let Some(Node::IfElse { cond, then, el }) = ast.first().map(|a| &a.node) else {
            panic!("Expected if, got {:?}", ast)
        };

        assert_eq!(cond.node, Node::Id { lit: String::from("a") });
        let then = match &then.node {
            Node::Block { statements } => statements[0].clone(),
            _ => panic!("Expected then block, got {:?}", then)
        };
        assert_eq!(then.node, Node::Id { lit: String::from("b") });

        assert!(el.is_none());
        Ok(())
    }

    #[test]
    fn if_else_expression() -> ParseResult<()> {
        let source = String::from("if a then\n    b\nelse\n    c");
        let ast = parse_direct(&source)?;

        let Some(Node::IfElse { cond, then, el }) = ast.first().map(|a| &a.node) else {
            panic!("Expected if, got {:?}", ast)
        };

        assert_eq!(cond.node, Node::Id { lit: String::from("a") });
        let then = match &then.node {
            Node::Block { statements } => statements[0].clone(),
            _ => panic!("Expected then block, got {:?}", then)
        };
        assert_eq!(then.node, Node::Id { lit: String::from("b") });

        let el = match el.clone().unwrap().node {
            Node::Block { statements } => statements[0].clone(),
            _ => panic!("Expected then block, got {:?}", then)
        };
        assert_eq!(el.node, Node::Id { lit: String::from("c") });
        Ok(())
    }

    #[test]
    fn if_then_missing_body() {
        let source = String::from("if a then b else");
        parse_direct(&source).unwrap_err();
    }

    #[test]
    fn match_missing_condition() {
        let source = String::from("match\n    a => b");
        parse_direct(&source).unwrap_err();
    }

    #[test]
    fn match_missing_arms() {
        let source = String::from("match a with\n    ");
        parse_direct(&source).unwrap_err();
    }

    #[test]
    fn match_missing_arms_no_newline() {
        let source = String::from("match a");
        parse_direct(&source).unwrap_err();
    }

    #[test]
    fn match_stmt() {
        let source = resource_content(true, &["control_flow"], "match_stmt.mamba");
        parse(&source).unwrap();
    }
}