diff --git a/src/labels/process.rs b/src/labels/process.rs index 7d382a5..960b522 100644 --- a/src/labels/process.rs +++ b/src/labels/process.rs @@ -17,22 +17,7 @@ pub fn process_labels(board: &mut Board) { 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() { - match chunk { - Chunk::Verbatim(_) => {} - Chunk::Label { - name, - is_ref: _, - is_anon: _, - } => { - name.name = registry.sanitize(name).into(); - } - } - } - } + assign_named_labels(&mut stats, &mut registry); // Assign names to anonymous labels anonymous_forward_pass(&mut stats, &mut registry); @@ -87,6 +72,34 @@ fn resolve_local_labels(stats: &mut [ParsedStat]) { } } +/// Assign sanitized names to all of the named labels. +fn assign_named_labels(stats: &mut [ParsedStat], registry: &mut Registry) { + for stat in stats.iter_mut() { + for chunk in stat.iter_mut() { + match chunk { + Chunk::Label { + name, + is_ref: _, + is_anon: false, + } => { + let mut full_name = CompactString::const_new(""); + if let Some(namespace) = &name.namespace { + full_name.push_str(&namespace); + full_name.push('~'); + } + full_name.push_str(&name.name); + if let Some(local) = &name.local { + full_name.push('.'); + full_name.push_str(&local); + } + name.name = registry.sanitize(&full_name).into(); + } + _ => {} + } + } + } +} + /// Simultaneously: /// 1. Assign names to anonymous labels. /// 2. Resolve anonymous backward references to their label names. diff --git a/src/labels/sanitize.rs b/src/labels/sanitize.rs index 05c493c..cfa4673 100644 --- a/src/labels/sanitize.rs +++ b/src/labels/sanitize.rs @@ -2,25 +2,14 @@ use std::collections::{HashMap, HashSet}; use compact_str::CompactString; -use super::parse::LabelName; - #[derive(PartialEq, Eq, Hash)] struct LabelId(CompactString); impl LabelId { - fn new(label: &LabelName) -> Self { - let mut result = CompactString::with_capacity(0); - if let Some(namespace) = &label.namespace { - result.push_str(&namespace); - result.push('~'); - } - result.push_str(&label.name); - if let Some(local) = &label.local { - result.push('.'); - result.push_str(&local); - } + fn new(label: &str) -> Self { + let mut result = CompactString::new(label); result.make_ascii_lowercase(); - LabelId(result) + Self(result) } } @@ -36,11 +25,12 @@ enum Transform { } impl Transform { - fn apply(&self, label: &LabelName) -> CompactString { + fn apply(&self, label: &str) -> CompactString { + let preferred = preferred_label_name(label); match self { - Transform::Preferred => Registry::preferred_name(label), + Transform::Preferred => preferred, Transform::FilteredWithSuffix(suffix) => { - let mut result = Registry::preferred_name(label); + let mut result = preferred; result = Registry::filter_label_chars(&result); result.push_str(&suffix); result @@ -66,11 +56,11 @@ impl Registry { } } - pub fn sanitize(&mut self, label: &LabelName) -> CompactString { + pub fn sanitize(&mut self, label: &str) -> CompactString { // Use existing sanitization if one exists let id = LabelId::new(label); if let Some(transform) = self.label_transforms.get(&id) { - return transform.apply(&label); + return transform.apply(label); } // Try to use label's preferred name as-is @@ -114,22 +104,6 @@ impl Registry { result } - fn preferred_name(label: &LabelName) -> CompactString { - 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 { - result.push_str(&label.name); - }; - result - } - fn is_valid_label(s: &CompactString) -> bool { if s.len() == 0 { return false; @@ -159,6 +133,22 @@ impl Registry { } } +/// Given an unsanitized full name, generate the first-pick name we'd like to +/// assign this label. (Results returned by this function are subject to veto +/// if they are invalid or already taken.) +fn preferred_label_name(s: &str) -> CompactString { + if let Some((_, suffix)) = s.rsplit_once(|c: char| !c.is_ascii_alphanumeric() && c != '_') { + // 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). + let mut result = CompactString::const_new("_"); + result.push_str(suffix); + result + } else { + CompactString::new(s) + } +} + /// Increments a string through label-safe characters. /// Characters loop through underscore and the letters a-z. /// Additionally, the final character is allowed to loop through 0-9. @@ -166,12 +156,10 @@ fn increment(s: &mut CompactString) { let original_len = s.len(); // Find a character we can increment without carry - dbg!(&s); let mut last_char = s.pop(); while !(last_char == None || last_char != Some('z')) { last_char = s.pop(); } - dbg!(s.len(), last_char); if let Some(c) = last_char { // Increment character in order: 0-9, then _, then a-z @@ -204,7 +192,7 @@ mod test { use compact_str::CompactString; use insta::assert_snapshot; - use crate::labels::{parse::LabelName, sanitize::Registry}; + use crate::labels::sanitize::Registry; use super::increment; @@ -259,64 +247,47 @@ mod test { fn test_sanitize_simple() { let mut reg = Registry::new(); let mut results = vec![]; - let simple = |s: &str| -> LabelName { - LabelName { - name: s.into(), - namespace: None, - local: None, - } - }; - let full = |ns: &str, name: &str, local: &str| -> LabelName { - let mut result = LabelName { - namespace: None, - name: name.into(), - local: None, - }; - if ns.len() > 0 { - result.namespace = Some(ns.into()); - } - if local.len() > 0 { - result.local = Some(local.into()); - } - result - }; let inputs = [ - simple("foo"), - full("ns1", "foo", ""), - full("ns2", "foo", ""), - full("", "foo", "thisloop"), - full("", "foo", "thatloop"), - full("", "bar", "thisloop"), - full("", "bar", "thatloop"), - simple("bar1"), - simple("bar2"), - simple("bar123"), - simple("BAR123"), - simple("bar456"), - simple("BAR456"), - simple("foo2bar"), + "foo", + "ns1~foo", + "ns2~foo", + "foo.thisloop", + "foo.thatloop", + "bar.thisloop", + "bar.thatloop", + "foo$1.thisloop", + "foo$1.thatloop", + "bar1", + "bar2", + "bar123", + "BAR123", + "bar456", + "BAR456", + "foo2bar", ]; for label in inputs { - let sanitized = reg.sanitize(&label); - assert_eq!(sanitized, reg.sanitize(&label)); + let sanitized = reg.sanitize(label); + assert_eq!(sanitized, reg.sanitize(label)); results.push(format!("{:?} => {}", &label, sanitized)); } let result = results.join("\n"); assert_snapshot!(result, @r#" - LabelName { namespace: None, name: "foo", local: None } => foo - 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_ - LabelName { namespace: None, name: "BAR123", local: None } => BAR_ - LabelName { namespace: None, name: "bar456", local: None } => bar_0 - LabelName { namespace: None, name: "BAR456", local: None } => BAR_0 - LabelName { namespace: None, name: "foo2bar", local: None } => foo_bar + "foo" => foo + "ns1~foo" => _foo + "ns2~foo" => _foo0 + "foo.thisloop" => _thisloop + "foo.thatloop" => _thatloop + "bar.thisloop" => _thisloop0 + "bar.thatloop" => _thatloop0 + "foo$1.thisloop" => _thisloop1 + "foo$1.thatloop" => _thatloop1 + "bar1" => bar1 + "bar2" => bar2 + "bar123" => bar_ + "BAR123" => BAR_ + "bar456" => bar_0 + "BAR456" => BAR_0 + "foo2bar" => foo_bar "#); } @@ -324,11 +295,7 @@ mod test { fn test_gen_anonymous() { let mut registry = Registry::new(); for letter in ["a", "e", "i"] { - registry.sanitize(&LabelName { - namespace: None, - name: letter.into(), - local: None, - }); + registry.sanitize(letter); } let result: Vec<_> = (0..10).map(|_| registry.gen_anonymous()).collect(); let result = result.join(", ");