summaryrefslogtreecommitdiff
path: root/src/fwdscript.c
diff options
context:
space:
mode:
authorKimplul <kimi.h.kuparinen@gmail.com>2026-08-01 19:46:40 +0300
committerKimplul <kimi.h.kuparinen@gmail.com>2026-08-01 19:46:40 +0300
commitc9c04c869051def472b3d3e381ef0682b46bc67a (patch)
treee1449dfb0e4a4592e71033c12f40b7ca3fe508b7 /src/fwdscript.c
parent2e688cefa1620a6dd79f917003e1317b277d805e (diff)
downloadfwdscript-c9c04c869051def472b3d3e381ef0682b46bc67a.tar.gz
fwdscript-c9c04c869051def472b3d3e381ef0682b46bc67a.zip
analysis more-or-less works
+ No tests so probably missing a lot of bugs, hah + Next up, implement the builtins and then it's just (just) running
Diffstat (limited to 'src/fwdscript.c')
-rw-r--r--src/fwdscript.c392
1 files changed, 384 insertions, 8 deletions
diff --git a/src/fwdscript.c b/src/fwdscript.c
index a44fa93..215f782 100644
--- a/src/fwdscript.c
+++ b/src/fwdscript.c
@@ -132,8 +132,45 @@ struct fwdbuiltin {
#define MAP_NAME fwdbuiltins
#include <conts/map.h>
+#define VEC_TYPE struct fwdast *
+#define VEC_NAME fwdrefs
+#include <conts/vec.h>
+
+struct fwdctx {
+ struct fwdfuncs defs;
+ struct fwdrefs refs;
+};
+
+static struct fwdctx fwdctx_create()
+{
+ return (struct fwdctx){
+ .defs = fwdfuncs_create(0),
+ .refs = fwdrefs_create(0)
+ };
+}
+
+static void fwdctx_destroy(struct fwdctx *ctx)
+{
+ fwdfuncs_destroy(&ctx->defs);
+ fwdrefs_destroy(&ctx->refs);
+}
+
+static struct fwdast *fwdctx_lookup(struct fwdctx *global, struct fwdctx *local, char *name)
+{
+ size_t *ref = NULL;
+ if (local)
+ if ((ref = fwdfuncs_find(&local->defs, name)) != NULL)
+ return *fwdrefs_at(&local->refs, *ref);
+
+ if (global)
+ if ((ref = fwdfuncs_find(&global->defs, name)) != NULL)
+ return *fwdrefs_at(&global->refs, *ref);
+
+ return NULL;
+}
+
struct fwdscript {
- struct fwdfuncs funcs;
+ struct fwdctx ctx;
struct fwdinsns insns;
struct fwdbuiltins builtins;
};
@@ -141,9 +178,9 @@ struct fwdscript {
static struct fwdscript fwdscript_create()
{
struct fwdscript inst = {
- .funcs = fwdfuncs_create(1),
.insns = fwdinsns_create(1),
- .builtins = fwdbuiltins_create(1)
+ .builtins = fwdbuiltins_create(1),
+ .ctx = fwdctx_create()
};
return inst;
@@ -491,12 +528,30 @@ struct fwdast {
char *s;
union {
+ /* reference to local var > 0, global < 0 (= 0 is invalid)
+ * To get the actual index, calculate abs(var) - 1.
+ *
+ * Populated during analysis */
+ ssize_t ref;
+
+ /* number value */
double d;
- enum fwdtype t;
+
};
- size_t argc;
- struct fwdast *args;
+ union {
+ /* node type and status */
+ struct {
+ bool moved;
+ enum fwdtype t;
+ };
+
+ /* cmd args and cnt params */
+ struct {
+ size_t argc;
+ struct fwdast *args;
+ };
+ };
size_t bodyc;
struct fwdast *body;
@@ -1044,6 +1099,322 @@ static void fwdscript_debug_ast(struct fwdast *ast, int depth)
}
}
+static int fwdctx_add(struct fwdctx *ctx, struct fwdast *symb)
+{
+ assert(symb->s);
+ if (fwdrefs_append(&ctx->refs, symb) == NULL) {
+ fprintf(stderr, "couldn't append new ref to fwdctx\n");
+ return -1;
+ }
+
+ size_t handle = fwdrefs_len(&ctx->refs) - 1;
+ /* not really a func, should maybe rename structure to be more generic? */
+ size_t *eh = fwdfuncs_insert(&ctx->defs, symb->s, handle);
+ if (!eh) {
+ fprintf(stderr, "couldn't insert ref to fwdfuncs\n");
+ return -1;
+ }
+
+ /* overwrite possible existing data (shadowing) */
+ if (*eh != handle)
+ *eh = handle;
+
+ return 0;
+}
+
+static int fwdscript_analyze_body(struct fwdscript *s, struct fwdasts *body, struct fwdctx *ctx, bool last);
+
+static int fwdscript_analyze_id(struct fwdscript *s, struct fwdast *id, struct fwdctx *ctx)
+{
+ assert(id->kind == FWDAST_ID);
+ struct fwdast *def = NULL;
+
+ /* only look for variable in the local scope since we don't have global
+ * variables and we don't support callbacks to global defs */
+ if ((def = fwdctx_lookup(NULL, ctx, id->s)) == NULL) {
+ fprintf(stderr, "no such variable: %s\n", id->s);
+ return -1;
+ }
+
+ assert(def->kind == FWDAST_VAR);
+
+ if (def->t != FWDSCRIPT_NUM && def->moved) {
+ fprintf(stderr, "trying to reuse moved var %s\n", def->s);
+ return -1;
+ }
+
+ def->moved = true;
+ id->t = def->t;
+ return 0;
+}
+
+static int fwdscript_analyze_var(struct fwdscript *s, struct fwdast *var, struct fwdctx *ctx)
+{
+ if (fwdctx_add(ctx, var)) {
+ fprintf(stderr, "failed adding %s to local context\n", var->s);
+ return -1;
+ }
+
+ return 0;
+}
+
+static int fwdscript_analyze_cnt(struct fwdscript *s, struct fwdast *cnt, struct fwdctx *ctx, bool last)
+{
+ assert(cnt->kind == FWDAST_CNT);
+ struct fwdasts params = fwdast_args(cnt);
+ struct fwdasts body = fwdast_body(cnt);
+
+ foreach(fwdasts, p, &params) {
+ if (fwdscript_analyze_var(s, p, ctx))
+ return -1;
+ }
+
+ cnt->t = FWDSCRIPT_CNT;
+ return fwdscript_analyze_body(s, &body, ctx, last);
+}
+
+static int fwdscript_analyze_arg(struct fwdscript *s, struct fwdast *arg, struct fwdctx *ctx, bool last)
+{
+ switch (arg->kind) {
+ case FWDAST_NUM:
+ arg->t = FWDSCRIPT_NUM;
+ return 0;
+
+ case FWDAST_STR:
+ arg->t = FWDSCRIPT_STR;
+ return 0;
+
+ case FWDAST_CNT:
+ /* for now, the body is handled in a separate pass later on so
+ * we don't overwrite local variable names */
+ arg->t = FWDSCRIPT_CNT;
+ return 0;
+
+ case FWDAST_ID:
+ return fwdscript_analyze_id(s, arg, ctx);
+
+ default:
+ break;
+ }
+
+ /* should never get here */
+ abort();
+ return -1;
+}
+
+static int fwdscript_analyze_builtin(struct fwdscript *s, struct fwdast *builtin, struct fwdctx *ctx, bool last)
+{
+ assert(builtin->kind == FWDAST_CMD && builtin->s);
+ struct fwdbuiltin *def = NULL;
+ if ((def = fwdbuiltins_find(&s->builtins, builtin->s)) == NULL) {
+ fprintf(stderr, "no such cmd: %s\n", builtin->s);
+ return -1;
+ }
+
+
+ bool must_be_last = false;
+ struct fwdasts args = fwdast_args(builtin);
+ struct fwdtypes types = fwdtypes_create(fwdasts_len(&args));
+ foreach(fwdasts, arg, &args) {
+ if (fwdscript_analyze_arg(s, arg, ctx, last))
+ return -1;
+
+ if (arg->kind == FWDAST_CNT)
+ must_be_last = true;
+
+ if (fwdtypes_append(&types, arg->t) == NULL) {
+ fprintf(stderr, "failed appending type to builtin call\n");
+ return -1;
+ }
+ }
+
+ if (must_be_last && !last) {
+ fprintf(stderr, "%s ref call should be last, but isn't\n", builtin->s);
+ return -1;
+ }
+
+ if (def->tp(s, &types)) {
+ fwdtypes_destroy(&types);
+ return -1;
+ }
+
+ fwdtypes_destroy(&types);
+
+ foreach(fwdasts, arg, &args) {
+ if (arg->kind != FWDAST_CNT)
+ continue;
+
+ if (fwdscript_analyze_cnt(s, arg, ctx, last))
+ return -1;
+ }
+
+ return 0;
+}
+
+static int fwdscript_analyze_ref_call(struct fwdscript *s, struct fwdast *cmd, struct fwdast *def, struct fwdctx *ctx, bool last)
+{
+ assert(def->kind == FWDAST_VAR);
+ if (def->t != FWDSCRIPT_CNT) {
+ fprintf(stderr, "trying to call %s %s\n",
+ fwdtype_str(def->t),
+ def->s);
+ return -1;
+ }
+
+ /* needs to be checked at runtime, but we can at least do the move
+ * checks here */
+ bool must_be_last = false;
+ struct fwdasts args = fwdast_args(cmd);
+ foreach(fwdasts, arg, &args) {
+ if (fwdscript_analyze_arg(s, arg, ctx, last))
+ return -1;
+
+ if (arg->kind == FWDAST_CNT)
+ must_be_last = true;
+ }
+
+ if (must_be_last && !last) {
+ fprintf(stderr, "%s ref call should be last, but isn't\n", cmd->s);
+ return -1;
+ }
+
+ foreach(fwdasts, arg, &args) {
+ if (arg->kind != FWDAST_CNT)
+ continue;
+
+ if (fwdscript_analyze_cnt(s, arg, ctx, last))
+ return -1;
+ }
+
+ return 0;
+}
+
+static int fwdscript_analyze_call(struct fwdscript *s, struct fwdast *cmd, struct fwdast *def, struct fwdctx *ctx, bool last)
+{
+ bool must_be_last = false;
+ struct fwdasts params = fwdast_args(def);
+ struct fwdasts args = fwdast_args(cmd);
+
+ size_t pl = fwdasts_len(&params);
+ size_t al = fwdasts_len(&args);
+
+ if (pl != al) {
+ fprintf(stderr, "%s expects %zu args, found %zu\n",
+ cmd->s, pl, al);
+ return -1;
+ }
+
+ for (size_t i = 0; i < pl; ++i) {
+ struct fwdast *param = fwdasts_at(&params, i);
+ struct fwdast *arg = fwdasts_at(&args, i);
+ assert(param);
+ assert(arg);
+
+ if (fwdscript_analyze_arg(s, arg, ctx, last))
+ return -1;
+
+ if (arg->kind == FWDAST_CNT)
+ must_be_last = true;
+
+ if (param->t != arg->t) {
+ fprintf(stderr, "%s %s expects %s, found %s\n",
+ cmd->s, param->s,
+ fwdtype_str(param->t),
+ fwdtype_str(arg->t));
+ return -1;
+ }
+ }
+
+ if (must_be_last && !last) {
+ fprintf(stderr, "%s call should be last, but isn't\n", cmd->s);
+ return -1;
+ }
+
+ foreach(fwdasts, arg, &args) {
+ if (arg->kind != FWDAST_CNT)
+ continue;
+
+ if (fwdscript_analyze_cnt(s, arg, ctx, last))
+ return -1;
+ }
+
+ return 0;
+}
+
+static int fwdscript_analyze_cmd(struct fwdscript *s, struct fwdast *cmd, struct fwdctx *ctx, bool last)
+{
+ assert(cmd->kind == FWDAST_CMD && cmd->s);
+ struct fwdast *def = NULL;
+ if ((def = fwdctx_lookup(&s->ctx, ctx, cmd->s)) == NULL)
+ return fwdscript_analyze_builtin(s, cmd, ctx, last);
+
+ if (def->kind == FWDAST_VAR)
+ return fwdscript_analyze_ref_call(s, cmd, def, ctx, last);
+
+ return fwdscript_analyze_call(s, cmd, def, ctx, last);
+}
+
+static int fwdscript_analyze_body(struct fwdscript *s, struct fwdasts *body, struct fwdctx *ctx, bool last)
+{
+ size_t n = fwdasts_len(body);
+ for (size_t i = 0; i < n; ++i) {
+ bool last_cmd = false;
+ if (i == n - 1)
+ last_cmd = last;
+
+ struct fwdast *cmd = fwdasts_at(body, i);
+ if (fwdscript_analyze_cmd(s, cmd, ctx, last_cmd))
+ return -1;
+ }
+
+ return 0;
+}
+
+static int fwdscript_analyze_def(struct fwdscript *s, struct fwdast *def)
+{
+ assert(def->kind == FWDAST_CNT && def->s);
+
+ /* add us to the global symbol table */
+ if (fwdctx_add(&s->ctx, def))
+ return -1;
+
+ /* create a local symbol table */
+ struct fwdctx ctx = fwdctx_create();
+
+ /* add local variables to local context */
+ struct fwdasts params = fwdast_args(def);
+ foreach(fwdasts, p, &params) {
+ p->moved = false;
+
+ if (fwdctx_add(&ctx, p)) {
+ fwdctx_destroy(&ctx);
+ return -1;
+ }
+ }
+
+ struct fwdasts body = fwdast_body(def);
+ if (fwdscript_analyze_body(s, &body, &ctx, true)) {
+ fwdctx_destroy(&ctx);
+ return -1;
+ }
+
+ fwdctx_destroy(&ctx);
+ return 0;
+}
+
+static int fwdscript_analyze(struct fwdscript *s, struct fwdast *top)
+{
+ assert(top->kind == FWDAST_TOP);
+
+ struct fwdasts defs = fwdast_body(top);
+ foreach(fwdasts, def, &defs) {
+ if (fwdscript_analyze_def(s, def))
+ return -1;
+ }
+
+ return 0;
+}
+
static int fwdscript_parse(struct fwdscript *s, const char *buf)
{
struct fwdtokens tokens = fwdtokens_create(0);
@@ -1065,6 +1436,12 @@ static int fwdscript_parse(struct fwdscript *s, const char *buf)
fwdscript_debug_ast(&top, 0);
free_tokens(&tokens);
+
+ if (fwdscript_analyze(s, &top)) {
+ free_ast(&top);
+ return -1;
+ }
+
return 0;
}
@@ -1078,7 +1455,6 @@ static void fwdscript_destroy(struct fwdscript *s)
foreach(fwdbuiltins, builtin, &s->builtins) {
free(builtin.t->key);
}
- fwdfuncs_destroy(&s->funcs);
fwdinsns_destroy(&s->insns);
fwdbuiltins_destroy(&s->builtins);
}
@@ -1093,7 +1469,7 @@ int main()
ret = fwdscript_parse(&inst,
"main /tbl args {\n"
- " get /str args 0 => {abort \"impossible\"}\n"
+ " get args 0 => {abort \"impossible\"}\n"
" => /str line;\n"
" print line\n"
"}\n");