Flesh out support for namespaces

This commit is contained in:
2025-06-21 22:32:35 -07:00
parent 84cec642a3
commit b65e188cb9
5 changed files with 125 additions and 14 deletions
Generated
+7
View File
@@ -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"
+1
View File
@@ -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"
+53 -14
View File
@@ -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<Option<CompactString>, 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<Option<CompactString>, 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));
}
}
@@ -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
+30
View File
@@ -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