/*************************************************************************/ /* gdscript_parser.cpp */ /*************************************************************************/ /* This file is part of: */ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ /* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */ /* Copyright (c) 2014-2020 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 "gdscript_parser.h" #include "core/core_string_names.h" #include "core/engine.h" #include "core/io/resource_loader.h" #include "core/os/file_access.h" #include "core/print_string.h" #include "core/project_settings.h" #include "core/reference.h" #include "core/script_language.h" #include "gdscript.h" template <class T> T *GDScriptParser::alloc_node() { T *t = memnew(T); t->next = list; list = t; if (!head) head = t; t->line = tokenizer->get_token_line(); t->column = tokenizer->get_token_column(); return t; } #ifdef DEBUG_ENABLED static String _find_function_name(const GDScriptParser::OperatorNode *p_call); #endif // DEBUG_ENABLED bool GDScriptParser::_end_statement() { if (tokenizer->get_token() == GDScriptTokenizer::TK_SEMICOLON) { tokenizer->advance(); return true; //handle next } else if (tokenizer->get_token() == GDScriptTokenizer::TK_NEWLINE || tokenizer->get_token() == GDScriptTokenizer::TK_EOF) { return true; //will be handled properly } return false; } bool GDScriptParser::_enter_indent_block(BlockNode *p_block) { if (tokenizer->get_token() != GDScriptTokenizer::TK_COLON) { // report location at the previous token (on the previous line) int error_line = tokenizer->get_token_line(-1); int error_column = tokenizer->get_token_column(-1); _set_error("':' expected at end of line.", error_line, error_column); return false; } tokenizer->advance(); if (tokenizer->get_token() == GDScriptTokenizer::TK_EOF) { return false; } if (tokenizer->get_token() != GDScriptTokenizer::TK_NEWLINE) { // be more python-like IndentLevel current_level = indent_level.back()->get(); indent_level.push_back(current_level); return true; //_set_error("newline expected after ':'."); //return false; } while (true) { if (tokenizer->get_token() != GDScriptTokenizer::TK_NEWLINE) { return false; //wtf } else if (tokenizer->get_token(1) == GDScriptTokenizer::TK_EOF) { return false; } else if (tokenizer->get_token(1) != GDScriptTokenizer::TK_NEWLINE) { int indent = tokenizer->get_token_line_indent(); int tabs = tokenizer->get_token_line_tab_indent(); IndentLevel current_level = indent_level.back()->get(); IndentLevel new_indent(indent, tabs); if (new_indent.is_mixed(current_level)) { _set_error("Mixed tabs and spaces in indentation."); return false; } if (indent <= current_level.indent) { return false; } indent_level.push_back(new_indent); tokenizer->advance(); return true; } else if (p_block) { NewLineNode *nl = alloc_node<NewLineNode>(); nl->line = tokenizer->get_token_line(); p_block->statements.push_back(nl); } tokenizer->advance(); // go to next newline } } bool GDScriptParser::_parse_arguments(Node *p_parent, Vector<Node *> &p_args, bool p_static, bool p_can_codecomplete, bool p_parsing_constant) { if (tokenizer->get_token() == GDScriptTokenizer::TK_PARENTHESIS_CLOSE) { tokenizer->advance(); } else { parenthesis++; int argidx = 0; while (true) { if (tokenizer->get_token() == GDScriptTokenizer::TK_CURSOR) { _make_completable_call(argidx); completion_node = p_parent; } else if (tokenizer->get_token() == GDScriptTokenizer::TK_CONSTANT && tokenizer->get_token_constant().get_type() == Variant::STRING && tokenizer->get_token(1) == GDScriptTokenizer::TK_CURSOR) { //completing a string argument.. completion_cursor = tokenizer->get_token_constant(); _make_completable_call(argidx); completion_node = p_parent; tokenizer->advance(1); return false; } Node *arg = _parse_expression(p_parent, p_static, false, p_parsing_constant); if (!arg) { return false; } p_args.push_back(arg); if (tokenizer->get_token() == GDScriptTokenizer::TK_PARENTHESIS_CLOSE) { tokenizer->advance(); break; } else if (tokenizer->get_token() == GDScriptTokenizer::TK_COMMA) { if (tokenizer->get_token(1) == GDScriptTokenizer::TK_PARENTHESIS_CLOSE) { _set_error("Expression expected"); return false; } tokenizer->advance(); argidx++; } else { // something is broken _set_error("Expected ',' or ')'"); return false; } } parenthesis--; } return true; } void GDScriptParser::_make_completable_call(int p_arg) { completion_cursor = StringName(); completion_type = COMPLETION_CALL_ARGUMENTS; completion_class = current_class; completion_function = current_function; completion_line = tokenizer->get_token_line(); completion_argument = p_arg; completion_block = current_block; completion_found = true; tokenizer->advance(); } bool GDScriptParser::_get_completable_identifier(CompletionType p_type, StringName &identifier) { identifier = StringName(); if (tokenizer->is_token_literal()) { identifier = tokenizer->get_token_literal(); tokenizer->advance(); } if (tokenizer->get_token() == GDScriptTokenizer::TK_CURSOR) { completion_cursor = identifier; completion_type = p_type; completion_class = current_class; completion_function = current_function; completion_line = tokenizer->get_token_line(); completion_block = current_block; completion_found = true; completion_ident_is_call = false; tokenizer->advance(); if (tokenizer->is_token_literal()) { identifier = identifier.operator String() + tokenizer->get_token_literal().operator String(); tokenizer->advance(); } if (tokenizer->get_token() == GDScriptTokenizer::TK_PARENTHESIS_OPEN) { completion_ident_is_call = true; } return true; } return false; } GDScriptParser::Node *GDScriptParser::_parse_expression(Node *p_parent, bool p_static, bool p_allow_assign, bool p_parsing_constant) { //Vector<Node*> expressions; //Vector<OperatorNode::Operator> operators; Vector<Expression> expression; Node *expr = NULL; int op_line = tokenizer->get_token_line(); // when operators are created at the bottom, the line might have been changed (\n found) while (true) { /*****************/ /* Parse Operand */ /*****************/ if (parenthesis > 0) { //remove empty space (only allowed if inside parenthesis while (tokenizer->get_token() == GDScriptTokenizer::TK_NEWLINE) { tokenizer->advance(); } } // Check that the next token is not TK_CURSOR and if it is, the offset should be incremented. int next_valid_offset = 1; if (tokenizer->get_token(next_valid_offset) == GDScriptTokenizer::TK_CURSOR) { next_valid_offset++; // There is a chunk of the identifier that also needs to be ignored (not always there!) if (tokenizer->get_token(next_valid_offset) == GDScriptTokenizer::TK_IDENTIFIER) { next_valid_offset++; } } if (tokenizer->get_token() == GDScriptTokenizer::TK_PARENTHESIS_OPEN) { //subexpression () tokenizer->advance(); parenthesis++; Node *subexpr = _parse_expression(p_parent, p_static, p_allow_assign, p_parsing_constant); parenthesis--; if (!subexpr) return NULL; if (tokenizer->get_token() != GDScriptTokenizer::TK_PARENTHESIS_CLOSE) { _set_error("Expected ')' in expression"); return NULL; } tokenizer->advance(); expr = subexpr; } else if (tokenizer->get_token() == GDScriptTokenizer::TK_DOLLAR) { tokenizer->advance(); String path; bool need_identifier = true; bool done = false; int line = tokenizer->get_token_line(); while (!done) { switch (tokenizer->get_token()) { case GDScriptTokenizer::TK_CURSOR: { completion_type = COMPLETION_GET_NODE; completion_class = current_class; completion_function = current_function; completion_line = tokenizer->get_token_line(); completion_cursor = path; completion_argument = 0; completion_block = current_block; completion_found = true; tokenizer->advance(); } break; case GDScriptTokenizer::TK_CONSTANT: { if (!need_identifier) { done = true; break; } if (tokenizer->get_token_constant().get_type() != Variant::STRING) { _set_error("Expected string constant or identifier after '$' or '/'."); return NULL; } path += String(tokenizer->get_token_constant()); tokenizer->advance(); need_identifier = false; } break; case GDScriptTokenizer::TK_OP_DIV: { if (need_identifier) { done = true; break; } path += "/"; tokenizer->advance(); need_identifier = true; } break; default: { // Instead of checking for TK_IDENTIFIER, we check with is_token_literal, as this allows us to use match/sync/etc. as a name if (need_identifier && tokenizer->is_token_literal()) { path += String(tokenizer->get_token_literal()); tokenizer->advance(); need_identifier = false; } else { done = true; } break; } } } if (path == "") { _set_error("Path expected after $."); return NULL; } OperatorNode *op = alloc_node<OperatorNode>(); op->op = OperatorNode::OP_CALL; op->line = line; op->arguments.push_back(alloc_node<SelfNode>()); op->arguments[0]->line = line; IdentifierNode *funcname = alloc_node<IdentifierNode>(); funcname->name = "get_node"; funcname->line = line; op->arguments.push_back(funcname); ConstantNode *nodepath = alloc_node<ConstantNode>(); nodepath->value = NodePath(StringName(path)); nodepath->datatype = _type_from_variant(nodepath->value); nodepath->line = line; op->arguments.push_back(nodepath); expr = op; } else if (tokenizer->get_token() == GDScriptTokenizer::TK_CURSOR) { tokenizer->advance(); continue; //no point in cursor in the middle of expression } else if (tokenizer->get_token() == GDScriptTokenizer::TK_CONSTANT) { //constant defined by tokenizer ConstantNode *constant = alloc_node<ConstantNode>(); constant->value = tokenizer->get_token_constant(); constant->datatype = _type_from_variant(constant->value); tokenizer->advance(); expr = constant; } else if (tokenizer->get_token() == GDScriptTokenizer::TK_CONST_PI) { //constant defined by tokenizer ConstantNode *constant = alloc_node<ConstantNode>(); constant->value = Math_PI; constant->datatype = _type_from_variant(constant->value); tokenizer->advance(); expr = constant; } else if (tokenizer->get_token() == GDScriptTokenizer::TK_CONST_TAU) { //constant defined by tokenizer ConstantNode *constant = alloc_node<ConstantNode>(); constant->value = Math_TAU; constant->datatype = _type_from_variant(constant->value); tokenizer->advance(); expr = constant; } else if (tokenizer->get_token() == GDScriptTokenizer::TK_CONST_INF) { //constant defined by tokenizer ConstantNode *constant = alloc_node<ConstantNode>(); constant->value = Math_INF; constant->datatype = _type_from_variant(constant->value); tokenizer->advance(); expr = constant; } else if (tokenizer->get_token() == GDScriptTokenizer::TK_CONST_NAN) { //constant defined by tokenizer ConstantNode *constant = alloc_node<ConstantNode>(); constant->value = Math_NAN; constant->datatype = _type_from_variant(constant->value); tokenizer->advance(); expr = constant; } else if (tokenizer->get_token() == GDScriptTokenizer::TK_PR_PRELOAD) { //constant defined by tokenizer tokenizer->advance(); if (tokenizer->get_token() != GDScriptTokenizer::TK_PARENTHESIS_OPEN) { _set_error("Expected '(' after 'preload'"); return NULL; } tokenizer->advance(); if (tokenizer->get_token() == GDScriptTokenizer::TK_CURSOR) { completion_cursor = StringName(); completion_node = p_parent; completion_type = COMPLETION_RESOURCE_PATH; completion_class = current_class; completion_function = current_function; completion_line = tokenizer->get_token_line(); completion_block = current_block; completion_argument = 0; completion_found = true; tokenizer->advance(); } String path; bool found_constant = false; bool valid = false; ConstantNode *cn; Node *subexpr = _parse_and_reduce_expression(p_parent, p_static); if (subexpr) { if (subexpr->type == Node::TYPE_CONSTANT) { cn = static_cast<ConstantNode *>(subexpr); found_constant = true; } if (subexpr->type == Node::TYPE_IDENTIFIER) { IdentifierNode *in = static_cast<IdentifierNode *>(subexpr); // Try to find the constant expression by the identifier if (current_class->constant_expressions.has(in->name)) { Node *cn_exp = current_class->constant_expressions[in->name].expression; if (cn_exp->type == Node::TYPE_CONSTANT) { cn = static_cast<ConstantNode *>(cn_exp); found_constant = true; } } } if (found_constant && cn->value.get_type() == Variant::STRING) { valid = true; path = (String)cn->value; } } if (!valid) { _set_error("expected string constant as 'preload' argument."); return NULL; } if (!path.is_abs_path() && base_path != "") path = base_path.plus_file(path); path = path.replace("///", "//").simplify_path(); if (path == self_path) { _set_error("Can't preload itself (use 'get_script()')."); return NULL; } Ref<Resource> res; dependencies.push_back(path); if (!dependencies_only) { if (!validating) { //this can be too slow for just validating code if (for_completion && ScriptCodeCompletionCache::get_singleton() && FileAccess::exists(path)) { res = ScriptCodeCompletionCache::get_singleton()->get_cached_resource(path); } else if (!for_completion || FileAccess::exists(path)) { res = ResourceLoader::load(path); } } else { if (!FileAccess::exists(path)) { _set_error("Can't preload resource at path: " + path); return NULL; } else if (ScriptCodeCompletionCache::get_singleton()) { res = ScriptCodeCompletionCache::get_singleton()->get_cached_resource(path); } } if (!res.is_valid()) { _set_error("Can't preload resource at path: " + path); return NULL; } } if (tokenizer->get_token() != GDScriptTokenizer::TK_PARENTHESIS_CLOSE) { _set_error("Expected ')' after 'preload' path"); return NULL; } Ref<GDScript> gds = res; if (gds.is_valid() && !gds->is_valid()) { _set_error("Couldn't fully preload the script, possible cyclic reference or compilation error. Use \"load()\" instead if a cyclic reference is intended."); return NULL; } tokenizer->advance(); ConstantNode *constant = alloc_node<ConstantNode>(); constant->value = res; constant->datatype = _type_from_variant(constant->value); expr = constant; } else if (tokenizer->get_token() == GDScriptTokenizer::TK_PR_YIELD) { if (!current_function) { _set_error("\"yield()\" can only be used inside function blocks."); return NULL; } current_function->has_yield = true; tokenizer->advance(); if (tokenizer->get_token() != GDScriptTokenizer::TK_PARENTHESIS_OPEN) { _set_error("Expected \"(\" after \"yield\"."); return NULL; } tokenizer->advance(); OperatorNode *yield = alloc_node<OperatorNode>(); yield->op = OperatorNode::OP_YIELD; while (tokenizer->get_token() == GDScriptTokenizer::TK_NEWLINE) { tokenizer->advance(); } if (tokenizer->get_token() == GDScriptTokenizer::TK_PARENTHESIS_CLOSE) { expr = yield; tokenizer->advance(); } else { parenthesis++; Node *object = _parse_and_reduce_expression(p_parent, p_static); if (!object) return NULL; yield->arguments.push_back(object); if (tokenizer->get_token() != GDScriptTokenizer::TK_COMMA) { _set_error("Expected \",\" after the first argument of \"yield\"."); return NULL; } tokenizer->advance(); if (tokenizer->get_token() == GDScriptTokenizer::TK_CURSOR) { completion_cursor = StringName(); completion_node = object; completion_type = COMPLETION_YIELD; completion_class = current_class; completion_function = current_function; completion_line = tokenizer->get_token_line(); completion_argument = 0; completion_block = current_block; completion_found = true; tokenizer->advance(); } Node *signal = _parse_and_reduce_expression(p_parent, p_static); if (!signal) return NULL; yield->arguments.push_back(signal); if (tokenizer->get_token() != GDScriptTokenizer::TK_PARENTHESIS_CLOSE) { _set_error("Expected \")\" after the second argument of \"yield\"."); return NULL; } parenthesis--; tokenizer->advance(); expr = yield; } } else if (tokenizer->get_token() == GDScriptTokenizer::TK_SELF) { if (p_static) { _set_error("\"self\" isn't allowed in a static function or constant expression."); return NULL; } //constant defined by tokenizer SelfNode *self = alloc_node<SelfNode>(); tokenizer->advance(); expr = self; } else if (tokenizer->get_token() == GDScriptTokenizer::TK_BUILT_IN_TYPE && tokenizer->get_token(1) == GDScriptTokenizer::TK_PERIOD) { Variant::Type bi_type = tokenizer->get_token_type(); tokenizer->advance(2); StringName identifier; if (_get_completable_identifier(COMPLETION_BUILT_IN_TYPE_CONSTANT, identifier)) { completion_built_in_constant = bi_type; } if (identifier == StringName()) { _set_error("Built-in type constant or static function expected after \".\"."); return NULL; } if (!Variant::has_constant(bi_type, identifier)) { if (tokenizer->get_token() == GDScriptTokenizer::TK_PARENTHESIS_OPEN && Variant::is_method_const(bi_type, identifier) && Variant::get_method_return_type(bi_type, identifier) == bi_type) { tokenizer->advance(); OperatorNode *construct = alloc_node<OperatorNode>(); construct->op = OperatorNode::OP_CALL; TypeNode *tn = alloc_node<TypeNode>(); tn->vtype = bi_type; construct->arguments.push_back(tn); OperatorNode *op = alloc_node<OperatorNode>(); op->op = OperatorNode::OP_CALL; op->arguments.push_back(construct); IdentifierNode *id = alloc_node<IdentifierNode>(); id->name = identifier; op->arguments.push_back(id); if (!_parse_arguments(op, op->arguments, p_static, true, p_parsing_constant)) return NULL; expr = op; } else { // Object is a special case bool valid = false; if (bi_type == Variant::OBJECT) { int object_constant = ClassDB::get_integer_constant("Object", identifier, &valid); if (valid) { ConstantNode *cn = alloc_node<ConstantNode>(); cn->value = object_constant; cn->datatype = _type_from_variant(cn->value); expr = cn; } } if (!valid) { _set_error("Static constant '" + identifier.operator String() + "' not present in built-in type " + Variant::get_type_name(bi_type) + "."); return NULL; } } } else { ConstantNode *cn = alloc_node<ConstantNode>(); cn->value = Variant::get_constant_value(bi_type, identifier); cn->datatype = _type_from_variant(cn->value); expr = cn; } } else if (tokenizer->get_token(next_valid_offset) == GDScriptTokenizer::TK_PARENTHESIS_OPEN && tokenizer->is_token_literal()) { // We check with is_token_literal, as this allows us to use match/sync/etc. as a name //function or constructor OperatorNode *op = alloc_node<OperatorNode>(); op->op = OperatorNode::OP_CALL; //Do a quick Array and Dictionary Check. Replace if either require no arguments. bool replaced = false; if (tokenizer->get_token() == GDScriptTokenizer::TK_BUILT_IN_TYPE) { Variant::Type ct = tokenizer->get_token_type(); if (!p_parsing_constant) { if (ct == Variant::ARRAY) { if (tokenizer->get_token(2) == GDScriptTokenizer::TK_PARENTHESIS_CLOSE) { ArrayNode *arr = alloc_node<ArrayNode>(); expr = arr; replaced = true; tokenizer->advance(3); } } if (ct == Variant::DICTIONARY) { if (tokenizer->get_token(2) == GDScriptTokenizer::TK_PARENTHESIS_CLOSE) { DictionaryNode *dict = alloc_node<DictionaryNode>(); expr = dict; replaced = true; tokenizer->advance(3); } } } if (!replaced) { TypeNode *tn = alloc_node<TypeNode>(); tn->vtype = tokenizer->get_token_type(); op->arguments.push_back(tn); tokenizer->advance(2); } } else if (tokenizer->get_token() == GDScriptTokenizer::TK_BUILT_IN_FUNC) { BuiltInFunctionNode *bn = alloc_node<BuiltInFunctionNode>(); bn->function = tokenizer->get_token_built_in_func(); op->arguments.push_back(bn); tokenizer->advance(2); } else { SelfNode *self = alloc_node<SelfNode>(); op->arguments.push_back(self); StringName identifier; if (_get_completable_identifier(COMPLETION_FUNCTION, identifier)) { } IdentifierNode *id = alloc_node<IdentifierNode>(); id->name = identifier; op->arguments.push_back(id); tokenizer->advance(1); } if (tokenizer->get_token() == GDScriptTokenizer::TK_CURSOR) { _make_completable_call(0); completion_node = op; } if (!replaced) { if (!_parse_arguments(op, op->arguments, p_static, true, p_parsing_constant)) return NULL; expr = op; } } else if (tokenizer->is_token_literal(0, true)) { // We check with is_token_literal, as this allows us to use match/sync/etc. as a name //identifier (reference) const ClassNode *cln = current_class; bool bfn = false; StringName identifier; int id_line = tokenizer->get_token_line(); if (_get_completable_identifier(COMPLETION_IDENTIFIER, identifier)) { } BlockNode *b = current_block; while (!bfn && b) { if (b->variables.has(identifier)) { IdentifierNode *id = alloc_node<IdentifierNode>(); id->name = identifier; id->declared_block = b; id->line = id_line; expr = id; bfn = true; #ifdef DEBUG_ENABLED LocalVarNode *lv = b->variables[identifier]; switch (tokenizer->get_token()) { case GDScriptTokenizer::TK_OP_ASSIGN_ADD: case GDScriptTokenizer::TK_OP_ASSIGN_BIT_AND: case GDScriptTokenizer::TK_OP_ASSIGN_BIT_OR: case GDScriptTokenizer::TK_OP_ASSIGN_BIT_XOR: case GDScriptTokenizer::TK_OP_ASSIGN_DIV: case GDScriptTokenizer::TK_OP_ASSIGN_MOD: case GDScriptTokenizer::TK_OP_ASSIGN_MUL: case GDScriptTokenizer::TK_OP_ASSIGN_SHIFT_LEFT: case GDScriptTokenizer::TK_OP_ASSIGN_SHIFT_RIGHT: case GDScriptTokenizer::TK_OP_ASSIGN_SUB: { if (lv->assignments == 0) { if (!lv->datatype.has_type) { _set_error("Using assignment with operation on a variable that was never assigned."); return NULL; } _add_warning(GDScriptWarning::UNASSIGNED_VARIABLE_OP_ASSIGN, -1, identifier.operator String()); } FALLTHROUGH; } case GDScriptTokenizer::TK_OP_ASSIGN: { lv->assignments += 1; lv->usages--; // Assignment is not really usage } break; default: { lv->usages++; } } #endif // DEBUG_ENABLED break; } b = b->parent_block; } if (!bfn && p_parsing_constant) { if (cln->constant_expressions.has(identifier)) { expr = cln->constant_expressions[identifier].expression; bfn = true; } else if (GDScriptLanguage::get_singleton()->get_global_map().has(identifier)) { //check from constants ConstantNode *constant = alloc_node<ConstantNode>(); constant->value = GDScriptLanguage::get_singleton()->get_global_array()[GDScriptLanguage::get_singleton()->get_global_map()[identifier]]; constant->datatype = _type_from_variant(constant->value); constant->line = id_line; expr = constant; bfn = true; } if (!bfn && GDScriptLanguage::get_singleton()->get_named_globals_map().has(identifier)) { //check from singletons ConstantNode *constant = alloc_node<ConstantNode>(); constant->value = GDScriptLanguage::get_singleton()->get_named_globals_map()[identifier]; expr = constant; bfn = true; } if (!dependencies_only) { if (!bfn && ScriptServer::is_global_class(identifier)) { Ref<Script> scr = ResourceLoader::load(ScriptServer::get_global_class_path(identifier)); if (scr.is_valid() && scr->is_valid()) { ConstantNode *constant = alloc_node<ConstantNode>(); constant->value = scr; expr = constant; bfn = true; } } // Check parents for the constant if (!bfn) { // Using current_class instead of cln here, since cln is const* _determine_inheritance(current_class, false); if (cln->base_type.has_type && cln->base_type.kind == DataType::GDSCRIPT && cln->base_type.script_type->is_valid()) { Map<StringName, Variant> parent_constants; current_class->base_type.script_type->get_constants(&parent_constants); if (parent_constants.has(identifier)) { ConstantNode *constant = alloc_node<ConstantNode>(); constant->value = parent_constants[identifier]; expr = constant; bfn = true; } } } } } if (!bfn) { #ifdef DEBUG_ENABLED if (current_function) { int arg_idx = current_function->arguments.find(identifier); if (arg_idx != -1) { switch (tokenizer->get_token()) { case GDScriptTokenizer::TK_OP_ASSIGN_ADD: case GDScriptTokenizer::TK_OP_ASSIGN_BIT_AND: case GDScriptTokenizer::TK_OP_ASSIGN_BIT_OR: case GDScriptTokenizer::TK_OP_ASSIGN_BIT_XOR: case GDScriptTokenizer::TK_OP_ASSIGN_DIV: case GDScriptTokenizer::TK_OP_ASSIGN_MOD: case GDScriptTokenizer::TK_OP_ASSIGN_MUL: case GDScriptTokenizer::TK_OP_ASSIGN_SHIFT_LEFT: case GDScriptTokenizer::TK_OP_ASSIGN_SHIFT_RIGHT: case GDScriptTokenizer::TK_OP_ASSIGN_SUB: case GDScriptTokenizer::TK_OP_ASSIGN: { // Assignment is not really usage } break; default: { current_function->arguments_usage.write[arg_idx] = current_function->arguments_usage[arg_idx] + 1; } } } } #endif // DEBUG_ENABLED IdentifierNode *id = alloc_node<IdentifierNode>(); id->name = identifier; id->line = id_line; expr = id; } } else if (tokenizer->get_token() == GDScriptTokenizer::TK_OP_ADD || tokenizer->get_token() == GDScriptTokenizer::TK_OP_SUB || tokenizer->get_token() == GDScriptTokenizer::TK_OP_NOT || tokenizer->get_token() == GDScriptTokenizer::TK_OP_BIT_INVERT) { //single prefix operators like !expr +expr -expr ++expr --expr alloc_node<OperatorNode>(); Expression e; e.is_op = true; switch (tokenizer->get_token()) { case GDScriptTokenizer::TK_OP_ADD: e.op = OperatorNode::OP_POS; break; case GDScriptTokenizer::TK_OP_SUB: e.op = OperatorNode::OP_NEG; break; case GDScriptTokenizer::TK_OP_NOT: e.op = OperatorNode::OP_NOT; break; case GDScriptTokenizer::TK_OP_BIT_INVERT: e.op = OperatorNode::OP_BIT_INVERT; break; default: { } } tokenizer->advance(); if (e.op != OperatorNode::OP_NOT && tokenizer->get_token() == GDScriptTokenizer::TK_OP_NOT) { _set_error("Misplaced 'not'."); return NULL; } expression.push_back(e); continue; //only exception, must continue... /* Node *subexpr=_parse_expression(op,p_static); if (!subexpr) return NULL; op->arguments.push_back(subexpr); expr=op;*/ } else if (tokenizer->get_token() == GDScriptTokenizer::TK_PR_IS && tokenizer->get_token(1) == GDScriptTokenizer::TK_BUILT_IN_TYPE) { // 'is' operator with built-in type if (!expr) { _set_error("Expected identifier before 'is' operator"); return NULL; } OperatorNode *op = alloc_node<OperatorNode>(); op->op = OperatorNode::OP_IS_BUILTIN; op->arguments.push_back(expr); tokenizer->advance(); TypeNode *tn = alloc_node<TypeNode>(); tn->vtype = tokenizer->get_token_type(); op->arguments.push_back(tn); tokenizer->advance(); expr = op; } else if (tokenizer->get_token() == GDScriptTokenizer::TK_BRACKET_OPEN) { // array tokenizer->advance(); ArrayNode *arr = alloc_node<ArrayNode>(); bool expecting_comma = false; while (true) { if (tokenizer->get_token() == GDScriptTokenizer::TK_EOF) { _set_error("Unterminated array"); return NULL; } else if (tokenizer->get_token() == GDScriptTokenizer::TK_BRACKET_CLOSE) { tokenizer->advance(); break; } else if (tokenizer->get_token() == GDScriptTokenizer::TK_NEWLINE) { tokenizer->advance(); //ignore newline } else if (tokenizer->get_token() == GDScriptTokenizer::TK_COMMA) { if (!expecting_comma) { _set_error("expression or ']' expected"); return NULL; } expecting_comma = false; tokenizer->advance(); //ignore newline } else { //parse expression if (expecting_comma) { _set_error("',' or ']' expected"); return NULL; } Node *n = _parse_expression(arr, p_static, p_allow_assign, p_parsing_constant); if (!n) return NULL; arr->elements.push_back(n); expecting_comma = true; } } expr = arr; } else if (tokenizer->get_token() == GDScriptTokenizer::TK_CURLY_BRACKET_OPEN) { // array tokenizer->advance(); DictionaryNode *dict = alloc_node<DictionaryNode>(); enum DictExpect { DICT_EXPECT_KEY, DICT_EXPECT_COLON, DICT_EXPECT_VALUE, DICT_EXPECT_COMMA }; Node *key = NULL; Set<Variant> keys; DictExpect expecting = DICT_EXPECT_KEY; while (true) { if (tokenizer->get_token() == GDScriptTokenizer::TK_EOF) { _set_error("Unterminated dictionary"); return NULL; } else if (tokenizer->get_token() == GDScriptTokenizer::TK_CURLY_BRACKET_CLOSE) { if (expecting == DICT_EXPECT_COLON) { _set_error("':' expected"); return NULL; } if (expecting == DICT_EXPECT_VALUE) { _set_error("value expected"); return NULL; } tokenizer->advance(); break; } else if (tokenizer->get_token() == GDScriptTokenizer::TK_NEWLINE) { tokenizer->advance(); //ignore newline } else if (tokenizer->get_token() == GDScriptTokenizer::TK_COMMA) { if (expecting == DICT_EXPECT_KEY) { _set_error("key or '}' expected"); return NULL; } if (expecting == DICT_EXPECT_VALUE) { _set_error("value expected"); return NULL; } if (expecting == DICT_EXPECT_COLON) { _set_error("':' expected"); return NULL; } expecting = DICT_EXPECT_KEY; tokenizer->advance(); //ignore newline } else if (tokenizer->get_token() == GDScriptTokenizer::TK_COLON) { if (expecting == DICT_EXPECT_KEY) { _set_error("key or '}' expected"); return NULL; } if (expecting == DICT_EXPECT_VALUE) { _set_error("value expected"); return NULL; } if (expecting == DICT_EXPECT_COMMA) { _set_error("',' or '}' expected"); return NULL; } expecting = DICT_EXPECT_VALUE; tokenizer->advance(); //ignore newline } else { if (expecting == DICT_EXPECT_COMMA) { _set_error("',' or '}' expected"); return NULL; } if (expecting == DICT_EXPECT_COLON) { _set_error("':' expected"); return NULL; } if (expecting == DICT_EXPECT_KEY) { if (tokenizer->is_token_literal() && tokenizer->get_token(1) == GDScriptTokenizer::TK_OP_ASSIGN) { // We check with is_token_literal, as this allows us to use match/sync/etc. as a name //lua style identifier, easier to write ConstantNode *cn = alloc_node<ConstantNode>(); cn->value = tokenizer->get_token_literal(); cn->datatype = _type_from_variant(cn->value); key = cn; tokenizer->advance(2); expecting = DICT_EXPECT_VALUE; } else { //python/js style more flexible key = _parse_expression(dict, p_static, p_allow_assign, p_parsing_constant); if (!key) return NULL; expecting = DICT_EXPECT_COLON; } } if (expecting == DICT_EXPECT_VALUE) { Node *value = _parse_expression(dict, p_static, p_allow_assign, p_parsing_constant); if (!value) return NULL; expecting = DICT_EXPECT_COMMA; if (key->type == GDScriptParser::Node::TYPE_CONSTANT) { Variant const &keyName = static_cast<const GDScriptParser::ConstantNode *>(key)->value; if (keys.has(keyName)) { _set_error("Duplicate key found in Dictionary literal"); return NULL; } keys.insert(keyName); } DictionaryNode::Pair pair; pair.key = key; pair.value = value; dict->elements.push_back(pair); key = NULL; } } } expr = dict; } else if (tokenizer->get_token() == GDScriptTokenizer::TK_PERIOD && (tokenizer->is_token_literal(1) || tokenizer->get_token(1) == GDScriptTokenizer::TK_CURSOR)) { // We check with is_token_literal, as this allows us to use match/sync/etc. as a name // parent call tokenizer->advance(); //goto identifier OperatorNode *op = alloc_node<OperatorNode>(); op->op = OperatorNode::OP_PARENT_CALL; /*SelfNode *self = alloc_node<SelfNode>(); op->arguments.push_back(self); forbidden for now */ StringName identifier; bool is_completion = _get_completable_identifier(COMPLETION_PARENT_FUNCTION, identifier) && for_completion; IdentifierNode *id = alloc_node<IdentifierNode>(); id->name = identifier; op->arguments.push_back(id); if (tokenizer->get_token() != GDScriptTokenizer::TK_PARENTHESIS_OPEN) { if (!is_completion) { _set_error("Expected '(' for parent function call."); return NULL; } } else { tokenizer->advance(); if (!_parse_arguments(op, op->arguments, p_static, false, p_parsing_constant)) { return NULL; } } expr = op; } else if (tokenizer->get_token() == GDScriptTokenizer::TK_BUILT_IN_TYPE && expression.size() > 0 && expression[expression.size() - 1].is_op && expression[expression.size() - 1].op == OperatorNode::OP_IS) { Expression e = expression[expression.size() - 1]; e.op = OperatorNode::OP_IS_BUILTIN; expression.write[expression.size() - 1] = e; TypeNode *tn = alloc_node<TypeNode>(); tn->vtype = tokenizer->get_token_type(); expr = tn; tokenizer->advance(); } else { //find list [ or find dictionary { _set_error("Error parsing expression, misplaced: " + String(tokenizer->get_token_name(tokenizer->get_token()))); return NULL; //nothing } ERR_FAIL_COND_V_MSG(!expr, NULL, "GDScriptParser bug, couldn't figure out what expression is."); /******************/ /* Parse Indexing */ /******************/ while (true) { //expressions can be indexed any number of times if (tokenizer->get_token() == GDScriptTokenizer::TK_PERIOD) { //indexing using "." if (tokenizer->get_token(1) != GDScriptTokenizer::TK_CURSOR && !tokenizer->is_token_literal(1)) { // We check with is_token_literal, as this allows us to use match/sync/etc. as a name _set_error("Expected identifier as member"); return NULL; } else if (tokenizer->get_token(2) == GDScriptTokenizer::TK_PARENTHESIS_OPEN) { //call!! OperatorNode *op = alloc_node<OperatorNode>(); op->op = OperatorNode::OP_CALL; tokenizer->advance(); IdentifierNode *id = alloc_node<IdentifierNode>(); StringName identifier; if (_get_completable_identifier(COMPLETION_METHOD, identifier)) { completion_node = op; //indexing stuff } id->name = identifier; op->arguments.push_back(expr); // call what op->arguments.push_back(id); // call func //get arguments tokenizer->advance(1); if (tokenizer->get_token() == GDScriptTokenizer::TK_CURSOR) { _make_completable_call(0); completion_node = op; } if (!_parse_arguments(op, op->arguments, p_static, true, p_parsing_constant)) return NULL; expr = op; } else { //simple indexing! OperatorNode *op = alloc_node<OperatorNode>(); op->op = OperatorNode::OP_INDEX_NAMED; tokenizer->advance(); StringName identifier; if (_get_completable_identifier(COMPLETION_INDEX, identifier)) { if (identifier == StringName()) { identifier = "@temp"; //so it parses alright } completion_node = op; //indexing stuff } IdentifierNode *id = alloc_node<IdentifierNode>(); id->name = identifier; op->arguments.push_back(expr); op->arguments.push_back(id); expr = op; } } else if (tokenizer->get_token() == GDScriptTokenizer::TK_BRACKET_OPEN) { //indexing using "[]" OperatorNode *op = alloc_node<OperatorNode>(); op->op = OperatorNode::OP_INDEX; tokenizer->advance(1); Node *subexpr = _parse_expression(op, p_static, p_allow_assign, p_parsing_constant); if (!subexpr) { return NULL; } if (tokenizer->get_token() != GDScriptTokenizer::TK_BRACKET_CLOSE) { _set_error("Expected ']'"); return NULL; } op->arguments.push_back(expr); op->arguments.push_back(subexpr); tokenizer->advance(1); expr = op; } else break; } /*****************/ /* Parse Casting */ /*****************/ bool has_casting = expr->type == Node::TYPE_CAST; if (tokenizer->get_token() == GDScriptTokenizer::TK_PR_AS) { if (has_casting) { _set_error("Unexpected 'as'."); return NULL; } CastNode *cn = alloc_node<CastNode>(); if (!_parse_type(cn->cast_type)) { _set_error("Expected type after 'as'."); return NULL; } has_casting = true; cn->source_node = expr; expr = cn; } /******************/ /* Parse Operator */ /******************/ if (parenthesis > 0) { //remove empty space (only allowed if inside parenthesis while (tokenizer->get_token() == GDScriptTokenizer::TK_NEWLINE) { tokenizer->advance(); } } Expression e; e.is_op = false; e.node = expr; expression.push_back(e); // determine which operator is next OperatorNode::Operator op; bool valid = true; //assign, if allowed is only allowed on the first operator #define _VALIDATE_ASSIGN \ if (!p_allow_assign || has_casting) { \ _set_error("Unexpected assign."); \ return NULL; \ } \ p_allow_assign = false; switch (tokenizer->get_token()) { //see operator case GDScriptTokenizer::TK_OP_IN: op = OperatorNode::OP_IN; break; case GDScriptTokenizer::TK_OP_EQUAL: op = OperatorNode::OP_EQUAL; break; case GDScriptTokenizer::TK_OP_NOT_EQUAL: op = OperatorNode::OP_NOT_EQUAL; break; case GDScriptTokenizer::TK_OP_LESS: op = OperatorNode::OP_LESS; break; case GDScriptTokenizer::TK_OP_LESS_EQUAL: op = OperatorNode::OP_LESS_EQUAL; break; case GDScriptTokenizer::TK_OP_GREATER: op = OperatorNode::OP_GREATER; break; case GDScriptTokenizer::TK_OP_GREATER_EQUAL: op = OperatorNode::OP_GREATER_EQUAL; break; case GDScriptTokenizer::TK_OP_AND: op = OperatorNode::OP_AND; break; case GDScriptTokenizer::TK_OP_OR: op = OperatorNode::OP_OR; break; case GDScriptTokenizer::TK_OP_ADD: op = OperatorNode::OP_ADD; break; case GDScriptTokenizer::TK_OP_SUB: op = OperatorNode::OP_SUB; break; case GDScriptTokenizer::TK_OP_MUL: op = OperatorNode::OP_MUL; break; case GDScriptTokenizer::TK_OP_DIV: op = OperatorNode::OP_DIV; break; case GDScriptTokenizer::TK_OP_MOD: op = OperatorNode::OP_MOD; break; //case GDScriptTokenizer::TK_OP_NEG: op=OperatorNode::OP_NEG ; break; case GDScriptTokenizer::TK_OP_SHIFT_LEFT: op = OperatorNode::OP_SHIFT_LEFT; break; case GDScriptTokenizer::TK_OP_SHIFT_RIGHT: op = OperatorNode::OP_SHIFT_RIGHT; break; case GDScriptTokenizer::TK_OP_ASSIGN: { _VALIDATE_ASSIGN op = OperatorNode::OP_ASSIGN; if (tokenizer->get_token(1) == GDScriptTokenizer::TK_CURSOR) { //code complete assignment completion_type = COMPLETION_ASSIGN; completion_node = expr; completion_class = current_class; completion_function = current_function; completion_line = tokenizer->get_token_line(); completion_block = current_block; completion_found = true; tokenizer->advance(); } } break; case GDScriptTokenizer::TK_OP_ASSIGN_ADD: _VALIDATE_ASSIGN op = OperatorNode::OP_ASSIGN_ADD; break; case GDScriptTokenizer::TK_OP_ASSIGN_SUB: _VALIDATE_ASSIGN op = OperatorNode::OP_ASSIGN_SUB; break; case GDScriptTokenizer::TK_OP_ASSIGN_MUL: _VALIDATE_ASSIGN op = OperatorNode::OP_ASSIGN_MUL; break; case GDScriptTokenizer::TK_OP_ASSIGN_DIV: _VALIDATE_ASSIGN op = OperatorNode::OP_ASSIGN_DIV; break; case GDScriptTokenizer::TK_OP_ASSIGN_MOD: _VALIDATE_ASSIGN op = OperatorNode::OP_ASSIGN_MOD; break; case GDScriptTokenizer::TK_OP_ASSIGN_SHIFT_LEFT: _VALIDATE_ASSIGN op = OperatorNode::OP_ASSIGN_SHIFT_LEFT; break; case GDScriptTokenizer::TK_OP_ASSIGN_SHIFT_RIGHT: _VALIDATE_ASSIGN op = OperatorNode::OP_ASSIGN_SHIFT_RIGHT; break; case GDScriptTokenizer::TK_OP_ASSIGN_BIT_AND: _VALIDATE_ASSIGN op = OperatorNode::OP_ASSIGN_BIT_AND; break; case GDScriptTokenizer::TK_OP_ASSIGN_BIT_OR: _VALIDATE_ASSIGN op = OperatorNode::OP_ASSIGN_BIT_OR; break; case GDScriptTokenizer::TK_OP_ASSIGN_BIT_XOR: _VALIDATE_ASSIGN op = OperatorNode::OP_ASSIGN_BIT_XOR; break; case GDScriptTokenizer::TK_OP_BIT_AND: op = OperatorNode::OP_BIT_AND; break; case GDScriptTokenizer::TK_OP_BIT_OR: op = OperatorNode::OP_BIT_OR; break; case GDScriptTokenizer::TK_OP_BIT_XOR: op = OperatorNode::OP_BIT_XOR; break; case GDScriptTokenizer::TK_PR_IS: op = OperatorNode::OP_IS; break; case GDScriptTokenizer::TK_CF_IF: op = OperatorNode::OP_TERNARY_IF; break; case GDScriptTokenizer::TK_CF_ELSE: op = OperatorNode::OP_TERNARY_ELSE; break; default: valid = false; break; } if (valid) { e.is_op = true; e.op = op; expression.push_back(e); tokenizer->advance(); } else { break; } } /* Reduce the set set of expressions and place them in an operator tree, respecting precedence */ while (expression.size() > 1) { int next_op = -1; int min_priority = 0xFFFFF; bool is_unary = false; bool is_ternary = false; for (int i = 0; i < expression.size(); i++) { if (!expression[i].is_op) { continue; } int priority; bool unary = false; bool ternary = false; bool error = false; bool right_to_left = false; switch (expression[i].op) { case OperatorNode::OP_IS: case OperatorNode::OP_IS_BUILTIN: priority = -1; break; //before anything case OperatorNode::OP_BIT_INVERT: priority = 0; unary = true; break; case OperatorNode::OP_NEG: case OperatorNode::OP_POS: priority = 1; unary = true; break; case OperatorNode::OP_MUL: priority = 2; break; case OperatorNode::OP_DIV: priority = 2; break; case OperatorNode::OP_MOD: priority = 2; break; case OperatorNode::OP_ADD: priority = 3; break; case OperatorNode::OP_SUB: priority = 3; break; case OperatorNode::OP_SHIFT_LEFT: priority = 4; break; case OperatorNode::OP_SHIFT_RIGHT: priority = 4; break; case OperatorNode::OP_BIT_AND: priority = 5; break; case OperatorNode::OP_BIT_XOR: priority = 6; break; case OperatorNode::OP_BIT_OR: priority = 7; break; case OperatorNode::OP_LESS: priority = 8; break; case OperatorNode::OP_LESS_EQUAL: priority = 8; break; case OperatorNode::OP_GREATER: priority = 8; break; case OperatorNode::OP_GREATER_EQUAL: priority = 8; break; case OperatorNode::OP_EQUAL: priority = 8; break; case OperatorNode::OP_NOT_EQUAL: priority = 8; break; case OperatorNode::OP_IN: priority = 10; break; case OperatorNode::OP_NOT: priority = 11; unary = true; break; case OperatorNode::OP_AND: priority = 12; break; case OperatorNode::OP_OR: priority = 13; break; case OperatorNode::OP_TERNARY_IF: priority = 14; ternary = true; right_to_left = true; break; case OperatorNode::OP_TERNARY_ELSE: priority = 14; error = true; // Rigth-to-left should be false in this case, otherwise it would always error. break; case OperatorNode::OP_ASSIGN: priority = 15; break; case OperatorNode::OP_ASSIGN_ADD: priority = 15; break; case OperatorNode::OP_ASSIGN_SUB: priority = 15; break; case OperatorNode::OP_ASSIGN_MUL: priority = 15; break; case OperatorNode::OP_ASSIGN_DIV: priority = 15; break; case OperatorNode::OP_ASSIGN_MOD: priority = 15; break; case OperatorNode::OP_ASSIGN_SHIFT_LEFT: priority = 15; break; case OperatorNode::OP_ASSIGN_SHIFT_RIGHT: priority = 15; break; case OperatorNode::OP_ASSIGN_BIT_AND: priority = 15; break; case OperatorNode::OP_ASSIGN_BIT_OR: priority = 15; break; case OperatorNode::OP_ASSIGN_BIT_XOR: priority = 15; break; default: { _set_error("GDScriptParser bug, invalid operator in expression: " + itos(expression[i].op)); return NULL; } } if (priority < min_priority || (right_to_left && priority == min_priority)) { // < is used for left to right (default) // <= is used for right to left if (error) { _set_error("Unexpected operator"); return NULL; } next_op = i; min_priority = priority; is_unary = unary; is_ternary = ternary; } } if (next_op == -1) { _set_error("Yet another parser bug...."); ERR_FAIL_V(NULL); } // OK! create operator.. if (is_unary) { int expr_pos = next_op; while (expression[expr_pos].is_op) { expr_pos++; if (expr_pos == expression.size()) { //can happen.. _set_error("Unexpected end of expression..."); return NULL; } } //consecutively do unary opeators for (int i = expr_pos - 1; i >= next_op; i--) { OperatorNode *op = alloc_node<OperatorNode>(); op->op = expression[i].op; op->arguments.push_back(expression[i + 1].node); op->line = op_line; //line might have been changed from a \n expression.write[i].is_op = false; expression.write[i].node = op; expression.remove(i + 1); } } else if (is_ternary) { if (next_op < 1 || next_op >= (expression.size() - 1)) { _set_error("Parser bug..."); ERR_FAIL_V(NULL); } if (next_op >= (expression.size() - 2) || expression[next_op + 2].op != OperatorNode::OP_TERNARY_ELSE) { _set_error("Expected else after ternary if."); return NULL; } if (next_op >= (expression.size() - 3)) { _set_error("Expected value after ternary else."); return NULL; } OperatorNode *op = alloc_node<OperatorNode>(); op->op = expression[next_op].op; op->line = op_line; //line might have been changed from a \n if (expression[next_op - 1].is_op) { _set_error("Parser bug..."); ERR_FAIL_V(NULL); } if (expression[next_op + 1].is_op) { // this is not invalid and can really appear // but it becomes invalid anyway because no binary op // can be followed by a unary op in a valid combination, // due to how precedence works, unaries will always disappear first _set_error("Unexpected two consecutive operators after ternary if."); return NULL; } if (expression[next_op + 3].is_op) { // this is not invalid and can really appear // but it becomes invalid anyway because no binary op // can be followed by a unary op in a valid combination, // due to how precedence works, unaries will always disappear first _set_error("Unexpected two consecutive operators after ternary else."); return NULL; } op->arguments.push_back(expression[next_op + 1].node); //next expression goes as first op->arguments.push_back(expression[next_op - 1].node); //left expression goes as when-true op->arguments.push_back(expression[next_op + 3].node); //expression after next goes as when-false //replace all 3 nodes by this operator and make it an expression expression.write[next_op - 1].node = op; expression.remove(next_op); expression.remove(next_op); expression.remove(next_op); expression.remove(next_op); } else { if (next_op < 1 || next_op >= (expression.size() - 1)) { _set_error("Parser bug..."); ERR_FAIL_V(NULL); } OperatorNode *op = alloc_node<OperatorNode>(); op->op = expression[next_op].op; op->line = op_line; //line might have been changed from a \n if (expression[next_op - 1].is_op) { _set_error("Parser bug..."); ERR_FAIL_V(NULL); } if (expression[next_op + 1].is_op) { // this is not invalid and can really appear // but it becomes invalid anyway because no binary op // can be followed by a unary op in a valid combination, // due to how precedence works, unaries will always disappear first _set_error("Unexpected two consecutive operators."); return NULL; } op->arguments.push_back(expression[next_op - 1].node); //expression goes as left op->arguments.push_back(expression[next_op + 1].node); //next expression goes as right //replace all 3 nodes by this operator and make it an expression expression.write[next_op - 1].node = op; expression.remove(next_op); expression.remove(next_op); } } return expression[0].node; } GDScriptParser::Node *GDScriptParser::_reduce_expression(Node *p_node, bool p_to_const) { switch (p_node->type) { case Node::TYPE_BUILT_IN_FUNCTION: { //many may probably be optimizable return p_node; } break; case Node::TYPE_ARRAY: { ArrayNode *an = static_cast<ArrayNode *>(p_node); bool all_constants = true; for (int i = 0; i < an->elements.size(); i++) { an->elements.write[i] = _reduce_expression(an->elements[i], p_to_const); if (an->elements[i]->type != Node::TYPE_CONSTANT) all_constants = false; } if (all_constants && p_to_const) { //reduce constant array expression ConstantNode *cn = alloc_node<ConstantNode>(); Array arr; arr.resize(an->elements.size()); for (int i = 0; i < an->elements.size(); i++) { ConstantNode *acn = static_cast<ConstantNode *>(an->elements[i]); arr[i] = acn->value; } cn->value = arr; cn->datatype = _type_from_variant(cn->value); return cn; } return an; } break; case Node::TYPE_DICTIONARY: { DictionaryNode *dn = static_cast<DictionaryNode *>(p_node); bool all_constants = true; for (int i = 0; i < dn->elements.size(); i++) { dn->elements.write[i].key = _reduce_expression(dn->elements[i].key, p_to_const); if (dn->elements[i].key->type != Node::TYPE_CONSTANT) all_constants = false; dn->elements.write[i].value = _reduce_expression(dn->elements[i].value, p_to_const); if (dn->elements[i].value->type != Node::TYPE_CONSTANT) all_constants = false; } if (all_constants && p_to_const) { //reduce constant array expression ConstantNode *cn = alloc_node<ConstantNode>(); Dictionary dict; for (int i = 0; i < dn->elements.size(); i++) { ConstantNode *key_c = static_cast<ConstantNode *>(dn->elements[i].key); ConstantNode *value_c = static_cast<ConstantNode *>(dn->elements[i].value); dict[key_c->value] = value_c->value; } cn->value = dict; cn->datatype = _type_from_variant(cn->value); return cn; } return dn; } break; case Node::TYPE_OPERATOR: { OperatorNode *op = static_cast<OperatorNode *>(p_node); bool all_constants = true; int last_not_constant = -1; for (int i = 0; i < op->arguments.size(); i++) { op->arguments.write[i] = _reduce_expression(op->arguments[i], p_to_const); if (op->arguments[i]->type != Node::TYPE_CONSTANT) { all_constants = false; last_not_constant = i; } } if (op->op == OperatorNode::OP_IS) { //nothing much return op; } if (op->op == OperatorNode::OP_PARENT_CALL) { //nothing much return op; } else if (op->op == OperatorNode::OP_CALL) { //can reduce base type constructors if ((op->arguments[0]->type == Node::TYPE_TYPE || (op->arguments[0]->type == Node::TYPE_BUILT_IN_FUNCTION && GDScriptFunctions::is_deterministic(static_cast<BuiltInFunctionNode *>(op->arguments[0])->function))) && last_not_constant == 0) { //native type constructor or intrinsic function const Variant **vptr = NULL; Vector<Variant *> ptrs; if (op->arguments.size() > 1) { ptrs.resize(op->arguments.size() - 1); for (int i = 0; i < ptrs.size(); i++) { ConstantNode *cn = static_cast<ConstantNode *>(op->arguments[i + 1]); ptrs.write[i] = &cn->value; } vptr = (const Variant **)&ptrs[0]; } Variant::CallError ce; Variant v; if (op->arguments[0]->type == Node::TYPE_TYPE) { TypeNode *tn = static_cast<TypeNode *>(op->arguments[0]); v = Variant::construct(tn->vtype, vptr, ptrs.size(), ce); } else { GDScriptFunctions::Function func = static_cast<BuiltInFunctionNode *>(op->arguments[0])->function; GDScriptFunctions::call(func, vptr, ptrs.size(), v, ce); } if (ce.error != Variant::CallError::CALL_OK) { String errwhere; if (op->arguments[0]->type == Node::TYPE_TYPE) { TypeNode *tn = static_cast<TypeNode *>(op->arguments[0]); errwhere = "'" + Variant::get_type_name(tn->vtype) + "' constructor"; } else { GDScriptFunctions::Function func = static_cast<BuiltInFunctionNode *>(op->arguments[0])->function; errwhere = String("'") + GDScriptFunctions::get_func_name(func) + "' intrinsic function"; } switch (ce.error) { case Variant::CallError::CALL_ERROR_INVALID_ARGUMENT: { _set_error("Invalid argument (#" + itos(ce.argument + 1) + ") for " + errwhere + "."); } break; case Variant::CallError::CALL_ERROR_TOO_MANY_ARGUMENTS: { _set_error("Too many arguments for " + errwhere + "."); } break; case Variant::CallError::CALL_ERROR_TOO_FEW_ARGUMENTS: { _set_error("Too few arguments for " + errwhere + "."); } break; default: { _set_error("Invalid arguments for " + errwhere + "."); } break; } error_line = op->line; return p_node; } ConstantNode *cn = alloc_node<ConstantNode>(); cn->value = v; cn->datatype = _type_from_variant(v); return cn; } return op; //don't reduce yet } else if (op->op == OperatorNode::OP_YIELD) { return op; } else if (op->op == OperatorNode::OP_INDEX) { //can reduce indices into constant arrays or dictionaries if (all_constants) { ConstantNode *ca = static_cast<ConstantNode *>(op->arguments[0]); ConstantNode *cb = static_cast<ConstantNode *>(op->arguments[1]); bool valid; Variant v = ca->value.get(cb->value, &valid); if (!valid) { _set_error("invalid index in constant expression"); error_line = op->line; return op; } ConstantNode *cn = alloc_node<ConstantNode>(); cn->value = v; cn->datatype = _type_from_variant(v); return cn; } return op; } else if (op->op == OperatorNode::OP_INDEX_NAMED) { if (op->arguments[0]->type == Node::TYPE_CONSTANT && op->arguments[1]->type == Node::TYPE_IDENTIFIER) { ConstantNode *ca = static_cast<ConstantNode *>(op->arguments[0]); IdentifierNode *ib = static_cast<IdentifierNode *>(op->arguments[1]); bool valid; Variant v = ca->value.get_named(ib->name, &valid); if (!valid) { _set_error("invalid index '" + String(ib->name) + "' in constant expression"); error_line = op->line; return op; } ConstantNode *cn = alloc_node<ConstantNode>(); cn->value = v; cn->datatype = _type_from_variant(v); return cn; } return op; } //validate assignment (don't assign to constant expression switch (op->op) { case OperatorNode::OP_ASSIGN: case OperatorNode::OP_ASSIGN_ADD: case OperatorNode::OP_ASSIGN_SUB: case OperatorNode::OP_ASSIGN_MUL: case OperatorNode::OP_ASSIGN_DIV: case OperatorNode::OP_ASSIGN_MOD: case OperatorNode::OP_ASSIGN_SHIFT_LEFT: case OperatorNode::OP_ASSIGN_SHIFT_RIGHT: case OperatorNode::OP_ASSIGN_BIT_AND: case OperatorNode::OP_ASSIGN_BIT_OR: case OperatorNode::OP_ASSIGN_BIT_XOR: { if (op->arguments[0]->type == Node::TYPE_CONSTANT) { _set_error("Can't assign to constant", tokenizer->get_token_line() - 1); error_line = op->line; return op; } else if (op->arguments[0]->type == Node::TYPE_SELF) { _set_error("Can't assign to self.", op->line); error_line = op->line; return op; } if (op->arguments[0]->type == Node::TYPE_OPERATOR) { OperatorNode *on = static_cast<OperatorNode *>(op->arguments[0]); if (on->op != OperatorNode::OP_INDEX && on->op != OperatorNode::OP_INDEX_NAMED) { _set_error("Can't assign to an expression", tokenizer->get_token_line() - 1); error_line = op->line; return op; } } } break; default: { break; } } //now se if all are constants if (!all_constants) return op; //nothing to reduce from here on #define _REDUCE_UNARY(m_vop) \ bool valid = false; \ Variant res; \ Variant::evaluate(m_vop, static_cast<ConstantNode *>(op->arguments[0])->value, Variant(), res, valid); \ if (!valid) { \ _set_error("Invalid operand for unary operator"); \ error_line = op->line; \ return p_node; \ } \ ConstantNode *cn = alloc_node<ConstantNode>(); \ cn->value = res; \ cn->datatype = _type_from_variant(res); \ return cn; #define _REDUCE_BINARY(m_vop) \ bool valid = false; \ Variant res; \ Variant::evaluate(m_vop, static_cast<ConstantNode *>(op->arguments[0])->value, static_cast<ConstantNode *>(op->arguments[1])->value, res, valid); \ if (!valid) { \ _set_error("Invalid operands for operator"); \ error_line = op->line; \ return p_node; \ } \ ConstantNode *cn = alloc_node<ConstantNode>(); \ cn->value = res; \ cn->datatype = _type_from_variant(res); \ return cn; switch (op->op) { //unary operators case OperatorNode::OP_NEG: { _REDUCE_UNARY(Variant::OP_NEGATE); } break; case OperatorNode::OP_POS: { _REDUCE_UNARY(Variant::OP_POSITIVE); } break; case OperatorNode::OP_NOT: { _REDUCE_UNARY(Variant::OP_NOT); } break; case OperatorNode::OP_BIT_INVERT: { _REDUCE_UNARY(Variant::OP_BIT_NEGATE); } break; //binary operators (in precedence order) case OperatorNode::OP_IN: { _REDUCE_BINARY(Variant::OP_IN); } break; case OperatorNode::OP_EQUAL: { _REDUCE_BINARY(Variant::OP_EQUAL); } break; case OperatorNode::OP_NOT_EQUAL: { _REDUCE_BINARY(Variant::OP_NOT_EQUAL); } break; case OperatorNode::OP_LESS: { _REDUCE_BINARY(Variant::OP_LESS); } break; case OperatorNode::OP_LESS_EQUAL: { _REDUCE_BINARY(Variant::OP_LESS_EQUAL); } break; case OperatorNode::OP_GREATER: { _REDUCE_BINARY(Variant::OP_GREATER); } break; case OperatorNode::OP_GREATER_EQUAL: { _REDUCE_BINARY(Variant::OP_GREATER_EQUAL); } break; case OperatorNode::OP_AND: { _REDUCE_BINARY(Variant::OP_AND); } break; case OperatorNode::OP_OR: { _REDUCE_BINARY(Variant::OP_OR); } break; case OperatorNode::OP_ADD: { _REDUCE_BINARY(Variant::OP_ADD); } break; case OperatorNode::OP_SUB: { _REDUCE_BINARY(Variant::OP_SUBTRACT); } break; case OperatorNode::OP_MUL: { _REDUCE_BINARY(Variant::OP_MULTIPLY); } break; case OperatorNode::OP_DIV: { _REDUCE_BINARY(Variant::OP_DIVIDE); } break; case OperatorNode::OP_MOD: { _REDUCE_BINARY(Variant::OP_MODULE); } break; case OperatorNode::OP_SHIFT_LEFT: { _REDUCE_BINARY(Variant::OP_SHIFT_LEFT); } break; case OperatorNode::OP_SHIFT_RIGHT: { _REDUCE_BINARY(Variant::OP_SHIFT_RIGHT); } break; case OperatorNode::OP_BIT_AND: { _REDUCE_BINARY(Variant::OP_BIT_AND); } break; case OperatorNode::OP_BIT_OR: { _REDUCE_BINARY(Variant::OP_BIT_OR); } break; case OperatorNode::OP_BIT_XOR: { _REDUCE_BINARY(Variant::OP_BIT_XOR); } break; case OperatorNode::OP_TERNARY_IF: { if (static_cast<ConstantNode *>(op->arguments[0])->value.booleanize()) { return op->arguments[1]; } else { return op->arguments[2]; } } break; default: { ERR_FAIL_V(op); } } } break; default: { return p_node; } break; } } GDScriptParser::Node *GDScriptParser::_parse_and_reduce_expression(Node *p_parent, bool p_static, bool p_reduce_const, bool p_allow_assign) { Node *expr = _parse_expression(p_parent, p_static, p_allow_assign, p_reduce_const); if (!expr || error_set) return NULL; expr = _reduce_expression(expr, p_reduce_const); if (!expr || error_set) return NULL; return expr; } bool GDScriptParser::_recover_from_completion() { if (!completion_found) { return false; //can't recover if no completion } //skip stuff until newline while (tokenizer->get_token() != GDScriptTokenizer::TK_NEWLINE && tokenizer->get_token() != GDScriptTokenizer::TK_EOF && tokenizer->get_token() != GDScriptTokenizer::TK_ERROR) { tokenizer->advance(); } completion_found = false; error_set = false; if (tokenizer->get_token() == GDScriptTokenizer::TK_ERROR) { error_set = true; } return true; } GDScriptParser::PatternNode *GDScriptParser::_parse_pattern(bool p_static) { PatternNode *pattern = alloc_node<PatternNode>(); GDScriptTokenizer::Token token = tokenizer->get_token(); if (error_set) return NULL; if (token == GDScriptTokenizer::TK_EOF) { return NULL; } switch (token) { // array case GDScriptTokenizer::TK_BRACKET_OPEN: { tokenizer->advance(); pattern->pt_type = GDScriptParser::PatternNode::PT_ARRAY; while (true) { if (tokenizer->get_token() == GDScriptTokenizer::TK_BRACKET_CLOSE) { tokenizer->advance(); break; } if (tokenizer->get_token() == GDScriptTokenizer::TK_PERIOD && tokenizer->get_token(1) == GDScriptTokenizer::TK_PERIOD) { // match everything tokenizer->advance(2); PatternNode *sub_pattern = alloc_node<PatternNode>(); sub_pattern->pt_type = GDScriptParser::PatternNode::PT_IGNORE_REST; pattern->array.push_back(sub_pattern); if (tokenizer->get_token() == GDScriptTokenizer::TK_COMMA && tokenizer->get_token(1) == GDScriptTokenizer::TK_BRACKET_CLOSE) { tokenizer->advance(2); break; } else if (tokenizer->get_token() == GDScriptTokenizer::TK_BRACKET_CLOSE) { tokenizer->advance(1); break; } else { _set_error("'..' pattern only allowed at the end of an array pattern"); return NULL; } } PatternNode *sub_pattern = _parse_pattern(p_static); if (!sub_pattern) { return NULL; } pattern->array.push_back(sub_pattern); if (tokenizer->get_token() == GDScriptTokenizer::TK_COMMA) { tokenizer->advance(); continue; } else if (tokenizer->get_token() == GDScriptTokenizer::TK_BRACKET_CLOSE) { tokenizer->advance(); break; } else { _set_error("Not a valid pattern"); return NULL; } } } break; // bind case GDScriptTokenizer::TK_PR_VAR: { tokenizer->advance(); if (!tokenizer->is_token_literal()) { _set_error("Expected identifier for binding variable name."); return NULL; } pattern->pt_type = GDScriptParser::PatternNode::PT_BIND; pattern->bind = tokenizer->get_token_literal(); // Check if variable name is already used BlockNode *bl = current_block; while (bl) { if (bl->variables.has(pattern->bind)) { _set_error("Binding name of '" + pattern->bind.operator String() + "' is already declared in this scope."); return NULL; } bl = bl->parent_block; } // Create local variable for proper identifier detection later LocalVarNode *lv = alloc_node<LocalVarNode>(); lv->name = pattern->bind; current_block->variables.insert(lv->name, lv); tokenizer->advance(); } break; // dictionary case GDScriptTokenizer::TK_CURLY_BRACKET_OPEN: { tokenizer->advance(); pattern->pt_type = GDScriptParser::PatternNode::PT_DICTIONARY; while (true) { if (tokenizer->get_token() == GDScriptTokenizer::TK_CURLY_BRACKET_CLOSE) { tokenizer->advance(); break; } if (tokenizer->get_token() == GDScriptTokenizer::TK_PERIOD && tokenizer->get_token(1) == GDScriptTokenizer::TK_PERIOD) { // match everything tokenizer->advance(2); PatternNode *sub_pattern = alloc_node<PatternNode>(); sub_pattern->pt_type = PatternNode::PT_IGNORE_REST; pattern->array.push_back(sub_pattern); if (tokenizer->get_token() == GDScriptTokenizer::TK_COMMA && tokenizer->get_token(1) == GDScriptTokenizer::TK_CURLY_BRACKET_CLOSE) { tokenizer->advance(2); break; } else if (tokenizer->get_token() == GDScriptTokenizer::TK_CURLY_BRACKET_CLOSE) { tokenizer->advance(1); break; } else { _set_error("'..' pattern only allowed at the end of a dictionary pattern"); return NULL; } } Node *key = _parse_and_reduce_expression(pattern, p_static); if (!key) { _set_error("Not a valid key in pattern"); return NULL; } if (key->type != GDScriptParser::Node::TYPE_CONSTANT) { _set_error("Not a constant expression as key"); return NULL; } if (tokenizer->get_token() == GDScriptTokenizer::TK_COLON) { tokenizer->advance(); PatternNode *value = _parse_pattern(p_static); if (!value) { _set_error("Expected pattern in dictionary value"); return NULL; } pattern->dictionary.insert(static_cast<ConstantNode *>(key), value); } else { pattern->dictionary.insert(static_cast<ConstantNode *>(key), NULL); } if (tokenizer->get_token() == GDScriptTokenizer::TK_COMMA) { tokenizer->advance(); continue; } else if (tokenizer->get_token() == GDScriptTokenizer::TK_CURLY_BRACKET_CLOSE) { tokenizer->advance(); break; } else { _set_error("Not a valid pattern"); return NULL; } } } break; case GDScriptTokenizer::TK_WILDCARD: { tokenizer->advance(); pattern->pt_type = PatternNode::PT_WILDCARD; } break; // all the constants like strings and numbers default: { Node *value = _parse_and_reduce_expression(pattern, p_static); if (!value) { _set_error("Expect constant expression or variables in a pattern"); return NULL; } if (value->type == Node::TYPE_OPERATOR) { // Maybe it's SomeEnum.VALUE Node *current_value = value; while (current_value->type == Node::TYPE_OPERATOR) { OperatorNode *op_node = static_cast<OperatorNode *>(current_value); if (op_node->op != OperatorNode::OP_INDEX_NAMED) { _set_error("Invalid operator in pattern. Only index (`A.B`) is allowed"); return NULL; } current_value = op_node->arguments[0]; } if (current_value->type != Node::TYPE_IDENTIFIER) { _set_error("Only constant expression or variables allowed in a pattern"); return NULL; } } else if (value->type != Node::TYPE_IDENTIFIER && value->type != Node::TYPE_CONSTANT) { _set_error("Only constant expressions or variables allowed in a pattern"); return NULL; } pattern->pt_type = PatternNode::PT_CONSTANT; pattern->constant = value; } break; } return pattern; } void GDScriptParser::_parse_pattern_block(BlockNode *p_block, Vector<PatternBranchNode *> &p_branches, bool p_static) { IndentLevel current_level = indent_level.back()->get(); p_block->has_return = true; bool catch_all_appeared = false; while (true) { while (tokenizer->get_token() == GDScriptTokenizer::TK_NEWLINE && _parse_newline()) ; // GDScriptTokenizer::Token token = tokenizer->get_token(); if (error_set) return; if (current_level.indent > indent_level.back()->get().indent) { break; // go back a level } pending_newline = -1; PatternBranchNode *branch = alloc_node<PatternBranchNode>(); branch->body = alloc_node<BlockNode>(); branch->body->parent_block = p_block; p_block->sub_blocks.push_back(branch->body); current_block = branch->body; branch->patterns.push_back(_parse_pattern(p_static)); if (!branch->patterns[0]) { break; } bool has_binding = branch->patterns[0]->pt_type == PatternNode::PT_BIND; bool catch_all = has_binding || branch->patterns[0]->pt_type == PatternNode::PT_WILDCARD; #ifdef DEBUG_ENABLED // Branches after a wildcard or binding are unreachable if (catch_all_appeared && !current_function->has_unreachable_code) { _add_warning(GDScriptWarning::UNREACHABLE_CODE, -1, current_function->name.operator String()); current_function->has_unreachable_code = true; } #endif while (tokenizer->get_token() == GDScriptTokenizer::TK_COMMA) { tokenizer->advance(); branch->patterns.push_back(_parse_pattern(p_static)); if (!branch->patterns[branch->patterns.size() - 1]) { return; } PatternNode::PatternType pt = branch->patterns[branch->patterns.size() - 1]->pt_type; if (pt == PatternNode::PT_BIND) { _set_error("Cannot use bindings with multipattern."); return; } catch_all = catch_all || pt == PatternNode::PT_WILDCARD; } catch_all_appeared = catch_all_appeared || catch_all; if (!_enter_indent_block()) { _set_error("Expected block in pattern branch"); return; } _parse_block(branch->body, p_static); current_block = p_block; if (!branch->body->has_return) { p_block->has_return = false; } p_branches.push_back(branch); } // Even if all branches return, there is possibility of default fallthrough if (!catch_all_appeared) { p_block->has_return = false; } } void GDScriptParser::_generate_pattern(PatternNode *p_pattern, Node *p_node_to_match, Node *&p_resulting_node, Map<StringName, Node *> &p_bindings) { const DataType &to_match_type = p_node_to_match->get_datatype(); switch (p_pattern->pt_type) { case PatternNode::PT_CONSTANT: { DataType pattern_type = _reduce_node_type(p_pattern->constant); if (error_set) { return; } OperatorNode *type_comp = NULL; // static type check if possible if (pattern_type.has_type && to_match_type.has_type) { if (!_is_type_compatible(to_match_type, pattern_type) && !_is_type_compatible(pattern_type, to_match_type)) { _set_error("The pattern type (" + pattern_type.to_string() + ") isn't compatible with the type of the value to match (" + to_match_type.to_string() + ").", p_pattern->line); return; } } else { // runtime typecheck BuiltInFunctionNode *typeof_node = alloc_node<BuiltInFunctionNode>(); typeof_node->function = GDScriptFunctions::TYPE_OF; OperatorNode *typeof_match_value = alloc_node<OperatorNode>(); typeof_match_value->op = OperatorNode::OP_CALL; typeof_match_value->arguments.push_back(typeof_node); typeof_match_value->arguments.push_back(p_node_to_match); OperatorNode *typeof_pattern_value = alloc_node<OperatorNode>(); typeof_pattern_value->op = OperatorNode::OP_CALL; typeof_pattern_value->arguments.push_back(typeof_node); typeof_pattern_value->arguments.push_back(p_pattern->constant); type_comp = alloc_node<OperatorNode>(); type_comp->op = OperatorNode::OP_EQUAL; type_comp->arguments.push_back(typeof_match_value); type_comp->arguments.push_back(typeof_pattern_value); } // compare the actual values OperatorNode *value_comp = alloc_node<OperatorNode>(); value_comp->op = OperatorNode::OP_EQUAL; value_comp->arguments.push_back(p_pattern->constant); value_comp->arguments.push_back(p_node_to_match); if (type_comp) { OperatorNode *full_comparison = alloc_node<OperatorNode>(); full_comparison->op = OperatorNode::OP_AND; full_comparison->arguments.push_back(type_comp); full_comparison->arguments.push_back(value_comp); p_resulting_node = full_comparison; } else { p_resulting_node = value_comp; } } break; case PatternNode::PT_BIND: { p_bindings[p_pattern->bind] = p_node_to_match; // a bind always matches ConstantNode *true_value = alloc_node<ConstantNode>(); true_value->value = Variant(true); p_resulting_node = true_value; } break; case PatternNode::PT_ARRAY: { bool open_ended = false; if (p_pattern->array.size() > 0) { if (p_pattern->array[p_pattern->array.size() - 1]->pt_type == PatternNode::PT_IGNORE_REST) { open_ended = true; } } // typeof(value_to_match) == TYPE_ARRAY && value_to_match.size() >= length // typeof(value_to_match) == TYPE_ARRAY && value_to_match.size() == length { OperatorNode *type_comp = NULL; // static type check if possible if (to_match_type.has_type) { // must be an array if (to_match_type.kind != DataType::BUILTIN || to_match_type.builtin_type != Variant::ARRAY) { _set_error("Cannot match an array pattern with a non-array expression.", p_pattern->line); return; } } else { // runtime typecheck BuiltInFunctionNode *typeof_node = alloc_node<BuiltInFunctionNode>(); typeof_node->function = GDScriptFunctions::TYPE_OF; OperatorNode *typeof_match_value = alloc_node<OperatorNode>(); typeof_match_value->op = OperatorNode::OP_CALL; typeof_match_value->arguments.push_back(typeof_node); typeof_match_value->arguments.push_back(p_node_to_match); IdentifierNode *typeof_array = alloc_node<IdentifierNode>(); typeof_array->name = "TYPE_ARRAY"; type_comp = alloc_node<OperatorNode>(); type_comp->op = OperatorNode::OP_EQUAL; type_comp->arguments.push_back(typeof_match_value); type_comp->arguments.push_back(typeof_array); } // size ConstantNode *length = alloc_node<ConstantNode>(); length->value = Variant(open_ended ? p_pattern->array.size() - 1 : p_pattern->array.size()); OperatorNode *call = alloc_node<OperatorNode>(); call->op = OperatorNode::OP_CALL; call->arguments.push_back(p_node_to_match); IdentifierNode *size = alloc_node<IdentifierNode>(); size->name = "size"; call->arguments.push_back(size); OperatorNode *length_comparison = alloc_node<OperatorNode>(); length_comparison->op = open_ended ? OperatorNode::OP_GREATER_EQUAL : OperatorNode::OP_EQUAL; length_comparison->arguments.push_back(call); length_comparison->arguments.push_back(length); if (type_comp) { OperatorNode *type_and_length_comparison = alloc_node<OperatorNode>(); type_and_length_comparison->op = OperatorNode::OP_AND; type_and_length_comparison->arguments.push_back(type_comp); type_and_length_comparison->arguments.push_back(length_comparison); p_resulting_node = type_and_length_comparison; } else { p_resulting_node = length_comparison; } } for (int i = 0; i < p_pattern->array.size(); i++) { PatternNode *pattern = p_pattern->array[i]; Node *condition = NULL; ConstantNode *index = alloc_node<ConstantNode>(); index->value = Variant(i); OperatorNode *indexed_value = alloc_node<OperatorNode>(); indexed_value->op = OperatorNode::OP_INDEX; indexed_value->arguments.push_back(p_node_to_match); indexed_value->arguments.push_back(index); _generate_pattern(pattern, indexed_value, condition, p_bindings); // concatenate all the patterns with && OperatorNode *and_node = alloc_node<OperatorNode>(); and_node->op = OperatorNode::OP_AND; and_node->arguments.push_back(p_resulting_node); and_node->arguments.push_back(condition); p_resulting_node = and_node; } } break; case PatternNode::PT_DICTIONARY: { bool open_ended = false; if (p_pattern->array.size() > 0) { open_ended = true; } // typeof(value_to_match) == TYPE_DICTIONARY && value_to_match.size() >= length // typeof(value_to_match) == TYPE_DICTIONARY && value_to_match.size() == length { OperatorNode *type_comp = NULL; // static type check if possible if (to_match_type.has_type) { // must be an dictionary if (to_match_type.kind != DataType::BUILTIN || to_match_type.builtin_type != Variant::DICTIONARY) { _set_error("Cannot match an dictionary pattern with a non-dictionary expression.", p_pattern->line); return; } } else { // runtime typecheck BuiltInFunctionNode *typeof_node = alloc_node<BuiltInFunctionNode>(); typeof_node->function = GDScriptFunctions::TYPE_OF; OperatorNode *typeof_match_value = alloc_node<OperatorNode>(); typeof_match_value->op = OperatorNode::OP_CALL; typeof_match_value->arguments.push_back(typeof_node); typeof_match_value->arguments.push_back(p_node_to_match); IdentifierNode *typeof_dictionary = alloc_node<IdentifierNode>(); typeof_dictionary->name = "TYPE_DICTIONARY"; type_comp = alloc_node<OperatorNode>(); type_comp->op = OperatorNode::OP_EQUAL; type_comp->arguments.push_back(typeof_match_value); type_comp->arguments.push_back(typeof_dictionary); } // size ConstantNode *length = alloc_node<ConstantNode>(); length->value = Variant(open_ended ? p_pattern->dictionary.size() - 1 : p_pattern->dictionary.size()); OperatorNode *call = alloc_node<OperatorNode>(); call->op = OperatorNode::OP_CALL; call->arguments.push_back(p_node_to_match); IdentifierNode *size = alloc_node<IdentifierNode>(); size->name = "size"; call->arguments.push_back(size); OperatorNode *length_comparison = alloc_node<OperatorNode>(); length_comparison->op = open_ended ? OperatorNode::OP_GREATER_EQUAL : OperatorNode::OP_EQUAL; length_comparison->arguments.push_back(call); length_comparison->arguments.push_back(length); if (type_comp) { OperatorNode *type_and_length_comparison = alloc_node<OperatorNode>(); type_and_length_comparison->op = OperatorNode::OP_AND; type_and_length_comparison->arguments.push_back(type_comp); type_and_length_comparison->arguments.push_back(length_comparison); p_resulting_node = type_and_length_comparison; } else { p_resulting_node = length_comparison; } } for (Map<ConstantNode *, PatternNode *>::Element *e = p_pattern->dictionary.front(); e; e = e->next()) { Node *condition = NULL; // check for has, then for pattern IdentifierNode *has = alloc_node<IdentifierNode>(); has->name = "has"; OperatorNode *has_call = alloc_node<OperatorNode>(); has_call->op = OperatorNode::OP_CALL; has_call->arguments.push_back(p_node_to_match); has_call->arguments.push_back(has); has_call->arguments.push_back(e->key()); if (e->value()) { OperatorNode *indexed_value = alloc_node<OperatorNode>(); indexed_value->op = OperatorNode::OP_INDEX; indexed_value->arguments.push_back(p_node_to_match); indexed_value->arguments.push_back(e->key()); _generate_pattern(e->value(), indexed_value, condition, p_bindings); OperatorNode *has_and_pattern = alloc_node<OperatorNode>(); has_and_pattern->op = OperatorNode::OP_AND; has_and_pattern->arguments.push_back(has_call); has_and_pattern->arguments.push_back(condition); condition = has_and_pattern; } else { condition = has_call; } // concatenate all the patterns with && OperatorNode *and_node = alloc_node<OperatorNode>(); and_node->op = OperatorNode::OP_AND; and_node->arguments.push_back(p_resulting_node); and_node->arguments.push_back(condition); p_resulting_node = and_node; } } break; case PatternNode::PT_IGNORE_REST: case PatternNode::PT_WILDCARD: { // simply generate a `true` ConstantNode *true_value = alloc_node<ConstantNode>(); true_value->value = Variant(true); p_resulting_node = true_value; } break; default: { } break; } } void GDScriptParser::_transform_match_statment(MatchNode *p_match_statement) { IdentifierNode *id = alloc_node<IdentifierNode>(); id->name = "#match_value"; id->line = p_match_statement->line; id->datatype = _reduce_node_type(p_match_statement->val_to_match); if (id->datatype.has_type) { _mark_line_as_safe(id->line); } else { _mark_line_as_unsafe(id->line); } if (error_set) { return; } for (int i = 0; i < p_match_statement->branches.size(); i++) { PatternBranchNode *branch = p_match_statement->branches[i]; MatchNode::CompiledPatternBranch compiled_branch; compiled_branch.compiled_pattern = NULL; Map<StringName, Node *> binding; for (int j = 0; j < branch->patterns.size(); j++) { PatternNode *pattern = branch->patterns[j]; _mark_line_as_safe(pattern->line); Map<StringName, Node *> bindings; Node *resulting_node = NULL; _generate_pattern(pattern, id, resulting_node, bindings); if (!resulting_node) { return; } if (!binding.empty() && !bindings.empty()) { _set_error("Multipatterns can't contain bindings"); return; } else { binding = bindings; } // Result is always a boolean DataType resulting_node_type; resulting_node_type.has_type = true; resulting_node_type.is_constant = true; resulting_node_type.kind = DataType::BUILTIN; resulting_node_type.builtin_type = Variant::BOOL; resulting_node->set_datatype(resulting_node_type); if (compiled_branch.compiled_pattern) { OperatorNode *or_node = alloc_node<OperatorNode>(); or_node->op = OperatorNode::OP_OR; or_node->arguments.push_back(compiled_branch.compiled_pattern); or_node->arguments.push_back(resulting_node); compiled_branch.compiled_pattern = or_node; } else { // single pattern | first one compiled_branch.compiled_pattern = resulting_node; } } // prepare the body ...hehe for (Map<StringName, Node *>::Element *e = binding.front(); e; e = e->next()) { if (!branch->body->variables.has(e->key())) { _set_error("Parser bug: missing pattern bind variable.", branch->line); ERR_FAIL(); } LocalVarNode *local_var = branch->body->variables[e->key()]; local_var->assign = e->value(); local_var->set_datatype(local_var->assign->get_datatype()); IdentifierNode *id2 = alloc_node<IdentifierNode>(); id2->name = local_var->name; id2->declared_block = branch->body; id2->set_datatype(local_var->assign->get_datatype()); OperatorNode *op = alloc_node<OperatorNode>(); op->op = OperatorNode::OP_ASSIGN; op->arguments.push_back(id2); op->arguments.push_back(local_var->assign); local_var->assign_op = op; branch->body->statements.push_front(op); branch->body->statements.push_front(local_var); } compiled_branch.body = branch->body; p_match_statement->compiled_pattern_branches.push_back(compiled_branch); } } void GDScriptParser::_parse_block(BlockNode *p_block, bool p_static) { IndentLevel current_level = indent_level.back()->get(); #ifdef DEBUG_ENABLED NewLineNode *nl = alloc_node<NewLineNode>(); nl->line = tokenizer->get_token_line(); p_block->statements.push_back(nl); #endif bool is_first_line = true; while (true) { if (!is_first_line && indent_level.back()->prev() && indent_level.back()->prev()->get().indent == current_level.indent) { if (indent_level.back()->prev()->get().is_mixed(current_level)) { _set_error("Mixed tabs and spaces in indentation."); return; } // pythonic single-line expression, don't parse future lines indent_level.pop_back(); p_block->end_line = tokenizer->get_token_line(); return; } is_first_line = false; GDScriptTokenizer::Token token = tokenizer->get_token(); if (error_set) return; if (current_level.indent > indent_level.back()->get().indent) { p_block->end_line = tokenizer->get_token_line(); return; //go back a level } if (pending_newline != -1) { NewLineNode *nl2 = alloc_node<NewLineNode>(); nl2->line = pending_newline; p_block->statements.push_back(nl2); pending_newline = -1; } #ifdef DEBUG_ENABLED switch (token) { case GDScriptTokenizer::TK_EOF: case GDScriptTokenizer::TK_ERROR: case GDScriptTokenizer::TK_NEWLINE: case GDScriptTokenizer::TK_CF_PASS: { // will check later } break; default: { if (p_block->has_return && !current_function->has_unreachable_code) { _add_warning(GDScriptWarning::UNREACHABLE_CODE, -1, current_function->name.operator String()); current_function->has_unreachable_code = true; } } break; } #endif // DEBUG_ENABLED switch (token) { case GDScriptTokenizer::TK_EOF: p_block->end_line = tokenizer->get_token_line(); case GDScriptTokenizer::TK_ERROR: { return; //go back //end of file! } break; case GDScriptTokenizer::TK_NEWLINE: { int line = tokenizer->get_token_line(); if (!_parse_newline()) { if (!error_set) { p_block->end_line = tokenizer->get_token_line(); pending_newline = p_block->end_line; } return; } NewLineNode *nl2 = alloc_node<NewLineNode>(); nl2->line = line; p_block->statements.push_back(nl2); } break; case GDScriptTokenizer::TK_CF_PASS: { if (tokenizer->get_token(1) != GDScriptTokenizer::TK_SEMICOLON && tokenizer->get_token(1) != GDScriptTokenizer::TK_NEWLINE && tokenizer->get_token(1) != GDScriptTokenizer::TK_EOF) { _set_error("Expected \";\" or a line break."); return; } _mark_line_as_safe(tokenizer->get_token_line()); tokenizer->advance(); if (tokenizer->get_token() == GDScriptTokenizer::TK_SEMICOLON) { // Ignore semicolon after 'pass'. tokenizer->advance(); } } break; case GDScriptTokenizer::TK_PR_VAR: { // Variable declaration and (eventual) initialization. tokenizer->advance(); int var_line = tokenizer->get_token_line(); if (!tokenizer->is_token_literal(0, true)) { _set_error("Expected an identifier for the local variable name."); return; } StringName n = tokenizer->get_token_literal(); tokenizer->advance(); if (current_function) { for (int i = 0; i < current_function->arguments.size(); i++) { if (n == current_function->arguments[i]) { _set_error("Variable \"" + String(n) + "\" already defined in the scope (at line " + itos(current_function->line) + ")."); return; } } } BlockNode *check_block = p_block; while (check_block) { if (check_block->variables.has(n)) { _set_error("Variable \"" + String(n) + "\" already defined in the scope (at line " + itos(check_block->variables[n]->line) + ")."); return; } check_block = check_block->parent_block; } //must know when the local variable is declared LocalVarNode *lv = alloc_node<LocalVarNode>(); lv->name = n; lv->line = var_line; p_block->statements.push_back(lv); Node *assigned = NULL; if (tokenizer->get_token() == GDScriptTokenizer::TK_COLON) { if (tokenizer->get_token(1) == GDScriptTokenizer::TK_OP_ASSIGN) { lv->datatype = DataType(); #ifdef DEBUG_ENABLED lv->datatype.infer_type = true; #endif tokenizer->advance(); } else if (!_parse_type(lv->datatype)) { _set_error("Expected a type for the variable."); return; } } if (tokenizer->get_token() == GDScriptTokenizer::TK_OP_ASSIGN) { tokenizer->advance(); Node *subexpr = _parse_and_reduce_expression(p_block, p_static); if (!subexpr) { if (_recover_from_completion()) { break; } return; } lv->assignments++; assigned = subexpr; } else { assigned = _get_default_value_for_type(lv->datatype, var_line); } //must be added later, to avoid self-referencing. p_block->variables.insert(n, lv); IdentifierNode *id = alloc_node<IdentifierNode>(); id->name = n; id->declared_block = p_block; id->line = var_line; OperatorNode *op = alloc_node<OperatorNode>(); op->op = OperatorNode::OP_ASSIGN; op->arguments.push_back(id); op->arguments.push_back(assigned); op->line = var_line; p_block->statements.push_back(op); lv->assign_op = op; lv->assign = assigned; if (!_end_statement()) { _set_error("Expected end of statement (\"var\")."); return; } } break; case GDScriptTokenizer::TK_CF_IF: { tokenizer->advance(); Node *condition = _parse_and_reduce_expression(p_block, p_static); if (!condition) { if (_recover_from_completion()) { break; } return; } ControlFlowNode *cf_if = alloc_node<ControlFlowNode>(); cf_if->cf_type = ControlFlowNode::CF_IF; cf_if->arguments.push_back(condition); cf_if->body = alloc_node<BlockNode>(); cf_if->body->parent_block = p_block; cf_if->body->if_condition = condition; //helps code completion p_block->sub_blocks.push_back(cf_if->body); if (!_enter_indent_block(cf_if->body)) { _set_error("Expected an indented block after \"if\"."); p_block->end_line = tokenizer->get_token_line(); return; } current_block = cf_if->body; _parse_block(cf_if->body, p_static); current_block = p_block; if (error_set) return; p_block->statements.push_back(cf_if); bool all_have_return = cf_if->body->has_return; bool have_else = false; while (true) { while (tokenizer->get_token() == GDScriptTokenizer::TK_NEWLINE && _parse_newline()) ; if (indent_level.back()->get().indent < current_level.indent) { //not at current indent level p_block->end_line = tokenizer->get_token_line(); return; } if (tokenizer->get_token() == GDScriptTokenizer::TK_CF_ELIF) { if (indent_level.back()->get().indent > current_level.indent) { _set_error("Invalid indentation."); return; } tokenizer->advance(); cf_if->body_else = alloc_node<BlockNode>(); cf_if->body_else->parent_block = p_block; p_block->sub_blocks.push_back(cf_if->body_else); ControlFlowNode *cf_else = alloc_node<ControlFlowNode>(); cf_else->cf_type = ControlFlowNode::CF_IF; //condition Node *condition2 = _parse_and_reduce_expression(p_block, p_static); if (!condition2) { if (_recover_from_completion()) { break; } return; } cf_else->arguments.push_back(condition2); cf_else->cf_type = ControlFlowNode::CF_IF; cf_if->body_else->statements.push_back(cf_else); cf_if = cf_else; cf_if->body = alloc_node<BlockNode>(); cf_if->body->parent_block = p_block; p_block->sub_blocks.push_back(cf_if->body); if (!_enter_indent_block(cf_if->body)) { _set_error("Expected an indented block after \"elif\"."); p_block->end_line = tokenizer->get_token_line(); return; } current_block = cf_else->body; _parse_block(cf_else->body, p_static); current_block = p_block; if (error_set) return; all_have_return = all_have_return && cf_else->body->has_return; } else if (tokenizer->get_token() == GDScriptTokenizer::TK_CF_ELSE) { if (indent_level.back()->get().indent > current_level.indent) { _set_error("Invalid indentation."); return; } tokenizer->advance(); cf_if->body_else = alloc_node<BlockNode>(); cf_if->body_else->parent_block = p_block; p_block->sub_blocks.push_back(cf_if->body_else); if (!_enter_indent_block(cf_if->body_else)) { _set_error("Expected an indented block after \"else\"."); p_block->end_line = tokenizer->get_token_line(); return; } current_block = cf_if->body_else; _parse_block(cf_if->body_else, p_static); current_block = p_block; if (error_set) return; all_have_return = all_have_return && cf_if->body_else->has_return; have_else = true; break; //after else, exit } else break; } cf_if->body->has_return = all_have_return; // If there's no else block, path out of the if might not have a return p_block->has_return = all_have_return && have_else; } break; case GDScriptTokenizer::TK_CF_WHILE: { tokenizer->advance(); Node *condition2 = _parse_and_reduce_expression(p_block, p_static); if (!condition2) { if (_recover_from_completion()) { break; } return; } ControlFlowNode *cf_while = alloc_node<ControlFlowNode>(); cf_while->cf_type = ControlFlowNode::CF_WHILE; cf_while->arguments.push_back(condition2); cf_while->body = alloc_node<BlockNode>(); cf_while->body->parent_block = p_block; p_block->sub_blocks.push_back(cf_while->body); if (!_enter_indent_block(cf_while->body)) { _set_error("Expected an indented block after \"while\"."); p_block->end_line = tokenizer->get_token_line(); return; } current_block = cf_while->body; _parse_block(cf_while->body, p_static); current_block = p_block; if (error_set) return; p_block->has_return = cf_while->body->has_return; p_block->statements.push_back(cf_while); } break; case GDScriptTokenizer::TK_CF_FOR: { tokenizer->advance(); if (!tokenizer->is_token_literal(0, true)) { _set_error("Identifier expected after \"for\"."); } IdentifierNode *id = alloc_node<IdentifierNode>(); id->name = tokenizer->get_token_identifier(); tokenizer->advance(); if (tokenizer->get_token() != GDScriptTokenizer::TK_OP_IN) { _set_error("\"in\" expected after identifier."); return; } tokenizer->advance(); Node *container = _parse_and_reduce_expression(p_block, p_static); if (!container) { if (_recover_from_completion()) { break; } return; } DataType iter_type; if (container->type == Node::TYPE_OPERATOR) { OperatorNode *op = static_cast<OperatorNode *>(container); if (op->op == OperatorNode::OP_CALL && op->arguments[0]->type == Node::TYPE_BUILT_IN_FUNCTION && static_cast<BuiltInFunctionNode *>(op->arguments[0])->function == GDScriptFunctions::GEN_RANGE) { //iterating a range, so see if range() can be optimized without allocating memory, by replacing it by vectors (which can work as iterable too!) Vector<Node *> args; Vector<double> constants; bool constant = false; for (int i = 1; i < op->arguments.size(); i++) { args.push_back(op->arguments[i]); if (constant && op->arguments[i]->type == Node::TYPE_CONSTANT) { ConstantNode *c = static_cast<ConstantNode *>(op->arguments[i]); if (c->value.get_type() == Variant::REAL || c->value.get_type() == Variant::INT) { constants.push_back(c->value); constant = true; } } else { constant = false; } } if (args.size() > 0 && args.size() < 4) { if (constant) { ConstantNode *cn = alloc_node<ConstantNode>(); switch (args.size()) { case 1: cn->value = (int)constants[0]; break; case 2: cn->value = Vector2(constants[0], constants[1]); break; case 3: cn->value = Vector3(constants[0], constants[1], constants[2]); break; } cn->datatype = _type_from_variant(cn->value); container = cn; } else { OperatorNode *on = alloc_node<OperatorNode>(); on->op = OperatorNode::OP_CALL; TypeNode *tn = alloc_node<TypeNode>(); on->arguments.push_back(tn); switch (args.size()) { case 1: tn->vtype = Variant::INT; break; case 2: tn->vtype = Variant::VECTOR2; break; case 3: tn->vtype = Variant::VECTOR3; break; } for (int i = 0; i < args.size(); i++) { on->arguments.push_back(args[i]); } container = on; } } iter_type.has_type = true; iter_type.kind = DataType::BUILTIN; iter_type.builtin_type = Variant::INT; } } ControlFlowNode *cf_for = alloc_node<ControlFlowNode>(); cf_for->cf_type = ControlFlowNode::CF_FOR; cf_for->arguments.push_back(id); cf_for->arguments.push_back(container); cf_for->body = alloc_node<BlockNode>(); cf_for->body->parent_block = p_block; p_block->sub_blocks.push_back(cf_for->body); if (!_enter_indent_block(cf_for->body)) { _set_error("Expected indented block after \"for\"."); p_block->end_line = tokenizer->get_token_line(); return; } current_block = cf_for->body; // this is for checking variable for redefining // inside this _parse_block LocalVarNode *lv = alloc_node<LocalVarNode>(); lv->name = id->name; lv->line = id->line; lv->assignments++; id->declared_block = cf_for->body; lv->set_datatype(iter_type); id->set_datatype(iter_type); cf_for->body->variables.insert(id->name, lv); _parse_block(cf_for->body, p_static); current_block = p_block; if (error_set) return; p_block->has_return = cf_for->body->has_return; p_block->statements.push_back(cf_for); } break; case GDScriptTokenizer::TK_CF_CONTINUE: { _mark_line_as_safe(tokenizer->get_token_line()); tokenizer->advance(); ControlFlowNode *cf_continue = alloc_node<ControlFlowNode>(); cf_continue->cf_type = ControlFlowNode::CF_CONTINUE; p_block->statements.push_back(cf_continue); if (!_end_statement()) { _set_error("Expected end of statement (\"continue\")."); return; } } break; case GDScriptTokenizer::TK_CF_BREAK: { _mark_line_as_safe(tokenizer->get_token_line()); tokenizer->advance(); ControlFlowNode *cf_break = alloc_node<ControlFlowNode>(); cf_break->cf_type = ControlFlowNode::CF_BREAK; p_block->statements.push_back(cf_break); if (!_end_statement()) { _set_error("Expected end of statement (\"break\")."); return; } } break; case GDScriptTokenizer::TK_CF_RETURN: { tokenizer->advance(); ControlFlowNode *cf_return = alloc_node<ControlFlowNode>(); cf_return->cf_type = ControlFlowNode::CF_RETURN; cf_return->line = tokenizer->get_token_line(-1); if (tokenizer->get_token() == GDScriptTokenizer::TK_SEMICOLON || tokenizer->get_token() == GDScriptTokenizer::TK_NEWLINE || tokenizer->get_token() == GDScriptTokenizer::TK_EOF) { //expect end of statement p_block->statements.push_back(cf_return); if (!_end_statement()) { return; } } else { //expect expression Node *retexpr = _parse_and_reduce_expression(p_block, p_static); if (!retexpr) { if (_recover_from_completion()) { break; } return; } cf_return->arguments.push_back(retexpr); p_block->statements.push_back(cf_return); if (!_end_statement()) { _set_error("Expected end of statement after return expression."); return; } } p_block->has_return = true; } break; case GDScriptTokenizer::TK_CF_MATCH: { tokenizer->advance(); MatchNode *match_node = alloc_node<MatchNode>(); Node *val_to_match = _parse_and_reduce_expression(p_block, p_static); if (!val_to_match) { if (_recover_from_completion()) { break; } return; } match_node->val_to_match = val_to_match; if (!_enter_indent_block()) { _set_error("Expected indented pattern matching block after \"match\"."); return; } BlockNode *compiled_branches = alloc_node<BlockNode>(); compiled_branches->parent_block = p_block; compiled_branches->parent_class = p_block->parent_class; p_block->sub_blocks.push_back(compiled_branches); _parse_pattern_block(compiled_branches, match_node->branches, p_static); if (error_set) return; ControlFlowNode *match_cf_node = alloc_node<ControlFlowNode>(); match_cf_node->cf_type = ControlFlowNode::CF_MATCH; match_cf_node->match = match_node; match_cf_node->body = compiled_branches; p_block->has_return = p_block->has_return || compiled_branches->has_return; p_block->statements.push_back(match_cf_node); _end_statement(); } break; case GDScriptTokenizer::TK_PR_ASSERT: { tokenizer->advance(); if (tokenizer->get_token() != GDScriptTokenizer::TK_PARENTHESIS_OPEN) { _set_error("Expected '(' after assert"); return; } tokenizer->advance(); Vector<Node *> args; const bool result = _parse_arguments(p_block, args, p_static); if (!result) { return; } if (args.empty() || args.size() > 2) { _set_error("Wrong number of arguments, expected 1 or 2"); return; } AssertNode *an = alloc_node<AssertNode>(); an->condition = _reduce_expression(args[0], p_static); if (args.size() == 2) { an->message = _reduce_expression(args[1], p_static); } else { ConstantNode *message_node = alloc_node<ConstantNode>(); message_node->value = String(); an->message = message_node; } p_block->statements.push_back(an); if (!_end_statement()) { _set_error("Expected end of statement after \"assert\"."); return; } } break; case GDScriptTokenizer::TK_PR_BREAKPOINT: { tokenizer->advance(); BreakpointNode *bn = alloc_node<BreakpointNode>(); p_block->statements.push_back(bn); if (!_end_statement()) { _set_error("Expected end of statement after \"breakpoint\"."); return; } } break; default: { Node *expression = _parse_and_reduce_expression(p_block, p_static, false, true); if (!expression) { if (_recover_from_completion()) { break; } return; } p_block->statements.push_back(expression); if (!_end_statement()) { // Attempt to guess a better error message if the user "retypes" a variable if (tokenizer->get_token() == GDScriptTokenizer::TK_COLON && tokenizer->get_token(1) == GDScriptTokenizer::TK_OP_ASSIGN) { _set_error("Unexpected ':=', use '=' instead. Expected end of statement after expression."); } else { _set_error(String() + "Expected end of statement after expression, got " + tokenizer->get_token_name(tokenizer->get_token()) + " instead"); } return; } } break; } } } bool GDScriptParser::_parse_newline() { if (tokenizer->get_token(1) != GDScriptTokenizer::TK_EOF && tokenizer->get_token(1) != GDScriptTokenizer::TK_NEWLINE) { IndentLevel current_level = indent_level.back()->get(); int indent = tokenizer->get_token_line_indent(); int tabs = tokenizer->get_token_line_tab_indent(); IndentLevel new_level(indent, tabs); if (new_level.is_mixed(current_level)) { _set_error("Mixed tabs and spaces in indentation."); return false; } if (indent > current_level.indent) { _set_error("Unexpected indentation."); return false; } if (indent < current_level.indent) { while (indent < current_level.indent) { //exit block if (indent_level.size() == 1) { _set_error("Invalid indentation. Bug?"); return false; } indent_level.pop_back(); if (indent_level.back()->get().indent < indent) { _set_error("Unindent does not match any outer indentation level."); return false; } if (indent_level.back()->get().is_mixed(current_level)) { _set_error("Mixed tabs and spaces in indentation."); return false; } current_level = indent_level.back()->get(); } tokenizer->advance(); return false; } } tokenizer->advance(); return true; } void GDScriptParser::_parse_extends(ClassNode *p_class) { if (p_class->extends_used) { _set_error("\"extends\" can only be present once per script."); return; } if (!p_class->constant_expressions.empty() || !p_class->subclasses.empty() || !p_class->functions.empty() || !p_class->variables.empty()) { _set_error("\"extends\" must be used before anything else."); return; } p_class->extends_used = true; tokenizer->advance(); if (tokenizer->get_token() == GDScriptTokenizer::TK_BUILT_IN_TYPE && tokenizer->get_token_type() == Variant::OBJECT) { p_class->extends_class.push_back(Variant::get_type_name(Variant::OBJECT)); tokenizer->advance(); return; } // see if inheritance happens from a file if (tokenizer->get_token() == GDScriptTokenizer::TK_CONSTANT) { Variant constant = tokenizer->get_token_constant(); if (constant.get_type() != Variant::STRING) { _set_error("\"extends\" constant must be a string."); return; } p_class->extends_file = constant; tokenizer->advance(); // Add parent script as a dependency String parent = constant; if (parent.is_rel_path()) { parent = base_path.plus_file(parent).simplify_path(); } dependencies.push_back(parent); if (tokenizer->get_token() != GDScriptTokenizer::TK_PERIOD) { return; } else tokenizer->advance(); } while (true) { switch (tokenizer->get_token()) { case GDScriptTokenizer::TK_IDENTIFIER: { StringName identifier = tokenizer->get_token_identifier(); p_class->extends_class.push_back(identifier); } break; case GDScriptTokenizer::TK_PERIOD: break; default: { _set_error("Invalid \"extends\" syntax, expected string constant (path) and/or identifier (parent class)."); return; } } tokenizer->advance(1); switch (tokenizer->get_token()) { case GDScriptTokenizer::TK_IDENTIFIER: case GDScriptTokenizer::TK_PERIOD: continue; default: return; } } } void GDScriptParser::_parse_class(ClassNode *p_class) { IndentLevel current_level = indent_level.back()->get(); while (true) { GDScriptTokenizer::Token token = tokenizer->get_token(); if (error_set) return; if (current_level.indent > indent_level.back()->get().indent) { p_class->end_line = tokenizer->get_token_line(); return; //go back a level } switch (token) { case GDScriptTokenizer::TK_CURSOR: { tokenizer->advance(); } break; case GDScriptTokenizer::TK_EOF: p_class->end_line = tokenizer->get_token_line(); case GDScriptTokenizer::TK_ERROR: { return; //go back //end of file! } break; case GDScriptTokenizer::TK_NEWLINE: { if (!_parse_newline()) { if (!error_set) { p_class->end_line = tokenizer->get_token_line(); } return; } } break; case GDScriptTokenizer::TK_PR_EXTENDS: { _mark_line_as_safe(tokenizer->get_token_line()); _parse_extends(p_class); if (error_set) return; if (!_end_statement()) { _set_error("Expected end of statement after \"extends\"."); return; } } break; case GDScriptTokenizer::TK_PR_CLASS_NAME: { _mark_line_as_safe(tokenizer->get_token_line()); if (p_class->owner) { _set_error("\"class_name\" is only valid for the main class namespace."); return; } if (self_path.begins_with("res://") && self_path.find("::") != -1) { _set_error("\"class_name\" isn't allowed in built-in scripts."); return; } if (tokenizer->get_token(1) != GDScriptTokenizer::TK_IDENTIFIER) { _set_error("\"class_name\" syntax: \"class_name <UniqueName>\""); return; } if (p_class->classname_used) { _set_error("\"class_name\" can only be present once per script."); return; } p_class->classname_used = true; p_class->name = tokenizer->get_token_identifier(1); if (self_path != String() && ScriptServer::is_global_class(p_class->name) && ScriptServer::get_global_class_path(p_class->name) != self_path) { _set_error("Unique global class \"" + p_class->name + "\" already exists at path: " + ScriptServer::get_global_class_path(p_class->name)); return; } if (ClassDB::class_exists(p_class->name)) { _set_error("The class \"" + p_class->name + "\" shadows a native class."); return; } if (p_class->classname_used && ProjectSettings::get_singleton()->has_setting("autoload/" + p_class->name)) { const String autoload_path = ProjectSettings::get_singleton()->get_setting("autoload/" + p_class->name); if (autoload_path.begins_with("*")) { // It's a singleton, and not just a regular AutoLoad script. _set_error("The class \"" + p_class->name + "\" conflicts with the AutoLoad singleton of the same name, and is therefore redundant. Remove the class_name declaration to fix this error."); } return; } tokenizer->advance(2); if (tokenizer->get_token() == GDScriptTokenizer::TK_COMMA) { tokenizer->advance(); if ((tokenizer->get_token() == GDScriptTokenizer::TK_CONSTANT && tokenizer->get_token_constant().get_type() == Variant::STRING)) { #ifdef TOOLS_ENABLED if (Engine::get_singleton()->is_editor_hint()) { Variant constant = tokenizer->get_token_constant(); String icon_path = constant.operator String(); String abs_icon_path = icon_path.is_rel_path() ? self_path.get_base_dir().plus_file(icon_path).simplify_path() : icon_path; if (!FileAccess::exists(abs_icon_path)) { _set_error("No class icon found at: " + abs_icon_path); return; } p_class->icon_path = icon_path; } #endif tokenizer->advance(); } else { _set_error("The optional parameter after \"class_name\" must be a string constant file path to an icon."); return; } } else if (tokenizer->get_token() == GDScriptTokenizer::TK_CONSTANT) { _set_error("The class icon must be separated by a comma."); return; } } break; case GDScriptTokenizer::TK_PR_TOOL: { if (p_class->tool) { _set_error("The \"tool\" keyword can only be present once per script."); return; } p_class->tool = true; tokenizer->advance(); } break; case GDScriptTokenizer::TK_PR_CLASS: { //class inside class :D StringName name; if (tokenizer->get_token(1) != GDScriptTokenizer::TK_IDENTIFIER) { _set_error("\"class\" syntax: \"class <Name>:\" or \"class <Name> extends <BaseClass>:\""); return; } name = tokenizer->get_token_identifier(1); tokenizer->advance(2); // Check if name is shadowing something else if (ClassDB::class_exists(name) || ClassDB::class_exists("_" + name.operator String())) { _set_error("The class \"" + String(name) + "\" shadows a native class."); return; } if (ScriptServer::is_global_class(name)) { _set_error("Can't override name of the unique global class \"" + name + "\". It already exists at: " + ScriptServer::get_global_class_path(p_class->name)); return; } ClassNode *outer_class = p_class; while (outer_class) { for (int i = 0; i < outer_class->subclasses.size(); i++) { if (outer_class->subclasses[i]->name == name) { _set_error("Another class named \"" + String(name) + "\" already exists in this scope (at line " + itos(outer_class->subclasses[i]->line) + ")."); return; } } if (outer_class->constant_expressions.has(name)) { _set_error("A constant named \"" + String(name) + "\" already exists in the outer class scope (at line" + itos(outer_class->constant_expressions[name].expression->line) + ")."); return; } outer_class = outer_class->owner; } ClassNode *newclass = alloc_node<ClassNode>(); newclass->initializer = alloc_node<BlockNode>(); newclass->initializer->parent_class = newclass; newclass->ready = alloc_node<BlockNode>(); newclass->ready->parent_class = newclass; newclass->name = name; newclass->owner = p_class; p_class->subclasses.push_back(newclass); if (tokenizer->get_token() == GDScriptTokenizer::TK_PR_EXTENDS) { _parse_extends(newclass); if (error_set) return; } if (!_enter_indent_block()) { _set_error("Indented block expected."); return; } current_class = newclass; _parse_class(newclass); current_class = p_class; } break; /* this is for functions.... case GDScriptTokenizer::TK_CF_PASS: { tokenizer->advance(1); } break; */ case GDScriptTokenizer::TK_PR_STATIC: { tokenizer->advance(); if (tokenizer->get_token() != GDScriptTokenizer::TK_PR_FUNCTION) { _set_error("Expected \"func\"."); return; } FALLTHROUGH; } case GDScriptTokenizer::TK_PR_FUNCTION: { bool _static = false; pending_newline = -1; if (tokenizer->get_token(-1) == GDScriptTokenizer::TK_PR_STATIC) { _static = true; } tokenizer->advance(); StringName name; if (_get_completable_identifier(COMPLETION_VIRTUAL_FUNC, name)) { } if (name == StringName()) { _set_error("Expected an identifier after \"func\" (syntax: \"func <identifier>([arguments]):\")."); return; } for (int i = 0; i < p_class->functions.size(); i++) { if (p_class->functions[i]->name == name) { _set_error("The function \"" + String(name) + "\" already exists in this class (at line " + itos(p_class->functions[i]->line) + ")."); } } for (int i = 0; i < p_class->static_functions.size(); i++) { if (p_class->static_functions[i]->name == name) { _set_error("The function \"" + String(name) + "\" already exists in this class (at line " + itos(p_class->static_functions[i]->line) + ")."); } } #ifdef DEBUG_ENABLED if (p_class->constant_expressions.has(name)) { _add_warning(GDScriptWarning::FUNCTION_CONFLICTS_CONSTANT, -1, name); } for (int i = 0; i < p_class->variables.size(); i++) { if (p_class->variables[i].identifier == name) { _add_warning(GDScriptWarning::FUNCTION_CONFLICTS_VARIABLE, -1, name); } } for (int i = 0; i < p_class->subclasses.size(); i++) { if (p_class->subclasses[i]->name == name) { _add_warning(GDScriptWarning::FUNCTION_CONFLICTS_CONSTANT, -1, name); } } #endif // DEBUG_ENABLED if (tokenizer->get_token() != GDScriptTokenizer::TK_PARENTHESIS_OPEN) { _set_error("Expected \"(\" after the identifier (syntax: \"func <identifier>([arguments]):\" )."); return; } tokenizer->advance(); Vector<StringName> arguments; Vector<DataType> argument_types; Vector<Node *> default_values; #ifdef DEBUG_ENABLED Vector<int> arguments_usage; #endif // DEBUG_ENABLED int fnline = tokenizer->get_token_line(); if (tokenizer->get_token() != GDScriptTokenizer::TK_PARENTHESIS_CLOSE) { //has arguments bool defaulting = false; while (true) { if (tokenizer->get_token() == GDScriptTokenizer::TK_NEWLINE) { tokenizer->advance(); continue; } if (tokenizer->get_token() == GDScriptTokenizer::TK_PR_VAR) { tokenizer->advance(); //var before the identifier is allowed } if (!tokenizer->is_token_literal(0, true)) { _set_error("Expected an identifier for an argument."); return; } StringName argname = tokenizer->get_token_identifier(); arguments.push_back(argname); #ifdef DEBUG_ENABLED arguments_usage.push_back(0); #endif // DEBUG_ENABLED tokenizer->advance(); DataType argtype; if (tokenizer->get_token() == GDScriptTokenizer::TK_COLON) { if (tokenizer->get_token(1) == GDScriptTokenizer::TK_OP_ASSIGN) { argtype.infer_type = true; tokenizer->advance(); } else if (!_parse_type(argtype)) { _set_error("Expected a type for an argument."); return; } } argument_types.push_back(argtype); if (defaulting && tokenizer->get_token() != GDScriptTokenizer::TK_OP_ASSIGN) { _set_error("Default parameter expected."); return; } //tokenizer->advance(); if (tokenizer->get_token() == GDScriptTokenizer::TK_OP_ASSIGN) { defaulting = true; tokenizer->advance(1); Node *defval = _parse_and_reduce_expression(p_class, _static); if (!defval || error_set) return; OperatorNode *on = alloc_node<OperatorNode>(); on->op = OperatorNode::OP_ASSIGN; on->line = fnline; IdentifierNode *in = alloc_node<IdentifierNode>(); in->name = argname; in->line = fnline; on->arguments.push_back(in); on->arguments.push_back(defval); /* no .. if (defval->type!=Node::TYPE_CONSTANT) { _set_error("default argument must be constant"); } */ default_values.push_back(on); } while (tokenizer->get_token() == GDScriptTokenizer::TK_NEWLINE) { tokenizer->advance(); } if (tokenizer->get_token() == GDScriptTokenizer::TK_COMMA) { tokenizer->advance(); continue; } else if (tokenizer->get_token() != GDScriptTokenizer::TK_PARENTHESIS_CLOSE) { _set_error("Expected \",\" or \")\"."); return; } break; } } tokenizer->advance(); BlockNode *block = alloc_node<BlockNode>(); block->parent_class = p_class; FunctionNode *function = alloc_node<FunctionNode>(); function->name = name; function->arguments = arguments; function->argument_types = argument_types; function->default_values = default_values; function->_static = _static; function->line = fnline; #ifdef DEBUG_ENABLED function->arguments_usage = arguments_usage; #endif // DEBUG_ENABLED function->rpc_mode = rpc_mode; rpc_mode = MultiplayerAPI::RPC_MODE_DISABLED; if (name == "_init") { if (_static) { _set_error("The constructor cannot be static."); return; } if (p_class->extends_used) { OperatorNode *cparent = alloc_node<OperatorNode>(); cparent->op = OperatorNode::OP_PARENT_CALL; block->statements.push_back(cparent); IdentifierNode *id = alloc_node<IdentifierNode>(); id->name = "_init"; cparent->arguments.push_back(id); if (tokenizer->get_token() == GDScriptTokenizer::TK_PERIOD) { tokenizer->advance(); if (tokenizer->get_token() != GDScriptTokenizer::TK_PARENTHESIS_OPEN) { _set_error("Expected \"(\" for parent constructor arguments."); return; } tokenizer->advance(); if (tokenizer->get_token() != GDScriptTokenizer::TK_PARENTHESIS_CLOSE) { //has arguments parenthesis++; while (true) { current_function = function; Node *arg = _parse_and_reduce_expression(p_class, _static); current_function = NULL; cparent->arguments.push_back(arg); if (tokenizer->get_token() == GDScriptTokenizer::TK_COMMA) { tokenizer->advance(); continue; } else if (tokenizer->get_token() != GDScriptTokenizer::TK_PARENTHESIS_CLOSE) { _set_error("Expected \",\" or \")\"."); return; } break; } parenthesis--; } tokenizer->advance(); } } else { if (tokenizer->get_token() == GDScriptTokenizer::TK_PERIOD) { _set_error("Parent constructor call found for a class without inheritance."); return; } } } DataType return_type; if (tokenizer->get_token() == GDScriptTokenizer::TK_FORWARD_ARROW) { if (!_parse_type(return_type, true)) { _set_error("Expected a return type for the function."); return; } } if (!_enter_indent_block(block)) { _set_error("Indented block expected."); return; } function->return_type = return_type; if (_static) p_class->static_functions.push_back(function); else p_class->functions.push_back(function); current_function = function; function->body = block; current_block = block; _parse_block(block, _static); current_block = NULL; //arguments } break; case GDScriptTokenizer::TK_PR_SIGNAL: { tokenizer->advance(); if (!tokenizer->is_token_literal()) { _set_error("Expected an identifier after \"signal\"."); return; } ClassNode::Signal sig; sig.name = tokenizer->get_token_identifier(); sig.emissions = 0; sig.line = tokenizer->get_token_line(); tokenizer->advance(); if (tokenizer->get_token() == GDScriptTokenizer::TK_PARENTHESIS_OPEN) { tokenizer->advance(); while (true) { if (tokenizer->get_token() == GDScriptTokenizer::TK_NEWLINE) { tokenizer->advance(); continue; } if (tokenizer->get_token() == GDScriptTokenizer::TK_PARENTHESIS_CLOSE) { tokenizer->advance(); break; } if (!tokenizer->is_token_literal(0, true)) { _set_error("Expected an identifier in a \"signal\" argument."); return; } sig.arguments.push_back(tokenizer->get_token_identifier()); tokenizer->advance(); while (tokenizer->get_token() == GDScriptTokenizer::TK_NEWLINE) { tokenizer->advance(); } if (tokenizer->get_token() == GDScriptTokenizer::TK_COMMA) { tokenizer->advance(); } else if (tokenizer->get_token() != GDScriptTokenizer::TK_PARENTHESIS_CLOSE) { _set_error("Expected \",\" or \")\" after a \"signal\" parameter identifier."); return; } } } p_class->_signals.push_back(sig); if (!_end_statement()) { _set_error("Expected end of statement (\"signal\")."); return; } } break; case GDScriptTokenizer::TK_PR_EXPORT: { tokenizer->advance(); if (tokenizer->get_token() == GDScriptTokenizer::TK_PARENTHESIS_OPEN) { #define _ADVANCE_AND_CONSUME_NEWLINES \ do { \ tokenizer->advance(); \ } while (tokenizer->get_token() == GDScriptTokenizer::TK_NEWLINE) _ADVANCE_AND_CONSUME_NEWLINES; parenthesis++; String hint_prefix = ""; bool is_arrayed = false; while (tokenizer->get_token() == GDScriptTokenizer::TK_BUILT_IN_TYPE && tokenizer->get_token_type() == Variant::ARRAY && tokenizer->get_token(1) == GDScriptTokenizer::TK_COMMA) { tokenizer->advance(); // Array tokenizer->advance(); // Comma if (is_arrayed) { hint_prefix += itos(Variant::ARRAY) + ":"; } else { is_arrayed = true; } } if (tokenizer->get_token() == GDScriptTokenizer::TK_BUILT_IN_TYPE) { Variant::Type type = tokenizer->get_token_type(); if (type == Variant::NIL) { _set_error("Can't export null type."); return; } if (type == Variant::OBJECT) { _set_error("Can't export raw object type."); return; } current_export.type = type; current_export.usage |= PROPERTY_USAGE_SCRIPT_VARIABLE; _ADVANCE_AND_CONSUME_NEWLINES; if (tokenizer->get_token() == GDScriptTokenizer::TK_COMMA) { // hint expected next! _ADVANCE_AND_CONSUME_NEWLINES; switch (type) { case Variant::INT: { if (tokenizer->get_token() == GDScriptTokenizer::TK_IDENTIFIER && tokenizer->get_token_identifier() == "FLAGS") { _ADVANCE_AND_CONSUME_NEWLINES; if (tokenizer->get_token() == GDScriptTokenizer::TK_PARENTHESIS_CLOSE) { WARN_DEPRECATED_MSG("Exporting bit flags hint requires string constants."); break; } if (tokenizer->get_token() != GDScriptTokenizer::TK_COMMA) { _set_error("Expected \",\" in the bit flags hint."); return; } current_export.hint = PROPERTY_HINT_FLAGS; _ADVANCE_AND_CONSUME_NEWLINES; bool first = true; while (true) { if (tokenizer->get_token() != GDScriptTokenizer::TK_CONSTANT || tokenizer->get_token_constant().get_type() != Variant::STRING) { current_export = PropertyInfo(); _set_error("Expected a string constant in the named bit flags hint."); return; } String c = tokenizer->get_token_constant(); if (!first) current_export.hint_string += ","; else first = false; current_export.hint_string += c.xml_escape(); _ADVANCE_AND_CONSUME_NEWLINES; if (tokenizer->get_token() == GDScriptTokenizer::TK_PARENTHESIS_CLOSE) break; if (tokenizer->get_token() != GDScriptTokenizer::TK_COMMA) { current_export = PropertyInfo(); _set_error("Expected \")\" or \",\" in the named bit flags hint."); return; } _ADVANCE_AND_CONSUME_NEWLINES; } break; } if (tokenizer->get_token() == GDScriptTokenizer::TK_IDENTIFIER && tokenizer->get_token_identifier() == "LAYERS_2D_RENDER") { _ADVANCE_AND_CONSUME_NEWLINES; if (tokenizer->get_token() != GDScriptTokenizer::TK_PARENTHESIS_CLOSE) { _set_error("Expected \")\" in the layers 2D render hint."); return; } current_export.hint = PROPERTY_HINT_LAYERS_2D_RENDER; break; } if (tokenizer->get_token() == GDScriptTokenizer::TK_IDENTIFIER && tokenizer->get_token_identifier() == "LAYERS_2D_PHYSICS") { _ADVANCE_AND_CONSUME_NEWLINES; if (tokenizer->get_token() != GDScriptTokenizer::TK_PARENTHESIS_CLOSE) { _set_error("Expected \")\" in the layers 2D physics hint."); return; } current_export.hint = PROPERTY_HINT_LAYERS_2D_PHYSICS; break; } if (tokenizer->get_token() == GDScriptTokenizer::TK_IDENTIFIER && tokenizer->get_token_identifier() == "LAYERS_3D_RENDER") { _ADVANCE_AND_CONSUME_NEWLINES; if (tokenizer->get_token() != GDScriptTokenizer::TK_PARENTHESIS_CLOSE) { _set_error("Expected \")\" in the layers 3D render hint."); return; } current_export.hint = PROPERTY_HINT_LAYERS_3D_RENDER; break; } if (tokenizer->get_token() == GDScriptTokenizer::TK_IDENTIFIER && tokenizer->get_token_identifier() == "LAYERS_3D_PHYSICS") { _ADVANCE_AND_CONSUME_NEWLINES; if (tokenizer->get_token() != GDScriptTokenizer::TK_PARENTHESIS_CLOSE) { _set_error("Expected \")\" in the layers 3D physics hint."); return; } current_export.hint = PROPERTY_HINT_LAYERS_3D_PHYSICS; break; } if (tokenizer->get_token() == GDScriptTokenizer::TK_CONSTANT && tokenizer->get_token_constant().get_type() == Variant::STRING) { //enumeration current_export.hint = PROPERTY_HINT_ENUM; bool first = true; while (true) { if (tokenizer->get_token() != GDScriptTokenizer::TK_CONSTANT || tokenizer->get_token_constant().get_type() != Variant::STRING) { current_export = PropertyInfo(); _set_error("Expected a string constant in the enumeration hint."); return; } String c = tokenizer->get_token_constant(); if (!first) current_export.hint_string += ","; else first = false; current_export.hint_string += c.xml_escape(); _ADVANCE_AND_CONSUME_NEWLINES; if (tokenizer->get_token() == GDScriptTokenizer::TK_PARENTHESIS_CLOSE) break; if (tokenizer->get_token() != GDScriptTokenizer::TK_COMMA) { current_export = PropertyInfo(); _set_error("Expected \")\" or \",\" in the enumeration hint."); return; } _ADVANCE_AND_CONSUME_NEWLINES; } break; } FALLTHROUGH; } case Variant::REAL: { if (tokenizer->get_token() == GDScriptTokenizer::TK_IDENTIFIER && tokenizer->get_token_identifier() == "EASE") { current_export.hint = PROPERTY_HINT_EXP_EASING; _ADVANCE_AND_CONSUME_NEWLINES; if (tokenizer->get_token() != GDScriptTokenizer::TK_PARENTHESIS_CLOSE) { _set_error("Expected \")\" in the hint."); return; } break; } // range if (tokenizer->get_token() == GDScriptTokenizer::TK_IDENTIFIER && tokenizer->get_token_identifier() == "EXP") { current_export.hint = PROPERTY_HINT_EXP_RANGE; _ADVANCE_AND_CONSUME_NEWLINES; if (tokenizer->get_token() == GDScriptTokenizer::TK_PARENTHESIS_CLOSE) break; else if (tokenizer->get_token() != GDScriptTokenizer::TK_COMMA) { _set_error("Expected \")\" or \",\" in the exponential range hint."); return; } _ADVANCE_AND_CONSUME_NEWLINES; } else current_export.hint = PROPERTY_HINT_RANGE; float sign = 1.0; if (tokenizer->get_token() == GDScriptTokenizer::TK_OP_SUB) { sign = -1; _ADVANCE_AND_CONSUME_NEWLINES; } if (tokenizer->get_token() != GDScriptTokenizer::TK_CONSTANT || !tokenizer->get_token_constant().is_num()) { current_export = PropertyInfo(); _set_error("Expected a range in the numeric hint."); return; } current_export.hint_string = rtos(sign * double(tokenizer->get_token_constant())); _ADVANCE_AND_CONSUME_NEWLINES; if (tokenizer->get_token() == GDScriptTokenizer::TK_PARENTHESIS_CLOSE) { current_export.hint_string = "0," + current_export.hint_string; break; } if (tokenizer->get_token() != GDScriptTokenizer::TK_COMMA) { current_export = PropertyInfo(); _set_error("Expected \",\" or \")\" in the numeric range hint."); return; } _ADVANCE_AND_CONSUME_NEWLINES; sign = 1.0; if (tokenizer->get_token() == GDScriptTokenizer::TK_OP_SUB) { sign = -1; _ADVANCE_AND_CONSUME_NEWLINES; } if (tokenizer->get_token() != GDScriptTokenizer::TK_CONSTANT || !tokenizer->get_token_constant().is_num()) { current_export = PropertyInfo(); _set_error("Expected a number as upper bound in the numeric range hint."); return; } current_export.hint_string += "," + rtos(sign * double(tokenizer->get_token_constant())); _ADVANCE_AND_CONSUME_NEWLINES; if (tokenizer->get_token() == GDScriptTokenizer::TK_PARENTHESIS_CLOSE) break; if (tokenizer->get_token() != GDScriptTokenizer::TK_COMMA) { current_export = PropertyInfo(); _set_error("Expected \",\" or \")\" in the numeric range hint."); return; } _ADVANCE_AND_CONSUME_NEWLINES; sign = 1.0; if (tokenizer->get_token() == GDScriptTokenizer::TK_OP_SUB) { sign = -1; _ADVANCE_AND_CONSUME_NEWLINES; } if (tokenizer->get_token() != GDScriptTokenizer::TK_CONSTANT || !tokenizer->get_token_constant().is_num()) { current_export = PropertyInfo(); _set_error("Expected a number as step in the numeric range hint."); return; } current_export.hint_string += "," + rtos(sign * double(tokenizer->get_token_constant())); _ADVANCE_AND_CONSUME_NEWLINES; } break; case Variant::STRING: { if (tokenizer->get_token() == GDScriptTokenizer::TK_CONSTANT && tokenizer->get_token_constant().get_type() == Variant::STRING) { //enumeration current_export.hint = PROPERTY_HINT_ENUM; bool first = true; while (true) { if (tokenizer->get_token() != GDScriptTokenizer::TK_CONSTANT || tokenizer->get_token_constant().get_type() != Variant::STRING) { current_export = PropertyInfo(); _set_error("Expected a string constant in the enumeration hint."); return; } String c = tokenizer->get_token_constant(); if (!first) current_export.hint_string += ","; else first = false; current_export.hint_string += c.xml_escape(); _ADVANCE_AND_CONSUME_NEWLINES; if (tokenizer->get_token() == GDScriptTokenizer::TK_PARENTHESIS_CLOSE) break; if (tokenizer->get_token() != GDScriptTokenizer::TK_COMMA) { current_export = PropertyInfo(); _set_error("Expected \")\" or \",\" in the enumeration hint."); return; } _ADVANCE_AND_CONSUME_NEWLINES; } break; } if (tokenizer->get_token() == GDScriptTokenizer::TK_IDENTIFIER && tokenizer->get_token_identifier() == "DIR") { _ADVANCE_AND_CONSUME_NEWLINES; if (tokenizer->get_token() == GDScriptTokenizer::TK_PARENTHESIS_CLOSE) current_export.hint = PROPERTY_HINT_DIR; else if (tokenizer->get_token() == GDScriptTokenizer::TK_COMMA) { _ADVANCE_AND_CONSUME_NEWLINES; if (tokenizer->get_token() != GDScriptTokenizer::TK_IDENTIFIER || !(tokenizer->get_token_identifier() == "GLOBAL")) { _set_error("Expected \"GLOBAL\" after comma in the directory hint."); return; } if (!p_class->tool) { _set_error("Global filesystem hints may only be used in tool scripts."); return; } current_export.hint = PROPERTY_HINT_GLOBAL_DIR; _ADVANCE_AND_CONSUME_NEWLINES; if (tokenizer->get_token() != GDScriptTokenizer::TK_PARENTHESIS_CLOSE) { _set_error("Expected \")\" in the hint."); return; } } else { _set_error("Expected \")\" or \",\" in the hint."); return; } break; } if (tokenizer->get_token() == GDScriptTokenizer::TK_IDENTIFIER && tokenizer->get_token_identifier() == "FILE") { current_export.hint = PROPERTY_HINT_FILE; _ADVANCE_AND_CONSUME_NEWLINES; if (tokenizer->get_token() == GDScriptTokenizer::TK_COMMA) { _ADVANCE_AND_CONSUME_NEWLINES; if (tokenizer->get_token() == GDScriptTokenizer::TK_IDENTIFIER && tokenizer->get_token_identifier() == "GLOBAL") { if (!p_class->tool) { _set_error("Global filesystem hints may only be used in tool scripts."); return; } current_export.hint = PROPERTY_HINT_GLOBAL_FILE; _ADVANCE_AND_CONSUME_NEWLINES; if (tokenizer->get_token() == GDScriptTokenizer::TK_PARENTHESIS_CLOSE) break; else if (tokenizer->get_token() == GDScriptTokenizer::TK_COMMA) _ADVANCE_AND_CONSUME_NEWLINES; else { _set_error("Expected \")\" or \",\" in the hint."); return; } } if (tokenizer->get_token() != GDScriptTokenizer::TK_CONSTANT || tokenizer->get_token_constant().get_type() != Variant::STRING) { if (current_export.hint == PROPERTY_HINT_GLOBAL_FILE) _set_error("Expected string constant with filter."); else _set_error("Expected \"GLOBAL\" or string constant with filter."); return; } current_export.hint_string = tokenizer->get_token_constant(); _ADVANCE_AND_CONSUME_NEWLINES; } if (tokenizer->get_token() != GDScriptTokenizer::TK_PARENTHESIS_CLOSE) { _set_error("Expected \")\" in the hint."); return; } break; } if (tokenizer->get_token() == GDScriptTokenizer::TK_IDENTIFIER && tokenizer->get_token_identifier() == "MULTILINE") { current_export.hint = PROPERTY_HINT_MULTILINE_TEXT; _ADVANCE_AND_CONSUME_NEWLINES; if (tokenizer->get_token() != GDScriptTokenizer::TK_PARENTHESIS_CLOSE) { _set_error("Expected \")\" in the hint."); return; } break; } } break; case Variant::COLOR: { if (tokenizer->get_token() != GDScriptTokenizer::TK_IDENTIFIER) { current_export = PropertyInfo(); _set_error("Color type hint expects RGB or RGBA as hints."); return; } String identifier = tokenizer->get_token_identifier(); if (identifier == "RGB") { current_export.hint = PROPERTY_HINT_COLOR_NO_ALPHA; } else if (identifier == "RGBA") { //none } else { current_export = PropertyInfo(); _set_error("Color type hint expects RGB or RGBA as hints."); return; } _ADVANCE_AND_CONSUME_NEWLINES; } break; default: { current_export = PropertyInfo(); _set_error("Type \"" + Variant::get_type_name(type) + "\" can't take hints."); return; } break; } } } else { parenthesis++; Node *subexpr = _parse_and_reduce_expression(p_class, true, true); if (!subexpr) { if (_recover_from_completion()) { break; } return; } parenthesis--; if (subexpr->type != Node::TYPE_CONSTANT) { current_export = PropertyInfo(); _set_error("Expected a constant expression."); } Variant constant = static_cast<ConstantNode *>(subexpr)->value; if (constant.get_type() == Variant::OBJECT) { GDScriptNativeClass *native_class = Object::cast_to<GDScriptNativeClass>(constant); if (native_class && ClassDB::is_parent_class(native_class->get_name(), "Resource")) { current_export.type = Variant::OBJECT; current_export.hint = PROPERTY_HINT_RESOURCE_TYPE; current_export.usage |= PROPERTY_USAGE_SCRIPT_VARIABLE; current_export.hint_string = native_class->get_name(); current_export.class_name = native_class->get_name(); } else { current_export = PropertyInfo(); _set_error("The export hint isn't a resource type."); } } else if (constant.get_type() == Variant::DICTIONARY) { // Enumeration bool is_flags = false; if (tokenizer->get_token() == GDScriptTokenizer::TK_COMMA) { _ADVANCE_AND_CONSUME_NEWLINES; if (tokenizer->get_token() == GDScriptTokenizer::TK_IDENTIFIER && tokenizer->get_token_identifier() == "FLAGS") { is_flags = true; _ADVANCE_AND_CONSUME_NEWLINES; } else { current_export = PropertyInfo(); _set_error("Expected \"FLAGS\" after comma."); } } current_export.type = Variant::INT; current_export.hint = is_flags ? PROPERTY_HINT_FLAGS : PROPERTY_HINT_ENUM; current_export.usage |= PROPERTY_USAGE_SCRIPT_VARIABLE; Dictionary enum_values = constant; List<Variant> keys; enum_values.get_key_list(&keys); bool first = true; for (List<Variant>::Element *E = keys.front(); E; E = E->next()) { if (enum_values[E->get()].get_type() == Variant::INT) { if (!first) current_export.hint_string += ","; else first = false; current_export.hint_string += E->get().operator String().camelcase_to_underscore(true).capitalize().xml_escape(); if (!is_flags) { current_export.hint_string += ":"; current_export.hint_string += enum_values[E->get()].operator String().xml_escape(); } } } } else { current_export = PropertyInfo(); _set_error("Expected type for export."); return; } } if (tokenizer->get_token() != GDScriptTokenizer::TK_PARENTHESIS_CLOSE) { current_export = PropertyInfo(); _set_error("Expected \")\" or \",\" after the export hint."); return; } tokenizer->advance(); parenthesis--; if (is_arrayed) { hint_prefix += itos(current_export.type); if (current_export.hint) { hint_prefix += "/" + itos(current_export.hint); } current_export.hint_string = hint_prefix + ":" + current_export.hint_string; current_export.hint = PROPERTY_HINT_TYPE_STRING; current_export.type = Variant::ARRAY; } #undef _ADVANCE_AND_CONSUME_NEWLINES } if (tokenizer->get_token() != GDScriptTokenizer::TK_PR_VAR && tokenizer->get_token() != GDScriptTokenizer::TK_PR_ONREADY && tokenizer->get_token() != GDScriptTokenizer::TK_PR_REMOTE && tokenizer->get_token() != GDScriptTokenizer::TK_PR_MASTER && tokenizer->get_token() != GDScriptTokenizer::TK_PR_PUPPET && tokenizer->get_token() != GDScriptTokenizer::TK_PR_SYNC && tokenizer->get_token() != GDScriptTokenizer::TK_PR_REMOTESYNC && tokenizer->get_token() != GDScriptTokenizer::TK_PR_MASTERSYNC && tokenizer->get_token() != GDScriptTokenizer::TK_PR_PUPPETSYNC && tokenizer->get_token() != GDScriptTokenizer::TK_PR_SLAVE) { current_export = PropertyInfo(); _set_error("Expected \"var\", \"onready\", \"remote\", \"master\", \"puppet\", \"sync\", \"remotesync\", \"mastersync\", \"puppetsync\"."); return; } continue; } break; case GDScriptTokenizer::TK_PR_ONREADY: { //may be fallthrough from export, ignore if so tokenizer->advance(); if (tokenizer->get_token() != GDScriptTokenizer::TK_PR_VAR) { _set_error("Expected \"var\"."); return; } continue; } break; case GDScriptTokenizer::TK_PR_REMOTE: { //may be fallthrough from export, ignore if so tokenizer->advance(); if (current_export.type) { if (tokenizer->get_token() != GDScriptTokenizer::TK_PR_VAR) { _set_error("Expected \"var\"."); return; } } else { if (tokenizer->get_token() != GDScriptTokenizer::TK_PR_VAR && tokenizer->get_token() != GDScriptTokenizer::TK_PR_FUNCTION) { _set_error("Expected \"var\" or \"func\"."); return; } } rpc_mode = MultiplayerAPI::RPC_MODE_REMOTE; continue; } break; case GDScriptTokenizer::TK_PR_MASTER: { //may be fallthrough from export, ignore if so tokenizer->advance(); if (current_export.type) { if (tokenizer->get_token() != GDScriptTokenizer::TK_PR_VAR) { _set_error("Expected \"var\"."); return; } } else { if (tokenizer->get_token() != GDScriptTokenizer::TK_PR_VAR && tokenizer->get_token() != GDScriptTokenizer::TK_PR_FUNCTION) { _set_error("Expected \"var\" or \"func\"."); return; } } rpc_mode = MultiplayerAPI::RPC_MODE_MASTER; continue; } break; case GDScriptTokenizer::TK_PR_SLAVE: #ifdef DEBUG_ENABLED _add_warning(GDScriptWarning::DEPRECATED_KEYWORD, tokenizer->get_token_line(), "slave", "puppet"); #endif FALLTHROUGH; case GDScriptTokenizer::TK_PR_PUPPET: { //may be fallthrough from export, ignore if so tokenizer->advance(); if (current_export.type) { if (tokenizer->get_token() != GDScriptTokenizer::TK_PR_VAR) { _set_error("Expected \"var\"."); return; } } else { if (tokenizer->get_token() != GDScriptTokenizer::TK_PR_VAR && tokenizer->get_token() != GDScriptTokenizer::TK_PR_FUNCTION) { _set_error("Expected \"var\" or \"func\"."); return; } } rpc_mode = MultiplayerAPI::RPC_MODE_PUPPET; continue; } break; case GDScriptTokenizer::TK_PR_REMOTESYNC: case GDScriptTokenizer::TK_PR_SYNC: { //may be fallthrough from export, ignore if so tokenizer->advance(); if (tokenizer->get_token() != GDScriptTokenizer::TK_PR_VAR && tokenizer->get_token() != GDScriptTokenizer::TK_PR_FUNCTION) { if (current_export.type) _set_error("Expected \"var\"."); else _set_error("Expected \"var\" or \"func\"."); return; } rpc_mode = MultiplayerAPI::RPC_MODE_REMOTESYNC; continue; } break; case GDScriptTokenizer::TK_PR_MASTERSYNC: { //may be fallthrough from export, ignore if so tokenizer->advance(); if (tokenizer->get_token() != GDScriptTokenizer::TK_PR_VAR && tokenizer->get_token() != GDScriptTokenizer::TK_PR_FUNCTION) { if (current_export.type) _set_error("Expected \"var\"."); else _set_error("Expected \"var\" or \"func\"."); return; } rpc_mode = MultiplayerAPI::RPC_MODE_MASTERSYNC; continue; } break; case GDScriptTokenizer::TK_PR_PUPPETSYNC: { //may be fallthrough from export, ignore if so tokenizer->advance(); if (tokenizer->get_token() != GDScriptTokenizer::TK_PR_VAR && tokenizer->get_token() != GDScriptTokenizer::TK_PR_FUNCTION) { if (current_export.type) _set_error("Expected \"var\"."); else _set_error("Expected \"var\" or \"func\"."); return; } rpc_mode = MultiplayerAPI::RPC_MODE_PUPPETSYNC; continue; } break; case GDScriptTokenizer::TK_PR_VAR: { // variable declaration and (eventual) initialization ClassNode::Member member; bool autoexport = tokenizer->get_token(-1) == GDScriptTokenizer::TK_PR_EXPORT; if (current_export.type != Variant::NIL) { member._export = current_export; current_export = PropertyInfo(); } bool onready = tokenizer->get_token(-1) == GDScriptTokenizer::TK_PR_ONREADY; tokenizer->advance(); if (!tokenizer->is_token_literal(0, true)) { _set_error("Expected an identifier for the member variable name."); return; } member.identifier = tokenizer->get_token_literal(); member.expression = NULL; member._export.name = member.identifier; member.line = tokenizer->get_token_line(); member.usages = 0; member.rpc_mode = rpc_mode; if (current_class->constant_expressions.has(member.identifier)) { _set_error("A constant named \"" + String(member.identifier) + "\" already exists in this class (at line: " + itos(current_class->constant_expressions[member.identifier].expression->line) + ")."); return; } for (int i = 0; i < current_class->variables.size(); i++) { if (current_class->variables[i].identifier == member.identifier) { _set_error("Variable \"" + String(member.identifier) + "\" already exists in this class (at line: " + itos(current_class->variables[i].line) + ")."); return; } } for (int i = 0; i < current_class->subclasses.size(); i++) { if (current_class->subclasses[i]->name == member.identifier) { _set_error("A class named \"" + String(member.identifier) + "\" already exists in this class (at line " + itos(current_class->subclasses[i]->line) + ")."); return; } } #ifdef DEBUG_ENABLED for (int i = 0; i < current_class->functions.size(); i++) { if (current_class->functions[i]->name == member.identifier) { _add_warning(GDScriptWarning::VARIABLE_CONFLICTS_FUNCTION, member.line, member.identifier); break; } } for (int i = 0; i < current_class->static_functions.size(); i++) { if (current_class->static_functions[i]->name == member.identifier) { _add_warning(GDScriptWarning::VARIABLE_CONFLICTS_FUNCTION, member.line, member.identifier); break; } } #endif // DEBUG_ENABLED tokenizer->advance(); rpc_mode = MultiplayerAPI::RPC_MODE_DISABLED; if (tokenizer->get_token() == GDScriptTokenizer::TK_COLON) { if (tokenizer->get_token(1) == GDScriptTokenizer::TK_OP_ASSIGN) { member.data_type = DataType(); #ifdef DEBUG_ENABLED member.data_type.infer_type = true; #endif tokenizer->advance(); } else if (!_parse_type(member.data_type)) { _set_error("Expected a type for the class variable."); return; } } if (autoexport && member.data_type.has_type) { if (member.data_type.kind == DataType::BUILTIN) { member._export.type = member.data_type.builtin_type; } else if (member.data_type.kind == DataType::NATIVE) { if (ClassDB::is_parent_class(member.data_type.native_type, "Resource")) { member._export.type = Variant::OBJECT; member._export.hint = PROPERTY_HINT_RESOURCE_TYPE; member._export.usage |= PROPERTY_USAGE_SCRIPT_VARIABLE; member._export.hint_string = member.data_type.native_type; member._export.class_name = member.data_type.native_type; } else { _set_error("Invalid export type. Only built-in and native resource types can be exported.", member.line); return; } } else { _set_error("Invalid export type. Only built-in and native resource types can be exported.", member.line); return; } } #ifdef TOOLS_ENABLED Variant::CallError ce; member.default_value = Variant::construct(member._export.type, NULL, 0, ce); #endif if (tokenizer->get_token() == GDScriptTokenizer::TK_OP_ASSIGN) { #ifdef DEBUG_ENABLED int line = tokenizer->get_token_line(); #endif tokenizer->advance(); Node *subexpr = _parse_and_reduce_expression(p_class, false, autoexport || member._export.type != Variant::NIL); if (!subexpr) { if (_recover_from_completion()) { break; } return; } //discourage common error if (!onready && subexpr->type == Node::TYPE_OPERATOR) { OperatorNode *op = static_cast<OperatorNode *>(subexpr); if (op->op == OperatorNode::OP_CALL && op->arguments[0]->type == Node::TYPE_SELF && op->arguments[1]->type == Node::TYPE_IDENTIFIER) { IdentifierNode *id = static_cast<IdentifierNode *>(op->arguments[1]); if (id->name == "get_node") { _set_error("Use \"onready var " + String(member.identifier) + " = get_node(...)\" instead."); return; } } } member.expression = subexpr; if (autoexport && !member.data_type.has_type) { if (subexpr->type != Node::TYPE_CONSTANT) { _set_error("Type-less export needs a constant expression assigned to infer type."); return; } ConstantNode *cn = static_cast<ConstantNode *>(subexpr); if (cn->value.get_type() == Variant::NIL) { _set_error("Can't accept a null constant expression for inferring export type."); return; } member._export.type = cn->value.get_type(); member._export.usage |= PROPERTY_USAGE_SCRIPT_VARIABLE; if (cn->value.get_type() == Variant::OBJECT) { Object *obj = cn->value; Resource *res = Object::cast_to<Resource>(obj); if (res == NULL) { _set_error("The exported constant isn't a type or resource."); return; } member._export.hint = PROPERTY_HINT_RESOURCE_TYPE; member._export.hint_string = res->get_class(); } } #ifdef TOOLS_ENABLED if (subexpr->type == Node::TYPE_CONSTANT && (member._export.type != Variant::NIL || member.data_type.has_type)) { ConstantNode *cn = static_cast<ConstantNode *>(subexpr); if (cn->value.get_type() != Variant::NIL) { if (member._export.type != Variant::NIL && cn->value.get_type() != member._export.type) { if (Variant::can_convert(cn->value.get_type(), member._export.type)) { Variant::CallError err; const Variant *args = &cn->value; cn->value = Variant::construct(member._export.type, &args, 1, err); } else { _set_error("Can't convert the provided value to the export type."); return; } } member.default_value = cn->value; } } #endif IdentifierNode *id = alloc_node<IdentifierNode>(); id->name = member.identifier; OperatorNode *op = alloc_node<OperatorNode>(); op->op = OperatorNode::OP_INIT_ASSIGN; op->arguments.push_back(id); op->arguments.push_back(subexpr); #ifdef DEBUG_ENABLED NewLineNode *nl2 = alloc_node<NewLineNode>(); nl2->line = line; if (onready) p_class->ready->statements.push_back(nl2); else p_class->initializer->statements.push_back(nl2); #endif if (onready) p_class->ready->statements.push_back(op); else p_class->initializer->statements.push_back(op); member.initial_assignment = op; } else { if (autoexport && !member.data_type.has_type) { _set_error("Type-less export needs a constant expression assigned to infer type."); return; } Node *expr; if (member.data_type.has_type) { expr = _get_default_value_for_type(member.data_type); } else { DataType exported_type; exported_type.has_type = true; exported_type.kind = DataType::BUILTIN; exported_type.builtin_type = member._export.type; expr = _get_default_value_for_type(exported_type); } IdentifierNode *id = alloc_node<IdentifierNode>(); id->name = member.identifier; OperatorNode *op = alloc_node<OperatorNode>(); op->op = OperatorNode::OP_INIT_ASSIGN; op->arguments.push_back(id); op->arguments.push_back(expr); p_class->initializer->statements.push_back(op); member.initial_assignment = op; } if (tokenizer->get_token() == GDScriptTokenizer::TK_PR_SETGET) { tokenizer->advance(); if (tokenizer->get_token() != GDScriptTokenizer::TK_COMMA) { //just comma means using only getter if (!tokenizer->is_token_literal()) { _set_error("Expected an identifier for the setter function after \"setget\"."); } member.setter = tokenizer->get_token_literal(); tokenizer->advance(); } if (tokenizer->get_token() == GDScriptTokenizer::TK_COMMA) { //there is a getter tokenizer->advance(); if (!tokenizer->is_token_literal()) { _set_error("Expected an identifier for the getter function after \",\"."); } member.getter = tokenizer->get_token_literal(); tokenizer->advance(); } } p_class->variables.push_back(member); if (!_end_statement()) { _set_error("Expected end of statement (\"continue\")."); return; } } break; case GDScriptTokenizer::TK_PR_CONST: { // constant declaration and initialization ClassNode::Constant constant; tokenizer->advance(); if (!tokenizer->is_token_literal(0, true)) { _set_error("Expected an identifier for the constant."); return; } StringName const_id = tokenizer->get_token_literal(); int line = tokenizer->get_token_line(); if (current_class->constant_expressions.has(const_id)) { _set_error("Constant \"" + String(const_id) + "\" already exists in this class (at line " + itos(current_class->constant_expressions[const_id].expression->line) + ")."); return; } for (int i = 0; i < current_class->variables.size(); i++) { if (current_class->variables[i].identifier == const_id) { _set_error("A variable named \"" + String(const_id) + "\" already exists in this class (at line " + itos(current_class->variables[i].line) + ")."); return; } } for (int i = 0; i < current_class->subclasses.size(); i++) { if (current_class->subclasses[i]->name == const_id) { _set_error("A class named \"" + String(const_id) + "\" already exists in this class (at line " + itos(current_class->subclasses[i]->line) + ")."); return; } } tokenizer->advance(); if (tokenizer->get_token() == GDScriptTokenizer::TK_COLON) { if (tokenizer->get_token(1) == GDScriptTokenizer::TK_OP_ASSIGN) { constant.type = DataType(); #ifdef DEBUG_ENABLED constant.type.infer_type = true; #endif tokenizer->advance(); } else if (!_parse_type(constant.type)) { _set_error("Expected a type for the class constant."); return; } } if (tokenizer->get_token() != GDScriptTokenizer::TK_OP_ASSIGN) { _set_error("Constants must be assigned immediately."); return; } tokenizer->advance(); Node *subexpr = _parse_and_reduce_expression(p_class, true, true); if (!subexpr) { if (_recover_from_completion()) { break; } return; } if (subexpr->type != Node::TYPE_CONSTANT) { _set_error("Expected a constant expression.", line); return; } subexpr->line = line; constant.expression = subexpr; p_class->constant_expressions.insert(const_id, constant); if (!_end_statement()) { _set_error("Expected end of statement (constant).", line); return; } } break; case GDScriptTokenizer::TK_PR_ENUM: { //multiple constant declarations.. int last_assign = -1; // Incremented by 1 right before the assignment. String enum_name; Dictionary enum_dict; tokenizer->advance(); if (tokenizer->is_token_literal(0, true)) { enum_name = tokenizer->get_token_literal(); if (current_class->constant_expressions.has(enum_name)) { _set_error("A constant named \"" + String(enum_name) + "\" already exists in this class (at line " + itos(current_class->constant_expressions[enum_name].expression->line) + ")."); return; } for (int i = 0; i < current_class->variables.size(); i++) { if (current_class->variables[i].identifier == enum_name) { _set_error("A variable named \"" + String(enum_name) + "\" already exists in this class (at line " + itos(current_class->variables[i].line) + ")."); return; } } for (int i = 0; i < current_class->subclasses.size(); i++) { if (current_class->subclasses[i]->name == enum_name) { _set_error("A class named \"" + String(enum_name) + "\" already exists in this class (at line " + itos(current_class->subclasses[i]->line) + ")."); return; } } tokenizer->advance(); } if (tokenizer->get_token() != GDScriptTokenizer::TK_CURLY_BRACKET_OPEN) { _set_error("Expected \"{\" in the enum declaration."); return; } tokenizer->advance(); while (true) { if (tokenizer->get_token() == GDScriptTokenizer::TK_NEWLINE) { tokenizer->advance(); // Ignore newlines } else if (tokenizer->get_token() == GDScriptTokenizer::TK_CURLY_BRACKET_CLOSE) { tokenizer->advance(); break; // End of enum } else if (!tokenizer->is_token_literal(0, true)) { if (tokenizer->get_token() == GDScriptTokenizer::TK_EOF) { _set_error("Unexpected end of file."); } else { _set_error(String("Unexpected ") + GDScriptTokenizer::get_token_name(tokenizer->get_token()) + ", expected an identifier."); } return; } else { // tokenizer->is_token_literal(0, true) StringName const_id = tokenizer->get_token_literal(); tokenizer->advance(); ConstantNode *enum_value_expr; if (tokenizer->get_token() == GDScriptTokenizer::TK_OP_ASSIGN) { tokenizer->advance(); Node *subexpr = _parse_and_reduce_expression(p_class, true, true); if (!subexpr) { if (_recover_from_completion()) { break; } return; } if (subexpr->type != Node::TYPE_CONSTANT) { _set_error("Expected a constant expression."); return; } enum_value_expr = static_cast<ConstantNode *>(subexpr); if (enum_value_expr->value.get_type() != Variant::INT) { _set_error("Expected an integer value for \"enum\"."); return; } last_assign = enum_value_expr->value; } else { last_assign = last_assign + 1; enum_value_expr = alloc_node<ConstantNode>(); enum_value_expr->value = last_assign; enum_value_expr->datatype = _type_from_variant(enum_value_expr->value); } if (tokenizer->get_token() == GDScriptTokenizer::TK_COMMA) { tokenizer->advance(); } else if (tokenizer->is_token_literal(0, true)) { _set_error("Unexpected identifier."); return; } if (enum_name != "") { enum_dict[const_id] = enum_value_expr->value; } else { if (current_class->constant_expressions.has(const_id)) { _set_error("A constant named \"" + String(const_id) + "\" already exists in this class (at line " + itos(current_class->constant_expressions[const_id].expression->line) + ")."); return; } for (int i = 0; i < current_class->variables.size(); i++) { if (current_class->variables[i].identifier == const_id) { _set_error("A variable named \"" + String(const_id) + "\" already exists in this class (at line " + itos(current_class->variables[i].line) + ")."); return; } } for (int i = 0; i < current_class->subclasses.size(); i++) { if (current_class->subclasses[i]->name == const_id) { _set_error("A class named \"" + String(const_id) + "\" already exists in this class (at line " + itos(current_class->subclasses[i]->line) + ")."); return; } } ClassNode::Constant constant; constant.type.has_type = true; constant.type.kind = DataType::BUILTIN; constant.type.builtin_type = Variant::INT; constant.expression = enum_value_expr; p_class->constant_expressions.insert(const_id, constant); } } } if (enum_name != "") { ClassNode::Constant enum_constant; ConstantNode *cn = alloc_node<ConstantNode>(); cn->value = enum_dict; cn->datatype = _type_from_variant(cn->value); enum_constant.expression = cn; enum_constant.type = cn->datatype; p_class->constant_expressions.insert(enum_name, enum_constant); } if (!_end_statement()) { _set_error("Expected end of statement (\"enum\")."); return; } } break; case GDScriptTokenizer::TK_CONSTANT: { if (tokenizer->get_token_constant().get_type() == Variant::STRING) { tokenizer->advance(); // Ignore } else { _set_error(String() + "Unexpected constant of type: " + Variant::get_type_name(tokenizer->get_token_constant().get_type())); return; } } break; default: { _set_error(String() + "Unexpected token: " + tokenizer->get_token_name(tokenizer->get_token()) + ":" + tokenizer->get_token_identifier()); return; } break; } } } void GDScriptParser::_determine_inheritance(ClassNode *p_class, bool p_recursive) { if (p_class->base_type.has_type) { // Already determined } else if (p_class->extends_used) { //do inheritance String path = p_class->extends_file; Ref<GDScript> script; StringName native; ClassNode *base_class = NULL; if (path != "") { //path (and optionally subclasses) if (path.is_rel_path()) { String base = base_path; if (base == "" || base.is_rel_path()) { _set_error("Couldn't resolve relative path for the parent class: " + path, p_class->line); return; } path = base.plus_file(path).simplify_path(); } script = ResourceLoader::load(path); if (script.is_null()) { _set_error("Couldn't load the base class: " + path, p_class->line); return; } if (!script->is_valid()) { _set_error("Script isn't fully loaded (cyclic preload?): " + path, p_class->line); return; } if (p_class->extends_class.size()) { for (int i = 0; i < p_class->extends_class.size(); i++) { String sub = p_class->extends_class[i]; if (script->get_subclasses().has(sub)) { Ref<Script> subclass = script->get_subclasses()[sub]; //avoid reference from disappearing script = subclass; } else { _set_error("Couldn't find the subclass: " + sub, p_class->line); return; } } } } else { if (p_class->extends_class.size() == 0) { _set_error("Parser bug: undecidable inheritance.", p_class->line); ERR_FAIL(); } //look around for the subclasses int extend_iter = 1; String base = p_class->extends_class[0]; ClassNode *p = p_class->owner; Ref<GDScript> base_script; if (ScriptServer::is_global_class(base)) { base_script = ResourceLoader::load(ScriptServer::get_global_class_path(base)); if (!base_script.is_valid()) { _set_error("The class \"" + base + "\" couldn't be fully loaded (script error or cyclic dependency).", p_class->line); return; } p = NULL; } else { List<PropertyInfo> props; ProjectSettings::get_singleton()->get_property_list(&props); for (List<PropertyInfo>::Element *E = props.front(); E; E = E->next()) { String s = E->get().name; if (!s.begins_with("autoload/")) { continue; } String name = s.get_slice("/", 1); if (name == base) { String singleton_path = ProjectSettings::get_singleton()->get(s); if (singleton_path.begins_with("*")) { singleton_path = singleton_path.right(1); } if (!singleton_path.begins_with("res://")) { singleton_path = "res://" + singleton_path; } base_script = ResourceLoader::load(singleton_path); if (!base_script.is_valid()) { _set_error("Class '" + base + "' could not be fully loaded (script error or cyclic inheritance).", p_class->line); return; } p = NULL; } } } while (p) { bool found = false; for (int i = 0; i < p->subclasses.size(); i++) { if (p->subclasses[i]->name == base) { ClassNode *test = p->subclasses[i]; while (test) { if (test == p_class) { _set_error("Cyclic inheritance.", test->line); return; } if (test->base_type.kind == DataType::CLASS) { test = test->base_type.class_type; } else { break; } } found = true; if (extend_iter < p_class->extends_class.size()) { // Keep looking at current classes if possible base = p_class->extends_class[extend_iter++]; p = p->subclasses[i]; } else { base_class = p->subclasses[i]; } break; } } if (base_class) break; if (found) continue; if (p->constant_expressions.has(base)) { if (p->constant_expressions[base].expression->type != Node::TYPE_CONSTANT) { _set_error("Couldn't resolve the constant \"" + base + "\".", p_class->line); return; } const ConstantNode *cn = static_cast<const ConstantNode *>(p->constant_expressions[base].expression); base_script = cn->value; if (base_script.is_null()) { _set_error("Constant isn't a class: " + base, p_class->line); return; } break; } p = p->owner; } if (base_script.is_valid()) { String ident = base; Ref<GDScript> find_subclass = base_script; for (int i = extend_iter; i < p_class->extends_class.size(); i++) { String subclass = p_class->extends_class[i]; ident += ("." + subclass); if (find_subclass->get_subclasses().has(subclass)) { find_subclass = find_subclass->get_subclasses()[subclass]; } else if (find_subclass->get_constants().has(subclass)) { Ref<GDScript> new_base_class = find_subclass->get_constants()[subclass]; if (new_base_class.is_null()) { _set_error("Constant isn't a class: " + ident, p_class->line); return; } find_subclass = new_base_class; } else { _set_error("Couldn't find the subclass: " + ident, p_class->line); return; } } script = find_subclass; } else if (!base_class) { if (p_class->extends_class.size() > 1) { _set_error("Invalid inheritance (unknown class + subclasses).", p_class->line); return; } //if not found, try engine classes if (!GDScriptLanguage::get_singleton()->get_global_map().has(base)) { _set_error("Unknown class: \"" + base + "\"", p_class->line); return; } native = base; } } if (base_class) { p_class->base_type.has_type = true; p_class->base_type.kind = DataType::CLASS; p_class->base_type.class_type = base_class; } else if (script.is_valid()) { p_class->base_type.has_type = true; p_class->base_type.kind = DataType::GDSCRIPT; p_class->base_type.script_type = script; p_class->base_type.native_type = script->get_instance_base_type(); } else if (native != StringName()) { p_class->base_type.has_type = true; p_class->base_type.kind = DataType::NATIVE; p_class->base_type.native_type = native; } else { _set_error("Couldn't determine inheritance.", p_class->line); return; } } else { // without extends, implicitly extend Reference p_class->base_type.has_type = true; p_class->base_type.kind = DataType::NATIVE; p_class->base_type.native_type = "Reference"; } if (p_recursive) { // Recursively determine subclasses for (int i = 0; i < p_class->subclasses.size(); i++) { _determine_inheritance(p_class->subclasses[i], p_recursive); } } } String GDScriptParser::DataType::to_string() const { if (!has_type) return "var"; switch (kind) { case BUILTIN: { if (builtin_type == Variant::NIL) return "null"; return Variant::get_type_name(builtin_type); } break; case NATIVE: { if (is_meta_type) { return "GDScriptNativeClass"; } return native_type.operator String(); } break; case GDSCRIPT: { Ref<GDScript> gds = script_type; const String &gds_class = gds->get_script_class_name(); if (!gds_class.empty()) { return gds_class; } FALLTHROUGH; } case SCRIPT: { if (is_meta_type) { return script_type->get_class_name().operator String(); } String name = script_type->get_name(); if (name != String()) { return name; } name = script_type->get_path().get_file(); if (name != String()) { return name; } return native_type.operator String(); } break; case CLASS: { ERR_FAIL_COND_V(!class_type, String()); if (is_meta_type) { return "GDScript"; } if (class_type->name == StringName()) { return "self"; } return class_type->name.operator String(); } break; case UNRESOLVED: { } break; } return "Unresolved"; } bool GDScriptParser::_parse_type(DataType &r_type, bool p_can_be_void) { tokenizer->advance(); r_type.has_type = true; bool finished = false; bool can_index = false; String full_name; if (tokenizer->get_token() == GDScriptTokenizer::TK_CURSOR) { completion_cursor = StringName(); completion_type = COMPLETION_TYPE_HINT; completion_class = current_class; completion_function = current_function; completion_line = tokenizer->get_token_line(); completion_argument = 0; completion_block = current_block; completion_found = true; completion_ident_is_call = p_can_be_void; tokenizer->advance(); } switch (tokenizer->get_token()) { case GDScriptTokenizer::TK_PR_VOID: { if (!p_can_be_void) { return false; } r_type.kind = DataType::BUILTIN; r_type.builtin_type = Variant::NIL; } break; case GDScriptTokenizer::TK_BUILT_IN_TYPE: { r_type.builtin_type = tokenizer->get_token_type(); if (tokenizer->get_token_type() == Variant::OBJECT) { r_type.kind = DataType::NATIVE; r_type.native_type = "Object"; } else { r_type.kind = DataType::BUILTIN; } } break; case GDScriptTokenizer::TK_IDENTIFIER: { r_type.native_type = tokenizer->get_token_identifier(); if (ClassDB::class_exists(r_type.native_type) || ClassDB::class_exists("_" + r_type.native_type.operator String())) { r_type.kind = DataType::NATIVE; } else { r_type.kind = DataType::UNRESOLVED; can_index = true; full_name = r_type.native_type; } } break; default: { return false; } } tokenizer->advance(); if (tokenizer->get_token() == GDScriptTokenizer::TK_CURSOR) { completion_cursor = r_type.native_type; completion_type = COMPLETION_TYPE_HINT; completion_class = current_class; completion_function = current_function; completion_line = tokenizer->get_token_line(); completion_argument = 0; completion_block = current_block; completion_found = true; completion_ident_is_call = p_can_be_void; tokenizer->advance(); } if (can_index) { while (!finished) { switch (tokenizer->get_token()) { case GDScriptTokenizer::TK_PERIOD: { if (!can_index) { _set_error("Unexpected \".\"."); return false; } can_index = false; tokenizer->advance(); } break; case GDScriptTokenizer::TK_IDENTIFIER: { if (can_index) { _set_error("Unexpected identifier."); return false; } StringName id; bool has_completion = _get_completable_identifier(COMPLETION_TYPE_HINT_INDEX, id); if (id == StringName()) { id = "@temp"; } full_name += "." + id.operator String(); can_index = true; if (has_completion) { completion_cursor = full_name; } } break; default: { finished = true; } break; } } if (tokenizer->get_token(-1) == GDScriptTokenizer::TK_PERIOD) { _set_error("Expected a subclass identifier."); return false; } r_type.native_type = full_name; } return true; } GDScriptParser::DataType GDScriptParser::_resolve_type(const DataType &p_source, int p_line) { if (!p_source.has_type) return p_source; if (p_source.kind != DataType::UNRESOLVED) return p_source; Vector<String> full_name = p_source.native_type.operator String().split(".", false); int name_part = 0; DataType result; result.has_type = true; while (name_part < full_name.size()) { bool found = false; StringName id = full_name[name_part]; DataType base_type = result; ClassNode *p = NULL; if (name_part == 0) { if (ScriptServer::is_global_class(id)) { String script_path = ScriptServer::get_global_class_path(id); if (script_path == self_path) { result.kind = DataType::CLASS; result.class_type = static_cast<ClassNode *>(head); } else { Ref<Script> script = ResourceLoader::load(script_path); Ref<GDScript> gds = script; if (gds.is_valid()) { if (!gds->is_valid()) { _set_error("The class \"" + id + "\" couldn't be fully loaded (script error or cyclic dependency).", p_line); return DataType(); } result.kind = DataType::GDSCRIPT; result.script_type = gds; } else if (script.is_valid()) { result.kind = DataType::SCRIPT; result.script_type = script; } else { _set_error("The class \"" + id + "\" was found in global scope, but its script couldn't be loaded.", p_line); return DataType(); } } name_part++; continue; } List<PropertyInfo> props; ProjectSettings::get_singleton()->get_property_list(&props); String singleton_path; for (List<PropertyInfo>::Element *E = props.front(); E; E = E->next()) { String s = E->get().name; if (!s.begins_with("autoload/")) { continue; } String name = s.get_slice("/", 1); if (name == id) { singleton_path = ProjectSettings::get_singleton()->get(s); if (singleton_path.begins_with("*")) { singleton_path = singleton_path.right(1); } if (!singleton_path.begins_with("res://")) { singleton_path = "res://" + singleton_path; } break; } } if (!singleton_path.empty()) { Ref<Script> script = ResourceLoader::load(singleton_path); Ref<GDScript> gds = script; if (gds.is_valid()) { if (!gds->is_valid()) { _set_error("Class '" + id + "' could not be fully loaded (script error or cyclic inheritance).", p_line); return DataType(); } result.kind = DataType::GDSCRIPT; result.script_type = gds; } else if (script.is_valid()) { result.kind = DataType::SCRIPT; result.script_type = script; } else { _set_error("Couldn't fully load singleton script '" + id + "' (possible cyclic reference or parse error).", p_line); return DataType(); } name_part++; continue; } p = current_class; } else if (base_type.kind == DataType::CLASS) { p = base_type.class_type; } while (p) { if (p->constant_expressions.has(id)) { if (p->constant_expressions[id].expression->type != Node::TYPE_CONSTANT) { _set_error("Parser bug: unresolved constant.", p_line); ERR_FAIL_V(result); } const ConstantNode *cn = static_cast<const ConstantNode *>(p->constant_expressions[id].expression); Ref<GDScript> gds = cn->value; if (gds.is_valid()) { result.kind = DataType::GDSCRIPT; result.script_type = gds; found = true; } else { Ref<Script> scr = cn->value; if (scr.is_valid()) { result.kind = DataType::SCRIPT; result.script_type = scr; found = true; } } break; } // Inner classes ClassNode *outer_class = p; while (outer_class) { if (outer_class->name == id) { found = true; result.kind = DataType::CLASS; result.class_type = outer_class; break; } for (int i = 0; i < outer_class->subclasses.size(); i++) { if (outer_class->subclasses[i] == p) { continue; } if (outer_class->subclasses[i]->name == id) { found = true; result.kind = DataType::CLASS; result.class_type = outer_class->subclasses[i]; break; } } if (found) { break; } outer_class = outer_class->owner; } if (!found && p->base_type.kind == DataType::CLASS) { p = p->base_type.class_type; } else { base_type = p->base_type; break; } } // Still look for class constants in parent scripts if (!found && (base_type.kind == DataType::GDSCRIPT || base_type.kind == DataType::SCRIPT)) { Ref<Script> scr = base_type.script_type; ERR_FAIL_COND_V(scr.is_null(), result); while (scr.is_valid()) { Map<StringName, Variant> constants; scr->get_constants(&constants); if (constants.has(id)) { Ref<GDScript> gds = constants[id]; if (gds.is_valid()) { result.kind = DataType::GDSCRIPT; result.script_type = gds; found = true; } else { Ref<Script> scr2 = constants[id]; if (scr2.is_valid()) { result.kind = DataType::SCRIPT; result.script_type = scr2; found = true; } } } if (found) { break; } else { scr = scr->get_base_script(); } } } if (!found && !for_completion) { String base; if (name_part == 0) { base = "self"; } else { base = result.to_string(); } _set_error("The identifier \"" + String(id) + "\" isn't a valid type (not a script or class), or couldn't be found on base \"" + base + "\".", p_line); return DataType(); } name_part++; } return result; } GDScriptParser::DataType GDScriptParser::_type_from_variant(const Variant &p_value) const { DataType result; result.has_type = true; result.is_constant = true; result.kind = DataType::BUILTIN; result.builtin_type = p_value.get_type(); if (result.builtin_type == Variant::OBJECT) { Object *obj = p_value.operator Object *(); if (!obj) { return DataType(); } result.native_type = obj->get_class_name(); Ref<Script> scr = p_value; if (scr.is_valid()) { result.is_meta_type = true; } else { result.is_meta_type = false; scr = obj->get_script(); } if (scr.is_valid()) { result.script_type = scr; Ref<GDScript> gds = scr; if (gds.is_valid()) { result.kind = DataType::GDSCRIPT; } else { result.kind = DataType::SCRIPT; } result.native_type = scr->get_instance_base_type(); } else { result.kind = DataType::NATIVE; } } return result; } GDScriptParser::DataType GDScriptParser::_type_from_property(const PropertyInfo &p_property, bool p_nil_is_variant) const { DataType ret; if (p_property.type == Variant::NIL && (p_nil_is_variant || (p_property.usage & PROPERTY_USAGE_NIL_IS_VARIANT))) { // Variant return ret; } ret.has_type = true; ret.builtin_type = p_property.type; if (p_property.type == Variant::OBJECT) { ret.kind = DataType::NATIVE; ret.native_type = p_property.class_name == StringName() ? "Object" : p_property.class_name; } else { ret.kind = DataType::BUILTIN; } return ret; } GDScriptParser::DataType GDScriptParser::_type_from_gdtype(const GDScriptDataType &p_gdtype) const { DataType result; if (!p_gdtype.has_type) { return result; } result.has_type = true; result.builtin_type = p_gdtype.builtin_type; result.native_type = p_gdtype.native_type; result.script_type = p_gdtype.script_type; switch (p_gdtype.kind) { case GDScriptDataType::UNINITIALIZED: { ERR_PRINT("Uninitialized datatype. Please report a bug."); } break; case GDScriptDataType::BUILTIN: { result.kind = DataType::BUILTIN; } break; case GDScriptDataType::NATIVE: { result.kind = DataType::NATIVE; } break; case GDScriptDataType::GDSCRIPT: { result.kind = DataType::GDSCRIPT; } break; case GDScriptDataType::SCRIPT: { result.kind = DataType::SCRIPT; } break; } return result; } GDScriptParser::DataType GDScriptParser::_get_operation_type(const Variant::Operator p_op, const DataType &p_a, const DataType &p_b, bool &r_valid) const { if (!p_a.has_type || !p_b.has_type) { r_valid = true; return DataType(); } Variant::Type a_type = p_a.kind == DataType::BUILTIN ? p_a.builtin_type : Variant::OBJECT; Variant::Type b_type = p_b.kind == DataType::BUILTIN ? p_b.builtin_type : Variant::OBJECT; Variant a; REF a_ref; if (a_type == Variant::OBJECT) { a_ref.instance(); a = a_ref; } else { Variant::CallError err; a = Variant::construct(a_type, NULL, 0, err); if (err.error != Variant::CallError::CALL_OK) { r_valid = false; return DataType(); } } Variant b; REF b_ref; if (b_type == Variant::OBJECT) { b_ref.instance(); b = b_ref; } else { Variant::CallError err; b = Variant::construct(b_type, NULL, 0, err); if (err.error != Variant::CallError::CALL_OK) { r_valid = false; return DataType(); } } // Avoid division by zero if (a_type == Variant::INT || a_type == Variant::REAL) { Variant::evaluate(Variant::OP_ADD, a, 1, a, r_valid); } if (b_type == Variant::INT || b_type == Variant::REAL) { Variant::evaluate(Variant::OP_ADD, b, 1, b, r_valid); } if (a_type == Variant::STRING && b_type != Variant::ARRAY) { a = "%s"; // Work around for formatting operator (%) } Variant ret; Variant::evaluate(p_op, a, b, ret, r_valid); if (r_valid) { return _type_from_variant(ret); } return DataType(); } Variant::Operator GDScriptParser::_get_variant_operation(const OperatorNode::Operator &p_op) const { switch (p_op) { case OperatorNode::OP_NEG: { return Variant::OP_NEGATE; } break; case OperatorNode::OP_POS: { return Variant::OP_POSITIVE; } break; case OperatorNode::OP_NOT: { return Variant::OP_NOT; } break; case OperatorNode::OP_BIT_INVERT: { return Variant::OP_BIT_NEGATE; } break; case OperatorNode::OP_IN: { return Variant::OP_IN; } break; case OperatorNode::OP_EQUAL: { return Variant::OP_EQUAL; } break; case OperatorNode::OP_NOT_EQUAL: { return Variant::OP_NOT_EQUAL; } break; case OperatorNode::OP_LESS: { return Variant::OP_LESS; } break; case OperatorNode::OP_LESS_EQUAL: { return Variant::OP_LESS_EQUAL; } break; case OperatorNode::OP_GREATER: { return Variant::OP_GREATER; } break; case OperatorNode::OP_GREATER_EQUAL: { return Variant::OP_GREATER_EQUAL; } break; case OperatorNode::OP_AND: { return Variant::OP_AND; } break; case OperatorNode::OP_OR: { return Variant::OP_OR; } break; case OperatorNode::OP_ASSIGN_ADD: case OperatorNode::OP_ADD: { return Variant::OP_ADD; } break; case OperatorNode::OP_ASSIGN_SUB: case OperatorNode::OP_SUB: { return Variant::OP_SUBTRACT; } break; case OperatorNode::OP_ASSIGN_MUL: case OperatorNode::OP_MUL: { return Variant::OP_MULTIPLY; } break; case OperatorNode::OP_ASSIGN_DIV: case OperatorNode::OP_DIV: { return Variant::OP_DIVIDE; } break; case OperatorNode::OP_ASSIGN_MOD: case OperatorNode::OP_MOD: { return Variant::OP_MODULE; } break; case OperatorNode::OP_ASSIGN_BIT_AND: case OperatorNode::OP_BIT_AND: { return Variant::OP_BIT_AND; } break; case OperatorNode::OP_ASSIGN_BIT_OR: case OperatorNode::OP_BIT_OR: { return Variant::OP_BIT_OR; } break; case OperatorNode::OP_ASSIGN_BIT_XOR: case OperatorNode::OP_BIT_XOR: { return Variant::OP_BIT_XOR; } break; case OperatorNode::OP_ASSIGN_SHIFT_LEFT: case OperatorNode::OP_SHIFT_LEFT: { return Variant::OP_SHIFT_LEFT; } case OperatorNode::OP_ASSIGN_SHIFT_RIGHT: case OperatorNode::OP_SHIFT_RIGHT: { return Variant::OP_SHIFT_RIGHT; } default: { return Variant::OP_MAX; } break; } } bool GDScriptParser::_is_type_compatible(const DataType &p_container, const DataType &p_expression, bool p_allow_implicit_conversion) const { // Ignore for completion if (!check_types || for_completion) { return true; } // Can't test if not all have type if (!p_container.has_type || !p_expression.has_type) { return true; } // Should never get here unresolved ERR_FAIL_COND_V(p_container.kind == DataType::UNRESOLVED, false); ERR_FAIL_COND_V(p_expression.kind == DataType::UNRESOLVED, false); if (p_container.kind == DataType::BUILTIN && p_expression.kind == DataType::BUILTIN) { bool valid = p_container.builtin_type == p_expression.builtin_type; if (p_allow_implicit_conversion) { valid = valid || Variant::can_convert_strict(p_expression.builtin_type, p_container.builtin_type); } return valid; } if (p_container.kind == DataType::BUILTIN && p_container.builtin_type == Variant::OBJECT) { // Object built-in is a special case, it's compatible with any object and with null if (p_expression.kind == DataType::BUILTIN) { return p_expression.builtin_type == Variant::NIL; } // If it's not a built-in, must be an object return true; } if (p_container.kind == DataType::BUILTIN || (p_expression.kind == DataType::BUILTIN && p_expression.builtin_type != Variant::NIL)) { // Can't mix built-ins with objects return false; } // From now on everything is objects, check polymorphism // The container must be the same class or a superclass of the expression if (p_expression.kind == DataType::BUILTIN && p_expression.builtin_type == Variant::NIL) { // Null can be assigned to object types return true; } StringName expr_native; Ref<Script> expr_script; ClassNode *expr_class = NULL; switch (p_expression.kind) { case DataType::NATIVE: { if (p_container.kind != DataType::NATIVE) { // Non-native type can't be a superclass of a native type return false; } if (p_expression.is_meta_type) { expr_native = GDScriptNativeClass::get_class_static(); } else { expr_native = p_expression.native_type; } } break; case DataType::SCRIPT: case DataType::GDSCRIPT: { if (p_container.kind == DataType::CLASS) { // This cannot be resolved without cyclic dependencies, so just bail out return false; } if (p_expression.is_meta_type) { expr_native = p_expression.script_type->get_class_name(); } else { expr_script = p_expression.script_type; expr_native = expr_script->get_instance_base_type(); } } break; case DataType::CLASS: { if (p_expression.is_meta_type) { expr_native = GDScript::get_class_static(); } else { expr_class = p_expression.class_type; ClassNode *base = expr_class; while (base->base_type.kind == DataType::CLASS) { base = base->base_type.class_type; } expr_native = base->base_type.native_type; expr_script = base->base_type.script_type; } } break; case DataType::BUILTIN: // Already handled above case DataType::UNRESOLVED: // Not allowed, see above break; } // Some classes are prefixed with `_` internally if (!ClassDB::class_exists(expr_native)) { expr_native = "_" + expr_native; } switch (p_container.kind) { case DataType::NATIVE: { if (p_container.is_meta_type) { return ClassDB::is_parent_class(expr_native, GDScriptNativeClass::get_class_static()); } else { StringName container_native = ClassDB::class_exists(p_container.native_type) ? p_container.native_type : StringName("_" + p_container.native_type); return ClassDB::is_parent_class(expr_native, container_native); } } break; case DataType::SCRIPT: case DataType::GDSCRIPT: { if (p_container.is_meta_type) { return ClassDB::is_parent_class(expr_native, GDScript::get_class_static()); } if (expr_class == head && p_container.script_type->get_path() == self_path) { // Special case: container is self script and expression is self return true; } while (expr_script.is_valid()) { if (expr_script == p_container.script_type) { return true; } expr_script = expr_script->get_base_script(); } return false; } break; case DataType::CLASS: { if (p_container.is_meta_type) { return ClassDB::is_parent_class(expr_native, GDScript::get_class_static()); } if (p_container.class_type == head && expr_script.is_valid() && expr_script->get_path() == self_path) { // Special case: container is self and expression is self script return true; } while (expr_class) { if (expr_class == p_container.class_type) { return true; } expr_class = expr_class->base_type.class_type; } return false; } break; case DataType::BUILTIN: // Already handled above case DataType::UNRESOLVED: // Not allowed, see above break; } return false; } GDScriptParser::Node *GDScriptParser::_get_default_value_for_type(const DataType &p_type, int p_line) { Node *result; if (p_type.has_type && p_type.kind == DataType::BUILTIN && p_type.builtin_type != Variant::NIL && p_type.builtin_type != Variant::OBJECT) { if (p_type.builtin_type == Variant::ARRAY) { result = alloc_node<ArrayNode>(); } else if (p_type.builtin_type == Variant::DICTIONARY) { result = alloc_node<DictionaryNode>(); } else { ConstantNode *c = alloc_node<ConstantNode>(); Variant::CallError err; c->value = Variant::construct(p_type.builtin_type, NULL, 0, err); result = c; } } else { ConstantNode *c = alloc_node<ConstantNode>(); c->value = Variant(); result = c; } result->line = p_line; return result; } GDScriptParser::DataType GDScriptParser::_reduce_node_type(Node *p_node) { #ifdef DEBUG_ENABLED if (p_node->get_datatype().has_type && p_node->type != Node::TYPE_ARRAY && p_node->type != Node::TYPE_DICTIONARY) { #else if (p_node->get_datatype().has_type) { #endif return p_node->get_datatype(); } DataType node_type; switch (p_node->type) { case Node::TYPE_CONSTANT: { node_type = _type_from_variant(static_cast<ConstantNode *>(p_node)->value); } break; case Node::TYPE_TYPE: { TypeNode *tn = static_cast<TypeNode *>(p_node); node_type.has_type = true; node_type.is_meta_type = true; node_type.kind = DataType::BUILTIN; node_type.builtin_type = tn->vtype; } break; case Node::TYPE_ARRAY: { node_type.has_type = true; node_type.kind = DataType::BUILTIN; node_type.builtin_type = Variant::ARRAY; #ifdef DEBUG_ENABLED // Check stuff inside the array ArrayNode *an = static_cast<ArrayNode *>(p_node); for (int i = 0; i < an->elements.size(); i++) { _reduce_node_type(an->elements[i]); } #endif // DEBUG_ENABLED } break; case Node::TYPE_DICTIONARY: { node_type.has_type = true; node_type.kind = DataType::BUILTIN; node_type.builtin_type = Variant::DICTIONARY; #ifdef DEBUG_ENABLED // Check stuff inside the dictionarty DictionaryNode *dn = static_cast<DictionaryNode *>(p_node); for (int i = 0; i < dn->elements.size(); i++) { _reduce_node_type(dn->elements[i].key); _reduce_node_type(dn->elements[i].value); } #endif // DEBUG_ENABLED } break; case Node::TYPE_SELF: { node_type.has_type = true; node_type.kind = DataType::CLASS; node_type.class_type = current_class; node_type.is_constant = true; } break; case Node::TYPE_IDENTIFIER: { IdentifierNode *id = static_cast<IdentifierNode *>(p_node); if (id->declared_block) { node_type = id->declared_block->variables[id->name]->get_datatype(); id->declared_block->variables[id->name]->usages += 1; } else if (id->name == "#match_value") { // It's a special id just for the match statetement, ignore break; } else if (current_function && current_function->arguments.find(id->name) >= 0) { int idx = current_function->arguments.find(id->name); node_type = current_function->argument_types[idx]; } else { node_type = _reduce_identifier_type(NULL, id->name, id->line, false); } } break; case Node::TYPE_CAST: { CastNode *cn = static_cast<CastNode *>(p_node); DataType source_type = _reduce_node_type(cn->source_node); cn->cast_type = _resolve_type(cn->cast_type, cn->line); if (source_type.has_type) { bool valid = false; if (check_types) { if (cn->cast_type.kind == DataType::BUILTIN && source_type.kind == DataType::BUILTIN) { valid = Variant::can_convert(source_type.builtin_type, cn->cast_type.builtin_type); } if (cn->cast_type.kind != DataType::BUILTIN && source_type.kind != DataType::BUILTIN) { valid = _is_type_compatible(cn->cast_type, source_type) || _is_type_compatible(source_type, cn->cast_type); } if (!valid) { _set_error("Invalid cast. Cannot convert from \"" + source_type.to_string() + "\" to \"" + cn->cast_type.to_string() + "\".", cn->line); return DataType(); } } } else { #ifdef DEBUG_ENABLED _add_warning(GDScriptWarning::UNSAFE_CAST, cn->line, cn->cast_type.to_string()); #endif // DEBUG_ENABLED _mark_line_as_unsafe(cn->line); } node_type = cn->cast_type; } break; case Node::TYPE_OPERATOR: { OperatorNode *op = static_cast<OperatorNode *>(p_node); switch (op->op) { case OperatorNode::OP_CALL: case OperatorNode::OP_PARENT_CALL: { node_type = _reduce_function_call_type(op); } break; case OperatorNode::OP_YIELD: { if (op->arguments.size() == 2) { DataType base_type = _reduce_node_type(op->arguments[0]); DataType signal_type = _reduce_node_type(op->arguments[1]); // TODO: Check if signal exists when it's a constant if (base_type.has_type && base_type.kind == DataType::BUILTIN && base_type.builtin_type != Variant::NIL && base_type.builtin_type != Variant::OBJECT) { _set_error("The first argument of \"yield()\" must be an object.", op->line); return DataType(); } if (signal_type.has_type && (signal_type.kind != DataType::BUILTIN || signal_type.builtin_type != Variant::STRING)) { _set_error("The second argument of \"yield()\" must be a string.", op->line); return DataType(); } } // yield can return anything node_type.has_type = false; } break; case OperatorNode::OP_IS: case OperatorNode::OP_IS_BUILTIN: { if (op->arguments.size() != 2) { _set_error("Parser bug: binary operation without 2 arguments.", op->line); ERR_FAIL_V(DataType()); } DataType value_type = _reduce_node_type(op->arguments[0]); DataType type_type = _reduce_node_type(op->arguments[1]); if (check_types && type_type.has_type) { if (!type_type.is_meta_type && (type_type.kind != DataType::NATIVE || !ClassDB::is_parent_class(type_type.native_type, "Script"))) { _set_error("Invalid \"is\" test: the right operand isn't a type (neither a native type nor a script).", op->line); return DataType(); } type_type.is_meta_type = false; // Test the actual type if (!_is_type_compatible(type_type, value_type) && !_is_type_compatible(value_type, type_type)) { if (op->op == OperatorNode::OP_IS) { _set_error("A value of type \"" + value_type.to_string() + "\" will never be an instance of \"" + type_type.to_string() + "\".", op->line); } else { _set_error("A value of type \"" + value_type.to_string() + "\" will never be of type \"" + type_type.to_string() + "\".", op->line); } return DataType(); } } node_type.has_type = true; node_type.is_constant = true; node_type.is_meta_type = false; node_type.kind = DataType::BUILTIN; node_type.builtin_type = Variant::BOOL; } break; // Unary operators case OperatorNode::OP_NEG: case OperatorNode::OP_POS: case OperatorNode::OP_NOT: case OperatorNode::OP_BIT_INVERT: { DataType argument_type = _reduce_node_type(op->arguments[0]); if (!argument_type.has_type) { break; } Variant::Operator var_op = _get_variant_operation(op->op); bool valid = false; node_type = _get_operation_type(var_op, argument_type, argument_type, valid); if (check_types && !valid) { _set_error("Invalid operand type (\"" + argument_type.to_string() + "\") to unary operator \"" + Variant::get_operator_name(var_op) + "\".", op->line, op->column); return DataType(); } } break; // Binary operators case OperatorNode::OP_IN: case OperatorNode::OP_EQUAL: case OperatorNode::OP_NOT_EQUAL: case OperatorNode::OP_LESS: case OperatorNode::OP_LESS_EQUAL: case OperatorNode::OP_GREATER: case OperatorNode::OP_GREATER_EQUAL: case OperatorNode::OP_AND: case OperatorNode::OP_OR: case OperatorNode::OP_ADD: case OperatorNode::OP_SUB: case OperatorNode::OP_MUL: case OperatorNode::OP_DIV: case OperatorNode::OP_MOD: case OperatorNode::OP_SHIFT_LEFT: case OperatorNode::OP_SHIFT_RIGHT: case OperatorNode::OP_BIT_AND: case OperatorNode::OP_BIT_OR: case OperatorNode::OP_BIT_XOR: { if (op->arguments.size() != 2) { _set_error("Parser bug: binary operation without 2 arguments.", op->line); ERR_FAIL_V(DataType()); } DataType argument_a_type = _reduce_node_type(op->arguments[0]); DataType argument_b_type = _reduce_node_type(op->arguments[1]); if (!argument_a_type.has_type || !argument_b_type.has_type) { _mark_line_as_unsafe(op->line); break; } Variant::Operator var_op = _get_variant_operation(op->op); bool valid = false; node_type = _get_operation_type(var_op, argument_a_type, argument_b_type, valid); if (check_types && !valid) { _set_error("Invalid operand types (\"" + argument_a_type.to_string() + "\" and \"" + argument_b_type.to_string() + "\") to operator \"" + Variant::get_operator_name(var_op) + "\".", op->line, op->column); return DataType(); } #ifdef DEBUG_ENABLED if (var_op == Variant::OP_DIVIDE && argument_a_type.kind == DataType::BUILTIN && argument_a_type.builtin_type == Variant::INT && argument_b_type.kind == DataType::BUILTIN && argument_b_type.builtin_type == Variant::INT) { _add_warning(GDScriptWarning::INTEGER_DIVISION, op->line); } #endif // DEBUG_ENABLED } break; // Ternary operators case OperatorNode::OP_TERNARY_IF: { if (op->arguments.size() != 3) { _set_error("Parser bug: ternary operation without 3 arguments."); ERR_FAIL_V(DataType()); } DataType true_type = _reduce_node_type(op->arguments[1]); DataType false_type = _reduce_node_type(op->arguments[2]); // Check arguments[0] errors. _reduce_node_type(op->arguments[0]); // If types are equal, then the expression is of the same type // If they are compatible, return the broader type if (true_type == false_type || _is_type_compatible(true_type, false_type)) { node_type = true_type; } else if (_is_type_compatible(false_type, true_type)) { node_type = false_type; } else { #ifdef DEBUG_ENABLED _add_warning(GDScriptWarning::INCOMPATIBLE_TERNARY, op->line); #endif // DEBUG_ENABLED } } break; // Assignment should never happen within an expression case OperatorNode::OP_ASSIGN: case OperatorNode::OP_ASSIGN_ADD: case OperatorNode::OP_ASSIGN_SUB: case OperatorNode::OP_ASSIGN_MUL: case OperatorNode::OP_ASSIGN_DIV: case OperatorNode::OP_ASSIGN_MOD: case OperatorNode::OP_ASSIGN_SHIFT_LEFT: case OperatorNode::OP_ASSIGN_SHIFT_RIGHT: case OperatorNode::OP_ASSIGN_BIT_AND: case OperatorNode::OP_ASSIGN_BIT_OR: case OperatorNode::OP_ASSIGN_BIT_XOR: case OperatorNode::OP_INIT_ASSIGN: { _set_error("Assignment inside an expression isn't allowed (parser bug?).", op->line); return DataType(); } break; case OperatorNode::OP_INDEX_NAMED: { if (op->arguments.size() != 2) { _set_error("Parser bug: named index with invalid arguments.", op->line); ERR_FAIL_V(DataType()); } if (op->arguments[1]->type != Node::TYPE_IDENTIFIER) { _set_error("Parser bug: named index without identifier argument.", op->line); ERR_FAIL_V(DataType()); } DataType base_type = _reduce_node_type(op->arguments[0]); IdentifierNode *member_id = static_cast<IdentifierNode *>(op->arguments[1]); if (base_type.has_type) { if (check_types && base_type.kind == DataType::BUILTIN) { // Variant type, just test if it's possible DataType result; switch (base_type.builtin_type) { case Variant::NIL: case Variant::DICTIONARY: { result.has_type = false; } break; default: { Variant::CallError err; Variant temp = Variant::construct(base_type.builtin_type, NULL, 0, err); bool valid = false; Variant res = temp.get(member_id->name.operator String(), &valid); if (valid) { result = _type_from_variant(res); } else if (check_types) { _set_error("Can't get index \"" + String(member_id->name.operator String()) + "\" on base \"" + base_type.to_string() + "\".", op->line); return DataType(); } } break; } result.is_constant = false; node_type = result; } else { node_type = _reduce_identifier_type(&base_type, member_id->name, op->line, true); #ifdef DEBUG_ENABLED if (!node_type.has_type) { _add_warning(GDScriptWarning::UNSAFE_PROPERTY_ACCESS, op->line, member_id->name.operator String(), base_type.to_string()); } #endif // DEBUG_ENABLED } } else { _mark_line_as_unsafe(op->line); } if (error_set) { return DataType(); } } break; case OperatorNode::OP_INDEX: { if (op->arguments[1]->type == Node::TYPE_CONSTANT) { ConstantNode *cn = static_cast<ConstantNode *>(op->arguments[1]); if (cn->value.get_type() == Variant::STRING) { // Treat this as named indexing IdentifierNode *id = alloc_node<IdentifierNode>(); id->name = cn->value.operator StringName(); op->op = OperatorNode::OP_INDEX_NAMED; op->arguments.write[1] = id; return _reduce_node_type(op); } } DataType base_type = _reduce_node_type(op->arguments[0]); DataType index_type = _reduce_node_type(op->arguments[1]); if (!base_type.has_type) { _mark_line_as_unsafe(op->line); break; } if (check_types && index_type.has_type) { if (base_type.kind == DataType::BUILTIN) { // Check if indexing is valid bool error = index_type.kind != DataType::BUILTIN && base_type.builtin_type != Variant::DICTIONARY; if (!error) { switch (base_type.builtin_type) { // Expect int or real as index case Variant::POOL_BYTE_ARRAY: case Variant::POOL_COLOR_ARRAY: case Variant::POOL_INT_ARRAY: case Variant::POOL_REAL_ARRAY: case Variant::POOL_STRING_ARRAY: case Variant::POOL_VECTOR2_ARRAY: case Variant::POOL_VECTOR3_ARRAY: case Variant::ARRAY: case Variant::STRING: { error = index_type.builtin_type != Variant::INT && index_type.builtin_type != Variant::REAL; } break; // Expect String only case Variant::RECT2: case Variant::PLANE: case Variant::QUAT: case Variant::AABB: case Variant::OBJECT: { error = index_type.builtin_type != Variant::STRING; } break; // Expect String or number case Variant::VECTOR2: case Variant::VECTOR3: case Variant::TRANSFORM2D: case Variant::BASIS: case Variant::TRANSFORM: { error = index_type.builtin_type != Variant::INT && index_type.builtin_type != Variant::REAL && index_type.builtin_type != Variant::STRING; } break; // Expect String or int case Variant::COLOR: { error = index_type.builtin_type != Variant::INT && index_type.builtin_type != Variant::STRING; } break; default: { } } } if (error) { _set_error("Invalid index type (" + index_type.to_string() + ") for base \"" + base_type.to_string() + "\".", op->line); return DataType(); } if (op->arguments[1]->type == GDScriptParser::Node::TYPE_CONSTANT) { ConstantNode *cn = static_cast<ConstantNode *>(op->arguments[1]); // Index is a constant, just try it if possible switch (base_type.builtin_type) { // Arrays/string have variable indexing, can't test directly case Variant::STRING: case Variant::ARRAY: case Variant::DICTIONARY: case Variant::POOL_BYTE_ARRAY: case Variant::POOL_COLOR_ARRAY: case Variant::POOL_INT_ARRAY: case Variant::POOL_REAL_ARRAY: case Variant::POOL_STRING_ARRAY: case Variant::POOL_VECTOR2_ARRAY: case Variant::POOL_VECTOR3_ARRAY: { break; } default: { Variant::CallError err; Variant temp = Variant::construct(base_type.builtin_type, NULL, 0, err); bool valid = false; Variant res = temp.get(cn->value, &valid); if (valid) { node_type = _type_from_variant(res); node_type.is_constant = false; } else if (check_types) { _set_error("Can't get index \"" + String(cn->value) + "\" on base \"" + base_type.to_string() + "\".", op->line); return DataType(); } } break; } } else { _mark_line_as_unsafe(op->line); } } else if (!for_completion && (index_type.kind != DataType::BUILTIN || index_type.builtin_type != Variant::STRING)) { _set_error("Only strings can be used as an index in the base type \"" + base_type.to_string() + "\".", op->line); return DataType(); } } if (check_types && !node_type.has_type && base_type.kind == DataType::BUILTIN) { // Can infer indexing type for some variant types DataType result; result.has_type = true; result.kind = DataType::BUILTIN; switch (base_type.builtin_type) { // Can't index at all case Variant::NIL: case Variant::BOOL: case Variant::INT: case Variant::REAL: case Variant::NODE_PATH: case Variant::_RID: { _set_error("Can't index on a value of type \"" + base_type.to_string() + "\".", op->line); return DataType(); } break; // Return int case Variant::POOL_BYTE_ARRAY: case Variant::POOL_INT_ARRAY: { result.builtin_type = Variant::INT; } break; // Return real case Variant::POOL_REAL_ARRAY: case Variant::VECTOR2: case Variant::VECTOR3: case Variant::QUAT: { result.builtin_type = Variant::REAL; } break; // Return color case Variant::POOL_COLOR_ARRAY: { result.builtin_type = Variant::COLOR; } break; // Return string case Variant::POOL_STRING_ARRAY: case Variant::STRING: { result.builtin_type = Variant::STRING; } break; // Return Vector2 case Variant::POOL_VECTOR2_ARRAY: case Variant::TRANSFORM2D: case Variant::RECT2: { result.builtin_type = Variant::VECTOR2; } break; // Return Vector3 case Variant::POOL_VECTOR3_ARRAY: case Variant::AABB: case Variant::BASIS: { result.builtin_type = Variant::VECTOR3; } break; // Depends on the index case Variant::TRANSFORM: case Variant::PLANE: case Variant::COLOR: default: { result.has_type = false; } break; } node_type = result; } } break; default: { _set_error("Parser bug: unhandled operation.", op->line); ERR_FAIL_V(DataType()); } } } break; default: { } } node_type = _resolve_type(node_type, p_node->line); p_node->set_datatype(node_type); return node_type; } bool GDScriptParser::_get_function_signature(DataType &p_base_type, const StringName &p_function, DataType &r_return_type, List<DataType> &r_arg_types, int &r_default_arg_count, bool &r_static, bool &r_vararg) const { r_static = false; r_default_arg_count = 0; DataType original_type = p_base_type; ClassNode *base = NULL; FunctionNode *callee = NULL; if (p_base_type.kind == DataType::CLASS) { base = p_base_type.class_type; } // Look up the current file (parse tree) while (!callee && base) { for (int i = 0; i < base->static_functions.size(); i++) { FunctionNode *func = base->static_functions[i]; if (p_function == func->name) { r_static = true; callee = func; break; } } if (!callee && !p_base_type.is_meta_type) { for (int i = 0; i < base->functions.size(); i++) { FunctionNode *func = base->functions[i]; if (p_function == func->name) { callee = func; break; } } } p_base_type = base->base_type; if (p_base_type.kind == DataType::CLASS) { base = p_base_type.class_type; } else { break; } } if (callee) { r_return_type = callee->get_datatype(); for (int i = 0; i < callee->argument_types.size(); i++) { r_arg_types.push_back(callee->argument_types[i]); } r_default_arg_count = callee->default_values.size(); return true; } // Nothing in current file, check parent script Ref<GDScript> base_gdscript; Ref<Script> base_script; StringName native; if (p_base_type.kind == DataType::GDSCRIPT) { base_gdscript = p_base_type.script_type; if (base_gdscript.is_null() || !base_gdscript->is_valid()) { // GDScript wasn't properly compíled, don't bother trying return false; } } else if (p_base_type.kind == DataType::SCRIPT) { base_script = p_base_type.script_type; } else if (p_base_type.kind == DataType::NATIVE) { native = p_base_type.native_type; } while (base_gdscript.is_valid()) { native = base_gdscript->get_instance_base_type(); Map<StringName, GDScriptFunction *> funcs = base_gdscript->get_member_functions(); if (funcs.has(p_function)) { GDScriptFunction *f = funcs[p_function]; r_static = f->is_static(); r_default_arg_count = f->get_default_argument_count(); r_return_type = _type_from_gdtype(f->get_return_type()); for (int i = 0; i < f->get_argument_count(); i++) { r_arg_types.push_back(_type_from_gdtype(f->get_argument_type(i))); } return true; } base_gdscript = base_gdscript->get_base_script(); } while (base_script.is_valid()) { native = base_script->get_instance_base_type(); MethodInfo mi = base_script->get_method_info(p_function); if (!(mi == MethodInfo())) { r_return_type = _type_from_property(mi.return_val, false); r_default_arg_count = mi.default_arguments.size(); for (List<PropertyInfo>::Element *E = mi.arguments.front(); E; E = E->next()) { r_arg_types.push_back(_type_from_property(E->get())); } return true; } base_script = base_script->get_base_script(); } if (native == StringName()) { // Empty native class, might happen in some Script implementations // Just ignore it return false; } #ifdef DEBUG_METHODS_ENABLED // Only native remains if (!ClassDB::class_exists(native)) { native = "_" + native.operator String(); } if (!ClassDB::class_exists(native)) { if (!check_types) return false; ERR_FAIL_V_MSG(false, "Parser bug: Class '" + String(native) + "' not found."); } MethodBind *method = ClassDB::get_method(native, p_function); if (!method) { // Try virtual methods List<MethodInfo> virtuals; ClassDB::get_virtual_methods(native, &virtuals); for (const List<MethodInfo>::Element *E = virtuals.front(); E; E = E->next()) { const MethodInfo &mi = E->get(); if (mi.name == p_function) { r_default_arg_count = mi.default_arguments.size(); for (const List<PropertyInfo>::Element *pi = mi.arguments.front(); pi; pi = pi->next()) { r_arg_types.push_back(_type_from_property(pi->get())); } r_return_type = _type_from_property(mi.return_val, false); r_vararg = mi.flags & METHOD_FLAG_VARARG; return true; } } // If the base is a script, it might be trying to access members of the Script class itself if (original_type.is_meta_type && !(p_function == "new") && (original_type.kind == DataType::SCRIPT || original_type.kind == DataType::GDSCRIPT)) { method = ClassDB::get_method(original_type.script_type->get_class_name(), p_function); if (method) { r_static = true; } else { // Try virtual methods of the script type virtuals.clear(); ClassDB::get_virtual_methods(original_type.script_type->get_class_name(), &virtuals); for (const List<MethodInfo>::Element *E = virtuals.front(); E; E = E->next()) { const MethodInfo &mi = E->get(); if (mi.name == p_function) { r_default_arg_count = mi.default_arguments.size(); for (const List<PropertyInfo>::Element *pi = mi.arguments.front(); pi; pi = pi->next()) { r_arg_types.push_back(_type_from_property(pi->get())); } r_return_type = _type_from_property(mi.return_val, false); r_static = true; r_vararg = mi.flags & METHOD_FLAG_VARARG; return true; } } return false; } } else { return false; } } r_default_arg_count = method->get_default_argument_count(); r_return_type = _type_from_property(method->get_return_info(), false); r_vararg = method->is_vararg(); for (int i = 0; i < method->get_argument_count(); i++) { r_arg_types.push_back(_type_from_property(method->get_argument_info(i))); } return true; #else return false; #endif } GDScriptParser::DataType GDScriptParser::_reduce_function_call_type(const OperatorNode *p_call) { if (p_call->arguments.size() < 1) { _set_error("Parser bug: function call without enough arguments.", p_call->line); ERR_FAIL_V(DataType()); } DataType return_type; List<DataType> arg_types; int default_args_count = 0; int arg_count = p_call->arguments.size(); String callee_name; bool is_vararg = false; switch (p_call->arguments[0]->type) { case GDScriptParser::Node::TYPE_TYPE: { // Built-in constructor, special case TypeNode *tn = static_cast<TypeNode *>(p_call->arguments[0]); Vector<DataType> par_types; par_types.resize(p_call->arguments.size() - 1); for (int i = 1; i < p_call->arguments.size(); i++) { par_types.write[i - 1] = _reduce_node_type(p_call->arguments[i]); } if (error_set) return DataType(); // Special case: check copy constructor. Those are defined implicitly in Variant. if (par_types.size() == 1) { if (!par_types[0].has_type || (par_types[0].kind == DataType::BUILTIN && par_types[0].builtin_type == tn->vtype)) { DataType result; result.has_type = true; result.kind = DataType::BUILTIN; result.builtin_type = tn->vtype; return result; } } bool match = false; List<MethodInfo> constructors; Variant::get_constructor_list(tn->vtype, &constructors); PropertyInfo return_type2; for (List<MethodInfo>::Element *E = constructors.front(); E; E = E->next()) { MethodInfo &mi = E->get(); if (p_call->arguments.size() - 1 < mi.arguments.size() - mi.default_arguments.size()) { continue; } if (p_call->arguments.size() - 1 > mi.arguments.size()) { continue; } bool types_match = true; for (int i = 0; i < par_types.size(); i++) { DataType arg_type; if (mi.arguments[i].type != Variant::NIL) { arg_type.has_type = true; arg_type.kind = mi.arguments[i].type == Variant::OBJECT ? DataType::NATIVE : DataType::BUILTIN; arg_type.builtin_type = mi.arguments[i].type; arg_type.native_type = mi.arguments[i].class_name; } if (!_is_type_compatible(arg_type, par_types[i], true)) { types_match = false; break; } else { #ifdef DEBUG_ENABLED if (arg_type.kind == DataType::BUILTIN && arg_type.builtin_type == Variant::INT && par_types[i].kind == DataType::BUILTIN && par_types[i].builtin_type == Variant::REAL) { _add_warning(GDScriptWarning::NARROWING_CONVERSION, p_call->line, Variant::get_type_name(tn->vtype)); } if (par_types[i].may_yield && p_call->arguments[i + 1]->type == Node::TYPE_OPERATOR) { _add_warning(GDScriptWarning::FUNCTION_MAY_YIELD, p_call->line, _find_function_name(static_cast<OperatorNode *>(p_call->arguments[i + 1]))); } #endif // DEBUG_ENABLED } } if (types_match) { match = true; return_type2 = mi.return_val; break; } } if (match) { return _type_from_property(return_type2, false); } else if (check_types) { String err = "No constructor of '"; err += Variant::get_type_name(tn->vtype); err += "' matches the signature '"; err += Variant::get_type_name(tn->vtype) + "("; for (int i = 0; i < par_types.size(); i++) { if (i > 0) err += ", "; err += par_types[i].to_string(); } err += ")'."; _set_error(err, p_call->line, p_call->column); return DataType(); } return DataType(); } break; case GDScriptParser::Node::TYPE_BUILT_IN_FUNCTION: { BuiltInFunctionNode *func = static_cast<BuiltInFunctionNode *>(p_call->arguments[0]); MethodInfo mi = GDScriptFunctions::get_info(func->function); return_type = _type_from_property(mi.return_val, false); // Check all arguments beforehand to solve warnings for (int i = 1; i < p_call->arguments.size(); i++) { _reduce_node_type(p_call->arguments[i]); } // Check arguments is_vararg = mi.flags & METHOD_FLAG_VARARG; default_args_count = mi.default_arguments.size(); callee_name = mi.name; arg_count -= 1; // Check each argument type for (List<PropertyInfo>::Element *E = mi.arguments.front(); E; E = E->next()) { arg_types.push_back(_type_from_property(E->get())); } } break; default: { if (p_call->op == OperatorNode::OP_CALL && p_call->arguments.size() < 2) { _set_error("Parser bug: self method call without enough arguments.", p_call->line); ERR_FAIL_V(DataType()); } int arg_id = p_call->op == OperatorNode::OP_CALL ? 1 : 0; if (p_call->arguments[arg_id]->type != Node::TYPE_IDENTIFIER) { _set_error("Parser bug: invalid function call argument.", p_call->line); ERR_FAIL_V(DataType()); } // Check all arguments beforehand to solve warnings for (int i = arg_id + 1; i < p_call->arguments.size(); i++) { _reduce_node_type(p_call->arguments[i]); } IdentifierNode *func_id = static_cast<IdentifierNode *>(p_call->arguments[arg_id]); callee_name = func_id->name; arg_count -= 1 + arg_id; DataType base_type; if (p_call->op == OperatorNode::OP_PARENT_CALL) { base_type = current_class->base_type; } else { base_type = _reduce_node_type(p_call->arguments[0]); } if (!base_type.has_type || (base_type.kind == DataType::BUILTIN && base_type.builtin_type == Variant::NIL)) { _mark_line_as_unsafe(p_call->line); return DataType(); } if (base_type.kind == DataType::BUILTIN) { Variant::CallError err; Variant tmp = Variant::construct(base_type.builtin_type, NULL, 0, err); if (check_types) { if (!tmp.has_method(callee_name)) { _set_error("The method \"" + callee_name + "\" isn't declared on base \"" + base_type.to_string() + "\".", p_call->line); return DataType(); } default_args_count = Variant::get_method_default_arguments(base_type.builtin_type, callee_name).size(); const Vector<Variant::Type> &var_arg_types = Variant::get_method_argument_types(base_type.builtin_type, callee_name); for (int i = 0; i < var_arg_types.size(); i++) { DataType argtype; if (var_arg_types[i] != Variant::NIL) { argtype.has_type = true; argtype.kind = DataType::BUILTIN; argtype.builtin_type = var_arg_types[i]; } arg_types.push_back(argtype); } } bool rets = false; return_type.has_type = true; return_type.kind = DataType::BUILTIN; return_type.builtin_type = Variant::get_method_return_type(base_type.builtin_type, callee_name, &rets); // If the method returns, but it might return any type, (Variant::NIL), pretend we don't know the type. // At least make sure we know that it returns if (rets && return_type.builtin_type == Variant::NIL) { return_type.has_type = false; } break; } DataType original_type = base_type; bool is_initializer = callee_name == "new"; bool is_static = false; bool valid = false; if (is_initializer && original_type.is_meta_type) { // Try to check it as initializer base_type = original_type; callee_name = "_init"; base_type.is_meta_type = false; valid = _get_function_signature(base_type, callee_name, return_type, arg_types, default_args_count, is_static, is_vararg); return_type = original_type; return_type.is_meta_type = false; valid = true; // There's always an initializer, we can assume this is true } if (!valid) { base_type = original_type; return_type = DataType(); valid = _get_function_signature(base_type, callee_name, return_type, arg_types, default_args_count, is_static, is_vararg); } if (!valid) { #ifdef DEBUG_ENABLED if (p_call->arguments[0]->type == Node::TYPE_SELF) { _set_error("The method \"" + callee_name + "\" isn't declared in the current class.", p_call->line); return DataType(); } DataType tmp_type; valid = _get_member_type(original_type, func_id->name, tmp_type); if (valid) { if (tmp_type.is_constant) { _add_warning(GDScriptWarning::CONSTANT_USED_AS_FUNCTION, p_call->line, callee_name, original_type.to_string()); } else { _add_warning(GDScriptWarning::PROPERTY_USED_AS_FUNCTION, p_call->line, callee_name, original_type.to_string()); } } _add_warning(GDScriptWarning::UNSAFE_METHOD_ACCESS, p_call->line, callee_name, original_type.to_string()); _mark_line_as_unsafe(p_call->line); #endif // DEBUG_ENABLED return DataType(); } #ifdef DEBUG_ENABLED if (current_function && !for_completion && !is_static && p_call->arguments[0]->type == Node::TYPE_SELF && current_function->_static) { _set_error("Can't call non-static function from a static function.", p_call->line); return DataType(); } if (check_types && !is_static && !is_initializer && base_type.is_meta_type) { _set_error("Non-static function \"" + String(callee_name) + "\" can only be called from an instance.", p_call->line); return DataType(); } // Check signal emission for warnings if (callee_name == "emit_signal" && p_call->op == OperatorNode::OP_CALL && p_call->arguments[0]->type == Node::TYPE_SELF && p_call->arguments.size() >= 3 && p_call->arguments[2]->type == Node::TYPE_CONSTANT) { ConstantNode *sig = static_cast<ConstantNode *>(p_call->arguments[2]); String emitted = sig->value.get_type() == Variant::STRING ? sig->value.operator String() : ""; for (int i = 0; i < current_class->_signals.size(); i++) { if (current_class->_signals[i].name == emitted) { current_class->_signals.write[i].emissions += 1; break; } } } #endif // DEBUG_ENABLED } break; } #ifdef DEBUG_ENABLED if (!check_types) { return return_type; } if (arg_count < arg_types.size() - default_args_count) { _set_error("Too few arguments for \"" + callee_name + "()\" call. Expected at least " + itos(arg_types.size() - default_args_count) + ".", p_call->line); return return_type; } if (!is_vararg && arg_count > arg_types.size()) { _set_error("Too many arguments for \"" + callee_name + "()\" call. Expected at most " + itos(arg_types.size()) + ".", p_call->line); return return_type; } int arg_diff = p_call->arguments.size() - arg_count; for (int i = arg_diff; i < p_call->arguments.size(); i++) { DataType par_type = _reduce_node_type(p_call->arguments[i]); if ((i - arg_diff) >= arg_types.size()) { continue; } DataType arg_type = arg_types[i - arg_diff]; if (!par_type.has_type) { _mark_line_as_unsafe(p_call->line); if (par_type.may_yield && p_call->arguments[i]->type == Node::TYPE_OPERATOR) { _add_warning(GDScriptWarning::FUNCTION_MAY_YIELD, p_call->line, _find_function_name(static_cast<OperatorNode *>(p_call->arguments[i]))); } } else if (!_is_type_compatible(arg_types[i - arg_diff], par_type, true)) { // Supertypes are acceptable for dynamic compliance if (!_is_type_compatible(par_type, arg_types[i - arg_diff])) { _set_error("At \"" + callee_name + "()\" call, argument " + itos(i - arg_diff + 1) + ". Assigned type (" + par_type.to_string() + ") doesn't match the function argument's type (" + arg_types[i - arg_diff].to_string() + ").", p_call->line); return DataType(); } else { _mark_line_as_unsafe(p_call->line); } } else { if (arg_type.kind == DataType::BUILTIN && arg_type.builtin_type == Variant::INT && par_type.kind == DataType::BUILTIN && par_type.builtin_type == Variant::REAL) { _add_warning(GDScriptWarning::NARROWING_CONVERSION, p_call->line, callee_name); } } } #endif // DEBUG_ENABLED return return_type; } bool GDScriptParser::_get_member_type(const DataType &p_base_type, const StringName &p_member, DataType &r_member_type) const { DataType base_type = p_base_type; // Check classes in current file ClassNode *base = NULL; if (base_type.kind == DataType::CLASS) { base = base_type.class_type; } while (base) { if (base->constant_expressions.has(p_member)) { r_member_type = base->constant_expressions[p_member].expression->get_datatype(); return true; } if (!base_type.is_meta_type) { for (int i = 0; i < base->variables.size(); i++) { if (base->variables[i].identifier == p_member) { r_member_type = base->variables[i].data_type; base->variables.write[i].usages += 1; return true; } } } else { for (int i = 0; i < base->subclasses.size(); i++) { ClassNode *c = base->subclasses[i]; if (c->name == p_member) { DataType class_type; class_type.has_type = true; class_type.is_constant = true; class_type.is_meta_type = true; class_type.kind = DataType::CLASS; class_type.class_type = c; r_member_type = class_type; return true; } } } base_type = base->base_type; if (base_type.kind == DataType::CLASS) { base = base_type.class_type; } else { break; } } Ref<GDScript> gds; if (base_type.kind == DataType::GDSCRIPT) { gds = base_type.script_type; if (gds.is_null() || !gds->is_valid()) { // GDScript wasn't properly compíled, don't bother trying return false; } } Ref<Script> scr; if (base_type.kind == DataType::SCRIPT) { scr = base_type.script_type; } StringName native; if (base_type.kind == DataType::NATIVE) { native = base_type.native_type; } // Check GDScripts while (gds.is_valid()) { if (gds->get_constants().has(p_member)) { Variant c = gds->get_constants()[p_member]; r_member_type = _type_from_variant(c); return true; } if (!base_type.is_meta_type) { if (gds->get_members().has(p_member)) { r_member_type = _type_from_gdtype(gds->get_member_type(p_member)); return true; } } native = gds->get_instance_base_type(); if (gds->get_base_script().is_valid()) { gds = gds->get_base_script(); scr = gds->get_base_script(); bool is_meta = base_type.is_meta_type; base_type = _type_from_variant(scr.operator Variant()); base_type.is_meta_type = is_meta; } else { break; } } // Check other script types while (scr.is_valid()) { Map<StringName, Variant> constants; scr->get_constants(&constants); if (constants.has(p_member)) { r_member_type = _type_from_variant(constants[p_member]); return true; } List<PropertyInfo> properties; scr->get_script_property_list(&properties); for (List<PropertyInfo>::Element *E = properties.front(); E; E = E->next()) { if (E->get().name == p_member) { r_member_type = _type_from_property(E->get()); return true; } } base_type = _type_from_variant(scr.operator Variant()); native = scr->get_instance_base_type(); scr = scr->get_base_script(); } if (native == StringName()) { // Empty native class, might happen in some Script implementations // Just ignore it return false; } // Check ClassDB if (!ClassDB::class_exists(native)) { native = "_" + native.operator String(); } if (!ClassDB::class_exists(native)) { if (!check_types) return false; ERR_FAIL_V_MSG(false, "Parser bug: Class \"" + String(native) + "\" not found."); } bool valid = false; ClassDB::get_integer_constant(native, p_member, &valid); if (valid) { DataType ct; ct.has_type = true; ct.is_constant = true; ct.kind = DataType::BUILTIN; ct.builtin_type = Variant::INT; r_member_type = ct; return true; } if (!base_type.is_meta_type) { List<PropertyInfo> properties; ClassDB::get_property_list(native, &properties); for (List<PropertyInfo>::Element *E = properties.front(); E; E = E->next()) { if (E->get().name == p_member) { // Check if a getter exists StringName getter_name = ClassDB::get_property_getter(native, p_member); if (getter_name != StringName()) { // Use the getter return type #ifdef DEBUG_METHODS_ENABLED MethodBind *getter_method = ClassDB::get_method(native, getter_name); if (getter_method) { r_member_type = _type_from_property(getter_method->get_return_info()); } else { r_member_type = DataType(); } #else r_member_type = DataType(); #endif } else { r_member_type = _type_from_property(E->get()); } return true; } } } // If the base is a script, it might be trying to access members of the Script class itself if (p_base_type.is_meta_type && (p_base_type.kind == DataType::SCRIPT || p_base_type.kind == DataType::GDSCRIPT)) { native = p_base_type.script_type->get_class_name(); ClassDB::get_integer_constant(native, p_member, &valid); if (valid) { DataType ct; ct.has_type = true; ct.is_constant = true; ct.kind = DataType::BUILTIN; ct.builtin_type = Variant::INT; r_member_type = ct; return true; } List<PropertyInfo> properties; ClassDB::get_property_list(native, &properties); for (List<PropertyInfo>::Element *E = properties.front(); E; E = E->next()) { if (E->get().name == p_member) { // Check if a getter exists StringName getter_name = ClassDB::get_property_getter(native, p_member); if (getter_name != StringName()) { // Use the getter return type #ifdef DEBUG_METHODS_ENABLED MethodBind *getter_method = ClassDB::get_method(native, getter_name); if (getter_method) { r_member_type = _type_from_property(getter_method->get_return_info()); } else { r_member_type = DataType(); } #else r_member_type = DataType(); #endif } else { r_member_type = _type_from_property(E->get()); } return true; } } } return false; } GDScriptParser::DataType GDScriptParser::_reduce_identifier_type(const DataType *p_base_type, const StringName &p_identifier, int p_line, bool p_is_indexing) { if (p_base_type && !p_base_type->has_type) { return DataType(); } DataType base_type; DataType member_type; if (!p_base_type) { base_type.has_type = true; base_type.is_constant = true; base_type.kind = DataType::CLASS; base_type.class_type = current_class; } else { base_type = DataType(*p_base_type); } if (_get_member_type(base_type, p_identifier, member_type)) { return member_type; } if (p_is_indexing) { // Don't look for globals since this is an indexed identifier return DataType(); } if (!p_base_type) { // Possibly this is a global, check before failing if (ClassDB::class_exists(p_identifier) || ClassDB::class_exists("_" + p_identifier.operator String())) { DataType result; result.has_type = true; result.is_constant = true; result.is_meta_type = true; if (Engine::get_singleton()->has_singleton(p_identifier) || Engine::get_singleton()->has_singleton("_" + p_identifier.operator String())) { result.is_meta_type = false; } result.kind = DataType::NATIVE; result.native_type = p_identifier; return result; } ClassNode *outer_class = current_class; while (outer_class) { if (outer_class->name == p_identifier) { DataType result; result.has_type = true; result.is_constant = true; result.is_meta_type = true; result.kind = DataType::CLASS; result.class_type = outer_class; return result; } if (outer_class->constant_expressions.has(p_identifier)) { return outer_class->constant_expressions[p_identifier].type; } for (int i = 0; i < outer_class->subclasses.size(); i++) { if (outer_class->subclasses[i] == current_class) { continue; } if (outer_class->subclasses[i]->name == p_identifier) { DataType result; result.has_type = true; result.is_constant = true; result.is_meta_type = true; result.kind = DataType::CLASS; result.class_type = outer_class->subclasses[i]; return result; } } outer_class = outer_class->owner; } if (ScriptServer::is_global_class(p_identifier)) { Ref<Script> scr = ResourceLoader::load(ScriptServer::get_global_class_path(p_identifier)); if (scr.is_valid()) { DataType result; result.has_type = true; result.script_type = scr; result.is_constant = true; result.is_meta_type = true; Ref<GDScript> gds = scr; if (gds.is_valid()) { if (!gds->is_valid()) { _set_error("The class \"" + p_identifier + "\" couldn't be fully loaded (script error or cyclic dependency)."); return DataType(); } result.kind = DataType::GDSCRIPT; } else { result.kind = DataType::SCRIPT; } return result; } _set_error("The class \"" + p_identifier + "\" was found in global scope, but its script couldn't be loaded."); return DataType(); } if (GDScriptLanguage::get_singleton()->get_global_map().has(p_identifier)) { int idx = GDScriptLanguage::get_singleton()->get_global_map()[p_identifier]; Variant g = GDScriptLanguage::get_singleton()->get_global_array()[idx]; return _type_from_variant(g); } if (GDScriptLanguage::get_singleton()->get_named_globals_map().has(p_identifier)) { Variant g = GDScriptLanguage::get_singleton()->get_named_globals_map()[p_identifier]; return _type_from_variant(g); } // Non-tool singletons aren't loaded, check project settings List<PropertyInfo> props; ProjectSettings::get_singleton()->get_property_list(&props); for (List<PropertyInfo>::Element *E = props.front(); E; E = E->next()) { String s = E->get().name; if (!s.begins_with("autoload/")) { continue; } String name = s.get_slice("/", 1); if (name == p_identifier) { String script = ProjectSettings::get_singleton()->get(s); if (script.begins_with("*")) { script = script.right(1); } if (!script.begins_with("res://")) { script = "res://" + script; } Ref<Script> singleton = ResourceLoader::load(script); if (singleton.is_valid()) { DataType result; result.has_type = true; result.is_constant = true; result.script_type = singleton; Ref<GDScript> gds = singleton; if (gds.is_valid()) { if (!gds->is_valid()) { _set_error("Couldn't fully load the singleton script \"" + p_identifier + "\" (possible cyclic reference or parse error).", p_line); return DataType(); } result.kind = DataType::GDSCRIPT; } else { result.kind = DataType::SCRIPT; } } } } // This means looking in the current class, which type is always known _set_error("The identifier \"" + p_identifier.operator String() + "\" isn't declared in the current scope.", p_line); } #ifdef DEBUG_ENABLED { DataType tmp_type; List<DataType> arg_types; int argcount; bool _static; bool vararg; if (_get_function_signature(base_type, p_identifier, tmp_type, arg_types, argcount, _static, vararg)) { _add_warning(GDScriptWarning::FUNCTION_USED_AS_PROPERTY, p_line, p_identifier.operator String(), base_type.to_string()); } } #endif // DEBUG_ENABLED _mark_line_as_unsafe(p_line); return DataType(); } void GDScriptParser::_check_class_level_types(ClassNode *p_class) { // Names of internal object properties that we check to avoid overriding them. // "__meta__" could also be in here, but since it doesn't really affect object metadata, // it is okay to override it on script. StringName script_name = CoreStringNames::get_singleton()->_script; _mark_line_as_safe(p_class->line); // Constants for (Map<StringName, ClassNode::Constant>::Element *E = p_class->constant_expressions.front(); E; E = E->next()) { ClassNode::Constant &c = E->get(); _mark_line_as_safe(c.expression->line); DataType cont = _resolve_type(c.type, c.expression->line); DataType expr = _resolve_type(c.expression->get_datatype(), c.expression->line); if (check_types && !_is_type_compatible(cont, expr)) { _set_error("The constant value type (" + expr.to_string() + ") isn't compatible with declared type (" + cont.to_string() + ").", c.expression->line); return; } expr.is_constant = true; c.type = expr; c.expression->set_datatype(expr); DataType tmp; const StringName &constant_name = E->key(); if (constant_name == script_name || _get_member_type(p_class->base_type, constant_name, tmp)) { _set_error("The member \"" + String(constant_name) + "\" already exists in a parent class.", c.expression->line); return; } } // Function declarations for (int i = 0; i < p_class->static_functions.size(); i++) { _check_function_types(p_class->static_functions[i]); if (error_set) return; } for (int i = 0; i < p_class->functions.size(); i++) { _check_function_types(p_class->functions[i]); if (error_set) return; } // Class variables for (int i = 0; i < p_class->variables.size(); i++) { ClassNode::Member &v = p_class->variables.write[i]; DataType tmp; if (v.identifier == script_name || _get_member_type(p_class->base_type, v.identifier, tmp)) { _set_error("The member \"" + String(v.identifier) + "\" already exists in a parent class.", v.line); return; } _mark_line_as_safe(v.line); v.data_type = _resolve_type(v.data_type, v.line); if (v.expression) { DataType expr_type = _reduce_node_type(v.expression); if (check_types && !_is_type_compatible(v.data_type, expr_type)) { // Try supertype test if (_is_type_compatible(expr_type, v.data_type)) { _mark_line_as_unsafe(v.line); } else { // Try with implicit conversion if (v.data_type.kind != DataType::BUILTIN || !_is_type_compatible(v.data_type, expr_type, true)) { _set_error("The assigned expression's type (" + expr_type.to_string() + ") doesn't match the variable's type (" + v.data_type.to_string() + ").", v.line); return; } // Replace assignment with implicit conversion BuiltInFunctionNode *convert = alloc_node<BuiltInFunctionNode>(); convert->line = v.line; convert->function = GDScriptFunctions::TYPE_CONVERT; ConstantNode *tgt_type = alloc_node<ConstantNode>(); tgt_type->line = v.line; tgt_type->value = (int)v.data_type.builtin_type; OperatorNode *convert_call = alloc_node<OperatorNode>(); convert_call->line = v.line; convert_call->op = OperatorNode::OP_CALL; convert_call->arguments.push_back(convert); convert_call->arguments.push_back(v.expression); convert_call->arguments.push_back(tgt_type); v.expression = convert_call; v.initial_assignment->arguments.write[1] = convert_call; } } if (v.data_type.infer_type) { if (!expr_type.has_type) { _set_error("The assigned value doesn't have a set type; the variable type can't be inferred.", v.line); return; } v.data_type = expr_type; v.data_type.is_constant = false; } } // Check export hint if (v.data_type.has_type && v._export.type != Variant::NIL) { DataType export_type = _type_from_property(v._export); if (!_is_type_compatible(v.data_type, export_type, true)) { _set_error("The export hint's type (" + export_type.to_string() + ") doesn't match the variable's type (" + v.data_type.to_string() + ").", v.line); return; } } // Setter and getter if (v.setter == StringName() && v.getter == StringName()) continue; bool found_getter = false; bool found_setter = false; for (int j = 0; j < p_class->functions.size(); j++) { if (v.setter == p_class->functions[j]->name) { found_setter = true; FunctionNode *setter = p_class->functions[j]; if (setter->get_required_argument_count() != 1 && !(setter->get_required_argument_count() == 0 && setter->default_values.size() > 0)) { _set_error("The setter function needs to receive exactly 1 argument. See \"" + setter->name + "()\" definition at line " + itos(setter->line) + ".", v.line); return; } if (!_is_type_compatible(v.data_type, setter->argument_types[0])) { _set_error("The setter argument's type (" + setter->argument_types[0].to_string() + ") doesn't match the variable's type (" + v.data_type.to_string() + "). See \"" + setter->name + "()\" definition at line " + itos(setter->line) + ".", v.line); return; } continue; } if (v.getter == p_class->functions[j]->name) { found_getter = true; FunctionNode *getter = p_class->functions[j]; if (getter->get_required_argument_count() != 0) { _set_error("The getter function can't receive arguments. See \"" + getter->name + "()\" definition at line " + itos(getter->line) + ".", v.line); return; } if (!_is_type_compatible(v.data_type, getter->get_datatype())) { _set_error("The getter return type (" + getter->get_datatype().to_string() + ") doesn't match the variable's type (" + v.data_type.to_string() + "). See \"" + getter->name + "()\" definition at line " + itos(getter->line) + ".", v.line); return; } } if (found_getter && found_setter) break; } if ((found_getter || v.getter == StringName()) && (found_setter || v.setter == StringName())) continue; // Check for static functions for (int j = 0; j < p_class->static_functions.size(); j++) { if (v.setter == p_class->static_functions[j]->name) { FunctionNode *setter = p_class->static_functions[j]; _set_error("The setter can't be a static function. See \"" + setter->name + "()\" definition at line " + itos(setter->line) + ".", v.line); return; } if (v.getter == p_class->static_functions[j]->name) { FunctionNode *getter = p_class->static_functions[j]; _set_error("The getter can't be a static function. See \"" + getter->name + "()\" definition at line " + itos(getter->line) + ".", v.line); return; } } if (!found_setter && v.setter != StringName()) { _set_error("The setter function isn't defined.", v.line); return; } if (!found_getter && v.getter != StringName()) { _set_error("The getter function isn't defined.", v.line); return; } } // Inner classes for (int i = 0; i < p_class->subclasses.size(); i++) { current_class = p_class->subclasses[i]; _check_class_level_types(current_class); if (error_set) return; current_class = p_class; } } void GDScriptParser::_check_function_types(FunctionNode *p_function) { p_function->return_type = _resolve_type(p_function->return_type, p_function->line); // Arguments int defaults_ofs = p_function->arguments.size() - p_function->default_values.size(); for (int i = 0; i < p_function->arguments.size(); i++) { if (i < defaults_ofs) { p_function->argument_types.write[i] = _resolve_type(p_function->argument_types[i], p_function->line); } else { if (p_function->default_values[i - defaults_ofs]->type != Node::TYPE_OPERATOR) { _set_error("Parser bug: invalid argument default value.", p_function->line, p_function->column); return; } OperatorNode *op = static_cast<OperatorNode *>(p_function->default_values[i - defaults_ofs]); if (op->op != OperatorNode::OP_ASSIGN || op->arguments.size() != 2) { _set_error("Parser bug: invalid argument default value operation.", p_function->line); return; } DataType def_type = _reduce_node_type(op->arguments[1]); if (p_function->argument_types[i].infer_type) { def_type.is_constant = false; p_function->argument_types.write[i] = def_type; } else { p_function->argument_types.write[i] = _resolve_type(p_function->argument_types[i], p_function->line); if (!_is_type_compatible(p_function->argument_types[i], def_type, true)) { String arg_name = p_function->arguments[i]; _set_error("Value type (" + def_type.to_string() + ") doesn't match the type of argument '" + arg_name + "' (" + p_function->argument_types[i].to_string() + ").", p_function->line); } } } #ifdef DEBUG_ENABLED if (p_function->arguments_usage[i] == 0 && !p_function->arguments[i].operator String().begins_with("_")) { _add_warning(GDScriptWarning::UNUSED_ARGUMENT, p_function->line, p_function->name, p_function->arguments[i].operator String()); } for (int j = 0; j < current_class->variables.size(); j++) { if (current_class->variables[j].identifier == p_function->arguments[i]) { _add_warning(GDScriptWarning::SHADOWED_VARIABLE, p_function->line, p_function->arguments[i], itos(current_class->variables[j].line)); } } #endif // DEBUG_ENABLED } if (!(p_function->name == "_init")) { // Signature for the initializer may vary #ifdef DEBUG_ENABLED DataType return_type; List<DataType> arg_types; int default_arg_count = 0; bool _static = false; bool vararg = false; DataType base_type = current_class->base_type; if (_get_function_signature(base_type, p_function->name, return_type, arg_types, default_arg_count, _static, vararg)) { bool valid = _static == p_function->_static; valid = valid && return_type == p_function->return_type; int argsize_diff = p_function->arguments.size() - arg_types.size(); valid = valid && argsize_diff >= 0; valid = valid && p_function->default_values.size() >= default_arg_count + argsize_diff; int i = 0; for (List<DataType>::Element *E = arg_types.front(); valid && E; E = E->next()) { valid = valid && E->get() == p_function->argument_types[i++]; } if (!valid) { String parent_signature = return_type.has_type ? return_type.to_string() : "Variant"; if (parent_signature == "null") { parent_signature = "void"; } parent_signature += " " + p_function->name + "("; if (arg_types.size()) { int j = 0; for (List<DataType>::Element *E = arg_types.front(); E; E = E->next()) { if (E != arg_types.front()) { parent_signature += ", "; } String arg = E->get().to_string(); if (arg == "null" || arg == "var") { arg = "Variant"; } parent_signature += arg; if (j == arg_types.size() - default_arg_count) { parent_signature += "=default"; } j++; } } parent_signature += ")"; _set_error("The function signature doesn't match the parent. Parent signature is: \"" + parent_signature + "\".", p_function->line); return; } } #endif // DEBUG_ENABLED } else { if (p_function->return_type.has_type && (p_function->return_type.kind != DataType::BUILTIN || p_function->return_type.builtin_type != Variant::NIL)) { _set_error("The constructor can't return a value.", p_function->line); return; } } if (p_function->return_type.has_type && (p_function->return_type.kind != DataType::BUILTIN || p_function->return_type.builtin_type != Variant::NIL)) { if (!p_function->body->has_return) { _set_error("A non-void function must return a value in all possible paths.", p_function->line); return; } } if (p_function->has_yield) { // yield() will make the function return a GDScriptFunctionState, so the type is ambiguous p_function->return_type.has_type = false; p_function->return_type.may_yield = true; } #ifdef DEBUG_ENABLED for (Map<StringName, LocalVarNode *>::Element *E = p_function->body->variables.front(); E; E = E->next()) { LocalVarNode *lv = E->get(); for (int i = 0; i < current_class->variables.size(); i++) { if (current_class->variables[i].identifier == lv->name) { _add_warning(GDScriptWarning::SHADOWED_VARIABLE, lv->line, lv->name, itos(current_class->variables[i].line)); } } } #endif // DEBUG_ENABLED } void GDScriptParser::_check_class_blocks_types(ClassNode *p_class) { // Function blocks for (int i = 0; i < p_class->static_functions.size(); i++) { current_function = p_class->static_functions[i]; current_block = current_function->body; _mark_line_as_safe(current_function->line); _check_block_types(current_block); current_block = NULL; current_function = NULL; if (error_set) return; } for (int i = 0; i < p_class->functions.size(); i++) { current_function = p_class->functions[i]; current_block = current_function->body; _mark_line_as_safe(current_function->line); _check_block_types(current_block); current_block = NULL; current_function = NULL; if (error_set) return; } #ifdef DEBUG_ENABLED // Warnings for (int i = 0; i < p_class->variables.size(); i++) { if (p_class->variables[i].usages == 0) { _add_warning(GDScriptWarning::UNUSED_CLASS_VARIABLE, p_class->variables[i].line, p_class->variables[i].identifier); } } for (int i = 0; i < p_class->_signals.size(); i++) { if (p_class->_signals[i].emissions == 0) { _add_warning(GDScriptWarning::UNUSED_SIGNAL, p_class->_signals[i].line, p_class->_signals[i].name); } } #endif // DEBUG_ENABLED // Inner classes for (int i = 0; i < p_class->subclasses.size(); i++) { current_class = p_class->subclasses[i]; _check_class_blocks_types(current_class); if (error_set) return; current_class = p_class; } } #ifdef DEBUG_ENABLED static String _find_function_name(const GDScriptParser::OperatorNode *p_call) { switch (p_call->arguments[0]->type) { case GDScriptParser::Node::TYPE_TYPE: { return Variant::get_type_name(static_cast<GDScriptParser::TypeNode *>(p_call->arguments[0])->vtype); } break; case GDScriptParser::Node::TYPE_BUILT_IN_FUNCTION: { return GDScriptFunctions::get_func_name(static_cast<GDScriptParser::BuiltInFunctionNode *>(p_call->arguments[0])->function); } break; default: { int id_index = p_call->op == GDScriptParser::OperatorNode::OP_PARENT_CALL ? 0 : 1; if (p_call->arguments.size() > id_index && p_call->arguments[id_index]->type == GDScriptParser::Node::TYPE_IDENTIFIER) { return static_cast<GDScriptParser::IdentifierNode *>(p_call->arguments[id_index])->name; } } break; } return String(); } #endif // DEBUG_ENABLED void GDScriptParser::_check_block_types(BlockNode *p_block) { Node *last_var_assign = NULL; // Check each statement for (List<Node *>::Element *E = p_block->statements.front(); E; E = E->next()) { Node *statement = E->get(); switch (statement->type) { case Node::TYPE_NEWLINE: case Node::TYPE_BREAKPOINT: case Node::TYPE_ASSERT: { // Nothing to do } break; case Node::TYPE_LOCAL_VAR: { LocalVarNode *lv = static_cast<LocalVarNode *>(statement); lv->datatype = _resolve_type(lv->datatype, lv->line); _mark_line_as_safe(lv->line); last_var_assign = lv->assign; if (lv->assign) { lv->assign_op->arguments[0]->set_datatype(lv->datatype); DataType assign_type = _reduce_node_type(lv->assign); #ifdef DEBUG_ENABLED if (assign_type.has_type && assign_type.kind == DataType::BUILTIN && assign_type.builtin_type == Variant::NIL) { if (lv->assign->type == Node::TYPE_OPERATOR) { OperatorNode *call = static_cast<OperatorNode *>(lv->assign); if (call->op == OperatorNode::OP_CALL || call->op == OperatorNode::OP_PARENT_CALL) { _add_warning(GDScriptWarning::VOID_ASSIGNMENT, lv->line, _find_function_name(call)); } } } if (lv->datatype.has_type && assign_type.may_yield && lv->assign->type == Node::TYPE_OPERATOR) { _add_warning(GDScriptWarning::FUNCTION_MAY_YIELD, lv->line, _find_function_name(static_cast<OperatorNode *>(lv->assign))); } #endif // DEBUG_ENABLED if (!_is_type_compatible(lv->datatype, assign_type)) { // Try supertype test if (_is_type_compatible(assign_type, lv->datatype)) { _mark_line_as_unsafe(lv->line); } else { // Try implicit conversion if (lv->datatype.kind != DataType::BUILTIN || !_is_type_compatible(lv->datatype, assign_type, true)) { _set_error("The assigned value type (" + assign_type.to_string() + ") doesn't match the variable's type (" + lv->datatype.to_string() + ").", lv->line); return; } // Replace assignment with implicit conversion BuiltInFunctionNode *convert = alloc_node<BuiltInFunctionNode>(); convert->line = lv->line; convert->function = GDScriptFunctions::TYPE_CONVERT; ConstantNode *tgt_type = alloc_node<ConstantNode>(); tgt_type->line = lv->line; tgt_type->value = (int)lv->datatype.builtin_type; OperatorNode *convert_call = alloc_node<OperatorNode>(); convert_call->line = lv->line; convert_call->op = OperatorNode::OP_CALL; convert_call->arguments.push_back(convert); convert_call->arguments.push_back(lv->assign); convert_call->arguments.push_back(tgt_type); lv->assign = convert_call; lv->assign_op->arguments.write[1] = convert_call; #ifdef DEBUG_ENABLED if (lv->datatype.builtin_type == Variant::INT && assign_type.builtin_type == Variant::REAL) { _add_warning(GDScriptWarning::NARROWING_CONVERSION, lv->line); } #endif // DEBUG_ENABLED } } if (lv->datatype.infer_type) { if (!assign_type.has_type) { _set_error("The assigned value doesn't have a set type; the variable type can't be inferred.", lv->line); return; } lv->datatype = assign_type; lv->datatype.is_constant = false; } if (lv->datatype.has_type && !assign_type.has_type) { _mark_line_as_unsafe(lv->line); } } } break; case Node::TYPE_OPERATOR: { OperatorNode *op = static_cast<OperatorNode *>(statement); switch (op->op) { case OperatorNode::OP_ASSIGN: case OperatorNode::OP_ASSIGN_ADD: case OperatorNode::OP_ASSIGN_SUB: case OperatorNode::OP_ASSIGN_MUL: case OperatorNode::OP_ASSIGN_DIV: case OperatorNode::OP_ASSIGN_MOD: case OperatorNode::OP_ASSIGN_SHIFT_LEFT: case OperatorNode::OP_ASSIGN_SHIFT_RIGHT: case OperatorNode::OP_ASSIGN_BIT_AND: case OperatorNode::OP_ASSIGN_BIT_OR: case OperatorNode::OP_ASSIGN_BIT_XOR: { if (op->arguments.size() < 2) { _set_error("Parser bug: operation without enough arguments.", op->line, op->column); return; } if (op->arguments[1] == last_var_assign) { // Assignment was already checked break; } _mark_line_as_safe(op->line); DataType lh_type = _reduce_node_type(op->arguments[0]); if (error_set) { return; } if (check_types) { if (!lh_type.has_type) { if (op->arguments[0]->type == Node::TYPE_OPERATOR) { _mark_line_as_unsafe(op->line); } } if (lh_type.is_constant) { _set_error("Can't assign a new value to a constant.", op->line); return; } } DataType rh_type; if (op->op != OperatorNode::OP_ASSIGN) { // Validate operation DataType arg_type = _reduce_node_type(op->arguments[1]); if (!arg_type.has_type) { _mark_line_as_unsafe(op->line); break; } Variant::Operator oper = _get_variant_operation(op->op); bool valid = false; rh_type = _get_operation_type(oper, lh_type, arg_type, valid); if (check_types && !valid) { _set_error("Invalid operand types (\"" + lh_type.to_string() + "\" and \"" + arg_type.to_string() + "\") to assignment operator \"" + Variant::get_operator_name(oper) + "\".", op->line); return; } } else { rh_type = _reduce_node_type(op->arguments[1]); } #ifdef DEBUG_ENABLED if (rh_type.has_type && rh_type.kind == DataType::BUILTIN && rh_type.builtin_type == Variant::NIL) { if (op->arguments[1]->type == Node::TYPE_OPERATOR) { OperatorNode *call = static_cast<OperatorNode *>(op->arguments[1]); if (call->op == OperatorNode::OP_CALL || call->op == OperatorNode::OP_PARENT_CALL) { _add_warning(GDScriptWarning::VOID_ASSIGNMENT, op->line, _find_function_name(call)); } } } if (lh_type.has_type && rh_type.may_yield && op->arguments[1]->type == Node::TYPE_OPERATOR) { _add_warning(GDScriptWarning::FUNCTION_MAY_YIELD, op->line, _find_function_name(static_cast<OperatorNode *>(op->arguments[1]))); } #endif // DEBUG_ENABLED bool type_match = lh_type.has_type && rh_type.has_type; if (check_types && !_is_type_compatible(lh_type, rh_type)) { type_match = false; // Try supertype test if (_is_type_compatible(rh_type, lh_type)) { _mark_line_as_unsafe(op->line); } else { // Try implicit conversion if (lh_type.kind != DataType::BUILTIN || !_is_type_compatible(lh_type, rh_type, true)) { _set_error("The assigned value's type (" + rh_type.to_string() + ") doesn't match the variable's type (" + lh_type.to_string() + ").", op->line); return; } if (op->op == OperatorNode::OP_ASSIGN) { // Replace assignment with implicit conversion BuiltInFunctionNode *convert = alloc_node<BuiltInFunctionNode>(); convert->line = op->line; convert->function = GDScriptFunctions::TYPE_CONVERT; ConstantNode *tgt_type = alloc_node<ConstantNode>(); tgt_type->line = op->line; tgt_type->value = (int)lh_type.builtin_type; OperatorNode *convert_call = alloc_node<OperatorNode>(); convert_call->line = op->line; convert_call->op = OperatorNode::OP_CALL; convert_call->arguments.push_back(convert); convert_call->arguments.push_back(op->arguments[1]); convert_call->arguments.push_back(tgt_type); op->arguments.write[1] = convert_call; type_match = true; // Since we are converting, the type is matching } #ifdef DEBUG_ENABLED if (lh_type.builtin_type == Variant::INT && rh_type.builtin_type == Variant::REAL) { _add_warning(GDScriptWarning::NARROWING_CONVERSION, op->line); } #endif // DEBUG_ENABLED } } #ifdef DEBUG_ENABLED if (!rh_type.has_type && (op->op != OperatorNode::OP_ASSIGN || lh_type.has_type || op->arguments[0]->type == Node::TYPE_OPERATOR)) { _mark_line_as_unsafe(op->line); } #endif // DEBUG_ENABLED op->datatype.has_type = type_match; } break; case OperatorNode::OP_CALL: case OperatorNode::OP_PARENT_CALL: { _mark_line_as_safe(op->line); DataType func_type = _reduce_function_call_type(op); #ifdef DEBUG_ENABLED if (func_type.has_type && (func_type.kind != DataType::BUILTIN || func_type.builtin_type != Variant::NIL)) { // Figure out function name for warning String func_name = _find_function_name(op); if (func_name.empty()) { func_name = "<undetected name>"; } _add_warning(GDScriptWarning::RETURN_VALUE_DISCARDED, op->line, func_name); } #endif // DEBUG_ENABLED if (error_set) return; } break; case OperatorNode::OP_YIELD: { _mark_line_as_safe(op->line); _reduce_node_type(op); } break; default: { _mark_line_as_safe(op->line); _reduce_node_type(op); // Test for safety anyway #ifdef DEBUG_ENABLED if (op->op == OperatorNode::OP_TERNARY_IF) { _add_warning(GDScriptWarning::STANDALONE_TERNARY, statement->line); } else { _add_warning(GDScriptWarning::STANDALONE_EXPRESSION, statement->line); } #endif // DEBUG_ENABLED } } } break; case Node::TYPE_CONTROL_FLOW: { ControlFlowNode *cf = static_cast<ControlFlowNode *>(statement); _mark_line_as_safe(cf->line); switch (cf->cf_type) { case ControlFlowNode::CF_RETURN: { DataType function_type = current_function->get_datatype(); DataType ret_type; if (cf->arguments.size() > 0) { ret_type = _reduce_node_type(cf->arguments[0]); if (error_set) { return; } } if (!function_type.has_type) break; if (function_type.kind == DataType::BUILTIN && function_type.builtin_type == Variant::NIL) { // Return void, should not have arguments if (cf->arguments.size() > 0) { _set_error("A void function cannot return a value.", cf->line, cf->column); return; } } else { // Return something, cannot be empty if (cf->arguments.size() == 0) { _set_error("A non-void function must return a value.", cf->line, cf->column); return; } if (!_is_type_compatible(function_type, ret_type)) { _set_error("The returned value type (" + ret_type.to_string() + ") doesn't match the function return type (" + function_type.to_string() + ").", cf->line, cf->column); return; } } } break; case ControlFlowNode::CF_MATCH: { MatchNode *match_node = cf->match; _transform_match_statment(match_node); } break; default: { if (cf->body_else) { _mark_line_as_safe(cf->body_else->line); } for (int i = 0; i < cf->arguments.size(); i++) { _reduce_node_type(cf->arguments[i]); } } break; } } break; case Node::TYPE_CONSTANT: { ConstantNode *cn = static_cast<ConstantNode *>(statement); // Strings are fine since they can be multiline comments if (cn->value.get_type() == Variant::STRING) { break; } FALLTHROUGH; } default: { _mark_line_as_safe(statement->line); _reduce_node_type(statement); // Test for safety anyway #ifdef DEBUG_ENABLED _add_warning(GDScriptWarning::STANDALONE_EXPRESSION, statement->line); #endif // DEBUG_ENABLED } } } // Parse sub blocks for (int i = 0; i < p_block->sub_blocks.size(); i++) { current_block = p_block->sub_blocks[i]; _check_block_types(current_block); current_block = p_block; if (error_set) return; } #ifdef DEBUG_ENABLED // Warnings check for (Map<StringName, LocalVarNode *>::Element *E = p_block->variables.front(); E; E = E->next()) { LocalVarNode *lv = E->get(); if (!lv->name.operator String().begins_with("_")) { if (lv->usages == 0) { _add_warning(GDScriptWarning::UNUSED_VARIABLE, lv->line, lv->name); } else if (lv->assignments == 0) { _add_warning(GDScriptWarning::UNASSIGNED_VARIABLE, lv->line, lv->name); } } } #endif // DEBUG_ENABLED } void GDScriptParser::_set_error(const String &p_error, int p_line, int p_column) { if (error_set) return; //allow no further errors error = p_error; error_line = p_line < 0 ? tokenizer->get_token_line() : p_line; error_column = p_column < 0 ? tokenizer->get_token_column() : p_column; error_set = true; } #ifdef DEBUG_ENABLED void GDScriptParser::_add_warning(int p_code, int p_line, const String &p_symbol1, const String &p_symbol2, const String &p_symbol3, const String &p_symbol4) { Vector<String> symbols; if (!p_symbol1.empty()) { symbols.push_back(p_symbol1); } if (!p_symbol2.empty()) { symbols.push_back(p_symbol2); } if (!p_symbol3.empty()) { symbols.push_back(p_symbol3); } if (!p_symbol4.empty()) { symbols.push_back(p_symbol4); } _add_warning(p_code, p_line, symbols); } void GDScriptParser::_add_warning(int p_code, int p_line, const Vector<String> &p_symbols) { if (GLOBAL_GET("debug/gdscript/warnings/exclude_addons").booleanize() && base_path.begins_with("res://addons/")) { return; } if (tokenizer->is_ignoring_warnings() || !GLOBAL_GET("debug/gdscript/warnings/enable").booleanize()) { return; } String warn_name = GDScriptWarning::get_name_from_code((GDScriptWarning::Code)p_code).to_lower(); if (tokenizer->get_warning_global_skips().has(warn_name)) { return; } if (!GLOBAL_GET("debug/gdscript/warnings/" + warn_name)) { return; } GDScriptWarning warn; warn.code = (GDScriptWarning::Code)p_code; warn.symbols = p_symbols; warn.line = p_line == -1 ? tokenizer->get_token_line() : p_line; List<GDScriptWarning>::Element *before = NULL; for (List<GDScriptWarning>::Element *E = warnings.front(); E; E = E->next()) { if (E->get().line > warn.line) { break; } before = E; } if (before) { warnings.insert_after(before, warn); } else { warnings.push_front(warn); } } #endif // DEBUG_ENABLED String GDScriptParser::get_error() const { return error; } int GDScriptParser::get_error_line() const { return error_line; } int GDScriptParser::get_error_column() const { return error_column; } bool GDScriptParser::has_error() const { return error_set; } Error GDScriptParser::_parse(const String &p_base_path) { base_path = p_base_path; //assume class ClassNode *main_class = alloc_node<ClassNode>(); main_class->initializer = alloc_node<BlockNode>(); main_class->initializer->parent_class = main_class; main_class->ready = alloc_node<BlockNode>(); main_class->ready->parent_class = main_class; current_class = main_class; _parse_class(main_class); if (tokenizer->get_token() == GDScriptTokenizer::TK_ERROR) { error_set = false; _set_error("Parse error: " + tokenizer->get_token_error()); } if (error_set && !for_completion) { return ERR_PARSE_ERROR; } if (dependencies_only) { return OK; } _determine_inheritance(main_class); if (error_set) { return ERR_PARSE_ERROR; } current_class = main_class; current_function = NULL; current_block = NULL; if (for_completion) check_types = false; // Resolve all class-level stuff before getting into function blocks _check_class_level_types(main_class); if (error_set) { return ERR_PARSE_ERROR; } // Resolve the function blocks _check_class_blocks_types(main_class); if (error_set) { return ERR_PARSE_ERROR; } #ifdef DEBUG_ENABLED // Resolve warning ignores Vector<Pair<int, String> > warning_skips = tokenizer->get_warning_skips(); bool warning_is_error = GLOBAL_GET("debug/gdscript/warnings/treat_warnings_as_errors").booleanize(); for (List<GDScriptWarning>::Element *E = warnings.front(); E;) { GDScriptWarning &w = E->get(); int skip_index = -1; for (int i = 0; i < warning_skips.size(); i++) { if (warning_skips[i].first >= w.line) { break; } skip_index = i; } List<GDScriptWarning>::Element *next = E->next(); bool erase = false; if (skip_index != -1) { if (warning_skips[skip_index].second == GDScriptWarning::get_name_from_code(w.code).to_lower()) { erase = true; } warning_skips.remove(skip_index); } if (erase) { warnings.erase(E); } else if (warning_is_error) { _set_error(w.get_message() + " (warning treated as error)", w.line); return ERR_PARSE_ERROR; } E = next; } #endif // DEBUG_ENABLED return OK; } Error GDScriptParser::parse_bytecode(const Vector<uint8_t> &p_bytecode, const String &p_base_path, const String &p_self_path) { clear(); self_path = p_self_path; GDScriptTokenizerBuffer *tb = memnew(GDScriptTokenizerBuffer); tb->set_code_buffer(p_bytecode); tokenizer = tb; Error ret = _parse(p_base_path); memdelete(tb); tokenizer = NULL; return ret; } Error GDScriptParser::parse(const String &p_code, const String &p_base_path, bool p_just_validate, const String &p_self_path, bool p_for_completion, Set<int> *r_safe_lines, bool p_dependencies_only) { clear(); self_path = p_self_path; GDScriptTokenizerText *tt = memnew(GDScriptTokenizerText); tt->set_code(p_code); validating = p_just_validate; for_completion = p_for_completion; dependencies_only = p_dependencies_only; #ifdef DEBUG_ENABLED safe_lines = r_safe_lines; #endif // DEBUG_ENABLED tokenizer = tt; Error ret = _parse(p_base_path); memdelete(tt); tokenizer = NULL; return ret; } bool GDScriptParser::is_tool_script() const { return (head && head->type == Node::TYPE_CLASS && static_cast<const ClassNode *>(head)->tool); } const GDScriptParser::Node *GDScriptParser::get_parse_tree() const { return head; } void GDScriptParser::clear() { while (list) { Node *l = list; list = list->next; memdelete(l); } head = NULL; list = NULL; completion_type = COMPLETION_NONE; completion_node = NULL; completion_class = NULL; completion_function = NULL; completion_block = NULL; current_block = NULL; current_class = NULL; completion_found = false; rpc_mode = MultiplayerAPI::RPC_MODE_DISABLED; current_function = NULL; validating = false; for_completion = false; error_set = false; indent_level.clear(); indent_level.push_back(IndentLevel(0, 0)); error_line = 0; error_column = 0; pending_newline = -1; parenthesis = 0; current_export.type = Variant::NIL; check_types = true; dependencies_only = false; dependencies.clear(); error = ""; #ifdef DEBUG_ENABLED safe_lines = NULL; #endif // DEBUG_ENABLED } GDScriptParser::CompletionType GDScriptParser::get_completion_type() { return completion_type; } StringName GDScriptParser::get_completion_cursor() { return completion_cursor; } int GDScriptParser::get_completion_line() { return completion_line; } Variant::Type GDScriptParser::get_completion_built_in_constant() { return completion_built_in_constant; } GDScriptParser::Node *GDScriptParser::get_completion_node() { return completion_node; } GDScriptParser::BlockNode *GDScriptParser::get_completion_block() { return completion_block; } GDScriptParser::ClassNode *GDScriptParser::get_completion_class() { return completion_class; } GDScriptParser::FunctionNode *GDScriptParser::get_completion_function() { return completion_function; } int GDScriptParser::get_completion_argument_index() { return completion_argument; } int GDScriptParser::get_completion_identifier_is_function() { return completion_ident_is_call; } GDScriptParser::GDScriptParser() { head = NULL; list = NULL; tokenizer = NULL; pending_newline = -1; clear(); } GDScriptParser::~GDScriptParser() { clear(); }