use crate::errors::ParseError; use crate::node::{NodeType, PNode, PString}; use crate::opcodes::{CONTROL_FUNCTIONS, INSTRUCTIONS}; use std::cmp::Ordering; use std::io::{self, BufRead, Read}; // TODO: allow for labels a la ".Lwhatever:" /// The Parser struct holds basic data for the current parsing session. #[derive(Default)] pub struct Parser { // The current line number. line: usize, // The current column number. column: usize, // The offset of the string being evaluated as a "line". Note that this can // vary wildly because on recursive expression parsing the line might be // slightly different. This property allows us to have a proper value for // each iteration. offset: usize, /// The nodes that have been evaluated for the current parsing session. You /// can count on this vector to be filled after calling /// `parser::Parser::parse`. pub nodes: Vec, } impl Parser { /// Parse the input from the given `reader`. You can then access the results /// from the `nodes` field. Otherwise, a vector of ParseError's might be /// returned. pub fn parse(&mut self, reader: impl Read) -> Result<(), Vec> { let mut errors = Vec::new(); for line in io::BufReader::new(reader).lines() { match line { Ok(l) => { if let Err(err) = self.parse_line(l.as_str()) { errors.push(err); } } Err(_) => errors.push(self.parser_error("could not get line")), } self.line += 1; } if errors.is_empty() { Ok(()) } else { Err(errors) } } // Parse a single `line` and push the parsed nodes into `self.nodes`. fn parse_line(&mut self, line: &str) -> Result<(), ParseError> { self.column = 0; self.offset = 0; // Skip until the first non-whitespace character. If that's not // possible, then it's an empty line and we can return early. if !self.skip_whitespace(line) { return Ok(()); } // Let's pin point the last character we need to care for parsing. This // can be either the start position of an inline comment (i.e. ';'), or // the real line end. let end = if let Some(comment) = line.find(';') { comment } else { line.len() }; // It's safe to trim the end of the resulting string. Moreover, doing so // can already show lines which are actually empty (e.g. a line which // simply contains a comment). If this is the case, just return an empty // node. let mut l = line.get(self.column..end).unwrap_or_default().trim_end(); if l.is_empty() { return Ok(()); } // Fetch the first element of the line, which we will call it an // "identifier" but might be a label or a statement. The label might be // followed by more code. Hence, push it first, then fetch the next // identifier and finally fall through. self.offset = 0; let (mut id, mut nt) = self.parse_identifier(l)?; if nt == NodeType::Label { self.nodes.push(PNode { node_type: nt, value: id, left: None, right: None, args: None, }); self.skip_whitespace(l); // Is it the label alone? If so return early. l = line.get(self.column..end).unwrap_or_default().trim_end(); if l.is_empty() { return Ok(()); } // The label is followed by a statement. Let's parse the identifier // for it and fall through. self.offset = 0; (id, nt) = self.parse_identifier(l)?; if nt == NodeType::Label { return Err(self.parser_error("cannot have multiple labels at the same location")); } self.skip_whitespace(l); } self.parse_statement(l, id) } // Given a `line` parses an identifier if possible. This identifier is not // necessary an "identifier" per se, but rather a first identifier-like // string which can be used to determine which kind of expression we are // dealing with. Returns a PString representing this identifier on success, // plus a hint on whether the identifier belongs to a label or not. fn parse_identifier(&mut self, line: &str) -> Result<(PString, NodeType), ParseError> { let start = self.column; let base_offset = self.offset; let mut nt = NodeType::Value; // For the general case we just need to iterate until a whitespace // character or an inline comment is found. Then our PString object is // merely whatever is on the column..self.column range. Note that we // need this iteration to be peekable so we can look ahead. This is // interesting for detecting identifiers which are scoped (e.g. // "Scope::Identifier"). let mut chars = line .get(self.offset..) .unwrap_or_default() .chars() .peekable(); while let Some(c) = chars.next() { // Check for characters that end an identifier. if c.is_whitespace() || c == ':' || c == '(' || c == ')' || c == '=' { // This next match looks scarier than what it actually is. To // sum things up, the ':' character is quite troublesome, since // it can mean three things depending on the context. // // 1. A label in the form of "label:". In this case there is no // "next" character. // 2. A scope operator in "Scope::Variable". In this case we // should swallow up both colon characters and continue // parsing the identifier. // 3. A relative label in "jmp :-" or "jmp :+". In this case we // want to exhaust the character stream match chars.peek() { Some(pc) => { if c == ':' { let next = *pc; match next { // Scope operator (e.g. "Scope::Variable"). ':' => { chars.next(); self.next(); self.next(); continue; } // Relative label (e.g. "jmp :+" or "jmp :-"). '+' | '-' => { // Relative labels start with ':' *always*. // Hence, if there was something behind it, // there is something wrong (e.g. "Bad:+"). if base_offset != self.offset { return Err(ParseError { line: self.line, message: "you cannot have a relative label inside of an identifier".to_string(), }); } // Skip the ':' character for the next loop. self.next(); // Let's exhaust the stream of characters. let mut size = 0; for cc in chars.by_ref() { // Only four levels of relative jumps // are allowed. This is a rather random // number, but if you are dealing with // more than two levels of relative // jumps you are already screwed, so we // are actually quite generous here for // what it's most probably just // spaghetti code. size += 1; if size > 4 { return Err(ParseError { line: self.line, message: "you can only jump to a maximum of four relative labels".to_string(), }); } // You cannot mix '+'/'-' characters // with others. if cc != next { let msg = if next == '+' { "forward" } else { "backward" }; return Err(ParseError { line: self.line, message: format!( "{} relative label can only have '{}' characters", msg, next ), }); } self.next(); } } // Regular label (e.g. "label:"). _ => nt = NodeType::Label, } } } // If there are no characters left, check if it was a regular label. None => { if c == ':' { nt = NodeType::Label; } } } // The value of the identifier is whatever we have picked up // along the parsing. let value = String::from(line.get(base_offset..self.offset).unwrap_or("").trim()); // The end of the identifier range has to be shortened for // regular labels because we want to ignore to extra ':' // character in the end. let end = match nt { NodeType::Label => { self.next(); self.column - 1 } _ => self.column, }; // And we are done. return Ok(( PString { value, line: self.line, start, end, }, nt, )); } self.next(); } // The line is merely the identifier (e.g. instruction with implied // addressing). let id = String::from(line.get(base_offset..).unwrap_or_default().trim()); Ok(( PString { value: id, line: self.line, start, end: self.column, }, NodeType::Value, )) } // Parse the top-level statement as found on the given `line` which has a // leading `id` positioned-string which may be an identifier. fn parse_statement(&mut self, line: &str, id: PString) -> Result<(), ParseError> { // There are only two top-level statements: instructions and // assignments. Other kinds of expressions can also be used in the // middle of assignments or instructions, and so they have to be handled // as common expressions. Whether expressions make sense at the // different levels is something to be figured out by the assembler. match INSTRUCTIONS.get(&id.value) { Some(_) => self.parse_instruction(line, id), None => { if line.contains('=') { self.parse_assignment(line, id) } else { let node = self.parse_expression_with_identifier(id, line)?; self.nodes.push(node); Ok(()) } } } } // Parse the given `line` as an instruction being identified by `id`. fn parse_instruction(&mut self, line: &str, id: PString) -> Result<(), ParseError> { let mut paren = 0; // After the initial instruction identifier (e.g. `lda`), there might be // an undefined white space. Let's skip it now. self.skip_whitespace(line); // After skipping the identifier, are we actually on a weird assignment // scenario? (e.g. `lda = #42`). If so, then complain on the programmer // using a reserved instruction mnemonic as a variable name. if line.chars().nth(self.offset).unwrap_or_default() == '=' { return Err(self.parser_error( format!( "cannot use the reserved mnemonic '{}' as a variable name", id.value ) .as_str(), )); } // If the line at the current point starts with an open paren, then we // assume that the indirect addressing mode is being used. If this is // the case, then the left arm is actually what's inside of the // parenthesis. Otherwise we have to grab until the very end. let indirect = line.chars().nth(self.offset).unwrap_or(',') == '('; let l = if indirect { // Skip the '(' character and skip whitespaces. self.next(); self.skip_whitespace(line); // Now let's find the matching paren for the one that opened the // indirect addressing mode, and that's the end of our left arm for // this instruction. paren = self.find_matching_paren(line, self.offset)?; line.get(self.offset..paren).unwrap_or_default() } else { line.get(self.offset..).unwrap_or_default() }; // Is there a left arm at all? If so, then parse it now but considering // the trimmed `l` variable, which is a bit special when using indirect // addressing mode. self.offset = 0; let mut left = if l.is_empty() { None } else { Some(Box::new(self.parse_left_arm(l)?)) }; // Skip whitespace until the possible right arm. Notice that both // `paren` on indirect addressing mode, and `parse_left_arm` have set // the "cursor" just after any possible comma. Hence, if there's // anything left, then it's the right arm which might have leading // spaces. self.skip_whitespace(l); // At this point, if there is nothing there, then we have no right arm. // Otherwise we have to parse the expression. let mut right_str = l.get(self.offset..).unwrap_or_default(); let mut right = if right_str.is_empty() { None } else { self.offset = 0; Some(Box::new(self.parse_expression(right_str)?)) }; // If we were in indirect addressing mode, then there's some juggling we // have to do for the parsed expressions. This is the most complex part // from this function, as it will mutate the `left` and the `right` // nodes in subtle ways. But this is better than having things in a // different function because the rest of the code is pretty much the // same. if indirect { // In indirect addressing mode there's *always* a left arm. if left.is_none() { return Err(self.parser_error("empty indirect addressing")); } // Now, here's the trick: if there's something left after the // parenthesis, then we have the right arm there. Note that the // Indirection node cannot have a right arm at the same time. If // this is the case, we are going to freak out now. right_str = line.get(paren..).unwrap_or_default(); if !(right_str.is_empty() || right_str == ")") && right.is_some() { return Err(self.parser_error("bad indirect addressing")); } // The left arm from an indirect addressing mode is actually the // indirection itself. left = Some(Box::new(PNode { node_type: NodeType::Indirection, value: PString::default(), left, right, args: None, })); // Do we have anything as a right arm? right = if right_str.is_empty() || right_str == ")" { None } else { self.offset = 0; // Skip any possible leading space on ") ,". self.next(); self.skip_whitespace(right_str); // Skip any possible leading space after the comma. self.next(); self.skip_whitespace(right_str); // And finally parse the right arm for the global instruction. Some(Box::new(self.parse_expression(right_str)?)) }; } // We can push the resulting parsed expressions. self.nodes.push(PNode { node_type: NodeType::Instruction, value: id, left, right, args: None, }); Ok(()) } // Parse the given `line` as an assignment statement which declares a // variable at `id`. fn parse_assignment(&mut self, line: &str, id: PString) -> Result<(), ParseError> { // Notice that `parse_identifier` pretty much swallows any kind of // identifier without doing any sanity checks. Now it's the time to do // so. if let Err(msg) = id.is_valid_identifier(false) { return Err(self.parser_error(&msg)); } // Skip whitespaces and make sure that we have a '=' sign. self.skip_whitespace(line); if line.chars().nth(self.offset).unwrap_or_default() != '=' { return Err(self.parser_error(format!("unknown instruction '{}'", id.value).as_str())); } // Skip the '=' sign and any possible whitespaces. self.next(); self.skip_whitespace(line); // Parse the expression on the right side of the assignment. let rest = line.get(self.offset..).unwrap_or("").trim_end(); if rest.is_empty() { return Err(self.parser_error("incomplete assignment")); }; self.offset = 0; let left = self.parse_expression(rest)?; // And push the node. self.nodes.push(PNode { node_type: NodeType::Assignment, value: id, left: Some(Box::new(left)), right: None, args: None, }); Ok(()) } // Parse any possible arguments for the given `line`. The offset is supposed // to be at a point where arguments might appear, either between parens or // not. fn parse_arguments(&mut self, line: &str) -> Result, ParseError> { // Skip any possible whitespace before the optional opening paren. self.skip_whitespace(line); // Scope the end of the argument list. If the arguments are enclosed on // parenthesis, take that into account, otherwise we will parse until // the end of the cleaned line. let paren = line.chars().nth(self.offset).unwrap_or_default() == '('; let end = if paren { // We have a parenthesis. Skip it. self.next(); self.skip_whitespace(line); // The end is actually the matching paren for the current opening // one. self.find_matching_paren(line, self.offset)? } else { line.len() }; let mut args = Vec::new(); let trimmed_str = line.get(..end).unwrap_or_default().trim_end(); // Having an infinite loop with `break`s inside is admittedly not the // cleanest thing ever, but it does its job. loop { // This looks scarier than it actually is. It first finds the end of // the argument. Then it calculates a diff on trimming the end or // not. This diff will be used to re-adjust the column after the // argument is parsed, so we skip any final spaces. let (arg_end, comma) = self.find_left_end(trimmed_str)?; let arg_untrimmed = line.get(self.offset..arg_end).unwrap_or_default(); let arg = arg_untrimmed.trim_end(); let diff = arg_untrimmed.len() - arg.len(); // Do we actually have an argument. If not then this is the end of // our loop. if arg.is_empty() { break; } // Parse the argument, which is trimmed down from the line and hence // the offset needs to be reset. self.offset = 0; args.push(self.parse_expression(arg)?); // After the parsing is done for the current argument, move both // `self.offset` and `self.column` right after the end of the // current argument. self.offset = arg_end; self.column += diff; // Was the argument ended by a comma? If so there are more arguments // to be parsed. Otherwise we can break the loop if it wasn't // catched for whatever reason by the previous check. if comma { // Skip the comma character and any leading white spaces for the // next argument. self.next(); self.skip_whitespace(line); } else { break; } } Ok(args) } // Parse the left arm from an instruction and leave `offset` and `column` // past the end of it. fn parse_left_arm(&mut self, line: &str) -> Result { let start_column = self.column; // We track the start value of the offset and we will keep track of the // movement of it on `end`. This allows us to preserve the value on // inner calls that might modify the offset value. let (end, comma) = self.find_left_end(line)?; // Set the offset to 0 since we are constraining the string to be // parsed. let str = line.get(..end).unwrap_or_default().trim_end(); self.offset = 0; // Parse the expression that we can get from the current offset to the // computed end. let expr = self.parse_expression(str); // Set the `offset` and `column` to the end of the line that is shared // with the caller. self.offset = end; self.column = start_column + end; // If there was a comma, then the caller expects this function to move // both `offset` and `column` past it. if comma { self.next(); } expr } // Find the end position for a "left arm"-like expression. That is, there // might be opening/closing parenthesis which need to be balanced. On // success it returns the index from within the given `line`, and a boolean // which is set to true/false on whether a comma was found. fn find_left_end(&self, line: &str) -> Result<(usize, bool), ParseError> { let mut idx = self.offset; let mut parens = 0; let mut comma = false; for c in line.get(self.offset..).unwrap_or_default().chars() { if c == ',' { if parens == 0 { comma = true; break; } } else if c == '(' { parens += 1; } else if c == ')' { parens -= 1; } idx += 1; if parens < 0 { return Err(self.parser_error("too many closing parenthesis")); } } if parens > 0 { return Err(self.parser_error("unclosed parenthesis")); } Ok((idx, comma)) } // Finds the matching parenthesis which closes the parenthesis that was just // opened. The `init` index point to the next character after the opening // paren from the given `line`. fn find_matching_paren(&self, line: &str, init: usize) -> Result { let mut idx = init; let mut parens = 1; for c in line.get(init..).unwrap_or_default().chars() { if c == '(' { parens += 1; } else if c == ')' { parens -= 1; } match parens.cmp(&0) { Ordering::Equal => return Ok(idx), Ordering::Less => return Err(self.parser_error("too many closing parenthesis")), Ordering::Greater => {} } idx += 1; } if parens > 0 { return Err(self.parser_error("unclosed parenthesis")); } Ok(idx) } // Parse the expression under `line`. Indeces such as `self.column` and // `self.offset` are assumed to be correct at this point for the given // `line` (e.g. the line might not be a full line but rather a limited range // and the offset has been set accordingly). Returns a new node for the // expression at hand. fn parse_expression(&mut self, line: &str) -> Result { let (id, nt) = self.parse_identifier(line)?; if nt == NodeType::Label { Err(self.parser_error("not expecting a label defined here")) } else { self.parse_expression_with_identifier(id, line) } } // Parse the expression under `line` by taking into consideration that a // part of it has already been parsed and evaluated as the given `id`. // Indeces such as `self.column` and `self.offset` are assumed to be correct // at this point. Returns a new node for the expression at hand. fn parse_expression_with_identifier( &mut self, id: PString, line: &str, ) -> Result { // Reaching this condition is usually a bad sign, but there is so many // ways in which it could go wrong, that an `assert!` wouldn't be fair // either. Hence, just error out. if id.is_empty() { return Err(self.parser_error("invalid identifier")); } if id.value.starts_with(".") { self.parse_control(id, line) } else if line.starts_with('$') || line.starts_with('#') || line.starts_with('%') { self.parse_literal(id, line) } else { // If there is an indication that it might be a macro call, process // it as such. self.skip_whitespace(line); if !line .get(self.offset..) .unwrap_or_default() .trim_end() .is_empty() { let args = self.parse_arguments(line)?; return Ok(PNode { node_type: NodeType::Call, value: id, left: None, right: None, args: if args.is_empty() { None } else { Some(args) }, }); } // Blindly return the identifier as a PNode. This might be either a // value as-is, or a macro call which we can't make sense at the // moment. Eitherway, let the assembler decide. Ok(PNode { node_type: NodeType::Value, value: id, left: None, right: None, args: None, }) } } // Returns a NodeType::Control node with whatever could be parsed // considering the given `id` and rest of the `line`. fn parse_control(&mut self, id: PString, line: &str) -> Result { let mut left = None; let required; let node_type; // Ensure that this is a function that we know of. In the past this was // not done and it brought too many problems that made the more // "abstract" way of handling this just too complicated. if let Some(control) = CONTROL_FUNCTIONS.get(&id.value.to_lowercase()) { node_type = control.control_type.clone(); required = control.required_args; // If this control function has an identifier (e.g. `.macro // Identifier(args...)`), let's parse it now. if control.has_identifier { self.skip_whitespace(line); left = Some(Box::new(PNode { node_type: NodeType::Value, value: self.parse_identifier(line)?.0, left: None, right: None, args: None, })); } } else { return Err(self.parser_error(format!("unknown function '{}'", id.value).as_str())); } // At this point we reached the arguments (i.e. any identifier required // by the control function has already been parsed and set in `left`). // Then, just parse the arguments and ensure that it matches the amount // required by the function. let args = self.parse_arguments(line)?; if let Some(args_required) = required { if args.len() != args_required { return Err(self.parser_error( format!("wrong number of arguments for function '{}'", id.value).as_str(), )); } } Ok(PNode { node_type: NodeType::Control(node_type), value: id, left, right: None, args: if args.is_empty() { None } else { Some(args) }, }) } // Returns a NodeType::Literal node with whatever could be parsed // considering the given `id` and rest of the `line`. fn parse_literal(&mut self, id: PString, line: &str) -> Result { // Force the column to point to the literal character just in case // of expressions like '#.hibyte'. Then skip whitespaces for super // ugly statements such as '# 20'. This is ugly but we should permit // it. A later linter can yell at a programmer for this. self.column = id.start; self.offset = 0; self.next(); self.skip_whitespace(line); // With this, just fetch the inner expression and return the literal // node. let inner = line.get(self.offset..).unwrap_or(""); self.offset = 0; let left = self.parse_expression(inner)?; Ok(PNode { node_type: NodeType::Literal, value: id, left: Some(Box::new(left)), right: None, args: None, }) } // Returns a new ParseError by using the current line. fn parser_error(&self, msg: &str) -> ParseError { ParseError { message: String::from(msg), line: self.line, } } // Advances `self.column` and `self.offset` until a non-whitespace character // is found. Note that the initial index is bound to `self.offset`. Returns // false if the line can be skipped entirely, true otherwise. fn skip_whitespace(&mut self, line: &str) -> bool { if line.is_empty() { return false; } for c in line.get(self.offset..).unwrap_or("").chars() { if !c.is_whitespace() { if c == ';' { return false; } return true; } self.next(); } true } // Increment `self.column` and `self.offset` by one. fn next(&mut self) { self.column += 1; self.offset += 1; } } #[cfg(test)] mod tests { use super::*; use crate::node::ControlType; fn assert_one_valid(parser: &mut Parser, line: &str) { assert!(parser.parse(line.as_bytes()).is_ok()); assert!(parser.nodes.len() == 1); } fn assert_node(node: &PNode, nt: NodeType, line: &str, value: &str) { assert_eq!(node.node_type, nt); assert_eq!( node.value.value.as_str(), line.get(node.value.start..node.value.end).unwrap() ); assert_eq!(node.value.value.as_str(), value); } // Empty #[test] fn empty_line() { let mut parser = Parser::default(); assert!(parser.parse("".as_bytes()).is_ok()); assert_eq!(parser.nodes.len(), 0); } #[test] fn spaced_line() { let mut parser = Parser::default(); assert!(parser.parse(" ".as_bytes()).is_ok()); assert_eq!(parser.nodes.len(), 0); } #[test] fn just_a_comment_line() { for line in vec![";; This is a comment", " ;; Comment"].into_iter() { let mut parser = Parser::default(); assert!(parser.parse(line.as_bytes()).is_ok()); assert_eq!(parser.nodes.len(), 0); } } // Labels #[test] fn anonymous_label() { let mut parser = Parser::default(); assert!(parser.parse(":".as_bytes()).is_ok()); assert_eq!(parser.nodes.len(), 1); assert!(parser.nodes.first().unwrap().value.value.is_empty()); assert_eq!(parser.nodes.first().unwrap().value.start, 0); assert_eq!(parser.nodes.first().unwrap().value.end, 0); parser = Parser::default(); assert!(parser.parse(" :".as_bytes()).is_ok()); assert_eq!(parser.nodes.len(), 1); assert!(parser.nodes.first().unwrap().value.value.is_empty()); assert_eq!(parser.nodes.first().unwrap().value.start, 2); assert_eq!(parser.nodes.first().unwrap().value.end, 2); } #[test] fn named_label() { let mut parser = Parser::default(); assert!(parser.parse("label:".as_bytes()).is_ok()); assert_eq!(parser.nodes.len(), 1); assert_eq!(parser.nodes.first().unwrap().value.value, "label"); assert_eq!(parser.nodes.first().unwrap().value.start, 0); assert_eq!(parser.nodes.first().unwrap().value.end, 5); parser = Parser::default(); assert!(parser.parse(" label:".as_bytes()).is_ok()); assert_eq!(parser.nodes.len(), 1); assert_eq!(parser.nodes.first().unwrap().value.value, "label"); assert_eq!(parser.nodes.first().unwrap().value.start, 2); assert_eq!(parser.nodes.first().unwrap().value.end, 7); } #[test] fn label_with_instruction() { let line = "label: dex"; let mut parser = Parser::default(); assert!(parser.parse(line.as_bytes()).is_ok()); assert_eq!(parser.nodes.len(), 2); // Label. assert_eq!(parser.nodes.first().unwrap().value.value, "label"); assert_eq!(parser.nodes.first().unwrap().value.start, 0); assert_eq!(parser.nodes.first().unwrap().value.end, 5); // Instruction assert_node( parser.nodes.last().unwrap(), NodeType::Instruction, line, "dex", ) } // Literals #[test] fn parse_pound_literal() { for line in vec!["#20", " #20 ", " #20 ; Comment", " label: # 20"].into_iter() { let mut parser = Parser::default(); assert!(parser.parse(line.as_bytes()).is_ok()); let node = parser.nodes.last().unwrap(); assert_eq!(node.node_type, NodeType::Literal); assert!(node.right.is_none()); assert!(node.args.is_none()); let left = node.left.clone().unwrap(); assert_eq!(left.node_type, NodeType::Value); assert_eq!(left.value.value, "20"); assert_eq!(line.get(left.value.start..left.value.end).unwrap(), "20"); } } #[test] fn parse_compound_literal() { let line = "#$20"; let mut parser = Parser::default(); assert!(parser.parse(line.as_bytes()).is_ok()); let node = parser.nodes.last().unwrap(); assert_eq!(node.node_type, NodeType::Literal); assert!(node.right.is_none()); assert!(node.args.is_none()); let inner = node.left.clone().unwrap(); assert_eq!(inner.node_type, NodeType::Literal); assert_eq!(inner.value.value, "$20"); assert_eq!(line.get(inner.value.start..inner.value.end).unwrap(), "$20"); let innerinner = inner.left.clone().unwrap(); assert_eq!(innerinner.node_type, NodeType::Value); assert_eq!(innerinner.value.value, "20"); assert_eq!( line.get(innerinner.value.start..innerinner.value.end) .unwrap(), "20" ); } #[test] fn parse_variable_in_literal() { let line = "#Variable"; let mut parser = Parser::default(); assert!(parser.parse(line.as_bytes()).is_ok()); let node = parser.nodes.last().unwrap(); assert_eq!(node.node_type, NodeType::Literal); assert!(node.right.is_none()); assert!(node.args.is_none()); let inner = node.left.clone().unwrap(); assert_eq!(inner.node_type, NodeType::Value); assert_eq!(inner.value.value, "Variable"); assert_eq!( line.get(inner.value.start..inner.value.end).unwrap(), "Variable" ); } #[test] fn parse_bad_literals() { for line in vec!["#", "#%", "$"].into_iter() { let mut parser = Parser::default(); let err = parser.parse(line.as_bytes()).unwrap_err(); assert_eq!(err.first().unwrap().message, "invalid identifier"); } } // Regular instructions. #[test] fn instruction_with_implied() { for line in vec![ "dex", " dex", " dex ", " dex ; Comment", " label: dex", ] .into_iter() { let mut parser = Parser::default(); assert!(parser.parse(line.as_bytes()).is_ok()); let node = parser.nodes.last().unwrap(); assert_node(node, NodeType::Instruction, line, "dex"); assert!(node.left.is_none()); assert!(node.right.is_none()); assert!(node.args.is_none()); } } #[test] fn instruction_with_implied_explicit() { for line in vec!["inc a", " inc a", " inc a "].into_iter() { let mut parser = Parser::default(); assert_one_valid(&mut parser, line); let node = parser.nodes.first().unwrap(); assert_node(node, NodeType::Instruction, line, "inc"); assert!(node.right.is_none()); assert!(node.args.is_none()); assert_node(&node.left.clone().unwrap(), NodeType::Value, line, "a"); } } #[test] fn instruction_with_zeropage() { for line in vec!["inc $20", " inc $20", " inc $20 "].into_iter() { let mut parser = Parser::default(); assert_one_valid(&mut parser, line); let node = parser.nodes.first().unwrap(); assert_node(node, NodeType::Instruction, line, "inc"); assert!(node.right.is_none()); assert!(node.args.is_none()); assert_node(&node.left.clone().unwrap(), NodeType::Literal, line, "$20"); } } #[test] fn instruction_with_immediate() { for line in vec!["adc #$20", " adc #$20 ", " adc #$20 "].into_iter() { let mut parser = Parser::default(); assert_one_valid(&mut parser, line); let node = parser.nodes.first().unwrap(); assert_node(node, NodeType::Instruction, line, "adc"); assert!(node.right.is_none()); assert!(node.args.is_none()); assert_node(&node.left.clone().unwrap(), NodeType::Literal, line, "#$20"); } } #[test] fn instruction_with_absolute() { for line in vec!["inc $2002", " inc $2002", " inc $2002 "].into_iter() { let mut parser = Parser::default(); assert_one_valid(&mut parser, line); let node = parser.nodes.first().unwrap(); assert_node(node, NodeType::Instruction, line, "inc"); assert!(node.right.is_none()); assert!(node.args.is_none()); assert_node( &node.left.clone().unwrap(), NodeType::Literal, line, "$2002", ); } } #[test] fn instruction_with_absolute_x() { for line in vec![ "inc $2002, x", " inc $2002, x", " inc $2002, x ", " label: inc $2002, x ; Comment", ] .into_iter() { let mut parser = Parser::default(); assert!(parser.parse(line.as_bytes()).is_ok()); let node = parser.nodes.last().unwrap(); assert_node(node, NodeType::Instruction, line, "inc"); assert!(node.args.is_none()); assert_node( &node.left.clone().unwrap(), NodeType::Literal, line, "$2002", ); assert_node(&node.right.clone().unwrap(), NodeType::Value, line, "x"); } } #[test] fn indirect_addressing_bare() { for line in vec![ "lda ($2000)", " lda ( $2000 ) ; Comment", " : lda ( $2000)", "lda($2000)", ] .into_iter() { let mut parser = Parser::default(); assert!(parser.parse(line.as_bytes()).is_ok()); let node = parser.nodes.last().unwrap(); assert_node(node, NodeType::Instruction, line, "lda"); assert!(node.right.is_none()); let left = node.left.clone().unwrap(); assert_eq!(left.node_type, NodeType::Indirection); assert_node(&left.left.unwrap(), NodeType::Literal, line, "$2000"); assert!(left.right.is_none()); } } #[test] fn indirect_addressing_x() { for line in vec![ "lda ($20, x)", " lda ($20, x)", " lda ($20,x) ", " : lda ($20 , x) ; Comment", " lda ( $20 , x ) ", ] .into_iter() { let mut parser = Parser::default(); assert!(parser.parse(line.as_bytes()).is_ok()); let node = parser.nodes.last().unwrap(); assert_node(node, NodeType::Instruction, line, "lda"); assert!(node.right.is_none()); let left = node.left.clone().unwrap(); assert_eq!(left.node_type, NodeType::Indirection); assert_node(&left.left.unwrap(), NodeType::Literal, line, "$20"); assert_node(&left.right.unwrap(), NodeType::Value, line, "x"); } } #[test] fn bad_indirect_addressing_x() { let mut parser = Parser::default(); let err = parser.parse("lda (Variable, x), y".as_bytes()).unwrap_err(); assert_eq!(err.first().unwrap().message, "bad indirect addressing"); } #[test] fn indirect_addressing_y() { for line in vec!["lda ($20), y"].into_iter() { let mut parser = Parser::default(); assert!(parser.parse(line.as_bytes()).is_ok()); let node = parser.nodes.last().unwrap(); assert_node(node, NodeType::Instruction, line, "lda"); let left = node.left.clone().unwrap(); assert_eq!(left.node_type, NodeType::Indirection); assert_node(&left.left.unwrap(), NodeType::Literal, line, "$20"); assert!(left.right.is_none()); let right = node.right.clone().unwrap(); assert_node(&right, NodeType::Value, line, "y"); } } #[test] fn variable_in_instruction() { let line = "lda Variable, x"; let mut parser = Parser::default(); assert!(parser.parse(line.as_bytes()).is_ok()); let node = parser.nodes.last().unwrap(); assert_node(node, NodeType::Instruction, line, "lda"); assert!(node.args.is_none()); assert_node( &node.left.clone().unwrap(), NodeType::Value, line, "Variable", ); assert_node(&node.right.clone().unwrap(), NodeType::Value, line, "x"); } #[test] fn variable_literal_in_instruction() { let line = "lda #Variable, x"; let mut parser = Parser::default(); assert!(parser.parse(line.as_bytes()).is_ok()); let node = parser.nodes.last().unwrap(); assert_node(node, NodeType::Instruction, line, "lda"); assert!(node.args.is_none()); assert_node( &node.left.clone().unwrap(), NodeType::Literal, line, "#Variable", ); assert_node(&node.right.clone().unwrap(), NodeType::Value, line, "x"); } #[test] fn scoped_variable_literal_in_instruction() { for var in vec!["Scope::Variable", "Scope::Inner::Variable"].into_iter() { let line = format!("lda #{}", var); let mut parser = Parser::default(); assert!(parser.parse(line.as_bytes()).is_ok()); let node = parser.nodes.last().unwrap(); assert_node(node, NodeType::Instruction, line.as_str(), "lda"); assert!(node.right.is_none()); assert!(node.args.is_none()); assert_node( &node.left.clone().unwrap(), NodeType::Literal, line.as_str(), format!("#{}", var).as_str(), ); } } #[test] fn bad_variable_scoping() { let mut parser = Parser::default(); let err = parser.parse("adc #One:Variable".as_bytes()).unwrap_err(); assert_eq!( err.first().unwrap().message, "not expecting a label defined here" ); } #[test] fn reserved_mnemonic_name() { let mut parser = Parser::default(); let err = parser.parse("lda = $10".as_bytes()).unwrap_err(); assert_eq!( err.first().unwrap().message, "cannot use the reserved mnemonic 'lda' as a variable name" ); } #[test] fn relative_labels() { for label in vec![":+", ":++", ":+++ ", ":++++", ":-", ":--", ":---", ":----"].into_iter() { let line = format!("jmp {}", label); let mut parser = Parser::default(); assert!(parser.parse(line.as_bytes()).is_ok()); let node = parser.nodes.last().unwrap(); assert_node(node, NodeType::Instruction, line.as_str(), "jmp"); assert!(node.right.is_none()); assert!(node.args.is_none()); assert_node( &node.left.clone().unwrap(), NodeType::Value, line.as_str(), label.trim(), ); } } #[test] fn bad_relative_labels() { let mut parser = Parser::default(); let mut line = "jmp :+++++"; let mut err = parser.parse(line.as_bytes()).unwrap_err(); assert_eq!( err.first().unwrap().message, "you can only jump to a maximum of four relative labels" ); line = "jmp :+-"; err = parser.parse(line.as_bytes()).unwrap_err(); assert_eq!( err.first().unwrap().message, "forward relative label can only have '+' characters" ); line = "jmp :-+-"; err = parser.parse(line.as_bytes()).unwrap_err(); assert_eq!( err.first().unwrap().message, "backward relative label can only have '-' characters" ); line = "jmp Identifier:++"; err = parser.parse(line.as_bytes()).unwrap_err(); assert_eq!( err.first().unwrap().message, "you cannot have a relative label inside of an identifier" ); } // Assignments #[test] fn bad_assignments() { let mut parser = Parser::default(); let mut err = parser.parse("abc = $10".as_bytes()).unwrap_err(); assert_eq!( err.first().unwrap().message, "cannot use names which are valid hexadecimal values such as 'abc'" ); parser = Parser::default(); err = parser.parse("var =".as_bytes()).unwrap_err(); assert_eq!(err.first().unwrap().message, "incomplete assignment"); parser = Parser::default(); err = parser.parse("var = ".as_bytes()).unwrap_err(); assert_eq!(err.first().unwrap().message, "incomplete assignment"); parser = Parser::default(); err = parser.parse("var = ; Comment".as_bytes()).unwrap_err(); assert_eq!(err.first().unwrap().message, "incomplete assignment"); } // Control statements. #[test] fn parse_control_no_args() { for line in vec![ ".endmacro", " .endmacro", " label: .endmacro ; Comment", ] .into_iter() { let mut parser = Parser::default(); assert!(parser.parse(line.as_bytes()).is_ok()); let node = parser.nodes.last().unwrap(); assert_node( node, NodeType::Control(ControlType::EndMacro), line, ".endmacro", ); assert!(node.left.is_none()); assert!(node.right.is_none()); assert!(node.args.is_none()); } } #[test] fn parse_control_one_arg() { for line in vec![ ".hibyte $2000", " .hibyte $2000", " label: .hibyte $2000 ; Comment", " .hibyte($2000)", " .hibyte ( $2000 )", ] .into_iter() { let mut parser = Parser::default(); assert!(parser.parse(line.as_bytes()).is_ok()); let node = parser.nodes.last().unwrap(); assert_node( node, NodeType::Control(ControlType::Hibyte), line, ".hibyte", ); assert!(node.left.is_none()); assert!(node.right.is_none()); let args = node.args.clone().unwrap(); assert_eq!(args.len(), 1); assert_node(args.first().unwrap(), NodeType::Literal, line, "$2000"); } } #[test] fn parse_control_multiple_args() { for line in vec![ ".byte $10, $20", " .byte $10, $20", " label: .byte $10, $20 ; Comment", " .byte($10, $20)", " .byte ( $10 , $20 )", ] .into_iter() { let mut parser = Parser::default(); assert!(parser.parse(line.as_bytes()).is_ok()); let node = parser.nodes.last().unwrap(); assert_node(node, NodeType::Control(ControlType::Byte), line, ".byte"); assert!(node.left.is_none()); assert!(node.right.is_none()); let args = node.args.clone().unwrap(); assert_eq!(args.len(), 2); assert_node(args.first().unwrap(), NodeType::Literal, line, "$10"); assert_node(args.last().unwrap(), NodeType::Literal, line, "$20"); } } #[test] fn parse_control_id_no_args() { for line in vec![ ".scope Scope", " .scope Scope", " label: .scope Scope ; Comment", " .scope Scope", ] .into_iter() { let mut parser = Parser::default(); assert!(parser.parse(line.as_bytes()).is_ok()); let node = parser.nodes.last().unwrap(); assert_node( node, NodeType::Control(ControlType::StartScope), line, ".scope", ); assert!(node.right.is_none()); assert!(node.args.is_none()); let left = node.left.clone().unwrap(); assert_node(&left, NodeType::Value, line, "Scope"); } } #[test] fn parse_control_id_one_arg() { for line in vec![ ".macro Macro(arg1)", ".macro Macro arg1 ", " .macro Macro(arg1)", " label: .macro Macro(arg1) ; Comment", " .macro Macro ( arg1 )", ] .into_iter() { let mut parser = Parser::default(); assert!(parser.parse(line.as_bytes()).is_ok()); let node = parser.nodes.last().unwrap(); assert_node( node, NodeType::Control(ControlType::StartMacro), line, ".macro", ); assert!(node.right.is_none()); let left = node.left.clone().unwrap(); assert_node(&left, NodeType::Value, line, "Macro"); let args = node.args.clone().unwrap(); assert_eq!(args.len(), 1); assert_node(args.first().unwrap(), NodeType::Value, line, "arg1"); } } #[test] fn parse_control_id_multiple_args() { for line in vec![ ".macro Macro(arg1, arg2)", ".macro Macro arg1, arg2 ", " .macro Macro(arg1, arg2)", " label: .macro Macro(arg1, arg2) ; Comment", " .macro Macro ( arg1 , arg2 )", ] .into_iter() { let mut parser = Parser::default(); assert!(parser.parse(line.as_bytes()).is_ok()); let node = parser.nodes.last().unwrap(); assert_node( node, NodeType::Control(ControlType::StartMacro), line, ".macro", ); assert!(node.right.is_none()); let left = node.left.clone().unwrap(); assert_node(&left, NodeType::Value, line, "Macro"); let args = node.args.clone().unwrap(); assert_eq!(args.len(), 2); assert_node(args.first().unwrap(), NodeType::Value, line, "arg1"); assert_node(args.last().unwrap(), NodeType::Value, line, "arg2"); } } #[test] fn parse_control_bad_number_args() { for line in vec![".hibyte", ".hibyte($20, $22)"].into_iter() { let mut parser = Parser::default(); let err = parser.parse(line.as_bytes()).unwrap_err(); assert_eq!( err.first().unwrap().message, "wrong number of arguments for function '.hibyte'" ); } } #[test] fn parse_control_in_instructions() { for line in vec!["lda #.hibyte($2010)", " label: lda #.hibyte $2010 "].into_iter() { let mut parser = Parser::default(); assert!(parser.parse(line.as_bytes()).is_ok()); let node = parser.nodes.last().unwrap(); assert_node(node, NodeType::Instruction, line, "lda"); assert!(node.right.is_none()); assert!(node.args.is_none()); let left = node.left.clone().unwrap(); assert_node(&left, NodeType::Literal, line, "#.hibyte"); assert!(left.right.is_none()); assert!(left.args.is_none()); let control = left.left.clone().unwrap(); assert_node( &control, NodeType::Control(ControlType::Hibyte), line, ".hibyte", ); assert!(control.left.is_none()); assert!(control.right.is_none()); let args = control.args.clone().unwrap(); assert_eq!(args.len(), 1); assert_node(args.first().unwrap(), NodeType::Literal, line, "$2010"); } } #[test] fn parse_control_in_indirect_x_instructions() { for line in vec![ "lda (#.hibyte($2010), x)", " label: lda (#.hibyte ( $2010 ) , x)", ] .into_iter() { let mut parser = Parser::default(); assert!(parser.parse(line.as_bytes()).is_ok()); let node = parser.nodes.last().unwrap(); assert_node(node, NodeType::Instruction, line, "lda"); assert!(node.right.is_none()); assert!(node.args.is_none()); let ind = node.left.clone().unwrap(); assert_eq!(ind.node_type, NodeType::Indirection); assert!(ind.args.is_none()); let left = ind.left.clone().unwrap(); assert_node(&left, NodeType::Literal, line, "#.hibyte"); assert!(left.right.is_none()); assert!(left.args.is_none()); let control = left.left.clone().unwrap(); assert_node( &control, NodeType::Control(ControlType::Hibyte), line, ".hibyte", ); assert!(control.left.is_none()); assert!(control.right.is_none()); let args = control.args.clone().unwrap(); assert_eq!(args.len(), 1); assert_node(args.first().unwrap(), NodeType::Literal, line, "$2010"); let right = ind.right.clone().unwrap(); assert_node(&right, NodeType::Value, line, "x"); } } #[test] fn parse_control_in_indirect_y_instructions() { for line in vec![ "lda (#.hibyte($2010)), y", " label: lda ( #.hibyte( $2010 ) ) , y", ] .into_iter() { let mut parser = Parser::default(); assert!(parser.parse(line.as_bytes()).is_ok()); let node = parser.nodes.last().unwrap(); assert_node(node, NodeType::Instruction, line, "lda"); assert!(node.args.is_none()); let ind = node.left.clone().unwrap(); assert_eq!(ind.node_type, NodeType::Indirection); assert!(ind.right.is_none()); assert!(ind.args.is_none()); let left = ind.left.clone().unwrap(); assert_node(&left, NodeType::Literal, line, "#.hibyte"); assert!(left.right.is_none()); assert!(left.args.is_none()); let control = left.left.clone().unwrap(); assert_node( &control, NodeType::Control(ControlType::Hibyte), line, ".hibyte", ); assert!(control.left.is_none()); assert!(control.right.is_none()); let args = control.args.clone().unwrap(); assert_eq!(args.len(), 1); assert_node(args.first().unwrap(), NodeType::Literal, line, "$2010"); let right = node.right.clone().unwrap(); assert_node(&right, NodeType::Value, line, "y"); } } #[test] fn parse_control_in_assignments() { for line in vec![ "lala = #.hibyte($2010)", " lala = #.hibyte($2010)", "label: lala = #.hibyte($2010) ; comment", ] .into_iter() { let mut parser = Parser::default(); assert!(parser.parse(line.as_bytes()).is_ok()); let node = parser.nodes.last().unwrap(); assert_node(node, NodeType::Assignment, line, "lala"); assert!(node.right.is_none()); assert!(node.args.is_none()); let left = node.left.clone().unwrap(); assert_node(&left, NodeType::Literal, line, "#.hibyte"); assert!(left.right.is_none()); assert!(left.args.is_none()); let control = left.left.clone().unwrap(); assert_node( &control, NodeType::Control(ControlType::Hibyte), line, ".hibyte", ); assert!(control.left.is_none()); assert!(control.right.is_none()); let args = control.args.clone().unwrap(); assert_eq!(args.len(), 1); assert_node(args.first().unwrap(), NodeType::Literal, line, "$2010"); } } #[test] fn parse_unknown_control() { let mut parser = Parser::default(); let mut err = parser.parse(".".as_bytes()).unwrap_err(); assert_eq!(err.first().unwrap().message, "unknown function '.'"); parser = Parser::default(); err = parser.parse(".whatever".as_bytes()).unwrap_err(); assert_eq!(err.first().unwrap().message, "unknown function '.whatever'"); } // Macro calls. #[test] fn parse_macro_call_no_args_variable_lookalike() { for line in vec![ "MACRO_CALL", " MACRO_CALL ", " label: MACRO_CALL ; comment", ] .into_iter() { let mut parser = Parser::default(); assert!(parser.parse(line.as_bytes()).is_ok()); let node = parser.nodes.last().unwrap(); assert_node(node, NodeType::Value, line, "MACRO_CALL"); assert!(node.left.is_none()); assert!(node.right.is_none()); assert!(node.args.is_none()); } } #[test] fn parse_macro_call_no_args() { for line in vec![ "MACRO_CALL()", " MACRO_CALL() ", " MACRO_CALL () ", " MACRO_CALL ( ) ", " label: MACRO_CALL () ; comment", ] .into_iter() { let mut parser = Parser::default(); assert!(parser.parse(line.as_bytes()).is_ok()); let node = parser.nodes.last().unwrap(); assert_node(node, NodeType::Call, line, "MACRO_CALL"); assert!(node.left.is_none()); assert!(node.right.is_none()); assert!(node.args.is_none()); } } #[test] fn parse_macro_call_one_arg() { for line in vec![ "MACRO_CALL(arg1)", "MACRO_CALL arg1 ", " MACRO_CALL (arg1)", " label: MACRO_CALL( arg1 ) ; Comment", ] .into_iter() { let mut parser = Parser::default(); assert!(parser.parse(line.as_bytes()).is_ok()); let node = parser.nodes.last().unwrap(); assert_node(node, NodeType::Call, line, "MACRO_CALL"); assert!(node.left.is_none()); assert!(node.right.is_none()); let args = node.args.clone().unwrap(); assert_eq!(args.len(), 1); assert_node(args.first().unwrap(), NodeType::Value, line, "arg1"); } } #[test] fn parse_macro_call_multiple_args() { for line in vec![ "MACRO_CALL(arg1, arg2)", "MACRO_CALL arg1, arg2 ", " MACRO_CALL (arg1,arg2)", " label: MACRO_CALL( arg1 , arg2 ) ; Comment", ] .into_iter() { let mut parser = Parser::default(); assert!(parser.parse(line.as_bytes()).is_ok()); let node = parser.nodes.last().unwrap(); assert_node(node, NodeType::Call, line, "MACRO_CALL"); assert!(node.left.is_none()); assert!(node.right.is_none()); let args = node.args.clone().unwrap(); assert_eq!(args.len(), 2); assert_node(args.first().unwrap(), NodeType::Value, line, "arg1"); assert_node(args.last().unwrap(), NodeType::Value, line, "arg2"); } } }