@dallaylaen/ski-interpreter 2.5.1 → 2.6.0
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 +22 -2
- package/lib/ski-interpreter.cjs.js +82 -44
- package/lib/ski-interpreter.cjs.js.map +2 -2
- package/lib/ski-interpreter.esm.js +82 -44
- package/lib/ski-interpreter.esm.js.map +2 -2
- package/lib/ski-interpreter.min.js +8 -8
- package/lib/ski-interpreter.min.js.map +3 -3
- package/lib/ski-quest.min.js +8 -8
- package/lib/ski-quest.min.js.map +3 -3
- package/lib/types/expr.d.ts +83 -35
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,26 @@ All notable changes to this project will be documented in this file.
|
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
|
6
6
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
7
|
|
|
8
|
+
## [2.6.0] - 2026-03-28
|
|
9
|
+
|
|
10
|
+
### BREAKING CHANGES
|
|
11
|
+
|
|
12
|
+
- `Alias`: `outdated` and `terminal` properties replaced by
|
|
13
|
+
`inline`: boolean.
|
|
14
|
+
|
|
15
|
+
### Added
|
|
16
|
+
|
|
17
|
+
- Experimental `Alias.makeInline()` that sets the inline property
|
|
18
|
+
and adjusts the invocation accordingly
|
|
19
|
+
- (don't wait for more args if inline).
|
|
20
|
+
|
|
21
|
+
## [2.5.2] - 2026-03-27
|
|
22
|
+
|
|
23
|
+
### Changed
|
|
24
|
+
|
|
25
|
+
- Make `Expr` class abstract (as was always intended)
|
|
26
|
+
and mark its abstract/final/protected methods appropriately.
|
|
27
|
+
|
|
8
28
|
## [2.5.1] - 2026-03-26
|
|
9
29
|
|
|
10
30
|
### Changed
|
|
@@ -19,7 +39,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
19
39
|
|
|
20
40
|
### BREAKING CHANGES
|
|
21
41
|
|
|
22
|
-
- `affine: true` in quests now means "has no duplicating subterms" rather than "non-duplicating as a whole."
|
|
42
|
+
- `affine: true` in quests now means "has no duplicating subterms" rather than "non-duplicating as a whole."
|
|
23
43
|
Aliases are exempted from this check, so a solution `SK`
|
|
24
44
|
will not pass as S is duplicating, but `false=SK; false`
|
|
25
45
|
will because `false` (as a whole term) _is_ affine.
|
|
@@ -74,7 +94,7 @@ will because `false` (as a whole term) _is_ affine.
|
|
|
74
94
|
### Changed
|
|
75
95
|
|
|
76
96
|
- `SKI.extras.foldr` is now removed in favor of
|
|
77
|
-
`Expr.foldBottomUp<T>(fun: (expr: Expr, args: T[]) => T): T`
|
|
97
|
+
`Expr.foldBottomUp<T>(fun: (expr: Expr, args: T[]) => T): T`
|
|
78
98
|
with the same semantics but more descriptive name
|
|
79
99
|
and simpler signature.
|
|
80
100
|
_Was experimental (and still is), so not considered a breaking change._
|
|
@@ -13889,17 +13889,11 @@ var FormatOptionsSchema = external_exports.object({
|
|
|
13889
13889
|
inventory: external_exports.record(external_exports.string(), external_exports.custom((v) => v instanceof Expr)).optional()
|
|
13890
13890
|
});
|
|
13891
13891
|
var Expr = class _Expr {
|
|
13892
|
-
static {
|
|
13893
|
-
this.control = control;
|
|
13894
|
-
}
|
|
13895
|
-
static {
|
|
13896
|
-
this.native = native;
|
|
13897
|
-
}
|
|
13898
13892
|
/**
|
|
13899
13893
|
*
|
|
13900
13894
|
* @desc Define properties of the term based on user supplied options and/or inference results.
|
|
13901
13895
|
* Typically useful for declaring Native and Alias terms.
|
|
13902
|
-
* @
|
|
13896
|
+
* @protected
|
|
13903
13897
|
* @param {Object} options
|
|
13904
13898
|
* @param {string} [options.note] - a brief description what the term does
|
|
13905
13899
|
* @param {number} [options.arity] - number of arguments the term is waiting for (if known)
|
|
@@ -13974,6 +13968,7 @@ var Expr = class _Expr {
|
|
|
13974
13968
|
* }} [options]
|
|
13975
13969
|
* @param {(e:Expr) => TraverseValue<Expr>} change
|
|
13976
13970
|
* @returns {Expr|null}
|
|
13971
|
+
* @final
|
|
13977
13972
|
*/
|
|
13978
13973
|
traverse(options, change) {
|
|
13979
13974
|
if (typeof options === "function") {
|
|
@@ -13987,7 +13982,8 @@ var Expr = class _Expr {
|
|
|
13987
13982
|
return expr ?? null;
|
|
13988
13983
|
}
|
|
13989
13984
|
/**
|
|
13990
|
-
* @
|
|
13985
|
+
* @protected
|
|
13986
|
+
* @final
|
|
13991
13987
|
* @param {Object} options
|
|
13992
13988
|
* @param {(e:Expr) => TraverseValue<Expr>} change
|
|
13993
13989
|
* @returns {TraverseValue<Expr>}
|
|
@@ -14006,7 +14002,7 @@ var Expr = class _Expr {
|
|
|
14006
14002
|
return action ? action(expr) : expr;
|
|
14007
14003
|
}
|
|
14008
14004
|
/**
|
|
14009
|
-
* @
|
|
14005
|
+
* @protected
|
|
14010
14006
|
* @param {Object} options
|
|
14011
14007
|
* @param {(e:Expr) => TraverseValue<Expr>} change
|
|
14012
14008
|
* @returns {TraverseValue<Expr>}
|
|
@@ -14036,7 +14032,11 @@ var Expr = class _Expr {
|
|
|
14036
14032
|
*
|
|
14037
14033
|
* This method is experimental and may change in the future.
|
|
14038
14034
|
*
|
|
14035
|
+
* @example // count the number of nodes in the expression tree
|
|
14036
|
+
* expr.fold(0, (acc, e) => acc + 1);
|
|
14037
|
+
*
|
|
14039
14038
|
* @experimental
|
|
14039
|
+
* @final
|
|
14040
14040
|
* @template T
|
|
14041
14041
|
* @param {T} initial
|
|
14042
14042
|
* @param {(acc: T, expr: Expr) => TraverseValue<T>} combine
|
|
@@ -14046,6 +14046,14 @@ var Expr = class _Expr {
|
|
|
14046
14046
|
const [value] = unwrap(this._fold(initial, combine));
|
|
14047
14047
|
return value ?? initial;
|
|
14048
14048
|
}
|
|
14049
|
+
/**
|
|
14050
|
+
* @desc Internal method for fold(), which performs the actual folding.
|
|
14051
|
+
* Should be implemented in subclasses having any internal structure.
|
|
14052
|
+
*
|
|
14053
|
+
* @protected
|
|
14054
|
+
* @param initial
|
|
14055
|
+
* @param combine
|
|
14056
|
+
*/
|
|
14049
14057
|
_fold(initial, combine) {
|
|
14050
14058
|
return combine(initial, this);
|
|
14051
14059
|
}
|
|
@@ -14089,6 +14097,7 @@ var Expr = class _Expr {
|
|
|
14089
14097
|
*
|
|
14090
14098
|
* Use toLambda() if you want to get a lambda term in any case.
|
|
14091
14099
|
*
|
|
14100
|
+
* @final
|
|
14092
14101
|
* @param {{max?: number, maxArgs?: number}} options
|
|
14093
14102
|
* @return {TermInfo}
|
|
14094
14103
|
*/
|
|
@@ -14181,6 +14190,7 @@ var Expr = class _Expr {
|
|
|
14181
14190
|
*
|
|
14182
14191
|
* See also Expr.walk() and Expr.toSKI().
|
|
14183
14192
|
*
|
|
14193
|
+
* @final
|
|
14184
14194
|
* @param {{
|
|
14185
14195
|
* max?: number,
|
|
14186
14196
|
* maxArgs?: number,
|
|
@@ -14227,6 +14237,7 @@ var Expr = class _Expr {
|
|
|
14227
14237
|
*
|
|
14228
14238
|
* See also Expr.walk() and Expr.toLambda().
|
|
14229
14239
|
*
|
|
14240
|
+
* @final
|
|
14230
14241
|
* @param {{max?: number}} [options]
|
|
14231
14242
|
* @return {IterableIterator<{final: boolean, expr: Expr, steps: number}>}
|
|
14232
14243
|
*/
|
|
@@ -14257,12 +14268,15 @@ var Expr = class _Expr {
|
|
|
14257
14268
|
}
|
|
14258
14269
|
}
|
|
14259
14270
|
/**
|
|
14260
|
-
* Replace all instances of
|
|
14271
|
+
* Replace all instances of `search` in the expression with `replace` and return the resulting expression,
|
|
14261
14272
|
* or null if no changes could be made.
|
|
14273
|
+
*
|
|
14262
14274
|
* Lambda terms and applications will never match if used as plug
|
|
14263
|
-
* as they are impossible
|
|
14275
|
+
* as they are impossible to compare without extensive computations.
|
|
14276
|
+
*
|
|
14264
14277
|
* Typically used on variables but can also be applied to other terms, e.g. aliases.
|
|
14265
|
-
* See also Expr.traverse().
|
|
14278
|
+
* See also Expr.traverse() for more flexible replacement of subterms.
|
|
14279
|
+
*
|
|
14266
14280
|
* @param {Expr} search
|
|
14267
14281
|
* @param {Expr} replace
|
|
14268
14282
|
* @return {Expr|null}
|
|
@@ -14303,6 +14317,7 @@ var Expr = class _Expr {
|
|
|
14303
14317
|
* @desc Run uninterrupted sequence of step() applications
|
|
14304
14318
|
* until the expression is irreducible, or max number of steps is reached.
|
|
14305
14319
|
* Default number of steps = 1000.
|
|
14320
|
+
* @final
|
|
14306
14321
|
* @param {{max?: number, steps?: number, throw?: boolean}|Expr} [opt]
|
|
14307
14322
|
* @param {Expr} args
|
|
14308
14323
|
* @return {{expr: Expr, steps: number, final: boolean}}
|
|
@@ -14333,7 +14348,9 @@ var Expr = class _Expr {
|
|
|
14333
14348
|
}
|
|
14334
14349
|
/**
|
|
14335
14350
|
* Execute step() while possible, yielding a brief description of events after each step.
|
|
14351
|
+
*
|
|
14336
14352
|
* Mnemonics: like run() but slower.
|
|
14353
|
+
* @final
|
|
14337
14354
|
* @param {{max?: number}} options
|
|
14338
14355
|
* @return {IterableIterator<{final: boolean, expr: Expr, steps: number}>}
|
|
14339
14356
|
*/
|
|
@@ -14363,6 +14380,7 @@ var Expr = class _Expr {
|
|
|
14363
14380
|
*
|
|
14364
14381
|
* @param {Expr} other
|
|
14365
14382
|
* @return {boolean}
|
|
14383
|
+
* @final
|
|
14366
14384
|
*/
|
|
14367
14385
|
equals(other) {
|
|
14368
14386
|
return !this.diff(other);
|
|
@@ -14381,6 +14399,8 @@ var Expr = class _Expr {
|
|
|
14381
14399
|
* To somewhat alleviate confusion, the output will include
|
|
14382
14400
|
* the internal id of the variable in square brackets.
|
|
14383
14401
|
*
|
|
14402
|
+
* Do not rely on the exact format of the output as it may change in the future.
|
|
14403
|
+
*
|
|
14384
14404
|
* @example "K(S != I)" is the result of comparing "KS" and "KI"
|
|
14385
14405
|
* @example "S(K([x[13] != x[14]]))K"
|
|
14386
14406
|
*
|
|
@@ -14401,6 +14421,11 @@ var Expr = class _Expr {
|
|
|
14401
14421
|
* `this` is the expected value and the argument is the actual one.
|
|
14402
14422
|
* Mnemonic: the expected value is always a combinator, the actual one may be anything.
|
|
14403
14423
|
*
|
|
14424
|
+
* In case of failure, an error is thrown with a message describing the first point of difference
|
|
14425
|
+
* and `expected` and `actual` properties like in AssertionError.
|
|
14426
|
+
* AssertionError is not used directly to because browsers don't recognize it.
|
|
14427
|
+
*
|
|
14428
|
+
* @final
|
|
14404
14429
|
* @param {Expr} actual
|
|
14405
14430
|
* @param {string} comment
|
|
14406
14431
|
*/
|
|
@@ -14420,7 +14445,10 @@ var Expr = class _Expr {
|
|
|
14420
14445
|
/**
|
|
14421
14446
|
* @desc Returns string representation of the expression.
|
|
14422
14447
|
* Same as format() without options.
|
|
14448
|
+
*
|
|
14449
|
+
* Use formatImpl() to override in subclasses.
|
|
14423
14450
|
* @return {string}
|
|
14451
|
+
* @final
|
|
14424
14452
|
*/
|
|
14425
14453
|
toString() {
|
|
14426
14454
|
return this.format();
|
|
@@ -14429,6 +14457,7 @@ var Expr = class _Expr {
|
|
|
14429
14457
|
* @desc Whether the expression needs parentheses when printed.
|
|
14430
14458
|
* @param {boolean} [first] - whether this is the first term in a sequence
|
|
14431
14459
|
* @return {boolean}
|
|
14460
|
+
* @protected
|
|
14432
14461
|
*/
|
|
14433
14462
|
_braced(_first) {
|
|
14434
14463
|
return false;
|
|
@@ -14437,7 +14466,7 @@ var Expr = class _Expr {
|
|
|
14437
14466
|
* @desc Whether the expression can be printed without a space when followed by arg.
|
|
14438
14467
|
* @param {Expr} arg
|
|
14439
14468
|
* @returns {boolean}
|
|
14440
|
-
* @
|
|
14469
|
+
* @protected
|
|
14441
14470
|
*/
|
|
14442
14471
|
_unspaced(arg) {
|
|
14443
14472
|
return this._braced(true);
|
|
@@ -14445,8 +14474,9 @@ var Expr = class _Expr {
|
|
|
14445
14474
|
/**
|
|
14446
14475
|
* @desc Stringify the expression with fancy formatting options.
|
|
14447
14476
|
* Said options mostly include wrappers around various constructs in form of ['(', ')'],
|
|
14448
|
-
* as well as terse and html flags that
|
|
14477
|
+
* as well as `terse` and `html` flags that fill in appropriate defaults.
|
|
14449
14478
|
* Format without options is equivalent to toString() and can be parsed back.
|
|
14479
|
+
* @final
|
|
14450
14480
|
*
|
|
14451
14481
|
* @param {Object} [options] - formatting options
|
|
14452
14482
|
* @param {boolean} [options.terse] - whether to use terse formatting (omitting unnecessary spaces and parentheses)
|
|
@@ -14504,16 +14534,6 @@ var Expr = class _Expr {
|
|
|
14504
14534
|
html: options.html ?? false
|
|
14505
14535
|
}, 0);
|
|
14506
14536
|
}
|
|
14507
|
-
/**
|
|
14508
|
-
* @desc Internal method for format(), which performs the actual formatting.
|
|
14509
|
-
* @param {Object} options
|
|
14510
|
-
* @param {number} nargs
|
|
14511
|
-
* @returns {string}
|
|
14512
|
-
* @private
|
|
14513
|
-
*/
|
|
14514
|
-
formatImpl(options, nargs) {
|
|
14515
|
-
throw new Error("No formatImpl() method defined in class " + this.constructor.name);
|
|
14516
|
-
}
|
|
14517
14537
|
/**
|
|
14518
14538
|
* @desc Returns a string representation of the expression tree, with indentation to show structure.
|
|
14519
14539
|
*
|
|
@@ -14541,6 +14561,7 @@ var Expr = class _Expr {
|
|
|
14541
14561
|
/**
|
|
14542
14562
|
* @desc Convert the expression to a JSON-serializable format.
|
|
14543
14563
|
* @returns {string}
|
|
14564
|
+
* @final
|
|
14544
14565
|
*/
|
|
14545
14566
|
toJSON() {
|
|
14546
14567
|
return this.format();
|
|
@@ -14809,8 +14830,8 @@ var Church = class _Church extends Expr {
|
|
|
14809
14830
|
return nargs >= 2 ? options.redex[0] + this.n + options.redex[1] : this.n + "";
|
|
14810
14831
|
}
|
|
14811
14832
|
};
|
|
14812
|
-
function waitn(
|
|
14813
|
-
return
|
|
14833
|
+
function waitn(n) {
|
|
14834
|
+
return n <= 1 ? (e) => (arg) => e.apply(arg) : (e) => (arg) => waitn(n - 1)(e.apply(arg));
|
|
14814
14835
|
}
|
|
14815
14836
|
var Alias = class extends Named {
|
|
14816
14837
|
constructor(name, impl, options = {}) {
|
|
@@ -14819,17 +14840,28 @@ var Alias = class extends Named {
|
|
|
14819
14840
|
throw new Error("Attempt to create an alias for a non-expression: " + impl);
|
|
14820
14841
|
this.impl = impl;
|
|
14821
14842
|
this._setup(options);
|
|
14822
|
-
this.
|
|
14823
|
-
this.invoke = waitn(impl, this.arity ?? 0);
|
|
14843
|
+
this.invoke = waitn(options.inline ? 0 : this.arity ?? 0)(impl);
|
|
14824
14844
|
this.size = impl.size;
|
|
14825
|
-
if (options.
|
|
14826
|
-
this.
|
|
14845
|
+
if (options.inline)
|
|
14846
|
+
this.inline = true;
|
|
14847
|
+
}
|
|
14848
|
+
/**
|
|
14849
|
+
* @desc Make the alias inline, i.e. replace it with its implementation everywhere.
|
|
14850
|
+
*
|
|
14851
|
+
* Replaces the old `outdated` attribute.
|
|
14852
|
+
* Used by the parser when a term definition is removed or updated.
|
|
14853
|
+
*
|
|
14854
|
+
* May change in future versions, use with caution.
|
|
14855
|
+
*
|
|
14856
|
+
* @experimental
|
|
14857
|
+
* @returns {this}
|
|
14858
|
+
*/
|
|
14859
|
+
makeInline() {
|
|
14860
|
+
this.invoke = waitn(0)(this.impl);
|
|
14861
|
+
this.inline = true;
|
|
14862
|
+
return this;
|
|
14827
14863
|
}
|
|
14828
14864
|
/**
|
|
14829
|
-
* @property {boolean} [outdated] - whether the alias is outdated
|
|
14830
|
-
* and should be replaced with its definition when encountered.
|
|
14831
|
-
* @property {boolean} [terminal] - whether the alias should behave like a standalone term
|
|
14832
|
-
* // TODO better name?
|
|
14833
14865
|
* @property {boolean} [proper] - whether the alias is a proper combinator (i.e. contains no free variables or constants)
|
|
14834
14866
|
* @property {number} [arity] - the number of arguments the alias waits for before expanding
|
|
14835
14867
|
* @property {Expr} [canonical] - equivalent lambda term.
|
|
@@ -14872,11 +14904,11 @@ var Alias = class extends Named {
|
|
|
14872
14904
|
return other.diff(this.impl, !swap);
|
|
14873
14905
|
}
|
|
14874
14906
|
_braced(first) {
|
|
14875
|
-
return this.
|
|
14907
|
+
return this.inline ? this.impl._braced(first) : false;
|
|
14876
14908
|
}
|
|
14877
14909
|
formatImpl(options, nargs) {
|
|
14878
|
-
const
|
|
14879
|
-
return
|
|
14910
|
+
const inline = options.inventory ? options.inventory[this.name] !== this : this.inline;
|
|
14911
|
+
return inline ? this.impl.formatImpl(options, nargs) : super.formatImpl(options, nargs);
|
|
14880
14912
|
}
|
|
14881
14913
|
diag(indent = "") {
|
|
14882
14914
|
return `${indent}Alias (${this.name}): \\
|
|
@@ -14994,6 +15026,9 @@ var Empty = class extends Expr {
|
|
|
14994
15026
|
postParse() {
|
|
14995
15027
|
throw new Error("Attempt to use empty expression () as a term");
|
|
14996
15028
|
}
|
|
15029
|
+
formatImpl(options, nargs) {
|
|
15030
|
+
return "()";
|
|
15031
|
+
}
|
|
14997
15032
|
};
|
|
14998
15033
|
var PartialLambda = class _PartialLambda extends Empty {
|
|
14999
15034
|
// TODO mutable! rewrite ro when have time
|
|
@@ -15085,8 +15120,9 @@ var Parser = class {
|
|
|
15085
15120
|
const named = this._named(term, impl);
|
|
15086
15121
|
const opts = typeof options === "string" ? { note: options, canonize: false } : options ?? {};
|
|
15087
15122
|
named._setup({ canonize: this.annotate, ...opts });
|
|
15088
|
-
|
|
15089
|
-
|
|
15123
|
+
const old = this.known[named.name];
|
|
15124
|
+
if (old instanceof Alias)
|
|
15125
|
+
old.makeInline();
|
|
15090
15126
|
this.known[named.name] = named;
|
|
15091
15127
|
this.allow.add(named.name);
|
|
15092
15128
|
return this;
|
|
@@ -15186,7 +15222,9 @@ var Parser = class {
|
|
|
15186
15222
|
* @return {SKI}
|
|
15187
15223
|
*/
|
|
15188
15224
|
remove(name) {
|
|
15189
|
-
this.known[name]
|
|
15225
|
+
const old = this.known[name];
|
|
15226
|
+
if (old instanceof Alias)
|
|
15227
|
+
old.makeInline();
|
|
15190
15228
|
delete this.known[name];
|
|
15191
15229
|
this.allow.delete(name);
|
|
15192
15230
|
return this;
|
|
@@ -15271,7 +15309,7 @@ var Parser = class {
|
|
|
15271
15309
|
let expr = new Empty();
|
|
15272
15310
|
for (const item of lines) {
|
|
15273
15311
|
if (expr instanceof Alias)
|
|
15274
|
-
expr.
|
|
15312
|
+
expr.makeInline();
|
|
15275
15313
|
const def = item.match(/^([A-Z]|[a-z][a-z_0-9]*)\s*=(.*)$/s);
|
|
15276
15314
|
if (def && def[2] === "")
|
|
15277
15315
|
expr = new FreeVar(def[1], options.scope ?? FreeVar.global);
|
|
@@ -15285,7 +15323,7 @@ var Parser = class {
|
|
|
15285
15323
|
}
|
|
15286
15324
|
if (this.addContext) {
|
|
15287
15325
|
if (expr instanceof Named)
|
|
15288
|
-
expr = new Alias(expr.name, expr, {
|
|
15326
|
+
expr = new Alias(expr.name, expr, { inline: true });
|
|
15289
15327
|
expr.context = {
|
|
15290
15328
|
env: { ...this.getTerms(), ...jar },
|
|
15291
15329
|
// also contains pre-parsed terms
|
|
@@ -15393,7 +15431,7 @@ var Quest = class {
|
|
|
15393
15431
|
for (const term of env ?? []) {
|
|
15394
15432
|
const expr = this.engineFull.parse(term, { env: jar, scope: this });
|
|
15395
15433
|
if (expr instanceof Alias)
|
|
15396
|
-
this.env[expr.name] = new Alias(expr.name, expr.impl, {
|
|
15434
|
+
this.env[expr.name] = new Alias(expr.name, expr.impl, { canonize: false });
|
|
15397
15435
|
else if (expr instanceof FreeVar)
|
|
15398
15436
|
this.env[expr.name] = expr;
|
|
15399
15437
|
else
|
|
@@ -15482,7 +15520,7 @@ var Quest = class {
|
|
|
15482
15520
|
if (e instanceof Named && arsenal[e.name] === e)
|
|
15483
15521
|
return control.prune(a + 1);
|
|
15484
15522
|
});
|
|
15485
|
-
const expr = impl instanceof FreeVar ? impl : new Alias(spec.fancy ?? spec.name, impl, {
|
|
15523
|
+
const expr = impl instanceof FreeVar ? impl : new Alias(spec.fancy ?? spec.name, impl, { canonize: false });
|
|
15486
15524
|
jar[spec.name] = expr;
|
|
15487
15525
|
prepared.push(expr);
|
|
15488
15526
|
}
|