#[macro_use] extern crate collect_mac; #[macro_use] extern crate pretty_assertions; use std::{ collections::BTreeMap, sync::{Arc, Mutex}, }; use futures::{prelude::*, task::Poll}; use gluon::{ base::{ pos::Line, types::{ArcType, Type, TypeExt}, }, vm::{ compiler::UpvarInfo, thread::{HookFlags, ThreadInternal}, }, RootedThread, ThreadExt, }; fn new_vm() -> RootedThread { let thread = gluon::new_vm(); thread.get_database_mut().set_optimize(false); thread } const SIMPLE_EXPR: &'static str = r#" let f x = x let g x = f x g 1 "#; #[test] fn function_hook() { let _ = env_logger::try_init(); let thread = new_vm(); let functions = Arc::new(Mutex::new(Vec::new())); { let functions = functions.clone(); let mut context = thread.context(); context.set_hook(Some(Box::new(move |_, debug_context| { functions.lock().unwrap().push( debug_context .stack_info(0) .unwrap() .function_name() .expect("function_name") .to_string(), ); Poll::Ready(Ok(())) }))); context.set_hook_mask(HookFlags::CALL_FLAG); } thread.get_database_mut().implicit_prelude(false); thread.run_expr::("test", SIMPLE_EXPR).unwrap(); assert_eq!( *functions.lock().unwrap(), vec!["test".to_string(), "g".to_string(), "f".to_string()] ); } fn run_line_hook_test(source: &str) -> Vec { let thread = new_vm(); { let mut context = thread.context(); context.set_hook(Some(Box::new(move |_, _| Poll::Pending))); context.set_hook_mask(HookFlags::LINE_FLAG); } thread.get_database_mut().implicit_prelude(false); let execute = thread.run_expr_async::("test", source).map_ok(|_| ()); futures::pin_mut!(execute); let mut result = Poll::Pending; let mut lines = Vec::new(); futures::executor::block_on(future::lazy(|cx| loop { match &result { Poll::Ready(Ok(())) => break, Poll::Pending => { let context = thread.context(); let debug_info = context.debug_info(); lines.extend(debug_info.stack_info(0).expect("stack info").line()); } Poll::Ready(Err(err)) => panic!("{}", err), } result = execute.poll_unpin(cx); })); lines } #[test] fn line_hook() { let _ = env_logger::try_init(); let lines = run_line_hook_test(SIMPLE_EXPR); assert_eq!( lines, vec![1, 3, 4, 3, 1] .into_iter() .map(Line::from) .collect::>() ); } #[test] fn line_hook_recursive_functions() { let _ = env_logger::try_init(); let expr = r#" rec let f x = x let g y = f 1 "#; let lines = run_line_hook_test(expr); assert_eq!( lines, vec![1, 2, 3, 4] .into_iter() .map(Line::from) .collect::>() ); } #[test] fn line_hook_after_call() { let _ = env_logger::try_init(); let thread = new_vm(); { let mut context = thread.context(); context.set_hook(Some(Box::new(move |_, _| Poll::Pending))); context.set_hook_mask(HookFlags::LINE_FLAG); } let expr = r#" let id x = x let _ = id 0 1 "#; thread.get_database_mut().implicit_prelude(false); let execute = thread.run_expr_async::("test", expr).map_ok(|_| ()); futures::pin_mut!(execute); let mut result = Poll::Pending; let mut lines = Vec::new(); futures::executor::block_on(future::lazy(|cx| loop { match &result { Poll::Ready(Ok(())) => break, Poll::Pending => { let context = thread.context(); let debug_info = context.debug_info(); lines.extend(debug_info.stack_info(0).and_then(|s| s.line())); } Poll::Ready(Err(err)) => panic!("{}", err), } result = execute.poll_unpin(cx); })); assert_eq!( lines, vec![1, 2, 1, 3] .into_iter() .map(Line::from) .collect::>() ); } #[test] fn implicit_prelude_lines_not_counted() { let _ = env_logger::try_init(); let thread = new_vm(); { let mut context = thread.context(); context.set_hook(Some(Box::new(move |_, debug_info| { eprintln!("{}", debug_info.stack_info(0).unwrap().source_name()); if debug_info.stack_info(0).unwrap().source_name() == "test" { Poll::Pending } else { Poll::Ready(Ok(())) } }))); context.set_hook_mask(HookFlags::LINE_FLAG); } let execute = thread.run_expr_async::("test", "1").map_ok(|_| ()); futures::pin_mut!(execute); let mut result = Poll::Pending; let mut lines = Vec::new(); futures::executor::block_on(future::lazy(|cx| loop { match &result { Poll::Ready(Ok(())) => break, Poll::Pending => { let context = thread.context(); let debug_info = context.debug_info(); let stack_info = debug_info.stack_info(0).unwrap(); lines.extend(stack_info.line()); } Poll::Ready(Err(err)) => panic!("{}", err), } result = execute.poll_unpin(cx); })); assert_eq!(lines, vec![Line::from(0)]); } #[test] fn read_variables() { let _ = env_logger::try_init(); let thread = new_vm(); let result = Arc::new(Mutex::new(BTreeMap::new())); { let result = result.clone(); let mut context = thread.context(); context.set_hook(Some(Box::new(move |_, debug_context| { let stack_info = debug_context.stack_info(0).unwrap(); result.lock().unwrap().insert( stack_info.line().unwrap().to_usize(), stack_info .locals() .map(|local| (local.name.declared_name().to_string(), local.typ.clone())) .collect::>(), ); Poll::Ready(Ok(())) }))); context.set_hook_mask(HookFlags::LINE_FLAG); } let expr = r#" let x = 1 let y = let y2 = "" () let _ = () let z = 1.0 1 "#; thread.get_database_mut().implicit_prelude(false); thread .run_expr::("test", expr) .unwrap_or_else(|err| panic!("{}", err)); let map = result.lock().unwrap(); assert_eq!( *map, collect![ (1, vec![]), (3, vec![("x".to_string(), Type::int())]), ( 4, vec![ ("x".to_string(), Type::int()), ("y2".to_string(), Type::string()), ], ), ( 6, vec![ ("x".to_string(), Type::int()), ("y".to_string(), Type::unit()), ], ), ( 7, vec![ ("x".to_string(), Type::int()), ("y".to_string(), Type::unit()), ("_".to_string(), Type::unit()), ], ), ( 8, vec![ ("x".to_string(), Type::int()), ("y".to_string(), Type::unit()), ("_".to_string(), Type::unit()), ("z".to_string(), Type::float()), ], ), ] ); } #[test] fn argument_types() { let _ = env_logger::try_init(); let thread = new_vm(); let result = Arc::new(Mutex::new(Vec::new())); { let result = result.clone(); let mut context = thread.context(); context.set_hook(Some(Box::new(move |_, debug_context| { let stack_info = debug_context.stack_info(0).unwrap(); result.lock().unwrap().push(( stack_info.line().unwrap().to_usize(), stack_info .locals() .map(|local| (local.name.declared_name().to_string(), local.typ.clone())) .collect::>(), )); Poll::Ready(Ok(())) }))); context.set_hook_mask(HookFlags::LINE_FLAG); } let expr = r#" let int_function x: Int -> Int = x let g = \y -> int_function y let f z = g z f 1 "#; thread.get_database_mut().implicit_prelude(false); thread.run_expr::("test", expr).unwrap(); let int_function: ArcType = Type::function(vec![Type::int()], Type::int()); let map = result.lock().unwrap(); assert_eq!( *map, vec![ (1, vec![]), (2, vec![("int_function".to_string(), int_function.clone())]), ( 3, vec![ ("int_function".to_string(), int_function.clone()), ("g".to_string(), int_function.clone()), ], ), ( 4, vec![ ("int_function".to_string(), int_function.clone()), ("g".to_string(), int_function.clone()), ("f".to_string(), int_function.clone()), ], ), (3, vec![("z".to_string(), Type::int())]), (2, vec![("y".to_string(), Type::int())]), (1, vec![("x".to_string(), Type::int())]), ] ); } #[test] fn source_name() { let _ = env_logger::try_init(); let thread = new_vm(); let result = Arc::new(Mutex::new(String::new())); { let result = result.clone(); let mut context = thread.context(); context.set_hook(Some(Box::new(move |_, debug_context| { let stack_info = debug_context.stack_info(0).unwrap(); *result.lock().unwrap() = stack_info.source_name().to_string(); Poll::Ready(Ok(())) }))); context.set_hook_mask(HookFlags::LINE_FLAG); } let expr = r#" let x = 1 let y = let y2 = "" () let _ = () let z = { x } 1 "#; thread.get_database_mut().implicit_prelude(false); thread.run_expr::("test", expr).unwrap(); let name = result.lock().unwrap(); assert_eq!(*name, "test"); } #[test] fn upvars() { let _ = env_logger::try_init(); let thread = new_vm(); let result = Arc::new(Mutex::new(Vec::new())); { let result = result.clone(); let mut context = thread.context(); context.set_hook(Some(Box::new(move |_, debug_info| { let stack_info = debug_info.stack_info(0).unwrap(); if stack_info.source_name() == "test" { result.lock().unwrap().push(stack_info.upvars().to_owned()); } Poll::Ready(Ok(())) }))); context.set_hook_mask(HookFlags::CALL_FLAG); } let expr = r#" let x = 1 let y = 2 let f z = let g w = x g x #Int+ y #Int+ z f 3 "#; thread.get_database_mut().implicit_prelude(false); thread.run_expr::("test", expr).unwrap(); assert_eq!( *result.lock().unwrap(), vec![ vec![], vec![ UpvarInfo { name: "x".to_string(), typ: Type::int(), }, UpvarInfo { name: "y".to_string(), typ: Type::int(), }, ], vec![UpvarInfo { name: "x".to_string(), typ: Type::int(), }], ] ); } #[test] fn implicit_prelude_variable_names() { let _ = env_logger::try_init(); let thread = new_vm(); let functions = Arc::new(Mutex::new(Vec::::new())); { let functions = functions.clone(); let mut context = thread.context(); context.set_hook(Some(Box::new(move |_, debug_context| { eprintln!("1123"); let stack_info = debug_context.stack_info(0).unwrap(); functions.lock().unwrap().extend( stack_info .locals() .filter(|local| local.name.declared_name() == "__implicit_prelude") .map(|local| local.typ.clone()), ); Poll::Ready(Ok(())) }))); context.set_hook_mask(HookFlags::LINE_FLAG); } thread.run_expr::("test", "\n1").unwrap(); let f = functions.lock().unwrap(); match *f[0] { Type::Record(ref row) => { assert!(row .row_iter() .any(|field| field.name.declared_name() == "+")); } _ => panic!("{:#?}", f[0]), } }