Share code between validation and step

This commit is contained in:
Richard Feldman 2022-10-27 03:32:04 -04:00
parent 0a80b543b4
commit 6fbafad3ff
No known key found for this signature in database
GPG key ID: F1F21AA5B1D9E43B
2 changed files with 108 additions and 62 deletions

View file

@ -49,9 +49,9 @@ struct PastDef {
#[derive(Completer, Helper, Hinter)]
pub struct ReplState {
pub prev_line_blank: bool,
pub pending_src: String,
validator: InputValidator,
prev_line_blank: bool,
pending_src: String,
_past_defs: LinkedList<PastDef>,
}
@ -66,8 +66,10 @@ impl ReplState {
}
pub fn step(&mut self, trim_line: &str) -> Result<String, i32> {
match trim_line.to_lowercase().as_str() {
"" => {
let arena = Bump::new();
match parse_src(&arena, trim_line) {
ParseOutcome::Empty => {
if self.pending_src.is_empty() {
self.prev_line_blank = false;
@ -96,14 +98,17 @@ impl ReplState {
Ok("\n".to_string())
}
}
":help" => {
ParseOutcome::Expr(_)
| ParseOutcome::ValueDef(_)
| ParseOutcome::TypeDef(_)
| ParseOutcome::Incomplete => self.eval_and_format(trim_line).map_err(|fail| {
todo!("gracefully report parse error in repl: {:?}", fail);
}),
ParseOutcome::Help => {
// TODO add link to repl tutorial(does not yet exist).
Ok(format!("\n{}\n", TIPS))
}
":exit" | ":quit" | ":q" => Err(0),
_ => self.eval_and_format(trim_line).map_err(|fail| {
todo!("gracefully report parse error in repl: {:?}", fail);
}),
ParseOutcome::Exit => Err(0),
}
}
@ -157,7 +162,8 @@ impl ReplState {
// Alias, Opaque, or Ability
todo!("handle Alias, Opaque, or Ability")
}
ParseOutcome::Incomplete => todo!(),
ParseOutcome::Incomplete => todo!("handle Incomplete parse"),
ParseOutcome::Empty | ParseOutcome::Help | ParseOutcome::Exit => unreachable!(),
};
let answer = gen_and_eval_llvm(
@ -193,50 +199,62 @@ enum ParseOutcome<'a> {
TypeDef(TypeDef<'a>),
Expr(Expr<'a>),
Incomplete,
Empty,
Help,
Exit,
}
fn parse_src<'a>(arena: &'a Bump, src: &'a str) -> ParseOutcome<'a> {
let src_bytes = src.trim().as_bytes();
fn parse_src<'a>(arena: &'a Bump, line: &'a str) -> ParseOutcome<'a> {
let trim_line = line.trim();
match roc_parse::expr::parse_loc_expr(0, &arena, State::new(src_bytes)) {
Ok((_, loc_expr, _)) => ParseOutcome::Expr(loc_expr.value),
// Special case some syntax errors to allow for multi-line inputs
Err((_, EExpr::Closure(EClosure::Body(_, _), _), _)) => 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(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)")
match trim_line.to_lowercase().as_str() {
"" => ParseOutcome::Empty,
":help" => ParseOutcome::Help,
":exit" | ":quit" | ":q" => ParseOutcome::Exit,
_ => {
let src_bytes = trim_line.as_bytes();
match roc_parse::expr::parse_loc_expr(0, &arena, State::new(src_bytes)) {
Ok((_, loc_expr, _)) => ParseOutcome::Expr(loc_expr.value),
// Special case some syntax errors to allow for multi-line inputs
Err((_, EExpr::Closure(EClosure::Body(_, _), _), _)) => 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(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::Incomplete,
}
}
Err(_) => ParseOutcome::Incomplete,
}
}
Err(_) => ParseOutcome::Incomplete,
}
}
@ -255,22 +273,20 @@ impl Validator for InputValidator {
}
pub fn validate(input: &str) -> rustyline::Result<ValidationResult> {
if input.is_empty() {
Ok(ValidationResult::Incomplete)
} else {
let arena = Bump::new();
let arena = Bump::new();
match parse_src(&arena, input) {
// 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!
ParseOutcome::ValueDef(ValueDef::Annotation(_, _)) | ParseOutcome::Incomplete => {
Ok(ValidationResult::Incomplete)
}
ParseOutcome::ValueDef(_) | ParseOutcome::TypeDef(_) | ParseOutcome::Expr(_) => {
Ok(ValidationResult::Valid(None))
}
}
match parse_src(&arena, input) {
// 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!
ParseOutcome::ValueDef(ValueDef::Annotation(_, _))
| ParseOutcome::Empty
| ParseOutcome::Incomplete => Ok(ValidationResult::Incomplete),
ParseOutcome::Help
| ParseOutcome::Exit
| ParseOutcome::ValueDef(_)
| ParseOutcome::TypeDef(_)
| ParseOutcome::Expr(_) => Ok(ValidationResult::Valid(None)),
}
}

View file

@ -6,10 +6,30 @@ fn one_plus_one() {
let mut state = ReplState::new();
let input = "1 + 1";
assert_validates(input);
assert_step(input, &mut state, Ok("2 : Num * # TODOval1"));
assert_done(&state);
}
#[test]
fn incomplete_annotation() {
let mut state = ReplState::new();
let input = "x : Str";
assert_incomplete(input);
assert_step(input, &mut state, Ok(""));
assert_done(&state);
}
fn assert_validates(input: &str) {
assert!(matches!(validate(input), Ok(ValidationResult::Valid(None))));
}
fn assert_incomplete(input: &str) {
assert!(matches!(validate(input), Ok(ValidationResult::Incomplete)));
}
/// step the given input, then check the Result vs the trimmed input with ANSI escape codes stripped.
fn assert_step(input: &str, state: &mut ReplState, expected_step_result: Result<&str, i32>) {
match state.step(input) {
Ok(string) => {
@ -25,3 +45,13 @@ fn assert_step(input: &str, state: &mut ReplState, expected_step_result: Result<
}
}
}
fn assert_done(state: &ReplState) {
assert_eq!(
state.pending_src,
String::new(),
"pending_src was not empty; it was {:?}",
state.pending_src
);
assert!(!state.prev_line_blank, "prev_line_blank was true");
}