summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorStefan Eßer <se@FreeBSD.org>2020-06-27 15:03:19 +0000
committerStefan Eßer <se@FreeBSD.org>2020-06-27 15:03:19 +0000
commit1f958cfad78842ab9a1193471589231e25596cb3 (patch)
tree4bbff8044605fcfff11c9d322bb6f53495e4faa7 /src
Diffstat (limited to 'src')
-rw-r--r--src/args.c215
-rw-r--r--src/bc/bc.c57
-rw-r--r--src/bc/lex.c410
-rw-r--r--src/bc/parse.c1526
-rw-r--r--src/data.c995
-rw-r--r--src/dc/dc.c57
-rw-r--r--src/dc/lex.c202
-rw-r--r--src/dc/parse.c236
-rw-r--r--src/file.c225
-rw-r--r--src/history/history.c1450
-rw-r--r--src/lang.c313
-rw-r--r--src/lex.c231
-rw-r--r--src/main.c93
-rw-r--r--src/num.c2837
-rw-r--r--src/opt.c250
-rw-r--r--src/parse.c222
-rw-r--r--src/program.c2300
-rw-r--r--src/rand/rand.c415
-rw-r--r--src/read.c226
-rw-r--r--src/vector.c311
-rw-r--r--src/vm.c819
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(&copy, 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(&copy, &copy, &copy, powrdx);
+ }
+
+ bc_num_copy(c, &copy);
+ resrdx = powrdx;
+
+ while (pow >>= 1) {
+
+ powrdx <<= 1;
+ bc_num_mul(&copy, &copy, &copy, powrdx);
+
+ if (pow & 1) {
+ resrdx += powrdx;
+ bc_num_mul(c, &copy, 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(&copy);
+ 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(&copy.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, &copy);
+
+ BC_UNSETJMP;
+
+ BC_SIG_UNLOCK;
+
+ return;
+
+exit:
+ BC_SIG_MAYLOCK;
+ bc_num_free(&copy.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(&param.n, BC_NUM_DEF_SIZE);
+ bc_vec_push(v, &param.n);
+ }
+ else {
+ assert(a->idx == BC_TYPE_ARRAY);
+ bc_array_init(&param.v, true);
+ bc_vec_push(v, &param.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);
+}