├── .gitignore ├── .travis.yml ├── src ├── main.rs ├── vm │ ├── mod.rs │ └── opcode.rs ├── lib.rs ├── term.rs ├── fun.rs ├── interpreter.rs ├── tests.rs └── emitter.rs ├── Cargo.toml ├── LICENSE ├── Cargo.lock └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | target 2 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: rust 2 | rust: 3 | - stable 4 | -------------------------------------------------------------------------------- /src/main.rs: -------------------------------------------------------------------------------- 1 | extern crate scotty; 2 | 3 | use scotty::vm::VM; 4 | 5 | fn main() { 6 | let vm = VM::new(); 7 | 8 | vm.start() 9 | } 10 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | authors = ["Sonny Scroggin "] 3 | name = "scotty" 4 | version = "0.1.0" 5 | 6 | [dependencies] 7 | byteorder = "0.3.13" 8 | enum_primitive = "0.1.1" 9 | -------------------------------------------------------------------------------- /src/vm/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod opcode; 2 | 3 | pub struct VM { 4 | 5 | } 6 | 7 | impl VM { 8 | pub fn new() -> Self { 9 | VM { 10 | 11 | } 12 | } 13 | 14 | pub fn start(self) { 15 | loop {} 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | #![allow(dead_code)] 2 | #![allow(unused_imports)] 3 | extern crate byteorder; 4 | #[macro_use] extern crate enum_primitive; 5 | 6 | pub mod emitter; 7 | pub mod interpreter; 8 | pub mod term; 9 | pub mod vm; 10 | 11 | #[cfg(test)] 12 | mod tests; 13 | -------------------------------------------------------------------------------- /src/term.rs: -------------------------------------------------------------------------------- 1 | #[derive(Clone, Debug, PartialEq)] 2 | pub enum Atom { 3 | Nil, 4 | True, 5 | False, 6 | UserDefined(String) 7 | } 8 | 9 | #[derive(Clone, Debug, PartialEq)] 10 | pub enum Term { 11 | Atom(Atom), 12 | Integer(i64), 13 | Float(f64), 14 | } 15 | -------------------------------------------------------------------------------- /src/fun.rs: -------------------------------------------------------------------------------- 1 | pub mod fun; 2 | 3 | use std::collections::HashMap; 4 | 5 | pub struct Fun { 6 | } 7 | 8 | pub struct Table { 9 | entries: HashMap 10 | } 11 | 12 | impl Table { 13 | fn new() -> Table { 14 | Table { 15 | entries: HashMap::new() 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/vm/opcode.rs: -------------------------------------------------------------------------------- 1 | enum_from_primitive! { 2 | #[derive(Debug, PartialEq)] 3 | pub enum OpCode { 4 | LoadU8 = 1, // arity: 2 - (value: u64, dest: u8) 5 | Add = 2, // arity: 3 - (a: u8, b: u8, dest: u8) 6 | Sub = 3, // arity: 3 - (a: u8, b: u8, dest: u8) 7 | Mult = 4, // arity: 3 - (a: u8, b: u8, dest: u8) 8 | LessThan = 5, // arity: 2 - (a: u8, b: u8, dest: u8) 9 | Return = 6, // arity: 1 - (reg: u8) 10 | JumpTrue = 7, // arity 11 | Print = 8, // arity: 1 - (reg: u8) 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Sonny Scroggin 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 | -------------------------------------------------------------------------------- /Cargo.lock: -------------------------------------------------------------------------------- 1 | [root] 2 | name = "scotty" 3 | version = "0.1.0" 4 | dependencies = [ 5 | "byteorder 0.3.13 (registry+https://github.com/rust-lang/crates.io-index)", 6 | "enum_primitive 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", 7 | ] 8 | 9 | [[package]] 10 | name = "byteorder" 11 | version = "0.3.13" 12 | source = "registry+https://github.com/rust-lang/crates.io-index" 13 | 14 | [[package]] 15 | name = "enum_primitive" 16 | version = "0.1.1" 17 | source = "registry+https://github.com/rust-lang/crates.io-index" 18 | dependencies = [ 19 | "num-traits 0.1.39 (registry+https://github.com/rust-lang/crates.io-index)", 20 | ] 21 | 22 | [[package]] 23 | name = "num-traits" 24 | version = "0.1.39" 25 | source = "registry+https://github.com/rust-lang/crates.io-index" 26 | 27 | [metadata] 28 | "checksum byteorder 0.3.13 (registry+https://github.com/rust-lang/crates.io-index)" = "29b2aa490a8f546381308d68fc79e6bd753cd3ad839f7a7172897f1feedfa175" 29 | "checksum enum_primitive 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "be4551092f4d519593039259a9ed8daedf0da12e5109c5280338073eaeb81180" 30 | "checksum num-traits 0.1.39 (registry+https://github.com/rust-lang/crates.io-index)" = "1708c0628602a98b52fad936cf3edb9a107af06e52e49fdf0707e884456a6af6" 31 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Scotty 2 | 3 | > A toy [BEAM] Virtual Machine in [Rust] 4 | 5 | [![Build Status](https://travis-ci.org/scrogson/scotty.svg?branch=master)](https://travis-ci.org/scrogson/scotty) 6 | 7 | This is just a toy project to help me learn more about the [BEAM], [Rust], 8 | virtural machines, emulators, interpreters, and compilers. 9 | 10 | ## Acknowledgements 11 | 12 | Many thanks to **[@jorendorff]** for mentoring me through this process! 13 | 14 | ## License 15 | 16 | The MIT License (MIT) 17 | 18 | Copyright (c) 2015 Sonny Scroggin 19 | 20 | Permission is hereby granted, free of charge, to any person obtaining a copy 21 | of this software and associated documentation files (the "Software"), to deal 22 | in the Software without restriction, including without limitation the rights 23 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 24 | copies of the Software, and to permit persons to whom the Software is 25 | furnished to do so, subject to the following conditions: 26 | 27 | The above copyright notice and this permission notice shall be included in all 28 | copies or substantial portions of the Software. 29 | 30 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 31 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 32 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 33 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 34 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 35 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 36 | SOFTWARE. 37 | 38 | [BEAM]: http://www.erlang.org/ 39 | [Rust]: https://www.rust-lang.org/ 40 | [@jorendorff]: https://github.com/jorendorff 41 | -------------------------------------------------------------------------------- /src/interpreter.rs: -------------------------------------------------------------------------------- 1 | use byteorder::{ByteOrder, LittleEndian}; 2 | use std::result::Result; 3 | 4 | use term::Term; 5 | use term::Atom::{Nil, True, False}; 6 | use vm::opcode::*; 7 | 8 | pub fn interpret(code: &[u8]) -> Result { 9 | let mut pc = 0; // "Program counter", the index of the current instruction. 10 | let mut regs = Vec::with_capacity(256); 11 | for _ in 0..256 { regs.push(Term::Atom(Nil)); } 12 | 13 | macro_rules! read_reg { 14 | () => ({ 15 | let val = code[pc] as usize; 16 | pc += 1; 17 | val 18 | }) 19 | } 20 | 21 | macro_rules! arith { 22 | ($v1: ident, $v2: ident, $expr: expr) => ({ 23 | let a = read_reg!(); 24 | let b = read_reg!(); 25 | let dest = read_reg!(); 26 | let result = match (®s[a], ®s[b]) { 27 | (&Term::Integer($v1), &Term::Integer($v2)) => $expr, 28 | (v1, v2) => return Err(format!("invalid types for arithmetic: {:?}, {:?}", *v1, *v2)) 29 | }; 30 | regs[dest] = result; 31 | }) 32 | } 33 | 34 | loop { 35 | use enum_primitive::FromPrimitive; 36 | 37 | let op = code[pc]; 38 | pc += 1; 39 | match OpCode::from_u8(op) { 40 | Some(OpCode::LoadU8) => { 41 | let val = Term::Integer(code[pc] as i64); 42 | pc += 1; 43 | let dest = code[pc] as usize; 44 | pc += 1; 45 | regs[dest] = val; 46 | }, 47 | Some(OpCode::Add) => arith!(a, b, Term::Integer(a + b)), 48 | Some(OpCode::Sub) => arith!(a, b, Term::Integer(a - b)), 49 | Some(OpCode::Mult) => arith!(a, b, Term::Integer(a * b)), 50 | Some(OpCode::LessThan) => arith!(a, b, if a < b { Term::Atom(True) } else { Term::Atom(False) } ), 51 | Some(OpCode::Return) => { 52 | let r = code[pc] as usize; 53 | return Ok(regs[r].clone()); 54 | }, 55 | Some(OpCode::JumpTrue) => { 56 | let r = code[pc] as usize; 57 | if regs[r] == Term::Atom(True) { 58 | pc += 1; 59 | let jump_size = LittleEndian::read_i32(&code[pc..pc + 4]); 60 | pc += 4; 61 | pc = pc.wrapping_add(jump_size as usize); 62 | } else { 63 | pc += 5; 64 | } 65 | }, 66 | Some(OpCode::Print) => { 67 | let r = code[pc] as usize; 68 | println!("{:?}", regs[r]); 69 | } 70 | None => return Err(format!("Invalid opcode at offset {:?}", pc - 1)) 71 | } 72 | } 73 | } 74 | 75 | fn main() {} 76 | -------------------------------------------------------------------------------- /src/tests.rs: -------------------------------------------------------------------------------- 1 | use term::Term; 2 | use term::Atom::{Nil, True, False}; 3 | use vm::opcode::OpCode::*; 4 | use interpreter::*; 5 | 6 | #[test] 7 | fn test_op_add() { 8 | let code = [ 9 | LoadU8 as u8, 73, 0, 10 | LoadU8 as u8, 68, 1, 11 | Add as u8, 0, 1, 2, 12 | Return as u8, 2 13 | ]; 14 | 15 | assert_eq!(interpret(&code), Ok(Term::Integer(141))); 16 | } 17 | 18 | #[test] 19 | fn test_op_sub() { 20 | let code1 = [ 21 | LoadU8 as u8, 10, 0, 22 | LoadU8 as u8, 8, 1, 23 | Sub as u8, 0, 1, 2, 24 | Return as u8, 2 25 | ]; 26 | 27 | assert_eq!(interpret(&code1), Ok(Term::Integer(2))); 28 | 29 | let code2 = [ 30 | LoadU8 as u8, 8, 0, 31 | LoadU8 as u8, 10, 1, 32 | Sub as u8, 0, 1, 2, 33 | Return as u8, 2 34 | ]; 35 | 36 | assert_eq!(interpret(&code2), Ok(Term::Integer(-2))); 37 | } 38 | 39 | #[test] 40 | fn test_op_mult() { 41 | let code = [ 42 | LoadU8 as u8, 2, 0, 43 | LoadU8 as u8, 4, 1, 44 | Mult as u8, 0, 1, 2, 45 | Return as u8, 2 46 | ]; 47 | 48 | assert_eq!(interpret(&code), Ok(Term::Integer(8))); 49 | } 50 | 51 | #[test] 52 | fn test_op_lt() { 53 | let code1 = [ 54 | LoadU8 as u8, 73, 0, 55 | LoadU8 as u8, 68, 1, 56 | LessThan as u8, 0, 1, 2, 57 | Return as u8, 2 58 | ]; 59 | 60 | assert_eq!(interpret(&code1), Ok(Term::Atom(False))); 61 | 62 | let code2 = [ 63 | LoadU8 as u8, 68, 0, 64 | LoadU8 as u8, 73, 1, 65 | LessThan as u8, 0, 1, 2, 66 | Return as u8, 2 67 | ]; 68 | 69 | assert_eq!(interpret(&code2), Ok(Term::Atom(True))); 70 | } 71 | 72 | #[test] 73 | fn test_loop() { 74 | let code = [ 75 | // Some bytecode to multiply two numbers. 76 | // 77 | // x = 6; 78 | // y = 8; 79 | // acc = 0; 80 | // 81 | // for (i = 0; i < x; i++) { 82 | // acc += y; 83 | // } 84 | // 85 | // return acc; 86 | 87 | LoadU8 as u8, 6, 0, // load the number 6 into R0 (`X`, the first number to multiply) 88 | LoadU8 as u8, 8, 1, // load the number 8 into R1 (`Y`, the second number to multiply) 89 | LoadU8 as u8, 0, 2, // load the number 0 into R2 (`i`, the loop counter) 90 | LoadU8 as u8, 0, 3, // load the number 0 into R3 (`acc`, the accumulator) 91 | LoadU8 as u8, 1, 4, // load the number 1 into R4 - it stays there forever 92 | Add as u8, 1, 3, 3, // add R1 to R3, store the result in R3 93 | Add as u8, 2, 4, 2, // increment R2 by 1 (because the value in R4 is 1) 94 | LessThan as u8, 2, 0, 5, // is R2 < R0? 95 | JumpTrue as u8, 5, 0xee, 0xff, 0xff, 0xff, // if so, jump back 18 bytes 96 | 97 | Return as u8, 3, // return contents of R3 (`acc`) 98 | ]; 99 | 100 | assert_eq!(interpret(&code), Ok(Term::Integer(48))); 101 | } 102 | -------------------------------------------------------------------------------- /src/emitter.rs: -------------------------------------------------------------------------------- 1 | use term::Term; 2 | use vm::opcode::*; 3 | use interpreter::*; 4 | 5 | pub enum Program { 6 | Integer(u8), 7 | Binary {op: u8, left: Box, right: Box}, 8 | Print(Box) 9 | } 10 | 11 | pub fn emit(program: &Program) -> Vec { 12 | let mut code = vec![]; 13 | emit_into(&mut code, program, 0); 14 | code.push(OpCode::Return as u8); 15 | code.push(0); 16 | code 17 | } 18 | 19 | fn emit_into(code: &mut Vec, program: &Program, target: u8) { 20 | match *program { 21 | Program::Integer(n) => { 22 | load_u8(code, n, target) 23 | }, 24 | Program::Binary {op, ref left, ref right} => { 25 | emit_into(code, &*left, target); 26 | emit_into(code, &*right, target + 1); 27 | code.push(op); 28 | code.push(target); 29 | code.push(target + 1); 30 | code.push(target); 31 | }, 32 | Program::Print(ref program) => { 33 | emit_into(code, &*program, target); 34 | code.push(OpCode::Print as u8); 35 | } 36 | } 37 | } 38 | 39 | fn load_u8(code: &mut Vec, value: u8, dest: u8) { 40 | code.push(OpCode::LoadU8 as u8); 41 | code.push(value); 42 | code.push(dest); 43 | } 44 | 45 | fn int(n: u8) -> Box { 46 | Box::new(Program::Integer(n)) 47 | } 48 | 49 | fn add(left: Box, right: Box) -> Box { 50 | let program = Program::Binary { 51 | op: OpCode::Add as u8, 52 | left: left, 53 | right: right 54 | }; 55 | Box::new(program) 56 | } 57 | 58 | #[test] 59 | fn test_emit_integer() { 60 | let code = emit(&Program::Integer(37)); 61 | assert_eq!(interpret(&code), Ok(Term::Integer(37))); 62 | 63 | let code1 = emit(&int(56)); 64 | assert_eq!(interpret(&code1), Ok(Term::Integer(56))); 65 | 66 | assert_eq!(interpret(&emit(&int(73))), Ok(Term::Integer(73))); 67 | } 68 | 69 | #[test] 70 | fn test_emit_simple_binary_op() { 71 | let program = Program::Binary { 72 | op: OpCode::Add as u8, 73 | left: int(73), 74 | right: int(68) 75 | }; 76 | 77 | let code = emit(&program); 78 | assert_eq!(interpret(&code), Ok(Term::Integer(141))); 79 | } 80 | 81 | #[test] 82 | fn test_emit_nested_binary_op() { 83 | // (73 + 68) + 50 = 191 84 | let program0 = Program::Binary { 85 | op: OpCode::Add as u8, 86 | left: add(int(73), int(68)), 87 | right: int(50) 88 | }; 89 | 90 | let code0 = emit(&program0); 91 | assert_eq!(interpret(&code0), Ok(Term::Integer(191))); 92 | 93 | // 1 + (2 + 3) = 6 94 | let program1 = Program::Binary { 95 | op: OpCode::Add as u8, 96 | left: int(1), 97 | right: add(int(2), int(3)) 98 | }; 99 | 100 | let code1 = emit(&program1); 101 | assert_eq!(interpret(&code1), Ok(Term::Integer(6))); 102 | } 103 | 104 | #[test] 105 | fn test_emit_deep_nested_binary_op() { 106 | // (1 + (2 + (3 + (4 + 5)))) 107 | let program = add(int(1), add(int(2), add(int(3), add(int(4), int(5))))); 108 | let code = emit(&program); 109 | assert_eq!(interpret(&code), Ok(Term::Integer(15))); 110 | } 111 | 112 | #[test] 113 | fn test_emit_print() { 114 | assert_eq!(interpret(&emit(&Program::Print(add(int(3), int(4))))), Ok(Term::Integer(7))); 115 | assert_eq!(interpret(&emit(&Program::Print(int(7)))), Ok(Term::Integer(7))); 116 | } 117 | --------------------------------------------------------------------------------