490 lines
10 KiB
Rust
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)
|
|
// );
|
|
// }
|
|
}
|