tipi-lang/src/compiler/compiler_tests.rs
2025-12-14 18:12:50 +01:00

490 lines
10 KiB
Rust

#[cfg(test)]
mod tests {
use crate::DATE_FORMAT_TIMEZONE;
use crate::compiler::{compile, run};
use crate::errors::CompilerError::{IllegalArgumentsException, ReservedFunctionName};
use crate::errors::CompilerErrorAtLine;
use crate::errors::RuntimeError::{IllegalArgumentException, IndexOutOfBounds, NotSortable};
use crate::errors::TipiLangError::{Compiler, Runtime};
use crate::value::{Value, string, i64};
use chrono::DateTime;
#[test]
fn literal_int() {
assert_eq!(run("1"), Ok(Value::I64(1)));
}
#[test]
fn literal_float() {
assert_eq!(run("2.1"), Ok(Value::F64(2.1)));
}
#[test]
fn literal_float_scientific() {
assert_eq!(run("2.1e5"), Ok(Value::F64(2.1e5)));
}
#[test]
fn literal_string() {
assert_eq!(run(r#""a""#), Ok(string("a")));
}
#[test]
fn literal_list() {
assert_eq!(
run(r#"["abc","def"]"#),
Ok(Value::List(vec![string("abc"), string("def")]))
);
}
#[test]
fn index_in_list_literal() {
assert_eq!(run(r#"["abc","def"][1]"#), Ok(string("def")))
}
#[test]
fn index_in_list_as_var() {
assert_eq!(
run(r#"let a:list = ["abc","def"]
a[1]"#),
Ok(string("def"))
)
}
#[test]
fn infer_type() {
assert_eq!(
run(r#"let a=1
a"#),
Ok(Value::I64(1))
);
}
#[test]
fn define_u32() {
assert_eq!(
run(r#"let a:u32=1
a"#),
Ok(Value::U32(1))
);
}
#[test]
fn define_char() {
assert_eq!(
run(r#"let a:char='a'
a"#),
Ok(Value::Char('a'))
);
}
#[test]
fn define_u32_invalid_value_negative() {
let r = compile("let a:u32=-1");
assert!(r.is_err());
if let Err(e) = &r {
assert_eq!(
e.to_string(),
"Compilation failed: error at line 1, Expected u32, found i32/64"
);
}
}
#[test]
fn define_u64_invalid_value_negative() {
let r = compile("let a:u64=-1");
assert!(r.is_err());
if let Err(e) = &r {
assert_eq!(
e.to_string(),
"Compilation failed: error at line 1, Expected u64, found i32/64"
);
}
}
#[test]
fn let_u64_invalid_value_string() {
let r = compile(r#"let a:u64="not ok""#);
assert!(r.is_err());
if let Err(e) = &r {
assert_eq!(
e.to_string(),
"Compilation failed: error at line 1, Expected u64, found string"
);
}
}
#[test]
fn call_fn_with_args_returns_value() {
assert_eq!(
run(r#"fn add_hello(name: string) -> string:
"Hello " + name
add_hello("world")"#),
Ok(string("Hello world"))
);
}
#[test]
fn define_object() {
let r = compile(
r#"
object Person:
name: string"#,
);
assert!(r.is_ok()); // does nothing runtime
}
#[test]
fn declare_and_instantiate_object() {
let r = run(r#"
object Person:
name: string
let p = Person(name: "Sander")
p"#);
assert!(r.is_ok());
assert_eq!(
r#"main/Person: [("name", String("Sander"))]"#,
format!("{}", r.unwrap().to_string())
);
}
#[test]
fn declare_and_instantiate_object_wrong_type() {
let r = run(r#"
object Person:
name: string
let p = Person(name: 0x42)
p"#);
// assert!(r.is_err());
assert_eq!(
r#"Compilation failed: error at line 5, Expected string, found integer"#,
format!("{}", r.unwrap_err().to_string())
);
}
#[test]
fn literal_map() {
let result = run(r#"{"name": "Dent", "age": 40 }"#);
assert!(result.is_ok());
let result = result.unwrap();
if let Value::Map(map) = result {
assert_eq!(map.get(&string("name")).unwrap(), &string("Dent"));
assert_eq!(map.get(&string("age")).unwrap(), &Value::I64(40));
}
}
#[test]
fn assign_map() {
let result = run(r#"let m = {"name": "Dent"}
m"#);
let result = result.unwrap();
if let Value::Map(map) = result {
assert_eq!(map.get(&string("name")).unwrap(), &string("Dent"));
}
}
#[test]
fn access_map() {
let result = run(r#"let m = {"name": "Dent"}
m["name"]"#);
let result = result.unwrap();
if let Value::String(v) = result {
assert_eq!(v.as_str(), "Dent");
}
}
#[test]
fn keyword_error() {
let result = run(r#"let map = {"name": "Dent"}"#);
assert!(result.is_err());
assert_eq!(
"Compilation failed: error at line 1, 'map' is a keyword. You cannot use it as an identifier",
result.unwrap_err().to_string()
);
}
#[test]
fn add_strings() {
assert_eq!(run(r#""a"+"b""#), Ok(string("ab")));
}
#[test]
fn add_string_and_int() {
assert_eq!(run(r#""a"+42"#), Ok(string("a42")));
}
#[test]
fn add_string_and_bool() {
assert_eq!(run(r#""a"+false"#), Ok(string("afalse")));
}
#[test]
fn add_string_and_scientific_float() {
assert_eq!(
run(r#""a"+4.2e10"#),
Ok(Value::String("a42000000000".into()))
);
}
#[test]
fn add_hex_ints() {
assert_eq!(run(r#"0x10 + 0x20"#), Ok(Value::U32(48)));
}
#[test]
fn date_literal() {
assert_eq!(
run(r#"let date:datetime = d"2025-11-09 16:44:28.000 +0100"
date"#),
Ok(Value::DateTime(Box::new(
DateTime::parse_from_str("2025-11-09 16:44:28.000 +0100", DATE_FORMAT_TIMEZONE)
.unwrap()
.into()
)))
);
}
#[test]
fn string_reverse() {
assert_eq!(run(r#""abc".reverse()"#), Ok(string("cba")));
}
#[test]
fn string_to_upper() {
assert_eq!(run(r#""abc".to_uppercase()"#), Ok(string("ABC")));
}
#[test]
fn string_len() {
assert_eq!(run(r#""abc".len()"#), Ok(Value::U64(3)));
}
#[test]
fn string_replace() {
assert_eq!(run(r#""Hello".replace_all("l","p")"#), Ok(string("Heppo")));
}
#[test]
fn string_replace_wrong_nr_of_args() {
assert_eq!(
run(r#""Hello".replace_all("l")"#),
Err(Compiler(CompilerErrorAtLine {
error: IllegalArgumentsException("string.replace_all".to_string(), 2, 1),
line: 1
}))
);
}
#[test]
fn string_replace_wrong_type_of_args() {
assert_eq!(
run(r#""Hello".replace_all("l", 1)"#),
Err(Runtime(IllegalArgumentException(
"Illegal replacement. Expected a string but got 1".to_string()
)))
);
}
#[test]
fn string_contains() {
assert_eq!(run(r#""Hello".contains("l")"#), Ok(Value::Bool(true)));
}
#[test]
fn list_length() {
assert_eq!(run(r#"[1,2,3].len()"#), Ok(Value::U64(3)));
}
#[test]
fn list_sort() {
assert_eq!(
run(r#"
let a = [3,2,1]
a.sort()
a"#),
Ok(Value::List(vec![i64(1), i64(2), i64(3)]))
);
}
#[test]
fn list_sort_invalid() {
assert_eq!(
run(r#"
let a = [3,2,"a"]
a.sort()
a"#),
Err(Runtime(NotSortable))
);
}
#[test]
fn list_push() {
assert_eq!(
run(r#"[1,2].push(3)"#),
Ok(Value::List(vec![
Value::I64(1),
Value::I64(2),
Value::I64(3)
]))
);
}
#[test]
fn list_remove() {
assert_eq!(
run(r#"[1,2,3].remove(0)"#),
Ok(Value::List(vec![Value::I64(2), Value::I64(3)]))
);
}
#[test]
fn list_remove_out_of_bounds() {
assert_eq!(
run(r#"[1,2,3].remove(4)"#),
Err(Runtime(IndexOutOfBounds(4, 3)))
);
}
#[test]
fn reassign() {
assert_eq!(
run(r#"let a=1
a=2"#),
Ok(Value::Void)
);
}
#[test]
fn simple_if_expr() {
assert_eq!(
run(r#"
let a = 2
let b = 3
if true:
a
else:
b
"#),
Ok(Value::I64(2))
);
}
#[test]
fn if_expr() {
assert_eq!(
run(r#"
if 1==1:
2
"#),
Ok(Value::I64(2))
);
}
#[test]
fn if_var_expr() {
assert_eq!(
run(r#"
let a=1
if a==1:
2
"#),
Ok(Value::I64(2))
);
}
#[test]
fn if_var_expr_else() {
assert_eq!(
run(r#"
let a=1
if a==2:
1
else:
2
"#),
Ok(Value::I64(2))
);
}
#[test]
fn inline_comment() {
assert_eq!(run(r#"// this is a comment"#), Ok(Value::Void));
}
#[test]
fn range_loop() {
assert_eq!(
run(r#"
let sum=0
for a in 1..4:
sum = sum + a
sum
"#),
Ok(Value::I64(10))
);
}
#[test]
fn non_constant_range_loop() {
assert_eq!(
run(r#"
let sum=0
let s = 1
let f = 5
for a in s..f:
sum = sum + a
sum
"#),
Ok(Value::I64(15))
);
}
#[test]
fn global_function_call() {
let value = run(r#"now()"#);
assert!(value.is_ok());
let value = value.unwrap();
let date_time_string = value.to_string();
assert!(DateTime::parse_from_str(&date_time_string, DATE_FORMAT_TIMEZONE).is_ok());
}
#[test]
fn global_fns_are_not_allowed() {
let value = run(r#"fn now():"#);
assert_eq!(
value,
Err(Compiler(CompilerErrorAtLine {
error: ReservedFunctionName("now".to_string()),
line: 1
}))
);
}
#[test]
fn test_if_expression_else() {
run(r#"
let a:i64 = if true:
42
else:
0
a"#)
.unwrap();
}
// #[test]
// fn package() {
// assert_eq!(run(r#"a.b.c()"#), Ok(Value::U32(48)));
// }
// #[test]
// fn guards() {
// assert_eq!(
// run(r#"fn get_all_users() -> list:
// | /{uuid} -> service.get_by_uuid(uuid)?"#),
// Ok(Value::Void)
// );
// }
}