mirror of
https://github.com/roc-lang/roc.git
synced 2025-09-27 13:59:08 +00:00
Persist defs
This commit is contained in:
parent
90e79379cb
commit
f841cdb2c0
2 changed files with 117 additions and 30 deletions
|
@ -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');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue