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::block::parse_block;
use crate::parse::definition::{parse_definition, parse_fun_arg};
use crate::parse::iterator::LexIterator;
use crate::parse::lex::token::Token;
use crate::parse::operation::parse_expression;
use crate::parse::result::{custom, expected, expected_one_of};
use crate::parse::result::ParseResult;
use crate::parse::ty::{parse_conditions, parse_id};
use crate::parse::ty::parse_type;

pub fn parse_class(it: &mut LexIterator) -> ParseResult {
    let start = it.start_pos("class")?;
    it.eat(&Token::Class, "class")?;
    let ty = it.parse(&parse_type, "class", start)?;

    let mut args = vec![];
    if it.eat_if(&Token::LRBrack).is_some() {
        it.peek_while_not_token(&Token::RRBrack, &mut |it, lex| match lex.token {
            Token::Def => {
                args.push(*it.parse(&parse_definition, "constructor argument", start)?);
                it.eat_if(&Token::Comma);
                Ok(())
            }
            _ => {
                args.push(*it.parse(&parse_fun_arg, "constructor argument", start)?);
                it.eat_if(&Token::Comma);
                Ok(())
            }
        })?;
        it.eat(&Token::RRBrack, "class arguments")?;
    }

    let mut parents = vec![];
    if it.eat_if(&Token::DoublePoint).is_some() {
        it.peek_while_not_token(&Token::NL, &mut |it, lex| match lex.token {
            Token::Id(_) | Token::LRBrack => {
                parents.push(*it.parse(&parse_parent, "parents", start)?);
                it.eat_if(&Token::Comma);
                Ok(())
            }
            _ => Err(Box::from(expected(&Token::Id(String::new()), &lex.clone(), "parents")))
        })?;
    }

    let (body, pos) = if it.peek_if_followed_by(&Token::NL, &Token::Indent) {
        let body = it.parse(&parse_block, "class", start)?;
        (Some(body.clone()), start.union(body.pos))
    } else {
        (None, start)
    };

    let node = Node::Class { ty, args, parents, body };
    Ok(Box::from(AST::new(pos, node)))
}

pub fn parse_parent(it: &mut LexIterator) -> ParseResult {
    let start = it.start_pos("parent")?;
    let ty = it.parse(&parse_type, "parent", start)?;

    let mut args = vec![];
    let end = if it.eat_if(&Token::LRBrack).is_some() {
        it.peek_while_not_token(&Token::RRBrack, &mut |it, lex| match &lex.token {
            Token::Id { .. } => {
                args.push(*it.parse(&parse_id, "parent arguments", start)?);
                it.eat_if(&Token::Comma);
                Ok(())
            }
            Token::Str { .. } => {
                args.push(*it.parse(&parse_expression, "parent arguments", start)?);
                it.eat_if(&Token::Comma);
                Ok(())
            }
            _ => Err(Box::from(expected_one_of(
                &[
                    Token::Id(String::new()),
                    Token::Str(String::new(), vec![]),
                    Token::Int(String::new()),
                    Token::Real(String::new()),
                    Token::ENum(String::new(), String::new()),
                    Token::Bool(true),
                    Token::Bool(false)
                ],
                lex,
                "parent arguments",
            )))
        })?;
        it.eat(&Token::RRBrack, "parent arguments")?
    } else {
        ty.pos
    };

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

pub fn parse_type_def(it: &mut LexIterator) -> ParseResult {
    let start = it.start_pos("type definition")?;
    it.eat(&Token::Type, "type definition")?;
    let ty = it.parse(&parse_type, "type definition", start)?;
    let isa = it.parse_if(&Token::DoublePoint, &parse_parent, "type parent", start)?;

    it.peek(
        &|it, lex| match lex.token {
            Token::When => {
                it.eat(&Token::When, "conditional type")?;
                let isa = isa
                    .clone()
                    .ok_or_else(|| custom("conditional type must have parent type", lex.pos))?;

                let conditions = it.parse_vec(&parse_conditions, "conditional type", start)?;
                let end = conditions.last().map_or(ty.pos, |cond| cond.pos);

                let node = Node::TypeAlias { ty: ty.clone(), isa, conditions };
                Ok(Box::from(AST::new(start.union(end), node)))
            }
            _ if it.peek_if_followed_by(&Token::NL, &Token::Indent) => {
                it.eat_if(&Token::NL);
                let body = it.parse(&parse_block, "type definition", start)?;
                let isa = isa.clone();
                let node = Node::TypeDef { ty: ty.clone(), isa, body: Some(body.clone()) };
                Ok(Box::from(AST::new(start.union(body.pos), node)))
            }
            _ => {
                let node = Node::TypeDef { ty: ty.clone(), isa: isa.clone(), body: None };
                Ok(Box::from(AST::new(start.union(ty.pos), node)))
            }
        },
        {
            let isa = isa.clone();
            let node = Node::TypeDef { ty: ty.clone(), isa, body: None };
            Ok(Box::from(AST::new(start.union(ty.pos), node)))
        },
    )
}

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

    #[test]
    fn import_verify() {
        let source = String::from("import d");
        let ast = parse(&source).unwrap();

        let (from, import, alias) = match ast.node {
            Node::Block { statements: modules, .. } => match &modules.first().expect("script empty.").node {
                Node::Import { from, import, alias } => (from.clone(), import.clone(), alias.clone()),
                _ => panic!("first element script was not list.")
            },
            _ => panic!("ast was not script.")
        };

        assert_eq!(from, None);
        assert_eq!(import.len(), 1);
        assert!(alias.is_empty());
        assert_eq!(import[0].node, Node::Id { lit: String::from("d") });
    }

    #[test]
    fn import_as_verify() {
        let source = String::from("import d as e");
        let ast = parse(&source).unwrap();

        let (from, import, alias) = match ast.node {
            Node::Block { statements: modules, .. } => match &modules.first().expect("script empty.").node {
                Node::Import { from, import, alias } => (from.clone(), import.clone(), alias.clone()),
                other => panic!("first element script was not import: {:?}.", other)
            },
            other => panic!("ast was not script: {:?}", other)
        };

        assert_eq!(from, None);
        assert_eq!(import.len(), 1);
        assert_eq!(alias.len(), 1);
        assert_eq!(import[0].node, Node::Id { lit: String::from("d") });
        assert_eq!(alias[0].node, Node::Id { lit: String::from("e") });
    }

    #[test]
    fn from_import_as_verify() {
        let source = String::from("from c import d,f as e,g");
        let ast = parse(&source).unwrap();

        let (from, import, alias) = match ast.node {
            Node::Block { statements: modules, .. } => match &modules.first().expect("script empty.").node {
                Node::Import { from, import, alias } => (from.clone(), import.clone(), alias.clone()),
                other => panic!("first element script was not from: {:?}.", other)
            },
            other => panic!("ast was not script: {:?}", other)
        };

        assert_eq!(from.unwrap().node, Node::Id { lit: String::from("c") });
        assert_eq!(import.len(), 2);
        assert_eq!(alias.len(), 2);
        assert_eq!(import[0].node, Node::Id { lit: String::from("d") });
        assert_eq!(import[1].node, Node::Id { lit: String::from("f") });
        assert_eq!(alias[0].node, Node::Id { lit: String::from("e") });
        assert_eq!(alias[1].node, Node::Id { lit: String::from("g") });
    }

    #[test]
    fn parse_class_alias() {
        let source = String::from("class MyErr1: Exception(\"Something went wrong\")");
        let ast = parse(&source).unwrap();

        let (ty, args, parents, body) = match ast.node {
            Node::Block { statements: modules, .. } => match &modules.first().expect("script empty.").node {
                Node::Class { ty, args, parents, body } =>
                    (ty.clone(), args.clone(), parents.clone(), body.clone()),
                other => panic!("Was not class: {:?}.", other)
            },
            other => panic!("Ast was not script: {:?}", other)
        };

        match ty.node {
            Node::Type { id, generics } => {
                assert_eq!(id.node, Node::Id { lit: String::from("MyErr1") });
                assert_eq!(generics.len(), 0);
            }
            _ => panic!("Expected type: {:?}", ty.node)
        };

        assert_eq!(args.len(), 0);
        assert_eq!(body, None);

        assert_eq!(parents.len(), 1);
        let parent = parents.first().unwrap();
        match &parent.node {
            Node::Parent { ty, args } => {
                match &ty.node {
                    Node::Type { id, generics } => {
                        assert_eq!(id.node, Node::Id { lit: String::from("Exception") });
                        assert_eq!(generics.len(), 0);
                    }
                    _ => panic!("Expected type: {:?}", ty.node)
                }
                assert_eq!(args.len(), 1);
                let arg = args.first().unwrap();
                assert_eq!(arg.node, Node::Str { lit: String::from("Something went wrong"), expressions: vec![] })
            }
            _ => panic!("Expected parent: {:?}", parent.node)
        }
    }

    #[test]
    fn parse_class() -> ParseResult<()> {
        let source = resource_content(true, &["class"], "types.mamba");
        parse(&source).map(|_| ())
    }

    #[test]
    fn parse_imports_class() -> ParseResult<()> {
        let source = resource_content(true, &["class"], "import.mamba");
        parse(&source).map(|_| ())
    }

    #[test]
    fn single_line_class() {
        let source = String::from("class MyClass");
        parse(&source).unwrap();
    }

    #[test]
    fn two_classes_no_newline_after() {
        let source = String::from("class MyClass\nclass MyClass1");
        parse(&source).unwrap();
    }

    #[test]
    fn two_classes_newline_after() {
        let source = String::from("class MyClass\nclass MyClass1\n");
        parse(&source).unwrap();
    }

    #[test]
    fn class_with_single_line_body_no_newline() -> Result<(), ParseErr> {
        let source = "class MyClass\n    def var := 10";
        parse(&source)
            .map_err(|e| e.with_source(&Some(String::from(source)), &None))
            .map(|_| ())
    }

    #[test]
    fn class_with_single_line_body_newline() -> Result<(), ParseErr> {
        let source = "class MyClass\n    def var := 10\n";
        parse(&source)
            .map_err(|e| e.with_source(&Some(String::from(source)), &None))
            .map(|_| ())
    }

    #[test]
    fn class_with_body_class_right_after() -> Result<(), ParseErr> {
        let source = "class MyClass\n    def var := 10\nclass MyClass1\n";
        parse(&source)
            .map_err(|e| e.with_source(&Some(String::from(source)), &None))
            .map(|_| ())
    }

    #[test]
    fn top_lvl_class_access() {
        let source = resource_content(false, &["syntax"], "top_lvl_class_access.mamba");
        parse(&source).unwrap_err();
    }
}