diff --git a/Cargo.lock b/Cargo.lock index 65ce7d4..c5af765 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -19,6 +19,7 @@ dependencies = [ "nom", "peg_macro", "pest", + "rustc-hash", ] [[package]] @@ -231,6 +232,12 @@ dependencies = [ "proc-macro2", ] +[[package]] +name = "rustc-hash" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "357703d41365b4b27c590e3ed91eabb1b663f07c4c084095e60cbed4362dff0d" + [[package]] name = "rustversion" version = "1.0.21" diff --git a/Cargo.toml b/Cargo.toml index b4d78b6..4e0a4bc 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -13,6 +13,7 @@ insta = "1.39.0" nom = "7.1.3" pest = "2.7.15" compact_str = "0.9.0" +rustc-hash = "2.1.1" [dev-dependencies] insta = "1.39.0" diff --git a/src/labels/process.rs b/src/labels/process.rs index dc4bdd0..fe870a2 100644 --- a/src/labels/process.rs +++ b/src/labels/process.rs @@ -1,4 +1,5 @@ use compact_str::CompactString; +use rustc_hash::FxHashMap; use crate::world::Board; @@ -43,8 +44,17 @@ pub fn process_labels(board: &mut Board) { /// Resolve ".local" labels to "name.local" form. fn resolve_local_labels(stats: &mut [ParsedStat]) { for stat in stats.iter_mut() { - let mut section_counter = 0; - let mut section = CompactString::const_new("$0"); + // Helper: Generate unique section strings like "touch$0" + let mut i = 0; + let mut make_section_id = |label_name: &str| -> CompactString { + let result = format!("{}${}", &label_name, i); + i += 1; + result.into() + }; + + let mut namespace_to_section: FxHashMap, CompactString> = + FxHashMap::default(); + for chunk in stat.iter_mut() { match chunk { Chunk::Label { @@ -58,13 +68,21 @@ fn resolve_local_labels(stats: &mut [ParsedStat]) { if label.name.is_empty() { // Expand :.local to :name.local assert!(label.local.is_some()); - label.name = section.clone(); + label.name = if let Some(section) = + namespace_to_section.get(&label.namespace) + { + section.clone() + } else { + let section = make_section_id(""); + namespace_to_section.insert(label.namespace.clone(), section.clone()); + section + } } else if label.local.is_none() { // Interpret label :name as start of new section. // Only label definitions do this; label references have no effect. if !*is_ref { - section_counter += 1; - section = format!("{}${}", &label.name, section_counter).into(); + namespace_to_section + .insert(label.namespace.clone(), make_section_id(&label.name)); } } } @@ -106,9 +124,25 @@ fn assign_named_labels(stats: &mut [ParsedStat], registry: &mut Registry) { /// 1. Assign names to anonymous labels. /// 2. Resolve anonymous backward references to their label names. fn anonymous_forward_pass(stats: &mut [ParsedStat], registry: &mut Registry) { + // Save generated label names so they can be reused across multiple objects let mut label_names = vec![]; + for stat in stats.iter_mut() { + // Helper: Get the next label name that hasn't been used in this object yet let mut i = 0; + let mut get_next_name = || -> CompactString { + if i == label_names.len() { + label_names.push(registry.gen_anonymous()); + } + let result = label_names[i].clone(); + i += 1; + result + }; + + // Track each namespace's most recently defined anonymous label + let mut namespace_to_latest: FxHashMap, CompactString> = + FxHashMap::default(); + for chunk in stat.iter_mut() { match chunk { Chunk::Label { @@ -116,11 +150,9 @@ fn anonymous_forward_pass(stats: &mut [ParsedStat], registry: &mut Registry) { is_anon: true, name, } => { - if i == label_names.len() { - label_names.push(registry.gen_anonymous()); - } - name.name = label_names[i].clone(); - i += 1; + let assigned = get_next_name(); + namespace_to_latest.insert(name.namespace.clone(), assigned.clone()); + name.name = assigned; } Chunk::Label { is_ref: true, @@ -129,7 +161,7 @@ fn anonymous_forward_pass(stats: &mut [ParsedStat], registry: &mut Registry) { } => { if name.name == "@b" { // TODO: a more explicit check that a "before" label exists - name.name = label_names[i - 1].clone(); + name.name = namespace_to_latest.get(&name.namespace).unwrap().clone(); } } _ => {} @@ -141,7 +173,7 @@ fn anonymous_forward_pass(stats: &mut [ParsedStat], registry: &mut Registry) { /// Resolve anonymous forward references to their label names. fn anonymous_backward_pass(stats: &mut [ParsedStat]) { for stat in stats.iter_mut() { - let mut last_name = None; + let mut namespace_to_latest = FxHashMap::default(); for chunk in stat.iter_mut().rev() { match chunk { Chunk::Label { @@ -149,7 +181,7 @@ fn anonymous_backward_pass(stats: &mut [ParsedStat]) { is_anon: true, name, } => { - last_name = Some(name.name.clone()); + namespace_to_latest.insert(name.namespace.clone(), name.name.clone()); } Chunk::Label { is_ref: true, @@ -157,7 +189,7 @@ fn anonymous_backward_pass(stats: &mut [ParsedStat]) { name, } => { if name.name == "@f" { - name.name = last_name.clone().unwrap(); + name.name = namespace_to_latest.get(&name.namespace).unwrap().clone(); } } _ => {} @@ -229,4 +261,11 @@ mod test { process_labels(&mut board); assert_snapshot!(board_to_text(board)); } + + #[test] + fn test_namespaces() { + let mut board = board_from_text("tests/labels/namespaces.txt"); + process_labels(&mut board); + assert_snapshot!(board_to_text(board)); + } } diff --git a/src/labels/snapshots/zasm__labels__process__test__namespaces.snap b/src/labels/snapshots/zasm__labels__process__test__namespaces.snap new file mode 100644 index 0000000..df9e592 --- /dev/null +++ b/src/labels/snapshots/zasm__labels__process__test__namespaces.snap @@ -0,0 +1,34 @@ +--- +source: src/labels/process.rs +expression: board_to_text(board) +--- +'Make sure anonymous references respect namespaces +#_ +#a +#b +:_ +:a +:b +#_ +#a +#b +--- +'Make sure namespaces don't interfere with anonymous label reuse. +'These three should match the ones above. +:_ +:a +:b +--- +'Make sure locals exist in separate namespaces +:do_stuff +#lock +:_loop +' +:_do_stuff +:_loop0 +/i#if blocked rndne _loop0 +' +#take time 1 _break +#_loop +:_break +#unlock diff --git a/tests/labels/namespaces.txt b/tests/labels/namespaces.txt new file mode 100644 index 0000000..aff3a42 --- /dev/null +++ b/tests/labels/namespaces.txt @@ -0,0 +1,30 @@ +'Make sure anonymous references respect namespaces +#@f +#foo~@f +#bar~@f +:@ +:foo~@ +:bar~@ +#@b +#foo~@b +#bar~@b +--- +'Make sure namespaces don't interfere with anonymous label reuse. +'These three should match the ones above. +:@ +:@ +:@ +--- +'Make sure locals exist in separate namespaces +:do_stuff +#lock +:.loop +' +:foo~do_stuff +:foo~.loop +/i#if blocked rndne foo~.loop +' +#take time 1 .break +#.loop +:.break +#unlock