diff --git a/compiler/cl-repl/src/ansi.rs b/compiler/cl-repl/src/ansi.rs index 0e537e8..c96e695 100644 --- a/compiler/cl-repl/src/ansi.rs +++ b/compiler/cl-repl/src/ansi.rs @@ -11,4 +11,4 @@ pub const RESET: &str = "\x1b[0m"; pub const OUTPUT: &str = "\x1b[38;5;117m"; pub const CLEAR_LINES: &str = "\x1b[G\x1b[J"; -pub const CLEAR_ALL: &str = "\x1b[H\x1b[2J"; +pub const CLEAR_ALL: &str = "\x1b[H\x1b[2J\x1b[3J"; diff --git a/compiler/cl-repl/src/args.rs b/compiler/cl-repl/src/args.rs index 57dba4e..18b5f10 100644 --- a/compiler/cl-repl/src/args.rs +++ b/compiler/cl-repl/src/args.rs @@ -49,10 +49,9 @@ pub fn is_terminal() -> bool { /// The CLI's operating mode #[derive(Clone, Copy, Debug, Default, PartialEq, Eq, PartialOrd, Ord)] pub enum Mode { - #[default] - Menu, Lex, Fmt, + #[default] Run, } diff --git a/compiler/cl-repl/src/cli.rs b/compiler/cl-repl/src/cli.rs index 6aa1d7a..55a36e9 100644 --- a/compiler/cl-repl/src/cli.rs +++ b/compiler/cl-repl/src/cli.rs @@ -18,12 +18,7 @@ pub fn run(args: Args) -> Result<(), Box> { let mut env = Environment::new(); env.add_builtins(&builtins! { - /// Clears the screen - fn clear() { - menu::clear(); - Ok(ConValue::Empty) - } - + /// Lexes, parses, and evaluates an expression in the current env fn eval(string) @env { use cl_interpret::error::Error; let string = match string { @@ -84,12 +79,7 @@ pub fn run(args: Args) -> Result<(), Box> { eprintln!("{e}") } let mut ctx = Context::with_env(env); - match mode { - Mode::Menu => menu::main_menu(&mut ctx)?, - Mode::Lex => menu::lex(&mut ctx)?, - Mode::Fmt => menu::fmt(&mut ctx)?, - Mode::Run => menu::run(&mut ctx)?, - } + menu::main_menu(mode, &mut ctx)?; } else { let path = format_path_for_display(file.as_deref()); let code = match &file { @@ -100,7 +90,7 @@ pub fn run(args: Args) -> Result<(), Box> { match mode { Mode::Lex => lex_code(&path, &code), Mode::Fmt => fmt_code(&path, &code), - Mode::Run | Mode::Menu => run_code(&path, &code, &mut env), + Mode::Run => run_code(&path, &code, &mut env), }?; } Ok(()) diff --git a/compiler/cl-repl/src/menu.rs b/compiler/cl-repl/src/menu.rs index 38f9ed8..da8d3b4 100644 --- a/compiler/cl-repl/src/menu.rs +++ b/compiler/cl-repl/src/menu.rs @@ -1,9 +1,11 @@ -use crate::{ansi, ctx}; +use std::error::Error; + +use crate::{ansi, args::Mode, ctx}; use cl_ast::Stmt; use cl_interpret::convalue::ConValue; use cl_lexer::Lexer; use cl_parser::Parser; -use repline::{error::ReplResult, prebaked::*}; +use repline::{Error as RlError, error::ReplResult, prebaked::*}; pub fn clear() { print!("{}", ansi::CLEAR_ALL); @@ -14,74 +16,111 @@ pub fn banner() { println!("--- conlang v{} 💪🦈 ---", env!("CARGO_PKG_VERSION")) } -/// Presents a selection interface to the user -pub fn main_menu(ctx: &mut ctx::Context) -> ReplResult<()> { - banner(); - run(ctx)?; - read_and(ansi::GREEN, "mu>", " ?>", |line| { - match line.trim() { - "clear" => clear(), - "l" | "lex" => lex(ctx)?, - "f" | "fmt" => fmt(ctx)?, - "r" | "run" => run(ctx)?, - "q" | "quit" => return Ok(Response::Break), - "h" | "help" => println!( - "Valid commands - lex (l): Spin up a lexer, and lex some lines - fmt (f): Format the input - run (r): Enter the REPL, and evaluate some statements - help (h): Print this list - quit (q): Exit the program" - ), - _ => Err("Unknown command. Type \"help\" for help")?, - } - Ok(Response::Accept) - }) +type ReplCallback = fn(&mut ctx::Context, &str) -> Result>; +type ReplMode = (&'static str, &'static str, &'static str, ReplCallback); + +#[rustfmt::skip] +const MODES: &[ReplMode] = &[ + (ansi::CYAN, ".>", " >", mode_run), + (ansi::BRIGHT_BLUE, "l>", " >", mode_lex), + (ansi::BRIGHT_MAGENTA, "f>", " >", mode_fmt), +]; + +const fn get_mode(mode: Mode) -> ReplMode { + match mode { + Mode::Lex => MODES[1], + Mode::Fmt => MODES[2], + Mode::Run => MODES[0], + } } -pub fn run(ctx: &mut ctx::Context) -> ReplResult<()> { +/// Presents a selection interface to the user +pub fn main_menu(mode: Mode, ctx: &mut ctx::Context) -> ReplResult<()> { + let mut mode = get_mode(mode); + banner(); + + let mut rl = repline::Repline::new(mode.0, mode.1, mode.2); + loop { + rl.set_prompt(mode.0, mode.1, mode.2); + + let line = match rl.read() { + Err(RlError::CtrlC(_)) => return Ok(()), + Err(RlError::CtrlD(line)) => { + rl.deny(); + line + } + Ok(line) => line, + Err(e) => Err(e)?, + }; + print!("\x1b[G\x1b[J"); + match line.trim() { + "" => continue, + "clear" => clear(), + "mode run" => mode = get_mode(Mode::Run), + "mode lex" => mode = get_mode(Mode::Lex), + "mode fmt" => mode = get_mode(Mode::Fmt), + "quit" => return Ok(()), + "help" => println!( + "Valid commands + help : Print this list + clear : Clear the screen + quit : Exit the program + mode lex : Lex the input + mode fmt : Format the input + mode run : Evaluate some expressions" + ), + _ => match mode.3(ctx, &line) { + Ok(Response::Accept) => { + rl.accept(); + continue; + } + Ok(Response::Deny) => {} + Ok(Response::Break) => return Ok(()), + Ok(Response::Continue) => continue, + Err(e) => rl.print_inline(format_args!("\t\x1b[91m{e}\x1b[0m"))?, + }, + } + rl.deny(); + } +} + +pub fn mode_run(ctx: &mut ctx::Context, line: &str) -> Result> { use cl_ast::ast_visitor::Fold; use cl_parser::inliner::ModuleInliner; - read_and(ansi::CYAN, "cl>", " ?>", |line| { - if line.trim().is_empty() { - return Ok(Response::Deny); - } - let code = Parser::new("", Lexer::new(line)).parse::()?; - let code = ModuleInliner::new(".").fold_stmt(code); + if line.trim().is_empty() { + return Ok(Response::Deny); + } + let code = Parser::new("", Lexer::new(line)).parse::()?; + let code = ModuleInliner::new(".").fold_stmt(code); - print!("{}", ansi::OUTPUT); - match ctx.run(&code) { - Ok(ConValue::Empty) => print!("{}", ansi::RESET), - Ok(v) => println!("{}{v}", ansi::RESET), - Err(e) => println!("{}! > {e}{}", ansi::RED, ansi::RESET), - } - Ok(Response::Accept) - }) + print!("{}", ansi::OUTPUT); + match ctx.run(&code) { + Ok(ConValue::Empty) => print!("{}", ansi::RESET), + Ok(v) => println!("{}{v}", ansi::RESET), + Err(e) => println!("{}! > {e}{}", ansi::RED, ansi::RESET), + } + Ok(Response::Accept) } -pub fn lex(_ctx: &mut ctx::Context) -> ReplResult<()> { - read_and(ansi::BRIGHT_BLUE, "lx>", " ?>", |line| { - for token in Lexer::new(line) { - match token { - Ok(token) => crate::tools::print_token(&token), - Err(e) => eprintln!("! > {}{e}{}", ansi::RED, ansi::RESET), - } +pub fn mode_lex(_ctx: &mut ctx::Context, line: &str) -> Result> { + for token in Lexer::new(line) { + match token { + Ok(token) => crate::tools::print_token(&token), + Err(e) => eprintln!("! > {}{e}{}", ansi::RED, ansi::RESET), } + } - Ok(Response::Accept) - }) + Ok(Response::Accept) } -pub fn fmt(_ctx: &mut ctx::Context) -> ReplResult<()> { - read_and(ansi::BRIGHT_MAGENTA, "cl>", " ?>", |line| { - let mut p = Parser::new("", Lexer::new(line)); +pub fn mode_fmt(_ctx: &mut ctx::Context, line: &str) -> Result> { + let mut p = Parser::new("", Lexer::new(line)); - match p.parse::() { - Ok(code) => println!("{}{code}{}", ansi::OUTPUT, ansi::RESET), - Err(e) => Err(e)?, - } + match p.parse::() { + Ok(code) => println!("{}{code}{}", ansi::OUTPUT, ansi::RESET), + Err(e) => Err(e)?, + } - Ok(Response::Accept) - }) + Ok(Response::Accept) }