#pragma warning(disable : 4996 6011 6385 6386 6387) #include /* Standard Library: malloc, free, exit */ #include /* Variable argument processing: va_list, va_start, va_end */ #include /* Standard Input/Output: fprintf, vfprintf, stderr, puts, fopen, fclose, fgets, EOF */ #include /* String Library: strdup, strchr, strrchr, strcmp, strcpy, strlen, strcat, memset, memcpy */ #include /* Character Library: isgraph, isspace, toupper, isdigit */ #include /* Constants: INT_MAX, UINT_MAX */ /* Maximum line buffer length */ #define MAXLINE 1024 /* input file name */ static char* filename = NULL; /* line number within input file */ static int lineNo; /* file pointer for current input file */ static FILE* fin; /* input line buffer */ static char* line = NULL; /* current scanning position within the input line buffer */ static char* pos; /* current radix (base) for number conversion */ static int base = 10; /* ========== Error Handling ========== */ /* Forward reference to the freeDict function to free the memory used by the dictionary. */ void freeDict(); /** * @brief Report an error message to the standard error and exit the program. * The error message may contain parameter place holders with the additional parameters being provide after the * message. This will display the message on the standard error stream, free any allocated memory and exit the * program with the exit code. * @param code the exit code. * @param format the error message to be displayed (may contain parameter descriptions). * @param ... any additional parameters required by the format. */ void Error(int code, char* format, ...) { if (format) { fprintf(stderr, "\n%s(%d): ", filename, lineNo); va_list vaargs; va_start(vaargs, format); vfprintf(stderr, format, vaargs); va_end(vaargs); } freeDict(); free(filename); if (line) { free(line); } if (fin) { fclose(fin); } exit(code); } /** * @brief Remove the directory name from a file path. * This will return a pointer to the first character of the last part of the path (or the start of the path, if the path does * not have any directories). * @param filename pointer to the start of the file path. * @return a pointer to the start of the filename within the path. */ char* rmDir(char* filename) { char* temp = strrchr(filename, '/'); /* Unix directory separator */ if (!temp) { temp = strrchr(filename, '\\'); /* Dos directory separator */ } return temp == NULL ? filename : ++temp; } /** * @brief Report the program usage, with an error message and a filename that will be displayed after the error * message. Note this does not free memory so may only be used in the initializations, before the dictionary memory * has been allocated. * @param progname the program name, may contain a full path name. * @param message the message to be displayed. * @param filename the filename causing the error. */ void usage(char* progname, char* message, char* filename) { progname = rmDir(progname); char* temp = strchr(progname, '.'); if (temp) { *temp = 0; } printf("Usage: %s \n", progname); if (filename) { printf(message, filename); } else { puts(message); } exit(EXIT_FAILURE); } /* ========== Data Stack ========== */ #define MAXSTACK 10 int stack[MAXSTACK]; /* Data Stack */ int dsp = 0; /* Data Stack pointer/depth */ /** * @brief Push a single cell item on to the data stack. * This will report an error if the stack is full. * @param data the item to be placed on the stack. * @return the data item placed on the stack. */ int push(int data) { if ( dsp >= MAXSTACK ) { Error(EXIT_FAILURE, "Stack Overflow"); } stack[dsp++] = (int) data; return data; } /** * @brief Remove the item at the top of the stack and return it. * This will report an error if the stack is empty. * @return the data item at the top of the stack. */ int pop() { if ( dsp == 0 ) { Error(EXIT_FAILURE, "Stack Underflow"); } return (int) stack[--dsp]; } /** * @brief Remove a double cell item from the top of the stack and return it. * @return a double cell item. */ long long popLong() { long long top = (long long) pop() << (sizeof(long) * 8); return top | pop(); } /** * @brief Remove the second item on the data stack. * @return the data item removed from the stack. */ int nip() { int data = stack[dsp]; stack[--dsp] = data; return data; } /* ========== Dictionary ========== */ /** * @brief A pointer to a function that takes no arguments are does not return a value, i.e., void func(). */ typedef void (*ptr_func_t)(); /** * @brief The Execution Token data structure. */ struct XT_s { char* name; ptr_func_t func; struct XT_s* next; }; /** * @brief A pointer to an Execution Token data structure. */ typedef struct XT_s* XT_t; /** * @brief The head of the dictionary linked list. * A pointer to the most recent XT in the dictionary. Each XT contains a pointer to the next XT in the dictionary * with the last XT in the list holding the NULL for the next value. */ static XT_t dict = NULL; /** * @brief Free all memory used by the dictionary. * Loop though the dictionary, one entry at a time, and free the memory used by the word name and the XT_s * data structure itself. */ void freeDict() { XT_t next = dict; while ( dict != NULL ) { next = dict->next; free(dict->name); free(dict); dict = next; } } /** * @brief Add a word into the dictionary. * This will build a new XT data structure which it will place at the head of the dictionary linked list, placing the * current head of the list as the next item in the XT data structure. * @param name word name to add. * @param func pointer to c-function to preform the word's action. */ void AddWord(const char* name, ptr_func_t func) { XT_t xt = (XT_t) malloc(sizeof(struct XT_s)); xt->func = (ptr_func_t) func; xt->name = strdup(name); xt->next = dict; dict = xt; } /** * @brief Find a word in the dictionary, returning the word's XT data structures or NULL if the word is not found. * This will start at the head of the dictionary and follow the links to each XT in the dictionary until it either finds the * XT with the given name or comes to the end of the linked list. * @param name the word to search for. * @return a pointer to the XT of the word or NULL if not found. */ XT_t find(char* name) { XT_t current = dict; while ( current != NULL ) { if ( strcmp(current->name, name) == 0 ) { break; } else { current = current->next; } } return current; } /* ========== Echo ========== */ static int echo = 1; void echoOff() { echo = 0; } void echoOn() { echo = 1; } void initEcho() { AddWord("+ECHO", echoOn); AddWord("-ECHO", echoOff); } /* ========== Scanning ========== */ /** * @brief Read the next character from the input file. * If the character is the end of line marker, read the next line from the file and return the first character of the new * line. If in echo mode write the character to the console, when reading a new line write the line number to the * console. * @return the character or EOF if at the end of the file. */ char nextChar() { char c = *pos++; if (c == 0) { if (fgets(line, MAXLINE, fin)) { pos = line; lineNo++; if (strlen(line) + 1 == MAXLINE) { Error(EXIT_FAILURE, "Line too long for buffer of %d characters", MAXLINE); } if (echo) { printf("\n%4d: ", lineNo); } c = *pos++; } else { return EOF; } } if (echo && (isgraph(c) || c == ' ' || c == '\t')) { putchar(c); } return c; } /** * @brief Return the next word from the input. * This will ignore any leading white space and return a pointer to the next non white-space character in the input * line. It will replace the first white-space character after the word name with an end of text marker to convert that * part of the input line into a string. * @return a pointer to the first character of the word. */ char* nextWord() { /* Ignore leading white space */ char c = ' '; while (isspace(c)) { c = nextChar(); } if (c == EOF) { return NULL; } /* Read name up to next space */ char* name = pos - 1; while (!isspace(c) && c != EOF) { c = nextChar(); } /* Mark end of word */ *(pos - 1) = 0; return name; } /** * @brief Attempt to convert text into a number using the current base. * This will attempt to convert the string in text into a number using the value is base as the radix. If successful the * number will be placed in the integer pointed to by the number parameter and a true value is returned otherwise a * false value is returned. * @param text the text to be parsed. * @param number a pointer to a location where the number can be stored. * @return true if the text is a number or false if not. */ int parseNumber(char* text, int* number) { int value = 0; int sign = 1; char c = *text++; if (c == '-') { sign = -1; c = *text++; } while (c > 0) { c = toupper(c); if (isdigit(c)) { c = c - '0'; } else if (c >= 'A' && c < 'A' + base - 10) { c = c - 'A' + 10; } else { return 0; /* Not a valid number */ } value *= base; value += c; c = *text++; } *number = (value * sign); return c == 0; } /* ========== Forth Words ========== */ /** * @brief Set BASE to 16 */ void hex() { base = 16; } /** * @brief Ignore all text up until the end of the line. */ void comment() { int len = strlen(pos); while (len-- > 0) { nextChar(); } } /** * @brief In-line comment - ignore all text up until the next ). */ void parn() { char c; do { c = nextChar(); } while (c != EOF && c != ')'); } /** * @brief Add the Forth words HEX, \ and ( to the dictionary. */ void initForth() { AddWord("HEX", hex); AddWord("\\", comment); AddWord("(", parn); } /* ========== Test Harness ========== */ static int testStack[MAXSTACK]; /* Test stack */ static int tend; /** * @brief Start a test, start with a clean data stack. */ void testStart() { dsp = 0; } /** * @brief Save the test stack. * Copy the current data stack to the test stack. */ void testSave() { memset(testStack, 0, sizeof(int) * MAXSTACK); memcpy(testStack, stack, sizeof(int) * dsp); tend = dsp; dsp = 0; } /** * @brief End a test. * Compare the current data stack with the test data stack. * Report an error if the two stacks to not match exactly. */ void testEnd() { int match = (tend == dsp); for (int n = 0; (match && n < tend); n++) { match = (stack[n] == testStack[n]); } if ( !match ) { fprintf(stderr, "\n%s(%d): Stack Mismatch\n", filename, lineNo); fprintf(stderr, "Found: "); for (int n = 0; n < tend; n++) { fprintf(stderr, "%d ", testStack[n]); } fprintf(stderr, "\nExpecting: "); for (int n = 0; n < dsp; n++) { fprintf(stderr, "%d ", stack[n]); } fprintf(stderr, "\n"); Error(EXIT_FAILURE, NULL); } dsp = 0; } /* ==== Temporary colon definitions to be removed once : is defined and tested */ /* { : BITSSET? IF 0 0 ELSE 0 THEN ; -> } */ void bittest() { int top = pop(); push(0); if (top) { push(0); } } /* : BITS ( x -- u ) 0 SWAP BEGIN DUP WHILE MSB AND IF >R 1+ R> THEN 2* REPEAT DROP ; */ void bits() { unsigned int top = pop(); int count = 0; while (top) { count++; top >>= 1; } push(count); } /* ==== Temporary CONSTANT definitions to be removed once CONSTANT is defined and tested */ void zeros() { push(0); } /* 0 */ void ones() { push(~0); } /* 0 INVERT */ void cfalse() { push(0); } /* 0S */ void ctrue() { push(~0); } /* 1S */ /* The Comparison operators define a number of constants */ /* MSB, MAX-UINT, MAX-INT, MIN-INT, MID-UINT, MID-UINT+1 */ void msb() { push(~INT_MAX); } /* 1S 1 RSHIFT INVERT */ void maxUInt() { push(UINT_MAX); } /* 0 INVERT */ void maxInt() { push(INT_MAX); } /* 0 INVERT 1 RSHIFT */ void minInt() { push(INT_MIN); } /* 0 INVERT 1 RSHIFT INVERT */ void midUInt() { push(UINT_MAX >> 1); } /* 0 INVERT 1 RSHIFT */ void midUI1() { push(~(UINT_MAX >> 1)); } /* 0 INVERT 1 RSHIFT INVERT */ /* C can use either Floored or Symmetric division */ /* The following temporary colon definitions can be removed once : is defined and tested */ int isFloored() { return (-3 / 2) == -1; } /* IFFLOORED : T/MOD >R S>D R> FM/MOD ; */ /* IFSYM : T/MOD >R S>D R> SM/REM ; */ void tdm() { int top = pop(); long long nos = popLong(); long long div; int rem; if ( isFloored() ) { div = nos / top; rem = (int) (nos - (div * top)); } else { div = nos / top; rem = (int) (nos % top); } push(rem); push((int) div); } /* IFFLOORED : T/ T/MOD SWAP DROP ; */ /* IFSYM : T/ T/MOD SWAP DROP ; */ void td() { tdm(); nip(); } /* IFFLOORED : TMOD T/MOD DROP ; */ /* IFSYM : TMOD T/MOD DROP ; */ void tm() { tdm(); pop(); } /* IFFLOORED : T*_/MOD >R M* R> FM/MOD ; */ /* IFSYM : T*_/MOD >R M* R> SM/REM ; */ void tsdm() { int top = pop(); long long a = pop(); long long b = pop(); long long mul = a * b; long long div; int rem; if ( isFloored() ) { div = mul / top; rem = (top - (int) div * top); } else { div = mul / top; rem = mul % top; } push(rem); push((int) div); } /* IFFLOORED : T*_/ T*_/MOD SWAP DROP ; */ /* IFSYM : T*_/ T*_/MOD SWAP DROP ; */ void tsd() { tsdm(); nip(); } /** * @brief Initialise the dictionary with the test harness and temporary definitions. */ void initTest() { /* Hayes test harness { -> } */ AddWord("{", testStart); AddWord("->", testSave); AddWord("}", testEnd); /* Forth200x test harness T{ -> }T */ AddWord("T{", testStart); AddWord("}T", testEnd); AddWord("TESTING", comment); /* Temporary colon definitions (Basic assumptions and Memory) */ AddWord("BITSSET?", bittest); AddWord("BITS", bits); /* Temporary constant definitions */ AddWord("0S", zeros); AddWord("1S", ones); AddWord("", ctrue); AddWord("", cfalse); /* Comparison operators use the following constants */ AddWord("MSB", msb); AddWord("MAX-UINT", maxUInt); AddWord("MAX-INT", maxInt); AddWord("MIN-INT", minInt); AddWord("MID-UINT", midUInt); AddWord("MID-UINT+1", midUI1); /* Temporary colon definitions (Division) */ AddWord("IFFLOORED", comment); /* Ignore rest of line */ AddWord("IFSYM", comment); /* Ignore rest of line */ AddWord("T/MOD", tdm); AddWord("T/", td); AddWord("TMOD", tm); AddWord("T*/MOD", tsdm); AddWord("T*/", tsd); } /* ========== Main ========== */ /** * @brief Open the file given in as the command line argument. * This will process all of the command line arguments, checking that there is only one. It will attempt to open the * file, if not able to it will add the .forth extension to the file and try again, if the file is still not found it will try the * .fr extension. If successful the fin and filename global variables configured, otherwise it will report a usage error * and exit. * @param argc the number of arguments contained in the argv array. * @param argv an array of strings, one for each command line argument. */ void openFile(int argc, char *argv[]) { /* Check we have the right number of arguments */ if ( argc == 1 ) { usage(argv[0], "We need a file to process !", NULL); } else if ( argc > 2 ) { usage(argv[0], "Can only process one file at a time", NULL); } filename = argv[1]; /* Process the file name (allow for ".forth" extension) */ int len = strlen(filename) + 10; char* name = (char*) malloc(len * sizeof(char)); strcpy(name, filename); /* Does the file exist? */ fin = fopen(name, "r"); if ( !fin ) { /* File not found, try ".forth" extension */ strcat(name, ".forth"); fin = fopen(name, "r"); } if ( !fin ) { /* Still not found, try ".fr" extension */ strcpy(name, filename); strcat(name, ".fr"); fin = fopen(name, "r"); } if ( !fin ) { /* Still not found, give in */ usage(argv[0], "Can not open input file: \"%s\"", filename); } filename = strdup(rmDir(name)); free(name); } /** * @brief Initialise the system. * First it will attempt to process the command line arguments. It will then initialise the dictionary before initialising * the line buffer so that the first call to nextChar will load the first line of the file into the buffer. * @param argc the number of command line arguments in the argv array. * @param argv an array of strings, one for each command line argument. */ void init(int argc, char* argv[]) { /* Open input file */ openFile(argc, argv); /* Initialise dictionary */ initEcho(); initTest(); initForth(); /* Initialise line buffer */ line = (char*)malloc(MAXLINE * sizeof(char)); pos = line; *pos = 0; lineNo = 0; } /** * @brief The main interpret loop. * This will read the input file, one word at a time, it will look up each word in the dictionary and if found it will * preform the action associated with the word, otherwise it will attempt to convert the word into a number (using the * current base). If successful it will place the number on the data stack otherwise it will report a word not found * error and abort. * @param argc the number of command line arguments in the argv array. * @param argv an array of strings, one for each command line argument. * @return does not return */ int main(int argc, char* argv[]) { init(argc, argv); /* Interpret loop */ char* name; while (name = nextWord()) { XT_t word = find(name); /* Lookup name in dictionary */ if (word) { /* if name found */ word->func(); /* Execute XT*/ } else { /* Name not found */ int value; /* convert to number */ if (parseNumber(name, &value)) { push(value); /* Push number onto stack */ } else { /* Not a word or a number */ Error(EXIT_FAILURE, "Word not found: %s", name); } } } Error(EXIT_SUCCESS, NULL); }