use std::fmt::Write as _;
use std::path::Path;
pub struct MethodRow {
pub name: String,
pub mutates: bool,
}
pub fn read_table(path: &Path) -> Vec<MethodRow> {
let text = std::fs::read_to_string(path)
.unwrap_or_else(|e| panic!("cannot read {}: {e}", path.display()));
let mut rows = Vec::new();
for (index, line) in text.lines().enumerate() {
let mut words = line.split_whitespace();
let Some(name) = words.next() else {
continue;
};
let mutates = match words.next() {
None => false,
Some("mut") => true,
Some(other) => panic!(
"{}:{}: unknown flag `{other}`, only `mut` is allowed",
path.display(),
index + 1
),
};
if let Some(previous) = rows.last().map(|row: &MethodRow| row.name.as_str())
&& previous >= name
{
panic!(
"{}:{}: `{name}` is out of order, the table is sorted and unique",
path.display(),
index + 1
);
}
rows.push(MethodRow {
name: name.to_string(),
mutates,
});
}
rows
}
pub fn camel(name: &str) -> String {
let mut out = String::with_capacity(name.len());
for part in name.split('_') {
let mut chars = part.chars();
if let Some(first) = chars.next() {
out.extend(first.to_uppercase());
out.push_str(chars.as_str());
}
}
out
}
pub fn generate(rows: &[MethodRow]) -> String {
let mut out = String::new();
out.push_str(
"// Generated by build.rs from src/interpreter/method_names.txt. Do not edit.\n\n",
);
out.push_str("#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)]\n");
out.push_str("pub enum BuiltinId {\n");
for row in rows {
let _ = writeln!(out, " {},", camel(&row.name));
}
out.push_str(" /// A name the table does not list, a user method or an error.\n");
out.push_str(" Other,\n}\n\n");
out.push_str("const NAMES: &[&str] = &[\n");
for row in rows {
let _ = writeln!(out, " {:?},", row.name);
}
out.push_str(" \"\",\n];\n\n");
out.push_str("const IDS: &[BuiltinId] = &[\n");
for row in rows {
let _ = writeln!(out, " BuiltinId::{},", camel(&row.name));
}
out.push_str("];\n\n");
out.push_str("impl BuiltinId {\n");
out.push_str(" pub fn resolve(name: &str) -> BuiltinId {\n");
out.push_str(" match IDS.binary_search_by(|id| NAMES[*id as usize].cmp(name)) {\n");
out.push_str(" Ok(index) => IDS[index],\n");
out.push_str(" Err(_) => BuiltinId::Other,\n }\n }\n\n");
out.push_str(" pub fn name(self) -> &'static str {\n");
out.push_str(" NAMES[self as usize]\n }\n\n");
out.push_str(" /// Whether the method mutates its receiver in place.\n");
out.push_str(" pub fn mutates(self) -> bool {\n matches!(\n self,\n");
let mutating: Vec<String> = rows
.iter()
.filter(|row| row.mutates)
.map(|row| format!("BuiltinId::{}", camel(&row.name)))
.collect();
let _ = writeln!(out, " {}", mutating.join("\n | "));
out.push_str(" )\n }\n}\n\n");
out.push_str("impl std::fmt::Display for BuiltinId {\n");
out.push_str(" fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {\n");
out.push_str(" f.write_str(self.name())\n }\n}\n");
out
}