run-rs 0.6.10

Run a subset of Rust as an interpreted script
//! Generate `BuiltinId` from `src/interpreter/method_names.txt` at build time.
//!
//! The table is one method name per line, sorted, with a trailing `mut` on a
//! method that mutates its receiver in place. Every bridge dispatches on the
//! generated enum, so a method name exists in exactly one place and a typo in
//! a bridge arm is a compile error instead of a dead arm.

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
}

/// `split_first` becomes `SplitFirst`, the variant name of a method.
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");

    // The names in variant order, so `NAMES[id as usize]` is the name and a
    // binary search over the sorted table resolves a name. `Other` is the
    // last variant and owns the empty name.
    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
}