@dallaylaen/ski-interpreter 2.7.0 → 2.8.1
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/CHANGELOG.md +39 -0
- package/bin/ski.js +24 -10
- package/lib/ski-interpreter.cjs.js +150 -119
- package/lib/ski-interpreter.cjs.js.map +3 -3
- package/lib/ski-interpreter.min.js +5 -5
- package/lib/ski-interpreter.min.js.map +4 -4
- package/lib/ski-interpreter.mjs +150 -119
- package/lib/ski-interpreter.mjs.map +3 -3
- package/lib/ski-quest.min.js +5 -5
- package/lib/ski-quest.min.js.map +4 -4
- package/lib/types/expr.d.ts +33 -6
- package/lib/types/extras.d.ts +41 -23
- package/lib/types/index.d.ts +3 -5
- package/lib/types/parser.d.ts +11 -13
- package/package.json +1 -1
package/lib/ski-interpreter.mjs
CHANGED
|
@@ -85,10 +85,13 @@ var control = {
|
|
|
85
85
|
var native = {};
|
|
86
86
|
var Expr = class _Expr {
|
|
87
87
|
/**
|
|
88
|
+
* Add metadata based on user-supplied values and/or the properties of the term itself.
|
|
88
89
|
*
|
|
89
|
-
*
|
|
90
|
-
*
|
|
91
|
-
*
|
|
90
|
+
* Typically applied to named terms shortly after instantiation.
|
|
91
|
+
*
|
|
92
|
+
* Experimental. Name and meaning may change in the future.
|
|
93
|
+
*
|
|
94
|
+
* @experimental
|
|
92
95
|
* @param {Object} options
|
|
93
96
|
* @param {string} [options.note] - a brief description what the term does
|
|
94
97
|
* @param {number} [options.arity] - number of arguments the term is waiting for (if known)
|
|
@@ -98,7 +101,7 @@ var Expr = class _Expr {
|
|
|
98
101
|
* @param {number} [options.maxArgs] - maximum number of arguments for inference, if canonize is true
|
|
99
102
|
* @return {this}
|
|
100
103
|
*/
|
|
101
|
-
|
|
104
|
+
annotate(options = {}) {
|
|
102
105
|
if (options.fancy !== void 0 && this instanceof Named)
|
|
103
106
|
this.fancyName = options.fancy;
|
|
104
107
|
if (options.note !== void 0)
|
|
@@ -328,11 +331,9 @@ var Expr = class _Expr {
|
|
|
328
331
|
let steps = 0;
|
|
329
332
|
let expr = this;
|
|
330
333
|
main: for (let i = 0; i < options.maxArgs; i++) {
|
|
331
|
-
const next = expr.run({ max: options.max - steps, maxSize: options.maxSize });
|
|
334
|
+
const next = expr.run({ max: Math.max((options.max - steps) / 2, 10), maxSize: options.maxSize });
|
|
332
335
|
steps += next.steps;
|
|
333
|
-
if (
|
|
334
|
-
break;
|
|
335
|
-
if (firstVar(next.expr)) {
|
|
336
|
+
if (next.final && firstVar(next.expr)) {
|
|
336
337
|
expr = next.expr;
|
|
337
338
|
if (!expr.any((e) => !(e instanceof FreeVar || e instanceof App)))
|
|
338
339
|
return maybeLambda(probe, expr, { steps });
|
|
@@ -402,7 +403,9 @@ var Expr = class _Expr {
|
|
|
402
403
|
*/
|
|
403
404
|
*toLambda(options = {}) {
|
|
404
405
|
let expr = this.traverse((e) => {
|
|
405
|
-
if (e instanceof FreeVar
|
|
406
|
+
if (e instanceof FreeVar)
|
|
407
|
+
return e;
|
|
408
|
+
if (e instanceof App || e instanceof Lambda || e instanceof Alias)
|
|
406
409
|
return null;
|
|
407
410
|
const guess = e.infer({ max: options.max, maxArgs: options.maxArgs });
|
|
408
411
|
if (!guess.normal)
|
|
@@ -768,16 +771,26 @@ var Expr = class _Expr {
|
|
|
768
771
|
diag(indent = "") {
|
|
769
772
|
return indent + this.constructor.name + ": " + this;
|
|
770
773
|
}
|
|
774
|
+
declare(options = {}) {
|
|
775
|
+
const { declaration: d = ["", "=", "; "], ...format } = options;
|
|
776
|
+
const res = toposort({ list: [this], env: format.inventory });
|
|
777
|
+
return res.list.map((s) => {
|
|
778
|
+
if (s instanceof Alias)
|
|
779
|
+
return d[0] + s.name + d[1] + s.impl.format({ ...format, inventory: res.env });
|
|
780
|
+
if (s instanceof FreeVar)
|
|
781
|
+
return d[0] + s.name + d[1];
|
|
782
|
+
return s.format({ ...format, inventory: res.env });
|
|
783
|
+
}).join(d[2]);
|
|
784
|
+
}
|
|
771
785
|
/**
|
|
772
786
|
* Convert the expression to a JSON-serializable format.
|
|
773
787
|
* Sadly the format is not yet finalized and may change in the future.
|
|
774
788
|
*
|
|
775
789
|
* @experimental
|
|
776
|
-
* @returns {string}
|
|
777
790
|
* @sealed
|
|
778
791
|
*/
|
|
779
792
|
toJSON() {
|
|
780
|
-
return this.
|
|
793
|
+
return this.declare();
|
|
781
794
|
}
|
|
782
795
|
};
|
|
783
796
|
var App = class _App extends Expr {
|
|
@@ -944,7 +957,7 @@ var Native = class extends Named {
|
|
|
944
957
|
constructor(name, impl, opt = {}) {
|
|
945
958
|
super(name);
|
|
946
959
|
this.invoke = impl;
|
|
947
|
-
this.
|
|
960
|
+
this.annotate({ canonize: true, ...opt });
|
|
948
961
|
}
|
|
949
962
|
};
|
|
950
963
|
var Lambda = class _Lambda extends Expr {
|
|
@@ -1051,7 +1064,7 @@ var Alias = class extends Named {
|
|
|
1051
1064
|
if (!(impl instanceof Expr))
|
|
1052
1065
|
throw new Error("Attempt to create an alias for a non-expression: " + impl);
|
|
1053
1066
|
this.impl = impl;
|
|
1054
|
-
this.
|
|
1067
|
+
this.annotate(options);
|
|
1055
1068
|
this.invoke = waitn(options.inline ? 0 : this.arity ?? 0)(impl);
|
|
1056
1069
|
this.size = impl.size;
|
|
1057
1070
|
if (options.inline)
|
|
@@ -1189,40 +1202,43 @@ function maybeLambda(args, expr, caps) {
|
|
|
1189
1202
|
function nthvar(n) {
|
|
1190
1203
|
return new FreeVar("abcdefgh"[n] ?? "x" + n);
|
|
1191
1204
|
}
|
|
1192
|
-
|
|
1193
|
-
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
if (
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
if (!list)
|
|
1203
|
-
return { list: [], env: {} };
|
|
1204
|
-
env = {};
|
|
1205
|
-
for (const item of list) {
|
|
1206
|
-
if (!(item instanceof Named))
|
|
1207
|
-
continue;
|
|
1208
|
-
if (env[item.name])
|
|
1209
|
-
throw new Error("duplicate name " + item);
|
|
1210
|
-
env[item.name] = item;
|
|
1205
|
+
function toposort(options) {
|
|
1206
|
+
if (typeof options !== "object" || options === null || Array.isArray(options) || options instanceof Expr)
|
|
1207
|
+
throw new Error("positional arguments to toposort are deprecated, use { list: ..., env: ... } instead");
|
|
1208
|
+
const allow = options.allow ? { ...options.allow } : null;
|
|
1209
|
+
const env = { ...options.env ?? {} };
|
|
1210
|
+
const list = options.list instanceof Expr ? [options.list] : options.list ?? [];
|
|
1211
|
+
if (allow) {
|
|
1212
|
+
for (const term of list) {
|
|
1213
|
+
if (term instanceof Named)
|
|
1214
|
+
allow[term.name] = term;
|
|
1211
1215
|
}
|
|
1212
1216
|
}
|
|
1217
|
+
for (const term of list) {
|
|
1218
|
+
if (term instanceof Named && env[term.name] === term)
|
|
1219
|
+
delete env[term.name];
|
|
1220
|
+
}
|
|
1213
1221
|
const out = [];
|
|
1214
|
-
const seen =
|
|
1222
|
+
const seen = new Set(Object.values(env));
|
|
1215
1223
|
const rec = (term) => {
|
|
1216
1224
|
if (seen.has(term))
|
|
1217
1225
|
return;
|
|
1218
|
-
term.fold(
|
|
1219
|
-
if (e
|
|
1226
|
+
term.fold(void 0, (_, e) => {
|
|
1227
|
+
if (!(e instanceof Named))
|
|
1228
|
+
return;
|
|
1229
|
+
if (allow && allow[e.name] !== e)
|
|
1230
|
+
return;
|
|
1231
|
+
if (!allow && e instanceof Alias && e.inline)
|
|
1232
|
+
return;
|
|
1233
|
+
if (e !== term) {
|
|
1220
1234
|
rec(e);
|
|
1221
|
-
return control.prune(
|
|
1235
|
+
return control.prune();
|
|
1222
1236
|
}
|
|
1223
1237
|
});
|
|
1224
1238
|
out.push(term);
|
|
1225
1239
|
seen.add(term);
|
|
1240
|
+
if (term instanceof Named)
|
|
1241
|
+
env[term.name] ||= term;
|
|
1226
1242
|
};
|
|
1227
1243
|
for (const term of list)
|
|
1228
1244
|
rec(term);
|
|
@@ -1231,6 +1247,7 @@ function toposort(list, env) {
|
|
|
1231
1247
|
env
|
|
1232
1248
|
};
|
|
1233
1249
|
}
|
|
1250
|
+
var classes = { Expr, App, Named, FreeVar, Native, Lambda, Church, Alias };
|
|
1234
1251
|
|
|
1235
1252
|
// src/parser.ts
|
|
1236
1253
|
var Empty = class extends Expr {
|
|
@@ -1333,7 +1350,7 @@ var Parser = class {
|
|
|
1333
1350
|
add(term, impl, options) {
|
|
1334
1351
|
const named = this._named(term, impl);
|
|
1335
1352
|
const opts = typeof options === "string" ? { note: options, canonize: false } : options ?? {};
|
|
1336
|
-
named.
|
|
1353
|
+
named.annotate({ canonize: this.annotate, ...opts });
|
|
1337
1354
|
const old = this.known[named.name];
|
|
1338
1355
|
if (old instanceof Alias)
|
|
1339
1356
|
old.makeInline();
|
|
@@ -1478,7 +1495,7 @@ var Parser = class {
|
|
|
1478
1495
|
env[temp.name] = temp;
|
|
1479
1496
|
delete env[name];
|
|
1480
1497
|
}
|
|
1481
|
-
const list = toposort(
|
|
1498
|
+
const list = toposort({ list: Object.values(env), allow: {} }).list;
|
|
1482
1499
|
const detour = /* @__PURE__ */ new Map();
|
|
1483
1500
|
if (Object.keys(needDetour).length) {
|
|
1484
1501
|
const rework = (expr) => {
|
|
@@ -1505,14 +1522,15 @@ var Parser = class {
|
|
|
1505
1522
|
return out;
|
|
1506
1523
|
}
|
|
1507
1524
|
/**
|
|
1508
|
-
* @template T
|
|
1509
1525
|
* @param {string} source
|
|
1510
1526
|
* @param {Object} [options]
|
|
1511
|
-
* @param
|
|
1512
|
-
* @param
|
|
1513
|
-
* @param {boolean} [options.numbers]
|
|
1514
|
-
* @param {boolean} [options.lambdas]
|
|
1515
|
-
* @param {string} [options.allow]
|
|
1527
|
+
* @param [options.env] - additional
|
|
1528
|
+
* @param [options.scope] - assign this scope to unknown free variables
|
|
1529
|
+
* @param {boolean} [options.numbers] - whether numbers are allowed
|
|
1530
|
+
* @param {boolean} [options.lambdas] - whether lambdas are allowed
|
|
1531
|
+
* @param {string} [options.allow] - restrict known terms
|
|
1532
|
+
* @param [options.canonize] - whether to calculate canonical form, arity, and properties
|
|
1533
|
+
* of intermediate aliases
|
|
1516
1534
|
* @return {Expr}
|
|
1517
1535
|
*/
|
|
1518
1536
|
parse(source, options = {}) {
|
|
@@ -1522,18 +1540,19 @@ var Parser = class {
|
|
|
1522
1540
|
const jar = { ...options.env };
|
|
1523
1541
|
let expr = new Empty();
|
|
1524
1542
|
for (const item of lines) {
|
|
1525
|
-
|
|
1526
|
-
|
|
1527
|
-
|
|
1528
|
-
|
|
1529
|
-
|
|
1543
|
+
const [_, name, def] = item.match(/^([A-Z]|[a-z][a-z_0-9]*)\s*=(.*)$/s) || [];
|
|
1544
|
+
if (name !== void 0) {
|
|
1545
|
+
if (jar[name] instanceof Alias && jar[name] !== options.env?.[name]) {
|
|
1546
|
+
jar[name].makeInline();
|
|
1547
|
+
}
|
|
1548
|
+
delete jar[name];
|
|
1549
|
+
}
|
|
1550
|
+
if (def === "")
|
|
1551
|
+
expr = new FreeVar(name, options.scope ?? FreeVar.global);
|
|
1530
1552
|
else
|
|
1531
1553
|
expr = this.parseLine(item, jar, options);
|
|
1532
|
-
if (
|
|
1533
|
-
|
|
1534
|
-
throw new Error("Attempt to redefine a known term: " + def[1]);
|
|
1535
|
-
jar[def[1]] = expr;
|
|
1536
|
-
}
|
|
1554
|
+
if (name)
|
|
1555
|
+
jar[name] = expr;
|
|
1537
1556
|
}
|
|
1538
1557
|
if (this.addContext) {
|
|
1539
1558
|
if (expr instanceof Named)
|
|
@@ -1565,7 +1584,7 @@ var Parser = class {
|
|
|
1565
1584
|
parseLine(source, env = {}, options = {}) {
|
|
1566
1585
|
const aliased = source.match(/^\s*([A-Z]|[a-z][a-z_0-9]*)\s*=\s*(.*)$/s);
|
|
1567
1586
|
if (aliased)
|
|
1568
|
-
return new Alias(aliased[1], this.parseLine(aliased[2], env, options));
|
|
1587
|
+
return new Alias(aliased[1], this.parseLine(aliased[2], env, options), { canonize: options.canonize });
|
|
1569
1588
|
const opt = {
|
|
1570
1589
|
numbers: options.numbers ?? this.hasNumbers,
|
|
1571
1590
|
lambdas: options.lambdas ?? this.hasLambdas,
|
|
@@ -2040,6 +2059,77 @@ function canonize(term, options = {}) {
|
|
|
2040
2059
|
}
|
|
2041
2060
|
|
|
2042
2061
|
// src/extras.ts
|
|
2062
|
+
var formatSchema = {
|
|
2063
|
+
html: (x) => typeof x === "boolean" ? void 0 : "must be a boolean",
|
|
2064
|
+
terse: (x) => typeof x === "boolean" ? void 0 : "must be a boolean",
|
|
2065
|
+
space: (x) => typeof x === "string" ? void 0 : "must be a string",
|
|
2066
|
+
brackets: isStringPair,
|
|
2067
|
+
var: isStringPair,
|
|
2068
|
+
around: isStringPair,
|
|
2069
|
+
redex: isStringPair,
|
|
2070
|
+
lambda: isStringTriple,
|
|
2071
|
+
inventory: (x) => {
|
|
2072
|
+
if (typeof x !== "object" || x === null || x.constructor !== Object)
|
|
2073
|
+
return "must be an object, not " + (x?.constructor?.name ?? typeof x);
|
|
2074
|
+
const refined = x;
|
|
2075
|
+
for (const key of Object.keys(refined)) {
|
|
2076
|
+
if (!(refined[key] instanceof Expr))
|
|
2077
|
+
return "key " + key + "is not an Expr";
|
|
2078
|
+
}
|
|
2079
|
+
return void 0;
|
|
2080
|
+
}
|
|
2081
|
+
};
|
|
2082
|
+
function checkFormatOptions(raw) {
|
|
2083
|
+
if (raw === null || raw === void 0)
|
|
2084
|
+
return { value: {} };
|
|
2085
|
+
if (typeof raw !== "object" || Array.isArray(raw) || raw.constructor !== Object)
|
|
2086
|
+
return { error: { object: "Format options must be an object, not " + (raw?.constructor?.name ?? typeof raw) } };
|
|
2087
|
+
const rec = raw;
|
|
2088
|
+
const error = {};
|
|
2089
|
+
for (const key in rec) {
|
|
2090
|
+
if (formatSchema[key]) {
|
|
2091
|
+
const err = formatSchema[key](rec[key]);
|
|
2092
|
+
if (err)
|
|
2093
|
+
error[key] = err;
|
|
2094
|
+
} else
|
|
2095
|
+
error[key] = "unknown option";
|
|
2096
|
+
}
|
|
2097
|
+
return Object.keys(error).length > 0 ? { error } : { value: rec };
|
|
2098
|
+
}
|
|
2099
|
+
function equiv(e1, e2, options = {}) {
|
|
2100
|
+
let steps = 0;
|
|
2101
|
+
const [n1, n2] = [e1, e2].map((x) => x.traverse((e) => {
|
|
2102
|
+
const props = e.infer(options);
|
|
2103
|
+
steps += props.steps ?? 0;
|
|
2104
|
+
return props.expr;
|
|
2105
|
+
}));
|
|
2106
|
+
const normal = !!(n1 && n2);
|
|
2107
|
+
return {
|
|
2108
|
+
steps,
|
|
2109
|
+
normal,
|
|
2110
|
+
equal: normal ? n1.equals(n2) : false,
|
|
2111
|
+
canonical: [n1, n2]
|
|
2112
|
+
};
|
|
2113
|
+
}
|
|
2114
|
+
function declare(expr, env) {
|
|
2115
|
+
return expr.declare({ inventory: env });
|
|
2116
|
+
}
|
|
2117
|
+
function deepFormat(obj, options = {}) {
|
|
2118
|
+
if (obj instanceof Expr)
|
|
2119
|
+
return obj.format(options);
|
|
2120
|
+
if (obj instanceof Quest)
|
|
2121
|
+
return "Quest(" + obj.name + ")";
|
|
2122
|
+
if (obj instanceof Quest.Case)
|
|
2123
|
+
return "Quest.Case";
|
|
2124
|
+
if (Array.isArray(obj))
|
|
2125
|
+
return obj.map((item) => deepFormat(item, options));
|
|
2126
|
+
if (typeof obj !== "object" || obj === null || obj.constructor !== Object)
|
|
2127
|
+
return obj;
|
|
2128
|
+
const out = {};
|
|
2129
|
+
for (const key in obj)
|
|
2130
|
+
out[key] = deepFormat(obj[key], options);
|
|
2131
|
+
return out;
|
|
2132
|
+
}
|
|
2043
2133
|
function search(seed, options, predicate) {
|
|
2044
2134
|
const {
|
|
2045
2135
|
depth = 16,
|
|
@@ -2103,72 +2193,13 @@ function search(seed, options, predicate) {
|
|
|
2103
2193
|
}
|
|
2104
2194
|
return { total, probed, gen: depth, ...options.retain ? { cache } : {} };
|
|
2105
2195
|
}
|
|
2106
|
-
function
|
|
2107
|
-
|
|
2108
|
-
return obj.format(options);
|
|
2109
|
-
if (obj instanceof Quest)
|
|
2110
|
-
return "Quest(" + obj.name + ")";
|
|
2111
|
-
if (obj instanceof Quest.Case)
|
|
2112
|
-
return "Quest.Case";
|
|
2113
|
-
if (Array.isArray(obj))
|
|
2114
|
-
return obj.map((item) => deepFormat(item, options));
|
|
2115
|
-
if (typeof obj !== "object" || obj === null || obj.constructor !== Object)
|
|
2116
|
-
return obj;
|
|
2117
|
-
const out = {};
|
|
2118
|
-
for (const key in obj)
|
|
2119
|
-
out[key] = deepFormat(obj[key], options);
|
|
2120
|
-
return out;
|
|
2196
|
+
function isStringPair(x) {
|
|
2197
|
+
return Array.isArray(x) && x.length === 2 && typeof x[0] === "string" && typeof x[1] === "string" ? void 0 : "must be a pair of strings";
|
|
2121
2198
|
}
|
|
2122
|
-
function
|
|
2123
|
-
|
|
2124
|
-
return res.list.map((s) => {
|
|
2125
|
-
if (s instanceof Alias)
|
|
2126
|
-
return s.name + "=" + s.impl.format({ inventory: res.env });
|
|
2127
|
-
if (s instanceof FreeVar)
|
|
2128
|
-
return s.name + "=";
|
|
2129
|
-
return s.format({ inventory: res.env });
|
|
2130
|
-
}).join("; ");
|
|
2131
|
-
}
|
|
2132
|
-
var isStringPair = (x) => Array.isArray(x) && x.length === 2 && typeof x[0] === "string" && typeof x[1] === "string" ? void 0 : "must be a pair of strings";
|
|
2133
|
-
var isStringTriple = (x) => Array.isArray(x) && x.length === 3 && typeof x[0] === "string" && typeof x[1] === "string" && typeof x[2] === "string" ? void 0 : "must be a triplet of strings";
|
|
2134
|
-
var schema = {
|
|
2135
|
-
html: (x) => typeof x === "boolean" ? void 0 : "must be a boolean",
|
|
2136
|
-
terse: (x) => typeof x === "boolean" ? void 0 : "must be a boolean",
|
|
2137
|
-
space: (x) => typeof x === "string" ? void 0 : "must be a string",
|
|
2138
|
-
brackets: isStringPair,
|
|
2139
|
-
var: isStringPair,
|
|
2140
|
-
around: isStringPair,
|
|
2141
|
-
redex: isStringPair,
|
|
2142
|
-
lambda: isStringTriple,
|
|
2143
|
-
inventory: (x) => {
|
|
2144
|
-
if (typeof x !== "object" || x === null || x.constructor !== Object)
|
|
2145
|
-
return "must be an object, not " + (x?.constructor?.name ?? typeof x);
|
|
2146
|
-
const refined = x;
|
|
2147
|
-
for (const key of Object.keys(refined)) {
|
|
2148
|
-
if (!(refined[key] instanceof Expr))
|
|
2149
|
-
return "key " + key + "is not an Expr";
|
|
2150
|
-
}
|
|
2151
|
-
return void 0;
|
|
2152
|
-
}
|
|
2153
|
-
};
|
|
2154
|
-
function checkFormatOptions(raw) {
|
|
2155
|
-
if (raw === null || raw === void 0)
|
|
2156
|
-
return { value: {} };
|
|
2157
|
-
if (typeof raw !== "object" || Array.isArray(raw) || raw.constructor !== Object)
|
|
2158
|
-
return { error: { object: "Format options must be an object, not " + (raw?.constructor?.name ?? typeof raw) } };
|
|
2159
|
-
const rec = raw;
|
|
2160
|
-
const error = {};
|
|
2161
|
-
for (const key in rec) {
|
|
2162
|
-
if (schema[key]) {
|
|
2163
|
-
const err = schema[key](rec[key]);
|
|
2164
|
-
if (err)
|
|
2165
|
-
error[key] = err;
|
|
2166
|
-
} else
|
|
2167
|
-
error[key] = "unknown option";
|
|
2168
|
-
}
|
|
2169
|
-
return Object.keys(error).length > 0 ? { error } : { value: rec };
|
|
2199
|
+
function isStringTriple(x) {
|
|
2200
|
+
return Array.isArray(x) && x.length === 3 && typeof x[0] === "string" && typeof x[1] === "string" && typeof x[2] === "string" ? void 0 : "must be a triplet of strings";
|
|
2170
2201
|
}
|
|
2171
|
-
var extras = { search, deepFormat, declare, toposort, checkFormatOptions };
|
|
2202
|
+
var extras = { search, deepFormat, declare, toposort, checkFormatOptions, equiv };
|
|
2172
2203
|
|
|
2173
2204
|
// src/index.ts
|
|
2174
2205
|
extras.toposort = toposort;
|