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