• source navigation  • diff markup  • identifier search  • freetext search  • 

Sources/ucode/main.c

  1 /*
  2  * Copyright (C) 2020-2021 Jo-Philipp Wich <jo@mein.io>
  3  *
  4  * Permission to use, copy, modify, and/or distribute this software for any
  5  * purpose with or without fee is hereby granted, provided that the above
  6  * copyright notice and this permission notice appear in all copies.
  7  *
  8  * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
  9  * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
 10  * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
 11  * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
 12  * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
 13  * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
 14  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 15  */
 16 
 17 #include <stdio.h>
 18 #include <stdbool.h>
 19 #include <stdint.h>
 20 #include <unistd.h>
 21 #include <errno.h>
 22 #include <ctype.h>
 23 #include <fcntl.h>
 24 #include <sys/stat.h>
 25 #include <sys/types.h>
 26 
 27 #include "json-c-compat.h"
 28 
 29 #include "ucode/compiler.h"
 30 #include "ucode/lexer.h"
 31 #include "ucode/lib.h"
 32 #include "ucode/vm.h"
 33 #include "ucode/source.h"
 34 #include "ucode/program.h"
 35 
 36 static FILE *stdin_unused;
 37 
 38 static void
 39 print_usage(const char *app)
 40 {
 41         printf(
 42         "Usage:\n"
 43         "  %1$s -h\n"
 44         "  %1$s -e \"expression\"\n"
 45         "  %1$s input.uc [input2.uc ...]\n"
 46         "  %1$s -c [-s] [-o output.uc] input.uc [input2.uc ...]\n\n"
 47 
 48         "-h\n"
 49         "  Help display this help.\n\n"
 50 
 51         "-e \"expression\"\n"
 52         "  Execute the given expression as ucode program.\n\n"
 53 
 54         "-t\n"
 55         "  Enable VM execution tracing.\n\n"
 56 
 57         "-g interval\n"
 58         "  Perform periodic garbage collection every `interval` object\n"
 59         "  allocations.\n\n"
 60 
 61         "-S\n"
 62         "  Enable strict mode.\n\n"
 63 
 64         "-R\n"
 65         "  Process source file(s) as raw script code (default).\n\n"
 66 
 67         "-T[flag,flag,...]\n"
 68         "  Process the source file(s) as templates, not as raw script code.\n"
 69         "  Supported flags: no-lstrip (don't strip leading whitespace before\n"
 70         "  block tags), no-rtrim (don't strip trailing newline after block tags).\n\n"
 71 
 72         "-D [name=]value\n"
 73         "  Define global variable. If `name` is omitted, a JSON dictionary is\n"
 74         "  expected with each property becoming a global variable set to the\n"
 75         "  corresponding value. If `name` is specified, it is defined as global\n"
 76         "  variable set to `value` parsed as JSON (or the literal `value` string\n"
 77         "  if JSON parsing fails).\n\n"
 78 
 79         "-F [name=]path\n"
 80         "  Like `-D` but reading the value from the file in `path`. The given\n"
 81         "  file must contain a single, well-formed JSON dictionary.\n\n"
 82 
 83         "-U name\n"
 84         "  Undefine the given global variable name.\n\n"
 85 
 86         "-l [name=]library\n"
 87         "  Preload the given `library`, optionally aliased to `name`.\n\n"
 88 
 89         "-L pattern\n"
 90         "  Append given `pattern` to default library search paths. If the pattern\n"
 91         "  contains no `*`, it is added twice, once with `/*.so` and once with\n"
 92         "  `/*.uc` appended to it.\n\n"
 93 
 94         "-c[flag,flag,...]\n"
 95         "  Compile the given source file(s) to bytecode instead of executing them.\n"
 96         "  Supported flags: no-interp (omit interpreter line), interp=... (over-\n"
 97         "  ride interpreter line with ...), dynlink=... (force import from ... to\n"
 98         "  be treated as shared extensions loaded at runtime).\n\n"
 99 
100         "-o path\n"
101         "  Output file path when compiling. If omitted, the compiled byte code\n"
102         "  is written to `./uc.out`. Only meaningful in conjunction with `-c`.\n\n"
103 
104         "-s\n"
105         "  Omit (strip) debug information when compiling files.\n"
106         "  Only meaningful in conjunction with `-c`.\n\n",
107                 app);
108 }
109 
110 
111 static int
112 compile(uc_vm_t *vm, uc_source_t *src, FILE *precompile, bool strip, char *interp)
113 {
114         uc_value_t *res = NULL;
115         uc_program_t *program;
116         int rc = 0;
117         char *err;
118 
119         program = uc_compile(vm->config, src, &err);
120 
121         if (!program) {
122                 fprintf(stderr, "%s", err);
123                 free(err);
124                 rc = -1;
125                 goto out;
126         }
127 
128         if (precompile) {
129                 if (interp)
130                         fprintf(precompile, "#!%s\n", interp);
131 
132                 uc_program_write(program, precompile, !strip);
133                 fclose(precompile);
134                 goto out;
135         }
136 
137         if (vm->gc_interval)
138                 uc_vm_gc_start(vm, vm->gc_interval);
139 
140         rc = uc_vm_execute(vm, program, &res);
141 
142         switch (rc) {
143         case STATUS_OK:
144                 rc = 0;
145                 break;
146 
147         case STATUS_EXIT:
148                 rc = (int)ucv_int64_get(res);
149                 break;
150 
151         case ERROR_COMPILE:
152                 rc = -1;
153                 break;
154 
155         case ERROR_RUNTIME:
156                 rc = -2;
157                 break;
158         }
159 
160 out:
161         uc_program_put(program);
162         ucv_put(res);
163 
164         return rc;
165 }
166 
167 static uc_source_t *
168 read_stdin(void)
169 {
170         size_t rlen = 0, tlen = 0;
171         char buf[128], *p = NULL;
172 
173         if (!stdin_unused) {
174                 fprintf(stderr, "The stdin can only be read once\n");
175                 errno = EINVAL;
176 
177                 return NULL;
178         }
179 
180         while (true) {
181                 rlen = fread(buf, 1, sizeof(buf), stdin_unused);
182 
183                 if (rlen == 0)
184                         break;
185 
186                 p = xrealloc(p, tlen + rlen);
187                 memcpy(p + tlen, buf, rlen);
188                 tlen += rlen;
189         }
190 
191         stdin_unused = NULL;
192 
193         return uc_source_new_buffer("[stdin]", p, tlen);
194 }
195 
196 static void
197 parse_template_modeflags(char *opt, uc_parse_config_t *config)
198 {
199         char *p;
200 
201         if (!opt)
202                 return;
203 
204         for (p = strtok(opt, ", "); p; p = strtok(NULL, ", ")) {
205                 if (!strcmp(p, "no-lstrip"))
206                         config->lstrip_blocks = false;
207                 else if (!strcmp(p, "no-rtrim"))
208                         config->trim_blocks = false;
209                 else
210                         fprintf(stderr, "Unrecognized -T flag \"%s\", ignoring\n", p);
211         }
212 }
213 
214 static void
215 parse_compile_flags(char *opt, char **interp, uc_search_path_t *dynlink_list)
216 {
217         char *p, *k, *v;
218 
219         if (!opt)
220                 return;
221 
222         for (p = strtok(opt, ","); p; p = strtok(NULL, ",")) {
223                 k = p;
224                 v = strchr(p, '=');
225 
226                 if (v)
227                         *v++ = 0;
228 
229                 if (!strcmp(k, "no-interp")) {
230                         if (v)
231                                 fprintf(stderr, "Compile flag \"%s\" takes no value, ignoring\n", k);
232 
233                         *interp = NULL;
234                 }
235                 else if (!strcmp(k, "interp")) {
236                         if (!v)
237                                 fprintf(stderr, "Compile flag \"%s\" requires a value, ignoring\n", k);
238                         else
239                                 *interp = v;
240                 }
241                 else if (!strcmp(k, "dynlink")) {
242                         if (!v)
243                                 fprintf(stderr, "Compile flag \"%s\" requires a value, ignoring\n", k);
244                         else
245                                 uc_vector_push(dynlink_list, v);
246                 }
247                 else {
248                         fprintf(stderr, "Unrecognized -c flag \"%s\", ignoring\n", k);
249                 }
250         }
251 }
252 
253 static bool
254 parse_define_file(char *opt, uc_value_t *globals)
255 {
256         enum json_tokener_error err = json_tokener_continue;
257         char buf[128], *name = NULL, *p;
258         struct json_tokener *tok;
259         json_object *jso = NULL;
260         size_t rlen;
261         FILE *fp;
262 
263         p = strchr(opt, '=');
264 
265         if (p) {
266                 name = opt;
267                 *p++ = 0;
268         }
269         else {
270                 p = opt;
271         }
272 
273         if (!strcmp(p, "-")) {
274                 if (!stdin_unused) {
275                         fprintf(stderr, "The stdin can only be read once\n");
276 
277                         return false;
278                 }
279 
280                 fp = stdin_unused;
281                 stdin_unused = NULL;
282         }
283         else
284                 fp = fopen(p, "r");
285 
286         if (!fp) {
287                 fprintf(stderr, "Unable to open definition file \"%s\": %s\n",
288                         p, strerror(errno));
289 
290                 return true;
291         }
292 
293         tok = xjs_new_tokener();
294 
295         while (true) {
296                 rlen = fread(buf, 1, sizeof(buf), fp);
297 
298                 if (rlen == 0)
299                         break;
300 
301                 jso = json_tokener_parse_ex(tok, buf, rlen);
302                 err = json_tokener_get_error(tok);
303 
304                 if (err != json_tokener_continue)
305                         break;
306         }
307 
308         json_tokener_free(tok);
309         fclose(fp);
310 
311         if (err != json_tokener_success || !json_object_is_type(jso, json_type_object)) {
312                 json_object_put(jso);
313 
314                 fprintf(stderr, "Invalid definition file \"%s\": %s\n",
315                         p, (err != json_tokener_success)
316                              ? "JSON parse failure" : "Not a valid JSON object");
317 
318                 return false;
319         }
320 
321         if (name && *name) {
322                 ucv_object_add(globals, name, ucv_from_json(NULL, jso));
323         }
324         else {
325                 json_object_object_foreach(jso, key, val)
326                         ucv_object_add(globals, key, ucv_from_json(NULL, val));
327         }
328 
329         json_object_put(jso);
330 
331         return true;
332 }
333 
334 static bool
335 parse_define_string(char *opt, uc_value_t *globals)
336 {
337         enum json_tokener_error err;
338         struct json_tokener *tok;
339         json_object *jso = NULL;
340         char *name = NULL, *p;
341         bool rv = false;
342         size_t len;
343 
344         p = strchr(opt, '=');
345 
346         if (p) {
347                 name = opt;
348                 *p++ = 0;
349         }
350         else {
351                 p = opt;
352         }
353 
354         len = strlen(p);
355         tok = xjs_new_tokener();
356 
357         /* NB: the len + 1 here is intentional to pass the terminating \0 byte
358          * to the json-c parser. This is required to work-around upstream
359          * issue #681 <https://github.com/json-c/json-c/issues/681> */
360         jso = json_tokener_parse_ex(tok, p, len + 1);
361 
362         err = json_tokener_get_error(tok);
363 
364         /* Treat trailing bytes after a parsed value as error */
365         if (err == json_tokener_success && json_tokener_get_parse_end(tok) < len)
366                 err = json_tokener_error_parse_unexpected;
367 
368         json_tokener_free(tok);
369 
370         if (err != json_tokener_success) {
371                 json_object_put(jso);
372 
373                 if (!name || !*name) {
374                         fprintf(stderr, "Invalid -D option value \"%s\": %s\n",
375                                 p, json_tokener_error_desc(err));
376 
377                         return false;
378                 }
379 
380                 ucv_object_add(globals, name, ucv_string_new(p));
381 
382                 return true;
383         }
384 
385         if (name && *name) {
386                 ucv_object_add(globals, name, ucv_from_json(NULL, jso));
387                 rv = true;
388         }
389         else if (json_object_is_type(jso, json_type_object)) {
390                 json_object_object_foreach(jso, key, val)
391                         ucv_object_add(globals, key, ucv_from_json(NULL, val));
392                 rv = true;
393         }
394         else {
395                 fprintf(stderr, "Invalid -D option value \"%s\": Not a valid JSON object\n", p);
396         }
397 
398         json_object_put(jso);
399 
400         return rv;
401 }
402 
403 static void
404 parse_search_path(char *pattern, uc_parse_config_t *config)
405 {
406         size_t len;
407         char *p;
408 
409         if (strchr(pattern, '*')) {
410                 uc_search_path_add(&config->module_search_path, pattern);
411                 return;
412         }
413 
414         len = strlen(pattern);
415 
416         if (!len)
417                 return;
418 
419         while (pattern[len-1] == '/')
420                 pattern[--len] = 0;
421 
422         xasprintf(&p, "%s/*.so", pattern);
423         uc_search_path_add(&config->module_search_path, p);
424         free(p);
425 
426         xasprintf(&p, "%s/*.uc", pattern);
427         uc_search_path_add(&config->module_search_path, p);
428         free(p);
429 }
430 
431 static bool
432 parse_library_load(char *opt, uc_vm_t *vm)
433 {
434         char *name = NULL, *p;
435         uc_value_t *lib, *ctx;
436 
437         p = strchr(opt, '=');
438 
439         if (p) {
440                 name = opt;
441                 *p++ = 0;
442         }
443         else {
444                 p = opt;
445         }
446 
447         lib = ucv_string_new(p);
448         ctx = uc_vm_invoke(vm, "require", 1, lib);
449         ucv_put(lib);
450 
451         if (!ctx)
452                 return vm->exception.type == EXCEPTION_NONE;
453 
454         ucv_object_add(uc_vm_scope_get(vm), name ? name : p, ctx);
455 
456         return true;
457 }
458 
459 static const char *
460 appname(const char *argv0)
461 {
462         const char *p;
463 
464         if (!argv0)
465                 return "ucode";
466 
467         p = strrchr(argv0, '/');
468 
469         if (p)
470                 return p + 1;
471 
472         return argv0;
473 }
474 
475 int
476 main(int argc, char **argv)
477 {
478         const char *optspec = "he:tg:ST::RD:F:U:l:L:c::o:s";
479         char *interp = "/usr/bin/env ucode";
480         uc_source_t *source = NULL;
481         FILE *precompile = NULL;
482         char *outfile = NULL;
483         bool strip = false;
484         uc_vm_t vm = { 0 };
485         int opt, rv = 0;
486         const char *app;
487         uc_value_t *o;
488         int fd;
489 
490         uc_parse_config_t config = {
491                 .strict_declarations = false,
492                 .lstrip_blocks = true,
493                 .trim_blocks = true,
494                 .raw_mode = true
495         };
496 
497         uc_search_path_init(&config.module_search_path);
498 
499         app = appname(argv[0]);
500 
501         if (argc == 1) {
502                 print_usage(app);
503                 goto out;
504         }
505 
506         if (!strcmp(app, "utpl"))
507                 config.raw_mode = false;
508         else if (!strcmp(app, "ucc"))
509                 outfile = "./uc.out";
510 
511         stdin_unused = stdin;
512 
513         /* parse options iteration 1: parse config related options */
514         while ((opt = getopt(argc, argv, optspec)) != -1)
515         {
516                 switch (opt) {
517                 case 'L':
518                         parse_search_path(optarg, &config);
519                         break;
520 
521                 case 'S':
522                         config.strict_declarations = true;
523                         break;
524 
525                 case 'R':
526                         config.raw_mode = true;
527                         break;
528 
529                 case 'T':
530                         config.raw_mode = false;
531                         parse_template_modeflags(optarg, &config);
532                         break;
533                 }
534         }
535 
536         optind = 1;
537 
538         uc_vm_init(&vm, &config);
539 
540         /* load std functions into global scope */
541         uc_stdlib_load(uc_vm_scope_get(&vm));
542 
543         /* register ARGV array but populate it later (to allow for -U ARGV) */
544         o = ucv_array_new(&vm);
545 
546         ucv_object_add(uc_vm_scope_get(&vm), "ARGV", ucv_get(o));
547 
548         /* parse options iteration 2: process remaining options */
549         while ((opt = getopt(argc, argv, optspec)) != -1)
550         {
551                 switch (opt) {
552                 case 'h':
553                         print_usage(argv[0]);
554                         goto out;
555 
556                 case 'e':
557                         source = uc_source_new_buffer("[-e argument]", xstrdup(optarg), strlen(optarg));
558                         break;
559 
560                 case 't':
561                         uc_vm_trace_set(&vm, 1);
562                         break;
563 
564                 case 'g':
565                         vm.gc_interval = atoi(optarg);
566                         break;
567 
568                 case 'D':
569                         if (!parse_define_string(optarg, uc_vm_scope_get(&vm))) {
570                                 rv = 1;
571                                 goto out;
572                         }
573 
574                         break;
575 
576                 case 'F':
577                         if (!parse_define_file(optarg, uc_vm_scope_get(&vm))) {
578                                 rv = 1;
579                                 goto out;
580                         }
581 
582                         break;
583 
584                 case 'U':
585                         ucv_object_delete(uc_vm_scope_get(&vm), optarg);
586                         break;
587 
588                 case 'l':
589                         if (!parse_library_load(optarg, &vm)) {
590                                 rv = 1;
591                                 goto out;
592                         }
593 
594                         break;
595 
596                 case 'c':
597                         outfile = "./uc.out";
598                         parse_compile_flags(optarg, &interp, &config.force_dynlink_list);
599                         break;
600 
601                 case 's':
602                         strip = true;
603                         break;
604 
605                 case 'o':
606                         outfile = optarg;
607                         break;
608                 }
609         }
610 
611         if (!source && argv[optind] != NULL) {
612                 if (!strcmp(argv[optind], "-"))
613                         source = read_stdin();
614                 else
615                         source = uc_source_new_file(argv[optind]);
616 
617                 if (!source) {
618                         fprintf(stderr, "Failed to open \"%s\": %s\n", argv[optind], strerror(errno));
619                         rv = 1;
620                         goto out;
621                 }
622 
623                 optind++;
624         }
625 
626         if (!source) {
627                 fprintf(stderr, "Require either -e expression or source file\n");
628                 rv = 1;
629                 goto out;
630         }
631 
632         if (outfile) {
633                 if (!strcmp(outfile, "-")) {
634                         precompile = stdout;
635                 }
636                 else {
637                         fd = open(outfile, O_WRONLY|O_CREAT|O_TRUNC, 0777);
638 
639                         if (fd == -1) {
640                                 fprintf(stderr, "Unable to open output file %s: %s\n",
641                                         outfile, strerror(errno));
642 
643                                 rv = 1;
644                                 goto out;
645                         }
646 
647                         precompile = fdopen(fd, "wb");
648                 }
649         }
650 
651         /* populate ARGV array */
652         for (; optind < argc; optind++)
653                 ucv_array_push(o, ucv_string_new(argv[optind]));
654 
655         ucv_put(o);
656 
657         rv = compile(&vm, source, precompile, strip, interp);
658 
659 out:
660         uc_search_path_free(&config.module_search_path);
661         uc_vector_clear(&config.force_dynlink_list);
662 
663         uc_source_put(source);
664 
665         uc_vm_free(&vm);
666 
667         return rv;
668 }
669 

This page was automatically generated by LXR 0.3.1.  •  OpenWrt