2019-06-16 17:04:53 -07:00

393 lines
9.6 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.
*
*/
#ifndef GLK_ADVSYS_VM
#define GLK_ADVSYS_VM
#include "glk/advsys/glk_interface.h"
#include "glk/advsys/game.h"
#include "common/stack.h"
namespace Glk {
namespace AdvSys {
/**
* Execution result states
*/
enum ExecutionResult {
IN_PROGRESS = 0, ///< Default state whilst script is in progress
FINISH = 1, ///< Script was finished
CHAIN = 2, ///< Another script is being chained to
ABORT = 3 ///< Script was aborted
};
/**
* Opcode list
*/
enum Opcode {
OP_BRT = 0x01, ///< Branch on true
OP_BRF = 0x02, ///< Branch on false
OP_BR = 0x03, ///< Branch unconditionally
OP_T = 0x04, ///< Load top of stack with t
OP_NIL = 0x05, ///< Load top of stack with nil
OP_PUSH = 0x06, ///< Push nil onto stack
OP_NOT = 0x07, ///< Logical negate top of stack
OP_ADD = 0x08, ///< Add two numeric expressions
OP_SUB = 0x09, ///< Subtract two numeric expressions
OP_MUL = 0x0A, ///< Multiply two numeric expressions
OP_DIV = 0x0B, ///< Divide two numeric expressions
OP_REM = 0x0C, ///< Remainder of two numeric expressions
OP_BAND = 0x0D, ///< Bitwise and of two numeric expressions
OP_BOR = 0x0E, ///< Bitwise or of two numeric expressions
OP_BNOT = 0x0F, ///< Bitwise not
OP_LT = 0x10, ///< Less than
OP_EQ = 0x11, ///< Equal to
OP_GT = 0x12, ///< Greater than
OP_LIT = 0x13, ///< Load literal
OP_VAR = 0x14, ///< Load a variable value
OP_GETP = 0x15, ///< Get the value of an object property
OP_SETP = 0x16, ///< Set the value of an object property
OP_SET = 0x17, ///< Set the value of a variable
OP_PRINT = 0x18, ///< Print messages
OP_TERPRI = 0x19, ///< Terminate the print line
OP_PNUMBER = 0x1A, ///< Print a number
OP_FINISH = 0x1B, ///< Finish handling this command
OP_CHAIN = 0x1C, ///< Chain to the next handler
OP_ABORT = 0x1D, ///< Abort this command
OP_EXIT = 0x1E, ///< Exit the game
OP_RETURN = 0x1F, ///< Return from function
OP_CALL = 0x20, ///< Call subroutine
OP_SVAR = 0x21, ///< Short load a variable
OP_SSET = 0x22, ///< Short set a variable
OP_SPLIT = 0x23, ///< Short load a positive literal
OP_SNLIT = 0x24, ///< Short load a negative literal
OP_YORN = 0x25, ///< Yes or No predicate
OP_SAVE = 0x26, ///< Save data structures
OP_RESTORE = 0x27, ///< Restore data structures
OP_ARG = 0x28, ///< Load an argument value
OP_ASET = 0x29, ///< Set an argument value
OP_TMP = 0x2A, ///< Load a temporary variable value
OP_TSET = 0x2B, ///< Set a temporary variable
OP_TSPACE = 0x2C, ///< Allocate temporary variable space
OP_CLASS = 0x2D, ///< Get the class of an object
OP_MATCH = 0x2E, ///< Match a noun phrase with an object
OP_PNOUN = 0x2F, ///< Print a noun phrase
OP_RESTART = 0x30, ///< Restart the current game
OP_RAND = 0x31, ///< Generate a random number
OP_RNDMIZE = 0x32, ///< Seed the random number generator
OP_SEND = 0x33, ///< Send a message to an object
OP_VOWEL = 0x34, ///< Check for vowel beginning string
OP_XVAR = 0x40, ///< Extra short load a variable
OP_XSET = 0x60, ///< Extra short set a variable
OP_XPLIT = 0x80, ///< Extra short load a positive literal
OP_XNLIT = 0xC0 ///< Extra short load a negative literal
};
/**
* Indexes useable in function pointer array offsets
*/
enum FPOffset {
FP_FP = 0,
FP_PC = 1,
FP_ARGS_SIZE = 2,
FP_ARGS = 3
};
/**
* Action flags
*/
enum ActionFlag {
A_ACTOR = 1, ///< Actor
A_DOBJECT = 2, ///< Direct object
A_IOBJECT = 4 ///< Indirect object
};
class VM;
typedef void (VM::*OpcodeMethod)();
/**
* Fixed stack
*/
class FixedStack : public Common::FixedStack<int, 500> {
public:
/**
* Resize the stack
*/
void resize(size_t newSize) {
assert(newSize <= 500);
_size = newSize;
}
/**
* Allocate extra space on the stack
*/
void allocate(size_t amount) {
uint oldSize = _size;
resize(_size + amount);
Common::fill(&_stack[oldSize], &_stack[oldSize + amount], 0);
}
};
/**
* Implements a function pointer reference into the stack. It also allows
* positive array indexing to reference the following:
* 0 = Previous function pointer
* 1 = Return PC
* 2 = Size of argument block
* 3+ = Any function call arguments
*/
class FunctionPointer {
private:
FixedStack &_stack;
int _index;
public:
/**
* Constructor
*/
FunctionPointer(FixedStack &s) : _stack(s), _index(0) {}
/**
* Array indexing
*/
int &operator[](int idx) { return _stack[_index - idx - 1]; }
/**
* Sets the index in the stack of the function pointer
*/
FunctionPointer &operator=(int index) {
_index = index;
return *this;
}
/**
* Clear the function pointer
*/
void clear() { _index = 0; }
/**
* Returns the index in the stack of the function pointer
*/
operator int() const { return _index; }
/**
* Sets the function pointer to the top of the stack
*/
void set() {
_index = _stack.size();
}
};
/**
* Main VM for AdvSys
*/
class VM : public GlkInterface, public Game {
struct InputWord {
Common::String _text;
int _number;
InputWord() : _number(0) {}
operator int() const { return _number; }
};
struct AdjectiveEntry {
int _list;
int _word;
AdjectiveEntry() : _list(0), _word(0) {}
};
struct Noun {
int _noun;
int _num;
AdjectiveEntry *_adjective;
Noun() : _noun(0), _num(0), _adjective(nullptr) {}
};
private:
// Execution fields
static OpcodeMethod _METHODS[0x34];
int _pc;
ExecutionResult _status;
FixedStack _stack;
FunctionPointer _fp;
// Parser fields
int _actor;
int _action;
int _dObject;
int _ndObjects;
int _iObject;
Common::Array<InputWord> _words;
Common::Array<InputWord>::iterator _wordPtr;
Common::Array<int> _verbs;
Common::Array<AdjectiveEntry> _adjectiveList;
Common::Array<Noun> _nouns;
private:
/**
* Execute a single opcode within the script
*/
void executeOpcode();
/**
* Get the next code byte and increments the PC counter
*/
int readCodeByte() {
return getCodeByte(_pc++);
}
/**
* Gets the next code word and increases the PC counter to after it
*/
int readCodeWord() {
int v = getCodeWord(_pc);
_pc += 2;
return v;
}
/**
* Gets an input line and parse it
*/
bool parseInput();
/**
* Gets an input line and splits it up into the words array
*/
bool getLine();
/**
* Get the next word of a passed input line
* @param line Input line
* @returns True if a valid word was extracted
*/
bool getWord(Common::String &line);
/**
* Get a noun phrase and return the object it refers to
*/
uint getNoun();
/**
* Get a verb phrase and return the action it refers to
*/
bool getVerb();
/**
* Match an object against a name and list of adjectives
*/
bool match(int obj, int noun, const AdjectiveEntry *adjectives);
/**
* Called when a parsing error occurs
*/
void parseError();
/**
* Returns true if a passed character is a skippable whitespace
*/
static bool isWhitespace(char c);
/**
* Skips over spaces in a passed string
*/
static bool skipSpaces(Common::String &str);
private:
void opBRT();
void opBRF();
void opBR();
void opT();
void opNIL();
void opPUSH();
void opNOT();
void opADD();
void opSUB();
void opMUL();
void opDIV();
void opREM();
void opBAND();
void opBOR();
void opBNOT();
void opLT();
void opEQ();
void opGT();
void opLIT();
void opVAR();
void opGETP();
void opSETP();
void opSET();
void opPRINT();
void opTERPRI();
void opPNUMBER();
void opFINISH();
void opCHAIN();
void opABORT();
void opEXIT();
void opRETURN();
void opCALL();
void opSVAR();
void opSSET();
void opSPLIT();
void opSNLIT();
void opYORN();
void opSAVE();
void opRESTORE();
void opARG();
void opASET();
void opTMP();
void opTSET();
void opTSPACE();
void opCLASS();
void opMATCH();
void opPNOUN();
void opRESTART();
void opRAND();
void opRNDMIZE();
void opSEND();
void opVOWEL();
public:
/**
* Constructor
*/
VM(OSystem *syst, const GlkGameDescription &gameDesc);
/**
* Exeecute a script
* @param offset Script offset
* @returns Script result code
*/
ExecutionResult execute(int offset);
/**
* Get an input line and parse it
*/
bool getInput();
/**
* Get the next command (next direct object)
*/
bool nextCommand();
};
} // End of namespace AdvSys
} // End of namespace Glk
#endif