diff options
-rw-r--r-- | engines/parallaction/balloons.cpp | 4 | ||||
-rw-r--r-- | engines/parallaction/parser.cpp | 185 | ||||
-rw-r--r-- | engines/parallaction/parser.h | 131 | ||||
-rw-r--r-- | engines/parallaction/parser_br.cpp | 25 |
4 files changed, 344 insertions, 1 deletions
diff --git a/engines/parallaction/balloons.cpp b/engines/parallaction/balloons.cpp index 81b32adb15..8c3859db63 100644 --- a/engines/parallaction/balloons.cpp +++ b/engines/parallaction/balloons.cpp @@ -245,6 +245,8 @@ void BalloonManager_ns::freeBalloons() { _numBalloons = 0; } +// TODO: get rid of parseNextToken from here. Use the +// StringTokenizer instead. void BalloonManager_ns::drawWrappedText(Font *font, Graphics::Surface* surf, char *text, byte color, int16 wrapwidth) { uint16 lines = 0; @@ -302,6 +304,8 @@ void BalloonManager_ns::drawWrappedText(Font *font, Graphics::Surface* surf, cha } +// TODO: get rid of parseNextToken from here. Use the +// StringTokenizer instead. void BalloonManager_ns::getStringExtent(Font *font, char *text, uint16 maxwidth, int16* width, int16* height) { uint16 lines = 0; diff --git a/engines/parallaction/parser.cpp b/engines/parallaction/parser.cpp index 6de0a7d7f5..710820f41a 100644 --- a/engines/parallaction/parser.cpp +++ b/engines/parallaction/parser.cpp @@ -28,6 +28,7 @@ namespace Parallaction { +int _numTokens; char _tokens[20][MAX_TOKEN_LEN]; Script::Script(Common::ReadStream *input, bool disposeSource) : _input(input), _disposeSource(disposeSource), _line(0) {} @@ -68,6 +69,8 @@ void Script::clearTokens() { for (uint16 i = 0; i < 20; i++) _tokens[i][0] = '\0'; + _numTokens = 0; + return; } @@ -157,6 +160,8 @@ uint16 Script::fillTokens(char* line) { i++; } + _numTokens = i; + return i; } @@ -243,4 +248,184 @@ void Parser::parseStatement() { } +#define BLOCK_BASE 100 + +class StatementDef { +protected: + Common::String makeLineFromTokens() { + Common::String space(" "); + Common::String newLine("\n"); + Common::String text; + for (int i = 0; i < _numTokens; i++) + text += (Common::String(_tokens[i]) + space); + text.deleteLastChar(); + text += newLine; + return text; + } + +public: + uint score; + const char* name; + + + StatementDef(uint score, const char *name) : score(score), name(name) { } + + virtual Common::String makeLine(Script &script) = 0;; + +}; + + +class SimpleStatementDef : public StatementDef { + +public: + SimpleStatementDef(uint score, const char *name) : StatementDef(score, name) { } + + Common::String makeLine(Script &script) { + return makeLineFromTokens(); + } + +}; + + + +class BlockStatementDef : public StatementDef { + + const char* ending1; + const char* ending2; + +public: + BlockStatementDef(uint score, const char *name, const char *ending1, const char *ending2 = 0) : StatementDef(score, name), ending1(ending1), + ending2(ending2) { } + + Common::String makeLine(Script &script) { + Common::String text = makeLineFromTokens(); + bool end; + do { + script.readLineToken(true); + text += makeLineFromTokens(); + end = !scumm_stricmp(ending1, _tokens[0]) || (ending2 && !scumm_stricmp(ending2, _tokens[0])); + } while (!end); + return text; + } + +}; + +class CommentStatementDef : public StatementDef { + + Common::String parseComment(Script &script) { + Common::String result; + char buf[129]; + + do { + script.readLine(buf, 128); + buf[strlen(buf)-1] = '\0'; + if (!scumm_stricmp(buf, "endtext")) + break; + result += Common::String(buf) + "\n"; + } while (true); + result += "endtext\n"; + return result; + } + +public: + CommentStatementDef(uint score, const char *name) : StatementDef(score, name) { } + + Common::String makeLine(Script &script) { + Common::String text = makeLineFromTokens(); + text += parseComment(script); + return text; + } + +}; + + + + +PreProcessor::PreProcessor() { + _defs.push_back(new SimpleStatementDef(1, "disk" )); + _defs.push_back(new SimpleStatementDef(2, "location" )); + _defs.push_back(new SimpleStatementDef(3, "localflags" )); + _defs.push_back(new SimpleStatementDef(4, "flags" )); + _defs.push_back(new SimpleStatementDef(5, "zeta" )); + _defs.push_back(new SimpleStatementDef(6, "music" )); + _defs.push_back(new SimpleStatementDef(7, "sound" )); + _defs.push_back(new SimpleStatementDef(8, "mask" )); + _defs.push_back(new SimpleStatementDef(9, "path" )); + _defs.push_back(new SimpleStatementDef(10, "character" )); + _defs.push_back(new CommentStatementDef(11, "comment" )); + _defs.push_back(new CommentStatementDef(12, "endcomment" )); + _defs.push_back(new BlockStatementDef(13, "ifchar", "endif" )); + _defs.push_back(new BlockStatementDef(BLOCK_BASE, "zone", "endanimation", "endzone" )); + _defs.push_back(new BlockStatementDef(BLOCK_BASE, "animation", "endanimation", "endzone" )); + _defs.push_back(new BlockStatementDef(1000, "commands", "endcommands" )); + _defs.push_back(new BlockStatementDef(1001, "acommands", "endcommands" )); + _defs.push_back(new BlockStatementDef(1002, "escape", "endcommands" )); + _defs.push_back(new SimpleStatementDef(2000, "endlocation")); +} + +PreProcessor::~PreProcessor() { + DefList::iterator it = _defs.begin(); + for (; it != _defs.end(); it++) { + delete *it; + } +} + +StatementDef* PreProcessor::findDef(const char* name) { + DefList::iterator it = _defs.begin(); + for (; it != _defs.end(); it++) { + if (!scumm_stricmp((*it)->name, name)) { + return *it; + } + } + return 0; +} + + + +uint PreProcessor::getDefScore(StatementDef* def) { + if (def->score == BLOCK_BASE) { + _numZones++; + return (_numZones + BLOCK_BASE); + } + return def->score; +} + + +void PreProcessor::preprocessScript(Script &script, StatementList &list) { + _numZones = 0; + Common::String text; + do { + script.readLineToken(false); + if (_numTokens == 0) + break; + + StatementDef *def = findDef(_tokens[0]); + assert(def); + + text = def->makeLine(script); + int score = getDefScore(def); + list.push_back(StatementListNode(score, def->name, text)); + } while (true); + Common::sort(list.begin(), list.end()); +} + + + + +void testPreprocessing(Parallaction *vm, const char *filename) { + Script *script = vm->_disk->loadLocation(filename); + StatementList list; + PreProcessor pp; + pp.preprocessScript(*script, list); + delete script; + Common::DumpFile dump; + dump.open(filename); + StatementList::iterator it = list.begin(); + for ( ; it != list.end(); it++) { + dump.write((*it)._text.c_str(), (*it)._text.size()); + } + dump.close(); +} + + } // namespace Parallaction diff --git a/engines/parallaction/parser.h b/engines/parallaction/parser.h index 79e6cf6640..7e7937fb19 100644 --- a/engines/parallaction/parser.h +++ b/engines/parallaction/parser.h @@ -35,6 +35,7 @@ namespace Parallaction { char *parseNextToken(char *s, char *tok, uint16 count, const char *brk, bool ignoreQuotes = false); #define MAX_TOKEN_LEN 50 +extern int _numTokens; extern char _tokens[][MAX_TOKEN_LEN]; class Script { @@ -63,6 +64,7 @@ typedef Common::Functor0<void> Opcode; typedef Common::Array<const Opcode*> OpcodeSet; + class Parser { public: @@ -95,6 +97,7 @@ public: class Parallaction_ns; class Parallaction_br; + class LocationParser_ns { protected: @@ -240,6 +243,23 @@ public: }; +/* + TODO: adapt the parser to effectively use the + statement list provided by preprocessor as its + input, instead of relying on the current Script + class. + + This would need a major rewrite of the parsing + system! + + parseNextToken could then be sealed into the + PreProcessor class forever, together with the + _tokens[] and _numTokens stuff, now dangling as + global objects. + + NS balloons code should be dealt with before, + though. +*/ class LocationParser_br : public LocationParser_ns { protected: @@ -402,6 +422,117 @@ public: }; + +/* + This simple stream is temporarily needed to hook the + preprocessor output to the parser. It will go away + when the parser is rewritten to fully exploit the + statement list provided by the preprocessor. +*/ + +class ReadStringStream : public Common::ReadStream { + + char *_text; + uint32 _pos; + uint32 _size; + +public: + ReadStringStream(const Common::String &text) { + _text = new char[text.size() + 1]; + strcpy(_text, text.c_str()); + _size = text.size(); + _pos = 0; + } + + ~ReadStringStream() { + delete []_text; + } + + uint32 read(void *buffer, uint32 size) { + if (_pos + size > _size) { + size = _size - _pos; + } + memcpy(buffer, _text + _pos, size); + _pos += size; + return size; + } + + bool eos() const { + return _pos == _size; + } + +}; + + +/* + Demented as it may sound, the success of a parsing operation in the + original BRA depends on what has been parsed before. The game features + an innovative chaos system that involves the parser and the very game + engine, in order to inflict the user an unforgettable game experience. + + Ok, now for the serious stuff. + + The PreProcessor implemented here fixes the location scripts before + they are fed to the parser. It tries to do so by a preliminary scan + of the text file, during which a score is assigned to each statement + (more on this later). When the whole file has been analyzed, the + statements are sorted according to their score, to create a parsable + sequence. + + For parsing, the statements in location scripts can be conveniently + divided into 3 groups: + + * location definitions + * element definitions + * start-up commands + + Since the parsing of element definitions requires location parameters + to be set, location definitions should be encountered first in the + script. Start-up commands in turn may reference elements, so they can + be parsed last. The first goal is to make sure the parser gets these + three sets in this order. + + Location definitions must also be presented in a certain sequence, + because resource files are not fully self-describing. In short, some + critical game data in contained in certain files, that must obviously + be read before any other can be analyzed. This is the second goal. + + TODO: some words about actual implementation. +*/ + +class StatementDef; + +struct StatementListNode { + int _score; + Common::String _name; + Common::String _text; + + StatementListNode(int score, const Common::String &name, const Common::String &text) : _score(score), _name(name), _text(text) { } + + bool operator<(const StatementListNode& node) const { + return _score < node._score; + } +}; +typedef Common::List<StatementListNode> StatementList; + + +class PreProcessor { + typedef Common::List<StatementDef*> DefList; + + int _numZones; + DefList _defs; + + StatementDef* findDef(const char* name); + uint getDefScore(StatementDef*); + +public: + PreProcessor(); + ~PreProcessor(); + void preprocessScript(Script &script, StatementList &list); +}; + + + } // namespace Parallaction #endif diff --git a/engines/parallaction/parser_br.cpp b/engines/parallaction/parser_br.cpp index 3b446805d7..a4298bbedb 100644 --- a/engines/parallaction/parser_br.cpp +++ b/engines/parallaction/parser_br.cpp @@ -25,6 +25,8 @@ #include "parallaction/parallaction.h" +#include "parallaction/preprocessor.h" + #include "parallaction/sound.h" namespace Parallaction { @@ -104,6 +106,7 @@ namespace Parallaction { #define INST_ENDIF 30 #define INST_STOP 31 + const char *_zoneTypeNamesRes_br[] = { "examine", "door", @@ -1168,8 +1171,27 @@ void ProgramParser_br::init() { INSTRUCTION_PARSER(endscript); } + +/* + Ancillary routine to support hooking preprocessor and + parser. +*/ +Common::ReadStream *getStream(StatementList &list) { + Common::String text; + StatementList::iterator it = list.begin(); + for ( ; it != list.end(); it++) { + text += (*it)._text; + } + return new ReadStringStream(text); +} + void LocationParser_br::parse(Script *script) { + PreProcessor pp; + StatementList list; + pp.preprocessScript(*script, list); + Script *script2 = new Script(getStream(list), true); + ctxt.numZones = 0; ctxt.bgName = 0; ctxt.maskName = 0; @@ -1177,7 +1199,7 @@ void LocationParser_br::parse(Script *script) { ctxt.characterName = 0; ctxt.info = new BackgroundInfo; - LocationParser_ns::parse(script); + LocationParser_ns::parse(script2); _vm->_disk->loadScenery(*ctxt.info, ctxt.bgName, ctxt.maskName, ctxt.pathName); _vm->_gfx->setBackground(kBackgroundLocation, ctxt.info); @@ -1193,6 +1215,7 @@ void LocationParser_br::parse(Script *script) { free(ctxt.pathName); free(ctxt.characterName); + delete script2; } } // namespace Parallaction |