| @@ -0,0 +1,884 @@ |
| 1 | +/* |
| 2 | +** Copyright (c) 2019 rxi |
| 3 | +** |
| 4 | +** Permission is hereby granted, free of charge, to any person obtaining a copy |
| 5 | +** of this software and associated documentation files (the "Software"), to |
| 6 | +** deal in the Software without restriction, including without limitation the |
| 7 | +** rights to use, copy, modify, merge, publish, distribute, sublicense, and/or |
| 8 | +** sell copies of the Software, and to permit persons to whom the Software is |
| 9 | +** furnished to do so, subject to the following conditions: |
| 10 | +** |
| 11 | +** The above copyright notice and this permission notice shall be included in |
| 12 | +** all copies or substantial portions of the Software. |
| 13 | +** |
| 14 | +** THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR |
| 15 | +** IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, |
| 16 | +** FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE |
| 17 | +** AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER |
| 18 | +** LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING |
| 19 | +** FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS |
| 20 | +** IN THE SOFTWARE. |
| 21 | +*/ |
| 22 | + |
| 23 | +#include <string.h> |
| 24 | +#include "fe.h" |
| 25 | + |
| 26 | +#define unused(x) ( (void) (x) ) |
| 27 | +#define car(x) ( (x)->car.o ) |
| 28 | +#define cdr(x) ( (x)->cdr.o ) |
| 29 | +#define tag(x) ( (x)->car.c ) |
| 30 | +#define isnil(x) ( (x) == &nil ) |
| 31 | +#define type(x) ( tag(x) & 0x1 ? tag(x) >> 2 : FE_TPAIR ) |
| 32 | +#define settype(x,t) ( tag(x) = (t) << 2 | 1 ) |
| 33 | +#define number(x) ( (x)->cdr.n ) |
| 34 | +#define prim(x) ( (x)->cdr.c ) |
| 35 | +#define cfunc(x) ( (x)->cdr.f ) |
| 36 | +#define strbuf(x) ( &(x)->car.c + 1 ) |
| 37 | + |
| 38 | +#define STRBUFSIZE ( (int) sizeof(fe_Object*) - 1 ) |
| 39 | +#define GCMARKBIT ( 0x2 ) |
| 40 | +#define GCSTACKSIZE ( 256 ) |
| 41 | + |
| 42 | + |
| 43 | +enum { |
| 44 | + P_LET, P_SET, P_IF, P_FN, P_MAC, P_WHILE, P_QUOTE, P_AND, P_OR, P_DO, P_CONS, |
| 45 | + P_CAR, P_CDR, P_SETCAR, P_SETCDR, P_LIST, P_NOT, P_IS, P_ATOM, P_PRINT, P_LT, |
| 46 | + P_LTE, P_ADD, P_SUB, P_MUL, P_DIV, P_MAX |
| 47 | +}; |
| 48 | + |
| 49 | +static const char *primnames[] = { |
| 50 | + "let", "=", "if", "fn", "mac", "while", "quote", "and", "or", "do", "cons", |
| 51 | + "car", "cdr", "setcar", "setcdr", "list", "not", "is", "atom", "print", "<", |
| 52 | + "<=", "+", "-", "*", "/" |
| 53 | +}; |
| 54 | + |
| 55 | +static const char *typenames[] = { |
| 56 | + "pair", "free", "nil", "number", "symbol", "string", |
| 57 | + "func", "macro", "prim", "cfunc", "ptr" |
| 58 | +}; |
| 59 | + |
| 60 | +typedef union { fe_Object *o; fe_CFunc f; fe_Number n; char c; } Value; |
| 61 | + |
| 62 | +struct fe_Object { Value car, cdr; }; |
| 63 | + |
| 64 | +struct fe_Context { |
| 65 | + fe_Handlers handlers; |
| 66 | + fe_Object *gcstack[GCSTACKSIZE]; |
| 67 | + int gcstack_idx; |
| 68 | + fe_Object *objects; |
| 69 | + int object_count; |
| 70 | + fe_Object *calllist; |
| 71 | + fe_Object *freelist; |
| 72 | + fe_Object *symlist; |
| 73 | + fe_Object *t; |
| 74 | + int nextchr; |
| 75 | +}; |
| 76 | + |
| 77 | +static fe_Object nil = {{ (void*) (FE_TNIL << 2 | 1) }, { NULL }}; |
| 78 | + |
| 79 | + |
| 80 | +fe_Handlers* fe_handlers(fe_Context *ctx) { |
| 81 | + return &ctx->handlers; |
| 82 | +} |
| 83 | + |
| 84 | + |
| 85 | +void fe_error(fe_Context *ctx, const char *msg) { |
| 86 | + fe_Object *cl = ctx->calllist; |
| 87 | + /* reset context state */ |
| 88 | + ctx->calllist = &nil; |
| 89 | + /* do error handler */ |
| 90 | + if (ctx->handlers.error) { ctx->handlers.error(ctx, msg, cl); } |
| 91 | + /* error handler returned -- print error and traceback, exit */ |
| 92 | + fprintf(stderr, "error: %s\n", msg); |
| 93 | + for (; !isnil(cl); cl = cdr(cl)) { |
| 94 | + char buf[64]; |
| 95 | + fe_tostring(ctx, car(cl), buf, sizeof(buf)); |
| 96 | + fprintf(stderr, "=> %s\n", buf); |
| 97 | + } |
| 98 | + exit(EXIT_FAILURE); |
| 99 | +} |
| 100 | + |
| 101 | + |
| 102 | +fe_Object* fe_nextarg(fe_Context *ctx, fe_Object **arg) { |
| 103 | + fe_Object *a = *arg; |
| 104 | + if (type(a) != FE_TPAIR) { |
| 105 | + if (isnil(a)) { fe_error(ctx, "too few arguments"); } |
| 106 | + fe_error(ctx, "dotted pair in argument list"); |
| 107 | + } |
| 108 | + *arg = cdr(a); |
| 109 | + return car(a); |
| 110 | +} |
| 111 | + |
| 112 | + |
| 113 | +static fe_Object* checktype(fe_Context *ctx, fe_Object *obj, int type) { |
| 114 | + char buf[64]; |
| 115 | + if (type(obj) != type) { |
| 116 | + sprintf(buf, "expected %s, got %s", typenames[type], typenames[type(obj)]); |
| 117 | + fe_error(ctx, buf); |
| 118 | + } |
| 119 | + return obj; |
| 120 | +} |
| 121 | + |
| 122 | + |
| 123 | +int fe_type(fe_Context *ctx, fe_Object *obj) { |
| 124 | + unused(ctx); |
| 125 | + return type(obj); |
| 126 | +} |
| 127 | + |
| 128 | + |
| 129 | +int fe_isnil(fe_Context *ctx, fe_Object *obj) { |
| 130 | + unused(ctx); |
| 131 | + return isnil(obj); |
| 132 | +} |
| 133 | + |
| 134 | + |
| 135 | +void fe_pushgc(fe_Context *ctx, fe_Object *obj) { |
| 136 | + if (ctx->gcstack_idx == GCSTACKSIZE) { |
| 137 | + fe_error(ctx, "gc stack overflow"); |
| 138 | + } |
| 139 | + ctx->gcstack[ctx->gcstack_idx++] = obj; |
| 140 | +} |
| 141 | + |
| 142 | + |
| 143 | +void fe_restoregc(fe_Context *ctx, int idx) { |
| 144 | + ctx->gcstack_idx = idx; |
| 145 | +} |
| 146 | + |
| 147 | + |
| 148 | +int fe_savegc(fe_Context *ctx) { |
| 149 | + return ctx->gcstack_idx; |
| 150 | +} |
| 151 | + |
| 152 | + |
| 153 | +void fe_mark(fe_Context *ctx, fe_Object *obj) { |
| 154 | + fe_Object *car; |
| 155 | +begin: |
| 156 | + if (tag(obj) & GCMARKBIT) { return; } |
| 157 | + car = car(obj); /* store car before modifying it with GCMARKBIT */ |
| 158 | + tag(obj) |= GCMARKBIT; |
| 159 | + |
| 160 | + switch (type(obj)) { |
| 161 | + case FE_TPAIR: |
| 162 | + fe_mark(ctx, car); |
| 163 | + /* fall through */ |
| 164 | + case FE_TFUNC: case FE_TMACRO: case FE_TSYMBOL: case FE_TSTRING: |
| 165 | + obj = cdr(obj); |
| 166 | + goto begin; |
| 167 | + |
| 168 | + case FE_TPTR: |
| 169 | + if (ctx->handlers.mark) { ctx->handlers.mark(ctx, obj); } |
| 170 | + break; |
| 171 | + } |
| 172 | +} |
| 173 | + |
| 174 | + |
| 175 | +static void collectgarbage(fe_Context *ctx) { |
| 176 | + int i; |
| 177 | + /* mark */ |
| 178 | + for (i = 0; i < ctx->gcstack_idx; i++) { |
| 179 | + fe_mark(ctx, ctx->gcstack[i]); |
| 180 | + } |
| 181 | + fe_mark(ctx, ctx->symlist); |
| 182 | + /* sweep and unmark */ |
| 183 | + for (i = 0; i < ctx->object_count; i++) { |
| 184 | + fe_Object *obj = &ctx->objects[i]; |
| 185 | + if (type(obj) == FE_TFREE) { continue; } |
| 186 | + if (~tag(obj) & GCMARKBIT) { |
| 187 | + if (type(obj) == FE_TPTR && ctx->handlers.gc) { |
| 188 | + ctx->handlers.gc(ctx, obj); |
| 189 | + } |
| 190 | + settype(obj, FE_TFREE); |
| 191 | + cdr(obj) = ctx->freelist; |
| 192 | + ctx->freelist = obj; |
| 193 | + } else { |
| 194 | + tag(obj) &= ~GCMARKBIT; |
| 195 | + } |
| 196 | + } |
| 197 | +} |
| 198 | + |
| 199 | + |
| 200 | +static int equal(fe_Object *a, fe_Object *b) { |
| 201 | + if (a == b) { return 1; } |
| 202 | + if (type(a) != type(b)) { return 0; } |
| 203 | + if (type(a) == FE_TNUMBER) { return number(a) == number(b); } |
| 204 | + if (type(a) == FE_TSTRING) { |
| 205 | + for (; !isnil(a) && !isnil(b); a = cdr(a), b = cdr(b)) { |
| 206 | + if (car(a) != car(b)) { return 0; } |
| 207 | + } |
| 208 | + return isnil(a) && isnil(b); |
| 209 | + } |
| 210 | + return 0; |
| 211 | +} |
| 212 | + |
| 213 | + |
| 214 | +static int streq(fe_Object *obj, const char *str) { |
| 215 | + while (!isnil(obj)) { |
| 216 | + int i; |
| 217 | + for (i = 0; i < STRBUFSIZE; i++) { |
| 218 | + if (strbuf(obj)[i] != *str) { return 0; } |
| 219 | + if (*str) { str++; } |
| 220 | + } |
| 221 | + obj = cdr(obj); |
| 222 | + } |
| 223 | + return *str == '\0'; |
| 224 | +} |
| 225 | + |
| 226 | + |
| 227 | +static fe_Object* object(fe_Context *ctx) { |
| 228 | + fe_Object *obj; |
| 229 | + /* do gc if freelist has no more objects */ |
| 230 | + if (isnil(ctx->freelist)) { |
| 231 | + collectgarbage(ctx); |
| 232 | + if (isnil(ctx->freelist)) { fe_error(ctx, "out of memory"); } |
| 233 | + } |
| 234 | + /* get object from freelist and push to the gcstack */ |
| 235 | + obj = ctx->freelist; |
| 236 | + ctx->freelist = cdr(obj); |
| 237 | + fe_pushgc(ctx, obj); |
| 238 | + return obj; |
| 239 | +} |
| 240 | + |
| 241 | + |
| 242 | +fe_Object* fe_cons(fe_Context *ctx, fe_Object *car, fe_Object *cdr) { |
| 243 | + fe_Object *obj = object(ctx); |
| 244 | + car(obj) = car; |
| 245 | + cdr(obj) = cdr; |
| 246 | + return obj; |
| 247 | +} |
| 248 | + |
| 249 | + |
| 250 | +fe_Object* fe_bool(fe_Context *ctx, int b) { |
| 251 | + return b ? ctx->t : &nil; |
| 252 | +} |
| 253 | + |
| 254 | + |
| 255 | +fe_Object* fe_number(fe_Context *ctx, fe_Number n) { |
| 256 | + fe_Object *obj = object(ctx); |
| 257 | + settype(obj, FE_TNUMBER); |
| 258 | + number(obj) = n; |
| 259 | + return obj; |
| 260 | +} |
| 261 | + |
| 262 | + |
| 263 | +static fe_Object* buildstring(fe_Context *ctx, fe_Object *tail, int chr) { |
| 264 | + if (!tail || strbuf(tail)[STRBUFSIZE - 1] != '\0') { |
| 265 | + fe_Object *obj = fe_cons(ctx, NULL, &nil); |
| 266 | + settype(obj, FE_TSTRING); |
| 267 | + if (tail) { |
| 268 | + cdr(tail) = obj; |
| 269 | + ctx->gcstack_idx--; |
| 270 | + } |
| 271 | + tail = obj; |
| 272 | + } |
| 273 | + strbuf(tail)[strlen(strbuf(tail))] = chr; |
| 274 | + return tail; |
| 275 | +} |
| 276 | + |
| 277 | + |
| 278 | +fe_Object* fe_string(fe_Context *ctx, const char *str) { |
| 279 | + fe_Object *obj = buildstring(ctx, NULL, '\0'); |
| 280 | + fe_Object *tail = obj; |
| 281 | + while (*str) { |
| 282 | + tail = buildstring(ctx, tail, *str++); |
| 283 | + } |
| 284 | + return obj; |
| 285 | +} |
| 286 | + |
| 287 | + |
| 288 | +fe_Object* fe_symbol(fe_Context *ctx, const char *name) { |
| 289 | + fe_Object *obj; |
| 290 | + /* try to find in symlist */ |
| 291 | + for (obj = ctx->symlist; !isnil(obj); obj = cdr(obj)) { |
| 292 | + if (streq(car(cdr(car(obj))), name)) { |
| 293 | + return car(obj); |
| 294 | + } |
| 295 | + } |
| 296 | + /* create new object, push to symlist and return */ |
| 297 | + obj = object(ctx); |
| 298 | + settype(obj, FE_TSYMBOL); |
| 299 | + cdr(obj) = fe_cons(ctx, fe_string(ctx, name), &nil); |
| 300 | + ctx->symlist = fe_cons(ctx, obj, ctx->symlist); |
| 301 | + return obj; |
| 302 | +} |
| 303 | + |
| 304 | + |
| 305 | +fe_Object* fe_cfunc(fe_Context *ctx, fe_CFunc fn) { |
| 306 | + fe_Object *obj = object(ctx); |
| 307 | + settype(obj, FE_TCFUNC); |
| 308 | + cfunc(obj) = fn; |
| 309 | + return obj; |
| 310 | +} |
| 311 | + |
| 312 | + |
| 313 | +fe_Object* fe_ptr(fe_Context *ctx, void *ptr) { |
| 314 | + fe_Object *obj = object(ctx); |
| 315 | + settype(obj, FE_TPTR); |
| 316 | + cdr(obj) = ptr; |
| 317 | + return obj; |
| 318 | +} |
| 319 | + |
| 320 | + |
| 321 | +fe_Object* fe_list(fe_Context *ctx, fe_Object **objs, int n) { |
| 322 | + fe_Object *res = &nil; |
| 323 | + while (n--) { |
| 324 | + res = fe_cons(ctx, objs[n], res); |
| 325 | + } |
| 326 | + return res; |
| 327 | +} |
| 328 | + |
| 329 | + |
| 330 | +fe_Object* fe_car(fe_Context *ctx, fe_Object *obj) { |
| 331 | + if (isnil(obj)) { return obj; } |
| 332 | + return car(checktype(ctx, obj, FE_TPAIR)); |
| 333 | +} |
| 334 | + |
| 335 | + |
| 336 | +fe_Object* fe_cdr(fe_Context *ctx, fe_Object *obj) { |
| 337 | + if (isnil(obj)) { return obj; } |
| 338 | + return cdr(checktype(ctx, obj, FE_TPAIR)); |
| 339 | +} |
| 340 | + |
| 341 | + |
| 342 | +static void writestr(fe_Context *ctx, fe_WriteFn fn, void *udata, const char *s) { |
| 343 | + while (*s) { fn(ctx, udata, *s++); } |
| 344 | +} |
| 345 | + |
| 346 | +void fe_write(fe_Context *ctx, fe_Object *obj, fe_WriteFn fn, void *udata, int qt) { |
| 347 | + char buf[32]; |
| 348 | + |
| 349 | + switch (type(obj)) { |
| 350 | + case FE_TNIL: |
| 351 | + writestr(ctx, fn, udata, "nil"); |
| 352 | + break; |
| 353 | + |
| 354 | + case FE_TNUMBER: |
| 355 | + sprintf(buf, "%.7g", number(obj)); |
| 356 | + writestr(ctx, fn, udata, buf); |
| 357 | + break; |
| 358 | + |
| 359 | + case FE_TPAIR: |
| 360 | + fn(ctx, udata, '('); |
| 361 | + for (;;) { |
| 362 | + fe_write(ctx, car(obj), fn, udata, 1); |
| 363 | + obj = cdr(obj); |
| 364 | + if (type(obj) != FE_TPAIR) { break; } |
| 365 | + fn(ctx, udata, ' '); |
| 366 | + } |
| 367 | + if (!isnil(obj)) { |
| 368 | + writestr(ctx, fn, udata, " . "); |
| 369 | + fe_write(ctx, obj, fn, udata, 1); |
| 370 | + } |
| 371 | + fn(ctx, udata, ')'); |
| 372 | + break; |
| 373 | + |
| 374 | + case FE_TSYMBOL: |
| 375 | + fe_write(ctx, car(cdr(obj)), fn, udata, 0); |
| 376 | + break; |
| 377 | + |
| 378 | + case FE_TSTRING: |
| 379 | + if (qt) { fn(ctx, udata, '"'); } |
| 380 | + while (!isnil(obj)) { |
| 381 | + int i; |
| 382 | + for (i = 0; i < STRBUFSIZE && strbuf(obj)[i]; i++) { |
| 383 | + if (qt && strbuf(obj)[i] == '"') { fn(ctx, udata, '\\'); } |
| 384 | + fn(ctx, udata, strbuf(obj)[i]); |
| 385 | + } |
| 386 | + obj = cdr(obj); |
| 387 | + } |
| 388 | + if (qt) { fn(ctx, udata, '"'); } |
| 389 | + break; |
| 390 | + |
| 391 | + default: |
| 392 | + sprintf(buf, "[%s %p]", typenames[type(obj)], (void*) obj); |
| 393 | + writestr(ctx, fn, udata, buf); |
| 394 | + break; |
| 395 | + } |
| 396 | +} |
| 397 | + |
| 398 | + |
| 399 | +static void writefp(fe_Context *ctx, void *udata, char chr) { |
| 400 | + unused(ctx); |
| 401 | + fputc(chr, udata); |
| 402 | +} |
| 403 | + |
| 404 | +void fe_writefp(fe_Context *ctx, fe_Object *obj, FILE *fp) { |
| 405 | + fe_write(ctx, obj, writefp, fp, 0); |
| 406 | +} |
| 407 | + |
| 408 | + |
| 409 | +typedef struct { char *p; int n; } CharPtrInt; |
| 410 | + |
| 411 | +static void writebuf(fe_Context *ctx, void *udata, char chr) { |
| 412 | + CharPtrInt *x = udata; |
| 413 | + unused(ctx); |
| 414 | + if (x->n) { *x->p++ = chr; x->n--; } |
| 415 | +} |
| 416 | + |
| 417 | +int fe_tostring(fe_Context *ctx, fe_Object *obj, char *dst, int size) { |
| 418 | + CharPtrInt x; |
| 419 | + x.p = dst; |
| 420 | + x.n = size - 1; |
| 421 | + fe_write(ctx, obj, writebuf, &x, 0); |
| 422 | + *x.p = '\0'; |
| 423 | + return size - x.n - 1; |
| 424 | +} |
| 425 | + |
| 426 | + |
| 427 | +fe_Number fe_tonumber(fe_Context *ctx, fe_Object *obj) { |
| 428 | + return number(checktype(ctx, obj, FE_TNUMBER)); |
| 429 | +} |
| 430 | + |
| 431 | + |
| 432 | +void* fe_toptr(fe_Context *ctx, fe_Object *obj) { |
| 433 | + return cdr(checktype(ctx, obj, FE_TPTR)); |
| 434 | +} |
| 435 | + |
| 436 | + |
| 437 | +static fe_Object* getbound(fe_Object *sym, fe_Object *env) { |
| 438 | + /* try to find in environment */ |
| 439 | + for (; !isnil(env); env = cdr(env)) { |
| 440 | + fe_Object *x = car(env); |
| 441 | + if (car(x) == sym) { return x; } |
| 442 | + } |
| 443 | + /* return global */ |
| 444 | + return cdr(sym); |
| 445 | +} |
| 446 | + |
| 447 | + |
| 448 | +void fe_set(fe_Context *ctx, fe_Object *sym, fe_Object *v) { |
| 449 | + unused(ctx); |
| 450 | + cdr(getbound(sym, &nil)) = v; |
| 451 | +} |
| 452 | + |
| 453 | + |
| 454 | +static fe_Object rparen; |
| 455 | + |
| 456 | +static fe_Object* read_(fe_Context *ctx, fe_ReadFn fn, void *udata) { |
| 457 | + const char *delimiter = " \n\t\r();"; |
| 458 | + fe_Object *v, *res, **tail; |
| 459 | + fe_Number n; |
| 460 | + int chr, gc; |
| 461 | + char buf[64], *p; |
| 462 | + |
| 463 | + /* get next character */ |
| 464 | + chr = ctx->nextchr ? ctx->nextchr : fn(ctx, udata); |
| 465 | + ctx->nextchr = '\0'; |
| 466 | + |
| 467 | + /* skip whitespace */ |
| 468 | + while (chr && strchr(" \n\t\r", chr)) { |
| 469 | + chr = fn(ctx, udata); |
| 470 | + } |
| 471 | + |
| 472 | + switch (chr) { |
| 473 | + case '\0': |
| 474 | + return NULL; |
| 475 | + |
| 476 | + case ';': |
| 477 | + while (chr && chr != '\n') { chr = fn(ctx, udata); } |
| 478 | + return read_(ctx, fn, udata); |
| 479 | + |
| 480 | + case ')': |
| 481 | + return &rparen; |
| 482 | + |
| 483 | + case '(': |
| 484 | + res = &nil; |
| 485 | + tail = &res; |
| 486 | + gc = fe_savegc(ctx); |
| 487 | + fe_pushgc(ctx, res); /* to cause error on too-deep nesting */ |
| 488 | + while ( (v = read_(ctx, fn, udata)) != &rparen ) { |
| 489 | + if (v == NULL) { fe_error(ctx, "unclosed list"); } |
| 490 | + if (type(v) == FE_TSYMBOL && streq(car(cdr(v)), ".")) { |
| 491 | + /* dotted pair */ |
| 492 | + *tail = fe_read(ctx, fn, udata); |
| 493 | + } else { |
| 494 | + /* proper pair */ |
| 495 | + *tail = fe_cons(ctx, v, &nil); |
| 496 | + tail = &cdr(*tail); |
| 497 | + } |
| 498 | + fe_restoregc(ctx, gc); |
| 499 | + fe_pushgc(ctx, res); |
| 500 | + } |
| 501 | + return res; |
| 502 | + |
| 503 | + case '\'': |
| 504 | + v = fe_read(ctx, fn, udata); |
| 505 | + if (!v) { fe_error(ctx, "stray '''"); } |
| 506 | + return fe_cons(ctx, fe_symbol(ctx, "quote"), fe_cons(ctx, v, &nil)); |
| 507 | + |
| 508 | + case '"': |
| 509 | + res = buildstring(ctx, NULL, '\0'); |
| 510 | + v = res; |
| 511 | + chr = fn(ctx, udata); |
| 512 | + while (chr != '"') { |
| 513 | + if (chr == '\0') { fe_error(ctx, "unclosed string"); } |
| 514 | + if (chr == '\\') { |
| 515 | + chr = fn(ctx, udata); |
| 516 | + if (strchr("nrt", chr)) { chr = strchr("n\nr\rt\t", chr)[1]; } |
| 517 | + } |
| 518 | + v = buildstring(ctx, v, chr); |
| 519 | + chr = fn(ctx, udata); |
| 520 | + } |
| 521 | + return res; |
| 522 | + |
| 523 | + default: |
| 524 | + p = buf; |
| 525 | + do { |
| 526 | + if (p == buf + sizeof(buf) - 1) { fe_error(ctx, "symbol too long"); } |
| 527 | + *p++ = chr; |
| 528 | + chr = fn(ctx, udata); |
| 529 | + } while (chr && !strchr(delimiter, chr)); |
| 530 | + *p = '\0'; |
| 531 | + ctx->nextchr = chr; |
| 532 | + n = strtod(buf, &p); /* try to read as number */ |
| 533 | + if (p != buf && strchr(delimiter, *p)) { return fe_number(ctx, n); } |
| 534 | + if (!strcmp(buf, "nil")) { return &nil; } |
| 535 | + return fe_symbol(ctx, buf); |
| 536 | + } |
| 537 | +} |
| 538 | + |
| 539 | + |
| 540 | +fe_Object* fe_read(fe_Context *ctx, fe_ReadFn fn, void *udata) { |
| 541 | + fe_Object* obj = read_(ctx, fn, udata); |
| 542 | + if (obj == &rparen) { fe_error(ctx, "stray ')'"); } |
| 543 | + return obj; |
| 544 | +} |
| 545 | + |
| 546 | + |
| 547 | +static char readfp(fe_Context *ctx, void *udata) { |
| 548 | + int chr; |
| 549 | + unused(ctx); |
| 550 | + return (chr = fgetc(udata)) == EOF ? '\0' : chr; |
| 551 | +} |
| 552 | + |
| 553 | +fe_Object* fe_readfp(fe_Context *ctx, FILE *fp) { |
| 554 | + return fe_read(ctx, readfp, fp); |
| 555 | +} |
| 556 | + |
| 557 | + |
| 558 | +static fe_Object* eval(fe_Context *ctx, fe_Object *obj, fe_Object *env, fe_Object **bind); |
| 559 | + |
| 560 | +static fe_Object* evallist(fe_Context *ctx, fe_Object *lst, fe_Object *env) { |
| 561 | + fe_Object *res = &nil; |
| 562 | + fe_Object **tail = &res; |
| 563 | + while (!isnil(lst)) { |
| 564 | + *tail = fe_cons(ctx, eval(ctx, fe_nextarg(ctx, &lst), env, NULL), &nil); |
| 565 | + tail = &cdr(*tail); |
| 566 | + } |
| 567 | + return res; |
| 568 | +} |
| 569 | + |
| 570 | + |
| 571 | +static fe_Object* dolist(fe_Context *ctx, fe_Object *lst, fe_Object *env) { |
| 572 | + fe_Object *res = &nil; |
| 573 | + int save = fe_savegc(ctx); |
| 574 | + while (!isnil(lst)) { |
| 575 | + fe_restoregc(ctx, save); |
| 576 | + fe_pushgc(ctx, env); |
| 577 | + res = eval(ctx, fe_nextarg(ctx, &lst), env, &env); |
| 578 | + } |
| 579 | + return res; |
| 580 | +} |
| 581 | + |
| 582 | + |
| 583 | +static fe_Object* argstoenv(fe_Context *ctx, fe_Object *prm, fe_Object *arg, fe_Object *env) { |
| 584 | + while (!isnil(prm)) { |
| 585 | + if (type(prm) != FE_TPAIR) { |
| 586 | + env = fe_cons(ctx, fe_cons(ctx, prm, arg), env); |
| 587 | + break; |
| 588 | + } |
| 589 | + env = fe_cons(ctx, fe_cons(ctx, car(prm), fe_car(ctx, arg)), env); |
| 590 | + prm = cdr(prm); |
| 591 | + arg = fe_cdr(ctx, arg); |
| 592 | + } |
| 593 | + return env; |
| 594 | +} |
| 595 | + |
| 596 | + |
| 597 | +#define evalarg() eval(ctx, fe_nextarg(ctx, &arg), env, NULL) |
| 598 | + |
| 599 | +#define arithop(op) { \ |
| 600 | + fe_Number x = fe_tonumber(ctx, evalarg()); \ |
| 601 | + while (!isnil(arg)) { \ |
| 602 | + x = x op fe_tonumber(ctx, evalarg()); \ |
| 603 | + } \ |
| 604 | + res = fe_number(ctx, x); \ |
| 605 | + } |
| 606 | + |
| 607 | +#define numcmpop(op) { \ |
| 608 | + va = checktype(ctx, evalarg(), FE_TNUMBER); \ |
| 609 | + vb = checktype(ctx, evalarg(), FE_TNUMBER); \ |
| 610 | + res = fe_bool(ctx, number(va) op number(vb)); \ |
| 611 | + } |
| 612 | + |
| 613 | + |
| 614 | +static fe_Object* eval(fe_Context *ctx, fe_Object *obj, fe_Object *env, fe_Object **newenv) { |
| 615 | + fe_Object *fn, *arg, *res; |
| 616 | + fe_Object cl, *va, *vb; |
| 617 | + int n, gc; |
| 618 | + |
| 619 | + switch (type(obj)) { |
| 620 | + case FE_TSYMBOL: return cdr(getbound(obj, env)); |
| 621 | + case FE_TPAIR: break; |
| 622 | + default: return obj; |
| 623 | + } |
| 624 | + |
| 625 | + car(&cl) = obj, cdr(&cl) = ctx->calllist; |
| 626 | + ctx->calllist = &cl; |
| 627 | + |
| 628 | + gc = fe_savegc(ctx); |
| 629 | + fn = eval(ctx, car(obj), env, NULL); |
| 630 | + arg = cdr(obj); |
| 631 | + res = &nil; |
| 632 | + |
| 633 | + switch (type(fn)) { |
| 634 | + case FE_TPRIM: |
| 635 | + switch (prim(fn)) { |
| 636 | + case P_LET: |
| 637 | + va = checktype(ctx, fe_nextarg(ctx, &arg), FE_TSYMBOL); |
| 638 | + if (newenv) { |
| 639 | + *newenv = fe_cons(ctx, fe_cons(ctx, va, evalarg()), env); |
| 640 | + } |
| 641 | + break; |
| 642 | + |
| 643 | + case P_SET: |
| 644 | + va = checktype(ctx, fe_nextarg(ctx, &arg), FE_TSYMBOL); |
| 645 | + cdr(getbound(va, env)) = evalarg(); |
| 646 | + break; |
| 647 | + |
| 648 | + case P_IF: |
| 649 | + while (!isnil(arg)) { |
| 650 | + va = evalarg(); |
| 651 | + if (!isnil(va)) { |
| 652 | + res = isnil(arg) ? va : evalarg(); |
| 653 | + break; |
| 654 | + } |
| 655 | + if (isnil(arg)) { break; } |
| 656 | + arg = cdr(arg); |
| 657 | + } |
| 658 | + break; |
| 659 | + |
| 660 | + case P_FN: case P_MAC: |
| 661 | + va = fe_cons(ctx, env, arg); |
| 662 | + fe_nextarg(ctx, &arg); |
| 663 | + res = object(ctx); |
| 664 | + settype(res, prim(fn) == P_FN ? FE_TFUNC : FE_TMACRO); |
| 665 | + cdr(res) = va; |
| 666 | + break; |
| 667 | + |
| 668 | + case P_WHILE: |
| 669 | + va = fe_nextarg(ctx, &arg); |
| 670 | + n = fe_savegc(ctx); |
| 671 | + while (!isnil(eval(ctx, va, env, NULL))) { |
| 672 | + dolist(ctx, arg, env); |
| 673 | + fe_restoregc(ctx, n); |
| 674 | + } |
| 675 | + break; |
| 676 | + |
| 677 | + case P_QUOTE: |
| 678 | + res = fe_nextarg(ctx, &arg); |
| 679 | + break; |
| 680 | + |
| 681 | + case P_AND: |
| 682 | + while (!isnil(arg) && !isnil(res = evalarg())); |
| 683 | + break; |
| 684 | + |
| 685 | + case P_OR: |
| 686 | + while (!isnil(arg) && isnil(res = evalarg())); |
| 687 | + break; |
| 688 | + |
| 689 | + case P_DO: |
| 690 | + res = dolist(ctx, arg, env); |
| 691 | + break; |
| 692 | + |
| 693 | + case P_CONS: |
| 694 | + va = evalarg(); |
| 695 | + res = fe_cons(ctx, va, evalarg()); |
| 696 | + break; |
| 697 | + |
| 698 | + case P_CAR: |
| 699 | + res = fe_car(ctx, evalarg()); |
| 700 | + break; |
| 701 | + |
| 702 | + case P_CDR: |
| 703 | + res = fe_cdr(ctx, evalarg()); |
| 704 | + break; |
| 705 | + |
| 706 | + case P_SETCAR: |
| 707 | + va = checktype(ctx, evalarg(), FE_TPAIR); |
| 708 | + car(va) = evalarg(); |
| 709 | + break; |
| 710 | + |
| 711 | + case P_SETCDR: |
| 712 | + va = checktype(ctx, evalarg(), FE_TPAIR); |
| 713 | + cdr(va) = evalarg(); |
| 714 | + break; |
| 715 | + |
| 716 | + case P_LIST: |
| 717 | + res = evallist(ctx, arg, env); |
| 718 | + break; |
| 719 | + |
| 720 | + case P_NOT: |
| 721 | + res = fe_bool(ctx, isnil(evalarg())); |
| 722 | + break; |
| 723 | + |
| 724 | + case P_IS: |
| 725 | + va = evalarg(); |
| 726 | + res = fe_bool(ctx, equal(va, evalarg())); |
| 727 | + break; |
| 728 | + |
| 729 | + case P_ATOM: |
| 730 | + res = fe_bool(ctx, fe_type(ctx, evalarg()) != FE_TPAIR); |
| 731 | + break; |
| 732 | + |
| 733 | + case P_PRINT: |
| 734 | + while (!isnil(arg)) { |
| 735 | + fe_writefp(ctx, evalarg(), stdout); |
| 736 | + if (!isnil(arg)) { printf(" "); } |
| 737 | + } |
| 738 | + printf("\n"); |
| 739 | + break; |
| 740 | + |
| 741 | + case P_LT: numcmpop(<); break; |
| 742 | + case P_LTE: numcmpop(<=); break; |
| 743 | + case P_ADD: arithop(+); break; |
| 744 | + case P_SUB: arithop(-); break; |
| 745 | + case P_MUL: arithop(*); break; |
| 746 | + case P_DIV: arithop(/); break; |
| 747 | + } |
| 748 | + break; |
| 749 | + |
| 750 | + case FE_TCFUNC: |
| 751 | + res = cfunc(fn)(ctx, evallist(ctx, arg, env)); |
| 752 | + break; |
| 753 | + |
| 754 | + case FE_TFUNC: |
| 755 | + arg = evallist(ctx, arg, env); |
| 756 | + va = cdr(fn); /* (env params ...) */ |
| 757 | + vb = cdr(va); /* (params ...) */ |
| 758 | + res = dolist(ctx, cdr(vb), argstoenv(ctx, car(vb), arg, car(va))); |
| 759 | + break; |
| 760 | + |
| 761 | + case FE_TMACRO: |
| 762 | + va = cdr(fn); /* (env params ...) */ |
| 763 | + vb = cdr(va); /* (params ...) */ |
| 764 | + /* replace caller object with code generated by macro and re-eval */ |
| 765 | + *obj = *dolist(ctx, cdr(vb), argstoenv(ctx, car(vb), arg, car(va))); |
| 766 | + fe_restoregc(ctx, gc); |
| 767 | + ctx->calllist = cdr(&cl); |
| 768 | + return eval(ctx, obj, env, NULL); |
| 769 | + |
| 770 | + default: |
| 771 | + fe_error(ctx, "tried to call non-callable value"); |
| 772 | + } |
| 773 | + |
| 774 | + fe_restoregc(ctx, gc); |
| 775 | + fe_pushgc(ctx, res); |
| 776 | + ctx->calllist = cdr(&cl); |
| 777 | + return res; |
| 778 | +} |
| 779 | + |
| 780 | + |
| 781 | +fe_Object* fe_eval(fe_Context *ctx, fe_Object *obj) { |
| 782 | + return eval(ctx, obj, &nil, NULL); |
| 783 | +} |
| 784 | + |
| 785 | + |
| 786 | +fe_Context* fe_open(void *ptr, int size) { |
| 787 | + int i, save; |
| 788 | + fe_Context *ctx; |
| 789 | + |
| 790 | + /* init context struct */ |
| 791 | + ctx = ptr; |
| 792 | + memset(ctx, 0, sizeof(fe_Context)); |
| 793 | + ptr = (char*) ptr + sizeof(fe_Context); |
| 794 | + size -= sizeof(fe_Context); |
| 795 | + |
| 796 | + /* make sure object memory region is 32bit aligned */ |
| 797 | + while ((size_t) ptr & 0x3) { ptr = (char*) ptr + 1; size--; } |
| 798 | + |
| 799 | + /* init objects memory region */ |
| 800 | + ctx->objects = (fe_Object*) ptr; |
| 801 | + ctx->object_count = size / sizeof(fe_Object); |
| 802 | + |
| 803 | + /* init lists */ |
| 804 | + ctx->calllist = &nil; |
| 805 | + ctx->freelist = &nil; |
| 806 | + ctx->symlist = &nil; |
| 807 | + |
| 808 | + /* populate freelist */ |
| 809 | + for (i = 0; i < ctx->object_count; i++) { |
| 810 | + fe_Object *obj = &ctx->objects[i]; |
| 811 | + settype(obj, FE_TFREE); |
| 812 | + cdr(obj) = ctx->freelist; |
| 813 | + ctx->freelist = obj; |
| 814 | + } |
| 815 | + |
| 816 | + /* init objects */ |
| 817 | + ctx->t = fe_symbol(ctx, "t"); |
| 818 | + fe_set(ctx, ctx->t, ctx->t); |
| 819 | + |
| 820 | + /* register built in primitives */ |
| 821 | + save = fe_savegc(ctx); |
| 822 | + for (i = 0; i < P_MAX; i++) { |
| 823 | + fe_Object *v = object(ctx); |
| 824 | + settype(v, FE_TPRIM); |
| 825 | + prim(v) = i; |
| 826 | + fe_set(ctx, fe_symbol(ctx, primnames[i]), v); |
| 827 | + fe_restoregc(ctx, save); |
| 828 | + } |
| 829 | + |
| 830 | + return ctx; |
| 831 | +} |
| 832 | + |
| 833 | + |
| 834 | +void fe_close(fe_Context *ctx) { |
| 835 | + /* clear gcstack and symlist; makes all objects unreachable */ |
| 836 | + ctx->gcstack_idx = 0; |
| 837 | + ctx->symlist = &nil; |
| 838 | + collectgarbage(ctx); |
| 839 | +} |
| 840 | + |
| 841 | + |
| 842 | +#ifdef FE_STANDALONE |
| 843 | + |
| 844 | +#include <setjmp.h> |
| 845 | + |
| 846 | +static jmp_buf toplevel; |
| 847 | +static char buf[64000]; |
| 848 | + |
| 849 | +static void onerror(fe_Context *ctx, const char *msg, fe_Object *cl) { |
| 850 | + unused(ctx), unused(cl); |
| 851 | + fprintf(stderr, "error: %s\n", msg); |
| 852 | + longjmp(toplevel, -1); |
| 853 | +} |
| 854 | + |
| 855 | + |
| 856 | +int main(int argc, char **argv) { |
| 857 | + int gc; |
| 858 | + fe_Object *obj; |
| 859 | + FILE *volatile fp = stdin; |
| 860 | + fe_Context *ctx = fe_open(buf, sizeof(buf)); |
| 861 | + |
| 862 | + /* init input file */ |
| 863 | + if (argc > 1) { |
| 864 | + fp = fopen(argv[1], "rb"); |
| 865 | + if (!fp) { fe_error(ctx, "could not open input file"); } |
| 866 | + } |
| 867 | + |
| 868 | + if (fp == stdin) { fe_handlers(ctx)->error = onerror; } |
| 869 | + gc = fe_savegc(ctx); |
| 870 | + setjmp(toplevel); |
| 871 | + |
| 872 | + /* re(p)l */ |
| 873 | + for (;;) { |
| 874 | + fe_restoregc(ctx, gc); |
| 875 | + if (fp == stdin) { printf("> "); } |
| 876 | + if (!(obj = fe_readfp(ctx, fp))) { break; } |
| 877 | + obj = fe_eval(ctx, obj); |
| 878 | + if (fp == stdin) { fe_writefp(ctx, obj, stdout); printf("\n"); } |
| 879 | + } |
| 880 | + |
| 881 | + return EXIT_SUCCESS; |
| 882 | +} |
| 883 | + |
| 884 | +#endif |