(svn r28004) -Update from Eints:
[openttd.git] / src / script / squirrel.cpp
blobe38067480d720615560e81f5d1c70740ec632c6a
1 /* $Id$ */
3 /*
4 * This file is part of OpenTTD.
5 * OpenTTD 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, version 2.
6 * OpenTTD 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.
7 * See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with OpenTTD. If not, see <http://www.gnu.org/licenses/>.
8 */
10 /** @file squirrel.cpp the implementation of the Squirrel class. It handles all Squirrel-stuff and gives a nice API back to work with. */
12 #include <stdarg.h>
13 #include "../stdafx.h"
14 #include "../debug.h"
15 #include "squirrel_std.hpp"
16 #include "../fileio_func.h"
17 #include "../string_func.h"
18 #include <sqstdaux.h>
19 #include <../squirrel/sqpcheader.h>
20 #include <../squirrel/sqvm.h>
22 #include "../safeguards.h"
24 void Squirrel::CompileError(HSQUIRRELVM vm, const SQChar *desc, const SQChar *source, SQInteger line, SQInteger column)
26 SQChar buf[1024];
28 seprintf(buf, lastof(buf), "Error %s:" OTTD_PRINTF64 "/" OTTD_PRINTF64 ": %s", source, line, column, desc);
30 /* Check if we have a custom print function */
31 Squirrel *engine = (Squirrel *)sq_getforeignptr(vm);
32 engine->crashed = true;
33 SQPrintFunc *func = engine->print_func;
34 if (func == NULL) {
35 DEBUG(misc, 0, "[Squirrel] Compile error: %s", buf);
36 } else {
37 (*func)(true, buf);
41 void Squirrel::ErrorPrintFunc(HSQUIRRELVM vm, const SQChar *s, ...)
43 va_list arglist;
44 SQChar buf[1024];
46 va_start(arglist, s);
47 vseprintf(buf, lastof(buf), s, arglist);
48 va_end(arglist);
50 /* Check if we have a custom print function */
51 SQPrintFunc *func = ((Squirrel *)sq_getforeignptr(vm))->print_func;
52 if (func == NULL) {
53 fprintf(stderr, "%s", buf);
54 } else {
55 (*func)(true, buf);
59 void Squirrel::RunError(HSQUIRRELVM vm, const SQChar *error)
61 /* Set the print function to something that prints to stderr */
62 SQPRINTFUNCTION pf = sq_getprintfunc(vm);
63 sq_setprintfunc(vm, &Squirrel::ErrorPrintFunc);
65 /* Check if we have a custom print function */
66 SQChar buf[1024];
67 seprintf(buf, lastof(buf), "Your script made an error: %s\n", error);
68 Squirrel *engine = (Squirrel *)sq_getforeignptr(vm);
69 SQPrintFunc *func = engine->print_func;
70 if (func == NULL) {
71 fprintf(stderr, "%s", buf);
72 } else {
73 (*func)(true, buf);
76 /* Print below the error the stack, so the users knows what is happening */
77 sqstd_printcallstack(vm);
78 /* Reset the old print function */
79 sq_setprintfunc(vm, pf);
82 SQInteger Squirrel::_RunError(HSQUIRRELVM vm)
84 const SQChar *sErr = 0;
86 if (sq_gettop(vm) >= 1) {
87 if (SQ_SUCCEEDED(sq_getstring(vm, -1, &sErr))) {
88 Squirrel::RunError(vm, sErr);
89 return 0;
93 Squirrel::RunError(vm, "unknown error");
94 return 0;
97 void Squirrel::PrintFunc(HSQUIRRELVM vm, const SQChar *s, ...)
99 va_list arglist;
100 SQChar buf[1024];
102 va_start(arglist, s);
103 vseprintf(buf, lastof(buf) - 2, s, arglist);
104 va_end(arglist);
105 strecat(buf, "\n", lastof(buf));
107 /* Check if we have a custom print function */
108 SQPrintFunc *func = ((Squirrel *)sq_getforeignptr(vm))->print_func;
109 if (func == NULL) {
110 printf("%s", buf);
111 } else {
112 (*func)(false, buf);
116 void Squirrel::AddMethod(const char *method_name, SQFUNCTION proc, uint nparam, const char *params, void *userdata, int size)
118 sq_pushstring(this->vm, method_name, -1);
120 if (size != 0) {
121 void *ptr = sq_newuserdata(vm, size);
122 memcpy(ptr, userdata, size);
125 sq_newclosure(this->vm, proc, size != 0 ? 1 : 0);
126 if (nparam != 0) sq_setparamscheck(this->vm, nparam, params);
127 sq_setnativeclosurename(this->vm, -1, method_name);
128 sq_newslot(this->vm, -3, SQFalse);
131 void Squirrel::AddConst(const char *var_name, int value)
133 sq_pushstring(this->vm, var_name, -1);
134 sq_pushinteger(this->vm, value);
135 sq_newslot(this->vm, -3, SQTrue);
138 void Squirrel::AddConst(const char *var_name, bool value)
140 sq_pushstring(this->vm, var_name, -1);
141 sq_pushbool(this->vm, value);
142 sq_newslot(this->vm, -3, SQTrue);
145 void Squirrel::AddClassBegin(const char *class_name)
147 sq_pushroottable(this->vm);
148 sq_pushstring(this->vm, class_name, -1);
149 sq_newclass(this->vm, SQFalse);
152 void Squirrel::AddClassBegin(const char *class_name, const char *parent_class)
154 sq_pushroottable(this->vm);
155 sq_pushstring(this->vm, class_name, -1);
156 sq_pushstring(this->vm, parent_class, -1);
157 if (SQ_FAILED(sq_get(this->vm, -3))) {
158 DEBUG(misc, 0, "[squirrel] Failed to initialize class '%s' based on parent class '%s'", class_name, parent_class);
159 DEBUG(misc, 0, "[squirrel] Make sure that '%s' exists before trying to define '%s'", parent_class, class_name);
160 return;
162 sq_newclass(this->vm, SQTrue);
165 void Squirrel::AddClassEnd()
167 sq_newslot(vm, -3, SQFalse);
168 sq_pop(vm, 1);
171 bool Squirrel::MethodExists(HSQOBJECT instance, const char *method_name)
173 assert(!this->crashed);
174 int top = sq_gettop(this->vm);
175 /* Go to the instance-root */
176 sq_pushobject(this->vm, instance);
177 /* Find the function-name inside the script */
178 sq_pushstring(this->vm, method_name, -1);
179 if (SQ_FAILED(sq_get(this->vm, -2))) {
180 sq_settop(this->vm, top);
181 return false;
183 sq_settop(this->vm, top);
184 return true;
187 bool Squirrel::Resume(int suspend)
189 assert(!this->crashed);
190 /* Did we use more operations than we should have in the
191 * previous tick? If so, subtract that from the current run. */
192 if (this->overdrawn_ops > 0 && suspend > 0) {
193 this->overdrawn_ops -= suspend;
194 /* Do we need to wait even more? */
195 if (this->overdrawn_ops >= 0) return true;
197 /* We can now only run whatever is "left". */
198 suspend = -this->overdrawn_ops;
201 this->crashed = !sq_resumecatch(this->vm, suspend);
202 this->overdrawn_ops = -this->vm->_ops_till_suspend;
203 return this->vm->_suspended != 0;
206 void Squirrel::ResumeError()
208 assert(!this->crashed);
209 sq_resumeerror(this->vm);
212 void Squirrel::CollectGarbage()
214 sq_collectgarbage(this->vm);
217 bool Squirrel::CallMethod(HSQOBJECT instance, const char *method_name, HSQOBJECT *ret, int suspend)
219 assert(!this->crashed);
220 /* Store the stack-location for the return value. We need to
221 * restore this after saving or the stack will be corrupted
222 * if we're in the middle of a DoCommand. */
223 SQInteger last_target = this->vm->_suspended_target;
224 /* Store the current top */
225 int top = sq_gettop(this->vm);
226 /* Go to the instance-root */
227 sq_pushobject(this->vm, instance);
228 /* Find the function-name inside the script */
229 sq_pushstring(this->vm, method_name, -1);
230 if (SQ_FAILED(sq_get(this->vm, -2))) {
231 DEBUG(misc, 0, "[squirrel] Could not find '%s' in the class", method_name);
232 sq_settop(this->vm, top);
233 return false;
235 /* Call the method */
236 sq_pushobject(this->vm, instance);
237 if (SQ_FAILED(sq_call(this->vm, 1, ret == NULL ? SQFalse : SQTrue, SQTrue, suspend))) return false;
238 if (ret != NULL) sq_getstackobj(vm, -1, ret);
239 /* Reset the top, but don't do so for the script main function, as we need
240 * a correct stack when resuming. */
241 if (suspend == -1 || !this->IsSuspended()) sq_settop(this->vm, top);
242 /* Restore the return-value location. */
243 this->vm->_suspended_target = last_target;
245 return true;
248 bool Squirrel::CallStringMethodStrdup(HSQOBJECT instance, const char *method_name, const char **res, int suspend)
250 HSQOBJECT ret;
251 if (!this->CallMethod(instance, method_name, &ret, suspend)) return false;
252 if (ret._type != OT_STRING) return false;
253 *res = stredup(ObjectToString(&ret));
254 ValidateString(*res);
255 return true;
258 bool Squirrel::CallIntegerMethod(HSQOBJECT instance, const char *method_name, int *res, int suspend)
260 HSQOBJECT ret;
261 if (!this->CallMethod(instance, method_name, &ret, suspend)) return false;
262 if (ret._type != OT_INTEGER) return false;
263 *res = ObjectToInteger(&ret);
264 return true;
267 bool Squirrel::CallBoolMethod(HSQOBJECT instance, const char *method_name, bool *res, int suspend)
269 HSQOBJECT ret;
270 if (!this->CallMethod(instance, method_name, &ret, suspend)) return false;
271 if (ret._type != OT_BOOL) return false;
272 *res = ObjectToBool(&ret);
273 return true;
276 /* static */ bool Squirrel::CreateClassInstanceVM(HSQUIRRELVM vm, const char *class_name, void *real_instance, HSQOBJECT *instance, SQRELEASEHOOK release_hook, bool prepend_API_name)
278 Squirrel *engine = (Squirrel *)sq_getforeignptr(vm);
280 int oldtop = sq_gettop(vm);
282 /* First, find the class */
283 sq_pushroottable(vm);
285 if (prepend_API_name) {
286 size_t len = strlen(class_name) + strlen(engine->GetAPIName()) + 1;
287 char *class_name2 = (char *)alloca(len);
288 seprintf(class_name2, class_name2 + len - 1, "%s%s", engine->GetAPIName(), class_name);
290 sq_pushstring(vm, class_name2, -1);
291 } else {
292 sq_pushstring(vm, class_name, -1);
295 if (SQ_FAILED(sq_get(vm, -2))) {
296 DEBUG(misc, 0, "[squirrel] Failed to find class by the name '%s%s'", prepend_API_name ? engine->GetAPIName() : "", class_name);
297 sq_settop(vm, oldtop);
298 return false;
301 /* Create the instance */
302 if (SQ_FAILED(sq_createinstance(vm, -1))) {
303 DEBUG(misc, 0, "[squirrel] Failed to create instance for class '%s%s'", prepend_API_name ? engine->GetAPIName() : "", class_name);
304 sq_settop(vm, oldtop);
305 return false;
308 if (instance != NULL) {
309 /* Find our instance */
310 sq_getstackobj(vm, -1, instance);
311 /* Add a reference to it, so it survives for ever */
312 sq_addref(vm, instance);
314 sq_remove(vm, -2); // Class-name
315 sq_remove(vm, -2); // Root-table
317 /* Store it in the class */
318 sq_setinstanceup(vm, -1, real_instance);
319 if (release_hook != NULL) sq_setreleasehook(vm, -1, release_hook);
321 if (instance != NULL) sq_settop(vm, oldtop);
323 return true;
326 bool Squirrel::CreateClassInstance(const char *class_name, void *real_instance, HSQOBJECT *instance)
328 return Squirrel::CreateClassInstanceVM(this->vm, class_name, real_instance, instance, NULL);
331 Squirrel::Squirrel(const char *APIName) :
332 APIName(APIName)
334 this->Initialize();
337 void Squirrel::Initialize()
339 this->global_pointer = NULL;
340 this->print_func = NULL;
341 this->crashed = false;
342 this->overdrawn_ops = 0;
343 this->vm = sq_open(1024);
345 /* Handle compile-errors ourself, so we can display it nicely */
346 sq_setcompilererrorhandler(this->vm, &Squirrel::CompileError);
347 sq_notifyallexceptions(this->vm, SQTrue);
348 /* Set a good print-function */
349 sq_setprintfunc(this->vm, &Squirrel::PrintFunc);
350 /* Handle runtime-errors ourself, so we can display it nicely */
351 sq_newclosure(this->vm, &Squirrel::_RunError, 0);
352 sq_seterrorhandler(this->vm);
354 /* Set the foreign pointer, so we can always find this instance from within the VM */
355 sq_setforeignptr(this->vm, this);
357 sq_pushroottable(this->vm);
358 squirrel_register_global_std(this);
361 class SQFile {
362 private:
363 FILE *file;
364 size_t size;
365 size_t pos;
367 public:
368 SQFile(FILE *file, size_t size) : file(file), size(size), pos(0) {}
370 size_t Read(void *buf, size_t elemsize, size_t count)
372 assert(elemsize != 0);
373 if (this->pos + (elemsize * count) > this->size) {
374 count = (this->size - this->pos) / elemsize;
376 if (count == 0) return 0;
377 size_t ret = fread(buf, elemsize, count, this->file);
378 this->pos += ret * elemsize;
379 return ret;
383 static WChar _io_file_lexfeed_ASCII(SQUserPointer file)
385 unsigned char c;
386 if (((SQFile *)file)->Read(&c, sizeof(c), 1) > 0) return c;
387 return 0;
390 static WChar _io_file_lexfeed_UTF8(SQUserPointer file)
392 char buffer[5];
394 /* Read the first character, and get the length based on UTF-8 specs. If invalid, bail out. */
395 if (((SQFile *)file)->Read(buffer, sizeof(buffer[0]), 1) != 1) return 0;
396 uint len = Utf8EncodedCharLen(buffer[0]);
397 if (len == 0) return -1;
399 /* Read the remaining bits. */
400 if (len > 1 && ((SQFile *)file)->Read(buffer + 1, sizeof(buffer[0]), len - 1) != len - 1) return 0;
402 /* Convert the character, and when definitely invalid, bail out as well. */
403 WChar c;
404 if (Utf8Decode(&c, buffer) != len) return -1;
406 return c;
409 static WChar _io_file_lexfeed_UCS2_no_swap(SQUserPointer file)
411 unsigned short c;
412 if (((SQFile *)file)->Read(&c, sizeof(c), 1) > 0) return (WChar)c;
413 return 0;
416 static WChar _io_file_lexfeed_UCS2_swap(SQUserPointer file)
418 unsigned short c;
419 if (((SQFile *)file)->Read(&c, sizeof(c), 1) > 0) {
420 c = ((c >> 8) & 0x00FF)| ((c << 8) & 0xFF00);
421 return (WChar)c;
423 return 0;
426 static SQInteger _io_file_read(SQUserPointer file, SQUserPointer buf, SQInteger size)
428 SQInteger ret = ((SQFile *)file)->Read(buf, 1, size);
429 if (ret == 0) return -1;
430 return ret;
433 SQRESULT Squirrel::LoadFile(HSQUIRRELVM vm, const char *filename, SQBool printerror)
435 size_t size;
436 FILE *file;
437 SQInteger ret;
438 unsigned short us;
439 unsigned char uc;
440 SQLEXREADFUNC func;
442 if (strncmp(this->GetAPIName(), "AI", 2) == 0) {
443 file = FioFOpenFile(filename, "rb", AI_DIR, &size);
444 if (file == NULL) file = FioFOpenFile(filename, "rb", AI_LIBRARY_DIR, &size);
445 } else if (strncmp(this->GetAPIName(), "GS", 2) == 0) {
446 file = FioFOpenFile(filename, "rb", GAME_DIR, &size);
447 if (file == NULL) file = FioFOpenFile(filename, "rb", GAME_LIBRARY_DIR, &size);
448 } else {
449 NOT_REACHED();
452 if (file != NULL) {
453 SQFile f(file, size);
454 ret = fread(&us, 1, sizeof(us), file);
455 /* Most likely an empty file */
456 if (ret != 2) us = 0;
458 switch (us) {
459 case SQ_BYTECODE_STREAM_TAG: { // BYTECODE
460 if (fseek(file, -2, SEEK_CUR) < 0) {
461 FioFCloseFile(file);
462 return sq_throwerror(vm, "cannot seek the file");
464 if (SQ_SUCCEEDED(sq_readclosure(vm, _io_file_read, &f))) {
465 FioFCloseFile(file);
466 return SQ_OK;
468 FioFCloseFile(file);
469 return sq_throwerror(vm, "Couldn't read bytecode");
471 case 0xFFFE:
472 /* Either this file is encoded as big-endian and we're on a little-endian
473 * machine, or this file is encoded as little-endian and we're on a big-endian
474 * machine. Either way, swap the bytes of every word we read. */
475 func = _io_file_lexfeed_UCS2_swap;
476 break;
477 case 0xFEFF: func = _io_file_lexfeed_UCS2_no_swap; break;
478 case 0xBBEF: // UTF-8
479 case 0xEFBB: // UTF-8 on big-endian machine
480 if (fread(&uc, 1, sizeof(uc), file) == 0) {
481 FioFCloseFile(file);
482 return sq_throwerror(vm, "I/O error");
484 if (uc != 0xBF) {
485 FioFCloseFile(file);
486 return sq_throwerror(vm, "Unrecognized encoding");
488 func = _io_file_lexfeed_UTF8;
489 break;
490 default: // ASCII
491 func = _io_file_lexfeed_ASCII;
492 if (fseek(file, -2, SEEK_CUR) < 0) {
493 FioFCloseFile(file);
494 return sq_throwerror(vm, "cannot seek the file");
496 break;
499 if (SQ_SUCCEEDED(sq_compile(vm, func, &f, filename, printerror))) {
500 FioFCloseFile(file);
501 return SQ_OK;
503 FioFCloseFile(file);
504 return SQ_ERROR;
506 return sq_throwerror(vm, "cannot open the file");
509 bool Squirrel::LoadScript(HSQUIRRELVM vm, const char *script, bool in_root)
511 /* Make sure we are always in the root-table */
512 if (in_root) sq_pushroottable(vm);
514 SQInteger ops_left = vm->_ops_till_suspend;
515 /* Load and run the script */
516 if (SQ_SUCCEEDED(LoadFile(vm, script, SQTrue))) {
517 sq_push(vm, -2);
518 if (SQ_SUCCEEDED(sq_call(vm, 1, SQFalse, SQTrue, 100000))) {
519 sq_pop(vm, 1);
520 /* After compiling the file we want to reset the amount of opcodes. */
521 vm->_ops_till_suspend = ops_left;
522 return true;
526 vm->_ops_till_suspend = ops_left;
527 DEBUG(misc, 0, "[squirrel] Failed to compile '%s'", script);
528 return false;
531 bool Squirrel::LoadScript(const char *script)
533 return LoadScript(this->vm, script);
536 Squirrel::~Squirrel()
538 this->Uninitialize();
541 void Squirrel::Uninitialize()
543 /* Clean up the stuff */
544 sq_pop(this->vm, 1);
545 sq_close(this->vm);
548 void Squirrel::Reset()
550 this->Uninitialize();
551 this->Initialize();
554 void Squirrel::InsertResult(bool result)
556 sq_pushbool(this->vm, result);
557 if (this->IsSuspended()) { // Called before resuming a suspended script?
558 vm->GetAt(vm->_stackbase + vm->_suspended_target) = vm->GetUp(-1);
559 vm->Pop();
563 void Squirrel::InsertResult(int result)
565 sq_pushinteger(this->vm, result);
566 if (this->IsSuspended()) { // Called before resuming a suspended script?
567 vm->GetAt(vm->_stackbase + vm->_suspended_target) = vm->GetUp(-1);
568 vm->Pop();
572 /* static */ void Squirrel::DecreaseOps(HSQUIRRELVM vm, int ops)
574 vm->DecreaseOps(ops);
577 bool Squirrel::IsSuspended()
579 return this->vm->_suspended != 0;
582 bool Squirrel::HasScriptCrashed()
584 return this->crashed;
587 void Squirrel::CrashOccurred()
589 this->crashed = true;
592 bool Squirrel::CanSuspend()
594 return sq_can_suspend(this->vm);
597 SQInteger Squirrel::GetOpsTillSuspend()
599 return this->vm->_ops_till_suspend;