finx 0.1.0

A fast, lightweight embeddable scripting language
Documentation
#[cfg(test)]
mod tests {
    use finx::lexer::Token;
    use finx::parser::{Expr, Parser, Stmt};
    use logos::Logos;

    fn lex(input: &str) -> Vec<Token> {
        Token::lexer(input)
            .map(|t| t.unwrap_or(Token::Error))
            .filter(|t| *t != Token::Error)
            .collect()
    }

    #[test]
    fn test_parse_let() {
        let tokens = lex("let a = 42;");
        let mut parser = Parser::new(&tokens);
        let ast = parser.parse().unwrap();
        assert_eq!(
            ast,
            vec![Stmt::Let {
                name: "a".to_string(),
                value: Expr::Number(42.0),
            }]
        );
    }

    #[test]
    fn test_parse_print() {
        let tokens = lex("print(\"hi\");");
        let mut parser = Parser::new(&tokens);
        let ast = parser.parse().unwrap();
        assert_eq!(
            ast,
            vec![Stmt::Expr(Expr::Call {
                callee: Box::new(Expr::Identifier("print".to_string())),
                args: vec![Expr::String("hi".to_string())],
            })]
        );
    }

    #[test]
    fn test_parse_expr_stmt() {
        let tokens = lex("a;");
        let mut parser = Parser::new(&tokens);
        let ast = parser.parse().unwrap();
        assert_eq!(ast, vec![Stmt::Expr(Expr::Identifier("a".to_string()))]);
    }

    #[test]
    fn test_variable_declaration_and_redefinition() {
        let tokens = lex("let a = 1; let b = \"a\"; let c = false;");
        let mut parser = Parser::new(&tokens);
        let ast = parser.parse().unwrap();
        assert_eq!(
            ast,
            vec![
                Stmt::Let {
                    name: "a".to_string(),
                    value: Expr::Number(1.0)
                },
                Stmt::Let {
                    name: "b".to_string(),
                    value: Expr::String("a".to_string())
                },
                Stmt::Let {
                    name: "c".to_string(),
                    value: Expr::Bool(false)
                },
            ]
        );
    }

    #[test]
    fn test_variable_declaration_with_other_variable() {
        let tokens = lex("let a = 1; let b = a;");
        let mut parser = Parser::new(&tokens);
        let ast = parser.parse().unwrap();
        assert_eq!(
            ast,
            vec![
                Stmt::Let {
                    name: "a".to_string(),
                    value: Expr::Number(1.0)
                },
                Stmt::Let {
                    name: "b".to_string(),
                    value: Expr::Identifier("a".to_string())
                },
            ]
        );
    }

    #[test]
    fn test_function_definition_and_call() {
        let tokens = lex("fn yap(msg) { return msg; } yap(\"Hello, World!\");");
        let mut parser = Parser::new(&tokens);
        let ast = parser.parse().unwrap();
        assert_eq!(
            ast,
            vec![
                Stmt::Fn {
                    name: "yap".to_string(),
                    params: vec!["msg".to_string()],
                    body: vec![Stmt::Return(Expr::Identifier("msg".to_string())),],
                },
                Stmt::Expr(Expr::Call {
                    callee: Box::new(Expr::Identifier("yap".to_string())),
                    args: vec![Expr::String("Hello, World!".to_string())],
                })
            ]
        );
    }

    #[test]
    fn test_if_number_condition() {
        let tokens = lex("if 1 { print(\"yes\"); }");
        let mut parser = Parser::new(&tokens);
        let ast = parser.parse().unwrap();
        assert_eq!(
            ast,
            vec![Stmt::If {
                cond: Expr::Number(1.0),
                then_branch: vec![Stmt::Expr(Expr::Call {
                    callee: Box::new(Expr::Identifier("print".to_string())),
                    args: vec![Expr::String("yes".to_string())],
                })],
                else_branch: None,
            }]
        );
    }

    #[test]
    fn test_if_else_number_condition() {
        let tokens = lex("if 1 { print(\"yes\"); } else { print(\"no\"); }");
        let mut parser = Parser::new(&tokens);
        let ast = parser.parse().unwrap();
        assert_eq!(
            ast,
            vec![Stmt::If {
                cond: Expr::Number(1.0),
                then_branch: vec![Stmt::Expr(Expr::Call {
                    callee: Box::new(Expr::Identifier("print".to_string())),
                    args: vec![Expr::String("yes".to_string())],
                })],
                else_branch: Some(Box::new(Stmt::Expr(Expr::Call {
                    callee: Box::new(Expr::Identifier("print".to_string())),
                    args: vec![Expr::String("no".to_string())],
                }))),
            }]
        );
    }

    #[test]
    fn test_if_else_if_condition() {
        let tokens =
            lex("if 1 { print(\"yes\"); } else if 2 { print(\"maybe\"); } else { print(\"no\"); }");
        let mut parser = Parser::new(&tokens);
        let ast = parser.parse().unwrap();
        assert_eq!(
            ast,
            vec![Stmt::If {
                cond: Expr::Number(1.0),
                then_branch: vec![Stmt::Expr(Expr::Call {
                    callee: Box::new(Expr::Identifier("print".to_string())),
                    args: vec![Expr::String("yes".to_string())],
                })],
                else_branch: Some(Box::new(Stmt::If {
                    cond: Expr::Number(2.0),
                    then_branch: vec![Stmt::Expr(Expr::Call {
                        callee: Box::new(Expr::Identifier("print".to_string())),
                        args: vec![Expr::String("maybe".to_string())],
                    })],
                    else_branch: Some(Box::new(Stmt::Expr(Expr::Call {
                        callee: Box::new(Expr::Identifier("print".to_string())),
                        args: vec![Expr::String("no".to_string())],
                    }))),
                }))
            }]
        );
    }

    #[test]
    fn test_if_variable_truthy() {
        let tokens = lex("if a { print(\"a exists and is not null\"); }");
        let mut parser = Parser::new(&tokens);
        let ast = parser.parse().unwrap();
        assert_eq!(
            ast,
            vec![Stmt::If {
                cond: Expr::Identifier("a".to_string()),
                then_branch: vec![Stmt::Expr(Expr::Call {
                    callee: Box::new(Expr::Identifier("print".to_string())),
                    args: vec![Expr::String("a exists and is not null".to_string())],
                })],
                else_branch: None
            }]
        );
    }

    #[test]
    fn test_if_bool_condition() {
        let tokens = lex("if false { print(\"no\"); }");
        let mut parser = Parser::new(&tokens);
        let ast = parser.parse().unwrap();
        assert_eq!(
            ast,
            vec![Stmt::If {
                cond: Expr::Bool(false),
                then_branch: vec![Stmt::Expr(Expr::Call {
                    callee: Box::new(Expr::Identifier("print".to_string())),
                    args: vec![Expr::String("no".to_string())],
                })],
                else_branch: None
            }]
        );
    }

    #[test]
    fn test_if_nested() {
        let tokens = lex("if a { if b { print(\"x\"); } }");
        let mut parser = Parser::new(&tokens);
        let ast = parser.parse().unwrap();
        assert_eq!(
            ast,
            vec![Stmt::If {
                cond: Expr::Identifier("a".to_string()),
                then_branch: vec![Stmt::If {
                    cond: Expr::Identifier("b".to_string()),
                    then_branch: vec![Stmt::Expr(Expr::Call {
                        callee: Box::new(Expr::Identifier("print".to_string())),
                        args: vec![Expr::String("x".to_string())],
                    })],
                    else_branch: None
                }],
                else_branch: None
            }]
        );
    }

    #[test]
    fn test_if_with_operators() {
        let tokens = lex("if 1 == 2 { print(\"no\"); } if a != b { print(\"yes\"); }");
        let mut parser = Parser::new(&tokens);
        let ast = parser.parse().unwrap();
        assert_eq!(
            ast,
            vec![
                Stmt::If {
                    cond: Expr::Binary {
                        left: Box::new(Expr::Number(1.0)),
                        op: Token::EqEq,
                        right: Box::new(Expr::Number(2.0)),
                    },
                    then_branch: vec![Stmt::Expr(Expr::Call {
                        callee: Box::new(Expr::Identifier("print".to_string())),
                        args: vec![Expr::String("no".to_string())],
                    })],
                    else_branch: None
                },
                Stmt::If {
                    cond: Expr::Binary {
                        left: Box::new(Expr::Identifier("a".to_string())),
                        op: Token::BangEq,
                        right: Box::new(Expr::Identifier("b".to_string())),
                    },
                    then_branch: vec![Stmt::Expr(Expr::Call {
                        callee: Box::new(Expr::Identifier("print".to_string())),
                        args: vec![Expr::String("yes".to_string())],
                    })],
                    else_branch: None
                },
            ]
        );
    }

    #[test]
    fn test_arithmetic_order_of_operations() {
        let tokens = lex("let x = 1 + 2 * 3 - 4 / 2;");
        let mut parser = Parser::new(&tokens);
        let ast = parser.parse().unwrap();
        assert_eq!(
            ast,
            vec![Stmt::Let {
                name: "x".to_string(),
                value: Expr::Binary {
                    left: Box::new(Expr::Binary {
                        left: Box::new(Expr::Number(1.0)),
                        op: Token::Plus,
                        right: Box::new(Expr::Binary {
                            left: Box::new(Expr::Number(2.0)),
                            op: Token::Star,
                            right: Box::new(Expr::Number(3.0)),
                        }),
                    }),
                    op: Token::Minus,
                    right: Box::new(Expr::Binary {
                        left: Box::new(Expr::Number(4.0)),
                        op: Token::Slash,
                        right: Box::new(Expr::Number(2.0)),
                    }),
                },
            }]
        );
    }
    #[test]
    fn test_arithmetic_parentheses() {
        let tokens = lex("let y = (1 + 2) * (3 - 4) / 2;");
        let mut parser = Parser::new(&tokens);
        let ast = parser.parse().unwrap();
        assert_eq!(
            ast,
            vec![Stmt::Let {
                name: "y".to_string(),
                value: Expr::Binary {
                    left: Box::new(Expr::Binary {
                        left: Box::new(Expr::Binary {
                            left: Box::new(Expr::Number(1.0)),
                            op: Token::Plus,
                            right: Box::new(Expr::Number(2.0)),
                        }),
                        op: Token::Star,
                        right: Box::new(Expr::Binary {
                            left: Box::new(Expr::Number(3.0)),
                            op: Token::Minus,
                            right: Box::new(Expr::Number(4.0)),
                        }),
                    }),
                    op: Token::Slash,
                    right: Box::new(Expr::Number(2.0)),
                },
            }]
        );
    }

    // Edge cases
    #[test]
    fn test_empty_input() {
        let tokens = lex("");
        let mut parser = Parser::new(&tokens);
        let ast = parser.parse().unwrap();
        assert_eq!(ast, vec![]);
    }

    #[test]
    fn test_let_missing_semicolon() {
        let tokens = lex("let a = 1");
        let mut parser = Parser::new(&tokens);
        let ast = parser.parse().unwrap();
        // Should still parse the let statement, but parser may stop after
        assert_eq!(
            ast,
            vec![Stmt::Let {
                name: "a".to_string(),
                value: Expr::Number(1.0)
            }]
        );
    }

    #[test]
    fn test_let_missing_value() {
        let tokens = lex("let a = ;");
        let mut parser = Parser::new(&tokens);
        let result = parser.parse();
        assert!(result.is_err());
    }

    #[test]
    fn test_function_call_exact_args() {
        let tokens = lex("fn f(x, y) { print(x); } f(1, 2);");
        let mut parser = Parser::new(&tokens);
        let ast = parser.parse().unwrap();
        assert_eq!(ast.len(), 2);
    }

    #[test]
    fn test_function_call_zero_args() {
        let tokens = lex("fn f() { print(1); } f();");
        let mut parser = Parser::new(&tokens);
        let ast = parser.parse().unwrap();
        assert_eq!(ast.len(), 2);
    }

    #[test]
    fn test_function_call_missing_paren() {
        let tokens = lex("fn f(x) { print(x); } f(1;");
        let mut parser = Parser::new(&tokens);
        let result = parser.parse();
        assert!(result.is_err() || result.is_ok()); // Should not panic, but may skip
    }
}