cl_repl/
menu.rs

1use std::error::Error;
2
3use crate::{ansi, args::Mode, ctx};
4use cl_ast::Stmt;
5use cl_interpret::convalue::ConValue;
6use cl_lexer::Lexer;
7use cl_parser::Parser;
8use repline::{Error as RlError, error::ReplResult, prebaked::*};
9
10pub fn clear() {
11    print!("{}", ansi::CLEAR_ALL);
12    banner()
13}
14
15pub fn banner() {
16    println!("--- conlang v{} 💪🦈 ---", env!("CARGO_PKG_VERSION"))
17}
18
19type ReplCallback = fn(&mut ctx::Context, &str) -> Result<Response, Box<dyn Error>>;
20type ReplMode = (&'static str, &'static str, &'static str, ReplCallback);
21
22#[rustfmt::skip]
23const MODES: &[ReplMode] = &[
24    (ansi::CYAN,            ".>", " >", mode_run),
25    (ansi::BRIGHT_BLUE,     "l>", " >", mode_lex),
26    (ansi::BRIGHT_MAGENTA,  "f>", " >", mode_fmt),
27];
28
29const fn get_mode(mode: Mode) -> ReplMode {
30    match mode {
31        Mode::Lex => MODES[1],
32        Mode::Fmt => MODES[2],
33        Mode::Run => MODES[0],
34    }
35}
36
37/// Presents a selection interface to the user
38pub fn main_menu(mode: Mode, ctx: &mut ctx::Context) -> ReplResult<()> {
39    let mut mode = get_mode(mode);
40    banner();
41
42    let mut rl = repline::Repline::new(mode.0, mode.1, mode.2);
43    loop {
44        rl.set_prompt(mode.0, mode.1, mode.2);
45
46        let line = match rl.read() {
47            Err(RlError::CtrlC(_)) => return Ok(()),
48            Err(RlError::CtrlD(line)) => {
49                rl.deny();
50                line
51            }
52            Ok(line) => line,
53            Err(e) => Err(e)?,
54        };
55        print!("\x1b[G\x1b[J");
56        match line.trim() {
57            "" => continue,
58            "clear" => clear(),
59            "mode run" => mode = get_mode(Mode::Run),
60            "mode lex" => mode = get_mode(Mode::Lex),
61            "mode fmt" => mode = get_mode(Mode::Fmt),
62            "quit" => return Ok(()),
63            "help" => println!(
64                "Valid commands
65    help     : Print this list
66    clear    : Clear the screen
67    quit     : Exit the program
68    mode lex : Lex the input
69    mode fmt : Format the input
70    mode run : Evaluate some expressions"
71            ),
72            _ => match mode.3(ctx, &line) {
73                Ok(Response::Accept) => {
74                    rl.accept();
75                    continue;
76                }
77                Ok(Response::Deny) => {}
78                Ok(Response::Break) => return Ok(()),
79                Ok(Response::Continue) => continue,
80                Err(e) => rl.print_inline(format_args!("\t\x1b[91m{e}\x1b[0m"))?,
81            },
82        }
83        rl.deny();
84    }
85}
86
87pub fn mode_run(ctx: &mut ctx::Context, line: &str) -> Result<Response, Box<dyn Error>> {
88    use cl_ast::ast_visitor::Fold;
89    use cl_parser::inliner::ModuleInliner;
90
91    if line.trim().is_empty() {
92        return Ok(Response::Deny);
93    }
94    let code = Parser::new("", Lexer::new(line)).parse::<Stmt>()?;
95    let code = ModuleInliner::new(".").fold_stmt(code);
96
97    print!("{}", ansi::OUTPUT);
98    match ctx.run(&code) {
99        Ok(ConValue::Empty) => print!("{}", ansi::RESET),
100        Ok(v) => println!("{}{v}", ansi::RESET),
101        Err(e) => println!("{}! > {e}{}", ansi::RED, ansi::RESET),
102    }
103    Ok(Response::Accept)
104}
105
106pub fn mode_lex(_ctx: &mut ctx::Context, line: &str) -> Result<Response, Box<dyn Error>> {
107    for token in Lexer::new(line) {
108        match token {
109            Ok(token) => crate::tools::print_token(&token),
110            Err(e) => eprintln!("! > {}{e}{}", ansi::RED, ansi::RESET),
111        }
112    }
113
114    Ok(Response::Accept)
115}
116
117pub fn mode_fmt(_ctx: &mut ctx::Context, line: &str) -> Result<Response, Box<dyn Error>> {
118    let mut p = Parser::new("", Lexer::new(line));
119
120    match p.parse::<Stmt>() {
121        Ok(code) => println!("{}{code}{}", ansi::OUTPUT, ansi::RESET),
122        Err(e) => Err(e)?,
123    }
124
125    Ok(Response::Accept)
126}