/*************************************************************************/ /* gd_editor.cpp */ /*************************************************************************/ /* This file is part of: */ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ /* Copyright (c) 2007-2018 Juan Linietsky, Ariel Manzur. */ /* Copyright (c) 2014-2018 Godot Engine contributors (cf. AUTHORS.md) */ /* */ /* 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_compiler.h" #include "gd_script.h" #include "global_constants.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() + "extends %BASE%\n\n" + "# class member variables go here, for 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"; return _template.replace("%BASE%", p_base_class_name); } String GDScriptLanguage::get_empty_template(const String &p_class_name, const String &p_base_class_name) const { String _template = String() + "extends %BASE%\n\n"; return _template.replace("%BASE%", p_base_class_name); } String GDScriptLanguage::get_nocomment_template(const String &p_class_name, const String &p_base_class_name) const { String _template = String() + "extends %BASE%\n\n" + "func _ready():\n" + "\tpass\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; i < cl->functions.size(); i++) { funcs[cl->functions[i]->line] = cl->functions[i]->name; } for (int i = 0; i < cl->static_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_source(); } 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)); } } ScriptInstance *GDScriptLanguage::debug_get_stack_level_instance(int p_level) { ERR_FAIL_COND_V(_debug_parse_err_line >= 0, NULL); ERR_FAIL_INDEX_V(p_level, _debug_call_stack_pos, NULL); int l = _debug_call_stack_pos - p_level - 1; GDInstance *instance = _call_stack[l].instance; return instance; } void GDScriptLanguage::debug_get_globals(List *p_globals, List *p_values, int p_max_subitems, int p_max_depth) { const Map &name_idx = GDScriptLanguage::get_singleton()->get_global_map(); const Variant *globals = GDScriptLanguage::get_singleton()->get_global_array(); for (const Map::Element *E = name_idx.front(); E; E = E->next()) { if (ObjectTypeDB::type_exists(E->key()) || Globals::get_singleton()->has_singleton(E->key()) || E->key() == "PI") continue; const Variant &var = globals[E->value()]; if (Object *obj = var) { if (obj->cast_to()) continue; } bool skip = false; for (int i = 0; i < GlobalConstants::get_global_constant_count(); i++) { if (E->key() == GlobalConstants::get_global_constant_name(i)) { skip = true; break; } } if (skip) continue; p_globals->push_back(E->key()); p_values->push_back(var); } } 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; i < GDFunctions::FUNC_MAX; i++) { p_functions->push_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:GDFunctionState"; 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; i < p_args.size(); i++) { if (i > 0) 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; i < context._class->extends_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]; 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; i < an->elements.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; i < an->elements.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; i < op->arguments.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