blob: d43d60acd59dd6354b8ae8b977d8e8621e44ecec [file] [log] [blame]
/*
* *****************************************************************************
*
* Copyright (c) 2018-2019 Gavin D. Howard and contributors.
*
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* * Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* * Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*
* *****************************************************************************
*
* Code common to all of bc and dc.
*
*/
#include <assert.h>
#include <ctype.h>
#include <errno.h>
#include <stdarg.h>
#include <stdio.h>
#include <string.h>
#include <signal.h>
#ifndef _WIN32
#include <sys/types.h>
#include <unistd.h>
#else // _WIN32
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#include <io.h>
#endif // _WIN32
#include <status.h>
#include <args.h>
#include <vm.h>
#include <read.h>
#include <bc.h>
#if BC_ENABLE_SIGNALS
#ifndef _WIN32
static void bc_vm_sig(int sig) {
int err = errno;
if (sig == SIGINT) {
size_t n = vm->siglen;
if (BC_ERR(write(STDERR_FILENO, vm->sigmsg, n) != (ssize_t) n)) sig = 0;
}
vm->sig = (uchar) sig;
errno = err;
}
#else // _WIN32
static BOOL WINAPI bc_vm_sig(DWORD sig) {
if (sig == CTRL_C_EVENT) bc_vm_puts(vm->sigmsg, stderr);
vm->sig = (uchar) sig;
return TRUE;
}
#endif // _WIN32
#endif // BC_ENABLE_SIGNALS
void bc_vm_info(const char* const help) {
bc_vm_printf("%s %s\n", vm->name, BC_VERSION);
bc_vm_puts(bc_copyright, stdout);
if (help) bc_vm_printf(help, vm->name);
}
BcStatus bc_vm_error(BcError e, size_t line, ...) {
va_list args;
uchar id = bc_err_ids[e];
const char* err_type = vm->err_ids[id];
assert(e < BC_ERROR_NELEMS);
#if BC_ENABLED
if (!BC_S && e >= BC_ERROR_POSIX_START) {
if (BC_W) {
// Make sure to not return an error.
id = UCHAR_MAX;
err_type = vm->err_ids[BC_ERR_IDX_WARN];
}
else return BC_STATUS_SUCCESS;
}
#endif // BC_ENABLED
// Make sure all of stdout is written first.
fflush(stdout);
va_start(args, line);
fprintf(stderr, "\n%s ", err_type);
vfprintf(stderr, vm->err_msgs[e], args);
va_end(args);
if (BC_NO_ERR(vm->file)) {
// This is the condition for parsing vs runtime.
// If line is not 0, it is parsing.
if (line) {
fprintf(stderr, "\n %s", vm->file);
fprintf(stderr, bc_err_line, line);
}
else {
BcInstPtr *ip = bc_vec_item_rev(&vm->prog.stack, 0);
BcFunc *f = bc_vec_item(&vm->prog.fns, ip->func);
fprintf(stderr, "\n %s %s", vm->func_header, f->name);
if (ip->func != BC_PROG_MAIN && ip->func != BC_PROG_READ)
fprintf(stderr, "()");
}
}
fputs("\n\n", stderr);
fflush(stderr);
return (BcStatus) (id + 1);
}
#if BC_ENABLED
static BcStatus bc_vm_envArgs(void) {
BcStatus s;
BcVec v;
char *env_args = getenv(bc_args_env_name), *buf;
if (!env_args) return BC_STATUS_SUCCESS;
vm->env_args = bc_vm_strdup(env_args);
buf = vm->env_args;
bc_vec_init(&v, sizeof(char*), NULL);
bc_vec_push(&v, &bc_args_env_name);
while (*buf) {
if (!isspace(*buf)) {
bc_vec_push(&v, &buf);
while (*buf && !isspace(*buf)) buf += 1;
if (*buf) {
*buf = '\0';
buf += 1;
}
}
else buf += 1;
}
// Make sure to push a NULL pointer at the end.
buf = NULL;
bc_vec_push(&v, &buf);
s = bc_args((int) v.len - 1, (char**) v.v);
bc_vec_free(&v);
return s;
}
#endif // BC_ENABLED
static size_t bc_vm_envLen(const char *var) {
char *lenv = getenv(var);
size_t i, len = BC_NUM_PRINT_WIDTH;
int num;
if (!lenv) return len;
len = strlen(lenv);
for (num = 1, i = 0; num && i < len; ++i) num = isdigit(lenv[i]);
if (num) {
len = (size_t) atoi(lenv) - 1;
if (len < 2 || len >= UINT16_MAX) len = BC_NUM_PRINT_WIDTH;
}
else len = BC_NUM_PRINT_WIDTH;
return len;
}
void bc_vm_shutdown(void) {
#if BC_ENABLE_NLS
catclose(vm->catalog);
#endif // BC_ENABLE_NLS
#if BC_ENABLE_HISTORY
// This must always run to ensure that the terminal is back to normal.
bc_history_free(&vm->history);
#endif // BC_ENABLE_HISTORY
#ifndef NDEBUG
bc_vec_free(&vm->files);
bc_vec_free(&vm->exprs);
bc_program_free(&vm->prog);
bc_parse_free(&vm->prs);
free(vm->env_args);
free(vm);
#endif // NDEBUG
}
static void bc_vm_exit(BcError e) {
BcStatus s = bc_vm_err(e);
bc_vm_shutdown();
exit((int) s);
}
size_t bc_vm_arraySize(size_t n, size_t size) {
size_t res = n * size;
if (BC_ERR(n != 0 && res / n != size)) bc_vm_exit(BC_ERROR_FATAL_ALLOC_ERR);
return res;
}
size_t bc_vm_growSize(size_t a, size_t b) {
size_t res = a + b;
if (BC_ERR(res < a || res < b)) bc_vm_exit(BC_ERROR_FATAL_ALLOC_ERR);
return res;
}
void* bc_vm_malloc(size_t n) {
void* ptr = malloc(n);
if (BC_ERR(!ptr)) bc_vm_exit(BC_ERROR_FATAL_ALLOC_ERR);
return ptr;
}
void* bc_vm_realloc(void *ptr, size_t n) {
void* temp = realloc(ptr, n);
if (BC_ERR(!temp)) bc_vm_exit(BC_ERROR_FATAL_ALLOC_ERR);
return temp;
}
char* bc_vm_strdup(const char *str) {
char *s = strdup(str);
if (BC_ERR(!s)) bc_vm_exit(BC_ERROR_FATAL_ALLOC_ERR);
return s;
}
size_t bc_vm_printf(const char *fmt, ...) {
va_list args;
int ret;
va_start(args, fmt);
ret = vprintf(fmt, args);
va_end(args);
if (BC_ERR(ret < 0 || ferror(stdout))) bc_vm_exit(BC_ERROR_FATAL_IO_ERR);
return (size_t) ret;
}
void bc_vm_puts(const char *str, FILE *restrict f) {
if (BC_IO_ERR(fputs(str, f), f)) bc_vm_exit(BC_ERROR_FATAL_IO_ERR);
}
void bc_vm_putchar(int c) {
if (BC_IO_ERR(fputc(c, stdout), stdout)) bc_vm_exit(BC_ERROR_FATAL_IO_ERR);
}
void bc_vm_fflush(FILE *restrict f) {
if (BC_IO_ERR(fflush(f), f)) bc_vm_exit(BC_ERROR_FATAL_IO_ERR);
}
static void bc_vm_clean() {
BcProgram *prog = &vm->prog;
BcVec *fns = &prog->fns;
BcFunc *f = bc_vec_item(fns, BC_PROG_MAIN);
BcInstPtr *ip = bc_vec_item(&prog->stack, 0);
bool good = false;
#if BC_ENABLED
if (BC_IS_BC) good = !BC_PARSE_NO_EXEC(&vm->prs);
#endif // BC_ENABLED
#if DC_ENABLED
if (!BC_IS_BC) {
size_t i;
for (i = 0; i < vm->prog.vars.len; ++i) {
BcVec *arr = bc_vec_item(&vm->prog.vars, i);
BcNum *n = bc_vec_top(arr);
if (arr->len != 1 || BC_PROG_STR(n)) break;
}
if (i == vm->prog.vars.len) {
for (i = 0; i < vm->prog.arrs.len; ++i) {
BcVec *arr = bc_vec_item(&vm->prog.arrs, i);
size_t j;
assert(arr->len == 1);
arr = bc_vec_top(arr);
for (j = 0; j < arr->len; ++j) {
BcNum *n = bc_vec_item(arr, j);
if (BC_PROG_STR(n)) break;
}
if (j != arr->len) break;
}
good = (i == vm->prog.arrs.len);
}
}
#endif // DC_ENABLED
// If this condition is true, we can get rid of strings,
// constants, and code. This is an idea from busybox.
if (good && prog->stack.len == 1 && !prog->results.len &&
ip->idx == f->code.len)
{
#if BC_ENABLED
bc_vec_npop(&f->labels, f->labels.len);
#endif // BC_ENABLED
bc_vec_npop(&f->strs, f->strs.len);
bc_vec_npop(&f->consts, f->consts.len);
bc_vec_npop(&f->code, f->code.len);
ip->idx = 0;
#if DC_ENABLED
if (!BC_IS_BC) bc_vec_npop(fns, fns->len - BC_PROG_REQ_FUNCS);
#endif // DC_ENABLED
}
}
static BcStatus bc_vm_process(const char *text, bool is_stdin) {
BcStatus s;
s = bc_parse_text(&vm->prs, text);
if (BC_ERR(s)) goto err;
while (vm->prs.l.t != BC_LEX_EOF) {
s = vm->parse(&vm->prs);
if (BC_ERR(s)) goto err;
}
#if BC_ENABLED
if (BC_PARSE_NO_EXEC(&vm->prs)) goto err;
#endif // BC_ENABLED
s = bc_program_exec(&vm->prog);
if (BC_I) bc_vm_fflush(stdout);
err:
bc_vm_clean();
return s == BC_STATUS_QUIT || !BC_I || !is_stdin ? s : BC_STATUS_SUCCESS;
}
static BcStatus bc_vm_file(const char *file) {
BcStatus s;
char *data;
bc_lex_file(&vm->prs.l, file);
s = bc_read_file(file, &data);
if (BC_ERR(s)) return s;
s = bc_vm_process(data, false);
if (BC_ERR(s)) goto err;
#if BC_ENABLED
if (BC_ERR(BC_PARSE_NO_EXEC(&vm->prs)))
s = bc_parse_err(&vm->prs, BC_ERROR_PARSE_BLOCK);
#endif // BC_ENABLED
err:
free(data);
return s;
}
static BcStatus bc_vm_stdin(void) {
BcStatus s = BC_STATUS_SUCCESS;
BcVec buf, buffer;
size_t string = 0;
bool comment = false, done = false;
bc_lex_file(&vm->prs.l, bc_program_stdin_name);
bc_vec_init(&buffer, sizeof(uchar), NULL);
bc_vec_init(&buf, sizeof(uchar), NULL);
bc_vec_pushByte(&buffer, '\0');
s = bc_read_line(&buf, ">>> ");
// 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.
for (; !BC_STATUS_IS_ERROR(s) && buf.len > 1 && BC_NO_SIG &&
s != BC_STATUS_SIGNAL; s = bc_read_line(&buf, ">>> "))
{
char c2, *str = buf.v;
size_t i, len = buf.len - 1;
done = (s == BC_STATUS_EOF);
if (len >= 2 && str[len - 1] == '\n' && str[len - 2] == '\\') {
bc_vec_concat(&buffer, buf.v);
continue;
}
for (i = 0; i < len; ++i) {
bool notend = len > i + 1;
uchar c = (uchar) str[i];
if (!comment && (i - 1 > len || str[i - 1] != '\\')) {
if (BC_IS_BC) string ^= c == '"';
else if (c == ']') string -= 1;
else if (c == '[') string += 1;
}
if (BC_IS_BC && !string && notend) {
c2 = str[i + 1];
if (c == '/' && !comment && c2 == '*') {
comment = true;
i += 1;
}
else if (c == '*' && comment && c2 == '/') {
comment = false;
i += 1;
}
}
}
bc_vec_concat(&buffer, buf.v);
if (string || comment) continue;
if (len >= 2 && str[len - 2] == '\\' && str[len - 1] == '\n') continue;
s = bc_vm_process(buffer.v, true);
if (BC_ERR(s)) goto err;
bc_vec_empty(&buffer);
if (done) break;
}
if (BC_ERR(s && s != BC_STATUS_EOF)) goto err;
else if (BC_NO_ERR(!s) && BC_SIG) s = BC_STATUS_SIGNAL;
else if (!BC_STATUS_IS_ERROR(s)) {
if (BC_ERR(comment))
s = bc_parse_err(&vm->prs, BC_ERROR_PARSE_COMMENT);
else if (BC_ERR(string))
s = bc_parse_err(&vm->prs, BC_ERROR_PARSE_STRING);
#if BC_ENABLED
else if (BC_ERR(BC_PARSE_NO_EXEC(&vm->prs)))
s = bc_parse_err(&vm->prs, BC_ERROR_PARSE_BLOCK);
#endif // BC_ENABLED
}
err:
bc_vec_free(&buf);
bc_vec_free(&buffer);
return s;
}
#if BC_ENABLED
static BcStatus bc_vm_load(const char *name, const char *text) {
BcStatus s;
bc_lex_file(&vm->prs.l, name);
s = bc_parse_text(&vm->prs, text);
while (BC_NO_ERR(!s) && vm->prs.l.t != BC_LEX_EOF) s = vm->parse(&vm->prs);
return s;
}
#endif // BC_ENABLED
static void bc_vm_defaultMsgs() {
size_t i;
vm->func_header = bc_err_func_header;
for (i = 0; i < BC_ERR_IDX_NELEMS + BC_ENABLED; ++i)
vm->err_ids[i] = bc_errs[i];
for (i = 0; i < BC_ERROR_NELEMS; ++i) vm->err_msgs[i] = bc_err_msgs[i];
}
static void bc_vm_gettext() {
#if BC_ENABLE_NLS
uchar id = 0;
int set, msg;
size_t i;
if (!vm->locale) {
bc_vm_defaultMsgs();
return;
}
vm->catalog = catopen(BC_MAINEXEC, NL_CAT_LOCALE);
if (vm->catalog == (nl_catd) -1) {
bc_vm_defaultMsgs();
return;
}
set = msg = 1;
vm->func_header = catgets(vm->catalog, set, msg, bc_err_func_header);
for (set += 1, msg = 1; msg <= BC_ERR_IDX_NELEMS + BC_ENABLED; ++msg)
vm->err_ids[msg - 1] = catgets(vm->catalog, set, msg, bc_errs[msg - 1]);
i = 0;
id = bc_err_ids[i];
for (set = id + 3, msg = 1; i < BC_ERROR_NELEMS; ++i, ++msg) {
if (id != bc_err_ids[i]) {
msg = 1;
id = bc_err_ids[i];
set = id + 3;
}
vm->err_msgs[i] = catgets(vm->catalog, set, msg, bc_err_msgs[i]);
}
#else // BC_ENABLE_NLS
bc_vm_defaultMsgs();
#endif // BC_ENABLE_NLS
}
static BcStatus bc_vm_exec(void) {
BcStatus s = BC_STATUS_SUCCESS;
size_t i;
#if BC_ENABLED
if (BC_IS_BC && vm->flags & BC_FLAG_L) {
s = bc_vm_load(bc_lib_name, bc_lib);
if (BC_ERR(s)) return s;
#if BC_ENABLE_EXTRA_MATH
if (!BC_IS_POSIX) {
s = bc_vm_load(bc_lib2_name, bc_lib2);
if (BC_ERR(s)) return s;
}
#endif // BC_ENABLE_EXTRA_MATH
}
#endif // BC_ENABLED
if (vm->exprs.len) {
bc_lex_file(&vm->prs.l, bc_program_exprs_name);
s = bc_vm_process(vm->exprs.v, false);
if (BC_ERR(s)) return s;
}
for (i = 0; BC_NO_ERR(!s) && i < vm->files.len; ++i)
s = bc_vm_file(*((char**) bc_vec_item(&vm->files, i)));
if (BC_ERR(s)) return s;
if ((BC_IS_BC || !vm->files.len) && !vm->exprs.len) s = bc_vm_stdin();
return s;
}
BcStatus bc_vm_boot(int argc, char *argv[], const char *env_len) {
BcStatus s;
#if BC_ENABLE_SIGNALS
#ifndef _WIN32
struct sigaction sa;
sigemptyset(&sa.sa_mask);
sa.sa_handler = bc_vm_sig;
sa.sa_flags = 0;
sigaction(SIGINT, &sa, NULL);
sigaction(SIGTERM, &sa, NULL);
sigaction(SIGQUIT, &sa, NULL);
#else // _WIN32
SetConsoleCtrlHandler(bc_vm_sig, TRUE);
#endif // _WIN32
#endif // BC_ENABLE_SIGNALS
bc_vm_gettext();
vm->line_len = (uint16_t) bc_vm_envLen(env_len);
bc_vec_init(&vm->files, sizeof(char*), NULL);
bc_vec_init(&vm->exprs, sizeof(uchar), NULL);
bc_program_init(&vm->prog);
bc_parse_init(&vm->prs, &vm->prog, BC_PROG_MAIN);
#if BC_ENABLE_HISTORY
bc_history_init(&vm->history);
#endif // BC_ENABLE_HISTORY
#if BC_ENABLED
if (BC_IS_BC) {
vm->flags |= BC_FLAG_S * (getenv("POSIXLY_CORRECT") != NULL);
s = bc_vm_envArgs();
if (BC_ERR(s)) goto exit;
}
#endif // BC_ENABLED
s = bc_args(argc, argv);
if (BC_ERR(s)) goto exit;
vm->flags |= isatty(STDIN_FILENO) ? BC_FLAG_TTYIN : 0;
vm->flags |= BC_TTYIN && isatty(STDOUT_FILENO) ? BC_FLAG_I : 0;
if (BC_IS_POSIX) vm->flags &= ~(BC_FLAG_G);
vm->max_ibase = BC_IS_BC && !BC_IS_POSIX ? BC_NUM_MAX_IBASE :
BC_NUM_MAX_POSIX_IBASE;
if (BC_I && !(vm->flags & BC_FLAG_Q)) bc_vm_info(NULL);
s = bc_vm_exec();
exit:
bc_vm_shutdown();
return !BC_STATUS_IS_ERROR(s) ? BC_STATUS_SUCCESS : s;
}