/ src / variable.js
variable.js
  1  import * as API from './api.js'
  2  import * as Type from './type.js'
  3  import * as Constant from './constant.js'
  4  
  5  export class Variable {
  6    static id = 1
  7    #name
  8    /**
  9     * @param {string|symbol} name
 10     */
 11    constructor(name = Symbol(), id = ++Variable.id) {
 12      this['?'] = { id }
 13      this.#name = name
 14    }
 15  
 16    get id() {
 17      return this['?'].id
 18    }
 19    get name() {
 20      return this.#name
 21    }
 22    toString() {
 23      return typeof this.#name === 'symbol' ?
 24          `?@${this.#name.description ?? this.id}`
 25        : `?${this.#name.toString()}`
 26    }
 27    get [Symbol.toStringTag]() {
 28      return this.toString()
 29    }
 30    [Symbol.for('nodejs.util.inspect.custom')]() {
 31      return this.toString()
 32    }
 33  
 34    /**
 35     *
 36     * @param {object} context
 37     * @param {number} context.id
 38     */
 39    with({ id }) {
 40      return new Variable(this.name, id)
 41    }
 42  
 43    // /**
 44    //  * @param {API.Term} term
 45    //  */
 46    // is(term) {
 47    //   return {
 48    //     match: { this: this, is: term },
 49    //     rule: { match: { this: this, is: this }, where: {} },
 50    //   }
 51    // }
 52  
 53    // /**
 54    //  * @param {API.Term} term
 55    //  */
 56    // not(term) {
 57    //   return {
 58    //     not: this.is(term),
 59    //   }
 60    // }
 61  
 62    toJSON() {
 63      return toJSON(this)
 64    }
 65  }
 66  
 67  /**
 68   * @param {string|symbol} name
 69   */
 70  export const create = (name) => new Variable(name)
 71  
 72  /**
 73   * @type {API.Variable<any>}
 74   */
 75  export const _ = create('_').with({ id: 0 })
 76  
 77  /**
 78   * @param {unknown} source
 79   * @returns {Iterable<API.Variable>}
 80   */
 81  export function* iterate(source) {
 82    if (is(source)) {
 83      yield source
 84    } else if (!Constant.is(source)) {
 85      for (const term of Object.values(source ?? {})) {
 86        if (is(term)) {
 87          yield term
 88        } else if (!Constant.is(term)) {
 89          yield* iterate(term)
 90        }
 91      }
 92    }
 93  }
 94  
 95  /**
 96   * @param {API.Variable} actual
 97   * @param {API.Variable} expected
 98   */
 99  export const equal = (actual, expected) => id(actual) === id(expected)
100  
101  /**
102   * Predicate function that checks if given `term` is a {@link API.Variable}.
103   *
104   * @param {unknown} term
105   * @returns {term is API.Variable}
106   */
107  export const is = (term) =>
108    typeof (/** @type {{['?']?: {id?:unknown}}} */ (term)?.['?']?.id) === 'number'
109  
110  /**
111   * @param {API.Variable} variable
112   * @returns {API.VariableID}
113   */
114  export const id = (variable) => variable['?'].id
115  
116  /**
117   * @template {API.Scalar} T
118   * @param {API.Variable<T>} variable
119   * @returns {API.Variable<T>}
120   */
121  export const toJSON = (variable) => {
122    const { type, id } = variable['?']
123    const instance = {}
124    if (type != null) {
125      instance.type = Type.toJSON(type)
126    }
127    if (id != null) {
128      instance.id = id
129    }
130  
131    return { ['?']: instance }
132  }
133  
134  export { toJSON as inspect }
135  
136  /**
137   * @param {API.Variable} variable
138   */
139  export const toString = (variable) => JSON.stringify(toJSON(variable))
140  
141  /**
142   * @param {API.Variable} variable
143   * @returns {boolean}
144   */
145  export const isBlank = (variable) => id(variable) === 0
146  
147  /**
148   * @param {API.Variable} variable
149   * @param {API.Variable} to
150   * @returns {-1|0|1}
151   */
152  export const compare = ({ ['?']: { id: left } }, { ['?']: { id: right } }) => {
153    return (
154      left < right ? -1
155      : left > right ? 1
156      : 0
157    )
158  }
159  
160  /**
161   * @param {API.Variable} variable
162   */
163  export const toDebugString = (variable) => {
164    const name = `${variable}`.slice(1)
165    return /^[a-zA-Z_]\w*$/.test(name) ?
166        `$.${name}`
167      : `$[${JSON.stringify(name)}]`
168  }