| /* | |
| * 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 "ForthParser.h" | |
| #include "SkTDArray.h" | |
| #include "SkString.h" | |
| #include "SkTDStack.h" | |
| ForthEngine::ForthEngine(ForthOutput* output) : fOutput(output) { | |
| size_t size = 32 * sizeof(intptr_t); | |
| fStackBase = reinterpret_cast<intptr_t*>(sk_malloc_throw(size)); | |
| fStackStop = fStackBase + size/sizeof(intptr_t); | |
| fStackCurr = fStackStop; | |
| } | |
| ForthEngine::~ForthEngine() { | |
| sk_free(fStackBase); | |
| } | |
| void ForthEngine::sendOutput(const char text[]) { | |
| if (fOutput) { | |
| fOutput->show(text); | |
| } else { | |
| SkDebugf("%s", text); | |
| } | |
| } | |
| void ForthEngine::push(intptr_t value) { | |
| if (fStackCurr > fStackBase) { | |
| SkASSERT(fStackCurr <= fStackStop && fStackCurr > fStackBase); | |
| *--fStackCurr = value; | |
| } else { | |
| this->signal_error("overflow"); | |
| } | |
| } | |
| intptr_t ForthEngine::peek(size_t index) const { | |
| SkASSERT(fStackCurr < fStackStop && fStackCurr >= fStackBase); | |
| if (fStackCurr + index < fStackStop) { | |
| return fStackCurr[index]; | |
| } else { | |
| this->signal_error("peek out of range"); | |
| return 0x80000001; | |
| } | |
| } | |
| void ForthEngine::setTop(intptr_t value) { | |
| if (fStackCurr < fStackStop) { | |
| SkASSERT(fStackCurr < fStackStop && fStackCurr >= fStackBase); | |
| *fStackCurr = value; | |
| } else { | |
| this->signal_error("underflow"); | |
| } | |
| } | |
| intptr_t ForthEngine::pop() { | |
| if (fStackCurr < fStackStop) { | |
| SkASSERT(fStackCurr < fStackStop && fStackCurr >= fStackBase); | |
| return *fStackCurr++; | |
| } else { | |
| this->signal_error("underflow"); | |
| return 0x80000001; | |
| } | |
| } | |
| /////////////////////////////////////////////////////////////////////////////// | |
| void ForthWord::call(ForthCallBlock* block) { | |
| ForthEngine engine(NULL); | |
| // setup the initial stack with the callers input data | |
| if (block) { | |
| // walk the array backwards, so that the top of the stack is data[0] | |
| for (size_t i = 0; i < block->in_count; i++) { | |
| engine.push(block->in_data[i]); | |
| } | |
| } | |
| // now invoke the word | |
| this->exec(&engine); | |
| // now copy back the stack into the caller's output data | |
| if (block) { | |
| size_t n = engine.depth(); | |
| block->out_depth = n; | |
| if (n > block->out_count) { | |
| n = block->out_count; | |
| } | |
| for (size_t i = 0; i < n; i++) { | |
| block->out_data[i] = engine.peek(i); | |
| } | |
| } | |
| } | |
| /////////////////////////////////////////////////////////////////////////////// | |
| /* | |
| reading an initial 32bit value from the code stream: | |
| xxxxxxxx xxxxxxxx xxxxxxxx xxxxxx00 | |
| Those last two bits are always 0 for a word, so we set those bits for other | |
| opcodes | |
| 00 -- execute this word | |
| 01 -- push (value & ~3) on the data stack | |
| 10 -- push value >> 2 on the data stack (sign extended) | |
| 11 -- switch (value >>> 2) for Code | |
| */ | |
| class FCode { | |
| public: | |
| enum { | |
| kCodeShift = 2, | |
| kCodeMask = 7, | |
| kCodeDataShift = 5 | |
| }; | |
| static unsigned GetCode(intptr_t c) { | |
| return ((uint32_t)c >> kCodeShift) & kCodeMask; | |
| } | |
| static unsigned GetCodeData(intptr_t c) { | |
| return (uint32_t)c >> kCodeDataShift; | |
| } | |
| enum Bits { | |
| kWord_Bits = 0, // must be zero for function address | |
| kDataClear2_Bits = 1, | |
| kDataShift2_Bits = 2, | |
| kCodeShift2_Bits = 3 | |
| }; | |
| enum Code { | |
| kPushInt_Code, // for data that needs more than 30 bits | |
| kIF_Code, | |
| kELSE_Code, | |
| kDone_Code | |
| }; | |
| static unsigned MakeCode(Code code) { | |
| return (code << kCodeShift) | kCodeShift2_Bits; | |
| } | |
| void appendInt(int32_t); | |
| void appendWord(ForthWord*); | |
| void appendIF(); | |
| bool appendELSE(); | |
| bool appendTHEN(); | |
| void done(); | |
| intptr_t* detach() { | |
| this->done(); | |
| return fData.detach(); | |
| } | |
| intptr_t* begin() { | |
| this->done(); | |
| return fData.begin(); | |
| } | |
| static void Exec(const intptr_t*, ForthEngine*); | |
| private: | |
| SkTDArray<intptr_t> fData; | |
| SkTDStack<size_t> fIfStack; | |
| }; | |
| void FCode::appendInt(int32_t value) { | |
| if ((value & 3) == 0) { | |
| *fData.append() = value | kDataClear2_Bits; | |
| } else if ((value << 2 >> 2) == value) { | |
| *fData.append() = (value << 2) | kDataShift2_Bits; | |
| } else { | |
| intptr_t* p = fData.append(2); | |
| *p++ = (kPushInt_Code << 2) | kCodeShift2_Bits; | |
| *p++ = value; | |
| } | |
| } | |
| void FCode::appendWord(ForthWord* word) { | |
| SkASSERT((reinterpret_cast<intptr_t>(word) & 3) == 0); | |
| *fData.append() = reinterpret_cast<intptr_t>(word); | |
| } | |
| void FCode::appendIF() { | |
| size_t ifIndex = fData.count(); | |
| fIfStack.push(ifIndex); | |
| *fData.append() = MakeCode(kIF_Code); | |
| } | |
| bool FCode::appendELSE() { | |
| if (fIfStack.empty()) { | |
| return false; | |
| } | |
| size_t elseIndex = fData.count(); | |
| *fData.append() = MakeCode(kELSE_Code); | |
| size_t ifIndex = fIfStack.top(); | |
| // record the offset in the data part of the if-code | |
| fData[ifIndex] |= (elseIndex - ifIndex) << kCodeDataShift; | |
| // now reuse this IfStack entry to track the else | |
| fIfStack.top() = elseIndex; | |
| return true; | |
| } | |
| bool FCode::appendTHEN() { | |
| if (fIfStack.empty()) { | |
| return false; | |
| } | |
| // this is either an IF or an ELSE | |
| size_t index = fIfStack.top(); | |
| // record the offset in the data part of the code | |
| fData[index] |= (fData.count() - index - 1) << kCodeDataShift; | |
| fIfStack.pop(); | |
| return true; | |
| } | |
| void FCode::done() { | |
| *fData.append() = (kDone_Code << 2) | kCodeShift2_Bits; | |
| } | |
| void FCode::Exec(const intptr_t* curr, ForthEngine* engine) { | |
| for (;;) { | |
| intptr_t c = *curr++; | |
| switch (c & 3) { | |
| case kWord_Bits: | |
| reinterpret_cast<ForthWord*>(c)->exec(engine); | |
| break; | |
| case kDataClear2_Bits: | |
| engine->push(c & ~3); | |
| break; | |
| case kDataShift2_Bits: | |
| engine->push(c >> 2); | |
| break; | |
| case kCodeShift2_Bits: | |
| switch (GetCode(c)) { | |
| case kPushInt_Code: | |
| engine->push(*curr++); | |
| break; | |
| case kIF_Code: | |
| if (!engine->pop()) { | |
| // takes us past the ELSE or THEN | |
| curr += GetCodeData(c); | |
| } | |
| break; | |
| case kELSE_Code: | |
| // takes us past the THEN | |
| curr += GetCodeData(c); | |
| break; | |
| case kDone_Code: | |
| return; | |
| } | |
| break; | |
| } | |
| } | |
| } | |
| /////////////////////////////////////////////////////////////////////////////// | |
| class CustomWord : public ForthWord { | |
| public: | |
| // we assume ownership of code[] | |
| CustomWord(intptr_t code[]) : fCode(code) {} | |
| virtual ~CustomWord() { sk_free(fCode); } | |
| virtual void exec(ForthEngine* engine) { | |
| FCode::Exec(fCode, engine); | |
| } | |
| private: | |
| intptr_t* fCode; | |
| }; | |
| /////////////////////////////////////////////////////////////////////////////// | |
| ForthParser::ForthParser() : fDict(4096) { | |
| this->addStdWords(); | |
| } | |
| ForthParser::~ForthParser() { | |
| SkTDict<ForthWord*>::Iter iter(fDict); | |
| ForthWord* word; | |
| while (iter.next(&word)) { | |
| delete word; | |
| } | |
| } | |
| static const char* parse_error(const char msg[]) { | |
| SkDebugf("-- parser error: %s\n", msg); | |
| return NULL; | |
| } | |
| /** returns true if c is whitespace, including null | |
| */ | |
| static bool is_ws(int c) { | |
| return c <= ' '; | |
| } | |
| static const char* parse_token(const char** text, size_t* len) { | |
| const char* s = *text; | |
| while (is_ws(*s)) { | |
| if (0 == *s) { | |
| return NULL; | |
| } | |
| s++; | |
| } | |
| const char* token = s++; | |
| while (!is_ws(*s)) { | |
| s++; | |
| } | |
| *text = s; | |
| *len = s - token; | |
| return token; | |
| } | |
| static bool is_digit(int c) { return (unsigned)(c - '0') <= 9; } | |
| static int hex_val(int c) { | |
| if (is_digit(c)) { | |
| return c - '0'; | |
| } else { | |
| if (c <= 'Z') { | |
| return 10 + c - 'A'; | |
| } else { | |
| return 10 + c - 'a'; | |
| } | |
| } | |
| } | |
| static bool parse_num(const char str[], size_t len, int32_t* numBits) { | |
| if (1 == len && !is_digit(*str)) { | |
| return false; | |
| } | |
| const char* start = str; | |
| int32_t num = 0; | |
| bool neg = false; | |
| if (*str == '-') { | |
| neg = true; | |
| str += 1; | |
| } else if (*str == '#') { | |
| str++; | |
| while (str - start < len) { | |
| num = num*16 + hex_val(*str); | |
| str += 1; | |
| } | |
| *numBits = num; | |
| return true; | |
| } | |
| while (is_digit(*str)) { | |
| num = 10*num + *str - '0'; | |
| str += 1; | |
| } | |
| SkASSERT(str - start <= len); | |
| if (str - start == len) { | |
| if (neg) { | |
| num = -num; | |
| } | |
| *numBits = num; | |
| return true; | |
| } | |
| // if we're not done with the token then the next char must be a decimal | |
| if (*str != '.') { | |
| return false; | |
| } | |
| str += 1; | |
| float x = num; | |
| float denom = 1; | |
| while (str - start < len && is_digit(*str)) { | |
| x = 10*x + *str - '0'; | |
| denom *= 10; | |
| str += 1; | |
| } | |
| x /= denom; | |
| if (str - start == len) { | |
| if (neg) { | |
| x = -x; | |
| } | |
| *numBits = f2i_bits(x); | |
| return true; | |
| } | |
| return false; | |
| } | |
| static const char* parse_comment(const char text[]) { | |
| SkASSERT(*text == '('); | |
| while (')' != *++text) { | |
| if (0 == *text) { | |
| return NULL; | |
| } | |
| } | |
| return text + 1; // skip past the closing ')' | |
| } | |
| const char* ForthParser::parse(const char text[], FCode* code) { | |
| for (;;) { | |
| size_t len; | |
| const char* token = parse_token(&text, &len); | |
| if (NULL == token) { | |
| break; | |
| } | |
| if (1 == len) { | |
| if ('(' == *token) { | |
| text = parse_comment(token); | |
| if (NULL == text) { | |
| return NULL; | |
| } | |
| continue; | |
| } | |
| if (';' == *token) { | |
| break; | |
| } | |
| if (':' == *token) { | |
| token = parse_token(&text, &len); | |
| if (NULL == token) { | |
| return parse_error("missing name after ':'"); | |
| } | |
| FCode subCode; | |
| text = this->parse(text, &subCode); | |
| if (NULL == text) { | |
| return NULL; | |
| } | |
| this->add(token, len, new CustomWord(subCode.detach())); | |
| continue; | |
| } | |
| } | |
| int32_t num; | |
| if (parse_num(token, len, &num)) { | |
| // note that num is just the bit representation of the float | |
| code->appendInt(num); | |
| } else if (2 == len && memcmp(token, "IF", 2) == 0) { | |
| code->appendIF(); | |
| } else if (4 == len && memcmp(token, "ELSE", 4) == 0) { | |
| if (!code->appendELSE()) { | |
| return parse_error("ELSE with no matching IF"); | |
| } | |
| } else if (4 == len && memcmp(token, "THEN", 4) == 0) { | |
| if (!code->appendTHEN()) { | |
| return parse_error("THEN with no matching IF"); | |
| } | |
| } else{ | |
| ForthWord* word = this->find(token, len); | |
| if (NULL == word) { | |
| SkString str(token, len); | |
| str.prepend("unknown word "); | |
| return parse_error(str.c_str()); | |
| } | |
| code->appendWord(word); | |
| } | |
| } | |
| return text; | |
| } | |
| /////////////////////////////////////////////////////////////////////////////// | |
| class ForthEnv::Impl { | |
| public: | |
| ForthParser fParser; | |
| FCode fBuilder; | |
| }; | |
| ForthEnv::ForthEnv() { | |
| fImpl = new Impl; | |
| } | |
| ForthEnv::~ForthEnv() { | |
| delete fImpl; | |
| } | |
| void ForthEnv::addWord(const char name[], ForthWord* word) { | |
| fImpl->fParser.addWord(name, word); | |
| } | |
| void ForthEnv::parse(const char text[]) { | |
| fImpl->fParser.parse(text, &fImpl->fBuilder); | |
| } | |
| ForthWord* ForthEnv::findWord(const char name[]) { | |
| return fImpl->fParser.find(name, strlen(name)); | |
| } | |
| void ForthEnv::run(ForthOutput* output) { | |
| ForthEngine engine(output); | |
| FCode::Exec(fImpl->fBuilder.begin(), &engine); | |
| } | |
| #if 0 | |
| void ForthEnv::run(const char text[], ForthOutput* output) { | |
| FCode builder; | |
| if (fImpl->fParser.parse(text, &builder)) { | |
| ForthEngine engine(output); | |
| FCode::Exec(builder.begin(), &engine); | |
| } | |
| } | |
| #endif |