/* * Copyright (c) 2026 Nakidai Perumenei * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include "thac.h" #include #include #include struct val nil = {VVAR, {.name = "nil"}}; static struct var valnil = {"nil", {VNIL, {0}}}; struct scope basescope = {NULL, &valnil, 1}; static struct val zero = {VNUM, {0}}; static struct val rnum(struct node *node, struct scope *scope) { struct val val; val.type = VNUM; val.num = node->nnum; return val; } static struct val rvar(struct node *node, struct scope *scope) { struct val val; val.type = VVAR; val.name = node->nvar; return val; } static struct val roper(struct node *node, struct scope *scope) { struct val l, m, r, ref, refl, refr, val; struct scope *lscope; vlong i, j; val.type = VNUM; switch (node->noper.type) { #define BIN(NAME, OPER) break; case NAME: \ { \ l = getval(VNUM, eval(node->noper.l, scope), scope, 1); \ r = getval(VNUM, eval(node->noper.r, scope), scope, 1); \ val.num = l.num OPER r.num; \ } BIN(OSUM, +) BIN(OSUB, -) BIN(OMUL, *) BIN(ODIV, /) BIN(OMOD, %) BIN(OLSHIFT, <<) BIN(ORSHIFT, >>) BIN(OLESS, <) BIN(OLESSEQ, <=) BIN(OGREAT, >) BIN(OGREATEQ, >=) BIN(OEQ, ==) BIN(ONEQ, !=) BIN(OAND, &&) BIN(OOR, ||) BIN(OBOR, |) BIN(OBAND, &) BIN(OBXOR, ^) #undef BIN break; case OPOW: l = getval(VNUM, eval(node->noper.l, scope), scope, 1); r = getval(VNUM, eval(node->noper.r, scope), scope, 1); val.num = 1; for (i = 0; i < r.num; ++i) val.num *= l.num; #define UN(NAME, OPER) break; case NAME: \ { \ m = getval(VNUM, eval(node->noper.m, scope), scope, 1); \ val.num = OPER m.num; \ } UN(ONOT, !) UN(OINV, ~) #undef UN break; case OASSIGN: refl = getval(VVAR, eval(node->noper.l, scope), scope, 0); lscope = scope; i = findvar(refl.name, &lscope); l = i >= 0 ? deref(refl, scope) : nil; refr = eval(node->noper.r, scope); r = deref(refr, scope); /* it's fine to use scope here as it won't be used after */ assignvar(refl.name, copyval(r), &scope); freeval(l); if (refr.type == VVAR) freeval(r); #define ASS(NAME, OPER) break; case NAME: \ { \ ref = getval(VVAR, eval(node->noper.l, scope), scope, 0); \ l = getval(VNUM, ref, scope, 1); \ r = getval(VNUM, eval(node->noper.r, scope), scope, 1); \ val.num = l.num OPER r.num; \ assignvar(ref.name, val, &scope); \ val = ref; \ } ASS(OASSSUM, +) ASS(OASSSUB, -) ASS(OASSMUL, *) ASS(OASSDIV, /) ASS(OASSMOD, %) ASS(OASSBOR, |) ASS(OASSBAND, &) ASS(OASSBXOR, ^) ASS(OASSLSHIFT, <<) ASS(OASSRSHIFT, >>) #undef ASS #define POST(NAME, OPER) break; case NAME: \ { \ ref = getval(VVAR, eval(node->noper.m, scope), scope, 0); \ val = getval(VNUM, ref, scope, 1); \ m = val; \ OPER m.num; \ assignvar(ref.name, m, &scope); \ } POST(OPOSTDECR, --) POST(OPOSTINCR, ++) #undef POST #define PRE(NAME, OPER) break; case NAME: \ { \ ref = getval(VVAR, eval(node->noper.m, scope), scope, 0); \ val = getval(VNUM, ref, scope, 1); \ OPER val.num; \ assignvar(ref.name, val, &scope); \ } PRE(OPREDECR, --) PRE(OPREINCR, ++) #undef PRE break; case OCOND: l = getval(VNUM, eval(node->noper.l, scope), scope, 1); val = eval(l.num ? node->noper.m : node->noper.r, scope); break; case OARRAY: { char varname[GENVARLEN]; int c; if ((c = varnextchar(scope)) == -1) complain(1, "too nested array is wanted (27+)"); snprintf(varname, sizeof(varname), "@%c", c); i = assignvar(varname, zero, &scope); l = getval(VNUM, eval(node->noper.l, scope), scope, 1); val.type = VARR; val.arr = NULL; val.len = 0; for (j = 0; j < l.num; ++j, scope->vars[i].val.num = j) { r = deref(eval(node->noper.r, scope), scope); val.arr = realloc(val.arr, sizeof(*val.arr) * ++val.len); if (!val.arr) dieno(1, "realloc()"); val.arr[j] = copyval(r); } delvarscope(scope, i); } break; case OINDEX: ref = eval(node->noper.l, scope); l = getval(VARR, ref, scope, 1); r = getval(VNUM, eval(node->noper.r, scope), scope, 1); if (r.num < 0 || r.num >= l.len) complain(1, "duh that's oob (0<=%lld<%lld)", r.num, l.len); val = copyval(l.arr[r.num]); if (ref.type == VARR) freeval(ref); break; case OSLICE: ref = eval(node->noper.l, scope); l = getval(VARR, ref, scope, 1); m = getval(VNUM, eval(node->noper.m, scope), scope, 1); r = getval(VNUM, eval(node->noper.r, scope), scope, 1); m.num = min(l.len, max(0, m.num)); r.num = min(l.len, max(0, r.num)); val.type = VARR; val.len = r.num - m.num; val.arr = malloc(sizeof(*val.arr) * val.len); if (!val.arr) dieno(1, "malloc()"); for (i = m.num; i < r.num; ++i) val.arr[i - m.num] = copyval(l.arr[i]); if (ref.type == VARR) freeval(ref); break; case OCONCAT: refl = eval(node->noper.l, scope); l = getval(VARR, refl, scope, 1); refr = eval(node->noper.r, scope); r = getval(VARR, refr, scope, 1); val.type = VARR; val.len = l.len + r.len; val.arr = malloc(sizeof(*val.arr) * val.len); if (!val.arr) dieno(1, "malloc()"); for (i = 0; i < l.len; ++i) val.arr[i] = copyval(l.arr[i]); for (i = 0; i < r.len; ++i) val.arr[l.len + i] = copyval(r.arr[i]); if (refl.type == VARR) freeval(refl); if (refr.type == VARR) freeval(refr); break; case OLEN: m = getval(VARR, eval(node->noper.m, scope), scope, 1); val.num = m.len; break; case OASSERT: m = getval(VNUM, eval(node->noper.m, scope), scope, 1); if (!(val.num = m.num)) complain(1, "assertion has failed!"); break; case OCONNECT: val = connectval( eval(node->noper.l, scope), eval(node->noper.r, scope), scope ); break; default: die(1, "undone %s: %s", nodename(node->type), nopername(node->noper.type)); } return val; } static struct val rnode(struct node *node, struct scope *scope) { struct val prop, val; ulong i; val.type = VNUM; val.num = mkvert(); for (i = 0; i < node->nnode.len; ++i) { prop = getval(VNUM, eval(node->nnode.params[i], scope), scope, 1); addprop(val.num, prop.num); } return val; } static struct val rcomp(struct node *node, struct scope *scope) { struct scope lscope = {scope, NULL, 0}; struct val val = nil; ulong i; for (i = 0; i < node->ncomp.len; ++i) { val = eval(node->ncomp.stmts[i], &lscope); if (val.type == VBREAK || val.type == VCONT) break; } for (i = 0; i < lscope.len; ++i) freeval(lscope.vars[i].val); free(lscope.vars); return val; } static struct val rcond(struct node *node, struct scope *scope) { struct val val; val = getval(VNUM, eval(node->ncond.cond, scope), scope, 1); return eval(val.num ? node->ncond.t : node->ncond.f, scope); } static struct val rfor(struct node *node, struct scope *scope) { struct val val; eval(node->nfor.before, scope); loop: val = getval(VNUM, eval(node->nfor.cond, scope), scope, 1); if (!val.num) return nil; val = eval(node->nfor.code, scope); if (val.type == VBREAK) return nil; eval(node->nfor.between, scope); goto loop; } static struct val rforeachval(struct val arr, struct scope *scope, struct node *code) { char varname[GENVARLEN]; struct scope *tscope; ulong i, j, target; struct val val; snprintf(varname, sizeof(varname), "@%lu", varnextnum(scope)); i = assignvar(varname, zero, &scope); for (j = 0; j < arr.len; ++j, scope->vars[i].val.num = j) { if (arr.arr[j].type == VARR) { val = rforeachval(arr.arr[j], scope, code); } else { tscope = scope; target = assignvar("@", copyval(arr.arr[j]), &tscope); val = eval(code, scope); delvarscope(tscope, target); } if (val.type == VBREAK) return val; } delvarscope(scope, i); return nil; } static struct val rforeach(struct node *node, struct scope *scope) { struct val arr; arr = getval(VARR, eval(node->nforeach.obj, scope), scope, 1); rforeachval(arr, scope, node->nforeach.code); return nil; } static struct val rmod(struct node *node, struct scope *scope) { struct val val; val.type = VNUM; val.num = (vlong)node; return val; } /* * TODO: module doesn't know caller's environment, so it should not * depend on its scope, but rather make an own one at rmod() */ static struct val rwith(struct node *node, struct scope *scope) { struct scope argscope = {scope, NULL, 0}; struct scope modscope = {&argscope, NULL, 0}; struct node *mod; struct val val; ulong i; val = getval(VNUM, eval(node->nwith.mod, scope), scope, 1); mod = (struct node *)val.num; if (mod->nmod.len != node->nwith.len) complain( 1, "have %lu parameter%s, got %lu argument%s", mod->nmod.len, mod->nmod.len != 1 ? "s" : "", node->nwith.len, node->nwith.len != 1 ? "s" : "" ); argscope.len = mod->nmod.len; argscope.vars = malloc(sizeof(*argscope.vars) * argscope.len); if (!argscope.vars) dieno(1, "malloc()"); for (i = 0; i < argscope.len; ++i) { argscope.vars[i].name = mod->nmod.params[i]; argscope.vars[i].val = deref(eval(node->nwith.args[i], scope), scope); } for (i = 0; i < mod->nmod.code->ncomp.len; ++i) { val = eval(mod->nmod.code->ncomp.stmts[i], &modscope); switch (val.type) { case VBREAK: case VCONT: complain(1, "cannot %s out of module", valname(val.type)); break; default: break; } } eval(node->nwith.code, &modscope); for (i = 0; i < modscope.len; ++i) freeval(modscope.vars[i].val); free(modscope.vars); return nil; } static struct val rbreak(struct node *node, struct scope *scope) { struct val val; val.type = node->type == NBREAK ? VBREAK : VCONT; return val; } static struct val rerr(struct node *node, struct scope *scope) { struct val val; complain(1, "unknown node %s found", nodename(node->type)); /* not reached */ return nil; } struct val eval(struct node *node, struct scope *scope) { if (!node) return nil; #define is(t) (node->type == t) return (is(NCOMP) ? rcomp : is(NCOND) ? rcond : is(NFOR) ? rfor : is(NFOREACH) ? rforeach : is(NMOD) ? rmod : is(NNUM) ? rnum : is(NNODE) ? rnode : is(NOPER) ? roper : is(NWITH) ? rwith : is(NVAR) ? rvar : is(NBREAK) ? rbreak : is(NCONT) ? rbreak : rerr)(node, scope); #undef is }