kei-lisp 2.1.0 → 2.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.cjs +1415 -86
- package/dist/index.cjs +1417 -85
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +560 -19
- package/dist/index.d.ts +560 -19
- package/dist/index.js +1415 -86
- package/dist/index.js.map +1 -1
- package/package.json +2 -1
package/dist/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.2.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);
|
|
@@ -585,6 +700,7 @@ var Parser = class Parser {
|
|
|
585
700
|
}
|
|
586
701
|
/**
|
|
587
702
|
* Returns the token number for a quote or for a 0-origin String-type (pseudo-Character); invoked from NextState.
|
|
703
|
+
* @return the next state number
|
|
588
704
|
*/
|
|
589
705
|
quoteOrChar() {
|
|
590
706
|
let aNumber = this.peekChar() === "\\" ? 3 : 2;
|
|
@@ -593,12 +709,14 @@ var Parser = class Parser {
|
|
|
593
709
|
}
|
|
594
710
|
/**
|
|
595
711
|
* Detects a right parenthesis (')', ']', '}') and returns the result; invoked from NextState.
|
|
712
|
+
* @return true when the next character is any right paren
|
|
596
713
|
*/
|
|
597
714
|
rightParen() {
|
|
598
715
|
return this.peekChar() === ")" || this.peekChar() === "]" || this.peekChar() === "}";
|
|
599
716
|
}
|
|
600
717
|
/**
|
|
601
718
|
* Returns the token number for a sign symbol ('+', '-'); invoked from NextState.
|
|
719
|
+
* @return the next state number (7, or 0 when the literal is complete)
|
|
602
720
|
*/
|
|
603
721
|
sign() {
|
|
604
722
|
this.concat();
|
|
@@ -610,6 +728,7 @@ var Parser = class Parser {
|
|
|
610
728
|
}
|
|
611
729
|
/**
|
|
612
730
|
* Skips whitespace; invoked from NextState.
|
|
731
|
+
* @return null
|
|
613
732
|
*/
|
|
614
733
|
skippingSpaces() {
|
|
615
734
|
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 +736,7 @@ var Parser = class Parser {
|
|
|
617
736
|
}
|
|
618
737
|
/**
|
|
619
738
|
* Returns the token number for an InterpretedSymbol; invoked from NextState.
|
|
739
|
+
* @return the next state number (8, or 0 when the literal is complete)
|
|
620
740
|
*/
|
|
621
741
|
symbolToken() {
|
|
622
742
|
this.concat();
|
|
@@ -628,6 +748,7 @@ var Parser = class Parser {
|
|
|
628
748
|
}
|
|
629
749
|
/**
|
|
630
750
|
* Converts the token into a 0-origin String-type (pseudo-Character); invoked from NextState.
|
|
751
|
+
* @return null
|
|
631
752
|
*/
|
|
632
753
|
tokenToCharacter() {
|
|
633
754
|
this.token = this.tokenString.charAt(0);
|
|
@@ -635,6 +756,7 @@ var Parser = class Parser {
|
|
|
635
756
|
}
|
|
636
757
|
/**
|
|
637
758
|
* Converts the token into a Number-type (double-precision floating point: pseudo-Double); invoked from NextState.
|
|
759
|
+
* @return null
|
|
638
760
|
*/
|
|
639
761
|
tokenToDouble() {
|
|
640
762
|
this.token = Number(this.tokenString);
|
|
@@ -642,6 +764,7 @@ var Parser = class Parser {
|
|
|
642
764
|
}
|
|
643
765
|
/**
|
|
644
766
|
* Converts the token into a Number-type (double-precision floating point: pseudo-Double); invoked from NextState.
|
|
767
|
+
* @return null
|
|
645
768
|
*/
|
|
646
769
|
tokenToDoubleAUX() {
|
|
647
770
|
this.concat();
|
|
@@ -650,6 +773,7 @@ var Parser = class Parser {
|
|
|
650
773
|
}
|
|
651
774
|
/**
|
|
652
775
|
* Converts the token into a Number-type (integer: pseudo-Integer); invoked from NextState.
|
|
776
|
+
* @return null
|
|
653
777
|
*/
|
|
654
778
|
tokenToInteger() {
|
|
655
779
|
if (this.tokenString[0] === "+") this.tokenString = this.tokenString.slice(1);
|
|
@@ -658,6 +782,7 @@ var Parser = class Parser {
|
|
|
658
782
|
}
|
|
659
783
|
/**
|
|
660
784
|
* Converts the token into a String-type; invoked from NextState.
|
|
785
|
+
* @return null
|
|
661
786
|
*/
|
|
662
787
|
tokenToString() {
|
|
663
788
|
this.token = this.tokenString;
|
|
@@ -665,6 +790,7 @@ var Parser = class Parser {
|
|
|
665
790
|
}
|
|
666
791
|
/**
|
|
667
792
|
* Converts the token into an InterpretedSymbol; invoked from NextState.
|
|
793
|
+
* @return null
|
|
668
794
|
*/
|
|
669
795
|
tokenToSymbol() {
|
|
670
796
|
this.token = InterpretedSymbol.of(this.tokenString);
|
|
@@ -673,6 +799,7 @@ var Parser = class Parser {
|
|
|
673
799
|
}
|
|
674
800
|
/**
|
|
675
801
|
* Builds the lookup table that maps character codes to their corresponding methods (tokens).
|
|
802
|
+
* @return null
|
|
676
803
|
*/
|
|
677
804
|
initializeStateTransitionTable() {
|
|
678
805
|
let aTable = /* @__PURE__ */ new Map();
|
|
@@ -723,6 +850,8 @@ var Parser = class Parser {
|
|
|
723
850
|
aTable = /* @__PURE__ */ new Map();
|
|
724
851
|
for (const index of IntStream.rangeClosed(9, 13)) aTable.set(String(index), this.nextState(0, "tokenToInteger"));
|
|
725
852
|
aTable.set(String(32), this.nextState(0, "tokenToInteger"));
|
|
853
|
+
aTable.set(String(43), this.nextState(8, "symbolToken"));
|
|
854
|
+
aTable.set(String(45), this.nextState(8, "symbolToken"));
|
|
726
855
|
aTable.set(String(46), this.nextState(3, "doubleToken"));
|
|
727
856
|
for (const index of IntStream.rangeClosed(48, 57)) aTable.set(String(index), this.nextState(2, "integerToken"));
|
|
728
857
|
aTable.set(String(69), this.nextState(4, "concat"));
|
|
@@ -826,9 +955,18 @@ var Parser = class Parser {
|
|
|
826
955
|
* @author Keisuke Ikeda
|
|
827
956
|
* @this {Cons}
|
|
828
957
|
*/
|
|
829
|
-
var Cons = class Cons {
|
|
958
|
+
var Cons = class Cons extends Object {
|
|
959
|
+
/**
|
|
960
|
+
* The shared empty-list sentinel. A Cons whose car and cdr are both itself, representing Lisp `nil`.
|
|
961
|
+
*/
|
|
830
962
|
static nil = new Cons();
|
|
963
|
+
/**
|
|
964
|
+
* The head element of this Cons cell.
|
|
965
|
+
*/
|
|
831
966
|
car;
|
|
967
|
+
/**
|
|
968
|
+
* The tail of this Cons cell (typically another Cons or nil).
|
|
969
|
+
*/
|
|
832
970
|
cdr;
|
|
833
971
|
/**
|
|
834
972
|
* Constructor.
|
|
@@ -837,6 +975,7 @@ var Cons = class Cons {
|
|
|
837
975
|
* @param cdr the cdr; defaults to nil when no argument is given.
|
|
838
976
|
*/
|
|
839
977
|
constructor(car = Cons.nil, cdr = Cons.nil) {
|
|
978
|
+
super();
|
|
840
979
|
this.car = car;
|
|
841
980
|
this.cdr = cdr;
|
|
842
981
|
}
|
|
@@ -1140,6 +1279,7 @@ const SIZE_DO_NOT_MATCH = "size do not match.";
|
|
|
1140
1279
|
//#endregion
|
|
1141
1280
|
//#region src/runtime/Applier/index.ts
|
|
1142
1281
|
const SELECT_PRINT_FUNCTION_NOT_DEFINED = "selectPrintFunction is not defined";
|
|
1282
|
+
const toCodePoints = (s) => [...s];
|
|
1143
1283
|
/**
|
|
1144
1284
|
* Class that mimics Lisp's universal function Apply.
|
|
1145
1285
|
* @class
|
|
@@ -1147,25 +1287,67 @@ const SELECT_PRINT_FUNCTION_NOT_DEFINED = "selectPrintFunction is not defined";
|
|
|
1147
1287
|
* @author Keisuke Ikeda
|
|
1148
1288
|
* @this {Applier}
|
|
1149
1289
|
*/
|
|
1150
|
-
var Applier = class Applier {
|
|
1290
|
+
var Applier = class Applier extends Object {
|
|
1291
|
+
/**
|
|
1292
|
+
* Dispatch map from a Lisp function name (InterpretedSymbol) to the name of the Applier method that implements it.
|
|
1293
|
+
*/
|
|
1151
1294
|
static buildInFunctions = Applier.setup();
|
|
1152
1295
|
static #generateNumber = 0;
|
|
1296
|
+
/**
|
|
1297
|
+
* The environment (variable bindings) used while applying procedures.
|
|
1298
|
+
*/
|
|
1153
1299
|
environment;
|
|
1300
|
+
/**
|
|
1301
|
+
* The stream manager used for I/O and spy output.
|
|
1302
|
+
*/
|
|
1154
1303
|
streamManager;
|
|
1304
|
+
/**
|
|
1305
|
+
* The current recursion depth, used for spy indentation.
|
|
1306
|
+
*/
|
|
1155
1307
|
depth;
|
|
1156
|
-
|
|
1308
|
+
/**
|
|
1309
|
+
* Registered plugins forwarded back into Evaluator on recursive evaluation (e.g. `entrustEvaluator`).
|
|
1310
|
+
*/
|
|
1311
|
+
plugins;
|
|
1312
|
+
/**
|
|
1313
|
+
* Constructor.
|
|
1314
|
+
* @constructor
|
|
1315
|
+
* @param aTable the parent environment to extend
|
|
1316
|
+
* @param aStreamManager the stream manager for I/O
|
|
1317
|
+
* @param aNumber the initial recursion depth
|
|
1318
|
+
* @param plugins the plugin chain to forward when re-entering the Evaluator
|
|
1319
|
+
*/
|
|
1320
|
+
constructor(aTable, aStreamManager, aNumber, plugins = []) {
|
|
1321
|
+
super();
|
|
1157
1322
|
this.environment = new Table(aTable);
|
|
1158
1323
|
this.streamManager = aStreamManager;
|
|
1159
1324
|
this.depth = aNumber;
|
|
1325
|
+
this.plugins = plugins;
|
|
1160
1326
|
}
|
|
1327
|
+
/**
|
|
1328
|
+
* Implementation of the Lisp `abs` function. Returns the absolute value of the given number.
|
|
1329
|
+
* @param args the argument Cons containing the target number
|
|
1330
|
+
* @return the absolute value
|
|
1331
|
+
*/
|
|
1161
1332
|
abs(args) {
|
|
1162
1333
|
if (Cons.isNumber(args.car)) return Math.abs(args.car);
|
|
1163
1334
|
throw new EvalError(cannotApply("abs", args.car));
|
|
1164
1335
|
}
|
|
1336
|
+
/**
|
|
1337
|
+
* Implementation of the Lisp `+` / `add` function. Returns the sum of the given numbers.
|
|
1338
|
+
* @param args the argument Cons containing the numbers to add
|
|
1339
|
+
* @return the sum of the arguments
|
|
1340
|
+
*/
|
|
1165
1341
|
add(args) {
|
|
1166
1342
|
if (Cons.isNumber(args.car)) return this.add_Number(args.car, args.cdr);
|
|
1167
1343
|
throw new EvalError(cannotApply("add", args.car));
|
|
1168
1344
|
}
|
|
1345
|
+
/**
|
|
1346
|
+
* Helper that accumulates the sum starting from an initial number and the remaining argument list.
|
|
1347
|
+
* @param init the initial number
|
|
1348
|
+
* @param args the remaining numbers to add
|
|
1349
|
+
* @return the sum of init and all remaining numbers
|
|
1350
|
+
*/
|
|
1169
1351
|
add_Number(init, args) {
|
|
1170
1352
|
let result = init;
|
|
1171
1353
|
let aCons = args;
|
|
@@ -1177,13 +1359,34 @@ var Applier = class Applier {
|
|
|
1177
1359
|
}
|
|
1178
1360
|
return result;
|
|
1179
1361
|
}
|
|
1180
|
-
|
|
1181
|
-
|
|
1362
|
+
/**
|
|
1363
|
+
* Static entry point that instantiates an Applier and applies the given procedure to the arguments.
|
|
1364
|
+
* @param procedure the procedure to apply (a symbol or a lambda Cons)
|
|
1365
|
+
* @param args the argument list
|
|
1366
|
+
* @param environment the environment to use
|
|
1367
|
+
* @param aStreamManager the stream manager for I/O
|
|
1368
|
+
* @param depth the current recursion depth
|
|
1369
|
+
* @param plugins the plugin chain to forward when re-entering the Evaluator
|
|
1370
|
+
* @return the result of applying the procedure
|
|
1371
|
+
*/
|
|
1372
|
+
static apply(procedure, args, environment, aStreamManager, depth, plugins = []) {
|
|
1373
|
+
return new Applier(environment, aStreamManager, depth, plugins).apply(procedure, args);
|
|
1182
1374
|
}
|
|
1375
|
+
/**
|
|
1376
|
+
* Applies the given procedure to the given arguments.
|
|
1377
|
+
* @param procedure the procedure to apply (a symbol or a lambda Cons)
|
|
1378
|
+
* @param args the argument list
|
|
1379
|
+
* @return the result of applying the procedure
|
|
1380
|
+
*/
|
|
1183
1381
|
apply(procedure, args) {
|
|
1184
1382
|
if (Cons.isSymbol(procedure)) return this.selectProcedure(procedure, args);
|
|
1185
1383
|
return this.entrustEvaluator(procedure, args);
|
|
1186
1384
|
}
|
|
1385
|
+
/**
|
|
1386
|
+
* Implementation of the Lisp `assoc` function. Looks up an association in an association list.
|
|
1387
|
+
* @param args the argument Cons containing the key and the association list
|
|
1388
|
+
* @return the matching pair, or nil if no match was found
|
|
1389
|
+
*/
|
|
1187
1390
|
assoc(args) {
|
|
1188
1391
|
const target = args.car;
|
|
1189
1392
|
if (Cons.isNotCons(args.nth(2))) return Cons.nil;
|
|
@@ -1196,10 +1399,20 @@ var Applier = class Applier {
|
|
|
1196
1399
|
}
|
|
1197
1400
|
return Cons.nil;
|
|
1198
1401
|
}
|
|
1402
|
+
/**
|
|
1403
|
+
* Implementation of the Lisp `atom` predicate. Returns t if the argument is an atom, otherwise nil.
|
|
1404
|
+
* @param args the argument Cons containing the value to test
|
|
1405
|
+
* @return t if atom, nil otherwise
|
|
1406
|
+
*/
|
|
1199
1407
|
atom_(args) {
|
|
1200
1408
|
if (Cons.isAtom(args.car)) return InterpretedSymbol.of("t");
|
|
1201
1409
|
return Cons.nil;
|
|
1202
1410
|
}
|
|
1411
|
+
/**
|
|
1412
|
+
* Binds the given parameter symbols to the corresponding argument values in this environment.
|
|
1413
|
+
* @param parameter the parameter list (a Cons of symbols, possibly dotted)
|
|
1414
|
+
* @param args the argument list to bind to the parameters
|
|
1415
|
+
*/
|
|
1203
1416
|
binding(parameter, args) {
|
|
1204
1417
|
if (Cons.isNil(parameter)) return null;
|
|
1205
1418
|
let aCons = parameter;
|
|
@@ -1222,6 +1435,12 @@ var Applier = class Applier {
|
|
|
1222
1435
|
else if (Cons.isNotNil(aCons.cdr)) throw new ReferenceError("aList is not defined");
|
|
1223
1436
|
return null;
|
|
1224
1437
|
}
|
|
1438
|
+
/**
|
|
1439
|
+
* Invokes the built-in method associated with the given procedure symbol.
|
|
1440
|
+
* @param procedure the symbol naming the built-in function
|
|
1441
|
+
* @param args the argument list
|
|
1442
|
+
* @return the result of the built-in function
|
|
1443
|
+
*/
|
|
1225
1444
|
buildInFunction(procedure, args) {
|
|
1226
1445
|
if (this.isSpy(procedure)) {
|
|
1227
1446
|
this.spyPrint(this.streamManager.spyStream(procedure), new Cons(procedure, args).toString());
|
|
@@ -1238,34 +1457,80 @@ var Applier = class Applier {
|
|
|
1238
1457
|
}
|
|
1239
1458
|
return answer;
|
|
1240
1459
|
}
|
|
1460
|
+
/**
|
|
1461
|
+
* Implementation of the Lisp `car` function. Returns the car of the given Cons.
|
|
1462
|
+
* @param args the argument Cons containing the target Cons
|
|
1463
|
+
* @return the car of the target
|
|
1464
|
+
*/
|
|
1241
1465
|
car(args) {
|
|
1242
1466
|
return args.car.car;
|
|
1243
1467
|
}
|
|
1468
|
+
/**
|
|
1469
|
+
* Implementation of the Lisp `cdr` function. Returns the cdr of the given Cons.
|
|
1470
|
+
* @param args the argument Cons containing the target Cons
|
|
1471
|
+
* @return the cdr of the target
|
|
1472
|
+
*/
|
|
1244
1473
|
cdr(args) {
|
|
1245
1474
|
return args.car.cdr;
|
|
1246
1475
|
}
|
|
1476
|
+
/**
|
|
1477
|
+
* Implementation of the Lisp `characterp` predicate. Returns t if the argument is a single-character string.
|
|
1478
|
+
* @param args the argument Cons containing the value to test
|
|
1479
|
+
* @return t if a character, nil otherwise
|
|
1480
|
+
*/
|
|
1247
1481
|
character_(args) {
|
|
1248
1482
|
if (Cons.isString(args.car) && args.car.length === 1) return InterpretedSymbol.of("t");
|
|
1249
1483
|
return Cons.nil;
|
|
1250
1484
|
}
|
|
1485
|
+
/**
|
|
1486
|
+
* Implementation of the Lisp `cons` function. Constructs a new Cons from the given car and cdr.
|
|
1487
|
+
* @param args the argument Cons containing the car and cdr
|
|
1488
|
+
* @return the newly constructed Cons
|
|
1489
|
+
*/
|
|
1251
1490
|
cons(args) {
|
|
1252
1491
|
return new Cons(args.car, args.nth(2));
|
|
1253
1492
|
}
|
|
1493
|
+
/**
|
|
1494
|
+
* Implementation of the Lisp `consp` predicate. Returns t if the argument is a Cons, otherwise nil.
|
|
1495
|
+
* @param args the argument Cons containing the value to test
|
|
1496
|
+
* @return t if a Cons, nil otherwise
|
|
1497
|
+
*/
|
|
1254
1498
|
cons_(args) {
|
|
1255
1499
|
if (Cons.isCons(args.car)) return InterpretedSymbol.of("t");
|
|
1256
1500
|
return Cons.nil;
|
|
1257
1501
|
}
|
|
1502
|
+
/**
|
|
1503
|
+
* Implementation of the Lisp `copy` function. Returns a deep clone of the given value.
|
|
1504
|
+
* @param args the argument Cons containing the value to copy
|
|
1505
|
+
* @return the cloned value
|
|
1506
|
+
*/
|
|
1258
1507
|
copy(args) {
|
|
1259
1508
|
return Cons.cloneValue(args.car);
|
|
1260
1509
|
}
|
|
1510
|
+
/**
|
|
1511
|
+
* Implementation of the Lisp `cos` function. Returns the cosine of the given number.
|
|
1512
|
+
* @param args the argument Cons containing the angle in radians
|
|
1513
|
+
* @return the cosine of the argument
|
|
1514
|
+
*/
|
|
1261
1515
|
cos(args) {
|
|
1262
1516
|
if (Cons.isNumber(args.car)) return Math.cos(args.car);
|
|
1263
1517
|
throw new ReferenceError(SELECT_PRINT_FUNCTION_NOT_DEFINED);
|
|
1264
1518
|
}
|
|
1519
|
+
/**
|
|
1520
|
+
* Implementation of the Lisp `/` / `divide` function. Returns the quotient of the given numbers.
|
|
1521
|
+
* @param args the argument Cons containing the numbers to divide
|
|
1522
|
+
* @return the quotient of the arguments
|
|
1523
|
+
*/
|
|
1265
1524
|
divide(args) {
|
|
1266
1525
|
if (Cons.isNumber(args.car)) return this.divide_Number(args.car, args.cdr);
|
|
1267
1526
|
throw new EvalError(cannotApply("divide", args.car));
|
|
1268
1527
|
}
|
|
1528
|
+
/**
|
|
1529
|
+
* Helper that accumulates the quotient starting from an initial number and the remaining argument list.
|
|
1530
|
+
* @param init the initial number (numerator)
|
|
1531
|
+
* @param args the remaining numbers to divide by
|
|
1532
|
+
* @return the quotient of init divided by all remaining numbers
|
|
1533
|
+
*/
|
|
1269
1534
|
divide_Number(init, args) {
|
|
1270
1535
|
let result = init;
|
|
1271
1536
|
let aCons = args;
|
|
@@ -1277,6 +1542,12 @@ var Applier = class Applier {
|
|
|
1277
1542
|
}
|
|
1278
1543
|
return result;
|
|
1279
1544
|
}
|
|
1545
|
+
/**
|
|
1546
|
+
* Delegates evaluation of a lambda body to the Evaluator after binding its parameters.
|
|
1547
|
+
* @param procedure the lambda Cons to apply
|
|
1548
|
+
* @param args the argument list to bind to the lambda's parameters
|
|
1549
|
+
* @return the result of evaluating the lambda body
|
|
1550
|
+
*/
|
|
1280
1551
|
entrustEvaluator(procedure, args) {
|
|
1281
1552
|
let anObject = Cons.nil;
|
|
1282
1553
|
let aCons = procedure.cdr;
|
|
@@ -1284,14 +1555,24 @@ var Applier = class Applier {
|
|
|
1284
1555
|
aCons = aCons.cdr;
|
|
1285
1556
|
for (const each of aCons.loop()) {
|
|
1286
1557
|
if (each instanceof Table) break;
|
|
1287
|
-
anObject = Evaluator.eval(each, this.environment, this.streamManager, this.depth);
|
|
1558
|
+
anObject = Evaluator.eval(each, this.environment, this.streamManager, this.depth, this.plugins);
|
|
1288
1559
|
}
|
|
1289
1560
|
return anObject;
|
|
1290
1561
|
}
|
|
1562
|
+
/**
|
|
1563
|
+
* Implementation of the Lisp `eq` predicate. Returns t when both arguments are identical (JS `===`).
|
|
1564
|
+
* @param args the argument Cons containing the two values to compare
|
|
1565
|
+
* @return t when identical, nil otherwise
|
|
1566
|
+
*/
|
|
1291
1567
|
eq_(args) {
|
|
1292
1568
|
if (args.car === args.nth(2)) return InterpretedSymbol.of("t");
|
|
1293
1569
|
return Cons.nil;
|
|
1294
1570
|
}
|
|
1571
|
+
/**
|
|
1572
|
+
* Implementation of the Lisp `equal` / `=` predicate. Returns t when both arguments are structurally equal.
|
|
1573
|
+
* @param args the argument Cons containing the two values to compare
|
|
1574
|
+
* @return t when equal, nil otherwise
|
|
1575
|
+
*/
|
|
1295
1576
|
equal_(args) {
|
|
1296
1577
|
const first = args.car;
|
|
1297
1578
|
const second = args.nth(2);
|
|
@@ -1302,10 +1583,20 @@ var Applier = class Applier {
|
|
|
1302
1583
|
}
|
|
1303
1584
|
return Cons.nil;
|
|
1304
1585
|
}
|
|
1586
|
+
/**
|
|
1587
|
+
* Implementation of the Lisp `exp` function. Returns e raised to the given power.
|
|
1588
|
+
* @param args the argument Cons containing the exponent
|
|
1589
|
+
* @return e raised to the given power
|
|
1590
|
+
*/
|
|
1305
1591
|
exp(args) {
|
|
1306
1592
|
if (Cons.isNumber(args.car)) return Math.exp(args.car);
|
|
1307
1593
|
throw new ReferenceError(SELECT_PRINT_FUNCTION_NOT_DEFINED);
|
|
1308
1594
|
}
|
|
1595
|
+
/**
|
|
1596
|
+
* Implementation of the Lisp `format` function. Writes a formatted string to standard output.
|
|
1597
|
+
* @param args the argument Cons containing the format string followed by its arguments
|
|
1598
|
+
* @return nil
|
|
1599
|
+
*/
|
|
1309
1600
|
format(args) {
|
|
1310
1601
|
if (!Cons.isString(args.car)) throw new EvalError(cannotApply("format", args.car));
|
|
1311
1602
|
const aCons = args.cdr;
|
|
@@ -1313,6 +1604,12 @@ var Applier = class Applier {
|
|
|
1313
1604
|
process.stdout.write(String(format));
|
|
1314
1605
|
return Cons.nil;
|
|
1315
1606
|
}
|
|
1607
|
+
/**
|
|
1608
|
+
* Helper that expands the given format string with the supplied arguments.
|
|
1609
|
+
* @param format the format string containing directives such as `~a`, `~%`, and width specifiers
|
|
1610
|
+
* @param aCons the argument list to interpolate into the directives
|
|
1611
|
+
* @return the formatted string
|
|
1612
|
+
*/
|
|
1316
1613
|
format_AUX(format, aCons) {
|
|
1317
1614
|
let theCons = aCons;
|
|
1318
1615
|
let index = 0;
|
|
@@ -1434,23 +1731,48 @@ var Applier = class Applier {
|
|
|
1434
1731
|
if (Cons.isNotNil(theCons)) throw new EvalError(SIZE_DO_NOT_MATCH);
|
|
1435
1732
|
return buffer;
|
|
1436
1733
|
}
|
|
1734
|
+
/**
|
|
1735
|
+
* Implementation of the Lisp `floatp` predicate. Returns t if the argument is a number representable as IEEE 32-bit float.
|
|
1736
|
+
* @param args the argument Cons containing the value to test
|
|
1737
|
+
* @return t if a float, nil otherwise
|
|
1738
|
+
*/
|
|
1437
1739
|
float_(args) {
|
|
1438
1740
|
if (Cons.isNumber(args.car) && -34e37 <= args.car && args.car <= 34e37) return InterpretedSymbol.of("t");
|
|
1439
1741
|
return Cons.nil;
|
|
1440
1742
|
}
|
|
1743
|
+
/**
|
|
1744
|
+
* Implementation of the Lisp `gensym` function. Generates a fresh, unique symbol.
|
|
1745
|
+
* @return a new, unique InterpretedSymbol
|
|
1746
|
+
*/
|
|
1441
1747
|
gensym() {
|
|
1442
1748
|
const aSymbol = InterpretedSymbol.of("id" + String(Applier.#generateNumber));
|
|
1443
1749
|
Applier.incrementGenerateNumber();
|
|
1444
1750
|
return aSymbol;
|
|
1445
1751
|
}
|
|
1752
|
+
/**
|
|
1753
|
+
* Returns the appropriate stream for the given object.
|
|
1754
|
+
* @param anObject the object used to select the stream
|
|
1755
|
+
* @return the selected stream
|
|
1756
|
+
*/
|
|
1446
1757
|
getStream(anObject) {
|
|
1447
1758
|
if (typeof anObject === "string") return process.out;
|
|
1448
1759
|
return this.streamManager.getStream();
|
|
1449
1760
|
}
|
|
1761
|
+
/**
|
|
1762
|
+
* Implementation of the Lisp `>` / `greaterThan` predicate. Returns t when arguments are in strictly decreasing order.
|
|
1763
|
+
* @param args the argument Cons containing the numbers to compare
|
|
1764
|
+
* @return t when each is greater than the next, nil otherwise
|
|
1765
|
+
*/
|
|
1450
1766
|
greaterThan(args) {
|
|
1451
1767
|
if (Cons.isNumber(args.car)) return this.greaterThan_Number(args.car, args.cdr);
|
|
1452
1768
|
throw new EvalError(cannotApply(">", args.car));
|
|
1453
1769
|
}
|
|
1770
|
+
/**
|
|
1771
|
+
* Helper that checks `>` ordering starting from an initial number against the remaining argument list.
|
|
1772
|
+
* @param init the initial number on the left side of the first comparison
|
|
1773
|
+
* @param args the remaining numbers to compare against
|
|
1774
|
+
* @return t when strictly decreasing, nil otherwise
|
|
1775
|
+
*/
|
|
1454
1776
|
greaterThan_Number(init, args) {
|
|
1455
1777
|
let leftValue = init;
|
|
1456
1778
|
let aCons = args;
|
|
@@ -1465,10 +1787,21 @@ var Applier = class Applier {
|
|
|
1465
1787
|
}
|
|
1466
1788
|
return InterpretedSymbol.of("t");
|
|
1467
1789
|
}
|
|
1790
|
+
/**
|
|
1791
|
+
* Implementation of the Lisp `>=` / `greaterThanOrEqual` predicate. Returns t when arguments are in non-increasing order.
|
|
1792
|
+
* @param args the argument Cons containing the numbers to compare
|
|
1793
|
+
* @return t when each is greater than or equal to the next, nil otherwise
|
|
1794
|
+
*/
|
|
1468
1795
|
greaterThanOrEqual(args) {
|
|
1469
1796
|
if (Cons.isNumber(args.car)) return this.greaterThanOrEqual_Number(args.car, args.cdr);
|
|
1470
1797
|
throw new EvalError(cannotApply(">=", args.car));
|
|
1471
1798
|
}
|
|
1799
|
+
/**
|
|
1800
|
+
* Helper that checks `>=` ordering starting from an initial number against the remaining argument list.
|
|
1801
|
+
* @param init the initial number on the left side of the first comparison
|
|
1802
|
+
* @param args the remaining numbers to compare against
|
|
1803
|
+
* @return t when non-increasing, nil otherwise
|
|
1804
|
+
*/
|
|
1472
1805
|
greaterThanOrEqual_Number(init, args) {
|
|
1473
1806
|
let leftValue = init;
|
|
1474
1807
|
let aCons = args;
|
|
@@ -1483,31 +1816,441 @@ var Applier = class Applier {
|
|
|
1483
1816
|
}
|
|
1484
1817
|
return InterpretedSymbol.of("t");
|
|
1485
1818
|
}
|
|
1819
|
+
/**
|
|
1820
|
+
* Increments the internal counter used by `gensym` to ensure uniqueness.
|
|
1821
|
+
*/
|
|
1486
1822
|
static incrementGenerateNumber() {
|
|
1487
1823
|
Applier.#generateNumber++;
|
|
1488
1824
|
return null;
|
|
1489
1825
|
}
|
|
1826
|
+
/**
|
|
1827
|
+
* Returns a string of indentation used as a prefix for spy output, based on the current depth.
|
|
1828
|
+
* @return the indentation string
|
|
1829
|
+
*/
|
|
1490
1830
|
indent() {
|
|
1491
1831
|
let index = 0;
|
|
1492
1832
|
let aString = "";
|
|
1493
1833
|
while (index++ < this.depth) aString += "| ";
|
|
1494
1834
|
return aString;
|
|
1495
1835
|
}
|
|
1836
|
+
/**
|
|
1837
|
+
* Implementation of the Lisp `integerp` predicate. Returns t if the argument is an integer.
|
|
1838
|
+
* @param args the argument Cons containing the value to test
|
|
1839
|
+
* @return t if an integer, nil otherwise
|
|
1840
|
+
*/
|
|
1496
1841
|
integer_(args) {
|
|
1497
1842
|
if (Cons.isNumber(args.car) && Number.isInteger(args.car)) return InterpretedSymbol.of("t");
|
|
1498
1843
|
return Cons.nil;
|
|
1499
1844
|
}
|
|
1845
|
+
/**
|
|
1846
|
+
* Implementation of the Lisp `evenp` predicate. Returns t if the argument is an even integer.
|
|
1847
|
+
* @param args the argument Cons containing the value to test
|
|
1848
|
+
* @return t if even, nil otherwise
|
|
1849
|
+
*/
|
|
1850
|
+
even_(args) {
|
|
1851
|
+
if (Cons.isNumber(args.car) && Number.isInteger(args.car) && args.car % 2 === 0) return InterpretedSymbol.of("t");
|
|
1852
|
+
return Cons.nil;
|
|
1853
|
+
}
|
|
1854
|
+
/**
|
|
1855
|
+
* Implementation of the Lisp `oddp` predicate. Returns t if the argument is an odd integer.
|
|
1856
|
+
* @param args the argument Cons containing the value to test
|
|
1857
|
+
* @return t if odd, nil otherwise
|
|
1858
|
+
*/
|
|
1859
|
+
odd_(args) {
|
|
1860
|
+
if (Cons.isNumber(args.car) && Number.isInteger(args.car) && args.car % 2 !== 0) return InterpretedSymbol.of("t");
|
|
1861
|
+
return Cons.nil;
|
|
1862
|
+
}
|
|
1863
|
+
/**
|
|
1864
|
+
* Implementation of the Lisp `zerop` predicate. Returns t if the argument equals zero.
|
|
1865
|
+
* @param args the argument Cons containing the value to test
|
|
1866
|
+
* @return t if zero, nil otherwise
|
|
1867
|
+
*/
|
|
1868
|
+
zero_(args) {
|
|
1869
|
+
if (Cons.isNumber(args.car) && args.car === 0) return InterpretedSymbol.of("t");
|
|
1870
|
+
return Cons.nil;
|
|
1871
|
+
}
|
|
1872
|
+
/**
|
|
1873
|
+
* Implementation of the Lisp `plusp` predicate. Returns t if the argument is strictly positive.
|
|
1874
|
+
* @param args the argument Cons containing the value to test
|
|
1875
|
+
* @return t if positive, nil otherwise
|
|
1876
|
+
*/
|
|
1877
|
+
plus_(args) {
|
|
1878
|
+
if (Cons.isNumber(args.car) && args.car > 0) return InterpretedSymbol.of("t");
|
|
1879
|
+
return Cons.nil;
|
|
1880
|
+
}
|
|
1881
|
+
/**
|
|
1882
|
+
* Implementation of the Lisp `minusp` predicate. Returns t if the argument is strictly negative.
|
|
1883
|
+
* @param args the argument Cons containing the value to test
|
|
1884
|
+
* @return t if negative, nil otherwise
|
|
1885
|
+
*/
|
|
1886
|
+
minus_(args) {
|
|
1887
|
+
if (Cons.isNumber(args.car) && args.car < 0) return InterpretedSymbol.of("t");
|
|
1888
|
+
return Cons.nil;
|
|
1889
|
+
}
|
|
1890
|
+
/**
|
|
1891
|
+
* Implementation of the Lisp `1+` function. Returns the argument incremented by one.
|
|
1892
|
+
* @param args the argument Cons containing the target number
|
|
1893
|
+
* @return the argument plus one
|
|
1894
|
+
*/
|
|
1895
|
+
oneplus(args) {
|
|
1896
|
+
if (Cons.isNumber(args.car)) return args.car + 1;
|
|
1897
|
+
throw new EvalError(cannotApply("1+", args.car));
|
|
1898
|
+
}
|
|
1899
|
+
/**
|
|
1900
|
+
* Implementation of the Lisp `1-` function. Returns the argument decremented by one.
|
|
1901
|
+
* @param args the argument Cons containing the target number
|
|
1902
|
+
* @return the argument minus one
|
|
1903
|
+
*/
|
|
1904
|
+
oneminus(args) {
|
|
1905
|
+
if (Cons.isNumber(args.car)) return args.car - 1;
|
|
1906
|
+
throw new EvalError(cannotApply("1-", args.car));
|
|
1907
|
+
}
|
|
1908
|
+
/**
|
|
1909
|
+
* Implementation of the Lisp `expt` function. Returns the base raised to the exponent.
|
|
1910
|
+
* @param args the argument Cons containing the base followed by the exponent
|
|
1911
|
+
* @return base raised to the exponent
|
|
1912
|
+
*/
|
|
1913
|
+
expt(args) {
|
|
1914
|
+
const base = args.car;
|
|
1915
|
+
const exponent = args.nth(2);
|
|
1916
|
+
if (Cons.isNumber(base) && Cons.isNumber(exponent)) return Math.pow(base, exponent);
|
|
1917
|
+
throw new EvalError(cannotApply("expt", base));
|
|
1918
|
+
}
|
|
1919
|
+
/**
|
|
1920
|
+
* Implementation of the Lisp `truncate` function. Returns the integer part of the given number.
|
|
1921
|
+
* @param args the argument Cons containing the target number
|
|
1922
|
+
* @return the truncated integer
|
|
1923
|
+
*/
|
|
1924
|
+
truncate(args) {
|
|
1925
|
+
if (Cons.isNumber(args.car)) return Math.trunc(args.car);
|
|
1926
|
+
throw new EvalError(cannotApply("truncate", args.car));
|
|
1927
|
+
}
|
|
1928
|
+
/**
|
|
1929
|
+
* Implementation of the Lisp `floor` function. Returns the largest integer not greater than the given number.
|
|
1930
|
+
* @param args the argument Cons containing the target number
|
|
1931
|
+
* @return the floor of the argument
|
|
1932
|
+
*/
|
|
1933
|
+
floor(args) {
|
|
1934
|
+
if (Cons.isNumber(args.car)) return Math.floor(args.car);
|
|
1935
|
+
throw new EvalError(cannotApply("floor", args.car));
|
|
1936
|
+
}
|
|
1937
|
+
/**
|
|
1938
|
+
* Implementation of the Lisp `ceiling` function. Returns the smallest integer not less than the given number.
|
|
1939
|
+
* @param args the argument Cons containing the target number
|
|
1940
|
+
* @return the ceiling of the argument
|
|
1941
|
+
*/
|
|
1942
|
+
ceiling(args) {
|
|
1943
|
+
if (Cons.isNumber(args.car)) return Math.ceil(args.car);
|
|
1944
|
+
throw new EvalError(cannotApply("ceiling", args.car));
|
|
1945
|
+
}
|
|
1946
|
+
/**
|
|
1947
|
+
* Implementation of the Lisp `min` function. Returns the minimum of the given numbers.
|
|
1948
|
+
* @param args the argument Cons containing the numbers to compare
|
|
1949
|
+
* @return the smallest number
|
|
1950
|
+
*/
|
|
1951
|
+
min(args) {
|
|
1952
|
+
const values = [];
|
|
1953
|
+
for (const each of args.loop()) {
|
|
1954
|
+
if (!Cons.isNumber(each)) throw new EvalError(cannotApply("min", each));
|
|
1955
|
+
values.push(each);
|
|
1956
|
+
}
|
|
1957
|
+
if (values.length === 0) throw new EvalError("min requires at least one argument");
|
|
1958
|
+
return Math.min(...values);
|
|
1959
|
+
}
|
|
1960
|
+
/**
|
|
1961
|
+
* Implementation of the Lisp `max` function. Returns the maximum of the given numbers.
|
|
1962
|
+
* @param args the argument Cons containing the numbers to compare
|
|
1963
|
+
* @return the largest number
|
|
1964
|
+
*/
|
|
1965
|
+
max(args) {
|
|
1966
|
+
const values = [];
|
|
1967
|
+
for (const each of args.loop()) {
|
|
1968
|
+
if (!Cons.isNumber(each)) throw new EvalError(cannotApply("max", each));
|
|
1969
|
+
values.push(each);
|
|
1970
|
+
}
|
|
1971
|
+
if (values.length === 0) throw new EvalError("max requires at least one argument");
|
|
1972
|
+
return Math.max(...values);
|
|
1973
|
+
}
|
|
1974
|
+
/**
|
|
1975
|
+
* Implementation of the Lisp `length` function. Returns the length of a list or string.
|
|
1976
|
+
* @param args the argument Cons containing the target sequence
|
|
1977
|
+
* @return the length of the sequence
|
|
1978
|
+
*/
|
|
1979
|
+
length(args) {
|
|
1980
|
+
const target = args.car;
|
|
1981
|
+
if (Cons.isString(target)) return toCodePoints(target).length;
|
|
1982
|
+
if (Cons.isCons(target)) return target.length();
|
|
1983
|
+
if (Cons.isNil(target)) return 0;
|
|
1984
|
+
throw new EvalError(cannotApply("length", target));
|
|
1985
|
+
}
|
|
1986
|
+
/**
|
|
1987
|
+
* Implementation of the Lisp `string-upcase` function. Returns the upper-cased form of the given string.
|
|
1988
|
+
* @param args the argument Cons containing the target string
|
|
1989
|
+
* @return the upper-cased string
|
|
1990
|
+
*/
|
|
1991
|
+
stringUpcase(args) {
|
|
1992
|
+
if (Cons.isString(args.car)) return args.car.toUpperCase();
|
|
1993
|
+
throw new EvalError(cannotApply("string-upcase", args.car));
|
|
1994
|
+
}
|
|
1995
|
+
/**
|
|
1996
|
+
* Implementation of the Lisp `string-downcase` function. Returns the lower-cased form of the given string.
|
|
1997
|
+
* @param args the argument Cons containing the target string
|
|
1998
|
+
* @return the lower-cased string
|
|
1999
|
+
*/
|
|
2000
|
+
stringDowncase(args) {
|
|
2001
|
+
if (Cons.isString(args.car)) return args.car.toLowerCase();
|
|
2002
|
+
throw new EvalError(cannotApply("string-downcase", args.car));
|
|
2003
|
+
}
|
|
2004
|
+
/**
|
|
2005
|
+
* Implementation of the Lisp `string-trim` function. Returns the given string with surrounding whitespace removed.
|
|
2006
|
+
* @param args the argument Cons containing the target string
|
|
2007
|
+
* @return the trimmed string
|
|
2008
|
+
*/
|
|
2009
|
+
stringTrim(args) {
|
|
2010
|
+
if (Cons.isString(args.car)) return args.car.trim();
|
|
2011
|
+
throw new EvalError(cannotApply("string-trim", args.car));
|
|
2012
|
+
}
|
|
2013
|
+
/**
|
|
2014
|
+
* Implementation of the Lisp `substring` function. Returns a portion of the given string between start and end (in code points).
|
|
2015
|
+
* @param args the argument Cons containing the target string, start index, and optional end index
|
|
2016
|
+
* @return the requested substring
|
|
2017
|
+
*/
|
|
2018
|
+
substring(args) {
|
|
2019
|
+
const target = args.car;
|
|
2020
|
+
const start = args.nth(2);
|
|
2021
|
+
const end = args.nth(3);
|
|
2022
|
+
if (!Cons.isString(target)) throw new EvalError(cannotApply("substring", target));
|
|
2023
|
+
if (!Cons.isNumber(start)) throw new EvalError(cannotApply("substring", start));
|
|
2024
|
+
const chars = toCodePoints(target);
|
|
2025
|
+
if (Cons.isNil(end)) return chars.slice(start).join("");
|
|
2026
|
+
if (!Cons.isNumber(end)) throw new EvalError(cannotApply("substring", end));
|
|
2027
|
+
return chars.slice(start, end).join("");
|
|
2028
|
+
}
|
|
2029
|
+
/**
|
|
2030
|
+
* Implementation of the Lisp `concatenate` function. Returns the concatenation of all the given strings.
|
|
2031
|
+
* @param args the argument Cons containing the strings to concatenate
|
|
2032
|
+
* @return the concatenated string
|
|
2033
|
+
*/
|
|
2034
|
+
concatenate(args) {
|
|
2035
|
+
let result = "";
|
|
2036
|
+
for (const each of args.loop()) {
|
|
2037
|
+
if (!Cons.isString(each)) throw new EvalError(cannotApply("concatenate", each));
|
|
2038
|
+
result += each;
|
|
2039
|
+
}
|
|
2040
|
+
return result;
|
|
2041
|
+
}
|
|
2042
|
+
/**
|
|
2043
|
+
* Implementation of the Lisp `elt` function. Returns the element at the given index of a string or list.
|
|
2044
|
+
* @param args the argument Cons containing the target sequence and the index
|
|
2045
|
+
* @return the element at the given index
|
|
2046
|
+
*/
|
|
2047
|
+
elt(args) {
|
|
2048
|
+
const target = args.car;
|
|
2049
|
+
const index = args.nth(2);
|
|
2050
|
+
if (!Cons.isNumber(index)) throw new EvalError(cannotApply("elt", index));
|
|
2051
|
+
if (Cons.isString(target)) {
|
|
2052
|
+
const chars = toCodePoints(target);
|
|
2053
|
+
if (index < 0 || index >= chars.length) throw new EvalError(`elt: index ${String(index)} out of range`);
|
|
2054
|
+
return chars[index];
|
|
2055
|
+
}
|
|
2056
|
+
if (Cons.isCons(target)) {
|
|
2057
|
+
if (index < 0 || index >= target.length()) throw new EvalError(`elt: index ${String(index)} out of range`);
|
|
2058
|
+
return target.nth(index + 1);
|
|
2059
|
+
}
|
|
2060
|
+
throw new EvalError(cannotApply("elt", target));
|
|
2061
|
+
}
|
|
2062
|
+
/**
|
|
2063
|
+
* Implementation of the Lisp `subseq` function. Returns a subsequence of a string or list between start and end.
|
|
2064
|
+
* @param args the argument Cons containing the target sequence, start index, and optional end index
|
|
2065
|
+
* @return the requested subsequence
|
|
2066
|
+
*/
|
|
2067
|
+
subseq(args) {
|
|
2068
|
+
const target = args.car;
|
|
2069
|
+
const start = args.nth(2);
|
|
2070
|
+
const end = args.nth(3);
|
|
2071
|
+
if (!Cons.isNumber(start)) throw new EvalError(cannotApply("subseq", start));
|
|
2072
|
+
if (Cons.isString(target)) {
|
|
2073
|
+
const chars = toCodePoints(target);
|
|
2074
|
+
if (Cons.isNil(end)) return chars.slice(start).join("");
|
|
2075
|
+
if (!Cons.isNumber(end)) throw new EvalError(cannotApply("subseq", end));
|
|
2076
|
+
return chars.slice(start, end).join("");
|
|
2077
|
+
}
|
|
2078
|
+
if (Cons.isCons(target)) {
|
|
2079
|
+
const stop = Cons.isNil(end) ? target.length() : end;
|
|
2080
|
+
if (!Cons.isNumber(stop)) throw new EvalError(cannotApply("subseq", end));
|
|
2081
|
+
let result = Cons.nil;
|
|
2082
|
+
for (let i = stop - 1; i >= start; i--) result = new Cons(target.nth(i + 1), result);
|
|
2083
|
+
return result;
|
|
2084
|
+
}
|
|
2085
|
+
throw new EvalError(cannotApply("subseq", target));
|
|
2086
|
+
}
|
|
2087
|
+
/**
|
|
2088
|
+
* Implementation of the Lisp `count` function. Counts the occurrences of an item within a string or list.
|
|
2089
|
+
* @param args the argument Cons containing the item and the target sequence
|
|
2090
|
+
* @return the number of occurrences
|
|
2091
|
+
*/
|
|
2092
|
+
count(args) {
|
|
2093
|
+
const item = args.car;
|
|
2094
|
+
const target = args.nth(2);
|
|
2095
|
+
let n = 0;
|
|
2096
|
+
if (Cons.isString(target)) {
|
|
2097
|
+
if (!Cons.isString(item) || item.length !== 1) return 0;
|
|
2098
|
+
for (const ch of target) if (ch === item) n++;
|
|
2099
|
+
return n;
|
|
2100
|
+
}
|
|
2101
|
+
if (Cons.isCons(target) || Cons.isNil(target)) {
|
|
2102
|
+
const list = Cons.isNil(target) ? Cons.nil : target;
|
|
2103
|
+
if (Cons.isCons(list)) {
|
|
2104
|
+
for (const each of list.loop()) if (each === item) n++;
|
|
2105
|
+
}
|
|
2106
|
+
return n;
|
|
2107
|
+
}
|
|
2108
|
+
throw new EvalError(cannotApply("count", target));
|
|
2109
|
+
}
|
|
2110
|
+
/**
|
|
2111
|
+
* Implementation of the Lisp `reduce` function. Combines the elements of a list using a binary procedure.
|
|
2112
|
+
* @param args the argument Cons containing the procedure, the list, and an optional initial value
|
|
2113
|
+
* @return the result of folding the procedure over the list
|
|
2114
|
+
*/
|
|
2115
|
+
reduce(args) {
|
|
2116
|
+
const procedure = args.car;
|
|
2117
|
+
const list = args.nth(2);
|
|
2118
|
+
const hasInit = args.length() >= 3;
|
|
2119
|
+
const init = args.nth(3);
|
|
2120
|
+
if (Cons.isNil(list)) {
|
|
2121
|
+
if (hasInit) return init;
|
|
2122
|
+
return Applier.apply(procedure, Cons.nil, this.environment, this.streamManager, this.depth);
|
|
2123
|
+
}
|
|
2124
|
+
if (!Cons.isCons(list)) throw new EvalError(cannotApply("reduce", list));
|
|
2125
|
+
const iter = list.loop();
|
|
2126
|
+
let acc;
|
|
2127
|
+
if (hasInit) acc = init;
|
|
2128
|
+
else {
|
|
2129
|
+
if (!iter.hasNext()) return Applier.apply(procedure, Cons.nil, this.environment, this.streamManager, this.depth);
|
|
2130
|
+
acc = iter.next();
|
|
2131
|
+
}
|
|
2132
|
+
while (iter.hasNext()) {
|
|
2133
|
+
const next = iter.next();
|
|
2134
|
+
acc = Applier.apply(procedure, new Cons(acc, new Cons(next, Cons.nil)), this.environment, this.streamManager, this.depth);
|
|
2135
|
+
}
|
|
2136
|
+
return acc;
|
|
2137
|
+
}
|
|
2138
|
+
/**
|
|
2139
|
+
* Implementation of the Lisp `every` function. Returns t when the predicate holds for every element of the list.
|
|
2140
|
+
* @param args the argument Cons containing the predicate and the list
|
|
2141
|
+
* @return t when the predicate holds for every element, nil otherwise
|
|
2142
|
+
*/
|
|
2143
|
+
every(args) {
|
|
2144
|
+
const procedure = args.car;
|
|
2145
|
+
const list = args.nth(2);
|
|
2146
|
+
if (Cons.isNil(list)) return InterpretedSymbol.of("t");
|
|
2147
|
+
if (!Cons.isCons(list)) throw new EvalError(cannotApply("every", list));
|
|
2148
|
+
for (const each of list.loop()) {
|
|
2149
|
+
const result = Applier.apply(procedure, new Cons(each, Cons.nil), this.environment, this.streamManager, this.depth);
|
|
2150
|
+
if (Cons.isNil(result)) return Cons.nil;
|
|
2151
|
+
}
|
|
2152
|
+
return InterpretedSymbol.of("t");
|
|
2153
|
+
}
|
|
2154
|
+
/**
|
|
2155
|
+
* Implementation of the Lisp `some` function. Returns the first non-nil predicate result, or nil if all are nil.
|
|
2156
|
+
* @param args the argument Cons containing the predicate and the list
|
|
2157
|
+
* @return the first non-nil result, or nil
|
|
2158
|
+
*/
|
|
2159
|
+
some(args) {
|
|
2160
|
+
const procedure = args.car;
|
|
2161
|
+
const list = args.nth(2);
|
|
2162
|
+
if (Cons.isNil(list)) return Cons.nil;
|
|
2163
|
+
if (!Cons.isCons(list)) throw new EvalError(cannotApply("some", list));
|
|
2164
|
+
for (const each of list.loop()) {
|
|
2165
|
+
const result = Applier.apply(procedure, new Cons(each, Cons.nil), this.environment, this.streamManager, this.depth);
|
|
2166
|
+
if (Cons.isNotNil(result)) return result;
|
|
2167
|
+
}
|
|
2168
|
+
return Cons.nil;
|
|
2169
|
+
}
|
|
2170
|
+
/**
|
|
2171
|
+
* Implementation of the Lisp `find` function. Returns the first element of the list that matches the given item.
|
|
2172
|
+
* @param args the argument Cons containing the item and the list
|
|
2173
|
+
* @return the matching element, or nil if none found
|
|
2174
|
+
*/
|
|
2175
|
+
find(args) {
|
|
2176
|
+
const item = args.car;
|
|
2177
|
+
const list = args.nth(2);
|
|
2178
|
+
if (Cons.isNil(list)) return Cons.nil;
|
|
2179
|
+
if (!Cons.isCons(list)) throw new EvalError(cannotApply("find", list));
|
|
2180
|
+
for (const each of list.loop()) if (this.eq_(new Cons(item, new Cons(each, Cons.nil))) === InterpretedSymbol.of("t")) return each;
|
|
2181
|
+
return Cons.nil;
|
|
2182
|
+
}
|
|
2183
|
+
/**
|
|
2184
|
+
* Implementation of the Lisp `mapcan` function. Applies the procedure to each element and concatenates the resulting lists.
|
|
2185
|
+
* @param args the argument Cons containing the procedure and the list
|
|
2186
|
+
* @return the concatenation of the per-element results
|
|
2187
|
+
*/
|
|
2188
|
+
mapcan(args) {
|
|
2189
|
+
const procedure = args.car;
|
|
2190
|
+
const list = args.nth(2);
|
|
2191
|
+
if (Cons.isNil(list)) return Cons.nil;
|
|
2192
|
+
if (!Cons.isCons(list)) throw new EvalError(cannotApply("mapcan", list));
|
|
2193
|
+
const collected = [];
|
|
2194
|
+
for (const each of list.loop()) {
|
|
2195
|
+
const part = Applier.apply(procedure, new Cons(each, Cons.nil), this.environment, this.streamManager, this.depth);
|
|
2196
|
+
if (Cons.isCons(part)) for (const x of part.loop()) collected.push(x);
|
|
2197
|
+
}
|
|
2198
|
+
let result = Cons.nil;
|
|
2199
|
+
for (let i = collected.length - 1; i >= 0; i--) result = new Cons(collected[i], result);
|
|
2200
|
+
return result;
|
|
2201
|
+
}
|
|
2202
|
+
/**
|
|
2203
|
+
* Implementation of the Lisp `sort` function. Returns a new list sorted by the given comparison predicate.
|
|
2204
|
+
* @param args the argument Cons containing the list and the comparison predicate
|
|
2205
|
+
* @return the sorted list
|
|
2206
|
+
*/
|
|
2207
|
+
sort(args) {
|
|
2208
|
+
const list = args.car;
|
|
2209
|
+
const procedure = args.nth(2);
|
|
2210
|
+
if (Cons.isNil(list)) return Cons.nil;
|
|
2211
|
+
if (!Cons.isCons(list)) throw new EvalError(cannotApply("sort", list));
|
|
2212
|
+
const items = [];
|
|
2213
|
+
for (const each of list.loop()) items.push(each);
|
|
2214
|
+
items.sort((a, b) => {
|
|
2215
|
+
const result = Applier.apply(procedure, new Cons(a, new Cons(b, Cons.nil)), this.environment, this.streamManager, this.depth);
|
|
2216
|
+
return Cons.isNil(result) ? 1 : -1;
|
|
2217
|
+
});
|
|
2218
|
+
let result = Cons.nil;
|
|
2219
|
+
for (let i = items.length - 1; i >= 0; i--) result = new Cons(items[i], result);
|
|
2220
|
+
return result;
|
|
2221
|
+
}
|
|
2222
|
+
/**
|
|
2223
|
+
* Returns whether the given symbol is currently being spied on.
|
|
2224
|
+
* @param aSymbol the symbol to check
|
|
2225
|
+
* @return true if spied on, false otherwise
|
|
2226
|
+
*/
|
|
1500
2227
|
isSpy(aSymbol) {
|
|
1501
2228
|
return this.streamManager.isSpy(aSymbol);
|
|
1502
2229
|
}
|
|
2230
|
+
/**
|
|
2231
|
+
* Implementation of the Lisp `last` function. Returns the last cell of the given list.
|
|
2232
|
+
* @param args the argument Cons containing the target list
|
|
2233
|
+
* @return the last cell of the list
|
|
2234
|
+
*/
|
|
1503
2235
|
last(args) {
|
|
1504
2236
|
if (Cons.isNotCons(args)) return Cons.nil;
|
|
1505
2237
|
return args.car.last();
|
|
1506
2238
|
}
|
|
2239
|
+
/**
|
|
2240
|
+
* Implementation of the Lisp `<` / `lessThan` predicate. Returns t when arguments are in strictly increasing order.
|
|
2241
|
+
* @param args the argument Cons containing the numbers to compare
|
|
2242
|
+
* @return t when each is less than the next, nil otherwise
|
|
2243
|
+
*/
|
|
1507
2244
|
lessThan(args) {
|
|
1508
2245
|
if (Cons.isNumber(args.car)) return this.lessThan_Number(args.car, args.cdr);
|
|
1509
2246
|
throw new EvalError(cannotApply("<", args.car));
|
|
1510
2247
|
}
|
|
2248
|
+
/**
|
|
2249
|
+
* Helper that checks `<` ordering starting from an initial number against the remaining argument list.
|
|
2250
|
+
* @param init the initial number on the left side of the first comparison
|
|
2251
|
+
* @param args the remaining numbers to compare against
|
|
2252
|
+
* @return t when strictly increasing, nil otherwise
|
|
2253
|
+
*/
|
|
1511
2254
|
lessThan_Number(init, args) {
|
|
1512
2255
|
let leftValue = init;
|
|
1513
2256
|
let aCons = args;
|
|
@@ -1522,10 +2265,21 @@ var Applier = class Applier {
|
|
|
1522
2265
|
}
|
|
1523
2266
|
return InterpretedSymbol.of("t");
|
|
1524
2267
|
}
|
|
2268
|
+
/**
|
|
2269
|
+
* Implementation of the Lisp `<=` / `lessThanOrEqual` predicate. Returns t when arguments are in non-decreasing order.
|
|
2270
|
+
* @param args the argument Cons containing the numbers to compare
|
|
2271
|
+
* @return t when each is less than or equal to the next, nil otherwise
|
|
2272
|
+
*/
|
|
1525
2273
|
lessThanOrEqual(args) {
|
|
1526
2274
|
if (Cons.isNumber(args.car)) return this.lessThanOrEqual_Number(args.car, args.cdr);
|
|
1527
2275
|
throw new EvalError(cannotApply("<=", args.car));
|
|
1528
2276
|
}
|
|
2277
|
+
/**
|
|
2278
|
+
* Helper that checks `<=` ordering starting from an initial number against the remaining argument list.
|
|
2279
|
+
* @param init the initial number on the left side of the first comparison
|
|
2280
|
+
* @param args the remaining numbers to compare against
|
|
2281
|
+
* @return t when non-decreasing, nil otherwise
|
|
2282
|
+
*/
|
|
1529
2283
|
lessThanOrEqual_Number(init, args) {
|
|
1530
2284
|
let leftValue = init;
|
|
1531
2285
|
let aCons = args;
|
|
@@ -1540,14 +2294,29 @@ var Applier = class Applier {
|
|
|
1540
2294
|
}
|
|
1541
2295
|
return InterpretedSymbol.of("t");
|
|
1542
2296
|
}
|
|
2297
|
+
/**
|
|
2298
|
+
* Implementation of the Lisp `list` function. Returns a list of the given arguments.
|
|
2299
|
+
* @param args the argument list
|
|
2300
|
+
* @return a Cons list of the arguments
|
|
2301
|
+
*/
|
|
1543
2302
|
list(args) {
|
|
1544
2303
|
if (Cons.isNil(args)) return Cons.nil;
|
|
1545
2304
|
return new Cons(args.car, this.list(args.cdr));
|
|
1546
2305
|
}
|
|
2306
|
+
/**
|
|
2307
|
+
* Implementation of the Lisp `listp` predicate. Returns t if the argument is a list (Cons or nil).
|
|
2308
|
+
* @param args the argument Cons containing the value to test
|
|
2309
|
+
* @return t if a list, nil otherwise
|
|
2310
|
+
*/
|
|
1547
2311
|
list_(args) {
|
|
1548
2312
|
if (Cons.isList(args.car)) return InterpretedSymbol.of("t");
|
|
1549
2313
|
return Cons.nil;
|
|
1550
2314
|
}
|
|
2315
|
+
/**
|
|
2316
|
+
* Implementation of the Lisp `mapcar` function. Applies the procedure to each tuple of corresponding elements.
|
|
2317
|
+
* @param args the argument Cons containing the procedure followed by one or more lists
|
|
2318
|
+
* @return the list of results
|
|
2319
|
+
*/
|
|
1551
2320
|
mapcar(args) {
|
|
1552
2321
|
const aCons = new Cons(Cons.nil, Cons.nil);
|
|
1553
2322
|
const procedure = args.car;
|
|
@@ -1571,6 +2340,11 @@ var Applier = class Applier {
|
|
|
1571
2340
|
}
|
|
1572
2341
|
return aCons.cdr;
|
|
1573
2342
|
}
|
|
2343
|
+
/**
|
|
2344
|
+
* Implementation of the Lisp `member` function. Returns the sublist whose car matches the given item.
|
|
2345
|
+
* @param args the argument Cons containing the item, the list, and an optional comparator symbol
|
|
2346
|
+
* @return the matching sublist, or nil if not found
|
|
2347
|
+
*/
|
|
1574
2348
|
member(args) {
|
|
1575
2349
|
let aSymbol = InterpretedSymbol.of("equal?");
|
|
1576
2350
|
if (Cons.isNotNil(args.nth(3))) aSymbol = args.nth(3);
|
|
@@ -1586,14 +2360,30 @@ var Applier = class Applier {
|
|
|
1586
2360
|
}
|
|
1587
2361
|
return Cons.nil;
|
|
1588
2362
|
}
|
|
2363
|
+
/**
|
|
2364
|
+
* Implementation of the Lisp `memq` predicate. Returns t when `member` finds a match, nil otherwise.
|
|
2365
|
+
* @param args the argument Cons forwarded to `member`
|
|
2366
|
+
* @return t when found, nil otherwise
|
|
2367
|
+
*/
|
|
1589
2368
|
memq(args) {
|
|
1590
2369
|
if (this.member(args) === Cons.nil) return Cons.nil;
|
|
1591
2370
|
return InterpretedSymbol.of("t");
|
|
1592
2371
|
}
|
|
2372
|
+
/**
|
|
2373
|
+
* Implementation of the Lisp `mod` / `//` function. Returns the remainder of dividing the arguments in sequence.
|
|
2374
|
+
* @param args the argument Cons containing the numbers
|
|
2375
|
+
* @return the modulo result
|
|
2376
|
+
*/
|
|
1593
2377
|
mod(args) {
|
|
1594
2378
|
if (Cons.isNumber(args.car)) return this.mod_Number(args.car, args.cdr);
|
|
1595
2379
|
throw new EvalError(cannotApply("mod", args.car));
|
|
1596
2380
|
}
|
|
2381
|
+
/**
|
|
2382
|
+
* Helper that accumulates the modulo starting from an initial number and the remaining argument list.
|
|
2383
|
+
* @param init the initial number
|
|
2384
|
+
* @param args the remaining numbers to mod by
|
|
2385
|
+
* @return the remainder after taking modulo with each of the remaining numbers
|
|
2386
|
+
*/
|
|
1597
2387
|
mod_Number(init, args) {
|
|
1598
2388
|
let result = init;
|
|
1599
2389
|
let aCons = args;
|
|
@@ -1605,10 +2395,21 @@ var Applier = class Applier {
|
|
|
1605
2395
|
}
|
|
1606
2396
|
return result;
|
|
1607
2397
|
}
|
|
2398
|
+
/**
|
|
2399
|
+
* Implementation of the Lisp `*` / `multiply` function. Returns the product of the given numbers.
|
|
2400
|
+
* @param args the argument Cons containing the numbers to multiply
|
|
2401
|
+
* @return the product of the arguments
|
|
2402
|
+
*/
|
|
1608
2403
|
multiply(args) {
|
|
1609
2404
|
if (Cons.isNumber(args.car)) return this.multiply_Number(args.car, args.cdr);
|
|
1610
2405
|
throw new EvalError(cannotApply("multiply", args.car));
|
|
1611
2406
|
}
|
|
2407
|
+
/**
|
|
2408
|
+
* Helper that accumulates the product starting from an initial number and the remaining argument list.
|
|
2409
|
+
* @param init the initial number
|
|
2410
|
+
* @param args the remaining numbers to multiply
|
|
2411
|
+
* @return the product of init and all remaining numbers
|
|
2412
|
+
*/
|
|
1612
2413
|
multiply_Number(init, args) {
|
|
1613
2414
|
let result = init;
|
|
1614
2415
|
let aCons = args;
|
|
@@ -1620,49 +2421,105 @@ var Applier = class Applier {
|
|
|
1620
2421
|
}
|
|
1621
2422
|
return result;
|
|
1622
2423
|
}
|
|
2424
|
+
/**
|
|
2425
|
+
* Implementation of the Lisp `napier` function. Returns Napier's constant (e).
|
|
2426
|
+
* @return Math.E
|
|
2427
|
+
*/
|
|
1623
2428
|
napier() {
|
|
1624
2429
|
return Math.E;
|
|
1625
2430
|
}
|
|
2431
|
+
/**
|
|
2432
|
+
* Implementation of the Lisp `neq` / `~~` predicate. The negation of `eq`.
|
|
2433
|
+
* @param args the argument Cons forwarded to `eq_`
|
|
2434
|
+
* @return nil when eq, t otherwise
|
|
2435
|
+
*/
|
|
1626
2436
|
neq(args) {
|
|
1627
2437
|
if (this.eq_(args) === InterpretedSymbol.of("t")) return Cons.nil;
|
|
1628
2438
|
return InterpretedSymbol.of("t");
|
|
1629
2439
|
}
|
|
2440
|
+
/**
|
|
2441
|
+
* Implementation of the Lisp `nequal` / `~=` predicate. The negation of `equal`.
|
|
2442
|
+
* @param args the argument Cons forwarded to `equal_`
|
|
2443
|
+
* @return nil when equal, t otherwise
|
|
2444
|
+
*/
|
|
1630
2445
|
nequal(args) {
|
|
1631
2446
|
if (this.equal_(args) === InterpretedSymbol.of("t")) return Cons.nil;
|
|
1632
2447
|
return InterpretedSymbol.of("t");
|
|
1633
2448
|
}
|
|
2449
|
+
/**
|
|
2450
|
+
* Implementation of the Lisp `nth` function. Returns the nth element of a list.
|
|
2451
|
+
* @param args the argument Cons containing the index and the list
|
|
2452
|
+
* @return the element at the given index
|
|
2453
|
+
*/
|
|
1634
2454
|
nth(args) {
|
|
1635
2455
|
if (!Number.isInteger(args.car)) return Cons.nil;
|
|
1636
2456
|
const index = args.car;
|
|
1637
2457
|
return args.nth(2).nth(index);
|
|
1638
2458
|
}
|
|
2459
|
+
/**
|
|
2460
|
+
* Implementation of the Lisp `null` predicate. Returns t if the argument is nil, otherwise nil.
|
|
2461
|
+
* @param args the argument Cons containing the value to test
|
|
2462
|
+
* @return t if nil, nil otherwise
|
|
2463
|
+
*/
|
|
1639
2464
|
null_(args) {
|
|
1640
2465
|
if (Cons.isNil(args.car)) return InterpretedSymbol.of("t");
|
|
1641
2466
|
return Cons.nil;
|
|
1642
2467
|
}
|
|
2468
|
+
/**
|
|
2469
|
+
* Implementation of the Lisp `numberp` / `doublep` predicate. Returns t if the argument is a number.
|
|
2470
|
+
* @param args the argument Cons containing the value to test
|
|
2471
|
+
* @return t if a number, nil otherwise
|
|
2472
|
+
*/
|
|
1643
2473
|
number_(args) {
|
|
1644
2474
|
if (Cons.isNumber(args.car)) return InterpretedSymbol.of("t");
|
|
1645
2475
|
return Cons.nil;
|
|
1646
2476
|
}
|
|
2477
|
+
/**
|
|
2478
|
+
* Implementation of the Lisp `pi` function. Returns the mathematical constant pi.
|
|
2479
|
+
* @return Math.PI
|
|
2480
|
+
*/
|
|
1647
2481
|
pi() {
|
|
1648
2482
|
return Math.PI;
|
|
1649
2483
|
}
|
|
2484
|
+
/**
|
|
2485
|
+
* Implementation of the Lisp `random` function. Returns a pseudo-random number in [0, 1).
|
|
2486
|
+
* @return a random number in [0, 1)
|
|
2487
|
+
*/
|
|
1650
2488
|
random() {
|
|
1651
2489
|
return Math.random();
|
|
1652
2490
|
}
|
|
2491
|
+
/**
|
|
2492
|
+
* Implementation of the Lisp `round` function. Returns the given number rounded to the nearest integer.
|
|
2493
|
+
* @param args the argument Cons containing the target number
|
|
2494
|
+
* @return the rounded integer
|
|
2495
|
+
*/
|
|
1653
2496
|
round(args) {
|
|
1654
2497
|
if (Cons.isNumber(args.car)) return Math.round(args.car);
|
|
1655
2498
|
throw new ReferenceError(SELECT_PRINT_FUNCTION_NOT_DEFINED);
|
|
1656
2499
|
}
|
|
2500
|
+
/**
|
|
2501
|
+
* Dispatches the call to either a built-in function or a user-defined function based on the symbol.
|
|
2502
|
+
* @param procedure the symbol naming the function
|
|
2503
|
+
* @param args the argument list
|
|
2504
|
+
* @return the result of the dispatched function
|
|
2505
|
+
*/
|
|
1657
2506
|
selectProcedure(procedure, args) {
|
|
1658
2507
|
if (Applier.buildInFunctions.has(procedure)) return this.buildInFunction(procedure, args);
|
|
1659
2508
|
if (this.environment.has(procedure)) return this.userFunction(procedure, args);
|
|
1660
2509
|
throw new EvalError(noProcedure(procedure));
|
|
1661
2510
|
}
|
|
2511
|
+
/**
|
|
2512
|
+
* Sets the current recursion depth.
|
|
2513
|
+
* @param aNumber the new depth value
|
|
2514
|
+
*/
|
|
1662
2515
|
setDepth(aNumber) {
|
|
1663
2516
|
this.depth = aNumber;
|
|
1664
2517
|
return null;
|
|
1665
2518
|
}
|
|
2519
|
+
/**
|
|
2520
|
+
* Builds and returns the Lisp-name to method-name dispatch map.
|
|
2521
|
+
* @return a Map associating each Lisp function name (as an InterpretedSymbol) with the corresponding Applier method name
|
|
2522
|
+
*/
|
|
1666
2523
|
static setup() {
|
|
1667
2524
|
try {
|
|
1668
2525
|
return new Map([
|
|
@@ -1676,22 +2533,36 @@ var Applier = class Applier {
|
|
|
1676
2533
|
["cons", "cons"],
|
|
1677
2534
|
["consp", "cons_"],
|
|
1678
2535
|
["copy", "copy"],
|
|
2536
|
+
["ceiling", "ceiling"],
|
|
1679
2537
|
["cos", "cos"],
|
|
1680
2538
|
["floatp", "float_"],
|
|
2539
|
+
["floor", "floor"],
|
|
1681
2540
|
["divide", "divide"],
|
|
1682
2541
|
["doublep", "number_"],
|
|
1683
2542
|
["eq", "eq_"],
|
|
1684
2543
|
["equal", "equal_"],
|
|
2544
|
+
["evenp", "even_"],
|
|
2545
|
+
["every", "every"],
|
|
1685
2546
|
["exp", "exp"],
|
|
2547
|
+
["expt", "expt"],
|
|
2548
|
+
["find", "find"],
|
|
1686
2549
|
["format", "format"],
|
|
1687
2550
|
["gensym", "gensym"],
|
|
1688
2551
|
["integerp", "integer_"],
|
|
2552
|
+
["concatenate", "concatenate"],
|
|
2553
|
+
["count", "count"],
|
|
2554
|
+
["elt", "elt"],
|
|
1689
2555
|
["last", "last"],
|
|
2556
|
+
["length", "length"],
|
|
1690
2557
|
["list", "list"],
|
|
1691
2558
|
["listp", "list_"],
|
|
2559
|
+
["mapcan", "mapcan"],
|
|
1692
2560
|
["mapcar", "mapcar"],
|
|
2561
|
+
["max", "max"],
|
|
1693
2562
|
["member", "member"],
|
|
1694
2563
|
["memq", "memq"],
|
|
2564
|
+
["min", "min"],
|
|
2565
|
+
["minusp", "minus_"],
|
|
1695
2566
|
["mod", "mod"],
|
|
1696
2567
|
["multiply", "multiply"],
|
|
1697
2568
|
["napier", "napier"],
|
|
@@ -1700,15 +2571,29 @@ var Applier = class Applier {
|
|
|
1700
2571
|
["nth", "nth"],
|
|
1701
2572
|
["null", "null_"],
|
|
1702
2573
|
["numberp", "number_"],
|
|
2574
|
+
["oddp", "odd_"],
|
|
1703
2575
|
["pi", "pi"],
|
|
2576
|
+
["plusp", "plus_"],
|
|
1704
2577
|
["random", "random"],
|
|
2578
|
+
["reduce", "reduce"],
|
|
1705
2579
|
["round", "round"],
|
|
1706
2580
|
["sin", "sin"],
|
|
2581
|
+
["some", "some"],
|
|
2582
|
+
["sort", "sort"],
|
|
1707
2583
|
["sqrt", "sqrt"],
|
|
1708
|
-
["
|
|
2584
|
+
["string-downcase", "stringDowncase"],
|
|
2585
|
+
["string-trim", "stringTrim"],
|
|
2586
|
+
["string-upcase", "stringUpcase"],
|
|
1709
2587
|
["stringp", "string_"],
|
|
2588
|
+
["subseq", "subseq"],
|
|
2589
|
+
["substring", "substring"],
|
|
2590
|
+
["subtract", "subtract"],
|
|
1710
2591
|
["symbolp", "symbol_"],
|
|
1711
2592
|
["tan", "tan"],
|
|
2593
|
+
["truncate", "truncate"],
|
|
2594
|
+
["zerop", "zero_"],
|
|
2595
|
+
["1+", "oneplus"],
|
|
2596
|
+
["1-", "oneminus"],
|
|
1712
2597
|
["+", "add"],
|
|
1713
2598
|
["-", "subtract"],
|
|
1714
2599
|
["*", "multiply"],
|
|
@@ -1727,26 +2612,57 @@ var Applier = class Applier {
|
|
|
1727
2612
|
throw new Error("NullPointerException (Applier, initialize)");
|
|
1728
2613
|
}
|
|
1729
2614
|
}
|
|
2615
|
+
/**
|
|
2616
|
+
* Implementation of the Lisp `sin` function. Returns the sine of the given number.
|
|
2617
|
+
* @param args the argument Cons containing the angle in radians
|
|
2618
|
+
* @return the sine of the argument
|
|
2619
|
+
*/
|
|
1730
2620
|
sin(args) {
|
|
1731
2621
|
if (Cons.isNumber(args.car)) return Math.sin(args.car);
|
|
1732
2622
|
throw new ReferenceError(SELECT_PRINT_FUNCTION_NOT_DEFINED);
|
|
1733
2623
|
}
|
|
2624
|
+
/**
|
|
2625
|
+
* Writes a single line of spy output (with indentation) to the given stream.
|
|
2626
|
+
* @param aStream the stream to write to, or null/string to fall back to process.stdout
|
|
2627
|
+
* @param line the line to write
|
|
2628
|
+
*/
|
|
1734
2629
|
spyPrint(aStream, line) {
|
|
1735
2630
|
(aStream != null && typeof aStream === "object" && "write" in aStream ? aStream : process.stdout).write(this.indent() + line + "\n");
|
|
1736
2631
|
return null;
|
|
1737
2632
|
}
|
|
2633
|
+
/**
|
|
2634
|
+
* Implementation of the Lisp `sqrt` function. Returns the square root of the given number.
|
|
2635
|
+
* @param args the argument Cons containing the target number
|
|
2636
|
+
* @return the square root of the argument
|
|
2637
|
+
*/
|
|
1738
2638
|
sqrt(args) {
|
|
1739
2639
|
if (Cons.isNumber(args.car)) return Math.sqrt(args.car);
|
|
1740
2640
|
throw new ReferenceError(SELECT_PRINT_FUNCTION_NOT_DEFINED);
|
|
1741
2641
|
}
|
|
2642
|
+
/**
|
|
2643
|
+
* Implementation of the Lisp `stringp` predicate. Returns t if the argument is a string.
|
|
2644
|
+
* @param args the argument Cons containing the value to test
|
|
2645
|
+
* @return t if a string, nil otherwise
|
|
2646
|
+
*/
|
|
1742
2647
|
string_(args) {
|
|
1743
2648
|
if (Cons.isString(args.car)) return InterpretedSymbol.of("t");
|
|
1744
2649
|
return Cons.nil;
|
|
1745
2650
|
}
|
|
2651
|
+
/**
|
|
2652
|
+
* Implementation of the Lisp `-` / `subtract` function. Returns the difference of the given numbers.
|
|
2653
|
+
* @param args the argument Cons containing the numbers to subtract
|
|
2654
|
+
* @return the difference of the arguments
|
|
2655
|
+
*/
|
|
1746
2656
|
subtract(args) {
|
|
1747
2657
|
if (Cons.isNumber(args.car)) return this.subtract_Number(args.car, args.cdr);
|
|
1748
2658
|
throw new EvalError(cannotApply("subtract", args.car));
|
|
1749
2659
|
}
|
|
2660
|
+
/**
|
|
2661
|
+
* Helper that accumulates the difference starting from an initial number and the remaining argument list.
|
|
2662
|
+
* @param init the initial number
|
|
2663
|
+
* @param args the remaining numbers to subtract
|
|
2664
|
+
* @return init with all remaining numbers subtracted
|
|
2665
|
+
*/
|
|
1750
2666
|
subtract_Number(init, args) {
|
|
1751
2667
|
let result = init;
|
|
1752
2668
|
let aCons = args;
|
|
@@ -1758,14 +2674,30 @@ var Applier = class Applier {
|
|
|
1758
2674
|
}
|
|
1759
2675
|
return result;
|
|
1760
2676
|
}
|
|
2677
|
+
/**
|
|
2678
|
+
* Implementation of the Lisp `symbolp` predicate. Returns t if the argument is an interpreted symbol.
|
|
2679
|
+
* @param args the argument Cons containing the value to test
|
|
2680
|
+
* @return t if a symbol, nil otherwise
|
|
2681
|
+
*/
|
|
1761
2682
|
symbol_(args) {
|
|
1762
2683
|
if (Cons.isSymbol(args.car)) return InterpretedSymbol.of("t");
|
|
1763
2684
|
return Cons.nil;
|
|
1764
2685
|
}
|
|
2686
|
+
/**
|
|
2687
|
+
* Implementation of the Lisp `tan` function. Returns the tangent of the given number.
|
|
2688
|
+
* @param args the argument Cons containing the angle in radians
|
|
2689
|
+
* @return the tangent of the argument
|
|
2690
|
+
*/
|
|
1765
2691
|
tan(args) {
|
|
1766
2692
|
if (Cons.isNumber(args.car)) return Math.tan(args.car);
|
|
1767
2693
|
throw new ReferenceError(SELECT_PRINT_FUNCTION_NOT_DEFINED);
|
|
1768
2694
|
}
|
|
2695
|
+
/**
|
|
2696
|
+
* Invokes a user-defined function (lambda) bound in the environment under the given symbol.
|
|
2697
|
+
* @param procedure the symbol naming the user function
|
|
2698
|
+
* @param args the argument list
|
|
2699
|
+
* @return the result of evaluating the user function
|
|
2700
|
+
*/
|
|
1769
2701
|
userFunction(procedure, args) {
|
|
1770
2702
|
if (this.isSpy(procedure)) {
|
|
1771
2703
|
this.spyPrint(this.streamManager.spyStream(procedure), new Cons(procedure, args).toString());
|
|
@@ -1785,21 +2717,42 @@ var Applier = class Applier {
|
|
|
1785
2717
|
//#region src/runtime/StreamManager/index.ts
|
|
1786
2718
|
/**
|
|
1787
2719
|
* @class
|
|
1788
|
-
* @classdesc
|
|
2720
|
+
* @classdesc Manages output streams (stdout / stderr / spy / trace) used by the interpreter.
|
|
1789
2721
|
* @author Keisuke Ikeda
|
|
1790
2722
|
* @this {StreamManager}
|
|
1791
2723
|
*/
|
|
1792
|
-
var StreamManager = class {
|
|
2724
|
+
var StreamManager = class extends Object {
|
|
2725
|
+
/**
|
|
2726
|
+
* Whether tracing is currently enabled.
|
|
2727
|
+
*/
|
|
1793
2728
|
isTrace = false;
|
|
2729
|
+
/**
|
|
2730
|
+
* Map from a named stream key (e.g. "default", "stdout", "stderr") to a WritableStream.
|
|
2731
|
+
*/
|
|
1794
2732
|
streamTable;
|
|
2733
|
+
/**
|
|
2734
|
+
* Map from a spied symbol to the stream key used for that symbol's output.
|
|
2735
|
+
*/
|
|
1795
2736
|
spyTable;
|
|
2737
|
+
/**
|
|
2738
|
+
* The stream that receives trace output while tracing is on.
|
|
2739
|
+
*/
|
|
1796
2740
|
traceStream;
|
|
2741
|
+
/**
|
|
2742
|
+
* Constructor.
|
|
2743
|
+
* @constructor
|
|
2744
|
+
*/
|
|
1797
2745
|
constructor() {
|
|
2746
|
+
super();
|
|
1798
2747
|
this.streamTable = /* @__PURE__ */ new Map();
|
|
1799
2748
|
this.spyTable = /* @__PURE__ */ new Map();
|
|
1800
2749
|
this.traceStream = null;
|
|
1801
2750
|
this.initialize();
|
|
1802
2751
|
}
|
|
2752
|
+
/**
|
|
2753
|
+
* Returns the currently selected output stream (trace stream when tracing, otherwise the default).
|
|
2754
|
+
* @return the active stream, or null when none is available
|
|
2755
|
+
*/
|
|
1803
2756
|
getStream() {
|
|
1804
2757
|
let aPrintStream = null;
|
|
1805
2758
|
if (this.isTrace) return this.traceStream();
|
|
@@ -1809,6 +2762,7 @@ var StreamManager = class {
|
|
|
1809
2762
|
}
|
|
1810
2763
|
/**
|
|
1811
2764
|
* Initializes the instance variables.
|
|
2765
|
+
* @return null
|
|
1812
2766
|
*/
|
|
1813
2767
|
initialize() {
|
|
1814
2768
|
this.streamTable.set("default", process.stdout);
|
|
@@ -1816,42 +2770,85 @@ var StreamManager = class {
|
|
|
1816
2770
|
this.streamTable.set("stderr", process.stderr);
|
|
1817
2771
|
return null;
|
|
1818
2772
|
}
|
|
2773
|
+
/**
|
|
2774
|
+
* Returns whether the given symbol is being spied (or whether tracing is on, in which case every symbol is "spied").
|
|
2775
|
+
* @param aSymbol the symbol to check, or null
|
|
2776
|
+
* @return true if the symbol is spied or tracing is on
|
|
2777
|
+
*/
|
|
1819
2778
|
isSpy(aSymbol) {
|
|
1820
2779
|
if (this.isTrace) return true;
|
|
1821
2780
|
if (aSymbol != null && this.spyTable_().has(aSymbol)) return true;
|
|
1822
2781
|
return false;
|
|
1823
2782
|
}
|
|
2783
|
+
/**
|
|
2784
|
+
* Removes the given symbol from the spy table.
|
|
2785
|
+
* @param aSymbol the symbol to stop spying
|
|
2786
|
+
* @return null
|
|
2787
|
+
*/
|
|
1824
2788
|
noSpy(aSymbol) {
|
|
1825
2789
|
if (this.spyTable_().has(aSymbol)) this.spyTable_().delete(aSymbol);
|
|
1826
2790
|
return null;
|
|
1827
2791
|
}
|
|
2792
|
+
/**
|
|
2793
|
+
* Turns tracing off and clears the spy table.
|
|
2794
|
+
* @return null
|
|
2795
|
+
*/
|
|
1828
2796
|
noTrace() {
|
|
1829
2797
|
this.setIsTrace(false);
|
|
1830
2798
|
this.spyTable.clear();
|
|
1831
2799
|
return null;
|
|
1832
2800
|
}
|
|
2801
|
+
/**
|
|
2802
|
+
* Sets the tracing flag.
|
|
2803
|
+
* @param aBoolean the new value for the tracing flag
|
|
2804
|
+
* @return null
|
|
2805
|
+
*/
|
|
1833
2806
|
setIsTrace(aBoolean) {
|
|
1834
2807
|
this.isTrace = aBoolean;
|
|
1835
2808
|
return null;
|
|
1836
2809
|
}
|
|
2810
|
+
/**
|
|
2811
|
+
* Sets the trace output stream.
|
|
2812
|
+
* @param aStream the stream to send trace output to
|
|
2813
|
+
* @return null
|
|
2814
|
+
*/
|
|
1837
2815
|
setTraceStream(aStream) {
|
|
1838
2816
|
this.traceStream = aStream;
|
|
1839
2817
|
return null;
|
|
1840
2818
|
}
|
|
2819
|
+
/**
|
|
2820
|
+
* Registers the given symbol as spied with the given stream key.
|
|
2821
|
+
* @param aSymbol the symbol to spy on
|
|
2822
|
+
* @param aString the stream key (e.g. "default")
|
|
2823
|
+
* @return null
|
|
2824
|
+
*/
|
|
1841
2825
|
spy(aSymbol, aString) {
|
|
1842
2826
|
if (this.getStream() != null) this.spyTable_().set(aSymbol, aString);
|
|
1843
2827
|
return null;
|
|
1844
2828
|
}
|
|
2829
|
+
/**
|
|
2830
|
+
* Returns the stream (or stream-key string) used for the given symbol's spy output.
|
|
2831
|
+
* @param aSymbol the symbol whose spy stream is requested, or null
|
|
2832
|
+
* @return the trace stream, the registered key string, or throws if none is found
|
|
2833
|
+
*/
|
|
1845
2834
|
spyStream(aSymbol) {
|
|
1846
2835
|
if (this.isTrace) return this.traceStream;
|
|
1847
2836
|
if (aSymbol != null && this.spyTable_().has(aSymbol)) return this.spyTable_().get(aSymbol);
|
|
1848
2837
|
throw new Error("Stream is not found.");
|
|
1849
2838
|
}
|
|
2839
|
+
/**
|
|
2840
|
+
* Returns a copy of the spy table (defensive copy so callers do not mutate the internal map).
|
|
2841
|
+
* @return a new map containing the same entries as the internal spy table
|
|
2842
|
+
*/
|
|
1850
2843
|
spyTable_() {
|
|
1851
2844
|
const aTable = /* @__PURE__ */ new Map();
|
|
1852
2845
|
for (const [key, value] of this.spyTable) aTable.set(key, value);
|
|
1853
2846
|
return aTable;
|
|
1854
2847
|
}
|
|
2848
|
+
/**
|
|
2849
|
+
* Turns tracing on, routing trace output to the currently active stream.
|
|
2850
|
+
* @return null
|
|
2851
|
+
*/
|
|
1855
2852
|
trace() {
|
|
1856
2853
|
this.noTrace();
|
|
1857
2854
|
const aPrintStream = this.getStream();
|
|
@@ -1876,36 +2873,81 @@ const triggerGc = () => {
|
|
|
1876
2873
|
* @author Keisuke Ikeda
|
|
1877
2874
|
* @this {Evaluator}
|
|
1878
2875
|
*/
|
|
1879
|
-
var Evaluator = class Evaluator {
|
|
2876
|
+
var Evaluator = class Evaluator extends Object {
|
|
2877
|
+
/**
|
|
2878
|
+
* Lisp-name to method-name dispatch map for special forms.
|
|
2879
|
+
*/
|
|
1880
2880
|
static buildInFunctions = Evaluator.setup();
|
|
2881
|
+
/**
|
|
2882
|
+
* The variable binding environment used during evaluation.
|
|
2883
|
+
*/
|
|
1881
2884
|
environment;
|
|
2885
|
+
/**
|
|
2886
|
+
* The stream manager used for trace and spy output.
|
|
2887
|
+
*/
|
|
1882
2888
|
streamManager;
|
|
2889
|
+
/**
|
|
2890
|
+
* The current call depth, used for indenting trace/spy output.
|
|
2891
|
+
*/
|
|
1883
2892
|
depth;
|
|
1884
|
-
|
|
2893
|
+
/**
|
|
2894
|
+
* Registered plugins consulted by `eval` when no special form matches.
|
|
2895
|
+
*/
|
|
2896
|
+
plugins;
|
|
2897
|
+
/**
|
|
2898
|
+
* Constructor.
|
|
2899
|
+
* @param aTable the variable binding environment
|
|
2900
|
+
* @param aStreamManager the stream manager for trace and spy output
|
|
2901
|
+
* @param aNumber the initial call depth
|
|
2902
|
+
* @param plugins the plugin chain consulted before falling through to Applier
|
|
2903
|
+
*/
|
|
2904
|
+
constructor(aTable, aStreamManager, aNumber, plugins = []) {
|
|
2905
|
+
super();
|
|
1885
2906
|
this.environment = aTable;
|
|
1886
2907
|
this.streamManager = aStreamManager;
|
|
1887
2908
|
this.depth = aNumber;
|
|
2909
|
+
this.plugins = plugins;
|
|
1888
2910
|
}
|
|
2911
|
+
/**
|
|
2912
|
+
* Implementation of the Lisp `and` special form.
|
|
2913
|
+
* @param aCons the argument Cons containing the expressions to evaluate
|
|
2914
|
+
* @return nil if any expression evaluates to nil, otherwise t
|
|
2915
|
+
*/
|
|
1889
2916
|
and(aCons) {
|
|
1890
2917
|
for (const each of aCons.loop()) {
|
|
1891
|
-
const anObject = Evaluator.eval(each, this.environment, this.streamManager, this.depth);
|
|
2918
|
+
const anObject = Evaluator.eval(each, this.environment, this.streamManager, this.depth, this.plugins);
|
|
1892
2919
|
if (Cons.isNil(anObject)) return Cons.nil;
|
|
1893
2920
|
}
|
|
1894
2921
|
return InterpretedSymbol.of("t");
|
|
1895
2922
|
}
|
|
2923
|
+
/**
|
|
2924
|
+
* Implementation of the Lisp `apply` special form.
|
|
2925
|
+
* @param aCons the argument Cons containing the procedure and its argument list
|
|
2926
|
+
* @return the result of applying the procedure to the arguments
|
|
2927
|
+
*/
|
|
1896
2928
|
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);
|
|
2929
|
+
const procedure = Evaluator.eval(aCons.car, this.environment, this.streamManager, this.depth, this.plugins);
|
|
2930
|
+
const args = Evaluator.eval(aCons.nth(2), this.environment, this.streamManager, this.depth, this.plugins);
|
|
1899
2931
|
let aTable = this.environment;
|
|
1900
2932
|
if (procedure instanceof Cons && procedure.last().car instanceof Table) aTable = procedure.last().car;
|
|
1901
|
-
return Applier.apply(procedure, args, aTable, this.streamManager, this.depth);
|
|
2933
|
+
return Applier.apply(procedure, args, aTable, this.streamManager, this.depth, this.plugins);
|
|
1902
2934
|
}
|
|
2935
|
+
/**
|
|
2936
|
+
* Implementation of the Lisp `bind` special form.
|
|
2937
|
+
* @param aCons the argument Cons whose car is the symbol to look up
|
|
2938
|
+
* @return the binding count for the symbol, or nil if unbound
|
|
2939
|
+
*/
|
|
1903
2940
|
bind(aCons) {
|
|
1904
2941
|
if (Cons.isNotSymbol(aCons.car)) throw new EvalError(cannotApply("bind", aCons.car));
|
|
1905
2942
|
const aSymbol = aCons.car;
|
|
1906
2943
|
if (!this.environment.has(aSymbol)) return Cons.nil;
|
|
1907
2944
|
return this.bindAUX(aSymbol);
|
|
1908
2945
|
}
|
|
2946
|
+
/**
|
|
2947
|
+
* Counts the number of distinct bindings for the given symbol along the environment chain.
|
|
2948
|
+
* @param aSymbol the symbol whose bindings are inspected
|
|
2949
|
+
* @return the number of distinct bindings found
|
|
2950
|
+
*/
|
|
1909
2951
|
bindAUX(aSymbol) {
|
|
1910
2952
|
let aTable = this.environment;
|
|
1911
2953
|
let anObject = aTable.get(aSymbol);
|
|
@@ -1921,98 +2963,138 @@ var Evaluator = class Evaluator {
|
|
|
1921
2963
|
}
|
|
1922
2964
|
return count;
|
|
1923
2965
|
}
|
|
2966
|
+
/**
|
|
2967
|
+
* Sequentially evaluates and binds each (symbol value) pair into the given table; used by let*.
|
|
2968
|
+
* @param parameters the Cons of (symbol value) pairs to bind
|
|
2969
|
+
* @param aTable the table into which the bindings are written
|
|
2970
|
+
*/
|
|
1924
2971
|
binding(parameters, aTable) {
|
|
1925
2972
|
for (const each of parameters.loop()) {
|
|
1926
2973
|
const theCons = each;
|
|
1927
2974
|
if (Cons.isNotSymbol(theCons.car)) throw new EvalError(notSymbol(theCons.car));
|
|
1928
2975
|
const key = theCons.car;
|
|
1929
|
-
const value = Evaluator.eval(theCons.nth(2), aTable, this.streamManager, this.depth);
|
|
2976
|
+
const value = Evaluator.eval(theCons.nth(2), aTable, this.streamManager, this.depth, this.plugins);
|
|
1930
2977
|
aTable.set(key, value);
|
|
1931
2978
|
}
|
|
1932
2979
|
return null;
|
|
1933
2980
|
}
|
|
2981
|
+
/**
|
|
2982
|
+
* Evaluates all (symbol value) pairs first and then writes them into the given table in parallel; used by let.
|
|
2983
|
+
* @param parameters the Cons of (symbol value) pairs to bind
|
|
2984
|
+
* @param aTable the table into which the bindings are written
|
|
2985
|
+
*/
|
|
1934
2986
|
bindingParallel(parameters, aTable) {
|
|
1935
2987
|
const theTable = /* @__PURE__ */ new Map();
|
|
1936
2988
|
for (const each of parameters.loop()) {
|
|
1937
2989
|
const theCons = each;
|
|
1938
2990
|
if (Cons.isNotSymbol(theCons.car)) throw new EvalError(notSymbol(theCons.car));
|
|
1939
2991
|
const key = theCons.car;
|
|
1940
|
-
const value = Evaluator.eval(theCons.nth(2), aTable, this.streamManager, this.depth);
|
|
2992
|
+
const value = Evaluator.eval(theCons.nth(2), aTable, this.streamManager, this.depth, this.plugins);
|
|
1941
2993
|
theTable.set(key, value);
|
|
1942
2994
|
}
|
|
1943
2995
|
for (const [key, value] of theTable) aTable.set(key, value);
|
|
1944
2996
|
return null;
|
|
1945
2997
|
}
|
|
2998
|
+
/**
|
|
2999
|
+
* Implementation of the Lisp `cond` special form.
|
|
3000
|
+
* @param aCons the argument Cons of (test consequent...) clauses
|
|
3001
|
+
* @return the result of the first clause whose test is non-nil, or nil
|
|
3002
|
+
*/
|
|
1946
3003
|
cond(aCons) {
|
|
1947
3004
|
if (Cons.isNil(aCons)) return Cons.nil;
|
|
1948
3005
|
const consCell = aCons;
|
|
1949
3006
|
const clause = consCell.car;
|
|
1950
|
-
let anObject = Evaluator.eval(clause.car, this.environment, this.streamManager, this.depth);
|
|
3007
|
+
let anObject = Evaluator.eval(clause.car, this.environment, this.streamManager, this.depth, this.plugins);
|
|
1951
3008
|
if (Cons.isNil(anObject)) return this.cond(consCell.cdr);
|
|
1952
3009
|
const consequent = clause.cdr;
|
|
1953
|
-
for (const each of consequent.loop()) anObject = Evaluator.eval(each, this.environment, this.streamManager, this.depth);
|
|
3010
|
+
for (const each of consequent.loop()) anObject = Evaluator.eval(each, this.environment, this.streamManager, this.depth, this.plugins);
|
|
1954
3011
|
return anObject;
|
|
1955
3012
|
}
|
|
3013
|
+
/**
|
|
3014
|
+
* Implementation of the Lisp `defun` special form.
|
|
3015
|
+
* @param aCons the argument Cons containing the function name, parameter list, and body
|
|
3016
|
+
* @return the function name symbol
|
|
3017
|
+
*/
|
|
1956
3018
|
defun(aCons) {
|
|
1957
3019
|
const variable = aCons.car;
|
|
1958
3020
|
let lambda = aCons.cdr;
|
|
1959
3021
|
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);
|
|
3022
|
+
lambda = Evaluator.eval(lambda, new Table(this.environment), this.streamManager, this.depth, this.plugins);
|
|
1961
3023
|
this.environment.set(variable, lambda);
|
|
1962
3024
|
return variable;
|
|
1963
3025
|
}
|
|
3026
|
+
/**
|
|
3027
|
+
* Implementation of the Lisp `do` special form (parallel binding update).
|
|
3028
|
+
* @param aCons the argument Cons containing bindings, termination clause, and body
|
|
3029
|
+
* @return the value of the termination clause's result form
|
|
3030
|
+
*/
|
|
1964
3031
|
do_(aCons) {
|
|
1965
3032
|
const parameters = aCons.car;
|
|
1966
3033
|
const bool = aCons.nth(2);
|
|
1967
3034
|
const expressions = aCons.cdr.cdr;
|
|
1968
3035
|
this.bindingParallel(parameters, this.environment);
|
|
1969
3036
|
if (Cons.isNil(bool)) bool.setCar(Cons.nil);
|
|
1970
|
-
while (Cons.isNil(Evaluator.eval(bool.car, this.environment, this.streamManager, this.depth))) {
|
|
3037
|
+
while (Cons.isNil(Evaluator.eval(bool.car, this.environment, this.streamManager, this.depth, this.plugins))) {
|
|
1971
3038
|
const theTable = /* @__PURE__ */ new Map();
|
|
1972
|
-
for (const each of expressions.loop()) Evaluator.eval(each, this.environment, this.streamManager, this.depth);
|
|
3039
|
+
for (const each of expressions.loop()) Evaluator.eval(each, this.environment, this.streamManager, this.depth, this.plugins);
|
|
1973
3040
|
for (const each of parameters.loop()) {
|
|
1974
3041
|
const theCons = each;
|
|
1975
3042
|
if (Cons.isNotSymbol(theCons.car)) throw new EvalError(notSymbol(theCons.car));
|
|
1976
3043
|
const key = theCons.car;
|
|
1977
3044
|
if (Cons.isNotNil(theCons.nth(3))) {
|
|
1978
|
-
const value = Evaluator.eval(theCons.nth(3), this.environment, this.streamManager, this.depth);
|
|
3045
|
+
const value = Evaluator.eval(theCons.nth(3), this.environment, this.streamManager, this.depth, this.plugins);
|
|
1979
3046
|
theTable.set(key, value);
|
|
1980
3047
|
}
|
|
1981
3048
|
}
|
|
1982
3049
|
for (const [key, value] of theTable) this.environment.set(key, value);
|
|
1983
3050
|
}
|
|
1984
|
-
return Evaluator.eval(bool.nth(2), this.environment, this.streamManager, this.depth);
|
|
3051
|
+
return Evaluator.eval(bool.nth(2), this.environment, this.streamManager, this.depth, this.plugins);
|
|
1985
3052
|
}
|
|
3053
|
+
/**
|
|
3054
|
+
* Implementation of the Lisp `dolist` special form.
|
|
3055
|
+
* @param aCons the argument Cons containing the binding clause and body
|
|
3056
|
+
* @return the value of the result form
|
|
3057
|
+
*/
|
|
1986
3058
|
doList(aCons) {
|
|
1987
3059
|
const parameter = aCons.car;
|
|
1988
3060
|
const theCons = aCons.cdr;
|
|
1989
|
-
const args = Evaluator.eval(parameter.nth(2), this.environment, this.streamManager, this.depth);
|
|
3061
|
+
const args = Evaluator.eval(parameter.nth(2), this.environment, this.streamManager, this.depth, this.plugins);
|
|
1990
3062
|
for (const element of args.loop()) {
|
|
1991
3063
|
this.environment.set(parameter.car, element);
|
|
1992
|
-
for (const each of theCons.loop()) Evaluator.eval(each, this.environment, this.streamManager, this.depth);
|
|
3064
|
+
for (const each of theCons.loop()) Evaluator.eval(each, this.environment, this.streamManager, this.depth, this.plugins);
|
|
1993
3065
|
}
|
|
1994
|
-
return Evaluator.eval(parameter.nth(3), this.environment, this.streamManager, this.depth);
|
|
3066
|
+
return Evaluator.eval(parameter.nth(3), this.environment, this.streamManager, this.depth, this.plugins);
|
|
1995
3067
|
}
|
|
3068
|
+
/**
|
|
3069
|
+
* Implementation of the Lisp `do*` special form (sequential binding update).
|
|
3070
|
+
* @param aCons the argument Cons containing bindings, termination clause, and body
|
|
3071
|
+
* @return the value of the termination clause's result form
|
|
3072
|
+
*/
|
|
1996
3073
|
doStar(aCons) {
|
|
1997
3074
|
const parameters = aCons.car;
|
|
1998
3075
|
const bool = aCons.nth(2);
|
|
1999
3076
|
const expressions = aCons.cdr.cdr;
|
|
2000
3077
|
this.binding(parameters, this.environment);
|
|
2001
3078
|
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);
|
|
3079
|
+
while (Cons.isNil(Evaluator.eval(bool.car, this.environment, this.streamManager, this.depth, this.plugins))) {
|
|
3080
|
+
for (const each of expressions.loop()) Evaluator.eval(each, this.environment, this.streamManager, this.depth, this.plugins);
|
|
2004
3081
|
for (const each of parameters.loop()) {
|
|
2005
3082
|
const theCons = each;
|
|
2006
3083
|
if (Cons.isNotSymbol(theCons.car)) throw new EvalError(notSymbol(theCons.car));
|
|
2007
3084
|
const key = theCons.car;
|
|
2008
3085
|
if (Cons.isNotNil(theCons.nth(3))) {
|
|
2009
|
-
const value = Evaluator.eval(theCons.nth(3), this.environment, this.streamManager, this.depth);
|
|
3086
|
+
const value = Evaluator.eval(theCons.nth(3), this.environment, this.streamManager, this.depth, this.plugins);
|
|
2010
3087
|
this.environment.set(key, value);
|
|
2011
3088
|
}
|
|
2012
3089
|
}
|
|
2013
3090
|
}
|
|
2014
|
-
return Evaluator.eval(bool.nth(2), this.environment, this.streamManager, this.depth);
|
|
3091
|
+
return Evaluator.eval(bool.nth(2), this.environment, this.streamManager, this.depth, this.plugins);
|
|
2015
3092
|
}
|
|
3093
|
+
/**
|
|
3094
|
+
* Evaluates a procedure call by delegating to the Applier after evaluating each argument.
|
|
3095
|
+
* @param form the call form whose car is the procedure and whose cdr is the argument list
|
|
3096
|
+
* @return the result of applying the procedure
|
|
3097
|
+
*/
|
|
2016
3098
|
entrustApplier(form) {
|
|
2017
3099
|
const aCons = form.cdr;
|
|
2018
3100
|
let args = new Cons(Cons.nil, Cons.nil);
|
|
@@ -2025,25 +3107,84 @@ var Evaluator = class Evaluator {
|
|
|
2025
3107
|
}
|
|
2026
3108
|
for (const each of aCons.loop()) {
|
|
2027
3109
|
if (each instanceof Table) break;
|
|
2028
|
-
args.add(Evaluator.eval(each, this.environment, this.streamManager, this.depth));
|
|
3110
|
+
args.add(Evaluator.eval(each, this.environment, this.streamManager, this.depth, this.plugins));
|
|
2029
3111
|
}
|
|
2030
3112
|
if (this.isSpy(aSymbol)) this.setDepth(this.depth - 1);
|
|
2031
3113
|
args = args.cdr;
|
|
2032
|
-
return Applier.apply(procedure, args, this.environment, this.streamManager, this.depth);
|
|
3114
|
+
return Applier.apply(procedure, args, this.environment, this.streamManager, this.depth, this.plugins);
|
|
2033
3115
|
}
|
|
2034
|
-
|
|
2035
|
-
|
|
3116
|
+
/**
|
|
3117
|
+
* Evaluates the given form in the given environment.
|
|
3118
|
+
* @param form the form to evaluate
|
|
3119
|
+
* @param environment the variable binding environment
|
|
3120
|
+
* @param aStreamManager the stream manager for trace and spy output
|
|
3121
|
+
* @param depth the current call depth
|
|
3122
|
+
* @param plugins the plugin chain consulted before falling through to Applier
|
|
3123
|
+
* @return the evaluation result
|
|
3124
|
+
*/
|
|
3125
|
+
static eval(form, environment, aStreamManager = new StreamManager(), depth = 1, plugins = []) {
|
|
3126
|
+
return new Evaluator(environment, aStreamManager, depth, plugins).eval(form);
|
|
2036
3127
|
}
|
|
3128
|
+
/**
|
|
3129
|
+
* Evaluates the given form using this Evaluator's environment.
|
|
3130
|
+
* @param form the form to evaluate
|
|
3131
|
+
* @return the evaluation result
|
|
3132
|
+
*/
|
|
2037
3133
|
eval(form) {
|
|
2038
3134
|
if (Cons.isSymbol(form)) return this.evaluateSymbol(form);
|
|
2039
3135
|
if (Cons.isNil(form) || Cons.isNotList(form)) return form;
|
|
2040
3136
|
const formCons = form;
|
|
2041
3137
|
if (Cons.isSymbol(formCons.car) && Evaluator.buildInFunctions.has(formCons.car)) return this.specialForm(formCons);
|
|
3138
|
+
if (Cons.isSymbol(formCons.car) && this.plugins.length > 0) {
|
|
3139
|
+
const symbol = formCons.car;
|
|
3140
|
+
const plugin = this.plugins.find((p) => p.has(symbol));
|
|
3141
|
+
if (plugin !== void 0) return this.entrustPlugin(plugin, formCons);
|
|
3142
|
+
}
|
|
2042
3143
|
return this.entrustApplier(formCons);
|
|
2043
3144
|
}
|
|
3145
|
+
/**
|
|
3146
|
+
* Evaluates the argument list (the same way `entrustApplier` does), then
|
|
3147
|
+
* delegates the call to the matched plugin with a context that allows
|
|
3148
|
+
* recursive evaluation.
|
|
3149
|
+
* @param plugin the plugin that claimed the call symbol
|
|
3150
|
+
* @param form the call form whose car is the symbol and whose cdr is the argument list
|
|
3151
|
+
* @return the result returned by the plugin
|
|
3152
|
+
*/
|
|
3153
|
+
entrustPlugin(plugin, form) {
|
|
3154
|
+
const aCons = form.cdr;
|
|
3155
|
+
let args = new Cons(Cons.nil, Cons.nil);
|
|
3156
|
+
const symbol = form.car;
|
|
3157
|
+
if (this.isSpy(symbol)) {
|
|
3158
|
+
this.spyPrint(this.streamManager.spyStream(symbol), form.toString());
|
|
3159
|
+
this.setDepth(this.depth + 1);
|
|
3160
|
+
}
|
|
3161
|
+
for (const each of aCons.loop()) {
|
|
3162
|
+
if (each instanceof Table) break;
|
|
3163
|
+
args.add(Evaluator.eval(each, this.environment, this.streamManager, this.depth, this.plugins));
|
|
3164
|
+
}
|
|
3165
|
+
if (this.isSpy(symbol)) this.setDepth(this.depth - 1);
|
|
3166
|
+
args = args.cdr;
|
|
3167
|
+
const ctx = {
|
|
3168
|
+
environment: this.environment,
|
|
3169
|
+
streamManager: this.streamManager,
|
|
3170
|
+
depth: this.depth,
|
|
3171
|
+
eval: (subForm) => Evaluator.eval(subForm, this.environment, this.streamManager, this.depth, this.plugins)
|
|
3172
|
+
};
|
|
3173
|
+
return plugin.apply(symbol, args, ctx);
|
|
3174
|
+
}
|
|
3175
|
+
/**
|
|
3176
|
+
* Implementation of the Lisp `eval` special form.
|
|
3177
|
+
* @param aCons the argument Cons whose car is the form to evaluate twice
|
|
3178
|
+
* @return the result of evaluating the form
|
|
3179
|
+
*/
|
|
2044
3180
|
eval_lisp(aCons) {
|
|
2045
|
-
return Evaluator.eval(Evaluator.eval(aCons.car, this.environment, this.streamManager, this.depth), this.environment, this.streamManager, this.depth);
|
|
3181
|
+
return Evaluator.eval(Evaluator.eval(aCons.car, this.environment, this.streamManager, this.depth, this.plugins), this.environment, this.streamManager, this.depth, this.plugins);
|
|
2046
3182
|
}
|
|
3183
|
+
/**
|
|
3184
|
+
* Resolves the value bound to the given symbol in the current environment.
|
|
3185
|
+
* @param aSymbol the symbol to resolve
|
|
3186
|
+
* @return the value bound to the symbol
|
|
3187
|
+
*/
|
|
2047
3188
|
evaluateSymbol(aSymbol) {
|
|
2048
3189
|
if (!this.environment.has(aSymbol)) throw new EvalError(noBinding(aSymbol));
|
|
2049
3190
|
if (this.isSpy(aSymbol)) {
|
|
@@ -2058,10 +3199,17 @@ var Evaluator = class Evaluator {
|
|
|
2058
3199
|
}
|
|
2059
3200
|
return answer;
|
|
2060
3201
|
}
|
|
3202
|
+
/**
|
|
3203
|
+
* Implementation of the Lisp `exit` special form; terminates the REPL by throwing an ExitError.
|
|
3204
|
+
*/
|
|
2061
3205
|
exit() {
|
|
2062
3206
|
console.log("Bye!");
|
|
2063
3207
|
throw new ExitError();
|
|
2064
3208
|
}
|
|
3209
|
+
/**
|
|
3210
|
+
* Implementation of the Lisp `gc` special form; triggers garbage collection and returns memory usage.
|
|
3211
|
+
* @return an association list of memory usage statistics
|
|
3212
|
+
*/
|
|
2065
3213
|
gc() {
|
|
2066
3214
|
triggerGc();
|
|
2067
3215
|
const usage = process.memoryUsage();
|
|
@@ -2075,110 +3223,198 @@ var Evaluator = class Evaluator {
|
|
|
2075
3223
|
for (const entry of entries) result = new Cons(entry, result);
|
|
2076
3224
|
return result;
|
|
2077
3225
|
}
|
|
3226
|
+
/**
|
|
3227
|
+
* Implementation of the Lisp `if` special form.
|
|
3228
|
+
* @param aCons the argument Cons containing the test, then-form, and else-form
|
|
3229
|
+
* @return the result of evaluating the selected branch
|
|
3230
|
+
*/
|
|
2078
3231
|
if_(aCons) {
|
|
2079
|
-
const bool = Evaluator.eval(aCons.car, this.environment, this.streamManager, this.depth);
|
|
3232
|
+
const bool = Evaluator.eval(aCons.car, this.environment, this.streamManager, this.depth, this.plugins);
|
|
2080
3233
|
const anObject = Cons.isNil(bool) ? aCons.nth(3) : aCons.nth(2);
|
|
2081
|
-
return Evaluator.eval(anObject, this.environment, this.streamManager, this.depth);
|
|
3234
|
+
return Evaluator.eval(anObject, this.environment, this.streamManager, this.depth, this.plugins);
|
|
2082
3235
|
}
|
|
3236
|
+
/**
|
|
3237
|
+
* Returns the indentation string used for trace and spy output at the current depth.
|
|
3238
|
+
* @return the indentation string
|
|
3239
|
+
*/
|
|
2083
3240
|
indent() {
|
|
2084
3241
|
let index = 0;
|
|
2085
3242
|
let aString = "";
|
|
2086
3243
|
while (index++ < this.depth) aString += "| ";
|
|
2087
3244
|
return aString;
|
|
2088
3245
|
}
|
|
3246
|
+
/**
|
|
3247
|
+
* Returns whether the given symbol is currently being spied on.
|
|
3248
|
+
* @param aSymbol the symbol to check
|
|
3249
|
+
* @return a boolean
|
|
3250
|
+
*/
|
|
2089
3251
|
isSpy(aSymbol) {
|
|
2090
3252
|
if (aSymbol == null) return false;
|
|
2091
3253
|
return this.streamManager.isSpy(aSymbol);
|
|
2092
3254
|
}
|
|
3255
|
+
/**
|
|
3256
|
+
* Implementation of the Lisp `lambda` special form; captures the current environment as a closure.
|
|
3257
|
+
* @param args the argument Cons containing the parameter list and body
|
|
3258
|
+
* @return a lambda form with the captured environment appended
|
|
3259
|
+
*/
|
|
2093
3260
|
lambda(args) {
|
|
2094
3261
|
const aCons = Cons.cloneValue(args);
|
|
2095
3262
|
aCons.cdr.setCdr(new Cons(this.environment, Cons.nil));
|
|
2096
3263
|
return new Cons(InterpretedSymbol.of("lambda"), aCons);
|
|
2097
3264
|
}
|
|
3265
|
+
/**
|
|
3266
|
+
* Implementation of the Lisp `let` special form (parallel binding).
|
|
3267
|
+
* @param aCons the argument Cons containing bindings and body
|
|
3268
|
+
* @return the value of the last body form
|
|
3269
|
+
*/
|
|
2098
3270
|
let(aCons) {
|
|
2099
3271
|
const aTable = new Table(this.environment);
|
|
2100
3272
|
const parameters = aCons.car;
|
|
2101
3273
|
const forms = aCons.cdr;
|
|
2102
3274
|
let anObject = Cons.nil;
|
|
2103
3275
|
this.bindingParallel(parameters, aTable);
|
|
2104
|
-
for (const each of forms.loop()) anObject = Evaluator.eval(each, aTable, this.streamManager, this.depth);
|
|
3276
|
+
for (const each of forms.loop()) anObject = Evaluator.eval(each, aTable, this.streamManager, this.depth, this.plugins);
|
|
2105
3277
|
return anObject;
|
|
2106
3278
|
}
|
|
3279
|
+
/**
|
|
3280
|
+
* Implementation of the Lisp `let*` special form (sequential binding).
|
|
3281
|
+
* @param aCons the argument Cons containing bindings and body
|
|
3282
|
+
* @return the value of the last body form
|
|
3283
|
+
*/
|
|
2107
3284
|
letStar(aCons) {
|
|
2108
3285
|
const aTable = new Table(this.environment);
|
|
2109
3286
|
const parameters = aCons.car;
|
|
2110
3287
|
const forms = aCons.cdr;
|
|
2111
3288
|
let anObject = Cons.nil;
|
|
2112
3289
|
this.binding(parameters, aTable);
|
|
2113
|
-
for (const each of forms.loop()) anObject = Evaluator.eval(each, aTable, this.streamManager, this.depth);
|
|
3290
|
+
for (const each of forms.loop()) anObject = Evaluator.eval(each, aTable, this.streamManager, this.depth, this.plugins);
|
|
2114
3291
|
return anObject;
|
|
2115
3292
|
}
|
|
3293
|
+
/**
|
|
3294
|
+
* Implementation of the Lisp `not` special form.
|
|
3295
|
+
* @param aCons the argument Cons whose car is the expression to negate
|
|
3296
|
+
* @return t if the expression evaluates to nil, otherwise nil
|
|
3297
|
+
*/
|
|
2116
3298
|
not(aCons) {
|
|
2117
|
-
if (Cons.isNil(Evaluator.eval(aCons.car, this.environment, this.streamManager, this.depth))) return InterpretedSymbol.of("t");
|
|
3299
|
+
if (Cons.isNil(Evaluator.eval(aCons.car, this.environment, this.streamManager, this.depth, this.plugins))) return InterpretedSymbol.of("t");
|
|
2118
3300
|
return Cons.nil;
|
|
2119
3301
|
}
|
|
3302
|
+
/**
|
|
3303
|
+
* Implementation of the Lisp `notrace` special form; disables tracing.
|
|
3304
|
+
* @return the symbol t
|
|
3305
|
+
*/
|
|
2120
3306
|
notrace() {
|
|
2121
3307
|
this.streamManager.noTrace();
|
|
2122
3308
|
return InterpretedSymbol.of("t");
|
|
2123
3309
|
}
|
|
3310
|
+
/**
|
|
3311
|
+
* Implementation of the Lisp `or` special form.
|
|
3312
|
+
* @param aCons the argument Cons containing the expressions to evaluate
|
|
3313
|
+
* @return t if any expression evaluates to non-nil, otherwise nil
|
|
3314
|
+
*/
|
|
2124
3315
|
or(aCons) {
|
|
2125
3316
|
for (const each of aCons.loop()) {
|
|
2126
|
-
const anObject = Evaluator.eval(each, this.environment, this.streamManager, this.depth);
|
|
3317
|
+
const anObject = Evaluator.eval(each, this.environment, this.streamManager, this.depth, this.plugins);
|
|
2127
3318
|
if (Cons.isNotNil(anObject)) return InterpretedSymbol.of("t");
|
|
2128
3319
|
}
|
|
2129
3320
|
return Cons.nil;
|
|
2130
3321
|
}
|
|
3322
|
+
/**
|
|
3323
|
+
* Implementation of the Lisp `pop` special form.
|
|
3324
|
+
* @param aCons the argument Cons whose car is the symbol bound to a list
|
|
3325
|
+
* @return the popped element, or nil if the binding is not a Cons
|
|
3326
|
+
*/
|
|
2131
3327
|
pop_(aCons) {
|
|
2132
3328
|
if (Cons.isNotSymbol(aCons.car)) throw new EvalError(argumentNotSymbol(1));
|
|
2133
3329
|
const aSymbol = aCons.car;
|
|
2134
|
-
const anObject = Evaluator.eval(aSymbol, this.environment, this.streamManager, this.depth);
|
|
3330
|
+
const anObject = Evaluator.eval(aSymbol, this.environment, this.streamManager, this.depth, this.plugins);
|
|
2135
3331
|
if (Cons.isNotCons(anObject)) return Cons.nil;
|
|
2136
3332
|
const consObject = anObject;
|
|
2137
3333
|
this.environment.setIfExist(aSymbol, consObject.cdr);
|
|
2138
3334
|
return consObject.car;
|
|
2139
3335
|
}
|
|
3336
|
+
/**
|
|
3337
|
+
* Implementation of the Lisp `progn` special form.
|
|
3338
|
+
* @param aCons the argument Cons containing the body expressions
|
|
3339
|
+
* @return the value of the last body form, or nil if there are none
|
|
3340
|
+
*/
|
|
2140
3341
|
progn(aCons) {
|
|
2141
3342
|
let anObject = Cons.nil;
|
|
2142
|
-
for (const each of aCons.loop()) anObject = Evaluator.eval(each, this.environment, this.streamManager, this.depth);
|
|
3343
|
+
for (const each of aCons.loop()) anObject = Evaluator.eval(each, this.environment, this.streamManager, this.depth, this.plugins);
|
|
2143
3344
|
return anObject;
|
|
2144
3345
|
}
|
|
3346
|
+
/**
|
|
3347
|
+
* Implementation of the Lisp `princ` special form; writes the evaluated argument without a trailing newline.
|
|
3348
|
+
* @param aCons the argument Cons whose car is the expression to print
|
|
3349
|
+
* @return the printed value
|
|
3350
|
+
*/
|
|
2145
3351
|
princ(aCons) {
|
|
2146
|
-
const anObject = Evaluator.eval(aCons.car, this.environment, this.streamManager, this.depth);
|
|
3352
|
+
const anObject = Evaluator.eval(aCons.car, this.environment, this.streamManager, this.depth, this.plugins);
|
|
2147
3353
|
process.stdout.write(String(anObject));
|
|
2148
3354
|
return anObject;
|
|
2149
3355
|
}
|
|
3356
|
+
/**
|
|
3357
|
+
* Implementation of the Lisp `print` special form; writes the evaluated argument followed by a newline.
|
|
3358
|
+
* @param aCons the argument Cons whose car is the expression to print
|
|
3359
|
+
* @return the printed value
|
|
3360
|
+
*/
|
|
2150
3361
|
print(aCons) {
|
|
2151
|
-
const anObject = Evaluator.eval(aCons.car, this.environment, this.streamManager, this.depth);
|
|
3362
|
+
const anObject = Evaluator.eval(aCons.car, this.environment, this.streamManager, this.depth, this.plugins);
|
|
2152
3363
|
process.stdout.write(String(anObject) + "\n");
|
|
2153
3364
|
return anObject;
|
|
2154
3365
|
}
|
|
3366
|
+
/**
|
|
3367
|
+
* Implementation of the Lisp `push` special form.
|
|
3368
|
+
* @param aCons the argument Cons containing the value to push and the target symbol
|
|
3369
|
+
* @return the new Cons stored in the symbol
|
|
3370
|
+
*/
|
|
2155
3371
|
push_(aCons) {
|
|
2156
|
-
let anObject = Evaluator.eval(aCons.car, this.environment, this.streamManager, this.depth);
|
|
3372
|
+
let anObject = Evaluator.eval(aCons.car, this.environment, this.streamManager, this.depth, this.plugins);
|
|
2157
3373
|
if (Cons.isNotSymbol(aCons.nth(2))) throw new EvalError(argumentNotSymbol(2));
|
|
2158
3374
|
const aSymbol = aCons.nth(2);
|
|
2159
|
-
anObject = new Cons(anObject, Evaluator.eval(aSymbol, this.environment, this.streamManager, this.depth));
|
|
3375
|
+
anObject = new Cons(anObject, Evaluator.eval(aSymbol, this.environment, this.streamManager, this.depth, this.plugins));
|
|
2160
3376
|
this.environment.setIfExist(aSymbol, anObject);
|
|
2161
3377
|
return anObject;
|
|
2162
3378
|
}
|
|
3379
|
+
/**
|
|
3380
|
+
* Implementation of the Lisp `quote` special form.
|
|
3381
|
+
* @param aCons the argument Cons whose car is the form to return unevaluated
|
|
3382
|
+
* @return the quoted form
|
|
3383
|
+
*/
|
|
2163
3384
|
quote(aCons) {
|
|
2164
3385
|
return aCons.car;
|
|
2165
3386
|
}
|
|
3387
|
+
/**
|
|
3388
|
+
* Implementation of the Lisp `rplaca` special form; destructively replaces the car of a Cons.
|
|
3389
|
+
* @param args the argument Cons containing the target Cons expression and the new car value
|
|
3390
|
+
* @return the modified Cons
|
|
3391
|
+
*/
|
|
2166
3392
|
rplaca(args) {
|
|
2167
|
-
let anObject = Evaluator.eval(args.car, this.environment, this.streamManager, this.depth);
|
|
3393
|
+
let anObject = Evaluator.eval(args.car, this.environment, this.streamManager, this.depth, this.plugins);
|
|
2168
3394
|
if (Cons.isNotCons(anObject)) throw new EvalError(cannotApply("set-car!", anObject));
|
|
2169
3395
|
const aCons = anObject;
|
|
2170
|
-
anObject = Evaluator.eval(args.nth(2), this.environment, this.streamManager, this.depth);
|
|
3396
|
+
anObject = Evaluator.eval(args.nth(2), this.environment, this.streamManager, this.depth, this.plugins);
|
|
2171
3397
|
aCons.setCar(anObject);
|
|
2172
|
-
return Evaluator.eval(args.car, this.environment, this.streamManager, this.depth);
|
|
3398
|
+
return Evaluator.eval(args.car, this.environment, this.streamManager, this.depth, this.plugins);
|
|
2173
3399
|
}
|
|
3400
|
+
/**
|
|
3401
|
+
* Implementation of the Lisp `rplacd` special form; destructively replaces the cdr of a Cons.
|
|
3402
|
+
* @param args the argument Cons containing the target Cons expression and the new cdr value
|
|
3403
|
+
* @return the modified Cons
|
|
3404
|
+
*/
|
|
2174
3405
|
rplacd(args) {
|
|
2175
|
-
let anObject = Evaluator.eval(args.car, this.environment, this.streamManager, this.depth);
|
|
3406
|
+
let anObject = Evaluator.eval(args.car, this.environment, this.streamManager, this.depth, this.plugins);
|
|
2176
3407
|
if (Cons.isNotCons(anObject)) throw new EvalError(cannotApply("set-cdr!", anObject));
|
|
2177
3408
|
const aCons = anObject;
|
|
2178
|
-
anObject = Evaluator.eval(args.nth(2), this.environment, this.streamManager, this.depth);
|
|
3409
|
+
anObject = Evaluator.eval(args.nth(2), this.environment, this.streamManager, this.depth, this.plugins);
|
|
2179
3410
|
aCons.setCdr(anObject);
|
|
2180
|
-
return Evaluator.eval(args.car, this.environment, this.streamManager, this.depth);
|
|
3411
|
+
return Evaluator.eval(args.car, this.environment, this.streamManager, this.depth, this.plugins);
|
|
2181
3412
|
}
|
|
3413
|
+
/**
|
|
3414
|
+
* Implementation of the Lisp `setq` special form; assigns values in the local environment.
|
|
3415
|
+
* @param args the argument Cons containing alternating (symbol value) pairs
|
|
3416
|
+
* @return the last assigned value
|
|
3417
|
+
*/
|
|
2182
3418
|
setq(args) {
|
|
2183
3419
|
let anObject = Cons.nil;
|
|
2184
3420
|
const anIterator = args.loop();
|
|
@@ -2186,26 +3422,39 @@ var Evaluator = class Evaluator {
|
|
|
2186
3422
|
if (!Cons.isSymbol(args.nth(1))) throw new EvalError(notSymbol(args.car));
|
|
2187
3423
|
const key = anIterator.next();
|
|
2188
3424
|
if (!anIterator.hasNext()) throw new EvalError(SIZES_DO_NOT_MATCH);
|
|
2189
|
-
anObject = Evaluator.eval(anIterator.next(), this.environment, this.streamManager, this.depth);
|
|
3425
|
+
anObject = Evaluator.eval(anIterator.next(), this.environment, this.streamManager, this.depth, this.plugins);
|
|
2190
3426
|
this.environment.set(key, anObject);
|
|
2191
3427
|
}
|
|
2192
3428
|
return anObject;
|
|
2193
3429
|
}
|
|
3430
|
+
/**
|
|
3431
|
+
* Implementation of the Lisp `set-allq` special form; assigns values in the binding's owning scope.
|
|
3432
|
+
* @param args the argument Cons containing alternating (symbol value) pairs
|
|
3433
|
+
* @return the last assigned value
|
|
3434
|
+
*/
|
|
2194
3435
|
set_allq(args) {
|
|
2195
3436
|
let anObject = Cons.nil;
|
|
2196
3437
|
const anIterator = args.loop();
|
|
2197
3438
|
while (anIterator.hasNext()) {
|
|
2198
3439
|
if (!Cons.isSymbol(args.nth(1))) throw new EvalError(notSymbol(args.car));
|
|
2199
3440
|
const key = anIterator.next();
|
|
2200
|
-
anObject = Evaluator.eval(anIterator.next(), this.environment, this.streamManager, this.depth);
|
|
3441
|
+
anObject = Evaluator.eval(anIterator.next(), this.environment, this.streamManager, this.depth, this.plugins);
|
|
2201
3442
|
this.environment.setIfExist(key, anObject);
|
|
2202
3443
|
}
|
|
2203
3444
|
return anObject;
|
|
2204
3445
|
}
|
|
3446
|
+
/**
|
|
3447
|
+
* Sets the current call depth used for trace and spy indentation.
|
|
3448
|
+
* @param aNumber the new depth
|
|
3449
|
+
*/
|
|
2205
3450
|
setDepth(aNumber) {
|
|
2206
3451
|
this.depth = aNumber;
|
|
2207
3452
|
return null;
|
|
2208
3453
|
}
|
|
3454
|
+
/**
|
|
3455
|
+
* Builds and returns the Lisp-name to method-name dispatch map for special forms.
|
|
3456
|
+
* @return the dispatch map
|
|
3457
|
+
*/
|
|
2209
3458
|
static setup() {
|
|
2210
3459
|
try {
|
|
2211
3460
|
return new Map([
|
|
@@ -2247,6 +3496,11 @@ var Evaluator = class Evaluator {
|
|
|
2247
3496
|
throw new Error("NullPointerException (Evaluator, initialize)");
|
|
2248
3497
|
}
|
|
2249
3498
|
}
|
|
3499
|
+
/**
|
|
3500
|
+
* Dispatches a special-form call to the corresponding method via the build-in dispatch map.
|
|
3501
|
+
* @param form the form whose car is the special-form symbol
|
|
3502
|
+
* @return the result of the special-form method
|
|
3503
|
+
*/
|
|
2250
3504
|
specialForm(form) {
|
|
2251
3505
|
const aSymbol = form.car;
|
|
2252
3506
|
if (this.isSpy(aSymbol)) {
|
|
@@ -2265,37 +3519,65 @@ var Evaluator = class Evaluator {
|
|
|
2265
3519
|
}
|
|
2266
3520
|
return answer;
|
|
2267
3521
|
}
|
|
3522
|
+
/**
|
|
3523
|
+
* Writes a trace/spy line to the given stream (or stdout) with the current indentation.
|
|
3524
|
+
* @param aStream the destination stream, or null/string to fall back to stdout
|
|
3525
|
+
* @param line the line to write
|
|
3526
|
+
*/
|
|
2268
3527
|
spyPrint(aStream, line) {
|
|
2269
3528
|
(aStream != null && typeof aStream === "object" && "write" in aStream ? aStream : process.stdout).write(this.indent() + line + "\n");
|
|
2270
3529
|
return null;
|
|
2271
3530
|
}
|
|
3531
|
+
/**
|
|
3532
|
+
* Implementation of the Lisp `terpri` special form; writes a newline to stdout.
|
|
3533
|
+
* @return the symbol t
|
|
3534
|
+
*/
|
|
2272
3535
|
terpri() {
|
|
2273
3536
|
process.stdout.write("\n");
|
|
2274
3537
|
return InterpretedSymbol.of("t");
|
|
2275
3538
|
}
|
|
3539
|
+
/**
|
|
3540
|
+
* Implementation of the Lisp `time` special form; measures evaluation time in milliseconds.
|
|
3541
|
+
* @param aCons the argument Cons whose car is the form to time
|
|
3542
|
+
* @return the elapsed time in milliseconds
|
|
3543
|
+
*/
|
|
2276
3544
|
time(aCons) {
|
|
2277
3545
|
const start = process.hrtime();
|
|
2278
|
-
Evaluator.eval(aCons.car, this.environment, this.streamManager, this.depth);
|
|
3546
|
+
Evaluator.eval(aCons.car, this.environment, this.streamManager, this.depth, this.plugins);
|
|
2279
3547
|
return process.hrtime(start)[1] / 1e6;
|
|
2280
3548
|
}
|
|
3549
|
+
/**
|
|
3550
|
+
* Implementation of the Lisp `trace` special form; enables tracing.
|
|
3551
|
+
* @return the symbol t
|
|
3552
|
+
*/
|
|
2281
3553
|
trace() {
|
|
2282
3554
|
this.streamManager.trace();
|
|
2283
3555
|
return InterpretedSymbol.of("t");
|
|
2284
3556
|
}
|
|
3557
|
+
/**
|
|
3558
|
+
* Implementation of the Lisp `unless` special form.
|
|
3559
|
+
* @param aCons the argument Cons containing the test and body
|
|
3560
|
+
* @return the value of the last body form if the test is nil, otherwise nil
|
|
3561
|
+
*/
|
|
2285
3562
|
unless(aCons) {
|
|
2286
3563
|
let anObject = Cons.nil;
|
|
2287
3564
|
const theCons = aCons.cdr;
|
|
2288
|
-
const flag = Evaluator.eval(aCons.car, this.environment, this.streamManager, this.depth);
|
|
3565
|
+
const flag = Evaluator.eval(aCons.car, this.environment, this.streamManager, this.depth, this.plugins);
|
|
2289
3566
|
if (Cons.isNotNil(flag)) return Cons.nil;
|
|
2290
|
-
for (const each of theCons.loop()) anObject = Evaluator.eval(each, this.environment, this.streamManager, this.depth);
|
|
3567
|
+
for (const each of theCons.loop()) anObject = Evaluator.eval(each, this.environment, this.streamManager, this.depth, this.plugins);
|
|
2291
3568
|
return anObject;
|
|
2292
3569
|
}
|
|
3570
|
+
/**
|
|
3571
|
+
* Implementation of the Lisp `when` special form.
|
|
3572
|
+
* @param aCons the argument Cons containing the test and body
|
|
3573
|
+
* @return the value of the last body form if the test is non-nil, otherwise nil
|
|
3574
|
+
*/
|
|
2293
3575
|
when(aCons) {
|
|
2294
3576
|
let anObject = Cons.nil;
|
|
2295
3577
|
const theCons = aCons.cdr;
|
|
2296
|
-
const flag = Evaluator.eval(aCons.car, this.environment, this.streamManager, this.depth);
|
|
3578
|
+
const flag = Evaluator.eval(aCons.car, this.environment, this.streamManager, this.depth, this.plugins);
|
|
2297
3579
|
if (Cons.isNil(flag)) return Cons.nil;
|
|
2298
|
-
for (const each of theCons.loop()) anObject = Evaluator.eval(each, this.environment, this.streamManager, this.depth);
|
|
3580
|
+
for (const each of theCons.loop()) anObject = Evaluator.eval(each, this.environment, this.streamManager, this.depth, this.plugins);
|
|
2299
3581
|
return anObject;
|
|
2300
3582
|
}
|
|
2301
3583
|
};
|
|
@@ -2307,23 +3589,54 @@ var Evaluator = class Evaluator {
|
|
|
2307
3589
|
* @author Keisuke Ikeda
|
|
2308
3590
|
* @this {LispInterpreter}
|
|
2309
3591
|
*/
|
|
2310
|
-
var LispInterpreter = class {
|
|
3592
|
+
var LispInterpreter = class extends Object {
|
|
3593
|
+
/**
|
|
3594
|
+
* The root (top-level) environment table, pre-populated with built-in symbols.
|
|
3595
|
+
*/
|
|
2311
3596
|
root;
|
|
3597
|
+
/**
|
|
3598
|
+
* The stream manager that owns the interpreter's output / spy streams.
|
|
3599
|
+
*/
|
|
2312
3600
|
streamManager;
|
|
3601
|
+
/**
|
|
3602
|
+
* Registered plugins consulted by the evaluator on every call. See `use`.
|
|
3603
|
+
*/
|
|
3604
|
+
plugins;
|
|
3605
|
+
/**
|
|
3606
|
+
* Constructor.
|
|
3607
|
+
* @constructor
|
|
3608
|
+
*/
|
|
2313
3609
|
constructor() {
|
|
3610
|
+
super();
|
|
2314
3611
|
this.root = this.initializeTable();
|
|
2315
3612
|
this.streamManager = new StreamManager();
|
|
3613
|
+
this.plugins = [];
|
|
3614
|
+
}
|
|
3615
|
+
/**
|
|
3616
|
+
* Registers a plugin. Subsequent `eval` calls will consult the plugin chain
|
|
3617
|
+
* (in registration order, first match wins) when no special form matches a
|
|
3618
|
+
* symbol, before falling through to the Applier built-ins.
|
|
3619
|
+
* @param plugin the plugin to register
|
|
3620
|
+
* @return this interpreter, for chaining
|
|
3621
|
+
*/
|
|
3622
|
+
use(plugin) {
|
|
3623
|
+
this.plugins.push(plugin);
|
|
3624
|
+
return this;
|
|
2316
3625
|
}
|
|
2317
3626
|
/**
|
|
2318
3627
|
* Evaluates the given expression and returns the result. Throws `ParseError`,
|
|
2319
3628
|
* `EvalError`, or `ExitError` on failure; library users are expected to catch
|
|
2320
3629
|
* these (see the `KeiLispError` base class for the parse/eval family).
|
|
3630
|
+
* @param aCons the expression to evaluate
|
|
3631
|
+
* @return the evaluation result
|
|
2321
3632
|
*/
|
|
2322
3633
|
eval(aCons) {
|
|
2323
|
-
return Evaluator.eval(aCons, this.root, this.streamManager);
|
|
3634
|
+
return Evaluator.eval(aCons, this.root, this.streamManager, 1, this.plugins);
|
|
2324
3635
|
}
|
|
2325
3636
|
/**
|
|
2326
3637
|
* Parses the source string, evaluates every expression it contains, and returns the results as an array.
|
|
3638
|
+
* @param source the Lisp source string
|
|
3639
|
+
* @return the array of evaluation results, one per top-level expression
|
|
2327
3640
|
*/
|
|
2328
3641
|
evalAll(source) {
|
|
2329
3642
|
const ast = this.parse(source);
|
|
@@ -2333,6 +3646,8 @@ var LispInterpreter = class {
|
|
|
2333
3646
|
}
|
|
2334
3647
|
/**
|
|
2335
3648
|
* Parses and evaluates the source string and returns the value of the last expression.
|
|
3649
|
+
* @param source the Lisp source string
|
|
3650
|
+
* @return the value of the last expression, or `Cons.nil` for empty input
|
|
2336
3651
|
*/
|
|
2337
3652
|
evalString(source) {
|
|
2338
3653
|
const results = this.evalAll(source);
|
|
@@ -2343,12 +3658,16 @@ var LispInterpreter = class {
|
|
|
2343
3658
|
* it. The result is always a `Cons` (possibly `Cons.nil` for empty input)
|
|
2344
3659
|
* because the source is wrapped in an outer list before parsing. Throws
|
|
2345
3660
|
* `ParseError` if the source cannot be parsed.
|
|
3661
|
+
* @param aString the Lisp source string
|
|
3662
|
+
* @return a Cons containing the parsed top-level expressions
|
|
2346
3663
|
*/
|
|
2347
3664
|
parse(aString) {
|
|
2348
3665
|
return Cons.parse("(" + aString + "\n);");
|
|
2349
3666
|
}
|
|
2350
3667
|
/**
|
|
2351
3668
|
* Sets the given environment as the root of the environment chain.
|
|
3669
|
+
* @param environment the environment table to install as root
|
|
3670
|
+
* @return null
|
|
2352
3671
|
*/
|
|
2353
3672
|
setRoot(environment) {
|
|
2354
3673
|
if (environment instanceof Table) {
|
|
@@ -2358,13 +3677,14 @@ var LispInterpreter = class {
|
|
|
2358
3677
|
return null;
|
|
2359
3678
|
}
|
|
2360
3679
|
/**
|
|
2361
|
-
*
|
|
3680
|
+
* Builds the root environment table by pre-registering every built-in symbol and the small set of bootstrap lambdas (append / butlast / nthcdr / reverse).
|
|
3681
|
+
* @return the freshly initialized root environment
|
|
2362
3682
|
*/
|
|
2363
3683
|
initializeTable() {
|
|
2364
3684
|
const aList = [];
|
|
2365
3685
|
const aTable = new Table();
|
|
2366
3686
|
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", "
|
|
3687
|
+
aList.push("abs", "add", "and", "apply", "assoc", "atom", "bind", "car", "cdr", "characterp", "cond", "ceiling", "concatenate", "cons", "consp", "copy", "cos", "count", "floatp", "floor", "defun", "divide", "do", "do*", "dolist", "doublep", "elt", "eq", "equal", "eval", "evenp", "every", "exit", "exp", "expt", "find", "format", "gc", "gensym", "if", "integerp", "lambda", "let", "let*", "last", "length", "list", "listp", "mapcan", "mapcar", "max", "member", "memq", "min", "minusp", "mod", "multiply", "napier", "neq", "nequal", "not", "notrace", "nth", "null", "numberp", "oddp", "or", "pi", "plusp", "pop", "princ", "print", "progn", "push", "quote", "random", "reduce", "round", "rplaca", "rplacd", "setq", "set-allq", "sin", "some", "sort", "sqrt", "string-downcase", "string-trim", "string-upcase", "stringp", "subseq", "substring", "subtract", "symbolp", "tan", "terpri", "time", "trace", "truncate", "unless", "when", "zerop", "1+", "1-", "+", "-", "*", "/", "//", "=", "==", "~=", "~~", "<", "<=", ">", ">=");
|
|
2368
3688
|
for (const each of aList) {
|
|
2369
3689
|
const aSymbol = InterpretedSymbol.of(each);
|
|
2370
3690
|
aTable.set(aSymbol, aSymbol);
|
|
@@ -2379,10 +3699,6 @@ var LispInterpreter = class {
|
|
|
2379
3699
|
aCons = Cons.parse(aString);
|
|
2380
3700
|
aCons.last().setCdr(new Cons(aTable, Cons.nil));
|
|
2381
3701
|
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
3702
|
aString = "(lambda (n l) (cond ((> n (length l)) nil) ((= 0 n) l) (t (nthcdr (- n 1) (cdr l)))))";
|
|
2387
3703
|
aCons = Cons.parse(aString);
|
|
2388
3704
|
aCons.last().setCdr(new Cons(aTable, Cons.nil));
|
|
@@ -2404,10 +3720,22 @@ const require$1 = (0, node_module.createRequire)(require("url").pathToFileURL(__
|
|
|
2404
3720
|
* @author Keisuke Ikeda
|
|
2405
3721
|
* @this {Repl}
|
|
2406
3722
|
*/
|
|
2407
|
-
var Repl = class {
|
|
3723
|
+
var Repl = class extends Object {
|
|
3724
|
+
/**
|
|
3725
|
+
* The underlying interpreter used to parse and evaluate user input.
|
|
3726
|
+
*/
|
|
2408
3727
|
interpreter;
|
|
3728
|
+
/**
|
|
3729
|
+
* The Node.js readline interface that supplies prompt I/O.
|
|
3730
|
+
*/
|
|
2409
3731
|
rl;
|
|
3732
|
+
/**
|
|
3733
|
+
* Constructor.
|
|
3734
|
+
* @constructor
|
|
3735
|
+
* @param interpreter the interpreter to evaluate input against (defaults to a fresh one)
|
|
3736
|
+
*/
|
|
2410
3737
|
constructor(interpreter = new LispInterpreter()) {
|
|
3738
|
+
super();
|
|
2411
3739
|
this.interpreter = interpreter;
|
|
2412
3740
|
const readline = require$1("node:readline");
|
|
2413
3741
|
this.rl = readline.createInterface({
|
|
@@ -2418,6 +3746,7 @@ var Repl = class {
|
|
|
2418
3746
|
}
|
|
2419
3747
|
/**
|
|
2420
3748
|
* Starts the REPL loop.
|
|
3749
|
+
* @return void
|
|
2421
3750
|
*/
|
|
2422
3751
|
run() {
|
|
2423
3752
|
let aString = "";
|