aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--include/args.h4
-rw-r--r--include/bc.h42
-rw-r--r--include/lex.h13
-rw-r--r--include/library.h2
-rw-r--r--include/rand.h14
-rw-r--r--include/vm.h16
-rw-r--r--manuals/development.md14
-rwxr-xr-xscripts/benchmark.sh7
-rw-r--r--src/args.c16
-rw-r--r--src/bc.c2
-rw-r--r--src/bc_lex.c5
-rw-r--r--src/bc_parse.c2
-rw-r--r--src/data.c148
-rw-r--r--src/dc.c2
-rw-r--r--src/dc_lex.c15
-rw-r--r--src/dc_parse.c69
-rw-r--r--src/file.c61
-rw-r--r--src/history.c12
-rw-r--r--src/lang.c41
-rw-r--r--src/lex.c54
-rw-r--r--src/library.c142
-rw-r--r--src/main.c9
-rw-r--r--src/num.c6
-rw-r--r--src/opt.c76
-rw-r--r--src/parse.c37
-rw-r--r--src/rand.c167
-rw-r--r--src/read.c34
-rw-r--r--src/vector.c81
-rw-r--r--src/vm.c7
-rwxr-xr-xtests/all.sh13
-rwxr-xr-xtests/bc/timeconst.sh22
-rwxr-xr-xtests/errors.sh16
-rwxr-xr-xtests/other.sh11
-rwxr-xr-xtests/read.sh13
-rwxr-xr-xtests/script.sh9
-rwxr-xr-xtests/scripts.sh2
-rwxr-xr-xtests/stdin.sh5
-rwxr-xr-xtests/test.sh9
38 files changed, 1080 insertions, 118 deletions
diff --git a/include/args.h b/include/args.h
index 6db7ff5e..a2f5b416 100644
--- a/include/args.h
+++ b/include/args.h
@@ -44,8 +44,8 @@
* Processes command-line arguments.
* @param argc How many arguments there are.
* @param argv The array of arguments.
- * @param exit_exprs True if bc/dc should exit there are expressions, false
- * otherwise.
+ * @param exit_exprs True if bc/dc should exit when there are expressions,
+ * false otherwise.
*/
void bc_args(int argc, char *argv[], bool exit_exprs);
diff --git a/include/bc.h b/include/bc.h
index fb70ad24..6ebb0aee 100644
--- a/include/bc.h
+++ b/include/bc.h
@@ -375,47 +375,51 @@ void bc_parse_expr(BcParse *p, uint8_t flags);
*/
void bc_parse_parse(BcParse *p);
-// References to the signal message and its length.
+/// References to the signal message and its length.
extern const char bc_sig_msg[];
extern const uchar bc_sig_msg_len;
-// An array of bits that are set if the corresponding lex token is valid in an
-// expression.
+/// A reference to an array of bits that are set if the corresponding lex token
+/// is valid in an expression.
extern const uint8_t bc_parse_exprs[];
-// An array of bc operators.
+/// A reference to an array of bc operators.
extern const uchar bc_parse_ops[];
// References to the various instances of BcParseNext's.
-// What tokens are valid as next tokens when parsing normal expressions. More
-// accurately. these are the tokens that are valid for *ending* the expression.
+/// A reference to what tokens are valid as next tokens when parsing normal
+/// expressions. More accurately. these are the tokens that are valid for
+/// *ending* the expression.
extern const BcParseNext bc_parse_next_expr;
-// What tokens are valid as next tokens when parsing function parameters (well,
-// actually arguments).
-extern const BcParseNext bc_parse_next_param;
+/// A reference to what tokens are valid as next tokens when parsing function
+/// parameters (well, actually arguments).
+extern const BcParseNext bc_parse_next_arg;
-// What tokens are valid as next tokens when parsing a print statement.
+/// A reference to what tokens are valid as next tokens when parsing a print
+/// statement.
extern const BcParseNext bc_parse_next_print;
-// What tokens are valid as next tokens when parsing things like loop headers
-// and builtin functions where the only thing expected is a right paren.
-//
-// The name is an artifact of history, and is related to @a BC_PARSE_REL (see
-// include/parse.h). It refers to how POSIX only allows some operators as part
-// of the conditional of for loops, while loops, and if statements.
+/// A reference to what tokens are valid as next tokens when parsing things like
+/// loop headers and builtin functions where the only thing expected is a right
+/// paren.
+///
+/// The name is an artifact of history, and is related to @a BC_PARSE_REL (see
+/// include/parse.h). It refers to how POSIX only allows some operators as part
+/// of the conditional of for loops, while loops, and if statements.
extern const BcParseNext bc_parse_next_rel;
// What tokens are valid as next tokens when parsing an array element
// expression.
extern const BcParseNext bc_parse_next_elem;
-// What tokens are valid as next tokens when parsing the first two parts of a
-// for loop header.
+/// A reference to what tokens are valid as next tokens when parsing the first
+/// two parts of a for loop header.
extern const BcParseNext bc_parse_next_for;
-// What tokens are valid as next tokens when parsing a read expression.
+/// A reference to what tokens are valid as next tokens when parsing a read
+/// expression.
extern const BcParseNext bc_parse_next_read;
#else // BC_ENABLED
diff --git a/include/lex.h b/include/lex.h
index 724303d6..7d947f23 100644
--- a/include/lex.h
+++ b/include/lex.h
@@ -71,11 +71,14 @@
#endif // BC_ENABLED
-/// Returns true if c is a valid number character.
-/// @param c The char to check.
-/// @param pt If a decimal point has already been seen.
-/// @param int_only True if the number is expected to be an int only, false if
-/// non-integers are allowed.
+/**
+ * Returns true if c is a valid number character.
+ * @param c The char to check.
+ * @param pt If a decimal point has already been seen.
+ * @param int_only True if the number is expected to be an int only, false if
+ * non-integers are allowed.
+ * @return True if @a c is a valid number character.
+ */
#define BC_LEX_NUM_CHAR(c, pt, int_only) \
(isdigit(c) != 0 || ((c) >= 'A' && (c) <= BC_LEX_LAST_NUM_CHAR) || \
((c) == '.' && !(pt) && !(int_only)))
diff --git a/include/library.h b/include/library.h
index 520578db..8a055eb8 100644
--- a/include/library.h
+++ b/include/library.h
@@ -171,8 +171,8 @@
/**
* A header to check the number in the context and return an error encoded as a
- * number if it is bad.
* @param c The context.
+ * number if it is bad.
* @param n The BclNumber.
*/
#define BC_CHECK_NUM(c, n) \
diff --git a/include/rand.h b/include/rand.h
index 62c4f0ed..a916d0de 100644
--- a/include/rand.h
+++ b/include/rand.h
@@ -52,6 +52,16 @@
#if BC_ENABLE_RAND
+#if BC_ENABLE_LIBRARY
+#define BC_RAND_USE_FREE (1)
+#else // BC_ENABLE_LIBRARY
+#ifndef NDEBUG
+#define BC_RAND_USE_FREE (1)
+#else // NDEBUG
+#define BC_RAND_USE_FREE (0)
+#endif // NDEBUG
+#endif // BC_ENABLE_LIBRARY
+
/**
* A function to return a random unsigned long.
* @param ptr A void ptr to some data that will help generate the random ulong.
@@ -436,7 +446,7 @@ typedef struct BcRNG {
*/
void bc_rand_init(BcRNG *r);
-#ifndef NDEBUG
+#if BC_RAND_USE_FREE
/**
* Frees a BcRNG. This is only in debug builds because it would only be freed on
@@ -445,7 +455,7 @@ void bc_rand_init(BcRNG *r);
*/
void bc_rand_free(BcRNG *r);
-#endif // NDEBUG
+#endif // BC_RAND_USE_FREE
/**
* Returns a random integer from the PRNG.
diff --git a/include/vm.h b/include/vm.h
index 9eb8813c..d5132fac 100644
--- a/include/vm.h
+++ b/include/vm.h
@@ -545,17 +545,11 @@ typedef struct BcVm {
/// A BcNum set to constant 0.
BcNum zero;
+#endif // !BC_ENABLE_LIBRARY
+
/// A BcNum set to constant 1.
BcNum one;
- // The BcDig array for the zero BcNum.
- BcDig zero_num[BC_VM_ONE_CAP];
-
- // The BcDig array for the one BcNum.
- BcDig one_num[BC_VM_ONE_CAP];
-
-#endif // !BC_ENABLE_LIBRARY
-
/// A BcNum holding the max number held by a BcBigDig plus 1.
BcNum max;
@@ -568,8 +562,14 @@ typedef struct BcVm {
/// The BcDig array for max2.
BcDig max2_num[BC_NUM_BIGDIG_LOG10];
+ // The BcDig array for the one BcNum.
+ BcDig one_num[BC_VM_ONE_CAP];
+
#if !BC_ENABLE_LIBRARY
+ // The BcDig array for the zero BcNum.
+ BcDig zero_num[BC_VM_ONE_CAP];
+
/// The stdout file.
BcFile fout;
diff --git a/manuals/development.md b/manuals/development.md
index 93b4c0a8..5e7abea9 100644
--- a/manuals/development.md
+++ b/manuals/development.md
@@ -1448,6 +1448,10 @@ TODO
TODO
+##### `read_errors.txt`
+
+TODO
+
##### `errors/`
TODO
@@ -1589,8 +1593,9 @@ done by the "standard tests" for each calculator.
These tests use the files in the [`tests/bc/`][161] and [`tests/dc/`][162]
directories (except for [`tests/bc/all.txt`][163], [`tests/bc/errors.txt`][164],
[`tests/bc/posix_errors.txt`][165], [`tests/bc/timeconst.sh`][166],
-[`tests/dc/all.txt`][167], and [`tests/dc/errors.txt`][168]), which are called
-the "standard test directories."
+[`tests/dc/all.txt`][167], [`tests/dc/errors.txt`][168], and
+[`tests/dc/read_errors.txt`][175]), which are called the "standard test
+directories."
For every test, there is the test file and the results file. The test files have
names of the form `<test>.txt`, where `<test>` is the name of the test, and the
@@ -1685,7 +1690,9 @@ TODO
TODO
* POSIX tests for `bc`.
-* Run files *and* through `stdin` with `cat`.
+* One test per line on files in standard test directory.
+* One test per file in errors directory.
+ * Run files *and* through `stdin` with `cat`.
* Adding tests.
### `stdin` Tests
@@ -3288,3 +3295,4 @@ TODO
[172]: #alltxt-4
[173]: #async-signal-safe-signal-handling
[174]: #vectorh
+[175]: #read_errorstxt
diff --git a/scripts/benchmark.sh b/scripts/benchmark.sh
index 43cc4a44..88073e10 100755
--- a/scripts/benchmark.sh
+++ b/scripts/benchmark.sh
@@ -82,6 +82,7 @@ shift
benchmarks=""
+# Create the list of benchmarks from the arguments.
while [ "$#" -gt 0 ]; do
if [ "$benchmarks" = "" ]; then
@@ -95,6 +96,7 @@ done
files=""
+# Create the list of files from the benchmarks.
for b in $benchmarks; do
f=$(printf "benchmarks/%s/%s.txt" "$d" "$b")
@@ -115,6 +117,7 @@ else
halt="q"
fi
+# Generate all of the benchmarks.
for b in $benchmarks; do
if [ ! -f "./benchmarks/$d/$b.txt" ]; then
@@ -124,15 +127,19 @@ for b in $benchmarks; do
fi
done
+# We use this format to make things easier to use with ministat.
format="%e %S %U %M %t %K %D %p %X %F %R %W %c %w %I %O %k"
printf 'Benchmarking %s...\n' "$files" >&2
i=0
+# Run the benchmarks as many times as told to.
while [ "$i" -lt "$runs" ]; do
printf '%s\n' "$halt" | /usr/bin/time -f "$format" bin/$d $opts $files 2>&1 > /dev/null
+ # Might as well use the existing bc.
i=$(printf '%s + 1\n' "$i" | bin/bc)
+
done
diff --git a/src/args.c b/src/args.c
index b3fb7494..f07c5be6 100644
--- a/src/args.c
+++ b/src/args.c
@@ -80,12 +80,6 @@ static void bc_args_file(const char *file) {
free(buf);
}
-/**
- * Processes command-line arguments.
- * @param argc The number of arguments.
- * @param argv The arguments.
- * @param exit_exprs Whether to exit if expressions are encountered.
- */
void bc_args(int argc, char *argv[], bool exit_exprs) {
int c;
@@ -105,22 +99,32 @@ void bc_args(int argc, char *argv[], bool exit_exprs) {
case 'e':
{
+ // Barf if not allowed.
if (vm.no_exprs)
bc_verr(BC_ERR_FATAL_OPTION, "-e (--expression)");
+
+ // Add the expressions and set exit.
bc_args_exprs(opts.optarg);
vm.exit_exprs = (exit_exprs || vm.exit_exprs);
+
break;
}
case 'f':
{
+ // Figure out if exiting on expressions is disabled.
if (!strcmp(opts.optarg, "-")) vm.no_exprs = true;
else {
+
+ // Barf if not allowed.
if (vm.no_exprs)
bc_verr(BC_ERR_FATAL_OPTION, "-f (--file)");
+
+ // Add the expressions and set exit.
bc_args_file(opts.optarg);
vm.exit_exprs = (exit_exprs || vm.exit_exprs);
}
+
break;
}
diff --git a/src/bc.c b/src/bc.c
index 2fa8cc88..4f35cc42 100644
--- a/src/bc.c
+++ b/src/bc.c
@@ -47,6 +47,8 @@
*/
void bc_main(int argc, char *argv[]) {
+ // All of these just set bc-specific items in BcVm.
+
vm.read_ret = BC_INST_RET;
vm.help = bc_help;
vm.sigmsg = bc_sig_msg;
diff --git a/src/bc_lex.c b/src/bc_lex.c
index 3a9df29f..1fc13926 100644
--- a/src/bc_lex.c
+++ b/src/bc_lex.c
@@ -84,7 +84,8 @@ static void bc_lex_identifier(BcLex *l) {
}
/**
- * Parses a bc string.
+ * Parses a bc string. This is separate from dc strings because dc strings need
+ * to be balanced.
* @param l The lexer.
*/
static void bc_lex_string(BcLex *l) {
@@ -114,11 +115,13 @@ static void bc_lex_string(BcLex *l) {
} while (got_more && c != '"');
+ // If the string did not end properly, barf.
if (c != '"') {
l->i = i;
bc_lex_err(l, BC_ERR_PARSE_STRING);
}
+ // Set the temp string to the parsed string.
len = i - l->i;
bc_vec_string(&l->str, len, l->buf + l->i);
diff --git a/src/bc_parse.c b/src/bc_parse.c
index 04e57090..cc4b3dfa 100644
--- a/src/bc_parse.c
+++ b/src/bc_parse.c
@@ -291,7 +291,7 @@ static void bc_parse_args(BcParse *p, uint8_t flags) {
// Count the arguments and parse them.
for (nargs = 0; p->l.t != BC_LEX_RPAREN; ++nargs) {
- bc_parse_expr_status(p, flags, bc_parse_next_param);
+ bc_parse_expr_status(p, flags, bc_parse_next_arg);
comma = (p->l.t == BC_LEX_COMMA);
if (comma) bc_lex_next(&p->l);
diff --git a/src/data.c b/src/data.c
index 4582998f..9301c4ec 100644
--- a/src/data.c
+++ b/src/data.c
@@ -49,14 +49,22 @@
#if !BC_ENABLE_LIBRARY
#if BC_ENABLED
+
+/// The bc signal message and its length.
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
+
+/// The dc signal message and its length.
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
+/// The copyright banner.
const char bc_copyright[] =
"Copyright (c) 2018-2021 Gavin D. Howard and contributors\n"
"Report bugs at: https://git.yzena.com/gavin/bc\n\n"
@@ -67,38 +75,64 @@ const char bc_copyright[] =
#if BC_ENABLE_EXTRA_MATH
#if BC_ENABLE_HISTORY
+
+/// The pledges for starting bc.
const char bc_pledge_start[] = "rpath stdio tty unveil";
+
+/// The final pledges with history enabled.
const char bc_pledge_end_history[] = "rpath stdio tty";
+
#else // BC_ENABLE_HISTORY
+
+/// The pledges for starting bc.
const char bc_pledge_start[] = "rpath stdio unveil";
+
#endif // BC_ENABLE_HISTORY
+/// The final pledges with history history disabled.
const char bc_pledge_end[] = "rpath stdio";
#else // BC_ENABLE_EXTRA_MATH
#if BC_ENABLE_HISTORY
+
+/// The pledges for starting bc.
const char bc_pledge_start[] = "rpath stdio tty";
+
+/// The final pledges with history enabled.
const char bc_pledge_end_history[] = "stdio tty";
+
#else // BC_ENABLE_HISTORY
+
+/// The pledges for starting bc.
const char bc_pledge_start[] = "rpath stdio";
+
#endif // BC_ENABLE_HISTORY
+/// The final pledges with history history disabled.
const char bc_pledge_end[] = "stdio";
#endif // BC_ENABLE_EXTRA_MATH
#else // __OpenBSD__
+/// The pledges for starting bc.
const char bc_pledge_start[] = "";
+
#if BC_ENABLE_HISTORY
+
+/// The final pledges with history enabled.
const char bc_pledge_end_history[] = "";
+
#endif // BC_ENABLE_HISTORY
+
+/// The final pledges with history history disabled.
const char bc_pledge_end[] = "";
#endif // __OpenBSD__
-/// The list of long options.
+/// The list of long options. There is a zero set at the end for detecting the
+/// end.
const BcOptLong bc_args_lopt[] = {
{ "expression", BC_OPT_REQUIRED, 'e' },
@@ -123,9 +157,13 @@ const BcOptLong bc_args_lopt[] = {
};
+/// The function header for error messages.
const char* const bc_err_func_header = "Function:";
+
+/// The line format string for error messages.
const char* const bc_err_line = ":%zu";
+/// The default error category strings.
const char *bc_errs[] = {
"Math error:",
"Parse error:",
@@ -136,6 +174,7 @@ const char *bc_errs[] = {
#endif // BC_ENABLED
};
+/// The error category for each error.
const uchar bc_err_ids[] = {
BC_ERR_IDX_MATH, BC_ERR_IDX_MATH, BC_ERR_IDX_MATH, BC_ERR_IDX_MATH,
@@ -162,6 +201,8 @@ const uchar bc_err_ids[] = {
};
+/// The default error messages. There are NULL pointers because the positions
+/// must be preserved for the locales.
const char* const bc_err_msgs[] = {
"negative number",
@@ -238,6 +279,7 @@ const char* const bc_err_msgs[] = {
#endif // !BC_ENABLE_LIBRARY
+/// The destructors corresponding to BcDtorType enum items.
const BcVecFree bc_vec_dtors[] = {
NULL,
bc_vec_free,
@@ -260,18 +302,28 @@ const BcVecFree bc_vec_dtors[] = {
#if !BC_ENABLE_LIBRARY
#if BC_ENABLE_HISTORY
+
+/// A flush type for not clearing current extras but not saving new ones either.
const BcFlushType bc_flush_none = BC_FLUSH_NO_EXTRAS_NO_CLEAR;
+
+/// A flush type for clearing extras and not saving new ones.
const BcFlushType bc_flush_err = BC_FLUSH_NO_EXTRAS_CLEAR;
+
+/// A flush type for clearing previous extras and saving new ones.
const BcFlushType bc_flush_save = BC_FLUSH_SAVE_EXTRAS_CLEAR;
#endif // BC_ENABLE_HISTORY
#if BC_ENABLE_HISTORY
+
+/// A list of known bad terminals.
const char *bc_history_bad_terms[] = { "dumb", "cons25", "emacs", NULL };
+/// A constant for tabs and its length. My tab handling is dumb and always
+/// outputs the entire thing.
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.
+/// A list of wide chars. These are listed in ascending order for efficiency.
const uint32_t bc_history_wchars[][2] = {
{ 0x1100, 0x115F },
{ 0x231A, 0x231B },
@@ -379,10 +431,12 @@ const uint32_t bc_history_wchars[][2] = {
{ 0x30000, 0x3FFFD },
};
+/// The length of the wide chars list.
const size_t bc_history_wchars_len =
sizeof(bc_history_wchars) / sizeof(bc_history_wchars[0]);
-// These are listed in ascending order for efficiency.
+/// A list of combining characters in Unicode. 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,
@@ -598,19 +652,20 @@ const uint32_t bc_history_combo_chars[] = {
0xE01EC,0xE01ED,0xE01EE,0xE01EF,
};
+/// The length of the combining characters list.
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
+/// The human-readable name of the main function in bc source code.
const char bc_func_main[] = "(main)";
+
+/// The human-readable name of the read function in bc source code.
const char bc_func_read[] = "(read)";
#if BC_DEBUG_CODE
+
+/// A list of names of instructions for easy debugging output.
const char* bc_inst_names[] = {
#if BC_ENABLED
@@ -758,12 +813,18 @@ const char* bc_inst_names[] = {
"BC_INST_NQUIT",
#endif // DC_ENABLED
};
+
#endif // BC_DEBUG_CODE
+/// A constant string for 0.
const char bc_parse_zero[2] = "0";
+
+/// A constant string for 1.
const char bc_parse_one[2] = "1";
#if BC_ENABLED
+
+/// A list of keywords for bc.
const BcLexKeyword bc_lex_kws[] = {
BC_LEX_KW_ENTRY("auto", 4, true),
BC_LEX_KW_ENTRY("break", 5, true),
@@ -803,10 +864,11 @@ const BcLexKeyword bc_lex_kws[] = {
BC_LEX_KW_ENTRY("else", 4, false),
};
+/// The length of the list of bc keywords.
const size_t bc_lex_kws_len = sizeof(bc_lex_kws) / sizeof(BcLexKeyword);
-// This is an array that corresponds to token types. An entry is
-// true if the token is valid in an expression, false otherwise.
+/// An array of booleans that correspond 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),
@@ -835,7 +897,7 @@ const uint8_t bc_parse_exprs[] = {
#endif // BC_ENABLE_EXTRA_MATH
};
-// This is an array of data for operators that correspond to token types.
+/// 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),
@@ -861,20 +923,42 @@ const uchar bc_parse_ops[] = {
};
// These identify what tokens can come after expressions in certain cases.
+
+/// The valid next tokens for normal expressions.
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 =
+
+/// The valid next tokens for function argument expressions.
+const BcParseNext bc_parse_next_arg =
BC_PARSE_NEXT(2, BC_LEX_RPAREN, BC_LEX_COMMA);
+
+/// The valid next tokens for expressions in print statements.
const BcParseNext bc_parse_next_print =
BC_PARSE_NEXT(4, BC_LEX_COMMA, BC_LEX_NLINE, BC_LEX_SCOLON, BC_LEX_EOF);
+
+/// The valid next tokens for if statement conditions or loop conditions. This
+/// is used in for loops for the update expression and for builtin function.
+///
+/// The name is an artifact of history, and is related to @a BC_PARSE_REL (see
+/// include/parse.h). It refers to how POSIX only allows some operators as part
+/// of the conditional of for loops, while loops, and if statements.
const BcParseNext bc_parse_next_rel = BC_PARSE_NEXT(1, BC_LEX_RPAREN);
+
+/// The valid next tokens for array element expressions.
const BcParseNext bc_parse_next_elem = BC_PARSE_NEXT(1, BC_LEX_RBRACKET);
+
+/// The valid next tokens for for loop initialization expressions and condition
+/// expressions.
const BcParseNext bc_parse_next_for = BC_PARSE_NEXT(1, BC_LEX_SCOLON);
+
+/// The valid next tokens for read expressions.
const BcParseNext bc_parse_next_read =
BC_PARSE_NEXT(2, BC_LEX_NLINE, BC_LEX_EOF);
#endif // BC_ENABLED
#if DC_ENABLED
+
+/// A list of instructions that need register arguments in dc.
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,
@@ -882,8 +966,13 @@ const uint8_t dc_lex_regs[] = {
BC_LEX_STORE_PUSH, BC_LEX_REG_STACK_LEVEL,
};
+/// The length of the list of register instructions.
const size_t dc_lex_regs_len = sizeof(dc_lex_regs) / sizeof(uint8_t);
+/// A list corresponding to characters starting at double quote ("). If an entry
+/// is BC_LEX_INVALID, then that character needs extra lexing in dc. If it does
+/// not, the character can trivially be replaced by the entry. Positions are
+/// kept because it corresponds to the ASCII table.
const uchar dc_lex_tokens[] = {
#if BC_ENABLE_EXTRA_MATH && BC_ENABLE_RAND
BC_LEX_KW_IRAND,
@@ -961,6 +1050,9 @@ const uchar dc_lex_tokens[] = {
BC_LEX_INVALID
};
+/// A list of instructions that correspond to lex tokens. If an entry is
+/// BC_INST_INVALID, that lex token needs extra parsing in the dc parser.
+/// Otherwise, the token can trivially be replaced by the entry.
const uchar dc_parse_insts[] = {
BC_INST_INVALID, BC_INST_INVALID,
#if BC_ENABLED
@@ -1032,16 +1124,21 @@ const uchar dc_parse_insts[] = {
#if BC_ENABLE_EXTRA_MATH && BC_ENABLE_RAND
+/// A constant for the rand multiplier.
const BcRandState bc_rand_multiplier = BC_RAND_MULTIPLIER;
#endif // BC_ENABLE_EXTRA_MATH && BC_ENABLE_RAND
#if BC_LONG_BIT >= 64
+
+/// A constant array for the max of a bigdig number as a BcDig array.
const BcDig bc_num_bigdigMax[] = {
709551616U,
446744073U,
18U,
};
+
+/// A constant array for the max of 2 times a bigdig number as a BcDig array.
const BcDig bc_num_bigdigMax2[] = {
768211456U,
374607431U,
@@ -1049,12 +1146,17 @@ const BcDig bc_num_bigdigMax2[] = {
282366920U,
340U,
};
+
#else // BC_LONG_BIT >= 64
+
+/// A constant array for the max of a bigdig number as a BcDig array.
const BcDig bc_num_bigdigMax[] = {
7296U,
9496U,
42U,
};
+
+/// A constant array for the max of 2 times a bigdig number as a BcDig array.
const BcDig bc_num_bigdigMax2[] = {
1616U,
955U,
@@ -1062,13 +1164,19 @@ const BcDig bc_num_bigdigMax2[] = {
6744U,
1844U,
};
+
#endif // BC_LONG_BIT >= 64
+/// The size of the bigdig max array.
const size_t bc_num_bigdigMax_size = sizeof(bc_num_bigdigMax) / sizeof(BcDig);
+
+/// The size of the bigdig max times 2 array.
const size_t bc_num_bigdigMax2_size = sizeof(bc_num_bigdigMax2) / sizeof(BcDig);
+/// A string of digits for easy conversion from characters to digits.
const char bc_num_hex_digits[] = "0123456789ABCDEF";
+/// An array for easy conversion from exponent to power of 10.
const BcBigDig bc_num_pow10[BC_BASE_DIGS + 1] = {
1,
10,
@@ -1086,6 +1194,8 @@ const BcBigDig bc_num_pow10[BC_BASE_DIGS + 1] = {
#if !BC_ENABLE_LIBRARY
+/// An array of functions for binary operators corresponding to the order of
+/// the instructions for the operators.
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
@@ -1093,6 +1203,8 @@ const BcNumBinaryOp bc_program_ops[] = {
#endif // BC_ENABLE_EXTRA_MATH
};
+/// An array of functions for binary operators allocation requests corresponding
+/// to the order of the instructions for the operators.
const BcNumBinaryOpReq bc_program_opReqs[] = {
bc_num_powReq, bc_num_mulReq, bc_num_divReq, bc_num_divReq,
bc_num_addReq, bc_num_addReq,
@@ -1101,6 +1213,8 @@ const BcNumBinaryOpReq bc_program_opReqs[] = {
#endif // BC_ENABLE_EXTRA_MATH
};
+/// An array of unary operator functions corresponding to the order of the
+/// instructions.
const BcProgramUnary bc_program_unarys[] = {
bc_program_negate, bc_program_not,
#if BC_ENABLE_EXTRA_MATH
@@ -1108,12 +1222,22 @@ const BcProgramUnary bc_program_unarys[] = {
#endif // BC_ENABLE_EXTRA_MATH
};
+/// A filename for when parsing expressions.
const char bc_program_exprs_name[] = "<exprs>";
+/// A filename for when parsing stdin..
const char bc_program_stdin_name[] = "<stdin>";
+
+/// A ready message for SIGINT catching.
const char bc_program_ready_msg[] = "ready for more input\n";
+
+/// The length of the ready message.
const size_t bc_program_ready_msg_len = sizeof(bc_program_ready_msg) - 1;
+
+/// A list of escape characters that a print statement should treat specially.
const char bc_program_esc_chars[] = "ab\\efnqrt";
+
+/// A list of characters corresponding to the escape characters above.
const char bc_program_esc_seqs[] = "\a\b\\\\\f\n\"\r\t";
#endif // !BC_ENABLE_LIBRARY
diff --git a/src/dc.c b/src/dc.c
index 9f479cc3..67bc3e16 100644
--- a/src/dc.c
+++ b/src/dc.c
@@ -47,6 +47,8 @@
*/
void dc_main(int argc, char *argv[]) {
+ // All of these just set dc-specific items in BcVm.
+
vm.read_ret = BC_INST_POP_EXEC;
vm.help = dc_help;
vm.sigmsg = dc_sig_msg;
diff --git a/src/dc_lex.c b/src/dc_lex.c
index 3f9c5270..d0e93c28 100644
--- a/src/dc_lex.c
+++ b/src/dc_lex.c
@@ -85,9 +85,9 @@ static void dc_lex_register(BcLex *l) {
}
/**
- * Parse a dc string. Since dc's strings need to check for balanced brackets, we
- * can't just parse bc and dc strings with different start and end characters.
- * Oh, and dc strings need to check for escaped brackets.
+ * Parses a dc string. Since dc's strings need to check for balanced brackets,
+ * we can't just parse bc and dc strings with different start and end
+ * characters. Oh, and dc strings need to check for escaped brackets.
* @param l The lexer.
*/
static void dc_lex_string(BcLex *l) {
@@ -149,7 +149,7 @@ static void dc_lex_string(BcLex *l) {
}
/**
- * Lex a dc token. This is the dc implementation of BcLexNext.
+ * Lexes a dc token. This is the dc implementation of BcLexNext.
* @param l The lexer.
*/
void dc_lex_token(BcLex *l) {
@@ -160,6 +160,8 @@ void dc_lex_token(BcLex *l) {
// If the last token was a command that needs a register, we need to parse a
// register, so do so.
for (i = 0; i < dc_lex_regs_len; ++i) {
+
+ // If the token is a register token, take care of it and return.
if (l->last == dc_lex_regs[i]) {
dc_lex_register(l);
return;
@@ -202,6 +204,7 @@ void dc_lex_token(BcLex *l) {
else bc_lex_invalidChar(l, c);
l->i += 1;
+
break;
}
@@ -214,9 +217,13 @@ void dc_lex_token(BcLex *l) {
case '.':
{
c2 = l->buf[l->i];
+
+ // If the character after is a number, this dot is part of a number.
+ // Otherwise, it's the BSD dot (equivalent to last).
if (BC_NO_ERR(BC_LEX_NUM_CHAR(c2, true, false)))
bc_lex_number(l, c);
else bc_lex_invalidChar(l, c);
+
break;
}
diff --git a/src/dc_parse.c b/src/dc_parse.c
index b8cbe35c..279ddf93 100644
--- a/src/dc_parse.c
+++ b/src/dc_parse.c
@@ -44,6 +44,12 @@
#include <program.h>
#include <vm.h>
+/**
+ * Parses a register. The lexer should have already lexed the true name of the
+ * register, per extended registers and such.
+ * @param p The parser.
+ * @param var True if the parser is for a variable, false otherwise.
+ */
static void dc_parse_register(BcParse *p, bool var) {
bc_lex_next(&p->l);
@@ -52,17 +58,33 @@ static void dc_parse_register(BcParse *p, bool var) {
bc_parse_pushName(p, p->l.str.v, var);
}
+/**
+ * Parses a dc string.
+ * @param p The parser.
+ */
static inline void dc_parse_string(BcParse *p) {
bc_parse_addString(p);
bc_lex_next(&p->l);
}
+/**
+ * Parses a token that requires a memory operation, like load or store.
+ * @param p The parser.
+ * @param inst The instruction to push for the memory operation.
+ * @param name Whether the load or store is to a variable or array, and not to
+ * a global.
+ * @param store True if the operation is a store, false otherwise.
+ */
static void dc_parse_mem(BcParse *p, uchar inst, bool name, bool store) {
+ // Push the instruction.
bc_parse_push(p, inst);
+ // Parse the register if necessary.
if (name) dc_parse_register(p, inst != BC_INST_ARRAY_ELEM);
+ // Stores use the bc assign infrastructure, but they need to do a swap
+ // first.
if (store) {
bc_parse_push(p, BC_INST_SWAP);
bc_parse_push(p, BC_INST_ASSIGN_NO_VAL);
@@ -71,22 +93,37 @@ static void dc_parse_mem(BcParse *p, uchar inst, bool name, bool store) {
bc_lex_next(&p->l);
}
+/**
+ * Parses a conditional execution instruction.
+ * @param p The parser.
+ * @param inst The instruction for the condition.
+ */
static void dc_parse_cond(BcParse *p, uchar inst) {
+ // Push the instruction for the condition and the conditional execution.
bc_parse_push(p, inst);
bc_parse_push(p, BC_INST_EXEC_COND);
+ // Parse the register.
dc_parse_register(p, true);
bc_lex_next(&p->l);
+ // If the next token is an else, parse the else.
if (p->l.t == BC_LEX_KW_ELSE) {
dc_parse_register(p, true);
bc_lex_next(&p->l);
}
+ // Otherwise, push a marker for no else.
else bc_parse_pushIndex(p, SIZE_MAX);
}
+/**
+ * Parses a token for dc.
+ * @param p The parser.
+ * @param t The token to parse.
+ * @param flags The flags that say what is allowed or not.
+ */
static void dc_parse_token(BcParse *p, BcLexType t, uint8_t flags) {
uchar inst;
@@ -121,6 +158,9 @@ static void dc_parse_token(BcParse *p, BcLexType t, uint8_t flags) {
case BC_LEX_NEG:
{
+ // This tells us whether or not the neg is for a command or at the
+ // beginning of a number. If it's a command, push it. Otherwise,
+ // fallthrough and parse the number.
if (dc_lex_negCommand(&p->l)) {
bc_parse_push(p, BC_INST_NEG);
get_token = true;
@@ -136,6 +176,7 @@ static void dc_parse_token(BcParse *p, BcLexType t, uint8_t flags) {
{
bc_parse_number(p);
+ // Push the negative instruction if we fell through from above.
if (t == BC_LEX_NEG) bc_parse_push(p, BC_INST_NEG);
get_token = true;
@@ -144,10 +185,13 @@ static void dc_parse_token(BcParse *p, BcLexType t, uint8_t flags) {
case BC_LEX_KW_READ:
{
+ // Make sure the read is not recursive.
if (BC_ERR(flags & BC_PARSE_NOREAD))
bc_parse_err(p, BC_ERR_EXEC_REC_READ);
else bc_parse_push(p, BC_INST_READ);
+
get_token = true;
+
break;
}
@@ -188,6 +232,8 @@ static void dc_parse_token(BcParse *p, BcLexType t, uint8_t flags) {
default:
{
+ // All other tokens should be taken care of by the caller, or they
+ // actually *are* invalid.
bc_parse_err(p, BC_ERR_PARSE_TOKEN);
}
}
@@ -199,17 +245,29 @@ void dc_parse_expr(BcParse *p, uint8_t flags) {
BcInst inst;
BcLexType t;
- bool have_expr = false, need_expr = (flags & BC_PARSE_NOREAD) != 0;
+ bool need_expr, have_expr = false;
+
+ need_expr = ((flags & BC_PARSE_NOREAD) != 0);
+
+ // dc can just keep parsing forever basically, unlike bc, which has to have
+ // a whole bunch of complicated nonsense because its language was horribly
+ // designed.
+ // While we don't have EOF...
while ((t = p->l.t) != BC_LEX_EOF) {
+ // Eat newline.
if (t == BC_LEX_NLINE) {
bc_lex_next(&p->l);
continue;
}
+ // Get the instruction that corresponds to the token.
inst = dc_parse_insts[t];
+ // If the instruction is invalid, that means we have to do some harder
+ // parsing. So if not invalid, just push the instruction; otherwise,
+ // parse the token.
if (inst != BC_INST_INVALID) {
bc_parse_push(p, inst);
bc_lex_next(&p->l);
@@ -219,6 +277,9 @@ void dc_parse_expr(BcParse *p, uint8_t flags) {
have_expr = true;
}
+ // If we don't have an expression and need one, barf. Otherwise, just push a
+ // BC_INST_POP_EXEC if we have EOF and BC_PARSE_NOCALL, which dc uses to
+ // indicate that it is executing a string.
if (BC_ERR(need_expr && !have_expr)) bc_err(BC_ERR_EXEC_READ_EXPR);
else if (p->l.t == BC_LEX_EOF && (flags & BC_PARSE_NOCALL))
bc_parse_push(p, BC_INST_POP_EXEC);
@@ -230,12 +291,18 @@ void dc_parse_parse(BcParse *p) {
BC_SETJMP(exit);
+ // If we have EOF, someone called this function one too many times.
+ // Otherwise, parse.
if (BC_ERR(p->l.t == BC_LEX_EOF)) bc_parse_err(p, BC_ERR_PARSE_EOF);
else dc_parse_expr(p, 0);
exit:
+
BC_SIG_MAYLOCK;
+
+ // Need to reset if there was an error.
if (BC_SIG_EXC) bc_parse_reset(p);
+
BC_LONGJMP_CONT;
}
#endif // DC_ENABLED
diff --git a/src/file.c b/src/file.c
index 3de4ac0f..35a4647d 100644
--- a/src/file.c
+++ b/src/file.c
@@ -44,25 +44,42 @@
#include <file.h>
#include <vm.h>
+/**
+ * Translates an integer into a string.
+ * @param val The value to translate.
+ * @param buf The return parameter.
+ */
static void bc_file_ultoa(unsigned long long val, char buf[BC_FILE_ULL_LENGTH])
{
char buf2[BC_FILE_ULL_LENGTH];
size_t i, len;
+ // We need to make sure the entire thing is zeroed.
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;
+ // Since buf2 is reversed, reverse it into buf.
for (i = 0; i < len; ++i) buf[i] = buf2[len - i - 1];
}
+/**
+ * Output to the file directly.
+ * @param fd The file descriptor.
+ * @param buf The buffer of data to output.
+ * @param n The number of bytes to output.
+ * @return A status indicating error or success. We could have a fatal I/O
+ * error or EOF.
+ */
static BcStatus bc_file_output(int fd, const char *buf, size_t n) {
size_t bytes = 0;
@@ -70,10 +87,13 @@ static BcStatus bc_file_output(int fd, const char *buf, size_t n) {
BC_SIG_TRYLOCK(lock);
+ // While the number of bytes written is less than intended...
while (bytes < n) {
+ // Write.
ssize_t written = write(fd, buf + bytes, n - bytes);
+ // Check for error and return, if any.
if (BC_ERR(written == -1))
return errno == EPIPE ? BC_STATUS_EOF : BC_STATUS_ERROR_FATAL;
@@ -89,28 +109,38 @@ BcStatus bc_file_flushErr(BcFile *restrict f, BcFlushType type)
{
BcStatus s;
+ // If there is stuff to output...
if (f->len) {
#if BC_ENABLE_HISTORY
+
+ // If history is enabled...
if (BC_TTY) {
+
+ // If we have been told to save the extras, and there *are*
+ // extras...
if (f->buf[f->len - 1] != '\n' &&
(type == BC_FLUSH_SAVE_EXTRAS_CLEAR ||
type == BC_FLUSH_SAVE_EXTRAS_NO_CLEAR))
{
size_t i;
+ // Look for the last newline.
for (i = f->len - 2; i < f->len && f->buf[i] != '\n'; --i);
i += 1;
+ // Save the extras.
bc_vec_string(&vm.history.extras, f->len - i, f->buf + i);
}
+ // Else clear the extras if told to.
else if (type >= BC_FLUSH_NO_EXTRAS_CLEAR) {
bc_vec_popAll(&vm.history.extras);
}
}
#endif // BC_ENABLE_HISTORY
+ // Actually output.
s = bc_file_output(f->fd, f->buf, f->len);
f->len = 0;
}
@@ -123,12 +153,15 @@ void bc_file_flush(BcFile *restrict f, BcFlushType type) {
BcStatus s = bc_file_flushErr(f, type);
+ // If we have an error...
if (BC_ERR(s)) {
+ // For EOF, set it and jump.
if (s == BC_STATUS_EOF) {
vm.status = (sig_atomic_t) s;
BC_JMP;
}
+ // Blow up on fatal error. Okay, not blow up, just quit.
else bc_vm_fatalError(BC_ERR_FATAL_IO_ERR);
}
}
@@ -136,11 +169,14 @@ void bc_file_flush(BcFile *restrict f, BcFlushType type) {
void bc_file_write(BcFile *restrict f, BcFlushType type,
const char *buf, size_t n)
{
+ // If we have enough to flush, do it.
if (n > f->cap - f->len) {
bc_file_flush(f, type);
assert(!f->len);
}
+ // If the output is large enough to flush by itself, just output it.
+ // Otherwise, put it into the buffer.
if (BC_UNLIKELY(n > f->cap - f->len)) bc_file_output(f->fd, buf, n);
else {
memcpy(f->buf + f->len, buf, n);
@@ -163,10 +199,18 @@ void bc_file_vprintf(BcFile *restrict f, const char *fmt, va_list args) {
const char *ptr = fmt;
char buf[BC_FILE_ULL_LENGTH];
+ // This is a poor man's printf(). While I could look up algorithms to make
+ // it as fast as possible, and should when I write the standard library for
+ // a new language, for bc, outputting is not the bottleneck. So we cheese it
+ // for now.
+
+ // Find each percent sign.
while ((percent = strchr(ptr, '%')) != NULL) {
char c;
+ // If the percent sign is not where we are, write what's inbetween to
+ // the buffer.
if (percent != ptr) {
size_t len = (size_t) (percent - ptr);
bc_file_write(f, bc_flush_none, ptr, len);
@@ -174,6 +218,8 @@ void bc_file_vprintf(BcFile *restrict f, const char *fmt, va_list args) {
c = percent[1];
+ // We only parse some format specifiers, the ones bc uses. If you add
+ // more, you need to make sure to add them here.
if (c == 'c') {
uchar uc = (uchar) va_arg(args, int);
@@ -187,15 +233,18 @@ void bc_file_vprintf(BcFile *restrict f, const char *fmt, va_list args) {
bc_file_puts(f, bc_flush_none, s);
}
#if BC_DEBUG_CODE
+ // We only print signed integers in debug code.
else if (c == 'd') {
int d = va_arg(args, int);
+ // Take care of negative. Let's not worry about overflow.
if (d < 0) {
bc_file_putchar(f, bc_flush_none, '-');
d = -d;
}
+ // Either print 0 or translate and print.
if (!d) bc_file_putchar(f, bc_flush_none, '0');
else {
bc_file_ultoa((unsigned long long) d, buf);
@@ -207,11 +256,15 @@ void bc_file_vprintf(BcFile *restrict f, const char *fmt, va_list args) {
unsigned long long ull;
+ // These are the ones that it expects from here. Fortunately, all of
+ // these are unsigned types, so they can use the same code, more or
+ // less.
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);
+ // Either print 0 or translate and print.
if (!ull) bc_file_putchar(f, bc_flush_none, '0');
else {
bc_file_ultoa(ull, buf);
@@ -219,9 +272,12 @@ void bc_file_vprintf(BcFile *restrict f, const char *fmt, va_list args) {
}
}
+ // Increment to the next spot after the specifier.
ptr = percent + 2 + (c == 'l' || c == 'z');
}
+ // If we get here, there are no more percent signs, so we just output
+ // whatever is left.
if (ptr[0]) bc_file_puts(f, bc_flush_none, ptr);
}
@@ -230,14 +286,19 @@ void bc_file_puts(BcFile *restrict f, BcFlushType type, const char *str) {
}
void bc_file_putchar(BcFile *restrict f, BcFlushType type, uchar c) {
+
if (f->len == f->cap) bc_file_flush(f, type);
+
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;
diff --git a/src/history.c b/src/history.c
index aeb080b3..2d62676a 100644
--- a/src/history.c
+++ b/src/history.c
@@ -169,6 +169,16 @@
#include <file.h>
#include <vm.h>
+#if BC_DEBUG_CODE
+
+/// A file for outputting to when debugging.
+BcFile bc_history_debug_fp;
+
+/// A buffer for the above file.
+char *bc_history_debug_buf;
+
+#endif // BC_DEBUG_CODE
+
/**
* Checks if the code is a wide character.
* @param cp The codepoint to check.
@@ -358,7 +368,7 @@ static size_t bc_history_prevLen(const char *buf, size_t pos) {
* Reads @a n characters from stdin.
* @param buf The buffer to read into. The caller is responsible for making
* sure this is big enough for @a n.
- * @param The number of characters to read.
+ * @param n The number of characters to read.
* @return The number of characters read or less than 0 on error.
*/
static ssize_t bc_history_read(char *buf, size_t n) {
diff --git a/src/lang.c b/src/lang.c
index 8e6e9744..2f6b80be 100644
--- a/src/lang.c
+++ b/src/lang.c
@@ -41,9 +41,13 @@
#include <vm.h>
void bc_const_free(void *constant) {
+
BcConst *c = constant;
+
BC_SIG_ASSERT_LOCKED;
+
assert(c->val != NULL);
+
bc_num_free(&c->num);
}
@@ -54,23 +58,32 @@ void bc_func_insert(BcFunc *f, BcProgram *p, char *name,
BcAuto a;
size_t i, idx;
+ // The function must *always* be valid.
assert(f != NULL);
+ // Get the index of the variable.
idx = bc_program_search(p, name, type == BC_TYPE_VAR);
+ // Search through all of the other autos/parameters.
for (i = 0; i < f->autos.len; ++i) {
+ // Get the auto.
BcAuto *aptr = bc_vec_item(&f->autos, i);
+ // If they match, barf.
if (BC_ERR(idx == aptr->idx && type == aptr->type)) {
+
const char *array = type == BC_TYPE_ARRAY ? "[]" : "";
+
bc_error(BC_ERR_PARSE_DUP_LOCAL, line, name, array);
}
}
+ // Set the auto.
a.idx = idx;
a.type = type;
+ // Push it.
bc_vec_push(&f->autos, &a);
}
#endif // BC_ENABLED
@@ -86,6 +99,7 @@ void bc_func_init(BcFunc *f, const char *name) {
bc_vec_init(&f->consts, sizeof(BcConst), BC_DTOR_CONST);
#if BC_ENABLED
+ // Only bc needs these things.
if (BC_IS_BC) {
bc_vec_init(&f->strs, sizeof(char*), BC_DTOR_NONE);
@@ -149,9 +163,14 @@ void bc_func_free(void *func) {
#endif // NDEBUG
void bc_array_init(BcVec *a, bool nums) {
+
BC_SIG_ASSERT_LOCKED;
+
+ // Set the proper vector.
if (nums) bc_vec_init(a, sizeof(BcNum), BC_DTOR_NUM);
else bc_vec_init(a, sizeof(BcVec), BC_DTOR_VEC);
+
+ // We always want at least one item in the array.
bc_array_expand(a, 1);
}
@@ -164,12 +183,21 @@ void bc_array_copy(BcVec *d, const BcVec *s) {
assert(d != NULL && s != NULL);
assert(d != s && d->size == s->size && d->dtor == s->dtor);
+ // Make sure to destroy everything currently in d.
bc_vec_popAll(d);
+
+ // Preexpand.
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);
+
+ BcNum *dnum, *snum;
+
+ dnum = bc_vec_item(d, i);
+ snum = bc_vec_item(s, i);
+
+ // We have to create a copy of the number as well.
bc_num_createCopy(dnum, snum);
}
}
@@ -182,14 +210,22 @@ void bc_array_expand(BcVec *a, size_t len) {
bc_vec_expand(a, len);
+ // If this is true, then we have a num array.
if (a->size == sizeof(BcNum) && a->dtor == BC_DTOR_NUM) {
+
+ // Initialize numbers until we reach the target.
while (len > a->len) {
BcNum *n = bc_vec_pushEmpty(a);
bc_num_init(n, BC_NUM_DEF_SIZE);
}
}
else {
+
assert(a->size == sizeof(BcVec) && a->dtor == BC_DTOR_VEC);
+
+ // Recursively initialize arrays until we reach the target. Having the
+ // second argument of bc_array_init() be true will activate the base
+ // case, so we're safe.
while (len > a->len) {
BcVec *v = bc_vec_pushEmpty(a);
bc_array_init(v, true);
@@ -209,8 +245,10 @@ void bc_result_copy(BcResult *d, BcResult *src) {
BC_SIG_ASSERT_LOCKED;
+ // d is assumed to not be valid yet.
d->t = src->t;
+ // Yes, it depends on what type.
switch (d->t) {
case BC_RESULT_TEMP:
@@ -253,6 +291,7 @@ void bc_result_copy(BcResult *d, BcResult *src) {
case BC_RESULT_LAST:
{
#ifndef NDEBUG
+ // We should *never* try copying either of these.
abort();
#endif // NDEBUG
}
diff --git a/src/lex.c b/src/lex.c
index cd6242e0..f8b32451 100644
--- a/src/lex.c
+++ b/src/lex.c
@@ -62,28 +62,39 @@ void bc_lex_comment(BcLex *l) {
l->i += 1;
l->t = BC_LEX_WHITESPACE;
+ // This loop is complex because it might need to request more data from
+ // stdin if the comment is not ended. This loop is taken until the comment
+ // is finished or we have EOF.
do {
buf = l->buf;
got_more = false;
+ // If we are in stdin mode, the buffer must be the one used for stdin.
assert(!vm.is_stdin || buf == vm.buffer.v);
+ // Find the end of the comment.
for (i = l->i; !end; i += !end) {
+ // While we don't have an asterisk, eat, but increment nlines.
for (; (c = buf[i]) && c != '*'; ++i) nlines += (c == '\n');
+ // If this is true, we need to request more data.
if (BC_ERR(!c || buf[i + 1] == '\0')) {
+ // Read more.
if (!vm.eof && l->is_stdin) got_more = bc_lex_readLine(l);
break;
}
+ // If this turns true, we found the end. Yay!
end = (buf[i + 1] == '/');
}
+
} while (got_more && !end);
+ // If we didn't find the end, barf.
if (!end) {
l->i = i;
bc_lex_err(l, BC_ERR_PARSE_COMMENT);
@@ -94,8 +105,12 @@ void bc_lex_comment(BcLex *l) {
}
void bc_lex_whitespace(BcLex *l) {
+
char c;
+
l->t = BC_LEX_WHITESPACE;
+
+ // Eat. We don't eat newlines because they can be special.
for (c = l->buf[l->i]; c != '\n' && isspace(c); c = l->buf[++l->i]);
}
@@ -105,6 +120,13 @@ void bc_lex_commonTokens(BcLex *l, char c) {
else bc_lex_whitespace(l);
}
+/**
+ * Parses a number.
+ * @param l The lexer.
+ * @param start The start character.
+ * @param int_only Whether this function should only look for an integer. This
+ * is used to implement the exponent of scientific notation.
+ */
static size_t bc_lex_num(BcLex *l, char start, bool int_only) {
const char *buf = l->buf + l->i;
@@ -112,6 +134,10 @@ static size_t bc_lex_num(BcLex *l, char start, bool int_only) {
char c;
bool last_pt, pt = (start == '.');
+ // This loop looks complex. It is not. It is asking if the character is not
+ // a nul byte and it if it a valid num character based on what we have found
+ // thus far, or whether it is a backslash followed by a newline. I can do
+ // i+1 on the buffer because the buffer must have a nul byte.
for (i = 0; (c = buf[i]) && (BC_LEX_NUM_CHAR(c, pt, int_only) ||
(c == '\\' && buf[i + 1] == '\n')); ++i)
{
@@ -126,11 +152,18 @@ static size_t bc_lex_num(BcLex *l, char start, bool int_only) {
c = buf[i];
+ // If the next character is not a number character, bail.
if (!BC_LEX_NUM_CHAR(c, pt, int_only)) break;
}
+ // Did we find the radix point?
last_pt = (c == '.');
+
+ // If we did, and we already have one, then break because it's not part
+ // of this number.
if (pt && last_pt) break;
+
+ // Set whether we have found a radix point.
pt = pt || last_pt;
bc_vec_push(&l->str, &c);
@@ -143,34 +176,42 @@ void bc_lex_number(BcLex *l, char start) {
l->t = BC_LEX_NUMBER;
+ // Make sure the string is clear.
bc_vec_popAll(&l->str);
bc_vec_push(&l->str, &start);
+ // Parse the number.
l->i += bc_lex_num(l, start, false);
#if BC_ENABLE_EXTRA_MATH
{
char c = l->buf[l->i];
+ // Do we have a number in scientific notation?
if (c == 'e') {
#if BC_ENABLED
+ // Barf for POSIX.
if (BC_IS_POSIX) bc_lex_err(l, BC_ERR_POSIX_EXP_NUM);
#endif // BC_ENABLED
+ // Push the e.
bc_vec_push(&l->str, &c);
l->i += 1;
c = l->buf[l->i];
+ // Check for negative specifically because bc_lex_num() does not.
if (c == BC_LEX_NEG_CHAR) {
bc_vec_push(&l->str, &c);
l->i += 1;
c = l->buf[l->i];
}
+ // We must have a number character, so barf if not.
if (BC_ERR(!BC_LEX_NUM_CHAR(c, false, true)))
bc_lex_verr(l, BC_ERR_PARSE_CHAR, c);
+ // Parse the exponent.
l->i += bc_lex_num(l, 0, true);
}
}
@@ -187,8 +228,10 @@ void bc_lex_name(BcLex *l) {
l->t = BC_LEX_NAME;
+ // Should be obvious. It's looking for valid characters.
while ((c >= 'a' && c <= 'z') || isdigit(c) || c == '_') c = buf[++i];
+ // Set the string to the identifier.
bc_vec_string(&l->str, i, buf);
// Increment the index. We minus 1 because it has already been incremented.
@@ -218,12 +261,16 @@ void bc_lex_next(BcLex *l) {
assert(l != NULL);
l->last = l->t;
+
+ // If this wasn't here, the line number would be off.
l->line += (l->i != 0 && l->buf[l->i - 1] == '\n');
+ // If the last token was EOF, someone called this one too many times.
if (BC_ERR(l->last == BC_LEX_EOF)) bc_lex_err(l, BC_ERR_PARSE_EOF);
l->t = BC_LEX_EOF;
+ // We are done if this is true.
if (l->i == l->len) return;
// Loop until failure or we don't have whitespace. This
@@ -233,6 +280,13 @@ void bc_lex_next(BcLex *l) {
} while (l->t == BC_LEX_WHITESPACE);
}
+/**
+ * Updates the buffer and len so that they are not invalidated when the stdin
+ * buffer grows.
+ * @param l The lexer.
+ * @param text The text.
+ * @param len The length of the text.
+ */
static void bc_lex_fixText(BcLex *l, const char *text, size_t len) {
l->buf = text;
l->len = len;
diff --git a/src/library.c b/src/library.c
index 1e9f43c2..ee30e13e 100644
--- a/src/library.c
+++ b/src/library.c
@@ -45,6 +45,18 @@
#include <num.h>
#include <vm.h>
+// The asserts in this file are important to testing; in many cases, the test
+// would not work without the asserts, so don't remove them without reason.
+//
+// Also, there are many uses of bc_num_clear() here; that is because numbers are
+// being reused, and a clean slate is required.
+//
+// Also, there are a bunch of BC_UNSETJMP and BC_SETJMP_LOCKED() between calls
+// to bc_num_init(). That is because locals are being initialized, and unlike bc
+// proper, this code cannot assume that allocation failures are fatal. So we
+// have to reset the jumps every time to ensure that the locals will be correct
+// after jumping.
+
void bcl_handleSignal(void) {
// Signal already in flight, or bc is not executing.
@@ -69,6 +81,8 @@ BclError bcl_init(void) {
if (vm.refs > 1) return e;
+ // Setting these to NULL ensures that if an error occurs, we only free what
+ // is necessary.
vm.ctxts.v = NULL;
vm.jmp_bufs.v = NULL;
vm.out.v = NULL;
@@ -77,6 +91,7 @@ BclError bcl_init(void) {
BC_SIG_LOCK;
+ // The jmp_bufs always has to be initialized first.
bc_vec_init(&vm.jmp_bufs, sizeof(sigjmp_buf), BC_DTOR_NONE);
BC_FUNC_HEADER_INIT(err);
@@ -86,10 +101,12 @@ BclError bcl_init(void) {
bc_vec_init(&vm.ctxts, sizeof(BclContext), BC_DTOR_NONE);
bc_vec_init(&vm.out, sizeof(uchar), BC_DTOR_NONE);
+ // We need to seed this in case /dev/random and /dev/urandm don't work.
srand((unsigned int) time(NULL));
bc_rand_init(&vm.rng);
err:
+ // This is why we had to set them to NULL.
if (BC_ERR(vm.err)) {
if (vm.out.v != NULL) bc_vec_free(&vm.out);
if (vm.jmp_bufs.v != NULL) bc_vec_free(&vm.jmp_bufs);
@@ -127,27 +144,23 @@ BclContext bcl_context(void) {
void bcl_free(void) {
+ size_t i;
+
vm.refs -= 1;
if (vm.refs) return;
BC_SIG_LOCK;
-#ifndef NDEBUG
bc_rand_free(&vm.rng);
bc_vec_free(&vm.out);
- {
- size_t i;
-
- for (i = 0; i < vm.ctxts.len; ++i) {
- BclContext ctxt = *((BclContext*) bc_vec_item(&vm.ctxts, i));
- bcl_ctxt_free(ctxt);
- }
+ for (i = 0; i < vm.ctxts.len; ++i) {
+ BclContext ctxt = *((BclContext*) bc_vec_item(&vm.ctxts, i));
+ bcl_ctxt_free(ctxt);
}
bc_vec_free(&vm.ctxts);
-#endif // NDEBUG
bc_vm_atexit();
@@ -178,6 +191,8 @@ BclContext bcl_ctxt_create(void) {
BC_FUNC_HEADER_LOCK(err);
+ // We want the context to be free of any interference of other parties, so
+ // malloc() is appropriate here.
ctxt = bc_vm_malloc(sizeof(BclCtxt));
bc_vec_init(&ctxt->nums, sizeof(BcNum), BC_DTOR_BCL_NUM);
@@ -246,6 +261,8 @@ BclError bcl_err(BclNumber n) {
BC_CHECK_CTXT_ERR(ctxt);
+ // Errors are encoded as (0 - error_code). If the index is in that range, it
+ // is an encoded error.
if (n.i >= ctxt->nums.len) {
if (n.i > 0 - (size_t) BCL_ERROR_NELEMS) return (BclError) (0 - n.i);
else return BCL_ERROR_INVALID_NUM;
@@ -253,22 +270,31 @@ BclError bcl_err(BclNumber n) {
else return BCL_ERROR_NONE;
}
+/**
+ * Inserts a BcNum into a context's list of numbers.
+ * @param ctxt The context to insert into.
+ * @param n The BcNum to insert.
+ * @return The resulting BclNumber from the insert.
+ */
static BclNumber bcl_num_insert(BclContext ctxt, BcNum *restrict n) {
BclNumber idx;
+ // If there is a free spot...
if (ctxt->free_nums.len) {
BcNum *ptr;
+ // Get the index of the free spot and remove it.
idx = *((BclNumber*) bc_vec_top(&ctxt->free_nums));
-
bc_vec_pop(&ctxt->free_nums);
+ // Copy the number into the spot.
ptr = bc_vec_item(&ctxt->nums, idx.i);
memcpy(ptr, n, sizeof(BcNum));
}
else {
+ // Just push the number onto the vector.
idx.i = ctxt->nums.len;
bc_vec_push(&ctxt->nums, n);
}
@@ -302,6 +328,12 @@ err:
return idx;
}
+/**
+ * Destructs a number and marks its spot as free.
+ * @param ctxt The context.
+ * @param n The index of the number.
+ * @param num The number to destroy.
+ */
static void bcl_num_dtor(BclContext ctxt, BclNumber n, BcNum *restrict num) {
BC_SIG_ASSERT_LOCKED;
@@ -377,8 +409,8 @@ BclNumber bcl_dup(BclNumber s) {
assert(src != NULL && src->num != NULL);
+ // Copy the number.
bc_num_clear(&dest);
-
bc_num_createCopy(&dest, src);
err:
@@ -548,9 +580,16 @@ err:
return idx;
}
-static BclNumber bcl_binary(BclNumber a, BclNumber b,
- const BcNumBinaryOp op,
- const BcNumBinaryOpReq req)
+/**
+ * Sets up and executes a binary operator operation.
+ * @param a The first operand.
+ * @param b The second operand.
+ * @param op The operation.
+ * @param req The function to get the size of the result for preallocation.
+ * @return The result of the operation.
+ */
+static BclNumber bcl_binary(BclNumber a, BclNumber b, const BcNumBinaryOp op,
+ const BcNumBinaryOpReq req)
{
BclError e = BCL_ERROR_NONE;
BcNum *aptr, *bptr;
@@ -575,8 +614,8 @@ static BclNumber bcl_binary(BclNumber a, BclNumber b,
assert(aptr != NULL && bptr != NULL);
assert(aptr->num != NULL && bptr->num != NULL);
+ // Clear and initialize the result.
bc_num_clear(&c);
-
bc_num_init(&c, req(aptr, bptr, ctxt->scale));
BC_SIG_UNLOCK;
@@ -584,9 +623,13 @@ static BclNumber bcl_binary(BclNumber a, BclNumber b,
op(aptr, bptr, &c, ctxt->scale);
err:
+
BC_SIG_MAYLOCK;
+
+ // Eat the operands.
bcl_num_dtor(ctxt, a, aptr);
if (b.i != a.i) bcl_num_dtor(ctxt, b, bptr);
+
BC_FUNC_FOOTER(e);
BC_MAYBE_SETUP(ctxt, e, c, idx);
@@ -690,7 +733,10 @@ BclError bcl_divmod(BclNumber a, BclNumber b, BclNumber *c, BclNumber *d) {
req = bc_num_divReq(aptr, bptr, ctxt->scale);
+ // Initialize the numbers.
bc_num_init(&cnum, req);
+ BC_UNSETJMP;
+ BC_SETJMP_LOCKED(err);
bc_num_init(&dnum, req);
BC_SIG_UNLOCK;
@@ -700,18 +746,28 @@ BclError bcl_divmod(BclNumber a, BclNumber b, BclNumber *c, BclNumber *d) {
err:
BC_SIG_MAYLOCK;
+ // Eat the operands.
bcl_num_dtor(ctxt, a, aptr);
if (b.i != a.i) bcl_num_dtor(ctxt, b, bptr);
+ // If there was an error...
if (BC_ERR(vm.err)) {
+
+ // Free the results.
if (cnum.num != NULL) bc_num_free(&cnum);
if (dnum.num != NULL) bc_num_free(&dnum);
+
+ // Make sure the return values are invalid.
c->i = 0 - (size_t) BCL_ERROR_INVALID_NUM;
d->i = c->i;
+
BC_FUNC_FOOTER(e);
}
else {
+
BC_FUNC_FOOTER(e);
+
+ // Insert the results into the context.
*c = bcl_num_insert(ctxt, &cnum);
*d = bcl_num_insert(ctxt, &dnum);
}
@@ -750,10 +806,12 @@ BclNumber bcl_modexp(BclNumber a, BclNumber b, BclNumber c) {
assert(aptr != NULL && bptr != NULL && cptr != NULL);
assert(aptr->num != NULL && bptr->num != NULL && cptr->num != NULL);
+ // Prepare the result.
bc_num_clear(&d);
req = bc_num_divReq(aptr, cptr, 0);
+ // Initialize the result.
bc_num_init(&d, req);
BC_SIG_UNLOCK;
@@ -763,6 +821,7 @@ BclNumber bcl_modexp(BclNumber a, BclNumber b, BclNumber c) {
err:
BC_SIG_MAYLOCK;
+ // Eat the operands.
bcl_num_dtor(ctxt, a, aptr);
if (b.i != a.i) bcl_num_dtor(ctxt, b, bptr);
if (c.i != a.i && c.i != b.i) bcl_num_dtor(ctxt, c, cptr);
@@ -841,6 +900,8 @@ BclNumber bcl_parse(const char *restrict val) {
assert(val != NULL);
+ // We have to take care of negative here because bc's number parsing does
+ // not.
neg = (val[0] == '-');
if (neg) val += 1;
@@ -850,14 +911,15 @@ BclNumber bcl_parse(const char *restrict val) {
goto err;
}
+ // Clear and initialize the number.
bc_num_clear(&n);
-
bc_num_init(&n, BC_NUM_DEF_SIZE);
BC_SIG_UNLOCK;
bc_num_parse(&n, val, (BcBigDig) ctxt->ibase);
+ // Set the negative.
n.rdx = BC_NUM_NEG_VAL_NP(n, neg);
err:
@@ -888,15 +950,21 @@ char* bcl_string(BclNumber n) {
assert(nptr != NULL && nptr->num != NULL);
+ // Clear the buffer.
bc_vec_popAll(&vm.out);
+ // Print to the buffer.
bc_num_print(nptr, (BcBigDig) ctxt->obase, false);
bc_vec_pushByte(&vm.out, '\0');
BC_SIG_LOCK;
+
+ // Just dup the string; the caller is responsible for it.
str = bc_vm_strdup(vm.out.v);
err:
+
+ // Eat the operand.
bcl_num_dtor(ctxt, n, nptr);
BC_FUNC_FOOTER_NO_ERR;
@@ -928,8 +996,8 @@ BclNumber bcl_irand(BclNumber a) {
assert(aptr != NULL && aptr->num != NULL);
+ // Clear and initialize the result.
bc_num_clear(&b);
-
bc_num_init(&b, BC_NUM_DEF_SIZE);
BC_SIG_UNLOCK;
@@ -938,7 +1006,10 @@ BclNumber bcl_irand(BclNumber a) {
err:
BC_SIG_MAYLOCK;
+
+ // Eat the operand.
bcl_num_dtor(ctxt, a, aptr);
+
BC_FUNC_FOOTER(e);
BC_MAYBE_SETUP(ctxt, e, b, idx);
@@ -947,12 +1018,19 @@ err:
return idx;
}
+/**
+ * Helps bcl_frand(). This is separate because the error handling is easier that
+ * way. It is also easier to do ifrand that way.
+ * @param b The return parameter.
+ * @param places The number of decimal places to generate.
+ */
static void bcl_frandHelper(BcNum *restrict b, size_t places) {
BcNum exp, pow, ten;
BcDig exp_digs[BC_NUM_BIGDIG_LOG10];
BcDig ten_digs[BC_NUM_BIGDIG_LOG10];
+ // Set up temporaries.
bc_num_setup(&exp, exp_digs, BC_NUM_BIGDIG_LOG10);
bc_num_setup(&ten, ten_digs, BC_NUM_BIGDIG_LOG10);
@@ -961,20 +1039,23 @@ static void bcl_frandHelper(BcNum *restrict b, size_t places) {
bc_num_bigdig2num(&exp, (BcBigDig) places);
+ // Clear the temporary that might need to grow.
bc_num_clear(&pow);
BC_SIG_LOCK;
BC_SETJMP_LOCKED(err);
+ // Initialize the temporary that might need to grow.
bc_num_init(&pow, bc_num_powReq(&ten, &exp, 0));
BC_SIG_UNLOCK;
+ // Generate the number.
bc_num_pow(&ten, &exp, &pow, 0);
-
bc_num_irand(&pow, b, &vm.rng);
+ // Make the number entirely fraction.
bc_num_shiftRight(b, places);
err:
@@ -996,8 +1077,8 @@ BclNumber bcl_frand(size_t places) {
bc_vec_grow(&ctxt->nums, 1);
+ // Clear and initialize the number.
bc_num_clear(&n);
-
bc_num_init(&n, BC_NUM_DEF_SIZE);
BC_SIG_UNLOCK;
@@ -1006,6 +1087,7 @@ BclNumber bcl_frand(size_t places) {
err:
BC_SIG_MAYLOCK;
+
BC_FUNC_FOOTER(e);
BC_MAYBE_SETUP(ctxt, e, n, idx);
@@ -1014,21 +1096,30 @@ err:
return idx;
}
+/**
+ * Helps bc_ifrand(). This is separate because error handling is easier that
+ * way.
+ * @param a The limit for bc_num_irand().
+ * @param b The return parameter.
+ * @param places The number of decimal places to generate.
+ */
static void bcl_ifrandHelper(BcNum *restrict a, BcNum *restrict b,
size_t places)
{
BcNum ir, fr;
+ // Clear the integer and fractional numbers.
bc_num_clear(&ir);
bc_num_clear(&fr);
BC_SIG_LOCK;
- BC_SETJMP_LOCKED(err);
-
+ // Initialize the integer and fractional numbers.
bc_num_init(&ir, BC_NUM_DEF_SIZE);
bc_num_init(&fr, BC_NUM_DEF_SIZE);
+ BC_SETJMP_LOCKED(err);
+
BC_SIG_UNLOCK;
bc_num_irand(a, &ir, &vm.rng);
@@ -1052,7 +1143,6 @@ BclNumber bcl_ifrand(BclNumber a, size_t places) {
BclContext ctxt;
BC_CHECK_CTXT(ctxt);
-
BC_CHECK_NUM(ctxt, a);
BC_FUNC_HEADER_LOCK(err);
@@ -1065,8 +1155,8 @@ BclNumber bcl_ifrand(BclNumber a, size_t places) {
assert(aptr != NULL && aptr->num != NULL);
+ // Clear and initialize the number.
bc_num_clear(&b);
-
bc_num_init(&b, BC_NUM_DEF_SIZE);
BC_SIG_UNLOCK;
@@ -1075,7 +1165,10 @@ BclNumber bcl_ifrand(BclNumber a, size_t places) {
err:
BC_SIG_MAYLOCK;
+
+ // Eat the oprand.
bcl_num_dtor(ctxt, a, aptr);
+
BC_FUNC_FOOTER(e);
BC_MAYBE_SETUP(ctxt, e, b, idx);
@@ -1091,7 +1184,6 @@ BclError bcl_rand_seedWithNum(BclNumber n) {
BclContext ctxt;
BC_CHECK_CTXT_ERR(ctxt);
-
BC_CHECK_NUM_ERR(ctxt, n);
BC_FUNC_HEADER(err);
@@ -1151,8 +1243,8 @@ BclNumber bcl_rand_seed2num(void) {
BC_FUNC_HEADER_LOCK(err);
+ // Clear and initialize the number.
bc_num_clear(&n);
-
bc_num_init(&n, BC_NUM_DEF_SIZE);
BC_SIG_UNLOCK;
diff --git a/src/main.c b/src/main.c
index 54110b16..38c87a41 100644
--- a/src/main.c
+++ b/src/main.c
@@ -56,17 +56,24 @@ int main(int argc, char *argv[]) {
char *name;
size_t len = strlen(BC_EXECPREFIX);
+ // Must set the locale properly in order to have the right error messages.
vm.locale = setlocale(LC_ALL, "");
+ // Set the start pledge().
bc_pledge(bc_pledge_start, NULL);
+ // Figure out the name of the calculator we are using. We can't use basename
+ // because it's not portable, but yes, this is stripping off the directory.
name = strrchr(argv[0], BC_FILE_SEP);
vm.name = (name == NULL) ? argv[0] : name + 1;
+ // If the name is longer than the length of the prefix, skip the prefix.
if (strlen(vm.name) > len) vm.name += len;
BC_SIG_LOCK;
+ // We *must* do this here. Otherwise, other code could not jump out all of
+ // the way.
bc_vec_init(&vm.jmp_bufs, sizeof(sigjmp_buf), BC_DTOR_NONE);
BC_SETJMP_LOCKED(exit);
@@ -76,6 +83,7 @@ int main(int argc, char *argv[]) {
#elif !BC_ENABLED
dc_main(argc, argv);
#else
+ // BC_IS_BC uses vm.name, which was set above. So we're good.
if (BC_IS_BC) bc_main(argc, argv);
else dc_main(argc, argv);
#endif
@@ -83,5 +91,6 @@ int main(int argc, char *argv[]) {
exit:
BC_SIG_MAYLOCK;
+ // Ensure we exit appropriately.
return bc_vm_atexit((int) vm.status);
}
diff --git a/src/num.c b/src/num.c
index 2af3b83c..50e5cdae 100644
--- a/src/num.c
+++ b/src/num.c
@@ -1460,7 +1460,7 @@ static void bc_num_divExtend(BcNum *restrict a, BcNum *restrict b,
/**
* Actually does division. This is a rewrite of my original code by Stefan Esser
- * <se@freebsd.org>.
+ * from FreeBSD.
* @param a The first operand.
* @param b The second operand.
* @param c The return parameter.
@@ -1821,8 +1821,8 @@ static void bc_num_p(BcNum *a, BcNum *b, BcNum *restrict c, size_t scale) {
BcNum copy, btemp;
BcBigDig exp;
- size_t i, powrdx, resrdx;
- bool neg, zero;
+ size_t powrdx, resrdx;
+ bool neg;
if (BC_ERR(bc_num_nonInt(b, &btemp))) bc_err(BC_ERR_MATH_NON_INTEGER);
diff --git a/src/opt.c b/src/opt.c
index 2450b9f4..ae9bc1cc 100644
--- a/src/opt.c
+++ b/src/opt.c
@@ -47,10 +47,22 @@
#include <opt.h>
#include <vm.h>
+/**
+ * Returns true if index @a i is the end of the longopts array.
+ * @param longopts The long options array.
+ * @param i The index to test.
+ * @return True if @a i is the last index, false otherwise.
+ */
static inline bool bc_opt_longoptsEnd(const BcOptLong *longopts, size_t i) {
return !longopts[i].name && !longopts[i].val;
}
+/**
+ * Returns the name of the long option that matches the character @a c.
+ * @param longopts The long options array.
+ * @param c The character to match against.
+ * @return The name of the long option that matches @a c, or "NULL".
+ */
static const char* bc_opt_longopt(const BcOptLong *longopts, int c) {
size_t i;
@@ -64,11 +76,23 @@ static const char* bc_opt_longopt(const BcOptLong *longopts, int c) {
return "NULL";
}
+/**
+ * Issues a fatal error for an option parsing failure.
+ * @param err The error.
+ * @param c The character for the failing option.
+ * @param str Either the string for the failing option, or the invalid option.
+ */
static void bc_opt_error(BcErr err, int c, const char *str) {
if (err == BC_ERR_FATAL_OPTION) bc_error(err, 0, str);
else bc_error(err, 0, (int) c, str);
}
+/**
+ * Returns the type of the long option that matches @a c.
+ * @param longopts The long options array.
+ * @param c The character to match against.
+ * @return The type of the long option as an integer, or -1 if none.
+ */
static int bc_opt_type(const BcOptLong *longopts, char c) {
size_t i;
@@ -82,6 +106,12 @@ static int bc_opt_type(const BcOptLong *longopts, char c) {
return (int) longopts[i].type;
}
+/**
+ * Parses a short option.
+ * @param o The option parser.
+ * @param longopts The long options array.
+ * @return The character for the short option, or -1 if none left.
+ */
static int bc_opt_parseShort(BcOpt *o, const BcOptLong *longopts) {
int type;
@@ -89,12 +119,15 @@ static int bc_opt_parseShort(BcOpt *o, const BcOptLong *longopts) {
char *option = o->argv[o->optind];
int ret = -1;
+ // Make sure to clear these.
o->optopt = 0;
o->optarg = NULL;
+ // Get the next option.
option += o->subopt + 1;
o->optopt = option[0];
+ // Get the type and the next data.
type = bc_opt_type(longopts, option[0]);
next = o->argv[o->optind + 1];
@@ -104,6 +137,7 @@ static int bc_opt_parseShort(BcOpt *o, const BcOptLong *longopts) {
case BC_OPT_BC_ONLY:
case BC_OPT_DC_ONLY:
{
+ // Check for invalid option and barf if so.
if (type == -1 || (type == BC_OPT_BC_ONLY && BC_IS_DC) ||
(type == BC_OPT_DC_ONLY && BC_IS_BC))
{
@@ -120,31 +154,41 @@ static int bc_opt_parseShort(BcOpt *o, const BcOptLong *longopts) {
case BC_OPT_NONE:
{
+ // If there is something else, update the suboption.
if (option[1]) o->subopt += 1;
else {
+
+ // Go to the next argument.
o->subopt = 0;
o->optind += 1;
}
ret = (int) option[0];
+
break;
}
case BC_OPT_REQUIRED:
{
+ // Always go to the next argument.
o->subopt = 0;
o->optind += 1;
+ // Use the next characters, if they exist.
if (option[1]) o->optarg = option + 1;
else if (next != NULL) {
+
+ // USe the next.
o->optarg = next;
o->optind += 1;
}
+ // No argument, barf.
else bc_opt_error(BC_ERR_FATAL_OPTION_NO_ARG, option[0],
bc_opt_longopt(longopts, option[0]));
ret = (int) option[0];
+
break;
}
}
@@ -152,21 +196,38 @@ static int bc_opt_parseShort(BcOpt *o, const BcOptLong *longopts) {
return ret;
}
+/**
+ * Ensures that a long option argument matches a long option name, regardless of
+ * "=<data>" at the end.
+ * @param name The name to match.
+ * @param option The command-line argument.
+ * @return True if @a option matches @a name, false otherwise.
+ */
static bool bc_opt_longoptsMatch(const char *name, const char *option) {
const char *a = option, *n = name;
+ // Can never match a NULL name.
if (name == NULL) return false;
+ // Loop through
for (; *a && *n && *a != '='; ++a, ++n) {
if (*a != *n) return false;
}
+ // Ensure they both end at the same place.
return (*n == '\0' && (*a == '\0' || *a == '='));
}
+/**
+ * Returns a pointer to the argument of a long option, or NULL if it not in the
+ * same argument.
+ * @param option The option to find the argument of.
+ * @return A pointer to the argument of the option, or NULL if none.
+ */
static char* bc_opt_longoptsArg(char *option) {
+ // Find the end or equals sign.
for (; *option && *option != '='; ++option);
if (*option == '=') return option + 1;
@@ -179,6 +240,7 @@ int bc_opt_parse(BcOpt *o, const BcOptLong *longopts) {
char *option;
bool empty;
+ // This just eats empty options.
do {
option = o->argv[o->optind];
@@ -189,15 +251,19 @@ int bc_opt_parse(BcOpt *o, const BcOptLong *longopts) {
} while (empty);
+ // If the option is just a "--".
if (BC_OPT_ISDASHDASH(option)) {
// Consume "--".
o->optind += 1;
return -1;
}
+ // Parse a short option.
else if (BC_OPT_ISSHORTOPT(option)) return bc_opt_parseShort(o, longopts);
+ // If the option is not long at this point, we are done.
else if (!BC_OPT_ISLONGOPT(option)) return -1;
+ // Clear these.
o->optopt = 0;
o->optarg = NULL;
@@ -205,33 +271,42 @@ int bc_opt_parse(BcOpt *o, const BcOptLong *longopts) {
option += 2;
o->optind += 1;
+ // Loop through the valid long options.
for (i = 0; !bc_opt_longoptsEnd(longopts, i); i++) {
const char *name = longopts[i].name;
+ // If we have a match...
if (bc_opt_longoptsMatch(name, option)) {
char *arg;
+ // Get the option char and the argument.
o->optopt = longopts[i].val;
arg = bc_opt_longoptsArg(option);
+ // Error if the option is invalid..
if ((longopts[i].type == BC_OPT_BC_ONLY && BC_IS_DC) ||
(longopts[i].type == BC_OPT_DC_ONLY && BC_IS_BC))
{
bc_opt_error(BC_ERR_FATAL_OPTION, o->optopt, name);
}
+ // Error if we have an argument and should not.
if (longopts[i].type == BC_OPT_NONE && arg != NULL)
{
bc_opt_error(BC_ERR_FATAL_OPTION_ARG, o->optopt, name);
}
+ // Set the argument, or check the next argument if we don't have
+ // one.
if (arg != NULL) o->optarg = arg;
else if (longopts[i].type == BC_OPT_REQUIRED) {
+ // Get the next argument.
o->optarg = o->argv[o->optind];
+ // All's good if it exists; otherwise, barf.
if (o->optarg != NULL) o->optind += 1;
else bc_opt_error(BC_ERR_FATAL_OPTION_NO_ARG,
o->optopt, name);
@@ -241,6 +316,7 @@ int bc_opt_parse(BcOpt *o, const BcOptLong *longopts) {
}
}
+ // If we reach this point, the option is invalid.
bc_opt_error(BC_ERR_FATAL_OPTION, 0, option);
BC_UNREACHABLE
diff --git a/src/parse.c b/src/parse.c
index 9dd69e3f..a6032c4e 100644
--- a/src/parse.c
+++ b/src/parse.c
@@ -53,6 +53,13 @@ inline void bc_parse_pushName(const BcParse *p, char *name, bool var) {
bc_parse_pushIndex(p, bc_program_search(p->prog, name, var));
}
+/**
+ * Updates the function, then pushes the instruction and the index. This is a
+ * convenience function.
+ * @param p The parser.
+ * @param inst The instruction to push.
+ * @param idx The index to push.
+ */
static void bc_parse_update(BcParse *p, uchar inst, size_t idx) {
bc_parse_updateFunc(p, p->fidx);
bc_parse_push(p, inst);
@@ -67,10 +74,14 @@ void bc_parse_addString(BcParse *p) {
BC_SIG_LOCK;
#if BC_ENABLED
+
+ // bc has different slab vectors than dc. dc also directly adds the string
+ // as a function.
if (BC_IS_BC) {
char **str = bc_vec_pushEmpty(strs);
+ // Figure out which slab vector to use.
BcVec *slabs = p->fidx == BC_PROG_MAIN || p->fidx == BC_PROG_READ ?
&vm.main_slabs : &vm.other_slabs;
@@ -87,6 +98,7 @@ void bc_parse_addString(BcParse *p) {
}
#endif // DC_ENABLED
+ // Push the string info.
bc_parse_update(p, BC_INST_STR, idx);
BC_SIG_UNLOCK;
@@ -99,31 +111,39 @@ static void bc_parse_addNum(BcParse *p, const char *string) {
BcConst *c;
BcVec *slabs;
+ // Special case 0.
if (bc_parse_zero[0] == string[0] && bc_parse_zero[1] == string[1]) {
bc_parse_push(p, BC_INST_ZERO);
return;
}
+
+ // Special case 1.
if (bc_parse_one[0] == string[0] && bc_parse_one[1] == string[1]) {
bc_parse_push(p, BC_INST_ONE);
return;
}
+ // Get the index.
idx = consts->len;
BC_SIG_LOCK;
#if BC_ENABLED
+ // Get the right slab.
slabs = p->fidx == BC_PROG_MAIN || p->fidx == BC_PROG_READ || BC_IS_DC ?
&vm.main_const_slab : &vm.other_slabs;
#else // BC_ENABLED
slabs = &vm.main_const_slab;
#endif // BC_ENABLED
+ // Push an empty constant.
c = bc_vec_pushEmpty(consts);
+ // Set the fields.
c->val = bc_slabvec_strdup(slabs, string);
c->base = BC_NUM_BIGDIG_MAX;
+ // We need this to be able to tell that the number has not been allocated.
bc_num_clear(&c->num);
bc_parse_update(p, BC_INST_NUM, idx);
@@ -137,6 +157,8 @@ void bc_parse_number(BcParse *p) {
char *exp = strchr(p->l.str.v, 'e');
size_t idx = SIZE_MAX;
+ // Do we have a number in scientific notation? If so, add a nul byte where
+ // the e is.
if (exp != NULL) {
idx = ((size_t) (exp - p->l.str.v));
*exp = 0;
@@ -146,12 +168,15 @@ void bc_parse_number(BcParse *p) {
bc_parse_addNum(p, p->l.str.v);
#if BC_ENABLE_EXTRA_MATH
+ // If we have a number in scientific notation...
if (exp != NULL) {
bool neg;
+ // Figure out if the exponent is negative.
neg = (*((char*) bc_vec_item(&p->l.str, idx + 1)) == BC_LEX_NEG_CHAR);
+ // Add the number and instruction.
bc_parse_addNum(p, bc_vec_item(&p->l.str, idx + 1 + neg));
bc_parse_push(p, BC_INST_LSHIFT + neg);
}
@@ -159,6 +184,7 @@ void bc_parse_number(BcParse *p) {
}
void bc_parse_text(BcParse *p, const char *text, bool is_stdin) {
+
// Make sure the pointer isn't invalidated.
p->func = bc_vec_item(&p->prog->fns, p->fidx);
bc_lex_text(&p->l, text, is_stdin);
@@ -168,16 +194,20 @@ void bc_parse_reset(BcParse *p) {
BC_SIG_ASSERT_LOCKED;
+ // Reset the function if it isn't main and switch to main.
if (p->fidx != BC_PROG_MAIN) {
bc_func_reset(p->func);
bc_parse_updateFunc(p, BC_PROG_MAIN);
}
+ // Reset the lexer.
p->l.i = p->l.len;
p->l.t = BC_LEX_EOF;
#if BC_ENABLED
if (BC_IS_BC) {
+
+ // Get rid of the bc parser state.
p->auto_part = false;
bc_vec_npop(&p->flags, p->flags.len - 1);
bc_vec_popAll(&p->exits);
@@ -186,8 +216,10 @@ void bc_parse_reset(BcParse *p) {
}
#endif // BC_ENABLED
+ // Reset the program. This might clear the error.
bc_program_reset(p->prog);
+ // Jump if there is an error.
if (BC_ERR(vm.status)) BC_JMP;
}
@@ -223,12 +255,16 @@ void bc_parse_init(BcParse *p, BcProgram *prog, size_t func) {
#if BC_ENABLED
if (BC_IS_BC) {
+
+ // We always want at least one flag set on the flags stack.
bc_vec_init(&p->flags, sizeof(uint16_t), BC_DTOR_NONE);
bc_vec_push(&p->flags, &flag);
+
bc_vec_init(&p->exits, sizeof(BcInstPtr), BC_DTOR_NONE);
bc_vec_init(&p->conds, sizeof(size_t), BC_DTOR_NONE);
bc_vec_init(&p->ops, sizeof(BcLexType), BC_DTOR_NONE);
bc_vec_init(&p->buf, sizeof(char), BC_DTOR_NONE);
+
bc_slabvec_init(&p->slab);
p->auto_part = false;
}
@@ -236,6 +272,7 @@ void bc_parse_init(BcParse *p, BcProgram *prog, size_t func) {
bc_lex_init(&p->l);
+ // Set up the function.
p->prog = prog;
bc_parse_updateFunc(p, func);
}
diff --git a/src/rand.c b/src/rand.c
index cae8a940..8ef74387 100644
--- a/src/rand.c
+++ b/src/rand.c
@@ -60,6 +60,12 @@
#if !BC_RAND_BUILTIN
+/**
+ * Adds two 64-bit values and preserves the overflow.
+ * @param a The first operand.
+ * @param b The second operand.
+ * @return The sum, including overflow.
+ */
static BcRandState bc_rand_addition(uint_fast64_t a, uint_fast64_t b) {
BcRandState res;
@@ -70,6 +76,12 @@ static BcRandState bc_rand_addition(uint_fast64_t a, uint_fast64_t b) {
return res;
}
+/**
+ * Adds two 128-bit values and discards the overflow.
+ * @param a The first operand.
+ * @param b The second operand.
+ * @return The sum, without overflow.
+ */
static BcRandState bc_rand_addition2(BcRandState a, BcRandState b) {
BcRandState temp, res;
@@ -81,6 +93,12 @@ static BcRandState bc_rand_addition2(BcRandState a, BcRandState b) {
return res;
}
+/**
+ * Multiplies two 64-bit values and preserves the overflow.
+ * @param a The first operand.
+ * @param b The second operand.
+ * @return The product, including overflow.
+ */
static BcRandState bc_rand_multiply(uint_fast64_t a, uint_fast64_t b) {
uint_fast64_t al, ah, bl, bh, c0, c1, c2, c3;
@@ -104,6 +122,12 @@ static BcRandState bc_rand_multiply(uint_fast64_t a, uint_fast64_t b) {
return res;
}
+/**
+ * Multiplies two 128-bit values and discards the overflow.
+ * @param a The first operand.
+ * @param b The second operand.
+ * @return The product, without overflow.
+ */
static BcRandState bc_rand_multiply2(BcRandState a, BcRandState b) {
BcRandState c0, c1, c2, carry;
@@ -121,6 +145,11 @@ static BcRandState bc_rand_multiply2(BcRandState a, BcRandState b) {
#endif // BC_RAND_BUILTIN
+/**
+ * Marks a PRNG as modified. This is important for properly maintaining the
+ * stack of PRNG's.
+ * @param r The PRNG to mark as modified.
+ */
static void bc_rand_setModified(BcRNGData *r) {
#if BC_RAND_BUILTIN
@@ -130,6 +159,11 @@ static void bc_rand_setModified(BcRNGData *r) {
#endif // BC_RAND_BUILTIN
}
+/**
+ * Marks a PRNG as not modified. This is important for properly maintaining the
+ * stack of PRNG's.
+ * @param r The PRNG to mark as not modified.
+ */
static void bc_rand_clearModified(BcRNGData *r) {
#if BC_RAND_BUILTIN
@@ -139,6 +173,12 @@ static void bc_rand_clearModified(BcRNGData *r) {
#endif // BC_RAND_BUILTIN
}
+/**
+ * Copies a PRNG to another and marks the copy as modified if it already was or
+ * marks it modified if it already was.
+ * @param d The destination PRNG.
+ * @param s The source PRNG.
+ */
static void bc_rand_copy(BcRNGData *d, BcRNGData *s) {
bool unmod = BC_RAND_NOTMODIFIED(d);
memcpy(d, s, sizeof(BcRNGData));
@@ -147,6 +187,12 @@ static void bc_rand_copy(BcRNGData *d, BcRNGData *s) {
}
#ifndef _WIN32
+
+/**
+ * Reads random data from a file.
+ * @param ptr A pointer to the file, as a void pointer.
+ * @return The random data as an unsigned long.
+ */
static ulong bc_rand_frand(void* ptr) {
ulong buf[1];
@@ -164,6 +210,12 @@ static ulong bc_rand_frand(void* ptr) {
return *((ulong*)buf);
}
#else // _WIN32
+
+/**
+ * Reads random data from BCryptGenRandom().
+ * @param ptr An unused parameter.
+ * @return The random data as an unsigned long.
+ */
static ulong bc_rand_winrand(void *ptr) {
ulong buf[1];
@@ -182,6 +234,14 @@ static ulong bc_rand_winrand(void *ptr) {
}
#endif // _WIN32
+/**
+ * Reads random data from rand(), byte-by-byte because rand() is only guaranteed
+ * to return 15 bits of random data. This is the final fallback and is not
+ * preferred as it is possible to access cryptographically-secure PRNG's on most
+ * systems.
+ * @param ptr An unused parameter.
+ * @return The random data as an unsigned long.
+ */
static ulong bc_rand_rand(void *ptr) {
size_t i;
@@ -189,12 +249,19 @@ static ulong bc_rand_rand(void *ptr) {
BC_UNUSED(ptr);
+ // Fill up the unsigned long byte-by-byte.
for (i = 0; i < sizeof(ulong); ++i)
res |= ((ulong) (rand() & BC_RAND_SRAND_BITS)) << (i * CHAR_BIT);
return res;
}
+/**
+ * Returns the actual increment of the PRNG, including the required last odd
+ * bit.
+ * @param r The PRNG.
+ * @return The increment of the PRNG, including the last odd bit.
+ */
static BcRandState bc_rand_inc(BcRNGData *r) {
BcRandState inc;
@@ -209,7 +276,11 @@ static BcRandState bc_rand_inc(BcRNGData *r) {
return inc;
}
-static void bc_rand_setInc(BcRNGData *r) {
+/**
+ * Sets up the increment for the PRNG.
+ * @param r The PRNG whose increment will be set up.
+ */
+static void bc_rand_setupInc(BcRNGData *r) {
#if BC_RAND_BUILTIN
r->inc <<= 1UL;
@@ -220,6 +291,12 @@ static void bc_rand_setInc(BcRNGData *r) {
#endif // BC_RAND_BUILTIN
}
+/**
+ * Seeds the state of a PRNG.
+ * @param state The return parameter; the state to seed.
+ * @param val1 The lower half of the state.
+ * @param val2 The upper half of the state.
+ */
static void bc_rand_seedState(BcRandState *state, ulong val1, ulong val2) {
#if BC_RAND_BUILTIN
@@ -230,14 +307,28 @@ static void bc_rand_seedState(BcRandState *state, ulong val1, ulong val2) {
#endif // BC_RAND_BUILTIN
}
+/**
+ * Seeds a PRNG.
+ * @param r The return parameter; the PRNG to seed.
+ * @param state1 The lower half of the state.
+ * @param state2 The upper half of the state.
+ * @param inc1 The lower half of the increment.
+ * @param inc2 The upper half of the increment.
+ */
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);
+ bc_rand_setupInc(r);
}
+/**
+ * Fills a PRNG with random data to seed it.
+ * @param r The PRNG.
+ * @param fulong The function to fill an unsigned long.
+ * @param ptr The parameter to pass to @a fulong.
+ */
static void bc_rand_fill(BcRNGData *r, BcRandUlong fulong, void *ptr) {
ulong state1, state2, inc1, inc2;
@@ -251,25 +342,46 @@ static void bc_rand_fill(BcRNGData *r, BcRandUlong fulong, void *ptr) {
bc_rand_seedRNG(r, state1, state2, inc1, inc2);
}
+/**
+ * Executes the "step" portion of a PCG udpate.
+ * @param r The PRNG.
+ */
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));
}
+/**
+ * Returns the new output of PCG.
+ * @param r The PRNG.
+ * @return The new output from the PRNG.
+ */
static BcRand bc_rand_output(BcRNGData *r) {
return BC_RAND_ROT(BC_RAND_FOLD(r->state), BC_RAND_ROTAMT(r->state));
}
+/**
+ * Seeds every PRNG on the PRNG stack between the top and @a idx that has not
+ * been seeded.
+ * @param r The PRNG stack.
+ * @param rng The PRNG on the top of the stack. Must have been seeded.
+ */
static void bc_rand_seedZeroes(BcRNG *r, BcRNGData *rng, size_t idx) {
BcRNGData *rng2;
+ // Just return if there are none to do.
if (r->v.len <= idx) return;
+ // Get the first PRNG that might need to be seeded.
rng2 = bc_vec_item_rev(&r->v, idx);
+ // Does it need seeding? Then it, and maybe more, do.
if (BC_RAND_ZERO(rng2)) {
+
size_t i;
+
+ // Seed the ones that need seeding.
for (i = 1; i < r->v.len; ++i)
bc_rand_copy(bc_vec_item_rev(&r->v, i), rng);
}
@@ -282,6 +394,8 @@ void bc_rand_srand(BcRNGData *rng) {
BC_SIG_LOCK;
#ifndef _WIN32
+
+ // Try /dev/urandom first.
fd = open("/dev/urandom", O_RDONLY);
if (BC_NO_ERR(fd >= 0)) {
@@ -290,6 +404,7 @@ void bc_rand_srand(BcRNGData *rng) {
}
else {
+ // Try /dev/random second.
fd = open("/dev/random", O_RDONLY);
if (BC_NO_ERR(fd >= 0)) {
@@ -298,40 +413,60 @@ void bc_rand_srand(BcRNGData *rng) {
}
}
#else // _WIN32
+ // Try BCryptGenRandom first.
bc_rand_fill(rng, bc_rand_winrand, NULL);
#endif // _WIN32
+ // Fallback to rand() until the thing is seeded.
while (BC_ERR(BC_RAND_ZERO(rng))) bc_rand_fill(rng, bc_rand_rand, NULL);
BC_SIG_UNLOCK;
}
+/**
+ * Propagates a change to the PRNG to all PRNG's in the stack that should have
+ * it. The ones that should have it are laid out in the manpages.
+ * @param r The PRNG stack.
+ * @param rng The PRNG that will be used to seed the others.
+ */
static void bc_rand_propagate(BcRNG *r, BcRNGData *rng) {
+ // Just return if there are none to do.
if (r->v.len <= 1) return;
+ // If the PRNG has not been modified...
if (BC_RAND_NOTMODIFIED(rng)) {
size_t i;
bool go = true;
+ // Find the first PRNG that is modified and seed the others.
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);
}
+ // Seed everything else.
bc_rand_seedZeroes(r, rng, i);
}
+ // Seed everything.
else bc_rand_seedZeroes(r, rng, 1);
}
BcRand bc_rand_int(BcRNG *r) {
+ // Get the actual PRNG.
BcRNGData *rng = bc_vec_top(&r->v);
+ // Make sure the PRNG is seeded.
if (BC_ERR(BC_RAND_ZERO(rng))) bc_rand_srand(rng);
+ // This is the important part of the PRNG. This is the stuff from PCG,
+ // including the return statement.
bc_rand_step(rng);
bc_rand_propagate(r, rng);
@@ -340,6 +475,7 @@ BcRand bc_rand_int(BcRNG *r) {
BcRand bc_rand_bounded(BcRNG *r, BcRand bound) {
+ // Calculate the threshold below which we have to try again.
BcRand rand, threshold = (0 - bound) % bound;
do {
@@ -351,21 +487,33 @@ BcRand bc_rand_bounded(BcRNG *r, BcRand bound) {
void bc_rand_seed(BcRNG *r, ulong state1, ulong state2, ulong inc1, ulong inc2)
{
+ // Get the actual PRNG.
BcRNGData *rng = bc_vec_top(&r->v);
+ // Seed and set up the PRNG's increment.
bc_rand_seedState(&rng->inc, inc1, inc2);
- bc_rand_setInc(rng);
+ bc_rand_setupInc(rng);
bc_rand_setModified(rng);
+ // If the state is 0, use the increment as the state. Otherwise, seed it
+ // with the state.
if (!state1 && !state2) {
memcpy(&rng->state, &rng->inc, sizeof(BcRandState));
bc_rand_step(rng);
}
else bc_rand_seedState(&rng->state, state1, state2);
+ // Propagate the change to PRNG's that need it.
bc_rand_propagate(r, rng);
}
+/**
+ * Returns the increment in the PRNG *without* the odd bit and also with being
+ * shifted one bit down.
+ * @param r The PRNG.
+ * @return The increment without the odd bit and with being shifted one bit
+ * down.
+ */
static BcRandState bc_rand_getInc(BcRNGData *r) {
BcRandState res;
@@ -389,18 +537,27 @@ void bc_rand_getRands(BcRNG *r, BcRand *s1, BcRand *s2, BcRand *i1, BcRand *i2)
if (BC_ERR(BC_RAND_ZERO(rng))) bc_rand_srand(rng);
+ // Get the increment.
inc = bc_rand_getInc(rng);
+ // Chop the state.
*s1 = BC_RAND_TRUNC(rng->state);
*s2 = BC_RAND_CHOP(rng->state);
+ // Chop the increment.
*i1 = BC_RAND_TRUNC(inc);
*i2 = BC_RAND_CHOP(inc);
}
void bc_rand_push(BcRNG *r) {
+
BcRNGData *rng = bc_vec_pushEmpty(&r->v);
+
+ // Make sure the PRNG is properly zeroed because that marks it as needing to
+ // be seeded.
memset(rng, 0, sizeof(BcRNGData));
+
+ // If there is another item, copy it too.
if (r->v.len > 1) bc_rand_copy(rng, bc_vec_item_rev(&r->v, 1));
}
@@ -414,11 +571,11 @@ void bc_rand_init(BcRNG *r) {
bc_rand_push(r);
}
-#ifndef NDEBUG
+#if BC_RAND_USE_FREE
void bc_rand_free(BcRNG *r) {
BC_SIG_ASSERT_LOCKED;
bc_vec_free(&r->v);
}
-#endif // NDEBUG
+#endif // BC_RAND_USE_FREE
#endif // BC_ENABLE_EXTRA_MATH && BC_ENABLE_RAND
diff --git a/src/read.c b/src/read.c
index f9ec033b..d2b3287e 100644
--- a/src/read.c
+++ b/src/read.c
@@ -76,16 +76,22 @@ bool bc_read_buf(BcVec *vec, char *buf, size_t *buf_len) {
char *nl;
+ // If nothing there, return.
if (!*buf_len) return false;
+ // Find the newline.
nl = strchr(buf, '\n');
+ // If a newline exists...
if (nl != NULL) {
+ // Get the size of the data up to, and including, the newline.
size_t nllen = (size_t) ((nl + 1) - buf);
nllen = *buf_len >= nllen ? nllen : *buf_len;
+ // Move data into the vector, and move the rest of the data in the
+ // buffer up.
bc_vec_npush(vec, nllen, buf);
*buf_len -= nllen;
memmove(buf, nl + 1, *buf_len + 1);
@@ -93,6 +99,7 @@ bool bc_read_buf(BcVec *vec, char *buf, size_t *buf_len) {
return true;
}
+ // Just put the data into the vector.
bc_vec_npush(vec, *buf_len, buf);
*buf_len = 0;
@@ -107,37 +114,49 @@ BcStatus bc_read_chars(BcVec *vec, const char *prompt) {
BC_SIG_ASSERT_NOT_LOCKED;
+ // Clear the vector.
bc_vec_popAll(vec);
+ // Handle the prompt, if desired.
if (BC_PROMPT) {
bc_file_puts(&vm.fout, bc_flush_none, prompt);
bc_file_flush(&vm.fout, bc_flush_none);
}
+ // Try reading from the buffer, and if successful, just return.
if (bc_read_buf(vec, vm.buf, &vm.buf_len)) {
bc_vec_pushByte(vec, '\0');
return BC_STATUS_SUCCESS;
}
+ // Loop until we have something.
while (!done) {
ssize_t r;
BC_SIG_LOCK;
+ // Read data from stdin.
r = read(STDIN_FILENO, vm.buf + vm.buf_len,
BC_VM_STDIN_BUF_SIZE - vm.buf_len);
+ // If there was an error...
if (BC_UNLIKELY(r < 0)) {
+ // If interupted...
if (errno == EINTR) {
+ // Jump out if we are supposed to quit, which certain signals
+ // will require.
if (vm.status == (sig_atomic_t) BC_STATUS_QUIT) BC_JMP;
assert(vm.sig);
+ // Clear the signal and status.
vm.sig = 0;
vm.status = (sig_atomic_t) BC_STATUS_SUCCESS;
+
+ // Print the ready message and prompt again.
bc_file_puts(&vm.fout, bc_flush_none, bc_program_ready_msg);
if (BC_PROMPT) bc_file_puts(&vm.fout, bc_flush_none, prompt);
bc_file_flush(&vm.fout, bc_flush_none);
@@ -149,22 +168,27 @@ BcStatus bc_read_chars(BcVec *vec, const char *prompt) {
BC_SIG_UNLOCK;
+ // If we get here, it's bad. Barf.
bc_vm_fatalError(BC_ERR_FATAL_IO_ERR);
}
BC_SIG_UNLOCK;
+ // If we read nothing, make sure to terminate the string and return EOF.
if (r == 0) {
bc_vec_pushByte(vec, '\0');
return BC_STATUS_EOF;
}
+ // Add to the buffer.
vm.buf_len += (size_t) r;
vm.buf[vm.buf_len] = '\0';
+ // Read from the buffer.
done = bc_read_buf(vec, vm.buf, &vm.buf_len);
}
+ // Terminate the string.
bc_vec_pushByte(vec, '\0');
return BC_STATUS_SUCCESS;
@@ -175,6 +199,7 @@ BcStatus bc_read_line(BcVec *vec, const char *prompt) {
BcStatus s;
#if BC_ENABLE_HISTORY
+ // Get a line from either history or manual reading.
if (BC_TTY && !vm.history.badTerm)
s = bc_history_line(&vm.history, vec, prompt);
else s = bc_read_chars(vec, prompt);
@@ -204,20 +229,29 @@ char* bc_read_file(const char *path) {
fd = bc_read_open(path, O_RDONLY);
+ // If we can't read a file, we just barf.
if (BC_ERR(fd < 0)) bc_verr(BC_ERR_FATAL_FILE_ERR, path);
+
+ // The reason we call fstat is to eliminate TOCTOU race conditions. This
+ // way, we have an open file, so it's not going anywhere.
if (BC_ERR(fstat(fd, &pstat) == -1)) goto malloc_err;
+ // Make sure it's not a directory.
if (BC_ERR(S_ISDIR(pstat.st_mode))) {
e = BC_ERR_FATAL_PATH_DIR;
goto malloc_err;
}
+ // Get the size of the file and allocate that much.
size = (size_t) pstat.st_size;
buf = bc_vm_malloc(size + 1);
+ // Read the file. We just bail if a signal interrupts. This is so that users
+ // can interrupt the reading of big files if they want.
r = (size_t) read(fd, buf, size);
if (BC_ERR(r != size)) goto read_err;
+ // Got to have a nul byte.
buf[size] = '\0';
close(fd);
diff --git a/src/vector.c b/src/vector.c
index 731b9fb1..f46665cd 100644
--- a/src/vector.c
+++ b/src/vector.c
@@ -50,8 +50,10 @@ void bc_vec_grow(BcVec *restrict v, size_t n) {
cap = v->cap;
len = v->len + n;
+ // If this is true, we might overflow.
if (len > SIZE_MAX / 2) cap = len;
else {
+ // Keep doubling until larger.
while (cap < len) cap += cap;
}
@@ -64,9 +66,13 @@ void bc_vec_grow(BcVec *restrict v, size_t n) {
}
void bc_vec_init(BcVec *restrict v, size_t esize, BcDtorType dtor) {
+
BC_SIG_ASSERT_LOCKED;
+
assert(v != NULL && esize);
+
v->v = bc_vm_malloc(bc_vm_arraySize(BC_VEC_START_CAP, esize));
+
v->size = (BcSize) esize;
v->cap = BC_VEC_START_CAP;
v->len = 0;
@@ -77,6 +83,7 @@ void bc_vec_expand(BcVec *restrict v, size_t req) {
assert(v != NULL);
+ // Only expand if necessary.
if (v->cap < req) {
sig_atomic_t lock;
@@ -100,9 +107,12 @@ void bc_vec_npop(BcVec *restrict v, size_t n) {
if (!v->dtor) v->len -= n;
else {
+
const BcVecFree d = bc_vec_dtors[v->dtor];
size_t esize = v->size;
size_t len = v->len - n;
+
+ // Loop through and manually destruct every element.
while (v->len > len) d(v->v + (esize * --v->len));
}
@@ -117,6 +127,7 @@ void bc_vec_npopAt(BcVec *restrict v, size_t n, size_t idx) {
assert(v != NULL);
assert(idx + n < v->len);
+ // Grab start and end pointers.
ptr = bc_vec_item(v, idx);
data = bc_vec_item(v, idx + n);
@@ -127,6 +138,7 @@ void bc_vec_npopAt(BcVec *restrict v, size_t n, size_t idx) {
size_t i;
const BcVecFree d = bc_vec_dtors[v->dtor];
+ // Destroy every popped item.
for (i = 0; i < n; ++i) d(bc_vec_item(v, idx + i));
}
@@ -145,10 +157,12 @@ void bc_vec_npush(BcVec *restrict v, size_t n, const void *data) {
BC_SIG_TRYLOCK(lock);
+ // Grow if necessary.
if (v->len + n > v->cap) bc_vec_grow(v, n);
esize = v->size;
+ // Copy the elements in.
memcpy(v->v + (esize * v->len), data, esize * n);
v->len += n;
@@ -168,10 +182,10 @@ void* bc_vec_pushEmpty(BcVec *restrict v) {
BC_SIG_TRYLOCK(lock);
+ // Grow if necessary.
if (v->len + 1 > v->cap) bc_vec_grow(v, 1);
ptr = v->v + v->size * v->len;
-
v->len += 1;
BC_SIG_TRYUNLOCK(lock);
@@ -191,6 +205,7 @@ void bc_vec_pushIndex(BcVec *restrict v, size_t idx) {
assert(v != NULL);
assert(v->size == sizeof(uchar));
+ // Encode the index.
for (amt = 0; idx; ++amt) {
nums[amt + 1] = (uchar) idx;
idx &= ((size_t) ~(UCHAR_MAX));
@@ -199,6 +214,7 @@ void bc_vec_pushIndex(BcVec *restrict v, size_t idx) {
nums[0] = amt;
+ // Push the index onto the vector.
bc_vec_npush(v, amt + 1, nums);
}
@@ -208,12 +224,14 @@ void bc_vec_pushAt(BcVec *restrict v, const void *data, size_t idx) {
BC_SIG_ASSERT_LOCKED;
+ // Do the easy case.
if (idx == v->len) bc_vec_push(v, data);
else {
char *ptr;
size_t esize;
+ // Grow if necessary.
if (v->len == v->cap) bc_vec_grow(v, 1);
esize = v->size;
@@ -221,7 +239,7 @@ void bc_vec_pushAt(BcVec *restrict v, const void *data, size_t idx) {
ptr = v->v + esize * idx;
memmove(ptr + esize, ptr, esize * (v->len++ - idx));
- memmove(ptr, data, esize);
+ memcpy(ptr, data, esize);
}
}
@@ -257,6 +275,7 @@ void bc_vec_concat(BcVec *restrict v, const char *restrict str) {
BC_SIG_TRYLOCK(lock);
+ // If there is already a string, erase its nul byte.
if (v->len) v->len -= 1;
bc_vec_npush(v, strlen(str) + 1, str);
@@ -321,6 +340,16 @@ void bc_vec_free(void *vec) {
}
#if !BC_ENABLE_LIBRARY
+
+/**
+ * Finds a name in a map by binary search. Returns the index where the item
+ * *would* be if it doesn't exist. Callers are responsible for checking that the
+ * item exists at the index.
+ * @param v The map.
+ * @param name The name to find.
+ * @return The index of the item with @a name, or where the item would be
+ * if it does not exist.
+ */
static size_t bc_map_find(const BcVec *restrict v, const char *name) {
size_t low = 0, high = v->len;
@@ -378,8 +407,10 @@ size_t bc_map_index(const BcVec *restrict v, const char *name) {
i = bc_map_find(v, name);
+ // If out of range, return invalid.
if (i >= v->len) return BC_VEC_INVALID_IDX;
+ // Make sure the item exists.
return strcmp(name, ((BcId*) bc_vec_item(v, i))->name) ?
BC_VEC_INVALID_IDX : i;
}
@@ -400,11 +431,24 @@ char* bc_map_name(const BcVec *restrict v, size_t idx) {
}
#endif // DC_ENABLED
+/**
+ * Initializes a single slab.
+ * @param s The slab to initialize.
+ */
static void bc_slab_init(BcSlab *s) {
s->s = bc_vm_malloc(BC_SLAB_SIZE);
s->len = 0;
}
+/**
+ * Adds a string to a slab and returns a pointer to it, or NULL if it could not
+ * be added.
+ * @param s The slab to add to.
+ * @param str The string to add.
+ * @param len The length of the string, including its nul byte.
+ * @return A pointer to the new string in the slab, or NULL if it could not
+ * be added.
+ */
static char* bc_slab_add(BcSlab *s, const char *str, size_t len) {
char *ptr;
@@ -435,6 +479,8 @@ void bc_slabvec_init(BcVec* v) {
assert(v != NULL);
bc_vec_init(v, sizeof(BcSlab), BC_DTOR_SLAB);
+
+ // We always want to have at least one slab.
slab = bc_vec_pushEmpty(v);
bc_slab_init(slab);
}
@@ -454,6 +500,7 @@ char* bc_slabvec_strdup(BcVec *v, const char *str) {
len = strlen(str) + 1;
+ // If the len is greater than 128, then just allocate it with malloc.
if (BC_UNLIKELY(len > 128)) {
size_t idx = v->len - 1;
@@ -462,18 +509,21 @@ char* bc_slabvec_strdup(BcVec *v, const char *str) {
slab.len = SIZE_MAX;
slab.s = bc_vm_strdup(str);
+ // This makes the direct malloc() allocation the second-to-last slab in
+ // the slab vector, thus always keeping a valid slab last.
bc_vec_pushAt(v, &slab, idx);
return slab.s;
}
+ // Add to a slab.
slab_ptr = bc_vec_top(v);
s = bc_slab_add(slab_ptr, str, len);
+ // If it couldn't be added, add a slab and try again.
if (BC_UNLIKELY(s == NULL)) {
slab_ptr = bc_vec_pushEmpty(v);
-
bc_slab_init(slab_ptr);
s = bc_slab_add(slab_ptr, str, len);
@@ -492,21 +542,35 @@ void bc_slabvec_undo(BcVec *v, size_t len) {
s = bc_vec_top(v);
+ // If this is true, there are no allocations in this slab, so we need to
+ // discard it. Well, maybe...
if (s->len == 0) {
+ // The reason this is true is because while undo can *empty* a slab
+ // vector, it should *never* go beyond that. If it does, then the
+ // calling code screwed up.
assert(v->len > 1);
+ // Get the second to last slab.
s = bc_vec_item_rev(v, 1);
+ // If it is a lone allocation, destroy it instead of the last (empty)
+ // slab.
if (s->len == SIZE_MAX) {
bc_vec_npopAt(v, 1, 0);
return;
}
+ // If we reach this point, we know the second-to-last slab is a valid
+ // slab, so we can discard the last slab.
bc_vec_pop(v);
}
+ // Remove the string. The reason we can do this even with the if statement
+ // is that s was updated to the second-to-last slab (now last slab).
s->len -= len;
+
+ assert(s->len == 0 || !s->s[s->len - 1]);
}
void bc_slabvec_clear(BcVec *v) {
@@ -514,19 +578,30 @@ void bc_slabvec_clear(BcVec *v) {
BcSlab *s;
bool again;
+ // This complicated loop exists because of standalone allocations over 128
+ // bytes.
do {
+
+ // Get the first slab.
s = bc_vec_item(v, 0);
+ // Either the slab must be valid (not standalone), or there must be
+ // another slab.
assert(s->len != SIZE_MAX || v->len > 1);
+ // Do we have to loop again? We do if it's a standalone allocation.
again = (s->len == SIZE_MAX);
+ // Pop the standalone allocation, not the one after it.
if (again) bc_vec_npopAt(v, 1, 0);
} while(again);
+ // If we get here, we know that the first slab is a valid slab. We want to
+ // pop all of the other slabs.
if (v->len > 1) bc_vec_npop(v, v->len - 1);
+ // Empty the first slab.
s->len = 0;
}
#endif // !BC_ENABLE_LIBRARY
diff --git a/src/vm.c b/src/vm.c
index e717981b..0ca9f853 100644
--- a/src/vm.c
+++ b/src/vm.c
@@ -390,7 +390,7 @@ static void bc_vm_setenvFlag(const char* const var, int def, uint16_t flag) {
/**
* Parses the arguments in {B,D]C_ENV_ARGS.
- * @param env_arg_name The environment variable to use.
+ * @param env_args_name The environment variable to use.
*/
static void bc_vm_envArgs(const char* const env_args_name) {
@@ -1400,12 +1400,13 @@ void bc_vm_init(void) {
BC_SIG_ASSERT_LOCKED;
#if !BC_ENABLE_LIBRARY
- // Set up the constant BcNum's.
+ // Set up the constant zero.
bc_num_setup(&vm.zero, vm.zero_num, BC_VM_ONE_CAP);
+#endif // !BC_ENABLE_LIBRARY
+ // Set up more constant BcNum's.
bc_num_setup(&vm.one, vm.one_num, BC_VM_ONE_CAP);
bc_num_one(&vm.one);
-#endif // !BC_ENABLE_LIBRARY
// Set up more constant BcNum's.
memcpy(vm.max_num, bc_num_bigdigMax,
diff --git a/tests/all.sh b/tests/all.sh
index c75d55f4..973f03b9 100755
--- a/tests/all.sh
+++ b/tests/all.sh
@@ -34,6 +34,7 @@ testdir=$(dirname "$script")
. "$testdir/../scripts/functions.sh"
+# Command-line processing.
if [ "$#" -ge 1 ]; then
d="$1"
shift
@@ -79,23 +80,28 @@ fi
stars="***********************************************************************"
printf '%s\n' "$stars"
+# Set stuff for the correct calculator.
if [ "$d" = "bc" ]; then
halt="quit"
else
halt="q"
fi
+# I use these, so unset them to make the tests work.
unset BC_ENV_ARGS
unset BC_LINE_LENGTH
unset DC_ENV_ARGS
unset DC_LINE_LENGTH
+# Get the list of tests that require extra math.
extra_required=$(cat "$testdir/extra_required.txt")
printf '\nRunning %s tests...\n\n' "$d"
+# Run the tests one at a time.
while read t; do
+ # If it requires extra, then skip if we don't have it.
if [ "$extra" -eq 0 ]; then
if [ -z "${extra_required##*$t*}" ]; then
printf 'Skipping %s %s\n' "$d" "$t"
@@ -107,13 +113,20 @@ while read t; do
done < "$testdir/$d/all.txt"
+# stdin tests.
sh "$testdir/stdin.sh" "$d" "$exe" "$@"
+# Script tests.
sh "$testdir/scripts.sh" "$d" "$extra" "$run_stack_tests" "$generate_tests" \
"$time_tests" "$exe" "$@"
+
+# Read tests.
sh "$testdir/read.sh" "$d" "$exe" "$@"
+
+# Error tests.
sh "$testdir/errors.sh" "$d" "$exe" "$@"
+# Other tests.
sh "$testdir/other.sh" "$d" "$exe" "$@"
printf '\nAll %s tests passed.\n' "$d"
diff --git a/tests/bc/timeconst.sh b/tests/bc/timeconst.sh
index 8cdc63a9..5c5ec380 100755
--- a/tests/bc/timeconst.sh
+++ b/tests/bc/timeconst.sh
@@ -32,6 +32,7 @@
script="$0"
testdir=$(dirname "$script")
+# Gets the timeconst script, which could be a command-line argument.
if [ "$#" -gt 0 ]; then
timeconst="$1"
shift
@@ -39,6 +40,7 @@ else
timeconst="$testdir/scripts/timeconst.bc"
fi
+# Gets the executable, which could also be a command-line argument.
if [ "$#" -gt 0 ]; then
bc="$1"
shift
@@ -46,17 +48,27 @@ else
bc="$testdir/../../bin/bc"
fi
-out1="$testdir/../.log_bc_timeconst.txt"
-out2="$testdir/../.log_bc_timeconst_test.txt"
+#
+out1="$testdir/bc_outputs/bc_timeconst.txt"
+out2="$testdir/bc_outputs/bc_timeconst_results.txt"
+
+outdir=$(dirname "$out1")
+
+# Make sure the directory exists.
+if [ ! -d "$outdir" ]; then
+ mkdir -p "$outdir"
+fi
base=$(basename "$timeconst")
+# If the script does not exist, just skip. Running this test is not necessary.
if [ ! -f "$timeconst" ]; then
printf 'Warning: %s does not exist\n' "$timeconst"
printf 'Skipping...\n'
exit 0
fi
+# I use these, so unset them to make the tests work.
unset BC_ENV_ARGS
unset BC_LINE_LENGTH
unset DC_ENV_ARGS
@@ -64,25 +76,31 @@ unset DC_LINE_LENGTH
printf 'Running %s...' "$base"
+# Get a list of numbers. Funny how bc can help with that.
nums=$(printf 'for (i = 0; i <= 1000; ++i) { i }\n' | bc)
+# Run each number through the script.
for i in $nums; do
+ # Run the GNU bc on the test.
printf '%s\n' "$i" | bc -q "$timeconst" > "$out1"
err="$?"
+ # If the other bc failed, it's not GNU bc, or this bc.
if [ "$err" -ne 0 ]; then
printf '\nOther bc is not GNU compatible. Skipping...\n'
exit 0
fi
+ # Run the built bc on the test.
printf '%s\n' "$i" | "$bc" "$@" -q "$timeconst" > "$out2"
diff "$out1" "$out2"
error="$?"
+ # If fail, bail.
if [ "$error" -ne 0 ]; then
printf '\nFailed on input: %s\n' "$i"
exit "$error"
diff --git a/tests/errors.sh b/tests/errors.sh
index a3dba0cb..6d68b422 100755
--- a/tests/errors.sh
+++ b/tests/errors.sh
@@ -34,6 +34,7 @@ testdir=$(dirname "$script")
. "$testdir/../scripts/functions.sh"
+# Command-line processing.
if [ "$#" -eq 0 ]; then
printf 'usage: %s dir [exec args...]\n' "$script"
exit 1
@@ -49,6 +50,7 @@ else
shift
fi
+# I use these, so unset them to make the tests work.
unset BC_ENV_ARGS
unset BC_LINE_LENGTH
unset DC_ENV_ARGS
@@ -57,15 +59,18 @@ unset DC_LINE_LENGTH
out="$testdir/${d}_outputs/errors_results.txt"
outdir=$(dirname "$out")
+# Make sure the directory exists.
if [ ! -d "$outdir" ]; then
mkdir -p "$outdir"
fi
exebase=$(basename "$exe")
+# These are the filenames for the extra tests.
posix="posix_errors"
read_errors="read_errors"
+# Set stuff for the correct calculator.
if [ "$d" = "bc" ]; then
opts="-l"
halt="halt"
@@ -90,6 +95,7 @@ checkerrtest "$d" "$err" "command-line -f test" "$out" "$exebase"
printf 'pass\n'
+# Now test the error files in the standard tests directory.
for testfile in $testdir/$d/*errors.txt; do
if [ -z "${testfile##*$read_errors*}" ]; then
@@ -97,8 +103,10 @@ for testfile in $testdir/$d/*errors.txt; do
continue
fi
+ # Test bc POSIX errors and warnings.
if [ -z "${testfile##*$posix*}" ]; then
+ # Just test warnings.
line="last"
printf '%s\n' "$line" | "$exe" "$@" "-lw" 2> "$out" > /dev/null
err="$?"
@@ -109,15 +117,20 @@ for testfile in $testdir/$d/*errors.txt; do
checkerrtest "$d" "1" "$line" "$out" "$exebase"
+ # Set the options for standard mode.
options="-ls"
+
else
options="$opts"
fi
+ # Output something pretty.
base=$(basename "$testfile")
base="${base%.*}"
printf 'Running %s %s...' "$d" "$base"
+ # Test errors on each line of the file. Yes, each line has a separate error
+ # case.
while read -r line; do
rm -f "$out"
@@ -133,6 +146,9 @@ for testfile in $testdir/$d/*errors.txt; do
done
+# Test all the files in the errors directory. While the loop above does one test
+# for every line, this does one test per file, but it runs the file through
+# stdin and as a file on the command-line.
for testfile in $testdir/$d/errors/*.txt; do
printf 'Running %s error file %s...' "$d" "$testfile"
diff --git a/tests/other.sh b/tests/other.sh
index 28126c3d..0de12868 100755
--- a/tests/other.sh
+++ b/tests/other.sh
@@ -34,6 +34,7 @@ testdir=$(dirname "$script")
. "$testdir/../scripts/functions.sh"
+# Command-line processing.
if [ "$#" -ge 1 ]; then
d="$1"
shift
@@ -54,11 +55,13 @@ else
halt="q"
fi
+# For tests later.
num=100000000000000000000000000000000000000000000000000000000000000000000000000000
numres="$num"
num70="10000000000000000000000000000000000000000000000000000000000000000000\\
0000000000"
+# Set stuff for the correct calculator.
if [ "$d" = "bc" ]; then
halt="halt"
opt="x"
@@ -80,6 +83,7 @@ printf '%s\n' "$halt" | "$exe" "$@" > /dev/null 2>&1
checktest_retcode "$d" "$?" "quit"
+# bc has two halt or quit commands, so test the second as well.
if [ "$d" = bc ]; then
printf '%s\n' "quit" | "$exe" "$@" > /dev/null 2>&1
@@ -99,10 +103,6 @@ printf 'pass\n'
base=$(basename "$exe")
-if [ "$base" != "bc" -a "$base" != "dc" ]; then
- exit 0
-fi
-
printf 'Running %s environment var tests...' "$d"
if [ "$d" = "bc" ]; then
@@ -138,6 +138,9 @@ else
set +e
+ # dc has an extra test for a case that someone found running this easter.dc
+ # script. It went into an infinite loop, so we want to check that we did not
+ # regress.
printf 'three\n' | cut -c1-3 > /dev/null
err=$?
diff --git a/tests/read.sh b/tests/read.sh
index 6eb07ee2..6348d4e5 100755
--- a/tests/read.sh
+++ b/tests/read.sh
@@ -34,6 +34,7 @@ testdir=$(dirname "$script")
. "$testdir/../scripts/functions.sh"
+# Command-line processing.
if [ "$#" -lt 1 ]; then
printf 'usage: %s dir [exe [args...]]\n' "$0"
printf 'valid dirs are:\n'
@@ -60,24 +61,22 @@ errors="$testdir/$d/read_errors.txt"
out="$testdir/${d}_outputs/read_results.txt"
outdir=$(dirname "$out")
+# Make sure the directory exists.
if [ ! -d "$outdir" ]; then
mkdir -p "$outdir"
fi
exebase=$(basename "$exe")
+# Set stuff for the correct calculator.
if [ "$d" = "bc" ]; then
options="-lq"
halt="halt"
-else
- options="-x"
- halt="q"
-fi
-
-if [ "$d" = "bc" ]; then
read_call="read()"
read_expr="${read_call}\n5+5;"
else
+ options="-x"
+ halt="q"
read_call="?"
read_expr="${read_call}"
fi
@@ -86,6 +85,7 @@ printf 'Running %s read...' "$d"
set +e
+# Run read() on every line.
while read line; do
printf '%s\n%s\n' "$read_call" "$line" | "$exe" "$@" "$options" > "$out"
@@ -97,6 +97,7 @@ printf 'pass\n'
printf 'Running %s read errors...' "$d"
+# Run read on every line.
while read line; do
printf '%s\n%s\n' "$read_call" "$line" | "$exe" "$@" "$options" 2> "$out" > /dev/null
diff --git a/tests/script.sh b/tests/script.sh
index 4fbb2cc8..84254ba0 100755
--- a/tests/script.sh
+++ b/tests/script.sh
@@ -35,6 +35,7 @@ testdir=$(dirname "${script}")
. "$testdir/../scripts/functions.sh"
+# Command-line processing.
if [ "$#" -lt 2 ]; then
printf 'usage: %s dir script [run_extra_tests] [run_stack_tests] [generate_tests] [time_tests] [exec args...]\n' "$script"
exit 1
@@ -81,6 +82,7 @@ else
exe="$testdir/../bin/$d"
fi
+# Set stuff for the correct calculator.
if [ "$d" = "bc" ]; then
if [ "$run_stack_tests" -ne 0 ]; then
@@ -100,10 +102,12 @@ scriptdir="$testdir/$d/scripts"
name="${f%.*}"
+# We specifically want to skip this because it is handled specially.
if [ "$f" = "timeconst.bc" ]; then
exit 0
fi
+# Skip the tests that require extra math if we don't have it.
if [ "$run_extra_tests" -eq 0 ]; then
if [ "$f" = "rand.bc" ]; then
printf 'Skipping %s script: %s\n' "$d" "$f"
@@ -111,6 +115,8 @@ if [ "$run_extra_tests" -eq 0 ]; then
fi
fi
+# Skip the tests that require global stacks flag if we are not allowed to run
+# them.
if [ "$run_stack_tests" -eq 0 ]; then
if [ "$f" = "globals.bc" -o "$f" = "references.bc" -o "$f" = "rand.bc" ]; then
@@ -123,10 +129,12 @@ fi
out="$testdir/${d}_outputs/${name}_script_results.txt"
outdir=$(dirname "$out")
+# Make sure the directory exists.
if [ ! -d "$outdir" ]; then
mkdir -p "$outdir"
fi
+# I use these, so unset them to make the tests work.
unset BC_ENV_ARGS
unset BC_LINE_LENGTH
unset DC_ENV_ARGS
@@ -160,6 +168,7 @@ set +e
printf 'Running %s script %s...' "$d" "$f"
+# Yes this is poor timing, but it works.
if [ "$time_tests" -ne 0 ]; then
printf '\n'
printf '%s\n' "$halt" | /usr/bin/time -p "$exe" "$@" $options "$s" > "$out"
diff --git a/tests/scripts.sh b/tests/scripts.sh
index a0415ec7..30fb42f1 100755
--- a/tests/scripts.sh
+++ b/tests/scripts.sh
@@ -33,6 +33,7 @@ script="$0"
testdir=$(dirname "${script}")
+# Command-line processing.
if [ "$#" -eq 0 ]; then
printf 'usage: %s dir [run_extra_tests] [run_stack_tests] [generate_tests] [time_tests] [exec args...]\n' "$script"
exit 1
@@ -80,6 +81,7 @@ scriptdir="$testdir/$d/scripts"
scripts=$(cat "$scriptdir/all.txt")
+# Run each script test individually.
for s in $scripts; do
f=$(basename "$s")
diff --git a/tests/stdin.sh b/tests/stdin.sh
index 6184a3ed..76d8e058 100755
--- a/tests/stdin.sh
+++ b/tests/stdin.sh
@@ -35,6 +35,7 @@ testdir=$(dirname "$script")
. "$testdir/../scripts/functions.sh"
+# Command-line processing.
if [ "$#" -lt 1 ]; then
printf 'usage: %s dir [exe [args...]]\n' "$0"
printf 'valid dirs are:\n'
@@ -57,10 +58,12 @@ fi
out="$testdir/${d}_outputs/stdin_results.txt"
outdir=$(dirname "$out")
+# Make sure the directory exists.
if [ ! -d "$outdir" ]; then
mkdir -p "$outdir"
fi
+# Set stuff for the correct calculator.
if [ "$d" = "bc" ]; then
options="-lq"
else
@@ -73,9 +76,11 @@ set +e
printf 'Running %s stdin tests...' "$d"
+# Run the file through stdin.
cat "$testdir/$d/stdin.txt" | "$exe" "$@" "$options" > "$out" 2> /dev/null
checktest "$d" "$?" "stdin" "$testdir/$d/stdin_results.txt" "$out"
+# bc has some more tests; run those.
if [ "$d" = "bc" ]; then
cat "$testdir/$d/stdin1.txt" | "$exe" "$@" "$options" > "$out" 2> /dev/null
diff --git a/tests/test.sh b/tests/test.sh
index 4583a766..ec7f6ba9 100755
--- a/tests/test.sh
+++ b/tests/test.sh
@@ -35,6 +35,7 @@ testdir=$(dirname "$script")
. "$testdir/../scripts/functions.sh"
+# Command-line processing.
if [ "$#" -lt 2 ]; then
printf 'usage: %s dir test [generate_tests] [time_tests] [exe [args...]]\n' "$0"
printf 'valid dirs are:\n'
@@ -76,15 +77,18 @@ fi
out="$testdir/${d}_outputs/${t}_results.txt"
outdir=$(dirname "$out")
+# Make sure the directory exists.
if [ ! -d "$outdir" ]; then
mkdir -p "$outdir"
fi
+# I use these, so unset them to make the tests work.
unset BC_ENV_ARGS
unset BC_LINE_LENGTH
unset DC_ENV_ARGS
unset DC_LINE_LENGTH
+# Set stuff for the correct calculator.
if [ "$d" = "bc" ]; then
options="-lq"
var="BC_LINE_LENGTH"
@@ -95,24 +99,29 @@ else
halt="q"
fi
+# If the test does not exist...
if [ ! -f "$name" ]; then
+ # Skip if we can't generate.
if [ "$generate_tests" -eq 0 ]; then
printf 'Skipping %s %s test\n' "$d" "$t"
exit 0
fi
+ # Generate.
printf 'Generating %s %s...' "$d" "$t"
"$d" "$testdir/$d/scripts/$t.$d" > "$name"
printf 'done\n'
fi
+# If the results do not exist, generate..
if [ ! -f "$results" ]; then
printf 'Generating %s %s results...' "$d" "$t"
printf '%s\n' "$halt" | "$d" $options "$name" > "$results"
printf 'done\n'
fi
+# We set this here because GNU dc does not have it.
if [ "$d" = "dc" ]; then
options="-x"
fi