summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorJo-Philipp Wich <jo@mein.io>2022-03-14 21:19:57 +0100
committerGitHub <noreply@github.com>2022-03-14 21:19:57 +0100
commitb8bc5b58368a9c6ce9dbfc80baca933004fd0722 (patch)
tree016872e56c12f5edb3df12cf9a9cc87680f2bcc5
parent23929951ad4b0ed2bcea793f6bf72d9e4236d3c7 (diff)
parentb4a1fd5bb4b24cec6b1410209de3f9511a00ff28 (diff)
Merge pull request #48 from jow-/cli-rework
main: rework CLI frontend
-rw-r--r--lib.c28
-rw-r--r--main.c563
-rw-r--r--tests/cram/test_basic.t97
-rw-r--r--tests/custom/02_runtime/07_raw_template_mode_switching45
-rw-r--r--tests/custom/03_stdlib/29_require26
-rw-r--r--tests/custom/03_stdlib/35_include32
-rwxr-xr-xtests/custom/run_tests.sh5
7 files changed, 563 insertions, 233 deletions
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);
@@ -2192,6 +2204,12 @@ uc_include(uc_vm_t *vm, size_t nargs)
}
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)
{
uc_string_t hdr = { .header = { .type = UC_STRING, .refcount = 1 } };
@@ -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/main.c b/main.c
index f9ce6f0..b825fdb 100644
--- a/main.c
+++ b/main.c
@@ -20,7 +20,9 @@
#include <unistd.h>
#include <errno.h>
#include <ctype.h>
+#include <fcntl.h>
#include <sys/stat.h>
+#include <sys/types.h>
#ifdef JSONC
#include <json.h>
@@ -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 <file> | -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 <https://github.com/json-c/json-c/issues/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 <file> | -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/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 --
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"