talc/talc-bin/src/repl.rs

159 lines
3.9 KiB
Rust

use std::{cell::RefCell, io::IsTerminal, process::ExitCode, rc::Rc};
use clap::ColorChoice;
use rustyline::{error::ReadlineError, history::{FileHistory, History}, ColorMode, Config, Editor};
use talc_lang::{compiler::compile_repl, symbol::Symbol, value::{function::disasm_recursive, Value}, Vm};
use crate::{helper::TalcHelper, Args};
#[derive(Clone, Copy, Default)]
pub struct ReplColors {
pub reset: &'static str,
pub error: &'static str,
}
impl ReplColors {
fn new(colors: ColorChoice) -> Self {
let colors = match colors {
ColorChoice::Always => true,
ColorChoice::Never => false,
ColorChoice::Auto => std::io::stdout().is_terminal(),
};
if !colors {
return Self::default()
}
Self {
reset: "\x1b[0m",
error: "\x1b[91m",
}
}
}
fn get_colmode(args: &Args) -> ColorMode {
match args.color {
ColorChoice::Auto => ColorMode::Enabled,
ColorChoice::Always => ColorMode::Forced,
ColorChoice::Never => ColorMode::Disabled,
}
}
pub fn init_rustyline(args: &Args) -> Result<Editor<TalcHelper, FileHistory>, ExitCode> {
let config = Config::builder()
.auto_add_history(true)
.color_mode(get_colmode(args))
.check_cursor_position(true)
.completion_type(rustyline::CompletionType::List)
.max_history_size(4096).unwrap()
.build();
let mut hist = FileHistory::default();
if let Some(f) = &args.histfile {
if hist.load(f).is_err() {
eprintln!("Warn: failed to load history");
} else if hist.save(f).is_err() {
eprintln!("Warn: failed to save history");
}
}
match rustyline::Editor::with_history(config, hist) {
Ok(rl) => Ok(rl),
Err(ReadlineError::Io(e)) => {
eprintln!("Error creating repl: {e}");
Err(ExitCode::FAILURE)
},
Err(ReadlineError::Errno(e)) => {
eprintln!("Error creating repl: {e}");
Err(ExitCode::FAILURE)
},
Err(_) => Err(ExitCode::SUCCESS)
}
}
pub fn repl(args: &Args) -> ExitCode {
if args.disasm {
eprintln!("input disassembly enabled");
}
let parser = talc_lang::Parser::new();
let mut compiler_globals = Vec::new();
let mut vm = Vm::new(256);
talc_std::load_all(&mut vm);
let interrupt = vm.get_interrupt();
let ctrlc_res = ctrlc::set_handler(move || {
interrupt.fetch_or(true, std::sync::atomic::Ordering::Relaxed);
});
if let Err(e) = ctrlc_res {
eprintln!("Warn: couldn't set ctrl+c handler: {e}");
}
let prev1_sym = Symbol::get("_");
let prev2_sym = Symbol::get("__");
let prev3_sym = Symbol::get("___");
vm.set_global(prev1_sym, Value::Nil);
vm.set_global(prev2_sym, Value::Nil);
vm.set_global(prev3_sym, Value::Nil);
let c = ReplColors::new(args.color);
let mut rl = match init_rustyline(args) {
Ok(rl) => rl,
Err(e) => return e,
};
let vm = Rc::new(RefCell::new(vm));
rl.set_helper(Some(TalcHelper::new(vm.clone())));
loop {
if let Some(f) = &args.histfile {
let _ = rl.save_history(f);
}
let line = rl.readline(">> ");
let line = match line {
Ok(line) => line,
Err(ReadlineError::Eof) => return ExitCode::SUCCESS,
Err(ReadlineError::Interrupted) => continue,
Err(e) => {
eprintln!("{}Error:{} {e}", c.error, c.reset);
continue
},
};
let ex = match parser.parse(&line) {
Ok(ex) => ex,
Err(e) => {
eprintln!("{}Error:{} {e}", c.error, c.reset);
continue
},
};
let (f, g) = compile_repl(&ex, &compiler_globals);
compiler_globals = g;
let func = Rc::new(f);
if args.disasm {
if let Err(e) = disasm_recursive(&func, &mut std::io::stderr()) {
eprintln!("{}Error:{} {e}", c.error, c.reset);
}
}
let mut vm = vm.borrow_mut();
match vm.run_function(func.clone(), vec![func.into()]) {
Ok(v) => {
let prev2 = vm.get_global(prev2_sym).unwrap().clone();
vm.set_global(prev3_sym, prev2);
let prev1 = vm.get_global(prev1_sym).unwrap().clone();
vm.set_global(prev2_sym, prev1);
vm.set_global(prev1_sym, v.clone());
if v != Value::Nil {
println!("{v:#}");
}
}
Err(e) => eprintln!("{}Error:{} {e}", c.error, c.reset),
}
}
}