aboutsummaryrefslogtreecommitdiff
path: root/lib/xixanta
diff options
context:
space:
mode:
authorMiquel Sabaté Solà <mikisabate@gmail.com>2025-08-17 07:31:44 +0200
committerMiquel Sabaté Solà <mikisabate@gmail.com>2025-08-17 07:31:44 +0200
commit6993a164dd57c57be9f79190824b774b162272eb (patch)
tree05152aca5e1cc9b166c9f472cd041fee47836ab4 /lib/xixanta
parentc6aaea6079f295c721b641f82b5f0f2a520490e3 (diff)
downloadtools.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/xixanta')
-rw-r--r--lib/xixanta/src/parser.rs102
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();