ledger.rs
7.7 kB · rust · 241 lines
1use crate::{checked, code_of, Fault};2use ledger::{3 self, identify, keys, numbers, search, sequence, Axis, Key, Measure, Sequence, Tier, BUDGET,4 RECORDS,5};6use mrlyrs::core::{json, Json};7use mrlyrs::math::bang::factory;8use mrlyrs::math::bang::Code;9use mrlyrs::math::counts;10use std::collections::BTreeMap;11use std::sync::{Mutex, OnceLock};12use wasm_bindgen::prelude::*;1314#[derive(Default)]15struct Shelf {16 rows: Vec<Sequence>,17 tiers: BTreeMap<Tier, (Vec<Key>, usize)>,18}1920fn key(code: &str, dimension: usize, base: usize, measure: &str, axis: &str) -> Result<Key, Fault> {21 let code = checked(code, dimension, base)?;22 Ok(Key::new(23 code,24 dimension,25 base,26 Measure::parse(measure)?,27 Axis::parse(axis)?,28 ))29}3031fn shelf() -> &'static Mutex<Shelf> {32 static SHELF: OnceLock<Mutex<Shelf>> = OnceLock::new();33 SHELF.get_or_init(|| Mutex::new(Shelf::default()))34}3536fn grow(shelf: &mut Shelf, tier: Tier, count: usize, span: usize) -> (usize, usize) {37 let (keys, cursor) = shelf.tiers.entry(tier).or_insert_with(|| (keys(tier), 0));38 let stop = keys.len().min(cursor.saturating_add(span));39 for key in &keys[*cursor..stop] {40 if let Ok(row) = sequence(key, count, BUDGET) {41 shelf.rows.push(row);42 }43 }44 *cursor = stop;45 (stop, keys.len())46}4748fn row(sequence: &Sequence) -> Json {49 let key = &sequence.key;50 json!({51 "name": key.name(),52 "code": key.code.to_string(),53 "d": key.dimension,54 "q": key.base,55 "measure": key.measure.slug(),56 "axis": key.axis.slug(),57 "number": key.number(),58 "start": key.axis.start(),59 "terms": sequence.terms.iter().map(|term| term.to_string()).collect::<Vec<String>>(),60 "capped": sequence.capped,61 "closed": sequence.closed.as_ref().map_or(String::new(), |form| form.text()),62 "oeis": sequence.record.map_or("", |(record, _)| record.id),63 "shift": sequence.record.map_or(0, |(_, shift)| shift),64 "tag": sequence.tag.map_or("", |tag| tag.text()),65 })66}6768/// Names every measure the ledger reads.69#[wasm_bindgen]70pub fn ledger_measures() -> Vec<String> {71 Measure::ALL72 .iter()73 .map(|measure| measure.slug().to_string())74 .collect()75}7677/// Lists the designs of a dimension and base, one code per orbit, as decimal strings.78#[wasm_bindgen]79pub fn ledger_designs(dimension: usize, base: usize) -> Result<Vec<String>, Fault> {80 Ok(ledger::designs(dimension, base)?81 .iter()82 .map(|code| code.to_string())83 .collect())84}8586/// Builds one tier of the catalog into memory, once, and returns the rows the catalog holds.87#[wasm_bindgen]88pub fn ledger_build(tier: &str, count: usize) -> Result<usize, Fault> {89 let tier = Tier::parse(tier)?;90 let mut guard = shelf().lock().expect("the shelf is not poisoned");91 grow(&mut guard, tier, count, usize::MAX);92 Ok(guard.rows.len())93}9495/// 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.96#[wasm_bindgen]97pub fn ledger_grow(tier: &str, count: usize, span: usize) -> Result<String, Fault> {98 let tier = Tier::parse(tier)?;99 let mut guard = shelf().lock().expect("the shelf is not poisoned");100 let (done, total) = grow(&mut guard, tier, count, span);101 Ok(json!({ "rows": guard.rows.len(), "done": done, "total": total }).to_string())102}103104/// 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.105#[wasm_bindgen]106pub fn ledger_search(107 query: &str,108 measure: &str,109 dimension: usize,110 base: usize,111 page: usize,112 rows: usize,113) -> String {114 let guard = shelf().lock().expect("the shelf is not poisoned");115 let wanted = Measure::parse(measure).ok();116 let hits: Vec<&Sequence> = search(&guard.rows, query)117 .into_iter()118 .map(|index| &guard.rows[index])119 .filter(|sequence| {120 let key = &sequence.key;121 wanted.is_none_or(|measure| key.measure == measure)122 && (dimension == 0 || key.dimension == dimension)123 && (base == 0 || key.base == base)124 })125 .collect();126 let shown: Vec<Json> = hits127 .iter()128 .skip(page * rows)129 .take(rows)130 .map(|sequence| row(sequence))131 .collect();132 json!({ "total": hits.len(), "rows": shown }).to_string()133}134135/// 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.136#[wasm_bindgen]137pub fn ledger_terms(138 code: &str,139 dimension: usize,140 base: usize,141 measure: &str,142 axis: &str,143 count: usize,144 cells: &str,145) -> Result<Vec<String>, Fault> {146 let key = key(code, dimension, base, measure, axis)?;147 let (terms, _) = ledger::terms(&key, count, code_of(cells)?)?;148 Ok(terms.iter().map(|term| term.to_string()).collect())149}150151/// Reads one design sequence of any code the space accepts as a catalog row within a cell budget, as JSON.152#[wasm_bindgen]153pub fn ledger_row(154 code: &str,155 dimension: usize,156 base: usize,157 measure: &str,158 axis: &str,159 count: usize,160 cells: &str,161) -> Result<String, Fault> {162 let key = key(code, dimension, base, measure, axis)?;163 Ok(row(&sequence(&key, count, code_of(cells)?)?).to_string())164}165166/// 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.167#[wasm_bindgen]168pub fn ledger_profile(169 code: &str,170 dimension: usize,171 base: usize,172 number: usize,173 level: u32,174) -> Result<Vec<String>, Fault> {175 let tile = factory::create(176 Code::from(checked(code, dimension, base)?),177 number,178 dimension,179 base,180 1,181 )?;182 Ok(counts::profile_of_tile(&tile, level)?183 .iter()184 .map(|count| count.to_string())185 .collect())186}187188/// Finds the records holding the typed terms as a window, each with the record's index of the first typed term, as JSON.189#[wasm_bindgen]190pub fn ledger_identify(terms: &str) -> String {191 let found: Vec<Json> = numbers(terms)192 .map(|window| identify(&window))193 .unwrap_or_default()194 .iter()195 .map(|(record, shift)| {196 json!({197 "id": record.id,198 "name": record.name,199 "offset": record.offset,200 "shift": shift,201 "terms": record.terms,202 })203 })204 .collect();205 json!(found).to_string()206}207208/// 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.209#[wasm_bindgen]210pub fn ledger_records() -> String {211 let rows: Vec<Json> = RECORDS212 .iter()213 .map(|record| {214 json!({215 "id": record.id,216 "name": record.name,217 "offset": record.offset,218 "terms": record.terms,219 "status": record.status.text(),220 "formula": record.formula,221 "witness": record.witness,222 "key": record.key.map_or(String::new(), |key| key.name()),223 "shift": record.shift,224 })225 })226 .collect();227 json!(rows).to_string()228}229230/// Spells the closed form of a design sequence, or an empty string when the ledger knows none.231#[wasm_bindgen]232pub fn ledger_closed(233 code: &str,234 dimension: usize,235 base: usize,236 measure: &str,237 axis: &str,238) -> Result<String, Fault> {239 let key = key(code, dimension, base, measure, axis)?;240 Ok(ledger::closed(&key)?.map_or(String::new(), |form| form.text()))241}