langkit 1.0.0

A builder library for creating programming languages in Rust
Documentation
pub mod bytecode;
pub mod cli;
pub mod interpreter;
pub mod lang;
pub mod lexer;
pub mod parser;
pub mod types;

pub use interpreter::{ErrorKind, LangError, Signal};
pub use lang::Lang;
pub use types::*;

/// Declarative macro to create a new language.
///
/// # Example
/// ```rust
/// use langkit::langkit;
/// let lang = langkit! {
///     name("arc"),
///     var("let"),
///     assign("=")
/// };
/// ```
#[macro_export]
macro_rules! langkit {
    ($($meth:ident ($($arg:expr),*)),* $(,)?) => {{
        let mut lang = $crate::lang::Lang::new();
        $(
            lang.$meth($($arg),*);
        )*
        lang
    }};
}

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

    fn base_lang() -> Lang {
        let mut lang = Lang::new();
        lang.var("let");
        lang.mut_kw("var");
        lang.assign("=");
        lang.typing(Typing::Dynamic);
        lang.implicit_mutability(ImplicitMutability::Mutable);
        lang.condition("if", "{}");
        lang.loop_("loop", "{}");
        lang.for_in("for", "in");
        lang.func("fn", "{}");
        lang.return_("return");
        lang.break_("break");
        lang.token("print");
        lang.action(
            "print",
            Box::new(|_interp, args| {
                let s: Vec<String> = args.iter().map(|a| a.to_string()).collect();
                println!("{}", s.join(" "));
                Value::Null
            }),
        );
        lang
    }

    #[test]
    fn test_arithmetic() {
        let mut lang = base_lang();
        let result = lang.run("let x = 2 + 3 * 4").unwrap();
        assert!(matches!(result, Value::Float(_) | Value::Int(_)));
    }

    #[test]
    fn test_var_and_print() {
        let mut lang = base_lang();
        assert!(lang.run("let x = 10 + 5\nprint x").is_ok());
    }

    #[test]
    fn test_if_else() {
        let mut lang = base_lang();
        assert!(lang.run("let x = 5\nif x > 3 {\nprint x\n}").is_ok());
    }

    #[test]
    fn test_for_in_range() {
        let mut lang = base_lang();
        assert!(lang.run("for i in 0..5 {\nprint i\n}").is_ok());
    }

    #[test]
    fn test_for_in_array() {
        let mut lang = base_lang();
        assert!(
            lang.run("let arr = [1, 2, 3]\nfor x in arr {\nprint x\n}")
                .is_ok()
        );
    }

    #[test]
    fn test_string_interpolation() {
        let mut lang = base_lang();
        let result = lang
            .run("let name = \"world\"\nlet msg = \"hello {name}!\"")
            .unwrap();
        assert_eq!(result.to_string(), "hello world!");
    }

    #[test]
    fn test_builtin_len() {
        let mut lang = base_lang();
        let result = lang.run("len(\"hello\")").unwrap();
        assert_eq!(result.to_string(), "5");
    }

    #[test]
    fn test_builtin_type() {
        let mut lang = base_lang();
        let result = lang.run("type(42)").unwrap();
        assert_eq!(result.to_string(), "float");
    }

    #[test]
    fn test_builtin_to_int() {
        let mut lang = base_lang();
        let result = lang.run("to_int(\"42\")").unwrap();
        assert_eq!(result.to_string(), "42");
    }

    #[test]
    fn test_fn_and_return() {
        let mut lang = base_lang();
        let result = lang
            .run("fn add(a, b) {\nreturn a + b\n}\nlet r = add(3, 4)")
            .unwrap();
        assert_eq!(result.to_string(), "7");
    }

    #[test]
    fn test_custom_error_handler() {
        let mut lang = base_lang();
        lang.on_error(Box::new(|e| format!("💥 {}", e.message)));
        let result = lang.run("print undefined_var");
        assert!(result.unwrap_err().starts_with("💥"));
    }

    #[test]
    fn test_undefined_var_error() {
        let mut lang = base_lang();
        let err = lang.run("print missing").unwrap_err();
        assert!(err.contains("missing") || err.contains("not defined"));
    }

    #[test]
    fn test_assign() {
        let mut lang = base_lang();
        let result = lang.run("x = 5\nx = 10\nx").unwrap();
        assert_eq!(result.to_string(), "10");
    }

    #[test]
    fn test_for_sum_no_break() {
        let mut lang = base_lang();
        let result = lang
            .run(
                "
            sum = 0
            for i in 0..5 {
                sum = sum + i
            }
            sum
        ",
            )
            .unwrap();
        assert_eq!(result.to_string(), "10"); // 0+1+2+3+4 = 10
    }

    #[test]
    fn test_for_i_value() {
        let mut lang = base_lang();
        let result = lang
            .run(
                "
            x = 0
            for i in 5..6 {
                x = i
            }
            x
        ",
            )
            .unwrap();
        assert_eq!(result.to_string(), "5");
    }

    #[test]
    fn test_break_in_for_v2() {
        let mut lang = base_lang();
        // Спрощений тест - break на i == 1
        let result = lang
            .run(
                "
            sum = 0
            for i in 0..10 {
                sum = sum + i
                if i == 1 { break }
            }
            sum
        ",
            )
            .unwrap();
        // sum = 0 + 0 = 0, потім i=1, sum = 0+1 = 1, потім break
        assert_eq!(result.to_string(), "1");
    }

    #[test]
    fn test_break_in_for() {
        let mut lang = base_lang();
        // Сума 0+1+2 = 3, break на i==3
        let result = lang
            .run(
                "
            sum = 0
            for i in 0..10 {
                if i >= 3 { break }
                sum = sum + i
            }
            sum
        ",
            )
            .unwrap();
        assert_eq!(result.to_string(), "3");
    }
}