-
Notifications
You must be signed in to change notification settings - Fork 9
/
lisp.cs
1414 lines (1253 loc) · 52 KB
/
lisp.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
// H29.3/1 - H30.6/27 by SUZUKI Hisao
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
// lisp.exe: csc /doc:lisp.xml /o lisp.cs
// doc: mdoc update -i lisp.xml -o xml lisp.exe; mdoc export-html -o html xml
[assembly: AssemblyProduct("Nukata Lisp Light")]
[assembly: AssemblyVersion("1.2.2.*")]
[assembly: AssemblyTitle("A Lisp interpreter in C# 7")]
[assembly: AssemblyCopyright("© 2017 Oki Software Co., Ltd.; " +
"© 2018 SUZUKI Hisao [MIT License]")]
/// <summary>
/// A Lisp interpreter written in C# 7
/// </summary><remarks>
/// This is ported from Nuka Lisp in Dart
/// (https://github.com/nukata/lisp-in-dart) except for bignum.
/// Its sole numeric type is <c>double</c> in C#.
/// It is named after ex-Nukata Town in Japan.
/// </remarks>
public static class NukataLisp {
/// <summary>Cons cell</summary>
public sealed class Cell {
/// <summary>Head part of the cons cell</summary>
public object Car;
/// <summary>Tail part of the cons cell</summary>
public object Cdr;
/// <summary>Construct a cons cell with its head and tail.</summary>
public Cell(object car, object cdr) {
Car = car;
Cdr = cdr;
}
/// <summary>Make a simple string representation.</summary>
/// <remarks>Do not invoke this for any circular list.</remarks>
public override string ToString() =>
$"({Car ?? "null"} . {Cdr ?? "null"})";
/// <summary>Length as a list</summary>
public int Length => FoldL(0, this, (i, e) => i + 1);
}
// MapCar((a b c), fn) => (fn(a) fn(b) fn(c))
static Cell MapCar(Cell j, Func<object, object> fn) {
if (j == null)
return null;
object a = fn(j.Car);
object d = j.Cdr;
if (d is Cell dc)
d = MapCar(dc, fn);
if (j.Car == a && j.Cdr == d)
return j;
return new Cell(a, d);
}
// FoldL(x, (a b c), fn) => fn(fn(fn(x, a), b), c)
static T FoldL<T> (T x, Cell j, Func<T, object, T> fn) {
while (j != null) {
x = fn(x, j.Car);
j = (Cell) j.Cdr;
}
return x;
}
/// <summary>Lisp symbol</summary>
public class Sym {
/// <summary>The symbol's name</summary>
public string Name { get; }
/// <summary>Construct a symbol that is not interned.</summary>
public Sym(string name) {
Name = name;
}
/// <summary>Return the symbol's name</summary>
public override string ToString() => Name;
/// <summary>Return the hashcode of the symbol's name</summary>
public override int GetHashCode() => Name.GetHashCode();
/// <summary>Table of interned symbols</summary>
protected static readonly Dictionary<string, Sym> Table =
new Dictionary<string, Sym>();
/// <summary>Return an interned symbol for the name.</summary>
/// <remarks>If the name is not interned yet, such a symbol
/// will be constructed with <paramref name="make"/>.</remarks>
protected static Sym New(string name, Func<string, Sym> make) {
lock (Table) {
if (! Table.TryGetValue(name, out Sym result)) {
result = make(name);
Table[name] = result;
}
return result;
}
}
/// <summary>Construct an interned symbol.</summary>
public static Sym New(string name) => New(name, s => new Sym(s));
/// <summary>Is it interned?</summary>
public bool IsInterned {
get {
lock (Table) {
return Table.TryGetValue(Name, out Sym s) &&
Object.ReferenceEquals(this, s);
}
}
}
}
// Expression keyword
sealed class Keyword: Sym {
Keyword(string name): base(name) {}
internal static new Sym New(string name)
=> New(name, s => new Keyword(s));
}
static readonly Sym CondSym = Keyword.New("cond");
static readonly Sym LambdaSym = Keyword.New("lambda");
static readonly Sym MacroSym = Keyword.New("macro");
static readonly Sym PrognSym = Keyword.New("progn");
static readonly Sym QuasiquoteSym = Keyword.New("quasiquote");
static readonly Sym QuoteSym = Keyword.New("quote");
static readonly Sym SetqSym = Keyword.New("setq");
static readonly Sym BackQuoteSym = Sym.New("`");
static readonly Sym CommaAtSym = Sym.New(",@");
static readonly Sym CommaSym = Sym.New(",");
static readonly Sym DotSym = Sym.New(".");
static readonly Sym LeftParenSym = Sym.New("(");
static readonly Sym RightParenSym = Sym.New(")");
static readonly Sym SingleQuoteSym = Sym.New("'");
static readonly Sym AppendSym = Sym.New("append");
static readonly Sym ConsSym = Sym.New("cons");
static readonly Sym ListSym = Sym.New("list");
static readonly Sym RestSym = Sym.New("&rest");
static readonly Sym UnquoteSym = Sym.New("unquote");
static readonly Sym UnquoteSplicingSym = Sym.New("unquote-splicing");
/// <summary>The symbol of <c>t</c></summary>
public static readonly Sym TSym = Sym.New("t");
//------------------------------------------------------------------
// Get cdr of list x as a Cell or null.
static Cell CdrCell(Cell x) {
var k = x.Cdr;
if (k == null) {
return null;
} else {
if (k is Cell c)
return c;
else
throw new EvalException("proper list expected", x);
}
}
/// <summary>Common base class of Lisp functions</summary>
public abstract class LispFunc {
/// <summary>Number of arguments, made negative if the function
/// has &rest</summary>
public int Carity { get; }
int Arity => (Carity < 0) ? -Carity : Carity;
bool HasRest => (Carity < 0);
// Number of fixed arguments
int FixedArgs => (Carity < 0) ? -Carity - 1 : Carity;
/// <summary>Construct with Carity.</summary>
protected LispFunc(int carity) {
Carity = carity;
}
/// <summary>Make a frame for local variables from a list of
/// actual arguments.</summary>
public object[] MakeFrame(Cell arg) {
var frame = new object[Arity];
int n = FixedArgs;
int i;
for (i = 0; i < n && arg != null; i++) {
// Set the list of fixed arguments.
frame[i] = arg.Car;
arg = CdrCell(arg);
}
if (i != n || (arg != null && !HasRest))
throw new EvalException("arity not matched", this);
if (HasRest)
frame[n] = arg;
return frame;
}
/// <summary>Evaluate each expression in a frame.</summary>
public void EvalFrame(object[] frame, Interp interp, Cell env) {
int n = FixedArgs;
for (int i = 0; i < n; i++)
frame[i] = interp.Eval(frame[i], env);
if (HasRest) {
if (frame[n] is Cell j) {
Cell z = null;
Cell y = null;
do {
var e = interp.Eval(j.Car, env);
Cell x = new Cell(e, null);
if (z == null)
z = x;
else
y.Cdr = x;
y = x;
j = CdrCell(j);
} while (j != null);
frame[n] = z;
}
}
}
}
// Common base class of functions which are defined with Lisp expressions
abstract class DefinedFunc: LispFunc {
// Lisp list as the function body
public readonly Cell Body;
protected DefinedFunc(int carity, Cell body): base(carity) {
Body = body;
}
}
// Common function type which represents any factory method of DefinedFunc
delegate DefinedFunc FuncFactory(int carity, Cell body, Cell env);
// Compiled macro expression
sealed class Macro: DefinedFunc {
Macro(int carity, Cell body): base(carity, body) {}
public override string ToString() => $"#<macro:{Carity}:{Str(Body)}>";
// Expand the macro with a list of actual arguments.
public object ExpandWith(Interp interp, Cell arg) {
object[] frame = MakeFrame(arg);
Cell env = new Cell(frame, null);
object x = null;
for (Cell j = Body; j != null; j = CdrCell(j))
x = interp.Eval(j.Car, env);
return x;
}
public static DefinedFunc Make(int carity, Cell body, Cell env) {
Debug.Assert(env == null);
return new Macro(carity, body);
}
}
// Compiled lambda expression (Within another function)
sealed class Lambda: DefinedFunc {
Lambda(int carity, Cell body): base(carity, body) {}
public override string ToString() => $"#<lambda:{Carity}:{Str(Body)}>";
public static DefinedFunc Make(int carity, Cell body, Cell env) {
Debug.Assert(env == null);
return new Lambda(carity, body);
}
}
// Compiled lambda expression (Closure with environment)
sealed class Closure: DefinedFunc {
// The environment of the closure
public readonly Cell Env;
Closure(int carity, Cell body, Cell env): base(carity, body) {
Env = env;
}
public Closure(Lambda x, Cell env): this(x.Carity, x.Body, env) {}
public override string ToString() =>
$"#<closure:{Carity}:{Str(Env)}:{Str(Body)}>";
// Make an environment to evaluate the body from a list of actual args.
public Cell MakeEnv(Interp interp, Cell arg, Cell interpEnv) {
object[] frame = MakeFrame(arg);
EvalFrame(frame, interp, interpEnv);
return new Cell(frame, Env); // Prepend the frame to this Env.
}
public static DefinedFunc Make(int carity, Cell body, Cell env) =>
new Closure(carity, body, env);
}
/// <summary>Function type which represents any built-in function body
/// </summary>
public delegate object BuiltInFuncBody(object[] frame);
/// <summary>Built-in function</summary>
public sealed class BuiltInFunc: LispFunc {
/// <summary>Name of this function</summary>
public string Name { get; }
/// <summary>C# function as the body of this function</summary>
public BuiltInFuncBody Body { get; }
/// <summary>Construct with Name, Carity and Body.</summary>
public BuiltInFunc(string name, int carity, BuiltInFuncBody body)
: base(carity) {
Name = name;
Body = body;
}
/// <summary>Return a string representation in Lisp.</summary>
public override string ToString() => $"#<{Name}:{Carity}>";
/// <summary>Invoke the built-in function with a list of
/// actual arguments.</summary>
public object EvalWith(Interp interp, Cell arg, Cell interpEnv) {
object[] frame = MakeFrame(arg);
EvalFrame(frame, interp, interpEnv);
try {
return Body(frame);
} catch (EvalException) {
throw;
} catch (Exception ex) {
throw new EvalException($"{ex} -- {Name}", frame);
}
}
}
// Bound variable in a compiled lambda/macro expression
sealed class Arg {
public readonly int Level;
public readonly int Offset;
public readonly Sym Symbol;
public Arg(int level, int offset, Sym symbol) {
Level = level;
Offset = offset;
Symbol = symbol;
}
public override string ToString() => $"#{Level}:{Offset}:{Symbol}";
// Set a value x to the location corresponding to the variable in env.
public void SetValue(object x, Cell env) {
for (int i = 0; i < Level; i++)
env = (Cell) env.Cdr;
object[] frame = (object[]) env.Car;
frame[Offset] = x;
}
// Get a value from the location corresponding to the variable in env.
public object GetValue(Cell env) {
for (int i = 0; i < Level; i++)
env = (Cell) env.Cdr;
object[] frame = (object[]) env.Car;
return frame[Offset];
}
}
/// <summary>Exception in evaluation</summary>
public class EvalException: Exception {
/// <summary>Stack trace of Lisp evaluation</summary>
public List<string> Trace { get; } = new List<string>();
/// <summary>Construct with a base message, cause, and
/// a flag whether to quote strings in the cause.</summary>
public EvalException(string msg, object x, bool quoteString=true)
: base(msg + ": " + Str(x, quoteString)) {}
/// <summary>Return a string representation which contains
/// the message and the stack trace.</summary>
public override string ToString() {
var sb = new StringBuilder($"EvalException: {Message}", 0);
foreach (string line in Trace)
sb.Append($"\n\t{line}");
return sb.ToString();
}
}
// Exception which indicates on absense of a variable
sealed class NotVariableException: EvalException {
public NotVariableException(object x): base("variable expected", x) {}
}
//------------------------------------------------------------------
/// <summary>Core of the Lisp interpreter</summary>
public class Interp {
/// <summary>Table of the global values of symbols</summary>
protected readonly Dictionary<Sym, object> Globals =
new Dictionary<Sym, object>();
/// <summary>Standard out</summary>
public TextWriter COut { get; set; } = Console.Out;
/// <summary>Set each built-in function/variable as the global value
/// of symbol.</summary>
public Interp() {
Globals[TSym] = TSym;
Def("car", 1, a => (a[0] as Cell)?.Car);
Def("cdr", 1, a => (a[0] as Cell)?.Cdr);
Def("cons", 2, a => new Cell(a[0], a[1]));
Def("atom", 1, a => (a[0] is Cell) ? null : TSym);
Def("eq", 2, a => (a[0] == a[1]) ? TSym : null);
Def("list", -1, a => a[0]);
Def("rplaca", 2, a => { ((Cell) a[0]).Car = a[1]; return a[1]; });
Def("rplacd", 2, a => { ((Cell) a[0]).Cdr = a[1]; return a[1]; });
Def("length", 1, a => {
dynamic x = a[0];
if (x == null)
return 0.0;
return (double) x.Length;
});
Def("stringp", 1, a => (a[0] is string) ? TSym : null);
Def("numberp", 1, a => (a[0] is double) ? TSym : null);
Def("eql", 2, a => ((a[0] == null) ? ((a[1] == null) ?
TSym : null) :
a[0].Equals(a[1]) ? TSym : null));
Def("<", 2, a => ((double) a[0] < (double) a[1]) ? TSym : null);
Def("%", 2, a => (double) a[0] % (double) a[1]);
Def("mod", 2, a => {
var x = (double) a[0];
var y = (double) a[1];
if ((x < 0 && y > 0) || (x > 0 && y < 0))
return x % y + y;
return x % y;
});
Def("+", -1, a => FoldL(0.0, (Cell) a[0],
(i, j) => i + (double) j));
Def("*", -1, a => FoldL(1.0, (Cell) a[0],
(i, j) => i * (double) j));
Def("-", -2, a => {
var x = (double) a[0];
var y = (Cell) a[1];
if (y == null)
return -x;
return FoldL(x, y, (i, j) => i - (double) j);
});
Def("/", -3, a => FoldL((double) a[0] / (double) a[1],
(Cell) a[2],
(i, j) => i / (double) j));
Def("truncate", -2, a => {
var x = (double) a[0];
var y = (Cell) a[1];
if (y == null)
return Math.Truncate(x);
else if (y.Cdr == null)
return Math.Truncate(x / (double) y.Car);
else
throw new ArgumentException
("one or two arguments expected");
});
Def("prin1", 1, a => {
COut.Write(Str(a[0], true)); return a[0];
});
Def("princ", 1, a => {
COut.Write(Str(a[0], false)); return a[0];
});
Def("terpri", 0, a => {
COut.WriteLine(); return TSym;
});
var gensymCounterSym = Sym.New("*gensym-counter*");
Globals[gensymCounterSym] = 1.0;
Def("gensym", 0, a => {
double x = (double) Globals[gensymCounterSym];
Globals[gensymCounterSym] = x + 1.0;
return new Sym($"G{(int) x}");
});
Def("make-symbol", 1, a => new Sym((string) a[0]));
Def("intern", 1, a => Sym.New((string) a[0]));
Def("symbol-name", 1, a => ((Sym) a[0]).Name);
Def("apply", 2, a =>
Eval(new Cell(a[0], MapCar((Cell) a[1], QqQuote)), null));
Def("exit", 1, a => {
Environment.Exit((int) ((double) a[0]));
return null;
});
Def("dump", 0, a =>
Globals.Keys.Aggregate((Cell) null, (x, y) => new Cell(y, x)));
var assembly = Assembly.GetExecutingAssembly();
var product = (AssemblyProductAttribute)
Attribute.GetCustomAttribute
(assembly, typeof(AssemblyProductAttribute));
var version = assembly.GetName().Version;
double iversion = version.Major + 0.1 * version.Minor +
0.01 * version.Build;
Globals[Sym.New("*version*")] =
new Cell(iversion,
new Cell("C# 7", new Cell(product.Product, null)));
}
/// <summary>Define a built-in function by a name, an arity,
/// and a body.</summary>
public void Def(string name, int carity, BuiltInFuncBody body) {
Globals[Sym.New(name)] = new BuiltInFunc(name, carity, body);
}
/// <summary>Evaluate a Lisp expression in an environment.</summary>
public object Eval(object x, Cell env) {
try {
for (;;) {
switch (x) {
case Arg xarg:
return xarg.GetValue(env);
case Sym xsym:
try {
return Globals[xsym];
} catch (KeyNotFoundException) {
throw new EvalException("void variable", x);
}
case Cell xcell:
var fn = xcell.Car;
Cell arg = CdrCell(xcell);
if (fn is Keyword) {
if (fn == QuoteSym) {
if (arg != null && arg.Cdr == null)
return arg.Car;
throw new EvalException("bad quote", x);
} else if (fn == PrognSym) {
x = EvalProgN(arg, env);
} else if (fn == CondSym) {
x = EvalCond(arg, env);
} else if (fn == SetqSym) {
return EvalSetQ(arg, env);
} else if (fn == LambdaSym) {
return Compile(arg, env, Closure.Make);
} else if (fn == MacroSym) {
if (env != null)
throw new EvalException("nested macro", x);
return Compile(arg, null, Macro.Make);
} else if (fn == QuasiquoteSym) {
if (arg != null && arg.Cdr == null)
x = QqExpand(arg.Car);
else
throw new EvalException ("bad quasiquote",
x);
} else {
throw new EvalException("bad keyword", fn);
}
} else { // Application of a function
if (fn is Sym fnsym) {
// Expand fn = Eval(fn, env) here for speed.
try {
fn = Globals[fnsym];
} catch (KeyNotFoundException) {
throw new EvalException("undefined",
fnsym);
}
} else {
fn = Eval(fn, env);
}
switch (fn) {
case Closure fnclosure:
env = fnclosure.MakeEnv(this, arg, env);
x = EvalProgN(fnclosure.Body, env);
break;
case Macro fnmacro:
x = fnmacro.ExpandWith(this, arg);
break;
case BuiltInFunc fnbulitin:
return fnbulitin.EvalWith(this, arg, env);
default:
throw new EvalException("not appliable", fn);
}
}
break;
case Lambda xlambda:
return new Closure(xlambda, env);
default:
return x; // numbers, strings, null etc.
}
}
} catch (EvalException ex) {
if (ex.Trace.Count < 10)
ex.Trace.Add(Str(x));
throw ex;
}
}
// (progn E1 ... En) => Evaluate E1, ... except for En and return it.
object EvalProgN(Cell j, Cell env) {
if (j == null)
return null;
for (;;) {
var x = j.Car;
j = CdrCell(j);
if (j == null)
return x; // The tail expression to be evaluated later
Eval(x, env);
}
}
// Evaluate a conditional expression and return the selection.
object EvalCond(Cell j, Cell env) {
for (; j != null; j = CdrCell(j)) {
var clause = j.Car;
if (clause != null) {
if (clause is Cell k) {
var result = Eval(k.Car, env);
if (result != null) { // If the condition holds
Cell body = CdrCell(k);
if (body == null)
return QqQuote(result);
else
return EvalProgN(body, env);
}
} else {
throw new EvalException("cond test expected", clause);
}
}
}
return null; // No clause holds.
}
// (setq V1 E1 ..) => Evaluate Ei and assign it to Vi; return the last.
object EvalSetQ(Cell j, Cell env) {
object result = null;
for (; j != null; j = CdrCell(j)) {
var lval = j.Car;
if (lval == TSym)
throw new EvalException("not assignable", lval);
j = CdrCell(j);
if (j == null)
throw new EvalException("right value expected", lval);
result = Eval(j.Car, env);
switch (lval) {
case Arg arg:
arg.SetValue(result, env);
break;
case Sym sym when !(sym is Keyword):
Globals[sym] = result;
break;
default:
throw new NotVariableException(lval);
}
}
return result;
}
// Compile a Lisp list (macro ...) or (lambda ...).
DefinedFunc Compile(Cell arg, Cell env, FuncFactory make) {
if (arg == null)
throw new EvalException("arglist and body expected", arg);
var table = new Dictionary<Sym, Arg>();
bool hasRest = MakeArgTable(arg.Car, table);
int arity = table.Count;
Cell body = CdrCell(arg);
body = ScanForArgs(body, table) as Cell;
body = ExpandMacros(body, 20) as Cell; // Expand up to 20 nestings.
body = CompileInners(body) as Cell;
return make(hasRest ? -arity : arity, body, env);
}
// Expand macros and quasi-quotations in an expression.
object ExpandMacros(object j, int count) {
if ((j is Cell cell) && count > 0) {
var k = cell.Car;
if (k == QuoteSym || k == LambdaSym || k == MacroSym) {
return cell;
} else if (k == QuasiquoteSym) {
Cell d = CdrCell(cell);
if (d != null && d.Cdr == null) {
var z = QqExpand(d.Car);
return ExpandMacros(z, count);
}
throw new EvalException("bad quasiquote", cell);
} else {
if (k is Sym sym)
k = Globals.ContainsKey(sym) ? Globals[sym] : null;
if (k is Macro macro) {
Cell d = CdrCell(cell);
var z = macro.ExpandWith(this, d);
return ExpandMacros(z, count - 1);
} else {
return MapCar(cell, x => ExpandMacros(x, count));
}
}
} else {
return j;
}
}
// Replace inner lambda-expressions with Lambda instances.
object CompileInners(object j) {
if (j is Cell cell) {
var k = cell.Car;
if (k == QuoteSym) {
return cell;
} else if (k == LambdaSym) {
Cell d = CdrCell(cell);
return Compile(d, null, Lambda.Make);
} else if (k == MacroSym) {
throw new EvalException("nested macro", cell);
} else {
return MapCar(cell, x => CompileInners(x));
}
} else {
return j;
}
}
}
//------------------------------------------------------------------
// Make an argument-table; return true if there is a rest argument.
static bool MakeArgTable(object arg, IDictionary<Sym, Arg> table) {
if (arg == null) {
return false;
} else if (arg is Cell argcell) {
int offset = 0; // offset value within the call-frame
bool hasRest = false;
for (; argcell != null; argcell = CdrCell(argcell)) {
var j = argcell.Car;
if (hasRest)
throw new EvalException("2nd rest", j);
if (j == RestSym) { // &rest var
argcell = CdrCell(argcell);
if (argcell == null)
throw new NotVariableException(argcell);
j = argcell.Car;
if (j == RestSym)
throw new NotVariableException(j);
hasRest = true;
}
Sym sym = j as Sym;
if (sym == null) {
Arg jarg = j as Arg;
if (jarg != null)
sym = jarg.Symbol;
else
throw new NotVariableException(j);
}
if (sym == TSym)
throw new EvalException("not assignable", sym);
if (table.ContainsKey(sym))
throw new EvalException("duplicated argument name", sym);
table[sym] = new Arg(0, offset, sym);
offset++;
}
return hasRest;
} else {
throw new EvalException("arglist expected", arg);
}
}
// Scan 'j' for formal arguments in 'table' and replace them with Args.
// And scan 'j' for free Args not in 'table' and promote their levels.
static object ScanForArgs(object j, IDictionary<Sym, Arg> table) {
switch (j) {
case Sym sym:
return ((table.TryGetValue(sym, out Arg a)) ? a :
j);
case Arg arg:
return ((table.TryGetValue(arg.Symbol, out Arg k)) ? k :
new Arg(arg.Level + 1, arg.Offset, arg.Symbol));
case Cell cell:
if (cell.Car == QuoteSym)
return j;
else if (cell.Car == QuasiquoteSym)
return new Cell(QuasiquoteSym,
ScanForQQ(cell.Cdr, table, 0));
else
return MapCar(cell, x => ScanForArgs(x, table));
default:
return j;
}
}
// Scan for quasi-quotes and ScanForArgs them depending on the nesting
// level.
static object ScanForQQ(object j, IDictionary<Sym, Arg> table, int level) {
if (j is Cell cell) {
var car = cell.Car;
var cdr = cell.Cdr;
if (car == QuasiquoteSym) {
return new Cell(car, ScanForQQ(cdr, table, level + 1));
} else if (car == UnquoteSym || car == UnquoteSplicingSym) {
var d = ((level == 0) ? ScanForArgs(cdr, table) :
ScanForQQ(cdr, table, level - 1));
if (d == cdr)
return j;
return new Cell(car, d);
} else {
return MapCar(cell, x => ScanForQQ(x, table, level));
}
} else {
return j;
}
}
//------------------------------------------------------------------
// Quasi-Quotation
/// <summary>Expand <c>x</c> of any quqsi-quotation <c>`x</c> into
/// the equivalent S-expression.</summary>
public static object QqExpand(object x) =>
QqExpand0(x, 0); // Begin with the nesting level 0.
/// <summary>Quote <c>x</c> so that the result evaluates to <c>x</c>.
/// </summary>
public static object QqQuote(object x) =>
(x is Sym || x is Cell) ? new Cell(QuoteSym, new Cell(x, null)) : x;
static object QqExpand0(object x, int level) {
if (x is Cell cell) {
if (cell.Car == UnquoteSym) { // ,a
if (level == 0)
return CdrCell(cell).Car; // ,a => a
}
Cell t = QqExpand1(cell, level);
if ((t.Car is Cell k) && t.Cdr == null) {
if (k.Car == ListSym || k.Car == ConsSym)
return k;
}
return new Cell(AppendSym, t);
} else {
return QqQuote(x);
}
}
// Expand x of `x so that the result can be used as an argument of append.
// Example 1: (,a b) => h=(list a) t=((list 'b)) => ((list a 'b))
// Example 2: (,a ,@(cons 2 3)) => h=(list a) t=((cons 2 3))
// => ((cons a (cons 2 3)))
static Cell QqExpand1(object x, int level) {
if (x is Cell cell) {
if (cell.Car == UnquoteSym) { // ,a
if (level == 0)
return CdrCell(cell); // ,a => (a)
level--;
} else if (cell.Car == QuasiquoteSym) { // `a
level++;
}
var h = QqExpand2(cell.Car, level);
Cell t = QqExpand1(cell.Cdr, level); // != null
if (t.Car == null && t.Cdr == null) {
return new Cell(h, null);
} else if (h is Cell hcell) {
if (hcell.Car == ListSym) {
if (t.Car is Cell tcar) {
if (tcar.Car == ListSym) {
var hh = QqConcat(hcell, tcar.Cdr);
return new Cell(hh, t.Cdr);
}
}
if (hcell.Cdr != null) {
var hh = QqConsCons(CdrCell(hcell), t.Car);
return new Cell(hh, t.Cdr);
}
}
}
return new Cell(h, t);
} else {
return new Cell(QqQuote(x), null);
}
}
// (1 2), (3 4) => (1 2 3 4)
static object QqConcat(Cell x, object y) =>
(x == null) ? y :
new Cell(x.Car, QqConcat(CdrCell(x), y));
// (1 2 3), "a" => (cons 1 (cons 2 (cons 3 "a")))
static object QqConsCons(Cell x, object y) =>
(x == null) ? y :
new Cell(ConsSym,
new Cell(x.Car,
new Cell(QqConsCons(CdrCell(x), y), null)));
// Expand x.car of `x so that the result can be used as an arg of append.
// Example: ,a => (list a); ,@(foo 1 2) => (foo 1 2); b => (list 'b)
static object QqExpand2(object y, int level) { // Let y be x.car.
if (y is Cell cell) {
if (cell.Car == UnquoteSym) { // ,a
if (level == 0)
return new Cell(ListSym, cell.Cdr); // ,a => (list a)
level--;
} else if (cell.Car == UnquoteSplicingSym) { // ,@a
if (level == 0)
return CdrCell(cell).Car; // ,@a => a
level--;
} else if (cell.Car == QuasiquoteSym) { // `a
level++;
}
}
return new Cell(ListSym, new Cell(QqExpand0(y, level), null));
}
//------------------------------------------------------------------
/// <summary>Reader of Lisp expressions</summary>
public class Reader {
readonly TextReader TReader;
object Token;
IEnumerator<string> Tokens =
((IEnumerable<string>) new string[0]).GetEnumerator();
int LineNo = 0;
string Line = "";
bool Erred = false;
/// <summary>Token of "End Of File"</summary>
public static object EOF = new Sym("#EOF");
/// <summary>Construct a Lisp reader.</summary>
/// <param name="tr">Text reader from which Lisp expressions will
/// be read</param>
public Reader(TextReader tr) {
TReader = tr;
}
/// <summary>Read a Lisp expression and return it.</summary>
/// <remarks>Return EOF if the input runs out.</remarks>
public async Task<object> Read() {
try {
await ReadToken();
return await ParseExpression();
} catch (FormatException ex) {
throw new EvalException("syntax error",
$"{ex.Message} -- {LineNo}: {Line}",
false);
}
}
async Task<object> ParseExpression() {
if (Token == LeftParenSym) { // (a b c)
await ReadToken();
return await ParseListBody();
} else if (Token == SingleQuoteSym) { // 'a => (quote a)
await ReadToken();
return new Cell(QuoteSym,
new Cell(await ParseExpression(), null));
} else if (Token == BackQuoteSym) { // `a => (quasiquote a)
await ReadToken();
return new Cell(QuasiquoteSym,
new Cell(await ParseExpression(), null));
} else if (Token == CommaSym) { // ,a => (unquote a)
await ReadToken();
return new Cell(UnquoteSym,
new Cell(await ParseExpression(), null));
} else if (Token == CommaAtSym) { // ,@a => (unquote-splicing a)
await ReadToken();
return new Cell(UnquoteSplicingSym,
new Cell(await ParseExpression(), null));
} else if (Token == DotSym || Token == RightParenSym) {
throw new FormatException($"unexpected {Token}");
} else {
return Token;
}
}
async Task<Cell> ParseListBody() {
if (Token == EOF) {
throw new FormatException("unexpected EOF");
} else if (Token == RightParenSym) {
return null;
} else {
var e1 = await ParseExpression();
await ReadToken();
object e2;
if (Token == DotSym) { // (a . b)
await ReadToken();
e2 = await ParseExpression();
await ReadToken();
if (Token != RightParenSym)
throw new FormatException($"\")\" expected: {Token}");
} else {
e2 = await ParseListBody();
}