aboutsummaryrefslogtreecommitdiff
path: root/lib/xixanta
diff options
context:
space:
mode:
Diffstat (limited to 'lib/xixanta')
-rw-r--r--lib/xixanta/src/assembler.rs87
-rw-r--r--lib/xixanta/src/node.rs2
-rw-r--r--lib/xixanta/src/opcodes.rs10
3 files changed, 98 insertions, 1 deletions
diff --git a/lib/xixanta/src/assembler.rs b/lib/xixanta/src/assembler.rs
index d7e036f..5c43387 100644
--- a/lib/xixanta/src/assembler.rs
+++ b/lib/xixanta/src/assembler.rs
@@ -30,7 +30,7 @@ enum LiteralMode {
/// The different stages that the assembler goes through and which are relevant
/// for the process.
-#[derive(PartialEq)]
+#[derive(Debug, PartialEq)]
enum Stage {
/// The context is still building up (i.e. we don't have all the variable
/// values, labels and their addresses yet).
@@ -883,6 +883,23 @@ impl<'a> Assembler<'a> {
self.labels_seen = pn.labels_seen;
self.context.force_context_switch(&pn.context);
+ // .fallthrough is handled here, whenever we already know addresses,
+ // sizes, etc. If this is the case, this is not a real node that can
+ // be bundled, but perform its check and move into the next
+ // iteration.
+ //
+ // 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 let Err(e) = self.fallthrough(&pn) {
+ errors.push(e);
+ }
+ continue;
+ }
+
self.literal_mode = None;
match self.evaluate_node(&pn.node) {
Ok(mut bundle) => {
@@ -949,6 +966,59 @@ impl<'a> Assembler<'a> {
}
}
+ fn fallthrough(&mut self, pn: &PendingNode) -> Result<(), Error> {
+ // 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() {
+ 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) {
+ return Err(Error {
+ line: pn.node.value.line,
+ message,
+ source: self.source_for(&pn.node),
+ expanded_from: pn.macro_context.clone(),
+ global: false,
+ });
+ }
+
+ // The check looks scarier than it is. We first grab the target address
+ // by evaluating the identifier as a variable. This should just gives as
+ // the address as is by calling .value(). The effective address is taken
+ // 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 current = &self.mappings[pn.mapping].segments[pn.segment];
+ let Some(effective) = current.bundles.get(pn.bundle_index) else {
+ return Err(Error {
+ line: pn.node.value.line,
+ message: String::from("statement does not fall through"),
+ source: self.source_for(&pn.node),
+ expanded_from: pn.macro_context.clone(),
+ global: false,
+ });
+ };
+
+ if effective.address != target_address {
+ return Err(Error {
+ line: pn.node.value.line,
+ message: String::from("statement does not fall through"),
+ source: self.source_for(&pn.node),
+ expanded_from: pn.macro_context.clone(),
+ global: false,
+ });
+ }
+ Ok(())
+ }
+
fn asan(&mut self, memory: &mut MemoryResult) -> Result<(), Vec<Error>> {
let mut errors = vec![];
@@ -1865,6 +1935,21 @@ 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 3f1778a..e13dd3c 100644
--- a/lib/xixanta/src/node.rs
+++ b/lib/xixanta/src/node.rs
@@ -166,6 +166,7 @@ pub enum ControlType {
EndIf,
Defined,
Echo(EchoKind),
+ Fallthrough,
}
impl fmt::Display for ControlType {
@@ -201,6 +202,7 @@ impl fmt::Display for ControlType {
EchoKind::Warning => write!(f, ".warning"),
EchoKind::Error => write!(f, ".error"),
},
+ ControlType::Fallthrough => write!(f, ".fallthrough"),
}
}
}
diff --git a/lib/xixanta/src/opcodes.rs b/lib/xixanta/src/opcodes.rs
index 48e4fca..bd3c207 100644
--- a/lib/xixanta/src/opcodes.rs
+++ b/lib/xixanta/src/opcodes.rs
@@ -1999,6 +1999,16 @@ 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
});