#include #include #include #include #include #include #include #include #define TODO() errx(EXIT_FAILURE, "%s:%d: todo", __FILE__, __LINE__) struct fwdval; struct fwdvals; struct fwdcnt { size_t pc; struct fwdvalues *ctx; }; enum fwdtype { FWDSCRIPT_NUM, FWDSCRIPT_STR, FWDSCRIPT_CNT, FWDSCRIPT_TBL, FWDSCRIPT_OPQ }; static const char *fwdtype_str(enum fwdtype type) { switch (type) { case FWDSCRIPT_NUM: return "/num"; case FWDSCRIPT_STR: return "/str"; case FWDSCRIPT_CNT: return "/cnt"; case FWDSCRIPT_TBL: return "/tbl"; case FWDSCRIPT_OPQ: return "/opq"; default: break; } return "/???"; } struct fwdval { enum fwdtype kind; union { double num; char *str; struct fwdcnt cnt; struct fwdtbl *tbl; void *opq; }; }; #define FWDSTR(s) (struct fwdval){.kind = FWDSCRIPT_STR, .str = (s)} static int fwdcmp(const struct fwdval *a, const struct fwdval *b) { if (a->kind != b->kind) return a->kind - b->kind; switch (a->kind) { case FWDSCRIPT_NUM: return a->num - b->num; case FWDSCRIPT_STR: return strcmp(a->str, b->str); case FWDSCRIPT_CNT: return a->cnt.pc - b->cnt.pc; case FWDSCRIPT_TBL: return (intptr_t)a->tbl - (intptr_t)b->tbl; case FWDSCRIPT_OPQ: return (intptr_t)a->opq - (intptr_t)b->opq; default: abort(); } return 0; } static int fwdhash(const struct fwdval *a) { switch (a->kind) { /* trash hashes, lol */ case FWDSCRIPT_NUM: return a->num * 15647839; case FWDSCRIPT_STR: return a->str[0]; case FWDSCRIPT_CNT: return a->cnt.pc * 15647839; case FWDSCRIPT_TBL: return 10; case FWDSCRIPT_OPQ: return (intptr_t)a->opq; default: abort(); } return 0; } #define MAP_KEY struct fwdval #define MAP_TYPE struct fwdval #define MAP_CMP(A, B) fwdcmp(&(A), &(B)) #define MAP_HASH(A) fwdhash(&(A)) #define MAP_NAME fwdtbl #include #define MAP_KEY char * #define MAP_TYPE size_t #define MAP_CMP(A, B) strcmp((A), (B)) #define MAP_HASH(A) CONTS_MAP_STR_HASH(A) #define MAP_NAME fwdfuncs #include #define VEC_TYPE struct fwdval #define VEC_NAME fwdvals #include #define VEC_TYPE enum fwdtype #define VEC_NAME fwdtypes #include struct fwdinsn { /* use args[0] as destination, if /cnt then call the * continuation, if /opq call builtin, otherwise abort */ struct fwdvals args; }; #define VEC_TYPE struct fwdinsn #define VEC_NAME fwdinsns #include struct fwdscript; typedef int (*fwd_typecheck_t)(struct fwdscript *s, struct fwdtypes *types); typedef int (*fwd_callback_t)(struct fwdscript *s, struct fwdvals *args); struct fwdbuiltin { fwd_callback_t cb; fwd_typecheck_t tp; }; #define MAP_KEY char * #define MAP_TYPE struct fwdbuiltin #define MAP_CMP(A, B) strcmp((A), (B)) #define MAP_HASH(A) (A)[0] #define MAP_NAME fwdbuiltins #include #define VEC_TYPE struct fwdast * #define VEC_NAME fwdrefs #include 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 fwdctx ctx; struct fwdinsns insns; struct fwdbuiltins builtins; }; static struct fwdscript fwdscript_create() { struct fwdscript inst = { .insns = fwdinsns_create(1), .builtins = fwdbuiltins_create(1), .ctx = fwdctx_create() }; return inst; } static void *fwdscript_move(void **p) { void *c = *p; *p = NULL; return c; } static int fwdscript_register(struct fwdscript *s, const char *name, fwd_callback_t cb, fwd_typecheck_t tp) { struct fwdbuiltin builtin = { .cb = cb, .tp = tp }; char *dname = strdup(name); if (!dname) { fprintf(stderr, "failed duping register name\n"); return -1; } struct fwdbuiltin *b = fwdbuiltins_insert(&s->builtins, dname, builtin); if (b == NULL) { fprintf(stderr, "failed registering %s\n", name); free(dname); return -1; } return 0; } static int fwdscript_get(struct fwdscript *s, struct fwdvals *args) { TODO(); } static int fwdscript_get_typecheck(struct fwdscript *s, struct fwdtypes *types) { (void)s; size_t l = fwdtypes_len(types); if (l != 4) { /** @todo better error messages? */ fprintf(stderr, "expected 4 args, got %zu\n", l); return -1; } enum fwdtype table = *fwdtypes_at(types, 0); /* index can be whatever but would look something like */ //enum fwdtype index = fwdtypes_at(types, 1); enum fwdtype err = *fwdtypes_at(types, 2); enum fwdtype ok = *fwdtypes_at(types, 3); if (table != FWDSCRIPT_TBL) { fprintf(stderr, "expected /tbl, got %s\n", fwdtype_str(table)); return -1; } if (err != FWDSCRIPT_CNT) { fprintf(stderr, "expected /cnt, got %s\n", fwdtype_str(err)); return -1; } if (ok != FWDSCRIPT_CNT) { fprintf(stderr, "expected /cnt, got %s\n", fwdtype_str(ok)); return -1; } /* check cnt args at runtime */ return 0; } static int fwdscript_abort(struct fwdscript *s, struct fwdvals *args) { TODO(); } static int fwdscript_abort_typecheck(struct fwdscript *s, struct fwdtypes *types) { (void)s; /* I guess we could also accept anything that's convertible to string, * so I guess number for now? */ size_t l = fwdtypes_len(types); if (l != 1) { fprintf(stderr, "expected 1 arg, got %zu\n", l); return -1; } enum fwdtype msg = *fwdtypes_at(types, 0); if (msg != FWDSCRIPT_STR) { fprintf(stderr, "expected /str, got %s\n", fwdtype_str(msg)); return -1; } return 0; } static int fwdscript_print(struct fwdscript *s, struct fwdvals *args) { TODO(); } static int fwdscript_print_typecheck(struct fwdscript *s, struct fwdtypes *args) { (void)s; /* we take any number of args as long as they're printable */ foreach(fwdtypes, type, args) { if (*type != FWDSCRIPT_STR && *type != FWDSCRIPT_NUM) { fprintf(stderr, "arg not printable\n"); return -1; } } return 0; } static int fwdscript_register_builtins(struct fwdscript *s) { if (fwdscript_register(s, "get", fwdscript_get, fwdscript_get_typecheck)) return -1; if (fwdscript_register(s, "abort", fwdscript_abort, fwdscript_abort_typecheck)) return -1; if (fwdscript_register(s, "print", fwdscript_print, fwdscript_print_typecheck)) return -1; return 0; } static const char *skip_ignored(const char *buf) { while (*buf && isspace(*buf) && *buf != '\n') buf++; if (*buf == '#') while (*buf && *buf != '\n') buf++; return buf; } static int separator(int c) { return c == ';' || c == '{' || c == '}' || c == ';' || c == '=' || c == '"' || c == '\n'; } static const char *next_separator(const char *buf) { while (*buf && !isspace(*buf) && !separator(*buf)) buf++; return buf; } enum fwdtokentype { FWDTOKEN_ID, FWDTOKEN_STR, FWDTOKEN_NUM, FWDTOKEN_OPEN, FWDTOKEN_CLOSE, FWDTOKEN_TO, FWDTOKEN_END, FWDTOKEN_EOL, /* special token used for peeking */ FWDTOKEN_EOF }; struct fwdtoken { enum fwdtokentype kind; size_t line; union { char *s; double d; }; }; #define VEC_TYPE struct fwdtoken #define VEC_NAME fwdtokens #include static int fwdscript_tokenize(struct fwdtokens *tokens, const char *buf) { size_t line = 0; const char *start = buf; while (*start) { const char *tok = skip_ignored(start); if (!*tok) break; if (*tok == '\n') { struct fwdtoken eol = { .kind = FWDTOKEN_EOL, .line = line, .s = NULL }; if (!fwdtokens_append(tokens, eol)) { fprintf(stderr, "failed appending eol token\n"); return -1; } line += 1; start = tok + 1; continue; } if (*tok == ';') { struct fwdtoken end = { .kind = FWDTOKEN_END, .line = line, .s = NULL }; if (!fwdtokens_append(tokens, end)) { fprintf(stderr, "failed appending end token\n"); return -1; } start = tok + 1; continue; } if (*tok == '{') { struct fwdtoken open = { .kind = FWDTOKEN_OPEN, .line = line, .s = NULL }; if (!fwdtokens_append(tokens, open)) { fprintf(stderr, "failed appending open token\n"); return -1; } start = tok + 1; continue; } if (*tok == '}') { struct fwdtoken close = { .kind = FWDTOKEN_CLOSE, .line = line, .s = NULL }; if (!fwdtokens_append(tokens, close)) { fprintf(stderr, "failed appending close token\n"); return -1; } start = tok + 1; continue; } if (*tok == '=' && *(tok + 1) == '>') { struct fwdtoken to = { .kind = FWDTOKEN_TO, .line = line, .s = NULL }; if (!fwdtokens_append(tokens, to)) { fprintf(stderr, "failed appending to token\n"); return -1; } start = tok + 2; continue; } if (*tok == '"') { size_t start_line = line; const char *end = tok + 1; while (*end && *end != '"') { if (*end == '\n') { line++; while (isblank(*end)) end++; } if (*end == '\'') end++; end++; } if (!*end) { fprintf(stderr, "unterminated string at %zu\n", start_line); return -1; } char *s = strndup(tok + 1, end - tok - 1); if (!s) { fprintf(stderr, "failed duping str token\n"); return -1; } struct fwdtoken str = { .kind = FWDTOKEN_STR, .line = start_line, .s = s }; if (!fwdtokens_append(tokens, str)) { fprintf(stderr, "failed appending str token\n"); free(s); return -1; } start = end + 1; continue; } /* otherwise, read until next separation and check if we're * maybe dealing with a number or a regular identifier */ const char *end = next_separator(tok); char *dparse = NULL; double d = strtod(tok, &dparse); if (end == dparse) { /* all of the non-separators were consumed, so we have a * number */ struct fwdtoken num = { .kind = FWDTOKEN_NUM, .line = line, .d = d }; if (!fwdtokens_append(tokens, num)) { fprintf(stderr, "failed appending num token\n"); return -1; } start = end + 1; continue; } char *s = strndup(tok, end - tok); if (!s) { fprintf(stderr, "failed duping id token\n"); return -1; } struct fwdtoken id = { .kind = FWDTOKEN_ID, .line = line, .s = s }; if (!fwdtokens_append(tokens, id)) { fprintf(stderr, "failed appending id token\n"); free(s); return -1; } start = end; } return 0; } static void free_tokens(struct fwdtokens *tokens) { foreach(fwdtokens, token, tokens) { switch (token->kind) { case FWDTOKEN_STR: case FWDTOKEN_ID: free(token->s); default: break; } } fwdtokens_destroy(tokens); } struct fwdast { enum { FWDAST_ID, FWDAST_VAR, FWDAST_STR, FWDAST_NUM, FWDAST_CNT, FWDAST_CMD, FWDAST_TOP } kind; 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; }; /* node type and status */ bool moved; enum fwdtype t; /* cmd args and cnt params */ size_t argc; struct fwdast *args; size_t bodyc; struct fwdast *body; }; #define VEC_TYPE struct fwdast #define VEC_NAME fwdasts #include static struct fwdasts fwdast_body(struct fwdast *ast) { return (struct fwdasts){.n = ast->bodyc, .s = 0, .buf = ast->body}; } static struct fwdasts fwdast_args(struct fwdast *ast) { return (struct fwdasts){.n = ast->argc, .s = 0, .buf = ast->args}; } static void free_asts(struct fwdasts *asts); static void free_ast(struct fwdast *ast) { struct fwdasts args = fwdast_args(ast); struct fwdasts body = fwdast_body(ast); switch (ast->kind) { case FWDAST_TOP: free_asts(&body); break; case FWDAST_CMD: free(ast->s); free_asts(&args); break; case FWDAST_STR: case FWDAST_ID: case FWDAST_VAR: free(ast->s); break; case FWDAST_CNT: free(ast->s); free_asts(&args); free_asts(&body); break; default: break; } } static void free_asts(struct fwdasts *asts) { foreach(fwdasts, n, asts) { free_ast(n); } fwdasts_destroy(asts); } static enum fwdtokentype fwdtoken_peek(struct fwdtokens *tokens, ssize_t pos) { if ((size_t)pos >= fwdtokens_len(tokens)) return FWDTOKEN_EOF; return fwdtokens_at(tokens, (size_t)pos)->kind; } static int fwdtoken_to_fwdtype(struct fwdtoken *token, enum fwdtype *type) { assert(token->kind == FWDTOKEN_ID); if (strcmp(token->s, "/num") == 0) { *type = FWDSCRIPT_NUM; return 0; } if (strcmp(token->s, "/str") == 0) { *type = FWDSCRIPT_STR; return 0; } if (strcmp(token->s, "/cnt") == 0) { *type = FWDSCRIPT_CNT; return 0; } if (strcmp(token->s, "/tbl") == 0) { *type = FWDSCRIPT_TBL; return 0; } if (strcmp(token->s, "/opq") == 0) { *type = FWDSCRIPT_OPQ; return 0; } fprintf(stderr, "line %zu, unknown type %s\n", token->line, token->s); return -1; } static const char *fwdtoken_kind_str(enum fwdtokentype kind) { switch (kind) { case FWDTOKEN_ID: return "id"; case FWDTOKEN_STR: return "str"; case FWDTOKEN_NUM: return "num"; case FWDTOKEN_OPEN: return "{"; case FWDTOKEN_CLOSE: return "}"; case FWDTOKEN_TO: return "=>"; case FWDTOKEN_END: return ";"; case FWDTOKEN_EOL: return "end of line"; case FWDTOKEN_EOF: return "end of file"; default: break; } return "???"; } static struct fwdtoken *fwdtokens_consume(struct fwdtokens *tokens, ssize_t *pos, enum fwdtokentype ex) { if ((size_t)*pos >= fwdtokens_len(tokens)) { fprintf(stderr, "ran out of tokens\n"); return NULL; } struct fwdtoken *tok = fwdtokens_at(tokens, *pos); if (tok->kind != ex) { fprintf(stderr, "line %zu, expected %s but found %s\n", tok->line, fwdtoken_kind_str(ex), fwdtoken_kind_str(tok->kind)); return NULL; } *pos += 1; return tok; } static ssize_t fwdscript_parse_cmds(struct fwdtokens *tokens, ssize_t pos, struct fwdasts *cmds); static ssize_t fwdscript_parse_body(struct fwdtokens *tokens, ssize_t pos, struct fwdasts *body); static ssize_t fwdscript_parse_empty(struct fwdtokens *tokens, ssize_t pos) { /* skip until first interesting feature */ while (1) { if (fwdtoken_peek(tokens, pos) == FWDTOKEN_END) { pos++; continue; } if (fwdtoken_peek(tokens, pos) == FWDTOKEN_EOL) { pos++; continue; } break; } return pos; } static ssize_t fwdscript_parse_var(struct fwdtokens *tokens, ssize_t pos, struct fwdast *var) { struct fwdtoken *type = NULL; struct fwdtoken *arg = NULL; if ((type = fwdtokens_consume(tokens, &pos, FWDTOKEN_ID)) == NULL) return -1; if ((arg = fwdtokens_consume(tokens, &pos, FWDTOKEN_ID)) == NULL) return -1; enum fwdtype tk; if (fwdtoken_to_fwdtype(type, &tk)) return -1; var->kind = FWDAST_VAR; var->s = fwdscript_move((void **)&arg->s); var->t = tk; return pos; } static ssize_t fwdscript_parse_closure(struct fwdtokens *tokens, ssize_t pos, struct fwdast *arg) { if (fwdtokens_consume(tokens, &pos, FWDTOKEN_TO) == NULL) return -1; struct fwdasts vars = fwdasts_create(0); while (fwdtoken_peek(tokens, pos) == FWDTOKEN_ID) { struct fwdast var = {}; if ((pos = fwdscript_parse_var(tokens, pos, &var)) < 0) { free_asts(&vars); return -1; } if (fwdasts_append(&vars, var) == NULL) { fprintf(stderr, "failed appending ast var to closure\n"); free_asts(&vars); return -1; } } struct fwdasts body = fwdasts_create(0); if (fwdtoken_peek(tokens, pos) == FWDTOKEN_OPEN) { if ((pos = fwdscript_parse_body(tokens, pos, &body)) < 0) { free_asts(&vars); free_asts(&body); return -1; } } else if (fwdtoken_peek(tokens, pos) == FWDTOKEN_END) { if ((pos = fwdscript_parse_cmds(tokens, pos, &body)) < 0) { free_asts(&vars); free_asts(&body); return -1; } } arg->kind = FWDAST_CNT; arg->argc = vars.n; arg->args = vars.buf; arg->bodyc = body.n; arg->body = body.buf; return pos; } static ssize_t fwdscript_parse_arg(struct fwdtokens *tokens, ssize_t pos, struct fwdast *arg) { struct fwdtoken *tok = fwdtokens_at(tokens, pos); switch (tok->kind) { case FWDTOKEN_ID: arg->kind = FWDAST_ID; arg->s = fwdscript_move((void **)&tok->s); return pos + 1; case FWDTOKEN_STR: arg->kind = FWDAST_STR; arg->s = fwdscript_move((void **)&tok->s); return pos + 1; case FWDTOKEN_NUM: arg->kind = FWDAST_NUM; arg->d = tok->d; return pos + 1; case FWDTOKEN_TO: return fwdscript_parse_closure(tokens, pos, arg); default: break; } /* let whoever called us handle other cases (?) */ return pos; } static ssize_t fwdscript_parse_cmd(struct fwdtokens *tokens, ssize_t pos, struct fwdast *cmd) { if ((pos = fwdscript_parse_empty(tokens, pos)) < 0) return -1; struct fwdasts parts = fwdasts_create(0); struct fwdtoken *name = NULL; if ((name = fwdtokens_consume(tokens, &pos, FWDTOKEN_ID)) == NULL) { free_asts(&parts); return -1; } /* parse args */ while (1) { if (fwdtoken_peek(tokens, pos) == FWDTOKEN_EOL) { pos++; continue; } if (fwdtoken_peek(tokens, pos) == FWDTOKEN_END) break; if (fwdtoken_peek(tokens, pos) == FWDTOKEN_CLOSE) break; struct fwdast part = {}; if ((pos = fwdscript_parse_arg(tokens, pos, &part)) < 0) { free_asts(&parts); return -1; } if (fwdasts_append(&parts, part) == NULL) { fprintf(stderr, "failed appending ast arg to parts\n"); free_asts(&parts); return -1; } } cmd->kind = FWDAST_CMD; cmd->s = fwdscript_move((void **)&name->s); cmd->argc = parts.n; cmd->args = parts.buf; return pos; } static ssize_t fwdscript_parse_cmds(struct fwdtokens *tokens, ssize_t pos, struct fwdasts *cmds) { if ((pos = fwdscript_parse_empty(tokens, pos)) < 0) { return -1; } /* each command must start with an identifier */ while (fwdtoken_peek(tokens, pos) == FWDTOKEN_ID) { struct fwdast cmd = {}; if ((pos = fwdscript_parse_cmd(tokens, pos, &cmd)) < 0) return -1; if (fwdasts_append(cmds, cmd) == NULL) { fprintf(stderr, "failed appending at cmd to body\n"); return -1; } } if ((pos = fwdscript_parse_empty(tokens, pos)) < 0) return -1; return pos; } static ssize_t fwdscript_parse_body(struct fwdtokens *tokens, ssize_t pos, struct fwdasts *body) { if (fwdtokens_consume(tokens, &pos, FWDTOKEN_OPEN) == NULL) return -1; if ((pos = fwdscript_parse_cmds(tokens, pos, body)) < 0) return -1; if (fwdtokens_consume(tokens, &pos, FWDTOKEN_CLOSE) == NULL) return -1; return pos; } static ssize_t fwdscript_parse_def(struct fwdtokens *tokens, ssize_t pos, struct fwdast *def) { struct fwdtoken *name = NULL; if ((name = fwdtokens_consume(tokens, &pos, FWDTOKEN_ID)) == NULL) return -1; struct fwdasts args = fwdasts_create(0); while (fwdtoken_peek(tokens, pos) == FWDTOKEN_ID) { struct fwdast var = {}; if ((pos = fwdscript_parse_var(tokens, pos, &var)) < 0) { free_asts(&args); return -1; } if (fwdasts_append(&args, var) == NULL) { fprintf(stderr, "failed appending ast var to def\n"); free_asts(&args); return -1; } } struct fwdasts body = fwdasts_create(0); if ((pos = fwdscript_parse_body(tokens, pos, &body)) < 0) { free_asts(&args); return -1; } /* move everything into ast node */ def->kind = FWDAST_CNT, def->s = fwdscript_move((void **)&name->s), def->argc = args.n, def->args = args.buf, def->bodyc = body.n, def->body = body.buf; return pos; } static int fwdscript_parse_top(struct fwdtokens *tokens, struct fwdast *top) { ssize_t pos = 0; struct fwdasts defs = fwdasts_create(1); while (1) { while (pos < (ssize_t)fwdtokens_len(tokens) && fwdtokens_at(tokens, pos)->kind == FWDTOKEN_EOL) pos++; /* all input succesfully read */ if (pos == (ssize_t)fwdtokens_len(tokens)) break; struct fwdast tmp = {}; struct fwdast *def = NULL; if ((def = fwdasts_append(&defs, tmp)) == NULL) { fprintf(stderr, "couldn't append temp ast def\n"); free_asts(&defs); return -1; } if ((pos = fwdscript_parse_def(tokens, pos, def)) < 0) { free_asts(&defs); return -1; } } /* transfer ownership or array to top */ top->kind = FWDAST_TOP; top->s = NULL; top->argc = 0; top->args = NULL; top->bodyc = defs.n; top->body = defs.buf; return 0; } static void fwdscript_debug_tokens(struct fwdtokens *tokens) { foreach(fwdtokens, tok, tokens) { switch (tok->kind) { case FWDTOKEN_ID: fprintf(stderr, "line %4zu: %s (%s)\n", tok->line, fwdtoken_kind_str(tok->kind), tok->s); break; case FWDTOKEN_STR: fprintf(stderr, "line %4zu: %s \"%s\"\n", tok->line, fwdtoken_kind_str(tok->kind), tok->s); break; case FWDTOKEN_NUM: fprintf(stderr, "line %4zu: %s (%g)\n", tok->line, fwdtoken_kind_str(tok->kind), tok->d); break; case FWDTOKEN_OPEN: fprintf(stderr, "line %4zu: %s\n", tok->line, fwdtoken_kind_str(tok->kind)); break; case FWDTOKEN_CLOSE: fprintf(stderr, "line %4zu: %s\n", tok->line, fwdtoken_kind_str(tok->kind)); break; case FWDTOKEN_TO: fprintf(stderr, "line %4zu: %s\n", tok->line, fwdtoken_kind_str(tok->kind)); break; case FWDTOKEN_END: fprintf(stderr, "line %4zu: %s\n", tok->line, fwdtoken_kind_str(tok->kind)); break; case FWDTOKEN_EOL: fprintf(stderr, "line %4zu: %s\n", tok->line, fwdtoken_kind_str(tok->kind)); break; case FWDTOKEN_EOF: fprintf(stderr, "line %4zu: %s\n", tok->line, fwdtoken_kind_str(tok->kind)); break; } } } static void fwdscript_debug_ast(struct fwdast *ast, int depth) { /** @todo line numbering disappears from ast, not fantastic I guess? */ struct fwdasts args = fwdast_args(ast); struct fwdasts body = fwdast_body(ast); switch (ast->kind) { case FWDAST_ID: fprintf(stderr, "%*s+ id (%s)\n", depth, "|", ast->s); break; case FWDAST_VAR: fprintf(stderr, "%*s+ var (%s, %s)\n", depth, "|", ast->s, fwdtype_str(ast->t)); break; case FWDAST_STR: fprintf(stderr, "%*s+ str \"%s\"\n", depth, "|", ast->s); break; case FWDAST_NUM: fprintf(stderr, "%*s+ num (%g)\n", depth, "|", ast->d); break; case FWDAST_CNT: fprintf(stderr, "%*s+ cnt (%s)\n", depth, "|", ast->s ? ast->s : "closure"); foreach(fwdasts, n, &args) { fwdscript_debug_ast(n, depth + 1); } foreach(fwdasts, n, &body) { fwdscript_debug_ast(n, depth + 1); } break; case FWDAST_CMD: fprintf(stderr, "%*s+ cmd (%s)\n", depth, "|", ast->s); foreach(fwdasts, n, &args) { fwdscript_debug_ast(n, depth + 1); } break; case FWDAST_TOP: fprintf(stderr, "TOP\n"); foreach(fwdasts, n, &body) { fwdscript_debug_ast(n, depth + 1); } break; } } 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, ¶ms) { 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(¶ms); 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(¶ms, 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, ¶ms) { 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); if (fwdscript_tokenize(&tokens, buf)) { free_tokens(&tokens); return -1; } fwdscript_debug_tokens(&tokens); /* convert tokens into some kind of structure */ struct fwdast top = {}; if (fwdscript_parse_top(&tokens, &top)) { free_tokens(&tokens); return -1; } fwdscript_debug_ast(&top, 0); free_tokens(&tokens); if (fwdscript_analyze(s, &top)) { free_ast(&top); return -1; } /** @todo this means that the global ctx in s will be illegal to * reference after this point, which is presumably not ideal if we want * to support modules. Will have to think of something better. */ free_ast(&top); return 0; } static int fwdscript_run(struct fwdscript *s, const char *name, struct fwdvals *args) { TODO(); } static void fwdscript_destroy(struct fwdscript *s) { foreach(fwdbuiltins, builtin, &s->builtins) { free(builtin.t->key); } fwdctx_destroy(&s->ctx); fwdinsns_destroy(&s->insns); fwdbuiltins_destroy(&s->builtins); } static const char *read_file(const char *fname) { FILE *f = fopen(fname, "rb"); if (!f) err(EXIT_FAILURE, "failed opening %s", fname); if (fseek(f, 0, SEEK_END) < 0) err(EXIT_FAILURE, "failed seeking %s", fname); long sz = ftell(f); if (sz < 0) err(EXIT_FAILURE, "failed telling %s", fname); if (fseek(f, 0, SEEK_SET) < 0) err(EXIT_FAILURE, "failed rewinding %s", fname); char *buf = malloc(sz + 1); if (!buf) err(EXIT_FAILURE, "failed allocating buf for %s", fname); if ((long)fread(buf, 1, sz, f) != sz) err(EXIT_FAILURE, "failed reading file %s", fname); /* trailing zero */ buf[sz] = '\0'; fclose(f); return buf; } int main(int argc, char *argv[]) { /* a bit silly but good enough for testing */ const char *buf = "main /tbl args {\n" " get args 0 => {abort \"impossible\"}\n" " => /str line;\n" " print line\n" "}\n"; if (argc == 2) buf = read_file(argv[1]); int ret = 0; struct fwdscript inst = fwdscript_create(); ret = fwdscript_register_builtins(&inst); assert(ret == 0); ret = fwdscript_parse(&inst, buf); assert(ret == 0); struct fwdvals args = fwdvals_create(1); fwdvals_append(&args, FWDSTR(strdup("Hello world!\n"))); fwdscript_run(&inst, "main", &args); /* string should've been consumed by main so we shouldn't try to free it */ fwdvals_destroy(&args); fwdscript_destroy(&inst); if (argc == 2) free((void *)buf); }