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