1use 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
14pub 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 fn clear() {
23 menu::clear();
24 Ok(ConValue::Empty)
25 }
26 fn eval(ConValue::Quote(quote)) @env {
28 env.eval(quote.as_ref())
29 }
30 fn import(ConValue::String(path)) @env {
32 load_file(env, &**path).or(Ok(ConValue::Empty))
33 }
34
35 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}