mirror of
https://github.com/libretro/scummvm.git
synced 2025-01-23 11:04:44 +00:00
d2da98d3b0
- Add helpers in Advsys, Comprehend & ZCode to match functionality provided for strings.
699 lines
14 KiB
C++
699 lines
14 KiB
C++
/* ScummVM - Graphic Adventure Engine
|
|
*
|
|
* ScummVM is the legal property of its developers, whose names
|
|
* are too numerous to list here. Please refer to the COPYRIGHT
|
|
* file distributed with this source distribution.
|
|
*
|
|
* This program is free software; you can redistribute it and/or
|
|
* modify it under the terms of the GNU General Public License
|
|
* as published by the Free Software Foundation; either version 2
|
|
* of the License, or (at your option) any later version.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with this program; if not, write to the Free Software
|
|
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
|
*
|
|
*/
|
|
|
|
#include "glk/advsys/vm.h"
|
|
#include "common/translation.h"
|
|
#include "common/ustr.h"
|
|
|
|
namespace Glk {
|
|
namespace AdvSys {
|
|
|
|
#define TRUE -1
|
|
|
|
OpcodeMethod VM::_METHODS[0x34] = {
|
|
&VM::opBRT,
|
|
&VM::opBRF,
|
|
&VM::opBR,
|
|
&VM::opT,
|
|
&VM::opNIL,
|
|
&VM::opPUSH,
|
|
&VM::opNOT,
|
|
&VM::opADD,
|
|
&VM::opSUB,
|
|
&VM::opMUL,
|
|
&VM::opDIV,
|
|
&VM::opREM,
|
|
&VM::opBAND,
|
|
&VM::opBOR,
|
|
&VM::opBNOT,
|
|
&VM::opLT,
|
|
&VM::opEQ,
|
|
&VM::opGT,
|
|
&VM::opLIT,
|
|
&VM::opVAR,
|
|
&VM::opGETP,
|
|
&VM::opSETP,
|
|
&VM::opSET,
|
|
&VM::opPRINT,
|
|
&VM::opTERPRI,
|
|
&VM::opPNUMBER,
|
|
&VM::opFINISH,
|
|
&VM::opCHAIN,
|
|
&VM::opABORT,
|
|
&VM::opEXIT,
|
|
&VM::opRETURN,
|
|
&VM::opCALL,
|
|
&VM::opSVAR,
|
|
&VM::opSSET,
|
|
&VM::opSPLIT,
|
|
&VM::opSNLIT,
|
|
&VM::opYORN,
|
|
&VM::opSAVE,
|
|
&VM::opRESTORE,
|
|
&VM::opARG,
|
|
&VM::opASET,
|
|
&VM::opTMP,
|
|
&VM::opTSET,
|
|
&VM::opTSPACE,
|
|
&VM::opCLASS,
|
|
&VM::opMATCH,
|
|
&VM::opPNOUN,
|
|
&VM::opRESTART,
|
|
&VM::opRAND,
|
|
&VM::opRNDMIZE,
|
|
&VM::opSEND,
|
|
&VM::opVOWEL
|
|
};
|
|
|
|
VM::VM(OSystem *syst, const GlkGameDescription &gameDesc) : GlkInterface(syst, gameDesc), Game(),
|
|
_fp(_stack), _pc(0), _status(IN_PROGRESS), _actor(-1), _action(-1), _dObject(-1),
|
|
_ndObjects(-1), _iObject(-1), _wordPtr(nullptr) {
|
|
}
|
|
|
|
ExecutionResult VM::execute(int offset) {
|
|
// Set the code pointer
|
|
_pc = offset;
|
|
|
|
// Clear the stack
|
|
_fp.clear();
|
|
_stack.clear();
|
|
|
|
// Iterate through the script
|
|
for (_status = IN_PROGRESS; !shouldQuit() && _status == IN_PROGRESS;)
|
|
executeOpcode();
|
|
|
|
return _status;
|
|
}
|
|
|
|
void VM::executeOpcode() {
|
|
// Get next opcode
|
|
uint opcode = readCodeByte();
|
|
|
|
if (gDebugLevel > 0) {
|
|
Common::String s;
|
|
for (int idx = (int)_stack.size() - 1; idx >= 0; --idx) s += Common::String::format(" %d", _stack[idx]);
|
|
debugC(kDebugScripts, "%.4x - %.2x - %d%s", _pc - 1, opcode, _stack.size(), s.c_str());
|
|
}
|
|
|
|
if (opcode >= OP_BRT && opcode <= OP_VOWEL) {
|
|
(this->*_METHODS[(int)opcode - 1])();
|
|
} else if (opcode >= OP_XVAR && opcode < OP_XSET) {
|
|
_stack.top() = getVariable((int)opcode - OP_XVAR);
|
|
} else if (opcode >= OP_XSET && opcode < OP_XPLIT) {
|
|
setVariable((int)opcode - OP_XSET, _stack.top());
|
|
} else if (opcode >= OP_XPLIT && opcode < OP_XNLIT) {
|
|
_stack.top() = (int)opcode - OP_XPLIT;
|
|
} else if (opcode >= OP_XNLIT && (int)opcode < 256) {
|
|
_stack.top() = OP_XNLIT - opcode;
|
|
} else {
|
|
error("Unknown opcode %x at offset %d", opcode, _pc);
|
|
}
|
|
}
|
|
void VM::opBRT() {
|
|
_pc = _stack.top() ? readCodeWord() : _pc + 2;
|
|
}
|
|
|
|
void VM::opBRF() {
|
|
_pc = !_stack.top() ? readCodeWord() : _pc + 2;
|
|
}
|
|
|
|
void VM::opBR() {
|
|
_pc = readCodeWord();
|
|
}
|
|
|
|
void VM::opT() {
|
|
_stack.top() = TRUE;
|
|
}
|
|
|
|
void VM::opNIL() {
|
|
_stack.top() = NIL;
|
|
}
|
|
|
|
void VM::opPUSH() {
|
|
_stack.push(NIL);
|
|
}
|
|
|
|
void VM::opNOT() {
|
|
_stack.top() = _stack.top() ? NIL : TRUE;
|
|
}
|
|
|
|
void VM::opADD() {
|
|
int v = _stack.pop();
|
|
_stack.top() += v;
|
|
}
|
|
|
|
void VM::opSUB() {
|
|
int v = _stack.pop();
|
|
_stack.top() -= v;
|
|
}
|
|
|
|
void VM::opMUL() {
|
|
int v = _stack.pop();
|
|
_stack.top() *= v;
|
|
}
|
|
|
|
void VM::opDIV() {
|
|
int v = _stack.pop();
|
|
_stack.top() = (v == 0) ? 0 : _stack.top() / v;
|
|
}
|
|
|
|
void VM::opREM() {
|
|
int v = _stack.pop();
|
|
_stack.top() = (v == 0) ? 0 : _stack.top() % v;
|
|
}
|
|
|
|
void VM::opBAND() {
|
|
int v = _stack.pop();
|
|
_stack.top() &= v;
|
|
}
|
|
|
|
void VM::opBOR() {
|
|
int v = _stack.pop();
|
|
_stack.top() |= v;
|
|
}
|
|
|
|
void VM::opBNOT() {
|
|
_stack.top() = ~_stack.top();
|
|
}
|
|
|
|
void VM::opLT() {
|
|
int v = _stack.pop();
|
|
_stack.top() = (_stack.top() < v) ? TRUE : NIL;
|
|
}
|
|
|
|
void VM::opEQ() {
|
|
int v = _stack.pop();
|
|
_stack.top() = (_stack.top() == v) ? TRUE : NIL;
|
|
}
|
|
|
|
void VM::opGT() {
|
|
int v = _stack.pop();
|
|
_stack.top() = (_stack.top() > v) ? TRUE : NIL;
|
|
}
|
|
|
|
void VM::opLIT() {
|
|
_stack.top() = readCodeWord();
|
|
}
|
|
|
|
void VM::opVAR() {
|
|
_stack.top() = getVariable(readCodeWord());
|
|
}
|
|
|
|
void VM::opGETP() {
|
|
int v = _stack.pop();
|
|
_stack.top() = getObjectProperty(_stack.top(), v);
|
|
}
|
|
|
|
void VM::opSETP() {
|
|
int v3 = _stack.pop();
|
|
int v2 = _stack.pop();
|
|
_stack.top() = setObjectProperty(_stack.top(), v2, v3);
|
|
}
|
|
|
|
void VM::opSET() {
|
|
setVariable(readCodeWord(), _stack.top());
|
|
}
|
|
|
|
void VM::opPRINT() {
|
|
Common::String msg = readString(_stack.top());
|
|
print(msg);
|
|
}
|
|
|
|
void VM::opTERPRI() {
|
|
print("\n");
|
|
}
|
|
|
|
void VM::opPNUMBER() {
|
|
print(_stack.top());
|
|
}
|
|
|
|
void VM::opFINISH() {
|
|
_status = FINISH;
|
|
}
|
|
|
|
void VM::opCHAIN() {
|
|
_status = CHAIN;
|
|
}
|
|
|
|
void VM::opABORT() {
|
|
_status = ABORT;
|
|
}
|
|
|
|
void VM::opEXIT() {
|
|
quitGame();
|
|
_status = ABORT;
|
|
}
|
|
|
|
void VM::opRETURN() {
|
|
if (_fp == 0) {
|
|
_status = CHAIN;
|
|
} else {
|
|
int val = _stack.top();
|
|
_stack.resize(_fp);
|
|
_fp = _stack.pop();
|
|
_pc = _stack.pop();
|
|
|
|
int argsSize = _stack.pop();
|
|
_stack.resize(_stack.size() - argsSize);
|
|
_stack.top() = val;
|
|
}
|
|
}
|
|
|
|
void VM::opCALL() {
|
|
int argsSize = readCodeByte();
|
|
|
|
_stack.push(argsSize);
|
|
_stack.push(_pc);
|
|
_stack.push(_fp);
|
|
|
|
_fp.set();
|
|
_pc = getActionField(_fp[_fp[FP_ARGS_SIZE] + FP_ARGS], A_CODE);
|
|
}
|
|
|
|
void VM::opSVAR() {
|
|
_stack.top() = getVariable(readCodeByte());
|
|
}
|
|
|
|
void VM::opSSET() {
|
|
setVariable(readCodeByte(), _stack.top());
|
|
}
|
|
|
|
void VM::opSPLIT() {
|
|
_stack.top() = readCodeByte();
|
|
}
|
|
|
|
void VM::opSNLIT() {
|
|
_stack.top() = readCodeByte();
|
|
}
|
|
|
|
void VM::opYORN() {
|
|
Common::String line = readLine();
|
|
_stack.top() = !line.empty() && (line[0] == 'Y' || line[0] == 'y') ? TRUE : NIL;
|
|
}
|
|
|
|
void VM::opSAVE() {
|
|
if (saveGame().getCode() != Common::kNoError)
|
|
print(_("Sorry, the savegame couldn't be created"));
|
|
}
|
|
|
|
void VM::opRESTORE() {
|
|
if (loadGame().getCode() != Common::kNoError)
|
|
print(_("Sorry, the savegame couldn't be restored"));
|
|
}
|
|
|
|
void VM::opARG() {
|
|
int argNum = readCodeByte();
|
|
if (argNum >= _fp[FP_ARGS_SIZE])
|
|
error("Invalid argument number");
|
|
_stack.top() = _fp[argNum + FP_ARGS];
|
|
}
|
|
|
|
void VM::opASET() {
|
|
int argNum = readCodeByte();
|
|
if (argNum >= _fp[FP_ARGS_SIZE])
|
|
error("Invalid argument number");
|
|
_fp[argNum + FP_ARGS] = _stack.top();
|
|
}
|
|
|
|
void VM::opTMP() {
|
|
int val = readCodeByte();
|
|
_stack.top() = _fp[-val - 1];
|
|
}
|
|
|
|
void VM::opTSET() {
|
|
int val = readCodeByte();
|
|
_fp[-val - 1] = _stack.top();
|
|
}
|
|
|
|
void VM::opTSPACE() {
|
|
_stack.allocate(readCodeByte());
|
|
}
|
|
|
|
void VM::opCLASS() {
|
|
_stack.top() = getObjectField(_stack.top(), O_CLASS);
|
|
}
|
|
|
|
void VM::opMATCH() {
|
|
int idx = _stack.pop() - 1;
|
|
_stack.top() = match(_stack.top(), _nouns[idx]._noun, _nouns[idx]._adjective) ? TRUE : NIL;
|
|
}
|
|
|
|
void VM::opPNOUN() {
|
|
int noun = _stack.top();
|
|
Common::String str;
|
|
|
|
// Add the adjectives
|
|
bool space = false;
|
|
for (const AdjectiveEntry *aPtr = &_adjectiveList[noun - 1]; aPtr->_list; ++aPtr, space = true) {
|
|
if (space)
|
|
str += " ";
|
|
str += _words[aPtr->_word]._text;
|
|
}
|
|
|
|
// Add the noun
|
|
if (space)
|
|
str += " ";
|
|
str += _words[_nouns[noun - 1]._num]._text;
|
|
|
|
print(str);
|
|
}
|
|
|
|
void VM::opRESTART() {
|
|
restart();
|
|
}
|
|
|
|
void VM::opRAND() {
|
|
_stack.top() = getRandomNumber(_stack.top());
|
|
}
|
|
|
|
void VM::opRNDMIZE() {
|
|
// No implementation
|
|
}
|
|
|
|
void VM::opSEND() {
|
|
int argsSize = readCodeByte();
|
|
_stack.push(argsSize);
|
|
_stack.push(_pc);
|
|
_stack.push(_fp);
|
|
_fp.set();
|
|
|
|
int val = _fp[_fp[FP_ARGS_SIZE] + FP_ARGS];
|
|
if (val)
|
|
val = getObjectField(val, O_CLASS);
|
|
else
|
|
val = _fp[_fp[FP_ARGS_SIZE] + FP_ARGS - 1];
|
|
|
|
if (val && (val = getObjectProperty(val, _fp[_fp[FP_ARGS_SIZE] + FP_ARGS - 2])) != 0) {
|
|
_pc = getActionField(val, A_CODE);
|
|
} else {
|
|
// Return NIL if there's no action for the given message
|
|
opRETURN();
|
|
}
|
|
}
|
|
|
|
void VM::opVOWEL() {
|
|
// No implementation
|
|
}
|
|
|
|
bool VM::getInput() {
|
|
if (!parseInput())
|
|
return false;
|
|
|
|
setVariable(V_ACTOR, _actor);
|
|
setVariable(V_ACTION, _action);
|
|
setVariable(V_DOBJECT, _dObject);
|
|
setVariable(V_NDOBJECTS, _ndObjects);
|
|
setVariable(V_IOBJECT, _iObject);
|
|
return true;
|
|
}
|
|
|
|
bool VM::nextCommand() {
|
|
if (getVariable(V_NDOBJECTS) > 1) {
|
|
setVariable(V_ACTOR, _actor);
|
|
setVariable(V_ACTION, _action);
|
|
setVariable(V_DOBJECT, getVariable(V_DOBJECT) + 1);
|
|
setVariable(V_NDOBJECTS, getVariable(V_NDOBJECTS) - 1);
|
|
setVariable(V_IOBJECT, _iObject);
|
|
return true;
|
|
} else {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
bool VM::parseInput() {
|
|
int noun1 = 0, cnt1 = 0, noun2 = 0, cnt2 = 0;
|
|
int preposition = 0, flags = 0;
|
|
|
|
// Initialize the parser result fields
|
|
_actor = _action = _dObject = _iObject = 0;
|
|
_ndObjects = 0;
|
|
_nouns.clear();
|
|
_adjectiveList.clear();
|
|
_adjectiveList.reserve(20);
|
|
|
|
// Get the input line
|
|
if (!getLine())
|
|
return false;
|
|
|
|
// Check for actor
|
|
WordType wordType = getWordType(*_wordPtr);
|
|
if (wordType == WT_ADJECTIVE || wordType == WT_NOUN) {
|
|
if (!(_actor = getNoun()))
|
|
return false;
|
|
flags |= A_ACTOR;
|
|
}
|
|
|
|
// Check for a verb
|
|
if (!getVerb())
|
|
return false;
|
|
|
|
// Get direct object, preposition, and/or indirect object
|
|
if (_wordPtr != _words.end()) {
|
|
// Get any direct objects
|
|
noun1 = _adjectiveList.size() + 1;
|
|
for (;;) {
|
|
// Get the next direct object
|
|
if (!getNoun())
|
|
return false;
|
|
++cnt1;
|
|
|
|
// Check for more direct objects
|
|
if (_wordPtr == _words.end() || getWordType(*_wordPtr) != WT_CONJUNCTION)
|
|
break;
|
|
++_wordPtr;
|
|
}
|
|
|
|
// Get any reposition and indirect object
|
|
if (_wordPtr != _words.end()) {
|
|
// Get the preposition
|
|
if (getWordType(*_wordPtr) == WT_PREPOSITION)
|
|
preposition = *_wordPtr++;
|
|
|
|
// Get the indirect object
|
|
noun2 = _adjectiveList.size() + 1;
|
|
for (;;) {
|
|
// Get the indirect object
|
|
if (!getNoun())
|
|
return false;
|
|
++cnt2;
|
|
|
|
// Check for more objects
|
|
if (_wordPtr == _words.end() || getWordType(*_wordPtr) != WT_CONJUNCTION)
|
|
break;
|
|
++_wordPtr;
|
|
}
|
|
}
|
|
|
|
// Ensure we're at the end of the input line
|
|
if (_wordPtr != _words.end()) {
|
|
parseError();
|
|
return false;
|
|
}
|
|
}
|
|
|
|
// Setup resulting properties
|
|
if (preposition) {
|
|
if (cnt2 > 1) {
|
|
parseError();
|
|
return false;
|
|
}
|
|
|
|
_dObject = noun1;
|
|
_ndObjects = cnt1;
|
|
_iObject = noun2;
|
|
} else if (noun2) {
|
|
if (cnt1 > 1) {
|
|
parseError();
|
|
return false;
|
|
}
|
|
|
|
preposition = findWord("to");
|
|
_dObject = noun2;
|
|
_ndObjects = cnt2;
|
|
_iObject = noun1;
|
|
} else {
|
|
_dObject = noun1;
|
|
_ndObjects = cnt1;
|
|
}
|
|
|
|
// Setup the flags for the action lookup
|
|
if (_dObject)
|
|
flags |= A_DOBJECT;
|
|
if (_iObject)
|
|
flags |= A_IOBJECT;
|
|
|
|
// Find the action
|
|
if (!(_action = findAction(_verbs, preposition, flags))) {
|
|
parseError();
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool VM::getLine() {
|
|
// Let the user type in an input line
|
|
Common::String line = readLine();
|
|
if (shouldQuit())
|
|
return false;
|
|
|
|
skipSpaces(line);
|
|
if (line.empty()) {
|
|
print(_("Speak up! I can't hear you!\n"));
|
|
return false;
|
|
}
|
|
|
|
// Get the words of the line
|
|
_words.clear();
|
|
while (!line.empty()) {
|
|
if (!getWord(line))
|
|
return false;
|
|
}
|
|
|
|
_wordPtr = _words.begin();
|
|
return true;
|
|
}
|
|
|
|
bool VM::getWord(Common::String &line) {
|
|
// Find the end of the word
|
|
const char *wordP = line.c_str();
|
|
for (; *wordP && !isWhitespace(*wordP); ++wordP) {}
|
|
|
|
// Copy out the next word
|
|
InputWord iw;
|
|
iw._text = Common::String(line.c_str(), wordP);
|
|
iw._text.toLowercase();
|
|
|
|
// Remove the word from the line
|
|
line = Common::String(wordP);
|
|
skipSpaces(line);
|
|
|
|
// Look up the word
|
|
iw._number = findWord(iw._text);
|
|
|
|
if (iw._number) {
|
|
_words.push_back(iw);
|
|
return true;
|
|
} else {
|
|
Common::U32String msg = Common::U32String::format(_("I don't know the word \"%s\".\n"), iw._text.c_str());
|
|
print(msg);
|
|
return false;
|
|
}
|
|
}
|
|
|
|
uint VM::getNoun() {
|
|
// Skip over optional article if present
|
|
if (_wordPtr != _words.end() && getWordType(*_wordPtr) == WT_ARTICLE)
|
|
++_wordPtr;
|
|
|
|
// Get optional adjectives
|
|
uint alStart = _adjectiveList.size();
|
|
while (_wordPtr != _words.end() && getWordType(*_wordPtr) == WT_ADJECTIVE) {
|
|
AdjectiveEntry ae;
|
|
ae._list = *_wordPtr++;
|
|
ae._word = _wordPtr - _words.begin() - 1;
|
|
_adjectiveList.push_back(ae);
|
|
}
|
|
_adjectiveList.push_back(AdjectiveEntry());
|
|
assert(_adjectiveList.size() <= 20);
|
|
|
|
if (_wordPtr == _words.end() || getWordType(*_wordPtr) != WT_NOUN) {
|
|
parseError();
|
|
return NIL;
|
|
}
|
|
|
|
// Add a noun entry to the list
|
|
Noun n;
|
|
n._adjective = &_adjectiveList[alStart];
|
|
n._noun = *_wordPtr++;
|
|
n._num = _wordPtr - _words.begin() - 1;
|
|
_nouns.push_back(n);
|
|
|
|
return _nouns.size();
|
|
}
|
|
|
|
bool VM::getVerb() {
|
|
_verbs.clear();
|
|
|
|
if (_wordPtr == _words.end() || getWordType(*_wordPtr) != WT_VERB) {
|
|
parseError();
|
|
return false;
|
|
}
|
|
|
|
_verbs.push_back(*_wordPtr++);
|
|
|
|
// Check for a word following the verb
|
|
if (_wordPtr < _words.end()) {
|
|
_verbs.push_back(*_wordPtr);
|
|
|
|
if (checkVerb(_verbs)) {
|
|
++_wordPtr;
|
|
} else {
|
|
_verbs.pop_back();
|
|
_verbs.push_back(_words.back());
|
|
|
|
if (checkVerb(_verbs)) {
|
|
_words.pop_back();
|
|
} else {
|
|
_verbs.pop_back();
|
|
|
|
if (!checkVerb(_verbs)) {
|
|
parseError();
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool VM::match(int obj, int noun, const VM::AdjectiveEntry *adjectives) {
|
|
if (!hasNoun(obj, noun))
|
|
return false;
|
|
|
|
for (const VM::AdjectiveEntry *adjPtr = adjectives; adjPtr->_list; ++adjPtr) {
|
|
if (!hasAdjective(obj, adjPtr->_list))
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
void VM::parseError() {
|
|
print(_("I don't understand.\n"));
|
|
}
|
|
|
|
bool VM::isWhitespace(char c) {
|
|
return c == ' ' || c == ',' || c == '.';
|
|
}
|
|
|
|
bool VM::skipSpaces(Common::String &str) {
|
|
while (!str.empty() && isWhitespace(str[0]))
|
|
str.deleteChar(0);
|
|
|
|
return !str.empty();
|
|
}
|
|
|
|
} // End of namespace AdvSys
|
|
} // End of namespace Glk
|