diff options
author | Alyssa Milburn | 2011-07-07 09:24:10 +0200 |
---|---|---|
committer | Alyssa Milburn | 2011-07-07 09:24:10 +0200 |
commit | 66e81d633f987310f12038147ae01b8ce4f640f7 (patch) | |
tree | 6035a7124789c3e6cdff5f603e9933529b7efdc7 /engines/mohawk/livingbooks_code.cpp | |
parent | affaa1f4d6cf5f27f654029133b1aec7b9eca4b5 (diff) | |
parent | 72da8ef5adf82d8a65da299207f30af5058ca8a9 (diff) | |
download | scummvm-rg350-66e81d633f987310f12038147ae01b8ce4f640f7.tar.gz scummvm-rg350-66e81d633f987310f12038147ae01b8ce4f640f7.tar.bz2 scummvm-rg350-66e81d633f987310f12038147ae01b8ce4f640f7.zip |
Merge remote-tracking branch 'origin/master' into soltys_wip2
Diffstat (limited to 'engines/mohawk/livingbooks_code.cpp')
-rw-r--r-- | engines/mohawk/livingbooks_code.cpp | 491 |
1 files changed, 432 insertions, 59 deletions
diff --git a/engines/mohawk/livingbooks_code.cpp b/engines/mohawk/livingbooks_code.cpp index e72318d86a..e9ef2516e2 100644 --- a/engines/mohawk/livingbooks_code.cpp +++ b/engines/mohawk/livingbooks_code.cpp @@ -127,6 +127,12 @@ Common::Rect LBValue::toRect() const { } LBCode::LBCode(MohawkEngine_LivingBooks *vm, uint16 baseId) : _vm(vm) { + if (!baseId) { + _data = NULL; + _size = 0; + return; + } + Common::SeekableSubReadStreamEndian *bcodStream = _vm->wrapStreamEndian(ID_BCOD, baseId); uint32 totalSize = bcodStream->readUint32(); @@ -172,12 +178,8 @@ LBValue LBCode::runCode(LBItem *src, uint32 offset) { } void LBCode::nextToken() { - if (_currOffset + 1 >= _size) { - // TODO - warning("went off the end of code"); - _currToken = kTokenEndOfFile; - _currValue = LBValue(); - return; + if (_currOffset >= _size) { + error("went off the end of code"); } _currToken = _data[_currOffset++]; @@ -186,6 +188,8 @@ void LBCode::nextToken() { switch (_currToken) { case kTokenIdentifier: { + if (_currOffset + 2 > _size) + error("went off the end of code reading identifier"); uint16 offset = READ_BE_UINT16(_data + _currOffset); // TODO: check string exists _currValue = _strings[offset]; @@ -195,9 +199,13 @@ void LBCode::nextToken() { case kTokenLiteral: { + if (_currOffset + 1 > _size) + error("went off the end of code reading literal"); byte literalType = _data[_currOffset++]; switch (literalType) { case kLBCodeLiteralInteger: + if (_currOffset + 2 > _size) + error("went off the end of code reading literal integer"); _currValue = READ_BE_UINT16(_data + _currOffset); _currOffset += 2; break; @@ -211,6 +219,8 @@ void LBCode::nextToken() { case kTokenConstEventId: case 0x5e: // TODO: ?? case kTokenKeycode: + if (_currOffset + 2 > _size) + error("went off the end of code reading immediate"); _currValue = READ_BE_UINT16(_data + _currOffset); _currOffset += 2; break; @@ -227,6 +237,8 @@ void LBCode::nextToken() { case kTokenString: { + if (_currOffset + 2 > _size) + error("went off the end of code reading string"); uint16 offset = READ_BE_UINT16(_data + _currOffset); // TODO: check string exists _currValue = _strings[offset]; @@ -265,27 +277,27 @@ LBValue LBCode::runCode(byte terminator) { void LBCode::parseStatement() { parseComparisons(); - if (_currToken != kTokenAnd && _currToken != kTokenOr) - return; - byte op = _currToken; - if (op == kTokenAnd) - debugN(" && "); - else - debugN(" || "); + while (_currToken == kTokenAnd || _currToken == kTokenOr) { + byte op = _currToken; + if (op == kTokenAnd) + debugN(" && "); + else + debugN(" || "); - nextToken(); - parseComparisons(); + nextToken(); + parseComparisons(); - LBValue val2 = _stack.pop(); - LBValue val1 = _stack.pop(); - bool result; - if (op == kTokenAnd) - result = !val1.isZero() && !val2.isZero(); - else - result = !val1.isZero() || !val2.isZero(); + LBValue val2 = _stack.pop(); + LBValue val1 = _stack.pop(); + bool result; + if (op == kTokenAnd) + result = !val1.isZero() && !val2.isZero(); + else + result = !val1.isZero() || !val2.isZero(); - debugN(" [--> %s]", result ? "true" : "false"); - _stack.push(result); + debugN(" [--> %s]", result ? "true" : "false"); + _stack.push(result); + } } void LBCode::parseComparisons() { @@ -353,49 +365,95 @@ void LBCode::parseComparisons() { void LBCode::parseConcat() { parseArithmetic1(); - if (_currToken != kTokenConcat) - return; - - debugN(" & "); - nextToken(); - parseArithmetic1(); + while (_currToken == kTokenConcat) { + debugN(" & "); + nextToken(); + parseArithmetic1(); - LBValue val2 = _stack.pop(); - LBValue val1 = _stack.pop(); - Common::String result = val1.toString() + val2.toString(); - debugN(" [--> \"%s\"]", result.c_str()); - _stack.push(result); + LBValue val2 = _stack.pop(); + LBValue val1 = _stack.pop(); + Common::String result = val1.toString() + val2.toString(); + debugN(" [--> \"%s\"]", result.c_str()); + _stack.push(result); + } } void LBCode::parseArithmetic1() { parseArithmetic2(); - if (_currToken != kTokenMinus && _currToken != kTokenPlus) - return; - - byte op = _currToken; - if (op == kTokenMinus) - debugN(" - "); - else if (op == kTokenPlus) - debugN(" + "); + while (_currToken == kTokenMinus || _currToken == kTokenPlus) { + byte op = _currToken; + if (op == kTokenMinus) + debugN(" - "); + else if (op == kTokenPlus) + debugN(" + "); - nextToken(); - parseArithmetic2(); - - LBValue val2 = _stack.pop(); - LBValue val1 = _stack.pop(); - LBValue result; - // TODO: cope with non-integers - if (op == kTokenMinus) - result = val1.toInt() - val2.toInt(); - else - result = val1.toInt() + val2.toInt(); - _stack.push(result); + nextToken(); + parseArithmetic2(); + + LBValue val2 = _stack.pop(); + LBValue val1 = _stack.pop(); + LBValue result; + // TODO: cope with non-integers + if (op == kTokenMinus) + result = val1.toInt() - val2.toInt(); + else + result = val1.toInt() + val2.toInt(); + debugN(" [--> %d]", result.toInt()); + _stack.push(result); + } } void LBCode::parseArithmetic2() { - // FIXME: other math operators parseMain(); + + while (true) { + byte op = _currToken; + switch (op) { + case kTokenMultiply: + debugN(" * "); + break; + case kTokenDivide: + debugN(" / "); + break; + case kTokenIntDivide: + debugN(" div "); + break; + case kTokenModulo: + debugN(" %% "); + break; + default: + return; + } + + nextToken(); + parseMain(); + + LBValue val2 = _stack.pop(); + LBValue val1 = _stack.pop(); + LBValue result; + // TODO: cope with non-integers + if (op == kTokenMultiply) { + result = val1.toInt() * val2.toInt(); + } else if (val2.toInt() == 0) { + result = 1; + } else { + switch (op) { + case kTokenDivide: + // TODO: fp divide + result = val1.toInt() / val2.toInt(); + break; + case kTokenIntDivide: + result = val1.toInt() / val2.toInt(); + break; + case kTokenModulo: + result = val1.toInt() % val2.toInt(); + break; + } + } + + _stack.push(result); + } } void LBCode::parseMain() { @@ -549,6 +607,16 @@ void LBCode::parseMain() { } } +LBItem *LBCode::resolveItem(const LBValue &value) { + if (value.type == kLBValueItemPtr) + return value.item; + if (value.type == kLBValueString) + return _vm->getItemByName(value.string); + if (value.type == kLBValueInteger) + return _vm->getItemById(value.integer); + return NULL; +} + Common::Array<LBValue> LBCode::readParams() { Common::Array<LBValue> params; @@ -616,8 +684,8 @@ struct CodeCommandInfo { #define NUM_GENERAL_COMMANDS 129 CodeCommandInfo generalCommandInfo[NUM_GENERAL_COMMANDS] = { - { "eval", 0 }, - { "random", 0 }, + { "eval", &LBCode::cmdEval }, + { "random", &LBCode::cmdRandom }, { "stringLen", 0 }, { "substring", 0 }, { "max", 0 }, @@ -773,6 +841,26 @@ void LBCode::cmdUnimplemented(const Common::Array<LBValue> ¶ms) { warning("unimplemented command called"); } +void LBCode::cmdEval(const Common::Array<LBValue> ¶ms) { + // FIXME: v4 eval is different? + if (params.size() != 1) + error("incorrect number of parameters (%d) to eval", params.size()); + + LBCode tempCode(_vm, 0); + + uint offset = tempCode.parseCode(params[0].toString()); + _stack.push(tempCode.runCode(_currSource, offset)); +} + +void LBCode::cmdRandom(const Common::Array<LBValue> ¶ms) { + if (params.size() != 2) + error("incorrect number of parameters (%d) to random", params.size()); + + int min = params[0].toInt(); + int max = params[1].toInt(); + _stack.push(_vm->_rnd->getRandomNumberRng(min, max)); +} + void LBCode::cmdGetRect(const Common::Array<LBValue> ¶ms) { if (params.size() < 2) { _stack.push(getRectFromParams(params)); @@ -915,7 +1003,7 @@ CodeCommandInfo itemCommandInfo[NUM_ITEM_COMMANDS] = { { "moveTo", &LBCode::itemMoveTo }, { "mute", 0 }, { "play", 0 }, - { "seek", 0 }, + { "seek", &LBCode::itemSeek }, { "seekToFrame", 0 }, { "setParent", &LBCode::itemSetParent }, { "setZOrder", 0 }, @@ -951,6 +1039,17 @@ void LBCode::itemMoveTo(const Common::Array<LBValue> ¶ms) { warning("ignoring moveTo"); } +void LBCode::itemSeek(const Common::Array<LBValue> ¶ms) { + if (params.size() != 2) + error("incorrect number of parameters (%d) to seek", params.size()); + + LBItem *item = resolveItem(params[0]); + if (!item) + error("attempted seek on invalid item (%s)", params[0].toString().c_str()); + uint seekTo = params[1].toInt(); + item->seek(seekTo); +} + void LBCode::itemSetParent(const Common::Array<LBValue> ¶ms) { if (params.size() > 2) error("incorrect number of parameters (%d) to setParent", params.size()); @@ -1035,4 +1134,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 |