kei-lisp 2.1.0 → 2.3.0

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