-
Notifications
You must be signed in to change notification settings - Fork 0
/
lil_schemer.d
1524 lines (1207 loc) · 48.2 KB
/
lil_schemer.d
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
module lil_schemer;
/* * Text: The Lil' Javascripter, Originally by Douglas Crockford
* Chapter: source, Modified by James Watson
* Project: SPARROW, [S]cheme [P]rogram [A]llowing [R]easonable [R]eckoning [O]f [W]ork
* Component: Scheme mini-implementation, written in Dlang instead of Javascript
rdmd lil_schemer.d
https://github.com/jwatson-CO-edu/FINCH/blob/main/JS/JS_Scheme_f_rename.js
James Watson, 2022-09 */
/*
///// DEV THOUGHTS & PLANS /////
/// Comparison of SPARROW and FINCH ///
Feature/Structure | SPARROW | FINCH
-------------------------------------------
Structure Cons Object // Can an object be versatile, lightweight, and fast? Needs study
Syntax Unit Atom Atom // Would like to have a lightweight atom for FINCH
Eval Unit ExprInContext Fragment // `ExprInContext` was a happy accident in partially modelling a Fragment
Context Scoped Flow // Is flow based programming relevant outside of an event loop?
*/
////////// INIT ////////////////////////////////////////////////////////////////////////////////////
///// Imports /////
import std.string; // ----------- `string` type
import std.stdio; // ------------ `writeln`
import std.conv; // ------------- string conversions
import std.uni; // -------------- `strip`
import std.math.operations; // -- `NaN`
import std.typecons; // ---------- Tuple
import std.ascii; // ------------- Whitespace test
import std.algorithm.searching; // `canFind``
alias is_white = std.ascii.isWhite;
///// Env Vars /////
bool _DEBUG_VERBOSE = false; // Set true for debug prints
////////// ATOMS ///////////////////////////////////////////////////////////////////////////////////
enum F_Error{
OKAY = "OKAY", // -- No error code applicable
NOVALUE = "NOVALUE", // There is no value held in this atom
NAN = "NAN", // --- Not A Number
DNE = "DNE", // --- Does Not Exist
SYNTAX = "SYNTAX", //- Syntax error
LEXER = "LEXER", // - Eval machinery failed
}
enum F_Type{
CONS, // Cons pair
STRN, // String/Symbol
NMBR, // Number
EROR, // Error object
BOOL, // Boolean value
FUNC, // Function
// NULL, // Null // 2022-09-03: Trying it w/o NULL
}
struct Atom{
F_Type kind; // ---------------- What kind of atom this is
Atom* car; // ----------------- Left `Atom` Pointer
Atom* cdr; // ----------------- Right `Atom` Pointer
double num; // ----------------- Number value
string str; // ----------------- String value, D-string underlies
bool bul; // ----------------- Boolean value
F_Error err = F_Error.NOVALUE; /* Error code, 2022-09-03: Any atom can have an error code
Instead of NULL, we can ask the Atom if it has a fault code assigned to it */
}
Atom* empty_atom(){
// Allocate and return an a `NOVALUE` error
return new Atom(
F_Type.EROR,
null,
null,
double.nan,
"NO VALUE",
false,
F_Error.NOVALUE
);
}
Atom* make_string( string str ){
// Make a string
return new Atom(
F_Type.STRN,
null,
null,
double.nan,
str,
(str.length > 0),
F_Error.OKAY
);
}
Atom* make_number( double nmbr ){
// Make a number
return new Atom(
F_Type.NMBR,
null,
null,
nmbr,
"",
(nmbr > 0.0),
F_Error.OKAY
);
}
Atom* make_error( F_Error code, string msg ){
// Make an error
return new Atom(
F_Type.EROR,
null,
null,
double.nan,
msg,
false,
code
);
}
Atom* make_bool( bool val ){
// Make a Boolean value
return new Atom(
F_Type.BOOL,
null,
null,
double.nan,
"",
val,
F_Error.OKAY
);
}
///// Cons /////
Atom* make_cons( Atom* car = null, Atom* cdr = null, ){
// Make a pair
return new Atom(
F_Type.CONS,
car,
cdr,
double.nan,
"",
true,
F_Error.OKAY
);
}
///// Function /////
Atom* make_function( Atom* parameters = null, Atom* definition = null ){
// Make a function
return new Atom(
F_Type.FUNC, // Function
parameters, //- Cons list of parameter symbols
definition, //- Cons structure containing code
double.nan, //- No number interpretation
"function", //- Name as a string
true, // ------ Assume truthiness
F_Error.OKAY // Assume okay
);
}
///// Getters and Setters ////////////////////////
// Basic Getters //
Atom* get_car( Atom* atm ){ return atm.car; } // ---------------------- Get left pair item
Atom* get_cdr( Atom* atm ){ return atm.cdr; } // ---------------------- Get right pair item
Atom* first( Atom* atm ){ return get_car(atm); } // ----------------- Return the first item of an LS pair
Atom* second( Atom* atm ){ return get_car(get_cdr(atm)); } // -------- Return the second item in a list, (cadr l)
Atom* third( Atom* atm ){ return get_car(get_cdr(get_cdr(atm))); } // Return the third item of 'l', (caddr l)
// Aliased Getters //
Atom* condLinesOf( Atom* atm ){ return get_cdr( atm ); }
Atom* formLinesOf( Atom* atm ){ return get_cdr( atm ); }
Atom* argsOf( Atom* atm ){ return get_cdr( atm ); }
// Atom* paramsOf( Atom* atm ){ return get_cdr( atm ); }
// Atom* tableOf( Atom* atm ){ return first( atm ); }
Atom* nameOf( Atom* atm ){ return first( atm ); }
Atom* questionOf( Atom* atm ){ return first( atm ); }
Atom* textOf( Atom* atm ){ return second( atm ); }
Atom* formalsOf( Atom* atm ){ return first( atm ); }
Atom* answerOf( Atom* atm ){ return second( atm ); }
Atom* bodyOf( Atom* atm ){ return second( atm ); }
// Basic Setters //
bool set_car_B( Atom* atm, Atom* carAtm ){
// Set left pair item
atm.car = carAtm;
return true;
}
bool set_cdr_B( Atom* atm, Atom* cdrAtm ){
// Set right pair item
atm.cdr = cdrAtm;
return true;
}
////////// PREDICATES //////////////////////////////////////////////////////////////////////////////
bool p_empty( Atom* atm ){
//- Atom either has the `NOVALUE` code or is null
return (atm == null) || (atm.err == F_Error.NOVALUE);
}
bool p_has_error( Atom* atm ){ return (atm.err != F_Error.OKAY); } // Atom has any code other than `OKAY`
bool p_cons( Atom* atm ){ return (atm.kind == F_Type.CONS); } // ---- Return true if Atom is a pair
bool p_literal( Atom* atm ){ return (atm.kind == F_Type.NMBR) || (atm.kind == F_Type.STRN) || (atm.kind == F_Type.BOOL); }
bool p_number( Atom* atm ){ return (atm.kind == F_Type.NMBR); }
bool p_string( Atom* atm ){ return (atm.kind == F_Type.STRN); }
bool p_bool( Atom* atm ){ return (atm.kind == F_Type.BOOL); }
bool p_zero( Atom* atm ){ return (atm.kind == F_Type.NMBR) && (atm.num == 0.0); }
////////// MATHEMATIC PRIMITIVE HELPERS ////////////////////////////////////////////////////////////
///// Dlang Math /////
double add1( double n ){ return n + 1.0; } // Increment
double sub1( double n ){ return n - 1.0; } // Decrement
double add( double[] args ){
// sums an arbitrary number of arguments, returns 0 if no args given
// typesafe variadic function: https://dlang.org/spec/function.html#typesafe_variadic_functions
double sum = 0.0;
foreach (double x; args)
sum += x;
return sum;
}
double minus( double[] args ){
// returns the difference between the first arg and all subsequent args, returns NaN if no args given
if( args.length == 0 ){
return NaN(0);
}else if( args.length == 1 ){
return -args[0];
}else{
double total = args[0];
foreach (double x; args[1..$])
total -= x;
return total;
}
return NaN(0);
}
double multiply( double[] args ){
// returns the product of an arbitrary number of arguments, returns 1 if no args given
// typesafe variadic function: https://dlang.org/spec/function.html#typesafe_variadic_functions
double prod = 1.0;
foreach (double x; args)
prod *= x;
return prod;
}
double divide( double[] args ){
// returns the quotient of the first agument divided by every subsequent argument, returns 1 if no args given
// typesafe variadic function: https://dlang.org/spec/function.html#typesafe_variadic_functions
if( args.length == 0 ){
return NaN(0);
}else if( args.length == 1 ){
return 1.0/args[0];
}else{
double total = args[0];
foreach (double x; args[1..$])
total /= x;
return total;
}
return NaN(0);
}
bool lt( double[] args ){
// Less Than, 2 or more arguments, If insufficient arguments, then return False
if( args.length < 2 ){ return false; }
else{
double last = args[0];
foreach (double x; args[1..$]){
if( last >= x ){ return false; }
last = x;
}
return true;
}
}
bool gt( double[] args ){
// Greater Than, 2 or more arguments, If insufficient arguments, then return False
if( args.length < 2 ){ return false; }
else{
double last = args[0];
foreach (double x; args[1..$]){
if( last <= x ){ return false; }
last = x;
}
return true;
}
}
bool le( double[] args ){
// Less Than Or Equal To, 2 or more arguments, If insufficient arguments, then return False
if( args.length < 2 ){ return false; }
else{
double last = args[0];
foreach (double x; args[1..$]){
if( last > x ){ return false; }
last = x;
}
return true;
}
}
bool ge( double[] args ){
// Greater Than Or Equal To, 2 or more arguments, If insufficient arguments, then return False
if( args.length < 2 ){ return false; }
else{
double last = args[0];
foreach (double x; args[1..$]){
if( last < x ){ return false; }
last = x;
}
return true;
}
}
////////// LIST PROCESSING /////////////////////////////////////////////////////////////////////////
///// Accessing & Constructing ///////////////////
Atom* consify_atom( Atom* carAtm ){
// Wrap the `atm` in a cons, with `carAtm` as 'car'
return make_cons( carAtm, empty_atom() );
}
Atom* find_terminus( Atom* list ){
// Iterate to the ending cons of the list and return a pointer to it
// 0. Set the argument equal to our pointer
Atom* curr = list;
// 1. If this is a cons structure, then we must find the end of it
if( list.kind == F_Type.CONS ){
// 2. Iterate pointer to next `cdr` until we reach a pair that contains the terminating null, return pair
while( !p_empty( curr.cdr ) ){ curr = curr.cdr; }
// while( !p_empty( curr ) ){ curr = curr.cdr; }
return curr;
}else{ // Else atom was literal, it is its own terminus
return list;
}
}
Atom* append( Atom* list, Atom* atm = null ){
// Append an atom to the end of a conslist, Create a conslist if none exists, return pointer to list head
Atom* rtnLst = null;
Atom* endCns = null;
// 1. If the given list is a cons list, it is either an empty cons or the head of a LISP list
if( list.kind == F_Type.CONS ){
/* 2. If we were given an atom to append, it either belongs in the `car` of the empty cons,
or in the `car` of a new terminal cons */
if( p_empty( list.car ) ){
set_car_B( list, atm );
}else{
endCns = find_terminus( list );
set_cdr_B( endCns, consify_atom( atm ) );
}
rtnLst = list;
// 3. Else we either have one or two non-cons atoms
}else{
rtnLst = consify_atom( list ); // --------------- ( `list` , [X] )
if( !p_empty( atm ) ){
set_cdr_B( rtnLst, consify_atom( atm ) ); // ( `list` , ( `atom` , [X] ) )
}
}
return rtnLst;
}
Atom* make_list_of_2( Atom* atm1, Atom* atm2 ){
// return a two-item list with 's1' as the first item and 's2' as the second item
return make_cons( atm1, make_cons( atm2, empty_atom() ) );
}
///// Printing ///////////////////////////////////
string str( Atom* item ){
// Return the string representation of the `item`
// Null Symbol: https://www.fileformat.info/info/unicode/char/29c4/index.htm
string rtnStr = "";
if( p_empty( item ) ){ rtnStr = "\xE2\xA7\x84"; }
else{
switch( item.kind ){
case F_Type.STRN:
rtnStr = item.str;
break;
case F_Type.BOOL:
if( item.bul ){ rtnStr = "T"; }
else{ rtnStr = "F"; }
break;
case F_Type.NMBR:
rtnStr = item.num.to!string();
break;
case F_Type.CONS:
rtnStr = "( "~str(item.car)~", "~str(item.cdr)~" )";
break;
case F_Type.EROR:
rtnStr = "( ERROR: " ~ item.err ~ ", " ~ item.str ~ " )";
break;
default: break;
}
}
return rtnStr;
}
void prnt( Atom* atm ){ writeln( str( atm ) ); } // Print a cons structure
////////// LEXING //////////////////////////////////////////////////////////////////////////////////
string[string] RESERVED;
void init_reserved(){
RESERVED["("] = "open_parn"; // Open paren
RESERVED[")"] = "clos_parn"; // Close paren
}
string find_reserved( string token ){
// Return the name of the reserved symbol, or an empty string if not found
string* res = token in RESERVED;
if( res !is null ){ return *res; } // If key in dict, then return the string name of the reserved token
else{ return ""; } // --------------- Else the search failed, return an empty string
}
string[] tokenize( string expStr, dchar sepChar = ' ' ){
// Separate an expression string into tokens
string[] tokens;
dchar c = ' ';
string token = "";
bool testWhite = false;
if( sepChar == ' ' ) testWhite = true;
// Helpers //
void stow_token(){ tokens ~= token; token = ""; }
void stow_char(){ tokens ~= to!string( c ); }
void cache_char(){ token ~= c; }
// 0. Apply the postfix hack
expStr ~= sepChar;
// 1. For every character in the string
foreach( i, ch_i; expStr ){
// 2. Fetch character
c = ch_i;
// 3. Either add char to present token or create a new one
// A. Case Open Paren
if ( find_reserved( c.to!string() ) == "open_parn" ){ stow_char(); }
// B. Case Close Paren
else if( find_reserved( c.to!string() ) == "clos_parn" ){
if( token.length > 0 ){ stow_token(); }
stow_char();
}
// C. Case separator
else if( (testWhite && is_white(c)) || (c == sepChar)){
if(token.length > 0){ stow_token(); }
// D. Case any other char
}else{ cache_char(); }
}
// N. Return the vector of tokens
return tokens;
}
////////// ENVIRONMENT /////////////////////////////////////////////////////////////////////////////
struct Env{
Env* /*----*/ parent = null; // Pointer to the environment that contains this one
Atom*[] /*-*/ freeVars; // ---- Free variables, without binding
Atom*[string] boundVars; // --- Bound variables, have names given to them by statements
}
void bind_atom( Env* env, string name, Atom* atom ){
// Bind an `atom` to a `name` by adding it to the mapping, If the name already exists, it will be updated
env.boundVars[ name ] = atom;
}
bool p_binding_exists( Env* env, string name ){
// Return T if the binding exists in the `boundVars` of `env`, otherwise return F
if((name in env.boundVars) is null){
if( env.parent == null )
return false;
else
return p_binding_exists( env.parent, name );
}else
return true;
}
Atom* get_bound_atom( Env* env, string name ){
// Return the atom bound to `name`, if it exists, Otherwise return an empty atom
if((name in env.boundVars) is null){
if( env.parent == null )
return empty_atom();
else
return get_bound_atom( env.parent, name );
}else
return env.boundVars[ name ];
}
Env* enclose( Env* parent, Atom* names, Atom* values ){
// Create a child `Env` of `parent`, then bind `values` to `names` in the child context
Env* rtnEnv = new Env();
rtnEnv.parent = parent;
string[] nams = flatten_string_list( names );
Atom*[] vals = flatten_atom_list( values );
if( nams.length == vals.length ){
foreach( i, nam; nams ){
bind_atom( rtnEnv, nam, vals[i] );
}
}
return rtnEnv;
}
Env* baseEnv; // Global context
void init_env(){
// Create the base environment that is parent of all contexts in the interpreter
baseEnv = new Env();
}
////////// INTERPRETATION && EXECUTION /////////////////////////////////////////////////////////////
///// Scheme --to-> D ////////////////////////////
double[] flatten_double_list( Atom* dbblList ){
// Take a LISP list of numbers and convert to a Dlang dyn. array
Atom* currCons = dbblList;
double[] rtnArr;
while( !p_empty( currCons ) ){
if( p_number( currCons.car ) ){ rtnArr ~= currCons.car.num; }
currCons = currCons.cdr;
}
return rtnArr;
}
string[] flatten_string_list( Atom* strnList ){
// Take a LISP list of strings and convert to a Dlang dyn. array
Atom* currCons = strnList;
string[] rtnArr;
while( !p_empty( currCons ) ){
if( p_string( currCons.car ) ){ rtnArr ~= currCons.car.str; }
currCons = currCons.cdr;
}
return rtnArr;
}
Atom*[] flatten_atom_list( Atom* atomList ){
// Take a LISP list of Atoms and convert to a Dlang dyn. array
Atom* currCons = atomList;
Atom*[] rtnArr;
while( !p_empty( currCons ) ){
rtnArr ~= currCons.car;
currCons = currCons.cdr;
}
return rtnArr;
}
Atom*[] flatten_cons_list( Atom* atomList ){
// Take a LISP list of Atoms and convert to a Dlang dyn. array
Atom* currCons = atomList;
Atom*[] rtnArr;
ulong depth = 0;
while( !p_empty( currCons ) ){
rtnArr ~= currCons.car;
currCons = currCons.cdr;
}
return rtnArr;
}
///// Primitives /////
Atom* function( Atom* )[string] primitiveFunctions; // Dictionary of foundational operations, implemented in Dlang
Atom* function()[string] /*--*/ primitiveSymbols; // - Dictionary of text aliases of important symbols
bool p_primitve_symbol( string token ){ return (token in primitiveSymbols) !is null; } // - In the primitive sym dict?
bool p_primitve_function( string token ){ return (token in primitiveFunctions) !is null; } // In the primitive func dict?
void init_primitives(){
/// Zero Arguments ///
primitiveSymbols["true"] = function Atom*(){ return make_bool(true); }; // Boolean True
primitiveSymbols["#t"] = function Atom*(){ return make_bool(true); }; // Boolean True
primitiveSymbols["false"] = function Atom*(){ return make_bool(false); }; // Boolean False
primitiveSymbols["#f"] = function Atom*(){ return make_bool(false); }; // Boolean False
/// One Argument ///
primitiveFunctions["atom?"] = function Atom*( Atom* args ){
// Predicate: Is this atom a literal?
// FIXME: CONVERT THIS FUNCTION TO HANDLE MANY ARGUMENTS WHERE ALL ATOMS TESTED
if( p_literal( first( args ) ) ){ return make_bool(true); }
else{ return make_bool(false); }
};
/// Many Arguments ///
primitiveFunctions["eq?"] = function Atom*( Atom* args ){
// Predicate: Are these atoms of the same type and value?
Atom*[] atoms = flatten_atom_list( args );
if( atoms.length > 1 ){
F_Type typ0 = atoms[0].kind;
foreach(Atom* atm; atoms[1..$]){ if(atm.kind != typ0){ return make_bool(false); } }
// NOTE: WOULD BE NICE TO USE A `Variant` HERE? (loops) (Algebraic?)
switch( typ0 ){
case F_Type.STRN:
string val0 = atoms[0].str;
foreach(Atom* atm; atoms[1..$]){ if(atm.str != val0){ return make_bool(false); } }
break;
case F_Type.NMBR:
double val0 = atoms[0].num;
foreach(Atom* atm; atoms[1..$]){ if(atm.num != val0){ return make_bool(false); } }
break;
case F_Type.BOOL:
bool val0 = atoms[0].bul;
foreach(Atom* atm; atoms[1..$]){ if(atm.bul != val0){ return make_bool(false); } }
break;
default:
return make_bool(false);
}
return make_bool(true);
}else{ return make_bool(false); }
};
primitiveFunctions["empty?"] = function Atom*( Atom* args ){
// Predicate: Is this an empty atom?
Atom*[] atoms = flatten_atom_list( args );
foreach(Atom* atm; atoms){ if( !p_empty( atm ) ){ return make_bool(false); } }
return make_bool(true);
};
primitiveFunctions["zero?"] = function Atom*( Atom* args ){
// Predicate: Is this a number atom wtih a zero value?
Atom*[] atoms = flatten_atom_list( args );
foreach(Atom* atm; atoms){ if( !p_zero( atm ) ){ return make_bool(false); } }
return make_bool(true);
};
primitiveFunctions["number?"] = function Atom*( Atom* args ){
// Predicate: Is this atom of number type?
Atom*[] atoms = flatten_atom_list( args );
foreach(Atom* atm; atoms){ if( !p_zero( atm ) ){ return make_bool(false); } }
return make_bool(true);
};
primitiveFunctions["+"] = function Atom*( Atom* args ){
// Add 1 or more number atoms
double[] ops = flatten_double_list( args );
return make_number( add( ops ) );
};
primitiveFunctions["-"] = function Atom*( Atom* args ){
// Negate 1 or subtract more number atoms
double[] ops = flatten_double_list( args );
return make_number( minus( ops ) );
};
primitiveFunctions["*"] = function Atom*( Atom* args ){
// Multiply 1 or more number atoms
double[] ops = flatten_double_list( args );
return make_number( multiply( ops ) );
};
primitiveFunctions["/"] = function Atom*( Atom* args ){
// Inverse 1 or divide more number atoms
double[] ops = flatten_double_list( args );
return make_number( divide( ops ) );
};
primitiveFunctions["1+"] = function Atom*( Atom* args ){
// Increment 1 or more number atoms by 1
Atom*[] ops = flatten_atom_list( args );
Atom* rtnLst;
if( ops.length > 1 ){
rtnLst = make_cons();
foreach(Atom* atm; ops){ if( p_number( atm ) ){ rtnLst = append( rtnLst, make_number( add1( atm.num ) ) ); } }
}else if( ops.length == 1 )
rtnLst = make_number( add1( ops[0].num ) );
return rtnLst;
};
primitiveFunctions["1-"] = function Atom*( Atom* args ){
// Decrement 1 or more number atoms by 1
Atom*[] ops = flatten_atom_list( args );
Atom* rtnLst;
if( ops.length > 1 ){
rtnLst = make_cons();
foreach(Atom* atm; ops){ if( p_number( atm ) ){ rtnLst = append( rtnLst, make_number( sub1( atm.num ) ) ); } }
}else if( ops.length == 1 )
rtnLst = make_number( sub1( ops[0].num ) );
return rtnLst;
};
primitiveFunctions["<"] = function Atom*( Atom* args ){
// Less Than, for 2 or more Number atoms
double[] ops = flatten_double_list( args );
return make_bool( lt( ops ) );
};
primitiveFunctions[">"] = function Atom*( Atom* args ){
// Greater Than, for 2 or more Number atoms
double[] ops = flatten_double_list( args );
return make_bool( gt( ops ) );
};
primitiveFunctions["<="] = function Atom*( Atom* args ){
// Less Than Or Equal To, for 2 or more Number atoms
double[] ops = flatten_double_list( args );
return make_bool( le( ops ) );
};
primitiveFunctions[">="] = function Atom*( Atom* args ){
// Greater Than Or Equal To, for 2 or more Number atoms
double[] ops = flatten_double_list( args );
return make_bool( ge( ops ) );
};
primitiveFunctions["cons"] = function Atom*( Atom* args ){
// Cons up to 2 atoms together into a pair. Missing params filled with `empty`
Atom*[] atoms = flatten_atom_list( args );
if( atoms.length >= 2 ) return make_cons( atoms[0], atoms[1] ); // Only takes 1st two args
if( atoms.length == 1 ) return make_cons( atoms[0] ); // --------- One arg is accepted, other empty
else /*--------------*/ return make_cons(); // ------------------- Two empty accepted
};
}
////////// PARSING /////////////////////////////////////////////////////////////////////////////////
///// Predicates /////
bool p_float_string( string inputStr ){
// Predicate: Is this string suitable to be converted to a (double) number?
string slimStr = strip( inputStr );
try{
slimStr.to!double();
return true;
}catch( ConvException e ){
if(_DEBUG_VERBOSE){ writeln( "CONVERSION ERROR: Cannot convert \"", slimStr, "\"" ); }
}
return false;
}
bool p_empty_atom_string( string inputStr ){
// Return T if the string is appropriate for empty atom conversion, otherwise return F
if( inputStr == null ){ /*------*/ return true; }
if( inputStr == "" ){ /*--------*/ return true; }
if( inputStr == "\xE2\xA7\x84" ){ return true; }
if( inputStr == "[/]" ){ /*-----*/ return true; }
return false;
}
///// Text Processing /////
Atom* atomize_string( string token ){
// Convert a string token into a non-cons atom
if( p_float_string( token ) ){ return make_number( token.to!double() ); }
if( p_empty_atom_string( token ) ){ return empty_atom(); }
if( p_primitve_symbol( token ) ){ return primitiveSymbols[ token ](); }
/* else assume string -------------*/ return make_string( token );
}
bool p_open_paren( string token ){ return find_reserved( token ) == "open_parn"; } // Is an open paren?
bool p_clos_paren( string token ){ return find_reserved( token ) == "clos_parn"; } // Is an close paren?
bool p_balanced_parens( string[] tokens ){
// Return true if parens open and close in a quantity and order that returns to root depth, Otherwise return false
int depth = 0;
foreach( string token; tokens ){
if( p_open_paren( token ) ) depth++;
else
if( p_clos_paren( token ) ) depth--;
}
return (depth == 0);
}
bool p_bounded_parens( string[] tokens ){
// Return true if the token sequence begins and ends with open and close parens, respectively
if( p_open_paren( tokens[0] ) ){
if( p_clos_paren( tokens[$-1] ) ) return true; else return false;
}else return false;
}
bool p_parent_parens( string[] tokens ){
// Return true if the outermost parens define a simple list
int depth = 0;
ulong i = 0;
ulong seqLen = tokens.length;
if( p_bounded_parens( tokens ) ){
foreach( string token; tokens ){
i++;
if( p_open_paren( token ) ) depth++;
else
if( p_clos_paren( token ) ) depth--;
if( (depth == 0) && (i<seqLen) ) return false;
}
return true;
}else return false;
}
Atom* consify_token_sequence( string[] tokens ){
// Recursively render tokens as a cons structure
// 2022-11: Rewritten
ulong seqLen = tokens.length;
ulong bgn;
ulong end;
int /**/ depth = 0;
ulong index;
string token;
string[] carPart;
string[] cdrPart;
Atom* lstRoot = null;
if( _DEBUG_VERBOSE ) writeln( "Input: " ~ tokens );
// Base Case: There were no tokens, return Empty
if( seqLen == 0 ){ return empty_atom(); }
// Base Case: There was one token, Return it
if( seqLen == 1 ){ return atomize_string( tokens[0] ); }
// Recursive Case: Multiple tokens, is at least a list
if( seqLen > 1 ){
// Establish bounds
bgn = 0;
end = seqLen-1;
// Are the parens correct?
if( p_balanced_parens( tokens ) ){
// Are we building a list?
if( p_parent_parens( tokens ) ){
bgn++;
end--;
// Begin list
index = bgn;
lstRoot = make_cons();
// While we are in the list bounds
while( index <= end ){
// Find an element
carPart = [];
// If element is a list
if( p_open_paren( tokens[index] ) ){
do{
token = tokens[index];
if( p_open_paren( token ) ) depth++;
else
if( p_clos_paren( token ) ) depth--;
carPart ~= token;
index++;
}while( (depth > 0) && (index <= end) );
// else element was atom
}else{
carPart ~= tokens[index];
index++;
}
// Append element to list
lstRoot = append(
lstRoot,
consify_token_sequence( carPart )
);
}
return lstRoot;
}else{
return make_error( F_Error.SYNTAX, "BAD LIST CASE" );
}
}else{
return make_error( F_Error.SYNTAX, "PARENTHESES MISMATCH" );
}
}
return make_error( F_Error.LEXER, "LEXER FAILED, This branch was ?UNREACHABLE?" );
}
Atom* expression_from_string( string expStr, dchar sepChar = ' ' ){
// Tokenize the `expStr`, express it as a nested cons struct, and return
string[] tokens = tokenize( expStr, sepChar );
// writeln( tokens );
return consify_token_sequence( tokens );
}
////////// SPECIAL FORMS ///////////////////////////////////////////////////////////////////////////
struct ExprInContext{
// Container struct for an expression and its context, Used to simultaneously return expression and context
Atom* expr;
Env* context;
string tag;
}
ExprInContext function( ExprInContext )[string] specialForms; // Dictionary of forms other than func applications, implemented in Dlang
bool truthiness( Atom* atm = null ){
// Determine the truth value of an atom
if( !(atm is null) ){
switch( atm.kind ){
case F_Type.STRN:
// A string is true if it has length
return (atm.str.length > 0);
case F_Type.BOOL:
// A Boolean is already a truth value
return atm.bul;
case F_Type.NMBR:
// A number is true if it above zero
return (atm.num > 0.0);
case F_Type.CONS:
// A cons is true if either side is non-empty
return (!p_empty(atm.car)) || (!p_empty(atm.cdr));
case F_Type.EROR:
// An error is always false
return false;
case F_Type.FUNC:
// A function is always true
return true;
default:
// This should not happen, return false
return false;
}
}
// No arg or null arg, returh false