import type { Fn , IObjectOf } from "@thi.ng/api" ; import { defmulti } from "@thi.ng/defmulti/defmulti" ; import { illegalArgs } from "@thi.ng/errors/illegal-arguments" ; import { fit } from "@thi.ng/math/fit" ; import { memoize1 } from "@thi.ng/memoize/memoize1" ; import type { NodeInputSpec , NodeSpec } from "@thi.ng/rstream-graph" ; import { addNode , node } from "@thi.ng/rstream-graph/graph" ; import { fromView } from "@thi.ng/rstream/view" ; import type { ASTNode , Implementations , Str , Sym } from "@thi.ng/sexpr" ; import { parse } from "@thi.ng/sexpr/parse" ; import { runtime } from "@thi.ng/sexpr/runtime" ; import { tokenize } from "@thi.ng/sexpr/tokenize" ; import { Z2 } from "@thi.ng/strings/pad-left" ; import { maybeParseFloat } from "@thi.ng/strings/parse" ; import { charRange } from "@thi.ng/strings/range" ; import type { Reducer } from "@thi.ng/transducers" ; import { add } from "@thi.ng/transducers/add" ; import { assocObj } from "@thi.ng/transducers/assoc-obj" ; import { comp } from "@thi.ng/transducers/comp" ; import { div } from "@thi.ng/transducers/div" ; import { filter } from "@thi.ng/transducers/filter" ; import { map } from "@thi.ng/transducers/map" ; import { mapIndexed } from "@thi.ng/transducers/map-indexed" ; import { mapcat } from "@thi.ng/transducers/mapcat" ; import { max } from "@thi.ng/transducers/max" ; import { mean } from "@thi.ng/transducers/mean" ; import { min } from "@thi.ng/transducers/min" ; import { mul } from "@thi.ng/transducers/mul" ; import { permutations } from "@thi.ng/transducers/permutations" ; import { range } from "@thi.ng/transducers/range" ; import { sub } from "@thi.ng/transducers/sub" ; import { transduce } from "@thi.ng/transducers/transduce" ; import { RE_CELL_ID , RE_CELL_RANGE } from "./api" ; import { DB , graph , removeCell } from "./state" ; /**
 * Runtime env, stores ID of result cell and tree/nesting depth of
 * currently interpreted S-expression.
 */ interface Env { id : string ; depth : number ; } const builtins = defmulti < Sym , ASTNode [ ] , Env , any > ( ( x ) => x . value ) ; const rt = runtime < Implementations < Env , any > , Env , any > ( { expr : ( x , env ) => builtins ( < Sym > x . children [ 0 ] , x . children , { ... env , depth : env . depth + 1 , } ) , sym : ( x ) => RE_CELL_ID . test ( x . value ) ? cellInput ( x . value ) : illegalArgs ( "invalid cell ID" ) , str : ( x ) => ( { const : x . value } ) , num : ( x ) => ( { const : x . value } ) , } ) ; param src param cellID export const $eval = ( src , cellID ) => rt ( parse ( tokenize ( src ) ) . children [ 0 ] , { id : cellID , depth : 0 } ) ; param spec param vals param env const defNode = ( spec , vals , env ) => { let id ; if ( env . depth === 1 ) { id = env . id ; spec . outs = { "*" : [ id , "value" ] , } ; } else { id = JSON . stringify ( vals ) ; if ( graph [ id ] ) { return { stream : ( ) => graph [ id ] . node } ; } } removeCell ( id ) ; const node = addNode ( graph , DB , id , spec ) ; return { stream : ( ) => node . node } ; } ; param fn const defBuiltin = ( fn ) => ( _ , vals , env ) => defNode ( { fn : node ( map ( fn ) ) , ins : transduce ( comp ( mapcat ( ( i ) => { try { return cellRangeInputs ( i ) ; } catch ( e ) { return < NodeInputSpec [ ] > [ rt ( i , env ) ] ; } } ) , mapIndexed ( ( i , input ) => < [ string , NodeInputSpec ] > [ Z2 ( i ) , input ] ) ) , assocObj < NodeInputSpec > ( ) , vals . slice ( 1 ) ) , } , vals , env ) ; param rfn param xf const defReducer = ( rfn , xf = ( x ) => x ) => defBuiltin ( ( ports ) => { const keys = Object . keys ( ports ) . sort ( ) ; return transduce ( comp ( map ( ( k ) => ports [ k ] ) , filter ( ( x ) => x != null ) , map ( xf ) ) , rfn ( ) , xf ( ports [ keys . shift ( ) ! ] ) , keys ) ; } ) ; /**
 * Returns a rstream-graph NodeInputSpec linked to the cell value given
 * by `id`, stored in the central DB state atom. The stream is
 * configured to attempt string-to-number conversion of its values.
 */ const cellInput = memoize1 ( ( id ) : NodeInputSpec => ( { stream : ( ) => fromView ( DB , { path : < const > [ id . toUpperCase ( ) , "value" ] , tx : ( x ) => maybeParseFloat ( < string > x , null ) , } ) , } ) ) ; param x const cellRangeInputs = ( x ) => { const [ acol , arow , bcol , brow ] = parseCellIDRange ( x ) ; return map < [ string , number ] , NodeInputSpec > ( ( [ c , r ] ) => cellInput ( `${ c }${ r }` ) , permutations ( charRange ( acol . toUpperCase ( ) , bcol . toUpperCase ( ) ) , range ( parseInt ( arow ) , parseInt ( brow ) + 1 ) ) ) ; } ; param x const parseCellIDRange = ( x ) => { const match = RE_CELL_RANGE . exec ( ( < Str > x ) . value ) ; if ( ! match ) illegalArgs ( "invalid cell range" ) ; return match ! . slice ( 1 , 5 ) ; } ; /**
 * Register built-ins.
 */ builtins . addAll ( { "+" : defReducer ( add ) , "*" : defReducer ( mul ) , "-" : defReducer ( sub ) , "/" : defReducer ( ( ) => div ( 1 ) ) , min : defReducer ( min ) , max : defReducer ( max ) , avg : defReducer ( ( ) => mean ( ) ) , mag : defReducer ( ( ) => [ ( ) => 0 , ( acc ) => Math . sqrt ( acc ) , ( acc , x ) => acc + x ] , ( x ) => x * x ) , abs : defBuiltin ( ( { "00" : x } ) => Math . abs ( x ) ) , fit : defBuiltin ( ( { "00" : x , "01" : a , "02" : b , "03" : c , "04" : d } ) => fit ( x , a , b , c , d ) ) , } ) ;