mirror of https://github.com/arendst/Tasmota.git
415 lines
12 KiB
C
415 lines
12 KiB
C
/********************************************************************
|
|
** 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 <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
|
|
#include "be_mapping.h"
|
|
|
|
/* using GNU/readline library */
|
|
#if defined(USE_READLINE_LIB)
|
|
#include <readline/readline.h>
|
|
#include <readline/history.h>
|
|
#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 <path> custom module search path(s) separated by '" PATH_SEPARATOR "'\n"\
|
|
" -c <file> compile script 'file' to bytecode file\n" \
|
|
" -o <file> 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" \
|
|
" <https://github.com/skiars/berry>.\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
|