From 3756806674da909ec6dc10ad25862b592792604e Mon Sep 17 00:00:00 2001 From: Jo-Philipp Wich Date: Wed, 23 Dec 2020 20:54:05 +0100 Subject: treewide: rewrite ucode interpreter Replace the former AST walking interpreter implementation with a single pass bytecode compiler and a corresponding virtual machine. The rewrite lays the groundwork for a couple of improvements with will be subsequently implemented: - Ability to precompile ucode sources into binary byte code - Strippable debug information - Reduced runtime memory usage Signed-off-by: Jo-Philipp Wich --- main.c | 249 +++++++++++++++++++++++++---------------------------------------- 1 file changed, 97 insertions(+), 152 deletions(-) (limited to 'main.c') diff --git a/main.c b/main.c index 11c191b..18a7d3b 100644 --- a/main.c +++ b/main.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2020 Jo-Philipp Wich + * Copyright (C) 2020-2021 Jo-Philipp Wich * * Permission to use, copy, modify, and/or distribute this software for any * purpose with or without fee is hereby granted, provided that the above @@ -19,6 +19,7 @@ #include #include #include +#include #include #ifdef JSONC @@ -27,10 +28,11 @@ #include #endif +#include "compiler.h" #include "lexer.h" -#include "parser.h" -#include "eval.h" #include "lib.h" +#include "vm.h" +#include "source.h" static void @@ -52,103 +54,56 @@ print_usage(char *app) app); } -#ifndef NDEBUG -static void dump(struct uc_state *state, uint32_t off, int level); - -static void dump_node(struct uc_state *state, uint32_t off) { - const char *p; - - switch (OP_TYPE(off)) { - case T_NUMBER: - printf("n%u [label=\"%"PRId64"\"];\n", off, json_object_get_int64(OP_VAL(off))); - break; - - case T_DOUBLE: - printf("n%u [label=\"%f\"];\n", off, json_object_get_double(OP_VAL(off))); - break; - - case T_BOOL: - printf("n%u [label=\"%s\"];\n", off, json_object_get_boolean(OP_VAL(off)) ? "true" : "false"); - break; - - case T_STRING: - case T_LABEL: - case T_TEXT: - printf("n%u [label=\"%s<", off, uc_get_tokenname(OP_TYPE(off))); - - for (p = json_object_get_string(OP_VAL(off)); *p; p++) - switch (*p) { - case '\n': - printf("\\\n"); - break; +static void +globals_init(uc_prototype *scope) +{ + json_object *arr = xjs_new_array(); + const char *p, *last; - case '\t': - printf("\\\t"); - break; + for (p = last = LIB_SEARCH_PATH;; p++) { + if (*p == ':' || *p == '\0') { + json_object_array_add(arr, xjs_new_string_len(last, p - last)); - case '"': - printf("\\\""); + if (!*p) break; - default: - printf("%c", *p); - } - - printf(">\"];\n"); - break; - - default: - printf("n%u [label=\"%s", off, uc_get_tokenname(OP_TYPE(off))); - - if (OP_IS_POSTFIX(off)) - printf(", postfix"); - - printf("\"];\n"); - } -} - -static void dump(struct uc_state *state, uint32_t off, int level) { - uint32_t prev_off, cur_off, child_off; - int i; - - if (level == 0) { - printf("digraph G {\nmain [shape=box];\n"); + last = p + 1; + } } - for (prev_off = 0, cur_off = off; cur_off != 0; prev_off = cur_off, cur_off = OP_NEXT(cur_off)) { - dump_node(state, cur_off); - - if (OP_TYPE(cur_off) < __T_MAX) { - for (i = 0; i < OPn_NUM; i++) { - child_off = OPn(cur_off, i); + json_object_object_add(scope->header.jso, "REQUIRE_SEARCH_PATH", arr); +} - if (child_off) { - dump(state, child_off, level + 1); - printf("n%u -> n%u [label=\"op%d\"];\n", cur_off, child_off, i + 1); - } - } - } +static void +register_variable(uc_prototype *scope, const char *key, json_object *val) +{ + char *name = strdup(key); + char *p; - if (prev_off) - printf("n%u -> n%u [style=dotted];\n", prev_off, cur_off); - } + if (!name) + return; - if (level == 0) { - printf("main -> n%u [style=dotted];\n", off); + for (p = name; *p; p++) + if (!isalnum(*p) && *p != '_') + *p = '_'; - printf("}\n"); - } + json_object_object_add(scope->header.jso, name, val); + free(name); } -#endif /* NDEBUG */ + static int -parse(struct uc_state *state, struct uc_source *src, bool dumponly, - bool skip_shebang, struct json_object *env, struct json_object *modules) +parse(uc_parse_config *config, uc_source *src, + bool skip_shebang, json_object *env, json_object *modules) { - struct json_object *rv; - char c, c2, *msg; + uc_prototype *globals = uc_prototype_new(NULL); + uc_function *entry; + uc_vm vm = {}; + char c, c2, *err; int rc = 0; + uc_vm_init(&vm, config); + if (skip_shebang) { c = fgetc(src->fp); c2 = fgetc(src->fp); @@ -167,33 +122,42 @@ parse(struct uc_state *state, struct uc_source *src, bool dumponly, } } - if (dumponly) { -#ifdef NDEBUG - rv = uc_new_exception(state, 0, "Debug support not compiled in"); -#else /* NDEBUG */ - rv = uc_parse(state, src->fp); + entry = uc_compile(config, src, &err); - if (!uc_is_type(rv, T_EXCEPTION)) - dump(state, state->main, 0); -#endif /* NDEBUG */ + if (!entry) { + fprintf(stderr, "%s", err); + free(err); + rc = 2; + goto out; } - else { - rv = uc_run(state, env, modules); + + /* load global variables */ + globals_init(globals); + + /* load env variables */ + if (env) { + json_object_object_foreach(env, key, val) + register_variable(globals, key, uc_value_get(val)); } - if (uc_is_type(rv, T_EXCEPTION)) { - msg = uc_format_error(state, src->fp); - fprintf(stderr, "%s\n\n", msg); - free(msg); + /* load std functions into global scope */ + uc_lib_init(globals); + + rc = uc_vm_execute(&vm, entry, globals, modules); + + if (rc) { rc = 1; + goto out; } - json_object_put(rv); +out: + uc_vm_free(&vm); + uc_value_put(globals->header.jso); return rc; } -static FILE * +static uc_source * read_stdin(char **ptr) { size_t rlen = 0, tlen = 0; @@ -217,13 +181,13 @@ read_stdin(char **ptr) tlen += rlen; } - return fmemopen(*ptr, tlen, "rb"); + return uc_source_new_buffer("[stdin]", *ptr, tlen); } -static struct json_object * +static json_object * parse_envfile(FILE *fp) { - struct json_object *rv = NULL; + json_object *rv = NULL; enum json_tokener_error err; struct json_tokener *tok; char buf[128]; @@ -257,29 +221,25 @@ parse_envfile(FILE *fp) int main(int argc, char **argv) { - struct json_object *env = NULL, *modules = NULL, *o, *p; - struct uc_state *state = NULL; - struct uc_source source = {}; + json_object *env = NULL, *modules = NULL, *o, *p; + uc_source *source = NULL, *envfile = NULL; char *stdin = NULL, *c; - bool dumponly = false; bool shebang = false; - FILE *envfile = NULL; int opt, rv = 0; + uc_parse_config config = { + .strict_declarations = false, + .lstrip_blocks = true, + .trim_blocks = true + }; + if (argc == 1) { print_usage(argv[0]); goto out; } - state = xalloc(sizeof(*state)); - state->lstrip_blocks = 1; - state->trim_blocks = 1; - - /* reserve opcode slot 0 */ - uc_new_op(state, 0, NULL, UINT32_MAX); - - while ((opt = getopt(argc, argv, "dhlrSe:E:i:s:m:")) != -1) + while ((opt = getopt(argc, argv, "hlrSe:E:i:s:m:")) != -1) { switch (opt) { case 'h': @@ -287,19 +247,15 @@ main(int argc, char **argv) goto out; case 'i': - if (source.fp) + if (source) fprintf(stderr, "Options -i and -s are exclusive\n"); - if (!strcmp(optarg, "-")) { - source.fp = read_stdin(&stdin); - source.filename = xstrdup("[stdin]"); - } - else { - source.fp = fopen(optarg, "rb"); - source.filename = xstrdup(optarg); - } + if (!strcmp(optarg, "-")) + source = read_stdin(&stdin); + else + source = uc_source_new_file(optarg); - if (!source.fp) { + if (!source) { fprintf(stderr, "Failed to open %s: %s\n", optarg, strerror(errno)); rv = 1; goto out; @@ -307,28 +263,23 @@ main(int argc, char **argv) break; - case 'd': - dumponly = true; - break; - case 'l': - state->lstrip_blocks = 0; + config.lstrip_blocks = false; break; case 'r': - state->trim_blocks = 0; + config.trim_blocks = false; break; case 's': - if (source.fp) + if (source) fprintf(stderr, "Options -i and -s are exclusive\n"); - source.fp = fmemopen(optarg, strlen(optarg), "rb"); - source.filename = xstrdup("[-s argument]"); + source = uc_source_new_buffer("[-s argument]", xstrdup(optarg), strlen(optarg)); break; case 'S': - state->strict_declarations = 1; + config.strict_declarations = true; break; case 'e': @@ -339,7 +290,7 @@ main(int argc, char **argv) else c = optarg; - envfile = fmemopen(c, strlen(c), "rb"); + envfile = uc_source_new_buffer("[-e argument]", xstrdup(c), strlen(c)); /* fallthrough */ case 'E': @@ -354,7 +305,7 @@ main(int argc, char **argv) if (!strcmp(c, "-")) envfile = read_stdin(&stdin); else - envfile = fopen(c, "rb"); + envfile = uc_source_new_file(c); if (!envfile) { fprintf(stderr, "Failed to open %s: %s\n", c, strerror(errno)); @@ -363,9 +314,9 @@ main(int argc, char **argv) } } - o = parse_envfile(envfile); + o = parse_envfile(envfile->fp); - fclose(envfile); + uc_source_put(envfile); envfile = NULL; @@ -401,11 +352,10 @@ main(int argc, char **argv) } } - if (!source.fp && argv[optind] != NULL) { - source.fp = fopen(argv[optind], "rb"); - source.filename = xstrdup(argv[optind]); + if (!source && argv[optind] != NULL) { + source = uc_source_new_file(argv[optind]); - if (!source.fp) { + if (!source) { fprintf(stderr, "Failed to open %s: %s\n", argv[optind], strerror(errno)); rv = 1; goto out; @@ -414,24 +364,19 @@ main(int argc, char **argv) shebang = true; } - if (!source.fp) { + if (!source) { fprintf(stderr, "One of -i or -s is required\n"); rv = 1; goto out; } - state->source = xalloc(sizeof(source)); - state->sources = state->source; - *state->source = source; - - rv = parse(state, state->source, dumponly, shebang, env, modules); + rv = parse(&config, source, shebang, env, modules); out: json_object_put(modules); json_object_put(env); - uc_free(state); - free(stdin); + uc_source_put(source); return rv; } -- cgit v1.2.3