cl-repl: Flatten menu structure so new friendo won't have to ^C^C

Plus it was more annoying than this is.
This commit is contained in:
John 2025-10-23 06:06:56 -04:00
parent 06ed0eae54
commit 8d641d0060
4 changed files with 102 additions and 74 deletions

View File

@ -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";

View File

@ -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,
}

View File

@ -18,12 +18,7 @@ pub fn run(args: Args) -> Result<(), Box<dyn Error>> {
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<dyn Error>> {
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<dyn Error>> {
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(())

View File

@ -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<Response, Box<dyn Error>>;
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<Response, Box<dyn Error>> {
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::<Stmt>()?;
let code = ModuleInliner::new(".").fold_stmt(code);
if line.trim().is_empty() {
return Ok(Response::Deny);
}
let code = Parser::new("", Lexer::new(line)).parse::<Stmt>()?;
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<Response, Box<dyn Error>> {
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<Response, Box<dyn Error>> {
let mut p = Parser::new("", Lexer::new(line));
match p.parse::<Stmt>() {
Ok(code) => println!("{}{code}{}", ansi::OUTPUT, ansi::RESET),
Err(e) => Err(e)?,
}
match p.parse::<Stmt>() {
Ok(code) => println!("{}{code}{}", ansi::OUTPUT, ansi::RESET),
Err(e) => Err(e)?,
}
Ok(Response::Accept)
})
Ok(Response::Accept)
}