From 16114b2ca358dbbef09cb5a8d3a843a0e11a3b88 Mon Sep 17 00:00:00 2001 From: Miquel Sabaté Solà Date: Wed, 9 Oct 2024 15:41:30 +0200 Subject: Adapt the assembler to the changes on the parser MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit As of 184c39579227 ("Re-work the parser from scratch") the parser has a proper AST, and the assembler has to traverse it accordingly. Moreover, the assembler has been stripped from a lot of unneeded memory allocations and it's overall more keen on using mere references when possible. Signed-off-by: Miquel Sabaté Solà --- lib/xixanta/src/assembler.rs | 2886 ++++++++++++++++-------------------------- 1 file changed, 1084 insertions(+), 1802 deletions(-) (limited to 'lib/xixanta/src/assembler.rs') diff --git a/lib/xixanta/src/assembler.rs b/lib/xixanta/src/assembler.rs index 1e9854d..46d3d94 100644 --- a/lib/xixanta/src/assembler.rs +++ b/lib/xixanta/src/assembler.rs @@ -1,227 +1,610 @@ -use crate::context::{Context, PValue}; -use crate::errors::ParseError; -use crate::instruction::{AddressingMode, Bundle}; -use crate::mapping::{Mapping, Segment}; -use crate::opcodes::INSTRUCTIONS; -use crate::parser::{NodeType, PNode, Parser}; +use crate::context::Context; +use crate::errors::{Error, EvalError}; +use crate::mapping::Segment; +use crate::node::{NodeType, PNode, PString}; +use crate::opcodes::{AddressingMode, INSTRUCTIONS}; +use crate::parser::Parser; +use std::cmp::Ordering; use std::collections::HashMap; use std::io::Read; +use std::ops::Range; -// TODO: proper AST: WRITE_PPU_DATA from NES is a good example -// TODO: for christ's sake, automated tests! -// TODO: macros are meant to be global! -// TODO: instead of mapping.nodes having a value of vec, the value should be a Context. -// TODO: proc's, labels, macros, and scopes can be merged dramatically. -// TODO: allow pointer arithmetic (e.g. 'adc #List::ptr + 1'). -// TODO: more to_owned() stuff, more rustacean way of doing things, more ... -// TODO: warning on empty segments +/// A Bundle represents a set of bytes that can be encoded as binary data. +#[derive(Debug, Default, Clone)] +pub struct Bundle { + /// The bytes which make up any encodable element for the application. The + /// capacity is of three bytes maximum, but the actual size is encoded in + /// the `size` property. + pub bytes: [u8; 3], -type Result = std::result::Result; + /// The amount of bytes which have actually been set on this bundle. + pub size: u8, -#[derive(Debug, Clone, PartialEq)] + /// The address where the given bytes are to be placed on the resulting + /// binary file. + pub address: usize, + + /// If this bundle encodes an instruction, the amount of cycles it takes for + /// the CPU to actually execute it. + pub cycles: u8, + + /// Whether the cost in cycles is affected when crossing a page boundary. + pub affected_on_page: bool, +} + +#[derive(Clone, PartialEq)] pub enum LiteralMode { Hexadecimal, Binary, Plain, } +// TODO: is it really necessary to be this fully fledged? +#[derive(PartialEq)] +pub enum Stage { + Init, + Parsing, + Context, + Unrolling, + Bundling, +} + +#[derive(Clone, Debug)] +pub struct Macro { + nodes: Range, + args: Vec, +} + pub struct Assembler { - line: usize, - column: usize, context: Context, literal_mode: Option, - only_context: bool, - force_decimal: bool, - mapping: Mapping, - offsets: HashMap, + stage: Stage, + macros: HashMap, } -// Control statements which end up affecting which context we are in. -const TOUCH_CONTEXT: [&str; 7] = [ - ".scope", - ".endscope", - ".proc", - ".endproc", - ".macro", - ".endmacro", - ".segment", -]; - impl Assembler { pub fn new(segments: Vec) -> Self { - assert!(segments.len() > 0); + assert!(!segments.is_empty()); + // TODO let mut offsets = HashMap::new(); - for segment in &segments { - offsets.insert(segment.name.clone(), 0); + for segment in segments { + offsets.insert(segment.name, 0); } + // TODO Self { - line: 0, - column: 0, - literal_mode: None, - only_context: false, - force_decimal: false, context: Context::new(), - mapping: Mapping::new(segments), - offsets, + literal_mode: None, + stage: Stage::Init, + macros: HashMap::new(), } } - pub fn reset(&mut self) { - self.line = 0; - self.column = 0; - self.context = Context::new(); - self.mapping.reset(); - - self.offsets = HashMap::new(); - for segment in &self.mapping.segments { - self.offsets.insert(segment.name.clone(), 0); + pub fn assemble(&mut self, reader: impl Read) -> Result, Vec> { + // First of all, parse the input so we get a list of nodes we can work + // with. + self.stage = Stage::Parsing; + let mut parser = Parser::default(); + if let Err(errors) = parser.parse(reader) { + return Err(errors.iter().map(|e| Error::Parse(e.clone())).collect()); } - } - pub fn assemble(&mut self, reader: impl Read) -> Result> { - let mut res = vec![]; + // Build the context by iterating over the parsed nodes and checking + // where scopes start/end, evaluating values for variables, labels, etc. + self.stage = Stage::Context; + self.eval_context(&parser.nodes)?; - let mut parser = Parser::new(); - parser.parse(reader)?; + // TODO: unroll macros, fill out labels, etc. + self.stage = Stage::Unrolling; - // println!("{:#?}", parser.nodes); + // Finally convert the relevant nodes into binary bundles which can be + // used by the caller. + self.stage = Stage::Bundling; + self.bundle(&parser.nodes) + } - // NOTE: first step: unroll macros, update context, set variables. + pub fn eval_context(&mut self, nodes: &[PNode]) -> Result<(), Vec> { + let mut errors = Vec::new(); + let mut current_macro = None; - self.only_context = true; - for node in parser.nodes.clone() { + for (idx, node) in nodes.iter().enumerate() { + // TODO: initilize labels on each scope. match node.node_type { NodeType::Assignment => { - self.evaluate_assignment(node)?; - - println!("{:#?}", self.context); + // TODO: in fact, we cannot have assignments in many places. + if current_macro.is_some() { + errors.push(Error::Eval(EvalError { + message: "cannot have assignments inside of macro definitions" + .to_string(), + line: node.value.line, + })); + continue; + } + match self.evaluate_node(node.left.as_ref().unwrap()) { + Ok(value) => { + if let Err(err) = self.context.set_variable(&node.value, &value) { + errors.push(Error::Context(err)); + } + } + Err(e) => errors.push(Error::Eval(e)), + } } NodeType::Control => { - self.evaluate_control(node)?; + // TODO: prevent nesting of control statements depending on + // a definition (e.g. .macro's cannot be nested inside of + // another control statement, but .if yes). + let id = node.value.value.as_str(); + + if id == ".macro" { + // TODO: macros are only on the global scope. + // + // TODO: boy this is ugly. In fact, this stupid shit if + // current_macro might not be relevant anymore. + current_macro = Some(&node.left.as_ref().unwrap().value); + // TODO: watch out for weird shit on the name of arguments. + self.macros + .entry(node.left.as_ref().unwrap().value.value.clone()) + .or_insert(Macro { + nodes: Range { + start: idx + 1, + end: idx + 1, + }, + args: node + .args + .clone() + .unwrap_or_default() + .into_iter() + .map(|a| a.value) + .collect::>(), + }); + } else if id == ".endmacro" { + // TODO: if m.nodes.start < idx - 1 => empty macro + + if let Some(name) = current_macro { + self.macros + .entry(name.value.clone()) + .and_modify(|m| m.nodes.end = idx - 1); + } + current_macro = None; + } + if let Err(err) = self.context.change_context(node) { + // TODO: forbid if inside_macro + errors.push(Error::Context(err)); + } } _ => {} } } - self.only_context = false; - - // Check for unclosed scope definition. - if !self.context.is_global() { - return Err(self.parser_error( - format!( - "definition for '{}' has not been closed", - self.context.name() - ) - .as_str(), - )); + + if errors.is_empty() { + Ok(()) + } else { + Err(errors) } + } - // NOTE: second step: let's rock. + pub fn bundle(&mut self, nodes: &Vec) -> Result, Vec> { + let mut bundles = Vec::new(); + let mut errors = Vec::new(); + let mut inside_macro = false; - for node in parser.nodes { + for node in nodes { match node.node_type { NodeType::Instruction => { - res.push(self.evaluate_node(node)?); + if !inside_macro { + match self.evaluate_node(node) { + Ok(bundle) => bundles.push(bundle), + Err(e) => errors.push(Error::Eval(e)), + } + } } NodeType::Control => { - self.evaluate_control(node)?; + let id = node.value.value.as_str(); + + // TODO: skip macros altogether. + if id == ".macro" { + if inside_macro { + errors.push(Error::Eval(EvalError { + message: "nesting macros is forbidden".to_string(), + line: node.value.line, + })); + continue; + } + inside_macro = true; + } else if id == ".endmacro" { + inside_macro = false; + } else if let Err(err) = self.context.change_context(node) { + // TODO: forbid if inside_macro + errors.push(Error::Context(err)); + } + } + NodeType::Value | NodeType::Call => { + if let Err(e) = self.bundle_call(node, nodes, &mut bundles) { + errors.push(Error::Eval(e)); + } } + _ => {} } } - // NOTE: third step: update addresses of referenced labels. - // TODO - - // println!("{:#?}", res); - Ok(res) + if errors.is_empty() { + Ok(bundles) + } else { + Err(errors) + } } - // pub fn disassemble(&mut self, reader: impl Read) -> Result> { - // self.from_byte_reader(reader)?; - - // let mut instructions: Vec<&dyn Encodable> = vec![]; - // for node in self.mapping.current() { - // println!("{:#?}", node); - // match node { - // Node::Instruction(instr) => instructions.push(instr), - // Node::Literal(lit) => instructions.push(lit), - // _ => {} - // } - // } - - // Ok(instructions) - // } - - fn evaluate_assignment(&mut self, node: Box) -> Result<()> { - if self - .context - .current_mut() - .unwrap() - .contains_key(&node.value.value) - { - return Err(ParseError { - line: self.line, + fn bundle_call( + &mut self, + node: &PNode, + nodes: &[PNode], + bundles: &mut Vec, + ) -> Result<(), EvalError> { + // Get the macro object for the given identifier. + let mcr = self + .macros + .get(&node.value.value) + .ok_or(EvalError { + line: node.value.line, + message: format!( + "could not find a macro with the name '{}'", + node.value.value + ), + })? + .clone(); + + // Detect missmatches between the number of arguments provided and the + // ones defined by the macro. + let args = node.args.as_ref(); + let nargs = match args { + Some(v) => v.len(), + None => 0, + }; + if mcr.args.len() != nargs { + return Err(EvalError { + line: node.value.line, message: format!( - "variable '{}' is being re-assigned: it was previously defined in line {}", - node.value.value, node.value.line, + "wrong number of arguments for '{}': {} required but {} given", + node.value.value, + mcr.args.len(), + nargs ), - parse: false, }); } - if let Some(value_node) = node.left { - self.force_decimal = true; - println!("{:#?}", value_node); - let val = self.evaluate_node(value_node.clone())?; - println!("{:#?}", val); - self.force_decimal = false; - - self.context.current_mut().unwrap().insert( - node.value.value.to_owned(), - PValue { - node: *value_node, - value: val, - label: false, - }, - ); + // If there are arguments defined by the macro, set their values now. + if nargs > 0 { + let mut margs = mcr.args.iter(); + + for (idx, arg) in args.unwrap().iter().enumerate() { + let bundle = self.evaluate_node(arg)?; + self.context + .set_variable(margs.nth(idx).unwrap(), &bundle)?; + } + } + + // And now replicate the nodes as contained inside of the macro + // definition. + for node in nodes + .get(mcr.nodes.start..=mcr.nodes.end) + .unwrap_or_default() + { + bundles.push(self.evaluate_node(node)?); } Ok(()) } - fn evaluate_node(&mut self, node: Box) -> Result { + fn evaluate_node(&mut self, node: &PNode) -> Result { match node.node_type { - NodeType::Control => self.evaluate_control(node), - NodeType::Literal => self.evaluate_literal(node), - NodeType::Instruction => self.evaluate_instruction(node), + NodeType::Instruction => Ok(self.evaluate_instruction(node)?), + NodeType::Literal => Ok(self.evaluate_literal(node)?), + NodeType::Control => Ok(self.evaluate_control(node)?), NodeType::Value => match self.literal_mode { - Some(LiteralMode::Hexadecimal) => self.evaluate_hexadecimal(node), - Some(LiteralMode::Binary) => self.evaluate_binary(node), - Some(LiteralMode::Plain) => self.evaluate_decimal(node), + Some(LiteralMode::Hexadecimal) => Ok(self.evaluate_hexadecimal(node)?), + Some(LiteralMode::Binary) => Ok(self.evaluate_binary(node)?), + Some(LiteralMode::Plain) => Ok(self.evaluate_decimal(node)?), None => { - if self.force_decimal { - self.evaluate_decimal(node) + if self.stage == Stage::Context { + // If we are just evaluating the context (e.g. parsing a + // variable), we'll assume that non-prefixed literals are + // just decimal values. + Ok(self.evaluate_decimal(node)?) + } else if node.value.is_valid_identifier(true).is_err() { + // If this is not a valid identifier, just error out. + Err(EvalError { + message: "no prefix was given to operand".to_string(), + line: node.value.line, + }) } else { - Err(self.parser_error("no prefix was given to operand")) + // This is actually a valid identifier! Try to fetch the + // variable. + match self.evaluate_variable(&node.value) { + Ok(v) => { + self.literal_mode = Some(LiteralMode::Hexadecimal); + Ok(v) + } + Err(err) => Err(EvalError { + message: format!( + "no prefix was given to operand and {} either", + err.message + ), + line: node.value.line, + }), + } } } }, - // TODO - _ => Ok(Bundle::new()), + _ => Err(EvalError { + message: format!("unexpected '{}' expression type", node.node_type), + line: node.value.line, + }), } } - fn evaluate_instruction(&mut self, node: Box) -> Result { - let mnemonic = node.value.value.to_lowercase(); + fn evaluate_hexadecimal(&mut self, node: &PNode) -> Result { + let mut chars = node.value.value.chars(); + let mut bytes = [0, 0, 0]; + let size: u8; + + match node.value.value.len() { + 1 => { + bytes[0] = self.char_to_hex(chars.next(), node)?; + size = 1; + } + 2 => { + bytes[0] = self.char_to_hex(chars.next(), node)? * 16; + bytes[0] += self.char_to_hex(chars.next(), node)?; + size = 1; + } + 3 => { + bytes[1] = self.char_to_hex(chars.next(), node)?; + bytes[0] = self.char_to_hex(chars.next(), node)? * 16; + bytes[0] += self.char_to_hex(chars.next(), node)?; + size = 2; + } + 4 => { + bytes[1] = self.char_to_hex(chars.next(), node)? * 16; + bytes[1] += self.char_to_hex(chars.next(), node)?; + bytes[0] = self.char_to_hex(chars.next(), node)? * 16; + bytes[0] += self.char_to_hex(chars.next(), node)?; + size = 2; + } + _ => { + if self.evaluate_variable(&node.value).is_ok() { + return Err(EvalError { + message: format!( + "you cannot use variables like '{}' in hexadecimal literals", + node.value.value + ), + line: node.value.line, + }); + } + return Err(EvalError { + message: "expecting a number of 1 to 4 hexadecimal digits".to_string(), + line: node.value.line, + }); + } + } + + Ok(Bundle { + bytes, + size, + address: 0, + cycles: 0, + affected_on_page: false, + }) + } + + fn evaluate_binary(&mut self, node: &PNode) -> Result { + let string = node.value.value.as_str(); + let mut value = 0; + let mut shift = 0; + + for c in string.chars().rev() { + if c == '1' { + let val = 1 << shift; + value += val; + } else if c != '0' { + if self.evaluate_variable(&node.value).is_ok() { + return Err(EvalError { + message: format!( + "you cannot use variables like '{}' in binary literals", + string + ), + line: node.value.line, + }); + } + return Err(EvalError { + message: format!("bad binary format for '{}'", string), + line: node.value.line, + }); + } + + shift += 1; + } + + match shift.cmp(&8) { + Ordering::Less => Err(EvalError { + message: "missing binary digits to get a full byte".to_string(), + line: node.value.line, + }), + Ordering::Greater => Err(EvalError { + message: "too many binary digits for a single byte".to_string(), + line: node.value.line, + }), + Ordering::Equal => Ok(Bundle { + bytes: [value as u8, 0, 0], + size: 1, + address: 0, + cycles: 0, + affected_on_page: false, + }), + } + } + + fn evaluate_decimal(&mut self, node: &PNode) -> Result { + let string = node.value.value.as_str(); + if string.is_empty() { + return Err(EvalError { + message: "empty decimal literal".to_string(), + line: node.value.line, + }); + } + + let mut value = 0; + let mut shift = 1; + + for c in string.chars().rev() { + if shift > 100 { + return Err(EvalError { + message: "decimal value is too big".to_string(), + line: node.value.line, + }); + } + if c != '0' { + match c.to_digit(10) { + Some(digit) => { + value += digit * shift; + } + None => { + if self.stage == Stage::Context { + return Err(EvalError { + message: format!( + "variables must come from a constant expression, \ + you cannot use other variables such as '{}' \ + in variable definitions", + string + ), + line: node.value.line, + }); + } + match self.evaluate_variable(&node.value) { + Ok(v) => return Ok(v), + Err(err) => { + return Err(EvalError { + message: format!( + "'{}' is not a decimal value and {} either", + c, err.message + ), + line: node.value.line, + }) + } + } + } + } + } + + shift *= 10; + } + if value > 255 { + return Err(EvalError { + message: "decimal value is too big".to_string(), + line: node.value.line, + }); + } + + Ok(Bundle { + bytes: [value as u8, 0, 0], + size: 1, + address: 0, + cycles: 0, + affected_on_page: false, + }) + } - let (mode, mut bundle) = if node.left.is_some() { - self.get_addressing_mode_and_bytes(node)? + fn evaluate_literal(&mut self, node: &PNode) -> Result { + // The value of the literal is guaranteed to not be empty by the parser. + // If that's not the case, then it's a bug. + let val = node.value.value.as_str(); + assert!(!val.is_empty(), "the value for the literal was empty!"); + + // Pick up the left node, which is the node to be further evaluated, and + // determine the literal mode to be used. + let left = node.left.as_ref().unwrap(); + + let lm; + if val.starts_with('$') { + lm = Some(LiteralMode::Hexadecimal); + if left.node_type == NodeType::Literal { + return Err(EvalError { + message: "literal cannot embed another literal".to_string(), + line: node.value.line, + }); + } + } else if val.starts_with('%') { + lm = Some(LiteralMode::Binary); + if left.node_type == NodeType::Literal { + return Err(EvalError { + message: "literal cannot embed another literal".to_string(), + line: node.value.line, + }); + } } else { - (AddressingMode::Implied, Bundle::new()) + lm = Some(LiteralMode::Plain); + } + + // And evaluate the left node. + self.literal_mode = lm.clone(); + let expr = self.evaluate_node(left)?; + self.literal_mode = lm; + + Ok(expr) + } + + fn char_to_hex(&mut self, oc: Option, source: &PNode) -> Result { + match oc { + Some(c) => match c.to_digit(16) { + Some(c) => Ok(c as u8), + None => { + if (c.is_alphabetic() || c == '_') + && self.evaluate_variable(&source.value).is_ok() + { + return Err(EvalError { + message: format!( + "you cannot use variables like '{}' in hexadecimal literals", + source.value.value + ), + line: source.value.line, + }); + } + Err(EvalError { + message: "could not convert digit to hexadecimal".to_string(), + line: source.value.line, + }) + } + }, + None => Err(EvalError { + message: "digit out of bounds".to_string(), + line: source.value.line, + }), + } + } + + fn evaluate_control(&mut self, node: &PNode) -> Result { + println!("NODE: {:#?}", node); + Ok(Bundle::default()) + } + + fn evaluate_variable(&mut self, id: &PString) -> Result { + match self.context.get_variable(id) { + Ok(value) => Ok(value), + Err(e) => Err(EvalError { + message: e.message, + line: id.line, + }), + } + } + + fn evaluate_instruction(&mut self, node: &PNode) -> Result { + let (mode, mut bundle) = match &node.left { + Some(_) => self.get_addressing_mode_and_bytes(node)?, + None => (AddressingMode::Implied, Bundle::default()), }; + let mnemonic = node.value.value.to_lowercase(); match INSTRUCTIONS.get(&mnemonic) { Some(entries) => match entries.get(&mode) { Some(values) => { @@ -233,17 +616,20 @@ impl Assembler { bundle.bytes[0] = values.opcode.to_le_bytes()[0]; } None => { - return Err(self.parser_error( - format!( + return Err(EvalError { + message: format!( "cannot use {} addressing mode for the instruction '{}'", mode, mnemonic - ) - .as_str(), - )) + ), + line: node.value.line, + }) } }, None => { - return Err(self.parser_error(format!("unknown instruction {}", mnemonic).as_str())) + return Err(EvalError { + message: format!("unknown instruction {}", mnemonic), + line: node.value.line, + }); } } Ok(bundle) @@ -251,78 +637,101 @@ impl Assembler { fn get_addressing_mode_and_bytes( &mut self, - node: Box, - ) -> Result<(AddressingMode, Bundle)> { - if node.clone().left.unwrap().node_type == NodeType::Indirection { + node: &PNode, + ) -> Result<(AddressingMode, Bundle), EvalError> { + let left = &node.left; + + if left.as_ref().unwrap().node_type == NodeType::Indirection { self.get_from_indirect(node) } else if node.right.is_some() { self.get_from_indexed(node) } else { - self.get_from_left(node) + self.get_from_left(left.as_ref().unwrap()) } } - fn get_from_indirect(&mut self, node: Box) -> Result<(AddressingMode, Bundle)> { - let left = node.left.unwrap(); + fn get_from_indirect(&mut self, node: &PNode) -> Result<(AddressingMode, Bundle), EvalError> { + let left = node.left.as_ref().unwrap(); - match node.right { + match node.right.as_ref() { Some(right) => { if right.value.value.trim().to_lowercase() == "y" { if left.right.is_some() { - return Err(self.parser_error( - "it has to be either X addressing or Y addressing, not all at once", - )); + return Err(EvalError { + message: + "it has to be either X addressing or Y addressing, not all at once" + .to_string(), + line: node.value.line, + }); } - let val = self.evaluate_node(left.left.unwrap())?; + let val = self.evaluate_node(left.left.as_ref().unwrap())?; if val.size != 1 { - return Err(self.parser_error( - "address can only be one byte long on indirect Y addressing", - )); + return Err(EvalError { + message: "address can only be one byte long on indirect Y addressing" + .to_string(), + line: node.value.line, + }); } return Ok((AddressingMode::IndirectY, val)); } - return Err( - self.parser_error("only the Y index is allowed on indirect Y addressing") - ); + Err(EvalError { + message: "only the Y index is allowed on indirect Y addressing".to_string(), + line: node.value.line, + }) } - None => match left.right { + None => match left.right.as_ref() { Some(right) => { if right.value.value.trim().to_lowercase() == "x" { - let val = self.evaluate_node(left.left.unwrap())?; + let val = self.evaluate_node(left.left.as_ref().unwrap())?; if val.size != 1 { - return Err(self.parser_error( - "address can only be one byte long on indirect X addressing", - )); + return Err(EvalError { + message: + "address can only be one byte long on indirect X addressing" + .to_string(), + line: node.value.line, + }); } return Ok((AddressingMode::IndirectX, val)); } - return Err( - self.parser_error("only the X index is allowed on indirect X addressing") - ); + Err(EvalError { + message: "only the X index is allowed on indirect X addressing".to_string(), + line: node.value.line, + }) } None => { - let val = self.evaluate_node(left.left.unwrap())?; + let val = self.evaluate_node(left.left.as_ref().unwrap())?; if val.size != 2 { - return Err(self.parser_error("expecting a full 16-bit address")); + return Err(EvalError { + message: "expecting a full 16-bit address".to_string(), + line: node.value.line, + }); } - return Ok((AddressingMode::Indirect, val)); + Ok((AddressingMode::Indirect, val)) } }, } } - fn get_from_indexed(&mut self, node: Box) -> Result<(AddressingMode, Bundle)> { - self.literal_mode = None; // TODO: needed? - let val = self.evaluate_node(node.left.unwrap())?; + fn get_from_indexed(&mut self, node: &PNode) -> Result<(AddressingMode, Bundle), EvalError> { + // Evaluate the left arm of the instruction. + let left = node.left.as_ref().unwrap(); + let val = self.evaluate_node(left)?; + // Ensure that the literal mode for the left arm ensures an address + // instead of some bogus number. if let Some(lm) = &self.literal_mode { if *lm != LiteralMode::Hexadecimal { - return Err(self.parser_error("indexed addressing only works with addresses")); + return Err(EvalError { + message: "indexed addressing only works with addresses".to_string(), + line: node.value.line, + }); } } - match node.right.unwrap().value.value.to_lowercase().trim() { + // Check the right arm to know the index being used. + let right = node.right.as_ref().unwrap(); + match right.value.value.to_lowercase().trim() { "x" => { if val.size == 1 { Ok((AddressingMode::ZeropageIndexedX, val)) @@ -337,20 +746,19 @@ impl Assembler { Ok((AddressingMode::IndexedY, val)) } } - _ => Err(self.parser_error("can only use X and Y as indices")), + _ => Err(EvalError { + message: "can only use X and Y as indices".to_string(), + line: node.value.line, + }), } } - fn get_from_left(&mut self, node: Box) -> Result<(AddressingMode, Bundle)> { - let left = node.left.unwrap(); - - if left.value.value.to_lowercase().trim() == "a" { - return Ok((AddressingMode::Implied, Bundle::new())); + fn get_from_left(&mut self, left_arm: &PNode) -> Result<(AddressingMode, Bundle), EvalError> { + if left_arm.value.value.to_lowercase().trim() == "a" { + return Ok((AddressingMode::Implied, Bundle::default())); } - self.literal_mode = None; // TODO: needed? - let val = self.evaluate_node(left)?; - + let val = self.evaluate_node(left_arm)?; match self.literal_mode { Some(LiteralMode::Hexadecimal) => { if val.size == 1 { @@ -361,1692 +769,631 @@ impl Assembler { } Some(LiteralMode::Plain) => { if val.size > 1 { - Err(self.parser_error("immediate is too big")) + Err(EvalError { + message: "immediate is too big".to_string(), + line: left_arm.value.line, + }) } else { Ok((AddressingMode::Immediate, val)) } } - _ => { - Err(self - .parser_error("left arm of instruction is neither an address nor an immediate")) - } - } - } - - fn evaluate_control(&mut self, node: Box) -> Result { - let id = node.value.value.to_lowercase(); - let id_str = id.as_str(); - - // If we are just dealing with context resolution/assignment and the - // current control statement does not matter on that regard, just skip - // it. - // if self.only_context && !TOUCH_CONTEXT.contains(&id_str) { - // return Ok(Bundle::new()); - // } - - match id_str { - ".hibyte" => self.evaluate_hilo_byte(node.args.unwrap_or(vec![]), true), - ".lobyte" => self.evaluate_hilo_byte(node.args.unwrap_or(vec![]), false), - ".scope" => self.evaluate_scope_definition(node), - ".endscope" => self.evaluate_scope_end(), - // ".segment" => self.parse_segment_definition(&id, line), - // ".byte" | ".db" => self.parse_literal_bytes(&id, line, false), - // ".word" | ".dw" | ".addr" => self.parse_literal_bytes(&id, line, true), - // ".proc" => self.parse_proc_definition(&id, line), - // ".endproc" => self.parse_proc_end(&id), - // ".macro" => self.parse_macro_definition(&id, line), - // ".endmacro" => self.parse_macro_end(&id), - _ => Err(self.parser_error(format!("unknown control statement '{}'", id).as_str())), + _ => Err(EvalError { + message: "left arm of instruction is neither an address nor an immediate" + .to_string(), + line: left_arm.value.line, + }), } } +} - fn evaluate_literal(&mut self, node: Box) -> Result { - let mut prev = None; - self.literal_mode = None; - - let ret = match node.value.value.chars().nth(0) { - Some(prefix) => { - if prefix == '$' { - prev = Some(LiteralMode::Hexadecimal); - } else if prefix == '%' { - prev = Some(LiteralMode::Binary); - } else { - prev = Some(LiteralMode::Plain); - } - self.literal_mode = prev.clone(); - self.evaluate_node(node.left.unwrap()) - } - None => Err(self.parser_error("no prefix was given to operand")), - }; - - self.literal_mode = prev; +#[cfg(test)] +mod tests { + use super::*; + use crate::mapping::EMPTY; - ret - } + fn assert_instruction(line: &str, hex: &[u8]) { + let mut asm = Assembler::new(EMPTY.to_vec()); + let res = asm.assemble(line.as_bytes()).unwrap(); - fn evaluate_hexadecimal(&mut self, node: Box) -> Result { - let mut chars = node.value.value.chars(); - let mut bytes = [0, 0, 0]; - let size: u8; + assert_eq!(res.len(), 1); - match node.value.value.len() { - 1 => { - bytes[0] = self.char_to_hex(chars.next())?; - size = 1; - } - 2 => { - bytes[0] = self.char_to_hex(chars.next())? * 16; - bytes[0] += self.char_to_hex(chars.next())?; - size = 1; - } - 3 => { - bytes[1] = self.char_to_hex(chars.next())?; - bytes[0] = self.char_to_hex(chars.next())? * 16; - bytes[0] += self.char_to_hex(chars.next())?; - size = 2; - } - 4 => { - bytes[1] = self.char_to_hex(chars.next())? * 16; - bytes[1] += self.char_to_hex(chars.next())?; - bytes[0] = self.char_to_hex(chars.next())? * 16; - bytes[0] += self.char_to_hex(chars.next())?; - size = 2; - } - _ => return Err(self.parser_error("expecting a number of 1 to 4 hexadecimal digits")), + for i in 0..res[0].size { + assert_eq!(hex[i as usize], res[0].bytes[i as usize]); } - - Ok(Bundle { - bytes, - size, - address: 0, - cycles: 0, - affected_on_page: false, - }) } - fn char_to_hex(&mut self, oc: Option) -> Result { - match oc { - Some(c) => match c.to_digit(16) { - Some(c) => Ok(c as u8), - None => Err(self.parser_error("could not convert digit to hexadecimal")), - }, - None => Err(self.parser_error("digit out of bounds")), - } + fn assert_error(line: &str, id: &str, line_num: usize, message: &str) { + let mut asm = Assembler::new(EMPTY.to_vec()); + let res = asm.assemble(line.as_bytes()); + let msg = format!("{} error (line {}): {}.", id, line_num, message); + assert_eq!(res.unwrap_err().first().unwrap().to_string().as_str(), msg); } - fn evaluate_binary(&mut self, node: Box) -> Result { - let string = node.value.value.as_str(); - let mut value = 0; - let mut shift = 0; - - for c in string.chars().rev() { - if c == '1' { - let val = 1 << shift; - value += val; - } else if c != '0' { - return Err( - self.parser_error(format!("bad binary format for '{}'", string).as_str()) - ); - } - - shift += 1; - } - - if shift < 8 { - Err(self.parser_error("missing binary digits to get a full byte")) - } else if shift > 8 { - Err(self.parser_error("too many binary digits for a single byte")) - } else { - Ok(Bundle { - bytes: [value as u8, 0, 0], - size: 1, - address: 0, - cycles: 0, - affected_on_page: false, - }) - } + fn assert_eval_error(line: &str, message: &str) { + assert_error(line, "Evaluation", 1, message); } - fn evaluate_decimal(&mut self, node: Box) -> Result { - let string = node.value.value.as_str(); - if string.is_empty() { - return Err(self.parser_error("empty decimal literal")); - } - - match self.do_evaluate_decimal(string) { - Ok(val) => Ok(val), - Err(e) => { - if e.parse { - Err(e) - } else { - self.fetch_variable(string) - } - } - } + fn assert_context_error(line: &str, message: &str, line_num: usize) { + assert_error(line, "Context", line_num, message); } - fn fetch_variable(&mut self, mut string: &str) -> Result { - // Get the context that might be being referenced. - let ctxt = match string.find("::") { - Some(_) => { - let tctxt = string.rsplit_once("::").unwrap_or(("", "")); - if tctxt.0.is_empty() { - self.context.current() - } else { - string = tctxt.1; - self.context.find(tctxt.0) - } - } - None => self.context.current(), - }; + // Empty - // println!("{:#?}", self.context); - // println!("{:#?}", ctxt); - - match ctxt { - Some(hash) => { - match hash.get(string) { - Some(var) => { - // TODO - // If this is just a memory address (e.g. - // label), then just return it as is. - // if var.label { - // return Ok((node.clone(), false)); - // } - Ok(var.value.clone()) - } - None => { - Err(self.parser_error(format!("unknown variable '{}'", string).as_str())) - } - } - } - None => Err(self.parser_error(format!("unknown scope '{}'", "Global").as_str())), + #[test] + fn empty_line() { + for line in vec!["", " ", ";; Comment", " ;; Comment"].into_iter() { + let mut assembler = Assembler::new(EMPTY.to_vec()); + let bundles = assembler.assemble(line.as_bytes()).unwrap(); + assert!(bundles.is_empty()); } } - fn do_evaluate_decimal(&mut self, string: &str) -> Result { - let mut value = 0; - let mut shift = 1; - - if string.is_empty() { - return Err(self.parser_error("empty decimal literal")); - } - - for c in string.chars().rev() { - if shift > 100 { - return Err(self.parser_error("decimal value is too big")); - } - if c != '0' { - match c.to_digit(10) { - Some(digit) => { - value += digit * shift; - } - None => { - return Err(ParseError { - line: self.line, - message: format!("'{}' is not a decimal value", c), - parse: false, - }); - } - } - } - - shift *= 10; - } - if value > 255 { - return Err(self.parser_error("decimal value is too big")); - } + // Literal modes - Ok(Bundle { - bytes: [value as u8, 0, 0], - size: 1, - address: 0, - cycles: 0, - affected_on_page: false, - }) + #[test] + fn parse_binary() { + assert_eval_error("adc #%0001", "missing binary digits to get a full byte"); + assert_eval_error("adc #%0001000", "missing binary digits to get a full byte"); + assert_eval_error( + "adc #%000100001", + "too many binary digits for a single byte", + ); + assert_error( + r#" +Variable = 42 +adc %Variable +"#, + "Evaluation", + 3, + "you cannot use variables like 'Variable' in binary literals", + ); + assert_instruction("adc #%10100010", &[0x69, 0xA2]); } - fn evaluate_hilo_byte(&mut self, args: Vec>, hi: bool) -> Result { - if args.len() != 1 { - return Err(self.parser_error("wrong number of arguments: expecting exactly one")); - } + #[test] + fn parse_hexadecimal() { + assert_eval_error( + "adc #$12345", + "expecting a number of 1 to 4 hexadecimal digits", + ); + assert_eval_error("adc $AW", "could not convert digit to hexadecimal"); + assert_error( + r#" +Variable = 42 +adc $Variable +"#, + "Evaluation", + 3, + "you cannot use variables like 'Variable' in hexadecimal literals", + ); + assert_error( + r#" +Four = 4 +adc $Four +"#, + "Evaluation", + 3, + "you cannot use variables like 'Four' in hexadecimal literals", + ); + assert_instruction("adc $AA", &[0x65, 0xAA]); + assert_instruction("adc $10", &[0x65, 0x10]); + assert_instruction("adc $10AB", &[0x6D, 0xAB, 0x10]); + } - let val = self.evaluate_node(args.first().unwrap().clone())?; - if val.size < 1 { - let s = if hi { ".hibyte" } else { ".lobyte" }; - return Err(self.parser_error(format!("empty value for {}", s).as_str())); - } + #[test] + fn parse_decimal() { + assert_eval_error("adc #256", "decimal value is too big"); + assert_eval_error("adc #2000", "decimal value is too big"); + assert_eval_error( + "adc #2A", + "'A' is not a decimal value and could not find variable '2A' in the global scope either", + ); + assert_instruction("adc #1", &[0x69, 0x01]); + } - let b = if hi { - if val.size == 1 { - val.bytes[0] - } else { - val.bytes[1] - } - } else { - val.bytes[0] - }; + // Variables - Ok(Bundle { - bytes: [b, 0, 0], - size: 1, - address: 0, - cycles: 0, - affected_on_page: false, - }) - } + #[test] + fn scoped_variable() { + let mut asm = Assembler::new(EMPTY.to_vec()); + let res = asm + .assemble( + r#" +.scope One ; This is a comment + adc #Variable - fn evaluate_scope_definition(&mut self, node: Box) -> Result { - println!("{:#?}", node); - match node.left { - Some(identifier) => { - self.context.push(&identifier.value.value); - // TODO: mapping? + Variable = $20 +.endscope - Ok(Bundle::new()) - } - None => return Err(self.parser_error("scope definition with no identifier")), - } - } +.scope Another + Variable = $40 +.endscope - fn evaluate_scope_end(&mut self) -> Result { - if !self.context.pop() { - return Err(self.parser_error("missmatched '.endscope': there is no scope to end")); - } +Variable = $30 +adc #Variable - // TODO: mapping? +adc #One::Variable +adc #Another::Variable +"# + .as_bytes(), + ) + .unwrap(); - Ok(Bundle::new()) - } + assert_eq!(res.len(), 4); + let instrs: Vec<[u8; 2]> = vec![[0x69, 0x20], [0x69, 0x30], [0x69, 0x20], [0x69, 0x40]]; - // pub fn assemble(&mut self, reader: impl Read) -> Result> { - // let mut instructions: Vec<&dyn Encodable> = vec![]; - - // self.assemble_nodes(reader)?; - - // let mut idx: usize = 0; - // for segment in &self.mapping.segments { - // let mut size: usize = 0; - - // while idx < segment.start.into() { - // match &segment.fill { - // Some(fill) => instructions.push(fill), - // None => instructions.push(&Fill { value: 0x00 }), - // } - // idx += 1; - // } - - // for node in &self.mapping.nodes[&segment.name] { - // match node { - // Node::Instruction(instr) => { - // instructions.push(instr); - // size += usize::from(instr.size()); - // } - // Node::Literal(lit) => { - // instructions.push(lit); - // size += usize::from(lit.size()); - // } - // _ => {} - // } - // } - - // if size > segment.size { - // return Err(ParseError { - // line: 0, - // message: format!( - // "segment '{}' expected a size of '{}' bytes but '{}' bytes were produced instead", - // segment.name, size, segment.size - // ), - // }); - // } - // idx += size; - // if segment.fill.is_none() { - // continue; - // } - - // while size < segment.size { - // instructions.push(segment.fill.as_ref().unwrap()); - // size += 1; - // idx += 1; - // } - // } - - // Ok(instructions) - // } - - // pub fn evaluate(&mut self) -> Result<()> { - // for segment in &self.mapping.segments { - // for node in self.mapping.nodes.get_mut(&segment.name).unwrap() { - // match node { - // Node::Instruction(instr) => { - // Self::update_instruction_with_context(instr, &self.context)?; - // instr.address = segment.start; - - // self.offsets - // .entry(segment.name.clone()) - // .and_modify(|value| { - // instr.address += *value as u16; - // *value += usize::from(instr.size()) - // }) - // .or_insert(instr.size().into()); - // } - // Node::Scoped(scope) => { - // if scope.start { - // self.context.push(&scope.identifier.value); - // } else { - // _ = self.context.pop(); - // } - // } - // Node::Literal(literal) => { - // Self::update_literal_with_context(literal, &self.context)?; - // self.offsets - // .entry(segment.name.clone()) - // .and_modify(|value| *value += usize::from(literal.size())) - // .or_insert(literal.size().into()); - // } - // Node::Label(label) => { - // let address = - // usize::from(segment.start) + self.offsets.get(&segment.name).unwrap(); - - // self.context - // .current_mut() - // .unwrap() - // .entry(label.value.clone()) - // .and_modify(|e| e.value = address); - // } - // _ => {} - // } - // } - // } - - // Ok(()) - // } - - // // TODO: oh boy... - // pub fn resolve_labels(&mut self) -> Result<()> { - // for segment in &self.mapping.segments { - // for node in self.mapping.nodes.get_mut(&segment.name).unwrap() { - // match node { - // Node::Instruction(instr) => { - // if !instr.resolved { - // match &instr.left { - // Some(pstring) => { - // match self.context.current().unwrap().get(&pstring.value) { - // Some(entry) => { - // if instr.mode == AddressingMode::Absolute { - // let bytes = entry.value.to_le_bytes(); - // instr.bytes = [bytes[0], bytes[1]]; - // } else { - // let diff: isize = entry.value as isize - // - (instr.address as isize + 2); - // if diff < -128 || diff > 127 { - // return Err(instr.mnemonic.parser_error( - // format!("relative addressing out of range") - // .as_str(), - // )); - // } - // let bytes = diff.to_le_bytes(); - // instr.bytes = [bytes[0], 0]; - // } - // } - // None => { - // return Err(instr.mnemonic.parser_error( - // format!("label '{}' not found", pstring.value) - // .as_str(), - // )) - // } - // } - // } - // None => { - // return Err(instr.mnemonic.parser_error( - // format!("there is no label for the given jump instruction") - // .as_str(), - // )) - // } - // } - // } - // } - // Node::Literal(literal) => { - // if !literal.resolved { - // match self - // .context - // .current() - // .unwrap() - // .get(&literal.identifier.value) - // { - // Some(entry) => { - // let bytes = entry.value.to_le_bytes(); - // literal.bytes = [bytes[0], bytes[1]]; - // } - // None => { - // return Err(literal.identifier.parser_error( - // format!( - // "'{}' is neither a known variable or label at this scope", - // literal.identifier.value - // ) - // .as_str(), - // )) - // } - // } - // } - // } - // _ => {} - // } - // } - // } - - // Ok(()) - // } - - // fn update_instruction_with_context(instr: &mut Instruction, context: &Context) -> Result<()> { - // // To keep things simple, we remove out the `implied` case and we parse - // // further with a known `Some` value for the base algorithm implemented - // // in `update_instruction_and_bytes`. - // if instr.left.is_some() { - // Self::update_addressing_and_bytes(instr, context)?; - // } else { - // instr.mode = AddressingMode::Implied; - // } - - // // Now that we have the addressing mode and the bytes, we can fill out - // // the rest of it by fetching the values on `INSTRUCTIONS`. - // match INSTRUCTIONS.get(&instr.mnemonic.value.to_lowercase()) { - // Some(entries) => match entries.get(&instr.mode) { - // Some(values) => { - // instr.cycles = values.cycles; - // instr.opcode = values.opcode; - // instr.size = values.size; - // instr.affected_on_page = values.affected_on_page; - // } - // None => { - // return Err(instr.mnemonic.parser_error( - // format!( - // "bad addressing mode '{}' for the instruction '{}'", - // &instr.mode, &instr.mnemonic.value - // ) - // .as_str(), - // )); - // } - // }, - // None => { - // return Err(instr.mnemonic.parser_error( - // format!("unknown instruction '{}'", &instr.mnemonic.value).as_str(), - // )); - // } - // } - - // Ok(()) - // } - - // fn update_addressing_and_bytes(instr: &mut Instruction, context: &Context) -> Result<()> { - // // `unwrap()` is guaranteed to work by the caller. - // let left = instr.left.as_ref().unwrap(); - - // // We will first try to check if there's any variable involved on the - // // left arm and replace the string if so. This will greatly simplify - // // things down the line. That being said, there is a special reserved - // // case, which is the implied addressing by using "a". In this case, we - // // want to ensure that we assume an implied addressing and not a - // // variable named "a". - // if left.value.to_lowercase() == "a" { - // instr.mode = AddressingMode::Implied; - // } else { - // let (nleft, resolved) = Self::replace_variable(left, context)?; - // // TODO - // instr.resolved = resolved; - // if !resolved { - // if instr.mnemonic.value == "jmp" { - // instr.mode = AddressingMode::Absolute; - // } else { - // instr.mode = AddressingMode::RelativeOrZeropage; - // } - // } - - // if nleft.value.starts_with('$') { - // // This is an address. At this point we should assume that the - // // left node contains the address itself, and that the right one - // // will contain whether there is indexing. - - // let string = nleft.value.chars().as_str(); - // instr.bytes = Self::parse_hex_from(string, &nleft, true, false, true)?; - - // match &instr.right { - // Some(xy) => match xy.value.to_lowercase().as_str() { - // "x" => { - // if string.len() == 3 { - // instr.mode = AddressingMode::ZeropageIndexedX; - // } else { - // instr.mode = AddressingMode::IndexedX; - // } - // } - // "y" => { - // if string.len() == 3 { - // instr.mode = AddressingMode::ZeropageIndexedY; - // } else { - // instr.mode = AddressingMode::IndexedY; - // } - // } - // _ => return Err(xy.parser_error("index is neither X nor Y")), - // }, - // None => { - // if string.len() == 3 { - // instr.mode = AddressingMode::RelativeOrZeropage; - // } else { - // instr.mode = AddressingMode::Absolute; - // } - // } - // } - // } else if nleft.value.starts_with('#') { - // // Immediate addressing in any case: hexadecimal, binary or - // // decimal. Hence, just figure out the character being used and - // // call the right function for it. - - // let mut chars = nleft.value.chars(); - // chars.next(); - // let string = chars.as_str(); - - // instr.bytes = Self::parse_numeric(string, &nleft, false)?; - // instr.mode = AddressingMode::Immediate; - // } else if nleft.value.starts_with('(') { - // // Indirect addressing. In this case the left arm can be further - // // subdivided. That is, indirect X-indexing is represented like - // // so: `instr ($NN, x)`. Hence, first of all we have to figure - // // out whether there is a subdivision. - - // let (left1, oleft2) = Self::split_left_arm(&nleft)?; - // match oleft2 { - // Some(left2) => { - // // There is subdivision. Thus, we have to assume - // // indirect X-indexing, which means that the right arm - // // should be None and that the right side of the left - // // node must match the X register. Other than that, the - // // address being referenced must be zero page. - // if instr.right.is_some() { - // return Err(instr.right.as_ref().unwrap().parser_error( - // "bad indirect mode, expecting an indirect X-indexed addressing mode" - // )); - // } - // if left2.value.to_lowercase() != "x" { - // return Err(left2.parser_error( - // "the index in indirect X-indexed addressing must be X", - // )); - // } - // match Self::parse_hex_from(&left1.value, &left1, false, false, true) { - // Ok(bytes) => instr.bytes = bytes, - // Err(e) => { - // let msg = String::from( - // "when parsing an instruction with indirect X-indexed addressing: ", - // ) + &e.message; - // return Err(left1.parser_error(msg.as_str())); - // } - // } - // instr.mode = AddressingMode::IndirectX; - // } - // None => { - // // There is no subdivision on the left arm. Hence, if - // // there is something on the right arm then we must - // // assume indirect Y-index addressing, and if not then - // // it's indirect addressing with no indices involvved. - // if instr.right.is_some() { - // if instr.right.as_ref().unwrap().value.to_lowercase() != "y" { - // return Err(instr.right.as_ref().unwrap().parser_error( - // "the index in indirect Y-indexed addressing must be Y", - // )); - // } - // match Self::parse_hex_from(&left1.value, &left1, false, false, true) { - // Ok(bytes) => instr.bytes = bytes, - // Err(e) => { - // let msg = String::from( - // "when parsing an instruction with indirect Y-indexed addressing: ", - // ) + &e.message; - // return Err(left1.parser_error(msg.as_str())); - // } - // } - // instr.mode = AddressingMode::IndirectY; - // } else { - // instr.bytes = - // Self::parse_hex_from(&left1.value, &left1, true, true, true)?; - // instr.mode = AddressingMode::Indirect; - // } - // } - // } - // } else { - // // At this point all of the syntax cases have been exhausted: - // // the programmer messed up. From this point on we try to figure - // // out how they messed up. - - // if nleft.value.starts_with('=') { - // return Err(instr.mnemonic.parser_error( - // format!( - // "cannot use '{}' in an assignment because it's a word reserved for an instruction mnemonic", - // instr.mnemonic.value - // ).as_str(), - // )); - // } - // // TODO: - // // instr.mode = AddressingMode::Absolute; - // // return Err(instr.mnemonic.parser_error( - // // format!( - // // "unknown addressing mode for instruction '{}'", - // // instr.mnemonic.value - // // ) - // // .as_str(), - // // )); - // } - // } - - // Ok(()) - // } - - // fn update_literal_with_context(literal: &mut Literal, context: &Context) -> Result<()> { - // // If it has already been set, skip it. - // // TODO: add a proper `is_set` thingie to it instead of this hack. - // if literal.bytes[0] != 0 || literal.bytes[1] != 0 { - // return Ok(()); - // } - - // // Evaluate any possible variable being used inside of this literal. - // let (evaled, resolved) = Self::replace_variable(&literal.identifier, context)?; - - // // It may happen that the literal is just a label that is to be resolved - // // in the future. If so, let's leave early. - // literal.resolved = resolved; - // if !resolved { - // return Ok(()); - // } - - // // Parse the numeric value after a possible variable has been replaced. - // let two_bytes_allowed = literal.size == 2; - // let res = Self::parse_numeric( - // evaled.value.as_str(), - // &literal.identifier, - // two_bytes_allowed, - // ); - - // // And finally assign the computed bytes. - // match res { - // Ok(bytes) => { - // literal.bytes = bytes; - // Ok(()) - // } - // Err(e) => { - // let msg = String::from("when parsing a data literal: ") + &e.message; - // Err(literal.identifier.parser_error(msg.as_str())) - // } - // } - // } - - // fn parse_numeric(string: &str, node: &PString, two_bytes_allowed: bool) -> Result<[u8; 2]> { - // if string.starts_with('$') { - // Ok(Self::parse_hex_from( - // string, - // node, - // two_bytes_allowed, - // false, - // true, - // )?) - // } else if string.starts_with('%') { - // Ok([Self::parse_binary_from(string, node)?, 0]) - // } else { - // Ok([Self::parse_decimal_from(string, node)?, 0]) - // } - // } - - // fn split_left_arm(node: &PString) -> Result<(PString, Option)> { - // let mut chars = node.value.chars(); - // chars.next(); - // let string = chars.as_str(); - - // match string.find(|c: char| c == ',') { - // Some(idx) => { - // let left1 = string.get(..idx).unwrap_or("").trim(); - // let left2 = string.get(idx + 1..).unwrap_or("").trim(); - - // Ok(( - // PString { - // value: left1.to_string(), - // line: node.line, - // range: Range { - // start: node.range.start + 1, - // end: node.range.start + 1 + left1.len(), - // }, - // }, - // Some(PString { - // value: left2.to_string(), - // line: node.line, - // range: Range { - // start: node.range.start + 1 + idx, - // end: node.range.start + 1 + idx + left2.len(), - // }, - // }), - // )) - // } - // None => Ok(( - // PString { - // value: string.to_string(), - // line: node.line, - // range: Range { - // start: node.range.start + 1, - // end: node.range.end, - // }, - // }, - // None, - // )), - // } - // } - - // fn parse_binary_from(string: &str, node: &PString) -> Result { - // let mut value = 0; - // let mut shift = 0; - - // for c in string.get(1..).unwrap_or("").chars().rev() { - // if c == '1' { - // let val = 1 << shift; - // value += val; - // } else if c != '0' { - // return Err( - // node.parser_error(format!("bad binary format for '{}'", string).as_str()) - // ); - // } - - // shift += 1; - // } - - // if shift < 8 { - // Err(node.parser_error("missing binary digits to get a full byte")) - // } else if shift > 8 { - // Err(node.parser_error("too many binary digits for a single byte")) - // } else { - // Ok(value) - // } - // } - - // // TODO: returns if resolved - // fn replace_variable(node: &PString, context: &Context) -> Result<(PString, bool)> { - // match node - // .value - // .find(|c: char| c.is_alphabetic() || c == '_' || c == '@') - // { - // Some(idx) => { - // // Before doing any replacement, let's check the character - // // before the one that was found. In this case, if it was a - // // proper ASCII digit, then it cannot be a variable but it's - // // part of a numeric literal (e.g. '1A'): then just let the - // // different numeric parsing functions do their job. - // if idx > 0 { - // let prev = node.value.chars().nth(idx - 1).unwrap_or(' '); - // if prev.is_ascii_digit() { - // return Ok((node.clone(), true)); - // } - // } - - // // The variable might still be before an inner comma (e.g. - // // sta ($20, x)). We will assume that variables can happen - // // only before that. - // let end = node.value.find(',').unwrap_or(node.value.len()); - // let mut string = node.value.get(idx..end).unwrap_or(""); - // let tail = node.value.get(end..).unwrap_or(""); - - // // Get the context that might be being referenced. - // let ctxt = match string.find("::") { - // Some(_) => { - // let tctxt = string.rsplit_once("::").unwrap_or(("", "")); - // if tctxt.0.is_empty() { - // context.current() - // } else { - // string = tctxt.1; - // context.find(tctxt.0) - // } - // } - // None => context.current(), - // }; - - // match ctxt { - // Some(hash) => { - // // If there was a comma before the "variable" (i.e. idx > - // // end and hence string == ""), or this is just the regular - // // X or Y index, just return early. - // match string.to_lowercase().as_str() { - // "x" | "y" | "" => return Ok((node.clone(), true)), - // _ => {} - // } - - // // It's not any of the indices, let's look for a match on - // // the current scope. - // match hash.get(string) { - // Some(var) => { - // // If this is just a memory address (e.g. - // // label), then just return it as is. - // if var.label { - // return Ok((node.clone(), false)); - // } - - // let value = String::from(node.value.get(..idx).unwrap_or("")) - // + var.node.value.as_str(); - // Ok(( - // PString { - // value: value.clone() + tail, - // line: node.line, - // range: Range { - // start: node.range.start, - // end: node.range.start + value.len(), - // }, - // }, - // true, - // )) - // } - // None => { - // // If a variable could not be found, check that - // // this is not a purely hexadecimal number (e.g. - // // 'AA'). If that's the case, then just return - // // its value. - // if Self::parse_hex_from(string, node, true, false, false).is_ok() { - // return Ok((node.clone(), true)); - // } - - // // We've tried hard to not assume the programmer - // // messing up, but there's no other way around - // // it: it's an "unknown variable" error. - // return Err(node.parser_error( - // format!("unknown variable '{}'", string).as_str(), - // )); - // } - // } - // } - // None => { - // Err(node.parser_error(format!("unknown scope '{}'", "Global").as_str())) - // } - // } - // } - // None => Ok((node.clone(), true)), - // } - // } - - // fn parse_macro_definition(&mut self, id: &PString, line: &str) -> Result<()> { - // self.skip_whitespace(line); - - // let identifier = self.fetch_identifier(id, line)?; - // if identifier.is_reserved() { - // return Err(identifier.parser_error( - // format!( - // "cannot use reserved name '{}' for proc name", - // identifier.value - // ) - // .as_str(), - // )); - // } - - // self.mapping.current_macro = Some(identifier.value.clone()); - // self.mapping.macros.entry(identifier.value).or_default(); - // Ok(()) - // } - - // fn parse_macro_end(&mut self, id: &PString) -> Result<()> { - // match self.mapping.current_macro { - // Some(_) => self.mapping.current_macro = None, - // None => { - // return Err(id.parser_error( - // format!("bad `.endmacro`: we are not inside of a macro definition").as_str(), - // )) - // } - // } - - // Ok(()) - // } - - // fn parse_proc_definition(&mut self, id: &PString, line: &str) -> Result<()> { - // self.skip_whitespace(line); - - // let identifier = self.fetch_identifier(id, line)?; - // if identifier.is_reserved() { - // return Err(identifier.parser_error( - // format!( - // "cannot use reserved name '{}' for proc name", - // identifier.value - // ) - // .as_str(), - // )); - // } - - // // Insert the given identifier into the context. - // if let Some(entry) = self.context.current_mut() { - // match entry.entry(identifier.value.clone()) { - // Entry::Occupied(e) => { - // return Err(ParseError { - // line: self.line, - // message: format!( - // "proc '{}' already exists for this context: it was previously defined in line {}", - // id.value, e.get().node.line), - // }) - // } - // Entry::Vacant(e) => e.insert(PValue { - // node: PString { - // value: identifier.value.clone(), - // line: self.line, - // range: Range { - // start: id.range.start, - // end: id.range.end, - // }, - // }, - // value: 0, - // label: true, - // }), - // }; - // } - - // // And add the node so it's picked up later. - // self.mapping.push(Node::Label(Label { - // value: identifier.value.to_string(), - // })); - - // // TODO: lol - // self.context.push_stack(&identifier.value); - - // self.mapping.push(Node::Scoped(Scoped { - // identifier: identifier.clone(), - // start: true, - // })); - - // Ok(()) - // } - - // fn parse_proc_end(&mut self, id: &PString) -> Result<()> { - // if !self.context.pop() { - // return Err(id.parser_error("missmatched '.endproc': there is no proc to end")); - // } - // self.mapping.push(Node::Scoped(Scoped { - // identifier: PString::new(), - // start: false, - // })); - - // Ok(()) - // } - - // fn parse_segment_definition(&mut self, id: &PString, line: &str) -> Result<()> { - // self.skip_whitespace(line); - - // let identifier = self.fetch_possibly_quoted_identifier(id, line)?; - // self.mapping.switch(&identifier)?; - - // Ok(()) - // } - - // fn parse_scope_definition(&mut self, id: &PString, line: &str) -> Result<()> { - // self.skip_whitespace(line); - - // let identifier = self.fetch_identifier(id, line)?; - // if identifier.is_reserved() { - // return Err(identifier.parser_error( - // format!("cannot use reserved name '{}'", identifier.value).as_str(), - // )); - // } - // self.context.push(&identifier.value); - // self.mapping.push(Node::Scoped(Scoped { - // identifier, - // start: true, - // })); - - // Ok(()) - // } - - // fn parse_scope_end(&mut self, id: &PString) -> Result<()> { - // if !self.context.pop() { - // return Err(id.parser_error("missmatched '.endscope': there is no scope to end")); - // } - // self.mapping.push(Node::Scoped(Scoped { - // identifier: PString::new(), - // start: false, - // })); - - // Ok(()) - // } - - // fn parse_literal_bytes( - // &mut self, - // node: &PString, - // line: &str, - // two_bytes_allowed: bool, - // ) -> Result<()> { - // loop { - // self.skip_whitespace(line); - - // match line.chars().nth(self.column) { - // Some(byte) => { - // let needle = if byte == '\'' { - // self.column += 1; - // self.skip_whitespace(line); - // '\'' - // } else if byte == '"' { - // self.column += 1; - // self.skip_whitespace(line); - // '"' - // } else { - // ',' - // }; - - // // Find the index of the needle. If it cannot be found, try - // // to find the first whitespace (e.g. to ditch out inline - // // comments or other artifacts). If neither of these are - // // found, it will simply return the end of the string. - // // - // // TODO: instead of ditching out what's right of the first - // // whitespace, try to error out on weird scenarios. - // let needle_idx = line - // .get(self.column..) - // .unwrap_or("") - // .find(|c: char| c == needle); - // let idx = match needle_idx { - // Some(v) => v, - // None => line - // .get(self.column..) - // .unwrap_or("") - // .find(|c: char| c.is_whitespace()) - // .unwrap_or(line.len() - self.column), - // }; - - // // If this is the last character, the needle was a quote and - // // the last char is not the needle, then it means that the - // // quote was left open. Complain about this as well. - // if idx == line.len() - self.column { - // if line.chars().nth(idx).unwrap_or(' ') != needle - // && (needle == '"' || needle == '\'') - // { - // return Err(node.parser_error("non-terminated quote for byte literal")); - // } - // } - - // // Now we have our string. Before pushing it, though, there - // // is a special case for alphabetic literals that need to be - // // translated. - // let string = line.get(self.column..self.column + idx).unwrap_or(" "); - // let mut bytes: [u8; 2] = [0, 0]; - // if string.len() == 1 && string.chars().nth(0).unwrap().is_ascii_alphabetic() { - // let v = Vec::from(string); - // bytes[0] = v[0]; - // } - - // // NOTE: for now we push an incomplete literal. We need the - // // first pass to fill the context and then a second pass - // // will evaluate each literal as needed (e.g. replacing - // // values from variables being used in this literal). - // self.mapping.push(Node::Literal(Literal { - // identifier: PString { - // value: string.to_owned(), - // line: self.line, - // range: Range { - // start: self.column, - // end: self.column + idx, - // }, - // }, - // size: if two_bytes_allowed { 2 } else { 1 }, - // bytes, - // resolved: true, - // })); - - // self.column += idx; - // for c in line.get(self.column..).unwrap_or(" ").chars() { - // if c == ',' { - // break; - // } - // if c == ';' { - // return Ok(()); - // } - // self.column += 1; - // } - // self.column += 1; - // self.skip_whitespace(line); - // } - // None => break, - // }; - // } - - // Ok(()) - // } - - // fn fetch_identifier(&mut self, id: &PString, line: &str) -> Result { - // let idx = line - // .get(self.column..) - // .unwrap_or(" ") - // .find(|c: char| c.is_whitespace()); - - // match idx { - // Some(offset) => { - // let end = self.column + offset; - // let rest = line.get(end..).unwrap_or("").trim(); - // if !rest.is_empty() { - // if rest.chars().nth(0).unwrap_or(' ') != ';' { - // return Err(id.parser_error( - // "there should not be any further content besides the identifier", - // )); - // } - // } - // Ok(PString { - // value: line.get(self.column..end).unwrap_or(" ").trim().to_string(), - // line: self.line, - // range: Range { - // start: self.column, - // end, - // }, - // }) - // } - // None => Ok(PString { - // value: line.get(self.column..).unwrap_or(" ").trim().to_string(), - // line: self.line, - // range: Range { - // start: self.column, - // end: line.len(), - // }, - // }), - // } - // } - - // fn fetch_possibly_quoted_identifier(&mut self, id: &PString, line: &str) -> Result { - // let mut identifier = self.fetch_identifier(id, line)?; - - // if identifier.value.starts_with('\'') || identifier.value.starts_with('`') { - // return Err(id.parser_error("use double quotes for the segment identifier instead")); - // } else if identifier.value.starts_with('"') { - // identifier.value = match identifier - // .value - // .get(1..(identifier.range.end - identifier.range.start - 1)) - // { - // Some(v) => v.to_string(), - // None => return Err(id.parser_error("could not fetch quoted identifier")), - // }; - // if identifier.value.contains('"') { - // return Err(id.parser_error("do not use double quotes inside of the identifier")); - // } - // identifier.range.start += 1; - // identifier.range.end -= 1; - // } - - // Ok(identifier) - // } - - // fn parse_label(&mut self, id: PString, _line: &str) -> Result<()> { - // let name = &id.value.as_str()[..id.value.len() - 1].to_string(); - - // // Forbid weird scenarios. - // if name.contains("::") { - // return Err(id.parser_error( - // format!( - // "the label '{}' is scoped: do not declare variables this way", - // id.value - // ) - // .as_str(), - // )); - // } - - // // Insert the given label into the context. - // if let Some(entry) = self.context.current_mut() { - // match entry.entry(name.clone()) { - // Entry::Occupied(e) => { - // return Err(ParseError { - // line: self.line, - // message: format!( - // "label '{}' already exists for this context: it was previously defined in line {}", - // id.value, e.get().node.line), - // }) - // } - // Entry::Vacant(e) => e.insert(PValue { - // node: PString { - // value: name.clone(), - // line: self.line, - // range: Range { - // start: id.range.start, - // end: id.range.end, - // }, - // }, - // value: 0, - // label: true, - // }), - // }; - // } - - // // And add the node so it's picked up later. - // self.mapping.push(Node::Label(Label { - // value: name.to_string(), - // })); - // Ok(()) - // } - - fn parser_error(&self, msg: &str) -> ParseError { - ParseError { - message: String::from(msg), - line: self.line, - parse: true, + for i in 0..4 { + assert_eq!(res[i].size, 2); + assert_eq!(res[i].bytes[0], instrs[i][0]); + assert_eq!(res[i].bytes[1], instrs[i][1]); } } - // fn from_byte_reader(&mut self, mut reader: R) -> Result<()> { - // loop { - // let mut buf = [0; 1]; - // let n = reader.read(&mut buf)?; - // if n == 0 { - // break; - // } - - // match OPCODES.get(&buf[0]) { - // Some(v) => { - // let mut bs = [0; 2]; - // for i in 0..v.size - 1 { - // let nn = reader.read(&mut buf)?; - // if nn == 0 { - // break; - // } - // bs[i as usize] = buf[0]; - // } - // self.mapping.push(Node::Instruction(Instruction { - // mnemonic: PString::from(&v.mnemonic), - // opcode: v.opcode, - // size: v.size, - // bytes: bs, - // left: None, - // right: None, - // mode: v.mode.to_owned(), - // cycles: v.cycles, - // affected_on_page: v.affected_on_page, - // address: 0, // TODO - // resolved: true, - // })) - // } - - // None => { - // return Err( - // self.parser_error(format!("unknown byte '0x{:02X}'", buf[0]).as_str()) - // ) - // } - // } - // } - - // Ok(()) - // } -} - -#[cfg(test)] -mod tests { - use super::*; - use crate::mapping::EMPTY; - - fn instruction_test(line: &str, hex: &[u8], skip_disassemble: bool) { + #[test] + fn bare_variables() { let mut asm = Assembler::new(EMPTY.to_vec()); - let res = asm.assemble(line.as_bytes()).unwrap(); + let res = asm + .assemble( + r#" +Variable = 4 +adc Variable +"# + .as_bytes(), + ) + .unwrap(); assert_eq!(res.len(), 1); - for i in 0..res[0].size { - assert_eq!(hex[i as usize], res[0].bytes[i as usize]); - } + let instr = res.first().unwrap(); + assert_eq!(instr.size, 2); + assert_eq!(instr.bytes[0], 0x65); + assert_eq!(instr.bytes[1], 0x04); + } - if skip_disassemble { - return; - } - // TODO + #[test] + fn bad_variable_but_valid_identifier_in_instruction() { + assert_eval_error( + "adc Variable", + "no prefix was given to operand and could not find variable 'Variable' in the global scope either", + ); + assert_eval_error( + "adc Scoped::Variable", + "no prefix was given to operand and did not find scope 'Scoped' either", + ); } - fn instruction_err(line: &str, message: &str) { - let mut asm = Assembler::new(EMPTY.to_vec()); - let err = asm.assemble(line.as_bytes()); + #[test] + fn redefined_variable() { + assert_context_error( + r#" +.scope One + Variable = 1 +.endscope - assert!(err.is_err()); - if let Err(e) = err { - assert_eq!(e.message, message); - } +Variable = 1 +Yet = 3 +Yet = 4 +"#, + "'Yet' already defined in the global scope: you cannot re-assign variables", + 8, + ); + } + + #[test] + fn unknown_variables() { + assert_eval_error( + "lda #Variable", + "'e' is not a decimal value and could not find variable \ + 'Variable' in the global scope either", + ); + assert_eval_error( + "lda #Scope::Variable", + "'e' is not a decimal value and did not find scope 'Scope' either", + ); + assert_error( + r#" +.scope Scope +.endscope +lda #Scope::Variable +"#, + "Evaluation", + 4, + "'e' is not a decimal value and could not find variable 'Variable' in 'Scope' either", + ); } + // Regular instructions + #[test] fn bad_addressing() { - instruction_err("unknown #$20", "unknown instruction 'unknown'"); - instruction_err( + assert_eval_error( + "unknown #$20", + "could not find a macro with the name 'unknown'", + ); + assert_eval_error( "adc ($2002, x)", "address can only be one byte long on indirect X addressing", ); - instruction_err( - "adc ($20, x), y", - "it has to be either X addressing or Y addressing, not all at once", - ); - instruction_err( + assert_eval_error( "adc ($2002), y", "address can only be one byte long on indirect Y addressing", ); - instruction_err( + assert_eval_error( "adc ($20, y)", "only the X index is allowed on indirect X addressing", ); - instruction_err( + assert_eval_error( "adc ($20), x", "only the Y index is allowed on indirect Y addressing", ); - instruction_err("jmp ($20)", "expecting a full 16-bit address"); - instruction_err("adc $20, z", "can only use X and Y as indices"); - instruction_err( + assert_eval_error("jmp ($20)", "expecting a full 16-bit address"); + assert_eval_error("adc $20, z", "can only use X and Y as indices"); + assert_eval_error( "adc ($2000)", "cannot use indirect addressing mode for the instruction 'adc'", ); - instruction_err("lda 12", "no prefix was given to operand") - } - - #[test] - fn parse_binary() { - instruction_err("adc #%", "missing binary digits to get a full byte"); - instruction_err("adc #%0001", "missing binary digits to get a full byte"); - instruction_err("adc #%0001000", "missing binary digits to get a full byte"); - instruction_err( - "adc #%000100001", - "too many binary digits for a single byte", - ); - instruction_test("adc #%10100010", &[0x69, 0xA2], true); - } - - #[test] - fn parse_hexadecimal() { - instruction_err("adc $", "expecting a number of 1 to 4 hexadecimal digits"); - // TODO: see comment on literal_mode being a stack. - instruction_err("adc #$", "expecting a number of 1 to 4 hexadecimal digits"); - instruction_err("adc $AW", "could not convert digit to hexadecimal"); - instruction_test("adc $AA", &[0x65, 0xAA], false); - instruction_test("adc $10", &[0x65, 0x10], false); - instruction_test("adc $10AB", &[0x6D, 0xAB, 0x10], false); + assert_eval_error("lda 12", "no prefix was given to operand") } - #[test] - fn parse_decimal() { - instruction_err("adc #", "empty decimal literal"); - instruction_err("adc #256", "decimal value is too big"); - instruction_err("adc #2000", "decimal value is too big"); - instruction_err("adc #2A", "unknown variable '2A'"); // TODO: not sure about this - instruction_test("adc #1", &[0x69, 0x01], true); - } - - // Individual instructions. - #[test] fn adc() { - instruction_test("adc #20", &[0x69, 0x14], true); - instruction_test("adc #$20", &[0x69, 0x20], false); - instruction_test("adc $2002", &[0x6D, 0x02, 0x20], false); - instruction_test("adc $20", &[0x65, 0x20], false); - instruction_test("adc $20, x", &[0x75, 0x20], false); - instruction_test("adc $2002, x", &[0x7D, 0x02, 0x20], false); - instruction_test("adc $2002, y", &[0x79, 0x02, 0x20], false); - instruction_test("adc ($20, x)", &[0x61, 0x20], false); - instruction_test("adc ($20), y", &[0x71, 0x20], false); + assert_instruction("adc #20", &[0x69, 0x14]); + assert_instruction("adc #$20", &[0x69, 0x20]); + assert_instruction("adc $2002", &[0x6D, 0x02, 0x20]); + assert_instruction("adc $20", &[0x65, 0x20]); + assert_instruction("adc $20, x", &[0x75, 0x20]); + assert_instruction("adc $2002, x", &[0x7D, 0x02, 0x20]); + assert_instruction("adc $2002, y", &[0x79, 0x02, 0x20]); + assert_instruction("adc ($20, x)", &[0x61, 0x20]); + assert_instruction("adc ($20), y", &[0x71, 0x20]); } #[test] fn sbc() { - instruction_test("sbc #$20", &[0xE9, 0x20], false); - instruction_test("sbc $2002", &[0xED, 0x02, 0x20], false); - instruction_test("sbc $20", &[0xE5, 0x20], false); - instruction_test("sbc $20, x", &[0xF5, 0x20], false); - instruction_test("sbc $2002, x", &[0xFD, 0x02, 0x20], false); - instruction_test("sbc $2002, y", &[0xF9, 0x02, 0x20], false); - instruction_test("sbc ($20, x)", &[0xE1, 0x20], false); - instruction_test("sbc ($20), y", &[0xF1, 0x20], false); + assert_instruction("sbc #$20", &[0xE9, 0x20]); + assert_instruction("sbc $2002", &[0xED, 0x02, 0x20]); + assert_instruction("sbc $20", &[0xE5, 0x20]); + assert_instruction("sbc $20, x", &[0xF5, 0x20]); + assert_instruction("sbc $2002, x", &[0xFD, 0x02, 0x20]); + assert_instruction("sbc $2002, y", &[0xF9, 0x02, 0x20]); + assert_instruction("sbc ($20, x)", &[0xE1, 0x20]); + assert_instruction("sbc ($20), y", &[0xF1, 0x20]); } #[test] fn shift() { // asl - instruction_test("asl", &[0x0A], false); - instruction_test("asl a", &[0x0A], true); - instruction_test("asl $20", &[0x06, 0x20], false); - instruction_test("asl $20, x", &[0x16, 0x20], false); - instruction_test("asl $2002", &[0x0E, 0x02, 0x20], false); - instruction_test("asl $2002, x", &[0x1E, 0x02, 0x20], false); + assert_instruction("asl", &[0x0A]); + assert_instruction("asl a", &[0x0A]); + assert_instruction("asl $20", &[0x06, 0x20]); + assert_instruction("asl $20, x", &[0x16, 0x20]); + assert_instruction("asl $2002", &[0x0E, 0x02, 0x20]); + assert_instruction("asl $2002, x", &[0x1E, 0x02, 0x20]); // lsr - instruction_test("lsr", &[0x4A], false); - instruction_test("lsr a", &[0x4A], true); - instruction_test("lsr $20", &[0x46, 0x20], false); - instruction_test("lsr $20, x", &[0x56, 0x20], false); - instruction_test("lsr $2002", &[0x4E, 0x02, 0x20], false); - instruction_test("lsr $2002, x", &[0x5E, 0x02, 0x20], false); + assert_instruction("lsr", &[0x4A]); + assert_instruction("lsr a", &[0x4A]); + assert_instruction("lsr $20", &[0x46, 0x20]); + assert_instruction("lsr $20, x", &[0x56, 0x20]); + assert_instruction("lsr $2002", &[0x4E, 0x02, 0x20]); + assert_instruction("lsr $2002, x", &[0x5E, 0x02, 0x20]); } #[test] fn rotate() { // rol - instruction_test("rol", &[0x2A], false); - instruction_test("rol a", &[0x2A], true); - instruction_test("rol $20", &[0x26, 0x20], false); - instruction_test("rol $20, x", &[0x36, 0x20], false); - instruction_test("rol $2002", &[0x2E, 0x02, 0x20], false); - instruction_test("rol $2002, x", &[0x3E, 0x02, 0x20], false); + assert_instruction("rol", &[0x2A]); + assert_instruction("rol a", &[0x2A]); + assert_instruction("rol $20", &[0x26, 0x20]); + assert_instruction("rol $20, x", &[0x36, 0x20]); + assert_instruction("rol $2002", &[0x2E, 0x02, 0x20]); + assert_instruction("rol $2002, x", &[0x3E, 0x02, 0x20]); // ror - instruction_test("ror", &[0x6A], false); - instruction_test("ror a", &[0x6A], true); - instruction_test("ror $20", &[0x66, 0x20], false); - instruction_test("ror $20, x", &[0x76, 0x20], false); - instruction_test("ror $2002", &[0x6E, 0x02, 0x20], false); - instruction_test("ror $2002, x", &[0x7E, 0x02, 0x20], false); + assert_instruction("ror", &[0x6A]); + assert_instruction("ror a", &[0x6A]); + assert_instruction("ror $20", &[0x66, 0x20]); + assert_instruction("ror $20, x", &[0x76, 0x20]); + assert_instruction("ror $2002", &[0x6E, 0x02, 0x20]); + assert_instruction("ror $2002, x", &[0x7E, 0x02, 0x20]); } #[test] fn and() { - instruction_test("and #$20", &[0x29, 0x20], false); - instruction_test("and $2002", &[0x2D, 0x02, 0x20], false); - instruction_test("and $20", &[0x25, 0x20], false); - instruction_test("and $20, x", &[0x35, 0x20], false); - instruction_test("and $2002, x", &[0x3D, 0x02, 0x20], false); - instruction_test("and $2002, y", &[0x39, 0x02, 0x20], false); - instruction_test("and ($20, x)", &[0x21, 0x20], false); - instruction_test("and ($20), y", &[0x31, 0x20], false); + assert_instruction("and #$20", &[0x29, 0x20]); + assert_instruction("and $2002", &[0x2D, 0x02, 0x20]); + assert_instruction("and $20", &[0x25, 0x20]); + assert_instruction("and $20, x", &[0x35, 0x20]); + assert_instruction("and $2002, x", &[0x3D, 0x02, 0x20]); + assert_instruction("and $2002, y", &[0x39, 0x02, 0x20]); + assert_instruction("and ($20, x)", &[0x21, 0x20]); + assert_instruction("and ($20), y", &[0x31, 0x20]); } #[test] fn or() { // eor - instruction_test("eor #$20", &[0x49, 0x20], false); - instruction_test("eor $20", &[0x45, 0x20], false); - instruction_test("eor $20, x", &[0x55, 0x20], false); - instruction_test("eor $2002", &[0x4D, 0x02, 0x20], false); - instruction_test("eor $2002, x", &[0x5D, 0x02, 0x20], false); - instruction_test("eor $2002, y", &[0x59, 0x02, 0x20], false); - instruction_test("eor ($20, x)", &[0x41, 0x20], false); - instruction_test("eor ($20), y", &[0x51, 0x20], false); + assert_instruction("eor #$20", &[0x49, 0x20]); + assert_instruction("eor $20", &[0x45, 0x20]); + assert_instruction("eor $20, x", &[0x55, 0x20]); + assert_instruction("eor $2002", &[0x4D, 0x02, 0x20]); + assert_instruction("eor $2002, x", &[0x5D, 0x02, 0x20]); + assert_instruction("eor $2002, y", &[0x59, 0x02, 0x20]); + assert_instruction("eor ($20, x)", &[0x41, 0x20]); + assert_instruction("eor ($20), y", &[0x51, 0x20]); // ora - instruction_test("ora #$20", &[0x09, 0x20], false); - instruction_test("ora $20", &[0x05, 0x20], false); - instruction_test("ora $20, x", &[0x15, 0x20], false); - instruction_test("ora $2002", &[0x0D, 0x02, 0x20], false); - instruction_test("ora $2002, x", &[0x1D, 0x02, 0x20], false); - instruction_test("ora $2002, y", &[0x19, 0x02, 0x20], false); - instruction_test("ora ($20, x)", &[0x01, 0x20], false); - instruction_test("ora ($20), y", &[0x11, 0x20], false); + assert_instruction("ora #$20", &[0x09, 0x20]); + assert_instruction("ora $20", &[0x05, 0x20]); + assert_instruction("ora $20, x", &[0x15, 0x20]); + assert_instruction("ora $2002", &[0x0D, 0x02, 0x20]); + assert_instruction("ora $2002, x", &[0x1D, 0x02, 0x20]); + assert_instruction("ora $2002, y", &[0x19, 0x02, 0x20]); + assert_instruction("ora ($20, x)", &[0x01, 0x20]); + assert_instruction("ora ($20), y", &[0x11, 0x20]); } #[test] fn load() { // lda - instruction_test("lda #$20", &[0xA9, 0x20], false); - instruction_test("lda $20", &[0xA5, 0x20], false); - instruction_test("lda $20, x", &[0xB5, 0x20], false); - instruction_test("lda $2002", &[0xAD, 0x02, 0x20], false); - instruction_test("lda $2002, x", &[0xBD, 0x02, 0x20], false); - instruction_test("lda $2002, y", &[0xB9, 0x02, 0x20], false); - instruction_test("lda ($20, x)", &[0xA1, 0x20], false); - instruction_test("lda ($20), y", &[0xB1, 0x20], false); + assert_instruction("lda #$20", &[0xA9, 0x20]); + assert_instruction("lda $20", &[0xA5, 0x20]); + assert_instruction("lda $20, x", &[0xB5, 0x20]); + assert_instruction("lda $2002", &[0xAD, 0x02, 0x20]); + assert_instruction("lda $2002, x", &[0xBD, 0x02, 0x20]); + assert_instruction("lda $2002, y", &[0xB9, 0x02, 0x20]); + assert_instruction("lda ($20, x)", &[0xA1, 0x20]); + assert_instruction("lda ($20), y", &[0xB1, 0x20]); // ldx - instruction_test("ldx #$20", &[0xA2, 0x20], false); - instruction_test("ldx $20", &[0xA6, 0x20], false); - instruction_test("ldx $20, y", &[0xB6, 0x20], false); - instruction_test("ldx $2002", &[0xAE, 0x02, 0x20], false); - instruction_test("ldx $2002, y", &[0xBE, 0x02, 0x20], false); + assert_instruction("ldx #$20", &[0xA2, 0x20]); + assert_instruction("ldx $20", &[0xA6, 0x20]); + assert_instruction("ldx $20, y", &[0xB6, 0x20]); + assert_instruction("ldx $2002", &[0xAE, 0x02, 0x20]); + assert_instruction("ldx $2002, y", &[0xBE, 0x02, 0x20]); // ldy - instruction_test("ldy #$20", &[0xA0, 0x20], false); - instruction_test("ldy $20", &[0xA4, 0x20], false); - instruction_test("ldy $20, x", &[0xB4, 0x20], false); - instruction_test("ldy $2002", &[0xAC, 0x02, 0x20], false); - instruction_test("ldy $2002, x", &[0xBC, 0x02, 0x20], false); + assert_instruction("ldy #$20", &[0xA0, 0x20]); + assert_instruction("ldy $20", &[0xA4, 0x20]); + assert_instruction("ldy $20, x", &[0xB4, 0x20]); + assert_instruction("ldy $2002", &[0xAC, 0x02, 0x20]); + assert_instruction("ldy $2002, x", &[0xBC, 0x02, 0x20]); } #[test] fn jump() { - instruction_test("jsr $2002", &[0x20, 0x02, 0x20], false); + assert_instruction("jsr $2002", &[0x20, 0x02, 0x20]); - instruction_test("jmp $2002", &[0x4C, 0x02, 0x20], false); - instruction_test("jmp ($2002)", &[0x6C, 0x02, 0x20], false); + assert_instruction("jmp $2002", &[0x4C, 0x02, 0x20]); + assert_instruction("jmp ($2002)", &[0x6C, 0x02, 0x20]); } #[test] fn inc_dec_instructions() { // inc - instruction_test("inc $10", &[0xE6, 0x10], false); - instruction_test("inc $1000", &[0xEE, 0x00, 0x10], false); - instruction_test("inc $10, x", &[0xF6, 0x10], false); - instruction_test("inc $1000, x", &[0xFE, 0x00, 0x10], false); + assert_instruction("inc $10", &[0xE6, 0x10]); + assert_instruction("inc $1000", &[0xEE, 0x00, 0x10]); + assert_instruction("inc $10, x", &[0xF6, 0x10]); + assert_instruction("inc $1000, x", &[0xFE, 0x00, 0x10]); - instruction_test("inx", &[0xE8], false); + assert_instruction("inx", &[0xE8]); - instruction_test("iny", &[0xC8], false); + assert_instruction("iny", &[0xC8]); // dec - instruction_test("dec $10", &[0xC6, 0x10], false); - instruction_test("dec $1000", &[0xCE, 0x00, 0x10], false); - instruction_test("dec $10, x", &[0xD6, 0x10], false); - instruction_test("dec $1000, x", &[0xDE, 0x00, 0x10], false); + assert_instruction("dec $10", &[0xC6, 0x10]); + assert_instruction("dec $1000", &[0xCE, 0x00, 0x10]); + assert_instruction("dec $10, x", &[0xD6, 0x10]); + assert_instruction("dec $1000, x", &[0xDE, 0x00, 0x10]); - instruction_test("dex", &[0xCA], false); + assert_instruction("dex", &[0xCA]); - instruction_test("dey", &[0x88], false); + assert_instruction("dey", &[0x88]); } #[test] fn transfer_instructions() { - instruction_test("tax", &[0xAA], false); - instruction_test("tay", &[0xA8], false); - instruction_test("tsx", &[0xBA], false); - instruction_test("txa", &[0x8A], false); - instruction_test("txs", &[0x9A], false); - instruction_test("tya", &[0x98], false); + assert_instruction("tax", &[0xAA]); + assert_instruction("tay", &[0xA8]); + assert_instruction("tsx", &[0xBA]); + assert_instruction("txa", &[0x8A]); + assert_instruction("txs", &[0x9A]); + assert_instruction("tya", &[0x98]); } #[test] fn return_instructions() { - instruction_test("rti", &[0x40], false); - instruction_test("rts", &[0x60], false); + assert_instruction("rti", &[0x40]); + assert_instruction("rts", &[0x60]); } #[test] fn set_clear_instructions() { - instruction_test("clc", &[0x18], false); - instruction_test("cld", &[0xD8], false); - instruction_test("cli", &[0x58], false); - instruction_test("clv", &[0xB8], false); - - instruction_test("sec", &[0x38], false); - instruction_test("sed", &[0xF8], false); - instruction_test("sei", &[0x78], false); + assert_instruction("clc", &[0x18]); + assert_instruction("cld", &[0xD8]); + assert_instruction("cli", &[0x58]); + assert_instruction("clv", &[0xB8]); + + assert_instruction("sec", &[0x38]); + assert_instruction("sed", &[0xF8]); + assert_instruction("sei", &[0x78]); } #[test] fn push_pull_instructions() { - instruction_test("pha", &[0x48], false); - instruction_test("php", &[0x08], false); - instruction_test("pla", &[0x68], false); - instruction_test("plp", &[0x28], false); + assert_instruction("pha", &[0x48]); + assert_instruction("php", &[0x08]); + assert_instruction("pla", &[0x68]); + assert_instruction("plp", &[0x28]); } #[test] fn nop_brk() { - instruction_test("nop", &[0xEA], false); - instruction_test("brk", &[0x00], false); + assert_instruction("nop", &[0xEA]); + assert_instruction("brk", &[0x00]); } #[test] fn cmp() { // cmp - instruction_test("cmp #$20", &[0xC9, 0x20], false); - instruction_test("cmp $2002", &[0xCD, 0x02, 0x20], false); - instruction_test("cmp $20", &[0xC5, 0x20], false); - instruction_test("cmp $20, x", &[0xD5, 0x20], false); - instruction_test("cmp $2002, x", &[0xDD, 0x02, 0x20], false); - instruction_test("cmp $2002, y", &[0xD9, 0x02, 0x20], false); - instruction_test("cmp ($20, x)", &[0xC1, 0x20], false); - instruction_test("cmp ($20), y", &[0xD1, 0x20], false); + assert_instruction("cmp #$20", &[0xC9, 0x20]); + assert_instruction("cmp $2002", &[0xCD, 0x02, 0x20]); + assert_instruction("cmp $20", &[0xC5, 0x20]); + assert_instruction("cmp $20, x", &[0xD5, 0x20]); + assert_instruction("cmp $2002, x", &[0xDD, 0x02, 0x20]); + assert_instruction("cmp $2002, y", &[0xD9, 0x02, 0x20]); + assert_instruction("cmp ($20, x)", &[0xC1, 0x20]); + assert_instruction("cmp ($20), y", &[0xD1, 0x20]); // cpx - instruction_test("cpx #$20", &[0xE0, 0x20], false); - instruction_test("cpx $2002", &[0xEC, 0x02, 0x20], false); - instruction_test("cpx $20", &[0xE4, 0x20], false); + assert_instruction("cpx #$20", &[0xE0, 0x20]); + assert_instruction("cpx $2002", &[0xEC, 0x02, 0x20]); + assert_instruction("cpx $20", &[0xE4, 0x20]); // cpy - instruction_test("cpy #$20", &[0xC0, 0x20], false); - instruction_test("cpy $2002", &[0xCC, 0x02, 0x20], false); - instruction_test("cpy $20", &[0xC4, 0x20], false); + assert_instruction("cpy #$20", &[0xC0, 0x20]); + assert_instruction("cpy $2002", &[0xCC, 0x02, 0x20]); + assert_instruction("cpy $20", &[0xC4, 0x20]); } #[test] fn store_instructions() { //sta - instruction_test("sta $20", &[0x85, 0x20], false); - instruction_test("sta $20, x", &[0x95, 0x20], false); - instruction_test("sta $2002", &[0x8D, 0x02, 0x20], false); - instruction_test("sta $2002, x", &[0x9D, 0x02, 0x20], false); - instruction_test("sta $2002, y", &[0x99, 0x02, 0x20], false); - instruction_test("sta ($20, x)", &[0x81, 0x20], false); - instruction_test("sta ($20), y", &[0x91, 0x20], false); + assert_instruction("sta $20", &[0x85, 0x20]); + assert_instruction("sta $20, x", &[0x95, 0x20]); + assert_instruction("sta $2002", &[0x8D, 0x02, 0x20]); + assert_instruction("sta $2002, x", &[0x9D, 0x02, 0x20]); + assert_instruction("sta $2002, y", &[0x99, 0x02, 0x20]); + assert_instruction("sta ($20, x)", &[0x81, 0x20]); + assert_instruction("sta ($20), y", &[0x91, 0x20]); // stx - instruction_test("stx $20", &[0x86, 0x20], false); - instruction_test("stx $20, y", &[0x96, 0x20], false); - instruction_test("stx $2002", &[0x8E, 0x02, 0x20], false); + assert_instruction("stx $20", &[0x86, 0x20]); + assert_instruction("stx $20, y", &[0x96, 0x20]); + assert_instruction("stx $2002", &[0x8E, 0x02, 0x20]); // sty - instruction_test("sty $20", &[0x84, 0x20], false); - instruction_test("sty $20, x", &[0x94, 0x20], false); - instruction_test("sty $2002", &[0x8C, 0x02, 0x20], false); + assert_instruction("sty $20", &[0x84, 0x20]); + assert_instruction("sty $20, x", &[0x94, 0x20]); + assert_instruction("sty $2002", &[0x8C, 0x02, 0x20]); } #[test] fn bit() { - instruction_test("bit $10", &[0x24, 0x10], false); - instruction_test("bit $1001", &[0x2C, 0x01, 0x10], false); + assert_instruction("bit $10", &[0x24, 0x10]); + assert_instruction("bit $1001", &[0x2C, 0x01, 0x10]); } - // Variables & scopes. + // Labels & branching + // TODO + + // Control statements + // TODO: .byte, .word, variables in between (e.g. `.byte Variable::Value`, `lda #.hibyte(Variable)`) + + // Macros #[test] - fn using_variables() { - // TODO - // todo!() + fn macro_no_arguments() { + let mut asm = Assembler::new(EMPTY.to_vec()); + let res = asm + .assemble( + r#" +lda #42 + +.macro MACRO + lda #2 +.endmacro + +lda #1 +MACRO +"# + .as_bytes(), + ) + .unwrap(); + + assert_eq!(res.len(), 3); + let instrs: Vec<[u8; 2]> = vec![[0xA9, 0x2A], [0xA9, 0x01], [0xA9, 0x02]]; + + for i in 0..3 { + assert_eq!(res[i].size, 2); + assert_eq!(res[i].bytes[0], instrs[i][0]); + assert_eq!(res[i].bytes[1], instrs[i][1]); + } } #[test] - fn scoped_variable() { + fn macro_not_enough_arguments() { let mut asm = Assembler::new(EMPTY.to_vec()); let res = asm .assemble( r#" -.scope One ; This is a comment - adc #Variable +lda #42 - Variable = $20 -.endscope +.macro MACRO(Var) + lda #Var +.endmacro -.scope Another - Variable = $40 -.endscope +lda #1 +MACRO +"# + .as_bytes(), + ) + .unwrap_err(); -Variable = $30 -adc #Variable + assert_eq!( + res.first().unwrap().to_string(), + "Evaluation error (line 9): wrong number of arguments for 'MACRO': 1 required but 0 given." + ); + } -adc #One::Variable -adc #Another::Variable + #[test] + fn macro_too_many_arguments() { + let mut asm = Assembler::new(EMPTY.to_vec()); + let res = asm + .assemble( + r#" +lda #42 + +.macro MACRO(Var) + lda #Var +.endmacro + +lda #1 +MACRO(1, 2) +"# + .as_bytes(), + ) + .unwrap_err(); + + assert_eq!( + res.first().unwrap().to_string(), + "Evaluation error (line 9): wrong number of arguments for 'MACRO': 1 required but 2 given." + ); + } + + #[test] + fn macro_with_one_argument() { + let mut asm = Assembler::new(EMPTY.to_vec()); + let res = asm + .assemble( + r#" +lda #42 + +.macro MACRO(Var) + lda #Var +.endmacro + +lda #1 +MACRO(2) "# .as_bytes(), ) .unwrap(); - assert_eq!(res.len(), 4); - let instrs: Vec<[u8; 2]> = vec![[0x69, 0x20], [0x69, 0x30], [0x69, 0x20], [0x69, 0x40]]; + assert_eq!(res.len(), 3); + let instrs: Vec<[u8; 2]> = vec![[0xA9, 0x2A], [0xA9, 0x01], [0xA9, 0x02]]; - for i in 0..4 { + for i in 0..3 { assert_eq!(res[i].size, 2); assert_eq!(res[i].bytes[0], instrs[i][0]); assert_eq!(res[i].bytes[1], instrs[i][1]); @@ -2054,120 +1401,55 @@ adc #Another::Variable } #[test] - fn redefined_variable() { - let mut parser = Assembler::new(EMPTY.to_vec()); - let res = parser.assemble( - r#" -.scope One - Variable = 1 -.endscope + fn macro_unknown_arguments() { + let mut asm = Assembler::new(EMPTY.to_vec()); + let res = asm + .assemble( + r#" +lda #42 -Variable = 1 -Yet = 3 -Yet = 4 +.macro MACRO(Var) + lda #Va +.endmacro + +lda #1 +MACRO(1) "# - .as_bytes(), - ); + .as_bytes(), + ) + .unwrap_err(); - assert!(res.is_err()); - if let Err(e) = res { - assert_eq!( - e.message, - "variable 'Yet' is being re-assigned: it was previously defined in line 7" - ); - } + assert_eq!( + res.first().unwrap().to_string(), + "Evaluation error (line 5): 'a' is not a decimal value and \ + could not find variable 'Va' in the global scope either." + ); } #[test] - fn bad_assignment() { - instruction_err("Variable =", "incomplete assignment"); - instruction_err("Variable = ; comment", "incomplete assignment"); + fn macro_shadow_argument() { + let mut asm = Assembler::new(EMPTY.to_vec()); + let res = asm + .assemble( + r#" +Var = 3 +lda #42 + +.macro MACRO(Var) + lda #Va +.endmacro + +lda #1 +MACRO(1) +"# + .as_bytes(), + ) + .unwrap_err(); + + assert_eq!( + res.first().unwrap().to_string(), + "Evaluation error (line 5): 'Var' already defined in the global scope: \ + you cannot re-assign variables." + ); } } - -// // Literals - -// #[test] -// fn byte_literals_errors() { -// // TODO -// // instruction_err( -// // ".byte $0102", -// // "when parsing a data literal: only one byte of data is allowed here", -// // ); -// // instruction_err(".byte '$01", "non-terminated quote for byte literal"); -// // instruction_err(".byte '$01, $02", "non-terminated quote for byte literal"); -// } - -// #[test] -// fn byte_literals() { -// let mut asm = Assembler::new(EMPTY.to_vec()); - -// let mut res = asm.assemble(".byte $01".as_bytes()).unwrap(); -// assert_eq!(res.len(), 1); -// assert_hex(res[0], &[0x01]); - -// asm.reset(); -// res = asm.assemble(".db $01, $02".as_bytes()).unwrap(); -// assert_eq!(res.len(), 2); -// assert_hex(res[0], &[0x01]); -// assert_hex(res[1], &[0x02]); - -// asm.reset(); -// res = asm -// .assemble(".byte $01, 2, '%00000011', \"$04\"".as_bytes()) -// .unwrap(); -// assert_eq!(res.len(), 4); -// assert_hex(res[0], &[0x01]); -// assert_hex(res[1], &[0x02]); -// assert_hex(res[2], &[0x03]); -// assert_hex(res[3], &[0x04]); -// } - -// #[test] -// fn word_literals() { -// let mut asm = Assembler::new(EMPTY.to_vec()); - -// let mut res = asm.assemble(".word $01".as_bytes()).unwrap(); -// assert_eq!(res.len(), 1); -// assert_hex(res[0], &[0x01, 0x00]); - -// asm.reset(); -// res = asm.assemble(".dw $0102, $02".as_bytes()).unwrap(); -// assert_eq!(res.len(), 2); -// assert_hex(res[0], &[0x02, 0x01]); -// assert_hex(res[1], &[0x02, 0x00]); - -// asm.reset(); -// res = asm -// .assemble(".word $0102, $0204, '$0308', \"$0410\"".as_bytes()) -// .unwrap(); -// assert_eq!(res.len(), 4); -// assert_hex(res[0], &[0x02, 0x01]); -// assert_hex(res[1], &[0x04, 0x02]); -// assert_hex(res[2], &[0x08, 0x03]); -// assert_hex(res[3], &[0x10, 0x04]); -// } - -// #[test] -// fn variables_in_literals() { -// let mut asm = Assembler::new(EMPTY.to_vec()); -// let res = asm -// .assemble( -// r#" -// .scope One -// Variable = $01 -// .endscope - -// Variable = $02 -// .byte One::Variable, Variable, $03 -// "# -// .as_bytes(), -// ) -// .unwrap(); - -// assert_eq!(res.len(), 3); -// assert_hex(res[0], &[0x01]); -// assert_hex(res[1], &[0x02]); -// assert_hex(res[2], &[0x03]); -// } -// } -- cgit v1.2.3