/******************************************************************** ** Copyright (c) 2018-2020 Guan Wenliang ** This file is part of the Berry default interpreter. ** skiars@qq.com, https://github.com/Skiars/berry ** See Copyright Notice in the LICENSE file or at ** https://github.com/Skiars/berry/blob/master/LICENSE ********************************************************************/ #ifndef TASMOTA // only when compiling stand-along #include "berry.h" #include "be_repl.h" #include "be_vm.h" #include #include #include #include "be_mapping.h" /* using GNU/readline library */ #if defined(USE_READLINE_LIB) #include #include #endif /* detect operating system name */ #if defined(__linux) #define OS_NAME "Linux" #elif defined(__unix) #define OS_NAME "Unix" #elif defined(__APPLE__) #define OS_NAME "Darwin" #elif defined(_WIN32) #define OS_NAME "Windows" #else #define OS_NAME "Unknown OS" #endif /* detect compiler name and version */ #if defined(__clang__) #define COMPILER "clang " __clang_version__ #elif defined(__GNUC__) #define COMPILER "GCC " __VERSION__ #elif defined(_MSC_VER) #define COMPILER "MSVC" #elif defined(__CC_ARM) #define COMPILER "ARMCC" #elif defined(__ICCARM__) #define COMPILER "IAR" #else #define COMPILER "Unknown Compiler" #endif #if BE_DEBUG #define FULL_VERSION "Berry " BERRY_VERSION " (debug)" #else #define FULL_VERSION "Berry " BERRY_VERSION #endif /* prompt message when REPL is loaded */ #define repl_prelude \ FULL_VERSION " (build in " __DATE__ ", " __TIME__ ")\n" \ "[" COMPILER "] on " OS_NAME " (default)\n" \ #if defined(_WIN32) #define PATH_SEPARATOR ";" #else #define PATH_SEPARATOR ":" #endif /* command help information */ #define help_information \ "Usage: berry [options] [script [args]]\n" \ "Avilable options are:\n" \ " -i enter interactive mode after executing 'file'\n" \ " -l all variables in 'file' are parsed as local\n" \ " -e load 'script' source string and execute\n" \ " -m custom module search path(s) separated by '" PATH_SEPARATOR "'\n"\ " -c compile script 'file' to bytecode file\n" \ " -o save bytecode to 'file'\n" \ " -g force named globals in VM\n" \ " -s force Berry compiler in strict mode\n" \ " -v show version information\n" \ " -h show help information\n\n" \ "For more information, please see:\n" \ " .\n" #define array_count(a) (sizeof(a) / sizeof((a)[0])) #define arg_i (1 << 0) #define arg_c (1 << 1) #define arg_o (1 << 2) #define arg_l (1 << 3) #define arg_h (1 << 4) #define arg_v (1 << 5) #define arg_e (1 << 6) #define arg_g (1 << 7) #define arg_s (1 << 8) #define arg_err (1 << 9) #define arg_m (1 << 10) struct arg_opts { int idx; const char *pattern; const char *optarg; const char *errarg; const char *src; const char *dst; const char *modulepath; }; /* check if the character is a letter */ static int is_letter(int ch) { return (ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z'); } /* matching options * pattern: pattern string, the set of vaild options * ch: option character to be matched * */ static const char* match_opt(const char *pattern, int ch) { int c = '\0'; if (pattern) { while ((c = *pattern) != '\0' && c != ch) { c = *(++pattern); while (c != '\0' && !is_letter(c)) { c = *(++pattern); /* skip characters that are not letters */ } } } return c == ch ? pattern : NULL; } /* read an option from the arguments * opt: option match state * argc: the number of arguments * argv: the arguments list * */ static int arg_getopt(struct arg_opts *opt, int argc, char *argv[]) { if (opt->idx < argc) { char *arg = argv[opt->idx]; if (arg[0] == '-' && strlen(arg) == 2) { const char *res = match_opt(opt->pattern, arg[1]); /* the '?' indicates an optional argument after the option */ if (++opt->idx < argc && res != NULL && res[1] == '?' && *argv[opt->idx] != '-') { opt->optarg = argv[opt->idx++]; /* save the argument */ return *res; } opt->optarg = NULL; opt->errarg = arg; return res != NULL ? *res : '?'; } } return 0; } /* portable readline function package */ static char* get_line(const char *prompt) { #if defined(USE_READLINE_LIB) char *line = readline(prompt); if (line && strlen(line)) { add_history(line); } return line; #else static char buffer[1000]; fputs(prompt, stdout); fflush(stdout); if (be_readstring(buffer, sizeof(buffer))) { buffer[strlen(buffer) - 1] = '\0'; return buffer; } return NULL; #endif } static void free_line(char *ptr) { #if defined(USE_READLINE_LIB) free(ptr); #else (void)ptr; #endif } static int handle_result(bvm *vm, int res) { switch (res) { case BE_OK: /* everything is OK */ return 0; case BE_EXCEPTION: /* uncatched exception */ be_dumpexcept(vm); return 1; case BE_EXIT: /* return exit code */ return be_toindex(vm, -1); case BE_IO_ERROR: be_writestring("error: "); be_writestring(be_tostring(vm, -1)); be_writenewline(); return -2; case BE_MALLOC_FAIL: be_writestring("error: memory allocation failed.\n"); return -1; default: /* unkonw result */ return 2; } } /* execute a script source or file and output a result or error */ static int doscript(bvm *vm, const char *name, int args) { /* load string, bytecode file or compile script file */ int res = args & arg_e ? /* check script source string */ be_loadstring(vm, name) : be_loadmode(vm, name, args & arg_l); if (res == BE_OK) { /* parsing succeeded */ res = be_pcall(vm, 0); /* execute */ } return handle_result(vm, res); } /* load a Berry script string or file and execute * args: the enabled options mask * */ static int load_script(bvm *vm, int argc, char *argv[], int args) { int res = 0; int repl_mode = args & arg_i || (args == 0 && argc == 0); if (repl_mode) { /* enter the REPL mode after executing the script file */ be_writestring(repl_prelude); } if (argc > 0) { /* check file path or source string argument */ res = doscript(vm, argv[0], args); } if (repl_mode) { /* enter the REPL mode */ res = be_repl(vm, get_line, free_line); if (res == -BE_MALLOC_FAIL) { be_writestring("error: memory allocation failed.\n"); } } return res; } /* compile the source code to a bytecode file */ static int build_file(bvm *vm, const char *dst, const char *src, int args) { int res = be_loadmode(vm, src, args & arg_l); /* compile script file */ if (res == BE_OK) { if (!dst) dst = "a.out"; /* the default output file name */ res = be_savecode(vm, dst); /* save bytecode file */ } return handle_result(vm, res); } static int parse_arg(struct arg_opts *opt, int argc, char *argv[]) { int ch, args = 0; opt->idx = 1; while ((ch = arg_getopt(opt, argc, argv)) != '\0') { switch (ch) { case 'h': args |= arg_h; break; case 'v': args |= arg_v; break; case 'i': args |= arg_i; break; case 'l': args |= arg_l; break; case 'e': args |= arg_e; break; case 'g': args |= arg_g; break; case 's': args |= arg_s; break; case 'm': args |= arg_m; opt->modulepath = opt->optarg; break; case '?': return args | arg_err; case 'c': args |= arg_c; opt->src = opt->optarg; break; case 'o': args |= arg_o; opt->dst = opt->optarg; break; default: break; } } return args; } static void push_args(bvm *vm, int argc, char *argv[]) { be_newobject(vm, "list"); while (argc--) { be_pushstring(vm, *argv++); be_data_push(vm, -2); be_pop(vm, 1); } be_pop(vm, 1); be_setglobal(vm, "_argv"); be_pop(vm, 1); } #if defined(_WIN32) #define BERRY_ROOT "\\Windows\\system32" static const char *module_paths[] = { BERRY_ROOT "\\berry\\packages", }; #else #define BERRY_ROOT "/usr/local" static const char *module_paths[] = { BERRY_ROOT "/lib/berry/packages", }; #endif static void berry_paths(bvm * vm) { size_t i; for (i = 0; i < array_count(module_paths); ++i) { be_module_path_set(vm, module_paths[i]); } } static void berry_custom_paths(bvm *vm, const char *modulepath) { const char delim[] = PATH_SEPARATOR; char *copy = malloc(strlen(modulepath) + 1); strcpy(copy, modulepath); char *ptr = strtok(copy, delim); while (ptr != NULL) { be_module_path_set(vm, ptr); ptr = strtok(NULL, delim); } free(copy); } /* * command format: berry [options] [script [args]] * command options: * -i: enter interactive mode after executing 'script' * -b: load code from bytecode file * -e: load 'script' source and execute * -m: specify custom module search path(s) * command format: berry options * command options: * -v: show version information * -h: show help information * command format: berry option file [option file] * command options: * -c: compile script file to bytecode file * -o: set the output file name * */ static int analysis_args(bvm *vm, int argc, char *argv[]) { int args = 0; struct arg_opts opt = { 0 }; opt.pattern = "m?vhilegsc?o?"; args = parse_arg(&opt, argc, argv); argc -= opt.idx; argv += opt.idx; if (args & arg_err) { be_writestring(be_pushfstring(vm, "error: missing argument to '%s'\n", opt.errarg)); be_pop(vm, 1); return -1; } if (args & arg_m) { berry_custom_paths(vm, opt.modulepath); args &= ~arg_m; } else { // use default module paths berry_paths(vm); } if (args & arg_g) { comp_set_named_gbl(vm); /* forced named global in VM code */ args &= ~arg_g; /* clear the flag for this option not to interfere with other options */ } if (args & arg_s) { comp_set_strict(vm); /* compiler in strict mode */ args &= ~arg_s; } if (args & arg_v) { be_writestring(FULL_VERSION "\n"); } if (args & arg_h) { be_writestring(help_information); } push_args(vm, argc, argv); if (args & (arg_c | arg_o)) { if (!opt.src && argc > 0) { opt.src = *argv; } return build_file(vm, opt.dst, opt.src, args); } return load_script(vm, argc, argv, args); } int main(int argc, char *argv[]) { int res; bvm *vm = be_vm_new(); /* create a virtual machine instance */ be_set_ctype_func_hanlder(vm, be_call_ctype_func); res = analysis_args(vm, argc, argv); be_vm_delete(vm); /* free all objects and vm */ return res; } #endif // COMPILE_BERRY_LIB