diff options
| author | Nakidai <nakidai@disroot.org> | 2026-04-05 19:51:14 +0300 |
|---|---|---|
| committer | Nakidai <nakidai@disroot.org> | 2026-04-05 20:01:32 +0300 |
| commit | 8afa976e073c7bc29c878230eead6dddfdcc08a1 (patch) | |
| tree | 044b8ddf87b9a3c8b5a229b27e84e870468fb950 | |
| download | thac-8afa976e073c7bc29c878230eead6dddfdcc08a1.tar.gz thac-8afa976e073c7bc29c878230eead6dddfdcc08a1.zip | |
Add code v0.1.0
| -rw-r--r-- | .gitignore | 4 | ||||
| -rw-r--r-- | LICENSE | 13 | ||||
| -rw-r--r-- | Makefile | 20 | ||||
| -rw-r--r-- | README | 18 | ||||
| -rw-r--r-- | compile.c | 480 | ||||
| -rw-r--r-- | debug.c | 177 | ||||
| -rw-r--r-- | doc/thac.1 | 53 | ||||
| -rw-r--r-- | doc/thalatta.5 | 299 | ||||
| -rw-r--r-- | lex.c | 279 | ||||
| -rw-r--r-- | main.c | 61 | ||||
| -rw-r--r-- | parse.c | 637 | ||||
| -rw-r--r-- | say.c | 89 | ||||
| -rw-r--r-- | str.c | 33 | ||||
| -rw-r--r-- | thac.h | 323 | ||||
| -rw-r--r-- | val.c | 144 | ||||
| -rw-r--r-- | var.c | 112 | ||||
| -rw-r--r-- | vertex.c | 100 |
17 files changed, 2842 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..78cb2b8 --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +*.o +*.core +tags +thac diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..1e2943d --- /dev/null +++ b/LICENSE @@ -0,0 +1,13 @@ +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. diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..808887a --- /dev/null +++ b/Makefile @@ -0,0 +1,20 @@ +OBJS += compile.o +OBJS += debug.o +OBJS += lex.o +OBJS += main.o +OBJS += parse.o +OBJS += say.o +OBJS += str.o +OBJS += val.o +OBJS += var.o +OBJS += vertex.o + +all: thac + +thac: ${OBJS} + ${CC} ${LDFLAGS} -o $@ ${OBJS} ${LDLIBS} + +${OBJS}: thac.h + +clean: + rm -f thac ${OBJS} diff --git a/README b/README new file mode 100644 index 0000000..e27fcf9 --- /dev/null +++ b/README @@ -0,0 +1,18 @@ +Thalatta +======== +Thalatta is a small language for making graphs. This is a source code +of its implementation thac, that is a single-pass compiler of a graph, +interpreter of a language. + +Building +======== +You need to have +- An ANSI environment +- A C compiler with support of + - anonymous, unnamed strctures + - passing structures by value + +Then run either: +$ make +or, if you don't have make installed: +$ $CC *.c 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 +} diff --git a/debug.c b/debug.c new file mode 100644 index 0000000..ecb4f78 --- /dev/null +++ b/debug.c @@ -0,0 +1,177 @@ +/* + * 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. + */ +/* + * These functions are called for debugging if needed + */ + +#include "thac.h" + + +void walknode(struct node *x, ulong depth) +{ + int i; + + for (i = 0; i < depth; ++i) + fputs(" ", stderr); + fprintf(stderr, "%s", nodename(x->type)); + + switch (x->type) + { + break; case NOPER: + { + fprintf(stderr, ": %s\n", nopername(x->noper.type)); + switch (x->noper.type) + { + break; case OINDEX: + { + walknode(x->noper.l, depth + 1); + walknode(x->noper.m, depth + 1); + if (x->noper.r) + walknode(x->noper.r, depth + 1); + } + break; case OASSERT: case OLEN: case OINV: + case ONOT: case OPOSTDECR: case OPOSTINCR: + case OPREDECR: case OPREINCR: + { + walknode(x->noper.m, depth + 1); + } + break; case OCOND: + { + walknode(x->noper.l, depth + 1); + walknode(x->noper.m, depth + 1); + walknode(x->noper.r, depth + 1); + } + break; default: + { + walknode(x->noper.l, depth + 1); + walknode(x->noper.r, depth + 1); + } + } + } + break; case NNODE: + { + ulong i; + fputc('\n', stderr); + for (i = 0; i < x->nnode.len; ++i) + walknode(x->nnode.params[i], depth + 1); + } + break; case NCOMP: + { + ulong i; + fputc('\n', stderr); + for (i = 0; i < x->ncomp.len; ++i) + walknode(x->ncomp.stmts[i], depth + 1); + } + break; case NMOD: + { + ulong i; + fputs(": ", stderr); + for (i = 0; i < x->nmod.len; ++i) + fprintf(stderr, "%s ", x->nmod.params[i]); + fputc('\n', stderr); + walknode(x->nmod.code, depth + 1); + } + break; case NVAR: + { + fprintf(stderr, ": %s\n", x->nvar); + } + break; case NNUM: + { + fprintf(stderr, ": %lld\n", x->nnum); + } + break; case NFOR: + { + fputc('\n', stderr); + if (x->nfor.before) + walknode(x->nfor.before, depth + 1); + if (x->nfor.cond) + walknode(x->nfor.cond, depth + 1); + if (x->nfor.between) + walknode(x->nfor.between, depth + 1); + walknode(x->nfor.code, depth + 1); + } + break; case NFOREACH: + { + fputc('\n', stderr); + walknode(x->nforeach.obj, depth + 1); + walknode(x->nforeach.code, depth + 1); + } + break; case NCOND: + { + fputc('\n', stderr); + walknode(x->ncond.cond, depth + 1); + walknode(x->ncond.t, depth + 1); + if (x->ncond.f) + walknode(x->ncond.f, depth + 1); + } + break; case NWITH: + { + ulong i; + fputc('\n', stderr); + walknode(x->nwith.mod, depth + 1); + for (i = 0; i < x->nwith.len; ++i) + walknode(x->nwith.args[i], depth + 1); + walknode(x->nwith.code, depth + 1); + } + break; default: putc('\n', stderr); + } +} + +void +walkscope(struct scope *scope, ulong depth) +{ +#define printdepth() for (j = 0; j < depth; ++j) fputs(" ", stderr) + ulong i, j; + + printdepth(); + fprintf(stderr, "scope(%lu)=%p:\n", scope->len, scope); + ++depth; + if (scope->parent) + walkscope(scope->parent, depth); + + for (i = 0; i < scope->len; ++i) + { + printdepth(); + fprintf( + stderr, + "%s:%s=%lld\n", + scope->vars[i].name, + valname(scope->vars[i].val.type), + scope->vars[i].val.num + ); + } +} + +void +walkval(struct val *v, ulong depth) +{ + ulong i; + + for (i = 0; i < depth; ++i) + fputs(" ", stderr); + fprintf(stderr, "%s", valname(v->type)); + + switch (v->type) + { + break; case VARR: + fprintf(stderr, ": %lu\n", v->len); + for (i = 0; i < v->len; ++i) + walkval(&v->arr[i], depth + 1); + break; case VNUM: fprintf(stderr, ": %lld\n", v->num); + break; case VVAR: fprintf(stderr, ": %s\n", v->name); + break; default: putc('\n', stderr); + } +} diff --git a/doc/thac.1 b/doc/thac.1 new file mode 100644 index 0000000..1da5f4c --- /dev/null +++ b/doc/thac.1 @@ -0,0 +1,53 @@ +.Dd April 5, 2026 +.Dt THAC 1 +.Os +. +.Sh NAME +.Nm thac +.Nd thalatta compiler +. +.Sh SYNOPSIS +.Nm +.Ar file +. +.Sh DESCRIPTION +.Nm +implements a procedural graph description language. +It evaluates +.Ar file +and then emits resulting graph in the following format: +.Do +.Va prop0 +.Va ... +.Va propN +| +.Va out0 +.Va ... +.Va outM +.Dc , +where +.Bl -dash -width Ds -compact +.It +.Va N , +.Va M +are nonnegative integers +.It +.Va X +in +.Va propX +is an integer +.It +.Va X +in +.Va outX +is a nonnegative integer +.El +. +.Sh RETURN VALUES +.Ex -std +. +.Sh SEE ALSO +.Xr thalatta 5 +. +.Sh AUTHORS +.An Nakidai Perumenei Aq Mt nakidai@disroot.org diff --git a/doc/thalatta.5 b/doc/thalatta.5 new file mode 100644 index 0000000..2cc35b7 --- /dev/null +++ b/doc/thalatta.5 @@ -0,0 +1,299 @@ +.Dd April 5, 2026 +.Dt THALATTA 5 +.Os +. +.Sh NAME +.Nm thalatta +.Nd procedural graph description language +. +.Sh DESCRIPTION +.Nm +is a language used to generate attributed digraphs. +It is intended to be minimalist, +yet provides enough syntax for convenient graph construction. +.Nm +has C-like syntax, +but introduces some new features as well, +while removing unnecessary ones for graph context. +This manual shows only where +.Nm +differs from C. +. +.Ss Values +There are only 3 types in +.Nm : +arrays, integers, and nil. +. +.Pp +Arrays are constant: +a new one is allocated on operations such as assignment, +concatenation etc. +. +.Pp +Integers store not only user-defined numbers, +but also node and module identifiers used by an implementation. +These identifiers are used by the implementation +and should not be edited by the programmer. +. +.Pp +Nil type is the one that does not hold anything. +It can be used as a placeholder. +.Nm +has a predefined variable +.Dv nil +of the type of the same name. +. +.Ss Operations +.Nm +has operations very like in C, +excluding function calls, +structures, +and some others. +Instead introduces some new ones: +.Bl -tag -width Ds +.It Connecting +.Nm +is a graph description language. +The +.Ic <- +operator is used to connect a graph. +Its precedencence is like of an addition. +It creates a connection from right to left, +returning the left operand, so +.Bd -literal -offset indent -compact +a <- b <- c; +.Ed +connects both b and c to a, +not b to a and c to b. +. +.Pp +Connecting a node to an array +means connecting it all the cells from the array. +Connecting array to array is possible only if they are isomorphic, +i.e., +they are of the same length, +and all subarrays are of the same length recursively, +if applicable. +. +.It Generation +.Nm +lacks array initializers like in C. +Instead, +there is a prefix operator +.Ic Bq Va expr , +where +.Va expr +must be an integer. +This will allocate an array +of size +.Va expr . +For each array cell of depth n +there is a variable starting with @ +followed by an nth letter of the alphabet +storing current depth's index. +. +.Pp +For example, +this code: +.Bd -literal -offset indent -compact +[2][3]node(@a, @b); +.Ed +. +.Pp +generates 4 nodes with properties as in binary count: +.Bd -literal -offset indent -compact +0 0 | +0 1 | +0 2 | +1 0 | +1 1 | +1 2 | +.Ed +. +.It Slicing +Besides simple indexing, +.Nm +has a postfix operator +.Ic Bq Va l Ic : Va r +that returns a slice of given array +from +.Va l +inclusive to +.Va r +non inclusive. +Both bounds are clamped +to the array limits. +. +.It Concatenation +The +.Ic >< +operator is used to concatenate 2 arrays. +Its precedence is like of a multiplication. +. +.It Exponentiation +Uses +.Ic ** +operator, +is done before multiplication and right-associative. +.El +. +.Pp +.Nm +has also the prefix operator +.Ic len +to get an array length, +and the prefix operator +.Ic assert +aborting the execution +if its argument evaluates to zero, +and returning it otherwise. +. +.Ss Nodes +Nodes are created using the following syntax: +.Bd -literal -offset indent -compact +node(prop1, prop2); +.Ed +where properties are numbers. +This will return a number \(em +a unique node identifier. +. +.Ss Foreach +The +.Ic foreach +construct is used to iterate over elements of an array. +It recursively walks through its argument, +for each cell executing its body with variables +.Va @0 +.Va @1 +\&... +set just like the generation operator does. +In addition, +it sets the +.Va @ +variable to the current element. +. +.Pp +This code ensures that every number in array +.Va arr +is greater than zero: +.Bd -literal -offset indent +foreach (arr) { + assert(@ > 0); +} +.Ed +. +.Ss Modules +Instead of functions, +.Nm +has modules. +A module with parameters +.Ar a , +.Ar b +can be defined like that: +.Bd -literal -offset indent +mod (a, b) { } +.Ed +It is possible to assign this +to a variable. +. +.Pp +Then, +to use this module later, +there is +.Ic with +statement, +that first evaluates the module +and then executes its body, +with parent scope set to the scope +of the module. +. +.Pp +This code tests whether the variable x is positive, +though in somewhat weird way to show the module usage: +.Bd -literal -offset indent +foo = mod(a) { + b = a; +} + +with foo(x) { + assert(b > 0); +} +.Ed +. +.Sh SYNTAX +This is the syntax of +.Nm +expressed in ABNF: +.Bd -literal -indent offset +code = *(stmt) +stmt = comp / for / foreach / if / with / expr ";" / break / continue +continue = "continue" ";" +break = "break" ";" +with = "with" expr "(" [*(expr ",") expr] ")" stmt +if = "if" "(" expr ")" stmt ["else" stmt] +foreach = "foreach" "(" expr ")" stmt +for = "for" "(" [expr] ";" [expr] ";" [expr] ")" stmt +expr-assign-oper =/ "|=" / "^=" / "=" / ">>=" / "<<=" / "%=" +expr-assign-oper = "+=" / "-=" / "*=" / "/=" / "%=" / "&=" +expr-assign = expr-cond [expr-assign-oper expr-assign] +expr-cond = expr-or ? expr : expr-cond +expr-pref = expr-post / ("assert" / "len" / "~" / "!" / "--" / "++") expr-pref +expr-post = expr-primary *("--" / "++" / "[" expr [":" expr] "]") +expr-mod = "mod" "(" [*(id ",") id] ")" comp +expr-node = "node" "(" [*(expr ",") expr] ")" +expr-primary = NUMBER / id / "nil" / expr-node / expr-mod / "(" expr ")" +comp = "{" *(stmt) "}" +expr = expr-assign +.Ed +. +.Sh EXAMPLES +This snippet creates a graph of a CLA adder: +.Bd -literal -offset indent +and = 0; +xor = 1; + +add = mod(a, b, c) +{ + assert(len(a) == len(b)); + l = len(a); + out = [l]node(xor); + { + p = [l]node(xor) <- a <- b; + g = [l]node(and) <- a <- b; + out <- p; + + foreach (out) + { + @ <- (node(and) <- p[0:@0] <- c); + for (i = 0; i < @0; ++i) + @ <- (node(and) <- p[i+1:@0] <- g[i]); + } + } +}; + +first = [8]node(xor, 0); +second = [8]node(xor, 1); +carry = node(xor, 2); +with add(first, second, carry) { } +.Ed +. +.Sh SEE ALSO +.Xr thac 1 +. +.Rs +.%A B. W. Kernighan +.%A D. M. Ritchie +.%D 1978 +.%B The C Programming Language +.Re +. +.Rs +.%A D. Crocker +.%A P. Overell +.%D January 2008 +.%R RFC 5234 +.%T Augmented BNF for Syntax Specifications: ABNF +.Re +. +.Sh AUTHORS +.An Nakidai Perumenei Aq Mt nakidai@disroot.org diff --git a/lex.c b/lex.c new file mode 100644 index 0000000..29b56f0 --- /dev/null +++ b/lex.c @@ -0,0 +1,279 @@ +/* + * 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 <ctype.h> +#include <stdarg.h> +#include <stddef.h> +#include <stdlib.h> +#include <string.h> + + +static struct tok storage; +struct tok curtok; +static int isunget; +ulong curline; + +static int +isidish(int ch) +{ + static char lut[256] = { + ['a'] = 1, ['b'] = 1, ['c'] = 1, ['d'] = 1, ['e'] = 1, ['f'] = 1, + ['g'] = 1, ['h'] = 1, ['i'] = 1, ['j'] = 1, ['k'] = 1, ['l'] = 1, + ['m'] = 1, ['n'] = 1, ['o'] = 1, ['p'] = 1, ['q'] = 1, ['r'] = 1, + ['s'] = 1, ['t'] = 1, ['u'] = 1, ['v'] = 1, ['w'] = 1, ['x'] = 1, + ['y'] = 1, ['z'] = 1, ['A'] = 1, ['B'] = 1, ['C'] = 1, ['D'] = 1, + ['E'] = 1, ['F'] = 1, ['G'] = 1, ['H'] = 1, ['I'] = 1, ['J'] = 1, + ['K'] = 1, ['L'] = 1, ['M'] = 1, ['N'] = 1, ['O'] = 1, ['P'] = 1, + ['Q'] = 1, ['R'] = 1, ['S'] = 1, ['T'] = 1, ['U'] = 1, ['V'] = 1, + ['W'] = 1, ['X'] = 1, ['Y'] = 1, ['Z'] = 1, ['@'] = 1, ['_'] = 1, + ['0'] = 1, ['1'] = 1, ['2'] = 1, ['3'] = 1, ['4'] = 1, ['5'] = 1, + ['6'] = 1, ['7'] = 1, ['8'] = 1, ['9'] = 1, ['\''] = 1, + }; + + if (ch < 0 || ch > 255) + return 0; + + return lut[ch]; +} + +static void +append(int ch) +{ + if (curtok.info.cap < curtok.info.len + 1) + curtok.info.p = realloc( + curtok.info.p, + curtok.info.cap = (curtok.info.cap + sizeof(*curtok.info.p)) * 2 + ); + if (!curtok.info.p) + dieno(1, "realloc()"); + curtok.info.p[curtok.info.len++] = ch; +} + +static int +get(void) +{ + int ch; + + if ((ch = getc(curfile)) == EOF) + return -1; + append(ch); + + curline += (ch == '\n'); + + return ch; +} + +static int +unget(int ch) +{ + --curtok.info.len; + curline -= (ch == '\n'); + return ungetc(ch, curfile); +} + +void +word(void) +{ + char *s; + int ch; + + while ((ch = get()) != EOF) + if (!isidish(ch)) + break; + if (ch != EOF) + unget(ch); + append('\0'); + + s = curtok.info.p; + + curtok.type = TID; +#define is(wanted) !strcmp(s, (wanted)) + curtok.type = isdigit(*s) ? TNUM : + is("assert") ? TKEYASSERT : + is("if") ? TKEYIF : + is("else") ? TKEYELSE : + is("for") ? TKEYFOR : + is("foreach") ? TKEYFOREACH : + is("len") ? TKEYLEN : + is("mod") ? TKEYMOD : + is("node") ? TKEYNODE : + is("with") ? TKEYWITH : + is("break") ? TKEYBREAK : + is("continue") ? TKEYCONT : + TID; +#undef is + + return; +} + +void +oper(void) +{ + int ch; + + switch (ch = get()) + { + /* these are not start of any other operator */ +#define SINGLE(ch, t) case (ch): curtok.type = (t); append('\0'); return + SINGLE('{', TOPBRACE); + SINGLE('}', TCLBRACE); + SINGLE('[', TOPBRACK); + SINGLE(']', TCLBRACK); + SINGLE('(', TOPPAREN); + SINGLE(')', TCLPAREN); + SINGLE(':', TCOL); + SINGLE(',', TCOMMA); + SINGLE('?', TQUEST); + SINGLE(';', TSEMICOL); + SINGLE('~', TTILDE); +#undef SINGLE + /* + * these are cases when operator is one of: + * t1 = {ch1} + * t21 = {ch1}{ch21} + * t22 = {ch1}{ch22} + */ +#define DOUBLE(ch1, ch21, ch22, t1, t21, t22) case (ch1): \ +{ \ + int next; \ + curtok.type = (t1); \ + if ((next = get()) == EOF) \ + (void)0; \ + else if (next == (ch21)) \ + curtok.type = (t21); \ + else if (next == (ch22)) \ + curtok.type = (t22); \ + else \ + unget(next); \ + append('\0'); \ + return; \ +} + DOUBLE('&', '=', '&', TAMPER, TASSAMPER, TAND); + DOUBLE('^', '=', EOF, TCARET, TASSCARET, 0); + DOUBLE('=', '=', EOF, TASSIGN, TEQ, 0); + DOUBLE('|', '=', '|', TPIPE, TASSPIPE, TOR); + DOUBLE('/', '=', EOF, TSLASH, TASSSLASH, 0); + DOUBLE('%', '=', EOF, TPERC, TASSPERC, 0); + DOUBLE('!', '=', EOF, TEXCLAM, TNEQ, 0); + DOUBLE('-', '=', '-', TMINUS, TASSMINUS, TDECR); + DOUBLE('+', '=', '+', TPLUS, TASSPLUS, TINCR); + DOUBLE('*', '=', '*', TASTER, TASSASTER, TPOW); +#undef DOUBLE + /* + * these are cases when operator is one of: + * t1 = {ch1} + * t21 = {ch1}{ch21} + * t22 = {ch1}{ch22} + * t23 = {ch1}{ch1} + * t3 = {ch1}{ch1}{ch3} + */ +#define TRIPLE(ch1, ch21, ch22, ch3, t1, t21, t22, t23, t3) case (ch1): \ +{ \ + int next; \ + curtok.type = (t1); \ + if ((next = get()) == EOF) \ + (void)0; \ + else if (next == (ch21)) \ + curtok.type = (t21); \ + else if (next == (ch22)) \ + curtok.type = (t22); \ + else if (next == ch) \ + { \ + curtok.type = (t23); \ + if ((next = get()) == EOF) \ + (void)0; \ + else if (next == (ch3)) \ + curtok.type = (t3); \ + else \ + unget(next); \ + } \ + else \ + unget(next); \ + append('\0'); \ + return; \ +} + TRIPLE('>', '=', '<', '=', TGREAT, TGREATEQ, TCONCAT, TRSHIFT, TASSRSHIFT); + TRIPLE('<', '=', '-', '=', TLESS, TLESSEQ, TARRLEFT, TLSHIFT, TASSLSHIFT); +#undef TRIPLE + } + + complain(1, "unknown operator starting with %c", ch); +} + +enum tok_t +gettok(void) +{ + int ch; + + curtok.info = (struct tinfo){NULL, 0, 0}; + + if (isunget) + { + isunget = 0; + curtok = storage; + /*say("reread %s(`%s')", tokname(curtok.type), curtok.info.p)*/; + return curtok.type; + } + + for (; (ch = get()) != EOF; curtok.info.len = 0) + if (!isspace(ch)) + goto found; + return curtok.type = TEOF; +found: + unget(ch); + + if (isidish(ch)) + word(); + else + oper(); + + /*say("read %s(`%s')", tokname(curtok.type), curtok.info.p)*/; + return curtok.type; +} + +void +ungettok() +{ + storage = curtok; + isunget = 1; +} + +int +_exptok(enum tok_t first, ...) +{ + enum tok_t next; + va_list ap; + int res; + + if (gettok() == TEOF) + return 0; + if (curtok.type == first) + return 1; + + res = 0; + va_start(ap, first); + while ((next = va_arg(ap, enum tok_t)) != TEOF) + if (curtok.type == next) + { + res = 1; + break; + } + va_end(ap); + + return res; +} diff --git a/main.c b/main.c new file mode 100644 index 0000000..56a094c --- /dev/null +++ b/main.c @@ -0,0 +1,61 @@ +/* + * 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 <stdio.h> +#include <stdlib.h> + + +FILE *curfile; +char *curname, *progname; + +void +usage(char *name) +{ + fprintf(stderr, "usage: %s <file>\n", name); + exit(1); +} + +int +main(int argc, char **argv) +{ + struct scope scope = {&basescope, NULL, 0}; + struct node *stmt; + struct val val; + + progname = *argv; + + if (!argv[1]) + usage(*argv); + + curname = argv[1]; + curfile = fopen(curname, "r"); + curline = 1; + + if (!curfile) + dieno(1, "fopen(`%s')", argv[1]); + + while ((stmt = getstmt())) + { + val = eval(stmt, &scope); + if (val.type == VBREAK || val.type == VCONT) + complain(1, "cannot %s out of nothing", valname(val.type)); + } + showverts(); + + return 0; +} diff --git a/parse.c b/parse.c new file mode 100644 index 0000000..b334db1 --- /dev/null +++ b/parse.c @@ -0,0 +1,637 @@ +/* + * 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> + + +/* + * expr = expr-assign + */ +#define pexpr pexpr_assign +static struct node *pexpr(void); +static struct node *pstmt(void); + +static enum noper_t +associate(enum tok_t t) +{ + switch (t) + { + case TKEYLEN: return OLEN; + case TKEYASSERT: return OASSERT; + case TARRLEFT: return OCONNECT; + case TCONCAT: return OCONCAT; + case TAMPER: return OBAND; + case TPIPE: return OBOR; + case TCARET: return OBXOR; + case TLSHIFT: return OLSHIFT; + case TRSHIFT: return ORSHIFT; + case TTILDE: return OINV; + case TAND: return OAND; + case TEXCLAM: return ONOT; + case TOR: return OOR; + case TEQ: return OEQ; + case TNEQ: return ONEQ; + case TLESS: return OLESS; + case TLESSEQ: return OLESSEQ; + case TGREAT: return OGREAT; + case TGREATEQ: return OGREATEQ; + case TPLUS: return OSUM; + case TMINUS: return OSUB; + case TASTER: return OMUL; + case TSLASH: return ODIV; + case TPERC: return OMOD; + case TPOW: return OPOW; + case TASSAMPER: return OASSBAND; + case TASSASTER: return OASSMUL; + case TASSCARET: return OASSBXOR; + case TASSIGN: return OASSIGN; + case TASSLSHIFT: return OASSLSHIFT; + case TASSMINUS: return OASSSUB; + case TASSPERC: return OASSMOD; + case TASSPIPE: return OASSBOR; + case TASSPLUS: return OASSSUM; + case TASSRSHIFT: return OASSRSHIFT; + case TASSSLASH: return OASSDIV; + default: complain(1, "%s cannot be associated", tokname(t)); + } + + /* not reached */ + return 0; +} + +static struct node * +mknode(enum node_t t) +{ + struct node *res = malloc(sizeof(*res)); + if (!res) + dieno(1, "malloc()"); + res->type = t; + return res; +} + +#define BINLEFT(NAME, NEXT, ...) static struct node *NAME(void) \ +{ \ + struct node *child, *node; \ + if (!(child = NEXT())) \ + return NULL; \ +loop: \ + if (!exptok(__VA_ARGS__)) \ + { \ + ungettok(); \ + return child; \ + } \ + node = mknode(NOPER); \ + node->noper.type = associate(curtok.type); \ + node->noper.l = child; \ + if (!(node->noper.r = NEXT())) \ + complain(1, "no right side for %s", nopername(node->noper.type)); \ + child = node; \ + goto loop; \ +} + +#define BINRIGHT(NAME, NEXT, ...) static struct node *NAME(void) \ +{ \ + struct node *child, *node; \ + if (!(child = NEXT())) \ + return NULL; \ + if (!exptok(__VA_ARGS__)) \ + { \ + ungettok(); \ + return child; \ + } \ + node = mknode(NOPER); \ + node->noper.type = associate(curtok.type); \ + node->noper.l = child; \ + if (!(node->noper.r = NAME())) \ + complain(1, "no right side for %s", nopername(node->noper.type)); \ + return node; \ +} + +/* + * comp = "{" *(stmt) "}" + */ +static struct node * +pcomp(void) +{ + struct node *node; + ulong i = 0; + + if (!exptok(TOPBRACE)) + return NULL; + node = mknode(NCOMP); + node->ncomp.stmts = NULL; + +loop: + /* TODO: add cap and increase like in lex.c */ + node->ncomp.stmts = realloc( + node->ncomp.stmts, + sizeof(struct node *) * (i + 1) + ); + + switch (gettok()) + { + break; case TEOF: complain(1, "no closing brace"); + break; case TCLBRACE: goto end; + break; default: + ungettok(); + if (!(node->ncomp.stmts[i] = pstmt())) + complain(1, "no statement"); + } + ++i; + goto loop; +end: + node->ncomp.len = i; + return node; +} + +/* + * expr-primary = NUMBER / id / "nil" / expr-node / expr-mod / "(" expr ")" + * expr-node = "node" "(" [*(expr ",") expr] ")" + * expr-mod = "mod" "(" [*(id ",") id] ")" comp + */ +static struct node * +pexpr_primary(void) +{ + struct node *node = NULL; + ulong i = 0; + + switch (gettok()) + { + break; case TEOF: return NULL; + break; case TNUM: + node = mknode(NNUM); + node->nnum = atol(curtok.info.p); + break; case TID: + node = mknode(NVAR); + node->nvar = curtok.info.p; + break; case TOPPAREN: + if (!(node = pexpr())) + complain(1, "empty paretheses"); + if (!exptok(TCLPAREN)) + complain(1, "no closing parenthesis"); + break; case TKEYNODE: + node = mknode(NNODE); + node->nnode.params = NULL; + + if (!exptok(TOPPAREN)) + complain(1, "no parentheses after node"); +nodeloop: + /* TODO: add cap and increase like in lex.c */ + node->nnode.params = realloc( + node->nnode.params, + sizeof(struct node *) * (i + 1) + ); + if (!node->nnode.params) + dieno(1, "realloc()"); + + switch(gettok()) + { + break; case TEOF: complain(1, "no closing parenthesis"); + break; case TCLPAREN: goto nodeend; + break; case TCOMMA: + if (!i || !(node->nnode.params[i] = pexpr())) + complain(1, "empty param in node"); + break; default: + ungettok(); + if (i) + complain(1, "no comma"); + else if (!(node->nnode.params[i] = pexpr())) + complain(1, "empty param in node"); + } + ++i; + goto nodeloop; +nodeend: + node->nnode.len = i; + break; case TKEYMOD: + node = mknode(NMOD); + node->nmod.params = NULL; + + if (!exptok(TOPPAREN)) + complain(1, "no parentheses after mod"); +modloop: + /* TODO: add cap and increase like in lex.c */ + node->nmod.params = realloc( + node->nmod.params, + sizeof(char *) * (i + 1) + ); + if (!node->nmod.params) + dieno(1, "realloc()"); + + switch(gettok()) + { + break; case TEOF: complain(1, "no closing parenthesis"); + break; case TCLPAREN: goto modend; + break; case TCOMMA: + if (!i || gettok() != TID) + complain(1, "expected id as a param name"); + else + node->nmod.params[i] = curtok.info.p; + break; default: + ungettok(); + if (i) + complain(1, "no comma"); + else if (gettok() != TID) + complain(1, "expected id as a param name"); + else + node->nmod.params[i] = curtok.info.p; + } + ++i; + goto modloop; +modend: + node->nmod.len = i; + + if (!(node->nmod.code = pcomp())) + complain(1, "no comp for mod"); + break; default: + ungettok(); + } + + return node; +} + +/* + * expr-post = expr-primary *("--" / "++" / "[" expr [":" expr] "]") + */ +static struct node * +pexpr_post(void) +{ + struct node *child, *node; + + child = pexpr_primary(); + if (!child) + return NULL; + +loop: + switch (gettok()) + { + break; case TEOF: return child; + break; case TDECR: case TINCR: + node = mknode(NOPER); + node->noper.type = curtok.type == TDECR ? OPOSTDECR : OPOSTINCR; + node->noper.m = child; + break; case TOPBRACK: + node = mknode(NOPER); + node->noper.type = OINDEX; + node->noper.l = child; + + if (!(node->noper.r = pexpr())) + complain(1, "no inside for []"); + switch (gettok()) + { + break; case TEOF: complain(1, "no closing bracket"); + break; case TCOL: + node->noper.type = OSLICE; + node->noper.m = node->noper.r; + if (!(node->noper.r = pexpr())) + complain(1, "no right side for []"); + if (!exptok(TCLBRACK)) + complain(1, "invalid syntax for []"); + /*FT*/ case TCLBRACK: + break; default: complain(1, "invalid syntax for []"); + } + break; default: + ungettok(); + return child; + } + + child = node; + goto loop; +} + +/* + * expr-pref = expr-post / ("assert" / "len" / "~" / "!" / "--" / "++") expr-pref + */ +static struct node * +pexpr_pref(void) +{ + struct node *node; + + switch (gettok()) + { + break; case TEOF: return NULL; + break; case TKEYASSERT: case TKEYLEN: case TTILDE: + case TEXCLAM: case TDECR: case TINCR: + node = mknode(NOPER); + node->noper.type = curtok.type == TDECR ? OPREDECR : + curtok.type == TINCR ? OPREINCR : + associate(curtok.type); + if (!(node->noper.m = pexpr_pref())) + complain(1, "no operand for %s", nopername(node->noper.type)); + break; case TOPBRACK: + node = mknode(NOPER); + node->noper.type = OARRAY; + + if (!(node->noper.l = pexpr())) + complain(1, "no inside for []"); + if (!exptok(TCLBRACK)) + complain(1, "invalid syntax for []"); + if (!(node->noper.r = pexpr_pref())) + complain(1, "no operand for []"); + break; default: + ungettok(); + return pexpr_post(); + } + + return node; +} + +/* expr-pow = expr-pref ["**" expr-pow] */ +BINRIGHT(pexpr_pow, pexpr_pref, TPOW) +/* expr-mult = expr-pow *(("*" / "><" / "%" / "/") expr-pow) */ +BINLEFT(pexpr_mult, pexpr_pow, TASTER, TCONCAT, TPERC, TSLASH) +/* expr-add = expr-mult *(("<-" / "-" / "+") expr-mult) */ +BINLEFT(pexpr_add, pexpr_mult, TARRLEFT, TMINUS, TPLUS) +/* expr-shift = expr-add *(("<<" / ">>") expr-add) */ +BINLEFT(pexpr_shift, pexpr_add, TLSHIFT, TRSHIFT) +/* expr-rel = expr-shift *(("<" / "<=" / ">" / ">=") expr-shift) */ +BINLEFT(pexpr_rel, pexpr_shift, TLESS, TLESSEQ, TGREAT, TGREATEQ) +/* expr-eq = expr-rel *(("==" / "!=") expr-rel) */ +BINLEFT(pexpr_eq, pexpr_rel, TEQ, TNEQ) +/* expr-band = expr-eq *("&" expr-eq) */ +BINLEFT(pexpr_band, pexpr_eq, TAMPER) +/* expr-bxor = expr-band *("^" expr-band) */ +BINLEFT(pexpr_bxor, pexpr_band, TCARET) +/* expr-bor = expr-bxor *("|" expr-bxor) */ +BINLEFT(pexpr_bor, pexpr_bxor, TPIPE) +/* expr-and = expr-bor *("&&" expr-bor) */ +BINLEFT(pexpr_and, pexpr_bor, TAND) +/* expr-or = expr-and *("||" expr-and) */ +BINLEFT(pexpr_or, pexpr_and, TOR) + +/* + * expr-cond = expr-or ? expr : expr-cond + */ +static struct node * +pexpr_cond(void) +{ + struct node *child, *node; + + child = pexpr_or(); + if (!child) + return NULL; + + if (!exptok(TQUEST)) + { + ungettok(); + return child; + } + + node = mknode(NOPER); + node->noper.type = OCOND; + node->noper.l = child; + + if (!(node->noper.m = pexpr())) + complain(1, "no then branch in ternary if"); + + if (!exptok(TCOL)) + complain(1, "no colon for ternary if"); + + if (!(node->noper.r = pexpr_cond())) + complain(1, "no else branch for ternary if"); + + return node; +} + +/* + * expr-assign = expr-cond [expr-assign-oper expr-assign] + * expr-assign-oper = "+=" / "-=" / "*=" / "/=" / "%=" / "&=" + * expr-assign-oper =/ "|=" / "^=" / "=" / ">>=" / "<<=" / "%=" + */ +BINRIGHT( + pexpr_assign, pexpr_cond, + TASSAMPER, TASSASTER, TASSCARET, TASSIGN, TASSLSHIFT, + TASSMINUS, TASSPERC, TASSPIPE, TASSPLUS, TASSRSHIFT, + TASSSLASH +) + +/* + * for = "for" "(" [expr] ";" [expr] ";" [expr] ")" stmt + */ +struct node * +pfor(void) +{ + struct node *node; + + if (!exptok(TKEYFOR)) + { + say("%s:%d: pfor selected but no TKEYFOR?", __FILE__, __LINE__); + ungettok(); + return NULL; + } + + node = mknode(NFOR); + + if (!exptok(TOPPAREN)) + complain(1, "no parentheses after for"); + node->nfor.before = pexpr(); + if (!exptok(TSEMICOL)) + complain(1, "invalid for parameters"); + node->nfor.cond = pexpr(); + if (!exptok(TSEMICOL)) + complain(1, "invalid for parameters"); + node->nfor.between = pexpr(); + if (!exptok(TCLPAREN)) + complain(1, "invalid for parameters"); + if (!(node->nfor.code = pstmt())) + complain(1, "no statement for for"); + + return node; +} + +/* + * foreach = "foreach" "(" expr ")" stmt + */ +struct node * +pforeach(void) +{ + struct node *node; + + if (!exptok(TKEYFOREACH)) + { + say("%s:%d: pforeach selected but no TKEYFOREACH?", __FILE__, __LINE__); + ungettok(); + return NULL; + } + + node = mknode(NFOREACH); + + if (!exptok(TOPPAREN) + || !(node->nforeach.obj = pexpr()) + || !exptok(TCLPAREN)) + complain(1, "invalid foreach parameter"); + if (!(node->nforeach.code = pstmt())) + complain(1, "no statement for foreach"); + + return node; +} + +/* + * if = "if" "(" expr ")" stmt ["else" stmt] + */ +struct node * +pif(void) +{ + struct node *node; + + if (!exptok(TKEYIF)) + { + say("%s:%d: pif selected but no TKEYIF?", __FILE__, __LINE__); + ungettok(); + return NULL; + } + + node = mknode(NCOND); + + if (!exptok(TOPPAREN) + || !(node->ncond.cond = pexpr()) + || !exptok(TCLPAREN)) + complain(1, "invalid if parameter"); + if (!(node->ncond.t = pstmt())) + complain(1, "no statement for then if branch"); + + node->ncond.f = NULL; + if (exptok(TKEYELSE)) + if (!(node->ncond.f = pstmt())) + complain(1, "no statement for else if branch"); + else; + else + ungettok(); + + return node; +} + +/* + * with = "with" expr "(" [*(expr ",") expr] ")" stmt + */ +struct node * +pwith(void) +{ + struct node *node; + ulong i = 0; + + if (!exptok(TKEYWITH)) + { + say("%s:%d: pwith selected but no TKEYWITH?", __FILE__, __LINE__); + ungettok(); + return NULL; + } + + node = mknode(NWITH); + node->nwith.args = NULL; + + if (!(node->nwith.mod = pexpr())) + complain(1, "no module specified"); + if (!exptok(TOPPAREN)) + complain(1, "no parameters in with"); +loop: + /* TODO: add cap and increase like in lex.c */ + node->nwith.args = realloc( + node->nwith.args, + sizeof(struct nnode *) * (i + 1) + ); + if (!node->nwith.args) + dieno(1, "realloc()"); + + switch(gettok()) + { + break; case TEOF: complain(1, "no closing parenthesis"); + break; case TCLPAREN: goto end; + break; case TCOMMA: + if (!i || !(node->nwith.args[i] = pexpr())) + complain(1, "empty param in with"); + break; default: + ungettok(); + if (i) + complain(1, "no comma"); + else if (!(node->nwith.args[i] = pexpr())) + complain(1, "empty param in with"); + } + ++i; + goto loop; +end: + if (!(node->nwith.code = pstmt())) + complain(1, "no statement for with"); + node->nwith.len = i; + + return node; +} + +/* + * break = "break" ";" + * continue = "continue" ";" + */ +struct node * +pbreak(void) +{ + struct node *node; + enum tok_t t; + + if (!exptok(TKEYBREAK, TKEYCONT)) + { + say("%s:%d: break selected but no break/continue?", __FILE__, __LINE__); + ungettok(); + return NULL; + } + + node = mknode(NWITH); + node->type = (t = curtok.type) == TKEYBREAK ? NBREAK : NCONT; + + if (!exptok(TSEMICOL)) + complain(1, "expected `;' after %s", tokname(t)); + + return node; +} + +/* + * stmt = comp / for / foreach / if / with / expr ";" / break / continue + */ +struct node * +pstmt(void) +{ + struct node *node; + + if (gettok() == TEOF) + return NULL; + ungettok(); + + switch (curtok.type) + { + break; case TOPBRACE: return pcomp(); + break; case TKEYFOR: return pfor(); + break; case TKEYFOREACH: return pforeach(); + break; case TKEYIF: return pif(); + break; case TKEYWITH: return pwith(); + break; case TKEYBREAK: + /*FT*/ case TKEYCONT: return pbreak(); + break; default: + node = pexpr(); + if (!exptok(TSEMICOL)) + complain(1, "expected `;' at the end of an expression"); + return node ? node : pstmt(); + } + + /* not reached */ + return NULL; +} + +struct node * +getstmt(void) +{ + return pstmt(); +} diff --git a/say.c b/say.c new file mode 100644 index 0000000..5a0c587 --- /dev/null +++ b/say.c @@ -0,0 +1,89 @@ +/* + * 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 <errno.h> +#include <stdarg.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + + +void vsay(char *fmt, va_list ap) +{ + fprintf(stderr, "%s:%ld: ", curname, curline); + vfprintf(stderr, fmt, ap); + fputc('\n', stderr); +} + +void +say(char *fmt, ...) +{ + va_list ap; + + va_start(ap, fmt); + vsay(fmt, ap); + va_end(ap); +} + +void +complain(int code, char *fmt, ...) +{ + va_list ap; + + va_start(ap, fmt); + vsay(fmt, ap); + va_end(ap); + + exit(code); +} + +void +die(int code, char *fmt, ...) +{ + va_list ap; + + fputs(progname, stderr); + if (fmt) + { + fputs(": ", stderr); + va_start(ap, fmt); + vfprintf(stderr, fmt, ap); + va_end(ap); + } + fputc('\n', stderr); + + exit(code); +} + +void +dieno(int code, char *fmt, ...) +{ + va_list ap; + + fputs(progname, stderr); + if (fmt) + { + fputs(": ", stderr); + va_start(ap, fmt); + vfprintf(stderr, fmt, ap); + va_end(ap); + } + fprintf(stderr, ": %s\n", strerror(errno)); + + exit(code); +} diff --git a/str.c b/str.c new file mode 100644 index 0000000..c8ed336 --- /dev/null +++ b/str.c @@ -0,0 +1,33 @@ +/* + * 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> + + +#define STRGEN(X) case X: return #X; +#define STRNAME(NAME, TYPES) char *NAME##name(enum NAME##_t t) \ +{ \ + switch (t) {TYPES(STRGEN)} \ + /* not reached */ \ + return NULL; \ +} + +STRNAME(tok, TOKTYPES); +STRNAME(node, NODETYPES); +STRNAME(val, VALTYPES); +STRNAME(noper, NOPERTYPES); diff --git a/thac.h b/thac.h new file mode 100644 index 0000000..37dd9f5 --- /dev/null +++ b/thac.h @@ -0,0 +1,323 @@ +/* + * 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. + */ + +#ifndef __THAC_H__ +#define __THAC_H__ + +#define _POSIX_SOURCE + +#include <stdio.h> + + +enum +{ + GENVARLEN = 16, +}; + +typedef unsigned long ulong; +typedef long long vlong; + +#define lengthof(X) (sizeof(X)/sizeof(*(X))) +#define max(X, Y) ((X) > (Y) ? (X) : (Y)) +#define min(X, Y) ((X) < (Y) ? (X) : (Y)) + +#define ENUMGEN(X) X, + +#define TOKTYPES(X) \ + X(TEOF) \ + X(TAMPER) /* & */ \ + X(TAND) /* && */ \ + X(TARRLEFT) /* <- */ \ + X(TASSAMPER) /* &= */ \ + X(TASSASTER) /* *= */ \ + X(TASSCARET) /* ^= */ \ + X(TASSIGN) /* = */ \ + X(TASSLSHIFT) /* <<= */ \ + X(TASSMINUS) /* -= */ \ + X(TASSPERC) /* %= */ \ + X(TASSPIPE) /* |= */ \ + X(TASSPLUS) /* += */ \ + X(TASSRSHIFT) /* >>= */ \ + X(TASSSLASH) /* /= */ \ + X(TASTER) /* * */ \ + X(TCARET) /* ^ */ \ + X(TCLBRACE) /* } */ \ + X(TCLBRACK) /* ] */ \ + X(TCLPAREN) /* ) */ \ + X(TCOL) /* : */ \ + X(TCOMMA) /* , */ \ + X(TCONCAT) /* >< */ \ + X(TDECR) /* -- */ \ + X(TEQ) /* == */ \ + X(TEXCLAM) /* ! */ \ + X(TID) /* <identifier> */ \ + X(TINCR) /* ++ */ \ + X(TKEYASSERT) /* assert */ \ + X(TKEYBREAK) /* break */ \ + X(TKEYCONT) /* continue */ \ + X(TKEYELSE) /* else */ \ + X(TKEYFOR) /* for */ \ + X(TKEYFOREACH) /* foreach */ \ + X(TKEYIF) /* if */ \ + X(TKEYLEN) /* len */ \ + X(TKEYMOD) /* mod */ \ + X(TKEYNODE) /* node */ \ + X(TKEYWITH) /* with */ \ + X(TLESS) /* < */ \ + X(TLESSEQ) /* <= */ \ + X(TLSHIFT) /* << */ \ + X(TMINUS) /* - */ \ + X(TGREAT) /* > */ \ + X(TGREATEQ) /* >= */ \ + X(TNEQ) /* != */ \ + X(TNUM) /* <number> */ \ + X(TOPBRACE) /* { */ \ + X(TOPBRACK) /* [ */ \ + X(TOPPAREN) /* ( */ \ + X(TOR) /* || */ \ + X(TPERC) /* % */ \ + X(TPIPE) /* | */ \ + X(TPLUS) /* + */ \ + X(TPOW) /* ** */ \ + X(TQUEST) /* ? */ \ + X(TRSHIFT) /* >> */ \ + X(TSEMICOL) /* ; */ \ + X(TSLASH) /* / */ \ + X(TTILDE) /* ~ */ \ + /* TOKTYPES */ + +#define NODETYPES(X) \ + X(NCOMP) /* compound statement */ \ + X(NCOND) /* if statement */ \ + X(NFOR) /* for statement */ \ + X(NFOREACH) /* for statement */ \ + X(NMOD) /* module expression */ \ + X(NNUM) /* number */ \ + X(NNODE) /* node expression */ \ + X(NOPER) /* (unary (m) / binary (l r) / ternary (l m r)) operator */ \ + X(NWITH) /* module invocation */ \ + X(NVAR) /* variable reference */ \ + X(NBREAK) /* break statement to exit the loop */ \ + X(NCONT) /* continue statement to go to next loop iteration */ \ + /* NODETYPES */ + +#define NOPERTYPES(X) \ + X(OLEN) \ + X(OASSERT) \ + X(OCONNECT) \ + X(OINDEX) \ + X(OSLICE) \ + X(OARRAY) \ + X(OCONCAT) \ + /* binary logic */ \ + X(OBAND) \ + X(OBOR) \ + X(OBXOR) \ + X(OLSHIFT) \ + X(ORSHIFT) \ + X(OINV) \ + /* logic */ \ + X(OAND) \ + X(ONOT) \ + X(OOR) \ + X(OCOND) \ + /* relations */ \ + X(OEQ) \ + X(ONEQ) \ + X(OLESS) \ + X(OLESSEQ) \ + X(OGREAT) \ + X(OGREATEQ) \ + /* algebraic */ \ + X(OPOSTDECR) \ + X(OPOSTINCR) \ + X(OPREDECR) \ + X(OPREINCR) \ + X(OSUM) \ + X(OSUB) \ + X(OMUL) \ + X(ODIV) \ + X(OMOD) \ + X(OPOW) \ + /* assignment */ \ + X(OASSBAND) \ + X(OASSMUL) \ + X(OASSBXOR) \ + X(OASSIGN) \ + X(OASSLSHIFT) \ + X(OASSSUB) \ + X(OASSMOD) \ + X(OASSBOR) \ + X(OASSSUM) \ + X(OASSRSHIFT) \ + X(OASSDIV) \ + /* NOPERTYPES */ + +#define VALTYPES(X) \ + X(VNIL) \ + X(VBREAK) \ + X(VCONT) \ + X(VNUM) \ + X(VVAR) \ + X(VARR) \ + /* VALTYPES */ + +struct tok +{ + enum tok_t {TOKTYPES(ENUMGEN)} type; + struct tinfo + { + char *p; + ulong len, cap; + } info; +}; + +struct node +{ + enum node_t {NODETYPES(ENUMGEN)} type; + union + { + struct ncomp + { + struct node **stmts; + ulong len; + } ncomp; + struct ncond + { + struct node *cond, *t, *f; + } ncond; + struct nfor + { + struct node *before, *cond, *between, *code; + } nfor; + struct nforeach + { + struct node *obj, *code; + } nforeach; + struct nmod + { + struct node *code; + char **params; + ulong len; + } nmod; + vlong nnum; + struct nnode + { + struct node **params; + ulong len; + } nnode; + struct noper + { + enum noper_t {NOPERTYPES(ENUMGEN)} type; + struct node *l, *m, *r; + } noper; + struct nwith + { + struct node *mod, **args, *code; + ulong len; + } nwith; + char *nvar; + }; +}; + +struct val +{ + enum val_t {VALTYPES(ENUMGEN)} type; + union + { + char *name; /* VVAR */ + vlong num; /* VNUM */ + struct /* VARR; */ + { + struct val *arr; + ulong len; + }; + }; +}; + +struct scope +{ + struct scope *parent; + struct var + { + char *name; + struct val val; + } *vars; + ulong len; +}; + +struct vertex +{ + struct + { + vlong *arr; + ulong len; + } props; + struct + { + ulong *arr; + ulong len; + } nbrs; +}; + +extern FILE *curfile; +extern char *curname, *progname; +extern ulong curline; + +extern struct scope basescope; +extern struct tok curtok; +extern struct val nil; + +void complain(int code, char *, ...); +void say(char *, ...); +void die(int code, char *, ...); +void dieno(int code, char *, ...); + +enum tok_t gettok(void); +void ungettok(void); +#define exptok(...) _exptok(__VA_ARGS__, TEOF) +int _exptok(enum tok_t, ...); + +struct node *getstmt(void); +struct val eval(struct node *, struct scope *); + +long findvar(char *, struct scope **); +ulong assignvar(char *, struct val, struct scope **); +int varnextchar(struct scope *); +ulong varnextnum(struct scope *); +void delvarscope(struct scope *, ulong); + +struct val deref(struct val, struct scope *); +struct val getval(enum val_t, struct val, struct scope *, int); +struct val copyval(struct val old); +void freeval(struct val); +struct val connectval(struct val, struct val, struct scope *); + +ulong mkvert(void); +void addprop(ulong, vlong); +void addedge(ulong, ulong); +void showverts(void); + +void walkval(struct val *, ulong); +void walknode(struct node *, ulong); +void walkscope(struct scope *, ulong); + +char *tokname(enum tok_t); +char *nodename(enum node_t); +char *valname(enum val_t); +char *nopername(enum noper_t); + +#endif /* __THAC_H__ */ diff --git a/val.c b/val.c new file mode 100644 index 0000000..b8dc30d --- /dev/null +++ b/val.c @@ -0,0 +1,144 @@ +/* + * 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 <stdio.h> +#include <stdlib.h> + + +struct val +deref(struct val val, struct scope *scope) +{ + long i; + + if (val.type == VVAR) + { + i = findvar(val.name, &scope); + if (i < 0) + complain(1, "no such variable: %s", val.name); + val = scope->vars[i].val; + } + + return val; +} + +struct val +getval(enum val_t t, struct val val, struct scope *scope, int doderef) +{ + if (doderef) + val = deref(val, scope); + if (val.type != t) + complain(1, "expected %s, got %s", valname(t), valname(val.type)); + return val; +} + +struct val +copyval(struct val old) +{ + struct val new; + ulong i; + + new = old; + if (new.type == VARR) + { + new.arr = malloc(sizeof(*new.arr) * new.len); + if (!new.arr) + dieno(1, "malloc()"); + + for (i = 0; i < new.len; ++i) + new.arr[i] = copyval(old.arr[i]); + } + + return new; +} + +void +freeval(struct val val) +{ + ulong i; + + if (val.type == VARR) + { + for (i = 0; i < val.len; ++i) + freeval(val.arr[i]); + free(val.arr); + } +} + +static void +connectarr(struct val arr, ulong single, int arr_to_single) +{ + ulong i; + + for (i = 0; i < arr.len; ++i) switch (arr.arr[i].type) + { + break; case VARR: + connectarr(arr.arr[i], single, arr_to_single); + break; case VNUM: + if (arr_to_single) + addedge(arr.arr[i].num, single); + else + addedge(single, arr.arr[i].num); + break; default: complain(1, "%s can not be connected", valname(arr.arr[i].type)); + } +} + +static void +connectarrarr(struct val from, struct val to) +{ + ulong i; + + if (from.len != to.len) + complain(1, "cannot connect nonisomorphic arrays"); + + for (i = 0; i < from.len; ++i) + { + if (from.arr[i].type != to.arr[i].type) + complain(1, "cannot connect nonisomorphic arrays"); + switch (from.arr[i].type) + { + break; case VARR: connectarrarr(from.arr[i], to.arr[i]); + break; case VNUM: addedge(from.arr[i].num, to.arr[i].num); + break; default: complain(1, "%s can not be connected", valname(from.arr[i].type)); + } + } +} + +struct val +connectval(struct val refl, struct val refr, struct scope *scope) +{ + struct val l, r; + + l = deref(refl, scope); + r = deref(refr, scope); + + if (l.type == VNUM && r.type == VNUM) + addedge(r.num, l.num); + else if (l.type == VARR && r.type == VNUM) + connectarr(l, r.num, 0); + else if (l.type == VNUM && r.type == VARR) + connectarr(r, l.num, 1); + else if (l.type == VARR && r.type == VARR) + connectarrarr(r, l); + else + complain(1, "cannot connect %s and %s", valname(l.type), valname(r.type)); + + if (refr.type != VVAR) + freeval(r); + + return refl; +} diff --git a/var.c b/var.c new file mode 100644 index 0000000..658f00b --- /dev/null +++ b/var.c @@ -0,0 +1,112 @@ +/* + * 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 <ctype.h> +#include <stdlib.h> +#include <string.h> + + +long +findvar(char *name, struct scope **scope) +{ + struct var *var; + ulong i; + + for (; *scope; *scope = (*scope)->parent) for (i = 0; i < (*scope)->len; ++i) + { + var = &(*scope)->vars[i]; + if (!strcmp(var->name, name)) + return i; + } + + return -1; +} + +ulong +assignvar(char *name, struct val val, struct scope **scope) +{ + struct scope *lscope; + long i; + + lscope = *scope; + i = findvar(name, &lscope); + if (i < 0) + { + /* TODO: add cap and increment like in lex.c */ + (*scope)->vars = realloc( + (*scope)->vars, + sizeof(*(*scope)->vars) * ++(*scope)->len + ); + if (!(*scope)->vars) + dieno(1, "realloc()"); + i = (*scope)->len - 1; + } else + { + scope = &lscope; + } + (*scope)->vars[i].name = name; + (*scope)->vars[i].val = val; + return i; +} + +int +varnextchar(struct scope *scope) +{ + struct var *var; + ulong i; + int c; + + for (c = -1; scope; scope = scope->parent) for (i = 0; i < scope->len; ++i) + { + var = &scope->vars[i]; + if (var->name[0] == '@' && islower(var->name[1])) + c = max(c, var->name[1]); + + if (c == -1) + continue; + + if (c == 'z') + return -1; + return var->name[1] + 1; + } + + return 'a'; +} + +ulong +varnextnum(struct scope *scope) +{ + struct var *var; + ulong i; + + for (; scope; scope = scope->parent) for (i = 0; i < scope->len; ++i) + { + var = &scope->vars[i]; + if (var->name[0] == '@' && isdigit(var->name[1])) + return atoll(&var->name[1]) + 1; + } + + return 0; +} + +void +delvarscope(struct scope *scope, ulong i) +{ + freeval(scope->vars[i].val); + scope->vars[i] = scope->vars[--scope->len]; +} diff --git a/vertex.c b/vertex.c new file mode 100644 index 0000000..b8290b1 --- /dev/null +++ b/vertex.c @@ -0,0 +1,100 @@ +/* + * 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. + */ +/* + * Unfortunately, the name "node" is already taken by the parser, so in + * this compiler language nodes are called vertices + */ + +#include "thac.h" + +#include <stddef.h> +#include <stdio.h> +#include <stdlib.h> + + +/* TODO: add cap and increase like in lex.c */ +static struct vertex *verts; +static ulong len; + +ulong +mkvert(void) +{ + static struct vertex vert = {{NULL, 0}, {NULL, 0}}; + + verts = realloc(verts, sizeof(*verts) * ++len); + if (!verts) + dieno(1, "realloc()"); + + verts[len - 1] = vert; + + return len - 1; +} + +void +addprop(ulong i, vlong prop) +{ + if (i < 0 || i >= len) + complain(1, "addprop: how would you want this? (0<=%lld<%lld)", i, len); + + verts[i].props.arr = realloc( + verts[i].props.arr, + sizeof(*verts[i].props.arr) * ++verts[i].props.len + ); + if (!verts[i].props.arr) + dieno(1, "realloc()"); + + verts[i].props.arr[verts[i].props.len - 1] = prop; +} + +void +addedge(ulong from, ulong to) +{ + ulong i; + + if (from < 0 || from >= len) + complain(1, "addedge: how would you want this? (0<=%lld<%lld)", from, len); + if (to < 0 || to >= len) + complain(1, "addedge: how would you want this? (0<=%lld<%lld)", from, len); + + for (i = 0; i < verts[from].nbrs.len; ++i) + if (verts[from].nbrs.arr[i] == to) + return; + + verts[from].nbrs.arr = realloc( + verts[from].nbrs.arr, + sizeof(*verts[from].nbrs.arr) * ++verts[from].nbrs.len + ); + if (!verts[from].nbrs.arr) + dieno(1, "realloc()"); + + verts[from].nbrs.arr[verts[from].nbrs.len - 1] = to; +} + +void +showverts(void) +{ + ulong i, j; + + for (i = 0; i < len; ++i) + { + for (j = 0; j < verts[i].props.len; ++j) + printf("%lld ", verts[i].props.arr[j]); + putchar('|'); + for (j = 0; j < verts[i].nbrs.len; ++j) + printf(" %lu", verts[i].nbrs.arr[j]); + putchar('\n'); + } +} |