js.rs

83.9 kB · rust · 2327 lines

1use crate::disk::save;2use crate::model::{3    Const, Cross, Function, Manifest, Result, SelfKind, Ty, Type, TypeCross, TypeKind,4};5use std::collections::{BTreeMap, BTreeSet};6use std::fmt::Write as _;7use std::path::Path;89const TYPED: &[&str] = &[10    "u8", "u16", "u32", "i8", "i16", "i32", "f32", "f64", "usize", "u64", "i64",11];1213const NATIVE_ITEM: &[&str] = &[14    "u8", "u16", "u32", "i8", "i16", "i32", "f32", "f64", "usize",15];1617const WORDS: &[&str] = &[18    "void",19    "new",20    "class",21    "function",22    "default",23    "delete",24    "in",25    "var",26    "let",27    "const",28    "export",29    "import",30    "switch",31    "case",32    "for",33    "if",34    "else",35    "this",36    "super",37    "with",38    "yield",39    "enum",40    "await",41    "typeof",42    "instanceof",43    "return",44    "try",45    "catch",46    "finally",47    "throw",48    "while",49    "do",50    "break",51    "continue",52    "debugger",53    "static",54    "interface",55    "package",56    "private",57    "protected",58    "public",59    "implements",60    "arguments",61    "eval",62    "null",63    "true",64    "false",65    "extends",66    "as",67    "crate",68    "extern",69    "fn",70    "impl",71    "loop",72    "match",73    "mod",74    "move",75    "mut",76    "pub",77    "ref",78    "self",79    "struct",80    "trait",81    "type",82    "unsafe",83    "use",84    "where",85    "async",86    "dyn",87    "box",88    "macro",89    "gen",90];9192const RNG_METHODS: &[&str] = &[93    "new",94    "unit",95    "below",96    "range",97    "boolean",98    "chance",99    "sample_indices",100    "choice",101    "shuffle",102    "__state",103    "__restore",104];105106const JS_RESERVED: &[&str] = &[107    "new",108    "void",109    "class",110    "function",111    "default",112    "delete",113    "in",114    "var",115    "let",116    "const",117    "export",118    "import",119    "switch",120    "case",121    "for",122    "if",123    "else",124    "this",125    "super",126    "with",127    "yield",128    "enum",129    "await",130    "typeof",131    "instanceof",132    "return",133    "try",134    "catch",135    "finally",136    "throw",137    "while",138    "do",139    "break",140    "continue",141    "debugger",142    "static",143    "interface",144    "package",145    "private",146    "protected",147    "public",148    "implements",149    "null",150    "true",151    "false",152    "extends",153];154155const HAND_RS: &str =156    "#[path = \"../../../hand/hand.rs\"]\nmod crossing;\n\npub use crossing::*;\n";157158const ALLOWS: &str = "#![allow(non_camel_case_types, non_snake_case, clippy::too_many_arguments)]";159160// FILES161162pub fn write(manifest: &Manifest, root: &Path) -> Result<()> {163    let names =164        std::fs::read_to_string(root.join("pkgs/bridge/units.txt")).map_err(|e| e.to_string())?;165    let names: Vec<&str> = names166        .lines()167        .map(str::trim)168        .filter(|l| !l.is_empty())169        .collect();170    let js = root.join("pkgs/mrlyjs");171    for name in &names {172        let unit = Unit::new(manifest, name);173        let dir = js.join("units").join(name);174        save(175            &dir.join("Cargo.toml"),176            &cargo_toml(name, &manifest.version),177        )?;178        save(&dir.join("src/hand.rs"), HAND_RS)?;179        save(&dir.join("src/lib.rs"), &unit.lib_rs()?)?;180        save(&js.join(format!("{name}.js")), &unit.wrapper_js())?;181        save(&js.join(format!("{name}.d.ts")), &unit.types_dts()?)?;182    }183    save(&js.join("manifest.test.js"), MANIFEST_TEST)?;184    Ok(())185}186187fn cargo_toml(name: &str, version: &str) -> String {188    let what = if name == "all" {189        "every module of mrlyrs".to_string()190    } else {191        format!("the {name} module of mrlyrs")192    };193    format!(194        "[package]\nname = \"mrlyjs_{name}\"\nversion = \"{version}\"\nedition.workspace = true\nlicense.workspace = true\nrepository.workspace = true\ndescription = \"The {name} wasm unit of mrlyjs: {what} in a browser.\"\npublish = false\n\n[lib]\ncrate-type = [\"cdylib\"]\n\n[dependencies]\nmrlyrs.workspace = true\nwasm-bindgen = \"0.2\"\nserde-wasm-bindgen = \"0.6\"\njs-sys = \"0.3\"\nserde = {{ version = \"1\", features = [\"derive\"] }}\nserde_json = \"1\"\n\n[package.metadata.wasm-pack.profile.release]\nwasm-opt = false\n"195    )196}197198// UNIT199200struct Unit<'a> {201    name: &'a str,202    manifest: &'a Manifest,203    types: BTreeMap<&'a str, &'a Type>,204    reserved: BTreeSet<String>,205}206207#[derive(Clone, Copy)]208enum Owner<'a> {209    Free,210    Hand(&'a Type),211    Class(&'a Type),212    Holder(&'a Type),213}214215#[derive(Clone, Copy)]216struct Cx<'a> {217    dim: Option<u8>,218    fname: &'a str,219}220221struct ParamPlan {222    name: String,223    sig: String,224    pre: Vec<String>,225    arg: String,226    post: Vec<String>,227}228229struct RetPlan {230    sig: String,231    done: String,232}233234impl<'a> Unit<'a> {235    fn new(manifest: &'a Manifest, name: &'a str) -> Unit<'a> {236        let mut unit = Unit {237            name,238            manifest,239            types: manifest240                .types241                .iter()242                .map(|t| (t.path.as_str(), t))243                .collect(),244            reserved: BTreeSet::new(),245        };246        unit.reserved = unit.symbols();247        unit248    }249250    fn symbols(&self) -> BTreeSet<String> {251        let mut out = BTreeSet::new();252        for method in RNG_METHODS {253            out.insert(format!("rng_{method}"));254        }255        for t in self.classes().into_iter().chain(self.holders()) {256            let lower = self.local(&t.path).to_lowercase();257            for f in self.functions() {258                if f.owner.as_deref() == Some(t.path.as_str()) {259                    out.insert(format!("{lower}_{}", f.name));260                }261            }262            for extra in ["from", "toJSON", "new"] {263                out.insert(format!("{lower}_{extra}"));264            }265        }266        out267    }268269    fn export(&self, name: &str) -> String {270        let mut out = name.to_string();271        while self.reserved.contains(&out) {272            out.push('_');273        }274        out275    }276277    fn holds(&self, path: &str) -> bool {278        self.name == "all" || path.split("::").next() == Some(self.name)279    }280281    fn rel<'p>(&self, path: &'p str) -> &'p str {282        if self.name == "all" {283            return path;284        }285        match path.strip_prefix(self.name) {286            Some("") => "",287            Some(rest) => rest.strip_prefix("::").unwrap_or(path),288            None => path,289        }290    }291292    fn local(&self, path: &str) -> String {293        self.rel(path).replace("::", "_")294    }295296    fn ty(&self, path: &str) -> Option<&'a Type> {297        self.types.get(path).copied()298    }299300    fn functions(&self) -> Vec<&'a Function> {301        self.manifest302            .functions303            .iter()304            .filter(|f| f.cross == Cross::Ok && self.holds(&f.module))305            .collect()306    }307308    fn groups(&self) -> Vec<Vec<&'a Function>> {309        let mut out: Vec<Vec<&'a Function>> = vec![];310        for f in self.functions() {311            match out.last_mut() {312                Some(group) if group[0].path == f.path => group.push(f),313                _ => out.push(vec![f]),314            }315        }316        out317    }318319    fn consts(&self) -> Vec<&'a Const> {320        self.manifest321            .consts322            .iter()323            .filter(|c| c.cross == Cross::Ok && self.holds(&c.path))324            .collect()325    }326327    fn classes(&self) -> Vec<&'a Type> {328        self.manifest329            .types330            .iter()331            .filter(|t| t.cross == TypeCross::Class && self.holds(&t.path))332            .collect()333    }334335    fn holders(&self) -> Vec<&'a Type> {336        let owners: BTreeSet<&str> = self337            .functions()338            .iter()339            .filter_map(|f| f.owner.as_deref())340            .collect();341        self.manifest342            .types343            .iter()344            .filter(|t| {345                matches!(t.cross, TypeCross::Plain | TypeCross::Enum { .. })346                    && self.holds(&t.path)347                    && owners.contains(t.path.as_str())348            })349            .collect()350    }351352    fn owner_of(&self, f: &Function) -> Owner<'a> {353        let Some(path) = f.owner.as_deref() else {354            return Owner::Free;355        };356        match self.ty(path).map(|t| (t, &t.cross)) {357            Some((t, TypeCross::Hand { .. })) => Owner::Hand(t),358            Some((t, TypeCross::Class)) => Owner::Class(t),359            Some((t, TypeCross::Plain | TypeCross::Enum { .. })) => Owner::Holder(t),360            _ => Owner::Free,361        }362    }363364    fn hand_name(&self, t: &Type) -> &'static str {365        match &t.cross {366            TypeCross::Hand { name } => match name.as_str() {367                "Tensor" => "Tensor",368                "Cell" => "Cell",369                "CellNd" => "CellNd",370                "Cell6d" => "Cell6d",371                "Image" => "Image",372                "Color" => "Color",373                "Code" => "Code",374                _ => "Rng",375            },376            _ => "Rng",377        }378    }379380    // RUST TYPES381382    fn rust_ty(&self, ty: &Ty, cx: Cx) -> Result<String> {383        Ok(match ty {384            Ty::Unit => "()".into(),385            Ty::Scalar { name } => name.clone(),386            Ty::Str | Ty::String => "String".into(),387            Ty::U128 => "u128".into(),388            Ty::I128 => "i128".into(),389            Ty::Code => "mrlyrs::math::bang::Code".into(),390            Ty::Json => "mrlyrs::core::Json".into(),391            Ty::Vec { item } | Ty::Slice { item, .. } | Ty::Set { item } => {392                format!("Vec<{}>", self.rust_ty(item, cx)?)393            }394            Ty::Option { item } => format!("Option<{}>", self.rust_ty(item, cx)?),395            Ty::Tuple { items } => {396                let parts: Result<Vec<String>> =397                    items.iter().map(|t| self.rust_ty(t, cx)).collect();398                format!("({})", parts?.join(", "))399            }400            Ty::Array { item, len } => format!("[{}; {len}]", self.rust_ty(item, cx)?),401            Ty::Ref { item, .. } | Ty::Result { item } => self.rust_ty(item, cx)?,402            Ty::Map { key, value } => format!(403                "std::collections::HashMap<{}, {}>",404                self.rust_ty(key, cx)?,405                self.rust_ty(value, cx)?406            ),407            Ty::Hand { name, dim } => match name.as_str() {408                "Tensor" => "mrlyrs::core::Tensor".into(),409                "Cell" => "mrlyrs::core::Cell".into(),410                "CellNd" => match dim.or(cx.dim) {411                    Some(2) => "mrlyrs::math::two::Cell2d".into(),412                    Some(3) => "mrlyrs::math::three::Cell3d".into(),413                    _ => return Err(format!("{}: a cell without a dimension", cx.fname)),414                },415                "Cell6d" => "mrlyrs::math::six::Cell6d".into(),416                "Image" => "mrlyrs::core::Image".into(),417                "Color" => "mrlyrs::core::Color".into(),418                "Code" => "mrlyrs::math::bang::Code".into(),419                "Rng" => "mrlyrs::core::Rng".into(),420                other => return Err(format!("{}: unknown hand type {other}", cx.fname)),421            },422            Ty::Plain { path } => {423                let generic = self.ty(path).is_some_and(|t| t.const_generic);424                if generic {425                    format!("mrlyrs::{path}<{}>", suffix_dim(cx.fname))426                } else {427                    format!("mrlyrs::{path}")428                }429            }430            Ty::Enum { path } | Ty::Class { path, .. } => format!("mrlyrs::{path}"),431            Ty::Opaque { path } => return Err(format!("{}: {path} cannot cross", cx.fname)),432            Ty::Unknown { text } => return Err(format!("{}: {text} cannot cross", cx.fname)),433        })434    }435436    fn is_copy(&self, ty: &Ty) -> bool {437        match ty {438            Ty::Unit | Ty::Scalar { .. } | Ty::U128 | Ty::I128 | Ty::Code => true,439            Ty::Hand { name, .. } => name == "Color" || name == "Code",440            Ty::Tuple { items } => items.iter().all(|t| self.is_copy(t)),441            Ty::Array { item, .. } | Ty::Option { item } => self.is_copy(item),442            Ty::Plain { path } | Ty::Enum { path } | Ty::Class { path, .. } => {443                self.ty(path).is_some_and(|t| derives(t, "Copy"))444            }445            _ => false,446        }447    }448449    // CROSSINGS IN450451    fn cross_in(&self, ty: &Ty, e: &str, cx: Cx, depth: usize) -> Result<String> {452        if pure_in(ty) {453            return Ok(format!("hand::from_js::<{}>({e})?", self.rust_ty(ty, cx)?));454        }455        let x = format!("x{depth}");456        Ok(match ty {457            Ty::Scalar { name } => match name.as_str() {458                "u64" => format!("hand::u64_from_js({e})?"),459                "i64" => format!("hand::i64_from_js({e})?"),460                "bool" => format!("hand::bool_from_js({e})?"),461                "char" => format!("hand::char_from_js({e})?"),462                other => format!("(hand::number_from_js({e})? as {other})"),463            },464            Ty::Str | Ty::String => format!("hand::string_from_js({e})?"),465            Ty::U128 => format!("hand::u128_from_js({e})?"),466            Ty::I128 => format!("hand::i128_from_js({e})?"),467            Ty::Code => format!("hand::code_from_js({e})?"),468            Ty::Json => format!("hand::json_from_js({e})?"),469            Ty::Vec { item } | Ty::Slice { item, .. } | Ty::Set { item } => format!(470                "hand::list_from_js({e}, {})?",471                closure(&x, ok(self.cross_in(item, &x, cx, depth + 1)?))472            ),473            Ty::Option { item } => format!(474                "hand::option_from_js({e}, {})?",475                closure(&x, ok(self.cross_in(item, &x, cx, depth + 1)?))476            ),477            Ty::Tuple { items } => {478                let mut parts = vec![];479                for (i, t) in items.iter().enumerate() {480                    parts.push(self.cross_in(481                        t,482                        &format!("&hand::item({e}, {i})?"),483                        cx,484                        depth + 1,485                    )?);486                }487                format!("({})", parts.join(", "))488            }489            Ty::Array { item, len } => format!(490                "hand::array_from_js::<_, {len}>({e}, {})?",491                closure(&x, ok(self.cross_in(item, &x, cx, depth + 1)?))492            ),493            Ty::Ref { item, .. } | Ty::Result { item } => self.cross_in(item, e, cx, depth)?,494            Ty::Map { key, value } => format!(495                "hand::map_from_js::<{}, _>({e}, {})?",496                self.rust_ty(key, cx)?,497                closure(&x, ok(self.cross_in(value, &x, cx, depth + 1)?))498            ),499            Ty::Hand { name, dim } => match name.as_str() {500                "Tensor" => format!("hand::tensor_from_js({e})?"),501                "Cell" => format!("hand::cell_from_js({e})?"),502                "CellNd" => match dim.or(cx.dim) {503                    Some(2) => format!("hand::cell2d_from_js({e})?"),504                    Some(3) => format!("hand::cell3d_from_js({e})?"),505                    _ => return Err(format!("{}: a cell without a dimension", cx.fname)),506                },507                "Cell6d" => format!("hand::cell6d_from_js({e})?"),508                "Image" => format!("hand::image_from_js({e})?"),509                "Color" => format!("hand::color_from_js({e})?"),510                "Code" => format!("hand::code_from_js({e})?"),511                other => return Err(format!("{}: {other} cannot cross in by value", cx.fname)),512            },513            Ty::Class { .. } => format!(514                "hand::from_js::<{}>(&hand::plain({e})?)?",515                self.rust_ty(ty, cx)?516            ),517            Ty::Unit | Ty::Plain { .. } | Ty::Enum { .. } => {518                format!("hand::from_js::<{}>({e})?", self.rust_ty(ty, cx)?)519            }520            Ty::Opaque { path } => return Err(format!("{}: {path} cannot cross", cx.fname)),521            Ty::Unknown { text } => return Err(format!("{}: {text} cannot cross", cx.fname)),522        })523    }524525    // CROSSINGS OUT526527    fn cross_out(&self, ty: &Ty, e: &str, cx: Cx, depth: usize) -> Result<String> {528        if pure_out(ty) {529            return Ok(format!("hand::to_js({e})?"));530        }531        let x = format!("x{depth}");532        Ok(match ty {533            Ty::Unit => "JsValue::UNDEFINED".into(),534            Ty::Scalar { name } => match name.as_str() {535                "u64" | "i64" => format!("JsValue::from({})", deref(e)),536                "bool" => format!("JsValue::from_bool({})", deref(e)),537                "char" => format!("JsValue::from_str(&{}.to_string())", place(e)),538                _ => format!("JsValue::from_f64({} as f64)", deref(e)),539            },540            Ty::Str | Ty::String => format!("JsValue::from_str({e})"),541            Ty::U128 | Ty::I128 => format!("JsValue::from_str(&{}.to_string())", place(e)),542            Ty::Code => format!("JsValue::from_str(&hand::code_to_js({}))", deref(e)),543            Ty::Json => format!("hand::json_to_js({e})?"),544            Ty::Vec { item } | Ty::Slice { item, .. } | Ty::Array { item, .. }545                if typed_scalar(item) =>546            {547                format!("hand::typed(&({})[..])", deref(e))548            }549            Ty::Vec { item }550            | Ty::Slice { item, .. }551            | Ty::Array { item, .. }552            | Ty::Set { item } => {553                format!(554                    "hand::list_to_js({e}, {})?",555                    closure(&x, ok(self.cross_out(item, &x, cx, depth + 1)?))556                )557            }558            Ty::Option { item } => match &**item {559                Ty::Ref { item: inner, .. } => format!(560                    "hand::option_to_js({}, {})?",561                    deref(e),562                    closure(&x, ok(self.cross_out(inner, &x, cx, depth + 1)?))563                ),564                _ => format!(565                    "hand::option_to_js({}.as_ref(), {})?",566                    place(e),567                    closure(&x, ok(self.cross_out(item, &x, cx, depth + 1)?))568                ),569            },570            Ty::Tuple { items } => {571                let mut parts = vec![];572                for (i, t) in items.iter().enumerate() {573                    parts.push(self.cross_out(t, &format!("&{}.{i}", place(e)), cx, depth + 1)?);574                }575                format!("hand::tuple_to_js(&[{}])", parts.join(", "))576            }577            Ty::Ref { item, .. } | Ty::Result { item } => {578                self.cross_out(item, &format!("({})", deref(e)), cx, depth)?579            }580            Ty::Map { value, .. } => format!(581                "hand::map_to_js({e}, {})?",582                closure(&x, ok(self.cross_out(value, &x, cx, depth + 1)?))583            ),584            Ty::Hand { name, dim } => match name.as_str() {585                "Tensor" => format!("hand::tensor_to_js({e})?"),586                "Cell" => format!("hand::cell_to_js({e})?"),587                "CellNd" => match dim.or(cx.dim) {588                    Some(2) => format!("hand::cell2d_to_js({e})?"),589                    Some(3) => format!("hand::cell3d_to_js({e})?"),590                    _ => return Err(format!("{}: a cell without a dimension", cx.fname)),591                },592                "Cell6d" => format!("hand::cell6d_to_js({e})?"),593                "Image" => format!("hand::image_to_js({e})?"),594                "Color" => format!("hand::color_to_js({})", deref(e)),595                "Code" => format!("JsValue::from_str(&hand::code_to_js({}))", deref(e)),596                _ => format!("JsValue::from(hand::Rng::wrap({}.clone()))", place(e)),597            },598            Ty::Class { path, .. } if self.holds(path) => {599                let inner = if self.is_copy(ty) {600                    deref(e)601                } else {602                    format!("{}.clone()", place(e))603                };604                format!("JsValue::from({} {{ inner: {inner} }})", self.local(path))605            }606            Ty::Class { .. } | Ty::Plain { .. } | Ty::Enum { .. } => format!("hand::to_js({e})?"),607            Ty::Opaque { path } => return Err(format!("{}: {path} cannot cross", cx.fname)),608            Ty::Unknown { text } => return Err(format!("{}: {text} cannot cross", cx.fname)),609        })610    }611612    fn ret_plan(&self, ty: &Ty, cx: Cx) -> Result<RetPlan> {613        let plan = |sig: &str, done: String| RetPlan {614            sig: sig.to_string(),615            done,616        };617        Ok(match ty {618            Ty::Unit => plan("()", "Ok(())".into()),619            Ty::Result { item } => self.ret_plan(item, cx)?,620            Ty::Scalar { name } => plan(name, "Ok(value)".into()),621            Ty::String => plan("String", "Ok(value)".into()),622            Ty::Str => plan("String", "Ok(value.to_string())".into()),623            Ty::Vec { item } if typed_scalar(item) => plan(624                &format!("Vec<{}>", self.rust_ty(item, cx)?),625                "Ok(value)".into(),626            ),627            Ty::Slice { item, .. } | Ty::Array { item, .. } if typed_scalar(item) => plan(628                &format!("Vec<{}>", self.rust_ty(item, cx)?),629                "Ok(value.to_vec())".into(),630            ),631            Ty::Class { path, .. } if self.holds(path) => {632                let ident = self.local(path);633                plan(&ident, format!("Ok({ident} {{ inner: value }})"))634            }635            Ty::Option { item } => match &**item {636                Ty::Class { path, .. } if self.holds(path) => {637                    let ident = self.local(path);638                    plan(639                        &format!("Option<{ident}>"),640                        format!("Ok(value.map(|inner| {ident} {{ inner }}))"),641                    )642                }643                _ => plan("JsValue", ok(self.cross_out(ty, "&value", cx, 1)?)),644            },645            Ty::Hand { name, .. } if name == "Rng" => {646                plan("hand::Rng", "Ok(hand::Rng::wrap(value))".into())647            }648            Ty::Ref { item, .. } => match &**item {649                Ty::Slice { item: inner, .. } | Ty::Array { item: inner, .. }650                    if typed_scalar(inner) =>651                {652                    plan(653                        &format!("Vec<{}>", self.rust_ty(inner, cx)?),654                        "Ok(value.to_vec())".into(),655                    )656                }657                Ty::Str => plan("String", "Ok(value.to_string())".into()),658                _ => plan("JsValue", ok(self.cross_out(item, "value", cx, 1)?)),659            },660            Ty::Slice { .. } => plan("JsValue", ok(self.cross_out(ty, "value", cx, 1)?)),661            _ => plan("JsValue", ok(self.cross_out(ty, "&value", cx, 1)?)),662        })663    }664665    // PARAMETERS666667    fn plan_param(&self, name: &str, ty: &Ty, cx: Cx, extra: bool) -> Result<ParamPlan> {668        let n = ident(name);669        let js = |pre: Vec<String>, arg: String, post: Vec<String>| ParamPlan {670            name: n.clone(),671            sig: "JsValue".into(),672            pre,673            arg,674            post,675        };676        let native = |sig: String, arg: String| ParamPlan {677            name: n.clone(),678            sig,679            pre: vec![],680            arg,681            post: vec![],682        };683        let read = |inner: &Ty| -> Result<String> {684            let converted = self.cross_in(inner, &format!("&{n}"), cx, 1)?;685            Ok(if extra {686                let want = format!("a {}d cell wants {name}.", cx.dim.unwrap_or(0));687                format!("let {n} = if {n}.is_undefined() {{ return Err(hand::refuse(\"{want}\")); }} else {{ {converted} }};")688            } else {689                format!("let {n} = {converted};")690            })691        };692        let owned = |inner: &Ty, arg: String| -> Result<ParamPlan> {693            Ok(js(vec![read(inner)?], arg, vec![]))694        };695        if !extra {696            match ty {697                Ty::Scalar { name } if !is_wide(name) => {698                    return Ok(native(name.clone(), n.clone()))699                }700                Ty::Str => return Ok(native("&str".into(), n.clone())),701                Ty::String => return Ok(native("String".into(), n.clone())),702                Ty::Slice { item, .. } if native_item(item) => {703                    return Ok(native(format!("&[{}]", self.rust_ty(item, cx)?), n.clone()))704                }705                Ty::Vec { item } if native_item(item) => {706                    return Ok(native(707                        format!("Vec<{}>", self.rust_ty(item, cx)?),708                        n.clone(),709                    ))710                }711                Ty::Class { path, .. } if self.holds(path) => {712                    let taken = if self.is_copy(ty) {713                        format!("{n}.inner")714                    } else {715                        format!("{n}.inner.clone()")716                    };717                    return Ok(native(format!("&{}", self.local(path)), taken));718                }719                Ty::Ref { mutable, item, .. } => match &**item {720                    Ty::Class { path, .. } if self.holds(path) => {721                        let ident = self.local(path);722                        return Ok(if *mutable {723                            native(format!("&mut {ident}"), format!("&mut {n}.inner"))724                        } else {725                            native(format!("&{ident}"), format!("&{n}.inner"))726                        });727                    }728                    Ty::Hand { name, .. } if name == "Rng" && *mutable => {729                        return Ok(native("&mut hand::Rng".into(), format!("{n}.stream()")))730                    }731                    _ => {}732                },733                _ => {}734            }735        }736        Ok(match ty {737            Ty::Ref {738                mutable: true,739                item,740                ..741            } => match &**item {742                Ty::Hand { name, .. } if name == "Tensor" || name == "Cell" => {743                    let back = if name == "Tensor" {744                        "tensor_into_js"745                    } else {746                        "cell_into_js"747                    };748                    let converted = self.cross_in(item, &format!("&{n}"), cx, 1)?;749                    js(750                        vec![format!("let mut {n}_value = {converted};")],751                        format!("&mut {n}_value"),752                        vec![format!("hand::{back}(&{n}, &{n}_value)?;")],753                    )754                }755                _ => {756                    return Err(format!(757                        "{}: {name} mutates a value that cannot cross",758                        cx.fname759                    ))760                }761            },762            Ty::Ref { item, .. } => owned(item, format!("&{n}"))?,763            Ty::Slice { item, .. } => match self.view(item, &n, cx)? {764                Some(seen) => {765                    let mut pre = vec![read(ty)?];766                    pre.push(seen);767                    js(pre, format!("&{n}_view"), vec![])768                }769                None => owned(ty, format!("&{n}"))?,770            },771            Ty::Option { item } => match &**item {772                Ty::Ref {773                    mutable: true,774                    item: inner,775                    ..776                } if is_rng(inner) => js(777                    vec![format!("let mut {n}_stream = hand::stream_from_js(&{n})?;")],778                    format!("{n}_stream.as_mut()"),779                    vec![format!(780                        "if let Some(stream) = &{n}_stream {{ hand::stream_to_js(&{n}, stream)?; }}"781                    )],782                ),783                Ty::Ref { item: inner, .. } if matches!(**inner, Ty::Slice { .. }) => {784                    owned(ty, format!("{n}.as_deref()"))?785                }786                Ty::Ref { .. } => owned(ty, format!("{n}.as_ref()"))?,787                Ty::Slice { .. } => owned(ty, format!("{n}.as_deref()"))?,788                _ => owned(ty, n.clone())?,789            },790            _ => owned(ty, n.clone())?,791        })792    }793794    fn view(&self, item: &Ty, n: &str, cx: Cx) -> Result<Option<String>> {795        Ok(match item {796            Ty::Str => Some(format!(797                "let {n}_view: Vec<&str> = {n}.iter().map(String::as_str).collect();"798            )),799            Ty::Slice { item: inner, .. } => Some(format!(800                "let {n}_view: Vec<&[{}]> = {n}.iter().map(Vec::as_slice).collect();",801                self.rust_ty(inner, cx)?802            )),803            Ty::Tuple { items } if items.iter().any(|t| matches!(t, Ty::Str)) => {804                let names: Vec<String> = (0..items.len()).map(|i| format!("a{i}")).collect();805                let parts: Vec<String> = items806                    .iter()807                    .zip(&names)808                    .map(|(t, a)| match t {809                        Ty::Str => format!("{a}.as_str()"),810                        Ty::Scalar { .. } => format!("*{a}"),811                        _ => format!("{a}.clone()"),812                    })813                    .collect();814                let mut tys = vec![];815                for t in items {816                    tys.push(match t {817                        Ty::Str => "&str".to_string(),818                        other => self.rust_ty(other, cx)?,819                    });820                }821                Some(format!(822                    "let {n}_view: Vec<({})> = {n}.iter().map(|({})| ({})).collect();",823                    tys.join(", "),824                    names.join(", "),825                    parts.join(", ")826                ))827            }828            _ => None,829        })830    }831832    fn self_param(&self, f: &Function, owner: &Type) -> Result<Option<(String, Ty)>> {833        let Some(kind) = f.self_kind else {834            return Ok(None);835        };836        let name = self.hand_name(owner);837        let inner = Ty::Hand {838            name: name.to_string(),839            dim: None,840        };841        let taken = f.params.iter().any(|p| p.name == hand_self(name));842        let label = if taken {843            "self_".to_string()844        } else {845            hand_self(name).to_string()846        };847        let ty = match (kind, name) {848            (SelfKind::Mut, _) => Ty::Ref {849                mutable: true,850                lifetime: None,851                item: Box::new(inner),852            },853            (_, "Rng") => return Err(format!("{}: an Rng self must be mutable", f.path)),854            _ => inner,855        };856        Ok(Some((label, ty)))857    }858859    // FUNCTIONS860861    fn rank_source(&self, params: &[(String, Ty, bool)]) -> Option<String> {862        fn cell(t: &Ty) -> bool {863            matches!(t, Ty::Hand { name, dim: None } if name == "CellNd")864        }865        fn shaped(t: &Ty) -> bool {866            matches!(t, Ty::Hand { name, .. } if name == "Tensor" || name == "Cell" || name == "CellNd")867        }868        for test in [cell, shaped] {869            for (name, ty, _) in params {870                let n = ident(name);871                match ty {872                    t if test(t) => return Some(format!("hand::cell_rank(&{n})?")),873                    Ty::Ref { item, .. } if test(item) => {874                        return Some(format!("hand::cell_rank(&{n})?"))875                    }876                    Ty::Slice { item, .. } | Ty::Vec { item } if test(item) => {877                        return Some(format!("hand::cell_rank(&hand::first(&{n})?)?"))878                    }879                    _ => {}880                }881            }882        }883        None884    }885886    fn emit_group(&self, out: &mut String, group: &[&Function], owner: Owner) -> Result<()> {887        let head = group[0];888        let dispatch = group.len() > 1 || !head.dims.is_empty();889        let name = match owner {890            Owner::Free | Owner::Hand(_) => self.local(&head.path),891            _ => head.name.clone(),892        };893        let mut merged: Vec<(String, Ty, bool)> = vec![];894        for f in group {895            if let Owner::Hand(t) = owner {896                if let Some((label, ty)) = self.self_param(f, t)? {897                    if !merged.iter().any(|(n, _, _)| *n == label) {898                        merged.push((label, ty, false));899                    }900                }901            }902            if let Owner::Holder(t) = owner {903                if f.self_kind.is_some() {904                    let label = t.name.to_lowercase();905                    let label = if f.params.iter().any(|p| p.name == label) {906                        "self_".to_string()907                    } else {908                        label909                    };910                    if !merged.iter().any(|(n, _, _)| *n == label) {911                        merged.push((label, plain_ty(t), false));912                    }913                }914            }915            for p in &f.params {916                if !merged.iter().any(|(n, _, _)| *n == p.name) {917                    merged.push((p.name.clone(), p.ty.clone(), false));918                }919            }920        }921        let shared: Vec<String> = merged922            .iter()923            .filter(|(n, _, _)| group.iter().all(|f| self.mentions(f, owner, n)))924            .map(|(n, _, _)| n.clone())925            .collect();926        for slot in &mut merged {927            slot.2 = !shared.contains(&slot.0);928        }929        let cx = Cx {930            dim: head.dims.first().copied(),931            fname: &head.path,932        };933        let ret = self.ret_plan(&head.ret, cx)?;934        let mut sigs: Vec<String> = vec![];935        let mut plans: Vec<ParamPlan> = vec![];936        for (n, ty, extra) in &merged {937            let plan = self.plan_param(n, ty, cx, *extra)?;938            sigs.push(format!("{}: {}", plan.name, plan.sig));939            plans.push(plan);940        }941        if let Owner::Class(_) = owner {942            let receiver = match head.self_kind {943                Some(SelfKind::Mut) => Some("&mut self"),944                Some(_) => Some("&self"),945                None => None,946            };947            if let Some(receiver) = receiver {948                sigs.insert(0, receiver.to_string());949            }950        }951        let doc = doc_line(&head.docs);952        let indent = if matches!(owner, Owner::Class(_) | Owner::Holder(_)) {953            "    "954        } else {955            ""956        };957        let constructor =958            matches!(owner, Owner::Class(_)) && head.name == "new" && head.self_kind.is_none();959        let renamed = match owner {960            Owner::Holder(_) => head.name == "new" || head.name == "default",961            Owner::Class(_) => head.name == "default",962            Owner::Free | Owner::Hand(_) => false,963        };964        if !doc.is_empty() {965            writeln!(out, "{indent}/// {doc}").ok();966        }967        if constructor {968            writeln!(out, "{indent}#[wasm_bindgen(constructor)]").ok();969        } else if renamed {970            writeln!(out, "{indent}#[wasm_bindgen(js_name = \"{}\")]", head.name).ok();971        } else if indent.is_empty() {972            writeln!(out, "#[wasm_bindgen]").ok();973        }974        let rust_name = if renamed {975            format!("{}_", head.name)976        } else if indent.is_empty() {977            self.export(&name)978        } else {979            name.clone()980        };981        writeln!(982            out,983            "{indent}pub fn {rust_name}({}) -> Result<{}, JsValue> {{",984            sigs.join(", "),985            ret.sig986        )987        .ok();988        if dispatch {989            let rank = self990                .rank_source(&merged)991                .ok_or_else(|| format!("{}: no cell to dispatch on", head.path))?;992            writeln!(out, "{indent}    match {rank} {{").ok();993            let mut dims: Vec<u8> = group.iter().flat_map(|f| f.dims.iter().copied()).collect();994            dims.sort();995            dims.dedup();996            for dim in dims {997                let f = group998                    .iter()999                    .find(|f| f.dims.contains(&dim))1000                    .ok_or_else(|| format!("{}: no entry for {dim}d", head.path))?;1001                let cx = Cx {1002                    dim: Some(dim),1003                    fname: &head.path,1004                };1005                let ret = self.ret_plan(&f.ret, cx)?;1006                let mut plans: Vec<ParamPlan> = vec![];1007                for (n, ty, extra) in &merged {1008                    if !self.mentions(f, owner, n) {1009                        continue;1010                    }1011                    plans.push(self.plan_param(n, ty, cx, *extra)?);1012                }1013                writeln!(out, "{indent}        {dim} => {{").ok();1014                out.push_str(&self.emit_body(1015                    f,1016                    owner,1017                    &plans,1018                    &ret,1019                    cx,1020                    &format!("{indent}            "),1021                )?);1022                writeln!(out, "{indent}        }}").ok();1023            }1024            writeln!(1025                out,1026                "{indent}        rank => Err(hand::refuse(&format!(\"a 2d or 3d cell was wanted, not {{rank}}d.\"))),"1027            )1028            .ok();1029            writeln!(out, "{indent}    }}").ok();1030        } else {1031            out.push_str(&self.emit_body(1032                head,1033                owner,1034                &plans,1035                &ret,1036                cx,1037                &format!("{indent}    "),1038            )?);1039        }1040        writeln!(out, "{indent}}}").ok();1041        Ok(())1042    }10431044    fn mentions(&self, f: &Function, owner: Owner, name: &str) -> bool {1045        if f.params.iter().any(|p| p.name == name) {1046            return true;1047        }1048        match owner {1049            Owner::Hand(t) => {1050                f.self_kind.is_some() && {1051                    let label = hand_self(self.hand_name(t));1052                    let taken = f.params.iter().any(|p| p.name == label);1053                    (if taken { "self_" } else { label }) == name1054                }1055            }1056            Owner::Holder(t) => {1057                f.self_kind.is_some() && {1058                    let label = t.name.to_lowercase();1059                    let taken = f.params.iter().any(|p| p.name == label);1060                    (if taken { "self_".to_string() } else { label }) == name1061                }1062            }1063            _ => false,1064        }1065    }10661067    fn emit_body(1068        &self,1069        f: &Function,1070        owner: Owner,1071        plans: &[ParamPlan],1072        ret: &RetPlan,1073        cx: Cx,1074        pad: &str,1075    ) -> Result<String> {1076        let mut out = String::new();1077        for plan in plans {1078            for line in &plan.pre {1079                writeln!(out, "{pad}{line}").ok();1080            }1081        }1082        let (receiver, rest): (Option<&ParamPlan>, &[ParamPlan]) = match owner {1083            Owner::Hand(_) | Owner::Holder(_) if f.self_kind.is_some() => {1084                (plans.first(), &plans[1..])1085            }1086            _ => (None, plans),1087        };1088        let args: Vec<String> = rest.iter().map(|p| p.arg.clone()).collect();1089        let args = args.join(", ");1090        let turbo = if f.dims.is_empty() {1091            String::new()1092        } else {1093            format!("::<{}>", cx.dim.unwrap_or(2))1094        };1095        let call = match owner {1096            Owner::Free => format!("mrlyrs::{}{turbo}({args})", f.path),1097            Owner::Hand(t) => match receiver {1098                Some(r) => {1099                    let name = match r.post.is_empty() {1100                        true => r.arg.trim_start_matches('&').to_string(),1101                        false => format!("{}_value", r.name),1102                    };1103                    let name = if self.hand_name(t) == "Rng" {1104                        r.arg.clone()1105                    } else {1106                        name1107                    };1108                    format!("{name}.{}({args})", f.name)1109                }1110                None => {1111                    let base = self.rust_ty(1112                        &Ty::Hand {1113                            name: self.hand_name(t).into(),1114                            dim: cx.dim,1115                        },1116                        cx,1117                    )?;1118                    let base = if self.hand_name(t) == "CellNd" {1119                        format!(1120                            "mrlyrs::math::cell::models::CellNd::<{}>",1121                            cx.dim.unwrap_or(2)1122                        )1123                    } else {1124                        base1125                    };1126                    format!("{base}::{}({args})", f.name)1127                }1128            },1129            Owner::Class(t) => {1130                let base = format!("mrlyrs::{}", t.path);1131                let copied = self.ty(&t.path).is_some_and(|t| derives(t, "Copy"));1132                let me = match f.self_kind {1133                    Some(SelfKind::Value) if copied => "self.inner".to_string(),1134                    Some(SelfKind::Value) => "self.inner.clone()".to_string(),1135                    Some(SelfKind::Ref) => "&self.inner".to_string(),1136                    Some(SelfKind::Mut) => "&mut self.inner".to_string(),1137                    None => String::new(),1138                };1139                match (&f.via, f.self_kind) {1140                    (Some(via), None) => format!("<{base} as mrlyrs::{via}>::{}({args})", f.name),1141                    (Some(via), Some(_)) => {1142                        let all = if args.is_empty() {1143                            me1144                        } else {1145                            format!("{me}, {args}")1146                        };1147                        format!("<{base} as mrlyrs::{via}>::{}({all})", f.name)1148                    }1149                    (None, None) => format!("{base}::{}({args})", f.name),1150                    (None, Some(SelfKind::Value)) => format!("{me}.{}({args})", f.name),1151                    (None, Some(_)) => format!("self.inner.{}({args})", f.name),1152                }1153            }1154            Owner::Holder(t) => match receiver {1155                Some(r) => format!("{}.{}({args})", r.arg, f.name),1156                None => format!("mrlyrs::{}::{}({args})", t.path, f.name),1157            },1158        };1159        let fallible = matches!(f.ret, Ty::Result { .. });1160        let unit = match &f.ret {1161            Ty::Unit => true,1162            Ty::Result { item } => matches!(**item, Ty::Unit),1163            _ => false,1164        };1165        match (unit, fallible) {1166            (true, true) => writeln!(out, "{pad}{call}.map_err(hand::throw)?;").ok(),1167            (true, false) => writeln!(out, "{pad}{call};").ok(),1168            (false, true) => writeln!(out, "{pad}let value = {call}.map_err(hand::throw)?;").ok(),1169            (false, false) => writeln!(out, "{pad}let value = {call};").ok(),1170        };1171        for plan in plans {1172            for line in &plan.post {1173                writeln!(out, "{pad}{line}").ok();1174            }1175        }1176        writeln!(out, "{pad}{}", ret.done).ok();1177        Ok(out)1178    }11791180    fn emit_const(&self, out: &mut String, c: &Const) -> Result<()> {1181        let cx = Cx {1182            dim: None,1183            fname: &c.path,1184        };1185        let ret = self.ret_plan(&c.ty, cx)?;1186        let doc = doc_line(&c.docs);1187        if !doc.is_empty() {1188            writeln!(out, "/// {doc}").ok();1189        }1190        let name = self.export(&self.local(&c.path));1191        writeln!(out, "#[wasm_bindgen]").ok();1192        writeln!(out, "pub fn {name}() -> Result<{}, JsValue> {{", ret.sig).ok();1193        writeln!(out, "    let value = mrlyrs::{};", c.path).ok();1194        writeln!(out, "    {}", ret.done).ok();1195        writeln!(out, "}}").ok();1196        Ok(())1197    }11981199    fn getter_ok(&self, ty: &Ty) -> bool {1200        let mut ok = true;1201        ty.walk(&mut |t| match t {1202            Ty::Unknown { .. } | Ty::Opaque { .. } => ok = false,1203            Ty::Class { path, .. } if !self.holds(path) => ok = false,1204            Ty::Ref {1205                lifetime: Some(l),1206                item,1207                ..1208            } if l == "'static" && !matches!(**item, Ty::Str) => ok = false,1209            _ => {}1210        });1211        ok1212    }12131214    fn emit_class(&self, out: &mut String, t: &Type, groups: &[Vec<&Function>]) -> Result<()> {1215        let ident = self.local(&t.path);1216        let cx = Cx {1217            dim: None,1218            fname: &t.path,1219        };1220        let doc = doc_line(&t.docs);1221        if !doc.is_empty() {1222            writeln!(out, "/// {doc}").ok();1223        }1224        writeln!(out, "#[wasm_bindgen]").ok();1225        writeln!(1226            out,1227            "pub struct {ident} {{\n    inner: mrlyrs::{},\n}}\n",1228            t.path1229        )1230        .ok();1231        writeln!(out, "#[wasm_bindgen]\nimpl {ident} {{").ok();1232        if derives(t, "Deserialize") {1233            writeln!(out, "    /// Reads the {} from its plain data.", t.name).ok();1234            writeln!(out, "    #[wasm_bindgen(js_name = \"from\")]").ok();1235            writeln!(1236                out,1237                "    pub fn from_plain(data: JsValue) -> Result<{ident}, JsValue> {{"1238            )1239            .ok();1240            writeln!(1241                out,1242                "        Ok({ident} {{ inner: hand::from_js(&data)? }})"1243            )1244            .ok();1245            writeln!(out, "    }}").ok();1246        }1247        if derives(t, "Serialize") {1248            writeln!(out, "    /// Writes the {} as plain data.", t.name).ok();1249            writeln!(out, "    #[wasm_bindgen(js_name = \"toJSON\")]").ok();1250            writeln!(1251                out,1252                "    pub fn to_plain(&self) -> Result<JsValue, JsValue> {{"1253            )1254            .ok();1255            writeln!(out, "        hand::to_js(&self.inner)").ok();1256            writeln!(out, "    }}").ok();1257        }1258        let methods: BTreeSet<&str> = groups.iter().map(|g| g[0].name.as_str()).collect();1259        for field in t.fields.iter().filter(|f| f.public) {1260            if !self.getter_ok(&field.ty) || methods.contains(field.name.as_str()) {1261                continue;1262            }1263            let ret = self.ret_plan(&field.ty, cx)?;1264            let doc = doc_line(&field.docs);1265            if !doc.is_empty() {1266                writeln!(out, "    /// {doc}").ok();1267            }1268            writeln!(out, "    #[wasm_bindgen(getter)]").ok();1269            writeln!(1270                out,1271                "    pub fn {}(&self) -> Result<{}, JsValue> {{",1272                field.name, ret.sig1273            )1274            .ok();1275            let value = match field.ty {1276                Ty::Ref { .. } => format!("self.inner.{}", field.name),1277                _ if self.is_copy(&field.ty) => format!("self.inner.{}", field.name),1278                _ => format!("self.inner.{}.clone()", field.name),1279            };1280            writeln!(out, "        let value = {value};").ok();1281            writeln!(out, "        {}", ret.done).ok();1282            writeln!(out, "    }}").ok();1283            if field.ty.settable() {1284                let plan = self.plan_param("value", &field.ty, cx, false)?;1285                writeln!(out, "    #[wasm_bindgen(setter)]").ok();1286                writeln!(1287                    out,1288                    "    pub fn set_{}(&mut self, {}: {}) -> Result<(), JsValue> {{",1289                    field.name, plan.name, plan.sig1290                )1291                .ok();1292                for line in &plan.pre {1293                    writeln!(out, "        {line}").ok();1294                }1295                writeln!(out, "        self.inner.{} = {};", field.name, plan.arg).ok();1296                for line in &plan.post {1297                    writeln!(out, "        {line}").ok();1298                }1299                writeln!(out, "        Ok(())").ok();1300                writeln!(out, "    }}").ok();1301            }1302        }1303        for group in groups {1304            self.emit_group(out, group, Owner::Class(t))?;1305        }1306        writeln!(out, "}}").ok();1307        Ok(())1308    }13091310    fn emit_holder(&self, out: &mut String, t: &Type, groups: &[Vec<&Function>]) -> Result<()> {1311        let ident = self.local(&t.path);1312        let doc = doc_line(&t.docs);1313        if !doc.is_empty() {1314            writeln!(out, "/// {doc}").ok();1315        }1316        writeln!(out, "#[wasm_bindgen]").ok();1317        writeln!(out, "pub struct {ident} {{}}\n").ok();1318        writeln!(out, "#[wasm_bindgen]\nimpl {ident} {{").ok();1319        for group in groups {1320            self.emit_group(out, group, Owner::Holder(t))?;1321        }1322        writeln!(out, "}}").ok();1323        Ok(())1324    }13251326    fn lib_rs(&self) -> Result<String> {1327        let mut out = String::new();1328        writeln!(1329            out,1330            "{ALLOWS}\n\nmod hand;\n\nuse wasm_bindgen::prelude::*;\n"1331        )1332        .ok();1333        let groups = self.groups();1334        let mut owned: BTreeMap<&str, Vec<Vec<&Function>>> = BTreeMap::new();1335        for group in &groups {1336            match self.owner_of(group[0]) {1337                Owner::Free | Owner::Hand(_) => {1338                    self.emit_group(&mut out, group, self.owner_of(group[0]))?;1339                    out.push('\n');1340                }1341                Owner::Class(t) | Owner::Holder(t) => {1342                    owned1343                        .entry(t.path.as_str())1344                        .or_default()1345                        .push(group.clone());1346                }1347            }1348        }1349        for c in self.consts() {1350            self.emit_const(&mut out, c)?;1351            out.push('\n');1352        }1353        for t in self.classes() {1354            let groups = owned.get(t.path.as_str()).cloned().unwrap_or_default();1355            self.emit_class(&mut out, t, &groups)?;1356            out.push('\n');1357        }1358        for t in self.holders() {1359            let groups = owned.get(t.path.as_str()).cloned().unwrap_or_default();1360            self.emit_holder(&mut out, t, &groups)?;1361            out.push('\n');1362        }1363        while out.ends_with("\n\n") {1364            out.pop();1365        }1366        Ok(out)1367    }13681369    // WRAPPER13701371    fn tree(&self) -> Tree {1372        let mut tree = Tree::default();1373        let mut seen: BTreeSet<String> = BTreeSet::new();1374        for f in self.functions() {1375            let (home, leaf) = match self.owner_of(f) {1376                Owner::Free | Owner::Hand(_) => (f.module.clone(), leaf(&f.path)),1377                Owner::Class(t) | Owner::Holder(t) => (parent(&t.path), t.name.clone()),1378            };1379            let key = format!("{home}::{leaf}");1380            if seen.insert(key) {1381                let export = match self.owner_of(f) {1382                    Owner::Free | Owner::Hand(_) => self.export(&self.local(&f.path)),1383                    Owner::Class(t) | Owner::Holder(t) => self.local(&t.path),1384                };1385                if JS_RESERVED.contains(&leaf.as_str()) {1386                    tree.insert(self.rel(&home), format!("{leaf}_"), export.clone());1387                }1388                tree.insert(self.rel(&home), leaf, export);1389            }1390        }1391        for t in self.classes() {1392            let key = format!("{}::{}", parent(&t.path), t.name);1393            if seen.insert(key) {1394                tree.insert(1395                    self.rel(&parent(&t.path)),1396                    t.name.clone(),1397                    self.local(&t.path),1398                );1399            }1400        }1401        for c in self.consts() {1402            let export = self.export(&self.local(&c.path));1403            tree.insert(self.rel(&parent(&c.path)), c.name.clone(), export);1404        }1405        tree1406    }14071408    fn wrapper_js(&self) -> String {1409        let name = self.name;1410        let mut out = String::new();1411        writeln!(1412            out,1413            "import * as wasm from \"./pkg/{name}/mrlyjs_{name}.js\";\n"1414        )1415        .ok();1416        writeln!(1417            out,1418            "export {{ default, initSync }} from \"./pkg/{name}/mrlyjs_{name}.js\";"1419        )1420        .ok();1421        writeln!(out, "export const Rng = wasm.Rng;").ok();1422        let tree = self.tree();1423        for (leaf, export) in &tree.leaves {1424            writeln!(out, "export const {leaf} = wasm.{export};").ok();1425        }1426        for (seg, child) in &tree.children {1427            writeln!(out, "export const {seg} = {};", child.literal(1)).ok();1428        }1429        out1430    }14311432    // TYPES14331434    fn ts_ref(&self, path: &str) -> String {1435        self.rel(path).replace("::", ".")1436    }14371438    fn ts_direct(&self, ty: &Ty, ret: bool) -> String {1439        match ty {1440            Ty::Unit => "void".into(),1441            Ty::Scalar { name } => match name.as_str() {1442                "bool" => "boolean".into(),1443                "char" => "string".into(),1444                "u64" | "i64" if ret => "bigint".into(),1445                "u64" | "i64" => "number | bigint".into(),1446                _ => "number".into(),1447            },1448            Ty::Str | Ty::String => "string".into(),1449            Ty::U128 | Ty::I128 | Ty::Code if ret => "string".into(),1450            Ty::U128 | Ty::I128 | Ty::Code => "string | number | bigint".into(),1451            Ty::Json => "any".into(),1452            Ty::Vec { item }1453            | Ty::Slice { item, .. }1454            | Ty::Array { item, .. }1455            | Ty::Set { item } => match &**item {1456                Ty::Scalar { name } if TYPED.contains(&name.as_str()) => {1457                    if ret {1458                        typed_array(name).into()1459                    } else if is_wide(name) {1460                        "ArrayLike<number | bigint>".into()1461                    } else {1462                        "ArrayLike<number>".into()1463                    }1464                }1465                _ => format!("{}[]", self.ts_wrap(&self.ts_direct(item, ret))),1466            },1467            Ty::Option { item } => format!("{} | undefined", self.ts_direct(item, ret)),1468            Ty::Tuple { items } => format!(1469                "[{}]",1470                items1471                    .iter()1472                    .map(|t| self.ts_direct(t, ret))1473                    .collect::<Vec<_>>()1474                    .join(", ")1475            ),1476            Ty::Ref { item, .. } | Ty::Result { item } => self.ts_direct(item, ret),1477            Ty::Map { value, .. } => format!("Record<string, {}>", self.ts_direct(value, ret)),1478            Ty::Hand { name, .. } => match name.as_str() {1479                "Tensor" => "Tensor".into(),1480                "Cell" | "CellNd" => "Cell".into(),1481                "Cell6d" => "Cell6d".into(),1482                "Image" => "Image".into(),1483                "Color" => "Color".into(),1484                "Code" if ret => "string".into(),1485                "Code" => "string | number | bigint".into(),1486                _ => "Rng".into(),1487            },1488            Ty::Plain { path } | Ty::Enum { path } | Ty::Class { path, .. } => self.ts_ref(path),1489            Ty::Opaque { .. } | Ty::Unknown { .. } => "unknown".into(),1490        }1491    }14921493    fn ts_wrap(&self, text: &str) -> String {1494        if text.contains(" | ") {1495            format!("({text})")1496        } else {1497            text.to_string()1498        }1499    }15001501    fn ts_serde(&self, ty: &Ty) -> String {1502        match ty {1503            Ty::Unit => "null".into(),1504            Ty::Scalar { name } => match name.as_str() {1505                "bool" => "boolean".into(),1506                "char" => "string".into(),1507                _ => "number".into(),1508            },1509            Ty::Str | Ty::String => "string".into(),1510            Ty::U128 | Ty::I128 | Ty::Code => "bigint".into(),1511            Ty::Json => "any".into(),1512            Ty::Vec { item }1513            | Ty::Slice { item, .. }1514            | Ty::Array { item, .. }1515            | Ty::Set { item } => {1516                format!("{}[]", self.ts_wrap(&self.ts_serde(item)))1517            }1518            Ty::Option { item } => format!("{} | undefined", self.ts_serde(item)),1519            Ty::Tuple { items } => format!(1520                "[{}]",1521                items1522                    .iter()1523                    .map(|t| self.ts_serde(t))1524                    .collect::<Vec<_>>()1525                    .join(", ")1526            ),1527            Ty::Ref { item, .. } | Ty::Result { item } => self.ts_serde(item),1528            Ty::Map { value, .. } => format!("Record<string, {}>", self.ts_serde(value)),1529            Ty::Hand { name, .. } => match name.as_str() {1530                "Tensor" => "TensorData".into(),1531                "Cell" => "CellData".into(),1532                "CellNd" => "{ cell: CellData }".into(),1533                "Cell6d" => "Cell6dData".into(),1534                "Image" => "{ width: number; height: number; colors: number[][] }".into(),1535                "Color" => "ColorData".into(),1536                "Code" => "bigint".into(),1537                _ => "unknown".into(),1538            },1539            Ty::Plain { path } | Ty::Enum { path } => self.ts_ref(path),1540            Ty::Class { path, .. } => format!("{}Data", self.ts_ref(path)),1541            Ty::Opaque { .. } | Ty::Unknown { .. } => "string".into(),1542        }1543    }15441545    fn ts_params(&self, params: &[(String, Ty, bool)]) -> String {1546        let last_required = params1547            .iter()1548            .rposition(|(_, ty, extra)| !matches!(ty, Ty::Option { .. }) && !extra)1549            .map(|i| i + 1)1550            .unwrap_or(0);1551        params1552            .iter()1553            .enumerate()1554            .map(|(i, (name, ty, extra))| {1555                let n = ident(name);1556                let inner = match ty {1557                    Ty::Option { item } => item,1558                    _ => ty,1559                };1560                if *extra || (i >= last_required && matches!(ty, Ty::Option { .. })) {1561                    format!("{n}?: {}", self.ts_direct(inner, false))1562                } else {1563                    format!("{n}: {}", self.ts_direct(ty, false))1564                }1565            })1566            .collect::<Vec<_>>()1567            .join(", ")1568    }15691570    fn ts_function(&self, group: &[&Function], owner: Owner) -> (String, String, String) {1571        let head = group[0];1572        let mut merged: Vec<(String, Ty, bool)> = vec![];1573        for f in group {1574            match owner {1575                Owner::Hand(t) => {1576                    if let Ok(Some((label, ty))) = self.self_param(f, t) {1577                        if !merged.iter().any(|(n, _, _)| *n == label) {1578                            merged.push((label, ty, false));1579                        }1580                    }1581                }1582                Owner::Holder(t) if f.self_kind.is_some() => {1583                    let label = t.name.to_lowercase();1584                    let label = if f.params.iter().any(|p| p.name == label) {1585                        "self_".to_string()1586                    } else {1587                        label1588                    };1589                    if !merged.iter().any(|(n, _, _)| *n == label) {1590                        merged.push((label, plain_ty(t), false));1591                    }1592                }1593                _ => {}1594            }1595            for p in &f.params {1596                if !merged.iter().any(|(n, _, _)| *n == p.name) {1597                    merged.push((p.name.clone(), p.ty.clone(), false));1598                }1599            }1600        }1601        for slot in &mut merged {1602            slot.2 = !group.iter().all(|f| self.mentions(f, owner, &slot.0));1603        }1604        let ret = match &head.ret {1605            Ty::Result { item } => self.ts_direct(item, true),1606            other => self.ts_direct(other, true),1607        };1608        (doc_line(&head.docs), self.ts_params(&merged), ret)1609    }16101611    fn referenced(&self) -> BTreeSet<String> {1612        let mut out: BTreeSet<String> = BTreeSet::new();1613        let mut queue: Vec<String> = vec![];1614        let note = |ty: &Ty, queue: &mut Vec<String>| {1615            ty.walk(&mut |t| {1616                if let Ty::Plain { path } | Ty::Enum { path } | Ty::Class { path, .. } = t {1617                    queue.push(path.clone());1618                }1619            })1620        };1621        for f in self.functions() {1622            for p in &f.params {1623                note(&p.ty, &mut queue);1624            }1625            note(&f.ret, &mut queue);1626        }1627        for c in self.consts() {1628            note(&c.ty, &mut queue);1629        }1630        for t in self.classes() {1631            queue.push(t.path.clone());1632        }1633        for t in self.holders() {1634            queue.push(t.path.clone());1635        }1636        while let Some(path) = queue.pop() {1637            if !out.insert(path.clone()) {1638                continue;1639            }1640            let Some(t) = self.ty(&path) else { continue };1641            for field in &t.fields {1642                note(&field.ty, &mut queue);1643            }1644            for v in &t.variants {1645                for field in &v.fields {1646                    note(&field.ty, &mut queue);1647                }1648            }1649        }1650        out1651    }16521653    fn ts_type_decl(&self, t: &Type, groups: &[Vec<&Function>], pad: &str) -> Result<String> {1654        let mut out = String::new();1655        let doc = doc_line(&t.docs);1656        let name = &t.name;1657        match &t.cross {1658            TypeCross::Enum { .. } => {1659                let words: Vec<String> = t1660                    .variants1661                    .iter()1662                    .map(|v| enum_word(t, &v.name, &v.serde))1663                    .collect();1664                let union = words1665                    .iter()1666                    .map(|w| format!("{w:?}"))1667                    .collect::<Vec<_>>()1668                    .join(" | ");1669                if !doc.is_empty() {1670                    writeln!(out, "{pad}/** {doc} */").ok();1671                }1672                writeln!(out, "{pad}export type {name} = {union};").ok();1673            }1674            TypeCross::Plain if t.kind == TypeKind::Alias => {1675                if let Some(alias) = &t.alias {1676                    writeln!(out, "{pad}export type {name} = {};", self.ts_serde(alias)).ok();1677                }1678            }1679            TypeCross::Plain if t.kind == TypeKind::Enum => {1680                if !doc.is_empty() {1681                    writeln!(out, "{pad}/** {doc} */").ok();1682                }1683                writeln!(out, "{pad}export type {name} = {};", self.ts_union(t)).ok();1684            }1685            TypeCross::Plain => {1686                if !doc.is_empty() {1687                    writeln!(out, "{pad}/** {doc} */").ok();1688                }1689                writeln!(out, "{pad}export interface {name} {{").ok();1690                self.ts_fields(&mut out, t, &format!("{pad}    "), true);1691                writeln!(out, "{pad}}}").ok();1692            }1693            TypeCross::Class => {1694                let all_public = t.fields.iter().all(|f| f.public);1695                let data = format!("{name}Data");1696                if t.kind == TypeKind::Enum {1697                    writeln!(out, "{pad}export type {data} = {};", self.ts_union(t)).ok();1698                } else if all_public {1699                    writeln!(out, "{pad}export interface {data} {{").ok();1700                    self.ts_fields(&mut out, t, &format!("{pad}    "), false);1701                    writeln!(out, "{pad}}}").ok();1702                } else {1703                    writeln!(out, "{pad}export type {data} = Record<string, unknown>;").ok();1704                }1705                if !doc.is_empty() {1706                    writeln!(out, "{pad}/** {doc} */").ok();1707                }1708                writeln!(out, "{pad}export class {name} {{").ok();1709                let inner = format!("{pad}    ");1710                let constructor = groups1711                    .iter()1712                    .find(|g| g[0].name == "new" && g[0].self_kind.is_none());1713                match constructor {1714                    Some(g) => {1715                        let (d, params, _) = self.ts_function(g, Owner::Class(t));1716                        if !d.is_empty() {1717                            writeln!(out, "{inner}/** {d} */").ok();1718                        }1719                        writeln!(out, "{inner}constructor({params});").ok();1720                    }1721                    None => {1722                        writeln!(out, "{inner}private constructor();").ok();1723                    }1724                }1725                writeln!(out, "{inner}free(): void;").ok();1726                if derives(t, "Deserialize") {1727                    writeln!(out, "{inner}/** Reads the {name} from its plain data. */").ok();1728                    writeln!(out, "{inner}static from(data: {data}): {name};").ok();1729                }1730                if derives(t, "Serialize") {1731                    writeln!(out, "{inner}/** Writes the {name} as plain data. */").ok();1732                    writeln!(out, "{inner}toJSON(): {data};").ok();1733                }1734                let methods: BTreeSet<&str> = groups.iter().map(|g| g[0].name.as_str()).collect();1735                for field in t.fields.iter().filter(|f| f.public) {1736                    if !self.getter_ok(&field.ty) || methods.contains(field.name.as_str()) {1737                        continue;1738                    }1739                    let d = doc_line(&field.docs);1740                    if !d.is_empty() {1741                        writeln!(out, "{inner}/** {d} */").ok();1742                    }1743                    let out_ty = self.ts_direct(&field.ty, true);1744                    if field.ty.settable() {1745                        writeln!(out, "{inner}get {}(): {out_ty};", field.name).ok();1746                        writeln!(1747                            out,1748                            "{inner}set {}(value: {});",1749                            field.name,1750                            self.ts_direct(&field.ty, false)1751                        )1752                        .ok();1753                    } else {1754                        writeln!(out, "{inner}readonly {}: {out_ty};", ident(&field.name)).ok();1755                    }1756                }1757                for g in groups {1758                    let f = g[0];1759                    if f.name == "new" && f.self_kind.is_none() {1760                        continue;1761                    }1762                    let (d, params, ret) = self.ts_function(g, Owner::Class(t));1763                    if !d.is_empty() {1764                        writeln!(out, "{inner}/** {d} */").ok();1765                    }1766                    let prefix = if f.self_kind.is_none() { "static " } else { "" };1767                    writeln!(out, "{inner}{prefix}{}({params}): {ret};", f.name).ok();1768                }1769                writeln!(out, "{pad}}}").ok();1770            }1771            TypeCross::Hand { .. } | TypeCross::Uncrossable { .. } => {}1772        }1773        if matches!(t.cross, TypeCross::Plain | TypeCross::Enum { .. }) && !groups.is_empty() {1774            writeln!(out, "{pad}export const {name}: {{").ok();1775            for g in groups {1776                let f = g[0];1777                let (d, params, ret) = self.ts_function(g, Owner::Holder(t));1778                if !d.is_empty() {1779                    writeln!(out, "{pad}    /** {d} */").ok();1780                }1781                let label = if f.name == "new" {1782                    "\"new\"".to_string()1783                } else {1784                    f.name.clone()1785                };1786                writeln!(out, "{pad}    {label}({params}): {ret};").ok();1787            }1788            writeln!(out, "{pad}}};").ok();1789        }1790        Ok(out)1791    }17921793    fn ts_union(&self, t: &Type) -> String {1794        let untagged = t.serde.iter().any(|s| s == "untagged");1795        let mut arms = vec![];1796        for v in &t.variants {1797            let word = enum_word(t, &v.name, &v.serde);1798            let body = if v.fields.is_empty() {1799                None1800            } else if v.fields.iter().all(|f| f.name.parse::<usize>().is_ok()) {1801                Some(if v.fields.len() == 1 {1802                    self.ts_serde(&v.fields[0].ty)1803                } else {1804                    format!(1805                        "[{}]",1806                        v.fields1807                            .iter()1808                            .map(|f| self.ts_serde(&f.ty))1809                            .collect::<Vec<_>>()1810                            .join(", ")1811                    )1812                })1813            } else {1814                Some(format!(1815                    "{{ {} }}",1816                    v.fields1817                        .iter()1818                        .map(|f| format!("{}: {}", f.name, self.ts_serde(&f.ty)))1819                        .collect::<Vec<_>>()1820                        .join("; ")1821                ))1822            };1823            arms.push(match (untagged, body) {1824                (true, None) => "null".to_string(),1825                (false, None) => format!("{word:?}"),1826                (true, Some(body)) => body,1827                (false, Some(body)) => format!("{{ {word}: {body} }}"),1828            });1829        }1830        arms.join(" | ")1831    }18321833    fn ts_fields(&self, out: &mut String, t: &Type, pad: &str, all: bool) {1834        for field in t1835            .fields1836            .iter()1837            .filter(|f| (all || f.public) && !f.serde_skip)1838        {1839            let d = doc_line(&field.docs);1840            if !d.is_empty() {1841                writeln!(out, "{pad}/** {d} */").ok();1842            }1843            let (name, ty) = match &field.ty {1844                Ty::Option { item } => (format!("{}?", ident(&field.name)), self.ts_serde(item)),1845                other => (ident(&field.name), self.ts_serde(other)),1846            };1847            writeln!(out, "{pad}{name}: {ty};").ok();1848        }1849    }18501851    fn types_dts(&self) -> Result<String> {1852        let name = self.name;1853        let mut out = String::new();1854        writeln!(1855            out,1856            "export {{ default, initSync }} from \"./pkg/{name}/mrlyjs_{name}.js\";\n"1857        )1858        .ok();1859        let words = |path: &str| -> String {1860            self.ty(path)1861                .map(|t| {1862                    t.variants1863                        .iter()1864                        .map(|v| enum_word(t, &v.name, &v.serde))1865                        .collect::<Vec<String>>()1866                })1867                .unwrap_or_default()1868                .iter()1869                .map(|w| format!("{w:?}"))1870                .collect::<Vec<_>>()1871                .join(" | ")1872        };1873        let projection = words("math::six::Projection");1874        let orientation = words("math::six::Orientation");1875        writeln!(out, "/** An rgba color as four bytes. */").ok();1876        writeln!(out, "export type Color = [number, number, number, number];").ok();1877        writeln!(1878            out,1879            "/** A tensor: its shape and its flat data as a typed array of its dtype. */"1880        )1881        .ok();1882        writeln!(out, "export interface Tensor {{\n    shape: number[];\n    data: Uint8Array | Uint16Array | Uint32Array | Int32Array;\n}}").ok();1883        writeln!(out, "/** A cell: the shape, the type bytes, and the flat rgba colors and the tags when present. */").ok();1884        writeln!(out, "export interface Cell {{\n    shape: number[];\n    types: Uint8Array | Uint16Array | Uint32Array | Int32Array;\n    colors?: Uint8Array;\n    tags?: Uint8Array | Uint16Array | Uint32Array | Int32Array;\n}}").ok();1885        writeln!(1886            out,1887            "/** A hex cell: a flat cell with its projection, orientation and start row. */"1888        )1889        .ok();1890        writeln!(out, "export interface Cell6d {{\n    cell: Cell;\n    projection: {projection};\n    orientation: {orientation};\n    start: number;\n}}").ok();1891        writeln!(1892            out,1893            "/** An image: its height and width, and its flat rgba colors row by row. */"1894        )1895        .ok();1896        writeln!(1897            out,1898            "export interface Image {{\n    shape: [number, number];\n    colors: Uint8Array;\n}}"1899        )1900        .ok();1901        writeln!(out, "/** A color inside plain data, serde's form. */").ok();1902        writeln!(out, "export interface ColorData {{\n    r: number;\n    g: number;\n    b: number;\n    a: number;\n}}").ok();1903        writeln!(out, "/** A tensor inside plain data, serde's form. */").ok();1904        writeln!(out, "export interface TensorData {{\n    shape: number[];\n    data: {{ U8: number[] }} | {{ U16: number[] }} | {{ U32: number[] }} | {{ I32: number[] }};\n}}").ok();1905        writeln!(out, "/** A cell inside plain data, serde's form. */").ok();1906        writeln!(out, "export interface CellData {{\n    types: TensorData;\n    colors?: number[][];\n    tags?: TensorData;\n}}").ok();1907        writeln!(out, "/** A hex cell inside plain data, serde's form. */").ok();1908        writeln!(out, "export interface Cell6dData {{\n    cell: {{ cell: CellData }};\n    projection: {projection};\n    orientation: {orientation};\n    start: number;\n}}").ok();1909        writeln!(1910            out,1911            "/** A seeded random stream, opened from a number or a bigint seed. */"1912        )1913        .ok();1914        writeln!(out, "export class Rng {{\n    constructor(seed: number | bigint | string);\n    free(): void;\n    /** Draws a float at or above zero and below one. */\n    unit(): number;\n    /** Draws an integer below n, or zero when n is zero. */\n    below(n: number): number;\n    /** Draws an integer between lo and hi inclusive, or lo when hi is not above lo. */\n    range(lo: number, hi: number): number;\n    /** Draws a fair coin flip. */\n    boolean(): boolean;\n    /** Returns true with probability p. */\n    chance(p: number): boolean;\n    /** Draws amount distinct indices below length, or every index when amount is larger. */\n    sample_indices(length: number, amount: number): Uint32Array;\n    /** Draws one item of the array, the same draw as Rust's choice. */\n    choice<T>(items: ArrayLike<T>): T;\n    /** Shuffles the array in place, the same permutation as Rust's shuffle. */\n    shuffle<T>(items: T[]): void;\n}}").ok();1915        let mut tree = DeclTree::default();1916        let groups = self.groups();1917        let mut owned: BTreeMap<&str, Vec<Vec<&Function>>> = BTreeMap::new();1918        for group in &groups {1919            match self.owner_of(group[0]) {1920                Owner::Free | Owner::Hand(_) => {1921                    let (d, params, ret) = self.ts_function(group, self.owner_of(group[0]));1922                    let mut text = String::new();1923                    if !d.is_empty() {1924                        text.push_str(&format!("/** {d} */\n"));1925                    }1926                    text.push_str(&format!(1927                        "export function {}({params}): {ret};\n",1928                        ts_leaf(&leaf(&group[0].path))1929                    ));1930                    tree.insert(self.rel(&group[0].module), text);1931                }1932                Owner::Class(t) | Owner::Holder(t) => {1933                    owned1934                        .entry(t.path.as_str())1935                        .or_default()1936                        .push(group.clone());1937                }1938            }1939        }1940        for c in self.consts() {1941            let mut text = String::new();1942            let d = doc_line(&c.docs);1943            if !d.is_empty() {1944                text.push_str(&format!("/** {d} */\n"));1945            }1946            text.push_str(&format!(1947                "export function {}(): {};\n",1948                c.name,1949                self.ts_direct(&c.ty, true)1950            ));1951            tree.insert(self.rel(&parent(&c.path)), text);1952        }1953        for path in self.referenced() {1954            let Some(t) = self.ty(&path) else { continue };1955            if matches!(1956                t.cross,1957                TypeCross::Hand { .. } | TypeCross::Uncrossable { .. }1958            ) {1959                continue;1960            }1961            let groups = owned.get(path.as_str()).cloned().unwrap_or_default();1962            let text = self.ts_type_decl(t, &groups, "")?;1963            tree.insert(self.rel(&parent(&t.path)), text);1964        }1965        out.push_str(&tree.render(0));1966        while out.ends_with("\n\n") {1967            out.pop();1968        }1969        Ok(out)1970    }1971}19721973// TREES19741975#[derive(Default)]1976struct Tree {1977    leaves: BTreeMap<String, String>,1978    children: BTreeMap<String, Tree>,1979}19801981impl Tree {1982    fn insert(&mut self, module: &str, leaf: String, export: String) {1983        let mut node = self;1984        for seg in module.split("::").filter(|s| !s.is_empty()) {1985            node = node.children.entry(seg.to_string()).or_default();1986        }1987        node.leaves.insert(leaf, export);1988    }19891990    fn literal(&self, depth: usize) -> String {1991        let pad = "    ".repeat(depth);1992        let mut out = String::from("{\n");1993        for (leaf, export) in &self.leaves {1994            out.push_str(&format!("{pad}{leaf}: wasm.{export},\n"));1995        }1996        for (seg, child) in &self.children {1997            out.push_str(&format!("{pad}{seg}: {},\n", child.literal(depth + 1)));1998        }1999        out.push_str(&"    ".repeat(depth - 1));2000        out.push('}');2001        out2002    }2003}20042005#[derive(Default)]2006struct DeclTree {2007    texts: Vec<String>,2008    children: BTreeMap<String, DeclTree>,2009}20102011impl DeclTree {2012    fn insert(&mut self, module: &str, text: String) {2013        let mut node = self;2014        for seg in module.split("::").filter(|s| !s.is_empty()) {2015            node = node.children.entry(seg.to_string()).or_default();2016        }2017        node.texts.push(text);2018    }20192020    fn render(&self, depth: usize) -> String {2021        let pad = "    ".repeat(depth);2022        let mut out = String::new();2023        for text in &self.texts {2024            for line in text.lines() {2025                out.push_str(&pad);2026                out.push_str(line);2027                out.push('\n');2028            }2029        }2030        for (seg, child) in &self.children {2031            let keyword = if depth == 0 {2032                "export declare namespace"2033            } else {2034                "export namespace"2035            };2036            out.push_str(&format!("{pad}{keyword} {seg} {{\n"));2037            out.push_str(&child.render(depth + 1));2038            out.push_str(&format!("{pad}}}\n"));2039        }2040        out2041    }2042}20432044// RULES20452046fn deref(e: &str) -> String {2047    match e.strip_prefix('&') {2048        Some(inner) => inner.to_string(),2049        None => format!("*{e}"),2050    }2051}20522053fn place(e: &str) -> String {2054    e.strip_prefix('&').unwrap_or(e).to_string()2055}20562057fn closure(x: &str, body: String) -> String {2058    let tail = format!("({x})");2059    match body.strip_suffix(&tail) {2060        Some(path)2061            if !path.contains(x) && !path.contains(' ') || path.starts_with("hand::from_js::<") =>2062        {2063            path.to_string()2064        }2065        _ => format!("|{x}| {body}"),2066    }2067}20682069fn ok(expr: String) -> String {2070    match expr.strip_suffix('?') {2071        Some(inner) => inner.to_string(),2072        None => format!("Ok({expr})"),2073    }2074}20752076fn is_wide(name: &str) -> bool {2077    name == "u64" || name == "i64"2078}20792080fn is_rng(ty: &Ty) -> bool {2081    matches!(ty, Ty::Hand { name, .. } if name == "Rng")2082}20832084fn typed_scalar(ty: &Ty) -> bool {2085    matches!(ty, Ty::Scalar { name } if TYPED.contains(&name.as_str()))2086}20872088fn native_item(ty: &Ty) -> bool {2089    matches!(ty, Ty::Scalar { name } if NATIVE_ITEM.contains(&name.as_str()))2090}20912092fn typed_array(name: &str) -> &'static str {2093    match name {2094        "u8" => "Uint8Array",2095        "u16" => "Uint16Array",2096        "u32" | "usize" => "Uint32Array",2097        "i8" => "Int8Array",2098        "i16" => "Int16Array",2099        "i32" => "Int32Array",2100        "f32" => "Float32Array",2101        "f64" => "Float64Array",2102        "u64" => "BigUint64Array",2103        _ => "BigInt64Array",2104    }2105}21062107fn pure_in(ty: &Ty) -> bool {2108    match ty {2109        Ty::Unit | Ty::Str | Ty::String | Ty::Json | Ty::Plain { .. } | Ty::Enum { .. } => true,2110        Ty::Scalar { name } => !is_wide(name),2111        Ty::Vec { item }2112        | Ty::Slice { item, .. }2113        | Ty::Option { item }2114        | Ty::Array { item, .. } => pure_in(item),2115        Ty::Ref {2116            mutable: false,2117            item,2118            ..2119        } => pure_in(item),2120        Ty::Tuple { items } => items.iter().all(pure_in),2121        _ => false,2122    }2123}21242125fn pure_out(ty: &Ty) -> bool {2126    match ty {2127        Ty::Unit | Ty::Str | Ty::String | Ty::Json | Ty::Plain { .. } | Ty::Enum { .. } => true,2128        Ty::Scalar { name } => !is_wide(name),2129        Ty::Vec { item } | Ty::Slice { item, .. } | Ty::Array { item, .. } => {2130            !typed_scalar(item) && pure_out(item)2131        }2132        Ty::Option { item } => pure_out(item),2133        Ty::Tuple { items } => items.iter().all(pure_out),2134        _ => false,2135    }2136}21372138fn plain_ty(t: &Type) -> Ty {2139    match t.cross {2140        TypeCross::Enum { .. } => Ty::Enum {2141            path: t.path.clone(),2142        },2143        _ => Ty::Plain {2144            path: t.path.clone(),2145        },2146    }2147}21482149fn hand_self(name: &str) -> &'static str {2150    match name {2151        "Tensor" => "tensor",2152        "Image" => "image",2153        "Color" => "color",2154        "Code" => "code",2155        "Rng" => "rng",2156        _ => "cell",2157    }2158}21592160fn suffix_dim(fname: &str) -> u8 {2161    if fname.ends_with("_3d") || fname.ends_with("_6d") {2162        32163    } else {2164        22165    }2166}21672168fn ident(name: &str) -> String {2169    if WORDS.contains(&name) {2170        format!("{name}_")2171    } else {2172        name.to_string()2173    }2174}21752176fn derives(t: &Type, what: &str) -> bool {2177    t.derives.iter().any(|d| d == what)2178}21792180fn doc_line(docs: &[String]) -> String {2181    docs.first()2182        .map(|d| d.trim().to_string())2183        .unwrap_or_default()2184}21852186fn ts_leaf(name: &str) -> String {2187    if JS_RESERVED.contains(&name) {2188        format!("{name}_")2189    } else {2190        name.to_string()2191    }2192}21932194fn leaf(path: &str) -> String {2195    path.rsplit("::").next().unwrap_or(path).to_string()2196}21972198fn parent(path: &str) -> String {2199    match path.rfind("::") {2200        Some(at) => path[..at].to_string(),2201        None => String::new(),2202    }2203}22042205fn enum_word(t: &Type, variant: &str, serde: &[String]) -> String {2206    for attr in serde {2207        if let Some(rest) = attr.trim().strip_prefix("rename = ") {2208            return rest.trim().trim_matches('"').to_string();2209        }2210    }2211    for attr in &t.serde {2212        if let Some(rest) = attr.trim().strip_prefix("rename_all = ") {2213            let rule = rest.trim().trim_matches('"');2214            return rename_all(variant, rule);2215        }2216    }2217    variant.to_string()2218}22192220fn rename_all(name: &str, rule: &str) -> String {2221    let words: Vec<String> = {2222        let mut out: Vec<String> = vec![];2223        for c in name.chars() {2224            if c.is_uppercase() || out.is_empty() {2225                out.push(String::new());2226            }2227            out.last_mut().unwrap().push(c);2228        }2229        out.into_iter().map(|w| w.to_lowercase()).collect()2230    };2231    match rule {2232        "lowercase" => name.to_lowercase(),2233        "UPPERCASE" => name.to_uppercase(),2234        "snake_case" => words.join("_"),2235        "SCREAMING_SNAKE_CASE" => words.join("_").to_uppercase(),2236        "kebab-case" => words.join("-"),2237        "SCREAMING-KEBAB-CASE" => words.join("-").to_uppercase(),2238        "camelCase" => {2239            let mut c = name.chars();2240            match c.next() {2241                Some(first) => first.to_lowercase().collect::<String>() + c.as_str(),2242                None => String::new(),2243            }2244        }2245        _ => name.to_string(),2246    }2247}22482249// TEST22502251const MANIFEST_TEST: &str = r#"import { expect, test } from "bun:test";22522253const manifest = await Bun.file(new URL("../bridge/manifest.json", import.meta.url)).json();2254const units = (await Bun.file(new URL("../bridge/units.txt", import.meta.url)).text())2255    .split("\n")2256    .map((line) => line.trim())2257    .filter(Boolean);2258const pkg = await Bun.file(new URL("./package.json", import.meta.url)).json();2259const kinds = new Map(manifest.types.map((t) => [t.path, t.cross.kind]));22602261function parent(path) {2262    const at = path.lastIndexOf("::");2263    return at < 0 ? "" : path.slice(0, at);2264}22652266function relative(unit, path) {2267    return unit === "all" ? path : path.slice(unit.length + 2);2268}22692270function walk(mod, path) {2271    let node = mod;2272    for (const seg of path.split("::").filter(Boolean)) {2273        if (node === undefined || node === null) return undefined;2274        node = node[seg];2275    }2276    return node;2277}22782279function holds(unit, path) {2280    return unit === "all" || path.split("::")[0] === unit;2281}22822283function check(mod, unit, f) {2284    const kind = f.owner ? kinds.get(f.owner) : undefined;2285    if (kind === "class") {2286        const cls = walk(mod, relative(unit, f.owner));2287        if (typeof cls !== "function") return false;2288        if (f.self_kind === null && f.name === "new") return true;2289        if (f.self_kind === null) return typeof cls[f.name] === "function";2290        return typeof cls.prototype[f.name] === "function";2291    }2292    if (kind === "plain" || kind === "enum") {2293        const holder = walk(mod, relative(unit, f.owner));2294        return typeof holder === "function" && typeof holder[f.name] === "function";2295    }2296    return typeof walk(mod, relative(unit, f.path)) === "function";2297}22982299for (const unit of units) {2300    test(`${unit} exports every ok name`, async () => {2301        expect(pkg.exports[unit === "all" ? "." : `./${unit}`]).toBeDefined();2302        const mod = await import(`./${unit}.js`);2303        const bytes = await Bun.file(new URL(`./pkg/${unit}/mrlyjs_${unit}_bg.wasm`, import.meta.url)).arrayBuffer();2304        mod.initSync({ module: bytes });2305        expect(typeof mod.Rng).toBe("function");2306        const missing = [];2307        const names = new Set();2308        for (const f of manifest.functions) {2309            if (f.cross.status !== "ok" || !holds(unit, f.module)) continue;2310            names.add(f.path);2311            if (!check(mod, unit, f)) missing.push(f.path);2312        }2313        for (const c of manifest.consts) {2314            if (c.cross.status !== "ok" || !holds(unit, c.path)) continue;2315            names.add(c.path);2316            if (typeof walk(mod, relative(unit, c.path)) !== "function") missing.push(c.path);2317        }2318        for (const t of manifest.types) {2319            if (t.cross.kind !== "class" || !holds(unit, t.path)) continue;2320            names.add(t.path);2321            if (typeof walk(mod, relative(unit, t.path)) !== "function") missing.push(t.path);2322        }2323        expect(missing).toEqual([]);2324        console.log(`${unit}: ${names.size} names`);2325    });2326}2327"#;