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}