From 8afa976e073c7bc29c878230eead6dddfdcc08a1 Mon Sep 17 00:00:00 2001 From: Nakidai Date: Sun, 5 Apr 2026 19:51:14 +0300 Subject: Add code --- .gitignore | 4 + LICENSE | 13 ++ Makefile | 20 ++ README | 18 ++ compile.c | 480 +++++++++++++++++++++++++++++++++++++++++++ debug.c | 177 ++++++++++++++++ doc/thac.1 | 53 +++++ doc/thalatta.5 | 299 +++++++++++++++++++++++++++ lex.c | 279 +++++++++++++++++++++++++ main.c | 61 ++++++ parse.c | 637 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++ say.c | 89 ++++++++ str.c | 33 +++ thac.h | 323 +++++++++++++++++++++++++++++ val.c | 144 +++++++++++++ var.c | 112 ++++++++++ vertex.c | 100 +++++++++ 17 files changed, 2842 insertions(+) create mode 100644 .gitignore create mode 100644 LICENSE create mode 100644 Makefile create mode 100644 README create mode 100644 compile.c create mode 100644 debug.c create mode 100644 doc/thac.1 create mode 100644 doc/thalatta.5 create mode 100644 lex.c create mode 100644 main.c create mode 100644 parse.c create mode 100644 say.c create mode 100644 str.c create mode 100644 thac.h create mode 100644 val.c create mode 100644 var.c create mode 100644 vertex.c 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 + +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 + * + * 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, 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 + * + * 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 + * + * 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 +#include +#include + + +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 + * + * 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 + + +FILE *curfile; +char *curname, *progname; + +void +usage(char *name) +{ + fprintf(stderr, "usage: %s \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 + * + * 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 + + +/* + * 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 + * + * 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 +#include +#include + + +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 + * + * 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 + + +#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 + * + * 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 + + +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) /* */ \ + 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) /* */ \ + 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 + * + * 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 + + +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 + * + * 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 + + +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 + * + * 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 +#include +#include + + +/* 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'); + } +} -- cgit 1.4.1