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

This page was automatically generated by LXR 0.3.1.  •  OpenWrt