diff options
-rw-r--r-- | include/args.h | 4 | ||||
-rw-r--r-- | include/bc.h | 42 | ||||
-rw-r--r-- | include/lex.h | 13 | ||||
-rw-r--r-- | include/library.h | 2 | ||||
-rw-r--r-- | include/rand.h | 14 | ||||
-rw-r--r-- | include/vm.h | 16 | ||||
-rw-r--r-- | manuals/development.md | 14 | ||||
-rwxr-xr-x | scripts/benchmark.sh | 7 | ||||
-rw-r--r-- | src/args.c | 16 | ||||
-rw-r--r-- | src/bc.c | 2 | ||||
-rw-r--r-- | src/bc_lex.c | 5 | ||||
-rw-r--r-- | src/bc_parse.c | 2 | ||||
-rw-r--r-- | src/data.c | 148 | ||||
-rw-r--r-- | src/dc.c | 2 | ||||
-rw-r--r-- | src/dc_lex.c | 15 | ||||
-rw-r--r-- | src/dc_parse.c | 69 | ||||
-rw-r--r-- | src/file.c | 61 | ||||
-rw-r--r-- | src/history.c | 12 | ||||
-rw-r--r-- | src/lang.c | 41 | ||||
-rw-r--r-- | src/lex.c | 54 | ||||
-rw-r--r-- | src/library.c | 142 | ||||
-rw-r--r-- | src/main.c | 9 | ||||
-rw-r--r-- | src/num.c | 6 | ||||
-rw-r--r-- | src/opt.c | 76 | ||||
-rw-r--r-- | src/parse.c | 37 | ||||
-rw-r--r-- | src/rand.c | 167 | ||||
-rw-r--r-- | src/read.c | 34 | ||||
-rw-r--r-- | src/vector.c | 81 | ||||
-rw-r--r-- | src/vm.c | 7 | ||||
-rwxr-xr-x | tests/all.sh | 13 | ||||
-rwxr-xr-x | tests/bc/timeconst.sh | 22 | ||||
-rwxr-xr-x | tests/errors.sh | 16 | ||||
-rwxr-xr-x | tests/other.sh | 11 | ||||
-rwxr-xr-x | tests/read.sh | 13 | ||||
-rwxr-xr-x | tests/script.sh | 9 | ||||
-rwxr-xr-x | tests/scripts.sh | 2 | ||||
-rwxr-xr-x | tests/stdin.sh | 5 | ||||
-rwxr-xr-x | tests/test.sh | 9 |
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 @@ -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; } @@ -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); @@ -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 @@ -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 @@ -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) { @@ -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 } @@ -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; @@ -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); } @@ -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); @@ -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); } @@ -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 @@ -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 @@ -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 |