├── samples ├── hello.onehour ├── concat.onehour └── add.onehour ├── Cargo.toml ├── README.md ├── LICENSE └── src └── main.rs /samples/hello.onehour: -------------------------------------------------------------------------------- 1 | set x "hello" 2 | get x -------------------------------------------------------------------------------- /samples/concat.onehour: -------------------------------------------------------------------------------- 1 | push "world" 2 | push "hello" 3 | add 4 | pop -------------------------------------------------------------------------------- /samples/add.onehour: -------------------------------------------------------------------------------- 1 | push 10 2 | push 20 3 | push 30 4 | add 5 | add 6 | pop -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "onehour" 3 | version = "0.1.0" 4 | edition = "2018" 5 | 6 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 7 | 8 | [dependencies] 9 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Building a programming language in an hour 2 | 3 | This is the project I made while doing the [Building a programming language in an hour](https://www.youtube.com/watch?v=Zkd3mZYOOvw) video. 4 | 5 | You can run it, once you have Rust installed, by doing: 6 | 7 | ``` 8 | cargo run -- 9 | ``` 10 | 11 | And run tests with 12 | 13 | ``` 14 | cargo test 15 | ``` 16 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 JT 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 | -------------------------------------------------------------------------------- /src/main.rs: -------------------------------------------------------------------------------- 1 | use std::collections::HashMap; 2 | 3 | enum Command { 4 | SetVar(String, Value), 5 | GetVar(String), 6 | PushVar(String), 7 | Push(Value), 8 | Pop, 9 | Add, 10 | } 11 | 12 | #[derive(Clone, PartialEq, Debug)] 13 | enum Value { 14 | Nothing, 15 | Int(i64), 16 | String(String), 17 | } 18 | 19 | #[derive(Clone, PartialEq, Debug)] 20 | enum Type { 21 | Int, 22 | String, 23 | Nothing, 24 | } 25 | 26 | #[derive(Debug)] 27 | enum EngineError { 28 | MismatchNumParams, 29 | MimatchType, 30 | UnknownCommand(String), 31 | MissingVariable(String), 32 | EmptyStack, 33 | } 34 | 35 | struct Evaluator { 36 | vars: HashMap, 37 | stack: Vec, 38 | } 39 | 40 | impl Evaluator { 41 | fn new() -> Evaluator { 42 | Self { 43 | vars: HashMap::new(), 44 | stack: vec![], 45 | } 46 | } 47 | 48 | fn pop(&mut self) -> Result { 49 | let result = self.stack.pop(); 50 | match result { 51 | Some(v) => Ok(v), 52 | None => return Err(EngineError::EmptyStack), 53 | } 54 | } 55 | 56 | fn add(&self, lhs: Value, rhs: Value) -> Result { 57 | match (lhs, rhs) { 58 | (Value::Int(i1), Value::Int(i2)) => Ok(Value::Int(i1 + i2)), 59 | (Value::String(s1), Value::String(s2)) => Ok(Value::String(s1 + &s2)), 60 | _ => Err(EngineError::MimatchType), 61 | } 62 | } 63 | 64 | fn evaluate(&mut self, commands: &[Command]) -> Result { 65 | let mut output = Ok(Value::Nothing); 66 | for command in commands { 67 | match command { 68 | Command::SetVar(name, value) => { 69 | self.vars.insert(name.into(), value.clone()); 70 | } 71 | Command::GetVar(name) => match self.vars.get(name) { 72 | Some(value) => output = Ok(value.clone()), 73 | None => return Err(EngineError::MissingVariable(name.into())), 74 | }, 75 | Command::PushVar(name) => match self.vars.get(name) { 76 | Some(value) => self.stack.push(value.clone()), 77 | None => return Err(EngineError::MissingVariable(name.into())), 78 | }, 79 | Command::Push(v) => self.stack.push(v.clone()), 80 | Command::Pop => { 81 | output = self.pop(); 82 | } 83 | Command::Add => { 84 | let lhs = self.pop()?; 85 | let rhs = self.pop()?; 86 | 87 | let result = self.add(lhs, rhs)?; 88 | self.stack.push(result) 89 | } 90 | } 91 | } 92 | output 93 | } 94 | } 95 | 96 | fn parse_var_name(var_name: &str) -> Result { 97 | Ok(var_name.into()) 98 | } 99 | 100 | fn parse_string(val: &str) -> Result { 101 | if val.starts_with('\"') && val.ends_with('\"') && val.len() > 1 { 102 | let inner = val[1..(val.len() - 1)].to_string(); 103 | 104 | Ok(Value::String(inner)) 105 | } else { 106 | Err(EngineError::MimatchType) 107 | } 108 | } 109 | 110 | fn parse_int(val: &str) -> Result { 111 | let result = val.parse::(); 112 | 113 | match result { 114 | Ok(x) => Ok(Value::Int(x)), 115 | _ => Err(EngineError::MimatchType), 116 | } 117 | } 118 | 119 | fn parse_value(val: &str) -> Result { 120 | if val.starts_with("\"") && val.ends_with("\"") && val.len() > 1 { 121 | // Parse the string 122 | parse_string(val) 123 | } else { 124 | // Parse the number 125 | parse_int(val) 126 | } 127 | } 128 | 129 | fn parse_set(input: &[&str]) -> Result { 130 | if input.len() != 3 { 131 | return Err(EngineError::MismatchNumParams); 132 | } 133 | 134 | let var_name = parse_var_name(input[1])?; 135 | let value = parse_value(input[2])?; 136 | 137 | Ok(Command::SetVar(var_name, value)) 138 | } 139 | 140 | fn parse_get(input: &[&str]) -> Result { 141 | if input.len() != 2 { 142 | return Err(EngineError::MismatchNumParams); 143 | } 144 | 145 | let var_name = parse_var_name(input[1])?; 146 | 147 | Ok(Command::GetVar(var_name)) 148 | } 149 | 150 | fn parse_pushvar(input: &[&str]) -> Result { 151 | if input.len() != 2 { 152 | return Err(EngineError::MismatchNumParams); 153 | } 154 | 155 | let var_name = parse_var_name(input[1])?; 156 | 157 | Ok(Command::PushVar(var_name)) 158 | } 159 | 160 | fn parse_push(input: &[&str]) -> Result { 161 | if input.len() != 2 { 162 | return Err(EngineError::MismatchNumParams); 163 | } 164 | 165 | let val = parse_value(input[1])?; 166 | 167 | Ok(Command::Push(val)) 168 | } 169 | 170 | fn parse(input: &str) -> Result, EngineError> { 171 | // set a 100 172 | // get a 173 | 174 | let mut output = vec![]; 175 | 176 | for line in input.lines() { 177 | let command: Vec<_> = line.split_ascii_whitespace().collect(); 178 | 179 | match command.get(0) { 180 | Some(x) if *x == "set" => { 181 | output.push(parse_set(&command)?); 182 | } 183 | Some(x) if *x == "get" => { 184 | output.push(parse_get(&command)?); 185 | } 186 | Some(x) if *x == "push" => { 187 | output.push(parse_push(&command)?); 188 | } 189 | Some(x) if *x == "pushvar" => { 190 | output.push(parse_pushvar(&command)?); 191 | } 192 | Some(x) if *x == "pop" => { 193 | output.push(Command::Pop); 194 | } 195 | Some(x) if *x == "add" => { 196 | output.push(Command::Add); 197 | } 198 | Some(name) => return Err(EngineError::UnknownCommand(name.to_string())), 199 | None => {} 200 | } 201 | } 202 | 203 | Ok(output) 204 | } 205 | 206 | struct Typechecker { 207 | stack: Vec, 208 | } 209 | 210 | impl Typechecker { 211 | fn typecheck_command(&mut self, commands: &Command) -> Result { 212 | Ok(Type::Nothing) 213 | } 214 | 215 | fn typecheck(&mut self, commands: &[Command]) -> Result { 216 | for command in commands { 217 | self.typecheck_command(command)?; 218 | } 219 | Ok(Type::Nothing) 220 | } 221 | } 222 | 223 | #[test] 224 | fn test1() -> Result<(), EngineError> { 225 | let commands = vec![ 226 | Command::SetVar("a".into(), Value::Int(100)), 227 | Command::GetVar("a".into()), 228 | ]; 229 | 230 | let mut evaluator = Evaluator::new(); 231 | 232 | let result = evaluator.evaluate(&commands)?; 233 | 234 | assert_eq!(result, Value::Int(100)); 235 | 236 | Ok(()) 237 | } 238 | 239 | #[test] 240 | fn eval_set_get() -> Result<(), EngineError> { 241 | let input = "set x 30\nget x"; 242 | 243 | let commands = parse(input)?; 244 | 245 | let mut evaluator = Evaluator::new(); 246 | let result = evaluator.evaluate(&commands)?; 247 | 248 | assert_eq!(result, Value::Int(30)); 249 | 250 | Ok(()) 251 | } 252 | 253 | #[test] 254 | fn eval_set_get_string() -> Result<(), EngineError> { 255 | let input = "set x \"hello\"\nget x"; 256 | 257 | let commands = parse(input)?; 258 | 259 | let mut evaluator = Evaluator::new(); 260 | let result = evaluator.evaluate(&commands)?; 261 | 262 | assert_eq!(result, Value::String("hello".into())); 263 | 264 | Ok(()) 265 | } 266 | 267 | #[test] 268 | fn eval_stack() -> Result<(), EngineError> { 269 | let input = "push 100\npush 30\nadd\npop"; 270 | 271 | let commands = parse(input)?; 272 | 273 | let mut evaluator = Evaluator::new(); 274 | let result = evaluator.evaluate(&commands)?; 275 | 276 | assert_eq!(result, Value::Int(130)); 277 | 278 | Ok(()) 279 | } 280 | 281 | #[test] 282 | fn eval_pushvar() -> Result<(), EngineError> { 283 | let input = "set x 33\npushvar x\npush 100\nadd\npop"; 284 | 285 | let commands = parse(input)?; 286 | 287 | let mut evaluator = Evaluator::new(); 288 | let result = evaluator.evaluate(&commands)?; 289 | 290 | assert_eq!(result, Value::Int(133)); 291 | 292 | Ok(()) 293 | } 294 | 295 | fn main() -> Result<(), EngineError> { 296 | for arg in std::env::args().skip(1) { 297 | let contents = std::fs::read_to_string(arg).unwrap(); 298 | let mut engine = Evaluator::new(); 299 | let commands = parse(&contents)?; 300 | let answer = engine.evaluate(&commands)?; 301 | 302 | println!("{:?}", answer); 303 | } 304 | 305 | Ok(()) 306 | } 307 | --------------------------------------------------------------------------------