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