Skip to main content

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::{At, Expr, types::Symbol};
9use cl_interpret::{builtin::builtins, convalue::ConValue, env::Environment, interpret::Interpret};
10use cl_lexer::Lexer;
11use cl_parser::{Parser, inliner::ModuleInliner};
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        /// Lexes, parses, and evaluates an expression in the current env
22        fn eval(string) @env {
23            use cl_interpret::error::Error;
24            let string = match string {
25                ConValue::Str(string) => string.to_ref(),
26                ConValue::String(string) => string.as_str(),
27                ConValue::Ref(v) => {
28                    let string = v.get(env).cloned().unwrap_or_default();
29                    return eval(env, &[string])
30                }
31                _ => Err(Error::TypeError("string", string.typename()))?
32            };
33            match Parser::new(Lexer::new("eval".into(), string)).parse::<Expr>(0) {
34                Err(e) => Ok(ConValue::String(format!("{e}"))),
35                Ok(v) => v.interpret(env),
36            }
37        }
38
39        /// Executes a file
40        fn import(path) @env {
41            use cl_interpret::error::Error;
42            match path {
43                ConValue::Str(path) => load_file(env, &**path).or(Ok(ConValue::Empty)),
44                ConValue::String(path) => load_file(env, &**path).or(Ok(ConValue::Empty)),
45                _ => Err(Error::TypeError("string", path.typename()))
46            }
47        }
48
49        fn putchar(ConValue::Char(c)) {
50            print!("{c}");
51            Ok(ConValue::Empty)
52        }
53
54        /// Gets a line of input from stdin
55        fn get_line(prompt) {
56            use cl_interpret::error::Error;
57            let prompt = match prompt {
58                ConValue::Str(prompt) => prompt.to_ref(),
59                ConValue::String(prompt) => prompt.as_str(),
60                _ => Err(Error::TypeError("string", prompt.typename()))?,
61            };
62            match repline::Repline::new("", prompt, "").read() {
63                Ok(line) => Ok(ConValue::String(line)),
64                Err(repline::Error::CtrlD(line)) => Ok(ConValue::String(line)),
65                Err(repline::Error::CtrlC(_)) => Err(cl_interpret::error::Error::Break(ConValue::Empty)),
66                Err(e) => Ok(ConValue::String(e.to_string())),
67            }
68        }
69    });
70
71    for path in include {
72        load_file(&mut env, path)?;
73    }
74
75    if repl {
76        if let Some(file) = file
77            && let Err(e) = load_file(&mut env, file)
78        {
79            eprintln!("{e}")
80        }
81        let mut ctx = Context::with_env(env);
82        menu::main_menu(mode, &mut ctx)?;
83    } else {
84        let path = format_path_for_display(file.as_deref());
85        let code = match &file {
86            Some(file) => std::fs::read_to_string(file)?,
87            None => std::io::read_to_string(std::io::stdin())?,
88        };
89
90        match mode {
91            Mode::Lex => lex_code(&path, &code),
92            Mode::Fmt => fmt_code(&path, &code),
93            Mode::Run => run_code(&path, &code, &mut env),
94        }?;
95    }
96    Ok(())
97}
98
99fn format_path_for_display(path: Option<&Path>) -> Cow<'_, str> {
100    match path {
101        Some(file) => file
102            .to_str()
103            .map(Cow::Borrowed)
104            .unwrap_or_else(|| Cow::Owned(file.display().to_string())),
105        None => Cow::Borrowed(""),
106    }
107}
108
109fn load_file(env: &mut Environment, path: impl AsRef<Path>) -> Result<ConValue, Box<dyn Error>> {
110    let path = path.as_ref();
111    let path_display: Symbol = path.display().to_string().as_str().into();
112    let inliner = ModuleInliner::new(path.with_extension(""));
113    let file = std::fs::read_to_string(path)?;
114    let code: Expr = Parser::new(Lexer::new(path_display, &file)).parse(0)?;
115    let code = match inliner.inline(code) {
116        Ok(a) => a,
117        Err((code, io_errs, parse_errs)) => {
118            for (file, err) in io_errs {
119                eprintln!("{}:{err}", file.display());
120            }
121            for (file, err) in parse_errs {
122                eprintln!("{}:{err}", file.display());
123            }
124            code
125        }
126    };
127    // use cl_ast::WeightOf;
128    // eprintln!("File {} weighs {} units", code.name, code.weight_of());
129
130    match env.eval(&code) {
131        Ok(v) => Ok(v),
132        Err(e) => {
133            eprintln!("{e}");
134            Ok(ConValue::Empty)
135        }
136    }
137}
138
139fn lex_code(path: &str, code: &str) -> Result<(), Box<dyn Error>> {
140    let mut lexer = Lexer::new(path.into(), code);
141    while let Ok(token) = lexer.scan() {
142        if !path.is_empty() {
143            print!("{path}:");
144        }
145        print_token(&token);
146    }
147    Ok(())
148}
149
150fn fmt_code(path: &str, code: &str) -> Result<(), Box<dyn Error>> {
151    let code = Parser::new(Lexer::new(path.into(), code)).parse::<At<Expr>>(0)?;
152    println!("{code}");
153    Ok(())
154}
155
156fn run_code(path: &str, code: &str, env: &mut Environment) -> Result<(), Box<dyn Error>> {
157    let code = Parser::new(Lexer::new(path.into(), code)).parse::<At<Expr>>(0)?;
158    code.interpret(env)?;
159    if env.get("main".into()).is_ok() {
160        match env.call("main".into(), &[]) {
161            Ok(ConValue::Empty) => {}
162            Ok(ret) => println!("{ret}"),
163            Err(e) => println!("Error: {e}"),
164        }
165    }
166    Ok(())
167}