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 self.files.clone().into_keys().collect()
522 }
523}