@josd/eyecode 0.0.0-stage → 0.1.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE.md +9 -0
- package/README.md +36 -2
- package/SPEC.md +921 -0
- package/bin/eyecode.js +28 -0
- package/package.json +43 -4
- package/src/arith.js +432 -0
- package/src/builtins.js +185 -0
- package/src/cli.js +126 -0
- package/src/common.js +120 -0
- package/src/engine.js +867 -0
- package/src/goals.js +44 -0
- package/src/index.js +15 -0
- package/src/lang.js +476 -0
- package/src/node.js +21 -0
- package/src/program.js +278 -0
- package/src/proof.js +679 -0
- package/src/reader.js +396 -0
- package/src/terms.js +502 -0
- package/src/version.js +2 -0
- package/src/writer.js +261 -0
package/src/terms.js
ADDED
|
@@ -0,0 +1,502 @@
|
|
|
1
|
+
// Finite-tree terms, substitutions, unification and the standard order.
|
|
2
|
+
//
|
|
3
|
+
// A term is one of
|
|
4
|
+
// - a string: an atom ('socrates'; '[]' is the empty list),
|
|
5
|
+
// - a bigint: an integer, of unbounded size,
|
|
6
|
+
// - a number: a float, an IEEE-754 double,
|
|
7
|
+
// - a Var: a logical variable, identified by its name,
|
|
8
|
+
// - a Struct: a compound term, a name with an array of argument terms.
|
|
9
|
+
// A list is built from Struct('.', [Head, Tail]) cells ending in '[]'.
|
|
10
|
+
// Terms never change once built.
|
|
11
|
+
|
|
12
|
+
export const EMPTY = '[]';
|
|
13
|
+
|
|
14
|
+
export class EyecodeError extends Error {
|
|
15
|
+
constructor(message) {
|
|
16
|
+
super(message);
|
|
17
|
+
this.name = 'EyecodeError';
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export class Var {
|
|
22
|
+
constructor(name) {
|
|
23
|
+
this.name = name;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
// Lets [H, ...T] write an open list: spreading a variable yields its tail.
|
|
27
|
+
*[Symbol.iterator]() {
|
|
28
|
+
yield new Tail(this);
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export class Struct {
|
|
33
|
+
constructor(name, args) {
|
|
34
|
+
this.name = name;
|
|
35
|
+
this.args = args;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
*[Symbol.iterator]() {
|
|
39
|
+
yield new Tail(this);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// The rest of an open list: [H, ...T] spreads T, which yields one Tail.
|
|
44
|
+
export class Tail {
|
|
45
|
+
constructor(term) {
|
|
46
|
+
this.term = term;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export const isVar = (term) => term instanceof Var;
|
|
51
|
+
export const isStruct = (term) => term instanceof Struct;
|
|
52
|
+
export const isAtom = (term) => typeof term === 'string';
|
|
53
|
+
export const isInt = (term) => typeof term === 'bigint';
|
|
54
|
+
export const isFloat = (term) => typeof term === 'number';
|
|
55
|
+
export const isNumber = (term) => typeof term === 'bigint' || typeof term === 'number';
|
|
56
|
+
export const isCallable = (term) => typeof term === 'string' || term instanceof Struct;
|
|
57
|
+
|
|
58
|
+
// 0, 1, 2 or 3: the rank of a term's kind in the standard order.
|
|
59
|
+
function kindRank(term) {
|
|
60
|
+
if (term instanceof Var) return 0;
|
|
61
|
+
const type = typeof term;
|
|
62
|
+
if (type === 'bigint' || type === 'number') return 1;
|
|
63
|
+
if (type === 'string') return 2;
|
|
64
|
+
return 3;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export function nonfinite(value, where = '') {
|
|
68
|
+
return new EyecodeError(`${where}a float term must be finite, not ${value}`);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
export function cons(head, tail) {
|
|
72
|
+
return new Struct('.', [head, tail]);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
export function listFromItems(items, tail = EMPTY) {
|
|
76
|
+
let result = tail;
|
|
77
|
+
for (let i = items.length - 1; i >= 0; i--) result = new Struct('.', [items[i], result]);
|
|
78
|
+
return result;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
export function isCons(term) {
|
|
82
|
+
return term instanceof Struct && term.name === '.' && term.args.length === 2;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
export function properListItems(term, env) {
|
|
86
|
+
const items = [];
|
|
87
|
+
let cursor = deref(term, env);
|
|
88
|
+
while (cursor instanceof Struct && cursor.name === '.' && cursor.args.length === 2) {
|
|
89
|
+
items.push(cursor.args[0]);
|
|
90
|
+
cursor = deref(cursor.args[1], env);
|
|
91
|
+
}
|
|
92
|
+
return cursor === EMPTY ? items : null;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// The name/arity of a callable term, as a string usable as a map key.
|
|
96
|
+
export function key(term) {
|
|
97
|
+
return typeof term === 'string' ? `${term}/0` : `${term.name}/${term.args.length}`;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
export function keyName(goalKey) {
|
|
101
|
+
return goalKey.slice(0, goalKey.lastIndexOf('/'));
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
export function keyArity(goalKey) {
|
|
105
|
+
return Number(goalKey.slice(goalKey.lastIndexOf('/') + 1));
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
export function isTerm(term, name, arity) {
|
|
109
|
+
if (typeof term === 'string') return arity === 0 && term === name;
|
|
110
|
+
return term instanceof Struct && term.name === name && term.args.length === arity;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
export function flattenConjunction(goal) {
|
|
114
|
+
const out = [];
|
|
115
|
+
const stack = [goal];
|
|
116
|
+
while (stack.length) {
|
|
117
|
+
const current = stack.pop();
|
|
118
|
+
if (current instanceof Struct && current.name === 'and' && current.args.length === 2) {
|
|
119
|
+
stack.push(current.args[1]);
|
|
120
|
+
stack.push(current.args[0]);
|
|
121
|
+
} else {
|
|
122
|
+
out.push(current);
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
return out;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
// Goals joined left to right: [g1, g2, g3] is and(and(g1, g2), g3).
|
|
129
|
+
export function conjunction(items) {
|
|
130
|
+
if (!items.length) return 'true';
|
|
131
|
+
let result = items[0];
|
|
132
|
+
for (let i = 1; i < items.length; i++) result = new Struct('and', [result, items[i]]);
|
|
133
|
+
return result;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
// One substitution per search, with an undo trail. Backtracking restores the
|
|
137
|
+
// bindings a failed branch made instead of copying the whole map for every
|
|
138
|
+
// alternative. The trail records each name with the value it had, so
|
|
139
|
+
// rebinding a name, which path compression in deref does, undoes correctly.
|
|
140
|
+
export class Env {
|
|
141
|
+
constructor() {
|
|
142
|
+
this.bindings = new Map();
|
|
143
|
+
this.trail = [];
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
mark() {
|
|
147
|
+
return this.trail.length;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
undo(mark) {
|
|
151
|
+
const { trail, bindings } = this;
|
|
152
|
+
while (trail.length > mark) {
|
|
153
|
+
const previous = trail.pop();
|
|
154
|
+
const name = trail.pop();
|
|
155
|
+
if (previous === undefined) bindings.delete(name);
|
|
156
|
+
else bindings.set(name, previous);
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
bind(name, value) {
|
|
161
|
+
this.trail.push(name, this.bindings.get(name));
|
|
162
|
+
this.bindings.set(name, value);
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
export function deref(term, env) {
|
|
167
|
+
if (!(term instanceof Var)) return term;
|
|
168
|
+
const bindings = env.bindings;
|
|
169
|
+
let value = bindings.get(term.name);
|
|
170
|
+
if (value === undefined) return term;
|
|
171
|
+
if (!(value instanceof Var)) return value;
|
|
172
|
+
// Resolving a chain of variable-to-variable bindings records where the
|
|
173
|
+
// whole chain ended, so a variable threaded through N resolution steps is
|
|
174
|
+
// not re-walked on every dereference.
|
|
175
|
+
const chain = [term.name];
|
|
176
|
+
while (value instanceof Var) {
|
|
177
|
+
const following = bindings.get(value.name);
|
|
178
|
+
if (following === undefined) break;
|
|
179
|
+
chain.push(value.name);
|
|
180
|
+
value = following;
|
|
181
|
+
}
|
|
182
|
+
for (const name of chain) env.bind(name, value);
|
|
183
|
+
return value;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
function occurs(name, term, env) {
|
|
187
|
+
const pending = [term];
|
|
188
|
+
while (pending.length) {
|
|
189
|
+
const item = deref(pending.pop(), env);
|
|
190
|
+
if (item instanceof Var) {
|
|
191
|
+
if (item.name === name) return true;
|
|
192
|
+
} else if (item instanceof Struct) {
|
|
193
|
+
for (const arg of item.args) pending.push(arg);
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
return false;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
// Atoms, integers and floats are equal when they are of the same kind and
|
|
200
|
+
// value: 7n and 7 stay distinct.
|
|
201
|
+
function sameConstant(a, b) {
|
|
202
|
+
return typeof a === typeof b && a === b;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
// Unify on finite trees: the occurs check always applies.
|
|
206
|
+
export function unify(left, right, env) {
|
|
207
|
+
const pending = [left, right];
|
|
208
|
+
while (pending.length) {
|
|
209
|
+
let b = pending.pop();
|
|
210
|
+
let a = pending.pop();
|
|
211
|
+
if (b instanceof Var) b = deref(b, env);
|
|
212
|
+
if (a instanceof Var) a = deref(a, env);
|
|
213
|
+
if (a === b) continue;
|
|
214
|
+
if (a instanceof Var) {
|
|
215
|
+
if (b instanceof Var && a.name === b.name) continue;
|
|
216
|
+
if (b instanceof Struct && occurs(a.name, b, env)) return false;
|
|
217
|
+
env.bind(a.name, b);
|
|
218
|
+
} else if (b instanceof Var) {
|
|
219
|
+
if (a instanceof Struct && occurs(b.name, a, env)) return false;
|
|
220
|
+
env.bind(b.name, a);
|
|
221
|
+
} else if (a instanceof Struct) {
|
|
222
|
+
if (!(b instanceof Struct) || a.name !== b.name || a.args.length !== b.args.length) return false;
|
|
223
|
+
const argsA = a.args;
|
|
224
|
+
const argsB = b.args;
|
|
225
|
+
for (let i = argsA.length - 1; i >= 0; i--) {
|
|
226
|
+
const argA = argsA[i];
|
|
227
|
+
const argB = argsB[i];
|
|
228
|
+
if (argA === argB) continue;
|
|
229
|
+
if (!(argA instanceof Var) && !(argA instanceof Struct) && !(argB instanceof Var)) {
|
|
230
|
+
if (!sameConstant(argA, argB)) return false;
|
|
231
|
+
continue;
|
|
232
|
+
}
|
|
233
|
+
pending.push(argA, argB);
|
|
234
|
+
}
|
|
235
|
+
} else if (b instanceof Struct || !sameConstant(a, b)) {
|
|
236
|
+
return false;
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
return true;
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
// Rename a term's variables apart. A subterm without variables is shared.
|
|
243
|
+
export function freshTerm(term, suffix, names = new Map()) {
|
|
244
|
+
if (term instanceof Var) {
|
|
245
|
+
let fresh = names.get(term.name);
|
|
246
|
+
if (fresh === undefined) {
|
|
247
|
+
fresh = new Var(`${term.name}#${suffix}`);
|
|
248
|
+
names.set(term.name, fresh);
|
|
249
|
+
}
|
|
250
|
+
return fresh;
|
|
251
|
+
}
|
|
252
|
+
if (!(term instanceof Struct)) return term;
|
|
253
|
+
const args = term.args;
|
|
254
|
+
let copied = null;
|
|
255
|
+
for (let index = 0; index < args.length; index++) {
|
|
256
|
+
const arg = args[index];
|
|
257
|
+
let copy;
|
|
258
|
+
if (arg instanceof Var) {
|
|
259
|
+
copy = names.get(arg.name);
|
|
260
|
+
if (copy === undefined) {
|
|
261
|
+
copy = new Var(`${arg.name}#${suffix}`);
|
|
262
|
+
names.set(arg.name, copy);
|
|
263
|
+
}
|
|
264
|
+
} else if (arg instanceof Struct) {
|
|
265
|
+
copy = freshTerm(arg, suffix, names);
|
|
266
|
+
} else {
|
|
267
|
+
copy = arg;
|
|
268
|
+
}
|
|
269
|
+
if (copied === null) {
|
|
270
|
+
if (copy === arg) continue;
|
|
271
|
+
copied = args.slice(0, index);
|
|
272
|
+
}
|
|
273
|
+
copied.push(copy);
|
|
274
|
+
}
|
|
275
|
+
return copied === null ? term : new Struct(term.name, copied);
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
// Terms nested deeper than this are copied iteratively instead.
|
|
279
|
+
const COPY_DEPTH = 200;
|
|
280
|
+
const DEEP = Symbol('deep');
|
|
281
|
+
|
|
282
|
+
// The term with every bound variable replaced by its value. A subterm that
|
|
283
|
+
// resolves to itself is not copied, and a value bound to a variable is copied
|
|
284
|
+
// once however often it is used, so shared subterms stay shared.
|
|
285
|
+
export function copyResolved(term, env) {
|
|
286
|
+
try {
|
|
287
|
+
return copy(term, env, new Map(), 0);
|
|
288
|
+
} catch (error) {
|
|
289
|
+
if (error !== DEEP) throw error;
|
|
290
|
+
return copyIteratively(term, env);
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
function copy(term, env, memo, depth) {
|
|
295
|
+
if (term instanceof Var) {
|
|
296
|
+
const value = deref(term, env);
|
|
297
|
+
if (!(value instanceof Struct)) return value;
|
|
298
|
+
let copied = memo.get(value);
|
|
299
|
+
if (copied === undefined) {
|
|
300
|
+
copied = copy(value, env, memo, depth);
|
|
301
|
+
memo.set(value, copied);
|
|
302
|
+
}
|
|
303
|
+
return copied;
|
|
304
|
+
}
|
|
305
|
+
if (!(term instanceof Struct)) return term;
|
|
306
|
+
if (depth > COPY_DEPTH) throw DEEP;
|
|
307
|
+
const args = term.args;
|
|
308
|
+
let copied = null;
|
|
309
|
+
for (let position = 0; position < args.length; position++) {
|
|
310
|
+
const arg = args[position];
|
|
311
|
+
const value = arg instanceof Var || arg instanceof Struct ? copy(arg, env, memo, depth + 1) : arg;
|
|
312
|
+
if (copied === null) {
|
|
313
|
+
if (value === arg) continue;
|
|
314
|
+
copied = args.slice(0, position);
|
|
315
|
+
}
|
|
316
|
+
copied.push(value);
|
|
317
|
+
}
|
|
318
|
+
return copied === null ? term : new Struct(term.name, copied);
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
function copyIteratively(term, env) {
|
|
322
|
+
const root = deref(term, env);
|
|
323
|
+
if (!(root instanceof Struct)) return root;
|
|
324
|
+
const done = new Map();
|
|
325
|
+
const stack = [root];
|
|
326
|
+
while (stack.length) {
|
|
327
|
+
const node = stack[stack.length - 1];
|
|
328
|
+
if (done.has(node)) {
|
|
329
|
+
stack.pop();
|
|
330
|
+
continue;
|
|
331
|
+
}
|
|
332
|
+
let missing = false;
|
|
333
|
+
for (const raw of node.args) {
|
|
334
|
+
const arg = deref(raw, env);
|
|
335
|
+
if (arg instanceof Struct && !done.has(arg)) {
|
|
336
|
+
stack.push(arg);
|
|
337
|
+
missing = true;
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
if (missing) continue;
|
|
341
|
+
stack.pop();
|
|
342
|
+
const args = [];
|
|
343
|
+
let same = true;
|
|
344
|
+
for (const arg of node.args) {
|
|
345
|
+
let value = deref(arg, env);
|
|
346
|
+
if (value instanceof Struct) value = done.get(value);
|
|
347
|
+
if (value !== arg) same = false;
|
|
348
|
+
args.push(value);
|
|
349
|
+
}
|
|
350
|
+
done.set(node, same ? node : new Struct(node.name, args));
|
|
351
|
+
}
|
|
352
|
+
return done.get(root);
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
// Whether a variable stands for a _ of a program or a document: _0, _1, ...
|
|
356
|
+
// in a clause, renamed apart or not, or one a reader made.
|
|
357
|
+
export function isAnonymous(variable) {
|
|
358
|
+
const hash = variable.name.indexOf('#');
|
|
359
|
+
const base = hash < 0 ? variable.name : variable.name.slice(0, hash);
|
|
360
|
+
return base === '_' || (base.length > 1 && base[0] === '_' && /^[0-9]+$/.test(base.slice(1)));
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
// Whether every variable left in term, read through env, is anonymous.
|
|
364
|
+
export function isGroundButAnonymous(term, env) {
|
|
365
|
+
const pending = [term];
|
|
366
|
+
while (pending.length) {
|
|
367
|
+
const item = deref(pending.pop(), env);
|
|
368
|
+
if (item instanceof Var) {
|
|
369
|
+
if (!isAnonymous(item)) return false;
|
|
370
|
+
} else if (item instanceof Struct) {
|
|
371
|
+
for (const arg of item.args) pending.push(arg);
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
return true;
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
export function isGround(term, env = null) {
|
|
378
|
+
const pending = [term];
|
|
379
|
+
let seen = null;
|
|
380
|
+
while (pending.length) {
|
|
381
|
+
let item = pending.pop();
|
|
382
|
+
if (env !== null) item = deref(item, env);
|
|
383
|
+
if (item instanceof Var) return false;
|
|
384
|
+
if (item instanceof Struct) {
|
|
385
|
+
if (seen === null) seen = new Set();
|
|
386
|
+
if (seen.has(item)) continue;
|
|
387
|
+
seen.add(item);
|
|
388
|
+
for (const arg of item.args) pending.push(arg);
|
|
389
|
+
}
|
|
390
|
+
}
|
|
391
|
+
return true;
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
// A key that two terms share exactly when their canonical texts are equal,
|
|
395
|
+
// cheaper to make than the text: the term's nodes in prefix order, each with
|
|
396
|
+
// a tag. Floats are keyed by their spelling, which keeps 0.0 and -0.0 apart
|
|
397
|
+
// as their texts do.
|
|
398
|
+
export function identity(term) {
|
|
399
|
+
const out = [];
|
|
400
|
+
const pending = [term];
|
|
401
|
+
while (pending.length) {
|
|
402
|
+
const item = pending.pop();
|
|
403
|
+
if (item instanceof Struct) {
|
|
404
|
+
out.push('\u0000c', item.name, String(item.args.length));
|
|
405
|
+
for (let i = item.args.length - 1; i >= 0; i--) pending.push(item.args[i]);
|
|
406
|
+
} else if (typeof item === 'string') {
|
|
407
|
+
out.push('\u0000a', item);
|
|
408
|
+
} else if (typeof item === 'bigint') {
|
|
409
|
+
out.push('\u0000i', String(item));
|
|
410
|
+
} else if (typeof item === 'number') {
|
|
411
|
+
out.push('\u0000f', Object.is(item, -0) ? '-0' : String(item));
|
|
412
|
+
} else {
|
|
413
|
+
out.push('\u0000v', item.name);
|
|
414
|
+
}
|
|
415
|
+
}
|
|
416
|
+
return out.join('\u0001');
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
// The variables of a term by name, in order of first appearance.
|
|
420
|
+
export function variables(term, result = new Map()) {
|
|
421
|
+
const pending = [term];
|
|
422
|
+
while (pending.length) {
|
|
423
|
+
const item = pending.pop();
|
|
424
|
+
if (item instanceof Var) {
|
|
425
|
+
if (!result.has(item.name)) result.set(item.name, item);
|
|
426
|
+
} else if (item instanceof Struct) {
|
|
427
|
+
for (let i = item.args.length - 1; i >= 0; i--) pending.push(item.args[i]);
|
|
428
|
+
}
|
|
429
|
+
}
|
|
430
|
+
return result;
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
// The code points of a string, in order.
|
|
434
|
+
export function codePoints(text) {
|
|
435
|
+
return Array.from(text, (ch) => ch.codePointAt(0));
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
// Strings ordered by the code points of their characters, as the standard
|
|
439
|
+
// order requires; JavaScript's < compares UTF-16 code units instead.
|
|
440
|
+
export function compareText(a, b) {
|
|
441
|
+
if (a === b) return 0;
|
|
442
|
+
const length = Math.min(a.length, b.length);
|
|
443
|
+
for (let i = 0; i < length; i++) {
|
|
444
|
+
const x = a.charCodeAt(i);
|
|
445
|
+
const y = b.charCodeAt(i);
|
|
446
|
+
if (x === y) continue;
|
|
447
|
+
// Only surrogates order differently by code unit than by code point.
|
|
448
|
+
const xs = x >= 0xd800 && x <= 0xdfff;
|
|
449
|
+
const ys = y >= 0xd800 && y <= 0xdfff;
|
|
450
|
+
if (xs !== ys) return xs ? 1 : -1;
|
|
451
|
+
return x < y ? -1 : 1;
|
|
452
|
+
}
|
|
453
|
+
return a.length < b.length ? -1 : a.length > b.length ? 1 : 0;
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
// -1, 0 or 1: integers and floats compared exactly by value.
|
|
457
|
+
export function compareNumbers(a, b) {
|
|
458
|
+
if (typeof a === typeof b) return a < b ? -1 : a > b ? 1 : 0;
|
|
459
|
+
// One is a bigint, the other a finite float.
|
|
460
|
+
const float = typeof a === 'number' ? a : b;
|
|
461
|
+
const integer = typeof a === 'bigint' ? a : b;
|
|
462
|
+
let order;
|
|
463
|
+
if (Number.isInteger(float)) {
|
|
464
|
+
const exact = BigInt(float);
|
|
465
|
+
order = exact < integer ? -1 : exact > integer ? 1 : 0;
|
|
466
|
+
} else {
|
|
467
|
+
const floor = BigInt(Math.floor(float));
|
|
468
|
+
order = floor < integer ? -1 : 1;
|
|
469
|
+
}
|
|
470
|
+
return typeof a === 'number' ? order : -order;
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
// Standard order: variables < numbers < atoms < compound terms. Numbers are
|
|
474
|
+
// ordered by value, and a float precedes an integer of equal value. Distinct
|
|
475
|
+
// variables are ordered by name, so the order is the same in every comparison.
|
|
476
|
+
export function compareTerms(left, right) {
|
|
477
|
+
const pending = [left, right];
|
|
478
|
+
while (pending.length) {
|
|
479
|
+
const r = pending.pop();
|
|
480
|
+
const l = pending.pop();
|
|
481
|
+
const lr = kindRank(l);
|
|
482
|
+
const rr = kindRank(r);
|
|
483
|
+
if (lr !== rr) return lr < rr ? -1 : 1;
|
|
484
|
+
if (lr === 1) {
|
|
485
|
+
const order = compareNumbers(l, r);
|
|
486
|
+
if (order) return order;
|
|
487
|
+
if (typeof l !== typeof r) return typeof l === 'bigint' ? 1 : -1;
|
|
488
|
+
} else if (lr === 0) {
|
|
489
|
+
const order = compareText(l.name, r.name);
|
|
490
|
+
if (order) return order;
|
|
491
|
+
} else if (lr === 2) {
|
|
492
|
+
const order = compareText(l, r);
|
|
493
|
+
if (order) return order;
|
|
494
|
+
} else {
|
|
495
|
+
if (l.args.length !== r.args.length) return l.args.length < r.args.length ? -1 : 1;
|
|
496
|
+
const order = compareText(l.name, r.name);
|
|
497
|
+
if (order) return order;
|
|
498
|
+
for (let i = l.args.length - 1; i >= 0; i--) pending.push(l.args[i], r.args[i]);
|
|
499
|
+
}
|
|
500
|
+
}
|
|
501
|
+
return 0;
|
|
502
|
+
}
|
package/src/version.js
ADDED