/* Unlambda interpreter */ /* Copyright 1999 Jacob L. Mandelson * This software may be distributed and modified without charge * for noncommercial recreational or educational purposes provided * that this copyright and statement is included in all copies. */ #include #include #include #include #include #include #include /* Macros */ #define dupatom(A) (((A)->refcount++, (A))) #define dupexpr(A) (((A)->refcount++, (A))) #define freeatom(A) (--(A)->refcount ? 0 : Freeatom((A))) #define freeexpr(A) (--(A)->refcount ? 0 : Freeexpr((A))) #define NextContext() ((CurrentContext+1 == \ CurrentContinuation.contextlist+CurrentContinuation.Ncontexts \ ? grow_context_list(&CurrentContinuation, &CurrentContext) \ : 0), CurrentContext+1) /* Take care using these macros */ #define PUSHCONTEXT CurrentContext++; continue; #define POPCONTEXT if (CurrentContext == CurrentContinuation.contextlist) \ { free(CurrentContext); return rv; } \ else { CurrentContext--; continue; } #define MKA(x) ((struct atom*)(x)) #define MKE(x) ((struct expr*)(x)) #define STRING(x) #x /* Make a #define */ #define MKDF(x) "#define " #x " " STRING(x) "\n" static char preamble [] = "#include \n" "#include \n" "#include \n" "#include \"strtail.h\"\n" "#define IN(x)\n" "#define output stdout\n" "#define atomI atom0002\n" "#define atomV atom0003\n" "#define zz ,\n" MKDF(dupatom(A)) MKDF(dupexpr(A)) MKDF(freeatom(A)) MKDF(freeexpr(A)) MKDF(NextContext()) MKDF(PUSHCONTEXT) MKDF(POPCONTEXT) MKDF(MKA(x)) MKDF(MKE(x)); #define IN(x) x /* Stuff that's only in the interpreter, not in * the compiler output. */ #define zz , /* Protect commas from the preprocessor */ long Nmalloc, Nfree; FILE *output; struct char_lock * input; void error(char []); struct atom; void unparseA(struct atom* a); static void *my_malloc(size_t size) { void *rv = malloc(size); Nmalloc++; if (!rv) error("Out of memory."); return rv; } static void *my_realloc(void *p, size_t size) { void *rv = realloc(p, size); if (!rv) error("Out of memory."); return rv; } #define malloc my_malloc #define realloc my_realloc #define free(x) ((Nfree++, free(x))) #define MKSC(x) #x ; x /* Make string and code */ char progbody[] = MKSC( /* Code common to the parser+interpreter and the * "compiled" (*cough*) interpreter. */ /* Data structures. */ /* Continuation is a list of contexts. */ struct continuation { IN(int tag;) long Ncontexts; struct context *contextlist; }; /* Contexts contain the "stage" of execution, and arguments to execute. * See comment below. */ struct context { enum Stage { Eval1 zz Eval2 zz Eval3 zz Apply1 zz S2a zz S2b zz D1a } stage; void *Arg1 zz *Arg2 zz *Int; }; /* The unlambda expression. * Expression is either an atom, or `FG where F, G are unlambda expressions. */ struct atom { IN(int tag;) long refcount; enum atomtype { K zz K1 zz S zz S1 zz S2 zz I zz V zz C zz C1 zz D zz D1 zz Print zz Exit zz Read zz Query zz Reprint } type; /* Arguments: K1, S1: One atom. D1: One expression. S2: Two atoms. */ /* Print: character. C1: continuation. */ union { char character; struct atom* oneatom; struct { struct atom *a1 zz *a2; } twoatom; struct expr* expression; struct continuation continuation; } d; }; struct expr { IN(int tag;) long refcount; enum { ATOM zz APPLY } type; void *arg1; /* struct atom* for ATOM, struct expr* for APPLY */ struct expr *e2; /* Not used if ATOM */ }; static int Freeexpr(struct expr *E); void error(char []); static void FreeContinuation(struct continuation , struct context *); static int Freeatom(struct atom *A) { switch (A->type) { case S: case K: case I: case V: case C: case D: case Print: case Exit: case Read: case Query: case Reprint: /* Nothing. A leaf. */ break; case S1: case K1: freeatom(A->d.oneatom); break; case S2: freeatom(A->d.twoatom.a1); freeatom(A->d.twoatom.a2); break; case D1: freeexpr(A->d.expression); break; case C1: FreeContinuation(A->d.continuation, A->d.continuation.contextlist+A->d.continuation.Ncontexts-2); /* Last saved context is contextlist[Ncontexts-2] because current * context wasn't saved when the continuation was saved. */ break; default: error("Unknown type to free!"); } free(A); return 0; } static int Freeexpr(struct expr *E) { if (E->type == ATOM) freeatom(MKA(E->arg1)); else { freeexpr(MKE(E->arg1)); freeexpr(E->e2); } free(E); return 0; } void error(char msg[]) { fprintf(stderr, "Error: %s\n", msg); exit(-1); } /* Context element: [ Stage, Arg1, Arg2, RV, Int ] Arg{1,2} are arguments Int is intermediate value RV is return value from last step. Not saved with continuatoin! Just variable for passing value "up" the stack. Stage is Eval1: Arg1 is e Eval2: Arg1 is e, RV is eval(e.e1) Eval3: Arg1 is e, Int is eval(e.e1), RV is eval(e.e2) Eval4: Arg1 is e, RV is apply(eval(e.e1), eval(e.e2)) Short-circuited to Apply1 Apply1: Arg1 is a1, Arg2 is a2 S2a: Arg1 is a1, Arg2 is a2, RV is apply(a1.a1, a2) S2b: Arg1 is a1, Arg2 is a2, RV is apply(a1.a2, a2), Int is apply(a1.a1, a2) S2c: RV is apply( apply(a1.a1, a2), apply(a1.a2, a2) ) Short-circuited to Apply1 D1a: Arg2 is a2, RV is eval(a1.e) D1b: RV is apply( eval(a1.e), a2) Short-circuited to Apply1 */ static int grow_context_list(struct continuation *CC, struct context **con) { long cidx = *con - CC->contextlist; CC->Ncontexts += CC->Ncontexts/2 + 128; CC->contextlist = realloc(CC->contextlist, CC->Ncontexts * sizeof(struct context) ); *con = CC->contextlist + cidx; return 0; } IN(static int Ncontinuations;) static int lastchar = EOF; static struct continuation SaveContinuation(struct continuation CC, struct context *cxt) { struct continuation rv; IN(rv.tag = ++Ncontinuations;) rv.Ncontexts = cxt - CC.contextlist + 1; rv.contextlist = malloc(rv.Ncontexts * sizeof(*rv.contextlist)); /* Save contexts up to (but not including) current context. * Don't save current context because it is consumed by making the * C call, and replaced by the argument when the continuation is resumed. */ while (--cxt >= CC.contextlist) { rv.contextlist[cxt - CC.contextlist] = *cxt; switch (cxt->stage) { case Eval3: MKA(cxt->Int)->refcount++; /* FALLTHROUGH */ case Eval2: MKE(cxt->Arg1)->refcount++; break; case S2b: MKA(cxt->Int)->refcount++; MKA(cxt->Arg1)->refcount++; break; case S2a: MKA(cxt->Arg1)->refcount++; /* FALLTHROUGH */ case D1a: MKA(cxt->Arg2)->refcount++; break; case Eval1: case Apply1: default: error("Unknown stage to SaveContinuation!"); } } return rv; } /* Free continuation, and contexts in continuation from * CC.contextlist[0] to ctx */ static void FreeContinuation(struct continuation CC, struct context *ctx) { for ( ; ctx >= CC.contextlist; ctx--) { switch (ctx->stage) { case Eval3: freeatom(MKA(ctx->Int)); /* FALLTHROUGH */ case Eval2: freeexpr(MKE(ctx->Arg1)); break; case S2b: freeatom(MKA(ctx->Int)); freeatom(MKA(ctx->Arg1)); break; case Apply1: case S2a: freeatom(MKA(ctx->Arg1)); /* FALLTHROUGH */ case D1a: freeatom(MKA(ctx->Arg2)); break; case Eval1: default: error("Bad stage to FreeContinuation!"); } } free(CC.contextlist); } /* Resuming a continuation must copy to the Current Continuation, not * just make a ref., because changes to the CC in the course of execution * should not affect copys of the continuation resumed. */ void RestoreContinuation(struct continuation cont, struct continuation *CC, struct context **cxt) { long i; CC->Ncontexts = cont.Ncontexts; CC->contextlist = malloc(cont.Ncontexts * sizeof(*CC->contextlist)); memcpy(CC->contextlist, cont.contextlist, cont.Ncontexts * sizeof(*CC->contextlist)); for (i = 0; i < cont.Ncontexts-1; i++) { /* Ncontexts-1 because Current Context wasn't saved with the continuation. */ switch (cont.contextlist[i].stage) { case Eval3: MKA(cont.contextlist[i].Int)->refcount++; /* FALLTHROUGH */ case Eval2: MKE(cont.contextlist[i].Arg1)->refcount++; break; case S2b: MKA(cont.contextlist[i].Int)->refcount++; MKA(cont.contextlist[i].Arg1)->refcount++; break; case S2a: MKA(cont.contextlist[i].Arg1)->refcount++; /* FALLTHROUGH */ case D1a: MKA(cont.contextlist[i].Arg2)->refcount++; break; case Eval1: case Apply1: default: error("Unknown stage to RestoreContinuation!"); } } *cxt = &CC->contextlist[cont.Ncontexts-1]; } /* Forward declerations */ extern struct atom atomV; extern struct atom atomI; /* Evaluate an expression. */ /* Maintains an explicit call stack, because it needs to save the contexts * of all the function calls when the continuation is saved. * The NextContext()->Arg1 = rv stuff is just setting up the Arguments * for the next context in the computation, a la passing arguments to * functions in Assembly. * For example, pasting the "function calls" together for S2 (the * most complicated case), gives: * rv = apply(dupatom(a1->d.twoatom.a1), dupatom(a2)); * rv = apply(rv, apply(dupatom(a1->d.twoatom.a2), a2)); * freeatom(a1); return rv; */ struct atom *eval(struct expr *e) { struct atom *rv; struct continuation CurrentContinuation; struct context *CurrentContext; struct context *nc; /* Next Context */ IN(CurrentContinuation.tag = 0;) CurrentContinuation.Ncontexts = 1024; CurrentContext = CurrentContinuation.contextlist = malloc(CurrentContinuation.Ncontexts * sizeof(struct context)); CurrentContext->Arg1 = e; CurrentContext->stage = Eval1; /* This state machine is the engine: It interprets the unlambda expression. */ for (;;) { switch (CurrentContext->stage) { case Eval1: e = CurrentContext->Arg1; if (e->type == ATOM) { rv = e->arg1; --e->refcount ? rv->refcount++ : free(e); POPCONTEXT } else /* APPLY */ { CurrentContext->stage = Eval2; (nc=NextContext())->stage = Eval1; nc->Arg1 = dupexpr(MKE(e->arg1)); PUSHCONTEXT } case Eval2: { struct atom *a1 = rv; e = CurrentContext->Arg1; if (a1->type == D) /* Delay */ { rv = malloc(sizeof *rv); rv->refcount = 1; rv->type = D1; rv->d.expression = e->e2; if (!--e->refcount) { freeexpr(MKE(e->arg1)); free(e); } else { e->e2->refcount++; } if (!--a1->refcount) free(a1); POPCONTEXT } else { CurrentContext->stage = Eval3; CurrentContext->Int = a1; (nc=NextContext())->stage = Eval1; nc->Arg1 = dupexpr(e->e2); PUSHCONTEXT } } case Eval3: freeexpr(MKE(CurrentContext->Arg1)); CurrentContext->stage = Apply1; CurrentContext->Arg2 = rv; CurrentContext->Arg1 = CurrentContext->Int; continue; case Apply1: { struct atom *a1 = CurrentContext->Arg1 zz *a2 = CurrentContext->Arg2; switch (a1->type) { case K: rv = malloc(sizeof *rv); rv->refcount = 1; rv->type = K1; rv->d.oneatom = a2; break; case K1: rv = a1->d.oneatom; freeatom(a2); if (a1->refcount != 1) rv->refcount++; break; case S: rv = malloc(sizeof *rv); rv->refcount = 1; rv->type = S1; rv->d.oneatom = a2; break; case S1: rv = malloc(sizeof *rv); rv->refcount = 1; rv->type = S2; rv->d.twoatom.a1 = a1->d.oneatom; rv->d.twoatom.a2 = a2; if (a1->refcount != 1) a1->d.oneatom->refcount++; break; case S2: /* Can't check a1->refcount against 1, because one * of the apply's might save the continuation, making * a copy of a1. */ CurrentContext->stage = S2a; (nc=NextContext())->stage = Apply1; nc->Arg1 = dupatom(a1->d.twoatom.a1); nc->Arg2 = dupatom(a2); PUSHCONTEXT case D: /* If 'd' is used as an argument to something that * applies its argument to something (eg, 'c'), * then 'd' will be applied without "delay". * Fishy if you ask me, but the <``r`cd`.*`cd> * program relies on it. */ /* Treat like I */ case I: rv = a2; break; case V: freeatom(a2); rv = a1; POPCONTEXT case C: if (a2->type == V) { /* <`cv> = v, no continuation. */ rv = a2; break; } rv = malloc(sizeof *rv); rv->refcount = 1; rv->type = C1; rv->d.continuation = SaveContinuation(CurrentContinuation, CurrentContext); freeatom(a1); CurrentContext->stage = Apply1; CurrentContext->Arg1 = a2; CurrentContext->Arg2 = rv; continue; case C1: /* Current Continuation holds references to a1, a2, * so need to dup them before we free the CC. */ rv = dupatom(a2); a1->refcount++; FreeContinuation(CurrentContinuation, CurrentContext); RestoreContinuation(a1->d.continuation, &CurrentContinuation, &CurrentContext); /* Now we can free a1. */ freeatom(a1); POPCONTEXT case D1: CurrentContext->stage = D1a; (nc=NextContext())->stage = Eval1; nc->Arg1 = a1->d.expression; --a1->refcount ? a1->d.expression->refcount++ : free(a1); PUSHCONTEXT case Print: putc(a1->d.character, output); rv = a2; break; case Exit: exit(0); /* Exit program */ case Read: lastchar = uncons( input ); freeatom(a1); /* CurrentContext->stage = Apply1; */ CurrentContext->Arg1 = a2; if (lastchar == EOF) CurrentContext->Arg2 = &atomV; else CurrentContext->Arg2 = &atomI; MKA(CurrentContext->Arg2)->refcount++; continue; case Query: /* CurrentContext->stage = Apply1; */ CurrentContext->Arg1 = a2; if (lastchar == a1->d.character) CurrentContext->Arg2 = &atomI; else CurrentContext->Arg2 = &atomV; freeatom(a1); MKA(CurrentContext->Arg2)->refcount++; continue; case Reprint: freeatom(a1); /* CurrentContext->stage = Apply1; */ CurrentContext->Arg1 = a2; if (lastchar == EOF) { CurrentContext->Arg2 = &atomV; MKA(CurrentContext->Arg2)->refcount++; } else { CurrentContext->Arg2 = rv = malloc(sizeof *rv); rv->refcount = 1; rv->type = Print; rv->d.character = lastchar; } continue; default: error("Unexpected type to apply!"); } if (!--a1->refcount) free(a1); POPCONTEXT } case S2a: CurrentContext->Int = rv; CurrentContext->stage = S2b; (nc=NextContext())->stage = Apply1; nc->Arg1 = dupatom(MKA(CurrentContext->Arg1)->d.twoatom.a2); nc->Arg2 = CurrentContext->Arg2; PUSHCONTEXT case S2b: freeatom(MKA(CurrentContext->Arg1)); CurrentContext->stage = Apply1; CurrentContext->Arg1 = CurrentContext->Int; CurrentContext->Arg2 = rv; continue; case D1a: CurrentContext->stage = Apply1; CurrentContext->Arg1 = rv; /* CurrentContext->Arg2 = CurrentContext->Arg2; */ continue; default: error("Unknown stage in evaluate!"); } } } ) struct atom atomK = { 0, 1, K, {0}}; struct atom atomS = { 1, 1, S, {0}}; struct atom atomI = { 2, 1, I, {0}}; struct atom atomV = { 3, 1, V, {0}}; struct atom atomC = { 4, 1, C, {0}}; struct atom atomD = { 5, 1, D, {0}}; struct atom atomR = { 6, 1, Print, {'\n'}}; struct atom atomE = { 7, 1, Exit, {0}}; struct atom atomAT = { 8, 1, Read, {0}}; struct atom atomPIPE = { 9, 1, Reprint, {0}}; struct expr exprK = { 0, 1, ATOM, &atomK }; struct expr exprS = { 1, 1, ATOM, &atomS }; struct expr exprI = { 2, 1, ATOM, &atomI }; struct expr exprV = { 3, 1, ATOM, &atomV }; struct expr exprC = { 4, 1, ATOM, &atomC }; struct expr exprD = { 5, 1, ATOM, &atomD }; struct expr exprR = { 6, 1, ATOM, &atomR }; struct expr exprE = { 7, 1, ATOM, &atomE }; struct expr exprAT = { 8, 1, ATOM, &atomAT }; struct expr exprPIPE = { 9, 1, ATOM, &atomPIPE }; struct atom *predefatomlist[] = { &atomK, &atomS, &atomI, &atomV, &atomC, &atomD, &atomR, &atomE, &atomAT, &atomPIPE}; struct expr *predefexprlist[] = { &exprK, &exprS, &exprI, &exprV, &exprC, &exprD, &exprR, &exprE, &exprAT, &exprPIPE}; struct atom **atomlist; struct expr **exprlist; int atomlistsz, exprlistsz; #define N_PREDEF_ATOMS 10 static int Natoms = N_PREDEF_ATOMS; static int Nexpr = N_PREDEF_ATOMS; static void grow_exprlist() { exprlist = realloc(exprlist, (exprlistsz += exprlistsz / 2) * sizeof(*exprlist)); } static void grow_atomlist() { atomlist = realloc(atomlist, (atomlistsz += 100) * sizeof(*atomlist)); } struct expr *parse(FILE *file) { int ch; struct expr *rv; for (;;) { ch = getc(file); if (ch == EOF) error("Unexpected EOF."); if (isspace(ch)) continue; if (ch == '#') { for (;;) { ch = getc(file); if (ch == EOF) error("Unexpected EOF."); if (ch == '\n') break; } continue; } break; } switch (tolower(ch)) { case '`': { struct expr E; int i; E.arg1 = parse(file); E.e2 = parse(file); rv = malloc(sizeof *rv); rv->refcount = 0; rv->type = APPLY; MKE(rv->arg1 = E.arg1)->refcount++; (rv->e2 = E.e2)->refcount++; Nexpr++; return rv; } case 'k': return &exprK; case 's': return &exprS; case 'i': return &exprI; case 'v': return &exprV; case 'c': return &exprC; case 'd': return &exprD; case 'r': return &exprR; case 'e': return &exprE; case '@': return &exprAT; case '|': return &exprPIPE; case '.': case '?': { int i; enum atomtype type = (ch == '?') ? Query : Print; ch = getc(file); if (ch == EOF) error("Unexpected EOF"); rv = malloc(sizeof *rv); Nexpr++; rv->refcount = 0; rv->type = ATOM; rv->arg1 = malloc(sizeof (struct atom)); Natoms++; MKA(rv->arg1)->refcount = 1; MKA(rv->arg1)->type = type; MKA(rv->arg1)->d.character = ch; return rv; } default: error("Unexpected character"); return 0; } } int main(int argc, char *argv[]) { FILE *file; struct expr *toplevel; struct atom *ret; input = get_input( argc , argv ); file = stdin; output = stdout; toplevel = parse(file); toplevel->refcount++; ret = eval(toplevel); freeatom(ret); free( input ); return 0; }