mdhender

super secret hq

A Toy Forth Interpreter, Part 2

Here’s the code:

//
//  falseforth/main.c
//
//  Created by Michael Henderson on 6/12/13.
//  Copyright (c) 2013 Michael Henderson. All rights reserved.
//

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>

typedef enum { ffsOK = 0, ffsEndOfInput, ffsInvalidStack, ffsInvalidState, ffsInvalidWord } FFS_STATUS;
typedef enum { wisError = 0, wisInteger, wisNumber, wisQuotedText, wisText } WORDKIND;

// word
typedef struct FFWORD {
    WORDKIND kind;
    union {
        int         integer;
        double      number;
        const char *text;
    } v;
} FFWORD;

// stack node
typedef struct FFNODE {
    struct FFNODE *prev;
    struct FFNODE *next;
    struct FFWORD *word;
} FFNODE;

// stack
typedef struct FFSTACK {
    struct FFNODE *top;
    struct FFNODE *bottom;
    int            height;
} FFSTACK;

// state
typedef struct FFSTATE {
    FFSTACK *data;
} FFSTATE;


FFS_STATUS FF_Interpret(FFSTATE *ffs, const char *input);
void       FF_StackDump(FFSTACK *s, int maxNodes);
FFWORD    *FF_StackPop(FFSTACK *s);
FFNODE    *FF_StackPush(FFSTACK *s, FFWORD *word);
void       FF_WordDump(FFWORD *word);
FFWORD    *FF_WordFromText(WORDKIND kind, const char *input, int len);

int main(int argc, char *argv[]) {
    int     idx;

    FFSTATE ffs;
    FFSTACK data;
    ffs.data = &data;
    ffs.data->top    = 0;
    ffs.data->bottom = 0;
    ffs.data->height = 0;

    // copy the command line into a buffer for testing
    //
    char buffer[4096];
    buffer[0] = 0;
    for (idx = 1; idx < argc; idx++) {
        if (idx > 1) {
            strncat(buffer, " ", sizeof(buffer) - 1);
        }
        if (argv[idx] && argv[idx][0]) {
            strncat(buffer, argv[idx], sizeof(buffer) - 1);
        }
    }

    FF_Interpret(&ffs, buffer);

    return 0;
}



FFS_STATUS FF_Interpret(FFSTATE *ffs, const char *input) {
    if (!ffs) {
        return ffsInvalidState;
    }
    printf("input: %s\n", input);

    const char *firstChar;
    WORDKIND    kindOfWord;

    while (input && *input) {

        if (isspace(*input)) {
            while (isspace(*input)) {
                input++;
            }
            continue;
        }

        firstChar = input++;

        if (*firstChar == '"' || *firstChar == '\'') {
            // scan quoted text
            //
            char quote         = *firstChar;
            int  escapedQuotes = 0;

            while (*input) {
                if (*input == quote) {
                    // end of quoted text?
                    if (*(input + 1) != quote) {
                        break;
                    }
                    // escaped quote, advance past it
                    input++;
                    escapedQuotes++;
                }
                input++;
            }

            // input must point past end of word when we're done
            if (*input == quote) {
                input++;
            }

            kindOfWord = wisQuotedText;
        } else {
            // scan unquoted text
            //

            // we'll check below to see if it is a number, but
            // start off assuming that the word is text.
            //
            kindOfWord = wisText;

            // see if we have an integer or a number
            //   an integer is [+-]? [0=9]+
            //   a  number  is [+-]? [0-9]+ '.' [0-9]+
            //
            if (isdigit(*firstChar) || (isdigit(*input) && (*firstChar == '-' || *firstChar == '+'))) {
                while (isdigit(*input)) {
                    input++;
                }
                if (!*input || isspace(*input)) {
                    kindOfWord = wisInteger;
                } else if (*input == '.' && isdigit(*(input+1))) {
                    do {
                        input++;
                    } while (isdigit(*input));
                    if (!*input || isspace(*input)) {
                        kindOfWord = wisNumber;
                    }
                }
            }

            // text ends at the first whitespace
            while (*input && !isspace(*input)) {
                input++;
            }
        }

        // temporary hack for words
        FFWORD *word = FF_WordFromText(kindOfWord, firstChar, (int)(input - firstChar));
        FF_WordDump(word);

        switch (word->kind) {
            case wisError:
                fprintf(stderr, "error:\t%s %s %d\n\tinternal error - passed wisError\n", __FILE__, __FUNCTION__, __LINE__);
                return ffsInvalidState;
            case wisInteger:
                if (!FF_StackPush(ffs->data, word)) {
                    fprintf(stderr, "error:\t%s %s %d\n\tinternal error - push failed\n", __FILE__, __FUNCTION__, __LINE__);
                    exit(2);
                }
                break;
            case wisNumber:
                if (!FF_StackPush(ffs->data, word)) {
                    fprintf(stderr, "error:\t%s %s %d\n\tinternal error - push failed\n", __FILE__, __FUNCTION__, __LINE__);
                    exit(2);
                }
                break;
            case wisQuotedText:
                if (!FF_StackPush(ffs->data, word)) {
                    fprintf(stderr, "error:\t%s %s %d\n\tinternal error - push failed\n", __FILE__, __FUNCTION__, __LINE__);
                    exit(2);
                }
                break;
            case wisText:
                if (strcmp(word->v.text, ".") == 0) {
                    if (ffs->data->height < 1) {
                        fprintf(stderr, "error:\tstack empty - nothing to display\n");
                    } else {
                        FF_WordDump(FF_StackPop(ffs->data));
                    }
                } else if (strcmp(word->v.text, ".stack") == 0) {
                    FF_StackDump(ffs->data, -1);
                } else if (strcmp(word->v.text, "-") == 0) {
                    if (ffs->data->height < 2) {
                        fprintf(stderr, "error:\t'-' requires two numbers on stack\n");
                        return ffsInvalidStack;
                    }
                    FFWORD *n2 = FF_StackPop(ffs->data);
                    FFWORD *n1 = FF_StackPop(ffs->data);

                    if (n1->kind == wisInteger && n2->kind == wisInteger) {
                        n1->v.integer = n1->v.integer - n2->v.integer;
                    } else if (n1->kind == wisInteger && n2->kind == wisNumber ) {
                        n1->kind = wisNumber;
                        n1->v.number  = n1->v.integer - n2->v.number;
                    } else if (n1->kind == wisNumber  && n2->kind == wisInteger) {
                        n1->kind = wisNumber;
                        n1->v.number  = n1->v.number  - n2->v.integer;
                    } else if (n1->kind == wisNumber  && n2->kind == wisNumber ) {
                        n1->kind = wisNumber;
                        n1->v.number  = n1->v.number  - n2->v.number;
                    } else {
                        fprintf(stderr, "error:\t'-' requires two numbers on stack\n");
                        return ffsInvalidStack;
                    }
                    if (!FF_StackPush(ffs->data, n1)) {
                        fprintf(stderr, "error:\t%s %s %d\n\tinternal error - push failed\n", __FILE__, __FUNCTION__, __LINE__);
                        exit(2);
                    }
                } else if (strcmp(word->v.text, "+") == 0) {
                    if (ffs->data->height < 2) {
                        fprintf(stderr, "error:\t'+' requires two numbers on stack\n");
                        return ffsInvalidStack;
                    }
                    FFWORD *n2 = FF_StackPop(ffs->data);
                    FFWORD *n1 = FF_StackPop(ffs->data);

                    if (n1->kind == wisInteger && n2->kind == wisInteger) {
                        n1->v.integer = n1->v.integer + n2->v.integer;
                    } else if (n1->kind == wisInteger && n2->kind == wisNumber ) {
                        n1->kind = wisNumber;
                        n1->v.number  = n1->v.integer + n2->v.number;
                    } else if (n1->kind == wisNumber  && n2->kind == wisInteger) {
                        n1->kind = wisNumber;
                        n1->v.number  = n1->v.number  + n2->v.integer;
                    } else if (n1->kind == wisNumber  && n2->kind == wisNumber ) {
                        n1->kind = wisNumber;
                        n1->v.number  = n1->v.number  + n2->v.number;
                    } else {
                        fprintf(stderr, "error:\t'+' requires two numbers on stack\n");
                        return ffsInvalidStack;
                    }
                    if (!FF_StackPush(ffs->data, n1)) {
                        fprintf(stderr, "error:\t%s %s %d\n\tinternal error - push failed\n", __FILE__, __FUNCTION__, __LINE__);
                        exit(2);
                    }
                } else {
                    fprintf(stderr, "error:\tunrecognized word '%s'\n", word->v.text);
                    return ffsInvalidWord;
                }
                break;
        }
        FF_StackDump(ffs->data, -1);
    }

    return ffsEndOfInput;
}


void FF_StackDump(FFSTACK *s, int maxNodes) {
    if (!s) {
        printf("stack:\tmissing stack\n");
    } else {
        printf("stack:\theight %d\n", s->height);
        maxNodes = (maxNodes < 1) ? s->height : maxNodes;
        FFNODE *n = s->top;
        if (!n) {
            printf("..top:\tis null\n");
        } else {
            while (n && maxNodes--) {
                FF_WordDump(n->word);
                n = n->prev;
            }
        }
    }
}



FFWORD *FF_StackPop(FFSTACK *stack) {
    FFWORD *word = 0;
    if (stack && stack->height) {
        FFNODE *node = stack->top;
        word = node->word;
        stack->top = stack->top->prev;
        if (stack->top) {
            stack->top->next = stack->top;
            stack->height--;
        } else {
            stack->bottom = 0;
            stack->height = 0;
        }
        free(node);
    }
    return word;
}



FFNODE *FF_StackPush(FFSTACK *stack, FFWORD *word) {
    FFNODE *node = 0;
    if (stack && word) {
        node = (FFNODE *)malloc(sizeof(*node));
        if (!node) {
            perror("Stack::Push");
            exit(2);
        }
        node->prev = node->next = 0;
        node->word = word;
        if (!stack->top) {
            stack->bottom = stack->top = node;
        } else {
            stack->top->next = node;
            node->prev = stack->top;
            stack->top = node;
        }
        stack->height++;
    }

    return node;
}


void FF_WordDump(FFWORD *word) {
    switch (word->kind) {
        case wisError:
            fprintf(stderr, "error:\t%s %s %d\n\tinternal error - passed wisError\n", __FILE__, __FUNCTION__, __LINE__);
            exit(2);
            break;
        case wisInteger:
            printf("word(int, %d)\n", word->v.integer);
            break;
        case wisNumber:
            printf("word(nbr, %f)\n", word->v.number);
            break;
        case wisQuotedText:
            printf("word(qtx, %s)\n", word->v.text);
            break;
        case wisText:
            printf("word(txt, %s)\n", word->v.text);
            break;
    }
}



FFWORD *FF_WordFromText(WORDKIND kind, const char *input, int len) {
    FFWORD *word = (FFWORD *)malloc(sizeof(*word));
    if (!word) {
        perror("Word::FromText");
        exit(2);
    }

    char  buf[128];
    char *text;

    switch (kind) {
        case wisError:
            fprintf(stderr, "error:\t%s %s %d\n\tinternal error - passed wisError\n", __FILE__, __FUNCTION__, __LINE__);
            exit(2);
            break;
        case wisInteger:
            len = (len > 127) ? 127 : len;
            memcpy(buf, input, len);
            buf[len] = 0;
            word->kind = kind;
            word->v.integer = atoi(buf);
            break;
        case wisNumber:
            len = (len > 127) ? 127 : len;
            memcpy(buf, input, len);
            buf[len] = 0;
            word->kind = kind;
            word->v.number = atof(buf);
            break;
        case wisQuotedText:
            text = (char*)malloc(len + 1);
            if (!text) {
                perror("Word::FromQuotedText");
                exit(2);
            } else {
                const char *src       = input + 1;
                const char *endOfWord = input + len - 1;
                char       *tgt       = text;
                while (src < endOfWord) {
                    if (*src == *input) {
                        src++;
                    }
                    *(tgt++) = *(src++);
                }
                *tgt = 0;
            }
            word->kind   = kind;
            word->v.text = text;
            break;
        case wisText:
            text = (char*)malloc(len + 1);
            if (!text) {
                perror("Word::FromUnquotedText");
                exit(2);
            }
            memcpy(text, input, len);
            text[len] = 0;
            word->kind   = kind;
            word->v.text = text;
            break;
    }

    return word;
}

Comments