view interps/underload/underload.c @ 11340:77399ae45cb1

<wob_jonas> slashlearn peace witch//Peace witches do alchemy: they turn mundane building material to gold. They\'re in the same universe where Bowser turned peaceful citizens of the Mushroom Kingdom to building material.
author HackBot
date Tue, 06 Feb 2018 23:37:00 +0000
parents 859f9b4339e6
children
line wrap: on
line source

#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <string.h>

#define CAT   -1  /* inner should be treated normally, except continue with
		    this el's next once it's evaluated */
#define INNER -2  /* inner is a quotation */
#define STACK -3  /* inner is a stack element */

typedef uint_fast8_t fint;

typedef struct tag_el {
  int_least32_t val;    /* character, or a special value if negative */
  size_t refcount;      /* reference count from inner, next, or globals */
  /* this counts the number of locations referencing, even if they're
     referenced multiple times themselves they only count as 1 */
  struct tag_el* next;  /* the element after this one */
  struct tag_el* inner; /* the element inside this one, if val is negative */
} el;

enum {unknown, underload, underlambda} lang = unknown;

int debuglevel = 0;

void serialize(el* e, FILE* f, fint stackdir);

void error(const char* errstr) {
  fprintf(stderr,"Error: %s\n",errstr);
  exit(EXIT_FAILURE); }

el* malloc_el(void) {
  el* x = malloc(sizeof (el));
  if(!x) error("Out of memory");
  return x; }
void checkfree(el* e) {
  if(!e) return;
  if(!(e->refcount)) {
    e->refcount=1;
    if(debuglevel >= 6) fprintf(stderr,"[free] ");
    if(debuglevel >= 6) serialize(e,stderr,1);
    if(debuglevel >= 6) fputc('\n',stderr);
    if(e->next) e->next->refcount--;
    checkfree(e->next);
    if(e->inner) e->inner->refcount--;
    checkfree(e->inner);
    free(e); }}

void uniputc(int_least32_t c, FILE* f) {
  if     (c <          0)
    error("Attempt to output a negative-coded character");
  if     (c <       0x80)
    putc(c,f);
  else if(c <      0x800) {
    putc(0xc0| (c>> 6),f);
    putc(0x80|( c     &0x3f),f); }
  else if(c <    0x10000) {
    putc(0xd0| (c>>12),f);
    putc(0x80|((c>> 6)&0x3f),f);
    putc(0x80| (c     &0x3f),f); }
  else if(c <   0x200000) {
    putc(0xf0| (c>>18),f);
    putc(0x80|((c>>12)&0x3f),f);
    putc(0x80|((c>> 6)&0x3f),f);
    putc(0x80|( c     &0x3f),f); }
  else if(c <  0x4000000) {
    putc(0xf8| (c>>24),f);
    putc(0x80|((c>>18)&0x3f),f);
    putc(0x80|((c>>12)&0x3f),f);
    putc(0x80|((c>> 6)&0x3f),f);
    putc(0x80|( c     &0x3f),f); }
  else {
    putc(0xfc| (c>>30),f);
    putc(0x80|((c>>24)&0x3f),f);
    putc(0x80|((c>>18)&0x3f),f);
    putc(0x80|((c>>12)&0x3f),f);
    putc(0x80|((c>> 6)&0x3f),f);
    putc(0x80|( c     &0x3f),f); }}
int_least32_t unigetc(FILE* f) {
  int_least32_t rv=0;
  int_least32_t min=0x40;
  fint count=0;
  int c=getc(f);
  unsigned char mask = 0xc0;
  if(c < 0x80) return c;
  while(c & mask) {count++; c &= ~mask; mask>>=1;}
  if(count < 1) error("UTF-8 input contains invalid first byte in character");
  if(count > 1) min <<= 1;
  rv = c;
  while(count--) {
    rv *= 1<<6; 
    min <<= 5;
    c = getc(f);
    if((c&0xc0) != 0x80)
      error("UTF-8 input contains invalid subsequent byte in character");
    rv += (c & 0x3f); }
  if(rv < min) error("UTF-8 character has a non-shortest representation");
  return rv; }

el* popstack(el** stack, const char* errmsg) {
  el* rv;
  el* temp;
  if(!*stack) error(errmsg);
  if((**stack).val != STACK) error("Internal error: Stack is not a stack");
  if((**stack).refcount != 1) error("Internal error: Overreferenced stack");
  rv = (**stack).inner;
  temp = (**stack).next;
  free(*stack);
  /* temp.refcount stays the same */
  *stack = temp;
  if(rv) rv->refcount--;
  return rv; }
void pushstack(el** stack, el* e) {
  el* temp = malloc_el();
  if(e) e->refcount++;
  temp->val = STACK;
  temp->refcount = 1;
  temp->inner = e;
  temp->next = *stack;
  /* *stack.refcount stays the same */
  *stack = temp; }

void serialize_underload(el* e, FILE* f, fint stackdir) {
serialise_tco: /* tail-call optimisation of this function by hand */;
  if(!e) return;
  if(debuglevel >= 6) fprintf(f,"[%lu]",(unsigned long)e->refcount);
  switch(e->val) {
  case STACK:
    if(!stackdir) {
      if(debuglevel >+ 6) fprintf(f,"[z]");
      if(e->inner) serialize_underload(e->inner,f,stackdir);
      break; }
    if(e->next) serialize_underload(e->next,f,stackdir);
    putc('{',f);
    if(e->inner) serialize_underload(e->inner,f,stackdir);
    fputs("} ",f);
    return;
  case INNER:
    putc('(',f);
    if(e->inner) serialize_underload(e->inner,f,stackdir);
    putc(')',f);
    break;
  case CAT:
    serialize_underload(e->inner,f,stackdir); break;
  default:
    uniputc(e->val,f); break; }
  if(debuglevel >= 5 && !e->refcount)
    error("Sanity check failed: This should be free by now");
  e=e->next; goto serialise_tco; }
void serialize(el* e, FILE* f, fint stackdir) {
  /* //tk: Efficient serialization for Underlambda */
  serialize_underload(e, f, stackdir); }

el  *stack;     /* the stack */
el  *ip;        /* current point in the program we're executing */
el  *zerostack; /* stack of places to go if we hit null next pointers */
int main(int argc, char **argv) {
  FILE* in;
  int usingstdin;
  el **working;
  el  *temp, *temp2, *temp3;
  int_least32_t c;
  if(!argc||!argv[0]) error("Could not determine executable name");
  char* a0 = argv[0];
  if(strrchr(argv[0],'/')) a0=strrchr(argv[0],'/')+1;
  if(!strcmp(a0,"derlo")) lang=underload;
  if(!strcmp(a0,"derla")) lang=underlambda;
  while(argc>=2) {
    if(!strcmp(argv[1],"-o")) {lang=underload; argv++; argc--;}
    else if(!strcmp(argv[1],"-a")) {lang=underlambda; argv++; argc--;}
    else if(!strncmp(argv[1],"-d",2)) {
      debuglevel=argv[1][2]-'0';
      if(debuglevel < 0 || debuglevel > 7) debuglevel = 0;
      argv++; argc--; }
    else break; }
  if(lang==unknown || argc > 2) {
    puts("Usage: derl (-o|-a) [inputfile]");
    puts("Options:");
    puts("    -o   Interpret input as Underload");
    puts("    -a   Interpret input as Underlambda");
    puts("    -d0  No debugging");
    puts("    -d1: enable warnings");
    puts("    -d2: free all used resources at program exit");
    puts("    -d3: show program and stack every step");
    puts("    -d5: enable internal sanity checks");
    puts("    -d6: debug memory allocation");
    puts("    -d7: debug internal interpreter program flow");
    puts("Instead of using the -o or -a option, you can invoke this program");
    puts("with the name 'derlo' or 'derla'. All -d options include all");
    puts("previous -d options except -d0. -d0 is the default.");
    return argc==1 ? 0 : EXIT_FAILURE; }
  if(argc==2) {
    in = fopen(argv[1],"rb");
    if(!in) {perror(argv[1]); return EXIT_FAILURE;}
  } else {in = stdin; usingstdin = 1;}
  stack = 0;
  ip = 0;
  zerostack = 0;
  working = &ip;
  while(((c=unigetc(in)))!=EOF) {
    if(c==')') {
      temp = popstack(&zerostack,"Unmatched )");
      working = &(temp->next);
      continue; }
    if(c=='(') {
      *working = malloc_el();
      (**working).refcount = 1;
      (**working).val = INNER;
      (**working).inner = 0;
      (**working).next = 0;
      pushstack(&zerostack,*working);
      working = &((**working).inner);
      continue; }
    /* //tk: string parsing for Underlambda */
    /* //tk: EOF parsing for Underlambda */
    *working = malloc_el();
    (**working).refcount = 1;
    (**working).val = c;
    (**working).inner = 0;
    (**working).next = 0;
    working = &((**working).next); }
  if(zerostack) error("Expected ) at end of input");

  while(ip || zerostack) {
    if(!ip) {
      if(debuglevel >= 7) fprintf(stderr,"Moving back in zerostack.\n");
      ip = popstack(&zerostack,"Internal error: Zerostack disappeared");
      if(ip) ip->refcount++;
      continue; }
    if(debuglevel >= 3) {
      putc('\t',stderr);
      serialize(stack,stderr,1);
      serialize(ip,stderr,0);
      serialize(zerostack,stderr,0);
      putc('\n',stderr); }
    switch(ip->val) {
    case STACK:
      error("Internal error: Stack element found in program");
    case CAT:
      /* not a command, but an optimisation to avoid duplicating things;
	 put ip->next on the zerostack so we can go back to it later, and
	 continue with ip->inner */
      if(debuglevel >= 7) fprintf(stderr,"Evaluating a lazy cat.\n");
      if(!ip->inner) break; /* continue with ip->next */
      if(debuglevel >= 7) fprintf(stderr,"The cat was nontrivial.\n");
      pushstack(&zerostack,ip->next);
      temp = ip->inner;
      ip->refcount--;
      temp->refcount++;
      checkfree(ip);
      ip = temp;
      continue;
    case INNER:
      if(debuglevel >= 7) fprintf(stderr,"Pushing to the stack.\n");
      if(ip->inner)
	pushstack(&stack,ip->inner);
      else
	pushstack(&stack,0);
      break;
    case '!':
      if(debuglevel >= 7) fprintf(stderr,"Popping the stack.\n");
      temp = popstack(&stack,"Stack underflow in !");
      checkfree(temp);
      break;
    case 'S':
      if(debuglevel >= 7) fprintf(stderr,"Serialising TOS.\n");
      temp = popstack(&stack,"Stack underflow in S");
      if(temp) temp->refcount++;
      serialize(temp,stdout,0);
      fflush(stdout);
      if(temp) temp->refcount--;
      if(debuglevel >= 3) putc('\n',stdout);
      checkfree(temp);
      break;
    case '~':
      if(debuglevel >= 7) fprintf(stderr,"Swapping stack elements.\n");
      temp  = popstack(&stack,"Stack underflow in ~");
      temp2 = popstack(&stack,"Stack underflow in ~");
      pushstack(&stack,temp);
      pushstack(&stack,temp2);
      break;
    case 'a':
      if(debuglevel >= 7) fprintf(stderr,"Wrapping the top stack element.\n");
      temp = malloc_el();
      temp->refcount = 0;
      temp->inner = popstack(&stack,"Stack underflow in a");
      if(temp->inner) temp->inner->refcount++;
      temp->val = INNER;
      temp->next = 0;
      pushstack(&stack,temp);
      break;
    case ':':
      if(debuglevel >= 7) fprintf(stderr,"Duplicating the top stack element.\n");
      temp = popstack(&stack,"Stack underflow in :");
      pushstack(&stack,temp);
      pushstack(&stack,temp);
      break;
    case '*':
      if(debuglevel >= 7) fprintf(stderr,"Catting two stack elements.\n");
      temp  = popstack(&stack,"Stack underflow in *");
      temp2 = popstack(&stack,"Stack underflow in *");
      /* The degenerate cases of catting a null string need to be handled */
      if(!temp ) {pushstack(&stack,temp2); break;}
      if(!temp2) {pushstack(&stack,temp ); break;}
      temp->refcount++; /* if temp and temp2 are aliased, this correctly
			   prevents the optimisation happening */
      if(temp2->refcount == 0) {
	/* Tailcat optimisation: if nothing else is using the stack
	   element we're catting to, just change its next pointer to do
	   the catting directly. */
	if(debuglevel >= 7) fprintf(stderr,"Trying tailcat optimisation.\n");
	temp3 = temp2;
	while(temp3->next) {
	  if(temp3->refcount > 1) goto cantoptimise_star;
	  temp3 = temp3->next; }
	if(temp3->refcount > 1) goto cantoptimise_star;
	if(debuglevel >= 7) fprintf(stderr,"Tailcat optimisation succeeded.\n");
	temp3->next = temp;
	temp3 = temp2; }
      else {
      cantoptimise_star: ;
	temp3 = malloc_el();
	temp3->refcount = 0;
	temp3->next = temp;
	temp3->inner = temp2;
	temp2->refcount++;
	temp3->val = CAT; }
      pushstack(&stack,temp3);
      break;
    case '^':
      if(debuglevel >= 7) fprintf(stderr,"Evalling the stack.\n");
      temp = popstack(&stack,"Stack underflow in ^");
      if(!temp) break; /* continue with ip->next */
      while(ip->refcount == 1 && !(ip->next) && zerostack) {
	/* Tailcall optimisation. */
	if(debuglevel >= 7) fprintf(stderr,"Tailcall optimisation.\n");
	ip->next = popstack(&zerostack,
			    "Internal error: Zerostack disappeared"); }
      if(ip->next) ip->next->refcount++;
      if(temp->refcount == 0) {
	/* Tailcat optimisation on the call stack; replace the return at
	   the end of the called code with a goto, as long as it isn't
	   used anywhere else */
	if(debuglevel >= 7) fprintf(stderr,"Trying tailcat optimisation.\n");
	temp3 = temp;
	while(temp3->next) {
	  if(temp3->refcount > 1) goto cantoptimise_caret;
	  temp3 = temp3->next; }
	if(temp3->refcount > 1) goto cantoptimise_caret;
	temp3->next = ip->next;
	if(debuglevel >= 7) fprintf(stderr,"Tailcat optimisation succeeded.\n");
	temp2 = temp; }
      else {
      cantoptimise_caret: ;
	temp2 = malloc_el();
	temp2->refcount = 0;
	temp2->next = ip->next;
	temp2->inner = temp;
	temp->refcount++;
	temp2->val = CAT; }
      ip->refcount--;
      if(temp2) temp2->refcount++;
      checkfree(ip);
      ip = temp2;
      continue;
    default:
      fprintf(stderr, "Attempt to execute unknown command %d\n",ip->val);
      return EXIT_FAILURE; }
    temp = ip->next;
    ip->refcount--;
    if(temp) temp->refcount++;
    checkfree(ip);
    ip = temp; }
  if(!usingstdin) fclose(in);
  if(debuglevel >= 1 && stack)
    fprintf(stderr, "Warning: Stack was not empty at program exit\n");
  if(debuglevel >= 3) {
    serialize(stack,stderr,1);
    putc('\n',stderr); }
  if(debuglevel >= 2)
    while(stack)
      checkfree(popstack(&stack,"Internal error: Stack has inconsistent size"));
  return 0; }