kei-lisp 2.1.0 → 2.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.cjs +1415 -86
- package/dist/index.cjs +1417 -85
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +560 -19
- package/dist/index.d.ts +560 -19
- package/dist/index.js +1415 -86
- package/dist/index.js.map +1 -1
- package/package.json +2 -1
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);
|
|
@@ -557,6 +672,7 @@ var Parser = class Parser {
|
|
|
557
672
|
}
|
|
558
673
|
/**
|
|
559
674
|
* Returns the token number for a quote or for a 0-origin String-type (pseudo-Character); invoked from NextState.
|
|
675
|
+
* @return the next state number
|
|
560
676
|
*/
|
|
561
677
|
quoteOrChar() {
|
|
562
678
|
let aNumber = this.peekChar() === "\\" ? 3 : 2;
|
|
@@ -565,12 +681,14 @@ var Parser = class Parser {
|
|
|
565
681
|
}
|
|
566
682
|
/**
|
|
567
683
|
* Detects a right parenthesis (')', ']', '}') and returns the result; invoked from NextState.
|
|
684
|
+
* @return true when the next character is any right paren
|
|
568
685
|
*/
|
|
569
686
|
rightParen() {
|
|
570
687
|
return this.peekChar() === ")" || this.peekChar() === "]" || this.peekChar() === "}";
|
|
571
688
|
}
|
|
572
689
|
/**
|
|
573
690
|
* Returns the token number for a sign symbol ('+', '-'); invoked from NextState.
|
|
691
|
+
* @return the next state number (7, or 0 when the literal is complete)
|
|
574
692
|
*/
|
|
575
693
|
sign() {
|
|
576
694
|
this.concat();
|
|
@@ -582,6 +700,7 @@ var Parser = class Parser {
|
|
|
582
700
|
}
|
|
583
701
|
/**
|
|
584
702
|
* Skips whitespace; invoked from NextState.
|
|
703
|
+
* @return null
|
|
585
704
|
*/
|
|
586
705
|
skippingSpaces() {
|
|
587
706
|
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 +708,7 @@ var Parser = class Parser {
|
|
|
589
708
|
}
|
|
590
709
|
/**
|
|
591
710
|
* Returns the token number for an InterpretedSymbol; invoked from NextState.
|
|
711
|
+
* @return the next state number (8, or 0 when the literal is complete)
|
|
592
712
|
*/
|
|
593
713
|
symbolToken() {
|
|
594
714
|
this.concat();
|
|
@@ -600,6 +720,7 @@ var Parser = class Parser {
|
|
|
600
720
|
}
|
|
601
721
|
/**
|
|
602
722
|
* Converts the token into a 0-origin String-type (pseudo-Character); invoked from NextState.
|
|
723
|
+
* @return null
|
|
603
724
|
*/
|
|
604
725
|
tokenToCharacter() {
|
|
605
726
|
this.token = this.tokenString.charAt(0);
|
|
@@ -607,6 +728,7 @@ var Parser = class Parser {
|
|
|
607
728
|
}
|
|
608
729
|
/**
|
|
609
730
|
* Converts the token into a Number-type (double-precision floating point: pseudo-Double); invoked from NextState.
|
|
731
|
+
* @return null
|
|
610
732
|
*/
|
|
611
733
|
tokenToDouble() {
|
|
612
734
|
this.token = Number(this.tokenString);
|
|
@@ -614,6 +736,7 @@ var Parser = class Parser {
|
|
|
614
736
|
}
|
|
615
737
|
/**
|
|
616
738
|
* Converts the token into a Number-type (double-precision floating point: pseudo-Double); invoked from NextState.
|
|
739
|
+
* @return null
|
|
617
740
|
*/
|
|
618
741
|
tokenToDoubleAUX() {
|
|
619
742
|
this.concat();
|
|
@@ -622,6 +745,7 @@ var Parser = class Parser {
|
|
|
622
745
|
}
|
|
623
746
|
/**
|
|
624
747
|
* Converts the token into a Number-type (integer: pseudo-Integer); invoked from NextState.
|
|
748
|
+
* @return null
|
|
625
749
|
*/
|
|
626
750
|
tokenToInteger() {
|
|
627
751
|
if (this.tokenString[0] === "+") this.tokenString = this.tokenString.slice(1);
|
|
@@ -630,6 +754,7 @@ var Parser = class Parser {
|
|
|
630
754
|
}
|
|
631
755
|
/**
|
|
632
756
|
* Converts the token into a String-type; invoked from NextState.
|
|
757
|
+
* @return null
|
|
633
758
|
*/
|
|
634
759
|
tokenToString() {
|
|
635
760
|
this.token = this.tokenString;
|
|
@@ -637,6 +762,7 @@ var Parser = class Parser {
|
|
|
637
762
|
}
|
|
638
763
|
/**
|
|
639
764
|
* Converts the token into an InterpretedSymbol; invoked from NextState.
|
|
765
|
+
* @return null
|
|
640
766
|
*/
|
|
641
767
|
tokenToSymbol() {
|
|
642
768
|
this.token = InterpretedSymbol.of(this.tokenString);
|
|
@@ -645,6 +771,7 @@ var Parser = class Parser {
|
|
|
645
771
|
}
|
|
646
772
|
/**
|
|
647
773
|
* Builds the lookup table that maps character codes to their corresponding methods (tokens).
|
|
774
|
+
* @return null
|
|
648
775
|
*/
|
|
649
776
|
initializeStateTransitionTable() {
|
|
650
777
|
let aTable = /* @__PURE__ */ new Map();
|
|
@@ -695,6 +822,8 @@ var Parser = class Parser {
|
|
|
695
822
|
aTable = /* @__PURE__ */ new Map();
|
|
696
823
|
for (const index of IntStream.rangeClosed(9, 13)) aTable.set(String(index), this.nextState(0, "tokenToInteger"));
|
|
697
824
|
aTable.set(String(32), this.nextState(0, "tokenToInteger"));
|
|
825
|
+
aTable.set(String(43), this.nextState(8, "symbolToken"));
|
|
826
|
+
aTable.set(String(45), this.nextState(8, "symbolToken"));
|
|
698
827
|
aTable.set(String(46), this.nextState(3, "doubleToken"));
|
|
699
828
|
for (const index of IntStream.rangeClosed(48, 57)) aTable.set(String(index), this.nextState(2, "integerToken"));
|
|
700
829
|
aTable.set(String(69), this.nextState(4, "concat"));
|
|
@@ -798,9 +927,18 @@ var Parser = class Parser {
|
|
|
798
927
|
* @author Keisuke Ikeda
|
|
799
928
|
* @this {Cons}
|
|
800
929
|
*/
|
|
801
|
-
var Cons = class Cons {
|
|
930
|
+
var Cons = class Cons extends Object {
|
|
931
|
+
/**
|
|
932
|
+
* The shared empty-list sentinel. A Cons whose car and cdr are both itself, representing Lisp `nil`.
|
|
933
|
+
*/
|
|
802
934
|
static nil = new Cons();
|
|
935
|
+
/**
|
|
936
|
+
* The head element of this Cons cell.
|
|
937
|
+
*/
|
|
803
938
|
car;
|
|
939
|
+
/**
|
|
940
|
+
* The tail of this Cons cell (typically another Cons or nil).
|
|
941
|
+
*/
|
|
804
942
|
cdr;
|
|
805
943
|
/**
|
|
806
944
|
* Constructor.
|
|
@@ -809,6 +947,7 @@ var Cons = class Cons {
|
|
|
809
947
|
* @param cdr the cdr; defaults to nil when no argument is given.
|
|
810
948
|
*/
|
|
811
949
|
constructor(car = Cons.nil, cdr = Cons.nil) {
|
|
950
|
+
super();
|
|
812
951
|
this.car = car;
|
|
813
952
|
this.cdr = cdr;
|
|
814
953
|
}
|
|
@@ -1094,6 +1233,7 @@ const SIZE_DO_NOT_MATCH = "size do not match.";
|
|
|
1094
1233
|
//#endregion
|
|
1095
1234
|
//#region src/runtime/Applier/index.ts
|
|
1096
1235
|
const SELECT_PRINT_FUNCTION_NOT_DEFINED = "selectPrintFunction is not defined";
|
|
1236
|
+
const toCodePoints = (s) => [...s];
|
|
1097
1237
|
/**
|
|
1098
1238
|
* Class that mimics Lisp's universal function Apply.
|
|
1099
1239
|
* @class
|
|
@@ -1101,25 +1241,67 @@ const SELECT_PRINT_FUNCTION_NOT_DEFINED = "selectPrintFunction is not defined";
|
|
|
1101
1241
|
* @author Keisuke Ikeda
|
|
1102
1242
|
* @this {Applier}
|
|
1103
1243
|
*/
|
|
1104
|
-
var Applier = class Applier {
|
|
1244
|
+
var Applier = class Applier extends Object {
|
|
1245
|
+
/**
|
|
1246
|
+
* Dispatch map from a Lisp function name (InterpretedSymbol) to the name of the Applier method that implements it.
|
|
1247
|
+
*/
|
|
1105
1248
|
static buildInFunctions = Applier.setup();
|
|
1106
1249
|
static #generateNumber = 0;
|
|
1250
|
+
/**
|
|
1251
|
+
* The environment (variable bindings) used while applying procedures.
|
|
1252
|
+
*/
|
|
1107
1253
|
environment;
|
|
1254
|
+
/**
|
|
1255
|
+
* The stream manager used for I/O and spy output.
|
|
1256
|
+
*/
|
|
1108
1257
|
streamManager;
|
|
1258
|
+
/**
|
|
1259
|
+
* The current recursion depth, used for spy indentation.
|
|
1260
|
+
*/
|
|
1109
1261
|
depth;
|
|
1110
|
-
|
|
1262
|
+
/**
|
|
1263
|
+
* Registered plugins forwarded back into Evaluator on recursive evaluation (e.g. `entrustEvaluator`).
|
|
1264
|
+
*/
|
|
1265
|
+
plugins;
|
|
1266
|
+
/**
|
|
1267
|
+
* Constructor.
|
|
1268
|
+
* @constructor
|
|
1269
|
+
* @param aTable the parent environment to extend
|
|
1270
|
+
* @param aStreamManager the stream manager for I/O
|
|
1271
|
+
* @param aNumber the initial recursion depth
|
|
1272
|
+
* @param plugins the plugin chain to forward when re-entering the Evaluator
|
|
1273
|
+
*/
|
|
1274
|
+
constructor(aTable, aStreamManager, aNumber, plugins = []) {
|
|
1275
|
+
super();
|
|
1111
1276
|
this.environment = new Table(aTable);
|
|
1112
1277
|
this.streamManager = aStreamManager;
|
|
1113
1278
|
this.depth = aNumber;
|
|
1279
|
+
this.plugins = plugins;
|
|
1114
1280
|
}
|
|
1281
|
+
/**
|
|
1282
|
+
* Implementation of the Lisp `abs` function. Returns the absolute value of the given number.
|
|
1283
|
+
* @param args the argument Cons containing the target number
|
|
1284
|
+
* @return the absolute value
|
|
1285
|
+
*/
|
|
1115
1286
|
abs(args) {
|
|
1116
1287
|
if (Cons.isNumber(args.car)) return Math.abs(args.car);
|
|
1117
1288
|
throw new EvalError(cannotApply("abs", args.car));
|
|
1118
1289
|
}
|
|
1290
|
+
/**
|
|
1291
|
+
* Implementation of the Lisp `+` / `add` function. Returns the sum of the given numbers.
|
|
1292
|
+
* @param args the argument Cons containing the numbers to add
|
|
1293
|
+
* @return the sum of the arguments
|
|
1294
|
+
*/
|
|
1119
1295
|
add(args) {
|
|
1120
1296
|
if (Cons.isNumber(args.car)) return this.add_Number(args.car, args.cdr);
|
|
1121
1297
|
throw new EvalError(cannotApply("add", args.car));
|
|
1122
1298
|
}
|
|
1299
|
+
/**
|
|
1300
|
+
* Helper that accumulates the sum starting from an initial number and the remaining argument list.
|
|
1301
|
+
* @param init the initial number
|
|
1302
|
+
* @param args the remaining numbers to add
|
|
1303
|
+
* @return the sum of init and all remaining numbers
|
|
1304
|
+
*/
|
|
1123
1305
|
add_Number(init, args) {
|
|
1124
1306
|
let result = init;
|
|
1125
1307
|
let aCons = args;
|
|
@@ -1131,13 +1313,34 @@ var Applier = class Applier {
|
|
|
1131
1313
|
}
|
|
1132
1314
|
return result;
|
|
1133
1315
|
}
|
|
1134
|
-
|
|
1135
|
-
|
|
1316
|
+
/**
|
|
1317
|
+
* Static entry point that instantiates an Applier and applies the given procedure to the arguments.
|
|
1318
|
+
* @param procedure the procedure to apply (a symbol or a lambda Cons)
|
|
1319
|
+
* @param args the argument list
|
|
1320
|
+
* @param environment the environment to use
|
|
1321
|
+
* @param aStreamManager the stream manager for I/O
|
|
1322
|
+
* @param depth the current recursion depth
|
|
1323
|
+
* @param plugins the plugin chain to forward when re-entering the Evaluator
|
|
1324
|
+
* @return the result of applying the procedure
|
|
1325
|
+
*/
|
|
1326
|
+
static apply(procedure, args, environment, aStreamManager, depth, plugins = []) {
|
|
1327
|
+
return new Applier(environment, aStreamManager, depth, plugins).apply(procedure, args);
|
|
1136
1328
|
}
|
|
1329
|
+
/**
|
|
1330
|
+
* Applies the given procedure to the given arguments.
|
|
1331
|
+
* @param procedure the procedure to apply (a symbol or a lambda Cons)
|
|
1332
|
+
* @param args the argument list
|
|
1333
|
+
* @return the result of applying the procedure
|
|
1334
|
+
*/
|
|
1137
1335
|
apply(procedure, args) {
|
|
1138
1336
|
if (Cons.isSymbol(procedure)) return this.selectProcedure(procedure, args);
|
|
1139
1337
|
return this.entrustEvaluator(procedure, args);
|
|
1140
1338
|
}
|
|
1339
|
+
/**
|
|
1340
|
+
* Implementation of the Lisp `assoc` function. Looks up an association in an association list.
|
|
1341
|
+
* @param args the argument Cons containing the key and the association list
|
|
1342
|
+
* @return the matching pair, or nil if no match was found
|
|
1343
|
+
*/
|
|
1141
1344
|
assoc(args) {
|
|
1142
1345
|
const target = args.car;
|
|
1143
1346
|
if (Cons.isNotCons(args.nth(2))) return Cons.nil;
|
|
@@ -1150,10 +1353,20 @@ var Applier = class Applier {
|
|
|
1150
1353
|
}
|
|
1151
1354
|
return Cons.nil;
|
|
1152
1355
|
}
|
|
1356
|
+
/**
|
|
1357
|
+
* Implementation of the Lisp `atom` predicate. Returns t if the argument is an atom, otherwise nil.
|
|
1358
|
+
* @param args the argument Cons containing the value to test
|
|
1359
|
+
* @return t if atom, nil otherwise
|
|
1360
|
+
*/
|
|
1153
1361
|
atom_(args) {
|
|
1154
1362
|
if (Cons.isAtom(args.car)) return InterpretedSymbol.of("t");
|
|
1155
1363
|
return Cons.nil;
|
|
1156
1364
|
}
|
|
1365
|
+
/**
|
|
1366
|
+
* Binds the given parameter symbols to the corresponding argument values in this environment.
|
|
1367
|
+
* @param parameter the parameter list (a Cons of symbols, possibly dotted)
|
|
1368
|
+
* @param args the argument list to bind to the parameters
|
|
1369
|
+
*/
|
|
1157
1370
|
binding(parameter, args) {
|
|
1158
1371
|
if (Cons.isNil(parameter)) return null;
|
|
1159
1372
|
let aCons = parameter;
|
|
@@ -1176,6 +1389,12 @@ var Applier = class Applier {
|
|
|
1176
1389
|
else if (Cons.isNotNil(aCons.cdr)) throw new ReferenceError("aList is not defined");
|
|
1177
1390
|
return null;
|
|
1178
1391
|
}
|
|
1392
|
+
/**
|
|
1393
|
+
* Invokes the built-in method associated with the given procedure symbol.
|
|
1394
|
+
* @param procedure the symbol naming the built-in function
|
|
1395
|
+
* @param args the argument list
|
|
1396
|
+
* @return the result of the built-in function
|
|
1397
|
+
*/
|
|
1179
1398
|
buildInFunction(procedure, args) {
|
|
1180
1399
|
if (this.isSpy(procedure)) {
|
|
1181
1400
|
this.spyPrint(this.streamManager.spyStream(procedure), new Cons(procedure, args).toString());
|
|
@@ -1192,34 +1411,80 @@ var Applier = class Applier {
|
|
|
1192
1411
|
}
|
|
1193
1412
|
return answer;
|
|
1194
1413
|
}
|
|
1414
|
+
/**
|
|
1415
|
+
* Implementation of the Lisp `car` function. Returns the car of the given Cons.
|
|
1416
|
+
* @param args the argument Cons containing the target Cons
|
|
1417
|
+
* @return the car of the target
|
|
1418
|
+
*/
|
|
1195
1419
|
car(args) {
|
|
1196
1420
|
return args.car.car;
|
|
1197
1421
|
}
|
|
1422
|
+
/**
|
|
1423
|
+
* Implementation of the Lisp `cdr` function. Returns the cdr of the given Cons.
|
|
1424
|
+
* @param args the argument Cons containing the target Cons
|
|
1425
|
+
* @return the cdr of the target
|
|
1426
|
+
*/
|
|
1198
1427
|
cdr(args) {
|
|
1199
1428
|
return args.car.cdr;
|
|
1200
1429
|
}
|
|
1430
|
+
/**
|
|
1431
|
+
* Implementation of the Lisp `characterp` predicate. Returns t if the argument is a single-character string.
|
|
1432
|
+
* @param args the argument Cons containing the value to test
|
|
1433
|
+
* @return t if a character, nil otherwise
|
|
1434
|
+
*/
|
|
1201
1435
|
character_(args) {
|
|
1202
1436
|
if (Cons.isString(args.car) && args.car.length === 1) return InterpretedSymbol.of("t");
|
|
1203
1437
|
return Cons.nil;
|
|
1204
1438
|
}
|
|
1439
|
+
/**
|
|
1440
|
+
* Implementation of the Lisp `cons` function. Constructs a new Cons from the given car and cdr.
|
|
1441
|
+
* @param args the argument Cons containing the car and cdr
|
|
1442
|
+
* @return the newly constructed Cons
|
|
1443
|
+
*/
|
|
1205
1444
|
cons(args) {
|
|
1206
1445
|
return new Cons(args.car, args.nth(2));
|
|
1207
1446
|
}
|
|
1447
|
+
/**
|
|
1448
|
+
* Implementation of the Lisp `consp` predicate. Returns t if the argument is a Cons, otherwise nil.
|
|
1449
|
+
* @param args the argument Cons containing the value to test
|
|
1450
|
+
* @return t if a Cons, nil otherwise
|
|
1451
|
+
*/
|
|
1208
1452
|
cons_(args) {
|
|
1209
1453
|
if (Cons.isCons(args.car)) return InterpretedSymbol.of("t");
|
|
1210
1454
|
return Cons.nil;
|
|
1211
1455
|
}
|
|
1456
|
+
/**
|
|
1457
|
+
* Implementation of the Lisp `copy` function. Returns a deep clone of the given value.
|
|
1458
|
+
* @param args the argument Cons containing the value to copy
|
|
1459
|
+
* @return the cloned value
|
|
1460
|
+
*/
|
|
1212
1461
|
copy(args) {
|
|
1213
1462
|
return Cons.cloneValue(args.car);
|
|
1214
1463
|
}
|
|
1464
|
+
/**
|
|
1465
|
+
* Implementation of the Lisp `cos` function. Returns the cosine of the given number.
|
|
1466
|
+
* @param args the argument Cons containing the angle in radians
|
|
1467
|
+
* @return the cosine of the argument
|
|
1468
|
+
*/
|
|
1215
1469
|
cos(args) {
|
|
1216
1470
|
if (Cons.isNumber(args.car)) return Math.cos(args.car);
|
|
1217
1471
|
throw new ReferenceError(SELECT_PRINT_FUNCTION_NOT_DEFINED);
|
|
1218
1472
|
}
|
|
1473
|
+
/**
|
|
1474
|
+
* Implementation of the Lisp `/` / `divide` function. Returns the quotient of the given numbers.
|
|
1475
|
+
* @param args the argument Cons containing the numbers to divide
|
|
1476
|
+
* @return the quotient of the arguments
|
|
1477
|
+
*/
|
|
1219
1478
|
divide(args) {
|
|
1220
1479
|
if (Cons.isNumber(args.car)) return this.divide_Number(args.car, args.cdr);
|
|
1221
1480
|
throw new EvalError(cannotApply("divide", args.car));
|
|
1222
1481
|
}
|
|
1482
|
+
/**
|
|
1483
|
+
* Helper that accumulates the quotient starting from an initial number and the remaining argument list.
|
|
1484
|
+
* @param init the initial number (numerator)
|
|
1485
|
+
* @param args the remaining numbers to divide by
|
|
1486
|
+
* @return the quotient of init divided by all remaining numbers
|
|
1487
|
+
*/
|
|
1223
1488
|
divide_Number(init, args) {
|
|
1224
1489
|
let result = init;
|
|
1225
1490
|
let aCons = args;
|
|
@@ -1231,6 +1496,12 @@ var Applier = class Applier {
|
|
|
1231
1496
|
}
|
|
1232
1497
|
return result;
|
|
1233
1498
|
}
|
|
1499
|
+
/**
|
|
1500
|
+
* Delegates evaluation of a lambda body to the Evaluator after binding its parameters.
|
|
1501
|
+
* @param procedure the lambda Cons to apply
|
|
1502
|
+
* @param args the argument list to bind to the lambda's parameters
|
|
1503
|
+
* @return the result of evaluating the lambda body
|
|
1504
|
+
*/
|
|
1234
1505
|
entrustEvaluator(procedure, args) {
|
|
1235
1506
|
let anObject = Cons.nil;
|
|
1236
1507
|
let aCons = procedure.cdr;
|
|
@@ -1238,14 +1509,24 @@ var Applier = class Applier {
|
|
|
1238
1509
|
aCons = aCons.cdr;
|
|
1239
1510
|
for (const each of aCons.loop()) {
|
|
1240
1511
|
if (each instanceof Table) break;
|
|
1241
|
-
anObject = Evaluator.eval(each, this.environment, this.streamManager, this.depth);
|
|
1512
|
+
anObject = Evaluator.eval(each, this.environment, this.streamManager, this.depth, this.plugins);
|
|
1242
1513
|
}
|
|
1243
1514
|
return anObject;
|
|
1244
1515
|
}
|
|
1516
|
+
/**
|
|
1517
|
+
* Implementation of the Lisp `eq` predicate. Returns t when both arguments are identical (JS `===`).
|
|
1518
|
+
* @param args the argument Cons containing the two values to compare
|
|
1519
|
+
* @return t when identical, nil otherwise
|
|
1520
|
+
*/
|
|
1245
1521
|
eq_(args) {
|
|
1246
1522
|
if (args.car === args.nth(2)) return InterpretedSymbol.of("t");
|
|
1247
1523
|
return Cons.nil;
|
|
1248
1524
|
}
|
|
1525
|
+
/**
|
|
1526
|
+
* Implementation of the Lisp `equal` / `=` predicate. Returns t when both arguments are structurally equal.
|
|
1527
|
+
* @param args the argument Cons containing the two values to compare
|
|
1528
|
+
* @return t when equal, nil otherwise
|
|
1529
|
+
*/
|
|
1249
1530
|
equal_(args) {
|
|
1250
1531
|
const first = args.car;
|
|
1251
1532
|
const second = args.nth(2);
|
|
@@ -1256,10 +1537,20 @@ var Applier = class Applier {
|
|
|
1256
1537
|
}
|
|
1257
1538
|
return Cons.nil;
|
|
1258
1539
|
}
|
|
1540
|
+
/**
|
|
1541
|
+
* Implementation of the Lisp `exp` function. Returns e raised to the given power.
|
|
1542
|
+
* @param args the argument Cons containing the exponent
|
|
1543
|
+
* @return e raised to the given power
|
|
1544
|
+
*/
|
|
1259
1545
|
exp(args) {
|
|
1260
1546
|
if (Cons.isNumber(args.car)) return Math.exp(args.car);
|
|
1261
1547
|
throw new ReferenceError(SELECT_PRINT_FUNCTION_NOT_DEFINED);
|
|
1262
1548
|
}
|
|
1549
|
+
/**
|
|
1550
|
+
* Implementation of the Lisp `format` function. Writes a formatted string to standard output.
|
|
1551
|
+
* @param args the argument Cons containing the format string followed by its arguments
|
|
1552
|
+
* @return nil
|
|
1553
|
+
*/
|
|
1263
1554
|
format(args) {
|
|
1264
1555
|
if (!Cons.isString(args.car)) throw new EvalError(cannotApply("format", args.car));
|
|
1265
1556
|
const aCons = args.cdr;
|
|
@@ -1267,6 +1558,12 @@ var Applier = class Applier {
|
|
|
1267
1558
|
process.stdout.write(String(format));
|
|
1268
1559
|
return Cons.nil;
|
|
1269
1560
|
}
|
|
1561
|
+
/**
|
|
1562
|
+
* Helper that expands the given format string with the supplied arguments.
|
|
1563
|
+
* @param format the format string containing directives such as `~a`, `~%`, and width specifiers
|
|
1564
|
+
* @param aCons the argument list to interpolate into the directives
|
|
1565
|
+
* @return the formatted string
|
|
1566
|
+
*/
|
|
1270
1567
|
format_AUX(format, aCons) {
|
|
1271
1568
|
let theCons = aCons;
|
|
1272
1569
|
let index = 0;
|
|
@@ -1388,23 +1685,48 @@ var Applier = class Applier {
|
|
|
1388
1685
|
if (Cons.isNotNil(theCons)) throw new EvalError(SIZE_DO_NOT_MATCH);
|
|
1389
1686
|
return buffer;
|
|
1390
1687
|
}
|
|
1688
|
+
/**
|
|
1689
|
+
* Implementation of the Lisp `floatp` predicate. Returns t if the argument is a number representable as IEEE 32-bit float.
|
|
1690
|
+
* @param args the argument Cons containing the value to test
|
|
1691
|
+
* @return t if a float, nil otherwise
|
|
1692
|
+
*/
|
|
1391
1693
|
float_(args) {
|
|
1392
1694
|
if (Cons.isNumber(args.car) && -34e37 <= args.car && args.car <= 34e37) return InterpretedSymbol.of("t");
|
|
1393
1695
|
return Cons.nil;
|
|
1394
1696
|
}
|
|
1697
|
+
/**
|
|
1698
|
+
* Implementation of the Lisp `gensym` function. Generates a fresh, unique symbol.
|
|
1699
|
+
* @return a new, unique InterpretedSymbol
|
|
1700
|
+
*/
|
|
1395
1701
|
gensym() {
|
|
1396
1702
|
const aSymbol = InterpretedSymbol.of("id" + String(Applier.#generateNumber));
|
|
1397
1703
|
Applier.incrementGenerateNumber();
|
|
1398
1704
|
return aSymbol;
|
|
1399
1705
|
}
|
|
1706
|
+
/**
|
|
1707
|
+
* Returns the appropriate stream for the given object.
|
|
1708
|
+
* @param anObject the object used to select the stream
|
|
1709
|
+
* @return the selected stream
|
|
1710
|
+
*/
|
|
1400
1711
|
getStream(anObject) {
|
|
1401
1712
|
if (typeof anObject === "string") return process.out;
|
|
1402
1713
|
return this.streamManager.getStream();
|
|
1403
1714
|
}
|
|
1715
|
+
/**
|
|
1716
|
+
* Implementation of the Lisp `>` / `greaterThan` predicate. Returns t when arguments are in strictly decreasing order.
|
|
1717
|
+
* @param args the argument Cons containing the numbers to compare
|
|
1718
|
+
* @return t when each is greater than the next, nil otherwise
|
|
1719
|
+
*/
|
|
1404
1720
|
greaterThan(args) {
|
|
1405
1721
|
if (Cons.isNumber(args.car)) return this.greaterThan_Number(args.car, args.cdr);
|
|
1406
1722
|
throw new EvalError(cannotApply(">", args.car));
|
|
1407
1723
|
}
|
|
1724
|
+
/**
|
|
1725
|
+
* Helper that checks `>` ordering starting from an initial number against the remaining argument list.
|
|
1726
|
+
* @param init the initial number on the left side of the first comparison
|
|
1727
|
+
* @param args the remaining numbers to compare against
|
|
1728
|
+
* @return t when strictly decreasing, nil otherwise
|
|
1729
|
+
*/
|
|
1408
1730
|
greaterThan_Number(init, args) {
|
|
1409
1731
|
let leftValue = init;
|
|
1410
1732
|
let aCons = args;
|
|
@@ -1419,10 +1741,21 @@ var Applier = class Applier {
|
|
|
1419
1741
|
}
|
|
1420
1742
|
return InterpretedSymbol.of("t");
|
|
1421
1743
|
}
|
|
1744
|
+
/**
|
|
1745
|
+
* Implementation of the Lisp `>=` / `greaterThanOrEqual` predicate. Returns t when arguments are in non-increasing order.
|
|
1746
|
+
* @param args the argument Cons containing the numbers to compare
|
|
1747
|
+
* @return t when each is greater than or equal to the next, nil otherwise
|
|
1748
|
+
*/
|
|
1422
1749
|
greaterThanOrEqual(args) {
|
|
1423
1750
|
if (Cons.isNumber(args.car)) return this.greaterThanOrEqual_Number(args.car, args.cdr);
|
|
1424
1751
|
throw new EvalError(cannotApply(">=", args.car));
|
|
1425
1752
|
}
|
|
1753
|
+
/**
|
|
1754
|
+
* Helper that checks `>=` ordering starting from an initial number against the remaining argument list.
|
|
1755
|
+
* @param init the initial number on the left side of the first comparison
|
|
1756
|
+
* @param args the remaining numbers to compare against
|
|
1757
|
+
* @return t when non-increasing, nil otherwise
|
|
1758
|
+
*/
|
|
1426
1759
|
greaterThanOrEqual_Number(init, args) {
|
|
1427
1760
|
let leftValue = init;
|
|
1428
1761
|
let aCons = args;
|
|
@@ -1437,31 +1770,441 @@ var Applier = class Applier {
|
|
|
1437
1770
|
}
|
|
1438
1771
|
return InterpretedSymbol.of("t");
|
|
1439
1772
|
}
|
|
1773
|
+
/**
|
|
1774
|
+
* Increments the internal counter used by `gensym` to ensure uniqueness.
|
|
1775
|
+
*/
|
|
1440
1776
|
static incrementGenerateNumber() {
|
|
1441
1777
|
Applier.#generateNumber++;
|
|
1442
1778
|
return null;
|
|
1443
1779
|
}
|
|
1780
|
+
/**
|
|
1781
|
+
* Returns a string of indentation used as a prefix for spy output, based on the current depth.
|
|
1782
|
+
* @return the indentation string
|
|
1783
|
+
*/
|
|
1444
1784
|
indent() {
|
|
1445
1785
|
let index = 0;
|
|
1446
1786
|
let aString = "";
|
|
1447
1787
|
while (index++ < this.depth) aString += "| ";
|
|
1448
1788
|
return aString;
|
|
1449
1789
|
}
|
|
1790
|
+
/**
|
|
1791
|
+
* Implementation of the Lisp `integerp` predicate. Returns t if the argument is an integer.
|
|
1792
|
+
* @param args the argument Cons containing the value to test
|
|
1793
|
+
* @return t if an integer, nil otherwise
|
|
1794
|
+
*/
|
|
1450
1795
|
integer_(args) {
|
|
1451
1796
|
if (Cons.isNumber(args.car) && Number.isInteger(args.car)) return InterpretedSymbol.of("t");
|
|
1452
1797
|
return Cons.nil;
|
|
1453
1798
|
}
|
|
1799
|
+
/**
|
|
1800
|
+
* Implementation of the Lisp `evenp` predicate. Returns t if the argument is an even integer.
|
|
1801
|
+
* @param args the argument Cons containing the value to test
|
|
1802
|
+
* @return t if even, nil otherwise
|
|
1803
|
+
*/
|
|
1804
|
+
even_(args) {
|
|
1805
|
+
if (Cons.isNumber(args.car) && Number.isInteger(args.car) && args.car % 2 === 0) return InterpretedSymbol.of("t");
|
|
1806
|
+
return Cons.nil;
|
|
1807
|
+
}
|
|
1808
|
+
/**
|
|
1809
|
+
* Implementation of the Lisp `oddp` predicate. Returns t if the argument is an odd integer.
|
|
1810
|
+
* @param args the argument Cons containing the value to test
|
|
1811
|
+
* @return t if odd, nil otherwise
|
|
1812
|
+
*/
|
|
1813
|
+
odd_(args) {
|
|
1814
|
+
if (Cons.isNumber(args.car) && Number.isInteger(args.car) && args.car % 2 !== 0) return InterpretedSymbol.of("t");
|
|
1815
|
+
return Cons.nil;
|
|
1816
|
+
}
|
|
1817
|
+
/**
|
|
1818
|
+
* Implementation of the Lisp `zerop` predicate. Returns t if the argument equals zero.
|
|
1819
|
+
* @param args the argument Cons containing the value to test
|
|
1820
|
+
* @return t if zero, nil otherwise
|
|
1821
|
+
*/
|
|
1822
|
+
zero_(args) {
|
|
1823
|
+
if (Cons.isNumber(args.car) && args.car === 0) return InterpretedSymbol.of("t");
|
|
1824
|
+
return Cons.nil;
|
|
1825
|
+
}
|
|
1826
|
+
/**
|
|
1827
|
+
* Implementation of the Lisp `plusp` predicate. Returns t if the argument is strictly positive.
|
|
1828
|
+
* @param args the argument Cons containing the value to test
|
|
1829
|
+
* @return t if positive, nil otherwise
|
|
1830
|
+
*/
|
|
1831
|
+
plus_(args) {
|
|
1832
|
+
if (Cons.isNumber(args.car) && args.car > 0) return InterpretedSymbol.of("t");
|
|
1833
|
+
return Cons.nil;
|
|
1834
|
+
}
|
|
1835
|
+
/**
|
|
1836
|
+
* Implementation of the Lisp `minusp` predicate. Returns t if the argument is strictly negative.
|
|
1837
|
+
* @param args the argument Cons containing the value to test
|
|
1838
|
+
* @return t if negative, nil otherwise
|
|
1839
|
+
*/
|
|
1840
|
+
minus_(args) {
|
|
1841
|
+
if (Cons.isNumber(args.car) && args.car < 0) return InterpretedSymbol.of("t");
|
|
1842
|
+
return Cons.nil;
|
|
1843
|
+
}
|
|
1844
|
+
/**
|
|
1845
|
+
* Implementation of the Lisp `1+` function. Returns the argument incremented by one.
|
|
1846
|
+
* @param args the argument Cons containing the target number
|
|
1847
|
+
* @return the argument plus one
|
|
1848
|
+
*/
|
|
1849
|
+
oneplus(args) {
|
|
1850
|
+
if (Cons.isNumber(args.car)) return args.car + 1;
|
|
1851
|
+
throw new EvalError(cannotApply("1+", args.car));
|
|
1852
|
+
}
|
|
1853
|
+
/**
|
|
1854
|
+
* Implementation of the Lisp `1-` function. Returns the argument decremented by one.
|
|
1855
|
+
* @param args the argument Cons containing the target number
|
|
1856
|
+
* @return the argument minus one
|
|
1857
|
+
*/
|
|
1858
|
+
oneminus(args) {
|
|
1859
|
+
if (Cons.isNumber(args.car)) return args.car - 1;
|
|
1860
|
+
throw new EvalError(cannotApply("1-", args.car));
|
|
1861
|
+
}
|
|
1862
|
+
/**
|
|
1863
|
+
* Implementation of the Lisp `expt` function. Returns the base raised to the exponent.
|
|
1864
|
+
* @param args the argument Cons containing the base followed by the exponent
|
|
1865
|
+
* @return base raised to the exponent
|
|
1866
|
+
*/
|
|
1867
|
+
expt(args) {
|
|
1868
|
+
const base = args.car;
|
|
1869
|
+
const exponent = args.nth(2);
|
|
1870
|
+
if (Cons.isNumber(base) && Cons.isNumber(exponent)) return Math.pow(base, exponent);
|
|
1871
|
+
throw new EvalError(cannotApply("expt", base));
|
|
1872
|
+
}
|
|
1873
|
+
/**
|
|
1874
|
+
* Implementation of the Lisp `truncate` function. Returns the integer part of the given number.
|
|
1875
|
+
* @param args the argument Cons containing the target number
|
|
1876
|
+
* @return the truncated integer
|
|
1877
|
+
*/
|
|
1878
|
+
truncate(args) {
|
|
1879
|
+
if (Cons.isNumber(args.car)) return Math.trunc(args.car);
|
|
1880
|
+
throw new EvalError(cannotApply("truncate", args.car));
|
|
1881
|
+
}
|
|
1882
|
+
/**
|
|
1883
|
+
* Implementation of the Lisp `floor` function. Returns the largest integer not greater than the given number.
|
|
1884
|
+
* @param args the argument Cons containing the target number
|
|
1885
|
+
* @return the floor of the argument
|
|
1886
|
+
*/
|
|
1887
|
+
floor(args) {
|
|
1888
|
+
if (Cons.isNumber(args.car)) return Math.floor(args.car);
|
|
1889
|
+
throw new EvalError(cannotApply("floor", args.car));
|
|
1890
|
+
}
|
|
1891
|
+
/**
|
|
1892
|
+
* Implementation of the Lisp `ceiling` function. Returns the smallest integer not less than the given number.
|
|
1893
|
+
* @param args the argument Cons containing the target number
|
|
1894
|
+
* @return the ceiling of the argument
|
|
1895
|
+
*/
|
|
1896
|
+
ceiling(args) {
|
|
1897
|
+
if (Cons.isNumber(args.car)) return Math.ceil(args.car);
|
|
1898
|
+
throw new EvalError(cannotApply("ceiling", args.car));
|
|
1899
|
+
}
|
|
1900
|
+
/**
|
|
1901
|
+
* Implementation of the Lisp `min` function. Returns the minimum of the given numbers.
|
|
1902
|
+
* @param args the argument Cons containing the numbers to compare
|
|
1903
|
+
* @return the smallest number
|
|
1904
|
+
*/
|
|
1905
|
+
min(args) {
|
|
1906
|
+
const values = [];
|
|
1907
|
+
for (const each of args.loop()) {
|
|
1908
|
+
if (!Cons.isNumber(each)) throw new EvalError(cannotApply("min", each));
|
|
1909
|
+
values.push(each);
|
|
1910
|
+
}
|
|
1911
|
+
if (values.length === 0) throw new EvalError("min requires at least one argument");
|
|
1912
|
+
return Math.min(...values);
|
|
1913
|
+
}
|
|
1914
|
+
/**
|
|
1915
|
+
* Implementation of the Lisp `max` function. Returns the maximum of the given numbers.
|
|
1916
|
+
* @param args the argument Cons containing the numbers to compare
|
|
1917
|
+
* @return the largest number
|
|
1918
|
+
*/
|
|
1919
|
+
max(args) {
|
|
1920
|
+
const values = [];
|
|
1921
|
+
for (const each of args.loop()) {
|
|
1922
|
+
if (!Cons.isNumber(each)) throw new EvalError(cannotApply("max", each));
|
|
1923
|
+
values.push(each);
|
|
1924
|
+
}
|
|
1925
|
+
if (values.length === 0) throw new EvalError("max requires at least one argument");
|
|
1926
|
+
return Math.max(...values);
|
|
1927
|
+
}
|
|
1928
|
+
/**
|
|
1929
|
+
* Implementation of the Lisp `length` function. Returns the length of a list or string.
|
|
1930
|
+
* @param args the argument Cons containing the target sequence
|
|
1931
|
+
* @return the length of the sequence
|
|
1932
|
+
*/
|
|
1933
|
+
length(args) {
|
|
1934
|
+
const target = args.car;
|
|
1935
|
+
if (Cons.isString(target)) return toCodePoints(target).length;
|
|
1936
|
+
if (Cons.isCons(target)) return target.length();
|
|
1937
|
+
if (Cons.isNil(target)) return 0;
|
|
1938
|
+
throw new EvalError(cannotApply("length", target));
|
|
1939
|
+
}
|
|
1940
|
+
/**
|
|
1941
|
+
* Implementation of the Lisp `string-upcase` function. Returns the upper-cased form of the given string.
|
|
1942
|
+
* @param args the argument Cons containing the target string
|
|
1943
|
+
* @return the upper-cased string
|
|
1944
|
+
*/
|
|
1945
|
+
stringUpcase(args) {
|
|
1946
|
+
if (Cons.isString(args.car)) return args.car.toUpperCase();
|
|
1947
|
+
throw new EvalError(cannotApply("string-upcase", args.car));
|
|
1948
|
+
}
|
|
1949
|
+
/**
|
|
1950
|
+
* Implementation of the Lisp `string-downcase` function. Returns the lower-cased form of the given string.
|
|
1951
|
+
* @param args the argument Cons containing the target string
|
|
1952
|
+
* @return the lower-cased string
|
|
1953
|
+
*/
|
|
1954
|
+
stringDowncase(args) {
|
|
1955
|
+
if (Cons.isString(args.car)) return args.car.toLowerCase();
|
|
1956
|
+
throw new EvalError(cannotApply("string-downcase", args.car));
|
|
1957
|
+
}
|
|
1958
|
+
/**
|
|
1959
|
+
* Implementation of the Lisp `string-trim` function. Returns the given string with surrounding whitespace removed.
|
|
1960
|
+
* @param args the argument Cons containing the target string
|
|
1961
|
+
* @return the trimmed string
|
|
1962
|
+
*/
|
|
1963
|
+
stringTrim(args) {
|
|
1964
|
+
if (Cons.isString(args.car)) return args.car.trim();
|
|
1965
|
+
throw new EvalError(cannotApply("string-trim", args.car));
|
|
1966
|
+
}
|
|
1967
|
+
/**
|
|
1968
|
+
* Implementation of the Lisp `substring` function. Returns a portion of the given string between start and end (in code points).
|
|
1969
|
+
* @param args the argument Cons containing the target string, start index, and optional end index
|
|
1970
|
+
* @return the requested substring
|
|
1971
|
+
*/
|
|
1972
|
+
substring(args) {
|
|
1973
|
+
const target = args.car;
|
|
1974
|
+
const start = args.nth(2);
|
|
1975
|
+
const end = args.nth(3);
|
|
1976
|
+
if (!Cons.isString(target)) throw new EvalError(cannotApply("substring", target));
|
|
1977
|
+
if (!Cons.isNumber(start)) throw new EvalError(cannotApply("substring", start));
|
|
1978
|
+
const chars = toCodePoints(target);
|
|
1979
|
+
if (Cons.isNil(end)) return chars.slice(start).join("");
|
|
1980
|
+
if (!Cons.isNumber(end)) throw new EvalError(cannotApply("substring", end));
|
|
1981
|
+
return chars.slice(start, end).join("");
|
|
1982
|
+
}
|
|
1983
|
+
/**
|
|
1984
|
+
* Implementation of the Lisp `concatenate` function. Returns the concatenation of all the given strings.
|
|
1985
|
+
* @param args the argument Cons containing the strings to concatenate
|
|
1986
|
+
* @return the concatenated string
|
|
1987
|
+
*/
|
|
1988
|
+
concatenate(args) {
|
|
1989
|
+
let result = "";
|
|
1990
|
+
for (const each of args.loop()) {
|
|
1991
|
+
if (!Cons.isString(each)) throw new EvalError(cannotApply("concatenate", each));
|
|
1992
|
+
result += each;
|
|
1993
|
+
}
|
|
1994
|
+
return result;
|
|
1995
|
+
}
|
|
1996
|
+
/**
|
|
1997
|
+
* Implementation of the Lisp `elt` function. Returns the element at the given index of a string or list.
|
|
1998
|
+
* @param args the argument Cons containing the target sequence and the index
|
|
1999
|
+
* @return the element at the given index
|
|
2000
|
+
*/
|
|
2001
|
+
elt(args) {
|
|
2002
|
+
const target = args.car;
|
|
2003
|
+
const index = args.nth(2);
|
|
2004
|
+
if (!Cons.isNumber(index)) throw new EvalError(cannotApply("elt", index));
|
|
2005
|
+
if (Cons.isString(target)) {
|
|
2006
|
+
const chars = toCodePoints(target);
|
|
2007
|
+
if (index < 0 || index >= chars.length) throw new EvalError(`elt: index ${String(index)} out of range`);
|
|
2008
|
+
return chars[index];
|
|
2009
|
+
}
|
|
2010
|
+
if (Cons.isCons(target)) {
|
|
2011
|
+
if (index < 0 || index >= target.length()) throw new EvalError(`elt: index ${String(index)} out of range`);
|
|
2012
|
+
return target.nth(index + 1);
|
|
2013
|
+
}
|
|
2014
|
+
throw new EvalError(cannotApply("elt", target));
|
|
2015
|
+
}
|
|
2016
|
+
/**
|
|
2017
|
+
* Implementation of the Lisp `subseq` function. Returns a subsequence of a string or list between start and end.
|
|
2018
|
+
* @param args the argument Cons containing the target sequence, start index, and optional end index
|
|
2019
|
+
* @return the requested subsequence
|
|
2020
|
+
*/
|
|
2021
|
+
subseq(args) {
|
|
2022
|
+
const target = args.car;
|
|
2023
|
+
const start = args.nth(2);
|
|
2024
|
+
const end = args.nth(3);
|
|
2025
|
+
if (!Cons.isNumber(start)) throw new EvalError(cannotApply("subseq", start));
|
|
2026
|
+
if (Cons.isString(target)) {
|
|
2027
|
+
const chars = toCodePoints(target);
|
|
2028
|
+
if (Cons.isNil(end)) return chars.slice(start).join("");
|
|
2029
|
+
if (!Cons.isNumber(end)) throw new EvalError(cannotApply("subseq", end));
|
|
2030
|
+
return chars.slice(start, end).join("");
|
|
2031
|
+
}
|
|
2032
|
+
if (Cons.isCons(target)) {
|
|
2033
|
+
const stop = Cons.isNil(end) ? target.length() : end;
|
|
2034
|
+
if (!Cons.isNumber(stop)) throw new EvalError(cannotApply("subseq", end));
|
|
2035
|
+
let result = Cons.nil;
|
|
2036
|
+
for (let i = stop - 1; i >= start; i--) result = new Cons(target.nth(i + 1), result);
|
|
2037
|
+
return result;
|
|
2038
|
+
}
|
|
2039
|
+
throw new EvalError(cannotApply("subseq", target));
|
|
2040
|
+
}
|
|
2041
|
+
/**
|
|
2042
|
+
* Implementation of the Lisp `count` function. Counts the occurrences of an item within a string or list.
|
|
2043
|
+
* @param args the argument Cons containing the item and the target sequence
|
|
2044
|
+
* @return the number of occurrences
|
|
2045
|
+
*/
|
|
2046
|
+
count(args) {
|
|
2047
|
+
const item = args.car;
|
|
2048
|
+
const target = args.nth(2);
|
|
2049
|
+
let n = 0;
|
|
2050
|
+
if (Cons.isString(target)) {
|
|
2051
|
+
if (!Cons.isString(item) || item.length !== 1) return 0;
|
|
2052
|
+
for (const ch of target) if (ch === item) n++;
|
|
2053
|
+
return n;
|
|
2054
|
+
}
|
|
2055
|
+
if (Cons.isCons(target) || Cons.isNil(target)) {
|
|
2056
|
+
const list = Cons.isNil(target) ? Cons.nil : target;
|
|
2057
|
+
if (Cons.isCons(list)) {
|
|
2058
|
+
for (const each of list.loop()) if (each === item) n++;
|
|
2059
|
+
}
|
|
2060
|
+
return n;
|
|
2061
|
+
}
|
|
2062
|
+
throw new EvalError(cannotApply("count", target));
|
|
2063
|
+
}
|
|
2064
|
+
/**
|
|
2065
|
+
* Implementation of the Lisp `reduce` function. Combines the elements of a list using a binary procedure.
|
|
2066
|
+
* @param args the argument Cons containing the procedure, the list, and an optional initial value
|
|
2067
|
+
* @return the result of folding the procedure over the list
|
|
2068
|
+
*/
|
|
2069
|
+
reduce(args) {
|
|
2070
|
+
const procedure = args.car;
|
|
2071
|
+
const list = args.nth(2);
|
|
2072
|
+
const hasInit = args.length() >= 3;
|
|
2073
|
+
const init = args.nth(3);
|
|
2074
|
+
if (Cons.isNil(list)) {
|
|
2075
|
+
if (hasInit) return init;
|
|
2076
|
+
return Applier.apply(procedure, Cons.nil, this.environment, this.streamManager, this.depth);
|
|
2077
|
+
}
|
|
2078
|
+
if (!Cons.isCons(list)) throw new EvalError(cannotApply("reduce", list));
|
|
2079
|
+
const iter = list.loop();
|
|
2080
|
+
let acc;
|
|
2081
|
+
if (hasInit) acc = init;
|
|
2082
|
+
else {
|
|
2083
|
+
if (!iter.hasNext()) return Applier.apply(procedure, Cons.nil, this.environment, this.streamManager, this.depth);
|
|
2084
|
+
acc = iter.next();
|
|
2085
|
+
}
|
|
2086
|
+
while (iter.hasNext()) {
|
|
2087
|
+
const next = iter.next();
|
|
2088
|
+
acc = Applier.apply(procedure, new Cons(acc, new Cons(next, Cons.nil)), this.environment, this.streamManager, this.depth);
|
|
2089
|
+
}
|
|
2090
|
+
return acc;
|
|
2091
|
+
}
|
|
2092
|
+
/**
|
|
2093
|
+
* Implementation of the Lisp `every` function. Returns t when the predicate holds for every element of the list.
|
|
2094
|
+
* @param args the argument Cons containing the predicate and the list
|
|
2095
|
+
* @return t when the predicate holds for every element, nil otherwise
|
|
2096
|
+
*/
|
|
2097
|
+
every(args) {
|
|
2098
|
+
const procedure = args.car;
|
|
2099
|
+
const list = args.nth(2);
|
|
2100
|
+
if (Cons.isNil(list)) return InterpretedSymbol.of("t");
|
|
2101
|
+
if (!Cons.isCons(list)) throw new EvalError(cannotApply("every", list));
|
|
2102
|
+
for (const each of list.loop()) {
|
|
2103
|
+
const result = Applier.apply(procedure, new Cons(each, Cons.nil), this.environment, this.streamManager, this.depth);
|
|
2104
|
+
if (Cons.isNil(result)) return Cons.nil;
|
|
2105
|
+
}
|
|
2106
|
+
return InterpretedSymbol.of("t");
|
|
2107
|
+
}
|
|
2108
|
+
/**
|
|
2109
|
+
* Implementation of the Lisp `some` function. Returns the first non-nil predicate result, or nil if all are nil.
|
|
2110
|
+
* @param args the argument Cons containing the predicate and the list
|
|
2111
|
+
* @return the first non-nil result, or nil
|
|
2112
|
+
*/
|
|
2113
|
+
some(args) {
|
|
2114
|
+
const procedure = args.car;
|
|
2115
|
+
const list = args.nth(2);
|
|
2116
|
+
if (Cons.isNil(list)) return Cons.nil;
|
|
2117
|
+
if (!Cons.isCons(list)) throw new EvalError(cannotApply("some", list));
|
|
2118
|
+
for (const each of list.loop()) {
|
|
2119
|
+
const result = Applier.apply(procedure, new Cons(each, Cons.nil), this.environment, this.streamManager, this.depth);
|
|
2120
|
+
if (Cons.isNotNil(result)) return result;
|
|
2121
|
+
}
|
|
2122
|
+
return Cons.nil;
|
|
2123
|
+
}
|
|
2124
|
+
/**
|
|
2125
|
+
* Implementation of the Lisp `find` function. Returns the first element of the list that matches the given item.
|
|
2126
|
+
* @param args the argument Cons containing the item and the list
|
|
2127
|
+
* @return the matching element, or nil if none found
|
|
2128
|
+
*/
|
|
2129
|
+
find(args) {
|
|
2130
|
+
const item = args.car;
|
|
2131
|
+
const list = args.nth(2);
|
|
2132
|
+
if (Cons.isNil(list)) return Cons.nil;
|
|
2133
|
+
if (!Cons.isCons(list)) throw new EvalError(cannotApply("find", list));
|
|
2134
|
+
for (const each of list.loop()) if (this.eq_(new Cons(item, new Cons(each, Cons.nil))) === InterpretedSymbol.of("t")) return each;
|
|
2135
|
+
return Cons.nil;
|
|
2136
|
+
}
|
|
2137
|
+
/**
|
|
2138
|
+
* Implementation of the Lisp `mapcan` function. Applies the procedure to each element and concatenates the resulting lists.
|
|
2139
|
+
* @param args the argument Cons containing the procedure and the list
|
|
2140
|
+
* @return the concatenation of the per-element results
|
|
2141
|
+
*/
|
|
2142
|
+
mapcan(args) {
|
|
2143
|
+
const procedure = args.car;
|
|
2144
|
+
const list = args.nth(2);
|
|
2145
|
+
if (Cons.isNil(list)) return Cons.nil;
|
|
2146
|
+
if (!Cons.isCons(list)) throw new EvalError(cannotApply("mapcan", list));
|
|
2147
|
+
const collected = [];
|
|
2148
|
+
for (const each of list.loop()) {
|
|
2149
|
+
const part = Applier.apply(procedure, new Cons(each, Cons.nil), this.environment, this.streamManager, this.depth);
|
|
2150
|
+
if (Cons.isCons(part)) for (const x of part.loop()) collected.push(x);
|
|
2151
|
+
}
|
|
2152
|
+
let result = Cons.nil;
|
|
2153
|
+
for (let i = collected.length - 1; i >= 0; i--) result = new Cons(collected[i], result);
|
|
2154
|
+
return result;
|
|
2155
|
+
}
|
|
2156
|
+
/**
|
|
2157
|
+
* Implementation of the Lisp `sort` function. Returns a new list sorted by the given comparison predicate.
|
|
2158
|
+
* @param args the argument Cons containing the list and the comparison predicate
|
|
2159
|
+
* @return the sorted list
|
|
2160
|
+
*/
|
|
2161
|
+
sort(args) {
|
|
2162
|
+
const list = args.car;
|
|
2163
|
+
const procedure = args.nth(2);
|
|
2164
|
+
if (Cons.isNil(list)) return Cons.nil;
|
|
2165
|
+
if (!Cons.isCons(list)) throw new EvalError(cannotApply("sort", list));
|
|
2166
|
+
const items = [];
|
|
2167
|
+
for (const each of list.loop()) items.push(each);
|
|
2168
|
+
items.sort((a, b) => {
|
|
2169
|
+
const result = Applier.apply(procedure, new Cons(a, new Cons(b, Cons.nil)), this.environment, this.streamManager, this.depth);
|
|
2170
|
+
return Cons.isNil(result) ? 1 : -1;
|
|
2171
|
+
});
|
|
2172
|
+
let result = Cons.nil;
|
|
2173
|
+
for (let i = items.length - 1; i >= 0; i--) result = new Cons(items[i], result);
|
|
2174
|
+
return result;
|
|
2175
|
+
}
|
|
2176
|
+
/**
|
|
2177
|
+
* Returns whether the given symbol is currently being spied on.
|
|
2178
|
+
* @param aSymbol the symbol to check
|
|
2179
|
+
* @return true if spied on, false otherwise
|
|
2180
|
+
*/
|
|
1454
2181
|
isSpy(aSymbol) {
|
|
1455
2182
|
return this.streamManager.isSpy(aSymbol);
|
|
1456
2183
|
}
|
|
2184
|
+
/**
|
|
2185
|
+
* Implementation of the Lisp `last` function. Returns the last cell of the given list.
|
|
2186
|
+
* @param args the argument Cons containing the target list
|
|
2187
|
+
* @return the last cell of the list
|
|
2188
|
+
*/
|
|
1457
2189
|
last(args) {
|
|
1458
2190
|
if (Cons.isNotCons(args)) return Cons.nil;
|
|
1459
2191
|
return args.car.last();
|
|
1460
2192
|
}
|
|
2193
|
+
/**
|
|
2194
|
+
* Implementation of the Lisp `<` / `lessThan` predicate. Returns t when arguments are in strictly increasing order.
|
|
2195
|
+
* @param args the argument Cons containing the numbers to compare
|
|
2196
|
+
* @return t when each is less than the next, nil otherwise
|
|
2197
|
+
*/
|
|
1461
2198
|
lessThan(args) {
|
|
1462
2199
|
if (Cons.isNumber(args.car)) return this.lessThan_Number(args.car, args.cdr);
|
|
1463
2200
|
throw new EvalError(cannotApply("<", args.car));
|
|
1464
2201
|
}
|
|
2202
|
+
/**
|
|
2203
|
+
* Helper that checks `<` ordering starting from an initial number against the remaining argument list.
|
|
2204
|
+
* @param init the initial number on the left side of the first comparison
|
|
2205
|
+
* @param args the remaining numbers to compare against
|
|
2206
|
+
* @return t when strictly increasing, nil otherwise
|
|
2207
|
+
*/
|
|
1465
2208
|
lessThan_Number(init, args) {
|
|
1466
2209
|
let leftValue = init;
|
|
1467
2210
|
let aCons = args;
|
|
@@ -1476,10 +2219,21 @@ var Applier = class Applier {
|
|
|
1476
2219
|
}
|
|
1477
2220
|
return InterpretedSymbol.of("t");
|
|
1478
2221
|
}
|
|
2222
|
+
/**
|
|
2223
|
+
* Implementation of the Lisp `<=` / `lessThanOrEqual` predicate. Returns t when arguments are in non-decreasing order.
|
|
2224
|
+
* @param args the argument Cons containing the numbers to compare
|
|
2225
|
+
* @return t when each is less than or equal to the next, nil otherwise
|
|
2226
|
+
*/
|
|
1479
2227
|
lessThanOrEqual(args) {
|
|
1480
2228
|
if (Cons.isNumber(args.car)) return this.lessThanOrEqual_Number(args.car, args.cdr);
|
|
1481
2229
|
throw new EvalError(cannotApply("<=", args.car));
|
|
1482
2230
|
}
|
|
2231
|
+
/**
|
|
2232
|
+
* Helper that checks `<=` ordering starting from an initial number against the remaining argument list.
|
|
2233
|
+
* @param init the initial number on the left side of the first comparison
|
|
2234
|
+
* @param args the remaining numbers to compare against
|
|
2235
|
+
* @return t when non-decreasing, nil otherwise
|
|
2236
|
+
*/
|
|
1483
2237
|
lessThanOrEqual_Number(init, args) {
|
|
1484
2238
|
let leftValue = init;
|
|
1485
2239
|
let aCons = args;
|
|
@@ -1494,14 +2248,29 @@ var Applier = class Applier {
|
|
|
1494
2248
|
}
|
|
1495
2249
|
return InterpretedSymbol.of("t");
|
|
1496
2250
|
}
|
|
2251
|
+
/**
|
|
2252
|
+
* Implementation of the Lisp `list` function. Returns a list of the given arguments.
|
|
2253
|
+
* @param args the argument list
|
|
2254
|
+
* @return a Cons list of the arguments
|
|
2255
|
+
*/
|
|
1497
2256
|
list(args) {
|
|
1498
2257
|
if (Cons.isNil(args)) return Cons.nil;
|
|
1499
2258
|
return new Cons(args.car, this.list(args.cdr));
|
|
1500
2259
|
}
|
|
2260
|
+
/**
|
|
2261
|
+
* Implementation of the Lisp `listp` predicate. Returns t if the argument is a list (Cons or nil).
|
|
2262
|
+
* @param args the argument Cons containing the value to test
|
|
2263
|
+
* @return t if a list, nil otherwise
|
|
2264
|
+
*/
|
|
1501
2265
|
list_(args) {
|
|
1502
2266
|
if (Cons.isList(args.car)) return InterpretedSymbol.of("t");
|
|
1503
2267
|
return Cons.nil;
|
|
1504
2268
|
}
|
|
2269
|
+
/**
|
|
2270
|
+
* Implementation of the Lisp `mapcar` function. Applies the procedure to each tuple of corresponding elements.
|
|
2271
|
+
* @param args the argument Cons containing the procedure followed by one or more lists
|
|
2272
|
+
* @return the list of results
|
|
2273
|
+
*/
|
|
1505
2274
|
mapcar(args) {
|
|
1506
2275
|
const aCons = new Cons(Cons.nil, Cons.nil);
|
|
1507
2276
|
const procedure = args.car;
|
|
@@ -1525,6 +2294,11 @@ var Applier = class Applier {
|
|
|
1525
2294
|
}
|
|
1526
2295
|
return aCons.cdr;
|
|
1527
2296
|
}
|
|
2297
|
+
/**
|
|
2298
|
+
* Implementation of the Lisp `member` function. Returns the sublist whose car matches the given item.
|
|
2299
|
+
* @param args the argument Cons containing the item, the list, and an optional comparator symbol
|
|
2300
|
+
* @return the matching sublist, or nil if not found
|
|
2301
|
+
*/
|
|
1528
2302
|
member(args) {
|
|
1529
2303
|
let aSymbol = InterpretedSymbol.of("equal?");
|
|
1530
2304
|
if (Cons.isNotNil(args.nth(3))) aSymbol = args.nth(3);
|
|
@@ -1540,14 +2314,30 @@ var Applier = class Applier {
|
|
|
1540
2314
|
}
|
|
1541
2315
|
return Cons.nil;
|
|
1542
2316
|
}
|
|
2317
|
+
/**
|
|
2318
|
+
* Implementation of the Lisp `memq` predicate. Returns t when `member` finds a match, nil otherwise.
|
|
2319
|
+
* @param args the argument Cons forwarded to `member`
|
|
2320
|
+
* @return t when found, nil otherwise
|
|
2321
|
+
*/
|
|
1543
2322
|
memq(args) {
|
|
1544
2323
|
if (this.member(args) === Cons.nil) return Cons.nil;
|
|
1545
2324
|
return InterpretedSymbol.of("t");
|
|
1546
2325
|
}
|
|
2326
|
+
/**
|
|
2327
|
+
* Implementation of the Lisp `mod` / `//` function. Returns the remainder of dividing the arguments in sequence.
|
|
2328
|
+
* @param args the argument Cons containing the numbers
|
|
2329
|
+
* @return the modulo result
|
|
2330
|
+
*/
|
|
1547
2331
|
mod(args) {
|
|
1548
2332
|
if (Cons.isNumber(args.car)) return this.mod_Number(args.car, args.cdr);
|
|
1549
2333
|
throw new EvalError(cannotApply("mod", args.car));
|
|
1550
2334
|
}
|
|
2335
|
+
/**
|
|
2336
|
+
* Helper that accumulates the modulo starting from an initial number and the remaining argument list.
|
|
2337
|
+
* @param init the initial number
|
|
2338
|
+
* @param args the remaining numbers to mod by
|
|
2339
|
+
* @return the remainder after taking modulo with each of the remaining numbers
|
|
2340
|
+
*/
|
|
1551
2341
|
mod_Number(init, args) {
|
|
1552
2342
|
let result = init;
|
|
1553
2343
|
let aCons = args;
|
|
@@ -1559,10 +2349,21 @@ var Applier = class Applier {
|
|
|
1559
2349
|
}
|
|
1560
2350
|
return result;
|
|
1561
2351
|
}
|
|
2352
|
+
/**
|
|
2353
|
+
* Implementation of the Lisp `*` / `multiply` function. Returns the product of the given numbers.
|
|
2354
|
+
* @param args the argument Cons containing the numbers to multiply
|
|
2355
|
+
* @return the product of the arguments
|
|
2356
|
+
*/
|
|
1562
2357
|
multiply(args) {
|
|
1563
2358
|
if (Cons.isNumber(args.car)) return this.multiply_Number(args.car, args.cdr);
|
|
1564
2359
|
throw new EvalError(cannotApply("multiply", args.car));
|
|
1565
2360
|
}
|
|
2361
|
+
/**
|
|
2362
|
+
* Helper that accumulates the product starting from an initial number and the remaining argument list.
|
|
2363
|
+
* @param init the initial number
|
|
2364
|
+
* @param args the remaining numbers to multiply
|
|
2365
|
+
* @return the product of init and all remaining numbers
|
|
2366
|
+
*/
|
|
1566
2367
|
multiply_Number(init, args) {
|
|
1567
2368
|
let result = init;
|
|
1568
2369
|
let aCons = args;
|
|
@@ -1574,49 +2375,105 @@ var Applier = class Applier {
|
|
|
1574
2375
|
}
|
|
1575
2376
|
return result;
|
|
1576
2377
|
}
|
|
2378
|
+
/**
|
|
2379
|
+
* Implementation of the Lisp `napier` function. Returns Napier's constant (e).
|
|
2380
|
+
* @return Math.E
|
|
2381
|
+
*/
|
|
1577
2382
|
napier() {
|
|
1578
2383
|
return Math.E;
|
|
1579
2384
|
}
|
|
2385
|
+
/**
|
|
2386
|
+
* Implementation of the Lisp `neq` / `~~` predicate. The negation of `eq`.
|
|
2387
|
+
* @param args the argument Cons forwarded to `eq_`
|
|
2388
|
+
* @return nil when eq, t otherwise
|
|
2389
|
+
*/
|
|
1580
2390
|
neq(args) {
|
|
1581
2391
|
if (this.eq_(args) === InterpretedSymbol.of("t")) return Cons.nil;
|
|
1582
2392
|
return InterpretedSymbol.of("t");
|
|
1583
2393
|
}
|
|
2394
|
+
/**
|
|
2395
|
+
* Implementation of the Lisp `nequal` / `~=` predicate. The negation of `equal`.
|
|
2396
|
+
* @param args the argument Cons forwarded to `equal_`
|
|
2397
|
+
* @return nil when equal, t otherwise
|
|
2398
|
+
*/
|
|
1584
2399
|
nequal(args) {
|
|
1585
2400
|
if (this.equal_(args) === InterpretedSymbol.of("t")) return Cons.nil;
|
|
1586
2401
|
return InterpretedSymbol.of("t");
|
|
1587
2402
|
}
|
|
2403
|
+
/**
|
|
2404
|
+
* Implementation of the Lisp `nth` function. Returns the nth element of a list.
|
|
2405
|
+
* @param args the argument Cons containing the index and the list
|
|
2406
|
+
* @return the element at the given index
|
|
2407
|
+
*/
|
|
1588
2408
|
nth(args) {
|
|
1589
2409
|
if (!Number.isInteger(args.car)) return Cons.nil;
|
|
1590
2410
|
const index = args.car;
|
|
1591
2411
|
return args.nth(2).nth(index);
|
|
1592
2412
|
}
|
|
2413
|
+
/**
|
|
2414
|
+
* Implementation of the Lisp `null` predicate. Returns t if the argument is nil, otherwise nil.
|
|
2415
|
+
* @param args the argument Cons containing the value to test
|
|
2416
|
+
* @return t if nil, nil otherwise
|
|
2417
|
+
*/
|
|
1593
2418
|
null_(args) {
|
|
1594
2419
|
if (Cons.isNil(args.car)) return InterpretedSymbol.of("t");
|
|
1595
2420
|
return Cons.nil;
|
|
1596
2421
|
}
|
|
2422
|
+
/**
|
|
2423
|
+
* Implementation of the Lisp `numberp` / `doublep` predicate. Returns t if the argument is a number.
|
|
2424
|
+
* @param args the argument Cons containing the value to test
|
|
2425
|
+
* @return t if a number, nil otherwise
|
|
2426
|
+
*/
|
|
1597
2427
|
number_(args) {
|
|
1598
2428
|
if (Cons.isNumber(args.car)) return InterpretedSymbol.of("t");
|
|
1599
2429
|
return Cons.nil;
|
|
1600
2430
|
}
|
|
2431
|
+
/**
|
|
2432
|
+
* Implementation of the Lisp `pi` function. Returns the mathematical constant pi.
|
|
2433
|
+
* @return Math.PI
|
|
2434
|
+
*/
|
|
1601
2435
|
pi() {
|
|
1602
2436
|
return Math.PI;
|
|
1603
2437
|
}
|
|
2438
|
+
/**
|
|
2439
|
+
* Implementation of the Lisp `random` function. Returns a pseudo-random number in [0, 1).
|
|
2440
|
+
* @return a random number in [0, 1)
|
|
2441
|
+
*/
|
|
1604
2442
|
random() {
|
|
1605
2443
|
return Math.random();
|
|
1606
2444
|
}
|
|
2445
|
+
/**
|
|
2446
|
+
* Implementation of the Lisp `round` function. Returns the given number rounded to the nearest integer.
|
|
2447
|
+
* @param args the argument Cons containing the target number
|
|
2448
|
+
* @return the rounded integer
|
|
2449
|
+
*/
|
|
1607
2450
|
round(args) {
|
|
1608
2451
|
if (Cons.isNumber(args.car)) return Math.round(args.car);
|
|
1609
2452
|
throw new ReferenceError(SELECT_PRINT_FUNCTION_NOT_DEFINED);
|
|
1610
2453
|
}
|
|
2454
|
+
/**
|
|
2455
|
+
* Dispatches the call to either a built-in function or a user-defined function based on the symbol.
|
|
2456
|
+
* @param procedure the symbol naming the function
|
|
2457
|
+
* @param args the argument list
|
|
2458
|
+
* @return the result of the dispatched function
|
|
2459
|
+
*/
|
|
1611
2460
|
selectProcedure(procedure, args) {
|
|
1612
2461
|
if (Applier.buildInFunctions.has(procedure)) return this.buildInFunction(procedure, args);
|
|
1613
2462
|
if (this.environment.has(procedure)) return this.userFunction(procedure, args);
|
|
1614
2463
|
throw new EvalError(noProcedure(procedure));
|
|
1615
2464
|
}
|
|
2465
|
+
/**
|
|
2466
|
+
* Sets the current recursion depth.
|
|
2467
|
+
* @param aNumber the new depth value
|
|
2468
|
+
*/
|
|
1616
2469
|
setDepth(aNumber) {
|
|
1617
2470
|
this.depth = aNumber;
|
|
1618
2471
|
return null;
|
|
1619
2472
|
}
|
|
2473
|
+
/**
|
|
2474
|
+
* Builds and returns the Lisp-name to method-name dispatch map.
|
|
2475
|
+
* @return a Map associating each Lisp function name (as an InterpretedSymbol) with the corresponding Applier method name
|
|
2476
|
+
*/
|
|
1620
2477
|
static setup() {
|
|
1621
2478
|
try {
|
|
1622
2479
|
return new Map([
|
|
@@ -1630,22 +2487,36 @@ var Applier = class Applier {
|
|
|
1630
2487
|
["cons", "cons"],
|
|
1631
2488
|
["consp", "cons_"],
|
|
1632
2489
|
["copy", "copy"],
|
|
2490
|
+
["ceiling", "ceiling"],
|
|
1633
2491
|
["cos", "cos"],
|
|
1634
2492
|
["floatp", "float_"],
|
|
2493
|
+
["floor", "floor"],
|
|
1635
2494
|
["divide", "divide"],
|
|
1636
2495
|
["doublep", "number_"],
|
|
1637
2496
|
["eq", "eq_"],
|
|
1638
2497
|
["equal", "equal_"],
|
|
2498
|
+
["evenp", "even_"],
|
|
2499
|
+
["every", "every"],
|
|
1639
2500
|
["exp", "exp"],
|
|
2501
|
+
["expt", "expt"],
|
|
2502
|
+
["find", "find"],
|
|
1640
2503
|
["format", "format"],
|
|
1641
2504
|
["gensym", "gensym"],
|
|
1642
2505
|
["integerp", "integer_"],
|
|
2506
|
+
["concatenate", "concatenate"],
|
|
2507
|
+
["count", "count"],
|
|
2508
|
+
["elt", "elt"],
|
|
1643
2509
|
["last", "last"],
|
|
2510
|
+
["length", "length"],
|
|
1644
2511
|
["list", "list"],
|
|
1645
2512
|
["listp", "list_"],
|
|
2513
|
+
["mapcan", "mapcan"],
|
|
1646
2514
|
["mapcar", "mapcar"],
|
|
2515
|
+
["max", "max"],
|
|
1647
2516
|
["member", "member"],
|
|
1648
2517
|
["memq", "memq"],
|
|
2518
|
+
["min", "min"],
|
|
2519
|
+
["minusp", "minus_"],
|
|
1649
2520
|
["mod", "mod"],
|
|
1650
2521
|
["multiply", "multiply"],
|
|
1651
2522
|
["napier", "napier"],
|
|
@@ -1654,15 +2525,29 @@ var Applier = class Applier {
|
|
|
1654
2525
|
["nth", "nth"],
|
|
1655
2526
|
["null", "null_"],
|
|
1656
2527
|
["numberp", "number_"],
|
|
2528
|
+
["oddp", "odd_"],
|
|
1657
2529
|
["pi", "pi"],
|
|
2530
|
+
["plusp", "plus_"],
|
|
1658
2531
|
["random", "random"],
|
|
2532
|
+
["reduce", "reduce"],
|
|
1659
2533
|
["round", "round"],
|
|
1660
2534
|
["sin", "sin"],
|
|
2535
|
+
["some", "some"],
|
|
2536
|
+
["sort", "sort"],
|
|
1661
2537
|
["sqrt", "sqrt"],
|
|
1662
|
-
["
|
|
2538
|
+
["string-downcase", "stringDowncase"],
|
|
2539
|
+
["string-trim", "stringTrim"],
|
|
2540
|
+
["string-upcase", "stringUpcase"],
|
|
1663
2541
|
["stringp", "string_"],
|
|
2542
|
+
["subseq", "subseq"],
|
|
2543
|
+
["substring", "substring"],
|
|
2544
|
+
["subtract", "subtract"],
|
|
1664
2545
|
["symbolp", "symbol_"],
|
|
1665
2546
|
["tan", "tan"],
|
|
2547
|
+
["truncate", "truncate"],
|
|
2548
|
+
["zerop", "zero_"],
|
|
2549
|
+
["1+", "oneplus"],
|
|
2550
|
+
["1-", "oneminus"],
|
|
1666
2551
|
["+", "add"],
|
|
1667
2552
|
["-", "subtract"],
|
|
1668
2553
|
["*", "multiply"],
|
|
@@ -1681,26 +2566,57 @@ var Applier = class Applier {
|
|
|
1681
2566
|
throw new Error("NullPointerException (Applier, initialize)");
|
|
1682
2567
|
}
|
|
1683
2568
|
}
|
|
2569
|
+
/**
|
|
2570
|
+
* Implementation of the Lisp `sin` function. Returns the sine of the given number.
|
|
2571
|
+
* @param args the argument Cons containing the angle in radians
|
|
2572
|
+
* @return the sine of the argument
|
|
2573
|
+
*/
|
|
1684
2574
|
sin(args) {
|
|
1685
2575
|
if (Cons.isNumber(args.car)) return Math.sin(args.car);
|
|
1686
2576
|
throw new ReferenceError(SELECT_PRINT_FUNCTION_NOT_DEFINED);
|
|
1687
2577
|
}
|
|
2578
|
+
/**
|
|
2579
|
+
* Writes a single line of spy output (with indentation) to the given stream.
|
|
2580
|
+
* @param aStream the stream to write to, or null/string to fall back to process.stdout
|
|
2581
|
+
* @param line the line to write
|
|
2582
|
+
*/
|
|
1688
2583
|
spyPrint(aStream, line) {
|
|
1689
2584
|
(aStream != null && typeof aStream === "object" && "write" in aStream ? aStream : process.stdout).write(this.indent() + line + "\n");
|
|
1690
2585
|
return null;
|
|
1691
2586
|
}
|
|
2587
|
+
/**
|
|
2588
|
+
* Implementation of the Lisp `sqrt` function. Returns the square root of the given number.
|
|
2589
|
+
* @param args the argument Cons containing the target number
|
|
2590
|
+
* @return the square root of the argument
|
|
2591
|
+
*/
|
|
1692
2592
|
sqrt(args) {
|
|
1693
2593
|
if (Cons.isNumber(args.car)) return Math.sqrt(args.car);
|
|
1694
2594
|
throw new ReferenceError(SELECT_PRINT_FUNCTION_NOT_DEFINED);
|
|
1695
2595
|
}
|
|
2596
|
+
/**
|
|
2597
|
+
* Implementation of the Lisp `stringp` predicate. Returns t if the argument is a string.
|
|
2598
|
+
* @param args the argument Cons containing the value to test
|
|
2599
|
+
* @return t if a string, nil otherwise
|
|
2600
|
+
*/
|
|
1696
2601
|
string_(args) {
|
|
1697
2602
|
if (Cons.isString(args.car)) return InterpretedSymbol.of("t");
|
|
1698
2603
|
return Cons.nil;
|
|
1699
2604
|
}
|
|
2605
|
+
/**
|
|
2606
|
+
* Implementation of the Lisp `-` / `subtract` function. Returns the difference of the given numbers.
|
|
2607
|
+
* @param args the argument Cons containing the numbers to subtract
|
|
2608
|
+
* @return the difference of the arguments
|
|
2609
|
+
*/
|
|
1700
2610
|
subtract(args) {
|
|
1701
2611
|
if (Cons.isNumber(args.car)) return this.subtract_Number(args.car, args.cdr);
|
|
1702
2612
|
throw new EvalError(cannotApply("subtract", args.car));
|
|
1703
2613
|
}
|
|
2614
|
+
/**
|
|
2615
|
+
* Helper that accumulates the difference starting from an initial number and the remaining argument list.
|
|
2616
|
+
* @param init the initial number
|
|
2617
|
+
* @param args the remaining numbers to subtract
|
|
2618
|
+
* @return init with all remaining numbers subtracted
|
|
2619
|
+
*/
|
|
1704
2620
|
subtract_Number(init, args) {
|
|
1705
2621
|
let result = init;
|
|
1706
2622
|
let aCons = args;
|
|
@@ -1712,14 +2628,30 @@ var Applier = class Applier {
|
|
|
1712
2628
|
}
|
|
1713
2629
|
return result;
|
|
1714
2630
|
}
|
|
2631
|
+
/**
|
|
2632
|
+
* Implementation of the Lisp `symbolp` predicate. Returns t if the argument is an interpreted symbol.
|
|
2633
|
+
* @param args the argument Cons containing the value to test
|
|
2634
|
+
* @return t if a symbol, nil otherwise
|
|
2635
|
+
*/
|
|
1715
2636
|
symbol_(args) {
|
|
1716
2637
|
if (Cons.isSymbol(args.car)) return InterpretedSymbol.of("t");
|
|
1717
2638
|
return Cons.nil;
|
|
1718
2639
|
}
|
|
2640
|
+
/**
|
|
2641
|
+
* Implementation of the Lisp `tan` function. Returns the tangent of the given number.
|
|
2642
|
+
* @param args the argument Cons containing the angle in radians
|
|
2643
|
+
* @return the tangent of the argument
|
|
2644
|
+
*/
|
|
1719
2645
|
tan(args) {
|
|
1720
2646
|
if (Cons.isNumber(args.car)) return Math.tan(args.car);
|
|
1721
2647
|
throw new ReferenceError(SELECT_PRINT_FUNCTION_NOT_DEFINED);
|
|
1722
2648
|
}
|
|
2649
|
+
/**
|
|
2650
|
+
* Invokes a user-defined function (lambda) bound in the environment under the given symbol.
|
|
2651
|
+
* @param procedure the symbol naming the user function
|
|
2652
|
+
* @param args the argument list
|
|
2653
|
+
* @return the result of evaluating the user function
|
|
2654
|
+
*/
|
|
1723
2655
|
userFunction(procedure, args) {
|
|
1724
2656
|
if (this.isSpy(procedure)) {
|
|
1725
2657
|
this.spyPrint(this.streamManager.spyStream(procedure), new Cons(procedure, args).toString());
|
|
@@ -1757,21 +2689,42 @@ var ExitError = class extends Error {
|
|
|
1757
2689
|
//#region src/runtime/StreamManager/index.ts
|
|
1758
2690
|
/**
|
|
1759
2691
|
* @class
|
|
1760
|
-
* @classdesc
|
|
2692
|
+
* @classdesc Manages output streams (stdout / stderr / spy / trace) used by the interpreter.
|
|
1761
2693
|
* @author Keisuke Ikeda
|
|
1762
2694
|
* @this {StreamManager}
|
|
1763
2695
|
*/
|
|
1764
|
-
var StreamManager = class {
|
|
2696
|
+
var StreamManager = class extends Object {
|
|
2697
|
+
/**
|
|
2698
|
+
* Whether tracing is currently enabled.
|
|
2699
|
+
*/
|
|
1765
2700
|
isTrace = false;
|
|
2701
|
+
/**
|
|
2702
|
+
* Map from a named stream key (e.g. "default", "stdout", "stderr") to a WritableStream.
|
|
2703
|
+
*/
|
|
1766
2704
|
streamTable;
|
|
2705
|
+
/**
|
|
2706
|
+
* Map from a spied symbol to the stream key used for that symbol's output.
|
|
2707
|
+
*/
|
|
1767
2708
|
spyTable;
|
|
2709
|
+
/**
|
|
2710
|
+
* The stream that receives trace output while tracing is on.
|
|
2711
|
+
*/
|
|
1768
2712
|
traceStream;
|
|
2713
|
+
/**
|
|
2714
|
+
* Constructor.
|
|
2715
|
+
* @constructor
|
|
2716
|
+
*/
|
|
1769
2717
|
constructor() {
|
|
2718
|
+
super();
|
|
1770
2719
|
this.streamTable = /* @__PURE__ */ new Map();
|
|
1771
2720
|
this.spyTable = /* @__PURE__ */ new Map();
|
|
1772
2721
|
this.traceStream = null;
|
|
1773
2722
|
this.initialize();
|
|
1774
2723
|
}
|
|
2724
|
+
/**
|
|
2725
|
+
* Returns the currently selected output stream (trace stream when tracing, otherwise the default).
|
|
2726
|
+
* @return the active stream, or null when none is available
|
|
2727
|
+
*/
|
|
1775
2728
|
getStream() {
|
|
1776
2729
|
let aPrintStream = null;
|
|
1777
2730
|
if (this.isTrace) return this.traceStream();
|
|
@@ -1781,6 +2734,7 @@ var StreamManager = class {
|
|
|
1781
2734
|
}
|
|
1782
2735
|
/**
|
|
1783
2736
|
* Initializes the instance variables.
|
|
2737
|
+
* @return null
|
|
1784
2738
|
*/
|
|
1785
2739
|
initialize() {
|
|
1786
2740
|
this.streamTable.set("default", process.stdout);
|
|
@@ -1788,42 +2742,85 @@ var StreamManager = class {
|
|
|
1788
2742
|
this.streamTable.set("stderr", process.stderr);
|
|
1789
2743
|
return null;
|
|
1790
2744
|
}
|
|
2745
|
+
/**
|
|
2746
|
+
* Returns whether the given symbol is being spied (or whether tracing is on, in which case every symbol is "spied").
|
|
2747
|
+
* @param aSymbol the symbol to check, or null
|
|
2748
|
+
* @return true if the symbol is spied or tracing is on
|
|
2749
|
+
*/
|
|
1791
2750
|
isSpy(aSymbol) {
|
|
1792
2751
|
if (this.isTrace) return true;
|
|
1793
2752
|
if (aSymbol != null && this.spyTable_().has(aSymbol)) return true;
|
|
1794
2753
|
return false;
|
|
1795
2754
|
}
|
|
2755
|
+
/**
|
|
2756
|
+
* Removes the given symbol from the spy table.
|
|
2757
|
+
* @param aSymbol the symbol to stop spying
|
|
2758
|
+
* @return null
|
|
2759
|
+
*/
|
|
1796
2760
|
noSpy(aSymbol) {
|
|
1797
2761
|
if (this.spyTable_().has(aSymbol)) this.spyTable_().delete(aSymbol);
|
|
1798
2762
|
return null;
|
|
1799
2763
|
}
|
|
2764
|
+
/**
|
|
2765
|
+
* Turns tracing off and clears the spy table.
|
|
2766
|
+
* @return null
|
|
2767
|
+
*/
|
|
1800
2768
|
noTrace() {
|
|
1801
2769
|
this.setIsTrace(false);
|
|
1802
2770
|
this.spyTable.clear();
|
|
1803
2771
|
return null;
|
|
1804
2772
|
}
|
|
2773
|
+
/**
|
|
2774
|
+
* Sets the tracing flag.
|
|
2775
|
+
* @param aBoolean the new value for the tracing flag
|
|
2776
|
+
* @return null
|
|
2777
|
+
*/
|
|
1805
2778
|
setIsTrace(aBoolean) {
|
|
1806
2779
|
this.isTrace = aBoolean;
|
|
1807
2780
|
return null;
|
|
1808
2781
|
}
|
|
2782
|
+
/**
|
|
2783
|
+
* Sets the trace output stream.
|
|
2784
|
+
* @param aStream the stream to send trace output to
|
|
2785
|
+
* @return null
|
|
2786
|
+
*/
|
|
1809
2787
|
setTraceStream(aStream) {
|
|
1810
2788
|
this.traceStream = aStream;
|
|
1811
2789
|
return null;
|
|
1812
2790
|
}
|
|
2791
|
+
/**
|
|
2792
|
+
* Registers the given symbol as spied with the given stream key.
|
|
2793
|
+
* @param aSymbol the symbol to spy on
|
|
2794
|
+
* @param aString the stream key (e.g. "default")
|
|
2795
|
+
* @return null
|
|
2796
|
+
*/
|
|
1813
2797
|
spy(aSymbol, aString) {
|
|
1814
2798
|
if (this.getStream() != null) this.spyTable_().set(aSymbol, aString);
|
|
1815
2799
|
return null;
|
|
1816
2800
|
}
|
|
2801
|
+
/**
|
|
2802
|
+
* Returns the stream (or stream-key string) used for the given symbol's spy output.
|
|
2803
|
+
* @param aSymbol the symbol whose spy stream is requested, or null
|
|
2804
|
+
* @return the trace stream, the registered key string, or throws if none is found
|
|
2805
|
+
*/
|
|
1817
2806
|
spyStream(aSymbol) {
|
|
1818
2807
|
if (this.isTrace) return this.traceStream;
|
|
1819
2808
|
if (aSymbol != null && this.spyTable_().has(aSymbol)) return this.spyTable_().get(aSymbol);
|
|
1820
2809
|
throw new Error("Stream is not found.");
|
|
1821
2810
|
}
|
|
2811
|
+
/**
|
|
2812
|
+
* Returns a copy of the spy table (defensive copy so callers do not mutate the internal map).
|
|
2813
|
+
* @return a new map containing the same entries as the internal spy table
|
|
2814
|
+
*/
|
|
1822
2815
|
spyTable_() {
|
|
1823
2816
|
const aTable = /* @__PURE__ */ new Map();
|
|
1824
2817
|
for (const [key, value] of this.spyTable) aTable.set(key, value);
|
|
1825
2818
|
return aTable;
|
|
1826
2819
|
}
|
|
2820
|
+
/**
|
|
2821
|
+
* Turns tracing on, routing trace output to the currently active stream.
|
|
2822
|
+
* @return null
|
|
2823
|
+
*/
|
|
1827
2824
|
trace() {
|
|
1828
2825
|
this.noTrace();
|
|
1829
2826
|
const aPrintStream = this.getStream();
|
|
@@ -1848,36 +2845,81 @@ const triggerGc = () => {
|
|
|
1848
2845
|
* @author Keisuke Ikeda
|
|
1849
2846
|
* @this {Evaluator}
|
|
1850
2847
|
*/
|
|
1851
|
-
var Evaluator = class Evaluator {
|
|
2848
|
+
var Evaluator = class Evaluator extends Object {
|
|
2849
|
+
/**
|
|
2850
|
+
* Lisp-name to method-name dispatch map for special forms.
|
|
2851
|
+
*/
|
|
1852
2852
|
static buildInFunctions = Evaluator.setup();
|
|
2853
|
+
/**
|
|
2854
|
+
* The variable binding environment used during evaluation.
|
|
2855
|
+
*/
|
|
1853
2856
|
environment;
|
|
2857
|
+
/**
|
|
2858
|
+
* The stream manager used for trace and spy output.
|
|
2859
|
+
*/
|
|
1854
2860
|
streamManager;
|
|
2861
|
+
/**
|
|
2862
|
+
* The current call depth, used for indenting trace/spy output.
|
|
2863
|
+
*/
|
|
1855
2864
|
depth;
|
|
1856
|
-
|
|
2865
|
+
/**
|
|
2866
|
+
* Registered plugins consulted by `eval` when no special form matches.
|
|
2867
|
+
*/
|
|
2868
|
+
plugins;
|
|
2869
|
+
/**
|
|
2870
|
+
* Constructor.
|
|
2871
|
+
* @param aTable the variable binding environment
|
|
2872
|
+
* @param aStreamManager the stream manager for trace and spy output
|
|
2873
|
+
* @param aNumber the initial call depth
|
|
2874
|
+
* @param plugins the plugin chain consulted before falling through to Applier
|
|
2875
|
+
*/
|
|
2876
|
+
constructor(aTable, aStreamManager, aNumber, plugins = []) {
|
|
2877
|
+
super();
|
|
1857
2878
|
this.environment = aTable;
|
|
1858
2879
|
this.streamManager = aStreamManager;
|
|
1859
2880
|
this.depth = aNumber;
|
|
2881
|
+
this.plugins = plugins;
|
|
1860
2882
|
}
|
|
2883
|
+
/**
|
|
2884
|
+
* Implementation of the Lisp `and` special form.
|
|
2885
|
+
* @param aCons the argument Cons containing the expressions to evaluate
|
|
2886
|
+
* @return nil if any expression evaluates to nil, otherwise t
|
|
2887
|
+
*/
|
|
1861
2888
|
and(aCons) {
|
|
1862
2889
|
for (const each of aCons.loop()) {
|
|
1863
|
-
const anObject = Evaluator.eval(each, this.environment, this.streamManager, this.depth);
|
|
2890
|
+
const anObject = Evaluator.eval(each, this.environment, this.streamManager, this.depth, this.plugins);
|
|
1864
2891
|
if (Cons.isNil(anObject)) return Cons.nil;
|
|
1865
2892
|
}
|
|
1866
2893
|
return InterpretedSymbol.of("t");
|
|
1867
2894
|
}
|
|
2895
|
+
/**
|
|
2896
|
+
* Implementation of the Lisp `apply` special form.
|
|
2897
|
+
* @param aCons the argument Cons containing the procedure and its argument list
|
|
2898
|
+
* @return the result of applying the procedure to the arguments
|
|
2899
|
+
*/
|
|
1868
2900
|
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);
|
|
2901
|
+
const procedure = Evaluator.eval(aCons.car, this.environment, this.streamManager, this.depth, this.plugins);
|
|
2902
|
+
const args = Evaluator.eval(aCons.nth(2), this.environment, this.streamManager, this.depth, this.plugins);
|
|
1871
2903
|
let aTable = this.environment;
|
|
1872
2904
|
if (procedure instanceof Cons && procedure.last().car instanceof Table) aTable = procedure.last().car;
|
|
1873
|
-
return Applier.apply(procedure, args, aTable, this.streamManager, this.depth);
|
|
2905
|
+
return Applier.apply(procedure, args, aTable, this.streamManager, this.depth, this.plugins);
|
|
1874
2906
|
}
|
|
2907
|
+
/**
|
|
2908
|
+
* Implementation of the Lisp `bind` special form.
|
|
2909
|
+
* @param aCons the argument Cons whose car is the symbol to look up
|
|
2910
|
+
* @return the binding count for the symbol, or nil if unbound
|
|
2911
|
+
*/
|
|
1875
2912
|
bind(aCons) {
|
|
1876
2913
|
if (Cons.isNotSymbol(aCons.car)) throw new EvalError(cannotApply("bind", aCons.car));
|
|
1877
2914
|
const aSymbol = aCons.car;
|
|
1878
2915
|
if (!this.environment.has(aSymbol)) return Cons.nil;
|
|
1879
2916
|
return this.bindAUX(aSymbol);
|
|
1880
2917
|
}
|
|
2918
|
+
/**
|
|
2919
|
+
* Counts the number of distinct bindings for the given symbol along the environment chain.
|
|
2920
|
+
* @param aSymbol the symbol whose bindings are inspected
|
|
2921
|
+
* @return the number of distinct bindings found
|
|
2922
|
+
*/
|
|
1881
2923
|
bindAUX(aSymbol) {
|
|
1882
2924
|
let aTable = this.environment;
|
|
1883
2925
|
let anObject = aTable.get(aSymbol);
|
|
@@ -1893,98 +2935,138 @@ var Evaluator = class Evaluator {
|
|
|
1893
2935
|
}
|
|
1894
2936
|
return count;
|
|
1895
2937
|
}
|
|
2938
|
+
/**
|
|
2939
|
+
* Sequentially evaluates and binds each (symbol value) pair into the given table; used by let*.
|
|
2940
|
+
* @param parameters the Cons of (symbol value) pairs to bind
|
|
2941
|
+
* @param aTable the table into which the bindings are written
|
|
2942
|
+
*/
|
|
1896
2943
|
binding(parameters, aTable) {
|
|
1897
2944
|
for (const each of parameters.loop()) {
|
|
1898
2945
|
const theCons = each;
|
|
1899
2946
|
if (Cons.isNotSymbol(theCons.car)) throw new EvalError(notSymbol(theCons.car));
|
|
1900
2947
|
const key = theCons.car;
|
|
1901
|
-
const value = Evaluator.eval(theCons.nth(2), aTable, this.streamManager, this.depth);
|
|
2948
|
+
const value = Evaluator.eval(theCons.nth(2), aTable, this.streamManager, this.depth, this.plugins);
|
|
1902
2949
|
aTable.set(key, value);
|
|
1903
2950
|
}
|
|
1904
2951
|
return null;
|
|
1905
2952
|
}
|
|
2953
|
+
/**
|
|
2954
|
+
* Evaluates all (symbol value) pairs first and then writes them into the given table in parallel; used by let.
|
|
2955
|
+
* @param parameters the Cons of (symbol value) pairs to bind
|
|
2956
|
+
* @param aTable the table into which the bindings are written
|
|
2957
|
+
*/
|
|
1906
2958
|
bindingParallel(parameters, aTable) {
|
|
1907
2959
|
const theTable = /* @__PURE__ */ new Map();
|
|
1908
2960
|
for (const each of parameters.loop()) {
|
|
1909
2961
|
const theCons = each;
|
|
1910
2962
|
if (Cons.isNotSymbol(theCons.car)) throw new EvalError(notSymbol(theCons.car));
|
|
1911
2963
|
const key = theCons.car;
|
|
1912
|
-
const value = Evaluator.eval(theCons.nth(2), aTable, this.streamManager, this.depth);
|
|
2964
|
+
const value = Evaluator.eval(theCons.nth(2), aTable, this.streamManager, this.depth, this.plugins);
|
|
1913
2965
|
theTable.set(key, value);
|
|
1914
2966
|
}
|
|
1915
2967
|
for (const [key, value] of theTable) aTable.set(key, value);
|
|
1916
2968
|
return null;
|
|
1917
2969
|
}
|
|
2970
|
+
/**
|
|
2971
|
+
* Implementation of the Lisp `cond` special form.
|
|
2972
|
+
* @param aCons the argument Cons of (test consequent...) clauses
|
|
2973
|
+
* @return the result of the first clause whose test is non-nil, or nil
|
|
2974
|
+
*/
|
|
1918
2975
|
cond(aCons) {
|
|
1919
2976
|
if (Cons.isNil(aCons)) return Cons.nil;
|
|
1920
2977
|
const consCell = aCons;
|
|
1921
2978
|
const clause = consCell.car;
|
|
1922
|
-
let anObject = Evaluator.eval(clause.car, this.environment, this.streamManager, this.depth);
|
|
2979
|
+
let anObject = Evaluator.eval(clause.car, this.environment, this.streamManager, this.depth, this.plugins);
|
|
1923
2980
|
if (Cons.isNil(anObject)) return this.cond(consCell.cdr);
|
|
1924
2981
|
const consequent = clause.cdr;
|
|
1925
|
-
for (const each of consequent.loop()) anObject = Evaluator.eval(each, this.environment, this.streamManager, this.depth);
|
|
2982
|
+
for (const each of consequent.loop()) anObject = Evaluator.eval(each, this.environment, this.streamManager, this.depth, this.plugins);
|
|
1926
2983
|
return anObject;
|
|
1927
2984
|
}
|
|
2985
|
+
/**
|
|
2986
|
+
* Implementation of the Lisp `defun` special form.
|
|
2987
|
+
* @param aCons the argument Cons containing the function name, parameter list, and body
|
|
2988
|
+
* @return the function name symbol
|
|
2989
|
+
*/
|
|
1928
2990
|
defun(aCons) {
|
|
1929
2991
|
const variable = aCons.car;
|
|
1930
2992
|
let lambda = aCons.cdr;
|
|
1931
2993
|
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);
|
|
2994
|
+
lambda = Evaluator.eval(lambda, new Table(this.environment), this.streamManager, this.depth, this.plugins);
|
|
1933
2995
|
this.environment.set(variable, lambda);
|
|
1934
2996
|
return variable;
|
|
1935
2997
|
}
|
|
2998
|
+
/**
|
|
2999
|
+
* Implementation of the Lisp `do` special form (parallel binding update).
|
|
3000
|
+
* @param aCons the argument Cons containing bindings, termination clause, and body
|
|
3001
|
+
* @return the value of the termination clause's result form
|
|
3002
|
+
*/
|
|
1936
3003
|
do_(aCons) {
|
|
1937
3004
|
const parameters = aCons.car;
|
|
1938
3005
|
const bool = aCons.nth(2);
|
|
1939
3006
|
const expressions = aCons.cdr.cdr;
|
|
1940
3007
|
this.bindingParallel(parameters, this.environment);
|
|
1941
3008
|
if (Cons.isNil(bool)) bool.setCar(Cons.nil);
|
|
1942
|
-
while (Cons.isNil(Evaluator.eval(bool.car, this.environment, this.streamManager, this.depth))) {
|
|
3009
|
+
while (Cons.isNil(Evaluator.eval(bool.car, this.environment, this.streamManager, this.depth, this.plugins))) {
|
|
1943
3010
|
const theTable = /* @__PURE__ */ new Map();
|
|
1944
|
-
for (const each of expressions.loop()) Evaluator.eval(each, this.environment, this.streamManager, this.depth);
|
|
3011
|
+
for (const each of expressions.loop()) Evaluator.eval(each, this.environment, this.streamManager, this.depth, this.plugins);
|
|
1945
3012
|
for (const each of parameters.loop()) {
|
|
1946
3013
|
const theCons = each;
|
|
1947
3014
|
if (Cons.isNotSymbol(theCons.car)) throw new EvalError(notSymbol(theCons.car));
|
|
1948
3015
|
const key = theCons.car;
|
|
1949
3016
|
if (Cons.isNotNil(theCons.nth(3))) {
|
|
1950
|
-
const value = Evaluator.eval(theCons.nth(3), this.environment, this.streamManager, this.depth);
|
|
3017
|
+
const value = Evaluator.eval(theCons.nth(3), this.environment, this.streamManager, this.depth, this.plugins);
|
|
1951
3018
|
theTable.set(key, value);
|
|
1952
3019
|
}
|
|
1953
3020
|
}
|
|
1954
3021
|
for (const [key, value] of theTable) this.environment.set(key, value);
|
|
1955
3022
|
}
|
|
1956
|
-
return Evaluator.eval(bool.nth(2), this.environment, this.streamManager, this.depth);
|
|
3023
|
+
return Evaluator.eval(bool.nth(2), this.environment, this.streamManager, this.depth, this.plugins);
|
|
1957
3024
|
}
|
|
3025
|
+
/**
|
|
3026
|
+
* Implementation of the Lisp `dolist` special form.
|
|
3027
|
+
* @param aCons the argument Cons containing the binding clause and body
|
|
3028
|
+
* @return the value of the result form
|
|
3029
|
+
*/
|
|
1958
3030
|
doList(aCons) {
|
|
1959
3031
|
const parameter = aCons.car;
|
|
1960
3032
|
const theCons = aCons.cdr;
|
|
1961
|
-
const args = Evaluator.eval(parameter.nth(2), this.environment, this.streamManager, this.depth);
|
|
3033
|
+
const args = Evaluator.eval(parameter.nth(2), this.environment, this.streamManager, this.depth, this.plugins);
|
|
1962
3034
|
for (const element of args.loop()) {
|
|
1963
3035
|
this.environment.set(parameter.car, element);
|
|
1964
|
-
for (const each of theCons.loop()) Evaluator.eval(each, this.environment, this.streamManager, this.depth);
|
|
3036
|
+
for (const each of theCons.loop()) Evaluator.eval(each, this.environment, this.streamManager, this.depth, this.plugins);
|
|
1965
3037
|
}
|
|
1966
|
-
return Evaluator.eval(parameter.nth(3), this.environment, this.streamManager, this.depth);
|
|
3038
|
+
return Evaluator.eval(parameter.nth(3), this.environment, this.streamManager, this.depth, this.plugins);
|
|
1967
3039
|
}
|
|
3040
|
+
/**
|
|
3041
|
+
* Implementation of the Lisp `do*` special form (sequential binding update).
|
|
3042
|
+
* @param aCons the argument Cons containing bindings, termination clause, and body
|
|
3043
|
+
* @return the value of the termination clause's result form
|
|
3044
|
+
*/
|
|
1968
3045
|
doStar(aCons) {
|
|
1969
3046
|
const parameters = aCons.car;
|
|
1970
3047
|
const bool = aCons.nth(2);
|
|
1971
3048
|
const expressions = aCons.cdr.cdr;
|
|
1972
3049
|
this.binding(parameters, this.environment);
|
|
1973
3050
|
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);
|
|
3051
|
+
while (Cons.isNil(Evaluator.eval(bool.car, this.environment, this.streamManager, this.depth, this.plugins))) {
|
|
3052
|
+
for (const each of expressions.loop()) Evaluator.eval(each, this.environment, this.streamManager, this.depth, this.plugins);
|
|
1976
3053
|
for (const each of parameters.loop()) {
|
|
1977
3054
|
const theCons = each;
|
|
1978
3055
|
if (Cons.isNotSymbol(theCons.car)) throw new EvalError(notSymbol(theCons.car));
|
|
1979
3056
|
const key = theCons.car;
|
|
1980
3057
|
if (Cons.isNotNil(theCons.nth(3))) {
|
|
1981
|
-
const value = Evaluator.eval(theCons.nth(3), this.environment, this.streamManager, this.depth);
|
|
3058
|
+
const value = Evaluator.eval(theCons.nth(3), this.environment, this.streamManager, this.depth, this.plugins);
|
|
1982
3059
|
this.environment.set(key, value);
|
|
1983
3060
|
}
|
|
1984
3061
|
}
|
|
1985
3062
|
}
|
|
1986
|
-
return Evaluator.eval(bool.nth(2), this.environment, this.streamManager, this.depth);
|
|
3063
|
+
return Evaluator.eval(bool.nth(2), this.environment, this.streamManager, this.depth, this.plugins);
|
|
1987
3064
|
}
|
|
3065
|
+
/**
|
|
3066
|
+
* Evaluates a procedure call by delegating to the Applier after evaluating each argument.
|
|
3067
|
+
* @param form the call form whose car is the procedure and whose cdr is the argument list
|
|
3068
|
+
* @return the result of applying the procedure
|
|
3069
|
+
*/
|
|
1988
3070
|
entrustApplier(form) {
|
|
1989
3071
|
const aCons = form.cdr;
|
|
1990
3072
|
let args = new Cons(Cons.nil, Cons.nil);
|
|
@@ -1997,25 +3079,84 @@ var Evaluator = class Evaluator {
|
|
|
1997
3079
|
}
|
|
1998
3080
|
for (const each of aCons.loop()) {
|
|
1999
3081
|
if (each instanceof Table) break;
|
|
2000
|
-
args.add(Evaluator.eval(each, this.environment, this.streamManager, this.depth));
|
|
3082
|
+
args.add(Evaluator.eval(each, this.environment, this.streamManager, this.depth, this.plugins));
|
|
2001
3083
|
}
|
|
2002
3084
|
if (this.isSpy(aSymbol)) this.setDepth(this.depth - 1);
|
|
2003
3085
|
args = args.cdr;
|
|
2004
|
-
return Applier.apply(procedure, args, this.environment, this.streamManager, this.depth);
|
|
3086
|
+
return Applier.apply(procedure, args, this.environment, this.streamManager, this.depth, this.plugins);
|
|
2005
3087
|
}
|
|
2006
|
-
|
|
2007
|
-
|
|
3088
|
+
/**
|
|
3089
|
+
* Evaluates the given form in the given environment.
|
|
3090
|
+
* @param form the form to evaluate
|
|
3091
|
+
* @param environment the variable binding environment
|
|
3092
|
+
* @param aStreamManager the stream manager for trace and spy output
|
|
3093
|
+
* @param depth the current call depth
|
|
3094
|
+
* @param plugins the plugin chain consulted before falling through to Applier
|
|
3095
|
+
* @return the evaluation result
|
|
3096
|
+
*/
|
|
3097
|
+
static eval(form, environment, aStreamManager = new StreamManager(), depth = 1, plugins = []) {
|
|
3098
|
+
return new Evaluator(environment, aStreamManager, depth, plugins).eval(form);
|
|
2008
3099
|
}
|
|
3100
|
+
/**
|
|
3101
|
+
* Evaluates the given form using this Evaluator's environment.
|
|
3102
|
+
* @param form the form to evaluate
|
|
3103
|
+
* @return the evaluation result
|
|
3104
|
+
*/
|
|
2009
3105
|
eval(form) {
|
|
2010
3106
|
if (Cons.isSymbol(form)) return this.evaluateSymbol(form);
|
|
2011
3107
|
if (Cons.isNil(form) || Cons.isNotList(form)) return form;
|
|
2012
3108
|
const formCons = form;
|
|
2013
3109
|
if (Cons.isSymbol(formCons.car) && Evaluator.buildInFunctions.has(formCons.car)) return this.specialForm(formCons);
|
|
3110
|
+
if (Cons.isSymbol(formCons.car) && this.plugins.length > 0) {
|
|
3111
|
+
const symbol = formCons.car;
|
|
3112
|
+
const plugin = this.plugins.find((p) => p.has(symbol));
|
|
3113
|
+
if (plugin !== void 0) return this.entrustPlugin(plugin, formCons);
|
|
3114
|
+
}
|
|
2014
3115
|
return this.entrustApplier(formCons);
|
|
2015
3116
|
}
|
|
3117
|
+
/**
|
|
3118
|
+
* Evaluates the argument list (the same way `entrustApplier` does), then
|
|
3119
|
+
* delegates the call to the matched plugin with a context that allows
|
|
3120
|
+
* recursive evaluation.
|
|
3121
|
+
* @param plugin the plugin that claimed the call symbol
|
|
3122
|
+
* @param form the call form whose car is the symbol and whose cdr is the argument list
|
|
3123
|
+
* @return the result returned by the plugin
|
|
3124
|
+
*/
|
|
3125
|
+
entrustPlugin(plugin, form) {
|
|
3126
|
+
const aCons = form.cdr;
|
|
3127
|
+
let args = new Cons(Cons.nil, Cons.nil);
|
|
3128
|
+
const symbol = form.car;
|
|
3129
|
+
if (this.isSpy(symbol)) {
|
|
3130
|
+
this.spyPrint(this.streamManager.spyStream(symbol), form.toString());
|
|
3131
|
+
this.setDepth(this.depth + 1);
|
|
3132
|
+
}
|
|
3133
|
+
for (const each of aCons.loop()) {
|
|
3134
|
+
if (each instanceof Table) break;
|
|
3135
|
+
args.add(Evaluator.eval(each, this.environment, this.streamManager, this.depth, this.plugins));
|
|
3136
|
+
}
|
|
3137
|
+
if (this.isSpy(symbol)) this.setDepth(this.depth - 1);
|
|
3138
|
+
args = args.cdr;
|
|
3139
|
+
const ctx = {
|
|
3140
|
+
environment: this.environment,
|
|
3141
|
+
streamManager: this.streamManager,
|
|
3142
|
+
depth: this.depth,
|
|
3143
|
+
eval: (subForm) => Evaluator.eval(subForm, this.environment, this.streamManager, this.depth, this.plugins)
|
|
3144
|
+
};
|
|
3145
|
+
return plugin.apply(symbol, args, ctx);
|
|
3146
|
+
}
|
|
3147
|
+
/**
|
|
3148
|
+
* Implementation of the Lisp `eval` special form.
|
|
3149
|
+
* @param aCons the argument Cons whose car is the form to evaluate twice
|
|
3150
|
+
* @return the result of evaluating the form
|
|
3151
|
+
*/
|
|
2016
3152
|
eval_lisp(aCons) {
|
|
2017
|
-
return Evaluator.eval(Evaluator.eval(aCons.car, this.environment, this.streamManager, this.depth), this.environment, this.streamManager, this.depth);
|
|
3153
|
+
return Evaluator.eval(Evaluator.eval(aCons.car, this.environment, this.streamManager, this.depth, this.plugins), this.environment, this.streamManager, this.depth, this.plugins);
|
|
2018
3154
|
}
|
|
3155
|
+
/**
|
|
3156
|
+
* Resolves the value bound to the given symbol in the current environment.
|
|
3157
|
+
* @param aSymbol the symbol to resolve
|
|
3158
|
+
* @return the value bound to the symbol
|
|
3159
|
+
*/
|
|
2019
3160
|
evaluateSymbol(aSymbol) {
|
|
2020
3161
|
if (!this.environment.has(aSymbol)) throw new EvalError(noBinding(aSymbol));
|
|
2021
3162
|
if (this.isSpy(aSymbol)) {
|
|
@@ -2030,10 +3171,17 @@ var Evaluator = class Evaluator {
|
|
|
2030
3171
|
}
|
|
2031
3172
|
return answer;
|
|
2032
3173
|
}
|
|
3174
|
+
/**
|
|
3175
|
+
* Implementation of the Lisp `exit` special form; terminates the REPL by throwing an ExitError.
|
|
3176
|
+
*/
|
|
2033
3177
|
exit() {
|
|
2034
3178
|
console.log("Bye!");
|
|
2035
3179
|
throw new ExitError();
|
|
2036
3180
|
}
|
|
3181
|
+
/**
|
|
3182
|
+
* Implementation of the Lisp `gc` special form; triggers garbage collection and returns memory usage.
|
|
3183
|
+
* @return an association list of memory usage statistics
|
|
3184
|
+
*/
|
|
2037
3185
|
gc() {
|
|
2038
3186
|
triggerGc();
|
|
2039
3187
|
const usage = process.memoryUsage();
|
|
@@ -2047,110 +3195,198 @@ var Evaluator = class Evaluator {
|
|
|
2047
3195
|
for (const entry of entries) result = new Cons(entry, result);
|
|
2048
3196
|
return result;
|
|
2049
3197
|
}
|
|
3198
|
+
/**
|
|
3199
|
+
* Implementation of the Lisp `if` special form.
|
|
3200
|
+
* @param aCons the argument Cons containing the test, then-form, and else-form
|
|
3201
|
+
* @return the result of evaluating the selected branch
|
|
3202
|
+
*/
|
|
2050
3203
|
if_(aCons) {
|
|
2051
|
-
const bool = Evaluator.eval(aCons.car, this.environment, this.streamManager, this.depth);
|
|
3204
|
+
const bool = Evaluator.eval(aCons.car, this.environment, this.streamManager, this.depth, this.plugins);
|
|
2052
3205
|
const anObject = Cons.isNil(bool) ? aCons.nth(3) : aCons.nth(2);
|
|
2053
|
-
return Evaluator.eval(anObject, this.environment, this.streamManager, this.depth);
|
|
3206
|
+
return Evaluator.eval(anObject, this.environment, this.streamManager, this.depth, this.plugins);
|
|
2054
3207
|
}
|
|
3208
|
+
/**
|
|
3209
|
+
* Returns the indentation string used for trace and spy output at the current depth.
|
|
3210
|
+
* @return the indentation string
|
|
3211
|
+
*/
|
|
2055
3212
|
indent() {
|
|
2056
3213
|
let index = 0;
|
|
2057
3214
|
let aString = "";
|
|
2058
3215
|
while (index++ < this.depth) aString += "| ";
|
|
2059
3216
|
return aString;
|
|
2060
3217
|
}
|
|
3218
|
+
/**
|
|
3219
|
+
* Returns whether the given symbol is currently being spied on.
|
|
3220
|
+
* @param aSymbol the symbol to check
|
|
3221
|
+
* @return a boolean
|
|
3222
|
+
*/
|
|
2061
3223
|
isSpy(aSymbol) {
|
|
2062
3224
|
if (aSymbol == null) return false;
|
|
2063
3225
|
return this.streamManager.isSpy(aSymbol);
|
|
2064
3226
|
}
|
|
3227
|
+
/**
|
|
3228
|
+
* Implementation of the Lisp `lambda` special form; captures the current environment as a closure.
|
|
3229
|
+
* @param args the argument Cons containing the parameter list and body
|
|
3230
|
+
* @return a lambda form with the captured environment appended
|
|
3231
|
+
*/
|
|
2065
3232
|
lambda(args) {
|
|
2066
3233
|
const aCons = Cons.cloneValue(args);
|
|
2067
3234
|
aCons.cdr.setCdr(new Cons(this.environment, Cons.nil));
|
|
2068
3235
|
return new Cons(InterpretedSymbol.of("lambda"), aCons);
|
|
2069
3236
|
}
|
|
3237
|
+
/**
|
|
3238
|
+
* Implementation of the Lisp `let` special form (parallel binding).
|
|
3239
|
+
* @param aCons the argument Cons containing bindings and body
|
|
3240
|
+
* @return the value of the last body form
|
|
3241
|
+
*/
|
|
2070
3242
|
let(aCons) {
|
|
2071
3243
|
const aTable = new Table(this.environment);
|
|
2072
3244
|
const parameters = aCons.car;
|
|
2073
3245
|
const forms = aCons.cdr;
|
|
2074
3246
|
let anObject = Cons.nil;
|
|
2075
3247
|
this.bindingParallel(parameters, aTable);
|
|
2076
|
-
for (const each of forms.loop()) anObject = Evaluator.eval(each, aTable, this.streamManager, this.depth);
|
|
3248
|
+
for (const each of forms.loop()) anObject = Evaluator.eval(each, aTable, this.streamManager, this.depth, this.plugins);
|
|
2077
3249
|
return anObject;
|
|
2078
3250
|
}
|
|
3251
|
+
/**
|
|
3252
|
+
* Implementation of the Lisp `let*` special form (sequential binding).
|
|
3253
|
+
* @param aCons the argument Cons containing bindings and body
|
|
3254
|
+
* @return the value of the last body form
|
|
3255
|
+
*/
|
|
2079
3256
|
letStar(aCons) {
|
|
2080
3257
|
const aTable = new Table(this.environment);
|
|
2081
3258
|
const parameters = aCons.car;
|
|
2082
3259
|
const forms = aCons.cdr;
|
|
2083
3260
|
let anObject = Cons.nil;
|
|
2084
3261
|
this.binding(parameters, aTable);
|
|
2085
|
-
for (const each of forms.loop()) anObject = Evaluator.eval(each, aTable, this.streamManager, this.depth);
|
|
3262
|
+
for (const each of forms.loop()) anObject = Evaluator.eval(each, aTable, this.streamManager, this.depth, this.plugins);
|
|
2086
3263
|
return anObject;
|
|
2087
3264
|
}
|
|
3265
|
+
/**
|
|
3266
|
+
* Implementation of the Lisp `not` special form.
|
|
3267
|
+
* @param aCons the argument Cons whose car is the expression to negate
|
|
3268
|
+
* @return t if the expression evaluates to nil, otherwise nil
|
|
3269
|
+
*/
|
|
2088
3270
|
not(aCons) {
|
|
2089
|
-
if (Cons.isNil(Evaluator.eval(aCons.car, this.environment, this.streamManager, this.depth))) return InterpretedSymbol.of("t");
|
|
3271
|
+
if (Cons.isNil(Evaluator.eval(aCons.car, this.environment, this.streamManager, this.depth, this.plugins))) return InterpretedSymbol.of("t");
|
|
2090
3272
|
return Cons.nil;
|
|
2091
3273
|
}
|
|
3274
|
+
/**
|
|
3275
|
+
* Implementation of the Lisp `notrace` special form; disables tracing.
|
|
3276
|
+
* @return the symbol t
|
|
3277
|
+
*/
|
|
2092
3278
|
notrace() {
|
|
2093
3279
|
this.streamManager.noTrace();
|
|
2094
3280
|
return InterpretedSymbol.of("t");
|
|
2095
3281
|
}
|
|
3282
|
+
/**
|
|
3283
|
+
* Implementation of the Lisp `or` special form.
|
|
3284
|
+
* @param aCons the argument Cons containing the expressions to evaluate
|
|
3285
|
+
* @return t if any expression evaluates to non-nil, otherwise nil
|
|
3286
|
+
*/
|
|
2096
3287
|
or(aCons) {
|
|
2097
3288
|
for (const each of aCons.loop()) {
|
|
2098
|
-
const anObject = Evaluator.eval(each, this.environment, this.streamManager, this.depth);
|
|
3289
|
+
const anObject = Evaluator.eval(each, this.environment, this.streamManager, this.depth, this.plugins);
|
|
2099
3290
|
if (Cons.isNotNil(anObject)) return InterpretedSymbol.of("t");
|
|
2100
3291
|
}
|
|
2101
3292
|
return Cons.nil;
|
|
2102
3293
|
}
|
|
3294
|
+
/**
|
|
3295
|
+
* Implementation of the Lisp `pop` special form.
|
|
3296
|
+
* @param aCons the argument Cons whose car is the symbol bound to a list
|
|
3297
|
+
* @return the popped element, or nil if the binding is not a Cons
|
|
3298
|
+
*/
|
|
2103
3299
|
pop_(aCons) {
|
|
2104
3300
|
if (Cons.isNotSymbol(aCons.car)) throw new EvalError(argumentNotSymbol(1));
|
|
2105
3301
|
const aSymbol = aCons.car;
|
|
2106
|
-
const anObject = Evaluator.eval(aSymbol, this.environment, this.streamManager, this.depth);
|
|
3302
|
+
const anObject = Evaluator.eval(aSymbol, this.environment, this.streamManager, this.depth, this.plugins);
|
|
2107
3303
|
if (Cons.isNotCons(anObject)) return Cons.nil;
|
|
2108
3304
|
const consObject = anObject;
|
|
2109
3305
|
this.environment.setIfExist(aSymbol, consObject.cdr);
|
|
2110
3306
|
return consObject.car;
|
|
2111
3307
|
}
|
|
3308
|
+
/**
|
|
3309
|
+
* Implementation of the Lisp `progn` special form.
|
|
3310
|
+
* @param aCons the argument Cons containing the body expressions
|
|
3311
|
+
* @return the value of the last body form, or nil if there are none
|
|
3312
|
+
*/
|
|
2112
3313
|
progn(aCons) {
|
|
2113
3314
|
let anObject = Cons.nil;
|
|
2114
|
-
for (const each of aCons.loop()) anObject = Evaluator.eval(each, this.environment, this.streamManager, this.depth);
|
|
3315
|
+
for (const each of aCons.loop()) anObject = Evaluator.eval(each, this.environment, this.streamManager, this.depth, this.plugins);
|
|
2115
3316
|
return anObject;
|
|
2116
3317
|
}
|
|
3318
|
+
/**
|
|
3319
|
+
* Implementation of the Lisp `princ` special form; writes the evaluated argument without a trailing newline.
|
|
3320
|
+
* @param aCons the argument Cons whose car is the expression to print
|
|
3321
|
+
* @return the printed value
|
|
3322
|
+
*/
|
|
2117
3323
|
princ(aCons) {
|
|
2118
|
-
const anObject = Evaluator.eval(aCons.car, this.environment, this.streamManager, this.depth);
|
|
3324
|
+
const anObject = Evaluator.eval(aCons.car, this.environment, this.streamManager, this.depth, this.plugins);
|
|
2119
3325
|
process.stdout.write(String(anObject));
|
|
2120
3326
|
return anObject;
|
|
2121
3327
|
}
|
|
3328
|
+
/**
|
|
3329
|
+
* Implementation of the Lisp `print` special form; writes the evaluated argument followed by a newline.
|
|
3330
|
+
* @param aCons the argument Cons whose car is the expression to print
|
|
3331
|
+
* @return the printed value
|
|
3332
|
+
*/
|
|
2122
3333
|
print(aCons) {
|
|
2123
|
-
const anObject = Evaluator.eval(aCons.car, this.environment, this.streamManager, this.depth);
|
|
3334
|
+
const anObject = Evaluator.eval(aCons.car, this.environment, this.streamManager, this.depth, this.plugins);
|
|
2124
3335
|
process.stdout.write(String(anObject) + "\n");
|
|
2125
3336
|
return anObject;
|
|
2126
3337
|
}
|
|
3338
|
+
/**
|
|
3339
|
+
* Implementation of the Lisp `push` special form.
|
|
3340
|
+
* @param aCons the argument Cons containing the value to push and the target symbol
|
|
3341
|
+
* @return the new Cons stored in the symbol
|
|
3342
|
+
*/
|
|
2127
3343
|
push_(aCons) {
|
|
2128
|
-
let anObject = Evaluator.eval(aCons.car, this.environment, this.streamManager, this.depth);
|
|
3344
|
+
let anObject = Evaluator.eval(aCons.car, this.environment, this.streamManager, this.depth, this.plugins);
|
|
2129
3345
|
if (Cons.isNotSymbol(aCons.nth(2))) throw new EvalError(argumentNotSymbol(2));
|
|
2130
3346
|
const aSymbol = aCons.nth(2);
|
|
2131
|
-
anObject = new Cons(anObject, Evaluator.eval(aSymbol, this.environment, this.streamManager, this.depth));
|
|
3347
|
+
anObject = new Cons(anObject, Evaluator.eval(aSymbol, this.environment, this.streamManager, this.depth, this.plugins));
|
|
2132
3348
|
this.environment.setIfExist(aSymbol, anObject);
|
|
2133
3349
|
return anObject;
|
|
2134
3350
|
}
|
|
3351
|
+
/**
|
|
3352
|
+
* Implementation of the Lisp `quote` special form.
|
|
3353
|
+
* @param aCons the argument Cons whose car is the form to return unevaluated
|
|
3354
|
+
* @return the quoted form
|
|
3355
|
+
*/
|
|
2135
3356
|
quote(aCons) {
|
|
2136
3357
|
return aCons.car;
|
|
2137
3358
|
}
|
|
3359
|
+
/**
|
|
3360
|
+
* Implementation of the Lisp `rplaca` special form; destructively replaces the car of a Cons.
|
|
3361
|
+
* @param args the argument Cons containing the target Cons expression and the new car value
|
|
3362
|
+
* @return the modified Cons
|
|
3363
|
+
*/
|
|
2138
3364
|
rplaca(args) {
|
|
2139
|
-
let anObject = Evaluator.eval(args.car, this.environment, this.streamManager, this.depth);
|
|
3365
|
+
let anObject = Evaluator.eval(args.car, this.environment, this.streamManager, this.depth, this.plugins);
|
|
2140
3366
|
if (Cons.isNotCons(anObject)) throw new EvalError(cannotApply("set-car!", anObject));
|
|
2141
3367
|
const aCons = anObject;
|
|
2142
|
-
anObject = Evaluator.eval(args.nth(2), this.environment, this.streamManager, this.depth);
|
|
3368
|
+
anObject = Evaluator.eval(args.nth(2), this.environment, this.streamManager, this.depth, this.plugins);
|
|
2143
3369
|
aCons.setCar(anObject);
|
|
2144
|
-
return Evaluator.eval(args.car, this.environment, this.streamManager, this.depth);
|
|
3370
|
+
return Evaluator.eval(args.car, this.environment, this.streamManager, this.depth, this.plugins);
|
|
2145
3371
|
}
|
|
3372
|
+
/**
|
|
3373
|
+
* Implementation of the Lisp `rplacd` special form; destructively replaces the cdr of a Cons.
|
|
3374
|
+
* @param args the argument Cons containing the target Cons expression and the new cdr value
|
|
3375
|
+
* @return the modified Cons
|
|
3376
|
+
*/
|
|
2146
3377
|
rplacd(args) {
|
|
2147
|
-
let anObject = Evaluator.eval(args.car, this.environment, this.streamManager, this.depth);
|
|
3378
|
+
let anObject = Evaluator.eval(args.car, this.environment, this.streamManager, this.depth, this.plugins);
|
|
2148
3379
|
if (Cons.isNotCons(anObject)) throw new EvalError(cannotApply("set-cdr!", anObject));
|
|
2149
3380
|
const aCons = anObject;
|
|
2150
|
-
anObject = Evaluator.eval(args.nth(2), this.environment, this.streamManager, this.depth);
|
|
3381
|
+
anObject = Evaluator.eval(args.nth(2), this.environment, this.streamManager, this.depth, this.plugins);
|
|
2151
3382
|
aCons.setCdr(anObject);
|
|
2152
|
-
return Evaluator.eval(args.car, this.environment, this.streamManager, this.depth);
|
|
3383
|
+
return Evaluator.eval(args.car, this.environment, this.streamManager, this.depth, this.plugins);
|
|
2153
3384
|
}
|
|
3385
|
+
/**
|
|
3386
|
+
* Implementation of the Lisp `setq` special form; assigns values in the local environment.
|
|
3387
|
+
* @param args the argument Cons containing alternating (symbol value) pairs
|
|
3388
|
+
* @return the last assigned value
|
|
3389
|
+
*/
|
|
2154
3390
|
setq(args) {
|
|
2155
3391
|
let anObject = Cons.nil;
|
|
2156
3392
|
const anIterator = args.loop();
|
|
@@ -2158,26 +3394,39 @@ var Evaluator = class Evaluator {
|
|
|
2158
3394
|
if (!Cons.isSymbol(args.nth(1))) throw new EvalError(notSymbol(args.car));
|
|
2159
3395
|
const key = anIterator.next();
|
|
2160
3396
|
if (!anIterator.hasNext()) throw new EvalError(SIZES_DO_NOT_MATCH);
|
|
2161
|
-
anObject = Evaluator.eval(anIterator.next(), this.environment, this.streamManager, this.depth);
|
|
3397
|
+
anObject = Evaluator.eval(anIterator.next(), this.environment, this.streamManager, this.depth, this.plugins);
|
|
2162
3398
|
this.environment.set(key, anObject);
|
|
2163
3399
|
}
|
|
2164
3400
|
return anObject;
|
|
2165
3401
|
}
|
|
3402
|
+
/**
|
|
3403
|
+
* Implementation of the Lisp `set-allq` special form; assigns values in the binding's owning scope.
|
|
3404
|
+
* @param args the argument Cons containing alternating (symbol value) pairs
|
|
3405
|
+
* @return the last assigned value
|
|
3406
|
+
*/
|
|
2166
3407
|
set_allq(args) {
|
|
2167
3408
|
let anObject = Cons.nil;
|
|
2168
3409
|
const anIterator = args.loop();
|
|
2169
3410
|
while (anIterator.hasNext()) {
|
|
2170
3411
|
if (!Cons.isSymbol(args.nth(1))) throw new EvalError(notSymbol(args.car));
|
|
2171
3412
|
const key = anIterator.next();
|
|
2172
|
-
anObject = Evaluator.eval(anIterator.next(), this.environment, this.streamManager, this.depth);
|
|
3413
|
+
anObject = Evaluator.eval(anIterator.next(), this.environment, this.streamManager, this.depth, this.plugins);
|
|
2173
3414
|
this.environment.setIfExist(key, anObject);
|
|
2174
3415
|
}
|
|
2175
3416
|
return anObject;
|
|
2176
3417
|
}
|
|
3418
|
+
/**
|
|
3419
|
+
* Sets the current call depth used for trace and spy indentation.
|
|
3420
|
+
* @param aNumber the new depth
|
|
3421
|
+
*/
|
|
2177
3422
|
setDepth(aNumber) {
|
|
2178
3423
|
this.depth = aNumber;
|
|
2179
3424
|
return null;
|
|
2180
3425
|
}
|
|
3426
|
+
/**
|
|
3427
|
+
* Builds and returns the Lisp-name to method-name dispatch map for special forms.
|
|
3428
|
+
* @return the dispatch map
|
|
3429
|
+
*/
|
|
2181
3430
|
static setup() {
|
|
2182
3431
|
try {
|
|
2183
3432
|
return new Map([
|
|
@@ -2219,6 +3468,11 @@ var Evaluator = class Evaluator {
|
|
|
2219
3468
|
throw new Error("NullPointerException (Evaluator, initialize)");
|
|
2220
3469
|
}
|
|
2221
3470
|
}
|
|
3471
|
+
/**
|
|
3472
|
+
* Dispatches a special-form call to the corresponding method via the build-in dispatch map.
|
|
3473
|
+
* @param form the form whose car is the special-form symbol
|
|
3474
|
+
* @return the result of the special-form method
|
|
3475
|
+
*/
|
|
2222
3476
|
specialForm(form) {
|
|
2223
3477
|
const aSymbol = form.car;
|
|
2224
3478
|
if (this.isSpy(aSymbol)) {
|
|
@@ -2237,37 +3491,65 @@ var Evaluator = class Evaluator {
|
|
|
2237
3491
|
}
|
|
2238
3492
|
return answer;
|
|
2239
3493
|
}
|
|
3494
|
+
/**
|
|
3495
|
+
* Writes a trace/spy line to the given stream (or stdout) with the current indentation.
|
|
3496
|
+
* @param aStream the destination stream, or null/string to fall back to stdout
|
|
3497
|
+
* @param line the line to write
|
|
3498
|
+
*/
|
|
2240
3499
|
spyPrint(aStream, line) {
|
|
2241
3500
|
(aStream != null && typeof aStream === "object" && "write" in aStream ? aStream : process.stdout).write(this.indent() + line + "\n");
|
|
2242
3501
|
return null;
|
|
2243
3502
|
}
|
|
3503
|
+
/**
|
|
3504
|
+
* Implementation of the Lisp `terpri` special form; writes a newline to stdout.
|
|
3505
|
+
* @return the symbol t
|
|
3506
|
+
*/
|
|
2244
3507
|
terpri() {
|
|
2245
3508
|
process.stdout.write("\n");
|
|
2246
3509
|
return InterpretedSymbol.of("t");
|
|
2247
3510
|
}
|
|
3511
|
+
/**
|
|
3512
|
+
* Implementation of the Lisp `time` special form; measures evaluation time in milliseconds.
|
|
3513
|
+
* @param aCons the argument Cons whose car is the form to time
|
|
3514
|
+
* @return the elapsed time in milliseconds
|
|
3515
|
+
*/
|
|
2248
3516
|
time(aCons) {
|
|
2249
3517
|
const start = process.hrtime();
|
|
2250
|
-
Evaluator.eval(aCons.car, this.environment, this.streamManager, this.depth);
|
|
3518
|
+
Evaluator.eval(aCons.car, this.environment, this.streamManager, this.depth, this.plugins);
|
|
2251
3519
|
return process.hrtime(start)[1] / 1e6;
|
|
2252
3520
|
}
|
|
3521
|
+
/**
|
|
3522
|
+
* Implementation of the Lisp `trace` special form; enables tracing.
|
|
3523
|
+
* @return the symbol t
|
|
3524
|
+
*/
|
|
2253
3525
|
trace() {
|
|
2254
3526
|
this.streamManager.trace();
|
|
2255
3527
|
return InterpretedSymbol.of("t");
|
|
2256
3528
|
}
|
|
3529
|
+
/**
|
|
3530
|
+
* Implementation of the Lisp `unless` special form.
|
|
3531
|
+
* @param aCons the argument Cons containing the test and body
|
|
3532
|
+
* @return the value of the last body form if the test is nil, otherwise nil
|
|
3533
|
+
*/
|
|
2257
3534
|
unless(aCons) {
|
|
2258
3535
|
let anObject = Cons.nil;
|
|
2259
3536
|
const theCons = aCons.cdr;
|
|
2260
|
-
const flag = Evaluator.eval(aCons.car, this.environment, this.streamManager, this.depth);
|
|
3537
|
+
const flag = Evaluator.eval(aCons.car, this.environment, this.streamManager, this.depth, this.plugins);
|
|
2261
3538
|
if (Cons.isNotNil(flag)) return Cons.nil;
|
|
2262
|
-
for (const each of theCons.loop()) anObject = Evaluator.eval(each, this.environment, this.streamManager, this.depth);
|
|
3539
|
+
for (const each of theCons.loop()) anObject = Evaluator.eval(each, this.environment, this.streamManager, this.depth, this.plugins);
|
|
2263
3540
|
return anObject;
|
|
2264
3541
|
}
|
|
3542
|
+
/**
|
|
3543
|
+
* Implementation of the Lisp `when` special form.
|
|
3544
|
+
* @param aCons the argument Cons containing the test and body
|
|
3545
|
+
* @return the value of the last body form if the test is non-nil, otherwise nil
|
|
3546
|
+
*/
|
|
2265
3547
|
when(aCons) {
|
|
2266
3548
|
let anObject = Cons.nil;
|
|
2267
3549
|
const theCons = aCons.cdr;
|
|
2268
|
-
const flag = Evaluator.eval(aCons.car, this.environment, this.streamManager, this.depth);
|
|
3550
|
+
const flag = Evaluator.eval(aCons.car, this.environment, this.streamManager, this.depth, this.plugins);
|
|
2269
3551
|
if (Cons.isNil(flag)) return Cons.nil;
|
|
2270
|
-
for (const each of theCons.loop()) anObject = Evaluator.eval(each, this.environment, this.streamManager, this.depth);
|
|
3552
|
+
for (const each of theCons.loop()) anObject = Evaluator.eval(each, this.environment, this.streamManager, this.depth, this.plugins);
|
|
2271
3553
|
return anObject;
|
|
2272
3554
|
}
|
|
2273
3555
|
};
|
|
@@ -2279,23 +3561,54 @@ var Evaluator = class Evaluator {
|
|
|
2279
3561
|
* @author Keisuke Ikeda
|
|
2280
3562
|
* @this {LispInterpreter}
|
|
2281
3563
|
*/
|
|
2282
|
-
var LispInterpreter = class {
|
|
3564
|
+
var LispInterpreter = class extends Object {
|
|
3565
|
+
/**
|
|
3566
|
+
* The root (top-level) environment table, pre-populated with built-in symbols.
|
|
3567
|
+
*/
|
|
2283
3568
|
root;
|
|
3569
|
+
/**
|
|
3570
|
+
* The stream manager that owns the interpreter's output / spy streams.
|
|
3571
|
+
*/
|
|
2284
3572
|
streamManager;
|
|
3573
|
+
/**
|
|
3574
|
+
* Registered plugins consulted by the evaluator on every call. See `use`.
|
|
3575
|
+
*/
|
|
3576
|
+
plugins;
|
|
3577
|
+
/**
|
|
3578
|
+
* Constructor.
|
|
3579
|
+
* @constructor
|
|
3580
|
+
*/
|
|
2285
3581
|
constructor() {
|
|
3582
|
+
super();
|
|
2286
3583
|
this.root = this.initializeTable();
|
|
2287
3584
|
this.streamManager = new StreamManager();
|
|
3585
|
+
this.plugins = [];
|
|
3586
|
+
}
|
|
3587
|
+
/**
|
|
3588
|
+
* Registers a plugin. Subsequent `eval` calls will consult the plugin chain
|
|
3589
|
+
* (in registration order, first match wins) when no special form matches a
|
|
3590
|
+
* symbol, before falling through to the Applier built-ins.
|
|
3591
|
+
* @param plugin the plugin to register
|
|
3592
|
+
* @return this interpreter, for chaining
|
|
3593
|
+
*/
|
|
3594
|
+
use(plugin) {
|
|
3595
|
+
this.plugins.push(plugin);
|
|
3596
|
+
return this;
|
|
2288
3597
|
}
|
|
2289
3598
|
/**
|
|
2290
3599
|
* Evaluates the given expression and returns the result. Throws `ParseError`,
|
|
2291
3600
|
* `EvalError`, or `ExitError` on failure; library users are expected to catch
|
|
2292
3601
|
* these (see the `KeiLispError` base class for the parse/eval family).
|
|
3602
|
+
* @param aCons the expression to evaluate
|
|
3603
|
+
* @return the evaluation result
|
|
2293
3604
|
*/
|
|
2294
3605
|
eval(aCons) {
|
|
2295
|
-
return Evaluator.eval(aCons, this.root, this.streamManager);
|
|
3606
|
+
return Evaluator.eval(aCons, this.root, this.streamManager, 1, this.plugins);
|
|
2296
3607
|
}
|
|
2297
3608
|
/**
|
|
2298
3609
|
* Parses the source string, evaluates every expression it contains, and returns the results as an array.
|
|
3610
|
+
* @param source the Lisp source string
|
|
3611
|
+
* @return the array of evaluation results, one per top-level expression
|
|
2299
3612
|
*/
|
|
2300
3613
|
evalAll(source) {
|
|
2301
3614
|
const ast = this.parse(source);
|
|
@@ -2305,6 +3618,8 @@ var LispInterpreter = class {
|
|
|
2305
3618
|
}
|
|
2306
3619
|
/**
|
|
2307
3620
|
* Parses and evaluates the source string and returns the value of the last expression.
|
|
3621
|
+
* @param source the Lisp source string
|
|
3622
|
+
* @return the value of the last expression, or `Cons.nil` for empty input
|
|
2308
3623
|
*/
|
|
2309
3624
|
evalString(source) {
|
|
2310
3625
|
const results = this.evalAll(source);
|
|
@@ -2315,12 +3630,16 @@ var LispInterpreter = class {
|
|
|
2315
3630
|
* it. The result is always a `Cons` (possibly `Cons.nil` for empty input)
|
|
2316
3631
|
* because the source is wrapped in an outer list before parsing. Throws
|
|
2317
3632
|
* `ParseError` if the source cannot be parsed.
|
|
3633
|
+
* @param aString the Lisp source string
|
|
3634
|
+
* @return a Cons containing the parsed top-level expressions
|
|
2318
3635
|
*/
|
|
2319
3636
|
parse(aString) {
|
|
2320
3637
|
return Cons.parse("(" + aString + "\n);");
|
|
2321
3638
|
}
|
|
2322
3639
|
/**
|
|
2323
3640
|
* Sets the given environment as the root of the environment chain.
|
|
3641
|
+
* @param environment the environment table to install as root
|
|
3642
|
+
* @return null
|
|
2324
3643
|
*/
|
|
2325
3644
|
setRoot(environment) {
|
|
2326
3645
|
if (environment instanceof Table) {
|
|
@@ -2330,13 +3649,14 @@ var LispInterpreter = class {
|
|
|
2330
3649
|
return null;
|
|
2331
3650
|
}
|
|
2332
3651
|
/**
|
|
2333
|
-
*
|
|
3652
|
+
* Builds the root environment table by pre-registering every built-in symbol and the small set of bootstrap lambdas (append / butlast / nthcdr / reverse).
|
|
3653
|
+
* @return the freshly initialized root environment
|
|
2334
3654
|
*/
|
|
2335
3655
|
initializeTable() {
|
|
2336
3656
|
const aList = [];
|
|
2337
3657
|
const aTable = new Table();
|
|
2338
3658
|
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", "
|
|
3659
|
+
aList.push("abs", "add", "and", "apply", "assoc", "atom", "bind", "car", "cdr", "characterp", "cond", "ceiling", "concatenate", "cons", "consp", "copy", "cos", "count", "floatp", "floor", "defun", "divide", "do", "do*", "dolist", "doublep", "elt", "eq", "equal", "eval", "evenp", "every", "exit", "exp", "expt", "find", "format", "gc", "gensym", "if", "integerp", "lambda", "let", "let*", "last", "length", "list", "listp", "mapcan", "mapcar", "max", "member", "memq", "min", "minusp", "mod", "multiply", "napier", "neq", "nequal", "not", "notrace", "nth", "null", "numberp", "oddp", "or", "pi", "plusp", "pop", "princ", "print", "progn", "push", "quote", "random", "reduce", "round", "rplaca", "rplacd", "setq", "set-allq", "sin", "some", "sort", "sqrt", "string-downcase", "string-trim", "string-upcase", "stringp", "subseq", "substring", "subtract", "symbolp", "tan", "terpri", "time", "trace", "truncate", "unless", "when", "zerop", "1+", "1-", "+", "-", "*", "/", "//", "=", "==", "~=", "~~", "<", "<=", ">", ">=");
|
|
2340
3660
|
for (const each of aList) {
|
|
2341
3661
|
const aSymbol = InterpretedSymbol.of(each);
|
|
2342
3662
|
aTable.set(aSymbol, aSymbol);
|
|
@@ -2351,10 +3671,6 @@ var LispInterpreter = class {
|
|
|
2351
3671
|
aCons = Cons.parse(aString);
|
|
2352
3672
|
aCons.last().setCdr(new Cons(aTable, Cons.nil));
|
|
2353
3673
|
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
3674
|
aString = "(lambda (n l) (cond ((> n (length l)) nil) ((= 0 n) l) (t (nthcdr (- n 1) (cdr l)))))";
|
|
2359
3675
|
aCons = Cons.parse(aString);
|
|
2360
3676
|
aCons.last().setCdr(new Cons(aTable, Cons.nil));
|
|
@@ -2376,10 +3692,22 @@ const require = createRequire(import.meta.url);
|
|
|
2376
3692
|
* @author Keisuke Ikeda
|
|
2377
3693
|
* @this {Repl}
|
|
2378
3694
|
*/
|
|
2379
|
-
var Repl = class {
|
|
3695
|
+
var Repl = class extends Object {
|
|
3696
|
+
/**
|
|
3697
|
+
* The underlying interpreter used to parse and evaluate user input.
|
|
3698
|
+
*/
|
|
2380
3699
|
interpreter;
|
|
3700
|
+
/**
|
|
3701
|
+
* The Node.js readline interface that supplies prompt I/O.
|
|
3702
|
+
*/
|
|
2381
3703
|
rl;
|
|
3704
|
+
/**
|
|
3705
|
+
* Constructor.
|
|
3706
|
+
* @constructor
|
|
3707
|
+
* @param interpreter the interpreter to evaluate input against (defaults to a fresh one)
|
|
3708
|
+
*/
|
|
2382
3709
|
constructor(interpreter = new LispInterpreter()) {
|
|
3710
|
+
super();
|
|
2383
3711
|
this.interpreter = interpreter;
|
|
2384
3712
|
const readline = require("node:readline");
|
|
2385
3713
|
this.rl = readline.createInterface({
|
|
@@ -2390,6 +3718,7 @@ var Repl = class {
|
|
|
2390
3718
|
}
|
|
2391
3719
|
/**
|
|
2392
3720
|
* Starts the REPL loop.
|
|
3721
|
+
* @return void
|
|
2393
3722
|
*/
|
|
2394
3723
|
run() {
|
|
2395
3724
|
let aString = "";
|
|
@@ -2428,6 +3757,6 @@ var Repl = class {
|
|
|
2428
3757
|
}
|
|
2429
3758
|
};
|
|
2430
3759
|
//#endregion
|
|
2431
|
-
export { Cons, EvalError, ExitError, InterpretedSymbol, KeiLispError, LispInterpreter, ParseError, Repl };
|
|
3760
|
+
export { Cons, EvalError, Evaluator, ExitError, InterpretedSymbol, KeiLispError, LispInterpreter, ParseError, Repl, StreamManager, Table };
|
|
2432
3761
|
|
|
2433
3762
|
//# sourceMappingURL=index.js.map
|