diff options
| author | Miquel Sabaté Solà <mssola@mssola.com> | 2026-08-18 16:39:37 +0200 |
|---|---|---|
| committer | Miquel Sabaté Solà <mssola@mssola.com> | 2026-08-18 16:39:37 +0200 |
| commit | fcd4c9a267b407e653ed6b21ae87f219f3e4c4af (patch) | |
| tree | 0b5f3e9bf99ab7377fa0bb6d204661ac8fbe3380 /lib/xixanta/src | |
| parent | d479b0fbd3778f5b6fc8b63f46aa0c5364e4fe7e (diff) | |
| download | tools.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')
| -rw-r--r-- | lib/xixanta/src/assembler.rs | 37 | ||||
| -rw-r--r-- | lib/xixanta/src/node.rs | 9 | ||||
| -rw-r--r-- | lib/xixanta/src/object.rs | 12 | ||||
| -rw-r--r-- | lib/xixanta/src/parser.rs | 57 |
4 files changed, 94 insertions, 21 deletions
diff --git a/lib/xixanta/src/assembler.rs b/lib/xixanta/src/assembler.rs index 91f1e14..3cf1e0b 100644 --- a/lib/xixanta/src/assembler.rs +++ b/lib/xixanta/src/assembler.rs @@ -523,7 +523,10 @@ impl<'a> Assembler<'a> { accessed: 1, }; - if let Err(err) = self.context.set_variable(&var_name, &var_value, false) { + if let Err(err) = self + .context + .set_variable(&var_name, &var_value, false, false) + { Err(Error { global: true, line: 0, @@ -557,6 +560,7 @@ impl<'a> Assembler<'a> { accessed: 0, }, false, + matches!(node.node_type, NodeType::Label(true)), ) { return Err(Error { message, @@ -585,7 +589,7 @@ impl<'a> Assembler<'a> { // context so it's known. The actual value cannot be computed // right now as we don't know the segment size where it belongs // yet. - NodeType::Label => { + NodeType::Label(_) => { // There's no good reason to declare a named label inside of // a macro. If that's the case, just error out. if (self.macros_seen > 0 || self.repeats_seen > 0) && !node.value.is_empty() { @@ -689,6 +693,7 @@ impl<'a> Assembler<'a> { accessed: 0, }, false, + false, ) { errors.push(Error { message: err, @@ -909,7 +914,12 @@ impl<'a> Assembler<'a> { }; if !node.value.is_empty() - && let Err(message) = self.context.set_variable(&node.value, &object, true) + && let Err(message) = self.context.set_variable( + &node.value, + &object, + true, + matches!(node.node_type, NodeType::Label(true)), + ) { return Err(Error { message, @@ -938,7 +948,7 @@ impl<'a> Assembler<'a> { // segment. Note that this is only the offset from the beginning // of the offset, the effective address will only be available // after calling `Context::get_variable` - NodeType::Label => { + NodeType::Label(_) => { if let Err(e) = self.apply_segment_offset_to_label(node, ObjectType::Address) { errors.push(e); } @@ -1150,11 +1160,16 @@ impl<'a> Assembler<'a> { // value. let mut dobj = d.obj.clone(); dobj.bundle = o; - self.context.set_variable(&d.id, &dobj, true) + self.context.set_variable( + &d.id, + &dobj, + true, + matches!(n.node_type, NodeType::Label(true)), + ) } // No attached node, we can proceed by setting the // variable as is. - None => self.context.set_variable(&d.id, &d.obj, true), + None => self.context.set_variable(&d.id, &d.obj, true, false), }; } } @@ -1714,7 +1729,7 @@ impl<'a> Assembler<'a> { // calls, just in case a macro is applied multiple times and we // need to get the latest value. let id = &margs.next().unwrap().value; - if let Err(message) = self.context.set_variable(id, &obj, true) { + if let Err(message) = self.context.set_variable(id, &obj, true, false) { return Err(Error { line: node.value.line, message, @@ -2618,6 +2633,7 @@ impl<'a> Assembler<'a> { accessed: 0, }, true, + false, ) { return Err(Error { @@ -3099,7 +3115,12 @@ impl<'a> Assembler<'a> { // again. If the variable could not be set, then it's not // that big of a deal at this stage. value.bundle = bundle.clone(); - let _ = self.context.set_variable(&node.value, &value, true); + let _ = self.context.set_variable( + &node.value, + &value, + true, + matches!(node.node_type, NodeType::Label(true)), + ); // And return the computed bundle. Ok(bundle) diff --git a/lib/xixanta/src/node.rs b/lib/xixanta/src/node.rs index 649f333..357b9e0 100644 --- a/lib/xixanta/src/node.rs +++ b/lib/xixanta/src/node.rs @@ -319,8 +319,10 @@ pub enum NodeType { /// '$'. The `left` node contains the inner expression. Literal, - /// A label statement, which only sets the `value`, the name of the label. - Label, + /// A label statement, which only sets the `value`, the name of the + /// label. If the inner boolean is true, then the label is considered to be + /// global, otherwise scopes apply. + Label(bool), /// A macro call. Note that a Value might also encode this, but when a Call /// has been detected, then there is no doubt on it. @@ -347,7 +349,8 @@ impl fmt::Display for NodeType { NodeType::Control(control_type) => write!(f, "control function ({control_type})"), NodeType::ControlBody => write!(f, "control function body"), NodeType::Literal => write!(f, "literal"), - NodeType::Label => write!(f, "label"), + NodeType::Label(true) => write!(f, "(global) label"), + NodeType::Label(false) => write!(f, "(scoped) label"), NodeType::Call => write!(f, "call"), NodeType::Fallthrough => write!(f, "fallthrough"), NodeType::Operation(op) => match op { diff --git a/lib/xixanta/src/object.rs b/lib/xixanta/src/object.rs index 6209afd..cc8908c 100644 --- a/lib/xixanta/src/object.rs +++ b/lib/xixanta/src/object.rs @@ -329,14 +329,22 @@ impl Context { /// Sets a value for an object identified by `id`. If `overwrite` is set to /// true, then this value will be set even if the id already existed, - /// otherwise it will return a ContextError + /// otherwise it will return a ContextError. If 'force_global' is set to + /// true, then the variable will be defined at the global context, not the + /// current one. pub fn set_variable( &mut self, id: &PString, object: &Object, overwrite: bool, + force_global: bool, ) -> Result<(), String> { - let scope_name = self.name().to_string(); + let scope_name = if force_global { + GLOBAL_CONTEXT + } else { + self.name() + } + .to_string(); let scope = self.map.get_mut(&scope_name).unwrap(); match scope.get_mut(&id.value) { 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] |
