
/*
 * Copyright 2011 Google Inc.
 *
 * Use of this source code is governed by a BSD-style license that can be
 * found in the LICENSE file.
 */
#include "Forth.h"
#include "SkString.h"

class Reporter {
public:
    int fFailureCount;

    Reporter() : fFailureCount(0) {}
    void reportFailure(const char expression[], const char file[], int line);
    void reportFailure(const char msg[]);
};

typedef void (*ForthWordTestProc)(ForthWord*, ForthEngine*, Reporter*);

#define FORTH_ASSERT(reporter, expression)      \
    do {                                        \
        if (!(expression)) {                    \
            reporter->reportFailure(#expression, __FILE__, __LINE__);   \
        }                                       \
    } while (0)

static void drop_test0(ForthWord* word, ForthEngine* fe, Reporter* reporter) {
    fe->push(-17);
    word->exec(fe);
    FORTH_ASSERT(reporter, 0 == fe->depth());
}

static void drop_test1(ForthWord* word, ForthEngine* fe, Reporter* reporter) {
    fe->push(-17);
    fe->push(93);
    word->exec(fe);
    FORTH_ASSERT(reporter, 1 == fe->depth());
    FORTH_ASSERT(reporter, -17 == fe->peek(0));
}

static void dup_test(ForthWord* word, ForthEngine* fe, Reporter* reporter) {
    fe->push(-17);
    word->exec(fe);
    FORTH_ASSERT(reporter, 2 == fe->depth());
    FORTH_ASSERT(reporter, -17 == fe->peek(0));
    FORTH_ASSERT(reporter, -17 == fe->peek(1));
}

static void swap_test(ForthWord* word, ForthEngine* fe, Reporter* reporter) {
    fe->push(-17);
    fe->push(42);
    word->exec(fe);
    FORTH_ASSERT(reporter, 2 == fe->depth());
    FORTH_ASSERT(reporter, -17 == fe->peek(0));
    FORTH_ASSERT(reporter, 42 == fe->peek(1));
}

static void over_test(ForthWord* word, ForthEngine* fe, Reporter* reporter) {
    fe->push(1);
    fe->push(2);
    word->exec(fe);
    FORTH_ASSERT(reporter, 3 == fe->depth());
    FORTH_ASSERT(reporter, 1 == fe->peek(0));
    FORTH_ASSERT(reporter, 2 == fe->peek(1));
    FORTH_ASSERT(reporter, 1 == fe->peek(2));
}

static void rot_test(ForthWord* word, ForthEngine* fe, Reporter* reporter) {
    fe->push(1);
    fe->push(2);
    fe->push(3);
    word->exec(fe);
    FORTH_ASSERT(reporter, 3 == fe->depth());
    FORTH_ASSERT(reporter, 2 == fe->peek(2));
    FORTH_ASSERT(reporter, 3 == fe->peek(1));
    FORTH_ASSERT(reporter, 1 == fe->peek(0));
}

static void rrot_test(ForthWord* word, ForthEngine* fe, Reporter* reporter) {
    fe->push(1);
    fe->push(2);
    fe->push(3);
    word->exec(fe);
    FORTH_ASSERT(reporter, 3 == fe->depth());
    FORTH_ASSERT(reporter, 2 == fe->peek(0));
    FORTH_ASSERT(reporter, 1 == fe->peek(1));
    FORTH_ASSERT(reporter, 3 == fe->peek(2));
}

static void swap2_test(ForthWord* word, ForthEngine* fe, Reporter* reporter) {
    fe->push(1);
    fe->push(2);
    fe->push(3);
    fe->push(4);
    word->exec(fe);
    FORTH_ASSERT(reporter, 4 == fe->depth());
    FORTH_ASSERT(reporter, 2 == fe->peek(0));
    FORTH_ASSERT(reporter, 1 == fe->peek(1));
    FORTH_ASSERT(reporter, 4 == fe->peek(2));
    FORTH_ASSERT(reporter, 3 == fe->peek(3));
}

static void dup2_test(ForthWord* word, ForthEngine* fe, Reporter* reporter) {
    fe->push(1);
    fe->push(2);
    word->exec(fe);
    FORTH_ASSERT(reporter, 4 == fe->depth());
    FORTH_ASSERT(reporter, 1 == fe->peek(3));
    FORTH_ASSERT(reporter, 2 == fe->peek(2));
    FORTH_ASSERT(reporter, 1 == fe->peek(1));
    FORTH_ASSERT(reporter, 2 == fe->peek(0));
}

static void over2_test(ForthWord* word, ForthEngine* fe, Reporter* reporter) {
    fe->push(1);
    fe->push(2);
    fe->push(3);
    fe->push(4);
    word->exec(fe);
    FORTH_ASSERT(reporter, 6 == fe->depth());
    FORTH_ASSERT(reporter, 1 == fe->peek(5));
    FORTH_ASSERT(reporter, 2 == fe->peek(4));
    FORTH_ASSERT(reporter, 3 == fe->peek(3));
    FORTH_ASSERT(reporter, 4 == fe->peek(2));
    FORTH_ASSERT(reporter, 1 == fe->peek(1));
    FORTH_ASSERT(reporter, 2 == fe->peek(0));
}

static void drop2_test(ForthWord* word, ForthEngine* fe, Reporter* reporter) {
    fe->push(1);
    fe->push(2);
    fe->push(3);
    fe->push(4);
    word->exec(fe);
    FORTH_ASSERT(reporter, 2 == fe->depth());
    FORTH_ASSERT(reporter, 1 == fe->peek(1));
    FORTH_ASSERT(reporter, 2 == fe->peek(0));
}

//////////////////////////////////////////////////////////////////////////////

static void iadd_test(ForthWord* word, ForthEngine* fe, Reporter* reporter) {
    fe->push(35);
    fe->push(99);
    word->exec(fe);
    FORTH_ASSERT(reporter, 1 == fe->depth());
    FORTH_ASSERT(reporter, 134 == fe->top());
    fe->push(-135);
    word->exec(fe);
    FORTH_ASSERT(reporter, 1 == fe->depth());
    FORTH_ASSERT(reporter, -1 == fe->top());
}

static void isub_test(ForthWord* word, ForthEngine* fe, Reporter* reporter) {
    fe->push(35);
    fe->push(99);
    word->exec(fe);
    FORTH_ASSERT(reporter, 1 == fe->depth());
    FORTH_ASSERT(reporter, 35-99 == fe->top());
}

static void imul_test(ForthWord* word, ForthEngine* fe, Reporter* reporter) {
    fe->push(15);
    fe->push(-20);
    word->exec(fe);
    FORTH_ASSERT(reporter, 1 == fe->depth());
    FORTH_ASSERT(reporter, -300 == fe->top());
    fe->push(0);
    word->exec(fe);
    FORTH_ASSERT(reporter, 1 == fe->depth());
    FORTH_ASSERT(reporter, 0 == fe->top());
}

static void idiv_test(ForthWord* word, ForthEngine* fe, Reporter* reporter) {
    fe->push(100);
    fe->push(25);
    word->exec(fe);
    FORTH_ASSERT(reporter, 1 == fe->depth());
    FORTH_ASSERT(reporter, 4 == fe->top());
    fe->setTop(10);
    fe->push(-3);
    word->exec(fe);
    FORTH_ASSERT(reporter, 1 == fe->depth());
    FORTH_ASSERT(reporter, -3 == fe->top());
    fe->setTop(-1);
    fe->push(-1);
    word->exec(fe);
    FORTH_ASSERT(reporter, 1 == fe->depth());
    FORTH_ASSERT(reporter, 1 == fe->top());
}

static void imod_test(ForthWord* word, ForthEngine* fe, Reporter* reporter) {
    fe->push(10);
    fe->push(3);
    word->exec(fe);
    FORTH_ASSERT(reporter, 1 == fe->depth());
    FORTH_ASSERT(reporter, 1 == fe->top());
    fe->push(5);
    word->exec(fe);
    FORTH_ASSERT(reporter, 1 == fe->depth());
    FORTH_ASSERT(reporter, 1 == fe->top());
}

static void idivmod_test(ForthWord* word, ForthEngine* fe, Reporter* reporter) {
    fe->push(10);
    fe->push(3);
    word->exec(fe);
    FORTH_ASSERT(reporter, 2 == fe->depth());
    FORTH_ASSERT(reporter, 1 == fe->peek(1));
    FORTH_ASSERT(reporter, 3 == fe->peek(0));
}

static void idot_test(ForthWord* word, ForthEngine* fe, Reporter* reporter) {
    fe->push(1);
    fe->push(2);
    word->exec(fe);
    FORTH_ASSERT(reporter, 1 == fe->depth());
    FORTH_ASSERT(reporter, 1 == fe->top());
}

static void iabs_test(ForthWord* word, ForthEngine* fe, Reporter* reporter) {
    fe->push(10);
    word->exec(fe);
    FORTH_ASSERT(reporter, 1 == fe->depth());
    FORTH_ASSERT(reporter, 10 == fe->top());
    fe->setTop(-10);
    word->exec(fe);
    FORTH_ASSERT(reporter, 1 == fe->depth());
    FORTH_ASSERT(reporter, 10 == fe->top());
}

static void inegate_test(ForthWord* word, ForthEngine* fe, Reporter* reporter) {
    fe->push(10);
    word->exec(fe);
    FORTH_ASSERT(reporter, 1 == fe->depth());
    FORTH_ASSERT(reporter, -10 == fe->top());
    fe->setTop(-10);
    word->exec(fe);
    FORTH_ASSERT(reporter, 1 == fe->depth());
    FORTH_ASSERT(reporter, 10 == fe->top());
}

static void imin_test(ForthWord* word, ForthEngine* fe, Reporter* reporter) {
    fe->push(10);
    fe->push(3);
    word->exec(fe);
    FORTH_ASSERT(reporter, 1 == fe->depth());
    FORTH_ASSERT(reporter, 3 == fe->top());
    fe->push(-10);
    word->exec(fe);
    FORTH_ASSERT(reporter, 1 == fe->depth());
    FORTH_ASSERT(reporter, -10 == fe->top());
}

static void imax_test(ForthWord* word, ForthEngine* fe, Reporter* reporter) {
    fe->push(10);
    fe->push(3);
    word->exec(fe);
    FORTH_ASSERT(reporter, 1 == fe->depth());
    FORTH_ASSERT(reporter, 10 == fe->top());
    fe->push(-10);
    word->exec(fe);
    FORTH_ASSERT(reporter, 1 == fe->depth());
    FORTH_ASSERT(reporter, 10 == fe->top());
}

///////////////////////////////////////////////////////////////////////////////

static void logical_and_test(ForthWord* word, ForthEngine* fe, Reporter* reporter) {
    const static int data[] = {
        0, 0, 0,
        2, 0, 0,
        0, -1, 0,
        1, 5, -1
    };
    for (size_t i = 0; i < SK_ARRAY_COUNT(data)/3; i++) {
        fe->push(data[i*3 + 0]);
        fe->push(data[i*3 + 1]);
        word->exec(fe);
        FORTH_ASSERT(reporter, 1 == fe->depth());
        FORTH_ASSERT(reporter, data[i*3 + 2] == fe->top());
        fe->pop();
    }
}

static void logical_or_test(ForthWord* word, ForthEngine* fe, Reporter* reporter) {
    const static int data[] = {
        0, 0, 0,
        2, 0, -1,
        0, -1, -1,
        1, 5, -1
    };
    for (size_t i = 0; i < SK_ARRAY_COUNT(data)/3; i++) {
        fe->push(data[i*3 + 0]);
        fe->push(data[i*3 + 1]);
        word->exec(fe);
        FORTH_ASSERT(reporter, 1 == fe->depth());
        FORTH_ASSERT(reporter, data[i*3 + 2] == fe->top());
        fe->pop();
    }
}

static void logical_not_test(ForthWord* word, ForthEngine* fe, Reporter* reporter) {
    const static int data[] = {
        0, -1,
        5, 0,
        -1, 0
    };
    for (size_t i = 0; i < SK_ARRAY_COUNT(data)/2; i++) {
        fe->push(data[i*2 + 0]);
        word->exec(fe);
        FORTH_ASSERT(reporter, 1 == fe->depth());
        FORTH_ASSERT(reporter, data[i*2 + 1] == fe->top());
        fe->pop();
    }
}

static void if_dup_test(ForthWord* word, ForthEngine* fe, Reporter* reporter) {
    fe->push(10);
    word->exec(fe);
    FORTH_ASSERT(reporter, 2 == fe->depth());
    FORTH_ASSERT(reporter, 10 == fe->peek(1));
    FORTH_ASSERT(reporter, 10 == fe->peek(0));
    fe->pop();
    fe->pop();
    fe->push(0);
    word->exec(fe);
    FORTH_ASSERT(reporter, 1 == fe->depth());
    FORTH_ASSERT(reporter, 0 == fe->top());
}

static const struct {
    const char*         fName;
    ForthWordTestProc   fProc;
} gRecs[] = {
    { "DROP",   drop_test0 },   { "DROP",   drop_test1 },
    { "DUP",    dup_test },
    { "SWAP",   swap_test },
    { "OVER",   over_test },
    { "ROT",    rot_test },
    { "-ROT",   rrot_test },
    { "2SWAP",  swap2_test },
    { "2DUP",   dup2_test },
    { "2OVER",  over2_test },
    { "2DROP",  drop2_test },

    { "+",      iadd_test },
    { "-",      isub_test },
    { "*",      imul_test },
    { "/",      idiv_test },
    { "MOD",    imod_test },
    { "/MOD",   idivmod_test },

//    { ".",      idot_test },
    { "ABS",    iabs_test },
    { "NEGATE", inegate_test },
    { "MIN",    imin_test },
    { "MAX",    imax_test },

    { "AND",    logical_and_test },
    { "OR",     logical_or_test },
    { "0=",     logical_not_test },
    { "?DUP",   if_dup_test },
};

///////////////////////////////////////////////////////////////////////////////

void Reporter::reportFailure(const char expression[], const char file[],
                             int line) {
    SkDebugf("failed %s:%d: %s\n", file, line, expression);
    fFailureCount += 1;
}

void Reporter::reportFailure(const char msg[]) {
    SkDebugf("%s\n");
    fFailureCount += 1;
}

void Forth_test_stdwords(bool verbose);
void Forth_test_stdwords(bool verbose) {
    ForthEnv env;
    Reporter reporter;

    for (size_t i = 0; i < SK_ARRAY_COUNT(gRecs); i++) {
        ForthEngine engine(NULL);

        ForthWord* word = env.findWord(gRecs[i].fName);
        if (NULL == word) {
            SkString str;
            str.printf("--- can't find stdword %d", gRecs[i].fName);
            reporter.reportFailure(str.c_str());
        } else {
            if (verbose) {
                SkDebugf("--- testing %s %p\n", gRecs[i].fName, word);
            }
            gRecs[i].fProc(word, &engine, &reporter);
        }
    }

    if (0 == reporter.fFailureCount) {
        SkDebugf("--- success!\n");
    } else {
        SkDebugf("--- %d failures\n", reporter.fFailureCount);
    }
}
