aboutsummaryrefslogtreecommitdiff
path: root/lib/xixanta/src/parser.rs
diff options
context:
space:
mode:
authorMiquel Sabaté Solà <mssola@mssola.com>2026-08-18 16:39:37 +0200
committerMiquel Sabaté Solà <mssola@mssola.com>2026-08-18 16:39:37 +0200
commitfcd4c9a267b407e653ed6b21ae87f219f3e4c4af (patch)
tree0b5f3e9bf99ab7377fa0bb6d204661ac8fbe3380 /lib/xixanta/src/parser.rs
parentd479b0fbd3778f5b6fc8b63f46aa0c5364e4fe7e (diff)
downloadtools.nes-fcd4c9a267b407e653ed6b21ae87f219f3e4c4af.tar.gz
tools.nes-fcd4c9a267b407e653ed6b21ae87f219f3e4c4af.zip
Add global labels
These are labels that are declared by prefixing a '#' symbol to the name, and it allows the label to be declared at the global scope instead of the current one. This is a feature which is not to be abused so to not make scopes pointless, but it can be quite handy with some optimizations while not abandoning scopes completely. Signed-off-by: Miquel Sabaté Solà <mssola@mssola.com>
Diffstat (limited to 'lib/xixanta/src/parser.rs')
-rw-r--r--lib/xixanta/src/parser.rs57
1 files changed, 49 insertions, 8 deletions
diff --git a/lib/xixanta/src/parser.rs b/lib/xixanta/src/parser.rs
index abcf1e8..a71c6f6 100644
--- a/lib/xixanta/src/parser.rs
+++ b/lib/xixanta/src/parser.rs
@@ -536,7 +536,7 @@ impl Parser {
// identifier and finally fall through.
self.offset = 0;
let (mut id, mut nt) = self.parse_identifier(l, true)?;
- if nt == NodeType::Label {
+ if matches!(nt, NodeType::Label(_)) {
self.nodes.last_mut().unwrap().push(PNode {
node_type: nt,
value: id,
@@ -558,7 +558,7 @@ impl Parser {
// for it and fall through.
self.offset = 0;
(id, nt) = self.parse_identifier(l, true)?;
- if nt == NodeType::Label {
+ if matches!(nt, NodeType::Label(_)) {
return Err(self
.parser_error("cannot have multiple labels at the same location")
.into());
@@ -624,10 +624,11 @@ impl Parser {
// statement. 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, dot: bool) -> Result<(PString, NodeType), Error> {
- let start = self.column;
- let base_offset = self.offset;
+ let mut start = self.column;
+ let mut base_offset = self.offset;
let mut nt = NodeType::Value;
let mut first_seen = false;
+ let mut global_label = false;
// For the general case we just need to iterate until a whitespace
// character or an inline comment is found. Then our PString object is
@@ -645,6 +646,25 @@ impl Parser {
if c == '.' && (!dot || first_seen) {
return Err(self.parser_error("cannot have a '.' in this context"));
}
+
+ // If this is the first character, then allow a leading '#'
+ // character to denote a global label. Note that the '#' symbol will
+ // be skipped in the resulting name, and the start/end positions
+ // from the node will also reflect the '#' symbol being skipped.
+ if c == '#' && !first_seen {
+ global_label = true;
+
+ // We don't want the '#' symbol to be considered part of the
+ // name, as it's just a literal from the syntax, not the name
+ // itself.
+ base_offset += 1;
+ start += 1;
+
+ // Nothing else to be done, just jump into the next character.
+ first_seen = true;
+ self.next();
+ continue;
+ }
first_seen = true;
// Check for the end of the identifier. For this, it's easier to
@@ -732,18 +752,26 @@ impl Parser {
}
}
// Regular label (e.g. "label:").
- _ => nt = NodeType::Label,
+ _ => nt = NodeType::Label(global_label),
}
}
}
// If there are no characters left, check if it was a regular label.
None => {
if c == ':' {
- nt = NodeType::Label;
+ nt = NodeType::Label(global_label);
}
}
}
+ // Detect bogus names starting with '#' but not being an actual
+ // label.
+ if global_label && !matches!(nt, NodeType::Label(true)) {
+ return Err(
+ self.parser_error("identifier starts with a '#' but it's not a 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());
@@ -755,7 +783,7 @@ impl Parser {
// regular labels because we want to ignore to extra ':'
// character in the end.
let end = match nt {
- NodeType::Label => {
+ NodeType::Label(_) => {
self.next();
self.column - 1
}
@@ -1729,7 +1757,7 @@ impl Parser {
return Err(self.parser_error("invalid identifier"));
}
- if nt == NodeType::Label {
+ if matches!(nt, NodeType::Label(_)) {
Err(self.parser_error("not expecting a label defined here"))
} else {
Ok(PNode {
@@ -2219,6 +2247,19 @@ mod tests {
assert_node(nodes.last().unwrap(), NodeType::Instruction, line, "dex")
}
+ #[test]
+ fn bad_pound_label() {
+ let mut parser = Parser::default();
+ let err = parser
+ .parse("#fakelabel = 1".as_bytes(), &SourceInfo::default())
+ .unwrap_err();
+
+ assert_eq!(
+ err.first().unwrap().message,
+ "identifier starts with a '#' but it's not a label"
+ );
+ }
+
// Literals
#[test]