/*************************************************************************/ /* gd_editor.cpp */ /*************************************************************************/ /* This file is part of: */ /* GODOT ENGINE */ /* http://www.godotengine.org */ /*************************************************************************/ /* Copyright (c) 2007-2016 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 "gd_compiler.h" #include "globals.h" #include "os/file_access.h" void GDScriptLanguage::get_comment_delimiters(List *p_delimiters) const { p_delimiters->push_back("#"); p_delimiters->push_back("\"\"\" \"\"\""); } void GDScriptLanguage::get_string_delimiters(List *p_delimiters) const { p_delimiters->push_back("\" \""); p_delimiters->push_back("' '"); } String GDScriptLanguage::get_template(const String& p_class_name, const String& p_base_class_name) const { String _template = String()+ "\nextends %BASE%\n\n"+ "# member variables here, example:\n"+ "# var a=2\n"+ "# var b=\"textvar\"\n\n"+ "func _ready():\n"+ "\t# Called every time the node is added to the scene.\n"+ "\t# Initialization here\n"+ "\tpass\n"+ "\n"+ "\n"; return _template.replace("%BASE%",p_base_class_name); } bool GDScriptLanguage::validate(const String& p_script, int &r_line_error,int &r_col_error,String& r_test_error, const String& p_path,List *r_functions) const { GDParser parser; Error err = parser.parse(p_script,p_path.get_base_dir(),true,p_path); if (err) { r_line_error=parser.get_error_line(); r_col_error=parser.get_error_column(); r_test_error=parser.get_error(); return false; } else { const GDParser::Node *root = parser.get_parse_tree(); ERR_FAIL_COND_V(root->type!=GDParser::Node::TYPE_CLASS,false); const GDParser::ClassNode *cl = static_cast(root); Map funcs; for(int i=0;ifunctions.size();i++) { funcs[cl->functions[i]->line]=cl->functions[i]->name; } for(int i=0;istatic_functions.size();i++) { funcs[cl->static_functions[i]->line]=cl->static_functions[i]->name; } for (Map::Element *E=funcs.front();E;E=E->next()) { r_functions->push_back(E->get()+":"+itos(E->key())); } } return true; } bool GDScriptLanguage::has_named_classes() const { return false; } int GDScriptLanguage::find_function(const String& p_function,const String& p_code) const { GDTokenizerText tokenizer; tokenizer.set_code(p_code); int indent=0; while(tokenizer.get_token()!=GDTokenizer::TK_EOF && tokenizer.get_token()!=GDTokenizer::TK_ERROR) { if (tokenizer.get_token()==GDTokenizer::TK_NEWLINE) { indent=tokenizer.get_token_line_indent(); } //print_line("TOKEN: "+String(GDTokenizer::get_token_name(tokenizer.get_token()))); if (indent==0 && tokenizer.get_token()==GDTokenizer::TK_PR_FUNCTION && tokenizer.get_token(1)==GDTokenizer::TK_IDENTIFIER) { String identifier = tokenizer.get_token_identifier(1); if (identifier==p_function) { return tokenizer.get_token_line(); } } tokenizer.advance(); //print_line("NEXT: "+String(GDTokenizer::get_token_name(tokenizer.get_token()))); } return -1; } Script *GDScriptLanguage::create_script() const { return memnew( GDScript ); } /* DEBUGGER FUNCTIONS */ bool GDScriptLanguage::debug_break_parse(const String& p_file, int p_line,const String& p_error) { //break because of parse error if (ScriptDebugger::get_singleton() && Thread::get_caller_ID()==Thread::get_main_ID()) { _debug_parse_err_line=p_line; _debug_parse_err_file=p_file; _debug_error=p_error; ScriptDebugger::get_singleton()->debug(this,false); return true; } else { return false; } } bool GDScriptLanguage::debug_break(const String& p_error,bool p_allow_continue) { if (ScriptDebugger::get_singleton() && Thread::get_caller_ID()==Thread::get_main_ID()) { _debug_parse_err_line=-1; _debug_parse_err_file=""; _debug_error=p_error; ScriptDebugger::get_singleton()->debug(this,p_allow_continue); return true; } else { return false; } } String GDScriptLanguage::debug_get_error() const { return _debug_error; } int GDScriptLanguage::debug_get_stack_level_count() const { if (_debug_parse_err_line>=0) return 1; return _debug_call_stack_pos; } int GDScriptLanguage::debug_get_stack_level_line(int p_level) const { if (_debug_parse_err_line>=0) return _debug_parse_err_line; ERR_FAIL_INDEX_V(p_level,_debug_call_stack_pos,-1); int l = _debug_call_stack_pos - p_level -1; return *(_call_stack[l].line); } String GDScriptLanguage::debug_get_stack_level_function(int p_level) const { if (_debug_parse_err_line>=0) return ""; ERR_FAIL_INDEX_V(p_level,_debug_call_stack_pos,""); int l = _debug_call_stack_pos - p_level -1; return _call_stack[l].function->get_name(); } String GDScriptLanguage::debug_get_stack_level_source(int p_level) const { if (_debug_parse_err_line>=0) return _debug_parse_err_file; ERR_FAIL_INDEX_V(p_level,_debug_call_stack_pos,""); int l = _debug_call_stack_pos - p_level -1; return _call_stack[l].function->get_script()->get_path(); } void GDScriptLanguage::debug_get_stack_level_locals(int p_level,List *p_locals, List *p_values, int p_max_subitems,int p_max_depth) { if (_debug_parse_err_line>=0) return; ERR_FAIL_INDEX(p_level,_debug_call_stack_pos); int l = _debug_call_stack_pos - p_level -1; GDFunction *f = _call_stack[l].function; List > locals; f->debug_get_stack_member_state(*_call_stack[l].line,&locals); for( List >::Element *E = locals.front();E;E=E->next() ) { p_locals->push_back(E->get().first); p_values->push_back(_call_stack[l].stack[E->get().second]); } } void GDScriptLanguage::debug_get_stack_level_members(int p_level,List *p_members, List *p_values, int p_max_subitems,int p_max_depth) { if (_debug_parse_err_line>=0) return; ERR_FAIL_INDEX(p_level,_debug_call_stack_pos); int l = _debug_call_stack_pos - p_level -1; GDInstance *instance = _call_stack[l].instance; if (!instance) return; Ref script = instance->get_script(); ERR_FAIL_COND( script.is_null() ); const Map& mi = script->debug_get_member_indices(); for(const Map::Element *E=mi.front();E;E=E->next()) { p_members->push_back(E->key()); p_values->push_back( instance->debug_get_member_by_index(E->get().index)); } } void GDScriptLanguage::debug_get_globals(List *p_locals, List *p_values, int p_max_subitems,int p_max_depth) { //no globals are really reachable in gdscript } String GDScriptLanguage::debug_parse_stack_level_expression(int p_level,const String& p_expression,int p_max_subitems,int p_max_depth) { if (_debug_parse_err_line>=0) return ""; return ""; } void GDScriptLanguage::get_recognized_extensions(List *p_extensions) const { p_extensions->push_back("gd"); } void GDScriptLanguage::get_public_functions(List *p_functions) const { for(int i=0;ipush_back(GDFunctions::get_info(GDFunctions::Function(i))); } //not really "functions", but.. { MethodInfo mi; mi.name="preload:Resource"; mi.arguments.push_back(PropertyInfo(Variant::STRING,"path")); mi.return_val=PropertyInfo(Variant::OBJECT,"",PROPERTY_HINT_RESOURCE_TYPE,"Resource"); p_functions->push_back(mi); } { MethodInfo mi; mi.name="yield"; mi.arguments.push_back(PropertyInfo(Variant::OBJECT,"object")); mi.arguments.push_back(PropertyInfo(Variant::STRING,"signal")); mi.default_arguments.push_back(Variant::NIL); mi.default_arguments.push_back(Variant::STRING); p_functions->push_back(mi); } { MethodInfo mi; mi.name="assert"; mi.arguments.push_back(PropertyInfo(Variant::BOOL,"condition")); p_functions->push_back(mi); } } void GDScriptLanguage::get_public_constants(List > *p_constants) const { Pair pi; pi.first="PI"; pi.second=Math_PI; p_constants->push_back(pi); } String GDScriptLanguage::make_function(const String& p_class,const String& p_name,const StringArray& p_args) const { String s="func "+p_name+"("; if (p_args.size()) { s+=" "; for(int i=0;i0) s+=", "; s+=p_args[i]; } s+=" "; } s+="):\n\tpass # replace with function body\n"; return s; } #if defined(DEBUG_METHODS_ENABLED) && defined(TOOLS_ENABLED) struct GDCompletionIdentifier { StringName obj_type; Ref script; Variant::Type type; Variant value; //im case there is a value, also return it }; static GDCompletionIdentifier _get_type_from_variant(const Variant& p_variant) { GDCompletionIdentifier t; t.type=p_variant.get_type(); t.value=p_variant; if (p_variant.get_type()==Variant::OBJECT) { Object *obj = p_variant; if (obj) { //if (obj->cast_to()) { // t.obj_type=obj->cast_to()->get_name(); // t.value=Variant(); //} else { t.obj_type=obj->get_type(); //} } } return t; } static GDCompletionIdentifier _get_type_from_pinfo(const PropertyInfo& p_info) { GDCompletionIdentifier t; t.type=p_info.type; if (p_info.hint==PROPERTY_HINT_RESOURCE_TYPE) { t.obj_type=p_info.hint_string; } return t; } struct GDCompletionContext { const GDParser::ClassNode *_class; const GDParser::FunctionNode *function; const GDParser::BlockNode *block; Object* base; String base_path; }; static Ref _get_parent_class(GDCompletionContext& context) { if (context._class->extends_used) { //do inheritance String path = context._class->extends_file; Ref script; Ref native; if (path!="") { //path (and optionally subclasses) if (path.is_rel_path()) { path=context.base_path.plus_file(path); } if (ScriptCodeCompletionCache::get_sigleton()) script = ScriptCodeCompletionCache::get_sigleton()->get_cached_resource(path); else script = ResourceLoader::load(path); if (script.is_null()) { return REF(); } if (script->is_valid()) { return REF(); } //print_line("EXTENDS PATH: "+path+" script is "+itos(script.is_valid())+" indices is "+itos(script->member_indices.size())+" valid? "+itos(script->valid)); if (context._class->extends_class.size()) { for(int i=0;iextends_class.size();i++) { String sub = context._class->extends_class[i]; if (script->get_subclasses().has(sub)) { script=script->get_subclasses()[sub]; } else { return REF(); } } } if (script.is_valid()) return script; } else { if (context._class->extends_class.size()==0) { ERR_PRINT("BUG"); return REF(); } String base=context._class->extends_class[0]; const GDParser::ClassNode *p = context._class->owner; Ref base_class; #if 0 while(p) { if (p->subclasses.has(base)) { base_class=p->subclasses[base]; break; } p=p->_owner; } #endif if (base_class.is_valid()) { #if 0 for(int i=1;iextends_class.size();i++) { String subclass=context._class->extends_class[i]; if (base_class->subclasses.has(subclass)) { base_class=base_class->subclasses[subclass]; } else { //print_line("Could not find subclass: "+subclass); return _get_type_from_class(context); //fail please } } script=base_class; #endif } else { if (context._class->extends_class.size()>1) { return REF(); } //if not found, try engine classes if (!GDScriptLanguage::get_singleton()->get_global_map().has(base)) { return REF(); } int base_idx = GDScriptLanguage::get_singleton()->get_global_map()[base]; native = GDScriptLanguage::get_singleton()->get_global_array()[base_idx]; return native; } } } return Ref(); } static GDCompletionIdentifier _get_native_class(GDCompletionContext& context) { //eeh... GDCompletionIdentifier id; id.type=Variant::NIL; REF pc = _get_parent_class(context); if (!pc.is_valid()) { return id; } Ref nc = pc; Ref s = pc; if (s.is_null() && nc.is_null()) { return id; } while(!s.is_null()) { nc=s->get_native(); s=s->get_base(); } if (nc.is_null()) { return id; } id.type=Variant::OBJECT; if (context.base) id.value=context.base; id.obj_type=nc->get_name(); return id; } static bool _guess_identifier_type(GDCompletionContext& context,int p_line,const StringName& p_identifier,GDCompletionIdentifier &r_type); static bool _guess_expression_type(GDCompletionContext& context,const GDParser::Node* p_node,int p_line,GDCompletionIdentifier &r_type) { if (p_node->type==GDParser::Node::TYPE_CONSTANT) { const GDParser::ConstantNode *cn=static_cast(p_node); r_type=_get_type_from_variant(cn->value); return true; } else if (p_node->type==GDParser::Node::TYPE_DICTIONARY) { r_type.type=Variant::DICTIONARY; //what the heck, fill it anyway const GDParser::DictionaryNode *an = static_cast(p_node); Dictionary d; for(int i=0;ielements.size();i++) { GDCompletionIdentifier k; if (_guess_expression_type(context,an->elements[i].key,p_line,k) && k.value.get_type()!=Variant::NIL) { GDCompletionIdentifier v; if (_guess_expression_type(context,an->elements[i].value,p_line,v)) { d[k.value]=v.value; } } } r_type.value=d; return true; } else if (p_node->type==GDParser::Node::TYPE_ARRAY) { r_type.type=Variant::ARRAY; //what the heck, fill it anyway const GDParser::ArrayNode *an = static_cast(p_node); Array arr; arr.resize(an->elements.size()); for(int i=0;ielements.size();i++) { GDCompletionIdentifier ci; if (_guess_expression_type(context,an->elements[i],p_line,ci)) { arr[i]=ci.value; } } r_type.value=arr; return true; } else if (p_node->type==GDParser::Node::TYPE_BUILT_IN_FUNCTION) { MethodInfo mi = GDFunctions::get_info(static_cast(p_node)->function); r_type=_get_type_from_pinfo(mi.return_val); return true; } else if (p_node->type==GDParser::Node::TYPE_IDENTIFIER) { return _guess_identifier_type(context,p_line-1,static_cast(p_node)->name,r_type); } else if (p_node->type==GDParser::Node::TYPE_SELF) { //eeh... r_type=_get_native_class(context); return r_type.type!=Variant::NIL; } else if (p_node->type==GDParser::Node::TYPE_OPERATOR) { const GDParser::OperatorNode *op = static_cast(p_node); if (op->op==GDParser::OperatorNode::OP_CALL) { if (op->arguments[0]->type==GDParser::Node::TYPE_TYPE) { const GDParser::TypeNode *tn = static_cast(op->arguments[0]); r_type.type=tn->vtype; return true; } else if (op->arguments[0]->type==GDParser::Node::TYPE_BUILT_IN_FUNCTION) { const GDParser::BuiltInFunctionNode *bin = static_cast(op->arguments[0]); return _guess_expression_type(context,bin,p_line,r_type); } else if (op->arguments.size()>1 && op->arguments[1]->type==GDParser::Node::TYPE_IDENTIFIER) { GDCompletionIdentifier base; if (!_guess_expression_type(context,op->arguments[0],p_line,base)) return false; StringName id = static_cast(op->arguments[1])->name; if (base.type==Variant::OBJECT) { if (id.operator String()=="new" && base.value.get_type()==Variant::OBJECT) { Object *obj = base.value; if (obj && obj->cast_to()) { GDNativeClass *gdnc = obj->cast_to(); r_type.type=Variant::OBJECT; r_type.value=Variant(); r_type.obj_type=gdnc->get_name(); return true; } } if (ObjectTypeDB::has_method(base.obj_type,id)) { #ifdef TOOLS_ENABLED MethodBind *mb = ObjectTypeDB::get_method(base.obj_type,id); PropertyInfo pi = mb->get_argument_info(-1); //try calling the function if constant and all args are constant, should not crash.. Object *baseptr = base.value; if (mb->is_const() && pi.type==Variant::OBJECT) { bool all_valid=true; Vector args; for(int i=2;iarguments.size();i++) { GDCompletionIdentifier arg; if (_guess_expression_type(context,op->arguments[i],p_line,arg)) { if (arg.value.get_type()!=Variant::NIL && arg.value.get_type()!=Variant::OBJECT) { // calling with object seems dangerous, i don' t know args.push_back(arg.value); } else { all_valid=false; break; } } else { all_valid=false; } } if (all_valid && String(id)=="get_node" && ObjectTypeDB::is_type(base.obj_type,"Node") && args.size()) { String arg1=args[0]; if (arg1.begins_with("/root/")) { String which = arg1.get_slice("/",2); if (which!="") { List props; Globals::get_singleton()->get_property_list(&props); //print_line("find singleton"); for(List::Element *E=props.front();E;E=E->next()) { String s = E->get().name; if (!s.begins_with("autoload/")) continue; //print_line("found "+s); String name = s.get_slice("/",1); //print_line("name: "+name+", which: "+which); if (name==which) { String script = Globals::get_singleton()->get(s); if (!script.begins_with("res://")) { script="res://"+script; } if (!script.ends_with(".gd")) { //not a script, try find the script anyway, //may have some success script=script.basename()+".gd"; } if (FileAccess::exists(script)) { //print_line("is a script"); Ref