diff --git a/README.md b/README.md index 6aaa43b..9920a5d 100644 --- a/README.md +++ b/README.md @@ -10,8 +10,11 @@ 4. [Important notes](#important-notes) 5. [Mechanics](#mechanics) 6. [Syntax](#syntax) -7. [Incoming features](#incoming-features) -8. [Examples](#examples) +7. [Preprocessor](#preprocessor)
+ 7.1 [Using it](#preprocessor-using-it)
+ 7.2 [Supported statements](#preprocessor-supported-statements) +8. [Incoming features](#incoming-features) +9. [Examples](#examples) ## Basic info @@ -42,6 +45,7 @@ See the table below for some flags you can provide when running your code. | --disable-warnings | `--disable-warnings` | Disable all warnings | | --disable-too-left-pointer-warning | `--disable-too-left-pointer-warning` | Disable the warning fired when you go to the -1 index in memory | | --hide-console | `--hide-console` | Hide the console when running the code | +| --sebek | `--sebek -1|0|1` | Specify the results for division by 0. First number is for dividing a number < 0, second for dividing 0 itself, and the third is for dividing a number > 0 | | --version | `--version 0.1.0` | Run the code using a specific version of the interpreter | ## Main features @@ -87,19 +91,8 @@ The magic of Brainfuck-like syntax is that it is easy and extremely difficult at | ~ | Subtracts one from the current cell | `~` | Yes | Yes | | + | Adds the cell in the inactive row to the cell in the active row | `+` | Yes | Yes | | - | Subtracts the cell in the inactive row from the cell in the active row | `-` | Yes | Yes | -| * | Multiplies the cell in the active row by the cell in the inactive row | `*`| Yes | Yes | -| / | Divides the cell in the active row by the cell in the inactive row |`/`| Yes | Yes | -| _ | Floors the current cell value (towards -infinity) |`\_`| No | Yes | -| & | Ceils the current cell value (towards +infinity) |`&`| No | Yes | -| ` | Sets the cell to a random number from 0 (inclusive) to 1 (exclusive) | ` | No | Yes | -| > | Move the cell pointer one to the right | `>`| Yes | Yes | -| < | Move the cell pointer one to the left |`<`| Yes | Yes | -| ^ | Switch active memory (sets the active as inactive and the inactive as active) |`^`| No | Yes | -| $. | Sets the cell to the value of user input as a number (if they input 69, the cell value will be 69) | `$.` | No | Yes | -| $, | Sets the cell to the value of user input as a character (if they input E, the cell value will be 69) | `$,`| No | Yes | -| \\. | Output the cell as a number (if the cell value is 69, 69 will be printed) |`\.`| No | Yes | -| \\, | Output the cell as a character (if the cell value is 69, E will be printed) |`\,`| No | Yes | -| [ | Start a while loop |`[` | No | Yes | +| _ | Multiplies the cell in the active row by the cell in the inactive row | `_`| Yes | Yes | | / | Divides the cell in the active row by the cell in the inactive row |`/`| Yes | Yes | | _ | Floors the current cell value (towards -infinity) |`\_`| No | Yes | | & | Ceils the current cell value (towards +infinity) |`&`| No | Yes | | ` | Sets the cell to a random number from 0 (inclusive) to 1 (exclusive) | ` | No | Yes | | > | Move the cell pointer one to the right | `>`| Yes | Yes | | < | Move the cell pointer one to the left |`<`| Yes | Yes | | ^ | Switch active memory (sets the active as inactive and the inactive as active) |`^`| No | Yes | | $. | Sets the cell to the value of user input as a number (if they input 69, the cell value will be 69) | `$.` | No | Yes | +| $, | Sets the cell to the value of user input as a character (if they input E, the cell value will be 69) | `$,`| No | Yes | | \\. | Output the cell as a number (if the cell value is 69, 69 will be printed) |`\.`| No | Yes | | \\, | Output the cell as a character (if the cell value is 69, E will be printed) |`\,`| No | Yes | | [ | Start a while loop |`[` | No | Yes | | ] | End a while loop | `]` | No | Yes | | [@ | Start a do-while loop | `[@` | No | Yes | | @] | End a do-while loop | `@]` | No | Yes | @@ -112,6 +105,26 @@ The magic of Brainfuck-like syntax is that it is easy and extremely difficult at Each line has to end with a punctuation (`:`) or else the program will crash. +## Preprocessor + +The preprocessor allows you to include flags into the code itself, so you don't have to rely on the user to run the code with the correct flags.
+The values parsed by the preprocessor are overridden by the flags passed in from the command line. + +### Using it + +The statements are put into the code file and begin with a `#`. + +### Supported statements + +Statement names are case-insensitive, so `version` is the same as `VERSION` and `VerSIoN`. However, this may not be true for other parts of the statement. + +| Statement | Aliases | Arguments | Explanation | Example | +| :----------------- | :------------------------------------ | :------------------------------------------------------------------------------------------------------------------------------ | :------------------------------------------------- | :----------------------------------- | +| `version` | None | None | Specifies the version of the interpreter to launch | `#version 0.3.0` | +| `no-console` | `noconsole`, `no_console` | None | Hides the console when running the code | `#no-console` | +| `disable-warnings` | `disablewarnings`, `disable_warnings` | The warning to disable: `too-left-pointer` (`tooleftpointer`) | Disables the specified warning | `#disable-warnings too-left-pointer` | +| `sebek` | None | The results of division by zero for negative numbers (``), zero itself (``), and positive numbers (`

`), separated by `\|`: `\|\|

` | Sets the result of division by zero to the specified number depending on the value of the number being divided | `sebek -1\|0\|1` (if a negative number was divided by 0 the result would be -1, if 0 was divided by 0 the result would be 0, and if a positive number was divided by 0 the result would be 1) | + ## Incoming features - Functions @@ -133,4 +146,4 @@ Fibonacci sequence: !>|10|!<^>|10|!<[@^+\.>\,<@]: ``` -You can find all the examples in the [examples folder](https://github.com/Pandicon/The-Golden/tree/main/examples). +You can find all the examples in the [examples folder](https://github.com/Pandicon/The-Golden/tree/main/examples). \ No newline at end of file diff --git a/the-golden/Cargo.toml b/the-golden/Cargo.toml index 772eb30..3278517 100644 --- a/the-golden/Cargo.toml +++ b/the-golden/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "the-golden" -version = "0.1.0" +version = "0.3.0" edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html diff --git a/the-golden/src/flags.rs b/the-golden/src/flags.rs index 5067129..f44b059 100644 --- a/the-golden/src/flags.rs +++ b/the-golden/src/flags.rs @@ -1,3 +1,5 @@ +use crate::Utils; + #[derive(Clone, Debug)] pub struct Warnings { pub too_left_pointer: bool, @@ -13,6 +15,7 @@ pub struct Flags { pub debug_heavy: bool, pub no_console: bool, pub raw_code_to_run: Option, + pub sebek: [Option; 3], pub version: Option, } @@ -27,6 +30,7 @@ impl Flags { debug_heavy: false, no_console: false, raw_code_to_run: None, + sebek: [None, None, None], version: None, } } @@ -51,6 +55,11 @@ impl Flags { } "--disable-warnings" => self.disabled_warnings = Warnings { too_left_pointer: true }, "--disable-too-left-pointer-warning" => self.disabled_warnings.too_left_pointer = true, + "--sebek" => { + if i + 1 < args_count { + self.sebek = Utils::parse_sebek(&args[i + 1]); + } + } "-" => { if self.raw_code_to_run.is_none() && i + 1 < args_count { self.raw_code_to_run = Some(args[i + 1].clone()); diff --git a/the-golden/src/interpreter/interpreter.rs b/the-golden/src/interpreter/interpreter.rs index e4c1ea0..0fd8788 100644 --- a/the-golden/src/interpreter/interpreter.rs +++ b/the-golden/src/interpreter/interpreter.rs @@ -1,5 +1,7 @@ use crate::Flags; +#[path = "./preprocessor.rs"] +mod preprocessor; #[path = "./versions/handler.rs"] mod versions_handler; @@ -14,14 +16,28 @@ pub struct Interpreter { } impl Interpreter { - pub fn new(mut version: String, code: String, code_path: std::path::PathBuf, flags: Flags, ansi_enabled: bool) -> Self { + pub fn new(version: Option, code: String, code_path: std::path::PathBuf, mut flags: Flags, ansi_enabled: bool) -> Self { + let mut preprocessor = preprocessor::Preprocessor::new(); + preprocessor.run(&code); + flags.no_console |= preprocessor.no_console; + let final_version = if let Some(ver) = version { + ver + } else if let Some(ver) = preprocessor.version { + ver + } else { + String::from("latest") + }; + if !flags.sebek.iter().any(|val| val.is_some()) { + flags.sebek = preprocessor.sebek; + }; let versions_handler = versions_handler::Handler::new(); - version = versions_handler.parse_version(version, ansi_enabled); + let parsed_version = versions_handler.parse_version(final_version, ansi_enabled); + Self { flags, ansi_enabled, code, - version, + version: parsed_version, versions_handler, code_path, } diff --git a/the-golden/src/interpreter/preprocessor.rs b/the-golden/src/interpreter/preprocessor.rs new file mode 100644 index 0000000..641a106 --- /dev/null +++ b/the-golden/src/interpreter/preprocessor.rs @@ -0,0 +1,79 @@ +use regex::Regex; + +use crate::Utils; + +#[derive(Clone, Debug)] +pub struct Warnings { + pub too_left_pointer: bool, +} + +#[derive(Clone, Debug)] +pub struct Preprocessor { + pub disabled_warnings: Warnings, + + pub no_console: bool, + pub sebek: [Option; 3], + pub version: Option, +} + +impl Preprocessor { + pub fn new() -> Self { + Self { + disabled_warnings: Warnings { too_left_pointer: false }, + + no_console: false, + sebek: [None, None, None], + version: None, + } + } + + pub fn run(&mut self, code: &str) { + let rule = Regex::new(crate::PREPROCESSOR_REGEX).unwrap(); + let statements = rule.find_iter(code).map(|m| m.as_str().trim()).collect::>(); + for &statement in &statements { + let mut statement_chars = statement.chars(); + statement_chars.next(); + if statement.ends_with(':') { + statement_chars.next_back(); + } + let args = statement_chars.as_str().split(' ').collect::>(); + if args.is_empty() { + continue; + } + let args_count = args.len(); + match args[0].to_lowercase().as_str() { + "version" => { + if args_count < 2 { + continue; + } + self.version = Some(args[1].to_string()); + } + "noconsole" | "no-console" | "no_console" => { + if args_count < 2 { + self.no_console = true; + continue; + } + self.no_console = args[1].to_lowercase() != "false"; + } + "disablewarnings" | "disable-warnings" | "disable_warnings" => { + if args_count < 2 { + continue; + } + match args[1].to_lowercase().as_str() { + "too-left-pointer" | "tooleftpointer" => { + self.disabled_warnings.too_left_pointer = true; + } + _ => {} + } + } + "sebek" => { + if args_count < 2 { + continue; + } + self.sebek = Utils::parse_sebek(args[1]); + } + _ => {} + } + } + } +} diff --git a/the-golden/src/interpreter/versions/handler.rs b/the-golden/src/interpreter/versions/handler.rs index 496926c..2ca27b4 100644 --- a/the-golden/src/interpreter/versions/handler.rs +++ b/the-golden/src/interpreter/versions/handler.rs @@ -4,6 +4,8 @@ use crate::Flags; mod v0_1_0; #[path = "./v0-2-0/main.rs"] mod v0_2_0; +#[path = "./v0-3-0/main.rs"] +mod v0_3_0; pub struct Handler { versions: Versions, @@ -16,6 +18,7 @@ impl Handler { vec![ Version::new(String::from("1"), vec![Version::new(String::from("0"), vec![])]), Version::new(String::from("2"), vec![Version::new(String::from("0"), vec![])]), + Version::new(String::from("3"), vec![Version::new(String::from("0"), vec![])]), ], ); let versions = Versions::new(vec![versions_0]); @@ -32,13 +35,13 @@ impl Handler { for _ in 0..3 - parts.len() { parts.push(String::from("x")); } - let s = parts[2].split("-").map(|x| x.to_string()).collect::>(); + let s = parts[2].split('-').map(|x| x.to_string()).collect::>(); let (parts, prerelease, _build_metadata) = if s.len() > 1 { let s_joined = s[1..].join("-"); let p = s_joined.clone(); - let s2 = p.split("+").map(|x| x.to_string()).collect::>(); + let s2 = p.split('+').map(|x| x.to_string()).collect::>(); if s2.len() > 1 { - (vec![parts[0].clone(), parts[1].clone(), s[0].clone()], Some(s2[0].clone()), Some(s2[1..].join("+").clone())) + (vec![parts[0].clone(), parts[1].clone(), s[0].clone()], Some(s2[0].clone()), Some(s2[1..].join("+"))) } else { (vec![parts[0].clone(), parts[1].clone(), s[0].clone()], Some(s_joined), None) } @@ -118,13 +121,18 @@ impl Handler { }; v0_2_0::Runner::new(code, code_path, flags, ansi_enabled).run() } + "0.3.0" => { + if flags.debug { + println!("{}Running version 0.3.0", crate::Utils::ansi_escape_text("94", "DEBUG", v0_3_0::INFO_PREFIX_LENGTH, ansi_enabled)); + }; + v0_3_0::Runner::new(code, code_path, flags, ansi_enabled).run() + } _ => { println!( "{}Couldn't run version {}", crate::Utils::ansi_escape_text("91", "ERROR", v0_1_0::INFO_PREFIX_LENGTH, ansi_enabled), version ); - return; } } } diff --git a/the-golden/src/interpreter/versions/v0-3-0/brackets_matcher.rs b/the-golden/src/interpreter/versions/v0-3-0/brackets_matcher.rs new file mode 100644 index 0000000..4519556 --- /dev/null +++ b/the-golden/src/interpreter/versions/v0-3-0/brackets_matcher.rs @@ -0,0 +1,77 @@ +use std::collections::HashMap; + +pub struct BracketsMatcher { + pub brackets: HashMap>, + brackets_mem: Vec<(String, usize, isize)>, + bracket_keys: HashMap, + ending_brackets_keys: HashMap +} + +impl BracketsMatcher { + pub fn new() -> Self { + Self { + brackets: HashMap::from([ + ("while".to_string(), HashMap::new()), + ("do_while".to_string(), HashMap::new()), + ("while_local".to_string(), HashMap::new()), + ("do_while_local".to_string(), HashMap::new()) + ]), + brackets_mem: vec![], + bracket_keys: HashMap::from([ + ("[".to_string(), "while".to_string()), + ("]".to_string(), "while".to_string()), + ("[@".to_string(), "do_while".to_string()), + ("@]".to_string(), "do_while".to_string()), + ("'[".to_string(), "while_local".to_string()), + ("']".to_string(), "while_local".to_string()), + ("'[@".to_string(), "do_while_local".to_string()), + ("'@]".to_string(), "do_while_local".to_string()), + ]), + ending_brackets_keys: HashMap::from([ + ("while".to_string(), "]".to_string()), + ("do_while".to_string(), "@]".to_string()), + ("while_local".to_string(), "']".to_string()), + ("do_while_local".to_string(), "'@]".to_string()), + ]) + } + } + + pub fn match_brackets(&mut self, code: &[String]) { + let starting_brackets = ["[", "[@", "'[", "'[@", ]; + let ending_brackets = ["]", "@]", "']", "'@]"]; + for (i, command) in code.iter().enumerate() { + let command_str = command.as_str(); + if !starting_brackets.contains(&command_str) && !ending_brackets.contains(&command_str) { + continue; + } + if starting_brackets.contains(&command_str) { + self.brackets_mem.push((command.clone(), i, 0)); + } + let mut keys_to_remove = vec![]; + for key in 0..self.brackets_mem.len() { + self.brackets_mem[key].2 += self.num_equals(&self.brackets_mem[key].0, command); + let wanted_end = self.ending_brackets_keys.get(self.bracket_keys.get(&self.brackets_mem[key].0).unwrap()).unwrap(); + if self.brackets_mem[key].2 == 0 && command == wanted_end { + let category = self.bracket_keys.get(wanted_end).unwrap(); + let sub_map = self.brackets.get_mut(category).unwrap(); + sub_map.insert(self.brackets_mem[key].1, i); + sub_map.insert(i, self.brackets_mem[key].1); + keys_to_remove.push(key); + } + } + for key in keys_to_remove { + self.brackets_mem.remove(key); + } + } + } + + fn num_equals(&self, left: &String, right: &String) -> isize { + if self.bracket_keys.get(left) != self.bracket_keys.get(right) { + return 0; + } + if left == right { + return 1; + } + -1 + } +} \ No newline at end of file diff --git a/the-golden/src/interpreter/versions/v0-3-0/lexer.rs b/the-golden/src/interpreter/versions/v0-3-0/lexer.rs new file mode 100644 index 0000000..93b6f24 --- /dev/null +++ b/the-golden/src/interpreter/versions/v0-3-0/lexer.rs @@ -0,0 +1,69 @@ +use lazy_static::lazy_static; +use regex::Regex; + +lazy_static! { + static ref COMMENT_REGEX: Regex = Regex::new("^\"").unwrap(); + static ref NEW_LINE_REGEX: Regex = Regex::new(r"^\r?\n").unwrap(); +} + +#[derive(Clone)] +pub struct Lexer { + text: String, + rules: Vec, + line: usize, + column: usize, + comment: bool, + file_path: std::path::PathBuf, + position: usize, +} + +impl Lexer { + pub fn new(text: String, rules: Vec, file_path: std::path::PathBuf) -> Self { + Self { + text, + rules, + line: 1, + column: 1, + comment: false, + file_path, + position: 0, + } + } + + pub fn next(&mut self) -> Result, String> { + let text = &self.text.as_str()[self.position..]; + if text.is_empty() { + return Ok(None); + } + if text == "\"" { + self.comment = !self.comment; + } + if self.comment { + return Ok(None); + } + for rule in &self.rules { + if let Some(captures) = rule.captures(text) { + if let Some(capture) = captures.get(0) { + let (command_line, command_column) = (self.line, self.column); + let command = capture.as_str(); + let command_length = capture.end() - capture.start(); + self.position += command_length; + if command.contains('\n') { + self.line += command.matches('\n').count(); + self.column += command.split('\n').last().unwrap().len(); + } else { + self.column += command_length; + } + return Ok(Some((command.to_string(), command_line, command_column, self.file_path.clone()))); + } + } + } + Err(format!( + "Syntax error at {}:{} in {:?} ({:?})", + self.line, + self.column, + self.file_path.file_name().unwrap(), + self.file_path.as_path() + )) + } +} diff --git a/the-golden/src/interpreter/versions/v0-3-0/main.rs b/the-golden/src/interpreter/versions/v0-3-0/main.rs new file mode 100644 index 0000000..fddf898 --- /dev/null +++ b/the-golden/src/interpreter/versions/v0-3-0/main.rs @@ -0,0 +1,421 @@ +use std::collections::HashMap; + +use crate::Flags; +use rand::Rng; +use regex::Regex; + +#[path = "./brackets_matcher.rs"] +mod brackets_matcher; +use brackets_matcher::BracketsMatcher; +#[path = "./lexer.rs"] +mod lexer; +pub use lexer::Lexer; +#[path = "./parser.rs"] +mod parser; +use crate::Utils; +pub use parser::Parser; +#[path = "./validator.rs"] +mod validator; +use validator::Validator; + +pub const INFO_PREFIX_LENGTH: usize = 12; + +pub struct Runner { + flags: Flags, + ansi_enabled: bool, + + brackets_matcher: BracketsMatcher, + brackets_categorised: HashMap>, + opposite_commands: HashMap, + + brackets: HashMap, + raw_code: String, + rules: Vec, + code_path: std::path::PathBuf, + + program_pointer: usize, + + loops: Vec, + memory: [Vec; 2], + memory_pointers: [usize; 2], + active_memory: usize, + + input_cache: Option, +} + +impl Runner { + pub fn new(raw_code: String, code_path: std::path::PathBuf, flags: Flags, ansi_enabled: bool) -> Self { + let rules = vec![ + Regex::new(r"^'?(\|-?[0-9]*\|)*!").unwrap(), // increment + Regex::new(r"^'?(\|-?[0-9]*\|)*~").unwrap(), // decrement + Regex::new(r"^'?(\|-?[0-9]*\|)*\+").unwrap(), // add + Regex::new(r"^'?(\|-?[0-9]*\|)*-").unwrap(), // subtract + Regex::new(r"^'?(\|-?[0-9]*\|)*\*").unwrap(), // multiply + Regex::new(r"^'?(\|-?[0-9]*\|)*/").unwrap(), // divide + Regex::new(r"^'?`").unwrap(), // generate a random number from 0 (inclusive) to 1 (exclusive) + Regex::new(r"^'?(\|-?[0-9]*\|)*>").unwrap(), // move right + Regex::new(r"^'?(\|-?[0-9]*\|)*<").unwrap(), // move left + Regex::new(r"^'?_").unwrap(), // floor + Regex::new(r"^'?&").unwrap(), // ceil + Regex::new(r"^'?\^").unwrap(), // switch active memory + Regex::new(r"^'?\[@?").unwrap(), // (do-)while start + Regex::new(r"^'?@?\]").unwrap(), // (do-)while end + Regex::new(r"^'?\$\.").unwrap(), // input number + Regex::new(r"^'?\$,").unwrap(), // input character + Regex::new(r"^'?\\\.").unwrap(), // output number + Regex::new(r"^'?\\,").unwrap(), // output character + Regex::new(r"^'?(\|-?[0-9]*\|)*\?=").unwrap(), // break if active memory address is equal to inactive memory address + Regex::new(r"^'?(\|-?[0-9]*\|)*\?>").unwrap(), // break if active memory address is greater than inactive memory address + Regex::new(r"^'?(\|-?[0-9]*\|)*\?<").unwrap(), // break if active memory address is less than inactive memory address + Regex::new(r"^'?\?\?").unwrap(), // set current active memory address to its index + Regex::new(r"^'?;").unwrap(), // swap main and local memory addresses + Regex::new(r"^:\r?\n?").unwrap(), // end of line + Regex::new("^\"[^\"]*\"").unwrap(), // comments + Regex::new(r"^[ \t\f\v]").unwrap(), // whitespace + Regex::new(crate::PREPROCESSOR_REGEX).unwrap(), //preprocessor regex + ]; + Self { + flags, + ansi_enabled, + + brackets_matcher: BracketsMatcher::new(), + + brackets: HashMap::new(), + brackets_categorised: HashMap::new(), + opposite_commands: HashMap::from([ + ("!".to_string(), "~".to_string()), + ("~".to_string(), "!".to_string()), + ("+".to_string(), "-".to_string()), + ("-".to_string(), "+".to_string()), + ("*".to_string(), "/".to_string()), + ("/".to_string(), "*".to_string()), + (">".to_string(), "<".to_string()), + ("<".to_string(), ">".to_string()), + ]), + + raw_code, + rules, + code_path, + + program_pointer: 0, + + loops: vec![], + memory: [vec![0.0], vec![0.0]], + memory_pointers: [0, 0], + active_memory: 0, + + input_cache: None, + } + } + + pub fn run(&mut self) { + if self.flags.debug { + println!("{}Raw code: {}", Utils::ansi_escape_text("94", "DEBUG", INFO_PREFIX_LENGTH, self.ansi_enabled), self.raw_code); + } + let lexer = Lexer::new(self.raw_code.clone(), self.rules.clone(), self.code_path.clone()); + let validator_result = Validator::run(lexer.clone(), self.flags.debug_heavy, self.ansi_enabled); + if let Err(e) = validator_result { + println!("{}{}", Utils::ansi_escape_text("91", "ERROR", INFO_PREFIX_LENGTH, self.ansi_enabled), e); + return; + } + if self.flags.debug { + println!("{}Valid code!", Utils::ansi_escape_text("94", "DEBUG", INFO_PREFIX_LENGTH, self.ansi_enabled)); + } + let mut parser = Parser::new(); + let parser_result = parser.run(lexer); + if let Err(e) = parser_result { + println!("{}{}", Utils::ansi_escape_text("91", "ERROR", INFO_PREFIX_LENGTH, self.ansi_enabled), e); + return; + } + if self.flags.debug { + println!( + "{}Parsed commands: {:?}", + Utils::ansi_escape_text("94", "DEBUG", INFO_PREFIX_LENGTH, self.ansi_enabled), + parser.commands + ); + } + self.brackets_matcher.match_brackets(&parser.commands); + self.brackets_categorised = self.brackets_matcher.brackets.clone(); + if self.flags.debug_heavy { + println!( + "{}Matched brackets: {:?}", + Utils::ansi_escape_text("34", "HEAVY DEBUG", INFO_PREFIX_LENGTH, self.ansi_enabled), + self.brackets_matcher.brackets + ); + } + for loop_type in self.brackets_categorised.keys() { + let map = self.brackets_categorised.get(loop_type).unwrap(); + for (key, value) in map.iter() { + self.brackets.insert(*key, *value); + } + } + if self.flags.debug_heavy { + println!( + "{}Matched brackets uncategorised: {:?}", + Utils::ansi_escape_text("34", "HEAVY DEBUG", INFO_PREFIX_LENGTH, self.ansi_enabled), + self.brackets + ); + } + if self.flags.debug { + println!("{}----- START OF CODE EXECUTION -----", Utils::ansi_escape_text("94", "DEBUG", INFO_PREFIX_LENGTH, self.ansi_enabled)); + } + let mut local_memory: [Vec; 2] = [vec![0.0], vec![0.0]]; + let mut local_memory_pointers: [usize; 2] = [0, 0]; + let mut active_local_memory: usize = 0; + let program_length = parser.commands.len(); + while self.program_pointer < program_length { + let command = &parser.commands[self.program_pointer]; + active_local_memory = match self.evaluate_command(command, &mut local_memory, &mut local_memory_pointers, active_local_memory) { + Ok(val) => val, + Err(e) => { + println!("{}{}", Utils::ansi_escape_text("91", "ERROR", INFO_PREFIX_LENGTH, self.ansi_enabled), e); + break; + } + }; + } + if self.flags.debug { + println!("\n{}----- END OF CODE EXECUTION -----", Utils::ansi_escape_text("94", "DEBUG", INFO_PREFIX_LENGTH, self.ansi_enabled)); + } + if self.flags.debug { + println!("{}Main memory:", Utils::ansi_escape_text("94", "DEBUG", INFO_PREFIX_LENGTH, self.ansi_enabled)); + println!("{}{:?}", Utils::ansi_escape_text("94", "DEBUG", INFO_PREFIX_LENGTH, self.ansi_enabled), self.memory); + println!("{}Local memory:", Utils::ansi_escape_text("94", "DEBUG", INFO_PREFIX_LENGTH, self.ansi_enabled)); + println!("{}{:?}", Utils::ansi_escape_text("94", "DEBUG", INFO_PREFIX_LENGTH, self.ansi_enabled), local_memory); + } + } + + pub fn evaluate_command(&mut self, command: &str, local_memory: &mut [Vec; 2], local_memory_pointers: &mut [usize; 2], active_local_memory: usize) -> Result { + let is_local = command.starts_with('\''); + let raw_command = command; + let command = if is_local { &command[1..] } else { command }; + let [(main_memory, main_memory_pointers, mut main_active_memory), (local_memory, local_memory_pointers, local_active_memory)] = if is_local { + [ + (local_memory, local_memory_pointers, active_local_memory), + (&mut self.memory, &mut self.memory_pointers, self.active_memory), + ] + } else { + [ + (&mut self.memory, &mut self.memory_pointers, self.active_memory), + (local_memory, local_memory_pointers, active_local_memory), + ] + }; + let split_command = command.split('|').collect::>(); + let (command, repeat) = if split_command.len() == 3 { + let count_str = split_command[1]; + let num = if count_str.is_empty() { + main_memory[main_active_memory][main_memory_pointers[main_active_memory]].floor() as i128 + } else if let Ok(val) = count_str.parse::() { + val + } else { + 1 + }; + let new_command = split_command[2]; + if num < 0 { + if let Some(opposite_command) = self.opposite_commands.get(new_command) { + (opposite_command.as_str(), -num) + } else { + (new_command, 0) + } + } else { + (new_command, num) + } + } else { + (command, 1) + }; + for _ in 0..repeat { + match command { + "!" => main_memory[main_active_memory][main_memory_pointers[main_active_memory]] += 1.0, + "~" => main_memory[main_active_memory][main_memory_pointers[main_active_memory]] -= 1.0, + "+" => main_memory[main_active_memory][main_memory_pointers[main_active_memory]] += main_memory[main_active_memory ^ 1][main_memory_pointers[main_active_memory ^ 1]], + "-" => main_memory[main_active_memory][main_memory_pointers[main_active_memory]] -= main_memory[main_active_memory ^ 1][main_memory_pointers[main_active_memory ^ 1]], + "*" => main_memory[main_active_memory][main_memory_pointers[main_active_memory]] *= main_memory[main_active_memory ^ 1][main_memory_pointers[main_active_memory ^ 1]], + "/" => { + let divisor = main_memory[main_active_memory ^ 1][main_memory_pointers[main_active_memory ^ 1]]; + let divident = &mut main_memory[main_active_memory][main_memory_pointers[main_active_memory]]; + if divisor == 0.0 { + let mut i = 0; + if *divident >= 0.0 { + i += 1; + } + if *divident > 0.0 { + i += 1; + } + let val = self.flags.sebek[i]; + if let Some(res) = val { + *divident = res; + } else { + return Err(format!( + "Mr. Sebek would support you. Attempted division by 0 for {}. You can set up custom values for division by 0 with the --sebek flag.", + divident + )); + } + } else { + *divident /= divisor + } + } + "`" => main_memory[main_active_memory][main_memory_pointers[main_active_memory]] = rand::thread_rng().gen(), + ">" => { + main_memory_pointers[main_active_memory] += 1; + if main_memory_pointers[main_active_memory] >= main_memory[main_active_memory].len() { + main_memory[main_active_memory].push(0.0); + } + } + "<" => { + if main_memory_pointers[main_active_memory] == 0 { + main_memory[main_active_memory].insert(0, 0.0); + if !self.flags.disabled_warnings.too_left_pointer { + println!("{}You moved to the -1 index in memory. This will not crash the program, but should generally be avoided (you can use the --disable-warnings flag to disable all warnings or --disable-too-left-pointer-warning to disable this particular warning)", Utils::ansi_escape_text("93", "WARNING", INFO_PREFIX_LENGTH, self.ansi_enabled)); + } + } else { + main_memory_pointers[main_active_memory] -= 1; + } + } + "_" => main_memory[main_active_memory][main_memory_pointers[main_active_memory]] = main_memory[main_active_memory][main_memory_pointers[main_active_memory]].floor(), + "&" => main_memory[main_active_memory][main_memory_pointers[main_active_memory]] = main_memory[main_active_memory][main_memory_pointers[main_active_memory]].ceil(), + "^" => main_active_memory ^= 1, + "$." => { + if self.input_cache.is_none() { + self.input_cache = Some(Utils::get_input_line()); + } + let input = &self.input_cache.clone().unwrap(); + self.input_cache = None; + main_memory[main_active_memory][main_memory_pointers[main_active_memory]] = match input.parse::() { + Ok(val) => val, + Err(e) => { + return Err(format!("Failed to convert {} from input to a number: {}", input, e)); + } + } + } + "$," => { + if self.input_cache.is_none() { + self.input_cache = Some(Utils::get_input_line()); + } + let input = &self.input_cache.clone().unwrap(); + let (char, remainder) = Utils::next_char(input); + self.input_cache = if !remainder.is_empty() { Some(remainder.to_string()) } else { None }; + main_memory[main_active_memory][main_memory_pointers[main_active_memory]] = (char as u32) as f64; + } + "\\." => { + print!("{}", main_memory[main_active_memory][main_memory_pointers[main_active_memory]]); + if let Err(e) = Utils::flush_console() { + println!("{}{}", Utils::ansi_escape_text("91", "ERROR", INFO_PREFIX_LENGTH, self.ansi_enabled), e); + } + } + "\\," => match char::from_u32(main_memory[main_active_memory][main_memory_pointers[main_active_memory]].floor() as u32) { + Some(c) => { + print!("{}", c); + if let Err(e) = Utils::flush_console() { + println!("{}{}", Utils::ansi_escape_text("91", "ERROR", INFO_PREFIX_LENGTH, self.ansi_enabled), e); + } + } + None => { + return Err(format!( + "Failed to convert {} from memory to a character", + main_memory[main_active_memory][main_memory_pointers[main_active_memory]].floor() + )); + } + }, + "[" => { + if main_memory[main_active_memory][main_memory_pointers[main_active_memory]] == 0.0 { + if let Some(index) = self.loops.iter().position(|value| *value == self.program_pointer) { + self.loops.remove(index); + } + self.program_pointer = *self.brackets.get(&self.program_pointer).unwrap(); + } else if !self.loops.contains(&self.program_pointer) { + self.loops.push(self.program_pointer); + } + } + "]" | "@]" => { + if main_memory[main_active_memory][main_memory_pointers[main_active_memory]] == 0.0 { + if let Some(index) = self.loops.iter().position(|value| *value == self.program_pointer) { + self.loops.remove(index); + } + } else { + self.program_pointer = *self.brackets.get(&self.program_pointer).unwrap(); + } + } + "[@" => { + if main_memory[main_active_memory][main_memory_pointers[main_active_memory]] == 0.0 && self.loops.contains(&self.program_pointer) { + if let Some(index) = self.loops.iter().position(|value| *value == self.program_pointer) { + self.loops.remove(index); + } + self.program_pointer = *self.brackets.get(&self.program_pointer).unwrap(); + } else if !self.loops.contains(&self.program_pointer) { + self.loops.push(self.program_pointer); + } + } + "?=" => { + let inactive_memory = main_active_memory ^ 1; + if main_memory[main_active_memory][main_memory_pointers[main_active_memory]] == main_memory[inactive_memory][main_memory_pointers[inactive_memory]] { + if let Some(current_loop) = self.loops.pop() { + self.program_pointer = *self.brackets.get(¤t_loop).unwrap(); + } + } + } + "?>" => { + let inactive_memory = main_active_memory ^ 1; + if main_memory[main_active_memory][main_memory_pointers[main_active_memory]] > main_memory[inactive_memory][main_memory_pointers[inactive_memory]] { + if let Some(current_loop) = self.loops.pop() { + self.program_pointer = *self.brackets.get(¤t_loop).unwrap(); + } + } + } + "?<" => { + let inactive_memory = main_active_memory ^ 1; + if main_memory[main_active_memory][main_memory_pointers[main_active_memory]] < main_memory[inactive_memory][main_memory_pointers[inactive_memory]] { + if let Some(current_loop) = self.loops.pop() { + self.program_pointer = *self.brackets.get(¤t_loop).unwrap(); + } + } + } + "??" => { + main_memory[main_active_memory][main_memory_pointers[main_active_memory]] = main_memory_pointers[main_active_memory] as f64; + } + ";" => { + std::mem::swap( + &mut local_memory[local_active_memory][local_memory_pointers[local_active_memory]], + &mut main_memory[main_active_memory][main_memory_pointers[main_active_memory]], + ); + } + _ => {} + } + } + self.program_pointer += 1; + self.active_memory = if is_local { local_active_memory } else { main_active_memory }; + if self.flags.debug_heavy { + println!( + "\n{}Raw command: {:?}", + Utils::ansi_escape_text("34", "HEAVY DEBUG", INFO_PREFIX_LENGTH, self.ansi_enabled), + raw_command + ); + println!("{}Command executed: {:?}", Utils::ansi_escape_text("34", "HEAVY DEBUG", INFO_PREFIX_LENGTH, self.ansi_enabled), command); + println!( + "{}Command was executed on local memory: {:?}", + Utils::ansi_escape_text("34", "HEAVY DEBUG", INFO_PREFIX_LENGTH, self.ansi_enabled), + is_local + ); + println!( + "{}Command repetitions: {:?}", + Utils::ansi_escape_text("34", "HEAVY DEBUG", INFO_PREFIX_LENGTH, self.ansi_enabled), + repeat + ); + println!( + "{}Global memory: {:?}", + Utils::ansi_escape_text("34", "HEAVY DEBUG", INFO_PREFIX_LENGTH, self.ansi_enabled), + self.memory + ); + println!( + "{}Global memory pointers: {:?}", + Utils::ansi_escape_text("34", "HEAVY DEBUG", INFO_PREFIX_LENGTH, self.ansi_enabled), + self.memory_pointers + ); + println!( + "{}Active global memory: {:?}", + Utils::ansi_escape_text("34", "HEAVY DEBUG", INFO_PREFIX_LENGTH, self.ansi_enabled), + self.active_memory + ); + std::thread::sleep(std::time::Duration::from_millis(500)); + } + Ok(if is_local { main_active_memory } else { local_active_memory }) + } +} diff --git a/the-golden/src/interpreter/versions/v0-3-0/parser.rs b/the-golden/src/interpreter/versions/v0-3-0/parser.rs new file mode 100644 index 0000000..7724194 --- /dev/null +++ b/the-golden/src/interpreter/versions/v0-3-0/parser.rs @@ -0,0 +1,35 @@ +use regex::Regex; + +pub struct Parser { + pub commands: Vec, + pub commands_info: Vec<(String, usize, usize, std::path::PathBuf)> +} + +impl Parser { + pub fn new() -> Self { + Self { commands: vec![], commands_info: vec![] } + } + + pub fn run(&mut self, mut lexer: super::Lexer) -> Result { + let mut t = lexer.next(); + let mut p = t.clone(); + while t.is_ok() && t.clone().unwrap().is_some() { + let val = t.clone().unwrap().unwrap(); + let (command, ..) = val.clone(); + if !((command.starts_with('"') && command.ends_with('"')) || command.contains(':')) { + self.commands.push(command); + self.commands_info.push(val); + } + p = t; + t = lexer.next(); + } + if let Err(e) = t { + return Err(e); + } + let (command, line, column, file_path) = p.unwrap().unwrap(); + if !Regex::new(r":\n?\r?").unwrap().is_match(&command) { + return Err(format!("Syntax error at {}:{} in {:?} ({:?}) - ':' expected", line, column, file_path.file_name().unwrap(), file_path.as_path())); + } + Ok(0) + } +} \ No newline at end of file diff --git a/the-golden/src/interpreter/versions/v0-3-0/validator.rs b/the-golden/src/interpreter/versions/v0-3-0/validator.rs new file mode 100644 index 0000000..2cbb93e --- /dev/null +++ b/the-golden/src/interpreter/versions/v0-3-0/validator.rs @@ -0,0 +1,37 @@ +use regex::Regex; + +use super::INFO_PREFIX_LENGTH; +use crate::Utils; + +pub struct Validator {} + +impl Validator { + pub fn run(mut lexer: super::Lexer, heavy_debug: bool, ansi_enabled: bool) -> Result { + let mut t = lexer.next(); + if heavy_debug { + println!("{}Matched command: {:?}", Utils::ansi_escape_text("34", "HEAVY DEBUG", INFO_PREFIX_LENGTH, ansi_enabled), t); + } + let mut p = t.clone(); + while t.is_ok() && t.clone().unwrap().is_some() { + p = t; + t = lexer.next(); + if heavy_debug { + println!("{}Matched command: {:?}", Utils::ansi_escape_text("34", "HEAVY DEBUG", INFO_PREFIX_LENGTH, ansi_enabled), t); + } + } + if let Err(e) = t { + return Err(e); + } + let (command, line, column, file_path) = p.unwrap().unwrap(); + if !Regex::new(r":\n?\r?").unwrap().is_match(&command) { + return Err(format!( + "Syntax error at {}:{} in {:?} ({:?}) - ':' expected", + line, + column, + file_path.file_name().unwrap(), + file_path.as_path() + )); + } + Ok(0) + } +} diff --git a/the-golden/src/main.rs b/the-golden/src/main.rs index 46540a9..0e4e9cc 100644 --- a/the-golden/src/main.rs +++ b/the-golden/src/main.rs @@ -12,6 +12,7 @@ mod utils; pub use utils::Utils; pub const INFO_PREFIX_LENGTH: usize = 12; +pub const PREPROCESSOR_REGEX: &str = "#[^:]*:\r?\n?"; fn main() { dotenv().ok(); @@ -26,7 +27,7 @@ fn main() { flags_handler.parse(&args); let mut _action = String::new(); - let mut version = String::from("latest"); + let mut version = None; let mut code = String::new(); let mut code_path = std::path::PathBuf::new(); @@ -59,7 +60,7 @@ fn main() { return; } if let Some(v) = cloned_flags.version { - version = v; + version = Some(v); } if let Ok(val) = env::var("LOGS") { if val.to_lowercase() == "off" && cfg!(target_os = "windows") { diff --git a/the-golden/src/utils.rs b/the-golden/src/utils.rs index d7772e0..1bc431a 100644 --- a/the-golden/src/utils.rs +++ b/the-golden/src/utils.rs @@ -30,4 +30,16 @@ impl Utils { pub fn flush_console() -> std::io::Result<()> { std::io::stdout().flush() } + + pub fn parse_sebek(input: &str) -> [Option; 3] { + let mut sebek = [None, None, None]; + let args_vec = input.split('|').filter_map(|val| val.parse::().ok()).collect::>(); + for (i, &val) in args_vec.iter().enumerate() { + if i >= sebek.len() { + break; + } + sebek[i] = Some(val); + } + sebek + } }