improved REPL a bit

now uses rustyline for input
This commit is contained in:
Moritz Gmeiner 2024-09-02 03:10:35 +02:00
commit fb88595b6c
13 changed files with 560 additions and 100 deletions

View file

@ -1,5 +1,9 @@
use clap::{ArgAction, Parser};
mod repl;
use repl::{run_repl, LoxResult};
#[derive(Parser, Debug)]
struct CliArgs {
#[arg()]
@ -82,6 +86,12 @@ fn main() {
}
}
rlox2_interpreter::run_repl(&mut runtime);
run_repl(
move |source| match rlox2_interpreter::run(source, &mut runtime) {
Ok(()) => LoxResult::Ok(()),
Err(rlox2_interpreter::LoxError::Exit { exit_code }) => LoxResult::Exit(exit_code),
Err(e) => LoxResult::Err(e),
},
);
}
}

199
src/repl.rs Normal file
View file

@ -0,0 +1,199 @@
use std::error::Error;
use colored::Colorize;
use rustyline::completion::Completer;
use rustyline::error::ReadlineError;
use rustyline::highlight::Highlighter;
use rustyline::hint::Hinter;
use rustyline::history::FileHistory;
use rustyline::validate::{ValidationContext, ValidationResult, Validator};
use rustyline::{Editor, Helper};
pub enum LoxResult<E: Error> {
Ok(()),
Err(E),
Exit(i32),
}
#[derive(Default)]
struct ReplHelper {}
impl Validator for ReplHelper {
fn validate(&self, ctx: &mut ValidationContext) -> rustyline::Result<ValidationResult> {
let input = ctx.input();
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum Brackets {
LeftParen,
LeftBrace,
}
use Brackets::*;
let mut brackets = Vec::new();
let chars: Vec<char> = input.chars().collect();
let mut pos = 0;
while pos < chars.len() {
match chars[pos] {
'(' => brackets.push(LeftParen),
')' => {
if brackets.is_empty() || brackets.last().unwrap() != &LeftParen {
// no matching opening paren on the stack -> parser error
return Ok(ValidationResult::Valid(None));
}
brackets.pop();
}
'{' => brackets.push(LeftBrace),
'}' => {
if brackets.is_empty() || brackets.last().unwrap() != &LeftBrace {
// no matching opening brace on the stack -> parser error
return Ok(ValidationResult::Valid(None));
}
brackets.pop();
}
'/' => {
// line comment
match chars[pos + 1] {
'/' => {
pos += 2;
while pos < chars.len() && chars[pos] != '\n' {
pos += 1;
}
}
'*' => {
pos += 2;
let mut comment_depth: u32 = 1;
let match_two = |pos, c1, c2| chars[pos] == c1 && chars[pos + 1] == c2;
while pos < chars.len() - 1 {
if match_two(pos, '/', '*') {
comment_depth += 1;
pos += 2;
continue;
}
if match_two(pos, '*', '/') {
comment_depth -= 1;
pos += 2;
if comment_depth == 0 {
break;
}
continue;
}
pos += 1;
}
continue;
}
_ => {}
}
}
_ => {}
}
pos += 1;
}
if !brackets.is_empty() {
return Ok(ValidationResult::Incomplete);
}
// all braces/parens/brackets closed => Ok
Ok(ValidationResult::Valid(None))
}
fn validate_while_typing(&self) -> bool {
false
}
}
impl Highlighter for ReplHelper {}
impl Hinter for ReplHelper {
type Hint = String;
}
impl Completer for ReplHelper {
type Candidate = String;
}
impl Helper for ReplHelper {}
pub fn run_repl<F: FnMut(&str) -> LoxResult<E>, E: Error>(mut run: F) {
// let mut readline = DefaultEditor::new().unwrap();
let mut readline: Editor<ReplHelper, FileHistory> = Editor::new().unwrap();
readline.set_helper(Some(ReplHelper::default()));
let _ = readline.load_history("/tmp/rlox_history.txt");
let mut input_buf = String::new();
'outer: loop {
input_buf.clear();
match readline.readline("> ") {
Ok(line) => {
readline.add_history_entry(&line).unwrap();
input_buf += &line;
}
Err(ReadlineError::Interrupted) => continue 'outer,
Err(ReadlineError::Eof) => break 'outer,
Err(err) => {
println!("Error: {:?}", err);
continue 'outer;
}
};
/* 'inner: loop {
let num_open_braces =
(input_buf.matches('{').count() as i64) - (input_buf.matches('}').count() as i64);
let num_open_parens =
(input_buf.matches('(').count() as i64) - (input_buf.matches(')').count() as i64);
let num_open_brackets =
(input_buf.matches('[').count() as i64) - (input_buf.matches(']').count() as i64);
// all braces/parens/brackets closed => break
if num_open_braces == 0 && num_open_parens == 0 && num_open_brackets == 0 {
break 'inner;
}
// any braces/parens/brackets more closing than opening => break (will be parse error)
if num_open_braces < 0 || num_open_parens < 0 || num_open_brackets < 0 {
break 'inner;
}
match readline.readline("< ") {
Ok(line) => {
input_buf += &line;
}
Err(ReadlineError::Interrupted) => continue 'outer,
Err(ReadlineError::Eof) => break 'outer,
Err(err) => {
println!("Error: {:?}", err);
continue 'outer;
}
};
} */
if input_buf.is_empty() || input_buf == "exit\n" || input_buf == "quit\n" {
std::process::exit(0);
}
let input_buf = input_buf.trim();
match run(input_buf) {
LoxResult::Ok(()) => {}
LoxResult::Err(err) => println!("{}", err.to_string().red().bold()),
LoxResult::Exit(exit_code) => std::process::exit(exit_code),
}
}
readline.save_history("/tmp/rlox_history.txt").unwrap();
}