aboutsummaryrefslogtreecommitdiff
path: root/lib
diff options
context:
space:
mode:
authorMiquel Sabaté Solà <mssola@mssola.com>2026-02-02 23:13:57 +0100
committerMiquel Sabaté Solà <mssola@mssola.com>2026-02-02 23:22:47 +0100
commit31dd071e927ab4375db6c408c08ab776e4a5c655 (patch)
tree36a2d892bcd3162e8cf2ae875ed834cb50431432 /lib
parent6ab4d0aa58928bf9a5bc0b60d216e1cebaf9a5a8 (diff)
downloadtools.nes-31dd071e927ab4375db6c408c08ab776e4a5c655.tar.gz
tools.nes-31dd071e927ab4375db6c408c08ab776e4a5c655.zip
Change .fallthrough to __fallthrough__
Implementing it as a control statement has the bad thing that it's impossible to be compatible with other assemblers such as ca65, as you cannot create dummy control statements or something like that in there. Instead of that, we define it with the special underscores which are still valid for identifiers, and give a "compiler-specific thingie" flair to it. This also has the benefit that the parser can be a bit more strict. Signed-off-by: Miquel Sabaté Solà <mssola@mssola.com>
Diffstat (limited to 'lib')
-rw-r--r--lib/xixanta/src/assembler.rs43
-rw-r--r--lib/xixanta/src/node.rs6
-rw-r--r--lib/xixanta/src/opcodes.rs10
-rw-r--r--lib/xixanta/src/parser.rs28
4 files changed, 49 insertions, 38 deletions
diff --git a/lib/xixanta/src/assembler.rs b/lib/xixanta/src/assembler.rs
index 5c43387..cb992d4 100644
--- a/lib/xixanta/src/assembler.rs
+++ b/lib/xixanta/src/assembler.rs
@@ -855,7 +855,20 @@ impl<'a> Assembler<'a> {
errors.append(&mut ers);
}
}
-
+ NodeType::Fallthrough => {
+ // We don't do much other than pushing a fake PendingNode with a
+ // 'bundle_index' value that will point to the next node.
+ let current = &mut self.mappings[self.current_mapping];
+ self.pending.push(PendingNode {
+ mapping: self.current_mapping,
+ segment: self.current_segment,
+ context: self.context.name().to_string(),
+ bundle_index: current.segments[self.current_segment].bundles.len(),
+ node: node.to_owned(),
+ labels_seen: self.context.labels_seen(),
+ macro_context: self.macro_context.clone(),
+ });
+ }
_ => {}
}
@@ -890,10 +903,7 @@ impl<'a> Assembler<'a> {
//
// NOTE: this has to happen with a context switch, otherwise the
// name resolution won't be accurate.
- if matches!(
- pn.node.node_type,
- NodeType::Control(ControlType::Fallthrough)
- ) {
+ if matches!(pn.node.node_type, NodeType::Fallthrough) {
if let Err(e) = self.fallthrough(&pn) {
errors.push(e);
}
@@ -970,16 +980,14 @@ impl<'a> Assembler<'a> {
// If we are not in 'crunch' mode, then it's a bug.
assert_eq!(self.stage, Stage::Crunching);
- let node = pn.node.left.as_ref().unwrap();
-
// If there was no "argument", then skip things altogether. The
// programmer opted for an explicit fallthrough without further checks.
- if node.value.is_empty() {
+ if pn.node.value.is_empty() {
return Ok(());
}
// If there is an argument, it has to be a valid address identifier.
- if let Err(message) = node.value.is_valid_identifier(false) {
+ if let Err(message) = pn.node.value.is_valid_identifier(false) {
return Err(Error {
line: pn.node.value.line,
message,
@@ -995,7 +1003,7 @@ impl<'a> Assembler<'a> {
// from the 'bundle_index' from the PendingNode, as it was pushed while
// pointing to the "next" bundle. Hence, we just fetch that bundle and
// get its address.
- let target_address = self.evaluate_variable(node)?.value() as usize;
+ let target_address = self.evaluate_variable(&pn.node)?.value() as usize;
let current = &self.mappings[pn.mapping].segments[pn.segment];
let Some(effective) = current.bundles.get(pn.bundle_index) else {
return Err(Error {
@@ -1935,21 +1943,6 @@ impl<'a> Assembler<'a> {
NodeType::Control(ControlType::EndIf) => Ok(()),
NodeType::Control(ControlType::IncludeSource) => Ok(()),
NodeType::Control(ControlType::Echo(_)) => Ok(()),
- NodeType::Control(ControlType::Fallthrough) => {
- // We don't do much other than pushing a fake PendingNode with a
- // 'bundle_index' value that will point to the next node.
- let current = &mut self.mappings[self.current_mapping];
- self.pending.push(PendingNode {
- mapping: self.current_mapping,
- segment: self.current_segment,
- context: self.context.name().to_string(),
- bundle_index: current.segments[self.current_segment].bundles.len(),
- node: node.to_owned(),
- labels_seen: self.context.labels_seen(),
- macro_context: self.macro_context.clone(),
- });
- Ok(())
- }
_ => Err(Error {
line: node.value.line,
message: format!(
diff --git a/lib/xixanta/src/node.rs b/lib/xixanta/src/node.rs
index 9fbe34c..5093ab3 100644
--- a/lib/xixanta/src/node.rs
+++ b/lib/xixanta/src/node.rs
@@ -166,7 +166,6 @@ pub enum ControlType {
EndIf,
Defined,
Echo(EchoKind),
- Fallthrough,
}
impl fmt::Display for ControlType {
@@ -202,7 +201,6 @@ impl fmt::Display for ControlType {
EchoKind::Warning => write!(f, ".warning"),
EchoKind::Error => write!(f, ".error"),
},
- ControlType::Fallthrough => write!(f, ".fallthrough"),
}
}
}
@@ -319,6 +317,9 @@ pub enum NodeType {
/// A comment which is relevant for the current session.
Comment(CommentType),
+
+ /// Fallthrough pseudo-instruction.
+ Fallthrough,
}
impl fmt::Display for NodeType {
@@ -333,6 +334,7 @@ impl fmt::Display for NodeType {
NodeType::Literal => write!(f, "literal"),
NodeType::Label => write!(f, "label"),
NodeType::Call => write!(f, "call"),
+ NodeType::Fallthrough => write!(f, "fallthrough"),
NodeType::Operation(op) => match op {
OperationType::Add => write!(f, "addition"),
OperationType::Sub => write!(f, "subtraction"),
diff --git a/lib/xixanta/src/opcodes.rs b/lib/xixanta/src/opcodes.rs
index bd3c207..48e4fca 100644
--- a/lib/xixanta/src/opcodes.rs
+++ b/lib/xixanta/src/opcodes.rs
@@ -1999,16 +1999,6 @@ pub static CONTROL_FUNCTIONS: LazyLock<HashMap<String, Control>> = LazyLock::new
only_string: true,
},
);
- functions.insert(
- String::from(".fallthrough"),
- Control {
- control_type: ControlType::Fallthrough,
- has_identifier: Some(false),
- required_args: Some((0, 0)),
- touches_context: false,
- only_string: false,
- },
- );
functions
});
diff --git a/lib/xixanta/src/parser.rs b/lib/xixanta/src/parser.rs
index 7a7e3f0..e2e31dc 100644
--- a/lib/xixanta/src/parser.rs
+++ b/lib/xixanta/src/parser.rs
@@ -766,9 +766,12 @@ impl Parser {
// Parse statements which are neither an instruction nor an assignment. This
// includes stuff like control statements or macro calls.
fn parse_other(&mut self, line: &str, id: PString) -> Result<(), Vec<Error>> {
- // This is either a control statement (i.e. starts with '.') or a macro call.
+ // This is either a control statement (i.e. starts with '.'), a
+ // __fallthrough__, or a macro call.
let node = if id.value.starts_with('.') {
self.parse_control(id, line, 0)?
+ } else if id.value == "__fallthrough__" {
+ self.parse_fallthrough(line)?
} else {
let args = self.parse_arguments(line, 0)?;
PNode {
@@ -1642,6 +1645,29 @@ impl Parser {
})
}
+ // Parse a fallthrough statement.
+ fn parse_fallthrough(&mut self, line: &str) -> Result<PNode, Error> {
+ // Skip whitespaces and fetch the identifier.
+ self.skip_whitespace(line);
+ let identifier = self.parse_identifier(line, false)?.0;
+
+ // If there was something else other than the identifier, it's a parsing
+ // error.
+ let rest = line.get(self.offset..).unwrap_or("").trim();
+ if !rest.is_empty() {
+ return Err(self.parser_error("too many arguments for __fallthrough__"));
+ }
+
+ Ok(PNode {
+ node_type: NodeType::Fallthrough,
+ value: identifier,
+ left: None,
+ right: None,
+ args: None,
+ source: self.current_source,
+ })
+ }
+
// Returns a NodeType::Literal node with whatever could be parsed
// considering the given `line` which starts with the given `symbol`. The
// recursivity `level` is also provided as it will call again