gabelang/
lib.rs

1#![warn(missing_docs)]
2#![doc(html_favicon_url = "https://buxogabriel.vercel.app/favicon.ico")]
3#![doc(html_logo_url = "https://buxogabriel.vercel.app/apple-touch-icon.png")]
4
5#[cfg(feature="wasm")]
6use wasm_bindgen::prelude::*;
7
8use std::{collections::HashMap, fs, io};
9
10mod ast;
11/// The repl module contains a start function run an isolated instance of the gabelang repl
12pub mod repl;
13/// The lexer module contains the Lexer struct which creates an iterator of tokens that may be used
14/// by the gabelang parser when given a string 
15pub mod lexer;
16/// The parser module contains the Parser struct which contains a lexer and creates an AST(Abstract Syntax Tree) when given a string
17pub mod parser;
18/// The evaluator module contains the GabrEnv struct which provides an environment in order to run gabelang code
19pub mod evaluator;
20
21use evaluator::Runtime;
22use parser::Parser;
23
24/// Config for running the gabelang interpretter
25#[derive(Default)]
26pub struct Config {
27    flags: HashMap<String, String>
28}
29
30impl Config {
31    /// Constructs a Config from a list of args
32    ///
33    /// Expects args to come in flag, value pairs
34    ///
35    /// Currently the only supported flag is file, which expects a filename to interpret
36    ///
37    /// No flags/empty arguments implys default behavior which is running the repl
38    ///
39    /// # Examples
40    ///
41    /// Getting args from the command line to generate config
42    /// ```
43    /// use std::env;
44    /// use gabelang::Config;
45    /// let args: Vec<String> = env::args().collect();
46    /// let config = Config::build(&args);
47    /// ```
48    ///
49    /// Manually setting a file argument
50    /// ```
51    /// use gabelang::Config;
52    /// let config = Config::build(&[String::from("--file"), String::from("index.gabe")]);
53    /// ```
54    pub fn build(args: &[String]) -> Result<Config, &'static str> {
55        let flags = Self::parse_flags(args);
56        Ok(Config { flags })
57    }
58
59    fn parse_flags(args: &[String]) -> HashMap<String, String> {
60        let mut map = HashMap::<String, String>::new();
61        args.windows(2).for_each(|pair| {
62            match pair[0].clone().split_once("--") {
63                Some((_, flag)) => {
64                    map.insert(flag.to_string(), pair[1].clone());
65                },
66                None => {},
67            }
68        });
69        map
70    }
71
72    fn get_flag(&self, flag: &str) -> Option<String> {
73        self.flags.get(flag).map(|f| f.clone())
74    }
75}
76
77/// Runs the gabelang interpretter by taking in a config and either running the repl or interpretting a .gabe file based on args
78/// 
79/// Config can be generated using gabelang::Config::build
80/// 
81/// # Errors
82///
83/// run can only generate errors if it is interpretting a file and either the file fails
84///
85/// # Example
86///
87/// ```
88/// use gabelang::Config;
89/// gabelang::run(Config::default()).expect("Application ran into an unexpected error");
90/// ```
91pub fn run(config: Config) -> io::Result<()> {
92    if let Some(file_name) = config.get_flag("file") {
93        let contents = fs::read_to_string(&file_name)?;
94        println!("parsing {}", &file_name);
95        let program = Parser::new(&contents).parse_program();
96        match program {
97            Ok(program) => {
98                println!("parsing complete! Running {}", &file_name);
99                let mut runtime = Runtime::new();
100                match runtime.run_program(&program) {
101                    Ok(res) => println!("{res}"),
102                    Err(err) => println!("Execution Failed: {err}")
103                }
104            },
105            Err(e) => println!("{e}"),
106        };
107        return Ok(())
108    }
109    repl::start();
110    Ok(())
111}
112
113/// Wasm Interpretter Binding for the gabelang language
114#[cfg_attr(feature = "wasm", wasm_bindgen)]
115pub struct Gabelang {
116    runtime: Runtime,
117}
118
119#[cfg_attr(feature = "wasm", wasm_bindgen)]
120impl Gabelang {
121    /// Creates a new interpretter
122    #[cfg_attr(feature = "wasm", wasm_bindgen(constructor))]
123    pub fn new() -> Self {
124        Self {
125            runtime: Runtime::new()
126        }
127    }
128
129    /// Returns the repl greeting text
130    #[cfg_attr(feature = "wasm", wasm_bindgen)]
131    pub fn repl_greeting() -> String {
132        repl::REPLGREETING.to_string()
133    }
134    
135    /// Executes a gabelang program
136    #[cfg_attr(feature = "wasm", wasm_bindgen)]
137    pub fn run_program(&mut self, program: &str) -> String {
138        let program = match Parser::new(program).parse_program() {
139            Ok(program) => program,
140            Err(e) => return format!("Error: {e}"),
141        };
142        self.runtime.reset_stack();
143        self.runtime.run_program(&program)
144            .map(|val| format!("{}", val))
145            .unwrap_or_else(|e| format!("Error: {e}"))
146    }
147
148    /// Executes a line of gabelang code
149    #[cfg_attr(feature="wasm", wasm_bindgen)]
150    pub fn execute(&mut self, code: &str) -> String {
151        let code = match Parser::new(code).parse_program() {
152            Ok(program) => program,
153            Err(e) => return format!("Error: {e}"),
154        };
155        self.runtime.run_program(&code)
156            .map(|val| format!("{}", val))
157            .unwrap_or_else(|e| format!("Error: {e}"))
158    }
159
160    /// Resets the GabrEnvironment including all variable scopes
161    #[cfg_attr(feature="wasm", wasm_bindgen)]
162    pub fn reset_stack(&mut self) {
163        self.runtime.reset_stack();
164    }
165
166    /// Pushes a new stack frame on to the stack
167    #[cfg_attr(feature="wasm", wasm_bindgen)]
168    pub fn push_frame(&mut self) {
169        self.runtime.current_context().push_scope();
170    }
171
172    /// Pops a stack frame off of the stack
173    #[cfg_attr(feature="wasm", wasm_bindgen)]
174    pub fn pop_frame(&mut self) -> bool {
175        match self.runtime.current_context().pop_scope() {
176            Ok(_) => true,
177            Err(_) => false
178        }
179    }
180}