use crate::vm::{ VirtualMachineInterface, VirtualMachine, Word }; static MAX_VALUE_WIDTH: usize = 8; fn expects_n_args(args: &Vec<&str>, n: usize) -> Result<(), String> { if args.len() - 1 != n { Err(format!("Command '{}' expected {n} arguments, {} were given!", args[0], args.len() - 1)) } else { Ok(()) } } fn parse_location(arg: &str, assignment: bool) -> Result, String> { let nums_str = arg.to_string(); let nums = nums_str .split(&['.', ':']) .collect::>(); let start = nums[0].parse::() .map_err(|_| format!("Unknown command '{}'!", nums[0]))?; if start < 0 { return Err(format!("Address '{}' cannot be negative!", start)); } // Single location if nums.len() == 1 { Ok(vec![start]) } // Range location else { let end = nums[1].parse::() .map_err(|_| format!("Expected location!"))?; if assignment { Ok(vec![start, end]) } // Range addresses else if end < 0 { return Err(format!("Address '{}' cannot be negative!", end)); } else { Ok(Vec::from_iter(start..=end)) } } } fn assign_location(locs: Vec, vm: &mut VirtualMachine) { print_locations(vec![locs[0] as usize], vm); vm.set_word(locs[0] as usize, locs[1] as Word); } fn print_locations(locs: Vec, vm: &VirtualMachine) { let padding = format!("{}", locs.last().unwrap()).len(); for (i, loc) in locs.into_iter().enumerate() { if i % MAX_VALUE_WIDTH == 0 { if i > 0 { println!(); } print!("{:indent$}:", loc, indent=padding); } print!(" {:ident$}", vm.get_word(loc), ident=6); } println!(); } pub(super) fn parse_and_exec(input: String, vm: &mut VirtualMachine) -> Result<(), String> { let args = input .split(&[' ', '\t', '\n', '\r']) .filter(|x| !x.is_empty()) .collect::>(); if args.len() == 0 { println!(); return Ok(()); } match args[0] { /* Run mode */ "r" | "run" => { expects_n_args(&args, 1)?; if ! vm.is_halted() { return Err(format!("Machine already running!")); } vm.set_start(args[1].parse().unwrap()); }, /* Single step mode */ "s" | "single" => { expects_n_args(&args, 1)?; if ! vm.is_halted() { return Err(format!("Machine already running!")); } if vm.is_single_step() { return Err(format!("Machine already in single-step mode!")); } vm.set_single_step(true); vm.set_start(args[1].parse().unwrap()); }, "c" | "continue" => { expects_n_args(&args, 0)?; if ! vm.is_single_step() { return Err(format!("Machine not in single-step mode!")); } vm.set_single_step(false); vm.set_halt(false); }, "n" | "next" => { expects_n_args(&args, 0)?; if ! vm.is_single_step() { return Err(format!("Machine not in single-step mode!")); } vm.step(); }, /* Other */ "q" | "quit" | "e" | "exit" => return Err(String::new()), "h" | "?" | "help" => { print!("
is a usize constant is a Word (i64) constant Multiple 'Memory IO' commands may be put on a single line, when separated by spaces General commands: h, ?, help This menu q, e, quit, exit Stop and exit the virtual machine Normal execution: r, run
Start normal execution at ADDRESS Only applicable when 'HALTED' Single-step execution: s, single
Start single-step execution at ADDRESS c, continue Resume normal execution Only applicable when in 'STEP' mode n, next Execute next instruction Only applicable when in 'STEP' mode Memory IO:
Print the value at the location
.
Print the values between the locations (inclusive range)
: Set value at ADDRESS to VALUE "); }, _ => { for arg in args { let assignment = arg.contains(':'); let locs = parse_location(arg, assignment)?; if assignment { assign_location(locs, vm); } else { let mut locs_casted = Vec::new(); for elem in locs { let elem = elem.try_into() .map_err(|_| format!("Expected address, got '{}'!", elem))?; locs_casted.push(elem); } print_locations(locs_casted, vm); } } }, } Ok(()) } #[cfg(test)] mod tests { use super::*; #[test] fn expect_args_too_few() { let res = expects_n_args(&vec!["abc", "arg1"], 2); assert!(res.is_err()); } #[test] fn expect_args_too_many() { let res = expects_n_args(&vec!["abc", "arg1", "arg2", "arg3"], 2); assert!(res.is_err()); } #[test] fn expect_args_exact() { let res = expects_n_args(&vec!["abc", "arg1", "arg2"], 2); assert!(res.is_ok()); } #[test] fn parse_location_single_positive() { let res = parse_location("1234", false); assert!(res.is_ok()); let res = res.unwrap(); assert_eq!(res.len(), 1); assert_eq!(res[0], 1234); } #[test] fn parse_location_single_negative() { let res = parse_location("-1234", false); assert!(res.is_err()); } #[test] fn parse_location_range_positives() { let res = parse_location("1002.1011", false); assert!(res.is_ok()); let res = res.unwrap(); assert_eq!(res.len(), 10); for i in 1002..=1011 { assert_eq!(res[i-1002], i as Word); } } #[test] fn parse_location_range_negative() { let res = parse_location("-1002.1011", false); assert!(res.is_err()); let res = parse_location("1002.-1011", false); assert!(res.is_err()); } #[test] fn parse_location_assignment_positive() { let res = parse_location("1829:99", true); assert!(res.is_ok()); let res = res.unwrap(); assert_eq!(res.len(), 2); assert_eq!(res[0], 1829); assert_eq!(res[1], 99); } #[test] fn parse_location_assignment_negative_address() { let res = parse_location("-1829:99", true); assert!(res.is_err()); } #[test] fn parse_location_assignment_negative_value() { let res = parse_location("1829:-99", true); assert!(res.is_ok()); let res = res.unwrap(); assert_eq!(res.len(), 2); assert_eq!(res[0], 1829); assert_eq!(res[1], -99); } }