use std::collections::BTreeMap;
use std::path::Path;
pub fn read_paths(path: &Path) -> Vec<String> {
let text = std::fs::read_to_string(path)
.unwrap_or_else(|e| panic!("cannot read {}: {e}", path.display()));
let mut rows: Vec<String> = Vec::new();
for (index, line) in text.lines().enumerate() {
let name = line.trim();
if name.is_empty() {
continue;
}
if let Some(previous) = rows.last()
&& previous.as_str() >= name
{
panic!(
"{}:{}: `{name}` is out of order, the table is sorted and unique",
path.display(),
index + 1
);
}
rows.push(name.to_string());
}
rows
}
pub fn variant_names(rows: &[String]) -> Vec<String> {
let mut names: Vec<String> = rows.iter().map(|row| camel_path(row)).collect();
let mut by_name: BTreeMap<String, Vec<usize>> = BTreeMap::new();
for (index, name) in names.iter().enumerate() {
by_name.entry(name.clone()).or_default().push(index);
}
for (name, indices) in by_name {
if indices.len() == 1 {
continue;
}
let mut constants = 0;
for &index in &indices {
if is_constant(&rows[index]) {
names[index] = format!("{name}Const");
constants += 1;
}
}
if constants != indices.len() - 1 {
let rows: Vec<&str> = indices.iter().map(|&i| rows[i].as_str()).collect();
panic!("path table entries {rows:?} all name the variant `{name}`");
}
}
names
}
fn is_constant(row: &str) -> bool {
let last = row.rsplit("::").next().unwrap_or(row);
!last.chars().any(char::is_lowercase)
}
fn camel_path(row: &str) -> String {
let mut out = String::with_capacity(row.len());
for segment in row.trim_start_matches("::").split("::") {
let all_caps = !segment.chars().any(char::is_lowercase);
for part in segment.split('_') {
let mut chars = part.chars();
let Some(first) = chars.next() else {
continue;
};
out.extend(first.to_uppercase());
if all_caps {
out.push_str(&chars.as_str().to_lowercase());
} else {
out.push_str(chars.as_str());
}
}
}
out
}
pub fn generate(rows: &[String]) -> String {
let names = variant_names(rows);
let max_segments = rows
.iter()
.map(|row| row.split("::").count())
.max()
.unwrap_or(1);
let mut out = String::new();
out.push_str("// Generated by build.rs from src/interpreter/path_names.txt. Do not edit.\n\n");
out.push_str("#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)]\n");
out.push_str("pub enum PathId {\n");
for name in &names {
out.push_str(&format!(" {name},\n"));
}
out.push_str(" /// A path the table does not list, a user item or a method reference.\n");
out.push_str(" Other,\n}\n\n");
out.push_str("const PATHS: &[&str] = &[\n");
for row in rows {
out.push_str(&format!(" {row:?},\n"));
}
out.push_str("];\n\n");
out.push_str("const PATH_IDS: &[PathId] = &[\n");
for name in &names {
out.push_str(&format!(" PathId::{name},\n"));
}
out.push_str("];\n\n");
out.push_str(&format!("const MAX_SEGMENTS: usize = {max_segments};\n\n"));
out.push_str("impl PathId {\n");
out.push_str(" /// The longest table entry that is a suffix of the path, or `Other`.\n");
out.push_str(" pub fn resolve(segs: &[String]) -> PathId {\n");
out.push_str(" let mut key = String::new();\n");
out.push_str(" for count in (1..=segs.len().min(MAX_SEGMENTS)).rev() {\n");
out.push_str(" key.clear();\n");
out.push_str(
" for (index, seg) in segs[segs.len() - count..].iter().enumerate() {\n",
);
out.push_str(" if index > 0 {\n key.push_str(\"::\");\n }\n");
out.push_str(" key.push_str(seg);\n }\n");
out.push_str(" if let Ok(index) = PATHS.binary_search(&key.as_str()) {\n");
out.push_str(" return PATH_IDS[index];\n }\n }\n");
out.push_str(" PathId::Other\n }\n\n");
out.push_str(" /// The table spelling, `fs::read_to_string`, empty for `Other`.\n");
out.push_str(" pub fn path(self) -> &'static str {\n");
out.push_str(" PATHS.get(self as usize).copied().unwrap_or(\"\")\n }\n\n");
out.push_str(" /// The last segment, `read_to_string`.\n");
out.push_str(" pub fn name(self) -> &'static str {\n");
out.push_str(" let path = self.path();\n");
out.push_str(" path.rsplit_once(\"::\").map_or(path, |(_, name)| name)\n }\n\n");
out.push_str(" /// The segment before the last, `fs`, empty for a bare name.\n");
out.push_str(" pub fn namespace(self) -> &'static str {\n");
out.push_str(" let path = self.path();\n");
out.push_str(" let head = path.rsplit_once(\"::\").map_or(\"\", |(head, _)| head);\n");
out.push_str(
" head.rsplit_once(\"::\").map_or(head, |(_, namespace)| namespace)\n }\n}\n\n",
);
out.push_str("impl std::fmt::Display for PathId {\n");
out.push_str(" fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {\n");
out.push_str(" f.write_str(self.path())\n }\n}\n");
out
}