Persist defs

This commit is contained in:
Richard Feldman 2022-10-27 15:52:27 -04:00
parent 90e79379cb
commit f841cdb2c0
No known key found for this signature in database
GPG key ID: F1F21AA5B1D9E43B
2 changed files with 117 additions and 30 deletions

View file

@ -2,12 +2,15 @@ use crate::cli_gen::gen_and_eval_llvm;
use crate::colors::{BLUE, END_COL, GREEN, PINK}; use crate::colors::{BLUE, END_COL, GREEN, PINK};
use bumpalo::Bump; use bumpalo::Bump;
use const_format::concatcp; use const_format::concatcp;
use roc_collections::linked_list_extra::drain_filter;
use roc_collections::MutSet;
use roc_mono::ir::OptLevel; use roc_mono::ir::OptLevel;
use roc_parse::ast::{Expr, TypeDef, ValueDef}; use roc_parse::ast::{Expr, Pattern, TypeDef, ValueDef};
use roc_parse::expr::{parse_single_def, ExprParseOptions, SingleDef}; use roc_parse::expr::{parse_single_def, ExprParseOptions, SingleDef};
use roc_parse::parser::Either; use roc_parse::parser::Either;
use roc_parse::parser::{EClosure, EExpr}; use roc_parse::parser::{EClosure, EExpr};
use roc_parse::state::State; use roc_parse::state::State;
use roc_region::all::Loc;
use roc_repl_eval::gen::ReplOutput; use roc_repl_eval::gen::ReplOutput;
use rustyline::highlight::{Highlighter, PromptInfo}; use rustyline::highlight::{Highlighter, PromptInfo};
use rustyline::validate::{self, ValidationContext, ValidationResult, Validator}; use rustyline::validate::{self, ValidationContext, ValidationResult, Validator};
@ -19,6 +22,10 @@ use target_lexicon::Triple;
pub const PROMPT: &str = concatcp!("\n", BLUE, "»", END_COL, " "); pub const PROMPT: &str = concatcp!("\n", BLUE, "»", END_COL, " ");
pub const CONT_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). // TODO add link to repl tutorial(does not yet exist).
pub const TIPS: &str = concatcp!( pub const TIPS: &str = concatcp!(
BLUE, BLUE,
@ -42,29 +49,34 @@ pub const TIPS: &str = concatcp!(
":help\n" ":help\n"
); );
#[derive(Debug, Clone, PartialEq)]
struct PastDef { struct PastDef {
_ident: String, ident: String,
_src: String, src: String,
} }
#[derive(Completer, Helper, Hinter)] #[derive(Completer, Helper, Hinter)]
pub struct ReplState { pub struct ReplState {
validator: InputValidator, validator: InputValidator,
_past_defs: LinkedList<PastDef>, past_defs: LinkedList<PastDef>,
past_def_idents: MutSet<String>,
last_auto_ident: u64,
} }
impl ReplState { impl ReplState {
pub fn new() -> Self { pub fn new() -> Self {
Self { Self {
validator: InputValidator::new(), validator: InputValidator::new(),
_past_defs: Default::default(), past_defs: Default::default(),
past_def_idents: Default::default(),
last_auto_ident: 0,
} }
} }
pub fn step(&mut self, line: &str) -> Result<String, i32> { pub fn step(&mut self, line: &str) -> Result<String, i32> {
let arena = Bump::new(); let arena = Bump::new();
match dbg!(parse_src(&arena, dbg!(line))) { match parse_src(&arena, line) {
ParseOutcome::Empty => { ParseOutcome::Empty => {
if line.is_empty() { if line.is_empty() {
return Ok(tips()); return Ok(tips());
@ -72,7 +84,7 @@ impl ReplState {
// After two blank lines in a row, give up and try parsing it // 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 // even though it's going to fail. This way you don't get stuck
// in a perpetual Incomplete state due to a syntax error. // in a perpetual Incomplete state due to a syntax error.
Ok(dbg!(self.eval_and_format(line))) Ok(self.eval_and_format(line))
} else { } else {
// The previous line wasn't blank, but the line isn't empty either. // 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` // This could mean that, for example, you're writing a multiline `when`
@ -103,29 +115,56 @@ impl ReplState {
ParseOutcome::Expr(_) => src, ParseOutcome::Expr(_) => src,
ParseOutcome::ValueDef(value_def) => { ParseOutcome::ValueDef(value_def) => {
match value_def { match value_def {
ValueDef::Annotation(_, _) => { ValueDef::Annotation(
if src.ends_with('\n') { Loc {
// Since pending_src ended in a blank line value: Pattern::Identifier(ident),
// (and was therefore nonempty), this must ..
// have been an annotation followed by a blank line. },
// Record it as standaline! _,
todo!("record a STANDALONE annotation!"); ) => {
} else {
// We received a type annotation, like `x : Str` // We received a type annotation, like `x : Str`
// //
// This might be the beginning of an AnnotatedBody, or it might be // This might be the beginning of an AnnotatedBody, or it might be
// a standalone annotation. To find out, we need more input from the user. // a standalone annotation.
//
// If the input ends in a newline, that means the user pressed Enter
// twice after the annotation, indicating this is not an AnnotatedBody,
// but rather a standalone Annotation. As such, record it as a PastDef!
if src.ends_with('\n') {
// Record the standalone type annotation for future use
self.add_past_def(ident.trim_end().to_string(), src.to_string());
}
// Return without running eval or clearing pending_src. // Return early without running eval, since neither standalone annotations
// nor pending potential AnnotatedBody exprs can be evaluated as expressions.
return String::new(); return String::new();
} }
} ValueDef::Body(
ValueDef::Body(_loc_pattern, _loc_expr) Loc {
| ValueDef::AnnotatedBody { value: Pattern::Identifier(ident),
body_pattern: _loc_pattern,
body_expr: _loc_expr,
.. ..
} => todo!("handle receiving a toplevel def of a value/function"), },
_,
)
| ValueDef::AnnotatedBody {
body_pattern:
Loc {
value: Pattern::Identifier(ident),
..
},
..
} => {
self.add_past_def(ident.to_string(), src.to_string());
// Return early without running eval, since neither standalone annotations
// nor pending potential AnnotatedBody exprs can be evaluated as expressions.
return String::new();
}
ValueDef::Annotation(_, _)
| ValueDef::Body(_, _)
| ValueDef::AnnotatedBody { .. } => {
todo!("handle pattern other than identifier (which repl doesn't support)")
}
ValueDef::Expect { .. } => { ValueDef::Expect { .. } => {
todo!("handle receiving an `expect` - what should the repl do for that?") todo!("handle receiving an `expect` - what should the repl do for that?")
} }
@ -148,22 +187,66 @@ impl ReplState {
ParseOutcome::Empty | ParseOutcome::Help | ParseOutcome::Exit => unreachable!(), ParseOutcome::Empty | ParseOutcome::Help | ParseOutcome::Exit => unreachable!(),
}; };
let var_name;
// 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!)
match self.past_def_idents.get(src.trim()) {
Some(existing_ident) => {
var_name = existing_ident.to_string();
}
None => {
var_name = format!("{AUTO_VAR_PREFIX}{}", self.next_auto_ident());
self.add_past_def(var_name.clone(), format!("{var_name} = {}", src.trim_end()));
}
};
let output = format_output(gen_and_eval_llvm( let output = format_output(gen_and_eval_llvm(
src, &self.with_past_defs(src),
Triple::host(), Triple::host(),
OptLevel::Normal, OptLevel::Normal,
"TODOval1".to_string(), var_name,
)); ));
output output
} }
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());
// Override any defs that would be shadowed
if !self.past_defs.is_empty() {
drain_filter(&mut self.past_defs, |PastDef { ident, .. }| {
if existing_idents.contains(ident) {
// We already have a newer def for this ident, so drop the old one.
false
} else {
// We've never seen this def, so record it!
existing_idents.insert(ident.clone());
true
}
});
}
self.past_defs.push_front(PastDef { ident, src });
}
/// 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 { pub fn with_past_defs(&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');
} }

View file

@ -16,8 +16,12 @@ fn standalone_annotation() {
let mut state = ReplState::new(); let mut state = ReplState::new();
let mut input = "x : Str".to_string(); let mut input = "x : Str".to_string();
assert_eq!(&state.with_past_defs("test"), "test");
incomplete(&mut input); incomplete(&mut input);
complete(&input, &mut state, Ok("")); complete(&input, &mut state, Ok(""));
assert_eq!(&state.with_past_defs("test"), "x : Str\ntest");
} }
/// validate and step the given input, then check the Result vs the input /// validate and step the given input, then check the Result vs the input