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}