summary refs log tree commit diff
path: root/compile.c
diff options
context:
space:
mode:
Diffstat (limited to 'compile.c')
-rw-r--r--compile.c480
1 files changed, 480 insertions, 0 deletions
diff --git a/compile.c b/compile.c
new file mode 100644
index 0000000..d488414
--- /dev/null
+++ b/compile.c
@@ -0,0 +1,480 @@
+/*
+ * Copyright (c) 2026 Nakidai Perumenei <nakidai at disroot dot org>
+ *
+ * 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 <stddef.h>
+#include <stdlib.h>
+#include <string.h>
+
+
+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, 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:
+	{
+		struct val shouldfree = nil;
+		struct var *var;
+
+		val = getval(VVAR, eval(node->noper.l, scope), scope, 0);
+		lscope = scope;
+		i = findvar(val.name, &lscope);
+		if (i > 0)
+			shouldfree = deref(val, scope);
+
+		r = deref(eval(node->noper.r, scope), scope);
+
+		/* it's fine to use scope here as it won't be used after */
+		assignvar(val.name, copyval(r), &scope);
+		freeval(shouldfree);
+		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:
+	{
+		struct val refl, refr;
+		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,
+			mod->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
+}