From 73fbc0e490e3aa8753fd563b8e7831feb8bd8ca3 Mon Sep 17 00:00:00 2001 From: Richard Feldman Date: Sat, 18 Apr 2020 15:32:04 -0400 Subject: [PATCH] Add basic 1-iteration repl --- cli/src/main.rs | 8 +- cli/src/repl.rs | 613 ++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 620 insertions(+), 1 deletion(-) create mode 100644 cli/src/repl.rs diff --git a/cli/src/main.rs b/cli/src/main.rs index 0ab5a5cab5..f2e47dc71f 100644 --- a/cli/src/main.rs +++ b/cli/src/main.rs @@ -2,7 +2,6 @@ extern crate roc_gen; extern crate roc_reporting; #[macro_use] extern crate clap; - use bumpalo::Bump; use inkwell::context::Context; use inkwell::module::Linkage; @@ -32,6 +31,8 @@ use target_lexicon::{Architecture, OperatingSystem, Triple, Vendor}; use tokio::process::Command; use tokio::runtime::Builder; +pub mod repl; + pub static FLAG_OPTIMIZE: &str = "optimize"; pub static FLAG_ROC_FILE: &str = "ROC_FILE"; @@ -66,6 +67,9 @@ pub fn build_app<'a>() -> App<'a> { .required(false), ) ) + .subcommand(App::new("repl") + .about("Launch the interactive Read Eval Print Loop (REPL)") + ) } fn main() -> io::Result<()> { @@ -74,10 +78,12 @@ fn main() -> io::Result<()> { match matches.subcommand_name() { Some("build") => build(matches.subcommand_matches("build").unwrap(), false), Some("run") => build(matches.subcommand_matches("run").unwrap(), true), + Some("repl") => repl::main(), _ => unreachable!(), } } + pub fn build(matches: &ArgMatches, run_after_build: bool) -> io::Result<()> { let filename = matches.value_of(FLAG_ROC_FILE).unwrap(); let opt_level = if matches.is_present(FLAG_OPTIMIZE) { diff --git a/cli/src/repl.rs b/cli/src/repl.rs new file mode 100644 index 0000000000..fa741f5633 --- /dev/null +++ b/cli/src/repl.rs @@ -0,0 +1,613 @@ +use bumpalo::Bump; +use inkwell::context::Context; +use inkwell::execution_engine::JitFunction; +use inkwell::passes::PassManager; +use inkwell::types::BasicType; +use inkwell::OptimizationLevel; +use roc_builtins::unique::uniq_stdlib; +use roc_can::constraint::Constraint; +use roc_can::env::Env; +use roc_can::expected::Expected; +use roc_can::expr::{canonicalize_expr, Output}; +use roc_can::operator; +use roc_can::scope::Scope; +use roc_collections::all::{ImMap, ImSet, MutMap, SendMap, SendSet}; +use roc_constrain::expr::constrain_expr; +use roc_constrain::module::{constrain_imported_values, load_builtin_aliases, Import}; +use roc_gen::llvm::build::{build_proc, build_proc_header, OptLevel}; +use roc_gen::llvm::convert::basic_type_from_layout; +use roc_module::ident::Ident; +use roc_module::symbol::{IdentIds, Interns, ModuleId, ModuleIds, Symbol}; +use roc_mono::expr::Procs; +use roc_mono::layout::Layout; +use roc_parse::ast::{self, Attempting}; +use roc_parse::blankspace::space0_before; +use roc_parse::parser::{loc, Fail, Parser, State}; +use roc_problem::can::Problem; +use roc_region::all::{Located, Region}; +use roc_solve::solve; +use roc_types::subs::{Content, Subs, VarStore, Variable}; +use roc_types::types::Type; +use std::hash::Hash; +use std::io::{self, Write}; +use std::path::PathBuf; +use target_lexicon::Triple; + +pub fn main() -> io::Result<()> { + use std::io::BufRead; + + print!("▶ "); + + io::stdout().flush().unwrap(); + + let stdin = io::stdin(); + let line = stdin + .lock() + .lines() + .next() + .expect("there was no next line") + .expect("the line could not be read"); + + let out = gen(line.as_str(), Triple::host(), OptLevel::Normal); + + println!("{}", out); + + Ok(()) +} + +pub fn repl_home() -> ModuleId { + ModuleIds::default().get_or_insert(&"REPL".into()) +} + +pub fn gen(src: &str, target: Triple, opt_level: OptLevel) -> String { + use roc_reporting::report::{can_problem, type_problem, RocDocAllocator, DEFAULT_PALETTE}; + + // Look up the types and expressions of the `provided` values + let ptr_bytes = target.pointer_width().unwrap().bytes() as u32; + let arena = Bump::new(); + let CanExprOut { + loc_expr, + var_store, + var, + constraint, + home, + interns, + problems: can_problems, + output: _, + } = can_expr(src); + let subs = Subs::new(var_store.into()); + let mut type_problems = Vec::new(); + let (content, mut subs) = infer_expr(subs, &mut type_problems, &constraint, var); + + // Report problems + let src_lines: Vec<&str> = src.split('\n').collect(); + let palette = DEFAULT_PALETTE; + + // Report parsing and canonicalization problems + let alloc = RocDocAllocator::new(&src_lines, home, &interns); + + // Used for reporting where an error came from. + // + // TODO: maybe Reporting should have this be an Option? + let path = PathBuf::new(); + + for problem in can_problems.into_iter() { + let report = can_problem(&alloc, path.clone(), problem); + let mut buf = String::new(); + + report.render_color_terminal(&mut buf, &alloc, &palette); + + println!("\n{}\n", buf); + } + + for problem in type_problems.into_iter() { + let report = type_problem(&alloc, path.clone(), problem); + let mut buf = String::new(); + + report.render_color_terminal(&mut buf, &alloc, &palette); + + println!("\n{}\n", buf); + } + + let context = Context::create(); + let module = roc_gen::llvm::build::module_from_builtins(&context, "app"); + let builder = context.create_builder(); + let fpm = PassManager::create(&module); + + roc_gen::llvm::build::add_passes(&fpm, opt_level); + + fpm.initialize(); + + // Compute main_fn_type before moving subs to Env + let layout = Layout::from_content(&arena, content, &subs, ptr_bytes).unwrap_or_else(|err| { + panic!( + "Code gen error in test: could not convert to layout. Err was {:?} and Subs were {:?}", + err, subs + ) + }); + let execution_engine = module + .create_jit_execution_engine(OptimizationLevel::None) + .expect("Error creating JIT execution engine for test"); + + let main_fn_type = + basic_type_from_layout(&arena, &context, &layout, ptr_bytes).fn_type(&[], false); + let main_fn_name = "$Test.main"; + + // Compile and add all the Procs before adding main + let mut env = roc_gen::llvm::build::Env { + arena: &arena, + builder: &builder, + context: &context, + interns, + module: arena.alloc(module), + ptr_bytes, + }; + let mut procs = Procs::default(); + let mut ident_ids = env.interns.all_ident_ids.remove(&home).unwrap(); + + // Populate Procs and get the low-level Expr from the canonical Expr + let mut mono_problems = Vec::new(); + let main_body = roc_mono::expr::Expr::new( + &arena, + &mut subs, + &mut mono_problems, + loc_expr.value, + &mut procs, + home, + &mut ident_ids, + ptr_bytes, + ); + + // Put this module's ident_ids back in the interns, so we can use them in Env. + env.interns.all_ident_ids.insert(home, ident_ids); + + let mut headers = Vec::with_capacity(procs.len()); + + // Add all the Proc headers to the module. + // We have to do this in a separate pass first, + // because their bodies may reference each other. + for (symbol, opt_proc) in procs.as_map().into_iter() { + if let Some(proc) = opt_proc { + let (fn_val, arg_basic_types) = build_proc_header(&env, symbol, &proc); + + headers.push((proc, fn_val, arg_basic_types)); + } + } + + // Build each proc using its header info. + for (proc, fn_val, arg_basic_types) in headers { + // NOTE: This is here to be uncommented in case verification fails. + // (This approach means we don't have to defensively clone name here.) + // + // println!("\n\nBuilding and then verifying function {}\n\n", name); + build_proc(&env, proc, &procs, fn_val, arg_basic_types); + + if fn_val.verify(true) { + fpm.run_on(&fn_val); + } else { + // NOTE: If this fails, uncomment the above println to debug. + panic!( + "Non-main function failed LLVM verification. Uncomment the above println to debug!" + ); + } + } + + // Add main to the module. + let main_fn = env.module.add_function(main_fn_name, main_fn_type, None); + let cc = + roc_gen::llvm::build::get_call_conventions(target.default_calling_convention().unwrap()); + + main_fn.set_call_conventions(cc); + + // Add main's body + let basic_block = context.append_basic_block(main_fn, "entry"); + + builder.position_at_end(basic_block); + + let ret = roc_gen::llvm::build::build_expr( + &env, + &ImMap::default(), + main_fn, + &main_body, + &mut Procs::default(), + ); + + builder.build_return(Some(&ret)); + + // Uncomment this to see the module's un-optimized LLVM instruction output: + // env.module.print_to_stderr(); + + if main_fn.verify(true) { + fpm.run_on(&main_fn); + } else { + panic!("Function {} failed LLVM verification.", main_fn_name); + } + + // Verify the module + if let Err(errors) = env.module.verify() { + panic!("Errors defining module: {:?}", errors); + } + + // Uncomment this to see the module's optimized LLVM instruction output: + // env.module.print_to_stderr(); + + unsafe { + let main: JitFunction i64> = execution_engine + .get_function(main_fn_name) + .ok() + .ok_or(format!("Unable to JIT compile `{}`", main_fn_name)) + .expect("errored"); + + format!("{}", main.call()) + } +} + +pub fn infer_expr( + subs: Subs, + problems: &mut Vec, + constraint: &Constraint, + expr_var: Variable, +) -> (Content, Subs) { + let env = solve::Env { + aliases: MutMap::default(), + vars_by_symbol: SendMap::default(), + }; + let (solved, _) = solve::run(&env, problems, subs, constraint); + + let content = solved.inner().get_without_compacting(expr_var).content; + + (content, solved.into_inner()) +} + +pub fn parse_with<'a>(arena: &'a Bump, input: &'a str) -> Result, Fail> { + parse_loc_with(arena, input).map(|loc_expr| loc_expr.value) +} + +pub fn parse_loc_with<'a>(arena: &'a Bump, input: &'a str) -> Result>, Fail> { + let state = State::new(&input, Attempting::Module); + let parser = space0_before(loc(roc_parse::expr::expr(0)), 0); + let answer = parser.parse(&arena, state); + + answer + .map(|(loc_expr, _)| loc_expr) + .map_err(|(fail, _)| fail) +} + +pub fn can_expr(expr_str: &str) -> CanExprOut { + can_expr_with(&Bump::new(), repl_home(), expr_str) +} + +pub fn uniq_expr( + expr_str: &str, +) -> ( + Located, + Output, + Vec, + Subs, + Variable, + Constraint, + ModuleId, + Interns, +) { + let declared_idents: &ImMap = &ImMap::default(); + + uniq_expr_with(&Bump::new(), expr_str, declared_idents) +} + +pub fn uniq_expr_with( + arena: &Bump, + expr_str: &str, + declared_idents: &ImMap, +) -> ( + Located, + Output, + Vec, + Subs, + Variable, + Constraint, + ModuleId, + Interns, +) { + let home = repl_home(); + let CanExprOut { + loc_expr, + output, + problems, + var_store: old_var_store, + var, + interns, + .. + } = can_expr_with(arena, home, expr_str); + + // double check + let var_store = VarStore::new(old_var_store.fresh()); + + let expected2 = Expected::NoExpectation(Type::Variable(var)); + let constraint = roc_constrain::uniq::constrain_declaration( + home, + &var_store, + Region::zero(), + &loc_expr, + declared_idents, + expected2, + ); + + let stdlib = uniq_stdlib(); + + let types = stdlib.types; + let imports: Vec<_> = types + .iter() + .map(|(symbol, (solved_type, region))| Import { + loc_symbol: Located::at(*region, *symbol), + solved_type, + }) + .collect(); + + // load builtin values + + // TODO what to do with those rigids? + let (_introduced_rigids, constraint) = + constrain_imported_values(imports, constraint, &var_store); + + // load builtin types + let mut constraint = load_builtin_aliases(&stdlib.aliases, constraint, &var_store); + + constraint.instantiate_aliases(&var_store); + + let subs2 = Subs::new(var_store.into()); + + ( + loc_expr, output, problems, subs2, var, constraint, home, interns, + ) +} + +pub struct CanExprOut { + pub loc_expr: Located, + pub output: Output, + pub problems: Vec, + pub home: ModuleId, + pub interns: Interns, + pub var_store: VarStore, + pub var: Variable, + pub constraint: Constraint, +} + +pub fn can_expr_with(arena: &Bump, home: ModuleId, expr_str: &str) -> CanExprOut { + let loc_expr = parse_loc_with(&arena, expr_str).unwrap_or_else(|e| { + panic!( + "can_expr_with() got a parse error when attempting to canonicalize:\n\n{:?} {:?}", + expr_str, e + ) + }); + + let var_store = VarStore::default(); + let var = var_store.fresh(); + let expected = Expected::NoExpectation(Type::Variable(var)); + let module_ids = ModuleIds::default(); + + // Desugar operators (convert them to Apply calls, taking into account + // operator precedence and associativity rules), before doing other canonicalization. + // + // If we did this *during* canonicalization, then each time we + // visited a BinOp node we'd recursively try to apply this to each of its nested + // operators, and then again on *their* nested operators, ultimately applying the + // rules multiple times unnecessarily. + let loc_expr = operator::desugar_expr(arena, &loc_expr); + + let mut scope = Scope::new(home); + let dep_idents = IdentIds::exposed_builtins(0); + let mut env = Env::new(home, dep_idents, &module_ids, IdentIds::default()); + let (loc_expr, output) = canonicalize_expr( + &mut env, + &var_store, + &mut scope, + Region::zero(), + &loc_expr.value, + ); + + let constraint = constrain_expr( + &roc_constrain::expr::Env { + rigids: ImMap::default(), + home, + }, + loc_expr.region, + &loc_expr.value, + expected, + ); + + let types = roc_builtins::std::types(); + + let imports: Vec<_> = types + .iter() + .map(|(symbol, (solved_type, region))| Import { + loc_symbol: Located::at(*region, *symbol), + solved_type, + }) + .collect(); + + //load builtin values + let (_introduced_rigids, constraint) = + constrain_imported_values(imports, constraint, &var_store); + + // TODO determine what to do with those rigids + // for var in introduced_rigids { + // output.ftv.insert(var, format!("internal_{:?}", var).into()); + // } + + //load builtin types + let mut constraint = + load_builtin_aliases(&roc_builtins::std::aliases(), constraint, &var_store); + + constraint.instantiate_aliases(&var_store); + + let mut all_ident_ids = MutMap::default(); + + // When pretty printing types, we may need the exposed builtins, + // so include them in the Interns we'll ultimately return. + for (module_id, ident_ids) in IdentIds::exposed_builtins(0) { + all_ident_ids.insert(module_id, ident_ids); + } + + all_ident_ids.insert(home, env.ident_ids); + + let interns = Interns { + module_ids: env.module_ids.clone(), + all_ident_ids, + }; + + CanExprOut { + loc_expr, + output, + problems: env.problems, + home: env.home, + var_store, + interns, + var, + constraint, + } +} + +pub fn mut_map_from_pairs(pairs: I) -> MutMap +where + I: IntoIterator, + K: Hash + Eq, +{ + let mut answer = MutMap::default(); + + for (key, value) in pairs { + answer.insert(key, value); + } + + answer +} + +pub fn im_map_from_pairs(pairs: I) -> ImMap +where + I: IntoIterator, + K: Hash + Eq + Clone, + V: Clone, +{ + let mut answer = ImMap::default(); + + for (key, value) in pairs { + answer.insert(key, value); + } + + answer +} + +pub fn send_set_from(elems: I) -> SendSet +where + I: IntoIterator, + V: Hash + Eq + Clone, +{ + let mut answer = SendSet::default(); + + for elem in elems { + answer.insert(elem); + } + + answer +} + +// Check constraints +// +// Keep track of the used (in types or expectations) variables, and the declared variables (in +// flex_vars or rigid_vars fields of LetConstraint. These roc_collections should match: no duplicates +// and no variables that are used but not declared are allowed. +// +// There is one exception: the initial variable (that stores the type of the whole expression) is +// never declared, but is used. +pub fn assert_correct_variable_usage(constraint: &Constraint) { + // variables declared in constraint (flex_vars or rigid_vars) + // and variables actually used in constraints + let (declared, used) = variable_usage(constraint); + + let used: ImSet = used.into(); + let mut decl: ImSet = declared.rigid_vars.clone().into(); + + for var in declared.flex_vars.clone() { + decl.insert(var); + } + + let diff = used.clone().relative_complement(decl); + + // NOTE: this checks whether we're using variables that are not declared. For recursive type + // definitions, their rigid types are declared twice, which is correct! + if !diff.is_empty() { + println!("VARIABLE USAGE PROBLEM"); + + println!("used: {:?}", &used); + println!("rigids: {:?}", &declared.rigid_vars); + println!("flexs: {:?}", &declared.flex_vars); + + println!("difference: {:?}", &diff); + + panic!("variable usage problem (see stdout for details)"); + } +} + +#[derive(Default)] +pub struct SeenVariables { + pub rigid_vars: Vec, + pub flex_vars: Vec, +} + +pub fn variable_usage(con: &Constraint) -> (SeenVariables, Vec) { + let mut declared = SeenVariables::default(); + let mut used = ImSet::default(); + variable_usage_help(con, &mut declared, &mut used); + + used.remove(unsafe { &Variable::unsafe_test_debug_variable(1) }); + + let mut used_vec: Vec = used.into_iter().collect(); + used_vec.sort(); + + declared.rigid_vars.sort(); + declared.flex_vars.sort(); + + (declared, used_vec) +} + +fn variable_usage_help(con: &Constraint, declared: &mut SeenVariables, used: &mut ImSet) { + use Constraint::*; + + match con { + True | SaveTheEnvironment => (), + Eq(tipe, expectation, _, _) => { + for v in tipe.variables() { + used.insert(v); + } + + for v in expectation.get_type_ref().variables() { + used.insert(v); + } + } + Lookup(_, expectation, _) => { + for v in expectation.get_type_ref().variables() { + used.insert(v); + } + } + Pattern(_, _, tipe, pexpectation) => { + for v in tipe.variables() { + used.insert(v); + } + + for v in pexpectation.get_type_ref().variables() { + used.insert(v); + } + } + Let(letcon) => { + declared.rigid_vars.extend(letcon.rigid_vars.clone()); + declared.flex_vars.extend(letcon.flex_vars.clone()); + + variable_usage_help(&letcon.defs_constraint, declared, used); + variable_usage_help(&letcon.ret_constraint, declared, used); + } + And(constraints) => { + for sub in constraints { + variable_usage_help(sub, declared, used); + } + } + } +}