/ src / plan / formula.js
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  }