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}