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}