mod colors; mod repl_state; use bumpalo::Bump; use const_format::concatcp; use inkwell::context::Context; use libloading::Library; use repl_state::ReplState; use roc_gen_llvm::llvm::build::LlvmBackendMode; use roc_intern::SingleThreadedInterner; use roc_mono::layout::Layout; use roc_types::subs::Subs; use target_lexicon::Triple; use colors::{BLUE, END_COL, PINK}; use roc_build::link::llvm_module_to_dylib; use roc_collections::all::MutSet; use roc_gen_llvm::llvm::externs::add_default_roc_externs; use roc_gen_llvm::{run_jit_function, run_jit_function_dynamic_type}; use roc_load::{EntryPoint, MonomorphizedModule}; use roc_mono::ir::OptLevel; use roc_parse::ast::Expr; use roc_repl_eval::{ReplApp, ReplAppMemory}; use roc_std::RocStr; use roc_target::TargetInfo; pub const WELCOME_MESSAGE: &str = concatcp!( "\n The rockin’ ", BLUE, "roc repl", END_COL, "\n", PINK, "────────────────────────", END_COL, "\n\n" ); // For when nothing is entered in the repl // TODO add link to repl tutorial(does not yet exist). pub const SHORT_INSTRUCTIONS: &str = "Enter an expression, or :help, or :q to quit.\n"; pub const PROMPT: &str = concatcp!("\n", BLUE, "»", END_COL, " "); pub const CONT_PROMPT: &str = concatcp!(BLUE, "…", END_COL, " "); struct CliApp { lib: Library, } struct CliMemory; impl<'a> ReplApp<'a> for CliApp { type Memory = CliMemory; /// Run user code that returns a type with a `Builtin` layout /// Size of the return value is statically determined from its Rust type fn call_function(&mut self, main_fn_name: &str, mut transform: F) -> Expr<'a> where F: FnMut(&'a Self::Memory, Return) -> Expr<'a>, Self::Memory: 'a, { run_jit_function!(self.lib, main_fn_name, Return, |v| transform(&CliMemory, v)) } /// Run user code that returns a struct or union, whose size is provided as an argument fn call_function_dynamic_size( &mut self, main_fn_name: &str, ret_bytes: usize, mut transform: F, ) -> T where F: FnMut(&'a Self::Memory, usize) -> T, Self::Memory: 'a, { run_jit_function_dynamic_type!(self.lib, main_fn_name, ret_bytes, |v| transform( &CliMemory, v )) } } macro_rules! deref_number { ($name: ident, $t: ty) => { fn $name(&self, addr: usize) -> $t { let ptr = addr as *const _; unsafe { *ptr } } }; } impl ReplAppMemory for CliMemory { deref_number!(deref_bool, bool); deref_number!(deref_u8, u8); deref_number!(deref_u16, u16); deref_number!(deref_u32, u32); deref_number!(deref_u64, u64); deref_number!(deref_u128, u128); deref_number!(deref_usize, usize); deref_number!(deref_i8, i8); deref_number!(deref_i16, i16); deref_number!(deref_i32, i32); deref_number!(deref_i64, i64); deref_number!(deref_i128, i128); deref_number!(deref_isize, isize); deref_number!(deref_f32, f32); deref_number!(deref_f64, f64); fn deref_str(&self, addr: usize) -> &str { let reference: &RocStr = unsafe { std::mem::transmute(addr) }; reference.as_str() } fn deref_pointer_with_tag_id(&self, addr: usize) -> (u16, u64) { let addr_with_id = self.deref_usize(addr); let tag_id_mask = 0b111; let tag_id = addr_with_id & tag_id_mask; let data_addr = addr_with_id & !tag_id_mask; (tag_id as _, data_addr as _) } } pub fn mono_module_to_dylib<'a>( arena: &'a Bump, target: Triple, loaded: MonomorphizedModule<'a>, opt_level: OptLevel, ) -> Result< ( libloading::Library, &'a str, Subs, SingleThreadedInterner<'a, Layout<'a>>, ), libloading::Error, > { let target_info = TargetInfo::from(&target); let MonomorphizedModule { procedures, entry_point, interns, subs, layout_interner, .. } = loaded; let context = Context::create(); let builder = context.create_builder(); let module = arena.alloc(roc_gen_llvm::llvm::build::module_from_builtins( &target, &context, "", )); let module = arena.alloc(module); let (module_pass, function_pass) = roc_gen_llvm::llvm::build::construct_optimization_passes(module, opt_level); let (dibuilder, compile_unit) = roc_gen_llvm::llvm::build::Env::new_debug_info(module); // Compile and add all the Procs before adding main let env = roc_gen_llvm::llvm::build::Env { arena, layout_interner: &layout_interner, builder: &builder, dibuilder: &dibuilder, compile_unit: &compile_unit, context: &context, interns, module, target_info, mode: LlvmBackendMode::GenTest, // so roc_panic is generated // important! we don't want any procedures to get the C calling convention exposed_to_host: MutSet::default(), }; // Add roc_alloc, roc_realloc, and roc_dealloc, since the repl has no // platform to provide them. add_default_roc_externs(&env); let entry_point = match entry_point { EntryPoint::Executable { symbol, layout, .. } => { roc_mono::ir::EntryPoint { symbol, layout } } EntryPoint::Test => { unreachable!() } }; let (main_fn_name, main_fn) = roc_gen_llvm::llvm::build::build_procedures_return_main( &env, opt_level, procedures, entry_point, ); env.dibuilder.finalize(); // we don't use the debug info, and it causes weird errors. module.strip_debug_info(); // Uncomment this to see the module's un-optimized LLVM instruction output: // env.module.print_to_stderr(); if main_fn.verify(true) { function_pass.run_on(&main_fn); } else { panic!("Main function {} failed LLVM verification in build. Uncomment things nearby to see more details.", main_fn_name); } module_pass.run_on(env.module); // Uncomment this to see the module's optimized LLVM instruction output: // env.module.print_to_stderr(); // Verify the module if let Err(errors) = env.module.verify() { panic!( "Errors defining module:\n{}\n\nUncomment things nearby to see more details.", errors.to_string() ); } llvm_module_to_dylib(env.module, &target, opt_level) .map(|lib| (lib, main_fn_name, subs, layout_interner)) } pub fn main() -> i32 { use rustyline::error::ReadlineError; use rustyline::Editor; // To debug rustyline: // env_logger::init(); // RUST_LOG=rustyline=debug cargo run repl 2> debug.log print!("{}{}", WELCOME_MESSAGE, SHORT_INSTRUCTIONS); let mut editor = Editor::::new(); let repl_helper = ReplState::new(); editor.set_helper(Some(repl_helper)); loop { let readline = editor.readline(PROMPT); match readline { Ok(line) => { let trim_line = line.trim(); editor.add_history_entry(trim_line); let repl_helper = editor.helper_mut().expect("Editor helper was not set"); dbg!(&editor); match step_repl_state(repl_helper, trim_line) { Ok(output) => { print!("{}", output); } Err(exit_code) => return exit_code, }; } #[cfg(windows)] Err(Readline::WindowResize) => { // This is fine; just ignore it. } Err(ReadlineError::Eof) => { // End of input; we're done! return 0; } Err(ReadlineError::Interrupted) => { eprintln!("CTRL-C"); return 1; } Err(err) => { eprintln!("REPL error: {:?}", err); return 1; } } } }