diff options
-rw-r--r-- | engines/mohawk/livingbooks_code.cpp | 274 | ||||
-rw-r--r-- | engines/mohawk/livingbooks_code.h | 4 |
2 files changed, 278 insertions, 0 deletions
diff --git a/engines/mohawk/livingbooks_code.cpp b/engines/mohawk/livingbooks_code.cpp index 0aaedfe274..89a77fbcfb 100644 --- a/engines/mohawk/livingbooks_code.cpp +++ b/engines/mohawk/livingbooks_code.cpp @@ -1057,4 +1057,278 @@ void LBCode::runNotifyCommand() { } } +/* + * Helper function for parseCode/parseCodeSymbol: + * Returns an unused string id. + */ +uint LBCode::nextFreeString() { + for (uint i = 0; i <= 0xffff; i++) { + if (!_strings.contains(i)) + return i; + } + + error("nextFreeString couldn't find a space"); +} + +/* + * Helper function for parseCode: + * Given a name, appends the appropriate data to the provided code array and + * returns true if it's a function, or false otherwise. + */ +bool LBCode::parseCodeSymbol(const Common::String &name, uint &pos, Common::Array<byte> &code) { + // first, check whether the name matches a known function + for (uint i = 0; i < 2; i++) { + byte cmdToken; + CodeCommandInfo *cmdInfo; + uint cmdCount; + + switch (i) { + case 0: + cmdInfo = generalCommandInfo; + cmdToken = kTokenGeneralCommand; + cmdCount = NUM_GENERAL_COMMANDS; + break; + case 1: + cmdInfo = itemCommandInfo; + cmdToken = kTokenItemCommand; + cmdCount = NUM_ITEM_COMMANDS; + break; + } + + for (uint n = 0; n < cmdCount; n++) { + const char *cmdName = cmdInfo[n].name; + if (!cmdName) + continue; + if (!name.equalsIgnoreCase(cmdName)) + continue; + + // found a matching function + code.push_back(cmdToken); + code.push_back(n + 1); + return true; + } + } + + // not a function, so must be an identifier + code.push_back(kTokenIdentifier); + + uint stringId = nextFreeString(); + _strings[stringId] = name; + + char tmp[2]; + WRITE_BE_UINT16(tmp, (int16)stringId); + code.push_back(tmp[0]); + code.push_back(tmp[1]); + + return false; +} + +/* + * Parse a string for later execution, and return the offset where it was + * stored. + */ +uint LBCode::parseCode(const Common::String &source) { + struct LBCodeOperator { + byte token; + byte op; + byte lookahead1; + byte lookahead1Op; + byte lookahead2; + byte lookahead2Op; + }; + + #define NUM_LB_OPERATORS 11 + static const LBCodeOperator operators[NUM_LB_OPERATORS] = { + { '+', kTokenPlus, '+', kTokenPlusPlus, '=', kTokenPlusEquals }, + { '-', kTokenMinus, '-', kTokenMinusMinus, '=', kTokenMinusEquals }, + { '/', kTokenDivide, '=', kTokenDivideEquals, 0, 0 }, + { '*', kTokenMultiply, '=', kTokenMultiplyEquals, 0, 0 }, + { '=', kTokenAssign, '=', kTokenEquals, 0, 0 }, + { '>', kTokenGreaterThan, '=', kTokenGreaterThanEq, 0, 0 }, + { '<', kTokenLessThan, '=', kTokenLessThanEq, 0, 0 }, + { '!', kTokenNot, '=', kTokenNotEq, 0, 0 }, + { '&', kTokenConcat, '&', kTokenAnd, '=', kTokenAndEquals }, + { '|', 0, '|', kTokenOr, 0, 0 }, + { ';', kTokenEndOfStatement, 0, 0, 0, 0 } + }; + + uint pos = 0; + Common::Array<byte> code; + Common::Array<uint> counterPositions; + bool wasFunction = false; + + while (pos < source.size()) { + byte token = source[pos]; + byte lookahead = 0; + if (pos + 1 < source.size()) + lookahead = source[pos + 1]; + pos++; + + if (token != ' ' && token != '(' && wasFunction) + error("while parsing script '%s', encountered incomplete function call", source.c_str()); + + // First, we check for simple operators. + for (uint i = 0; i < NUM_LB_OPERATORS; i++) { + if (token != operators[i].token) + continue; + if (lookahead) { + if (lookahead == operators[i].lookahead1) { + code.push_back(operators[i].lookahead1Op); + token = 0; + } else if (lookahead == operators[i].lookahead2) { + code.push_back(operators[i].lookahead2Op); + token = 0; + } + if (!token) { + pos++; + break; + } + } + if (operators[i].op) { + code.push_back(operators[i].op); + token = 0; + } + break; + } + if (!token) + continue; + + // Then, we check for more complex tokens. + switch (token) { + // whitespace + case ' ': + // ignore + break; + // literal string + case '"': + case '\'': + { + Common::String tempString; + while (pos < source.size()) { + if (source[pos] == token) + break; + tempString += source[pos++]; + } + if (pos++ == source.size()) + error("while parsing script '%s', string had no end", source.c_str()); + + code.push_back(kTokenString); + + uint stringId = nextFreeString(); + _strings[stringId] = tempString; + + char tmp[2]; + WRITE_BE_UINT16(tmp, (int16)stringId); + code.push_back(tmp[0]); + code.push_back(tmp[1]); + } + break; + // open bracket + case '(': + if (wasFunction) { + // function call parameters + wasFunction = false; + // we will need to back-patch the parameter count, + // if parameters are encountered + counterPositions.push_back(code.size()); + code.push_back(1); + // if the next token is a ) then there are no + // parameters, otherwise start with 1 and increment + // if/when we encounter commas + for (uint i = pos; i < source.size(); i++) { + if (source[i] == ' ') + continue; + if (source[i] != ')') + break; + code[code.size() - 1] = 0; + break; + } + } else { + // brackets around expression + counterPositions.push_back(0); + } + code.push_back(kTokenOpenBracket); + break; + // close bracket + case ')': + if (counterPositions.empty()) + error("while parsing script '%s', encountered unmatched )", source.c_str()); + counterPositions.pop_back(); + code.push_back(kTokenCloseBracket); + break; + // comma (seperating function params) + case ',': + { + if (counterPositions.empty()) + error("while parsing script '%s', encountered unexpected ,", source.c_str()); + code.push_back(kTokenComma); + uint counterPos = counterPositions.back(); + if (!counterPos) + error("while parsing script '%s', encountered , outside parameter list", source.c_str()); + code[counterPos]++; + } + break; + // old-style explicit function call + case '@': + { + Common::String tempString; + while (pos < source.size()) { + if (!isalpha(source[pos]) && !isdigit(source[pos])) + break; + tempString += source[pos++]; + } + wasFunction = parseCodeSymbol(tempString, pos, code); + if (!wasFunction) + error("while parsing script '%s', encountered explicit function call to unknown function '%s'", + source.c_str(), tempString.c_str()); + } + break; + default: + if (isdigit(token)) { + const char *in = source.c_str() + pos - 1; + // FIXME: handle floats? + char *endptr; + long int intValue = strtol(in, &endptr, 0); + assert(endptr > in); + pos += (endptr - in) - 1; + + // FIXME: handle storing longs if needed + code.push_back(kTokenLiteral); + code.push_back(kLBCodeLiteralInteger); + char tmp[2]; + WRITE_BE_UINT16(tmp, (int16)intValue); + code.push_back(tmp[0]); + code.push_back(tmp[1]); + } else if (isalpha(token)) { + Common::String tempString; + tempString += token; + while (pos < source.size()) { + if (!isalpha(source[pos]) && !isdigit(source[pos])) + break; + tempString += source[pos++]; + } + wasFunction = parseCodeSymbol(tempString, pos, code); + } else { + error("while parsing script '%s', couldn't parse '%c'", source.c_str(), token); + } + } + } + + if (wasFunction) + error("while parsing script '%s', encountered incomplete function call", source.c_str()); + if (counterPositions.size()) + error("while parsing script '%s', unmatched (", source.c_str()); + + code.push_back(kTokenEndOfFile); + + uint codeOffset = _size; + byte *newData = new byte[_size + code.size()]; + memcpy(newData, _data, _size); + memcpy(newData, &code[0], code.size()); + delete[] _data; + _data = newData; + _size += code.size(); + return codeOffset; +} + } // End of namespace Mohawk diff --git a/engines/mohawk/livingbooks_code.h b/engines/mohawk/livingbooks_code.h index 3e33549b4d..85bb706515 100644 --- a/engines/mohawk/livingbooks_code.h +++ b/engines/mohawk/livingbooks_code.h @@ -181,6 +181,7 @@ public: ~LBCode(); LBValue runCode(LBItem *src, uint32 offset); + uint parseCode(const Common::String &source); protected: MohawkEngine_LivingBooks *_vm; @@ -214,6 +215,9 @@ protected: void runItemCommand(); void runNotifyCommand(); + uint nextFreeString(); + bool parseCodeSymbol(const Common::String &name, uint &pos, Common::Array<byte> &code); + public: void cmdUnimplemented(const Common::Array<LBValue> ¶ms); void cmdGetRect(const Common::Array<LBValue> ¶ms); |