Add support for local labels

This commit is contained in:
2025-06-19 18:59:50 -07:00
parent 0f59dd668a
commit 71906cf4b8
4 changed files with 102 additions and 10 deletions
+47
View File
@@ -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));
}
}
+19 -10
View File
@@ -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_
@@ -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