ledger.rs
7.6 kB · rust · 234 lines
1use crate::{checked, code_of, Fault};2use mrlycore::{json, Json};3use mrlylab::ledger::{4 self, identify, keys, numbers, search, sequence, Axis, Key, Measure, Sequence, Tier, BUDGET,5 RECORDS,6};7use mrlymath::bang::factory;8use mrlymath::formulas;9use std::collections::BTreeMap;10use std::sync::{Mutex, OnceLock};11use wasm_bindgen::prelude::*;1213#[derive(Default)]14struct Shelf {15 rows: Vec<Sequence>,16 tiers: BTreeMap<Tier, (Vec<Key>, usize)>,17}1819fn key(code: &str, dimension: usize, base: usize, measure: &str, axis: &str) -> Result<Key, Fault> {20 let code = checked(code, dimension, base)?;21 Ok(Key::new(22 code,23 dimension,24 base,25 Measure::parse(measure)?,26 Axis::parse(axis)?,27 ))28}2930fn shelf() -> &'static Mutex<Shelf> {31 static SHELF: OnceLock<Mutex<Shelf>> = OnceLock::new();32 SHELF.get_or_init(|| Mutex::new(Shelf::default()))33}3435fn grow(shelf: &mut Shelf, tier: Tier, count: usize, span: usize) -> (usize, usize) {36 let (keys, cursor) = shelf.tiers.entry(tier).or_insert_with(|| (keys(tier), 0));37 let stop = keys.len().min(cursor.saturating_add(span));38 for key in &keys[*cursor..stop] {39 if let Ok(row) = sequence(key, count, BUDGET) {40 shelf.rows.push(row);41 }42 }43 *cursor = stop;44 (stop, keys.len())45}4647fn row(sequence: &Sequence) -> Json {48 let key = &sequence.key;49 json!({50 "name": key.name(),51 "code": key.code.to_string(),52 "d": key.dimension,53 "q": key.base,54 "measure": key.measure.slug(),55 "axis": key.axis.slug(),56 "number": key.number(),57 "start": key.axis.start(),58 "terms": sequence.terms.iter().map(|term| term.to_string()).collect::<Vec<String>>(),59 "capped": sequence.capped,60 "closed": sequence.closed.as_ref().map_or(String::new(), |form| form.text()),61 "oeis": sequence.record.map_or("", |(record, _)| record.id),62 "shift": sequence.record.map_or(0, |(_, shift)| shift),63 "tag": sequence.tag.map_or("", |tag| tag.text()),64 })65}6667/// Names every measure the ledger reads.68#[wasm_bindgen]69pub fn ledger_measures() -> Vec<String> {70 Measure::ALL71 .iter()72 .map(|measure| measure.slug().to_string())73 .collect()74}7576/// Lists the designs of a dimension and base, one code per orbit, as decimal strings.77#[wasm_bindgen]78pub fn ledger_designs(dimension: usize, base: usize) -> Result<Vec<String>, Fault> {79 Ok(ledger::designs(dimension, base)?80 .iter()81 .map(|code| code.to_string())82 .collect())83}8485/// Builds one tier of the catalog into memory, once, and returns the rows the catalog holds.86#[wasm_bindgen]87pub fn ledger_build(tier: &str, count: usize) -> Result<usize, Fault> {88 let tier = Tier::parse(tier)?;89 let mut guard = shelf().lock().expect("the shelf is not poisoned");90 grow(&mut guard, tier, count, usize::MAX);91 Ok(guard.rows.len())92}9394/// Builds the next span of keys of one tier into memory and reports the rows so far with the tier's keys done and in all, as JSON, so a page can build between frames.95#[wasm_bindgen]96pub fn ledger_grow(tier: &str, count: usize, span: usize) -> Result<String, Fault> {97 let tier = Tier::parse(tier)?;98 let mut guard = shelf().lock().expect("the shelf is not poisoned");99 let (done, total) = grow(&mut guard, tier, count, span);100 Ok(json!({ "rows": guard.rows.len(), "done": done, "total": total }).to_string())101}102103/// Searches the catalog by a window of terms or a name fragment, narrowed by measure, dimension and base where given, one page of rows at a time, as JSON.104#[wasm_bindgen]105pub fn ledger_search(106 query: &str,107 measure: &str,108 dimension: usize,109 base: usize,110 page: usize,111 rows: usize,112) -> String {113 let guard = shelf().lock().expect("the shelf is not poisoned");114 let wanted = Measure::parse(measure).ok();115 let hits: Vec<&Sequence> = search(&guard.rows, query)116 .into_iter()117 .map(|index| &guard.rows[index])118 .filter(|sequence| {119 let key = &sequence.key;120 wanted.is_none_or(|measure| key.measure == measure)121 && (dimension == 0 || key.dimension == dimension)122 && (base == 0 || key.base == base)123 })124 .collect();125 let shown: Vec<Json> = hits126 .iter()127 .skip(page * rows)128 .take(rows)129 .map(|sequence| row(sequence))130 .collect();131 json!({ "total": hits.len(), "rows": shown }).to_string()132}133134/// Reads the first terms of a design sequence within a cell budget, as decimal strings, fewer than asked when the budget or a u128 stops them.135#[wasm_bindgen]136pub fn ledger_terms(137 code: &str,138 dimension: usize,139 base: usize,140 measure: &str,141 axis: &str,142 count: usize,143 cells: &str,144) -> Result<Vec<String>, Fault> {145 let key = key(code, dimension, base, measure, axis)?;146 let (terms, _) = ledger::terms(&key, count, code_of(cells)?)?;147 Ok(terms.iter().map(|term| term.to_string()).collect())148}149150/// Reads one design sequence of any code the space accepts as a catalog row within a cell budget, as JSON.151#[wasm_bindgen]152pub fn ledger_row(153 code: &str,154 dimension: usize,155 base: usize,156 measure: &str,157 axis: &str,158 count: usize,159 cells: &str,160) -> Result<String, Fault> {161 let key = key(code, dimension, base, measure, axis)?;162 Ok(row(&sequence(&key, count, code_of(cells)?)?).to_string())163}164165/// Counts the filled cells on every diagonal plane of a design at the side and level, as decimal strings: the strip itself in dimension one.166#[wasm_bindgen]167pub fn ledger_profile(168 code: &str,169 dimension: usize,170 base: usize,171 number: usize,172 level: u32,173) -> Result<Vec<String>, Fault> {174 let tile = factory::create(checked(code, dimension, base)?, number, dimension, base, 1)?;175 Ok(formulas::profile_of_tile(&tile, level)?176 .iter()177 .map(|count| count.to_string())178 .collect())179}180181/// Finds the records holding the typed terms as a window, each with the record's index of the first typed term, as JSON.182#[wasm_bindgen]183pub fn ledger_identify(terms: &str) -> String {184 let found: Vec<Json> = numbers(terms)185 .map(|window| identify(&window))186 .unwrap_or_default()187 .iter()188 .map(|(record, shift)| {189 json!({190 "id": record.id,191 "name": record.name,192 "offset": record.offset,193 "shift": shift,194 "terms": record.terms,195 })196 })197 .collect();198 json!(found).to_string()199}200201/// Lists the curated records: id, name, offset, first terms, status, formula, witness, and the key and shift where the entry names a design sequence, as JSON.202#[wasm_bindgen]203pub fn ledger_records() -> String {204 let rows: Vec<Json> = RECORDS205 .iter()206 .map(|record| {207 json!({208 "id": record.id,209 "name": record.name,210 "offset": record.offset,211 "terms": record.terms,212 "status": record.status.text(),213 "formula": record.formula,214 "witness": record.witness,215 "key": record.key.map_or(String::new(), |key| key.name()),216 "shift": record.shift,217 })218 })219 .collect();220 json!(rows).to_string()221}222223/// Spells the closed form of a design sequence, or an empty string when the ledger knows none.224#[wasm_bindgen]225pub fn ledger_closed(226 code: &str,227 dimension: usize,228 base: usize,229 measure: &str,230 axis: &str,231) -> Result<String, Fault> {232 let key = key(code, dimension, base, measure, axis)?;233 Ok(ledger::closed(&key)?.map_or(String::new(), |form| form.text()))234}