Building Replicalc Part 3: Porting to MS-DOS
With Replicalc running well under Linux, it was time to begin porting to MS-DOS. The first step was to make sure the existing code (not including ncurses-specifics) would compile.
I fired up DOSBox, installed Open Watcom, removed the ncurses-specific code, and tried compiling. It didn't work. I discovered that although I had been compiling for C90 in GCC, GCC has a very liberal interpretation of what "C90" is. I had accidentally written C99 code, and GCC didn't warn me despite having enabled compiler warnings.
First there were a handful of instances where I had declared new variables in the middle of a function - these were easily fixed. Then there were also a few cases of the vague Error! E1054: Expression must be constant error. I determined that this was because I had been using variable-length arrays, which was only introduced in C99. In C90, array sizes must be compile-time constants. With those fixed, the code was compiling under MS-DOS. Time to move onto the user interface.
Since ncurses wasn't available for MS-DOS, my plan was to create a new user interface from scratch. By designing it with the same API as its Linux counterpart, I would be able to use preprocessor macros to choose one interface or the other during compilation based on the user's operating system - sort of like a compile-time interface.
My plan was to get user input using getch()
from conio.h, then process it
using a behind-the-scenes buffer. This would allow me to track the cursor position in order
to allow moving left and right, or using the backspace and delete keys:
struct Buffer {
char* str; /* Buffer text. */
int top; /* Index of last character in the buffer. */
int index; /* Index of the cursor. */
};
This was a bit of a challenge for two reasons. First, I initially couldn't get arrow keys to work at all under DOSBox. After much troubleshooting and reading, I found that I needed to add some additional lines to my DOSBox config:
[sdl]
usescancodes=false
Then I realised that arrow keys are part of the extended keyset, and must be detected using
two separate getch()
calls. The first call returns 0
to indicate
that an extended key was pressed, and the second call indicates the extended key number.
It was at this point that I discovered PDCurses and felt a bit silly. PDCurses is a public-domain curses implementation that supports both DOS and Windows, and for my purposes its API is 100% ncurses-compatible1. It took a little work to figure out how to integrate it into the build process, but once I did it worked like a charm, and the ncurses code I had written for Linux was suddenly working under MS-DOS. In the end, PDCurses practically did the porting for me, but I learned a lot along the way.
There was something that bugged me. Often when exiting Replicalc in MS-DOS, an ominous
error message would appear: *** NULL assignment detected
. I determined this was
due to some kind of memory corruption error, but I wasn't sure what.
First I tried running Replicalc through Valgrind, learning how Valgrind works along the way. This enabled me to find and fix a memory leak due to not freeing stacks and their elements properly. However, this didn't fix the NULL assignment error in MS-DOS, and no other issues were reported.
I decided I would have to troubleshoot this in MS-DOS, but I couldn't get the Open Watcom debugger to work. Instead, I began commenting out blocks of code and testing to see if the issue had gone, slowing paring the program down until I found the source of the bug. In the end, it was another memory issue due to mallocing a 2D array incorrectly:
/* Good */
malloc(sizeof(double[STACK_MAX_CAP]));
/* Bad */
malloc(sizeof(double[STACK_MAX_CAP][EXPR_MAX_WIDTH]));
In both cases, these bugs were due to me misunderstanding how 2D arrays work: the array and its individual elements must be allocated separately and freed separately.
With no major bugs remaining, I decided this would become the first Replicalc release, 0.1.0. It's not perfect - there are still other bugs, especially due to lacking input validation. However, it's reliable enough that I can now use it every day instead of my old TI-83.
PDCurses also supports Linux, but I didn't see a need to switch from ncurses.↩