naive refcount gc sample

% ./fe cat.in
CMD a=3
CMD c(az)
CMD a=2
CMD c(ac)
CMD a=1
CMD c(ac)
CMD a=6
CMD d(az)
CMD a=5
CMD d(ad)
CMD a=4
CMD d(ad)
CMD e(dz)
CMD e(ce)
CMD e?
((1 2 3) (4 5 6))
CMD c"e
CMD c?
(cat ((1 2 3) (4 5 6)))
CMD a=5
CMD h^ac
CMD h?
(head 5 (cat ((1 2 3) (4 5 6))))
CMD -- fact (5!)
CMD pPh
CMD p?
(prod (head 5 (cat ((1 2 3) (4 5 6)))))
CMD p*
=> 120
% ./fe ex01.in
CMD a?
$
CMD a=0
CMD a?
0
CMD a(aa
CMD a?
(0 . 0)
% ./fe hi.in
CMD a=33
CMD c(az)
CMD a=105
CMD c(ac)
CMD a=72
CMD c(ac)
CMD c?
(72 105 33)
CMD c!
Hi!
% ./fe range.in
CMD -- fact (5!)
CMD a=1
CMD b=6
CMD r[ab]
CMD fPr
CMD f?
(prod [1 .. 6])
CMD f*
=> 120
CMD -- same as above
CMD a=2
CMD b=6
CMD r[ab]
CMD a=1
CMD r(ar)
CMD fPr
CMD f?
(prod (1 . [2 .. 6]))
CMD f*
=> 120
CMD -- chars
CMD a=0
CMD b=128
CMD r[ab]
CMD r?
[0 .. 128]
CMD r!
******************************** !"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~*
% ./fe smith.in
CMD -- from Seven Languages in Seven Weeks - chapter 6. Erlang
CMD a=32
CMD c(az)
CMD a=97
CMD c(ac)
CMD a=72
CMD c(ac)
CMD cCc
CMD a=8
CMD h^ac
CMD h!
Ha Ha Ha
% ./fe sp235.in
CMD a=5
CMD c(az)
CMD a=3
CMD c(ac)
CMD a=2
CMD c(ac)
CMD c?
(2 3 5)
CMD cCc
CMD c?
(cycle (2 3 5) | cur=(2 3 5))
CMD a=5
CMD h^ac
CMD h?
(head 5 (cycle (2 3 5) | cur=(2 3 5)))
CMD -- (+ 2 3 5 2 3)
CMD sSh
CMD s*
=> 15
CMD -- (* 2 3 5 2 3)
CMD pPh
CMD p*
=> 180
% ./fe sumtest.in
CMD a=1
CMD y@a
CMD a=3
CMD h^ay
CMD sSh
CMD ??
a: 3
h: (head 3 [1 ...])
s: (sum (head 3 [1 ...]))
y: [1 ...]
CMD s*
=> 3
% ./fe types.in
CMD z:t
z NIL (NIL)
CMD a=0
CMD a:t
a VAL (VAL)
CMD c(az)
CMD c:t
c CONS (CONS)
CMD b=128
CMD r[ab]
CMD r:t
r RANGE (CONS)
CMD sSr
CMD s:t
s SUM (VAL)
% cat cat.in
a=3
c(az)
a=2
c(ac)
a=1
c(ac)

a=6
d(az)
a=5
d(ad)
a=4
d(ad)

e(dz)
e(ce)
e?
c"e
c?

a=5
h^ac
h?

-- fact (5!)
pPh
p?
p*
% cat ex01.in
a?
a=0
a?
a(aa
a?
% cat hi.in
a=33
c(az)
a=105
c(ac)
a=72
c(ac)
c?
c!
% cat range.in
-- fact (5!)
a=1
b=6
r[ab]
fPr
f?
f*
-- same as above
a=2
b=6
r[ab]
a=1
r(ar)
fPr
f?
f*
-- chars
a=0
b=128
r[ab]
r?
r!
% cat smith.in
-- from Seven Languages in Seven Weeks - chapter 6. Erlang
a=32
c(az)
a=97
c(ac)
a=72
c(ac)
cCc
a=8
h^ac
h!
% cat sp235.in
a=5
c(az)
a=3
c(ac)
a=2
c(ac)
c?
cCc
c?
a=5
h^ac
h?
-- (+ 2 3 5 2 3)
sSh
s*
-- (* 2 3 5 2 3)
pPh
p*
% cat sumtest.in
a=1
y@a
a=3
h^ay
sSh
??
s*
% cat types.in
z:t
a=0
a:t
c(az)
c:t
b=128
r[ab]
r:t
sSr
s:t
% cat fe
{ echo n-; echo e--; cat "$@"; } | ./be
% cat be.c
/* backend */
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#define N_VARS 26

// debug options

int opt_show_note = 1;
int opt_show_debug;
int opt_echo_cmd;

#define err(fmt, x...) do { \
   printf("ERR " fmt "\n", ##x); } while (0)

#define note(fmt, x...) do { \
   if (opt_show_note) printf("NOTE " fmt "\n", ##x); } while (0)

#define debug(fmt, x...) do { \
   if (opt_show_debug) printf("   # " fmt "\n", ##x); \
   else putchar('\n'); } while (0)

#define echo_cmd(x) do { \
   printf("CMD %s\n", x); } while (0)

// end of debug options

// types

enum {
   NIL, VAL, CONS, // basic
   PROD, SUM, // reduce
   CAT, CYCLE, FIB, HEAD, MERGE, RANGE, YES, ZIP, // cons-like
};

#define type_name(x) [x] = #x
const char *type_names[] = {
   type_name(NIL),
   type_name(VAL),
   type_name(CONS),
   type_name(PROD),
   type_name(SUM),
   type_name(CAT),
   type_name(CYCLE),
   type_name(FIB),
   type_name(HEAD),
   type_name(MERGE),
   type_name(RANGE),
   type_name(YES),
   type_name(ZIP),
};

struct obj {
   int type, count;
   union {
      int val;
      struct { struct obj *l, *r; }; // cons cycle merge zip
      struct { int n; struct obj *o; }; // cat head prod sum yes
      struct { int a, b; }; // fib, range
   };
};

int
type(const struct obj * const v)
{
   return v ? v->type : NIL;
}

int
type_class(const struct obj * const v)
{
   switch (type(v)) {
   case NIL: return NIL;
   case PROD:
   case SUM:
   case VAL: return VAL;
   case CAT:
   case CYCLE:
   case FIB:
   case HEAD:
   case MERGE:
   case RANGE:
   case YES:
   case ZIP:
   case CONS: return CONS;
   }
}

// vars

struct obj *vars[N_VARS];
struct obj **gc_list;
int gc_len;

int
var(const int c)
{
   const int v = c - 'a';
   if (v < 0 || v >= N_VARS) {
      err("invalid var name.");
      return -1; }
   return v;
}

// show section

void
show_nil(void)
{
   putchar('$');
   debug("(nil)");
}

void show_cons_outer(const struct obj *v);

void
show_cons_inner(const struct obj * const v)
{
   switch (type(v)) {
   case NIL: putchar('$'); break;
   case VAL: printf("%d", v->val); break;
   case CONS: show_cons_outer(v);  break;
   case CAT: printf("(cat "); show_cons_inner(v->o); putchar(')'); break;
   case YES: putchar('['); show_cons_inner(v->o); printf(" ...]"); break;
   case RANGE: printf("[%d .. %d]", v->a, v->b); break;
   case FIB: printf("(%d %d ...)", v->a, v->b); break;
   case CYCLE: printf("(cycle "); show_cons_inner(v->l);
                   printf(" | cur="); show_cons_inner(v->r); putchar(')'); break;
   case SUM: printf("(sum "); show_cons_inner(v->o); putchar(')'); break;
   case PROD: printf("(prod "); show_cons_inner(v->o); putchar(')'); break;
   case HEAD: printf("(head %d ", v->n); show_cons_inner(v->o); putchar(')'); break;
   }
}

void
show_cons_outer(const struct obj *v)
{
   putchar('(');
   show_cons_inner(v->l);

   while (v = v->r) {
      putchar(' ');
      if (v->type != CONS) {
         printf(". ");
         show_cons_inner(v); break; }
      show_cons_inner(v->l); }

   putchar(')');
}

void
show_val(const struct obj * const v)
{
   show_cons_inner(v);
   debug("(%d %p)", v->count, v);
}

void
show_cons(const struct obj * const v)
{
   show_cons_inner(v);
   debug("(%d %p (%p %p))", v->count, v, v->l, v->r);
}

void
show_head(const struct obj * const v)
{
   show_cons_inner(v);
   debug("(%d %p (%p))", v->count, v, v->o);
}

void
show_obj(const struct obj * const v)
{
   switch (type(v)) {
   case NIL:  return show_nil();
   case VAL:  return show_val(v);
   case FIB:  return show_val(v);
   case RANGE: return show_val(v);
   case CAT:  return show_head(v);
   case YES:  return show_head(v);
   case SUM:  return show_head(v);
   case PROD: return show_head(v);
   case HEAD: return show_head(v);
   case CONS: return show_cons(v);
   case CYCLE: return show_cons(v);
   default: err("unknown data type."); }
}

void
show_all_vars(void)
{
   for (int i = 0; i < N_VARS; i++)
      if (vars[i]) {
         printf("%c: ", i + 'a');
         show_obj(vars[i]); }
}

void
cmd_show(const char x, const char * const y)
{
   if (x == '?') return show_all_vars();
   const int n = var(x); if (n < 0) return;
   const struct obj * const v = vars[n];
   show_obj(v);
}

// end of show section

// new, discard, update section

struct obj *
new_obj(const struct obj * const o)
{
   const size_t size = sizeof *o;
   struct obj * const n = malloc(size);
   if (!n) {
      err("runtime error - out of memory.");
      return NULL; }

   *n = *o;
   return n;
}

struct obj *
new_cons(struct obj *l, struct obj *r)
{
   const struct obj t = { .type = CONS, .l = l, .r = r, };
   struct obj * const o = new_obj(&t);
   if (!o) return o;

   if (l) l->count++;
   if (r) r->count++;
   return o;
}

struct obj *
new_val(const int n)
{
   const struct obj t = { .type = VAL, .val = n, };
   return new_obj(&t);
}

struct obj *
new_val_lit(const char * s)
{
   int n = 0;
   for (; *s; s++) {
      int c = *s - '0';
      if (c < 0 || c > 9) break;
      n = n * 10 + c; }
   return new_val(n);
}

// move an item into gc_list
void
discard(struct obj * const o)
{
   note("%p will be free'ed", o);
   const size_t size = (gc_len + 1) * sizeof *o;
   struct obj ** const t = realloc(gc_list, size);
   if (!t) {
      err("runtime error - out of memory.");
      return; }
   gc_list = t;
   gc_list[gc_len++] = o;

   // XXX Debug
   for (int i = 0; i < gc_len; i++)
      if (gc_list[i])
         note("gc_list %3d %p [%s]", i, gc_list[i],
               type_names[gc_list[i]->type]);
}

void
unref(struct obj * const p)
{
   if (p && p->count && !--p->count) discard(p);
}

void
update_var(const int n, struct obj * const o)
{
   if (o) o->count++;
   struct obj * const p = vars[n];
   unref(p);
   vars[n] = o;
}

// end of new, discard, update section

// car, cdr, and eval section

struct obj * car(struct obj * const v);
struct obj * cdr(struct obj * const v);
struct obj *
cycle_car(const struct obj * const o)
{
   return car(o->r);
}

struct obj *
cycle_cdr(const struct obj * const o)
{
   struct obj * const p = o->r;
   struct obj * const r = cdr(p) ?: o->l;
   const struct obj t = { .type = CYCLE, .l = o->l, .r = r, };
   return new_obj(&t);
}

struct obj *
cat_car(const struct obj * const o)
{
   return car(car(o->o));
}

struct obj *
cat_cdr(const struct obj * const o)
{
   struct obj * const p = o->o;
   struct obj * const q = car(p);
   struct obj * const r = cdr(p);

   struct obj * const s =
      cdr(q) ? new_cons(cdr(q), r): r && car(r) ? r: NULL;

   if (!s) return NULL;

   const struct obj t = { .type = CAT, .o = s, };
   return new_obj(&t);

#if 0
   if (cdr(q)) {
      struct obj * const new_p = new_cons(cdr(q), r);
      const struct obj t = { .type = CAT, .o = new_p, };
      return new_obj(&t); }

   if (r && car(r)) {
      struct obj * const new_p = r;
      const struct obj t = { .type = CAT, .o = new_p, };
      return new_obj(&t); }

   return NULL;
#endif
}

struct obj *
fib_car(const struct obj * const o)
{
   const struct obj t = { .type = VAL, .val = o->a, };
   return new_obj(&t);
}

struct obj *
fib_cdr(const struct obj * const o)
{
   const int a = o->b;
   const int b = o->a + o->b;
   const struct obj t = { .type = FIB, .a = a, .b = b, };
   return new_obj(&t);
}

#define range_car fib_car

struct obj *
range_cdr(const struct obj * const o)
{
   const int a = o->a + 1;
   const int b = o->b;
   if (a == b) return NULL;
   const struct obj t = { .type = RANGE, .a = a, .b = b, };
   return new_obj(&t);
}

struct obj *
head_cdr(const struct obj * const o)
{
   const int n = o->n;
   if (n - 1 <= 0) return NULL; // end

   struct obj * const r = cdr(o->o);
   if(type(r) == NIL) return NULL; // end of seq

   const struct obj t = { .type = HEAD, .n = n - 1, .o = r, };
   return new_obj(&t);
}

struct obj *
cxr(struct obj * const v, const int l)
{
   struct obj * o;
   switch (type(v)) {
   case CONS:
      o = l ? v->l : v->r; break;
   case CAT:
      o = l ? cat_car(v) : cat_cdr(v); break;
   case FIB:
      o = l ? fib_car(v) : fib_cdr(v); break;
   case RANGE:
      o = l ? range_car(v) : range_cdr(v); break;
   case HEAD:
      o = l ? car(v->o) : head_cdr(v); break;
   case CYCLE:
      o = l ? cycle_car(v) : cycle_cdr(v); break;
   case YES:
      o = l ? v->o : v;    break;
   default:
      err("invalid data type."); return NULL; }
   return o;
}

struct obj * car(struct obj * const v) { return cxr(v, 1); }
struct obj * cdr(struct obj * const v) { return cxr(v, 0); }

int
eval_prod(const struct obj *o)
{
   int r = 1;
   for (struct obj *i = o->o; i; i = cdr(i)) {
      if (type_class(i) != CONS) {
         err("prod: not a list"); return -999; }
      if (type_class(car(i)) != VAL) {
         err("prod: not a list of val"); return -999; }
      r *= car(i)->val; }
   return r;
}

int
eval_sum(const struct obj *o)
{
   int r = 0;
   for (struct obj *i = o->o; i; i = cdr(i)) {
      if (type_class(i) != CONS) {
         err("sum: not a list"); return -999; }
      if (type_class(car(i)) != VAL) {
         err("sum: not a list of val"); return -999; }
      r += car(i)->val; }
   return r;
}

int
eval(const struct obj *o)
{
   switch (type(o)) {
   case VAL:
      return o->val;
   case PROD:
      return eval_prod(o);
   case SUM:
      return eval_sum(o);
   default:
      err("unsupported type");
      return -999;
   }
}

// end of car, cdr, and eval section

// command section

void
cmd_cxr(const char x, const char * const y, const int a_or_d)
{
   const int n = var(x);  if (n < 0) return;
   const int m = var(*y); if (m < 0) return;
   const int l = a_or_d == 'a'; // car or cdr: true if car, false otherwise
   struct obj * const v = vars[m];
   if (type(v) == YES &&!l && n == m) return;
   struct obj * const o = cxr(v, l);
   update_var(n, o);
}

void
cmd_car(const char x, const char * const y)
{
   return cmd_cxr(x, y, 'a');
}

void
cmd_cdr(const char x, const char * const y)
{
   return cmd_cxr(x, y, 'd');
}

void
cmd_range(const char x, const char * const y)
{
   const int n = var(x);    if (n < 0) return;
   const int l = var(y[0]); if (l < 0) return;
   const int r = var(y[1]); if (r < 0) return;
   if (type_class(vars[l]) != VAL) { err("invalid range"); return; }
   if (type_class(vars[r]) != VAL) { err("invalid range"); return; }
   const int a = eval(vars[l]);
   const int b = eval(vars[r]);
   const struct obj t = { .type = RANGE, .a = a, .b = b, };
   struct obj * const o = new_obj(&t);
   if (!o) return;
   update_var(n, o);
}

void
cmd_fib(const char x, const char * const y)
{
   const int n = var(x);  if (n < 0) return;
   const struct obj t = { .type = FIB, .a = 0, .b = 1, };
   struct obj * const o = new_obj(&t); if (!o) return;
   update_var(n, o);
}

void
cmd_head(const char x, const char * const y)
{
   const int n = var(x);    if (n < 0) return;
   const int l = var(y[0]); if (l < 0) return;
   const int r = var(y[1]); if (r < 0) return;
   if (type(vars[l]) != VAL) {
      err("invalid data type."); return; }
   if (vars[l]->val < 1)
      return update_var(n, NULL);
   const struct obj t = { .type = HEAD, .n = vars[l]->val, .o = vars[r], };
   struct obj * const o = new_obj(&t); if (!o) return;

   if (t.o) t.o->count++;
   update_var(n, o);
}

void
cmd_cycle(const char x, const char * const y)
{
   const int n = var(x);  if (n < 0) return;
   const int v = var(*y); if (v < 0) return;
   if (type_class(vars[v]) != CONS) {
      err("invalid data type."); return; }
   const struct obj t = { .type = CYCLE, .l = vars[v], .r = vars[v], };
   struct obj * const o = new_obj(&t); if (!o) return;

   if (t.l) t.l->count++;
   update_var(n, o);
}

void
cmd_sum_yes(const char x, const char * const y, const int type)
{
   const int n = var(x);  if (n < 0) return;
   const int v = var(*y); if (v < 0) return;
   const struct obj t = { .type = type, .o = vars[v], };
   struct obj * const o = new_obj(&t); if (!o) return;
   if (t.o) t.o->count++;
   update_var(n, o);
}

void
cmd_yes(const char x, const char * const y)
{
   cmd_sum_yes(x, y, YES);
}

void
cmd_cat(const char x, const char * const y)
{
   cmd_sum_yes(x, y, CAT);
}

void
cmd_prod(const char x, const char * const y)
{
   cmd_sum_yes(x, y, PROD);
}

void
cmd_sum(const char x, const char * const y)
{
   cmd_sum_yes(x, y, SUM);
}

void
cmd_null(const char x, const char * const y)
{
   const int n = var(x);
   if (n < 0) return;

   update_var(n, NULL);
}

void
cmd_setv(const char x, const char * const y)
{
   const int n = var(x);
   if (n < 0) return;

   struct obj * o = NULL;
   switch (*y) {
   case '0'...'9': o = new_val_lit(y); break;
   case 'a'...'z': o = vars[var(*y)];  break;
   default: err("unknown data type."); return; }

   update_var(n, o);
}

void
cmd_cons(const char x, const char * const y)
{
   const int n = var(x);    if (n < 0) return;
   const int l = var(y[0]); if (l < 0) return;
   const int r = var(y[1]); if (r < 0) return;
   struct obj * const o = new_cons(vars[l], vars[r]);
   if (!o) return;

   update_var(n, o);
}

// special commands

void
cmd_eval(const char x, const char * const y)
{
   const int n = var(x); if (n < 0) return;
   const struct obj *o = vars[n];
   int val = eval(o);
   printf("=> %d\n", val);
}

void
cmd_str(const char x, const char * const y)
{
   const int n = var(x); if (n < 0) return;
   for (struct obj *i = vars[n]; i; i = cdr(i)) {
      if (type_class(i) != CONS) {
         err("str: not a list"); return; }
      if (type_class(car(i)) != VAL) {
         err("str: not a list of val"); return; }
      int val = eval(car(i));
      if (val >= ' ' && val < 0x7f)
         putchar(val);
      else
         putchar('*'); }
   putchar('\n');
}

void
cmd_gc(const char x, const char * const y)
{
   note("freeing %d items.", gc_len);

   // cannot use pointer as loop counter -- gc_list may move when discard.
   // also gc_len may grow when discard.
   for (int i = 0; i < gc_len; i++) {
      struct obj * const p = gc_list[i];
      if (!p) continue;
      note("freeing %p...", p);
      if (p->type == CONS) {
         unref(p->l);
         unref(p->r); }
      if (p->type == YES)
         unref(p->o);
      free(p); gc_list[i] = NULL; }
   note("%d items free'ed.", gc_len);

   free(gc_list); gc_list = NULL;
   gc_len = 0;
}

void
cmd_opt(const char x, const char * const y)
{
   switch (x) {
   case '-': break; // -- comment
   case 'e': opt_echo_cmd   = !!*y; break;
   case 'n': opt_show_note  = !!*y; break;
   case 'd': opt_show_debug = !!*y; break;
   default: err("unknown option"); }
}

void
cmd_cmd_type(const char x)
{
   const int n = var(x); if (n < 0) return;
   const int t = type(vars[n]);
   const int c = type_class(vars[n]);
   printf("%c %s (%s)\n", x, type_names[t], type_names[c]);
}

void
cmd_cmd(const char x, const char * const y)
{
   switch (*y) {
   case 't': cmd_cmd_type(x); break;
   default: err("unknown subcommand"); }
}

// end of command section

void
sub(const char *s)
{
   const char * const t = &s[2];

   switch (s[1]) {
   case '?': return cmd_show(*s, t);
   case '!': return cmd_str (*s, t);
   case '=': return cmd_setv(*s, t);
   case '$': return cmd_null(*s, t);
   case '(': return cmd_cons(*s, t);
   case '[': return cmd_range(*s, t);
   case '^': return cmd_head(*s, t);
   case '"': return cmd_cat (*s, t);
   case '@': return cmd_yes (*s, t);
   case 'C': return cmd_cycle(*s, t);
   case 'F': return cmd_fib (*s, t);
   case 'P': return cmd_prod(*s, t);
   case 'S': return cmd_sum (*s, t);
   case '<': return cmd_car (*s, t);
   case '>': return cmd_cdr (*s, t);
   case '*': return cmd_eval(*s, t);
   case '+': return cmd_gc  (*s, t);
   case '-': return cmd_opt (*s, t);
   case ':': return cmd_cmd (*s, t);
   }
   err("unknown command.");
}

// this chomp is evil ... delete everything after the first newline.
void
chomp(char *s)
{
   for (; *s; s++)
      if (*s == '\n') {
         *s = '\0';
         return; }
}

void
prompt(const char *s)
{
   if (isatty(STDIN_FILENO)) fputs(s, stderr);
}

int
main(void)
{
   char buf[80];

   while (prompt("* "), fgets(buf, sizeof buf, stdin)) {
      chomp(buf);
      if (!*buf) continue;
      if (opt_echo_cmd) echo_cmd(buf);
      sub(buf); }

   prompt("EXIT\n");
   return 0;
}