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/>.
10 /** @file squirrel.cpp the implementation of the Squirrel class. It handles all Squirrel-stuff and gives a nice API back to work with. */
13 #include "../stdafx.h"
15 #include "squirrel_std.hpp"
16 #include "../fileio_func.h"
17 #include "../string_func.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
)
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
;
35 DEBUG(misc
, 0, "[Squirrel] Compile error: %s", buf
);
41 void Squirrel::ErrorPrintFunc(HSQUIRRELVM vm
, const SQChar
*s
, ...)
47 vseprintf(buf
, lastof(buf
), s
, arglist
);
50 /* Check if we have a custom print function */
51 SQPrintFunc
*func
= ((Squirrel
*)sq_getforeignptr(vm
))->print_func
;
53 fprintf(stderr
, "%s", 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 */
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
;
71 fprintf(stderr
, "%s", 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
);
93 Squirrel::RunError(vm
, "unknown error");
97 void Squirrel::PrintFunc(HSQUIRRELVM vm
, const SQChar
*s
, ...)
102 va_start(arglist
, s
);
103 vseprintf(buf
, lastof(buf
) - 2, s
, 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
;
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);
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
);
162 sq_newclass(this->vm
, SQTrue
);
165 void Squirrel::AddClassEnd()
167 sq_newslot(vm
, -3, SQFalse
);
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
);
183 sq_settop(this->vm
, top
);
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
);
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
;
248 bool Squirrel::CallStringMethodStrdup(HSQOBJECT instance
, const char *method_name
, const char **res
, int suspend
)
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
);
258 bool Squirrel::CallIntegerMethod(HSQOBJECT instance
, const char *method_name
, int *res
, int suspend
)
261 if (!this->CallMethod(instance
, method_name
, &ret
, suspend
)) return false;
262 if (ret
._type
!= OT_INTEGER
) return false;
263 *res
= ObjectToInteger(&ret
);
267 bool Squirrel::CallBoolMethod(HSQOBJECT instance
, const char *method_name
, bool *res
, int suspend
)
270 if (!this->CallMethod(instance
, method_name
, &ret
, suspend
)) return false;
271 if (ret
._type
!= OT_BOOL
) return false;
272 *res
= ObjectToBool(&ret
);
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);
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
);
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
);
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
);
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
) :
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);
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
;
383 static WChar
_io_file_lexfeed_ASCII(SQUserPointer file
)
386 if (((SQFile
*)file
)->Read(&c
, sizeof(c
), 1) > 0) return c
;
390 static WChar
_io_file_lexfeed_UTF8(SQUserPointer file
)
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. */
404 if (Utf8Decode(&c
, buffer
) != len
) return -1;
409 static WChar
_io_file_lexfeed_UCS2_no_swap(SQUserPointer file
)
412 if (((SQFile
*)file
)->Read(&c
, sizeof(c
), 1) > 0) return (WChar
)c
;
416 static WChar
_io_file_lexfeed_UCS2_swap(SQUserPointer file
)
419 if (((SQFile
*)file
)->Read(&c
, sizeof(c
), 1) > 0) {
420 c
= ((c
>> 8) & 0x00FF)| ((c
<< 8) & 0xFF00);
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;
433 SQRESULT
Squirrel::LoadFile(HSQUIRRELVM vm
, const char *filename
, SQBool printerror
)
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
);
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;
459 case SQ_BYTECODE_STREAM_TAG
: { // BYTECODE
460 if (fseek(file
, -2, SEEK_CUR
) < 0) {
462 return sq_throwerror(vm
, "cannot seek the file");
464 if (SQ_SUCCEEDED(sq_readclosure(vm
, _io_file_read
, &f
))) {
469 return sq_throwerror(vm
, "Couldn't read bytecode");
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
;
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) {
482 return sq_throwerror(vm
, "I/O error");
486 return sq_throwerror(vm
, "Unrecognized encoding");
488 func
= _io_file_lexfeed_UTF8
;
491 func
= _io_file_lexfeed_ASCII
;
492 if (fseek(file
, -2, SEEK_CUR
) < 0) {
494 return sq_throwerror(vm
, "cannot seek the file");
499 if (SQ_SUCCEEDED(sq_compile(vm
, func
, &f
, filename
, printerror
))) {
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
))) {
518 if (SQ_SUCCEEDED(sq_call(vm
, 1, SQFalse
, SQTrue
, 100000))) {
520 /* After compiling the file we want to reset the amount of opcodes. */
521 vm
->_ops_till_suspend
= ops_left
;
526 vm
->_ops_till_suspend
= ops_left
;
527 DEBUG(misc
, 0, "[squirrel] Failed to compile '%s'", script
);
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 */
548 void Squirrel::Reset()
550 this->Uninitialize();
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);
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);
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
;