use std::collections::hash_map::DefaultHasher;
use std::hash::{Hash, Hasher};
use brink_format::{DefinitionId, DefinitionTag};
use crate::FileId;
use crate::hir;
use crate::symbols::{SymbolIndex, SymbolKind};
pub fn stamp_container_ids(files: &mut [(FileId, hir::HirFile)], index: &SymbolIndex) {
for (file_id, hir_file) in files {
let mut seq = 0;
stamp_block(
&mut hir_file.root_content,
*file_id,
"",
"",
index,
&mut seq,
);
for knot in &mut hir_file.knots {
let knot_path = &knot.name.text;
let mut seq = 0;
stamp_block(
&mut knot.body,
*file_id,
knot_path,
knot_path,
index,
&mut seq,
);
for stitch in &mut knot.stitches {
let stitch_path = format!("{knot_path}.{}", stitch.name.text);
let mut seq = 0;
stamp_block(
&mut stitch.body,
*file_id,
&stitch_path,
&stitch_path,
index,
&mut seq,
);
}
}
}
}
fn stamp_block(
block: &mut hir::Block,
_file: FileId,
scope_path: &str,
label_scope: &str,
index: &SymbolIndex,
seq_counter: &mut usize,
) {
let mut choice_counter = 0usize;
let mut gather_counter = 0usize;
for stmt in &mut block.stmts {
stamp_stmt(
stmt,
scope_path,
label_scope,
index,
seq_counter,
&mut choice_counter,
&mut gather_counter,
);
}
}
#[expect(
clippy::too_many_lines,
reason = "structural match over all statement types"
)]
fn stamp_stmt(
stmt: &mut hir::Stmt,
scope_path: &str,
label_scope: &str,
index: &SymbolIndex,
seq_counter: &mut usize,
choice_counter: &mut usize,
gather_counter: &mut usize,
) {
match stmt {
hir::Stmt::ChoiceSet(cs) => {
let gather_id = if let Some(ref label) = cs.continuation.label {
let label_path = qualify(label_scope, &label.text);
lookup_label_id(index, &label_path)
.unwrap_or_else(|| alloc_address(&format!("{scope_path}.g-{gather_counter}")))
} else {
alloc_address(&format!("{scope_path}.g-{gather_counter}"))
};
cs.gather_id = Some(gather_id);
cs.continuation.container_id = Some(gather_id);
*gather_counter += 1;
for choice in &mut cs.choices {
let choice_id = if let Some(ref label) = choice.label {
let label_path = qualify(label_scope, &label.text);
lookup_label_id(index, &label_path).unwrap_or_else(|| {
alloc_address(&format!("{scope_path}.c{choice_counter}"))
})
} else {
alloc_address(&format!("{scope_path}.c{choice_counter}"))
};
choice.container_id = Some(choice_id);
*choice_counter += 1;
let child_scope = format!("{scope_path}.c{}", *choice_counter - 1);
let mut child_choice_counter = 0;
let mut child_gather_counter = 0;
for body_stmt in &mut choice.body.stmts {
stamp_stmt(
body_stmt,
&child_scope,
label_scope,
index,
seq_counter,
&mut child_choice_counter,
&mut child_gather_counter,
);
}
}
for cont_stmt in &mut cs.continuation.stmts {
stamp_stmt(
cont_stmt,
scope_path,
label_scope,
index,
seq_counter,
choice_counter,
gather_counter,
);
}
}
hir::Stmt::LabeledBlock(block) => {
if block.label.is_some() {
let label_path = block
.label
.as_ref()
.map(|l| qualify(label_scope, &l.text))
.unwrap_or_default();
let label_id = lookup_label_id(index, &label_path)
.unwrap_or_else(|| alloc_address(&label_path));
block.container_id = Some(label_id);
*gather_counter += 1;
}
for s in &mut block.stmts {
stamp_stmt(
s,
scope_path,
label_scope,
index,
seq_counter,
choice_counter,
gather_counter,
);
}
}
hir::Stmt::Conditional(cond) => {
let cond_idx = *seq_counter;
*seq_counter += 1;
let cond_scope = format!("b-{cond_idx}");
for (branch_idx, branch) in cond.branches.iter_mut().enumerate() {
let branch_scope = if scope_path.is_empty() {
format!("{cond_scope}.{branch_idx}")
} else {
format!("{scope_path}.{cond_scope}.{branch_idx}")
};
let branch_id = alloc_address(&branch_scope);
branch.container_id = Some(branch_id);
for s in &mut branch.body.stmts {
stamp_stmt(
s,
&branch_scope,
label_scope,
index,
seq_counter,
choice_counter,
gather_counter,
);
}
}
}
hir::Stmt::Sequence(seq) => {
let seq_idx = *seq_counter;
*seq_counter += 1;
let display_name = format!("s-{seq_idx}");
let child_scope = if scope_path.is_empty() {
display_name.clone()
} else {
format!("{scope_path}.{display_name}")
};
let wrapper_id = alloc_address(&child_scope);
seq.container_id = Some(wrapper_id);
for (branch_idx, branch) in seq.branches.iter_mut().enumerate() {
let branch_path = if child_scope.is_empty() {
format!("{branch_idx}")
} else {
format!("{child_scope}.{branch_idx}")
};
let branch_id = alloc_address(&branch_path);
branch.container_id = Some(branch_id);
let mut bc = 0;
let mut gc = 0;
for s in &mut branch.stmts {
stamp_stmt(
s,
&child_scope,
label_scope,
index,
seq_counter,
&mut bc,
&mut gc,
);
}
}
}
hir::Stmt::Content(_)
| hir::Stmt::Divert(_)
| hir::Stmt::TunnelCall(_)
| hir::Stmt::ThreadStart(_)
| hir::Stmt::TempDecl(_)
| hir::Stmt::Assignment(_)
| hir::Stmt::Return(_)
| hir::Stmt::ExprStmt(_)
| hir::Stmt::EndOfLine => {}
}
}
fn alloc_address(path: &str) -> DefinitionId {
let mut hasher = DefaultHasher::new();
path.hash(&mut hasher);
DefinitionId::new(DefinitionTag::Address, hasher.finish())
}
fn lookup_label_id(index: &SymbolIndex, name: &str) -> Option<DefinitionId> {
index.by_name.get(name).and_then(|ids| {
ids.iter()
.find(|&&id| {
index.symbols.get(&id).is_some_and(|info| {
matches!(
info.kind,
SymbolKind::Knot | SymbolKind::Stitch | SymbolKind::Label
)
})
})
.copied()
})
}
fn qualify(scope_path: &str, name: &str) -> String {
if scope_path.is_empty() {
name.to_string()
} else {
format!("{scope_path}.{name}")
}
}