From 05103e82fa59ac28166b69b7f96cd8f2c0b86d03 Mon Sep 17 00:00:00 2001 From: Joshua Megnauth <48846352+joshuamegnauth54@users.noreply.github.com> Date: Fri, 25 Sep 2026 16:40:31 +0000 Subject: [PATCH] unicodedata: Simplify generate_name_lookups (#8817) * Reuse helpers where appropriate * Implement a helper for data that doesn't follow the pattern of the rest of the Unicode files * Use BTreeMap, which is deduped by key and sorted instead of sorting a Vec --- crates/unicode/build.rs | 98 +++++++++++++++++++++++++---------------- 1 file changed, 61 insertions(+), 37 deletions(-) diff --git a/crates/unicode/build.rs b/crates/unicode/build.rs index 15ecc891b..e32d99d54 100644 --- a/crates/unicode/build.rs +++ b/crates/unicode/build.rs @@ -53,6 +53,10 @@ impl UnicodeLineReader { } }) } + + fn raw_iter(&mut self) -> UnicodeRawReader<'_, R> { + UnicodeRawReader(self) + } } impl UnicodeLineReader> { @@ -138,6 +142,16 @@ impl Iterator for UnicodeLineReader { } } +struct UnicodeRawReader<'u, R: BufRead>(&'u mut UnicodeLineReader); + +impl Iterator for UnicodeRawReader<'_, R> { + type Item = Box; + + fn next(&mut self) -> Option { + self.0.next_line_raw() + } +} + struct UnicodeLine { start: u32, end: u32, @@ -231,6 +245,21 @@ fn write_slice_unordered( write_slice_post(writer); } +/// Helper to write raw values for data sources that don't follow the common format. +/// +/// See: [`write_slice_display`] +fn write_slice_raw( + writer: &mut W, + static_name: &str, + array_type: &str, + values: T, + mut f: impl FnMut(&mut W, T), +) { + write_slice_pre(writer, static_name, array_type); + f(writer, values); + write_slice_post(writer); +} + fn write_slice_pre(writer: &mut impl Write, static_name: &str, array_type: &str) { write!(writer, "static {static_name}: &[{array_type}] = &[").unwrap(); } @@ -295,7 +324,6 @@ fn full_data_parsers_latest() { generate_decomp(decomp_lines); generate_algo_names(algo_names); - generate_name_lookups(); } fn generate_decomp(decomp_lines: Vec<(u32, String)>) { @@ -420,7 +448,7 @@ fn generate_algo_names(names: Vec<(u32, Box)>) { /// accepts only a single character, so named sequences stay out of /// `lookup_character`. fn generate_name_lookups() { - let mut aliases: Vec<(String, u32)> = Vec::new(); + let mut aliases = BTreeMap::new(); for line in UnicodeLineReader::from_file_name("NameAliases.txt", true) { let name = line .field( @@ -431,28 +459,12 @@ fn generate_name_lookups() { )), ) .to_ascii_uppercase(); - aliases.push((name, line.start)); + aliases.insert(name, line.start); } - aliases.sort_unstable_by(|a, b| a.0.cmp(&b.0)); - aliases.dedup_by(|a, b| a.0 == b.0); - let mut sequences: Vec<(String, String)> = Vec::new(); - let path = Path::new(env!("CARGO_MANIFEST_DIR")) - .join("unicode") - .join("latest") - .join("NamedSequences.txt"); - let file = File::open(&path).unwrap_or_else(|e| { - panic!( - "{e}: vendored Unicode data file should exist: {}", - path.display() - ) - }); - for raw in BufReader::new(file).lines() { - let raw = raw.unwrap(); - let line = raw.trim(); - if line.is_empty() || line.starts_with('#') { - continue; - } + let mut sequences = BTreeMap::new(); + let mut sequences_reader = UnicodeLineReader::from_file_name("NamedSequences.txt", true); + for line in sequences_reader.raw_iter() { let (name, codes) = line .split_once(';') .unwrap_or_else(|| panic!("NamedSequences.txt line should be `NAME; HEX HEX`: {line}")); @@ -466,25 +478,36 @@ fn generate_name_lookups() { escaped.push_str(&format!("{cp:X}")); escaped.push('}'); } - sequences.push((name, escaped)); + sequences.insert(name, escaped); } - sequences.sort_unstable_by(|a, b| a.0.cmp(&b.0)); - sequences.dedup_by(|a, b| a.0 == b.0); let mut writer = open_writer("name_lookups.rs"); - writeln!(writer, "static NAME_ALIASES: &[(&str, char)] = &[").unwrap(); - for (name, code) in &aliases { - let ch = char::from_u32(*code) - .unwrap_or_else(|| panic!("NameAliases.txt code point should be a scalar: {code:04X}")); - writeln!(writer, " (\"{name}\", '\\u{{{:X}}}'),", ch as u32).unwrap(); - } - writeln!(writer, "];").unwrap(); + write_slice_raw( + &mut writer, + "NAME_ALIASES", + "(&str, char)", + aliases, + |writer, aliases| { + for (name, code) in &aliases { + let ch = char::from_u32(*code).unwrap_or_else(|| { + panic!("NameAliases.txt code point should be a scalar: {code:04X}") + }); + writeln!(writer, "(\"{name}\", '\\u{{{:X}}}'),", ch as u32).unwrap(); + } + }, + ); - writeln!(writer, "static NAMED_SEQUENCES: &[(&str, &str)] = &[").unwrap(); - for (name, escaped) in &sequences { - writeln!(writer, " (\"{name}\", \"{escaped}\"),").unwrap(); - } - writeln!(writer, "];").unwrap(); + write_slice_raw( + &mut writer, + "NAMED_SEQUENCES", + "(&str, &str)", + sequences, + |writer, sequences| { + for (name, escaped) in &sequences { + writeln!(writer, "(\"{name}\", \"{escaped}\"),").unwrap(); + } + }, + ); } /// Drive parsers that require the full 3.2.0 data. @@ -864,6 +887,7 @@ fn drive_parsers() { bidi_class_3_2, binary_props_3_2, combining_class_3_2, + generate_name_lookups, ]; let mut handles = Vec::with_capacity(parsers.len() + 2);