js.rs

79.9 kB · rust · 2002 lines

1use crate::model::{Const, Cross, Function, Manifest, Result, SelfKind, Ty, Type, TypeCross, TypeKind};2use std::collections::{BTreeMap, BTreeSet};3use std::fmt::Write as _;4use std::path::Path;56const TYPED: &[&str] = &[7    "u8", "u16", "u32", "i8", "i16", "i32", "f32", "f64", "usize", "u64", "i64",8];910const NATIVE_ITEM: &[&str] = &["u8", "u16", "u32", "i8", "i16", "i32", "f32", "f64", "usize"];1112const WORDS: &[&str] = &[13    "void", "new", "class", "function", "default", "delete", "in", "var", "let", "const", "export",14    "import", "switch", "case", "for", "if", "else", "this", "super", "with", "yield", "enum",15    "await", "typeof", "instanceof", "return", "try", "catch", "finally", "throw", "while", "do",16    "break", "continue", "debugger", "static", "interface", "package", "private", "protected",17    "public", "implements", "arguments", "eval", "null", "true", "false", "extends", "as", "crate",18    "extern", "fn", "impl", "loop", "match", "mod", "move", "mut", "pub", "ref", "self", "struct",19    "trait", "type", "unsafe", "use", "where", "async", "dyn", "box", "macro", "gen",20];2122const RNG_METHODS: &[&str] = &[23    "new", "unit", "below", "range", "boolean", "chance", "sample_indices", "choice", "shuffle",24    "__state", "__restore",25];2627const JS_RESERVED: &[&str] = &[28    "new", "void", "class", "function", "default", "delete", "in", "var", "let", "const", "export",29    "import", "switch", "case", "for", "if", "else", "this", "super", "with", "yield", "enum",30    "await", "typeof", "instanceof", "return", "try", "catch", "finally", "throw", "while", "do",31    "break", "continue", "debugger", "static", "interface", "package", "private", "protected",32    "public", "implements", "null", "true", "false", "extends",33];3435const HAND_RS: &str = "#[path = \"../../../hand/hand.rs\"]\nmod crossing;\n\npub use crossing::*;\n";3637const ALLOWS: &str = "#![allow(non_camel_case_types, non_snake_case, clippy::too_many_arguments)]";3839// FILES4041pub fn write(manifest: &Manifest, root: &Path) -> Result<()> {42    let names = std::fs::read_to_string(root.join("bridge/units.txt")).map_err(|e| e.to_string())?;43    let names: Vec<&str> = names44        .lines()45        .map(str::trim)46        .filter(|l| !l.is_empty())47        .collect();48    let js = root.join("pkgs/mrlyjs");49    for name in &names {50        let unit = Unit::new(manifest, name);51        let dir = js.join("units").join(name);52        save(&dir.join("Cargo.toml"), &cargo_toml(name))?;53        save(&dir.join("src/hand.rs"), HAND_RS)?;54        save(&dir.join("src/lib.rs"), &unit.lib_rs()?)?;55        save(&js.join(format!("{name}.js")), &unit.wrapper_js())?;56        save(&js.join(format!("{name}.d.ts")), &unit.types_dts()?)?;57    }58    save(&js.join("manifest.test.js"), MANIFEST_TEST)?;59    Ok(())60}6162fn save(path: &Path, text: &str) -> Result<()> {63    if let Some(parent) = path.parent() {64        std::fs::create_dir_all(parent).map_err(|e| e.to_string())?;65    }66    if std::fs::read_to_string(path).ok().as_deref() == Some(text) {67        return Ok(());68    }69    std::fs::write(path, text).map_err(|e| format!("{}: {e}", path.display()))70}7172fn cargo_toml(name: &str) -> String {73    let what = if name == "all" {74        "every module of mrlyrs".to_string()75    } else {76        format!("the {name} module of mrlyrs")77    };78    format!(79        "[package]\nname = \"mrlyjs_{name}\"\nversion = \"0.2.0\"\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"80    )81}8283// UNIT8485struct Unit<'a> {86    name: &'a str,87    manifest: &'a Manifest,88    types: BTreeMap<&'a str, &'a Type>,89    reserved: BTreeSet<String>,90}9192#[derive(Clone, Copy)]93enum Owner<'a> {94    Free,95    Hand(&'a Type),96    Class(&'a Type),97    Holder(&'a Type),98}99100#[derive(Clone, Copy)]101struct Cx<'a> {102    dim: Option<u8>,103    fname: &'a str,104}105106struct ParamPlan {107    name: String,108    sig: String,109    pre: Vec<String>,110    arg: String,111    post: Vec<String>,112}113114struct RetPlan {115    sig: String,116    done: String,117}118119impl<'a> Unit<'a> {120    fn new(manifest: &'a Manifest, name: &'a str) -> Unit<'a> {121        let mut unit = Unit {122            name,123            manifest,124            types: manifest.types.iter().map(|t| (t.path.as_str(), t)).collect(),125            reserved: BTreeSet::new(),126        };127        unit.reserved = unit.symbols();128        unit129    }130131    fn symbols(&self) -> BTreeSet<String> {132        let mut out = BTreeSet::new();133        for method in RNG_METHODS {134            out.insert(format!("rng_{method}"));135        }136        for t in self.classes().into_iter().chain(self.holders()) {137            let lower = self.local(&t.path).to_lowercase();138            for f in self.functions() {139                if f.owner.as_deref() == Some(t.path.as_str()) {140                    out.insert(format!("{lower}_{}", f.name));141                }142            }143            for extra in ["from", "toJSON", "new"] {144                out.insert(format!("{lower}_{extra}"));145            }146        }147        out148    }149150    fn export(&self, name: &str) -> String {151        let mut out = name.to_string();152        while self.reserved.contains(&out) {153            out.push('_');154        }155        out156    }157158    fn holds(&self, path: &str) -> bool {159        self.name == "all" || path.split("::").next() == Some(self.name)160    }161162    fn rel<'p>(&self, path: &'p str) -> &'p str {163        if self.name == "all" {164            return path;165        }166        match path.strip_prefix(self.name) {167            Some("") => "",168            Some(rest) => rest.strip_prefix("::").unwrap_or(path),169            None => path,170        }171    }172173    fn local(&self, path: &str) -> String {174        self.rel(path).replace("::", "_")175    }176177    fn ty(&self, path: &str) -> Option<&'a Type> {178        self.types.get(path).copied()179    }180181    fn functions(&self) -> Vec<&'a Function> {182        self.manifest183            .functions184            .iter()185            .filter(|f| f.cross == Cross::Ok && self.holds(&f.module))186            .collect()187    }188189    fn groups(&self) -> Vec<Vec<&'a Function>> {190        let mut out: Vec<Vec<&'a Function>> = vec![];191        for f in self.functions() {192            match out.last_mut() {193                Some(group) if group[0].path == f.path => group.push(f),194                _ => out.push(vec![f]),195            }196        }197        out198    }199200    fn consts(&self) -> Vec<&'a Const> {201        self.manifest202            .consts203            .iter()204            .filter(|c| c.cross == Cross::Ok && self.holds(&c.path))205            .collect()206    }207208    fn classes(&self) -> Vec<&'a Type> {209        self.manifest210            .types211            .iter()212            .filter(|t| t.cross == TypeCross::Class && self.holds(&t.path))213            .collect()214    }215216    fn holders(&self) -> Vec<&'a Type> {217        let owners: BTreeSet<&str> = self218            .functions()219            .iter()220            .filter_map(|f| f.owner.as_deref())221            .collect();222        self.manifest223            .types224            .iter()225            .filter(|t| {226                matches!(t.cross, TypeCross::Plain | TypeCross::Enum { .. })227                    && self.holds(&t.path)228                    && owners.contains(t.path.as_str())229            })230            .collect()231    }232233    fn owner_of(&self, f: &Function) -> Owner<'a> {234        let Some(path) = f.owner.as_deref() else {235            return Owner::Free;236        };237        match self.ty(path).map(|t| (t, &t.cross)) {238            Some((t, TypeCross::Hand { .. })) => Owner::Hand(t),239            Some((t, TypeCross::Class)) => Owner::Class(t),240            Some((t, TypeCross::Plain | TypeCross::Enum { .. })) => Owner::Holder(t),241            _ => Owner::Free,242        }243    }244245    fn hand_name(&self, t: &Type) -> &'static str {246        match &t.cross {247            TypeCross::Hand { name } => match name.as_str() {248                "Tensor" => "Tensor",249                "Cell" => "Cell",250                "CellNd" => "CellNd",251                "Cell6d" => "Cell6d",252                "Color" => "Color",253                "Code" => "Code",254                _ => "Rng",255            },256            _ => "Rng",257        }258    }259260    // RUST TYPES261262    fn rust_ty(&self, ty: &Ty, cx: Cx) -> Result<String> {263        Ok(match ty {264            Ty::Unit => "()".into(),265            Ty::Scalar { name } => name.clone(),266            Ty::Str | Ty::String => "String".into(),267            Ty::U128 => "u128".into(),268            Ty::I128 => "i128".into(),269            Ty::Code => "mrlyrs::math::bang::Code".into(),270            Ty::Json => "mrlyrs::core::Json".into(),271            Ty::Vec { item } | Ty::Slice { item, .. } | Ty::Set { item } => {272                format!("Vec<{}>", self.rust_ty(item, cx)?)273            }274            Ty::Option { item } => format!("Option<{}>", self.rust_ty(item, cx)?),275            Ty::Tuple { items } => {276                let parts: Result<Vec<String>> =277                    items.iter().map(|t| self.rust_ty(t, cx)).collect();278                format!("({})", parts?.join(", "))279            }280            Ty::Array { item, len } => format!("[{}; {len}]", self.rust_ty(item, cx)?),281            Ty::Ref { item, .. } | Ty::Result { item } => self.rust_ty(item, cx)?,282            Ty::Map { key, value } => format!(283                "std::collections::HashMap<{}, {}>",284                self.rust_ty(key, cx)?,285                self.rust_ty(value, cx)?286            ),287            Ty::Hand { name, dim } => match name.as_str() {288                "Tensor" => "mrlyrs::core::Tensor".into(),289                "Cell" => "mrlyrs::core::Cell".into(),290                "CellNd" => match dim.or(cx.dim) {291                    Some(2) => "mrlyrs::math::two::Cell2d".into(),292                    Some(3) => "mrlyrs::math::three::Cell3d".into(),293                    _ => return Err(format!("{}: a cell without a dimension", cx.fname)),294                },295                "Cell6d" => "mrlyrs::math::six::Cell6d".into(),296                "Color" => "mrlyrs::core::Color".into(),297                "Code" => "mrlyrs::math::bang::Code".into(),298                "Rng" => "mrlyrs::core::Rng".into(),299                other => return Err(format!("{}: unknown hand type {other}", cx.fname)),300            },301            Ty::Plain { path } => {302                let generic = self.ty(path).is_some_and(|t| t.const_generic);303                if generic {304                    format!("mrlyrs::{path}<{}>", suffix_dim(cx.fname))305                } else {306                    format!("mrlyrs::{path}")307                }308            }309            Ty::Enum { path } | Ty::Class { path, .. } => format!("mrlyrs::{path}"),310            Ty::Opaque { path } => return Err(format!("{}: {path} cannot cross", cx.fname)),311            Ty::Unknown { text } => return Err(format!("{}: {text} cannot cross", cx.fname)),312        })313    }314315    fn is_copy(&self, ty: &Ty) -> bool {316        match ty {317            Ty::Unit | Ty::Scalar { .. } | Ty::U128 | Ty::I128 | Ty::Code => true,318            Ty::Hand { name, .. } => name == "Color" || name == "Code",319            Ty::Tuple { items } => items.iter().all(|t| self.is_copy(t)),320            Ty::Array { item, .. } | Ty::Option { item } => self.is_copy(item),321            Ty::Plain { path } | Ty::Enum { path } | Ty::Class { path, .. } => {322                self.ty(path).is_some_and(|t| derives(t, "Copy"))323            }324            _ => false,325        }326    }327328    // CROSSINGS IN329330    fn cross_in(&self, ty: &Ty, e: &str, cx: Cx, depth: usize) -> Result<String> {331        if pure_in(ty) {332            return Ok(format!("hand::from_js::<{}>({e})?", self.rust_ty(ty, cx)?));333        }334        let x = format!("x{depth}");335        Ok(match ty {336            Ty::Scalar { name } => match name.as_str() {337                "u64" => format!("hand::u64_from_js({e})?"),338                "i64" => format!("hand::i64_from_js({e})?"),339                "bool" => format!("hand::bool_from_js({e})?"),340                "char" => format!("hand::char_from_js({e})?"),341                other => format!("(hand::number_from_js({e})? as {other})"),342            },343            Ty::Str | Ty::String => format!("hand::string_from_js({e})?"),344            Ty::U128 => format!("hand::u128_from_js({e})?"),345            Ty::I128 => format!("hand::i128_from_js({e})?"),346            Ty::Code => format!("hand::code_from_js({e})?"),347            Ty::Json => format!("hand::json_from_js({e})?"),348            Ty::Vec { item } | Ty::Slice { item, .. } | Ty::Set { item } => format!(349                "hand::list_from_js({e}, {})?",350                closure(&x, ok(self.cross_in(item, &x, cx, depth + 1)?))351            ),352            Ty::Option { item } => format!(353                "hand::option_from_js({e}, {})?",354                closure(&x, ok(self.cross_in(item, &x, cx, depth + 1)?))355            ),356            Ty::Tuple { items } => {357                let mut parts = vec![];358                for (i, t) in items.iter().enumerate() {359                    parts.push(self.cross_in(t, &format!("&hand::item({e}, {i})?"), cx, depth + 1)?);360                }361                format!("({})", parts.join(", "))362            }363            Ty::Array { item, len } => format!(364                "hand::array_from_js::<_, {len}>({e}, {})?",365                closure(&x, ok(self.cross_in(item, &x, cx, depth + 1)?))366            ),367            Ty::Ref { item, .. } | Ty::Result { item } => self.cross_in(item, e, cx, depth)?,368            Ty::Map { key, value } => format!(369                "hand::map_from_js::<{}, _>({e}, {})?",370                self.rust_ty(key, cx)?,371                closure(&x, ok(self.cross_in(value, &x, cx, depth + 1)?))372            ),373            Ty::Hand { name, dim } => match name.as_str() {374                "Tensor" => format!("hand::tensor_from_js({e})?"),375                "Cell" => format!("hand::cell_from_js({e})?"),376                "CellNd" => match dim.or(cx.dim) {377                    Some(2) => format!("hand::cell2d_from_js({e})?"),378                    Some(3) => format!("hand::cell3d_from_js({e})?"),379                    _ => return Err(format!("{}: a cell without a dimension", cx.fname)),380                },381                "Cell6d" => format!("hand::cell6d_from_js({e})?"),382                "Color" => format!("hand::color_from_js({e})?"),383                "Code" => format!("hand::code_from_js({e})?"),384                other => return Err(format!("{}: {other} cannot cross in by value", cx.fname)),385            },386            Ty::Class { .. } => format!(387                "hand::from_js::<{}>(&hand::plain({e})?)?",388                self.rust_ty(ty, cx)?389            ),390            Ty::Unit | Ty::Plain { .. } | Ty::Enum { .. } => {391                format!("hand::from_js::<{}>({e})?", self.rust_ty(ty, cx)?)392            }393            Ty::Opaque { path } => return Err(format!("{}: {path} cannot cross", cx.fname)),394            Ty::Unknown { text } => return Err(format!("{}: {text} cannot cross", cx.fname)),395        })396    }397398    // CROSSINGS OUT399400    fn cross_out(&self, ty: &Ty, e: &str, cx: Cx, depth: usize) -> Result<String> {401        if pure_out(ty) {402            return Ok(format!("hand::to_js({e})?"));403        }404        let x = format!("x{depth}");405        Ok(match ty {406            Ty::Unit => "JsValue::UNDEFINED".into(),407            Ty::Scalar { name } => match name.as_str() {408                "u64" | "i64" => format!("JsValue::from({})", deref(e)),409                "bool" => format!("JsValue::from_bool({})", deref(e)),410                "char" => format!("JsValue::from_str(&{}.to_string())", place(e)),411                _ => format!("JsValue::from_f64({} as f64)", deref(e)),412            },413            Ty::Str | Ty::String => format!("JsValue::from_str({e})"),414            Ty::U128 | Ty::I128 => format!("JsValue::from_str(&{}.to_string())", place(e)),415            Ty::Code => format!("JsValue::from_str(&hand::code_to_js({}))", deref(e)),416            Ty::Json => format!("hand::json_to_js({e})?"),417            Ty::Vec { item } | Ty::Slice { item, .. } | Ty::Array { item, .. }418                if typed_scalar(item) =>419            {420                format!("hand::typed(&({})[..])", deref(e))421            }422            Ty::Vec { item } | Ty::Slice { item, .. } | Ty::Array { item, .. } | Ty::Set { item } => {423                format!(424                    "hand::list_to_js({e}, {})?",425                    closure(&x, ok(self.cross_out(item, &x, cx, depth + 1)?))426                )427            }428            Ty::Option { item } => match &**item {429                Ty::Ref { item: inner, .. } => format!(430                    "hand::option_to_js({}, {})?",431                    deref(e),432                    closure(&x, ok(self.cross_out(inner, &x, cx, depth + 1)?))433                ),434                _ => format!(435                    "hand::option_to_js({}.as_ref(), {})?",436                    place(e),437                    closure(&x, ok(self.cross_out(item, &x, cx, depth + 1)?))438                ),439            },440            Ty::Tuple { items } => {441                let mut parts = vec![];442                for (i, t) in items.iter().enumerate() {443                    parts.push(self.cross_out(t, &format!("&{}.{i}", place(e)), cx, depth + 1)?);444                }445                format!("hand::tuple_to_js(&[{}])", parts.join(", "))446            }447            Ty::Ref { item, .. } | Ty::Result { item } => {448                self.cross_out(item, &format!("({})", deref(e)), cx, depth)?449            }450            Ty::Map { value, .. } => format!(451                "hand::map_to_js({e}, {})?",452                closure(&x, ok(self.cross_out(value, &x, cx, depth + 1)?))453            ),454            Ty::Hand { name, dim } => match name.as_str() {455                "Tensor" => format!("hand::tensor_to_js({e})?"),456                "Cell" => format!("hand::cell_to_js({e})?"),457                "CellNd" => match dim.or(cx.dim) {458                    Some(2) => format!("hand::cell2d_to_js({e})?"),459                    Some(3) => format!("hand::cell3d_to_js({e})?"),460                    _ => return Err(format!("{}: a cell without a dimension", cx.fname)),461                },462                "Cell6d" => format!("hand::cell6d_to_js({e})?"),463                "Color" => format!("hand::color_to_js({})", deref(e)),464                "Code" => format!("JsValue::from_str(&hand::code_to_js({}))", deref(e)),465                _ => format!("JsValue::from(hand::Rng::wrap({}.clone()))", place(e)),466            },467            Ty::Class { path, .. } if self.holds(path) => {468                let inner = if self.is_copy(ty) { deref(e) } else { format!("{}.clone()", place(e)) };469                format!("JsValue::from({} {{ inner: {inner} }})", self.local(path))470            }471            Ty::Class { .. } | Ty::Plain { .. } | Ty::Enum { .. } => format!("hand::to_js({e})?"),472            Ty::Opaque { path } => return Err(format!("{}: {path} cannot cross", cx.fname)),473            Ty::Unknown { text } => return Err(format!("{}: {text} cannot cross", cx.fname)),474        })475    }476477    fn ret_plan(&self, ty: &Ty, cx: Cx) -> Result<RetPlan> {478        let plan = |sig: &str, done: String| RetPlan {479            sig: sig.to_string(),480            done,481        };482        Ok(match ty {483            Ty::Unit => plan("()", "Ok(())".into()),484            Ty::Result { item } => self.ret_plan(item, cx)?,485            Ty::Scalar { name } => plan(name, "Ok(value)".into()),486            Ty::String => plan("String", "Ok(value)".into()),487            Ty::Str => plan("String", "Ok(value.to_string())".into()),488            Ty::Vec { item } if typed_scalar(item) => {489                plan(&format!("Vec<{}>", self.rust_ty(item, cx)?), "Ok(value)".into())490            }491            Ty::Slice { item, .. } | Ty::Array { item, .. } if typed_scalar(item) => plan(492                &format!("Vec<{}>", self.rust_ty(item, cx)?),493                "Ok(value.to_vec())".into(),494            ),495            Ty::Class { path, .. } if self.holds(path) => {496                let ident = self.local(path);497                plan(&ident, format!("Ok({ident} {{ inner: value }})"))498            }499            Ty::Option { item } => match &**item {500                Ty::Class { path, .. } if self.holds(path) => {501                    let ident = self.local(path);502                    plan(503                        &format!("Option<{ident}>"),504                        format!("Ok(value.map(|inner| {ident} {{ inner }}))"),505                    )506                }507                _ => plan("JsValue", ok(self.cross_out(ty, "&value", cx, 1)?)),508            },509            Ty::Hand { name, .. } if name == "Rng" => {510                plan("hand::Rng", "Ok(hand::Rng::wrap(value))".into())511            }512            Ty::Ref { item, .. } => match &**item {513                Ty::Slice { item: inner, .. } | Ty::Array { item: inner, .. }514                    if typed_scalar(inner) =>515                {516                    plan(517                        &format!("Vec<{}>", self.rust_ty(inner, cx)?),518                        "Ok(value.to_vec())".into(),519                    )520                }521                Ty::Str => plan("String", "Ok(value.to_string())".into()),522                _ => plan("JsValue", ok(self.cross_out(item, "value", cx, 1)?)),523            },524            Ty::Slice { .. } => plan("JsValue", ok(self.cross_out(ty, "value", cx, 1)?)),525            _ => plan("JsValue", ok(self.cross_out(ty, "&value", cx, 1)?)),526        })527    }528529    // PARAMETERS530531    fn plan_param(&self, name: &str, ty: &Ty, cx: Cx, extra: bool) -> Result<ParamPlan> {532        let n = ident(name);533        let js = |pre: Vec<String>, arg: String, post: Vec<String>| ParamPlan {534            name: n.clone(),535            sig: "JsValue".into(),536            pre,537            arg,538            post,539        };540        let native = |sig: String, arg: String| ParamPlan {541            name: n.clone(),542            sig,543            pre: vec![],544            arg,545            post: vec![],546        };547        let read = |inner: &Ty| -> Result<String> {548            let converted = self.cross_in(inner, &format!("&{n}"), cx, 1)?;549            Ok(if extra {550                let want = format!("a {}d cell wants {name}.", cx.dim.unwrap_or(0));551                format!("let {n} = if {n}.is_undefined() {{ return Err(hand::refuse(\"{want}\")); }} else {{ {converted} }};")552            } else {553                format!("let {n} = {converted};")554            })555        };556        let owned = |inner: &Ty, arg: String| -> Result<ParamPlan> {557            Ok(js(vec![read(inner)?], arg, vec![]))558        };559        if !extra {560            match ty {561                Ty::Scalar { name } if !is_wide(name) => return Ok(native(name.clone(), n.clone())),562                Ty::Str => return Ok(native("&str".into(), n.clone())),563                Ty::String => return Ok(native("String".into(), n.clone())),564                Ty::Slice { item, .. } if native_item(item) => {565                    return Ok(native(format!("&[{}]", self.rust_ty(item, cx)?), n.clone()))566                }567                Ty::Vec { item } if native_item(item) => {568                    return Ok(native(format!("Vec<{}>", self.rust_ty(item, cx)?), n.clone()))569                }570                Ty::Class { path, .. } if self.holds(path) => {571                    let taken = if self.is_copy(ty) { format!("{n}.inner") } else { format!("{n}.inner.clone()") };572                    return Ok(native(format!("&{}", self.local(path)), taken));573                }574                Ty::Ref { mutable, item, .. } => match &**item {575                    Ty::Class { path, .. } if self.holds(path) => {576                        let ident = self.local(path);577                        return Ok(if *mutable {578                            native(format!("&mut {ident}"), format!("&mut {n}.inner"))579                        } else {580                            native(format!("&{ident}"), format!("&{n}.inner"))581                        });582                    }583                    Ty::Hand { name, .. } if name == "Rng" && *mutable => {584                        return Ok(native("&mut hand::Rng".into(), format!("{n}.stream()")))585                    }586                    _ => {}587                },588                _ => {}589            }590        }591        Ok(match ty {592            Ty::Ref { mutable: true, item, .. } => match &**item {593                Ty::Hand { name, .. } if name == "Tensor" || name == "Cell" => {594                    let back = if name == "Tensor" { "tensor_into_js" } else { "cell_into_js" };595                    let converted = self.cross_in(item, &format!("&{n}"), cx, 1)?;596                    js(597                        vec![format!("let mut {n}_value = {converted};")],598                        format!("&mut {n}_value"),599                        vec![format!("hand::{back}(&{n}, &{n}_value)?;")],600                    )601                }602                _ => return Err(format!("{}: {name} mutates a value that cannot cross", cx.fname)),603            },604            Ty::Ref { item, .. } => owned(item, format!("&{n}"))?,605            Ty::Slice { item, .. } => match self.view(item, &n, cx)? {606                Some(seen) => {607                    let mut pre = vec![read(ty)?];608                    pre.push(seen);609                    js(pre, format!("&{n}_view"), vec![])610                }611                None => owned(ty, format!("&{n}"))?,612            },613            Ty::Option { item } => match &**item {614                Ty::Ref { mutable: true, item: inner, .. } if is_rng(inner) => js(615                    vec![format!("let mut {n}_stream = hand::stream_from_js(&{n})?;")],616                    format!("{n}_stream.as_mut()"),617                    vec![format!(618                        "if let Some(stream) = &{n}_stream {{ hand::stream_to_js(&{n}, stream)?; }}"619                    )],620                ),621                Ty::Ref { item: inner, .. } if matches!(**inner, Ty::Slice { .. }) => {622                    owned(ty, format!("{n}.as_deref()"))?623                }624                Ty::Ref { .. } => owned(ty, format!("{n}.as_ref()"))?,625                Ty::Slice { .. } => owned(ty, format!("{n}.as_deref()"))?,626                _ => owned(ty, n.clone())?,627            },628            _ => owned(ty, n.clone())?,629        })630    }631632    fn view(&self, item: &Ty, n: &str, cx: Cx) -> Result<Option<String>> {633        Ok(match item {634            Ty::Str => Some(format!(635                "let {n}_view: Vec<&str> = {n}.iter().map(String::as_str).collect();"636            )),637            Ty::Slice { item: inner, .. } => Some(format!(638                "let {n}_view: Vec<&[{}]> = {n}.iter().map(Vec::as_slice).collect();",639                self.rust_ty(inner, cx)?640            )),641            Ty::Tuple { items } if items.iter().any(|t| matches!(t, Ty::Str)) => {642                let names: Vec<String> = (0..items.len()).map(|i| format!("a{i}")).collect();643                let parts: Vec<String> = items644                    .iter()645                    .zip(&names)646                    .map(|(t, a)| match t {647                        Ty::Str => format!("{a}.as_str()"),648                        Ty::Scalar { .. } => format!("*{a}"),649                        _ => format!("{a}.clone()"),650                    })651                    .collect();652                let mut tys = vec![];653                for t in items {654                    tys.push(match t {655                        Ty::Str => "&str".to_string(),656                        other => self.rust_ty(other, cx)?,657                    });658                }659                Some(format!(660                    "let {n}_view: Vec<({})> = {n}.iter().map(|({})| ({})).collect();",661                    tys.join(", "),662                    names.join(", "),663                    parts.join(", ")664                ))665            }666            _ => None,667        })668    }669670    fn self_param(&self, f: &Function, owner: &Type) -> Result<Option<(String, Ty)>> {671        let Some(kind) = f.self_kind else {672            return Ok(None);673        };674        let name = self.hand_name(owner);675        let inner = Ty::Hand {676            name: name.to_string(),677            dim: None,678        };679        let taken = f.params.iter().any(|p| p.name == hand_self(name));680        let label = if taken { "self_".to_string() } else { hand_self(name).to_string() };681        let ty = match (kind, name) {682            (SelfKind::Mut, _) => Ty::Ref {683                mutable: true,684                lifetime: None,685                item: Box::new(inner),686            },687            (_, "Rng") => return Err(format!("{}: an Rng self must be mutable", f.path)),688            _ => inner,689        };690        Ok(Some((label, ty)))691    }692693    // FUNCTIONS694695    fn rank_source(&self, params: &[(String, Ty, bool)]) -> Option<String> {696        fn cell(t: &Ty) -> bool {697            matches!(t, Ty::Hand { name, dim: None } if name == "CellNd")698        }699        fn shaped(t: &Ty) -> bool {700            matches!(t, Ty::Hand { name, .. } if name == "Tensor" || name == "Cell" || name == "CellNd")701        }702        for test in [cell, shaped] {703            for (name, ty, _) in params {704                let n = ident(name);705                match ty {706                    t if test(t) => return Some(format!("hand::cell_rank(&{n})?")),707                    Ty::Ref { item, .. } if test(item) => {708                        return Some(format!("hand::cell_rank(&{n})?"))709                    }710                    Ty::Slice { item, .. } | Ty::Vec { item } if test(item) => {711                        return Some(format!("hand::cell_rank(&hand::first(&{n})?)?"))712                    }713                    _ => {}714                }715            }716        }717        None718    }719720    fn emit_group(&self, out: &mut String, group: &[&Function], owner: Owner) -> Result<()> {721        let head = group[0];722        let dispatch = group.len() > 1 || !head.dims.is_empty();723        let name = match owner {724            Owner::Free | Owner::Hand(_) => self.local(&head.path),725            _ => head.name.clone(),726        };727        let mut merged: Vec<(String, Ty, bool)> = vec![];728        for f in group {729            if let Owner::Hand(t) = owner {730                if let Some((label, ty)) = self.self_param(f, t)? {731                    if !merged.iter().any(|(n, _, _)| *n == label) {732                        merged.push((label, ty, false));733                    }734                }735            }736            if let Owner::Holder(t) = owner {737                if f.self_kind.is_some() {738                    let label = t.name.to_lowercase();739                    let label = if f.params.iter().any(|p| p.name == label) {740                        "self_".to_string()741                    } else {742                        label743                    };744                    if !merged.iter().any(|(n, _, _)| *n == label) {745                        merged.push((label, plain_ty(t), false));746                    }747                }748            }749            for p in &f.params {750                if !merged.iter().any(|(n, _, _)| *n == p.name) {751                    merged.push((p.name.clone(), p.ty.clone(), false));752                }753            }754        }755        let shared: Vec<String> = merged756            .iter()757            .filter(|(n, _, _)| group.iter().all(|f| self.mentions(f, owner, n)))758            .map(|(n, _, _)| n.clone())759            .collect();760        for slot in &mut merged {761            slot.2 = !shared.contains(&slot.0);762        }763        let cx = Cx {764            dim: head.dims.first().copied(),765            fname: &head.path,766        };767        let ret = self.ret_plan(&head.ret, cx)?;768        let mut sigs: Vec<String> = vec![];769        let mut plans: Vec<ParamPlan> = vec![];770        for (n, ty, extra) in &merged {771            let plan = self.plan_param(n, ty, cx, *extra)?;772            sigs.push(format!("{}: {}", plan.name, plan.sig));773            plans.push(plan);774        }775        if let Owner::Class(_) = owner {776            let receiver = match head.self_kind {777                Some(SelfKind::Mut) => Some("&mut self"),778                Some(_) => Some("&self"),779                None => None,780            };781            if let Some(receiver) = receiver {782                sigs.insert(0, receiver.to_string());783            }784        }785        let doc = doc_line(&head.docs);786        let indent = if matches!(owner, Owner::Class(_) | Owner::Holder(_)) { "    " } else { "" };787        let constructor = matches!(owner, Owner::Class(_)) && head.name == "new" && head.self_kind.is_none();788        let renamed = match owner {789            Owner::Holder(_) => head.name == "new" || head.name == "default",790            Owner::Class(_) => head.name == "default",791            Owner::Free | Owner::Hand(_) => false,792        };793        if !doc.is_empty() {794            writeln!(out, "{indent}/// {doc}").ok();795        }796        if constructor {797            writeln!(out, "{indent}#[wasm_bindgen(constructor)]").ok();798        } else if renamed {799            writeln!(out, "{indent}#[wasm_bindgen(js_name = \"{}\")]", head.name).ok();800        } else if indent.is_empty() {801            writeln!(out, "#[wasm_bindgen]").ok();802        }803        let rust_name = if renamed {804            format!("{}_", head.name)805        } else if indent.is_empty() {806            self.export(&name)807        } else {808            name.clone()809        };810        writeln!(811            out,812            "{indent}pub fn {rust_name}({}) -> Result<{}, JsValue> {{",813            sigs.join(", "),814            ret.sig815        )816        .ok();817        if dispatch {818            let rank = self819                .rank_source(&merged)820                .ok_or_else(|| format!("{}: no cell to dispatch on", head.path))?;821            writeln!(out, "{indent}    match {rank} {{").ok();822            let mut dims: Vec<u8> = group.iter().flat_map(|f| f.dims.iter().copied()).collect();823            dims.sort();824            dims.dedup();825            for dim in dims {826                let f = group827                    .iter()828                    .find(|f| f.dims.contains(&dim))829                    .ok_or_else(|| format!("{}: no entry for {dim}d", head.path))?;830                let cx = Cx {831                    dim: Some(dim),832                    fname: &head.path,833                };834                let ret = self.ret_plan(&f.ret, cx)?;835                let mut plans: Vec<ParamPlan> = vec![];836                for (n, ty, extra) in &merged {837                    if !self.mentions(f, owner, n) {838                        continue;839                    }840                    plans.push(self.plan_param(n, ty, cx, *extra)?);841                }842                writeln!(out, "{indent}        {dim} => {{").ok();843                out.push_str(&self.emit_body(f, owner, &plans, &ret, cx, &format!("{indent}            "))?);844                writeln!(out, "{indent}        }}").ok();845            }846            writeln!(847                out,848                "{indent}        rank => Err(hand::refuse(&format!(\"a 2d or 3d cell was wanted, not {{rank}}d.\"))),"849            )850            .ok();851            writeln!(out, "{indent}    }}").ok();852        } else {853            out.push_str(&self.emit_body(head, owner, &plans, &ret, cx, &format!("{indent}    "))?);854        }855        writeln!(out, "{indent}}}").ok();856        Ok(())857    }858859    fn mentions(&self, f: &Function, owner: Owner, name: &str) -> bool {860        if f.params.iter().any(|p| p.name == name) {861            return true;862        }863        match owner {864            Owner::Hand(t) => f.self_kind.is_some() && {865                let label = hand_self(self.hand_name(t));866                let taken = f.params.iter().any(|p| p.name == label);867                (if taken { "self_" } else { label }) == name868            },869            Owner::Holder(t) => f.self_kind.is_some() && {870                let label = t.name.to_lowercase();871                let taken = f.params.iter().any(|p| p.name == label);872                (if taken { "self_".to_string() } else { label }) == name873            },874            _ => false,875        }876    }877878    fn emit_body(879        &self,880        f: &Function,881        owner: Owner,882        plans: &[ParamPlan],883        ret: &RetPlan,884        cx: Cx,885        pad: &str,886    ) -> Result<String> {887        let mut out = String::new();888        for plan in plans {889            for line in &plan.pre {890                writeln!(out, "{pad}{line}").ok();891            }892        }893        let (receiver, rest): (Option<&ParamPlan>, &[ParamPlan]) = match owner {894            Owner::Hand(_) | Owner::Holder(_) if f.self_kind.is_some() => {895                (plans.first(), &plans[1..])896            }897            _ => (None, plans),898        };899        let args: Vec<String> = rest.iter().map(|p| p.arg.clone()).collect();900        let args = args.join(", ");901        let turbo = if f.dims.is_empty() { String::new() } else { format!("::<{}>", cx.dim.unwrap_or(2)) };902        let call = match owner {903            Owner::Free => format!("mrlyrs::{}{turbo}({args})", f.path),904            Owner::Hand(t) => match receiver {905                Some(r) => {906                    let name = match r.post.is_empty() {907                        true => r.arg.trim_start_matches('&').to_string(),908                        false => format!("{}_value", r.name),909                    };910                    let name = if self.hand_name(t) == "Rng" { r.arg.clone() } else { name };911                    format!("{name}.{}({args})", f.name)912                }913                None => {914                    let base = self.rust_ty(&Ty::Hand { name: self.hand_name(t).into(), dim: cx.dim }, cx)?;915                    let base = if self.hand_name(t) == "CellNd" {916                        format!("mrlyrs::math::cell::models::CellNd::<{}>", cx.dim.unwrap_or(2))917                    } else {918                        base919                    };920                    format!("{base}::{}({args})", f.name)921                }922            },923            Owner::Class(t) => {924                let base = format!("mrlyrs::{}", t.path);925                let copied = self.ty(&t.path).is_some_and(|t| derives(t, "Copy"));926                let me = match f.self_kind {927                    Some(SelfKind::Value) if copied => "self.inner".to_string(),928                    Some(SelfKind::Value) => "self.inner.clone()".to_string(),929                    Some(SelfKind::Ref) => "&self.inner".to_string(),930                    Some(SelfKind::Mut) => "&mut self.inner".to_string(),931                    None => String::new(),932                };933                match (&f.via, f.self_kind) {934                    (Some(via), None) => format!("<{base} as mrlyrs::{via}>::{}({args})", f.name),935                    (Some(via), Some(_)) => {936                        let all = if args.is_empty() { me } else { format!("{me}, {args}") };937                        format!("<{base} as mrlyrs::{via}>::{}({all})", f.name)938                    }939                    (None, None) => format!("{base}::{}({args})", f.name),940                    (None, Some(SelfKind::Value)) => format!("{me}.{}({args})", f.name),941                    (None, Some(_)) => format!("self.inner.{}({args})", f.name),942                }943            }944            Owner::Holder(t) => match receiver {945                Some(r) => format!("{}.{}({args})", r.arg, f.name),946                None => format!("mrlyrs::{}::{}({args})", t.path, f.name),947            },948        };949        let fallible = matches!(f.ret, Ty::Result { .. });950        let unit = match &f.ret {951            Ty::Unit => true,952            Ty::Result { item } => matches!(**item, Ty::Unit),953            _ => false,954        };955        match (unit, fallible) {956            (true, true) => writeln!(out, "{pad}{call}.map_err(hand::throw)?;").ok(),957            (true, false) => writeln!(out, "{pad}{call};").ok(),958            (false, true) => writeln!(out, "{pad}let value = {call}.map_err(hand::throw)?;").ok(),959            (false, false) => writeln!(out, "{pad}let value = {call};").ok(),960        };961        for plan in plans {962            for line in &plan.post {963                writeln!(out, "{pad}{line}").ok();964            }965        }966        writeln!(out, "{pad}{}", ret.done).ok();967        Ok(out)968    }969970    fn emit_const(&self, out: &mut String, c: &Const) -> Result<()> {971        let cx = Cx {972            dim: None,973            fname: &c.path,974        };975        let ret = self.ret_plan(&c.ty, cx)?;976        let doc = doc_line(&c.docs);977        if !doc.is_empty() {978            writeln!(out, "/// {doc}").ok();979        }980        let name = self.export(&self.local(&c.path));981        writeln!(out, "#[wasm_bindgen]").ok();982        writeln!(out, "pub fn {name}() -> Result<{}, JsValue> {{", ret.sig).ok();983        writeln!(out, "    let value = mrlyrs::{};", c.path).ok();984        writeln!(out, "    {}", ret.done).ok();985        writeln!(out, "}}").ok();986        Ok(())987    }988989    fn getter_ok(&self, ty: &Ty) -> bool {990        let mut ok = true;991        ty.walk(&mut |t| match t {992            Ty::Unknown { .. } | Ty::Opaque { .. } => ok = false,993            Ty::Class { path, .. } if !self.holds(path) => ok = false,994            Ty::Ref { lifetime: Some(l), item, .. } if l == "'static" && !matches!(**item, Ty::Str) => ok = false,995            _ => {}996        });997        ok998    }9991000    fn emit_class(&self, out: &mut String, t: &Type, groups: &[Vec<&Function>]) -> Result<()> {1001        let ident = self.local(&t.path);1002        let cx = Cx {1003            dim: None,1004            fname: &t.path,1005        };1006        let doc = doc_line(&t.docs);1007        if !doc.is_empty() {1008            writeln!(out, "/// {doc}").ok();1009        }1010        writeln!(out, "#[wasm_bindgen]").ok();1011        writeln!(out, "pub struct {ident} {{\n    inner: mrlyrs::{},\n}}\n", t.path).ok();1012        writeln!(out, "#[wasm_bindgen]\nimpl {ident} {{").ok();1013        if derives(t, "Deserialize") {1014            writeln!(out, "    /// Reads the {} from its plain data.", t.name).ok();1015            writeln!(out, "    #[wasm_bindgen(js_name = \"from\")]").ok();1016            writeln!(out, "    pub fn from_plain(data: JsValue) -> Result<{ident}, JsValue> {{").ok();1017            writeln!(out, "        Ok({ident} {{ inner: hand::from_js(&data)? }})").ok();1018            writeln!(out, "    }}").ok();1019        }1020        if derives(t, "Serialize") {1021            writeln!(out, "    /// Writes the {} as plain data.", t.name).ok();1022            writeln!(out, "    #[wasm_bindgen(js_name = \"toJSON\")]").ok();1023            writeln!(out, "    pub fn to_plain(&self) -> Result<JsValue, JsValue> {{").ok();1024            writeln!(out, "        hand::to_js(&self.inner)").ok();1025            writeln!(out, "    }}").ok();1026        }1027        let methods: BTreeSet<&str> = groups.iter().map(|g| g[0].name.as_str()).collect();1028        for field in t.fields.iter().filter(|f| f.public) {1029            if !self.getter_ok(&field.ty) || methods.contains(field.name.as_str()) {1030                continue;1031            }1032            let ret = self.ret_plan(&field.ty, cx)?;1033            let doc = doc_line(&field.docs);1034            if !doc.is_empty() {1035                writeln!(out, "    /// {doc}").ok();1036            }1037            writeln!(out, "    #[wasm_bindgen(getter)]").ok();1038            writeln!(1039                out,1040                "    pub fn {}(&self) -> Result<{}, JsValue> {{",1041                field.name,1042                ret.sig1043            )1044            .ok();1045            let value = match field.ty {1046                Ty::Ref { .. } => format!("self.inner.{}", field.name),1047                _ if self.is_copy(&field.ty) => format!("self.inner.{}", field.name),1048                _ => format!("self.inner.{}.clone()", field.name),1049            };1050            writeln!(out, "        let value = {value};").ok();1051            writeln!(out, "        {}", ret.done).ok();1052            writeln!(out, "    }}").ok();1053            if field.ty.settable() {1054                let plan = self.plan_param("value", &field.ty, cx, false)?;1055                writeln!(out, "    #[wasm_bindgen(setter)]").ok();1056                writeln!(1057                    out,1058                    "    pub fn set_{}(&mut self, {}: {}) -> Result<(), JsValue> {{",1059                    field.name, plan.name, plan.sig1060                )1061                .ok();1062                for line in &plan.pre {1063                    writeln!(out, "        {line}").ok();1064                }1065                writeln!(out, "        self.inner.{} = {};", field.name, plan.arg).ok();1066                for line in &plan.post {1067                    writeln!(out, "        {line}").ok();1068                }1069                writeln!(out, "        Ok(())").ok();1070                writeln!(out, "    }}").ok();1071            }1072        }1073        for group in groups {1074            self.emit_group(out, group, Owner::Class(t))?;1075        }1076        writeln!(out, "}}").ok();1077        Ok(())1078    }10791080    fn emit_holder(&self, out: &mut String, t: &Type, groups: &[Vec<&Function>]) -> Result<()> {1081        let ident = self.local(&t.path);1082        let doc = doc_line(&t.docs);1083        if !doc.is_empty() {1084            writeln!(out, "/// {doc}").ok();1085        }1086        writeln!(out, "#[wasm_bindgen]").ok();1087        writeln!(out, "pub struct {ident} {{}}\n").ok();1088        writeln!(out, "#[wasm_bindgen]\nimpl {ident} {{").ok();1089        for group in groups {1090            self.emit_group(out, group, Owner::Holder(t))?;1091        }1092        writeln!(out, "}}").ok();1093        Ok(())1094    }10951096    fn lib_rs(&self) -> Result<String> {1097        let mut out = String::new();1098        writeln!(out, "{ALLOWS}\n\nmod hand;\n\nuse wasm_bindgen::prelude::*;\n").ok();1099        let groups = self.groups();1100        let mut owned: BTreeMap<&str, Vec<Vec<&Function>>> = BTreeMap::new();1101        for group in &groups {1102            match self.owner_of(group[0]) {1103                Owner::Free | Owner::Hand(_) => {1104                    self.emit_group(&mut out, group, self.owner_of(group[0]))?;1105                    out.push('\n');1106                }1107                Owner::Class(t) | Owner::Holder(t) => {1108                    owned.entry(t.path.as_str()).or_default().push(group.clone());1109                }1110            }1111        }1112        for c in self.consts() {1113            self.emit_const(&mut out, c)?;1114            out.push('\n');1115        }1116        for t in self.classes() {1117            let groups = owned.get(t.path.as_str()).cloned().unwrap_or_default();1118            self.emit_class(&mut out, t, &groups)?;1119            out.push('\n');1120        }1121        for t in self.holders() {1122            let groups = owned.get(t.path.as_str()).cloned().unwrap_or_default();1123            self.emit_holder(&mut out, t, &groups)?;1124            out.push('\n');1125        }1126        while out.ends_with("\n\n") {1127            out.pop();1128        }1129        Ok(out)1130    }11311132    // WRAPPER11331134    fn tree(&self) -> Tree {1135        let mut tree = Tree::default();1136        let mut seen: BTreeSet<String> = BTreeSet::new();1137        for f in self.functions() {1138            let (home, leaf) = match self.owner_of(f) {1139                Owner::Free | Owner::Hand(_) => (f.module.clone(), leaf(&f.path)),1140                Owner::Class(t) | Owner::Holder(t) => (parent(&t.path), t.name.clone()),1141            };1142            let key = format!("{home}::{leaf}");1143            if seen.insert(key) {1144                let export = match self.owner_of(f) {1145                    Owner::Free | Owner::Hand(_) => self.export(&self.local(&f.path)),1146                    Owner::Class(t) | Owner::Holder(t) => self.local(&t.path),1147                };1148                if JS_RESERVED.contains(&leaf.as_str()) {1149                    tree.insert(self.rel(&home), format!("{leaf}_"), export.clone());1150                }1151                tree.insert(self.rel(&home), leaf, export);1152            }1153        }1154        for t in self.classes() {1155            let key = format!("{}::{}", parent(&t.path), t.name);1156            if seen.insert(key) {1157                tree.insert(self.rel(&parent(&t.path)), t.name.clone(), self.local(&t.path));1158            }1159        }1160        for c in self.consts() {1161            let export = self.export(&self.local(&c.path));1162            tree.insert(self.rel(&parent(&c.path)), c.name.clone(), export);1163        }1164        tree1165    }11661167    fn wrapper_js(&self) -> String {1168        let name = self.name;1169        let mut out = String::new();1170        writeln!(out, "import * as wasm from \"./pkg/{name}/mrlyjs_{name}.js\";\n").ok();1171        writeln!(out, "export {{ default, initSync }} from \"./pkg/{name}/mrlyjs_{name}.js\";").ok();1172        writeln!(out, "export const Rng = wasm.Rng;").ok();1173        let tree = self.tree();1174        for (leaf, export) in &tree.leaves {1175            writeln!(out, "export const {leaf} = wasm.{export};").ok();1176        }1177        for (seg, child) in &tree.children {1178            writeln!(out, "export const {seg} = {};", child.literal(1)).ok();1179        }1180        out1181    }11821183    // TYPES11841185    fn ts_ref(&self, path: &str) -> String {1186        self.rel(path).replace("::", ".")1187    }11881189    fn ts_direct(&self, ty: &Ty, ret: bool) -> String {1190        match ty {1191            Ty::Unit => "void".into(),1192            Ty::Scalar { name } => match name.as_str() {1193                "bool" => "boolean".into(),1194                "char" => "string".into(),1195                "u64" | "i64" if ret => "bigint".into(),1196                "u64" | "i64" => "number | bigint".into(),1197                _ => "number".into(),1198            },1199            Ty::Str | Ty::String => "string".into(),1200            Ty::U128 | Ty::I128 | Ty::Code if ret => "string".into(),1201            Ty::U128 | Ty::I128 | Ty::Code => "string | number | bigint".into(),1202            Ty::Json => "any".into(),1203            Ty::Vec { item } | Ty::Slice { item, .. } | Ty::Array { item, .. } | Ty::Set { item } => {1204                match &**item {1205                    Ty::Scalar { name } if TYPED.contains(&name.as_str()) => {1206                        if ret {1207                            typed_array(name).into()1208                        } else if is_wide(name) {1209                            "ArrayLike<number | bigint>".into()1210                        } else {1211                            "ArrayLike<number>".into()1212                        }1213                    }1214                    _ => format!("{}[]", self.ts_wrap(&self.ts_direct(item, ret))),1215                }1216            }1217            Ty::Option { item } => format!("{} | undefined", self.ts_direct(item, ret)),1218            Ty::Tuple { items } => format!(1219                "[{}]",1220                items1221                    .iter()1222                    .map(|t| self.ts_direct(t, ret))1223                    .collect::<Vec<_>>()1224                    .join(", ")1225            ),1226            Ty::Ref { item, .. } | Ty::Result { item } => self.ts_direct(item, ret),1227            Ty::Map { value, .. } => format!("Record<string, {}>", self.ts_direct(value, ret)),1228            Ty::Hand { name, .. } => match name.as_str() {1229                "Tensor" => "Tensor".into(),1230                "Cell" | "CellNd" => "Cell".into(),1231                "Cell6d" => "Cell6d".into(),1232                "Color" => "Color".into(),1233                "Code" if ret => "string".into(),1234                "Code" => "string | number | bigint".into(),1235                _ => "Rng".into(),1236            },1237            Ty::Plain { path } | Ty::Enum { path } | Ty::Class { path, .. } => self.ts_ref(path),1238            Ty::Opaque { .. } | Ty::Unknown { .. } => "unknown".into(),1239        }1240    }12411242    fn ts_wrap(&self, text: &str) -> String {1243        if text.contains(" | ") {1244            format!("({text})")1245        } else {1246            text.to_string()1247        }1248    }12491250    fn ts_serde(&self, ty: &Ty) -> String {1251        match ty {1252            Ty::Unit => "null".into(),1253            Ty::Scalar { name } => match name.as_str() {1254                "bool" => "boolean".into(),1255                "char" => "string".into(),1256                _ => "number".into(),1257            },1258            Ty::Str | Ty::String => "string".into(),1259            Ty::U128 | Ty::I128 | Ty::Code => "bigint".into(),1260            Ty::Json => "any".into(),1261            Ty::Vec { item } | Ty::Slice { item, .. } | Ty::Array { item, .. } | Ty::Set { item } => {1262                format!("{}[]", self.ts_wrap(&self.ts_serde(item)))1263            }1264            Ty::Option { item } => format!("{} | undefined", self.ts_serde(item)),1265            Ty::Tuple { items } => format!(1266                "[{}]",1267                items.iter().map(|t| self.ts_serde(t)).collect::<Vec<_>>().join(", ")1268            ),1269            Ty::Ref { item, .. } | Ty::Result { item } => self.ts_serde(item),1270            Ty::Map { value, .. } => format!("Record<string, {}>", self.ts_serde(value)),1271            Ty::Hand { name, .. } => match name.as_str() {1272                "Tensor" => "TensorData".into(),1273                "Cell" => "CellData".into(),1274                "CellNd" => "{ cell: CellData }".into(),1275                "Cell6d" => "Cell6dData".into(),1276                "Color" => "ColorData".into(),1277                "Code" => "bigint".into(),1278                _ => "unknown".into(),1279            },1280            Ty::Plain { path } | Ty::Enum { path } => self.ts_ref(path),1281            Ty::Class { path, .. } => format!("{}Data", self.ts_ref(path)),1282            Ty::Opaque { .. } | Ty::Unknown { .. } => "string".into(),1283        }1284    }12851286    fn ts_params(&self, params: &[(String, Ty, bool)]) -> String {1287        let last_required = params1288            .iter()1289            .rposition(|(_, ty, extra)| !matches!(ty, Ty::Option { .. }) && !extra)1290            .map(|i| i + 1)1291            .unwrap_or(0);1292        params1293            .iter()1294            .enumerate()1295            .map(|(i, (name, ty, extra))| {1296                let n = ident(name);1297                let inner = match ty {1298                    Ty::Option { item } => item,1299                    _ => ty,1300                };1301                if *extra || (i >= last_required && matches!(ty, Ty::Option { .. })) {1302                    format!("{n}?: {}", self.ts_direct(inner, false))1303                } else {1304                    format!("{n}: {}", self.ts_direct(ty, false))1305                }1306            })1307            .collect::<Vec<_>>()1308            .join(", ")1309    }13101311    fn ts_function(&self, group: &[&Function], owner: Owner) -> (String, String, String) {1312        let head = group[0];1313        let mut merged: Vec<(String, Ty, bool)> = vec![];1314        for f in group {1315            match owner {1316                Owner::Hand(t) => {1317                    if let Ok(Some((label, ty))) = self.self_param(f, t) {1318                        if !merged.iter().any(|(n, _, _)| *n == label) {1319                            merged.push((label, ty, false));1320                        }1321                    }1322                }1323                Owner::Holder(t) if f.self_kind.is_some() => {1324                    let label = t.name.to_lowercase();1325                    let label = if f.params.iter().any(|p| p.name == label) {1326                        "self_".to_string()1327                    } else {1328                        label1329                    };1330                    if !merged.iter().any(|(n, _, _)| *n == label) {1331                        merged.push((label, plain_ty(t), false));1332                    }1333                }1334                _ => {}1335            }1336            for p in &f.params {1337                if !merged.iter().any(|(n, _, _)| *n == p.name) {1338                    merged.push((p.name.clone(), p.ty.clone(), false));1339                }1340            }1341        }1342        for slot in &mut merged {1343            slot.2 = !group.iter().all(|f| self.mentions(f, owner, &slot.0));1344        }1345        let ret = match &head.ret {1346            Ty::Result { item } => self.ts_direct(item, true),1347            other => self.ts_direct(other, true),1348        };1349        (doc_line(&head.docs), self.ts_params(&merged), ret)1350    }13511352    fn referenced(&self) -> BTreeSet<String> {1353        let mut out: BTreeSet<String> = BTreeSet::new();1354        let mut queue: Vec<String> = vec![];1355        let note = |ty: &Ty, queue: &mut Vec<String>| {1356            ty.walk(&mut |t| {1357                if let Ty::Plain { path } | Ty::Enum { path } | Ty::Class { path, .. } = t {1358                    queue.push(path.clone());1359                }1360            })1361        };1362        for f in self.functions() {1363            for p in &f.params {1364                note(&p.ty, &mut queue);1365            }1366            note(&f.ret, &mut queue);1367        }1368        for c in self.consts() {1369            note(&c.ty, &mut queue);1370        }1371        for t in self.classes() {1372            queue.push(t.path.clone());1373        }1374        for t in self.holders() {1375            queue.push(t.path.clone());1376        }1377        while let Some(path) = queue.pop() {1378            if !out.insert(path.clone()) {1379                continue;1380            }1381            let Some(t) = self.ty(&path) else { continue };1382            for field in &t.fields {1383                note(&field.ty, &mut queue);1384            }1385            for v in &t.variants {1386                for field in &v.fields {1387                    note(&field.ty, &mut queue);1388                }1389            }1390        }1391        out1392    }13931394    fn ts_type_decl(&self, t: &Type, groups: &[Vec<&Function>], pad: &str) -> Result<String> {1395        let mut out = String::new();1396        let doc = doc_line(&t.docs);1397        let name = &t.name;1398        match &t.cross {1399            TypeCross::Enum { .. } => {1400                let words: Vec<String> =1401                    t.variants.iter().map(|v| enum_word(t, &v.name, &v.serde)).collect();1402                let union = words.iter().map(|w| format!("{w:?}")).collect::<Vec<_>>().join(" | ");1403                if !doc.is_empty() {1404                    writeln!(out, "{pad}/** {doc} */").ok();1405                }1406                writeln!(out, "{pad}export type {name} = {union};").ok();1407            }1408            TypeCross::Plain if t.kind == TypeKind::Alias => {1409                if let Some(alias) = &t.alias {1410                    writeln!(out, "{pad}export type {name} = {};", self.ts_serde(alias)).ok();1411                }1412            }1413            TypeCross::Plain if t.kind == TypeKind::Enum => {1414                if !doc.is_empty() {1415                    writeln!(out, "{pad}/** {doc} */").ok();1416                }1417                writeln!(out, "{pad}export type {name} = {};", self.ts_union(t)).ok();1418            }1419            TypeCross::Plain => {1420                if !doc.is_empty() {1421                    writeln!(out, "{pad}/** {doc} */").ok();1422                }1423                writeln!(out, "{pad}export interface {name} {{").ok();1424                self.ts_fields(&mut out, t, &format!("{pad}    "), true);1425                writeln!(out, "{pad}}}").ok();1426            }1427            TypeCross::Class => {1428                let all_public = t.fields.iter().all(|f| f.public);1429                let data = format!("{name}Data");1430                if t.kind == TypeKind::Enum {1431                    writeln!(out, "{pad}export type {data} = {};", self.ts_union(t)).ok();1432                } else if all_public {1433                    writeln!(out, "{pad}export interface {data} {{").ok();1434                    self.ts_fields(&mut out, t, &format!("{pad}    "), false);1435                    writeln!(out, "{pad}}}").ok();1436                } else {1437                    writeln!(out, "{pad}export type {data} = Record<string, unknown>;").ok();1438                }1439                if !doc.is_empty() {1440                    writeln!(out, "{pad}/** {doc} */").ok();1441                }1442                writeln!(out, "{pad}export class {name} {{").ok();1443                let inner = format!("{pad}    ");1444                let constructor = groups.iter().find(|g| g[0].name == "new" && g[0].self_kind.is_none());1445                match constructor {1446                    Some(g) => {1447                        let (d, params, _) = self.ts_function(g, Owner::Class(t));1448                        if !d.is_empty() {1449                            writeln!(out, "{inner}/** {d} */").ok();1450                        }1451                        writeln!(out, "{inner}constructor({params});").ok();1452                    }1453                    None => {1454                        writeln!(out, "{inner}private constructor();").ok();1455                    }1456                }1457                writeln!(out, "{inner}free(): void;").ok();1458                if derives(t, "Deserialize") {1459                    writeln!(out, "{inner}/** Reads the {name} from its plain data. */").ok();1460                    writeln!(out, "{inner}static from(data: {data}): {name};").ok();1461                }1462                if derives(t, "Serialize") {1463                    writeln!(out, "{inner}/** Writes the {name} as plain data. */").ok();1464                    writeln!(out, "{inner}toJSON(): {data};").ok();1465                }1466                let methods: BTreeSet<&str> = groups.iter().map(|g| g[0].name.as_str()).collect();1467                for field in t.fields.iter().filter(|f| f.public) {1468                    if !self.getter_ok(&field.ty) || methods.contains(field.name.as_str()) {1469                        continue;1470                    }1471                    let d = doc_line(&field.docs);1472                    if !d.is_empty() {1473                        writeln!(out, "{inner}/** {d} */").ok();1474                    }1475                    let out_ty = self.ts_direct(&field.ty, true);1476                    if field.ty.settable() {1477                        writeln!(out, "{inner}get {}(): {out_ty};", field.name).ok();1478                        writeln!(1479                            out,1480                            "{inner}set {}(value: {});",1481                            field.name,1482                            self.ts_direct(&field.ty, false)1483                        )1484                        .ok();1485                    } else {1486                        writeln!(out, "{inner}readonly {}: {out_ty};", ident(&field.name)).ok();1487                    }1488                }1489                for g in groups {1490                    let f = g[0];1491                    if f.name == "new" && f.self_kind.is_none() {1492                        continue;1493                    }1494                    let (d, params, ret) = self.ts_function(g, Owner::Class(t));1495                    if !d.is_empty() {1496                        writeln!(out, "{inner}/** {d} */").ok();1497                    }1498                    let prefix = if f.self_kind.is_none() { "static " } else { "" };1499                    writeln!(out, "{inner}{prefix}{}({params}): {ret};", f.name).ok();1500                }1501                writeln!(out, "{pad}}}").ok();1502            }1503            TypeCross::Hand { .. } | TypeCross::Uncrossable { .. } => {}1504        }1505        if matches!(t.cross, TypeCross::Plain | TypeCross::Enum { .. }) && !groups.is_empty() {1506            writeln!(out, "{pad}export const {name}: {{").ok();1507            for g in groups {1508                let f = g[0];1509                let (d, params, ret) = self.ts_function(g, Owner::Holder(t));1510                if !d.is_empty() {1511                    writeln!(out, "{pad}    /** {d} */").ok();1512                }1513                let label = if f.name == "new" { "\"new\"".to_string() } else { f.name.clone() };1514                writeln!(out, "{pad}    {label}({params}): {ret};").ok();1515            }1516            writeln!(out, "{pad}}};").ok();1517        }1518        Ok(out)1519    }15201521    fn ts_union(&self, t: &Type) -> String {1522        let untagged = t.serde.iter().any(|s| s == "untagged");1523        let mut arms = vec![];1524        for v in &t.variants {1525            let word = enum_word(t, &v.name, &v.serde);1526            let body = if v.fields.is_empty() {1527                None1528            } else if v.fields.iter().all(|f| f.name.parse::<usize>().is_ok()) {1529                Some(if v.fields.len() == 1 {1530                    self.ts_serde(&v.fields[0].ty)1531                } else {1532                    format!(1533                        "[{}]",1534                        v.fields.iter().map(|f| self.ts_serde(&f.ty)).collect::<Vec<_>>().join(", ")1535                    )1536                })1537            } else {1538                Some(format!(1539                    "{{ {} }}",1540                    v.fields1541                        .iter()1542                        .map(|f| format!("{}: {}", f.name, self.ts_serde(&f.ty)))1543                        .collect::<Vec<_>>()1544                        .join("; ")1545                ))1546            };1547            arms.push(match (untagged, body) {1548                (true, None) => "null".to_string(),1549                (false, None) => format!("{word:?}"),1550                (true, Some(body)) => body,1551                (false, Some(body)) => format!("{{ {word}: {body} }}"),1552            });1553        }1554        arms.join(" | ")1555    }15561557    fn ts_fields(&self, out: &mut String, t: &Type, pad: &str, all: bool) {1558        for field in t.fields.iter().filter(|f| (all || f.public) && !f.serde_skip) {1559            let d = doc_line(&field.docs);1560            if !d.is_empty() {1561                writeln!(out, "{pad}/** {d} */").ok();1562            }1563            let (name, ty) = match &field.ty {1564                Ty::Option { item } => (format!("{}?", ident(&field.name)), self.ts_serde(item)),1565                other => (ident(&field.name), self.ts_serde(other)),1566            };1567            writeln!(out, "{pad}{name}: {ty};").ok();1568        }1569    }15701571    fn types_dts(&self) -> Result<String> {1572        let name = self.name;1573        let mut out = String::new();1574        writeln!(out, "export {{ default, initSync }} from \"./pkg/{name}/mrlyjs_{name}.js\";\n").ok();1575        let words = |path: &str| -> String {1576            self.ty(path)1577                .map(|t| {1578                    t.variants1579                        .iter()1580                        .map(|v| enum_word(t, &v.name, &v.serde))1581                        .collect::<Vec<String>>()1582                })1583                .unwrap_or_default()1584                .iter()1585                .map(|w| format!("{w:?}"))1586                .collect::<Vec<_>>()1587                .join(" | ")1588        };1589        let projection = words("math::six::Projection");1590        let orientation = words("math::six::Orientation");1591        writeln!(out, "/** An rgba color as four bytes. */").ok();1592        writeln!(out, "export type Color = [number, number, number, number];").ok();1593        writeln!(out, "/** A tensor: its shape and its flat data as a typed array of its dtype. */").ok();1594        writeln!(out, "export interface Tensor {{\n    shape: number[];\n    data: Uint8Array | Uint16Array | Uint32Array | Int32Array;\n}}").ok();1595        writeln!(out, "/** A cell: the shape, the type bytes, and the flat rgba colors and the tags when present. */").ok();1596        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();1597        writeln!(out, "/** A hex cell: a flat cell with its projection, orientation and start row. */").ok();1598        writeln!(out, "export interface Cell6d {{\n    cell: Cell;\n    projection: {projection};\n    orientation: {orientation};\n    start: number;\n}}").ok();1599        writeln!(out, "/** A color inside plain data, serde's form. */").ok();1600        writeln!(out, "export interface ColorData {{\n    r: number;\n    g: number;\n    b: number;\n    a: number;\n}}").ok();1601        writeln!(out, "/** A tensor inside plain data, serde's form. */").ok();1602        writeln!(out, "export interface TensorData {{\n    shape: number[];\n    data: {{ U8: number[] }} | {{ U16: number[] }} | {{ U32: number[] }} | {{ I32: number[] }};\n}}").ok();1603        writeln!(out, "/** A cell inside plain data, serde's form. */").ok();1604        writeln!(out, "export interface CellData {{\n    types: TensorData;\n    colors?: number[][];\n    tags?: TensorData;\n}}").ok();1605        writeln!(out, "/** A hex cell inside plain data, serde's form. */").ok();1606        writeln!(out, "export interface Cell6dData {{\n    cell: {{ cell: CellData }};\n    projection: {projection};\n    orientation: {orientation};\n    start: number;\n}}").ok();1607        writeln!(out, "/** A seeded random stream, opened from a number or a bigint seed. */").ok();1608        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();1609        let mut tree = DeclTree::default();1610        let groups = self.groups();1611        let mut owned: BTreeMap<&str, Vec<Vec<&Function>>> = BTreeMap::new();1612        for group in &groups {1613            match self.owner_of(group[0]) {1614                Owner::Free | Owner::Hand(_) => {1615                    let (d, params, ret) = self.ts_function(group, self.owner_of(group[0]));1616                    let mut text = String::new();1617                    if !d.is_empty() {1618                        text.push_str(&format!("/** {d} */\n"));1619                    }1620                    text.push_str(&format!(1621                        "export function {}({params}): {ret};\n",1622                        ts_leaf(&leaf(&group[0].path))1623                    ));1624                    tree.insert(self.rel(&group[0].module), text);1625                }1626                Owner::Class(t) | Owner::Holder(t) => {1627                    owned.entry(t.path.as_str()).or_default().push(group.clone());1628                }1629            }1630        }1631        for c in self.consts() {1632            let mut text = String::new();1633            let d = doc_line(&c.docs);1634            if !d.is_empty() {1635                text.push_str(&format!("/** {d} */\n"));1636            }1637            text.push_str(&format!(1638                "export function {}(): {};\n",1639                c.name,1640                self.ts_direct(&c.ty, true)1641            ));1642            tree.insert(self.rel(&parent(&c.path)), text);1643        }1644        for path in self.referenced() {1645            let Some(t) = self.ty(&path) else { continue };1646            if matches!(t.cross, TypeCross::Hand { .. } | TypeCross::Uncrossable { .. }) {1647                continue;1648            }1649            let groups = owned.get(path.as_str()).cloned().unwrap_or_default();1650            let text = self.ts_type_decl(t, &groups, "")?;1651            tree.insert(self.rel(&parent(&t.path)), text);1652        }1653        out.push_str(&tree.render(0));1654        while out.ends_with("\n\n") {1655            out.pop();1656        }1657        Ok(out)1658    }1659}16601661// TREES16621663#[derive(Default)]1664struct Tree {1665    leaves: BTreeMap<String, String>,1666    children: BTreeMap<String, Tree>,1667}16681669impl Tree {1670    fn insert(&mut self, module: &str, leaf: String, export: String) {1671        let mut node = self;1672        for seg in module.split("::").filter(|s| !s.is_empty()) {1673            node = node.children.entry(seg.to_string()).or_default();1674        }1675        node.leaves.insert(leaf, export);1676    }16771678    fn literal(&self, depth: usize) -> String {1679        let pad = "    ".repeat(depth);1680        let mut out = String::from("{\n");1681        for (leaf, export) in &self.leaves {1682            out.push_str(&format!("{pad}{leaf}: wasm.{export},\n"));1683        }1684        for (seg, child) in &self.children {1685            out.push_str(&format!("{pad}{seg}: {},\n", child.literal(depth + 1)));1686        }1687        out.push_str(&"    ".repeat(depth - 1));1688        out.push('}');1689        out1690    }1691}16921693#[derive(Default)]1694struct DeclTree {1695    texts: Vec<String>,1696    children: BTreeMap<String, DeclTree>,1697}16981699impl DeclTree {1700    fn insert(&mut self, module: &str, text: String) {1701        let mut node = self;1702        for seg in module.split("::").filter(|s| !s.is_empty()) {1703            node = node.children.entry(seg.to_string()).or_default();1704        }1705        node.texts.push(text);1706    }17071708    fn render(&self, depth: usize) -> String {1709        let pad = "    ".repeat(depth);1710        let mut out = String::new();1711        for text in &self.texts {1712            for line in text.lines() {1713                out.push_str(&pad);1714                out.push_str(line);1715                out.push('\n');1716            }1717        }1718        for (seg, child) in &self.children {1719            let keyword = if depth == 0 { "export declare namespace" } else { "export namespace" };1720            out.push_str(&format!("{pad}{keyword} {seg} {{\n"));1721            out.push_str(&child.render(depth + 1));1722            out.push_str(&format!("{pad}}}\n"));1723        }1724        out1725    }1726}17271728// RULES17291730fn deref(e: &str) -> String {1731    match e.strip_prefix('&') {1732        Some(inner) => inner.to_string(),1733        None => format!("*{e}"),1734    }1735}17361737fn place(e: &str) -> String {1738    e.strip_prefix('&').unwrap_or(e).to_string()1739}17401741fn closure(x: &str, body: String) -> String {1742    let tail = format!("({x})");1743    match body.strip_suffix(&tail) {1744        Some(path) if !path.contains(x) && !path.contains(' ') || path.starts_with("hand::from_js::<") => {1745            path.to_string()1746        }1747        _ => format!("|{x}| {body}"),1748    }1749}17501751fn ok(expr: String) -> String {1752    match expr.strip_suffix('?') {1753        Some(inner) => inner.to_string(),1754        None => format!("Ok({expr})"),1755    }1756}17571758fn is_wide(name: &str) -> bool {1759    name == "u64" || name == "i64"1760}17611762fn is_rng(ty: &Ty) -> bool {1763    matches!(ty, Ty::Hand { name, .. } if name == "Rng")1764}17651766fn typed_scalar(ty: &Ty) -> bool {1767    matches!(ty, Ty::Scalar { name } if TYPED.contains(&name.as_str()))1768}17691770fn native_item(ty: &Ty) -> bool {1771    matches!(ty, Ty::Scalar { name } if NATIVE_ITEM.contains(&name.as_str()))1772}17731774fn typed_array(name: &str) -> &'static str {1775    match name {1776        "u8" => "Uint8Array",1777        "u16" => "Uint16Array",1778        "u32" | "usize" => "Uint32Array",1779        "i8" => "Int8Array",1780        "i16" => "Int16Array",1781        "i32" => "Int32Array",1782        "f32" => "Float32Array",1783        "f64" => "Float64Array",1784        "u64" => "BigUint64Array",1785        _ => "BigInt64Array",1786    }1787}17881789fn pure_in(ty: &Ty) -> bool {1790    match ty {1791        Ty::Unit | Ty::Str | Ty::String | Ty::Json | Ty::Plain { .. } | Ty::Enum { .. } => true,1792        Ty::Scalar { name } => !is_wide(name),1793        Ty::Vec { item } | Ty::Slice { item, .. } | Ty::Option { item } | Ty::Array { item, .. } => {1794            pure_in(item)1795        }1796        Ty::Ref { mutable: false, item, .. } => pure_in(item),1797        Ty::Tuple { items } => items.iter().all(pure_in),1798        _ => false,1799    }1800}18011802fn pure_out(ty: &Ty) -> bool {1803    match ty {1804        Ty::Unit | Ty::Str | Ty::String | Ty::Json | Ty::Plain { .. } | Ty::Enum { .. } => true,1805        Ty::Scalar { name } => !is_wide(name),1806        Ty::Vec { item } | Ty::Slice { item, .. } | Ty::Array { item, .. } => {1807            !typed_scalar(item) && pure_out(item)1808        }1809        Ty::Option { item } => pure_out(item),1810        Ty::Tuple { items } => items.iter().all(pure_out),1811        _ => false,1812    }1813}18141815fn plain_ty(t: &Type) -> Ty {1816    match t.cross {1817        TypeCross::Enum { .. } => Ty::Enum {1818            path: t.path.clone(),1819        },1820        _ => Ty::Plain {1821            path: t.path.clone(),1822        },1823    }1824}18251826fn hand_self(name: &str) -> &'static str {1827    match name {1828        "Tensor" => "tensor",1829        "Color" => "color",1830        "Code" => "code",1831        "Rng" => "rng",1832        _ => "cell",1833    }1834}18351836fn suffix_dim(fname: &str) -> u8 {1837    if fname.ends_with("_3d") || fname.ends_with("_6d") {1838        31839    } else {1840        21841    }1842}18431844fn ident(name: &str) -> String {1845    if WORDS.contains(&name) {1846        format!("{name}_")1847    } else {1848        name.to_string()1849    }1850}185118521853fn derives(t: &Type, what: &str) -> bool {1854    t.derives.iter().any(|d| d == what)1855}18561857fn doc_line(docs: &[String]) -> String {1858    docs.first().map(|d| d.trim().to_string()).unwrap_or_default()1859}18601861fn ts_leaf(name: &str) -> String {1862    if JS_RESERVED.contains(&name) {1863        format!("{name}_")1864    } else {1865        name.to_string()1866    }1867}18681869fn leaf(path: &str) -> String {1870    path.rsplit("::").next().unwrap_or(path).to_string()1871}18721873fn parent(path: &str) -> String {1874    match path.rfind("::") {1875        Some(at) => path[..at].to_string(),1876        None => String::new(),1877    }1878}18791880fn enum_word(t: &Type, variant: &str, serde: &[String]) -> String {1881    for attr in serde {1882        if let Some(rest) = attr.trim().strip_prefix("rename = ") {1883            return rest.trim().trim_matches('"').to_string();1884        }1885    }1886    for attr in &t.serde {1887        if let Some(rest) = attr.trim().strip_prefix("rename_all = ") {1888            let rule = rest.trim().trim_matches('"');1889            return rename_all(variant, rule);1890        }1891    }1892    variant.to_string()1893}18941895fn rename_all(name: &str, rule: &str) -> String {1896    let words: Vec<String> = {1897        let mut out: Vec<String> = vec![];1898        for c in name.chars() {1899            if c.is_uppercase() || out.is_empty() {1900                out.push(String::new());1901            }1902            out.last_mut().unwrap().push(c);1903        }1904        out.into_iter().map(|w| w.to_lowercase()).collect()1905    };1906    match rule {1907        "lowercase" => name.to_lowercase(),1908        "UPPERCASE" => name.to_uppercase(),1909        "snake_case" => words.join("_"),1910        "SCREAMING_SNAKE_CASE" => words.join("_").to_uppercase(),1911        "kebab-case" => words.join("-"),1912        "SCREAMING-KEBAB-CASE" => words.join("-").to_uppercase(),1913        "camelCase" => {1914            let mut c = name.chars();1915            match c.next() {1916                Some(first) => first.to_lowercase().collect::<String>() + c.as_str(),1917                None => String::new(),1918            }1919        }1920        _ => name.to_string(),1921    }1922}19231924// TEST19251926const MANIFEST_TEST: &str = r#"import { expect, test } from "bun:test";19271928const manifest = await Bun.file(new URL("../../bridge/manifest.json", import.meta.url)).json();1929const units = (await Bun.file(new URL("../../bridge/units.txt", import.meta.url)).text())1930    .split("\n")1931    .map((line) => line.trim())1932    .filter(Boolean);1933const pkg = await Bun.file(new URL("./package.json", import.meta.url)).json();1934const kinds = new Map(manifest.types.map((t) => [t.path, t.cross.kind]));19351936function parent(path) {1937    const at = path.lastIndexOf("::");1938    return at < 0 ? "" : path.slice(0, at);1939}19401941function relative(unit, path) {1942    return unit === "all" ? path : path.slice(unit.length + 2);1943}19441945function walk(mod, path) {1946    let node = mod;1947    for (const seg of path.split("::").filter(Boolean)) {1948        if (node === undefined || node === null) return undefined;1949        node = node[seg];1950    }1951    return node;1952}19531954function holds(unit, path) {1955    return unit === "all" || path.split("::")[0] === unit;1956}19571958function check(mod, unit, f) {1959    const kind = f.owner ? kinds.get(f.owner) : undefined;1960    if (kind === "class") {1961        const cls = walk(mod, relative(unit, f.owner));1962        if (typeof cls !== "function") return false;1963        if (f.self_kind === null && f.name === "new") return true;1964        if (f.self_kind === null) return typeof cls[f.name] === "function";1965        return typeof cls.prototype[f.name] === "function";1966    }1967    if (kind === "plain" || kind === "enum") {1968        const holder = walk(mod, relative(unit, f.owner));1969        return typeof holder === "function" && typeof holder[f.name] === "function";1970    }1971    return typeof walk(mod, relative(unit, f.path)) === "function";1972}19731974for (const unit of units) {1975    test(`${unit} exports every ok name`, async () => {1976        expect(pkg.exports[unit === "all" ? "." : `./${unit}`]).toBeDefined();1977        const mod = await import(`./${unit}.js`);1978        const bytes = await Bun.file(new URL(`./pkg/${unit}/mrlyjs_${unit}_bg.wasm`, import.meta.url)).arrayBuffer();1979        mod.initSync({ module: bytes });1980        expect(typeof mod.Rng).toBe("function");1981        const missing = [];1982        const names = new Set();1983        for (const f of manifest.functions) {1984            if (f.cross.status !== "ok" || !holds(unit, f.module)) continue;1985            names.add(f.path);1986            if (!check(mod, unit, f)) missing.push(f.path);1987        }1988        for (const c of manifest.consts) {1989            if (c.cross.status !== "ok" || !holds(unit, c.path)) continue;1990            names.add(c.path);1991            if (typeof walk(mod, relative(unit, c.path)) !== "function") missing.push(c.path);1992        }1993        for (const t of manifest.types) {1994            if (t.cross.kind !== "class" || !holds(unit, t.path)) continue;1995            names.add(t.path);1996            if (typeof walk(mod, relative(unit, t.path)) !== "function") missing.push(t.path);1997        }1998        expect(missing).toEqual([]);1999        console.log(`${unit}: ${names.size} names`);2000    });2001}2002"#;