diff --git a/src/labels/process.rs b/src/labels/process.rs index a61d5ce..7d382a5 100644 --- a/src/labels/process.rs +++ b/src/labels/process.rs @@ -1,3 +1,5 @@ +use compact_str::CompactString; + use crate::world::Board; use super::{ @@ -14,6 +16,8 @@ pub fn process_labels(board: &mut Board) { .collect(); let mut registry = Registry::new(); + resolve_local_labels(&mut stats); + // Replace each label with its sanitized equivalent for stat in stats.iter_mut() { for chunk in stat.iter_mut() { @@ -51,6 +55,41 @@ 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 = CompactString::const_new(""); + for chunk in stat.iter_mut() { + match chunk { + Chunk::Label { + is_ref, + is_anon: false, + name: label, + } => { + // Handle cases where either one of (section name, label) is missing. + // If both are present ("name.local") then the label is already fully resolved + // and there is nothing to do. + if label.name.is_empty() { + // Expand :.local to :name.local + assert!(label.local.is_some()); + label.name = section.clone() + } 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 = label.name.clone(); + } + } + } + _ => {} + } + } + } +} + +/// Simultaneously: +/// 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) { let mut label_names = vec![]; for stat in stats.iter_mut() { @@ -84,6 +123,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; @@ -167,4 +207,11 @@ mod test { process_labels(&mut board); assert_snapshot!(board_to_text(board)); } + + #[test] + fn test_local_labels() { + let mut board = board_from_text("tests/labels/local.txt"); + process_labels(&mut board); + assert_snapshot!(board_to_text(board)); + } } diff --git a/src/labels/sanitize.rs b/src/labels/sanitize.rs index 3998466..05c493c 100644 --- a/src/labels/sanitize.rs +++ b/src/labels/sanitize.rs @@ -115,12 +115,19 @@ impl Registry { } fn preferred_name(label: &LabelName) -> CompactString { - let src = if let Some(local) = &label.local { - local + let mut result = CompactString::const_new(""); + if label.namespace.is_some() || label.local.is_some() { + // Prevent namespaces and local labels from starting with a digit. + // This ensures stuff like `#take gems 100.99orless` won't compile + // to `#take gems 10099orless` (would parse incorrectly). + result.push('_'); + } + if let Some(local) = &label.local { + result.push_str(local); } else { - &label.name + result.push_str(&label.name); }; - CompactString::new(src) + result } fn is_valid_label(s: &CompactString) -> bool { @@ -275,7 +282,8 @@ mod test { }; let inputs = [ simple("foo"), - full("alt", "foo", ""), + full("ns1", "foo", ""), + full("ns2", "foo", ""), full("", "foo", "thisloop"), full("", "foo", "thatloop"), full("", "bar", "thisloop"), @@ -296,11 +304,12 @@ mod test { let result = results.join("\n"); assert_snapshot!(result, @r#" LabelName { namespace: None, name: "foo", local: None } => foo - LabelName { namespace: Some("alt"), name: "foo", local: None } => foo0 - LabelName { namespace: None, name: "foo", local: Some("thisloop") } => thisloop - LabelName { namespace: None, name: "foo", local: Some("thatloop") } => thatloop - LabelName { namespace: None, name: "bar", local: Some("thisloop") } => thisloop0 - LabelName { namespace: None, name: "bar", local: Some("thatloop") } => thatloop0 + LabelName { namespace: Some("ns1"), name: "foo", local: None } => _foo + LabelName { namespace: Some("ns2"), name: "foo", local: None } => _foo0 + LabelName { namespace: None, name: "foo", local: Some("thisloop") } => _thisloop + LabelName { namespace: None, name: "foo", local: Some("thatloop") } => _thatloop + LabelName { namespace: None, name: "bar", local: Some("thisloop") } => _thisloop0 + LabelName { namespace: None, name: "bar", local: Some("thatloop") } => _thatloop0 LabelName { namespace: None, name: "bar1", local: None } => bar1 LabelName { namespace: None, name: "bar2", local: None } => bar2 LabelName { namespace: None, name: "bar123", local: None } => bar_ diff --git a/src/labels/snapshots/zasm__labels__process__test__local_labels.snap b/src/labels/snapshots/zasm__labels__process__test__local_labels.snap new file mode 100644 index 0000000..af0fea6 --- /dev/null +++ b/src/labels/snapshots/zasm__labels__process__test__local_labels.snap @@ -0,0 +1,20 @@ +--- +source: src/labels/process.rs +expression: board_to_text(board) +--- +'Slightly contrived example: two routines +'using the same local label ".loop" +:run_w +#walk w +:_loop +#try w stop +#_loop +' +:run_e +#walk e +:_loop0 +#try e stop +#_loop0 +' +:stop +#walk i diff --git a/tests/labels/local.txt b/tests/labels/local.txt new file mode 100644 index 0000000..141a81f --- /dev/null +++ b/tests/labels/local.txt @@ -0,0 +1,16 @@ +'Slightly contrived example: two routines +'using the same local label ".loop" +:run_w +#walk w +:.loop +#try w stop +#.loop +' +:run_e +#walk e +:.loop +#try e stop +#.loop +' +:stop +#walk i