mirror of
https://github.com/roc-lang/roc.git
synced 2025-08-01 10:52:18 +00:00
merge upstream/main
This commit is contained in:
commit
cec67721e6
59 changed files with 2542 additions and 990 deletions
|
@ -23,6 +23,7 @@ libloading = "0.7.1"
|
|||
rustyline = {git = "https://github.com/roc-lang/rustyline", rev = "e74333c"}
|
||||
rustyline-derive = {git = "https://github.com/roc-lang/rustyline", rev = "e74333c"}
|
||||
target-lexicon = "0.12.2"
|
||||
unicode-segmentation = "1.10.0"
|
||||
|
||||
roc_build = {path = "../compiler/build"}
|
||||
roc_builtins = {path = "../compiler/builtins"}
|
||||
|
|
281
crates/repl_cli/src/cli_gen.rs
Normal file
281
crates/repl_cli/src/cli_gen.rs
Normal file
|
@ -0,0 +1,281 @@
|
|||
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_repl_eval::eval::jit_to_ast;
|
||||
use roc_repl_eval::gen::{compile_to_mono, format_answer, Problems, 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, I: Iterator<Item = &'a str>>(
|
||||
defs: I,
|
||||
src: &str,
|
||||
target: Triple,
|
||||
opt_level: OptLevel,
|
||||
) -> (Option<ReplOutput>, Problems) {
|
||||
let arena = Bump::new();
|
||||
let target_info = TargetInfo::from(&target);
|
||||
|
||||
let mut loaded;
|
||||
let problems;
|
||||
|
||||
match compile_to_mono(&arena, defs, src, target_info, DEFAULT_PALETTE) {
|
||||
(Some(mono), probs) => {
|
||||
loaded = mono;
|
||||
problems = probs;
|
||||
}
|
||||
(None, probs) => {
|
||||
return (None, probs);
|
||||
}
|
||||
};
|
||||
|
||||
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 => {
|
||||
let empty_vec: Vec<String> = Vec::new(); // rustc can't infer the type of this Vec.
|
||||
debug_assert_ne!(problems.errors, empty_vec, "Got no errors but also no valid layout for the generated main function in the repl!");
|
||||
|
||||
return (None, problems);
|
||||
}
|
||||
};
|
||||
|
||||
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 expr = jit_to_ast(
|
||||
&arena,
|
||||
&mut app,
|
||||
main_fn_name,
|
||||
main_fn_layout,
|
||||
main_fn_var,
|
||||
&subs,
|
||||
&interns,
|
||||
layout_interner.into_global().fork(),
|
||||
target_info,
|
||||
);
|
||||
let expr_str = format_answer(&arena, expr).to_string();
|
||||
|
||||
(
|
||||
Some(ReplOutput {
|
||||
expr: expr_str,
|
||||
expr_type: expr_type_str,
|
||||
}),
|
||||
problems,
|
||||
)
|
||||
}
|
||||
|
||||
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))
|
||||
}
|
4
crates/repl_cli/src/colors.rs
Normal file
4
crates/repl_cli/src/colors.rs
Normal file
|
@ -0,0 +1,4 @@
|
|||
pub const BLUE: &str = "\u{001b}[36m";
|
||||
pub const PINK: &str = "\u{001b}[35m";
|
||||
pub const GREEN: &str = "\u{001b}[32m";
|
||||
pub const END_COL: &str = "\u{001b}[0m";
|
|
@ -1,38 +1,13 @@
|
|||
//! Command Line Interface (CLI) functionality for the Read-Evaluate-Print-Loop (REPL).
|
||||
use bumpalo::Bump;
|
||||
mod cli_gen;
|
||||
mod colors;
|
||||
pub mod repl_state;
|
||||
|
||||
use colors::{BLUE, END_COL, PINK};
|
||||
use const_format::concatcp;
|
||||
use inkwell::context::Context;
|
||||
use libloading::Library;
|
||||
use roc_gen_llvm::llvm::build::LlvmBackendMode;
|
||||
use roc_intern::SingleThreadedInterner;
|
||||
use roc_mono::layout::Layout;
|
||||
use roc_types::subs::Subs;
|
||||
use rustyline::highlight::{Highlighter, PromptInfo};
|
||||
use rustyline::validate::{self, ValidationContext, ValidationResult, Validator};
|
||||
use rustyline_derive::{Completer, Helper, Hinter};
|
||||
use std::borrow::Cow;
|
||||
use std::io;
|
||||
use target_lexicon::Triple;
|
||||
use repl_state::ReplState;
|
||||
|
||||
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_parse::parser::{EClosure, EExpr, 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};
|
||||
|
||||
const BLUE: &str = "\u{001b}[36m";
|
||||
const PINK: &str = "\u{001b}[35m";
|
||||
const END_COL: &str = "\u{001b}[0m";
|
||||
use crate::repl_state::PROMPT;
|
||||
|
||||
pub const WELCOME_MESSAGE: &str = concatcp!(
|
||||
"\n The rockin’ ",
|
||||
|
@ -50,480 +25,54 @@ pub const WELCOME_MESSAGE: &str = concatcp!(
|
|||
// 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\n";
|
||||
|
||||
// TODO add link to repl tutorial(does not yet exist).
|
||||
pub const TIPS: &str = concatcp!(
|
||||
BLUE,
|
||||
" - ",
|
||||
END_COL,
|
||||
"Entered code needs to return something. For example:\n\n",
|
||||
PINK,
|
||||
" » foo = 1\n … foo\n\n",
|
||||
END_COL,
|
||||
BLUE,
|
||||
" - ",
|
||||
END_COL,
|
||||
"You can use `Ctrl+V`+`Ctrl+J` to make a newline. The repl will also insert a newline if you press enter",
|
||||
" when the current expression can not be evaluated, e.g. when you type `foo =<ENTER>`.\n\n",
|
||||
BLUE,
|
||||
" - ",
|
||||
END_COL,
|
||||
":q to quit\n\n",
|
||||
BLUE,
|
||||
" - ",
|
||||
END_COL,
|
||||
":help\n"
|
||||
);
|
||||
pub const PROMPT: &str = concatcp!("\n", BLUE, "»", END_COL, " ");
|
||||
pub const CONT_PROMPT: &str = concatcp!(BLUE, "…", END_COL, " ");
|
||||
|
||||
#[derive(Completer, Helper, Hinter)]
|
||||
struct ReplHelper {
|
||||
validator: InputValidator,
|
||||
pending_src: String,
|
||||
}
|
||||
|
||||
impl ReplHelper {
|
||||
pub(crate) fn new() -> ReplHelper {
|
||||
ReplHelper {
|
||||
validator: InputValidator::new(),
|
||||
pending_src: String::new(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Highlighter for ReplHelper {
|
||||
fn has_continuation_prompt(&self) -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
fn highlight_prompt<'b, 's: 'b, 'p: 'b>(
|
||||
&'s self,
|
||||
prompt: &'p str,
|
||||
info: PromptInfo<'_>,
|
||||
) -> Cow<'b, str> {
|
||||
if info.line_no() > 0 {
|
||||
CONT_PROMPT.into()
|
||||
} else {
|
||||
prompt.into()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Validator for ReplHelper {
|
||||
fn validate(
|
||||
&self,
|
||||
ctx: &mut validate::ValidationContext,
|
||||
) -> rustyline::Result<validate::ValidationResult> {
|
||||
self.validator.validate(ctx)
|
||||
}
|
||||
|
||||
fn validate_while_typing(&self) -> bool {
|
||||
self.validator.validate_while_typing()
|
||||
}
|
||||
}
|
||||
|
||||
struct InputValidator {}
|
||||
|
||||
impl InputValidator {
|
||||
pub(crate) fn new() -> InputValidator {
|
||||
InputValidator {}
|
||||
}
|
||||
}
|
||||
|
||||
impl Validator for InputValidator {
|
||||
fn validate(&self, ctx: &mut ValidationContext) -> rustyline::Result<ValidationResult> {
|
||||
if ctx.input().is_empty() {
|
||||
Ok(ValidationResult::Incomplete)
|
||||
} else {
|
||||
let arena = bumpalo::Bump::new();
|
||||
let state = roc_parse::state::State::new(ctx.input().trim().as_bytes());
|
||||
|
||||
match roc_parse::expr::parse_loc_expr(&arena, state, 0) {
|
||||
// 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)),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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))
|
||||
}
|
||||
|
||||
fn gen_and_eval_llvm<'a>(
|
||||
src: &str,
|
||||
target: Triple,
|
||||
opt_level: OptLevel,
|
||||
) -> 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,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
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,
|
||||
);
|
||||
|
||||
let formatted = format_answer(&arena, res_answer, expr_type_str);
|
||||
Ok(formatted)
|
||||
}
|
||||
|
||||
fn eval_and_format<'a>(src: &str) -> Result<String, SyntaxError<'a>> {
|
||||
let format_output = |output| match output {
|
||||
ReplOutput::NoProblems { expr, expr_type } => {
|
||||
format!("\n{} {}:{} {}", expr, PINK, END_COL, expr_type)
|
||||
}
|
||||
ReplOutput::Problems(lines) => format!("\n{}\n", lines.join("\n\n")),
|
||||
};
|
||||
|
||||
gen_and_eval_llvm(src, Triple::host(), OptLevel::Normal).map(format_output)
|
||||
}
|
||||
|
||||
fn report_parse_error(fail: SyntaxError) {
|
||||
println!("TODO Gracefully report parse error in repl: {:?}", fail);
|
||||
}
|
||||
|
||||
pub fn main() -> io::Result<()> {
|
||||
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, TIPS);
|
||||
print!("{}{}", WELCOME_MESSAGE, SHORT_INSTRUCTIONS);
|
||||
|
||||
let mut prev_line_blank = false;
|
||||
let mut editor = Editor::<ReplHelper>::new();
|
||||
let repl_helper = ReplHelper::new();
|
||||
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 {
|
||||
match editor.readline(PROMPT) {
|
||||
Ok(line) => {
|
||||
let trim_line = line.trim();
|
||||
editor.add_history_entry(trim_line);
|
||||
editor.add_history_entry(line.trim());
|
||||
|
||||
let pending_src = &mut editor
|
||||
.helper_mut()
|
||||
.expect("Editor helper was not set")
|
||||
.pending_src;
|
||||
let dimensions = editor.dimensions();
|
||||
let repl_helper = editor.helper_mut().expect("Editor helper was not set");
|
||||
|
||||
match trim_line.to_lowercase().as_str() {
|
||||
"" => {
|
||||
if pending_src.is_empty() {
|
||||
print!("\n{}", SHORT_INSTRUCTIONS);
|
||||
} else if prev_line_blank {
|
||||
// After two blank lines in a row, give up and try parsing it
|
||||
// even though it's going to fail. This way you don't get stuck.
|
||||
match eval_and_format(pending_src.as_str()) {
|
||||
Ok(output) => {
|
||||
println!("{}", output);
|
||||
}
|
||||
Err(fail) => {
|
||||
report_parse_error(fail);
|
||||
}
|
||||
}
|
||||
|
||||
pending_src.clear();
|
||||
} else {
|
||||
pending_src.push('\n');
|
||||
|
||||
prev_line_blank = true;
|
||||
continue; // Skip the part where we reset prev_line_blank to false
|
||||
match repl_helper.step(&line, dimensions) {
|
||||
Ok(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);
|
||||
}
|
||||
}
|
||||
":help" => {
|
||||
// TODO add link to repl tutorial(does not yet exist).
|
||||
println!("Use :q to exit.");
|
||||
}
|
||||
":exit" | ":quit" | ":q" => {
|
||||
break;
|
||||
}
|
||||
_ => {
|
||||
let result = if pending_src.is_empty() {
|
||||
eval_and_format(trim_line)
|
||||
} else {
|
||||
pending_src.push('\n');
|
||||
pending_src.push_str(trim_line);
|
||||
|
||||
eval_and_format(pending_src.as_str())
|
||||
};
|
||||
|
||||
match result {
|
||||
Ok(output) => {
|
||||
println!("{}", output);
|
||||
pending_src.clear();
|
||||
}
|
||||
// Err(Fail {
|
||||
// reason: FailReason::Eof(_),
|
||||
// ..
|
||||
// }) => {}
|
||||
Err(fail) => {
|
||||
report_parse_error(fail);
|
||||
pending_src.clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Err(exit_code) => return exit_code,
|
||||
};
|
||||
}
|
||||
Err(ReadlineError::Interrupted) => {
|
||||
println!("CTRL-C");
|
||||
break;
|
||||
#[cfg(windows)]
|
||||
Err(ReadlineError::WindowResize) => {
|
||||
// This is fine; just ignore it.
|
||||
}
|
||||
Err(ReadlineError::Eof) => {
|
||||
// If we hit an eof, and we're allowed to keep going,
|
||||
// append the str to the src we're building up and continue.
|
||||
// (We only need to append it here if it was empty before;
|
||||
// otherwise, we already appended it before calling eval_and_format.)
|
||||
let pending_src = &mut editor
|
||||
.helper_mut()
|
||||
.expect("Editor helper was not set")
|
||||
.pending_src;
|
||||
|
||||
if pending_src.is_empty() {
|
||||
pending_src.push_str("");
|
||||
}
|
||||
break;
|
||||
// End of input; we're done!
|
||||
return 0;
|
||||
}
|
||||
Err(ReadlineError::Interrupted) => {
|
||||
eprintln!("CTRL-C");
|
||||
return 1;
|
||||
}
|
||||
Err(err) => {
|
||||
eprintln!("REPL error: {:?}", err);
|
||||
break;
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
prev_line_blank = false;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
|
630
crates/repl_cli/src/repl_state.rs
Normal file
630
crates/repl_cli/src/repl_state.rs
Normal file
|
@ -0,0 +1,630 @@
|
|||
use crate::cli_gen::gen_and_eval_llvm;
|
||||
use crate::colors::{BLUE, END_COL, GREEN, PINK};
|
||||
use bumpalo::Bump;
|
||||
use const_format::concatcp;
|
||||
use roc_collections::MutSet;
|
||||
use roc_mono::ir::OptLevel;
|
||||
use roc_parse::ast::{Expr, Pattern, TypeDef, TypeHeader, ValueDef};
|
||||
use roc_parse::expr::{parse_single_def, ExprParseOptions, SingleDef};
|
||||
use roc_parse::parser::{EClosure, EExpr, EPattern};
|
||||
use roc_parse::parser::{EWhen, Either};
|
||||
use roc_parse::state::State;
|
||||
use roc_parse::{join_alias_to_body, join_ann_to_body};
|
||||
use roc_region::all::Loc;
|
||||
use roc_repl_eval::gen::{Problems, ReplOutput};
|
||||
use rustyline::highlight::{Highlighter, PromptInfo};
|
||||
use rustyline::validate::{self, ValidationContext, ValidationResult, Validator};
|
||||
use rustyline_derive::{Completer, Helper, Hinter};
|
||||
use std::borrow::Cow;
|
||||
use target_lexicon::Triple;
|
||||
|
||||
pub const PROMPT: &str = concatcp!(BLUE, "»", END_COL, " ");
|
||||
pub const CONT_PROMPT: &str = concatcp!(BLUE, "…", END_COL, " ");
|
||||
|
||||
/// The prefix we use for the automatic variable names we assign to each expr,
|
||||
/// e.g. if the prefix is "val" then the first expr you enter will be named "val1"
|
||||
pub const AUTO_VAR_PREFIX: &str = "val";
|
||||
|
||||
// TODO add link to repl tutorial(does not yet exist).
|
||||
pub const TIPS: &str = concatcp!(
|
||||
"\nEnter an expression to evaluate, or a definition (like ",
|
||||
BLUE,
|
||||
"x = 1",
|
||||
END_COL,
|
||||
") to use in future expressions.\n\nUnless there was a compile-time error, expressions get automatically named so you can refer to them later.\nFor example, if you see ",
|
||||
GREEN,
|
||||
"# val1",
|
||||
END_COL,
|
||||
" after an output, you can now refer to that expression as ",
|
||||
BLUE,
|
||||
"val1",
|
||||
END_COL,
|
||||
" in future expressions.\n\nTips:\n\n",
|
||||
BLUE,
|
||||
" - ",
|
||||
END_COL,
|
||||
PINK,
|
||||
"ctrl-v",
|
||||
END_COL,
|
||||
" + ",
|
||||
PINK,
|
||||
"ctrl-j",
|
||||
END_COL,
|
||||
" makes a newline\n\n",
|
||||
BLUE,
|
||||
" - ",
|
||||
END_COL,
|
||||
":q to quit\n\n",
|
||||
BLUE,
|
||||
" - ",
|
||||
END_COL,
|
||||
":help"
|
||||
);
|
||||
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
struct PastDef {
|
||||
ident: String,
|
||||
src: String,
|
||||
}
|
||||
|
||||
#[derive(Completer, Helper, Hinter)]
|
||||
pub struct ReplState {
|
||||
validator: InputValidator,
|
||||
past_defs: Vec<PastDef>,
|
||||
past_def_idents: MutSet<String>,
|
||||
last_auto_ident: u64,
|
||||
}
|
||||
|
||||
impl Default for ReplState {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
impl ReplState {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
validator: InputValidator::new(),
|
||||
past_defs: Default::default(),
|
||||
past_def_idents: Default::default(),
|
||||
last_auto_ident: 0,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn step(&mut self, line: &str, dimensions: Option<(usize, usize)>) -> Result<String, i32> {
|
||||
let arena = Bump::new();
|
||||
|
||||
match parse_src(&arena, line) {
|
||||
ParseOutcome::Empty => {
|
||||
if line.is_empty() {
|
||||
Ok(TIPS.to_string())
|
||||
} else if line.ends_with('\n') {
|
||||
// After two blank lines in a row, give up and try parsing it
|
||||
// even though it's going to fail. This way you don't get stuck
|
||||
// in a perpetual Incomplete state due to a syntax error.
|
||||
Ok(self.eval_and_format(line, dimensions))
|
||||
} else {
|
||||
// The previous line wasn't blank, but the line isn't empty either.
|
||||
// This could mean that, for example, you're writing a multiline `when`
|
||||
// and want to add a blank line. No problem! Print a blank line and
|
||||
// continue waiting for input.
|
||||
//
|
||||
// If the user presses enter again, next time prev_line_blank() will be true
|
||||
// and we'll try parsing the source as-is.
|
||||
Ok("\n".to_string())
|
||||
}
|
||||
}
|
||||
ParseOutcome::Expr(_)
|
||||
| ParseOutcome::ValueDef(_)
|
||||
| ParseOutcome::TypeDef(_)
|
||||
| ParseOutcome::SyntaxErr
|
||||
| ParseOutcome::Incomplete => Ok(self.eval_and_format(line, dimensions)),
|
||||
ParseOutcome::Help => {
|
||||
// TODO add link to repl tutorial(does not yet exist).
|
||||
Ok(TIPS.to_string())
|
||||
}
|
||||
ParseOutcome::Exit => Err(0),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn eval_and_format(&mut self, src: &str, dimensions: Option<(usize, usize)>) -> String {
|
||||
let arena = Bump::new();
|
||||
let pending_past_def;
|
||||
let mut opt_var_name;
|
||||
let src = match parse_src(&arena, src) {
|
||||
ParseOutcome::Expr(_) | ParseOutcome::Incomplete | ParseOutcome::SyntaxErr => {
|
||||
pending_past_def = None;
|
||||
// If it's a SyntaxErr (or Incomplete at this point, meaning it will
|
||||
// become a SyntaxErr as soon as we evaluate it),
|
||||
// proceed as normal and let the error reporting happen during eval.
|
||||
opt_var_name = None;
|
||||
|
||||
src
|
||||
}
|
||||
ParseOutcome::ValueDef(value_def) => {
|
||||
match value_def {
|
||||
ValueDef::Annotation(
|
||||
Loc {
|
||||
value: Pattern::Identifier(ident),
|
||||
..
|
||||
},
|
||||
_,
|
||||
) => {
|
||||
// Record the standalone type annotation for future use.
|
||||
self.add_past_def(ident.trim_end().to_string(), src.to_string());
|
||||
|
||||
// Return early without running eval, since standalone annotations
|
||||
// cannnot be evaluated as expressions.
|
||||
return String::new();
|
||||
}
|
||||
ValueDef::Body(
|
||||
Loc {
|
||||
value: Pattern::Identifier(ident),
|
||||
..
|
||||
},
|
||||
_,
|
||||
)
|
||||
| ValueDef::AnnotatedBody {
|
||||
body_pattern:
|
||||
Loc {
|
||||
value: Pattern::Identifier(ident),
|
||||
..
|
||||
},
|
||||
..
|
||||
} => {
|
||||
pending_past_def = Some((ident.to_string(), src.to_string()));
|
||||
opt_var_name = Some(ident.to_string());
|
||||
|
||||
// Recreate the body of the def and then evaluate it as a lookup.
|
||||
// We do this so that any errors will get reported as part of this expr;
|
||||
// if we just did a lookup on the past def, then errors wouldn't get
|
||||
// reported because we filter out errors whose regions are in past defs.
|
||||
let mut buf = bumpalo::collections::string::String::with_capacity_in(
|
||||
ident.len() + src.len() + 1,
|
||||
&arena,
|
||||
);
|
||||
|
||||
buf.push_str(src);
|
||||
buf.push('\n');
|
||||
buf.push_str(ident);
|
||||
|
||||
buf.into_bump_str()
|
||||
}
|
||||
ValueDef::Annotation(_, _)
|
||||
| ValueDef::Body(_, _)
|
||||
| ValueDef::AnnotatedBody { .. } => {
|
||||
todo!("handle pattern other than identifier (which repl doesn't support)")
|
||||
}
|
||||
ValueDef::Expect { .. } => {
|
||||
todo!("handle receiving an `expect` - what should the repl do for that?")
|
||||
}
|
||||
ValueDef::ExpectFx { .. } => {
|
||||
todo!("handle receiving an `expect-fx` - what should the repl do for that?")
|
||||
}
|
||||
}
|
||||
}
|
||||
ParseOutcome::TypeDef(TypeDef::Alias {
|
||||
header:
|
||||
TypeHeader {
|
||||
name: Loc { value: ident, .. },
|
||||
..
|
||||
},
|
||||
..
|
||||
})
|
||||
| ParseOutcome::TypeDef(TypeDef::Opaque {
|
||||
header:
|
||||
TypeHeader {
|
||||
name: Loc { value: ident, .. },
|
||||
..
|
||||
},
|
||||
..
|
||||
})
|
||||
| ParseOutcome::TypeDef(TypeDef::Ability {
|
||||
header:
|
||||
TypeHeader {
|
||||
name: Loc { value: ident, .. },
|
||||
..
|
||||
},
|
||||
..
|
||||
}) => {
|
||||
// Record the type for future use.
|
||||
self.add_past_def(ident.trim_end().to_string(), src.to_string());
|
||||
|
||||
// Return early without running eval, since none of these
|
||||
// can be evaluated as expressions.
|
||||
return String::new();
|
||||
}
|
||||
ParseOutcome::Empty | ParseOutcome::Help | ParseOutcome::Exit => unreachable!(),
|
||||
};
|
||||
|
||||
// Record e.g. "val1" as a past def, unless our input was exactly the name of
|
||||
// an existing identifer (e.g. I just typed "val1" into the prompt - there's no
|
||||
// need to reassign "val1" to "val2" just because I wanted to see what its value was!)
|
||||
let (output, problems) =
|
||||
match opt_var_name.or_else(|| self.past_def_idents.get(src.trim()).cloned()) {
|
||||
Some(existing_ident) => {
|
||||
opt_var_name = Some(existing_ident);
|
||||
|
||||
gen_and_eval_llvm(
|
||||
self.past_defs.iter().map(|def| def.src.as_str()),
|
||||
src,
|
||||
Triple::host(),
|
||||
OptLevel::Normal,
|
||||
)
|
||||
}
|
||||
None => {
|
||||
let (output, problems) = gen_and_eval_llvm(
|
||||
self.past_defs.iter().map(|def| def.src.as_str()),
|
||||
src,
|
||||
Triple::host(),
|
||||
OptLevel::Normal,
|
||||
);
|
||||
|
||||
// Don't persist defs that have compile errors
|
||||
if problems.errors.is_empty() {
|
||||
let var_name = format!("{AUTO_VAR_PREFIX}{}", self.next_auto_ident());
|
||||
let src = format!("{var_name} = {}", src.trim_end());
|
||||
|
||||
opt_var_name = Some(var_name.clone());
|
||||
|
||||
self.add_past_def(var_name, src);
|
||||
} else {
|
||||
opt_var_name = None;
|
||||
}
|
||||
|
||||
(output, problems)
|
||||
}
|
||||
};
|
||||
|
||||
if let Some((ident, src)) = pending_past_def {
|
||||
self.add_past_def(ident, src);
|
||||
}
|
||||
|
||||
format_output(output, problems, opt_var_name, dimensions)
|
||||
}
|
||||
|
||||
fn next_auto_ident(&mut self) -> u64 {
|
||||
self.last_auto_ident += 1;
|
||||
self.last_auto_ident
|
||||
}
|
||||
|
||||
fn add_past_def(&mut self, ident: String, src: String) {
|
||||
let existing_idents = &mut self.past_def_idents;
|
||||
|
||||
existing_idents.insert(ident.clone());
|
||||
|
||||
self.past_defs.push(PastDef { ident, src });
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq)]
|
||||
enum ParseOutcome<'a> {
|
||||
ValueDef(ValueDef<'a>),
|
||||
TypeDef(TypeDef<'a>),
|
||||
Expr(Expr<'a>),
|
||||
Incomplete,
|
||||
SyntaxErr,
|
||||
Empty,
|
||||
Help,
|
||||
Exit,
|
||||
}
|
||||
|
||||
fn parse_src<'a>(arena: &'a Bump, line: &'a str) -> ParseOutcome<'a> {
|
||||
match line.trim().to_lowercase().as_str() {
|
||||
"" => ParseOutcome::Empty,
|
||||
":help" => ParseOutcome::Help,
|
||||
":exit" | ":quit" | ":q" => ParseOutcome::Exit,
|
||||
_ => {
|
||||
let src_bytes = line.as_bytes();
|
||||
|
||||
match roc_parse::expr::parse_loc_expr(arena, State::new(src_bytes), 0) {
|
||||
Ok((_, loc_expr, _)) => ParseOutcome::Expr(loc_expr.value),
|
||||
// Special case some syntax errors to allow for multi-line inputs
|
||||
Err((_, EExpr::Closure(EClosure::Body(_, _), _), _))
|
||||
| Err((_, EExpr::When(EWhen::Pattern(EPattern::Start(_), _), _), _))
|
||||
| Err((_, EExpr::Start(_), _))
|
||||
| Err((_, EExpr::IndentStart(_), _)) => 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(TypeDef::Alias { header, ann }),
|
||||
..
|
||||
}),
|
||||
state,
|
||||
)) => {
|
||||
// This *could* be an AnnotatedBody, e.g. in a case like this:
|
||||
//
|
||||
// UserId x : [UserId Int]
|
||||
// UserId x = UserId 42
|
||||
//
|
||||
// We optimistically parsed the first line as an alias; we might now
|
||||
// turn it into an annotation.
|
||||
match parse_single_def(
|
||||
ExprParseOptions {
|
||||
accept_multi_backpassing: true,
|
||||
check_for_arrow: true,
|
||||
},
|
||||
0,
|
||||
arena,
|
||||
state,
|
||||
) {
|
||||
Ok((
|
||||
_,
|
||||
Some(SingleDef {
|
||||
type_or_value:
|
||||
Either::Second(ValueDef::Body(loc_pattern, loc_def_expr)),
|
||||
region,
|
||||
spaces_before,
|
||||
}),
|
||||
_,
|
||||
)) if spaces_before.len() <= 1 => {
|
||||
// This was, in fact, an AnnotatedBody! Build and return it.
|
||||
let (value_def, _) = join_alias_to_body!(
|
||||
arena,
|
||||
loc_pattern,
|
||||
loc_def_expr,
|
||||
header,
|
||||
&ann,
|
||||
spaces_before,
|
||||
region
|
||||
);
|
||||
|
||||
ParseOutcome::ValueDef(value_def)
|
||||
}
|
||||
_ => {
|
||||
// This was not an AnnotatedBody, so return the alias.
|
||||
ParseOutcome::TypeDef(TypeDef::Alias { header, ann })
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok((
|
||||
_,
|
||||
Some(SingleDef {
|
||||
type_or_value:
|
||||
Either::Second(ValueDef::Annotation(ann_pattern, ann_type)),
|
||||
..
|
||||
}),
|
||||
state,
|
||||
)) => {
|
||||
// This *could* be an AnnotatedBody, if the next line is a body.
|
||||
match parse_single_def(
|
||||
ExprParseOptions {
|
||||
accept_multi_backpassing: true,
|
||||
check_for_arrow: true,
|
||||
},
|
||||
0,
|
||||
arena,
|
||||
state,
|
||||
) {
|
||||
Ok((
|
||||
_,
|
||||
Some(SingleDef {
|
||||
type_or_value:
|
||||
Either::Second(ValueDef::Body(loc_pattern, loc_def_expr)),
|
||||
region,
|
||||
spaces_before,
|
||||
}),
|
||||
_,
|
||||
)) if spaces_before.len() <= 1 => {
|
||||
// Inlining this borrow makes clippy unhappy for some reason.
|
||||
let ann_pattern = &ann_pattern;
|
||||
|
||||
// This was, in fact, an AnnotatedBody! Build and return it.
|
||||
let (value_def, _) = join_ann_to_body!(
|
||||
arena,
|
||||
loc_pattern,
|
||||
loc_def_expr,
|
||||
ann_pattern,
|
||||
&ann_type,
|
||||
spaces_before,
|
||||
region
|
||||
);
|
||||
|
||||
ParseOutcome::ValueDef(value_def)
|
||||
}
|
||||
_ => {
|
||||
// This was not an AnnotatedBody, so return the standalone annotation.
|
||||
ParseOutcome::ValueDef(ValueDef::Annotation(
|
||||
ann_pattern,
|
||||
ann_type,
|
||||
))
|
||||
}
|
||||
}
|
||||
}
|
||||
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::SyntaxErr,
|
||||
}
|
||||
}
|
||||
Err(_) => ParseOutcome::SyntaxErr,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct InputValidator {}
|
||||
|
||||
impl InputValidator {
|
||||
pub fn new() -> InputValidator {
|
||||
InputValidator {}
|
||||
}
|
||||
}
|
||||
|
||||
impl Validator for InputValidator {
|
||||
fn validate(&self, ctx: &mut ValidationContext) -> rustyline::Result<ValidationResult> {
|
||||
if is_incomplete(ctx.input()) {
|
||||
Ok(ValidationResult::Incomplete)
|
||||
} else {
|
||||
Ok(ValidationResult::Valid(None))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn is_incomplete(input: &str) -> bool {
|
||||
let arena = Bump::new();
|
||||
|
||||
match parse_src(&arena, input) {
|
||||
ParseOutcome::Incomplete => !input.ends_with('\n'),
|
||||
// Standalone annotations are default incomplete, because we can't know
|
||||
// whether they're about to annotate a body on the next line
|
||||
// (or if not, meaning they stay standalone) until you press Enter again!
|
||||
//
|
||||
// So it's Incomplete until you've pressed Enter again (causing the input to end in "\n")
|
||||
ParseOutcome::ValueDef(ValueDef::Annotation(_, _)) if !input.ends_with('\n') => true,
|
||||
ParseOutcome::Expr(Expr::When(_, _)) => {
|
||||
// There might be lots of `when` branches, so don't assume the user is done entering
|
||||
// them until they enter a blank line!
|
||||
!input.ends_with('\n')
|
||||
}
|
||||
ParseOutcome::Empty
|
||||
| ParseOutcome::Help
|
||||
| ParseOutcome::Exit
|
||||
| ParseOutcome::ValueDef(_)
|
||||
| ParseOutcome::TypeDef(_)
|
||||
| ParseOutcome::SyntaxErr
|
||||
| ParseOutcome::Expr(_) => false,
|
||||
}
|
||||
}
|
||||
|
||||
impl Highlighter for ReplState {
|
||||
fn has_continuation_prompt(&self) -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
fn highlight_prompt<'b, 's: 'b, 'p: 'b>(
|
||||
&'s self,
|
||||
prompt: &'p str,
|
||||
info: PromptInfo<'_>,
|
||||
) -> Cow<'b, str> {
|
||||
if info.line_no() > 0 {
|
||||
CONT_PROMPT.into()
|
||||
} else {
|
||||
prompt.into()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Validator for ReplState {
|
||||
fn validate(
|
||||
&self,
|
||||
ctx: &mut validate::ValidationContext,
|
||||
) -> rustyline::Result<validate::ValidationResult> {
|
||||
self.validator.validate(ctx)
|
||||
}
|
||||
|
||||
fn validate_while_typing(&self) -> bool {
|
||||
self.validator.validate_while_typing()
|
||||
}
|
||||
}
|
||||
|
||||
fn format_output(
|
||||
opt_output: Option<ReplOutput>,
|
||||
problems: Problems,
|
||||
opt_var_name: Option<String>,
|
||||
dimensions: Option<(usize, usize)>,
|
||||
) -> String {
|
||||
let mut buf = String::new();
|
||||
|
||||
for message in problems.errors.iter().chain(problems.warnings.iter()) {
|
||||
if !buf.is_empty() {
|
||||
buf.push_str("\n\n");
|
||||
}
|
||||
|
||||
buf.push('\n');
|
||||
buf.push_str(message);
|
||||
buf.push('\n');
|
||||
}
|
||||
|
||||
if let Some(ReplOutput { expr, expr_type }) = opt_output {
|
||||
// If expr was empty, it was a type annotation or ability declaration;
|
||||
// don't print anything!
|
||||
//
|
||||
// Also, for now we also don't print anything if there was a compile-time error.
|
||||
// In the future, it would be great to run anyway and print useful output here!
|
||||
if !expr.is_empty() && problems.errors.is_empty() {
|
||||
const EXPR_TYPE_SEPARATOR: &str = " : "; // e.g. in "5 : Num *"
|
||||
|
||||
// Print the expr and its type
|
||||
{
|
||||
buf.push('\n');
|
||||
buf.push_str(&expr);
|
||||
buf.push_str(PINK); // Color for the type separator
|
||||
buf.push_str(EXPR_TYPE_SEPARATOR);
|
||||
buf.push_str(END_COL);
|
||||
buf.push_str(&expr_type);
|
||||
}
|
||||
|
||||
// Print var_name right-aligned on the last line of output.
|
||||
if let Some(var_name) = opt_var_name {
|
||||
use unicode_segmentation::UnicodeSegmentation;
|
||||
|
||||
const VAR_NAME_PREFIX: &str = " # "; // e.g. in " # val1"
|
||||
const VAR_NAME_COLUMN_MAX: usize = 32; // Right-align the var_name at this column
|
||||
|
||||
let term_width = match dimensions {
|
||||
Some((width, _)) => width.min(VAR_NAME_COLUMN_MAX),
|
||||
None => VAR_NAME_COLUMN_MAX as usize,
|
||||
};
|
||||
|
||||
let expr_with_type = format!("{expr}{EXPR_TYPE_SEPARATOR}{expr_type}");
|
||||
|
||||
// Count graphemes because we care about what's *rendered* in the terminal
|
||||
let last_line_len = expr_with_type
|
||||
.split('\n')
|
||||
.last()
|
||||
.unwrap_or_default()
|
||||
.graphemes(true)
|
||||
.count();
|
||||
let var_name_len =
|
||||
var_name.graphemes(true).count() + VAR_NAME_PREFIX.graphemes(true).count();
|
||||
let spaces_needed = if last_line_len + var_name_len > term_width {
|
||||
buf.push('\n');
|
||||
term_width - var_name_len
|
||||
} else {
|
||||
term_width - last_line_len - var_name_len
|
||||
};
|
||||
|
||||
for _ in 0..spaces_needed {
|
||||
buf.push(' ');
|
||||
}
|
||||
|
||||
buf.push_str(GREEN);
|
||||
buf.push_str(VAR_NAME_PREFIX);
|
||||
buf.push_str(&var_name);
|
||||
buf.push_str(END_COL);
|
||||
buf.push('\n');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
buf
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue