diff options
| author | Miquel Sabaté Solà <mikisabate@gmail.com> | 2024-09-26 15:53:57 +0200 |
|---|---|---|
| committer | Miquel Sabaté Solà <mikisabate@gmail.com> | 2024-12-12 07:41:30 +0100 |
| commit | 184c39579227c0add80a61d489c242120001d6b6 (patch) | |
| tree | 48737e0e00b5fb655090a1c9a4fc216dcf278cc0 /lib/xixanta/src/parser.rs | |
| parent | 54e1239fcccb2383e5caa88844a7229b5441c31e (diff) | |
| download | tools.nes-184c39579227.tar.gz tools.nes-184c39579227.zip | |
Re-work the parser from scratch
The initial implementation was incredibly naive on how complex an
assembler can actually be. Because of this, it never bothered to define
and produce a proper AST, and hence it was overall extremely fragile on
(not so) corner cases.
This commit re-writes everything from scratch for the parser, and it
should really be the last fundamental change to the parser other than
minor additions/features here and there.
Signed-off-by: Miquel Sabaté Solà <mikisabate@gmail.com>
Diffstat (limited to 'lib/xixanta/src/parser.rs')
| -rw-r--r-- | lib/xixanta/src/parser.rs | 1496 |
1 files changed, 1496 insertions, 0 deletions
diff --git a/lib/xixanta/src/parser.rs b/lib/xixanta/src/parser.rs new file mode 100644 index 0000000..d11e98c --- /dev/null +++ b/lib/xixanta/src/parser.rs @@ -0,0 +1,1496 @@ +use crate::errors::ParseError; +use crate::instruction::PString; +use crate::opcodes::{CONTROL_FUNCTIONS, INSTRUCTIONS}; +use std::io::{self, BufRead, Read}; +use std::ops::Range; + +// TODO: add cargo-fuzz + +type Result<T> = std::result::Result<T, ParseError>; + +#[derive(Debug, Clone, PartialEq)] +pub enum NodeType { + Value, + Instruction, + Indirection, + Assignment, + Control, + Literal, + Identifier, + Label, + Call, + Empty, +} + +#[derive(Debug, Clone, PartialEq)] +pub struct PNode { + pub node_type: NodeType, + pub value: PString, + pub left: Option<Box<PNode>>, + pub right: Option<Box<PNode>>, + pub args: Option<Vec<Box<PNode>>>, +} + +impl PNode { + pub fn empty() -> PNode { + Self { + node_type: NodeType::Empty, + value: PString::new(), + left: None, + right: None, + args: None, + } + } +} + +#[derive(Debug, Clone, PartialEq)] +pub struct Parser { + line: usize, + column: usize, + offset: usize, + pub nodes: Vec<Box<PNode>>, + pub errors: Vec<ParseError>, +} + +impl Parser { + pub fn new() -> Self { + Self { + line: 0, + column: 0, + offset: 0, + nodes: Vec::new(), + errors: Vec::new(), + } + } + + pub fn reset(&mut self) { + self.line = 0; + self.column = 0; + self.offset = 0; + self.nodes = Vec::new(); + self.errors = Vec::new(); + } + + pub fn parse(&mut self, reader: impl Read) -> Result<()> { + for line in io::BufReader::new(reader).lines() { + if let Err(err) = self.parse_line(line?.as_str()) { + self.errors.push(err); + } + self.line += 1; + } + + println!("NODES: {:#?}", self.nodes); + + match self.errors.last() { + Some(err) => Err(err.clone()), + None => Ok(()), + } + } + + fn parse_line(&mut self, line: &str) -> Result<()> { + self.column = 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(|c: char| c == ';') { + 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(Box::new(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) + } + + fn parse_identifier(&mut self, line: &str) -> Result<(PString, NodeType)> { + let start = self.column; + let base_offset = self.offset; + + // 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. + for c in line.get(self.offset..).unwrap_or("").chars() { + if c.is_whitespace() || c == ':' || c == '(' || c == ')' || c == '=' { + let val = String::from(line.get(base_offset..self.offset).unwrap_or("").trim()); + let nt = if c == ':' { + NodeType::Label + } else { + NodeType::Value + }; + + // TODO + // self.next(); + let end = if c == ':' { + self.next(); + self.column - 1 + } else { + self.column + }; + + return Ok(( + PString { + value: val, + line: self.line, + range: Range { + start, + end, // TODO + // end: self.column - 1, + }, + }, + nt, + )); + } else if !c.is_alphanumeric() && c != '_' { + // TODO: on the contrary, if alphanumeric or _, just follow + // through. Otherwise always break. TODO NOT REALLY + // return Err(self.parser_error("bad character for possible identifier")); + } + + self.next(); + } + + // The line is merely the identifier (e.g. instruction with implied + // addressing). + let id = String::from(line.get(base_offset..).unwrap_or("").trim()); + Ok(( + PString { + value: id, + line: self.line, + range: Range { + start, + end: self.column, + }, + }, + NodeType::Value, + )) + } + + fn parse_statement(&mut self, line: &str, id: PString) -> Result<()> { + // 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(()) + } + } + } + } + + fn parse_instruction(&mut self, line: &str, id: PString) -> Result<()> { + let mut paren = 0; + + self.skip_whitespace(line); + + if line.contains("=") { + return Err(self.parser_error( + format!("cannot used reserved name for the mnemonic '{}'", id.value).as_str(), + )); + } + + let indirect = line.chars().nth(self.offset).unwrap_or(',') == '('; + let l = if indirect { + self.next(); + self.skip_whitespace(line); + paren = self.find_matching_paren(line, self.offset)?; + line.get(self.offset..paren).unwrap_or_default() + } else { + line.get(self.offset..).unwrap_or_default() + }; + + self.offset = 0; + let mut left = if l.is_empty() { + None + } else { + Some(self.parse_left_arm(l)?) + }; + + self.skip_whitespace(l); + + // The parsing of the left arm should have advanced the offset right + // into the right arm. If there is nothing there, then we have no right + // arm. Otherwise we have to parse the expression. + // TODO + 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(self.parse_expression(right_str)?) + }; + + if indirect { + if left.is_none() { + return Err(self.parser_error("empty indirect addressing")); + } + + left = Some(Box::new(PNode { + node_type: NodeType::Indirection, + value: PString::new(), + left, + right: right.clone(), + args: None, + })); + + 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")); + } + + right = if right_str.is_empty() || right_str == ")" { + None + } else { + self.offset = 0; + + // TODO: ") ," + self.next(); + self.skip_whitespace(right_str); + + // TODO: ", " + self.next(); + self.skip_whitespace(right_str); + + Some(self.parse_expression(right_str)?) + }; + } + + self.nodes.push(Box::new(PNode { + node_type: NodeType::Instruction, + value: id, + left, + right, + args: None, + })); + + Ok(()) + } + + fn parse_assignment(&mut self, line: &str, id: PString) -> Result<()> { + if let Err(msg) = id.is_valid_identifier() { + 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(' ') != '=' { + 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 = Some(self.parse_expression(rest)?); + + self.nodes.push(Box::new(PNode { + node_type: NodeType::Assignment, + value: id.clone(), + left, + right: None, + args: None, + })); + + Ok(()) + } + + fn parse_arguments(&mut self, line: &str) -> Result<Vec<Box<PNode>>> { + // 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 { + self.next(); + self.skip_whitespace(line); + self.find_matching_paren(line, self.offset)? + } else { + line.len() + }; + + let mut args = Vec::new(); + + loop { + // TODO: trimmed_str out? + let trimmed_str = line.get(..end).unwrap_or_default().trim_end(); + println!("TRIMME: {:#?}", trimmed_str.get(self.offset..)); + + let (arg_end, comma) = self.find_left_end(trimmed_str, false)?; + 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(); + // .trim_end(); + println!( + "ARG_END: {:#?} -- ARG: {:#?} - DIFF: {}", + arg_end, arg, diff + ); + if arg.is_empty() { + break; + } + // if !comma { + // s = arg.to_owned() + " "; + // arg = s.as_str(); + // } + + self.offset = 0; + args.push(self.parse_expression(arg)?); + + self.offset = arg_end; + self.column += diff; + println!("{:#?}", line.get(self.offset..end)); + self.skip_whitespace(line); // TODO + if comma { + self.next(); + self.skip_whitespace(line); + } + } + + println!("ARGS: {:#?}", args); + + Ok(args) + } + + fn parse_left_arm(&mut self, line: &str) -> Result<Box<PNode>> { + 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. + // TODO + let (end, comma) = self.find_left_end(line, false)?; + + // 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 to the end of the line that is shared with the caller. + // let diff_column = (end - start) - (self.column - start_column); + self.offset = end; + self.column = start_column + end; + if comma { + self.next(); + } + expr + } + + // TODO: revisit inside_paren + fn find_left_end(&self, line: &str, inside_paren: bool) -> Result<(usize, bool)> { + let mut idx = self.offset; + let mut parens = if inside_paren { 1 } else { 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)) + } + + fn find_matching_paren(&self, line: &str, init: usize) -> Result<usize> { + 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; + } + + if parens == 0 { + return Ok(idx); + } else if parens < 0 { + return Err(self.parser_error("too many closing parenthesis")); + } + + 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<Box<PNode>> { + 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<Box<PNode>> { + // 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_valid() { + 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(Box::new(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(Box::new(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<Box<PNode>> { + let mut left = None; + let required; + + // 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()) { + 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(Box::new(PNode { + node_type: NodeType::Control, + 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<Box<PNode>> { + // 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.range.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(Box::new(PNode { + node_type: NodeType::Literal, + value: id, + left: Some(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, + parse: true, + } + } + + // 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::*; + + 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: &Box<PNode>, nt: NodeType, line: &str, value: &str) { + assert_eq!(node.node_type, nt); + assert_eq!( + node.value.value.as_str(), + line.get(node.value.range.clone()).unwrap() + ); + assert_eq!(node.value.value.as_str(), value); + } + + // Empty + + #[test] + fn empty_line() { + let mut parser = Parser::new(); + assert!(!parser.parse("".as_bytes()).is_err()); + assert_eq!(parser.nodes.len(), 0); + } + + #[test] + fn spaced_line() { + let mut parser = Parser::new(); + assert!(!parser.parse(" ".as_bytes()).is_err()); + 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::new(); + assert!(!parser.parse(line.as_bytes()).is_err()); + assert_eq!(parser.nodes.len(), 0); + } + } + + // Labels + + #[test] + fn anonymous_label() { + let mut parser = Parser::new(); + 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.range.start, 0); + assert_eq!(parser.nodes.first().unwrap().value.range.end, 0); + + parser = Parser::new(); + 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.range.start, 2); + assert_eq!(parser.nodes.first().unwrap().value.range.end, 2); + } + + #[test] + fn named_label() { + let mut parser = Parser::new(); + 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.range.start, 0); + assert_eq!(parser.nodes.first().unwrap().value.range.end, 5); + + parser = Parser::new(); + 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.range.start, 2); + assert_eq!(parser.nodes.first().unwrap().value.range.end, 7); + } + + #[test] + fn label_with_instruction() { + let line = "label: dex"; + + let mut parser = Parser::new(); + 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.range.start, 0); + assert_eq!(parser.nodes.first().unwrap().value.range.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::new(); + 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.range).unwrap(), "20"); + } + } + + #[test] + fn parse_compound_literal() { + let line = "#$20"; + let mut parser = Parser::new(); + 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.range).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.range).unwrap(), "20"); + } + + #[test] + fn parse_variable_in_literal() { + let line = "#Variable"; + let mut parser = Parser::new(); + 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.range).unwrap(), "Variable"); + } + + // Regular instructions. + + #[test] + fn instruction_with_implied() { + for line in vec![ + "dex", + " dex", + " dex ", + " dex ; Comment", + " label: dex", + ] + .into_iter() + { + let mut parser = Parser::new(); + 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::new(); + 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::new(); + 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::new(); + 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::new(); + 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::new(); + 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::new(); + 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::new(); + 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::new(); + + let err = parser.parse("lda (Variable, x), y".as_bytes()); + assert_eq!(err.unwrap_err().message, "bad indirect addressing"); + } + + #[test] + fn indirect_addressing_y() { + for line in vec!["lda ($20), y"].into_iter() { + let mut parser = Parser::new(); + 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::new(); + 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::new(); + 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"); + } + + // Assignments + + #[test] + fn bad_assignments() { + let mut parser = Parser::new(); + + let mut err = parser.parse("abc = $10".as_bytes()); + assert_eq!( + err.unwrap_err().message, + "cannot use names which are valid hexadecimal values such as 'abc'" + ); + + parser = Parser::new(); + err = parser.parse("var =".as_bytes()); + assert_eq!(err.unwrap_err().message, "incomplete assignment"); + + parser = Parser::new(); + err = parser.parse("var = ".as_bytes()); + assert_eq!(err.unwrap_err().message, "incomplete assignment"); + + parser = Parser::new(); + err = parser.parse("var = ; Comment".as_bytes()); + assert_eq!(err.unwrap_err().message, "incomplete assignment"); + } + + // Control statements. + + #[test] + fn parse_control_no_args() { + for line in vec![".end", " .end", " label: .end ; Comment"].into_iter() { + let mut parser = Parser::new(); + assert!(parser.parse(line.as_bytes()).is_ok()); + + let node = parser.nodes.last().unwrap(); + assert_node(node, NodeType::Control, line, ".end"); + 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::new(); + assert!(parser.parse(line.as_bytes()).is_ok()); + + let node = parser.nodes.last().unwrap(); + assert_node(node, NodeType::Control, 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::new(); + assert!(parser.parse(line.as_bytes()).is_ok()); + + let node = parser.nodes.last().unwrap(); + assert_node(node, NodeType::Control, 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::new(); + assert!(parser.parse(line.as_bytes()).is_ok()); + + let node = parser.nodes.last().unwrap(); + assert_node(node, NodeType::Control, 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::new(); + assert!(parser.parse(line.as_bytes()).is_ok()); + + let node = parser.nodes.last().unwrap(); + assert_node(node, NodeType::Control, 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::new(); + assert!(parser.parse(line.as_bytes()).is_ok()); + + let node = parser.nodes.last().unwrap(); + assert_node(node, NodeType::Control, 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::new(); + assert_eq!( + parser.parse(line.as_bytes()).unwrap_err().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::new(); + 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, 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::new(); + 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, 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::new(); + 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, 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::new(); + 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, 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::new(); + assert_eq!( + parser.parse(".".as_bytes()).unwrap_err().message, + "unknown function '.'" + ); + + parser = Parser::new(); + assert_eq!( + parser.parse(".whatever".as_bytes()).unwrap_err().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::new(); + 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::new(); + 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::new(); + 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::new(); + 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"); + } + } +} |
