From 35256904c49a961fbaebbd134539516725c53455 Mon Sep 17 00:00:00 2001 From: Miquel Sabaté Solà Date: Fri, 20 Dec 2024 10:52:27 +0100 Subject: Find variable values on parent scopes MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit If a given variable cannot be found on the current scope, attempt to go up the context hierarchy to find it. Signed-off-by: Miquel Sabaté Solà --- lib/xixanta/src/assembler.rs | 34 +++++++++++++++++++ lib/xixanta/src/object.rs | 77 +++++++++++++++++++++++++++++++++++++------- 2 files changed, 99 insertions(+), 12 deletions(-) (limited to 'lib/xixanta/src') diff --git a/lib/xixanta/src/assembler.rs b/lib/xixanta/src/assembler.rs index 4e33d66..888e122 100644 --- a/lib/xixanta/src/assembler.rs +++ b/lib/xixanta/src/assembler.rs @@ -1774,6 +1774,40 @@ adc Variable assert_eq!(instr.bytes[1], 0x04); } + #[test] + fn reference_outer_variables() { + let mut asm = Assembler::new(EMPTY.to_vec()); + asm.mappings[0].segments[0].bundles = minimal_header(); + asm.mappings[0].offset = 6; + asm.current_mapping = 1; + let res = &asm + .assemble( + std::env::current_dir().unwrap().to_path_buf(), + r#" +foo: + rts + +.proc inner + jsr foo +.endproc +"# + .as_bytes(), + ) + .unwrap()[0x10..]; + + assert_eq!(res.len(), 2); + + // foo: rts + assert_eq!(res[0].size, 1); + assert_eq!(res[0].bytes[0], 0x60); + + // inner: jsr foo + assert_eq!(res[1].size, 3); + assert_eq!(res[1].bytes[0], 0x20); + assert_eq!(res[1].bytes[1], 0x00); + assert_eq!(res[1].bytes[2], 0x80); + } + #[test] fn bad_variable_but_valid_identifier_in_instruction() { assert_eval_error( diff --git a/lib/xixanta/src/object.rs b/lib/xixanta/src/object.rs index 2708647..41ec67e 100644 --- a/lib/xixanta/src/object.rs +++ b/lib/xixanta/src/object.rs @@ -177,12 +177,25 @@ impl Context { pub fn get_variable(&self, id: &PString, mappings: &[Mapping]) -> Result { // First of all, figure out the name of the scope and the real name of // the variable. If this was not scoped at all (None case when trying to - // rsplit by the "::" operator), then we assume it's a global variable. + // rsplit by the "::" operator), then we assume on the current scope. let (scope_name, var_name) = match id.value.rsplit_once("::") { Some((scope, name)) => (scope, name), None => (self.name(), id.value.as_str()), }; + self.get_variable_in_scope(id.line, scope_name, var_name, mappings) + } + + // Get the `var_name` variable on the `scope_name` scope (or parents). For + // further context, take the `mappings` into consideration when resolving + // labels, and `line` when producing context errors. + fn get_variable_in_scope( + &self, + line: usize, + scope_name: &str, + var_name: &str, + mappings: &[Mapping], + ) -> Result { // And with that, the only thing left is to find the scope and the // variable in it. match self.map.get(scope_name) { @@ -191,20 +204,46 @@ impl Context { ObjectType::Value => Ok(var.clone()), ObjectType::Address => Ok(self.resolve_label(mappings, var)?), }, - None => Err(ContextError { - message: format!( - "could not find variable '{}' in {}", - var_name, - self.to_human_with(scope_name) - ), - line: id.line, - reason: ContextErrorReason::UnknownVariable, - global: false, - }), + None => { + // If it cannot be found, then we have to move up through + // the scope hierarchy to see if we can fetch it there. For + // that, though, we first prepare an error so we return the + // original one, not the propagated one (see below). + let err = Err(ContextError { + message: format!( + "could not find variable '{}' in {}", + var_name, + self.to_human_with(scope_name) + ), + line, + reason: ContextErrorReason::UnknownVariable, + global: false, + }); + + // If we are already in the global context and the object + // was not found, just leave with an error. + if scope_name == GLOBAL_CONTEXT { + err + } else { + // Recursive call to find the object on the parent + // scope. If this cannot be found, instead of using the + // error from the recursive call, preserve the original + // error so it better reflects the original scope where + // this was first attempted. + let parent = self.parent(scope_name); + if let Ok(object) = + self.get_variable_in_scope(line, parent, var_name, mappings) + { + Ok(object) + } else { + err + } + } + } }, None => Err(ContextError { message: format!("did not find scope '{}'", scope_name), - line: id.line, + line, reason: ContextErrorReason::BadScope, global: false, }), @@ -443,6 +482,20 @@ impl Context { Ok(()) } + // Returns the name context that is directly above the one named `name`. + fn parent(&self, name: &str) -> &str { + let index = self + .stack + .iter() + .position(|n| n.as_str() == name) + .unwrap_or(0); + if index < 2 { + GLOBAL_CONTEXT + } else { + self.stack.get(index - 1).unwrap() + } + } + /// Returns the name of the current context. pub fn name(&self) -> &str { match self.stack.last() { -- cgit v1.2.3