aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorGavin Howard <gavin@yzena.com>2021-06-24 10:49:00 -0600
committerGavin Howard <gavin@yzena.com>2021-06-24 10:49:00 -0600
commit6110b6c977dbca97a5d3b084503705b544de3604 (patch)
tree09ad90cd135c6935cd8ddd7d02bbc025a764d947
parent29e4d559c2bcfb8168ad1f124aa2758e7697c5d1 (diff)
downloadplatform_external_bc-6110b6c977dbca97a5d3b084503705b544de3604.tar.gz
platform_external_bc-6110b6c977dbca97a5d3b084503705b544de3604.tar.bz2
platform_external_bc-6110b6c977dbca97a5d3b084503705b544de3604.zip
More documentation and cleanup work
Signed-off-by: Gavin Howard <gavin@yzena.com>
-rw-r--r--NEWS.md21
-rwxr-xr-xconfigure.sh6
-rw-r--r--include/args.h4
-rw-r--r--include/file.h79
-rw-r--r--include/history.h47
-rw-r--r--include/opt.h1
-rw-r--r--include/read.h29
-rw-r--r--include/version.h3
-rw-r--r--manuals/bc.1.md.in16
-rw-r--r--manuals/dc.1.md.in44
-rw-r--r--manuals/development.md25
-rw-r--r--src/args.c38
-rw-r--r--src/bc.c5
-rw-r--r--src/data.c26
-rw-r--r--src/dc.c5
-rw-r--r--src/dc_lex.c48
-rw-r--r--src/history.c2
-rw-r--r--src/read.c26
-rw-r--r--src/vm.c4
19 files changed, 371 insertions, 58 deletions
diff --git a/NEWS.md b/NEWS.md
index 648d0a0b..a67b0cb4 100644
--- a/NEWS.md
+++ b/NEWS.md
@@ -1,12 +1,12 @@
# News
-## 4.1.0
+## 5.0.0
This is a production release with several changes:
* Add support for OpenBSD's `pledge()` and `unveil()`.
* Fix print bug where a backslash newline combo was printed even if only one
- digit was left, something I blindly did as well, like a fool.
+ digit was left, something I blindly copied from GNU `bc`, like a fool.
* Fix bugs in the manuals.
* Fix a possible multiplication overflow in power.
* Temporary numbers are garbage collected if allocation fails, and the
@@ -15,6 +15,23 @@ This is a production release with several changes:
* `bc` and `dc` now do not give fatal errors when trying to allocate more space
for temporary numbers. Instead, more temporary numbers are just freed.
* The number of temporary numbers is now limited.
+* Allow integers with non-zero `scale` to be used with power, places, and shift
+ operators.
+* Added greatest common divisor and least common multiple to `lib2.bc`.
+* Added `SIGQUIT` handling to history.
+* Added a command to `dc` (`y`) to get the length of register stacks.
+* Fixed multi-digit bugs in `lib2.bc`.
+* Removed the no prompt build option.
+* Created settings that builders can set defaults for and users can set their
+ preferences for. This includes the `bc` banner, resetting on `SIGINT`, TTY
+ mode, and prompt.
+* Added history support to Windows.
+* Made using `[` as a register name an error because it just cannot easily be
+ checked for without doing weird things on `stdin`.
+* Clarify the above situation in the manuals, since it can't really be avoided
+ with the terrible interaction of REPL and language constraints.
+* Add a new error type and message for `dc` when register stacks don't have
+ enough items.
## 4.0.2
diff --git a/configure.sh b/configure.sh
index f1de8a63..c146b84d 100755
--- a/configure.sh
+++ b/configure.sh
@@ -891,6 +891,10 @@ elif [ -z "${HOSTCFLAGS+set}" ]; then
HOSTCFLAGS="$HOST_CFLAGS"
fi
+# Store these for the cross compilation detection later.
+OLDCFLAGS="$CFLAGS"
+OLDHOSTCFLAGS="$HOSTCFLAGS"
+
link="@printf 'No link necessary\\\\n'"
main_exec="BC"
executable="BC_EXEC"
@@ -1179,7 +1183,7 @@ if [ "$nls" -ne 0 ]; then
# It turns out that POSIX locales are really terrible, and running
# gencat on one machine is not guaranteed to make those cat files
# portable to another machine, so we had better warn the user here.
- if [ "$HOSTCC" != "$CC" ] || [ "$HOSTCFLAGS" != "$CFLAGS" ]; then
+ if [ "$HOSTCC" != "$CC" ] || [ "$OLDHOSTCFLAGS" != "$OLDCFLAGS" ]; then
printf 'Cross-compile detected.\n\n'
printf 'WARNING: Catalog files generated with gencat may not be portable\n'
printf ' across different architectures.\n\n'
diff --git a/include/args.h b/include/args.h
index 7aef80a9..6db7ff5e 100644
--- a/include/args.h
+++ b/include/args.h
@@ -37,6 +37,7 @@
#define BC_ARGS_H
#include <status.h>
+#include <opt.h>
#include <vm.h>
/**
@@ -48,4 +49,7 @@
*/
void bc_args(int argc, char *argv[], bool exit_exprs);
+// A reference to the list of long options.
+extern const BcOptLong bc_args_lopt[];
+
#endif // BC_ARGS_H
diff --git a/include/file.h b/include/file.h
index fbd4b9b9..db087d68 100644
--- a/include/file.h
+++ b/include/file.h
@@ -52,38 +52,117 @@ typedef struct BcFile {
} BcFile;
#if BC_ENABLE_HISTORY
+
+/// Types of flushing. These are important because of history and printing
+/// strings without newlines, something that users could use as their own
+/// prompts.
typedef enum BcFlushType {
+ /// Do not clear the stored partial line, but don't add to it.
BC_FLUSH_NO_EXTRAS_NO_CLEAR,
+
+ /// Do not clear the stored partial line and add to it.
BC_FLUSH_SAVE_EXTRAS_NO_CLEAR,
+
+ /// Clear the stored partial line and do not save the new stuff either.
BC_FLUSH_NO_EXTRAS_CLEAR,
+
+ /// Clear the stored partial line, but save the new stuff.
BC_FLUSH_SAVE_EXTRAS_CLEAR,
} BcFlushType;
+
#else // BC_ENABLE_HISTORY
+
+// These make sure that the BcFlushType parameter disappears if history is not
+// used.
+
#define bc_file_putchar(f, t, c) bc_file_putchar(f, c)
#define bc_file_flushErr(f, t) bc_file_flushErr(f)
#define bc_file_flush(f, t) bc_file_flush(f)
#define bc_file_write(f, t, b, n) bc_file_write(f, b, n)
#define bc_file_puts(f, t, s) bc_file_puts(f, s)
+
#endif // BC_ENABLE_HISTORY
+/**
+ * Initialize a file.
+ * @param f The file to initialize.
+ * @param fd The file descriptor.
+ * @param buf The buffer for the file.
+ * @param cap The capacity of the buffer.
+ */
void bc_file_init(BcFile *f, int fd, char *buf, size_t cap);
+
+/**
+ * Frees a file, including flushing it.
+ * @param f The file to free.
+ */
void bc_file_free(BcFile *f);
+/**
+ * Print a char into the file.
+ * @param f The file to print to.
+ * @param type The flush type.
+ * @param c The character to write.
+ */
void bc_file_putchar(BcFile *restrict f, BcFlushType type, uchar c);
+
+/**
+ * Flush and return an error if it failed. This is meant to be used when needing
+ * to flush in error situations when an error is already in flight. It would be
+ * a very bad deal to throw another error.
+ * @param f The file to flush.
+ * @param type The flush type.
+ */
BcStatus bc_file_flushErr(BcFile *restrict f, BcFlushType type);
+
+/**
+ * Flush and throw an error on failure.
+ * @param f The file to flush.
+ * @param type The flush type.
+ */
void bc_file_flush(BcFile *restrict f, BcFlushType type);
+
+/**
+ * Write the contents of buf to the file.
+ * @param f The file to flush.
+ * @param type The flush type.
+ * @param buf The buffer whose contents will be written to the file.
+ * @param n The length of buf.
+ */
void bc_file_write(BcFile *restrict f, BcFlushType type,
const char *buf, size_t n);
+
+/**
+ * Write to the file like fprintf would. This is very rudimentary.
+ * @param f The file to flush.
+ * @param fmt The format string.
+ */
void bc_file_printf(BcFile *restrict f, const char *fmt, ...);
+
+/**
+ * Write to the file like vfprintf would. This is very rudimentary.
+ * @param f The file to flush.
+ * @param fmt The format string.
+ */
void bc_file_vprintf(BcFile *restrict f, const char *fmt, va_list args);
+
+/**
+ * Write str to the file.
+ * @param f The file to flush.
+ * @param type The flush type.
+ * @param str The string to write to the file.
+ */
void bc_file_puts(BcFile *restrict f, BcFlushType type, const char *str);
#if BC_ENABLE_HISTORY
+
+// Some constant flush types for ease of use.
extern const BcFlushType bc_flush_none;
extern const BcFlushType bc_flush_err;
extern const BcFlushType bc_flush_save;
+
#endif // BC_ENABLE_HISTORY
#endif // BC_FILE_H
diff --git a/include/history.h b/include/history.h
index 15a28e06..b0a2ab94 100644
--- a/include/history.h
+++ b/include/history.h
@@ -122,19 +122,31 @@
#include <file.h>
#endif // BC_DEBUG_CODE
+/// Default columns.
#define BC_HIST_DEF_COLS (80)
+
+/// Max number of history entries.
#define BC_HIST_MAX_LEN (128)
+
+/// Max length of a line.
#define BC_HIST_MAX_LINE (4095)
+
+/// Max size for cursor position buffer.
#define BC_HIST_SEQ_SIZE (64)
+/// The number of entries in the history.
#define BC_HIST_BUF_LEN(h) ((h)->buf.len - 1)
+
+/// Read n characters into s and check the error.
#define BC_HIST_READ(s, n) (bc_history_read((s), (n)) == -1)
+/// Markers for direction when using arrow keys.
#define BC_HIST_NEXT (false)
#define BC_HIST_PREV (true)
#if BC_DEBUG_CODE
+// These are just for debugging.
#define BC_HISTORY_DEBUG_BUF_SIZE (1024)
#define lndebug(...) \
@@ -157,6 +169,7 @@
#define lndebug(fmt, ...)
#endif // BC_DEBUG_CODE
+/// An enum of useful actions.
typedef enum BcHistoryAction {
BC_ACTION_NULL = 0,
@@ -246,23 +259,55 @@ typedef struct BcHistory {
} BcHistory;
+/**
+ * Get a line from stdin using history. This returns a status because I don't
+ * want to throw errors while the terminal is in raw mode.
+ * @param h The history data.
+ * @param vec A vector to put the line into.
+ * @param prompt The prompt to display, if desired.
+ */
BcStatus bc_history_line(BcHistory *h, BcVec *vec, const char *prompt);
+/**
+ * Initialize history data.
+ * @param h The struct to initialize.
+ */
void bc_history_init(BcHistory *h);
+
+/**
+ * Free history data (and recook the terminal).
+ * @param h The struct to free.
+ */
void bc_history_free(BcHistory *h);
+// A list of terminals that don't work.
extern const char *bc_history_bad_terms[];
+
+// A tab in history and its length.
extern const char bc_history_tab[];
extern const size_t bc_history_tab_len;
+
+// A ctrl+c string.
extern const char bc_history_ctrlc[];
+
+// UTF-8 data arrays.
extern const uint32_t bc_history_wchars[][2];
extern const size_t bc_history_wchars_len;
extern const uint32_t bc_history_combo_chars[];
extern const size_t bc_history_combo_chars_len;
+
#if BC_DEBUG_CODE
+
+// Debug data.
extern BcFile bc_history_debug_fp;
extern char *bc_history_debug_buf;
-void bc_history_printKeyCodes(BcHistory* l);
+
+/**
+ * A function to print keycodes for debugging.
+ * @param h The history data.
+ */
+void bc_history_printKeyCodes(BcHistory* h);
+
#endif // BC_DEBUG_CODE
#endif // BC_ENABLE_HISTORY
diff --git a/include/opt.h b/include/opt.h
index e828a92c..e14b8431 100644
--- a/include/opt.h
+++ b/include/opt.h
@@ -41,6 +41,7 @@
#define BC_OPT_H
#include <stdbool.h>
+#include <stdlib.h>
typedef struct BcOpt {
char **argv;
diff --git a/include/read.h b/include/read.h
index 102cc5cd..007dcaef 100644
--- a/include/read.h
+++ b/include/read.h
@@ -41,11 +41,38 @@
#include <status.h>
#include <vector.h>
+/// Returns true if c is a non-ASCII char.
#define BC_READ_BIN_CHAR(c) (((c) < ' ' && !isspace((c))) || ((uchar) c) > '~')
+/**
+ * Reads a line from stdin after printing prompt, if desired.
+ * @param vec The vector to put the stdin data into.
+ * @param prompt The prompt to print, if desired.
+ */
BcStatus bc_read_line(BcVec *vec, const char *prompt);
-void bc_read_file(const char *path, char **buf);
+
+/**
+ * Read a file and return a buffer with the data. The buffer must be freed by
+ * the caller.
+ * @param path The path to the file to read.
+ */
+char* bc_read_file(const char *path);
+
+/**
+ * Helper function for reading characters from stdin. This takes care of a bunch
+ * of complex error handling. Thus, it returns a status instead of throwing an
+ * error, except for fatal errors.
+ * @param vec The vec to put the stdin into.
+ * @param prompt The prompt to print, if desired.
+ */
BcStatus bc_read_chars(BcVec *vec, const char *prompt);
+
+/**
+ * Read a line from buf into vec.
+ * @param vec The vector to read data into.
+ * @param buf The buffer to read from.
+ * @param buf_len The length of the buffer.
+ */
bool bc_read_buf(BcVec *vec, char *buf, size_t *buf_len);
#endif // BC_READ_H
diff --git a/include/version.h b/include/version.h
index dfbb26e6..e29bbf69 100644
--- a/include/version.h
+++ b/include/version.h
@@ -36,6 +36,7 @@
#ifndef BC_VERSION_H
#define BC_VERSION_H
-#define VERSION 4.1.0
+// The current version.
+#define VERSION 5.0.0
#endif // BC_VERSION_H
diff --git a/manuals/bc.1.md.in b/manuals/bc.1.md.in
index 94484a4a..bb710680 100644
--- a/manuals/bc.1.md.in
+++ b/manuals/bc.1.md.in
@@ -264,6 +264,22 @@ The following are the options that bc(1) accepts.
All long options are **non-portable extensions**.
+# STDIN
+
+If no files or expressions are given by the **-f**, **-\-file**, **-e**, or
+**-\-expression** options, then bc(1) read from **stdin**.
+
+However, there are a few caveats to this.
+
+First, **stdin** is evaluated a line at a time. The only exception to this is if
+the parse cannot complete. That means that starting a string without ending it
+or starting a function, **if** statement, or loop without ending it will also
+cause bc(1) to not execute.
+
+Second, after an **if** statement, bc(1) doesn't know if an **else** statement
+will follow, so it will not execute until it knows there will not be an **else**
+statement.
+
# STDOUT
Any non-error output is written to **stdout**. In addition, if history (see the
diff --git a/manuals/dc.1.md.in b/manuals/dc.1.md.in
index f47d7c59..6916adb9 100644
--- a/manuals/dc.1.md.in
+++ b/manuals/dc.1.md.in
@@ -42,18 +42,14 @@ dc(1) is an arbitrary-precision calculator. It uses a stack (reverse Polish
notation) to store numbers and results of computations. Arithmetic operations
pop arguments off of the stack and push the results.
-If no files are given on the command-line as extra arguments (i.e., not as
-**-f** or **-\-file** arguments), then dc(1) reads from **stdin**. Otherwise,
-those files are processed, and dc(1) will then exit.
-
-This is different from the dc(1) on OpenBSD and possibly other dc(1)
-implementations, where **-e** (**-\-expression**) and **-f** (**-\-file**)
-arguments cause dc(1) to execute them and exit. The reason for this is that this
-dc(1) allows users to set arguments in the environment variable **DC_ENV_ARGS**
-(see the **ENVIRONMENT VARIABLES** section). Any expressions given on the
-command-line should be used to set up a standard environment. For example, if a
-user wants the **scale** always set to **10**, they can set **DC_ENV_ARGS** to
-**-e 10k**, and this dc(1) will always start with a **scale** of **10**.
+If no files are given on the command-line, then dc(1) reads from **stdin** (see
+the **STDIN** section). Otherwise, those files are processed, and dc(1) will
+then exit.
+
+If a user wants to set up a standard environment, they can use **DC_ENV_ARGS**
+(see the **ENVIRONMENT VARIABLES** section). For example, if a user wants the
+**scale** always set to **10**, they can set **DC_ENV_ARGS** to **-e 10k**, and
+this dc(1) will always start with a **scale** of **10**.
# OPTIONS
@@ -137,6 +133,25 @@ The following are the options that dc(1) accepts.
All long options are **non-portable extensions**.
+# STDIN
+
+If no files are given on the command-line and no files or expressions are given
+by the **-f**, **-\-file**, **-e**, or **-\-expression** options, then dc(1)
+read from **stdin**.
+
+However, there are a few caveats to this.
+
+First, **stdin** is evaluated a line at a time. The only exception to this is if
+a string has been finished, but not ended. This means that, except for escaped
+brackets, all brackets must be balanced before dc(1) parses and executes.
+
+Second, as a consequence of the above, if the user attempts to use the left
+bracket character, **[**, as a register name, dc(1) will not execute until a
+balancing right bracket, **]** is given. Then it will give an error since it is
+an error to use the left bracket as register name (see the **REGISTERS**
+section). In fact, this is why it is an error.
+
+
# STDOUT
Any non-error output is written to **stdout**. In addition, if history (see the
@@ -958,8 +973,9 @@ the stack for the register. All registers, when first referenced, have one value
off of the register stack.
In non-extended register mode, a register name is just the single character that
-follows any command that needs a register name. The only exception is a newline
-(**'\\n'**); it is a parse error for a newline to be used as a register name.
+follows any command that needs a register name. The only exceptions are: a
+newline (**'\\n'**) and a left bracket (**'['**); it is a parse error for a
+newline or a left bracket to be used as a register name.
## Extended Register Mode
diff --git a/manuals/development.md b/manuals/development.md
index 373cea47..9b91ddbf 100644
--- a/manuals/development.md
+++ b/manuals/development.md
@@ -553,6 +553,9 @@ The code associated with this header is in [`src/file.c`][47].
This header is for `bc`'s implementation of command-line editing/history, which
is based on a [UTF-8-aware fork][28] of [`linenoise`][29].
+At one point, I attempted to get history to work on Windows. It did not work
+out. Windows
+
The code associated with this header is in [`src/history.c`][48].
#### `lang.h`
@@ -1087,6 +1090,28 @@ A list of the various settings combos to be used by [`test_settings.sh`][104].
### `src/`
+This folder is, obviously, where the actual meat, the source code, is.
+
+#### `args.c`
+
+Code for processing command-line arguments.
+
+#### `bc.c`
+
+The code for the `bc` main function `bc_main()`.
+
+#### `bc_lex.c`
+
+The code for lexing that only `bc` needs.
+
+#### `dc.c`
+
+The code for the `dc` main function `dc_main()`.
+
+#### `dc_lex.c`
+
+The code for lexing that only `dc` needs.
+
### `tests/`
## POSIX Shell Scripts
diff --git a/src/args.c b/src/args.c
index 80d416cb..952a5950 100644
--- a/src/args.c
+++ b/src/args.c
@@ -48,31 +48,6 @@
#include <args.h>
#include <opt.h>
-/// The list of long options.
-static const BcOptLong bc_args_lopt[] = {
-
- { "expression", BC_OPT_REQUIRED, 'e' },
- { "file", BC_OPT_REQUIRED, 'f' },
- { "help", BC_OPT_NONE, 'h' },
- { "interactive", BC_OPT_NONE, 'i' },
- { "no-prompt", BC_OPT_NONE, 'P' },
- { "no-read-prompt", BC_OPT_NONE, 'R' },
-#if BC_ENABLED
- { "global-stacks", BC_OPT_BC_ONLY, 'g' },
- { "mathlib", BC_OPT_BC_ONLY, 'l' },
- { "quiet", BC_OPT_BC_ONLY, 'q' },
- { "standard", BC_OPT_BC_ONLY, 's' },
- { "warn", BC_OPT_BC_ONLY, 'w' },
-#endif // BC_ENABLED
- { "version", BC_OPT_NONE, 'v' },
- { "version", BC_OPT_NONE, 'V' },
-#if DC_ENABLED
- { "extended-register", BC_OPT_DC_ONLY, 'x' },
-#endif // DC_ENABLED
- { NULL, 0, 0 },
-
-};
-
/**
* Adds @a str to the list of expressions to execute later.
* @param str The string to add to the list of expressions.
@@ -97,11 +72,20 @@ static void bc_args_file(const char *file) {
vm.file = file;
- bc_read_file(file, &buf);
+ buf = bc_read_file(file);
+
+ assert(buf != NULL);
+
bc_args_exprs(buf);
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;
@@ -113,6 +97,8 @@ void bc_args(int argc, char *argv[], bool exit_exprs) {
bc_opt_init(&opts, argv);
+ // This loop should look familiar to anyone who has used getopt() or
+ // getopt_long() in C.
while ((c = bc_opt_parse(&opts, bc_args_lopt)) != -1) {
switch (c) {
diff --git a/src/bc.c b/src/bc.c
index 95e129e9..2fa8cc88 100644
--- a/src/bc.c
+++ b/src/bc.c
@@ -40,6 +40,11 @@
#include <bc.h>
#include <vm.h>
+/**
+ * The main function for bc.
+ * @param argc The number of arguments.
+ * @param argv The arguments.
+ */
void bc_main(int argc, char *argv[]) {
vm.read_ret = BC_INST_RET;
diff --git a/src/data.c b/src/data.c
index 318a3a1f..48707820 100644
--- a/src/data.c
+++ b/src/data.c
@@ -33,6 +33,7 @@
*
*/
+#include <opt.h>
#include <args.h>
#include <lex.h>
#include <parse.h>
@@ -93,6 +94,31 @@ const char bc_pledge_end[] = "";
#endif // __OpenBSD__
+/// The list of long options.
+const BcOptLong bc_args_lopt[] = {
+
+ { "expression", BC_OPT_REQUIRED, 'e' },
+ { "file", BC_OPT_REQUIRED, 'f' },
+ { "help", BC_OPT_NONE, 'h' },
+ { "interactive", BC_OPT_NONE, 'i' },
+ { "no-prompt", BC_OPT_NONE, 'P' },
+ { "no-read-prompt", BC_OPT_NONE, 'R' },
+#if BC_ENABLED
+ { "global-stacks", BC_OPT_BC_ONLY, 'g' },
+ { "mathlib", BC_OPT_BC_ONLY, 'l' },
+ { "quiet", BC_OPT_BC_ONLY, 'q' },
+ { "standard", BC_OPT_BC_ONLY, 's' },
+ { "warn", BC_OPT_BC_ONLY, 'w' },
+#endif // BC_ENABLED
+ { "version", BC_OPT_NONE, 'v' },
+ { "version", BC_OPT_NONE, 'V' },
+#if DC_ENABLED
+ { "extended-register", BC_OPT_DC_ONLY, 'x' },
+#endif // DC_ENABLED
+ { NULL, 0, 0 },
+
+};
+
const char* const bc_err_func_header = "Function:";
const char* const bc_err_line = ":%zu";
diff --git a/src/dc.c b/src/dc.c
index bb22d1d8..9f479cc3 100644
--- a/src/dc.c
+++ b/src/dc.c
@@ -40,6 +40,11 @@
#include <dc.h>
#include <vm.h>
+/**
+ * The main function for dc.
+ * @param argc The number of arguments.
+ * @param argv The arguments.
+ */
void dc_main(int argc, char *argv[]) {
vm.read_ret = BC_INST_POP_EXEC;
diff --git a/src/dc_lex.c b/src/dc_lex.c
index da0b6668..7ccc1176 100644
--- a/src/dc_lex.c
+++ b/src/dc_lex.c
@@ -45,24 +45,40 @@ bool dc_lex_negCommand(BcLex *l) {
return !BC_LEX_NUM_CHAR(c, false, false);
}
+/**
+ * Processes a dc command that needs a register. This is where the
+ * extended-register extension is implemented.
+ * @param l The lexer.
+ */
static void dc_lex_register(BcLex *l) {
+ // If extended register is enabled and the character is whitespace...
if (DC_X && isspace(l->buf[l->i - 1])) {
char c;
+ // Eat the whitespace.
bc_lex_whitespace(l);
c = l->buf[l->i];
- if (BC_ERR(!isalnum(c) && c != '_'))
+ // Check for a letter or underscore.
+ if (BC_ERR(!isalpha(c) && c != '_'))
bc_lex_verr(l, BC_ERR_PARSE_CHAR, c);
+ // Parse a normal identifier.
l->i += 1;
bc_lex_name(l);
}
else {
- if (BC_ERR(l->buf[l->i - 1] == '\n'))
- bc_lex_verr(l, BC_ERR_PARSE_CHAR, '\n');
+
+ // I don't allow newlines because newlines are used for controlling when
+ // execution happens, and allowing newlines would just be complex. For
+ // the same reason, I don't allow the '[' character; it would just be
+ // too complex.
+ if (BC_ERR(l->buf[l->i - 1] == '\n' || l->buf[l->i - 1] == '['))
+ bc_lex_verr(l, BC_ERR_PARSE_CHAR, l->buf[l->i - 1]);
+
+ // Set the lexer string and token.
bc_vec_popAll(&l->str);
bc_vec_pushByte(&l->str, (uchar) l->buf[l->i - 1]);
bc_vec_pushByte(&l->str, '\0');
@@ -70,16 +86,27 @@ 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.
+ * @param l The lexer.
+ */
static void dc_lex_string(BcLex *l) {
size_t depth = 1, nls = 0, i = l->i;
char c;
+ // Set the token and clear the string.
l->t = BC_LEX_STR;
bc_vec_popAll(&l->str);
+ // This is the meat. As long as we don't run into the NUL byte, and we have
+ // "depth", which means we haven't completely balanced brackets yet, we
+ // continue eating the string.
for (; (c = l->buf[i]) && depth; ++i) {
+ // Check for escaped brackets and set the depths as appropriate.
if (c == '\\') {
c = l->buf[++i];
if (!c) break;
@@ -89,11 +116,13 @@ static void dc_lex_string(BcLex *l) {
depth -= (c == ']');
}
+ // We want to adjust the line in the lexer as necessary.
nls += (c == '\n');
if (depth) bc_vec_push(&l->str, &c);
}
+ // Obviously, if we didn't balance, that's an error.
if (BC_ERR(c == '\0' && depth)) {
l->i = i;
bc_lex_err(l, BC_ERR_PARSE_STRING);
@@ -105,11 +134,17 @@ static void dc_lex_string(BcLex *l) {
l->line += nls;
}
+/**
+ * Lex a dc token. This is the dc implementation of BcLexNext.
+ * @param l The lexer.
+ */
void dc_lex_token(BcLex *l) {
char c = l->buf[l->i++], c2;
size_t i;
+ // 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 (l->last == dc_lex_regs[i]) {
dc_lex_register(l);
@@ -117,13 +152,16 @@ void dc_lex_token(BcLex *l) {
}
}
+ // These lines are for tokens that easily correspond to one character. We
+ // just set the token.
if (c >= '"' && c <= '~' &&
(l->t = dc_lex_tokens[(c - '"')]) != BC_LEX_INVALID)
{
return;
}
- // This is the workhorse of the lexer.
+ // This is the workhorse of the lexer when more complicated things are
+ // needed.
switch (c) {
case '\0':
@@ -138,6 +176,8 @@ void dc_lex_token(BcLex *l) {
break;
}
+ // We don't have the ! command, so we always expect certain things
+ // after the exclamation point.
case '!':
{
c2 = l->buf[l->i];
diff --git a/src/history.c b/src/history.c
index 5d5fa559..5c1a8c4b 100644
--- a/src/history.c
+++ b/src/history.c
@@ -174,6 +174,7 @@ static void bc_history_add_empty(BcHistory *h);
/**
* Check if the code is a wide character.
+ * @param cp The codepoint to check.
*/
static bool bc_history_wchar(uint32_t cp) {
@@ -196,6 +197,7 @@ static bool bc_history_wchar(uint32_t cp) {
/**
* Check if the code is a combining character.
+ * @param cp The codepoint to check.
*/
static bool bc_history_comboChar(uint32_t cp) {
diff --git a/src/read.c b/src/read.c
index 39f7fbab..61a490c5 100644
--- a/src/read.c
+++ b/src/read.c
@@ -53,6 +53,11 @@
#include <program.h>
#include <vm.h>
+/**
+ * A portability file open function.
+ * @param path The path to the file to open.
+ * @param mode The mode to open in.
+ */
static int bc_read_open(const char* path, int mode) {
int fd;
@@ -67,6 +72,11 @@ static int bc_read_open(const char* path, int mode) {
return fd;
}
+/**
+ * Returns true if the buffer data is non-ASCII.
+ * @param buf The buffer to test.
+ * @param size The size of the buffer.
+ */
static bool bc_read_binary(const char *buf, size_t size) {
size_t i;
@@ -197,12 +207,13 @@ BcStatus bc_read_line(BcVec *vec, const char *prompt) {
return s;
}
-void bc_read_file(const char *path, char **buf) {
+char* bc_read_file(const char *path) {
BcErr e = BC_ERR_FATAL_IO_ERR;
size_t size, r;
struct stat pstat;
int fd;
+ char* buf;
BC_SIG_ASSERT_LOCKED;
@@ -224,25 +235,26 @@ void bc_read_file(const char *path, char **buf) {
}
size = (size_t) pstat.st_size;
- *buf = bc_vm_malloc(size + 1);
+ buf = bc_vm_malloc(size + 1);
- r = (size_t) read(fd, *buf, size);
+ r = (size_t) read(fd, buf, size);
if (BC_ERR(r != size)) goto read_err;
- (*buf)[size] = '\0';
+ buf[size] = '\0';
- if (BC_ERR(bc_read_binary(*buf, size))) {
+ if (BC_ERR(bc_read_binary(buf, size))) {
e = BC_ERR_FATAL_BIN_FILE;
goto read_err;
}
close(fd);
- return;
+ return buf;
read_err:
- free(*buf);
+ free(buf);
malloc_err:
close(fd);
bc_vm_verr(e, path);
+ return NULL;
}
diff --git a/src/vm.c b/src/vm.c
index cf809792..6c39ef88 100644
--- a/src/vm.c
+++ b/src/vm.c
@@ -742,7 +742,9 @@ static void bc_vm_file(const char *file) {
BC_SIG_LOCK;
- bc_read_file(file, &data);
+ data = bc_read_file(file);
+
+ assert(data != NULL);
BC_SETJMP_LOCKED(err);