b324ff7ea5
-IMA-ADPCM support for samples, this means that sound effects can be compressed and use 4 timess less RAM. -New 3D import workflow based on Wavefront OBJ. Import single objects as mesh resources instead of full scenes. Many people prefers to work this way. Just like the rest of the imported resources, these are updated in realtime if modified externally. -Mesh resources now support naming surfaces. This helps reimporting to identify which user-created materials must be kept. -Several fixes and improvements to SurfaceTool. -Anti Aliasing added to WorldEnvironment effects (using FXAA) -2D Physics bodies (RigidBody, KinematicBody, etc), Raycasts, Tilemap, etc support collision layers. This makes easy to group which objects collide against which. -2D Trigger shapes can now also trigger collision reporting in other 2D bodies (it used to be in Area2D before) -Viewport render target textures can now be filtered. -Few fixes in GDscript make it easier to work with static functions and class members. -Several and many bugfixes.
2305 lines
51 KiB
C++
2305 lines
51 KiB
C++
/*************************************************************************/
|
|
/* gd_script.cpp */
|
|
/*************************************************************************/
|
|
/* This file is part of: */
|
|
/* GODOT ENGINE */
|
|
/* http://www.godotengine.org */
|
|
/*************************************************************************/
|
|
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
|
|
/* */
|
|
/* Permission is hereby granted, free of charge, to any person obtaining */
|
|
/* a copy of this software and associated documentation files (the */
|
|
/* "Software"), to deal in the Software without restriction, including */
|
|
/* without limitation the rights to use, copy, modify, merge, publish, */
|
|
/* distribute, sublicense, and/or sell copies of the Software, and to */
|
|
/* permit persons to whom the Software is furnished to do so, subject to */
|
|
/* the following conditions: */
|
|
/* */
|
|
/* The above copyright notice and this permission notice shall be */
|
|
/* included in all copies or substantial portions of the Software. */
|
|
/* */
|
|
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
|
|
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
|
|
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
|
|
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
|
|
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
|
|
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
|
|
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
|
|
/*************************************************************************/
|
|
#include "gd_script.h"
|
|
#include "globals.h"
|
|
#include "global_constants.h"
|
|
#include "gd_compiler.h"
|
|
#include "os/file_access.h"
|
|
|
|
/* TODO:
|
|
|
|
*populate globals
|
|
*do checks as close to debugger as possible (but don't do debugger)
|
|
*const check plz
|
|
*check arguments and default arguments in GDFunction
|
|
-get property list in instance?
|
|
*missing opcodes
|
|
-const checks
|
|
-make thread safe
|
|
*/
|
|
|
|
|
|
|
|
Variant *GDFunction::_get_variant(int p_address,GDInstance *p_instance,GDScript *p_script,Variant &self, Variant *p_stack,String& r_error) const{
|
|
|
|
int address = p_address&ADDR_MASK;
|
|
|
|
//sequential table (jump table generated by compiler)
|
|
switch((p_address&ADDR_TYPE_MASK)>>ADDR_BITS) {
|
|
|
|
case ADDR_TYPE_SELF: {
|
|
|
|
if (!p_instance) {
|
|
r_error="Cannot access self without instance.";
|
|
return NULL;
|
|
}
|
|
return &self;
|
|
} break;
|
|
case ADDR_TYPE_CLASS: {
|
|
|
|
return &p_script->_static_ref;
|
|
} break;
|
|
case ADDR_TYPE_MEMBER: {
|
|
//member indexing is O(1)
|
|
if (!p_instance) {
|
|
r_error="Cannot access member without instance.";
|
|
return NULL;
|
|
}
|
|
return &p_instance->members[address];
|
|
} break;
|
|
case ADDR_TYPE_CLASS_CONSTANT: {
|
|
|
|
//todo change to index!
|
|
GDScript *o=p_script;
|
|
ERR_FAIL_INDEX_V(address,_global_names_count,NULL);
|
|
const StringName *sn = &_global_names_ptr[address];
|
|
|
|
while(o) {
|
|
GDScript *s=o;
|
|
while(s) {
|
|
|
|
Map<StringName,Variant>::Element *E=s->constants.find(*sn);
|
|
if (E) {
|
|
return &E->get();
|
|
}
|
|
s=s->_base;
|
|
}
|
|
o=o->_owner;
|
|
}
|
|
|
|
|
|
ERR_EXPLAIN("GDCompiler bug..");
|
|
ERR_FAIL_V(NULL);
|
|
} break;
|
|
case ADDR_TYPE_LOCAL_CONSTANT: {
|
|
ERR_FAIL_INDEX_V(address,_constant_count,NULL);
|
|
return &_constants_ptr[address];
|
|
} break;
|
|
case ADDR_TYPE_STACK:
|
|
case ADDR_TYPE_STACK_VARIABLE: {
|
|
ERR_FAIL_INDEX_V(address,_stack_size,NULL);
|
|
return &p_stack[address];
|
|
} break;
|
|
case ADDR_TYPE_GLOBAL: {
|
|
|
|
|
|
ERR_FAIL_INDEX_V(address,GDScriptLanguage::get_singleton()->get_global_array_size(),NULL);
|
|
|
|
|
|
return &GDScriptLanguage::get_singleton()->get_global_array()[address];
|
|
} break;
|
|
case ADDR_TYPE_NIL: {
|
|
return &nil;
|
|
} break;
|
|
}
|
|
|
|
ERR_EXPLAIN("Bad Code! (Addressing Mode)");
|
|
ERR_FAIL_V(NULL);
|
|
return NULL;
|
|
}
|
|
|
|
|
|
String GDFunction::_get_call_error(const Variant::CallError& p_err, const String& p_where,const Variant**argptrs) const {
|
|
|
|
|
|
|
|
String err_text;
|
|
|
|
if (p_err.error==Variant::CallError::CALL_ERROR_INVALID_ARGUMENT) {
|
|
int errorarg=p_err.argument;
|
|
err_text="Invalid type in "+p_where+". Cannot convert argument "+itos(errorarg+1)+" from "+Variant::get_type_name(argptrs[errorarg]->get_type())+" to "+Variant::get_type_name(p_err.expected)+".";
|
|
} else if (p_err.error==Variant::CallError::CALL_ERROR_TOO_MANY_ARGUMENTS) {
|
|
err_text="Invalid call to "+p_where+". Expected "+itos(p_err.argument)+" arguments.";
|
|
} else if (p_err.error==Variant::CallError::CALL_ERROR_TOO_FEW_ARGUMENTS) {
|
|
err_text="Invalid call to "+p_where+". Expected "+itos(p_err.argument)+" arguments.";
|
|
} else if (p_err.error==Variant::CallError::CALL_ERROR_INVALID_METHOD) {
|
|
err_text="Invalid call. Unexisting "+p_where+".";
|
|
} else if (p_err.error==Variant::CallError::CALL_ERROR_INSTANCE_IS_NULL) {
|
|
err_text="Attempt to call "+p_where+" on a null instance.";
|
|
} else {
|
|
err_text="Bug, call error: #"+itos(p_err.error);
|
|
}
|
|
|
|
return err_text;
|
|
|
|
}
|
|
|
|
static String _get_var_type(const Variant* p_type) {
|
|
|
|
String basestr;
|
|
|
|
if (p_type->get_type()==Variant::OBJECT) {
|
|
Object *bobj = *p_type;
|
|
if (!bobj) {
|
|
basestr = "null instance";
|
|
} else {
|
|
#ifdef DEBUG_ENABLED
|
|
if (ObjectDB::instance_validate(bobj)) {
|
|
if (bobj->get_script_instance())
|
|
basestr= bobj->get_type()+" ("+bobj->get_script_instance()->get_script()->get_path().get_file()+")";
|
|
else
|
|
basestr = bobj->get_type();
|
|
} else {
|
|
basestr="previously freed instance";
|
|
}
|
|
|
|
#else
|
|
basestr="Object";
|
|
#endif
|
|
}
|
|
|
|
} else {
|
|
basestr = Variant::get_type_name(p_type->get_type());
|
|
}
|
|
|
|
return basestr;
|
|
|
|
}
|
|
|
|
Variant GDFunction::call(GDInstance *p_instance,const Variant **p_args, int p_argcount,Variant::CallError& r_err) {
|
|
|
|
|
|
if (!_code_ptr) {
|
|
|
|
return Variant();
|
|
}
|
|
|
|
r_err.error=Variant::CallError::CALL_OK;
|
|
|
|
Variant self;
|
|
Variant retvalue;
|
|
Variant *stack = NULL;
|
|
Variant **call_args;
|
|
int defarg=0;
|
|
|
|
#ifdef DEBUG_ENABLED
|
|
|
|
//GDScriptLanguage::get_singleton()->calls++;
|
|
|
|
#endif
|
|
|
|
if (p_argcount!=_argument_count) {
|
|
|
|
if (p_argcount>_argument_count) {
|
|
|
|
r_err.error=Variant::CallError::CALL_ERROR_TOO_MANY_ARGUMENTS;
|
|
r_err.argument=_argument_count;
|
|
|
|
return Variant();
|
|
} else if (p_argcount < _argument_count - _default_arg_count) {
|
|
|
|
r_err.error=Variant::CallError::CALL_ERROR_TOO_FEW_ARGUMENTS;
|
|
r_err.argument=_argument_count - _default_arg_count;
|
|
return Variant();
|
|
} else {
|
|
|
|
defarg=_argument_count-p_argcount;
|
|
}
|
|
}
|
|
|
|
uint32_t alloca_size = sizeof(Variant*)*_call_size + sizeof(Variant)*_stack_size;
|
|
|
|
if (alloca_size) {
|
|
|
|
uint8_t *aptr = (uint8_t*)alloca(alloca_size);
|
|
|
|
if (_stack_size) {
|
|
|
|
stack=(Variant*)aptr;
|
|
for(int i=0;i<p_argcount;i++)
|
|
memnew_placement(&stack[i],Variant(*p_args[i]));
|
|
for(int i=p_argcount;i<_stack_size;i++)
|
|
memnew_placement(&stack[i],Variant);
|
|
} else {
|
|
stack=NULL;
|
|
}
|
|
|
|
if (_call_size) {
|
|
|
|
call_args = (Variant**)&aptr[sizeof(Variant)*_stack_size];
|
|
} else {
|
|
|
|
call_args=NULL;
|
|
}
|
|
|
|
|
|
} else {
|
|
stack=NULL;
|
|
call_args=NULL;
|
|
}
|
|
|
|
|
|
GDScript *_class;
|
|
|
|
if (p_instance) {
|
|
if (p_instance->base_ref && static_cast<Reference*>(p_instance->owner)->is_referenced()) {
|
|
|
|
self=REF(static_cast<Reference*>(p_instance->owner));
|
|
} else {
|
|
self=p_instance->owner;
|
|
}
|
|
_class=p_instance->script.ptr();
|
|
} else {
|
|
_class=_script;
|
|
}
|
|
|
|
int ip=0;
|
|
int line=_initial_line;
|
|
String err_text;
|
|
|
|
|
|
|
|
#ifdef DEBUG_ENABLED
|
|
|
|
if (ScriptDebugger::get_singleton())
|
|
GDScriptLanguage::get_singleton()->enter_function(p_instance,this,stack,&ip,&line);
|
|
|
|
#define CHECK_SPACE(m_space)\
|
|
ERR_BREAK((ip+m_space)>_code_size)
|
|
|
|
#define GET_VARIANT_PTR(m_v,m_code_ofs) \
|
|
Variant *m_v; \
|
|
m_v = _get_variant(_code_ptr[ip+m_code_ofs],p_instance,_class,self,stack,err_text);\
|
|
if (!m_v)\
|
|
break;
|
|
|
|
|
|
#else
|
|
#define CHECK_SPACE(m_space)
|
|
#define GET_VARIANT_PTR(m_v,m_code_ofs) \
|
|
Variant *m_v; \
|
|
m_v = _get_variant(_code_ptr[ip+m_code_ofs],p_instance,_class,self,stack,err_text);
|
|
|
|
#endif
|
|
|
|
|
|
|
|
bool exit_ok=false;
|
|
|
|
while(ip<_code_size) {
|
|
|
|
|
|
int last_opcode=_code_ptr[ip];
|
|
switch(_code_ptr[ip]) {
|
|
|
|
case OPCODE_OPERATOR: {
|
|
|
|
CHECK_SPACE(5);
|
|
|
|
bool valid;
|
|
Variant::Operator op = (Variant::Operator)_code_ptr[ip+1];
|
|
ERR_BREAK(op>=Variant::OP_MAX);
|
|
|
|
GET_VARIANT_PTR(a,2);
|
|
GET_VARIANT_PTR(b,3);
|
|
GET_VARIANT_PTR(dst,4);
|
|
|
|
Variant::evaluate(op,*a,*b,*dst,valid);
|
|
if (!valid) {
|
|
if (false && dst->get_type()==Variant::STRING) {
|
|
//return a string when invalid with the error
|
|
err_text=*dst;
|
|
} else {
|
|
err_text="Invalid operands '"+Variant::get_type_name(a->get_type())+"' and '"+Variant::get_type_name(b->get_type())+"' in operator '"+Variant::get_operator_name(op)+"'.";
|
|
}
|
|
break;
|
|
}
|
|
|
|
ip+=5;
|
|
|
|
} continue;
|
|
case OPCODE_EXTENDS_TEST: {
|
|
|
|
CHECK_SPACE(4);
|
|
|
|
GET_VARIANT_PTR(a,1);
|
|
GET_VARIANT_PTR(b,2);
|
|
GET_VARIANT_PTR(dst,3);
|
|
|
|
#ifdef DEBUG_ENABLED
|
|
|
|
if (a->get_type()!=Variant::OBJECT || a->operator Object*()==NULL) {
|
|
|
|
err_text="Left operand of 'extends' is not an instance of anything.";
|
|
break;
|
|
|
|
}
|
|
if (b->get_type()!=Variant::OBJECT || b->operator Object*()==NULL) {
|
|
|
|
err_text="Right operand of 'extends' is not a class.";
|
|
break;
|
|
|
|
}
|
|
#endif
|
|
|
|
|
|
Object *obj_A = *a;
|
|
Object *obj_B = *b;
|
|
|
|
|
|
GDScript *scr_B = obj_B->cast_to<GDScript>();
|
|
|
|
bool extends_ok=false;
|
|
|
|
if (scr_B) {
|
|
//if B is a script, the only valid condition is that A has an instance which inherits from the script
|
|
//in other situation, this shoul return false.
|
|
|
|
if (obj_A->get_script_instance() && obj_A->get_script_instance()->get_language()==GDScriptLanguage::get_singleton()) {
|
|
|
|
GDInstance *ins = static_cast<GDInstance*>(obj_A->get_script_instance());
|
|
GDScript *cmp = ins->script.ptr();
|
|
//bool found=false;
|
|
while(cmp) {
|
|
|
|
if (cmp==scr_B) {
|
|
//inherits from script, all ok
|
|
extends_ok=true;
|
|
break;
|
|
|
|
}
|
|
|
|
cmp=cmp->_base;
|
|
}
|
|
|
|
}
|
|
} else {
|
|
|
|
GDNativeClass *nc= obj_B->cast_to<GDNativeClass>();
|
|
|
|
if (!nc) {
|
|
|
|
err_text="Right operand of 'extends' is not a class (type: '"+obj_B->get_type()+"').";
|
|
break;
|
|
}
|
|
|
|
extends_ok=ObjectTypeDB::is_type(obj_A->get_type_name(),nc->get_name());
|
|
}
|
|
|
|
*dst=extends_ok;
|
|
ip+=4;
|
|
|
|
} continue;
|
|
case OPCODE_SET: {
|
|
|
|
CHECK_SPACE(3);
|
|
|
|
GET_VARIANT_PTR(dst,1);
|
|
GET_VARIANT_PTR(index,2);
|
|
GET_VARIANT_PTR(value,3);
|
|
|
|
bool valid;
|
|
dst->set(*index,*value,&valid);
|
|
|
|
if (!valid) {
|
|
String v = index->operator String();
|
|
if (v!="") {
|
|
v="'"+v+"'";
|
|
} else {
|
|
v="of type '"+_get_var_type(index)+"'";
|
|
}
|
|
err_text="Invalid set index "+v+" (on base: '"+_get_var_type(dst)+"').";
|
|
break;
|
|
}
|
|
|
|
ip+=4;
|
|
} continue;
|
|
case OPCODE_GET: {
|
|
|
|
CHECK_SPACE(3);
|
|
|
|
GET_VARIANT_PTR(src,1);
|
|
GET_VARIANT_PTR(index,2);
|
|
GET_VARIANT_PTR(dst,3);
|
|
|
|
bool valid;
|
|
*dst = src->get(*index,&valid);
|
|
|
|
if (!valid) {
|
|
String v = index->operator String();
|
|
if (v!="") {
|
|
v="'"+v+"'";
|
|
} else {
|
|
v="of type '"+_get_var_type(index)+"'";
|
|
}
|
|
err_text="Invalid get index "+v+" (on base: '"+_get_var_type(src)+"').";
|
|
break;
|
|
}
|
|
ip+=4;
|
|
} continue;
|
|
case OPCODE_SET_NAMED: {
|
|
|
|
CHECK_SPACE(3);
|
|
|
|
GET_VARIANT_PTR(dst,1);
|
|
GET_VARIANT_PTR(value,3);
|
|
|
|
int indexname = _code_ptr[ip+2];
|
|
|
|
ERR_BREAK(indexname<0 || indexname>=_global_names_count);
|
|
const StringName *index = &_global_names_ptr[indexname];
|
|
|
|
bool valid;
|
|
dst->set_named(*index,*value,&valid);
|
|
|
|
if (!valid) {
|
|
String err_type;
|
|
err_text="Invalid set index '"+String(*index)+"' (on base: '"+_get_var_type(dst)+"').";
|
|
break;
|
|
}
|
|
|
|
ip+=4;
|
|
} continue;
|
|
case OPCODE_GET_NAMED: {
|
|
|
|
|
|
CHECK_SPACE(3);
|
|
|
|
GET_VARIANT_PTR(src,1);
|
|
GET_VARIANT_PTR(dst,3);
|
|
|
|
int indexname = _code_ptr[ip+2];
|
|
|
|
ERR_BREAK(indexname<0 || indexname>=_global_names_count);
|
|
const StringName *index = &_global_names_ptr[indexname];
|
|
|
|
bool valid;
|
|
*dst = src->get_named(*index,&valid);
|
|
|
|
if (!valid) {
|
|
err_text="Invalid get index '"+index->operator String()+"' (on base: '"+_get_var_type(src)+"').";
|
|
break;
|
|
}
|
|
|
|
ip+=4;
|
|
} continue;
|
|
case OPCODE_ASSIGN: {
|
|
|
|
CHECK_SPACE(3);
|
|
GET_VARIANT_PTR(dst,1);
|
|
GET_VARIANT_PTR(src,2);
|
|
|
|
*dst = *src;
|
|
|
|
ip+=3;
|
|
|
|
} continue;
|
|
case OPCODE_ASSIGN_TRUE: {
|
|
|
|
CHECK_SPACE(2);
|
|
GET_VARIANT_PTR(dst,1);
|
|
|
|
*dst = true;
|
|
|
|
ip+=2;
|
|
} continue;
|
|
case OPCODE_ASSIGN_FALSE: {
|
|
|
|
CHECK_SPACE(2);
|
|
GET_VARIANT_PTR(dst,1);
|
|
|
|
*dst = false;
|
|
|
|
ip+=2;
|
|
} continue;
|
|
case OPCODE_CONSTRUCT: {
|
|
|
|
CHECK_SPACE(2);
|
|
Variant::Type t=Variant::Type(_code_ptr[ip+1]);
|
|
int argc=_code_ptr[ip+2];
|
|
CHECK_SPACE(argc+2);
|
|
Variant **argptrs = call_args;
|
|
for(int i=0;i<argc;i++) {
|
|
GET_VARIANT_PTR(v,3+i);
|
|
argptrs[i]=v;
|
|
}
|
|
|
|
GET_VARIANT_PTR(dst,3+argc);
|
|
Variant::CallError err;
|
|
*dst = Variant::construct(t,(const Variant**)argptrs,argc,err);
|
|
|
|
if (err.error!=Variant::CallError::CALL_OK) {
|
|
|
|
err_text=_get_call_error(err,"'"+Variant::get_type_name(t)+"' constructor",(const Variant**)argptrs);
|
|
break;
|
|
}
|
|
|
|
ip+=4+argc;
|
|
//construct a basic type
|
|
} continue;
|
|
case OPCODE_CONSTRUCT_ARRAY: {
|
|
|
|
CHECK_SPACE(1);
|
|
int argc=_code_ptr[ip+1];
|
|
Array array(true); //arrays are always shared
|
|
array.resize(argc);
|
|
CHECK_SPACE(argc+2);
|
|
|
|
for(int i=0;i<argc;i++) {
|
|
GET_VARIANT_PTR(v,2+i);
|
|
array[i]=*v;
|
|
|
|
}
|
|
|
|
GET_VARIANT_PTR(dst,2+argc);
|
|
|
|
*dst=array;
|
|
|
|
ip+=3+argc;
|
|
|
|
} continue;
|
|
case OPCODE_CONSTRUCT_DICTIONARY: {
|
|
|
|
CHECK_SPACE(1);
|
|
int argc=_code_ptr[ip+1];
|
|
Dictionary dict(true); //arrays are always shared
|
|
|
|
CHECK_SPACE(argc*2+2);
|
|
|
|
for(int i=0;i<argc;i++) {
|
|
|
|
GET_VARIANT_PTR(k,2+i*2+0);
|
|
GET_VARIANT_PTR(v,2+i*2+1);
|
|
dict[*k]=*v;
|
|
|
|
}
|
|
|
|
GET_VARIANT_PTR(dst,2+argc*2);
|
|
|
|
*dst=dict;
|
|
|
|
ip+=3+argc*2;
|
|
|
|
} continue;
|
|
case OPCODE_CALL_RETURN:
|
|
case OPCODE_CALL: {
|
|
|
|
|
|
CHECK_SPACE(4);
|
|
bool call_ret = _code_ptr[ip]==OPCODE_CALL_RETURN;
|
|
|
|
int argc=_code_ptr[ip+1];
|
|
GET_VARIANT_PTR(base,2);
|
|
int nameg=_code_ptr[ip+3];
|
|
|
|
ERR_BREAK(nameg<0 || nameg>=_global_names_count);
|
|
const StringName *methodname = &_global_names_ptr[nameg];
|
|
|
|
ERR_BREAK(argc<0);
|
|
ip+=4;
|
|
CHECK_SPACE(argc+1);
|
|
Variant **argptrs = call_args;
|
|
|
|
for(int i=0;i<argc;i++) {
|
|
GET_VARIANT_PTR(v,i);
|
|
argptrs[i]=v;
|
|
}
|
|
|
|
Variant::CallError err;
|
|
if (call_ret) {
|
|
|
|
GET_VARIANT_PTR(ret,argc);
|
|
*ret = base->call(*methodname,(const Variant**)argptrs,argc,err);
|
|
} else {
|
|
|
|
base->call(*methodname,(const Variant**)argptrs,argc,err);
|
|
}
|
|
|
|
if (err.error!=Variant::CallError::CALL_OK) {
|
|
|
|
|
|
String methodstr = *methodname;
|
|
String basestr = _get_var_type(base);
|
|
|
|
if (methodstr=="call") {
|
|
if (argc>=1) {
|
|
methodstr=String(*argptrs[0])+" (via call)";
|
|
if (err.error==Variant::CallError::CALL_ERROR_INVALID_ARGUMENT) {
|
|
err.argument-=1;
|
|
}
|
|
}
|
|
} if (methodstr=="free") {
|
|
|
|
if (err.error==Variant::CallError::CALL_ERROR_INVALID_METHOD) {
|
|
|
|
if (base->is_ref()) {
|
|
err_text="Attempted to free a reference.";
|
|
break;
|
|
} else if (base->get_type()==Variant::OBJECT) {
|
|
|
|
err_text="Attempted to free a locked object (calling or emitting).";
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
err_text=_get_call_error(err,"function '"+methodstr+"' in base '"+basestr+"'",(const Variant**)argptrs);
|
|
break;
|
|
}
|
|
|
|
//_call_func(NULL,base,*methodname,ip,argc,p_instance,stack);
|
|
ip+=argc+1;
|
|
|
|
} continue;
|
|
case OPCODE_CALL_BUILT_IN: {
|
|
|
|
CHECK_SPACE(4);
|
|
|
|
GDFunctions::Function func = GDFunctions::Function(_code_ptr[ip+1]);
|
|
int argc=_code_ptr[ip+2];
|
|
ERR_BREAK(argc<0);
|
|
|
|
ip+=3;
|
|
CHECK_SPACE(argc+1);
|
|
Variant **argptrs = call_args;
|
|
|
|
for(int i=0;i<argc;i++) {
|
|
GET_VARIANT_PTR(v,i);
|
|
argptrs[i]=v;
|
|
}
|
|
|
|
GET_VARIANT_PTR(dst,argc);
|
|
|
|
Variant::CallError err;
|
|
|
|
GDFunctions::call(func,(const Variant**)argptrs,argc,*dst,err);
|
|
|
|
if (err.error!=Variant::CallError::CALL_OK) {
|
|
|
|
|
|
String methodstr = GDFunctions::get_func_name(func);
|
|
err_text=_get_call_error(err,"built-in function '"+methodstr+"'",(const Variant**)argptrs);
|
|
break;
|
|
}
|
|
ip+=argc+1;
|
|
|
|
} continue;
|
|
case OPCODE_CALL_SELF: {
|
|
|
|
|
|
} break;
|
|
case OPCODE_CALL_SELF_BASE: {
|
|
|
|
CHECK_SPACE(2);
|
|
int self_fun = _code_ptr[ip+1];
|
|
#ifdef DEBUG_ENABLED
|
|
|
|
if (self_fun<0 || self_fun>=_global_names_count) {
|
|
|
|
err_text="compiler bug, function name not found";
|
|
break;
|
|
}
|
|
#endif
|
|
const StringName *methodname = &_global_names_ptr[self_fun];
|
|
|
|
int argc=_code_ptr[ip+2];
|
|
|
|
CHECK_SPACE(2+argc+1);
|
|
|
|
Variant **argptrs = call_args;
|
|
|
|
for(int i=0;i<argc;i++) {
|
|
GET_VARIANT_PTR(v,i+3);
|
|
argptrs[i]=v;
|
|
}
|
|
|
|
GET_VARIANT_PTR(dst,argc+3);
|
|
|
|
const GDScript *gds = _script;
|
|
|
|
|
|
const Map<StringName,GDFunction>::Element *E=NULL;
|
|
while (gds->base.ptr()) {
|
|
gds=gds->base.ptr();
|
|
E=gds->member_functions.find(*methodname);
|
|
if (E)
|
|
break;
|
|
}
|
|
|
|
Variant::CallError err;
|
|
|
|
if (E) {
|
|
|
|
*dst=((GDFunction*)&E->get())->call(p_instance,(const Variant**)argptrs,argc,err);
|
|
} else if (gds->native.ptr()) {
|
|
|
|
if (*methodname!=GDScriptLanguage::get_singleton()->strings._init) {
|
|
|
|
MethodBind *mb = ObjectTypeDB::get_method(gds->native->get_name(),*methodname);
|
|
if (!mb) {
|
|
err.error=Variant::CallError::CALL_ERROR_INVALID_METHOD;
|
|
} else {
|
|
*dst=mb->call(p_instance->owner,(const Variant**)argptrs,argc,err);
|
|
}
|
|
} else {
|
|
err.error=Variant::CallError::CALL_OK;
|
|
}
|
|
} else {
|
|
|
|
if (*methodname!=GDScriptLanguage::get_singleton()->strings._init) {
|
|
err.error=Variant::CallError::CALL_ERROR_INVALID_METHOD;
|
|
} else {
|
|
err.error=Variant::CallError::CALL_OK;
|
|
}
|
|
}
|
|
|
|
|
|
if (err.error!=Variant::CallError::CALL_OK) {
|
|
|
|
|
|
String methodstr = *methodname;
|
|
err_text=_get_call_error(err,"function '"+methodstr+"'",(const Variant**)argptrs);
|
|
|
|
break;
|
|
}
|
|
|
|
ip+=4+argc;
|
|
|
|
} continue;
|
|
case OPCODE_JUMP: {
|
|
|
|
CHECK_SPACE(2);
|
|
int to = _code_ptr[ip+1];
|
|
|
|
ERR_BREAK(to<0 || to>_code_size);
|
|
ip=to;
|
|
|
|
} continue;
|
|
case OPCODE_JUMP_IF: {
|
|
|
|
CHECK_SPACE(3);
|
|
|
|
GET_VARIANT_PTR(test,1);
|
|
|
|
bool valid;
|
|
bool result = test->booleanize(valid);
|
|
#ifdef DEBUG_ENABLED
|
|
if (!valid) {
|
|
|
|
err_text="cannot evaluate conditional expression of type: "+Variant::get_type_name(test->get_type());
|
|
break;
|
|
}
|
|
#endif
|
|
if (result) {
|
|
int to = _code_ptr[ip+2];
|
|
ERR_BREAK(to<0 || to>_code_size);
|
|
ip=to;
|
|
continue;
|
|
}
|
|
ip+=3;
|
|
} continue;
|
|
case OPCODE_JUMP_IF_NOT: {
|
|
|
|
CHECK_SPACE(3);
|
|
|
|
GET_VARIANT_PTR(test,1);
|
|
|
|
bool valid;
|
|
bool result = test->booleanize(valid);
|
|
#ifdef DEBUG_ENABLED
|
|
if (!valid) {
|
|
|
|
err_text="cannot evaluate conditional expression of type: "+Variant::get_type_name(test->get_type());
|
|
break;
|
|
}
|
|
#endif
|
|
if (!result) {
|
|
int to = _code_ptr[ip+2];
|
|
ERR_BREAK(to<0 || to>_code_size);
|
|
ip=to;
|
|
continue;
|
|
}
|
|
ip+=3;
|
|
} continue;
|
|
case OPCODE_JUMP_TO_DEF_ARGUMENT: {
|
|
|
|
CHECK_SPACE(2);
|
|
ip=_default_arg_ptr[defarg];
|
|
|
|
} continue;
|
|
case OPCODE_RETURN: {
|
|
|
|
CHECK_SPACE(2);
|
|
GET_VARIANT_PTR(r,1);
|
|
retvalue=*r;
|
|
exit_ok=true;
|
|
|
|
} break;
|
|
case OPCODE_ITERATE_BEGIN: {
|
|
|
|
CHECK_SPACE(8); //space for this an regular iterate
|
|
|
|
GET_VARIANT_PTR(counter,1);
|
|
GET_VARIANT_PTR(container,2);
|
|
|
|
bool valid;
|
|
if (!container->iter_init(*counter,valid)) {
|
|
if (!valid) {
|
|
err_text="Unable to iterate on object of type "+Variant::get_type_name(container->get_type())+"'.";
|
|
break;
|
|
}
|
|
int jumpto=_code_ptr[ip+3];
|
|
ERR_BREAK(jumpto<0 || jumpto>_code_size);
|
|
ip=jumpto;
|
|
continue;
|
|
}
|
|
GET_VARIANT_PTR(iterator,4);
|
|
|
|
|
|
*iterator=container->iter_get(*counter,valid);
|
|
if (!valid) {
|
|
err_text="Unable to obtain iterator object of type "+Variant::get_type_name(container->get_type())+"'.";
|
|
break;
|
|
}
|
|
|
|
|
|
ip+=5; //skip regular iterate which is always next
|
|
|
|
} continue;
|
|
case OPCODE_ITERATE: {
|
|
|
|
CHECK_SPACE(4);
|
|
|
|
GET_VARIANT_PTR(counter,1);
|
|
GET_VARIANT_PTR(container,2);
|
|
|
|
bool valid;
|
|
if (!container->iter_next(*counter,valid)) {
|
|
if (!valid) {
|
|
err_text="Unable to iterate on object of type "+Variant::get_type_name(container->get_type())+"' (type changed since first iteration?).";
|
|
break;
|
|
}
|
|
int jumpto=_code_ptr[ip+3];
|
|
ERR_BREAK(jumpto<0 || jumpto>_code_size);
|
|
ip=jumpto;
|
|
continue;
|
|
}
|
|
GET_VARIANT_PTR(iterator,4);
|
|
|
|
*iterator=container->iter_get(*counter,valid);
|
|
if (!valid) {
|
|
err_text="Unable to obtain iterator object of type "+Variant::get_type_name(container->get_type())+"' (but was obtained on first iteration?).";
|
|
break;
|
|
}
|
|
|
|
ip+=5; //loop again
|
|
} continue;
|
|
case OPCODE_ASSERT: {
|
|
CHECK_SPACE(2);
|
|
GET_VARIANT_PTR(test,1);
|
|
|
|
#ifdef DEBUG_ENABLED
|
|
bool valid;
|
|
bool result = test->booleanize(valid);
|
|
|
|
|
|
if (!valid) {
|
|
|
|
err_text="cannot evaluate conditional expression of type: "+Variant::get_type_name(test->get_type());
|
|
break;
|
|
}
|
|
|
|
|
|
if (!result) {
|
|
|
|
err_text="Assertion failed.";
|
|
break;
|
|
}
|
|
|
|
#endif
|
|
|
|
ip+=2;
|
|
} continue;
|
|
case OPCODE_LINE: {
|
|
CHECK_SPACE(2);
|
|
|
|
line=_code_ptr[ip+1];
|
|
ip+=2;
|
|
|
|
if (ScriptDebugger::get_singleton()) {
|
|
// line
|
|
bool do_break=false;
|
|
|
|
if (ScriptDebugger::get_singleton()->get_lines_left()>0) {
|
|
|
|
if (ScriptDebugger::get_singleton()->get_depth()<=0)
|
|
ScriptDebugger::get_singleton()->set_lines_left( ScriptDebugger::get_singleton()->get_lines_left() -1 );
|
|
if (ScriptDebugger::get_singleton()->get_lines_left()<=0)
|
|
do_break=true;
|
|
}
|
|
|
|
if (ScriptDebugger::get_singleton()->is_breakpoint(line,source))
|
|
do_break=true;
|
|
|
|
if (do_break) {
|
|
GDScriptLanguage::get_singleton()->debug_break("Breakpoint",true);
|
|
}
|
|
|
|
ScriptDebugger::get_singleton()->line_poll();
|
|
|
|
}
|
|
} continue;
|
|
case OPCODE_END: {
|
|
|
|
exit_ok=true;
|
|
break;
|
|
|
|
} break;
|
|
default: {
|
|
|
|
err_text="Illegal opcode "+itos(_code_ptr[ip])+" at address "+itos(ip);
|
|
} break;
|
|
|
|
}
|
|
|
|
if (exit_ok)
|
|
break;
|
|
//error
|
|
// function, file, line, error, explanation
|
|
String err_file;
|
|
if (p_instance)
|
|
err_file=p_instance->script->path;
|
|
else if (_class)
|
|
err_file=_class->path;
|
|
if (err_file=="")
|
|
err_file="<built-in>";
|
|
String err_func = name;
|
|
if (p_instance && p_instance->script->name!="")
|
|
err_func=p_instance->script->name+"."+err_func;
|
|
int err_line=line;
|
|
if (err_text=="") {
|
|
err_text="Internal Script Error! - opcode #"+itos(last_opcode)+" (report please).";
|
|
}
|
|
|
|
if (!GDScriptLanguage::get_singleton()->debug_break(err_text,false)) {
|
|
// debugger break did not happen
|
|
|
|
_err_print_error(err_func.utf8().get_data(),err_file.utf8().get_data(),err_line,err_text.utf8().get_data());
|
|
}
|
|
|
|
|
|
break;
|
|
}
|
|
|
|
if (ScriptDebugger::get_singleton())
|
|
GDScriptLanguage::get_singleton()->exit_function();
|
|
|
|
|
|
if (_stack_size) {
|
|
//free stack
|
|
for(int i=0;i<_stack_size;i++)
|
|
stack[i].~Variant();
|
|
}
|
|
|
|
return retvalue;
|
|
|
|
}
|
|
|
|
const int* GDFunction::get_code() const {
|
|
|
|
return _code_ptr;
|
|
}
|
|
int GDFunction::get_code_size() const{
|
|
|
|
return _code_size;
|
|
}
|
|
|
|
Variant GDFunction::get_constant(int p_idx) const {
|
|
|
|
ERR_FAIL_INDEX_V(p_idx,constants.size(),"<errconst>");
|
|
return constants[p_idx];
|
|
}
|
|
|
|
StringName GDFunction::get_global_name(int p_idx) const {
|
|
|
|
ERR_FAIL_INDEX_V(p_idx,global_names.size(),"<errgname>");
|
|
return global_names[p_idx];
|
|
}
|
|
|
|
int GDFunction::get_default_argument_count() const {
|
|
|
|
return default_arguments.size();
|
|
}
|
|
int GDFunction::get_default_argument_addr(int p_arg) const{
|
|
|
|
ERR_FAIL_INDEX_V(p_arg,default_arguments.size(),-1);
|
|
return default_arguments[p_arg];
|
|
}
|
|
|
|
|
|
StringName GDFunction::get_name() const {
|
|
|
|
return name;
|
|
}
|
|
|
|
int GDFunction::get_max_stack_size() const {
|
|
|
|
return _stack_size;
|
|
}
|
|
|
|
struct _GDFKC {
|
|
|
|
int order;
|
|
List<int> pos;
|
|
};
|
|
|
|
struct _GDFKCS {
|
|
|
|
int order;
|
|
StringName id;
|
|
int pos;
|
|
|
|
bool operator<(const _GDFKCS &p_r) const {
|
|
|
|
return order<p_r.order;
|
|
}
|
|
};
|
|
|
|
void GDFunction::debug_get_stack_member_state(int p_line,List<Pair<StringName,int> > *r_stackvars) const {
|
|
|
|
|
|
int oc=0;
|
|
Map<StringName,_GDFKC> sdmap;
|
|
for( const List<StackDebug>::Element *E=stack_debug.front();E;E=E->next()) {
|
|
|
|
const StackDebug &sd=E->get();
|
|
if (sd.line>p_line)
|
|
break;
|
|
|
|
if (sd.added) {
|
|
|
|
if (!sdmap.has(sd.identifier)) {
|
|
_GDFKC d;
|
|
d.order=oc++;
|
|
d.pos.push_back(sd.pos);
|
|
sdmap[sd.identifier]=d;
|
|
|
|
} else {
|
|
sdmap[sd.identifier].pos.push_back(sd.pos);
|
|
}
|
|
} else {
|
|
|
|
|
|
ERR_CONTINUE(!sdmap.has(sd.identifier));
|
|
|
|
sdmap[sd.identifier].pos.pop_back();
|
|
if (sdmap[sd.identifier].pos.empty())
|
|
sdmap.erase(sd.identifier);
|
|
}
|
|
|
|
}
|
|
|
|
|
|
List<_GDFKCS> stackpositions;
|
|
for(Map<StringName,_GDFKC>::Element *E=sdmap.front();E;E=E->next() ) {
|
|
|
|
_GDFKCS spp;
|
|
spp.id=E->key();
|
|
spp.order=E->get().order;
|
|
spp.pos=E->get().pos.back()->get();
|
|
stackpositions.push_back(spp);
|
|
}
|
|
|
|
stackpositions.sort();
|
|
|
|
for(List<_GDFKCS>::Element *E=stackpositions.front();E;E=E->next()) {
|
|
|
|
Pair<StringName,int> p;
|
|
p.first=E->get().id;
|
|
p.second=E->get().pos;
|
|
r_stackvars->push_back(p);
|
|
}
|
|
|
|
|
|
}
|
|
|
|
#if 0
|
|
void GDFunction::clear() {
|
|
|
|
name=StringName();
|
|
constants.clear();
|
|
_stack_size=0;
|
|
code.clear();
|
|
_constants_ptr=NULL;
|
|
_constant_count=0;
|
|
_global_names_ptr=NULL;
|
|
_global_names_count=0;
|
|
_code_ptr=NULL;
|
|
_code_size=0;
|
|
|
|
}
|
|
#endif
|
|
GDFunction::GDFunction() {
|
|
|
|
_stack_size=0;
|
|
_call_size=0;
|
|
name="<anonymous>";
|
|
|
|
}
|
|
|
|
GDNativeClass::GDNativeClass(const StringName& p_name) {
|
|
|
|
name=p_name;
|
|
}
|
|
|
|
/*void GDNativeClass::call_multilevel(const StringName& p_method,const Variant** p_args,int p_argcount){
|
|
|
|
|
|
}*/
|
|
|
|
|
|
bool GDNativeClass::_get(const StringName& p_name,Variant &r_ret) const {
|
|
|
|
bool ok;
|
|
int v = ObjectTypeDB::get_integer_constant(name, p_name, &ok);
|
|
|
|
if (ok) {
|
|
r_ret=v;
|
|
return true;
|
|
} else {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
|
|
void GDNativeClass::_bind_methods() {
|
|
|
|
ObjectTypeDB::bind_method(_MD("new"),&GDNativeClass::_new);
|
|
|
|
}
|
|
|
|
Variant GDNativeClass::_new() {
|
|
|
|
Object *o = instance();
|
|
if (!o) {
|
|
ERR_EXPLAIN("Class type: '"+String(name)+"' is not instantiable.");
|
|
ERR_FAIL_COND_V(!o,Variant());
|
|
}
|
|
|
|
Reference *ref = o->cast_to<Reference>();
|
|
if (ref) {
|
|
return REF(ref);
|
|
} else {
|
|
return o;
|
|
}
|
|
|
|
}
|
|
|
|
Object *GDNativeClass::instance() {
|
|
|
|
return ObjectTypeDB::instance(name);
|
|
}
|
|
|
|
|
|
|
|
GDInstance* GDScript::_create_instance(const Variant** p_args,int p_argcount,Object *p_owner,bool p_isref) {
|
|
|
|
|
|
/* STEP 1, CREATE */
|
|
|
|
GDInstance* instance = memnew( GDInstance );
|
|
instance->base_ref=p_isref;
|
|
instance->members.resize(member_indices.size());
|
|
instance->script=Ref<GDScript>(this);
|
|
instance->owner=p_owner;
|
|
instance->owner->set_script_instance(instance);
|
|
|
|
/* STEP 2, INITIALIZE AND CONSRTUCT */
|
|
|
|
instances.insert(instance->owner);
|
|
|
|
Variant::CallError err;
|
|
initializer->call(instance,p_args,p_argcount,err);
|
|
|
|
if (err.error!=Variant::CallError::CALL_OK) {
|
|
instance->script=Ref<GDScript>();
|
|
instances.erase(p_owner);
|
|
memdelete(instance);
|
|
ERR_FAIL_COND_V(err.error!=Variant::CallError::CALL_OK, NULL); //error consrtucting
|
|
}
|
|
|
|
//@TODO make thread safe
|
|
return instance;
|
|
|
|
}
|
|
|
|
Variant GDScript::_new(const Variant** p_args,int p_argcount,Variant::CallError& r_error) {
|
|
|
|
/* STEP 1, CREATE */
|
|
|
|
r_error.error=Variant::CallError::CALL_OK;
|
|
REF ref;
|
|
Object *owner=NULL;
|
|
|
|
GDScript *_baseptr=this;
|
|
while (_baseptr->_base) {
|
|
_baseptr=_baseptr->_base;
|
|
}
|
|
|
|
if (_baseptr->native.ptr()) {
|
|
owner=_baseptr->native->instance();
|
|
} else {
|
|
owner=memnew( Reference ); //by default, no base means use reference
|
|
}
|
|
|
|
Reference *r=owner->cast_to<Reference>();
|
|
if (r) {
|
|
ref=REF(r);
|
|
}
|
|
|
|
|
|
GDInstance* instance = _create_instance(p_args,p_argcount,owner,r!=NULL);
|
|
if (!instance) {
|
|
if (ref.is_null()) {
|
|
memdelete(owner); //no owner, sorry
|
|
}
|
|
return Variant();
|
|
}
|
|
|
|
if (ref.is_valid()) {
|
|
return ref;
|
|
} else {
|
|
return owner;
|
|
}
|
|
}
|
|
|
|
bool GDScript::can_instance() const {
|
|
|
|
return valid; //any script in GDscript can instance
|
|
}
|
|
|
|
StringName GDScript::get_instance_base_type() const {
|
|
|
|
if (native.is_valid())
|
|
return native->get_name();
|
|
if (base.is_valid())
|
|
return base->get_instance_base_type();
|
|
return StringName();
|
|
}
|
|
|
|
struct _GDScriptMemberSort {
|
|
|
|
int index;
|
|
StringName name;
|
|
_FORCE_INLINE_ bool operator<(const _GDScriptMemberSort& p_member) const { return index < p_member.index; }
|
|
|
|
};
|
|
|
|
|
|
#ifdef TOOLS_ENABLED
|
|
|
|
|
|
void GDScript::_placeholder_erased(PlaceHolderScriptInstance *p_placeholder) {
|
|
|
|
placeholders.erase(p_placeholder);
|
|
}
|
|
|
|
void GDScript::_update_placeholder(PlaceHolderScriptInstance *p_placeholder) {
|
|
|
|
|
|
List<PropertyInfo> plist;
|
|
GDScript *scr=this;
|
|
|
|
Map<StringName,Variant> default_values;
|
|
while(scr) {
|
|
|
|
Vector<_GDScriptMemberSort> msort;
|
|
for(Map<StringName,PropertyInfo>::Element *E=scr->member_info.front();E;E=E->next()) {
|
|
|
|
_GDScriptMemberSort ms;
|
|
ERR_CONTINUE(!scr->member_indices.has(E->key()));
|
|
ms.index=scr->member_indices[E->key()];
|
|
ms.name=E->key();
|
|
|
|
msort.push_back(ms);
|
|
|
|
}
|
|
|
|
msort.sort();
|
|
msort.invert();
|
|
for(int i=0;i<msort.size();i++) {
|
|
|
|
plist.push_front(scr->member_info[msort[i].name]);
|
|
if (scr->member_default_values.has(msort[i].name))
|
|
default_values[msort[i].name]=scr->member_default_values[msort[i].name];
|
|
else {
|
|
Variant::CallError err;
|
|
default_values[msort[i].name]=Variant::construct(scr->member_info[msort[i].name].type,NULL,0,err);
|
|
}
|
|
}
|
|
|
|
scr=scr->_base;
|
|
}
|
|
|
|
|
|
p_placeholder->update(plist,default_values);
|
|
|
|
}
|
|
#endif
|
|
ScriptInstance* GDScript::instance_create(Object *p_this) {
|
|
|
|
if (!tool && !ScriptServer::is_scripting_enabled()) {
|
|
|
|
#ifdef TOOLS_ENABLED
|
|
|
|
//instance a fake script for editing the values
|
|
//plist.invert();
|
|
|
|
/*print_line("CREATING PLACEHOLDER");
|
|
for(List<PropertyInfo>::Element *E=plist.front();E;E=E->next()) {
|
|
print_line(E->get().name);
|
|
}*/
|
|
PlaceHolderScriptInstance *si = memnew( PlaceHolderScriptInstance(GDScriptLanguage::get_singleton(),Ref<Script>(this),p_this) );
|
|
placeholders.insert(si);
|
|
_update_placeholder(si);
|
|
return si;
|
|
#else
|
|
return NULL;
|
|
#endif
|
|
}
|
|
|
|
GDScript *top=this;
|
|
while(top->_base)
|
|
top=top->_base;
|
|
|
|
if (top->native.is_valid()) {
|
|
if (!ObjectTypeDB::is_type(p_this->get_type_name(),top->native->get_name())) {
|
|
|
|
if (ScriptDebugger::get_singleton()) {
|
|
GDScriptLanguage::get_singleton()->debug_break_parse(get_path(),0,"Script inherits from native type '"+String(top->native->get_name())+"', so it can't be instanced in object of type: '"+p_this->get_type()+"'");
|
|
}
|
|
ERR_EXPLAIN("Script inherits from native type '"+String(top->native->get_name())+"', so it can't be instanced in object of type: '"+p_this->get_type()+"'");
|
|
ERR_FAIL_V(NULL);
|
|
|
|
}
|
|
}
|
|
|
|
return _create_instance(NULL,0,p_this,p_this->cast_to<Reference>());
|
|
|
|
}
|
|
bool GDScript::instance_has(const Object *p_this) const {
|
|
|
|
return instances.has((Object*)p_this);
|
|
}
|
|
|
|
bool GDScript::has_source_code() const {
|
|
|
|
return source!="";
|
|
}
|
|
String GDScript::get_source_code() const {
|
|
|
|
return source;
|
|
}
|
|
void GDScript::set_source_code(const String& p_code) {
|
|
|
|
source=p_code;
|
|
|
|
}
|
|
|
|
void GDScript::_set_subclass_path(Ref<GDScript>& p_sc,const String& p_path) {
|
|
|
|
p_sc->path=p_path;
|
|
for(Map<StringName,Ref<GDScript> >::Element *E=p_sc->subclasses.front();E;E=E->next()) {
|
|
|
|
_set_subclass_path(E->get(),p_path);
|
|
}
|
|
}
|
|
|
|
Error GDScript::reload() {
|
|
|
|
|
|
ERR_FAIL_COND_V(instances.size(),ERR_ALREADY_IN_USE);
|
|
|
|
String basedir=path;
|
|
|
|
if (basedir=="")
|
|
basedir=get_path();
|
|
|
|
if (basedir!="")
|
|
basedir=basedir.get_base_dir();
|
|
|
|
|
|
|
|
valid=false;
|
|
GDParser parser;
|
|
Error err = parser.parse(source,basedir);
|
|
if (err) {
|
|
if (ScriptDebugger::get_singleton()) {
|
|
GDScriptLanguage::get_singleton()->debug_break_parse(get_path(),parser.get_error_line(),"Parser Error: "+parser.get_error());
|
|
}
|
|
_err_print_error("GDScript::reload",path.empty()?"built-in":(const char*)path.utf8().get_data(),parser.get_error_line(),("Parse Error: "+parser.get_error()).utf8().get_data());
|
|
ERR_FAIL_V(ERR_PARSE_ERROR);
|
|
}
|
|
|
|
GDCompiler compiler;
|
|
err = compiler.compile(&parser,this);
|
|
|
|
if (err) {
|
|
if (ScriptDebugger::get_singleton()) {
|
|
GDScriptLanguage::get_singleton()->debug_break_parse(get_path(),compiler.get_error_line(),"Parser Error: "+compiler.get_error());
|
|
}
|
|
_err_print_error("GDScript::reload",path.empty()?"built-in":(const char*)path.utf8().get_data(),compiler.get_error_line(),("Compile Error: "+compiler.get_error()).utf8().get_data());
|
|
ERR_FAIL_V(ERR_COMPILATION_FAILED);
|
|
}
|
|
|
|
valid=true;
|
|
|
|
for(Map<StringName,Ref<GDScript> >::Element *E=subclasses.front();E;E=E->next()) {
|
|
|
|
_set_subclass_path(E->get(),path);
|
|
}
|
|
|
|
#ifdef TOOLS_ENABLED
|
|
for (Set<PlaceHolderScriptInstance*>::Element *E=placeholders.front();E;E=E->next()) {
|
|
|
|
_update_placeholder(E->get());
|
|
}
|
|
#endif
|
|
return OK;
|
|
}
|
|
|
|
String GDScript::get_node_type() const {
|
|
|
|
return ""; // ?
|
|
}
|
|
|
|
ScriptLanguage *GDScript::get_language() const {
|
|
|
|
return GDScriptLanguage::get_singleton();
|
|
}
|
|
|
|
|
|
Variant GDScript::call(const StringName& p_method,const Variant** p_args,int p_argcount,Variant::CallError &r_error) {
|
|
|
|
|
|
GDScript *top=this;
|
|
while(top) {
|
|
|
|
Map<StringName,GDFunction>::Element *E=top->member_functions.find(p_method);
|
|
if (E) {
|
|
|
|
if (!E->get().is_static()) {
|
|
WARN_PRINT(String("Can't call non-static function: '"+String(p_method)+"' in script.").utf8().get_data());
|
|
}
|
|
|
|
return E->get().call(NULL,p_args,p_argcount,r_error);
|
|
}
|
|
top=top->_base;
|
|
}
|
|
|
|
//none found, regular
|
|
|
|
return Script::call(p_method,p_args,p_argcount,r_error);
|
|
|
|
}
|
|
|
|
bool GDScript::_get(const StringName& p_name,Variant &r_ret) const {
|
|
|
|
{
|
|
|
|
|
|
const GDScript *top=this;
|
|
while(top) {
|
|
|
|
{
|
|
const Map<StringName,Variant>::Element *E=top->constants.find(p_name);
|
|
if (E) {
|
|
|
|
r_ret= E->get();
|
|
return true;
|
|
}
|
|
}
|
|
|
|
{
|
|
const Map<StringName,Ref<GDScript> >::Element *E=subclasses.find(p_name);
|
|
if (E) {
|
|
|
|
r_ret=E->get();
|
|
return true;
|
|
}
|
|
}
|
|
top=top->_base;
|
|
}
|
|
|
|
if (p_name==GDScriptLanguage::get_singleton()->strings._script_source) {
|
|
|
|
r_ret=get_source_code();
|
|
return true;
|
|
}
|
|
}
|
|
|
|
|
|
|
|
return false;
|
|
|
|
}
|
|
bool GDScript::_set(const StringName& p_name, const Variant& p_value) {
|
|
|
|
if (p_name==GDScriptLanguage::get_singleton()->strings._script_source) {
|
|
|
|
set_source_code(p_value);
|
|
reload();
|
|
} else
|
|
return false;
|
|
|
|
return true;
|
|
}
|
|
|
|
void GDScript::_get_property_list(List<PropertyInfo> *p_properties) const {
|
|
|
|
p_properties->push_back( PropertyInfo(Variant::STRING,"script/source",PROPERTY_HINT_NONE,"",PROPERTY_USAGE_NOEDITOR) );
|
|
}
|
|
|
|
|
|
void GDScript::_bind_methods() {
|
|
|
|
ObjectTypeDB::bind_native_method(METHOD_FLAGS_DEFAULT,"new",&GDScript::_new,MethodInfo("new"));
|
|
|
|
}
|
|
|
|
|
|
|
|
Error GDScript::load_byte_code(const String& p_path) {
|
|
|
|
Vector<uint8_t> bytecode = FileAccess::get_file_as_array(p_path);
|
|
ERR_FAIL_COND_V(bytecode.size()==0,ERR_PARSE_ERROR);
|
|
path=p_path;
|
|
|
|
String basedir=path;
|
|
|
|
if (basedir=="")
|
|
basedir=get_path();
|
|
|
|
if (basedir!="")
|
|
basedir=basedir.get_base_dir();
|
|
|
|
valid=false;
|
|
GDParser parser;
|
|
Error err = parser.parse_bytecode(bytecode,basedir);
|
|
if (err) {
|
|
_err_print_error("GDScript::load_byte_code",path.empty()?"built-in":(const char*)path.utf8().get_data(),parser.get_error_line(),("Parse Error: "+parser.get_error()).utf8().get_data());
|
|
ERR_FAIL_V(ERR_PARSE_ERROR);
|
|
}
|
|
|
|
GDCompiler compiler;
|
|
err = compiler.compile(&parser,this);
|
|
|
|
if (err) {
|
|
_err_print_error("GDScript::load_byte_code",path.empty()?"built-in":(const char*)path.utf8().get_data(),compiler.get_error_line(),("Compile Error: "+compiler.get_error()).utf8().get_data());
|
|
ERR_FAIL_V(ERR_COMPILATION_FAILED);
|
|
}
|
|
|
|
valid=true;
|
|
|
|
for(Map<StringName,Ref<GDScript> >::Element *E=subclasses.front();E;E=E->next()) {
|
|
|
|
_set_subclass_path(E->get(),path);
|
|
}
|
|
|
|
return OK;
|
|
}
|
|
|
|
|
|
Error GDScript::load_source_code(const String& p_path) {
|
|
|
|
|
|
DVector<uint8_t> sourcef;
|
|
Error err;
|
|
FileAccess *f=FileAccess::open(p_path,FileAccess::READ,&err);
|
|
if (err) {
|
|
|
|
ERR_FAIL_COND_V(err,err);
|
|
}
|
|
|
|
int len = f->get_len();
|
|
sourcef.resize(len+1);
|
|
DVector<uint8_t>::Write w = sourcef.write();
|
|
int r = f->get_buffer(w.ptr(),len);
|
|
f->close();
|
|
memdelete(f);
|
|
ERR_FAIL_COND_V(r!=len,ERR_CANT_OPEN);
|
|
w[len]=0;
|
|
|
|
String s;
|
|
if (s.parse_utf8((const char*)w.ptr())) {
|
|
|
|
ERR_EXPLAIN("Script '"+p_path+"' contains invalid unicode (utf-8), so it was not loaded. Please ensure that scripts are saved in valid utf-8 unicode.");
|
|
ERR_FAIL_V(ERR_INVALID_DATA);
|
|
}
|
|
|
|
source=s;
|
|
path=p_path;
|
|
return OK;
|
|
|
|
}
|
|
|
|
|
|
const Map<StringName,GDFunction>& GDScript::debug_get_member_functions() const {
|
|
|
|
return member_functions;
|
|
}
|
|
|
|
|
|
|
|
StringName GDScript::debug_get_member_by_index(int p_idx) const {
|
|
|
|
|
|
for(const Map<StringName,int>::Element *E=member_indices.front();E;E=E->next()) {
|
|
|
|
if (E->get()==p_idx)
|
|
return E->key();
|
|
}
|
|
|
|
return "<error>";
|
|
}
|
|
|
|
|
|
Ref<GDScript> GDScript::get_base() const {
|
|
|
|
return base;
|
|
}
|
|
|
|
GDScript::GDScript() {
|
|
|
|
|
|
_static_ref=this;
|
|
valid=false;
|
|
subclass_count=0;
|
|
initializer=NULL;
|
|
_base=NULL;
|
|
_owner=NULL;
|
|
tool=false;
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
//////////////////////////////
|
|
// INSTANCE //
|
|
//////////////////////////////
|
|
|
|
|
|
bool GDInstance::set(const StringName& p_name, const Variant& p_value) {
|
|
|
|
//member
|
|
{
|
|
const Map<StringName,int>::Element *E = script->member_indices.find(p_name);
|
|
if (E) {
|
|
members[E->get()]=p_value;
|
|
return true;
|
|
|
|
}
|
|
}
|
|
|
|
GDScript *sptr=script.ptr();
|
|
while(sptr) {
|
|
|
|
|
|
Map<StringName,GDFunction>::Element *E = sptr->member_functions.find(GDScriptLanguage::get_singleton()->strings._set);
|
|
if (E) {
|
|
|
|
Variant name=p_name;
|
|
const Variant *args[2]={&name,&p_value};
|
|
|
|
Variant::CallError err;
|
|
Variant ret = E->get().call(this,(const Variant**)args,2,err);
|
|
if (err.error==Variant::CallError::CALL_OK && ret.get_type()==Variant::BOOL && ret.operator bool())
|
|
return true;
|
|
}
|
|
sptr = sptr->_base;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
bool GDInstance::get(const StringName& p_name, Variant &r_ret) const {
|
|
|
|
|
|
|
|
const GDScript *sptr=script.ptr();
|
|
while(sptr) {
|
|
|
|
{
|
|
const Map<StringName,int>::Element *E = script->member_indices.find(p_name);
|
|
if (E) {
|
|
r_ret=members[E->get()];
|
|
return true; //index found
|
|
|
|
}
|
|
}
|
|
|
|
{
|
|
const Map<StringName,Variant>::Element *E = script->constants.find(p_name);
|
|
if (E) {
|
|
r_ret=E->get();
|
|
return true; //index found
|
|
|
|
}
|
|
}
|
|
|
|
{
|
|
const Map<StringName,GDFunction>::Element *E = sptr->member_functions.find(GDScriptLanguage::get_singleton()->strings._get);
|
|
if (E) {
|
|
|
|
Variant name=p_name;
|
|
const Variant *args[1]={&name};
|
|
|
|
Variant::CallError err;
|
|
Variant ret = const_cast<GDFunction*>(&E->get())->call(const_cast<GDInstance*>(this),(const Variant**)args,1,err);
|
|
if (err.error==Variant::CallError::CALL_OK && ret.get_type()!=Variant::NIL) {
|
|
r_ret=ret;
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
sptr = sptr->_base;
|
|
}
|
|
|
|
return false;
|
|
|
|
}
|
|
void GDInstance::get_property_list(List<PropertyInfo> *p_properties) const {
|
|
// exported members, not doen yet!
|
|
|
|
const GDScript *sptr=script.ptr();
|
|
List<PropertyInfo> props;
|
|
|
|
while(sptr) {
|
|
|
|
|
|
const Map<StringName,GDFunction>::Element *E = sptr->member_functions.find(GDScriptLanguage::get_singleton()->strings._get_property_list);
|
|
if (E) {
|
|
|
|
|
|
Variant::CallError err;
|
|
Variant ret = const_cast<GDFunction*>(&E->get())->call(const_cast<GDInstance*>(this),NULL,0,err);
|
|
if (err.error==Variant::CallError::CALL_OK) {
|
|
|
|
if (ret.get_type()!=Variant::ARRAY) {
|
|
|
|
ERR_EXPLAIN("Wrong type for _get_property list, must be an array of dictionaries.");
|
|
ERR_FAIL();
|
|
}
|
|
Array arr = ret;
|
|
for(int i=0;i<arr.size();i++) {
|
|
|
|
Dictionary d = arr[i];
|
|
ERR_CONTINUE(!d.has("name"));
|
|
ERR_CONTINUE(!d.has("type"));
|
|
PropertyInfo pinfo;
|
|
pinfo.type = Variant::Type( d["type"].operator int());
|
|
ERR_CONTINUE(pinfo.type<0 || pinfo.type>=Variant::VARIANT_MAX );
|
|
pinfo.name = d["name"];
|
|
ERR_CONTINUE(pinfo.name=="");
|
|
if (d.has("hint"))
|
|
pinfo.hint=PropertyHint(d["hint"].operator int());
|
|
if (d.has("hint_string"))
|
|
pinfo.hint_string=d["hint_string"];
|
|
if (d.has("usage"))
|
|
pinfo.usage=d["usage"];
|
|
|
|
props.push_back(pinfo);
|
|
|
|
}
|
|
|
|
}
|
|
}
|
|
|
|
//instance a fake script for editing the values
|
|
|
|
Vector<_GDScriptMemberSort> msort;
|
|
for(Map<StringName,PropertyInfo>::Element *E=sptr->member_info.front();E;E=E->next()) {
|
|
|
|
_GDScriptMemberSort ms;
|
|
ERR_CONTINUE(!sptr->member_indices.has(E->key()));
|
|
ms.index=sptr->member_indices[E->key()];
|
|
ms.name=E->key();
|
|
msort.push_back(ms);
|
|
|
|
}
|
|
|
|
msort.sort();
|
|
msort.invert();
|
|
for(int i=0;i<msort.size();i++) {
|
|
|
|
props.push_front(sptr->member_info[msort[i].name]);
|
|
|
|
}
|
|
#if 0
|
|
if (sptr->member_functions.has("_get_property_list")) {
|
|
|
|
Variant::CallError err;
|
|
GDFunction *f = const_cast<GDFunction*>(&sptr->member_functions["_get_property_list"]);
|
|
Variant plv = f->call(const_cast<GDInstance*>(this),NULL,0,err);
|
|
|
|
if (plv.get_type()!=Variant::ARRAY) {
|
|
|
|
ERR_PRINT("_get_property_list: expected array returned");
|
|
} else {
|
|
|
|
Array pl=plv;
|
|
|
|
for(int i=0;i<pl.size();i++) {
|
|
|
|
Dictionary p = pl[i];
|
|
PropertyInfo pinfo;
|
|
if (!p.has("name")) {
|
|
ERR_PRINT("_get_property_list: expected 'name' key of type string.")
|
|
continue;
|
|
}
|
|
if (!p.has("type")) {
|
|
ERR_PRINT("_get_property_list: expected 'type' key of type integer.")
|
|
continue;
|
|
}
|
|
pinfo.name=p["name"];
|
|
pinfo.type=Variant::Type(int(p["type"]));
|
|
if (p.has("hint"))
|
|
pinfo.hint=PropertyHint(int(p["hint"]));
|
|
if (p.has("hint_string"))
|
|
pinfo.hint_string=p["hint_string"];
|
|
if (p.has("usage"))
|
|
pinfo.usage=p["usage"];
|
|
|
|
|
|
props.push_back(pinfo);
|
|
}
|
|
}
|
|
}
|
|
#endif
|
|
|
|
sptr = sptr->_base;
|
|
}
|
|
|
|
//props.invert();
|
|
|
|
for (List<PropertyInfo>::Element *E=props.front();E;E=E->next()) {
|
|
|
|
p_properties->push_back(E->get());
|
|
}
|
|
}
|
|
|
|
void GDInstance::get_method_list(List<MethodInfo> *p_list) const {
|
|
|
|
const GDScript *sptr=script.ptr();
|
|
while(sptr) {
|
|
|
|
for (Map<StringName,GDFunction>::Element *E = sptr->member_functions.front();E;E=E->next()) {
|
|
|
|
MethodInfo mi;
|
|
mi.name=E->key();
|
|
for(int i=0;i<E->get().get_argument_count();i++)
|
|
mi.arguments.push_back(PropertyInfo(Variant::NIL,"arg"+itos(i)));
|
|
p_list->push_back(mi);
|
|
}
|
|
sptr = sptr->_base;
|
|
}
|
|
|
|
}
|
|
|
|
bool GDInstance::has_method(const StringName& p_method) const {
|
|
|
|
const GDScript *sptr=script.ptr();
|
|
while(sptr) {
|
|
const Map<StringName,GDFunction>::Element *E = sptr->member_functions.find(p_method);
|
|
if (E)
|
|
return true;
|
|
sptr = sptr->_base;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
Variant GDInstance::call(const StringName& p_method,const Variant** p_args,int p_argcount,Variant::CallError &r_error) {
|
|
|
|
//printf("calling %ls:%i method %ls\n", script->get_path().c_str(), -1, String(p_method).c_str());
|
|
|
|
GDScript *sptr=script.ptr();
|
|
while(sptr) {
|
|
Map<StringName,GDFunction>::Element *E = sptr->member_functions.find(p_method);
|
|
if (E) {
|
|
return E->get().call(this,p_args,p_argcount,r_error);
|
|
}
|
|
sptr = sptr->_base;
|
|
}
|
|
r_error.error=Variant::CallError::CALL_ERROR_INVALID_METHOD;
|
|
return Variant();
|
|
}
|
|
|
|
void GDInstance::call_multilevel(const StringName& p_method,const Variant** p_args,int p_argcount) {
|
|
|
|
GDScript *sptr=script.ptr();
|
|
Variant::CallError ce;
|
|
|
|
while(sptr) {
|
|
Map<StringName,GDFunction>::Element *E = sptr->member_functions.find(p_method);
|
|
if (E) {
|
|
E->get().call(this,p_args,p_argcount,ce);
|
|
}
|
|
sptr = sptr->_base;
|
|
}
|
|
|
|
}
|
|
|
|
|
|
void GDInstance::_ml_call_reversed(GDScript *sptr,const StringName& p_method,const Variant** p_args,int p_argcount) {
|
|
|
|
if (sptr->_base)
|
|
_ml_call_reversed(sptr->_base,p_method,p_args,p_argcount);
|
|
|
|
Variant::CallError ce;
|
|
|
|
Map<StringName,GDFunction>::Element *E = sptr->member_functions.find(p_method);
|
|
if (E) {
|
|
E->get().call(this,p_args,p_argcount,ce);
|
|
}
|
|
|
|
}
|
|
|
|
void GDInstance::call_multilevel_reversed(const StringName& p_method,const Variant** p_args,int p_argcount) {
|
|
|
|
if (script.ptr()) {
|
|
_ml_call_reversed(script.ptr(),p_method,p_args,p_argcount);
|
|
}
|
|
}
|
|
|
|
void GDInstance::notification(int p_notification) {
|
|
|
|
//notification is not virutal, it gets called at ALL levels just like in C.
|
|
Variant value=p_notification;
|
|
const Variant *args[1]={&value };
|
|
|
|
GDScript *sptr=script.ptr();
|
|
while(sptr) {
|
|
Map<StringName,GDFunction>::Element *E = sptr->member_functions.find(GDScriptLanguage::get_singleton()->strings._notification);
|
|
if (E) {
|
|
Variant::CallError err;
|
|
E->get().call(this,args,1,err);
|
|
if (err.error!=Variant::CallError::CALL_OK) {
|
|
//print error about notification call
|
|
|
|
}
|
|
}
|
|
sptr = sptr->_base;
|
|
}
|
|
|
|
}
|
|
|
|
Ref<Script> GDInstance::get_script() const {
|
|
|
|
return script;
|
|
}
|
|
|
|
ScriptLanguage *GDInstance::get_language() {
|
|
|
|
return GDScriptLanguage::get_singleton();
|
|
}
|
|
|
|
|
|
GDInstance::GDInstance() {
|
|
owner=NULL;
|
|
base_ref=false;
|
|
}
|
|
|
|
GDInstance::~GDInstance() {
|
|
if (script.is_valid() && owner) {
|
|
script->instances.erase(owner);
|
|
}
|
|
}
|
|
|
|
/************* SCRIPT LANGUAGE **************/
|
|
/************* SCRIPT LANGUAGE **************/
|
|
/************* SCRIPT LANGUAGE **************/
|
|
/************* SCRIPT LANGUAGE **************/
|
|
/************* SCRIPT LANGUAGE **************/
|
|
|
|
GDScriptLanguage *GDScriptLanguage::singleton=NULL;
|
|
|
|
|
|
String GDScriptLanguage::get_name() const {
|
|
|
|
return "GDScript";
|
|
}
|
|
|
|
/* LANGUAGE FUNCTIONS */
|
|
|
|
void GDScriptLanguage::_add_global(const StringName& p_name,const Variant& p_value) {
|
|
|
|
|
|
if (globals.has(p_name)) {
|
|
//overwrite existing
|
|
global_array[globals[p_name]]=p_value;
|
|
return;
|
|
}
|
|
globals[p_name]=global_array.size();
|
|
global_array.push_back(p_value);
|
|
_global_array=global_array.ptr();
|
|
}
|
|
|
|
void GDScriptLanguage::init() {
|
|
|
|
|
|
//populate global constants
|
|
int gcc=GlobalConstants::get_global_constant_count();
|
|
for(int i=0;i<gcc;i++) {
|
|
|
|
_add_global(StaticCString::create(GlobalConstants::get_global_constant_name(i)),GlobalConstants::get_global_constant_value(i));
|
|
}
|
|
|
|
_add_global(StaticCString::create("PI"),Math_PI);
|
|
|
|
//populate native classes
|
|
|
|
List<String> class_list;
|
|
ObjectTypeDB::get_type_list(&class_list);
|
|
for(List<String>::Element *E=class_list.front();E;E=E->next()) {
|
|
|
|
StringName n = E->get();
|
|
String s = String(n);
|
|
if (s.begins_with("_"))
|
|
n=s.substr(1,s.length());
|
|
|
|
if (globals.has(n))
|
|
continue;
|
|
Ref<GDNativeClass> nc = memnew( GDNativeClass(E->get()) );
|
|
_add_global(n,nc);
|
|
}
|
|
|
|
//populate singletons
|
|
|
|
List<Globals::Singleton> singletons;
|
|
Globals::get_singleton()->get_singletons(&singletons);
|
|
for(List<Globals::Singleton>::Element *E=singletons.front();E;E=E->next()) {
|
|
|
|
_add_global(E->get().name,E->get().ptr);
|
|
}
|
|
}
|
|
|
|
String GDScriptLanguage::get_type() const {
|
|
|
|
return "GDScript";
|
|
}
|
|
String GDScriptLanguage::get_extension() const {
|
|
|
|
return "gd";
|
|
}
|
|
Error GDScriptLanguage::execute_file(const String& p_path) {
|
|
|
|
// ??
|
|
return OK;
|
|
}
|
|
void GDScriptLanguage::finish() {
|
|
|
|
|
|
}
|
|
|
|
|
|
void GDScriptLanguage::frame() {
|
|
|
|
// print_line("calls: "+itos(calls));
|
|
calls=0;
|
|
}
|
|
|
|
/* EDITOR FUNCTIONS */
|
|
void GDScriptLanguage::get_reserved_words(List<String> *p_words) const {
|
|
|
|
static const char *_reserved_words[]={
|
|
"break",
|
|
"class",
|
|
"continue",
|
|
"const",
|
|
"else",
|
|
"elif",
|
|
"enum",
|
|
"extends" ,
|
|
"for" ,
|
|
"func" ,
|
|
"if" ,
|
|
"in" ,
|
|
"varl",
|
|
"null" ,
|
|
"return" ,
|
|
"self" ,
|
|
"while" ,
|
|
"true" ,
|
|
"false" ,
|
|
"tool",
|
|
"var",
|
|
"pass",
|
|
"and",
|
|
"or",
|
|
"export",
|
|
"assert",
|
|
0};
|
|
|
|
|
|
const char **w=_reserved_words;
|
|
|
|
|
|
while (*w) {
|
|
|
|
p_words->push_back(*w);
|
|
w++;
|
|
}
|
|
|
|
for(int i=0;i<GDFunctions::FUNC_MAX;i++) {
|
|
p_words->push_back(GDFunctions::get_func_name(GDFunctions::Function(i)));
|
|
}
|
|
|
|
}
|
|
|
|
GDScriptLanguage::GDScriptLanguage() {
|
|
|
|
calls=0;
|
|
ERR_FAIL_COND(singleton);
|
|
singleton=this;
|
|
strings._init = StaticCString::create("_init");
|
|
strings._notification = StaticCString::create("_notification");
|
|
strings._set= StaticCString::create("_set");
|
|
strings._get= StaticCString::create("_get");
|
|
strings._get_property_list= StaticCString::create("_get_property_list");
|
|
strings._script_source=StaticCString::create("script/source");
|
|
_debug_parse_err_line=-1;
|
|
_debug_parse_err_file="";
|
|
|
|
_debug_call_stack_pos=0;
|
|
int dmcs=GLOBAL_DEF("debug/script_max_call_stack",1024);
|
|
if (ScriptDebugger::get_singleton()) {
|
|
//debugging enabled!
|
|
|
|
_debug_max_call_stack = dmcs;
|
|
if (_debug_max_call_stack<1024)
|
|
_debug_max_call_stack=1024;
|
|
_call_stack = memnew_arr( CallLevel, _debug_max_call_stack+1 );
|
|
|
|
} else {
|
|
_debug_max_call_stack=0;
|
|
_call_stack=NULL;
|
|
}
|
|
|
|
}
|
|
|
|
|
|
GDScriptLanguage::~GDScriptLanguage() {
|
|
|
|
if (_call_stack) {
|
|
memdelete_arr(_call_stack);
|
|
}
|
|
singleton=NULL;
|
|
}
|
|
|
|
/*************** RESOURCE ***************/
|
|
|
|
RES ResourceFormatLoaderGDScript::load(const String &p_path,const String& p_original_path) {
|
|
|
|
GDScript *script = memnew( GDScript );
|
|
|
|
Ref<GDScript> scriptres(script);
|
|
|
|
if (p_path.ends_with(".gdc")) {
|
|
|
|
script->set_script_path(p_original_path); // script needs this.
|
|
script->set_path(p_original_path);
|
|
Error err = script->load_byte_code(p_path);
|
|
|
|
|
|
if (err!=OK) {
|
|
|
|
ERR_FAIL_COND_V(err!=OK, RES());
|
|
}
|
|
|
|
} else {
|
|
Error err = script->load_source_code(p_path);
|
|
|
|
if (err!=OK) {
|
|
|
|
ERR_FAIL_COND_V(err!=OK, RES());
|
|
}
|
|
|
|
script->set_script_path(p_original_path); // script needs this.
|
|
script->set_path(p_original_path);
|
|
//script->set_name(p_path.get_file());
|
|
|
|
script->reload();
|
|
}
|
|
|
|
return scriptres;
|
|
}
|
|
void ResourceFormatLoaderGDScript::get_recognized_extensions(List<String> *p_extensions) const {
|
|
|
|
p_extensions->push_back("gd");
|
|
p_extensions->push_back("gdc");
|
|
}
|
|
|
|
bool ResourceFormatLoaderGDScript::handles_type(const String& p_type) const {
|
|
|
|
return (p_type=="Script" || p_type=="GDScript");
|
|
}
|
|
|
|
String ResourceFormatLoaderGDScript::get_resource_type(const String &p_path) const {
|
|
|
|
String el = p_path.extension().to_lower();
|
|
if (el=="gd" || el=="gdc")
|
|
return "GDScript";
|
|
return "";
|
|
}
|
|
|
|
|
|
Error ResourceFormatSaverGDScript::save(const String &p_path,const RES& p_resource,uint32_t p_flags) {
|
|
|
|
Ref<GDScript> sqscr = p_resource;
|
|
ERR_FAIL_COND_V(sqscr.is_null(),ERR_INVALID_PARAMETER);
|
|
|
|
String source = sqscr->get_source_code();
|
|
|
|
Error err;
|
|
FileAccess *file = FileAccess::open(p_path,FileAccess::WRITE,&err);
|
|
|
|
|
|
if (err) {
|
|
|
|
ERR_FAIL_COND_V(err,err);
|
|
}
|
|
|
|
file->store_string(source);
|
|
|
|
file->close();
|
|
memdelete(file);
|
|
return OK;
|
|
}
|
|
|
|
void ResourceFormatSaverGDScript::get_recognized_extensions(const RES& p_resource,List<String> *p_extensions) const {
|
|
|
|
if (p_resource->cast_to<GDScript>()) {
|
|
p_extensions->push_back("gd");
|
|
}
|
|
|
|
}
|
|
bool ResourceFormatSaverGDScript::recognize(const RES& p_resource) const {
|
|
|
|
return p_resource->cast_to<GDScript>()!=NULL;
|
|
}
|