cli.rs

29.1 kB · rust · 892 lines

1use crate::disk::save;2use crate::model::{3    Cross, Function, Manifest, Result, SelfKind, Source, Ty, Type, TypeCross, TypeKind,4};5use std::collections::BTreeMap;6use std::path::Path;78pub fn write(manifest: &Manifest, root: &Path) -> Result<()> {9    let shop = Shop::new(manifest);10    let doors = doors(&shop, manifest)?;11    save(&root.join("pkgs/mrlyrs/src/bin/mrly.rs"), &render(&doors))12}1314// SHOP1516struct Shop<'a> {17    types: BTreeMap<&'a str, &'a Type>,18    hands: BTreeMap<&'a str, &'a str>,19}2021impl<'a> Shop<'a> {22    fn new(manifest: &'a Manifest) -> Shop<'a> {23        let mut types = BTreeMap::new();24        let mut hands = BTreeMap::new();25        for ty in &manifest.types {26            types.insert(ty.path.as_str(), ty);27            if let TypeCross::Hand { name } = &ty.cross {28                if ty.kind != TypeKind::Alias {29                    hands.insert(name.as_str(), ty.path.as_str());30                }31            }32        }33        Shop { types, hands }34    }35    fn hand(&self, name: &str) -> Result<String> {36        let path = self37            .hands38            .get(name)39            .ok_or_else(|| format!("no hand type named {name}"))?;40        Ok(format!("mrlyrs::{path}"))41    }42    fn derives(&self, path: &str, what: &str) -> bool {43        self.types44            .get(path)45            .is_some_and(|ty| ty.derives.iter().any(|derive| derive == what))46    }47    fn wide(&self, path: &str) -> bool {48        self.types.get(path).is_some_and(|ty| ty.const_generic)49    }50    fn owner(&self, path: &str) -> Result<Ty> {51        let ty = self52            .types53            .get(path)54            .ok_or_else(|| format!("no type named {path}"))?;55        Ok(match &ty.cross {56            TypeCross::Hand { name } => Ty::Hand {57                name: name.clone(),58                dim: None,59            },60            TypeCross::Class => Ty::Class {61                path: path.to_string(),62                dim: None,63            },64            TypeCross::Plain => Ty::Plain {65                path: path.to_string(),66            },67            TypeCross::Enum { .. } => Ty::Enum {68                path: path.to_string(),69            },70            TypeCross::Uncrossable { reason } => return Err(format!("{path}: {reason}")),71        })72    }73}7475// SHAPES7677struct Door {78    name: String,79    sig: String,80    ident: String,81    body: Option<String>,82}8384enum Made {85    Open(String),86    Shut(String),87}8889enum Bind {90    Open(String, String),91    Shut(String),92}9394type Args = Vec<(String, Ty)>;9596fn bare(ty: &Ty) -> &Ty {97    match ty {98        Ty::Ref { item, .. } | Ty::Result { item } => bare(item),99        other => other,100    }101}102103fn rng(ty: &Ty) -> bool {104    matches!(ty, Ty::Hand { name, .. } if name == "Rng")105}106107fn special(ty: &Ty) -> bool {108    match ty {109        Ty::U128 | Ty::I128 | Ty::Code => true,110        Ty::Hand { name, .. } => name == "Color",111        Ty::Vec { item }112        | Ty::Slice { item, .. }113        | Ty::Option { item }114        | Ty::Array { item, .. }115        | Ty::Ref { item, .. }116        | Ty::Set { item }117        | Ty::Result { item } => special(item),118        Ty::Tuple { items } => items.iter().any(special),119        Ty::Map { key, value } => special(key) || special(value),120        _ => false,121    }122}123124fn lacks(shop: &Shop, ty: &Ty, what: &str) -> Option<String> {125    let mut reason = None;126    ty.walk(&mut |seen| {127        if let Ty::Plain { path } | Ty::Enum { path } | Ty::Class { path, .. } = seen {128            if reason.is_none() && !shop.derives(path, what) {129                reason = Some(format!("{path} has no {what}"));130            }131        }132    });133    reason134}135136fn borrowed(ty: &Ty) -> bool {137    match ty {138        Ty::Str | Ty::Slice { .. } => true,139        Ty::Vec { item } | Ty::Option { item } | Ty::Array { item, .. } | Ty::Ref { item, .. } => {140            borrowed(item)141        }142        Ty::Tuple { items } => items.iter().any(borrowed),143        _ => false,144    }145}146147// NAMES148149fn show(ty: &Ty, dim: Option<u8>) -> String {150    match ty {151        Ty::Unit => "null".to_string(),152        Ty::Scalar { name } => name.clone(),153        Ty::Str => "str".to_string(),154        Ty::String => "String".to_string(),155        Ty::U128 => "u128".to_string(),156        Ty::I128 => "i128".to_string(),157        Ty::Code => "Code".to_string(),158        Ty::Json => "Json".to_string(),159        Ty::Vec { item } | Ty::Slice { item, .. } | Ty::Set { item } => {160            format!("[{}]", show(item, dim))161        }162        Ty::Option { item } => format!("{}?", show(item, dim)),163        Ty::Array { item, len } => format!("[{}; {len}]", show(item, dim)),164        Ty::Tuple { items } => {165            let parts: Vec<String> = items.iter().map(|item| show(item, dim)).collect();166            format!("({})", parts.join(", "))167        }168        Ty::Map { key, value } => format!("{{{}: {}}}", show(key, dim), show(value, dim)),169        Ty::Ref { mutable, item, .. } => {170            if *mutable && rng(item) {171                "Rng(seed)".to_string()172            } else {173                show(item, dim)174            }175        }176        Ty::Result { item } => show(item, dim),177        Ty::Hand { name, dim: own } => match (name.as_str(), own.or(dim)) {178            ("CellNd", Some(n)) => format!("Cell{n}d"),179            ("CellNd", None) => "Cell".to_string(),180            (other, _) => other.to_string(),181        },182        Ty::Plain { path } | Ty::Enum { path } | Ty::Class { path, .. } | Ty::Opaque { path } => {183            path.replace("::", ".")184        }185        Ty::Unknown { text } => text.clone(),186    }187}188189fn owned(shop: &Shop, ty: &Ty, dim: Option<u8>) -> Result<String> {190    Ok(match ty {191        Ty::Unit => "()".to_string(),192        Ty::Scalar { name } => name.clone(),193        Ty::Str | Ty::String => "String".to_string(),194        Ty::U128 => "u128".to_string(),195        Ty::I128 => "i128".to_string(),196        Ty::Code => shop.hand("Code")?,197        Ty::Json => "serde_json::Value".to_string(),198        Ty::Vec { item } | Ty::Slice { item, .. } => {199            format!("Vec<{}>", owned(shop, item, dim)?)200        }201        Ty::Set { item } => format!("std::collections::BTreeSet<{}>", owned(shop, item, dim)?),202        Ty::Option { item } => format!("Option<{}>", owned(shop, item, dim)?),203        Ty::Array { item, len } => format!("[{}; {len}]", owned(shop, item, dim)?),204        Ty::Tuple { items } => {205            let mut parts = Vec::new();206            for item in items {207                parts.push(owned(shop, item, dim)?);208            }209            format!("({})", parts.join(", "))210        }211        Ty::Ref { item, .. } | Ty::Result { item } => owned(shop, item, dim)?,212        Ty::Map { .. } => "_".to_string(),213        Ty::Hand { name, dim: own } => {214            let path = shop.hand(name)?;215            match (name.as_str(), own.or(dim)) {216                ("CellNd", Some(n)) => format!("{path}<{n}>"),217                ("CellNd", None) => return Err("a cell with no dimension".to_string()),218                _ => path,219            }220        }221        Ty::Plain { path } | Ty::Enum { path } | Ty::Class { path, .. } => {222            if shop.wide(path) {223                return Err(format!("{path} has a const generic"));224            }225            format!("mrlyrs::{path}")226        }227        Ty::Opaque { path } => return Err(format!("{path} does not cross")),228        Ty::Unknown { text } => return Err(format!("{text} does not cross")),229    })230}231232// READING233234fn take(at: usize, value: &str) -> String {235    format!("take!(name, {at}, {value})")236}237238fn read(shop: &Shop, ty: &Ty, at: usize, value: &str, deep: usize) -> Result<String> {239    if !special(ty) {240        return Ok(take(at, value));241    }242    Ok(match ty {243        Ty::U128 => format!("big(name, {at}, {value})?"),244        Ty::I128 => format!("small(name, {at}, {value})?"),245        Ty::Code => format!("{}::from(big(name, {at}, {value})?)", shop.hand("Code")?),246        Ty::Hand { .. } => format!("color(name, {at}, {value})?"),247        Ty::Ref { item, .. } | Ty::Result { item } => read(shop, item, at, value, deep)?,248        Ty::Option { item } => format!(249            "if Value::is_null({value}) {{ None }} else {{ Some({}) }}",250            read(shop, item, at, value, deep)?251        ),252        Ty::Vec { item } | Ty::Slice { item, .. } | Ty::Set { item } => {253            let one = read(shop, item, at, &format!("item{deep}"), deep + 1)?;254            let run = format!(255                "{{ let mut list{deep} = Vec::new(); for item{deep} in items(name, {at}, {value})? {{ list{deep}.push({one}); }} list{deep} }}"256            );257            match ty {258                Ty::Set { .. } => format!("{run}.into_iter().collect()"),259                _ => run,260            }261        }262        Ty::Array { item, len } => {263            let one = read(shop, item, at, &format!("item{deep}"), deep + 1)?;264            format!(265                "{{ let mut list{deep} = Vec::new(); for item{deep} in parts(name, {at}, {value}, {len})? {{ list{deep}.push({one}); }} match list{deep}.try_into() {{ Ok(fixed) => fixed, Err(_) => return Err(bad(name, {at}, \"a fixed array\")) }} }}"266            )267        }268        Ty::Tuple { items } => {269            let mut parts = Vec::new();270            for (index, item) in items.iter().enumerate() {271                parts.push(read(272                    shop,273                    item,274                    at,275                    &format!("&parts{deep}[{index}]"),276                    deep + 1,277                )?);278            }279            format!(280                "{{ let parts{deep} = parts(name, {at}, {value}, {})?; ({}) }}",281                items.len(),282                parts.join(", ")283            )284        }285        Ty::Map { key, value: held } => {286            let one = read(shop, held, at, &format!("item{deep}"), deep + 1)?;287            let keyed = owned(shop, key, None)?;288            let held = owned(shop, held, None)?;289            format!(290                "{{ let mut pairs{deep}: Vec<({keyed}, {held})> = Vec::new(); for (text{deep}, item{deep}) in fields(name, {at}, {value})? {{ let key{deep} = match text{deep}.parse() {{ Ok(key) => key, Err(_) => return Err(bad(name, {at}, \"an object key\")) }}; pairs{deep}.push((key{deep}, {one})); }} pairs{deep} }}.into_iter().collect()"291            )292        }293        other => return Err(format!("{other:?} cannot be read")),294    })295}296297fn view(ty: &Ty, at: &str, top: bool) -> String {298    match ty {299        Ty::Str | Ty::String => format!("{at}.as_str()"),300        Ty::Vec { .. } | Ty::Slice { .. } => format!("{at}.as_slice()"),301        Ty::Tuple { items } => {302            let parts: Vec<String> = items303                .iter()304                .enumerate()305                .map(|(index, item)| view(item, &format!("{at}.{index}"), false))306                .collect();307            format!("({})", parts.join(", "))308        }309        _ if top => format!("*{at}"),310        _ => at.to_string(),311    }312}313314fn seen(shop: &Shop, ty: &Ty, dim: Option<u8>) -> Result<String> {315    Ok(match ty {316        Ty::Str | Ty::String => "&str".to_string(),317        Ty::Vec { item } | Ty::Slice { item, .. } => format!("&[{}]", owned(shop, item, dim)?),318        Ty::Tuple { items } => {319            let mut parts = Vec::new();320            for item in items {321                parts.push(seen(shop, item, dim)?);322            }323            format!("({})", parts.join(", "))324        }325        other => owned(shop, other, dim)?,326    })327}328329// WRITING330331fn give(ty: &Ty, value: &str, deep: usize) -> Result<String> {332    if !special(ty) {333        return Ok(match ty {334            Ty::Unit => "Value::Null".to_string(),335            _ => format!("give!({value})"),336        });337    }338    Ok(match ty {339        Ty::U128 | Ty::I128 => format!("Value::String({value}.to_string())"),340        Ty::Code => format!("Value::String({value}.get().to_string())"),341        Ty::Hand { .. } => format!("tint({value})"),342        Ty::Ref { item, .. } | Ty::Result { item } => give(item, value, deep)?,343        Ty::Option { item } => format!(344            "match {value} {{ Some(item{deep}) => {}, None => Value::Null }}",345            give(item, &format!("item{deep}"), deep + 1)?346        ),347        Ty::Vec { item } | Ty::Slice { item, .. } | Ty::Set { item } | Ty::Array { item, .. } => {348            format!(349                "{{ let mut list{deep} = Vec::new(); for item{deep} in {value} {{ list{deep}.push({}); }} Value::Array(list{deep}) }}",350                give(item, &format!("item{deep}"), deep + 1)?351            )352        }353        Ty::Tuple { items } => {354            let mut parts = Vec::new();355            for (index, item) in items.iter().enumerate() {356                parts.push(give(item, &format!("parts{deep}.{index}"), deep + 1)?);357            }358            format!(359                "{{ let parts{deep} = {value}; Value::Array(vec![{}]) }}",360                parts.join(", ")361            )362        }363        Ty::Map { value: held, .. } => format!(364            "{{ let mut pairs{deep} = serde_json::Map::new(); for (key{deep}, item{deep}) in {value} {{ pairs{deep}.insert(key{deep}.to_string(), {}); }} Value::Object(pairs{deep}) }}",365            give(held, &format!("item{deep}"), deep + 1)?366        ),367        other => return Err(format!("{other:?} cannot be written")),368    })369}370371// DOORS372373fn args_of(shop: &Shop, function: &Function) -> Result<Args> {374    let mut out = Vec::new();375    if let Some(kind) = function.self_kind {376        let owner = function377            .owner378            .as_ref()379            .ok_or_else(|| format!("{}: a self with no owner", function.path))?;380        let ty = shop.owner(owner)?;381        out.push((382            "self".to_string(),383            match kind {384                SelfKind::Value => ty,385                SelfKind::Ref => Ty::Ref {386                    mutable: false,387                    lifetime: None,388                    item: Box::new(ty),389                },390                SelfKind::Mut => Ty::Ref {391                    mutable: true,392                    lifetime: None,393                    item: Box::new(ty),394                },395            },396        ));397    }398    for param in &function.params {399        out.push((param.name.clone(), param.ty.clone()));400    }401    Ok(out)402}403404fn sign(shop: &Shop, function: &Function) -> Result<String> {405    let dim = match function.dims.as_slice() {406        [one] => Some(*one),407        _ => None,408    };409    let args = args_of(shop, function)?;410    let parts: Vec<String> = args411        .iter()412        .map(|(name, ty)| format!("{name}: {}", show(ty, dim)))413        .collect();414    Ok(format!(415        "({}) -> {}",416        parts.join(", "),417        show(&function.ret, dim)418    ))419}420421fn callee(shop: &Shop, function: &Function, dim: Option<u8>) -> String {422    let name = &function.name;423    match (&function.owner, &function.via) {424        (Some(owner), Some(via)) if function.source == Source::Trait => {425            format!("<mrlyrs::{owner} as mrlyrs::{via}>::{name}")426        }427        (Some(owner), _) => {428            let turbo = match dim {429                Some(n) if shop.wide(owner) => format!("::<{n}>"),430                _ => String::new(),431            };432            format!("mrlyrs::{owner}{turbo}::{name}")433        }434        (None, _) => {435            let turbo = match dim {436                Some(n) => format!("::<{n}>"),437                None => String::new(),438            };439            format!("mrlyrs::{}{turbo}", function.path)440        }441    }442}443444fn hold(shop: &Shop, ty: &Ty, at: usize, dim: Option<u8>) -> Result<Bind> {445    let slot = format!("args[{at}]");446    if let Ty::Ref {447        mutable: true,448        item,449        ..450    } = ty451    {452        if rng(item) {453            let open = format!("mrlyrs::core::Rng::new(seed(name, {at}, &{slot})?)");454            return Ok(Bind::Open(455                format!("let mut a{at} = {open};"),456                format!("&mut a{at}"),457            ));458        }459        return Ok(Bind::Shut("mutates its argument in place".to_string()));460    }461    if let Ty::Option { item } = ty {462        if let Ty::Ref {463            mutable: true,464            item: held,465            ..466        } = item.as_ref()467        {468            if rng(held) {469                let open = format!("mrlyrs::core::Rng::new(seed(name, {at}, &{slot})?)");470                return Ok(Bind::Open(471                    format!(472                        "let mut a{at} = if {slot}.is_null() {{ None }} else {{ Some({open}) }};"473                    ),474                    format!("a{at}.as_mut()"),475                ));476            }477            return Ok(Bind::Shut("mutates its argument in place".to_string()));478        }479    }480    if let Some(reason) = lacks(shop, ty, "Deserialize") {481        return Ok(Bind::Shut(reason));482    }483    let start = match read(shop, ty, at, &format!("&{slot}"), 0) {484        Ok(text) => text,485        Err(reason) => return Ok(Bind::Shut(reason)),486    };487    let mut lets = match owned(shop, ty, dim) {488        Ok(text) if text != "_" => format!("let a{at}: {text} = {start};"),489        _ => format!("let a{at} = {start};"),490    };491    let stripped = match ty {492        Ty::Ref { item, .. } => item.as_ref(),493        other => other,494    };495    let pass = match stripped {496        Ty::Str => format!("a{at}.as_str()"),497        Ty::Slice { item, .. } | Ty::Vec { item } if borrowed(item) => {498            let sight = match seen(shop, item, dim) {499                Ok(text) => text,500                Err(reason) => return Ok(Bind::Shut(reason)),501            };502            lets.push_str(&format!(503                " let b{at}: Vec<{sight}> = a{at}.iter().map(|item| {}).collect();",504                view(item, "item", true)505            ));506            format!("&b{at}")507        }508        Ty::Slice { .. } => format!("&a{at}"),509        Ty::Option { item } => match item.as_ref() {510            Ty::Ref { .. } => format!("a{at}.as_ref()"),511            Ty::Slice { .. } | Ty::Str => format!("a{at}.as_deref()"),512            _ => format!("a{at}"),513        },514        _ if matches!(ty, Ty::Ref { .. }) => format!("&a{at}"),515        _ => format!("a{at}"),516    };517    Ok(Bind::Open(lets, pass))518}519520fn probe(shop: &Shop, function: &Function) -> Result<String> {521    let args = args_of(shop, function)?;522    let cell = |ty: &Ty| matches!(ty, Ty::Hand { name, dim: None } if name == "CellNd");523    for (at, (_, ty)) in args.iter().enumerate() {524        let inner = bare(ty);525        if cell(inner) {526            return Ok(format!("cell_rank(name, args, {at})?"));527        }528        if let Ty::Slice { item, .. } | Ty::Vec { item } = inner {529            if cell(bare(item)) {530                return Ok(format!("cells_rank(name, args, {at})?"));531            }532        }533    }534    for (at, (_, ty)) in args.iter().enumerate() {535        if matches!(bare(ty), Ty::Hand { name, .. } if name == "Tensor") {536            return Ok(format!("tensor_rank(name, args, {at})?"));537        }538    }539    Err(format!("{}: no cell or tensor to rank", function.path))540}541542fn one(shop: &Shop, function: &Function, dim: Option<u8>) -> Result<Made> {543    let args = args_of(shop, function)?;544    let mut lines = vec![format!("count(name, args, {})?;", args.len())];545    let mut passes = Vec::new();546    for (at, (_, ty)) in args.iter().enumerate() {547        match hold(shop, ty, at, dim)? {548            Bind::Open(lets, pass) => {549                lines.push(lets);550                passes.push(pass);551            }552            Bind::Shut(reason) => return Ok(Made::Shut(reason)),553        }554    }555    let call = format!("{}({})", callee(shop, function, dim), passes.join(", "));556    if let Some(reason) = lacks(shop, &function.ret, "Serialize") {557        return Ok(Made::Shut(reason));558    }559    let tail = match &function.ret {560        Ty::Result { item } => {561            let good = match item.as_ref() {562                Ty::Unit => "Ok(()) => Ok(Value::Null)".to_string(),563                other => match give(other, "value", 0) {564                    Ok(text) => format!("Ok(value) => Ok({text})"),565                    Err(reason) => return Ok(Made::Shut(reason)),566                },567            };568            format!("match {call} {{ {good}, Err(error) => Err(Fail::Error(error.to_string())) }}")569        }570        Ty::Unit => format!("{call};\nOk(Value::Null)"),571        other => match give(other, &call, 0) {572            Ok(text) => format!("Ok({text})"),573            Err(reason) => return Ok(Made::Shut(reason)),574        },575    };576    lines.push(tail);577    Ok(Made::Open(lines.join("\n")))578}579580fn build(shop: &Shop, group: &[&Function]) -> Result<Made> {581    let mut branches: Vec<(Option<u8>, &Function)> = Vec::new();582    for function in group {583        if function.dims.is_empty() {584            branches.push((None, function));585        } else {586            for dim in &function.dims {587                branches.push((Some(*dim), function));588            }589        }590    }591    if branches.len() == 1 {592        let (dim, function) = branches[0];593        return one(shop, function, dim);594    }595    let mut body = format!("match {} {{\n", probe(shop, branches[0].1)?);596    for (dim, function) in &branches {597        let dim = dim.ok_or("a branch with no dimension")?;598        match one(shop, function, Some(dim))? {599            Made::Open(text) => body.push_str(&format!("{dim} => {{\n{text}\n}}\n")),600            shut => return Ok(shut),601        }602    }603    body.push_str(604        "other => Err(usage(format!(\"{name} wants a 2d or 3d argument, got {other}d\"))),\n}",605    );606    Ok(Made::Open(body))607}608609fn doors(shop: &Shop, manifest: &Manifest) -> Result<Vec<Door>> {610    let mut groups: BTreeMap<String, Vec<&Function>> = BTreeMap::new();611    for function in &manifest.functions {612        if function.cross != Cross::Ok {613            continue;614        }615        groups616            .entry(function.path.replace("::", "."))617            .or_default()618            .push(function);619    }620    let mut taken: BTreeMap<String, usize> = BTreeMap::new();621    let mut out = Vec::new();622    for (name, group) in groups {623        let mut signs = Vec::new();624        for function in &group {625            signs.push(sign(shop, function)?);626        }627        let stem = format!("door_{}", name.replace('.', "_").to_lowercase());628        let count = taken.entry(stem.clone()).or_insert(0);629        *count += 1;630        let ident = match *count {631            1 => stem,632            other => format!("{stem}_{other}"),633        };634        let (sig, body) = match build(shop, &group)? {635            Made::Open(body) => (signs.join(" | "), Some(body)),636            Made::Shut(reason) => (637                format!("{} # uncallable: {reason}", signs.join(" | ")),638                None,639            ),640        };641        out.push(Door {642            name,643            sig,644            ident,645            body,646        });647    }648    Ok(out)649}650651// TEXT652653const HEAD: &str = r#"use serde_json::Value;654use std::process::ExitCode;655656// PLUMBING657658enum Fail {659    Usage(String),660    Error(String),661}662663type Done = std::result::Result<Value, Fail>;664665type Call = fn(&str, &[Value]) -> Done;666667type Door = (&'static str, &'static str, Option<Call>);668669const USAGE: &str = "usage: mrly list | mrly <module.fn> '[json, args]'";670671macro_rules! take {672    ($name:expr, $at:expr, $value:expr) => {673        match serde_json::from_value(Value::clone($value)) {674            Ok(value) => value,675            Err(error) => return Err(bad($name, $at, &error.to_string())),676        }677    };678}679680macro_rules! give {681    ($value:expr) => {682        match serde_json::to_value($value) {683            Ok(value) => value,684            Err(error) => return Err(Fail::Error(error.to_string())),685        }686    };687}688689fn usage(what: String) -> Fail {690    Fail::Usage(format!("mrly: {what}; {USAGE}"))691}692693fn bad(name: &str, at: usize, why: &str) -> Fail {694    usage(format!("{name} argument {at}: {why}"))695}696697fn count(name: &str, args: &[Value], want: usize) -> std::result::Result<(), Fail> {698    if args.len() == want {699        Ok(())700    } else {701        Err(usage(format!(702            "{name} takes {want} arguments, got {}",703            args.len()704        )))705    }706}707708fn slot<'a>(name: &str, args: &'a [Value], at: usize) -> std::result::Result<&'a Value, Fail> {709    args.get(at)710        .ok_or_else(|| usage(format!("{name} wants an argument {at}")))711}712713fn big(name: &str, at: usize, value: &Value) -> std::result::Result<u128, Fail> {714    let read = match value.as_str() {715        Some(text) => text.parse().ok(),716        None => value.as_u64().map(u128::from),717    };718    match read {719        Some(number) => Ok(number),720        None => Err(bad(name, at, "a u128 is a decimal string")),721    }722}723724fn small(name: &str, at: usize, value: &Value) -> std::result::Result<i128, Fail> {725    let read = match value.as_str() {726        Some(text) => text.parse().ok(),727        None => value.as_i64().map(i128::from),728    };729    match read {730        Some(number) => Ok(number),731        None => Err(bad(name, at, "an i128 is a decimal string")),732    }733}734735fn seed(name: &str, at: usize, value: &Value) -> std::result::Result<u64, Fail> {736    match value.as_u64() {737        Some(number) => Ok(number),738        None => Err(bad(name, at, "an Rng is a seed number")),739    }740}741742fn color(name: &str, at: usize, value: &Value) -> std::result::Result<mrlyrs::core::Color, Fail> {743    let bytes: [u8; 4] = match serde_json::from_value(value.clone()) {744        Ok(bytes) => bytes,745        Err(_) => return Err(bad(name, at, "a Color is four bytes")),746    };747    Ok(mrlyrs::core::Color {748        r: bytes[0],749        g: bytes[1],750        b: bytes[2],751        a: bytes[3],752    })753}754755fn tint(color: mrlyrs::core::Color) -> Value {756    Value::Array(vec![757        color.r.into(),758        color.g.into(),759        color.b.into(),760        color.a.into(),761    ])762}763764fn items<'a>(name: &str, at: usize, value: &'a Value) -> std::result::Result<&'a [Value], Fail> {765    match value.as_array() {766        Some(list) => Ok(list),767        None => Err(bad(name, at, "a JSON array")),768    }769}770771fn parts<'a>(772    name: &str,773    at: usize,774    value: &'a Value,775    want: usize,776) -> std::result::Result<&'a [Value], Fail> {777    match value.as_array() {778        Some(list) if list.len() == want => Ok(list),779        _ => Err(bad(name, at, &format!("a JSON array of {want}"))),780    }781}782783fn fields<'a>(784    name: &str,785    at: usize,786    value: &'a Value,787) -> std::result::Result<&'a serde_json::Map<String, Value>, Fail> {788    match value.as_object() {789        Some(map) => Ok(map),790        None => Err(bad(name, at, "a JSON object")),791    }792}793794fn axes(value: &Value) -> Option<usize> {795    Some(value.get("shape")?.as_array()?.len())796}797798fn walls(value: &Value) -> Option<usize> {799    axes(value.get("cell")?.get("types")?)800}801802fn tensor_rank(name: &str, args: &[Value], at: usize) -> std::result::Result<usize, Fail> {803    match axes(slot(name, args, at)?) {804        Some(rank) => Ok(rank),805        None => Err(bad(name, at, "a Tensor with a shape")),806    }807}808809fn cell_rank(name: &str, args: &[Value], at: usize) -> std::result::Result<usize, Fail> {810    match walls(slot(name, args, at)?) {811        Some(rank) => Ok(rank),812        None => Err(bad(name, at, "a cell")),813    }814}815816fn cells_rank(name: &str, args: &[Value], at: usize) -> std::result::Result<usize, Fail> {817    let value = slot(name, args, at)?;818    match value.as_array().and_then(|list| list.first()).and_then(walls) {819        Some(rank) => Ok(rank),820        None => Err(bad(name, at, "a list of cells")),821    }822}823824fn main() -> ExitCode {825    let words: Vec<String> = std::env::args().skip(1).collect();826    match run(&words) {827        Ok(()) => ExitCode::SUCCESS,828        Err(Fail::Error(message)) => {829            eprintln!("{message}");830            ExitCode::from(1)831        }832        Err(Fail::Usage(message)) => {833            eprintln!("{message}");834            ExitCode::from(2)835        }836    }837}838839fn run(words: &[String]) -> std::result::Result<(), Fail> {840    match words {841        [one] if one == "list" => {842            for (name, sig, _) in DOORS {843                println!("{name}{sig}");844            }845            Ok(())846        }847        [name, text] => {848            let at = DOORS849                .binary_search_by_key(&name.as_str(), |door| door.0)850                .map_err(|_| usage(format!("no door named {name:?}")))?;851            let call = DOORS[at]852                .2853                .ok_or_else(|| usage(format!("{name} is not callable from the shell")))?;854            let args = match serde_json::from_str::<Value>(text) {855                Ok(Value::Array(args)) => args,856                Ok(_) => return Err(usage(format!("{name} wants a JSON array of arguments"))),857                Err(error) => return Err(usage(format!("{name} arguments: {error}"))),858            };859            println!("{}", call(name, &args)?);860            Ok(())861        }862        _ => Err(usage("say list or a door".to_string())),863    }864}865"#;866867fn render(doors: &[Door]) -> String {868    let mut out = String::from(HEAD);869    out.push_str("\n// DOORS\n\nstatic DOORS: &[Door] = &[\n");870    for door in doors {871        let call = match &door.body {872            Some(_) => format!("Some({})", door.ident),873            None => "None".to_string(),874        };875        out.push_str(&format!("    ({:?}, {:?}, {call}),\n", door.name, door.sig));876    }877    out.push_str("];\n\n// CALLS\n");878    for door in doors {879        let Some(body) = &door.body else { continue };880        out.push_str(&format!(881            "\nfn {}(name: &str, args: &[Value]) -> Done {{\n",882            door.ident883        ));884        for line in body.lines() {885            out.push_str("    ");886            out.push_str(line);887            out.push('\n');888        }889        out.push_str("}\n");890    }891    out892}