@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.
@@ -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
- * Define properties of the term based on user supplied options and/or inference results.
90
- * Typically useful for declaring Native and Alias terms.
91
- * @protected
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
- _setup(options = {}) {
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 (!next.final)
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 || e instanceof App || e instanceof Lambda || e instanceof Alias)
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.format();
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._setup({ canonize: true, ...opt });
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._setup(options);
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
- var classes = { Expr, App, Named, FreeVar, Native, Lambda, Church, Alias };
1193
-
1194
- // src/toposort.ts
1195
- function toposort(list, env) {
1196
- if (list instanceof Expr)
1197
- list = [list];
1198
- if (env) {
1199
- if (!list)
1200
- list = Object.keys(env).sort().map((k) => env[k]);
1201
- } else {
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 = /* @__PURE__ */ new Set();
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(false, (acc, e) => {
1219
- if (e !== term && e instanceof Named && env[e.name] === 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(false);
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._setup({ canonize: this.annotate, ...opts });
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(void 0, env).list;
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 {{[keys: string]: Expr}} [options.env]
1512
- * @param {T} [options.scope]
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
- if (expr instanceof Alias)
1526
- expr.makeInline();
1527
- const def = item.match(/^([A-Z]|[a-z][a-z_0-9]*)\s*=(.*)$/s);
1528
- if (def && def[2] === "")
1529
- expr = new FreeVar(def[1], options.scope ?? FreeVar.global);
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 (def) {
1533
- if (jar[def[1]] !== void 0)
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 deepFormat(obj, options = {}) {
2107
- if (obj instanceof Expr)
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 declare(expr, env = {}) {
2123
- const res = toposort([expr], env);
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;