diff options
| author | Miquel Sabaté Solà <mikisabate@gmail.com> | 2025-08-17 07:31:44 +0200 |
|---|---|---|
| committer | Miquel Sabaté Solà <mikisabate@gmail.com> | 2025-08-17 07:31:44 +0200 |
| commit | 6993a164dd57c57be9f79190824b774b162272eb (patch) | |
| tree | 05152aca5e1cc9b166c9f472cd041fee47836ab4 /lib | |
| parent | c6aaea6079f295c721b641f82b5f0f2a520490e3 (diff) | |
| download | tools.nes-6993a164dd57c57be9f79190824b774b162272eb.tar.gz tools.nes-6993a164dd57c57be9f79190824b774b162272eb.zip | |
parser: Fix disambiguation paren on first argument
Arguments can be put inside of enclosing parenthesis, but the parser was
assuming that if an opening paren was found when parsing the first
argument on an argument list, then that was all it was needed to be
parsed. This though conflicts with situations like:
.byte ($01 << 2) | ($01 << 1)
In this case, the parser would have ignored everything past the first
closing paren.
This commit provides a fix in which if an operation is found past the
first enclosing parenthesis, then this assumption is discarded in favor
of a parenthesis being used for disambiguating on an
arithmetical/logical expression.
Signed-off-by: Miquel Sabaté Solà <mikisabate@gmail.com>
Diffstat (limited to 'lib')
| -rw-r--r-- | lib/xixanta/src/parser.rs | 102 |
1 files changed, 99 insertions, 3 deletions
diff --git a/lib/xixanta/src/parser.rs b/lib/xixanta/src/parser.rs index 8672923..d6d8d6f 100644 --- a/lib/xixanta/src/parser.rs +++ b/lib/xixanta/src/parser.rs @@ -879,9 +879,36 @@ impl Parser { 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)? + // If there is a parenthesis it might be either that the command + // enclosed its arguments into parenthesis, or that the first + // argument is using parenthesis to disambiguate with an + // arithmetic/logical operation. + let e = self.find_matching_paren(line, self.offset)?; + match line.get(e + 1..line.len()) { + Some(rest) => { + let trimmed = rest.trim_start(); + if trimmed.is_empty() { + // The rest of the line was just empty: return the + // matching closing paren as there was no need to + // disambiguate. + e + } else if self.get_operation_from_line(trimmed).is_ok() { + // There is an arithmetic/logical operation in sight! In + // that case we assume it was a disambiguation scenario + // and so we rollback from the previous `next()` call + // and return the end of the line. + self.prev(); + line.len() + } else { + // No operation in sight, let's pick the matching paren + // as the end of the line. + e + } + } + // No arithmetic/logical operation in sight, let's return the + // matching closing paren as the end. + None => e, + } } else { line.len() }; @@ -1572,6 +1599,12 @@ impl Parser { self.column += 1; self.offset += 1; } + + // Decrement `self.column` and `self.offset` by one. + fn prev(&mut self) { + self.column -= 1; + self.offset -= 1; + } } #[cfg(test)] @@ -2335,6 +2368,69 @@ mod tests { } #[test] + fn parens_to_desambiguate() { + let line = ".byte ($01 << 4) | ($01 << 2) | ($01 << 1)"; + let mut parser = Parser::default(); + assert!(parser.parse(line.as_bytes(), SourceInfo::default()).is_ok()); + + let node = parser.nodes.last().unwrap().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(), 1); + + let arg = args.first().unwrap(); + assert_eq!(arg.node_type, NodeType::Operation(OperationType::Or)); + + let first = arg.left.clone().unwrap(); + assert_eq!(first.node_type, NodeType::Operation(OperationType::Lshift)); + assert_node(&first.left.clone().unwrap(), NodeType::Literal, line, "$01"); + assert_node(&first.right.clone().unwrap(), NodeType::Value, line, "4"); + + let other = arg.right.clone().unwrap(); + + let second = other.left.clone().unwrap(); + assert_eq!(second.node_type, NodeType::Operation(OperationType::Lshift)); + assert_node( + &second.left.clone().unwrap(), + NodeType::Literal, + line, + "$01", + ); + assert_node(&second.right.clone().unwrap(), NodeType::Value, line, "2"); + + let third = other.right.clone().unwrap(); + assert_eq!(third.node_type, NodeType::Operation(OperationType::Lshift)); + assert_node(&third.left.clone().unwrap(), NodeType::Literal, line, "$01"); + assert_node(&third.right.clone().unwrap(), NodeType::Value, line, "1"); + } + + #[test] + fn parens_to_desambiguate2() { + let line = ".byte ($01 << 4) | ($01 << 2) | ($01 << 1), $02"; + let mut parser = Parser::default(); + assert!(parser.parse(line.as_bytes(), SourceInfo::default()).is_ok()); + + let node = parser.nodes.last().unwrap().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); + + let arg = args.first().unwrap(); + assert_eq!(arg.node_type, NodeType::Operation(OperationType::Or)); + // NOTE: no further assertions needed as this is covered in + // `parens_to_disambiguate`. + + let second = args.last().unwrap(); + assert_node(second, NodeType::Literal, line, "$02"); + } + + #[test] fn unary_operator_test() { let line = "ldx #<NUM_SPRITES"; let mut parser = Parser::default(); |
