diff --git a/vasm/src/parse_error.rs b/vasm/src/parse_error.rs index 974a469..35efdec 100644 --- a/vasm/src/parse_error.rs +++ b/vasm/src/parse_error.rs @@ -2,7 +2,7 @@ use std::fmt::{Display, Formatter}; use vcore::opcodes::InvalidMnemonic; -#[derive(Debug, PartialEq)] +#[derive(Debug, PartialEq, Clone)] pub enum ParseError<'a> { LineParseFailure, InvalidInstruction(&'a str), diff --git a/vasm/src/vasm_assembler.rs b/vasm/src/vasm_assembler.rs index 74f6978..48b4d90 100644 --- a/vasm/src/vasm_assembler.rs +++ b/vasm/src/vasm_assembler.rs @@ -1,10 +1,13 @@ use crate::ast::{Label, VASMLine}; +use crate::parse_error::ParseError; use crate::vasm_evaluator::{eval, EvalError, Scope}; +use crate::vasm_parser::parse_vasm_line; use std::collections::BTreeMap; use std::fmt::{Display, Formatter}; #[derive(Debug, Clone, PartialEq)] pub enum AssembleError<'a> { + ParseError(i32, ParseError<'a>), EquResolveError(i32, &'a str, EvalError<'a>), EquDuplicateError(i32, &'a str), OrgResolveError(i32, EvalError<'a>), @@ -30,6 +33,9 @@ impl<'a> Display for AssembleError<'a> { AssembleError::NoCode => { write!(f, "No output would be generated by this code") } + AssembleError::ParseError(line, err) => { + write!(f, "Parse error on line {}: {}", line, err) + } } } } @@ -37,13 +43,13 @@ impl<'a> Display for AssembleError<'a> { /// 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 /// only preceding .equ directives. Anything else is an error. -pub fn solve_equs<'a>(lines: &[VASMLine<'a>]) -> Result, AssembleError<'a>> { +fn solve_equs<'a>(lines: &Vec>) -> Result, AssembleError<'a>> { let mut scope: Scope = Scope::new(); let line_nums: BTreeMap = BTreeMap::new(); for (line_idx, line) in lines.iter().enumerate() { let line_num = (line_idx + 1) as i32; if let VASMLine::Equ(Label(name), expr) = line { - let value = eval(expr, line_num as i32, &line_nums, &scope) + let value = eval(&expr, line_num as i32, &line_nums, &scope) .map_err(|e| AssembleError::EquResolveError(line_num as i32, name, e))?; if let Some(_old_value) = scope.insert(name, value) { @@ -56,7 +62,6 @@ pub fn solve_equs<'a>(lines: &[VASMLine<'a>]) -> Result, AssembleError type LineLengths = BTreeMap; type LineAddresses = BTreeMap; -type LineArgs = BTreeMap; fn arg_length(val: i32) -> usize { if val < 0 { @@ -84,7 +89,7 @@ fn arg_length(val: i32) -> usize { /// what we know right now (.equs), are however long that argument is. If we don't /// know right now (based on a label, say) then we'll set aside the full 3 bytes (so it's /// 4 bytes long, with the instruction byte). -pub fn measure_instructions<'a>(lines: &[VASMLine<'a>], scope: &Scope) -> LineLengths { +fn measure_instructions<'a>(lines: &Vec>, scope: &Scope) -> LineLengths { let line_nums: BTreeMap = BTreeMap::new(); let mut lengths = LineLengths::new(); for (line_idx, line) in lines.iter().enumerate() { @@ -94,7 +99,7 @@ pub fn measure_instructions<'a>(lines: &[VASMLine<'a>], scope: &Scope) -> LineLe lengths.insert(line_num, 1); } VASMLine::Instruction(_, _, Some(node)) => { - let len = eval(node, line_num as i32, &line_nums, scope).map_or(3, arg_length); + let len = eval(&node, line_num as i32, &line_nums, scope).map_or(3, arg_length); lengths.insert(line_num, len + 1); } VASMLine::Db(_, _) => { @@ -122,7 +127,7 @@ pub fn measure_instructions<'a>(lines: &[VASMLine<'a>], scope: &Scope) -> LineLe /// But, we'll skip labels that come before .equs: that would make every .equ set to its address, /// rather than the argument. fn place_labels<'a>( - lines: &[VASMLine<'a>], + lines: &Vec>, scope: Scope<'a>, lengths: &LineLengths, ) -> Result<(LineAddresses, Scope<'a>), AssembleError<'a>> { @@ -133,7 +138,7 @@ fn place_labels<'a>( let line_num = (line_idx + 1) as i32; if let VASMLine::Org(_, expr) = line { - address = eval(expr, line_num, &addresses, &scope) + address = eval(&expr, line_num, &addresses, &scope) .map_err(|err| AssembleError::OrgResolveError(line_num, err))?; addresses.insert(line_num, address); } @@ -155,33 +160,6 @@ fn place_labels<'a>( Ok((addresses, scope)) } -/// At this point we have everything we need to calculate the arguments, so we'll do that. -/// This will take the lines, addresses, and scope we've calculated so far, and iterate through -/// each line. If the line has an argument, evaluate it based on the scope, and store the number -/// that it evaluates to -fn calculate_args<'a>( - lines: &[VASMLine<'a>], - scope: Scope, - line_addresses: &LineAddresses, -) -> Result> { - let mut args = LineArgs::new(); - - for (line_idx, line) in lines.iter().enumerate() { - let line_num = (line_idx + 1) as i32; - - match line { - VASMLine::Instruction(_, _, Some(arg)) | VASMLine::Db(_, arg) => { - let num = eval(arg, line_num, line_addresses, &scope) - .map_err(|err| AssembleError::ArgError(line_num, err))?; - args.insert(line_num, num); - } - _ => {} - } - } - - Ok(args) -} - fn poke_word(code: &mut Vec, at: usize, word: i32) { let [low, mid, high, _] = word.to_le_bytes(); code[at] = low; @@ -191,7 +169,7 @@ fn poke_word(code: &mut Vec, at: usize, word: i32) { /// Find the lower and upper bounds where this program will place memory fn code_bounds<'a>( - lines: &[VASMLine<'a>], + lines: &Vec>, line_addresses: &LineAddresses, line_lengths: &LineLengths, ) -> Result<(usize, usize), AssembleError<'a>> { @@ -213,6 +191,18 @@ fn code_bounds<'a>( Ok((start, end + end_length - 1)) } +pub fn assemble<'a, T: IntoIterator>( + lines: T, +) -> Result, AssembleError<'a>> { + let mut parsed = Vec::new(); + for (line_idx, line) in lines.into_iter().enumerate() { + let line_num = (line_idx + 1) as i32; + parsed.push(parse_vasm_line(line).map_err(|err| AssembleError::ParseError(line_num, err))?) + } + + generate_code(parsed) +} + /// At this point all lines have addresses and lengths, and all arguments are reduced to /// numeric constants. It's time to generate code. /// @@ -221,21 +211,20 @@ fn code_bounds<'a>( /// - .db instructions turn into byte values starting at `address - start` /// - Opcodes turn into instruction bytes at `address - start` followed (maybe) by /// arguments. -/// - Any other directive is skipped (even .orgs, we already have the addresses calculated) +/// - .orgs cause us to skip ahead some in the output /// /// The instruction bytes are formed of six bits defining the instruction followed by two /// bits denoting how many bytes of argument follow it. /// /// Vulcan is a little-endian architecture: multi-byte arguments / .dbs will store the /// least-significant byte at the lowest address, then the more significant bytes following. -fn generate_code<'a>( - lines: &[VASMLine<'a>], - scope: Scope, - line_addresses: LineAddresses, - line_lengths: LineLengths, -) -> Result, AssembleError<'a>> { - let line_args = calculate_args(lines, scope, &line_addresses)?; - let (start, end) = code_bounds(lines, &line_addresses, &line_lengths)?; +fn generate_code<'a>(lines: Vec>) -> Result, AssembleError<'a>> { + let lines: Vec> = lines.into_iter().collect(); + let scope = solve_equs(&lines)?; + let line_lengths = measure_instructions(&lines, &scope); + let (line_addresses, scope) = place_labels(&lines, scope, &line_lengths)?; + let (start, end) = code_bounds(&lines, &line_addresses, &line_lengths)?; + let mut code = vec![0u8; end - start + 1]; let mut current_addr = start; @@ -246,8 +235,9 @@ fn generate_code<'a>( code[current_addr] = u8::from(*opcode) << 2; current_addr += 1; } - VASMLine::Instruction(_, opcode, Some(_)) => { - let arg = line_args[&line_num]; + VASMLine::Instruction(_, opcode, Some(arg)) => { + let arg = eval(arg, line_num, &line_addresses, &scope) + .map_err(|err| AssembleError::ArgError(line_num, err))?; let len = arg_length(arg); let instr = (u8::from(*opcode) << 2) + len as u8; code[current_addr - start] = instr; @@ -261,8 +251,9 @@ fn generate_code<'a>( } current_addr += len + 1; } - VASMLine::Db(_, _) => { - let arg = line_args[&line_num]; + VASMLine::Db(_, arg) => { + let arg = eval(arg, line_num, &line_addresses, &scope) + .map_err(|err| AssembleError::ArgError(line_num, err))?; poke_word(&mut code, current_addr - start, arg); current_addr += 3; } @@ -288,61 +279,51 @@ mod test { use super::EvalError::*; use super::*; use crate::ast::VASMLine; + use crate::parse_error; use crate::vasm_parser::parse_vasm_line; - fn parse<'a>(lines: &'a [&str]) -> Vec> { + fn parse<'a, T: IntoIterator>(lines: T) -> Vec> { lines - .iter() - .map(|line| parse_vasm_line(*line).unwrap()) + .into_iter() + .map(|line| parse_vasm_line(line).unwrap()) .collect() } - fn place_labels_pass<'a>( - lines: &'a [&str], + fn place_labels_pass<'a, T: IntoIterator>( + lines: T, ) -> Result<(LineAddresses, Scope<'a>), AssembleError<'a>> { - let lines = parse(lines); - let scope = solve_equs(&lines).unwrap(); - let lengths = measure_instructions(&lines, &scope); - place_labels(&lines, scope, &lengths) + let parsed_lines = parse(lines); + let scope = solve_equs(&parsed_lines).unwrap(); + let lengths = measure_instructions(&parsed_lines, &scope); + place_labels(&parsed_lines, scope, &lengths) } - fn calculate_args_pass<'a>(lines: &'a [&str]) -> Result> { - let (line_addresses, scope) = place_labels_pass(lines)?; - let lines = parse(lines); - calculate_args(&lines, scope, &line_addresses) - } - - fn bounds<'a>(lines: &'a [&str]) -> Result<(usize, usize), AssembleError<'a>> { - let (line_addresses, scope) = place_labels_pass(lines)?; - let lines = parse(lines); - let lengths = measure_instructions(&lines, &scope); - code_bounds(&lines, &line_addresses, &lengths) - } - - fn generate_code_pass<'a>(lines: &'a [&str]) -> Result, AssembleError<'a>> { - let lines = parse(lines); - let scope = solve_equs(&lines).unwrap(); - let lengths = measure_instructions(&lines, &scope); - let (line_addresses, scope) = place_labels(&lines, scope, &lengths)?; - generate_code(&lines, scope, line_addresses, lengths) + fn bounds<'a, T: IntoIterator>( + lines: T, + ) -> Result<(usize, usize), AssembleError<'a>> { + let parsed_lines = parse(lines); + let scope = solve_equs(&parsed_lines).unwrap(); + let lengths = measure_instructions(&parsed_lines, &scope); + let (line_addresses, _scope) = place_labels(&parsed_lines, scope, &lengths)?; + code_bounds(&parsed_lines, &line_addresses, &lengths) } #[test] fn test_equs() { assert_eq!( - solve_equs(&parse(&["blah: .equ 5+3"])), + solve_equs(&parse(["blah: .equ 5+3"])), Ok([("blah", 8)].into()) ); assert_eq!( - solve_equs(&parse(&["blah: .equ 5", "foo: .equ 3"])), + solve_equs(&parse(["blah: .equ 5", "foo: .equ 3"])), Ok([("blah", 5), ("foo", 3)].into()) ); assert_eq!( - solve_equs(&parse(&["blah: .equ 5", "foo: .equ blah + 7"])), + solve_equs(&parse(["blah: .equ 5", "foo: .equ blah + 7"])), Ok([("blah", 5), ("foo", 12)].into()) ); assert_eq!( - solve_equs(&parse(&["add", "blah: .equ 5"])), + solve_equs(&parse(["add", "blah: .equ 5"])), Ok([("blah", 5)].into()) ); } @@ -350,15 +331,15 @@ mod test { #[test] fn test_unsolvable_equs() { assert_eq!( - solve_equs(&parse(&["blah: .equ 5", "foo: .equ banana"])), + solve_equs(&parse(["blah: .equ 5", "foo: .equ banana"])), Err(EquResolveError(2, "foo", MissingLabel("banana"))) ); assert_eq!( - solve_equs(&parse(&["blah: .equ foo+3", "foo: .equ 7"])), + solve_equs(&parse(["blah: .equ foo+3", "foo: .equ 7"])), Err(EquResolveError(1, "blah", MissingLabel("foo"))) ); assert_eq!( - solve_equs(&parse(&["blah: .equ 3", "blah: .equ 7"])), + solve_equs(&parse(["blah: .equ 3", "blah: .equ 7"])), Err(EquDuplicateError(2, "blah")) ); } @@ -367,22 +348,22 @@ mod test { fn test_lengths() { assert_eq!( measure_instructions( - &parse(&["add", "add 1", "add 500", "add 70000", "add -7"]), + &parse(["add", "add 1", "add 500", "add 70000", "add -7"]), &[].into() ), [(1, 1), (2, 2), (3, 3), (4, 4), (5, 4)].into() ); assert_eq!( - measure_instructions(&parse(&[".db 7", ".db \"hello\\0\""]), &[].into()), + measure_instructions(&parse([".db 7", ".db \"hello\\0\""]), &[].into()), [(1, 3), (2, 6)].into() ); assert_eq!( - measure_instructions(&parse(&[".org 256", "blah:", "foo: .equ 7"]), &[].into()), + measure_instructions(&parse([".org 256", "blah:", "foo: .equ 7"]), &[].into()), [(1, 0), (2, 0), (3, 0)].into() ); assert_eq!( measure_instructions( - &parse(&["add 2 + foo", "add 3 + blah", "jmpr @foo"]), + &parse(["add 2 + foo", "add 3 + blah", "jmpr @foo"]), &[("blah", 300)].into() ), [(1, 4), (2, 3), (3, 4)].into() @@ -392,25 +373,25 @@ mod test { #[test] fn test_place_labels() { assert_eq!( - place_labels_pass(&["start: .org 256", "add", "dup"]), + place_labels_pass(["start: .org 256", "add", "dup"]), Ok(( [(1, 256), (2, 256), (3, 257)].into(), [("start", 256)].into() )) ); assert_eq!( - place_labels_pass(&["push 70000", "dup"]), + place_labels_pass(["push 70000", "dup"]), Ok(([(1, 0), (2, 4)].into(), [].into())) ); assert_eq!( - place_labels_pass(&["start: .equ 256", "blah: .org start + 4", "add"]), + place_labels_pass(["start: .equ 256", "blah: .org start + 4", "add"]), Ok(( [(1, 0), (2, 260), (3, 260)].into(), [("blah", 260), ("start", 256)].into() )) ); assert_eq!( - place_labels_pass(&["start: .org 256", "blah: .org start + 10", "add"]), + place_labels_pass(["start: .org 256", "blah: .org start + 10", "add"]), Ok(( [(1, 256), (2, 266), (3, 266)].into(), [("blah", 266), ("start", 256)].into() @@ -421,81 +402,61 @@ mod test { #[test] fn test_unresolvable_orgs() { assert_eq!( - place_labels_pass(&[".org 0xffffff - blah"]), + place_labels_pass([".org 0xffffff - blah"]), Err(OrgResolveError(1, EvalError::MissingLabel("blah"))) ); assert_eq!( - place_labels_pass(&["blah: .org blah"]), + place_labels_pass(["blah: .org blah"]), Err(OrgResolveError(1, EvalError::MissingLabel("blah"))) ); } - #[test] - fn test_calculate_args() { - assert_eq!( - calculate_args_pass(&["add 3", "add 5"]), - Ok([(1, 3), (2, 5)].into()) - ); - assert_eq!( - calculate_args_pass(&["foo: .equ 4", "add 3+foo", "add foo+7"]), - Ok([(2, 7), (3, 11)].into()) - ); - assert_eq!( - calculate_args_pass(&[".org 0x400", "blah:", "jmp blah + 10"]), - Ok([(3, 1034)].into()) - ); - assert_eq!( - calculate_args_pass(&[".org 0x400", "blah:", "add -4", "jmp @blah + 10"]), - Ok([(3, -4), (4, 6)].into()) // blah is 1024, address of line 4 is 1028, so 6 - ); - assert_eq!( - calculate_args_pass(&[".org 0x400", "sub", "jmp $-1"]), - Ok([(3, 0x400)].into()) - ); - assert_eq!( - calculate_args_pass(&[".org 0x400", "sub", "agt -3", "brz @-2"]), - Ok([(3, -3), (4, -5)].into()) - ); - assert_eq!( - calculate_args_pass(&["add banana + 3"]), - Err(AssembleError::ArgError( - 1, - EvalError::MissingLabel("banana") - )) - ); - } - #[test] fn test_bounds() { - assert_eq!(bounds(&["add"]), Ok((0, 0))); - assert_eq!(bounds(&["add -4"]), Ok((0, 3))); - assert_eq!(bounds(&["add 7"]), Ok((0, 1))); - assert_eq!(bounds(&[".org 0x400", "add"]), Ok((1024, 1024))); + assert_eq!(bounds(["add"]), Ok((0, 0))); + assert_eq!(bounds(["add -4"]), Ok((0, 3))); + assert_eq!(bounds(["add 7"]), Ok((0, 1))); + assert_eq!(bounds([".org 0x400", "add"]), Ok((1024, 1024))); assert_eq!( - bounds(&["start: .equ 1024", ".org start", "add"]), + bounds(["start: .equ 1024", ".org start", "add"]), Ok((1024, 1024)) ); assert_eq!( - bounds(&[".org 0x400", "add", ".org 0x800"]), + bounds([".org 0x400", "add", ".org 0x800"]), Ok((1024, 1024)) ); assert_eq!( - bounds(&[".org 0x400", "add", ".org 0x800", ".db 5", "blah:"]), + bounds([".org 0x400", "add", ".org 0x800", ".db 5", "blah:"]), Ok((1024, 2050)) ); - assert_eq!(bounds(&[]), Err(AssembleError::NoCode)); - assert_eq!(bounds(&[".org 0x400"]), Err(AssembleError::NoCode)); + assert_eq!(bounds([]), Err(AssembleError::NoCode)); + assert_eq!(bounds([".org 0x400"]), Err(AssembleError::NoCode)); assert_eq!( - bounds(&["foo: .equ 3", ".org 0x400"]), + bounds(["foo: .equ 3", ".org 0x400"]), Err(AssembleError::NoCode) ); } #[test] fn test_generate_code() { - assert_eq!(generate_code_pass(&["add"]), Ok(vec![4])); - assert_eq!(generate_code_pass(&[".org 0x400", "add 7"]), Ok(vec![5, 7])); - assert_eq!(generate_code_pass(&[".db 57"]), Ok(vec![57, 0, 0])); - assert_eq!(generate_code_pass(&[".db \"AZ\0\""]), Ok(vec![65, 90, 0])); + assert_eq!(generate_code(parse(["add"])), Ok(vec![4])); + assert_eq!( + generate_code(parse([".org 0x400", "add 7"])), + Ok(vec![5, 7]) + ); + assert_eq!(generate_code(parse([".db 57"])), Ok(vec![57, 0, 0])); + assert_eq!(generate_code(parse([".db \"AZ\0\""])), Ok(vec![65, 90, 0])); + } + + #[test] + fn test_assemble() { + assert_eq!(assemble(["add"]), Ok(vec![4])); + assert_eq!( + assemble(["apple"]), + Err(ParseError( + 1, + parse_error::ParseError::InvalidInstruction("apple") + )) + ); } }