cl_repl/
cli.rs

1//! Implement's the command line interface
2use crate::{
3    args::{Args, Mode},
4    ctx::Context,
5    menu,
6    tools::print_token,
7};
8use cl_ast::File;
9use cl_interpret::{builtin::builtins, convalue::ConValue, env::Environment, interpret::Interpret};
10use cl_lexer::Lexer;
11use cl_parser::Parser;
12use std::{borrow::Cow, error::Error, path::Path};
13
14/// Run the command line interface
15pub fn run(args: Args) -> Result<(), Box<dyn Error>> {
16    let Args { file, include, mode, repl } = args;
17
18    let mut env = Environment::new();
19
20    env.add_builtins(&builtins! {
21        /// Clears the screen
22        fn clear() {
23            menu::clear();
24            Ok(ConValue::Empty)
25        }
26        /// Evaluates a quoted expression
27        fn eval(ConValue::Quote(quote)) @env {
28            env.eval(quote.as_ref())
29        }
30        /// Executes a file
31        fn import(ConValue::String(path)) @env {
32            load_file(env, &**path).or(Ok(ConValue::Empty))
33        }
34
35        /// Gets a line of input from stdin
36        fn get_line() {
37            match repline::Repline::new("", "", "").read() {
38                Ok(line) => Ok(ConValue::String(line.into())),
39                Err(repline::Error::CtrlD(line)) => Ok(ConValue::String(line.into())),
40                Err(repline::Error::CtrlC(_)) => Err(cl_interpret::error::Error::Break(ConValue::Empty)),
41                Err(e) => Ok(ConValue::String(e.to_string().into())),
42            }
43        }
44    });
45
46    for path in include {
47        load_file(&mut env, path)?;
48    }
49
50    if repl {
51        if let Some(file) = file {
52            if let Err(e) = load_file(&mut env, file) {
53                eprintln!("{e}")
54            }
55        }
56        let mut ctx = Context::with_env(env);
57        match mode {
58            Mode::Menu => menu::main_menu(&mut ctx)?,
59            Mode::Lex => menu::lex(&mut ctx)?,
60            Mode::Fmt => menu::fmt(&mut ctx)?,
61            Mode::Run => menu::run(&mut ctx)?,
62        }
63    } else {
64        let path = format_path_for_display(file.as_deref());
65        let code = match &file {
66            Some(file) => std::fs::read_to_string(file)?,
67            None => std::io::read_to_string(std::io::stdin())?,
68        };
69
70        match mode {
71            Mode::Lex => lex_code(&path, &code),
72            Mode::Fmt => fmt_code(&path, &code),
73            Mode::Run | Mode::Menu => run_code(&path, &code, &mut env),
74        }?;
75    }
76    Ok(())
77}
78
79fn format_path_for_display(path: Option<&Path>) -> Cow<str> {
80    match path {
81        Some(file) => file
82            .to_str()
83            .map(Cow::Borrowed)
84            .unwrap_or_else(|| Cow::Owned(file.display().to_string())),
85        None => Cow::Borrowed(""),
86    }
87}
88
89fn load_file(env: &mut Environment, path: impl AsRef<Path>) -> Result<ConValue, Box<dyn Error>> {
90    let path = path.as_ref();
91    let inliner = cl_parser::inliner::ModuleInliner::new(path.with_extension(""));
92    let file = std::fs::read_to_string(path)?;
93    let code = Parser::new(path.display().to_string(), Lexer::new(&file)).parse()?;
94    let code = match inliner.inline(code) {
95        Ok(a) => a,
96        Err((code, io_errs, parse_errs)) => {
97            for (file, err) in io_errs {
98                eprintln!("{}:{err}", file.display());
99            }
100            for (file, err) in parse_errs {
101                eprintln!("{}:{err}", file.display());
102            }
103            code
104        }
105    };
106    use cl_ast::WeightOf;
107    eprintln!("File {} weighs {} units", code.name, code.weight_of());
108
109    match env.eval(&code) {
110        Ok(v) => Ok(v),
111        Err(e) => {
112            eprintln!("{e}");
113            Ok(ConValue::Empty)
114        }
115    }
116}
117
118fn lex_code(path: &str, code: &str) -> Result<(), Box<dyn Error>> {
119    for token in Lexer::new(code) {
120        if !path.is_empty() {
121            print!("{}:", path);
122        }
123        match token {
124            Ok(token) => print_token(&token),
125            Err(e) => println!("{e}"),
126        }
127    }
128    Ok(())
129}
130
131fn fmt_code(path: &str, code: &str) -> Result<(), Box<dyn Error>> {
132    let code = Parser::new(path, Lexer::new(code)).parse::<File>()?;
133    println!("{code}");
134    Ok(())
135}
136
137fn run_code(path: &str, code: &str, env: &mut Environment) -> Result<(), Box<dyn Error>> {
138    let code = Parser::new(path, Lexer::new(code)).parse::<File>()?;
139    match code.interpret(env)? {
140        ConValue::Empty => {}
141        ret => println!("{ret}"),
142    }
143    if env.get("main".into()).is_ok() {
144        match env.call("main".into(), &[])? {
145            ConValue::Empty => {}
146            ret => println!("{ret}"),
147        }
148    }
149    Ok(())
150}