enalang/
lib.rs

1use clap::ValueEnum;
2use colored::Colorize;
3use enalang_checker::checker::{CheckError, Checker};
4use enalang_macro::{MacroError, MacroUnwrapper};
5use enalang_repl::{Repl, ReplError};
6use flexstr::ToLocalStr;
7use glob::glob;
8use std::{
9    collections::HashMap,
10    fmt::Display,
11    fs::{self, OpenOptions},
12    io::{Read, Write},
13    path::PathBuf,
14    process,
15};
16use vm::{
17    blocks::{Blocks, BlocksError},
18    machine::{VMOptions, VM},
19    native,
20};
21
22pub use enalang_checker as checker;
23pub use enalang_compiler as compiler;
24pub use enalang_docgen as docgen;
25pub use enalang_ir as ir;
26pub use enalang_optimizer as optimizer;
27pub use enalang_vm as vm;
28
29pub mod util;
30
31#[derive(Debug, thiserror::Error)]
32pub enum EnaError {
33    #[error("tokenizer error in file `{0}` - `{1}`")]
34    TokenizerError(String, compiler::tok::TokenizerError),
35    #[error("ast error in file `{0}` - `{1}`")]
36    ASTError(String, compiler::ast::ASTError),
37    #[error("irgen error in file `{0}` - `{1}`")]
38    IRGenError(String, compiler::irgen::IRGenError),
39    #[error("ir error - `{0}`")]
40    IRError(ir::IRError),
41    #[error("serialization error - `{0}`")]
42    SerializationError(ir::SerializationError),
43    #[error("VM error - `{0}`")]
44    VMError(vm::machine::VMError),
45    #[error("checker error - `{0}`")]
46    CheckerError(Box<dyn CheckError>),
47    #[error("checker errors(output not supported)")]
48    CheckerErrors(Vec<Box<dyn CheckError>>),
49    #[error("failed to read glob pattern `{0}`")]
50    FailedToReadGlobPattern(String),
51    #[error("fs error `{0}`")]
52    FSError(String),
53    #[error("blocks error - `{0}`")]
54    BlocksError(BlocksError),
55    #[error("not yet parsed `{0}`")]
56    NotYetParsed(String),
57    #[error("optimizer error - `{0}`")]
58    OptimizerError(Box<dyn optimizer::OptimizationError>),
59    #[error("files have not been linked")]
60    NotLinked,
61    #[error("no ir was provided")]
62    NoIR,
63    #[error("repl error - `{0}`")]
64    ReplError(ReplError),
65    #[error("macro error in file {0} - `{1}`")]
66    MacroError(String, MacroError),
67}
68
69#[derive(Copy, Clone)]
70pub struct EnaOptions {
71    pub debug_gc: bool,
72    pub gc: bool,
73    pub debug_calls: bool,
74    pub debug_stack: bool,
75}
76
77impl Default for EnaOptions {
78    fn default() -> Self {
79        Self {
80            debug_gc: false,
81            gc: true,
82            debug_calls: false,
83            debug_stack: false,
84        }
85    }
86}
87
88#[derive(ValueEnum, Debug, Clone, Copy)]
89pub enum DocGen {
90    JSON,
91    Markdown,
92    HTML,
93}
94
95impl Display for DocGen {
96    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
97        match self {
98            DocGen::JSON => write!(f, "json"),
99            DocGen::Markdown => write!(f, "markdown"),
100            DocGen::HTML => write!(f, "html"),
101        }
102    }
103}
104
105pub struct Ena {
106    pub tokenizer: compiler::tok::Tokenizer,
107    pub ast: compiler::ast::ASTBuilder,
108    pub compiler: compiler::irgen::IRGen,
109    pub vm: Option<vm::machine::VM>,
110    pub files: HashMap<String, String>,
111    pub astified_files: HashMap<String, compiler::ast::ASTNode>,
112    pub compiled_files: HashMap<String, ir::IR>,
113    pub checker: Checker,
114    pub optimizer: optimizer::Optimizer,
115    pub ir: Option<ir::IR>,
116    pub macro_unwrapper: MacroUnwrapper,
117}
118
119impl Default for Ena {
120    fn default() -> Self {
121        Self::new()
122    }
123}
124
125impl Ena {
126    pub fn new() -> Self {
127        Self {
128            optimizer: optimizer::Optimizer::default(),
129            tokenizer: compiler::tok::Tokenizer::new(),
130            checker: Checker::default(),
131            ast: compiler::ast::ASTBuilder::new(),
132            compiler: compiler::irgen::IRGen::new(),
133            vm: None,
134            files: HashMap::new(),
135            astified_files: HashMap::new(),
136            compiled_files: HashMap::new(),
137            ir: None,
138            macro_unwrapper: MacroUnwrapper::default(),
139        }
140    }
141
142    pub fn generate_doc(&self, generator: DocGen) -> Result<String, EnaError> {
143        let gen: Box<dyn docgen::DocRenderer> = match generator {
144            DocGen::JSON => Box::new(docgen::renderers::json::JsonRenderer),
145            DocGen::Markdown => Box::new(docgen::renderers::md::MarkdownRenderer),
146            DocGen::HTML => Box::new(docgen::renderers::html::HtmlRenderer),
147        };
148        let documentation = docgen::Documentation::from_ir(self.ir.as_ref().unwrap().clone());
149        Ok(gen.render(documentation))
150    }
151
152    pub fn optimize(&mut self, main: &str) -> Result<(), EnaError> {
153        let ir = match &self.ir {
154            Some(i) => i,
155            None => return Err(EnaError::NoIR),
156        };
157        self.ir = Some(
158            self.optimizer
159                .optimize(ir.clone(), &main.to_local_str())
160                .map_err(|x| EnaError::OptimizerError(x))?,
161        );
162        Ok(())
163    }
164
165    pub fn check(&mut self) -> Result<(), EnaError> {
166        let blocks = Blocks::new(native::group(), self.ir.as_ref().unwrap().clone())
167            .map_err(EnaError::BlocksError)?;
168        self.checker.blocks = Some(blocks);
169        let vec = self.checker.run_checks(false);
170        if !vec.is_empty() {
171            Err(EnaError::CheckerErrors(vec))
172        } else {
173            Ok(())
174        }
175    }
176
177    fn print_error(
178        &self,
179        data: &str,
180        file: &str,
181        line: usize,
182        col: usize,
183        file_data: &str,
184        print_line: bool,
185    ) {
186        eprintln!(
187            "{} {} {}",
188            "error".red().bold(),
189            format!("in {}:{}:{}:", file, line, col).dimmed(),
190            data.bold().bright_white(),
191        );
192        if print_line {
193            eprintln!(
194                "\t {} {}",
195                format!("{line} |").dimmed(),
196                Self::highlight_char_in_string(file_data.lines().nth(line - 1).unwrap(), col - 1)
197            );
198        }
199    }
200
201    pub fn report_error(&self, err: EnaError) {
202        match err {
203            EnaError::TokenizerError(file, data) => {
204                let file_data = self.files.get(&file).unwrap();
205                let (line, col) = util::get_line(file_data, data.0);
206                self.print_error(&format!("{}", data.1), &file, line, col, file_data, true);
207            }
208            EnaError::MacroError(file, data) => {
209                let file_data = self.files.get(&file).unwrap();
210                let (line, col) = util::get_line(file_data, data.get_pos());
211                self.print_error(&format!("{}", data), &file, line, col, file_data, true);
212            }
213            EnaError::ASTError(file, data) => {
214                let file_data = self.files.get(&file).unwrap();
215                let token = self.tokenizer.tokens.get(data.0).unwrap();
216                let (line, col) = util::get_line(file_data, token.0);
217                self.print_error(&format!("{}", data.1), &file, line, col, file_data, true);
218            }
219            EnaError::IRGenError(file, data) => {
220                let file_data = self.files.get(&file).unwrap();
221                let (line, col) = util::get_line(file_data, data.0 .0);
222                self.print_error(&format!("{}", data.1), &file, line, col, file_data, true);
223            }
224            EnaError::VMError(err) => {
225                eprintln!("{red}: {err}", red = "error".red().bold());
226                for call in self.vm.as_ref().unwrap().call_stack.iter().rev() {
227                    eprintln!("{}", format!("\t^ {call}").dimmed());
228                }
229            }
230            EnaError::CheckerErrors(errs) => {
231                for err in errs {
232                    self.report_error(EnaError::CheckerError(err));
233                }
234            }
235            EnaError::CheckerError(err) => {
236                eprintln!("{red}: {err}", red = "error".red().bold());
237            }
238            other => eprintln!("{}: {other}", "error".red().bold()),
239        }
240    }
241
242    pub fn report_error_and_exit(&self, err: EnaError) -> ! {
243        self.report_error(err);
244        process::exit(1);
245    }
246
247    pub fn unwrap_error<T>(&self, err: Result<T, EnaError>) -> T {
248        match err {
249            Ok(i) => i,
250            Err(e) => {
251                self.report_error_and_exit(e);
252            }
253        }
254    }
255
256    pub fn read_files(&mut self, paths: &[String]) -> Result<(), EnaError> {
257        let unwrapped = Self::read_paths(paths)?;
258
259        for path in unwrapped {
260            match fs::read_to_string(&path) {
261                Ok(i) => self.files.insert(path.display().to_string(), i),
262                Err(e) => {
263                    return Err(EnaError::FSError(e.to_string()));
264                }
265            };
266        }
267
268        Ok(())
269    }
270
271    pub fn parse_files(&mut self) -> Result<(), EnaError> {
272        let files = self.get_keys();
273
274        for name in files {
275            self.parse_file(&name)?;
276        }
277
278        Ok(())
279    }
280
281    pub fn parse_file(&mut self, name: &String) -> Result<(), EnaError> {
282        let file = self.files.get(name);
283        let file = match file {
284            Some(i) => i,
285            None => {
286                return Err(EnaError::FSError(name.clone()));
287            }
288        };
289
290        let a = self
291            .tokenizer
292            .parse(file)
293            .map_err(|x| EnaError::TokenizerError(name.clone(), x))?;
294        let with_unwrapped_macros = self
295            .macro_unwrapper
296            .unwrap_macros(a)
297            .map_err(|x| EnaError::MacroError(name.clone(), x))?;
298        let ast = self
299            .ast
300            .parse(&with_unwrapped_macros)
301            .map_err(|x| EnaError::ASTError(name.clone(), x))?;
302
303        self.tokenizer.clean();
304        self.ast.clean();
305
306        self.astified_files.insert(name.clone(), ast);
307        Ok(())
308    }
309
310    pub fn compile_files(&mut self) -> Result<(), EnaError> {
311        let files = self
312            .astified_files
313            .clone()
314            .into_keys()
315            .collect::<Vec<String>>();
316
317        for name in files {
318            self.compile_file(&name)?;
319        }
320
321        Ok(())
322    }
323
324    pub fn compile_file(&mut self, name: &String) -> Result<(), EnaError> {
325        let data = self.astified_files.get(name);
326        let data = match data {
327            Some(i) => i,
328            None => {
329                return Err(EnaError::NotYetParsed(name.clone()));
330            }
331        };
332
333        let ir = self
334            .compiler
335            .compile(data)
336            .map_err(|x| EnaError::IRGenError(name.clone(), x))?;
337
338        self.compiled_files.insert(name.clone(), ir);
339
340        Ok(())
341    }
342
343    pub fn link_files(&mut self) -> Result<(), EnaError> {
344        let mut ir = ir::IR::new();
345
346        for sub_ir in self
347            .compiled_files
348            .clone()
349            .into_values()
350            .collect::<Vec<ir::IR>>()
351        {
352            ir.add(&sub_ir).map_err(EnaError::IRError)?;
353        }
354
355        self.ir = Some(ir);
356        Ok(())
357    }
358
359    pub fn save(&self, output: &str) -> Result<(), EnaError> {
360        match &self.ir {
361            Some(i) => {
362                let u8vec = i
363                    .into_serializable()
364                    .into_vec()
365                    .map_err(EnaError::SerializationError)?;
366                let mut file = OpenOptions::new()
367                    .create(true)
368                    .write(true)
369                    .read(false)
370                    .open(output)
371                    .map_err(|x| EnaError::FSError(x.to_string()))?;
372                file.write_all(&u8vec)
373                    .map_err(|x| EnaError::FSError(x.to_string()))
374            }
375            None => Err(EnaError::NotLinked),
376        }
377    }
378
379    pub fn load_irs(&mut self, paths: &[String]) -> Result<(), EnaError> {
380        let paths = Self::read_paths(paths)?;
381        let mut ir = ir::IR::new();
382
383        for path in paths {
384            let sub_ir = self.load_ir(path.to_str().unwrap())?;
385            ir.add(&sub_ir).unwrap();
386        }
387
388        self.ir = Some(ir);
389
390        Ok(())
391    }
392
393    pub fn load_ir(&mut self, from: &str) -> Result<ir::IR, EnaError> {
394        let mut open_opts = OpenOptions::new()
395            .read(true)
396            .open(from)
397            .map_err(|x| EnaError::FSError(x.to_string()))?;
398        let mut v = Vec::<u8>::new();
399        open_opts
400            .read_to_end(&mut v)
401            .map_err(|x| EnaError::FSError(x.to_string()))?;
402
403        let serial = ir::from_vec(&v).map_err(EnaError::SerializationError)?;
404        serial.into_ir().map_err(EnaError::SerializationError)
405    }
406
407    pub fn run_repl(&mut self, options: VMOptions) -> Result<(), EnaError> {
408        let mut repl = Repl::new(VM::new(options));
409
410        repl.run_interactive();
411    }
412
413    pub fn display_json(&self, pretty: bool) -> Result<(), EnaError> {
414        match &self.ir {
415            Some(ir) => {
416                let res = if pretty {
417                    serde_json::to_string_pretty(ir).unwrap()
418                } else {
419                    serde_json::to_string(ir).unwrap()
420                };
421                println!("{}", res);
422                Ok(())
423            }
424            None => Err(EnaError::NotLinked),
425        }
426    }
427
428    pub fn run(&mut self, main: &str, options: vm::machine::VMOptions) -> Result<(), EnaError> {
429        self.vm = Some(vm::machine::VM::new(options));
430        let ir = match self.ir {
431            Some(ref mut i) => i,
432            None => {
433                return Err(EnaError::NotLinked);
434            }
435        };
436
437        let blocks = vm::blocks::Blocks::new(vm::native::group(), ir.clone());
438        let blocks = match blocks {
439            Ok(blocks) => blocks,
440            Err(err) => {
441                return Err(EnaError::IRError(err.into()));
442            }
443        };
444
445        self.vm
446            .as_mut()
447            .unwrap()
448            .run(&main.to_local_str(), blocks)
449            .map_err(EnaError::VMError)
450            .map(|_| ())
451    }
452
453    pub fn run_main(&mut self, options: VMOptions) -> Result<(), EnaError> {
454        self.run("main", options)
455    }
456
457    pub fn clean(&mut self) {
458        self.tokenizer.clean();
459        self.ast.clean();
460        self.vm = None;
461        self.files = HashMap::new();
462        self.astified_files = HashMap::new();
463        self.compiled_files = HashMap::new();
464        self.ir = None;
465    }
466
467    fn read_paths(paths: &[String]) -> Result<Vec<PathBuf>, EnaError> {
468        let mut unwrapped = Vec::<PathBuf>::new();
469
470        for path in paths {
471            let resolved_paths = match glob(path) {
472                Ok(i) => i,
473                Err(e) => {
474                    return Err(EnaError::FailedToReadGlobPattern(format!(
475                        "{}: {}",
476                        e.pos, e.msg
477                    )));
478                }
479            };
480
481            for resolved_path in resolved_paths {
482                match resolved_path {
483                    Ok(i) => {
484                        unwrapped.push(i);
485                    }
486                    Err(e) => {
487                        return Err(EnaError::FailedToReadGlobPattern(format!(
488                            "{}: {}",
489                            e.path().display(),
490                            e.error()
491                        )));
492                    }
493                };
494            }
495        }
496
497        for u in &unwrapped {
498            if !u.as_path().exists() || u.as_path().is_dir() {
499                return Err(EnaError::FSError(format!("file {} not found", u.display())));
500            }
501        }
502
503        if unwrapped.is_empty() {
504            return Err(EnaError::FSError("files not found".to_string()));
505        }
506
507        Ok(unwrapped)
508    }
509
510    fn highlight_char_in_string(initial: &str, at: usize) -> String {
511        let start: String = initial.chars().take(at).collect();
512        let end: String = initial.chars().skip(at + 1).collect();
513        let ch = String::from(initial.chars().nth(at).unwrap_or('\0'));
514
515        format!("{}{}{}", start, ch.bold().red(), end)
516    }
517
518    fn get_keys(&self) -> Vec<String> {
519        // some cloning and evil shenanigans are needed to trick borrow checker
520        // todo: redo this
521        self.files.clone().into_keys().collect()
522    }
523}