OpenTTD: src/script/squirrel.cpp Source File

OpenTTD

- Main Page
- Related Pages
- Modules
- Namespaces
- Data Structures
- Files

- File List
- Globals

squirrel.cpp
Go to the documentation of this file.
1 /* $Id: squirrel.cpp 26797 2014-09-07 16:03:41Z rubidium $ */
2
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 */
9
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>
21
22 #include "../safeguards.h"
23
24 void Squirrel::CompileError(HSQUIRRELVM vm, const SQChar *desc, const SQChar *source, SQInteger line, SQInteger column)
25 {
26 SQChar buf[1024];
27
28 seprintf(buf, lastof(buf), "Error %s:" OTTD_PRINTF64 "/" OTTD_PRINTF64 ": %s", source, line, column, desc);
29
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);
38 }
39 }
40
41 void Squirrel::ErrorPrintFunc(HSQUIRRELVM vm, const SQChar *s, ...)
42 {
43 va_list arglist;
44 SQChar buf[1024];
45
46 va_start(arglist, s);
47 vseprintf(buf, lastof(buf), s, arglist);
48 va_end(arglist);
49
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);
56 }
57 }
58
59 void Squirrel::RunError(HSQUIRRELVM vm, const SQChar *error)
60 {
61 /* Set the print function to something that prints to stderr */
62 SQPRINTFUNCTION pf = sq_getprintfunc(vm);
63 sq_setprintfunc(vm, &Squirrel::ErrorPrintFunc);
64
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);
74 }
75
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);
80 }
81
82 SQInteger Squirrel::_RunError(HSQUIRRELVM vm)
83 {
84 const SQChar *sErr = 0;
85
86 if (sq_gettop(vm) >= 1) {
87 if (SQ_SUCCEEDED(sq_getstring(vm, -1, &sErr))) {
88 Squirrel::RunError(vm, sErr);
89 return 0;
90 }
91 }
92
93 Squirrel::RunError(vm, "unknown error");
94 return 0;
95 }
96
97 void Squirrel::PrintFunc(HSQUIRRELVM vm, const SQChar *s, ...)
98 {
99 va_list arglist;
100 SQChar buf[1024];
101
102 va_start(arglist, s);
103 vseprintf(buf, lastof(buf) - 2, s, arglist);
104 va_end(arglist);
105 strecat(buf, "\n", lastof(buf));
106
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);
113 }
114 }
115
116 void Squirrel::AddMethod(const char *method_name, SQFUNCTION proc, uint nparam, const char *params, void *userdata, int size)
117 {
118 sq_pushstring(this->vm, method_name, -1);
119
120 if (size != 0) {
121 void *ptr = sq_newuserdata(vm, size);
122 memcpy(ptr, userdata, size);
123 }
124
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);
129 }
130
131 void Squirrel::AddConst(const char *var_name, int value)
132 {
133 sq_pushstring(this->vm, var_name, -1);
134 sq_pushinteger(this->vm, value);
135 sq_newslot(this->vm, -3, SQTrue);
136 }
137
138 void Squirrel::AddConst(const char *var_name, bool value)
139 {
140 sq_pushstring(this->vm, var_name, -1);
141 sq_pushbool(this->vm, value);
142 sq_newslot(this->vm, -3, SQTrue);
143 }
144
145 void Squirrel::AddClassBegin(const char *class_name)
146 {
147 sq_pushroottable(this->vm);
148 sq_pushstring(this->vm, class_name, -1);
149 sq_newclass(this->vm, SQFalse);
150 }
151
152 void Squirrel::AddClassBegin(const char *class_name, const char *parent_class)
153 {
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;
161 }
162 sq_newclass(this->vm, SQTrue);
163 }
164
165 void Squirrel::AddClassEnd()
166 {
167 sq_newslot(vm, -3, SQFalse);
168 sq_pop(vm, 1);
169 }
170
171 bool Squirrel::MethodExists(HSQOBJECT instance, const char *method_name)
172 {
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;
182 }
183 sq_settop(this->vm, top);
184 return true;
185 }
186
187 bool Squirrel::Resume(int suspend)
188 {
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;
196
197 /* We can now only run whatever is "left". */
198 suspend = -this->overdrawn_ops;
199 }
200
201 this->crashed = !sq_resumecatch(this->vm, suspend);
202 this->overdrawn_ops = -this->vm->_ops_till_suspend;
203 return this->vm->_suspended != 0;
204 }
205
206 void Squirrel::ResumeError()
207 {
208 assert(!this->crashed);
209 sq_resumeerror(this->vm);
210 }
211
212 void Squirrel::CollectGarbage()
213 {
214 sq_collectgarbage(this->vm);
215 }
216
217 bool Squirrel::CallMethod(HSQOBJECT instance, const char *method_name, HSQOBJECT *ret, int suspend)
218 {
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;
234 }
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;
244
245 return true;
246 }
247
248 bool Squirrel::CallStringMethodStrdup(HSQOBJECT instance, const char *method_name, const char **res, int suspend)
249 {
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;
256 }
257
258 bool Squirrel::CallIntegerMethod(HSQOBJECT instance, const char *method_name, int *res, int suspend)
259 {
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;
265 }
266
267 bool Squirrel::CallBoolMethod(HSQOBJECT instance, const char *method_name, bool *res, int suspend)
268 {
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;
274 }
275
276 /* static */ bool Squirrel::CreateClassInstanceVM(HSQUIRRELVM vm, const char *class_name, void *real_instance, HSQOBJECT *instance, SQRELEASEHOOK release_hook, bool prepend_API_name)
277 {
278 Squirrel *engine = (Squirrel *)sq_getforeignptr(vm);
279
280 int oldtop = sq_gettop(vm);
281
282 /* First, find the class */
283 sq_pushroottable(vm);
284
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);
289
290 sq_pushstring(vm, class_name2, -1);
291 } else {
292 sq_pushstring(vm, class_name, -1);
293 }
294
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;
299 }
300
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;
306 }
307
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);
313 }
314 sq_remove(vm, -2); // Class-name
315 sq_remove(vm, -2); // Root-table
316
317 /* Store it in the class */
318 sq_setinstanceup(vm, -1, real_instance);
319 if (release_hook != NULL) sq_setreleasehook(vm, -1, release_hook);
320
321 if (instance != NULL) sq_settop(vm, oldtop);
322
323 return true;
324 }
325
326 bool Squirrel::CreateClassInstance(const char *class_name, void *real_instance, HSQOBJECT *instance)
327 {
328 return Squirrel::CreateClassInstanceVM(this->vm, class_name, real_instance, instance, NULL);
329 }
330
331 Squirrel::Squirrel(const char *APIName) :
332 APIName(APIName)
333 {
334 this->Initialize();
335 }
336
337 void Squirrel::Initialize()
338 {
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);
344
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);
353
354 /* Set the foreign pointer, so we can always find this instance from within the VM */
355 sq_setforeignptr(this->vm, this);
356
357 sq_pushroottable(this->vm);
358 squirrel_register_global_std(this);
359 }
360
361 class SQFile {
362 private:
363 FILE *file;
364 size_t size;
365 size_t pos;
366
367 public:
368 SQFile(FILE *file, size_t size) : file(file), size(size), pos(0) {}
369
370 size_t Read(void *buf, size_t elemsize, size_t count)
371 {
372 assert(elemsize != 0);
373 if (this->pos + (elemsize * count) > this->size) {
374 count = (this->size - this->pos) / elemsize;
375 }
376 if (count == 0) return 0;
377 size_t ret = fread(buf, elemsize, count, this->file);
378 this->pos += ret * elemsize;
379 return ret;
380 }
381 };
382
383 static WChar _io_file_lexfeed_ASCII(SQUserPointer file)
384 {
385 unsigned char c;
386 if (((SQFile *)file)->Read(&c, sizeof(c), 1) > 0) return c;
387 return 0;
388 }
389
390 static WChar _io_file_lexfeed_UTF8(SQUserPointer file)
391 {
392 char buffer[5];
393
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;
398
399 /* Read the remaining bits. */
400 if (len > 1 && ((SQFile *)file)->Read(buffer + 1, sizeof(buffer[0]), len - 1) != len - 1) return 0;
401
402 /* Convert the character, and when definitely invalid, bail out as well. */
403 WChar c;
404 if (Utf8Decode(&c, buffer) != len) return -1;
405
406 return c;
407 }
408
409 static WChar _io_file_lexfeed_UCS2_no_swap(SQUserPointer file)
410 {
411 unsigned short c;
412 if (((SQFile *)file)->Read(&c, sizeof(c), 1) > 0) return (WChar)c;
413 return 0;
414 }
415
416 static WChar _io_file_lexfeed_UCS2_swap(SQUserPointer file)
417 {
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;
422 }
423 return 0;
424 }
425
426 static SQInteger _io_file_read(SQUserPointer file, SQUserPointer buf, SQInteger size)
427 {
428 SQInteger ret = ((SQFile *)file)->Read(buf, 1, size);
429 if (ret == 0) return -1;
430 return ret;
431 }
432
433 SQRESULT Squirrel::LoadFile(HSQUIRRELVM vm, const char *filename, SQBool printerror)
434 {
435 size_t size;
436 FILE *file;
437 SQInteger ret;
438 unsigned short us;
439 unsigned char uc;
440 SQLEXREADFUNC func;
441
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();
450 }
451
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;
457
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");
463 }
464 if (SQ_SUCCEEDED(sq_readclosure(vm, _io_file_read, &f))) {
465 FioFCloseFile(file);
466 return SQ_OK;
467 }
468 FioFCloseFile(file);
469 return sq_throwerror(vm, "Couldn't read bytecode");
470 }
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");
483 }
484 if (uc != 0xBF) {
485 FioFCloseFile(file);
486 return sq_throwerror(vm, "Unrecognized encoding");
487 }
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");
495 }
496 break;
497 }
498
499 if (SQ_SUCCEEDED(sq_compile(vm, func, &f, filename, printerror))) {
500 FioFCloseFile(file);
501 return SQ_OK;
502 }
503 FioFCloseFile(file);
504 return SQ_ERROR;
505 }
506 return sq_throwerror(vm, "cannot open the file");
507 }
508
509 bool Squirrel::LoadScript(HSQUIRRELVM vm, const char *script, bool in_root)
510 {
511 /* Make sure we are always in the root-table */
512 if (in_root) sq_pushroottable(vm);
513
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;
523 }
524 }
525
526 vm->_ops_till_suspend = ops_left;
527 DEBUG(misc, 0, "[squirrel] Failed to compile '%s'", script);
528 return false;
529 }
530
531 bool Squirrel::LoadScript(const char *script)
532 {
533 return LoadScript(this->vm, script);
534 }
535
536 Squirrel::~Squirrel()
537 {
538 this->Uninitialize();
539 }
540
541 void Squirrel::Uninitialize()
542 {
543 /* Clean up the stuff */
544 sq_pop(this->vm, 1);
545 sq_close(this->vm);
546 }
547
548 void Squirrel::Reset()
549 {
550 this->Uninitialize();
551 this->Initialize();
552 }
553
554 void Squirrel::InsertResult(bool result)
555 {
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();
560 }
561 }
562
563 void Squirrel::InsertResult(int result)
564 {
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();
569 }
570 }
571
572 /* static */ void Squirrel::DecreaseOps(HSQUIRRELVM vm, int ops)
573 {
574 vm->DecreaseOps(ops);
575 }
576
577 bool Squirrel::IsSuspended()
578 {
579 return this->vm->_suspended != 0;
580 }
581
582 bool Squirrel::HasScriptCrashed()
583 {
584 return this->crashed;
585 }
586
587 void Squirrel::CrashOccurred()
588 {
589 this->crashed = true;
590 }
591
592 bool Squirrel::CanSuspend()
593 {
594 return sq_can_suspend(this->vm);
595 }
596
597 SQInteger Squirrel::GetOpsTillSuspend()
598 {
599 return this->vm->_ops_till_suspend;
600 }