gcsx_script.cpp 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349
  1. /* GCSx
  2. ** SCRIPT.CPP
  3. **
  4. ** Script support
  5. ** Non-EDITOR members
  6. */
  7. /*****************************************************************************
  8. ** Copyright (C) 2003-2006 Janson
  9. **
  10. ** This program is free software; you can redistribute it and/or modify
  11. ** it under the terms of the GNU General Public License as published by
  12. ** the Free Software Foundation; either version 2 of the License, or
  13. ** (at your option) any later version.
  14. **
  15. ** This program is distributed in the hope that it will be useful,
  16. ** but WITHOUT ANY WARRANTY; without even the implied warranty of
  17. ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  18. ** GNU General Public License for more details.
  19. **
  20. ** You should have received a copy of the GNU General Public License
  21. ** along with this program; if not, write to the Free Software
  22. ** Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111, USA.
  23. *****************************************************************************/
  24. #include "all.h"
  25. Script::Script(class World* myWorld, ScriptType type, int myId) : name(blankString), nameL(blankString), source() { start_func
  26. if (myId) assert(myWorld);
  27. id = myId;
  28. cached = 0;
  29. lockCount = 0;
  30. cacheFile = NULL;
  31. world = myWorld;
  32. bytecode = NULL;
  33. links = NULL;
  34. functions = NULL;
  35. diskStatus = CODE_UNCOMPILED;
  36. diskSource = 0;
  37. memStatus = CODE_UNCOMPILED;
  38. scriptType = type;
  39. defaultAnimgroup = NULL;
  40. defaultTileset = NULL;
  41. defaultId = 0;
  42. compileWarnings = compileErrors = 0;
  43. }
  44. Script::~Script() { start_func
  45. delete cacheFile;
  46. delete[] bytecode;
  47. delete links;
  48. destroyFunctionMap(functions);
  49. delete functions;
  50. // @TODO: For when you can copy stuff that includes refs to scripts/actors
  51. // clipboardClearScript(this);
  52. }
  53. void Script::cacheLoad() throw_File { start_func
  54. if (cached) {
  55. try {
  56. // load into memory
  57. assert(cacheFile);
  58. assert(cacheFile->getVersion() <= 1);
  59. assert(source.empty());
  60. source.clear();
  61. cacheFile->rewind();
  62. // @TODO: error checking on all of these, including unwinding
  63. // @TODO: skip loading source if not needed
  64. if (diskSource) {
  65. int rows = cacheFile->readInt();
  66. while (rows--) {
  67. string data;
  68. cacheFile->readStr(data);
  69. source.push_back(data);
  70. }
  71. }
  72. if ((diskStatus >= CODE_COMPILED) && (memStatus < CODE_COMPILED)) {
  73. // Would've been recached if and only if diskStatus >= CODE_COMPILED
  74. assert(!bytecode);
  75. assert(!links);
  76. Uint32 size = cacheFile->readInt();
  77. bytecode = new Uint32[size + 2];
  78. bytecode[0] = size;
  79. cacheFile->readIntBulk(bytecode + 1, size + 1);
  80. links = new list<LinkEntry>;
  81. int count = cacheFile->readInt();
  82. while (count--) {
  83. LinkEntry link;
  84. link.type = (LinkEntry::LinkEntryType)(cacheFile->readInt());
  85. link.offset = cacheFile->readInt();
  86. cacheFile->readStr(link.scope);
  87. cacheFile->readStr(link.name);
  88. cacheFile->readStr(link.wart);
  89. links->push_back(link);
  90. }
  91. memStatus = diskStatus;
  92. }
  93. // We retain this pointer and reuse it IF we
  94. // unlock to 0 AND aren't modified
  95. cached = 0;
  96. }
  97. catch (...) {
  98. // Recache
  99. source.clear();
  100. throw;
  101. }
  102. }
  103. }
  104. void Script::loadHeader(FileRead* file) throw_File { start_func
  105. assert(world);
  106. if (file->getVersion() > 1) {
  107. throw FileException("Unsupported script version %d", file->getVersion());
  108. }
  109. id = file->readInt();
  110. file->readStr(name);
  111. nameL = name;
  112. toLower(nameL);
  113. diskStatus = (ScriptStatus)file->readInt();
  114. diskSource = file->readInt();
  115. memStatus = CODE_UNCOMPILED;
  116. scriptType = (ScriptType)file->readInt();
  117. defaultAnimgroup = world->findAnimGroup(file->readInt());
  118. defaultTileset = world->findTileSet(file->readInt());
  119. defaultId = file->readInt();
  120. // Check validity
  121. if ((scriptType < SCRIPT_CODE) || (scriptType > SCRIPT_LIBRARY) ||
  122. (diskStatus < CODE_UNCOMPILED) || (diskStatus > CODE_COMPILED) ||
  123. (diskSource < 0) || (diskSource > 1) ||
  124. (defaultId < 0) || ((defaultAnimgroup) && (defaultTileset)) ||
  125. ((scriptType == SCRIPT_NOTE) && ((diskStatus > CODE_UNCOMPILED) || (defaultAnimgroup) || (defaultTileset))) ||
  126. ((scriptType == SCRIPT_LIBRARY) && ((defaultAnimgroup) || (defaultTileset))) ||
  127. ((defaultId) && (!defaultAnimgroup) && (!defaultTileset)) ||
  128. ((defaultAnimgroup) && (!defaultId)) ||
  129. ((defaultTileset) && (!defaultId))) {
  130. throw FileException("Corrupted script header");
  131. }
  132. // @TODO: Check validity
  133. if (diskStatus >= CODE_PARSED) {
  134. assert(!functions);
  135. assert(memStatus == CODE_UNCOMPILED);
  136. functions = new FunctionMap;
  137. int count = file->readInt();
  138. while (count--) {
  139. string name;
  140. file->readStr(name);
  141. Function& func = addFunctionMap(functions, name);
  142. func.numparam = file->readInt();
  143. if (func.numparam) {
  144. func.parameters = new DataType[func.numparam];
  145. for (int param = 0; param < func.numparam; ++param) {
  146. func.parameters[param].baseType = file->readInt();
  147. func.parameters[param].subType = file->readInt();
  148. func.parameters[param].flags = file->readInt();
  149. }
  150. }
  151. else {
  152. func.parameters = NULL;
  153. }
  154. func.returntype.baseType = file->readInt();
  155. func.returntype.subType = file->readInt();
  156. func.returntype.flags = file->readInt();
  157. func.offset = file->readInt();
  158. }
  159. memStatus = CODE_PARSED;
  160. }
  161. }
  162. void Script::loadContent(FileRead* file) { start_func
  163. assert(world);
  164. delete cacheFile;
  165. cacheFile = file;
  166. cached = 1;
  167. }
  168. int Script::isContentCached() const { start_func
  169. return cached;
  170. }
  171. int Script::markLock() throw_File { start_func
  172. cacheLoad();
  173. return ++lockCount;
  174. }
  175. int Script::markUnlock() { start_func
  176. --lockCount;
  177. assert(lockCount >= 0);
  178. if ((lockCount == 0) && (cacheFile)) {
  179. // Recache ourselves
  180. cached = 1;
  181. source.clear();
  182. // Don't clear compile results if it's not on disk
  183. // Don't clear compile results if linked and we're retaining linked code
  184. if ((diskStatus >= CODE_COMPILED) &&
  185. ((memStatus != CODE_LINKED) || (!config->readNum(LINKED_RETAIN)))) {
  186. delete[] bytecode;
  187. delete links;
  188. bytecode = NULL;
  189. links = NULL;
  190. if (functions) memStatus = CODE_PARSED;
  191. else memStatus = CODE_UNCOMPILED;
  192. }
  193. }
  194. return lockCount;
  195. }
  196. int Script::isLocked() const { start_func
  197. return lockCount;
  198. }
  199. void Script::parseFuncs() { start_func
  200. assert(lockCount);
  201. if (memStatus == CODE_UNCOMPILED) {
  202. // Prepare to parse functions
  203. assert(!functions);
  204. functions = new FunctionMap;
  205. Compiler c(&source, world);
  206. debugWrite(DEBUG_COMPILE, "Pre-parsing '%s'...", name.c_str());
  207. // If error, destroy results
  208. if (c.parseSymbols(functions)) {
  209. destroyFunctionMap(functions);
  210. delete functions;
  211. functions = NULL;
  212. }
  213. else {
  214. // Otherwise, update status
  215. memStatus = CODE_PARSED;
  216. }
  217. compileWarnings = c.numWarnings();
  218. compileErrors = c.numErrors();
  219. }
  220. }
  221. void Script::compile() { start_func
  222. assert(lockCount);
  223. if (memStatus == CODE_UNCOMPILED) {
  224. parseFuncs();
  225. if (compileErrors) return;
  226. }
  227. if (memStatus == CODE_PARSED) {
  228. // Prepare to compile
  229. assert(!links);
  230. links = new list<LinkEntry>;
  231. Compiler c(&source, world);
  232. debugWrite("Compiling '%s'...", name.c_str());
  233. // If error, destroy results
  234. if (c.compile(&bytecode, functions, links, id)) {
  235. delete links;
  236. links = new list<LinkEntry>;
  237. delete[] bytecode;
  238. bytecode = NULL;
  239. }
  240. else {
  241. // Otherwise, update status
  242. memStatus = CODE_COMPILED;
  243. }
  244. compileWarnings = c.numWarnings();
  245. compileErrors = c.numErrors();
  246. }
  247. }
  248. void Script::doLink() { start_func
  249. assert(lockCount);
  250. assert(memStatus >= CODE_COMPILED);
  251. assert(bytecode);
  252. assert(!compileErrors);
  253. if (memStatus == CODE_COMPILED) {
  254. debugWrite(DEBUG_COMPILE, "Linking '%s'...", name.c_str());
  255. // @TODO: actual linking! if any errors can result, need
  256. // to prevent deletion of links in Script object, and also
  257. // cause situations in gameplay as well
  258. assert(links);
  259. memStatus = CODE_LINKED;
  260. }
  261. }
  262. void Script::collectGlobalLinks(World::GlobalMap* globalLinks, int* globalLinksCount) { start_func
  263. assert(lockCount);
  264. assert(memStatus >= CODE_COMPILED);
  265. assert(links);
  266. assert(!compileErrors);
  267. list<LinkEntry>::iterator pos = links->begin();
  268. list<LinkEntry>::iterator end = links->end();
  269. for (; pos != end; ++pos) {
  270. LinkEntry& link = *pos;
  271. if ((link.type == LinkEntry::LINK_GLOBAL) || (link.type == LinkEntry::LINK_GLOBALVAR)) {
  272. //link.wart
  273. World::GlobalMap::iterator prev = globalLinks->find(link.name.c_str());
  274. if (prev == globalLinks->end()) {
  275. // New global- just add it
  276. pair<int,const char*>& newGlobal = (*globalLinks)[newCpCopy(link.name)];
  277. newGlobal.first = (*globalLinksCount)++;
  278. newGlobal.second = newCpCopy(link.wart);
  279. }
  280. else {
  281. // Verify matches existing global
  282. if (link.wart != (*prev).second.second) {
  283. // @TODO: Better output (debug window during gameplay; error window during editor)
  284. debugWrite("ERROR in %s: Datatype of global variable '%s' differs from other scripts", getName().c_str(), link.name.c_str());
  285. ++compileErrors;
  286. }
  287. }
  288. }
  289. }
  290. }
  291. void Script::link() { start_func
  292. doLink();
  293. // In non-edit mode, we can discard links now!
  294. delete links;
  295. links = NULL;
  296. }
  297. Uint32* Script::getCode() { start_func
  298. assert(bytecode);
  299. assert(memStatus == CODE_LINKED);
  300. return bytecode + 2;
  301. }