mod value_ext; use value_ext::*; extern crate haru; use haru::ast::grammar; use haru::compiler; use haru::vmbindings::value::Value; use haru::vmbindings::vm::{Vm, VmOpcode}; use haru::vmbindings::vmerror::VmError; #[cfg(test)] pub mod interpreter_tests { use super::*; macro_rules! eval { ($x:expr) => {{ let prog = grammar::start($x).unwrap(); let mut c = compiler::Compiler::new(true); for stmt in prog { stmt.emit(&mut c); } c.cpushop(VmOpcode::OP_HALT); let mut vm = c.into_vm(); vm.gc_enable(); vm.execute(); vm }}; } // #region vars #[test] fn int_literal() { let vm: Vm = eval!("y = 10"); assert_eq!(vm.global().get("y").unwrap().unwraps(), Value::Int(10)); } #[test] fn float_literal() { let vm: Vm = eval!("y = 420.69"); assert_eq!( vm.global().get("y").unwrap().unwraps(), Value::Float(420.69) ); } #[test] fn string_literal() { let vm: Vm = eval!("y = 'test'"); assert_eq!(vm.global().get("y").unwrap().unwraps().string(), "test"); } // #endregion // #region vars #[test] fn global_var() { let vm: Vm = eval!("y = 10"); assert_eq!(vm.global().get("y").unwrap().unwraps(), Value::Int(10)); } #[test] fn global_var_dollar() { let vm: Vm = eval!("$y = 10"); assert_eq!(vm.global().get("y").unwrap().unwraps(), Value::Int(10)); } // #endregion // #region operators #[test] fn basic_arith() { let vm: Vm = eval!("y = 2*(3+5)"); assert_eq!(vm.global().get("y").unwrap().unwraps(), Value::Int(16)); } #[test] fn bitwise_ops() { { let vm: Vm = eval!("y = 2&5"); assert_eq!(vm.global().get("y").unwrap().unwraps(), Value::Int(2 & 5)); } { let vm: Vm = eval!("y = 2|5"); assert_eq!(vm.global().get("y").unwrap().unwraps(), Value::Int(2 | 5)); } { let vm: Vm = eval!("y = 2 xor 5"); assert_eq!(vm.global().get("y").unwrap().unwraps(), Value::Int(2 ^ 5)); } } #[test] fn cmp_gt() { let vm: Vm = eval!("y = 1 > 0"); assert_eq!(vm.global().get("y").unwrap().unwraps(), Value::Int(1)); } #[test] fn cmp_lt() { let vm: Vm = eval!("y = 1 < 0"); assert_eq!(vm.global().get("y").unwrap().unwraps(), Value::Int(0)); } #[test] fn cmp_gte() { let vm: Vm = eval!("y = 0 >= 0"); assert_eq!(vm.global().get("y").unwrap().unwraps(), Value::Int(1)); } #[test] fn cmp_lte() { let vm: Vm = eval!("y = 0 <= 0"); assert_eq!(vm.global().get("y").unwrap().unwraps(), Value::Int(1)); } #[test] fn cmp_eq() { let vm: Vm = eval!("y = 0 == 0"); assert_eq!(vm.global().get("y").unwrap().unwraps(), Value::Int(1)); } #[test] fn and_op() { let vm: Vm = eval!("y = 5 and 0"); assert_eq!(vm.global().get("y").unwrap().unwraps(), Value::Int(0)); } #[test] fn or_op() { let vm: Vm = eval!("y = 5 or 0"); assert_eq!(vm.global().get("y").unwrap().unwraps(), Value::Int(5)); } #[test] fn condexpr() { let vm: Vm = eval!("y = 1 ? 2*2 : 0"); assert_eq!(vm.global().get("y").unwrap().unwraps(), Value::Int(4)); } #[test] fn adds_not_in_place() { let vm: Vm = eval!( " a = 0 a += 1 " ); assert_eq!(vm.global().get("a").unwrap().unwraps().int(), 1); } #[test] fn adds_in_place() { let vm: Vm = eval!( " a = 'a' a += 'b' " ); assert_eq!( *vm.global().get("a").unwrap().unwraps().string(), "ab".to_string() ); } #[test] fn adds_indexed() { let vm: Vm = eval!( " a = ['b','c'] y = 'a' y += a[0] " ); assert_eq!( *vm.global().get("y").unwrap().unwraps().string(), "ab".to_string() ); } #[test] fn adds_to_array_indexed_in_place() { let vm: Vm = eval!( " a = ['a','c'] a[0] += 'b' y = a[0] " ); assert_eq!( *vm.global().get("y").unwrap().unwraps().string(), "ab".to_string() ); } #[test] fn adds_to_record_indexed_in_place() { let vm: Vm = eval!( " record a y = 'a' end a.y += 'b' y = a.y " ); assert_eq!( *vm.global().get("y").unwrap().unwraps().string(), "ab".to_string() ); } #[test] fn muls_in_place() { let vm: Vm = eval!( " x = 'a' x *= 3 " ); assert_eq!( vm.global().get("x").unwrap().unwraps().string(), &"aaa".to_string() ); } // #endregion // #region if statement #[test] fn if_stmt() { let vm: Vm = eval!( " if 0 then y = 1 " ); assert!(vm.global().get("y").is_none()); } #[test] fn if_else_stmt() { let vm: Vm = eval!( " if 0 then y = 1 else y = 2 " ); assert_eq!(vm.global().get("y").unwrap().unwraps(), Value::Int(2)); } // #endregion // #region while statement #[test] fn while_stmt() { let vm: Vm = eval!( " i = 0 while i < 10 begin i = i + 1 end " ); assert_eq!(vm.global().get("i").unwrap().unwraps(), Value::Int(10)); } // #endregion // #region for statement #[test] fn for_stmt() { let vm: Vm = eval!( " for i=0 to 10 begin end " ); assert_eq!(vm.global().get("i").unwrap().unwraps(), Value::Int(10)); } #[test] fn for_downto_stmt() { let vm: Vm = eval!( " for i=10 downto 0 begin end " ); assert_eq!(vm.global().get("i").unwrap().unwraps(), Value::Int(0)); } #[test] fn for_in_stmt() { let vm: Vm = eval!( " for i in [1,2,3,10] begin end " ); assert_eq!(vm.global().get("i").unwrap().unwraps(), Value::Int(10)); } #[test] fn for_in_stmt_empty() { let vm: Vm = eval!( " for i in [] begin end " ); assert!(vm.global().get("i").is_none()); assert_eq!(vm.stack.len(), 0); } #[test] fn for_in_stmt_string() { let vm: Vm = eval!( " y = 0 for i in 'abcd' begin y += 1 end " ); assert_eq!(vm.global().get("y").unwrap().unwraps(), Value::Int(4)); } #[test] fn for_in_stmt_iterator() { let vm: Vm = eval!( " record x i = 1 function next(self) begin self.i += 1 if self.i == 10 begin self.stopped = 1 end return self.i end end for i in x begin end " ); let rec = vm.global().get("x").unwrap().unwraps().record(); assert_eq!(rec.get(&"stopped".to_string()).unwrap().unwraps().int(), 1); assert_eq!(rec.get(&"i".to_string()).unwrap().unwraps().int(), 10); } // #endregion // #region continue/break #[test] fn break_stmt() { let vm: Vm = eval!( " for i=0 to 10 begin if i == 5 then break end " ); assert_eq!(vm.global().get("i").unwrap().unwraps(), Value::Int(5)); } // #endregion // #region functions #[test] fn function_stmt() { let vm: Vm = eval!( " function A() begin end " ); assert!(match vm.global().get("A").unwrap().unwraps() { Value::Fn(_) => true, _ => false, }); } #[test] fn function_stmt_call() { let vm: Vm = eval!( " function A() begin return 10 end y = A() " ); assert_eq!(vm.global().get("y").unwrap().unwraps(), Value::Int(10)); } #[test] fn function_stmt_call_args() { let vm: Vm = eval!( " function A(x) begin return 10+x end y = A(10) " ); assert_eq!(vm.global().get("y").unwrap().unwraps(), Value::Int(20)); assert!(vm.global().get("x").is_none()); } #[test] fn function_stmt_scope() { let vm: Vm = eval!( " $x = 1 function outer() begin x = 2 function inner() begin x = 3 $z = x end inner() $y = x end outer() " ); assert_eq!(vm.global().get("x").unwrap().unwraps(), Value::Int(1)); assert_eq!(vm.global().get("y").unwrap().unwraps(), Value::Int(2)); assert_eq!(vm.global().get("z").unwrap().unwraps(), Value::Int(3)); } #[test] fn function_stmt_scope_up() { let vm: Vm = eval!( " (function() begin a = 10 function A() begin function B() begin $x = a end B() end A() end)() " ); assert_eq!(vm.global().get("x").unwrap().unwraps(), Value::Int(10)); } #[test] fn function_stmt_iife() { let vm: Vm = eval!( " (function() begin $y = 0 end)() " ); assert_eq!(vm.global().get("y").unwrap().unwraps(), Value::Int(0)); } #[test] fn function_expr() { let vm: Vm = eval!( " fib(n) = n <= 1 ? 1 : fib(n-1) + fib(n-2) " ); assert!(match vm.global().get("fib").unwrap().unwraps() { Value::Fn(_) => true, _ => false, }); } #[test] fn function_return() { let vm: Vm = eval!( " function a() begin return 1 $y = 0 end y = a() " ); assert_eq!(vm.global().get("y").unwrap().unwraps(), Value::Int(1)); } #[test] fn function_tco() { let vm: Vm = eval!( " y = 0 function a() begin if $y == 1000 then return $y += 1 return a() end a() " ); assert_eq!(vm.global().get("y").unwrap().unwraps(), Value::Int(1000)); } #[test] fn function_tco_short() { let vm: Vm = eval!( " a(x) = x == 1000 ? x : a(x+1) y = a(0) " ); assert_eq!(vm.global().get("y").unwrap().unwraps(), Value::Int(1000)); } #[test] fn function_call_memexpr_index() { let vm: Vm = eval!( " x = [_() = 0] y = x[0] z = y() " ); assert_eq!(vm.global().get("z").unwrap().unwraps(), Value::Int(0)); } /* // TODO: this test won't work because vm halts #[test] fn function_call_from_native() { let mut vm : Vm = eval!(" function a() begin return 10 end "); let val = vm.global().get("a").unwraps().clone(); assert_eq!(vm.call(val, CArray::new_nil()).unwrap().unwraps(), Value::Int(10)); } */ // #endregion // #region exceptions #[test] fn try_stmt_simple() { let vm: Vm = eval!( " try y = 10 end " ); assert_eq!(vm.global().get("y").unwrap().unwraps(), Value::Int(10)); } #[test] fn try_stmt_unhandled_raise() { let vm: Vm = eval!( " raise 0 " ); assert_eq!(vm.error, VmError::ERROR_UNHANDLED_EXCEPTION); } #[test] fn try_stmt_handled_raise() { let vm: Vm = eval!( " record A function constructor(self) begin return self end end try raise A() case A y = 10 end " ); assert_eq!(vm.global().get("y").unwrap().unwraps(), Value::Int(10)); } // #endregion // #region record #[test] fn record_stmt_simple() { let vm: Vm = eval!( " record A end " ); vm.global().get("A").unwrap().unwraps().record(); } #[test] fn record_stmt_with_body() { let vm: Vm = eval!( " record A y = 0 function x() begin end end " ); let rec = vm.global().get("A").unwrap().unwraps().record(); assert_eq!(rec.get(&"y".to_string()).unwrap().unwraps(), Value::Int(0)); assert!(match rec.get(&"x".to_string()).unwrap().unwraps() { Value::Fn(_) => true, _ => false, }); } #[test] fn memexpr_get_unk() { let vm: Vm = eval!( " record A end y = A.x " ); assert_eq!(vm.error, VmError::ERROR_UNKNOWN_KEY); } #[test] fn memexpr_indexed_record() { let vm: Vm = eval!( " record A x = 10 end y = A['x'] " ); assert_eq!(vm.global().get("y").unwrap().unwraps().int(), 10); } #[test] fn memexpr_indexed_record_key() { let vm: Vm = eval!( " record A x = 10 end a = 'x' y = A[a] " ); assert_eq!(vm.global().get("y").unwrap().unwraps().int(), 10); } #[test] fn memexpr_indexed_set_record_key() { let vm: Vm = eval!( " record A x = 10 end a = 'x' A[a] = 15 y = A.x " ); assert_eq!(vm.global().get("y").unwrap().unwraps().int(), 15); } #[test] fn memexpr_indexed_adds_record_key() { let vm: Vm = eval!( " record A x = 10 end a = 'x' A[a] += 5 y = A.x " ); assert_eq!(vm.global().get("y").unwrap().unwraps().int(), 15); } #[test] fn memexpr_set() { let vm: Vm = eval!( " record A y = 0 end function x() begin A.y = 1 end x() " ); let rec = vm.global().get("A").unwrap().unwraps().record(); assert_eq!(rec.get(&"y".to_string()).unwrap().unwraps(), Value::Int(1)); } #[test] fn memexpr_adds() { let vm: Vm = eval!( " record A y = 0 end function x() begin A.y += 1 end x() " ); let rec = vm.global().get("A").unwrap().unwraps().record(); assert_eq!(rec.get(&"y".to_string()).unwrap().unwraps(), Value::Int(1)); } #[test] fn memexpr_adds_ordering() { let vm: Vm = eval!( " record A y = 'a' end function x() begin A.y += 'b' end x() " ); let rec = vm.global().get("A").unwrap().unwraps().record(); assert_eq!( *rec.get(&"y".to_string()).unwrap().unwraps().string(), "ab".to_string() ); } #[test] fn record_stmt_constructor() { let vm: Vm = eval!( " record A function constructor(self) begin return self end end a = A() " ); let a = vm.global().get("a").unwrap().unwraps().record(); assert_eq!( *a.get(&"prototype".to_string()).unwrap(), *vm.global().get("A").unwrap() ); } #[test] fn record_stmt_prototype_method() { let vm: Vm = eval!( " record A function constructor(self) begin return self end function test(self) begin return 10 end end a = A() y = a.test() " ); assert_eq!(vm.global().get("y").unwrap().unwraps(), Value::Int(10)); } // #endregion // #region array #[test] fn array_simple() { let vm: Vm = eval!( " a = [] " ); vm.global().get("a").unwrap().unwraps().array(); } #[test] fn array_repeat() { let vm: Vm = eval!( " a = [1]*5 " ); let arr = vm.global().get("a").unwrap().unwraps().array(); assert_eq!(arr.len(), 5); } #[test] fn array_multiple() { let vm: Vm = eval!( " a = ['a', 'b'] " ); let arr = vm.global().get("a").unwrap().unwraps().array(); assert_eq!(arr.len(), 2); assert_eq!(arr[0].unwraps().string(), &"a".to_string()); assert_eq!(arr[1].unwraps().string(), &"b".to_string()); } #[test] fn array_index() { let vm: Vm = eval!( " a = ['a', 'b'] y = a[0] " ); assert_eq!( vm.global().get("y").unwrap().unwraps().string(), &"a".to_string() ); } #[test] fn array_index_set() { let vm: Vm = eval!( " a = ['a', 'b'] a[0] = 'x' y = a[0] " ); assert_eq!( vm.global().get("y").unwrap().unwraps().string(), &"x".to_string() ); } // #endregion // #region string #[test] fn string_index() { let vm: Vm = eval!( " a = 'abcdef' y = a[0] " ); assert_eq!( vm.global().get("y").unwrap().unwraps().string(), &"a".to_string() ); } // #endregion // #region modules #[test] fn module_absolute_import() { std::fs::write("/tmp/module_absolute_import", "$y = 10").unwrap(); let prog = grammar::start( " use '/tmp/module_absolute_import' ", ) .unwrap(); let mut c = compiler::Compiler::new(true); c.modules_info.borrow_mut().files.push("/tmp/x".to_string()); for stmt in prog { stmt.emit(&mut c); } c.cpushop(VmOpcode::OP_HALT); let mut vm = c.into_vm(); vm.execute(); assert_eq!(vm.global().get("y").unwrap().unwraps().int(), 10); } #[test] fn module_relative_import() { std::fs::write("/tmp/module_relative_import", "$y = 10").unwrap(); let prog = grammar::start( " use './module_relative_import' ", ) .unwrap(); let mut c = compiler::Compiler::new(true); c.modules_info.borrow_mut().files.push("/tmp/x".to_string()); for stmt in prog { stmt.emit(&mut c); } c.cpushop(VmOpcode::OP_HALT); let mut vm = c.into_vm(); vm.execute(); assert_eq!(vm.global().get("y").unwrap().unwraps().int(), 10); } #[test] fn module_native_import() { std::fs::write("/tmp/module_native_import.hana", "$y = 10").unwrap(); std::env::set_var("HANA_PATH", "/tmp"); let prog = grammar::start( " use 'module_native_import' ", ) .unwrap(); let mut c = compiler::Compiler::new(true); c.modules_info.borrow_mut().files.push("/tmp/x".to_string()); for stmt in prog { stmt.emit(&mut c); } c.cpushop(VmOpcode::OP_HALT); let mut vm = c.into_vm(); vm.execute(); assert_eq!(vm.global().get("y").unwrap().unwraps().int(), 10); } // #endregion }