roc/crates/repl_cli/src/lib.rs
2022-10-27 18:47:27 -04:00

280 lines
7.9 KiB
Rust
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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<Return, F>(&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<T, F>(
&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:
// <UNCOMMENT> env_logger::init();
// <RUN WITH:> RUST_LOG=rustyline=debug cargo run repl 2> debug.log
print!("{}{}", WELCOME_MESSAGE, SHORT_INSTRUCTIONS);
let mut editor = Editor::<ReplState>::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;
}
}
}
}