An implementation of Unix dc and POSIX bc with GNU and BSD extensions
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 

601 lines
13 KiB

/*
* *****************************************************************************
*
* 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 <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 len = vm->sig_len;
if (write(STDERR_FILENO, vm->sig_msg, len) != (ssize_t) len) 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->sig_msg, 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);
}
static BcStatus bc_vm_printError(BcError e, const char* const fmt,
size_t line, va_list args)
{
size_t id = (size_t) bc_err_ids[e];
// Make sure all of stdout is written first.
fflush(stdout);
fprintf(stderr, fmt, bc_errs[id]);
vfprintf(stderr, vm->err_msgs[e], args);
assert(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 Function: %s", f->name);
}
fputs("\n\n", stderr);
fflush(stderr);
return (BcStatus) (id + 1);
}
BcStatus bc_vm_error(BcError e, size_t line, ...) {
BcStatus s;
va_list args;
#if BC_ENABLED
assert(e < BC_ERROR_POSIX_START || e > BC_ERROR_POSIX_END);
#endif // BC_ENABLED
va_start(args, line);
s = bc_vm_printError(e, bc_err_fmt, line, args);
va_end(args);
return s;
}
#if BC_ENABLED
BcStatus bc_vm_posixError(BcError e, size_t line, ...) {
BcStatus s;
va_list args;
int p = (int) BC_S, w = (int) BC_W;
assert(e >= BC_ERROR_POSIX_START && e <= BC_ERROR_POSIX_END);
if (!(p || w)) return BC_STATUS_SUCCESS;
va_start(args, line);
s = bc_vm_printError(e, p ? bc_err_fmt : bc_warn_fmt, line, args);
va_end(args);
return p ? s : BC_STATUS_SUCCESS;
}
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);
}
void* bc_vm_malloc(size_t n) {
void* ptr = malloc(n);
if (!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 (!temp) bc_vm_exit(BC_ERROR_FATAL_ALLOC_ERR);
return temp;
}
char* bc_vm_strdup(const char *str) {
char *s = strdup(str);
if (!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 (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 (fputs(str, f) == EOF || ferror(f)) bc_vm_exit(BC_ERROR_FATAL_IO_ERR);
}
void bc_vm_putchar(int c) {
if (putchar(c) == EOF || ferror(stdout)) bc_vm_exit(BC_ERROR_FATAL_IO_ERR);
}
void bc_vm_fflush(FILE *restrict f) {
if (fflush(f) == EOF || ferror(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 (s) goto err;
while (vm->prs.l.t != BC_LEX_EOF) {
s = vm->parse(&vm->prs);
if (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 (s) return s;
s = bc_vm_process(data, false);
if (s) goto err;
#if BC_ENABLED
if (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 (; !done && !BC_STATUS_IS_ERROR(s) && buf.len > 1 && !BC_SIGNAL &&
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 (s) goto err;
bc_vec_empty(&buffer);
}
if (s && s != BC_STATUS_EOF) goto err;
else if (BC_SIGNAL && !s) s = BC_STATUS_SIGNAL;
else if (!BC_STATUS_IS_ERROR(s)) {
if (comment) s = bc_parse_err(&vm->prs, BC_ERROR_PARSE_COMMENT);
else if (string) s = bc_parse_err(&vm->prs, BC_ERROR_PARSE_STRING);
#if BC_ENABLED
else if (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 (!s && vm->prs.l.t != BC_LEX_EOF) s = vm->parse(&vm->prs);
return s;
}
#endif // BC_ENABLED
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 (s) return s;
#if BC_ENABLE_EXTRA_MATH
if (!BC_IS_POSIX) {
s = bc_vm_load(bc_lib2_name, bc_lib2);
if (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 (s) return s;
}
for (i = 0; !s && i < vm->files.len; ++i)
s = bc_vm_file(*((char**) bc_vec_item(&vm->files, i)));
if (s && s != BC_STATUS_QUIT) 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;
int i;
#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
#if BC_ENABLE_NLS
{
BcVec dir;
char *dot;
bc_vec_init(&dir, sizeof(char), NULL);
bc_vec_string(&dir, strlen(BC_LOCALEDIR), BC_LOCALEDIR);
bc_vec_concat(&dir, "/");
bc_vec_concat(&dir, vm->locale);
dot = strrchr(dir.v, '.');
if (dot) {
uintptr_t idx = ((uintptr_t) dot) - ((uintptr_t) dir.v);
bc_vec_npop(&dir, dir.len - idx);
bc_vec_pushByte(&dir, '\0');
}
vm->catalog = catopen(dir.v, NL_CAT_LOCALE);
for (i = 0; i < BC_ERROR_NELEMS; ++i)
vm->err_msgs[i] = catgets(vm->catalog, NL_SETD, i + 1, bc_err_msgs[i]);
bc_vec_free(&dir);
}
#else // BC_ENABLE_NLS
for (i = 0; i < BC_ERROR_NELEMS; ++i)
vm->err_msgs[i] = bc_err_msgs[i];
#endif // BC_ENABLE_NLS
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 (s) goto exit;
}
#endif // BC_ENABLED
s = bc_args(argc, argv);
if (s) goto exit;
vm->flags |= isatty(STDIN_FILENO) ? BC_FLAG_TTYIN : 0;
vm->flags |= BC_TTYIN && isatty(STDOUT_FILENO) ? BC_FLAG_I : 0;
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;
}