├── .gitignore ├── Cargo.toml ├── src ├── ui │ ├── list_action.rs │ ├── output.rs │ ├── references.rs │ ├── uistate.rs │ ├── status.rs │ ├── mod.rs │ ├── action.rs │ └── memory_list.rs ├── cli.yml ├── state │ ├── memory_line.rs │ ├── mod.rs │ ├── operator.rs │ └── tests.rs ├── main.rs └── io │ └── mod.rs ├── ndp └── example.ndp ├── README.md └── Cargo.lock /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | **/*.rs.bk 3 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "rust_neander" 3 | version = "0.1.0" 4 | authors = ["macabeus"] 5 | 6 | [dependencies] 7 | tui = "0.3.0" 8 | termion = "1.5.1" 9 | clap = {version = "2.32", features = ["yaml"]} 10 | -------------------------------------------------------------------------------- /src/ui/list_action.rs: -------------------------------------------------------------------------------- 1 | use ui::uistate::ListState; 2 | use state::State; 3 | 4 | pub struct ListActions { 5 | pub move_up_cursor_handle: fn(&mut ListState), 6 | pub move_down_cursor_handle: fn(&mut ListState), 7 | pub set_type_u8_handle: fn(&mut State, usize, u8), 8 | pub select_line_handle: fn(&mut State, usize), 9 | } 10 | -------------------------------------------------------------------------------- /src/cli.yml: -------------------------------------------------------------------------------- 1 | name: rust-neander 2 | version: "0.1" 3 | author: Macabeus 4 | about: Educational purpose virtual machine to assembly Neander and Neander+ 5 | args: 6 | - FILEPATH: 7 | help: Sets the input file to use 8 | required: true 9 | index: 1 10 | - input: 11 | short: i 12 | help: Sets the level of verbosity 13 | takes_value: true 14 | 15 | -------------------------------------------------------------------------------- /src/state/memory_line.rs: -------------------------------------------------------------------------------- 1 | use state::operator::Operator; 2 | use state::operator::NOP; 3 | 4 | #[derive(Copy, Clone)] 5 | pub enum LineKind { 6 | Operator, 7 | Argument, 8 | } 9 | 10 | #[derive(Copy, Clone)] 11 | pub struct MemoryLine { 12 | pub operator: Operator, 13 | pub value: u8, 14 | pub kind: LineKind, 15 | } 16 | 17 | pub const MEMORY_LINE_BLANK: MemoryLine = MemoryLine { 18 | operator: NOP, 19 | value: 0x00, 20 | kind: LineKind::Operator, 21 | }; 22 | -------------------------------------------------------------------------------- /src/ui/output.rs: -------------------------------------------------------------------------------- 1 | use state::State; 2 | use ui::tui::backend::Backend; 3 | use ui::tui::widgets::{Widget, Block, Borders, Text, List}; 4 | use ui::tui::layout::Rect; 5 | use ui::tui::Frame; 6 | 7 | pub fn draw(state: &State, f: &mut Frame, area: Rect) 8 | where 9 | B: Backend, 10 | { 11 | let output_list = state.output 12 | .iter() 13 | .map(|value| 14 | Text::raw(format!(" {:#04X}", value)) 15 | ); 16 | 17 | List::new(output_list) 18 | .block(Block::default().borders(Borders::ALL).title(" Output ")) 19 | .render(f, area); 20 | } 21 | -------------------------------------------------------------------------------- /src/main.rs: -------------------------------------------------------------------------------- 1 | #[macro_use] 2 | extern crate clap; 3 | mod state; 4 | mod io; 5 | mod ui; 6 | use state::State; 7 | use clap::App; 8 | 9 | struct CLICommands { 10 | filename: String, 11 | input: String, 12 | } 13 | 14 | fn extract_cli_commands() -> CLICommands { 15 | let yaml = load_yaml!("cli.yml"); 16 | let matches = App::from_yaml(yaml).get_matches(); 17 | 18 | CLICommands { 19 | filename: matches.value_of("FILEPATH").unwrap().to_string(), 20 | input: matches.value_of("input").unwrap_or("").to_string(), 21 | } 22 | } 23 | 24 | fn main() -> Result<(), Box> { 25 | let cli_commands = extract_cli_commands(); 26 | let memory = io::load_file(&cli_commands.filename); 27 | let inputs = io::load_inputs(&cli_commands.input); 28 | 29 | let state = State::new(memory, inputs); 30 | 31 | ui::draw_screen(&cli_commands.filename, state) 32 | } 33 | 34 | -------------------------------------------------------------------------------- /src/ui/references.rs: -------------------------------------------------------------------------------- 1 | use ui::tui::layout::Rect; 2 | use ui::tui::backend::Backend; 3 | use ui::tui::widgets::{Widget, Block, Borders, Paragraph, Text}; 4 | use ui::tui::Frame; 5 | 6 | pub fn draw(f: &mut Frame, area: Rect) 7 | where 8 | B: Backend, 9 | { 10 | let texts = [ 11 | Text::raw("[00] NOP | "), 12 | Text::raw("[10] STA address | "), 13 | Text::raw("[20] LDA address | "), 14 | Text::raw("[30] ADD address | "), 15 | Text::raw("[40] OR address | "), 16 | Text::raw("[50] AND address | "), 17 | Text::raw("[60] NOT | "), 18 | Text::raw("[70] SUB address | "), 19 | Text::raw("[80] JMP value | "), 20 | Text::raw("[90] JN value | "), 21 | Text::raw("[A0] JZ value | "), 22 | Text::raw("[B0] JNZ value | "), 23 | Text::raw("[C0] IN index | "), 24 | Text::raw("[D0] OUT | "), 25 | Text::raw("[E0] LDI value | "), 26 | Text::raw("[F0] HLT"), 27 | ]; 28 | 29 | Paragraph::new(texts.into_iter()) 30 | .block(Block::default().borders(Borders::ALL).title(" Opcodes ")) 31 | .wrap(true) 32 | .render(f, area); 33 | } 34 | -------------------------------------------------------------------------------- /src/ui/uistate.rs: -------------------------------------------------------------------------------- 1 | use ui::list_action::ListActions; 2 | 3 | pub enum BlockLists { 4 | Status, 5 | Operators, 6 | Variables, 7 | } 8 | 9 | pub struct ListState { 10 | pub current_line: usize, 11 | pub first_line: usize, 12 | pub last_line: usize, 13 | pub handle_action: Box, 14 | } 15 | 16 | pub struct UIState { 17 | pub filepath: String, 18 | pub block_selected: BlockLists, 19 | pub status_block: ListState, 20 | pub memory_list_operators: ListState, 21 | pub memory_list_variables: ListState, 22 | pub is_typing: bool, 23 | pub typing_char: Option, 24 | pub quit: bool, 25 | } 26 | 27 | impl UIState { 28 | pub fn current_list(&self) -> &ListState { 29 | match self.block_selected { 30 | BlockLists::Status => &self.status_block, 31 | BlockLists::Operators => &self.memory_list_operators, 32 | BlockLists::Variables => &self.memory_list_variables, 33 | } 34 | } 35 | 36 | pub fn mutable_current_list(&mut self) -> &mut ListState { 37 | match self.block_selected { 38 | BlockLists::Status => &mut self.status_block, 39 | BlockLists::Operators => &mut self.memory_list_operators, 40 | BlockLists::Variables => &mut self.memory_list_variables, 41 | } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/io/mod.rs: -------------------------------------------------------------------------------- 1 | use std::io::BufReader; 2 | use std::io::BufRead; 3 | use std::fs::File; 4 | use std::io::Write; 5 | 6 | pub fn load_file(filepath: &str) -> [u8; 255] { 7 | let mut output: [u8; 255] = [0x00; 255]; 8 | 9 | let f = File::open(filepath).expect("File not found!"); 10 | let file = BufReader::new(&f); 11 | for (num, line) in file.lines().enumerate() { 12 | let l = line.unwrap(); 13 | output[num] = u8::from_str_radix(&l, 16).unwrap(); 14 | } 15 | 16 | output 17 | } 18 | 19 | pub fn load_inputs(string: &str) -> [u8; 255] { 20 | let mut output: [u8; 255] = [0x00; 255]; 21 | 22 | if string == "" { 23 | return output 24 | } 25 | 26 | for (num, line) in string.split(" ").enumerate() { 27 | output[num] = u8::from_str_radix(&line, 16).unwrap(); 28 | } 29 | 30 | output 31 | } 32 | 33 | pub fn save_memory(filepath: &str, memory: [u8; 255]) { 34 | let mut f = File::create(filepath).expect("Error!"); 35 | 36 | let mut content_placeholder: Vec> = vec![vec![]; 255]; 37 | 38 | for (num, line) in memory.iter().enumerate() { 39 | content_placeholder[num] = format!("{:02X?}\n", line).as_bytes().to_vec(); 40 | } 41 | 42 | let content_flatten: Vec = content_placeholder 43 | .iter() 44 | .flat_map(|array| array.iter()) 45 | .cloned() 46 | .collect(); 47 | 48 | f.write_all(content_flatten.as_slice()).expect("Error while saving!"); 49 | } 50 | -------------------------------------------------------------------------------- /ndp/example.ndp: -------------------------------------------------------------------------------- 1 | E0 2 | 0A 3 | D0 4 | FF 5 | 00 6 | 00 7 | 00 8 | 00 9 | 00 10 | 00 11 | 00 12 | 00 13 | 00 14 | 00 15 | 00 16 | 00 17 | 00 18 | 00 19 | 00 20 | 00 21 | 00 22 | 00 23 | 00 24 | 00 25 | 00 26 | 00 27 | 00 28 | 00 29 | 00 30 | 00 31 | 00 32 | 00 33 | 00 34 | 00 35 | 00 36 | 00 37 | 00 38 | 00 39 | 00 40 | 00 41 | 00 42 | 00 43 | 00 44 | 00 45 | 00 46 | 00 47 | 00 48 | 00 49 | 00 50 | 00 51 | 00 52 | 00 53 | 00 54 | 00 55 | 00 56 | 00 57 | 00 58 | 00 59 | 00 60 | 00 61 | 00 62 | 00 63 | 00 64 | 00 65 | 00 66 | 00 67 | 00 68 | 00 69 | 00 70 | 00 71 | 00 72 | 00 73 | 00 74 | 00 75 | 00 76 | 00 77 | 00 78 | 00 79 | 00 80 | 00 81 | 00 82 | 00 83 | 00 84 | 00 85 | 00 86 | 00 87 | 00 88 | 00 89 | 00 90 | 00 91 | 00 92 | 00 93 | 00 94 | 00 95 | 00 96 | 00 97 | 00 98 | 00 99 | 00 100 | 00 101 | 00 102 | 00 103 | 00 104 | 00 105 | 00 106 | 00 107 | 00 108 | 00 109 | 00 110 | 00 111 | 00 112 | 00 113 | 00 114 | 00 115 | 00 116 | 00 117 | 00 118 | 00 119 | 00 120 | 00 121 | 00 122 | 00 123 | 00 124 | 00 125 | 00 126 | 00 127 | 00 128 | 00 129 | 00 130 | 00 131 | 00 132 | 00 133 | 00 134 | 00 135 | 00 136 | 00 137 | 00 138 | 00 139 | 00 140 | 00 141 | 00 142 | 00 143 | 00 144 | 00 145 | 00 146 | 00 147 | 00 148 | 00 149 | 00 150 | 00 151 | 00 152 | 00 153 | 00 154 | 00 155 | 00 156 | 00 157 | 00 158 | 00 159 | 00 160 | 00 161 | 00 162 | 00 163 | 00 164 | 00 165 | 00 166 | 00 167 | 00 168 | 00 169 | 00 170 | 00 171 | 00 172 | 00 173 | 00 174 | 00 175 | 00 176 | 00 177 | 00 178 | 00 179 | 00 180 | 00 181 | 00 182 | 00 183 | 00 184 | 00 185 | 00 186 | 00 187 | 00 188 | 00 189 | 00 190 | 00 191 | 00 192 | 00 193 | 00 194 | 00 195 | 00 196 | 00 197 | 00 198 | 00 199 | 00 200 | 00 201 | 00 202 | 00 203 | 00 204 | 00 205 | 00 206 | 00 207 | 00 208 | 00 209 | 00 210 | 00 211 | 00 212 | 00 213 | 00 214 | 00 215 | 00 216 | 00 217 | 00 218 | 00 219 | 00 220 | 00 221 | 00 222 | 00 223 | 00 224 | 00 225 | 00 226 | 00 227 | 00 228 | 00 229 | 00 230 | 00 231 | 00 232 | 00 233 | 00 234 | 00 235 | 00 236 | 00 237 | 00 238 | 00 239 | 00 240 | 00 241 | 00 242 | 00 243 | 00 244 | 00 245 | 00 246 | 00 247 | 00 248 | 00 249 | 00 250 | 00 251 | 00 252 | 00 253 | 00 254 | 00 255 | 00 256 | -------------------------------------------------------------------------------- /src/state/mod.rs: -------------------------------------------------------------------------------- 1 | #[cfg(test)] 2 | mod tests; 3 | 4 | pub mod operator; 5 | pub mod memory_line; 6 | use state::operator::Operator; 7 | use state::operator::get_operator; 8 | use state::memory_line::LineKind; 9 | use state::memory_line::MemoryLine; 10 | use state::memory_line::MEMORY_LINE_BLANK; 11 | 12 | pub struct State { 13 | pub memory: [u8; 255], 14 | pub pc: usize, 15 | pub ac: u8, 16 | inputs: [u8; 255], 17 | pub halt: bool, 18 | pub output: [u8; 40], 19 | } 20 | 21 | impl State { 22 | pub fn new(memory: [u8; 255], inputs: [u8; 255]) -> State { 23 | State { 24 | memory, 25 | pc: 0, 26 | ac: 0, 27 | inputs, 28 | halt: false, 29 | output: [0x00; 40], 30 | } 31 | } 32 | 33 | pub fn next_tick(&self) -> State { 34 | let operator = self.fetch_operator(); 35 | 36 | let operator_argument: u8 = if operator.requires_arg { 37 | self.memory[self.pc + 1] 38 | } else { 39 | 0 40 | }; 41 | 42 | let mut new_state = self.execute_operator(operator, operator_argument); 43 | 44 | if new_state.pc >= 255 { 45 | new_state.halt = true; 46 | } 47 | 48 | new_state 49 | } 50 | 51 | fn fetch_operator(&self) -> Operator { 52 | get_operator(&self.memory[self.pc]) 53 | } 54 | 55 | fn execute_operator(&self, operator: Operator, operator_argument: u8) -> State { 56 | (operator.run)(self, operator_argument) 57 | } 58 | 59 | pub fn play(&mut self) { 60 | while self.halt == false { 61 | *self = self.next_tick(); 62 | } 63 | } 64 | 65 | pub fn list_operators(&self) -> std::vec::Vec { 66 | let mut output: Vec = vec![MEMORY_LINE_BLANK; 256]; 67 | let mut line_kind = LineKind::Operator; 68 | 69 | for (num, memory) in self.memory.iter().enumerate() { 70 | let operator = get_operator(memory); 71 | output[num] = MemoryLine { 72 | operator, 73 | value: *memory, 74 | kind: line_kind, 75 | }; 76 | 77 | match line_kind { 78 | LineKind::Operator => { 79 | if operator.requires_arg { 80 | line_kind = LineKind::Argument; 81 | } 82 | }, 83 | LineKind::Argument => { 84 | line_kind = LineKind::Operator; 85 | }, 86 | } 87 | } 88 | 89 | output 90 | } 91 | } 92 | 93 | -------------------------------------------------------------------------------- /src/ui/status.rs: -------------------------------------------------------------------------------- 1 | use state::State; 2 | use ui::uistate::UIState; 3 | use ui::uistate::BlockLists; 4 | use ui::uistate::ListState; 5 | use ui::list_action::ListActions; 6 | use ui::tui::backend::Backend; 7 | use ui::tui::widgets::{Widget, Block, Borders, List, Text}; 8 | use ui::tui::layout::Rect; 9 | use ui::tui::Frame; 10 | use std::fmt::UpperHex; 11 | 12 | pub const TOTAL_LINES: usize = 3; 13 | 14 | enum LinesType { 15 | AC, 16 | PC, 17 | Halt, 18 | } 19 | 20 | fn map_index_to_line_type(index: usize) -> LinesType { 21 | match index { 22 | 0 => LinesType::AC, 23 | 1 => LinesType::PC, 24 | 2 => LinesType::Halt, 25 | _ => panic!("Unknown status index line"), 26 | } 27 | } 28 | 29 | fn format_line(name: &str, position: usize, uistate: &UIState, value: T) -> [String; 2] 30 | where 31 | T: UpperHex 32 | { 33 | let is_the_selected_line = match uistate.block_selected { 34 | BlockLists::Status => position == uistate.current_list().current_line, 35 | _ => false, 36 | }; 37 | 38 | let line_name = format!("{}", name); 39 | let line_value = if is_the_selected_line && uistate.is_typing { 40 | format!("0x{}_", uistate.typing_char.unwrap()) 41 | } else { 42 | format!("{:#04X}", value) 43 | }; 44 | 45 | [line_name, line_value] 46 | } 47 | 48 | fn format_line_bool(name: &str, value: bool) -> [String; 2] 49 | { 50 | let line_name = format!("{}", name); 51 | let line_value = format!("{}", value); 52 | [line_name, line_value] 53 | } 54 | 55 | pub fn draw(uistate: &UIState, state: &State, f: &mut Frame, area: Rect) 56 | where 57 | B: Backend, 58 | { 59 | let list_state = uistate.current_list(); 60 | 61 | let lines: [[String; 2]; TOTAL_LINES] = [ 62 | format_line("AC ", 0, uistate, state.ac), 63 | format_line("PC ", 1, uistate, state.pc), 64 | format_line_bool("HALT", state.halt), 65 | ]; 66 | 67 | let list = lines 68 | .iter() 69 | .enumerate() 70 | .map(|(i, [name, value])| 71 | match uistate.block_selected { 72 | BlockLists::Status => if i == list_state.current_line { 73 | Text::raw(format!(" -> {} {}", name, value)) 74 | } else { 75 | Text::raw(format!(" {} {}", name, value)) 76 | } 77 | 78 | _ => Text::raw(format!(" {} {}", name, value)) 79 | } 80 | ); 81 | 82 | List::new(list.into_iter()) 83 | .block(Block::default().borders(Borders::ALL).title(" Status ")) 84 | .render(f, area); 85 | } 86 | 87 | // Actions 88 | pub const STATUS_ACTIONS: ListActions = ListActions { 89 | move_up_cursor_handle: |list_state: &mut ListState| { 90 | if list_state.current_line == 0 { 91 | list_state.current_line = TOTAL_LINES - 1; 92 | return 93 | } 94 | 95 | list_state.current_line -= 1; 96 | }, 97 | 98 | move_down_cursor_handle: |list_state: &mut ListState| { 99 | list_state.current_line = (list_state.current_line + 1) % TOTAL_LINES; 100 | }, 101 | 102 | set_type_u8_handle: |state: &mut State, line_number: usize, u8_value: u8| { 103 | match map_index_to_line_type(line_number) { 104 | LinesType::AC => state.ac = u8_value, 105 | LinesType::PC => state.pc = u8_value as usize, 106 | LinesType::Halt => {}, 107 | } 108 | }, 109 | 110 | select_line_handle: |state: &mut State, line_number: usize| { 111 | match map_index_to_line_type(line_number) { 112 | LinesType::AC => state.ac = if state.ac == 0xFF { 0 } else { state.ac + 1 }, 113 | LinesType::PC => state.pc = if state.pc == 0xFF { 0 } else { state.pc + 1 }, 114 | LinesType::Halt => state.halt = !state.halt, 115 | } 116 | }, 117 | }; 118 | -------------------------------------------------------------------------------- /src/ui/mod.rs: -------------------------------------------------------------------------------- 1 | extern crate termion; 2 | extern crate tui; 3 | mod memory_list; 4 | mod status; 5 | mod uistate; 6 | mod action; 7 | mod output; 8 | mod list_action; 9 | mod references; 10 | use ui::uistate::BlockLists; 11 | use ui::uistate::ListState; 12 | use ui::uistate::UIState; 13 | use state::State; 14 | use std::io::stdout; 15 | use ui::tui::Terminal; 16 | use ui::tui::backend::TermionBackend; 17 | use ui::tui::layout::{Layout, Constraint, Direction}; 18 | use ui::termion::clear; 19 | use ui::termion::raw::IntoRawMode; 20 | 21 | pub fn draw_screen(filepath: &str, state: State) -> Result<(), Box> { 22 | let stdout_raw_mode = stdout().into_raw_mode()?; 23 | let backend = TermionBackend::new(stdout_raw_mode); 24 | let mut terminal = Terminal::new(backend)?; 25 | terminal.hide_cursor()?; 26 | 27 | let size = terminal.size()?; 28 | 29 | let chunks_main_and_references = Layout::default() 30 | .direction(Direction::Vertical) 31 | .constraints([Constraint::Min(1), Constraint::Length(4)].as_ref()) 32 | .split(size); 33 | let chunks_main = Layout::default() 34 | .direction(Direction::Horizontal) 35 | .constraints([Constraint::Percentage(20), Constraint::Percentage(40), Constraint::Percentage(40)].as_ref()) 36 | .split(chunks_main_and_references[0]); 37 | let chunks_left_bar = Layout::default() 38 | .direction(Direction::Vertical) 39 | .constraints([Constraint::Length((status::TOTAL_LINES + 2) as u16), Constraint::Min(1)].as_ref()) 40 | .split(chunks_main[0]); 41 | let references_chunk = chunks_main_and_references[1]; 42 | let status_chunk = chunks_left_bar[0]; 43 | let output_chunk = chunks_left_bar[1]; 44 | let memory_list_operators_chunk = chunks_main[1]; 45 | let memory_list_variables_chunk = chunks_main[2]; 46 | 47 | let memory_list_operators_count_line = (memory_list_operators_chunk.height - 3) as usize; 48 | let memory_list_variables_count_line = (memory_list_variables_chunk.height - 3) as usize; 49 | 50 | let mut uistate = UIState { 51 | filepath: filepath.to_string(), 52 | block_selected: BlockLists::Operators, 53 | status_block: ListState { 54 | current_line: 0, 55 | first_line: 0, 56 | last_line: status::TOTAL_LINES, 57 | handle_action: Box::new(status::STATUS_ACTIONS), 58 | }, 59 | memory_list_operators: ListState { 60 | current_line: 0, 61 | first_line: 0, 62 | last_line: memory_list_operators_count_line, 63 | handle_action: Box::new(memory_list::MEMORY_LIST_ACTIONS), 64 | }, 65 | memory_list_variables: ListState { 66 | current_line: 128, 67 | first_line: 128, 68 | last_line: 128 + memory_list_variables_count_line, 69 | handle_action: Box::new(memory_list::MEMORY_LIST_ACTIONS), 70 | }, 71 | is_typing: false, 72 | typing_char: None, 73 | quit: false, 74 | }; 75 | 76 | let mut current_state = state; 77 | 78 | println!("{}", clear::All); 79 | 80 | loop { 81 | terminal.draw(|mut f| { 82 | status::draw(&uistate, ¤t_state, &mut f, status_chunk); 83 | output::draw(¤t_state, &mut f, output_chunk); 84 | memory_list::draw( 85 | &uistate, 86 | ¤t_state, 87 | memory_list::MemoryListKind::Operators, 88 | &mut f, 89 | memory_list_operators_chunk 90 | ); 91 | memory_list::draw( 92 | &uistate, 93 | ¤t_state, 94 | memory_list::MemoryListKind::Variables, 95 | &mut f, 96 | memory_list_variables_chunk 97 | ); 98 | references::draw(&mut f, references_chunk); 99 | })?; 100 | 101 | let input = action::wait_for_valid_input(); 102 | action::execute(input, &mut current_state, &mut uistate); 103 | 104 | if uistate.quit { 105 | break Ok(()) 106 | } 107 | } 108 | } 109 | 110 | -------------------------------------------------------------------------------- /src/ui/action.rs: -------------------------------------------------------------------------------- 1 | use io; 2 | use state::State; 3 | use ui::uistate::BlockLists; 4 | use ui::uistate::UIState; 5 | use std::io::stdin; 6 | use ui::termion::input::TermRead; 7 | use ui::termion::event::Key; 8 | 9 | pub enum Input { 10 | NextTick, 11 | Play, 12 | ToggleHalt, 13 | MoveUpCursor, 14 | MoveDownCursor, 15 | ChangeBlockSelected, 16 | TypeChar(char), 17 | CancelType, 18 | SetPC, 19 | Save, 20 | Quit, 21 | } 22 | 23 | pub fn wait_for_valid_input() -> Input { 24 | loop { for c in stdin().keys() { 25 | match c.unwrap() { 26 | Key::Char('n') => return Input::NextTick, 27 | Key::Char('p') => return Input::Play, 28 | Key::Char('h') => return Input::ToggleHalt, 29 | Key::Up => return Input::MoveUpCursor, 30 | Key::Down => return Input::MoveDownCursor, 31 | Key::Char('\t') => return Input::ChangeBlockSelected, 32 | Key::Esc => return Input::CancelType, 33 | Key::Char(' ') => return Input::SetPC, 34 | Key::Char('s') => return Input::Save, 35 | Key::Char('q') => return Input::Quit, 36 | Key::Char(key) => { 37 | match key { 38 | c @ '0'...'9' | c @ 'A'...'F' | c @ 'a'...'f' => return Input::TypeChar(c), 39 | _ => {} 40 | } 41 | }, 42 | _ => {}, 43 | }; 44 | } } 45 | } 46 | 47 | pub fn execute(input: Input, state: &mut State, uistate: &mut UIState) { 48 | match input { 49 | Input::NextTick => next_tick_handle(state), 50 | Input::Play => play_handle(state), 51 | Input::ToggleHalt => toggle_halt_handle(state), 52 | Input::MoveUpCursor => move_up_cursor_handle(uistate), 53 | Input::MoveDownCursor => move_down_cursor_handle(uistate), 54 | Input::ChangeBlockSelected => change_block_selected_handle(uistate), 55 | Input::TypeChar(key) => type_char_handle(key, state, uistate), 56 | Input::CancelType => cancel_type_handle(uistate), 57 | Input::SetPC => set_pc_handle(state, uistate), 58 | Input::Save => save_handle(state, uistate), 59 | Input::Quit => quit_handle(uistate), 60 | } 61 | } 62 | 63 | fn next_tick_handle(state: &mut State) { 64 | *state = state.next_tick(); 65 | } 66 | 67 | fn play_handle(state: &mut State) { 68 | state.play(); 69 | } 70 | 71 | fn toggle_halt_handle(state: &mut State) { 72 | state.halt = !state.halt; 73 | } 74 | 75 | fn move_up_cursor_handle(uistate: &mut UIState) { 76 | (uistate.current_list().handle_action.move_up_cursor_handle)(uistate.mutable_current_list()); 77 | } 78 | 79 | fn move_down_cursor_handle(uistate: &mut UIState) { 80 | (uistate.current_list().handle_action.move_down_cursor_handle)(uistate.mutable_current_list()); 81 | } 82 | 83 | fn change_block_selected_handle(uistate: &mut UIState) { 84 | match uistate.block_selected { 85 | BlockLists::Status => uistate.block_selected = BlockLists::Operators, 86 | BlockLists::Operators => uistate.block_selected = BlockLists::Variables, 87 | BlockLists::Variables => uistate.block_selected = BlockLists::Status, 88 | } 89 | } 90 | 91 | fn type_char_handle(key: char, state: &mut State, uistate: &mut UIState) { 92 | if uistate.is_typing { 93 | let s = format!("{}{}", uistate.typing_char.unwrap(), key).to_string(); 94 | let u8_typed = u8::from_str_radix(&s, 16).unwrap(); 95 | (uistate.current_list().handle_action.set_type_u8_handle)(state, uistate.current_list().current_line, u8_typed); 96 | 97 | uistate.is_typing = false; 98 | uistate.typing_char = None; 99 | } else { 100 | uistate.is_typing = true; 101 | uistate.typing_char = Some(key); 102 | } 103 | } 104 | 105 | fn cancel_type_handle(uistate: &mut UIState) { 106 | uistate.is_typing = false; 107 | uistate.typing_char = None; 108 | } 109 | 110 | fn set_pc_handle(state: &mut State, uistate: &mut UIState) { 111 | (uistate.current_list().handle_action.select_line_handle)(state, uistate.current_list().current_line); 112 | } 113 | 114 | fn save_handle(state: &mut State, uistate: &mut UIState) { 115 | io::save_memory(&uistate.filepath, state.memory); 116 | } 117 | 118 | fn quit_handle(uistate: &mut UIState) { 119 | uistate.quit = true; 120 | } 121 | 122 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # rust-neander 2 | > Do you want the easiest way to learning virtual machine and assembly? You've found it! 3 | 4 | **rust-neander** is a virtual machine that runs assembly Neander and Neander+! 5 | 6 | They are very didactic hypothetical machines. It is useful if you want to learn more about how a CPU works and how to write a simple assembly code, because Neander and Neander+ were developed in order to be simple for beginners. You will learn these languages and hypothetical machines in a few minutes! 7 | 8 | ## Neander hypothetic machine 9 | 10 | - memory size: 256 11 | - 8-bit to data width and addresses 12 | - datas are represented using complement two (if the bit 7 is turn on, the number is negative) 13 | - one accumalator with 8-bit (`AC`) 14 | - one program counter with 8-bit (`PC`) 15 | - one status register with 2 condition codes: negative (`N`) and zero (`Z`) 16 | 17 | ### Neander+ hypothetic machine 18 | 19 | This machine is the same of the above plus: 20 | 21 | - 256 registers to input data (accessible with the `IN` operator) 22 | 23 | ## Assembly 24 | 25 | Both assembly have few operators. Neander+ is compatible with Neander and it has a little more operators. 26 | 27 | ### Neander 28 | 29 | | Hex code | Instruction | Description | 30 | |-------------|---------------|------------------------------------------------------------------------| 31 | | `00 ... 1F` | `NOP` | No operation | 32 | | `10 ... 1F` | `STA address` | Store the value from `AC` at the address | 33 | | `20 ... 2F` | `LDA address` | Load at `AC` the value from the address | 34 | | `30 ... 3F` | `ADD address` | Add the `AC` plus the value from the address | 35 | | `40 ... 4F` | `OR address` | Use the `OR` binary operator with `AC` and the value from the address | 36 | | `50 ... 5F` | `AND address` | Use the `AND` binary operator with `AC` and the value from the address | 37 | | `60 ... 6F` | `NOT` | Use the `NOT` binary operator with `AC` | 38 | | `80 ... 8F` | `JMP value` | Load at `PC` the value | 39 | | `90 ... 9F` | `JN value` | If `AC` is a negative value, load at `PC` the value | 40 | | `A0 ... AF` | `JZ value` | If `AC` is zero, load at `PC` the value | 41 | | `F0 ... FF` | `HLT` | Finish the execution | 42 | 43 | If it tries to execute an nonexistent operator, such as `7A`, it will raise an error. 44 | 45 | Example to sum 2 and 3: 46 | 47 | | Address | Hex code | 48 | | ------- | -------- | 49 | | 00 | 20 | 50 | | 01 | 05 | 51 | | 02 | 30 | 52 | | 03 | 06 | 53 | | 04 | FF | 54 | | 05 | 02 | 55 | | 06 | 03 | 56 | 57 | ### Neander+ 58 | 59 | Neander+ has five more operators than Neander: `SUB`, `JNZ`, `IN`, `OUT`, `LDI`. The remaining operators are the same as the ones Neander has. 60 | 61 | Neander+ was *inspirated* in [Neander-X](http://www.dcc.ufrj.br/~gabriel/neander.php) 62 | 63 | | Hex code | Instruction | Description | 64 | |-------------|---------------|-----------------------------------------------| 65 | | `70 ... 7F` | `SUB address` | Sub the `AC` with the value from the address | 66 | | `B0 ... BF` | `JNZ value` | If `AC` is not zero, load at `PC` the value | 67 | | `C0 ... CF` | `IN index` | Load at `AC` the value the input at the index | 68 | | `D0 ... DF` | `OUT` | Copy the `AC` at the out | 69 | | `E0 ... EF` | `LDI value` | Store at `AC` the value | 70 | 71 | Example to duplicate the value at first position of the input and output the result: 72 | 73 | | Address | Hex code | 74 | | ------- | -------- | 75 | | 00 | C0 | 76 | | 01 | 00 | 77 | | 02 | 10 | 78 | | 03 | 07 | 79 | | 04 | 30 | 80 | | 05 | 07 | 81 | | 06 | FF | 82 | | 07 | 00 | 83 | 84 | ## How to execute 85 | 86 | To execute your code (for example, that you wrote at `ndp/example.ndp` file), you can execute it using: 87 | 88 | ``` 89 | > cargo run ndp/example.ndp 90 | ``` 91 | 92 | And, to set the inputs used in `IN` instruction: 93 | 94 | ``` 95 | > cargo run ndp/example.ndp -i="02 0A" 96 | ``` 97 | -------------------------------------------------------------------------------- /src/state/operator.rs: -------------------------------------------------------------------------------- 1 | use state::State; 2 | 3 | #[derive(Copy, Clone, Debug)] 4 | pub enum OpCode { 5 | NOP, 6 | STA, 7 | LDA, 8 | ADD, 9 | OR, 10 | AND, 11 | NOT, 12 | SUB, 13 | JMP, 14 | JN, 15 | JZ, 16 | JNZ, 17 | IN, 18 | OUT, 19 | LDI, 20 | HLT, 21 | } 22 | 23 | #[derive(Copy, Clone)] 24 | pub struct Operator { 25 | pub mnemonic: OpCode, 26 | pub requires_arg: bool, 27 | pub run: fn(&State, u8) -> State, 28 | } 29 | 30 | pub const NOP: Operator = Operator { 31 | mnemonic: OpCode::NOP, 32 | requires_arg: false, 33 | run: |state, _| { 34 | State { 35 | pc: state.pc + 1, 36 | ..*state 37 | } 38 | }, 39 | }; 40 | 41 | pub const STA: Operator = Operator { 42 | mnemonic: OpCode::STA, 43 | requires_arg: true, 44 | run: |state, argument| { 45 | let mut memory = state.memory; 46 | memory[argument as usize] = state.ac; 47 | 48 | State { 49 | pc: state.pc + 2, 50 | memory, 51 | ..*state 52 | } 53 | } 54 | }; 55 | 56 | pub const LDA: Operator = Operator { 57 | mnemonic: OpCode::LDA, 58 | requires_arg: true, 59 | run: |state, argument| { 60 | State { 61 | pc: state.pc + 2, 62 | ac: state.memory[argument as usize], 63 | ..*state 64 | } 65 | } 66 | }; 67 | 68 | pub const ADD: Operator = Operator { 69 | mnemonic: OpCode::ADD, 70 | requires_arg: true, 71 | run: |state, argument| { 72 | let memory_value = state.memory[argument as usize]; 73 | 74 | State { 75 | pc: state.pc + 2, 76 | ac: state.ac + memory_value, 77 | ..*state 78 | } 79 | } 80 | }; 81 | 82 | pub const OR: Operator = Operator { 83 | mnemonic: OpCode::OR, 84 | requires_arg: true, 85 | run: |state, argument| { 86 | let memory_value = state.memory[argument as usize]; 87 | 88 | State { 89 | pc: state.pc + 2, 90 | ac: memory_value | state.ac, 91 | ..*state 92 | } 93 | } 94 | }; 95 | 96 | pub const AND: Operator = Operator { 97 | mnemonic: OpCode::AND, 98 | requires_arg: true, 99 | run: |state, argument| { 100 | let memory_value = state.memory[argument as usize]; 101 | 102 | State { 103 | pc: state.pc + 2, 104 | ac: memory_value & state.ac, 105 | ..*state 106 | } 107 | } 108 | }; 109 | 110 | pub const NOT: Operator = Operator { 111 | mnemonic: OpCode::NOT, 112 | requires_arg: false, 113 | run: |state, _| { 114 | State { 115 | pc: state.pc + 1, 116 | ac: !state.ac, 117 | ..*state 118 | } 119 | } 120 | }; 121 | 122 | pub const SUB: Operator = Operator { 123 | mnemonic: OpCode::SUB, 124 | requires_arg: true, 125 | run: |state, argument| { 126 | let memory_value = state.memory[argument as usize]; 127 | 128 | State { 129 | pc: state.pc + 2, 130 | ac: state.ac - memory_value, 131 | ..*state 132 | } 133 | } 134 | }; 135 | 136 | pub const JMP: Operator = Operator { 137 | mnemonic: OpCode::JMP, 138 | requires_arg: true, 139 | run: |state, argument| { 140 | State { 141 | pc: argument as usize, 142 | ..*state 143 | } 144 | } 145 | }; 146 | 147 | pub const JN: Operator = Operator { 148 | mnemonic: OpCode::JN, 149 | requires_arg: true, 150 | run: |state, argument| { 151 | let next_pc = if state.ac >= 0b1000000 { 152 | argument as usize 153 | } else { 154 | state.pc + 2 155 | }; 156 | 157 | State { 158 | pc: next_pc, 159 | ..*state 160 | } 161 | } 162 | }; 163 | 164 | pub const JZ: Operator = Operator { 165 | mnemonic: OpCode::JZ, 166 | requires_arg: true, 167 | run: |state, argument| { 168 | let next_pc = if state.ac == 0 { 169 | argument as usize 170 | } else { 171 | state.pc + 2 172 | }; 173 | 174 | State { 175 | pc: next_pc, 176 | ..*state 177 | } 178 | } 179 | }; 180 | 181 | pub const JNZ: Operator = Operator { 182 | mnemonic: OpCode::JNZ, 183 | requires_arg: true, 184 | run: |state, argument| { 185 | let next_pc = if state.ac != 0 { 186 | argument as usize 187 | } else { 188 | state.pc + 2 189 | }; 190 | 191 | State { 192 | pc: next_pc, 193 | ..*state 194 | } 195 | } 196 | }; 197 | 198 | pub const IN: Operator = Operator { 199 | mnemonic: OpCode::IN, 200 | requires_arg: true, 201 | run: |state, argument| { 202 | State { 203 | pc: state.pc + 2, 204 | ac: state.inputs[argument as usize], 205 | ..*state 206 | } 207 | } 208 | }; 209 | 210 | pub const OUT: Operator = Operator { 211 | mnemonic: OpCode::OUT, 212 | requires_arg: false, 213 | run: |state, _| { 214 | let mut output = [0x00; 40]; 215 | output[0] = state.ac; 216 | for (i, value) in state.output[..39].iter().enumerate() { 217 | output[i + 1] = *value; 218 | } 219 | 220 | State { 221 | pc: state.pc + 1, 222 | output, 223 | ..*state 224 | } 225 | } 226 | }; 227 | 228 | pub const LDI: Operator = Operator { 229 | mnemonic: OpCode::LDI, 230 | requires_arg: true, 231 | run: |state, argument| { 232 | State { 233 | pc: state.pc + 2, 234 | ac: argument, 235 | ..*state 236 | } 237 | } 238 | }; 239 | 240 | pub const HLT: Operator = Operator { 241 | mnemonic: OpCode::HLT, 242 | requires_arg: false, 243 | run: |state, _| { 244 | State { 245 | pc: state.pc + 1, 246 | halt: true, 247 | ..*state 248 | } 249 | }, 250 | }; 251 | 252 | pub fn get_operator(code: &u8) -> Operator { 253 | match code { 254 | 0x00 ... 0x0F => NOP, 255 | 0x10 ... 0x1F => STA, 256 | 0x20 ... 0x2F => LDA, 257 | 0x30 ... 0x3F => ADD, 258 | 0x40 ... 0x4F => OR, 259 | 0x50 ... 0x5F => AND, 260 | 0x60 ... 0x6F => NOT, 261 | 0x70 ... 0x7F => SUB, 262 | 0x80 ... 0x8F => JMP, 263 | 0x90 ... 0x9F => JN, 264 | 0xA0 ... 0xAF => JZ, 265 | 0xB0 ... 0xBF => JNZ, 266 | 0xC0 ... 0xCF => IN, 267 | 0xD0 ... 0xDF => OUT, 268 | 0xE0 ... 0xEF => LDI, 269 | 0xF0 ... 0xFF => HLT, 270 | opcode => panic!("Unknow OpCode: {:#04X}", opcode), 271 | } 272 | } 273 | -------------------------------------------------------------------------------- /src/ui/memory_list.rs: -------------------------------------------------------------------------------- 1 | use state::State; 2 | use state::operator::Operator; 3 | use state::memory_line::LineKind; 4 | use state::memory_line::MemoryLine; 5 | use ui::uistate::BlockLists; 6 | use ui::uistate::ListState; 7 | use ui::uistate::UIState; 8 | use ui::list_action::ListActions; 9 | use ui::tui::backend::Backend; 10 | use ui::tui::widgets::{Widget, Block, Borders, Text, List}; 11 | use ui::tui::layout::Rect; 12 | use ui::tui::Frame; 13 | 14 | pub enum MemoryListKind { 15 | Operators, 16 | Variables, 17 | } 18 | 19 | fn format_operator_line(position: usize, operator: &Operator, operator_memory: &u8) -> String { 20 | format!( 21 | "{:#04X}: {:?} {:#04X}", 22 | position, operator.mnemonic, operator_memory, 23 | ) 24 | } 25 | 26 | fn format_argument_line(position: usize, argument: &u8) -> String { 27 | format!( 28 | "{:#04X}: {:#04X}", 29 | position, argument, 30 | ) 31 | } 32 | 33 | fn format_variable_line(position: usize, value: &u8) -> String { 34 | format!( 35 | "{:#04X}: {:#04X}", 36 | position, value, 37 | ) 38 | } 39 | 40 | fn format_typing_operator_line(position: usize, typing: char) -> String { 41 | format!( 42 | "{:#04X}: 0x{}_", 43 | position, typing, 44 | ) 45 | } 46 | 47 | fn format_typing_argument_line(position: usize, typing: char) -> String { 48 | format!( 49 | "{:#04X}: 0x{}_", 50 | position, typing, 51 | ) 52 | } 53 | 54 | fn format_typing_variable_line(position: usize, typing: char) -> String { 55 | format!( 56 | "{:#04X}: 0x{}_", 57 | position, typing, 58 | ) 59 | } 60 | 61 | fn add_selected_line_arrow( 62 | uistate: &UIState, 63 | memory_list_kind: &MemoryListKind, 64 | position: usize, 65 | line: String 66 | ) -> String { 67 | if position != uistate.current_list().current_line { 68 | return format!(" {}", line) 69 | } 70 | 71 | match (&uistate.block_selected, memory_list_kind) { 72 | (BlockLists::Operators, MemoryListKind::Operators) => format!(" -> {}", line), 73 | (BlockLists::Variables, MemoryListKind::Variables) => format!(" -> {}", line), 74 | _ => format!(" {}", line), 75 | } 76 | } 77 | 78 | fn format_memory( 79 | uistate: &UIState, 80 | state: &State, 81 | memory_line: &MemoryLine, 82 | position: usize, 83 | ) -> String { 84 | let is_the_selected_line = match uistate.block_selected { 85 | BlockLists::Operators => position == uistate.current_list().current_line, 86 | _ => false, 87 | }; 88 | let is_the_running_line = position == state.pc; 89 | 90 | let line = match (memory_line.kind, is_the_selected_line, uistate.is_typing) { 91 | (LineKind::Operator, true, true) => format_typing_operator_line(position, uistate.typing_char.unwrap()), 92 | (LineKind::Argument, true, true) => format_typing_argument_line(position, uistate.typing_char.unwrap()), 93 | (LineKind::Operator, _, _) => format_operator_line(position, &memory_line.operator, &memory_line.value), 94 | (LineKind::Argument, _, _) => format_argument_line(position, &memory_line.value), 95 | }; 96 | 97 | if is_the_running_line { 98 | format!("{} (PC)", line) 99 | } else { 100 | line 101 | } 102 | } 103 | 104 | fn format_variable( 105 | uistate: &UIState, 106 | memory_line: &MemoryLine, 107 | position: usize, 108 | ) -> String { 109 | let is_the_selected_line = match uistate.block_selected { 110 | BlockLists::Variables => position == uistate.current_list().current_line, 111 | _ => false, 112 | }; 113 | 114 | match (is_the_selected_line, uistate.is_typing) { 115 | (true, true) => format_typing_variable_line(position, uistate.typing_char.unwrap()), 116 | (_, _) => format_variable_line(position, &memory_line.value), 117 | } 118 | } 119 | 120 | pub fn draw( 121 | uistate: &UIState, 122 | current_state: &State, 123 | memory_list_kind: MemoryListKind, 124 | f: &mut Frame, 125 | area: Rect 126 | ) where B: Backend, 127 | { 128 | let memory_list_state: &ListState; 129 | let format: Box String>; 130 | match memory_list_kind { 131 | MemoryListKind::Operators => { 132 | memory_list_state = &uistate.memory_list_operators; 133 | format = Box::new(|(i, memory_line)| { 134 | format_memory( 135 | uistate, 136 | current_state, 137 | memory_line, 138 | i + memory_list_state.first_line, 139 | ) 140 | }) 141 | }, 142 | 143 | MemoryListKind::Variables => { 144 | memory_list_state = &uistate.memory_list_variables; 145 | format = Box::new(|(i, memory_line)| { 146 | format_variable( 147 | uistate, 148 | memory_line, 149 | i + memory_list_state.first_line, 150 | ) 151 | }) 152 | }, 153 | } 154 | 155 | let list_operators = current_state.list_operators(); 156 | let list_operators_slice = &list_operators[memory_list_state.first_line..=memory_list_state.last_line]; 157 | 158 | let memory_str_table = list_operators_slice 159 | .iter() 160 | .enumerate() 161 | .map(|i| format(i)) 162 | .enumerate() 163 | .map(|(i, line)| 164 | add_selected_line_arrow( 165 | &uistate, 166 | &memory_list_kind, 167 | i + memory_list_state.first_line, 168 | line, 169 | ) 170 | ) 171 | .map(Text::raw); 172 | 173 | List::new(memory_str_table) 174 | .block(Block::default().borders(Borders::ALL).title(" Memory ")) 175 | .render(f, area); 176 | } 177 | 178 | // Actions 179 | pub const MEMORY_LIST_ACTIONS: ListActions = ListActions { 180 | move_up_cursor_handle: |list_state: &mut ListState| { 181 | if list_state.current_line == 0 { 182 | return; 183 | } 184 | 185 | list_state.current_line -= 1; 186 | 187 | if list_state.first_line + 3 > list_state.current_line && list_state.first_line > 0 { 188 | list_state.first_line -= 1; 189 | list_state.last_line -= 1; 190 | } 191 | }, 192 | 193 | move_down_cursor_handle: |list_state: &mut ListState| { 194 | if list_state.current_line == 0xFF { 195 | return; 196 | } 197 | 198 | list_state.current_line += 1; 199 | 200 | if list_state.last_line - 3 < list_state.current_line && list_state.last_line < 0xFF { 201 | list_state.first_line += 1; 202 | list_state.last_line += 1; 203 | } 204 | }, 205 | 206 | set_type_u8_handle: |state: &mut State, line_number: usize, u8_value: u8| { 207 | state.memory[line_number] = u8_value; 208 | }, 209 | 210 | select_line_handle: |state: &mut State, line_number: usize| { 211 | state.pc = line_number; 212 | }, 213 | }; 214 | -------------------------------------------------------------------------------- /Cargo.lock: -------------------------------------------------------------------------------- 1 | [[package]] 2 | name = "ansi_term" 3 | version = "0.11.0" 4 | source = "registry+https://github.com/rust-lang/crates.io-index" 5 | dependencies = [ 6 | "winapi 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", 7 | ] 8 | 9 | [[package]] 10 | name = "atty" 11 | version = "0.2.11" 12 | source = "registry+https://github.com/rust-lang/crates.io-index" 13 | dependencies = [ 14 | "libc 0.2.43 (registry+https://github.com/rust-lang/crates.io-index)", 15 | "termion 1.5.1 (registry+https://github.com/rust-lang/crates.io-index)", 16 | "winapi 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", 17 | ] 18 | 19 | [[package]] 20 | name = "bitflags" 21 | version = "1.0.4" 22 | source = "registry+https://github.com/rust-lang/crates.io-index" 23 | 24 | [[package]] 25 | name = "cassowary" 26 | version = "0.3.0" 27 | source = "registry+https://github.com/rust-lang/crates.io-index" 28 | 29 | [[package]] 30 | name = "cfg-if" 31 | version = "0.1.6" 32 | source = "registry+https://github.com/rust-lang/crates.io-index" 33 | 34 | [[package]] 35 | name = "clap" 36 | version = "2.32.0" 37 | source = "registry+https://github.com/rust-lang/crates.io-index" 38 | dependencies = [ 39 | "ansi_term 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)", 40 | "atty 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)", 41 | "bitflags 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)", 42 | "strsim 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)", 43 | "textwrap 0.10.0 (registry+https://github.com/rust-lang/crates.io-index)", 44 | "unicode-width 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", 45 | "vec_map 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)", 46 | "yaml-rust 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)", 47 | ] 48 | 49 | [[package]] 50 | name = "either" 51 | version = "1.5.0" 52 | source = "registry+https://github.com/rust-lang/crates.io-index" 53 | 54 | [[package]] 55 | name = "itertools" 56 | version = "0.7.8" 57 | source = "registry+https://github.com/rust-lang/crates.io-index" 58 | dependencies = [ 59 | "either 1.5.0 (registry+https://github.com/rust-lang/crates.io-index)", 60 | ] 61 | 62 | [[package]] 63 | name = "libc" 64 | version = "0.2.43" 65 | source = "registry+https://github.com/rust-lang/crates.io-index" 66 | 67 | [[package]] 68 | name = "log" 69 | version = "0.4.6" 70 | source = "registry+https://github.com/rust-lang/crates.io-index" 71 | dependencies = [ 72 | "cfg-if 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)", 73 | ] 74 | 75 | [[package]] 76 | name = "redox_syscall" 77 | version = "0.1.40" 78 | source = "registry+https://github.com/rust-lang/crates.io-index" 79 | 80 | [[package]] 81 | name = "redox_termios" 82 | version = "0.1.1" 83 | source = "registry+https://github.com/rust-lang/crates.io-index" 84 | dependencies = [ 85 | "redox_syscall 0.1.40 (registry+https://github.com/rust-lang/crates.io-index)", 86 | ] 87 | 88 | [[package]] 89 | name = "rust_neander" 90 | version = "0.1.0" 91 | dependencies = [ 92 | "clap 2.32.0 (registry+https://github.com/rust-lang/crates.io-index)", 93 | "termion 1.5.1 (registry+https://github.com/rust-lang/crates.io-index)", 94 | "tui 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", 95 | ] 96 | 97 | [[package]] 98 | name = "strsim" 99 | version = "0.7.0" 100 | source = "registry+https://github.com/rust-lang/crates.io-index" 101 | 102 | [[package]] 103 | name = "termion" 104 | version = "1.5.1" 105 | source = "registry+https://github.com/rust-lang/crates.io-index" 106 | dependencies = [ 107 | "libc 0.2.43 (registry+https://github.com/rust-lang/crates.io-index)", 108 | "redox_syscall 0.1.40 (registry+https://github.com/rust-lang/crates.io-index)", 109 | "redox_termios 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", 110 | ] 111 | 112 | [[package]] 113 | name = "textwrap" 114 | version = "0.10.0" 115 | source = "registry+https://github.com/rust-lang/crates.io-index" 116 | dependencies = [ 117 | "unicode-width 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", 118 | ] 119 | 120 | [[package]] 121 | name = "tui" 122 | version = "0.3.0" 123 | source = "registry+https://github.com/rust-lang/crates.io-index" 124 | dependencies = [ 125 | "bitflags 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)", 126 | "cassowary 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", 127 | "either 1.5.0 (registry+https://github.com/rust-lang/crates.io-index)", 128 | "itertools 0.7.8 (registry+https://github.com/rust-lang/crates.io-index)", 129 | "log 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)", 130 | "termion 1.5.1 (registry+https://github.com/rust-lang/crates.io-index)", 131 | "unicode-segmentation 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)", 132 | "unicode-width 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", 133 | ] 134 | 135 | [[package]] 136 | name = "unicode-segmentation" 137 | version = "1.2.1" 138 | source = "registry+https://github.com/rust-lang/crates.io-index" 139 | 140 | [[package]] 141 | name = "unicode-width" 142 | version = "0.1.5" 143 | source = "registry+https://github.com/rust-lang/crates.io-index" 144 | 145 | [[package]] 146 | name = "vec_map" 147 | version = "0.8.1" 148 | source = "registry+https://github.com/rust-lang/crates.io-index" 149 | 150 | [[package]] 151 | name = "winapi" 152 | version = "0.3.6" 153 | source = "registry+https://github.com/rust-lang/crates.io-index" 154 | dependencies = [ 155 | "winapi-i686-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", 156 | "winapi-x86_64-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", 157 | ] 158 | 159 | [[package]] 160 | name = "winapi-i686-pc-windows-gnu" 161 | version = "0.4.0" 162 | source = "registry+https://github.com/rust-lang/crates.io-index" 163 | 164 | [[package]] 165 | name = "winapi-x86_64-pc-windows-gnu" 166 | version = "0.4.0" 167 | source = "registry+https://github.com/rust-lang/crates.io-index" 168 | 169 | [[package]] 170 | name = "yaml-rust" 171 | version = "0.3.5" 172 | source = "registry+https://github.com/rust-lang/crates.io-index" 173 | 174 | [metadata] 175 | "checksum ansi_term 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ee49baf6cb617b853aa8d93bf420db2383fab46d314482ca2803b40d5fde979b" 176 | "checksum atty 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)" = "9a7d5b8723950951411ee34d271d99dddcc2035a16ab25310ea2c8cfd4369652" 177 | "checksum bitflags 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)" = "228047a76f468627ca71776ecdebd732a3423081fcf5125585bcd7c49886ce12" 178 | "checksum cassowary 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "df8670b8c7b9dae1793364eafadf7239c40d669904660c5960d74cfd80b46a53" 179 | "checksum cfg-if 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)" = "082bb9b28e00d3c9d39cc03e64ce4cea0f1bb9b3fde493f0cbc008472d22bdf4" 180 | "checksum clap 2.32.0 (registry+https://github.com/rust-lang/crates.io-index)" = "b957d88f4b6a63b9d70d5f454ac8011819c6efa7727858f458ab71c756ce2d3e" 181 | "checksum either 1.5.0 (registry+https://github.com/rust-lang/crates.io-index)" = "3be565ca5c557d7f59e7cfcf1844f9e3033650c929c6566f511e8005f205c1d0" 182 | "checksum itertools 0.7.8 (registry+https://github.com/rust-lang/crates.io-index)" = "f58856976b776fedd95533137617a02fb25719f40e7d9b01c7043cd65474f450" 183 | "checksum libc 0.2.43 (registry+https://github.com/rust-lang/crates.io-index)" = "76e3a3ef172f1a0b9a9ff0dd1491ae5e6c948b94479a3021819ba7d860c8645d" 184 | "checksum log 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)" = "c84ec4b527950aa83a329754b01dbe3f58361d1c5efacd1f6d68c494d08a17c6" 185 | "checksum redox_syscall 0.1.40 (registry+https://github.com/rust-lang/crates.io-index)" = "c214e91d3ecf43e9a4e41e578973adeb14b474f2bee858742d127af75a0112b1" 186 | "checksum redox_termios 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "7e891cfe48e9100a70a3b6eb652fef28920c117d366339687bd5576160db0f76" 187 | "checksum strsim 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "bb4f380125926a99e52bc279241539c018323fab05ad6368b56f93d9369ff550" 188 | "checksum termion 1.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "689a3bdfaab439fd92bc87df5c4c78417d3cbe537487274e9b0b2dce76e92096" 189 | "checksum textwrap 0.10.0 (registry+https://github.com/rust-lang/crates.io-index)" = "307686869c93e71f94da64286f9a9524c0f308a9e1c87a583de8e9c9039ad3f6" 190 | "checksum tui 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "89923340858fdc4bf6a4655edb6b27dbc3f69f21eac312379f46047e46432770" 191 | "checksum unicode-segmentation 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "aa6024fc12ddfd1c6dbc14a80fa2324d4568849869b779f6bd37e5e4c03344d1" 192 | "checksum unicode-width 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "882386231c45df4700b275c7ff55b6f3698780a650026380e72dabe76fa46526" 193 | "checksum vec_map 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)" = "05c78687fb1a80548ae3250346c3db86a80a7cdd77bda190189f2d0a0987c81a" 194 | "checksum winapi 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)" = "92c1eb33641e276cfa214a0522acad57be5c56b10cb348b3c5117db75f3ac4b0" 195 | "checksum winapi-i686-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" 196 | "checksum winapi-x86_64-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" 197 | "checksum yaml-rust 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)" = "e66366e18dc58b46801afbf2ca7661a9f59cc8c5962c29892b6039b4f86fa992" 198 | -------------------------------------------------------------------------------- /src/state/tests.rs: -------------------------------------------------------------------------------- 1 | use super::super::state::State; 2 | use super::operator; 3 | 4 | fn compare_u8_slices(slice1: &[u8], slice2: &[u8]) { 5 | for (i, item) in slice1.iter().enumerate() { 6 | assert_eq!(item, &slice2[i]); 7 | } 8 | } 9 | 10 | #[test] 11 | fn nop() { 12 | let state = State::new([0; 255], [0; 255]); 13 | 14 | let new_state = (operator::NOP.run)(&state, 0); 15 | 16 | assert_eq!(new_state.pc, state.pc + 1); 17 | 18 | assert_eq!(new_state.ac, state.ac); 19 | 20 | assert_eq!(new_state.halt, false); 21 | 22 | compare_u8_slices(&new_state.memory, &state.memory); 23 | 24 | compare_u8_slices(&new_state.output, &state.output); 25 | } 26 | 27 | #[test] 28 | fn sta() { 29 | let mut state = State::new([0; 255], [0; 255]); 30 | 31 | state.ac = 25; 32 | 33 | let new_state = (operator::STA.run)(&state, 50); 34 | 35 | assert_eq!(new_state.pc, state.pc + 2); 36 | 37 | assert_eq!(new_state.ac, state.ac); 38 | 39 | assert_eq!(new_state.halt, false); 40 | 41 | for (i, item) in new_state.memory.iter().enumerate() { 42 | if i == 50 { 43 | assert_eq!(item, &state.ac); 44 | } else { 45 | assert_eq!(item, &state.memory[i]); 46 | } 47 | } 48 | 49 | compare_u8_slices(&new_state.output, &state.output); 50 | } 51 | 52 | #[test] 53 | fn lda() { 54 | let mut state = State::new([0; 255], [0; 255]); 55 | 56 | state.memory[50] = 25; 57 | 58 | let new_state = (operator::LDA.run)(&state, 50); 59 | 60 | assert_eq!(new_state.pc, state.pc + 2); 61 | 62 | assert_eq!(new_state.ac, state.memory[50]); 63 | 64 | assert_eq!(new_state.halt, false); 65 | 66 | compare_u8_slices(&new_state.memory, &state.memory); 67 | 68 | compare_u8_slices(&new_state.output, &state.output); 69 | } 70 | 71 | #[test] 72 | fn add() { 73 | let mut state = State::new([0; 255], [0; 255]); 74 | 75 | state.memory[50] = 20; 76 | 77 | state.ac = 10; 78 | 79 | let new_state = (operator::ADD.run)(&state, 50); 80 | 81 | assert_eq!(new_state.pc, state.pc + 2); 82 | 83 | assert_eq!(new_state.ac, state.ac + 20); 84 | 85 | assert_eq!(new_state.halt, false); 86 | 87 | compare_u8_slices(&new_state.memory, &state.memory); 88 | 89 | compare_u8_slices(&new_state.output, &state.output); 90 | } 91 | 92 | #[test] 93 | fn or() { 94 | let mut state = State::new([0; 255], [0; 255]); 95 | 96 | state.memory[50] = 20; 97 | 98 | state.ac = 10; 99 | 100 | let new_state = (operator::OR.run)(&state, 50); 101 | 102 | assert_eq!(new_state.pc, state.pc + 2); 103 | 104 | assert_eq!(new_state.ac, state.ac | 20); 105 | 106 | assert_eq!(new_state.halt, false); 107 | 108 | compare_u8_slices(&new_state.memory, &state.memory); 109 | 110 | compare_u8_slices(&new_state.output, &state.output); 111 | } 112 | 113 | #[test] 114 | fn and() { 115 | let mut state = State::new([0; 255], [0; 255]); 116 | 117 | state.memory[50] = 20; 118 | 119 | state.ac = 10; 120 | 121 | let new_state = (operator::AND.run)(&state, 50); 122 | 123 | assert_eq!(new_state.pc, state.pc + 2); 124 | 125 | assert_eq!(new_state.ac, state.ac & 20); 126 | 127 | assert_eq!(new_state.halt, false); 128 | 129 | compare_u8_slices(&new_state.memory, &state.memory); 130 | 131 | compare_u8_slices(&new_state.output, &state.output); 132 | } 133 | 134 | #[test] 135 | fn not() { 136 | let mut state = State::new([0; 255], [0; 255]); 137 | 138 | state.ac = 10; 139 | 140 | let new_state = (operator::NOT.run)(&state, 0); 141 | 142 | assert_eq!(new_state.pc, state.pc + 1); 143 | 144 | assert_eq!(new_state.ac, !state.ac); 145 | 146 | assert_eq!(new_state.halt, false); 147 | 148 | compare_u8_slices(&new_state.memory, &state.memory); 149 | 150 | compare_u8_slices(&new_state.output, &state.output); 151 | } 152 | 153 | #[test] 154 | fn sub() { 155 | let mut state = State::new([0; 255], [0; 255]); 156 | 157 | state.memory[50] = 5; 158 | 159 | state.ac = 10; 160 | 161 | let new_state = (operator::SUB.run)(&state, 50); 162 | 163 | assert_eq!(new_state.pc, state.pc + 2); 164 | 165 | assert_eq!(new_state.ac, state.ac - 5); 166 | 167 | assert_eq!(new_state.halt, false); 168 | 169 | compare_u8_slices(&new_state.memory, &state.memory); 170 | 171 | compare_u8_slices(&new_state.output, &state.output); 172 | } 173 | 174 | #[test] 175 | fn jmp() { 176 | let state = State::new([0; 255], [0; 255]); 177 | 178 | let new_state = (operator::JMP.run)(&state, 50); 179 | 180 | assert_eq!(new_state.pc, 50); 181 | 182 | assert_eq!(new_state.ac, state.ac); 183 | 184 | assert_eq!(new_state.halt, false); 185 | 186 | compare_u8_slices(&new_state.memory, &state.memory); 187 | 188 | compare_u8_slices(&new_state.output, &state.output); 189 | } 190 | 191 | #[test] 192 | fn jn_when_accumulator_is_negative() { 193 | let mut state = State::new([0; 255], [0; 255]); 194 | 195 | state.ac = 0b1000000; 196 | 197 | let new_state = (operator::JN.run)(&state, 50); 198 | 199 | assert_eq!(new_state.pc, 50); 200 | 201 | assert_eq!(new_state.ac, state.ac); 202 | 203 | assert_eq!(new_state.halt, false); 204 | 205 | compare_u8_slices(&new_state.memory, &state.memory); 206 | 207 | compare_u8_slices(&new_state.output, &state.output); 208 | } 209 | 210 | #[test] 211 | fn jn_when_accumulator_is_positive() { 212 | let mut state = State::new([0; 255], [0; 255]); 213 | 214 | state.ac = 0; 215 | 216 | let new_state = (operator::JN.run)(&state, 50); 217 | 218 | assert_eq!(new_state.pc, state.pc + 2); 219 | 220 | assert_eq!(new_state.ac, state.ac); 221 | 222 | assert_eq!(new_state.halt, false); 223 | 224 | compare_u8_slices(&new_state.memory, &state.memory); 225 | 226 | compare_u8_slices(&new_state.output, &state.output); 227 | } 228 | 229 | #[test] 230 | fn jz_when_accumulator_is_zero() { 231 | let mut state = State::new([0; 255], [0; 255]); 232 | 233 | state.ac = 0; 234 | 235 | let new_state = (operator::JZ.run)(&state, 50); 236 | 237 | assert_eq!(new_state.pc, 50); 238 | 239 | assert_eq!(new_state.ac, state.ac); 240 | 241 | assert_eq!(new_state.halt, false); 242 | 243 | compare_u8_slices(&new_state.memory, &state.memory); 244 | 245 | compare_u8_slices(&new_state.output, &state.output); 246 | } 247 | 248 | #[test] 249 | fn jz_when_accumulator_is_not_zero() { 250 | let mut state = State::new([0; 255], [0; 255]); 251 | 252 | state.ac = 2; 253 | 254 | let new_state = (operator::JZ.run)(&state, 50); 255 | 256 | assert_eq!(new_state.pc, state.pc + 2); 257 | 258 | assert_eq!(new_state.ac, state.ac); 259 | 260 | assert_eq!(new_state.halt, false); 261 | 262 | compare_u8_slices(&new_state.memory, &state.memory); 263 | 264 | compare_u8_slices(&new_state.output, &state.output); 265 | } 266 | 267 | #[test] 268 | fn jnz_when_accumulator_is_not_zero() { 269 | let mut state = State::new([0; 255], [0; 255]); 270 | 271 | state.ac = 0; 272 | 273 | let new_state = (operator::JNZ.run)(&state, 50); 274 | 275 | assert_eq!(new_state.pc, state.pc + 2); 276 | 277 | assert_eq!(new_state.ac, state.ac); 278 | 279 | assert_eq!(new_state.halt, false); 280 | 281 | compare_u8_slices(&new_state.memory, &state.memory); 282 | 283 | compare_u8_slices(&new_state.output, &state.output); 284 | } 285 | 286 | #[test] 287 | fn jnz_when_accumulator_is_zero() { 288 | let mut state = State::new([0; 255], [0; 255]); 289 | 290 | state.ac = 2; 291 | 292 | let new_state = (operator::JNZ.run)(&state, 50); 293 | 294 | assert_eq!(new_state.pc, 50); 295 | 296 | assert_eq!(new_state.ac, state.ac); 297 | 298 | assert_eq!(new_state.halt, false); 299 | 300 | compare_u8_slices(&new_state.memory, &state.memory); 301 | 302 | compare_u8_slices(&new_state.output, &state.output); 303 | } 304 | 305 | #[test] 306 | fn in_operator() { 307 | let state = State::new([0; 255], [1; 255]); 308 | 309 | let new_state = (operator::IN.run)(&state, 0); 310 | 311 | assert_eq!(new_state.pc, state.pc + 2); 312 | 313 | assert_eq!(new_state.ac, 1); 314 | 315 | assert_eq!(new_state.halt, false); 316 | 317 | compare_u8_slices(&new_state.memory, &state.memory); 318 | 319 | compare_u8_slices(&new_state.output, &state.output); 320 | } 321 | 322 | #[test] 323 | fn out() { 324 | let mut state = State::new([0; 255], [0; 255]); 325 | 326 | state.output = [22; 40]; 327 | 328 | state.ac = 10; 329 | 330 | let new_state = (operator::OUT.run)(&state, 0); 331 | 332 | assert_eq!(new_state.pc, state.pc + 1); 333 | 334 | assert_eq!(new_state.ac, state.ac); 335 | 336 | assert_eq!(new_state.halt, false); 337 | 338 | compare_u8_slices(&new_state.memory, &state.memory); 339 | 340 | for (i, item) in new_state.output.iter().enumerate() { 341 | if i == 0 { 342 | assert_eq!(item, &state.ac); 343 | } else { 344 | assert_eq!(item, &state.output[i]); 345 | } 346 | } 347 | } 348 | 349 | #[test] 350 | fn ldi() { 351 | let state = State::new([0; 255], [0; 255]); 352 | 353 | let new_state = (operator::LDI.run)(&state, 50); 354 | 355 | assert_eq!(new_state.pc, state.pc + 2); 356 | 357 | assert_eq!(new_state.ac, 50); 358 | 359 | assert_eq!(new_state.halt, false); 360 | 361 | compare_u8_slices(&new_state.memory, &state.memory); 362 | 363 | compare_u8_slices(&new_state.output, &state.output); 364 | } 365 | 366 | #[test] 367 | fn hlt() { 368 | let state = State::new([0; 255], [0; 255]); 369 | 370 | let new_state = (operator::HLT.run)(&state, 0); 371 | 372 | assert_eq!(new_state.pc, state.pc + 1); 373 | 374 | assert_eq!(new_state.ac, state.ac); 375 | 376 | assert_eq!(new_state.halt, true); 377 | 378 | compare_u8_slices(&new_state.memory, &state.memory); 379 | 380 | compare_u8_slices(&new_state.output, &state.output); 381 | } 382 | --------------------------------------------------------------------------------