blob: 62efacb903c703766237c6159770076106c509f0 [file] [log] [blame]
/*
* *****************************************************************************
*
* Copyright 2018 Gavin D. Howard
*
* Permission to use, copy, modify, and/or distribute this software for any
* purpose with or without fee is hereby granted.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
* REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
* AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
* INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
* LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
* OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
* PERFORMANCE OF THIS SOFTWARE.
*
* *****************************************************************************
*
* Code common to all of bc and dc.
*
*/
#include <assert.h>
#include <ctype.h>
#include <stdio.h>
#include <string.h>
#include <sys/types.h>
#include <signal.h>
#include <unistd.h>
#include <status.h>
#include <args.h>
#include <vm.h>
#include <io.h>
void bc_vm_sig(int sig) {
if (sig == SIGINT) {
size_t len = strlen(bcg.sig_msg);
if (write(2, bcg.sig_msg, len) == (ssize_t) len)
bcg.sig += (bcg.signe = bcg.sig == bcg.sigc);
}
else bcg.sig_other = 1;
}
BcStatus bc_vm_info(const char* const help) {
if (printf("%s %s\n", bcg.name, BC_VERSION) < 0) return BC_STATUS_IO_ERR;
if (puts(bc_copyright) == EOF) return BC_STATUS_IO_ERR;
if (help && printf(help, bcg.name) < 0) return BC_STATUS_IO_ERR;
return BC_STATUS_SUCCESS;
}
BcStatus bc_vm_envArgs(BcVm *vm) {
BcStatus s;
BcVec args;
char *env_args = NULL, *buf;
if (!(env_args = getenv(bc_args_env_name))) return BC_STATUS_SUCCESS;
if (!(buf = (vm->env_args = strdup(env_args)))) return BC_STATUS_ALLOC_ERR;
if ((s = bc_vec_init(&args, sizeof(char*), NULL))) goto args_err;
if ((s = bc_vec_push(&args, &bc_args_env_name))) goto err;
while (*buf) {
if (!isspace(*buf)) {
if ((s = bc_vec_push(&args, &buf))) goto err;
while (*buf && !isspace(*buf)) ++buf;
if (*buf) (*(buf++)) = '\0';
}
else ++buf;
}
s = bc_args((int) args.len, (char**) args.v,
&vm->flags, &vm->exprs, &vm->files);
if (s) goto err;
bc_vec_free(&args);
return s;
err:
bc_vec_free(&args);
args_err:
free(vm->env_args);
vm->env_args = NULL;
return s;
}
size_t bc_vm_envLen(const char *var) {
char *lenv;
size_t i, len = BC_NUM_PRINT_WIDTH;
int num;
if ((lenv = getenv(var))) {
len = strlen(lenv);
for (num = 1, i = 0; num && i < len; ++i) num = isdigit(lenv[i]);
if (!num || ((len = (size_t) atoi(lenv) - 1) < 2 && len >= INT32_MAX))
len = BC_NUM_PRINT_WIDTH;
}
return len;
}
BcStatus bc_vm_error(BcStatus s, const char *file, size_t line) {
assert(file);
if (!s || s >= BC_STATUS_POSIX_NAME_LEN) return s;
fprintf(stderr, bc_err_fmt, bc_errs[bc_err_indices[s]], bc_err_descs[s]);
fprintf(stderr, " %s", file);
fprintf(stderr, bc_err_line + 4 * !line, line);
return s * (!bcg.tty || !!strcmp(file, bc_program_stdin_name));
}
#ifdef BC_ENABLED
BcStatus bc_vm_posixError(BcStatus s, const char *file,
size_t line, const char *msg)
{
int p = (int) bcg.posix, w = (int) bcg.warn;
if (!(p || w) || s < BC_STATUS_POSIX_NAME_LEN) return BC_STATUS_SUCCESS;
fprintf(stderr, "\n%s %s: %s\n", bc_errs[bc_err_indices[s]],
p ? "error" : "warning", bc_err_descs[s]);
if (msg) fprintf(stderr, " %s\n", msg);
if (file) {
fprintf(stderr, " %s", file);
fprintf(stderr, bc_err_line + 4 * !line, line);
}
return s * (!bcg.tty && !!p);
}
#endif // BC_ENABLED
BcStatus bc_vm_process(BcVm *vm, const char *text) {
BcStatus s = bc_parse_text(&vm->prs, text);
if ((s = bc_vm_error(s, vm->prs.l.f, vm->prs.l.line))) return s;
while (vm->prs.l.t.t != BC_LEX_EOF) {
if ((s = vm->prs.parse(&vm->prs)) == BC_STATUS_LIMITS) {
s = BC_STATUS_IO_ERR;
if (putchar('\n') == EOF) return s;
if (printf("BC_BASE_MAX = %lu\n", BC_MAX_OBASE) < 0) return s;
if (printf("BC_DIM_MAX = %lu\n", BC_MAX_DIM) < 0) return s;
if (printf("BC_SCALE_MAX = %lu\n", BC_MAX_SCALE) < 0) return s;
if (printf("BC_STRING_MAX = %lu\n", BC_MAX_STRING) < 0) return s;
if (printf("BC_NAME_MAX = %lu\n", BC_MAX_NAME) < 0) return s;
if (printf("BC_NUM_MAX = %lu\n", BC_MAX_NUM) < 0) return s;
if (printf("Max Exponent = %lu\n", BC_MAX_EXP) < 0) return s;
if (printf("Number of Vars = %lu\n", BC_MAX_VARS) < 0) return s;
if (putchar('\n') == EOF) return s;
s = BC_STATUS_SUCCESS;
}
else if (s == BC_STATUS_QUIT || bcg.sig_other ||
(s = bc_vm_error(s, vm->prs.l.f, vm->prs.l.line)))
{
return s;
}
}
if (BC_PARSE_CAN_EXEC(&vm->prs)) {
s = bc_program_exec(&vm->prog);
if (bcg.tty) fflush(stdout);
if (s && s != BC_STATUS_QUIT)
s = bc_vm_error(bc_program_reset(&vm->prog, s), vm->prs.l.f, 0);
}
return s;
}
BcStatus bc_vm_file(BcVm *vm, const char *file) {
BcStatus s;
char *data;
BcFunc *main_func;
BcInstPtr *ip;
vm->prog.file = file;
if ((s = bc_io_fread(file, &data))) return bc_vm_error(s, file, 0);
bc_lex_file(&vm->prs.l, file);
if ((s = bc_vm_process(vm, data))) goto err;
main_func = bc_vec_item(&vm->prog.fns, BC_PROG_MAIN);
ip = bc_vec_item(&vm->prog.stack, 0);
if (main_func->code.len < ip->idx)
s = bc_vm_error(BC_STATUS_EXEC_FILE_NOT_EXECUTABLE, file, 0);
err:
free(data);
return s;
}
BcStatus bc_vm_stdin(BcVm *vm) {
BcStatus s;
BcVec buf, buffer;
char c;
size_t len, i, string = 0;
bool comment = false, notend;
vm->prog.file = bc_program_stdin_name;
bc_lex_file(&vm->prs.l, bc_program_stdin_name);
if ((s = bc_vec_init(&buffer, sizeof(char), NULL))) return s;
if ((s = bc_vec_init(&buf, sizeof(char), NULL))) goto buf_err;
if ((s = bc_vec_pushByte(&buffer, '\0'))) goto err;
// This loop is complex because the vm tries not to send any lines that end
// with a backslash to the parser. The reason for that is because the parser
// treats a backslash+newline combo as whitespace, per the bc spec. In that
// case, and for strings and comments, the parser will expect more stuff.
while (!s && !(s = bc_io_getline(&buf, ">>> "))) {
char *str = buf.v;
len = buf.len - 1;
if (len == 1) {
if (string && buf.v[0] == vm->exe.strend) string -= 1;
else if (buf.v[0] == vm->exe.strbgn) string += 1;
}
else if (len > 1 || comment) {
for (i = 0; i < len; ++i) {
notend = len > i + 1;
c = str[i];
if (i - 1 > len || str[i - 1] != '\\') {
if (c == vm->exe.strend) string -= 1;
else if (c == vm->exe.strbgn) string += 1;
}
else if (c == '/' && notend && !comment && str[i + 1] == '*') {
comment = true;
break;
}
else if (c == '*' && notend && comment && str[i + 1] == '/')
comment = false;
}
if (string || comment || str[len - 2] == '\\') {
if ((s = bc_vec_concat(&buffer, buf.v))) goto err;
continue;
}
}
if ((s = bc_vec_concat(&buffer, buf.v))) goto err;
if ((s = bc_vm_process(vm, buffer.v))) goto err;
bc_vec_npop(&buffer, buffer.len);
}
if (s == BC_STATUS_BIN_FILE)
s = bc_vm_error(s, vm->prs.l.f, vm->prs.l.line);
// I/O error will always happen when stdin is
// closed. It's not a problem in that case.
s = s == BC_STATUS_IO_ERR || s == BC_STATUS_QUIT ? BC_STATUS_SUCCESS : s;
if (string) s = bc_vm_error(BC_STATUS_LEX_NO_STRING_END,
vm->prs.l.f, vm->prs.l.line);
else if (comment) s = bc_vm_error(BC_STATUS_LEX_NO_COMMENT_END,
vm->prs.l.f, vm->prs.l.line);
err:
bc_vec_free(&buf);
buf_err:
bc_vec_free(&buffer);
return s;
}
BcStatus bc_vm_exec(BcVm *vm) {
BcStatus s = BC_STATUS_SUCCESS;
size_t i;
#ifdef BC_ENABLED
if (vm->flags & BC_FLAG_L) {
bc_lex_file(&vm->prs.l, bc_lib_name);
if ((s = bc_parse_text(&vm->prs, bc_lib))) return s;
while (!s && vm->prs.l.t.t != BC_LEX_EOF) s = vm->prs.parse(&vm->prs);
if (s || (s = bc_program_exec(&vm->prog))) return s;
}
#endif // BC_ENABLED
if (vm->exprs.len > 1 && (s = bc_vm_process(vm, vm->exprs.v))) return s;
for (i = 0; !bcg.sig_other && !s && i < vm->files.len; ++i)
s = bc_vm_file(vm, *((char**) bc_vec_item(&vm->files, i)));
if ((s && s != BC_STATUS_QUIT) || bcg.sig_other) return s;
if ((bcg.bc || !vm->files.len) && vm->exprs.len <= 1) s = bc_vm_stdin(vm);
return s == BC_STATUS_QUIT ? BC_STATUS_SUCCESS : s;
}
BcStatus bc_vm_init(BcVm *vm, BcVmExe exe, const char *env_len) {
BcStatus s;
struct sigaction sa;
size_t len = bc_vm_envLen(env_len);
sigemptyset(&sa.sa_mask);
sa.sa_handler = bc_vm_sig;
sa.sa_flags = 0;
if (sigaction(SIGINT, &sa, NULL) < 0 || sigaction(SIGPIPE, &sa, NULL) < 0 ||
sigaction(SIGHUP, &sa, NULL) < 0 || sigaction(SIGTERM, &sa, NULL) < 0)
{
return BC_STATUS_EXEC_SIGACTION_FAIL;
}
vm->exe = exe;
vm->flags = 0;
vm->env_args = NULL;
if ((s = bc_vec_init(&vm->files, sizeof(char*), NULL))) return s;
if ((s = bc_vec_init(&vm->exprs, sizeof(char), NULL))) goto exprs_err;
#ifdef BC_ENABLED
vm->flags |= BC_FLAG_S * bcg.bc * (getenv("POSIXLY_CORRECT") != NULL);
if (bcg.bc && (s = bc_vm_envArgs(vm))) goto prog_err;
#endif // BC_ENABLED
if ((s = bc_program_init(&vm->prog, len, exe.init, exe.exp))) goto prog_err;
if ((s = exe.init(&vm->prs, &vm->prog, BC_PROG_MAIN))) goto parse_err;
return s;
parse_err:
bc_program_free(&vm->prog);
prog_err:
bc_vec_free(&vm->exprs);
exprs_err:
bc_vec_free(&vm->files);
return s;
}
void bc_vm_free(BcVm *vm) {
bc_vec_free(&vm->files);
bc_vec_free(&vm->exprs);
bc_program_free(&vm->prog);
bc_parse_free(&vm->prs);
free(vm->env_args);
}
BcStatus bc_vm_run(int argc, char *argv[], BcVmExe exe, const char *env_len) {
BcStatus st;
BcVm vm;
int ttyout = isatty(1);
if ((st = bc_vm_init(&vm, exe, env_len))) return st;
if ((st = bc_args(argc, argv, &vm.flags, &vm.exprs, &vm.files))) goto err;
bcg.ttyin = isatty(0);
bcg.tty = (vm.flags & BC_FLAG_I) || bcg.ttyin || ttyout;
#ifdef BC_ENABLED
bcg.posix = vm.flags & BC_FLAG_S;
bcg.warn = vm.flags & BC_FLAG_W;
#endif // BC_ENABLED
#ifdef DC_ENABLED
bcg.exreg = vm.flags & BC_FLAG_X;
#endif // DC_ENABLED
if (bcg.tty && ttyout && !(vm.flags & BC_FLAG_Q) && (st = bc_vm_info(NULL)))
goto err;
st = bc_vm_exec(&vm);
err:
bc_vm_free(&vm);
return st;
}