//! Implement's the command line interface use crate::{ args::{Args, Mode}, ctx::Context, menu, tools::print_token, }; use cl_ast::File; use cl_interpret::{builtin::builtins, convalue::ConValue, env::Environment, interpret::Interpret}; use cl_lexer::Lexer; use cl_parser::Parser; use std::{borrow::Cow, error::Error, path::Path}; /// Run the command line interface pub fn run(args: Args) -> Result<(), Box> { let Args { file, include, mode, repl } = args; let mut env = Environment::new(); env.add_builtins(&builtins! { /// Clears the screen fn clear() { menu::clear(); Ok(ConValue::Empty) } fn eval(string) @env { use cl_interpret::error::Error; let string = match *string { ConValue::String(string) => string, ConValue::Ref(v) => { let string = env.get_id(v).cloned().unwrap_or_default(); return eval(env, &[string]) } _ => Err(Error::TypeError())? }; match Parser::new("eval", Lexer::new(string.to_ref())).parse::() { Err(e) => Ok(ConValue::String(format!("{e}").into())), Ok(v) => v.interpret(env), } } /// Executes a file fn import(ConValue::String(path)) @env { load_file(env, &**path).or(Ok(ConValue::Empty)) } fn putchar(ConValue::Char(c)) { print!("{c}"); Ok(ConValue::Empty) } /// Gets a line of input from stdin fn get_line(ConValue::String(prompt)) { match repline::Repline::new("", prompt.to_ref(), "").read() { Ok(line) => Ok(ConValue::String(line.into())), Err(repline::Error::CtrlD(line)) => Ok(ConValue::String(line.into())), Err(repline::Error::CtrlC(_)) => Err(cl_interpret::error::Error::Break(ConValue::Empty)), Err(e) => Ok(ConValue::String(e.to_string().into())), } } }); for path in include { load_file(&mut env, path)?; } if repl { if let Some(file) = file { if let Err(e) = load_file(&mut env, file) { 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)?, } } else { let path = format_path_for_display(file.as_deref()); let code = match &file { Some(file) => std::fs::read_to_string(file)?, None => std::io::read_to_string(std::io::stdin())?, }; match mode { Mode::Lex => lex_code(&path, &code), Mode::Fmt => fmt_code(&path, &code), Mode::Run | Mode::Menu => run_code(&path, &code, &mut env), }?; } Ok(()) } fn format_path_for_display(path: Option<&Path>) -> Cow { match path { Some(file) => file .to_str() .map(Cow::Borrowed) .unwrap_or_else(|| Cow::Owned(file.display().to_string())), None => Cow::Borrowed(""), } } fn load_file(env: &mut Environment, path: impl AsRef) -> Result> { let path = path.as_ref(); let inliner = cl_parser::inliner::ModuleInliner::new(path.with_extension("")); let file = std::fs::read_to_string(path)?; let code = Parser::new(path.display().to_string(), Lexer::new(&file)).parse()?; let code = match inliner.inline(code) { Ok(a) => a, Err((code, io_errs, parse_errs)) => { for (file, err) in io_errs { eprintln!("{}:{err}", file.display()); } for (file, err) in parse_errs { eprintln!("{}:{err}", file.display()); } code } }; use cl_ast::WeightOf; eprintln!("File {} weighs {} units", code.name, code.weight_of()); match env.eval(&code) { Ok(v) => Ok(v), Err(e) => { eprintln!("{e}"); Ok(ConValue::Empty) } } } fn lex_code(path: &str, code: &str) -> Result<(), Box> { for token in Lexer::new(code) { if !path.is_empty() { print!("{}:", path); } match token { Ok(token) => print_token(&token), Err(e) => println!("{e}"), } } Ok(()) } fn fmt_code(path: &str, code: &str) -> Result<(), Box> { let code = Parser::new(path, Lexer::new(code)).parse::()?; println!("{code}"); Ok(()) } fn run_code(path: &str, code: &str, env: &mut Environment) -> Result<(), Box> { let code = Parser::new(path, Lexer::new(code)).parse::()?; match code.interpret(env)? { ConValue::Empty => {} ret => println!("{ret}"), } if env.get("main".into()).is_ok() { match env.call("main".into(), &[])? { ConValue::Empty => {} ret => println!("{ret}"), } } Ok(()) }