diff options
author | Stefan Eßer <se@FreeBSD.org> | 2020-06-27 15:03:19 +0000 |
---|---|---|
committer | Stefan Eßer <se@FreeBSD.org> | 2020-06-27 15:03:19 +0000 |
commit | 1f958cfad78842ab9a1193471589231e25596cb3 (patch) | |
tree | 4bbff8044605fcfff11c9d322bb6f53495e4faa7 /src |
Diffstat (limited to 'src')
-rw-r--r-- | src/args.c | 215 | ||||
-rw-r--r-- | src/bc/bc.c | 57 | ||||
-rw-r--r-- | src/bc/lex.c | 410 | ||||
-rw-r--r-- | src/bc/parse.c | 1526 | ||||
-rw-r--r-- | src/data.c | 995 | ||||
-rw-r--r-- | src/dc/dc.c | 57 | ||||
-rw-r--r-- | src/dc/lex.c | 202 | ||||
-rw-r--r-- | src/dc/parse.c | 236 | ||||
-rw-r--r-- | src/file.c | 225 | ||||
-rw-r--r-- | src/history/history.c | 1450 | ||||
-rw-r--r-- | src/lang.c | 313 | ||||
-rw-r--r-- | src/lex.c | 231 | ||||
-rw-r--r-- | src/main.c | 93 | ||||
-rw-r--r-- | src/num.c | 2837 | ||||
-rw-r--r-- | src/opt.c | 250 | ||||
-rw-r--r-- | src/parse.c | 222 | ||||
-rw-r--r-- | src/program.c | 2300 | ||||
-rw-r--r-- | src/rand/rand.c | 415 | ||||
-rw-r--r-- | src/read.c | 226 | ||||
-rw-r--r-- | src/vector.c | 311 | ||||
-rw-r--r-- | src/vm.c | 819 |
21 files changed, 13390 insertions, 0 deletions
diff --git a/src/args.c b/src/args.c new file mode 100644 index 000000000000..d3735178fc48 --- /dev/null +++ b/src/args.c @@ -0,0 +1,215 @@ +/* + * ***************************************************************************** + * + * Copyright (c) 2018-2020 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 for processing command-line arguments. + * + */ + +#include <assert.h> +#include <ctype.h> +#include <stdbool.h> +#include <stdlib.h> +#include <string.h> + +#include <unistd.h> + +#include <status.h> +#include <vector.h> +#include <read.h> +#include <vm.h> +#include <args.h> +#include <opt.h> + +static const BcOptLong bc_args_lopt[] = { + + { "expression", BC_OPT_REQUIRED, 'e' }, + { "file", BC_OPT_REQUIRED, 'f' }, + { "help", BC_OPT_NONE, 'h' }, + { "interactive", BC_OPT_NONE, 'i' }, + { "no-prompt", BC_OPT_NONE, 'P' }, +#if BC_ENABLED + { "global-stacks", BC_OPT_BC_ONLY, 'g' }, + { "mathlib", BC_OPT_BC_ONLY, 'l' }, + { "quiet", BC_OPT_BC_ONLY, 'q' }, + { "standard", BC_OPT_BC_ONLY, 's' }, + { "warn", BC_OPT_BC_ONLY, 'w' }, +#endif // BC_ENABLED + { "version", BC_OPT_NONE, 'v' }, + { "version", BC_OPT_NONE, 'V' }, +#if DC_ENABLED + { "extended-register", BC_OPT_DC_ONLY, 'x' }, +#endif // DC_ENABLED + { NULL, 0, 0 }, + +}; + +static void bc_args_exprs(const char *str) { + BC_SIG_ASSERT_LOCKED; + if (vm.exprs.v == NULL) bc_vec_init(&vm.exprs, sizeof(uchar), NULL); + bc_vec_concat(&vm.exprs, str); + bc_vec_concat(&vm.exprs, "\n"); +} + +static void bc_args_file(const char *file) { + + char *buf; + + BC_SIG_ASSERT_LOCKED; + + vm.file = file; + + bc_read_file(file, &buf); + bc_args_exprs(buf); + free(buf); +} + +void bc_args(int argc, char *argv[]) { + + int c; + size_t i; + bool do_exit = false, version = false; + BcOpt opts; + + BC_SIG_ASSERT_LOCKED; + + bc_opt_init(&opts, argv); + + while ((c = bc_opt_parse(&opts, bc_args_lopt)) != -1) { + + switch (c) { + + case 'e': + { + bc_args_exprs(opts.optarg); + break; + } + + case 'f': + { + bc_args_file(opts.optarg); + break; + } + + case 'h': + { + bc_vm_info(vm.help); + do_exit = true; + break; + } + + case 'i': + { + vm.flags |= BC_FLAG_I; + break; + } + + case 'P': + { + vm.flags |= BC_FLAG_P; + break; + } + +#if BC_ENABLED + case 'g': + { + assert(BC_IS_BC); + vm.flags |= BC_FLAG_G; + break; + } + + case 'l': + { + assert(BC_IS_BC); + vm.flags |= BC_FLAG_L; + break; + } + + case 'q': + { + assert(BC_IS_BC); + vm.flags |= BC_FLAG_Q; + break; + } + + case 's': + { + assert(BC_IS_BC); + vm.flags |= BC_FLAG_S; + break; + } + + case 'w': + { + assert(BC_IS_BC); + vm.flags |= BC_FLAG_W; + break; + } +#endif // BC_ENABLED + + case 'V': + case 'v': + { + do_exit = version = true; + break; + } + +#if DC_ENABLED + case 'x': + { + assert(!BC_IS_BC); + vm.flags |= DC_FLAG_X; + break; + } +#endif // DC_ENABLED + +#ifndef NDEBUG + // We shouldn't get here because bc_opt_error()/bc_vm_error() should + // longjmp() out. + case '?': + case ':': + default: + { + abort(); + } +#endif // NDEBUG + } + } + + if (version) bc_vm_info(NULL); + if (do_exit) exit((int) vm.status); + if (vm.exprs.len > 1 || !BC_IS_BC) vm.flags |= BC_FLAG_Q; + + if (opts.optind < (size_t) argc) + bc_vec_init(&vm.files, sizeof(char*), NULL); + + for (i = opts.optind; i < (size_t) argc; ++i) + bc_vec_push(&vm.files, argv + i); +} diff --git a/src/bc/bc.c b/src/bc/bc.c new file mode 100644 index 000000000000..86d8f81dd401 --- /dev/null +++ b/src/bc/bc.c @@ -0,0 +1,57 @@ +/* + * ***************************************************************************** + * + * Copyright (c) 2018-2020 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. + * + * ***************************************************************************** + * + * The main procedure of bc. + * + */ + +#if BC_ENABLED + +#include <string.h> + +#include <status.h> +#include <bc.h> +#include <vm.h> + +void bc_main(int argc, char **argv) { + + vm.read_ret = BC_INST_RET; + vm.help = bc_help; + vm.sigmsg = bc_sig_msg; + vm.siglen = bc_sig_msg_len; + + vm.next = bc_lex_token; + vm.parse = bc_parse_parse; + vm.expr = bc_parse_expr; + + bc_vm_boot(argc, argv, "BC_LINE_LENGTH", "BC_ENV_ARGS", "BC_EXPR_EXIT"); +} +#endif // BC_ENABLED diff --git a/src/bc/lex.c b/src/bc/lex.c new file mode 100644 index 000000000000..d4c6bd4c192e --- /dev/null +++ b/src/bc/lex.c @@ -0,0 +1,410 @@ +/* + * ***************************************************************************** + * + * Copyright (c) 2018-2020 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. + * + * ***************************************************************************** + * + * The lexer for bc. + * + */ + +#if BC_ENABLED + +#include <assert.h> +#include <ctype.h> +#include <string.h> + +#include <lex.h> +#include <bc.h> +#include <vm.h> + +static void bc_lex_identifier(BcLex *l) { + + size_t i; + const char *buf = l->buf + l->i - 1; + + for (i = 0; i < bc_lex_kws_len; ++i) { + + const BcLexKeyword *kw = bc_lex_kws + i; + size_t n = BC_LEX_KW_LEN(kw); + + if (!strncmp(buf, kw->name, n) && !isalnum(buf[n]) && buf[n] != '_') { + + l->t = BC_LEX_KW_AUTO + (BcLexType) i; + + if (!BC_LEX_KW_POSIX(kw)) + bc_lex_verr(l, BC_ERROR_POSIX_KW, kw->name); + + // We minus 1 because the index has already been incremented. + l->i += n - 1; + return; + } + } + + bc_lex_name(l); + + if (BC_ERR(l->str.len - 1 > 1)) + bc_lex_verr(l, BC_ERROR_POSIX_NAME_LEN, l->str.v); +} + +static void bc_lex_string(BcLex *l) { + + size_t len, nlines = 0, i = l->i; + const char *buf = l->buf; + char c; + + l->t = BC_LEX_STR; + + for (; (c = buf[i]) && c != '"'; ++i) nlines += c == '\n'; + + if (BC_ERR(c == '\0')) { + l->i = i; + bc_lex_err(l, BC_ERROR_PARSE_STRING); + } + + len = i - l->i; + bc_vec_string(&l->str, len, l->buf + l->i); + + l->i = i + 1; + l->line += nlines; +} + +static void bc_lex_assign(BcLex *l, BcLexType with, BcLexType without) { + if (l->buf[l->i] == '=') { + l->i += 1; + l->t = with; + } + else l->t = without; +} + +void bc_lex_token(BcLex *l) { + + char c = l->buf[l->i++], c2; + + // This is the workhorse of the lexer. + switch (c) { + + case '\0': + case '\n': + case '\t': + case '\v': + case '\f': + case '\r': + case ' ': + { + bc_lex_commonTokens(l, c); + break; + } + + case '!': + { + bc_lex_assign(l, BC_LEX_OP_REL_NE, BC_LEX_OP_BOOL_NOT); + + if (l->t == BC_LEX_OP_BOOL_NOT) + bc_lex_verr(l, BC_ERROR_POSIX_BOOL, "!"); + + break; + } + + case '"': + { + bc_lex_string(l); + break; + } + + case '#': + { + bc_lex_err(l, BC_ERROR_POSIX_COMMENT); + bc_lex_lineComment(l); + break; + } + + case '%': + { + bc_lex_assign(l, BC_LEX_OP_ASSIGN_MODULUS, BC_LEX_OP_MODULUS); + break; + } + + case '&': + { + c2 = l->buf[l->i]; + if (BC_NO_ERR(c2 == '&')) { + + bc_lex_verr(l, BC_ERROR_POSIX_BOOL, "&&"); + + l->i += 1; + l->t = BC_LEX_OP_BOOL_AND; + } + else bc_lex_invalidChar(l, c); + + break; + } +#if BC_ENABLE_EXTRA_MATH + case '$': + { + l->t = BC_LEX_OP_TRUNC; + break; + } + + case '@': + { + bc_lex_assign(l, BC_LEX_OP_ASSIGN_PLACES, BC_LEX_OP_PLACES); + break; + } +#endif // BC_ENABLE_EXTRA_MATH + case '(': + case ')': + { + l->t = (BcLexType) (c - '(' + BC_LEX_LPAREN); + break; + } + + case '*': + { + bc_lex_assign(l, BC_LEX_OP_ASSIGN_MULTIPLY, BC_LEX_OP_MULTIPLY); + break; + } + + case '+': + { + c2 = l->buf[l->i]; + if (c2 == '+') { + l->i += 1; + l->t = BC_LEX_OP_INC; + } + else bc_lex_assign(l, BC_LEX_OP_ASSIGN_PLUS, BC_LEX_OP_PLUS); + break; + } + + case ',': + { + l->t = BC_LEX_COMMA; + break; + } + + case '-': + { + c2 = l->buf[l->i]; + if (c2 == '-') { + l->i += 1; + l->t = BC_LEX_OP_DEC; + } + else bc_lex_assign(l, BC_LEX_OP_ASSIGN_MINUS, BC_LEX_OP_MINUS); + break; + } + + case '.': + { + c2 = l->buf[l->i]; + if (BC_LEX_NUM_CHAR(c2, true, false)) bc_lex_number(l, c); + else { + l->t = BC_LEX_KW_LAST; + bc_lex_err(l, BC_ERROR_POSIX_DOT); + } + break; + } + + case '/': + { + c2 = l->buf[l->i]; + if (c2 =='*') bc_lex_comment(l); + else bc_lex_assign(l, BC_LEX_OP_ASSIGN_DIVIDE, BC_LEX_OP_DIVIDE); + break; + } + + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + case 'A': + case 'B': + case 'C': + case 'D': + case 'E': + case 'F': + // Apparently, GNU bc (and maybe others) allows any uppercase letter as + // a number. When single digits, they act like the ones above. When + // multi-digit, any letter above the input base is automatically set to + // the biggest allowable digit in the input base. + case 'G': + case 'H': + case 'I': + case 'J': + case 'K': + case 'L': + case 'M': + case 'N': + case 'O': + case 'P': + case 'Q': + case 'R': + case 'S': + case 'T': + case 'U': + case 'V': + case 'W': + case 'X': + case 'Y': + case 'Z': + { + bc_lex_number(l, c); + break; + } + + case ';': + { + l->t = BC_LEX_SCOLON; + break; + } + + case '<': + { +#if BC_ENABLE_EXTRA_MATH + c2 = l->buf[l->i]; + + if (c2 == '<') { + l->i += 1; + bc_lex_assign(l, BC_LEX_OP_ASSIGN_LSHIFT, BC_LEX_OP_LSHIFT); + break; + } +#endif // BC_ENABLE_EXTRA_MATH + bc_lex_assign(l, BC_LEX_OP_REL_LE, BC_LEX_OP_REL_LT); + break; + } + + case '=': + { + bc_lex_assign(l, BC_LEX_OP_REL_EQ, BC_LEX_OP_ASSIGN); + break; + } + + case '>': + { +#if BC_ENABLE_EXTRA_MATH + c2 = l->buf[l->i]; + + if (c2 == '>') { + l->i += 1; + bc_lex_assign(l, BC_LEX_OP_ASSIGN_RSHIFT, BC_LEX_OP_RSHIFT); + break; + } +#endif // BC_ENABLE_EXTRA_MATH + bc_lex_assign(l, BC_LEX_OP_REL_GE, BC_LEX_OP_REL_GT); + break; + } + + case '[': + case ']': + { + l->t = (BcLexType) (c - '[' + BC_LEX_LBRACKET); + break; + } + + case '\\': + { + if (BC_NO_ERR(l->buf[l->i] == '\n')) { + l->i += 1; + l->t = BC_LEX_WHITESPACE; + } + else bc_lex_invalidChar(l, c); + break; + } + + case '^': + { + bc_lex_assign(l, BC_LEX_OP_ASSIGN_POWER, BC_LEX_OP_POWER); + break; + } + + case 'a': + case 'b': + case 'c': + case 'd': + case 'e': + case 'f': + case 'g': + case 'h': + case 'i': + case 'j': + case 'k': + case 'l': + case 'm': + case 'n': + case 'o': + case 'p': + case 'q': + case 'r': + case 's': + case 't': + case 'u': + case 'v': + case 'w': + case 'x': + case 'y': + case 'z': + { + bc_lex_identifier(l); + break; + } + + case '{': + case '}': + { + l->t = (BcLexType) (c - '{' + BC_LEX_LBRACE); + break; + } + + case '|': + { + c2 = l->buf[l->i]; + + if (BC_NO_ERR(c2 == '|')) { + + bc_lex_verr(l, BC_ERROR_POSIX_BOOL, "||"); + + l->i += 1; + l->t = BC_LEX_OP_BOOL_OR; + } + else bc_lex_invalidChar(l, c); + + break; + } + + default: + { + bc_lex_invalidChar(l, c); + } + } +} +#endif // BC_ENABLED diff --git a/src/bc/parse.c b/src/bc/parse.c new file mode 100644 index 000000000000..fb00447ee6bc --- /dev/null +++ b/src/bc/parse.c @@ -0,0 +1,1526 @@ +/* + * ***************************************************************************** + * + * Copyright (c) 2018-2020 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. + * + * ***************************************************************************** + * + * The parser for bc. + * + */ + +#if BC_ENABLED + +#include <assert.h> +#include <stdbool.h> +#include <stdlib.h> +#include <string.h> + +#include <setjmp.h> + +#include <lex.h> +#include <parse.h> +#include <bc.h> +#include <num.h> +#include <vm.h> + +static void bc_parse_else(BcParse *p); +static void bc_parse_stmt(BcParse *p); +static BcParseStatus bc_parse_expr_err(BcParse *p, uint8_t flags, + BcParseNext next); + +static bool bc_parse_inst_isLeaf(BcInst t) { + return (t >= BC_INST_NUM && t <= BC_INST_MAXSCALE) || +#if BC_ENABLE_EXTRA_MATH + t == BC_INST_TRUNC || +#endif // BC_ENABLE_EXTRA_MATH + t <= BC_INST_DEC; +} + +static bool bc_parse_isDelimiter(const BcParse *p) { + + BcLexType t = p->l.t; + bool good = false; + + if (BC_PARSE_DELIMITER(t)) return true; + + if (t == BC_LEX_KW_ELSE) { + + size_t i; + uint16_t *fptr = NULL, flags = BC_PARSE_FLAG_ELSE; + + for (i = 0; i < p->flags.len && BC_PARSE_BLOCK_STMT(flags); ++i) { + + fptr = bc_vec_item_rev(&p->flags, i); + flags = *fptr; + + if ((flags & BC_PARSE_FLAG_BRACE) && p->l.last != BC_LEX_RBRACE) + return false; + } + + good = ((flags & BC_PARSE_FLAG_IF) != 0); + } + else if (t == BC_LEX_RBRACE) { + + size_t i; + + for (i = 0; !good && i < p->flags.len; ++i) { + uint16_t *fptr = bc_vec_item_rev(&p->flags, i); + good = (((*fptr) & BC_PARSE_FLAG_BRACE) != 0); + } + } + + return good; +} + +static void bc_parse_setLabel(BcParse *p) { + + BcFunc *func = p->func; + BcInstPtr *ip = bc_vec_top(&p->exits); + size_t *label; + + assert(func == bc_vec_item(&p->prog->fns, p->fidx)); + + label = bc_vec_item(&func->labels, ip->idx); + *label = func->code.len; + + bc_vec_pop(&p->exits); +} + +static void bc_parse_createLabel(BcParse *p, size_t idx) { + bc_vec_push(&p->func->labels, &idx); +} + +static void bc_parse_createCondLabel(BcParse *p, size_t idx) { + bc_parse_createLabel(p, p->func->code.len); + bc_vec_push(&p->conds, &idx); +} + +static void bc_parse_createExitLabel(BcParse *p, size_t idx, bool loop) { + + BcInstPtr ip; + + assert(p->func == bc_vec_item(&p->prog->fns, p->fidx)); + + ip.func = loop; + ip.idx = idx; + ip.len = 0; + + bc_vec_push(&p->exits, &ip); + bc_parse_createLabel(p, SIZE_MAX); +} + +static void bc_parse_operator(BcParse *p, BcLexType type, + size_t start, size_t *nexprs) +{ + BcLexType t; + uchar l, r = BC_PARSE_OP_PREC(type); + uchar left = BC_PARSE_OP_LEFT(type); + + while (p->ops.len > start) { + + t = BC_PARSE_TOP_OP(p); + if (t == BC_LEX_LPAREN) break; + + l = BC_PARSE_OP_PREC(t); + if (l >= r && (l != r || !left)) break; + + bc_parse_push(p, BC_PARSE_TOKEN_INST(t)); + bc_vec_pop(&p->ops); + *nexprs -= !BC_PARSE_OP_PREFIX(t); + } + + bc_vec_push(&p->ops, &type); +} + +static void bc_parse_rightParen(BcParse *p, size_t *nexs) { + + BcLexType top; + + while ((top = BC_PARSE_TOP_OP(p)) != BC_LEX_LPAREN) { + bc_parse_push(p, BC_PARSE_TOKEN_INST(top)); + bc_vec_pop(&p->ops); + *nexs -= !BC_PARSE_OP_PREFIX(top); + } + + bc_vec_pop(&p->ops); + + bc_lex_next(&p->l); +} + +static void bc_parse_params(BcParse *p, uint8_t flags) { + + bool comma = false; + size_t nparams; + + bc_lex_next(&p->l); + + for (nparams = 0; p->l.t != BC_LEX_RPAREN; ++nparams) { + + flags &= ~(BC_PARSE_PRINT | BC_PARSE_REL); + flags |= (BC_PARSE_ARRAY | BC_PARSE_NEEDVAL); + + bc_parse_expr_status(p, flags, bc_parse_next_param); + + comma = (p->l.t == BC_LEX_COMMA); + if (comma) bc_lex_next(&p->l); + } + + if (BC_ERR(comma)) bc_parse_err(p, BC_ERROR_PARSE_TOKEN); + bc_parse_push(p, BC_INST_CALL); + bc_parse_pushIndex(p, nparams); +} + +static void bc_parse_call(BcParse *p, const char *name, uint8_t flags) { + + size_t idx; + + bc_parse_params(p, flags); + + // We just assert this because bc_parse_params() should + // ensure that the next token is what it should be. + assert(p->l.t == BC_LEX_RPAREN); + + // We cannot use bc_program_insertFunc() here + // because it will overwrite an existing function. + idx = bc_map_index(&p->prog->fn_map, name); + + if (idx == BC_VEC_INVALID_IDX) { + + BC_SIG_LOCK; + + idx = bc_program_insertFunc(p->prog, name); + + BC_SIG_UNLOCK; + + assert(idx != BC_VEC_INVALID_IDX); + + // Make sure that this pointer was not invalidated. + p->func = bc_vec_item(&p->prog->fns, p->fidx); + } + else idx = ((BcId*) bc_vec_item(&p->prog->fn_map, idx))->idx; + + bc_parse_pushIndex(p, idx); + + bc_lex_next(&p->l); +} + +static void bc_parse_name(BcParse *p, BcInst *type, + bool *can_assign, uint8_t flags) +{ + char *name; + + BC_SIG_LOCK; + + name = bc_vm_strdup(p->l.str.v); + + BC_SETJMP_LOCKED(err); + + BC_SIG_UNLOCK; + + bc_lex_next(&p->l); + + if (p->l.t == BC_LEX_LBRACKET) { + + bc_lex_next(&p->l); + + if (p->l.t == BC_LEX_RBRACKET) { + + if (BC_ERR(!(flags & BC_PARSE_ARRAY))) + bc_parse_err(p, BC_ERROR_PARSE_EXPR); + + *type = BC_INST_ARRAY; + *can_assign = false; + } + else { + + uint8_t flags2 = (flags & ~(BC_PARSE_PRINT | BC_PARSE_REL)) | + BC_PARSE_NEEDVAL; + + bc_parse_expr_status(p, flags2, bc_parse_next_elem); + + if (BC_ERR(p->l.t != BC_LEX_RBRACKET)) + bc_parse_err(p, BC_ERROR_PARSE_TOKEN); + + *type = BC_INST_ARRAY_ELEM; + *can_assign = true; + } + + bc_lex_next(&p->l); + + bc_parse_push(p, *type); + bc_parse_pushName(p, name, false); + } + else if (p->l.t == BC_LEX_LPAREN) { + + if (BC_ERR(flags & BC_PARSE_NOCALL)) + bc_parse_err(p, BC_ERROR_PARSE_TOKEN); + + *type = BC_INST_CALL; + *can_assign = false; + + bc_parse_call(p, name, flags); + } + else { + *type = BC_INST_VAR; + *can_assign = true; + bc_parse_push(p, BC_INST_VAR); + bc_parse_pushName(p, name, true); + } + +err: + BC_SIG_MAYLOCK; + free(name); + BC_LONGJMP_CONT; +} + +static void bc_parse_noArgBuiltin(BcParse *p, BcInst inst) { + + bc_lex_next(&p->l); + if (BC_ERR(p->l.t != BC_LEX_LPAREN)) bc_parse_err(p, BC_ERROR_PARSE_TOKEN); + + bc_lex_next(&p->l); + if ((p->l.t != BC_LEX_RPAREN)) bc_parse_err(p, BC_ERROR_PARSE_TOKEN); + + bc_parse_push(p, inst); + + bc_lex_next(&p->l); +} + +static void bc_parse_builtin(BcParse *p, BcLexType type, + uint8_t flags, BcInst *prev) +{ + bc_lex_next(&p->l); + if (BC_ERR(p->l.t != BC_LEX_LPAREN)) + bc_parse_err(p, BC_ERROR_PARSE_TOKEN); + + bc_lex_next(&p->l); + + flags &= ~(BC_PARSE_PRINT | BC_PARSE_REL); + flags |= BC_PARSE_NEEDVAL; + if (type == BC_LEX_KW_LENGTH) flags |= BC_PARSE_ARRAY; + + bc_parse_expr_status(p, flags, bc_parse_next_rel); + + if (BC_ERR(p->l.t != BC_LEX_RPAREN)) + bc_parse_err(p, BC_ERROR_PARSE_TOKEN); + + *prev = type - BC_LEX_KW_LENGTH + BC_INST_LENGTH; + bc_parse_push(p, *prev); + + bc_lex_next(&p->l); +} + +static void bc_parse_scale(BcParse *p, BcInst *type, + bool *can_assign, uint8_t flags) +{ + bc_lex_next(&p->l); + + if (p->l.t != BC_LEX_LPAREN) { + *type = BC_INST_SCALE; + *can_assign = true; + bc_parse_push(p, BC_INST_SCALE); + return; + } + + *type = BC_INST_SCALE_FUNC; + *can_assign = false; + flags &= ~(BC_PARSE_PRINT | BC_PARSE_REL); + flags |= BC_PARSE_NEEDVAL; + + bc_lex_next(&p->l); + + bc_parse_expr_status(p, flags, bc_parse_next_rel); + if (BC_ERR(p->l.t != BC_LEX_RPAREN)) + bc_parse_err(p, BC_ERROR_PARSE_TOKEN); + + bc_parse_push(p, BC_INST_SCALE_FUNC); + + bc_lex_next(&p->l); +} + +static void bc_parse_incdec(BcParse *p, BcInst *prev, bool *can_assign, + size_t *nexs, uint8_t flags) +{ + BcLexType type; + uchar inst; + BcInst etype = *prev; + BcLexType last = p->l.last; + + assert(prev != NULL && can_assign != NULL); + + if (BC_ERR(last == BC_LEX_OP_INC || last == BC_LEX_OP_DEC || + last == BC_LEX_RPAREN)) + { + bc_parse_err(p, BC_ERROR_PARSE_ASSIGN); + } + + if (BC_PARSE_INST_VAR(etype)) { + + if (!*can_assign) bc_parse_err(p, BC_ERROR_PARSE_ASSIGN); + + *prev = inst = BC_INST_INC + (p->l.t != BC_LEX_OP_INC); + bc_parse_push(p, inst); + bc_lex_next(&p->l); + *can_assign = false; + } + else { + + *prev = inst = BC_INST_ASSIGN_PLUS + (p->l.t != BC_LEX_OP_INC); + + bc_lex_next(&p->l); + type = p->l.t; + + // Because we parse the next part of the expression + // right here, we need to increment this. + *nexs = *nexs + 1; + + if (type == BC_LEX_NAME) + bc_parse_name(p, prev, can_assign, flags | BC_PARSE_NOCALL); + else if (type >= BC_LEX_KW_LAST && type <= BC_LEX_KW_OBASE) { + bc_parse_push(p, type - BC_LEX_KW_LAST + BC_INST_LAST); + bc_lex_next(&p->l); + } + else if (BC_NO_ERR(type == BC_LEX_KW_SCALE)) { + + bc_lex_next(&p->l); + + if (BC_ERR(p->l.t == BC_LEX_LPAREN)) + bc_parse_err(p, BC_ERROR_PARSE_TOKEN); + else bc_parse_push(p, BC_INST_SCALE); + } + else bc_parse_err(p, BC_ERROR_PARSE_TOKEN); + + *can_assign = false; + + bc_parse_push(p, BC_INST_ONE); + bc_parse_push(p, inst); + } +} + +static void bc_parse_minus(BcParse *p, BcInst *prev, size_t ops_bgn, + bool rparen, bool binlast, size_t *nexprs) +{ + BcLexType type; + + bc_lex_next(&p->l); + + type = BC_PARSE_LEAF(*prev, binlast, rparen) ? BC_LEX_OP_MINUS : BC_LEX_NEG; + *prev = BC_PARSE_TOKEN_INST(type); + + // We can just push onto the op stack because this is the largest + // precedence operator that gets pushed. Inc/dec does not. + if (type != BC_LEX_OP_MINUS) bc_vec_push(&p->ops, &type); + else bc_parse_operator(p, type, ops_bgn, nexprs); +} + +static void bc_parse_str(BcParse *p, char inst) { + bc_parse_addString(p); + bc_parse_push(p, inst); + bc_lex_next(&p->l); +} + +static void bc_parse_print(BcParse *p) { + + BcLexType t; + bool comma = false; + + bc_lex_next(&p->l); + + t = p->l.t; + + if (bc_parse_isDelimiter(p)) bc_parse_err(p, BC_ERROR_PARSE_PRINT); + + do { + if (t == BC_LEX_STR) bc_parse_str(p, BC_INST_PRINT_POP); + else { + bc_parse_expr_status(p, BC_PARSE_NEEDVAL, bc_parse_next_print); + bc_parse_push(p, BC_INST_PRINT_POP); + } + + comma = (p->l.t == BC_LEX_COMMA); + + if (comma) bc_lex_next(&p->l); + else { + if (!bc_parse_isDelimiter(p)) + bc_parse_err(p, BC_ERROR_PARSE_TOKEN); + else break; + } + + t = p->l.t; + } while (true); + + if (BC_ERR(comma)) bc_parse_err(p, BC_ERROR_PARSE_TOKEN); +} + +static void bc_parse_return(BcParse *p) { + + BcLexType t; + bool paren; + uchar inst = BC_INST_RET0; + + if (BC_ERR(!BC_PARSE_FUNC(p))) bc_parse_err(p, BC_ERROR_PARSE_TOKEN); + + if (p->func->voidfn) inst = BC_INST_RET_VOID; + + bc_lex_next(&p->l); + + t = p->l.t; + paren = t == BC_LEX_LPAREN; + + if (bc_parse_isDelimiter(p)) bc_parse_push(p, inst); + else { + + BcParseStatus s; + + s = bc_parse_expr_err(p, BC_PARSE_NEEDVAL, bc_parse_next_expr); + + if (s == BC_PARSE_STATUS_EMPTY_EXPR) { + bc_parse_push(p, inst); + bc_lex_next(&p->l); + } + + if (!paren || p->l.last != BC_LEX_RPAREN) { + bc_parse_err(p, BC_ERROR_POSIX_RET); + } + else if (BC_ERR(p->func->voidfn)) + bc_parse_verr(p, BC_ERROR_PARSE_RET_VOID, p->func->name); + + bc_parse_push(p, BC_INST_RET); + } +} + +static void bc_parse_endBody(BcParse *p, bool brace) { + + bool has_brace, new_else = false; + + if (BC_ERR(p->flags.len <= 1)) bc_parse_err(p, BC_ERROR_PARSE_TOKEN); + + if (brace) { + + assert(p->l.t == BC_LEX_RBRACE); + + bc_lex_next(&p->l); + if (BC_ERR(!bc_parse_isDelimiter(p))) + bc_parse_err(p, BC_ERROR_PARSE_TOKEN); + } + + has_brace = (BC_PARSE_BRACE(p) != 0); + + do { + size_t len = p->flags.len; + bool loop; + + if (has_brace && !brace) bc_parse_err(p, BC_ERROR_PARSE_TOKEN); + + loop = (BC_PARSE_LOOP_INNER(p) != 0); + + if (loop || BC_PARSE_ELSE(p)) { + + if (loop) { + + size_t *label = bc_vec_top(&p->conds); + + bc_parse_push(p, BC_INST_JUMP); + bc_parse_pushIndex(p, *label); + + bc_vec_pop(&p->conds); + } + + bc_parse_setLabel(p); + bc_vec_pop(&p->flags); + } + else if (BC_PARSE_FUNC_INNER(p)) { + BcInst inst = (p->func->voidfn ? BC_INST_RET_VOID : BC_INST_RET0); + bc_parse_push(p, inst); + bc_parse_updateFunc(p, BC_PROG_MAIN); + bc_vec_pop(&p->flags); + } + else if (BC_PARSE_BRACE(p) && !BC_PARSE_IF(p)) bc_vec_pop(&p->flags); + + // This needs to be last to parse nested if's properly. + if (BC_PARSE_IF(p) && (len == p->flags.len || !BC_PARSE_BRACE(p))) { + + while (p->l.t == BC_LEX_NLINE) bc_lex_next(&p->l); + + bc_vec_pop(&p->flags); + + if (!BC_S) { + + *(BC_PARSE_TOP_FLAG_PTR(p)) |= BC_PARSE_FLAG_IF_END; + new_else = (p->l.t == BC_LEX_KW_ELSE); + + if (new_else) bc_parse_else(p); + else if (!has_brace && (!BC_PARSE_IF_END(p) || brace)) + bc_parse_noElse(p); + } + else bc_parse_noElse(p); + } + + if (brace && has_brace) brace = false; + + } while (p->flags.len > 1 && !new_else && (!BC_PARSE_IF_END(p) || brace) && + !(has_brace = (BC_PARSE_BRACE(p) != 0))); + + if (BC_ERR(p->flags.len == 1 && brace)) + bc_parse_err(p, BC_ERROR_PARSE_TOKEN); + else if (brace && BC_PARSE_BRACE(p)) { + + uint16_t flags = BC_PARSE_TOP_FLAG(p); + + if (!(flags & (BC_PARSE_FLAG_FUNC_INNER | BC_PARSE_FLAG_LOOP_INNER)) && + !(flags & (BC_PARSE_FLAG_IF | BC_PARSE_FLAG_ELSE)) && + !(flags & (BC_PARSE_FLAG_IF_END))) + { + bc_vec_pop(&p->flags); + } + } +} + +static void bc_parse_startBody(BcParse *p, uint16_t flags) { + assert(flags); + flags |= (BC_PARSE_TOP_FLAG(p) & (BC_PARSE_FLAG_FUNC | BC_PARSE_FLAG_LOOP)); + flags |= BC_PARSE_FLAG_BODY; + bc_vec_push(&p->flags, &flags); +} + +void bc_parse_noElse(BcParse *p) { + uint16_t *flag_ptr = BC_PARSE_TOP_FLAG_PTR(p); + *flag_ptr = (*flag_ptr & ~(BC_PARSE_FLAG_IF_END)); + bc_parse_setLabel(p); +} + +static void bc_parse_if(BcParse *p) { + + size_t idx; + uint8_t flags = (BC_PARSE_REL | BC_PARSE_NEEDVAL); + + bc_lex_next(&p->l); + if (BC_ERR(p->l.t != BC_LEX_LPAREN)) + bc_parse_err(p, BC_ERROR_PARSE_TOKEN); + + bc_lex_next(&p->l); + bc_parse_expr_status(p, flags, bc_parse_next_rel); + if (BC_ERR(p->l.t != BC_LEX_RPAREN)) + bc_parse_err(p, BC_ERROR_PARSE_TOKEN); + + bc_lex_next(&p->l); + bc_parse_push(p, BC_INST_JUMP_ZERO); + + idx = p->func->labels.len; + + bc_parse_pushIndex(p, idx); + bc_parse_createExitLabel(p, idx, false); + bc_parse_startBody(p, BC_PARSE_FLAG_IF); +} + +static void bc_parse_else(BcParse *p) { + + size_t idx = p->func->labels.len; + + if (BC_ERR(!BC_PARSE_IF_END(p))) + bc_parse_err(p, BC_ERROR_PARSE_TOKEN); + + bc_parse_push(p, BC_INST_JUMP); + bc_parse_pushIndex(p, idx); + + bc_parse_noElse(p); + + bc_parse_createExitLabel(p, idx, false); + bc_parse_startBody(p, BC_PARSE_FLAG_ELSE); + + bc_lex_next(&p->l); +} + +static void bc_parse_while(BcParse *p) { + + size_t idx; + uint8_t flags = (BC_PARSE_REL | BC_PARSE_NEEDVAL); + + bc_lex_next(&p->l); + if (BC_ERR(p->l.t != BC_LEX_LPAREN)) + bc_parse_err(p, BC_ERROR_PARSE_TOKEN); + bc_lex_next(&p->l); + + bc_parse_createCondLabel(p, p->func->labels.len); + idx = p->func->labels.len; + bc_parse_createExitLabel(p, idx, true); + + bc_parse_expr_status(p, flags, bc_parse_next_rel); + if (BC_ERR(p->l.t != BC_LEX_RPAREN)) + bc_parse_err(p, BC_ERROR_PARSE_TOKEN); + bc_lex_next(&p->l); + + bc_parse_push(p, BC_INST_JUMP_ZERO); + bc_parse_pushIndex(p, idx); + bc_parse_startBody(p, BC_PARSE_FLAG_LOOP | BC_PARSE_FLAG_LOOP_INNER); +} + +static void bc_parse_for(BcParse *p) { + + size_t cond_idx, exit_idx, body_idx, update_idx; + + bc_lex_next(&p->l); + if (BC_ERR(p->l.t != BC_LEX_LPAREN)) + bc_parse_err(p, BC_ERROR_PARSE_TOKEN); + bc_lex_next(&p->l); + + if (p->l.t != BC_LEX_SCOLON) + bc_parse_expr_status(p, 0, bc_parse_next_for); + else bc_parse_err(p, BC_ERROR_POSIX_FOR); + + if (BC_ERR(p->l.t != BC_LEX_SCOLON)) + bc_parse_err(p, BC_ERROR_PARSE_TOKEN); + bc_lex_next(&p->l); + + cond_idx = p->func->labels.len; + update_idx = cond_idx + 1; + body_idx = update_idx + 1; + exit_idx = body_idx + 1; + + bc_parse_createLabel(p, p->func->code.len); + + if (p->l.t != BC_LEX_SCOLON) { + uint8_t flags = (BC_PARSE_REL | BC_PARSE_NEEDVAL); + bc_parse_expr_status(p, flags, bc_parse_next_for); + } + else { + + // Set this for the next call to bc_parse_number. + // This is safe to set because the current token + // is a semicolon, which has no string requirement. + bc_vec_string(&p->l.str, strlen(bc_parse_const1), bc_parse_const1); + bc_parse_number(p); + + bc_parse_err(p, BC_ERROR_POSIX_FOR); + } + + if (BC_ERR(p->l.t != BC_LEX_SCOLON)) + bc_parse_err(p, BC_ERROR_PARSE_TOKEN); + + bc_lex_next(&p->l); + + bc_parse_push(p, BC_INST_JUMP_ZERO); + bc_parse_pushIndex(p, exit_idx); + bc_parse_push(p, BC_INST_JUMP); + bc_parse_pushIndex(p, body_idx); + + bc_parse_createCondLabel(p, update_idx); + + if (p->l.t != BC_LEX_RPAREN) + bc_parse_expr_status(p, 0, bc_parse_next_rel); + else bc_parse_err(p, BC_ERROR_POSIX_FOR); + + if (BC_ERR(p->l.t != BC_LEX_RPAREN)) + bc_parse_err(p, BC_ERROR_PARSE_TOKEN); + bc_parse_push(p, BC_INST_JUMP); + bc_parse_pushIndex(p, cond_idx); + bc_parse_createLabel(p, p->func->code.len); + + bc_parse_createExitLabel(p, exit_idx, true); + bc_lex_next(&p->l); + bc_parse_startBody(p, BC_PARSE_FLAG_LOOP | BC_PARSE_FLAG_LOOP_INNER); +} + +static void bc_parse_loopExit(BcParse *p, BcLexType type) { + + size_t i; + BcInstPtr *ip; + + if (BC_ERR(!BC_PARSE_LOOP(p))) bc_parse_err(p, BC_ERROR_PARSE_TOKEN); + + if (type == BC_LEX_KW_BREAK) { + + if (BC_ERR(!p->exits.len)) bc_parse_err(p, BC_ERROR_PARSE_TOKEN); + + i = p->exits.len - 1; + ip = bc_vec_item(&p->exits, i); + + while (!ip->func && i < p->exits.len) ip = bc_vec_item(&p->exits, i--); + assert(ip != NULL && (i < p->exits.len || ip->func)); + i = ip->idx; + } + else i = *((size_t*) bc_vec_top(&p->conds)); + + bc_parse_push(p, BC_INST_JUMP); + bc_parse_pushIndex(p, i); + + bc_lex_next(&p->l); +} + +static void bc_parse_func(BcParse *p) { + + bool comma = false, voidfn; + uint16_t flags; + size_t idx; + + bc_lex_next(&p->l); + + if (BC_ERR(p->l.t != BC_LEX_NAME)) + bc_parse_err(p, BC_ERROR_PARSE_FUNC); + + voidfn = (!BC_IS_POSIX && p->l.t == BC_LEX_NAME && + !strcmp(p->l.str.v, "void")); + + bc_lex_next(&p->l); + + voidfn = (voidfn && p->l.t == BC_LEX_NAME); + + if (voidfn) { + bc_parse_err(p, BC_ERROR_POSIX_VOID); + bc_lex_next(&p->l); + } + + if (BC_ERR(p->l.t != BC_LEX_LPAREN)) + bc_parse_err(p, BC_ERROR_PARSE_FUNC); + + assert(p->prog->fns.len == p->prog->fn_map.len); + + BC_SIG_LOCK; + + idx = bc_program_insertFunc(p->prog, p->l.str.v); + + BC_SIG_UNLOCK; + + assert(idx); + bc_parse_updateFunc(p, idx); + p->func->voidfn = voidfn; + + bc_lex_next(&p->l); + + while (p->l.t != BC_LEX_RPAREN) { + + BcType t = BC_TYPE_VAR; + + if (p->l.t == BC_LEX_OP_MULTIPLY) { + t = BC_TYPE_REF; + bc_lex_next(&p->l); + bc_parse_err(p, BC_ERROR_POSIX_REF); + } + + if (BC_ERR(p->l.t != BC_LEX_NAME)) + bc_parse_err(p, BC_ERROR_PARSE_FUNC); + + p->func->nparams += 1; + + bc_vec_string(&p->buf, p->l.str.len, p->l.str.v); + + bc_lex_next(&p->l); + + if (p->l.t == BC_LEX_LBRACKET) { + + if (t == BC_TYPE_VAR) t = BC_TYPE_ARRAY; + + bc_lex_next(&p->l); + + if (BC_ERR(p->l.t != BC_LEX_RBRACKET)) + bc_parse_err(p, BC_ERROR_PARSE_FUNC); + + bc_lex_next(&p->l); + } + else if (BC_ERR(t == BC_TYPE_REF)) + bc_parse_verr(p, BC_ERROR_PARSE_REF_VAR, p->buf.v); + + comma = (p->l.t == BC_LEX_COMMA); + if (comma) { + bc_lex_next(&p->l); + } + + bc_func_insert(p->func, p->prog, p->buf.v, t, p->l.line); + } + + if (BC_ERR(comma)) bc_parse_err(p, BC_ERROR_PARSE_FUNC); + + flags = BC_PARSE_FLAG_FUNC | BC_PARSE_FLAG_FUNC_INNER; + bc_parse_startBody(p, flags); + + bc_lex_next(&p->l); + + if (p->l.t != BC_LEX_LBRACE) bc_parse_err(p, BC_ERROR_POSIX_BRACE); +} + +static void bc_parse_auto(BcParse *p) { + + bool comma, one; + + if (BC_ERR(!p->auto_part)) bc_parse_err(p, BC_ERROR_PARSE_TOKEN); + bc_lex_next(&p->l); + + p->auto_part = comma = false; + one = p->l.t == BC_LEX_NAME; + + while (p->l.t == BC_LEX_NAME) { + + BcType t; + + bc_vec_string(&p->buf, p->l.str.len - 1, p->l.str.v); + + bc_lex_next(&p->l); + + if (p->l.t == BC_LEX_LBRACKET) { + + t = BC_TYPE_ARRAY; + + bc_lex_next(&p->l); + + if (BC_ERR(p->l.t != BC_LEX_RBRACKET)) + bc_parse_err(p, BC_ERROR_PARSE_FUNC); + + bc_lex_next(&p->l); + } + else t = BC_TYPE_VAR; + + comma = (p->l.t == BC_LEX_COMMA); + if (comma) bc_lex_next(&p->l); + + bc_func_insert(p->func, p->prog, p->buf.v, t, p->l.line); + } + + if (BC_ERR(comma)) bc_parse_err(p, BC_ERROR_PARSE_FUNC); + if (BC_ERR(!one)) bc_parse_err(p, BC_ERROR_PARSE_NO_AUTO); + if (BC_ERR(!bc_parse_isDelimiter(p))) + bc_parse_err(p, BC_ERROR_PARSE_TOKEN); +} + +static void bc_parse_body(BcParse *p, bool brace) { + + uint16_t *flag_ptr = BC_PARSE_TOP_FLAG_PTR(p); + + assert(flag_ptr != NULL); + assert(p->flags.len >= 2); + + *flag_ptr &= ~(BC_PARSE_FLAG_BODY); + + if (*flag_ptr & BC_PARSE_FLAG_FUNC_INNER) { + + if (BC_ERR(!brace)) bc_parse_err(p, BC_ERROR_PARSE_TOKEN); + + p->auto_part = (p->l.t != BC_LEX_KW_AUTO); + + if (!p->auto_part) { + + // Make sure this is true to not get a parse error. + p->auto_part = true; + + bc_parse_auto(p); + } + + if (p->l.t == BC_LEX_NLINE) bc_lex_next(&p->l); + } + else { + + size_t len = p->flags.len; + + assert(*flag_ptr); + + bc_parse_stmt(p); + + if (!brace && !BC_PARSE_BODY(p) && len <= p->flags.len) + bc_parse_endBody(p, false); + } +} + +static void bc_parse_stmt(BcParse *p) { + + size_t len; + uint16_t flags; + BcLexType type = p->l.t; + + if (type == BC_LEX_NLINE) { + bc_lex_next(&p->l); + return; + } + if (type == BC_LEX_KW_AUTO) { + bc_parse_auto(p); + return; + } + + p->auto_part = false; + + if (type != BC_LEX_KW_ELSE) { + + if (BC_PARSE_IF_END(p)) { + bc_parse_noElse(p); + if (p->flags.len > 1 && !BC_PARSE_BRACE(p)) + bc_parse_endBody(p, false); + return; + } + else if (type == BC_LEX_LBRACE) { + + if (!BC_PARSE_BODY(p)) { + bc_parse_startBody(p, BC_PARSE_FLAG_BRACE); + bc_lex_next(&p->l); + } + else { + *(BC_PARSE_TOP_FLAG_PTR(p)) |= BC_PARSE_FLAG_BRACE; + bc_lex_next(&p->l); + bc_parse_body(p, true); + } + + return; + } + else if (BC_PARSE_BODY(p) && !BC_PARSE_BRACE(p)) { + bc_parse_body(p, false); + return; + } + } + + len = p->flags.len; + flags = BC_PARSE_TOP_FLAG(p); + + switch (type) { + + case BC_LEX_OP_INC: + case BC_LEX_OP_DEC: + case BC_LEX_OP_MINUS: + case BC_LEX_OP_BOOL_NOT: + case BC_LEX_LPAREN: + case BC_LEX_NAME: + case BC_LEX_NUMBER: + case BC_LEX_KW_IBASE: + case BC_LEX_KW_LAST: + case BC_LEX_KW_LENGTH: + case BC_LEX_KW_OBASE: + case BC_LEX_KW_SCALE: +#if BC_ENABLE_EXTRA_MATH + case BC_LEX_KW_SEED: +#endif // BC_ENABLE_EXTRA_MATH + case BC_LEX_KW_SQRT: + case BC_LEX_KW_ABS: +#if BC_ENABLE_EXTRA_MATH + case BC_LEX_KW_IRAND: +#endif // BC_ENABLE_EXTRA_MATH + case BC_LEX_KW_READ: +#if BC_ENABLE_EXTRA_MATH + case BC_LEX_KW_RAND: +#endif // BC_ENABLE_EXTRA_MATH + case BC_LEX_KW_MAXIBASE: + case BC_LEX_KW_MAXOBASE: + case BC_LEX_KW_MAXSCALE: +#if BC_ENABLE_EXTRA_MATH + case BC_LEX_KW_MAXRAND: +#endif // BC_ENABLE_EXTRA_MATH + { + bc_parse_expr_status(p, BC_PARSE_PRINT, bc_parse_next_expr); + break; + } + + case BC_LEX_KW_ELSE: + { + bc_parse_else(p); + break; + } + + case BC_LEX_SCOLON: + { + // Do nothing. + break; + } + + case BC_LEX_RBRACE: + { + bc_parse_endBody(p, true); + break; + } + + case BC_LEX_STR: + { + bc_parse_str(p, BC_INST_PRINT_STR); + break; + } + + case BC_LEX_KW_BREAK: + case BC_LEX_KW_CONTINUE: + { + bc_parse_loopExit(p, p->l.t); + break; + } + + case BC_LEX_KW_FOR: + { + bc_parse_for(p); + break; + } + + case BC_LEX_KW_HALT: + { + bc_parse_push(p, BC_INST_HALT); + bc_lex_next(&p->l); + break; + } + + case BC_LEX_KW_IF: + { + bc_parse_if(p); + break; + } + + case BC_LEX_KW_LIMITS: + { + bc_vm_printf("BC_LONG_BIT = %lu\n", (ulong) BC_LONG_BIT); + bc_vm_printf("BC_BASE_DIGS = %lu\n", (ulong) BC_BASE_DIGS); + bc_vm_printf("BC_BASE_POW = %lu\n", (ulong) BC_BASE_POW); + bc_vm_printf("BC_OVERFLOW_MAX = %lu\n", (ulong) BC_NUM_BIGDIG_MAX); + bc_vm_printf("\n"); + bc_vm_printf("BC_BASE_MAX = %lu\n", BC_MAX_OBASE); + bc_vm_printf("BC_DIM_MAX = %lu\n", BC_MAX_DIM); + bc_vm_printf("BC_SCALE_MAX = %lu\n", BC_MAX_SCALE); + bc_vm_printf("BC_STRING_MAX = %lu\n", BC_MAX_STRING); + bc_vm_printf("BC_NAME_MAX = %lu\n", BC_MAX_NAME); + bc_vm_printf("BC_NUM_MAX = %lu\n", BC_MAX_NUM); +#if BC_ENABLE_EXTRA_MATH + bc_vm_printf("BC_RAND_MAX = %lu\n", BC_MAX_RAND); +#endif // BC_ENABLE_EXTRA_MATH + bc_vm_printf("MAX Exponent = %lu\n", BC_MAX_EXP); + bc_vm_printf("Number of vars = %lu\n", BC_MAX_VARS); + + bc_lex_next(&p->l); + + break; + } + + case BC_LEX_KW_PRINT: + { + bc_parse_print(p); + break; + } + + case BC_LEX_KW_QUIT: + { + // Quit is a compile-time command. We don't exit directly, + // so the vm can clean up. Limits do the same thing. + vm.status = BC_STATUS_QUIT; + BC_VM_JMP; + break; + } + + case BC_LEX_KW_RETURN: + { + bc_parse_return(p); + break; + } + + case BC_LEX_KW_WHILE: + { + bc_parse_while(p); + break; + } + + default: + { + bc_parse_err(p, BC_ERROR_PARSE_TOKEN); + } + } + + if (len == p->flags.len && flags == BC_PARSE_TOP_FLAG(p)) { + if (BC_ERR(!bc_parse_isDelimiter(p))) + bc_parse_err(p, BC_ERROR_PARSE_TOKEN); + } + + // Make sure semicolons are eaten. + while (p->l.t == BC_LEX_SCOLON) bc_lex_next(&p->l); +} + +void bc_parse_parse(BcParse *p) { + + assert(p); + + BC_SETJMP(exit); + + if (BC_ERR(p->l.t == BC_LEX_EOF)) bc_parse_err(p, BC_ERROR_PARSE_EOF); + else if (p->l.t == BC_LEX_KW_DEFINE) { + if (BC_ERR(BC_PARSE_NO_EXEC(p))) + bc_parse_err(p, BC_ERROR_PARSE_TOKEN); + bc_parse_func(p); + } + else bc_parse_stmt(p); + +exit: + BC_SIG_MAYLOCK; + if (BC_ERR((vm.status && vm.status != BC_STATUS_QUIT))) bc_parse_reset(p); + BC_LONGJMP_CONT; +} + +static BcParseStatus bc_parse_expr_err(BcParse *p, uint8_t flags, + BcParseNext next) +{ + BcInst prev = BC_INST_PRINT; + uchar inst = BC_INST_INVALID; + BcLexType top, t = p->l.t; + size_t nexprs = 0, ops_bgn = p->ops.len; + uint32_t i, nparens, nrelops; + bool pfirst, rprn, done, get_token, assign, bin_last, incdec, can_assign; + + assert(!(flags & BC_PARSE_PRINT) || !(flags & BC_PARSE_NEEDVAL)); + + pfirst = (p->l.t == BC_LEX_LPAREN); + nparens = nrelops = 0; + rprn = done = get_token = assign = incdec = can_assign = false; + bin_last = true; + + // We want to eat newlines if newlines are not a valid ending token. + // This is for spacing in things like for loop headers. + if (!(flags & BC_PARSE_NOREAD)) { + while ((t = p->l.t) == BC_LEX_NLINE) bc_lex_next(&p->l); + } + + for (; !done && BC_PARSE_EXPR(t); t = p->l.t) + { + switch (t) { + + case BC_LEX_OP_INC: + case BC_LEX_OP_DEC: + { + if (BC_ERR(incdec)) bc_parse_err(p, BC_ERROR_PARSE_ASSIGN); + bc_parse_incdec(p, &prev, &can_assign, &nexprs, flags); + rprn = get_token = bin_last = false; + incdec = true; + flags &= ~(BC_PARSE_ARRAY); + break; + } + +#if BC_ENABLE_EXTRA_MATH + case BC_LEX_OP_TRUNC: + { + if (BC_ERR(!BC_PARSE_LEAF(prev, bin_last, rprn))) + bc_parse_err(p, BC_ERROR_PARSE_TOKEN); + + // I can just add the instruction because + // negative will already be taken care of. + bc_parse_push(p, BC_INST_TRUNC); + rprn = can_assign = false; + get_token = true; + flags &= ~(BC_PARSE_ARRAY); + break; + } +#endif // BC_ENABLE_EXTRA_MATH + + case BC_LEX_OP_MINUS: + { + bc_parse_minus(p, &prev, ops_bgn, rprn, bin_last, &nexprs); + rprn = get_token = can_assign = false; + bin_last = (prev == BC_INST_MINUS); + if (bin_last) incdec = false; + flags &= ~(BC_PARSE_ARRAY); + break; + } + + case BC_LEX_OP_ASSIGN_POWER: + case BC_LEX_OP_ASSIGN_MULTIPLY: + case BC_LEX_OP_ASSIGN_DIVIDE: + case BC_LEX_OP_ASSIGN_MODULUS: + case BC_LEX_OP_ASSIGN_PLUS: + case BC_LEX_OP_ASSIGN_MINUS: +#if BC_ENABLE_EXTRA_MATH + case BC_LEX_OP_ASSIGN_PLACES: + case BC_LEX_OP_ASSIGN_LSHIFT: + case BC_LEX_OP_ASSIGN_RSHIFT: +#endif // BC_ENABLE_EXTRA_MATH + case BC_LEX_OP_ASSIGN: + { + if (!BC_PARSE_INST_VAR(prev)) + bc_parse_err(p, BC_ERROR_PARSE_ASSIGN); + } + // Fallthrough. + case BC_LEX_OP_POWER: + case BC_LEX_OP_MULTIPLY: + case BC_LEX_OP_DIVIDE: + case BC_LEX_OP_MODULUS: + case BC_LEX_OP_PLUS: +#if BC_ENABLE_EXTRA_MATH + case BC_LEX_OP_PLACES: + case BC_LEX_OP_LSHIFT: + case BC_LEX_OP_RSHIFT: +#endif // BC_ENABLE_EXTRA_MATH + case BC_LEX_OP_REL_EQ: + case BC_LEX_OP_REL_LE: + case BC_LEX_OP_REL_GE: + case BC_LEX_OP_REL_NE: + case BC_LEX_OP_REL_LT: + case BC_LEX_OP_REL_GT: + case BC_LEX_OP_BOOL_NOT: + case BC_LEX_OP_BOOL_OR: + case BC_LEX_OP_BOOL_AND: + { + if (BC_PARSE_OP_PREFIX(t)) { + if (BC_ERR(!bin_last && !BC_PARSE_OP_PREFIX(p->l.last))) + bc_parse_err(p, BC_ERROR_PARSE_EXPR); + } + else if (BC_ERR(BC_PARSE_PREV_PREFIX(prev) || bin_last)) + bc_parse_err(p, BC_ERROR_PARSE_EXPR); + + nrelops += (t >= BC_LEX_OP_REL_EQ && t <= BC_LEX_OP_REL_GT); + prev = BC_PARSE_TOKEN_INST(t); + bc_parse_operator(p, t, ops_bgn, &nexprs); + rprn = incdec = can_assign = false; + get_token = true; + bin_last = !BC_PARSE_OP_PREFIX(t); + flags &= ~(BC_PARSE_ARRAY); + + break; + } + + case BC_LEX_LPAREN: + { + if (BC_ERR(BC_PARSE_LEAF(prev, bin_last, rprn))) + bc_parse_err(p, BC_ERROR_PARSE_EXPR); + + nparens += 1; + rprn = incdec = can_assign = false; + get_token = true; + bc_vec_push(&p->ops, &t); + + break; + } + + case BC_LEX_RPAREN: + { + // This needs to be a status. The error + // is handled in bc_parse_expr_status(). + if (BC_ERR(p->l.last == BC_LEX_LPAREN)) + return BC_PARSE_STATUS_EMPTY_EXPR; + + if (BC_ERR(bin_last || BC_PARSE_PREV_PREFIX(prev))) + bc_parse_err(p, BC_ERROR_PARSE_EXPR); + + if (!nparens) { + done = true; + get_token = false; + break; + } + + nparens -= 1; + rprn = true; + get_token = bin_last = incdec = false; + + bc_parse_rightParen(p, &nexprs); + + break; + } + + case BC_LEX_NAME: + { + if (BC_ERR(BC_PARSE_LEAF(prev, bin_last, rprn))) + bc_parse_err(p, BC_ERROR_PARSE_EXPR); + + get_token = bin_last = false; + bc_parse_name(p, &prev, &can_assign, + flags & ~BC_PARSE_NOCALL); + rprn = (prev == BC_INST_CALL); + nexprs += 1; + flags &= ~(BC_PARSE_ARRAY); + + break; + } + + case BC_LEX_NUMBER: + { + if (BC_ERR(BC_PARSE_LEAF(prev, bin_last, rprn))) + bc_parse_err(p, BC_ERROR_PARSE_EXPR); + + bc_parse_number(p); + nexprs += 1; + prev = BC_INST_NUM; + get_token = true; + rprn = bin_last = can_assign = false; + flags &= ~(BC_PARSE_ARRAY); + + break; + } + + case BC_LEX_KW_IBASE: + case BC_LEX_KW_LAST: + case BC_LEX_KW_OBASE: +#if BC_ENABLE_EXTRA_MATH + case BC_LEX_KW_SEED: +#endif // BC_ENABLE_EXTRA_MATH + { + if (BC_ERR(BC_PARSE_LEAF(prev, bin_last, rprn))) + bc_parse_err(p, BC_ERROR_PARSE_EXPR); + + prev = t - BC_LEX_KW_LAST + BC_INST_LAST; + bc_parse_push(p, prev); + + get_token = can_assign = true; + rprn = bin_last = false; + nexprs += 1; + flags &= ~(BC_PARSE_ARRAY); + + break; + } + + case BC_LEX_KW_LENGTH: + case BC_LEX_KW_SQRT: + case BC_LEX_KW_ABS: +#if BC_ENABLE_EXTRA_MATH + case BC_LEX_KW_IRAND: +#endif // BC_ENABLE_EXTRA_MATH + { + if (BC_ERR(BC_PARSE_LEAF(prev, bin_last, rprn))) + bc_parse_err(p, BC_ERROR_PARSE_EXPR); + + bc_parse_builtin(p, t, flags, &prev); + rprn = get_token = bin_last = incdec = can_assign = false; + nexprs += 1; + flags &= ~(BC_PARSE_ARRAY); + + break; + } + + case BC_LEX_KW_READ: +#if BC_ENABLE_EXTRA_MATH + case BC_LEX_KW_RAND: +#endif // BC_ENABLE_EXTRA_MATH + case BC_LEX_KW_MAXIBASE: + case BC_LEX_KW_MAXOBASE: + case BC_LEX_KW_MAXSCALE: +#if BC_ENABLE_EXTRA_MATH + case BC_LEX_KW_MAXRAND: +#endif // BC_ENABLE_EXTRA_MATH + { + if (BC_ERR(BC_PARSE_LEAF(prev, bin_last, rprn))) + bc_parse_err(p, BC_ERROR_PARSE_EXPR); + else if (t == BC_LEX_KW_READ && BC_ERR(flags & BC_PARSE_NOREAD)) + bc_parse_err(p, BC_ERROR_EXEC_REC_READ); + else { + prev = t - BC_LEX_KW_READ + BC_INST_READ; + bc_parse_noArgBuiltin(p, prev); + } + + rprn = get_token = bin_last = incdec = can_assign = false; + nexprs += 1; + flags &= ~(BC_PARSE_ARRAY); + + break; + } + + case BC_LEX_KW_SCALE: + { + if (BC_ERR(BC_PARSE_LEAF(prev, bin_last, rprn))) + bc_parse_err(p, BC_ERROR_PARSE_EXPR); + + bc_parse_scale(p, &prev, &can_assign, flags); + rprn = get_token = bin_last = false; + nexprs += 1; + flags &= ~(BC_PARSE_ARRAY); + + break; + } + + default: + { +#ifndef NDEBUG + bc_parse_err(p, BC_ERROR_PARSE_TOKEN); + break; +#endif // NDEBUG + } + } + + if (get_token) bc_lex_next(&p->l); + } + + while (p->ops.len > ops_bgn) { + + top = BC_PARSE_TOP_OP(p); + assign = top >= BC_LEX_OP_ASSIGN_POWER && top <= BC_LEX_OP_ASSIGN; + + if (BC_ERR(top == BC_LEX_LPAREN || top == BC_LEX_RPAREN)) + bc_parse_err(p, BC_ERROR_PARSE_EXPR); + + bc_parse_push(p, BC_PARSE_TOKEN_INST(top)); + + nexprs -= !BC_PARSE_OP_PREFIX(top); + bc_vec_pop(&p->ops); + } + + if (BC_ERR(nexprs != 1)) bc_parse_err(p, BC_ERROR_PARSE_EXPR); + + for (i = 0; i < next.len && t != next.tokens[i]; ++i); + if (BC_ERR(i == next.len && !bc_parse_isDelimiter(p))) + bc_parse_err(p, BC_ERROR_PARSE_EXPR); + + if (!(flags & BC_PARSE_REL) && nrelops) + bc_parse_err(p, BC_ERROR_POSIX_REL_POS); + else if ((flags & BC_PARSE_REL) && nrelops > 1) + bc_parse_err(p, BC_ERROR_POSIX_MULTIREL); + + if (!(flags & BC_PARSE_NEEDVAL) && !pfirst) { + + if (assign) { + inst = *((uchar*) bc_vec_top(&p->func->code)); + inst += (BC_INST_ASSIGN_POWER_NO_VAL - BC_INST_ASSIGN_POWER); + incdec = false; + } + else if (incdec && !(flags & BC_PARSE_PRINT)) { + inst = *((uchar*) bc_vec_top(&p->func->code)); + incdec = (inst <= BC_INST_DEC); + inst = BC_INST_ASSIGN_PLUS_NO_VAL + (inst != BC_INST_INC && + inst != BC_INST_ASSIGN_PLUS); + } + + if (inst >= BC_INST_ASSIGN_PLUS_NO_VAL && inst <= BC_INST_ASSIGN_NO_VAL) + { + bc_vec_pop(&p->func->code); + if (incdec) bc_parse_push(p, BC_INST_ONE); + bc_parse_push(p, inst); + } + } + + if ((flags & BC_PARSE_PRINT)) { + if (pfirst || !assign) bc_parse_push(p, BC_INST_PRINT); + } + else if (!(flags & BC_PARSE_NEEDVAL) && + (inst < BC_INST_ASSIGN_POWER_NO_VAL || + inst > BC_INST_ASSIGN_NO_VAL)) + { + bc_parse_push(p, BC_INST_POP); + } + + // We want to eat newlines if newlines are not a valid ending token. + // This is for spacing in things like for loop headers. + for (incdec = true, i = 0; i < next.len && incdec; ++i) + incdec = (next.tokens[i] != BC_LEX_NLINE); + if (incdec) { + while (p->l.t == BC_LEX_NLINE) bc_lex_next(&p->l); + } + + return BC_PARSE_STATUS_SUCCESS; +} + +void bc_parse_expr_status(BcParse *p, uint8_t flags, BcParseNext next) { + + BcParseStatus s = bc_parse_expr_err(p, flags, next); + + if (BC_ERR(s == BC_PARSE_STATUS_EMPTY_EXPR)) + bc_parse_err(p, BC_ERROR_PARSE_EMPTY_EXPR); +} + +void bc_parse_expr(BcParse *p, uint8_t flags) { + assert(p); + bc_parse_expr_status(p, flags, bc_parse_next_read); +} +#endif // BC_ENABLED diff --git a/src/data.c b/src/data.c new file mode 100644 index 000000000000..3dae13763470 --- /dev/null +++ b/src/data.c @@ -0,0 +1,995 @@ +/* + * ***************************************************************************** + * + * Copyright (c) 2018-2020 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. + * + * ***************************************************************************** + * + * Constant data for bc. + * + */ + +#include <args.h> +#include <lex.h> +#include <parse.h> +#include <bc.h> +#include <dc.h> +#include <num.h> +#include <rand.h> +#include <program.h> +#include <vm.h> + +#if BC_ENABLED +const char bc_sig_msg[] = "\ninterrupt (type \"quit\" to exit)\n"; +const uchar bc_sig_msg_len = (uchar) (sizeof(bc_sig_msg) - 1); +#endif // BC_ENABLED +#if DC_ENABLED +const char dc_sig_msg[] = "\ninterrupt (type \"q\" to exit)\n"; +const uchar dc_sig_msg_len = (uchar) (sizeof(dc_sig_msg) - 1); +#endif // DC_ENABLED + +const char bc_copyright[] = + "Copyright (c) 2018-2020 Gavin D. Howard and contributors\n" + "Report bugs at: https://git.yzena.com/gavin/bc\n\n" + "This is free software with ABSOLUTELY NO WARRANTY.\n"; + +const char* const bc_err_func_header = "Function:"; +const char* const bc_err_line = ":%zu"; + +const char *bc_errs[] = { + "Math error:", + "Parse error:", + "Runtime error:", + "Fatal error:", +#if BC_ENABLED + "Warning:", +#endif // BC_ENABLED +}; + +const uchar bc_err_ids[] = { + + BC_ERR_IDX_MATH, BC_ERR_IDX_MATH, BC_ERR_IDX_MATH, BC_ERR_IDX_MATH, + + BC_ERR_IDX_FATAL, BC_ERR_IDX_FATAL, BC_ERR_IDX_FATAL, BC_ERR_IDX_FATAL, + BC_ERR_IDX_FATAL, BC_ERR_IDX_FATAL, BC_ERR_IDX_FATAL, BC_ERR_IDX_FATAL, + + BC_ERR_IDX_EXEC, BC_ERR_IDX_EXEC, BC_ERR_IDX_EXEC, BC_ERR_IDX_EXEC, + BC_ERR_IDX_EXEC, BC_ERR_IDX_EXEC, BC_ERR_IDX_EXEC, BC_ERR_IDX_EXEC, + BC_ERR_IDX_EXEC, BC_ERR_IDX_EXEC, + + BC_ERR_IDX_PARSE, BC_ERR_IDX_PARSE, BC_ERR_IDX_PARSE, BC_ERR_IDX_PARSE, + BC_ERR_IDX_PARSE, +#if BC_ENABLED + BC_ERR_IDX_PARSE, BC_ERR_IDX_PARSE, BC_ERR_IDX_PARSE, BC_ERR_IDX_PARSE, + BC_ERR_IDX_PARSE, BC_ERR_IDX_PARSE, BC_ERR_IDX_PARSE, BC_ERR_IDX_PARSE, + BC_ERR_IDX_PARSE, BC_ERR_IDX_PARSE, + + BC_ERR_IDX_PARSE, BC_ERR_IDX_PARSE, BC_ERR_IDX_PARSE, BC_ERR_IDX_PARSE, + BC_ERR_IDX_PARSE, BC_ERR_IDX_PARSE, BC_ERR_IDX_PARSE, BC_ERR_IDX_PARSE, + BC_ERR_IDX_PARSE, BC_ERR_IDX_PARSE, BC_ERR_IDX_PARSE, BC_ERR_IDX_PARSE, + BC_ERR_IDX_PARSE, +#endif // BC_ENABLED + +}; + +const char* const bc_err_msgs[] = { + + "negative number", + "non-integer number", + "overflow: number cannot fit", + "divide by 0", + + "memory allocation failed", + "I/O error", + "cannot open file: %s", + "file is not ASCII: %s", + "path is a directory: %s", + "bad command-line option: \"%s\"", + "option requires an argument: '%c' (\"%s\")", + "option takes no arguments: '%c' (\"%s\")", + + "bad ibase: must be [%lu, %lu]", + "bad obase: must be [%lu, %lu]", + "bad scale: must be [%lu, %lu]", + "bad read() expression", + "read() call inside of a read() call", + "variable or array element is the wrong type", +#if DC_ENABLED + "stack has too few elements", +#else // DC_ENABLED + NULL, +#endif // DC_ENABLED +#if BC_ENABLED + "wrong number of parameters; need %zu, have %zu", + "undefined function: %s()", + "cannot use a void value in an expression", +#else + NULL, NULL, NULL, +#endif // BC_ENABLED + + "end of file", + "bad character '%c'", + "string end cannot be found", + "comment end cannot be found", + "bad token", +#if BC_ENABLED + "bad expression", + "empty expression", + "bad print statement", + "bad function definition", + "bad assignment: left side must be scale, ibase, " + "obase, seed, last, var, or array element", + "no auto variable found", + "function parameter or auto \"%s%s\" already exists", + "block end cannot be found", + "cannot return a value from void function: %s()", + "var cannot be a reference: %s", + + "POSIX does not allow names longer than 1 character: %s", + "POSIX does not allow '#' script comments", + "POSIX does not allow the following keyword: %s", + "POSIX does not allow a period ('.') as a shortcut for the last result", + "POSIX requires parentheses around return expressions", + "POSIX does not allow the following operator: %s", + "POSIX does not allow comparison operators outside if statements or loops", + "POSIX requires 0 or 1 comparison operators per condition", + "POSIX requires all 3 parts of a for loop to be non-empty", +#if BC_ENABLE_EXTRA_MATH + "POSIX does not allow exponential notation", +#else + NULL, +#endif // BC_ENABLE_EXTRA_MATH + "POSIX does not allow array references as function parameters", + "POSIX does not allow void functions", + "POSIX requires the left brace be on the same line as the function header", +#endif // BC_ENABLED + +}; + +#if BC_ENABLE_HISTORY +const char *bc_history_bad_terms[] = { "dumb", "cons25", "emacs", NULL }; + +const char bc_history_tab[] = " "; +const size_t bc_history_tab_len = sizeof(bc_history_tab) - 1; + +// These are listed in ascending order for efficiency. +const uint32_t bc_history_wchars[][2] = { + { 0x1100, 0x115F }, + { 0x231A, 0x231B }, + { 0x2329, 0x232A }, + { 0x23E9, 0x23EC }, + { 0x23F0, 0x23F0 }, + { 0x23F3, 0x23F3 }, + { 0x25FD, 0x25FE }, + { 0x2614, 0x2615 }, + { 0x2648, 0x2653 }, + { 0x267F, 0x267F }, + { 0x2693, 0x2693 }, + { 0x26A1, 0x26A1 }, + { 0x26AA, 0x26AB }, + { 0x26BD, 0x26BE }, + { 0x26C4, 0x26C5 }, + { 0x26CE, 0x26CE }, + { 0x26D4, 0x26D4 }, + { 0x26EA, 0x26EA }, + { 0x26F2, 0x26F3 }, + { 0x26F5, 0x26F5 }, + { 0x26FA, 0x26FA }, + { 0x26FD, 0x26FD }, + { 0x2705, 0x2705 }, + { 0x270A, 0x270B }, + { 0x2728, 0x2728 }, + { 0x274C, 0x274C }, + { 0x274E, 0x274E }, + { 0x2753, 0x2755 }, + { 0x2757, 0x2757 }, + { 0x2795, 0x2797 }, + { 0x27B0, 0x27B0 }, + { 0x27BF, 0x27BF }, + { 0x2B1B, 0x2B1C }, + { 0x2B50, 0x2B50 }, + { 0x2B55, 0x2B55 }, + { 0x2E80, 0x2E99 }, + { 0x2E9B, 0x2EF3 }, + { 0x2F00, 0x2FD5 }, + { 0x2FF0, 0x2FFB }, + { 0x3001, 0x303E }, + { 0x3041, 0x3096 }, + { 0x3099, 0x30FF }, + { 0x3105, 0x312D }, + { 0x3131, 0x318E }, + { 0x3190, 0x31BA }, + { 0x31C0, 0x31E3 }, + { 0x31F0, 0x321E }, + { 0x3220, 0x3247 }, + { 0x3250, 0x32FE }, + { 0x3300, 0x4DBF }, + { 0x4E00, 0xA48C }, + { 0xA490, 0xA4C6 }, + { 0xA960, 0xA97C }, + { 0xAC00, 0xD7A3 }, + { 0xF900, 0xFAFF }, + { 0xFE10, 0xFE19 }, + { 0xFE30, 0xFE52 }, + { 0xFE54, 0xFE66 }, + { 0xFE68, 0xFE6B }, + { 0x16FE0, 0x16FE0 }, + { 0x17000, 0x187EC }, + { 0x18800, 0x18AF2 }, + { 0x1B000, 0x1B001 }, + { 0x1F004, 0x1F004 }, + { 0x1F0CF, 0x1F0CF }, + { 0x1F18E, 0x1F18E }, + { 0x1F191, 0x1F19A }, + { 0x1F200, 0x1F202 }, + { 0x1F210, 0x1F23B }, + { 0x1F240, 0x1F248 }, + { 0x1F250, 0x1F251 }, + { 0x1F300, 0x1F320 }, + { 0x1F32D, 0x1F335 }, + { 0x1F337, 0x1F37C }, + { 0x1F37E, 0x1F393 }, + { 0x1F3A0, 0x1F3CA }, + { 0x1F3CF, 0x1F3D3 }, + { 0x1F3E0, 0x1F3F0 }, + { 0x1F3F4, 0x1F3F4 }, + { 0x1F3F8, 0x1F43E }, + { 0x1F440, 0x1F440 }, + { 0x1F442, 0x1F4FC }, + { 0x1F4FF, 0x1F53D }, + { 0x1F54B, 0x1F54E }, + { 0x1F550, 0x1F567 }, + { 0x1F57A, 0x1F57A }, + { 0x1F595, 0x1F596 }, + { 0x1F5A4, 0x1F5A4 }, + { 0x1F5FB, 0x1F64F }, + { 0x1F680, 0x1F6C5 }, + { 0x1F6CC, 0x1F6CC }, + { 0x1F6D0, 0x1F6D2 }, + { 0x1F6EB, 0x1F6EC }, + { 0x1F6F4, 0x1F6F6 }, + { 0x1F910, 0x1F91E }, + { 0x1F920, 0x1F927 }, + { 0x1F930, 0x1F930 }, + { 0x1F933, 0x1F93E }, + { 0x1F940, 0x1F94B }, + { 0x1F950, 0x1F95E }, + { 0x1F980, 0x1F991 }, + { 0x1F9C0, 0x1F9C0 }, + { 0x20000, 0x2FFFD }, + { 0x30000, 0x3FFFD }, +}; + +const size_t bc_history_wchars_len = + sizeof(bc_history_wchars) / sizeof(bc_history_wchars[0]); + +// These are listed in ascending order for efficiency. +const uint32_t bc_history_combo_chars[] = { + 0x0300,0x0301,0x0302,0x0303,0x0304,0x0305,0x0306,0x0307, + 0x0308,0x0309,0x030A,0x030B,0x030C,0x030D,0x030E,0x030F, + 0x0310,0x0311,0x0312,0x0313,0x0314,0x0315,0x0316,0x0317, + 0x0318,0x0319,0x031A,0x031B,0x031C,0x031D,0x031E,0x031F, + 0x0320,0x0321,0x0322,0x0323,0x0324,0x0325,0x0326,0x0327, + 0x0328,0x0329,0x032A,0x032B,0x032C,0x032D,0x032E,0x032F, + 0x0330,0x0331,0x0332,0x0333,0x0334,0x0335,0x0336,0x0337, + 0x0338,0x0339,0x033A,0x033B,0x033C,0x033D,0x033E,0x033F, + 0x0340,0x0341,0x0342,0x0343,0x0344,0x0345,0x0346,0x0347, + 0x0348,0x0349,0x034A,0x034B,0x034C,0x034D,0x034E,0x034F, + 0x0350,0x0351,0x0352,0x0353,0x0354,0x0355,0x0356,0x0357, + 0x0358,0x0359,0x035A,0x035B,0x035C,0x035D,0x035E,0x035F, + 0x0360,0x0361,0x0362,0x0363,0x0364,0x0365,0x0366,0x0367, + 0x0368,0x0369,0x036A,0x036B,0x036C,0x036D,0x036E,0x036F, + 0x0483,0x0484,0x0485,0x0486,0x0487,0x0591,0x0592,0x0593, + 0x0594,0x0595,0x0596,0x0597,0x0598,0x0599,0x059A,0x059B, + 0x059C,0x059D,0x059E,0x059F,0x05A0,0x05A1,0x05A2,0x05A3, + 0x05A4,0x05A5,0x05A6,0x05A7,0x05A8,0x05A9,0x05AA,0x05AB, + 0x05AC,0x05AD,0x05AE,0x05AF,0x05B0,0x05B1,0x05B2,0x05B3, + 0x05B4,0x05B5,0x05B6,0x05B7,0x05B8,0x05B9,0x05BA,0x05BB, + 0x05BC,0x05BD,0x05BF,0x05C1,0x05C2,0x05C4,0x05C5,0x05C7, + 0x0610,0x0611,0x0612,0x0613,0x0614,0x0615,0x0616,0x0617, + 0x0618,0x0619,0x061A,0x064B,0x064C,0x064D,0x064E,0x064F, + 0x0650,0x0651,0x0652,0x0653,0x0654,0x0655,0x0656,0x0657, + 0x0658,0x0659,0x065A,0x065B,0x065C,0x065D,0x065E,0x065F, + 0x0670,0x06D6,0x06D7,0x06D8,0x06D9,0x06DA,0x06DB,0x06DC, + 0x06DF,0x06E0,0x06E1,0x06E2,0x06E3,0x06E4,0x06E7,0x06E8, + 0x06EA,0x06EB,0x06EC,0x06ED,0x0711,0x0730,0x0731,0x0732, + 0x0733,0x0734,0x0735,0x0736,0x0737,0x0738,0x0739,0x073A, + 0x073B,0x073C,0x073D,0x073E,0x073F,0x0740,0x0741,0x0742, + 0x0743,0x0744,0x0745,0x0746,0x0747,0x0748,0x0749,0x074A, + 0x07A6,0x07A7,0x07A8,0x07A9,0x07AA,0x07AB,0x07AC,0x07AD, + 0x07AE,0x07AF,0x07B0,0x07EB,0x07EC,0x07ED,0x07EE,0x07EF, + 0x07F0,0x07F1,0x07F2,0x07F3,0x0816,0x0817,0x0818,0x0819, + 0x081B,0x081C,0x081D,0x081E,0x081F,0x0820,0x0821,0x0822, + 0x0823,0x0825,0x0826,0x0827,0x0829,0x082A,0x082B,0x082C, + 0x082D,0x0859,0x085A,0x085B,0x08D4,0x08D5,0x08D6,0x08D7, + 0x08D8,0x08D9,0x08DA,0x08DB,0x08DC,0x08DD,0x08DE,0x08DF, + 0x08E0,0x08E1,0x08E3,0x08E4,0x08E5,0x08E6,0x08E7,0x08E8, + 0x08E9,0x08EA,0x08EB,0x08EC,0x08ED,0x08EE,0x08EF,0x08F0, + 0x08F1,0x08F2,0x08F3,0x08F4,0x08F5,0x08F6,0x08F7,0x08F8, + 0x08F9,0x08FA,0x08FB,0x08FC,0x08FD,0x08FE,0x08FF,0x0900, + 0x0901,0x0902,0x093A,0x093C,0x0941,0x0942,0x0943,0x0944, + 0x0945,0x0946,0x0947,0x0948,0x094D,0x0951,0x0952,0x0953, + 0x0954,0x0955,0x0956,0x0957,0x0962,0x0963,0x0981,0x09BC, + 0x09C1,0x09C2,0x09C3,0x09C4,0x09CD,0x09E2,0x09E3,0x0A01, + 0x0A02,0x0A3C,0x0A41,0x0A42,0x0A47,0x0A48,0x0A4B,0x0A4C, + 0x0A4D,0x0A51,0x0A70,0x0A71,0x0A75,0x0A81,0x0A82,0x0ABC, + 0x0AC1,0x0AC2,0x0AC3,0x0AC4,0x0AC5,0x0AC7,0x0AC8,0x0ACD, + 0x0AE2,0x0AE3,0x0B01,0x0B3C,0x0B3F,0x0B41,0x0B42,0x0B43, + 0x0B44,0x0B4D,0x0B56,0x0B62,0x0B63,0x0B82,0x0BC0,0x0BCD, + 0x0C00,0x0C3E,0x0C3F,0x0C40,0x0C46,0x0C47,0x0C48,0x0C4A, + 0x0C4B,0x0C4C,0x0C4D,0x0C55,0x0C56,0x0C62,0x0C63,0x0C81, + 0x0CBC,0x0CBF,0x0CC6,0x0CCC,0x0CCD,0x0CE2,0x0CE3,0x0D01, + 0x0D41,0x0D42,0x0D43,0x0D44,0x0D4D,0x0D62,0x0D63,0x0DCA, + 0x0DD2,0x0DD3,0x0DD4,0x0DD6,0x0E31,0x0E34,0x0E35,0x0E36, + 0x0E37,0x0E38,0x0E39,0x0E3A,0x0E47,0x0E48,0x0E49,0x0E4A, + 0x0E4B,0x0E4C,0x0E4D,0x0E4E,0x0EB1,0x0EB4,0x0EB5,0x0EB6, + 0x0EB7,0x0EB8,0x0EB9,0x0EBB,0x0EBC,0x0EC8,0x0EC9,0x0ECA, + 0x0ECB,0x0ECC,0x0ECD,0x0F18,0x0F19,0x0F35,0x0F37,0x0F39, + 0x0F71,0x0F72,0x0F73,0x0F74,0x0F75,0x0F76,0x0F77,0x0F78, + 0x0F79,0x0F7A,0x0F7B,0x0F7C,0x0F7D,0x0F7E,0x0F80,0x0F81, + 0x0F82,0x0F83,0x0F84,0x0F86,0x0F87,0x0F8D,0x0F8E,0x0F8F, + 0x0F90,0x0F91,0x0F92,0x0F93,0x0F94,0x0F95,0x0F96,0x0F97, + 0x0F99,0x0F9A,0x0F9B,0x0F9C,0x0F9D,0x0F9E,0x0F9F,0x0FA0, + 0x0FA1,0x0FA2,0x0FA3,0x0FA4,0x0FA5,0x0FA6,0x0FA7,0x0FA8, + 0x0FA9,0x0FAA,0x0FAB,0x0FAC,0x0FAD,0x0FAE,0x0FAF,0x0FB0, + 0x0FB1,0x0FB2,0x0FB3,0x0FB4,0x0FB5,0x0FB6,0x0FB7,0x0FB8, + 0x0FB9,0x0FBA,0x0FBB,0x0FBC,0x0FC6,0x102D,0x102E,0x102F, + 0x1030,0x1032,0x1033,0x1034,0x1035,0x1036,0x1037,0x1039, + 0x103A,0x103D,0x103E,0x1058,0x1059,0x105E,0x105F,0x1060, + 0x1071,0x1072,0x1073,0x1074,0x1082,0x1085,0x1086,0x108D, + 0x109D,0x135D,0x135E,0x135F,0x1712,0x1713,0x1714,0x1732, + 0x1733,0x1734,0x1752,0x1753,0x1772,0x1773,0x17B4,0x17B5, + 0x17B7,0x17B8,0x17B9,0x17BA,0x17BB,0x17BC,0x17BD,0x17C6, + 0x17C9,0x17CA,0x17CB,0x17CC,0x17CD,0x17CE,0x17CF,0x17D0, + 0x17D1,0x17D2,0x17D3,0x17DD,0x180B,0x180C,0x180D,0x1885, + 0x1886,0x18A9,0x1920,0x1921,0x1922,0x1927,0x1928,0x1932, + 0x1939,0x193A,0x193B,0x1A17,0x1A18,0x1A1B,0x1A56,0x1A58, + 0x1A59,0x1A5A,0x1A5B,0x1A5C,0x1A5D,0x1A5E,0x1A60,0x1A62, + 0x1A65,0x1A66,0x1A67,0x1A68,0x1A69,0x1A6A,0x1A6B,0x1A6C, + 0x1A73,0x1A74,0x1A75,0x1A76,0x1A77,0x1A78,0x1A79,0x1A7A, + 0x1A7B,0x1A7C,0x1A7F,0x1AB0,0x1AB1,0x1AB2,0x1AB3,0x1AB4, + 0x1AB5,0x1AB6,0x1AB7,0x1AB8,0x1AB9,0x1ABA,0x1ABB,0x1ABC, + 0x1ABD,0x1B00,0x1B01,0x1B02,0x1B03,0x1B34,0x1B36,0x1B37, + 0x1B38,0x1B39,0x1B3A,0x1B3C,0x1B42,0x1B6B,0x1B6C,0x1B6D, + 0x1B6E,0x1B6F,0x1B70,0x1B71,0x1B72,0x1B73,0x1B80,0x1B81, + 0x1BA2,0x1BA3,0x1BA4,0x1BA5,0x1BA8,0x1BA9,0x1BAB,0x1BAC, + 0x1BAD,0x1BE6,0x1BE8,0x1BE9,0x1BED,0x1BEF,0x1BF0,0x1BF1, + 0x1C2C,0x1C2D,0x1C2E,0x1C2F,0x1C30,0x1C31,0x1C32,0x1C33, + 0x1C36,0x1C37,0x1CD0,0x1CD1,0x1CD2,0x1CD4,0x1CD5,0x1CD6, + 0x1CD7,0x1CD8,0x1CD9,0x1CDA,0x1CDB,0x1CDC,0x1CDD,0x1CDE, + 0x1CDF,0x1CE0,0x1CE2,0x1CE3,0x1CE4,0x1CE5,0x1CE6,0x1CE7, + 0x1CE8,0x1CED,0x1CF4,0x1CF8,0x1CF9,0x1DC0,0x1DC1,0x1DC2, + 0x1DC3,0x1DC4,0x1DC5,0x1DC6,0x1DC7,0x1DC8,0x1DC9,0x1DCA, + 0x1DCB,0x1DCC,0x1DCD,0x1DCE,0x1DCF,0x1DD0,0x1DD1,0x1DD2, + 0x1DD3,0x1DD4,0x1DD5,0x1DD6,0x1DD7,0x1DD8,0x1DD9,0x1DDA, + 0x1DDB,0x1DDC,0x1DDD,0x1DDE,0x1DDF,0x1DE0,0x1DE1,0x1DE2, + 0x1DE3,0x1DE4,0x1DE5,0x1DE6,0x1DE7,0x1DE8,0x1DE9,0x1DEA, + 0x1DEB,0x1DEC,0x1DED,0x1DEE,0x1DEF,0x1DF0,0x1DF1,0x1DF2, + 0x1DF3,0x1DF4,0x1DF5,0x1DFB,0x1DFC,0x1DFD,0x1DFE,0x1DFF, + 0x20D0,0x20D1,0x20D2,0x20D3,0x20D4,0x20D5,0x20D6,0x20D7, + 0x20D8,0x20D9,0x20DA,0x20DB,0x20DC,0x20E1,0x20E5,0x20E6, + 0x20E7,0x20E8,0x20E9,0x20EA,0x20EB,0x20EC,0x20ED,0x20EE, + 0x20EF,0x20F0,0x2CEF,0x2CF0,0x2CF1,0x2D7F,0x2DE0,0x2DE1, + 0x2DE2,0x2DE3,0x2DE4,0x2DE5,0x2DE6,0x2DE7,0x2DE8,0x2DE9, + 0x2DEA,0x2DEB,0x2DEC,0x2DED,0x2DEE,0x2DEF,0x2DF0,0x2DF1, + 0x2DF2,0x2DF3,0x2DF4,0x2DF5,0x2DF6,0x2DF7,0x2DF8,0x2DF9, + 0x2DFA,0x2DFB,0x2DFC,0x2DFD,0x2DFE,0x2DFF,0x302A,0x302B, + 0x302C,0x302D,0x3099,0x309A,0xA66F,0xA674,0xA675,0xA676, + 0xA677,0xA678,0xA679,0xA67A,0xA67B,0xA67C,0xA67D,0xA69E, + 0xA69F,0xA6F0,0xA6F1,0xA802,0xA806,0xA80B,0xA825,0xA826, + 0xA8C4,0xA8C5,0xA8E0,0xA8E1,0xA8E2,0xA8E3,0xA8E4,0xA8E5, + 0xA8E6,0xA8E7,0xA8E8,0xA8E9,0xA8EA,0xA8EB,0xA8EC,0xA8ED, + 0xA8EE,0xA8EF,0xA8F0,0xA8F1,0xA926,0xA927,0xA928,0xA929, + 0xA92A,0xA92B,0xA92C,0xA92D,0xA947,0xA948,0xA949,0xA94A, + 0xA94B,0xA94C,0xA94D,0xA94E,0xA94F,0xA950,0xA951,0xA980, + 0xA981,0xA982,0xA9B3,0xA9B6,0xA9B7,0xA9B8,0xA9B9,0xA9BC, + 0xA9E5,0xAA29,0xAA2A,0xAA2B,0xAA2C,0xAA2D,0xAA2E,0xAA31, + 0xAA32,0xAA35,0xAA36,0xAA43,0xAA4C,0xAA7C,0xAAB0,0xAAB2, + 0xAAB3,0xAAB4,0xAAB7,0xAAB8,0xAABE,0xAABF,0xAAC1,0xAAEC, + 0xAAED,0xAAF6,0xABE5,0xABE8,0xABED,0xFB1E,0xFE00,0xFE01, + 0xFE02,0xFE03,0xFE04,0xFE05,0xFE06,0xFE07,0xFE08,0xFE09, + 0xFE0A,0xFE0B,0xFE0C,0xFE0D,0xFE0E,0xFE0F,0xFE20,0xFE21, + 0xFE22,0xFE23,0xFE24,0xFE25,0xFE26,0xFE27,0xFE28,0xFE29, + 0xFE2A,0xFE2B,0xFE2C,0xFE2D,0xFE2E,0xFE2F, + 0x101FD,0x102E0,0x10376,0x10377,0x10378,0x10379,0x1037A,0x10A01, + 0x10A02,0x10A03,0x10A05,0x10A06,0x10A0C,0x10A0D,0x10A0E,0x10A0F, + 0x10A38,0x10A39,0x10A3A,0x10A3F,0x10AE5,0x10AE6,0x11001,0x11038, + 0x11039,0x1103A,0x1103B,0x1103C,0x1103D,0x1103E,0x1103F,0x11040, + 0x11041,0x11042,0x11043,0x11044,0x11045,0x11046,0x1107F,0x11080, + 0x11081,0x110B3,0x110B4,0x110B5,0x110B6,0x110B9,0x110BA,0x11100, + 0x11101,0x11102,0x11127,0x11128,0x11129,0x1112A,0x1112B,0x1112D, + 0x1112E,0x1112F,0x11130,0x11131,0x11132,0x11133,0x11134,0x11173, + 0x11180,0x11181,0x111B6,0x111B7,0x111B8,0x111B9,0x111BA,0x111BB, + 0x111BC,0x111BD,0x111BE,0x111CA,0x111CB,0x111CC,0x1122F,0x11230, + 0x11231,0x11234,0x11236,0x11237,0x1123E,0x112DF,0x112E3,0x112E4, + 0x112E5,0x112E6,0x112E7,0x112E8,0x112E9,0x112EA,0x11300,0x11301, + 0x1133C,0x11340,0x11366,0x11367,0x11368,0x11369,0x1136A,0x1136B, + 0x1136C,0x11370,0x11371,0x11372,0x11373,0x11374,0x11438,0x11439, + 0x1143A,0x1143B,0x1143C,0x1143D,0x1143E,0x1143F,0x11442,0x11443, + 0x11444,0x11446,0x114B3,0x114B4,0x114B5,0x114B6,0x114B7,0x114B8, + 0x114BA,0x114BF,0x114C0,0x114C2,0x114C3,0x115B2,0x115B3,0x115B4, + 0x115B5,0x115BC,0x115BD,0x115BF,0x115C0,0x115DC,0x115DD,0x11633, + 0x11634,0x11635,0x11636,0x11637,0x11638,0x11639,0x1163A,0x1163D, + 0x1163F,0x11640,0x116AB,0x116AD,0x116B0,0x116B1,0x116B2,0x116B3, + 0x116B4,0x116B5,0x116B7,0x1171D,0x1171E,0x1171F,0x11722,0x11723, + 0x11724,0x11725,0x11727,0x11728,0x11729,0x1172A,0x1172B,0x11C30, + 0x11C31,0x11C32,0x11C33,0x11C34,0x11C35,0x11C36,0x11C38,0x11C39, + 0x11C3A,0x11C3B,0x11C3C,0x11C3D,0x11C3F,0x11C92,0x11C93,0x11C94, + 0x11C95,0x11C96,0x11C97,0x11C98,0x11C99,0x11C9A,0x11C9B,0x11C9C, + 0x11C9D,0x11C9E,0x11C9F,0x11CA0,0x11CA1,0x11CA2,0x11CA3,0x11CA4, + 0x11CA5,0x11CA6,0x11CA7,0x11CAA,0x11CAB,0x11CAC,0x11CAD,0x11CAE, + 0x11CAF,0x11CB0,0x11CB2,0x11CB3,0x11CB5,0x11CB6,0x16AF0,0x16AF1, + 0x16AF2,0x16AF3,0x16AF4,0x16B30,0x16B31,0x16B32,0x16B33,0x16B34, + 0x16B35,0x16B36,0x16F8F,0x16F90,0x16F91,0x16F92,0x1BC9D,0x1BC9E, + 0x1D167,0x1D168,0x1D169,0x1D17B,0x1D17C,0x1D17D,0x1D17E,0x1D17F, + 0x1D180,0x1D181,0x1D182,0x1D185,0x1D186,0x1D187,0x1D188,0x1D189, + 0x1D18A,0x1D18B,0x1D1AA,0x1D1AB,0x1D1AC,0x1D1AD,0x1D242,0x1D243, + 0x1D244,0x1DA00,0x1DA01,0x1DA02,0x1DA03,0x1DA04,0x1DA05,0x1DA06, + 0x1DA07,0x1DA08,0x1DA09,0x1DA0A,0x1DA0B,0x1DA0C,0x1DA0D,0x1DA0E, + 0x1DA0F,0x1DA10,0x1DA11,0x1DA12,0x1DA13,0x1DA14,0x1DA15,0x1DA16, + 0x1DA17,0x1DA18,0x1DA19,0x1DA1A,0x1DA1B,0x1DA1C,0x1DA1D,0x1DA1E, + 0x1DA1F,0x1DA20,0x1DA21,0x1DA22,0x1DA23,0x1DA24,0x1DA25,0x1DA26, + 0x1DA27,0x1DA28,0x1DA29,0x1DA2A,0x1DA2B,0x1DA2C,0x1DA2D,0x1DA2E, + 0x1DA2F,0x1DA30,0x1DA31,0x1DA32,0x1DA33,0x1DA34,0x1DA35,0x1DA36, + 0x1DA3B,0x1DA3C,0x1DA3D,0x1DA3E,0x1DA3F,0x1DA40,0x1DA41,0x1DA42, + 0x1DA43,0x1DA44,0x1DA45,0x1DA46,0x1DA47,0x1DA48,0x1DA49,0x1DA4A, + 0x1DA4B,0x1DA4C,0x1DA4D,0x1DA4E,0x1DA4F,0x1DA50,0x1DA51,0x1DA52, + 0x1DA53,0x1DA54,0x1DA55,0x1DA56,0x1DA57,0x1DA58,0x1DA59,0x1DA5A, + 0x1DA5B,0x1DA5C,0x1DA5D,0x1DA5E,0x1DA5F,0x1DA60,0x1DA61,0x1DA62, + 0x1DA63,0x1DA64,0x1DA65,0x1DA66,0x1DA67,0x1DA68,0x1DA69,0x1DA6A, + 0x1DA6B,0x1DA6C,0x1DA75,0x1DA84,0x1DA9B,0x1DA9C,0x1DA9D,0x1DA9E, + 0x1DA9F,0x1DAA1,0x1DAA2,0x1DAA3,0x1DAA4,0x1DAA5,0x1DAA6,0x1DAA7, + 0x1DAA8,0x1DAA9,0x1DAAA,0x1DAAB,0x1DAAC,0x1DAAD,0x1DAAE,0x1DAAF, + 0x1E000,0x1E001,0x1E002,0x1E003,0x1E004,0x1E005,0x1E006,0x1E008, + 0x1E009,0x1E00A,0x1E00B,0x1E00C,0x1E00D,0x1E00E,0x1E00F,0x1E010, + 0x1E011,0x1E012,0x1E013,0x1E014,0x1E015,0x1E016,0x1E017,0x1E018, + 0x1E01B,0x1E01C,0x1E01D,0x1E01E,0x1E01F,0x1E020,0x1E021,0x1E023, + 0x1E024,0x1E026,0x1E027,0x1E028,0x1E029,0x1E02A,0x1E8D0,0x1E8D1, + 0x1E8D2,0x1E8D3,0x1E8D4,0x1E8D5,0x1E8D6,0x1E944,0x1E945,0x1E946, + 0x1E947,0x1E948,0x1E949,0x1E94A,0xE0100,0xE0101,0xE0102,0xE0103, + 0xE0104,0xE0105,0xE0106,0xE0107,0xE0108,0xE0109,0xE010A,0xE010B, + 0xE010C,0xE010D,0xE010E,0xE010F,0xE0110,0xE0111,0xE0112,0xE0113, + 0xE0114,0xE0115,0xE0116,0xE0117,0xE0118,0xE0119,0xE011A,0xE011B, + 0xE011C,0xE011D,0xE011E,0xE011F,0xE0120,0xE0121,0xE0122,0xE0123, + 0xE0124,0xE0125,0xE0126,0xE0127,0xE0128,0xE0129,0xE012A,0xE012B, + 0xE012C,0xE012D,0xE012E,0xE012F,0xE0130,0xE0131,0xE0132,0xE0133, + 0xE0134,0xE0135,0xE0136,0xE0137,0xE0138,0xE0139,0xE013A,0xE013B, + 0xE013C,0xE013D,0xE013E,0xE013F,0xE0140,0xE0141,0xE0142,0xE0143, + 0xE0144,0xE0145,0xE0146,0xE0147,0xE0148,0xE0149,0xE014A,0xE014B, + 0xE014C,0xE014D,0xE014E,0xE014F,0xE0150,0xE0151,0xE0152,0xE0153, + 0xE0154,0xE0155,0xE0156,0xE0157,0xE0158,0xE0159,0xE015A,0xE015B, + 0xE015C,0xE015D,0xE015E,0xE015F,0xE0160,0xE0161,0xE0162,0xE0163, + 0xE0164,0xE0165,0xE0166,0xE0167,0xE0168,0xE0169,0xE016A,0xE016B, + 0xE016C,0xE016D,0xE016E,0xE016F,0xE0170,0xE0171,0xE0172,0xE0173, + 0xE0174,0xE0175,0xE0176,0xE0177,0xE0178,0xE0179,0xE017A,0xE017B, + 0xE017C,0xE017D,0xE017E,0xE017F,0xE0180,0xE0181,0xE0182,0xE0183, + 0xE0184,0xE0185,0xE0186,0xE0187,0xE0188,0xE0189,0xE018A,0xE018B, + 0xE018C,0xE018D,0xE018E,0xE018F,0xE0190,0xE0191,0xE0192,0xE0193, + 0xE0194,0xE0195,0xE0196,0xE0197,0xE0198,0xE0199,0xE019A,0xE019B, + 0xE019C,0xE019D,0xE019E,0xE019F,0xE01A0,0xE01A1,0xE01A2,0xE01A3, + 0xE01A4,0xE01A5,0xE01A6,0xE01A7,0xE01A8,0xE01A9,0xE01AA,0xE01AB, + 0xE01AC,0xE01AD,0xE01AE,0xE01AF,0xE01B0,0xE01B1,0xE01B2,0xE01B3, + 0xE01B4,0xE01B5,0xE01B6,0xE01B7,0xE01B8,0xE01B9,0xE01BA,0xE01BB, + 0xE01BC,0xE01BD,0xE01BE,0xE01BF,0xE01C0,0xE01C1,0xE01C2,0xE01C3, + 0xE01C4,0xE01C5,0xE01C6,0xE01C7,0xE01C8,0xE01C9,0xE01CA,0xE01CB, + 0xE01CC,0xE01CD,0xE01CE,0xE01CF,0xE01D0,0xE01D1,0xE01D2,0xE01D3, + 0xE01D4,0xE01D5,0xE01D6,0xE01D7,0xE01D8,0xE01D9,0xE01DA,0xE01DB, + 0xE01DC,0xE01DD,0xE01DE,0xE01DF,0xE01E0,0xE01E1,0xE01E2,0xE01E3, + 0xE01E4,0xE01E5,0xE01E6,0xE01E7,0xE01E8,0xE01E9,0xE01EA,0xE01EB, + 0xE01EC,0xE01ED,0xE01EE,0xE01EF, +}; + +const size_t bc_history_combo_chars_len = + sizeof(bc_history_combo_chars) / sizeof(bc_history_combo_chars[0]); + +#if BC_DEBUG_CODE +BcFile bc_history_debug_fp; +char *bc_history_debug_buf; +#endif // BC_DEBUG_CODE +#endif // BC_ENABLE_HISTORY + +const char bc_func_main[] = "(main)"; +const char bc_func_read[] = "(read)"; + +#if BC_DEBUG_CODE +const char* bc_inst_names[] = { + +#if BC_ENABLED + "BC_INST_INC", + "BC_INST_DEC", +#endif // BC_ENABLED + + "BC_INST_NEG", + "BC_INST_BOOL_NOT", +#if BC_ENABLE_EXTRA_MATH + "BC_INST_TRUNC", +#endif // BC_ENABLE_EXTRA_MATH + + "BC_INST_POWER", + "BC_INST_MULTIPLY", + "BC_INST_DIVIDE", + "BC_INST_MODULUS", + "BC_INST_PLUS", + "BC_INST_MINUS", + +#if BC_ENABLE_EXTRA_MATH + "BC_INST_PLACES", + + "BC_INST_LSHIFT", + "BC_INST_RSHIFT", +#endif // BC_ENABLE_EXTRA_MATH + + "BC_INST_REL_EQ", + "BC_INST_REL_LE", + "BC_INST_REL_GE", + "BC_INST_REL_NE", + "BC_INST_REL_LT", + "BC_INST_REL_GT", + + "BC_INST_BOOL_OR", + "BC_INST_BOOL_AND", + +#if BC_ENABLED + "BC_INST_ASSIGN_POWER", + "BC_INST_ASSIGN_MULTIPLY", + "BC_INST_ASSIGN_DIVIDE", + "BC_INST_ASSIGN_MODULUS", + "BC_INST_ASSIGN_PLUS", + "BC_INST_ASSIGN_MINUS", +#if BC_ENABLE_EXTRA_MATH + "BC_INST_ASSIGN_PLACES", + "BC_INST_ASSIGN_LSHIFT", + "BC_INST_ASSIGN_RSHIFT", +#endif // BC_ENABLE_EXTRA_MATH + "BC_INST_ASSIGN", + + "BC_INST_ASSIGN_POWER_NO_VAL", + "BC_INST_ASSIGN_MULTIPLY_NO_VAL", + "BC_INST_ASSIGN_DIVIDE_NO_VAL", + "BC_INST_ASSIGN_MODULUS_NO_VAL", + "BC_INST_ASSIGN_PLUS_NO_VAL", + "BC_INST_ASSIGN_MINUS_NO_VAL", +#if BC_ENABLE_EXTRA_MATH + "BC_INST_ASSIGN_PLACES_NO_VAL", + "BC_INST_ASSIGN_LSHIFT_NO_VAL", + "BC_INST_ASSIGN_RSHIFT_NO_VAL", +#endif // BC_ENABLE_EXTRA_MATH +#endif // BC_ENABLED + "BC_INST_ASSIGN_NO_VAL", + + "BC_INST_NUM", + "BC_INST_VAR", + "BC_INST_ARRAY_ELEM", +#if BC_ENABLED + "BC_INST_ARRAY", +#endif // BC_ENABLED + + "BC_INST_ONE", + +#if BC_ENABLED + "BC_INST_LAST", +#endif // BC_ENABLED + "BC_INST_IBASE", + "BC_INST_OBASE", + "BC_INST_SCALE", +#if BC_ENABLE_EXTRA_MATH + "BC_INST_SEED", +#endif // BC_ENABLE_EXTRA_MATH + "BC_INST_LENGTH", + "BC_INST_SCALE_FUNC", + "BC_INST_SQRT", + "BC_INST_ABS", +#if BC_ENABLE_EXTRA_MATH + "BC_INST_IRAND", +#endif // BC_ENABLE_EXTRA_MATH + "BC_INST_READ", +#if BC_ENABLE_EXTRA_MATH + "BC_INST_RAND", +#endif // BC_ENABLE_EXTRA_MATH + "BC_INST_MAXIBASE", + "BC_INST_MAXOBASE", + "BC_INST_MAXSCALE", +#if BC_ENABLE_EXTRA_MATH + "BC_INST_MAXRAND", +#endif // BC_ENABLE_EXTRA_MATH + + "BC_INST_PRINT", + "BC_INST_PRINT_POP", + "BC_INST_STR", + "BC_INST_PRINT_STR", + +#if BC_ENABLED + "BC_INST_JUMP", + "BC_INST_JUMP_ZERO", + + "BC_INST_CALL", + + "BC_INST_RET", + "BC_INST_RET0", + "BC_INST_RET_VOID", + + "BC_INST_HALT", +#endif // BC_ENABLED + +#if DC_ENABLED + "BC_INST_POP", + "BC_INST_POP_EXEC", + "BC_INST_MODEXP", + "BC_INST_DIVMOD", + + "BC_INST_EXECUTE", + "BC_INST_EXEC_COND", + + "BC_INST_ASCIIFY", + "BC_INST_PRINT_STREAM", + + "BC_INST_PRINT_STACK", + "BC_INST_CLEAR_STACK", + "BC_INST_STACK_LEN", + "BC_INST_DUPLICATE", + "BC_INST_SWAP", + + "BC_INST_LOAD", + "BC_INST_PUSH_VAR", + "BC_INST_PUSH_TO_VAR", + + "BC_INST_QUIT", + "BC_INST_NQUIT", +#endif // DC_ENABLED +}; +#endif // BC_DEBUG_CODE + +#if BC_ENABLE_EXTRA_MATH + +const BcRandState bc_rand_multiplier = BC_RAND_MULTIPLIER; + +#endif // BC_ENABLE_EXTRA_MATH + +#if BC_ENABLED +const BcLexKeyword bc_lex_kws[] = { + BC_LEX_KW_ENTRY("auto", 4, true), + BC_LEX_KW_ENTRY("break", 5, true), + BC_LEX_KW_ENTRY("continue", 8, false), + BC_LEX_KW_ENTRY("define", 6, true), + BC_LEX_KW_ENTRY("for", 3, true), + BC_LEX_KW_ENTRY("if", 2, true), + BC_LEX_KW_ENTRY("limits", 6, false), + BC_LEX_KW_ENTRY("return", 6, true), + BC_LEX_KW_ENTRY("while", 5, true), + BC_LEX_KW_ENTRY("halt", 4, false), + BC_LEX_KW_ENTRY("last", 4, false), + BC_LEX_KW_ENTRY("ibase", 5, true), + BC_LEX_KW_ENTRY("obase", 5, true), + BC_LEX_KW_ENTRY("scale", 5, true), +#if BC_ENABLE_EXTRA_MATH + BC_LEX_KW_ENTRY("seed", 4, false), +#endif // BC_ENABLE_EXTRA_MATH + BC_LEX_KW_ENTRY("length", 6, true), + BC_LEX_KW_ENTRY("print", 5, false), + BC_LEX_KW_ENTRY("sqrt", 4, true), + BC_LEX_KW_ENTRY("abs", 3, false), +#if BC_ENABLE_EXTRA_MATH + BC_LEX_KW_ENTRY("irand", 5, false), +#endif // BC_ENABLE_EXTRA_MATH + BC_LEX_KW_ENTRY("quit", 4, true), + BC_LEX_KW_ENTRY("read", 4, false), +#if BC_ENABLE_EXTRA_MATH + BC_LEX_KW_ENTRY("rand", 4, false), +#endif // BC_ENABLE_EXTRA_MATH + BC_LEX_KW_ENTRY("maxibase", 8, false), + BC_LEX_KW_ENTRY("maxobase", 8, false), + BC_LEX_KW_ENTRY("maxscale", 8, false), +#if BC_ENABLE_EXTRA_MATH + BC_LEX_KW_ENTRY("maxrand", 7, false), +#endif // BC_ENABLE_EXTRA_MATH + BC_LEX_KW_ENTRY("else", 4, false), +}; + +const size_t bc_lex_kws_len = sizeof(bc_lex_kws) / sizeof(BcLexKeyword); + +const char* const bc_parse_const1 = "1"; + +// This is an array that corresponds to token types. An entry is +// true if the token is valid in an expression, false otherwise. +const uint8_t bc_parse_exprs[] = { + BC_PARSE_EXPR_ENTRY(false, false, true, true, true, true, true, true), + BC_PARSE_EXPR_ENTRY(true, true, true, true, true, true, true, true), + BC_PARSE_EXPR_ENTRY(true, true, true, true, true, true, true, true), +#if BC_ENABLE_EXTRA_MATH + BC_PARSE_EXPR_ENTRY(true, true, true, true, true, true, true, true), + BC_PARSE_EXPR_ENTRY(true, true, false, false, true, true, false, false), + BC_PARSE_EXPR_ENTRY(false, false, false, false, false, true, true, false), + BC_PARSE_EXPR_ENTRY(false, false, false, false, false, false, false, false), + BC_PARSE_EXPR_ENTRY(false, true, true, true, true, true, true, false), + BC_PARSE_EXPR_ENTRY(true, true, true, false, true, true, true, true), + BC_PARSE_EXPR_ENTRY(true, true, false, 0, 0, 0, 0, 0) +#else // BC_ENABLE_EXTRA_MATH + BC_PARSE_EXPR_ENTRY(true, true, true, false, false, true, true, false), + BC_PARSE_EXPR_ENTRY(false, false, false, false, false, false, true, true), + BC_PARSE_EXPR_ENTRY(false, false, false, false, false, false, false, false), + BC_PARSE_EXPR_ENTRY(false, false, true, true, true, true, true, false), + BC_PARSE_EXPR_ENTRY(true, true, false, true, true, true, true, false) +#endif // BC_ENABLE_EXTRA_MATH +}; + +// This is an array of data for operators that correspond to token types. +const uchar bc_parse_ops[] = { + BC_PARSE_OP(0, false), BC_PARSE_OP(0, false), + BC_PARSE_OP(1, false), BC_PARSE_OP(1, false), +#if BC_ENABLE_EXTRA_MATH + BC_PARSE_OP(2, false), +#endif // BC_ENABLE_EXTRA_MATH + BC_PARSE_OP(4, false), + BC_PARSE_OP(5, true), BC_PARSE_OP(5, true), BC_PARSE_OP(5, true), + BC_PARSE_OP(6, true), BC_PARSE_OP(6, true), +#if BC_ENABLE_EXTRA_MATH + BC_PARSE_OP(3, false), + BC_PARSE_OP(7, true), BC_PARSE_OP(7, true), +#endif // BC_ENABLE_EXTRA_MATH + BC_PARSE_OP(9, true), BC_PARSE_OP(9, true), BC_PARSE_OP(9, true), + BC_PARSE_OP(9, true), BC_PARSE_OP(9, true), BC_PARSE_OP(9, true), + BC_PARSE_OP(11, true), BC_PARSE_OP(10, true), + BC_PARSE_OP(8, false), BC_PARSE_OP(8, false), BC_PARSE_OP(8, false), + BC_PARSE_OP(8, false), BC_PARSE_OP(8, false), BC_PARSE_OP(8, false), +#if BC_ENABLE_EXTRA_MATH + BC_PARSE_OP(8, false), BC_PARSE_OP(8, false), BC_PARSE_OP(8, false), +#endif // BC_ENABLE_EXTRA_MATH + BC_PARSE_OP(8, false), +}; + +// These identify what tokens can come after expressions in certain cases. +const BcParseNext bc_parse_next_expr = + BC_PARSE_NEXT(4, BC_LEX_NLINE, BC_LEX_SCOLON, BC_LEX_RBRACE, BC_LEX_EOF); +const BcParseNext bc_parse_next_param = + BC_PARSE_NEXT(2, BC_LEX_RPAREN, BC_LEX_COMMA); +const BcParseNext bc_parse_next_print = + BC_PARSE_NEXT(4, BC_LEX_COMMA, BC_LEX_NLINE, BC_LEX_SCOLON, BC_LEX_EOF); +const BcParseNext bc_parse_next_rel = BC_PARSE_NEXT(1, BC_LEX_RPAREN); +const BcParseNext bc_parse_next_elem = BC_PARSE_NEXT(1, BC_LEX_RBRACKET); +const BcParseNext bc_parse_next_for = BC_PARSE_NEXT(1, BC_LEX_SCOLON); +const BcParseNext bc_parse_next_read = + BC_PARSE_NEXT(2, BC_LEX_NLINE, BC_LEX_EOF); +#endif // BC_ENABLED + +#if DC_ENABLED +const uint8_t dc_lex_regs[] = { + BC_LEX_OP_REL_EQ, BC_LEX_OP_REL_LE, BC_LEX_OP_REL_GE, BC_LEX_OP_REL_NE, + BC_LEX_OP_REL_LT, BC_LEX_OP_REL_GT, BC_LEX_SCOLON, BC_LEX_COLON, + BC_LEX_KW_ELSE, BC_LEX_LOAD, BC_LEX_LOAD_POP, BC_LEX_OP_ASSIGN, + BC_LEX_STORE_PUSH, +}; + +const size_t dc_lex_regs_len = sizeof(dc_lex_regs) / sizeof(uint8_t); + +const uchar dc_lex_tokens[] = { +#if BC_ENABLE_EXTRA_MATH && DC_ENABLE_RAND + BC_LEX_KW_IRAND, +#else // BC_ENABLE_EXTRA_MATH && DC_ENABLE_RAND + BC_LEX_INVALID, +#endif // BC_ENABLE_EXTRA_MATH && DC_ENABLE_RAND + BC_LEX_INVALID, +#if BC_ENABLE_EXTRA_MATH + BC_LEX_OP_TRUNC, +#else // BC_ENABLE_EXTRA_MATH + BC_LEX_INVALID, +#endif // BC_ENABLE_EXTRA_MATH + BC_LEX_OP_MODULUS, BC_LEX_INVALID, +#if BC_ENABLE_EXTRA_MATH && DC_ENABLE_RAND + BC_LEX_KW_RAND, +#else // BC_ENABLE_EXTRA_MATH && DC_ENABLE_RAND + BC_LEX_INVALID, +#endif // BC_ENABLE_EXTRA_MATH && DC_ENABLE_RAND + BC_LEX_LPAREN, BC_LEX_RPAREN, BC_LEX_OP_MULTIPLY, BC_LEX_OP_PLUS, + BC_LEX_INVALID, BC_LEX_OP_MINUS, BC_LEX_INVALID, BC_LEX_OP_DIVIDE, + BC_LEX_INVALID, BC_LEX_INVALID, BC_LEX_INVALID, BC_LEX_INVALID, + BC_LEX_INVALID, BC_LEX_INVALID, BC_LEX_INVALID, BC_LEX_INVALID, + BC_LEX_INVALID, BC_LEX_INVALID, + BC_LEX_COLON, BC_LEX_SCOLON, BC_LEX_OP_REL_GT, BC_LEX_OP_REL_EQ, + BC_LEX_OP_REL_LT, BC_LEX_KW_READ, +#if BC_ENABLE_EXTRA_MATH + BC_LEX_OP_PLACES, +#else // BC_ENABLE_EXTRA_MATH + BC_LEX_INVALID, +#endif // BC_ENABLE_EXTRA_MATH + BC_LEX_INVALID, BC_LEX_INVALID, BC_LEX_INVALID, BC_LEX_INVALID, + BC_LEX_INVALID, BC_LEX_INVALID, BC_LEX_EQ_NO_REG, +#if BC_ENABLE_EXTRA_MATH + BC_LEX_OP_LSHIFT, +#else // BC_ENABLE_EXTRA_MATH + BC_LEX_INVALID, +#endif // BC_ENABLE_EXTRA_MATH + BC_LEX_KW_IBASE, +#if BC_ENABLE_EXTRA_MATH && DC_ENABLE_RAND + BC_LEX_KW_SEED, +#else // BC_ENABLE_EXTRA_MATH && DC_ENABLE_RAND + BC_LEX_INVALID, +#endif // BC_ENABLE_EXTRA_MATH && DC_ENABLE_RAND + BC_LEX_KW_SCALE, BC_LEX_LOAD_POP, BC_LEX_OP_BOOL_AND, BC_LEX_OP_BOOL_NOT, + BC_LEX_KW_OBASE, BC_LEX_PRINT_STREAM, BC_LEX_NQUIT, BC_LEX_POP, + BC_LEX_STORE_PUSH, BC_LEX_KW_MAXIBASE, BC_LEX_KW_MAXOBASE, + BC_LEX_KW_MAXSCALE, +#if BC_ENABLE_EXTRA_MATH && DC_ENABLE_RAND + BC_LEX_KW_MAXRAND, +#else // BC_ENABLE_EXTRA_MATH && DC_ENABLE_RAND + BC_LEX_INVALID, +#endif // BC_ENABLE_EXTRA_MATH && DC_ENABLE_RAND + BC_LEX_SCALE_FACTOR, + BC_LEX_INVALID, BC_LEX_KW_LENGTH, BC_LEX_INVALID, BC_LEX_INVALID, + BC_LEX_INVALID, BC_LEX_OP_POWER, BC_LEX_NEG, BC_LEX_INVALID, + BC_LEX_ASCIIFY, BC_LEX_KW_ABS, BC_LEX_CLEAR_STACK, BC_LEX_DUPLICATE, + BC_LEX_KW_ELSE, BC_LEX_PRINT_STACK, BC_LEX_INVALID, +#if BC_ENABLE_EXTRA_MATH + BC_LEX_OP_RSHIFT, +#else // BC_ENABLE_EXTRA_MATH + BC_LEX_INVALID, +#endif // BC_ENABLE_EXTRA_MATH + BC_LEX_STORE_IBASE, +#if BC_ENABLE_EXTRA_MATH && DC_ENABLE_RAND + BC_LEX_STORE_SEED, +#else // BC_ENABLE_EXTRA_MATH && DC_ENABLE_RAND + BC_LEX_INVALID, +#endif // BC_ENABLE_EXTRA_MATH && DC_ENABLE_RAND + BC_LEX_STORE_SCALE, BC_LEX_LOAD, + BC_LEX_OP_BOOL_OR, BC_LEX_PRINT_POP, BC_LEX_STORE_OBASE, BC_LEX_KW_PRINT, + BC_LEX_KW_QUIT, BC_LEX_SWAP, BC_LEX_OP_ASSIGN, BC_LEX_INVALID, + BC_LEX_INVALID, BC_LEX_KW_SQRT, BC_LEX_INVALID, BC_LEX_EXECUTE, + BC_LEX_INVALID, BC_LEX_STACK_LEVEL, + BC_LEX_LBRACE, BC_LEX_OP_MODEXP, BC_LEX_RBRACE, BC_LEX_OP_DIVMOD, + BC_LEX_INVALID +}; + +const uchar dc_parse_insts[] = { + BC_INST_INVALID, BC_INST_INVALID, +#if BC_ENABLED + BC_INST_INVALID, BC_INST_INVALID, +#endif // BC_ENABLED + BC_INST_INVALID, BC_INST_BOOL_NOT, +#if BC_ENABLE_EXTRA_MATH + BC_INST_TRUNC, +#endif // BC_ENABLE_EXTRA_MATH + BC_INST_POWER, BC_INST_MULTIPLY, BC_INST_DIVIDE, BC_INST_MODULUS, + BC_INST_PLUS, BC_INST_MINUS, +#if BC_ENABLE_EXTRA_MATH + BC_INST_PLACES, + BC_INST_LSHIFT, BC_INST_RSHIFT, +#endif // BC_ENABLE_EXTRA_MATH + BC_INST_INVALID, BC_INST_INVALID, BC_INST_INVALID, BC_INST_INVALID, + BC_INST_INVALID, BC_INST_INVALID, + BC_INST_BOOL_OR, BC_INST_BOOL_AND, +#if BC_ENABLED + BC_INST_INVALID, BC_INST_INVALID, BC_INST_INVALID, BC_INST_INVALID, + BC_INST_INVALID, BC_INST_INVALID, +#if BC_ENABLE_EXTRA_MATH + BC_INST_INVALID, BC_INST_INVALID, BC_INST_INVALID, +#endif // BC_ENABLE_EXTRA_MATH +#endif // BC_ENABLED + BC_INST_INVALID, + BC_INST_INVALID, BC_INST_INVALID, BC_INST_REL_GT, BC_INST_REL_LT, + BC_INST_INVALID, BC_INST_INVALID, BC_INST_INVALID, BC_INST_REL_GE, + BC_INST_INVALID, BC_INST_REL_LE, + BC_INST_INVALID, BC_INST_INVALID, BC_INST_INVALID, +#if BC_ENABLED + BC_INST_INVALID, BC_INST_INVALID, BC_INST_INVALID, BC_INST_INVALID, + BC_INST_INVALID, BC_INST_INVALID, BC_INST_INVALID, BC_INST_INVALID, + BC_INST_INVALID, BC_INST_INVALID, BC_INST_INVALID, +#endif // BC_ENABLED + BC_INST_IBASE, BC_INST_OBASE, BC_INST_SCALE, +#if BC_ENABLE_EXTRA_MATH + BC_INST_SEED, +#endif // BC_ENABLE_EXTRA_MATH + BC_INST_LENGTH, BC_INST_PRINT, + BC_INST_SQRT, BC_INST_ABS, +#if BC_ENABLE_EXTRA_MATH + BC_INST_IRAND, +#endif // BC_ENABLE_EXTRA_MATH + BC_INST_QUIT, BC_INST_INVALID, +#if BC_ENABLE_EXTRA_MATH + BC_INST_RAND, +#endif // BC_ENABLE_EXTRA_MATH + BC_INST_MAXIBASE, + BC_INST_MAXOBASE, BC_INST_MAXSCALE, +#if BC_ENABLE_EXTRA_MATH + BC_INST_MAXRAND, +#endif // BC_ENABLE_EXTRA_MATH + BC_INST_INVALID, + BC_INST_REL_EQ, BC_INST_MODEXP, BC_INST_DIVMOD, BC_INST_INVALID, + BC_INST_EXECUTE, BC_INST_PRINT_STACK, BC_INST_CLEAR_STACK, + BC_INST_STACK_LEN, BC_INST_DUPLICATE, BC_INST_SWAP, BC_INST_POP, + BC_INST_ASCIIFY, BC_INST_PRINT_STREAM, + BC_INST_INVALID, BC_INST_INVALID, BC_INST_INVALID, +#if BC_ENABLE_EXTRA_MATH + BC_INST_INVALID, +#endif // BC_ENABLE_EXTRA_MATH + BC_INST_INVALID, BC_INST_INVALID, BC_INST_INVALID, + BC_INST_PRINT_POP, BC_INST_NQUIT, BC_INST_SCALE_FUNC, +}; +#endif // DC_ENABLED + +#if BC_LONG_BIT >= 64 +const BcDig bc_num_bigdigMax[] = { + 709551616U, + 446744073U, + 18U +}; +#else // BC_LONG_BIT >= 64 +const BcDig bc_num_bigdigMax[] = { + 7296U, + 9496U, + 42U, +}; +#endif // BC_LONG_BIT >= 64 + +const size_t bc_num_bigdigMax_size = sizeof(bc_num_bigdigMax) / sizeof(BcDig); + +const char bc_parse_one[] = "1"; + +const char bc_num_hex_digits[] = "0123456789ABCDEF"; + +const BcBigDig bc_num_pow10[BC_BASE_DIGS + 1] = { + 1, + 10, + 100, + 1000, + 10000, +#if BC_BASE_DIGS > 4 + 100000, + 1000000, + 10000000, + 100000000, + 1000000000, +#endif // BC_BASE_DIGS > 4 +}; + +const BcNumBinaryOp bc_program_ops[] = { + bc_num_pow, bc_num_mul, bc_num_div, bc_num_mod, bc_num_add, bc_num_sub, +#if BC_ENABLE_EXTRA_MATH + bc_num_places, bc_num_lshift, bc_num_rshift, +#endif // BC_ENABLE_EXTRA_MATH +}; + +const BcNumBinaryOpReq bc_program_opReqs[] = { + bc_num_powReq, bc_num_mulReq, bc_num_mulReq, bc_num_mulReq, + bc_num_addReq, bc_num_addReq, +#if BC_ENABLE_EXTRA_MATH + bc_num_placesReq, bc_num_placesReq, bc_num_placesReq, +#endif // BC_ENABLE_EXTRA_MATH +}; + +const BcProgramUnary bc_program_unarys[] = { + bc_program_negate, bc_program_not, +#if BC_ENABLE_EXTRA_MATH + bc_program_trunc, +#endif // BC_ENABLE_EXTRA_MATH +}; + +const char bc_program_exprs_name[] = "<exprs>"; + +const char bc_program_stdin_name[] = "<stdin>"; +const char bc_program_ready_msg[] = "ready for more input\n"; +const size_t bc_program_ready_msg_len = sizeof(bc_program_ready_msg) - 1; +const char bc_program_esc_chars[] = "ab\\efnqrt"; +const char bc_program_esc_seqs[] = "\a\b\\\\\f\n\"\r\t"; diff --git a/src/dc/dc.c b/src/dc/dc.c new file mode 100644 index 000000000000..0e0774b5322a --- /dev/null +++ b/src/dc/dc.c @@ -0,0 +1,57 @@ +/* + * ***************************************************************************** + * + * Copyright (c) 2018-2020 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. + * + * ***************************************************************************** + * + * The main procedure of dc. + * + */ + +#if DC_ENABLED + +#include <string.h> + +#include <status.h> +#include <dc.h> +#include <vm.h> + +void dc_main(int argc, char **argv) { + + vm.read_ret = BC_INST_POP_EXEC; + vm.help = dc_help; + vm.sigmsg = dc_sig_msg; + vm.siglen = dc_sig_msg_len; + + vm.next = dc_lex_token; + vm.parse = dc_parse_parse; + vm.expr = dc_parse_expr; + + bc_vm_boot(argc, argv, "DC_LINE_LENGTH", "DC_ENV_ARGS", "DC_EXPR_EXIT"); +} +#endif // DC_ENABLED diff --git a/src/dc/lex.c b/src/dc/lex.c new file mode 100644 index 000000000000..663a828dfe7c --- /dev/null +++ b/src/dc/lex.c @@ -0,0 +1,202 @@ +/* + * ***************************************************************************** + * + * Copyright (c) 2018-2020 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. + * + * ***************************************************************************** + * + * The lexer for dc. + * + */ + +#if DC_ENABLED + +#include <ctype.h> + +#include <status.h> +#include <lex.h> +#include <dc.h> +#include <vm.h> + +bool dc_lex_negCommand(BcLex *l) { + char c = l->buf[l->i]; + return !BC_LEX_NUM_CHAR(c, false, false); +} + +static void dc_lex_register(BcLex *l) { + + if (DC_X && isspace(l->buf[l->i - 1])) { + + char c; + + bc_lex_whitespace(l); + c = l->buf[l->i]; + + if (!isalnum(c) && c != '_') + bc_lex_verr(l, BC_ERROR_PARSE_CHAR, c); + + l->i += 1; + bc_lex_name(l); + } + else { + bc_vec_npop(&l->str, l->str.len); + bc_vec_pushByte(&l->str, (uchar) l->buf[l->i - 1]); + bc_vec_pushByte(&l->str, '\0'); + l->t = BC_LEX_NAME; + } +} + +static void dc_lex_string(BcLex *l) { + + size_t depth = 1, nls = 0, i = l->i; + char c; + + l->t = BC_LEX_STR; + bc_vec_npop(&l->str, l->str.len); + + for (; (c = l->buf[i]) && depth; ++i) { + + if (c == '\\') { + c = l->buf[++i]; + if (!c) break; + } + else { + depth += (c == '['); + depth -= (c == ']'); + } + + nls += (c == '\n'); + + if (depth) bc_vec_push(&l->str, &c); + } + + if (BC_ERR(c == '\0' && depth)) { + l->i = i; + bc_lex_err(l, BC_ERROR_PARSE_STRING); + } + + bc_vec_pushByte(&l->str, '\0'); + + l->i = i; + l->line += nls; +} + +void dc_lex_token(BcLex *l) { + + char c = l->buf[l->i++], c2; + size_t i; + + for (i = 0; i < dc_lex_regs_len; ++i) { + if (l->last == dc_lex_regs[i]) { + dc_lex_register(l); + return; + } + } + + if (c >= '"' && c <= '~' && + (l->t = dc_lex_tokens[(c - '"')]) != BC_LEX_INVALID) + { + return; + } + + // This is the workhorse of the lexer. + switch (c) { + + case '\0': + case '\n': + case '\t': + case '\v': + case '\f': + case '\r': + case ' ': + { + bc_lex_commonTokens(l, c); + break; + } + + case '!': + { + c2 = l->buf[l->i]; + + if (c2 == '=') l->t = BC_LEX_OP_REL_NE; + else if (c2 == '<') l->t = BC_LEX_OP_REL_LE; + else if (c2 == '>') l->t = BC_LEX_OP_REL_GE; + else bc_lex_invalidChar(l, c); + + l->i += 1; + break; + } + + case '#': + { + bc_lex_lineComment(l); + break; + } + + case '.': + { + c2 = l->buf[l->i]; + if (BC_NO_ERR(BC_LEX_NUM_CHAR(c2, true, false))) + bc_lex_number(l, c); + else bc_lex_invalidChar(l, c); + break; + } + + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + case 'A': + case 'B': + case 'C': + case 'D': + case 'E': + case 'F': + { + bc_lex_number(l, c); + break; + } + + case '[': + { + dc_lex_string(l); + break; + } + + default: + { + bc_lex_invalidChar(l, c); + } + } +} +#endif // DC_ENABLED diff --git a/src/dc/parse.c b/src/dc/parse.c new file mode 100644 index 000000000000..9ec746b96f7f --- /dev/null +++ b/src/dc/parse.c @@ -0,0 +1,236 @@ +/* + * ***************************************************************************** + * + * Copyright (c) 2018-2020 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. + * + * ***************************************************************************** + * + * The parser for dc. + * + */ + +#if DC_ENABLED + +#include <assert.h> +#include <stdlib.h> +#include <string.h> +#include <setjmp.h> + +#include <status.h> +#include <parse.h> +#include <dc.h> +#include <program.h> +#include <vm.h> + +static void dc_parse_register(BcParse *p, bool var) { + + bc_lex_next(&p->l); + if (p->l.t != BC_LEX_NAME) bc_parse_err(p, BC_ERROR_PARSE_TOKEN); + + bc_parse_pushName(p, p->l.str.v, var); +} + +static inline void dc_parse_string(BcParse *p) { + bc_parse_addString(p); + bc_lex_next(&p->l); +} + +static void dc_parse_mem(BcParse *p, uchar inst, bool name, bool store) { + + bc_parse_push(p, inst); + + if (name) dc_parse_register(p, inst != BC_INST_ARRAY_ELEM); + + if (store) { + bc_parse_push(p, BC_INST_SWAP); + bc_parse_push(p, BC_INST_ASSIGN_NO_VAL); + } + + bc_lex_next(&p->l); +} + +static void dc_parse_cond(BcParse *p, uchar inst) { + + bc_parse_push(p, inst); + bc_parse_push(p, BC_INST_EXEC_COND); + + dc_parse_register(p, true); + + bc_lex_next(&p->l); + + if (p->l.t == BC_LEX_KW_ELSE) { + dc_parse_register(p, true); + bc_lex_next(&p->l); + } + else bc_parse_pushIndex(p, SIZE_MAX); +} + +static void dc_parse_token(BcParse *p, BcLexType t, uint8_t flags) { + + uchar inst; + bool assign, get_token = false; + + switch (t) { + + case BC_LEX_OP_REL_EQ: + case BC_LEX_OP_REL_LE: + case BC_LEX_OP_REL_GE: + case BC_LEX_OP_REL_NE: + case BC_LEX_OP_REL_LT: + case BC_LEX_OP_REL_GT: + { + inst = (uchar) (t - BC_LEX_OP_REL_EQ + BC_INST_REL_EQ); + dc_parse_cond(p, inst); + break; + } + + case BC_LEX_SCOLON: + case BC_LEX_COLON: + { + dc_parse_mem(p, BC_INST_ARRAY_ELEM, true, t == BC_LEX_COLON); + break; + } + + case BC_LEX_STR: + { + dc_parse_string(p); + break; + } + + case BC_LEX_NEG: + { + if (dc_lex_negCommand(&p->l)) { + bc_parse_push(p, BC_INST_NEG); + get_token = true; + break; + } + + bc_lex_next(&p->l); + } + // Fallthrough. + case BC_LEX_NUMBER: + { + bc_parse_number(p); + + if (t == BC_LEX_NEG) bc_parse_push(p, BC_INST_NEG); + get_token = true; + + break; + } + + case BC_LEX_KW_READ: + { + if (BC_ERR(flags & BC_PARSE_NOREAD)) + bc_parse_err(p, BC_ERROR_EXEC_REC_READ); + else bc_parse_push(p, BC_INST_READ); + get_token = true; + break; + } + + case BC_LEX_OP_ASSIGN: + case BC_LEX_STORE_PUSH: + { + assign = t == BC_LEX_OP_ASSIGN; + inst = assign ? BC_INST_VAR : BC_INST_PUSH_TO_VAR; + dc_parse_mem(p, inst, true, assign); + break; + } + + case BC_LEX_LOAD: + case BC_LEX_LOAD_POP: + { + inst = t == BC_LEX_LOAD_POP ? BC_INST_PUSH_VAR : BC_INST_LOAD; + dc_parse_mem(p, inst, true, false); + break; + } + + case BC_LEX_STORE_IBASE: + case BC_LEX_STORE_OBASE: + case BC_LEX_STORE_SCALE: +#if BC_ENABLE_EXTRA_MATH + case BC_LEX_STORE_SEED: +#endif // BC_ENABLE_EXTRA_MATH + { + inst = (uchar) (t - BC_LEX_STORE_IBASE + BC_INST_IBASE); + dc_parse_mem(p, inst, false, true); + break; + } + + default: + { + bc_parse_err(p, BC_ERROR_PARSE_TOKEN); + } + } + + if (get_token) bc_lex_next(&p->l); +} + +void dc_parse_expr(BcParse *p, uint8_t flags) { + + BcInst inst; + BcLexType t; + bool have_expr = false, need_expr = (flags & BC_PARSE_NOREAD) != 0; + + while ((t = p->l.t) != BC_LEX_EOF) { + + if (t == BC_LEX_NLINE) { + bc_lex_next(&p->l); + continue; + } + + inst = dc_parse_insts[t]; + + if (inst != BC_INST_INVALID) { + bc_parse_push(p, inst); + bc_lex_next(&p->l); + } + else dc_parse_token(p, t, flags); + + have_expr = true; + } + + if (BC_ERR(need_expr && !have_expr)) + bc_vm_err(BC_ERROR_EXEC_READ_EXPR); + else if (p->l.t == BC_LEX_EOF && (flags & BC_PARSE_NOCALL)) + bc_parse_push(p, BC_INST_POP_EXEC); +} + +void dc_parse_parse(BcParse *p) { + + assert(p != NULL); + + BC_SETJMP(exit); + + if (BC_ERR(p->l.t == BC_LEX_EOF)) bc_parse_err(p, BC_ERROR_PARSE_EOF); + else dc_parse_expr(p, 0); + +exit: + BC_SIG_MAYLOCK; + if (BC_ERR(vm.status)) bc_parse_reset(p); + BC_LONGJMP_CONT; +} +#endif // DC_ENABLED diff --git a/src/file.c b/src/file.c new file mode 100644 index 000000000000..ce878a018ca5 --- /dev/null +++ b/src/file.c @@ -0,0 +1,225 @@ +/* + * ***************************************************************************** + * + * Copyright (c) 2018-2020 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 for implementing buffered I/O on my own terms. + * + */ + +#include <assert.h> +#include <errno.h> +#include <string.h> +#include <unistd.h> + +#include <file.h> +#include <vm.h> + +void bc_file_ultoa(unsigned long long val, char buf[BC_FILE_ULL_LENGTH]) { + + char buf2[BC_FILE_ULL_LENGTH]; + size_t i, len; + + memset(buf2, 0, BC_FILE_ULL_LENGTH); + + // The i = 1 is to ensure that there is a null byte at the end. + for (i = 1; val; ++i) { + unsigned long long mod = val % 10; + buf2[i] = ((char) mod) + '0'; + val /= 10; + } + + len = i; + + for (i = 0; i < len; ++i) buf[i] = buf2[len - i - 1]; +} + +static BcStatus bc_file_output(int fd, const char *buf, size_t n) { + + size_t bytes = 0; + sig_atomic_t lock; + + BC_SIG_TRYLOCK(lock); + + while (bytes < n) { + + ssize_t written = write(fd, buf + bytes, n - bytes); + + if (BC_ERR(written == -1)) + return errno == EPIPE ? BC_STATUS_EOF : BC_STATUS_ERROR_FATAL; + + bytes += (size_t) written; + } + + BC_SIG_TRYUNLOCK(lock); + + return BC_STATUS_SUCCESS; +} + +BcStatus bc_file_flushErr(BcFile *restrict f) { + + BcStatus s; + + if (f->len) { + s = bc_file_output(f->fd, f->buf, f->len); + f->len = 0; + } + else s = BC_STATUS_SUCCESS; + + return s; +} + +void bc_file_flush(BcFile *restrict f) { + + BcStatus s = bc_file_flushErr(f); + + if (BC_ERR(s)) { + + if (s == BC_STATUS_EOF) { + vm.status = (sig_atomic_t) s; + BC_VM_JMP; + } + else bc_vm_err(BC_ERROR_FATAL_IO_ERR); + } +} + +void bc_file_write(BcFile *restrict f, const char *buf, size_t n) { + + if (n > f->cap - f->len) { + bc_file_flush(f); + assert(!f->len); + } + + if (BC_UNLIKELY(n > f->cap - f->len)) bc_file_output(f->fd, buf, n); + else { + memcpy(f->buf + f->len, buf, n); + f->len += n; + } +} + +void bc_file_printf(BcFile *restrict f, const char *fmt, ...) { + + va_list args; + + va_start(args, fmt); + bc_file_vprintf(f, fmt, args); + va_end(args); +} + +void bc_file_vprintf(BcFile *restrict f, const char *fmt, va_list args) { + + char *percent; + const char *ptr = fmt; + char buf[BC_FILE_ULL_LENGTH]; + + while ((percent = strchr(ptr, '%')) != NULL) { + + char c; + + if (percent != ptr) { + size_t len = (size_t) (percent - ptr); + bc_file_write(f, ptr, len); + } + + c = percent[1]; + + if (c == 'c') { + + uchar uc = (uchar) va_arg(args, int); + + bc_file_putchar(f, uc); + } + else if (c == 's') { + + char *s = va_arg(args, char*); + + bc_file_puts(f, s); + } +#if BC_DEBUG_CODE + else if (c == 'd') { + + int d = va_arg(args, int); + + if (d < 0) { + bc_file_putchar(f, '-'); + d = -d; + } + + if (!d) bc_file_putchar(f, '0'); + else { + bc_file_ultoa((unsigned long long) d, buf); + bc_file_puts(f, buf); + } + } +#endif // BC_DEBUG_CODE + else { + + unsigned long long ull; + + assert((c == 'l' || c == 'z') && percent[2] == 'u'); + + if (c == 'z') ull = (unsigned long long) va_arg(args, size_t); + else ull = (unsigned long long) va_arg(args, unsigned long); + + if (!ull) bc_file_putchar(f, '0'); + else { + bc_file_ultoa(ull, buf); + bc_file_puts(f, buf); + } + } + + ptr = percent + 2 + (c == 'l' || c == 'z'); + } + + if (ptr[0]) bc_file_puts(f, ptr); +} + +void bc_file_puts(BcFile *restrict f, const char *str) { + bc_file_write(f, str, strlen(str)); +} + +void bc_file_putchar(BcFile *restrict f, uchar c) { + if (f->len == f->cap) bc_file_flush(f); + assert(f->len < f->cap); + f->buf[f->len] = (char) c; + f->len += 1; +} + +void bc_file_init(BcFile *f, int fd, char *buf, size_t cap) { + BC_SIG_ASSERT_LOCKED; + f->fd = fd; + f->buf = buf; + f->len = 0; + f->cap = cap; +} + +void bc_file_free(BcFile *f) { + BC_SIG_ASSERT_LOCKED; + bc_file_flush(f); +} diff --git a/src/history/history.c b/src/history/history.c new file mode 100644 index 000000000000..ad917e65ba08 --- /dev/null +++ b/src/history/history.c @@ -0,0 +1,1450 @@ +/* + * ***************************************************************************** + * + * Copyright (c) 2018-2020 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. + * + * ***************************************************************************** + * + * Adapted from the following: + * + * linenoise.c -- guerrilla line editing library against the idea that a + * line editing lib needs to be 20,000 lines of C code. + * + * You can find the original source code at: + * http://github.com/antirez/linenoise + * + * You can find the fork that this code is based on at: + * https://github.com/rain-1/linenoise-mob + * + * ------------------------------------------------------------------------ + * + * This code is also under the following license: + * + * Copyright (c) 2010-2016, Salvatore Sanfilippo <antirez at gmail dot com> + * Copyright (c) 2010-2013, Pieter Noordhuis <pcnoordhuis at gmail dot com> + * + * 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. + * + * ------------------------------------------------------------------------ + * + * Does a number of crazy assumptions that happen to be true in 99.9999% of + * the 2010 UNIX computers around. + * + * References: + * - http://invisible-island.net/xterm/ctlseqs/ctlseqs.html + * - http://www.3waylabs.com/nw/WWW/products/wizcon/vt220.html + * + * Todo list: + * - Filter bogus Ctrl+<char> combinations. + * - Win32 support + * + * Bloat: + * - History search like Ctrl+r in readline? + * + * List of escape sequences used by this program, we do everything just + * with three sequences. In order to be so cheap we may have some + * flickering effect with some slow terminal, but the lesser sequences + * the more compatible. + * + * EL (Erase Line) + * Sequence: ESC [ n K + * Effect: if n is 0 or missing, clear from cursor to end of line + * Effect: if n is 1, clear from beginning of line to cursor + * Effect: if n is 2, clear entire line + * + * CUF (CUrsor Forward) + * Sequence: ESC [ n C + * Effect: moves cursor forward n chars + * + * CUB (CUrsor Backward) + * Sequence: ESC [ n D + * Effect: moves cursor backward n chars + * + * The following is used to get the terminal width if getting + * the width with the TIOCGWINSZ ioctl fails + * + * DSR (Device Status Report) + * Sequence: ESC [ 6 n + * Effect: reports the current cusor position as ESC [ n ; m R + * where n is the row and m is the column + * + * When multi line mode is enabled, we also use two additional escape + * sequences. However multi line editing is disabled by default. + * + * CUU (CUrsor Up) + * Sequence: ESC [ n A + * Effect: moves cursor up of n chars. + * + * CUD (CUrsor Down) + * Sequence: ESC [ n B + * Effect: moves cursor down of n chars. + * + * When bc_history_clearScreen() is called, two additional escape sequences + * are used in order to clear the screen and position the cursor at home + * position. + * + * CUP (CUrsor Position) + * Sequence: ESC [ H + * Effect: moves the cursor to upper left corner + * + * ED (Erase Display) + * Sequence: ESC [ 2 J + * Effect: clear the whole screen + * + * ***************************************************************************** + * + * Code for line history. + * + */ + +#if BC_ENABLE_HISTORY + +#include <assert.h> +#include <stdlib.h> +#include <errno.h> +#include <string.h> +#include <strings.h> +#include <ctype.h> + +#include <signal.h> + +#include <termios.h> +#include <unistd.h> +#include <sys/stat.h> +#include <sys/types.h> +#include <sys/ioctl.h> +#include <sys/select.h> + +#include <vector.h> +#include <history.h> +#include <read.h> +#include <file.h> +#include <vm.h> + +static void bc_history_add(BcHistory *h, char *line); +static void bc_history_add_empty(BcHistory *h); + +/** + * Check if the code is a wide character. + */ +static bool bc_history_wchar(uint32_t cp) { + + size_t i; + + for (i = 0; i < bc_history_wchars_len; ++i) { + + // Ranges are listed in ascending order. Therefore, once the + // whole range is higher than the codepoint we're testing, the + // codepoint won't be found in any remaining range => bail early. + if (bc_history_wchars[i][0] > cp) return false; + + // Test this range. + if (bc_history_wchars[i][0] <= cp && cp <= bc_history_wchars[i][1]) + return true; + } + + return false; +} + +/** + * Check if the code is a combining character. + */ +static bool bc_history_comboChar(uint32_t cp) { + + size_t i; + + for (i = 0; i < bc_history_combo_chars_len; ++i) { + + // Combining chars are listed in ascending order, so once we pass + // the codepoint of interest, we know it's not a combining char. + if (bc_history_combo_chars[i] > cp) return false; + if (bc_history_combo_chars[i] == cp) return true; + } + + return false; +} + +/** + * Get length of previous UTF8 character. + */ +static size_t bc_history_prevCharLen(const char *buf, size_t pos) { + size_t end = pos; + for (pos -= 1; pos < end && (buf[pos] & 0xC0) == 0x80; --pos); + return end - (pos >= end ? 0 : pos); +} + +/** + * Convert UTF-8 to Unicode code point. + */ +static size_t bc_history_codePoint(const char *s, size_t len, uint32_t *cp) { + + if (len) { + + uchar byte = (uchar) s[0]; + + if ((byte & 0x80) == 0) { + *cp = byte; + return 1; + } + else if ((byte & 0xE0) == 0xC0) { + + if (len >= 2) { + *cp = (((uint32_t) (s[0] & 0x1F)) << 6) | + ((uint32_t) (s[1] & 0x3F)); + return 2; + } + } + else if ((byte & 0xF0) == 0xE0) { + + if (len >= 3) { + *cp = (((uint32_t) (s[0] & 0x0F)) << 12) | + (((uint32_t) (s[1] & 0x3F)) << 6) | + ((uint32_t) (s[2] & 0x3F)); + return 3; + } + } + else if ((byte & 0xF8) == 0xF0) { + + if (len >= 4) { + *cp = (((uint32_t) (s[0] & 0x07)) << 18) | + (((uint32_t) (s[1] & 0x3F)) << 12) | + (((uint32_t) (s[2] & 0x3F)) << 6) | + ((uint32_t) (s[3] & 0x3F)); + return 4; + } + } + else { + *cp = 0xFFFD; + return 1; + } + } + + *cp = 0; + + return 1; +} + +/** + * Get length of next grapheme. + */ +static size_t bc_history_nextLen(const char *buf, size_t buf_len, + size_t pos, size_t *col_len) +{ + uint32_t cp; + size_t beg = pos; + size_t len = bc_history_codePoint(buf + pos, buf_len - pos, &cp); + + if (bc_history_comboChar(cp)) { + // Currently unreachable? + return 0; + } + + if (col_len != NULL) *col_len = bc_history_wchar(cp) ? 2 : 1; + + pos += len; + + while (pos < buf_len) { + + len = bc_history_codePoint(buf + pos, buf_len - pos, &cp); + + if (!bc_history_comboChar(cp)) return pos - beg; + + pos += len; + } + + return pos - beg; +} + +/** + * Get length of previous grapheme. + */ +static size_t bc_history_prevLen(const char *buf, size_t pos, size_t *col_len) { + + size_t end = pos; + + while (pos > 0) { + + uint32_t cp; + size_t len = bc_history_prevCharLen(buf, pos); + + pos -= len; + bc_history_codePoint(buf + pos, len, &cp); + + if (!bc_history_comboChar(cp)) { + if (col_len != NULL) *col_len = 1 + (bc_history_wchar(cp) != 0); + return end - pos; + } + } + + // Currently unreachable? + return 0; +} + +static ssize_t bc_history_read(char *buf, size_t n) { + + ssize_t ret; + + BC_SIG_LOCK; + + do { + ret = read(STDIN_FILENO, buf, n); + } while (ret == EINTR); + + BC_SIG_UNLOCK; + + return ret; +} + +/** + * Read a Unicode code point from a file. + */ +static BcStatus bc_history_readCode(char *buf, size_t buf_len, + uint32_t *cp, size_t *nread) +{ + ssize_t n; + + assert(buf_len >= 1); + + n = bc_history_read(buf, 1); + if (BC_ERR(n <= 0)) goto err; + + uchar byte = (uchar) buf[0]; + + if ((byte & 0x80) != 0) { + + if ((byte & 0xE0) == 0xC0) { + assert(buf_len >= 2); + n = bc_history_read(buf + 1, 1); + if (BC_ERR(n <= 0)) goto err; + } + else if ((byte & 0xF0) == 0xE0) { + assert(buf_len >= 3); + n = bc_history_read(buf + 1, 2); + if (BC_ERR(n <= 0)) goto err; + } + else if ((byte & 0xF8) == 0xF0) { + assert(buf_len >= 3); + n = bc_history_read(buf + 1, 3); + if (BC_ERR(n <= 0)) goto err; + } + else { + n = -1; + goto err; + } + } + + *nread = bc_history_codePoint(buf, buf_len, cp); + + return BC_STATUS_SUCCESS; + +err: + if (BC_ERR(n < 0)) bc_vm_err(BC_ERROR_FATAL_IO_ERR); + else *nread = (size_t) n; + return BC_STATUS_EOF; +} + +/** + * Get column length from begining of buffer to current byte position. + */ +static size_t bc_history_colPos(const char *buf, size_t buf_len, size_t pos) { + + size_t ret = 0, off = 0; + + while (off < pos) { + + size_t col_len, len; + + len = bc_history_nextLen(buf, buf_len, off, &col_len); + + off += len; + ret += col_len; + } + + return ret; +} + +/** + * Return true if the terminal name is in the list of terminals we know are + * not able to understand basic escape sequences. + */ +static inline bool bc_history_isBadTerm(void) { + + size_t i; + char *term = getenv("TERM"); + + if (term == NULL) return false; + + for (i = 0; bc_history_bad_terms[i]; ++i) { + if (!strcasecmp(term, bc_history_bad_terms[i])) return true; + } + + return false; +} + +/** + * Raw mode: 1960's black magic. + */ +static void bc_history_enableRaw(BcHistory *h) { + + struct termios raw; + int err; + + assert(BC_TTYIN); + + if (h->rawMode) return; + + BC_SIG_LOCK; + + if (BC_ERR(tcgetattr(STDIN_FILENO, &h->orig_termios) == -1)) + bc_vm_err(BC_ERROR_FATAL_IO_ERR); + + BC_SIG_UNLOCK; + + // Modify the original mode. + raw = h->orig_termios; + + // Input modes: no break, no CR to NL, no parity check, no strip char, + // no start/stop output control. + raw.c_iflag &= (unsigned int) (~(BRKINT | ICRNL | INPCK | ISTRIP | IXON)); + + // Control modes - set 8 bit chars. + raw.c_cflag |= (CS8); + + // Local modes - choing off, canonical off, no extended functions, + // no signal chars (^Z,^C). + raw.c_lflag &= (unsigned int) (~(ECHO | ICANON | IEXTEN | ISIG)); + + // Control chars - set return condition: min number of bytes and timer. + // We want read to give every single byte, w/o timeout (1 byte, no timer). + raw.c_cc[VMIN] = 1; + raw.c_cc[VTIME] = 0; + + BC_SIG_LOCK; + + // Put terminal in raw mode after flushing. + do { + err = tcsetattr(STDIN_FILENO, TCSAFLUSH, &raw); + } while (BC_ERR(err < 0) && errno == EINTR); + + BC_SIG_UNLOCK; + + if (BC_ERR(err < 0)) bc_vm_err(BC_ERROR_FATAL_IO_ERR); + + h->rawMode = true; +} + +static void bc_history_disableRaw(BcHistory *h) { + + // Don't even check the return value as it's too late. + if (!h->rawMode) return; + + BC_SIG_LOCK; + + if (BC_ERR(tcsetattr(STDIN_FILENO, TCSAFLUSH, &h->orig_termios) != -1)) + h->rawMode = false; + + BC_SIG_UNLOCK; +} + +/** + * Use the ESC [6n escape sequence to query the horizontal cursor position + * and return it. On error -1 is returned, on success the position of the + * cursor. + */ +static size_t bc_history_cursorPos(void) { + + char buf[BC_HIST_SEQ_SIZE]; + char *ptr, *ptr2; + size_t cols, rows, i; + + // Report cursor location. + bc_file_write(&vm.fout, "\x1b[6n", 4); + bc_file_flush(&vm.fout); + + // Read the response: ESC [ rows ; cols R. + for (i = 0; i < sizeof(buf) - 1; ++i) { + if (bc_history_read(buf + i, 1) != 1 || buf[i] == 'R') break; + } + + buf[i] = '\0'; + + if (BC_ERR(buf[0] != BC_ACTION_ESC || buf[1] != '[')) return SIZE_MAX; + + // Parse it. + ptr = buf + 2; + rows = strtoul(ptr, &ptr2, 10); + + if (BC_ERR(!rows || ptr2[0] != ';')) return SIZE_MAX; + + ptr = ptr2 + 1; + cols = strtoul(ptr, NULL, 10); + + if (BC_ERR(!cols)) return SIZE_MAX; + + return cols <= UINT16_MAX ? cols : 0; +} + +/** + * Try to get the number of columns in the current terminal, or assume 80 + * if it fails. + */ +static size_t bc_history_columns(void) { + + struct winsize ws; + int ret; + + BC_SIG_LOCK; + + ret = ioctl(vm.fout.fd, TIOCGWINSZ, &ws); + + BC_SIG_UNLOCK; + + if (BC_ERR(ret == -1 || !ws.ws_col)) { + + // Calling ioctl() failed. Try to query the terminal itself. + size_t start, cols; + + // Get the initial position so we can restore it later. + start = bc_history_cursorPos(); + if (BC_ERR(start == SIZE_MAX)) return BC_HIST_DEF_COLS; + + // Go to right margin and get position. + bc_file_write(&vm.fout, "\x1b[999C", 6); + bc_file_flush(&vm.fout); + cols = bc_history_cursorPos(); + if (BC_ERR(cols == SIZE_MAX)) return BC_HIST_DEF_COLS; + + // Restore position. + if (cols > start) { + bc_file_printf(&vm.fout, "\x1b[%zuD", cols - start); + bc_file_flush(&vm.fout); + } + + return cols; + } + + return ws.ws_col; +} + +#if BC_ENABLE_PROMPT +/** + * Check if text is an ANSI escape sequence. + */ +static bool bc_history_ansiEscape(const char *buf, size_t buf_len, size_t *len) +{ + if (buf_len > 2 && !memcmp("\033[", buf, 2)) { + + size_t off = 2; + + while (off < buf_len) { + + char c = buf[off++]; + + if ((c >= 'A' && c <= 'K' && c != 'I') || + c == 'S' || c == 'T' || c == 'f' || c == 'm') + { + *len = off; + return true; + } + } + } + + return false; +} + +/** + * Get column length of prompt text. + */ +static size_t bc_history_promptColLen(const char *prompt, size_t plen) { + + char buf[BC_HIST_MAX_LINE + 1]; + size_t buf_len = 0, off = 0; + + while (off < plen) { + + size_t len; + + if (bc_history_ansiEscape(prompt + off, plen - off, &len)) { + off += len; + continue; + } + + buf[buf_len++] = prompt[off++]; + } + + return bc_history_colPos(buf, buf_len, buf_len); +} +#endif // BC_ENABLE_PROMPT + +/** + * Rewrites the currently edited line accordingly to the buffer content, + * cursor position, and number of columns of the terminal. + */ +static void bc_history_refresh(BcHistory *h) { + + char* buf = h->buf.v; + size_t colpos, len = BC_HIST_BUF_LEN(h), pos = h->pos; + + bc_file_flush(&vm.fout); + + while(h->pcol + bc_history_colPos(buf, len, pos) >= h->cols) { + + size_t chlen = bc_history_nextLen(buf, len, 0, NULL); + + buf += chlen; + len -= chlen; + pos -= chlen; + } + + while (h->pcol + bc_history_colPos(buf, len, len) > h->cols) + len -= bc_history_prevLen(buf, len, NULL); + + // Cursor to left edge. + bc_file_write(&vm.fout, "\r", 1); + + // Write the prompt, if desired. +#if BC_ENABLE_PROMPT + if (BC_USE_PROMPT) bc_file_write(&vm.fout, h->prompt, h->plen); +#endif // BC_ENABLE_PROMPT + + bc_file_write(&vm.fout, buf, BC_HIST_BUF_LEN(h)); + + // Erase to right. + bc_file_write(&vm.fout, "\x1b[0K", 4); + + // Move cursor to original position. + colpos = bc_history_colPos(buf, len, pos) + h->pcol; + + if (colpos) bc_file_printf(&vm.fout, "\r\x1b[%zuC", colpos); + + bc_file_flush(&vm.fout); +} + +/** + * Insert the character 'c' at cursor current position. + */ +static void bc_history_edit_insert(BcHistory *h, const char *cbuf, size_t clen) +{ + bc_vec_expand(&h->buf, bc_vm_growSize(h->buf.len, clen)); + + if (h->pos == BC_HIST_BUF_LEN(h)) { + + size_t colpos = 0, len; + + memcpy(bc_vec_item(&h->buf, h->pos), cbuf, clen); + + h->pos += clen; + h->buf.len += clen - 1; + bc_vec_pushByte(&h->buf, '\0'); + + len = BC_HIST_BUF_LEN(h); +#if BC_ENABLE_PROMPT + colpos = bc_history_promptColLen(h->prompt, h->plen); +#endif // BC_ENABLE_PROMPT + colpos += bc_history_colPos(h->buf.v, len, len); + + if (colpos < h->cols) { + + // Avoid a full update of the line in the trivial case. + bc_file_write(&vm.fout, cbuf, clen); + bc_file_flush(&vm.fout); + } + else bc_history_refresh(h); + } + else { + + size_t amt = BC_HIST_BUF_LEN(h) - h->pos; + + memmove(h->buf.v + h->pos + clen, h->buf.v + h->pos, amt); + memcpy(h->buf.v + h->pos, cbuf, clen); + + h->pos += clen; + h->buf.len += clen; + h->buf.v[BC_HIST_BUF_LEN(h)] = '\0'; + + bc_history_refresh(h); + } +} + +/** + * Move cursor to the left. + */ +static void bc_history_edit_left(BcHistory *h) { + + if (h->pos <= 0) return; + + h->pos -= bc_history_prevLen(h->buf.v, h->pos, NULL); + + bc_history_refresh(h); +} + +/** + * Move cursor on the right. +*/ +static void bc_history_edit_right(BcHistory *h) { + + if (h->pos == BC_HIST_BUF_LEN(h)) return; + + h->pos += bc_history_nextLen(h->buf.v, BC_HIST_BUF_LEN(h), h->pos, NULL); + + bc_history_refresh(h); +} + +/** + * Move cursor to the end of the current word. + */ +static void bc_history_edit_wordEnd(BcHistory *h) { + + size_t len = BC_HIST_BUF_LEN(h); + + if (!len || h->pos >= len) return; + + while (h->pos < len && isspace(h->buf.v[h->pos])) h->pos += 1; + while (h->pos < len && !isspace(h->buf.v[h->pos])) h->pos += 1; + + bc_history_refresh(h); +} + +/** + * Move cursor to the start of the current word. + */ +static void bc_history_edit_wordStart(BcHistory *h) { + + size_t len = BC_HIST_BUF_LEN(h); + + if (!len) return; + + while (h->pos > 0 && isspace(h->buf.v[h->pos - 1])) h->pos -= 1; + while (h->pos > 0 && !isspace(h->buf.v[h->pos - 1])) h->pos -= 1; + + bc_history_refresh(h); +} + +/** + * Move cursor to the start of the line. + */ +static void bc_history_edit_home(BcHistory *h) { + + if (!h->pos) return; + + h->pos = 0; + + bc_history_refresh(h); +} + +/** + * Move cursor to the end of the line. + */ +static void bc_history_edit_end(BcHistory *h) { + + if (h->pos == BC_HIST_BUF_LEN(h)) return; + + h->pos = BC_HIST_BUF_LEN(h); + + bc_history_refresh(h); +} + +/** + * Substitute the currently edited line with the next or previous history + * entry as specified by 'dir' (direction). + */ +static void bc_history_edit_next(BcHistory *h, bool dir) { + + const char *dup, *str; + + if (h->history.len <= 1) return; + + BC_SIG_LOCK; + + if (h->buf.v[0]) dup = bc_vm_strdup(h->buf.v); + else dup = ""; + + // Update the current history entry before + // overwriting it with the next one. + bc_vec_replaceAt(&h->history, h->history.len - 1 - h->idx, &dup); + + BC_SIG_UNLOCK; + + // Show the new entry. + h->idx += (dir == BC_HIST_PREV ? 1 : SIZE_MAX); + + if (h->idx == SIZE_MAX) { + h->idx = 0; + return; + } + else if (h->idx >= h->history.len) { + h->idx = h->history.len - 1; + return; + } + + str = *((char**) bc_vec_item(&h->history, h->history.len - 1 - h->idx)); + bc_vec_string(&h->buf, strlen(str), str); + assert(h->buf.len > 0); + + h->pos = BC_HIST_BUF_LEN(h); + + bc_history_refresh(h); +} + +/** + * Delete the character at the right of the cursor without altering the cursor + * position. Basically this is what happens with the "Delete" keyboard key. + */ +static void bc_history_edit_delete(BcHistory *h) { + + size_t chlen, len = BC_HIST_BUF_LEN(h); + + if (!len || h->pos >= len) return; + + chlen = bc_history_nextLen(h->buf.v, len, h->pos, NULL); + + memmove(h->buf.v + h->pos, h->buf.v + h->pos + chlen, len - h->pos - chlen); + + h->buf.len -= chlen; + h->buf.v[BC_HIST_BUF_LEN(h)] = '\0'; + + bc_history_refresh(h); +} + +static void bc_history_edit_backspace(BcHistory *h) { + + size_t chlen, len = BC_HIST_BUF_LEN(h); + + if (!h->pos || !len) return; + + chlen = bc_history_prevLen(h->buf.v, h->pos, NULL); + + memmove(h->buf.v + h->pos - chlen, h->buf.v + h->pos, len - h->pos); + + h->pos -= chlen; + h->buf.len -= chlen; + h->buf.v[BC_HIST_BUF_LEN(h)] = '\0'; + + bc_history_refresh(h); +} + +/** + * Delete the previous word, maintaining the cursor at the start of the + * current word. + */ +static void bc_history_edit_deletePrevWord(BcHistory *h) { + + size_t diff, old_pos = h->pos; + + while (h->pos > 0 && h->buf.v[h->pos - 1] == ' ') --h->pos; + while (h->pos > 0 && h->buf.v[h->pos - 1] != ' ') --h->pos; + + diff = old_pos - h->pos; + memmove(h->buf.v + h->pos, h->buf.v + old_pos, + BC_HIST_BUF_LEN(h) - old_pos + 1); + h->buf.len -= diff; + + bc_history_refresh(h); +} + +/** + * Delete the next word, maintaining the cursor at the same position. + */ +static void bc_history_edit_deleteNextWord(BcHistory *h) { + + size_t next_end = h->pos, len = BC_HIST_BUF_LEN(h); + + while (next_end < len && h->buf.v[next_end] == ' ') ++next_end; + while (next_end < len && h->buf.v[next_end] != ' ') ++next_end; + + memmove(h->buf.v + h->pos, h->buf.v + next_end, len - next_end); + + h->buf.len -= next_end - h->pos; + + bc_history_refresh(h); +} + +static void bc_history_swap(BcHistory *h) { + + size_t pcl, ncl; + char auxb[5]; + + pcl = bc_history_prevLen(h->buf.v, h->pos, NULL); + ncl = bc_history_nextLen(h->buf.v, BC_HIST_BUF_LEN(h), h->pos, NULL); + + // To perform a swap we need: + // * nonzero char length to the left + // * not at the end of the line + if (pcl && h->pos != BC_HIST_BUF_LEN(h) && pcl < 5 && ncl < 5) { + + memcpy(auxb, h->buf.v + h->pos - pcl, pcl); + memcpy(h->buf.v + h->pos - pcl, h->buf.v + h->pos, ncl); + memcpy(h->buf.v + h->pos - pcl + ncl, auxb, pcl); + + h->pos += -pcl + ncl; + + bc_history_refresh(h); + } +} + +/** + * Handle escape sequences. + */ +static void bc_history_escape(BcHistory *h) { + + char c, seq[3]; + + if (BC_ERR(BC_HIST_READ(seq, 1))) return; + + c = seq[0]; + + // ESC ? sequences. + if (c != '[' && c != 'O') { + if (c == 'f') bc_history_edit_wordEnd(h); + else if (c == 'b') bc_history_edit_wordStart(h); + else if (c == 'd') bc_history_edit_deleteNextWord(h); + } + else { + + if (BC_ERR(BC_HIST_READ(seq + 1, 1))) + bc_vm_err(BC_ERROR_FATAL_IO_ERR); + + // ESC [ sequences. + if (c == '[') { + + c = seq[1]; + + if (c >= '0' && c <= '9') { + + // Extended escape, read additional byte. + if (BC_ERR(BC_HIST_READ(seq + 2, 1))) + bc_vm_err(BC_ERROR_FATAL_IO_ERR); + + if (seq[2] == '~' && c == '3') bc_history_edit_delete(h); + else if(seq[2] == ';') { + + if (BC_ERR(BC_HIST_READ(seq, 2))) + bc_vm_err(BC_ERROR_FATAL_IO_ERR); + + if (seq[0] != '5') return; + else if (seq[1] == 'C') bc_history_edit_wordEnd(h); + else if (seq[1] == 'D') bc_history_edit_wordStart(h); + } + } + else { + + switch(c) { + + // Up. + case 'A': + { + bc_history_edit_next(h, BC_HIST_PREV); + break; + } + + // Down. + case 'B': + { + bc_history_edit_next(h, BC_HIST_NEXT); + break; + } + + // Right. + case 'C': + { + bc_history_edit_right(h); + break; + } + + // Left. + case 'D': + { + bc_history_edit_left(h); + break; + } + + // Home. + case 'H': + case '1': + { + bc_history_edit_home(h); + break; + } + + // End. + case 'F': + case '4': + { + bc_history_edit_end(h); + break; + } + + case 'd': + { + bc_history_edit_deleteNextWord(h); + break; + } + } + } + } + // ESC O sequences. + else if (c == 'O') { + + switch (seq[1]) { + + case 'A': + { + bc_history_edit_next(h, BC_HIST_PREV); + break; + } + + case 'B': + { + bc_history_edit_next(h, BC_HIST_NEXT); + break; + } + + case 'C': + { + bc_history_edit_right(h); + break; + } + + case 'D': + { + bc_history_edit_left(h); + break; + } + + case 'F': + { + bc_history_edit_end(h); + break; + } + + case 'H': + { + bc_history_edit_home(h); + break; + } + } + } + } +} + +static void bc_history_reset(BcHistory *h) { + + h->oldcolpos = h->pos = h->idx = 0; + h->cols = bc_history_columns(); + + // The latest history entry is always our current buffer, that + // initially is just an empty string. + bc_history_add_empty(h); + + // Buffer starts empty. + bc_vec_empty(&h->buf); +} + +static void bc_history_printCtrl(BcHistory *h, unsigned int c) { + + char str[3] = "^A"; + const char newline[2] = "\n"; + + str[1] = (char) (c + 'A' - BC_ACTION_CTRL_A); + + bc_vec_concat(&h->buf, str); + + bc_history_refresh(h); + + bc_vec_npop(&h->buf, sizeof(str)); + bc_vec_pushByte(&h->buf, '\0'); + + if (c != BC_ACTION_CTRL_C && c != BC_ACTION_CTRL_D) { + bc_file_write(&vm.fout, newline, sizeof(newline) - 1); + bc_history_refresh(h); + } +} + +/** + * This function is the core of the line editing capability of bc history. + * It expects 'fd' to be already in "raw mode" so that every key pressed + * will be returned ASAP to read(). + */ +static BcStatus bc_history_edit(BcHistory *h, const char *prompt) { + + bc_history_reset(h); + +#if BC_ENABLE_PROMPT + if (BC_USE_PROMPT) { + + h->prompt = prompt; + h->plen = strlen(prompt); + h->pcol = bc_history_promptColLen(prompt, h->plen); + + bc_file_write(&vm.fout, prompt, h->plen); + bc_file_flush(&vm.fout); + } +#endif // BC_ENABLE_PROMPT + + for (;;) { + + BcStatus s; + // Large enough for any encoding? + char cbuf[32]; + unsigned int c = 0; + size_t nread = 0; + + s = bc_history_readCode(cbuf, sizeof(cbuf), &c, &nread); + if (BC_ERR(s)) return s; + + switch (c) { + + case BC_ACTION_LINE_FEED: + case BC_ACTION_ENTER: + { + bc_vec_pop(&h->history); + return s; + } + + case BC_ACTION_TAB: + { + memcpy(cbuf, bc_history_tab, bc_history_tab_len + 1); + bc_history_edit_insert(h, cbuf, bc_history_tab_len); + break; + } + + case BC_ACTION_CTRL_C: + { + bc_history_printCtrl(h, c); + bc_file_write(&vm.fout, vm.sigmsg, vm.siglen); + bc_file_write(&vm.fout, bc_program_ready_msg, + bc_program_ready_msg_len); + bc_history_reset(h); + bc_history_refresh(h); + break; + } + + case BC_ACTION_BACKSPACE: + case BC_ACTION_CTRL_H: + { + bc_history_edit_backspace(h); + break; + } + + // Act as end-of-file. + case BC_ACTION_CTRL_D: + { + bc_history_printCtrl(h, c); + return BC_STATUS_EOF; + } + + // Swaps current character with previous. + case BC_ACTION_CTRL_T: + { + bc_history_swap(h); + break; + } + + case BC_ACTION_CTRL_B: + { + bc_history_edit_left(h); + break; + } + + case BC_ACTION_CTRL_F: + { + bc_history_edit_right(h); + break; + } + + case BC_ACTION_CTRL_P: + { + bc_history_edit_next(h, BC_HIST_PREV); + break; + } + + case BC_ACTION_CTRL_N: + { + bc_history_edit_next(h, BC_HIST_NEXT); + break; + } + + case BC_ACTION_ESC: + { + bc_history_escape(h); + break; + } + + // Delete the whole line. + case BC_ACTION_CTRL_U: + { + bc_vec_string(&h->buf, 0, ""); + h->pos = 0; + + bc_history_refresh(h); + + break; + } + + // Delete from current to end of line. + case BC_ACTION_CTRL_K: + { + bc_vec_npop(&h->buf, h->buf.len - h->pos); + bc_vec_pushByte(&h->buf, '\0'); + bc_history_refresh(h); + break; + } + + // Go to the start of the line. + case BC_ACTION_CTRL_A: + { + bc_history_edit_home(h); + break; + } + + // Go to the end of the line. + case BC_ACTION_CTRL_E: + { + bc_history_edit_end(h); + break; + } + + // Clear screen. + case BC_ACTION_CTRL_L: + { + bc_file_write(&vm.fout, "\x1b[H\x1b[2J", 7); + bc_history_refresh(h); + break; + } + + // Delete previous word. + case BC_ACTION_CTRL_W: + { + bc_history_edit_deletePrevWord(h); + break; + } + + default: + { + if (c >= BC_ACTION_CTRL_A && c <= BC_ACTION_CTRL_Z) + bc_history_printCtrl(h, c); + else bc_history_edit_insert(h, cbuf, nread); + break; + } + } + } + + return BC_STATUS_SUCCESS; +} + +static inline bool bc_history_stdinHasData(BcHistory *h) { + int n; + return pselect(1, &h->rdset, NULL, NULL, &h->ts, &h->sigmask) > 0 || + (ioctl(STDIN_FILENO, FIONREAD, &n) >= 0 && n > 0); +} + +/** + * This function calls the line editing function bc_history_edit() + * using the STDIN file descriptor set in raw mode. + */ +static BcStatus bc_history_raw(BcHistory *h, const char *prompt) { + + BcStatus s; + + assert(vm.fout.len == 0); + + bc_history_enableRaw(h); + + s = bc_history_edit(h, prompt); + + h->stdin_has_data = bc_history_stdinHasData(h); + if (!h->stdin_has_data) bc_history_disableRaw(h); + + bc_file_write(&vm.fout, "\n", 1); + bc_file_flush(&vm.fout); + + return s; +} + +BcStatus bc_history_line(BcHistory *h, BcVec *vec, const char *prompt) { + + BcStatus s; + char* line; + + s = bc_history_raw(h, prompt); + assert(!s || s == BC_STATUS_EOF); + + bc_vec_string(vec, BC_HIST_BUF_LEN(h), h->buf.v); + + if (h->buf.v[0]) { + + BC_SIG_LOCK; + + line = bc_vm_strdup(h->buf.v); + + BC_SIG_UNLOCK; + + bc_history_add(h, line); + } + else bc_history_add_empty(h); + + bc_vec_concat(vec, "\n"); + + return s; +} + +static void bc_history_add(BcHistory *h, char *line) { + + if (h->history.len) { + + char *s = *((char**) bc_vec_item_rev(&h->history, 0)); + + if (!strcmp(s, line)) { + + BC_SIG_LOCK; + + free(line); + + BC_SIG_UNLOCK; + + return; + } + } + + bc_vec_push(&h->history, &line); +} + +static void bc_history_add_empty(BcHistory *h) { + + const char *line = ""; + + if (h->history.len) { + + char *s = *((char**) bc_vec_item_rev(&h->history, 0)); + + if (!s[0]) return; + } + + bc_vec_push(&h->history, &line); +} + +static void bc_history_string_free(void *str) { + char *s = *((char**) str); + BC_SIG_ASSERT_LOCKED; + if (s[0]) free(s); +} + +void bc_history_init(BcHistory *h) { + + BC_SIG_ASSERT_LOCKED; + + bc_vec_init(&h->buf, sizeof(char), NULL); + bc_vec_init(&h->history, sizeof(char*), bc_history_string_free); + + FD_ZERO(&h->rdset); + FD_SET(STDIN_FILENO, &h->rdset); + h->ts.tv_sec = 0; + h->ts.tv_nsec = 0; + + sigemptyset(&h->sigmask); + sigaddset(&h->sigmask, SIGINT); + + h->rawMode = h->stdin_has_data = false; + h->badTerm = bc_history_isBadTerm(); +} + +void bc_history_free(BcHistory *h) { + BC_SIG_ASSERT_LOCKED; + bc_history_disableRaw(h); +#ifndef NDEBUG + bc_vec_free(&h->buf); + bc_vec_free(&h->history); +#endif // NDEBUG +} + +/** + * This special mode is used by bc history in order to print scan codes + * on screen for debugging / development purposes. + */ +#if BC_DEBUG_CODE +void bc_history_printKeyCodes(BcHistory *h) { + + char quit[4]; + + bc_vm_printf("Linenoise key codes debugging mode.\n" + "Press keys to see scan codes. " + "Type 'quit' at any time to exit.\n"); + + bc_history_enableRaw(h); + memset(quit, ' ', 4); + + while(true) { + + char c; + ssize_t nread; + + nread = bc_history_read(&c, 1); + if (nread <= 0) continue; + + // Shift string to left. + memmove(quit, quit + 1, sizeof(quit) - 1); + + // Insert current char on the right. + quit[sizeof(quit) - 1] = c; + if (!memcmp(quit, "quit", sizeof(quit))) break; + + bc_vm_printf("'%c' %lu (type quit to exit)\n", + isprint(c) ? c : '?', (unsigned long) c); + + // Go left edge manually, we are in raw mode. + bc_vm_putchar('\r'); + bc_file_flush(&vm.fout); + } + + bc_history_disableRaw(h); +} +#endif // BC_DEBUG_CODE + +#endif // BC_ENABLE_HISTORY diff --git a/src/lang.c b/src/lang.c new file mode 100644 index 000000000000..6959af80fbfe --- /dev/null +++ b/src/lang.c @@ -0,0 +1,313 @@ +/* + * ***************************************************************************** + * + * Copyright (c) 2018-2020 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 to manipulate data structures in programs. + * + */ + +#include <assert.h> +#include <stdlib.h> +#include <string.h> + +#include <lang.h> +#include <vm.h> + +#ifndef NDEBUG +void bc_id_free(void *id) { + BC_SIG_ASSERT_LOCKED; + assert(id != NULL); + free(((BcId*) id)->name); +} +#endif // NDEBUG + +void bc_string_free(void *string) { + BC_SIG_ASSERT_LOCKED; + assert(string != NULL && (*((char**) string)) != NULL); + if (BC_IS_BC) free(*((char**) string)); +} + +void bc_const_free(void *constant) { + BcConst *c = constant; + BC_SIG_ASSERT_LOCKED; + assert(c->val != NULL); + free(c->val); + bc_num_free(&c->num); +} + +#if BC_ENABLED +void bc_func_insert(BcFunc *f, BcProgram *p, char *name, + BcType type, size_t line) +{ + BcLoc a; + size_t i, idx; + + assert(f != NULL); + + idx = bc_program_search(p, name, type == BC_TYPE_VAR); + + for (i = 0; i < f->autos.len; ++i) { + BcLoc *id = bc_vec_item(&f->autos, i); + if (BC_ERR(idx == id->loc && type == (BcType) id->idx)) { + const char *array = type == BC_TYPE_ARRAY ? "[]" : ""; + bc_vm_error(BC_ERROR_PARSE_DUP_LOCAL, line, name, array); + } + } + + a.loc = idx; + a.idx = type; + + bc_vec_push(&f->autos, &a); +} +#endif // BC_ENABLED + +void bc_func_init(BcFunc *f, const char *name) { + + BC_SIG_ASSERT_LOCKED; + + assert(f != NULL && name != NULL); + + bc_vec_init(&f->code, sizeof(uchar), NULL); + + // This is necessary for not allocating memory where it isn't used. + // dc does not use strings except in the main function. The else part + // is necessary to stop uninitiazed data errors in valgrind. + if (BC_IS_BC || !strcmp(name, bc_func_main)) + bc_vec_init(&f->strs, sizeof(char*), bc_string_free); +#if BC_ENABLE_FUNC_FREE + else bc_vec_clear(&f->strs); +#endif // BC_ENABLE_FUNC_FREE + + bc_vec_init(&f->consts, sizeof(BcConst), bc_const_free); +#if BC_ENABLED + if (BC_IS_BC) { + bc_vec_init(&f->autos, sizeof(BcLoc), NULL); + bc_vec_init(&f->labels, sizeof(size_t), NULL); + f->nparams = 0; + f->voidfn = false; + } +#endif // BC_ENABLED + f->name = name; +} + +void bc_func_reset(BcFunc *f) { + BC_SIG_ASSERT_LOCKED; + assert(f != NULL); + bc_vec_npop(&f->code, f->code.len); + bc_vec_npop(&f->strs, f->strs.len); + bc_vec_npop(&f->consts, f->consts.len); +#if BC_ENABLED + if (BC_IS_BC) { + bc_vec_npop(&f->autos, f->autos.len); + bc_vec_npop(&f->labels, f->labels.len); + f->nparams = 0; + f->voidfn = false; + } +#endif // BC_ENABLED +} + +void bc_func_free(void *func) { +#if BC_ENABLE_FUNC_FREE + + BcFunc *f = (BcFunc*) func; + BC_SIG_ASSERT_LOCKED; + assert(f != NULL); + bc_vec_free(&f->code); + bc_vec_free(&f->strs); + bc_vec_free(&f->consts); +#if BC_ENABLED +#ifndef NDEBUG + if (BC_IS_BC) { + bc_vec_free(&f->autos); + bc_vec_free(&f->labels); + } +#endif // NDEBUG +#endif // BC_ENABLED + +#else // BC_ENABLE_FUNC_FREE + BC_UNUSED(func); +#endif // BC_ENABLE_FUNC_FREE +} + +void bc_array_init(BcVec *a, bool nums) { + BC_SIG_ASSERT_LOCKED; + if (nums) bc_vec_init(a, sizeof(BcNum), bc_num_free); + else bc_vec_init(a, sizeof(BcVec), bc_vec_free); + bc_array_expand(a, 1); +} + +void bc_array_copy(BcVec *d, const BcVec *s) { + + size_t i; + + BC_SIG_ASSERT_LOCKED; + + assert(d != NULL && s != NULL); + assert(d != s && d->size == s->size && d->dtor == s->dtor); + + bc_vec_npop(d, d->len); + bc_vec_expand(d, s->cap); + d->len = s->len; + + for (i = 0; i < s->len; ++i) { + BcNum *dnum = bc_vec_item(d, i), *snum = bc_vec_item(s, i); + bc_num_createCopy(dnum, snum); + } +} + +void bc_array_expand(BcVec *a, size_t len) { + + assert(a != NULL); + + BC_SIG_ASSERT_LOCKED; + + bc_vec_expand(a, len); + + if (a->size == sizeof(BcNum) && a->dtor == bc_num_free) { + BcNum n; + while (len > a->len) { + bc_num_init(&n, BC_NUM_DEF_SIZE); + bc_vec_push(a, &n); + } + } + else { + BcVec v; + assert(a->size == sizeof(BcVec) && a->dtor == bc_vec_free); + while (len > a->len) { + bc_array_init(&v, true); + bc_vec_push(a, &v); + } + } +} + +void bc_result_clear(BcResult *r) { + r->t = BC_RESULT_TEMP; + bc_num_clear(&r->d.n); +} + +#if DC_ENABLED +void bc_result_copy(BcResult *d, BcResult *src) { + + assert(d != NULL && src != NULL); + + BC_SIG_ASSERT_LOCKED; + + d->t = src->t; + + switch (d->t) { + + case BC_RESULT_TEMP: + case BC_RESULT_IBASE: + case BC_RESULT_SCALE: + case BC_RESULT_OBASE: +#if BC_ENABLE_EXTRA_MATH + case BC_RESULT_SEED: +#endif // BC_ENABLE_EXTRA_MATH + { + bc_num_createCopy(&d->d.n, &src->d.n); + break; + } + + case BC_RESULT_VAR: +#if BC_ENABLED + case BC_RESULT_ARRAY: +#endif // BC_ENABLED + case BC_RESULT_ARRAY_ELEM: + { + memcpy(&d->d.loc, &src->d.loc, sizeof(BcLoc)); + break; + } + + case BC_RESULT_CONSTANT: + case BC_RESULT_STR: + { + memcpy(&d->d.n, &src->d.n, sizeof(BcNum)); + break; + } + + case BC_RESULT_ONE: + { + // Do nothing. + break; + } + +#if BC_ENABLED + case BC_RESULT_VOID: + case BC_RESULT_LAST: + { +#ifndef NDEBUG + abort(); +#endif // NDEBUG + } +#endif // BC_ENABLED + } +} +#endif // DC_ENABLED + +void bc_result_free(void *result) { + + BcResult *r = (BcResult*) result; + + BC_SIG_ASSERT_LOCKED; + + assert(r != NULL); + + switch (r->t) { + + case BC_RESULT_TEMP: + case BC_RESULT_IBASE: + case BC_RESULT_SCALE: + case BC_RESULT_OBASE: +#if BC_ENABLE_EXTRA_MATH + case BC_RESULT_SEED: +#endif // BC_ENABLE_EXTRA_MATH + { + bc_num_free(&r->d.n); + break; + } + + case BC_RESULT_VAR: +#if BC_ENABLED + case BC_RESULT_ARRAY: +#endif // BC_ENABLED + case BC_RESULT_ARRAY_ELEM: + case BC_RESULT_STR: + case BC_RESULT_CONSTANT: + case BC_RESULT_ONE: +#if BC_ENABLED + case BC_RESULT_VOID: + case BC_RESULT_LAST: +#endif // BC_ENABLED + { + // Do nothing. + break; + } + } +} diff --git a/src/lex.c b/src/lex.c new file mode 100644 index 000000000000..8bbc694c161f --- /dev/null +++ b/src/lex.c @@ -0,0 +1,231 @@ +/* + * ***************************************************************************** + * + * Copyright (c) 2018-2020 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. + * + * ***************************************************************************** + * + * Common code for the lexers. + * + */ + +#include <assert.h> +#include <ctype.h> +#include <stdbool.h> +#include <string.h> + +#include <status.h> +#include <lex.h> +#include <vm.h> +#include <bc.h> + +void bc_lex_invalidChar(BcLex *l, char c) { + l->t = BC_LEX_INVALID; + bc_lex_verr(l, BC_ERROR_PARSE_CHAR, c); +} + +void bc_lex_lineComment(BcLex *l) { + l->t = BC_LEX_WHITESPACE; + while (l->i < l->len && l->buf[l->i] != '\n') l->i += 1; +} + +void bc_lex_comment(BcLex *l) { + + size_t i, nlines = 0; + const char *buf = l->buf; + bool end = false; + char c; + + l->i += 1; + l->t = BC_LEX_WHITESPACE; + + for (i = l->i; !end; i += !end) { + + for (; (c = buf[i]) && c != '*'; ++i) nlines += (c == '\n'); + + if (BC_ERR(!c || buf[i + 1] == '\0')) { + l->i = i; + bc_lex_err(l, BC_ERROR_PARSE_COMMENT); + } + + end = buf[i + 1] == '/'; + } + + l->i = i + 2; + l->line += nlines; +} + +void bc_lex_whitespace(BcLex *l) { + char c; + l->t = BC_LEX_WHITESPACE; + for (c = l->buf[l->i]; c != '\n' && isspace(c); c = l->buf[++l->i]); +} + +void bc_lex_commonTokens(BcLex *l, char c) { + if (!c) l->t = BC_LEX_EOF; + else if (c == '\n') l->t = BC_LEX_NLINE; + else bc_lex_whitespace(l); +} + +static size_t bc_lex_num(BcLex *l, char start, bool int_only) { + + const char *buf = l->buf + l->i; + size_t i; + char c; + bool last_pt, pt = (start == '.'); + + for (i = 0; (c = buf[i]) && (BC_LEX_NUM_CHAR(c, pt, int_only) || + (c == '\\' && buf[i + 1] == '\n')); ++i) + { + if (c == '\\') { + + if (buf[i + 1] == '\n') { + + i += 2; + + // Make sure to eat whitespace at the beginning of the line. + while(isspace(buf[i]) && buf[i] != '\n') i += 1; + + c = buf[i]; + + if (!BC_LEX_NUM_CHAR(c, pt, int_only)) break; + } + else break; + } + + last_pt = (c == '.'); + if (pt && last_pt) break; + pt = pt || last_pt; + + bc_vec_push(&l->str, &c); + } + + return i; +} + +void bc_lex_number(BcLex *l, char start) { + + l->t = BC_LEX_NUMBER; + + bc_vec_npop(&l->str, l->str.len); + bc_vec_push(&l->str, &start); + + l->i += bc_lex_num(l, start, false); + +#if BC_ENABLE_EXTRA_MATH + { + char c = l->buf[l->i]; + + if (c == 'e') { + +#if BC_ENABLED + if (BC_IS_POSIX) bc_lex_err(l, BC_ERROR_POSIX_EXP_NUM); +#endif // BC_ENABLED + + bc_vec_push(&l->str, &c); + l->i += 1; + c = l->buf[l->i]; + + if (c == BC_LEX_NEG_CHAR) { + bc_vec_push(&l->str, &c); + l->i += 1; + c = l->buf[l->i]; + } + + if (BC_ERR(!BC_LEX_NUM_CHAR(c, false, true))) + bc_lex_verr(l, BC_ERROR_PARSE_CHAR, c); + + l->i += bc_lex_num(l, 0, true); + } + } +#endif // BC_ENABLE_EXTRA_MATH + + bc_vec_pushByte(&l->str, '\0'); +} + +void bc_lex_name(BcLex *l) { + + size_t i = 0; + const char *buf = l->buf + l->i - 1; + char c = buf[i]; + + l->t = BC_LEX_NAME; + + while ((c >= 'a' && c <= 'z') || isdigit(c) || c == '_') c = buf[++i]; + + bc_vec_string(&l->str, i, buf); + + // Increment the index. We minus 1 because it has already been incremented. + l->i += i - 1; +} + +void bc_lex_init(BcLex *l) { + BC_SIG_ASSERT_LOCKED; + assert(l != NULL); + bc_vec_init(&l->str, sizeof(char), NULL); +} + +void bc_lex_free(BcLex *l) { + BC_SIG_ASSERT_LOCKED; + assert(l != NULL); + bc_vec_free(&l->str); +} + +void bc_lex_file(BcLex *l, const char *file) { + assert(l != NULL && file != NULL); + l->line = 1; + vm.file = file; +} + +void bc_lex_next(BcLex *l) { + + assert(l != NULL); + + l->last = l->t; + l->line += (l->i != 0 && l->buf[l->i - 1] == '\n'); + + if (BC_ERR(l->last == BC_LEX_EOF)) bc_lex_err(l, BC_ERROR_PARSE_EOF); + + l->t = BC_LEX_EOF; + + if (l->i == l->len) return; + + // Loop until failure or we don't have whitespace. This + // is so the parser doesn't get inundated with whitespace. + do { + vm.next(l); + } while (l->t == BC_LEX_WHITESPACE); +} + +void bc_lex_text(BcLex *l, const char *text) { + assert(l != NULL && text != NULL); + l->buf = text; + l->i = 0; + l->len = strlen(text); + l->t = l->last = BC_LEX_INVALID; + bc_lex_next(l); +} diff --git a/src/main.c b/src/main.c new file mode 100644 index 000000000000..68941c5e24a5 --- /dev/null +++ b/src/main.c @@ -0,0 +1,93 @@ +/* + * ***************************************************************************** + * + * Copyright (c) 2018-2020 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. + * + * ***************************************************************************** + * + * The entry point for bc. + * + */ + +#include <assert.h> +#include <stdlib.h> +#include <string.h> + +#include <locale.h> +#include <libgen.h> + +#include <setjmp.h> + +#include <status.h> +#include <vm.h> +#include <bc.h> +#include <dc.h> + +char output_bufs[BC_VM_BUF_SIZE]; +BcVm vm; + +int main(int argc, char *argv[]) { + + int s; + char *name; + size_t len = strlen(BC_EXECPREFIX); + + vm.locale = setlocale(LC_ALL, ""); + + name = strrchr(argv[0], '/'); + vm.name = (name == NULL) ? argv[0] : name + 1; + + if (strlen(vm.name) > len) vm.name += len; + + BC_SIG_LOCK; + + bc_vec_init(&vm.jmp_bufs, sizeof(sigjmp_buf), NULL); + + BC_SETJMP_LOCKED(exit); + +#if !DC_ENABLED + bc_main(argc, argv); +#elif !BC_ENABLED + dc_main(argc, argv); +#else + if (BC_IS_BC) bc_main(argc, argv); + else dc_main(argc, argv); +#endif + +exit: + BC_SIG_MAYLOCK; + + s = !BC_STATUS_IS_ERROR(vm.status) ? BC_STATUS_SUCCESS : (int) vm.status; + + bc_vm_shutdown(); + +#ifndef NDEBUG + bc_vec_free(&vm.jmp_bufs); +#endif // NDEBUG + + return s; +} diff --git a/src/num.c b/src/num.c new file mode 100644 index 000000000000..ac255295e970 --- /dev/null +++ b/src/num.c @@ -0,0 +1,2837 @@ +/* + * ***************************************************************************** + * + * Copyright (c) 2018-2020 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 for the number type. + * + */ + +#include <assert.h> +#include <ctype.h> +#include <stdbool.h> +#include <stdlib.h> +#include <string.h> +#include <setjmp.h> +#include <limits.h> + +#include <status.h> +#include <num.h> +#include <rand.h> +#include <vm.h> + +static void bc_num_m(BcNum *a, BcNum *b, BcNum *restrict c, size_t scale); + +static inline ssize_t bc_num_neg(size_t n, bool neg) { + return (((ssize_t) n) ^ -((ssize_t) neg)) + neg; +} + +ssize_t bc_num_cmpZero(const BcNum *n) { + return bc_num_neg((n)->len != 0, (n)->neg); +} + +static inline size_t bc_num_int(const BcNum *n) { + return n->len ? n->len - n->rdx : 0; +} + +static void bc_num_expand(BcNum *restrict n, size_t req) { + + assert(n != NULL); + + req = req >= BC_NUM_DEF_SIZE ? req : BC_NUM_DEF_SIZE; + + if (req > n->cap) { + + BC_SIG_LOCK; + + n->num = bc_vm_realloc(n->num, BC_NUM_SIZE(req)); + n->cap = req; + + BC_SIG_UNLOCK; + } +} + +static void bc_num_setToZero(BcNum *restrict n, size_t scale) { + assert(n != NULL); + n->scale = scale; + n->len = n->rdx = 0; + n->neg = false; +} + +static inline void bc_num_zero(BcNum *restrict n) { + bc_num_setToZero(n, 0); +} + +void bc_num_one(BcNum *restrict n) { + bc_num_zero(n); + n->len = 1; + n->num[0] = 1; +} + +static void bc_num_clean(BcNum *restrict n) { + + while (BC_NUM_NONZERO(n) && !n->num[n->len - 1]) n->len -= 1; + + if (BC_NUM_ZERO(n)) { + n->neg = false; + n->rdx = 0; + } + else if (n->len < n->rdx) n->len = n->rdx; +} + +static size_t bc_num_log10(size_t i) { + size_t len; + for (len = 1; i; i /= BC_BASE, ++len); + assert(len - 1 <= BC_BASE_DIGS + 1); + return len - 1; +} + +static inline size_t bc_num_zeroDigits(const BcDig *n) { + assert(*n >= 0); + assert(((size_t) *n) < BC_BASE_POW); + return BC_BASE_DIGS - bc_num_log10((size_t) *n); +} + +static size_t bc_num_intDigits(const BcNum *n) { + size_t digits = bc_num_int(n) * BC_BASE_DIGS; + if (digits > 0) digits -= bc_num_zeroDigits(n->num + n->len - 1); + return digits; +} + +static size_t bc_num_nonzeroLen(const BcNum *restrict n) { + size_t i, len = n->len; + assert(len == n->rdx); + for (i = len - 1; i < len && !n->num[i]; --i); + assert(i + 1 > 0); + return i + 1; +} + +static BcDig bc_num_addDigits(BcDig a, BcDig b, bool *carry) { + + assert(((BcBigDig) BC_BASE_POW) * 2 == ((BcDig) BC_BASE_POW) * 2); + assert(a < BC_BASE_POW); + assert(b < BC_BASE_POW); + + a += b + *carry; + *carry = (a >= BC_BASE_POW); + if (*carry) a -= BC_BASE_POW; + + assert(a >= 0); + assert(a < BC_BASE_POW); + + return a; +} + +static BcDig bc_num_subDigits(BcDig a, BcDig b, bool *carry) { + + assert(a < BC_BASE_POW); + assert(b < BC_BASE_POW); + + b += *carry; + *carry = (a < b); + if (*carry) a += BC_BASE_POW; + + assert(a - b >= 0); + assert(a - b < BC_BASE_POW); + + return a - b; +} + +static void bc_num_addArrays(BcDig *restrict a, const BcDig *restrict b, + size_t len) +{ + size_t i; + bool carry = false; + + for (i = 0; i < len; ++i) a[i] = bc_num_addDigits(a[i], b[i], &carry); + + for (; carry; ++i) a[i] = bc_num_addDigits(a[i], 0, &carry); +} + +static void bc_num_subArrays(BcDig *restrict a, const BcDig *restrict b, + size_t len) +{ + size_t i; + bool carry = false; + + for (i = 0; i < len; ++i) a[i] = bc_num_subDigits(a[i], b[i], &carry); + + for (; carry; ++i) a[i] = bc_num_subDigits(a[i], 0, &carry); +} + +static void bc_num_mulArray(const BcNum *restrict a, BcBigDig b, + BcNum *restrict c) +{ + size_t i; + BcBigDig carry = 0; + + assert(b <= BC_BASE_POW); + + if (a->len + 1 > c->cap) bc_num_expand(c, a->len + 1); + + memset(c->num, 0, BC_NUM_SIZE(c->cap)); + + for (i = 0; i < a->len; ++i) { + BcBigDig in = ((BcBigDig) a->num[i]) * b + carry; + c->num[i] = in % BC_BASE_POW; + carry = in / BC_BASE_POW; + } + + assert(carry < BC_BASE_POW); + c->num[i] = (BcDig) carry; + c->len = a->len; + c->len += (carry != 0); + + bc_num_clean(c); + + assert(!c->neg || BC_NUM_NONZERO(c)); + assert(c->rdx <= c->len || !c->len); + assert(!c->len || c->num[c->len - 1] || c->rdx == c->len); +} + +static void bc_num_divArray(const BcNum *restrict a, BcBigDig b, + BcNum *restrict c, BcBigDig *rem) +{ + size_t i; + BcBigDig carry = 0; + + assert(c->cap >= a->len); + + for (i = a->len - 1; i < a->len; --i) { + BcBigDig in = ((BcBigDig) a->num[i]) + carry * BC_BASE_POW; + assert(in / b < BC_BASE_POW); + c->num[i] = (BcDig) (in / b); + carry = in % b; + } + + c->len = a->len; + bc_num_clean(c); + *rem = carry; + + assert(!c->neg || BC_NUM_NONZERO(c)); + assert(c->rdx <= c->len || !c->len); + assert(!c->len || c->num[c->len - 1] || c->rdx == c->len); +} + +static ssize_t bc_num_compare(const BcDig *restrict a, const BcDig *restrict b, + size_t len) +{ + size_t i; + BcDig c = 0; + for (i = len - 1; i < len && !(c = a[i] - b[i]); --i); + return bc_num_neg(i + 1, c < 0); +} + +ssize_t bc_num_cmp(const BcNum *a, const BcNum *b) { + + size_t i, min, a_int, b_int, diff; + BcDig *max_num, *min_num; + bool a_max, neg = false; + ssize_t cmp; + + assert(a != NULL && b != NULL); + + if (a == b) return 0; + if (BC_NUM_ZERO(a)) return bc_num_neg(b->len != 0, !b->neg); + if (BC_NUM_ZERO(b)) return bc_num_cmpZero(a); + if (a->neg) { + if (b->neg) neg = true; + else return -1; + } + else if (b->neg) return 1; + + a_int = bc_num_int(a); + b_int = bc_num_int(b); + a_int -= b_int; + + if (a_int) return neg ? -((ssize_t) a_int) : (ssize_t) a_int; + + a_max = (a->rdx > b->rdx); + + if (a_max) { + min = b->rdx; + diff = a->rdx - b->rdx; + max_num = a->num + diff; + min_num = b->num; + } + else { + min = a->rdx; + diff = b->rdx - a->rdx; + max_num = b->num + diff; + min_num = a->num; + } + + cmp = bc_num_compare(max_num, min_num, b_int + min); + + if (cmp) return bc_num_neg((size_t) cmp, !a_max == !neg); + + for (max_num -= diff, i = diff - 1; i < diff; --i) { + if (max_num[i]) return bc_num_neg(1, !a_max == !neg); + } + + return 0; +} + +void bc_num_truncate(BcNum *restrict n, size_t places) { + + size_t places_rdx; + + if (!places) return; + + places_rdx = n->rdx ? n->rdx - BC_NUM_RDX(n->scale - places) : 0; + assert(places <= n->scale && (BC_NUM_ZERO(n) || places_rdx <= n->len)); + + n->scale -= places; + n->rdx -= places_rdx; + + if (BC_NUM_NONZERO(n)) { + + size_t pow; + + pow = n->scale % BC_BASE_DIGS; + pow = pow ? BC_BASE_DIGS - pow : 0; + pow = bc_num_pow10[pow]; + + n->len -= places_rdx; + memmove(n->num, n->num + places_rdx, BC_NUM_SIZE(n->len)); + + // Clear the lower part of the last digit. + if (BC_NUM_NONZERO(n)) n->num[0] -= n->num[0] % (BcDig) pow; + + bc_num_clean(n); + } +} + +static void bc_num_extend(BcNum *restrict n, size_t places) { + + size_t places_rdx; + + if (!places) return; + if (BC_NUM_ZERO(n)) { + n->scale += places; + return; + } + + places_rdx = BC_NUM_RDX(places + n->scale) - n->rdx; + + if (places_rdx) { + bc_num_expand(n, bc_vm_growSize(n->len, places_rdx)); + memmove(n->num + places_rdx, n->num, BC_NUM_SIZE(n->len)); + memset(n->num, 0, BC_NUM_SIZE(places_rdx)); + } + + n->rdx += places_rdx; + n->scale += places; + n->len += places_rdx; + + assert(n->rdx == BC_NUM_RDX(n->scale)); +} + +static void bc_num_retireMul(BcNum *restrict n, size_t scale, + bool neg1, bool neg2) +{ + if (n->scale < scale) bc_num_extend(n, scale - n->scale); + else bc_num_truncate(n, n->scale - scale); + + bc_num_clean(n); + if (BC_NUM_NONZERO(n)) n->neg = (!neg1 != !neg2); +} + +static void bc_num_split(const BcNum *restrict n, size_t idx, + BcNum *restrict a, BcNum *restrict b) +{ + assert(BC_NUM_ZERO(a)); + assert(BC_NUM_ZERO(b)); + + if (idx < n->len) { + + b->len = n->len - idx; + a->len = idx; + a->scale = a->rdx = b->scale = b->rdx = 0; + + assert(a->cap >= a->len); + assert(b->cap >= b->len); + + memcpy(b->num, n->num + idx, BC_NUM_SIZE(b->len)); + memcpy(a->num, n->num, BC_NUM_SIZE(idx)); + + bc_num_clean(b); + } + else bc_num_copy(a, n); + + bc_num_clean(a); +} + +static size_t bc_num_shiftZero(BcNum *restrict n) { + + size_t i; + + assert(!n->rdx || BC_NUM_ZERO(n)); + + for (i = 0; i < n->len && !n->num[i]; ++i); + + n->len -= i; + n->num += i; + + return i; +} + +static void bc_num_unshiftZero(BcNum *restrict n, size_t places_rdx) { + n->len += places_rdx; + n->num -= places_rdx; +} + +static void bc_num_shift(BcNum *restrict n, BcBigDig dig) { + + size_t i, len = n->len; + BcBigDig carry = 0, pow; + BcDig *ptr = n->num; + + assert(dig < BC_BASE_DIGS); + + pow = bc_num_pow10[dig]; + dig = bc_num_pow10[BC_BASE_DIGS - dig]; + + for (i = len - 1; i < len; --i) { + BcBigDig in, temp; + in = ((BcBigDig) ptr[i]); + temp = carry * dig; + carry = in % pow; + ptr[i] = ((BcDig) (in / pow)) + (BcDig) temp; + } + + assert(!carry); +} + +static void bc_num_shiftLeft(BcNum *restrict n, size_t places) { + + BcBigDig dig; + size_t places_rdx; + bool shift; + + if (!places) return; + if (places > n->scale) { + size_t size = bc_vm_growSize(BC_NUM_RDX(places - n->scale), n->len); + if (size > SIZE_MAX - 1) bc_vm_err(BC_ERROR_MATH_OVERFLOW); + } + if (BC_NUM_ZERO(n)) { + if (n->scale >= places) n->scale -= places; + else n->scale = 0; + return; + } + + dig = (BcBigDig) (places % BC_BASE_DIGS); + shift = (dig != 0); + places_rdx = BC_NUM_RDX(places); + + if (n->scale) { + + if (n->rdx >= places_rdx) { + + size_t mod = n->scale % BC_BASE_DIGS, revdig; + + mod = mod ? mod : BC_BASE_DIGS; + revdig = dig ? BC_BASE_DIGS - dig : 0; + + if (mod + revdig > BC_BASE_DIGS) places_rdx = 1; + else places_rdx = 0; + } + else places_rdx -= n->rdx; + } + + if (places_rdx) { + bc_num_expand(n, bc_vm_growSize(n->len, places_rdx)); + memmove(n->num + places_rdx, n->num, BC_NUM_SIZE(n->len)); + memset(n->num, 0, BC_NUM_SIZE(places_rdx)); + n->len += places_rdx; + } + + if (places > n->scale) n->scale = n->rdx = 0; + else { + n->scale -= places; + n->rdx = BC_NUM_RDX(n->scale); + } + + if (shift) bc_num_shift(n, BC_BASE_DIGS - dig); + + bc_num_clean(n); +} + +static void bc_num_shiftRight(BcNum *restrict n, size_t places) { + + BcBigDig dig; + size_t places_rdx, scale, scale_mod, int_len, expand; + bool shift; + + if (!places) return; + if (BC_NUM_ZERO(n)) { + n->scale += places; + bc_num_expand(n, BC_NUM_RDX(n->scale)); + return; + } + + dig = (BcBigDig) (places % BC_BASE_DIGS); + shift = (dig != 0); + scale = n->scale; + scale_mod = scale % BC_BASE_DIGS; + scale_mod = scale_mod ? scale_mod : BC_BASE_DIGS; + int_len = bc_num_int(n); + places_rdx = BC_NUM_RDX(places); + + if (scale_mod + dig > BC_BASE_DIGS) { + expand = places_rdx - 1; + places_rdx = 1; + } + else { + expand = places_rdx; + places_rdx = 0; + } + + if (expand > int_len) expand -= int_len; + else expand = 0; + + bc_num_extend(n, places_rdx * BC_BASE_DIGS); + bc_num_expand(n, bc_vm_growSize(expand, n->len)); + memset(n->num + n->len, 0, BC_NUM_SIZE(expand)); + n->len += expand; + n->scale = n->rdx = 0; + + if (shift) bc_num_shift(n, dig); + + n->scale = scale + places; + n->rdx = BC_NUM_RDX(n->scale); + + bc_num_clean(n); + + assert(n->rdx <= n->len && n->len <= n->cap); + assert(n->rdx == BC_NUM_RDX(n->scale)); +} + +static void bc_num_inv(BcNum *a, BcNum *b, size_t scale) { + + BcNum one; + BcDig num[2]; + + assert(BC_NUM_NONZERO(a)); + + bc_num_setup(&one, num, sizeof(num) / sizeof(BcDig)); + bc_num_one(&one); + + bc_num_div(&one, a, b, scale); +} + +#if BC_ENABLE_EXTRA_MATH +static void bc_num_intop(const BcNum *a, const BcNum *b, BcNum *restrict c, + BcBigDig *v) +{ + if (BC_ERR(b->rdx)) bc_vm_err(BC_ERROR_MATH_NON_INTEGER); + bc_num_copy(c, a); + bc_num_bigdig(b, v); +} +#endif // BC_ENABLE_EXTRA_MATH + +static void bc_num_as(BcNum *a, BcNum *b, BcNum *restrict c, size_t sub) { + + BcDig *ptr_c, *ptr_l, *ptr_r; + size_t i, min_rdx, max_rdx, diff, a_int, b_int, min_len, max_len, max_int; + size_t len_l, len_r; + bool b_neg, do_sub, do_rev_sub, carry; + + // Because this function doesn't need to use scale (per the bc spec), + // I am hijacking it to say whether it's doing an add or a subtract. + // Convert substraction to addition of negative second operand. + + if (BC_NUM_ZERO(b)) { + bc_num_copy(c, a); + return; + } + if (BC_NUM_ZERO(a)) { + bc_num_copy(c, b); + c->neg = (b->neg != sub); + return; + } + + // Invert sign of b if it is to be subtracted. This operation must + // preced the tests for any of the operands being zero. + b_neg = (b->neg != sub); + + // Actually add the numbers if their signs are equal, else subtract. + do_sub = (a->neg != b_neg); + + a_int = bc_num_int(a); + b_int = bc_num_int(b); + max_int = BC_MAX(a_int, b_int); + + min_rdx = BC_MIN(a->rdx, b->rdx); + max_rdx = BC_MAX(a->rdx, b->rdx); + diff = max_rdx - min_rdx; + + max_len = max_int + max_rdx; + + if (do_sub) { + + // Check whether b has to be subtracted from a or a from b. + if (a_int != b_int) do_rev_sub = (a_int < b_int); + else if (a->rdx > b->rdx) + do_rev_sub = (bc_num_compare(a->num + diff, b->num, b->len) < 0); + else + do_rev_sub = (bc_num_compare(a->num, b->num + diff, a->len) <= 0); + } + else { + + // The result array of the addition might come out one element + // longer than the bigger of the operand arrays. + max_len += 1; + do_rev_sub = (a_int < b_int); + } + + assert(max_len <= c->cap); + + if (do_rev_sub) { + ptr_l = b->num; + ptr_r = a->num; + len_l = b->len; + len_r = a->len; + } + else { + ptr_l = a->num; + ptr_r = b->num; + len_l = a->len; + len_r = b->len; + } + + ptr_c = c->num; + carry = false; + + if (diff) { + + // If the rdx values of the operands do not match, the result will + // have low end elements that are the positive or negative trailing + // elements of the operand with higher rdx value. + if ((a->rdx > b->rdx) != do_rev_sub) { + + // !do_rev_sub && a->rdx > b->rdx || do_rev_sub && b->rdx > a->rdx + // The left operand has BcDig values that need to be copied, + // either from a or from b (in case of a reversed subtraction). + memcpy(ptr_c, ptr_l, BC_NUM_SIZE(diff)); + ptr_l += diff; + len_l -= diff; + } + else { + + // The right operand has BcDig values that need to be copied + // or subtracted from zero (in case of a subtraction). + if (do_sub) { + + // do_sub (do_rev_sub && a->rdx > b->rdx || + // !do_rev_sub && b->rdx > a->rdx) + for (i = 0; i < diff; i++) + ptr_c[i] = bc_num_subDigits(0, ptr_r[i], &carry); + } + else { + + // !do_sub && b->rdx > a->rdx + memcpy(ptr_c, ptr_r, BC_NUM_SIZE(diff)); + } + + ptr_r += diff; + len_r -= diff; + } + + ptr_c += diff; + } + + min_len = BC_MIN(len_l, len_r); + + // After dealing with possible low array elements that depend on only one + // operand, the actual add or subtract can be performed as if the rdx of + // both operands was the same. + // Inlining takes care of eliminating constant zero arguments to + // addDigit/subDigit (checked in disassembly of resulting bc binary + // compiled with gcc and clang). + if (do_sub) { + for (i = 0; i < min_len; ++i) + ptr_c[i] = bc_num_subDigits(ptr_l[i], ptr_r[i], &carry); + for (; i < len_l; ++i) ptr_c[i] = bc_num_subDigits(ptr_l[i], 0, &carry); + } + else { + for (i = 0; i < min_len; ++i) + ptr_c[i] = bc_num_addDigits(ptr_l[i], ptr_r[i], &carry); + for (; i < len_l; ++i) ptr_c[i] = bc_num_addDigits(ptr_l[i], 0, &carry); + ptr_c[i] = bc_num_addDigits(0, 0, &carry); + } + + assert(carry == false); + + // The result has the same sign as a, unless the operation was a + // reverse subtraction (b - a). + c->neg = (a->neg != (do_sub && do_rev_sub)); + c->len = max_len; + c->rdx = max_rdx; + c->scale = BC_MAX(a->scale, b->scale); + + bc_num_clean(c); +} + +static void bc_num_m_simp(const BcNum *a, const BcNum *b, BcNum *restrict c) +{ + size_t i, alen = a->len, blen = b->len, clen; + BcDig *ptr_a = a->num, *ptr_b = b->num, *ptr_c; + BcBigDig sum = 0, carry = 0; + + assert(sizeof(sum) >= sizeof(BcDig) * 2); + assert(!a->rdx && !b->rdx); + + clen = bc_vm_growSize(alen, blen); + bc_num_expand(c, bc_vm_growSize(clen, 1)); + + ptr_c = c->num; + memset(ptr_c, 0, BC_NUM_SIZE(c->cap)); + + for (i = 0; i < clen; ++i) { + + ssize_t sidx = (ssize_t) (i - blen + 1); + size_t j = (size_t) BC_MAX(0, sidx), k = BC_MIN(i, blen - 1); + + for (; j < alen && k < blen; ++j, --k) { + + sum += ((BcBigDig) ptr_a[j]) * ((BcBigDig) ptr_b[k]); + + if (sum >= ((BcBigDig) BC_BASE_POW) * BC_BASE_POW) { + carry += sum / BC_BASE_POW; + sum %= BC_BASE_POW; + } + } + + if (sum >= BC_BASE_POW) { + carry += sum / BC_BASE_POW; + sum %= BC_BASE_POW; + } + + ptr_c[i] = (BcDig) sum; + assert(ptr_c[i] < BC_BASE_POW); + sum = carry; + carry = 0; + } + + // This should always be true because there should be no carry on the last + // digit; multiplication never goes above the sum of both lengths. + assert(!sum); + + c->len = clen; +} + +static void bc_num_shiftAddSub(BcNum *restrict n, const BcNum *restrict a, + size_t shift, BcNumShiftAddOp op) +{ + assert(n->len >= shift + a->len); + assert(!n->rdx && !a->rdx); + op(n->num + shift, a->num, a->len); +} + +static void bc_num_k(BcNum *a, BcNum *b, BcNum *restrict c) { + + size_t max, max2, total; + BcNum l1, h1, l2, h2, m2, m1, z0, z1, z2, temp; + BcDig *digs, *dig_ptr; + BcNumShiftAddOp op; + bool aone = BC_NUM_ONE(a); + + assert(BC_NUM_ZERO(c)); + + if (BC_NUM_ZERO(a) || BC_NUM_ZERO(b)) return; + if (aone || BC_NUM_ONE(b)) { + bc_num_copy(c, aone ? b : a); + if ((aone && a->neg) || b->neg) c->neg = !c->neg; + return; + } + if (a->len < BC_NUM_KARATSUBA_LEN || b->len < BC_NUM_KARATSUBA_LEN) { + bc_num_m_simp(a, b, c); + return; + } + + max = BC_MAX(a->len, b->len); + max = BC_MAX(max, BC_NUM_DEF_SIZE); + max2 = (max + 1) / 2; + + total = bc_vm_arraySize(BC_NUM_KARATSUBA_ALLOCS, max); + + BC_SIG_LOCK; + + digs = dig_ptr = bc_vm_malloc(BC_NUM_SIZE(total)); + + bc_num_setup(&l1, dig_ptr, max); + dig_ptr += max; + bc_num_setup(&h1, dig_ptr, max); + dig_ptr += max; + bc_num_setup(&l2, dig_ptr, max); + dig_ptr += max; + bc_num_setup(&h2, dig_ptr, max); + dig_ptr += max; + bc_num_setup(&m1, dig_ptr, max); + dig_ptr += max; + bc_num_setup(&m2, dig_ptr, max); + max = bc_vm_growSize(max, 1); + bc_num_init(&z0, max); + bc_num_init(&z1, max); + bc_num_init(&z2, max); + max = bc_vm_growSize(max, max) + 1; + bc_num_init(&temp, max); + + BC_SETJMP_LOCKED(err); + + BC_SIG_UNLOCK; + + bc_num_split(a, max2, &l1, &h1); + bc_num_split(b, max2, &l2, &h2); + + bc_num_expand(c, max); + c->len = max; + memset(c->num, 0, BC_NUM_SIZE(c->len)); + + bc_num_sub(&h1, &l1, &m1, 0); + bc_num_sub(&l2, &h2, &m2, 0); + + if (BC_NUM_NONZERO(&h1) && BC_NUM_NONZERO(&h2)) { + + bc_num_m(&h1, &h2, &z2, 0); + bc_num_clean(&z2); + + bc_num_shiftAddSub(c, &z2, max2 * 2, bc_num_addArrays); + bc_num_shiftAddSub(c, &z2, max2, bc_num_addArrays); + } + + if (BC_NUM_NONZERO(&l1) && BC_NUM_NONZERO(&l2)) { + + bc_num_m(&l1, &l2, &z0, 0); + bc_num_clean(&z0); + + bc_num_shiftAddSub(c, &z0, max2, bc_num_addArrays); + bc_num_shiftAddSub(c, &z0, 0, bc_num_addArrays); + } + + if (BC_NUM_NONZERO(&m1) && BC_NUM_NONZERO(&m2)) { + + bc_num_m(&m1, &m2, &z1, 0); + bc_num_clean(&z1); + + op = (m1.neg != m2.neg) ? bc_num_subArrays : bc_num_addArrays; + bc_num_shiftAddSub(c, &z1, max2, op); + } + +err: + BC_SIG_MAYLOCK; + free(digs); + bc_num_free(&temp); + bc_num_free(&z2); + bc_num_free(&z1); + bc_num_free(&z0); + BC_LONGJMP_CONT; +} + +static void bc_num_m(BcNum *a, BcNum *b, BcNum *restrict c, size_t scale) { + + BcNum cpa, cpb; + size_t ascale, bscale, ardx, brdx, azero = 0, bzero = 0, zero, len, rscale; + + bc_num_zero(c); + ascale = a->scale; + bscale = b->scale; + scale = BC_MAX(scale, ascale); + scale = BC_MAX(scale, bscale); + + rscale = ascale + bscale; + scale = BC_MIN(rscale, scale); + + if ((a->len == 1 || b->len == 1) && !a->rdx && !b->rdx) { + + BcNum *operand; + BcBigDig dig; + + if (a->len == 1) { + dig = (BcBigDig) a->num[0]; + operand = b; + } + else { + dig = (BcBigDig) b->num[0]; + operand = a; + } + + bc_num_mulArray(operand, dig, c); + + if (BC_NUM_NONZERO(c)) c->neg = (a->neg != b->neg); + + return; + } + + BC_SIG_LOCK; + + bc_num_init(&cpa, a->len + a->rdx); + bc_num_init(&cpb, b->len + b->rdx); + + BC_SETJMP_LOCKED(err); + + BC_SIG_UNLOCK; + + bc_num_copy(&cpa, a); + bc_num_copy(&cpb, b); + + cpa.neg = cpb.neg = false; + + ardx = cpa.rdx * BC_BASE_DIGS; + bc_num_shiftLeft(&cpa, ardx); + + brdx = cpb.rdx * BC_BASE_DIGS; + bc_num_shiftLeft(&cpb, brdx); + + // We need to reset the jump here because azero and bzero are used in the + // cleanup, and local variables are not guaranteed to be the same after a + // jump. + BC_SIG_LOCK; + + BC_UNSETJMP; + + azero = bc_num_shiftZero(&cpa); + bzero = bc_num_shiftZero(&cpb); + + BC_SETJMP_LOCKED(err); + + BC_SIG_UNLOCK; + + bc_num_clean(&cpa); + bc_num_clean(&cpb); + + bc_num_k(&cpa, &cpb, c); + + zero = bc_vm_growSize(azero, bzero); + len = bc_vm_growSize(c->len, zero); + + bc_num_expand(c, len); + bc_num_shiftLeft(c, (len - c->len) * BC_BASE_DIGS); + bc_num_shiftRight(c, ardx + brdx); + + bc_num_retireMul(c, scale, a->neg, b->neg); + +err: + BC_SIG_MAYLOCK; + bc_num_unshiftZero(&cpb, bzero); + bc_num_free(&cpb); + bc_num_unshiftZero(&cpa, azero); + bc_num_free(&cpa); + BC_LONGJMP_CONT; +} + +static bool bc_num_nonZeroDig(BcDig *restrict a, size_t len) { + size_t i; + bool nonzero = false; + for (i = len - 1; !nonzero && i < len; --i) nonzero = (a[i] != 0); + return nonzero; +} + +static ssize_t bc_num_divCmp(const BcDig *a, const BcNum *b, size_t len) { + + ssize_t cmp; + + if (b->len > len && a[len]) cmp = bc_num_compare(a, b->num, len + 1); + else if (b->len <= len) { + if (a[len]) cmp = 1; + else cmp = bc_num_compare(a, b->num, len); + } + else cmp = -1; + + return cmp; +} + +static void bc_num_divExtend(BcNum *restrict a, BcNum *restrict b, + BcBigDig divisor) +{ + size_t pow; + + assert(divisor < BC_BASE_POW); + + pow = BC_BASE_DIGS - bc_num_log10((size_t) divisor); + + bc_num_shiftLeft(a, pow); + bc_num_shiftLeft(b, pow); +} + +static void bc_num_d_long(BcNum *restrict a, BcNum *restrict b, + BcNum *restrict c, size_t scale) +{ + BcBigDig divisor; + size_t len, end, i, rdx; + BcNum cpb; + bool nonzero = false; + + assert(b->len < a->len); + len = b->len; + end = a->len - len; + assert(len >= 1); + + bc_num_expand(c, a->len); + memset(c->num, 0, c->cap * sizeof(BcDig)); + + c->rdx = a->rdx; + c->scale = a->scale; + c->len = a->len; + + divisor = (BcBigDig) b->num[len - 1]; + + if (len > 1 && bc_num_nonZeroDig(b->num, len - 1)) { + + nonzero = (divisor > 1 << ((10 * BC_BASE_DIGS) / 6 + 1)); + + if (!nonzero) { + + bc_num_divExtend(a, b, divisor); + + len = BC_MAX(a->len, b->len); + bc_num_expand(a, len + 1); + + if (len + 1 > a->len) a->len = len + 1; + + len = b->len; + end = a->len - len; + divisor = (BcBigDig) b->num[len - 1]; + + nonzero = bc_num_nonZeroDig(b->num, len - 1); + } + } + + divisor += nonzero; + + bc_num_expand(c, a->len); + memset(c->num, 0, BC_NUM_SIZE(c->cap)); + + assert(c->scale >= scale); + rdx = c->rdx - BC_NUM_RDX(scale); + + BC_SIG_LOCK; + + bc_num_init(&cpb, len + 1); + + BC_SETJMP_LOCKED(err); + + BC_SIG_UNLOCK; + + i = end - 1; + + for (; i < end && i >= rdx && BC_NUM_NONZERO(a); --i) { + + ssize_t cmp; + BcDig *n; + BcBigDig result; + + n = a->num + i; + assert(n >= a->num); + result = 0; + + cmp = bc_num_divCmp(n, b, len); + + while (cmp >= 0) { + + BcBigDig n1, dividend, q; + + n1 = (BcBigDig) n[len]; + dividend = n1 * BC_BASE_POW + (BcBigDig) n[len - 1]; + q = (dividend / divisor); + + if (q <= 1) { + q = 1; + bc_num_subArrays(n, b->num, len); + } + else { + + assert(q <= BC_BASE_POW); + + bc_num_mulArray(b, (BcBigDig) q, &cpb); + bc_num_subArrays(n, cpb.num, cpb.len); + } + + result += q; + assert(result <= BC_BASE_POW); + + if (nonzero) cmp = bc_num_divCmp(n, b, len); + else cmp = -1; + } + + assert(result < BC_BASE_POW); + + c->num[i] = (BcDig) result; + } + +err: + BC_SIG_MAYLOCK; + bc_num_free(&cpb); + BC_LONGJMP_CONT; +} + +static void bc_num_d(BcNum *a, BcNum *b, BcNum *restrict c, size_t scale) { + + size_t len; + BcNum cpa, cpb; + + if (BC_NUM_ZERO(b)) bc_vm_err(BC_ERROR_MATH_DIVIDE_BY_ZERO); + if (BC_NUM_ZERO(a)) { + bc_num_setToZero(c, scale); + return; + } + if (BC_NUM_ONE(b)) { + bc_num_copy(c, a); + bc_num_retireMul(c, scale, a->neg, b->neg); + return; + } + if (!a->rdx && !b->rdx && b->len == 1 && !scale) { + BcBigDig rem; + bc_num_divArray(a, (BcBigDig) b->num[0], c, &rem); + bc_num_retireMul(c, scale, a->neg, b->neg); + return; + } + + len = bc_num_mulReq(a, b, scale); + + BC_SIG_LOCK; + + bc_num_init(&cpa, len); + bc_num_copy(&cpa, a); + bc_num_createCopy(&cpb, b); + + BC_SETJMP_LOCKED(err); + + BC_SIG_UNLOCK; + + len = b->len; + + if (len > cpa.len) { + bc_num_expand(&cpa, bc_vm_growSize(len, 2)); + bc_num_extend(&cpa, (len - cpa.len) * BC_BASE_DIGS); + } + + cpa.scale = cpa.rdx * BC_BASE_DIGS; + + bc_num_extend(&cpa, b->scale); + cpa.rdx -= BC_NUM_RDX(b->scale); + cpa.scale = cpa.rdx * BC_BASE_DIGS; + + if (scale > cpa.scale) { + bc_num_extend(&cpa, scale); + cpa.scale = cpa.rdx * BC_BASE_DIGS; + } + + if (cpa.cap == cpa.len) bc_num_expand(&cpa, bc_vm_growSize(cpa.len, 1)); + + // We want an extra zero in front to make things simpler. + cpa.num[cpa.len++] = 0; + + if (cpa.rdx == cpa.len) cpa.len = bc_num_nonzeroLen(&cpa); + if (cpb.rdx == cpb.len) cpb.len = bc_num_nonzeroLen(&cpb); + cpb.scale = cpb.rdx = 0; + + bc_num_d_long(&cpa, &cpb, c, scale); + + bc_num_retireMul(c, scale, a->neg, b->neg); + +err: + BC_SIG_MAYLOCK; + bc_num_free(&cpb); + bc_num_free(&cpa); + BC_LONGJMP_CONT; +} + +static void bc_num_r(BcNum *a, BcNum *b, BcNum *restrict c, + BcNum *restrict d, size_t scale, size_t ts) +{ + BcNum temp; + bool neg; + + if (BC_NUM_ZERO(b)) bc_vm_err(BC_ERROR_MATH_DIVIDE_BY_ZERO); + if (BC_NUM_ZERO(a)) { + bc_num_setToZero(c, ts); + bc_num_setToZero(d, ts); + return; + } + + BC_SIG_LOCK; + + bc_num_init(&temp, d->cap); + + BC_SETJMP_LOCKED(err); + + BC_SIG_UNLOCK; + + bc_num_d(a, b, c, scale); + + if (scale) scale = ts + 1; + + bc_num_m(c, b, &temp, scale); + bc_num_sub(a, &temp, d, scale); + + if (ts > d->scale && BC_NUM_NONZERO(d)) bc_num_extend(d, ts - d->scale); + + neg = d->neg; + bc_num_retireMul(d, ts, a->neg, b->neg); + d->neg = BC_NUM_NONZERO(d) ? neg : false; + +err: + BC_SIG_MAYLOCK; + bc_num_free(&temp); + BC_LONGJMP_CONT; +} + +static void bc_num_rem(BcNum *a, BcNum *b, BcNum *restrict c, size_t scale) { + + BcNum c1; + size_t ts; + + ts = bc_vm_growSize(scale, b->scale); + ts = BC_MAX(ts, a->scale); + + BC_SIG_LOCK; + + bc_num_init(&c1, bc_num_mulReq(a, b, ts)); + + BC_SETJMP_LOCKED(err); + + BC_SIG_UNLOCK; + + bc_num_r(a, b, &c1, c, scale, ts); + +err: + BC_SIG_MAYLOCK; + bc_num_free(&c1); + BC_LONGJMP_CONT; +} + +static void bc_num_p(BcNum *a, BcNum *b, BcNum *restrict c, size_t scale) { + + BcNum copy; + BcBigDig pow = 0; + size_t i, powrdx, resrdx; + bool neg, zero; + + if (BC_ERR(b->rdx)) bc_vm_err(BC_ERROR_MATH_NON_INTEGER); + + if (BC_NUM_ZERO(b)) { + bc_num_one(c); + return; + } + if (BC_NUM_ZERO(a)) { + if (b->neg) bc_vm_err(BC_ERROR_MATH_DIVIDE_BY_ZERO); + bc_num_setToZero(c, scale); + return; + } + if (BC_NUM_ONE(b)) { + if (!b->neg) bc_num_copy(c, a); + else bc_num_inv(a, c, scale); + return; + } + + BC_SIG_LOCK; + + neg = b->neg; + b->neg = false; + bc_num_bigdig(b, &pow); + b->neg = neg; + + bc_num_createCopy(©, a); + + BC_SETJMP_LOCKED(err); + + BC_SIG_UNLOCK; + + if (!neg) { + size_t max = BC_MAX(scale, a->scale), scalepow = a->scale * pow; + scale = BC_MIN(scalepow, max); + } + + for (powrdx = a->scale; !(pow & 1); pow >>= 1) { + powrdx <<= 1; + bc_num_mul(©, ©, ©, powrdx); + } + + bc_num_copy(c, ©); + resrdx = powrdx; + + while (pow >>= 1) { + + powrdx <<= 1; + bc_num_mul(©, ©, ©, powrdx); + + if (pow & 1) { + resrdx += powrdx; + bc_num_mul(c, ©, c, resrdx); + } + } + + if (neg) bc_num_inv(c, c, scale); + + if (c->scale > scale) bc_num_truncate(c, c->scale - scale); + + // We can't use bc_num_clean() here. + for (zero = true, i = 0; zero && i < c->len; ++i) zero = !c->num[i]; + if (zero) bc_num_setToZero(c, scale); + +err: + BC_SIG_MAYLOCK; + bc_num_free(©); + BC_LONGJMP_CONT; +} + +#if BC_ENABLE_EXTRA_MATH +static void bc_num_place(BcNum *a, BcNum *b, BcNum *restrict c, size_t scale) { + + BcBigDig val = 0; + + BC_UNUSED(scale); + + bc_num_intop(a, b, c, &val); + + if (val < c->scale) bc_num_truncate(c, c->scale - val); + else if (val > c->scale) bc_num_extend(c, val - c->scale); +} + +static void bc_num_left(BcNum *a, BcNum *b, BcNum *restrict c, size_t scale) { + + BcBigDig val = 0; + + BC_UNUSED(scale); + + bc_num_intop(a, b, c, &val); + + bc_num_shiftLeft(c, (size_t) val); +} + +static void bc_num_right(BcNum *a, BcNum *b, BcNum *restrict c, size_t scale) { + + BcBigDig val = 0; + + BC_UNUSED(scale); + + bc_num_intop(a, b, c, &val); + + if (BC_NUM_ZERO(c)) return; + + bc_num_shiftRight(c, (size_t) val); +} +#endif // BC_ENABLE_EXTRA_MATH + +static void bc_num_binary(BcNum *a, BcNum *b, BcNum *c, size_t scale, + BcNumBinaryOp op, size_t req) +{ + BcNum num2, *ptr_a, *ptr_b; + bool init = false; + + assert(a != NULL && b != NULL && c != NULL && op != NULL); + + BC_SIG_LOCK; + + if (c == a) { + + ptr_a = &num2; + + memcpy(ptr_a, c, sizeof(BcNum)); + init = true; + } + else ptr_a = a; + + if (c == b) { + + ptr_b = &num2; + + if (c != a) { + memcpy(ptr_b, c, sizeof(BcNum)); + init = true; + } + } + else ptr_b = b; + + if (init) { + + bc_num_init(c, req); + + BC_SETJMP_LOCKED(err); + BC_SIG_UNLOCK; + } + else { + BC_SIG_UNLOCK; + bc_num_expand(c, req); + } + + op(ptr_a, ptr_b, c, scale); + + assert(!c->neg || BC_NUM_NONZERO(c)); + assert(c->rdx <= c->len || !c->len); + assert(!c->len || c->num[c->len - 1] || c->rdx == c->len); + +err: + if (init) { + BC_SIG_MAYLOCK; + bc_num_free(&num2); + BC_LONGJMP_CONT; + } +} + +#ifndef NDEBUG +static bool bc_num_strValid(const char *val) { + + bool radix = false; + size_t i, len = strlen(val); + + if (!len) return true; + + for (i = 0; i < len; ++i) { + + BcDig c = val[i]; + + if (c == '.') { + + if (radix) return false; + + radix = true; + continue; + } + + if (!(isdigit(c) || isupper(c))) return false; + } + + return true; +} +#endif // NDEBUG + +static BcBigDig bc_num_parseChar(char c, size_t base_t) { + + if (isupper(c)) { + c = BC_NUM_NUM_LETTER(c); + c = ((size_t) c) >= base_t ? (char) base_t - 1 : c; + } + else c -= '0'; + + return (BcBigDig) (uchar) c; +} + +static void bc_num_parseDecimal(BcNum *restrict n, const char *restrict val) { + + size_t len, i, temp, mod; + const char *ptr; + bool zero = true, rdx; + + for (i = 0; val[i] == '0'; ++i); + + val += i; + assert(!val[0] || isalnum(val[0]) || val[0] == '.'); + + // All 0's. We can just return, since this + // procedure expects a virgin (already 0) BcNum. + if (!val[0]) return; + + len = strlen(val); + + ptr = strchr(val, '.'); + rdx = (ptr != NULL); + + for (i = 0; i < len && (zero = (val[i] == '0' || val[i] == '.')); ++i); + + n->scale = (size_t) (rdx * ((val + len) - (ptr + 1))); + n->rdx = BC_NUM_RDX(n->scale); + + i = len - (ptr == val ? 0 : i) - rdx; + temp = BC_NUM_ROUND_POW(i); + mod = n->scale % BC_BASE_DIGS; + i = mod ? BC_BASE_DIGS - mod : 0; + n->len = ((temp + i) / BC_BASE_DIGS); + + bc_num_expand(n, n->len); + memset(n->num, 0, BC_NUM_SIZE(n->len)); + + if (zero) n->len = n->rdx = 0; + else { + + BcBigDig exp, pow; + + assert(i <= BC_NUM_BIGDIG_MAX); + + exp = (BcBigDig) i; + pow = bc_num_pow10[exp]; + + for (i = len - 1; i < len; --i, ++exp) { + + char c = val[i]; + + if (c == '.') exp -= 1; + else { + + size_t idx = exp / BC_BASE_DIGS; + + if (isupper(c)) c = '9'; + n->num[idx] += (((BcBigDig) c) - '0') * pow; + + if ((exp + 1) % BC_BASE_DIGS == 0) pow = 1; + else pow *= BC_BASE; + } + } + } +} + +static void bc_num_parseBase(BcNum *restrict n, const char *restrict val, + BcBigDig base) +{ + BcNum temp, mult1, mult2, result1, result2, *m1, *m2, *ptr; + char c = 0; + bool zero = true; + BcBigDig v; + size_t i, digs, len = strlen(val); + + for (i = 0; zero && i < len; ++i) zero = (val[i] == '.' || val[i] == '0'); + if (zero) return; + + BC_SIG_LOCK; + + bc_num_init(&temp, BC_NUM_BIGDIG_LOG10); + bc_num_init(&mult1, BC_NUM_BIGDIG_LOG10); + + BC_SETJMP_LOCKED(int_err); + + BC_SIG_UNLOCK; + + for (i = 0; i < len && (c = val[i]) && c != '.'; ++i) { + + v = bc_num_parseChar(c, base); + + bc_num_mulArray(n, base, &mult1); + bc_num_bigdig2num(&temp, v); + bc_num_add(&mult1, &temp, n, 0); + } + + if (i == len && !(c = val[i])) goto int_err; + + assert(c == '.'); + + BC_SIG_LOCK; + + BC_UNSETJMP; + + bc_num_init(&mult2, BC_NUM_BIGDIG_LOG10); + bc_num_init(&result1, BC_NUM_DEF_SIZE); + bc_num_init(&result2, BC_NUM_DEF_SIZE); + bc_num_one(&mult1); + + BC_SETJMP_LOCKED(err); + + BC_SIG_UNLOCK; + + m1 = &mult1; + m2 = &mult2; + + for (i += 1, digs = 0; i < len && (c = val[i]); ++i, ++digs) { + + v = bc_num_parseChar(c, base); + + bc_num_mulArray(&result1, base, &result2); + + bc_num_bigdig2num(&temp, v); + bc_num_add(&result2, &temp, &result1, 0); + bc_num_mulArray(m1, base, m2); + + if (m2->len < m2->rdx) m2->len = m2->rdx; + + ptr = m1; + m1 = m2; + m2 = ptr; + } + + // This one cannot be a divide by 0 because mult starts out at 1, then is + // multiplied by base, and base cannot be 0, so mult cannot be 0. + bc_num_div(&result1, m1, &result2, digs * 2); + bc_num_truncate(&result2, digs); + bc_num_add(n, &result2, n, digs); + + if (BC_NUM_NONZERO(n)) { + if (n->scale < digs) bc_num_extend(n, digs - n->scale); + } + else bc_num_zero(n); + +err: + BC_SIG_MAYLOCK; + bc_num_free(&result2); + bc_num_free(&result1); + bc_num_free(&mult2); +int_err: + BC_SIG_MAYLOCK; + bc_num_free(&mult1); + bc_num_free(&temp); + BC_LONGJMP_CONT; +} + +static void bc_num_printNewline(void) { + if (vm.nchars >= vm.line_len - 1) { + bc_vm_putchar('\\'); + bc_vm_putchar('\n'); + } +} + +static void bc_num_putchar(int c) { + if (c != '\n') bc_num_printNewline(); + bc_vm_putchar(c); +} + +#if DC_ENABLED +static void bc_num_printChar(size_t n, size_t len, bool rdx) { + BC_UNUSED(rdx); + BC_UNUSED(len); + assert(len == 1); + bc_vm_putchar((uchar) n); +} +#endif // DC_ENABLED + +static void bc_num_printDigits(size_t n, size_t len, bool rdx) { + + size_t exp, pow; + + bc_num_putchar(rdx ? '.' : ' '); + + for (exp = 0, pow = 1; exp < len - 1; ++exp, pow *= BC_BASE); + + for (exp = 0; exp < len; pow /= BC_BASE, ++exp) { + size_t dig = n / pow; + n -= dig * pow; + bc_num_putchar(((uchar) dig) + '0'); + } +} + +static void bc_num_printHex(size_t n, size_t len, bool rdx) { + + BC_UNUSED(len); + + assert(len == 1); + + if (rdx) bc_num_putchar('.'); + + bc_num_putchar(bc_num_hex_digits[n]); +} + +static void bc_num_printDecimal(const BcNum *restrict n) { + + size_t i, j, rdx = n->rdx; + bool zero = true; + size_t buffer[BC_BASE_DIGS]; + + if (n->neg) bc_num_putchar('-'); + + for (i = n->len - 1; i < n->len; --i) { + + BcDig n9 = n->num[i]; + size_t temp; + bool irdx = (i == rdx - 1); + + zero = (zero & !irdx); + temp = n->scale % BC_BASE_DIGS; + temp = i || !temp ? 0 : BC_BASE_DIGS - temp; + + memset(buffer, 0, BC_BASE_DIGS * sizeof(size_t)); + + for (j = 0; n9 && j < BC_BASE_DIGS; ++j) { + buffer[j] = n9 % BC_BASE; + n9 /= BC_BASE; + } + + for (j = BC_BASE_DIGS - 1; j < BC_BASE_DIGS && j >= temp; --j) { + bool print_rdx = (irdx & (j == BC_BASE_DIGS - 1)); + zero = (zero && buffer[j] == 0); + if (!zero) bc_num_printHex(buffer[j], 1, print_rdx); + } + } +} + +#if BC_ENABLE_EXTRA_MATH +static void bc_num_printExponent(const BcNum *restrict n, bool eng) { + + bool neg = (n->len <= n->rdx); + BcNum temp, exp; + size_t places, mod; + BcDig digs[BC_NUM_BIGDIG_LOG10]; + + BC_SIG_LOCK; + + bc_num_createCopy(&temp, n); + + BC_SETJMP_LOCKED(exit); + + BC_SIG_UNLOCK; + + if (neg) { + + size_t i, idx = bc_num_nonzeroLen(n) - 1; + + places = 1; + + for (i = BC_BASE_DIGS - 1; i < BC_BASE_DIGS; --i) { + if (bc_num_pow10[i] > (BcBigDig) n->num[idx]) places += 1; + else break; + } + + places += (n->rdx - (idx + 1)) * BC_BASE_DIGS; + mod = places % 3; + + if (eng && mod != 0) places += 3 - mod; + bc_num_shiftLeft(&temp, places); + } + else { + places = bc_num_intDigits(n) - 1; + mod = places % 3; + if (eng && mod != 0) places -= 3 - (3 - mod); + bc_num_shiftRight(&temp, places); + } + + bc_num_printDecimal(&temp); + bc_num_putchar('e'); + + if (!places) { + bc_num_printHex(0, 1, false); + goto exit; + } + + if (neg) bc_num_putchar('-'); + + bc_num_setup(&exp, digs, BC_NUM_BIGDIG_LOG10); + bc_num_bigdig2num(&exp, (BcBigDig) places); + + bc_num_printDecimal(&exp); + +exit: + BC_SIG_MAYLOCK; + bc_num_free(&temp); + BC_LONGJMP_CONT; +} +#endif // BC_ENABLE_EXTRA_MATH + +static void bc_num_printFixup(BcNum *restrict n, BcBigDig rem, + BcBigDig pow, size_t idx) +{ + size_t i, len = n->len - idx; + BcBigDig acc; + BcDig *a = n->num + idx; + + if (len < 2) return; + + for (i = len - 1; i > 0; --i) { + + acc = ((BcBigDig) a[i]) * rem + ((BcBigDig) a[i - 1]); + a[i - 1] = (BcDig) (acc % pow); + acc /= pow; + acc += (BcBigDig) a[i]; + + if (acc >= BC_BASE_POW) { + + if (i == len - 1) { + len = bc_vm_growSize(len, 1); + bc_num_expand(n, bc_vm_growSize(len, idx)); + a = n->num + idx; + a[len - 1] = 0; + } + + a[i + 1] += acc / BC_BASE_POW; + acc %= BC_BASE_POW; + } + + assert(acc < BC_BASE_POW); + a[i] = (BcDig) acc; + } + + n->len = len + idx; +} + +static void bc_num_printPrepare(BcNum *restrict n, BcBigDig rem, + BcBigDig pow) +{ + size_t i; + + for (i = 0; i < n->len; ++i) bc_num_printFixup(n, rem, pow, i); + + for (i = 0; i < n->len; ++i) { + + assert(pow == ((BcBigDig) ((BcDig) pow))); + + if (n->num[i] >= (BcDig) pow) { + + if (i + 1 == n->len) { + n->len = bc_vm_growSize(n->len, 1); + bc_num_expand(n, n->len); + n->num[i + 1] = 0; + } + + assert(pow < BC_BASE_POW); + n->num[i + 1] += n->num[i] / ((BcDig) pow); + n->num[i] %= (BcDig) pow; + } + } +} + +static void bc_num_printNum(BcNum *restrict n, BcBigDig base, + size_t len, BcNumDigitOp print) +{ + BcVec stack; + BcNum intp, fracp1, fracp2, digit, flen1, flen2, *n1, *n2, *temp; + BcBigDig dig = 0, *ptr, acc, exp; + size_t i, j; + bool radix; + BcDig digit_digs[BC_NUM_BIGDIG_LOG10 + 1]; + + assert(base > 1); + + if (BC_NUM_ZERO(n)) { + print(0, len, false); + return; + } + + // This function uses an algorithm that Stefan Esser <se@freebsd.org> came + // up with to print the integer part of a number. What it does is convert + // intp into a number of the specified base, but it does it directly, + // instead of just doing a series of divisions and printing the remainders + // in reverse order. + // + // Let me explain in a bit more detail: + // + // The algorithm takes the current least significant digit (after intp has + // been converted to an integer) and the next to least significant digit, + // and it converts the least significant digit into one of the specified + // base, putting any overflow into the next to least significant digit. It + // iterates through the whole number, from least significant to most + // significant, doing this conversion. At the end of that iteration, the + // least significant digit is converted, but the others are not, so it + // iterates again, starting at the next to least significant digit. It keeps + // doing that conversion, skipping one more digit than the last time, until + // all digits have been converted. Then it prints them in reverse order. + // + // That is the gist of the algorithm. It leaves out several things, such as + // the fact that digits are not always converted into the specified base, + // but into something close, basically a power of the specified base. In + // Stefan's words, "You could consider BcDigs to be of base 10^BC_BASE_DIGS + // in the normal case and obase^N for the largest value of N that satisfies + // obase^N <= 10^BC_BASE_DIGS. [This means that] the result is not in base + // "obase", but in base "obase^N", which happens to be printable as a number + // of base "obase" without consideration for neighbouring BcDigs." This fact + // is what necessitates the existence of the loop later in this function. + // + // The conversion happens in bc_num_printPrepare() where the outer loop + // happens and bc_num_printFixup() where the inner loop, or actual + // conversion, happens. + + BC_SIG_LOCK; + + bc_vec_init(&stack, sizeof(BcBigDig), NULL); + bc_num_init(&fracp1, n->rdx); + + bc_num_createCopy(&intp, n); + + BC_SETJMP_LOCKED(err); + + BC_SIG_UNLOCK; + + bc_num_truncate(&intp, intp.scale); + + bc_num_sub(n, &intp, &fracp1, 0); + + if (base != vm.last_base) { + + vm.last_pow = 1; + vm.last_exp = 0; + + while (vm.last_pow * base <= BC_BASE_POW) { + vm.last_pow *= base; + vm.last_exp += 1; + } + + vm.last_rem = BC_BASE_POW - vm.last_pow; + vm.last_base = base; + } + + exp = vm.last_exp; + + if (vm.last_rem != 0) bc_num_printPrepare(&intp, vm.last_rem, vm.last_pow); + + for (i = 0; i < intp.len; ++i) { + + acc = (BcBigDig) intp.num[i]; + + for (j = 0; j < exp && (i < intp.len - 1 || acc != 0); ++j) + { + if (j != exp - 1) { + dig = acc % base; + acc /= base; + } + else { + dig = acc; + acc = 0; + } + + assert(dig < base); + + bc_vec_push(&stack, &dig); + } + + assert(acc == 0); + } + + for (i = 0; i < stack.len; ++i) { + ptr = bc_vec_item_rev(&stack, i); + assert(ptr != NULL); + print(*ptr, len, false); + } + + if (!n->scale) goto err; + + BC_SIG_LOCK; + + BC_UNSETJMP; + + bc_num_init(&fracp2, n->rdx); + bc_num_setup(&digit, digit_digs, sizeof(digit_digs) / sizeof(BcDig)); + bc_num_init(&flen1, BC_NUM_BIGDIG_LOG10); + bc_num_init(&flen2, BC_NUM_BIGDIG_LOG10); + + BC_SETJMP_LOCKED(frac_err); + + BC_SIG_UNLOCK; + + bc_num_one(&flen1); + + radix = true; + n1 = &flen1; + n2 = &flen2; + + fracp2.scale = n->scale; + fracp2.rdx = BC_NUM_RDX(fracp2.scale); + + while (bc_num_intDigits(n1) < n->scale + 1) { + + bc_num_expand(&fracp2, fracp1.len + 1); + bc_num_mulArray(&fracp1, base, &fracp2); + if (fracp2.len < fracp2.rdx) fracp2.len = fracp2.rdx; + + // fracp is guaranteed to be non-negative and small enough. + bc_num_bigdig2(&fracp2, &dig); + + bc_num_bigdig2num(&digit, dig); + bc_num_sub(&fracp2, &digit, &fracp1, 0); + + print(dig, len, radix); + bc_num_mulArray(n1, base, n2); + + radix = false; + temp = n1; + n1 = n2; + n2 = temp; + } + +frac_err: + BC_SIG_MAYLOCK; + bc_num_free(&flen2); + bc_num_free(&flen1); + bc_num_free(&fracp2); +err: + BC_SIG_MAYLOCK; + bc_num_free(&fracp1); + bc_num_free(&intp); + bc_vec_free(&stack); + BC_LONGJMP_CONT; +} + +static void bc_num_printBase(BcNum *restrict n, BcBigDig base) { + + size_t width; + BcNumDigitOp print; + bool neg = n->neg; + + if (neg) bc_num_putchar('-'); + + n->neg = false; + + if (base <= BC_NUM_MAX_POSIX_IBASE) { + width = 1; + print = bc_num_printHex; + } + else { + assert(base <= BC_BASE_POW); + width = bc_num_log10(base - 1); + print = bc_num_printDigits; + } + + bc_num_printNum(n, base, width, print); + n->neg = neg; +} + +#if DC_ENABLED +void bc_num_stream(BcNum *restrict n, BcBigDig base) { + bc_num_printNum(n, base, 1, bc_num_printChar); +} +#endif // DC_ENABLED + +void bc_num_setup(BcNum *restrict n, BcDig *restrict num, size_t cap) { + assert(n != NULL); + n->num = num; + n->cap = cap; + bc_num_zero(n); +} + +void bc_num_init(BcNum *restrict n, size_t req) { + + BcDig *num; + + BC_SIG_ASSERT_LOCKED; + + assert(n != NULL); + + req = req >= BC_NUM_DEF_SIZE ? req : BC_NUM_DEF_SIZE; + + if (req == BC_NUM_DEF_SIZE && vm.temps.len) { + BcNum *nptr = bc_vec_top(&vm.temps); + num = nptr->num; + req = nptr->cap; + bc_vec_pop(&vm.temps); + } + else num = bc_vm_malloc(BC_NUM_SIZE(req)); + + bc_num_setup(n, num, req); +} + +void bc_num_clear(BcNum *restrict n) { + n->num = NULL; + n->cap = 0; +} + +void bc_num_free(void *num) { + + BcNum *n = (BcNum*) num; + + BC_SIG_ASSERT_LOCKED; + + assert(n != NULL); + + if (n->cap == BC_NUM_DEF_SIZE) bc_vec_push(&vm.temps, n); + else free(n->num); +} + +void bc_num_copy(BcNum *d, const BcNum *s) { + assert(d != NULL && s != NULL); + if (d == s) return; + bc_num_expand(d, s->len); + d->len = s->len; + d->neg = s->neg; + d->rdx = s->rdx; + d->scale = s->scale; + memcpy(d->num, s->num, BC_NUM_SIZE(d->len)); +} + +void bc_num_createCopy(BcNum *d, const BcNum *s) { + BC_SIG_ASSERT_LOCKED; + bc_num_init(d, s->len); + bc_num_copy(d, s); +} + +void bc_num_createFromBigdig(BcNum *n, BcBigDig val) { + BC_SIG_ASSERT_LOCKED; + bc_num_init(n, (BC_NUM_BIGDIG_LOG10 - 1) / BC_BASE_DIGS + 1); + bc_num_bigdig2num(n, val); +} + +size_t bc_num_scale(const BcNum *restrict n) { + return n->scale; +} + +size_t bc_num_len(const BcNum *restrict n) { + + size_t len = n->len; + + if (BC_NUM_ZERO(n)) return 0; + + if (n->rdx == len) { + + size_t zero, scale; + + len = bc_num_nonzeroLen(n); + + scale = n->scale % BC_BASE_DIGS; + scale = scale ? scale : BC_BASE_DIGS; + + zero = bc_num_zeroDigits(n->num + len - 1); + + len = len * BC_BASE_DIGS - zero - (BC_BASE_DIGS - scale); + } + else len = bc_num_intDigits(n) + n->scale; + + return len; +} + +void bc_num_parse(BcNum *restrict n, const char *restrict val, + BcBigDig base, bool letter) +{ + assert(n != NULL && val != NULL && base); + assert(base >= BC_NUM_MIN_BASE && base <= vm.maxes[BC_PROG_GLOBALS_IBASE]); + assert(bc_num_strValid(val)); + + if (letter) { + BcBigDig dig = bc_num_parseChar(val[0], BC_NUM_MAX_LBASE); + bc_num_bigdig2num(n, dig); + } + else if (base == BC_BASE) bc_num_parseDecimal(n, val); + else bc_num_parseBase(n, val, base); +} + +void bc_num_print(BcNum *restrict n, BcBigDig base, bool newline) { + + assert(n != NULL); + assert(BC_ENABLE_EXTRA_MATH || base >= BC_NUM_MIN_BASE); + + bc_num_printNewline(); + + if (BC_NUM_ZERO(n)) bc_num_printHex(0, 1, false); + else if (base == BC_BASE) bc_num_printDecimal(n); +#if BC_ENABLE_EXTRA_MATH + else if (base == 0 || base == 1) + bc_num_printExponent(n, base != 0); +#endif // BC_ENABLE_EXTRA_MATH + else bc_num_printBase(n, base); + + if (newline) bc_num_putchar('\n'); +} + +void bc_num_bigdig2(const BcNum *restrict n, BcBigDig *result) { + + // This function returns no errors because it's guaranteed to succeed if + // its preconditions are met. Those preconditions include both parameters + // being non-NULL, n being non-negative, and n being less than vm.max. If + // all of that is true, then we can just convert without worrying about + // negative errors or overflow. We also don't care about signals because + // this function should execute in only a few iterations, meaning that + // ignoring signals here should be fine. + + BcBigDig r = 0; + + assert(n != NULL && result != NULL); + assert(!n->neg); + assert(bc_num_cmp(n, &vm.max) < 0); + assert(n->len - n->rdx <= 3); + + // There is a small speed win from unrolling the loop here, and since it + // only adds 53 bytes, I decided that it was worth it. + switch (n->len - n->rdx) { + case 3: + r = (BcBigDig) n->num[n->rdx + 2]; + // Fallthrough. + case 2: + r = r * BC_BASE_POW + (BcBigDig) n->num[n->rdx + 1]; + // Fallthrough. + case 1: + r = r * BC_BASE_POW + (BcBigDig) n->num[n->rdx]; + } + + *result = r; +} + +void bc_num_bigdig(const BcNum *restrict n, BcBigDig *result) { + + assert(n != NULL && result != NULL); + + if (BC_ERR(n->neg)) bc_vm_err(BC_ERROR_MATH_NEGATIVE); + if (BC_ERR(bc_num_cmp(n, &vm.max) >= 0)) + bc_vm_err(BC_ERROR_MATH_OVERFLOW); + + bc_num_bigdig2(n, result); +} + +void bc_num_bigdig2num(BcNum *restrict n, BcBigDig val) { + + BcDig *ptr; + size_t i; + + assert(n != NULL); + + bc_num_zero(n); + + if (!val) return; + + bc_num_expand(n, BC_NUM_BIGDIG_LOG10); + + for (ptr = n->num, i = 0; val; ++i, val /= BC_BASE_POW) + ptr[i] = val % BC_BASE_POW; + + n->len = i; +} + +#if BC_ENABLE_EXTRA_MATH +void bc_num_rng(const BcNum *restrict n, BcRNG *rng) { + + BcNum pow, temp, temp2, intn, frac; + BcRand state1, state2, inc1, inc2; + BcDig pow_num[BC_RAND_NUM_SIZE]; + + bc_num_setup(&pow, pow_num, sizeof(pow_num) / sizeof(BcDig)); + + BC_SIG_LOCK; + + bc_num_init(&temp, n->len); + bc_num_init(&temp2, n->len); + bc_num_init(&frac, n->rdx); + bc_num_init(&intn, bc_num_int(n)); + + BC_SETJMP_LOCKED(err); + + BC_SIG_UNLOCK; + + bc_num_mul(&vm.max, &vm.max, &pow, 0); + + memcpy(frac.num, n->num, BC_NUM_SIZE(n->rdx)); + frac.len = n->rdx; + frac.rdx = n->rdx; + frac.scale = n->scale; + + bc_num_mul(&frac, &pow, &temp, 0); + + bc_num_truncate(&temp, temp.scale); + bc_num_copy(&frac, &temp); + + memcpy(intn.num, n->num + n->rdx, BC_NUM_SIZE(bc_num_int(n))); + intn.len = bc_num_int(n); + + // This assert is here because it has to be true. It is also here to justify + // the use of BC_ERROR_SIGNAL_ONLY() on each of the divmod's and mod's + // below. + assert(BC_NUM_NONZERO(&vm.max)); + + if (BC_NUM_NONZERO(&frac)) { + + bc_num_divmod(&frac, &vm.max, &temp, &temp2, 0); + + // frac is guaranteed to be smaller than vm.max * vm.max (pow). + // This means that when dividing frac by vm.max, as above, the + // quotient and remainder are both guaranteed to be less than vm.max, + // which means we can use bc_num_bigdig2() here and not worry about + // overflow. + bc_num_bigdig2(&temp2, (BcBigDig*) &state1); + bc_num_bigdig2(&temp, (BcBigDig*) &state2); + } + else state1 = state2 = 0; + + if (BC_NUM_NONZERO(&intn)) { + + bc_num_divmod(&intn, &vm.max, &temp, &temp2, 0); + + // Because temp2 is the mod of vm.max, from above, it is guaranteed + // to be small enough to use bc_num_bigdig2(). + bc_num_bigdig2(&temp2, (BcBigDig*) &inc1); + + if (bc_num_cmp(&temp, &vm.max) >= 0) { + bc_num_copy(&temp2, &temp); + bc_num_mod(&temp2, &vm.max, &temp, 0); + } + + // The if statement above ensures that temp is less than vm.max, which + // means that we can use bc_num_bigdig2() here. + bc_num_bigdig2(&temp, (BcBigDig*) &inc2); + } + else inc1 = inc2 = 0; + + bc_rand_seed(rng, state1, state2, inc1, inc2); + +err: + BC_SIG_MAYLOCK; + bc_num_free(&intn); + bc_num_free(&frac); + bc_num_free(&temp2); + bc_num_free(&temp); + BC_LONGJMP_CONT; +} + +void bc_num_createFromRNG(BcNum *restrict n, BcRNG *rng) { + + BcRand s1, s2, i1, i2; + BcNum pow, conv, temp1, temp2, temp3; + BcDig pow_num[BC_RAND_NUM_SIZE]; + BcDig temp1_num[BC_RAND_NUM_SIZE], temp2_num[BC_RAND_NUM_SIZE]; + BcDig conv_num[BC_NUM_BIGDIG_LOG10]; + + BC_SIG_LOCK; + + bc_num_init(&temp3, 2 * BC_RAND_NUM_SIZE); + + BC_SETJMP_LOCKED(err); + + BC_SIG_UNLOCK; + + bc_num_setup(&pow, pow_num, sizeof(pow_num) / sizeof(BcDig)); + bc_num_setup(&temp1, temp1_num, sizeof(temp1_num) / sizeof(BcDig)); + bc_num_setup(&temp2, temp2_num, sizeof(temp2_num) / sizeof(BcDig)); + bc_num_setup(&conv, conv_num, sizeof(conv_num) / sizeof(BcDig)); + + // This assert is here because it has to be true. It is also here to justify + // the assumption that pow is not zero. + assert(BC_NUM_NONZERO(&vm.max)); + + bc_num_mul(&vm.max, &vm.max, &pow, 0); + + // Because this is true, we can just use BC_ERROR_SIGNAL_ONLY() below when + // dividing by pow. + assert(BC_NUM_NONZERO(&pow)); + + bc_rand_getRands(rng, &s1, &s2, &i1, &i2); + + bc_num_bigdig2num(&conv, (BcBigDig) s2); + + bc_num_mul(&conv, &vm.max, &temp1, 0); + + bc_num_bigdig2num(&conv, (BcBigDig) s1); + + bc_num_add(&conv, &temp1, &temp2, 0); + + bc_num_div(&temp2, &pow, &temp3, BC_RAND_STATE_BITS); + + bc_num_bigdig2num(&conv, (BcBigDig) i2); + + bc_num_mul(&conv, &vm.max, &temp1, 0); + + bc_num_bigdig2num(&conv, (BcBigDig) i1); + + bc_num_add(&conv, &temp1, &temp2, 0); + + bc_num_add(&temp2, &temp3, n, 0); + +err: + BC_SIG_MAYLOCK; + bc_num_free(&temp3); + BC_LONGJMP_CONT; +} + +void bc_num_irand(const BcNum *restrict a, BcNum *restrict b, + BcRNG *restrict rng) +{ + BcRand r; + BcBigDig modl; + BcNum pow, pow2, cp, cp2, mod, temp1, temp2, rand; + BcNum *p1, *p2, *t1, *t2, *c1, *c2, *tmp; + BcDig rand_num[BC_NUM_BIGDIG_LOG10]; + bool carry; + ssize_t cmp; + + assert(a != b); + + if (BC_ERR(a->neg)) bc_vm_err(BC_ERROR_MATH_NEGATIVE); + if (BC_ERR(a->rdx)) bc_vm_err(BC_ERROR_MATH_NON_INTEGER); + if (BC_NUM_ZERO(a) || BC_NUM_ONE(a)) return; + + cmp = bc_num_cmp(a, &vm.max); + + if (cmp <= 0) { + + BcRand bits = 0; + + if (cmp < 0) bc_num_bigdig2(a, (BcBigDig*) &bits); + + // This condition means that bits is a power of 2. In that case, we + // can just grab a full-size int and mask out the unneeded bits. + // Also, this condition says that 0 is a power of 2, which works for + // us, since a value of 0 means a == rng->max. The bitmask will mask + // nothing in that case as well. + if (!(bits & (bits - 1))) r = bc_rand_int(rng) & (bits - 1); + else r = bc_rand_bounded(rng, bits); + + // We made sure that r is less than vm.max, + // so we can use bc_num_bigdig2() here. + bc_num_bigdig2num(b, r); + + return; + } + + // In the case where a is less than rng->max, we have to make sure we have + // an exclusive bound. This ensures that it happens. (See below.) + carry = (cmp < 0); + + BC_SIG_LOCK; + + bc_num_createCopy(&cp, a); + + bc_num_init(&cp2, cp.len); + bc_num_init(&mod, BC_NUM_BIGDIG_LOG10); + bc_num_init(&temp1, BC_NUM_DEF_SIZE); + bc_num_init(&temp2, BC_NUM_DEF_SIZE); + bc_num_init(&pow2, BC_NUM_DEF_SIZE); + bc_num_init(&pow, BC_NUM_DEF_SIZE); + bc_num_one(&pow); + bc_num_setup(&rand, rand_num, sizeof(rand_num) / sizeof(BcDig)); + + BC_SETJMP_LOCKED(err); + + BC_SIG_UNLOCK; + + p1 = &pow; + p2 = &pow2; + t1 = &temp1; + t2 = &temp2; + c1 = &cp; + c2 = &cp2; + + // This assert is here because it has to be true. It is also here to justify + // the use of BC_ERROR_SIGNAL_ONLY() on each of the divmod's and mod's + // below. + assert(BC_NUM_NONZERO(&vm.max)); + + while (BC_NUM_NONZERO(c1)) { + + bc_num_divmod(c1, &vm.max, c2, &mod, 0); + + // Because mod is the mod of vm.max, it is guaranteed to be smaller, + // which means we can use bc_num_bigdig2() here. + bc_num_bigdig(&mod, &modl); + + if (bc_num_cmp(c1, &vm.max) < 0) { + + // In this case, if there is no carry, then we know we can generate + // an integer *equal* to modl. Thus, we add one if there is no + // carry. Otherwise, we add zero, and we are still bounded properly. + // Since the last portion is guaranteed to be greater than 1, we + // know modl isn't 0 unless there is no carry. + modl += !carry; + + if (modl == 1) r = 0; + else if (!modl) r = bc_rand_int(rng); + else r = bc_rand_bounded(rng, (BcRand) modl); + } + else { + if (modl) modl -= carry; + r = bc_rand_int(rng); + carry = (r >= (BcRand) modl); + } + + bc_num_bigdig2num(&rand, r); + + bc_num_mul(&rand, p1, p2, 0); + bc_num_add(p2, t1, t2, 0); + + if (BC_NUM_NONZERO(c2)) { + + bc_num_mul(&vm.max, p1, p2, 0); + + tmp = p1; + p1 = p2; + p2 = tmp; + + tmp = c1; + c1 = c2; + c2 = tmp; + } + else c1 = c2; + + tmp = t1; + t1 = t2; + t2 = tmp; + } + + bc_num_copy(b, t1); + bc_num_clean(b); + +err: + BC_SIG_MAYLOCK; + bc_num_free(&pow); + bc_num_free(&pow2); + bc_num_free(&temp2); + bc_num_free(&temp1); + bc_num_free(&mod); + bc_num_free(&cp2); + bc_num_free(&cp); + BC_LONGJMP_CONT; +} +#endif // BC_ENABLE_EXTRA_MATH + +size_t bc_num_addReq(const BcNum *a, const BcNum *b, size_t scale) { + + size_t aint, bint, ardx, brdx; + + BC_UNUSED(scale); + + ardx = a->rdx; + aint = bc_num_int(a); + assert(aint <= a->len && ardx <= a->len); + + brdx = b->rdx; + bint = bc_num_int(b); + assert(bint <= b->len && brdx <= b->len); + + ardx = BC_MAX(ardx, brdx); + aint = BC_MAX(aint, bint); + + return bc_vm_growSize(bc_vm_growSize(ardx, aint), 1); +} + +size_t bc_num_mulReq(const BcNum *a, const BcNum *b, size_t scale) { + size_t max, rdx; + rdx = bc_vm_growSize(a->rdx, b->rdx); + max = BC_NUM_RDX(scale); + max = bc_vm_growSize(BC_MAX(max, rdx), 1); + rdx = bc_vm_growSize(bc_vm_growSize(bc_num_int(a), bc_num_int(b)), max); + return rdx; +} + +size_t bc_num_powReq(const BcNum *a, const BcNum *b, size_t scale) { + BC_UNUSED(scale); + return bc_vm_growSize(bc_vm_growSize(a->len, b->len), 1); +} + +#if BC_ENABLE_EXTRA_MATH +size_t bc_num_placesReq(const BcNum *a, const BcNum *b, size_t scale) { + BC_UNUSED(scale); + return a->len + b->len - a->rdx - b->rdx; +} +#endif // BC_ENABLE_EXTRA_MATH + +void bc_num_add(BcNum *a, BcNum *b, BcNum *c, size_t scale) { + bc_num_binary(a, b, c, false, bc_num_as, bc_num_addReq(a, b, scale)); +} + +void bc_num_sub(BcNum *a, BcNum *b, BcNum *c, size_t scale) { + bc_num_binary(a, b, c, true, bc_num_as, bc_num_addReq(a, b, scale)); +} + +void bc_num_mul(BcNum *a, BcNum *b, BcNum *c, size_t scale) { + bc_num_binary(a, b, c, scale, bc_num_m, bc_num_mulReq(a, b, scale)); +} + +void bc_num_div(BcNum *a, BcNum *b, BcNum *c, size_t scale) { + bc_num_binary(a, b, c, scale, bc_num_d, bc_num_mulReq(a, b, scale)); +} + +void bc_num_mod(BcNum *a, BcNum *b, BcNum *c, size_t scale) { + bc_num_binary(a, b, c, scale, bc_num_rem, bc_num_mulReq(a, b, scale)); +} + +void bc_num_pow(BcNum *a, BcNum *b, BcNum *c, size_t scale) { + bc_num_binary(a, b, c, scale, bc_num_p, bc_num_powReq(a, b, scale)); +} + +#if BC_ENABLE_EXTRA_MATH +void bc_num_places(BcNum *a, BcNum *b, BcNum *c, size_t scale) { + bc_num_binary(a, b, c, scale, bc_num_place, bc_num_placesReq(a, b, scale)); +} + +void bc_num_lshift(BcNum *a, BcNum *b, BcNum *c, size_t scale) { + bc_num_binary(a, b, c, scale, bc_num_left, bc_num_placesReq(a, b, scale)); +} + +void bc_num_rshift(BcNum *a, BcNum *b, BcNum *c, size_t scale) { + bc_num_binary(a, b, c, scale, bc_num_right, bc_num_placesReq(a, b, scale)); +} +#endif // BC_ENABLE_EXTRA_MATH + +void bc_num_sqrt(BcNum *restrict a, BcNum *restrict b, size_t scale) { + + BcNum num1, num2, half, f, fprime, *x0, *x1, *temp; + size_t pow, len, rdx, req, digs, digs1, digs2, resscale; + BcDig half_digs[1]; + + assert(a != NULL && b != NULL && a != b); + + if (BC_ERR(a->neg)) bc_vm_err(BC_ERROR_MATH_NEGATIVE); + + if (a->scale > scale) scale = a->scale; + + len = bc_vm_growSize(bc_num_intDigits(a), 1); + rdx = BC_NUM_RDX(scale); + req = bc_vm_growSize(BC_MAX(rdx, a->rdx), len >> 1); + + BC_SIG_LOCK; + + bc_num_init(b, bc_vm_growSize(req, 1)); + + BC_SIG_UNLOCK; + + if (BC_NUM_ZERO(a)) { + bc_num_setToZero(b, scale); + return; + } + if (BC_NUM_ONE(a)) { + bc_num_one(b); + bc_num_extend(b, scale); + return; + } + + rdx = BC_NUM_RDX(scale); + rdx = BC_MAX(rdx, a->rdx); + len = bc_vm_growSize(a->len, rdx); + + BC_SIG_LOCK; + + bc_num_init(&num1, len); + bc_num_init(&num2, len); + bc_num_setup(&half, half_digs, sizeof(half_digs) / sizeof(BcDig)); + + bc_num_one(&half); + half.num[0] = BC_BASE_POW / 2; + half.len = 1; + half.rdx = 1; + half.scale = 1; + + bc_num_init(&f, len); + bc_num_init(&fprime, len); + + BC_SETJMP_LOCKED(err); + + BC_SIG_UNLOCK; + + x0 = &num1; + x1 = &num2; + + bc_num_one(x0); + pow = bc_num_intDigits(a); + + if (pow) { + + if (pow & 1) x0->num[0] = 2; + else x0->num[0] = 6; + + pow -= 2 - (pow & 1); + bc_num_shiftLeft(x0, pow / 2); + } + + x0->scale = x0->rdx = digs = digs1 = digs2 = 0; + resscale = (scale + BC_BASE_DIGS) + 2; + + while (bc_num_cmp(x1, x0)) { + + assert(BC_NUM_NONZERO(x0)); + + bc_num_div(a, x0, &f, resscale); + bc_num_add(x0, &f, &fprime, resscale); + bc_num_mul(&fprime, &half, x1, resscale); + + temp = x0; + x0 = x1; + x1 = temp; + } + + bc_num_copy(b, x0); + if (b->scale > scale) bc_num_truncate(b, b->scale - scale); + + assert(!b->neg || BC_NUM_NONZERO(b)); + assert(b->rdx <= b->len || !b->len); + assert(!b->len || b->num[b->len - 1] || b->rdx == b->len); + +err: + BC_SIG_MAYLOCK; + bc_num_free(&fprime); + bc_num_free(&f); + bc_num_free(&num2); + bc_num_free(&num1); + BC_LONGJMP_CONT; +} + +void bc_num_divmod(BcNum *a, BcNum *b, BcNum *c, BcNum *d, size_t scale) { + + BcNum num2, *ptr_a; + bool init = false; + size_t ts, len; + + ts = BC_MAX(scale + b->scale, a->scale); + len = bc_num_mulReq(a, b, ts); + + assert(a != NULL && b != NULL && c != NULL && d != NULL); + assert(c != d && a != d && b != d && b != c); + + if (c == a) { + + memcpy(&num2, c, sizeof(BcNum)); + ptr_a = &num2; + + BC_SIG_LOCK; + + bc_num_init(c, len); + + init = true; + + BC_SETJMP_LOCKED(err); + + BC_SIG_UNLOCK; + } + else { + ptr_a = a; + bc_num_expand(c, len); + } + + if (BC_NUM_NONZERO(a) && !a->rdx && !b->rdx && b->len == 1 && !scale) { + + BcBigDig rem; + + bc_num_divArray(ptr_a, (BcBigDig) b->num[0], c, &rem); + + assert(rem < BC_BASE_POW); + + d->num[0] = (BcDig) rem; + d->len = (rem != 0); + } + else bc_num_r(ptr_a, b, c, d, scale, ts); + + assert(!c->neg || BC_NUM_NONZERO(c)); + assert(c->rdx <= c->len || !c->len); + assert(!c->len || c->num[c->len - 1] || c->rdx == c->len); + assert(!d->neg || BC_NUM_NONZERO(d)); + assert(d->rdx <= d->len || !d->len); + assert(!d->len || d->num[d->len - 1] || d->rdx == d->len); + +err: + if (init) { + BC_SIG_MAYLOCK; + bc_num_free(&num2); + BC_LONGJMP_CONT; + } +} + +#if DC_ENABLED +void bc_num_modexp(BcNum *a, BcNum *b, BcNum *c, BcNum *restrict d) { + + BcNum base, exp, two, temp; + BcDig two_digs[2]; + + assert(a != NULL && b != NULL && c != NULL && d != NULL); + assert(a != d && b != d && c != d); + + if (BC_ERR(BC_NUM_ZERO(c))) bc_vm_err(BC_ERROR_MATH_DIVIDE_BY_ZERO); + if (BC_ERR(b->neg)) bc_vm_err(BC_ERROR_MATH_NEGATIVE); + if (BC_ERR(a->rdx || b->rdx || c->rdx)) + bc_vm_err(BC_ERROR_MATH_NON_INTEGER); + + bc_num_expand(d, c->len); + + BC_SIG_LOCK; + + bc_num_init(&base, c->len); + bc_num_setup(&two, two_digs, sizeof(two_digs) / sizeof(BcDig)); + bc_num_init(&temp, b->len + 1); + bc_num_createCopy(&exp, b); + + BC_SETJMP_LOCKED(err); + + BC_SIG_UNLOCK; + + bc_num_one(&two); + two.num[0] = 2; + bc_num_one(d); + + // We already checked for 0. + bc_num_rem(a, c, &base, 0); + + while (BC_NUM_NONZERO(&exp)) { + + // Num two cannot be 0, so no errors. + bc_num_divmod(&exp, &two, &exp, &temp, 0); + + if (BC_NUM_ONE(&temp) && !temp.neg) { + + bc_num_mul(d, &base, &temp, 0); + + // We already checked for 0. + bc_num_rem(&temp, c, d, 0); + } + + bc_num_mul(&base, &base, &temp, 0); + + // We already checked for 0. + bc_num_rem(&temp, c, &base, 0); + } + +err: + BC_SIG_MAYLOCK; + bc_num_free(&exp); + bc_num_free(&temp); + bc_num_free(&base); + BC_LONGJMP_CONT; + assert(!d->neg || d->len); + assert(!d->len || d->num[d->len - 1] || d->rdx == d->len); +} +#endif // DC_ENABLED + +#if BC_DEBUG_CODE +void bc_num_printDebug(const BcNum *n, const char *name, bool emptyline) { + bc_file_puts(&vm.fout, name); + bc_file_puts(&vm.fout, ": "); + bc_num_printDecimal(n); + bc_file_putchar(&vm.fout, '\n'); + if (emptyline) bc_file_putchar(&vm.fout, '\n'); + vm.nchars = 0; +} + +void bc_num_printDigs(const BcDig *n, size_t len, bool emptyline) { + + size_t i; + + for (i = len - 1; i < len; --i) + bc_file_printf(&vm.fout, " %lu", (unsigned long) n[i]); + + bc_file_putchar(&vm.fout, '\n'); + if (emptyline) bc_file_putchar(&vm.fout, '\n'); + vm.nchars = 0; +} + +void bc_num_printWithDigs(const BcNum *n, const char *name, bool emptyline) { + bc_file_puts(&vm.fout, name); + bc_file_printf(&vm.fout, " len: %zu, rdx: %zu, scale: %zu\n", + name, n->len, n->rdx, n->scale); + bc_num_printDigs(n->num, n->len, emptyline); +} + +void bc_num_dump(const char *varname, const BcNum *n) { + + ulong i, scale = n->scale; + + bc_file_printf(&vm.ferr, "\n%s = %s", varname, + n->len ? (n->neg ? "-" : "+") : "0 "); + + for (i = n->len - 1; i < n->len; --i) { + + if (i + 1 == n->rdx) bc_file_puts(&vm.ferr, ". "); + + if (scale / BC_BASE_DIGS != n->rdx - i - 1) + bc_file_printf(&vm.ferr, "%lu ", (unsigned long) n->num[i]); + else { + + int mod = scale % BC_BASE_DIGS; + int d = BC_BASE_DIGS - mod; + BcDig div; + + if (mod != 0) { + div = n->num[i] / ((BcDig) bc_num_pow10[(ulong) d]); + bc_file_printf(&vm.ferr, "%lu", (unsigned long) div); + } + + div = n->num[i] % ((BcDig) bc_num_pow10[(ulong) d]); + bc_file_printf(&vm.ferr, " ' %lu ", (unsigned long) div); + } + } + + bc_file_printf(&vm.ferr, "(%zu | %zu.%zu / %zu) %lu\n", + n->scale, n->len, n->rdx, n->cap, + (unsigned long) (void*) n->num); +} +#endif // BC_DEBUG_CODE diff --git a/src/opt.c b/src/opt.c new file mode 100644 index 000000000000..730b1cd51f19 --- /dev/null +++ b/src/opt.c @@ -0,0 +1,250 @@ +/* + * ***************************************************************************** + * + * Copyright (c) 2018-2020 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. + * + * ***************************************************************************** + * + * Adapted from https://github.com/skeeto/optparse + * + * ***************************************************************************** + * + * Code for getopt_long() replacement. It turns out that getopt_long() has + * different behavior on different platforms. + * + */ + +#include <assert.h> +#include <stdbool.h> +#include <stdlib.h> +#include <string.h> + +#include <status.h> +#include <opt.h> +#include <vm.h> + +static inline bool bc_opt_longoptsEnd(const BcOptLong *longopts, size_t i) { + return !longopts[i].name && !longopts[i].val; +} + +static const char* bc_opt_longopt(const BcOptLong *longopts, int c) { + + size_t i; + + for (i = 0; !bc_opt_longoptsEnd(longopts, i); ++i) { + if (longopts[i].val == c) return longopts[i].name; + } + + return "NULL"; +} + +static void bc_opt_error(BcError err, int c, const char *str) { + if (err == BC_ERROR_FATAL_OPTION) bc_vm_error(err, 0, str); + else bc_vm_error(err, 0, (int) c, str); +} + +static int bc_opt_type(const BcOptLong *longopts, char c) { + + size_t i; + + if (c == ':') return -1; + + for (i = 0; !bc_opt_longoptsEnd(longopts, i) && longopts[i].val != c; ++i); + + if (bc_opt_longoptsEnd(longopts, i)) return -1; + + return (int) longopts[i].type; +} + +static int bc_opt_parseShort(BcOpt *o, const BcOptLong *longopts) { + + int type; + char *next; + char *option = o->argv[o->optind]; + int ret = -1; + + o->optopt = 0; + o->optarg = NULL; + + option += o->subopt + 1; + o->optopt = option[0]; + + type = bc_opt_type(longopts, option[0]); + next = o->argv[o->optind + 1]; + + switch (type) { + + case -1: + case BC_OPT_BC_ONLY: + case BC_OPT_DC_ONLY: + { + if (type == -1 || (type == BC_OPT_BC_ONLY && !BC_IS_BC) || + (type == BC_OPT_DC_ONLY && BC_IS_BC)) + { + char str[2] = {0, 0}; + + str[0] = option[0]; + o->optind += 1; + + bc_opt_error(BC_ERROR_FATAL_OPTION, option[0], str); + } + } + // Fallthrough. + case BC_OPT_NONE: + { + if (option[1]) o->subopt += 1; + else { + o->subopt = 0; + o->optind += 1; + } + + ret = (int) option[0]; + break; + } + + case BC_OPT_REQUIRED: + { + o->subopt = 0; + o->optind += 1; + + if (option[1]) o->optarg = option + 1; + else if (next != NULL) { + o->optarg = next; + o->optind += 1; + } + else bc_opt_error(BC_ERROR_FATAL_OPTION_NO_ARG, option[0], + bc_opt_longopt(longopts, option[0])); + + + ret = (int) option[0]; + break; + } + } + + return ret; +} + +static bool bc_opt_longoptsMatch(const char *name, const char *option) { + + const char *a = option, *n = name; + + if (name == NULL) return false; + + for (; *a && *n && *a != '='; ++a, ++n) { + if (*a != *n) return false; + } + + return (*n == '\0' && (*a == '\0' || *a == '=')); +} + +static char* bc_opt_longoptsArg(char *option) { + + for (; *option && *option != '='; ++option); + + if (*option == '=') return option + 1; + else return NULL; +} + +int bc_opt_parse(BcOpt *o, const BcOptLong *longopts) { + + size_t i; + char *option; + bool empty; + + do { + + option = o->argv[o->optind]; + if (option == NULL) return -1; + + empty = !strcmp(option, ""); + o->optind += empty; + + } while (empty); + + if (BC_OPT_ISDASHDASH(option)) { + + // Consume "--". + o->optind += 1; + return -1; + } + else if (BC_OPT_ISSHORTOPT(option)) return bc_opt_parseShort(o, longopts); + else if (!BC_OPT_ISLONGOPT(option)) return -1; + + o->optopt = 0; + o->optarg = NULL; + + // Skip "--" at beginning of the option. + option += 2; + o->optind += 1; + + for (i = 0; !bc_opt_longoptsEnd(longopts, i); i++) { + + const char *name = longopts[i].name; + + if (bc_opt_longoptsMatch(name, option)) { + + char *arg; + + o->optopt = longopts[i].val; + arg = bc_opt_longoptsArg(option); + + if ((longopts[i].type == BC_OPT_BC_ONLY && !BC_IS_BC) || + (longopts[i].type == BC_OPT_DC_ONLY && BC_IS_BC)) + { + bc_opt_error(BC_ERROR_FATAL_OPTION, o->optopt, name); + } + + if (longopts[i].type == BC_OPT_NONE && arg != NULL) + { + bc_opt_error(BC_ERROR_FATAL_OPTION_ARG, o->optopt, name); + } + + if (arg != NULL) o->optarg = arg; + else if (longopts[i].type == BC_OPT_REQUIRED) { + + o->optarg = o->argv[o->optind]; + + if (o->optarg != NULL) o->optind += 1; + else bc_opt_error(BC_ERROR_FATAL_OPTION_NO_ARG, + o->optopt, name); + } + + return o->optopt; + } + } + + bc_opt_error(BC_ERROR_FATAL_OPTION, 0, option); + + return -1; +} + +void bc_opt_init(BcOpt *o, char *argv[]) { + o->argv = argv; + o->optind = 1; + o->subopt = 0; + o->optarg = NULL; +} diff --git a/src/parse.c b/src/parse.c new file mode 100644 index 000000000000..5d35fe66d3b6 --- /dev/null +++ b/src/parse.c @@ -0,0 +1,222 @@ +/* + * ***************************************************************************** + * + * Copyright (c) 2018-2020 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 the parsers. + * + */ + +#include <assert.h> +#include <stddef.h> +#include <stdlib.h> +#include <string.h> + +#include <limits.h> + +#include <status.h> +#include <vector.h> +#include <lex.h> +#include <parse.h> +#include <program.h> +#include <vm.h> + +void bc_parse_updateFunc(BcParse *p, size_t fidx) { + p->fidx = fidx; + p->func = bc_vec_item(&p->prog->fns, fidx); +} + +inline void bc_parse_pushName(const BcParse *p, char *name, bool var) { + bc_parse_pushIndex(p, bc_program_search(p->prog, name, var)); +} + +static void bc_parse_update(BcParse *p, uchar inst, size_t idx) { + bc_parse_updateFunc(p, p->fidx); + bc_parse_push(p, inst); + bc_parse_pushIndex(p, idx); +} + +void bc_parse_addString(BcParse *p) { + + BcFunc *f = BC_IS_BC ? p->func : bc_vec_item(&p->prog->fns, BC_PROG_MAIN); + size_t idx; + + BC_SIG_LOCK; + + if (BC_IS_BC) { + const char *str = bc_vm_strdup(p->l.str.v); + idx = f->strs.len; + bc_vec_push(&f->strs, &str); + } +#if DC_ENABLED + else idx = bc_program_insertFunc(p->prog, p->l.str.v) - BC_PROG_REQ_FUNCS; +#endif // DC_ENABLED + +#ifndef NDEBUG + f = BC_IS_BC ? p->func : bc_vec_item(&p->prog->fns, BC_PROG_MAIN); + assert(f->strs.len > idx); +#endif // NDEBUG + + bc_parse_update(p, BC_INST_STR, idx); + + BC_SIG_UNLOCK; +} + +static void bc_parse_addNum(BcParse *p, const char *string) { + + BcFunc *f = BC_IS_BC ? p->func : bc_vec_item(&p->prog->fns, BC_PROG_MAIN); + size_t idx; + BcConst c; + + if (bc_parse_one[0] == string[0] && bc_parse_one[1] == string[1]) { + bc_parse_push(p, BC_INST_ONE); + return; + } + + idx = f->consts.len; + + BC_SIG_LOCK; + + c.val = bc_vm_strdup(string); + c.base = BC_NUM_BIGDIG_MAX; + + bc_num_clear(&c.num); + bc_vec_push(&f->consts, &c); + + bc_parse_update(p, BC_INST_NUM, idx); + + BC_SIG_UNLOCK; +} + +void bc_parse_number(BcParse *p) { + +#if BC_ENABLE_EXTRA_MATH + char *exp = strchr(p->l.str.v, 'e'); + size_t idx = SIZE_MAX; + + if (exp != NULL) { + idx = ((size_t) (exp - p->l.str.v)); + *exp = 0; + } +#endif // BC_ENABLE_EXTRA_MATH + + bc_parse_addNum(p, p->l.str.v); + +#if BC_ENABLE_EXTRA_MATH + if (exp != NULL) { + + bool neg; + + neg = (*((char*) bc_vec_item(&p->l.str, idx + 1)) == BC_LEX_NEG_CHAR); + + bc_parse_addNum(p, bc_vec_item(&p->l.str, idx + 1 + neg)); + bc_parse_push(p, BC_INST_LSHIFT + neg); + } +#endif // BC_ENABLE_EXTRA_MATH +} + +void bc_parse_text(BcParse *p, const char *text) { + // Make sure the pointer isn't invalidated. + p->func = bc_vec_item(&p->prog->fns, p->fidx); + bc_lex_text(&p->l, text); +} + +void bc_parse_reset(BcParse *p) { + + BC_SIG_ASSERT_LOCKED; + + if (p->fidx != BC_PROG_MAIN) { + bc_func_reset(p->func); + bc_parse_updateFunc(p, BC_PROG_MAIN); + } + + p->l.i = p->l.len; + p->l.t = BC_LEX_EOF; + p->auto_part = false; + +#if BC_ENABLED + if (BC_IS_BC) { + bc_vec_npop(&p->flags, p->flags.len - 1); + bc_vec_npop(&p->exits, p->exits.len); + bc_vec_npop(&p->conds, p->conds.len); + bc_vec_npop(&p->ops, p->ops.len); + } +#endif // BC_ENABLED + + bc_program_reset(p->prog); + + if (BC_ERR(vm.status)) BC_VM_JMP; +} + +void bc_parse_free(BcParse *p) { + + BC_SIG_ASSERT_LOCKED; + + assert(p != NULL); + +#if BC_ENABLED + if (BC_IS_BC) { + bc_vec_free(&p->flags); + bc_vec_free(&p->exits); + bc_vec_free(&p->conds); + bc_vec_free(&p->ops); + bc_vec_free(&p->buf); + } +#endif // BC_ENABLED + + bc_lex_free(&p->l); +} + +void bc_parse_init(BcParse *p, BcProgram *prog, size_t func) { + +#if BC_ENABLED + uint16_t flag = 0; +#endif // BC_ENABLED + + BC_SIG_ASSERT_LOCKED; + + assert(p != NULL && prog != NULL); + +#if BC_ENABLED + if (BC_IS_BC) { + bc_vec_init(&p->flags, sizeof(uint16_t), NULL); + bc_vec_push(&p->flags, &flag); + bc_vec_init(&p->exits, sizeof(BcInstPtr), NULL); + bc_vec_init(&p->conds, sizeof(size_t), NULL); + bc_vec_init(&p->ops, sizeof(BcLexType), NULL); + bc_vec_init(&p->buf, sizeof(char), NULL); + } +#endif // BC_ENABLED + + bc_lex_init(&p->l); + + p->prog = prog; + p->auto_part = false; + bc_parse_updateFunc(p, func); +} diff --git a/src/program.c b/src/program.c new file mode 100644 index 000000000000..8f2270f5e71d --- /dev/null +++ b/src/program.c @@ -0,0 +1,2300 @@ +/* + * ***************************************************************************** + * + * Copyright (c) 2018-2020 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 to execute bc programs. + * + */ + +#include <assert.h> +#include <stdbool.h> +#include <string.h> + +#include <setjmp.h> + +#include <signal.h> + +#include <time.h> + +#include <read.h> +#include <parse.h> +#include <program.h> +#include <vm.h> + +static void bc_program_addFunc(BcProgram *p, BcFunc *f, BcId *id_ptr); + +static inline void bc_program_setVecs(BcProgram *p, BcFunc *f) { + p->consts = &f->consts; + p->strs = &f->strs; +} + +static void bc_program_type_num(BcResult *r, BcNum *n) { + +#if BC_ENABLED + assert(r->t != BC_RESULT_VOID); +#endif // BC_ENABLED + + if (BC_ERR(!BC_PROG_NUM(r, n))) bc_vm_err(BC_ERROR_EXEC_TYPE); +} + +#if BC_ENABLED +static void bc_program_type_match(BcResult *r, BcType t) { + +#if DC_ENABLED + assert(!BC_IS_BC || BC_NO_ERR(r->t != BC_RESULT_STR)); +#endif // DC_ENABLED + + if (BC_ERR((r->t != BC_RESULT_ARRAY) != (!t))) + bc_vm_err(BC_ERROR_EXEC_TYPE); +} +#endif // BC_ENABLED + +static size_t bc_program_index(const char *restrict code, size_t *restrict bgn) +{ + uchar amt = (uchar) code[(*bgn)++], i = 0; + size_t res = 0; + + for (; i < amt; ++i, ++(*bgn)) { + size_t temp = ((size_t) ((int) (uchar) code[*bgn]) & UCHAR_MAX); + res |= (temp << (i * CHAR_BIT)); + } + + return res; +} + +static void bc_program_prepGlobals(BcProgram *p) { + + size_t i; + + for (i = 0; i < BC_PROG_GLOBALS_LEN; ++i) + bc_vec_push(p->globals_v + i, p->globals + i); + +#if BC_ENABLE_EXTRA_MATH + bc_rand_push(&p->rng); +#endif // BC_ENABLE_EXTRA_MATH +} + +static void bc_program_popGlobals(BcProgram *p, bool reset) { + + size_t i; + + for (i = 0; i < BC_PROG_GLOBALS_LEN; ++i) { + BcVec *v = p->globals_v + i; + bc_vec_npop(v, reset ? v->len - 1 : 1); + p->globals[i] = BC_PROG_GLOBAL(v); + } + +#if BC_ENABLE_EXTRA_MATH + bc_rand_pop(&p->rng, reset); +#endif // BC_ENABLE_EXTRA_MATH +} + +static void bc_program_pushBigdig(BcProgram *p, BcBigDig dig, BcResultType type) +{ + BcResult res; + + res.t = type; + + BC_SIG_LOCK; + + bc_num_createFromBigdig(&res.d.n, dig); + bc_vec_push(&p->results, &res); + + BC_SIG_UNLOCK; +} + +#if BC_ENABLED +static BcVec* bc_program_dereference(const BcProgram *p, BcVec *vec) { + + BcVec *v; + size_t vidx, nidx, i = 0; + + assert(vec->size == sizeof(uchar)); + + vidx = bc_program_index(vec->v, &i); + nidx = bc_program_index(vec->v, &i); + + v = bc_vec_item(bc_vec_item(&p->arrs, vidx), nidx); + + assert(v->size != sizeof(uchar)); + + return v; +} +#endif // BC_ENABLED + +size_t bc_program_search(BcProgram *p, const char *id, bool var) { + + BcVec *v, *map; + size_t i; + BcResultData data; + + v = var ? &p->vars : &p->arrs; + map = var ? &p->var_map : &p->arr_map; + + BC_SIG_LOCK; + + if (bc_map_insert(map, id, v->len, &i)) { + bc_array_init(&data.v, var); + bc_vec_push(v, &data.v); + } + + BC_SIG_UNLOCK; + + return ((BcId*) bc_vec_item(map, i))->idx; +} + +static inline BcVec* bc_program_vec(const BcProgram *p, size_t idx, BcType type) +{ + const BcVec *v = (type == BC_TYPE_VAR) ? &p->vars : &p->arrs; + return bc_vec_item(v, idx); +} + +static BcNum* bc_program_num(BcProgram *p, BcResult *r) { + + BcNum *n; + + switch (r->t) { + + case BC_RESULT_CONSTANT: + { + BcConst *c = bc_vec_item(p->consts, r->d.loc.loc); + BcBigDig base = BC_PROG_IBASE(p); + + if (c->base != base) { + + if (c->num.num == NULL) { + BC_SIG_LOCK; + bc_num_init(&c->num, BC_NUM_RDX(strlen(c->val))); + BC_SIG_UNLOCK; + } + + // bc_num_parse() should only do operations that cannot fail. + bc_num_parse(&c->num, c->val, base, !c->val[1]); + + c->base = base; + } + + BC_SIG_LOCK; + + n = &r->d.n; + + r->t = BC_RESULT_TEMP; + + bc_num_createCopy(n, &c->num); + + BC_SIG_UNLOCK; + + break; + } + + case BC_RESULT_STR: + case BC_RESULT_TEMP: + case BC_RESULT_IBASE: + case BC_RESULT_SCALE: + case BC_RESULT_OBASE: +#if BC_ENABLE_EXTRA_MATH + case BC_RESULT_SEED: +#endif // BC_ENABLE_EXTRA_MATH + { + n = &r->d.n; + break; + } + + case BC_RESULT_VAR: +#if BC_ENABLED + case BC_RESULT_ARRAY: +#endif // BC_ENABLED + case BC_RESULT_ARRAY_ELEM: + { + BcVec *v; + BcType type = (r->t == BC_RESULT_VAR) ? BC_TYPE_VAR : BC_TYPE_ARRAY; + + v = bc_program_vec(p, r->d.loc.loc, type); + + if (r->t == BC_RESULT_ARRAY_ELEM) { + + size_t idx = r->d.loc.idx; + + v = bc_vec_top(v); + +#if BC_ENABLED + if (v->size == sizeof(uchar)) v = bc_program_dereference(p, v); +#endif // BC_ENABLED + + assert(v->size == sizeof(BcNum)); + + if (v->len <= idx) { + BC_SIG_LOCK; + bc_array_expand(v, bc_vm_growSize(idx, 1)); + BC_SIG_UNLOCK; + } + + n = bc_vec_item(v, idx); + } + else n = bc_vec_top(v); + + break; + } + + case BC_RESULT_ONE: + { + n = &p->one; + break; + } + +#if BC_ENABLED + case BC_RESULT_VOID: +#ifndef NDEBUG + { + abort(); + } +#endif // NDEBUG + // Fallthrough + case BC_RESULT_LAST: + { + n = &p->last; + break; + } +#endif // BC_ENABLED + } + + return n; +} + +static void bc_program_operand(BcProgram *p, BcResult **r, + BcNum **n, size_t idx) +{ + *r = bc_vec_item_rev(&p->results, idx); + +#if BC_ENABLED + if (BC_ERR((*r)->t == BC_RESULT_VOID)) bc_vm_err(BC_ERROR_EXEC_VOID_VAL); +#endif // BC_ENABLED + + *n = bc_program_num(p, *r); +} + +static void bc_program_binPrep(BcProgram *p, BcResult **l, BcNum **ln, + BcResult **r, BcNum **rn, size_t idx) +{ + BcResultType lt; + + assert(p != NULL && l != NULL && ln != NULL && r != NULL && rn != NULL); + +#ifndef BC_PROG_NO_STACK_CHECK + if (!BC_IS_BC) { + if (BC_ERR(!BC_PROG_STACK(&p->results, idx + 2))) + bc_vm_err(BC_ERROR_EXEC_STACK); + } +#endif // BC_PROG_NO_STACK_CHECK + + assert(BC_PROG_STACK(&p->results, idx + 2)); + + bc_program_operand(p, l, ln, idx + 1); + bc_program_operand(p, r, rn, idx); + + lt = (*l)->t; + +#if BC_ENABLED + assert(lt != BC_RESULT_VOID && (*r)->t != BC_RESULT_VOID); +#endif // BC_ENABLED + + // We run this again under these conditions in case any vector has been + // reallocated out from under the BcNums or arrays we had. + if (lt == (*r)->t && (lt == BC_RESULT_VAR || lt == BC_RESULT_ARRAY_ELEM)) + *ln = bc_program_num(p, *l); + + if (BC_ERR(lt == BC_RESULT_STR)) bc_vm_err(BC_ERROR_EXEC_TYPE); +} + +static void bc_program_binOpPrep(BcProgram *p, BcResult **l, BcNum **ln, + BcResult **r, BcNum **rn, size_t idx) +{ + bc_program_binPrep(p, l, ln, r, rn, idx); + bc_program_type_num(*l, *ln); + bc_program_type_num(*r, *rn); +} + +static void bc_program_assignPrep(BcProgram *p, BcResult **l, BcNum **ln, + BcResult **r, BcNum **rn) +{ + BcResultType lt, min; + + min = BC_RESULT_CONSTANT - ((unsigned int) (BC_IS_BC << 1)); + + bc_program_binPrep(p, l, ln, r, rn, 0); + + lt = (*l)->t; + + if (BC_ERR(lt >= min && lt <= BC_RESULT_ONE)) + bc_vm_err(BC_ERROR_EXEC_TYPE); + +#if DC_ENABLED + if(!BC_IS_BC) { + + bool good = (((*r)->t == BC_RESULT_STR || BC_PROG_STR(*rn)) && + lt <= BC_RESULT_ARRAY_ELEM); + + if (!good) bc_program_type_num(*r, *rn); + } +#else + assert((*r)->t != BC_RESULT_STR); +#endif // DC_ENABLED +} + +static void bc_program_prep(BcProgram *p, BcResult **r, BcNum **n, size_t idx) { + + assert(p != NULL && r != NULL && n != NULL); + +#ifndef BC_PROG_NO_STACK_CHECK + if (!BC_IS_BC) { + if (BC_ERR(!BC_PROG_STACK(&p->results, idx + 1))) + bc_vm_err(BC_ERROR_EXEC_STACK); + } +#endif // BC_PROG_NO_STACK_CHECK + + assert(BC_PROG_STACK(&p->results, idx + 1)); + + bc_program_operand(p, r, n, idx); + +#if DC_ENABLED + assert((*r)->t != BC_RESULT_VAR || !BC_PROG_STR(*n)); +#endif // DC_ENABLED + + bc_program_type_num(*r, *n); +} + +static BcResult* bc_program_prepResult(BcProgram *p) { + + BcResult res; + + bc_result_clear(&res); + bc_vec_push(&p->results, &res); + + return bc_vec_top(&p->results); +} + +static void bc_program_op(BcProgram *p, uchar inst) { + + BcResult *opd1, *opd2, *res; + BcNum *n1, *n2; + size_t idx = inst - BC_INST_POWER; + + res = bc_program_prepResult(p); + + bc_program_binOpPrep(p, &opd1, &n1, &opd2, &n2, 1); + + BC_SIG_LOCK; + + bc_num_init(&res->d.n, bc_program_opReqs[idx](n1, n2, BC_PROG_SCALE(p))); + + BC_SIG_UNLOCK; + + bc_program_ops[idx](n1, n2, &res->d.n, BC_PROG_SCALE(p)); + + bc_program_retire(p, 1, 2); +} + +static void bc_program_read(BcProgram *p) { + + BcStatus s; + BcParse parse; + BcVec buf; + BcInstPtr ip; + size_t i; + const char* file; + BcFunc *f = bc_vec_item(&p->fns, BC_PROG_READ); + + for (i = 0; i < p->stack.len; ++i) { + BcInstPtr *ip_ptr = bc_vec_item(&p->stack, i); + if (ip_ptr->func == BC_PROG_READ) + bc_vm_err(BC_ERROR_EXEC_REC_READ); + } + + BC_SIG_LOCK; + + file = vm.file; + bc_parse_init(&parse, p, BC_PROG_READ); + bc_vec_init(&buf, sizeof(char), NULL); + + BC_SETJMP_LOCKED(exec_err); + + BC_SIG_UNLOCK; + + bc_lex_file(&parse.l, bc_program_stdin_name); + bc_vec_npop(&f->code, f->code.len); + + s = bc_read_line(&buf, BC_IS_BC ? "read> " : "?> "); + if (s == BC_STATUS_EOF) bc_vm_err(BC_ERROR_EXEC_READ_EXPR); + + bc_parse_text(&parse, buf.v); + vm.expr(&parse, BC_PARSE_NOREAD | BC_PARSE_NEEDVAL); + + if (BC_ERR(parse.l.t != BC_LEX_NLINE && parse.l.t != BC_LEX_EOF)) + bc_vm_err(BC_ERROR_EXEC_READ_EXPR); + + if (BC_G) bc_program_prepGlobals(p); + + ip.func = BC_PROG_READ; + ip.idx = 0; + ip.len = p->results.len; + + // Update this pointer, just in case. + f = bc_vec_item(&p->fns, BC_PROG_READ); + + bc_vec_pushByte(&f->code, vm.read_ret); + bc_vec_push(&p->stack, &ip); +#if DC_ENABLED + if (!BC_IS_BC) { + size_t temp = 0; + bc_vec_push(&p->tail_calls, &temp); + } +#endif // DC_ENABLED + +exec_err: + BC_SIG_MAYLOCK; + bc_parse_free(&parse); + bc_vec_free(&buf); + vm.file = file; + BC_LONGJMP_CONT; +} + +#if BC_ENABLE_EXTRA_MATH +static void bc_program_rand(BcProgram *p) { + BcRand rand = bc_rand_int(&p->rng); + bc_program_pushBigdig(p, (BcBigDig) rand, BC_RESULT_TEMP); +} +#endif // BC_ENABLE_EXTRA_MATH + +static void bc_program_printChars(const char *str) { + + const char *nl; + size_t len = vm.nchars + strlen(str); + + bc_file_puts(&vm.fout, str); + nl = strrchr(str, '\n'); + + if (nl != NULL) len = strlen(nl + 1); + + vm.nchars = len > UINT16_MAX ? UINT16_MAX : (uint16_t) len; +} + +static void bc_program_printString(const char *restrict str) { + + size_t i, len = strlen(str); + +#if DC_ENABLED + if (!len && !BC_IS_BC) { + bc_vm_putchar('\0'); + return; + } +#endif // DC_ENABLED + + for (i = 0; i < len; ++i) { + + int c = str[i]; + + if (c == '\\' && i != len - 1) { + + const char *ptr; + + c = str[++i]; + ptr = strchr(bc_program_esc_chars, c); + + if (ptr != NULL) { + if (c == 'n') vm.nchars = UINT16_MAX; + c = bc_program_esc_seqs[(size_t) (ptr - bc_program_esc_chars)]; + } + else { + // Just print the backslash. The following + // character will be printed later. + bc_vm_putchar('\\'); + } + } + + bc_vm_putchar(c); + } +} + +static void bc_program_print(BcProgram *p, uchar inst, size_t idx) { + + BcResult *r; + char *str; + BcNum *n; + bool pop = (inst != BC_INST_PRINT); + + assert(p != NULL); + +#ifndef BC_PROG_NO_STACK_CHECK + if (!BC_IS_BC) { + if (BC_ERR(!BC_PROG_STACK(&p->results, idx + 1))) + bc_vm_err(BC_ERROR_EXEC_STACK); + } +#endif // BC_PROG_NO_STACK_CHECK + + assert(BC_PROG_STACK(&p->results, idx + 1)); + + assert(BC_IS_BC || + p->strs == &((BcFunc*) bc_vec_item(&p->fns, BC_PROG_MAIN))->strs); + + r = bc_vec_item_rev(&p->results, idx); + +#if BC_ENABLED + if (r->t == BC_RESULT_VOID) { + if (BC_ERR(pop)) bc_vm_err(BC_ERROR_EXEC_VOID_VAL); + bc_vec_pop(&p->results); + return; + } +#endif // BC_ENABLED + + n = bc_program_num(p, r); + + if (BC_PROG_NUM(r, n)) { + assert(inst != BC_INST_PRINT_STR); + bc_num_print(n, BC_PROG_OBASE(p), !pop); +#if BC_ENABLED + if (BC_IS_BC) bc_num_copy(&p->last, n); +#endif // BC_ENABLED + } + else { + + size_t i = (r->t == BC_RESULT_STR) ? r->d.loc.loc : n->scale; + + str = *((char**) bc_vec_item(p->strs, i)); + + if (inst == BC_INST_PRINT_STR) bc_program_printChars(str); + else { + bc_program_printString(str); + if (inst == BC_INST_PRINT) bc_vm_putchar('\n'); + } + } + + if (BC_IS_BC || pop) bc_vec_pop(&p->results); +} + +void bc_program_negate(BcResult *r, BcNum *n) { + bc_num_copy(&r->d.n, n); + if (BC_NUM_NONZERO(&r->d.n)) r->d.n.neg = !r->d.n.neg; +} + +void bc_program_not(BcResult *r, BcNum *n) { + if (!bc_num_cmpZero(n)) bc_num_one(&r->d.n); +} + +#if BC_ENABLE_EXTRA_MATH +void bc_program_trunc(BcResult *r, BcNum *n) { + bc_num_copy(&r->d.n, n); + bc_num_truncate(&r->d.n, n->scale); +} +#endif // BC_ENABLE_EXTRA_MATH + +static void bc_program_unary(BcProgram *p, uchar inst) { + + BcResult *res, *ptr; + BcNum *num; + + res = bc_program_prepResult(p); + + bc_program_prep(p, &ptr, &num, 1); + + BC_SIG_LOCK; + + bc_num_init(&res->d.n, num->len); + + BC_SIG_UNLOCK; + + bc_program_unarys[inst - BC_INST_NEG](res, num); + bc_program_retire(p, 1, 1); +} + +static void bc_program_logical(BcProgram *p, uchar inst) { + + BcResult *opd1, *opd2, *res; + BcNum *n1, *n2; + bool cond = 0; + ssize_t cmp; + + res = bc_program_prepResult(p); + + bc_program_binOpPrep(p, &opd1, &n1, &opd2, &n2, 1); + + if (inst == BC_INST_BOOL_AND) + cond = (bc_num_cmpZero(n1) && bc_num_cmpZero(n2)); + else if (inst == BC_INST_BOOL_OR) + cond = (bc_num_cmpZero(n1) || bc_num_cmpZero(n2)); + else { + + cmp = bc_num_cmp(n1, n2); + + switch (inst) { + + case BC_INST_REL_EQ: + { + cond = (cmp == 0); + break; + } + + case BC_INST_REL_LE: + { + cond = (cmp <= 0); + break; + } + + case BC_INST_REL_GE: + { + cond = (cmp >= 0); + break; + } + + case BC_INST_REL_NE: + { + cond = (cmp != 0); + break; + } + + case BC_INST_REL_LT: + { + cond = (cmp < 0); + break; + } + + case BC_INST_REL_GT: + { + cond = (cmp > 0); + break; + } +#ifndef NDEBUG + default: + { + abort(); + } +#endif // NDEBUG + } + } + + BC_SIG_LOCK; + + bc_num_init(&res->d.n, BC_NUM_DEF_SIZE); + + BC_SIG_UNLOCK; + + if (cond) bc_num_one(&res->d.n); + + bc_program_retire(p, 1, 2); +} + +#if DC_ENABLED +static void bc_program_assignStr(BcProgram *p, BcResult *r, + BcVec *v, bool push) +{ + BcNum n2; + + bc_num_clear(&n2); + n2.scale = r->d.loc.loc; + + assert(BC_PROG_STACK(&p->results, 1 + !push)); + + if (!push) bc_vec_pop(v); + + bc_vec_npop(&p->results, 1 + !push); + bc_vec_push(v, &n2); +} +#endif // DC_ENABLED + +static void bc_program_copyToVar(BcProgram *p, size_t idx, + BcType t, bool last) +{ + BcResult *ptr = NULL, r; + BcVec *vec; + BcNum *n = NULL; + bool var = (t == BC_TYPE_VAR); + +#if DC_ENABLED + if (!BC_IS_BC) { + + if (BC_ERR(!BC_PROG_STACK(&p->results, 1))) + bc_vm_err(BC_ERROR_EXEC_STACK); + + assert(BC_PROG_STACK(&p->results, 1)); + + bc_program_operand(p, &ptr, &n, 0); + } +#endif + +#if BC_ENABLED + if (BC_IS_BC) + { + ptr = bc_vec_top(&p->results); + + bc_program_type_match(ptr, t); + + if (last) n = bc_program_num(p, ptr); + else if (var) + n = bc_vec_item_rev(bc_program_vec(p, ptr->d.loc.loc, t), 1); + } +#endif // BC_ENABLED + + vec = bc_program_vec(p, idx, t); + +#if DC_ENABLED + if (ptr->t == BC_RESULT_STR) { + if (BC_ERR(!var)) bc_vm_err(BC_ERROR_EXEC_TYPE); + bc_program_assignStr(p, ptr, vec, true); + return; + } +#endif // DC_ENABLED + + BC_SIG_LOCK; + + if (var) bc_num_createCopy(&r.d.n, n); + else { + + BcVec *v = (BcVec*) n, *rv = &r.d.v; +#if BC_ENABLED + BcVec *parent; + bool ref, ref_size; + + parent = bc_program_vec(p, ptr->d.loc.loc, t); + assert(parent != NULL); + + if (!last) v = bc_vec_item_rev(parent, !last); + assert(v != NULL); + + ref = (v->size == sizeof(BcNum) && t == BC_TYPE_REF); + ref_size = (v->size == sizeof(uchar)); + + if (ref || (ref_size && t == BC_TYPE_REF)) { + + bc_vec_init(rv, sizeof(uchar), NULL); + + if (ref) { + + assert(parent->len >= (size_t) (!last + 1)); + + // Make sure the pointer was not invalidated. + vec = bc_program_vec(p, idx, t); + + bc_vec_pushIndex(rv, ptr->d.loc.loc); + bc_vec_pushIndex(rv, parent->len - !last - 1); + } + // If we get here, we are copying a ref to a ref. + else bc_vec_npush(rv, v->len * sizeof(uchar), v->v); + + // We need to return early. + bc_vec_push(vec, &r.d); + bc_vec_pop(&p->results); + + BC_SIG_UNLOCK; + return; + } + else if (ref_size && t != BC_TYPE_REF) v = bc_program_dereference(p, v); +#endif // BC_ENABLED + + bc_array_init(rv, true); + bc_array_copy(rv, v); + } + + bc_vec_push(vec, &r.d); + bc_vec_pop(&p->results); + + BC_SIG_UNLOCK; +} + +static void bc_program_assign(BcProgram *p, uchar inst) { + + BcResult *left, *right, res; + BcNum *l, *r; + bool ob, sc, use_val = BC_INST_USE_VAL(inst); + + bc_program_assignPrep(p, &left, &l, &right, &r); + +#if DC_ENABLED + assert(left->t != BC_RESULT_STR); + + if (right->t == BC_RESULT_STR || BC_PROG_STR(r)) { + + size_t idx = right->d.loc.loc; + + if (left->t == BC_RESULT_ARRAY_ELEM) { + BC_SIG_LOCK; + bc_num_free(l); + bc_num_clear(l); + l->scale = idx; + bc_vec_npop(&p->results, 2); + BC_SIG_UNLOCK; + } + else { + BcVec *v = bc_program_vec(p, left->d.loc.loc, BC_TYPE_VAR); + bc_program_assignStr(p, right, v, false); + } + + return; + } +#endif // DC_ENABLED + + if (BC_INST_IS_ASSIGN(inst)) bc_num_copy(l, r); +#if BC_ENABLED + else { + + BcBigDig scale = BC_PROG_SCALE(p); + + if (!use_val) + inst -= (BC_INST_ASSIGN_POWER_NO_VAL - BC_INST_ASSIGN_POWER); + + bc_program_ops[inst - BC_INST_ASSIGN_POWER](l, r, l, scale); + } +#endif // BC_ENABLED + + ob = (left->t == BC_RESULT_OBASE); + sc = (left->t == BC_RESULT_SCALE); + + if (ob || sc || left->t == BC_RESULT_IBASE) { + + BcVec *v; + BcBigDig *ptr, *ptr_t, val, max, min; + BcError e; + + bc_num_bigdig(l, &val); + e = left->t - BC_RESULT_IBASE + BC_ERROR_EXEC_IBASE; + + if (sc) { + min = 0; + max = vm.maxes[BC_PROG_GLOBALS_SCALE]; + v = p->globals_v + BC_PROG_GLOBALS_SCALE; + ptr_t = p->globals + BC_PROG_GLOBALS_SCALE; + } + else { + min = BC_NUM_MIN_BASE; + if (BC_ENABLE_EXTRA_MATH && ob && (!BC_IS_BC || !BC_IS_POSIX)) + min = 0; + max = vm.maxes[ob + BC_PROG_GLOBALS_IBASE]; + v = p->globals_v + BC_PROG_GLOBALS_IBASE + ob; + ptr_t = p->globals + BC_PROG_GLOBALS_IBASE + ob; + } + + if (BC_ERR(val > max || val < min)) bc_vm_verr(e, min, max); + + ptr = bc_vec_top(v); + *ptr = val; + *ptr_t = val; + } +#if BC_ENABLE_EXTRA_MATH + else if (left->t == BC_RESULT_SEED) bc_num_rng(l, &p->rng); +#endif // BC_ENABLE_EXTRA_MATH + + BC_SIG_LOCK; + + if (use_val) { + bc_num_createCopy(&res.d.n, l); + res.t = BC_RESULT_TEMP; + bc_vec_npop(&p->results, 2); + bc_vec_push(&p->results, &res); + } + else bc_vec_npop(&p->results, 2); + + BC_SIG_UNLOCK; +} + +static void bc_program_pushVar(BcProgram *p, const char *restrict code, + size_t *restrict bgn, bool pop, bool copy) +{ + BcResult r; + size_t idx = bc_program_index(code, bgn); + + r.t = BC_RESULT_VAR; + r.d.loc.loc = idx; + +#if DC_ENABLED + if (!BC_IS_BC && (pop || copy)) { + + BcVec *v = bc_program_vec(p, idx, BC_TYPE_VAR); + BcNum *num = bc_vec_top(v); + + if (BC_ERR(!BC_PROG_STACK(v, 2 - copy))) bc_vm_err(BC_ERROR_EXEC_STACK); + + assert(BC_PROG_STACK(v, 2 - copy)); + + if (!BC_PROG_STR(num)) { + + BC_SIG_LOCK; + + r.t = BC_RESULT_TEMP; + bc_num_createCopy(&r.d.n, num); + + if (!copy) bc_vec_pop(v); + + bc_vec_push(&p->results, &r); + + BC_SIG_UNLOCK; + + return; + } + else { + r.d.loc.loc = num->scale; + r.t = BC_RESULT_STR; + } + + if (!copy) bc_vec_pop(v); + } +#endif // DC_ENABLED + + bc_vec_push(&p->results, &r); +} + +static void bc_program_pushArray(BcProgram *p, const char *restrict code, + size_t *restrict bgn, uchar inst) +{ + BcResult r, *operand; + BcNum *num; + BcBigDig temp; + + r.d.loc.loc = bc_program_index(code, bgn); + +#if BC_ENABLED + if (inst == BC_INST_ARRAY) { + r.t = BC_RESULT_ARRAY; + bc_vec_push(&p->results, &r); + return; + } +#endif // BC_ENABLED + + bc_program_prep(p, &operand, &num, 0); + bc_num_bigdig(num, &temp); + + r.t = BC_RESULT_ARRAY_ELEM; + r.d.loc.idx = (size_t) temp; + bc_vec_pop(&p->results); + bc_vec_push(&p->results, &r); +} + +#if BC_ENABLED +static void bc_program_incdec(BcProgram *p, uchar inst) { + + BcResult *ptr, res, copy; + BcNum *num; + uchar inst2; + + bc_program_prep(p, &ptr, &num, 0); + + BC_SIG_LOCK; + + copy.t = BC_RESULT_TEMP; + bc_num_createCopy(©.d.n, num); + + BC_SETJMP_LOCKED(exit); + + BC_SIG_UNLOCK; + + res.t = BC_RESULT_ONE; + inst2 = BC_INST_ASSIGN_PLUS + (inst & 0x01); + + bc_vec_push(&p->results, &res); + bc_program_assign(p, inst2); + + BC_SIG_LOCK; + + bc_vec_pop(&p->results); + bc_vec_push(&p->results, ©); + + BC_UNSETJMP; + + BC_SIG_UNLOCK; + + return; + +exit: + BC_SIG_MAYLOCK; + bc_num_free(©.d.n); + BC_LONGJMP_CONT; +} + +static void bc_program_call(BcProgram *p, const char *restrict code, + size_t *restrict idx) +{ + BcInstPtr ip; + size_t i, nparams = bc_program_index(code, idx); + BcFunc *f; + BcVec *v; + BcLoc *a; + BcResultData param; + BcResult *arg; + + ip.idx = 0; + ip.func = bc_program_index(code, idx); + f = bc_vec_item(&p->fns, ip.func); + + if (BC_ERR(!f->code.len)) bc_vm_verr(BC_ERROR_EXEC_UNDEF_FUNC, f->name); + if (BC_ERR(nparams != f->nparams)) + bc_vm_verr(BC_ERROR_EXEC_PARAMS, f->nparams, nparams); + ip.len = p->results.len - nparams; + + assert(BC_PROG_STACK(&p->results, nparams)); + + if (BC_G) bc_program_prepGlobals(p); + + for (i = 0; i < nparams; ++i) { + + size_t j; + bool last = true; + + arg = bc_vec_top(&p->results); + if (BC_ERR(arg->t == BC_RESULT_VOID)) + bc_vm_err(BC_ERROR_EXEC_VOID_VAL); + + a = bc_vec_item(&f->autos, nparams - 1 - i); + + // If I have already pushed to a var, I need to make sure I + // get the previous version, not the already pushed one. + if (arg->t == BC_RESULT_VAR || arg->t == BC_RESULT_ARRAY) { + for (j = 0; j < i && last; ++j) { + BcLoc *loc = bc_vec_item(&f->autos, nparams - 1 - j); + last = (arg->d.loc.loc != loc->loc || + (!loc->idx) != (arg->t == BC_RESULT_VAR)); + } + } + + bc_program_copyToVar(p, a->loc, (BcType) a->idx, last); + } + + BC_SIG_LOCK; + + for (; i < f->autos.len; ++i) { + + a = bc_vec_item(&f->autos, i); + v = bc_program_vec(p, a->loc, (BcType) a->idx); + + if (a->idx == BC_TYPE_VAR) { + bc_num_init(¶m.n, BC_NUM_DEF_SIZE); + bc_vec_push(v, ¶m.n); + } + else { + assert(a->idx == BC_TYPE_ARRAY); + bc_array_init(¶m.v, true); + bc_vec_push(v, ¶m.v); + } + } + + bc_vec_push(&p->stack, &ip); + + BC_SIG_UNLOCK; +} + +static void bc_program_return(BcProgram *p, uchar inst) { + + BcResult *res; + BcFunc *f; + BcInstPtr *ip = bc_vec_top(&p->stack); + size_t i, nops = p->results.len - ip->len; + + assert(BC_PROG_STACK(&p->stack, 2)); + assert(BC_PROG_STACK(&p->results, ip->len + (inst == BC_INST_RET))); + + f = bc_vec_item(&p->fns, ip->func); + res = bc_program_prepResult(p); + + if (inst == BC_INST_RET) { + + BcNum *num; + BcResult *operand; + + bc_program_operand(p, &operand, &num, 1); + + BC_SIG_LOCK; + + bc_num_createCopy(&res->d.n, num); + } + else if (inst == BC_INST_RET_VOID) res->t = BC_RESULT_VOID; + else { + BC_SIG_LOCK; + bc_num_init(&res->d.n, BC_NUM_DEF_SIZE); + } + + BC_SIG_MAYUNLOCK; + + // We need to pop arguments as well, so this takes that into account. + for (i = 0; i < f->autos.len; ++i) { + + BcLoc *a = bc_vec_item(&f->autos, i); + BcVec *v = bc_program_vec(p, a->loc, (BcType) a->idx); + + bc_vec_pop(v); + } + + bc_program_retire(p, 1, nops); + + if (BC_G) bc_program_popGlobals(p, false); + + bc_vec_pop(&p->stack); +} +#endif // BC_ENABLED + +static void bc_program_builtin(BcProgram *p, uchar inst) { + + BcResult *opd, *res; + BcNum *num; + bool len = (inst == BC_INST_LENGTH); + +#if BC_ENABLE_EXTRA_MATH + assert(inst >= BC_INST_LENGTH && inst <= BC_INST_IRAND); +#else // BC_ENABLE_EXTRA_MATH + assert(inst >= BC_INST_LENGTH && inst <= BC_INST_ABS); +#endif // BC_ENABLE_EXTRA_MATH + +#ifndef BC_PROG_NO_STACK_CHECK + if (!BC_IS_BC) { + if (BC_ERR(!BC_PROG_STACK(&p->results, 1))) + bc_vm_err(BC_ERROR_EXEC_STACK); + } +#endif // BC_PROG_NO_STACK_CHECK + + assert(BC_PROG_STACK(&p->results, 1)); + + res = bc_program_prepResult(p); + + bc_program_operand(p, &opd, &num, 1); + + assert(num != NULL); + +#if DC_ENABLED + if (!len && inst != BC_INST_SCALE_FUNC) bc_program_type_num(opd, num); +#endif // DC_ENABLED + + if (inst == BC_INST_SQRT) bc_num_sqrt(num, &res->d.n, BC_PROG_SCALE(p)); + else if (inst == BC_INST_ABS) { + + BC_SIG_LOCK; + + bc_num_createCopy(&res->d.n, num); + + BC_SIG_UNLOCK; + + res->d.n.neg = false; + } +#if BC_ENABLE_EXTRA_MATH + else if (inst == BC_INST_IRAND) { + + BC_SIG_LOCK; + + bc_num_init(&res->d.n, num->len - num->rdx); + + BC_SIG_UNLOCK; + + bc_num_irand(num, &res->d.n, &p->rng); + } +#endif // BC_ENABLE_EXTRA_MATH + else { + + BcBigDig val = 0; + + if (len) { +#if BC_ENABLED + if (BC_IS_BC && opd->t == BC_RESULT_ARRAY) { + + BcVec *v = (BcVec*) num; + + if (v->size == sizeof(uchar)) v = bc_program_dereference(p, v); + + assert(v->size == sizeof(BcNum)); + + val = (BcBigDig) v->len; + } + else +#endif // BC_ENABLED + { +#if DC_ENABLED + if (!BC_PROG_NUM(opd, num)) { + size_t idx; + char *str; + idx = opd->t == BC_RESULT_STR ? opd->d.loc.loc : num->scale; + str = *((char**) bc_vec_item(p->strs, idx)); + val = (BcBigDig) strlen(str); + } + else +#endif // DC_ENABLED + { + val = (BcBigDig) bc_num_len(num); + } + } + } + else if (BC_IS_BC || BC_PROG_NUM(opd, num)) + val = (BcBigDig) bc_num_scale(num); + + BC_SIG_LOCK; + + bc_num_createFromBigdig(&res->d.n, val); + + BC_SIG_UNLOCK; + } + + bc_program_retire(p, 1, 1); +} + +#if DC_ENABLED +static void bc_program_divmod(BcProgram *p) { + + BcResult *opd1, *opd2, *res, *res2; + BcNum *n1, *n2; + size_t req; + + res2 = bc_program_prepResult(p); + res = bc_program_prepResult(p); + + // Update the pointer, just in case. + res2 = bc_vec_item_rev(&p->results, 1); + + bc_program_binOpPrep(p, &opd1, &n1, &opd2, &n2, 2); + + req = bc_num_mulReq(n1, n2, BC_PROG_SCALE(p)); + + BC_SIG_LOCK; + + bc_num_init(&res->d.n, req); + bc_num_init(&res2->d.n, req); + + BC_SIG_UNLOCK; + + bc_num_divmod(n1, n2, &res2->d.n, &res->d.n, BC_PROG_SCALE(p)); + + bc_program_retire(p, 2, 2); +} + +static void bc_program_modexp(BcProgram *p) { + + BcResult *r1, *r2, *r3, *res; + BcNum *n1, *n2, *n3; + + if (BC_ERR(!BC_PROG_STACK(&p->results, 3))) bc_vm_err(BC_ERROR_EXEC_STACK); + + assert(BC_PROG_STACK(&p->results, 3)); + + res = bc_program_prepResult(p); + + bc_program_operand(p, &r1, &n1, 3); + bc_program_type_num(r1, n1); + + bc_program_binOpPrep(p, &r2, &n2, &r3, &n3, 1); + + // Make sure that the values have their pointers updated, if necessary. + // Only array elements are possible. + if (r1->t == BC_RESULT_ARRAY_ELEM && (r1->t == r2->t || r1->t == r3->t)) + n1 = bc_program_num(p, r1); + + BC_SIG_LOCK; + + bc_num_init(&res->d.n, n3->len); + + BC_SIG_UNLOCK; + + bc_num_modexp(n1, n2, n3, &res->d.n); + + bc_program_retire(p, 1, 3); +} + +static void bc_program_stackLen(BcProgram *p) { + bc_program_pushBigdig(p, (BcBigDig) p->results.len, BC_RESULT_TEMP); +} + +static uchar bc_program_asciifyNum(BcProgram *p, BcNum *n) { + + BcNum num; + BcBigDig val = 0; + + bc_num_clear(&num); + + BC_SETJMP(num_err); + + BC_SIG_LOCK; + + bc_num_createCopy(&num, n); + + BC_SIG_UNLOCK; + + bc_num_truncate(&num, num.scale); + num.neg = false; + + // This is guaranteed to not have a divide by 0 + // because strmb is equal to UCHAR_MAX + 1. + bc_num_mod(&num, &p->strmb, &num, 0); + + // This is also guaranteed to not error because num is in the range + // [0, UCHAR_MAX], which is definitely in range for a BcBigDig. And + // it is not negative. + bc_num_bigdig2(&num, &val); + +num_err: + BC_SIG_MAYLOCK; + bc_num_free(&num); + BC_LONGJMP_CONT; + return (uchar) val; +} + +static void bc_program_asciify(BcProgram *p) { + + BcResult *r, res; + BcNum *n; + char str[2], *str2; + uchar c; + size_t idx; + + if (BC_ERR(!BC_PROG_STACK(&p->results, 1))) bc_vm_err(BC_ERROR_EXEC_STACK); + + assert(BC_PROG_STACK(&p->results, 1)); + + bc_program_operand(p, &r, &n, 0); + + assert(n != NULL); + + assert(p->strs->len + BC_PROG_REQ_FUNCS == p->fns.len); + + if (BC_PROG_NUM(r, n)) c = bc_program_asciifyNum(p, n); + else { + size_t index = r->t == BC_RESULT_STR ? r->d.loc.loc : n->scale; + str2 = *((char**) bc_vec_item(p->strs, index)); + c = (uchar) str2[0]; + } + + str[0] = (char) c; + str[1] = '\0'; + + BC_SIG_LOCK; + + idx = bc_program_insertFunc(p, str) - BC_PROG_REQ_FUNCS; + + BC_SIG_UNLOCK; + + res.t = BC_RESULT_STR; + res.d.loc.loc = idx; + bc_vec_pop(&p->results); + bc_vec_push(&p->results, &res); +} + +static void bc_program_printStream(BcProgram *p) { + + BcResult *r; + BcNum *n; + + if (BC_ERR(!BC_PROG_STACK(&p->results, 1))) bc_vm_err(BC_ERROR_EXEC_STACK); + + assert(BC_PROG_STACK(&p->results, 1)); + + bc_program_operand(p, &r, &n, 0); + + assert(n != NULL); + + if (BC_PROG_NUM(r, n)) bc_num_stream(n, p->strm); + else { + size_t idx = (r->t == BC_RESULT_STR) ? r->d.loc.loc : n->scale; + bc_program_printChars(*((char**) bc_vec_item(p->strs, idx))); + } +} + +static void bc_program_nquit(BcProgram *p, uchar inst) { + + BcResult *opnd; + BcNum *num; + BcBigDig val; + size_t i; + + assert(p->stack.len == p->tail_calls.len); + + if (inst == BC_INST_QUIT) val = 2; + else { + + bc_program_prep(p, &opnd, &num, 0); + bc_num_bigdig(num, &val); + + bc_vec_pop(&p->results); + } + + for (i = 0; val && i < p->tail_calls.len; ++i) { + size_t calls = *((size_t*) bc_vec_item_rev(&p->tail_calls, i)) + 1; + if (calls >= val) val = 0; + else val -= calls; + } + + if (i == p->stack.len) { + vm.status = BC_STATUS_QUIT; + BC_VM_JMP; + } + else { + bc_vec_npop(&p->stack, i); + bc_vec_npop(&p->tail_calls, i); + } +} + +static void bc_program_execStr(BcProgram *p, const char *restrict code, + size_t *restrict bgn, bool cond, size_t len) +{ + BcResult *r; + char *str; + BcFunc *f; + BcParse prs; + BcInstPtr ip; + size_t fidx, sidx; + BcNum *n; + + assert(p->stack.len == p->tail_calls.len); + + if (BC_ERR(!BC_PROG_STACK(&p->results, 1))) bc_vm_err(BC_ERROR_EXEC_STACK); + + assert(BC_PROG_STACK(&p->results, 1)); + + bc_program_operand(p, &r, &n, 0); + + if (cond) { + + bool exec; + size_t idx, then_idx, else_idx; + + then_idx = bc_program_index(code, bgn); + else_idx = bc_program_index(code, bgn); + + exec = (r->d.n.len != 0); + + idx = exec ? then_idx : else_idx; + + BC_SIG_LOCK; + BC_SETJMP_LOCKED(exit); + + if (exec || (else_idx != SIZE_MAX)) + n = bc_vec_top(bc_program_vec(p, idx, BC_TYPE_VAR)); + else goto exit; + + if (BC_ERR(!BC_PROG_STR(n))) bc_vm_err(BC_ERROR_EXEC_TYPE); + + BC_UNSETJMP; + BC_SIG_UNLOCK; + + sidx = n->scale; + } + else { + + // In non-conditional situations, only the top of stack can be executed, + // and in those cases, variables are not allowed to be "on the stack"; + // they are only put on the stack to be assigned to. + assert(r->t != BC_RESULT_VAR); + + if (r->t == BC_RESULT_STR) sidx = r->d.loc.loc; + else return; + } + + fidx = sidx + BC_PROG_REQ_FUNCS; + str = *((char**) bc_vec_item(p->strs, sidx)); + f = bc_vec_item(&p->fns, fidx); + + if (!f->code.len) { + + BC_SIG_LOCK; + + bc_parse_init(&prs, p, fidx); + bc_lex_file(&prs.l, vm.file); + + BC_SETJMP_LOCKED(err); + + BC_SIG_UNLOCK; + + bc_parse_text(&prs, str); + vm.expr(&prs, BC_PARSE_NOCALL); + + BC_SIG_LOCK; + + BC_UNSETJMP; + + // We can just assert this here because + // dc should parse everything until EOF. + assert(prs.l.t == BC_LEX_EOF); + + bc_parse_free(&prs); + + BC_SIG_UNLOCK; + } + + ip.idx = 0; + ip.len = p->results.len; + ip.func = fidx; + + bc_vec_pop(&p->results); + + // Tail call. + if (p->stack.len > 1 && *bgn == len - 1 && code[*bgn] == BC_INST_POP_EXEC) { + size_t *call_ptr = bc_vec_top(&p->tail_calls); + *call_ptr += 1; + bc_vec_pop(&p->stack); + } + else bc_vec_push(&p->tail_calls, &ip.idx); + + bc_vec_push(&p->stack, &ip); + + return; + +err: + BC_SIG_MAYLOCK; + bc_parse_free(&prs); + f = bc_vec_item(&p->fns, fidx); + bc_vec_npop(&f->code, f->code.len); +exit: + bc_vec_pop(&p->results); + BC_LONGJMP_CONT; +} + +static void bc_program_printStack(BcProgram *p) { + + size_t idx; + + for (idx = 0; idx < p->results.len; ++idx) + bc_program_print(p, BC_INST_PRINT, idx); +} +#endif // DC_ENABLED + +static void bc_program_pushGlobal(BcProgram *p, uchar inst) { + + BcResultType t; + + assert(inst >= BC_INST_IBASE && inst <= BC_INST_SCALE); + + t = inst - BC_INST_IBASE + BC_RESULT_IBASE; + bc_program_pushBigdig(p, p->globals[inst - BC_INST_IBASE], t); +} + +#if BC_ENABLE_EXTRA_MATH +static void bc_program_pushSeed(BcProgram *p) { + + BcResult *res; + + res = bc_program_prepResult(p); + res->t = BC_RESULT_SEED; + + BC_SIG_LOCK; + + bc_num_init(&res->d.n, 2 * BC_RAND_NUM_SIZE); + + BC_SIG_UNLOCK; + + bc_num_createFromRNG(&res->d.n, &p->rng); +} +#endif // BC_ENABLE_EXTRA_MATH + +static void bc_program_addFunc(BcProgram *p, BcFunc *f, BcId *id_ptr) { + + BcInstPtr *ip; + + BC_SIG_ASSERT_LOCKED; + + bc_func_init(f, id_ptr->name); + bc_vec_push(&p->fns, f); + + // This is to make sure pointers are updated if the array was moved. + if (BC_IS_BC && p->stack.len) { + ip = bc_vec_item_rev(&p->stack, 0); + bc_program_setVecs(p, (BcFunc*) bc_vec_item(&p->fns, ip->func)); + } + else bc_program_setVecs(p, (BcFunc*) bc_vec_item(&p->fns, BC_PROG_MAIN)); +} + +size_t bc_program_insertFunc(BcProgram *p, const char *name) { + + BcId *id_ptr; + BcFunc f; + bool new; + size_t idx; + + BC_SIG_ASSERT_LOCKED; + + assert(p != NULL && name != NULL); + + new = bc_map_insert(&p->fn_map, name, p->fns.len, &idx); + id_ptr = (BcId*) bc_vec_item(&p->fn_map, idx); + idx = id_ptr->idx; + + if (!new) { + if (BC_IS_BC) { + BcFunc *func = bc_vec_item(&p->fns, idx); + bc_func_reset(func); + } + } + else { + + bc_program_addFunc(p, &f, id_ptr); + +#if DC_ENABLED + if (!BC_IS_BC && strcmp(name, bc_func_main) && + strcmp(name, bc_func_read)) + { + bc_vec_push(p->strs, &id_ptr->name); + assert(p->strs->len == p->fns.len - BC_PROG_REQ_FUNCS); + } +#endif // DC_ENABLED + } + + return idx; +} + +#ifndef NDEBUG +void bc_program_free(BcProgram *p) { + + size_t i; + + BC_SIG_ASSERT_LOCKED; + + assert(p != NULL); + + for (i = 0; i < BC_PROG_GLOBALS_LEN; ++i) bc_vec_free(p->globals_v + i); + + bc_vec_free(&p->fns); + bc_vec_free(&p->fn_map); + bc_vec_free(&p->vars); + bc_vec_free(&p->var_map); + bc_vec_free(&p->arrs); + bc_vec_free(&p->arr_map); + bc_vec_free(&p->results); + bc_vec_free(&p->stack); + +#if BC_ENABLED + if (BC_IS_BC) bc_num_free(&p->last); +#endif // BC_ENABLED + +#if BC_ENABLE_EXTRA_MATH + bc_rand_free(&p->rng); +#endif // BC_ENABLE_EXTRA_MATH + +#if DC_ENABLED + if (!BC_IS_BC) bc_vec_free(&p->tail_calls); +#endif // DC_ENABLED +} +#endif // NDEBUG + +void bc_program_init(BcProgram *p) { + + BcInstPtr ip; + size_t i; + BcBigDig val = BC_BASE; + + BC_SIG_ASSERT_LOCKED; + + assert(p != NULL); + + memset(p, 0, sizeof(BcProgram)); + memset(&ip, 0, sizeof(BcInstPtr)); + + for (i = 0; i < BC_PROG_GLOBALS_LEN; ++i) { + bc_vec_init(p->globals_v + i, sizeof(BcBigDig), NULL); + val = i == BC_PROG_GLOBALS_SCALE ? 0 : val; + bc_vec_push(p->globals_v + i, &val); + p->globals[i] = val; + } + +#if DC_ENABLED + if (!BC_IS_BC) { + + bc_vec_init(&p->tail_calls, sizeof(size_t), NULL); + i = 0; + bc_vec_push(&p->tail_calls, &i); + + p->strm = UCHAR_MAX + 1; + bc_num_setup(&p->strmb, p->strmb_num, BC_NUM_BIGDIG_LOG10); + bc_num_bigdig2num(&p->strmb, p->strm); + } +#endif // DC_ENABLED + +#if BC_ENABLE_EXTRA_MATH + srand((unsigned int) time(NULL)); + bc_rand_init(&p->rng); +#endif // BC_ENABLE_EXTRA_MATH + + bc_num_setup(&p->one, p->one_num, BC_PROG_ONE_CAP); + bc_num_one(&p->one); + +#if BC_ENABLED + if (BC_IS_BC) bc_num_init(&p->last, BC_NUM_DEF_SIZE); +#endif // BC_ENABLED + + bc_vec_init(&p->fns, sizeof(BcFunc), bc_func_free); + bc_map_init(&p->fn_map); + bc_program_insertFunc(p, bc_func_main); + bc_program_insertFunc(p, bc_func_read); + + bc_vec_init(&p->vars, sizeof(BcVec), bc_vec_free); + bc_map_init(&p->var_map); + + bc_vec_init(&p->arrs, sizeof(BcVec), bc_vec_free); + bc_map_init(&p->arr_map); + + bc_vec_init(&p->results, sizeof(BcResult), bc_result_free); + bc_vec_init(&p->stack, sizeof(BcInstPtr), NULL); + bc_vec_push(&p->stack, &ip); +} + +void bc_program_reset(BcProgram *p) { + + BcFunc *f; + BcInstPtr *ip; + + BC_SIG_ASSERT_LOCKED; + + bc_vec_npop(&p->stack, p->stack.len - 1); + bc_vec_npop(&p->results, p->results.len); + + if (BC_G) bc_program_popGlobals(p, true); + + f = bc_vec_item(&p->fns, BC_PROG_MAIN); + ip = bc_vec_top(&p->stack); + if (BC_IS_BC) bc_program_setVecs(p, f); + ip->idx = f->code.len; + + if (vm.sig) { + bc_file_write(&vm.fout, bc_program_ready_msg, bc_program_ready_msg_len); + bc_file_flush(&vm.fout); + vm.sig = 0; + } +} + +void bc_program_exec(BcProgram *p) { + + size_t idx; + BcResult r, *ptr; + BcInstPtr *ip = bc_vec_top(&p->stack); + BcFunc *func = bc_vec_item(&p->fns, ip->func); + char *code = func->code.v; + bool cond = false; +#if BC_ENABLED + BcNum *num; +#endif // BC_ENABLED +#ifndef NDEBUG + size_t jmp_bufs_len; +#endif // NDEBUG + +#ifndef NDEBUG + jmp_bufs_len = vm.jmp_bufs.len; +#endif // NDEBUG + + if (BC_IS_BC) bc_program_setVecs(p, func); + else bc_program_setVecs(p, (BcFunc*) bc_vec_item(&p->fns, BC_PROG_MAIN)); + + while (ip->idx < func->code.len) { + + BC_SIG_ASSERT_NOT_LOCKED; + + uchar inst = (uchar) code[(ip->idx)++]; + + switch (inst) { + +#if BC_ENABLED + case BC_INST_JUMP_ZERO: + { + bc_program_prep(p, &ptr, &num, 0); + cond = !bc_num_cmpZero(num); + bc_vec_pop(&p->results); + } + // Fallthrough. + case BC_INST_JUMP: + { + idx = bc_program_index(code, &ip->idx); + + if (inst == BC_INST_JUMP || cond) { + + size_t *addr = bc_vec_item(&func->labels, idx); + + assert(*addr != SIZE_MAX); + + ip->idx = *addr; + } + + break; + } + + case BC_INST_CALL: + { + assert(BC_IS_BC); + + bc_program_call(p, code, &ip->idx); + + ip = bc_vec_top(&p->stack); + func = bc_vec_item(&p->fns, ip->func); + code = func->code.v; + + bc_program_setVecs(p, func); + + break; + } + + case BC_INST_INC: + case BC_INST_DEC: + { + bc_program_incdec(p, inst); + break; + } + + case BC_INST_HALT: + { + vm.status = BC_STATUS_QUIT; + BC_VM_JMP; + break; + } + + case BC_INST_RET: + case BC_INST_RET0: + case BC_INST_RET_VOID: + { + bc_program_return(p, inst); + + ip = bc_vec_top(&p->stack); + func = bc_vec_item(&p->fns, ip->func); + code = func->code.v; + + if (BC_IS_BC) bc_program_setVecs(p, func); + + break; + } +#endif // BC_ENABLED + + case BC_INST_BOOL_OR: + case BC_INST_BOOL_AND: + case BC_INST_REL_EQ: + case BC_INST_REL_LE: + case BC_INST_REL_GE: + case BC_INST_REL_NE: + case BC_INST_REL_LT: + case BC_INST_REL_GT: + { + bc_program_logical(p, inst); + break; + } + + case BC_INST_READ: + { + bc_program_read(p); + + ip = bc_vec_top(&p->stack); + func = bc_vec_item(&p->fns, ip->func); + code = func->code.v; + + if (BC_IS_BC) bc_program_setVecs(p, func); + + break; + } + +#if BC_ENABLE_EXTRA_MATH + case BC_INST_RAND: + { + bc_program_rand(p); + break; + } +#endif // BC_ENABLE_EXTRA_MATH + + case BC_INST_MAXIBASE: + case BC_INST_MAXOBASE: + case BC_INST_MAXSCALE: +#if BC_ENABLE_EXTRA_MATH + case BC_INST_MAXRAND: +#endif // BC_ENABLE_EXTRA_MATH + { + BcBigDig dig = vm.maxes[inst - BC_INST_MAXIBASE]; + bc_program_pushBigdig(p, dig, BC_RESULT_TEMP); + break; + } + + case BC_INST_VAR: + { + bc_program_pushVar(p, code, &ip->idx, false, false); + break; + } + + case BC_INST_ARRAY_ELEM: +#if BC_ENABLED + case BC_INST_ARRAY: +#endif // BC_ENABLED + { + bc_program_pushArray(p, code, &ip->idx, inst); + break; + } + + case BC_INST_IBASE: + case BC_INST_SCALE: + case BC_INST_OBASE: + { + bc_program_pushGlobal(p, inst); + break; + } + +#if BC_ENABLE_EXTRA_MATH + case BC_INST_SEED: + { + bc_program_pushSeed(p); + break; + } +#endif // BC_ENABLE_EXTRA_MATH + + case BC_INST_LENGTH: + case BC_INST_SCALE_FUNC: + case BC_INST_SQRT: + case BC_INST_ABS: +#if BC_ENABLE_EXTRA_MATH + case BC_INST_IRAND: +#endif // BC_ENABLE_EXTRA_MATH + { + bc_program_builtin(p, inst); + break; + } + + case BC_INST_NUM: + { + r.t = BC_RESULT_CONSTANT; + r.d.loc.loc = bc_program_index(code, &ip->idx); + bc_vec_push(&p->results, &r); + break; + } + + case BC_INST_ONE: +#if BC_ENABLED + case BC_INST_LAST: +#endif // BC_ENABLED + { + r.t = BC_RESULT_ONE + (inst - BC_INST_ONE); + bc_vec_push(&p->results, &r); + break; + } + + case BC_INST_PRINT: + case BC_INST_PRINT_POP: + case BC_INST_PRINT_STR: + { + bc_program_print(p, inst, 0); + break; + } + + case BC_INST_STR: + { + r.t = BC_RESULT_STR; + r.d.loc.loc = bc_program_index(code, &ip->idx); + bc_vec_push(&p->results, &r); + break; + } + + case BC_INST_POWER: + case BC_INST_MULTIPLY: + case BC_INST_DIVIDE: + case BC_INST_MODULUS: + case BC_INST_PLUS: + case BC_INST_MINUS: +#if BC_ENABLE_EXTRA_MATH + case BC_INST_PLACES: + case BC_INST_LSHIFT: + case BC_INST_RSHIFT: +#endif // BC_ENABLE_EXTRA_MATH + { + bc_program_op(p, inst); + break; + } + + case BC_INST_NEG: + case BC_INST_BOOL_NOT: +#if BC_ENABLE_EXTRA_MATH + case BC_INST_TRUNC: +#endif // BC_ENABLE_EXTRA_MATH + { + bc_program_unary(p, inst); + break; + } + +#if BC_ENABLED + case BC_INST_ASSIGN_POWER: + case BC_INST_ASSIGN_MULTIPLY: + case BC_INST_ASSIGN_DIVIDE: + case BC_INST_ASSIGN_MODULUS: + case BC_INST_ASSIGN_PLUS: + case BC_INST_ASSIGN_MINUS: +#if BC_ENABLE_EXTRA_MATH + case BC_INST_ASSIGN_PLACES: + case BC_INST_ASSIGN_LSHIFT: + case BC_INST_ASSIGN_RSHIFT: +#endif // BC_ENABLE_EXTRA_MATH + case BC_INST_ASSIGN: + case BC_INST_ASSIGN_POWER_NO_VAL: + case BC_INST_ASSIGN_MULTIPLY_NO_VAL: + case BC_INST_ASSIGN_DIVIDE_NO_VAL: + case BC_INST_ASSIGN_MODULUS_NO_VAL: + case BC_INST_ASSIGN_PLUS_NO_VAL: + case BC_INST_ASSIGN_MINUS_NO_VAL: +#if BC_ENABLE_EXTRA_MATH + case BC_INST_ASSIGN_PLACES_NO_VAL: + case BC_INST_ASSIGN_LSHIFT_NO_VAL: + case BC_INST_ASSIGN_RSHIFT_NO_VAL: +#endif // BC_ENABLE_EXTRA_MATH +#endif // BC_ENABLED + case BC_INST_ASSIGN_NO_VAL: + { + bc_program_assign(p, inst); + break; + } + + case BC_INST_POP: + { +#ifndef BC_PROG_NO_STACK_CHECK + if (!BC_IS_BC) { + if (BC_ERR(!BC_PROG_STACK(&p->results, 1))) + bc_vm_err(BC_ERROR_EXEC_STACK); + } +#endif // BC_PROG_NO_STACK_CHECK + + assert(BC_PROG_STACK(&p->results, 1)); + + bc_vec_pop(&p->results); + break; + } + +#if DC_ENABLED + case BC_INST_POP_EXEC: + { + assert(BC_PROG_STACK(&p->stack, 2)); + bc_vec_pop(&p->stack); + bc_vec_pop(&p->tail_calls); + ip = bc_vec_top(&p->stack); + func = bc_vec_item(&p->fns, ip->func); + code = func->code.v; + break; + } + + case BC_INST_MODEXP: + { + bc_program_modexp(p); + break; + } + + case BC_INST_DIVMOD: + { + bc_program_divmod(p); + break; + } + + case BC_INST_EXECUTE: + case BC_INST_EXEC_COND: + { + cond = (inst == BC_INST_EXEC_COND); + bc_program_execStr(p, code, &ip->idx, cond, func->code.len); + ip = bc_vec_top(&p->stack); + func = bc_vec_item(&p->fns, ip->func); + code = func->code.v; + break; + } + + case BC_INST_PRINT_STACK: + { + bc_program_printStack(p); + break; + } + + case BC_INST_CLEAR_STACK: + { + bc_vec_npop(&p->results, p->results.len); + break; + } + + case BC_INST_STACK_LEN: + { + bc_program_stackLen(p); + break; + } + + case BC_INST_DUPLICATE: + { + if (BC_ERR(!BC_PROG_STACK(&p->results, 1))) + bc_vm_err(BC_ERROR_EXEC_STACK); + + assert(BC_PROG_STACK(&p->results, 1)); + + ptr = bc_vec_top(&p->results); + + BC_SIG_LOCK; + + bc_result_copy(&r, ptr); + bc_vec_push(&p->results, &r); + + BC_SIG_UNLOCK; + + break; + } + + case BC_INST_SWAP: + { + BcResult *ptr2; + + if (BC_ERR(!BC_PROG_STACK(&p->results, 2))) + bc_vm_err(BC_ERROR_EXEC_STACK); + + assert(BC_PROG_STACK(&p->results, 2)); + + ptr = bc_vec_item_rev(&p->results, 0); + ptr2 = bc_vec_item_rev(&p->results, 1); + memcpy(&r, ptr, sizeof(BcResult)); + memcpy(ptr, ptr2, sizeof(BcResult)); + memcpy(ptr2, &r, sizeof(BcResult)); + + break; + } + + case BC_INST_ASCIIFY: + { + bc_program_asciify(p); + ip = bc_vec_top(&p->stack); + func = bc_vec_item(&p->fns, ip->func); + code = func->code.v; + break; + } + + case BC_INST_PRINT_STREAM: + { + bc_program_printStream(p); + break; + } + + case BC_INST_LOAD: + case BC_INST_PUSH_VAR: + { + bool copy = (inst == BC_INST_LOAD); + bc_program_pushVar(p, code, &ip->idx, true, copy); + break; + } + + case BC_INST_PUSH_TO_VAR: + { + idx = bc_program_index(code, &ip->idx); + bc_program_copyToVar(p, idx, BC_TYPE_VAR, true); + break; + } + + case BC_INST_QUIT: + case BC_INST_NQUIT: + { + bc_program_nquit(p, inst); + ip = bc_vec_top(&p->stack); + func = bc_vec_item(&p->fns, ip->func); + code = func->code.v; + break; + } +#endif // DC_ENABLED +#ifndef NDEBUG + default: + { + abort(); + } +#endif // NDEBUG + } + +#ifndef NDEBUG + // This is to allow me to use a debugger to see the last instruction, + // which will point to which function was the problem. + assert(jmp_bufs_len == vm.jmp_bufs.len); +#endif // NDEBUG + } +} + +#if BC_DEBUG_CODE +#if BC_ENABLED && DC_ENABLED +void bc_program_printStackDebug(BcProgram *p) { + bc_file_puts(&vm.fout, "-------------- Stack ----------\n"); + bc_program_printStack(p); + bc_file_puts(&vm.fout, "-------------- Stack End ------\n"); +} + +static void bc_program_printIndex(const char *restrict code, + size_t *restrict bgn) +{ + uchar byte, i, bytes = (uchar) code[(*bgn)++]; + ulong val = 0; + + for (byte = 1, i = 0; byte && i < bytes; ++i) { + byte = (uchar) code[(*bgn)++]; + if (byte) val |= ((ulong) byte) << (CHAR_BIT * i); + } + + bc_vm_printf(" (%lu) ", val); +} + +static void bc_program_printStr(const BcProgram *p, const char *restrict code, + size_t *restrict bgn) +{ + size_t idx = bc_program_index(code, bgn); + char *s; + + s = *((char**) bc_vec_item(p->strs, idx)); + + bc_vm_printf(" (\"%s\") ", s); +} + +void bc_program_printInst(const BcProgram *p, const char *restrict code, + size_t *restrict bgn) +{ + uchar inst = (uchar) code[(*bgn)++]; + + bc_vm_printf("Inst[%zu]: %s [%lu]; ", *bgn - 1, + bc_inst_names[inst], (unsigned long) inst); + + if (inst == BC_INST_VAR || inst == BC_INST_ARRAY_ELEM || + inst == BC_INST_ARRAY) + { + bc_program_printIndex(code, bgn); + } + else if (inst == BC_INST_STR) bc_program_printStr(p, code, bgn); + else if (inst == BC_INST_NUM) { + size_t idx = bc_program_index(code, bgn); + BcConst *c = bc_vec_item(p->consts, idx); + bc_vm_printf("(%s)", c->val); + } + else if (inst == BC_INST_CALL || + (inst > BC_INST_STR && inst <= BC_INST_JUMP_ZERO)) + { + bc_program_printIndex(code, bgn); + if (inst == BC_INST_CALL) bc_program_printIndex(code, bgn); + } + + bc_vm_putchar('\n'); +} + +void bc_program_code(const BcProgram* p) { + + BcFunc *f; + char *code; + BcInstPtr ip; + size_t i; + + for (i = 0; i < p->fns.len; ++i) { + + ip.idx = ip.len = 0; + ip.func = i; + + f = bc_vec_item(&p->fns, ip.func); + code = f->code.v; + + bc_vm_printf("func[%zu]:\n", ip.func); + while (ip.idx < f->code.len) bc_program_printInst(p, code, &ip.idx); + bc_file_puts(&vm.fout, "\n\n"); + } +} +#endif // BC_ENABLED && DC_ENABLED +#endif // BC_DEBUG_CODE diff --git a/src/rand/rand.c b/src/rand/rand.c new file mode 100644 index 000000000000..aa589bbd533d --- /dev/null +++ b/src/rand/rand.c @@ -0,0 +1,415 @@ +/* + * ***************************************************************************** + * + * 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. + * + * ***************************************************************************** + * + * Parts of this code are adapted from the following: + * + * PCG, A Family of Better Random Number Generators. + * + * You can find the original source code at: + * https://github.com/imneme/pcg-c + * + * ----------------------------------------------------------------------------- + * + * Parts of this code are also under the following license: + * + * Copyright (c) 2014-2017 Melissa O'Neill and PCG Project contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + * ***************************************************************************** + * + * Code for the RNG. + * + */ + +#if BC_ENABLE_EXTRA_MATH + +#include <assert.h> +#include <stdlib.h> +#include <string.h> +#include <time.h> +#include <fcntl.h> +#include <unistd.h> + +#include <status.h> +#include <num.h> +#include <rand.h> +#include <vm.h> + +#if !BC_RAND_BUILTIN + +static BcRandState bc_rand_addition(uint_fast64_t a, uint_fast64_t b) { + + BcRandState res; + + res.lo = a + b; + res.hi = (res.lo < a); + + return res; +} + +static BcRandState bc_rand_addition2(BcRandState a, BcRandState b) { + + BcRandState temp, res; + + res = bc_rand_addition(a.lo, b.lo); + temp = bc_rand_addition(a.hi, b.hi); + res.hi += temp.lo; + + return res; +} + +static BcRandState bc_rand_multiply(uint_fast64_t a, uint_fast64_t b) { + + uint_fast64_t al, ah, bl, bh, c0, c1, c2, c3; + BcRandState carry, res; + + al = BC_RAND_TRUNC32(a); + ah = BC_RAND_CHOP32(a); + bl = BC_RAND_TRUNC32(b); + bh = BC_RAND_CHOP32(b); + + c0 = al * bl; + c1 = al * bh; + c2 = ah * bl; + c3 = ah * bh; + + carry = bc_rand_addition(c1, c2); + + res = bc_rand_addition(c0, (BC_RAND_TRUNC32(carry.lo)) << 32); + res.hi += BC_RAND_CHOP32(carry.lo) + c3 + (carry.hi << 32); + + return res; +} + +static BcRandState bc_rand_multiply2(BcRandState a, BcRandState b) { + + BcRandState c0, c1, c2, carry; + + c0 = bc_rand_multiply(a.lo, b.lo); + c1 = bc_rand_multiply(a.lo, b.hi); + c2 = bc_rand_multiply(a.hi, b.lo); + + carry = bc_rand_addition2(c1, c2); + carry.hi = carry.lo; + carry.lo = 0; + + return bc_rand_addition2(c0, carry); +} + +#endif // BC_RAND_BUILTIN + +static void bc_rand_setModified(BcRNGData *r) { + +#if BC_RAND_BUILTIN + r->inc |= (BcRandState) 1UL; +#else // BC_RAND_BUILTIN + r->inc.lo |= (uint_fast64_t) 1UL; +#endif // BC_RAND_BUILTIN +} + +static void bc_rand_clearModified(BcRNGData *r) { + +#if BC_RAND_BUILTIN + r->inc &= ~((BcRandState) 1UL); +#else // BC_RAND_BUILTIN + r->inc.lo &= ~(1UL); +#endif // BC_RAND_BUILTIN +} + +static void bc_rand_copy(BcRNGData *d, BcRNGData *s) { + bool unmod = BC_RAND_NOTMODIFIED(d); + memcpy(d, s, sizeof(BcRNGData)); + if (!unmod) bc_rand_setModified(d); + else if (!BC_RAND_NOTMODIFIED(s)) bc_rand_clearModified(d); +} + +static ulong bc_rand_frand(void *ptr) { + + ulong buf[1]; + int fd; + ssize_t nread; + + assert(ptr != NULL); + + fd = *((int*) ptr); + + nread = read(fd, buf, sizeof(ulong)); + + if (BC_ERR(nread != sizeof(ulong))) bc_vm_err(BC_ERROR_FATAL_IO_ERR); + + return *((ulong*) buf); +} + +static ulong bc_rand_rand(void *ptr) { + + size_t i; + ulong res = 0; + + BC_UNUSED(ptr); + + for (i = 0; i < sizeof(ulong); ++i) + res |= ((ulong) (rand() & BC_RAND_SRAND_BITS)) << (i * CHAR_BIT); + + return res; +} + +static BcRandState bc_rand_inc(BcRNGData *r) { + + BcRandState inc; + +#if BC_RAND_BUILTIN + inc = r->inc | 1; +#else // BC_RAND_BUILTIN + inc.lo = r->inc.lo | 1; + inc.hi = r->inc.hi; +#endif // BC_RAND_BUILTIN + + return inc; +} + +static void bc_rand_setInc(BcRNGData *r) { + +#if BC_RAND_BUILTIN + r->inc <<= 1UL; +#else // BC_RAND_BUILTIN + r->inc.hi <<= 1UL; + r->inc.hi |= (r->inc.lo & (1UL << (BC_LONG_BIT - 1))) >> (BC_LONG_BIT - 1); + r->inc.lo <<= 1UL; +#endif // BC_RAND_BUILTIN +} + +static void bc_rand_seedState(BcRandState *state, ulong val1, ulong val2) { + +#if BC_RAND_BUILTIN + *state = ((BcRandState) val1) | ((BcRandState) val2) << (BC_LONG_BIT); +#else // BC_RAND_BUILTIN + state->lo = val1; + state->hi = val2; +#endif // BC_RAND_BUILTIN +} + +static void bc_rand_seedRNG(BcRNGData *r, ulong state1, ulong state2, + ulong inc1, ulong inc2) +{ + bc_rand_seedState(&r->state, state1, state2); + bc_rand_seedState(&r->inc, inc1, inc2); + bc_rand_setInc(r); +} + +static void bc_rand_fill(BcRNGData *r, BcRandUlong fulong, void *ptr) { + + ulong state1, state2, inc1, inc2; + + state1 = fulong(ptr); + state2 = fulong(ptr); + + inc1 = fulong(ptr); + inc2 = fulong(ptr); + + bc_rand_seedRNG(r, state1, state2, inc1, inc2); +} + +static void bc_rand_step(BcRNGData *r) { + BcRandState temp = bc_rand_mul2(r->state, bc_rand_multiplier); + r->state = bc_rand_add2(temp, bc_rand_inc(r)); +} + +static BcRand bc_rand_output(BcRNGData *r) { + return BC_RAND_ROT(BC_RAND_FOLD(r->state), BC_RAND_ROTAMT(r->state)); +} + +static void bc_rand_seedZeroes(BcRNG *r, BcRNGData *rng, size_t idx) { + + BcRNGData *rng2; + + if (r->v.len <= idx) return; + + rng2 = bc_vec_item_rev(&r->v, idx); + + if (BC_RAND_ZERO(rng2)) { + size_t i; + for (i = 1; i < r->v.len; ++i) + bc_rand_copy(bc_vec_item_rev(&r->v, i), rng); + } +} + +static void bc_rand_srand(BcRNGData *rng) { + + int fd; + + BC_SIG_LOCK; + + fd = open("/dev/urandom", O_RDONLY); + + if (BC_NO_ERR(fd >= 0)) { + bc_rand_fill(rng, bc_rand_frand, &fd); + close(fd); + } + + while (BC_ERR(BC_RAND_ZERO(rng))) bc_rand_fill(rng, bc_rand_rand, NULL); + + BC_SIG_UNLOCK; +} + +static void bc_rand_propagate(BcRNG *r, BcRNGData *rng) { + + if (r->v.len <= 1) return; + + if (BC_RAND_NOTMODIFIED(rng)) { + + size_t i; + bool go = true; + + for (i = 1; go && i < r->v.len; ++i) { + BcRNGData *rng2 = bc_vec_item_rev(&r->v, i); + go = BC_RAND_NOTMODIFIED(rng2); + bc_rand_copy(rng2, rng); + } + + bc_rand_seedZeroes(r, rng, i); + } + else bc_rand_seedZeroes(r, rng, 1); +} + +BcRand bc_rand_int(BcRNG *r) { + + BcRNGData *rng = bc_vec_top(&r->v); + + if (BC_ERR(BC_RAND_ZERO(rng))) bc_rand_srand(rng); + + bc_rand_step(rng); + bc_rand_propagate(r, rng); + + return bc_rand_output(rng); +} + +BcRand bc_rand_bounded(BcRNG *r, BcRand bound) { + + BcRand rand, threshold = (0 - bound) % bound; + + do { + rand = bc_rand_int(r); + } while (rand < threshold); + + return rand % bound; +} + +void bc_rand_seed(BcRNG *r, ulong state1, ulong state2, ulong inc1, ulong inc2) +{ + BcRNGData *rng = bc_vec_top(&r->v); + + bc_rand_seedState(&rng->inc, inc1, inc2); + bc_rand_setInc(rng); + bc_rand_setModified(rng); + + if (!state1 && !state2) { + memcpy(&rng->state, &rng->inc, sizeof(BcRandState)); + bc_rand_step(rng); + } + else bc_rand_seedState(&rng->state, state1, state2); + + bc_rand_propagate(r, rng); +} + +static BcRandState bc_rand_getInc(BcRNGData *r) { + + BcRandState res; + +#if BC_RAND_BUILTIN + res = r->inc >> 1; +#else // BC_RAND_BUILTIN + res = r->inc; + res.lo >>= 1; + res.lo |= (res.hi & 1) << (BC_LONG_BIT - 1); + res.hi >>= 1; +#endif // BC_RAND_BUILTIN + + return res; +} + +void bc_rand_getRands(BcRNG *r, BcRand *s1, BcRand *s2, BcRand *i1, BcRand *i2) +{ + BcRandState inc; + BcRNGData *rng = bc_vec_top(&r->v); + + if (BC_ERR(BC_RAND_ZERO(rng))) bc_rand_srand(rng); + + inc = bc_rand_getInc(rng); + + *s1 = BC_RAND_TRUNC(rng->state); + *s2 = BC_RAND_CHOP(rng->state); + + *i1 = BC_RAND_TRUNC(inc); + *i2 = BC_RAND_CHOP(inc); +} + +void bc_rand_push(BcRNG *r) { + BcRNGData rng; + memset(&rng, 0, sizeof(BcRNGData)); + if (r->v.len > 0) bc_rand_copy(&rng, bc_vec_top(&r->v)); + bc_vec_push(&r->v, &rng); +} + +void bc_rand_pop(BcRNG *r, bool reset) { + bc_vec_npop(&r->v, reset ? r->v.len - 1 : 1); +} + +void bc_rand_init(BcRNG *r) { + BC_SIG_ASSERT_LOCKED; + bc_vec_init(&r->v, sizeof(BcRNGData), NULL); + bc_rand_push(r); +} + +#ifndef NDEBUG +void bc_rand_free(BcRNG *r) { + BC_SIG_ASSERT_LOCKED; + bc_vec_free(&r->v); +} +#endif // NDEBUG + +#endif // BC_ENABLE_EXTRA_MATH diff --git a/src/read.c b/src/read.c new file mode 100644 index 000000000000..c63952523181 --- /dev/null +++ b/src/read.c @@ -0,0 +1,226 @@ +/* + * ***************************************************************************** + * + * Copyright (c) 2018-2020 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 to handle special I/O for bc. + * + */ + +#include <assert.h> +#include <ctype.h> +#include <errno.h> +#include <stdlib.h> +#include <string.h> + +#include <signal.h> + +#include <fcntl.h> +#include <sys/stat.h> +#include <unistd.h> + +#include <read.h> +#include <history.h> +#include <program.h> +#include <vm.h> + +static bool bc_read_binary(const char *buf, size_t size) { + + size_t i; + + for (i = 0; i < size; ++i) { + if (BC_ERR(BC_READ_BIN_CHAR(buf[i]))) return true; + } + + return false; +} + +static bool bc_read_buf(BcVec *vec) { + + char *nl; + + if (!vm.buf_len) return false; + + nl = strchr(vm.buf, '\n'); + + if (nl != NULL) { + + size_t nllen = (size_t) ((nl + 1) - vm.buf); + + nllen = vm.buf_len >= nllen ? nllen : vm.buf_len; + + bc_vec_npush(vec, nllen, vm.buf); + vm.buf_len -= nllen; + memmove(vm.buf, nl + 1, vm.buf_len); + + return true; + } + + bc_vec_npush(vec, vm.buf_len, vm.buf); + vm.buf_len = 0; + + return false; +} + +BcStatus bc_read_chars(BcVec *vec, const char *prompt) { + + bool done = false; + + assert(vec != NULL && vec->size == sizeof(char)); + + BC_SIG_ASSERT_NOT_LOCKED; + + bc_vec_npop(vec, vec->len); + +#if BC_ENABLE_PROMPT + if (BC_USE_PROMPT) { + bc_file_puts(&vm.fout, prompt); + bc_file_flush(&vm.fout); + } +#endif // BC_ENABLE_PROMPT + + if (bc_read_buf(vec)) { + bc_vec_pushByte(vec, '\0'); + return BC_STATUS_SUCCESS; + } + + while (!done) { + + ssize_t r; + + BC_SIG_LOCK; + + r = read(STDIN_FILENO, vm.buf + vm.buf_len, + BC_VM_STDIN_BUF_SIZE - vm.buf_len); + + if (BC_UNLIKELY(r < 0)) { + + if (errno == EINTR) { + + if (vm.status == (sig_atomic_t) BC_STATUS_QUIT) { + BC_SIG_UNLOCK; + return BC_STATUS_QUIT; + } + + assert(vm.sig); + + vm.status = (sig_atomic_t) BC_STATUS_SUCCESS; +#if BC_ENABLE_PROMPT + if (BC_USE_PROMPT) bc_file_puts(&vm.fout, prompt); +#endif // BC_ENABLE_PROMPT + bc_file_flush(&vm.fout); + + BC_SIG_UNLOCK; + + continue; + } + + BC_SIG_UNLOCK; + + bc_vm_err(BC_ERROR_FATAL_IO_ERR); + } + + BC_SIG_UNLOCK; + + if (r == 0) { + bc_vec_pushByte(vec, '\0'); + return BC_STATUS_EOF; + } + + vm.buf_len += (size_t) r; + + done = bc_read_buf(vec); + } + + bc_vec_pushByte(vec, '\0'); + + return BC_STATUS_SUCCESS; +} + +BcStatus bc_read_line(BcVec *vec, const char *prompt) { + + BcStatus s; + +#if BC_ENABLE_HISTORY + if (BC_TTY && !vm.history.badTerm) + s = bc_history_line(&vm.history, vec, prompt); + else s = bc_read_chars(vec, prompt); +#else // BC_ENABLE_HISTORY + s = bc_read_chars(vec, prompt); +#endif // BC_ENABLE_HISTORY + + if (BC_ERR(bc_read_binary(vec->v, vec->len - 1))) + bc_vm_verr(BC_ERROR_FATAL_BIN_FILE, bc_program_stdin_name); + + return s; +} + +void bc_read_file(const char *path, char **buf) { + + BcError e = BC_ERROR_FATAL_IO_ERR; + size_t size, r; + struct stat pstat; + int fd; + + BC_SIG_ASSERT_LOCKED; + + assert(path != NULL); + + fd = open(path, O_RDONLY); + if (BC_ERR(fd < 0)) bc_vm_verr(BC_ERROR_FATAL_FILE_ERR, path); + if (BC_ERR(fstat(fd, &pstat) == -1)) goto malloc_err; + + if (BC_ERR(S_ISDIR(pstat.st_mode))) { + e = BC_ERROR_FATAL_PATH_DIR; + goto malloc_err; + } + + size = (size_t) pstat.st_size; + *buf = bc_vm_malloc(size + 1); + + r = (size_t) read(fd, *buf, size); + if (BC_ERR(r != size)) goto read_err; + + (*buf)[size] = '\0'; + + if (BC_ERR(bc_read_binary(*buf, size))) { + e = BC_ERROR_FATAL_BIN_FILE; + goto read_err; + } + + close(fd); + + return; + +read_err: + free(*buf); +malloc_err: + close(fd); + bc_vm_verr(e, path); +} diff --git a/src/vector.c b/src/vector.c new file mode 100644 index 000000000000..28c7440693eb --- /dev/null +++ b/src/vector.c @@ -0,0 +1,311 @@ +/* + * ***************************************************************************** + * + * Copyright (c) 2018-2020 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 to manipulate vectors (resizable arrays). + * + */ + +#include <assert.h> +#include <stdlib.h> +#include <string.h> + +#include <status.h> +#include <vector.h> +#include <lang.h> +#include <vm.h> + +static void bc_vec_grow(BcVec *restrict v, size_t n) { + + size_t len, cap = v->cap; + sig_atomic_t lock; + + len = bc_vm_growSize(v->len, n); + + while (cap < len) cap = bc_vm_growSize(cap, cap); + + BC_SIG_TRYLOCK(lock); + v->v = bc_vm_realloc(v->v, bc_vm_arraySize(cap, v->size)); + v->cap = cap; + BC_SIG_TRYUNLOCK(lock); +} + +void bc_vec_init(BcVec *restrict v, size_t esize, BcVecFree dtor) { + BC_SIG_ASSERT_LOCKED; + assert(v != NULL && esize); + v->size = esize; + v->cap = BC_VEC_START_CAP; + v->len = 0; + v->dtor = dtor; + v->v = bc_vm_malloc(bc_vm_arraySize(BC_VEC_START_CAP, esize)); +} + +void bc_vec_expand(BcVec *restrict v, size_t req) { + + assert(v != NULL); + + if (v->cap < req) { + + sig_atomic_t lock; + + BC_SIG_TRYLOCK(lock); + + v->v = bc_vm_realloc(v->v, bc_vm_arraySize(req, v->size)); + v->cap = req; + + BC_SIG_TRYUNLOCK(lock); + } +} + +void bc_vec_npop(BcVec *restrict v, size_t n) { + + assert(v != NULL && n <= v->len); + + if (v->dtor == NULL) v->len -= n; + else { + + sig_atomic_t lock; + size_t len = v->len - n; + + BC_SIG_TRYLOCK(lock); + + while (v->len > len) v->dtor(v->v + (v->size * --v->len)); + + BC_SIG_TRYUNLOCK(lock); + } +} + +void bc_vec_npopAt(BcVec *restrict v, size_t n, size_t idx) { + + char* ptr, *data; + + assert(v != NULL); + assert(idx + n < v->len); + + ptr = bc_vec_item(v, idx); + data = bc_vec_item(v, idx + n); + + if (v->dtor != NULL) { + + size_t i; + + BC_SIG_LOCK; + + for (i = 0; i < n; ++i) v->dtor(bc_vec_item(v, idx + i)); + + BC_SIG_UNLOCK; + } + + v->len -= n; + memmove(ptr, data, (v->len - idx) * v->size); +} + +void bc_vec_npush(BcVec *restrict v, size_t n, const void *data) { + assert(v != NULL && data != NULL); + if (v->len + n > v->cap) bc_vec_grow(v, n); + memcpy(v->v + (v->size * v->len), data, v->size * n); + v->len += n; +} + +inline void bc_vec_push(BcVec *restrict v, const void *data) { + bc_vec_npush(v, 1, data); +} + +void bc_vec_pushByte(BcVec *restrict v, uchar data) { + assert(v != NULL && v->size == sizeof(uchar)); + if (v->len == v->cap) bc_vec_grow(v, 1); + v->v[v->len] = (char) data; + v->len += 1; +} + +void bc_vec_pushIndex(BcVec *restrict v, size_t idx) { + + uchar amt, nums[sizeof(size_t)]; + + assert(v != NULL); + assert(v->size == sizeof(uchar)); + + for (amt = 0; idx; ++amt) { + nums[amt] = (uchar) idx; + idx &= ((size_t) ~(UCHAR_MAX)); + idx >>= sizeof(uchar) * CHAR_BIT; + } + + bc_vec_push(v, &amt); + bc_vec_npush(v, amt, nums); +} + +static void bc_vec_pushAt(BcVec *restrict v, const void *data, size_t idx) { + + assert(v != NULL && data != NULL && idx <= v->len); + + if (idx == v->len) bc_vec_push(v, data); + else { + + char *ptr; + + if (v->len == v->cap) bc_vec_grow(v, 1); + + ptr = v->v + v->size * idx; + + memmove(ptr + v->size, ptr, v->size * (v->len++ - idx)); + memmove(ptr, data, v->size); + } +} + +void bc_vec_string(BcVec *restrict v, size_t len, const char *restrict str) { + + assert(v != NULL && v->size == sizeof(char)); + assert(v->dtor == NULL); + assert(!v->len || !v->v[v->len - 1]); + assert(v->v != str); + + bc_vec_npop(v, v->len); + bc_vec_expand(v, bc_vm_growSize(len, 1)); + memcpy(v->v, str, len); + v->len = len; + + bc_vec_pushByte(v, '\0'); +} + +void bc_vec_concat(BcVec *restrict v, const char *restrict str) { + + assert(v != NULL && v->size == sizeof(char)); + assert(v->dtor == NULL); + assert(!v->len || !v->v[v->len - 1]); + assert(v->v != str); + + if (v->len) v->len -= 1; + + bc_vec_npush(v, strlen(str) + 1, str); +} + +void bc_vec_empty(BcVec *restrict v) { + assert(v != NULL && v->size == sizeof(char)); + assert(v->dtor == NULL); + bc_vec_npop(v, v->len); + bc_vec_pushByte(v, '\0'); +} + +#if BC_ENABLE_HISTORY +void bc_vec_replaceAt(BcVec *restrict v, size_t idx, const void *data) { + + char *ptr; + + BC_SIG_ASSERT_LOCKED; + + assert(v != NULL); + + ptr = bc_vec_item(v, idx); + + if (v->dtor != NULL) v->dtor(ptr); + + memcpy(ptr, data, v->size); +} +#endif // BC_ENABLE_HISTORY + +inline void* bc_vec_item(const BcVec *restrict v, size_t idx) { + assert(v != NULL && v->len && idx < v->len); + return v->v + v->size * idx; +} + +inline void* bc_vec_item_rev(const BcVec *restrict v, size_t idx) { + assert(v != NULL && v->len && idx < v->len); + return v->v + v->size * (v->len - idx - 1); +} + +inline void bc_vec_clear(BcVec *restrict v) { + v->v = NULL; + v->len = 0; + v->dtor = NULL; +} + +void bc_vec_free(void *vec) { + BcVec *v = (BcVec*) vec; + BC_SIG_ASSERT_LOCKED; + bc_vec_npop(v, v->len); + free(v->v); +} + +static size_t bc_map_find(const BcVec *restrict v, const char *name) { + + size_t low = 0, high = v->len; + + while (low < high) { + + size_t mid = (low + high) / 2; + const BcId *id = bc_vec_item(v, mid); + int result = strcmp(name, id->name); + + if (!result) return mid; + else if (result < 0) high = mid; + else low = mid + 1; + } + + return low; +} + +bool bc_map_insert(BcVec *restrict v, const char *name, + size_t idx, size_t *restrict i) +{ + BcId id; + + BC_SIG_ASSERT_LOCKED; + + assert(v != NULL && name != NULL && i != NULL); + + *i = bc_map_find(v, name); + + assert(*i <= v->len); + + if (*i != v->len && !strcmp(name, ((BcId*) bc_vec_item(v, *i))->name)) + return false; + + id.name = bc_vm_strdup(name); + id.idx = idx; + + bc_vec_pushAt(v, &id, *i); + + return true; +} + +size_t bc_map_index(const BcVec *restrict v, const char *name) { + + size_t i; + + assert(v != NULL && name != NULL); + + i = bc_map_find(v, name); + + if (i >= v->len) return BC_VEC_INVALID_IDX; + + return strcmp(name, ((BcId*) bc_vec_item(v, i))->name) ? + BC_VEC_INVALID_IDX : i; +} diff --git a/src/vm.c b/src/vm.c new file mode 100644 index 000000000000..d569cc70a008 --- /dev/null +++ b/src/vm.c @@ -0,0 +1,819 @@ +/* + * ***************************************************************************** + * + * Copyright (c) 2018-2020 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 <string.h> + +#include <signal.h> + +#include <setjmp.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 <vector.h> +#include <args.h> +#include <vm.h> +#include <read.h> +#include <bc.h> + +#if BC_DEBUG_CODE +BC_NORETURN void bc_vm_jmp(const char* f) { +#else // BC_DEBUG_CODE +BC_NORETURN void bc_vm_jmp(void) { +#endif + + assert(vm.status != BC_STATUS_SUCCESS || vm.sig); + + BC_SIG_MAYLOCK; + +#if BC_DEBUG_CODE + bc_file_puts(&vm.ferr, "Longjmp: "); + bc_file_puts(&vm.ferr, f); + bc_file_putchar(&vm.ferr, '\n'); + bc_file_flush(&vm.ferr); +#endif // BC_DEBUG_CODE + +#ifndef NDEBUG + assert(vm.jmp_bufs.len - (size_t) vm.sig_pop); +#endif // NDEBUG + + 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); +} + +static void bc_vm_sig(int sig) { + + // There is already a signal in flight. + if (vm.status == (sig_atomic_t) BC_STATUS_QUIT || vm.sig) { + if (!BC_TTY || sig != SIGINT) vm.status = BC_STATUS_QUIT; + return; + } + + if (BC_TTY && sig == SIGINT) { + + int err = errno; + + if (write(STDOUT_FILENO, vm.sigmsg, vm.siglen) != (ssize_t) vm.siglen) + vm.status = BC_STATUS_ERROR_FATAL; + else vm.sig = 1; + + errno = err; + } + else vm.status = BC_STATUS_QUIT; + + assert(vm.jmp_bufs.len); + + if (!vm.sig_lock) BC_VM_JMP; +} + +void bc_vm_info(const char* const help) { + + BC_SIG_ASSERT_LOCKED; + + bc_file_puts(&vm.fout, vm.name); + bc_file_putchar(&vm.fout, ' '); + bc_file_puts(&vm.fout, BC_VERSION); + bc_file_putchar(&vm.fout, '\n'); + bc_file_puts(&vm.fout, bc_copyright); + + if (help) { + bc_file_putchar(&vm.fout, '\n'); + bc_file_printf(&vm.fout, help, vm.name, vm.name); + } + + bc_file_flush(&vm.fout); +} + +void bc_vm_error(BcError e, size_t line, ...) { + + BcStatus s; + va_list args; + uchar id = bc_err_ids[e]; + const char* err_type = vm.err_ids[id]; + sig_atomic_t lock; + + assert(e < BC_ERROR_NELEMS); + assert(!vm.sig_pop); + +#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; + } +#endif // BC_ENABLED + + BC_SIG_TRYLOCK(lock); + + // Make sure all of stdout is written first. + s = bc_file_flushErr(&vm.fout); + + if (BC_ERR(s == BC_STATUS_ERROR_FATAL)) { + vm.status = (sig_atomic_t) s; + BC_VM_JMP; + } + + va_start(args, line); + bc_file_putchar(&vm.ferr, '\n'); + bc_file_puts(&vm.ferr, err_type); + bc_file_putchar(&vm.ferr, ' '); + bc_file_vprintf(&vm.ferr, 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) { + bc_file_puts(&vm.ferr, "\n "); + bc_file_puts(&vm.ferr, vm.file); + bc_file_printf(&vm.ferr, 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); + + bc_file_puts(&vm.ferr, "\n "); + bc_file_puts(&vm.ferr, vm.func_header); + bc_file_putchar(&vm.ferr, ' '); + bc_file_puts(&vm.ferr, f->name); + + if (BC_IS_BC && ip->func != BC_PROG_MAIN && + ip->func != BC_PROG_READ) + { + bc_file_puts(&vm.ferr, "()"); + } + } + } + + bc_file_puts(&vm.ferr, "\n\n"); + + s = bc_file_flushErr(&vm.ferr); + + vm.status = s == BC_STATUS_ERROR_FATAL ? + (sig_atomic_t) s : (sig_atomic_t) (uchar) (id + 1); + + if (BC_ERR(vm.status)) BC_VM_JMP; + + BC_SIG_TRYUNLOCK(lock); +} + +static void bc_vm_envArgs(const char* const env_args_name) { + + char *env_args = getenv(env_args_name), *buf, *start; + char instr = '\0'; + + BC_SIG_ASSERT_LOCKED; + + if (env_args == NULL) return; + + start = buf = vm.env_args_buffer = bc_vm_strdup(env_args); + + assert(buf != NULL); + + bc_vec_init(&vm.env_args, sizeof(char*), NULL); + bc_vec_push(&vm.env_args, &env_args_name); + + while (*buf) { + + if (!isspace(*buf)) { + + if (*buf == '"' || *buf == '\'') { + + instr = *buf; + buf += 1; + + if (*buf == instr) { + instr = '\0'; + buf += 1; + continue; + } + } + + bc_vec_push(&vm.env_args, &buf); + + while (*buf && ((!instr && !isspace(*buf)) || + (instr && *buf != instr))) + { + buf += 1; + } + + if (*buf) { + + if (instr) instr = '\0'; + + *buf = '\0'; + buf += 1; + start = buf; + } + else if (instr) bc_vm_error(BC_ERROR_FATAL_OPTION, 0, start); + } + else buf += 1; + } + + // Make sure to push a NULL pointer at the end. + buf = NULL; + bc_vec_push(&vm.env_args, &buf); + + bc_args((int) vm.env_args.len - 1, bc_vec_item(&vm.env_args, 0)); +} + +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 == NULL) 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) { + + BC_SIG_ASSERT_LOCKED; + +#if BC_ENABLE_NLS + if (vm.catalog != BC_VM_INVALID_CATALOG) catclose(vm.catalog); +#endif // BC_ENABLE_NLS + +#if BC_ENABLE_HISTORY + // This must always run to ensure that the terminal is back to normal. + if (BC_TTY) bc_history_free(&vm.history); +#endif // BC_ENABLE_HISTORY + +#ifndef NDEBUG + bc_vec_free(&vm.env_args); + free(vm.env_args_buffer); + bc_vec_free(&vm.files); + bc_vec_free(&vm.exprs); + + bc_program_free(&vm.prog); + bc_parse_free(&vm.prs); + + { + size_t i; + for (i = 0; i < vm.temps.len; ++i) + free(((BcNum*) bc_vec_item(&vm.temps, i))->num); + + bc_vec_free(&vm.temps); + } +#endif // NDEBUG + + bc_file_free(&vm.fout); + bc_file_free(&vm.ferr); +} + +size_t bc_vm_arraySize(size_t n, size_t size) { + size_t res = n * size; + if (BC_ERR(res >= SIZE_MAX || (n != 0 && res / n != size))) + bc_vm_err(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 >= SIZE_MAX || res < a || res < b)) + bc_vm_err(BC_ERROR_FATAL_ALLOC_ERR); + return res; +} + +void* bc_vm_malloc(size_t n) { + + void* ptr; + + BC_SIG_ASSERT_LOCKED; + + ptr = malloc(n); + + if (BC_ERR(ptr == NULL)) bc_vm_err(BC_ERROR_FATAL_ALLOC_ERR); + + return ptr; +} + +void* bc_vm_realloc(void *ptr, size_t n) { + + void* temp; + + BC_SIG_ASSERT_LOCKED; + + temp = realloc(ptr, n); + + if (BC_ERR(temp == NULL)) bc_vm_err(BC_ERROR_FATAL_ALLOC_ERR); + + return temp; +} + +char* bc_vm_strdup(const char *str) { + + char *s; + + BC_SIG_ASSERT_LOCKED; + + s = strdup(str); + + if (BC_ERR(!s)) bc_vm_err(BC_ERROR_FATAL_ALLOC_ERR); + + return s; +} + +void bc_vm_printf(const char *fmt, ...) { + + va_list args; + + BC_SIG_LOCK; + + va_start(args, fmt); + bc_file_vprintf(&vm.fout, fmt, args); + va_end(args); + + vm.nchars = 0; + + BC_SIG_UNLOCK; +} + +void bc_vm_putchar(int c) { + bc_file_putchar(&vm.fout, (uchar) c); + vm.nchars = (c == '\n' ? 0 : vm.nchars + 1); +} + +static void bc_vm_clean(void) { + + 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 = (vm.status && vm.status != BC_STATUS_QUIT); + + if (good) bc_program_reset(&vm.prog); + +#if BC_ENABLED + if (good && 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 + if (BC_IS_BC) { + bc_vec_npop(&f->labels, f->labels.len); + bc_vec_npop(&f->strs, f->strs.len); + } +#endif // BC_ENABLED + bc_vec_npop(&f->consts, f->consts.len); + bc_vec_npop(&f->code, f->code.len); + ip->idx = 0; + } +} + +static void bc_vm_process(const char *text, bool is_stdin) { + + bc_parse_text(&vm.prs, text); + + do { + +#if BC_ENABLED + if (vm.prs.l.t == BC_LEX_KW_DEFINE) vm.parse(&vm.prs); +#endif // BC_ENABLED + + while (BC_PARSE_CAN_PARSE(vm.prs)) vm.parse(&vm.prs); + +#if BC_ENABLED + if (BC_IS_BC) { + + uint16_t *flags = BC_PARSE_TOP_FLAG_PTR(&vm.prs); + + if (!is_stdin && vm.prs.flags.len == 1 && + *flags == BC_PARSE_FLAG_IF_END) + { + bc_parse_noElse(&vm.prs); + } + + if (BC_PARSE_NO_EXEC(&vm.prs)) return; + } +#endif // BC_ENABLED + + bc_program_exec(&vm.prog); + if (BC_I) bc_file_flush(&vm.fout); + + } while (vm.prs.l.t != BC_LEX_EOF); +} + +static void bc_vm_file(const char *file) { + + char *data = NULL; + + assert(!vm.sig_pop); + + bc_lex_file(&vm.prs.l, file); + + BC_SIG_LOCK; + + bc_read_file(file, &data); + + BC_SETJMP_LOCKED(err); + + BC_SIG_UNLOCK; + + bc_vm_process(data, false); + +#if BC_ENABLED + if (BC_IS_BC && BC_ERR(BC_PARSE_NO_EXEC(&vm.prs))) + bc_parse_err(&vm.prs, BC_ERROR_PARSE_BLOCK); +#endif // BC_ENABLED + +err: + BC_SIG_MAYLOCK; + + free(data); + 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; + + BC_LONGJMP_CONT; +} + +static void bc_vm_stdin(void) { + + BcStatus s; + BcVec buf, buffer; + size_t string = 0; + bool comment = false, hash = false; + + bc_lex_file(&vm.prs.l, bc_program_stdin_name); + + BC_SIG_LOCK; + bc_vec_init(&buffer, sizeof(uchar), NULL); + bc_vec_init(&buf, sizeof(uchar), NULL); + bc_vec_pushByte(&buffer, '\0'); + BC_SETJMP_LOCKED(err); + BC_SIG_UNLOCK; + +restart: + + // 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 = bc_read_line(&buf, ">>> ")) || + (vm.eof = (s == BC_STATUS_EOF))) && buf.len > 1) + { + char c2, *str = buf.v; + size_t i, len = buf.len - 1; + + for (i = 0; i < len; ++i) { + + bool notend = len > i + 1; + uchar c = (uchar) str[i]; + + hash = (!comment && !string && ((hash && c != '\n') || + (!hash && c == '#'))); + + if (!hash && !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 && !hash && !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; +#if BC_ENABLE_HISTORY + if (vm.history.stdin_has_data) continue; +#endif // BC_ENABLE_HISTORY + + bc_vm_process(buffer.v, true); + bc_vec_empty(&buffer); + + if (vm.eof) break; + } + + if (!BC_STATUS_IS_ERROR(s)) { + if (BC_ERR(comment)) + bc_parse_err(&vm.prs, BC_ERROR_PARSE_COMMENT); + else if (BC_ERR(string)) + bc_parse_err(&vm.prs, BC_ERROR_PARSE_STRING); +#if BC_ENABLED + else if (BC_IS_BC && BC_ERR(BC_PARSE_NO_EXEC(&vm.prs))) + bc_parse_err(&vm.prs, BC_ERROR_PARSE_BLOCK); +#endif // BC_ENABLED + } + +err: + BC_SIG_MAYLOCK; + + bc_vm_clean(); + + vm.status = vm.status == BC_STATUS_ERROR_FATAL || + vm.status == BC_STATUS_QUIT || !BC_I ? + vm.status : BC_STATUS_SUCCESS; + + if (!vm.status && !vm.eof) { + bc_vec_empty(&buffer); + BC_LONGJMP_STOP; + BC_SIG_UNLOCK; + goto restart; + } + + bc_vec_free(&buf); + bc_vec_free(&buffer); + + BC_LONGJMP_CONT; +} + +#if BC_ENABLED +static void bc_vm_load(const char *name, const char *text) { + + bc_lex_file(&vm.prs.l, name); + bc_parse_text(&vm.prs, text); + + while (vm.prs.l.t != BC_LEX_EOF) vm.parse(&vm.prs); +} +#endif // BC_ENABLED + +static void bc_vm_defaultMsgs(void) { + + 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(void) { + +#if BC_ENABLE_NLS + uchar id = 0; + int set = 1, msg = 1; + size_t i; + + if (vm.locale == NULL) { + vm.catalog = BC_VM_INVALID_CATALOG; + bc_vm_defaultMsgs(); + return; + } + + vm.catalog = catopen(BC_MAINEXEC, NL_CAT_LOCALE); + + if (vm.catalog == BC_VM_INVALID_CATALOG) { + bc_vm_defaultMsgs(); + return; + } + + vm.func_header = catgets(vm.catalog, set, msg, bc_err_func_header); + + 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]; + + 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 void bc_vm_exec(const char* env_exp_exit) { + + size_t i; + bool has_file = false; + +#if BC_ENABLED + if (BC_IS_BC && (vm.flags & BC_FLAG_L)) { + + bc_vm_load(bc_lib_name, bc_lib); + +#if BC_ENABLE_EXTRA_MATH + if (!BC_IS_POSIX) bc_vm_load(bc_lib2_name, bc_lib2); +#endif // BC_ENABLE_EXTRA_MATH + } +#endif // BC_ENABLED + + if (vm.exprs.len) { + bc_lex_file(&vm.prs.l, bc_program_exprs_name); + bc_vm_process(vm.exprs.v, false); + if (getenv(env_exp_exit) != NULL) return; + } + + for (i = 0; i < vm.files.len; ++i) { + char *path = *((char**) bc_vec_item(&vm.files, i)); + if (!strcmp(path, "")) continue; + has_file = true; + bc_vm_file(path); + } + + if (BC_IS_BC || !has_file) bc_vm_stdin(); +} + +void bc_vm_boot(int argc, char *argv[], const char *env_len, + const char* const env_args, const char* env_exp_exit) +{ + int ttyin, ttyout, ttyerr; + struct sigaction sa; + + BC_SIG_ASSERT_LOCKED; + + ttyin = isatty(STDIN_FILENO); + ttyout = isatty(STDOUT_FILENO); + ttyerr = isatty(STDERR_FILENO); + + vm.flags |= ttyin ? BC_FLAG_TTYIN : 0; + vm.flags |= (ttyin != 0 && ttyout != 0 && ttyerr != 0) ? BC_FLAG_TTY : 0; + vm.flags |= ttyin && ttyout ? BC_FLAG_I : 0; + + sigemptyset(&sa.sa_mask); + sa.sa_handler = bc_vm_sig; + sa.sa_flags = SA_NODEFER; + + sigaction(SIGTERM, &sa, NULL); + sigaction(SIGQUIT, &sa, NULL); + sigaction(SIGINT, &sa, NULL); + +#if BC_ENABLE_HISTORY + if (BC_TTY) sigaction(SIGHUP, &sa, NULL); +#endif // BC_ENABLE_HISTORY + + memcpy(vm.max_num, bc_num_bigdigMax, + bc_num_bigdigMax_size * sizeof(BcDig)); + bc_num_setup(&vm.max, vm.max_num, BC_NUM_BIGDIG_LOG10); + vm.max.len = bc_num_bigdigMax_size; + + vm.file = NULL; + + bc_vm_gettext(); + + 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); + vm.buf = output_bufs + BC_VM_STDOUT_BUF_SIZE + BC_VM_STDERR_BUF_SIZE; + + vm.line_len = (uint16_t) bc_vm_envLen(env_len); + + bc_vec_clear(&vm.files); + bc_vec_clear(&vm.exprs); + + bc_vec_init(&vm.temps, sizeof(BcNum), NULL); + + bc_program_init(&vm.prog); + bc_parse_init(&vm.prs, &vm.prog, BC_PROG_MAIN); + +#if BC_ENABLE_HISTORY + if (BC_TTY) 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); +#endif // BC_ENABLED + + bc_vm_envArgs(env_args); + bc_args(argc, argv); + + if (BC_IS_POSIX) vm.flags &= ~(BC_FLAG_G); + + 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; +#endif // BC_ENABLE_EXTRA_MATH + + if (BC_IS_BC && !BC_IS_POSIX) + vm.maxes[BC_PROG_GLOBALS_IBASE] = BC_NUM_MAX_IBASE; + + if (BC_IS_BC && BC_I && !(vm.flags & BC_FLAG_Q)) bc_vm_info(NULL); + + BC_SIG_UNLOCK; + + bc_vm_exec(env_exp_exit); +} |