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 }