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/README.md +4 -2
- package/dist/cli.cjs +1706 -170
- package/dist/index.cjs +1708 -169
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +656 -19
- package/dist/index.d.ts +656 -19
- package/dist/index.js +1706 -170
- package/dist/index.js.map +1 -1
- package/package.json +5 -4
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
|
-
*
|
|
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
|
-
*
|
|
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
|
-
*
|
|
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(
|
|
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, "
|
|
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
|
-
|
|
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
|
-
|
|
1135
|
-
|
|
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
|
-
|
|
1455
|
-
|
|
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
|
-
|
|
1458
|
-
|
|
1459
|
-
|
|
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
|
-
|
|
1462
|
-
|
|
1463
|
-
|
|
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
|
-
|
|
1466
|
-
|
|
1467
|
-
|
|
1468
|
-
|
|
1469
|
-
|
|
1470
|
-
|
|
1471
|
-
|
|
1472
|
-
|
|
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
|
-
|
|
1480
|
-
|
|
1481
|
-
|
|
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
|
-
|
|
1484
|
-
|
|
1485
|
-
|
|
1486
|
-
|
|
1487
|
-
|
|
1488
|
-
|
|
1489
|
-
|
|
1490
|
-
|
|
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
|
-
|
|
1498
|
-
|
|
1499
|
-
|
|
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
|
-
|
|
1502
|
-
|
|
1503
|
-
|
|
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
|
-
|
|
1506
|
-
|
|
1507
|
-
|
|
1508
|
-
|
|
1509
|
-
|
|
1510
|
-
|
|
1511
|
-
|
|
1512
|
-
|
|
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
|
-
|
|
1529
|
-
|
|
1530
|
-
|
|
1531
|
-
|
|
1532
|
-
|
|
1533
|
-
|
|
1534
|
-
|
|
1535
|
-
|
|
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
|
-
|
|
1544
|
-
|
|
1545
|
-
|
|
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
|
-
["
|
|
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
|
-
|
|
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
|
-
|
|
2007
|
-
|
|
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
|
-
*
|
|
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", "
|
|
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
|