summaryrefslogtreecommitdiffhomepage
path: root/main.c
diff options
context:
space:
mode:
authorJo-Philipp Wich <jo@mein.io>2022-03-14 08:47:58 +0100
committerJo-Philipp Wich <jo@mein.io>2022-03-14 15:31:34 +0100
commit46188077ef727c21513008f4e0c42e8cb211e90e (patch)
tree47933baeb43ff6ff3a95810c25fc189a6062e7be /main.c
parent23929951ad4b0ed2bcea793f6bf72d9e4236d3c7 (diff)
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 <jo@mein.io>
Diffstat (limited to 'main.c')
-rw-r--r--main.c563
1 files changed, 381 insertions, 182 deletions
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);