Monorepo for Aesthetic.Computer aesthetic.computer
at main 467 lines 12 kB view raw
1/* tinylisp-commented.c with NaN boxing by Robert A. van Engelen 2022 */ 2/* tinylisp.c but adorned with comments in an (overly) verbose C style */ 3 4#include <stdlib.h> 5#include <stdio.h> 6#include <string.h> 7 8/* we only need two types to implement a Lisp interpreter: 9 I unsigned integer (either 16 bit, 32 bit or 64 bit unsigned) 10 L Lisp expression (double with NaN boxing) 11 I variables and function parameters are named as follows: 12 i any unsigned integer, e.g. a NaN-boxed ordinal value 13 t a NaN-boxed tag 14 L variables and function parameters are named as follows: 15 x,y any Lisp expression 16 n number 17 t list 18 f function, a lambda closure or Lisp primitive 19 p pair, a cons of two Lisp expressions 20 e,d environment, a list of pairs, e.g. created with (define v x) 21 v the name of a variable (an atom) or a list of variables */ 22#define I unsigned 23#define L double 24 25/* T(x) returns the tag bits of a NaN-boxed Lisp expression x */ 26#define T(x) *(unsigned long long*)&x >> 48 27 28/* address of the atom heap is at the bottom of the cell stack */ 29#define A (char*)cell 30 31/* number of cells for the shared stack and atom heap, increase N as desired */ 32#define N 1024 33 34/* hp: top of the atom heap pointer, A+hp with hp=0 points to the first atom string in cell[] 35 sp: cell stack pointer, the stack starts at the top of cell[] with sp=N 36 safety invariant: hp <= sp<<3 */ 37I hp = 0, sp = N; 38 39/* atom, primitive, cons, closure and nil tags for NaN boxing */ 40I ATOM = 0x7ff8, PRIM = 0x7ff9, CONS = 0x7ffa, CLOS = 0x7ffb, NIL = 0x7ffc; 41 42/* cell[N] array of Lisp expressions, shared by the stack and atom heap */ 43L cell[N]; 44 45/* Lisp constant expressions () (nil), #t, ERR, and the global environment env */ 46L nil, tru, err, env; 47 48/* NaN-boxing specific functions: 49 box(t,i): returns a new NaN-boxed double with tag t and ordinal i 50 ord(x): returns the ordinal of the NaN-boxed double x 51 num(n): convert or check number n (does nothing, e.g. could check for NaN) 52 equ(x,y): returns nonzero if x equals y */ 53L box(I t, I i) { 54 L x; 55 *(unsigned long long*)&x = (unsigned long long)t << 48 | i; 56 return x; 57} 58 59I ord(L x) { 60 return *(unsigned long long*)&x; /* the return value is narrowed to 32 bit unsigned integer to remove the tag */ 61} 62 63L num(L n) { 64 return n; 65} 66 67I equ(L x, L y) { 68 return *(unsigned long long*)&x == *(unsigned long long*)&y; 69} 70 71/* interning of atom names (Lisp symbols), returns a unique NaN-boxed ATOM */ 72L atom(const char *s) { 73 I i = 0; 74 while (i < hp && strcmp(A+i, s)) /* search for a matching atom name on the heap */ 75 i += strlen(A+i)+1; 76 if (i == hp) { /* if not found */ 77 hp += strlen(strcpy(A+i, s))+1; /* allocate and add a new atom name to the heap */ 78 if (hp > sp<<3) /* abort when out of memory */ 79 abort(); 80 } 81 return box(ATOM, i); 82} 83 84/* construct pair (x . y) returns a NaN-boxed CONS */ 85L cons(L x, L y) { 86 cell[--sp] = x; /* push the car value x */ 87 cell[--sp] = y; /* push the cdr value y */ 88 if (hp > sp<<3) /* abort when out of memory */ 89 abort(); 90 return box(CONS, sp); 91} 92 93/* return the car of a pair or ERR if not a pair */ 94L car(L p) { 95 return (T(p) & ~(CONS^CLOS)) == CONS ? cell[ord(p)+1] : err; 96} 97 98/* return the cdr of a pair or ERR if not a pair */ 99L cdr(L p) { 100 return (T(p) & ~(CONS^CLOS)) == CONS ? cell[ord(p)] : err; 101} 102 103/* construct a pair to add to environment e, returns the list ((v . x) . e) */ 104L pair(L v, L x, L e) { 105 return cons(cons(v, x), e); 106} 107 108/* construct a lambda closure with variables v body x environment e, returns a NaN-boxed CLOS */ 109L closure(L v, L x, L e) { 110 return box(CLOS, ord(pair(v, x, equ(e, env) ? nil : e))); 111} 112 113/* look up a symbol v in environment e, return its value or ERR if not found */ 114L assoc(L v, L e) { 115 while (T(e) == CONS && !equ(v, car(car(e)))) 116 e = cdr(e); 117 return T(e) == CONS ? cdr(car(e)) : err; 118} 119 120/* not(x) is nonzero if x is the Lisp () empty list a.k.a. nil or false */ 121I not(L x) { 122 return T(x) == NIL; 123} 124 125/* let(x) is nonzero if x has more than one item, used by let* */ 126I let(L x) { 127 return !not(x) && !not(cdr(x)); 128} 129 130/* return a new list of evaluated Lisp expressions t in environment e */ 131L eval(L, L); 132L evlis(L t, L e) { 133 return T(t) == CONS ? cons(eval(car(t), e), evlis(cdr(t), e)) : T(t) == ATOM ? assoc(t,e) : nil; 134} 135 136/* Lisp primitives: 137 (eval x) return evaluated x (such as when x was quoted) 138 (quote x) special form, returns x unevaluated "as is" 139 (cons x y) construct pair (x . y) 140 (car p) car of pair p 141 (cdr p) cdr of pair p 142 (+ n1 n2 ... nk) sum of n1 to nk 143 (- n1 n2 ... nk) n1 minus sum of n2 to nk 144 (* n1 n2 ... nk) product of n1 to nk 145 (/ n1 n2 ... nk) n1 divided by the product of n2 to nk 146 (int n) integer part of n 147 (< n1 n2) #t if n1<n2, otherwise () 148 (eq? x y) #t if x equals y, otherwise () 149 (pair? x) #t if x is a non-empty list, a cons cell or closure 150 (or x1 x2 ... xk) first x that is not (), otherwise () 151 (and x1 x2 ... xk) last x if all x are not (), otherwise () 152 (not x) #t if x is (), otherwise () 153 (cond (x1 y1) 154 (x2 y2) 155 ... 156 (xk yk)) the first yi for which xi evaluates to non-() 157 (if x y z) if x is non-() then y else z 158 (let* (v1 x1) 159 (v2 x2) 160 ... 161 y) sequentially binds each variable v1 to xi to evaluate y 162 (lambda v x) construct a closure 163 (define v x) define a named value globally */ 164L f_eval(L t, L e) { 165 return eval(car(evlis(t, e)), e); 166} 167 168L f_quote(L t, L _) { 169 return car(t); 170} 171 172L f_cons(L t, L e) { 173 t = evlis(t, e); 174 return cons(car(t), car(cdr(t))); 175} 176 177L f_car(L t, L e) { 178 return car(car(evlis(t, e))); 179} 180 181L f_cdr(L t, L e) { 182 return cdr(car(evlis(t, e))); 183} 184 185L f_add(L t, L e) { 186 L n; 187 t = evlis(t, e); 188 n = car(t); 189 while (!not(t = cdr(t))) 190 n += car(t); 191 return num(n); 192} 193 194L f_sub(L t, L e) { 195 L n; 196 t = evlis(t, e); 197 n = car(t); 198 while (!not(t = cdr(t))) 199 n -= car(t); 200 return num(n); 201} 202 203L f_mul(L t, L e) { 204 L n; 205 t = evlis(t, e); 206 n = car(t); 207 while (!not(t = cdr(t))) 208 n *= car(t); 209 return num(n); 210} 211 212L f_div(L t, L e) { 213 L n; 214 t = evlis(t, e); 215 n = car(t); 216 while (!not(t = cdr(t))) 217 n /= car(t); 218 return num(n); 219} 220 221L f_int(L t, L e) { 222 L n = car(evlis(t, e)); 223 return n<1e16 && n>-1e16 ? (long long)n : n; 224} 225 226L f_lt(L t, L e) { 227 return t = evlis(t, e), car(t) - car(cdr(t)) < 0 ? tru : nil; 228} 229 230L f_eq(L t, L e) { 231 return t = evlis(t, e), equ(car(t), car(cdr(t))) ? tru : nil; 232} 233 234L f_pair(L t,L e) { 235 L x = car(evlis(t,e)); 236 return T(x) == CONS ? tru : nil; 237} 238 239L f_or(L t,L e) { 240 L x = nil; 241 while (!not(t) && not(x = eval(car(t),e))) 242 t = cdr(t); 243 return x; 244} 245 246L f_and(L t,L e) { 247 L x = tru; 248 while (!not(t) && !not(x = eval(car(t),e))) 249 t = cdr(t); 250 return x; 251} 252 253L f_not(L t, L e) { 254 return not(car(evlis(t, e))) ? tru : nil; 255} 256 257L f_cond(L t, L e) { 258 while (!not(t) && not(eval(car(car(t)), e))) 259 t = cdr(t); 260 return eval(car(cdr(car(t))), e); 261} 262 263L f_if(L t, L e) { 264 return eval(car(cdr(not(eval(car(t), e)) ? cdr(t) : t)), e); 265} 266 267L f_leta(L t, L e) { 268 for (; let(t); t = cdr(t)) 269 e = pair(car(car(t)), eval(car(cdr(car(t))), e), e); 270 return eval(car(t), e); 271} 272 273L f_lambda(L t, L e) { 274 return closure(car(t), car(cdr(t)), e); 275} 276 277L f_define(L t, L e) { 278 env = pair(car(t), eval(car(cdr(t)), e), env); 279 return car(t); 280} 281 282/* table of Lisp primitives, each has a name s and function pointer f */ 283struct { 284 const char *s; 285 L (*f)(L, L); 286} prim[] = { 287 {"eval", f_eval}, 288 {"quote", f_quote}, 289 {"cons", f_cons}, 290 {"car", f_car}, 291 {"cdr", f_cdr}, 292 {"+", f_add}, 293 {"-", f_sub}, 294 {"*", f_mul}, 295 {"/", f_div}, 296 {"int", f_int}, 297 {"<", f_lt}, 298 {"eq?", f_eq}, 299 {"pair?", f_pair}, 300 {"or", f_or}, 301 {"and", f_and}, 302 {"not", f_not}, 303 {"cond", f_cond}, 304 {"if", f_if}, 305 {"let*", f_leta}, 306 {"lambda", f_lambda}, 307 {"define", f_define}, 308 {0}}; 309 310/* create environment by extending e with variables v bound to values t */ 311L bind(L v, L t, L e) { 312 return not(v) ? e : 313 T(v) == CONS ? bind(cdr(v), cdr(t), pair(car(v), car(t), e)) : 314 pair(v, t, e); 315} 316 317/* apply closure f to arguments t in environemt e */ 318L reduce(L f, L t, L e) { 319 return eval(cdr(car(f)), bind(car(car(f)), evlis(t, e), not(cdr(f)) ? env : cdr(f))); 320} 321 322/* apply closure or primitive f to arguments t in environment e, or return ERR */ 323L apply(L f, L t, L e) { 324 return T(f) == PRIM ? prim[ord(f)].f(t, e) : 325 T(f) == CLOS ? reduce(f, t, e) : 326 err; 327} 328 329/* evaluate x and return its value in environment e */ 330L eval(L x, L e) { 331 return T(x) == ATOM ? assoc(x, e) : 332 T(x) == CONS ? apply(eval(car(x), e), cdr(x), e) : 333 x; 334} 335 336/* tokenization buffer and the next character that we are looking at */ 337char buf[40], see = ' '; 338 339/* advance to the next character */ 340void look() { 341 int c = getchar(); 342 see = c; 343 if (c == EOF) 344 exit(0); 345} 346 347/* return nonzero if we are looking at character c, ' ' means any white space */ 348I seeing(char c) { 349 return c == ' ' ? see > 0 && see <= c : see == c; 350} 351 352/* return the look ahead character from standard input, advance to the next */ 353char get() { 354 char c = see; 355 look(); 356 return c; 357} 358 359/* tokenize into buf[], return first character of buf[] */ 360char scan() { 361 I i = 0; 362 while (seeing(' ')) 363 look(); 364 if (seeing('(') || seeing(')') || seeing('\'')) 365 buf[i++] = get(); 366 else 367 do 368 buf[i++] = get(); 369 while (i < 39 && !seeing('(') && !seeing(')') && !seeing(' ')); 370 buf[i] = 0; 371 return *buf; 372} 373 374/* return the Lisp expression read from standard input */ 375L parse(); 376L Read() { 377 scan(); 378 return parse(); 379} 380 381/* return a parsed Lisp list */ 382L list() { 383 L x; 384 if (scan() == ')') 385 return nil; 386 if (!strcmp(buf, ".")) { 387 x = Read(); 388 scan(); 389 return x; 390 } 391 x = parse(); 392 return cons(x, list()); 393} 394 395/* return a parsed Lisp expression x quoted as (quote x) */ 396L quote() { 397 return cons(atom("quote"), cons(Read(), nil)); 398} 399 400/* return a parsed atomic Lisp expression (a number or an atom) */ 401L atomic() { 402 L n; I i; 403 return (sscanf(buf, "%lg%n", &n, &i) > 0 && !buf[i]) ? n : 404 atom(buf); 405} 406 407/* return a parsed Lisp expression */ 408L parse() { 409 return *buf == '(' ? list() : 410 *buf == '\'' ? quote() : 411 atomic(); 412} 413 414/* display a Lisp list t */ 415void print(L); 416void printlist(L t) { 417 for (putchar('('); ; putchar(' ')) { 418 print(car(t)); 419 t = cdr(t); 420 if (not(t)) 421 break; 422 if (T(t) != CONS) { 423 printf(" . "); 424 print(t); 425 break; 426 } 427 } 428 putchar(')'); 429} 430 431/* display a Lisp expression x */ 432void print(L x) { 433 if (T(x) == NIL) 434 printf("()"); 435 else if (T(x) == ATOM) 436 printf("%s", A+ord(x)); 437 else if (T(x) == PRIM) 438 printf("<%s>", prim[ord(x)].s); 439 else if (T(x) == CONS) 440 printlist(x); 441 else if (T(x) == CLOS) 442 printf("{%u}", ord(x)); 443 else 444 printf("%.10lg", x); 445} 446 447/* garbage collection removes temporary cells, keeps global environment */ 448void gc() { 449 sp = ord(env); 450} 451 452/* Lisp initialization and REPL */ 453int main() { 454 I i; 455 printf("tinylisp"); 456 nil = box(NIL, 0); 457 err = atom("ERR"); 458 tru = atom("#t"); 459 env = pair(tru, tru, nil); 460 for (i = 0; prim[i].s; ++i) 461 env = pair(atom(prim[i].s), box(PRIM, i), env); 462 while (1) { 463 printf("\n%u>", sp-hp/8); 464 print(eval(Read(), env)); 465 gc(); 466 } 467}