diff options
| author | Paul Gilbert | 2019-07-20 14:58:39 -0700 | 
|---|---|---|
| committer | Paul Gilbert | 2019-09-29 15:08:52 -0700 | 
| commit | 255f2b4c82867b3c113743d02cf91b91a15176bd (patch) | |
| tree | 6157bd018d16aa066d22cdc53926fe4c17da0491 | |
| parent | 6d6dd2ed62b08a89b15982160ef79faef340d524 (diff) | |
| download | scummvm-rg350-255f2b4c82867b3c113743d02cf91b91a15176bd.tar.gz scummvm-rg350-255f2b4c82867b3c113743d02cf91b91a15176bd.tar.bz2 scummvm-rg350-255f2b4c82867b3c113743d02cf91b91a15176bd.zip | |
GLK: QUEST: Initial subengine commit
29 files changed, 8735 insertions, 0 deletions
| diff --git a/engines/glk/detection.cpp b/engines/glk/detection.cpp index 60b2faa01c..f6531723d0 100644 --- a/engines/glk/detection.cpp +++ b/engines/glk/detection.cpp @@ -39,6 +39,8 @@  #include "glk/hugo/hugo.h"  #include "glk/magnetic/detection.h"  #include "glk/magnetic/magnetic.h" +#include "glk/quest/detection.h" +#include "glk/quest/quest.h"  #include "glk/scott/detection.h"  #include "glk/scott/scott.h"  #include "glk/tads/detection.h" @@ -163,6 +165,7 @@ Common::Error GlkMetaEngine::createInstance(OSystem *syst, Engine **engine) cons  	else if ((*engine = create<Glk::Frotz::FrotzMetaEngine, Glk::Frotz::Frotz>(syst, gameDesc)) != nullptr) {}  	else if ((*engine = create<Glk::Glulxe::GlulxeMetaEngine, Glk::Glulxe::Glulxe>(syst, gameDesc)) != nullptr) {}  	else if ((*engine = create<Glk::Hugo::HugoMetaEngine, Glk::Hugo::Hugo>(syst, gameDesc)) != nullptr) {} +	else if ((*engine = create<Glk::Quest::QuestMetaEngine, Glk::Quest::Quest>(syst, gameDesc)) != nullptr) {}  	else if ((*engine = create<Glk::Scott::ScottMetaEngine, Glk::Scott::Scott>(syst, gameDesc)) != nullptr) {}  #ifndef RELEASE_BUILD  	else if ((*engine = create<Glk::Magnetic::MagneticMetaEngine, Glk::Magnetic::Magnetic>(syst, gameDesc)) != nullptr) {} @@ -211,6 +214,7 @@ PlainGameList GlkMetaEngine::getSupportedGames() const {  	Glk::Frotz::FrotzMetaEngine::getSupportedGames(list);  	Glk::Glulxe::GlulxeMetaEngine::getSupportedGames(list);  	Glk::Hugo::HugoMetaEngine::getSupportedGames(list); +	Glk::Quest::QuestMetaEngine::getSupportedGames(list);  	Glk::Scott::ScottMetaEngine::getSupportedGames(list);  #ifndef RELEASE_BUILD  	Glk::Magnetic::MagneticMetaEngine::getSupportedGames(list); @@ -242,6 +246,9 @@ PlainGameDescriptor GlkMetaEngine::findGame(const char *gameId) const {  	gd = Glk::Hugo::HugoMetaEngine::findGame(gameId);  	if (gd._description) return gd; +	gd = Glk::Quest::QuestMetaEngine::findGame(gameId); +	if (gd._description) return gd; +  	gd = Glk::Scott::ScottMetaEngine::findGame(gameId);  	if (gd._description) return gd; @@ -267,6 +274,7 @@ DetectedGames GlkMetaEngine::detectGames(const Common::FSList &fslist) const {  	Glk::Frotz::FrotzMetaEngine::detectGames(fslist, detectedGames);  	Glk::Glulxe::GlulxeMetaEngine::detectGames(fslist, detectedGames);  	Glk::Hugo::HugoMetaEngine::detectGames(fslist, detectedGames); +	Glk::Quest::QuestMetaEngine::detectGames(fslist, detectedGames);  	Glk::Scott::ScottMetaEngine::detectGames(fslist, detectedGames);  #ifndef RELEASE_BUILD @@ -286,6 +294,7 @@ void GlkMetaEngine::detectClashes() const {  	Glk::Frotz::FrotzMetaEngine::detectClashes(map);  	Glk::Glulxe::GlulxeMetaEngine::detectClashes(map);  	Glk::Hugo::HugoMetaEngine::detectClashes(map); +	Glk::Quest::QuestMetaEngine::detectClashes(map);  	Glk::Scott::ScottMetaEngine::detectClashes(map);  #ifndef RELEASE_BUILD diff --git a/engines/glk/glk_types.h b/engines/glk/glk_types.h index ca20fe8a06..f99d2b1972 100644 --- a/engines/glk/glk_types.h +++ b/engines/glk/glk_types.h @@ -48,6 +48,7 @@ enum InterpreterType {  	INTERPRETER_JACL,  	INTERPRETER_LEVEL9,  	INTERPRETER_MAGNETIC, +	INTERPRETER_QUEST,  	INTERPRETER_SCARE,  	INTERPRETER_SCOTT,  	INTERPRETER_TADS2, diff --git a/engines/glk/module.mk b/engines/glk/module.mk index dd63c77952..4f4608af83 100644 --- a/engines/glk/module.mk +++ b/engines/glk/module.mk @@ -182,6 +182,17 @@ MODULE_OBJS := \  	magnetic/graphics.o \  	magnetic/magnetic.o \  	magnetic/sound.o \ +	quest/detection.o \ +	quest/geas_file.o \ +	quest/geas_glk.o \ +	quest/geas_runner.o \ +	quest/geas_state.o \ +	quest/geas_util.o \ +	quest/quest.o \ +	quest/tstring.o \ +	quest/read_file.o \ +	quest/string.o \ +	quest/streams.o \  	scott/detection.o \  	scott/scott.o \  	tads/detection.o \ diff --git a/engines/glk/quest/detection.cpp b/engines/glk/quest/detection.cpp new file mode 100644 index 0000000000..a0aa6b8634 --- /dev/null +++ b/engines/glk/quest/detection.cpp @@ -0,0 +1,93 @@ +/* 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/quest/detection.h" +#include "glk/quest/detection_tables.h" +#include "common/debug.h" +#include "common/file.h" +#include "common/md5.h" +#include "engines/game.h" + +namespace Glk { +namespace Quest { + +void QuestMetaEngine::getSupportedGames(PlainGameList &games) { +	for (const PlainGameDescriptor *pd = QUEST_GAME_LIST; pd->gameId; ++pd) +		games.push_back(*pd); +} + +GameDescriptor QuestMetaEngine::findGame(const char *gameId) { +	for (const PlainGameDescriptor *pd = QUEST_GAME_LIST; pd->gameId; ++pd) { +		if (!strcmp(gameId, pd->gameId)) +			return *pd; +	} + +	return GameDescriptor::empty(); +} + +bool QuestMetaEngine::detectGames(const Common::FSList &fslist, DetectedGames &gameList) { +	// Loop through the files of the folder +	for (Common::FSList::const_iterator file = fslist.begin(); file != fslist.end(); ++file) { +		// Check for a recognised filename +		if (file->isDirectory()) +			continue; + +		Common::String filename = file->getName(); +		if (!filename.hasSuffixIgnoreCase(".quest")) +			continue; + +		Common::File gameFile; +		if (!gameFile.open(*file)) +			continue; + +		gameFile.seek(0); +		Common::String md5 = Common::computeStreamMD5AsString(gameFile, 5000); +		uint32 filesize = gameFile.size(); + +		// Scan through the Quest game list for a match +		const GlkDetectionEntry *p = QUEST_GAMES; +		while (p->_md5 && p->_filesize != filesize && md5 != p->_md5) +			++p; + +		if (!p->_gameId) { +			const PlainGameDescriptor &desc = QUEST_GAME_LIST[0]; +			gameList.push_back(GlkDetectedGame(desc.gameId, desc.description, filename, md5, filesize)); +		} else { +			// Found a match +			PlainGameDescriptor gameDesc = findGame(p->_gameId); +			gameList.push_back(GlkDetectedGame(p->_gameId, gameDesc.description, filename)); +		} +	} + +	return !gameList.empty(); +} + +void QuestMetaEngine::detectClashes(Common::StringMap &map) { +	for (const PlainGameDescriptor *pd = QUEST_GAME_LIST; pd->gameId; ++pd) { +		if (map.contains(pd->gameId)) +			error("Duplicate game Id found - %s", pd->gameId); +		map[pd->gameId] = ""; +	} +} + +} // End of namespace Quest +} // End of namespace Glk diff --git a/engines/glk/quest/detection.h b/engines/glk/quest/detection.h new file mode 100644 index 0000000000..d94e40da95 --- /dev/null +++ b/engines/glk/quest/detection.h @@ -0,0 +1,60 @@ +/* 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_QUEST_DETECTION +#define GLK_QUEST_DETECTION + +#include "common/fs.h" +#include "common/hash-str.h" +#include "engines/game.h" +#include "glk/detection.h" + +namespace Glk { +namespace Quest { + +class QuestMetaEngine { +public: +	/** +	 * Get a list of supported games +	 */ +	static void getSupportedGames(PlainGameList &games); + +	/** +	 * Returns a game description for the given game Id, if it's supported +	 */ +	static GameDescriptor findGame(const char *gameId); + +	/** +	 * Detect supported games +	 */ +	static bool detectGames(const Common::FSList &fslist, DetectedGames &gameList); + +	/** +	 * Check for game Id clashes with other sub-engines +	 */ +	static void detectClashes(Common::StringMap &map); +}; + +} // End of namespace Quest +} // End of namespace Glk + +#endif diff --git a/engines/glk/quest/detection_tables.h b/engines/glk/quest/detection_tables.h new file mode 100644 index 0000000000..8e1fe11ba6 --- /dev/null +++ b/engines/glk/quest/detection_tables.h @@ -0,0 +1,81 @@ +/* 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 "engines/game.h" +#include "common/gui_options.h" +#include "common/language.h" + +namespace Glk { +namespace Quest { + +const PlainGameDescriptor QUEST_GAME_LIST[] = { +	{ "quest", "Quest Game" }, + +	{ "attackonfrightside", "Attack On Frightside" }, +	{ "balaclava", "Balaclava" }, +	{ "bearsepicquest", "Bear's Epic Quest" }, +	{ "caught", "Caught!" }, +	{ "cuttings", "Cuttings" }, +	{ "draculacrl", "Dracula: CRL remake" }, +	{ "elections4", "It's election time in Pakistan: Go rich boy, go!" }, +	{ "everyman", "Everyman" }, +	{ "firstTimes", "First Times" }, +	{ "giftofthemagi", "Gift of the Magi" }, +	{ "medievalistsquest", "Medievalist's Quest" }, +	{ "parishotel", "Welcome to the Paris Hotel" }, +	{ "questforloot", "Quest for loot and something else" }, +	{ "sleepingassassin", "El asesino durmiente (The Sleeping Assassin)" }, +	{ "spondre", "Spondre" }, +	{ "thelasthero", "The Last Hero" }, +	{ "tokindlealight", "To Kindle a Light" }, +	{ "xanadu", "Xanadu - The World's Only Hope" }, + +	{ nullptr, nullptr } +}; + +const GlkDetectionEntry QUEST_GAMES[] = { +  DT_ENTRY0("attackonfrightside", "84542fc6460833bbf2594ed83f8b1fc7", 46019), +  DT_ENTRY0("balaclava", "8b30af05d9986f9f962c677181ecc766", 57719), +  DT_ENTRY0("bearsepicquest", "e6896a65527f456b4362aaebcf39e354", 62075), +  DT_ENTRY0("caught", "4502d89d8e304fe4165d46eb22f21f10", 5168593), +  DT_ENTRY0("cuttings", "e0ded5a6b78e8c9482e746d55f61972c", 6583866), +  DT_ENTRY0("draculacrl", "1af3ec877584b290f7ab1a1be8f944a5", 4548737), +  DT_ENTRY0("elections4", "d0bc0cd54182d6099808767068592b94", 591994), +  DT_ENTRY0("everyman", "410c7211d3f0c700f34e97ed258e33f1", 56218), +  DT_ENTRY0("firstTimes", "31d878c82d99856d473762612f154eb6", 10253826), +  DT_ENTRY0("giftofthemagi", "b33132ce71c8a2eed0f6c1c1af284765", 78647), +  DT_ENTRY0("medievalistsquest", "e0a15bc2a74a0bd6bb5c24661ea35829", 127977271), +  DT_ENTRY0("parishotel", "c9a42bc3f306aba5e318b0a74115e0d4", 474983), +  DT_ENTRY0("questforloot", "f7e32aec0f961a59a69bead3fadff4f0", 1357373), +  DT_ENTRY0("sleepingassassin", "9c2aa213bb73d8083506ee6f64436d9d", 287227), +  DT_ENTRY0("spondre", "c639077eb487eb6d1b63cda2c9ba5a9b", 1169469), +  DT_ENTRY0("thelasthero", "31e10b8a7f11a6289955b89437f8178c", 62512), +  DT_ENTRY0("tokindlealight", "5d3b57830b003046a621620ba0869d7c", 811845), + + +	DT_ENTRY0("xanadu", "fef25e3473755ec572d4236d56f918e2", 396973), + +	DT_END_MARKER +}; + +} // End of namespace Quest +} // End of namespace Glk diff --git a/engines/glk/quest/geas_file.cpp b/engines/glk/quest/geas_file.cpp new file mode 100644 index 0000000000..05c78e3e08 --- /dev/null +++ b/engines/glk/quest/geas_file.cpp @@ -0,0 +1,659 @@ +/* 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/quest/geas_file.h" +#include "glk/quest/reserved_words.h" +#include "glk/quest/read_file.h" +#include "glk/quest/geas_util.h" +#include "glk/quest/geas_impl.h" +#include "glk/quest/streams.h" +#include "glk/quest/string.h" + +namespace Glk { +namespace Quest { + +void report_error(const String &s); + +reserved_words obj_tag_property("look", "examine", "speak", "take", "alias", "prefix", "suffix", "detail", "displaytype", "gender", "article", "hidden", "invisible", (char *) NULL); + +reserved_words room_tag_property("look", "alias", "prefix", "indescription", "description", "north", "south", "east", "west", "northwest", "northeast", "southeast", "southwest", "up", "down", "out", (char *) NULL); + +void GeasFile::debug_print(String s) const { +	if (gi == NULL) +		cerr << s << endl; +	else +		gi->debug_print(s); +} + +const GeasBlock *GeasFile::find_by_name(String type, String name) const { +	//name = lcase (name); +	for (uint i = 0; i < size(type); i ++) { +		//cerr << "find_by_name (" << type << ", " << name << "), vs. '" +		//     << block(type, i).name << "'\n"; +		//if (block(type, i).lname == name) +		if (ci_equal(block(type, i).name, name)) +			return &block(type, i); +	} +	return NULL; +} + +const GeasBlock &GeasFile::block(String type, uint index) const { +	StringArrayIntMap::const_iterator iter; +	iter = type_indecies.find(type); +	if (!(iter != type_indecies.end() && index < (*iter)._value.size())) +		cerr << "Unable to find type " << type << "\n"; + +	assert(iter != type_indecies.end() && index < (*iter)._value.size()); +	//assert (index >= 0 && index < size(type)); +	return blocks[(*iter)._value[index]]; +} + +uint GeasFile::size(String type) const { +	//cerr << "GeasFile::size (" << type << ")" << endl; + +	// SENSITIVE? +	//std::map<String, Common::Array<int>, CI_LESS>::const_iterator iter; +	StringArrayIntMap::const_iterator iter; +	//cerr << type_indecies << endl; +	iter = type_indecies.find(type); +	if (iter == type_indecies.end()) { +		//cerr << "  returning 0" << endl; +		return 0; +	} +	//cerr << "  returning " << (*iter)._value.size() << endl; +	return (*iter)._value.size(); +} + + +bool GeasFile::obj_has_property(String objname, String propname) const { +	String tmp; +	return get_obj_property(objname, propname, tmp); +} + +Common::WriteStream &operator<< (Common::WriteStream &, const Set<String> &); + +/** + * Currently only works for actual objects, not rooms or the game + */ +//Set<String, CI_LESS> GeasFile::get_obj_keys (String obj) const +Set<String> GeasFile::get_obj_keys(String obj) const { +	//Set<String, CI_LESS> rv; +	Set<String> rv; +	get_obj_keys(obj, rv); +	return rv; +} + +void GeasFile::get_obj_keys(String obj, Set<String> &rv) const { +	cerr << "get_obj_keys (gf, <" << obj << ">)\n"; +	//Set<String> rv; + +	uint c1, c2; +	String tok, line; +	reserved_words *rw = NULL; + +	const GeasBlock *gb = find_by_name("object", obj); +	rw = &obj_tag_property; + +	if (gb == NULL) { +		cerr << "No such object found, aborting\n"; +		//return rv; +		return; +	} + +	for (uint i = 0; i < gb->data.size(); i ++) { +		line = gb->data[i]; +		cerr << "  handling line <" << line << ">\n"; +		tok = first_token(line, c1, c2); +		// SENSITIVE? +		if (tok == "properties") { +			tok = next_token(line, c1, c2); +			if (is_param(tok)) { +				vstring params = split_param(param_contents(tok)); +				for (uint j = 0; j < params.size(); j ++) { +					cerr << "   handling parameter <" << params[j] << ">\n"; +					uint k = params[j].find('='); +					// SENSITIVE? +					if (starts_with(params[j], "not ")) { +						rv.insert(trim(params[j].substr(4))); +						cerr << "     adding <" << trim(params[j].substr(4)) +						     << ">\n"; +					} else if (k == -1) { +						rv.insert(params[j]); +						cerr << "     adding <" << params[j] << ">\n"; +					} else { +						rv.insert(trim(params[j].substr(0, k))); +						cerr << "     adding <" << trim(params[j].substr(0, k)) +						     << ">\n"; +					} +				} +			} +		} +		// SENSITIVE? +		else if (tok == "type") { +			tok = next_token(line, c1, c2); +			if (is_param(tok)) +				get_type_keys(param_contents(tok), rv); +		} +		//else if (has (tag_property, tok) && tag_property[tok]) +		else if (rw != NULL && rw->has(tok)) { +			String tok1 = next_token(line, c1, c2); +			if (is_param(tok1)) +				rv.insert(tok); +		} +	} + +	cerr << "Returning (" << rv << ")\n"; +} + +void GeasFile::get_type_keys(String typen, Set<String> &rv) const { +	cerr << "get_type_keys (" << typen << ", " << rv << ")\n"; +	const GeasBlock *gb = find_by_name("type", typen); +	if (gb == NULL) { +		cerr << "  g_t_k: Nonexistent type\n"; +		return; +	} +	String line, tok; +	uint c1, c2; +	for (uint i = 0; i < gb->data.size(); i ++) { +		line = gb->data[i]; +		//cerr << "    g_t_k: Handling line '" << line << "'\n"; +		tok = first_token(line, c1, c2); +		// SENSISTIVE? +		if (tok == "type") { +			tok = next_token(line, c1, c2); +			if (is_param(tok)) { +				get_type_keys(param_contents(tok), rv); +				cerr << "      g_t_k: Adding <" << tok << "> to rv: " << rv << "\n"; +			} +		} +		// SENSITIVE? +		else if (tok == "action") { +			cerr << "       action, skipping\n"; +		} else { +			uint ch = line.find('='); +			if (ch != -1) { +				rv.insert(trim(line.substr(0, ch))); +				cerr << "      adding <" << trim(line.substr(0, ch)) << ">\n"; +			} +		} +	} +	cerr << "Returning (" << rv << ")\n"; +} + +bool GeasFile::get_obj_property(String objname, String propname, String &string_rv) const { +	cerr << "g_o_p: Getting prop <" << propname << "> of obj <" << objname << ">\n"; +	string_rv = "!"; +	bool bool_rv = false; + +	//cerr << "obj_types == " << obj_types << endl; +	/* +	cerr << "obj_types == \n"; +	for (map<String, String>::const_iterator iter = obj_types.begin(); +	     iter != obj_types.end(); iter ++) +	  cerr << "  " << (*iter)._key << " -> " << (*iter)._value << "\n"; +	cerr << ".\n"; +	*/ + +	/* +	String objtype; + +	if (objname == "game") +	  objtype = "game"; +	else if (!has (obj_types, objname)) +	  { +	    debug_print ("Checking property of nonexistent object " + objname); +	    return false; +	  } +	else +	  objtype = (*obj_types.find(objname))._value; +	*/ + +	if (!has(obj_types, objname)) { +		debug_print("Checking nonexistent object <" + objname + "> for property <" + propname + ">"); +		return false; +	} +	String objtype = (*obj_types.find(objname))._value; + +	const GeasBlock *block = find_by_name(objtype, objname); + +	String not_prop = "not " + propname; +	uint c1, c2; +	assert(block != NULL); +	//assert (block->data != NULL); +	for (uint i = 0; i < block->data.size(); i ++) { +		String line = block->data[i]; +		//cerr << "  g_o_p: Handling line <" << line << ">\n"; +		String tok = first_token(line, c1, c2); +		// SENSITIVE? +		if (tok == "type") { +			tok = next_token(line, c1, c2); +			if (is_param(tok)) +				get_type_property(param_contents(tok), propname, bool_rv, string_rv); +			else { +				debug_print("Expected parameter for type in " + line); +			} +		} +		// SENSITIVE? +		else if (tok == "properties") { +			tok = next_token(line, c1, c2); +			if (!is_param(tok)) { +				debug_print("Expected param on line " + line); +				continue; +			} +			Common::Array<String> props = split_param(param_contents(tok)); +			for (uint j = 0; j < props.size(); j ++) { +				//cerr << "    g_o_p: Comparing against <" << props[j] << ">\n"; +				uint index; +				if (props[j] == propname) { +					//cerr << "      g_o_p: Present but empty, blanking\n"; +					string_rv = ""; +					bool_rv = true; +				} else if (props[j] == not_prop) { +					//cerr << "      g_o_p: Negation, removing\n"; +					string_rv = "!"; +					bool_rv = false; +				} else if ((index = props[j].find('=')) != -1 && +				           (trim(props[j].substr(0, index)) == propname)) { +					string_rv = props[j].substr(index + 1); +					bool_rv = true; +					//cerr << "      g_o_p: Normal prop, now to <" << string_rv << ">\n"; +				} +			} +		} +	} +	cerr << "g_o_p: Ultimately returning " << (bool_rv ? "true" : "false") +	     << ", with String <" << string_rv << ">\n\n"; +	return bool_rv; +} + +void GeasFile::get_type_property(String typenamex, String propname, bool &bool_rv, String &string_rv) const { +	//cerr << "  Checking type <" << typenamex << "> for prop <" << propname << ">\n"; +	const GeasBlock *block = find_by_name("type", typenamex); +	if (block == NULL) { +		debug_print("Object of nonexistent type " + typenamex); +		return; +	} +	for (uint i = 0; i < block->data.size(); i ++) { +		String line = block->data[i]; +		//cerr << "    Comparing vs. line <" << line << ">\n"; +		uint c1, c2; +		String tok = first_token(line, c1, c2); + +		// SENSITIVE? +		if (tok == "type") { +			tok = next_token(line, c1, c2); +			if (is_param(tok)) +				get_type_property(param_contents(tok), propname, bool_rv, string_rv); +		} else if (line == propname) { +			bool_rv = true; +			string_rv = ""; +		} else { +			c1 = line.find('='); +			if (c1 != -1) { +				tok = trim(line.substr(0, c1)); +				if (tok == propname) { +					string_rv = trim(line.substr(c1 + 1)); +					bool_rv = true; +				} +			} +		} +		/* +		if (tok == propname) +		{ +		  cerr << "      match..."; +		  tok = next_token (line, c1, c2); +		  if (tok == "") +		    { +		      bool_rv = true; +		      string_rv = ""; +		      //cerr << " present but empty\n"; +		    } +		  else if (tok == "=") +		    { +		      bool_rv = true; +		      string_rv = trim (line.substr (c2)); +		      //cerr << " now <" << string_rv << ">\n"; +		    } +		  else +		    { +		      cerr << "Bad line while checking " << typenamex << " for prop " +		      << propname << ": " << line << endl; +		    } +		} +		     else if (tok == "type") +		{ +		  tok = next_token (line, c1, c2); +		  if (is_param (tok)) +		    get_type_property (param_contents(tok), propname, bool_rv, string_rv); +		} +		*/ +	} +} + + + +bool GeasFile::obj_of_type(String objname, String typenamex) const { +	if (!has(obj_types, objname)) { +		debug_print("Checking nonexistent obj <" + objname + "> for type <" + +		            typenamex + ">"); +		return false; +	} +	String objtype = (*obj_types.find(objname))._value; + +	const GeasBlock *block = find_by_name(objtype, objname); + +	uint c1, c2; +	assert(block != NULL); +	for (uint i = 0; i < block->data.size(); i ++) { +		String line = block->data[i]; +		String tok = first_token(line, c1, c2); +		// SENSITIVE? +		if (tok == "type") { +			tok = next_token(line, c1, c2); +			if (is_param(tok)) { +				if (type_of_type(param_contents(tok), typenamex)) +					return true; +			} else { +				debug_print("Eg_o_p: xpected parameter for type in " + line); +			} +		} +	} +	return false; +} + + +bool GeasFile::type_of_type(String subtype, String supertype) const { +	if (ci_equal(subtype, supertype)) +		return true; +	//cerr << "  Checking type <" << subtype << "> for type <" << supertype << ">\n"; +	const GeasBlock *block = find_by_name("type", subtype); +	if (block == NULL) { +		debug_print("t_o_t: Nonexistent type " + subtype); +		return false; +	} +	for (uint i = 0; i < block->data.size(); i ++) { +		String line = block->data[i]; +		//cerr << "    Comparing vs. line <" << line << ">\n"; +		uint c1, c2; +		String tok = first_token(line, c1, c2); +		// SENSITIVE? +		if (tok == "type") { +			tok = next_token(line, c1, c2); +			if (is_param(tok) && type_of_type(param_contents(tok), supertype)) +				return true; +		} +	} +	return false; +} + + + +bool GeasFile::get_obj_action(String objname, String propname, String &string_rv) const { +	cerr << "g_o_a: Getting action <" << propname << "> of object <" << objname << ">\n"; +	string_rv = "!"; +	bool bool_rv = false; + +	//cerr << "obj_types == " << obj_types << endl; +	/* +	cerr << "obj_types == \n"; +	for (map<String, String>::const_iterator iter = obj_types.begin(); +	     iter != obj_types.end(); iter ++) +	  cerr << "  " << (*iter)._key << " -> " << (*iter)._value << "\n"; +	cerr << ".\n"; +	*/ +	if (!has(obj_types, objname)) { +		debug_print("Checking nonexistent object <" + objname + "> for action <" + propname + ">."); +		return false; +	} +	String objtype = (*obj_types.find(objname))._value; + +	//reserved_words *rw; + +	const GeasBlock *block = find_by_name(objtype, objname); +	String not_prop = "not " + propname; +	uint c1, c2; +	for (uint i = 0; i < block->data.size(); i ++) { +		String line = block->data[i]; +		//cerr << "  g_o_a: Handling line <" << line << ">\n"; +		String tok = first_token(line, c1, c2); +		// SENSITIVE? +		if (tok == "type") { +			tok = next_token(line, c1, c2); +			if (is_param(tok)) +				get_type_action(param_contents(tok), propname, bool_rv, string_rv); +			else { +				gi->debug_print("Expected parameter for type in " + line); +			} +		} +		/* +		else if (rw != NULL && tok == propname && rw->has(propname)) +		{ +		  tok = next_token (line, c1, c2); +		  if (is_param(tok)) +		    { +		      cerr << "   Parameter, skipping\n"; +		    } +		  else +		    { +		      //cerr << "   Action, skipping\n"; +		      cerr << "   Action, string_rv is now <" << string_rv << ">\n"; +		      string_rv = line.substr (c1); +		      bool_rv = true; +		    } +		} +		     */ +		// SENSITIVE? +		else if (tok == "action") { +			tok = next_token(line, c1, c2); +			if (is_param(tok) && param_contents(tok) == propname) { +				if (c2 + 1 < line.length()) +					string_rv = line.substr(c2 + 1); +				else +					string_rv = ""; +				bool_rv = true; +				cerr << "   Action line, string_rv now <" << string_rv << ">\n"; +			} +		} +	} + +	cerr << "g_o_a: Ultimately returning value " << (bool_rv ? "true" : "false")  << ", with String <" << string_rv << ">\n\n"; + +	return bool_rv; +} + +void GeasFile::get_type_action(String typenamex, String actname, bool &bool_rv, String &string_rv) const { +	//cerr << "  Checking type <" << typenamex << "> for action <" << actname << ">\n"; +	const GeasBlock *block = find_by_name("type", typenamex); +	if (block == NULL) { +		debug_print("Object of nonexistent type " + typenamex); +		return; +	} +	for (uint i = 0; i < block->data.size(); i ++) { +		String line = block->data[i]; +		//cerr << "    g_t_a: Comparing vs. line <" << line << ">\n"; +		uint c1, c2; +		String tok = first_token(line, c1, c2); +		// SENSITIVE? +		if (tok == "action") { +			//cerr << "      match...\n"; +			tok = next_token(line, c1, c2); +			if (is_param(tok) && param_contents(tok) == actname) { +				bool_rv = true; +				string_rv = line.substr(c2); +				//cerr << " present: {" + string_rv + "}\n"; +			} +		} +		// SENSITIVE? +		else if (tok == "type") { +			tok = next_token(line, c1, c2); +			if (is_param(tok)) +				get_type_action(param_contents(tok), actname, bool_rv, string_rv); +		} +	} +} + +void GeasFile::register_block(String blockname, String blocktype) { +	cerr << "registering block " << blockname << " / " << blocktype << endl; +	if (has(obj_types, blockname)) { +		String errdesc = "Trying to register block of named <" + blockname + +		                 "> of type <" + blocktype + "> when there is already one, of type <" + +		                 obj_types[blockname] + ">"; +		debug_print(errdesc); +		throw errdesc; +	} +	obj_types[blockname] = blocktype; +} + +String GeasFile::static_svar_lookup(String varname) const { +	cerr << "static_svar_lookup(" << varname << ")" << endl; +	//varname = lcase (varname); +	for (uint i = 0; i < size("variable"); i ++) +		//if (blocks[i].lname == varname) +		if (ci_equal(blocks[i].name, varname)) { +			String rv; +			String tok; +			uint c1, c2; +			bool found_typeline = false; +			for (uint j = 0; j < blocks[i].data.size(); j ++) { +				String line = blocks[i].data[j]; +				tok = first_token(line, c1, c2); +				// SENSITIVE? +				if (tok == "type") { +					tok = next_token(line, c1, c2); +					// SENSITIVE? +					if (tok == "numeric") +						throw String("Trying to evaluate int var '" + varname + +						             "' as String"); +					// SENSITIVE? +					if (tok != "String") +						throw String("Bad variable type " + tok); +					found_typeline = true; +				} +				// SENSITIVE? +				else if (tok == "value") { +					tok = next_token(line, c1, c2); +					if (!is_param(tok)) +						throw String("Expected param after value in " + line); +					rv = param_contents(tok); +				} +			} +			if (!found_typeline) +				throw String(varname + " is a numeric variable"); +			cerr << "static_svar_lookup(" << varname << ") -> \"" << rv << "\"" << endl; +			return rv; +		} +	debug_print("Variable <" + varname + "> not found."); +	return ""; +} + +String GeasFile::static_ivar_lookup(String varname) const { +	//varname = lcase (varname); +	for (uint i = 0; i < size("variable"); i ++) +		//if (blocks[i].lname == varname) +		if (ci_equal(blocks[i].name, varname)) { +			String rv; +			String tok; +			uint c1, c2; +			for (uint j = 0; j < blocks[i].data.size(); j ++) { +				String line = blocks[i].data[j]; +				tok = first_token(line, c1, c2); +				// SENSITIVE? +				if (tok == "type") { +					tok = next_token(line, c1, c2); +					// SENSITIVE? +					if (tok == "String") +						throw String("Trying to evaluate String var '" + varname + +						             "' as numeric"); +					// SENSITIVE? +					if (tok != "numeric") +						throw String("Bad variable type " + tok); +				} +				// SENSITIVE? +				else if (tok == "value") { +					tok = next_token(line, c1, c2); +					if (!is_param(tok)) +						throw String("Expected param after value in " + line); +					rv = param_contents(tok); +				} +			} +			return rv; +		} +	debug_print("Variable <" + varname + "> not found"); +	return "-32768"; +} + +String GeasFile::static_eval(String input) const { +	//cerr << "static_eval (" << input << ")" << endl; +	String rv = ""; +	for (uint i = 0; i < input.length(); i ++) { +		if (input[i] == '#') { +			uint j; +			for (j = i + 1; j < input.length() && input[j] != '#'; j ++) +				; +			if (j == input.length()) +				throw String("Error processing '" + input + "', odd hashes"); +			uint k; +			for (k = i + 1; k < j && input[k] != ':'; k ++) +				; +			if (k == ':') { +				String objname; +				if (input[i + 1] == '(' && input[k - 1] == ')') +					objname = static_svar_lookup(input.substr(i + 2, k - i - 4)); +				else +					objname = input.substr(i + 1, k - i - 2); +				cerr << "  objname == '" << objname << endl; +				//rv += get_obj_property (objname, input.substr (k+1, j-k-2)); +				String tmp; +				bool had_var; + +				String objprop = input.substr(k + 1, j - k - 2); +				cerr << "  objprop == " << objprop << endl; +				had_var = get_obj_property(objname, objprop, tmp); +				rv += tmp; +				if (!had_var) +					debug_print("Requesting nonexistent property <" + objprop + +					            "> of object <" + objname + ">"); +			} else { +				cerr << "i == " << i << ", j == " << j << ", length is " << input.length() << endl; +				cerr << "Looking up static var " << input.substr(i + 1, j - i - 1) << endl; +				rv += static_svar_lookup(input.substr(i + 1, j - i - 1)); +			} +			i = j; +		} else if (input[i] == '%') { +			uint j; +			for (j = i; j < input.length() && input[j] != '%'; j ++) +				; +			if (j == input.length()) +				throw String("Error processing '" + input + "', unmatched %"); +			rv += static_ivar_lookup(input.substr(i + 1, j - i - 2)); +			i = j; +		} else +			rv += input[i]; +	} +	if (rv != input) +		cerr << "*** CHANGED ***\n"; +	//cerr << "static_eval (" << input << ") --> \"" << rv << "\"" << endl; +	return rv; +} + +} // End of namespace Quest +} // End of namespace Glk diff --git a/engines/glk/quest/geas_file.h b/engines/glk/quest/geas_file.h new file mode 100644 index 0000000000..e9558cadd8 --- /dev/null +++ b/engines/glk/quest/geas_file.h @@ -0,0 +1,123 @@ +/* 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_QUEST_GEAS_FILE +#define GLK_QUEST_GEAS_FILE + +#include "glk/quest/string.h" +#include "common/algorithm.h" +#include "common/hashmap.h" +#include "common/hash-str.h" +#include "common/stream.h" + +namespace Glk { +namespace Quest { + +class GeasInterface; + +class reserved_words; + +/** + * Ordered array of items + */ +template<class T> +class Set : public Common::Array<T> { +public: +	/** +	 * Insert a new item +	 */ +	void insert(T val) { +		this->push_back(val); +		Common::sort(this->begin(), this->end()); +	} +}; + +struct GeasBlock { +	////// lname == lowercase name +	////// nname == normal name +	////// parent == initial parent object (lowercased) +	// name == name +	// parent == initial parent object +	String blocktype, name, parent; +	Common::Array<String> data; +	//GeasBlock (const Common::Array<String> &, String, uint, bool); +	GeasBlock() {} +}; + +struct GeasFile { +	GeasInterface *gi; +	void debug_print(String s) const; + +	//vector<GeasBlock> rooms, objects, textblocks, functions, procedures, types; +	//GeasBlock synonyms, game; +	Common::Array <GeasBlock> blocks; + +	//Common::Array<GeasBlock> rooms, objects, textblocks, functions, procedures, +	//  types, synonyms, game, variables, timers, choices; +	StringMap obj_types; +	StringArrayIntMap type_indecies; + +	void register_block(String blockname, String blocktype); + +	const GeasBlock &block(String type, uint index) const; +	uint size(String type) const; + +	void read_into(const Common::Array<String> &, String, uint, bool, const reserved_words &, const reserved_words &); + + + +	GeasFile() : gi(nullptr) {} +	explicit GeasFile(const Common::Array<String> &in_data, +	                  GeasInterface *gi); + +	bool obj_has_property(String objname, String propname) const; +	bool get_obj_property(String objname, String propname, +	                      String &rv) const; + +	void get_type_property(String typenamex, String propname, +	                       bool &, String &) const; +	bool obj_of_type(String object, String type) const; +	bool type_of_type(String subtype, String supertype) const; + +	Set<String> get_obj_keys(String obj) const; +	void get_obj_keys(String, Set<String> &) const; +	void get_type_keys(String, Set<String> &) const; + +	bool obj_has_action(String objname, String propname) const; +	bool get_obj_action(String objname, String propname, +	                    String &rv) const; +	void get_type_action(String typenamex, String propname, +	                     bool &, String &) const; +	String static_eval(String) const; +	String static_ivar_lookup(String varname) const; +	String static_svar_lookup(String varname) const; + +	const GeasBlock *find_by_name(String type, String name) const; +}; + +Common::WriteStream &operator<<(Common::WriteStream &, const GeasBlock &); +Common::WriteStream &operator<<(Common::WriteStream &, const GeasFile &); + +} // End of namespace Quest +} // End of namespace Glk + +#endif diff --git a/engines/glk/quest/geas_glk.cpp b/engines/glk/quest/geas_glk.cpp new file mode 100644 index 0000000000..8f48799d9a --- /dev/null +++ b/engines/glk/quest/geas_glk.cpp @@ -0,0 +1,240 @@ +/* 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/quest/geas_glk.h" +#include "glk/quest/geas_runner.h" +#include "glk/quest/quest.h" +#include "glk/quest/streams.h" +#include "glk/windows.h" + +namespace Glk { +namespace Quest { + +void glk_put_cstring(const char *); + + +winid_t mainglkwin; +winid_t inputwin; +winid_t bannerwin; +strid_t inputwinstream; + +const bool use_inputwindow = false; + +int ignore_lines;			// count of lines to ignore in game output + +void draw_banner() { +	uint32 width; +	uint32 index; +	if (bannerwin) { +		g_vm->glk_window_clear(bannerwin); +		g_vm->glk_window_move_cursor(bannerwin, 0, 0); +		strid_t stream = g_vm->glk_window_get_stream(bannerwin); + +		g_vm->glk_set_style_stream(stream, style_User1); +		g_vm->glk_window_get_size(bannerwin, &width, NULL); +		for (index = 0; index < width; index++) +			g_vm->glk_put_char_stream(stream, ' '); +		g_vm->glk_window_move_cursor(bannerwin, 1, 0); + +		if (g_vm->banner.empty()) +			g_vm->glk_put_string_stream(stream, (char *)"Geas 0.4"); +		else +			g_vm->glk_put_string_stream(stream, (char *)g_vm->banner.c_str()); +	} +} + +void glk_put_cstring(const char *s) { +	/* The cast to remove const is necessary because g_vm->glk_put_string +	 * receives a "char *" despite the fact that it could equally well use +	 * "const char *". */ +	g_vm->glk_put_string((char *)s); +} + +GeasResult GeasGlkInterface::print_normal(const String &s) { +	if (!ignore_lines) +		glk_put_cstring(s.c_str()); +	return r_success; +} + +GeasResult GeasGlkInterface::print_newline() { +	if (!ignore_lines) +		glk_put_cstring("\n"); +	else +		ignore_lines--; +	return r_success; +} + +GeasResult GeasGlkInterface::set_style(const GeasFontStyle &style) { +	// Glk styles are defined before the window opens, so at this point we can only +	// pick the most suitable style, not define a new one. +	uint match; +	if (style.is_italic && style.is_bold) +		match = style_Alert; +	else if (style.is_italic) +		match = style_Emphasized; +	else if (style.is_bold) +		match = style_Subheader; +	else if (style.is_underlined) +		match = style_User2; +	else +		match = style_Normal; + +	g_vm->glk_set_style_stream(g_vm->glk_window_get_stream(mainglkwin), match); +	return r_success; +} + +void GeasGlkInterface::set_foreground(String s) { +	if (s != "") { +	} +} + +void GeasGlkInterface::set_background(String s) { +	if (s != "") { +	} +} + +/* Code lifted from GeasWindow.  Should be common.  Maybe in + * GeasInterface? + */ +String GeasGlkInterface::get_file(const String &fname) const { +	Common::File ifs; +	if (ifs.open(fname)) { +		glk_put_cstring("Couldn't open "); +		glk_put_cstring(fname.c_str()); +		g_vm->glk_put_char(0x0a); +		return ""; +	} + +	// Read entirety of the file +	char *buf = new char[ifs.size()]; +	ifs.read(buf, ifs.size()); +	 +	String result(buf, buf + ifs.size()); +	delete[] buf; + +	return result; +} + +String GeasGlkInterface::get_string() { +	char buf[200]; +	g_vm->glk_request_line_event(inputwin, buf, (sizeof buf) - 1, 0); +	while (1) { +		event_t ev; + +		g_vm->glk_select(&ev); + +		if (ev.type == evtype_LineInput && ev.window == inputwin) { +			return String(buf, ev.val1); +		} +		/* All other events, including timer, are deliberately +		 * ignored. +		 */ +	} +} + +uint GeasGlkInterface::make_choice(String label, Common::Array<String> v) { +	size_t n; + +	g_vm->glk_window_clear(inputwin); + +	glk_put_cstring(label.c_str()); +	g_vm->glk_put_char(0x0a); +	n = v.size(); +	for (size_t i = 0; i < n; ++i) { +		StringStream t; +		String s; +		t << i + 1; +		t >> s; +		glk_put_cstring(s.c_str()); +		glk_put_cstring(": "); +		glk_put_cstring(v[i].c_str()); +		glk_put_cstring("\n"); +	} + +	StringStream t; +	String s; +	String s1; +	t << n; +	t >> s; +	s1 = "Choose [1-" + s + "]> "; +	g_vm->glk_put_string_stream(inputwinstream, (char *)(s1.c_str())); + +	int choice = atoi(get_string().c_str()); +	if (choice < 1) { +		choice = 1; +	} +	if ((size_t)choice > n) { +		choice = n; +	} + +	StringStream u; +	u << choice; +	u >> s; +	s1 = "Chosen: " +  s + "\n"; +	glk_put_cstring(s1.c_str()); + +	return choice - 1; +} + +String GeasGlkInterface::absolute_name(String rel_name, String parent) const { +	cerr << "absolute_name ('" << rel_name << "', '" << parent << "')\n"; +	if (parent[0] != '/') +		return rel_name; + +	if (rel_name[0] == '/') { +		cerr << "  --> " << rel_name << "\n"; +		return rel_name; +	} +	Common::Array<String> path; +	uint dir_start = 1, dir_end; +	while (dir_start < parent.length()) { +		dir_end = dir_start; +		while (dir_end < parent.length() && parent[dir_end] != '/') +			dir_end ++; +		path.push_back(parent.substr(dir_start, dir_end - dir_start)); +		dir_start = dir_end + 1; +	} +	path.pop_back(); +	dir_start = 0; +	String tmp; +	while (dir_start < rel_name.length()) { +		dir_end = dir_start; +		while (dir_end < rel_name.length() && rel_name[dir_end] != '/') +			dir_end ++; +		tmp = rel_name.substr(dir_start, dir_end - dir_start); +		dir_start = dir_end + 1; +		if (tmp == ".") +			continue; +		else if (tmp == "..") +			path.pop_back(); +		else +			path.push_back(tmp); +	} +	String rv; +	for (uint i = 0; i < path.size(); i ++) +		rv = rv + "/" + path[i]; +	cerr << " ---> " << rv << "\n"; +	return rv; +} + +} // End of namespace Quest +} // End of namespace Glk diff --git a/engines/glk/quest/geas_glk.h b/engines/glk/quest/geas_glk.h new file mode 100644 index 0000000000..2647bf13b5 --- /dev/null +++ b/engines/glk/quest/geas_glk.h @@ -0,0 +1,92 @@ +/* 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_QUEST_GEAS_GLK +#define GLK_QUEST_GEAS_GLK + +#include "glk/quest/geas_runner.h" +#include "glk/windows.h" + +namespace Glk { +namespace Quest { + + +/* User interface bridge from Geas Core to Glk. + +  Glk Window arrangment. + +    +---------+ +    |    B    | +    +---------+ +    |    M    | +    |         | +    +---------+ +    |    I    | +    +---------+ + +  B is a one line "banner window", showing the game name and author.  Kept +  in the global variable, it's optional, null if unavailable. +  optional. +  M is the main window where the text of the game appears.  Kept in the +  global variable mainglkwin. +  I is a one line "input window" where the user inputs their commands. +  Kept in the global variable inputwin, it's optional, and if not separate +  is set to mainglkwin. + +  Maybe in future revisions there will be a status window (including a +  compass rose). +*/ + +class GeasGlkInterface : public GeasInterface { +protected: +	virtual String get_file(const String &fname) const; +	virtual GeasResult print_normal(const String &s); +	virtual GeasResult print_newline(); + +	virtual void set_foreground(String); +	virtual void set_background(String); +	virtual GeasResult set_style(const GeasFontStyle &); + +	virtual String get_string(); +	virtual uint make_choice(String, Common::Array<String>); + +	virtual String absolute_name(String, String) const; +public: +	GeasGlkInterface() { +		; +	} +}; + +extern winid_t mainglkwin; +extern winid_t inputwin; +extern winid_t bannerwin; +extern strid_t inputwinstream; +extern int ignore_lines; +extern const bool use_inputwindow; + +extern void glk_put_cstring(const char *); +extern void draw_banner(); + +} // End of namespace Quest +} // End of namespace Glk + +#endif diff --git a/engines/glk/quest/geas_impl.h b/engines/glk/quest/geas_impl.h new file mode 100644 index 0000000000..1bdbbfdd9a --- /dev/null +++ b/engines/glk/quest/geas_impl.h @@ -0,0 +1,214 @@ +/* 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_QUEST_GEAS_IMPL +#define GLK_QUEST_GEAS_IMPL + +#include "glk/quest/geas_runner.h" +#include "glk/quest/geas_state.h" +#include "glk/quest/limit_stack.h" + +namespace Glk { +namespace Quest { + +struct match_binding { +	String var_name; +	String var_text; +	uint start, end; +	//operator String(); +	String tostring(); +	match_binding(String vn, uint i) : var_name(vn), start(i) {} +	void set(String vt, uint i) { +		var_text = vt; +		end = i; +	} +}; + +Common::WriteStream &operator<< (Common::WriteStream &, const match_binding &); + + +struct match_rv { +	bool success; +	Common::Array<match_binding> bindings; +	//match_rv (bool b, const Common::Array<String> &v) : success(b), bindings(v) {} +	match_rv() : success(false) {} +	match_rv(bool b, const match_rv &rv) : success(b), bindings(rv.bindings) {} +	operator bool () { +		return success; +	} +}; + +Common::WriteStream &operator<< (Common::WriteStream &o, const match_rv &rv); +/* +  inline ostream &operator<< (ostream &o, const match_rv &rv) +{ +  //o << "match_rv {" << (rv.success ? "TRUE" : "FALSE") << ": " << rv.bindings << "}"; +  o << "match_rv {" << (rv.success ? "TRUE" : "FALSE") << ": ["; +  //o << rv.bindings.size(); +  //o << rv.bindings; +  for (uint i = 0; i < rv.bindings.size(); i ++) +    o << rv.bindings[i] << ", "; +  o << "]}"; +  return o; +} +*/ + +class geas_implementation : public GeasRunner { +	//GeasInterface *gi; +	GeasFile gf; +	//bool running; +	bool dont_process, outputting; +	LimitStack <GeasState> undo_buffer; +	Common::Array <String> function_args; +	String this_object; +	v2string current_places; +	bool is_running_; +	Logger logger; + +public: +	geas_implementation(GeasInterface *in_gi) +		: GeasRunner(in_gi), undo_buffer(20), is_running_(true) {} +	void set_game(const String &fname); + +	bool is_running() const; +	String get_banner(); +	void run_command(String); +	bool try_match(String s, bool, bool); +	match_rv match_command(String input, String action) const; +	match_rv match_command(String input, uint ichar, +	                       String action, uint achar, match_rv rv) const; +	bool dereference_vars(Common::Array<match_binding> &bindings, bool is_internal) const; +	bool dereference_vars(Common::Array<match_binding> &, const Common::Array<String> &, bool is_internal) const; +	bool match_object(String text, String name, bool is_internal = false) const; +	void set_vars(const Common::Array<match_binding> &v); +	bool run_commands(String, const GeasBlock *, bool is_internal = false); + +	void display_error(String errorname, String object = ""); + +	String substitute_synonyms(String) const; + +	void set_svar(String, String); +	void set_svar(String, uint, String); +	void set_ivar(String, int); +	void set_ivar(String, uint, int); + +	String get_svar(String) const; +	String get_svar(String, uint) const; +	int get_ivar(String) const; +	int get_ivar(String, uint) const; + +	bool find_ivar(String, uint &) const; +	bool find_svar(String, uint &) const; + +	void regen_var_look(); +	void regen_var_dirs(); +	void regen_var_objects(); +	void regen_var_room(); + +	void look(); + +	String displayed_name(String object) const; +	//String get_obj_name (const Common::Array<String> &args) const; +	String get_obj_name(String name, const Common::Array<String> &where, bool is_internal) const; + +	bool has_obj_property(String objname, String propname) const; +	bool get_obj_property(String objname, String propname, +	                      String &rv) const; +	bool has_obj_action(String obj, String prop) const; +	bool get_obj_action(String objname, String actname, +	                    String &rv) const; +	String exit_dest(String room, String dir, bool *is_act = NULL) const; +	Common::Array<Common::Array<String> > get_places(String room); + +	void set_obj_property(String obj, String prop); +	void set_obj_action(String obj, String act); +	void move(String obj, String dest); +	void goto_room(String room); +	String get_obj_parent(String obj); + +	void print_eval(String); +	void print_eval_p(String); +	String eval_string(String s); +	String eval_param(String s) { +		assert(is_param(s)); +		return eval_string(param_contents(s)); +	} + + +	void run_script_as(String, String); +	void run_script(String); +	void run_script(String, String &); +	void run_procedure(String); +	void run_procedure(String, Common::Array<String> args); +	String run_function(String); +	String run_function(String, Common::Array<String> args); +	String bad_arg_count(String); + +	bool eval_conds(String); +	bool eval_cond(String); +	GeasState state; + +	virtual void tick_timers(); +	virtual v2string get_inventory(); +	virtual v2string get_room_contents(); +	v2string get_room_contents(String); +	virtual vstring get_status_vars(); +	virtual Common::Array<bool> get_valid_exits(); + + +	inline void print_formatted(String s) const { +		if (outputting) gi->print_formatted(s); +	} +	inline void print_normal(String s) const { +		if (outputting) gi->print_normal(s); +	} +	inline void print_newline() const { +		if (outputting) gi->print_newline(); +	} + +	/* +	inline void print_formatted (String s) const { +	  if (outputting) +	    gi->print_formatted(s); +	  else +	    gi->print_formatted ("{{" + s + "}}"); +	} +	inline void print_normal (String s) const +	{ +	  if (outputting) +	    gi->print_normal (s); +	  else +	    gi->print_normal("{{" + s + "}}"); +	} +	inline void print_newline() const { +	  if (outputting) +	    gi->print_newline(); +	  else +	    gi->print_normal ("{{|n}}"); +	} +	*/ +}; + +} // End of namespace Quest +} // End of namespace Glk + +#endif diff --git a/engines/glk/quest/geas_runner.cpp b/engines/glk/quest/geas_runner.cpp new file mode 100644 index 0000000000..a029c07824 --- /dev/null +++ b/engines/glk/quest/geas_runner.cpp @@ -0,0 +1,3675 @@ +/* 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/quest/geas_runner.h" +#include "glk/quest/read_file.h" +#include "glk/quest/geas_state.h" +#include "glk/quest/geas_util.h" +#include "glk/quest/reserved_words.h" +#include "glk/quest/geas_impl.h" +#include "glk/quest/quest.h" +#include "glk/quest/streams.h" +#include "glk/quest/String.h" + +namespace Glk { +namespace Quest { + +class GeasInterface; + +static const char *dir_names[] = {"north", "south", "east", "west", "northeast", "northwest", "southeast", "southwest", "up", "down", "out"}; +static const char *short_dir_names[] = {"n", "s", "e", "w", "ne", "nw", "se", "sw", "u", "d", "out"}; + +const ObjectRecord *get_obj_record(const Common::Array<ObjectRecord> &v, const String &name) { +	for (uint i = 0; i < v.size(); i ++) +		if (ci_equal(v[i].name, name)) +			return &v[i]; +	return nullptr; +} + + + +GeasRunner *GeasRunner::get_runner(GeasInterface *gi) { +	return new geas_implementation(gi); +} + +bool geas_implementation::find_ivar(String name, uint &rv) const { +	for (uint n = 0; n < state.ivars.size(); n ++) +		if (ci_equal(state.ivars[n].name, name)) { +			rv = n; +			return true; +		} +	return false; +} + +bool geas_implementation::find_svar(String name, uint &rv) const { +	//name = lcase (name); +	for (uint n = 0; n < state.svars.size(); n ++) +		if (ci_equal(state.svars[n].name, name)) { +			rv = n; +			return true; +		} +	return false; +} + +void geas_implementation::set_svar(String varname, String varval) { +	cerr << "set_svar (" << varname << ", " << varval << ")\n"; +	int i1 = varname.find('['); +	if (i1 == -1) +		return set_svar(varname, 0, varval); +	if (varname[varname.length() - 1] != ']') { +		gi->debug_print("set_svar: Badly formatted name " + varname); +		return; +	} +	String arrayname = varname.substr(0, i1); +	String indextext = varname.substr(i1 + 1, varname.length() - i1 - 2); +	cerr << "set_svar(" << varname << ") --> set_svar (" << arrayname << ", " << indextext << ")\n"; +	for (uint c3 = 0; c3 < indextext.size(); c3 ++) +		if (indextext[c3] < '0' || indextext[c3] > '9') { +			set_svar(arrayname, get_ivar(indextext), varval); +			return; +		} +	set_svar(arrayname, parse_int(indextext), varval); +	return; +} + +void geas_implementation::set_svar(String varname, uint index, String varval) { +	uint n, m; +	if (!find_svar(varname, n)) { +		if (find_ivar(varname, m)) { +			gi->debug_print("Defining " + varname + " as String variable when there is already a numeric variable of that name."); +			return; +		} +		SVarRecord svr; +		svr.name = varname; +		n = state.svars.size(); +		state.svars.push_back(svr); +	} +	state.svars[n].set(index, varval); +	if (index == 0) { +		for (uint varn = 0; varn < gf.size("variable"); varn ++) { +			const GeasBlock &go(gf.block("variable", varn)); +			if (ci_equal(go.name, varname)) { +				String script = ""; +				uint c1, c2; +				for (uint j = 0; j < go.data.size(); j ++) +					// SENSITIVE ? +					if (first_token(go.data[j], c1, c2) == "onchange") +						script = trim(go.data[j].substr(c2 + 1)); +				if (script != "") +					run_script(script); +			} +		} +	} +} + +String geas_implementation::get_svar(String varname) const { +	int i1 = varname.find('['); +	if (i1 == -1) +		return get_svar(varname, 0); +	if (varname[varname.length() - 1] != ']') { +		gi->debug_print("get_svar: badly formatted name " + varname); +		return ""; +	} +	String arrayname = varname.substr(0, i1); +	String indextext = varname.substr(i1 + 1, varname.length() - i1 - 2); +	cerr << "get_svar(" << varname << ") --> get_svar (" << arrayname << ", " << indextext << ")\n"; +	for (uint c3 = 0; c3 < indextext.size(); c3 ++) +		if (indextext[c3] < '0' || indextext[c3] > '9') +			return get_svar(arrayname, get_ivar(indextext)); +	return get_svar(arrayname, parse_int(indextext)); +} +String geas_implementation::get_svar(String varname, uint index) const { +	for (uint i = 0; i < state.svars.size(); i ++) { +		if (ci_equal(state.svars[i].name, varname)) +			return state.svars[i].get(index); +	} + +	gi->debug_print("get_svar (" + varname + ", " + string_int(index) + "): No such variable defined."); +	return ""; +} + +int geas_implementation::get_ivar(String varname) const { +	int i1 = varname.find('['); +	if (i1 == -1) +		return get_ivar(varname, 0); +	if (varname[varname.length() - 1] != ']') { +		gi->debug_print("get_ivar: Badly formatted name " + varname); +		return -32767; +	} +	String arrayname = varname.substr(0, i1); +	String indextext = varname.substr(i1 + 1, varname.length() - i1 - 2); +	cerr << "get_ivar(" << varname << ") --> get_ivar (" << arrayname << ", " << indextext << ")\n"; +	for (uint c3 = 0; c3 < indextext.size(); c3 ++) +		if (indextext[c3] < '0' || indextext[c3] > '9') +			return get_ivar(arrayname, get_ivar(indextext)); +	return get_ivar(arrayname, parse_int(indextext)); +} +int geas_implementation::get_ivar(String varname, uint index) const { +	for (uint i = 0; i < state.ivars.size(); i ++) +		if (ci_equal(state.ivars[i].name, varname)) +			return state.ivars[i].get(index); +	gi->debug_print("get_ivar: Tried to read undefined int '" + varname + +	                "' [" + string_int(index) + "]"); +	return -32767; +} +void geas_implementation::set_ivar(String varname, int varval) { +	int i1 = varname.find('['); +	if (i1 == -1) +		return set_ivar(varname, 0, varval); +	if (varname[varname.length() - 1] != ']') { +		gi->debug_print("set_ivar: Badly formatted name " + varname); +		return; +	} +	String arrayname = varname.substr(0, i1); +	String indextext = varname.substr(i1 + 1, varname.length() - i1 - 2); +	cerr << "set_svar(" << varname << ") --> set_svar (" << arrayname << ", " << indextext << ")\n"; +	for (uint c3 = 0; c3 < indextext.size(); c3 ++) +		if (indextext[c3] < '0' || indextext[c3] > '9') { +			set_ivar(arrayname, get_ivar(indextext), varval); +			return; +		} +	set_ivar(arrayname, parse_int(indextext), varval); +} + +void geas_implementation::set_ivar(String varname, uint index, int varval) { +	uint n, m; +	if (!find_ivar(varname, n)) { +		if (find_svar(varname, m)) { +			gi->debug_print("Defining " + varname + " as numeric variable when there is already a String variable of that name."); +			return; +		} +		IVarRecord ivr; +		ivr.name = varname; +		n = state.ivars.size(); +		state.ivars.push_back(ivr); +	} +	state.ivars[n].set(index, varval); +	if (index == 0) { +		for (uint varn = 0; varn < gf.size("variable"); varn ++) { +			const GeasBlock &go(gf.block("variable", varn)); +			//if (go.lname == varname) +			if (ci_equal(go.name, varname)) { +				String script = ""; +				uint c1, c2; +				for (uint j = 0; j < go.data.size(); j ++) +					// SENSITIVE? +					if (first_token(go.data[j], c1, c2) == "onchange") +						script = trim(go.data[j].substr(c2 + 1)); +				if (script != "") +					run_script(script); +			} +		} +	} +} + + + +Common::WriteStream &operator<<(Common::WriteStream &o, const match_binding &mb) { +	o << "MB['" << mb.var_name << "' == '" << mb.var_text << "' @ " +	  << mb.start << " to " << mb.end << "]"; +	return o; +} + +String match_binding::tostring() { +	ostringstream oss; +	oss << *this; +	return oss.str(); +} + +Common::WriteStream &operator<<(Common::WriteStream &o, const Set<String> &s) { +	o << "{ "; +	for (Set<String>::const_iterator i = s.begin(); i != s.end(); i ++) { +		if (i != s.begin()) +			o << ", "; +		o << (*i); +	} +	o << " }"; +	return o; +} + +bool geas_implementation::has_obj_action(String obj, String prop) const { +	String tmp; +	return get_obj_action(obj, prop, tmp); +} + + +bool geas_implementation::get_obj_action(String objname, String actname, +        String &rv) const { +	//String backup_object = this_object; +	//this_object = objname; + +	cerr << "get_obj_action (" << objname << ", " << actname << ")\n"; +	String tok; +	uint c1, c2; +	for (uint i = state.props.size() - 1; i + 1 > 0; i --) +		if (state.props[i].name == objname) { +			String line = state.props[i].data; +			// SENSITIVE? +			if (first_token(line, c1, c2) != "action") +				continue; +			tok = next_token(line, c1, c2); +			if (!is_param(tok) || ci_equal(param_contents(tok), actname)) +				continue; +			rv = trim(line.substr(c2)); +			cerr << "  g_o_a: returning true, \"" << rv << "\"."; +			return true; +		} +	return gf.get_obj_action(objname, actname, rv); +	//bool bool_rv = gf.get_obj_action (objname, actname, rv); +	//this_object = backup_object; +	//return bool_rv; +} + +bool geas_implementation::has_obj_property(String obj, String prop) const { +	String tmp; +	return get_obj_property(obj, prop, tmp); +} + +bool geas_implementation::get_obj_property(String obj, String prop, +        String &string_rv) const { +	String is_prop = "properties " + prop; +	String not_prop = "properties not " + prop; +	for (uint i = state.props.size() - 1; i + 1 > 0; i --) +		if (ci_equal(state.props[i].name, obj)) { +			String dat = state.props[i].data; +			//cerr << "In looking for " << obj << ":" << prop << ", got line " +			//     << dat << endl; +			if (ci_equal(dat, not_prop)) { +				//cerr << "   not_prop, returning false\n"; +				string_rv = "!"; +				return false; +			} +			if (ci_equal(dat, is_prop)) { +				//cerr << "   is_prop, returning true\n"; +				string_rv = ""; +				return true; +			} +			int index = dat.find('='); +			if (index != -1 && ci_equal(dat.substr(0, index), is_prop)) { +				string_rv = dat.substr(index + 1); +				return true; +			} +		} +	return gf.get_obj_property(obj, prop, string_rv); +} + +void geas_implementation::set_obj_property(String obj, String prop) { +	state.props.push_back(PropertyRecord(obj, "properties " + prop)); +	if (ci_equal(prop, "hidden") || ci_equal(prop, "not hidden") || +	        ci_equal(prop, "invisible") || ci_equal(prop, "not invisible")) { +		gi->update_sidebars(); +		regen_var_objects(); +	} +} + +void geas_implementation::set_obj_action(String obj, String act) { +	state.props.push_back(PropertyRecord(obj, "action " + act)); +} + +void geas_implementation::move(String obj, String dest) { +	for (uint i = 0; i < state.objs.size(); i ++) +		if (ci_equal(state.objs[i].name, obj)) { +			state.objs[i].parent = dest; +			gi->update_sidebars(); +			regen_var_objects(); +			return; +		} +	gi->debug_print("Tried to move nonexistent object '" + obj + +	                "' to '" + dest + "'."); +} + +String geas_implementation::get_obj_parent(String obj) { +	//obj = lcase (obj); +	for (uint i = 0; i < state.objs.size(); i ++) +		if (ci_equal(state.objs[i].name, obj)) +			return state.objs[i].parent; +	gi->debug_print("Tried to find parent of nonexistent object " + obj); +	return ""; +} + +void geas_implementation::goto_room(String room) { +	state.location = room; +	regen_var_room(); +	regen_var_dirs(); +	regen_var_look(); +	regen_var_objects(); +	String scr; +	if (get_obj_action(room, "script", scr)) +		run_script_as(room, scr); +	//run_script (scr); +	look(); +} + +void geas_implementation::display_error(String errorname, String obj) { +	cerr << "display_error (" << errorname << ", " << obj << ")\n"; +	if (obj != "") { +		String tmp; +		if (!get_obj_property(obj, "gender", tmp)) +			tmp = "it"; +		set_svar("quest.error.gender", tmp); + +		if (!get_obj_property(obj, "article", tmp)) +			tmp = "it"; +		set_svar("quest.error.article", tmp); + +		cerr << "In erroring " << errorname << " / " << obj << ", qeg == " +		     << get_svar("quest.error.gender") << ", qea == " +		     << get_svar("quest.error.article") << endl; +		// TODO quest.error.charactername +	} + +	const GeasBlock *game = gf.find_by_name("game", "game"); +	assert(game != NULL); +	String tok; +	uint c1, c2; +	for (uint i = 0; i < game->data.size(); i ++) { +		String line = game->data[i]; +		tok = first_token(line, c1, c2); +		// SENSITIVE? +		if (tok == "error") { +			tok = next_token(line, c1, c2); +			if (is_param(tok)) { +				String text = param_contents(tok); +				int index = text.find(';'); +				String errortype = trim(text.substr(0, index)); +				// SENSITIVE? +				if (errortype == errorname) { +					print_eval_p(trim(text.substr(index + 1))); +					return; +				} +			} else +				gi->debug_print("Bad error line: " + line); +		} +	} +	//print_formatted ("Default error " + errorname); + +	// ARE THESE SENSITIVE? +	if (errorname == "badcommand") +		print_eval("I don't understand your command. Type HELP for a list of valid commands."); +	else if (errorname == "badgo") +		print_eval("I don't understand your use of 'GO' - you must either GO in some direction, or GO TO a place."); +	else if (errorname == "badgive") +		print_eval("You didn't say who you wanted to give that to."); +	else if (errorname == "badcharacter") +		print_eval("I can't see anybody of that name here."); +	else if (errorname == "noitem") +		print_eval("You don't have that."); +	else if (errorname == "itemunwanted") +		print_eval_p("#quest.error.gender# doesn't want #quest.error.article#."); +	else if (errorname == "badlook") +		print_eval("You didn't say what you wanted to look at."); +	else if (errorname == "badthing") +		print_eval("I can't see that here."); +	else if (errorname == "defaultlook") +		print_eval("Nothing out of the ordinary."); +	else if (errorname == "defaultspeak") +		print_eval_p("#quest.error.gender# says nothing."); +	else if (errorname == "baditem") +		print_eval("I can't see that anywhere."); +	else if (errorname == "defaulttake") +		print_eval("You pick #quest.error.article# up."); +	else if (errorname == "baduse") +		print_eval("You didn't say what you wanted to use that on."); +	else if (errorname == "defaultuse") +		print_eval("You can't use that here."); +	else if (errorname == "defaultout") +		print_eval("There's nowhere you can go out to around here."); +	else if (errorname == "badplace") +		print_eval("You can't go there."); +	else if (errorname == "defaultexamine") +		print_eval("Nothing out of the ordinary."); +	else if (errorname == "badtake") +		print_eval("You can't take #quest.error.article#."); +	else if (errorname == "cantdrop") +		print_eval("You can't drop that here."); +	else if (errorname == "defaultdrop") +		print_eval("You drop #quest.error.article#."); +	else if (errorname == "baddrop") +		print_eval("You are not carrying such a thing."); +	else if (errorname == "badpronoun") +		print_eval("I don't know what '#quest.error.pronoun#' you are referring to."); +	else if (errorname == "badexamine") +		print_eval("You didn't say what you wanted to examine."); +	else +		gi->debug_print("Bad error name " + errorname); +} + +String geas_implementation::displayed_name(String obj) const { +	String rv = obj, tmp; + +	if (get_obj_property(obj, "alias", tmp)) +		rv = tmp; +	else { +		for (uint i = 0; i < gf.blocks.size(); i ++) +			if (ci_equal(gf.blocks[i].name, obj)) { +				rv = gf.blocks[i].name; +				break; +			} +	} +	return rv; +} + +/* For each destination, give: + * - printed name + * - accepted name, with prefix + * - accepted name, without prefix + * - destination, internal format + * - script (optional) + */ +Common::Array<Common::Array<String> > geas_implementation::get_places(String room) { +	Common::Array<Common::Array<String> > rv; + +	const GeasBlock *gb = gf.find_by_name("room", room); +	if (gb == NULL) +		return rv; + +	String line, tok; +	uint c1, c2; +	for (uint i = 0; i < gb->data.size(); i ++) { +		line = gb->data[i]; +		tok = first_token(line, c1, c2); +		if (tok == "place") { +			tok = next_token(line, c1, c2); +			if (!is_param(tok)) { +				gi->debug_print("Expected parameter after 'place' in " + line); +				continue; +			} +			String dest_param = eval_param(tok); +			if (dest_param == "") { +				gi->debug_print("Parameter empty in " + line); +				continue; +			} + +			int j = dest_param.find(';'); +			String dest, prefix = ""; +			if (j == -1) +				dest = trim(dest_param); +			else { +				dest = trim(dest_param.substr(j + 1)); +				prefix = trim(dest_param.substr(0, j)); +			} +			String displayed = displayed_name(dest); +			String printed_dest = (prefix != "" ? prefix + " " : "") + +			                      "|b" + displayed + "|xb"; + +			Common::Array<String> tmp; +			tmp.push_back(printed_dest); +			tmp.push_back(prefix + " " + displayed); +			tmp.push_back(displayed); +			tmp.push_back(dest); +			String rest = trim(line.substr(c2)); +			if (rest != "") +				tmp.push_back(rest); +			rv.push_back(tmp); +		} +	} + +	for (uint i = 0; i < state.exits.size(); i ++) { +		if (state.exits[i].src != room) +			continue; +		line = state.exits[i].dest; +		tok = first_token(line, c1, c2); +		if (tok == "exit") { +			tok = next_token(line, c1, c2); +			if (!is_param(tok)) +				continue; +			tok = next_token(line, c1, c2); +			assert(is_param(tok)); +			tok = param_contents(tok); +			Common::Array<String> args = split_param(tok); +			if (args.size() != 2) { +				gi->debug_print("Expected two arguments in " + tok); +				continue; +			} +			assert(args[0] == room); +			Common::Array<String> tmp; +			String displayed = displayed_name(args[1]); +			tmp.push_back("|b" + displayed + "|xb"); +			tmp.push_back(displayed); +			tmp.push_back(displayed); +			tmp.push_back(args[1]); +			rv.push_back(tmp); +		} else if (tok == "destroy") { +			tok = next_token(line, c1, c2); +			assert(tok == "exit"); +			tok = next_token(line, c1, c2); + +			for (v2string::iterator j = rv.begin(); j != rv.end(); j ++) +				if ((*j)[3] == tok) { +					rv.erase(j); +					break; +				} +		} + + +	} + +	cerr << "get_places (" << room << ") -> " << rv << "\n"; +	return rv; +} + +String geas_implementation::exit_dest(String room, String dir, bool *is_script) const { +	uint c1, c2; +	String tok; +	if (is_script != NULL) +		*is_script = false; +	for (uint i = state.exits.size() - 1; i + 1 > 0; i --) +		if (state.exits[i].src == room) { +			String line = state.exits[i].dest; +			cerr << "Processing exit line '" << state.exits[i].dest << "'\n"; +			tok = first_token(line, c1, c2); +			cerr << "   first tok is " << tok << " (vs. exit)\n"; +			// SENSITIVE? +			if (tok != "exit") +				continue; +			tok = next_token(line, c1, c2); +			cerr << "   second tok is " << tok << " (vs. " << dir << ")\n"; +			if (tok != dir) +				continue; +			tok = next_token(line, c1, c2); +			cerr << "   third tok is " << tok << " (expecting parameter)\n"; +			assert(is_param(tok)); +			Common::Array<String> p = split_param(param_contents(tok)); +			assert(p.size() == 2); +			assert(ci_equal(p[0], room)); +			return p[1]; +		} +	/* +	if (gf.get_obj_action (room, dir, tok)) +	  { +	    if (is_script != NULL) +	*is_script = true; +	    return tok; +	  } +	if (gf.get_obj_property (room, dir, tok)) +	  return tok; +	else +	  return ""; +	*/ + +	const GeasBlock *gb = gf.find_by_name("room", room); +	if (gb == NULL) { +		gi->debug_print(String("Trying to find exit <") + dir + +		                "> of nonexistent room <" + room + ">."); +		return ""; +	} +	// TODO: what's the priority on this? +	for (uint i = 0; i < gb->data.size(); i ++) { +		String line = gb->data[i]; +		tok = first_token(line, c1, c2); +		if (tok == dir) { +			uint line_start = c2; +			tok = next_token(line, c1, c2); +			if (is_param(tok)) +				return param_contents(tok); +			if (tok != "") { +				if (is_script != NULL) +					*is_script = true; +				return trim(line.substr(line_start + 1)); +			} +			return ""; +		} +	} +	return ""; +} + +void geas_implementation::look() { +	String tmp; +	if (get_obj_action(state.location, "description", tmp)) +		run_script_as(state.location, tmp); +	//run_script(tmp); +	else if (get_obj_property(state.location, "description", tmp)) +		print_formatted(tmp); +	else if (get_obj_action("game", "description", tmp)) +		run_script_as("game", tmp); +	//run_script (tmp); +	else if (get_obj_property("game", "description", tmp)) +		print_formatted(tmp); +	else { +		String in_desc; +		if (get_obj_property(state.location, "indescription", tmp)) +			in_desc = tmp; +		else +			in_desc = "You are in"; +		print_formatted(in_desc + " " + get_svar("quest.formatroom")); + +		if ((tmp = get_svar("quest.formatobjects")) != "") +			//print_formatted ("There is " + tmp + " here."); +			print_eval("There is #quest.formatobjects# here."); +		if ((tmp = get_svar("quest.doorways.out")) != "") +			print_formatted("You can go out to " + tmp + "."); +		if ((tmp = get_svar("quest.doorways.dirs")) != "") +			//print_formatted ("You can go " + tmp + "."); +			print_eval("You can go #quest.doorways.dirs#."); +		if ((tmp = get_svar("quest.doorways.places")) != "") +			print_formatted("You can go to " + tmp + "."); +		if ((tmp = get_svar("quest.lookdesc")) != "") +			print_formatted(tmp); +	} +} + +void geas_implementation::set_game(const String &fname) { +	cerr << "set_game (...)\n"; + +	gf = read_geas_file(gi, fname); +	if (gf.blocks.size() == 0) { +		is_running_ = false; +		return; +	} +	//print_formatted ("Ready...|n|cbblack|crred|clblue|cggreen|cyyellow|n|uunderlined: |cbblack|crred|clblue|cggreen|cyyellow|xu|n"); +	//cerr << "Read game " << gf << endl; +	uint tok_start, tok_end; +	outputting = true; + +	state = GeasState(*gi, gf); + +	state.running = true; + +	for (uint gline = 0; gline < gf.block("game", 0).data.size(); gline ++) { +		String s = gf.block("game", 0).data[gline]; +		String tok = first_token(s, tok_start, tok_end); +		// SENSITIVE? +		if (tok == "asl-version") { +			String ver = next_token(s, tok_start, tok_end); +			if (!is_param(ver)) { +				gi->debug_print("Version " + s + " has invalid version " + +					            ver); +				continue; +			} +			int vernum = parse_int(param_contents(ver)); +			if (vernum < 311 || vernum > 353) +				gi->debug_print("Warning: Geas only supports ASL " +					            " versions 3.11 to 3.53"); +		} +		// SENSITIVE? +		else if (tok == "background") { +			tok = next_token(s, tok_start, tok_end); +			if (!is_param(tok)) +				gi->debug_print(nonparam("background color", s)); +			else +				gi->set_background(param_contents(tok)); +		} +		// SENSITIVE? +		else if (tok == "default") { +			tok = next_token(s, tok_start, tok_end); +			// SENSITIVE? +			if (tok == "fontname") { +				tok = next_token(s, tok_start, tok_end); +				if (!is_param(tok)) +					gi->debug_print(nonparam("font name", s)); +				else +					gi->set_default_font(param_contents(tok)); +			} +			// SENSITIVE? +			else if (tok == "fontsize") { +				tok = next_token(s, tok_start, tok_end); +				if (!is_param(tok)) +					gi->debug_print(nonparam("font size", s)); +				else +					gi->set_default_font_size(param_contents(tok)); +			} +		} +		// SENSITIVE? +		else if (tok == "foreground") { +			tok = next_token(s, tok_start, tok_end); +			if (!is_param(tok)) +				gi->debug_print(nonparam("foreground color", s)); +			else +				gi->set_foreground(param_contents(tok)); +		} +		// SENSITIVE? +		else if (tok == "gametype") { +			tok = next_token(s, tok_start, tok_end); +			// SENSITIVE? +			if (tok == "singleplayer") +				continue; +			// SENSITIVE? +			if (tok == "multiplayer") +				throw String("Error: geas is single player only."); +			gi->debug_print("Unexpected game type " + s); +		} +		// SENSITIVE? +		else if (tok == "nodebug") { +		} +		// SENSITIVE? +		else if (tok == "start") { +			tok = next_token(s, tok_start, tok_end); +			if (!is_param(tok)) +				gi->debug_print(nonparam("start room", s)); +			else { +				state.location = param_contents(tok); +			} +		} +	} + +	const GeasBlock &game = gf.block("game", 0); +	cerr << gf << endl; +	//print_formatted ("Done loading " + game.name); +	uint c1, c2; +	String tok; + +	/* TODO do I run the startscript or print the opening text first? */ +	run_script("displaytext <intro>"); + +	for (uint i = 0; i < game.data.size(); i ++) +		// SENSITIVE? +		if (first_token(game.data[i], c1, c2) == "startscript") { +			run_script_as("game", game.data[i].substr(c2 + 1)); +			//run_script (game.data[i].substr (c2 + 1)); +			break; +		} + +	regen_var_room(); +	regen_var_objects(); +	regen_var_dirs(); +	regen_var_look(); +	look(); + +	cerr << "s_g: done with set_game (...)\n\n"; +} + +void geas_implementation::regen_var_objects() { +	String tmp; +	Common::Array <String> objs; +	for (uint i = 0; i < state.objs.size(); i ++) { +		//cerr << "r_v_o: Checking '" << state.objs[i].name << "' (" << state.objs[i].parent << "): " << ((state.objs[i].parent == state.location) ? "YES" : "NO") << endl; +		if (ci_equal(state.objs[i].parent, state.location) && +		        !get_obj_property(state.objs[i].name, "hidden", tmp) && +		        !get_obj_property(state.objs[i].name, "invisible", tmp)) +			//!state.objs[i].hidden && +			//!state.objs[i].invisible) +			objs.push_back(state.objs[i].name); +	} +	String qobjs = "", qfobjs = ""; +	String objname, prefix, main, suffix, propval, print1, print2; +	for (uint i = 0; i < objs.size(); i ++) { +		objname = objs[i]; +		if (!get_obj_property(objname, "alias", main)) +			main = objname; +		print1 = main; +		print2 = "|b" + main + "|xb"; +		if (get_obj_property(objname, "prefix", prefix)) { +			print1 = prefix + " " + print1; +			print2 = prefix + " " + print2; +		} +		if (get_obj_property(objname, "suffix", suffix)) { +			print1 = print1 + " " + suffix; +			print2 = print2 + " " + suffix; +		} +		qobjs = qobjs + print1; +		qfobjs = qfobjs + print2; +		if (i + 2 < objs.size()) { +			qobjs = qobjs + ", "; +			qfobjs = qfobjs + ", "; +		} else if (i + 2 == objs.size()) { +			qobjs = qobjs + " and "; +			qfobjs = qfobjs + " and "; +		} +	} +	set_svar("quest.objects", qobjs); +	set_svar("quest.formatobjects", qfobjs); +} + +void geas_implementation::regen_var_room() { +	set_svar("quest.currentroom", state.location); + +	String tmp, formatroom; +	if (!get_obj_property(state.location, "alias", formatroom)) +		formatroom = state.location; +	formatroom = "|cr" + formatroom + "|cb"; +	if (get_obj_property(state.location, "prefix", tmp)) +		formatroom = tmp + " " + formatroom; +	if (get_obj_property(state.location, "suffix", tmp)) +		formatroom = formatroom + " " + tmp; +	//set_svar ("quest.formatroom", displayed_name (state.location)); +	set_svar("quest.formatroom", formatroom); + +	// regen_var_objects(); +	/* +	String out_dest = exit_dest (state.location, "out"); +	if (out_dest == "") +	  { +	    set_svar ("quest.doorways.out", ""); +	    set_svar ("quest.doorways.out.display", ""); +	  } +	else +	  { +	    cerr << "Updating quest.doorways.out; out_dest == {" << out_dest << "}"; +	    uint i = out_dest.find (';'); +	    cerr << ", i == " << i; +	    String prefix = ""; +	    if (i != -1) +	{ +	  prefix = trim (out_dest.substr (0, i-1)); +	  out_dest = trim (out_dest.substr (i + 1)); +	  cerr << "; prefix == {" << prefix << "}, out_dest == {" << out_dest << "}"; +	} +	    cerr << "  quest.doorways.out == {" << out_dest << "}"; +	    set_svar ("quest.doorways.out", out_dest); +	    cerr << endl; + +	    String tmp = displayed_name (out_dest); + +	    cerr << ", tmp == {" << tmp << "}"; + +	    if (tmp != "") +	tmp = "|b" + tmp + "|xb"; +	    else if (prefix != "") +	tmp = prefix + " |b" + out_dest + "|xb"; +	    else +	tmp = "|b" + out_dest + "|xb"; + +	    cerr << ",    final value {" << tmp << "}" << endl; + +	    set_svar ("quest.doorways.out.display", tmp); +	  } +	*/ +} + + +void geas_implementation::regen_var_look() { +	String look_tag; +	if (!get_obj_property(state.location, "look", look_tag)) +		look_tag = ""; +	set_svar("quest.lookdesc", look_tag); +} + + +void geas_implementation::regen_var_dirs() { +	Common::Array <String> dirs; +	// the -1 is so that it skips 'out' +	for (uint i = 0; i < ARRAYSIZE(dir_names) - 1; i ++) +		if (exit_dest(state.location, dir_names[i]) != "") +			dirs.push_back(dir_names[i]); +	String exits = ""; +	if (dirs.size() == 1) +		exits = "|b" + dirs[0] + "|xb"; +	else if (dirs.size() > 1) { +		for (uint i = 0; i < dirs.size(); i ++) { +			exits = exits + "|b" + dirs[i] + "|xb"; +			if (i < dirs.size() - 2) +				exits = exits + ", "; +			else if (i == dirs.size() - 2) +				exits = exits + " or "; +		} +	} +	set_svar("quest.doorways.dirs", exits); + +	/* +	String tmp; +	if ((tmp = exit_dest (state.location, "out")) != "") +	  set_svar ("quest.doorways.out", displayed_name (tmp)); +	else +	  set_svar ("quest.doorways.out", ""); +	*/ + +	String out_dest = exit_dest(state.location, "out"); +	if (out_dest == "") { +		set_svar("quest.doorways.out", ""); +		set_svar("quest.doorways.out.display", ""); +	} else { +		cerr << "Updating quest.doorways.out; out_dest == {" << out_dest << "}"; +		int i = out_dest.find(';'); +		cerr << ", i == " << i; +		String prefix = ""; +		if (i != -1) { +			prefix = trim(out_dest.substr(0, i - 1)); +			out_dest = trim(out_dest.substr(i + 1)); +			cerr << "; prefix == {" << prefix << "}, out_dest == {" << out_dest << "}"; +		} +		cerr << "  quest.doorways.out == {" << out_dest << "}"; +		set_svar("quest.doorways.out", out_dest); +		cerr << endl; + +		String tmp = displayed_name(out_dest); + +		cerr << ", tmp == {" << tmp << "}"; + +		if (tmp != "") +			tmp = "|b" + tmp + "|xb"; +		else if (prefix != "") +			tmp = prefix + " |b" + out_dest + "|xb"; +		else +			tmp = "|b" + out_dest + "|xb"; + +		cerr << ",    final value {" << tmp << "}" << endl; + +		set_svar("quest.doorways.out.display", tmp); +	} + +	/* TODO handle this */ +	//set_svar ("quest.doorways.places", ""); +	current_places = get_places(state.location); +	String printed_places = ""; +	for (uint i = 0; i < current_places.size(); i ++) { +		if (i == 0) +			printed_places = current_places[i][0]; +		else if (i < current_places.size() - 1) +			printed_places = printed_places + ", " + current_places[i][0]; +		else if (current_places.size() == 2) +			printed_places = printed_places + " or " + current_places[i][0]; +		else +			printed_places = printed_places + ", or " + current_places[i][0]; +	} +	set_svar("quest.doorways.places", printed_places); +} + + + +// TODO:  SENSITIVE??? +String geas_implementation::substitute_synonyms(String s) const { +	String orig = s; +	cerr << "substitute_synonyms (" << s << ")\n"; +	const GeasBlock *gb = gf.find_by_name("synonyms", ""); +	if (gb != NULL) { +		/* TODO: exactly in what order does it try synonyms? +		 * Does it have to be flanked by whitespace? +		 */ +		for (uint i = 0; i < gb->data.size(); i ++) { +			String line = gb->data[i]; +			int index = line.find('='); +			if (index == -1) +				continue; +			Common::Array<String> words = split_param(line.substr(0, index)); +			String rhs = trim(line.substr(index + 1)); +			if (rhs == "") +				continue; +			for (uint j = 0; j < words.size(); j ++) { +				String lhs = words[j]; +				if (lhs == "") +					continue; +				int k = 0; +				while ((k = s.find(lhs, k)) != -1) { +					uint end_index = k + lhs.length(); +					if ((k == 0 || s[k - 1] == ' ') && +					        (end_index == s.length() || s[end_index] == ' ')) { +						s = s.substr(0, k) + rhs + s.substr(k + lhs.length()); +						k = k + rhs.length(); +					} else +						k ++; +				} +			} +		} +	} +	cerr << "substitute_synonyms (" << orig << ") -> '" << s << "'\n"; +	return s; +} + +bool geas_implementation::is_running() const { +	return is_running_; +} + +String geas_implementation::get_banner() { +	String banner; +	const GeasBlock *gb = gf.find_by_name("game", "game"); +	if (gb) { +		String line = gb->data[0]; +		uint c1, c2; +		String tok = first_token(line, c1, c2); +		tok = next_token(line, c1, c2); +		tok = next_token(line, c1, c2); +		if (is_param(tok)) { +			banner = eval_param(tok); + +			for (uint i = 0; i < gb->data.size(); i ++) { +				line = gb->data[i]; +				if (first_token(line, c1, c2) == "game" && +				        next_token(line, c1, c2) == "version" && +				        is_param(tok = next_token(line, c1, c2))) { +					banner += ", v"; +					banner += eval_param(tok); +				} +			} + +			for (uint i = 0; i < gb->data.size(); i ++) { +				line = gb->data[i]; +				if (first_token(line, c1, c2) == "game" && +				        next_token(line, c1, c2) == "author" && +				        is_param(tok = next_token(line, c1, c2))) { +					banner += " | "; +					banner += eval_param(tok); +				} +			} +		} +	} +	return banner; +} + +void geas_implementation::run_command(String s) { +	/* if s == "restore" or "restart" or "quit" or "undo" */ + +	if (!is_running_) +		return; + +	print_newline(); +	print_normal("> " + s); +	print_newline(); + +	if (s == "dump status") { +		//cerr << state << endl; +		ostringstream oss; +		oss << state; +		print_normal(oss.str()); +		return; +	} + +	if (s == "undo") { +		if (undo_buffer.size() < 2) { +			print_formatted("(No more undo information available!)"); +			return; +		} +		undo_buffer.pop(); +		state = undo_buffer.peek(); +		print_formatted("Undone."); +		return; +	} + +	if (!state.running) +		return; +	// TODO: does this get the original command, or the lowercased version? +	set_svar("quest.originalcommand", s); +	s = substitute_synonyms(lcase(s)); +	set_svar("quest.command", s); + +	bool overridden = false; +	dont_process = false; + +	const GeasBlock *gb = gf.find_by_name("room", state.location); +	if (gb != NULL) { +		String line, tok; +		uint c1, c2; +		for (uint i = 0; i < gb->data.size(); i ++) { +			line = gb->data[i]; +			tok = first_token(line, c1, c2); +			// SENSITIVE? +			if (tok == "beforeturn") { +				uint scr_starts = c2; +				tok = next_token(line, c1, c2); +				// SENSITIVE? +				if (tok == "override") { +					overridden = true; +					scr_starts = c2; +				} +				String scr = line.substr(scr_starts); +				run_script(state.location, scr); +				//run_script (scr); +			} +		} +	} else +		gi->debug_print("Unable to find block " + state.location + ".\n"); + +	if (!overridden) { +		gb = gf.find_by_name("game", "game"); +		if (gb != NULL) { +			String line, tok; +			uint c1, c2; +			for (uint i = 0; i < gb->data.size(); i ++) { +				line = gb->data[i]; +				tok = first_token(line, c1, c2); +				// SENSITIVE? +				if (tok == "beforeturn") { +					uint scr_starts = c2; +					tok = next_token(line, c1, c2); +					// SENSITIVE? +					if (tok == "override") { +						overridden = true; +						scr_starts = c2; +					} +					String scr = line.substr(scr_starts); +					run_script_as("game", scr); +					//run_script (scr); +				} +			} +		} else +			gi->debug_print("Unable to find block game.\n"); +	} + +	if (!dont_process) { +		if (try_match(s, false, false)) { +			/* TODO TODO */ +			// run after turn events ??? +		} else +			display_error("badcommand"); +	} + +	overridden = false; + +	gb = gf.find_by_name("room", state.location); +	if (gb != NULL) { +		String line, tok; +		uint c1, c2; +		for (uint i = 0; i < gb->data.size(); i ++) { +			line = gb->data[i]; +			tok = first_token(line, c1, c2); +			// SENSITIVE? +			if (tok == "afterturn") { +				uint scr_starts = c2; +				tok = next_token(line, c1, c2); +				// SENSITIVE? +				if (tok == "override") { +					overridden = true; +					scr_starts = c2; +				} +				String scr = line.substr(scr_starts); +				run_script_as(state.location, scr); +				//run_script (scr); +			} +		} +	} +	if (!overridden) { +		gb = gf.find_by_name("game", "game"); +		if (gb != NULL) { +			String line, tok; +			uint c1, c2; +			for (uint i = 0; i < gb->data.size(); i ++) { +				line = gb->data[i]; +				tok = first_token(line, c1, c2); +				// SENSITIVE? +				if (tok == "afterturn") { +					uint scr_starts = c2; +					tok = next_token(line, c1, c2); +					// SENSITIVE? +					if (tok == "override") { +						overridden = true; +						scr_starts = c2; +					} +					String scr = line.substr(scr_starts); +					run_script_as("game", scr); +					//run_script (scr); +				} +			} +		} +	} + +	if (state.running) +		undo_buffer.push(state); +} + +Common::WriteStream &operator<< (Common::WriteStream &o, const match_rv &rv) { +	//o << "match_rv {" << (rv.success ? "TRUE" : "FALSE") << ": " << rv.bindings << "}"; +	o << "match_rv {" << (rv.success ? "TRUE" : "FALSE") << ": ["; +	//o << rv.bindings.size(); +	o << rv.bindings; +	//for (uint i = 0; i < rv.bindings.size(); i ++) +	//  o << rv.bindings[i] << ", "; +	o << "]}"; +	return o; +} + +match_rv geas_implementation::match_command(String input, String action) const { +	//cerr << "match_command (\"" << input << "\", \"" << action << "\")" << endl; +	match_rv rv = match_command(input, 0, action, 0, match_rv()); +	cerr << "match_command (\"" << input << "\", \"" << action << "\") -> " << rv << endl; +	return rv; +	//return match_command (input, 0, action, 0, match_rv ()); +} + +match_rv geas_implementation::match_command(String input, uint ichar, String action, uint achar, match_rv rv) const { +	//cerr << "match_command (\"" << input << "\", " << ichar << ", \"" << action << "\", " << achar << ", " << rv << ")" << endl; +	for (;;) { +		if (achar == action.length()) { +			//cerr << "End of action, returning " << (ichar == input.length()) << "\n"; +			return match_rv(ichar == input.length(), rv); +		} +		if (action[achar] == '#') { + +			achar ++; +			String varname; +			while (achar != action.length() && action[achar] != '#') { +				varname += action[achar]; +				achar ++; +			} +			if (achar == action.length()) +				throw String("Unpaired hashes in command String " + action); +			//rv.bindings.push_back (varname); +			int index = rv.bindings.size(); +			rv.bindings.push_back(match_binding(varname, ichar)); +			achar ++; +			varname = ""; +			//rv.bindings.push_back (varname); +			rv.bindings[index].set(varname, ichar); +			while (ichar < input.length()) { +				match_rv tmp = match_command(input, ichar, action, achar, rv); +				if (tmp.success) +					return tmp; +				varname += input[ichar]; +				ichar ++; +				//rv.bindings[index] = varname; +				rv.bindings[index].set(varname, ichar); +			} +			return match_rv(achar == action.length(), rv); +		} +		// SENSITIVE? +		if (ichar == input.length() || !c_equal_i(input[ichar], action[achar])) +			return match_rv(); +		//cerr << "Matched " << input[ichar] << " to " << action[achar] << endl; +		++ achar; +		++ ichar; +	} +} + +bool match_object_alts(String text, const Common::Array<String> &alts, bool is_internal) { +	for (uint i = 0; i < alts.size(); i ++) { +		cerr << "m_o_a: Checking '" << text << "' v. alt '" << alts[i] << "'.\n"; +		if (starts_with(text, alts[i])) { +			uint len = alts[i].length(); +			if (text.length() == len) +				return true; +			if (text.length() > len  &&  text[len] == ' '  && +			        match_object_alts(text.substr(len + 1), alts, is_internal)) +				return true; +		} +	} +	return false; +} + + +bool geas_implementation::match_object(String text, String name, bool is_internal) const { +	cerr << "* * * match_object (" << text << ", " << name << ", " +	     << (is_internal ? "true" : "false") << ")\n"; + +	String alias, alt_list, prefix, suffix; + +	if (is_internal && ci_equal(text, name)) return true; + +	if (get_obj_property(name, "prefix", prefix) && +	        starts_with(text, prefix + " ") && +	        match_object(text.substr(prefix.length() + 1), name, false)) +		return true; + +	if (get_obj_property(name, "suffix", suffix) && +	        ends_with(text, " " + suffix) && +	        match_object(text.substr(0, text.length() - suffix.length() - 1), name, false)) +		return true; + +	if (!get_obj_property(name, "alias", alias)) +		alias = name; +	if (ci_equal(text, alias)) +		return true; + +	const GeasBlock *gb = gf.find_by_name("object", name); +	if (gb != NULL) { +		String tok, line; +		uint c1, c2; +		for (uint ln = 0; ln < gb->data.size(); ln ++) { +			line = gb->data[ln]; +			tok = first_token(line, c1, c2); +			// SENSITIVE? +			if (tok == "alt") { +				tok = next_token(line, c1, c2); +				if (!is_param(tok)) +					gi->debug_print("Expected param after alt in " + line); +				else { +					Common::Array<String> alts = split_param(param_contents(tok)); +					cerr << "  m_o: alt == " << alts << "\n"; +					return match_object_alts(text, alts, is_internal); +				} +			} +		} +	} + +	return false; +} + + +bool geas_implementation::dereference_vars(Common::Array<match_binding> &bindings, bool is_internal) const { +	/* TODO */ +	Common::Array<String> where; +	where.push_back("inventory"); +	where.push_back(state.location); +	return dereference_vars(bindings, where, is_internal); +} + +bool geas_implementation::dereference_vars(Common::Array<match_binding> &bindings, const Common::Array<String> &where, bool is_internal) const { +	bool rv = true; +	for (uint i = 0; i < bindings.size(); i ++) +		if (bindings[i].var_name[0] == '@') { +			String obj_name = get_obj_name(bindings[i].var_text, where, is_internal); +			if (obj_name == "!") { +				print_formatted("You don't see any " + bindings[i].var_text + "."); +				rv = false; +			} else { +				bindings[i].var_text = obj_name; +				bindings[i].var_name = bindings[i].var_name.substr(1); +			} +		} +	return rv; +} + +String geas_implementation::get_obj_name(String name, const Common::Array<String> &where, bool is_internal) const { +	Common::Array<String> objs, printed_objs; +	for (uint objnum = 0; objnum < state.objs.size(); objnum ++) { +		bool is_used = false; +		for (uint j = 0; j < where.size(); j ++) { +			cerr << "Object #" << objnum << ": " << state.objs[objnum].name +			     << "@" << state.objs[objnum].parent << " vs. " +			     << where[j] << endl; +			// SENSITIVE? +			if (where[j] == "game" || state.objs[objnum].parent == where[j]) +				is_used = true; +		} +		if (is_used && !has_obj_property(state.objs[objnum].name, "hidden") && +		        match_object(name, state.objs[objnum].name, is_internal)) { +			String printed_name, tmp, oname = state.objs[objnum].name; +			objs.push_back(oname); +			if (!get_obj_property(oname, "alias", printed_name)) +				printed_name = oname; +			if (get_obj_property(oname, "detail", tmp)) +				printed_name = tmp; +			printed_objs.push_back(printed_name); +		} +	} +	cerr << "objs == " << objs << ", printed_objs == " << printed_objs << "\n"; +	if (objs.size() > 1) { +		//bindings[i].var_name = bindings[i].var_name.substr(1); +		uint num = 0; +		//if (objs.size() > 1) +		num = gi->make_choice("Which " + name + " do you mean?", printed_objs); + +		//bindings[i].var_text = objs[num]; +		return objs[num]; +	} +	if (objs.size() == 1) +		return objs[0]; +	return "!"; +} + + +void geas_implementation::set_vars(const Common::Array<match_binding> &v) { +	for (uint i = 0; i < v.size(); i ++) +		set_svar(v[i].var_name, v[i].var_text); +} + + +bool geas_implementation::run_commands(String cmd, const GeasBlock *room, bool is_internal) { +	uint c1, c2; +	String line, tok; +	match_rv match; + +	if (room != NULL) { +		for (uint i = 0; i < room->data.size(); i++) { +			line = room->data[i]; +			tok = first_token(line, c1, c2); +			// SENSITIVE? +			if (tok == "command") { +				tok = next_token(line, c1, c2); +				if (is_param(tok)) { +					Common::Array<String> tmp = split_param(param_contents(tok)); + +					for (uint j = 0; j < tmp.size(); j++) +						if (match = match_command(cmd, tmp[j])) { +							if (!dereference_vars(match.bindings, is_internal)) +								return false; +							set_vars(match.bindings); +							run_script_as(state.location, line.substr(c2 + 1)); +							//run_script (line.substr (c2+1)); +							return true; +						} +					/* +					  if (match = match_command (cmd, param_contents(tok))) +					  { +					  if (!dereference_vars (match.bindings)) +					  return false; +					  set_vars (match.bindings); +					  run_script (line.substr (c2+1)); +					  return true; +					  } +					*/ +				} else { +					gi->debug_print("Bad command line: " + line); +				} +			} +		} +	} else +		gi->debug_print("room is null\n"); + +	return false; +} + +bool geas_implementation::try_match(String cmd, bool is_internal, bool is_normal) { +	//print_formatted ("geas_impl registers " + cmd); + +	String line, tok; +	match_rv match; + +	if (!is_normal) { +		if (run_commands(cmd, gf.find_by_name("room", state.location)) || +		        run_commands(cmd, gf.find_by_name("game", "game"))) +			return true; +	} + +	if ((match = match_command(cmd, "look at #@object#")) || +	        (match = match_command(cmd, "look #@object#"))) { +		if (!dereference_vars(match.bindings, is_internal)) +			return true; + +		String object = match.bindings[0].var_text; + +		if (get_obj_action(object, "look", tok)) +			run_script_as(object, tok); +		//run_script (tok); +		else if (get_obj_property(object, "look", tok)) +			print_formatted(tok); +		else +			display_error("defaultlook", object); + +		return true; +	} + +	if ((match = match_command(cmd, "examine #@object#")) || +	        (match = match_command(cmd, "x #@object#"))) { +		if (!dereference_vars(match.bindings, is_internal)) +			return true; + +		String object = match.bindings[0].var_text; +		if (get_obj_action(object, "examine", tok)) +			run_script_as(object, tok); +		//run_script (tok); +		else if (get_obj_property(object, "examine", tok)) +			print_formatted(tok); +		else if (get_obj_action(object, "look", tok)) +			run_script_as(object, tok); +		//run_script (tok); +		else if (get_obj_property(object, "look", tok)) +			print_formatted(tok); +		else +			display_error("defaultexamine", object); +		return true; +	} + +	if (match = match_command(cmd, "look")) { +		look(); +		return true; +	} + +	if (match = match_command(cmd, "give #@first# to #@second#")) { +		if (!dereference_vars(match.bindings, is_internal)) +			return true; +		String script, first = match.bindings[0].var_text, second = match.bindings[1].var_text; +		if (! ci_equal(get_obj_parent(first), "inventory")) +			display_error("noitem", first); +		else if (get_obj_action(second, "give " + first, script)) +			run_script(second, script); +		//run_script (script); +		else if (get_obj_action(first, "give to " + second, script)) +			run_script_as(first, script); +		//run_script (script); +		else if (get_obj_action(second, "give anything", script)) { +			set_svar("quest.give.object.name", first); +			run_script_as(second, script); +			//run_script (script); +		} else if (get_obj_action(first, "give to anything", script)) { +			set_svar("quest.give.object.name", second); +			run_script_as(first, script); +			//run_script (script); +		} else { +			String tmp; +			if (!get_obj_property(second, "gender", tmp)) +				tmp = "it"; +			set_svar("quest.error.gender", tmp); +			if (!get_obj_property(first, "article", tmp)) +				tmp = "it"; +			set_svar("quest.error.article", tmp); +			display_error("itemunwanted"); +		} +		return true; +	} + +	if ((match = match_command(cmd, "use #@first# on #@second#")) || +	        (match = match_command(cmd, "use #@first# with #@second#"))) { +		if (!dereference_vars(match.bindings, is_internal)) +			return true; +		String script, first = match.bindings[0].var_text, second = match.bindings[1].var_text; +		if (! ci_equal(get_obj_parent(first), "inventory")) +			display_error("noitem", first); +		else if (get_obj_action(second, "use " + first, script)) { +			//set_svar ("quest.use.object", first); +			run_script_as(second, script); +			//run_script (script); +		} else if (get_obj_action(first, "use on " + second, script)) { +			//set_svar ("quest.use.object", second); +			run_script_as(first, script); +			//run_script (script); +		} else if (get_obj_action(second, "use anything", script)) { +			set_svar("quest.use.object", first); +			run_script(second, script); +			//run_script (script); +		} else if (get_obj_action(first, "use on anything", script)) { +			set_svar("quest.use.object", second); +			run_script_as(first, script); +			//run_script (script); +		} else +			display_error("defaultuse"); + +		return true; +	} + +	if (match = match_command(cmd, "use #@first#")) { +		if (!dereference_vars(match.bindings, is_internal)) +			return true; +		String tmp, obj = match.bindings[0].var_text; +		if (!ci_equal(get_obj_parent(obj), "inventory")) +			display_error("noitem", obj); +		else if (get_obj_action(obj, "use", tmp)) +			run_script_as(obj, tmp); +		//run_script (tmp); +		else if (get_obj_property(obj, "use", tmp)) +			print_formatted(tmp); +		else +			display_error("defaultuse", obj); +		return true; +	} + + +	if ((match = match_command(cmd, "take #@object#")) || +	        (match = match_command(cmd, "get #@object#"))) { +		if (!dereference_vars(match.bindings, is_internal)) +			return true; + +		String object = match.bindings[0].var_text; +		if (get_obj_action(object, "take", tok)) { +			cerr << "Running script '" << tok << "' for take " << object << endl; +			run_script_as(object, tok); +			//run_script (tok); +		} else if (get_obj_property(object, "take", tok)) { +			cerr << "Found property '" << tok << "' for take " << object << endl; +			if (tok != "") +				print_formatted(tok); +			else +				display_error("defaulttake", object); +			String tmp; +			move(object, "inventory"); +			if (get_obj_action(object, "gain", tmp)) +				run_script(object, tmp); +			//run_script (tmp); +			else if (get_obj_property(object, "gain", tmp)) +				print_formatted(tmp); +		} else { +			cerr << "No match found for take " << object << endl; +			// TODO set variable with object name +			display_error("badtake", object); +		} +		return true; +	} + + +	if (match = match_command(cmd, "drop #@object#")) { +		if (!dereference_vars(match.bindings, is_internal)) +			return true; +		String scr, obj = match.bindings[0].var_text; +		if (get_obj_action(obj, "drop", scr)) { +			run_script_as(obj, scr); +			//run_script (scr); +			return true; +		} + +		const GeasBlock *gb = gf.find_by_name("object", obj); +		if (gb != NULL) { +			uint c1, c2, script_begins; +			for (uint i = 0; i < gb->data.size(); i ++) { +				line = gb->data[i]; +				tok = first_token(line, c1, c2); +				// SENSITIVE? +				if (tok == "drop") { +					script_begins = c2; +					tok = next_token(line, c1, c2); +					// SENSITIVE? +					if (tok == "everywhere") { +						tok = next_token(line, c1, c2); +						move(obj, state.location); +						if (is_param(tok)) +							print_eval(tok); +						else +							gi->debug_print("Expected param after drop everywhere in " + line); +						return true; +					} +					// SENSITIVE? +					if (tok == "nowhere") { +						if (is_param(tok)) +							print_eval(tok); +						else +							gi->debug_print("Expected param after drop nowhere in " + line); +						return true; +					} +					run_script_as(obj, line.substr(script_begins)); +					//run_script (line.substr (script_begins)); +					return true; +				} +			} +		} +		move(obj, state.location); +		display_error("defaultdrop", obj); +		return true; +	} + +	if ((match = match_command(cmd, "speak to #@object#")) || +	        (match = match_command(cmd, "speak #@object#")) || +	        (match = match_command(cmd, "talk to #@object#")) || +	        (match = match_command(cmd, "talk #@object#"))) { +		//print_formatted ("Talk to <" + String (match.bindings[0]) + ">"); +		if (!dereference_vars(match.bindings, is_internal)) +			return true; +		String obj = match.bindings[0].var_text; +		String script; +		if (get_obj_action(obj, "speak", script)) +			run_script_as(obj, script); +		//run_script (script); +		else +			display_error("defaultspeak", obj); +		//print_formatted ("Talk to <" + String (match.bindings[0]) + ">"); +		return true; +	} + +	if (cmd == "exit" || cmd == "out" || cmd == "go out") { +		const GeasBlock *gb = gf.find_by_name("room", state.location); +		if (gb == NULL) { +			gi->debug_print("Bad room"); +			return true; +		} + +		line = ""; +		int c1 = -1, c2 = -1; +		uint uc1, uc2; +		// TODO: Use the first matching line or the last? +		for (uint i = 0; i < gb->data.size(); i ++) { +			if (first_token(gb->data[i], uc1, uc2) == "out") +				line = gb->data[i]; +			c1 = uc1; +			c2 = uc2; +		} + +		//gi->debug_print ("COMMAND " + cmd + ": line == " + line); + +		if (line == "") +			display_error("defaultout"); +		else { +			c1 = line.find('<'); +			if (c1 != -1) +				c2 = line.find('>', c1); + +			if (c1 == -1 || c2 == -1) +				gi->debug_print("Bad out line: " + line); +			else { +				String tmp = trim(line.substr(c2 + 1)); +				//gi->debug_print ("tmp1 == {" + tmp + "}"); +				if (tmp != "") +					run_script_as(state.location, tmp); +				//run_script (tmp); +				else { +					tmp = line.substr(c1, c2 - c1 + 1); +					//gi->debug_print ("tmp2 == {" + tmp + "}"); +					assert(is_param(tmp)); +					tmp = param_contents(tmp); +					c1 = tmp.find(';'); +					if (c1 == -1) +						goto_room(trim(tmp)); +					else +						goto_room(trim(tmp.substr(c1 + 1))); +				} +			} +		} +		return true; +	} + +	for (uint i = 0; i < ARRAYSIZE(dir_names); i ++) +		if (cmd == dir_names[i] || cmd == (String("go ") + dir_names[i]) || +		        cmd == short_dir_names[i] || cmd == (String("go ") + short_dir_names[i])) { +			bool is_script = false; +			//print_formatted ("Trying to go " + dir_names[i]); +			if ((tok = exit_dest(state.location, dir_names[i], &is_script)) == "") { +				// TODO Which display_error do I use? +				print_formatted("You can't go that way."); +				return true; +			} +			if (is_script) +				run_script_as(state.location, tok); +			//run_script (tok); +			else { +				int index = tok.find(';'); +				if (index == -1) +					goto_room(trim(tok)); +				else +					goto_room(trim(tok.substr(index + 1))); +			} +			return true; +		} + +	if ((match = match_command(cmd, "go to #@room#")) || +	        (match = match_command(cmd, "go #@room#"))) { +		assert(match.bindings.size() == 1); +		String destination = match.bindings[0].var_text; +		for (uint i = 0; i < current_places.size(); i ++) { +			if (ci_equal(destination, current_places[i][1]) || +			        ci_equal(destination, current_places[i][2])) { +				if (current_places[i].size() == 5) +					run_script_as(state.location, current_places[i][4]); +				//run_script (current_places[i][4]); +				else +					goto_room(current_places[i][3]); +				return true; +			} +		} +		display_error("badplace", destination); +		return true; +	} + +	if (ci_equal(cmd, "inventory") || ci_equal(cmd, "i")) { +		Common::Array<Common::Array<String> > inv = get_inventory(); +		if (inv.size() == 0) +			print_formatted("You are carrying nothing."); +		else +			print_formatted("You are carrying:"); +		for (uint i = 0; i < inv.size(); i ++) { +			print_normal(inv[i][0]); +			print_newline(); +		} +		return true; +	} + +	if (ci_equal(cmd, "help")) { +		print_formatted("|b|cl|s14Quest Quick Help|xb|cb|s00|n|n|cl|bMoving|xb|cb Press the direction buttons in the 'Compass' pane, or type |bGO NORTH|xb, |bSOUTH|xb, |bE|xb, etc. |xnTo go into a place, type |bGO TO ...|xb . To leave a place, type |bOUT, EXIT|xb or |bLEAVE|xb, or press the '|crOUT|cb' button.|n|cl|bObjects and Characters|xb|cb Use |bTAKE ...|xb, |bGIVE ... TO ...|xb, |bTALK|xb/|bSPEAK TO ...|xb, |bUSE ... ON|xb/|bWITH ...|xb, |bLOOK AT ...|xb, etc.|n|cl|bExit Quest|xb|cb Type |bQUIT|xb to leave Quest.|n|cl|bMisc|xb|cb Type |bABOUT|xb to get information on the current game."); +		return true; +	} + +	if (ci_equal(cmd, "about")) { +		const GeasBlock *gb = gf.find_by_name("game", "game"); +		if (gb == NULL) +			return true; +		cerr << *gb << endl; + +		uint c1, c2; +		//print_formatted ("Game name: "); +		line = gb->data[0]; +		tok = first_token(line, c1, c2);  // game +		tok = next_token(line, c1, c2);  // name +		tok = next_token(line, c1, c2);  // <whatever> +		if (is_param(tok)) +			print_formatted("Game name: " + eval_param(tok)); + + +		for (uint i = 0; i < gb->data.size(); i ++) { +			line = gb->data[i]; +			// SENSITIVE? +			if (first_token(line, c1, c2) == "game" && +			        next_token(line, c1, c2) == "version" && +			        is_param(tok = next_token(line, c1, c2))) +				print_formatted("Version " + eval_param(tok)); +		} + +		for (uint i = 0; i < gb->data.size(); i ++) { +			line = gb->data[i]; +			// SENSITIVE? +			if (first_token(line, c1, c2) == "game" && +			        next_token(line, c1, c2) == "author" && +			        is_param(tok = next_token(line, c1, c2))) +				print_formatted("Author: " + eval_param(tok)); +		} + +		for (uint i = 0; i < gb->data.size(); i ++) { +			line = gb->data[i]; +			// SENSITIVE? +			if (first_token(line, c1, c2) == "game" && +			        next_token(line, c1, c2) == "copyright" && +			        is_param(tok = next_token(line, c1, c2))) +				print_formatted("Copyright: " + eval_param(tok)); +		} + +		for (uint i = 0; i < gb->data.size(); i ++) { +			line = gb->data[i]; +			// SENSITIVE? +			if (first_token(line, c1, c2) == "game" && +			        next_token(line, c1, c2) == "info" && +			        is_param(tok = next_token(line, c1, c2))) +				print_formatted(eval_param(tok)); +		} + +		return true; +	} + +	if (ci_equal(cmd, "quit")) { +		is_running_ = false; +		return true; +	} + +	return false; +} + +void geas_implementation::run_script_as(String obj, String scr) { +	String backup_object, garbage; +	backup_object = this_object; +	this_object = obj; +	run_script(scr, garbage); +	this_object = backup_object; +} + +void geas_implementation::run_script(String s) { +	String garbage; +	run_script(s, garbage); +} + +void geas_implementation::run_script(String s, String &rv) { +	//print_formatted ("     Running script " + s + "."); +	cerr << "Script line '" << s << "'\n"; +	String tok; +	uint c1, c2; + +	tok = first_token(s, c1, c2); + +	if (tok == "") return; + +	if (tok[0] == '{') { +		uint brace1 = c1 + 1, brace2; +		for (brace2 = s.length() - 1; brace2 >= brace1 && s[brace2] != '}'; brace2 --) +			; +		if (brace2 >= brace1) +			run_script(s.substr(brace1, brace2 - brace1)); +		else +			gi->debug_print("Unterminated brace block in " + s); +		return; +	} + +	// SENSITIVE? +	if (tok == "action") { +		tok = next_token(s, c1, c2); +		if (!is_param(tok)) { +			gi->debug_print("Expected parameter after action in " + s); +			return; +		} +		tok = eval_param(tok); +		int index = tok.find(';'); +		if (index == -1) { +			gi->debug_print("Error: no semicolon in " + s); +			return; +		} +		set_obj_action(trim(tok.substr(0, index)), +		               "<" + trim(tok.substr(index + 1)) + "> " + s.substr(c2 + 1)); +		return; +	} +	// SENSITIVE? +	else if (tok == "animate") { +	} +	// SENSITIVE? +	else if (tok == "background") { +		tok = next_token(s, c1, c2); +		if (is_param(tok)) +			gi->set_background(eval_param(tok)); +		else +			gi->debug_print("Expected parameter after foreground in " + s); +		return; +	} +	// SENSITIVE? +	else if (tok == "choose") { +		tok = next_token(s, c1, c2); +		if (!is_param(tok)) { +			gi->debug_print("Expected parameter after choose in " + s); +			return; +		} +		tok = eval_param(tok); +		const GeasBlock *gb = gf.find_by_name("selection", tok); +		if (gb == NULL) { +			gi->debug_print("No selection called " + tok + " found"); +			return; +		} +		String question, line; +		Common::Array<String> choices, actions; +		for (uint ln = 0; ln < gb->data.size(); ln ++) { +			line = gb->data[ln]; +			tok = first_token(line, c1, c2); +			// SENSITIVE? +			if (tok == "info") { +				tok = next_token(line, c1, c2); +				if (is_param(tok)) +					question = eval_param(tok); +				else +					gi->debug_print("Expected parameter after info in " + line); +			} +			// SENSITIVE? +			else if (tok == "choice") { +				tok = next_token(line, c1, c2); +				if (is_param(tok)) { +					choices.push_back(eval_param(tok)); +					actions.push_back(line.substr(c2)); +				} else +					gi->debug_print("Expected parameter after choice in " + line); +			} else +				gi->debug_print("Bad line " + line + " in selection"); +		} +		if (choices.size() == 0) +			//gi->debug_print ("No choices in selection " + gb->lname); +			gi->debug_print("No choices in selection " + gb->name); +		else +			run_script(actions[gi->make_choice(question, choices)]); +		return; +	} +	// SENSITIVE? +	else if (tok == "clear") { +		gi->clear_screen(); +		return; +	} +	// SENSITIVE? +	else if (tok == "clone") { +		/* TODO */ +	} +	// SENSITIVE? +	else if (tok == "create") { +		tok = next_token(s, c1, c2); +		// SENSITIVE? +		if (tok == "exit") { // create exit +			String dir = ""; + +			tok = next_token(s, c1, c2); +			if (!is_param(tok)) { +				dir = tok; +				tok = next_token(s, c1, c2); +			} + +			if (!is_param(tok)) { +				gi->debug_print("Expected param after create exit in " + s); +				return; +			} +			tok = eval_param(tok); +			Common::Array<String> args = split_param(tok); +			if (args.size() != 2) { +				gi->debug_print("Expected 2 elements in param in " + s); +				return; +			} +			if (dir != "") +				state.exits.push_back(ExitRecord(args[0], +				                                 "exit " + dir + " <" + tok + ">")); +			else +				state.exits.push_back(ExitRecord(args[0], "exit <" + tok + ">")); +			//gi->debug_print ("Not yet able to create place type exits"); +			regen_var_dirs(); +			return; +		} +		// SENSITIVE? +		else if (tok == "object") { // create object +			/* TODO */ +		} +		// SENSITIVE? +		else if (tok == "room") { // create room +			/* TODO */ +		} else +			gi->debug_print("Bad create line " + s); +		return; +	} +	// SENSITIVE? +	else if (tok == "debug") { +		tok = next_token(s, c1, c2); +		if (is_param(tok)) +			gi->debug_print(eval_param(tok)); +		else +			gi->debug_print("Expected param after debug in " + s); +		return; +	} +	// SENSITIVE? +	else if (tok == "destroy") { +		tok = next_token(s, c1, c2); +		if (tok != "exit") { +			gi->debug_print("expected 'exit' after 'destroy' in " + s); +			return; +		} +		tok = next_token(s, c1, c2); +		if (!is_param(tok)) { +			gi->debug_print("Expected param after 'destroy exit' in " + s); +			return; +		} +		tok = eval_param(tok); +		Common::Array<String> args = split_param(tok); +		if (args.size() != 2) { +			gi->debug_print("Expected two arguments in " + s); +			return; +		} +		//state.exits.push_back (ExitRecord (args[0], "destroy exit <" + tok + ">")); +		state.exits.push_back(ExitRecord(args[0], "destroy exit " + args[1])); +		regen_var_dirs(); +		return; +	} +	// SENSITIVE? +	else if (tok == "disconnect") { +		/* QNSO */ +	} +	// SENSITIVE? +	else if (tok == "displaytext") { +		tok = next_token(s, c1, c2); +		if (!is_param(tok)) { +			gi->debug_print("Expected parameter after displaytext in " + s); +			return; +		} +		const GeasBlock *gb = gf.find_by_name("text", param_contents(tok)); +		if (gb != NULL) { +			for (uint i = 0; i < gb->data.size(); i ++) { +				print_formatted(gb->data[i]); +				print_newline(); +			} +		} else +			gi->debug_print("No such text block " + tok); +		return; +	} +	// SENSITIVE? +	else if (tok == "do") { +		tok = next_token(s, c1, c2); +		if (!is_param(tok)) { +			gi->debug_print("Expected parameter after do in " + s); +			return; +		} +		String fname = eval_param(tok); +		int index = fname.find('('); +		if (index != -1) { +			int index2 = fname.find(')'); +			run_procedure(trim(fname.substr(0, index)), +			              split_f_args(fname.substr(index + 1, index2 - index - 1))); +		} else +			run_procedure(fname); + +		//run_procedure (fname); +		return; +	} +	// SENSITIVE? +	else if (tok == "doaction") { +		tok = next_token(s, c1, c2); +		if (!is_param(tok)) { +			gi->debug_print("Expected parameter after doaction in " + s); +			return; +		} +		String line = eval_param(tok); +		int index = line.find(';'); +		String obj = trim(line.substr(0, index)); +		String act = trim(line.substr(index + 1)); +		String old_object = this_object; +		this_object = obj; +		if (get_obj_action(obj, act, tok)) +			run_script_as(obj, tok); +		//run_script (tok); +		else +			gi->debug_print("No action defined for " + obj + " // " + act); +		this_object = old_object; +		return; +	} +	// SENSITIVE? +	else if (tok == "dontprocess") { +		dont_process = true; +		return; +	} +	// SENSITIVE? +	else if (tok == "enter") { +		tok = next_token(s, c1, c2); +		if (!is_param(tok)) { +			gi->debug_print("Expected parameter after enter in " + s); +			return; +		} +		tok = eval_param(tok); +		set_svar(tok, gi->get_string()); +		return; +	} +	// SENSITIVE? +	else if (tok == "exec") { +		tok = next_token(s, c1, c2); +		if (!is_param(tok)) { +			gi->debug_print("Expected parameter after exec in " + s); +			return; +		} +		tok = eval_param(tok); +		int index = tok.find(';'); +		if (index != -1) { +			String tmp = trim(tok.substr(index + 1)); +			// SENSITIVE? +			if (tmp == "normal") { +				//run_command (trim (tok.substr (0, index)), true, true); +				try_match(trim(tok.substr(0, index)), true, true); +			} else { +				gi->debug_print("Bad " + tmp + " in exec in " + s); +				//run_command (trim (tok.substr (0, index)), true, false); +				try_match(trim(tok.substr(0, index)), true, false); +			} +		} else { +			//run_command (trim (tok.substr (0, index)), true, false); +			try_match(trim(tok.substr(0, index)), true, false); +		} +		return; +	} +	// SENSITIVE? +	else if (tok == "flag") { +		tok = next_token(s, c1, c2); +		bool is_on; +		// SENSITIVE? +		if (tok == "on") +			is_on = true; +		// SENSITIVE? +		else if (tok == "off") +			is_on = false; +		else { +			gi->debug_print("Expected 'on' or 'off' after flag in " + s); +			return; +		} +		String onoff = tok; + +		tok = next_token(s, c1, c2); +		if (is_param(tok)) +			set_obj_property("game", (is_on ? "" : "not ") + eval_param(tok)); +		else +			gi->debug_print("Expected param after flag " + onoff + " in " + s); +		return; +	} +	// SENSITIVE? +	else if (tok == "font") { +		/* TODO */ +	} +	// SENSITIVE? +	else if (tok == "for") { +		tok = next_token(s, c1, c2); +		// SENSITIVE? +		if (tok == "each") { +			// SENSITIVE? +			if (next_token(s, c1, c2) == "object" && +			        next_token(s, c1, c2) == "in") { +				tok = next_token(s, c1, c2); +				// SENSITIVE? +				if (tok == "game") { +					/* TODO: This will run over the game, rooms, and objects */ +					/* It should just do the objects. */ +					String script = s.substr(c2); +					// Start at 1 to skip game +					for (uint i = 1; i < state.objs.size(); i ++) { +						cerr << "  quest.thing -> " + state.objs[i].name + "\n"; +						set_svar("quest.thing", state.objs[i].name); +						run_script(script); +					} +					return; +				} else if (is_param(tok)) { +					tok = trim(eval_param(tok)); +					String script = s.substr(c2); +					for (uint i = 0; i < state.objs.size(); i ++) +						if (state.objs[i].parent == tok) { +							set_svar("quest.thing", state.objs[i].name); +							run_script(script); +						} +					return; +				} +			} +		} else if (is_param(tok)) { +			Common::Array<String> args = split_param(eval_param(tok)); +			String varname = args[0]; +			String script = s.substr(c2); +			int startindex = parse_int(args[1]); +			int endindex = parse_int(args[2]); +			int step = 1; +			if (args.size() > 3) +				step = parse_int(args[3]); +			for (set_ivar(varname, startindex); get_ivar(varname) < endindex; +			        set_ivar(varname, get_ivar(varname) + step)) +				run_script(script); +			return; +		} + +	} +	// SENSITIVE? +	else if (tok == "foreground") { +		tok = next_token(s, c1, c2); +		if (is_param(tok)) +			gi->set_foreground(eval_param(tok)); +		else +			gi->debug_print("Expected parameter after foreground in " + s); +		return; +	} +	// SENSITIVE? +	else if (tok == "give") { +		tok = next_token(s, c1, c2); +		if (!is_param(tok)) { +			gi->debug_print("Expected parameter after give in " + s); +			return; +		} +		tok = eval_param(tok); +		move(tok, "inventory"); +		String tmp; +		if (get_obj_action(tok, "gain", tmp)) +			run_script_as(tok, tmp); +		//run_script (tmp); +		else if (get_obj_property(tok, "gain", tmp)) +			print_formatted(tmp); +		return; +	} +	// SENSITIVE? +	else if (tok == "goto") { +		tok = next_token(s, c1, c2); +		if (is_param(tok)) +			goto_room(trim(eval_param(tok))); +		else +			gi->debug_print("Expected parameter after goto in " + s); +		return; +	} +	// SENSITIVE? +	else if (tok == "helpclear") { +	} +	// SENSITIVE? +	else if (tok == "helpclose") { +	} +	// SENSITIVE? +	else if (tok == "helpdisplaytext") { +	} +	// SENSITIVE? +	else if (tok == "helpmsg") { +	} +	// SENSITIVE? +	else if (tok == "hide") { +		tok = next_token(s, c1, c2); +		if (is_param(tok)) +			set_obj_property(eval_param(tok), "hidden"); +		else +			gi->debug_print("Expected param after conceal in " + s); +		return; +	} +	// SENSITIVE? +	else if (tok == "show") { +		tok = next_token(s, c1, c2); +		if (is_param(tok)) +			set_obj_property(eval_param(tok), "not hidden"); +		else +			gi->debug_print("Expected param after conceal in " + s); +		return; +	} +	// SENSITIVE? +	else if (tok == "if") { +		/* TODO TODO */ +		uint begin_cond = c2 + 1, end_cond, begin_then, end_then; + +		do { +			tok = next_token(s, c1, c2); +			// SENSITIVE? +		} while (tok != "then" && tok != ""); + +		if (tok == "") { +			gi->debug_print("Expected then in if: " + s); +			return; +		} +		end_cond = c1; +		String cond_str = s.substr(begin_cond, end_cond - begin_cond); + +		begin_then = c2 + 1; +		int brace_count = 0; +		do { +			tok = next_token(s, c1, c2); +			for (uint i = 0; i < tok.length(); i ++) +				if (tok[i] == '{') +					brace_count ++; +				else if (tok[i] == '}') +					brace_count --; +			// SENSITIVE? +		} while (tok != "" && !(brace_count == 0 && tok == "else")); +		end_then = c1; + + +		if (eval_conds(cond_str)) +			run_script(s.substr(begin_then, end_then - begin_then), rv); +		else if (c2 < s.length()) +			run_script(s.substr(c2), rv); +		return; +	} +	// SENSITIVE? +	else if (tok == "inc" || tok == "dec") { +		// SENSITIVE? +		bool is_dec = (tok == "dec"); +		tok = next_token(s, c1, c2); +		if (!is_param(tok)) { +			gi->debug_print("Expected parameter after inc in " + s); +			return; +		} +		tok = eval_param(tok); +		int diff; +		int index = tok.find(';'); +		String varname; +		if (index == -1) { +			varname = trim(tok); +			diff = 1; +		} else { +			varname = trim(tok.substr(0, index)); +			diff = eval_int(tok.substr(index + 1)); +		} +		if (is_dec) +			set_ivar(varname, get_ivar(varname) - diff); +		else +			set_ivar(varname, get_ivar(varname) + diff); +		return; +	} +	// SENSITIVE? +	else if (tok == "lose") { +		/* TODO TODO */ +		tok = next_token(s, c1, c2); +		if (!is_param(tok)) { +			gi->debug_print("Expected parameter after lose in " + s); +			return; +		} +		tok = eval_param(tok); + +		/* TODO: is the object always moved to location, or only +		 * when it had been in the inventory ? +		 */ +		bool was_lost = (ci_equal(get_obj_parent(tok), "inventory")); +		if (was_lost) { +			move(tok, state.location); +			String tmp; +			if (get_obj_action(tok, "lose", tmp)) +				run_script_as(tok, tmp); +			//run_script (tmp); +			else if (get_obj_property(tok, "lose", tmp)) +				print_formatted(tmp); +		} +		return; +	} +	// SENSITIVE? +	else if (tok == "mailto") { +	} +	// SENSITIVE? +	else if (tok == "modvolume") { +	} +	// SENSITIVE? +	else if (tok == "move") { +		tok = next_token(s, c1, c2); +		if (!is_param(tok)) { +			gi->debug_print("Expected parameter after move in " + s); +			return; +		} +		tok = eval_param(tok); +		int index = tok.find(';'); +		if (index == -1) { +			gi->debug_print("No semi in " + tok + " in " + s); +			return; +		} +		move(trim(tok.substr(0, index)), trim(tok.substr(index + 1))); +		return; +	} +	// SENSITIVE? +	else if (tok == "msg") { +		tok = next_token(s, c1, c2); +		if (is_param(tok)) +			print_eval(param_contents(tok)); +		else +			gi->debug_print("Expected parameter after msg in " + s); +		return; +	} +	// SENSITIVE? +	else if (tok == "msgto") { +		/* QNSO */ +	} +	// SENSITIVE? +	else if (tok == "outputoff") { +		//print_formatted ("<<"); +		outputting = false; +		return; +	} +	// SENSITIVE? +	else if (tok == "outputon") { +		outputting = true; +		//print_formatted (">>"); +		return; +	} +	// SENSITIVE? +	else if (tok == "panes") { +		/* TODO */ +	} +	// SENSITIVE? +	else if (tok == "pause") { +		tok = next_token(s, c1, c2); +		if (!is_param(tok)) { +			gi->debug_print("Expected parameter after pause in " + s);; +			return; +		} +		int i = eval_int(param_contents(tok)); +		gi->pause(i); +		return; +	} +	// SENSITIVE? +	else if (tok == "picture") { +	} +	// SENSITIVE? +	else if (tok == "playerlose") { +		run_script("displaytext <lose>"); +		state.running = false; +		return; +	} +	// SENSITIVE? +	else if (tok == "playerwin") { +		run_script("displaytext <win>"); +		state.running = false; +		return; +	} +	// SENSITIVE? +	else if (tok == "playmidi") { +	} +	// SENSITIVE? +	else if (tok == "playmod") { +	} +	// SENSITIVE? +	else if (tok == "playwav") { +	} +	// SENSITIVE? +	else if (tok == "property") { +		tok = next_token(s, c1, c2); +		if (!is_param(tok)) { +			gi->debug_print("Expected parameter in '" + s + "'"); +			return; +		} +		Common::Array<String> args = split_param(eval_param(tok)); +		for (uint i = 1; i < args.size(); i ++) { +			String val = args[i]; +			/* +			if (val[0] == '[' && val[val.length() - 1] == ']') +			  val = val.substr (1, val.length() - 2); +			//state.props.push_back (PropertyRecord (args[0], val)); +			*/ +			val = trim_braces(val); +			set_obj_property(args[0], val); +		} +		return; +	} +	// SENSITIVE? +	else if (tok == "repeat") { +		/* TODO TODO: assumes script is a "do ..." */ +		tok = next_token(s, c1, c2); +		// SENSITIVE? +		if (tok != "while" && tok != "until") { +			gi->debug_print("Expected while or until after repeat in " + s); +			return; +		} +		bool is_while = (tok == "while"); +		uint start_cond = c2, end_cond = (uint) -1; +		while ((tok = next_token(s, c1, c2)) != "") { +			// SENSITIVE? +			if (tok == "do") { +				end_cond = c1; +				break; // TODO: Do I break here? +			} +		} +		if (end_cond == -1) { +			gi->debug_print("No script found after condition in " + s); +			return; +		} +		String cond = trim(s.substr(start_cond, end_cond - start_cond)); +		String script = trim(s.substr(end_cond)); +		cerr << "Interpreting '" << s << "' as (" +		     << (is_while ? "WHILE" : "UNTIL") << ") (" +		     << cond << ") {" << script << "}\n"; +		while (eval_conds(cond) == is_while) +			run_script(script); +		return; +	} +	// SENSITIVE? +	else if (tok == "return") { +		tok = next_token(s, c1, c2); +		if (is_param(tok)) +			rv = eval_param(tok); +		else +			gi->debug_print("Expected parameter after return in " + s); +		return; +	} +	// SENSITIVE? +	else if (tok == "reveal") { +		tok = next_token(s, c1, c2); +		if (is_param(tok)) +			set_obj_property(eval_param(tok), "not invisible"); +		else +			gi->debug_print("Expected param after reveal in " + s); +		return; +	} +	// SENSITIVE? +	else if (tok == "conceal") { +		tok = next_token(s, c1, c2); +		if (is_param(tok)) +			set_obj_property(eval_param(tok), "invisible"); +		else +			gi->debug_print("Expected param after conceal in " + s); +		return; +	} +	// SENSITIVE? +	else if (tok == "say") { +		tok = next_token(s, c1, c2); +		if (is_param(tok)) { +			tok = eval_param(tok); +			print_formatted("\"" + tok + "\""); +		} else +			gi->debug_print("Expected param after say in " + s); +		return; +	} +	// SENSITIVE? +	else if (tok == "set") { +		String vartype = ""; +		tok = next_token(s, c1, c2); +		// SENSITIVE? +		if (tok == "interval") { +			tok = next_token(s, c1, c2); +			if (!is_param(tok)) { +				gi->debug_print("Expected param after set interval in " + s); +				return; +			} +			tok = eval_param(tok); +			int index = tok.find(';'); +			if (index == -1) { +				gi->debug_print("No semicolon in param in " + s); +				return; +			} +			//String timer_name = lcase (trim (tok.substr (0, index))); +			String timer_name = trim(tok.substr(0, index)); +			uint time_val = parse_int(trim(tok.substr(index + 1))); + +			for (uint i = 0; i < state.timers.size(); i ++) +				if (state.timers[i].name == timer_name) { +					state.timers[i].interval = time_val; +					return; +				} +			gi->debug_print("no interval named " + timer_name + " found!"); +			return; +		} +		// SENSITIVE? +		if (tok == "String" || tok == "numeric") { +			vartype = tok; +			tok = next_token(s, c1, c2); +		} +		if (!is_param(tok)) { +			gi->debug_print("Expected parameter in " + s); +			return; +		} +		if (tok.find(';') == -1) { +			gi->debug_print("Only one expression in set in " + s); +			return; +		} +		tok = eval_param(tok); +		int index = tok.find(';'); +		//String varname = lcase (trim (tok.substr (0, index))); +		String varname = trim(tok.substr(0, index)); +		if (vartype == "") { +			for (uint varn = 0; varn < state.ivars.size(); varn ++) +				if (state.ivars[varn].name == varname) { +					vartype = "numeric"; +					break; +				} +			if (vartype == "") +				for (uint varn = 0; varn < state.svars.size(); varn ++) +					if (state.svars[varn].name == varname) { +						vartype = "String"; +						break; +					} +		} +		if (vartype == "") { +			gi->debug_print("Undefined variable " + varname + " in " + s); +			return; +		} +		if (vartype == "String") +			set_svar(varname, trim_braces(trim(tok.substr(index + 1)))); +		else +			set_ivar(varname, eval_int(tok.substr(index + 1))); +		return; +	} +	// SENSITIVE? +	else if (tok == "setstring") { +		tok = next_token(s, c1, c2); +		if (!is_param(tok)) { +			gi->debug_print("Expected parameter in " + s); +			return; +		} +		if (tok.find(';') == -1) { +			gi->debug_print("Only one expression in set in " + s); +			return; +		} +		tok = eval_param(tok); +		int index = tok.find(';'); +		String varname = trim(tok.substr(0, index)); +		set_svar(varname, trim_braces(trim(tok.substr(index + 1)))); +		return; +	} +	// SENSITIVE? +	else if (tok == "setvar") { +		tok = next_token(s, c1, c2); +		if (!is_param(tok)) { +			gi->debug_print("Expected parameter in " + s); +			return; +		} +		if (tok.find(';') == -1) { +			gi->debug_print("Only one expression in set in " + s); +			return; +		} +		tok = eval_param(tok); +		int index = tok.find(';'); +		String varname = trim(tok.substr(0, index)); +		set_ivar(varname, eval_int(tok.substr(index + 1))); +		return; +	} +	// SENSITIVE? +	else if (tok == "shell") { + +	} +	// SENSITIVE? +	else if (tok == "shellexe") { +	} +	// SENSITIVE? +	else if (tok == "speak") { +		tok = next_token(s, c1, c2); +		if (is_param(tok)) +			gi->speak(eval_param(tok)); +		else +			gi->debug_print("Expected param after speak in " + s); +		return; +	} +	// SENSITIVE? +	else if (tok == "stop") { +		state.running = false; +		return; +	} +	// SENSITIVE? +	else if (tok == "timeron" || tok == "timeroff") { +		// SENSITIVE? +		bool running = (tok == "timeron"); +		//tok = lcase (next_token (s, c1, c2)); +		tok = next_token(s, c1, c2); +		if (is_param(tok)) { +			tok = eval_param(tok); +			for (uint i = 0; i < state.timers.size(); i ++) +				if (state.timers[i].name == tok) { +					if (running) +						state.timers[i].timeleft = state.timers[i].interval; +					state.timers[i].is_running = running; +					return; +				} +			gi->debug_print("No timer " + tok + " found"); +			return; +		} +		gi->debug_print(String("Expected parameter after timer") + +		                (running ? "on" : "off") + " in " + s); +		return; +	} +	// SENSITIVE? +	else if (tok == "type") { +		/* TODO */ +	} +	// SENSITIVE? +	else if (tok == "wait") { +		tok = next_token(s, c1, c2); +		if (tok != "") { +			if (!is_param(tok)) { +				gi->debug_print("Expected parameter after wait in " + s); +				return; +			} +			tok = eval_param(tok); +		} +		gi->wait_keypress(tok); +		return; +	} +	// SENSITIVE? +	else if (tok == "with") { +		// QNSO +	} +	gi->debug_print("Unrecognized script " + s); +} + +bool geas_implementation::eval_conds(String s) { +	cerr << "if (" + s + ")" << endl; + +	uint c1, c2; +	String tok = first_token(s, c1, c2); + +	if (tok == "") return true; + +	bool rv = eval_cond(s); + +	while (tok != "" && tok != "and") +		tok = next_token(s, c1, c2); + +	if (tok == "and") +		rv = rv && eval_conds(s.substr(c2)); +	else { +		tok = first_token(s, c1, c2); +		while (tok != "" && tok != "or") +			tok = next_token(s, c1, c2); +		if (tok == "or") +			rv = rv || eval_conds(s.substr(c2)); +	} + +	cerr << "if (" << s << ") --> " << (rv ? "true" : "false") << endl; +	return rv; +} + +bool geas_implementation::eval_cond(String s) { +	uint c1, c2; +	String tok = first_token(s, c1, c2); +	// SENSITIVE? +	if (tok == "not") +		return !eval_cond(s.substr(c2)); +	// SENSITIVE? +	else if (tok == "action") { +		tok = next_token(s, c1, c2); +		if (!is_param(tok)) { +			gi->debug_print("expected parameter after property in " + s); +			return false; +		} +		tok = eval_param(tok); +		int index = tok.find(';'); +		if (index == -1) { +			gi->debug_print("Only one argument to property in " + s); +			return false; +		} +		String obj = trim(tok.substr(0, index)); +		String act = trim(tok.substr(index + 1)); +		return has_obj_action(obj, act); +	} +	// SENSITIVE? +	else if (tok == "ask") { +		tok = next_token(s, c1, c2); +		if (!is_param(tok)) { +			gi->debug_print("expected parameter after ask in " + s); +			return false; +		} +		tok = eval_param(tok); +		return gi->choose_yes_no(tok); +	} +	// SENSITIVE? +	else if (tok == "exists") { +		tok = next_token(s, c1, c2); +		if (!is_param(tok)) { +			gi->debug_print("expected parameter after exists in " + s); +			return false; +		} +		Common::Array<String> args = split_param(eval_param(tok)); +		bool do_report = false; +		for (uint i = 1; i < args.size(); i ++) +			// SENSITIVE? +			if (args[i] == "report") +				do_report = true; +			else +				gi->debug_print("Got modifier " + args[i] + " after exists"); +		//args[0] = lcase (args[0]); +		for (uint i = 0; i < state.objs.size(); i ++) +			if (ci_equal(state.objs[i].name, args[0])) +				return state.objs[i].parent != ""; +		if (do_report) +			gi->debug_print("exists " + args[0] + " failed due to nonexistence"); +		return false; +	} +	// SENSITIVE? +	else if (tok == "flag") { +		tok = next_token(s, c1, c2); +		if (!is_param(tok)) { +			gi->debug_print("expected parameter after flag in " + s); +			return false; +		} +		tok = trim(eval_param(tok)); +		return has_obj_property("game", tok); +	} +	// SENSITIVE? +	else if (tok == "got") { +		tok = next_token(s, c1, c2); +		if (!is_param(tok)) { +			gi->debug_print("expected parameter after got in " + s); +			return false; +		} +		//tok = lcase (trim (eval_param (tok))); +		tok = trim(eval_param(tok)); +		for (uint i = 0; i < state.objs.size(); i ++) +			if (ci_equal(state.objs[i].name, tok)) +				return ci_equal(state.objs[i].parent, "inventory"); +		gi->debug_print("No object " + tok + " found while evaling " + s); +		return false; +	} +	// SENSITIVE? +	else if (tok == "here") { +		tok = next_token(s, c1, c2); +		if (!is_param(tok)) { +			gi->debug_print("expected parameter after here in " + s); +			return false; +		} +		//tok = lcase (trim (eval_param (tok))); +		tok = trim(eval_param(tok)); +		for (uint i = 0; i < state.objs.size(); i ++) +			if (ci_equal(state.objs[i].name, tok)) { +				//return (ci_equal (state.objs[i].parent, state.location) && +				//    !has_obj_property (tok, "invisible")); +				return (ci_equal(state.objs[i].parent, state.location)); +			} +		/* TODO: is it invisible or hidden? */ +		gi->debug_print("No object " + tok + " found while evaling " + s); +		return false; +	} +	// SENSITIVE? +	else if (tok == "is") { +		tok = next_token(s, c1, c2); +		if (!is_param(tok)) { +			gi->debug_print("expected parameter after is in " + s); +			return false; +		} +		tok = eval_param(tok); +		int index; +		// SENSITIVE? +		if ((index = tok.find("!=;")) != -1) { +			uint index1 = index; +			do { +				-- index1; +			} while (index1 > 0 && tok[index1] != ';'); + +			cerr << "Comparing <" << trim_braces(trim(tok.substr(0, index1))) +			     << "> != <" << trim_braces(trim(tok.substr(index + 3))) +			     << ">\n"; +			return ci_notequal(trim_braces(trim(tok.substr(0, index - 1))), +			                   trim_braces(trim(tok.substr(index + 3)))); +		} +		if ((index = tok.find("lt=;")) != -1) { +			cerr << "Comparing <" << trim_braces(trim(tok.substr(0, index))) +			     << "> < <" << trim_braces(trim(tok.substr(index + 4))) +			     << ">\n"; +			return eval_int(tok.substr(0, index - 1)) +			       <= eval_int(tok.substr(index + 4)); +		} +		if ((index = tok.find("gt=;")) != -1) +			return eval_int(tok.substr(0, index)) +			       >= eval_int(tok.substr(index + 4)); +		if ((index = tok.find("lt;")) != -1) +			return eval_int(tok.substr(0, index)) +			       < eval_int(tok.substr(index + 3)); +		if ((index = tok.find("gt;")) != -1) +			return eval_int(tok.substr(0, index)) +			       > eval_int(tok.substr(index + 3)); +		if ((index = tok.find(";")) != -1) { +			cerr << "Comparing <" << trim_braces(trim(tok.substr(0, index))) +			     << "> == <" << trim_braces(trim(tok.substr(index + 1))) +			     << ">\n"; +			return ci_equal(trim_braces(trim(tok.substr(0, index))), +			                trim_braces(trim(tok.substr(index + 1)))); +		} +		gi->debug_print("Bad is condition " + tok + " in " + s); +		return false; +	} +	// SENSITIVE? +	else if (tok == "property") { +		tok = next_token(s, c1, c2); +		if (!is_param(tok)) { +			gi->debug_print("expected parameter after property in " + s); +			return false; +		} +		tok = eval_param(tok); +		int index = tok.find(';'); +		if (index == -1) { +			gi->debug_print("Only one argument to property in " + s); +			return false; +		} +		String obj = trim(tok.substr(0, index)); +		String prop = trim(tok.substr(index + 1)); +		return has_obj_property(obj, prop); +	} +	// SENSITIVE? +	else if (tok == "real") { +		tok = next_token(s, c1, c2); +		if (!is_param(tok)) { +			gi->debug_print("expected parameter after real in " + s); +			return false; +		} +		Common::Array<String> args = split_param(eval_param(tok)); +		bool do_report = false; +		for (uint i = 1; i < args.size(); i ++) +			// SENSITIVE? +			if (args[i] == "report") +				do_report = true; +			else +				gi->debug_print("Got modifier " + args[i] + " after exists"); +		//args[0] = lcase (args[0]); +		for (uint i = 0; i < state.objs.size(); i ++) +			if (ci_equal(state.objs[i].name, args[0])) +				return true; +		if (do_report) +			gi->debug_print("real " + args[0] + " failed due to nonexistence"); +		return false; +	} +	// SENSITIVE? +	else if (tok == "type") { +		tok = next_token(s, c1, c2); +		if (!is_param(tok)) { +			gi->debug_print("Expected parameter after type in " + s); +			return false; +		} +		Common::Array<String> args = split_param(eval_param(tok)); +		if (args.size() != 2) { +			gi->debug_print("Expected two parameters to type in " + s); +			return false; +		} +		return gf.obj_of_type(args[0], args[1]); +	} + +	gi->debug_print("Bad condition " + s); +	return false; +} + +void geas_implementation::run_procedure(String pname, Common::Array<String> args) { +	cerr << "run_procedure " << pname << " (" << args << ")\n"; +	Common::Array<String> backup = function_args; +	function_args = args; +	run_procedure(pname); +	function_args = backup; +} + +void geas_implementation::run_procedure(String pname) { +	for (uint i = 0; i < gf.size("procedure"); i ++) +		if (ci_equal(gf.block("procedure", i).name, pname)) { +			const GeasBlock &proc = gf.block("procedure", i); +			//cerr << "Running procedure " << proc << endl; +			for (uint j = 0; j < proc.data.size(); j ++) { +				//cerr << "  Running line #" << j << ": " << proc.data[j] << endl; +				run_script(proc.data[j]); +			} +			return; +		} +	gi->debug_print("No procedure " + pname + " found."); +} + +String geas_implementation::run_function(String pname, Common::Array<String> args) { +	cerr << "run_function (w/ args) " << pname << " (" << args << ")\n"; +	/* Parameter is handled specially because it can't change the stack */ +	// SENSITIVE? +	if (pname == "parameter") { +		if (args.size() != 1) { +			gi->debug_print("parameter called with " + string_int(args.size()) +			                + " args"); +			return ""; +		} +		uint num = parse_int(args[0]); +		if (0 < num && num <= function_args.size()) { +			cerr << "   --> " << function_args[num - 1] << "\n"; +			return function_args[num - 1]; +		} +		cerr << "   --> too many arguments\n"; +		return ""; +	} +	Common::Array<String> backup = function_args; +	function_args = args; +	for (uint i = 0; i < args.size(); i ++) +		set_svar("quest.function.parameter." + string_int(i + 1), args[i]); +	String rv = run_function(pname); +	function_args = backup; +	return rv; +} + +String geas_implementation::bad_arg_count(String fname) { +	gi->debug_print("Called " + fname + " with " + +	                string_int(function_args.size()) + " arguments."); +	return ""; +} + +String geas_implementation::run_function(String pname) { +	cerr << "geas_implementation::run_function (" << pname << ", " << function_args << ")\n"; +	//pname = lcase (pname); +	// SENSITIVE? +	if (pname == "getobjectname") { +		if (function_args.size() == 0) +			return bad_arg_count(pname); +		//return get_obj_name (function_args); +		Common::Array<String> where; +		for (uint i = 1; i < function_args.size(); i ++) +			where.push_back(function_args[i]); +		if (where.size() == 0) { +			where.push_back(state.location); +			where.push_back("inventory"); +		} +		bool is_internal = false; +		return get_obj_name(function_args[0], where, is_internal); +	} +	// SENSITIVE? +	else if (pname == "loadmethod") { +		/* TODO TODO */ +		return "normal"; +		// "loaded" on restore from qsg +	} +	// SENSITIVE? +	else if (pname == "locationof") { +		if (function_args.size() != 1) +			return bad_arg_count(pname); + +		return get_obj_parent(function_args[0]); +	} +	// SENSITIVE? +	else if (pname == "objectproperty") { +		if (function_args.size() != 2) +			return bad_arg_count(pname); + +		String rv; +		get_obj_property(function_args[0], function_args[1], rv); +		return rv; +	} +	// SENSITIVE? +	else if (pname == "timerstate") { +		if (function_args.size() != 1) +			return bad_arg_count(pname); + +		//String timername = lcase (function_args[0]); +		String timername = function_args[0]; +		for (uint i = 0; i < state.timers.size(); i ++) +			if (state.timers[i].name == timername) +				return state.timers[i].is_running ? "1" : "0"; +		return "!"; +	} +	// SENSITIVE? +	else if (pname == "displayname") { +		if (function_args.size() != 1) +			return bad_arg_count(pname); + +		return displayed_name(function_args[0]); +	} +	// SENSITIVE? +	else if (pname == "capfirst") { +		if (function_args.size() != 1) +			return bad_arg_count(pname); + +		return pcase(function_args[0]); +	} +	// SENSITIVE? +	else if (pname == "instr") { +		/* TODO What if it's not present? */ +		if (function_args.size() != 2 && function_args.size() != 3) +			return bad_arg_count(pname); + +		int rv; +		if (function_args.size() == 2) +			rv = function_args[0].find(function_args[1]); +		else +			rv = function_args[1].find(function_args[2], +			                           parse_int(function_args[0])); + +		if (rv == -1) +			return string_int(rv); // TODO What goes here? +		else +			return string_int(rv); +	} +	// SENSITIVE? +	else if (pname == "lcase") { +		if (function_args.size() != 1) +			return bad_arg_count(pname); + +		String rv = function_args[0]; +		for (uint i = 0; i < rv.size(); i ++) +			rv[i] = tolower(rv[i]); +		return rv; +	} +	// SENSITIVE? +	else if (pname == "left") { +		if (function_args.size() != 2) +			return bad_arg_count(pname); + +		uint i = parse_int(function_args[1]); +		if (i > function_args[0].length()) +			return function_args[0]; +		else +			return function_args[0].substr(0, i); +	} +	// SENSITIVE? +	else if (pname == "lengthof") { +		if (function_args.size() != 1) +			return bad_arg_count(pname); + +		return string_int(function_args[0].length()); +	} +	// SENSITIVE? +	else if (pname == "mid") { +		if (function_args.size() != 3) +			return bad_arg_count(pname); + +		uint start = parse_int(function_args[1]), +		     len = parse_int(function_args[2]); +		if (start > function_args[0].length()) +			return ""; +		if (start + len > function_args[0].length()) +			return function_args[0].substr(start); +		return function_args[0].substr(start, len); +	} +	// SENSITIVE? +	else if (pname == "right") { +		if (function_args.size() != 2) +			return bad_arg_count(pname); + +		uint size = parse_int(function_args[1]); +		if (size > function_args[0].length()) +			return function_args[0]; +		return function_args[0].substr(function_args[0].length() - size); +	} else if (pname == "ubound") { +		if (function_args.size() != 1) +			return bad_arg_count(pname); + +		/* TODO TODO */ +		return ""; +	} +	// SENSITIVE? +	else if (pname == "ucase") { +		if (function_args.size() != 1) +			return bad_arg_count(pname); + +		String rv = function_args[0]; +		for (uint i = 0; i < rv.length(); i ++) +			rv[i] = toupper(rv[i]); +		return rv; +	} +	// SENSITIVE? +	else if (pname == "rand") { +		if (function_args.size() != 2) +			return bad_arg_count(pname); +		uint lower = parse_int(function_args[0]), +		     upper = parse_int(function_args[1]); + +		// TODO: change this to use the high order bits of the random # instead +		return string_int(lower + (g_vm->getRandomNumber(0x7fffff) % (upper + 1 - lower))); +	} +	// SENSITIVE? +	else if (pname == "speechenabled") { +		if (function_args.size() != 0) +			return bad_arg_count(pname); + +		return "0"; +		/* TODO: return 1 if speech is enabled */ +	} +	// SENSITIVE? +	else if (pname == "symbol") { +		if (function_args.size() != 1) +			return bad_arg_count(pname); + +		// SENSITIVE? +		if (function_args[0] == "gt") +			return ">"; +		// SENSITIVE? +		else if (function_args[0] == "lt") +			return "<"; +		gi->debug_print("Bad symbol argument: " + function_args[0]); +		return ""; +	} +	// SENSITIVE? +	else if (pname == "numberparameters") +		return string_int(function_args.size()); +	// SENSITIVE? +	else if (pname == "thisobject") +		return this_object; + +	/* disconnectedby, id, name, removeformatting */ + +	String rv = ""; + +	for (uint i = 0; i < gf.size("function"); i ++) +		if (ci_equal(gf.block("function", i).name, pname)) { +			const GeasBlock &proc = gf.block("function", i); +			cerr << "Running function " << proc << endl; +			for (uint j = 0; j < proc.data.size(); j ++) { +				cerr << "  Running line #" << j << ": " << proc.data[j] << endl; +				run_script(proc.data[j], rv); +			} +			return rv; +		} +	gi->debug_print("No function " + pname + " found."); +	return ""; +} + + + +v2string geas_implementation::get_inventory() { +	return get_room_contents("inventory"); +} + +v2string geas_implementation::get_room_contents() { +	return get_room_contents(state.location); +} + +v2string geas_implementation::get_room_contents(String room) { +	v2string rv; +	String objname; +	for (uint i = 0; i < state.objs.size(); i ++) +		if (state.objs[i].parent == room) { +			objname = state.objs[i].name; +			if (!has_obj_property(objname, "invisible") && +			        !has_obj_property(objname, "hidden")) { +				vstring tmp; + +				String print_name, temp_str; +				if (!get_obj_property(objname, "alias", print_name)) +					print_name = objname; +				/* +				if (get_obj_property (objname, "prefix", temp_str)) +				  print_name = temp_str + " " + print_name; +				if (get_obj_property (objname, "suffix", temp_str)) +				  print_name = print_name + " " + temp_str; +				*/ +				tmp.push_back(print_name); + +				String otype; +				if (!get_obj_property(objname, "displaytype", otype)) +					otype = "object"; +				tmp.push_back(otype); +				rv.push_back(tmp); +			} +		} +	return rv; +} + +vstring geas_implementation::get_status_vars() { +	vstring rv; + +	String tok, line; +	uint c1, c2; + +	for (uint i = 0; i < gf.size("variable"); i ++) { +		const GeasBlock &gb = gf.block("variable", i); + +		bool nozero = false; +		String disp; +		bool is_numeric = true; + +		cerr << "g_s_v: " << gb << endl; + +		for (uint j = 0; j < gb.data.size(); j ++) { +			line = gb.data[j]; +			cerr << "  g_s_v:  " << line << endl; +			tok = first_token(line, c1, c2); +			// SENSITIVE? +			if (tok == "display") { +				tok = next_token(line, c1, c2); + +				// SENSITIVE? +				if (tok == "nozero") { +					nozero = true; +					tok = next_token(line, c1, c2); +				} +				if (!is_param(tok)) +					gi->debug_print("Expected param after display: " + line); +				else +					disp = tok; +			} +			// SENSITIVE? +			else if (tok == "type") { +				tok = next_token(line, c1, c2); +				// SENSITIVE? +				if (tok == "String") +					is_numeric = false; +			} +		} + +		cerr << "  g_s_v, block 2, tok == '" << tok << "'" << endl; +		if (!(is_numeric && nozero && get_ivar(gb.name) == 0) && disp != "") { +			disp = param_contents(disp); +			String outval = ""; +			for (uint j = 0; j < disp.length(); j ++) +				if (disp[j] == '!') { +					if (is_numeric) +						outval = outval + string_int(get_ivar(gb.name)); +					//outval = outval + string_int (get_ivar (gb.lname)); +					else +						outval = outval + get_svar(gb.name); +					//outval = outval + get_svar (gb.lname); +				} else if (disp[j] == '*') { +					uint k; +					for (k = j + 1; k < disp.length() && disp[k] != '*'; k ++) +						; +					//if (!is_numeric || get_ivar (gb.lname) != 1) +					if (!is_numeric || get_ivar(gb.name) != 1) +						outval = outval + disp.substr(j + 1, k - j - 1); +					j = k; +				} else +					outval = outval + disp[j]; +			rv.push_back(eval_string(outval)); +		} +	} +	return rv; +} + +Common::Array<bool> geas_implementation::get_valid_exits() { +	Common::Array<bool> rv; +	cerr << "Getting valid exits\n"; +	rv.push_back(exit_dest(state.location, "northwest") != ""); +	rv.push_back(exit_dest(state.location, "north") != ""); +	rv.push_back(exit_dest(state.location, "northeast") != ""); +	rv.push_back(exit_dest(state.location, "west") != ""); +	rv.push_back(exit_dest(state.location, "out") != ""); +	rv.push_back(exit_dest(state.location, "east") != ""); +	rv.push_back(exit_dest(state.location, "southwest") != ""); +	rv.push_back(exit_dest(state.location, "south") != ""); +	rv.push_back(exit_dest(state.location, "southeast") != ""); +	rv.push_back(exit_dest(state.location, "up") != ""); +	rv.push_back(exit_dest(state.location, "down") != ""); +	cerr << "Done getting valid exits\n"; + +	return rv; +} + +void geas_implementation::print_eval_p(String s) { +	print_formatted(pcase(eval_string(s))); +} + +void geas_implementation::print_eval(String s) { +	print_formatted(eval_string(s)); +} + +String geas_implementation::eval_string(String s) { +	String rv; +	uint i, j; +	bool do_print = (s.find('$') != -1); +	if (do_print) cerr << "eval_string (" << s << ")\n"; +	for (i = 0; i < s.length(); i ++) { +		//if (do_print) cerr << "e_s: i == " << i << ", s[i] == '" << s[i] << "'\n"; +		if (i + 1 < s.length() && s[i] == '#' && s[i + 1] == '@') { +			for (j = i + 1; j < s.length() && s[j] != '#'; j ++) +				; +			if (j == s.length()) { +				gi->debug_print("eval_string: Unmatched hash in " + s); +				break; +			} +			//cerr << "dereferencing " + s.substr (i+2, j-i-2) << endl; +			rv = rv + displayed_name(get_svar(s.substr(i + 2, j - i - 2))); +			i = j; +		} else if (s[i] == '#') { +			for (j = i + 1; j < s.length() && s[j] != '#'; j ++) +				; +			if (j == s.length()) { +				gi->debug_print("eval_string: Unmatched hash in " + s); +				break; +			} +			uint k; +			for (k = i + 1; k < j && s[k] != ':'; k ++) +				; +			if (k == j && j == i + 1) +				rv += "#"; +			else if (k == j) +				rv += get_svar(s.substr(i + 1, j - i - 1)); +			else { +				String propname = s.substr(k + 1, j - k - 1); +				if (s[i + 1] == '(') { +					if (s[k - 1] != ')') { +						gi->debug_print("e_s: Missing paren in '" + +						                s.substr(i, j - i) + "' of '" + s + "'"); +						break; +					} +					String objvar = s.substr(i + 2, k - i - 3); +					String objname = get_svar(objvar); +					/* +					cerr << "e_s: Getting prop [(" << objvar << ")] --> [" +					     << objname +					     << "]:[" << propname << "] for " << s << endl; +					*/ +					String tmp; +					if (get_obj_property(objname, propname, tmp)) +						rv += tmp; +					else +						gi->debug_print("e_s: Evaluating nonexistent object prop " +						                "{" + objname + "}:{" + propname + "}"); +				} else { +					String objname = s.substr(i + 1, k - i - 1); +					/* +					cerr << "e_s: Getting prop [" << objname << "]:[" +					     << propname << "] for " << s << endl; +					*/ +					String tmp; +					if (get_obj_property(objname, propname, tmp)) +						rv += tmp; +					else +						gi->debug_print("e_s: Evaluating nonexistent var " + objname); +				} +			} +			i = j; +		} else if (s[i] == '%') { +			for (j = i + 1; j < s.length() && s[j] != '%'; j ++) +				; +			if (j == s.length()) { +				gi->debug_print("e_s: Unmatched %s in " + s); +				break; +			} +			//cerr << "e_s: Getting ivar [" << s.substr (i+1, j-i-1) << "] for " << s << endl; +			if (j == i + 1) +				rv += "%"; +			else +				rv += string_int(get_ivar(s.substr(i + 1, j - i - 1))); +			i = j; +		} else if (s[i] == '$') { +			j = s.find('$', i + 1); +			/* +			for (j = i + 1; j < s.length() && s[j] != '$'; j ++) +			  { +			    cerr << "  In searching for partner, j == " << j +			   << ", s[j] == '" << s[j] << "', (s[j] == '$') == " +			   << ((s[j] == '$') ? "true" : "false") << "\n"; +			  } +			*/ +			//if (j == rv.size()) +			if (j == -1) { +				gi->debug_print("Unmatched $s in " + s); +				return rv + s.substr(i); +			} +			String tmp1 = s.substr(i + 1, j - i - 1); +			cerr << "e_s: first substr was '" << tmp1 << "'\n"; +			String tmp = eval_string(tmp1); +			//String tmp = eval_string (s.substr (i + 1, j - i - 2)); +			//cerr << "Taking substring of '" + s + "': '" + tmp + "'\n"; +			cerr << "e_s: eval substr " + s + "': '" + tmp + "'\n"; + +			String func_eval; + +			int paren_open, paren_close; +			if ((paren_open = tmp.find('(')) == -1) +				func_eval = run_function(tmp); +			else { +				paren_close = tmp.find(')', paren_open); +				if (paren_close == -1) +					gi->debug_print("No matching right paren in " + tmp); +				else { +					String f_name = tmp.substr(0, paren_open); +					String f_args = tmp.substr(paren_open + 1, +					                           paren_close - paren_open - 1); +					func_eval = run_function(f_name, split_f_args(f_args)); +				} +			} +			rv = rv + func_eval; +			i = j; +		} else +			rv += s[i]; +	} + + +	//cerr << "eval_string (" << s << ") -> <" << rv << ">\n"; +	return rv; +} + +void geas_implementation::tick_timers() { +	if (!state.running) +		return; +	//cerr << "tick_timers()\n"; +	for (uint i = 0; i < state.timers.size(); i ++) { +		TimerRecord &tr = state.timers[i]; +		//cerr << "  Examining " << tr << "\n"; +		if (tr.is_running) { +			//cerr << "    Advancing " << tr.name << ": " << tr.timeleft +			//     << " / " << tr.interval << "\n"; +			if (tr.timeleft != 0) +				tr.timeleft --; +			else { +				tr.is_running = false; +				tr.timeleft = tr.interval; +				const GeasBlock *gb = gf.find_by_name("timer", tr.name); +				if (gb != NULL) { +					//cout << "Running it!\n"; +					String tok, line; +					uint c1, c2; +					for (uint j = 0; j < gb->data.size(); j ++) { +						line = gb->data[j]; +						tok = first_token(line, c1, c2); +						// SENSITIVE? +						if (tok == "action") { +							run_script(line.substr(c2)); +							break; +						} +					} +				} +			} +		} +	} +} + +/*********************************** + *                                 * + *                                 * + *                                 * + * GeasInterface related functions * + *                                 * + *                                 * + *                                 * + ***********************************/ + + +GeasResult GeasInterface::print_formatted(String s, bool with_newline) { +	unsigned int i, j; + +	//cerr << "print_formatted (" << s << ", " << with_newline << ")" << endl; + +	for (i = 0; i < s.length(); i ++) { +		//std::cerr << "i == " << i << std::endl; +		if (s[i] == '|') { +			// changed indicated whether cur_style has been changed +			// and update_style should be called at the end. +			// it is true unless cleared (by |n or |w). +			bool changed = true; +			j = i; +			i ++; +			if (i == s.length()) +				continue; + +			// Are the | codes case-sensitive? +			switch (s[i]) { +			case 'u': +				cur_style.is_underlined = true; +				break; +			case 'i': +				cur_style.is_italic     = true; +				break; +			case 'b': +				cur_style.is_bold       = true; +				break; +			case 'c': +				i ++; + +				if (i == s.length()) { +					clear_screen(); +					break; +				} + +				switch (s[i]) { +				case 'y': +					cur_style.color = "#ffff00"; +					break; +				case 'g': +					cur_style.color = "#00ff00"; +					break; +				case 'l': +					cur_style.color = "#0000ff"; +					break; +				case 'r': +					cur_style.color = "#ff0000"; +					break; +				case 'b': +					cur_style.color = ""; +					break; + +				default: +					clear_screen(); +					--i; +				} +				break; + +			case 's': { +				i ++; +				if (i == s.length() || !(s[i] >= '0' && s[i] <= '9')) +					continue; +				i ++; +				if (i == s.length() || !(s[i] >= '0' && s[i] <= '9')) +					continue; + +				int newsize = parse_int(s.substr(j, i - j)); +				if (newsize > 0) +					cur_style.size = newsize; +				else +					cur_style.size = default_size; +			} +			break; + +			case 'j': +				i ++; + +				if (i == s.length() || +				        !(s[i] == 'l' || s[i] == 'c' || s[i] == 'r')) +					continue; +				if (s[i] == 'l') cur_style.justify = JUSTIFY_LEFT; +				else if (s[i] == 'r') cur_style.justify = JUSTIFY_RIGHT; +				else if (s[i] == 'c') cur_style.justify = JUSTIFY_CENTER; +				break; + +			case 'n': +				print_newline(); +				changed = false; +				break; + +			case 'w': +				wait_keypress(""); +				changed = false; +				break; + +			case 'x': +				i ++; + +				if (s[i] == 'b') +					cur_style.is_bold = false; +				else if (s[i] == 'u') +					cur_style.is_underlined = false; +				else if (s[i] == 'i') +					cur_style.is_italic = false; +				else if (s[i] == 'n' && i + 1 == s.length()) +					changed = with_newline = false; +				break; + +			default: +				cerr << "p_f: Fallthrough " << s[i] << endl; +				changed = false; +			} +			if (changed) +				update_style(); +		} else { +			for (j = i; i != s.length() && s[i] != '|'; i ++) +				; +			print_normal(s.substr(j, i - j)); +			if (s[i] == '|') +				-- i; +		} +	} +	if (with_newline) +		print_newline(); +	return r_success; +} + +void GeasInterface::set_default_font_size(String size) { +	default_size = parse_int(size); +} + +void GeasInterface::set_default_font(String font) { +	default_font = font; +} + +bool GeasInterface::choose_yes_no(String question) { +	Common::Array<String> v; +	v.push_back("Yes"); +	v.push_back("No"); +	return (make_choice(question, v) == 0); +} + +} // End of namespace Quest +} // End of namespace Glk diff --git a/engines/glk/quest/geas_runner.h b/engines/glk/quest/geas_runner.h new file mode 100644 index 0000000000..bc6d858d5b --- /dev/null +++ b/engines/glk/quest/geas_runner.h @@ -0,0 +1,220 @@ +/* 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_QUEST_GEAS_RUNNER +#define GLK_QUEST_GEAS_RUNNER + +#include "glk/quest/string.h" +#include "common/array.h" +#include "common/stream.h" + +namespace Glk { +namespace Quest { + +typedef Common::Array<String> vstring; +typedef Common::Array<vstring> v2string; + +enum geas_justification { JUSTIFY_LEFT, JUSTIFY_RIGHT, JUSTIFY_CENTER }; + +struct GeasFontStyle { +	bool is_underlined, is_italic, is_bold; +	String color, font; +	int size; +	geas_justification justify; + +	GeasFontStyle() : is_underlined(false), is_italic(false), is_bold(false), +		color(""), font(""), size(10), justify(JUSTIFY_LEFT) {} +}; + +class GeasFontStyleCompare { +public: +	int operator()(const GeasFontStyle &a, const GeasFontStyle &b) { +		if (a.size           !=  b.size)           return a.size < b.size; +		if (a.is_underlined  !=  b.is_underlined)  return a.is_underlined; +		if (a.is_bold        !=  b.is_bold)        return a.is_bold; +		if (a.is_italic      !=  b.is_italic)      return a.is_italic; +		if (a.color          !=  b.color)          return a.color < b.color; +		if (a.justify        !=  b.justify)        return a.justify < b.justify; +		return 0; +	} +}; + +Common::WriteStream &operator<< (Common::WriteStream &o, const GeasFontStyle &gfs); + +enum GeasResult { +	r_success, +	r_failure, +	r_not_supported +}; + +/*  Callback object used to pass information from GeasCore + *  to the interface objects + */ +class GeasInterface { +private: +	GeasFontStyle cur_style; +	String default_font; +	int default_size; +	//string fgcolor, bgcolor; + +public: +	/* Takes 1 argument, a string with Quest markup +	 * Will output it to the user interface +	 * If the with_newline flag is set, it will print a newline afterwords +	 *     unless the string ends in "|xn" +	 */ +	GeasResult print_formatted(String s, bool with_newline = true); + +	/* Takes one argument; that string is printed without interpretation +	 * Must be implemented +	 * Called by print_formatted and by Geas directly. +	 */ +	virtual GeasResult print_normal(const String &s) = 0; + +	virtual GeasResult print_newline() = 0; + +protected: + +	void update_style() { +		set_style(cur_style); +	} + +	/* Changes style of output text. +	 * Need not be implemented +	 * Only called by update_style() +	 */ +	virtual GeasResult set_style(const GeasFontStyle &) { +		return r_not_supported; +	} + +public: +	virtual String absolute_name(String rel_name, String parent) const = 0; +	virtual String get_file(const String &filename) const = 0; +	virtual void debug_print(const String &s) { +		warning("%s", s.c_str()); +	} +	virtual GeasResult wait_keypress(String) { +		return r_not_supported; +	} +	virtual GeasResult pause(int msec) { +		return r_not_supported; +	} +	virtual GeasResult clear_screen() { +		return r_not_supported; +	} + +	//virtual GeasResult set_foreground (string) { return r_not_supported; } +	//virtual GeasResult set_background (string) { return r_not_supported; } +	virtual void set_foreground(String) = 0; +	virtual void set_background(String) = 0; +	void set_default_font_size(String s); +	void set_default_font(String s); + +	/* Unsure what arguments this will take. +	 * May also add animated, persistent, close image +	 */ +	virtual GeasResult show_image(String filename, String resolution, +	                              String caption, ...) { +		return r_not_supported; +	} + +	/* Again, unsure what arguments to give +	 * May add sound type +	 * If sync is true, do not return until file ends +	 * If filename is "", stop playing sounds. +	 */ +	virtual GeasResult play_sound(String filename, bool looped, bool sync) { +		return r_not_supported; +	} + +	/* Asks the user to type a free format string +	 */ +	virtual String get_string() = 0; + +	/* Presents a list with header 'info', and prompts the user to +	 * choose one item from 'choices'. +	 * returns the index chosen. +	 */ +	virtual uint make_choice(String info, Common::Array<String> choices) = 0; + +	/* Asks the user a yes/no question +	 * (If not overridden, this has an implementation that uses make_choice() +	 */ +	virtual bool choose_yes_no(String question); + +	/* args holds arguments sent to program. +	 * if active is true, geas should retain focus +	 * returns -   0 if disallowed +	 *         -   1 if succeeded +	 *         -   2 if file not found +	 *         -   3 if it couldn't find a program to run it +	 *         -   4 if it ran out of memory +	 */ +	virtual int shell(Common::Array<String> args, bool active) { +		return 0; +	} + +	/* say the argument using text-to-speech +	 */ +	virtual GeasResult speak(String) { +		return r_not_supported; +	} + +	virtual ~GeasInterface() {} + +	/* This is a notification that some object has changed, and +	 * the interpreter may want to update the inventory or room object +	 * listings. +	 */ +	virtual void update_sidebars() { } +}; + + +/* Callback for passing information from the UI to the execution core + */ +class GeasRunner { +protected: +	GeasInterface *gi; + +public: +	GeasRunner(GeasInterface *_gi) : gi(_gi) {} + +	virtual bool is_running() const = 0; +	virtual String get_banner() = 0; +	virtual void run_command(String) = 0; + +	virtual v2string get_inventory() = 0; +	virtual v2string get_room_contents() = 0; +	virtual vstring  get_status_vars() = 0; +	virtual Common::Array<bool> get_valid_exits() = 0; + +	virtual void tick_timers() = 0; + +	virtual ~GeasRunner() {  } +	virtual void set_game(const String &fname) = 0; +	static GeasRunner *get_runner(GeasInterface *gi); +}; + +} // End of namespace Quest +} // End of namespace Glk + +#endif diff --git a/engines/glk/quest/geas_state.cpp b/engines/glk/quest/geas_state.cpp new file mode 100644 index 0000000000..985b4e935e --- /dev/null +++ b/engines/glk/quest/geas_state.cpp @@ -0,0 +1,356 @@ +/* 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/quest/geas_state.h" +#include "glk/quest/geas_runner.h" +#include "glk/quest/geas_util.h" +#include "glk/quest/read_file.h" +#include "glk/quest/streams.h" + +namespace Glk { +namespace Quest { + +Common::WriteStream &operator<< (Common::WriteStream &o, const StringMap &m) { +	for (StringMap::iterator i = m.begin(); i != m.end(); ++i) +		o << (*i)._key << " -> " << (*i)._value << "\n"; + +	return o; +} + +class GeasOutputStream { +	Common::WriteStream *_ws; +public: +	GeasOutputStream &put(const String &s) { +		_ws->writeString(s); +		_ws->writeByte(0); +		return *this; +	} +	GeasOutputStream &put(char ch) { +		_ws->writeByte(ch); +		return *this; +	} +	GeasOutputStream &put(int i) { +		Common::String s = Common::String::format("%d", i); +		_ws->writeString(s); +		_ws->writeByte(0); +		return *this; +	} +	GeasOutputStream &put(uint i) { +		Common::String s = Common::String::format("%u", i); +		_ws->writeString(s); +		_ws->writeByte(0); +		return *this; +	} +	GeasOutputStream &put(unsigned long i) { +		Common::String s = Common::String::format("%lu", i); +		_ws->writeString(s); +		_ws->writeByte(0); +		return *this; +	} + +	void write_out(const String &gameName, const String &saveName) { +#ifdef TODO +		ofstream ofs; +		ofs.open(savename.c_str()); +		if (!ofs.is_open()) +			throw String("Unable to open \"" + savename + "\""); +		ofs << "QUEST300" << char(0) << gamename << char(0); +		String tmp = o.str(); +		for (uint i = 0; i < tmp.size(); i ++) +			ofs << char (255 - tmp[i]); +		cerr << "Done writing save game\n"; +#else +		error("TODO"); +#endif +	} +}; + +template <class T> void write_to(GeasOutputStream &gos, const Common::Array<T> &v) { +	gos.put(v.size()); +	for (uint i = 0; i < v.size(); i ++) +		write_to(gos, v[i]); +} + +void write_to(GeasOutputStream &gos, const PropertyRecord &pr) { +	gos.put(pr.name).put(pr.data); +} + +void write_to(GeasOutputStream &gos, const ObjectRecord &pr) { +	gos.put(pr.name).put(char(pr.hidden ? 0 : 1)) +	.put(char(pr.invisible ? 0 : 1)).put(pr.parent); +} + +void write_to(GeasOutputStream &gos, const ExitRecord &er) { +	gos.put(er.src).put(er.dest); +} + +void write_to(GeasOutputStream &gos, const TimerRecord &tr) { +	gos.put(tr.name).put(tr.is_running ? 0 : 1).put(tr.interval) +	.put(tr.timeleft); +} + + +void write_to(GeasOutputStream &gos, const SVarRecord &svr) { +	gos.put(svr.name); +	gos.put(svr.max()); +	for (uint i = 0; i < svr.size(); i ++) +		gos.put(svr.get(i)); +} + +void write_to(GeasOutputStream &gos, const IVarRecord &ivr) { +	gos.put(ivr.name); +	gos.put(ivr.max()); +	for (uint i = 0; i < ivr.size(); i ++) +		gos.put(ivr.get(i)); +} + +void write_to(GeasOutputStream &gos, const GeasState &gs) { +	gos.put(gs.location); +	write_to(gos, gs.props); +	write_to(gos, gs.objs); +	write_to(gos, gs.exits); +	write_to(gos, gs.timers); +	write_to(gos, gs.svars); +	write_to(gos, gs.ivars); +} + +void save_game_to(String gamename, String savename, const GeasState &gs) { +	GeasOutputStream gos; +	write_to(gos, gs); +	gos.write_out(gamename, savename); +} + +GeasState::GeasState(GeasInterface &gi, const GeasFile &gf) { +	running = false; + +	cerr << "GeasState::GeasState()" << endl; +	for (uint i = 0; i < gf.size("game"); i ++) { +		//const GeasBlock &go = gf.game[i]; +		//register_block ("game", "game"); +		ObjectRecord data; +		data.name = "game"; +		data.parent = ""; +		data.hidden = false; +		data.invisible = true; +		objs.push_back(data); +	} + +	cerr << "GeasState::GeasState() done setting game" << endl; +	for (uint i = 0; i < gf.size("room"); i ++) { +		const GeasBlock &go = gf.block("room", i); +		ObjectRecord data; +		//data.name = go.lname; +		data.name = go.name; +		data.parent = ""; +		data.hidden = data.invisible = true; +		//register_block (data.name, "room"); +		objs.push_back(data); +	} + +	cerr << "GeasState::GeasState() done setting rooms" << endl; +	for (uint i = 0; i < gf.size("object"); i++) { +		const GeasBlock &go = gf.block("object", i); +		ObjectRecord data; +		//data.name = go.lname; +		data.name = go.name; +		if (go.parent == "") +			data.parent = ""; +		else +			//data.parent = lcase (param_contents (go.parent)); +			data.parent = param_contents(go.parent); +		//register_block (data.name, "object"); +		data.hidden = data.invisible = false; +		objs.push_back(data); +	} + +	cerr << "GeasState::GeasState() done setting objects" << endl; +	for (uint i = 0; i < gf.size("timer"); i ++) { +		const GeasBlock &go = gf.block("timer", i); +		//cerr << "GS::GS: Handling timer " << go << "\n"; +		TimerRecord tr; +		String interval = "", status = ""; +		for (uint j = 0; j < go.data.size(); j ++) { +			String line = go.data[j]; +			uint c1, c2; +			String tok = first_token(line, c1, c2); +			if (tok == "interval") { +				tok = next_token(line, c1, c2); +				if (!is_param(tok)) +					gi.debug_print(nonparam("interval", line)); +				else +					interval = param_contents(tok); +			} else if (tok == "enabled" || tok == "disabled") { +				if (status != "") +					gi.debug_print("Repeated status for timer"); +				else +					status = tok; +			} else if (tok == "action") { +			} else { +				gi.debug_print("Bad timer line " + line); +			} +		} +		//tr.name = go.lname; +		tr.name = go.name; +		tr.is_running = (status == "enabled"); +		tr.interval = tr.timeleft = parse_int(interval); +		//register_block (tr.name, "timer"); +		timers.push_back(tr); +	} + +	cerr << "GeasState::GeasState() done with timers" << endl; +	for (uint i = 0; i < gf.size("variable"); i ++) { +		const GeasBlock &go(gf.block("variable", i)); +		cerr << "GS::GS: Handling variable #" << i << ": " << go << endl; +		String vartype; +		String value; +		for (uint j = 0; j < go.data.size(); j ++) { +			String line = go.data[j]; +			cerr << "   Line #" << j << " of var: \"" << line << "\"" << endl; +			uint c1, c2; +			String tok = first_token(line, c1, c2); +			if (tok == "type") { +				tok = next_token(line, c1, c2); +				if (tok == "") +					gi.debug_print(String("Missing variable type in ") +					               + string_geas_block(go)); +				else if (vartype != "") +					gi.debug_print(String("Redefining var. type in ") +					               + string_geas_block(go)); +				else if (tok == "numeric" || tok == "String") +					vartype = tok; +				else +					gi.debug_print(String("Bad var. type ") + line); +			} else if (tok == "value") { +				tok = next_token(line, c1, c2); +				if (!is_param(tok)) +					gi.debug_print(String("Expected parameter in " + line)); +				else +					value = param_contents(tok); +			} else if (tok == "display" || tok == "onchange") { +			} else { +				gi.debug_print(String("Bad var. line: ") + line); +			} +		} +		if (vartype == "" || vartype == "numeric") { +			IVarRecord ivr; +			//ivr.name = go.lname; +			ivr.name = go.name; +			ivr.set(0, parse_int(value)); +			ivars.push_back(ivr); +			//register_block (ivr.name, "numeric"); +		} else { +			SVarRecord svr; +			//svr.name = go.lname; +			svr.name = go.name; +			svr.set(0, value); +			svars.push_back(svr); +			//register_block (svr.name, "String"); +		} +	} +	//cerr << obj_types << endl; +	cerr << "GeasState::GeasState() done with variables" << endl; +} + +Common::WriteStream &operator<< (Common::WriteStream &o, const PropertyRecord &pr) { +	o << pr.name << ", data == " << pr.data; +	return o; +} + +Common::WriteStream &operator<< (Common::WriteStream &o, const ObjectRecord &objr) { +	o << objr.name << ", parent == " << objr.parent; +	if (objr.hidden) +		o << ", hidden"; +	if (objr.invisible) +		o << ", invisible"; +	return o; +} + +Common::WriteStream &operator<< (Common::WriteStream &o, const ExitRecord er) { +	return o << er.src << ": " << er.dest; +} + +Common::WriteStream &operator<< (Common::WriteStream &o, const TimerRecord &tr) { +	return o << tr.name << ": " << (tr.is_running ? "" : "not ") << "running (" +	       << tr.timeleft << " // " << tr.interval << ")"; +} + +Common::WriteStream &operator<< (Common::WriteStream &o, const SVarRecord &sr) { +	o << sr.name << ": "; +	if (sr.size() == 0) +		o << "(empty)"; +	else if (sr.size() <= 1) +		o << "<" << sr.get(0) << ">"; +	else +		for (uint i = 0; i < sr.size(); i ++) { +			o << i << ": <" << sr.get(i) << ">"; +			if (i + 1 < sr.size()) +				o << ", "; +		} +	return o; +} + +Common::WriteStream &operator<< (Common::WriteStream &o, const IVarRecord &ir) { +	o << ir.name << ": "; +	if (ir.size() == 0) +		o << "(empty)"; +	else if (ir.size() <= 1) +		o << ir.get(0); +	else +		for (uint i = 0; i < ir.size(); i ++) { +			o << i << ": " << ir.get(i); +			if (i + 1 < ir.size()) +				o << ", "; +		} +	return o; +} + +Common::WriteStream &operator<< (Common::WriteStream &o, const GeasState &gs) { +	o << "location == " << gs.location << "\nprops: \n"; + +	for (uint i = 0; i < gs.props.size(); i ++) +		o << "    " << i << ": " << gs.props[i] << "\n"; + +	o << "objs:\n"; +	for (uint i = 0; i < gs.objs.size(); i ++) +		o << "    " << i << ": " << gs.objs[i] << "\n"; + +	o << "exits:\n"; +	for (uint i = 0; i < gs.exits.size(); i ++) +		o << "    " << i << ": " << gs.exits[i] << "\n"; + +	o << "timers:\n"; +	for (uint i = 0; i < gs.timers.size(); i ++) +		o << "    " << i << ": " << gs.timers[i] << "\n"; + +	o << "String variables:\n"; +	for (uint i = 0; i < gs.svars.size(); i ++) +		o << "    " << i << ": " << gs.svars[i] << "\n"; + +	o << "integer variables:\n"; +	for (uint i = 0; i < gs.svars.size(); i ++) +		o << "    " << i << ": " << gs.svars[i] << "\n"; + +	return o; +} + +} // End of namespace Quest +} // End of namespace Glk diff --git a/engines/glk/quest/geas_state.h b/engines/glk/quest/geas_state.h new file mode 100644 index 0000000000..70365f1fd8 --- /dev/null +++ b/engines/glk/quest/geas_state.h @@ -0,0 +1,171 @@ +/* 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_QUEST_GEAS_STATE +#define GLK_QUEST_GEAS_STATE + +#include "glk/quest/string.h" +#include "common/array.h" +#include "common/stream.h" + +namespace Glk { +namespace Quest { + +struct PropertyRecord { +	String name, data; +	PropertyRecord(String in_name, String in_data) : name(in_name), data(in_data) {} +}; + +struct ObjectRecord { +	String name, parent; +	bool hidden, invisible; + +	//ObjectRecord (String in_name, String in_parent) : name (in_name), parent (in_parent), hidden (false), concealed (false) {} + +}; + +struct ExitRecord { +	String src, dest; +	ExitRecord(String in_src, String in_dest) : src(in_src), dest(in_dest) {} +}; + +struct TimerRecord { +	String name; +	bool is_running; +	uint interval, timeleft; +}; + +struct SVarRecord { +private: +	Common::Array<String> data; +public: +	String name; + +	SVarRecord() {} +	SVarRecord(String in_name) : name(in_name) { +		set(0, ""); +	} +	uint size() const { +		return data.size(); +	} +	uint max() const { +		return size() - 1; +	} +	void set(uint i, String val) { +		if (i >= size()) data.resize(i + 1); +		data[i] = val; +	} +	String get(uint i) const { +		if (i < size()) return data[i]; +		return "!"; +	} +	void set(String val) { +		data[0] = val; +	} +	String get() const { +		return data[0]; +	} +}; + +struct IVarRecord { +private: +	Common::Array<int> data; +public: +	String name; + +	IVarRecord() {} +	IVarRecord(String in_name) : name(in_name) { +		set(0, 0); +	} +	uint size() const { +		return data.size(); +	} +	uint max() const { +		return size() - 1; +	} +	void set(uint i, int val) { +		if (i >= size()) data.resize(i + 1); +		data[i] = val; +	} +	int get(uint i) const { +		if (i < size()) return data[i]; +		else return -32767; +	} +	void set(int val) { +		data[0] = val; +	} +	int get() const { +		return data[0]; +	} +}; + +struct GeasFile; +class GeasInterface; + +struct GeasState { +	//private: +	//std::auto_ptr<GeasFile> gf; + +public: +	bool running; +	String location; +	Common::Array<PropertyRecord> props; +	Common::Array<ObjectRecord> objs; +	Common::Array<ExitRecord> exits; +	Common::Array<TimerRecord> timers; +	Common::Array<SVarRecord> svars; +	Common::Array<IVarRecord> ivars; +	//std::map <String, String> obj_types; + +	//void register_block (String blockname, String blocktype); + +	GeasState() {} +	//GeasState (GeasRunner &, const GeasFile &); +	GeasState(GeasInterface &, const GeasFile &); +	/* +	bool has_svar (string s) { for (uint i = 0; i < svars.size(); i ++) if (svars[i].name == s) return true; } +	uint find_svar (string s) { for (uint i = 0; i < svars.size(); i ++) if (svars[i].name == s) return i; svars.push_back (SVarRecord (s)); return svars.size() - 1;} +	string get_svar (string s, uint index) { if (!has_svar(s)) return "!"; return svars[find_svar(s)].get(index); } +	string get_svar (string s) { return get_svar (s, 0); } + +	bool has_ivar (string s) { for (uint i = 0; i < ivars.size(); i ++) if (ivars[i].name == s) return true; } +	uint find_ivar (string s) { for (uint i = 0; i < ivars.size(); i ++) if (ivars[i].name == s) return i; ivars.push_back (IVarRecord (s)); return ivars.size() - 1;} +	int get_ivar (string s, uint index) { if (!has_ivar(s)) return -32767; return ivars[find_ivar(s)].get(index); } +	int get_ivar (string s) { return get_ivar (s, 0); } +	*/ +}; + +void save_game_to(String gamename, String savename, const GeasState &gs); + +Common::WriteStream &operator<< (Common::WriteStream &o, const StringMap &m); +Common::WriteStream &operator<< (Common::WriteStream &o, const PropertyRecord &pr); +Common::WriteStream &operator<< (Common::WriteStream &o, const ObjectRecord &objr); +Common::WriteStream &operator<< (Common::WriteStream &o, const ExitRecord er); +Common::WriteStream &operator<< (Common::WriteStream &o, const TimerRecord &tr); +Common::WriteStream &operator<< (Common::WriteStream &o, const SVarRecord &sr); +Common::WriteStream &operator<< (Common::WriteStream &o, const IVarRecord &ir); +Common::WriteStream &operator<< (Common::WriteStream &o, const GeasState &gs); + +} // End of namespace Quest +} // End of namespace Glk + +#endif diff --git a/engines/glk/quest/geas_util.cpp b/engines/glk/quest/geas_util.cpp new file mode 100644 index 0000000000..bc5eaac177 --- /dev/null +++ b/engines/glk/quest/geas_util.cpp @@ -0,0 +1,245 @@ +/* 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/quest/geas_util.h" +#include "glk/quest/geas_file.h" +#include "glk/quest/streams.h" +#include "glk/quest/string.h" + +namespace Glk { +namespace Quest { + +int eval_int(String s) { +	cerr << "eval_int (" << s << ")" << endl; + +	uint index = 0, index2; +	String tmp; +	while (index < s.length() && Common::isSpace(s[index])) { +		cerr << "  index == " << index << endl; +		index ++; +	} +	if (index == s.length() || !Common::isDigit(s[index])) { +		cerr << "Failed to match, returning 0" << endl; +		return 0; +	} +	for (index2 = index; index2 < s.length() && Common::isDigit(s[index2]); index2 ++) { +		cerr << "  index2 == " << index2 << endl; +	} +	//; +	tmp = s.substr(index, index2 - index); +	cerr << "tmp == < " << tmp << ">" << endl; + +	//cerr << "index == " << index << ", index2 == " << index2 +	//     << ", tmp == " << tmp << endl; + +	int arg1 = atoi(tmp.c_str()); +	cerr << "arg1 == " << arg1 << endl; +	index = index2; +	while (index < s.length() && Common::isSpace(s[index])) +		++ index; +	if (index == s.length()) +		return arg1; + +	//cerr << "index == " << index << ", s.length() == " << s.length() << endl; + +	char symbol = s[index]; + +	//cerr << "symbol == " << symbol << "; find --> " +	//     << String("+-*/").find (symbol) << endl; + +	if (String("+-*/").find(symbol) == String::npos) +		return arg1; + +	++ index; +	while (index < s.length() && Common::isSpace(s[index])) +		++ index; +	if (index == s.length() || ! Common::isDigit(s[index])) { +		if (symbol == '*') +			return 0; +		return arg1; +	} +	index2 = index + 1; +	while (index2 < s.length() && Common::isDigit(s[index2])) +		++ index2; +	tmp = s.substr(index, index2 - index); +	int arg2 = atoi(tmp.c_str()); + +	switch (symbol) { +	case '+': +		return arg1 + arg2; +	case '-': +		return arg1 - arg2; +	case '*': +		return arg1 * arg2; +	case '/': +		return arg1 / arg2; +		// TODO: division should use accountant's round +	} +	return 0; +} + +String trim_braces(String s) { +	if (s.length() > 1 && s[0] == '[' && s[s.length() - 1] == ']') +		return s.substr(1, s.length() - 2); +	else +		return s; +} + +bool is_param(String s) { +	return s.length() > 1 && s[0] == '<' && s[s.length() - 1] == '>'; +} + +String param_contents(String s) { +	//cerr << "param_contents (" << s << ")" << endl; +	assert(is_param(s)); +	return s.substr(1, s.length() - 2); +} + +String nonparam(String type, String var) { +	return "Non-parameter for " + type + " in \"" + var + "\""; +} + +//ostream &operator << (ostream &o, const GeasBlock &gb) { return o; } +//String trim (String s, trim_modes) { return s; } + +String string_geas_block(const GeasBlock &gb) { +	ostringstream oss; +	oss << gb;  // temporary removed TODO +	return oss.str(); +} + + +bool starts_with(String a, String b) { +	return (a.length() >= b.length()) && (a.substr(0, b.length()) == b); +} +bool ends_with(String a, String b) { +	return (a.length() >= b.length()) && +	       (a.substr(a.length() - b.length(), b.length()) == b); +} + +bool starts_with_i(String a, String b) { +	return (a.length() >= b.length()) && ci_equal(a.substr(0, b.length()), b); +	//  return starts_with (lcase(a), lcase(b)); +} +bool ends_with_i(String a, String b) { +	return (a.length() >= b.length()) && +	       ci_equal(a.substr(a.length() - b.length(), b.length()), b); +	//return ends_with (lcase(a), lcase(b)); +} + +String pcase(String s) { +	if (s.length() == 0) +		return s; +	if (Common::isLower(s[0])) +		s[0] = toupper(s[0]); +	return s; +} + +String ucase(String s) { +	for (uint i = 0; i < s.length(); i ++) +		s[i] = toupper(s[i]); +	return s; +} + +// There's a good chance s is already all-lowercase, in which case +// the test will avoid making a copy +String lcase(String s) { +	for (uint i = 0; i < s.length(); i ++) +		if (Common::isUpper(s[i])) +			s[i] = tolower(s[i]); +	return s; +} + +Common::Array<String> split_param(String s) { +	Common::Array<String> rv; +	uint c1 = 0, c2; + +	for (;;) { +		c2 = s.find(';', c1); +		if (c2 == -1) { +			rv.push_back(s.substr(c1).trim()); +			return rv; +		} +		rv.push_back(s.substr(c1, c2 - c1).trim()); +		c1 = c2 + 1; +	} +} + +Common::Array<String> split_f_args(String s) { +	Common::Array<String> rv = split_param(s); +	for (uint i = 0; i < rv.size(); i ++) { +		String tmp = rv[i]; +		if (tmp[0] == '_') +			rv[i][0] = ' '; +		if (tmp[tmp.length() - 1] == '_') +			rv[i][tmp.length() - 1] = ' '; +	} +	return rv; +} + +void show_split(String s) { +	Common::Array<String> tmp = split_param(s); +	cerr << "Splitting <" << s << ">: "; +	for (uint i = 0; i < tmp.size(); i ++) +		cerr << "<" << tmp[i] << ">, "; +	cerr << "\n"; +} + +Logger::Nullstreambuf Logger::cnull; + +Logger::Logger() : logfilestr_(NULL) { //, cerrbuf_(NULL) { +/* +	cerr.flush(); + +	const char *const logfile = getenv("GEAS_LOGFILE"); +	if (logfile) { +		ofstream *filestr = new ofstream(logfile); +		if (filestr->fail()) +			delete filestr; +		else { +			logfilestr_ = filestr; +			cerrbuf_ = cerr.rdbuf(filestr->rdbuf()); +		} +	} + +	if (!cerrbuf_) +		cerrbuf_ = cerr.rdbuf(&cnull); +		*/ +} + +Logger::~Logger() { +	/* +	cerr.flush(); + +	cerr.rdbuf(cerrbuf_); +	cerrbuf_ = NULL; + +	if (logfilestr_) { +		logfilestr_->close(); +		delete logfilestr_; +		logfilestr_ = NULL; +	} +	*/ +} + +} // End of namespace Quest +} // End of namespace Glk diff --git a/engines/glk/quest/geas_util.h b/engines/glk/quest/geas_util.h new file mode 100644 index 0000000000..2cedadde02 --- /dev/null +++ b/engines/glk/quest/geas_util.h @@ -0,0 +1,97 @@ +/* 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_QUEST_GEAS_UTIL +#define GLK_QUEST_GEAS_UTIL + +#include "glk/quest/read_file.h" +#include "common/stream.h" + +namespace Glk { +namespace Quest { + +typedef Common::Array<String> vstring; + +inline int parse_int(const String &s) { +	return atoi(s.c_str()); +} + +vstring split_param(String s); +vstring split_f_args(String s); + +bool is_param(String s); +String param_contents(String s); + +String nonparam(String, String); + +String string_geas_block(const GeasBlock &); + +bool starts_with(String, String); +bool ends_with(String, String); + +String string_int(int i); + +String trim_braces(String s); + +int eval_int(String s); + +String pcase(String s); +String ucase(String s); +String lcase(String s); + + +template<class T> Common::WriteStream &operator<<(Common::WriteStream &o, Common::Array<T> v) { +	o << "{ '"; +	for (uint i = 0; i < v.size(); i ++) { +		o << v[i]; +		if (i + 1 < v.size()) +			o << "', '"; +	} +	o << "' }"; +	return o; +} + +template <class KEYTYPE, class VALTYPE> +bool has(Common::HashMap<KEYTYPE, VALTYPE, Common::IgnoreCase_Hash, Common::IgnoreCase_EqualTo> m, KEYTYPE key) { +	return m.contains(key); +}; + +class Logger { +public: +	Logger(); +	~Logger(); + +private: +	class Nullstreambuf : public Common::WriteStream { +		virtual uint32 write(const void *dataPtr, uint32 dataSize) { return dataSize; } +		virtual int32 pos() const { return 0; } +	}; + +	Common::WriteStream *logfilestr_; +//	std::streambuf *cerrbuf_; +	static Nullstreambuf cnull; +}; + +} // End of namespace Quest +} // End of namespace Glk + +#endif diff --git a/engines/glk/quest/limit_stack.h b/engines/glk/quest/limit_stack.h new file mode 100644 index 0000000000..75cad4401d --- /dev/null +++ b/engines/glk/quest/limit_stack.h @@ -0,0 +1,103 @@ +/* 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_QUEST_LIMIT_STACK +#define GLK_QUEST_LIMIT_STACK + +namespace Glk { +namespace Quest { + +template <class T> class LimitStack { +	uint stack_size, cur_ptr, end_ptr; +	Common::Array<T> data; +	//bool last_push; + + +	uint dofwd(uint i) const { +		i ++; +		return i == stack_size ? 0 : i; +	} +	uint dobwd(uint i) const { +		return (i == 0 ? stack_size : i) - 1; +	} +	void fwd(uint &i) const { +		i = dofwd(i); +	} +	void bwd(uint &i) const { +		i = dobwd(i); +	} + +	/* +	void fwd (uint &i) { i ++; if (i == stack_size) i = 0; } +	void bwd (uint &i) { i = (i == 0 ? stack_size : i) - 1; } +	uint dofwd (uint i) { uint rv = i; fwd(rv); return rv; } +	uint dobwd (uint i) { uint rv = i; bwd(rv); return rv; } +	*/ + +public: +	LimitStack(uint size) : stack_size(size), cur_ptr(0), end_ptr(size - 1), data(Common::Array<T> (size)) { } + +	void push(T &item) { +		if (cur_ptr == end_ptr) +			fwd(end_ptr); +		data[cur_ptr] = item; +		fwd(cur_ptr); +	} + +	T &pop() { +		assert(!is_empty()); +		bwd(cur_ptr); +		return data[cur_ptr]; +	} + +	bool is_empty() { +		return dobwd(cur_ptr) == end_ptr; +	} + +	uint size() { +		if (cur_ptr > end_ptr) +			return cur_ptr - end_ptr - 1; +		else +			return (cur_ptr + stack_size) - end_ptr - 1; +	} + +	void dump(Common::WriteStream &o) { +		o << size() << ": < "; +		for (uint i = dobwd(cur_ptr); i != end_ptr; bwd(i)) +			o << data[i] << " "; +		o << ">"; +	} + +	T &peek() { +		return data[dobwd(cur_ptr)]; +	} +}; + +template<class T> Common::WriteStream &operator<< (Common::WriteStream &o, LimitStack<T> st) { +	st.dump(o); +	return o; +} + +} // End of namespace Quest +} // End of namespace Glk + +#endif diff --git a/engines/glk/quest/quest.cpp b/engines/glk/quest/quest.cpp new file mode 100644 index 0000000000..93de43f6d7 --- /dev/null +++ b/engines/glk/quest/quest.cpp @@ -0,0 +1,149 @@ +/* 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/quest/quest.h" +#include "glk/quest/geas_glk.h" +#include "glk/quest/geas_glk.h" +#include "glk/quest/streams.h" +#include "common/config-manager.h" +#include "common/translation.h" + +namespace Glk { +namespace Quest { + +Quest *g_vm; + +void Quest::runGame() { +	// Check for savegame +	_saveSlot = ConfMan.hasKey("save_slot") ? ConfMan.getInt("save_slot") : -1; + +	if (!initialize()) { +		GUIErrorMessage(_("Could not start Quest game")); +		return; +	} + +	playGame(); + +	deinitialize(); +} + +void Quest::playGame() { +	char cur_buf[1024]; +	char buf[200]; + +	GeasRunner *gr = GeasRunner::get_runner(new GeasGlkInterface()); +	gr->set_game(String(getFilename().c_str())); +	banner = gr->get_banner(); +	draw_banner(); + +	while (gr->is_running()) { +		if (inputwin != mainglkwin) +			glk_window_clear(inputwin); +		else +			glk_put_cstring("\n"); + +		sprintf(cur_buf, "> "); +		glk_put_string_stream(inputwinstream, cur_buf); + +		glk_request_line_event(inputwin, buf, (sizeof buf) - 1, 0); + +		event_t ev; +		ev.type = evtype_None; + +		while (ev.type != evtype_LineInput) { +			glk_select(&ev); + +			switch (ev.type) { +			case evtype_LineInput: +				if (ev.window == inputwin) { +					String cmd = String(buf, ev.val1); +					if (inputwin == mainglkwin) +						ignore_lines = 2; +					gr->run_command(cmd); +				} +				break; + +			case evtype_Timer: +				gr->tick_timers(); +				break; + +			case evtype_Arrange: +			case evtype_Redraw: +				draw_banner(); +				break; +			} +		} +	} +} + +bool Quest::initialize() { +	glk_stylehint_set(wintype_TextBuffer, style_User2, stylehint_ReverseColor, 1); + +	// Open the main window +	mainglkwin = glk_window_open(0, 0, 0, wintype_TextBuffer, 1); +	if (!mainglkwin) +		return false; +	glk_set_window(mainglkwin); + +	glk_stylehint_set(wintype_TextGrid, style_User1, stylehint_ReverseColor, 1); +	bannerwin = glk_window_open(mainglkwin, +		winmethod_Above | winmethod_Fixed, +		1, wintype_TextGrid, 0); + +	if (use_inputwindow) +		inputwin = glk_window_open(mainglkwin, +			winmethod_Below | winmethod_Fixed, +			1, wintype_TextBuffer, 0); +	else +		inputwin = NULL; + +	if (!inputwin) +		inputwin = mainglkwin; + +	inputwinstream = glk_window_get_stream(inputwin); + +	if (!glk_gestalt(gestalt_Timer, 0)) { +		const char *err = "\nNote -- The underlying Glk library does not support" +			" timers.  If this game tries to use timers, then some" +			" functionality may not work correctly.\n\n"; +		glk_put_string(err); +	} + +	glk_request_timer_events(1000); +	ignore_lines = 0; + +	return true; +} + +void Quest::deinitialize() { +} + +Common::Error Quest::readSaveData(Common::SeekableReadStream *rs) { +	return Common::kNoError; +} + +Common::Error Quest::writeGameData(Common::WriteStream *ws) { +	return Common::kNoError; +} + +} // End of namespace Quest +} // End of namespace Glk diff --git a/engines/glk/quest/quest.h b/engines/glk/quest/quest.h new file mode 100644 index 0000000000..69aa2db094 --- /dev/null +++ b/engines/glk/quest/quest.h @@ -0,0 +1,92 @@ +/* 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_QUEST_QUEST +#define GLK_QUEST_QUEST + +#include "glk/glk_api.h" +#include "glk/quest/string.h" + +namespace Glk { +namespace Quest { + +/** + * Quest game interpreter + */ +class Quest : public GlkAPI { +private: +	int _saveSlot; +public: +	String banner; +private: +	/** +	 * Engine initialization +	 */ +	bool initialize(); + +	/** +	 * Engine cleanup +	 */ +	void deinitialize(); + +	/** +	 * Inner gameplay method +	 */ +	void playGame(); +public: +	/** +	 * Constructor +	 */ +	Quest(OSystem *syst, const GlkGameDescription &gameDesc) : GlkAPI(syst, gameDesc), _saveSlot(-1) { +		g_vm = this; +	} + +	/** +	 * Run the game +	 */ +	virtual void runGame() override; + +	/** +	 * Returns the running interpreter type +	 */ +	virtual InterpreterType getInterpreterType() const override { +		return INTERPRETER_QUEST; +	} + +	/** +	 * Load a savegame from the passed Quetzal file chunk stream +	 */ +	virtual Common::Error readSaveData(Common::SeekableReadStream *rs) override; + +	/** +	 * Save the game. The passed write stream represents access to the UMem chunk +	 * in the Quetzal save file that will be created +	 */ +	virtual Common::Error writeGameData(Common::WriteStream *ws) override; +}; + +extern Quest *g_vm; + +} // End of namespace Quest +} // End of namespace Glk + +#endif diff --git a/engines/glk/quest/read_file.cpp b/engines/glk/quest/read_file.cpp new file mode 100644 index 0000000000..c749bb2718 --- /dev/null +++ b/engines/glk/quest/read_file.cpp @@ -0,0 +1,1043 @@ +/* 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/quest/read_file.h" +#include "glk/quest/geas_util.h" +#include "glk/quest/reserved_words.h" +#include "glk/quest/geas_runner.h" +#include "glk/quest/streams.h" +#include "common/util.h" + +namespace Glk { +namespace Quest { + +String next_token(String full, uint &tok_start, uint &tok_end, bool cvt_paren) { +	tok_start = tok_end; +	while (tok_start < full.size() && Common::isSpace(full[tok_start])) +		++ tok_start; +	if (tok_start >= full.size()) { +		tok_start = tok_end = full.size(); +		//tok_start = tok_end = String::npos; +		return ""; +	} +	tok_end = tok_start + 1; +	if (full[tok_start] == '{' || full[tok_start] == '}') +		/* brace is a token by itself */; +	else if (full[tok_start] == '<') { +		while (tok_end < full.size() && full [tok_end] != '>') +			++ tok_end; +		if (full[tok_end] == '>') +			++ tok_end; +	} else if (cvt_paren && full[tok_start] == '(') { +		uint depth = 1; +		/* +		while (tok_end < full.size() && full [tok_end] != ')') +		++ tok_end; +		     if (full[tok_end] == ')') +		++ tok_end; +		     */ +		do { +			if (full[tok_end] == '(') +				++ depth; +			else if (full[tok_end] == ')') +				-- depth; +			++ tok_end; +		} while (tok_end < full.size() && depth > 0); +	} else +		while (tok_end < full.size() && !Common::isSpace(full[tok_end])) +			++ tok_end; +	return full.substr(tok_start, tok_end - tok_start); +} + +String first_token(String s, uint &t_start, uint &t_end) { +	t_end = 0; +	return next_token(s, t_start, t_end); +} + +String nth_token(String s, int n) { +	uint x1, x2 = 0; +	String rv; +	do +		rv = next_token(s, x1, x2); +	while (-- n > 0); +	return rv; +} + +String get_token(String s, bool cvt_paren) { +	uint x1, x2 = 0; +	return next_token(s, x1, x2, cvt_paren); +} + +bool find_token(String s, String tok, int &tok_start, int &tok_end, bool cvt_paren) { +	uint copy_start, copy_end; +	copy_end = tok_end; + +	do { +		String tmp = next_token(s, copy_start, copy_end, cvt_paren); +		if (tmp == tok) { +			tok_start = copy_start; +			tok_end = copy_end; +			return true; +		} +	} while (copy_end < s.size()); + +	return false; +} + +bool is_define(String s) { +	return get_token(s) == "define"; +} + +bool is_define(String s, String t) { +	uint t1, t2 = 0; +	return next_token(s, t1, t2) == "define" && +	       next_token(s, t1, t2) == t; +} + +bool is_start_textmode(String s) { +	uint start_char, end_char = 0; +	if (next_token(s, start_char, end_char) != "define") return false; +	String tmp = next_token(s, start_char, end_char); +	// SENSITIVE? +	return tmp == "text" || tmp == "synonyms"; +} + +bool is_end_define(String s) { +	uint start_char, end_char = 0; +	// SENSITIVE? +	return (next_token(s, start_char, end_char) == "end" && +	        next_token(s, start_char, end_char) == "define"); +} + + +extern Common::Array<String> split_lines(String data); + +reserved_words dir_tag_property("north", "south", "east", "west", "northwest", "northeast", "southeast", "southwest", "up", "down", "out", (char *) NULL); + +void GeasFile::read_into(const Common::Array<String> &in_data, +                         String in_parent, uint cur_line, bool recurse, +                         const reserved_words &props, +                         const reserved_words &actions) { +	//cerr << "r_i: Reading in from" << cur_line << ": " << in_data[cur_line] << endl; +	//output.push_back (GeasBlock()); +	//GeasBlock &out_block = output[output.size() - 1]; +	int blocknum = blocks.size(); +	blocks.push_back(GeasBlock()); +	GeasBlock &out_block = blocks[blocknum]; + +	Common::Array<String> &out_data = out_block.data; +	out_block.parent = in_parent; +	uint t1, t2; +	String line = in_data[cur_line]; +	// SENSITIVE? +	assert(first_token(line, t1, t2) == "define"); +	String blocktype = out_block.blocktype = next_token(line, t1, t2);  // "object", or the like +	//cerr << "r_i: Pushing back block of type " << blocktype << "\n"; +	type_indecies[blocktype].push_back(blocknum); +	String name = next_token(line, t1, t2);  // "<itemname>", or the like + +	// SENSITIVE? +	if (blocktype == "game") { +		out_block.name = "game"; +		out_data.push_back("game name " + name); +	} else if (is_param(name)) +		out_block.name = param_contents(name); +	else if (name != "") +		error("Expected parameter; %s found instead.", name.c_str()); +	//out_block.lname = lcase (out_block.nname); + +	// apparently not all block types are unique ... TODO which? +	// SENSITIVE? +	if (blocktype == "room" || blocktype == "object" || blocktype == "game") +		register_block(out_block.name, blocktype); +	//register_block (out_block.lname, blocktype); + +	// SENSITIVE? +	if (blocktype == "room" && find_by_name("type", "defaultroom")) +		out_data.push_back("type <defaultroom>"); +	// SENSITIVE? +	if (blocktype == "object" && find_by_name("type", "default")) +		out_data.push_back("type <default>"); + +	cur_line ++; +	uint depth = 1; +	while (cur_line < in_data.size() && depth > 0) { +		line = in_data[cur_line]; +		if (recurse && is_define(line)) +			++ depth; +		else if (is_end_define(in_data[cur_line])) +			-- depth; +		else if (depth == 1) { +			//cerr << "r_i: Processing line #" << cur_line << ": " << line << endl; +			//String dup_data = ""; +			String tok = first_token(line, t1, t2); +			String rest = next_token(line, t1, t2); + +			//cerr << "r_i: tok == '" << tok << "', props[tok] == " << props[tok] +			//     << ", actions[tok] == " << actions[tok] << "\n"; + +			if (props[tok] && dir_tag_property[tok]) +				out_data.push_back(line); + +			if (props[tok] && rest == "") { +				//cerr << "r_i: Handling as props <tok>\n"; +				line = "properties <" + tok + ">"; +			} else if (props[tok] && is_param(rest)) { +				//cerr << "r_i: Handling as props <tok = ...>\n"; +				line = "properties <" + tok + "=" + param_contents(rest) + ">"; +			} else if (actions[tok] && +			           (tok == "use" || tok == "give" || !is_param(rest))) { +				//cerr << "r_i: Handling as action '" << tok << "'\n"; +				// SENSITIVE? +				if (tok == "use") { +					//cerr << "r_i: ********** Use line: <" + line + "> ---> "; +					String lhs = "action <use "; +					// SENSITIVE? +					if (rest == "on") { +						rest = next_token(line, t1, t2); +						String rhs = line.substr(t2); +						// SENSITIVE? +						if (rest == "anything") +							line = lhs + "on anything> " + rhs; +						// SENSITIVE? +						else if (is_param(rest)) +							line = lhs + "on " + param_contents(rest) + "> " + rhs; +						else { +							//cerr << "r_i: Error handling '" << line << "'" << endl; +							line = "ERROR: " + line; +						} +					} +					// SENSITIVE? +					else if (rest == "anything") +						line = lhs + "anything> " + line.substr(t2); +					else if (is_param(rest)) +						line = lhs + param_contents(rest) + "> " + line.substr(t2); +					else +						line = "action <use> " + line.substr(t1); +					//cerr << "r_i: <" << line << ">\n"; +				} +				// SENSITIVE? +				else if (tok == "give") { +					String lhs = "action <give "; +					// SENSITIVE? +					if (rest == "to") { +						rest = next_token(line, t1, t2); +						String rhs = line.substr(t2); +						// SENSITIVE? +						if (rest == "anything") +							line = lhs + "to anything> " + rhs; +						else if (is_param(rest)) +							line = lhs + "to " + param_contents(rest) + "> " + rhs; +						else { +							cerr << "Error handling '" << line << "'" << endl; +							line = "ERROR: " + line; +						} +					} +					// SENSITIVE? +					else if (rest == "anything") +						line = lhs + "anything> " + line.substr(t2); +					else if (is_param(rest)) +						line = lhs + param_contents(rest) + "> " + line.substr(t2); +					else +						line = "action <give> " + line.substr(t1); +				} else +					line = "action <" + tok + "> " + line.substr(t1); +			} +			//else +			//  cerr << "Handling as ordinary line\n"; + +			// recalculating tok because it might have changed +			/* TODO: Make sure this only happens on object-type blocks */ +			tok = first_token(line, t1, t2); +			// SENSITIVE? +			if (tok == "properties") { +				rest = next_token(line, t1, t2); +				if (is_param(rest)) { +					vstring items = split_param(param_contents(rest)); +					for (uint i = 0; i < items.size(); i ++) +						out_data.push_back("properties <" + +						                   static_eval(items[i]) + ">"); +				} else +					out_data.push_back("ERROR " + line); +			} else +				out_data.push_back(line); + +			//if (dup_data != "") +			//  out_data.push_back (dup_data); +		} +		cur_line ++; +	} +} + +GeasFile::GeasFile(const Common::Array<String> &v, GeasInterface *_gi) : gi(_gi) { +	uint depth = 0; + +	String parentname, parenttype; + +	static String pass_names[] = { +		"game", "type", "room", "variable", "object", "procedure", +		"function", "selection", "synonyms", "text", "timer" +	}; + +	reserved_words recursive_passes("game", "room", (char *) NULL), +	               object_passes("game", "room", "objects", (char *) NULL); + + +	//Common::Array <GeasBlock> outv; +	for (uint pass = 0; pass < sizeof(pass_names) / sizeof(*pass_names); +	        pass ++) { +		String this_pass = pass_names[pass]; +		bool recursive = recursive_passes[this_pass]; +		//bool is_object = object_passes[this_pass]; + +		reserved_words actions((char *) NULL), props((char *) NULL); +		// SENSITIVE? +		if (this_pass == "room") { +			props = reserved_words("look", "alias", "prefix", "indescription", "description", "north", "south", "east", "west", "northeast", "northwest", "southeast", "southwest", "out", "up", "down", (char *) NULL); +			actions = reserved_words("description", "script", "north", "south", "east", "west", "northeast", "northwest", "southeast", "southwest", "out", "up", "down", (char *) NULL); +		} +		// SENSITIVE? +		else if (this_pass == "object") { +			props = reserved_words("look", "examine", "speak", "take", "alias", "prefix", "suffix", "detail", "displaytype", "gender", "article", "hidden", "invisible", (char *) NULL); +			actions = reserved_words("look", "examine", "speak", "take", "gain", "lose", "use", "give", (char *) NULL); +		} + +		depth = 0; +		for (uint i = 0; i < v.size(); i ++) +			if (is_define(v[i])) { +				++ depth; +				String blocktype = nth_token(v[i], 2); +				if (depth == 1) { +					parenttype = blocktype; +					parentname = nth_token(v[i], 3); + +					// SENSITIVE? +					if (blocktype == this_pass) +						read_into(v, "", i, recursive, props, actions); +				} else if (depth == 2 && blocktype == this_pass) { +					// SENSITIVE? +					if (this_pass == "object" && parenttype == "room") +						read_into(v, parentname, i, false, props, actions); +					// SENSITIVE? +					else if (this_pass == "variable" && parenttype == "game") +						read_into(v, "", i, false, props, actions); +				} +			} else if (is_end_define(v[i])) +				-- depth; +	} + +} + +bool decompile(String data, Common::Array<String> &rv); + +bool preprocess(Common::Array<String> v, String fname, Common::Array<String> &rv, GeasInterface *gi); + +GeasFile read_geas_file(GeasInterface *gi, const String &filename) { +	//return GeasFile (split_lines(gi->get_file(s)), gi); +	String file_contents = gi->get_file(filename); + +	if (file_contents == "") +		return GeasFile(); + +	Common::Array<String> data; +	bool success; + +	cerr << "Header is '" << file_contents.substr(0, 7) << "'.\n"; +	if (file_contents.size() > 8 && file_contents.substr(0, 7) == "QCGF002") { +		cerr << "Decompiling\n"; +		success = decompile(file_contents, data); +	} else { +		cerr << "Preprocessing\n"; +		success = preprocess(split_lines(file_contents), filename, data, gi); +	} + +	cerr << "File load was " << (success ? "success" : "failure") << "\n"; + +	if (success) +		return GeasFile(data, gi); + +	gi->debug_print("Unable to read file " + filename); +	return GeasFile(); +} + +Common::WriteStream &operator<<(Common::WriteStream &o, const GeasBlock &gb) { +	//o << "Block " << gb.blocktype << " '" << gb.nname; +	o << "Block " << gb.blocktype << " '" << gb.name; +	if (gb.parent != "") +		o << "' and parent '" << gb.parent; +	o << "'\n"; +	for (uint i = 0; i < gb.data.size(); i ++) +		o << "    " << gb.data[i] << "\n"; +	o << "\n"; +	return o; +} + +void print_vblock(Common::WriteStream &o, String blockname, const Common::Array<GeasBlock> &blocks) { +	o << blockname << ":\n"; +	for (uint i = 0; i < blocks.size(); i ++) +		o << "  " << blocks[i] << "\n"; +	o << "\n"; +} + +Common::WriteStream &operator<<(Common::WriteStream &o, const GeasFile &gf) { +	/* +	o << "Geas File\nThing-type blocks:\n"; +	for (uint i = 0; i < gf.things.size(); i ++) +	  o << gf.things[i]; +	o << "\nOther-type blocks:\n"; +	for (uint i = 0; i < gf.others.size(); i ++) +	  o << gf.others[i]; +	*/ +	o << "Geas File\n"; +	for (StringArrayIntMap::const_iterator i = gf.type_indecies.begin(); i != gf.type_indecies.end(); i ++) { +		o << "Blocks of type " << (*i)._key << "\n"; +		for (uint j = 0; j < (*i)._value.size(); j ++) +			o << gf.blocks[(*i)._value[j]]; +		o << "\n"; +	} + +	/* +	o << "Geas File\n"; +	print_vblock (o, "game",        gf.game); +	print_vblock (o, "rooms",       gf.rooms); +	print_vblock (o, "objects",     gf.objects); +	print_vblock (o, "text blocks", gf.textblocks); +	print_vblock (o, "functions",   gf.functions); +	print_vblock (o, "procedures",  gf.procedures); +	print_vblock (o, "types",       gf.types); +	print_vblock (o, "synonyms",    gf.synonyms); +	print_vblock (o, "timers",      gf.timers); +	print_vblock (o, "variables",   gf.variables); +	print_vblock (o, "choices",     gf.choices); +	*/ +	o << endl; +	return o; +} + + + + + +String compilation_tokens[256] = { +	"", "game", "procedure", "room", "object", "character", "text", "selection", +	"define", "end", "", "asl-version", "game", "version", "author", "copyright", +	"info", "start", "possitems", "startitems", "prefix", "look", "out", "gender", +	"speak", "take", "alias", "place", "east", "north", "west", "south", "give", +	"hideobject", "hidechar", "showobject", "showchar", "collectable", +	"collecatbles", "command", "use", "hidden", "script", "font", "default", +	"fontname", "fontsize", "startscript", "nointro", "indescription", +	"description", "function", "setvar", "for", "error", "synonyms", "beforeturn", +	"afterturn", "invisible", "nodebug", "suffix", "startin", "northeast", +	"northwest", "southeast", "southwest", "items", "examine", "detail", "drop", +	"everywhere", "nowhere", "on", "anything", "article", "gain", "properties", +	"type", "action", "displaytype", "override", "enabled", "disabled", +	"variable", "value", "display", "nozero", "onchange", "timer", "alt", "lib", +	"up", "down", "gametype", "singleplayer", "multiplayer", "", "", "", "", "", +	"", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", +	"", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", +	"", "", "", "", "", "", "", "", "", "", "", "do", "if", "got", "then", "else", +	"has", "say", "playwav", "lose", "msg", "not", "playerlose", "playerwin", +	"ask", "goto", "set", "show", "choice", "choose", "is", "setstring", +	"displaytext", "exec", "pause", "clear", "debug", "enter", "movechar", +	"moveobject", "revealchar", "revealobject", "concealchar", "concealobject", +	"mailto", "and", "or", "outputoff", "outputon", "here", "playmidi", "drop", +	"helpmsg", "helpdisplaytext", "helpclear", "helpclose", "hide", "show", +	"move", "conceal", "reveal", "numeric", "String", "collectable", "property", +	"create", "exit", "doaction", "close", "each", "in", "repeat", "while", +	"until", "timeron", "timeroff", "stop", "panes", "on", "off", "return", +	"playmod", "modvolume", "clone", "shellexe", "background", "foreground", +	"wait", "picture", "nospeak", "animate", "persist", "inc", "dec", "flag", +	"dontprocess", "destroy", "beforesave", "onload", "", "", "", "", "", "", +	"", "", "", "", "", "", "", "", "", "", "", "" +}; + + +bool decompile(String s, Common::Array<String> &rv) { +	String cur_line, tok; +	uint expect_text = 0, obfus = 0; +	unsigned char ch; + +	for (uint i = 8; i < s.size(); i ++) { +		ch = s[i]; +		if (obfus == 1 && ch == 0) { +			cur_line += "> "; +			obfus = 0; +		} else if (obfus == 1) +			cur_line += char (255 - ch); +		else if (obfus == 2 && ch == 254) { +			obfus = 0; +			cur_line += " "; +		} else if (obfus == 2) +			cur_line += ch; +		else if (expect_text == 2) { +			if (ch == 253) { +				expect_text = 0; +				rv.push_back(cur_line); +				cur_line = ""; +			} else if (ch == 0) { +				rv.push_back(cur_line); +				cur_line = ""; +			} else +				cur_line += char (255 - ch); +		} else if (obfus == 0 && ch == 10) { +			cur_line += "<"; +			obfus = 1; +		} else if (obfus == 0 && ch == 254) +			obfus = 2; +		else if (ch == 255) { +			if (expect_text == 1) +				expect_text = 2; +			rv.push_back(cur_line); +			cur_line = ""; +		} else { +			tok = compilation_tokens[ch]; +			if ((tok == "text" || tok == "synonyms" || tok == "type") && +			        cur_line == "define ") +				expect_text = 1; +			cur_line += tok + " "; +		} +	} +	rv.push_back(cur_line); + +	for (uint i = 0; i < rv.size(); i ++) +		cerr << "rv[" << i << "]: " << rv[i] << "\n"; + +	return true; +} + + + + + + + + +Common::Array<String> tokenize(String s) { +	uint tok_start, tok_end; +	String tok; +	Common::Array<String> rv; +	tok_end = 0; +	while (tok_end + 1 <= s.size()) +		rv.push_back(next_token(s, tok_start, tok_end)); +	return rv; +} + +String string_int(int i) { +	ostringstream o; +	o << i; +	return o.str(); +} + +void report_error(String s) { +	//cerr << s << endl; +	cerr << s << endl; +	throw s; +} + +Common::Array<String> split_lines(String data) { +	Common::Array <String> rv; +	String tmp; +	uint i = 0; +	while (i < data.size()) { +		//cerr << "data[" << i << "] == " << int(data[i]) << '\n'; + +		if (data[i] == '\n' || data[i] == '\r') { +			/* +			if (data[i] == '\n' && i < data.size() && data[i+1] == '\r') +			  ++ i; +			*/ +			if (tmp.size() > 0 && tmp[tmp.size() - 1] == '_') { +				//cerr << "Line with trailing underscores: " << tmp << '\n'; +				tmp.erase(tmp.size() - 1); +				if (tmp[tmp.size() - 1] == '_') +					tmp.erase(tmp.size() - 1); +				if (i < data.size() && data[i] == '\r' && data[i + 1] == '\n') +					++ i; +				++ i; +				//cerr << "   WSK: data[" << i<< "] == " << int(data[i]) << '\n'; +				while (i < data.size() && data[i] != '\r' && +				        data[i] != '\n' && Common::isSpace(data[i])) { +					//cerr << "   WS: data[" << i<< "] = " << int(data[i]) << '\n'; +					++ i; +				} +				//cerr << "   WS: data[" << i<< "] == " << int(data[i]) << '\n'; +				-- i; +			} else { +				//cerr << "Pushing back {<{" << tmp << "}>}\n"; +				rv.push_back(tmp); +				tmp = ""; +				if (i < data.size() && data[i] == '\r' && data[i + 1] == '\n') +					++ i; +			} +		} else +			tmp += data[i]; +		++ i; +	} +	if (tmp != "") +		rv.push_back(tmp); +	return rv; +} + +void show_tokenize(String s) { +	//cerr << "s_t: Tokenizing '" << s << "' --> " << tokenize(s) << endl; +} + +void say_push(const Common::Array<String> &v) { +	//cerr << "s_p: Pushing '" << v[v.size() - 1] << "'" << endl; +} + + + + + +//enum trim_modes { TRIM_SPACES, TRIM_UNDERSCORE, TRIM_BRACE }; + +//String trim (String s, trim_modes trim_mode = TRIM_SPACES) +String trim(String s, trim_modes trim_mode) { +	uint i, j; +	/* +	cerr << "Trimming (" << s << "): ["; +	for (i = 0; i < s.size(); i ++) +	  cerr << int (s[i]) << "(" << s[i] << "), "; +	cerr << "]\n"; +	*/ +	for (i = 0; i < s.size() && Common::isSpace(s[i]); i ++) +		; +	if (i == s.size()) return ""; +	if ((trim_mode == TRIM_UNDERSCORE   &&  s[i] == '_') || +	        (trim_mode == TRIM_BRACE        &&  s[i] == '[')) +		++ i; +	if (i == s.size()) return ""; +	for (j = s.size() - 1; Common::isSpace(s[j]); j --) +		; +	if ((trim_mode == TRIM_UNDERSCORE && i < s.size() && s[j] == '_') || +	        (trim_mode == TRIM_BRACE && i < s.size()      && s[j] == ']')) +		-- j; +	return s.substr(i, j - i + 1); +} + +/* bool is_balanced (String) + * + * Decides whether the String has balanced braces (and needn't be deinlined) + * - Track the nesting depth, starting at first lbrace to end of line + * - If it is ever at depth 0 before the end, it's balanced + * - Otherwise, it's unbalanced + */ +bool is_balanced(String str) { +	uint index = str.find('{'); +	if (index == -1) +		return true; +	int depth; +	for (depth = 1, index ++;  depth > 0 && index < str.size();  index ++) +		if (str[index] == '{') +			++ depth; +		else if (str[index] == '}') +			-- depth; +	return depth == 0; +} + +int count_depth(String str, int count) { +	//cerr << "count_depth (" << str << ", " << count << ")" << endl; +	uint index = 0; +	if (count == 0) +		index = str.find('{'); +	while (index < str.size()) { +		if (str[index] == '{') +			++ count; +		else if (str[index] == '}') +			-- count; +		//cerr << "    After char #" << index << ", count is " << count << endl; +		++ index; +	} +	//cerr << "returning " << count << endl; +	return count; +} + +void handle_includes(const Common::Array<String> &in_data, String filename, Common::Array<String> &out_data, GeasInterface *gi) { +	String line, tok; +	uint tok_start, tok_end; +	for (uint ln = 0; ln < in_data.size(); ln ++) { +		line = in_data[ln]; +		tok = first_token(line, tok_start, tok_end); +		if (tok == "!include") { +			tok = next_token(line, tok_start, tok_end); +			if (!is_param(tok)) { +				gi->debug_print("Expected parameter after !include"); +				continue; +			} +			//handle_includes (split_lines (gi->get_file (param_contents (tok))), out_data, gi); +			String newname = gi->absolute_name(param_contents(tok), filename); +			handle_includes(split_lines(gi->get_file(newname)), newname, out_data, gi); +		} else if (tok == "!QDK") { +			while (ln < in_data.size() && +			        first_token(in_data[ln], tok_start, tok_end) != "!end") +				++ ln; +		} else +			out_data.push_back(line); +	} +} + +bool preprocess(Common::Array<String> v, String fname, Common::Array<String> &rv, +                GeasInterface *gi) { +	//cerr << "Before preprocessing:\n" << v << "\n\n" << endl; +	/* +	  cerr << "Before preprocessing:\n"; +	for (uint i = 0; i < v.size(); i ++) +	  cerr << i << ": " << v[i] << "\n"; +	cerr << "\n\n"; +	*/ + +	// TODO: Is it "!=" or "<>" or both, and if both, which has priority? +	static String comps[][2] = { +		{ "<>", "!=;" },  { "!=", "!=;" },  { "<=", "lt=;" },  { ">=", "gt=;" }, +		{ "<",  "lt;" },  { ">",  "gt;" },  { "=",  "" } +	}; + +	uint tok_start, tok_end; +	String tok; + +	// preprocessing step 0: +	// Loop through the lines.  Replace !include with appropriate text + +	Common::Array<String> v2; +	handle_includes(v, fname, v2, gi); +	v.clear(); + +	StringArrayStringMap addtos; +	for (uint line = 0; line < v2.size(); line ++) { +		//cerr << "Line #" << line << ", looking for addtos: " << v2[line] << endl; +		tok = first_token(v2[line], tok_start, tok_end); +		if (tok == "!addto") { +			tok = next_token(v2[line], tok_start, tok_end); +			if (!(tok == "game" || tok == "synonyms" || tok == "type")) { +				gi->debug_print("Error: Had addto for '" + tok + "'."); +				continue; +			} +			if (tok == "type") { +				String tmp = next_token(v2[line], tok_start, tok_end); +				if (!(tmp == "<default>" || tmp == "<defaultroom>")) { +					gi->debug_print("Error: Bad addto '" + v2[line] + "'\n"); +					continue; +				} +				tok = tok + " " + tmp; +			} +			++ line; // skip !addto +			while (line < v2.size() && +			        first_token(v2[line], tok_start, tok_end) != "!end") +				addtos[tok].push_back(v2[line ++]); +		} +	} +	for (uint line = 0; line < v2.size(); line ++) { +		tok = first_token(v2[line], tok_start, tok_end); +		if (tok == "!addto") { +			while (line < v2.size() && +			        first_token(v2[line], tok_start, tok_end) != "!end") +				line ++; +		} else { +			v.push_back(v2[line]); +			if (tok == "define") { +				// TODO: What if there's a !addto for a block other than +				// game, synonyms, default, defaultroom? +				// +				// Also, do the !addto'ed lines go at the front or end? +				tok = next_token(v2[line], tok_start, tok_end); +				if (tok == "type") +					tok = tok + " " + next_token(v2[line], tok_start, tok_end); + +				if (addtos.find(tok) != addtos.end()) { +					Common::Array<String> &lines = addtos[tok]; +					for (uint line2 = 0; line2 < lines.size(); line2 ++) +						v.push_back(lines[line2]); +					addtos.erase(tok); +				} +			} +		} +	} +	//cerr << "Done looking for addtos" << endl; +	v2.clear(); + +	for (StringArrayStringMap::iterator i = addtos.begin(); i != addtos.end(); i ++) { +		v.push_back("define " + i->_key); +		for (uint j = 0; j < i->_value.size(); j ++) +			v.push_back(i->_value[j]); +		v.push_back("end define"); +	} +	/* +	if (addtos.find("<default>") != addtos.end()) +	  { +	    Common::Array<String> &lines = addtos ["<default>"]; +	    v.push_back ("define type <default>"); +	    for (uint i = 0; i < lines.size(); i ++) +	v.push_back (lines[i]); +	    v.push_back ("end define"); +	  } + +	if (addtos.find("<defaultroom>") != addtos.end()) +	  { +	    Common::Array<String> &lines = addtos ["<defaultroom>"]; +	    v.push_back ("define type <defaultroom>"); +	    for (uint i = 0; i < lines.size(); i ++) +	v.push_back (lines[i]); +	    v.push_back ("end define"); +	  } +	*/ +	/* +	cerr << "Done special-pushing <default> and <defaultroom>\n"; +	cerr << "After includes & addtos:\n"; +	for (uint i = 0; i < v.size(); i ++) +	  cerr << i << ": " << v[i] << "\n"; +	cerr << "\n\n"; +	*/ + +	// Preprocessing step 1: +	// Loop through the lines.  Look for "if/and/or (.. <.. )" or the like +	// If there is such a pair, convert the second to "if/and/or is <..;lt;..>" + +	for (uint line = 0; line < v.size(); line ++) { +		tok_end = 0; +		while (tok_end < v[line].size()) { +			tok = next_token(v[line], tok_start, tok_end, false); +			if (tok == "if" || tok == "repeat" || tok == "until" || +			        tok == "and" || tok == "or") { +				tok = next_token(v[line], tok_start, tok_end, true); +				//cerr << "Checking for comparison {" << tok << "}\n"; +				if (tok.size() > 2 && tok[0] == '(' && +				        tok [tok.size() - 1] == ')') { +					//cerr << "   IT IS!\n"; +					tok = tok.substr(1, tok.size() - 2); +					String str = v[line]; +					int cmp_start; +					for (uint cmp = 0; cmp < ARRAYSIZE(comps); cmp ++) +						if ((cmp_start = tok.find(comps[cmp][0])) != -1) { +							uint cmp_end = cmp_start + comps[cmp][0].size(); +							//cerr << "Changed str from {" << str << "} to {"; +							str = str.substr(0, tok_start) + +							      "is <" + trim(tok.substr(0, cmp_start)) + ";" + +							      comps[cmp][1] + +							      trim(tok.substr(cmp_end)) + ">" + +							      str.substr(tok_end); +							//cerr << str << "}\n"; +							cmp = ARRAYSIZE(comps); +							v[line] = str; +							tok_end = tok_start; // old value of tok_end invalid +						} +				} +			} +		} +	} +	//cerr << "Done with pass 1!" << endl; + +	// Pass 2:  Extract comments from non-text blocks +	bool in_text_block = false; +	for (uint line = 0; line < v.size(); line ++) { +		//cerr << "Checking line '" << v[line] << "' for comments\n"; +		if (!in_text_block && is_start_textmode(v[line])) +			in_text_block = true; +		else if (in_text_block && is_end_define(v[line])) +			in_text_block = false; +		else if (!in_text_block) { +			//cerr << "  checking...\n"; +			uint start_ch = 0, end_ch = 0; +			while (start_ch < v[line].size()) +				if (next_token(v[line], start_ch, end_ch)[0] == '\'') { +					v[line] = v[line].substr(0, start_ch); +					//cerr << "  abbreviating to '" << v[line] << "'\n"; +					break; +				} +		} +	} +	//cerr << "Done with pass 2!" << endl; + +	/* There should be a pass 2.5: check that lbraces count equals +	 * rbrace count, but I'm skipping that +	 */ + +	// Pass 3: Deinline braces +	in_text_block = false; +	int int_proc_count = 0; +	for (uint line = 0; line < v.size(); line ++) { +		//cerr << "Pass 3, line #" << line << endl; +		String str = v[line]; +		if (!in_text_block && is_start_textmode(str)) +			in_text_block = true; +		else if (in_text_block && is_end_define(str)) +			in_text_block = false; +		else if (!is_balanced(str)) { +			//cerr << "...Special line!" << endl; +			uint init_size = v.size(); +			v.push_back("define procedure <!intproc" + +			            string_int(++int_proc_count) + ">"); +			//cerr << "Pushing back on v: '" << v[v.size()-1] << "'" << endl; + + +			uint tmp_index = str.find('{'); +			v2.push_back(trim(str.substr(0, tmp_index)) + +			             " do <!intproc" + string_int(int_proc_count) + "> "); +			//cerr << "Done with '" << v2[v2.size()-1] << "'" << endl; + +			{ +				/* +				String tmp_str = trim (str.substr (tmp_index + 1)); +				if (tmp_str != "") +				  { +				v.push_back (tmp_str); +				cerr << "Pushing back on v: '" << v[v.size()-1] << "'" << endl; +				  } +				*/ +				v.push_back(str.substr(tmp_index + 1)); +				//cerr << "Pushing back on v: '" << v[v.size()-1] << "'" << endl; +			} + +			int count = count_depth(str, 0); +			while (++ line < init_size && count != 0) { +				str = v[line]; +				count = count_depth(str, count); +				if (count != 0) { +					/* +					str = trim(str); +					if (str != "") +					  { +					    v.push_back (str); +					    cerr << "Pushing back on v: '" << str << "'" << endl; +					  } +					*/ +					v.push_back(str); +					//cerr << "Pushing back on v: '" << str << "'" << endl; +				} +			} +			if (count != 0) { +				report_error("Braces Unbalanced"); +				return false; +			} +			tmp_index = str.rfind('}'); +			{ +				/* +				String tmp2 = trim (str.substr (0, tmp_index)); +				if (tmp2 != "") +				  v.push_back (tmp2); +				*/ +				v.push_back(str.substr(0, tmp_index)); +				//cerr << "Pushing back on v: '" << v[v.size()-1] << "'" << endl; +			} + +			v.push_back("end define"); +			//cerr << "Pushing back on v: '" << v[v.size()-1] << "'" << endl; +			//cerr << "Done with special line" << endl; +			line --; +			continue; +			// The continue is to avoid the v2.push_back(...); +			// this block pushes stuff onto v2 on its own. +		} +		//cerr << "Done with '" << str << "'" << endl; +		v2.push_back(str); +	} +	//cerr << "Done with pass 3!" << endl; + +	/* Pass 4:  trim lines, drop blank lines, combine elses */ + +	in_text_block = false; +	int_proc_count = 0; +	for (uint line = 0; line < v2.size(); line ++) { +		String str = v2[line]; +		//cerr << "Pass 4, line #" << line << ", " << in_text_block << ": '" << str << "'\n"; +		if (!in_text_block && is_start_textmode(str)) +			in_text_block = true; +		else if (in_text_block && is_end_define(str)) +			in_text_block = false; +		else if (rv.size() > 0 && !in_text_block && get_token(str) == "else") { +			rv[rv.size() - 1] = rv[rv.size() - 1] + " " + trim(str); +			//cerr << "  Replacing else: " << rv[rv.size() - 1] << "\n"; +			continue; +		} +		if (!in_text_block) +			str = trim(str); +		if (in_text_block || str != "") +			rv.push_back(str); +		//if (rv.size() > 0) +		//  cerr << "  Result: " << rv[rv.size() - 1] << "\n"; +	} + +	/* +	cerr << "At end of procedure, v == " << v << "\n"; +	cerr << "and v2 == " << v2 << "\n"; +	cerr << "and rv == " << rv << "\n"; +	*/ +	//rv = v2; + +	/* +	cerr << "After all preprocessing\n"; +	for (uint i = 0; i < rv.size(); i ++) +	  cerr << i << ": " << rv[i] << "\n"; +	cerr << "\n\n"; +	*/ + +	return true; +	//return v2; +} + + +void show_find(String s, char ch) { +	cerr << "Finding '" << ch << "' in '" << s << "': " << s.find(ch) + 1 << endl; +} + +void show_trim(String s) { +	cerr << "Trimming '" << s << "': spaces (" << trim(s) +	     << "), underscores (" << trim(s, TRIM_UNDERSCORE) +	     << "), braces (" << trim(s, TRIM_BRACE) << ").\n"; + +	//cerr << "Trimming '" << s << "': '" << trim (s) << "'\n"; +} + +template<class T> Common::Array<T> &operator<< (Common::Array<T> &v, T val) { +	v.push_back(val); +	return v; +} + +template<class T> class makevector { +	Common::Array<T> dat; +public: +	makevector<T> &operator<<(T it) { +		dat.push_back(it); +		return *this; +	} +	operator Common::Array<T>() { +		return dat; +	} +}; + +Common::Array <String> split(String s, char ch) { +	uint i = 0, j; +	Common::Array<String> rv; +	do { +		//cerr << "split (" << s << "): i == " << i << ", j == " << j << endl; +		j = s.find(ch, i); +		if (i != j) +			rv.push_back(s.substr(i, j - i)); +		i = j + 1; +	} while (j < s.size()); + +	//cerr << rv << endl; +	return rv; +} + +} // End of namespace Quest +} // End of namespace Glk diff --git a/engines/glk/quest/read_file.h b/engines/glk/quest/read_file.h new file mode 100644 index 0000000000..062234d586 --- /dev/null +++ b/engines/glk/quest/read_file.h @@ -0,0 +1,49 @@ +/* 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_QUEST_READ_FILE +#define GLK_QUEST_READ_FILE + +#include "glk/quest/geas_file.h" +#include "glk/quest/string.h" +#include "common/array.h" + +namespace Glk { +namespace Quest { + +extern Common::Array<String> tokenize(String s); +extern String next_token(String full, uint &tok_start, uint &tok_end, bool cvt_paren = false); +extern String first_token(String s, uint &t_start, uint &t_end); +extern String nth_token(String s, int n); +extern String get_token(String s, bool cvt_paren = false); +extern bool find_token(String s, String tok, int &tok_start, int &tok_end, bool cvt_paren = false); +extern GeasFile read_geas_file(GeasInterface *gi, const String &filename); + +enum trim_modes { TRIM_SPACES, TRIM_UNDERSCORE, TRIM_BRACE }; +extern String trim(String, trim_modes mode = TRIM_SPACES); + +//Common::WriteStream &operator<< (Common::WriteStream &o, const Common::Array<String> &v); + +} // End of namespace Quest +} // End of namespace Glk + +#endif diff --git a/engines/glk/quest/reserved_words.h b/engines/glk/quest/reserved_words.h new file mode 100644 index 0000000000..5d736078b7 --- /dev/null +++ b/engines/glk/quest/reserved_words.h @@ -0,0 +1,84 @@ +/* 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_QUEST_RESERVED_WORDS +#define GLK_QUEST_RESERVED_WORDS + +#include "glk/quest/string.h" +#include "common/hashmap.h" +#include "common/stream.h" + +namespace Glk { +namespace Quest { + +class reserved_words { +private: +	StringBoolMap _data; + +public: +	/** +	 * Constructor +	 */ +	reserved_words(const char *c, ...) { +		va_list ap; +		va_start(ap, c); + +		while (c != nullptr) { +			_data[String(c)] = true; +			c = va_arg(ap, const char *); +		} +	} + +	/** +	 * Returns true if the passed string is a reserved word +	 */ +	bool operator[](const String &s) const { +		return has(s); +	} + +	/** +	 * Returns true if the passed string is a reserved word +	 */ +	bool has(const String &s) const { +		return _data.contains(s) && (*this)[s]; +	} + +	/** +	 * Dumps the list of reserved words to the passed output stream +	 */ +	void dump(Common::WriteStream &o) const { +		o.writeString("RW {"); + +		for (StringBoolMap::iterator i = _data.begin(); i != _data.end(); ++i) { +			if (i != _data.begin()) +				o.writeString(", "); +			o.writeString((*i)._key); +		} + +		o.writeString("}"); +	} +}; + +} // End of namespace Quest +} // End of namespace Glk + +#endif diff --git a/engines/glk/quest/streams.cpp b/engines/glk/quest/streams.cpp new file mode 100644 index 0000000000..37ea04298a --- /dev/null +++ b/engines/glk/quest/streams.cpp @@ -0,0 +1,78 @@ +/* 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/quest/streams.h" +#include "common/debug.h" +#include "common/str.h" + +namespace Glk { +namespace Quest { + +ConsoleStream *g_cerr; +const char endl = '\n'; + +void Streams::initialize() { +	g_cerr = new ConsoleStream(); +} + +void Streams::deinitialize() { +	delete g_cerr; +} + +uint32 ConsoleStream::write(const void *dataPtr, uint32 dataSize) { +	Common::String s((const char *)dataPtr, (const char *)dataPtr + dataSize); +	debug("%s", s.c_str()); +	return dataSize; +} + +/*--------------------------------------------------------------------------*/ + +String ostringstream::str() { +	return String((const char *)getData(), (const char *)getData() + size()); +} + +/*--------------------------------------------------------------------------*/ + +Common::WriteStream &operator<<(Common::WriteStream &ws, const String &s) { +	ws.writeString(s); +	return ws; +} + +Common::WriteStream &operator<<(Common::WriteStream &ws, char c) { +	ws.writeByte(c); +	return ws; +} + +Common::WriteStream &operator<<(Common::WriteStream &ws, int i) { +	Common::String s = Common::String::format("%d", i); +	ws.writeString(s); +	return ws; +} + +Common::WriteStream &operator<<(Common::WriteStream &ws, uint i) { +	Common::String s = Common::String::format("%u", i); +	ws.writeString(s); +	return ws; +} + +} // End of namespace Quest +} // End of namespace Glk diff --git a/engines/glk/quest/streams.h b/engines/glk/quest/streams.h new file mode 100644 index 0000000000..3a8d2a4a60 --- /dev/null +++ b/engines/glk/quest/streams.h @@ -0,0 +1,85 @@ +/* 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_QUEST_STREAMS +#define GLK_QUEST_STREAMS + +#include "glk/quest/string.h" +#include "common/memstream.h" +#include "common/stream.h" + +namespace Glk { +namespace Quest { + +/** + * Write stream wrapper around ScummVM debug calls. Can only handle text being written + */ +class ConsoleStream : public Common::WriteStream { +public: +	virtual uint32 write(const void *dataPtr, uint32 dataSize) override; +	virtual int32 pos() const override { return 0; } +}; + +class ostringstream : public Common::MemoryWriteStreamDynamic { +public: +	ostringstream() : Common::MemoryWriteStreamDynamic(DisposeAfterUse::YES) {} + +	String str(); +}; +class StringStream : public ostringstream { +public: +	StringStream &operator>>(String &rhs) { +		rhs = str(); +		return *this; +	} +}; + +/** + * Simple wrapper for managing streams initialization + */ +class Streams { +public: +	/** +	 * Initialization +	 */ +	static void initialize(); + +	/** +	 * Deinitialization +	 */ +	static void deinitialize(); +}; + +extern ConsoleStream *g_cerr; +extern const char endl; + +#define cerr (*g_cerr) + +Common::WriteStream &operator<<(Common::WriteStream &, const String &); +Common::WriteStream &operator<<(Common::WriteStream &, char); +Common::WriteStream &operator<<(Common::WriteStream &, int); +Common::WriteStream &operator<<(Common::WriteStream &, uint); + +} // End of namespace Quest +} // End of namespace Glk + +#endif diff --git a/engines/glk/quest/string.cpp b/engines/glk/quest/string.cpp new file mode 100644 index 0000000000..d6a17847c6 --- /dev/null +++ b/engines/glk/quest/string.cpp @@ -0,0 +1,99 @@ +/* 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/quest/string.h" + +namespace Glk { +namespace Quest { + +CI_EQUAL   ci_equal_obj; +CI_LESS    ci_less_obj; +CI_LESS_EQ ci_less_eq_obj; + +String operator+(const String &x, const String &y) { +	String temp(x); +	temp += y; +	return temp; +} + +String operator+(const char *x, const String &y) { +	String temp(x); +	temp += y; +	return temp; +} + +String operator+(const String &x, const char *y) { +	String temp(x); +	temp += y; +	return temp; +} + +String operator+(char x, const String &y) { +	String temp(x); +	temp += y; +	return temp; +} + +String operator+(const String &x, char y) { +	String temp(x); +	temp += y; +	return temp; +} + +/*--------------------------------------------------------------------------*/ + +// Code for testing case insensitively by John Harrison + +bool c_equal_i(char ch1, char ch2) { +	return tolower((unsigned char)ch1) == tolower((unsigned char)ch2); +} + +size_t ci_find(const String &str1, const String &str2) { +	const char *pos = strstr(str1.c_str(), str2.c_str()); +	return !pos ? String::npos : pos - str1.c_str(); +} + +static int my_stricmp(const String &s1, const String &s2) { +	return s1.compareToIgnoreCase(s2); +} + +bool ci_equal(const String &str1, const String &str2) { +	return my_stricmp(str1, str2) == 0; +} +bool ci_less_eq(const String &str1, const String &str2) { +	return my_stricmp(str1, str2) <= 0; +} +bool ci_less(const String &str1, const String &str2) { +	return my_stricmp(str1, str2) < 0; +} +bool ci_notequal(const String &str1, const String &str2) { +	return !ci_equal(str1, str2); +} +bool ci_gt_eq(const String &str1, const String &str2) { +	return my_stricmp(str1, str2) >= 0; +} +bool ci_gt(const String &str1, const String &str2) { +	return my_stricmp(str1, str2) > 0; +} + +} // End of namespace Quest +} // End of namespace Glk diff --git a/engines/glk/quest/string.h b/engines/glk/quest/string.h new file mode 100644 index 0000000000..43f6ab602b --- /dev/null +++ b/engines/glk/quest/string.h @@ -0,0 +1,134 @@ +/* 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_QUEST_STRING +#define GLK_QUEST_STRING + +#include "common/hashmap.h" +#include "common/hash-str.h" +#include "common/str.h" + +namespace Glk { +namespace Quest { + +class String; + +typedef Common::HashMap<String, String, Common::IgnoreCase_Hash, Common::IgnoreCase_EqualTo> StringMap; +typedef Common::HashMap<String, bool, Common::IgnoreCase_Hash, Common::IgnoreCase_EqualTo> StringBoolMap; +typedef Common::HashMap<String, Common::Array<int>, Common::IgnoreCase_Hash, Common::IgnoreCase_EqualTo> StringArrayIntMap; +typedef Common::HashMap<String, Common::Array<String>, Common::IgnoreCase_Hash, Common::IgnoreCase_EqualTo> StringArrayStringMap; + +class String : public Common::String { +public: +	String() : Common::String() {} +	String(const char *str) : Common::String(str) {} +	String(const char *str, uint32 len) : Common::String(str, len) {} +	String(const char *beginP, const char *endP) : Common::String(beginP, endP) {} +	String(const String &str) : Common::String(str) {} +	explicit String(char c) : Common::String(c) {} + +	char &operator[](int idx) { +		assert(_str && idx >= 0 && idx < (int)_size); +		return _str[idx]; +	} + +	inline uint length() const { +		return size(); +	} + +	String substr(size_t pos, size_t len) const { +		return String(c_str() + pos, c_str() + pos + len); +	} +	String substr(size_t pos) const { +		return String(c_str() + pos); +	} + +	int find(char c, int pos = 0) const { +		const char *p = strchr(c_str() + pos, c); +		return p ? p - c_str() : -1; +	} + +	int find(const Common::String &s, int pos = 0) const { +		const char *p = strstr(c_str() + pos, s.c_str()); +		return p ? p - c_str() : -1; +	} + +	int rfind(char c) const { +		const char *p = strrchr(c_str(), c); +		return p ? p - c_str() : -1; +	} + +	String trim() const { +		String result = *this; +		static_cast<Common::String>(result).trim(); +		return result; +	} +}; + +// Append two strings to form a new (temp) string +String operator+(const String &x, const String &y); + +String operator+(const char *x, const String &y); +String operator+(const String &x, const char *y); + +String operator+(const String &x, char y); +String operator+(char x, const String &y); + + +bool c_equal_i(char ch1, char ch2); +size_t ci_find(const String &str1, const String &str2); +bool   ci_equal(const String &str1, const String &str2); +bool   ci_less_eq(const String &str1, const String &str2); +bool   ci_less(const String &str1, const String &str2); +bool   ci_notequal(const String &str1, const String &str2); +bool   ci_gt_eq(const String &str1, const String &str2); +bool   ci_gt(const String &str1, const String &str2); + +class CI_EQUAL { +public: +	bool operator()(const String &str1, const String &str2) { +		return ci_equal(str1, str2); +	} +}; + +class CI_LESS_EQ { +public: +	bool operator()(const String &str1, const String &str2) { +		return ci_less_eq(str1, str2); +	} +}; + +class CI_LESS { +public: +	bool operator()(const String &str1, const String &str2) { +		return ci_less(str1, str2); +	} +}; + +extern CI_EQUAL   ci_equal_obj; +extern CI_LESS    ci_less_obj; +extern CI_LESS_EQ ci_less_eq_obj; + +} // End of namespace Quest +} // End of namespace Glk + +#endif diff --git a/engines/glk/quest/uncas.pl b/engines/glk/quest/uncas.pl new file mode 100644 index 0000000000..4e5dc8ee92 --- /dev/null +++ b/engines/glk/quest/uncas.pl @@ -0,0 +1,471 @@ +#!/usr/bin/perl + +############################################################################### +#                                                                             # +#  Copyright (C) 2006 by Mark J. Tilford                                      # +#                                                                             # +#  This file is part of Geas.                                                 # +#                                                                             # +#  Geas 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.                                        # +#                                                                             # +#  Geas 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 Geas; if not, write to the Free Software                        # +#  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA # +#                                                                             # +############################################################################### + +use strict; + +sub mtext { +	my $str = shift; +	my $rv = chr(254); +	foreach (split //, $str) { +	$rv . = chr(255 - ord $_); +} +return $rv . chr(0); +} + + +sub obfus { +	my $str = shift; +	my $rv = chr(10); +	foreach (split //, $str) { +	$rv . = chr(255 - ord $_); +} +return $rv . chr(0); +} + +my $is_raw = 0; + +my @hash_data = +    ([1, 'game'], [2, 'procedure'], [3, 'room'], [4, 'object'], +     [5, 'character'], [6, 'text'], [7, 'selection'], [8, 'define'], +     [9, 'end'], [11, 'asl-version'], [12, 'game'], [13, 'version'], +     [14, 'author'], [15, 'copyright'], [16, 'info'], [17, 'start'], +     [18, 'possitems'], [19, 'startitems'], [20, 'prefix'], [21, 'look'], +     [22, 'out'], [23, 'gender'], [24, 'speak'], [25, 'take'], [26, 'alias'], +     [27, 'place'], [28, 'east'], [29, 'north'], [30, 'west'], [31, 'south'], +     [32, 'give'], [33, 'hideobject'], [34, 'hidechar'], [35, 'showobject'], +     [36, 'showchar'], [37, 'collectable'], [38, 'collecatbles'], +     [39, 'command'], [40, 'use'], [41, 'hidden'], [42, 'script'], +     [43, 'font'], [44, 'default'], [45, 'fontname'], [46, 'fontsize'], +     [47, 'startscript'], [48, 'nointro'], [49, 'indescription'], +     [50, 'description'], [51, 'function'], [52, 'setvar'], [53, 'for'], +     [54, 'error'], [55, 'synonyms'], [56, 'beforeturn'], [57, 'afterturn'], +     [58, 'invisible'], [59, 'nodebug'], [60, 'suffix'], [61, 'startin'], +     [62, 'northeast'], [63, 'northwest'], [64, 'southeast'], +     [65, 'southwest'], [66, 'items'], [67, 'examine'], [68, 'detail'], +     [69, 'drop'], [70, 'everywhere'], [71, 'nowhere'], [72, 'on'], +     [73, 'anything'], [74, 'article'], [75, 'gain'], [76, 'properties'], +     [77, 'type'], [78, 'action'], [79, 'displaytype'], [80, 'override'], +     [81, 'enabled'], [82, 'disabled'], [83, 'variable'], [84, 'value'], +     [85, 'display'], [86, 'nozero'], [87, 'onchange'], [88, 'timer'], +     [89, 'alt'], [90, 'lib'], [91, 'up'], [92, 'down'], [93, 'gametype'], +     [94, 'singleplayer'], [95, 'multiplayer'], [150, 'do'], [151, 'if'], +     [152, 'got'], [153, 'then'], [154, 'else'], [155, 'has'], [156, 'say'], +     [157, 'playwav'], [158, 'lose'], [159, 'msg'], [160, 'not'], +     [161, 'playerlose'], [162, 'playerwin'], [163, 'ask'], [164, 'goto'], +     [165, 'set'], [166, 'show'], [167, 'choice'], [168, 'choose'], +     [169, 'is'], [170, 'setstring'], [171, 'displaytext'], [172, 'exec'], +     [173, 'pause'], [174, 'clear'], [175, 'debug'], [176, 'enter'], +     [177, 'movechar'], [178, 'moveobject'], [179, 'revealchar'], +     [180, 'revealobject'], [181, 'concealchar'], [182, 'concealobject'], +     [183, 'mailto'], [184, 'and'], [185, 'or'], [186, 'outputoff'], +     [187, 'outputon'], [188, 'here'], [189, 'playmidi'], [190, 'drop'], +     [191, 'helpmsg'], [192, 'helpdisplaytext'], [193, 'helpclear'], +     [194, 'helpclose'], [195, 'hide'], [196, 'show'], [197, 'move'], +     [198, 'conceal'], [199, 'reveal'], [200, 'numeric'], [201, 'string'], +     [202, 'collectable'], [203, 'property'], [204, 'create'], [205, 'exit'], +     [206, 'doaction'], [207, 'close'], [208, 'each'], [209, 'in'], +     [210, 'repeat'], [211, 'while'], [212, 'until'], [213, 'timeron'], +     [214, 'timeroff'], [215, 'stop'], [216, 'panes'], [217, 'on'], +     [218, 'off'], [219, 'return'], [220, 'playmod'], [221, 'modvolume'], +     [222, 'clone'], [223, 'shellexe'], [224, 'background'], +     [225, 'foreground'], [226, 'wait'], [227, 'picture'], [228, 'nospeak'], +     [229, 'animate'], [230, 'persist'], [231, 'inc'], [232, 'dec'], +     [233, 'flag'], [234, 'dontprocess'], [235, 'destroy'], +     [236, 'beforesave'], [237, 'onload']); + +my % tokens = (); +my % rtokens = (); +foreach (@hash_data) { +	if ($_->[0] >= 0 && $_->[0] < 256) { +		if ($_->[1] eq '') { +			$_->[1] = "[?" . $_->[0] . "?]"; +		} +		$rtokens{chr($_->[0])} = $_->[1]; +		$tokens{$_->[1]} = chr($_->[0]); +	} +} + +#print "{"; +#for (my $i = 0; $i < 256; $i ++) { +#    print "\"", $rtokens{chr($i)}, "\", "; +#} +#print "}\n"; +#die; + + +my % text_block_starters = map { $_ => 1 } qw / text synonyms type /; + +sub uncompile_fil { +	my $IFH; +	open($IFH, "<", $_[0]); +	binmode $IFH; +	$ / = undef; +	my $dat = < $IFH >; +#print "uncompile_fil : "; +#print "\$IFH == '$IFH',"; +#print "\$dat == '$dat'\n"; +	my @dat = split //, $dat; + + +	my $OFH; +	if (@_ == 1) { +		push @_, "&STDOUT"; +	} +	open $OFH, ">$_[1]" or die "Can't open '$_[1]' for output: $!"; + +	my @output = (); +	my $curline = ""; + + +	my $obfus = 0; +	my $expect_text == 0; +	my($ch, $chn, $tok); +	for (my $n = 8; $n < @dat; $n ++) { +		$ch = $dat[$n]; +		$chn = ord $ch; +		$tok = $rtokens{$ch}; +		if ($obfus == 1 && $chn == 0) { +#print $OFH "> "; +			$curline . = "> "; +			$obfus = 0; +		} +		elsif($obfus == 1) { +#print $OFH chr (255 - $chn); +			$curline . = chr(255 - $chn); +		} +		elsif($obfus == 2 && $chn == 254) { +			$obfus = 0; +#print $OFH " "; +			$curline . = " "; +		} +		elsif($obfus == 2) { +#print $OFH chr ($chn); +			$curline . = chr($chn); +		} +		elsif($expect_text == 2) { +			if ($chn == 253) { +				$expect_text = 0; +##print $OFH "\n"; +				push @output, $curline; +				$curline = ""; +			} +			elsif($chn == 0) { +#print $OFH "\n"; +				push @output, $curline; +				$curline = ""; +			} +			else { +#print $OFH chr (255 - $chn); +				$curline . = chr(255 - $chn); +			} +		} +		elsif($obfus == 0 && $chn == 10) { +#print $OFH "<"; +			$curline . = "<"; +			$obfus = 1; +		} +		elsif($obfus == 0 && $chn == 254) { +			$obfus = 2; +		} +		elsif($chn == 255) { +			if ($expect_text == 1) { +				$expect_text = 2; +			} +#print $OFH "\n"; +			push @output, $curline; +			$curline = ""; +		} +		else { +			if (($tok eq 'text' || $tok eq 'synonyms' || $tok eq 'type') && +			        $dat[$n - 1] eq chr(8)) { +				$expect_text = 1; +			} +#print $OFH "$tok "; +			$curline . = "$tok "; +		} +	} +	push @output, $curline; +	$curline = ""; + +	if (!$is_raw) { +		@output = pretty_print(reinline(@output)); +	} + +	foreach (@output) { +		print $OFH $_, "\r\n"; +	} +} + +sub list_grab_file { +	my $IFH; +	open($IFH, "<:crlf", $_[0]); +	my @rv = < $IFH >; +	chomp @rv; +	return @rv; +} + + +sub compile_fil { +	my @dat = list_grab_file($ARGV[0]); +	my $OFH; +	open $OFH, ">$ARGV[1]"; + +	print $OFH "QCGF200".chr(0); + +# Mode 0 == normal, mode 1 == block text +	my $mode = 0; +	for (my $n = 0; $n < @dat; $n ++) { +		my $l = $dat[$n]; +		while (substr($l, length($l) - 1, 1) eq '_' && $n < @dat) { +			$n ++; +			$l = substr($l, 0, length($l) - 1) . $dat[$n]; +		} +		if ($l = ~ / ^ !include *<([\S] *)> /) { +			@dat = (@dat[0..$n], list_grab_file($1), @dat[$n + 1..$#dat]); +		} +		elsif($l = ~ / ^ !addto.* /) { +# TODO +		} +		else { +			my $i = 0; +			my $max = length $l; +			my @l = split //, $l; + +			if ($mode == 1) { +				if ($l = ~ / ^\s * end\s * define\s*$ /) { +					print $OFH chr(253); +					$mode = 0; +# FALL THROUGH +				} else { +#print $OFH chr(0); +					foreach (split //, $l) { +					         print $OFH chr(255 - ord $_); +				} +			next; +		} +	} +	if ($l = ~ / ^\s*$ /) { +			next; +		} +		if ($l = ~ / ^\s * define\s * (text | synonyms | type) /) { +#[\s$] +			$mode = 1; +		} +		while ($i < $max) { +			while ($i <= $max && $l[$i] = ~ / \s /) { +				++ $i; +			} +			if ($i == $max) { +				next; +			} + +			my $j = $i + 1; +			if ($l[$i] eq '<') { +				while ($j < $max && $l[$j] ne '>') { +					++ $j; +				} +				if ($l[$j] eq '>') { +					print $OFH obfus(substr($l, $i + 1, $j - $i - 1)); +					$i = $j + 1; +					next; +				} +				$j = $i + 1; +				while ($j < $max && $l[$j] ne ' ') { +					++ $j; +				} +				print $OFH chr(254). substr($l, $i + 1, $j - $i - 1). chr(0); +				$i = $j + 1; +				next; +			} +			while ($j < $max && $l[$j] ne ' ') { +				++ $j; +			} +			my $str = substr($l, $i, $j - $i); +			if (defined $tokens{$str}) { +				print $OFH $tokens{$str}; +			} +			else { +				print $OFH chr(254). $str. chr(254); +			} +			$i = $j + 1; +		} +	} +	print $OFH chr(255); +} +} + +sub is_define_t { +	my($line, $type) = (@_); +	return ($line = ~ / ^ *define[\s] + $type + /); +} +sub is_define { +	my($line) = (@_); +	return ($line = ~ / ^ *define[\s] + [^\s] /); +} +sub is_end_define { return (shift = ~ / ^ *end + define *$ /); } + +sub trim { +	my $tmp = trim1($_[0]); +#print "trimming ($_[0]) -> ($tmp)\n"; +	return $tmp; +} + +sub trim1 { +	if ($_[0] = ~ / ^[\s] * (.* ?)[\s]*$ /) { +		return $1; +	} +	print "* * * Huh on trimming '$_[0]' * * *\n"; +} + +sub reinline { +	my % reinlined = (); +	my @head_prog = (); +	my @rest_prog = (); +	while (@_) { +		push @rest_prog, (pop @_); +	} + +	while (@rest_prog) { +		my $line = pop @rest_prog; +#print "processing $line\n"; +		if ($line = ~ / ^ (.* |)do ( < !intproc[0 - 9] * >) * (.*)$ /) { +#print "  reinlining...\n"; +				my($prefix, $func_name, $suffix) = ($1, $2, $3); +				$prefix = trim($prefix); +				$suffix = trim($suffix); +				$reinlined{$func_name} = 1; +				for (my $line_num = 0; $line_num < @rest_prog; $line_num ++) { +					if ($rest_prog[$line_num] = ~ / ^ *define + procedure + $func_name *$ /) { +						my $end_line = $line_num; +						while (!is_end_define($rest_prog[$end_line])) { +#print "   checking $rest_prog[$end_line]\n"; +							-- $end_line; +						} +						++ $end_line; +#print "    backpushing } ".$suffix."\n"; +#push @rest_prog, trim ("} " . $suffix); +						if ($suffix ne '') { +							push @rest_prog, $suffix; +						} +						push @rest_prog, "}"; +						while ($end_line < $line_num) { +							push @rest_prog, $rest_prog[$end_line]; +#print "    backpushing $rest_prog[$end_line]\n"; +							$end_line ++; +						} +#print "    backpushing $prefix {\n"; +						push @rest_prog, trim($prefix." {"); +						$line_num = scalar @rest_prog; +					} +				} +			} +		else { +			push @head_prog, $line; +		} +	} +	my @rv = (); +	for (my $n = 0; $n < @head_prog; $n ++) { +		if ($head_prog[$n] = ~ / ^define procedure(<.*>) *$ / && +		                     $reinlined{$1}) { +			while (!is_end_define($head_prog[$n])) { +				++ $n; +			} +		} +		else { +			push @rv, $head_prog[$n]; +		} +	} +#for (my $n = 0; $n < @rv; $n ++) { +#   print "$n: $rv[$n]\n"; +#} +	return @rv; +} + +sub pretty_print { +	my $indent = 0; +	my $not_in_text_mode = 1; + +	my @rv = (); + +	for (my $n = 0; $n < @_; $n ++) { +		my $line = $_[$n]; +		if (is_end_define($line)) { +			-- $indent; +			$not_in_text_mode = 1; +		} +		/ { /; if ($line = ~ / ^} /) { +			-- $indent; +		} +###if (is_define ($line) && ($n == 0 || !is_define ($_[$n-1]))) { print "\n"; } +		if (is_define($line) && ($n == 0 || !is_define($_[$n - 1]))) { +			push @rv, ""; +		} +###if ($in_text_mode == 0) { print "    "x$indent; } +		push @rv, ("    "x($indent*$not_in_text_mode)).trim($line); +###print $line, "  line $n, indent $indent, text $in_text_mode\n"; +###print $line, "\n"; +		if (is_end_define($line) && $n < @_ && !is_end_define($_[$n + 1]) +		        && !is_define($_[$n + 1])) { +###print "\n"; +			push @rv, ""; +		} +		if (is_define($line)) { +			++ $indent; +		} +		if ($line = ~ / {$ /) { +		++ $indent; +	} / +	   } /; +	if ($line = ~ / ^ *define + text /) { +			$not_in_text_mode = 0; +		} +	} +	return @rv; +} + + +sub error_msg { +	die "Usage: 'perl uncas.pl file.asl file2.cas' to compile to file\n". +	"       'perl uncas.pl file.cas'           to decompile to console\n". +	"       'perl uncas.pl file.cas file2.asl' to decompile to file\n"; +} + +if ($ARGV[0] eq '-raw') { +	$is_raw = 1; +	shift @ARGV; +} + +if ($ARGV[0] = ~ / \.asl$ /) { +	if (@ARGV != 2) { +		error_msg(); +	} +	compile_fil(@ARGV); +} +elsif($ARGV[0] = ~ / \.cas$ /) { +#print "compile_fil (", join (", ", @ARGV), ")\n"; +	if (@ARGV != 1 && @ARGV != 2) { +		error_msg(); +	} +	uncompile_fil(@ARGV); +} diff --git a/engines/glk/quetzal.cpp b/engines/glk/quetzal.cpp index e60460209a..0a2bc448ac 100644 --- a/engines/glk/quetzal.cpp +++ b/engines/glk/quetzal.cpp @@ -44,6 +44,7 @@ const uint32 INTERPRETER_IDS[INTERPRETER_TADS3 + 1] = {  	MKTAG('J', 'A', 'C', 'L'),  	MKTAG('L', 'V', 'L', '9'),  	MKTAG('M', 'A', 'G', 'N'), +	MKTAG('Q', 'E', 'S', 'T'),  	MKTAG('S', 'C', 'A', 'R'),  	MKTAG('S', 'C', 'O', 'T'),  	MKTAG('T', 'A', 'D', '2'), | 
