aboutsummaryrefslogtreecommitdiff
path: root/lib/xixanta/src/assembler.rs
diff options
context:
space:
mode:
authorMiquel Sabaté Solà <mikisabate@gmail.com>2024-10-09 15:41:30 +0200
committerMiquel Sabaté Solà <mikisabate@gmail.com>2024-12-12 07:46:17 +0100
commit16114b2ca358dbbef09cb5a8d3a843a0e11a3b88 (patch)
tree49103ed4c0e906128df23c20656d88e4c4d1645e /lib/xixanta/src/assembler.rs
parentabd9a7679e5da78a51ed5eb488ae081a2bcb2b6c (diff)
downloadtools.nes-16114b2ca358dbbef09cb5a8d3a843a0e11a3b88.tar.gz
tools.nes-16114b2ca358dbbef09cb5a8d3a843a0e11a3b88.zip
Adapt the assembler to the changes on the parser
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à <mikisabate@gmail.com>
Diffstat (limited to 'lib/xixanta/src/assembler.rs')
-rw-r--r--lib/xixanta/src/assembler.rs2886
1 files changed, 1084 insertions, 1802 deletions
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<node>, 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<T> = std::result::Result<T, ParseError>;
+ /// 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<usize>,
+ args: Vec<PString>,
+}
+
pub struct Assembler {
- line: usize,
- column: usize,
context: Context,
literal_mode: Option<LiteralMode>,
- only_context: bool,
- force_decimal: bool,
- mapping: Mapping,
- offsets: HashMap<String, usize>,
+ stage: Stage,
+ macros: HashMap<String, Macro>,
}
-// 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<Segment>) -> 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<Bundle>, Vec<Error>> {
+ // 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<Vec<Bundle>> {
- 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<Error>> {
+ 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::<Vec<_>>(),
+ });
+ } 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<PNode>) -> Result<Vec<Bundle>, Vec<Error>> {
+ 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<Vec<&dyn Encodable>> {
- // 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<PNode>) -> 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<Bundle>,
+ ) -> 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<PNode>) -> Result<Bundle> {
+ fn evaluate_node(&mut self, node: &PNode) -> Result<Bundle, EvalError> {
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<PNode>) -> Result<Bundle> {
- let mnemonic = node.value.value.to_lowercase();
+ fn evaluate_hexadecimal(&mut self, node: &PNode) -> Result<Bundle, EvalError> {
+ 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<Bundle, EvalError> {
+ 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<Bundle, EvalError> {
+ 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<Bundle, EvalError> {
+ // 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<char>, source: &PNode) -> Result<u8, EvalError> {
+ 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<Bundle, EvalError> {
+ println!("NODE: {:#?}", node);
+ Ok(Bundle::default())
+ }
+
+ fn evaluate_variable(&mut self, id: &PString) -> Result<Bundle, EvalError> {
+ 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<Bundle, EvalError> {
+ 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<PNode>,
- ) -> 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<PNode>) -> 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<PNode>) -> 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<PNode>) -> 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<PNode>) -> Result<Bundle> {
- 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<PNode>) -> Result<Bundle> {
- 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<PNode>) -> Result<Bundle> {
- 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<char>) -> Result<u8> {
- 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<PNode>) -> Result<Bundle> {
- 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<PNode>) -> Result<Bundle> {
- 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<Bundle> {
- // 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<Bundle> {
- 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<Box<PNode>>, hi: bool) -> Result<Bundle> {
- 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<PNode>) -> Result<Bundle> {
- 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<Bundle> {
- 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<Vec<&dyn Encodable>> {
- // 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<PString>)> {
- // 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<u8> {
- // 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<PString> {
- // 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<PString> {
- // 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<R: Read>(&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]);
-// }
-// }