mirror of
https://github.com/roc-lang/roc.git
synced 2025-09-26 13:29:12 +00:00
Reorganize ReplState logic for cli/wasm compat
This commit is contained in:
parent
e010e7239f
commit
dbf928bc46
4 changed files with 315 additions and 300 deletions
|
@ -7,49 +7,26 @@ use roc_error_macros::internal_error;
|
|||
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_load::{EntryPoint, FunctionKind, MonomorphizedModule};
|
||||
use roc_load::{EntryPoint, MonomorphizedModule};
|
||||
use roc_mono::ir::OptLevel;
|
||||
use roc_mono::layout::STLayoutInterner;
|
||||
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::gen::{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, I: Iterator<Item = &'a str>>(
|
||||
defs: I,
|
||||
src: &str,
|
||||
target: Triple,
|
||||
pub fn eval_llvm<'a>(
|
||||
mut loaded: MonomorphizedModule<'a>,
|
||||
target: &Triple,
|
||||
opt_level: OptLevel,
|
||||
) -> (Option<ReplOutput>, Problems) {
|
||||
) -> Option<ReplOutput> {
|
||||
let arena = Bump::new();
|
||||
let target_info = TargetInfo::from(&target);
|
||||
let function_kind = FunctionKind::LambdaSet;
|
||||
|
||||
let mut loaded;
|
||||
let problems;
|
||||
|
||||
match compile_to_mono(
|
||||
&arena,
|
||||
defs,
|
||||
src,
|
||||
target_info,
|
||||
function_kind,
|
||||
DEFAULT_PALETTE,
|
||||
) {
|
||||
(Some(mono), probs) => {
|
||||
loaded = mono;
|
||||
problems = probs;
|
||||
}
|
||||
(None, probs) => {
|
||||
return (None, probs);
|
||||
}
|
||||
};
|
||||
let target_info = TargetInfo::from(target);
|
||||
|
||||
debug_assert_eq!(loaded.exposed_to_host.top_level_values.len(), 1);
|
||||
let (main_fn_symbol, main_fn_var) = loaded
|
||||
|
@ -70,20 +47,15 @@ pub fn gen_and_eval_llvm<'a, I: Iterator<Item = &'a str>>(
|
|||
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 (_, main_fn_layout) = *loaded
|
||||
.procedures
|
||||
.keys()
|
||||
.find(|(s, _)| *s == main_fn_symbol)?;
|
||||
|
||||
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");
|
||||
mono_module_to_dylib(&arena, &target, loaded, opt_level).expect("we produce a valid Dylib");
|
||||
|
||||
let mut app = CliApp { lib };
|
||||
|
||||
|
@ -100,13 +72,10 @@ pub fn gen_and_eval_llvm<'a, I: Iterator<Item = &'a str>>(
|
|||
);
|
||||
let expr_str = format_answer(&arena, expr).to_string();
|
||||
|
||||
(
|
||||
Some(ReplOutput {
|
||||
expr: expr_str,
|
||||
expr_type: expr_type_str,
|
||||
}),
|
||||
problems,
|
||||
)
|
||||
Some(ReplOutput {
|
||||
expr: expr_str,
|
||||
expr_type: expr_type_str,
|
||||
})
|
||||
}
|
||||
|
||||
struct CliApp {
|
||||
|
@ -191,11 +160,11 @@ impl ReplAppMemory for CliMemory {
|
|||
|
||||
fn mono_module_to_dylib<'a>(
|
||||
arena: &'a Bump,
|
||||
target: Triple,
|
||||
target: &Triple,
|
||||
loaded: MonomorphizedModule<'a>,
|
||||
opt_level: OptLevel,
|
||||
) -> Result<(libloading::Library, &'a str, Subs, STLayoutInterner<'a>), libloading::Error> {
|
||||
let target_info = TargetInfo::from(&target);
|
||||
let target_info = TargetInfo::from(target);
|
||||
|
||||
let MonomorphizedModule {
|
||||
procedures,
|
||||
|
@ -210,7 +179,7 @@ fn mono_module_to_dylib<'a>(
|
|||
let context = Context::create();
|
||||
let builder = context.create_builder();
|
||||
let module = arena.alloc(roc_gen_llvm::llvm::build::module_from_builtins(
|
||||
&target, &context, "",
|
||||
target, &context, "",
|
||||
));
|
||||
|
||||
let module = arena.alloc(module);
|
||||
|
|
|
@ -3,11 +3,24 @@ mod cli_gen;
|
|||
mod colors;
|
||||
pub mod repl_state;
|
||||
|
||||
use colors::{BLUE, END_COL, PINK};
|
||||
use const_format::concatcp;
|
||||
use repl_state::ReplState;
|
||||
use std::borrow::Cow;
|
||||
|
||||
use crate::repl_state::PROMPT;
|
||||
use bumpalo::Bump;
|
||||
use colors::{BLUE, END_COL, GREEN, PINK};
|
||||
use const_format::concatcp;
|
||||
use repl_state::ReplAction;
|
||||
use repl_state::{parse_src, ParseOutcome, ReplState};
|
||||
use roc_mono::ir::OptLevel;
|
||||
use roc_parse::ast::{Expr, ValueDef};
|
||||
use roc_repl_eval::gen::{Problems, ReplOutput};
|
||||
use roc_reporting::report::DEFAULT_PALETTE;
|
||||
use roc_target::TargetInfo;
|
||||
use rustyline::highlight::{Highlighter, PromptInfo};
|
||||
use rustyline::validate::{self, ValidationContext, ValidationResult, Validator};
|
||||
use rustyline_derive::{Completer, Helper, Hinter};
|
||||
use target_lexicon::Triple;
|
||||
|
||||
use crate::cli_gen::eval_llvm;
|
||||
|
||||
pub const WELCOME_MESSAGE: &str = concatcp!(
|
||||
"\n The rockin’ ",
|
||||
|
@ -21,10 +34,55 @@ pub const WELCOME_MESSAGE: &str = concatcp!(
|
|||
"\n\n"
|
||||
);
|
||||
|
||||
// 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"
|
||||
);
|
||||
|
||||
// For when nothing is entered in the repl
|
||||
// TODO add link to repl tutorial(does not yet exist).
|
||||
pub const SHORT_INSTRUCTIONS: &str = "Enter an expression, or :help, or :q to quit.\n\n";
|
||||
|
||||
pub const PROMPT: &str = concatcp!(BLUE, "»", END_COL, " ");
|
||||
pub const CONT_PROMPT: &str = concatcp!(BLUE, "…", END_COL, " ");
|
||||
|
||||
#[derive(Completer, Helper, Hinter, Default)]
|
||||
pub struct ReplHelper {
|
||||
validator: InputValidator,
|
||||
state: ReplState,
|
||||
}
|
||||
|
||||
pub fn main() -> i32 {
|
||||
use rustyline::error::ReadlineError;
|
||||
use rustyline::Editor;
|
||||
|
@ -34,9 +92,12 @@ pub fn main() -> i32 {
|
|||
// <RUN WITH:> RUST_LOG=rustyline=debug cargo run repl 2> debug.log
|
||||
print!("{WELCOME_MESSAGE}{SHORT_INSTRUCTIONS}");
|
||||
|
||||
let mut editor = Editor::<ReplState>::new();
|
||||
let repl_helper = ReplState::new();
|
||||
let mut editor = Editor::<ReplHelper>::new();
|
||||
let repl_helper = ReplHelper::default();
|
||||
editor.set_helper(Some(repl_helper));
|
||||
let target = Triple::host();
|
||||
let target_info = TargetInfo::from(&target);
|
||||
let mut arena = Bump::new();
|
||||
|
||||
loop {
|
||||
match editor.readline(PROMPT) {
|
||||
|
@ -44,18 +105,36 @@ pub fn main() -> i32 {
|
|||
editor.add_history_entry(line.trim());
|
||||
|
||||
let dimensions = editor.dimensions();
|
||||
let repl_helper = editor.helper_mut().expect("Editor helper was not set");
|
||||
let repl_state = &mut editor
|
||||
.helper_mut()
|
||||
.expect("Editor helper was not set")
|
||||
.state;
|
||||
|
||||
arena.reset();
|
||||
match repl_state.step(&arena, &line, target_info, DEFAULT_PALETTE) {
|
||||
// If there was no output, don't print a blank line!
|
||||
// (This happens for something like a type annotation.)
|
||||
ReplAction::Eval {
|
||||
opt_mono,
|
||||
problems,
|
||||
opt_var_name,
|
||||
} => {
|
||||
let opt_output =
|
||||
opt_mono.and_then(|mono| eval_llvm(mono, &target, OptLevel::Normal));
|
||||
let output = format_output(opt_output, problems, opt_var_name, dimensions);
|
||||
|
||||
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}");
|
||||
}
|
||||
}
|
||||
Err(exit_code) => return exit_code,
|
||||
};
|
||||
ReplAction::Exit => {
|
||||
return 0;
|
||||
}
|
||||
ReplAction::Help => {
|
||||
println!("{TIPS}");
|
||||
}
|
||||
ReplAction::Nothing => {}
|
||||
}
|
||||
}
|
||||
#[cfg(windows)]
|
||||
Err(ReadlineError::WindowResize) => {
|
||||
|
@ -76,3 +155,156 @@ pub fn main() -> i32 {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
struct 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 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()
|
||||
}
|
||||
}
|
||||
|
||||
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,
|
||||
};
|
||||
|
||||
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
|
||||
}
|
||||
|
|
|
@ -1,9 +1,6 @@
|
|||
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_load::MonomorphizedModule;
|
||||
use roc_parse::ast::{Expr, Pattern, TypeDef, TypeHeader, ValueDef};
|
||||
use roc_parse::expr::{parse_single_def, ExprParseOptions, SingleDef};
|
||||
use roc_parse::parser::Parser;
|
||||
|
@ -12,65 +9,20 @@ 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, " ");
|
||||
use roc_repl_eval::gen::{compile_to_mono, Problems};
|
||||
use roc_reporting::report::Palette;
|
||||
use roc_target::TargetInfo;
|
||||
|
||||
/// 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,
|
||||
|
@ -82,36 +34,51 @@ impl Default for ReplState {
|
|||
}
|
||||
}
|
||||
|
||||
pub enum ReplAction<'a> {
|
||||
Eval {
|
||||
opt_mono: Option<MonomorphizedModule<'a>>,
|
||||
problems: Problems,
|
||||
opt_var_name: Option<String>,
|
||||
},
|
||||
Exit,
|
||||
Help,
|
||||
Nothing,
|
||||
}
|
||||
|
||||
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();
|
||||
|
||||
pub fn step<'a>(
|
||||
&mut self,
|
||||
arena: &'a Bump,
|
||||
line: &str,
|
||||
target_info: TargetInfo,
|
||||
palette: Palette,
|
||||
) -> ReplAction<'a> {
|
||||
match parse_src(&arena, line) {
|
||||
ParseOutcome::Empty => Ok(TIPS.to_string()),
|
||||
ParseOutcome::Empty | ParseOutcome::Help => ReplAction::Help,
|
||||
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),
|
||||
| ParseOutcome::Incomplete => self.next_action(arena, line, target_info, palette),
|
||||
ParseOutcome::Exit => ReplAction::Exit,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn eval_and_format(&mut self, src: &str, dimensions: Option<(usize, usize)>) -> String {
|
||||
let arena = Bump::new();
|
||||
fn next_action<'a>(
|
||||
&mut self,
|
||||
arena: &'a Bump,
|
||||
src: &str,
|
||||
target_info: TargetInfo,
|
||||
palette: Palette,
|
||||
) -> ReplAction<'a> {
|
||||
let pending_past_def;
|
||||
let mut opt_var_name;
|
||||
let src = match parse_src(&arena, src) {
|
||||
|
@ -138,7 +105,7 @@ impl ReplState {
|
|||
|
||||
// Return early without running eval, since standalone annotations
|
||||
// cannnot be evaluated as expressions.
|
||||
return String::new();
|
||||
return ReplAction::Nothing;
|
||||
}
|
||||
ValueDef::Body(
|
||||
Loc {
|
||||
|
@ -218,7 +185,7 @@ impl ReplState {
|
|||
|
||||
// Return early without running eval, since none of these
|
||||
// can be evaluated as expressions.
|
||||
return String::new();
|
||||
return ReplAction::Nothing;
|
||||
}
|
||||
ParseOutcome::Empty | ParseOutcome::Help | ParseOutcome::Exit => unreachable!(),
|
||||
};
|
||||
|
@ -226,24 +193,26 @@ impl ReplState {
|
|||
// 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) =
|
||||
let (opt_mono, 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(
|
||||
compile_to_mono(
|
||||
&arena,
|
||||
self.past_defs.iter().map(|def| def.src.as_str()),
|
||||
src,
|
||||
Triple::host(),
|
||||
OptLevel::Normal,
|
||||
target_info,
|
||||
palette,
|
||||
)
|
||||
}
|
||||
None => {
|
||||
let (output, problems) = gen_and_eval_llvm(
|
||||
let (output, problems) = compile_to_mono(
|
||||
&arena,
|
||||
self.past_defs.iter().map(|def| def.src.as_str()),
|
||||
src,
|
||||
Triple::host(),
|
||||
OptLevel::Normal,
|
||||
target_info,
|
||||
palette,
|
||||
);
|
||||
|
||||
// Don't persist defs that have compile errors
|
||||
|
@ -266,7 +235,11 @@ impl ReplState {
|
|||
self.add_past_def(ident, src);
|
||||
}
|
||||
|
||||
format_output(output, problems, opt_var_name, dimensions)
|
||||
ReplAction::Eval {
|
||||
opt_mono,
|
||||
problems,
|
||||
opt_var_name,
|
||||
}
|
||||
}
|
||||
|
||||
fn next_auto_ident(&mut self) -> u64 {
|
||||
|
@ -284,7 +257,7 @@ impl ReplState {
|
|||
}
|
||||
|
||||
#[derive(Debug, PartialEq)]
|
||||
enum ParseOutcome<'a> {
|
||||
pub enum ParseOutcome<'a> {
|
||||
ValueDef(ValueDef<'a>),
|
||||
TypeDef(TypeDef<'a>),
|
||||
Expr(Expr<'a>),
|
||||
|
@ -295,7 +268,7 @@ enum ParseOutcome<'a> {
|
|||
Exit,
|
||||
}
|
||||
|
||||
fn parse_src<'a>(arena: &'a Bump, line: &'a str) -> ParseOutcome<'a> {
|
||||
pub fn parse_src<'a>(arena: &'a Bump, line: &'a str) -> ParseOutcome<'a> {
|
||||
match line.trim().to_lowercase().as_str() {
|
||||
"" => ParseOutcome::Empty,
|
||||
":help" => ParseOutcome::Help,
|
||||
|
@ -456,161 +429,3 @@ fn parse_src<'a>(arena: &'a Bump, line: &'a str) -> ParseOutcome<'a> {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
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,
|
||||
};
|
||||
|
||||
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