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:
parent
06ed0eae54
commit
8d641d0060
@ -11,4 +11,4 @@ pub const RESET: &str = "\x1b[0m";
|
|||||||
pub const OUTPUT: &str = "\x1b[38;5;117m";
|
pub const OUTPUT: &str = "\x1b[38;5;117m";
|
||||||
|
|
||||||
pub const CLEAR_LINES: &str = "\x1b[G\x1b[J";
|
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";
|
||||||
|
@ -49,10 +49,9 @@ pub fn is_terminal() -> bool {
|
|||||||
/// The CLI's operating mode
|
/// The CLI's operating mode
|
||||||
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, PartialOrd, Ord)]
|
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, PartialOrd, Ord)]
|
||||||
pub enum Mode {
|
pub enum Mode {
|
||||||
#[default]
|
|
||||||
Menu,
|
|
||||||
Lex,
|
Lex,
|
||||||
Fmt,
|
Fmt,
|
||||||
|
#[default]
|
||||||
Run,
|
Run,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -18,12 +18,7 @@ pub fn run(args: Args) -> Result<(), Box<dyn Error>> {
|
|||||||
let mut env = Environment::new();
|
let mut env = Environment::new();
|
||||||
|
|
||||||
env.add_builtins(&builtins! {
|
env.add_builtins(&builtins! {
|
||||||
/// Clears the screen
|
/// Lexes, parses, and evaluates an expression in the current env
|
||||||
fn clear() {
|
|
||||||
menu::clear();
|
|
||||||
Ok(ConValue::Empty)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn eval(string) @env {
|
fn eval(string) @env {
|
||||||
use cl_interpret::error::Error;
|
use cl_interpret::error::Error;
|
||||||
let string = match string {
|
let string = match string {
|
||||||
@ -84,12 +79,7 @@ pub fn run(args: Args) -> Result<(), Box<dyn Error>> {
|
|||||||
eprintln!("{e}")
|
eprintln!("{e}")
|
||||||
}
|
}
|
||||||
let mut ctx = Context::with_env(env);
|
let mut ctx = Context::with_env(env);
|
||||||
match mode {
|
menu::main_menu(mode, &mut ctx)?;
|
||||||
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)?,
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
let path = format_path_for_display(file.as_deref());
|
let path = format_path_for_display(file.as_deref());
|
||||||
let code = match &file {
|
let code = match &file {
|
||||||
@ -100,7 +90,7 @@ pub fn run(args: Args) -> Result<(), Box<dyn Error>> {
|
|||||||
match mode {
|
match mode {
|
||||||
Mode::Lex => lex_code(&path, &code),
|
Mode::Lex => lex_code(&path, &code),
|
||||||
Mode::Fmt => fmt_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(())
|
Ok(())
|
||||||
|
@ -1,9 +1,11 @@
|
|||||||
use crate::{ansi, ctx};
|
use std::error::Error;
|
||||||
|
|
||||||
|
use crate::{ansi, args::Mode, ctx};
|
||||||
use cl_ast::Stmt;
|
use cl_ast::Stmt;
|
||||||
use cl_interpret::convalue::ConValue;
|
use cl_interpret::convalue::ConValue;
|
||||||
use cl_lexer::Lexer;
|
use cl_lexer::Lexer;
|
||||||
use cl_parser::Parser;
|
use cl_parser::Parser;
|
||||||
use repline::{error::ReplResult, prebaked::*};
|
use repline::{Error as RlError, error::ReplResult, prebaked::*};
|
||||||
|
|
||||||
pub fn clear() {
|
pub fn clear() {
|
||||||
print!("{}", ansi::CLEAR_ALL);
|
print!("{}", ansi::CLEAR_ALL);
|
||||||
@ -14,74 +16,111 @@ pub fn banner() {
|
|||||||
println!("--- conlang v{} 💪🦈 ---", env!("CARGO_PKG_VERSION"))
|
println!("--- conlang v{} 💪🦈 ---", env!("CARGO_PKG_VERSION"))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Presents a selection interface to the user
|
type ReplCallback = fn(&mut ctx::Context, &str) -> Result<Response, Box<dyn Error>>;
|
||||||
pub fn main_menu(ctx: &mut ctx::Context) -> ReplResult<()> {
|
type ReplMode = (&'static str, &'static str, &'static str, ReplCallback);
|
||||||
banner();
|
|
||||||
run(ctx)?;
|
#[rustfmt::skip]
|
||||||
read_and(ansi::GREEN, "mu>", " ?>", |line| {
|
const MODES: &[ReplMode] = &[
|
||||||
match line.trim() {
|
(ansi::CYAN, ".>", " >", mode_run),
|
||||||
"clear" => clear(),
|
(ansi::BRIGHT_BLUE, "l>", " >", mode_lex),
|
||||||
"l" | "lex" => lex(ctx)?,
|
(ansi::BRIGHT_MAGENTA, "f>", " >", mode_fmt),
|
||||||
"f" | "fmt" => fmt(ctx)?,
|
];
|
||||||
"r" | "run" => run(ctx)?,
|
|
||||||
"q" | "quit" => return Ok(Response::Break),
|
const fn get_mode(mode: Mode) -> ReplMode {
|
||||||
"h" | "help" => println!(
|
match mode {
|
||||||
"Valid commands
|
Mode::Lex => MODES[1],
|
||||||
lex (l): Spin up a lexer, and lex some lines
|
Mode::Fmt => MODES[2],
|
||||||
fmt (f): Format the input
|
Mode::Run => MODES[0],
|
||||||
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)
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
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_ast::ast_visitor::Fold;
|
||||||
use cl_parser::inliner::ModuleInliner;
|
use cl_parser::inliner::ModuleInliner;
|
||||||
|
|
||||||
read_and(ansi::CYAN, "cl>", " ?>", |line| {
|
if line.trim().is_empty() {
|
||||||
if line.trim().is_empty() {
|
return Ok(Response::Deny);
|
||||||
return Ok(Response::Deny);
|
}
|
||||||
}
|
let code = Parser::new("", Lexer::new(line)).parse::<Stmt>()?;
|
||||||
let code = Parser::new("", Lexer::new(line)).parse::<Stmt>()?;
|
let code = ModuleInliner::new(".").fold_stmt(code);
|
||||||
let code = ModuleInliner::new(".").fold_stmt(code);
|
|
||||||
|
|
||||||
print!("{}", ansi::OUTPUT);
|
print!("{}", ansi::OUTPUT);
|
||||||
match ctx.run(&code) {
|
match ctx.run(&code) {
|
||||||
Ok(ConValue::Empty) => print!("{}", ansi::RESET),
|
Ok(ConValue::Empty) => print!("{}", ansi::RESET),
|
||||||
Ok(v) => println!("{}{v}", ansi::RESET),
|
Ok(v) => println!("{}{v}", ansi::RESET),
|
||||||
Err(e) => println!("{}! > {e}{}", ansi::RED, ansi::RESET),
|
Err(e) => println!("{}! > {e}{}", ansi::RED, ansi::RESET),
|
||||||
}
|
}
|
||||||
Ok(Response::Accept)
|
Ok(Response::Accept)
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn lex(_ctx: &mut ctx::Context) -> ReplResult<()> {
|
pub fn mode_lex(_ctx: &mut ctx::Context, line: &str) -> Result<Response, Box<dyn Error>> {
|
||||||
read_and(ansi::BRIGHT_BLUE, "lx>", " ?>", |line| {
|
for token in Lexer::new(line) {
|
||||||
for token in Lexer::new(line) {
|
match token {
|
||||||
match token {
|
Ok(token) => crate::tools::print_token(&token),
|
||||||
Ok(token) => crate::tools::print_token(&token),
|
Err(e) => eprintln!("! > {}{e}{}", ansi::RED, ansi::RESET),
|
||||||
Err(e) => eprintln!("! > {}{e}{}", ansi::RED, ansi::RESET),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Ok(Response::Accept)
|
Ok(Response::Accept)
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn fmt(_ctx: &mut ctx::Context) -> ReplResult<()> {
|
pub fn mode_fmt(_ctx: &mut ctx::Context, line: &str) -> Result<Response, Box<dyn Error>> {
|
||||||
read_and(ansi::BRIGHT_MAGENTA, "cl>", " ?>", |line| {
|
let mut p = Parser::new("", Lexer::new(line));
|
||||||
let mut p = Parser::new("", Lexer::new(line));
|
|
||||||
|
|
||||||
match p.parse::<Stmt>() {
|
match p.parse::<Stmt>() {
|
||||||
Ok(code) => println!("{}{code}{}", ansi::OUTPUT, ansi::RESET),
|
Ok(code) => println!("{}{code}{}", ansi::OUTPUT, ansi::RESET),
|
||||||
Err(e) => Err(e)?,
|
Err(e) => Err(e)?,
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(Response::Accept)
|
Ok(Response::Accept)
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user