formula.js
1 import * as API from '../api.js' 2 import * as Cursor from '../cursor.js' 3 import * as Term from '../term.js' 4 import * as Constant from '../constant.js' 5 import * as Match from '../match.js' 6 import operators from '../formula/lib.js' 7 import * as Terms from '../terms.js' 8 9 export class FormulaApplication { 10 /** 11 * @param {API.SystemOperator} source 12 * @param {Map<API.Variable, number>} cells 13 * @param {Record<string, API.Term>|API.Term} from 14 * @param {Record<string, API.Term>} to 15 * @param {API.Cursor} cursor 16 * @param {API.MatchFrame} parameters 17 */ 18 constructor(source, cells, from, to, cursor, parameters) { 19 this.cells = cells 20 this.source = source 21 this.from = from 22 this.to = to 23 this.cursor = cursor 24 this.parameters = parameters 25 } 26 27 get recurs() { 28 return null 29 } 30 31 // /** 32 // * Base execution cost of the formula application operation. 33 // */ 34 // get cost() { 35 // return 5 36 // } 37 38 /** 39 * @template {API.Terms} Terms 40 * @param {Terms} terms 41 * @param {API.MatchFrame} bindings 42 * @returns {API.InferTerms<Terms>} 43 */ 44 resolve(terms, bindings) { 45 return /** @type {API.InferTerms<Terms>} */ ( 46 Term.is(terms) ? Cursor.get(bindings, this.cursor, terms) 47 : Array.isArray(terms) ? 48 terms.map((term) => Cursor.get(bindings, this.cursor, term)) 49 : Object.fromEntries( 50 Object.entries(terms).map(([key, term]) => [ 51 key, 52 Cursor.get(bindings, this.cursor, term), 53 ]) 54 ) 55 ) 56 } 57 58 /** 59 * @param {API.EvaluationContext} context 60 */ 61 *evaluate({ selection }) { 62 const { source } = this 63 const operator = 64 /** @type {(input: API.Operand) => Iterable<API.Operand>} */ 65 (source.formula ?? operators[source.operator]) 66 67 const matches = [] 68 for (const frame of selection) { 69 for (const output of operator(this.read(frame))) { 70 // If function returns single output we treat it as { is: output } 71 // because is will be a cell in the formula application. 72 const extension = Constant.is(output) ? { is: output } : output 73 const frames = this.write( 74 frame, 75 /** @type {Record<string, API.Scalar>} */ (extension) 76 ) 77 matches.push(...frames) 78 } 79 } 80 81 return matches 82 } 83 84 /** 85 * @template {API.Terms} Terms 86 * @param {API.MatchFrame} frame 87 * @returns {API.InferTerms<Terms>} 88 */ 89 read(frame) { 90 const { from } = this 91 const result = 92 Term.is(from) ? 93 Constant.expect(Match.get(frame, from), 'Formula input was not present') 94 : Object.fromEntries( 95 Object.entries(from).map(([key, term]) => [ 96 key, 97 Constant.expect( 98 Match.get(frame, term), 99 'Formula input was not present' 100 ), 101 ]) 102 ) 103 104 return /** @type {API.InferTerms<Terms>} */ (result) 105 } 106 107 /** 108 * @param {API.MatchFrame} frame 109 * @param {Record<string, API.Scalar>} extension 110 */ 111 write(frame, extension) { 112 const terms = Object.entries(this.to) 113 if (terms.length === 0) { 114 return [frame] 115 } else { 116 let match = Match.clone(frame) 117 for (const [key, term] of terms) { 118 const { ok } = Match.unify(match, term, extension[key]) 119 if (ok) { 120 match = ok 121 } else { 122 return [] 123 } 124 } 125 return [match] 126 } 127 } 128 129 toJSON() { 130 return { 131 match: this.source.match, 132 operator: this.source.operator, 133 } 134 } 135 136 toDebugString() { 137 const { match, operator } = this.source 138 139 return `{ match: ${Terms.toDebugString(match)}, operator: "${operator}" }` 140 } 141 }