diff options
Diffstat (limited to 'contrib/bc/src/vm.c')
-rw-r--r-- | contrib/bc/src/vm.c | 1316 |
1 files changed, 829 insertions, 487 deletions
diff --git a/contrib/bc/src/vm.c b/contrib/bc/src/vm.c index ef2257644f52..f4cd82e3ee4b 100644 --- a/contrib/bc/src/vm.c +++ b/contrib/bc/src/vm.c @@ -3,7 +3,7 @@ * * SPDX-License-Identifier: BSD-2-Clause * - * Copyright (c) 2018-2021 Gavin D. Howard and contributors. + * Copyright (c) 2018-2023 Gavin D. Howard and contributors. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: @@ -63,38 +63,53 @@ #include <vm.h> #include <read.h> #include <bc.h> +#if BC_ENABLE_LIBRARY +#include <library.h> +#endif // BC_ENABLE_LIBRARY + +#if !BC_ENABLE_LIBRARY // The actual globals. -static BcDig* temps_buf[BC_VM_MAX_TEMPS]; char output_bufs[BC_VM_BUF_SIZE]; -BcVm vm; +BcVm vm_data; +BcVm* vm = &vm_data; + +#endif // !BC_ENABLE_LIBRARY #if BC_DEBUG_CODE -BC_NORETURN void bc_vm_jmp(const char* f) { +BC_NORETURN void +bc_vm_jmp(const char* f) +{ #else // BC_DEBUG_CODE -BC_NORETURN void bc_vm_jmp(void) { +BC_NORETURN void +bc_vm_jmp(void) +{ #endif - assert(BC_SIG_EXC); +#if BC_ENABLE_LIBRARY + BcVm* vm = bcl_getspecific(); +#endif // BC_ENABLE_LIBRARY + + assert(BC_SIG_EXC(vm)); BC_SIG_MAYLOCK; #if BC_DEBUG_CODE - bc_file_puts(&vm.ferr, bc_flush_none, "Longjmp: "); - bc_file_puts(&vm.ferr, bc_flush_none, f); - bc_file_putchar(&vm.ferr, bc_flush_none, '\n'); - bc_file_flush(&vm.ferr, bc_flush_none); + bc_file_puts(&vm->ferr, bc_flush_none, "Longjmp: "); + bc_file_puts(&vm->ferr, bc_flush_none, f); + bc_file_putchar(&vm->ferr, bc_flush_none, '\n'); + bc_file_flush(&vm->ferr, bc_flush_none); #endif // BC_DEBUG_CODE -#ifndef NDEBUG - assert(vm.jmp_bufs.len - (size_t) vm.sig_pop); -#endif // NDEBUG +#if BC_DEBUG + assert(vm->jmp_bufs.len - (size_t) vm->sig_pop); +#endif // BC_DEBUG - if (vm.jmp_bufs.len == 0) abort(); - if (vm.sig_pop) bc_vec_pop(&vm.jmp_bufs); - else vm.sig_pop = 1; + if (vm->jmp_bufs.len == 0) abort(); + if (vm->sig_pop) bc_vec_pop(&vm->jmp_bufs); + else vm->sig_pop = 1; - siglongjmp(*((sigjmp_buf*) bc_vec_top(&vm.jmp_bufs)), 1); + siglongjmp(*((sigjmp_buf*) bc_vec_top(&vm->jmp_bufs)), 1); } #if !BC_ENABLE_LIBRARY @@ -103,51 +118,123 @@ BC_NORETURN void bc_vm_jmp(void) { * Handles signals. This is the signal handler. * @param sig The signal to handle. */ -static void bc_vm_sig(int sig) { +static void +bc_vm_sig(int sig) +{ +#if BC_ENABLE_EDITLINE + // Editline needs this to resize the terminal. This also needs to come first + // because a resize always needs to happen. + if (sig == SIGWINCH) + { + if (BC_TTY) + { + el_resize(vm->history.el); + + // If the signal was a SIGWINCH, clear it because we don't need to + // print a stack trace in that case. + if (vm->sig == SIGWINCH) + { + vm->sig = 0; + } + } - // There is already a signal in flight. - if (vm.status == (sig_atomic_t) BC_STATUS_QUIT || vm.sig) { - if (!BC_I || sig != SIGINT) vm.status = BC_STATUS_QUIT; return; } +#endif // BC_ENABLE_EDITLINE - // Only reset under these conditions; otherwise, quit. - if (sig == SIGINT && BC_SIGINT && BC_I) { + // There is already a signal in flight if this is true. + if (vm->status == (sig_atomic_t) BC_STATUS_QUIT || vm->sig != 0) + { + if (!BC_I || sig != SIGINT) vm->status = BC_STATUS_QUIT; + return; + } + // We always want to set this because a stack trace can be printed if we do. + vm->sig = sig; + + // Only reset under these conditions; otherwise, quit. + if (sig == SIGINT && BC_SIGINT && BC_I) + { int err = errno; +#if BC_ENABLE_EDITLINE + // Editline needs this, for some unknown reason. + if (write(STDOUT_FILENO, "^C", 2) != (ssize_t) 2) + { + vm->status = BC_STATUS_ERROR_FATAL; + } +#endif // BC_ENABLE_EDITLINE + // Write the message. - if (write(STDOUT_FILENO, vm.sigmsg, vm.siglen) != (ssize_t) vm.siglen) - vm.status = BC_STATUS_ERROR_FATAL; - else vm.sig = 1; + if (write(STDOUT_FILENO, vm->sigmsg, vm->siglen) != + (ssize_t) vm->siglen) + { + vm->status = BC_STATUS_ERROR_FATAL; + } errno = err; } - else vm.status = BC_STATUS_QUIT; + else + { +#if BC_ENABLE_EDITLINE + if (write(STDOUT_FILENO, "^C", 2) != (ssize_t) 2) + { + vm->status = BC_STATUS_ERROR_FATAL; + return; + } +#endif // BC_ENABLE_EDITLINE - assert(vm.jmp_bufs.len); + vm->status = BC_STATUS_QUIT; + } + +#if BC_ENABLE_LINE_LIB + // Readline and Editline need this to actually handle sigints correctly. + if (sig == SIGINT && bc_history_inlinelib) + { + bc_history_inlinelib = 0; + siglongjmp(bc_history_jmpbuf, 1); + } +#endif // BC_ENABLE_LINE_LIB + + assert(vm->jmp_bufs.len); // Only jump if signals are not locked. The jump will happen by whoever // unlocks signals. - if (!vm.sig_lock) BC_JMP; + if (!vm->sig_lock) BC_JMP; } /** * Sets up signal handling. */ -static void bc_vm_sigaction(void) { +static void +bc_vm_sigaction(void) +{ #ifndef _WIN32 struct sigaction sa; sigemptyset(&sa.sa_mask); + sa.sa_flags = BC_ENABLE_EDITLINE ? 0 : SA_NODEFER; + + // This mess is to silence a warning on Clang with regards to glibc's + // sigaction handler, which activates the warning here. +#if BC_CLANG +#pragma clang diagnostic ignored "-Wdisabled-macro-expansion" +#endif // BC_CLANG sa.sa_handler = bc_vm_sig; - sa.sa_flags = SA_NODEFER; +#if BC_CLANG +#pragma clang diagnostic warning "-Wdisabled-macro-expansion" +#endif // BC_CLANG sigaction(SIGTERM, &sa, NULL); sigaction(SIGQUIT, &sa, NULL); sigaction(SIGINT, &sa, NULL); +#if BC_ENABLE_EDITLINE + // Editline needs this to resize the terminal. + if (BC_TTY) sigaction(SIGWINCH, &sa, NULL); +#endif // BC_ENABLE_EDITLINE + #if BC_ENABLE_HISTORY if (BC_TTY) sigaction(SIGHUP, &sa, NULL); #endif // BC_ENABLE_HISTORY @@ -160,37 +247,37 @@ static void bc_vm_sigaction(void) { #endif // _WIN32 } -void bc_vm_info(const char* const help) { - +void +bc_vm_info(const char* const help) +{ BC_SIG_ASSERT_LOCKED; // Print the banner. - bc_file_puts(&vm.fout, bc_flush_none, vm.name); - bc_file_putchar(&vm.fout, bc_flush_none, ' '); - bc_file_puts(&vm.fout, bc_flush_none, BC_VERSION); - bc_file_putchar(&vm.fout, bc_flush_none, '\n'); - bc_file_puts(&vm.fout, bc_flush_none, bc_copyright); + bc_file_printf(&vm->fout, "%s %s\n%s", vm->name, BC_VERSION, bc_copyright); // Print the help. - if (help) { - - bc_file_putchar(&vm.fout, bc_flush_none, '\n'); + if (help != NULL) + { + bc_file_putchar(&vm->fout, bc_flush_none, '\n'); #if BC_ENABLED - if (BC_IS_BC) { - + if (BC_IS_BC) + { const char* const banner = BC_DEFAULT_BANNER ? "to" : "to not"; const char* const sigint = BC_DEFAULT_SIGINT_RESET ? "to reset" : - "to exit"; + "to exit"; const char* const tty = BC_DEFAULT_TTY_MODE ? "enabled" : - "disabled"; + "disabled"; const char* const prompt = BC_DEFAULT_PROMPT ? "enabled" : - "disabled"; + "disabled"; const char* const expr = BC_DEFAULT_EXPR_EXIT ? "to exit" : - "to not exit"; + "to not exit"; + const char* const clamp = BC_DEFAULT_DIGIT_CLAMP ? "to clamp" : + "to not clamp"; - bc_file_printf(&vm.fout, help, vm.name, vm.name, BC_VERSION, - BC_BUILD_TYPE, banner, sigint, tty, prompt, expr); + bc_file_printf(&vm->fout, help, vm->name, vm->name, BC_VERSION, + BC_BUILD_TYPE, banner, sigint, tty, prompt, expr, + clamp); } #endif // BC_ENABLED @@ -198,77 +285,96 @@ void bc_vm_info(const char* const help) { if (BC_IS_DC) { const char* const sigint = DC_DEFAULT_SIGINT_RESET ? "to reset" : - "to exit"; + "to exit"; const char* const tty = DC_DEFAULT_TTY_MODE ? "enabled" : - "disabled"; + "disabled"; const char* const prompt = DC_DEFAULT_PROMPT ? "enabled" : - "disabled"; + "disabled"; const char* const expr = DC_DEFAULT_EXPR_EXIT ? "to exit" : - "to not exit"; + "to not exit"; + const char* const clamp = DC_DEFAULT_DIGIT_CLAMP ? "to clamp" : + "to not clamp"; - bc_file_printf(&vm.fout, help, vm.name, vm.name, BC_VERSION, - BC_BUILD_TYPE, sigint, tty, prompt, expr); + bc_file_printf(&vm->fout, help, vm->name, vm->name, BC_VERSION, + BC_BUILD_TYPE, sigint, tty, prompt, expr, clamp); } #endif // DC_ENABLED } // Flush. - bc_file_flush(&vm.fout, bc_flush_none); + bc_file_flush(&vm->fout, bc_flush_none); } #endif // !BC_ENABLE_LIBRARY #if !BC_ENABLE_LIBRARY && !BC_ENABLE_MEMCHECK BC_NORETURN #endif // !BC_ENABLE_LIBRARY && !BC_ENABLE_MEMCHECK -void bc_vm_fatalError(BcErr e) { +void +bc_vm_fatalError(BcErr e) +{ bc_err(e); #if !BC_ENABLE_LIBRARY && !BC_ENABLE_MEMCHECK BC_UNREACHABLE +#if !BC_CLANG abort(); +#endif // !BC_CLANG #endif // !BC_ENABLE_LIBRARY && !BC_ENABLE_MEMCHECK } #if BC_ENABLE_LIBRARY -void bc_vm_handleError(BcErr e) { +BC_NORETURN void +bc_vm_handleError(BcErr e) +{ +#if BC_ENABLE_LIBRARY + BcVm* vm = bcl_getspecific(); +#endif // BC_ENABLE_LIBRARY assert(e < BC_ERR_NELEMS); - assert(!vm.sig_pop); + assert(!vm->sig_pop); BC_SIG_LOCK; // If we have a normal error... - if (e <= BC_ERR_MATH_DIVIDE_BY_ZERO) { - + if (e <= BC_ERR_MATH_DIVIDE_BY_ZERO) + { // Set the error. - vm.err = (BclError) (e - BC_ERR_MATH_NEGATIVE + - BCL_ERROR_MATH_NEGATIVE); + vm->err = (BclError) (e - BC_ERR_MATH_NEGATIVE + + BCL_ERROR_MATH_NEGATIVE); } // Abort if we should. - else if (vm.abrt) abort(); - else if (e == BC_ERR_FATAL_ALLOC_ERR) vm.err = BCL_ERROR_FATAL_ALLOC_ERR; - else vm.err = BCL_ERROR_FATAL_UNKNOWN_ERR; + else if (vm->abrt) abort(); + else if (e == BC_ERR_FATAL_ALLOC_ERR) vm->err = BCL_ERROR_FATAL_ALLOC_ERR; + else vm->err = BCL_ERROR_FATAL_UNKNOWN_ERR; BC_JMP; } #else // BC_ENABLE_LIBRARY -void bc_vm_handleError(BcErr e, size_t line, ...) { - +#if BC_DEBUG +void +bc_vm_handleError(BcErr e, const char* file, int fline, size_t line, ...) +#else // BC_DEBUG +void +bc_vm_handleError(BcErr e, size_t line, ...) +#endif // BC_DEBUG +{ BcStatus s; va_list args; uchar id = bc_err_ids[e]; - const char* err_type = vm.err_ids[id]; + const char* err_type = vm->err_ids[id]; sig_atomic_t lock; assert(e < BC_ERR_NELEMS); - assert(!vm.sig_pop); + assert(!vm->sig_pop); #if BC_ENABLED // Figure out if the POSIX error should be an error, a warning, or nothing. - if (!BC_S && e >= BC_ERR_POSIX_START) { - if (BC_W) { + if (!BC_S && e >= BC_ERR_POSIX_START) + { + if (BC_W) + { // Make sure to not return an error. id = UCHAR_MAX; - err_type = vm.err_ids[BC_ERR_IDX_WARN]; + err_type = vm->err_ids[BC_ERR_IDX_WARN]; } else return; } @@ -277,78 +383,79 @@ void bc_vm_handleError(BcErr e, size_t line, ...) { BC_SIG_TRYLOCK(lock); // Make sure all of stdout is written first. - s = bc_file_flushErr(&vm.fout, bc_flush_err); + s = bc_file_flushErr(&vm->fout, bc_flush_err); // Just jump out if the flush failed; there's nothing we can do. - if (BC_ERR(s == BC_STATUS_ERROR_FATAL)) { - vm.status = (sig_atomic_t) s; + if (BC_ERR(s == BC_STATUS_ERROR_FATAL)) + { + vm->status = (sig_atomic_t) s; BC_JMP; } // Print the error message. va_start(args, line); - bc_file_putchar(&vm.ferr, bc_flush_none, '\n'); - bc_file_puts(&vm.ferr, bc_flush_none, err_type); - bc_file_putchar(&vm.ferr, bc_flush_none, ' '); - bc_file_vprintf(&vm.ferr, vm.err_msgs[e], args); + bc_file_putchar(&vm->ferr, bc_flush_none, '\n'); + bc_file_puts(&vm->ferr, bc_flush_none, err_type); + bc_file_putchar(&vm->ferr, bc_flush_none, ' '); + bc_file_vprintf(&vm->ferr, vm->err_msgs[e], args); va_end(args); // Print the extra information if we have it. - if (BC_NO_ERR(vm.file != NULL)) { - + if (BC_NO_ERR(vm->file != NULL)) + { // This is the condition for parsing vs runtime. // If line is not 0, it is parsing. - if (line) { - bc_file_puts(&vm.ferr, bc_flush_none, "\n "); - bc_file_puts(&vm.ferr, bc_flush_none, vm.file); - bc_file_printf(&vm.ferr, bc_err_line, line); + if (line) + { + bc_file_puts(&vm->ferr, bc_flush_none, "\n "); + bc_file_puts(&vm->ferr, bc_flush_none, vm->file); + bc_file_printf(&vm->ferr, ":%zu\n", line); } - else { - - BcInstPtr *ip = bc_vec_item_rev(&vm.prog.stack, 0); - BcFunc *f = bc_vec_item(&vm.prog.fns, ip->func); - - bc_file_puts(&vm.ferr, bc_flush_none, "\n "); - bc_file_puts(&vm.ferr, bc_flush_none, vm.func_header); - bc_file_putchar(&vm.ferr, bc_flush_none, ' '); - bc_file_puts(&vm.ferr, bc_flush_none, f->name); - -#if BC_ENABLED - if (BC_IS_BC && ip->func != BC_PROG_MAIN && - ip->func != BC_PROG_READ) - { - bc_file_puts(&vm.ferr, bc_flush_none, "()"); - } -#endif // BC_ENABLED + else + { + // Print a stack trace. + bc_file_putchar(&vm->ferr, bc_flush_none, '\n'); + bc_program_printStackTrace(&vm->prog); } } + else + { + bc_file_putchar(&vm->ferr, bc_flush_none, '\n'); + } - bc_file_puts(&vm.ferr, bc_flush_none, "\n\n"); +#if BC_DEBUG + bc_file_printf(&vm->ferr, "\n %s:%d\n", file, fline); +#endif // BC_DEBUG - s = bc_file_flushErr(&vm.ferr, bc_flush_err); + bc_file_puts(&vm->ferr, bc_flush_none, "\n"); + + s = bc_file_flushErr(&vm->ferr, bc_flush_err); #if !BC_ENABLE_MEMCHECK // Because this function is called by a BC_NORETURN function when fatal // errors happen, we need to make sure to exit on fatal errors. This will // be faster anyway. This function *cannot jump when a fatal error occurs!* if (BC_ERR(id == BC_ERR_IDX_FATAL || s == BC_STATUS_ERROR_FATAL)) + { exit(bc_vm_atexit((int) BC_STATUS_ERROR_FATAL)); + } #else // !BC_ENABLE_MEMCHECK - if (BC_ERR(s == BC_STATUS_ERROR_FATAL)) vm.status = (sig_atomic_t) s; + if (BC_ERR(s == BC_STATUS_ERROR_FATAL)) vm->status = (sig_atomic_t) s; else #endif // !BC_ENABLE_MEMCHECK { - vm.status = (sig_atomic_t) (uchar) (id + 1); + vm->status = (sig_atomic_t) (uchar) (id + 1); } // Only jump if there is an error. - if (BC_ERR(vm.status)) BC_JMP; + if (BC_ERR(vm->status)) BC_JMP; BC_SIG_TRYUNLOCK(lock); } -char* bc_vm_getenv(const char* var) { - +char* +bc_vm_getenv(const char* var) +{ char* ret; #ifndef _WIN32 @@ -360,7 +467,9 @@ char* bc_vm_getenv(const char* var) { return ret; } -void bc_vm_getenvFree(char* val) { +void +bc_vm_getenvFree(char* val) +{ BC_UNUSED(val); #ifdef _WIN32 free(val); @@ -373,21 +482,22 @@ void bc_vm_getenvFree(char* val) { * @param def The default. * @param flag The flag to set. */ -static void bc_vm_setenvFlag(const char* const var, int def, uint16_t flag) { - +static void +bc_vm_setenvFlag(const char* const var, int def, uint16_t flag) +{ // Get the value. char* val = bc_vm_getenv(var); // If there is no value... - if (val == NULL) { - + if (val == NULL) + { // Set the default. - if (def) vm.flags |= flag; - else vm.flags &= ~(flag); + if (def) vm->flags |= flag; + else vm->flags &= ~(flag); } // Parse the value. - else if (strtoul(val, NULL, 0)) vm.flags |= flag; - else vm.flags &= ~(flag); + else if (strtoul(val, NULL, 0)) vm->flags |= flag; + else vm->flags &= ~(flag); bc_vm_getenvFree(val); } @@ -395,9 +505,17 @@ static void bc_vm_setenvFlag(const char* const var, int def, uint16_t flag) { /** * Parses the arguments in {B,D]C_ENV_ARGS. * @param env_args_name The environment variable to use. + * @param scale A pointer to return the scale that the arguments set, + * if any. + * @param ibase A pointer to return the ibase that the arguments set, + * if any. + * @param obase A pointer to return the obase that the arguments set, + * if any. */ -static void bc_vm_envArgs(const char* const env_args_name) { - +static void +bc_vm_envArgs(const char* const env_args_name, BcBigDig* scale, BcBigDig* ibase, + BcBigDig* obase) +{ char *env_args = bc_vm_getenv(env_args_name), *buf, *start; char instr = '\0'; @@ -405,11 +523,11 @@ static void bc_vm_envArgs(const char* const env_args_name) { if (env_args == NULL) return; - // Windows already allocates, so we don't need to. + // Windows already allocates, so we don't need to. #ifndef _WIN32 - start = buf = vm.env_args_buffer = bc_vm_strdup(env_args); + start = buf = vm->env_args_buffer = bc_vm_strdup(env_args); #else // _WIN32 - start = buf = vm.env_args_buffer = env_args; + start = buf = vm->env_args_buffer = env_args; #endif // _WIN32 assert(buf != NULL); @@ -417,24 +535,25 @@ static void bc_vm_envArgs(const char* const env_args_name) { // Create two buffers for parsing. These need to stay throughout the entire // execution of bc, unfortunately, because of filenames that might be in // there. - bc_vec_init(&vm.env_args, sizeof(char*), BC_DTOR_NONE); - bc_vec_push(&vm.env_args, &env_args_name); + bc_vec_init(&vm->env_args, sizeof(char*), BC_DTOR_NONE); + bc_vec_push(&vm->env_args, &env_args_name); // While we haven't reached the end of the args... - while (*buf) { - + while (*buf) + { // If we don't have whitespace... - if (!isspace(*buf)) { - + if (!isspace(*buf)) + { // If we have the start of a string... - if (*buf == '"' || *buf == '\'') { - + if (*buf == '"' || *buf == '\'') + { // Set stuff appropriately. instr = *buf; buf += 1; // Check for the empty string. - if (*buf == instr) { + if (*buf == instr) + { instr = '\0'; buf += 1; continue; @@ -442,18 +561,18 @@ static void bc_vm_envArgs(const char* const env_args_name) { } // Push the pointer to the args buffer. - bc_vec_push(&vm.env_args, &buf); + bc_vec_push(&vm->env_args, &buf); // Parse the string. - while (*buf && ((!instr && !isspace(*buf)) || - (instr && *buf != instr))) + while (*buf && + ((!instr && !isspace(*buf)) || (instr && *buf != instr))) { buf += 1; } // If we did find the end of the string... - if (*buf) { - + if (*buf) + { if (instr) instr = '\0'; // Reset stuff. @@ -469,10 +588,11 @@ static void bc_vm_envArgs(const char* const env_args_name) { // Make sure to push a NULL pointer at the end. buf = NULL; - bc_vec_push(&vm.env_args, &buf); + bc_vec_push(&vm->env_args, &buf); // Parse the arguments. - bc_args((int) vm.env_args.len - 1, bc_vec_item(&vm.env_args, 0), false); + bc_args((int) vm->env_args.len - 1, bc_vec_item(&vm->env_args, 0), false, + scale, ibase, obase); } /** @@ -480,9 +600,10 @@ static void bc_vm_envArgs(const char* const env_args_name) { * @param var The environment variable to pull it from. * @return The line length. */ -static size_t bc_vm_envLen(const char *var) { - - char *lenv = bc_vm_getenv(var); +static size_t +bc_vm_envLen(const char* var) +{ + char* lenv = bc_vm_getenv(var); size_t i, len = BC_NUM_PRINT_WIDTH; int num; @@ -492,14 +613,21 @@ static size_t bc_vm_envLen(const char *var) { len = strlen(lenv); // Figure out if it's a number. - for (num = 1, i = 0; num && i < len; ++i) num = isdigit(lenv[i]); + for (num = 1, i = 0; num && i < len; ++i) + { + num = isdigit(lenv[i]); + } // If it is a number... - if (num) { - + if (num) + { // Parse it and clamp it if needed. - len = (size_t) atoi(lenv) - 1; - if (len == 1 || len >= UINT16_MAX) len = BC_NUM_PRINT_WIDTH; + len = (size_t) strtol(lenv, NULL, 10); + if (len != 0) + { + len -= 1; + if (len < 2 || len >= UINT16_MAX) len = BC_NUM_PRINT_WIDTH; + } } // Set the default. else len = BC_NUM_PRINT_WIDTH; @@ -510,113 +638,179 @@ static size_t bc_vm_envLen(const char *var) { } #endif // BC_ENABLE_LIBRARY -void bc_vm_shutdown(void) { - +void +bc_vm_shutdown(void) +{ BC_SIG_ASSERT_LOCKED; #if BC_ENABLE_NLS - if (vm.catalog != BC_VM_INVALID_CATALOG) catclose(vm.catalog); + if (vm->catalog != BC_VM_INVALID_CATALOG) catclose(vm->catalog); #endif // BC_ENABLE_NLS +#if !BC_ENABLE_LIBRARY #if BC_ENABLE_HISTORY // This must always run to ensure that the terminal is back to normal, i.e., - // has raw mode disabled. - if (BC_TTY) bc_history_free(&vm.history); + // has raw mode disabled. But we should only do it if we did not have a bad + // terminal because history was not initialized if it is a bad terminal. + if (BC_TTY && !vm->history.badTerm) bc_history_free(&vm->history); #endif // BC_ENABLE_HISTORY +#endif // !BC_ENABLE_LIBRARY -#ifndef NDEBUG +#if BC_DEBUG #if !BC_ENABLE_LIBRARY - bc_vec_free(&vm.env_args); - free(vm.env_args_buffer); - bc_vec_free(&vm.files); - bc_vec_free(&vm.exprs); - - if (BC_PARSE_IS_INITED(&vm.read_prs, &vm.prog)) { - bc_vec_free(&vm.read_buf); - bc_parse_free(&vm.read_prs); + bc_vec_free(&vm->env_args); + free(vm->env_args_buffer); + bc_vec_free(&vm->files); + bc_vec_free(&vm->exprs); + + if (BC_PARSE_IS_INITED(&vm->read_prs, &vm->prog)) + { + bc_vec_free(&vm->read_buf); + bc_parse_free(&vm->read_prs); } - bc_parse_free(&vm.prs); - bc_program_free(&vm.prog); + bc_parse_free(&vm->prs); + bc_program_free(&vm->prog); - bc_slabvec_free(&vm.other_slabs); - bc_slabvec_free(&vm.main_slabs); - bc_slabvec_free(&vm.main_const_slab); + bc_slabvec_free(&vm->slabs); #endif // !BC_ENABLE_LIBRARY bc_vm_freeTemps(); -#endif // NDEBUG +#endif // BC_DEBUG #if !BC_ENABLE_LIBRARY // We always want to flush. - bc_file_free(&vm.fout); - bc_file_free(&vm.ferr); + bc_file_free(&vm->fout); + bc_file_free(&vm->ferr); #endif // !BC_ENABLE_LIBRARY } -void bc_vm_addTemp(BcDig *num) { +void +bc_vm_addTemp(BcDig* num) +{ +#if BC_ENABLE_LIBRARY + BcVm* vm = bcl_getspecific(); +#endif // BC_ENABLE_LIBRARY BC_SIG_ASSERT_LOCKED; // If we don't have room, just free. - if (vm.temps_len == BC_VM_MAX_TEMPS) free(num); - else { - + if (vm->temps_len == BC_VM_MAX_TEMPS) free(num); + else + { // Add to the buffer and length. - temps_buf[vm.temps_len] = num; - vm.temps_len += 1; + vm->temps_buf[vm->temps_len] = num; + vm->temps_len += 1; } } -BcDig* bc_vm_takeTemp(void) { +BcDig* +bc_vm_takeTemp(void) +{ +#if BC_ENABLE_LIBRARY + BcVm* vm = bcl_getspecific(); +#endif // BC_ENABLE_LIBRARY BC_SIG_ASSERT_LOCKED; - if (!vm.temps_len) return NULL; + if (!vm->temps_len) return NULL; - vm.temps_len -= 1; + vm->temps_len -= 1; - return temps_buf[vm.temps_len]; + return vm->temps_buf[vm->temps_len]; } -void bc_vm_freeTemps(void) { +BcDig* +bc_vm_getTemp(void) +{ +#if BC_ENABLE_LIBRARY + BcVm* vm = bcl_getspecific(); +#endif // BC_ENABLE_LIBRARY + + BC_SIG_ASSERT_LOCKED; + + if (!vm->temps_len) return NULL; + return vm->temps_buf[vm->temps_len - 1]; +} + +void +bc_vm_freeTemps(void) +{ size_t i; +#if BC_ENABLE_LIBRARY + BcVm* vm = bcl_getspecific(); +#endif // BC_ENABLE_LIBRARY BC_SIG_ASSERT_LOCKED; - if (!vm.temps_len) return; + if (!vm->temps_len) return; // Free them all... - for (i = 0; i < vm.temps_len; ++i) free(temps_buf[i]); + for (i = 0; i < vm->temps_len; ++i) + { + free(vm->temps_buf[i]); + } + + vm->temps_len = 0; +} + +#if !BC_ENABLE_LIBRARY + +size_t +bc_vm_numDigits(size_t val) +{ + size_t digits = 0; + + do + { + digits += 1; + val /= 10; + } + while (val != 0); - vm.temps_len = 0; + return digits; } -inline size_t bc_vm_arraySize(size_t n, size_t size) { +#endif // !BC_ENABLE_LIBRARY + +inline size_t +bc_vm_arraySize(size_t n, size_t size) +{ size_t res = n * size; + if (BC_ERR(BC_VM_MUL_OVERFLOW(n, size, res))) + { bc_vm_fatalError(BC_ERR_FATAL_ALLOC_ERR); + } + return res; } -inline size_t bc_vm_growSize(size_t a, size_t b) { +inline size_t +bc_vm_growSize(size_t a, size_t b) +{ size_t res = a + b; + if (BC_ERR(res >= SIZE_MAX || res < a)) + { bc_vm_fatalError(BC_ERR_FATAL_ALLOC_ERR); + } + return res; } -void* bc_vm_malloc(size_t n) { - +void* +bc_vm_malloc(size_t n) +{ void* ptr; BC_SIG_ASSERT_LOCKED; ptr = malloc(n); - if (BC_ERR(ptr == NULL)) { - + if (BC_ERR(ptr == NULL)) + { bc_vm_freeTemps(); ptr = malloc(n); @@ -627,16 +821,17 @@ void* bc_vm_malloc(size_t n) { return ptr; } -void* bc_vm_realloc(void *ptr, size_t n) { - +void* +bc_vm_realloc(void* ptr, size_t n) +{ void* temp; BC_SIG_ASSERT_LOCKED; temp = realloc(ptr, n); - if (BC_ERR(temp == NULL)) { - + if (BC_ERR(temp == NULL)) + { bc_vm_freeTemps(); temp = realloc(ptr, n); @@ -647,16 +842,17 @@ void* bc_vm_realloc(void *ptr, size_t n) { return temp; } -char* bc_vm_strdup(const char *str) { - - char *s; +char* +bc_vm_strdup(const char* str) +{ + char* s; BC_SIG_ASSERT_LOCKED; s = strdup(str); - if (BC_ERR(s == NULL)) { - + if (BC_ERR(s == NULL)) + { bc_vm_freeTemps(); s = strdup(str); @@ -668,29 +864,37 @@ char* bc_vm_strdup(const char *str) { } #if !BC_ENABLE_LIBRARY -void bc_vm_printf(const char *fmt, ...) { - +void +bc_vm_printf(const char* fmt, ...) +{ va_list args; +#if BC_ENABLE_LIBRARY + BcVm* vm = bcl_getspecific(); +#else // BC_ENABLE_LIBRARY sig_atomic_t lock; +#endif // BC_ENABLE_LIBRARY BC_SIG_TRYLOCK(lock); va_start(args, fmt); - bc_file_vprintf(&vm.fout, fmt, args); + bc_file_vprintf(&vm->fout, fmt, args); va_end(args); - vm.nchars = 0; + vm->nchars = 0; BC_SIG_TRYUNLOCK(lock); } #endif // !BC_ENABLE_LIBRARY -void bc_vm_putchar(int c, BcFlushType type) { +void +bc_vm_putchar(int c, BcFlushType type) +{ #if BC_ENABLE_LIBRARY - bc_vec_pushByte(&vm.out, (uchar) c); + BcVm* vm = bcl_getspecific(); + bc_vec_pushByte(&vm->out, (uchar) c); #else // BC_ENABLE_LIBRARY - bc_file_putchar(&vm.fout, type, (uchar) c); - vm.nchars = (c == '\n' ? 0 : vm.nchars + 1); + bc_file_putchar(&vm->fout, type, (uchar) c); + vm->nchars = (c == '\n' ? 0 : vm->nchars + 1); #endif // BC_ENABLE_LIBRARY } @@ -704,14 +908,18 @@ void bc_vm_putchar(int c, BcFlushType type) { * just in case. * @param msg The message to print. */ -BC_NORETURN static void bc_abortm(const char* msg) { - bc_file_puts(&vm.ferr, bc_flush_none, msg); - bc_file_puts(&vm.ferr, bc_flush_none, "; this is a bug"); - bc_file_flush(&vm.ferr, bc_flush_none); +BC_NORETURN static void +bc_abortm(const char* msg) +{ + bc_file_puts(&vm->ferr, bc_flush_none, msg); + bc_file_puts(&vm->ferr, bc_flush_none, "; this is a bug"); + bc_file_flush(&vm->ferr, bc_flush_none); abort(); } -void bc_pledge(const char *promises, const char* execpromises) { +void +bc_pledge(const char* promises, const char* execpromises) +{ int r = pledge(promises, execpromises); if (r) bc_abortm("pledge() failed"); } @@ -723,21 +931,28 @@ void bc_pledge(const char *promises, const char* execpromises) { * @param path The path. * @param permissions The permissions for the path. */ -static void bc_unveil(const char *path, const char *permissions) { +static void +bc_unveil(const char* path, const char* permissions) +{ int r = unveil(path, permissions); if (r) bc_abortm("unveil() failed"); } + #endif // BC_ENABLE_EXTRA_MATH #else // __OpenBSD__ -void bc_pledge(const char *promises, const char *execpromises) { +void +bc_pledge(const char* promises, const char* execpromises) +{ BC_UNUSED(promises); BC_UNUSED(execpromises); } #if BC_ENABLE_EXTRA_MATH -static void bc_unveil(const char *path, const char *permissions) { +static void +bc_unveil(const char* path, const char* permissions) +{ BC_UNUSED(path); BC_UNUSED(permissions); } @@ -750,36 +965,38 @@ static void bc_unveil(const char *path, const char *permissions) { * done executing a line of stdin. This is to prevent memory usage growing * without bound. This is an idea from busybox. */ -static void bc_vm_clean(void) { - - BcVec *fns = &vm.prog.fns; - BcFunc *f = bc_vec_item(fns, BC_PROG_MAIN); - BcInstPtr *ip = bc_vec_item(&vm.prog.stack, 0); - bool good = ((vm.status && vm.status != BC_STATUS_QUIT) || vm.sig); +static void +bc_vm_clean(void) +{ + BcVec* fns = &vm->prog.fns; + BcFunc* f = bc_vec_item(fns, BC_PROG_MAIN); + BcInstPtr* ip = bc_vec_item(&vm->prog.stack, 0); + bool good = ((vm->status && vm->status != BC_STATUS_QUIT) || vm->sig != 0); BC_SIG_ASSERT_LOCKED; // If all is good, go ahead and reset. - if (good) bc_program_reset(&vm.prog); + if (good) bc_program_reset(&vm->prog); #if BC_ENABLED // bc has this extra condition. If it not satisfied, it is in the middle of // a parse. - if (good && BC_IS_BC) good = !BC_PARSE_NO_EXEC(&vm.prs); + if (good && BC_IS_BC) good = !BC_PARSE_NO_EXEC(&vm->prs); #endif // BC_ENABLED #if DC_ENABLED // For dc, it is safe only when all of the results on the results stack are // safe, which means that they are temporaries or other things that don't // need strings or constants. - if (BC_IS_DC) { - + if (BC_IS_DC) + { size_t i; good = true; - for (i = 0; good && i < vm.prog.results.len; ++i) { - BcResult *r = (BcResult*) bc_vec_item(&vm.prog.results, i); + for (i = 0; good && i < vm->prog.results.len; ++i) + { + BcResult* r = (BcResult*) bc_vec_item(&vm->prog.results, i); good = BC_VM_SAFE_RESULT(r); } } @@ -787,31 +1004,19 @@ static void bc_vm_clean(void) { // If this condition is true, we can get rid of strings, // constants, and code. - if (good && vm.prog.stack.len == 1 && ip->idx == f->code.len) { + if (good && vm->prog.stack.len == 1 && ip->idx == f->code.len) + { + // XXX: Nothing can be popped in dc. Deal with it. #if BC_ENABLED - if (BC_IS_BC) { - + if (BC_IS_BC) + { + // XXX: you cannot delete strings, functions, or constants in bc. + // Deal with it. bc_vec_popAll(&f->labels); - bc_vec_popAll(&f->strs); - bc_vec_popAll(&f->consts); - - // I can't clear out the other_slabs because it has functions, - // consts, strings, vars, and arrays. It has strings from *other* - // functions, specifically. - bc_slabvec_clear(&vm.main_const_slab); - bc_slabvec_clear(&vm.main_slabs); } #endif // BC_ENABLED -#if DC_ENABLED - // Note to self: you cannot delete strings and functions. Deal with it. - if (BC_IS_DC) { - bc_vec_popAll(vm.prog.consts); - bc_slabvec_clear(&vm.main_const_slab); - } -#endif // DC_ENABLED - bc_vec_popAll(&f->code); ip->idx = 0; @@ -820,38 +1025,31 @@ static void bc_vm_clean(void) { /** * Process a bunch of text. - * @param text The text to process. - * @param is_stdin True if the text came from stdin, false otherwise. + * @param text The text to process. + * @param mode The mode to process in. */ -static void bc_vm_process(const char *text, bool is_stdin) { - +static void +bc_vm_process(const char* text, BcMode mode) +{ // Set up the parser. - bc_parse_text(&vm.prs, text, is_stdin); - - do { + bc_parse_text(&vm->prs, text, mode); + while (vm->prs.l.t != BC_LEX_EOF) + { + // Parsing requires a signal lock. We also don't parse everything; we + // want to execute as soon as possible for *everything*. BC_SIG_LOCK; - -#if BC_ENABLED - // If the first token is the keyword define, then we need to do this - // specially because bc thinks it may not be able to parse. - if (vm.prs.l.t == BC_LEX_KW_DEFINE) vm.parse(&vm.prs); -#endif // BC_ENABLED - - // Parse it all. - while (BC_PARSE_CAN_PARSE(vm.prs)) vm.parse(&vm.prs); - + vm->parse(&vm->prs); BC_SIG_UNLOCK; // Execute if possible. - if(BC_IS_DC || !BC_PARSE_NO_EXEC(&vm.prs)) bc_program_exec(&vm.prog); + if (BC_IS_DC || !BC_PARSE_NO_EXEC(&vm->prs)) bc_program_exec(&vm->prog); - assert(BC_IS_DC || vm.prog.results.len == 0); + assert(BC_IS_DC || vm->prog.results.len == 0); // Flush in interactive mode. - if (BC_I) bc_file_flush(&vm.fout, bc_flush_save); - - } while (vm.prs.l.t != BC_LEX_EOF); + if (BC_I) bc_file_flush(&vm->fout, bc_flush_save); + } } #if BC_ENABLED @@ -862,24 +1060,33 @@ static void bc_vm_process(const char *text, bool is_stdin) { * it cannot parse any further. But if we reach the end of a file or stdin has * no more data, we know we can add an empty else clause. */ -static void bc_vm_endif(void) { - bc_parse_endif(&vm.prs); - bc_program_exec(&vm.prog); +static void +bc_vm_endif(void) +{ + bc_parse_endif(&vm->prs); + bc_program_exec(&vm->prog); } + #endif // BC_ENABLED /** * Processes a file. * @param file The filename. */ -static void bc_vm_file(const char *file) { +static void +bc_vm_file(const char* file) +{ + char* data = NULL; +#if BC_ENABLE_LIBRARY + BcVm* vm = bcl_getspecific(); +#endif // BC_ENABLE_LIBRARY - char *data = NULL; + assert(!vm->sig_pop); - assert(!vm.sig_pop); + vm->mode = BC_MODE_FILE; // Set up the lexer. - bc_lex_file(&vm.prs.l, file); + bc_lex_file(&vm->prs.l, file); BC_SIG_LOCK; @@ -888,12 +1095,12 @@ static void bc_vm_file(const char *file) { assert(data != NULL); - BC_SETJMP_LOCKED(err); + BC_SETJMP_LOCKED(vm, err); BC_SIG_UNLOCK; // Process it. - bc_vm_process(data, false); + bc_vm_process(data, BC_MODE_FILE); #if BC_ENABLED // Make sure to end any open if statements. @@ -901,6 +1108,7 @@ static void bc_vm_file(const char *file) { #endif // BC_ENABLED err: + BC_SIG_MAYLOCK; // Cleanup. @@ -909,38 +1117,41 @@ err: // bc_program_reset(), called by bc_vm_clean(), resets the status. // We want it to clear the sig_pop variable in case it was set. - if (vm.status == (sig_atomic_t) BC_STATUS_SUCCESS) BC_LONGJMP_STOP; + if (vm->status == (sig_atomic_t) BC_STATUS_SUCCESS) BC_LONGJMP_STOP; - BC_LONGJMP_CONT; + BC_LONGJMP_CONT(vm); } -bool bc_vm_readLine(bool clear) { - +bool +bc_vm_readLine(bool clear) +{ BcStatus s; bool good; BC_SIG_ASSERT_NOT_LOCKED; // Clear the buffer if desired. - if (clear) bc_vec_empty(&vm.buffer); + if (clear) bc_vec_empty(&vm->buffer); // Empty the line buffer. - bc_vec_empty(&vm.line_buf); + bc_vec_empty(&vm->line_buf); - if (vm.eof) return false; + if (vm->eof) return false; - do { + do + { // bc_read_line() must always return either BC_STATUS_SUCCESS or // BC_STATUS_EOF. Everything else, it and whatever it calls, must jump // out instead. - s = bc_read_line(&vm.line_buf, ">>> "); - vm.eof = (s == BC_STATUS_EOF); - } while (!(s) && !vm.eof && vm.line_buf.len < 1); + s = bc_read_line(&vm->line_buf, ">>> "); + vm->eof = (s == BC_STATUS_EOF); + } + while (s == BC_STATUS_SUCCESS && !vm->eof && vm->line_buf.len < 1); - good = (vm.line_buf.len > 1); + good = (vm->line_buf.len > 1); // Concat if we found something. - if (good) bc_vec_concat(&vm.buffer, vm.line_buf.v); + if (good) bc_vec_concat(&vm->buffer, vm->line_buf.v); return good; } @@ -948,25 +1159,33 @@ bool bc_vm_readLine(bool clear) { /** * Processes text from stdin. */ -static void bc_vm_stdin(void) { +static void +bc_vm_stdin(void) +{ + bool clear; - bool clear = true; +#if BC_ENABLE_LIBRARY + BcVm* vm = bcl_getspecific(); +#endif // BC_ENABLE_LIBRARY - vm.is_stdin = true; + clear = true; + vm->mode = BC_MODE_STDIN; // Set up the lexer. - bc_lex_file(&vm.prs.l, bc_program_stdin_name); - - // These are global so that the dc lexer can access them, but they are tied - // to this function, really. Well, this and bc_vm_readLine(). These are the - // reason that we have vm.is_stdin to tell the dc lexer if we are reading - // from stdin. Well, both lexers care. And the reason they care is so that - // if a comment or a string goes across multiple lines, the lexer can - // request more data from stdin until the comment or string is ended. + bc_lex_file(&vm->prs.l, bc_program_stdin_name); + + // These are global so that the lexers can access them, but they are + // allocated and freed in this function because they should only be used for + // stdin and expressions (they are used in bc_vm_exprs() as well). So they + // are tied to this function, really. Well, this and bc_vm_readLine(). These + // are the reasons that we have vm->is_stdin to tell the lexers if we are + // reading from stdin. Well, both lexers care. And the reason they care is + // so that if a comment or a string goes across multiple lines, the lexer + // can request more data from stdin until the comment or string is ended. BC_SIG_LOCK; - bc_vec_init(&vm.buffer, sizeof(uchar), BC_DTOR_NONE); - bc_vec_init(&vm.line_buf, sizeof(uchar), BC_DTOR_NONE); - BC_SETJMP_LOCKED(err); + bc_vec_init(&vm->buffer, sizeof(uchar), BC_DTOR_NONE); + bc_vec_init(&vm->line_buf, sizeof(uchar), BC_DTOR_NONE); + BC_SETJMP_LOCKED(vm, err); BC_SIG_UNLOCK; // This label exists because errors can cause jumps to end up at the err label @@ -975,10 +1194,10 @@ static void bc_vm_stdin(void) { restart: // While we still read data from stdin. - while (bc_vm_readLine(clear)) { - - size_t len = vm.buffer.len - 1; - const char *str = vm.buffer.v; + while (bc_vm_readLine(clear)) + { + size_t len = vm->buffer.len - 1; + const char* str = vm->buffer.v; // We don't want to clear the buffer when the line ends with a backslash // because a backslash newline is special in bc. @@ -986,10 +1205,11 @@ restart: if (!clear) continue; // Process the data. - bc_vm_process(vm.buffer.v, true); + bc_vm_process(vm->buffer.v, BC_MODE_STDIN); - if (vm.eof) break; - else { + if (vm->eof) break; + else + { BC_SIG_LOCK; bc_vm_clean(); BC_SIG_UNLOCK; @@ -1002,36 +1222,122 @@ restart: #endif // BC_ENABLED err: + BC_SIG_MAYLOCK; // Cleanup. bc_vm_clean(); #if !BC_ENABLE_MEMCHECK - assert(vm.status != BC_STATUS_ERROR_FATAL); + assert(vm->status != BC_STATUS_ERROR_FATAL); - vm.status = vm.status == BC_STATUS_QUIT || !BC_I ? - vm.status : BC_STATUS_SUCCESS; + vm->status = vm->status == BC_STATUS_QUIT || !BC_I ? vm->status : + BC_STATUS_SUCCESS; #else // !BC_ENABLE_MEMCHECK - vm.status = vm.status == BC_STATUS_ERROR_FATAL || - vm.status == BC_STATUS_QUIT || !BC_I ? - vm.status : BC_STATUS_SUCCESS; + vm->status = vm->status == BC_STATUS_ERROR_FATAL || + vm->status == BC_STATUS_QUIT || !BC_I ? + vm->status : + BC_STATUS_SUCCESS; #endif // !BC_ENABLE_MEMCHECK - if (!vm.status && !vm.eof) { - bc_vec_empty(&vm.buffer); + if (!vm->status && !vm->eof) + { + bc_vec_empty(&vm->buffer); BC_LONGJMP_STOP; BC_SIG_UNLOCK; goto restart; } -#ifndef NDEBUG - // Since these are tied to this function, free them here. - bc_vec_free(&vm.line_buf); - bc_vec_free(&vm.buffer); -#endif // NDEBUG +#if BC_DEBUG + // Since these are tied to this function, free them here. We only free in + // debug mode because stdin is always the last thing read. + bc_vec_free(&vm->line_buf); + bc_vec_free(&vm->buffer); +#endif // BC_DEBUG + + BC_LONGJMP_CONT(vm); +} + +bool +bc_vm_readBuf(bool clear) +{ + size_t len = vm->exprs.len - 1; + bool more; + + BC_SIG_ASSERT_NOT_LOCKED; + + // Clear the buffer if desired. + if (clear) bc_vec_empty(&vm->buffer); + + // We want to pop the nul byte off because that's what bc_read_buf() + // expects. + bc_vec_pop(&vm->buffer); + + // Read one line of expressions. + more = bc_read_buf(&vm->buffer, vm->exprs.v, &len); + bc_vec_pushByte(&vm->buffer, '\0'); + + return more; +} + +static void +bc_vm_exprs(void) +{ + bool clear; + +#if BC_ENABLE_LIBRARY + BcVm* vm = bcl_getspecific(); +#endif // BC_ENABLE_LIBRARY + + clear = true; + vm->mode = BC_MODE_EXPRS; + + // Prepare the lexer. + bc_lex_file(&vm->prs.l, bc_program_exprs_name); + + // We initialize this so that the lexer can access it in the case that it + // needs more data for expressions, such as for a multiline string or + // comment. See the comment on the allocation of vm->buffer above in + // bc_vm_stdin() for more information. + BC_SIG_LOCK; + bc_vec_init(&vm->buffer, sizeof(uchar), BC_DTOR_NONE); + BC_SETJMP_LOCKED(vm, err); + BC_SIG_UNLOCK; + + while (bc_vm_readBuf(clear)) + { + size_t len = vm->buffer.len - 1; + const char* str = vm->buffer.v; + + // We don't want to clear the buffer when the line ends with a backslash + // because a backslash newline is special in bc. + clear = (len < 2 || str[len - 2] != '\\' || str[len - 1] != '\n'); + if (!clear) continue; + + // Process the data. + bc_vm_process(vm->buffer.v, BC_MODE_EXPRS); + } + + // If we were not supposed to clear, then we should process everything. This + // makes sure that errors get reported. + if (!clear) bc_vm_process(vm->buffer.v, BC_MODE_EXPRS); - BC_LONGJMP_CONT; +err: + + BC_SIG_MAYLOCK; + + // Cleanup. + bc_vm_clean(); + + // bc_program_reset(), called by bc_vm_clean(), resets the status. + // We want it to clear the sig_pop variable in case it was set. + if (vm->status == (sig_atomic_t) BC_STATUS_SUCCESS) BC_LONGJMP_STOP; + + // Since this is tied to this function, free it here. We always free it here + // because bc_vm_stdin() may or may not use it later. + bc_vec_free(&vm->buffer); + + BC_LONGJMP_CONT(vm); } #if BC_ENABLED @@ -1041,14 +1347,18 @@ err: * @param name The name of the library. * @param text The text of the source code. */ -static void bc_vm_load(const char *name, const char *text) { - - bc_lex_file(&vm.prs.l, name); - bc_parse_text(&vm.prs, text, false); +static void +bc_vm_load(const char* name, const char* text) +{ + bc_lex_file(&vm->prs.l, name); + bc_parse_text(&vm->prs, text, BC_MODE_FILE); BC_SIG_LOCK; - while (vm.prs.l.t != BC_LEX_EOF) vm.parse(&vm.prs); + while (vm->prs.l.t != BC_LEX_EOF) + { + vm->parse(&vm->prs); + } BC_SIG_UNLOCK; } @@ -1058,67 +1368,74 @@ static void bc_vm_load(const char *name, const char *text) { /** * Loads the default error messages. */ -static void bc_vm_defaultMsgs(void) { - +static void +bc_vm_defaultMsgs(void) +{ size_t i; - vm.func_header = bc_err_func_header; - // Load the error categories. for (i = 0; i < BC_ERR_IDX_NELEMS + BC_ENABLED; ++i) - vm.err_ids[i] = bc_errs[i]; + { + vm->err_ids[i] = bc_errs[i]; + } // Load the error messages. - for (i = 0; i < BC_ERR_NELEMS; ++i) vm.err_msgs[i] = bc_err_msgs[i]; + for (i = 0; i < BC_ERR_NELEMS; ++i) + { + vm->err_msgs[i] = bc_err_msgs[i]; + } } /** * Loads the error messages for the locale. If NLS is disabled, this just loads * the default messages. */ -static void bc_vm_gettext(void) { - +static void +bc_vm_gettext(void) +{ #if BC_ENABLE_NLS uchar id = 0; - int set = 1, msg = 1; + int set, msg = 1; size_t i; // If no locale, load the defaults. - if (vm.locale == NULL) { - vm.catalog = BC_VM_INVALID_CATALOG; + if (vm->locale == NULL) + { + vm->catalog = BC_VM_INVALID_CATALOG; bc_vm_defaultMsgs(); return; } - vm.catalog = catopen(BC_MAINEXEC, NL_CAT_LOCALE); + vm->catalog = catopen(BC_MAINEXEC, NL_CAT_LOCALE); // If no catalog, load the defaults. - if (vm.catalog == BC_VM_INVALID_CATALOG) { + if (vm->catalog == BC_VM_INVALID_CATALOG) + { bc_vm_defaultMsgs(); return; } - // Load the function header. - vm.func_header = catgets(vm.catalog, set, msg, bc_err_func_header); - // Load the error categories. - for (set += 1; msg <= BC_ERR_IDX_NELEMS + BC_ENABLED; ++msg) - vm.err_ids[msg - 1] = catgets(vm.catalog, set, msg, bc_errs[msg - 1]); + for (set = 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]; // Load the error messages. In order to understand this loop, you must know // the order of messages and categories in the enum and in the locale files. - for (set = id + 3, msg = 1; i < BC_ERR_NELEMS; ++i, ++msg) { - - if (id != bc_err_ids[i]) { + for (set = id + 2, msg = 1; i < BC_ERR_NELEMS; ++i, ++msg) + { + if (id != bc_err_ids[i]) + { msg = 1; id = bc_err_ids[i]; - set = id + 3; + set = id + 2; } - vm.err_msgs[i] = catgets(vm.catalog, set, msg, bc_err_msgs[i]); + vm->err_msgs[i] = catgets(vm->catalog, set, msg, bc_err_msgs[i]); } #else // BC_ENABLE_NLS bc_vm_defaultMsgs(); @@ -1130,18 +1447,20 @@ static void bc_vm_gettext(void) { * probably be combined with bc_vm_boot(), but I don't care enough. Really, this * function starts when execution of bc or dc source code starts. */ -static void bc_vm_exec(void) { - +static void +bc_vm_exec(void) +{ size_t i; +#if DC_ENABLED bool has_file = false; - BcVec buf; +#endif // DC_ENABLED #if BC_ENABLED // Load the math libraries. - if (BC_IS_BC && (vm.flags & BC_FLAG_L)) { - + if (BC_IS_BC && (vm->flags & BC_FLAG_L)) + { // Can't allow redefinitions in the builtin library. - vm.no_redefine = true; + vm->no_redefine = true; bc_vm_load(bc_lib_name, bc_lib); @@ -1150,64 +1469,32 @@ static void bc_vm_exec(void) { #endif // BC_ENABLE_EXTRA_MATH // Make sure to clear this. - vm.no_redefine = false; + vm->no_redefine = false; // Execute to ensure that all is hunky dory. Without this, scale can be // set improperly. - bc_program_exec(&vm.prog); + bc_program_exec(&vm->prog); } #endif // BC_ENABLED // If there are expressions to execute... - if (vm.exprs.len) { - - size_t len = vm.exprs.len - 1; - bool more; - - BC_SIG_LOCK; - - // Create this as a buffer for reading into. - bc_vec_init(&buf, sizeof(uchar), BC_DTOR_NONE); - -#ifndef NDEBUG - BC_SETJMP_LOCKED(err); -#endif // NDEBUG - - BC_SIG_UNLOCK; - - // Prepare the lexer. - bc_lex_file(&vm.prs.l, bc_program_exprs_name); - - // Process the expressions one at a time. - do { - - more = bc_read_buf(&buf, vm.exprs.v, &len); - bc_vec_pushByte(&buf, '\0'); - bc_vm_process(buf.v, false); - - bc_vec_popAll(&buf); - - } while (more); - - BC_SIG_LOCK; - - bc_vec_free(&buf); - -#ifndef NDEBUG - BC_UNSETJMP; -#endif // NDEBUG - - BC_SIG_UNLOCK; + if (vm->exprs.len) + { + // Process the expressions. + bc_vm_exprs(); // Sometimes, executing expressions means we need to quit. - if (!vm.no_exprs && vm.exit_exprs && BC_EXPR_EXIT) return; + if (!vm->no_exprs && vm->exit_exprs && BC_EXPR_EXIT) return; } // Process files. - for (i = 0; i < vm.files.len; ++i) { - char *path = *((char**) bc_vec_item(&vm.files, i)); + for (i = 0; i < vm->files.len; ++i) + { + char* path = *((char**) bc_vec_item(&vm->files, i)); if (!strcmp(path, "")) continue; +#if DC_ENABLED has_file = true; +#endif // DC_ENABLED bc_vm_file(path); } @@ -1222,7 +1509,7 @@ static void bc_vm_exec(void) { // We need to keep tty if history is enabled, and we need to keep rpath for // the times when we read from /dev/urandom. - if (BC_TTY && !vm.history.badTerm) bc_pledge(bc_pledge_end_history, NULL); + if (BC_TTY && !vm->history.badTerm) bc_pledge(bc_pledge_end_history, NULL); else #endif // BC_ENABLE_HISTORY { @@ -1238,29 +1525,26 @@ static void bc_vm_exec(void) { #endif // BC_ENABLE_AFL // Execute from stdin. bc always does. - if (BC_IS_BC || !has_file) bc_vm_stdin(); - -// These are all protected by ifndef NDEBUG because if these are needed, bc is -// going to exit anyway, and I see no reason to include this code in a release -// build when the OS is going to free all of the resources anyway. -#ifndef NDEBUG - return; - -err: - BC_SIG_MAYLOCK; - bc_vec_free(&buf); - BC_LONGJMP_CONT; -#endif // NDEBUG + if (BC_VM_RUN_STDIN(has_file)) bc_vm_stdin(); } -void bc_vm_boot(int argc, char *argv[]) { - +void +bc_vm_boot(int argc, char* argv[]) +{ int ttyin, ttyout, ttyerr; bool tty; - const char* const env_len = BC_IS_BC ? "BC_LINE_LENGTH" : "DC_LINE_LENGTH"; - const char* const env_args = BC_IS_BC ? "BC_ENV_ARGS" : "DC_ENV_ARGS"; - const char* const env_exit = BC_IS_BC ? "BC_EXPR_EXIT" : "DC_EXPR_EXIT"; - int env_exit_def = BC_IS_BC ? BC_DEFAULT_EXPR_EXIT : DC_DEFAULT_EXPR_EXIT; + const char* const env_len = BC_VM_LINE_LENGTH_STR; + const char* const env_args = BC_VM_ENV_ARGS_STR; + const char* const env_exit = BC_VM_EXPR_EXIT_STR; + const char* const env_clamp = BC_VM_DIGIT_CLAMP_STR; + int env_exit_def = BC_VM_EXPR_EXIT_DEF; + int env_clamp_def = BC_VM_DIGIT_CLAMP_DEF; + BcBigDig scale = BC_NUM_BIGDIG_MAX; + BcBigDig env_scale = BC_NUM_BIGDIG_MAX; + BcBigDig ibase = BC_NUM_BIGDIG_MAX; + BcBigDig env_ibase = BC_NUM_BIGDIG_MAX; + BcBigDig obase = BC_NUM_BIGDIG_MAX; + BcBigDig env_obase = BC_NUM_BIGDIG_MAX; // We need to know which of stdin, stdout, and stderr are tty's. ttyin = isatty(STDIN_FILENO); @@ -1268,9 +1552,9 @@ void bc_vm_boot(int argc, char *argv[]) { ttyerr = isatty(STDERR_FILENO); tty = (ttyin != 0 && ttyout != 0 && ttyerr != 0); - vm.flags |= ttyin ? BC_FLAG_TTYIN : 0; - vm.flags |= tty ? BC_FLAG_TTY : 0; - vm.flags |= ttyin && ttyout ? BC_FLAG_I : 0; + vm->flags |= ttyin ? BC_FLAG_TTYIN : 0; + vm->flags |= tty ? BC_FLAG_TTY : 0; + vm->flags |= ttyin && ttyout ? BC_FLAG_I : 0; // Set up signals. bc_vm_sigaction(); @@ -1280,57 +1564,68 @@ void bc_vm_boot(int argc, char *argv[]) { bc_vm_init(); // Explicitly set this in case NULL isn't all zeroes. - vm.file = NULL; + vm->file = NULL; // Set the error messages. bc_vm_gettext(); +#if BC_ENABLE_LINE_LIB + + // Initialize the output file buffers. + bc_file_init(&vm->ferr, stderr); + bc_file_init(&vm->fout, stdout); + + // Set the input buffer. + vm->buf = output_bufs; + +#else // BC_ENABLE_LINE_LIB + // Initialize the output file buffers. They each take portions of the global // buffer. stdout gets more because it will probably have more data. - bc_file_init(&vm.ferr, STDERR_FILENO, output_bufs + BC_VM_STDOUT_BUF_SIZE, + bc_file_init(&vm->ferr, STDERR_FILENO, output_bufs + BC_VM_STDOUT_BUF_SIZE, BC_VM_STDERR_BUF_SIZE); - bc_file_init(&vm.fout, STDOUT_FILENO, output_bufs, BC_VM_STDOUT_BUF_SIZE); + bc_file_init(&vm->fout, STDOUT_FILENO, output_bufs, BC_VM_STDOUT_BUF_SIZE); // Set the input buffer to the rest of the global buffer. - vm.buf = output_bufs + BC_VM_STDOUT_BUF_SIZE + BC_VM_STDERR_BUF_SIZE; + vm->buf = output_bufs + BC_VM_STDOUT_BUF_SIZE + BC_VM_STDERR_BUF_SIZE; +#endif // BC_ENABLE_LINE_LIB // Set the line length by environment variable. - vm.line_len = (uint16_t) bc_vm_envLen(env_len); + vm->line_len = (uint16_t) bc_vm_envLen(env_len); bc_vm_setenvFlag(env_exit, env_exit_def, BC_FLAG_EXPR_EXIT); + bc_vm_setenvFlag(env_clamp, env_clamp_def, BC_FLAG_DIGIT_CLAMP); // Clear the files and expressions vectors, just in case. This marks them as // *not* allocated. - bc_vec_clear(&vm.files); - bc_vec_clear(&vm.exprs); + bc_vec_clear(&vm->files); + bc_vec_clear(&vm->exprs); #if !BC_ENABLE_LIBRARY - // Initialize the slab vectors. - bc_slabvec_init(&vm.main_const_slab); - bc_slabvec_init(&vm.main_slabs); - bc_slabvec_init(&vm.other_slabs); + // Initialize the slab vector. + bc_slabvec_init(&vm->slabs); #endif // !BC_ENABLE_LIBRARY // Initialize the program and main parser. These have to be in this order // because the program has to be initialized first, since a pointer to it is // passed to the parser. - bc_program_init(&vm.prog); - bc_parse_init(&vm.prs, &vm.prog, BC_PROG_MAIN); + bc_program_init(&vm->prog); + bc_parse_init(&vm->prs, &vm->prog, BC_PROG_MAIN); // Set defaults. - vm.flags |= BC_TTY ? BC_FLAG_P | BC_FLAG_R : 0; - vm.flags |= BC_I ? BC_FLAG_Q : 0; + vm->flags |= BC_TTY ? BC_FLAG_P | BC_FLAG_R : 0; + vm->flags |= BC_I ? BC_FLAG_Q : 0; #if BC_ENABLED - if (BC_IS_BC) { - + if (BC_IS_BC) + { // bc checks this environment variable to see if it should run in // standard mode. char* var = bc_vm_getenv("POSIXLY_CORRECT"); - vm.flags |= BC_FLAG_S * (var != NULL); + vm->flags |= BC_FLAG_S * (var != NULL); bc_vm_getenvFree(var); // Set whether we print the banner or not. @@ -1339,12 +1634,12 @@ void bc_vm_boot(int argc, char *argv[]) { #endif // BC_ENABLED // Are we in TTY mode? - if (BC_TTY) { - - const char* const env_tty = BC_IS_BC ? "BC_TTY_MODE" : "DC_TTY_MODE"; - int env_tty_def = BC_IS_BC ? BC_DEFAULT_TTY_MODE : DC_DEFAULT_TTY_MODE; - const char* const env_prompt = BC_IS_BC ? "BC_PROMPT" : "DC_PROMPT"; - int env_prompt_def = BC_IS_BC ? BC_DEFAULT_PROMPT : DC_DEFAULT_PROMPT; + if (BC_TTY) + { + const char* const env_tty = BC_VM_TTY_MODE_STR; + int env_tty_def = BC_VM_TTY_MODE_DEF; + const char* const env_prompt = BC_VM_PROMPT_STR; + int env_prompt_def = BC_VM_PROMPT_DEF; // Set flags for TTY mode and prompt. bc_vm_setenvFlag(env_tty, env_tty_def, BC_FLAG_TTY); @@ -1352,21 +1647,52 @@ void bc_vm_boot(int argc, char *argv[]) { #if BC_ENABLE_HISTORY // If TTY mode is used, activate history. - if (BC_TTY) bc_history_init(&vm.history); + if (BC_TTY) bc_history_init(&vm->history); #endif // BC_ENABLE_HISTORY } // Process environment and command-line arguments. - bc_vm_envArgs(env_args); - bc_args(argc, argv, true); + bc_vm_envArgs(env_args, &env_scale, &env_ibase, &env_obase); + bc_args(argc, argv, true, &scale, &ibase, &obase); - // If we are in interactive mode... - if (BC_I) { + // This section is here because we don't want the math library to stomp on + // the user's given value for scale. And we don't want ibase affecting how + // the scale is interpreted. Also, it's sectioned off just for this comment. + { + BC_SIG_UNLOCK; + + scale = scale == BC_NUM_BIGDIG_MAX ? env_scale : scale; +#if BC_ENABLED + // Assign the library value only if it is used and no value was set. + scale = scale == BC_NUM_BIGDIG_MAX && BC_L ? 20 : scale; +#endif // BC_ENABLED + obase = obase == BC_NUM_BIGDIG_MAX ? env_obase : obase; + ibase = ibase == BC_NUM_BIGDIG_MAX ? env_ibase : ibase; + + if (scale != BC_NUM_BIGDIG_MAX) + { + bc_program_assignBuiltin(&vm->prog, true, false, scale); + } + + if (obase != BC_NUM_BIGDIG_MAX) + { + bc_program_assignBuiltin(&vm->prog, false, true, obase); + } - const char* const env_sigint = BC_IS_BC ? "BC_SIGINT_RESET" : - "DC_SIGINT_RESET"; - int env_sigint_def = BC_IS_BC ? BC_DEFAULT_SIGINT_RESET : - DC_DEFAULT_SIGINT_RESET; + // This is last to avoid it affecting the value of the others. + if (ibase != BC_NUM_BIGDIG_MAX) + { + bc_program_assignBuiltin(&vm->prog, false, false, ibase); + } + + BC_SIG_LOCK; + } + + // If we are in interactive mode... + if (BC_I) + { + const char* const env_sigint = BC_VM_SIGINT_RESET_STR; + int env_sigint_def = BC_VM_SIGINT_RESET_DEF; // Set whether we reset on SIGINT or not. bc_vm_setenvFlag(env_sigint, env_sigint_def, BC_FLAG_SIGINT); @@ -1374,14 +1700,15 @@ void bc_vm_boot(int argc, char *argv[]) { #if BC_ENABLED // Disable global stacks in POSIX mode. - if (BC_IS_POSIX) vm.flags &= ~(BC_FLAG_G); + if (BC_IS_POSIX) vm->flags &= ~(BC_FLAG_G); // Print the banner if allowed. We have to be in bc, in interactive mode, // and not be quieted by command-line option or environment variable. - if (BC_IS_BC && BC_I && (vm.flags & BC_FLAG_Q)) { + if (BC_IS_BC && BC_I && (vm->flags & BC_FLAG_Q)) + { bc_vm_info(NULL); - bc_file_putchar(&vm.fout, bc_flush_none, '\n'); - bc_file_flush(&vm.fout, bc_flush_none); + bc_file_putchar(&vm->fout, bc_flush_none, '\n'); + bc_file_flush(&vm->fout, bc_flush_none); } #endif // BC_ENABLED @@ -1392,36 +1719,43 @@ void bc_vm_boot(int argc, char *argv[]) { } #endif // !BC_ENABLE_LIBRARY -void bc_vm_init(void) { +void +bc_vm_init(void) +{ +#if BC_ENABLE_LIBRARY + BcVm* vm = bcl_getspecific(); +#endif // BC_ENABLE_LIBRARY BC_SIG_ASSERT_LOCKED; #if !BC_ENABLE_LIBRARY // Set up the constant zero. - bc_num_setup(&vm.zero, vm.zero_num, BC_VM_ONE_CAP); + bc_num_setup(&vm->zero, vm->zero_num, BC_VM_ONE_CAP); #endif // !BC_ENABLE_LIBRARY // Set up more constant BcNum's. - bc_num_setup(&vm.one, vm.one_num, BC_VM_ONE_CAP); - bc_num_one(&vm.one); + bc_num_setup(&vm->one, vm->one_num, BC_VM_ONE_CAP); + bc_num_one(&vm->one); // Set up more constant BcNum's. - memcpy(vm.max_num, bc_num_bigdigMax, + // NOLINTNEXTLINE + memcpy(vm->max_num, bc_num_bigdigMax, bc_num_bigdigMax_size * sizeof(BcDig)); - memcpy(vm.max2_num, bc_num_bigdigMax2, + // NOLINTNEXTLINE + memcpy(vm->max2_num, bc_num_bigdigMax2, bc_num_bigdigMax2_size * sizeof(BcDig)); - bc_num_setup(&vm.max, vm.max_num, BC_NUM_BIGDIG_LOG10); - bc_num_setup(&vm.max2, vm.max2_num, BC_NUM_BIGDIG_LOG10); - vm.max.len = bc_num_bigdigMax_size; - vm.max2.len = bc_num_bigdigMax2_size; + bc_num_setup(&vm->max, vm->max_num, BC_NUM_BIGDIG_LOG10); + bc_num_setup(&vm->max2, vm->max2_num, BC_NUM_BIGDIG_LOG10); + vm->max.len = bc_num_bigdigMax_size; + vm->max2.len = bc_num_bigdigMax2_size; // Set up the maxes for the globals. - vm.maxes[BC_PROG_GLOBALS_IBASE] = BC_NUM_MAX_POSIX_IBASE; - vm.maxes[BC_PROG_GLOBALS_OBASE] = BC_MAX_OBASE; - vm.maxes[BC_PROG_GLOBALS_SCALE] = BC_MAX_SCALE; + vm->maxes[BC_PROG_GLOBALS_IBASE] = BC_NUM_MAX_POSIX_IBASE; + vm->maxes[BC_PROG_GLOBALS_OBASE] = BC_MAX_OBASE; + vm->maxes[BC_PROG_GLOBALS_SCALE] = BC_MAX_SCALE; #if BC_ENABLE_EXTRA_MATH - vm.maxes[BC_PROG_MAX_RAND] = ((BcRand) 0) - 1; + vm->maxes[BC_PROG_MAX_RAND] = ((BcRand) 0) - 1; #endif // BC_ENABLE_EXTRA_MATH #if BC_ENABLED @@ -1430,31 +1764,39 @@ void bc_vm_init(void) { if (BC_IS_BC && !BC_IS_POSIX) #endif // !BC_ENABLE_LIBRARY { - vm.maxes[BC_PROG_GLOBALS_IBASE] = BC_NUM_MAX_IBASE; + vm->maxes[BC_PROG_GLOBALS_IBASE] = BC_NUM_MAX_IBASE; } #endif // BC_ENABLED } #if BC_ENABLE_LIBRARY -void bc_vm_atexit(void) { +void +bc_vm_atexit(void) +{ +#if BC_DEBUG +#if BC_ENABLE_LIBRARY + BcVm* vm = bcl_getspecific(); +#endif // BC_ENABLE_LIBRARY +#endif // BC_DEBUG bc_vm_shutdown(); -#ifndef NDEBUG - bc_vec_free(&vm.jmp_bufs); -#endif // NDEBUG +#if BC_DEBUG + bc_vec_free(&vm->jmp_bufs); +#endif // BC_DEBUG } #else // BC_ENABLE_LIBRARY -int bc_vm_atexit(int status) { - +int +bc_vm_atexit(int status) +{ // Set the status correctly. int s = BC_STATUS_IS_ERROR(status) ? status : BC_STATUS_SUCCESS; bc_vm_shutdown(); -#ifndef NDEBUG - bc_vec_free(&vm.jmp_bufs); -#endif // NDEBUG +#if BC_DEBUG + bc_vec_free(&vm->jmp_bufs); +#endif // BC_DEBUG return s; } |