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