kei-lisp 2.1.0 → 2.2.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/dist/cli.cjs CHANGED
@@ -27,7 +27,7 @@ node_v8 = __toESM(node_v8, 1);
27
27
  let node_vm = require("node:vm");
28
28
  node_vm = __toESM(node_vm, 1);
29
29
  //#region package.json
30
- var version = "2.1.0";
30
+ var version = "2.2.0";
31
31
  //#endregion
32
32
  //#region src/runtime/Table/index.ts
33
33
  /**
@@ -37,10 +37,17 @@ var version = "2.1.0";
37
37
  * @this {Table}
38
38
  */
39
39
  var Table = class Table extends Map {
40
+ /**
41
+ * The enclosing (parent) environment, or null when this is the root.
42
+ */
40
43
  source;
44
+ /**
45
+ * Whether this environment is the root of its chain.
46
+ */
41
47
  root;
42
48
  /**
43
49
  * Constructor.
50
+ * @constructor
44
51
  * @param aTable the environment in which this environment was created
45
52
  */
46
53
  constructor(aTable = null) {
@@ -50,6 +57,7 @@ var Table = class Table extends Map {
50
57
  }
51
58
  /**
52
59
  * Clones this Table and returns the clone.
60
+ * @return the cloned Table
53
61
  */
54
62
  clone() {
55
63
  const aTable = new Table(this);
@@ -62,6 +70,8 @@ var Table = class Table extends Map {
62
70
  }
63
71
  /**
64
72
  * Returns whether anything is bound to the given property (key).
73
+ * @param aSymbol the symbol to look up
74
+ * @return true if a binding exists in this scope or any enclosing scope
65
75
  */
66
76
  has(aSymbol) {
67
77
  if (super.has(aSymbol)) return true;
@@ -70,12 +80,16 @@ var Table = class Table extends Map {
70
80
  }
71
81
  /**
72
82
  * Returns whether this instance equals the given object.
83
+ * @param anObject the object to compare against
84
+ * @return true when the underlying Map.equals would return true
73
85
  */
74
86
  equals(anObject) {
75
87
  return Map.prototype.equals(anObject);
76
88
  }
77
89
  /**
78
- * Returns the value bound to the given interpreted symbol.
90
+ * Returns the value bound to the given interpreted symbol, walking up the scope chain.
91
+ * @param aSymbol the symbol to look up
92
+ * @return the bound value, or null when no binding exists
79
93
  */
80
94
  get(aSymbol) {
81
95
  if (super.has(aSymbol)) return super.get(aSymbol);
@@ -84,13 +98,16 @@ var Table = class Table extends Map {
84
98
  }
85
99
  /**
86
100
  * Returns whether this instance is the root of the environment chain.
101
+ * @return true if this is the root environment
87
102
  */
88
103
  isRoot() {
89
104
  return this.root;
90
105
  }
91
106
  /**
92
- * Reassigns the symbol bound in the innermost scope (equivalent to Common Lisp's setq).
93
- * If a binding exists in the current scope, update it and return; otherwise recurse into the parent scope.
107
+ * Reassigns the symbol bound in the innermost scope (equivalent to Common Lisp's setq). If a binding exists in the current scope, update it and return; otherwise recurse into the parent scope.
108
+ * @param aSymbol the symbol to update
109
+ * @param anObject the new bound value
110
+ * @return the new bound value, or null when no enclosing scope has a binding
94
111
  */
95
112
  setIfExist(aSymbol, anObject) {
96
113
  if (super.has(aSymbol)) {
@@ -102,6 +119,8 @@ var Table = class Table extends Map {
102
119
  }
103
120
  /**
104
121
  * Sets whether this instance is the root of its environment chain.
122
+ * @param aBoolean the new root flag
123
+ * @return null
105
124
  */
106
125
  setRoot(aBoolean) {
107
126
  this.root = aBoolean;
@@ -109,6 +128,8 @@ var Table = class Table extends Map {
109
128
  }
110
129
  /**
111
130
  * Sets the parent environment.
131
+ * @param aTable the new parent environment (or null)
132
+ * @return null
112
133
  */
113
134
  setSource(aTable) {
114
135
  this.source = aTable;
@@ -116,6 +137,7 @@ var Table = class Table extends Map {
116
137
  }
117
138
  /**
118
139
  * Returns a formatted string representation of this instance.
140
+ * @return the formatted string
119
141
  */
120
142
  toString() {
121
143
  return "#<Environment>";
@@ -129,21 +151,30 @@ var Table = class Table extends Map {
129
151
  * @author Keisuke Ikeda
130
152
  * @this {InterpretedSymbol}
131
153
  */
132
- var InterpretedSymbol = class InterpretedSymbol {
154
+ var InterpretedSymbol = class InterpretedSymbol extends Object {
133
155
  /**
134
156
  * Table that stores InterpretedSymbol instances (lazily initialized to avoid a circular dependency).
135
157
  */
136
158
  static #intern = null;
159
+ /**
160
+ * Lazy accessor for the intern table that holds every InterpretedSymbol instance.
161
+ * @return the intern Table (created on first access)
162
+ */
137
163
  static get table() {
138
164
  this.#intern ??= new Table();
139
165
  return this.#intern;
140
166
  }
167
+ /**
168
+ * The printed name of this symbol.
169
+ */
141
170
  name;
142
171
  /**
143
172
  * Constructor.
173
+ * @constructor
144
174
  * @param name printed name
145
175
  */
146
176
  constructor(name = "null") {
177
+ super();
147
178
  this.name = name;
148
179
  }
149
180
  /**
@@ -160,6 +191,8 @@ var InterpretedSymbol = class InterpretedSymbol {
160
191
  }
161
192
  /**
162
193
  * Returns whether this symbol equals the given object.
194
+ * @param anObject the object to compare against
195
+ * @return true when identity-equal (since intern guarantees uniqueness)
163
196
  */
164
197
  equals(anObject) {
165
198
  return this === anObject;
@@ -167,6 +200,7 @@ var InterpretedSymbol = class InterpretedSymbol {
167
200
  /**
168
201
  * Returns the same interpreted symbol for a given printed name.
169
202
  * @param aString printed name
203
+ * @return the canonical InterpretedSymbol for that name
170
204
  */
171
205
  static of(aString) {
172
206
  let aSymbol = this.table.get(aString);
@@ -178,6 +212,7 @@ var InterpretedSymbol = class InterpretedSymbol {
178
212
  }
179
213
  /**
180
214
  * Returns the string representation of this symbol.
215
+ * @return the printed name
181
216
  */
182
217
  toString() {
183
218
  return this.name;
@@ -191,33 +226,47 @@ var InterpretedSymbol = class InterpretedSymbol {
191
226
  * @author Keisuke Ikeda
192
227
  * @this {Loop}
193
228
  */
194
- var Loop = class {
229
+ var Loop = class extends Object {
230
+ /**
231
+ * The Cons being iterated over.
232
+ */
195
233
  aCons;
234
+ /**
235
+ * The number of elements in the underlying Cons (computed once at construction time).
236
+ */
196
237
  length;
238
+ /**
239
+ * The 1-based index of the next element to return.
240
+ */
197
241
  index;
198
242
  /**
199
243
  * Constructor.
244
+ * @constructor
200
245
  * @param aCons the Cons to iterate over
201
246
  */
202
247
  constructor(aCons) {
248
+ super();
203
249
  this.aCons = aCons;
204
250
  this.length = aCons.length();
205
251
  this.index = 1;
206
252
  }
207
253
  /**
208
- * Returns this instance.
254
+ * Returns this instance so it can be used as its own iterator.
255
+ * @return this Loop instance
209
256
  */
210
257
  iterator() {
211
258
  return this;
212
259
  }
213
260
  /**
214
261
  * Returns whether a next element exists.
262
+ * @return true if there is at least one more element
215
263
  */
216
264
  hasNext() {
217
265
  return this.index <= this.length;
218
266
  }
219
267
  /**
220
- * Returns the next element.
268
+ * Returns the next element and advances the cursor.
269
+ * @return the element at the current index
221
270
  */
222
271
  next() {
223
272
  const anObject = this.aCons.nth(this.index);
@@ -225,8 +274,8 @@ var Loop = class {
225
274
  return anObject;
226
275
  }
227
276
  /**
228
- * Implementation of the iterable protocol.
229
- * Enables iteration with for...of and similar constructs.
277
+ * Implementation of the iterable protocol. Enables iteration with for...of and similar constructs.
278
+ * @return an iterator over the Cons elements
230
279
  */
231
280
  [Symbol.iterator]() {
232
281
  return { next: () => {
@@ -242,8 +291,8 @@ var Loop = class {
242
291
  } };
243
292
  }
244
293
  /**
245
- * Implementation of the async iterable protocol.
246
- * Enables iteration with for await...of and similar constructs.
294
+ * Implementation of the async iterable protocol. Enables iteration with for await...of and similar constructs.
295
+ * @return an async iterator over the Cons elements
247
296
  */
248
297
  [Symbol.asyncIterator]() {
249
298
  return { next: () => {
@@ -259,7 +308,8 @@ var Loop = class {
259
308
  } };
260
309
  }
261
310
  /**
262
- * Advances to the next element.
311
+ * Advances the cursor to the next element.
312
+ * @return null
263
313
  */
264
314
  remove() {
265
315
  this.index++;
@@ -274,9 +324,12 @@ var Loop = class {
274
324
  * @author Keisuke Ikeda
275
325
  * @this {IntStream}
276
326
  */
277
- var IntStream = class {
327
+ var IntStream = class extends Object {
278
328
  /**
279
329
  * Builds and returns an array of consecutive integers from start to afterEnd (exclusive).
330
+ * @param start the first integer (inclusive)
331
+ * @param afterEnd the integer one past the last value to include
332
+ * @return the array of integers in [start, afterEnd)
280
333
  */
281
334
  static range(start, afterEnd) {
282
335
  const end = afterEnd - 1;
@@ -284,6 +337,9 @@ var IntStream = class {
284
337
  }
285
338
  /**
286
339
  * Builds and returns an array of consecutive integers from start to end (inclusive).
340
+ * @param start the first integer (inclusive)
341
+ * @param end the last integer (inclusive)
342
+ * @return the array of integers in [start, end]
287
343
  */
288
344
  static rangeClosed(start, end) {
289
345
  const range = end - start + 1;
@@ -336,21 +392,39 @@ var ParseError = class extends KeiLispError {
336
392
  * @author Keisuke Ikeda
337
393
  * @this {NextState}
338
394
  */
339
- var NextState = class {
395
+ var NextState = class extends Object {
396
+ /**
397
+ * The parser whose method will be invoked. Set on each call to `next`.
398
+ */
340
399
  automaton = null;
400
+ /**
401
+ * The fallback state number returned when no method is configured (or as the initial value).
402
+ */
341
403
  nextState;
404
+ /**
405
+ * Cached reference to the resolved method (kept as `unknown` because lookup happens by name).
406
+ */
342
407
  method;
408
+ /**
409
+ * The name of the parser method to invoke, or null if only `nextState` should be returned.
410
+ */
343
411
  methodName;
344
412
  /**
345
413
  * Constructor.
414
+ * @constructor
415
+ * @param aNumber the fallback state number (or null)
416
+ * @param aString the parser method name to invoke (or null)
346
417
  */
347
418
  constructor(aNumber, aString) {
419
+ super();
348
420
  this.nextState = aNumber;
349
421
  this.method = null;
350
422
  this.methodName = aString;
351
423
  }
352
424
  /**
353
425
  * Invokes the method corresponding to the input character and returns the resulting token number.
426
+ * @param anAutomaton the parser to invoke the method on
427
+ * @return the next state number
354
428
  */
355
429
  next(anAutomaton) {
356
430
  this.automaton = anAutomaton;
@@ -382,18 +456,38 @@ const SYNTAX_ERROR = "Syntax Error!";
382
456
  * @author Keisuke Ikeda
383
457
  * @this {Parser}
384
458
  */
385
- var Parser = class Parser {
459
+ var Parser = class Parser extends Object {
460
+ /**
461
+ * Iterator over the input source characters.
462
+ */
386
463
  stream;
464
+ /**
465
+ * The most recently produced parse token (a value or sub-Cons).
466
+ */
387
467
  token;
468
+ /**
469
+ * The accumulator for the current literal being read (number, symbol, string).
470
+ */
388
471
  tokenString;
472
+ /**
473
+ * The state transition table: current state -> (input code point string -> NextState).
474
+ */
389
475
  states;
476
+ /**
477
+ * The current automaton state number.
478
+ */
390
479
  state;
480
+ /**
481
+ * The look-ahead buffer of characters (size `PEEKCOUNT + 1`).
482
+ */
391
483
  nexts;
392
484
  /**
393
485
  * Constructor.
486
+ * @constructor
394
487
  * @param aString the string to parse
395
488
  */
396
489
  constructor(aString) {
490
+ super();
397
491
  this.stream = aString[Symbol.iterator]();
398
492
  this.token = null;
399
493
  this.tokenString = "";
@@ -406,12 +500,14 @@ var Parser = class Parser {
406
500
  }
407
501
  /**
408
502
  * Returns whether this is the last element.
503
+ * @return true if there are no more characters to read
409
504
  */
410
505
  atEnd() {
411
506
  return this.peekChar() == null;
412
507
  }
413
508
  /**
414
509
  * Concatenates the current character into the token string.
510
+ * @return null
415
511
  */
416
512
  concatCharacter() {
417
513
  this.tokenString = this.tokenString.concat(String(this.nexts[0]));
@@ -419,6 +515,8 @@ var Parser = class Parser {
419
515
  }
420
516
  /**
421
517
  * Parses a single character of the input string.
518
+ * @param aCharacter the character to feed into the automaton; defaults to the next character
519
+ * @return the token produced so far (may be null if still accumulating)
422
520
  */
423
521
  input(aCharacter = this.nextChar()) {
424
522
  const inputs = this.states.get(this.state);
@@ -430,6 +528,7 @@ var Parser = class Parser {
430
528
  }
431
529
  /**
432
530
  * Returns the next character to be parsed from the input string.
531
+ * @return the next character, or null at end of input
433
532
  */
434
533
  nextChar() {
435
534
  let aCharacter = null;
@@ -449,6 +548,7 @@ var Parser = class Parser {
449
548
  }
450
549
  /**
451
550
  * Determines and returns the next token.
551
+ * @return the next parsed token
452
552
  */
453
553
  nextToken() {
454
554
  this.token = null;
@@ -463,18 +563,25 @@ var Parser = class Parser {
463
563
  }
464
564
  /**
465
565
  * Instantiates and returns a NextState.
566
+ * @param aNumber the fallback state number (or null)
567
+ * @param aString the parser method name (or null)
568
+ * @return the new NextState
466
569
  */
467
570
  nextState(aNumber, aString) {
468
571
  return new NextState(aNumber, aString);
469
572
  }
470
573
  /**
471
574
  * Parses the given string and returns the result.
575
+ * @param aString the source string
576
+ * @return the parsed value
472
577
  */
473
578
  static parse(aString) {
474
579
  return new Parser(aString).nextToken();
475
580
  }
476
581
  /**
477
582
  * Returns the next character if one exists.
583
+ * @param aNumber 1-based offset into the look-ahead buffer
584
+ * @return the character at that offset, or null if not present
478
585
  */
479
586
  peekChar(aNumber = 1) {
480
587
  if (aNumber > this.nexts.length) throw new ParseError("Read Error!");
@@ -482,6 +589,7 @@ var Parser = class Parser {
482
589
  }
483
590
  /**
484
591
  * Concatenates characters; invoked from NextState.
592
+ * @return null
485
593
  */
486
594
  concat() {
487
595
  this.concatCharacter();
@@ -492,6 +600,7 @@ var Parser = class Parser {
492
600
  * (`\n`, `\t`, `\r`, `\\`, `\"`) into their actual characters. Invoked from NextState
493
601
  * inside a string literal after a backslash. Unknown escapes pass through as the
494
602
  * literal character (e.g. `\x` becomes `x`).
603
+ * @return null
495
604
  */
496
605
  escapeConcat() {
497
606
  const c = String(this.nexts[0]);
@@ -507,6 +616,7 @@ var Parser = class Parser {
507
616
  }
508
617
  /**
509
618
  * Returns the token number for a Number-type (double-precision floating point: pseudo-Double); invoked from NextState.
619
+ * @return the next state number (3, or 0 when the literal is complete)
510
620
  */
511
621
  doubleToken() {
512
622
  this.concat();
@@ -518,6 +628,7 @@ var Parser = class Parser {
518
628
  }
519
629
  /**
520
630
  * Returns the token number for a Number-type (double-precision floating point: pseudo-Double); invoked from NextState.
631
+ * @return the next state number (5, or 0 when the literal is complete)
521
632
  */
522
633
  doubleTokenAUX() {
523
634
  this.concat();
@@ -529,6 +640,7 @@ var Parser = class Parser {
529
640
  }
530
641
  /**
531
642
  * Returns the token number for a Number-type (integer: pseudo-Integer); invoked from NextState.
643
+ * @return the next state number (2, or 0 when the literal is complete)
532
644
  */
533
645
  integerToken() {
534
646
  this.concat();
@@ -540,6 +652,7 @@ var Parser = class Parser {
540
652
  }
541
653
  /**
542
654
  * Converts the token into a list (Cons) and returns the token number for a list (Cons); invoked from NextState.
655
+ * @return 0
543
656
  */
544
657
  parseList() {
545
658
  this.skippingSpaces();
@@ -551,6 +664,7 @@ var Parser = class Parser {
551
664
  }
552
665
  /**
553
666
  * Helper that converts the token into a list (Cons); invoked from NextState.
667
+ * @return the parsed Cons (or its cdr in dotted-pair form)
554
668
  */
555
669
  parseListAUX() {
556
670
  this.skippingSpaces();
@@ -577,6 +691,7 @@ var Parser = class Parser {
577
691
  }
578
692
  /**
579
693
  * Recognizes a quote, wraps the token into a list (Cons), and returns the token number; invoked from NextState.
694
+ * @return 0
580
695
  */
581
696
  quote() {
582
697
  const anObject = new Cons(this.nextToken(), Cons.nil);
@@ -585,6 +700,7 @@ var Parser = class Parser {
585
700
  }
586
701
  /**
587
702
  * Returns the token number for a quote or for a 0-origin String-type (pseudo-Character); invoked from NextState.
703
+ * @return the next state number
588
704
  */
589
705
  quoteOrChar() {
590
706
  let aNumber = this.peekChar() === "\\" ? 3 : 2;
@@ -593,12 +709,14 @@ var Parser = class Parser {
593
709
  }
594
710
  /**
595
711
  * Detects a right parenthesis (')', ']', '}') and returns the result; invoked from NextState.
712
+ * @return true when the next character is any right paren
596
713
  */
597
714
  rightParen() {
598
715
  return this.peekChar() === ")" || this.peekChar() === "]" || this.peekChar() === "}";
599
716
  }
600
717
  /**
601
718
  * Returns the token number for a sign symbol ('+', '-'); invoked from NextState.
719
+ * @return the next state number (7, or 0 when the literal is complete)
602
720
  */
603
721
  sign() {
604
722
  this.concat();
@@ -610,6 +728,7 @@ var Parser = class Parser {
610
728
  }
611
729
  /**
612
730
  * Skips whitespace; invoked from NextState.
731
+ * @return null
613
732
  */
614
733
  skippingSpaces() {
615
734
  while (this.nexts[1] === String.fromCodePoint(9) || this.nexts[1] === String.fromCodePoint(10) || this.nexts[1] === String.fromCodePoint(11) || this.nexts[1] === String.fromCodePoint(12) || this.nexts[1] === String.fromCodePoint(13) || this.nexts[1] === String.fromCodePoint(32)) this.nextChar();
@@ -617,6 +736,7 @@ var Parser = class Parser {
617
736
  }
618
737
  /**
619
738
  * Returns the token number for an InterpretedSymbol; invoked from NextState.
739
+ * @return the next state number (8, or 0 when the literal is complete)
620
740
  */
621
741
  symbolToken() {
622
742
  this.concat();
@@ -628,6 +748,7 @@ var Parser = class Parser {
628
748
  }
629
749
  /**
630
750
  * Converts the token into a 0-origin String-type (pseudo-Character); invoked from NextState.
751
+ * @return null
631
752
  */
632
753
  tokenToCharacter() {
633
754
  this.token = this.tokenString.charAt(0);
@@ -635,6 +756,7 @@ var Parser = class Parser {
635
756
  }
636
757
  /**
637
758
  * Converts the token into a Number-type (double-precision floating point: pseudo-Double); invoked from NextState.
759
+ * @return null
638
760
  */
639
761
  tokenToDouble() {
640
762
  this.token = Number(this.tokenString);
@@ -642,6 +764,7 @@ var Parser = class Parser {
642
764
  }
643
765
  /**
644
766
  * Converts the token into a Number-type (double-precision floating point: pseudo-Double); invoked from NextState.
767
+ * @return null
645
768
  */
646
769
  tokenToDoubleAUX() {
647
770
  this.concat();
@@ -650,6 +773,7 @@ var Parser = class Parser {
650
773
  }
651
774
  /**
652
775
  * Converts the token into a Number-type (integer: pseudo-Integer); invoked from NextState.
776
+ * @return null
653
777
  */
654
778
  tokenToInteger() {
655
779
  if (this.tokenString[0] === "+") this.tokenString = this.tokenString.slice(1);
@@ -658,6 +782,7 @@ var Parser = class Parser {
658
782
  }
659
783
  /**
660
784
  * Converts the token into a String-type; invoked from NextState.
785
+ * @return null
661
786
  */
662
787
  tokenToString() {
663
788
  this.token = this.tokenString;
@@ -665,6 +790,7 @@ var Parser = class Parser {
665
790
  }
666
791
  /**
667
792
  * Converts the token into an InterpretedSymbol; invoked from NextState.
793
+ * @return null
668
794
  */
669
795
  tokenToSymbol() {
670
796
  this.token = InterpretedSymbol.of(this.tokenString);
@@ -673,6 +799,7 @@ var Parser = class Parser {
673
799
  }
674
800
  /**
675
801
  * Builds the lookup table that maps character codes to their corresponding methods (tokens).
802
+ * @return null
676
803
  */
677
804
  initializeStateTransitionTable() {
678
805
  let aTable = /* @__PURE__ */ new Map();
@@ -723,6 +850,8 @@ var Parser = class Parser {
723
850
  aTable = /* @__PURE__ */ new Map();
724
851
  for (const index of IntStream.rangeClosed(9, 13)) aTable.set(String(index), this.nextState(0, "tokenToInteger"));
725
852
  aTable.set(String(32), this.nextState(0, "tokenToInteger"));
853
+ aTable.set(String(43), this.nextState(8, "symbolToken"));
854
+ aTable.set(String(45), this.nextState(8, "symbolToken"));
726
855
  aTable.set(String(46), this.nextState(3, "doubleToken"));
727
856
  for (const index of IntStream.rangeClosed(48, 57)) aTable.set(String(index), this.nextState(2, "integerToken"));
728
857
  aTable.set(String(69), this.nextState(4, "concat"));
@@ -826,9 +955,18 @@ var Parser = class Parser {
826
955
  * @author Keisuke Ikeda
827
956
  * @this {Cons}
828
957
  */
829
- var Cons = class Cons {
958
+ var Cons = class Cons extends Object {
959
+ /**
960
+ * The shared empty-list sentinel. A Cons whose car and cdr are both itself, representing Lisp `nil`.
961
+ */
830
962
  static nil = new Cons();
963
+ /**
964
+ * The head element of this Cons cell.
965
+ */
831
966
  car;
967
+ /**
968
+ * The tail of this Cons cell (typically another Cons or nil).
969
+ */
832
970
  cdr;
833
971
  /**
834
972
  * Constructor.
@@ -837,6 +975,7 @@ var Cons = class Cons {
837
975
  * @param cdr the cdr; defaults to nil when no argument is given.
838
976
  */
839
977
  constructor(car = Cons.nil, cdr = Cons.nil) {
978
+ super();
840
979
  this.car = car;
841
980
  this.cdr = cdr;
842
981
  }
@@ -1140,6 +1279,7 @@ const SIZE_DO_NOT_MATCH = "size do not match.";
1140
1279
  //#endregion
1141
1280
  //#region src/runtime/Applier/index.ts
1142
1281
  const SELECT_PRINT_FUNCTION_NOT_DEFINED = "selectPrintFunction is not defined";
1282
+ const toCodePoints = (s) => [...s];
1143
1283
  /**
1144
1284
  * Class that mimics Lisp's universal function Apply.
1145
1285
  * @class
@@ -1147,25 +1287,67 @@ const SELECT_PRINT_FUNCTION_NOT_DEFINED = "selectPrintFunction is not defined";
1147
1287
  * @author Keisuke Ikeda
1148
1288
  * @this {Applier}
1149
1289
  */
1150
- var Applier = class Applier {
1290
+ var Applier = class Applier extends Object {
1291
+ /**
1292
+ * Dispatch map from a Lisp function name (InterpretedSymbol) to the name of the Applier method that implements it.
1293
+ */
1151
1294
  static buildInFunctions = Applier.setup();
1152
1295
  static #generateNumber = 0;
1296
+ /**
1297
+ * The environment (variable bindings) used while applying procedures.
1298
+ */
1153
1299
  environment;
1300
+ /**
1301
+ * The stream manager used for I/O and spy output.
1302
+ */
1154
1303
  streamManager;
1304
+ /**
1305
+ * The current recursion depth, used for spy indentation.
1306
+ */
1155
1307
  depth;
1156
- constructor(aTable, aStreamManager, aNumber) {
1308
+ /**
1309
+ * Registered plugins forwarded back into Evaluator on recursive evaluation (e.g. `entrustEvaluator`).
1310
+ */
1311
+ plugins;
1312
+ /**
1313
+ * Constructor.
1314
+ * @constructor
1315
+ * @param aTable the parent environment to extend
1316
+ * @param aStreamManager the stream manager for I/O
1317
+ * @param aNumber the initial recursion depth
1318
+ * @param plugins the plugin chain to forward when re-entering the Evaluator
1319
+ */
1320
+ constructor(aTable, aStreamManager, aNumber, plugins = []) {
1321
+ super();
1157
1322
  this.environment = new Table(aTable);
1158
1323
  this.streamManager = aStreamManager;
1159
1324
  this.depth = aNumber;
1325
+ this.plugins = plugins;
1160
1326
  }
1327
+ /**
1328
+ * Implementation of the Lisp `abs` function. Returns the absolute value of the given number.
1329
+ * @param args the argument Cons containing the target number
1330
+ * @return the absolute value
1331
+ */
1161
1332
  abs(args) {
1162
1333
  if (Cons.isNumber(args.car)) return Math.abs(args.car);
1163
1334
  throw new EvalError(cannotApply("abs", args.car));
1164
1335
  }
1336
+ /**
1337
+ * Implementation of the Lisp `+` / `add` function. Returns the sum of the given numbers.
1338
+ * @param args the argument Cons containing the numbers to add
1339
+ * @return the sum of the arguments
1340
+ */
1165
1341
  add(args) {
1166
1342
  if (Cons.isNumber(args.car)) return this.add_Number(args.car, args.cdr);
1167
1343
  throw new EvalError(cannotApply("add", args.car));
1168
1344
  }
1345
+ /**
1346
+ * Helper that accumulates the sum starting from an initial number and the remaining argument list.
1347
+ * @param init the initial number
1348
+ * @param args the remaining numbers to add
1349
+ * @return the sum of init and all remaining numbers
1350
+ */
1169
1351
  add_Number(init, args) {
1170
1352
  let result = init;
1171
1353
  let aCons = args;
@@ -1177,13 +1359,34 @@ var Applier = class Applier {
1177
1359
  }
1178
1360
  return result;
1179
1361
  }
1180
- static apply(procedure, args, environment, aStreamManager, depth) {
1181
- return new Applier(environment, aStreamManager, depth).apply(procedure, args);
1362
+ /**
1363
+ * Static entry point that instantiates an Applier and applies the given procedure to the arguments.
1364
+ * @param procedure the procedure to apply (a symbol or a lambda Cons)
1365
+ * @param args the argument list
1366
+ * @param environment the environment to use
1367
+ * @param aStreamManager the stream manager for I/O
1368
+ * @param depth the current recursion depth
1369
+ * @param plugins the plugin chain to forward when re-entering the Evaluator
1370
+ * @return the result of applying the procedure
1371
+ */
1372
+ static apply(procedure, args, environment, aStreamManager, depth, plugins = []) {
1373
+ return new Applier(environment, aStreamManager, depth, plugins).apply(procedure, args);
1182
1374
  }
1375
+ /**
1376
+ * Applies the given procedure to the given arguments.
1377
+ * @param procedure the procedure to apply (a symbol or a lambda Cons)
1378
+ * @param args the argument list
1379
+ * @return the result of applying the procedure
1380
+ */
1183
1381
  apply(procedure, args) {
1184
1382
  if (Cons.isSymbol(procedure)) return this.selectProcedure(procedure, args);
1185
1383
  return this.entrustEvaluator(procedure, args);
1186
1384
  }
1385
+ /**
1386
+ * Implementation of the Lisp `assoc` function. Looks up an association in an association list.
1387
+ * @param args the argument Cons containing the key and the association list
1388
+ * @return the matching pair, or nil if no match was found
1389
+ */
1187
1390
  assoc(args) {
1188
1391
  const target = args.car;
1189
1392
  if (Cons.isNotCons(args.nth(2))) return Cons.nil;
@@ -1196,10 +1399,20 @@ var Applier = class Applier {
1196
1399
  }
1197
1400
  return Cons.nil;
1198
1401
  }
1402
+ /**
1403
+ * Implementation of the Lisp `atom` predicate. Returns t if the argument is an atom, otherwise nil.
1404
+ * @param args the argument Cons containing the value to test
1405
+ * @return t if atom, nil otherwise
1406
+ */
1199
1407
  atom_(args) {
1200
1408
  if (Cons.isAtom(args.car)) return InterpretedSymbol.of("t");
1201
1409
  return Cons.nil;
1202
1410
  }
1411
+ /**
1412
+ * Binds the given parameter symbols to the corresponding argument values in this environment.
1413
+ * @param parameter the parameter list (a Cons of symbols, possibly dotted)
1414
+ * @param args the argument list to bind to the parameters
1415
+ */
1203
1416
  binding(parameter, args) {
1204
1417
  if (Cons.isNil(parameter)) return null;
1205
1418
  let aCons = parameter;
@@ -1222,6 +1435,12 @@ var Applier = class Applier {
1222
1435
  else if (Cons.isNotNil(aCons.cdr)) throw new ReferenceError("aList is not defined");
1223
1436
  return null;
1224
1437
  }
1438
+ /**
1439
+ * Invokes the built-in method associated with the given procedure symbol.
1440
+ * @param procedure the symbol naming the built-in function
1441
+ * @param args the argument list
1442
+ * @return the result of the built-in function
1443
+ */
1225
1444
  buildInFunction(procedure, args) {
1226
1445
  if (this.isSpy(procedure)) {
1227
1446
  this.spyPrint(this.streamManager.spyStream(procedure), new Cons(procedure, args).toString());
@@ -1238,34 +1457,80 @@ var Applier = class Applier {
1238
1457
  }
1239
1458
  return answer;
1240
1459
  }
1460
+ /**
1461
+ * Implementation of the Lisp `car` function. Returns the car of the given Cons.
1462
+ * @param args the argument Cons containing the target Cons
1463
+ * @return the car of the target
1464
+ */
1241
1465
  car(args) {
1242
1466
  return args.car.car;
1243
1467
  }
1468
+ /**
1469
+ * Implementation of the Lisp `cdr` function. Returns the cdr of the given Cons.
1470
+ * @param args the argument Cons containing the target Cons
1471
+ * @return the cdr of the target
1472
+ */
1244
1473
  cdr(args) {
1245
1474
  return args.car.cdr;
1246
1475
  }
1476
+ /**
1477
+ * Implementation of the Lisp `characterp` predicate. Returns t if the argument is a single-character string.
1478
+ * @param args the argument Cons containing the value to test
1479
+ * @return t if a character, nil otherwise
1480
+ */
1247
1481
  character_(args) {
1248
1482
  if (Cons.isString(args.car) && args.car.length === 1) return InterpretedSymbol.of("t");
1249
1483
  return Cons.nil;
1250
1484
  }
1485
+ /**
1486
+ * Implementation of the Lisp `cons` function. Constructs a new Cons from the given car and cdr.
1487
+ * @param args the argument Cons containing the car and cdr
1488
+ * @return the newly constructed Cons
1489
+ */
1251
1490
  cons(args) {
1252
1491
  return new Cons(args.car, args.nth(2));
1253
1492
  }
1493
+ /**
1494
+ * Implementation of the Lisp `consp` predicate. Returns t if the argument is a Cons, otherwise nil.
1495
+ * @param args the argument Cons containing the value to test
1496
+ * @return t if a Cons, nil otherwise
1497
+ */
1254
1498
  cons_(args) {
1255
1499
  if (Cons.isCons(args.car)) return InterpretedSymbol.of("t");
1256
1500
  return Cons.nil;
1257
1501
  }
1502
+ /**
1503
+ * Implementation of the Lisp `copy` function. Returns a deep clone of the given value.
1504
+ * @param args the argument Cons containing the value to copy
1505
+ * @return the cloned value
1506
+ */
1258
1507
  copy(args) {
1259
1508
  return Cons.cloneValue(args.car);
1260
1509
  }
1510
+ /**
1511
+ * Implementation of the Lisp `cos` function. Returns the cosine of the given number.
1512
+ * @param args the argument Cons containing the angle in radians
1513
+ * @return the cosine of the argument
1514
+ */
1261
1515
  cos(args) {
1262
1516
  if (Cons.isNumber(args.car)) return Math.cos(args.car);
1263
1517
  throw new ReferenceError(SELECT_PRINT_FUNCTION_NOT_DEFINED);
1264
1518
  }
1519
+ /**
1520
+ * Implementation of the Lisp `/` / `divide` function. Returns the quotient of the given numbers.
1521
+ * @param args the argument Cons containing the numbers to divide
1522
+ * @return the quotient of the arguments
1523
+ */
1265
1524
  divide(args) {
1266
1525
  if (Cons.isNumber(args.car)) return this.divide_Number(args.car, args.cdr);
1267
1526
  throw new EvalError(cannotApply("divide", args.car));
1268
1527
  }
1528
+ /**
1529
+ * Helper that accumulates the quotient starting from an initial number and the remaining argument list.
1530
+ * @param init the initial number (numerator)
1531
+ * @param args the remaining numbers to divide by
1532
+ * @return the quotient of init divided by all remaining numbers
1533
+ */
1269
1534
  divide_Number(init, args) {
1270
1535
  let result = init;
1271
1536
  let aCons = args;
@@ -1277,6 +1542,12 @@ var Applier = class Applier {
1277
1542
  }
1278
1543
  return result;
1279
1544
  }
1545
+ /**
1546
+ * Delegates evaluation of a lambda body to the Evaluator after binding its parameters.
1547
+ * @param procedure the lambda Cons to apply
1548
+ * @param args the argument list to bind to the lambda's parameters
1549
+ * @return the result of evaluating the lambda body
1550
+ */
1280
1551
  entrustEvaluator(procedure, args) {
1281
1552
  let anObject = Cons.nil;
1282
1553
  let aCons = procedure.cdr;
@@ -1284,14 +1555,24 @@ var Applier = class Applier {
1284
1555
  aCons = aCons.cdr;
1285
1556
  for (const each of aCons.loop()) {
1286
1557
  if (each instanceof Table) break;
1287
- anObject = Evaluator.eval(each, this.environment, this.streamManager, this.depth);
1558
+ anObject = Evaluator.eval(each, this.environment, this.streamManager, this.depth, this.plugins);
1288
1559
  }
1289
1560
  return anObject;
1290
1561
  }
1562
+ /**
1563
+ * Implementation of the Lisp `eq` predicate. Returns t when both arguments are identical (JS `===`).
1564
+ * @param args the argument Cons containing the two values to compare
1565
+ * @return t when identical, nil otherwise
1566
+ */
1291
1567
  eq_(args) {
1292
1568
  if (args.car === args.nth(2)) return InterpretedSymbol.of("t");
1293
1569
  return Cons.nil;
1294
1570
  }
1571
+ /**
1572
+ * Implementation of the Lisp `equal` / `=` predicate. Returns t when both arguments are structurally equal.
1573
+ * @param args the argument Cons containing the two values to compare
1574
+ * @return t when equal, nil otherwise
1575
+ */
1295
1576
  equal_(args) {
1296
1577
  const first = args.car;
1297
1578
  const second = args.nth(2);
@@ -1302,10 +1583,20 @@ var Applier = class Applier {
1302
1583
  }
1303
1584
  return Cons.nil;
1304
1585
  }
1586
+ /**
1587
+ * Implementation of the Lisp `exp` function. Returns e raised to the given power.
1588
+ * @param args the argument Cons containing the exponent
1589
+ * @return e raised to the given power
1590
+ */
1305
1591
  exp(args) {
1306
1592
  if (Cons.isNumber(args.car)) return Math.exp(args.car);
1307
1593
  throw new ReferenceError(SELECT_PRINT_FUNCTION_NOT_DEFINED);
1308
1594
  }
1595
+ /**
1596
+ * Implementation of the Lisp `format` function. Writes a formatted string to standard output.
1597
+ * @param args the argument Cons containing the format string followed by its arguments
1598
+ * @return nil
1599
+ */
1309
1600
  format(args) {
1310
1601
  if (!Cons.isString(args.car)) throw new EvalError(cannotApply("format", args.car));
1311
1602
  const aCons = args.cdr;
@@ -1313,6 +1604,12 @@ var Applier = class Applier {
1313
1604
  process.stdout.write(String(format));
1314
1605
  return Cons.nil;
1315
1606
  }
1607
+ /**
1608
+ * Helper that expands the given format string with the supplied arguments.
1609
+ * @param format the format string containing directives such as `~a`, `~%`, and width specifiers
1610
+ * @param aCons the argument list to interpolate into the directives
1611
+ * @return the formatted string
1612
+ */
1316
1613
  format_AUX(format, aCons) {
1317
1614
  let theCons = aCons;
1318
1615
  let index = 0;
@@ -1434,23 +1731,48 @@ var Applier = class Applier {
1434
1731
  if (Cons.isNotNil(theCons)) throw new EvalError(SIZE_DO_NOT_MATCH);
1435
1732
  return buffer;
1436
1733
  }
1734
+ /**
1735
+ * Implementation of the Lisp `floatp` predicate. Returns t if the argument is a number representable as IEEE 32-bit float.
1736
+ * @param args the argument Cons containing the value to test
1737
+ * @return t if a float, nil otherwise
1738
+ */
1437
1739
  float_(args) {
1438
1740
  if (Cons.isNumber(args.car) && -34e37 <= args.car && args.car <= 34e37) return InterpretedSymbol.of("t");
1439
1741
  return Cons.nil;
1440
1742
  }
1743
+ /**
1744
+ * Implementation of the Lisp `gensym` function. Generates a fresh, unique symbol.
1745
+ * @return a new, unique InterpretedSymbol
1746
+ */
1441
1747
  gensym() {
1442
1748
  const aSymbol = InterpretedSymbol.of("id" + String(Applier.#generateNumber));
1443
1749
  Applier.incrementGenerateNumber();
1444
1750
  return aSymbol;
1445
1751
  }
1752
+ /**
1753
+ * Returns the appropriate stream for the given object.
1754
+ * @param anObject the object used to select the stream
1755
+ * @return the selected stream
1756
+ */
1446
1757
  getStream(anObject) {
1447
1758
  if (typeof anObject === "string") return process.out;
1448
1759
  return this.streamManager.getStream();
1449
1760
  }
1761
+ /**
1762
+ * Implementation of the Lisp `>` / `greaterThan` predicate. Returns t when arguments are in strictly decreasing order.
1763
+ * @param args the argument Cons containing the numbers to compare
1764
+ * @return t when each is greater than the next, nil otherwise
1765
+ */
1450
1766
  greaterThan(args) {
1451
1767
  if (Cons.isNumber(args.car)) return this.greaterThan_Number(args.car, args.cdr);
1452
1768
  throw new EvalError(cannotApply(">", args.car));
1453
1769
  }
1770
+ /**
1771
+ * Helper that checks `>` ordering starting from an initial number against the remaining argument list.
1772
+ * @param init the initial number on the left side of the first comparison
1773
+ * @param args the remaining numbers to compare against
1774
+ * @return t when strictly decreasing, nil otherwise
1775
+ */
1454
1776
  greaterThan_Number(init, args) {
1455
1777
  let leftValue = init;
1456
1778
  let aCons = args;
@@ -1465,10 +1787,21 @@ var Applier = class Applier {
1465
1787
  }
1466
1788
  return InterpretedSymbol.of("t");
1467
1789
  }
1790
+ /**
1791
+ * Implementation of the Lisp `>=` / `greaterThanOrEqual` predicate. Returns t when arguments are in non-increasing order.
1792
+ * @param args the argument Cons containing the numbers to compare
1793
+ * @return t when each is greater than or equal to the next, nil otherwise
1794
+ */
1468
1795
  greaterThanOrEqual(args) {
1469
1796
  if (Cons.isNumber(args.car)) return this.greaterThanOrEqual_Number(args.car, args.cdr);
1470
1797
  throw new EvalError(cannotApply(">=", args.car));
1471
1798
  }
1799
+ /**
1800
+ * Helper that checks `>=` ordering starting from an initial number against the remaining argument list.
1801
+ * @param init the initial number on the left side of the first comparison
1802
+ * @param args the remaining numbers to compare against
1803
+ * @return t when non-increasing, nil otherwise
1804
+ */
1472
1805
  greaterThanOrEqual_Number(init, args) {
1473
1806
  let leftValue = init;
1474
1807
  let aCons = args;
@@ -1483,31 +1816,441 @@ var Applier = class Applier {
1483
1816
  }
1484
1817
  return InterpretedSymbol.of("t");
1485
1818
  }
1819
+ /**
1820
+ * Increments the internal counter used by `gensym` to ensure uniqueness.
1821
+ */
1486
1822
  static incrementGenerateNumber() {
1487
1823
  Applier.#generateNumber++;
1488
1824
  return null;
1489
1825
  }
1826
+ /**
1827
+ * Returns a string of indentation used as a prefix for spy output, based on the current depth.
1828
+ * @return the indentation string
1829
+ */
1490
1830
  indent() {
1491
1831
  let index = 0;
1492
1832
  let aString = "";
1493
1833
  while (index++ < this.depth) aString += "| ";
1494
1834
  return aString;
1495
1835
  }
1836
+ /**
1837
+ * Implementation of the Lisp `integerp` predicate. Returns t if the argument is an integer.
1838
+ * @param args the argument Cons containing the value to test
1839
+ * @return t if an integer, nil otherwise
1840
+ */
1496
1841
  integer_(args) {
1497
1842
  if (Cons.isNumber(args.car) && Number.isInteger(args.car)) return InterpretedSymbol.of("t");
1498
1843
  return Cons.nil;
1499
1844
  }
1845
+ /**
1846
+ * Implementation of the Lisp `evenp` predicate. Returns t if the argument is an even integer.
1847
+ * @param args the argument Cons containing the value to test
1848
+ * @return t if even, nil otherwise
1849
+ */
1850
+ even_(args) {
1851
+ if (Cons.isNumber(args.car) && Number.isInteger(args.car) && args.car % 2 === 0) return InterpretedSymbol.of("t");
1852
+ return Cons.nil;
1853
+ }
1854
+ /**
1855
+ * Implementation of the Lisp `oddp` predicate. Returns t if the argument is an odd integer.
1856
+ * @param args the argument Cons containing the value to test
1857
+ * @return t if odd, nil otherwise
1858
+ */
1859
+ odd_(args) {
1860
+ if (Cons.isNumber(args.car) && Number.isInteger(args.car) && args.car % 2 !== 0) return InterpretedSymbol.of("t");
1861
+ return Cons.nil;
1862
+ }
1863
+ /**
1864
+ * Implementation of the Lisp `zerop` predicate. Returns t if the argument equals zero.
1865
+ * @param args the argument Cons containing the value to test
1866
+ * @return t if zero, nil otherwise
1867
+ */
1868
+ zero_(args) {
1869
+ if (Cons.isNumber(args.car) && args.car === 0) return InterpretedSymbol.of("t");
1870
+ return Cons.nil;
1871
+ }
1872
+ /**
1873
+ * Implementation of the Lisp `plusp` predicate. Returns t if the argument is strictly positive.
1874
+ * @param args the argument Cons containing the value to test
1875
+ * @return t if positive, nil otherwise
1876
+ */
1877
+ plus_(args) {
1878
+ if (Cons.isNumber(args.car) && args.car > 0) return InterpretedSymbol.of("t");
1879
+ return Cons.nil;
1880
+ }
1881
+ /**
1882
+ * Implementation of the Lisp `minusp` predicate. Returns t if the argument is strictly negative.
1883
+ * @param args the argument Cons containing the value to test
1884
+ * @return t if negative, nil otherwise
1885
+ */
1886
+ minus_(args) {
1887
+ if (Cons.isNumber(args.car) && args.car < 0) return InterpretedSymbol.of("t");
1888
+ return Cons.nil;
1889
+ }
1890
+ /**
1891
+ * Implementation of the Lisp `1+` function. Returns the argument incremented by one.
1892
+ * @param args the argument Cons containing the target number
1893
+ * @return the argument plus one
1894
+ */
1895
+ oneplus(args) {
1896
+ if (Cons.isNumber(args.car)) return args.car + 1;
1897
+ throw new EvalError(cannotApply("1+", args.car));
1898
+ }
1899
+ /**
1900
+ * Implementation of the Lisp `1-` function. Returns the argument decremented by one.
1901
+ * @param args the argument Cons containing the target number
1902
+ * @return the argument minus one
1903
+ */
1904
+ oneminus(args) {
1905
+ if (Cons.isNumber(args.car)) return args.car - 1;
1906
+ throw new EvalError(cannotApply("1-", args.car));
1907
+ }
1908
+ /**
1909
+ * Implementation of the Lisp `expt` function. Returns the base raised to the exponent.
1910
+ * @param args the argument Cons containing the base followed by the exponent
1911
+ * @return base raised to the exponent
1912
+ */
1913
+ expt(args) {
1914
+ const base = args.car;
1915
+ const exponent = args.nth(2);
1916
+ if (Cons.isNumber(base) && Cons.isNumber(exponent)) return Math.pow(base, exponent);
1917
+ throw new EvalError(cannotApply("expt", base));
1918
+ }
1919
+ /**
1920
+ * Implementation of the Lisp `truncate` function. Returns the integer part of the given number.
1921
+ * @param args the argument Cons containing the target number
1922
+ * @return the truncated integer
1923
+ */
1924
+ truncate(args) {
1925
+ if (Cons.isNumber(args.car)) return Math.trunc(args.car);
1926
+ throw new EvalError(cannotApply("truncate", args.car));
1927
+ }
1928
+ /**
1929
+ * Implementation of the Lisp `floor` function. Returns the largest integer not greater than the given number.
1930
+ * @param args the argument Cons containing the target number
1931
+ * @return the floor of the argument
1932
+ */
1933
+ floor(args) {
1934
+ if (Cons.isNumber(args.car)) return Math.floor(args.car);
1935
+ throw new EvalError(cannotApply("floor", args.car));
1936
+ }
1937
+ /**
1938
+ * Implementation of the Lisp `ceiling` function. Returns the smallest integer not less than the given number.
1939
+ * @param args the argument Cons containing the target number
1940
+ * @return the ceiling of the argument
1941
+ */
1942
+ ceiling(args) {
1943
+ if (Cons.isNumber(args.car)) return Math.ceil(args.car);
1944
+ throw new EvalError(cannotApply("ceiling", args.car));
1945
+ }
1946
+ /**
1947
+ * Implementation of the Lisp `min` function. Returns the minimum of the given numbers.
1948
+ * @param args the argument Cons containing the numbers to compare
1949
+ * @return the smallest number
1950
+ */
1951
+ min(args) {
1952
+ const values = [];
1953
+ for (const each of args.loop()) {
1954
+ if (!Cons.isNumber(each)) throw new EvalError(cannotApply("min", each));
1955
+ values.push(each);
1956
+ }
1957
+ if (values.length === 0) throw new EvalError("min requires at least one argument");
1958
+ return Math.min(...values);
1959
+ }
1960
+ /**
1961
+ * Implementation of the Lisp `max` function. Returns the maximum of the given numbers.
1962
+ * @param args the argument Cons containing the numbers to compare
1963
+ * @return the largest number
1964
+ */
1965
+ max(args) {
1966
+ const values = [];
1967
+ for (const each of args.loop()) {
1968
+ if (!Cons.isNumber(each)) throw new EvalError(cannotApply("max", each));
1969
+ values.push(each);
1970
+ }
1971
+ if (values.length === 0) throw new EvalError("max requires at least one argument");
1972
+ return Math.max(...values);
1973
+ }
1974
+ /**
1975
+ * Implementation of the Lisp `length` function. Returns the length of a list or string.
1976
+ * @param args the argument Cons containing the target sequence
1977
+ * @return the length of the sequence
1978
+ */
1979
+ length(args) {
1980
+ const target = args.car;
1981
+ if (Cons.isString(target)) return toCodePoints(target).length;
1982
+ if (Cons.isCons(target)) return target.length();
1983
+ if (Cons.isNil(target)) return 0;
1984
+ throw new EvalError(cannotApply("length", target));
1985
+ }
1986
+ /**
1987
+ * Implementation of the Lisp `string-upcase` function. Returns the upper-cased form of the given string.
1988
+ * @param args the argument Cons containing the target string
1989
+ * @return the upper-cased string
1990
+ */
1991
+ stringUpcase(args) {
1992
+ if (Cons.isString(args.car)) return args.car.toUpperCase();
1993
+ throw new EvalError(cannotApply("string-upcase", args.car));
1994
+ }
1995
+ /**
1996
+ * Implementation of the Lisp `string-downcase` function. Returns the lower-cased form of the given string.
1997
+ * @param args the argument Cons containing the target string
1998
+ * @return the lower-cased string
1999
+ */
2000
+ stringDowncase(args) {
2001
+ if (Cons.isString(args.car)) return args.car.toLowerCase();
2002
+ throw new EvalError(cannotApply("string-downcase", args.car));
2003
+ }
2004
+ /**
2005
+ * Implementation of the Lisp `string-trim` function. Returns the given string with surrounding whitespace removed.
2006
+ * @param args the argument Cons containing the target string
2007
+ * @return the trimmed string
2008
+ */
2009
+ stringTrim(args) {
2010
+ if (Cons.isString(args.car)) return args.car.trim();
2011
+ throw new EvalError(cannotApply("string-trim", args.car));
2012
+ }
2013
+ /**
2014
+ * Implementation of the Lisp `substring` function. Returns a portion of the given string between start and end (in code points).
2015
+ * @param args the argument Cons containing the target string, start index, and optional end index
2016
+ * @return the requested substring
2017
+ */
2018
+ substring(args) {
2019
+ const target = args.car;
2020
+ const start = args.nth(2);
2021
+ const end = args.nth(3);
2022
+ if (!Cons.isString(target)) throw new EvalError(cannotApply("substring", target));
2023
+ if (!Cons.isNumber(start)) throw new EvalError(cannotApply("substring", start));
2024
+ const chars = toCodePoints(target);
2025
+ if (Cons.isNil(end)) return chars.slice(start).join("");
2026
+ if (!Cons.isNumber(end)) throw new EvalError(cannotApply("substring", end));
2027
+ return chars.slice(start, end).join("");
2028
+ }
2029
+ /**
2030
+ * Implementation of the Lisp `concatenate` function. Returns the concatenation of all the given strings.
2031
+ * @param args the argument Cons containing the strings to concatenate
2032
+ * @return the concatenated string
2033
+ */
2034
+ concatenate(args) {
2035
+ let result = "";
2036
+ for (const each of args.loop()) {
2037
+ if (!Cons.isString(each)) throw new EvalError(cannotApply("concatenate", each));
2038
+ result += each;
2039
+ }
2040
+ return result;
2041
+ }
2042
+ /**
2043
+ * Implementation of the Lisp `elt` function. Returns the element at the given index of a string or list.
2044
+ * @param args the argument Cons containing the target sequence and the index
2045
+ * @return the element at the given index
2046
+ */
2047
+ elt(args) {
2048
+ const target = args.car;
2049
+ const index = args.nth(2);
2050
+ if (!Cons.isNumber(index)) throw new EvalError(cannotApply("elt", index));
2051
+ if (Cons.isString(target)) {
2052
+ const chars = toCodePoints(target);
2053
+ if (index < 0 || index >= chars.length) throw new EvalError(`elt: index ${String(index)} out of range`);
2054
+ return chars[index];
2055
+ }
2056
+ if (Cons.isCons(target)) {
2057
+ if (index < 0 || index >= target.length()) throw new EvalError(`elt: index ${String(index)} out of range`);
2058
+ return target.nth(index + 1);
2059
+ }
2060
+ throw new EvalError(cannotApply("elt", target));
2061
+ }
2062
+ /**
2063
+ * Implementation of the Lisp `subseq` function. Returns a subsequence of a string or list between start and end.
2064
+ * @param args the argument Cons containing the target sequence, start index, and optional end index
2065
+ * @return the requested subsequence
2066
+ */
2067
+ subseq(args) {
2068
+ const target = args.car;
2069
+ const start = args.nth(2);
2070
+ const end = args.nth(3);
2071
+ if (!Cons.isNumber(start)) throw new EvalError(cannotApply("subseq", start));
2072
+ if (Cons.isString(target)) {
2073
+ const chars = toCodePoints(target);
2074
+ if (Cons.isNil(end)) return chars.slice(start).join("");
2075
+ if (!Cons.isNumber(end)) throw new EvalError(cannotApply("subseq", end));
2076
+ return chars.slice(start, end).join("");
2077
+ }
2078
+ if (Cons.isCons(target)) {
2079
+ const stop = Cons.isNil(end) ? target.length() : end;
2080
+ if (!Cons.isNumber(stop)) throw new EvalError(cannotApply("subseq", end));
2081
+ let result = Cons.nil;
2082
+ for (let i = stop - 1; i >= start; i--) result = new Cons(target.nth(i + 1), result);
2083
+ return result;
2084
+ }
2085
+ throw new EvalError(cannotApply("subseq", target));
2086
+ }
2087
+ /**
2088
+ * Implementation of the Lisp `count` function. Counts the occurrences of an item within a string or list.
2089
+ * @param args the argument Cons containing the item and the target sequence
2090
+ * @return the number of occurrences
2091
+ */
2092
+ count(args) {
2093
+ const item = args.car;
2094
+ const target = args.nth(2);
2095
+ let n = 0;
2096
+ if (Cons.isString(target)) {
2097
+ if (!Cons.isString(item) || item.length !== 1) return 0;
2098
+ for (const ch of target) if (ch === item) n++;
2099
+ return n;
2100
+ }
2101
+ if (Cons.isCons(target) || Cons.isNil(target)) {
2102
+ const list = Cons.isNil(target) ? Cons.nil : target;
2103
+ if (Cons.isCons(list)) {
2104
+ for (const each of list.loop()) if (each === item) n++;
2105
+ }
2106
+ return n;
2107
+ }
2108
+ throw new EvalError(cannotApply("count", target));
2109
+ }
2110
+ /**
2111
+ * Implementation of the Lisp `reduce` function. Combines the elements of a list using a binary procedure.
2112
+ * @param args the argument Cons containing the procedure, the list, and an optional initial value
2113
+ * @return the result of folding the procedure over the list
2114
+ */
2115
+ reduce(args) {
2116
+ const procedure = args.car;
2117
+ const list = args.nth(2);
2118
+ const hasInit = args.length() >= 3;
2119
+ const init = args.nth(3);
2120
+ if (Cons.isNil(list)) {
2121
+ if (hasInit) return init;
2122
+ return Applier.apply(procedure, Cons.nil, this.environment, this.streamManager, this.depth);
2123
+ }
2124
+ if (!Cons.isCons(list)) throw new EvalError(cannotApply("reduce", list));
2125
+ const iter = list.loop();
2126
+ let acc;
2127
+ if (hasInit) acc = init;
2128
+ else {
2129
+ if (!iter.hasNext()) return Applier.apply(procedure, Cons.nil, this.environment, this.streamManager, this.depth);
2130
+ acc = iter.next();
2131
+ }
2132
+ while (iter.hasNext()) {
2133
+ const next = iter.next();
2134
+ acc = Applier.apply(procedure, new Cons(acc, new Cons(next, Cons.nil)), this.environment, this.streamManager, this.depth);
2135
+ }
2136
+ return acc;
2137
+ }
2138
+ /**
2139
+ * Implementation of the Lisp `every` function. Returns t when the predicate holds for every element of the list.
2140
+ * @param args the argument Cons containing the predicate and the list
2141
+ * @return t when the predicate holds for every element, nil otherwise
2142
+ */
2143
+ every(args) {
2144
+ const procedure = args.car;
2145
+ const list = args.nth(2);
2146
+ if (Cons.isNil(list)) return InterpretedSymbol.of("t");
2147
+ if (!Cons.isCons(list)) throw new EvalError(cannotApply("every", list));
2148
+ for (const each of list.loop()) {
2149
+ const result = Applier.apply(procedure, new Cons(each, Cons.nil), this.environment, this.streamManager, this.depth);
2150
+ if (Cons.isNil(result)) return Cons.nil;
2151
+ }
2152
+ return InterpretedSymbol.of("t");
2153
+ }
2154
+ /**
2155
+ * Implementation of the Lisp `some` function. Returns the first non-nil predicate result, or nil if all are nil.
2156
+ * @param args the argument Cons containing the predicate and the list
2157
+ * @return the first non-nil result, or nil
2158
+ */
2159
+ some(args) {
2160
+ const procedure = args.car;
2161
+ const list = args.nth(2);
2162
+ if (Cons.isNil(list)) return Cons.nil;
2163
+ if (!Cons.isCons(list)) throw new EvalError(cannotApply("some", list));
2164
+ for (const each of list.loop()) {
2165
+ const result = Applier.apply(procedure, new Cons(each, Cons.nil), this.environment, this.streamManager, this.depth);
2166
+ if (Cons.isNotNil(result)) return result;
2167
+ }
2168
+ return Cons.nil;
2169
+ }
2170
+ /**
2171
+ * Implementation of the Lisp `find` function. Returns the first element of the list that matches the given item.
2172
+ * @param args the argument Cons containing the item and the list
2173
+ * @return the matching element, or nil if none found
2174
+ */
2175
+ find(args) {
2176
+ const item = args.car;
2177
+ const list = args.nth(2);
2178
+ if (Cons.isNil(list)) return Cons.nil;
2179
+ if (!Cons.isCons(list)) throw new EvalError(cannotApply("find", list));
2180
+ for (const each of list.loop()) if (this.eq_(new Cons(item, new Cons(each, Cons.nil))) === InterpretedSymbol.of("t")) return each;
2181
+ return Cons.nil;
2182
+ }
2183
+ /**
2184
+ * Implementation of the Lisp `mapcan` function. Applies the procedure to each element and concatenates the resulting lists.
2185
+ * @param args the argument Cons containing the procedure and the list
2186
+ * @return the concatenation of the per-element results
2187
+ */
2188
+ mapcan(args) {
2189
+ const procedure = args.car;
2190
+ const list = args.nth(2);
2191
+ if (Cons.isNil(list)) return Cons.nil;
2192
+ if (!Cons.isCons(list)) throw new EvalError(cannotApply("mapcan", list));
2193
+ const collected = [];
2194
+ for (const each of list.loop()) {
2195
+ const part = Applier.apply(procedure, new Cons(each, Cons.nil), this.environment, this.streamManager, this.depth);
2196
+ if (Cons.isCons(part)) for (const x of part.loop()) collected.push(x);
2197
+ }
2198
+ let result = Cons.nil;
2199
+ for (let i = collected.length - 1; i >= 0; i--) result = new Cons(collected[i], result);
2200
+ return result;
2201
+ }
2202
+ /**
2203
+ * Implementation of the Lisp `sort` function. Returns a new list sorted by the given comparison predicate.
2204
+ * @param args the argument Cons containing the list and the comparison predicate
2205
+ * @return the sorted list
2206
+ */
2207
+ sort(args) {
2208
+ const list = args.car;
2209
+ const procedure = args.nth(2);
2210
+ if (Cons.isNil(list)) return Cons.nil;
2211
+ if (!Cons.isCons(list)) throw new EvalError(cannotApply("sort", list));
2212
+ const items = [];
2213
+ for (const each of list.loop()) items.push(each);
2214
+ items.sort((a, b) => {
2215
+ const result = Applier.apply(procedure, new Cons(a, new Cons(b, Cons.nil)), this.environment, this.streamManager, this.depth);
2216
+ return Cons.isNil(result) ? 1 : -1;
2217
+ });
2218
+ let result = Cons.nil;
2219
+ for (let i = items.length - 1; i >= 0; i--) result = new Cons(items[i], result);
2220
+ return result;
2221
+ }
2222
+ /**
2223
+ * Returns whether the given symbol is currently being spied on.
2224
+ * @param aSymbol the symbol to check
2225
+ * @return true if spied on, false otherwise
2226
+ */
1500
2227
  isSpy(aSymbol) {
1501
2228
  return this.streamManager.isSpy(aSymbol);
1502
2229
  }
2230
+ /**
2231
+ * Implementation of the Lisp `last` function. Returns the last cell of the given list.
2232
+ * @param args the argument Cons containing the target list
2233
+ * @return the last cell of the list
2234
+ */
1503
2235
  last(args) {
1504
2236
  if (Cons.isNotCons(args)) return Cons.nil;
1505
2237
  return args.car.last();
1506
2238
  }
2239
+ /**
2240
+ * Implementation of the Lisp `<` / `lessThan` predicate. Returns t when arguments are in strictly increasing order.
2241
+ * @param args the argument Cons containing the numbers to compare
2242
+ * @return t when each is less than the next, nil otherwise
2243
+ */
1507
2244
  lessThan(args) {
1508
2245
  if (Cons.isNumber(args.car)) return this.lessThan_Number(args.car, args.cdr);
1509
2246
  throw new EvalError(cannotApply("<", args.car));
1510
2247
  }
2248
+ /**
2249
+ * Helper that checks `<` ordering starting from an initial number against the remaining argument list.
2250
+ * @param init the initial number on the left side of the first comparison
2251
+ * @param args the remaining numbers to compare against
2252
+ * @return t when strictly increasing, nil otherwise
2253
+ */
1511
2254
  lessThan_Number(init, args) {
1512
2255
  let leftValue = init;
1513
2256
  let aCons = args;
@@ -1522,10 +2265,21 @@ var Applier = class Applier {
1522
2265
  }
1523
2266
  return InterpretedSymbol.of("t");
1524
2267
  }
2268
+ /**
2269
+ * Implementation of the Lisp `<=` / `lessThanOrEqual` predicate. Returns t when arguments are in non-decreasing order.
2270
+ * @param args the argument Cons containing the numbers to compare
2271
+ * @return t when each is less than or equal to the next, nil otherwise
2272
+ */
1525
2273
  lessThanOrEqual(args) {
1526
2274
  if (Cons.isNumber(args.car)) return this.lessThanOrEqual_Number(args.car, args.cdr);
1527
2275
  throw new EvalError(cannotApply("<=", args.car));
1528
2276
  }
2277
+ /**
2278
+ * Helper that checks `<=` ordering starting from an initial number against the remaining argument list.
2279
+ * @param init the initial number on the left side of the first comparison
2280
+ * @param args the remaining numbers to compare against
2281
+ * @return t when non-decreasing, nil otherwise
2282
+ */
1529
2283
  lessThanOrEqual_Number(init, args) {
1530
2284
  let leftValue = init;
1531
2285
  let aCons = args;
@@ -1540,14 +2294,29 @@ var Applier = class Applier {
1540
2294
  }
1541
2295
  return InterpretedSymbol.of("t");
1542
2296
  }
2297
+ /**
2298
+ * Implementation of the Lisp `list` function. Returns a list of the given arguments.
2299
+ * @param args the argument list
2300
+ * @return a Cons list of the arguments
2301
+ */
1543
2302
  list(args) {
1544
2303
  if (Cons.isNil(args)) return Cons.nil;
1545
2304
  return new Cons(args.car, this.list(args.cdr));
1546
2305
  }
2306
+ /**
2307
+ * Implementation of the Lisp `listp` predicate. Returns t if the argument is a list (Cons or nil).
2308
+ * @param args the argument Cons containing the value to test
2309
+ * @return t if a list, nil otherwise
2310
+ */
1547
2311
  list_(args) {
1548
2312
  if (Cons.isList(args.car)) return InterpretedSymbol.of("t");
1549
2313
  return Cons.nil;
1550
2314
  }
2315
+ /**
2316
+ * Implementation of the Lisp `mapcar` function. Applies the procedure to each tuple of corresponding elements.
2317
+ * @param args the argument Cons containing the procedure followed by one or more lists
2318
+ * @return the list of results
2319
+ */
1551
2320
  mapcar(args) {
1552
2321
  const aCons = new Cons(Cons.nil, Cons.nil);
1553
2322
  const procedure = args.car;
@@ -1571,6 +2340,11 @@ var Applier = class Applier {
1571
2340
  }
1572
2341
  return aCons.cdr;
1573
2342
  }
2343
+ /**
2344
+ * Implementation of the Lisp `member` function. Returns the sublist whose car matches the given item.
2345
+ * @param args the argument Cons containing the item, the list, and an optional comparator symbol
2346
+ * @return the matching sublist, or nil if not found
2347
+ */
1574
2348
  member(args) {
1575
2349
  let aSymbol = InterpretedSymbol.of("equal?");
1576
2350
  if (Cons.isNotNil(args.nth(3))) aSymbol = args.nth(3);
@@ -1586,14 +2360,30 @@ var Applier = class Applier {
1586
2360
  }
1587
2361
  return Cons.nil;
1588
2362
  }
2363
+ /**
2364
+ * Implementation of the Lisp `memq` predicate. Returns t when `member` finds a match, nil otherwise.
2365
+ * @param args the argument Cons forwarded to `member`
2366
+ * @return t when found, nil otherwise
2367
+ */
1589
2368
  memq(args) {
1590
2369
  if (this.member(args) === Cons.nil) return Cons.nil;
1591
2370
  return InterpretedSymbol.of("t");
1592
2371
  }
2372
+ /**
2373
+ * Implementation of the Lisp `mod` / `//` function. Returns the remainder of dividing the arguments in sequence.
2374
+ * @param args the argument Cons containing the numbers
2375
+ * @return the modulo result
2376
+ */
1593
2377
  mod(args) {
1594
2378
  if (Cons.isNumber(args.car)) return this.mod_Number(args.car, args.cdr);
1595
2379
  throw new EvalError(cannotApply("mod", args.car));
1596
2380
  }
2381
+ /**
2382
+ * Helper that accumulates the modulo starting from an initial number and the remaining argument list.
2383
+ * @param init the initial number
2384
+ * @param args the remaining numbers to mod by
2385
+ * @return the remainder after taking modulo with each of the remaining numbers
2386
+ */
1597
2387
  mod_Number(init, args) {
1598
2388
  let result = init;
1599
2389
  let aCons = args;
@@ -1605,10 +2395,21 @@ var Applier = class Applier {
1605
2395
  }
1606
2396
  return result;
1607
2397
  }
2398
+ /**
2399
+ * Implementation of the Lisp `*` / `multiply` function. Returns the product of the given numbers.
2400
+ * @param args the argument Cons containing the numbers to multiply
2401
+ * @return the product of the arguments
2402
+ */
1608
2403
  multiply(args) {
1609
2404
  if (Cons.isNumber(args.car)) return this.multiply_Number(args.car, args.cdr);
1610
2405
  throw new EvalError(cannotApply("multiply", args.car));
1611
2406
  }
2407
+ /**
2408
+ * Helper that accumulates the product starting from an initial number and the remaining argument list.
2409
+ * @param init the initial number
2410
+ * @param args the remaining numbers to multiply
2411
+ * @return the product of init and all remaining numbers
2412
+ */
1612
2413
  multiply_Number(init, args) {
1613
2414
  let result = init;
1614
2415
  let aCons = args;
@@ -1620,49 +2421,105 @@ var Applier = class Applier {
1620
2421
  }
1621
2422
  return result;
1622
2423
  }
2424
+ /**
2425
+ * Implementation of the Lisp `napier` function. Returns Napier's constant (e).
2426
+ * @return Math.E
2427
+ */
1623
2428
  napier() {
1624
2429
  return Math.E;
1625
2430
  }
2431
+ /**
2432
+ * Implementation of the Lisp `neq` / `~~` predicate. The negation of `eq`.
2433
+ * @param args the argument Cons forwarded to `eq_`
2434
+ * @return nil when eq, t otherwise
2435
+ */
1626
2436
  neq(args) {
1627
2437
  if (this.eq_(args) === InterpretedSymbol.of("t")) return Cons.nil;
1628
2438
  return InterpretedSymbol.of("t");
1629
2439
  }
2440
+ /**
2441
+ * Implementation of the Lisp `nequal` / `~=` predicate. The negation of `equal`.
2442
+ * @param args the argument Cons forwarded to `equal_`
2443
+ * @return nil when equal, t otherwise
2444
+ */
1630
2445
  nequal(args) {
1631
2446
  if (this.equal_(args) === InterpretedSymbol.of("t")) return Cons.nil;
1632
2447
  return InterpretedSymbol.of("t");
1633
2448
  }
2449
+ /**
2450
+ * Implementation of the Lisp `nth` function. Returns the nth element of a list.
2451
+ * @param args the argument Cons containing the index and the list
2452
+ * @return the element at the given index
2453
+ */
1634
2454
  nth(args) {
1635
2455
  if (!Number.isInteger(args.car)) return Cons.nil;
1636
2456
  const index = args.car;
1637
2457
  return args.nth(2).nth(index);
1638
2458
  }
2459
+ /**
2460
+ * Implementation of the Lisp `null` predicate. Returns t if the argument is nil, otherwise nil.
2461
+ * @param args the argument Cons containing the value to test
2462
+ * @return t if nil, nil otherwise
2463
+ */
1639
2464
  null_(args) {
1640
2465
  if (Cons.isNil(args.car)) return InterpretedSymbol.of("t");
1641
2466
  return Cons.nil;
1642
2467
  }
2468
+ /**
2469
+ * Implementation of the Lisp `numberp` / `doublep` predicate. Returns t if the argument is a number.
2470
+ * @param args the argument Cons containing the value to test
2471
+ * @return t if a number, nil otherwise
2472
+ */
1643
2473
  number_(args) {
1644
2474
  if (Cons.isNumber(args.car)) return InterpretedSymbol.of("t");
1645
2475
  return Cons.nil;
1646
2476
  }
2477
+ /**
2478
+ * Implementation of the Lisp `pi` function. Returns the mathematical constant pi.
2479
+ * @return Math.PI
2480
+ */
1647
2481
  pi() {
1648
2482
  return Math.PI;
1649
2483
  }
2484
+ /**
2485
+ * Implementation of the Lisp `random` function. Returns a pseudo-random number in [0, 1).
2486
+ * @return a random number in [0, 1)
2487
+ */
1650
2488
  random() {
1651
2489
  return Math.random();
1652
2490
  }
2491
+ /**
2492
+ * Implementation of the Lisp `round` function. Returns the given number rounded to the nearest integer.
2493
+ * @param args the argument Cons containing the target number
2494
+ * @return the rounded integer
2495
+ */
1653
2496
  round(args) {
1654
2497
  if (Cons.isNumber(args.car)) return Math.round(args.car);
1655
2498
  throw new ReferenceError(SELECT_PRINT_FUNCTION_NOT_DEFINED);
1656
2499
  }
2500
+ /**
2501
+ * Dispatches the call to either a built-in function or a user-defined function based on the symbol.
2502
+ * @param procedure the symbol naming the function
2503
+ * @param args the argument list
2504
+ * @return the result of the dispatched function
2505
+ */
1657
2506
  selectProcedure(procedure, args) {
1658
2507
  if (Applier.buildInFunctions.has(procedure)) return this.buildInFunction(procedure, args);
1659
2508
  if (this.environment.has(procedure)) return this.userFunction(procedure, args);
1660
2509
  throw new EvalError(noProcedure(procedure));
1661
2510
  }
2511
+ /**
2512
+ * Sets the current recursion depth.
2513
+ * @param aNumber the new depth value
2514
+ */
1662
2515
  setDepth(aNumber) {
1663
2516
  this.depth = aNumber;
1664
2517
  return null;
1665
2518
  }
2519
+ /**
2520
+ * Builds and returns the Lisp-name to method-name dispatch map.
2521
+ * @return a Map associating each Lisp function name (as an InterpretedSymbol) with the corresponding Applier method name
2522
+ */
1666
2523
  static setup() {
1667
2524
  try {
1668
2525
  return new Map([
@@ -1676,22 +2533,36 @@ var Applier = class Applier {
1676
2533
  ["cons", "cons"],
1677
2534
  ["consp", "cons_"],
1678
2535
  ["copy", "copy"],
2536
+ ["ceiling", "ceiling"],
1679
2537
  ["cos", "cos"],
1680
2538
  ["floatp", "float_"],
2539
+ ["floor", "floor"],
1681
2540
  ["divide", "divide"],
1682
2541
  ["doublep", "number_"],
1683
2542
  ["eq", "eq_"],
1684
2543
  ["equal", "equal_"],
2544
+ ["evenp", "even_"],
2545
+ ["every", "every"],
1685
2546
  ["exp", "exp"],
2547
+ ["expt", "expt"],
2548
+ ["find", "find"],
1686
2549
  ["format", "format"],
1687
2550
  ["gensym", "gensym"],
1688
2551
  ["integerp", "integer_"],
2552
+ ["concatenate", "concatenate"],
2553
+ ["count", "count"],
2554
+ ["elt", "elt"],
1689
2555
  ["last", "last"],
2556
+ ["length", "length"],
1690
2557
  ["list", "list"],
1691
2558
  ["listp", "list_"],
2559
+ ["mapcan", "mapcan"],
1692
2560
  ["mapcar", "mapcar"],
2561
+ ["max", "max"],
1693
2562
  ["member", "member"],
1694
2563
  ["memq", "memq"],
2564
+ ["min", "min"],
2565
+ ["minusp", "minus_"],
1695
2566
  ["mod", "mod"],
1696
2567
  ["multiply", "multiply"],
1697
2568
  ["napier", "napier"],
@@ -1700,15 +2571,29 @@ var Applier = class Applier {
1700
2571
  ["nth", "nth"],
1701
2572
  ["null", "null_"],
1702
2573
  ["numberp", "number_"],
2574
+ ["oddp", "odd_"],
1703
2575
  ["pi", "pi"],
2576
+ ["plusp", "plus_"],
1704
2577
  ["random", "random"],
2578
+ ["reduce", "reduce"],
1705
2579
  ["round", "round"],
1706
2580
  ["sin", "sin"],
2581
+ ["some", "some"],
2582
+ ["sort", "sort"],
1707
2583
  ["sqrt", "sqrt"],
1708
- ["subtract", "subtract"],
2584
+ ["string-downcase", "stringDowncase"],
2585
+ ["string-trim", "stringTrim"],
2586
+ ["string-upcase", "stringUpcase"],
1709
2587
  ["stringp", "string_"],
2588
+ ["subseq", "subseq"],
2589
+ ["substring", "substring"],
2590
+ ["subtract", "subtract"],
1710
2591
  ["symbolp", "symbol_"],
1711
2592
  ["tan", "tan"],
2593
+ ["truncate", "truncate"],
2594
+ ["zerop", "zero_"],
2595
+ ["1+", "oneplus"],
2596
+ ["1-", "oneminus"],
1712
2597
  ["+", "add"],
1713
2598
  ["-", "subtract"],
1714
2599
  ["*", "multiply"],
@@ -1727,26 +2612,57 @@ var Applier = class Applier {
1727
2612
  throw new Error("NullPointerException (Applier, initialize)");
1728
2613
  }
1729
2614
  }
2615
+ /**
2616
+ * Implementation of the Lisp `sin` function. Returns the sine of the given number.
2617
+ * @param args the argument Cons containing the angle in radians
2618
+ * @return the sine of the argument
2619
+ */
1730
2620
  sin(args) {
1731
2621
  if (Cons.isNumber(args.car)) return Math.sin(args.car);
1732
2622
  throw new ReferenceError(SELECT_PRINT_FUNCTION_NOT_DEFINED);
1733
2623
  }
2624
+ /**
2625
+ * Writes a single line of spy output (with indentation) to the given stream.
2626
+ * @param aStream the stream to write to, or null/string to fall back to process.stdout
2627
+ * @param line the line to write
2628
+ */
1734
2629
  spyPrint(aStream, line) {
1735
2630
  (aStream != null && typeof aStream === "object" && "write" in aStream ? aStream : process.stdout).write(this.indent() + line + "\n");
1736
2631
  return null;
1737
2632
  }
2633
+ /**
2634
+ * Implementation of the Lisp `sqrt` function. Returns the square root of the given number.
2635
+ * @param args the argument Cons containing the target number
2636
+ * @return the square root of the argument
2637
+ */
1738
2638
  sqrt(args) {
1739
2639
  if (Cons.isNumber(args.car)) return Math.sqrt(args.car);
1740
2640
  throw new ReferenceError(SELECT_PRINT_FUNCTION_NOT_DEFINED);
1741
2641
  }
2642
+ /**
2643
+ * Implementation of the Lisp `stringp` predicate. Returns t if the argument is a string.
2644
+ * @param args the argument Cons containing the value to test
2645
+ * @return t if a string, nil otherwise
2646
+ */
1742
2647
  string_(args) {
1743
2648
  if (Cons.isString(args.car)) return InterpretedSymbol.of("t");
1744
2649
  return Cons.nil;
1745
2650
  }
2651
+ /**
2652
+ * Implementation of the Lisp `-` / `subtract` function. Returns the difference of the given numbers.
2653
+ * @param args the argument Cons containing the numbers to subtract
2654
+ * @return the difference of the arguments
2655
+ */
1746
2656
  subtract(args) {
1747
2657
  if (Cons.isNumber(args.car)) return this.subtract_Number(args.car, args.cdr);
1748
2658
  throw new EvalError(cannotApply("subtract", args.car));
1749
2659
  }
2660
+ /**
2661
+ * Helper that accumulates the difference starting from an initial number and the remaining argument list.
2662
+ * @param init the initial number
2663
+ * @param args the remaining numbers to subtract
2664
+ * @return init with all remaining numbers subtracted
2665
+ */
1750
2666
  subtract_Number(init, args) {
1751
2667
  let result = init;
1752
2668
  let aCons = args;
@@ -1758,14 +2674,30 @@ var Applier = class Applier {
1758
2674
  }
1759
2675
  return result;
1760
2676
  }
2677
+ /**
2678
+ * Implementation of the Lisp `symbolp` predicate. Returns t if the argument is an interpreted symbol.
2679
+ * @param args the argument Cons containing the value to test
2680
+ * @return t if a symbol, nil otherwise
2681
+ */
1761
2682
  symbol_(args) {
1762
2683
  if (Cons.isSymbol(args.car)) return InterpretedSymbol.of("t");
1763
2684
  return Cons.nil;
1764
2685
  }
2686
+ /**
2687
+ * Implementation of the Lisp `tan` function. Returns the tangent of the given number.
2688
+ * @param args the argument Cons containing the angle in radians
2689
+ * @return the tangent of the argument
2690
+ */
1765
2691
  tan(args) {
1766
2692
  if (Cons.isNumber(args.car)) return Math.tan(args.car);
1767
2693
  throw new ReferenceError(SELECT_PRINT_FUNCTION_NOT_DEFINED);
1768
2694
  }
2695
+ /**
2696
+ * Invokes a user-defined function (lambda) bound in the environment under the given symbol.
2697
+ * @param procedure the symbol naming the user function
2698
+ * @param args the argument list
2699
+ * @return the result of evaluating the user function
2700
+ */
1769
2701
  userFunction(procedure, args) {
1770
2702
  if (this.isSpy(procedure)) {
1771
2703
  this.spyPrint(this.streamManager.spyStream(procedure), new Cons(procedure, args).toString());
@@ -1785,21 +2717,42 @@ var Applier = class Applier {
1785
2717
  //#region src/runtime/StreamManager/index.ts
1786
2718
  /**
1787
2719
  * @class
1788
- * @classdesc
2720
+ * @classdesc Manages output streams (stdout / stderr / spy / trace) used by the interpreter.
1789
2721
  * @author Keisuke Ikeda
1790
2722
  * @this {StreamManager}
1791
2723
  */
1792
- var StreamManager = class {
2724
+ var StreamManager = class extends Object {
2725
+ /**
2726
+ * Whether tracing is currently enabled.
2727
+ */
1793
2728
  isTrace = false;
2729
+ /**
2730
+ * Map from a named stream key (e.g. "default", "stdout", "stderr") to a WritableStream.
2731
+ */
1794
2732
  streamTable;
2733
+ /**
2734
+ * Map from a spied symbol to the stream key used for that symbol's output.
2735
+ */
1795
2736
  spyTable;
2737
+ /**
2738
+ * The stream that receives trace output while tracing is on.
2739
+ */
1796
2740
  traceStream;
2741
+ /**
2742
+ * Constructor.
2743
+ * @constructor
2744
+ */
1797
2745
  constructor() {
2746
+ super();
1798
2747
  this.streamTable = /* @__PURE__ */ new Map();
1799
2748
  this.spyTable = /* @__PURE__ */ new Map();
1800
2749
  this.traceStream = null;
1801
2750
  this.initialize();
1802
2751
  }
2752
+ /**
2753
+ * Returns the currently selected output stream (trace stream when tracing, otherwise the default).
2754
+ * @return the active stream, or null when none is available
2755
+ */
1803
2756
  getStream() {
1804
2757
  let aPrintStream = null;
1805
2758
  if (this.isTrace) return this.traceStream();
@@ -1809,6 +2762,7 @@ var StreamManager = class {
1809
2762
  }
1810
2763
  /**
1811
2764
  * Initializes the instance variables.
2765
+ * @return null
1812
2766
  */
1813
2767
  initialize() {
1814
2768
  this.streamTable.set("default", process.stdout);
@@ -1816,42 +2770,85 @@ var StreamManager = class {
1816
2770
  this.streamTable.set("stderr", process.stderr);
1817
2771
  return null;
1818
2772
  }
2773
+ /**
2774
+ * Returns whether the given symbol is being spied (or whether tracing is on, in which case every symbol is "spied").
2775
+ * @param aSymbol the symbol to check, or null
2776
+ * @return true if the symbol is spied or tracing is on
2777
+ */
1819
2778
  isSpy(aSymbol) {
1820
2779
  if (this.isTrace) return true;
1821
2780
  if (aSymbol != null && this.spyTable_().has(aSymbol)) return true;
1822
2781
  return false;
1823
2782
  }
2783
+ /**
2784
+ * Removes the given symbol from the spy table.
2785
+ * @param aSymbol the symbol to stop spying
2786
+ * @return null
2787
+ */
1824
2788
  noSpy(aSymbol) {
1825
2789
  if (this.spyTable_().has(aSymbol)) this.spyTable_().delete(aSymbol);
1826
2790
  return null;
1827
2791
  }
2792
+ /**
2793
+ * Turns tracing off and clears the spy table.
2794
+ * @return null
2795
+ */
1828
2796
  noTrace() {
1829
2797
  this.setIsTrace(false);
1830
2798
  this.spyTable.clear();
1831
2799
  return null;
1832
2800
  }
2801
+ /**
2802
+ * Sets the tracing flag.
2803
+ * @param aBoolean the new value for the tracing flag
2804
+ * @return null
2805
+ */
1833
2806
  setIsTrace(aBoolean) {
1834
2807
  this.isTrace = aBoolean;
1835
2808
  return null;
1836
2809
  }
2810
+ /**
2811
+ * Sets the trace output stream.
2812
+ * @param aStream the stream to send trace output to
2813
+ * @return null
2814
+ */
1837
2815
  setTraceStream(aStream) {
1838
2816
  this.traceStream = aStream;
1839
2817
  return null;
1840
2818
  }
2819
+ /**
2820
+ * Registers the given symbol as spied with the given stream key.
2821
+ * @param aSymbol the symbol to spy on
2822
+ * @param aString the stream key (e.g. "default")
2823
+ * @return null
2824
+ */
1841
2825
  spy(aSymbol, aString) {
1842
2826
  if (this.getStream() != null) this.spyTable_().set(aSymbol, aString);
1843
2827
  return null;
1844
2828
  }
2829
+ /**
2830
+ * Returns the stream (or stream-key string) used for the given symbol's spy output.
2831
+ * @param aSymbol the symbol whose spy stream is requested, or null
2832
+ * @return the trace stream, the registered key string, or throws if none is found
2833
+ */
1845
2834
  spyStream(aSymbol) {
1846
2835
  if (this.isTrace) return this.traceStream;
1847
2836
  if (aSymbol != null && this.spyTable_().has(aSymbol)) return this.spyTable_().get(aSymbol);
1848
2837
  throw new Error("Stream is not found.");
1849
2838
  }
2839
+ /**
2840
+ * Returns a copy of the spy table (defensive copy so callers do not mutate the internal map).
2841
+ * @return a new map containing the same entries as the internal spy table
2842
+ */
1850
2843
  spyTable_() {
1851
2844
  const aTable = /* @__PURE__ */ new Map();
1852
2845
  for (const [key, value] of this.spyTable) aTable.set(key, value);
1853
2846
  return aTable;
1854
2847
  }
2848
+ /**
2849
+ * Turns tracing on, routing trace output to the currently active stream.
2850
+ * @return null
2851
+ */
1855
2852
  trace() {
1856
2853
  this.noTrace();
1857
2854
  const aPrintStream = this.getStream();
@@ -1876,36 +2873,81 @@ const triggerGc = () => {
1876
2873
  * @author Keisuke Ikeda
1877
2874
  * @this {Evaluator}
1878
2875
  */
1879
- var Evaluator = class Evaluator {
2876
+ var Evaluator = class Evaluator extends Object {
2877
+ /**
2878
+ * Lisp-name to method-name dispatch map for special forms.
2879
+ */
1880
2880
  static buildInFunctions = Evaluator.setup();
2881
+ /**
2882
+ * The variable binding environment used during evaluation.
2883
+ */
1881
2884
  environment;
2885
+ /**
2886
+ * The stream manager used for trace and spy output.
2887
+ */
1882
2888
  streamManager;
2889
+ /**
2890
+ * The current call depth, used for indenting trace/spy output.
2891
+ */
1883
2892
  depth;
1884
- constructor(aTable, aStreamManager, aNumber) {
2893
+ /**
2894
+ * Registered plugins consulted by `eval` when no special form matches.
2895
+ */
2896
+ plugins;
2897
+ /**
2898
+ * Constructor.
2899
+ * @param aTable the variable binding environment
2900
+ * @param aStreamManager the stream manager for trace and spy output
2901
+ * @param aNumber the initial call depth
2902
+ * @param plugins the plugin chain consulted before falling through to Applier
2903
+ */
2904
+ constructor(aTable, aStreamManager, aNumber, plugins = []) {
2905
+ super();
1885
2906
  this.environment = aTable;
1886
2907
  this.streamManager = aStreamManager;
1887
2908
  this.depth = aNumber;
2909
+ this.plugins = plugins;
1888
2910
  }
2911
+ /**
2912
+ * Implementation of the Lisp `and` special form.
2913
+ * @param aCons the argument Cons containing the expressions to evaluate
2914
+ * @return nil if any expression evaluates to nil, otherwise t
2915
+ */
1889
2916
  and(aCons) {
1890
2917
  for (const each of aCons.loop()) {
1891
- const anObject = Evaluator.eval(each, this.environment, this.streamManager, this.depth);
2918
+ const anObject = Evaluator.eval(each, this.environment, this.streamManager, this.depth, this.plugins);
1892
2919
  if (Cons.isNil(anObject)) return Cons.nil;
1893
2920
  }
1894
2921
  return InterpretedSymbol.of("t");
1895
2922
  }
2923
+ /**
2924
+ * Implementation of the Lisp `apply` special form.
2925
+ * @param aCons the argument Cons containing the procedure and its argument list
2926
+ * @return the result of applying the procedure to the arguments
2927
+ */
1896
2928
  apply_lisp(aCons) {
1897
- const procedure = Evaluator.eval(aCons.car, this.environment, this.streamManager, this.depth);
1898
- const args = Evaluator.eval(aCons.nth(2), this.environment, this.streamManager, this.depth);
2929
+ const procedure = Evaluator.eval(aCons.car, this.environment, this.streamManager, this.depth, this.plugins);
2930
+ const args = Evaluator.eval(aCons.nth(2), this.environment, this.streamManager, this.depth, this.plugins);
1899
2931
  let aTable = this.environment;
1900
2932
  if (procedure instanceof Cons && procedure.last().car instanceof Table) aTable = procedure.last().car;
1901
- return Applier.apply(procedure, args, aTable, this.streamManager, this.depth);
2933
+ return Applier.apply(procedure, args, aTable, this.streamManager, this.depth, this.plugins);
1902
2934
  }
2935
+ /**
2936
+ * Implementation of the Lisp `bind` special form.
2937
+ * @param aCons the argument Cons whose car is the symbol to look up
2938
+ * @return the binding count for the symbol, or nil if unbound
2939
+ */
1903
2940
  bind(aCons) {
1904
2941
  if (Cons.isNotSymbol(aCons.car)) throw new EvalError(cannotApply("bind", aCons.car));
1905
2942
  const aSymbol = aCons.car;
1906
2943
  if (!this.environment.has(aSymbol)) return Cons.nil;
1907
2944
  return this.bindAUX(aSymbol);
1908
2945
  }
2946
+ /**
2947
+ * Counts the number of distinct bindings for the given symbol along the environment chain.
2948
+ * @param aSymbol the symbol whose bindings are inspected
2949
+ * @return the number of distinct bindings found
2950
+ */
1909
2951
  bindAUX(aSymbol) {
1910
2952
  let aTable = this.environment;
1911
2953
  let anObject = aTable.get(aSymbol);
@@ -1921,98 +2963,138 @@ var Evaluator = class Evaluator {
1921
2963
  }
1922
2964
  return count;
1923
2965
  }
2966
+ /**
2967
+ * Sequentially evaluates and binds each (symbol value) pair into the given table; used by let*.
2968
+ * @param parameters the Cons of (symbol value) pairs to bind
2969
+ * @param aTable the table into which the bindings are written
2970
+ */
1924
2971
  binding(parameters, aTable) {
1925
2972
  for (const each of parameters.loop()) {
1926
2973
  const theCons = each;
1927
2974
  if (Cons.isNotSymbol(theCons.car)) throw new EvalError(notSymbol(theCons.car));
1928
2975
  const key = theCons.car;
1929
- const value = Evaluator.eval(theCons.nth(2), aTable, this.streamManager, this.depth);
2976
+ const value = Evaluator.eval(theCons.nth(2), aTable, this.streamManager, this.depth, this.plugins);
1930
2977
  aTable.set(key, value);
1931
2978
  }
1932
2979
  return null;
1933
2980
  }
2981
+ /**
2982
+ * Evaluates all (symbol value) pairs first and then writes them into the given table in parallel; used by let.
2983
+ * @param parameters the Cons of (symbol value) pairs to bind
2984
+ * @param aTable the table into which the bindings are written
2985
+ */
1934
2986
  bindingParallel(parameters, aTable) {
1935
2987
  const theTable = /* @__PURE__ */ new Map();
1936
2988
  for (const each of parameters.loop()) {
1937
2989
  const theCons = each;
1938
2990
  if (Cons.isNotSymbol(theCons.car)) throw new EvalError(notSymbol(theCons.car));
1939
2991
  const key = theCons.car;
1940
- const value = Evaluator.eval(theCons.nth(2), aTable, this.streamManager, this.depth);
2992
+ const value = Evaluator.eval(theCons.nth(2), aTable, this.streamManager, this.depth, this.plugins);
1941
2993
  theTable.set(key, value);
1942
2994
  }
1943
2995
  for (const [key, value] of theTable) aTable.set(key, value);
1944
2996
  return null;
1945
2997
  }
2998
+ /**
2999
+ * Implementation of the Lisp `cond` special form.
3000
+ * @param aCons the argument Cons of (test consequent...) clauses
3001
+ * @return the result of the first clause whose test is non-nil, or nil
3002
+ */
1946
3003
  cond(aCons) {
1947
3004
  if (Cons.isNil(aCons)) return Cons.nil;
1948
3005
  const consCell = aCons;
1949
3006
  const clause = consCell.car;
1950
- let anObject = Evaluator.eval(clause.car, this.environment, this.streamManager, this.depth);
3007
+ let anObject = Evaluator.eval(clause.car, this.environment, this.streamManager, this.depth, this.plugins);
1951
3008
  if (Cons.isNil(anObject)) return this.cond(consCell.cdr);
1952
3009
  const consequent = clause.cdr;
1953
- for (const each of consequent.loop()) anObject = Evaluator.eval(each, this.environment, this.streamManager, this.depth);
3010
+ for (const each of consequent.loop()) anObject = Evaluator.eval(each, this.environment, this.streamManager, this.depth, this.plugins);
1954
3011
  return anObject;
1955
3012
  }
3013
+ /**
3014
+ * Implementation of the Lisp `defun` special form.
3015
+ * @param aCons the argument Cons containing the function name, parameter list, and body
3016
+ * @return the function name symbol
3017
+ */
1956
3018
  defun(aCons) {
1957
3019
  const variable = aCons.car;
1958
3020
  let lambda = aCons.cdr;
1959
3021
  lambda = aCons.length() === 2 ? lambda.car : new Cons(InterpretedSymbol.of("lambda"), lambda);
1960
- lambda = Evaluator.eval(lambda, new Table(this.environment), this.streamManager, this.depth);
3022
+ lambda = Evaluator.eval(lambda, new Table(this.environment), this.streamManager, this.depth, this.plugins);
1961
3023
  this.environment.set(variable, lambda);
1962
3024
  return variable;
1963
3025
  }
3026
+ /**
3027
+ * Implementation of the Lisp `do` special form (parallel binding update).
3028
+ * @param aCons the argument Cons containing bindings, termination clause, and body
3029
+ * @return the value of the termination clause's result form
3030
+ */
1964
3031
  do_(aCons) {
1965
3032
  const parameters = aCons.car;
1966
3033
  const bool = aCons.nth(2);
1967
3034
  const expressions = aCons.cdr.cdr;
1968
3035
  this.bindingParallel(parameters, this.environment);
1969
3036
  if (Cons.isNil(bool)) bool.setCar(Cons.nil);
1970
- while (Cons.isNil(Evaluator.eval(bool.car, this.environment, this.streamManager, this.depth))) {
3037
+ while (Cons.isNil(Evaluator.eval(bool.car, this.environment, this.streamManager, this.depth, this.plugins))) {
1971
3038
  const theTable = /* @__PURE__ */ new Map();
1972
- for (const each of expressions.loop()) Evaluator.eval(each, this.environment, this.streamManager, this.depth);
3039
+ for (const each of expressions.loop()) Evaluator.eval(each, this.environment, this.streamManager, this.depth, this.plugins);
1973
3040
  for (const each of parameters.loop()) {
1974
3041
  const theCons = each;
1975
3042
  if (Cons.isNotSymbol(theCons.car)) throw new EvalError(notSymbol(theCons.car));
1976
3043
  const key = theCons.car;
1977
3044
  if (Cons.isNotNil(theCons.nth(3))) {
1978
- const value = Evaluator.eval(theCons.nth(3), this.environment, this.streamManager, this.depth);
3045
+ const value = Evaluator.eval(theCons.nth(3), this.environment, this.streamManager, this.depth, this.plugins);
1979
3046
  theTable.set(key, value);
1980
3047
  }
1981
3048
  }
1982
3049
  for (const [key, value] of theTable) this.environment.set(key, value);
1983
3050
  }
1984
- return Evaluator.eval(bool.nth(2), this.environment, this.streamManager, this.depth);
3051
+ return Evaluator.eval(bool.nth(2), this.environment, this.streamManager, this.depth, this.plugins);
1985
3052
  }
3053
+ /**
3054
+ * Implementation of the Lisp `dolist` special form.
3055
+ * @param aCons the argument Cons containing the binding clause and body
3056
+ * @return the value of the result form
3057
+ */
1986
3058
  doList(aCons) {
1987
3059
  const parameter = aCons.car;
1988
3060
  const theCons = aCons.cdr;
1989
- const args = Evaluator.eval(parameter.nth(2), this.environment, this.streamManager, this.depth);
3061
+ const args = Evaluator.eval(parameter.nth(2), this.environment, this.streamManager, this.depth, this.plugins);
1990
3062
  for (const element of args.loop()) {
1991
3063
  this.environment.set(parameter.car, element);
1992
- for (const each of theCons.loop()) Evaluator.eval(each, this.environment, this.streamManager, this.depth);
3064
+ for (const each of theCons.loop()) Evaluator.eval(each, this.environment, this.streamManager, this.depth, this.plugins);
1993
3065
  }
1994
- return Evaluator.eval(parameter.nth(3), this.environment, this.streamManager, this.depth);
3066
+ return Evaluator.eval(parameter.nth(3), this.environment, this.streamManager, this.depth, this.plugins);
1995
3067
  }
3068
+ /**
3069
+ * Implementation of the Lisp `do*` special form (sequential binding update).
3070
+ * @param aCons the argument Cons containing bindings, termination clause, and body
3071
+ * @return the value of the termination clause's result form
3072
+ */
1996
3073
  doStar(aCons) {
1997
3074
  const parameters = aCons.car;
1998
3075
  const bool = aCons.nth(2);
1999
3076
  const expressions = aCons.cdr.cdr;
2000
3077
  this.binding(parameters, this.environment);
2001
3078
  if (Cons.isNil(bool)) bool.setCar(Cons.nil);
2002
- while (Cons.isNil(Evaluator.eval(bool.car, this.environment, this.streamManager, this.depth))) {
2003
- for (const each of expressions.loop()) Evaluator.eval(each, this.environment, this.streamManager, this.depth);
3079
+ while (Cons.isNil(Evaluator.eval(bool.car, this.environment, this.streamManager, this.depth, this.plugins))) {
3080
+ for (const each of expressions.loop()) Evaluator.eval(each, this.environment, this.streamManager, this.depth, this.plugins);
2004
3081
  for (const each of parameters.loop()) {
2005
3082
  const theCons = each;
2006
3083
  if (Cons.isNotSymbol(theCons.car)) throw new EvalError(notSymbol(theCons.car));
2007
3084
  const key = theCons.car;
2008
3085
  if (Cons.isNotNil(theCons.nth(3))) {
2009
- const value = Evaluator.eval(theCons.nth(3), this.environment, this.streamManager, this.depth);
3086
+ const value = Evaluator.eval(theCons.nth(3), this.environment, this.streamManager, this.depth, this.plugins);
2010
3087
  this.environment.set(key, value);
2011
3088
  }
2012
3089
  }
2013
3090
  }
2014
- return Evaluator.eval(bool.nth(2), this.environment, this.streamManager, this.depth);
3091
+ return Evaluator.eval(bool.nth(2), this.environment, this.streamManager, this.depth, this.plugins);
2015
3092
  }
3093
+ /**
3094
+ * Evaluates a procedure call by delegating to the Applier after evaluating each argument.
3095
+ * @param form the call form whose car is the procedure and whose cdr is the argument list
3096
+ * @return the result of applying the procedure
3097
+ */
2016
3098
  entrustApplier(form) {
2017
3099
  const aCons = form.cdr;
2018
3100
  let args = new Cons(Cons.nil, Cons.nil);
@@ -2025,25 +3107,84 @@ var Evaluator = class Evaluator {
2025
3107
  }
2026
3108
  for (const each of aCons.loop()) {
2027
3109
  if (each instanceof Table) break;
2028
- args.add(Evaluator.eval(each, this.environment, this.streamManager, this.depth));
3110
+ args.add(Evaluator.eval(each, this.environment, this.streamManager, this.depth, this.plugins));
2029
3111
  }
2030
3112
  if (this.isSpy(aSymbol)) this.setDepth(this.depth - 1);
2031
3113
  args = args.cdr;
2032
- return Applier.apply(procedure, args, this.environment, this.streamManager, this.depth);
3114
+ return Applier.apply(procedure, args, this.environment, this.streamManager, this.depth, this.plugins);
2033
3115
  }
2034
- static eval(form, environment, aStreamManager = new StreamManager(), depth = 1) {
2035
- return new Evaluator(environment, aStreamManager, depth).eval(form);
3116
+ /**
3117
+ * Evaluates the given form in the given environment.
3118
+ * @param form the form to evaluate
3119
+ * @param environment the variable binding environment
3120
+ * @param aStreamManager the stream manager for trace and spy output
3121
+ * @param depth the current call depth
3122
+ * @param plugins the plugin chain consulted before falling through to Applier
3123
+ * @return the evaluation result
3124
+ */
3125
+ static eval(form, environment, aStreamManager = new StreamManager(), depth = 1, plugins = []) {
3126
+ return new Evaluator(environment, aStreamManager, depth, plugins).eval(form);
2036
3127
  }
3128
+ /**
3129
+ * Evaluates the given form using this Evaluator's environment.
3130
+ * @param form the form to evaluate
3131
+ * @return the evaluation result
3132
+ */
2037
3133
  eval(form) {
2038
3134
  if (Cons.isSymbol(form)) return this.evaluateSymbol(form);
2039
3135
  if (Cons.isNil(form) || Cons.isNotList(form)) return form;
2040
3136
  const formCons = form;
2041
3137
  if (Cons.isSymbol(formCons.car) && Evaluator.buildInFunctions.has(formCons.car)) return this.specialForm(formCons);
3138
+ if (Cons.isSymbol(formCons.car) && this.plugins.length > 0) {
3139
+ const symbol = formCons.car;
3140
+ const plugin = this.plugins.find((p) => p.has(symbol));
3141
+ if (plugin !== void 0) return this.entrustPlugin(plugin, formCons);
3142
+ }
2042
3143
  return this.entrustApplier(formCons);
2043
3144
  }
3145
+ /**
3146
+ * Evaluates the argument list (the same way `entrustApplier` does), then
3147
+ * delegates the call to the matched plugin with a context that allows
3148
+ * recursive evaluation.
3149
+ * @param plugin the plugin that claimed the call symbol
3150
+ * @param form the call form whose car is the symbol and whose cdr is the argument list
3151
+ * @return the result returned by the plugin
3152
+ */
3153
+ entrustPlugin(plugin, form) {
3154
+ const aCons = form.cdr;
3155
+ let args = new Cons(Cons.nil, Cons.nil);
3156
+ const symbol = form.car;
3157
+ if (this.isSpy(symbol)) {
3158
+ this.spyPrint(this.streamManager.spyStream(symbol), form.toString());
3159
+ this.setDepth(this.depth + 1);
3160
+ }
3161
+ for (const each of aCons.loop()) {
3162
+ if (each instanceof Table) break;
3163
+ args.add(Evaluator.eval(each, this.environment, this.streamManager, this.depth, this.plugins));
3164
+ }
3165
+ if (this.isSpy(symbol)) this.setDepth(this.depth - 1);
3166
+ args = args.cdr;
3167
+ const ctx = {
3168
+ environment: this.environment,
3169
+ streamManager: this.streamManager,
3170
+ depth: this.depth,
3171
+ eval: (subForm) => Evaluator.eval(subForm, this.environment, this.streamManager, this.depth, this.plugins)
3172
+ };
3173
+ return plugin.apply(symbol, args, ctx);
3174
+ }
3175
+ /**
3176
+ * Implementation of the Lisp `eval` special form.
3177
+ * @param aCons the argument Cons whose car is the form to evaluate twice
3178
+ * @return the result of evaluating the form
3179
+ */
2044
3180
  eval_lisp(aCons) {
2045
- return Evaluator.eval(Evaluator.eval(aCons.car, this.environment, this.streamManager, this.depth), this.environment, this.streamManager, this.depth);
3181
+ return Evaluator.eval(Evaluator.eval(aCons.car, this.environment, this.streamManager, this.depth, this.plugins), this.environment, this.streamManager, this.depth, this.plugins);
2046
3182
  }
3183
+ /**
3184
+ * Resolves the value bound to the given symbol in the current environment.
3185
+ * @param aSymbol the symbol to resolve
3186
+ * @return the value bound to the symbol
3187
+ */
2047
3188
  evaluateSymbol(aSymbol) {
2048
3189
  if (!this.environment.has(aSymbol)) throw new EvalError(noBinding(aSymbol));
2049
3190
  if (this.isSpy(aSymbol)) {
@@ -2058,10 +3199,17 @@ var Evaluator = class Evaluator {
2058
3199
  }
2059
3200
  return answer;
2060
3201
  }
3202
+ /**
3203
+ * Implementation of the Lisp `exit` special form; terminates the REPL by throwing an ExitError.
3204
+ */
2061
3205
  exit() {
2062
3206
  console.log("Bye!");
2063
3207
  throw new ExitError();
2064
3208
  }
3209
+ /**
3210
+ * Implementation of the Lisp `gc` special form; triggers garbage collection and returns memory usage.
3211
+ * @return an association list of memory usage statistics
3212
+ */
2065
3213
  gc() {
2066
3214
  triggerGc();
2067
3215
  const usage = process.memoryUsage();
@@ -2075,110 +3223,198 @@ var Evaluator = class Evaluator {
2075
3223
  for (const entry of entries) result = new Cons(entry, result);
2076
3224
  return result;
2077
3225
  }
3226
+ /**
3227
+ * Implementation of the Lisp `if` special form.
3228
+ * @param aCons the argument Cons containing the test, then-form, and else-form
3229
+ * @return the result of evaluating the selected branch
3230
+ */
2078
3231
  if_(aCons) {
2079
- const bool = Evaluator.eval(aCons.car, this.environment, this.streamManager, this.depth);
3232
+ const bool = Evaluator.eval(aCons.car, this.environment, this.streamManager, this.depth, this.plugins);
2080
3233
  const anObject = Cons.isNil(bool) ? aCons.nth(3) : aCons.nth(2);
2081
- return Evaluator.eval(anObject, this.environment, this.streamManager, this.depth);
3234
+ return Evaluator.eval(anObject, this.environment, this.streamManager, this.depth, this.plugins);
2082
3235
  }
3236
+ /**
3237
+ * Returns the indentation string used for trace and spy output at the current depth.
3238
+ * @return the indentation string
3239
+ */
2083
3240
  indent() {
2084
3241
  let index = 0;
2085
3242
  let aString = "";
2086
3243
  while (index++ < this.depth) aString += "| ";
2087
3244
  return aString;
2088
3245
  }
3246
+ /**
3247
+ * Returns whether the given symbol is currently being spied on.
3248
+ * @param aSymbol the symbol to check
3249
+ * @return a boolean
3250
+ */
2089
3251
  isSpy(aSymbol) {
2090
3252
  if (aSymbol == null) return false;
2091
3253
  return this.streamManager.isSpy(aSymbol);
2092
3254
  }
3255
+ /**
3256
+ * Implementation of the Lisp `lambda` special form; captures the current environment as a closure.
3257
+ * @param args the argument Cons containing the parameter list and body
3258
+ * @return a lambda form with the captured environment appended
3259
+ */
2093
3260
  lambda(args) {
2094
3261
  const aCons = Cons.cloneValue(args);
2095
3262
  aCons.cdr.setCdr(new Cons(this.environment, Cons.nil));
2096
3263
  return new Cons(InterpretedSymbol.of("lambda"), aCons);
2097
3264
  }
3265
+ /**
3266
+ * Implementation of the Lisp `let` special form (parallel binding).
3267
+ * @param aCons the argument Cons containing bindings and body
3268
+ * @return the value of the last body form
3269
+ */
2098
3270
  let(aCons) {
2099
3271
  const aTable = new Table(this.environment);
2100
3272
  const parameters = aCons.car;
2101
3273
  const forms = aCons.cdr;
2102
3274
  let anObject = Cons.nil;
2103
3275
  this.bindingParallel(parameters, aTable);
2104
- for (const each of forms.loop()) anObject = Evaluator.eval(each, aTable, this.streamManager, this.depth);
3276
+ for (const each of forms.loop()) anObject = Evaluator.eval(each, aTable, this.streamManager, this.depth, this.plugins);
2105
3277
  return anObject;
2106
3278
  }
3279
+ /**
3280
+ * Implementation of the Lisp `let*` special form (sequential binding).
3281
+ * @param aCons the argument Cons containing bindings and body
3282
+ * @return the value of the last body form
3283
+ */
2107
3284
  letStar(aCons) {
2108
3285
  const aTable = new Table(this.environment);
2109
3286
  const parameters = aCons.car;
2110
3287
  const forms = aCons.cdr;
2111
3288
  let anObject = Cons.nil;
2112
3289
  this.binding(parameters, aTable);
2113
- for (const each of forms.loop()) anObject = Evaluator.eval(each, aTable, this.streamManager, this.depth);
3290
+ for (const each of forms.loop()) anObject = Evaluator.eval(each, aTable, this.streamManager, this.depth, this.plugins);
2114
3291
  return anObject;
2115
3292
  }
3293
+ /**
3294
+ * Implementation of the Lisp `not` special form.
3295
+ * @param aCons the argument Cons whose car is the expression to negate
3296
+ * @return t if the expression evaluates to nil, otherwise nil
3297
+ */
2116
3298
  not(aCons) {
2117
- if (Cons.isNil(Evaluator.eval(aCons.car, this.environment, this.streamManager, this.depth))) return InterpretedSymbol.of("t");
3299
+ if (Cons.isNil(Evaluator.eval(aCons.car, this.environment, this.streamManager, this.depth, this.plugins))) return InterpretedSymbol.of("t");
2118
3300
  return Cons.nil;
2119
3301
  }
3302
+ /**
3303
+ * Implementation of the Lisp `notrace` special form; disables tracing.
3304
+ * @return the symbol t
3305
+ */
2120
3306
  notrace() {
2121
3307
  this.streamManager.noTrace();
2122
3308
  return InterpretedSymbol.of("t");
2123
3309
  }
3310
+ /**
3311
+ * Implementation of the Lisp `or` special form.
3312
+ * @param aCons the argument Cons containing the expressions to evaluate
3313
+ * @return t if any expression evaluates to non-nil, otherwise nil
3314
+ */
2124
3315
  or(aCons) {
2125
3316
  for (const each of aCons.loop()) {
2126
- const anObject = Evaluator.eval(each, this.environment, this.streamManager, this.depth);
3317
+ const anObject = Evaluator.eval(each, this.environment, this.streamManager, this.depth, this.plugins);
2127
3318
  if (Cons.isNotNil(anObject)) return InterpretedSymbol.of("t");
2128
3319
  }
2129
3320
  return Cons.nil;
2130
3321
  }
3322
+ /**
3323
+ * Implementation of the Lisp `pop` special form.
3324
+ * @param aCons the argument Cons whose car is the symbol bound to a list
3325
+ * @return the popped element, or nil if the binding is not a Cons
3326
+ */
2131
3327
  pop_(aCons) {
2132
3328
  if (Cons.isNotSymbol(aCons.car)) throw new EvalError(argumentNotSymbol(1));
2133
3329
  const aSymbol = aCons.car;
2134
- const anObject = Evaluator.eval(aSymbol, this.environment, this.streamManager, this.depth);
3330
+ const anObject = Evaluator.eval(aSymbol, this.environment, this.streamManager, this.depth, this.plugins);
2135
3331
  if (Cons.isNotCons(anObject)) return Cons.nil;
2136
3332
  const consObject = anObject;
2137
3333
  this.environment.setIfExist(aSymbol, consObject.cdr);
2138
3334
  return consObject.car;
2139
3335
  }
3336
+ /**
3337
+ * Implementation of the Lisp `progn` special form.
3338
+ * @param aCons the argument Cons containing the body expressions
3339
+ * @return the value of the last body form, or nil if there are none
3340
+ */
2140
3341
  progn(aCons) {
2141
3342
  let anObject = Cons.nil;
2142
- for (const each of aCons.loop()) anObject = Evaluator.eval(each, this.environment, this.streamManager, this.depth);
3343
+ for (const each of aCons.loop()) anObject = Evaluator.eval(each, this.environment, this.streamManager, this.depth, this.plugins);
2143
3344
  return anObject;
2144
3345
  }
3346
+ /**
3347
+ * Implementation of the Lisp `princ` special form; writes the evaluated argument without a trailing newline.
3348
+ * @param aCons the argument Cons whose car is the expression to print
3349
+ * @return the printed value
3350
+ */
2145
3351
  princ(aCons) {
2146
- const anObject = Evaluator.eval(aCons.car, this.environment, this.streamManager, this.depth);
3352
+ const anObject = Evaluator.eval(aCons.car, this.environment, this.streamManager, this.depth, this.plugins);
2147
3353
  process.stdout.write(String(anObject));
2148
3354
  return anObject;
2149
3355
  }
3356
+ /**
3357
+ * Implementation of the Lisp `print` special form; writes the evaluated argument followed by a newline.
3358
+ * @param aCons the argument Cons whose car is the expression to print
3359
+ * @return the printed value
3360
+ */
2150
3361
  print(aCons) {
2151
- const anObject = Evaluator.eval(aCons.car, this.environment, this.streamManager, this.depth);
3362
+ const anObject = Evaluator.eval(aCons.car, this.environment, this.streamManager, this.depth, this.plugins);
2152
3363
  process.stdout.write(String(anObject) + "\n");
2153
3364
  return anObject;
2154
3365
  }
3366
+ /**
3367
+ * Implementation of the Lisp `push` special form.
3368
+ * @param aCons the argument Cons containing the value to push and the target symbol
3369
+ * @return the new Cons stored in the symbol
3370
+ */
2155
3371
  push_(aCons) {
2156
- let anObject = Evaluator.eval(aCons.car, this.environment, this.streamManager, this.depth);
3372
+ let anObject = Evaluator.eval(aCons.car, this.environment, this.streamManager, this.depth, this.plugins);
2157
3373
  if (Cons.isNotSymbol(aCons.nth(2))) throw new EvalError(argumentNotSymbol(2));
2158
3374
  const aSymbol = aCons.nth(2);
2159
- anObject = new Cons(anObject, Evaluator.eval(aSymbol, this.environment, this.streamManager, this.depth));
3375
+ anObject = new Cons(anObject, Evaluator.eval(aSymbol, this.environment, this.streamManager, this.depth, this.plugins));
2160
3376
  this.environment.setIfExist(aSymbol, anObject);
2161
3377
  return anObject;
2162
3378
  }
3379
+ /**
3380
+ * Implementation of the Lisp `quote` special form.
3381
+ * @param aCons the argument Cons whose car is the form to return unevaluated
3382
+ * @return the quoted form
3383
+ */
2163
3384
  quote(aCons) {
2164
3385
  return aCons.car;
2165
3386
  }
3387
+ /**
3388
+ * Implementation of the Lisp `rplaca` special form; destructively replaces the car of a Cons.
3389
+ * @param args the argument Cons containing the target Cons expression and the new car value
3390
+ * @return the modified Cons
3391
+ */
2166
3392
  rplaca(args) {
2167
- let anObject = Evaluator.eval(args.car, this.environment, this.streamManager, this.depth);
3393
+ let anObject = Evaluator.eval(args.car, this.environment, this.streamManager, this.depth, this.plugins);
2168
3394
  if (Cons.isNotCons(anObject)) throw new EvalError(cannotApply("set-car!", anObject));
2169
3395
  const aCons = anObject;
2170
- anObject = Evaluator.eval(args.nth(2), this.environment, this.streamManager, this.depth);
3396
+ anObject = Evaluator.eval(args.nth(2), this.environment, this.streamManager, this.depth, this.plugins);
2171
3397
  aCons.setCar(anObject);
2172
- return Evaluator.eval(args.car, this.environment, this.streamManager, this.depth);
3398
+ return Evaluator.eval(args.car, this.environment, this.streamManager, this.depth, this.plugins);
2173
3399
  }
3400
+ /**
3401
+ * Implementation of the Lisp `rplacd` special form; destructively replaces the cdr of a Cons.
3402
+ * @param args the argument Cons containing the target Cons expression and the new cdr value
3403
+ * @return the modified Cons
3404
+ */
2174
3405
  rplacd(args) {
2175
- let anObject = Evaluator.eval(args.car, this.environment, this.streamManager, this.depth);
3406
+ let anObject = Evaluator.eval(args.car, this.environment, this.streamManager, this.depth, this.plugins);
2176
3407
  if (Cons.isNotCons(anObject)) throw new EvalError(cannotApply("set-cdr!", anObject));
2177
3408
  const aCons = anObject;
2178
- anObject = Evaluator.eval(args.nth(2), this.environment, this.streamManager, this.depth);
3409
+ anObject = Evaluator.eval(args.nth(2), this.environment, this.streamManager, this.depth, this.plugins);
2179
3410
  aCons.setCdr(anObject);
2180
- return Evaluator.eval(args.car, this.environment, this.streamManager, this.depth);
3411
+ return Evaluator.eval(args.car, this.environment, this.streamManager, this.depth, this.plugins);
2181
3412
  }
3413
+ /**
3414
+ * Implementation of the Lisp `setq` special form; assigns values in the local environment.
3415
+ * @param args the argument Cons containing alternating (symbol value) pairs
3416
+ * @return the last assigned value
3417
+ */
2182
3418
  setq(args) {
2183
3419
  let anObject = Cons.nil;
2184
3420
  const anIterator = args.loop();
@@ -2186,26 +3422,39 @@ var Evaluator = class Evaluator {
2186
3422
  if (!Cons.isSymbol(args.nth(1))) throw new EvalError(notSymbol(args.car));
2187
3423
  const key = anIterator.next();
2188
3424
  if (!anIterator.hasNext()) throw new EvalError(SIZES_DO_NOT_MATCH);
2189
- anObject = Evaluator.eval(anIterator.next(), this.environment, this.streamManager, this.depth);
3425
+ anObject = Evaluator.eval(anIterator.next(), this.environment, this.streamManager, this.depth, this.plugins);
2190
3426
  this.environment.set(key, anObject);
2191
3427
  }
2192
3428
  return anObject;
2193
3429
  }
3430
+ /**
3431
+ * Implementation of the Lisp `set-allq` special form; assigns values in the binding's owning scope.
3432
+ * @param args the argument Cons containing alternating (symbol value) pairs
3433
+ * @return the last assigned value
3434
+ */
2194
3435
  set_allq(args) {
2195
3436
  let anObject = Cons.nil;
2196
3437
  const anIterator = args.loop();
2197
3438
  while (anIterator.hasNext()) {
2198
3439
  if (!Cons.isSymbol(args.nth(1))) throw new EvalError(notSymbol(args.car));
2199
3440
  const key = anIterator.next();
2200
- anObject = Evaluator.eval(anIterator.next(), this.environment, this.streamManager, this.depth);
3441
+ anObject = Evaluator.eval(anIterator.next(), this.environment, this.streamManager, this.depth, this.plugins);
2201
3442
  this.environment.setIfExist(key, anObject);
2202
3443
  }
2203
3444
  return anObject;
2204
3445
  }
3446
+ /**
3447
+ * Sets the current call depth used for trace and spy indentation.
3448
+ * @param aNumber the new depth
3449
+ */
2205
3450
  setDepth(aNumber) {
2206
3451
  this.depth = aNumber;
2207
3452
  return null;
2208
3453
  }
3454
+ /**
3455
+ * Builds and returns the Lisp-name to method-name dispatch map for special forms.
3456
+ * @return the dispatch map
3457
+ */
2209
3458
  static setup() {
2210
3459
  try {
2211
3460
  return new Map([
@@ -2247,6 +3496,11 @@ var Evaluator = class Evaluator {
2247
3496
  throw new Error("NullPointerException (Evaluator, initialize)");
2248
3497
  }
2249
3498
  }
3499
+ /**
3500
+ * Dispatches a special-form call to the corresponding method via the build-in dispatch map.
3501
+ * @param form the form whose car is the special-form symbol
3502
+ * @return the result of the special-form method
3503
+ */
2250
3504
  specialForm(form) {
2251
3505
  const aSymbol = form.car;
2252
3506
  if (this.isSpy(aSymbol)) {
@@ -2265,37 +3519,65 @@ var Evaluator = class Evaluator {
2265
3519
  }
2266
3520
  return answer;
2267
3521
  }
3522
+ /**
3523
+ * Writes a trace/spy line to the given stream (or stdout) with the current indentation.
3524
+ * @param aStream the destination stream, or null/string to fall back to stdout
3525
+ * @param line the line to write
3526
+ */
2268
3527
  spyPrint(aStream, line) {
2269
3528
  (aStream != null && typeof aStream === "object" && "write" in aStream ? aStream : process.stdout).write(this.indent() + line + "\n");
2270
3529
  return null;
2271
3530
  }
3531
+ /**
3532
+ * Implementation of the Lisp `terpri` special form; writes a newline to stdout.
3533
+ * @return the symbol t
3534
+ */
2272
3535
  terpri() {
2273
3536
  process.stdout.write("\n");
2274
3537
  return InterpretedSymbol.of("t");
2275
3538
  }
3539
+ /**
3540
+ * Implementation of the Lisp `time` special form; measures evaluation time in milliseconds.
3541
+ * @param aCons the argument Cons whose car is the form to time
3542
+ * @return the elapsed time in milliseconds
3543
+ */
2276
3544
  time(aCons) {
2277
3545
  const start = process.hrtime();
2278
- Evaluator.eval(aCons.car, this.environment, this.streamManager, this.depth);
3546
+ Evaluator.eval(aCons.car, this.environment, this.streamManager, this.depth, this.plugins);
2279
3547
  return process.hrtime(start)[1] / 1e6;
2280
3548
  }
3549
+ /**
3550
+ * Implementation of the Lisp `trace` special form; enables tracing.
3551
+ * @return the symbol t
3552
+ */
2281
3553
  trace() {
2282
3554
  this.streamManager.trace();
2283
3555
  return InterpretedSymbol.of("t");
2284
3556
  }
3557
+ /**
3558
+ * Implementation of the Lisp `unless` special form.
3559
+ * @param aCons the argument Cons containing the test and body
3560
+ * @return the value of the last body form if the test is nil, otherwise nil
3561
+ */
2285
3562
  unless(aCons) {
2286
3563
  let anObject = Cons.nil;
2287
3564
  const theCons = aCons.cdr;
2288
- const flag = Evaluator.eval(aCons.car, this.environment, this.streamManager, this.depth);
3565
+ const flag = Evaluator.eval(aCons.car, this.environment, this.streamManager, this.depth, this.plugins);
2289
3566
  if (Cons.isNotNil(flag)) return Cons.nil;
2290
- for (const each of theCons.loop()) anObject = Evaluator.eval(each, this.environment, this.streamManager, this.depth);
3567
+ for (const each of theCons.loop()) anObject = Evaluator.eval(each, this.environment, this.streamManager, this.depth, this.plugins);
2291
3568
  return anObject;
2292
3569
  }
3570
+ /**
3571
+ * Implementation of the Lisp `when` special form.
3572
+ * @param aCons the argument Cons containing the test and body
3573
+ * @return the value of the last body form if the test is non-nil, otherwise nil
3574
+ */
2293
3575
  when(aCons) {
2294
3576
  let anObject = Cons.nil;
2295
3577
  const theCons = aCons.cdr;
2296
- const flag = Evaluator.eval(aCons.car, this.environment, this.streamManager, this.depth);
3578
+ const flag = Evaluator.eval(aCons.car, this.environment, this.streamManager, this.depth, this.plugins);
2297
3579
  if (Cons.isNil(flag)) return Cons.nil;
2298
- for (const each of theCons.loop()) anObject = Evaluator.eval(each, this.environment, this.streamManager, this.depth);
3580
+ for (const each of theCons.loop()) anObject = Evaluator.eval(each, this.environment, this.streamManager, this.depth, this.plugins);
2299
3581
  return anObject;
2300
3582
  }
2301
3583
  };
@@ -2307,23 +3589,54 @@ var Evaluator = class Evaluator {
2307
3589
  * @author Keisuke Ikeda
2308
3590
  * @this {LispInterpreter}
2309
3591
  */
2310
- var LispInterpreter = class {
3592
+ var LispInterpreter = class extends Object {
3593
+ /**
3594
+ * The root (top-level) environment table, pre-populated with built-in symbols.
3595
+ */
2311
3596
  root;
3597
+ /**
3598
+ * The stream manager that owns the interpreter's output / spy streams.
3599
+ */
2312
3600
  streamManager;
3601
+ /**
3602
+ * Registered plugins consulted by the evaluator on every call. See `use`.
3603
+ */
3604
+ plugins;
3605
+ /**
3606
+ * Constructor.
3607
+ * @constructor
3608
+ */
2313
3609
  constructor() {
3610
+ super();
2314
3611
  this.root = this.initializeTable();
2315
3612
  this.streamManager = new StreamManager();
3613
+ this.plugins = [];
3614
+ }
3615
+ /**
3616
+ * Registers a plugin. Subsequent `eval` calls will consult the plugin chain
3617
+ * (in registration order, first match wins) when no special form matches a
3618
+ * symbol, before falling through to the Applier built-ins.
3619
+ * @param plugin the plugin to register
3620
+ * @return this interpreter, for chaining
3621
+ */
3622
+ use(plugin) {
3623
+ this.plugins.push(plugin);
3624
+ return this;
2316
3625
  }
2317
3626
  /**
2318
3627
  * Evaluates the given expression and returns the result. Throws `ParseError`,
2319
3628
  * `EvalError`, or `ExitError` on failure; library users are expected to catch
2320
3629
  * these (see the `KeiLispError` base class for the parse/eval family).
3630
+ * @param aCons the expression to evaluate
3631
+ * @return the evaluation result
2321
3632
  */
2322
3633
  eval(aCons) {
2323
- return Evaluator.eval(aCons, this.root, this.streamManager);
3634
+ return Evaluator.eval(aCons, this.root, this.streamManager, 1, this.plugins);
2324
3635
  }
2325
3636
  /**
2326
3637
  * Parses the source string, evaluates every expression it contains, and returns the results as an array.
3638
+ * @param source the Lisp source string
3639
+ * @return the array of evaluation results, one per top-level expression
2327
3640
  */
2328
3641
  evalAll(source) {
2329
3642
  const ast = this.parse(source);
@@ -2333,6 +3646,8 @@ var LispInterpreter = class {
2333
3646
  }
2334
3647
  /**
2335
3648
  * Parses and evaluates the source string and returns the value of the last expression.
3649
+ * @param source the Lisp source string
3650
+ * @return the value of the last expression, or `Cons.nil` for empty input
2336
3651
  */
2337
3652
  evalString(source) {
2338
3653
  const results = this.evalAll(source);
@@ -2343,12 +3658,16 @@ var LispInterpreter = class {
2343
3658
  * it. The result is always a `Cons` (possibly `Cons.nil` for empty input)
2344
3659
  * because the source is wrapped in an outer list before parsing. Throws
2345
3660
  * `ParseError` if the source cannot be parsed.
3661
+ * @param aString the Lisp source string
3662
+ * @return a Cons containing the parsed top-level expressions
2346
3663
  */
2347
3664
  parse(aString) {
2348
3665
  return Cons.parse("(" + aString + "\n);");
2349
3666
  }
2350
3667
  /**
2351
3668
  * Sets the given environment as the root of the environment chain.
3669
+ * @param environment the environment table to install as root
3670
+ * @return null
2352
3671
  */
2353
3672
  setRoot(environment) {
2354
3673
  if (environment instanceof Table) {
@@ -2358,13 +3677,14 @@ var LispInterpreter = class {
2358
3677
  return null;
2359
3678
  }
2360
3679
  /**
2361
- * Initializes the environment table.
3680
+ * Builds the root environment table by pre-registering every built-in symbol and the small set of bootstrap lambdas (append / butlast / nthcdr / reverse).
3681
+ * @return the freshly initialized root environment
2362
3682
  */
2363
3683
  initializeTable() {
2364
3684
  const aList = [];
2365
3685
  const aTable = new Table();
2366
3686
  aTable.setRoot(true);
2367
- aList.push("abs", "add", "and", "apply", "assoc", "atom", "bind", "car", "cdr", "characterp", "cond", "cons", "consp", "copy", "cos", "floatp", "defun", "divide", "do", "do*", "dolist", "doublep", "eq", "equal", "exit", "exp", "gc", "gensym", "if", "integerp", "lambda", "let", "let*", "last", "list", "listp", "mapcar", "member", "memq", "mod", "multiply", "napier", "neq", "nequal", "not", "notrace", "nth", "null", "numberp", "or", "pi", "pop", "progn", "printc", "print", "push", "quote", "random", "reload", "round", "rplaca", "rplacd", "setq", "set-allq", "sin", "sqrt", "subtract", "stringp", "symbolp", "tan", "terpri", "time", "trace", "unless", "when", "+", "-", "*", "/", "//", "=", "==", "~=", "~~", "<", "<=", ">", ">=");
3687
+ aList.push("abs", "add", "and", "apply", "assoc", "atom", "bind", "car", "cdr", "characterp", "cond", "ceiling", "concatenate", "cons", "consp", "copy", "cos", "count", "floatp", "floor", "defun", "divide", "do", "do*", "dolist", "doublep", "elt", "eq", "equal", "eval", "evenp", "every", "exit", "exp", "expt", "find", "format", "gc", "gensym", "if", "integerp", "lambda", "let", "let*", "last", "length", "list", "listp", "mapcan", "mapcar", "max", "member", "memq", "min", "minusp", "mod", "multiply", "napier", "neq", "nequal", "not", "notrace", "nth", "null", "numberp", "oddp", "or", "pi", "plusp", "pop", "princ", "print", "progn", "push", "quote", "random", "reduce", "round", "rplaca", "rplacd", "setq", "set-allq", "sin", "some", "sort", "sqrt", "string-downcase", "string-trim", "string-upcase", "stringp", "subseq", "substring", "subtract", "symbolp", "tan", "terpri", "time", "trace", "truncate", "unless", "when", "zerop", "1+", "1-", "+", "-", "*", "/", "//", "=", "==", "~=", "~~", "<", "<=", ">", ">=");
2368
3688
  for (const each of aList) {
2369
3689
  const aSymbol = InterpretedSymbol.of(each);
2370
3690
  aTable.set(aSymbol, aSymbol);
@@ -2379,10 +3699,6 @@ var LispInterpreter = class {
2379
3699
  aCons = Cons.parse(aString);
2380
3700
  aCons.last().setCdr(new Cons(aTable, Cons.nil));
2381
3701
  aTable.set(InterpretedSymbol.of("butlast"), aCons);
2382
- aString = "(lambda (l) (cond ((null (listp l)) nil) ((null l) 0) (t (+ 1 (length (cdr l))))))";
2383
- aCons = Cons.parse(aString);
2384
- aCons.last().setCdr(new Cons(aTable, Cons.nil));
2385
- aTable.set(InterpretedSymbol.of("length"), aCons);
2386
3702
  aString = "(lambda (n l) (cond ((> n (length l)) nil) ((= 0 n) l) (t (nthcdr (- n 1) (cdr l)))))";
2387
3703
  aCons = Cons.parse(aString);
2388
3704
  aCons.last().setCdr(new Cons(aTable, Cons.nil));
@@ -2404,10 +3720,22 @@ const require$1 = (0, node_module.createRequire)(require("url").pathToFileURL(__
2404
3720
  * @author Keisuke Ikeda
2405
3721
  * @this {Repl}
2406
3722
  */
2407
- var Repl = class {
3723
+ var Repl = class extends Object {
3724
+ /**
3725
+ * The underlying interpreter used to parse and evaluate user input.
3726
+ */
2408
3727
  interpreter;
3728
+ /**
3729
+ * The Node.js readline interface that supplies prompt I/O.
3730
+ */
2409
3731
  rl;
3732
+ /**
3733
+ * Constructor.
3734
+ * @constructor
3735
+ * @param interpreter the interpreter to evaluate input against (defaults to a fresh one)
3736
+ */
2410
3737
  constructor(interpreter = new LispInterpreter()) {
3738
+ super();
2411
3739
  this.interpreter = interpreter;
2412
3740
  const readline = require$1("node:readline");
2413
3741
  this.rl = readline.createInterface({
@@ -2418,6 +3746,7 @@ var Repl = class {
2418
3746
  }
2419
3747
  /**
2420
3748
  * Starts the REPL loop.
3749
+ * @return void
2421
3750
  */
2422
3751
  run() {
2423
3752
  let aString = "";