From ab2b0d3f8659eb2f6b7031ba06a668f619d07bb1 Mon Sep 17 00:00:00 2001 From: Ross Andrews Date: Wed, 16 Mar 2022 23:13:02 -0500 Subject: [PATCH] Evaluating expressions --- vasm/src/ast.rs | 9 +- vasm/src/lib.rs | 1 + vasm/src/vasm_evaluator.rs | 193 +++++++++++++++++++++++++++++++++++++ 3 files changed, 202 insertions(+), 1 deletion(-) create mode 100644 vasm/src/vasm_evaluator.rs diff --git a/vasm/src/ast.rs b/vasm/src/ast.rs index 8aca42d..a1d6eaf 100644 --- a/vasm/src/ast.rs +++ b/vasm/src/ast.rs @@ -5,6 +5,7 @@ use pest::iterators::Pair; use vcore::opcodes::Opcode; use crate::vasm_parser::Rule; +use std::fmt::{Display, Formatter}; /// A non-opcode directive to the assembler #[derive(Debug, PartialEq, Copy, Clone)] @@ -60,7 +61,7 @@ pub enum Node<'a> { RelativeLabel(&'a str), AbsoluteOffset(i32), RelativeOffset(i32), - String(String), + String(String), // TODO: this shouldn't exist Expr(Box>, Vec<(Operator, Node<'a>)>), } @@ -147,6 +148,12 @@ impl<'a> From> for Node<'a> { #[derive(Debug, PartialEq, Clone)] pub struct Label<'a>(pub &'a str); +impl<'a> Display for Label<'a> { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + write!(f, "{}", self.0) + } +} + #[derive(Debug, PartialEq, Clone)] pub enum VASMLine<'a> { Instruction(Option>, Opcode, Option>), diff --git a/vasm/src/lib.rs b/vasm/src/lib.rs index b7a14f4..8ad2349 100644 --- a/vasm/src/lib.rs +++ b/vasm/src/lib.rs @@ -4,4 +4,5 @@ extern crate pest_derive; pub mod ast; pub mod parse_error; +pub mod vasm_evaluator; pub mod vasm_parser; diff --git a/vasm/src/vasm_evaluator.rs b/vasm/src/vasm_evaluator.rs new file mode 100644 index 0000000..d0c5526 --- /dev/null +++ b/vasm/src/vasm_evaluator.rs @@ -0,0 +1,193 @@ +use crate::ast::{Node, Operator}; +use std::collections::BTreeMap; +use std::fmt::{Display, Formatter}; + +#[derive(Debug, Clone, PartialEq)] +pub enum EvalError<'a> { + MissingLabel(&'a str), + UnknownAddress(i32), +} + +impl<'a> Display for EvalError<'a> { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + match self { + EvalError::MissingLabel(label) => write!(f, "Unable to resolve label {}", label), + EvalError::UnknownAddress(line_num) => write!( + f, + "Unable to calculate starting address of line {}", + line_num + ), + } + } +} + +pub fn eval<'a>( + node: Node<'a>, + line_num: i32, + line_addresses: &BTreeMap, + scope: &BTreeMap<&'a str, i32>, +) -> Result> { + match node { + Node::Number(n) => Ok(n), + Node::Label(label) => scope + .get(label) + .map_or_else(|| Err(EvalError::MissingLabel(label)), |val| Ok(*val)), + Node::RelativeLabel(label) => { + if let Some(address) = line_addresses.get(&line_num) { + scope.get(label).map_or_else( + || Err(EvalError::MissingLabel(label)), + |val| Ok(*val - address), + ) + } else { + Err(EvalError::UnknownAddress(line_num)) + } + } + Node::AbsoluteOffset(offset) => { + if let Some(dest_address) = line_addresses.get(&(line_num + offset)) { + Ok(*dest_address) + } else { + Err(EvalError::UnknownAddress(line_num + offset)) + } + } + Node::RelativeOffset(offset) => { + if let (Some(line_address), Some(dest_address)) = ( + line_addresses.get(&line_num), + line_addresses.get(&(line_num + offset)), + ) { + Ok(*dest_address - *line_address) + } else { + Err(EvalError::UnknownAddress(line_num + offset)) + } + } + Node::String(_) => { + unreachable!() + } + Node::Expr(car, cdr) => { + let car = eval(*car, line_num, line_addresses, scope); + if let Ok(mut acc) = car { + for (op, node) in cdr { + if let Ok(rhs) = eval(node, line_num, line_addresses, scope) { + match op { + Operator::Add => acc += rhs, + Operator::Sub => acc -= rhs, + Operator::Mul => acc *= rhs, + Operator::Div => acc /= rhs, + Operator::Mod => acc %= rhs, + } + } + } + Ok(acc) + } else { + car + } + } + } +} + +#[cfg(test)] +mod test { + use super::*; + use crate::ast::VASMLine; + use crate::vasm_parser::parse_vasm_line; + + fn test_eval(line: &str) -> Result { + test_scope_addresses_eval(BTreeMap::new(), [(1, 0x400)].into(), line) + } + + fn test_scope_eval<'a>( + scope: BTreeMap<&'a str, i32>, + line: &'a str, + ) -> Result> { + test_scope_addresses_eval(scope, [(1, 0x400)].into(), line) + } + + fn test_addresses_eval( + line_addresses: BTreeMap, + line: &str, + ) -> Result { + test_scope_addresses_eval([].into(), line_addresses, line) + } + + fn test_scope_addresses_eval<'a>( + scope: BTreeMap<&'a str, i32>, + line_addresses: BTreeMap, + line: &'a str, + ) -> Result> { + if let Ok(VASMLine::Instruction(_, _, Some(arg))) = parse_vasm_line(line) { + eval(arg, 1, &line_addresses, &scope) + } else { + panic!("Failed to parse an instruction line with an argument") + } + } + + #[test] + fn test_arithmetic() { + assert_eq!(test_eval("add 4"), Ok(4)); + assert_eq!(test_eval("add 2 + 3"), Ok(5)); + assert_eq!(test_eval("add 6 - 3 - 1"), Ok(2)); + assert_eq!(test_eval("add 6 - (3 - 1)"), Ok(4)); + assert_eq!(test_eval("add 6 / (3-1) * 7"), Ok(21)); + assert_eq!(test_eval("add (1+2+4) % 5"), Ok(2)); + } + + #[test] + fn test_labels() { + assert_eq!(test_scope_eval([("apple", 5)].into(), "add apple"), Ok(5)); + assert_eq!( + test_scope_eval([("apple", 5)].into(), "add apple + 7"), + Ok(12) + ); + assert_eq!( + test_scope_eval([("apple", 5), ("banana", 3)].into(), "add apple * banana"), + Ok(15) + ); + assert_eq!( + test_scope_eval([("apple", 5)].into(), "add banana"), + Err(EvalError::MissingLabel("banana")) + ); + } + + #[test] + fn test_relative_labels() { + assert_eq!( + test_scope_eval([("apple", 0x500)].into(), "jmpr @apple"), + Ok(0x100) + ); + assert_eq!( + test_scope_eval([("apple", 0x300)].into(), "jmpr @apple"), + Ok(-256) + ); + assert_eq!( + test_scope_addresses_eval([("apple", 0x300)].into(), [].into(), "jmpr @apple"), + Err(EvalError::UnknownAddress(1)) + ); + } + + #[test] + fn test_absolute_offset() { + assert_eq!( + test_addresses_eval([(4, 0x410)].into(), "jmp $+3"), + Ok(0x410) + ); + assert_eq!( + test_addresses_eval([(4, 0x410)].into(), "jmp $+1"), + Err(EvalError::UnknownAddress(2)) + ); + } + + #[test] + fn test_relative_offset() { + assert_eq!( + test_addresses_eval([(1, 0x400), (4, 0x410)].into(), "brz @+3"), + Ok(0x10) + ); + assert_eq!( + test_addresses_eval([(4, 0x410)].into(), "brz @+2"), + Err(EvalError::UnknownAddress(3)) + ); + assert_eq!( + test_addresses_eval([(1, 0x400)].into(), "brz @+7"), + Err(EvalError::UnknownAddress(8)) + ); + } +}