diff options
| author | Miquel Sabaté Solà <mikisabate@gmail.com> | 2025-01-21 21:05:06 +0100 |
|---|---|---|
| committer | Miquel Sabaté Solà <mikisabate@gmail.com> | 2025-01-21 21:05:06 +0100 |
| commit | 0c8bb1284de221eae92b08bcc16db7002bf9f3ba (patch) | |
| tree | 9903c1d22f1beebe14e47d71ef399b9facc31ac6 /lib/xixanta | |
| parent | f00ead262076a1cf0429d6fb0de8671f6376af57 (diff) | |
| download | tools.nes-0c8bb1284de221eae92b08bcc16db7002bf9f3ba.tar.gz tools.nes-0c8bb1284de221eae92b08bcc16db7002bf9f3ba.zip | |
Introduce looking ahead when parsing literals
There were certain situations in which syntax ambiguity could arise. For
example, the parser as it stood could treat a valid expression such as
'lda #$80 >> 2' in an unexpected 'lda #$(80 >> 2)'.
This is of course bad, and it came from the fact that literals don't
have enclosing characters, and white spaces are not enough to provide
disambiguation in some cases. Because of this fact, the parser now has a
"look ahead" capability similar to many other parsers, and it's applied
for now only to literals. This looking ahead actually honors
parenthesis, so these can be added if the programmer wants to
explicitely disambiguate an expression.
This involved quite the heavy lifting, and some functions like
'parse_expression_with_identifier' had to be removed with the rewrite.
This had the side effect of having (hopefully) more sane functions all
around, and the parser also has a better capability to differentiate
between regular Values and Calls.
Signed-off-by: Miquel Sabaté Solà <mikisabate@gmail.com>
Diffstat (limited to 'lib/xixanta')
| -rw-r--r-- | lib/xixanta/src/assembler.rs | 2 | ||||
| -rw-r--r-- | lib/xixanta/src/parser.rs | 487 |
2 files changed, 296 insertions, 193 deletions
diff --git a/lib/xixanta/src/assembler.rs b/lib/xixanta/src/assembler.rs index 5e94070..dfaff19 100644 --- a/lib/xixanta/src/assembler.rs +++ b/lib/xixanta/src/assembler.rs @@ -3943,7 +3943,7 @@ lda #Variable .segment "THREE" .segment "TWO" - nop' + nop "#, 2, false, diff --git a/lib/xixanta/src/parser.rs b/lib/xixanta/src/parser.rs index dbe5fbe..322bd76 100644 --- a/lib/xixanta/src/parser.rs +++ b/lib/xixanta/src/parser.rs @@ -46,6 +46,21 @@ pub struct Parser { /// The index of the source that explicitely belongs to this session (and /// not subsequent calls done afterwards). Initialized on `parse`. current_source: usize, + + /// A stack of "look ahead" levels. That is, a stack of saved + /// `look_ahead_index` values that functions that deal with having to look + /// ahead (e.g. 'parse_literal') have to push/pop so 'parse_expression' can + /// know when to return early. + look_ahead_levels: Vec<usize>, + + /// The current level of "looking ahead". That is, some expressions like + /// '#$1 >> 2' have to evaluate to '(#$1) >> 2' instead of '#$(1 >> 2)'. + /// This can't be known at first glance because literals, for example, don't + /// have a termination character (i.e. the parser cannot determine where any + /// given literal expression ends). Hence, in this case, the parser has to + /// look ahead to know where the literal ends before dealing with further + /// operators. + look_ahead_index: usize, } impl Parser { @@ -241,16 +256,7 @@ impl Parser { while let Some(c) = chars.next() { // Check for the end of the identifier. For this, it's easier to // simply list what is allowed and negate it. - if !(c.is_ascii_alphanumeric() - || c == '.' - || c == '#' - || c == '$' - || c == '%' - || c == '@' - || c == '_' - || c == '\'' - || c == '"') - { + if !(c.is_ascii_alphanumeric() || 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. @@ -577,11 +583,22 @@ impl Parser { } // Parse statements which are neither an instruction nor an assignment. This - // includes stuff like control statements. + // includes stuff like control statements or macro calls. fn parse_other(&mut self, line: &str, id: PString) -> Result<(), Vec<Error>> { - // The main job of this function is to parse the expression and - // afterwards deal with corner cases. So, let's first just parse this. - let node = self.parse_expression_with_identifier(id, line, 0)?; + // This is either a control statement (i.e. starts with '.') or a macro call. + let node = if id.value.starts_with('.') { + self.parse_control(id, line, 0)? + } else { + let args = self.parse_arguments(line, 0)?; + PNode { + node_type: NodeType::Call, + value: id, + left: None, + right: None, + args: if args.is_empty() { None } else { Some(args) }, + source: self.current_source, + } + }; // If this was an .include statement we have to handle it now as this is // not a regular statement but more like a preprocessor statement which @@ -833,7 +850,7 @@ impl Parser { // 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<Vec<PNode>, Error> { + fn parse_arguments(&mut self, line: &str, level: usize) -> Result<Vec<PNode>, Error> { // Skip any possible whitespace before the optional opening paren. self.skip_whitespace(line); @@ -877,7 +894,7 @@ impl Parser { // 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, 0)?); + args.push(self.parse_expression(arg, level + 1)?); // After the parsing is done for the current argument, move both // `self.offset` and `self.column` right after the end of the @@ -999,22 +1016,38 @@ impl Parser { // Parses the given line by assuming it's an expression under parenthesis. // This function then grabs whatever is inside of these parenthesis and // parses the expression inside of them. - fn extract_parenthesized_expression( - &mut self, - line: &str, - level: usize, - ) -> Result<PNode, Error> { + fn extract_paren_expression(&mut self, line: &str, level: usize) -> Result<PNode, Error> { // Skip '(' character and whitespace characters in between. self.next(); self.skip_whitespace(line); - // Extract what's inside of the enclosing parenthesis. + // Extract the string inside of the enclosing parenthesis. let paren = self.find_matching_paren(line, self.offset)?; let l = line.get(self.offset..paren).unwrap_or_default(); - // And return what you can parse from the inner expression. + // The offset will be reset since we are trimming the string to be + // parsed. That being said, keep the previous value so it can be added + // afterwards. + let prev = self.offset; self.offset = 0; - self.parse_expression(l, level + 1) + + // Parenthesis reset the index of looking ahead, since ambiguities are + // removed by using parenthesis. Hence, store the current index and + // reset it. + let prev_idx = self.look_ahead_index; + self.look_ahead_index = 0; + + // Parse the inner expression and restore the 'look_ahead_index'. As for + // the 'offset', the inner expression has moved it, so add the previous + // value to it so the value matches the line originally given to this + // function. + let node = self.parse_expression(l, level + 1)?; + self.look_ahead_index = prev_idx; + self.offset += prev; + + // Skip ')' character and return the parsed expression. + self.next(); + Ok(node) } // Consumes the given line by assuming is a string in double quotes. @@ -1066,6 +1099,10 @@ impl Parser { line: &str, level: usize, ) -> Result<PNode, Error> { + // Save the symbol before discarding it, since it's going to be used for + // the PString's value. + let symbol = line.chars().nth(0).unwrap(); + // Skip operator and whitespaces. let start = self.column; self.next(); @@ -1079,7 +1116,7 @@ impl Parser { Ok(PNode { node_type, value: PString { - value: String::from(""), + value: String::from(symbol) + &right.value.value, line: self.line, start, end: right.value.end, @@ -1094,20 +1131,105 @@ impl Parser { // 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. + // and the offset has been set accordingly). Moreover, this function is + // expected to be called rather often in a recursive manner. In order to + // avoid stack problems, a `level` of recursivity is passed so we can + // prevent too many nested expressions before exhausting the call stack. + // Returns a new node for the expression at hand. fn parse_expression(&mut self, line: &str, level: usize) -> Result<PNode, Error> { // Avoid stack overflows from too many recursive calls for expressions - // that are too nested. This mainly requires fuzzy testing to get to - // this point, but let's be safe about this anyways. + // that are too nested. I have only seen this recursion level on wild + // inputs while fuzzy testing, so no real human code should reach this + // point. Eitherway, let's be safe. if level >= 16 { return Err(self.parser_error("there are too many nested expressions")); } - // Cases where fetching an "identifier" is really not needed. + // Parse the expression that we have here and now. + let expr = self.parse_expression_at_point(line, level)?; + + // Were we looking ahead? If so simply return so the caller can react to + // it. + let starting_level = *self.look_ahead_levels.last().unwrap_or(&0); + if self.look_ahead_index > starting_level { + return Ok(expr); + } + + // We were not looking ahead at this point. Let's grab the rest of the + // string and check if there is a binary operation at hand. + self.skip_whitespace(line); + let l = line.get(self.offset..).unwrap_or_default().trim_end(); + + let (op, op_size) = self.get_operation_from_line(l)?; + if let Some(node_type) = op { + // The left node of the operator is simply the "identifier" that + // came to us. + let left = Some(Box::new(expr)); + + // Fetch a trimmed version of the string that comes after the + // operator. + self.offset += op_size; + self.column += op_size; + self.skip_whitespace(line); + let right_str = line.get(self.offset..).unwrap_or_default().trim_end(); + + // The right node of the operation will be the parsed expression + // of the string we just got right of the operator. + let prev = self.offset; + self.offset = 0; + let right = self.parse_expression(right_str, level + 1)?; + self.offset += prev; + + return Ok(PNode { + node_type, + value: PString { + value: String::from(""), + line: left.as_ref().unwrap().value.line, + start: left.as_ref().unwrap().value.start, + end: right.value.end, + }, + left, + right: Some(Box::new(right)), + args: None, + source: self.current_source, + }); + } + + // There is no binary operation. Hence, just return the expression as + // expected. + Ok(expr) + } + + // Parse the expression from the very start of the given `line` and by + // assuming that there are no further expressions to be parsed after the + // given one (this is the task for the caller). The `level` of recursivity + // is also passed since it might internally call `parse_expression` again. + fn parse_expression_at_point(&mut self, line: &str, level: usize) -> Result<PNode, Error> { let first = line.chars().next().unwrap_or_default(); + if first == '(' { - return self.extract_parenthesized_expression(line, level); + self.extract_paren_expression(line, level) + } else if first == '.' { + let (id, _) = self.parse_identifier(line)?; + self.parse_control(id, line, level) + } else if first == '\'' { + self.parse_char(line) + } else if first == '$' || first == '#' || first == '%' { + // Literal symbols come with a single character, or with two only on + // '#$' or '#%'. Other variations are illegal and should be avoided + // to prevent crashes. + if let Some(next) = line.chars().nth(1) { + if next == '#' || (first != '#' && (next == '$' || next == '%')) { + return Err(Error { + line: self.line, + global: false, + source: self.sources[self.current_source].clone(), + message: "bad literal syntax".to_string(), + }); + } + } + + self.parse_literal(line, first, level) } else if let Some(node_type) = self.get_unary_from_line(line) { // Only treat this as a unary operator if the next character is not // another unary operator (e.g. disambiguate between '<<' and '<'). @@ -1115,19 +1237,33 @@ impl Parser { .get_unary_from_line(line.get(1..).unwrap_or("")) .is_none() { - return self.parse_unary_operation(node_type, line, level); + self.parse_unary_operation(node_type, line, level) + } else { + self.parse_value(line) } + } else { + self.parse_value(line) } + } - // Now that we have changed specific cases where detecting an - // "identifier" is not really relevant, let's fetch the "identifier" and - // parse the expression with that into consideration. + // Like `parse_identifier` but it returns an error if a label was found. + fn parse_value(&mut self, line: &str) -> Result<PNode, Error> { let (id, nt) = self.parse_identifier(line)?; + if id.is_empty() { + return Err(self.parser_error("invalid identifier")); + } if nt == NodeType::Label { Err(self.parser_error("not expecting a label defined here")) } else { - self.parse_expression_with_identifier(id, line, level) + Ok(PNode { + node_type: NodeType::Value, + value: id, + left: None, + right: None, + args: None, + source: self.current_source, + }) } } @@ -1190,124 +1326,6 @@ impl Parser { } } - // 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, - level: usize, - ) -> Result<PNode, Error> { - // 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")); - } - - // Cache the first character on the next part as it's used in lots of - // places. - let start = line.chars().nth(0).unwrap_or(' '); - - if id.value.starts_with(".") { - self.parse_control(id, line) - } else if start == '$' || start == '#' || start == '%' { - // Literal symbols come with a single character, or with two only on - // '#$' or '#%'. Other variations are illegal and should be avoided - // to prevent crashes. - if let Some(next) = line.chars().nth(1) { - if next == '#' || (start != '#' && (next == '$' || next == '%')) { - return Err(Error { - line: id.line, - global: false, - source: self.sources[self.current_source].clone(), - message: "bad literal syntax".to_string(), - }); - } - } - - self.parse_literal(id, line, level) - } else if start == '\'' { - self.parse_char(id) - } else { - // Skip any whitespace after our identifier. - self.skip_whitespace(line); - let l = line.get(self.offset..).unwrap_or_default().trim_end(); - - // The line might start with a binary operator. This would mean that - // the "id" is actually just the left node of a binary operation and - // that we just need to parse the right arm. - let (op, op_size) = self.get_operation_from_line(l)?; - if let Some(node_type) = op { - // The left node of the operator is simply the "identifier" that - // came to us. - let left = Some(Box::new(PNode { - node_type: NodeType::Value, - value: id.clone(), - left: None, - right: None, - args: None, - source: self.current_source, - })); - - // Fetch a trimmed version of the string that comes after the - // operator. - self.offset += op_size; - self.column += op_size; - self.skip_whitespace(line); - let right_str = line.get(self.offset..).unwrap_or_default().trim_end(); - - // The right node of the operation will be the parsed expression - // of the string we just got right of the operator. - self.offset = 0; - let right = self.parse_expression(right_str, level)?; - - return Ok(PNode { - node_type, - value: PString { - value: String::from(""), - line: id.line, - start: id.start, - end: right.value.end, - }, - left, - right: Some(Box::new(right)), - args: None, - source: self.current_source, - }); - } - - // Ok, so the line did not indicate any sort of operation. That - // being said, if the rest of the line still contains stuff, it - // might as well be a macro call. Try to process it as such. - if !l.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) }, - source: self.current_source, - }); - } - - // 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, - source: self.current_source, - }) - } - } - // Generate a unique identifier with the given prefix. fn unique_identifier(&self, prefix: String) -> String { prefix + &String::from("-") + &Alphanumeric.sample_string(&mut rand::thread_rng(), 16) @@ -1315,7 +1333,7 @@ impl Parser { // 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<PNode, Error> { + fn parse_control(&mut self, id: PString, line: &str, level: usize) -> Result<PNode, Error> { let mut left = None; // Ensure that this is a function that we know of. In the past this was @@ -1360,7 +1378,7 @@ impl Parser { self.skip_whitespace(line); vec![self.parse_quoted_string(line)?] } else { - self.parse_arguments(line)? + self.parse_arguments(line, level)? }; if let Some(args_required) = control.required_args { if args.len() < args_required.0 || args.len() > args_required.1 { @@ -1381,25 +1399,19 @@ impl Parser { } // 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, level: usize) -> Result<PNode, Error> { - // 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(); - + // considering the given `line` which starts with the given `symbol`. The + // recursivity `level` is also provided as it will call again + // `parse_expression`. + fn parse_literal(&mut self, line: &str, symbol: char, level: usize) -> Result<PNode, Error> { // Enforce that literal symbols and their values are not separated by // random white space characters. Other assemblers (e.g. ca65) also take // this stance, and through fuzzy testing I realized that not doing this // could result in general bad behavior. - let inner = line.get(self.offset..).unwrap_or(""); + let inner = line.get(1..).unwrap_or(""); if let Some(c) = inner.chars().nth(0) { if c.is_whitespace() { return Err(Error { - line: id.line, + line: self.line, global: false, source: self.sources[self.current_source].clone(), message: "numeric literals cannot have white spaces".to_string(), @@ -1407,13 +1419,33 @@ impl Parser { } } - // Just fetch the inner expression and return the literal node. + // Preserve the initial column value and advance it to skip the 'symbol' + // character. Then we need to reset the offset as usual since the parsed + // expression has been trimmed. + let start = self.column; + self.column += 1; self.offset = 0; + + // Parse the inner expression while also pushing/popping the look ahead + // status. This is needed as there are no closing characters for a given + // literal, so to disambiguate we must look ahead. + self.look_ahead_levels.push(self.look_ahead_index); + self.look_ahead_index += 1; let left = self.parse_expression(inner, level + 1)?; + self.look_ahead_index -= 1; + self.look_ahead_levels.pop(); + + // Add the 'symbol' to the final offset. + self.offset += 1; Ok(PNode { node_type: NodeType::Literal, - value: id, + value: PString { + value: String::from(symbol) + &left.value.value, + line: self.line, + start, + end: left.value.end, + }, left: Some(Box::new(left)), right: None, args: None, @@ -1424,15 +1456,17 @@ impl Parser { // Returns a NodeType::Literal node with the given `id` parsed as a // character literal. This literal will have on the left node a value which // is the character transformed into a decimal value. - fn parse_char(&mut self, id: PString) -> Result<PNode, Error> { - if id.value.len() != 3 { + fn parse_char(&mut self, line: &str) -> Result<PNode, Error> { + if line.len() != 3 { return Err(self.parser_error("bad character literal")); } - let mut chars = id.value.chars(); + // Grab the actual character and the delimiter. + let mut chars = line.chars(); let del = chars.next().unwrap(); let ch = chars.next().unwrap(); + // Sanity checks. if !ch.is_ascii_alphanumeric() { return Err(self.parser_error( "only alphanumeric ASCII characters are allowed on character literals", @@ -1442,16 +1476,25 @@ impl Parser { return Err(self.parser_error("bad character literal")); } + // Advance the column passed ahead of this literal (note the the PNode + // below will have to take this into account with the start/end values). + self.column += 3; + Ok(PNode { node_type: NodeType::Literal, - value: id.clone(), + value: PString { + value: format!("'{}'", ch), + line: self.line, + start: self.column - 3, + end: self.column, + }, left: Some(Box::new(PNode { node_type: NodeType::Value, value: PString { value: (ch as u8).to_string(), - line: id.line, - start: id.start + 1, - end: id.end - 1, + line: self.line, + start: self.column - 2, + end: self.column - 1, }, left: None, right: None, @@ -1622,11 +1665,20 @@ mod tests { #[test] fn parse_pound_literal() { - for line in vec!["#20", " #20 ", " #20 ; Comment", " label: #20"].into_iter() { + for line in vec![ + "lda #20", + " lda #20 ", + " lda #20 ; Comment", + " label: lda #20", + ] + .into_iter() + { let mut parser = Parser::default(); assert!(parser.parse(line.as_bytes(), SourceInfo::default()).is_ok()); - let node = parser.nodes.last().unwrap().last().unwrap(); + let nodes = parser.nodes(); + + let node = nodes.last().unwrap().left.as_ref().unwrap(); assert_eq!(node.node_type, NodeType::Literal); assert!(node.right.is_none()); assert!(node.args.is_none()); @@ -1640,11 +1692,13 @@ mod tests { #[test] fn parse_compound_literal() { - let line = "#$20"; + let line = "lda #$20"; let mut parser = Parser::default(); assert!(parser.parse(line.as_bytes(), SourceInfo::default()).is_ok()); - let node = parser.nodes.last().unwrap().last().unwrap(); + let nodes = parser.nodes(); + + let node = nodes.last().unwrap().left.as_ref().unwrap(); assert_eq!(node.node_type, NodeType::Literal); assert!(node.right.is_none()); assert!(node.args.is_none()); @@ -1666,11 +1720,13 @@ mod tests { #[test] fn parse_variable_in_literal() { - let line = "#Variable"; + let line = "lda #Variable"; let mut parser = Parser::default(); assert!(parser.parse(line.as_bytes(), SourceInfo::default()).is_ok()); - let node = parser.nodes.last().unwrap().last().unwrap(); + let nodes = parser.nodes(); + + let node = nodes.last().unwrap().left.as_ref().unwrap(); assert_eq!(node.node_type, NodeType::Literal); assert!(node.right.is_none()); assert!(node.args.is_none()); @@ -1711,7 +1767,7 @@ mod tests { #[test] fn parse_bad_literals() { - for line in vec!["#", "#%", "$"].into_iter() { + for line in vec!["lda #", "lda #%", "lda $"].into_iter() { let mut parser = Parser::default(); let err = parser .parse(line.as_bytes(), SourceInfo::default()) @@ -1720,7 +1776,7 @@ mod tests { assert_eq!(err.first().unwrap().message, "invalid identifier"); } - for line in vec!["$ 2", "#% 2", "# 2"].into_iter() { + for line in vec!["lda $ 2", "lda #% 2", "lda # 2"].into_iter() { let mut parser = Parser::default(); let err = parser .parse(line.as_bytes(), SourceInfo::default()) @@ -1732,7 +1788,16 @@ mod tests { ); } - for line in vec!["##2", "#$$2", "$$2", "#$#$2", "#$#2", "###"].into_iter() { + for line in vec![ + "lda ##2", + "lda #$$2", + "lda $$2", + "lda #$#$2", + "lda #$#2", + "lda ###", + ] + .into_iter() + { let mut parser = Parser::default(); let err = parser .parse(line.as_bytes(), SourceInfo::default()) @@ -2162,7 +2227,7 @@ mod tests { assert!(node.right.is_none()); let literal = &node.left.clone().unwrap(); - assert_node(literal, NodeType::Literal, line, "#"); + assert_eq!(literal.node_type, NodeType::Literal); let op = &literal.left.clone().unwrap(); assert_eq!(op.node_type, NodeType::Operation(OperationType::Mul)); @@ -2176,6 +2241,44 @@ mod tests { } #[test] + fn nested_expression_test() { + let line = "lda #$80 >> ((BCD_BITS - 1) & 3)"; + let mut parser = Parser::default(); + assert!(parser.parse(line.as_bytes(), SourceInfo::default()).is_ok()); + + let instr = parser.nodes.last().unwrap().last().unwrap(); + assert_node(instr, NodeType::Instruction, line, "lda"); + assert!(instr.args.is_none()); + assert!(instr.right.is_none()); + + // Shift object + let left = instr.left.as_ref().unwrap(); + assert_eq!(left.node_type, NodeType::Operation(OperationType::Rshift)); + + // Left arm of the shift. + assert_node(left.left.as_ref().unwrap(), NodeType::Literal, line, "#$80"); + + // Right arm of the shift: bitwise and. + let right = left.right.as_ref().unwrap(); + assert_eq!(right.node_type, NodeType::Operation(OperationType::And)); + + // Left arm of the bitwise and. + let left_and = right.left.as_ref().unwrap(); + assert_eq!(left_and.node_type, NodeType::Operation(OperationType::Sub)); + assert_node( + left_and.left.as_ref().unwrap(), + NodeType::Value, + line, + "BCD_BITS", + ); + assert_node(left_and.right.as_ref().unwrap(), NodeType::Value, line, "1"); + + // Right arm of the bitwise and. + let right_and = right.right.as_ref().unwrap(); + assert_node(right_and, NodeType::Value, line, "3"); + } + + #[test] fn unary_operator_test() { let line = "ldx #<NUM_SPRITES"; let mut parser = Parser::default(); @@ -2187,7 +2290,7 @@ mod tests { assert!(node.right.is_none()); let literal = &node.left.clone().unwrap(); - assert_node(literal, NodeType::Literal, line, "#"); + assert_node(literal, NodeType::Literal, line, "#<NUM_SPRITES"); let op = &literal.left.clone().unwrap(); assert_eq!(op.node_type, NodeType::Operation(OperationType::LoByte)); @@ -2450,9 +2553,9 @@ mod tests { #[test] fn parse_control_wrong_close() { let code = r#".scope Scope -.macro Macro -.endscope -.endmacro"#; + .macro Macro + .endscope + .endmacro"#; let mut parser = Parser::default(); let err = parser .parse(code.as_bytes(), SourceInfo::default()) @@ -2784,7 +2887,7 @@ inc $20 assert!(parser.parse(line.as_bytes(), SourceInfo::default()).is_ok()); let node = parser.nodes.last().unwrap().last().unwrap(); - assert_node(node, NodeType::Value, line, "MACRO_CALL"); + assert_node(node, NodeType::Call, line, "MACRO_CALL"); assert!(node.left.is_none()); assert!(node.right.is_none()); assert!(node.args.is_none()); |
