From 46188077ef727c21513008f4e0c42e8cb211e90e Mon Sep 17 00:00:00 2001 From: Jo-Philipp Wich Date: Mon, 14 Mar 2022 08:47:58 +0100 Subject: main: rework CLI frontend - Change command line flags to be align better with those of other interpreters and with the gcc compiler, e.g. `-D` and `-U` to define and undefine globals, `-e` to execute script expression etc. - Pass only excess CLI arguments as `ARGV` to scripts, e.g. `ucode -e 'print("Hello world")' -- -x -y` would pass only `[ "-x", "-y" ]` as ARGV contents - Default to raw mode and introduce flag to enable template mode Signed-off-by: Jo-Philipp Wich --- main.c | 563 +++++++++++++++++++++++++++++++--------------- tests/cram/test_basic.t | 97 +++++--- tests/custom/run_tests.sh | 5 +- 3 files changed, 452 insertions(+), 213 deletions(-) diff --git a/main.c b/main.c index f9ce6f0..b825fdb 100644 --- a/main.c +++ b/main.c @@ -20,7 +20,9 @@ #include #include #include +#include #include +#include #ifdef JSONC #include @@ -35,50 +37,80 @@ #include "ucode/source.h" #include "ucode/program.h" +static FILE *stdin_unused; static void print_usage(const char *app) { - printf( - "Usage\n\n" - " # %s [-t] [-l] [-r] [-S] [-R] [-x function [-x ...]] [-e '[prefix=]{\"var\": ...}'] [-E [prefix=]env.json] {-i | -s \"ucode script...\"}\n" - " -h, --help Print this help\n" - " -i file Execute the given ucode script file\n" - " -s \"ucode script...\" Execute the given string as ucode script\n" - " -t Enable VM execution tracing\n" - " -l Do not strip leading block whitespace\n" - " -r Do not trim trailing block newlines\n" - " -S Enable strict mode\n" - " -R Enable raw code mode\n" - " -e Set global variables from given JSON object\n" - " -E Set global variables from given JSON file\n" - " -x Disable given function\n" - " -m Preload given module\n" - " -o Write precompiled byte code to given file\n" - " -O Write precompiled byte code to given file and strip debug information\n", - basename(app)); -} - -static void -register_variable(uc_value_t *scope, const char *key, uc_value_t *val) -{ - char *name = strdup(key); - char *p; + const char *p = strrchr(app, '/'); - if (!name) - return; - - for (p = name; *p; p++) - if (!isalnum(*p) && *p != '_') - *p = '_'; - - ucv_object_add(scope, name, val); - free(name); + printf( + "Usage:\n" + " %1$s -h\n" + " %1$s -e \"expression\"\n" + " %1$s input.uc [input2.uc ...]\n" + " %1$s -c [-s] [-o output.uc] input.uc [input2.uc ...]\n\n" + + "-h\n" + " Help display this help.\n\n" + + "-e \"expression\"\n" + " Execute the given expression as ucode program.\n\n" + + "-t\n" + " Enable VM execution tracing.\n\n" + + "-S\n" + " Enable strict mode.\n\n" + + "-R\n" + " Process source file(s) as raw script code (default).\n\n" + + "-T[flag,flag,...]\n" + " Process the source file(s) as templates, not as raw script code.\n" + " Supported flags: no-lstrip (don't strip leading whitespace before\n" + " block tags), no-rtrim (don't strip trailing newline after block tags).\n\n" + + "-D [name=]value\n" + " Define global variable. If `name` is omitted, a JSON dictionary is\n" + " expected with each property becoming a global variable set to the\n" + " corresponding value. If `name` is specified, it is defined as global\n" + " variable set to `value` parsed as JSON (or the literal `value` string\n" + " if JSON parsing fails).\n\n" + + "-F [name=]path\n" + " Like `-D` but reading the value from the file in `path`. The given\n" + " file must contain a single, well-formed JSON dictionary.\n\n" + + "-U name\n" + " Undefine the given global variable name.\n\n" + + "-l [name=]library\n" + " Preload the given `library`, optionally aliased to `name`.\n\n" + + "-L pattern\n" + " Append given `pattern` to default library search paths. If the pattern\n" + " contains no `*`, it is added twice, once with `/*.so` and once with\n" + " `/*.uc` appended to it.\n\n" + + "-c[flag,flag,...]\n" + " Compile the given source file(s) to bytecode instead of executing them.\n" + " Supported flags: no-interp (omit interpreter line), interp=... (over-\n" + " ride interpreter line with ...)\n\n" + + "-o path\n" + " Output file path when compiling. If omitted, the compiled byte code\n" + " is written to `./uc.out`. Only meaningful in conjunction with `-c`.\n\n" + + "-s\n" + " Omit (strip) debug information when compiling files.\n" + " Only meaningful in conjunction with `-c`.\n\n", + p ? p + 1 : app); } static int -compile(uc_vm_t *vm, uc_source_t *src, FILE *precompile, bool strip) +compile(uc_vm_t *vm, uc_source_t *src, FILE *precompile, bool strip, char *interp) { uc_value_t *res = NULL; uc_program_t *program; @@ -95,6 +127,9 @@ compile(uc_vm_t *vm, uc_source_t *src, FILE *precompile, bool strip) } if (precompile) { + if (interp) + fprintf(precompile, "#!%s\n", interp); + uc_program_write(program, precompile, !strip); fclose(precompile); goto out; @@ -128,41 +163,124 @@ out: } static uc_source_t * -read_stdin(char **ptr) +read_stdin(void) { size_t rlen = 0, tlen = 0; - char buf[128]; + char buf[128], *p = NULL; - if (*ptr) { - fprintf(stderr, "Can read from stdin only once\n"); + if (!stdin_unused) { + fprintf(stderr, "The stdin can only be read once\n"); errno = EINVAL; return NULL; } while (true) { - rlen = fread(buf, 1, sizeof(buf), stdin); + rlen = fread(buf, 1, sizeof(buf), stdin_unused); if (rlen == 0) break; - *ptr = xrealloc(*ptr, tlen + rlen); - memcpy(*ptr + tlen, buf, rlen); + p = xrealloc(p, tlen + rlen); + memcpy(p + tlen, buf, rlen); tlen += rlen; } - return uc_source_new_buffer("[stdin]", *ptr, tlen); + stdin_unused = NULL; + + return uc_source_new_buffer("[stdin]", p, tlen); } -static uc_value_t * -parse_envfile(FILE *fp) +static void +parse_template_modeflags(char *opt, uc_parse_config_t *config) +{ + char *p; + + if (!opt) + return; + + for (p = strtok(opt, ", "); p; p = strtok(NULL, ", ")) { + if (!strcmp(p, "no-lstrip")) + config->lstrip_blocks = false; + else if (!strcmp(p, "no-rtrim")) + config->trim_blocks = false; + else + fprintf(stderr, "Unrecognized -T flag \"%s\", ignoring\n", p); + } +} + +static void +parse_compile_flags(char *opt, char **interp) +{ + char *p, *k, *v; + + if (!opt) + return; + + for (p = strtok(opt, ","); p; p = strtok(NULL, ",")) { + k = p; + v = strchr(p, '='); + + if (v) + *v++ = 0; + + if (!strcmp(k, "no-interp")) { + if (v) + fprintf(stderr, "Compile flag \"%s\" takes no value, ignoring\n", k); + + *interp = NULL; + } + else if (!strcmp(k, "interp")) { + if (!v) + fprintf(stderr, "Compile flag \"%s\" requires a value, ignoring\n", k); + else + *interp = v; + } + else { + fprintf(stderr, "Unrecognized -c flag \"%s\", ignoring\n", k); + } + } +} + +static bool +parse_define_file(char *opt, uc_value_t *globals) { enum json_tokener_error err = json_tokener_continue; + char buf[128], *name = NULL, *p; struct json_tokener *tok; json_object *jso = NULL; - uc_value_t *rv; - char buf[128]; size_t rlen; + FILE *fp; + + p = strchr(opt, '='); + + if (p) { + name = opt; + *p++ = 0; + } + else { + p = opt; + } + + if (!strcmp(p, "-")) { + if (!stdin_unused) { + fprintf(stderr, "The stdin can only be read once\n"); + + return false; + } + + fp = stdin_unused; + stdin_unused = NULL; + } + else + fp = fopen(p, "r"); + + if (!fp) { + fprintf(stderr, "Unable to open definition file \"%s\": %s\n", + p, strerror(errno)); + + return true; + } tok = xjs_new_tokener(); @@ -179,100 +297,209 @@ parse_envfile(FILE *fp) break; } + json_tokener_free(tok); + fclose(fp); + if (err != json_tokener_success || !json_object_is_type(jso, json_type_object)) { json_object_put(jso); - return NULL; + fprintf(stderr, "Invalid definition file \"%s\": %s\n", + p, (err != json_tokener_success) + ? "JSON parse failure" : "Not a valid JSON object"); + + return false; + } + + if (name && *name) { + ucv_object_add(globals, name, ucv_from_json(NULL, jso)); } + else { + json_object_object_foreach(jso, key, val) + ucv_object_add(globals, key, ucv_from_json(NULL, val)); + } + + json_object_put(jso); + + return true; +} + +static bool +parse_define_string(char *opt, uc_value_t *globals) +{ + enum json_tokener_error err; + struct json_tokener *tok; + json_object *jso = NULL; + char *name = NULL, *p; + bool rv = false; + size_t len; + + p = strchr(opt, '='); + + if (p) { + name = opt; + *p++ = 0; + } + else { + p = opt; + } + + len = strlen(p); + tok = xjs_new_tokener(); + + /* NB: the len + 1 here is intentional to pass the terminating \0 byte + * to the json-c parser. This is required to work-around upstream + * issue #681 */ + jso = json_tokener_parse_ex(tok, p, len + 1); + + err = json_tokener_get_error(tok); + + /* Treat trailing bytes after a parsed value as error */ + if (err == json_tokener_success && json_tokener_get_parse_end(tok) < len) + err = json_tokener_error_parse_unexpected; json_tokener_free(tok); - rv = ucv_from_json(NULL, jso); + if (err != json_tokener_success) { + json_object_put(jso); + + if (!name || !*name) { + fprintf(stderr, "Invalid -D option value \"%s\": %s\n", + p, json_tokener_error_desc(err)); + + return false; + } + + ucv_object_add(globals, name, ucv_string_new(p)); + + return true; + } + + if (name && *name) { + ucv_object_add(globals, name, ucv_from_json(NULL, jso)); + rv = true; + } + else if (json_object_is_type(jso, json_type_object)) { + json_object_object_foreach(jso, key, val) + ucv_object_add(globals, key, ucv_from_json(NULL, val)); + rv = true; + } + else { + fprintf(stderr, "Invalid -D option value \"%s\": Not a valid JSON object\n", p); + } json_object_put(jso); return rv; } +static void +parse_search_path(char *pattern, uc_value_t *globals) +{ + uc_value_t *rsp = ucv_object_get(globals, "REQUIRE_SEARCH_PATH", NULL); + size_t len; + char *p; + + if (strchr(pattern, '*')) { + ucv_array_push(rsp, ucv_string_new(pattern)); + return; + } + + len = strlen(pattern); + + if (!len) + return; + + while (pattern[len-1] == '/') + pattern[--len] = 0; + + xasprintf(&p, "%s/*.so", pattern); + ucv_array_push(rsp, ucv_string_new(p)); + free(p); + + xasprintf(&p, "%s/*.uc", pattern); + ucv_array_push(rsp, ucv_string_new(p)); + free(p); +} + +static bool +parse_library_load(char *opt, uc_vm_t *vm) +{ + char *name = NULL, *p; + uc_value_t *lib, *ctx; + + p = strchr(opt, '='); + + if (p) { + name = opt; + *p++ = 0; + } + else { + p = opt; + } + + lib = ucv_string_new(p); + ctx = uc_vm_invoke(vm, "require", 1, lib); + ucv_put(lib); + + if (!ctx) + return false; + + ucv_object_add(uc_vm_scope_get(vm), name ? name : p, ctx); + + return true; +} + int main(int argc, char **argv) { - uc_source_t *source = NULL, *envfile = NULL; + char *interp = "/usr/bin/env ucode"; + uc_source_t *source = NULL; FILE *precompile = NULL; - char *stdin = NULL, *c; + char *outfile = NULL; bool strip = false; uc_vm_t vm = { 0 }; - uc_value_t *o, *p; int opt, rv = 0; + uc_value_t *o; + int fd; uc_parse_config_t config = { .strict_declarations = false, .lstrip_blocks = true, - .trim_blocks = true + .trim_blocks = true, + .raw_mode = true }; - if (argc == 1) - { + if (argc == 1) { print_usage(argv[0]); goto out; } + stdin_unused = stdin; + uc_vm_init(&vm, &config); /* load std functions into global scope */ uc_stdlib_load(uc_vm_scope_get(&vm)); - /* register ARGV array */ - o = ucv_array_new_length(&vm, argc); - - for (opt = 0; opt < argc; opt++) - ucv_array_push(o, ucv_string_new(argv[opt])); + /* register ARGV array but populate it later (to allow for -U ARGV) */ + o = ucv_array_new(&vm); - ucv_object_add(uc_vm_scope_get(&vm), "ARGV", o); + ucv_object_add(uc_vm_scope_get(&vm), "ARGV", ucv_get(o)); /* parse options */ - while ((opt = getopt(argc, argv, "hlrtSRe:E:i:s:m:x:o:O:")) != -1) + while ((opt = getopt(argc, argv, "he:tST::RD:F:U:l:L:c::o:s")) != -1) { switch (opt) { case 'h': print_usage(argv[0]); goto out; - case 'i': - if (source) - fprintf(stderr, "Options -i and -s are exclusive\n"); - - if (!strcmp(optarg, "-")) - source = read_stdin(&stdin); - else - source = uc_source_new_file(optarg); - - if (!source) { - fprintf(stderr, "Failed to open %s: %s\n", optarg, strerror(errno)); - rv = 1; - goto out; - } - - break; - - case 'l': - config.lstrip_blocks = false; - break; - - case 'r': - config.trim_blocks = false; + case 'e': + source = uc_source_new_buffer("[-e argument]", xstrdup(optarg), strlen(optarg)); break; - case 's': - if (source) - fprintf(stderr, "Options -i and -s are exclusive\n"); - - c = xstrdup(optarg); - source = uc_source_new_buffer("[-s argument]", c, strlen(c)); - - if (!source) - free(c); - + case 't': + uc_vm_trace_set(&vm, 1); break; case 'S': @@ -283,129 +510,101 @@ main(int argc, char **argv) config.raw_mode = true; break; - case 'e': - c = strchr(optarg, '='); + case 'T': + config.raw_mode = false; + parse_template_modeflags(optarg, &config); + break; - if (c) - *c++ = 0; - else - c = optarg; - - envfile = uc_source_new_buffer("[-e argument]", xstrdup(c), strlen(c)); - /* fallthrough */ - - case 'E': - if (!envfile) { - c = strchr(optarg, '='); - - if (c) - *c++ = 0; - else - c = optarg; - - if (!strcmp(c, "-")) - envfile = read_stdin(&stdin); - else - envfile = uc_source_new_file(c); - - if (!envfile) { - fprintf(stderr, "Failed to open %s: %s\n", c, strerror(errno)); - rv = 1; - goto out; - } + case 'D': + if (!parse_define_string(optarg, uc_vm_scope_get(&vm))) { + rv = 1; + goto out; } - o = parse_envfile(envfile->fp); - - uc_source_put(envfile); - - envfile = NULL; + break; - if (!o) { - fprintf(stderr, "Option -%c must point to a valid JSON object\n", opt); + case 'F': + if (!parse_define_file(optarg, uc_vm_scope_get(&vm))) { rv = 1; goto out; } - if (c > optarg && optarg[0]) { - p = ucv_object_new(&vm); - ucv_object_add(uc_vm_scope_get(&vm), optarg, p); - } - else { - p = uc_vm_scope_get(&vm); - } - - ucv_object_foreach(o, key, val) - register_variable(p, key, ucv_get(val)); - - ucv_put(o); - break; - case 'm': - p = ucv_string_new(optarg); - o = uc_vm_invoke(&vm, "require", 1, p); - - if (o) - register_variable(uc_vm_scope_get(&vm), optarg, o); - - ucv_put(p); - + case 'U': + ucv_object_delete(uc_vm_scope_get(&vm), optarg); break; - case 't': - uc_vm_trace_set(&vm, 1); + case 'L': + parse_search_path(optarg, uc_vm_scope_get(&vm)); break; - case 'x': - o = ucv_object_get(uc_vm_scope_get(&vm), optarg, NULL); + case 'l': + parse_library_load(optarg, &vm); + break; - if (ucv_is_callable(o)) - ucv_object_delete(uc_vm_scope_get(&vm), optarg); - else - fprintf(stderr, "Unknown function %s specified\n", optarg); + case 'c': + outfile = "./uc.out"; + parse_compile_flags(optarg, &interp); + break; + case 's': + strip = true; break; case 'o': - case 'O': - strip = (opt == 'O'); - - if (!strcmp(optarg, "-")) { - precompile = stdout; - } - else { - precompile = fopen(optarg, "wb"); - - if (!precompile) { - fprintf(stderr, "Unable to open output file %s: %s\n", - optarg, strerror(errno)); - - goto out; - } - } - + outfile = optarg; break; } } if (!source && argv[optind] != NULL) { - source = uc_source_new_file(argv[optind]); + if (!strcmp(argv[optind], "-")) + source = read_stdin(); + else + source = uc_source_new_file(argv[optind]); if (!source) { - fprintf(stderr, "Failed to open %s: %s\n", argv[optind], strerror(errno)); + fprintf(stderr, "Failed to open \"%s\": %s\n", argv[optind], strerror(errno)); rv = 1; goto out; } + + optind++; } if (!source) { - fprintf(stderr, "One of -i or -s is required\n"); + fprintf(stderr, "Require either -e expression or source file\n"); rv = 1; goto out; } - rv = compile(&vm, source, precompile, strip); + if (outfile) { + if (!strcmp(outfile, "-")) { + precompile = stdout; + } + else { + fd = open(outfile, O_WRONLY|O_CREAT|O_TRUNC, 0777); + + if (fd == -1) { + fprintf(stderr, "Unable to open output file %s: %s\n", + outfile, strerror(errno)); + + rv = 1; + goto out; + } + + precompile = fdopen(fd, "wb"); + } + } + + /* populate ARGV array */ + for (; optind < argc; optind++) + ucv_array_push(o, ucv_string_new(argv[optind])); + + ucv_put(o); + + rv = compile(&vm, source, precompile, strip, interp); out: uc_source_put(source); diff --git a/tests/cram/test_basic.t b/tests/cram/test_basic.t index d2a3605..b85167f 100644 --- a/tests/cram/test_basic.t +++ b/tests/cram/test_basic.t @@ -10,52 +10,95 @@ setup common environment: check that ucode provides exepected help: $ ucode | sed 's/ucode-san/ucode/' - Usage - - # ucode [-t] [-l] [-r] [-S] [-R] [-x function [-x ...]] [-e '[prefix=]{"var": ...}'] [-E [prefix=]env.json] {-i | -s "ucode script..."} - -h, --help\tPrint this help (esc) - -i file\tExecute the given ucode script file (esc) - -s "ucode script..."\tExecute the given string as ucode script (esc) - -t Enable VM execution tracing - -l Do not strip leading block whitespace - -r Do not trim trailing block newlines - -S Enable strict mode - -R Enable raw code mode - -e Set global variables from given JSON object - -E Set global variables from given JSON file - -x Disable given function - -m Preload given module - -o Write precompiled byte code to given file - -O Write precompiled byte code to given file and strip debug information + Usage: + ucode -h + ucode -e "expression" + ucode input.uc [input2.uc ...] + ucode -c [-s] [-o output.uc] input.uc [input2.uc ...] + + -h + Help display this help. + + -e "expression" + Execute the given expression as ucode program. + + -t + Enable VM execution tracing. + + -S + Enable strict mode. + + -R + Process source file(s) as raw script code (default). + + -T[flag,flag,...] + Process the source file(s) as templates, not as raw script code. + Supported flags: no-lstrip (don't strip leading whitespace before + block tags), no-rtrim (don't strip trailing newline after block tags). + + -D [name=]value + Define global variable. If `name` is omitted, a JSON dictionary is + expected with each property becoming a global variable set to the + corresponding value. If `name` is specified, it is defined as global + variable set to `value` parsed as JSON (or the literal `value` string + if JSON parsing fails). + + -F [name=]path + Like `-D` but reading the value from the file in `path`. The given + file must contain a single, well-formed JSON dictionary. + + -U name + Undefine the given global variable name. + + -l [name=]library + Preload the given `library`, optionally aliased to `name`. + + -L pattern + Append given `pattern` to default library search paths. If the pattern + contains no `*`, it is added twice, once with `/*.so` and once with + `/*.uc` appended to it. + + -c[flag,flag,...] + Compile the given source file(s) to bytecode instead of executing them. + Supported flags: no-interp (omit interpreter line), interp=... (over- + ride interpreter line with ...) + + -o path + Output file path when compiling. If omitted, the compiled byte code + is written to `./uc.out`. Only meaningful in conjunction with `-c`. + + -s + Omit (strip) debug information when compiling files. + Only meaningful in conjunction with `-c`. + check that ucode prints greetings: - $ ucode -s "{% print('hello world') %}" + $ ucode -e "print('hello world')" hello world (no-eol) check that ucode provides proper error messages: - $ ucode -m foo - One of -i or -s is required + $ ucode -l foo + Require either -e expression or source file [1] - $ ucode -m foo -s ' ' + $ ucode -l foo -e ' ' Runtime error: No module named 'foo' could be found [254] - $ touch moo; ucode -m foo -i moo + $ touch moo; ucode -l foo moo Runtime error: No module named 'foo' could be found [254] check that ucode can load fs module: - $ ucode -m fs - One of -i or -s is required + $ ucode -l fs + Require either -e expression or source file [1] - $ ucode -m fs -s ' ' - (no-eol) + $ ucode -l fs -e ' ' - $ touch moo; ucode -m fs -i moo + $ touch moo; ucode -l fs moo diff --git a/tests/custom/run_tests.sh b/tests/custom/run_tests.sh index 2f13c3b..c2839df 100755 --- a/tests/custom/run_tests.sh +++ b/tests/custom/run_tests.sh @@ -93,10 +93,7 @@ run_testcase() { IFS=$' \t\n' - $ucode_bin $args -e '{ - "REQUIRE_SEARCH_PATH": [ "'"$ucode_lib"'/*.so" ], - "TESTFILES_PATH": "'"$dir"'/files" - }' -i - <"$in" >"$dir/res.out" 2>"$dir/res.err" + $ucode_bin -T -L "$ucode_lib/*.so" -D TESTFILES_PATH="$dir/files" $args - <"$in" >"$dir/res.out" 2>"$dir/res.err" ) printf "%d\n" $? > "$dir/res.code" -- cgit v1.2.3 From b4a1fd5bb4b24cec6b1410209de3f9511a00ff28 Mon Sep 17 00:00:00 2001 From: Jo-Philipp Wich Date: Mon, 14 Mar 2022 16:25:30 +0100 Subject: lib: adjust require(), render() and include() raw mode semantics - Let `require()` always evaluate the executed code in raw mode - Let `render()` always evaluate the executed code in template mode - Let `include()` inherit the raw mode semantics of the calling scope Signed-off-by: Jo-Philipp Wich --- lib.c | 28 +++++++++++--- .../02_runtime/07_raw_template_mode_switching | 45 ++++++++++++++++++++++ tests/custom/03_stdlib/29_require | 26 ++++++------- tests/custom/03_stdlib/35_include | 32 +++++++++++++++ 4 files changed, 111 insertions(+), 20 deletions(-) create mode 100644 tests/custom/02_runtime/07_raw_template_mode_switching diff --git a/lib.c b/lib.c index f715484..df05250 100644 --- a/lib.c +++ b/lib.c @@ -1505,14 +1505,16 @@ uc_require_so(uc_vm_t *vm, const char *path, uc_value_t **res) } static bool -uc_require_ucode(uc_vm_t *vm, const char *path, uc_value_t *scope, uc_value_t **res) +uc_require_ucode(uc_vm_t *vm, const char *path, uc_value_t *scope, uc_value_t **res, bool raw_mode) { + uc_parse_config_t config = { 0 }; uc_exception_type_t extype; uc_program_t *program; uc_value_t *prev_scope; uc_value_t *closure; uc_source_t *source; struct stat st; + bool prev_mode; char *err; if (stat(path, &st)) @@ -1527,6 +1529,12 @@ uc_require_ucode(uc_vm_t *vm, const char *path, uc_value_t *scope, uc_value_t ** return true; } + if (!vm->config) + vm->config = &config; + + prev_mode = vm->config->raw_mode; + vm->config->raw_mode = raw_mode; + program = uc_compile(vm->config, source, &err); uc_source_put(source); @@ -1537,6 +1545,8 @@ uc_require_ucode(uc_vm_t *vm, const char *path, uc_value_t *scope, uc_value_t ** free(err); + vm->config->raw_mode = prev_mode; + return true; } @@ -1558,6 +1568,8 @@ uc_require_ucode(uc_vm_t *vm, const char *path, uc_value_t *scope, uc_value_t ** if (extype == EXCEPTION_NONE) *res = uc_vm_stack_pop(vm); + vm->config->raw_mode = prev_mode; + return true; } @@ -1604,7 +1616,7 @@ uc_require_path(uc_vm_t *vm, const char *path_template, const char *name, uc_val if (!strcmp(p + 1, ".so")) rv = uc_require_so(vm, buf->buf, res); else if (!strcmp(p + 1, ".uc")) - rv = uc_require_ucode(vm, buf->buf, NULL, res); + rv = uc_require_ucode(vm, buf->buf, NULL, res, true); if (rv) ucv_object_add(modtable, name, ucv_get(*res)); @@ -2124,7 +2136,7 @@ include_path(const char *curpath, const char *incpath) } static uc_value_t * -uc_include(uc_vm_t *vm, size_t nargs) +uc_include_common(uc_vm_t *vm, size_t nargs, bool raw_mode) { uc_value_t *path = uc_fn_arg(0); uc_value_t *scope = uc_fn_arg(1); @@ -2182,7 +2194,7 @@ uc_include(uc_vm_t *vm, size_t nargs) sc = ucv_get(uc_vm_scope_get(vm)); } - if (uc_require_ucode(vm, p, sc, &rv)) + if (uc_require_ucode(vm, p, sc, &rv, raw_mode)) ucv_put(rv); ucv_put(sc); @@ -2191,6 +2203,12 @@ uc_include(uc_vm_t *vm, size_t nargs) return NULL; } +static uc_value_t * +uc_include(uc_vm_t *vm, size_t nargs) +{ + return uc_include_common(vm, nargs, vm->config && vm->config->raw_mode); +} + static uc_value_t * uc_render(uc_vm_t *vm, size_t nargs) { @@ -2213,7 +2231,7 @@ uc_render(uc_vm_t *vm, size_t nargs) vm->output = mem; /* execute include */ - (void) uc_include(vm, nargs); + (void) uc_include_common(vm, nargs, false); /* restore previous VM output */ vm->output = prev; diff --git a/tests/custom/02_runtime/07_raw_template_mode_switching b/tests/custom/02_runtime/07_raw_template_mode_switching new file mode 100644 index 0000000..5ae53e8 --- /dev/null +++ b/tests/custom/02_runtime/07_raw_template_mode_switching @@ -0,0 +1,45 @@ +Testing that require(), render() and include() properly toggle between +raw- and template parse mode. + + +1. Testing recursive invocation. + +-- Testcase -- +require("files.requiretest"); +print(render("files/render-test.uc")); +include("files/include-test.uc"); +-- End -- + +-- Args -- +-R +-- End -- + +-- File requiretest.uc -- +print("This is a raw mode file loaded by require()\n"); +print(render("require-render-test.uc")); +include("require-include-test.uc"); +-- End -- + +-- File require-include-test.uc -- +print("This is a raw mode file included by a required file\n"); +-- End -- + +-- File require-render-test.uc -- +This is a {{ "template mode" }} file rendered by a required file +-- End -- + +-- File render-test.uc -- +This is a {{ "template mode" }} file loaded by render() from a raw mode file +-- End -- + +-- File include-test.uc -- +print("This is a raw mode file loaded by include() from a raw mode file\n"); +-- End -- + +-- Expect stdout -- +This is a raw mode file loaded by require() +This is a template mode file rendered by a required file +This is a raw mode file included by a required file +This is a template mode file loaded by render() from a raw mode file +This is a raw mode file loaded by include() from a raw mode file +-- End -- diff --git a/tests/custom/03_stdlib/29_require b/tests/custom/03_stdlib/29_require index 681f3f7..4fb4216 100644 --- a/tests/custom/03_stdlib/29_require +++ b/tests/custom/03_stdlib/29_require @@ -42,15 +42,13 @@ Returns the value returned by the invoked module code (typically an object). -- End -- -- File require/test/module.uc -- -{% - print("This is require.test.module running!\n\n"); +print("This is require.test.module running!\n\n"); - return { - greeting: function(name) { - printf("Hello, %s!\n", name); - } - }; -%} +return { + greeting: function(name) { + printf("Hello, %s!\n", name); + } +}; -- End -- -- Expect stdout -- @@ -139,19 +137,17 @@ A compilation error in the module triggers an exception. -- End -- -- File require/test/broken.uc -- -{% - // Unclosed object to force syntax error - return { -%} +// Unclosed object to force syntax error +return { -- End -- -- Expect stderr -- Unable to compile module '.../require/test/broken.uc': Syntax error: Expecting label -In line 3, byte 11: +In line 2, byte 10: - ` return {` - Near here --^ + `return {` + ^-- Near here diff --git a/tests/custom/03_stdlib/35_include b/tests/custom/03_stdlib/35_include index 6d808f2..1d428f1 100644 --- a/tests/custom/03_stdlib/35_include +++ b/tests/custom/03_stdlib/35_include @@ -171,3 +171,35 @@ In line 12, byte 8: -- End -- + + +Ensure that included files inherit the parse mode of their calling file. + +-- Testcase -- +{% include("files/inctest.uc"); %} +-- End -- + +-- File inctest.uc -- +print("Test\n"); +-- End -- + +-- Expect stdout -- +print("Test\n"); +-- End -- + + +-- Testcase -- +include("files/inctest.uc"); +-- End -- + +-- Args -- +-R +-- End -- + +-- File inctest.uc -- +print("Test\n"); +-- End -- + +-- Expect stdout -- +Test +-- End -- -- cgit v1.2.3