├── .gitignore ├── game.rpg ├── Cargo.toml ├── src ├── lib.rs ├── main.rs ├── models.rs ├── runtime.rs └── lang.rs ├── test.rpg ├── grammar.txt ├── LICENCE ├── README.md └── Cargo.lock /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | -------------------------------------------------------------------------------- /game.rpg: -------------------------------------------------------------------------------- 1 | "Hello World" 2 | 3 | "Do you wanna play?" { 4 | "yes" => { 5 | "Yoo! We can now start" 6 | } 7 | "no" => { 8 | "Bruh!" 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "rpg-rs" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 7 | 8 | [dependencies] 9 | pancurses = "0.17.0" 10 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | use pancurses::*; 2 | use std::{thread, time, fmt::Write}; 3 | 4 | mod models; // Constants and data structures 5 | mod runtime; // The ast interpreter 6 | mod lang; // The rpg compiler 7 | pub use lang::compile; 8 | pub use models::*; 9 | pub use runtime::start_game; 10 | 11 | -------------------------------------------------------------------------------- /test.rpg: -------------------------------------------------------------------------------- 1 | first := (name) { 2 | age := ? "What is your age?" 3 | - "Hi $name$, you are $age$ years old!" 4 | } 5 | 6 | second := (name) { 7 | # "Are you 18+ $name$?" { 8 | "yes" => - "Whooo, you can vote" 9 | "no" => - "Sorry, you can't vote" 10 | } 11 | } 12 | 13 | main := { 14 | - "Hello, World! This is just so awesome!!!" 15 | name := ? "What is your name?" 16 | 17 | # "Which Branch?" { 18 | "first" => first(name) 19 | "second" => second(name) 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /grammar.txt: -------------------------------------------------------------------------------- 1 | source: 2 | *statement 3 | 4 | statement: 5 | expr 6 | tell 7 | branch 8 | expr 9 | 10 | expr: 11 | ask 12 | block 13 | 14 | 15 | ask: 16 | 17 | tell: 18 | 19 | branch: branches 20 | 21 | branches: 22 | 23 | +( statement) 24 | 25 | 26 | block: ?arguments *statement 27 | 28 | arguments: + 29 | -------------------------------------------------------------------------------- /src/main.rs: -------------------------------------------------------------------------------- 1 | use pancurses::*; 2 | use rpg_rs::*; 3 | use std::{env, fs}; 4 | 5 | fn main() -> Result<(), i32> { 6 | // Command line args 7 | let args: Vec = env::args().collect(); 8 | if args.len() <= 1 { 9 | println!("Usage: "); 10 | return Err(0); 11 | } 12 | 13 | // First command line argument be rpg source file name 14 | let source = if let Ok(m) = fs::read_to_string(args[1].clone()) { 15 | m 16 | } else { 17 | eprintln!("Error: Error while reading the file"); 18 | return Err(-1); 19 | }; 20 | 21 | // rpg compiler returns either Result, Error> 22 | // Vec be the ast 23 | match rpg_rs::compile(source) { 24 | Ok(messages) => { 25 | raw(); 26 | 27 | let mut game = Game::new(initscr(), messages); 28 | game.window.keypad(true); 29 | start_game(&mut game); 30 | 31 | endwin(); 32 | Ok(()) 33 | } 34 | 35 | Err(error) => error.complain() 36 | } 37 | 38 | } 39 | 40 | -------------------------------------------------------------------------------- /LICENCE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2022 DuskyElf 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Terminal Based Rpg Game (Engine) 2 | ### In it's early stages of development 3 | 4 | ### Quick Start 5 | ```shell 6 | $ git clone git@github.com:DuskyElf/rpg-rs.git 7 | $ cd rpg-rs 8 | $ git checkout tags/v0.0.1-alpha 9 | ``` 10 | 11 | ### Sample Program 12 | ``` 13 | "Hello, World! This is just so awesome!!!" 14 | ?0 "What is your name?" 15 | "Which Branch?" { 16 | "first" => { 17 | ?1 "What is your age?" 18 | "Hi $0, you are $1 years old!" 19 | } 20 | 21 | "second" => { 22 | "Are you 18+ $0?" { 23 | "yes" => { "Whooo, you can vote" } 24 | "no" => { "Sorry, you can't vote" } 25 | } 26 | } 27 | } 28 | ``` 29 | 30 | ### Quick Run 31 | ```shell 32 | $ cargo r --release test.rpg 33 | ``` 34 | 35 | ### Features that are currently implemented 36 | - Variables 37 | - Asking question into a variable (identifier) 38 | - Branching System 39 | 40 | ## Rpg lang Reference 41 | 42 | ### Messages 43 | Currently, it clears the terminal and animates the message as it being typed, then stops for the user to proceed. 44 | ``` 45 | "Example message" 46 | ``` 47 | ![image](https://user-images.githubusercontent.com/91879372/235681566-37732814-5ccd-48c9-941b-7da36991492b.png) 48 | 49 | ### Questions 50 | Currently, it clears the terminal and animtes the question as it being typed, then stops for the user to type in the answer till a newline. Then saves the answer in the variable index provided (`0` in the following example). 51 | ``` 52 | ?0 "What's your name?" 53 | ``` 54 | ![image](https://user-images.githubusercontent.com/91879372/235684202-d37e6c12-1f52-4921-b28a-4d1f2585ee80.png) 55 | 56 | ### Variables 57 | As stated above, [questions](#questions) save the answer in the variable index provided. Those values could be accessed via the index as `$` inside a quotes. 58 | ``` 59 | ?0 "What's your name?" 60 | "Hi $0!" 61 | ``` 62 | ![image](https://user-images.githubusercontent.com/91879372/235685837-661fe884-c7a5-4dea-91cf-41f4d0aa942c.png) 63 | | 64 | ![image](https://user-images.githubusercontent.com/91879372/235686117-244a41f1-2710-42b0-b241-77cfd76bfd3b.png) 65 | 66 | ### Branches 67 | Currently, it's able to ask a question then show the possible options to select, on the basis of which it branches the code flow. 68 | ``` 69 | "Select an option -" { 70 | "First" => { 71 | "You selected the first branch!" 72 | } 73 | "Second" => { 74 | "You selected the second branch!" 75 | } 76 | } 77 | ``` 78 | ![image](https://user-images.githubusercontent.com/91879372/235689591-1f79e7f5-7e13-41cc-8200-970bbd06be32.png) 79 | 80 | -------------------------------------------------------------------------------- /Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 3 4 | 5 | [[package]] 6 | name = "cc" 7 | version = "1.0.73" 8 | source = "registry+https://github.com/rust-lang/crates.io-index" 9 | checksum = "2fff2a6927b3bb87f9595d67196a70493f627687a71d87a0d692242c33f58c11" 10 | 11 | [[package]] 12 | name = "cfg-if" 13 | version = "1.0.0" 14 | source = "registry+https://github.com/rust-lang/crates.io-index" 15 | checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" 16 | 17 | [[package]] 18 | name = "libc" 19 | version = "0.2.133" 20 | source = "registry+https://github.com/rust-lang/crates.io-index" 21 | checksum = "c0f80d65747a3e43d1596c7c5492d95d5edddaabd45a7fcdb02b95f644164966" 22 | 23 | [[package]] 24 | name = "log" 25 | version = "0.4.17" 26 | source = "registry+https://github.com/rust-lang/crates.io-index" 27 | checksum = "abb12e687cfb44aa40f41fc3978ef76448f9b6038cad6aef4259d3c095a2382e" 28 | dependencies = [ 29 | "cfg-if", 30 | ] 31 | 32 | [[package]] 33 | name = "ncurses" 34 | version = "5.101.0" 35 | source = "registry+https://github.com/rust-lang/crates.io-index" 36 | checksum = "5e2c5d34d72657dc4b638a1c25d40aae81e4f1c699062f72f467237920752032" 37 | dependencies = [ 38 | "cc", 39 | "libc", 40 | "pkg-config", 41 | ] 42 | 43 | [[package]] 44 | name = "pancurses" 45 | version = "0.17.0" 46 | source = "registry+https://github.com/rust-lang/crates.io-index" 47 | checksum = "0352975c36cbacb9ee99bfb709b9db818bed43af57751797f8633649759d13db" 48 | dependencies = [ 49 | "libc", 50 | "log", 51 | "ncurses", 52 | "pdcurses-sys", 53 | "winreg", 54 | ] 55 | 56 | [[package]] 57 | name = "pdcurses-sys" 58 | version = "0.7.1" 59 | source = "registry+https://github.com/rust-lang/crates.io-index" 60 | checksum = "084dd22796ff60f1225d4eb6329f33afaf4c85419d51d440ab6b8c6f4529166b" 61 | dependencies = [ 62 | "cc", 63 | "libc", 64 | ] 65 | 66 | [[package]] 67 | name = "pkg-config" 68 | version = "0.3.25" 69 | source = "registry+https://github.com/rust-lang/crates.io-index" 70 | checksum = "1df8c4ec4b0627e53bdf214615ad287367e482558cf84b109250b37464dc03ae" 71 | 72 | [[package]] 73 | name = "rpg-rs" 74 | version = "0.1.0" 75 | dependencies = [ 76 | "pancurses", 77 | ] 78 | 79 | [[package]] 80 | name = "winapi" 81 | version = "0.3.9" 82 | source = "registry+https://github.com/rust-lang/crates.io-index" 83 | checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" 84 | dependencies = [ 85 | "winapi-i686-pc-windows-gnu", 86 | "winapi-x86_64-pc-windows-gnu", 87 | ] 88 | 89 | [[package]] 90 | name = "winapi-i686-pc-windows-gnu" 91 | version = "0.4.0" 92 | source = "registry+https://github.com/rust-lang/crates.io-index" 93 | checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" 94 | 95 | [[package]] 96 | name = "winapi-x86_64-pc-windows-gnu" 97 | version = "0.4.0" 98 | source = "registry+https://github.com/rust-lang/crates.io-index" 99 | checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" 100 | 101 | [[package]] 102 | name = "winreg" 103 | version = "0.5.1" 104 | source = "registry+https://github.com/rust-lang/crates.io-index" 105 | checksum = "a27a759395c1195c4cc5cda607ef6f8f6498f64e78f7900f5de0a127a424704a" 106 | dependencies = [ 107 | "winapi", 108 | ] 109 | -------------------------------------------------------------------------------- /src/models.rs: -------------------------------------------------------------------------------- 1 | use std::collections::HashMap; 2 | use std::iter::Peekable; 3 | use std::vec::IntoIter; 4 | 5 | use pancurses::Window; 6 | 7 | pub static DIGITS: [char; 10] = ['1', '2', '3', '4', '5', '6', '7', '8', '9', '0']; 8 | 9 | pub struct Game { 10 | pub window: Window, 11 | pub byte_code: Vec, 12 | pub states: HashMap, 13 | } 14 | 15 | impl Game { 16 | pub fn new(window: Window, byte_code: Vec) -> Self { 17 | Self { 18 | window, 19 | byte_code, 20 | states: HashMap::new(), 21 | } 22 | } 23 | } 24 | 25 | #[derive(Clone, Debug)] 26 | pub enum OpCode { 27 | NOP, 28 | END, 29 | JMP(usize), 30 | TELL(String), 31 | ASK(String, Option), 32 | BRANCH(String, Vec), 33 | } 34 | 35 | #[derive(Clone, Debug)] 36 | pub struct Branch { 37 | pub option: String, 38 | pub handler: OpCode, 39 | } 40 | 41 | impl Branch { 42 | pub fn new(option: String, handler: OpCode) -> Self { 43 | Self { 44 | option, 45 | handler, 46 | } 47 | } 48 | } 49 | 50 | #[derive(Clone, Debug)] 51 | pub struct Position { 52 | pub line: usize, 53 | pub column: usize, 54 | } 55 | 56 | #[derive(PartialEq, Clone, Debug)] 57 | pub enum TokenType { 58 | AskOp, // ? 59 | TellOp, // - 60 | ParOpen, // ( 61 | ParClose, // ) 62 | BranchOp, // # 63 | LambdaOp, // => 64 | BrackOpen, // { 65 | BrackClose, // } 66 | AssignmentOp, // := 67 | Identifier(String), // 68 | StringLiteral(String), // "" 69 | } 70 | 71 | pub struct Lexer { 72 | pub source: String, 73 | pub index: usize, 74 | pub line: usize, 75 | pub column: usize, 76 | } 77 | 78 | #[derive(Clone, Debug)] 79 | pub struct Token { 80 | pub position: Position, 81 | pub token_type: TokenType, 82 | } 83 | 84 | pub type ParseableTokens = Peekable>; 85 | pub struct Parser { 86 | pub tokens: ParseableTokens, 87 | pub byte_code: Vec, 88 | pub curr_token: Token, 89 | pub value_identifiers: HashMap, 90 | pub block_identifiers: HashMap, 91 | } 92 | 93 | pub enum ErrorType { 94 | InvalidSyntax, 95 | Missing(String), 96 | Expected(String), 97 | InvalidIdentifier(usize), 98 | } 99 | 100 | pub struct Error { 101 | error_type: ErrorType, 102 | line: usize, 103 | column: usize, 104 | } 105 | 106 | impl Error { 107 | pub fn lex_error(error_type: ErrorType, lexer: &Lexer) -> Self { 108 | Self { 109 | error_type, 110 | line: lexer.line, 111 | column: lexer.column, 112 | } 113 | } 114 | 115 | pub fn parse_error(error_type: ErrorType, parser: &Parser) -> Self { 116 | Self { 117 | error_type, 118 | line: parser.curr_token.position.line, 119 | column: parser.curr_token.position.column, 120 | } 121 | } 122 | 123 | pub fn complain(self) -> Result<(), i32> { 124 | match self.error_type { 125 | ErrorType::InvalidSyntax =>{ 126 | eprintln!( 127 | "Error: Invalid Syntax\nAt line: {}, column: {}", 128 | self.line, self.column 129 | ); 130 | Err(-1) 131 | }, 132 | 133 | ErrorType::Missing(error) => { 134 | eprintln!( 135 | "Error: Missing {}\nAt line: {}, column: {}", 136 | error, self.line, self.column 137 | ); 138 | Err(40) 139 | }, 140 | 141 | ErrorType::Expected(error) => { 142 | eprintln!( 143 | "Error: Expected {}\nAt line: {}, column: {}", 144 | error, self.line, self.column 145 | ); 146 | Err(41) 147 | }, 148 | 149 | ErrorType::InvalidIdentifier(identifier) => { 150 | eprintln!( 151 | "Error: Identifer '{}' used in StringLiteral without delaration 152 | At line: {}, column: {}", 153 | identifier, self.line, self.column 154 | ); 155 | Err(42) 156 | }, 157 | } 158 | } 159 | } 160 | -------------------------------------------------------------------------------- /src/runtime.rs: -------------------------------------------------------------------------------- 1 | pub use crate::*; 2 | use models::DIGITS; 3 | 4 | pub fn start_game(game: &mut Game) { 5 | let byte_code = game.byte_code.clone(); 6 | let mut iptr = 0; 7 | loop { 8 | let is_end = run(byte_code[iptr], &mut iptr, game); 9 | if is_end { 10 | break; 11 | } 12 | } 13 | } 14 | 15 | fn run(op_code: OpCode, iptr: &mut usize, game: &mut Game) -> bool { 16 | // Decoding instructions to different functions 17 | match op_code { 18 | OpCode::NOP => (), 19 | OpCode::END => return true, 20 | OpCode::JMP(ptr) => { 21 | *iptr = ptr; 22 | return false; 23 | }, 24 | OpCode::TELL(info) => msg_tell(info, game), 25 | OpCode::ASK(question, id) => msg_question(question, id, game), 26 | OpCode::BRANCH(question, branches) => 27 | return run(msg_branch(question, branches, game), iptr, game), 28 | } 29 | 30 | *iptr += 1; 31 | false 32 | } 33 | 34 | fn msg_tell(info: String, game: &Game) { 35 | tell_info(info, game); 36 | game.window.addstr("\n\nPress any key to continue"); 37 | game.window.refresh(); 38 | 39 | curs_set(0); 40 | noecho(); 41 | game.window.getch(); 42 | echo(); 43 | curs_set(1); 44 | } 45 | 46 | fn msg_question(question: String, id: Option, game: &mut Game) { 47 | tell_info(question, game); 48 | 49 | game.window.addstr("\n\n>"); 50 | let mut responce = String::new(); 51 | scan(&game.window, &mut responce); 52 | 53 | if let Some(id) = id { 54 | game.states.insert(id, responce); 55 | } 56 | } 57 | 58 | fn msg_branch(question: String, branches: Vec, game: &mut Game) -> OpCode { 59 | tell_info(question, game); 60 | game.window.addstr("\n\n"); 61 | 62 | let branch = branch_selection(branches, game); 63 | 64 | branch.handler 65 | } 66 | 67 | fn tell_info(info: String, game: &Game) { 68 | game.window.clear(); 69 | game.window.mv(0, 0); 70 | 71 | let viewable = parse(info, game); 72 | 73 | for letter in viewable.chars() { 74 | game.window.addch(letter as u32); 75 | thread::sleep(time::Duration::from_millis(15)); 76 | game.window.refresh(); 77 | } 78 | } 79 | 80 | // Parsing StringLiteral to find an identifer reference 81 | fn parse(info: String, game: &Game) -> String { 82 | let mut i = 0; 83 | let mut result = String::new(); 84 | while i < info.len() { 85 | let letter = info.chars().nth(i).unwrap(); 86 | if letter == '$' { 87 | result += &handle_states(&mut i, info, game); 88 | } 89 | else { 90 | result.write_char(letter).unwrap(); 91 | i += 1; 92 | } 93 | } 94 | result 95 | } 96 | 97 | // Parsing identifer reference inside a StringLiteral 98 | // And replacing it with it's value from game.states (runtime identifer pool) 99 | fn handle_states(i: &mut usize, info: String, game: &Game) -> String { 100 | *i += 1; 101 | let mut result = String::new(); 102 | if *i < info.len() { 103 | let mut letter = info.chars().nth(*i).unwrap(); 104 | let mut number = String::new(); 105 | while DIGITS.contains(&letter) { 106 | number.write_char(letter).unwrap(); 107 | *i += 1; 108 | if *i >= info.len() { break; } 109 | letter = info.chars().nth(*i).unwrap(); 110 | } 111 | 112 | if !number.is_empty() { 113 | let number: usize = number.parse().unwrap(); 114 | result += &game.states[&number]; 115 | } 116 | 117 | else { 118 | result.write_char(info.chars().nth(*i-1).unwrap()).unwrap(); 119 | } 120 | } 121 | 122 | else { 123 | result.write_char(info.chars().nth(*i-1).unwrap()).unwrap(); 124 | } 125 | 126 | result 127 | } 128 | 129 | fn branch_selection(branches: Vec, game: &Game) -> &Branch { 130 | curs_set(0); 131 | noecho(); 132 | let mut selection = 0; 133 | let y = game.window.get_cur_y(); 134 | let x = game.window.get_cur_x(); 135 | 136 | // Looping through all choices in the branch 137 | // And rendering them in responce to user 138 | loop { 139 | game.window.mv(y, x); 140 | for (i, branch) in branches.iter().enumerate() { 141 | if i == selection { 142 | game.window.addstr(format!(">[ {} ]\n", branch.option)); 143 | } else { 144 | game.window.addstr(format!(" {} \n", branch.option)); 145 | } 146 | } 147 | 148 | match game.window.getch().unwrap() { 149 | Input::Character('\n') => break, // Enter / Return 150 | Input::KeyDown => selection += 1, 151 | Input::KeyUp => if selection != 0 {selection -= 1}, 152 | _ => (), 153 | } 154 | 155 | if selection >= branches.len() { 156 | selection = branches.len() - 1; 157 | } 158 | 159 | } 160 | echo(); 161 | curs_set(1); 162 | branches.get(selection).unwrap() 163 | } 164 | 165 | // Simulating stdin 166 | fn scan(window: &Window, buffer: &mut String) { 167 | noecho(); 168 | loop { 169 | match window.getch().unwrap() { 170 | // Enter / Return 171 | Input::Character('\n') => { 172 | echo(); 173 | break; 174 | }, 175 | 176 | Input::KeyBackspace => { 177 | if buffer.len() != 0 { 178 | buffer.pop(); 179 | window.mv(window.get_cur_y(), window.get_cur_x() - 1); 180 | window.delch(); 181 | } 182 | continue; 183 | }, 184 | 185 | Input::Character(read) => { 186 | window.addch(read); 187 | buffer.push(read); 188 | }, 189 | 190 | _ => (), 191 | } 192 | } 193 | } 194 | 195 | -------------------------------------------------------------------------------- /src/lang.rs: -------------------------------------------------------------------------------- 1 | use std::{fmt::Write, vec, collections::HashMap}; 2 | 3 | use crate::models::*; 4 | use TokenType::*; 5 | use ErrorType::*; 6 | 7 | // The tokenizer 8 | impl Lexer { 9 | fn lex(source: String) -> Result, Error> { 10 | let mut tokens: Vec = Vec::new(); 11 | 12 | // Global variables for different parts of the lexer 13 | let mut lexer = Lexer { 14 | source, 15 | index: 0, 16 | line: 1, 17 | column: 1, 18 | }; 19 | 20 | // Iterates through each char in the rpg_source 21 | loop { 22 | if lexer.index >= lexer.source.len() { 23 | break; 24 | } 25 | 26 | // AskOp, // ? 27 | // TellOp, // - 28 | // ParOpen, // ( 29 | // ParClose, // ) 30 | // BranchOp, // # 31 | // LambdaOp, // => 32 | // BrackOpen, // { 33 | // BrackClose, // } 34 | // AssignmentOp, // := 35 | // Identifier(String), // 36 | // StringLiteral(String), // "" 37 | 38 | let current_char = lexer.source.chars().nth(lexer.index).unwrap(); 39 | let position = Position { line: lexer.line, column: lexer.column }; 40 | match current_char { 41 | '?' => tokens.push(Token{ 42 | position, token_type: AskOp, 43 | }), 44 | 45 | '-' => tokens.push(Token{ 46 | position, token_type: TellOp, 47 | }), 48 | 49 | '(' => tokens.push(Token{ 50 | position, token_type: ParOpen, 51 | }), 52 | 53 | ')' => tokens.push(Token{ 54 | position, token_type: ParClose, 55 | }), 56 | 57 | '#' => tokens.push(Token{ 58 | position, token_type: BranchOp, 59 | }), 60 | 61 | '=' => tokens.push(lexer.lex_lambda_op()?), 62 | 63 | '{' => tokens.push(Token{ 64 | position, token_type: BrackOpen, 65 | }), 66 | 67 | '}' => tokens.push(Token{ 68 | position, token_type: BrackClose, 69 | }), 70 | 71 | ':' => tokens.push(lexer.lex_assignment_op()?), 72 | 73 | 'a'..='z' | 'A'..='Z' | '0'..='9' => tokens.push(lexer.lex_identifier()?), 74 | 75 | '"' => tokens.push(lexer.lex_string_literal()?), 76 | 77 | '\n' => { 78 | lexer.line += 1; 79 | lexer.column = 0; 80 | }, 81 | 82 | ' ' | '\t' => (), // Ignoring white spaces 83 | 84 | _ => return Err(Error::lex_error( 85 | // TODO: InvalidChar Error 86 | InvalidSyntax, &lexer 87 | )) 88 | } 89 | lexer.index += 1; 90 | lexer.column += 1; 91 | } 92 | 93 | Ok(tokens) 94 | } 95 | 96 | // `=>` 97 | fn lex_lambda_op(&mut self) -> Result { 98 | self.index += 1; 99 | self.column += 1; 100 | if self.index >= self.source.len() { 101 | // For error to point at the right location 102 | self.index -= 1; 103 | self.column -= 1; 104 | 105 | return Err(Error::lex_error( 106 | Missing("'>' after '=', for '=>' operator".to_string()), 107 | self 108 | )) 109 | } 110 | 111 | if self.source.chars().nth(self.index).unwrap() != '>' { 112 | return Err(Error::lex_error( 113 | Expected("'>' after '=', for '=>' operator".to_string()), 114 | self 115 | )) 116 | } 117 | 118 | Ok(Token { 119 | position: Position { line: self.line, column: self.column - 1 }, 120 | token_type: LambdaOp, 121 | }) 122 | } 123 | 124 | // `:=` 125 | fn lex_assignment_op(&mut self) -> Result { 126 | self.index += 1; 127 | self.column += 1; 128 | if self.index >= self.source.len() { 129 | // For error to point at the right location 130 | self.index -= 1; 131 | self.column -= 1; 132 | 133 | return Err(Error::lex_error( 134 | Missing("'=' after ':', for AssignmentOp (':=')".to_string()), 135 | self 136 | )) 137 | } 138 | 139 | if self.source.chars().nth(self.index).unwrap() != '=' { 140 | return Err(Error::lex_error( 141 | Expected("'=' after ':', for AssignmentOp (':=')".to_string()), 142 | self 143 | )) 144 | } 145 | 146 | Ok(Token { 147 | position: Position { line: self.line, column: self.column - 1 }, 148 | token_type: AssignmentOp, 149 | }) 150 | } 151 | 152 | // `*` 153 | fn lex_identifier(&mut self) -> Result { 154 | let start_column = self.column; 155 | let identifier = String::new(); 156 | 157 | let mut letter = self.source.chars().nth(self.index).unwrap(); 158 | while 159 | ('a'..='z').contains(&letter) | 160 | ('A'..='Z').contains(&letter) | 161 | ('0'..='9').contains(&letter) 162 | { 163 | identifier.write_char(letter).unwrap(); 164 | 165 | self.index += 1; 166 | self.column += 1; 167 | if self.index >= self.source.len() { break; } 168 | let mut letter = self.source.chars().nth(self.index).unwrap(); 169 | } 170 | Ok(Token { 171 | position: Position { line: self.line, column: start_column}, 172 | token_type: Identifier(identifier), 173 | }) 174 | } 175 | 176 | // `"*<.-">"` 177 | fn lex_string_literal(&mut self) -> Result { 178 | let start_line = self.line; 179 | let start_column = self.column; 180 | 181 | self.index += 1; 182 | self.column += 1; 183 | let mut result = String::new(); 184 | 185 | if self.index >= self.source.len() { 186 | // For error to point at the right location 187 | self.index -= 1; 188 | self.column -= 1; 189 | return Err(Error::lex_error( 190 | Missing("end of '\"' (String literal)".to_string()), 191 | self 192 | )) 193 | } 194 | 195 | let mut letter = self.source.chars().nth(self.index).unwrap(); 196 | while letter != '"' { 197 | // Keeping track of line, column for error messages 198 | if letter == '\n' { 199 | self.line += 1; 200 | self.column = 0; 201 | } 202 | 203 | result.write_char(letter).unwrap(); 204 | 205 | self.index += 1; 206 | self.column += 1; 207 | if self.index >= self.source.len() { 208 | self.line = start_line; 209 | self.column = start_column; 210 | return Err(Error::lex_error( 211 | Missing("This '\"' (String literal) have no ending".to_string()), 212 | self 213 | )) 214 | } 215 | 216 | letter = self.source.chars().nth(self.index).unwrap(); 217 | } 218 | 219 | Ok(Token { 220 | position: Position { line: start_line, column: start_column }, 221 | token_type: StringLiteral(result), 222 | }) 223 | } 224 | } 225 | 226 | enum ExprType{ 227 | Immediate(ImmediateType), 228 | Reference(usize, ImmediateType), 229 | } 230 | 231 | enum ImmediateType { 232 | Block, 233 | Value, 234 | } 235 | 236 | // TODO: Implement parser according to the new grammar 237 | impl Parser { 238 | fn new(tokens: ParseableTokens) -> Self { 239 | // Global variables for different parts of the Parser 240 | Self { 241 | tokens, 242 | byte_code: vec![], 243 | curr_token: Token { 244 | position: Position { line: 0, column: 0 }, 245 | token_type: BrackOpen, 246 | }, 247 | value_identifiers: HashMap::new(), 248 | block_identifiers: HashMap::new(), 249 | } 250 | } 251 | 252 | fn parse(tokens: Vec) -> Result, Error> { 253 | let mut parser = Parser::new(tokens.into_iter().peekable()); 254 | 255 | if let Some(token) = parser.tokens.next() { 256 | parser.curr_token = token; 257 | parser.source()?; 258 | } 259 | 260 | Ok(parser.byte_code) 261 | } 262 | 263 | fn source(&mut self) -> Result<(), Error> { 264 | loop { 265 | if let Some(statement_token) = self.tokens.next() { 266 | self.curr_token = statement_token; 267 | self.statement()?; 268 | } 269 | else { 270 | break; 271 | } 272 | } 273 | 274 | Ok(()) 275 | 276 | } 277 | 278 | fn statement(&mut self) -> Result<(), Error> { 279 | if let TokenType::TellOp = self.curr_token.token_type { 280 | self.tell(usize::MAX)?; 281 | return Ok(()); 282 | } 283 | if let TokenType::BranchOp = self.curr_token.token_type { 284 | self.branch()?; 285 | return Ok(()); 286 | } 287 | 288 | if let TokenType::Identifier(identifier) = self.curr_token.token_type { 289 | if let Some(_) = self.tokens.next_if(|x| x.token_type == AssignmentOp) { 290 | if let Some(statement_token) = self.tokens.next() { 291 | self.curr_token = statement_token; 292 | 293 | let value_spot = self.value_identifiers.len(); 294 | let block_spot = self.block_identifiers.len(); 295 | match self.expr(value_spot, block_spot)? { 296 | ExprType::Immediate(immediate) => match immediate { 297 | ImmediateType::Value => { 298 | self.value_identifiers.insert(identifier, value_spot); 299 | }, 300 | ImmediateType::Block => { 301 | self.block_identifiers.insert(identifier, block_spot); 302 | } 303 | } 304 | 305 | ExprType::Reference(original, original_type) => match original_type { 306 | ImmediateType::Value => { 307 | self.value_identifiers.insert(identifier, original); 308 | }, 309 | ImmediateType::Block => { 310 | self.block_identifiers.insert(identifier, original); 311 | } 312 | } 313 | }; 314 | return Ok(()); 315 | } 316 | else { 317 | return Err(Error::parse_error( 318 | Missing("expression for variable assignment".to_string()), 319 | self 320 | )); 321 | } 322 | } 323 | } 324 | 325 | self.expr(usize::MAX, usize::MAX)?; 326 | Ok(()) 327 | } 328 | 329 | fn expr(&mut self, value_spot: usize, block_spot: usize) -> Result { 330 | if let TokenType::TellOp = self.curr_token.token_type { 331 | self.tell(value_spot)?; 332 | return Ok(ExprType::Immediate(ImmediateType::Value)); 333 | } 334 | 335 | if let TokenType::Identifier(identifier) = self.curr_token.token_type { 336 | 337 | } 338 | 339 | todo!() 340 | } 341 | 342 | fn ask() { 343 | todo!() 344 | } 345 | 346 | fn tell(&mut self, value_spot: usize) -> Result<(), Error> { 347 | todo!() 348 | } 349 | 350 | fn branch(&mut self) -> Result<(), Error> { 351 | todo!() 352 | } 353 | 354 | fn branches() { 355 | todo!() 356 | } 357 | 358 | fn block() { 359 | todo!() 360 | } 361 | 362 | fn arguments() { 363 | todo!() 364 | } 365 | 366 | // StringLiteral 367 | // Identifer 368 | fn handle_token(&mut self) -> Result { 369 | match self.curr_token.token_type { 370 | StringLiteral(_) => self.parse_strings(), 371 | Identifier(_) => self.parse_identifier(), 372 | _ => { 373 | return Err(Error::parse_error( 374 | Expected("Message".to_string()), 375 | self 376 | )) 377 | }, 378 | } 379 | } 380 | 381 | // StringLiteral BrackOpen 382 | // StringLiteral 383 | fn parse_strings(&mut self) -> Result { 384 | self.validate_string()?; 385 | let message = match self.curr_token.token_type.clone() { 386 | StringLiteral(m) => m, 387 | _ => unreachable!(), 388 | }; 389 | 390 | if let Some(_) = 391 | self.tokens.next_if(|x| x.token_type == BrackOpen) { 392 | self.parse_branch(message); 393 | } 394 | else { 395 | } 396 | unreachable!() 397 | 398 | } 399 | 400 | // Identifer StringLiteral 401 | fn parse_identifier(&mut self) -> Result { 402 | let save_id = match self.curr_token.token_type { 403 | Identifier(m) => m, 404 | _ => unreachable!(), 405 | }; 406 | 407 | let question_token = if let Some(token) = self.tokens.next() { 408 | self.curr_token = token.clone(); 409 | token 410 | } else { 411 | return Err(Error::parse_error( 412 | Missing("question (StringLiteral)".to_string()), 413 | self 414 | )) 415 | }; 416 | let question = if let StringLiteral(question) = question_token.token_type { 417 | self.validate_string()?; 418 | question 419 | } else { 420 | return Err(Error::parse_error( 421 | Expected("question (StringLiteral)".to_string()), 422 | self 423 | )) 424 | }; 425 | 426 | unreachable!() 427 | } 428 | 429 | // BrackOpen +(StringLiteral LambdaOperator BrackOpen *. BrackClose) BrackClose 430 | fn parse_branch(&mut self, question: String) -> Result { 431 | let mut branches: Vec = Vec::new(); 432 | 433 | // Looping through all choices of the branch 434 | while let None = self.tokens.next_if(|x| x.token_type == BrackClose) { 435 | let branch_name_token = if let Some(token) = self.tokens.next() { 436 | self.curr_token = token.clone(); 437 | token 438 | } else { 439 | return Err(Error::parse_error( 440 | Missing("'}' (Branch ending)".to_string()), 441 | self 442 | )) 443 | }; 444 | 445 | let branch_name = if let StringLiteral(branch_name) = branch_name_token.token_type { 446 | self.validate_string()?; 447 | branch_name 448 | } else { 449 | return Err(Error::parse_error( 450 | Expected("Branch Node".to_string()), 451 | self 452 | )) 453 | }; 454 | 455 | let node_delaration = if let Some(token) = self.tokens.next() { 456 | self.curr_token = token.clone(); 457 | token 458 | } else { 459 | return Err(Error::parse_error( 460 | Missing("'=>' (Branch Node declaration)".to_string()), 461 | self 462 | )) 463 | }; 464 | return Err(Error::parse_error( 465 | Expected("'=>' (Branch Node declaration)".to_string()), 466 | self 467 | )) 468 | 469 | let node_starting = if let Some(token) = self.tokens.next() { 470 | self.curr_token = token.clone(); 471 | token 472 | } else { 473 | return Err(Error::parse_error( 474 | Missing("'{' (Branch Node starting)".to_string()), 475 | self 476 | )) 477 | }; 478 | if node_starting.token_type != BrackOpen { 479 | return Err(Error::parse_error( 480 | Expected("'{' (Branch Node starting)".to_string()), 481 | self 482 | )) 483 | }; 484 | 485 | let mut branch_node_block: Vec = Vec::new(); 486 | let tmp = self.value_identifiers.clone(); 487 | 488 | // Looping through all tokens inside the branch body 489 | // to recursively parse them all 490 | while let None = self.tokens.next_if(|x| x.token_type == BrackClose) { 491 | if let Some(token) = self.tokens.next() { 492 | self.curr_token = token.clone(); 493 | token 494 | } else { 495 | return Err(Error::parse_error( 496 | Missing("'}' (Branch Node ending)".to_string()), 497 | self 498 | )) 499 | }; 500 | 501 | branch_node_block.push(self.handle_token()?); 502 | } 503 | self.value_identifiers = tmp; 504 | 505 | branches.push(Branch::new(branch_name, branch_node_block)) 506 | } 507 | 508 | Ok(OpCode::new_branch(question, branches)) 509 | } 510 | 511 | // Checking if StringLiteral have valid identifier references 512 | // Identifer reference be -> `$<*digit>` 513 | fn validate_string(&self) -> Result<(), Error> { 514 | let info = if let StringLiteral(m) = 515 | self.curr_token.token_type.clone() { m } else { unreachable!() }; 516 | 517 | let mut i = 0; 518 | while i < info.len() { 519 | let mut letter = info.chars().nth(i).unwrap(); 520 | if letter == '$' { 521 | i += 1; 522 | if i >= info.len() { break; } 523 | letter = info.chars().nth(i).unwrap(); 524 | 525 | let mut number = String::new(); 526 | while DIGITS.contains(&letter) { 527 | number.write_char(letter).unwrap(); 528 | i += 1; 529 | if i >= info.len() { break; } 530 | letter = info.chars().nth(i).unwrap(); 531 | } 532 | 533 | if !number.is_empty() { 534 | let number: usize = number.parse().unwrap(); 535 | if !self.value_identifiers.contains(&number) { 536 | //"Identifer '{}' used in StringLiteral without delaration", 537 | return Err(Error::parse_error( 538 | InvalidIdentifier(number), 539 | self 540 | )) 541 | } 542 | } 543 | } 544 | 545 | i += 1; 546 | } 547 | Ok(()) 548 | } 549 | } 550 | 551 | pub fn compile(source: String) -> Result, Error> { 552 | let tokens = Lexer::lex(source)?; 553 | let messages = Parser::parse(tokens)?; 554 | 555 | Ok(messages) 556 | } 557 | --------------------------------------------------------------------------------