mirror of
https://github.com/roc-lang/roc.git
synced 2025-09-26 13:29:12 +00:00
Reorganize repl modules
This commit is contained in:
parent
9f498add60
commit
a6c1667d37
9 changed files with 400 additions and 359 deletions
1
Cargo.lock
generated
1
Cargo.lock
generated
|
@ -3236,6 +3236,7 @@ dependencies = [
|
||||||
"roc_cli",
|
"roc_cli",
|
||||||
"roc_repl_cli",
|
"roc_repl_cli",
|
||||||
"roc_test_utils",
|
"roc_test_utils",
|
||||||
|
"rustyline",
|
||||||
"strip-ansi-escapes",
|
"strip-ansi-escapes",
|
||||||
"wasmer",
|
"wasmer",
|
||||||
"wasmer-wasi",
|
"wasmer-wasi",
|
||||||
|
|
|
@ -177,14 +177,7 @@ fn main() -> io::Result<()> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Some((CMD_REPL, _)) => {
|
Some((CMD_REPL, _)) => Ok(roc_repl_cli::main()),
|
||||||
{
|
|
||||||
roc_repl_cli::main()?;
|
|
||||||
|
|
||||||
// Exit 0 if the repl exited normally
|
|
||||||
Ok(0)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Some((CMD_EDIT, matches)) => {
|
Some((CMD_EDIT, matches)) => {
|
||||||
match matches
|
match matches
|
||||||
.values_of_os(DIRECTORY_OR_FILES)
|
.values_of_os(DIRECTORY_OR_FILES)
|
||||||
|
|
270
crates/repl_cli/src/cli_gen.rs
Normal file
270
crates/repl_cli/src/cli_gen.rs
Normal file
|
@ -0,0 +1,270 @@
|
||||||
|
use bumpalo::Bump;
|
||||||
|
use inkwell::context::Context;
|
||||||
|
use libloading::Library;
|
||||||
|
use roc_build::link::llvm_module_to_dylib;
|
||||||
|
use roc_collections::all::MutSet;
|
||||||
|
use roc_gen_llvm::llvm::build::LlvmBackendMode;
|
||||||
|
use roc_gen_llvm::llvm::externs::add_default_roc_externs;
|
||||||
|
use roc_gen_llvm::{run_jit_function, run_jit_function_dynamic_type};
|
||||||
|
use roc_intern::SingleThreadedInterner;
|
||||||
|
use roc_load::{EntryPoint, MonomorphizedModule};
|
||||||
|
use roc_mono::ir::OptLevel;
|
||||||
|
use roc_mono::layout::Layout;
|
||||||
|
use roc_parse::ast::Expr;
|
||||||
|
use roc_parse::parser::SyntaxError;
|
||||||
|
use roc_repl_eval::eval::jit_to_ast;
|
||||||
|
use roc_repl_eval::gen::{compile_to_mono, format_answer, ReplOutput};
|
||||||
|
use roc_repl_eval::{ReplApp, ReplAppMemory};
|
||||||
|
use roc_reporting::report::DEFAULT_PALETTE;
|
||||||
|
use roc_std::RocStr;
|
||||||
|
use roc_target::TargetInfo;
|
||||||
|
use roc_types::pretty_print::{name_and_print_var, DebugPrint};
|
||||||
|
use roc_types::subs::Subs;
|
||||||
|
use target_lexicon::Triple;
|
||||||
|
|
||||||
|
pub fn gen_and_eval_llvm<'a>(
|
||||||
|
src: &str,
|
||||||
|
target: Triple,
|
||||||
|
opt_level: OptLevel,
|
||||||
|
val_name: String,
|
||||||
|
) -> Result<ReplOutput, SyntaxError<'a>> {
|
||||||
|
let arena = Bump::new();
|
||||||
|
let target_info = TargetInfo::from(&target);
|
||||||
|
|
||||||
|
let mut loaded = match compile_to_mono(&arena, src, target_info, DEFAULT_PALETTE) {
|
||||||
|
Ok(x) => x,
|
||||||
|
Err(prob_strings) => {
|
||||||
|
return Ok(ReplOutput::Problems(prob_strings));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
debug_assert_eq!(loaded.exposed_to_host.values.len(), 1);
|
||||||
|
let (main_fn_symbol, main_fn_var) = loaded.exposed_to_host.values.iter().next().unwrap();
|
||||||
|
let main_fn_symbol = *main_fn_symbol;
|
||||||
|
let main_fn_var = *main_fn_var;
|
||||||
|
|
||||||
|
// pretty-print the expr type string for later.
|
||||||
|
let expr_type_str = name_and_print_var(
|
||||||
|
main_fn_var,
|
||||||
|
&mut loaded.subs,
|
||||||
|
loaded.module_id,
|
||||||
|
&loaded.interns,
|
||||||
|
DebugPrint::NOTHING,
|
||||||
|
);
|
||||||
|
|
||||||
|
let (_, main_fn_layout) = match loaded.procedures.keys().find(|(s, _)| *s == main_fn_symbol) {
|
||||||
|
Some(layout) => *layout,
|
||||||
|
None => {
|
||||||
|
return Ok(ReplOutput::NoProblems {
|
||||||
|
expr: "<function>".to_string(),
|
||||||
|
expr_type: expr_type_str,
|
||||||
|
val_name,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let interns = loaded.interns.clone();
|
||||||
|
|
||||||
|
let (lib, main_fn_name, subs, layout_interner) =
|
||||||
|
mono_module_to_dylib(&arena, target, loaded, opt_level).expect("we produce a valid Dylib");
|
||||||
|
|
||||||
|
let mut app = CliApp { lib };
|
||||||
|
|
||||||
|
let res_answer = jit_to_ast(
|
||||||
|
&arena,
|
||||||
|
&mut app,
|
||||||
|
main_fn_name,
|
||||||
|
main_fn_layout,
|
||||||
|
main_fn_var,
|
||||||
|
&subs,
|
||||||
|
&interns,
|
||||||
|
layout_interner.into_global().fork(),
|
||||||
|
target_info,
|
||||||
|
);
|
||||||
|
|
||||||
|
Ok(format_answer(&arena, res_answer, expr_type_str, val_name))
|
||||||
|
}
|
||||||
|
|
||||||
|
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 _)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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))
|
||||||
|
}
|
|
@ -1,28 +1,12 @@
|
||||||
|
mod cli_gen;
|
||||||
mod colors;
|
mod colors;
|
||||||
mod repl_state;
|
pub 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 colors::{BLUE, END_COL, PINK};
|
||||||
use roc_build::link::llvm_module_to_dylib;
|
use const_format::concatcp;
|
||||||
use roc_collections::all::MutSet;
|
use repl_state::ReplState;
|
||||||
use roc_gen_llvm::llvm::externs::add_default_roc_externs;
|
|
||||||
use roc_gen_llvm::{run_jit_function, run_jit_function_dynamic_type};
|
use crate::repl_state::PROMPT;
|
||||||
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!(
|
pub const WELCOME_MESSAGE: &str = concatcp!(
|
||||||
"\n The rockin’ ",
|
"\n The rockin’ ",
|
||||||
|
@ -38,194 +22,7 @@ pub const WELCOME_MESSAGE: &str = concatcp!(
|
||||||
|
|
||||||
// For when nothing is entered in the repl
|
// For when nothing is entered in the repl
|
||||||
// TODO add link to repl tutorial(does not yet exist).
|
// 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";
|
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 {
|
pub fn main() -> i32 {
|
||||||
use rustyline::error::ReadlineError;
|
use rustyline::error::ReadlineError;
|
||||||
|
@ -250,11 +47,13 @@ pub fn main() -> i32 {
|
||||||
|
|
||||||
let repl_helper = editor.helper_mut().expect("Editor helper was not set");
|
let repl_helper = editor.helper_mut().expect("Editor helper was not set");
|
||||||
|
|
||||||
dbg!(&editor);
|
match repl_helper.step(trim_line) {
|
||||||
|
|
||||||
match step_repl_state(repl_helper, trim_line) {
|
|
||||||
Ok(output) => {
|
Ok(output) => {
|
||||||
print!("{}", output);
|
// If there was no output, don't print a blank line!
|
||||||
|
// (This happens for something like a type annotation.)
|
||||||
|
if !output.is_empty() {
|
||||||
|
println!("{}", output);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
Err(exit_code) => return exit_code,
|
Err(exit_code) => return exit_code,
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,20 +1,23 @@
|
||||||
use crate::colors::{BLUE, END_COL, PINK};
|
use crate::cli_gen::gen_and_eval_llvm;
|
||||||
|
use crate::colors::{BLUE, END_COL, GREEN, PINK};
|
||||||
use bumpalo::Bump;
|
use bumpalo::Bump;
|
||||||
use const_format::concatcp;
|
use const_format::concatcp;
|
||||||
use roc_parse::ast::ValueDef;
|
use roc_mono::ir::OptLevel;
|
||||||
use roc_parse::expr::{parse_single_def, ExprParseOptions};
|
use roc_parse::ast::{Expr, TypeDef, ValueDef};
|
||||||
use roc_parse::parser::{EClosure, EExpr, Parser};
|
use roc_parse::expr::{parse_single_def, ExprParseOptions, SingleDef};
|
||||||
|
use roc_parse::parser::{EClosure, EExpr};
|
||||||
use roc_parse::parser::{Either, SyntaxError};
|
use roc_parse::parser::{Either, SyntaxError};
|
||||||
use roc_parse::state::State;
|
use roc_parse::state::State;
|
||||||
use roc_repl_eval::eval::jit_to_ast;
|
use roc_repl_eval::gen::ReplOutput;
|
||||||
use roc_repl_eval::gen::{compile_to_mono, format_answer, ReplOutput};
|
|
||||||
use roc_reporting::report::DEFAULT_PALETTE;
|
|
||||||
use roc_types::pretty_print::{name_and_print_var, DebugPrint};
|
|
||||||
use rustyline::highlight::{Highlighter, PromptInfo};
|
use rustyline::highlight::{Highlighter, PromptInfo};
|
||||||
use rustyline::validate::{self, ValidationContext, ValidationResult, Validator};
|
use rustyline::validate::{self, ValidationContext, ValidationResult, Validator};
|
||||||
use rustyline_derive::{Completer, Helper, Hinter};
|
use rustyline_derive::{Completer, Helper, Hinter};
|
||||||
use std::borrow::Cow;
|
use std::borrow::Cow;
|
||||||
use std::collections::LinkedList;
|
use std::collections::LinkedList;
|
||||||
|
use target_lexicon::Triple;
|
||||||
|
|
||||||
|
pub const PROMPT: &str = concatcp!("\n", BLUE, "»", END_COL, " ");
|
||||||
|
pub const CONT_PROMPT: &str = concatcp!(BLUE, "…", END_COL, " ");
|
||||||
|
|
||||||
// TODO add link to repl tutorial(does not yet exist).
|
// TODO add link to repl tutorial(does not yet exist).
|
||||||
pub const TIPS: &str = concatcp!(
|
pub const TIPS: &str = concatcp!(
|
||||||
|
@ -39,17 +42,17 @@ pub const TIPS: &str = concatcp!(
|
||||||
":help\n"
|
":help\n"
|
||||||
);
|
);
|
||||||
|
|
||||||
pub struct PastDef {
|
struct PastDef {
|
||||||
ident: String,
|
_ident: String,
|
||||||
src: String,
|
_src: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Completer, Helper, Hinter)]
|
#[derive(Completer, Helper, Hinter)]
|
||||||
pub(crate) struct ReplState {
|
pub struct ReplState {
|
||||||
validator: InputValidator,
|
validator: InputValidator,
|
||||||
prev_line_blank: bool,
|
prev_line_blank: bool,
|
||||||
pending_src: String,
|
pending_src: String,
|
||||||
past_defs: LinkedList<PastDef>,
|
_past_defs: LinkedList<PastDef>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ReplState {
|
impl ReplState {
|
||||||
|
@ -58,11 +61,11 @@ impl ReplState {
|
||||||
validator: InputValidator::new(),
|
validator: InputValidator::new(),
|
||||||
prev_line_blank: false,
|
prev_line_blank: false,
|
||||||
pending_src: String::new(),
|
pending_src: String::new(),
|
||||||
past_defs: Default::default(),
|
_past_defs: Default::default(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn step(&mut self, trim_line: &str) -> Result<String, i32> {
|
pub fn step(&mut self, trim_line: &str) -> Result<String, i32> {
|
||||||
match trim_line.to_lowercase().as_str() {
|
match trim_line.to_lowercase().as_str() {
|
||||||
"" => {
|
"" => {
|
||||||
if self.pending_src.is_empty() {
|
if self.pending_src.is_empty() {
|
||||||
|
@ -114,24 +117,12 @@ impl ReplState {
|
||||||
self.pending_src.as_str()
|
self.pending_src.as_str()
|
||||||
};
|
};
|
||||||
|
|
||||||
dbg!(&src);
|
let arena = Bump::new();
|
||||||
|
|
||||||
// First, try to parse it as a Def. If that succeeds, record it in self and continue.
|
let src = match parse_src(&arena, src) {
|
||||||
let src = match parse_single_def(
|
ParseOutcome::Expr(_) => src,
|
||||||
ExprParseOptions {
|
ParseOutcome::ValueDef(value_def) => {
|
||||||
accept_multi_backpassing: true,
|
match value_def {
|
||||||
check_for_arrow: true,
|
|
||||||
},
|
|
||||||
0,
|
|
||||||
&Bump::new(),
|
|
||||||
State::new(src.as_bytes()),
|
|
||||||
) {
|
|
||||||
Ok((_, Some(single_def), _)) => match single_def.type_or_value {
|
|
||||||
Either::First(type_def) => {
|
|
||||||
// Alias, Opaque, or Ability
|
|
||||||
todo!("handle Alias, Opaque, or Ability")
|
|
||||||
}
|
|
||||||
Either::Second(value_def) => match value_def {
|
|
||||||
ValueDef::Annotation(_, _) => {
|
ValueDef::Annotation(_, _) => {
|
||||||
// Needed to avoid a borrow error.
|
// Needed to avoid a borrow error.
|
||||||
let src = src.to_string();
|
let src = src.to_string();
|
||||||
|
@ -145,13 +136,13 @@ impl ReplState {
|
||||||
self.pending_src.push_str(src.as_str());
|
self.pending_src.push_str(src.as_str());
|
||||||
self.pending_src.push('\n');
|
self.pending_src.push('\n');
|
||||||
|
|
||||||
// Return without clearing pending_src.
|
// Return without running eval or clearing pending_src.
|
||||||
return Ok(String::new());
|
return Ok(String::new());
|
||||||
}
|
}
|
||||||
ValueDef::Body(loc_pattern, loc_expr)
|
ValueDef::Body(_loc_pattern, _loc_expr)
|
||||||
| ValueDef::AnnotatedBody {
|
| ValueDef::AnnotatedBody {
|
||||||
body_pattern: loc_pattern,
|
body_pattern: _loc_pattern,
|
||||||
body_expr: loc_expr,
|
body_expr: _loc_expr,
|
||||||
..
|
..
|
||||||
} => todo!("handle receiving a toplevel def of a value/function"),
|
} => todo!("handle receiving a toplevel def of a value/function"),
|
||||||
ValueDef::Expect { .. } => {
|
ValueDef::Expect { .. } => {
|
||||||
|
@ -160,12 +151,13 @@ impl ReplState {
|
||||||
ValueDef::ExpectFx { .. } => {
|
ValueDef::ExpectFx { .. } => {
|
||||||
todo!("handle receiving an `expect-fx` - what should the repl do for that?")
|
todo!("handle receiving an `expect-fx` - what should the repl do for that?")
|
||||||
}
|
}
|
||||||
},
|
|
||||||
},
|
|
||||||
Ok(_) => src,
|
|
||||||
Err((_, eexpr, _)) => {
|
|
||||||
return Err(fail);
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
ParseOutcome::TypeDef(_) => {
|
||||||
|
// Alias, Opaque, or Ability
|
||||||
|
todo!("handle Alias, Opaque, or Ability")
|
||||||
|
}
|
||||||
|
ParseOutcome::Incomplete => todo!(),
|
||||||
};
|
};
|
||||||
|
|
||||||
let answer = gen_and_eval_llvm(
|
let answer = gen_and_eval_llvm(
|
||||||
|
@ -182,11 +174,11 @@ impl ReplState {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Wrap the given expresssion in the appropriate past defs
|
/// Wrap the given expresssion in the appropriate past defs
|
||||||
fn wrapped_expr_src(&self, src: &str) -> String {
|
fn _wrapped_expr_src(&self, src: &str) -> String {
|
||||||
let mut buf = String::new();
|
let mut buf = String::new();
|
||||||
|
|
||||||
for past_def in self.past_defs.iter() {
|
for past_def in self._past_defs.iter() {
|
||||||
buf.push_str(past_def.src.as_str());
|
buf.push_str(past_def._src.as_str());
|
||||||
buf.push('\n');
|
buf.push('\n');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -196,6 +188,58 @@ impl ReplState {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
enum ParseOutcome<'a> {
|
||||||
|
ValueDef(ValueDef<'a>),
|
||||||
|
TypeDef(TypeDef<'a>),
|
||||||
|
Expr(Expr<'a>),
|
||||||
|
Incomplete,
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse_src<'a>(arena: &'a Bump, src: &'a str) -> ParseOutcome<'a> {
|
||||||
|
let src_bytes = src.trim().as_bytes();
|
||||||
|
|
||||||
|
match roc_parse::expr::parse_loc_expr(0, &arena, State::new(src_bytes)) {
|
||||||
|
Ok((_, loc_expr, _)) => ParseOutcome::Expr(loc_expr.value),
|
||||||
|
// Special case some syntax errors to allow for multi-line inputs
|
||||||
|
Err((_, EExpr::Closure(EClosure::Body(_, _), _), _)) => ParseOutcome::Incomplete,
|
||||||
|
Err((_, EExpr::DefMissingFinalExpr(_), _))
|
||||||
|
| Err((_, EExpr::DefMissingFinalExpr2(_, _), _)) => {
|
||||||
|
// This indicates that we had an attempted def; re-parse it as a single-line def.
|
||||||
|
match parse_single_def(
|
||||||
|
ExprParseOptions {
|
||||||
|
accept_multi_backpassing: true,
|
||||||
|
check_for_arrow: true,
|
||||||
|
},
|
||||||
|
0,
|
||||||
|
&arena,
|
||||||
|
State::new(src_bytes),
|
||||||
|
) {
|
||||||
|
Ok((
|
||||||
|
_,
|
||||||
|
Some(SingleDef {
|
||||||
|
type_or_value: Either::First(type_def),
|
||||||
|
..
|
||||||
|
}),
|
||||||
|
_,
|
||||||
|
)) => ParseOutcome::TypeDef(type_def),
|
||||||
|
Ok((
|
||||||
|
_,
|
||||||
|
Some(SingleDef {
|
||||||
|
type_or_value: Either::Second(value_def),
|
||||||
|
..
|
||||||
|
}),
|
||||||
|
_,
|
||||||
|
)) => ParseOutcome::ValueDef(value_def),
|
||||||
|
Ok((_, None, _)) => {
|
||||||
|
todo!("TODO determine appropriate ParseOutcome for Ok(None)")
|
||||||
|
}
|
||||||
|
Err(_) => ParseOutcome::Incomplete,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Err(_) => ParseOutcome::Incomplete,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
struct InputValidator {}
|
struct InputValidator {}
|
||||||
|
|
||||||
impl InputValidator {
|
impl InputValidator {
|
||||||
|
@ -206,36 +250,26 @@ impl InputValidator {
|
||||||
|
|
||||||
impl Validator for InputValidator {
|
impl Validator for InputValidator {
|
||||||
fn validate(&self, ctx: &mut ValidationContext) -> rustyline::Result<ValidationResult> {
|
fn validate(&self, ctx: &mut ValidationContext) -> rustyline::Result<ValidationResult> {
|
||||||
if ctx.input().is_empty() {
|
validate(ctx.input())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn validate(input: &str) -> rustyline::Result<ValidationResult> {
|
||||||
|
if input.is_empty() {
|
||||||
Ok(ValidationResult::Incomplete)
|
Ok(ValidationResult::Incomplete)
|
||||||
} else {
|
} else {
|
||||||
let arena = bumpalo::Bump::new();
|
let arena = Bump::new();
|
||||||
let state = roc_parse::state::State::new(ctx.input().trim().as_bytes());
|
|
||||||
let answer = match roc_parse::expr::toplevel_defs(0).parse(&arena, state) {
|
|
||||||
// Special case some syntax errors to allow for multi-line inputs
|
|
||||||
Err((_, EExpr::DefMissingFinalExpr(_), _))
|
|
||||||
| Err((_, EExpr::DefMissingFinalExpr2(_, _), _))
|
|
||||||
| Err((_, EExpr::Closure(EClosure::Body(_, _), _), _)) => {
|
|
||||||
Ok(ValidationResult::Incomplete)
|
|
||||||
}
|
|
||||||
Err((_, _, state)) => {
|
|
||||||
// It wasn't a valid top-level decl, so continue parsing it as an expr.
|
|
||||||
match roc_parse::expr::parse_loc_expr(0, &arena, state) {
|
|
||||||
// Special case some syntax errors to allow for multi-line inputs
|
|
||||||
Err((_, EExpr::DefMissingFinalExpr(_), _))
|
|
||||||
| Err((_, EExpr::DefMissingFinalExpr2(_, _), _))
|
|
||||||
| Err((_, EExpr::Closure(EClosure::Body(_, _), _), _)) => {
|
|
||||||
Ok(ValidationResult::Incomplete)
|
|
||||||
}
|
|
||||||
_ => Ok(ValidationResult::Valid(None)),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Ok(_) => Ok(ValidationResult::Valid(None)),
|
|
||||||
};
|
|
||||||
|
|
||||||
// This is necessary to extend the lifetime of `arena`; without it,
|
match parse_src(&arena, input) {
|
||||||
// we get a borrow checker error!
|
// Standalone annotations are default incomplete, because we can't know
|
||||||
answer
|
// whether they're about to annotate a body on the next line
|
||||||
|
// (or if not, meaning they stay standalone) until you press Enter again!
|
||||||
|
ParseOutcome::ValueDef(ValueDef::Annotation(_, _)) | ParseOutcome::Incomplete => {
|
||||||
|
Ok(ValidationResult::Incomplete)
|
||||||
|
}
|
||||||
|
ParseOutcome::ValueDef(_) | ParseOutcome::TypeDef(_) | ParseOutcome::Expr(_) => {
|
||||||
|
Ok(ValidationResult::Valid(None))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -289,66 +323,3 @@ fn format_output(output: ReplOutput) -> String {
|
||||||
ReplOutput::Problems(lines) => format!("\n{}\n", lines.join("\n\n")),
|
ReplOutput::Problems(lines) => format!("\n{}\n", lines.join("\n\n")),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn gen_and_eval_llvm<'a>(
|
|
||||||
src: &str,
|
|
||||||
target: Triple,
|
|
||||||
opt_level: OptLevel,
|
|
||||||
val_name: String,
|
|
||||||
) -> Result<ReplOutput, SyntaxError<'a>> {
|
|
||||||
let arena = Bump::new();
|
|
||||||
let target_info = TargetInfo::from(&target);
|
|
||||||
|
|
||||||
let mut loaded = match compile_to_mono(&arena, src, target_info, DEFAULT_PALETTE) {
|
|
||||||
Ok(x) => x,
|
|
||||||
Err(prob_strings) => {
|
|
||||||
return Ok(ReplOutput::Problems(prob_strings));
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
debug_assert_eq!(loaded.exposed_to_host.values.len(), 1);
|
|
||||||
let (main_fn_symbol, main_fn_var) = loaded.exposed_to_host.values.iter().next().unwrap();
|
|
||||||
let main_fn_symbol = *main_fn_symbol;
|
|
||||||
let main_fn_var = *main_fn_var;
|
|
||||||
|
|
||||||
// pretty-print the expr type string for later.
|
|
||||||
let expr_type_str = name_and_print_var(
|
|
||||||
main_fn_var,
|
|
||||||
&mut loaded.subs,
|
|
||||||
loaded.module_id,
|
|
||||||
&loaded.interns,
|
|
||||||
DebugPrint::NOTHING,
|
|
||||||
);
|
|
||||||
|
|
||||||
let (_, main_fn_layout) = match loaded.procedures.keys().find(|(s, _)| *s == main_fn_symbol) {
|
|
||||||
Some(layout) => *layout,
|
|
||||||
None => {
|
|
||||||
return Ok(ReplOutput::NoProblems {
|
|
||||||
expr: "<function>".to_string(),
|
|
||||||
expr_type: expr_type_str,
|
|
||||||
val_name,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
let interns = loaded.interns.clone();
|
|
||||||
|
|
||||||
let (lib, main_fn_name, subs, layout_interner) =
|
|
||||||
mono_module_to_dylib(&arena, target, loaded, opt_level).expect("we produce a valid Dylib");
|
|
||||||
|
|
||||||
let mut app = CliApp { lib };
|
|
||||||
|
|
||||||
let res_answer = jit_to_ast(
|
|
||||||
&arena,
|
|
||||||
&mut app,
|
|
||||||
main_fn_name,
|
|
||||||
main_fn_layout,
|
|
||||||
main_fn_var,
|
|
||||||
&subs,
|
|
||||||
&interns,
|
|
||||||
layout_interner.into_global().fork(),
|
|
||||||
target_info,
|
|
||||||
);
|
|
||||||
|
|
||||||
Ok(format_answer(&arena, res_answer, expr_type_str, val_name))
|
|
||||||
}
|
|
||||||
|
|
|
@ -15,6 +15,7 @@ lazy_static = "1.4.0"
|
||||||
indoc = "1.0.7"
|
indoc = "1.0.7"
|
||||||
strip-ansi-escapes = "0.1.1"
|
strip-ansi-escapes = "0.1.1"
|
||||||
wasmer-wasi = "2.2.1"
|
wasmer-wasi = "2.2.1"
|
||||||
|
rustyline = {git = "https://github.com/roc-lang/rustyline", rev = "e74333c"}
|
||||||
|
|
||||||
roc_repl_cli = {path = "../repl_cli"}
|
roc_repl_cli = {path = "../repl_cli"}
|
||||||
roc_test_utils = {path = "../test_utils"}
|
roc_test_utils = {path = "../test_utils"}
|
||||||
|
|
|
@ -3,7 +3,8 @@ use std::io::Write;
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
use std::process::{Command, ExitStatus, Stdio};
|
use std::process::{Command, ExitStatus, Stdio};
|
||||||
|
|
||||||
use roc_repl_cli::{TIPS, WELCOME_MESSAGE};
|
use roc_repl_cli::repl_state::TIPS;
|
||||||
|
use roc_repl_cli::WELCOME_MESSAGE;
|
||||||
use roc_test_utils::assert_multiline_str_eq;
|
use roc_test_utils::assert_multiline_str_eq;
|
||||||
|
|
||||||
const ERROR_MESSAGE_START: char = '─';
|
const ERROR_MESSAGE_START: char = '─';
|
||||||
|
|
|
@ -5,6 +5,9 @@ extern crate lazy_static;
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests;
|
mod tests;
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod state;
|
||||||
|
|
||||||
#[cfg(all(test, not(feature = "wasm")))]
|
#[cfg(all(test, not(feature = "wasm")))]
|
||||||
mod cli;
|
mod cli;
|
||||||
|
|
||||||
|
|
|
@ -270,6 +270,8 @@ pub async fn entrypoint_from_js(src: String) -> Result<String, String> {
|
||||||
target_info,
|
target_info,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
let val_name = String::new(); // TODO
|
||||||
|
|
||||||
// Transform the Expr to a string
|
// Transform the Expr to a string
|
||||||
// `Result::Err` becomes a JS exception that will be caught and displayed
|
// `Result::Err` becomes a JS exception that will be caught and displayed
|
||||||
match format_answer(arena, res_answer, expr_type_str, val_name) {
|
match format_answer(arena, res_answer, expr_type_str, val_name) {
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue