From 84230676cbcf1b990850de8a7a31874ed401335e Mon Sep 17 00:00:00 2001 From: Ross Andrews Date: Wed, 1 Jun 2022 16:11:34 -0500 Subject: [PATCH] vasm now supports files --- vasm/src/lib.rs | 1 + vasm/src/parse_error.rs | 6 +++- vasm/src/vasm_assembler.rs | 33 +++++++++++++++---- vasm/src/vasm_preprocessor.rs | 32 +++++++++++-------- vemu/src/main.rs | 10 +++--- vtest/src/integration_tests.rs | 58 +++++++++++++++++----------------- 6 files changed, 85 insertions(+), 55 deletions(-) diff --git a/vasm/src/lib.rs b/vasm/src/lib.rs index f26d477..825ada8 100644 --- a/vasm/src/lib.rs +++ b/vasm/src/lib.rs @@ -10,3 +10,4 @@ mod vasm_parser; mod vasm_preprocessor; pub use vasm_assembler::assemble_snippet; +pub use vasm_assembler::assemble_file; diff --git a/vasm/src/parse_error.rs b/vasm/src/parse_error.rs index b0106ba..2027cfd 100644 --- a/vasm/src/parse_error.rs +++ b/vasm/src/parse_error.rs @@ -33,6 +33,7 @@ pub enum AssembleError { ArgError(usize, EvalError), NoCode, IncludeError(usize, String), + FileError(String), MacroError(usize), } @@ -63,6 +64,9 @@ impl Display for AssembleError { AssembleError::MacroError(line) => { write!(f, "Malformed macro control structure on line {}", line) } + AssembleError::FileError(file) => { + write!(f, "File read error in {}", file) + } } } } @@ -88,4 +92,4 @@ impl Display for EvalError { } } } -} +} \ No newline at end of file diff --git a/vasm/src/vasm_assembler.rs b/vasm/src/vasm_assembler.rs index fb20a62..25599fe 100644 --- a/vasm/src/vasm_assembler.rs +++ b/vasm/src/vasm_assembler.rs @@ -3,6 +3,7 @@ use crate::parse_error::AssembleError; use crate::vasm_evaluator::eval; use crate::vasm_preprocessor::{Line, LineSource}; use std::collections::BTreeMap; +use std::fs; /// This will solve all the .equ directives and return a symbol table of them. /// .equ directives must be able to be solved in order, that is, in terms of @@ -162,14 +163,14 @@ fn code_bounds( /// bytes long and index 0 will represent 0x400. /// ``` /// assert_eq!( -/// vasm::assemble_snippet(vec![".org 0x400", "push 5", "add 7"]), +/// vasm::assemble_snippet(".org 0x400 \n push 5 \n add 7".lines().map(String::from)), /// Ok(vec![0x01, 0x05, 0x05, 0x07]) /// ) /// ``` -pub fn assemble_snippet<'a, T: IntoIterator>( +pub fn assemble_snippet>( lines: T, ) -> Result, AssembleError> { - let mut line_results: Vec> = + let line_results: Vec> = LineSource::new("_snippet", lines, |_file| { Err(AssembleError::IncludeError( 0, @@ -178,6 +179,19 @@ pub fn assemble_snippet<'a, T: IntoIterator>( }) .collect(); + assemble_line_results(line_results) +} + +pub fn assemble_file(filename: &str) -> Result, AssembleError> { + let lines = lines_from_file(filename)?; + let line_results: Vec> = LineSource::new(filename, lines, |file| { + lines_from_file(file.as_str()) + }).collect(); + + assemble_line_results(line_results) +} + +fn assemble_line_results(mut line_results: Vec>) -> Result, AssembleError> { if let Some(Err(error)) = line_results.iter().find(|line| line.is_err()) { Err(error.clone()) } else { @@ -189,6 +203,11 @@ pub fn assemble_snippet<'a, T: IntoIterator>( } } +fn lines_from_file(filename: &str) -> Result, AssembleError> { + let file = fs::read_to_string(filename).map_err(|_e| AssembleError::FileError(filename.into()))?; + Ok(file.lines().map(String::from).collect()) +} + /// At this point all lines have addresses and lengths, and all arguments are reduced to /// numeric constants. It's time to generate code. /// @@ -474,9 +493,9 @@ mod test { #[test] fn test_assemble_snippet() { - assert_eq!(assemble_snippet(["add"]), Ok(vec![4])); + assert_eq!(assemble_snippet(["add"].map(String::from)), Ok(vec![4])); assert_eq!( - assemble_snippet(["apple"]), + assemble_snippet(["apple"].map(String::from)), Err(ParseError( 1, parse_error::ParseError::InvalidInstruction("apple".into()) @@ -491,7 +510,7 @@ mod test { hlt blah: mul 2 ret" - .lines() + .lines().map(String::from) ), Ok(vec![ 0x01, 0x03, // nop 3 @@ -509,7 +528,7 @@ mod test { nop $+2 nop 0x111111 - nop 0x222222".lines()), + nop 0x222222".lines().map(String::from)), Ok(vec![ 0x03, 0x08, 0x04, 0x00, 0x03, 0x11, 0x11, 0x11, diff --git a/vasm/src/vasm_preprocessor.rs b/vasm/src/vasm_preprocessor.rs index d1257d5..4d8aabd 100644 --- a/vasm/src/vasm_preprocessor.rs +++ b/vasm/src/vasm_preprocessor.rs @@ -35,8 +35,7 @@ enum ControlStructure { } pub struct LineSource< - 'a, - T: IntoIterator, + T: IntoIterator, F: Fn(String) -> Result, > { generated_lines: VecDeque, @@ -48,8 +47,8 @@ pub struct LineSource< include: F, } -impl<'a, T: IntoIterator, F: Fn(String) -> Result> Iterator - for LineSource<'a, T, F> +impl, F: Fn(String) -> Result> Iterator + for LineSource { type Item = Result; @@ -68,7 +67,7 @@ impl<'a, T: IntoIterator, F: Fn(String) -> Result Some(Err(AssembleError::ParseError(self.current_line, err))), @@ -99,9 +98,10 @@ impl<'a, T: IntoIterator, F: Fn(String) -> Result, F: Fn(String) -> Result> - LineSource<'a, T, F> +impl, F: Fn(String) -> Result> + LineSource { + // TODO: linesource should take a deref instead so it accepts either str or string. pub fn new(file: &str, lines: T, include: F) -> Self { LineSource { generated_lines: VecDeque::new(), @@ -206,16 +206,20 @@ mod tests { use crate::ast::{Label, Node}; use vcore::opcodes::Opcode::*; - fn lines_for(source: Vec<&str>) -> Vec { + fn lines_for(source: Vec) -> Vec { let include = |_name: String| panic!(); let src = LineSource::new("blah", source, include); src.map(|line| line.unwrap().line).collect() } + fn stringify(a: Vec<&str>) -> Vec { + a.into_iter().map(String::from).collect() + } + #[test] fn test_preprocess() { let include = |_name: String| panic!(); - let lines = vec!["add"]; + let lines = stringify(vec!["add"]); let mut src = LineSource::new("blah", lines, include); assert_eq!( src.next(), @@ -230,8 +234,8 @@ mod tests { #[test] fn test_preprocess_include() { - let include = |_name: String| Ok(vec!["sub"]); - let lines = vec!["#include \"foo\"", "add"]; + let include = |_name: String| Ok(stringify(vec!["sub"])); + let lines = stringify(vec!["#include \"foo\"", "add"]); let src = LineSource::new("blah", lines, include); assert_eq!( src.collect::>>(), @@ -253,7 +257,7 @@ mod tests { #[test] fn test_preprocess_if_end() { assert_eq!( - lines_for(vec!["#if", "#end"]), + lines_for(stringify(vec!["#if", "#end"])), vec![ VASMLine::Instruction(None, Brz, Some(Node::relative_label("__gensym_1"))), VASMLine::LabelDef(Label::from("__gensym_1")) @@ -264,7 +268,7 @@ mod tests { #[test] fn test_preprocess_else() { assert_eq!( - lines_for(vec!["#if", "add", "#else", "sub", "#end"]), + lines_for(stringify(vec!["#if", "add", "#else", "sub", "#end"])), vec![ VASMLine::Instruction(None, Brz, Some(Node::relative_label("__gensym_1"))), VASMLine::Instruction(None, Add, None), @@ -279,7 +283,7 @@ mod tests { #[test] fn test_preprocess_do_end() { assert_eq!( - lines_for(vec!["#while", "#do", "#end"]), + lines_for(stringify(vec!["#while", "#do", "#end"])), vec![ VASMLine::LabelDef(Label("__gensym_1".to_string())), VASMLine::Instruction( diff --git a/vemu/src/main.rs b/vemu/src/main.rs index 8995e01..b5b5663 100644 --- a/vemu/src/main.rs +++ b/vemu/src/main.rs @@ -43,7 +43,7 @@ fn main() { let memory = Memory::from(rng); let mut cpu = CPU::new(memory); display::reset(&mut cpu); - let code = assemble_snippet(include_str!("typewriter.asm").lines()).expect("Assemble error"); + let code = assemble_snippet(include_str!("typewriter.asm").lines().map(String::from)).expect("Assemble error"); println!("ROM size: {} bytes", code.len()); cpu.poke_slice(0x400.into(), code.as_slice()); cpu.start(); @@ -103,12 +103,14 @@ fn window_loop(event_loop: EventLoop<()>, window: Window, mut pixels: Pixels, mu let start = Instant::now(); draw(pixels.get_frame(), &mut cpu); pixels.render().expect("Problem displaying framebuffer"); - loop { + loop { // TODO: this will run the CPU at 100%, need to not spin while halted if let Some((int, arg)) = interrupt_events.pop_front() { cpu.interrupt(int, arg) } - for _ in 1..1000 { - cpu.tick() + if cpu.running() { + for _ in 1..1000 { + cpu.tick() + } } if Instant::now() > start + Duration::from_millis(25) { break; diff --git a/vtest/src/integration_tests.rs b/vtest/src/integration_tests.rs index 3e116d7..9b6571c 100644 --- a/vtest/src/integration_tests.rs +++ b/vtest/src/integration_tests.rs @@ -4,19 +4,19 @@ use vcore::memory::{Memory, PeekPokeExt}; use vcore::word::Word; use vcore::CPU; -fn cpu_test<'a, T: IntoIterator>(code: T) -> CPU { +fn cpu_test>(code: T) -> CPU { let bin = assemble_snippet(code).unwrap(); let mut cpu = vcore::cpu::CPU::new(Memory::with_program(bin)); cpu.run_to_halt(); cpu } -fn test_stack<'a, T: IntoIterator>(code: T, expected_stack: Vec) { +fn test_stack>(code: T, expected_stack: Vec) { let cpu = cpu_test(code); assert_eq!(cpu.get_stack(), expected_stack); } -fn test_rstack<'a, T: IntoIterator>( +fn test_rstack>( code: T, expected_stack: Vec, expected_rstack: Vec, @@ -26,7 +26,7 @@ fn test_rstack<'a, T: IntoIterator>( assert_eq!(cpu.get_call(), expected_rstack); } -fn test_mem<'a, T: IntoIterator>( +fn test_mem>( code: T, expected_memory: BTreeMap, ) -> CPU { @@ -45,7 +45,7 @@ fn test_arithmetic() { push 2 add 3 hlt" - .lines(), + .lines().map(String::from), vec![5], ); @@ -55,7 +55,7 @@ fn test_arithmetic() { push 7 mul 1000 hlt" - .lines(), + .lines().map(String::from), vec![7000], ); } @@ -69,7 +69,7 @@ fn test_call_stack() { hlt blah: mul 2 ret" - .lines(), + .lines().map(String::from), vec![6], ); @@ -81,7 +81,7 @@ fn test_call_stack() { pushr pushr hlt" - .lines(), + .lines().map(String::from), vec![10], vec![3, 4], ); @@ -95,7 +95,7 @@ fn test_call_stack() { popr add hlt" - .lines(), + .lines().map(String::from), vec![10, 7], vec![20], ); @@ -106,7 +106,7 @@ fn test_call_stack() { call blah blah: pushr 3 hlt" - .lines(), + .lines().map(String::from), vec![], vec![5, 0x406, 3], ); @@ -120,7 +120,7 @@ fn test_call_stack() { popr popr hlt" - .lines(), + .lines().map(String::from), vec![5, 20, 20, 10], vec![], ) @@ -139,7 +139,7 @@ fn test_comparison() { push 10 lt 5 hlt" - .lines(), + .lines().map(String::from), vec![0, 1, 1, 0], ); @@ -155,7 +155,7 @@ fn test_comparison() { push 10 alt -5 hlt" - .lines(), + .lines().map(String::from), vec![0, 1, 1, 0], ); @@ -164,7 +164,7 @@ fn test_comparison() { not 10 not 0 hlt" - .lines(), + .lines().map(String::from), vec![0, 1], ) } @@ -177,7 +177,7 @@ fn test_pick() { nop 20 pick 1 hlt" - .lines(), + .lines().map(String::from), vec![10, 20, 10], ); @@ -188,7 +188,7 @@ fn test_pick() { pick 2 pick 5 hlt" - .lines(), + .lines().map(String::from), vec![10, 20, 0, 0], ) } @@ -202,7 +202,7 @@ fn test_store() { push 0x123456 storew 203 hlt" - .lines(), + .lines().map(String::from), [(201, 10), (203, 0x56), (204, 0x34), (205, 0x12)].into(), ); } @@ -216,7 +216,7 @@ fn test_load() { hlt .org 0x500 .db 0x123456" - .lines(), + .lines().map(String::from), vec![0x34, 0x123456], ) } @@ -231,7 +231,7 @@ fn test_rotate() { push 300 rot hlt" - .lines(), + .lines().map(String::from), vec![10, 200, 300, 100], ) } @@ -244,7 +244,7 @@ fn test_sdp() { pushr 20 sdp hlt" - .lines(), + .lines().map(String::from), vec![10, 1021, 265], ) } @@ -262,7 +262,7 @@ fn test_underflow() { onunder: push 200 store 10 hlt" - .lines(), + .lines().map(String::from), [(10, 200)].into(), ); @@ -277,7 +277,7 @@ fn test_underflow() { onrunder: push 200 store 10 hlt" - .lines(), + .lines().map(String::from), [(10, 200)].into(), ); @@ -292,7 +292,7 @@ fn test_underflow() { onunder: push 200 store 10 hlt" - .lines(), + .lines().map(String::from), [(10, 200)].into(), ); } @@ -311,7 +311,7 @@ fn test_div_zero() { ondiv0: push 200 store 10 hlt" - .lines(), + .lines().map(String::from), [(10, 200)].into(), ); @@ -327,7 +327,7 @@ fn test_div_zero() { ondiv0: push 200 store 10 hlt" - .lines(), + .lines().map(String::from), [(10, 200)].into(), ); } @@ -348,7 +348,7 @@ fn test_overflow() { push 3 ; boom! store ; never happens onover: hlt" - .lines(), + .lines().map(String::from), [(16, 1), (19, 2)].into(), // expect the two successful pushes to be still there ); @@ -378,7 +378,7 @@ fn test_simple_overflow() { add 5 ; full stack + argument! store ; never happens onover: hlt" - .lines(), + .lines().map(String::from), [(16, 1), (19, 2)].into(), // expect the two successful pushes to be still there ); @@ -408,7 +408,7 @@ fn test_overflow_promotion() { div ; This is a div 0, but there's no room to handle it, so it's promoted to overflow store ; never happens onover: hlt" - .lines(), + .lines().map(String::from), [(16, 1), (19, 0)].into(), // expect the two successful pushes to be still there ); @@ -434,7 +434,7 @@ fn test_invalid_opcode() { .db 0xf6 ; this isn't an instruction! store ; never happens onop: hlt" - .lines(), + .lines().map(String::from), [(256, 1), (259, 2)].into(), // expect the two successful pushes to be still there );