cli.rs

29.1 kB · rust · 877 lines

1use crate::model::{Cross, Function, Manifest, Result, SelfKind, Source, Ty, Type, TypeCross, TypeKind};2use std::collections::BTreeMap;3use std::path::Path;45pub fn write(manifest: &Manifest, root: &Path) -> Result<()> {6    let shop = Shop::new(manifest);7    let doors = doors(&shop, manifest)?;8    let file = root.join("pkgs/mrlyrs/src/bin/mrly.rs");9    let folder = file.parent().ok_or("mrly.rs needs a folder")?;10    std::fs::create_dir_all(folder).map_err(|error| error.to_string())?;11    std::fs::write(&file, render(&doors)).map_err(|error| error.to_string())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 borrowed(ty: &Ty) -> bool {125    match ty {126        Ty::Str | Ty::Slice { .. } => true,127        Ty::Vec { item } | Ty::Option { item } | Ty::Array { item, .. } | Ty::Ref { item, .. } => {128            borrowed(item)129        }130        Ty::Tuple { items } => items.iter().any(borrowed),131        _ => false,132    }133}134135// NAMES136137fn show(ty: &Ty, dim: Option<u8>) -> String {138    match ty {139        Ty::Unit => "null".to_string(),140        Ty::Scalar { name } => name.clone(),141        Ty::Str => "str".to_string(),142        Ty::String => "String".to_string(),143        Ty::U128 => "u128".to_string(),144        Ty::I128 => "i128".to_string(),145        Ty::Code => "Code".to_string(),146        Ty::Json => "Json".to_string(),147        Ty::Vec { item } | Ty::Slice { item, .. } | Ty::Set { item } => {148            format!("[{}]", show(item, dim))149        }150        Ty::Option { item } => format!("{}?", show(item, dim)),151        Ty::Array { item, len } => format!("[{}; {len}]", show(item, dim)),152        Ty::Tuple { items } => {153            let parts: Vec<String> = items.iter().map(|item| show(item, dim)).collect();154            format!("({})", parts.join(", "))155        }156        Ty::Map { key, value } => format!("{{{}: {}}}", show(key, dim), show(value, dim)),157        Ty::Ref { mutable, item, .. } => {158            if *mutable && rng(item) {159                "Rng(seed)".to_string()160            } else {161                show(item, dim)162            }163        }164        Ty::Result { item } => show(item, dim),165        Ty::Hand { name, dim: own } => match (name.as_str(), own.or(dim)) {166            ("CellNd", Some(n)) => format!("Cell{n}d"),167            ("CellNd", None) => "Cell".to_string(),168            (other, _) => other.to_string(),169        },170        Ty::Plain { path } | Ty::Enum { path } | Ty::Class { path, .. } | Ty::Opaque { path } => {171            path.replace("::", ".")172        }173        Ty::Unknown { text } => text.clone(),174    }175}176177fn owned(shop: &Shop, ty: &Ty, dim: Option<u8>) -> Result<String> {178    Ok(match ty {179        Ty::Unit => "()".to_string(),180        Ty::Scalar { name } => name.clone(),181        Ty::Str | Ty::String => "String".to_string(),182        Ty::U128 => "u128".to_string(),183        Ty::I128 => "i128".to_string(),184        Ty::Code => shop.hand("Code")?,185        Ty::Json => "serde_json::Value".to_string(),186        Ty::Vec { item } | Ty::Slice { item, .. } => {187            format!("Vec<{}>", owned(shop, item, dim)?)188        }189        Ty::Set { item } => format!("std::collections::BTreeSet<{}>", owned(shop, item, dim)?),190        Ty::Option { item } => format!("Option<{}>", owned(shop, item, dim)?),191        Ty::Array { item, len } => format!("[{}; {len}]", owned(shop, item, dim)?),192        Ty::Tuple { items } => {193            let mut parts = Vec::new();194            for item in items {195                parts.push(owned(shop, item, dim)?);196            }197            format!("({})", parts.join(", "))198        }199        Ty::Ref { item, .. } | Ty::Result { item } => owned(shop, item, dim)?,200        Ty::Map { .. } => "_".to_string(),201        Ty::Hand { name, dim: own } => {202            let path = shop.hand(name)?;203            match (name.as_str(), own.or(dim)) {204                ("CellNd", Some(n)) => format!("{path}<{n}>"),205                ("CellNd", None) => return Err("a cell with no dimension".to_string()),206                _ => path,207            }208        }209        Ty::Plain { path } | Ty::Enum { path } | Ty::Class { path, .. } => {210            if shop.wide(path) {211                return Err(format!("{path} has a const generic"));212            }213            format!("mrlyrs::{path}")214        }215        Ty::Opaque { path } => return Err(format!("{path} does not cross")),216        Ty::Unknown { text } => return Err(format!("{text} does not cross")),217    })218}219220// READING221222fn take(at: usize, value: &str) -> String {223    format!("take!(name, {at}, {value})")224}225226fn read(shop: &Shop, ty: &Ty, at: usize, value: &str, deep: usize) -> Result<String> {227    if !special(ty) {228        return Ok(take(at, value));229    }230    Ok(match ty {231        Ty::U128 => format!("big(name, {at}, {value})?"),232        Ty::I128 => format!("small(name, {at}, {value})?"),233        Ty::Code => format!("{}::from(big(name, {at}, {value})?)", shop.hand("Code")?),234        Ty::Hand { .. } => format!("color(name, {at}, {value})?"),235        Ty::Ref { item, .. } | Ty::Result { item } => read(shop, item, at, value, deep)?,236        Ty::Option { item } => format!(237            "if Value::is_null({value}) {{ None }} else {{ Some({}) }}",238            read(shop, item, at, value, deep)?239        ),240        Ty::Vec { item } | Ty::Slice { item, .. } | Ty::Set { item } => {241            let one = read(shop, item, at, &format!("item{deep}"), deep + 1)?;242            let run = format!(243                "{{ let mut list{deep} = Vec::new(); for item{deep} in items(name, {at}, {value})? {{ list{deep}.push({one}); }} list{deep} }}"244            );245            match ty {246                Ty::Set { .. } => format!("{run}.into_iter().collect()"),247                _ => run,248            }249        }250        Ty::Array { item, len } => {251            let one = read(shop, item, at, &format!("item{deep}"), deep + 1)?;252            format!(253                "{{ 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\")) }} }}"254            )255        }256        Ty::Tuple { items } => {257            let mut parts = Vec::new();258            for (index, item) in items.iter().enumerate() {259                parts.push(read(260                    shop,261                    item,262                    at,263                    &format!("&parts{deep}[{index}]"),264                    deep + 1,265                )?);266            }267            format!(268                "{{ let parts{deep} = parts(name, {at}, {value}, {})?; ({}) }}",269                items.len(),270                parts.join(", ")271            )272        }273        Ty::Map { key, value: held } => {274            let one = read(shop, held, at, &format!("item{deep}"), deep + 1)?;275            let keyed = owned(shop, key, None)?;276            let held = owned(shop, held, None)?;277            format!(278                "{{ 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()"279            )280        }281        other => return Err(format!("{other:?} cannot be read")),282    })283}284285fn view(ty: &Ty, at: &str, top: bool) -> String {286    match ty {287        Ty::Str | Ty::String => format!("{at}.as_str()"),288        Ty::Vec { .. } | Ty::Slice { .. } => format!("{at}.as_slice()"),289        Ty::Tuple { items } => {290            let parts: Vec<String> = items291                .iter()292                .enumerate()293                .map(|(index, item)| view(item, &format!("{at}.{index}"), false))294                .collect();295            format!("({})", parts.join(", "))296        }297        _ if top => format!("*{at}"),298        _ => at.to_string(),299    }300}301302fn seen(shop: &Shop, ty: &Ty, dim: Option<u8>) -> Result<String> {303    Ok(match ty {304        Ty::Str | Ty::String => "&str".to_string(),305        Ty::Vec { item } | Ty::Slice { item, .. } => format!("&[{}]", owned(shop, item, dim)?),306        Ty::Tuple { items } => {307            let mut parts = Vec::new();308            for item in items {309                parts.push(seen(shop, item, dim)?);310            }311            format!("({})", parts.join(", "))312        }313        other => owned(shop, other, dim)?,314    })315}316317// WRITING318319fn give(ty: &Ty, value: &str, deep: usize) -> Result<String> {320    if !special(ty) {321        return Ok(match ty {322            Ty::Unit => "Value::Null".to_string(),323            _ => format!("give!({value})"),324        });325    }326    Ok(match ty {327        Ty::U128 | Ty::I128 => format!("Value::String({value}.to_string())"),328        Ty::Code => format!("Value::String({value}.get().to_string())"),329        Ty::Hand { .. } => format!("tint({value})"),330        Ty::Ref { item, .. } | Ty::Result { item } => give(item, value, deep)?,331        Ty::Option { item } => format!(332            "match {value} {{ Some(item{deep}) => {}, None => Value::Null }}",333            give(item, &format!("item{deep}"), deep + 1)?334        ),335        Ty::Vec { item } | Ty::Slice { item, .. } | Ty::Set { item } | Ty::Array { item, .. } => {336            format!(337                "{{ let mut list{deep} = Vec::new(); for item{deep} in {value} {{ list{deep}.push({}); }} Value::Array(list{deep}) }}",338                give(item, &format!("item{deep}"), deep + 1)?339            )340        }341        Ty::Tuple { items } => {342            let mut parts = Vec::new();343            for (index, item) in items.iter().enumerate() {344                parts.push(give(item, &format!("parts{deep}.{index}"), deep + 1)?);345            }346            format!(347                "{{ let parts{deep} = {value}; Value::Array(vec![{}]) }}",348                parts.join(", ")349            )350        }351        Ty::Map { value: held, .. } => format!(352            "{{ 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}) }}",353            give(held, &format!("item{deep}"), deep + 1)?354        ),355        other => return Err(format!("{other:?} cannot be written")),356    })357}358359// DOORS360361fn args_of(shop: &Shop, function: &Function) -> Result<Args> {362    let mut out = Vec::new();363    if let Some(kind) = function.self_kind {364        let owner = function365            .owner366            .as_ref()367            .ok_or_else(|| format!("{}: a self with no owner", function.path))?;368        let ty = shop.owner(owner)?;369        out.push((370            "self".to_string(),371            match kind {372                SelfKind::Value => ty,373                SelfKind::Ref => Ty::Ref {374                    mutable: false,375                    lifetime: None,376                    item: Box::new(ty),377                },378                SelfKind::Mut => Ty::Ref {379                    mutable: true,380                    lifetime: None,381                    item: Box::new(ty),382                },383            },384        ));385    }386    for param in &function.params {387        out.push((param.name.clone(), param.ty.clone()));388    }389    Ok(out)390}391392fn sign(shop: &Shop, function: &Function) -> Result<String> {393    let dim = match function.dims.as_slice() {394        [one] => Some(*one),395        _ => None,396    };397    let args = args_of(shop, function)?;398    let parts: Vec<String> = args399        .iter()400        .map(|(name, ty)| format!("{name}: {}", show(ty, dim)))401        .collect();402    Ok(format!(403        "({}) -> {}",404        parts.join(", "),405        show(&function.ret, dim)406    ))407}408409fn callee(shop: &Shop, function: &Function, dim: Option<u8>) -> String {410    let name = &function.name;411    match (&function.owner, &function.via) {412        (Some(owner), Some(via)) if function.source == Source::Trait => {413            format!("<mrlyrs::{owner} as mrlyrs::{via}>::{name}")414        }415        (Some(owner), _) => {416            let turbo = match dim {417                Some(n) if shop.wide(owner) => format!("::<{n}>"),418                _ => String::new(),419            };420            format!("mrlyrs::{owner}{turbo}::{name}")421        }422        (None, _) => {423            let turbo = match dim {424                Some(n) => format!("::<{n}>"),425                None => String::new(),426            };427            format!("mrlyrs::{}{turbo}", function.path)428        }429    }430}431432fn hold(shop: &Shop, ty: &Ty, at: usize, dim: Option<u8>) -> Result<Bind> {433    let slot = format!("args[{at}]");434    if let Ty::Ref { mutable: true, item, .. } = ty {435        if rng(item) {436            let open = format!("mrlyrs::core::Rng::new(seed(name, {at}, &{slot})?)");437            return Ok(Bind::Open(438                format!("let mut a{at} = {open};"),439                format!("&mut a{at}"),440            ));441        }442        return Ok(Bind::Shut("mutates its argument in place".to_string()));443    }444    if let Ty::Option { item } = ty {445        if let Ty::Ref { mutable: true, item: held, .. } = item.as_ref() {446            if rng(held) {447                let open = format!("mrlyrs::core::Rng::new(seed(name, {at}, &{slot})?)");448                return Ok(Bind::Open(449                    format!("let mut a{at} = if {slot}.is_null() {{ None }} else {{ Some({open}) }};"),450                    format!("a{at}.as_mut()"),451                ));452            }453            return Ok(Bind::Shut("mutates its argument in place".to_string()));454        }455    }456    let known = bare(ty);457    if let Ty::Plain { path } | Ty::Enum { path } | Ty::Class { path, .. } = known {458        if !shop.derives(path, "Deserialize") {459            return Ok(Bind::Shut(format!("{path} has no Deserialize")));460        }461    }462    let start = match read(shop, ty, at, &format!("&{slot}"), 0) {463        Ok(text) => text,464        Err(reason) => return Ok(Bind::Shut(reason)),465    };466    let mut lets = match owned(shop, ty, dim) {467        Ok(text) if text != "_" => format!("let a{at}: {text} = {start};"),468        _ => format!("let a{at} = {start};"),469    };470    let stripped = match ty {471        Ty::Ref { item, .. } => item.as_ref(),472        other => other,473    };474    let pass = match stripped {475        Ty::Str => format!("a{at}.as_str()"),476        Ty::Slice { item, .. } | Ty::Vec { item } if borrowed(item) => {477            let sight = match seen(shop, item, dim) {478                Ok(text) => text,479                Err(reason) => return Ok(Bind::Shut(reason)),480            };481            lets.push_str(&format!(482                " let b{at}: Vec<{sight}> = a{at}.iter().map(|item| {}).collect();",483                view(item, "item", true)484            ));485            format!("&b{at}")486        }487        Ty::Slice { .. } => format!("&a{at}"),488        Ty::Option { item } => match item.as_ref() {489            Ty::Ref { .. } => format!("a{at}.as_ref()"),490            Ty::Slice { .. } | Ty::Str => format!("a{at}.as_deref()"),491            _ => format!("a{at}"),492        },493        _ if matches!(ty, Ty::Ref { .. }) => format!("&a{at}"),494        _ => format!("a{at}"),495    };496    Ok(Bind::Open(lets, pass))497}498499fn probe(shop: &Shop, function: &Function) -> Result<String> {500    let args = args_of(shop, function)?;501    let cell = |ty: &Ty| matches!(ty, Ty::Hand { name, dim: None } if name == "CellNd");502    for (at, (_, ty)) in args.iter().enumerate() {503        let inner = bare(ty);504        if cell(inner) {505            return Ok(format!("cell_rank(name, args, {at})?"));506        }507        if let Ty::Slice { item, .. } | Ty::Vec { item } = inner {508            if cell(bare(item)) {509                return Ok(format!("cells_rank(name, args, {at})?"));510            }511        }512    }513    for (at, (_, ty)) in args.iter().enumerate() {514        if matches!(bare(ty), Ty::Hand { name, .. } if name == "Tensor") {515            return Ok(format!("tensor_rank(name, args, {at})?"));516        }517    }518    Err(format!("{}: no cell or tensor to rank", function.path))519}520521fn one(shop: &Shop, function: &Function, dim: Option<u8>) -> Result<Made> {522    let args = args_of(shop, function)?;523    let mut lines = vec![format!("count(name, args, {})?;", args.len())];524    let mut passes = Vec::new();525    for (at, (_, ty)) in args.iter().enumerate() {526        match hold(shop, ty, at, dim)? {527            Bind::Open(lets, pass) => {528                lines.push(lets);529                passes.push(pass);530            }531            Bind::Shut(reason) => return Ok(Made::Shut(reason)),532        }533    }534    let call = format!("{}({})", callee(shop, function, dim), passes.join(", "));535    let known = bare(&function.ret);536    if let Ty::Plain { path } | Ty::Enum { path } | Ty::Class { path, .. } = known {537        if !shop.derives(path, "Serialize") {538            return Ok(Made::Shut(format!("{path} has no Serialize")));539        }540    }541    let tail = match &function.ret {542        Ty::Result { item } => {543            let good = match item.as_ref() {544                Ty::Unit => "Ok(()) => Ok(Value::Null)".to_string(),545                other => match give(other, "value", 0) {546                    Ok(text) => format!("Ok(value) => Ok({text})"),547                    Err(reason) => return Ok(Made::Shut(reason)),548                },549            };550            format!("match {call} {{ {good}, Err(error) => Err(Fail::Error(error.to_string())) }}")551        }552        Ty::Unit => format!("{call};\nOk(Value::Null)"),553        other => match give(other, &call, 0) {554            Ok(text) => format!("Ok({text})"),555            Err(reason) => return Ok(Made::Shut(reason)),556        },557    };558    lines.push(tail);559    Ok(Made::Open(lines.join("\n")))560}561562fn build(shop: &Shop, group: &[&Function]) -> Result<Made> {563    let mut branches: Vec<(Option<u8>, &Function)> = Vec::new();564    for function in group {565        if function.dims.is_empty() {566            branches.push((None, function));567        } else {568            for dim in &function.dims {569                branches.push((Some(*dim), function));570            }571        }572    }573    if branches.len() == 1 {574        let (dim, function) = branches[0];575        return one(shop, function, dim);576    }577    let mut body = format!("match {} {{\n", probe(shop, branches[0].1)?);578    for (dim, function) in &branches {579        let dim = dim.ok_or("a branch with no dimension")?;580        match one(shop, function, Some(dim))? {581            Made::Open(text) => body.push_str(&format!("{dim} => {{\n{text}\n}}\n")),582            shut => return Ok(shut),583        }584    }585    body.push_str(586        "other => Err(usage(format!(\"{name} wants a 2d or 3d argument, got {other}d\"))),\n}",587    );588    Ok(Made::Open(body))589}590591fn doors(shop: &Shop, manifest: &Manifest) -> Result<Vec<Door>> {592    let mut groups: BTreeMap<String, Vec<&Function>> = BTreeMap::new();593    for function in &manifest.functions {594        if function.cross != Cross::Ok {595            continue;596        }597        groups598            .entry(function.path.replace("::", "."))599            .or_default()600            .push(function);601    }602    let mut taken: BTreeMap<String, usize> = BTreeMap::new();603    let mut out = Vec::new();604    for (name, group) in groups {605        let mut signs = Vec::new();606        for function in &group {607            signs.push(sign(shop, function)?);608        }609        let stem = format!("door_{}", name.replace('.', "_").to_lowercase());610        let count = taken.entry(stem.clone()).or_insert(0);611        *count += 1;612        let ident = match *count {613            1 => stem,614            other => format!("{stem}_{other}"),615        };616        let (sig, body) = match build(shop, &group)? {617            Made::Open(body) => (signs.join(" | "), Some(body)),618            Made::Shut(reason) => (619                format!("{} # uncallable: {reason}", signs.join(" | ")),620                None,621            ),622        };623        out.push(Door {624            name,625            sig,626            ident,627            body,628        });629    }630    Ok(out)631}632633// TEXT634635const HEAD: &str = r#"use serde_json::Value;636use std::process::ExitCode;637638// PLUMBING639640enum Fail {641    Usage(String),642    Error(String),643}644645type Done = std::result::Result<Value, Fail>;646647type Call = fn(&str, &[Value]) -> Done;648649type Door = (&'static str, &'static str, Option<Call>);650651const USAGE: &str = "usage: mrly list | mrly <module.fn> '[json, args]'";652653macro_rules! take {654    ($name:expr, $at:expr, $value:expr) => {655        match serde_json::from_value(Value::clone($value)) {656            Ok(value) => value,657            Err(error) => return Err(bad($name, $at, &error.to_string())),658        }659    };660}661662macro_rules! give {663    ($value:expr) => {664        match serde_json::to_value($value) {665            Ok(value) => value,666            Err(error) => return Err(Fail::Error(error.to_string())),667        }668    };669}670671fn usage(what: String) -> Fail {672    Fail::Usage(format!("mrly: {what}; {USAGE}"))673}674675fn bad(name: &str, at: usize, why: &str) -> Fail {676    usage(format!("{name} argument {at}: {why}"))677}678679fn count(name: &str, args: &[Value], want: usize) -> std::result::Result<(), Fail> {680    if args.len() == want {681        Ok(())682    } else {683        Err(usage(format!(684            "{name} takes {want} arguments, got {}",685            args.len()686        )))687    }688}689690fn slot<'a>(name: &str, args: &'a [Value], at: usize) -> std::result::Result<&'a Value, Fail> {691    args.get(at)692        .ok_or_else(|| usage(format!("{name} wants an argument {at}")))693}694695fn big(name: &str, at: usize, value: &Value) -> std::result::Result<u128, Fail> {696    let read = match value.as_str() {697        Some(text) => text.parse().ok(),698        None => value.as_u64().map(u128::from),699    };700    match read {701        Some(number) => Ok(number),702        None => Err(bad(name, at, "a u128 is a decimal string")),703    }704}705706fn small(name: &str, at: usize, value: &Value) -> std::result::Result<i128, Fail> {707    let read = match value.as_str() {708        Some(text) => text.parse().ok(),709        None => value.as_i64().map(i128::from),710    };711    match read {712        Some(number) => Ok(number),713        None => Err(bad(name, at, "an i128 is a decimal string")),714    }715}716717fn seed(name: &str, at: usize, value: &Value) -> std::result::Result<u64, Fail> {718    match value.as_u64() {719        Some(number) => Ok(number),720        None => Err(bad(name, at, "an Rng is a seed number")),721    }722}723724fn color(name: &str, at: usize, value: &Value) -> std::result::Result<mrlyrs::core::Color, Fail> {725    let bytes: [u8; 4] = match serde_json::from_value(value.clone()) {726        Ok(bytes) => bytes,727        Err(_) => return Err(bad(name, at, "a Color is four bytes")),728    };729    Ok(mrlyrs::core::Color {730        r: bytes[0],731        g: bytes[1],732        b: bytes[2],733        a: bytes[3],734    })735}736737fn tint(color: mrlyrs::core::Color) -> Value {738    Value::Array(vec![739        color.r.into(),740        color.g.into(),741        color.b.into(),742        color.a.into(),743    ])744}745746fn items<'a>(name: &str, at: usize, value: &'a Value) -> std::result::Result<&'a [Value], Fail> {747    match value.as_array() {748        Some(list) => Ok(list),749        None => Err(bad(name, at, "a JSON array")),750    }751}752753fn parts<'a>(754    name: &str,755    at: usize,756    value: &'a Value,757    want: usize,758) -> std::result::Result<&'a [Value], Fail> {759    match value.as_array() {760        Some(list) if list.len() == want => Ok(list),761        _ => Err(bad(name, at, &format!("a JSON array of {want}"))),762    }763}764765fn fields<'a>(766    name: &str,767    at: usize,768    value: &'a Value,769) -> std::result::Result<&'a serde_json::Map<String, Value>, Fail> {770    match value.as_object() {771        Some(map) => Ok(map),772        None => Err(bad(name, at, "a JSON object")),773    }774}775776fn axes(value: &Value) -> Option<usize> {777    Some(value.get("shape")?.as_array()?.len())778}779780fn walls(value: &Value) -> Option<usize> {781    axes(value.get("cell")?.get("types")?)782}783784fn tensor_rank(name: &str, args: &[Value], at: usize) -> std::result::Result<usize, Fail> {785    match axes(slot(name, args, at)?) {786        Some(rank) => Ok(rank),787        None => Err(bad(name, at, "a Tensor with a shape")),788    }789}790791fn cell_rank(name: &str, args: &[Value], at: usize) -> std::result::Result<usize, Fail> {792    match walls(slot(name, args, at)?) {793        Some(rank) => Ok(rank),794        None => Err(bad(name, at, "a cell")),795    }796}797798fn cells_rank(name: &str, args: &[Value], at: usize) -> std::result::Result<usize, Fail> {799    let value = slot(name, args, at)?;800    match value.as_array().and_then(|list| list.first()).and_then(walls) {801        Some(rank) => Ok(rank),802        None => Err(bad(name, at, "a list of cells")),803    }804}805806fn main() -> ExitCode {807    let words: Vec<String> = std::env::args().skip(1).collect();808    match run(&words) {809        Ok(()) => ExitCode::SUCCESS,810        Err(Fail::Error(message)) => {811            eprintln!("{message}");812            ExitCode::from(1)813        }814        Err(Fail::Usage(message)) => {815            eprintln!("{message}");816            ExitCode::from(2)817        }818    }819}820821fn run(words: &[String]) -> std::result::Result<(), Fail> {822    match words {823        [one] if one == "list" => {824            for (name, sig, _) in DOORS {825                println!("{name}{sig}");826            }827            Ok(())828        }829        [name, text] => {830            let at = DOORS831                .binary_search_by_key(&name.as_str(), |door| door.0)832                .map_err(|_| usage(format!("no door named {name:?}")))?;833            let call = DOORS[at]834                .2835                .ok_or_else(|| usage(format!("{name} is not callable from the shell")))?;836            let args = match serde_json::from_str::<Value>(text) {837                Ok(Value::Array(args)) => args,838                Ok(_) => return Err(usage(format!("{name} wants a JSON array of arguments"))),839                Err(error) => return Err(usage(format!("{name} arguments: {error}"))),840            };841            println!("{}", call(name, &args)?);842            Ok(())843        }844        _ => Err(usage("say list or a door".to_string())),845    }846}847"#;848849fn render(doors: &[Door]) -> String {850    let mut out = String::from(HEAD);851    out.push_str("\n// DOORS\n\nstatic DOORS: &[Door] = &[\n");852    for door in doors {853        let call = match &door.body {854            Some(_) => format!("Some({})", door.ident),855            None => "None".to_string(),856        };857        out.push_str(&format!(858            "    ({:?}, {:?}, {call}),\n",859            door.name, door.sig860        ));861    }862    out.push_str("];\n\n// CALLS\n");863    for door in doors {864        let Some(body) = &door.body else { continue };865        out.push_str(&format!(866            "\nfn {}(name: &str, args: &[Value]) -> Done {{\n",867            door.ident868        ));869        for line in body.lines() {870            out.push_str("    ");871            out.push_str(line);872            out.push('\n');873        }874        out.push_str("}\n");875    }876    out877}