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::*;
#[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"); }
#[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();
let result = lang
.run(
"
sum = 0
for i in 0..10 {
sum = sum + i
if i == 1 { break }
}
sum
",
)
.unwrap();
assert_eq!(result.to_string(), "1");
}
#[test]
fn test_break_in_for() {
let mut lang = base_lang();
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");
}
}