Tasmota/lib/libesp32/berry/default/berry.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