/*************************************************************************/ /* gdscript_compiler.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_compiler.h" #include "gdscript.h" #include "gdscript_cache.h" bool GDScriptCompiler::_is_class_member_property(CodeGen &codegen, const StringName &p_name) { if (codegen.function_node && codegen.function_node->is_static) { return false; } if (codegen.stack_identifiers.has(p_name)) { return false; //shadowed } return _is_class_member_property(codegen.script, p_name); } bool GDScriptCompiler::_is_class_member_property(GDScript *owner, const StringName &p_name) { GDScript *scr = owner; GDScriptNativeClass *nc = nullptr; while (scr) { if (scr->native.is_valid()) { nc = scr->native.ptr(); } scr = scr->_base; } ERR_FAIL_COND_V(!nc, false); return ClassDB::has_property(nc->get_name(), p_name); } void GDScriptCompiler::_set_error(const String &p_error, const GDScriptParser::Node *p_node) { if (error != "") { return; } error = p_error; if (p_node) { err_line = p_node->start_line; err_column = p_node->leftmost_column; } else { err_line = 0; err_column = 0; } } bool GDScriptCompiler::_create_unary_operator(CodeGen &codegen, const GDScriptParser::UnaryOpNode *on, Variant::Operator op, int p_stack_level) { int src_address_a = _parse_expression(codegen, on->operand, p_stack_level); if (src_address_a < 0) { return false; } codegen.opcodes.push_back(GDScriptFunction::OPCODE_OPERATOR); // perform operator codegen.opcodes.push_back(op); //which operator codegen.opcodes.push_back(src_address_a); // argument 1 codegen.opcodes.push_back(src_address_a); // argument 2 (repeated) //codegen.opcodes.push_back(GDScriptFunction::ADDR_TYPE_NIL); // argument 2 (unary only takes one parameter) return true; } bool GDScriptCompiler::_create_binary_operator(CodeGen &codegen, const GDScriptParser::ExpressionNode *p_left_operand, const GDScriptParser::ExpressionNode *p_right_operand, Variant::Operator op, int p_stack_level, bool p_initializer, int p_index_addr) { int src_address_a = _parse_expression(codegen, p_left_operand, p_stack_level, false, p_initializer, p_index_addr); if (src_address_a < 0) { return false; } if (src_address_a & GDScriptFunction::ADDR_TYPE_STACK << GDScriptFunction::ADDR_BITS) { p_stack_level++; //uses stack for return, increase stack } int src_address_b = _parse_expression(codegen, p_right_operand, p_stack_level, false, p_initializer); if (src_address_b < 0) { return false; } codegen.opcodes.push_back(GDScriptFunction::OPCODE_OPERATOR); // perform operator codegen.opcodes.push_back(op); //which operator codegen.opcodes.push_back(src_address_a); // argument 1 codegen.opcodes.push_back(src_address_b); // argument 2 (unary only takes one parameter) return true; } bool GDScriptCompiler::_create_binary_operator(CodeGen &codegen, const GDScriptParser::BinaryOpNode *on, Variant::Operator op, int p_stack_level, bool p_initializer, int p_index_addr) { return _create_binary_operator(codegen, on->left_operand, on->right_operand, op, p_stack_level, p_initializer, p_index_addr); } GDScriptDataType GDScriptCompiler::_gdtype_from_datatype(const GDScriptParser::DataType &p_datatype) const { if (!p_datatype.is_set() || !p_datatype.is_hard_type()) { return GDScriptDataType(); } GDScriptDataType result; result.has_type = true; switch (p_datatype.kind) { case GDScriptParser::DataType::VARIANT: { result.has_type = false; } break; case GDScriptParser::DataType::BUILTIN: { result.kind = GDScriptDataType::BUILTIN; result.builtin_type = p_datatype.builtin_type; } break; case GDScriptParser::DataType::NATIVE: { result.kind = GDScriptDataType::NATIVE; result.native_type = p_datatype.native_type; } break; case GDScriptParser::DataType::SCRIPT: { result.kind = GDScriptDataType::SCRIPT; result.script_type = p_datatype.script_type; result.native_type = result.script_type->get_instance_base_type(); } break; case GDScriptParser::DataType::CLASS: { // Locate class by constructing the path to it and following that path GDScriptParser::ClassNode *class_type = p_datatype.class_type; if (class_type) { if (class_type->fqcn.begins_with(main_script->path) || (!main_script->name.empty() && class_type->fqcn.begins_with(main_script->name))) { // Local class. List names; while (class_type->outer) { names.push_back(class_type->identifier->name); class_type = class_type->outer; } Ref script = Ref(main_script); while (names.back()) { if (!script->subclasses.has(names.back()->get())) { ERR_PRINT("Parser bug: Cannot locate datatype class."); result.has_type = false; return GDScriptDataType(); } script = script->subclasses[names.back()->get()]; names.pop_back(); } result.kind = GDScriptDataType::GDSCRIPT; result.script_type = script; result.native_type = script->get_instance_base_type(); } else { result.kind = GDScriptDataType::GDSCRIPT; result.script_type = GDScriptCache::get_shallow_script(p_datatype.script_path, main_script->path); result.native_type = p_datatype.native_type; } } } break; case GDScriptParser::DataType::ENUM_VALUE: result.has_type = true; result.kind = GDScriptDataType::BUILTIN; result.builtin_type = Variant::INT; break; case GDScriptParser::DataType::ENUM: result.has_type = true; result.kind = GDScriptDataType::BUILTIN; result.builtin_type = Variant::DICTIONARY; break; case GDScriptParser::DataType::UNRESOLVED: { ERR_PRINT("Parser bug: converting unresolved type."); return GDScriptDataType(); } } return result; } int GDScriptCompiler::_parse_assign_right_expression(CodeGen &codegen, const GDScriptParser::AssignmentNode *p_assignment, int p_stack_level, int p_index_addr) { Variant::Operator var_op = Variant::OP_MAX; switch (p_assignment->operation) { case GDScriptParser::AssignmentNode::OP_ADDITION: var_op = Variant::OP_ADD; break; case GDScriptParser::AssignmentNode::OP_SUBTRACTION: var_op = Variant::OP_SUBTRACT; break; case GDScriptParser::AssignmentNode::OP_MULTIPLICATION: var_op = Variant::OP_MULTIPLY; break; case GDScriptParser::AssignmentNode::OP_DIVISION: var_op = Variant::OP_DIVIDE; break; case GDScriptParser::AssignmentNode::OP_MODULO: var_op = Variant::OP_MODULE; break; case GDScriptParser::AssignmentNode::OP_BIT_SHIFT_LEFT: var_op = Variant::OP_SHIFT_LEFT; break; case GDScriptParser::AssignmentNode::OP_BIT_SHIFT_RIGHT: var_op = Variant::OP_SHIFT_RIGHT; break; case GDScriptParser::AssignmentNode::OP_BIT_AND: var_op = Variant::OP_BIT_AND; break; case GDScriptParser::AssignmentNode::OP_BIT_OR: var_op = Variant::OP_BIT_OR; break; case GDScriptParser::AssignmentNode::OP_BIT_XOR: var_op = Variant::OP_BIT_XOR; break; case GDScriptParser::AssignmentNode::OP_NONE: { //none } break; default: { ERR_FAIL_V(-1); } } // bool initializer = p_expression->op == GDScriptParser::OperatorNode::OP_INIT_ASSIGN; if (var_op == Variant::OP_MAX) { return _parse_expression(codegen, p_assignment->assigned_value, p_stack_level, false, false); } if (!_create_binary_operator(codegen, p_assignment->assignee, p_assignment->assigned_value, var_op, p_stack_level, false, p_index_addr)) { return -1; } int dst_addr = (p_stack_level) | (GDScriptFunction::ADDR_TYPE_STACK << GDScriptFunction::ADDR_BITS); codegen.opcodes.push_back(dst_addr); // append the stack level as destination address of the opcode codegen.alloc_stack(p_stack_level); return dst_addr; } bool GDScriptCompiler::_generate_typed_assign(CodeGen &codegen, int p_src_address, int p_dst_address, const GDScriptDataType &p_datatype, const GDScriptParser::DataType &p_value_type) { if (p_datatype.has_type && p_value_type.is_variant()) { // Typed assignment switch (p_datatype.kind) { case GDScriptDataType::BUILTIN: { codegen.opcodes.push_back(GDScriptFunction::OPCODE_ASSIGN_TYPED_BUILTIN); // perform operator codegen.opcodes.push_back(p_datatype.builtin_type); // variable type codegen.opcodes.push_back(p_dst_address); // argument 1 codegen.opcodes.push_back(p_src_address); // argument 2 } break; case GDScriptDataType::NATIVE: { int class_idx; if (GDScriptLanguage::get_singleton()->get_global_map().has(p_datatype.native_type)) { class_idx = GDScriptLanguage::get_singleton()->get_global_map()[p_datatype.native_type]; class_idx |= (GDScriptFunction::ADDR_TYPE_GLOBAL << GDScriptFunction::ADDR_BITS); //argument (stack root) } else { // _set_error("Invalid native class type '" + String(p_datatype.native_type) + "'.", on->arguments[0]); return false; } codegen.opcodes.push_back(GDScriptFunction::OPCODE_ASSIGN_TYPED_NATIVE); // perform operator codegen.opcodes.push_back(class_idx); // variable type codegen.opcodes.push_back(p_dst_address); // argument 1 codegen.opcodes.push_back(p_src_address); // argument 2 } break; case GDScriptDataType::SCRIPT: case GDScriptDataType::GDSCRIPT: { Variant script = p_datatype.script_type; int idx = codegen.get_constant_pos(script); //make it a local constant (faster access) codegen.opcodes.push_back(GDScriptFunction::OPCODE_ASSIGN_TYPED_SCRIPT); // perform operator codegen.opcodes.push_back(idx); // variable type codegen.opcodes.push_back(p_dst_address); // argument 1 codegen.opcodes.push_back(p_src_address); // argument 2 } break; default: { ERR_PRINT("Compiler bug: unresolved assign."); // Shouldn't get here, but fail-safe to a regular assignment codegen.opcodes.push_back(GDScriptFunction::OPCODE_ASSIGN); // perform operator codegen.opcodes.push_back(p_dst_address); // argument 1 codegen.opcodes.push_back(p_src_address); // argument 2 (unary only takes one parameter) } } } else { if (p_datatype.kind == GDScriptDataType::BUILTIN && p_value_type.kind == GDScriptParser::DataType::BUILTIN && p_datatype.builtin_type != p_value_type.builtin_type) { // Need conversion. codegen.opcodes.push_back(GDScriptFunction::OPCODE_ASSIGN_TYPED_BUILTIN); // perform operator codegen.opcodes.push_back(p_datatype.builtin_type); // variable type codegen.opcodes.push_back(p_dst_address); // argument 1 codegen.opcodes.push_back(p_src_address); // argument 2 } else { // Either untyped assignment or already type-checked by the parser codegen.opcodes.push_back(GDScriptFunction::OPCODE_ASSIGN); // perform operator codegen.opcodes.push_back(p_dst_address); // argument 1 codegen.opcodes.push_back(p_src_address); // argument 2 (unary only takes one parameter) } } return true; } int GDScriptCompiler::_parse_expression(CodeGen &codegen, const GDScriptParser::ExpressionNode *p_expression, int p_stack_level, bool p_root, bool p_initializer, int p_index_addr) { if (p_expression->is_constant) { return codegen.get_constant_pos(p_expression->reduced_value); } switch (p_expression->type) { //should parse variable declaration and adjust stack accordingly... case GDScriptParser::Node::IDENTIFIER: { //return identifier //wait, identifier could be a local variable or something else... careful here, must reference properly //as stack may be more interesting to work with //This could be made much simpler by just indexing "self", but done this way (with custom self-addressing modes) increases performance a lot. const GDScriptParser::IdentifierNode *in = static_cast(p_expression); StringName identifier = in->name; // TRY STACK! if (!p_initializer && codegen.stack_identifiers.has(identifier)) { int pos = codegen.stack_identifiers[identifier]; return pos | (GDScriptFunction::ADDR_TYPE_STACK_VARIABLE << GDScriptFunction::ADDR_BITS); } // TRY LOCAL CONSTANTS! if (codegen.local_named_constants.has(identifier)) { return codegen.local_named_constants[identifier] | (GDScriptFunction::ADDR_TYPE_LOCAL_CONSTANT << GDScriptFunction::ADDR_BITS); } // TRY CLASS MEMBER if (_is_class_member_property(codegen, identifier)) { //get property codegen.opcodes.push_back(GDScriptFunction::OPCODE_GET_MEMBER); // perform operator codegen.opcodes.push_back(codegen.get_name_map_pos(identifier)); // argument 2 (unary only takes one parameter) int dst_addr = (p_stack_level) | (GDScriptFunction::ADDR_TYPE_STACK << GDScriptFunction::ADDR_BITS); codegen.opcodes.push_back(dst_addr); // append the stack level as destination address of the opcode codegen.alloc_stack(p_stack_level); return dst_addr; } //TRY MEMBERS! if (!codegen.function_node || !codegen.function_node->is_static) { // TRY MEMBER VARIABLES! //static function if (codegen.script->member_indices.has(identifier)) { if (codegen.script->member_indices[identifier].getter != StringName() && codegen.script->member_indices[identifier].getter != codegen.function_name) { // Perform getter. codegen.opcodes.push_back(GDScriptFunction::OPCODE_CALL_RETURN); codegen.opcodes.push_back(0); // Argument count. codegen.opcodes.push_back(GDScriptFunction::ADDR_TYPE_SELF << GDScriptFunction::ADDR_BITS); // Base (self). codegen.opcodes.push_back(codegen.get_name_map_pos(codegen.script->member_indices[identifier].getter)); // Method name. // Destination. int dst_addr = (p_stack_level) | (GDScriptFunction::ADDR_TYPE_STACK << GDScriptFunction::ADDR_BITS); codegen.opcodes.push_back(dst_addr); // append the stack level as destination address of the opcode codegen.alloc_stack(p_stack_level); return dst_addr; } else { // No getter or inside getter: direct member access. int idx = codegen.script->member_indices[identifier].index; return idx | (GDScriptFunction::ADDR_TYPE_MEMBER << GDScriptFunction::ADDR_BITS); //argument (stack root) } } } //TRY CLASS CONSTANTS GDScript *owner = codegen.script; while (owner) { GDScript *scr = owner; GDScriptNativeClass *nc = nullptr; while (scr) { if (scr->constants.has(identifier)) { //int idx=scr->constants[identifier]; int idx = codegen.get_name_map_pos(identifier); return idx | (GDScriptFunction::ADDR_TYPE_CLASS_CONSTANT << GDScriptFunction::ADDR_BITS); //argument (stack root) } if (scr->native.is_valid()) { nc = scr->native.ptr(); } scr = scr->_base; } // CLASS C++ Integer Constant if (nc) { bool success = false; int constant = ClassDB::get_integer_constant(nc->get_name(), identifier, &success); if (success) { Variant key = constant; int idx; if (!codegen.constant_map.has(key)) { idx = codegen.constant_map.size(); codegen.constant_map[key] = idx; } else { idx = codegen.constant_map[key]; } return idx | (GDScriptFunction::ADDR_TYPE_LOCAL_CONSTANT << GDScriptFunction::ADDR_BITS); //make it a local constant (faster access) } } owner = owner->_owner; } // TRY SIGNALS AND METHODS (can be made callables) if (codegen.class_node->members_indices.has(identifier)) { const GDScriptParser::ClassNode::Member &member = codegen.class_node->members[codegen.class_node->members_indices[identifier]]; if (member.type == GDScriptParser::ClassNode::Member::FUNCTION || member.type == GDScriptParser::ClassNode::Member::SIGNAL) { // Get like it was a property. codegen.opcodes.push_back(GDScriptFunction::OPCODE_GET_NAMED); // perform operator codegen.opcodes.push_back(GDScriptFunction::ADDR_TYPE_SELF << GDScriptFunction::ADDR_BITS); // Self. codegen.opcodes.push_back(codegen.get_name_map_pos(identifier)); // argument 2 (unary only takes one parameter) int dst_addr = (p_stack_level) | (GDScriptFunction::ADDR_TYPE_STACK << GDScriptFunction::ADDR_BITS); codegen.opcodes.push_back(dst_addr); // append the stack level as destination address of the opcode codegen.alloc_stack(p_stack_level); return dst_addr; } } if (GDScriptLanguage::get_singleton()->get_global_map().has(identifier)) { int idx = GDScriptLanguage::get_singleton()->get_global_map()[identifier]; return idx | (GDScriptFunction::ADDR_TYPE_GLOBAL << GDScriptFunction::ADDR_BITS); //argument (stack root) } /* TRY GLOBAL CLASSES */ if (ScriptServer::is_global_class(identifier)) { const GDScriptParser::ClassNode *class_node = codegen.class_node; while (class_node->outer) { class_node = class_node->outer; } RES res; if (class_node->identifier && class_node->identifier->name == identifier) { res = Ref(main_script); } else { res = ResourceLoader::load(ScriptServer::get_global_class_path(identifier)); if (res.is_null()) { _set_error("Can't load global class " + String(identifier) + ", cyclic reference?", p_expression); return -1; } } Variant key = res; int idx; if (!codegen.constant_map.has(key)) { idx = codegen.constant_map.size(); codegen.constant_map[key] = idx; } else { idx = codegen.constant_map[key]; } return idx | (GDScriptFunction::ADDR_TYPE_LOCAL_CONSTANT << GDScriptFunction::ADDR_BITS); //make it a local constant (faster access) } #ifdef TOOLS_ENABLED if (GDScriptLanguage::get_singleton()->get_named_globals_map().has(identifier)) { int idx = codegen.named_globals.find(identifier); if (idx == -1) { idx = codegen.named_globals.size(); codegen.named_globals.push_back(identifier); } return idx | (GDScriptFunction::ADDR_TYPE_NAMED_GLOBAL << GDScriptFunction::ADDR_BITS); } #endif //not found, error _set_error("Identifier not found: " + String(identifier), p_expression); return -1; } break; case GDScriptParser::Node::LITERAL: { //return constant const GDScriptParser::LiteralNode *cn = static_cast(p_expression); int idx; if (!codegen.constant_map.has(cn->value)) { idx = codegen.constant_map.size(); codegen.constant_map[cn->value] = idx; } else { idx = codegen.constant_map[cn->value]; } return idx | (GDScriptFunction::ADDR_TYPE_LOCAL_CONSTANT << GDScriptFunction::ADDR_BITS); //argument (stack root) } break; case GDScriptParser::Node::SELF: { //return constant if (codegen.function_node && codegen.function_node->is_static) { _set_error("'self' not present in static function!", p_expression); return -1; } return (GDScriptFunction::ADDR_TYPE_SELF << GDScriptFunction::ADDR_BITS); } break; case GDScriptParser::Node::ARRAY: { const GDScriptParser::ArrayNode *an = static_cast(p_expression); Vector values; int slevel = p_stack_level; for (int i = 0; i < an->elements.size(); i++) { int ret = _parse_expression(codegen, an->elements[i], slevel); if (ret < 0) { return ret; } if ((ret >> GDScriptFunction::ADDR_BITS & GDScriptFunction::ADDR_TYPE_STACK) == GDScriptFunction::ADDR_TYPE_STACK) { slevel++; codegen.alloc_stack(slevel); } values.push_back(ret); } codegen.opcodes.push_back(GDScriptFunction::OPCODE_CONSTRUCT_ARRAY); codegen.opcodes.push_back(values.size()); for (int i = 0; i < values.size(); i++) { codegen.opcodes.push_back(values[i]); } int dst_addr = (p_stack_level) | (GDScriptFunction::ADDR_TYPE_STACK << GDScriptFunction::ADDR_BITS); codegen.opcodes.push_back(dst_addr); // append the stack level as destination address of the opcode codegen.alloc_stack(p_stack_level); return dst_addr; } break; case GDScriptParser::Node::DICTIONARY: { const GDScriptParser::DictionaryNode *dn = static_cast(p_expression); Vector elements; int slevel = p_stack_level; for (int i = 0; i < dn->elements.size(); i++) { // Key. int ret = -1; switch (dn->style) { case GDScriptParser::DictionaryNode::PYTHON_DICT: // Python-style: key is any expression. ret = _parse_expression(codegen, dn->elements[i].key, slevel); if (ret < 0) { return ret; } if ((ret >> GDScriptFunction::ADDR_BITS & GDScriptFunction::ADDR_TYPE_STACK) == GDScriptFunction::ADDR_TYPE_STACK) { slevel++; codegen.alloc_stack(slevel); } break; case GDScriptParser::DictionaryNode::LUA_TABLE: // Lua-style: key is an identifier interpreted as string. String key = static_cast(dn->elements[i].key)->name; ret = codegen.get_constant_pos(key); break; } elements.push_back(ret); ret = _parse_expression(codegen, dn->elements[i].value, slevel); if (ret < 0) { return ret; } if ((ret >> GDScriptFunction::ADDR_BITS & GDScriptFunction::ADDR_TYPE_STACK) == GDScriptFunction::ADDR_TYPE_STACK) { slevel++; codegen.alloc_stack(slevel); } elements.push_back(ret); } codegen.opcodes.push_back(GDScriptFunction::OPCODE_CONSTRUCT_DICTIONARY); codegen.opcodes.push_back(dn->elements.size()); for (int i = 0; i < elements.size(); i++) { codegen.opcodes.push_back(elements[i]); } int dst_addr = (p_stack_level) | (GDScriptFunction::ADDR_TYPE_STACK << GDScriptFunction::ADDR_BITS); codegen.opcodes.push_back(dst_addr); // append the stack level as destination address of the opcode codegen.alloc_stack(p_stack_level); return dst_addr; } break; case GDScriptParser::Node::CAST: { const GDScriptParser::CastNode *cn = static_cast(p_expression); int slevel = p_stack_level; int src_addr = _parse_expression(codegen, cn->operand, slevel); if (src_addr < 0) { return src_addr; } if (src_addr & GDScriptFunction::ADDR_TYPE_STACK << GDScriptFunction::ADDR_BITS) { slevel++; codegen.alloc_stack(slevel); } GDScriptDataType cast_type = _gdtype_from_datatype(cn->cast_type->get_datatype()); switch (cast_type.kind) { case GDScriptDataType::BUILTIN: { codegen.opcodes.push_back(GDScriptFunction::OPCODE_CAST_TO_BUILTIN); codegen.opcodes.push_back(cast_type.builtin_type); } break; case GDScriptDataType::NATIVE: { int class_idx; if (GDScriptLanguage::get_singleton()->get_global_map().has(cast_type.native_type)) { class_idx = GDScriptLanguage::get_singleton()->get_global_map()[cast_type.native_type]; class_idx |= (GDScriptFunction::ADDR_TYPE_GLOBAL << GDScriptFunction::ADDR_BITS); //argument (stack root) } else { _set_error("Invalid native class type '" + String(cast_type.native_type) + "'.", cn); return -1; } codegen.opcodes.push_back(GDScriptFunction::OPCODE_CAST_TO_NATIVE); // perform operator codegen.opcodes.push_back(class_idx); // variable type } break; case GDScriptDataType::SCRIPT: case GDScriptDataType::GDSCRIPT: { Variant script = cast_type.script_type; int idx = codegen.get_constant_pos(script); //make it a local constant (faster access) codegen.opcodes.push_back(GDScriptFunction::OPCODE_CAST_TO_SCRIPT); // perform operator codegen.opcodes.push_back(idx); // variable type } break; default: { _set_error("Parser bug: unresolved data type.", cn); return -1; } } codegen.opcodes.push_back(src_addr); // source address int dst_addr = (p_stack_level) | (GDScriptFunction::ADDR_TYPE_STACK << GDScriptFunction::ADDR_BITS); codegen.opcodes.push_back(dst_addr); // append the stack level as destination address of the opcode codegen.alloc_stack(p_stack_level); return dst_addr; } break; //hell breaks loose #define OPERATOR_RETURN \ int dst_addr = (p_stack_level) | (GDScriptFunction::ADDR_TYPE_STACK << GDScriptFunction::ADDR_BITS); \ codegen.opcodes.push_back(dst_addr); \ codegen.alloc_stack(p_stack_level); \ return dst_addr case GDScriptParser::Node::CALL: { const GDScriptParser::CallNode *call = static_cast(p_expression); if (!call->is_super && call->callee->type == GDScriptParser::Node::IDENTIFIER && GDScriptParser::get_builtin_type(static_cast(call->callee)->name) != Variant::VARIANT_MAX) { //construct a basic type Variant::Type vtype = GDScriptParser::get_builtin_type(static_cast(call->callee)->name); Vector arguments; int slevel = p_stack_level; for (int i = 0; i < call->arguments.size(); i++) { int ret = _parse_expression(codegen, call->arguments[i], slevel); if (ret < 0) { return ret; } if ((ret >> GDScriptFunction::ADDR_BITS & GDScriptFunction::ADDR_TYPE_STACK) == GDScriptFunction::ADDR_TYPE_STACK) { slevel++; codegen.alloc_stack(slevel); } arguments.push_back(ret); } //push call bytecode codegen.opcodes.push_back(GDScriptFunction::OPCODE_CONSTRUCT); // basic type constructor codegen.opcodes.push_back(vtype); //instance codegen.opcodes.push_back(arguments.size()); //argument count codegen.alloc_call(arguments.size()); for (int i = 0; i < arguments.size(); i++) { codegen.opcodes.push_back(arguments[i]); //arguments } } else if (!call->is_super && call->callee->type == GDScriptParser::Node::IDENTIFIER && GDScriptParser::get_builtin_function(static_cast(call->callee)->name) != GDScriptFunctions::FUNC_MAX) { //built in function Vector arguments; int slevel = p_stack_level; for (int i = 0; i < call->arguments.size(); i++) { int ret = _parse_expression(codegen, call->arguments[i], slevel); if (ret < 0) { return ret; } if ((ret >> GDScriptFunction::ADDR_BITS & GDScriptFunction::ADDR_TYPE_STACK) == GDScriptFunction::ADDR_TYPE_STACK) { slevel++; codegen.alloc_stack(slevel); } arguments.push_back(ret); } codegen.opcodes.push_back(GDScriptFunction::OPCODE_CALL_BUILT_IN); codegen.opcodes.push_back(GDScriptParser::get_builtin_function(static_cast(call->callee)->name)); codegen.opcodes.push_back(arguments.size()); codegen.alloc_call(arguments.size()); for (int i = 0; i < arguments.size(); i++) { codegen.opcodes.push_back(arguments[i]); } } else { //regular function const GDScriptParser::ExpressionNode *callee = call->callee; Vector arguments; int slevel = p_stack_level; // TODO: Use callables when possible if needed. int ret = -1; int super_address = -1; if (call->is_super) { // Super call. if (call->callee == nullptr) { // Implicit super function call. super_address = codegen.get_name_map_pos(codegen.function_node->identifier->name); } else { super_address = codegen.get_name_map_pos(static_cast(call->callee)->name); } } else { if (callee->type == GDScriptParser::Node::IDENTIFIER) { // Self function call. if (codegen.function_node && codegen.function_node->is_static) { ret = (GDScriptFunction::ADDR_TYPE_CLASS << GDScriptFunction::ADDR_BITS); } else { ret = (GDScriptFunction::ADDR_TYPE_SELF << GDScriptFunction::ADDR_BITS); } arguments.push_back(ret); ret = codegen.get_name_map_pos(static_cast(call->callee)->name); arguments.push_back(ret); } else if (callee->type == GDScriptParser::Node::SUBSCRIPT) { const GDScriptParser::SubscriptNode *subscript = static_cast(call->callee); if (subscript->is_attribute) { ret = _parse_expression(codegen, subscript->base, slevel); if (ret < 0) { return ret; } if ((ret >> GDScriptFunction::ADDR_BITS & GDScriptFunction::ADDR_TYPE_STACK) == GDScriptFunction::ADDR_TYPE_STACK) { slevel++; codegen.alloc_stack(slevel); } arguments.push_back(ret); arguments.push_back(codegen.get_name_map_pos(subscript->attribute->name)); } else { _set_error("Cannot call something that isn't a function.", call->callee); return -1; } } else { _set_error("Cannot call something that isn't a function.", call->callee); return -1; } } for (int i = 0; i < call->arguments.size(); i++) { ret = _parse_expression(codegen, call->arguments[i], slevel); if (ret < 0) { return ret; } if ((ret >> GDScriptFunction::ADDR_BITS & GDScriptFunction::ADDR_TYPE_STACK) == GDScriptFunction::ADDR_TYPE_STACK) { slevel++; codegen.alloc_stack(slevel); } arguments.push_back(ret); } int opcode = GDScriptFunction::OPCODE_CALL_RETURN; if (call->is_super) { opcode = GDScriptFunction::OPCODE_CALL_SELF_BASE; } else if (within_await) { opcode = GDScriptFunction::OPCODE_CALL_ASYNC; } else if (p_root) { opcode = GDScriptFunction::OPCODE_CALL; } codegen.opcodes.push_back(opcode); // perform operator if (call->is_super) { codegen.opcodes.push_back(super_address); } codegen.opcodes.push_back(call->arguments.size()); codegen.alloc_call(call->arguments.size()); for (int i = 0; i < arguments.size(); i++) { codegen.opcodes.push_back(arguments[i]); } } OPERATOR_RETURN; } break; case GDScriptParser::Node::GET_NODE: { const GDScriptParser::GetNodeNode *get_node = static_cast(p_expression); String node_name; if (get_node->string != nullptr) { node_name += String(get_node->string->value); } else { for (int i = 0; i < get_node->chain.size(); i++) { if (i > 0) { node_name += "/"; } node_name += get_node->chain[i]->name; } } int arg_address = codegen.get_constant_pos(NodePath(node_name)); codegen.opcodes.push_back(p_root ? GDScriptFunction::OPCODE_CALL : GDScriptFunction::OPCODE_CALL_RETURN); codegen.opcodes.push_back(1); // number of arguments. codegen.alloc_call(1); codegen.opcodes.push_back(GDScriptFunction::ADDR_TYPE_SELF << GDScriptFunction::ADDR_BITS); // self. codegen.opcodes.push_back(codegen.get_name_map_pos("get_node")); // function. codegen.opcodes.push_back(arg_address); // argument (NodePath). OPERATOR_RETURN; } break; case GDScriptParser::Node::PRELOAD: { const GDScriptParser::PreloadNode *preload = static_cast(p_expression); // Add resource as constant. return codegen.get_constant_pos(preload->resource); } break; case GDScriptParser::Node::AWAIT: { const GDScriptParser::AwaitNode *await = static_cast(p_expression); int slevel = p_stack_level; within_await = true; int argument = _parse_expression(codegen, await->to_await, slevel); within_await = false; if (argument < 0) { return argument; } if ((argument >> GDScriptFunction::ADDR_BITS & GDScriptFunction::ADDR_TYPE_STACK) == GDScriptFunction::ADDR_TYPE_STACK) { slevel++; codegen.alloc_stack(slevel); } //push call bytecode codegen.opcodes.push_back(GDScriptFunction::OPCODE_AWAIT); codegen.opcodes.push_back(argument); codegen.opcodes.push_back(GDScriptFunction::OPCODE_AWAIT_RESUME); //next will be where to place the result :) OPERATOR_RETURN; } break; //indexing operator case GDScriptParser::Node::SUBSCRIPT: { int slevel = p_stack_level; const GDScriptParser::SubscriptNode *subscript = static_cast(p_expression); int from = _parse_expression(codegen, subscript->base, slevel); if (from < 0) { return from; } bool named = subscript->is_attribute; int index; if (p_index_addr != 0) { index = p_index_addr; } else if (subscript->is_attribute) { if (subscript->base->type == GDScriptParser::Node::SELF && codegen.script) { GDScriptParser::IdentifierNode *identifier = subscript->attribute; const Map::Element *MI = codegen.script->member_indices.find(identifier->name); #ifdef DEBUG_ENABLED if (MI && MI->get().getter == codegen.function_name) { String n = identifier->name; _set_error("Must use '" + n + "' instead of 'self." + n + "' in getter.", identifier); return -1; } #endif if (MI && MI->get().getter == "") { // Faster than indexing self (as if no self. had been used) return (MI->get().index) | (GDScriptFunction::ADDR_TYPE_MEMBER << GDScriptFunction::ADDR_BITS); } } index = codegen.get_name_map_pos(subscript->attribute->name); } else { if (subscript->index->type == GDScriptParser::Node::LITERAL && static_cast(subscript->index)->value.get_type() == Variant::STRING) { //also, somehow, named (speed up anyway) StringName name = static_cast(subscript->index)->value; index = codegen.get_name_map_pos(name); named = true; } else { //regular indexing if (from & GDScriptFunction::ADDR_TYPE_STACK << GDScriptFunction::ADDR_BITS) { slevel++; codegen.alloc_stack(slevel); } index = _parse_expression(codegen, subscript->index, slevel); if (index < 0) { return index; } } } codegen.opcodes.push_back(named ? GDScriptFunction::OPCODE_GET_NAMED : GDScriptFunction::OPCODE_GET); // perform operator codegen.opcodes.push_back(from); // argument 1 codegen.opcodes.push_back(index); // argument 2 (unary only takes one parameter) OPERATOR_RETURN; } break; case GDScriptParser::Node::UNARY_OPERATOR: { //unary operators const GDScriptParser::UnaryOpNode *unary = static_cast(p_expression); switch (unary->operation) { case GDScriptParser::UnaryOpNode::OP_NEGATIVE: { if (!_create_unary_operator(codegen, unary, Variant::OP_NEGATE, p_stack_level)) { return -1; } } break; case GDScriptParser::UnaryOpNode::OP_POSITIVE: { if (!_create_unary_operator(codegen, unary, Variant::OP_POSITIVE, p_stack_level)) { return -1; } } break; case GDScriptParser::UnaryOpNode::OP_LOGIC_NOT: { if (!_create_unary_operator(codegen, unary, Variant::OP_NOT, p_stack_level)) { return -1; } } break; case GDScriptParser::UnaryOpNode::OP_COMPLEMENT: { if (!_create_unary_operator(codegen, unary, Variant::OP_BIT_NEGATE, p_stack_level)) { return -1; } } break; } OPERATOR_RETURN; } case GDScriptParser::Node::BINARY_OPERATOR: { //binary operators (in precedence order) const GDScriptParser::BinaryOpNode *binary = static_cast(p_expression); switch (binary->operation) { case GDScriptParser::BinaryOpNode::OP_LOGIC_AND: { // AND operator with early out on failure int res = _parse_expression(codegen, binary->left_operand, p_stack_level); if (res < 0) { return res; } codegen.opcodes.push_back(GDScriptFunction::OPCODE_JUMP_IF_NOT); codegen.opcodes.push_back(res); int jump_fail_pos = codegen.opcodes.size(); codegen.opcodes.push_back(0); res = _parse_expression(codegen, binary->right_operand, p_stack_level); if (res < 0) { return res; } codegen.opcodes.push_back(GDScriptFunction::OPCODE_JUMP_IF_NOT); codegen.opcodes.push_back(res); int jump_fail_pos2 = codegen.opcodes.size(); codegen.opcodes.push_back(0); codegen.alloc_stack(p_stack_level); //it will be used.. codegen.opcodes.push_back(GDScriptFunction::OPCODE_ASSIGN_TRUE); codegen.opcodes.push_back(p_stack_level | GDScriptFunction::ADDR_TYPE_STACK << GDScriptFunction::ADDR_BITS); codegen.opcodes.push_back(GDScriptFunction::OPCODE_JUMP); codegen.opcodes.push_back(codegen.opcodes.size() + 3); codegen.opcodes.write[jump_fail_pos] = codegen.opcodes.size(); codegen.opcodes.write[jump_fail_pos2] = codegen.opcodes.size(); codegen.opcodes.push_back(GDScriptFunction::OPCODE_ASSIGN_FALSE); codegen.opcodes.push_back(p_stack_level | GDScriptFunction::ADDR_TYPE_STACK << GDScriptFunction::ADDR_BITS); return p_stack_level | GDScriptFunction::ADDR_TYPE_STACK << GDScriptFunction::ADDR_BITS; } break; case GDScriptParser::BinaryOpNode::OP_LOGIC_OR: { // OR operator with early out on success int res = _parse_expression(codegen, binary->left_operand, p_stack_level); if (res < 0) { return res; } codegen.opcodes.push_back(GDScriptFunction::OPCODE_JUMP_IF); codegen.opcodes.push_back(res); int jump_success_pos = codegen.opcodes.size(); codegen.opcodes.push_back(0); res = _parse_expression(codegen, binary->right_operand, p_stack_level); if (res < 0) { return res; } codegen.opcodes.push_back(GDScriptFunction::OPCODE_JUMP_IF); codegen.opcodes.push_back(res); int jump_success_pos2 = codegen.opcodes.size(); codegen.opcodes.push_back(0); codegen.alloc_stack(p_stack_level); //it will be used.. codegen.opcodes.push_back(GDScriptFunction::OPCODE_ASSIGN_FALSE); codegen.opcodes.push_back(p_stack_level | GDScriptFunction::ADDR_TYPE_STACK << GDScriptFunction::ADDR_BITS); codegen.opcodes.push_back(GDScriptFunction::OPCODE_JUMP); codegen.opcodes.push_back(codegen.opcodes.size() + 3); codegen.opcodes.write[jump_success_pos] = codegen.opcodes.size(); codegen.opcodes.write[jump_success_pos2] = codegen.opcodes.size(); codegen.opcodes.push_back(GDScriptFunction::OPCODE_ASSIGN_TRUE); codegen.opcodes.push_back(p_stack_level | GDScriptFunction::ADDR_TYPE_STACK << GDScriptFunction::ADDR_BITS); return p_stack_level | GDScriptFunction::ADDR_TYPE_STACK << GDScriptFunction::ADDR_BITS; } break; case GDScriptParser::BinaryOpNode::OP_TYPE_TEST: { int slevel = p_stack_level; int src_address_a = _parse_expression(codegen, binary->left_operand, slevel); if (src_address_a < 0) { return -1; } if (src_address_a & GDScriptFunction::ADDR_TYPE_STACK << GDScriptFunction::ADDR_BITS) { slevel++; //uses stack for return, increase stack } int src_address_b = -1; bool builtin = false; if (binary->right_operand->type == GDScriptParser::Node::IDENTIFIER && GDScriptParser::get_builtin_type(static_cast(binary->right_operand)->name) != Variant::VARIANT_MAX) { // `is` with builtin type builtin = true; src_address_b = (int)GDScriptParser::get_builtin_type(static_cast(binary->right_operand)->name); } else { src_address_b = _parse_expression(codegen, binary->right_operand, slevel); if (src_address_b < 0) { return -1; } } codegen.opcodes.push_back(builtin ? GDScriptFunction::OPCODE_IS_BUILTIN : GDScriptFunction::OPCODE_EXTENDS_TEST); // perform operator codegen.opcodes.push_back(src_address_a); // argument 1 codegen.opcodes.push_back(src_address_b); // argument 2 (unary only takes one parameter) } break; case GDScriptParser::BinaryOpNode::OP_CONTENT_TEST: { if (!_create_binary_operator(codegen, binary, Variant::OP_IN, p_stack_level)) { return -1; } } break; case GDScriptParser::BinaryOpNode::OP_COMP_EQUAL: { if (!_create_binary_operator(codegen, binary, Variant::OP_EQUAL, p_stack_level)) { return -1; } } break; case GDScriptParser::BinaryOpNode::OP_COMP_NOT_EQUAL: { if (!_create_binary_operator(codegen, binary, Variant::OP_NOT_EQUAL, p_stack_level)) { return -1; } } break; case GDScriptParser::BinaryOpNode::OP_COMP_LESS: { if (!_create_binary_operator(codegen, binary, Variant::OP_LESS, p_stack_level)) { return -1; } } break; case GDScriptParser::BinaryOpNode::OP_COMP_LESS_EQUAL: { if (!_create_binary_operator(codegen, binary, Variant::OP_LESS_EQUAL, p_stack_level)) { return -1; } } break; case GDScriptParser::BinaryOpNode::OP_COMP_GREATER: { if (!_create_binary_operator(codegen, binary, Variant::OP_GREATER, p_stack_level)) { return -1; } } break; case GDScriptParser::BinaryOpNode::OP_COMP_GREATER_EQUAL: { if (!_create_binary_operator(codegen, binary, Variant::OP_GREATER_EQUAL, p_stack_level)) { return -1; } } break; case GDScriptParser::BinaryOpNode::OP_ADDITION: { if (!_create_binary_operator(codegen, binary, Variant::OP_ADD, p_stack_level)) { return -1; } } break; case GDScriptParser::BinaryOpNode::OP_SUBTRACTION: { if (!_create_binary_operator(codegen, binary, Variant::OP_SUBTRACT, p_stack_level)) { return -1; } } break; case GDScriptParser::BinaryOpNode::OP_MULTIPLICATION: { if (!_create_binary_operator(codegen, binary, Variant::OP_MULTIPLY, p_stack_level)) { return -1; } } break; case GDScriptParser::BinaryOpNode::OP_DIVISION: { if (!_create_binary_operator(codegen, binary, Variant::OP_DIVIDE, p_stack_level)) { return -1; } } break; case GDScriptParser::BinaryOpNode::OP_MODULO: { if (!_create_binary_operator(codegen, binary, Variant::OP_MODULE, p_stack_level)) { return -1; } } break; case GDScriptParser::BinaryOpNode::OP_BIT_AND: { if (!_create_binary_operator(codegen, binary, Variant::OP_BIT_AND, p_stack_level)) { return -1; } } break; case GDScriptParser::BinaryOpNode::OP_BIT_OR: { if (!_create_binary_operator(codegen, binary, Variant::OP_BIT_OR, p_stack_level)) { return -1; } } break; case GDScriptParser::BinaryOpNode::OP_BIT_XOR: { if (!_create_binary_operator(codegen, binary, Variant::OP_BIT_XOR, p_stack_level)) { return -1; } } break; //shift case GDScriptParser::BinaryOpNode::OP_BIT_LEFT_SHIFT: { if (!_create_binary_operator(codegen, binary, Variant::OP_SHIFT_LEFT, p_stack_level)) { return -1; } } break; case GDScriptParser::BinaryOpNode::OP_BIT_RIGHT_SHIFT: { if (!_create_binary_operator(codegen, binary, Variant::OP_SHIFT_RIGHT, p_stack_level)) { return -1; } } break; } OPERATOR_RETURN; } break; // ternary operators case GDScriptParser::Node::TERNARY_OPERATOR: { // x IF a ELSE y operator with early out on failure const GDScriptParser::TernaryOpNode *ternary = static_cast(p_expression); int res = _parse_expression(codegen, ternary->condition, p_stack_level); if (res < 0) { return res; } codegen.opcodes.push_back(GDScriptFunction::OPCODE_JUMP_IF_NOT); codegen.opcodes.push_back(res); int jump_fail_pos = codegen.opcodes.size(); codegen.opcodes.push_back(0); res = _parse_expression(codegen, ternary->true_expr, p_stack_level); if (res < 0) { return res; } codegen.alloc_stack(p_stack_level); //it will be used.. codegen.opcodes.push_back(GDScriptFunction::OPCODE_ASSIGN); codegen.opcodes.push_back(p_stack_level | GDScriptFunction::ADDR_TYPE_STACK << GDScriptFunction::ADDR_BITS); codegen.opcodes.push_back(res); codegen.opcodes.push_back(GDScriptFunction::OPCODE_JUMP); int jump_past_pos = codegen.opcodes.size(); codegen.opcodes.push_back(0); codegen.opcodes.write[jump_fail_pos] = codegen.opcodes.size(); res = _parse_expression(codegen, ternary->false_expr, p_stack_level); if (res < 0) { return res; } codegen.opcodes.push_back(GDScriptFunction::OPCODE_ASSIGN); codegen.opcodes.push_back(p_stack_level | GDScriptFunction::ADDR_TYPE_STACK << GDScriptFunction::ADDR_BITS); codegen.opcodes.push_back(res); codegen.opcodes.write[jump_past_pos] = codegen.opcodes.size(); return p_stack_level | GDScriptFunction::ADDR_TYPE_STACK << GDScriptFunction::ADDR_BITS; } break; //assignment operators case GDScriptParser::Node::ASSIGNMENT: { const GDScriptParser::AssignmentNode *assignment = static_cast(p_expression); if (assignment->assignee->type == GDScriptParser::Node::SUBSCRIPT) { // SET (chained) MODE! const GDScriptParser::SubscriptNode *subscript = static_cast(assignment->assignee); #ifdef DEBUG_ENABLED if (subscript->is_attribute) { if (subscript->base->type == GDScriptParser::Node::SELF && codegen.script) { const Map::Element *MI = codegen.script->member_indices.find(subscript->attribute->name); if (MI && MI->get().setter == codegen.function_name) { String n = subscript->attribute->name; _set_error("Must use '" + n + "' instead of 'self." + n + "' in setter.", subscript); return -1; } } } #endif int slevel = p_stack_level; /* Find chain of sets */ StringName assign_property; List chain; { //create get/set chain const GDScriptParser::SubscriptNode *n = subscript; while (true) { chain.push_back(n); if (n->base->type != GDScriptParser::Node::SUBSCRIPT) { //check for a built-in property if (n->base->type == GDScriptParser::Node::IDENTIFIER) { GDScriptParser::IdentifierNode *identifier = static_cast(n->base); if (_is_class_member_property(codegen, identifier->name)) { assign_property = identifier->name; } } break; } n = static_cast(n->base); } } /* Chain of gets */ //get at (potential) root stack pos, so it can be returned int prev_pos = _parse_expression(codegen, chain.back()->get()->base, slevel); if (prev_pos < 0) { return prev_pos; } int retval = prev_pos; if (retval & GDScriptFunction::ADDR_TYPE_STACK << GDScriptFunction::ADDR_BITS) { slevel++; codegen.alloc_stack(slevel); } Vector setchain; if (assign_property != StringName()) { // recover and assign at the end, this allows stuff like // position.x+=2.0 // in Node2D setchain.push_back(prev_pos); setchain.push_back(codegen.get_name_map_pos(assign_property)); setchain.push_back(GDScriptFunction::OPCODE_SET_MEMBER); } for (List::Element *E = chain.back(); E; E = E->prev()) { if (E == chain.front()) { //ignore first break; } const GDScriptParser::SubscriptNode *subscript_elem = E->get(); int key_idx; if (subscript_elem->is_attribute) { key_idx = codegen.get_name_map_pos(subscript_elem->attribute->name); //printf("named key %x\n",key_idx); } else { if (prev_pos & (GDScriptFunction::ADDR_TYPE_STACK << GDScriptFunction::ADDR_BITS)) { slevel++; codegen.alloc_stack(slevel); } GDScriptParser::ExpressionNode *key = subscript_elem->index; key_idx = _parse_expression(codegen, key, slevel); //printf("expr key %x\n",key_idx); //stack was raised here if retval was stack but.. } if (key_idx < 0) { //error return key_idx; } codegen.opcodes.push_back(subscript_elem->is_attribute ? GDScriptFunction::OPCODE_GET_NAMED : GDScriptFunction::OPCODE_GET); codegen.opcodes.push_back(prev_pos); codegen.opcodes.push_back(key_idx); slevel++; codegen.alloc_stack(slevel); int dst_pos = (GDScriptFunction::ADDR_TYPE_STACK << GDScriptFunction::ADDR_BITS) | slevel; codegen.opcodes.push_back(dst_pos); //add in reverse order, since it will be reverted setchain.push_back(dst_pos); setchain.push_back(key_idx); setchain.push_back(prev_pos); setchain.push_back(subscript_elem->is_attribute ? GDScriptFunction::OPCODE_SET_NAMED : GDScriptFunction::OPCODE_SET); prev_pos = dst_pos; } setchain.invert(); int set_index; if (subscript->is_attribute) { set_index = codegen.get_name_map_pos(subscript->attribute->name); } else { set_index = _parse_expression(codegen, subscript->index, slevel + 1); } if (set_index < 0) { //error return set_index; } if (set_index & GDScriptFunction::ADDR_TYPE_STACK << GDScriptFunction::ADDR_BITS) { slevel++; codegen.alloc_stack(slevel); } int set_value = _parse_assign_right_expression(codegen, assignment, slevel + 1, subscript->is_attribute ? 0 : set_index); if (set_value < 0) { //error return set_value; } codegen.opcodes.push_back(subscript->is_attribute ? GDScriptFunction::OPCODE_SET_NAMED : GDScriptFunction::OPCODE_SET); codegen.opcodes.push_back(prev_pos); codegen.opcodes.push_back(set_index); codegen.opcodes.push_back(set_value); for (int i = 0; i < setchain.size(); i++) { codegen.opcodes.push_back(setchain[i]); } return retval; } else if (assignment->assignee->type == GDScriptParser::Node::IDENTIFIER && _is_class_member_property(codegen, static_cast(assignment->assignee)->name)) { //assignment to member property int slevel = p_stack_level; int src_address = _parse_assign_right_expression(codegen, assignment, slevel); if (src_address < 0) { return -1; } StringName name = static_cast(assignment->assignee)->name; codegen.opcodes.push_back(GDScriptFunction::OPCODE_SET_MEMBER); codegen.opcodes.push_back(codegen.get_name_map_pos(name)); codegen.opcodes.push_back(src_address); return GDScriptFunction::ADDR_TYPE_NIL << GDScriptFunction::ADDR_BITS; } else { //REGULAR ASSIGNMENT MODE!! int slevel = p_stack_level; int dst_address_a = -1; bool has_setter = false; bool is_in_setter = false; StringName setter_function; if (assignment->assignee->type == GDScriptParser::Node::IDENTIFIER) { StringName var_name = static_cast(assignment->assignee)->name; if (!codegen.stack_identifiers.has(var_name) && codegen.script->member_indices.has(var_name)) { setter_function = codegen.script->member_indices[var_name].setter; if (setter_function != StringName()) { has_setter = true; is_in_setter = setter_function == codegen.function_name; dst_address_a = codegen.script->member_indices[var_name].index; } } } if (has_setter) { if (is_in_setter) { // Use direct member access. dst_address_a |= GDScriptFunction::ADDR_TYPE_MEMBER << GDScriptFunction::ADDR_BITS; } else { // Store stack slot for the temp value. dst_address_a = slevel++; codegen.alloc_stack(slevel); dst_address_a |= GDScriptFunction::ADDR_TYPE_STACK << GDScriptFunction::ADDR_BITS; } } else { dst_address_a = _parse_expression(codegen, assignment->assignee, slevel); if (dst_address_a < 0) { return -1; } if (dst_address_a & GDScriptFunction::ADDR_TYPE_STACK << GDScriptFunction::ADDR_BITS) { slevel++; codegen.alloc_stack(slevel); } } int src_address_b = _parse_assign_right_expression(codegen, assignment, slevel); if (src_address_b < 0) { return -1; } GDScriptDataType assign_type = _gdtype_from_datatype(assignment->assignee->get_datatype()); if (has_setter && !is_in_setter) { // Call setter. codegen.opcodes.push_back(GDScriptFunction::OPCODE_CALL); codegen.opcodes.push_back(1); // Argument count. codegen.opcodes.push_back(GDScriptFunction::ADDR_TYPE_SELF << GDScriptFunction::ADDR_BITS); // Base (self). codegen.opcodes.push_back(codegen.get_name_map_pos(setter_function)); // Method name. codegen.opcodes.push_back(dst_address_a); // Argument. codegen.opcodes.push_back(dst_address_a); // Result address (won't be used here). codegen.alloc_call(1); } else if (!_generate_typed_assign(codegen, src_address_b, dst_address_a, assign_type, assignment->assigned_value->get_datatype())) { return -1; } return dst_address_a; //if anything, returns wathever was assigned or correct stack position } } break; #undef OPERATOR_RETURN //TYPE_TYPE, default: { ERR_FAIL_V_MSG(-1, "Bug in bytecode compiler, unexpected node in parse tree while parsing expression."); //unreachable code } break; } } Error GDScriptCompiler::_parse_match_pattern(CodeGen &codegen, const GDScriptParser::PatternNode *p_pattern, int p_stack_level, int p_value_addr, int p_type_addr, int &r_bound_variables, Vector &r_patch_addresses, Vector &r_block_patch_address) { // TODO: Many "repeated" code here that could be abstracted. This compiler is going away when new VM arrives though, so... switch (p_pattern->pattern_type) { case GDScriptParser::PatternNode::PT_LITERAL: { // Get literal type into constant map. int literal_type_addr = -1; if (!codegen.constant_map.has((int)p_pattern->literal->value.get_type())) { literal_type_addr = codegen.constant_map.size(); codegen.constant_map[(int)p_pattern->literal->value.get_type()] = literal_type_addr; } else { literal_type_addr = codegen.constant_map[(int)p_pattern->literal->value.get_type()]; } literal_type_addr |= GDScriptFunction::ADDR_TYPE_LOCAL_CONSTANT << GDScriptFunction::ADDR_BITS; // Check type equality. int equality_addr = p_stack_level++; equality_addr |= GDScriptFunction::ADDR_TYPE_STACK << GDScriptFunction::ADDR_BITS; codegen.alloc_stack(p_stack_level); codegen.opcodes.push_back(GDScriptFunction::OPCODE_OPERATOR); codegen.opcodes.push_back(Variant::OP_EQUAL); codegen.opcodes.push_back(p_type_addr); codegen.opcodes.push_back(literal_type_addr); codegen.opcodes.push_back(equality_addr); // Address to result. // Jump away if not the same type. codegen.opcodes.push_back(GDScriptFunction::OPCODE_JUMP_IF_NOT); codegen.opcodes.push_back(equality_addr); r_patch_addresses.push_back(codegen.opcodes.size()); codegen.opcodes.push_back(0); // Will be replaced. // Get literal. int literal_addr = _parse_expression(codegen, p_pattern->literal, p_stack_level); // Check value equality. codegen.opcodes.push_back(GDScriptFunction::OPCODE_OPERATOR); codegen.opcodes.push_back(Variant::OP_EQUAL); codegen.opcodes.push_back(p_value_addr); codegen.opcodes.push_back(literal_addr); codegen.opcodes.push_back(equality_addr); // Address to result. // Jump away if doesn't match. codegen.opcodes.push_back(GDScriptFunction::OPCODE_JUMP_IF_NOT); codegen.opcodes.push_back(equality_addr); r_patch_addresses.push_back(codegen.opcodes.size()); codegen.opcodes.push_back(0); // Will be replaced. // Jump to the actual block since it matches. This is needed to take multi-pattern into account. codegen.opcodes.push_back(GDScriptFunction::OPCODE_JUMP); r_block_patch_address.push_back(codegen.opcodes.size()); codegen.opcodes.push_back(0); // Will be replaced. } break; case GDScriptParser::PatternNode::PT_EXPRESSION: { // Evaluate expression. int expr_addr = _parse_expression(codegen, p_pattern->expression, p_stack_level); if ((expr_addr >> GDScriptFunction::ADDR_BITS & GDScriptFunction::ADDR_TYPE_STACK) == GDScriptFunction::ADDR_TYPE_STACK) { p_stack_level++; codegen.alloc_stack(p_stack_level); } // Evaluate expression type. int expr_type_addr = p_stack_level++; expr_type_addr |= GDScriptFunction::ADDR_TYPE_STACK << GDScriptFunction::ADDR_BITS; codegen.alloc_stack(p_stack_level); codegen.opcodes.push_back(GDScriptFunction::OPCODE_CALL_BUILT_IN); codegen.opcodes.push_back(GDScriptFunctions::TYPE_OF); codegen.opcodes.push_back(1); // One argument. codegen.opcodes.push_back(expr_addr); // Argument is the value we want to test. codegen.opcodes.push_back(expr_type_addr); // Address to result. // Check type equality. int equality_addr = p_stack_level++; equality_addr |= GDScriptFunction::ADDR_TYPE_STACK << GDScriptFunction::ADDR_BITS; codegen.alloc_stack(p_stack_level); codegen.opcodes.push_back(GDScriptFunction::OPCODE_OPERATOR); codegen.opcodes.push_back(Variant::OP_EQUAL); codegen.opcodes.push_back(p_type_addr); codegen.opcodes.push_back(expr_type_addr); codegen.opcodes.push_back(equality_addr); // Address to result. // Jump away if not the same type. codegen.opcodes.push_back(GDScriptFunction::OPCODE_JUMP_IF_NOT); codegen.opcodes.push_back(equality_addr); r_patch_addresses.push_back(codegen.opcodes.size()); codegen.opcodes.push_back(0); // Will be replaced. // Check value equality. codegen.opcodes.push_back(GDScriptFunction::OPCODE_OPERATOR); codegen.opcodes.push_back(Variant::OP_EQUAL); codegen.opcodes.push_back(p_value_addr); codegen.opcodes.push_back(expr_addr); codegen.opcodes.push_back(equality_addr); // Address to result. // Jump away if doesn't match. codegen.opcodes.push_back(GDScriptFunction::OPCODE_JUMP_IF_NOT); codegen.opcodes.push_back(equality_addr); r_patch_addresses.push_back(codegen.opcodes.size()); codegen.opcodes.push_back(0); // Will be replaced. // Jump to the actual block since it matches. This is needed to take multi-pattern into account. codegen.opcodes.push_back(GDScriptFunction::OPCODE_JUMP); r_block_patch_address.push_back(codegen.opcodes.size()); codegen.opcodes.push_back(0); // Will be replaced. } break; case GDScriptParser::PatternNode::PT_BIND: { // Create new stack variable. int bind_addr = p_stack_level | (GDScriptFunction::ADDR_TYPE_STACK_VARIABLE << GDScriptFunction::ADDR_BITS); codegen.add_stack_identifier(p_pattern->bind->name, p_stack_level++); codegen.alloc_stack(p_stack_level); r_bound_variables++; // Assign value to bound variable. codegen.opcodes.push_back(GDScriptFunction::OPCODE_ASSIGN); codegen.opcodes.push_back(bind_addr); // Destination. codegen.opcodes.push_back(p_value_addr); // Source. // Not need to block jump because bind happens only once. } break; case GDScriptParser::PatternNode::PT_ARRAY: { int slevel = p_stack_level; // Get array type into constant map. int array_type_addr = codegen.get_constant_pos(Variant::ARRAY); // Check type equality. int equality_addr = slevel++; equality_addr |= GDScriptFunction::ADDR_TYPE_STACK << GDScriptFunction::ADDR_BITS; codegen.alloc_stack(slevel); codegen.opcodes.push_back(GDScriptFunction::OPCODE_OPERATOR); codegen.opcodes.push_back(Variant::OP_EQUAL); codegen.opcodes.push_back(p_type_addr); codegen.opcodes.push_back(array_type_addr); codegen.opcodes.push_back(equality_addr); // Address to result. // Jump away if not the same type. codegen.opcodes.push_back(GDScriptFunction::OPCODE_JUMP_IF_NOT); codegen.opcodes.push_back(equality_addr); r_patch_addresses.push_back(codegen.opcodes.size()); codegen.opcodes.push_back(0); // Will be replaced. // Store pattern length in constant map. int array_length_addr = codegen.get_constant_pos(p_pattern->rest_used ? p_pattern->array.size() - 1 : p_pattern->array.size()); // Get value length. int value_length_addr = slevel++; codegen.alloc_stack(slevel); codegen.opcodes.push_back(GDScriptFunction::OPCODE_CALL_BUILT_IN); codegen.opcodes.push_back(GDScriptFunctions::LEN); codegen.opcodes.push_back(1); // One argument. codegen.opcodes.push_back(p_value_addr); // Argument is the value we want to test. codegen.opcodes.push_back(value_length_addr); // Address to result. // Test length compatibility. codegen.opcodes.push_back(GDScriptFunction::OPCODE_OPERATOR); codegen.opcodes.push_back(p_pattern->rest_used ? Variant::OP_GREATER_EQUAL : Variant::OP_EQUAL); codegen.opcodes.push_back(value_length_addr); codegen.opcodes.push_back(array_length_addr); codegen.opcodes.push_back(equality_addr); // Address to result. // Jump away if length is not compatible. codegen.opcodes.push_back(GDScriptFunction::OPCODE_JUMP_IF_NOT); codegen.opcodes.push_back(equality_addr); r_patch_addresses.push_back(codegen.opcodes.size()); codegen.opcodes.push_back(0); // Will be replaced. // Evaluate element by element. for (int i = 0; i < p_pattern->array.size(); i++) { if (p_pattern->array[i]->pattern_type == GDScriptParser::PatternNode::PT_REST) { // Don't want to access an extra element of the user array. break; } int stlevel = p_stack_level; Vector element_block_patches; // I want to internal patterns try the next element instead of going to the block. // Add index to constant map. int index_addr = codegen.get_constant_pos(i); // Get the actual element from the user-sent array. int element_addr = stlevel++; codegen.alloc_stack(stlevel); codegen.opcodes.push_back(GDScriptFunction::OPCODE_GET); codegen.opcodes.push_back(p_value_addr); // Source. codegen.opcodes.push_back(index_addr); // Index. codegen.opcodes.push_back(element_addr); // Destination. // Also get type of element. int element_type_addr = stlevel++; element_type_addr |= GDScriptFunction::ADDR_TYPE_STACK << GDScriptFunction::ADDR_BITS; codegen.alloc_stack(stlevel); codegen.opcodes.push_back(GDScriptFunction::OPCODE_CALL_BUILT_IN); codegen.opcodes.push_back(GDScriptFunctions::TYPE_OF); codegen.opcodes.push_back(1); // One argument. codegen.opcodes.push_back(element_addr); // Argument is the value we want to test. codegen.opcodes.push_back(element_type_addr); // Address to result. // Try the pattern inside the element. Error err = _parse_match_pattern(codegen, p_pattern->array[i], stlevel, element_addr, element_type_addr, r_bound_variables, r_patch_addresses, element_block_patches); if (err != OK) { return err; } // Patch jumps to block to try the next element. for (int j = 0; j < element_block_patches.size(); j++) { codegen.opcodes.write[element_block_patches[j]] = codegen.opcodes.size(); } } // Jump to the actual block since it matches. This is needed to take multi-pattern into account. // Also here for the case of empty arrays. codegen.opcodes.push_back(GDScriptFunction::OPCODE_JUMP); r_block_patch_address.push_back(codegen.opcodes.size()); codegen.opcodes.push_back(0); // Will be replaced. } break; case GDScriptParser::PatternNode::PT_DICTIONARY: { int slevel = p_stack_level; // Get dictionary type into constant map. int dict_type_addr = codegen.get_constant_pos(Variant::DICTIONARY); // Check type equality. int equality_addr = slevel++; equality_addr |= GDScriptFunction::ADDR_TYPE_STACK << GDScriptFunction::ADDR_BITS; codegen.alloc_stack(slevel); codegen.opcodes.push_back(GDScriptFunction::OPCODE_OPERATOR); codegen.opcodes.push_back(Variant::OP_EQUAL); codegen.opcodes.push_back(p_type_addr); codegen.opcodes.push_back(dict_type_addr); codegen.opcodes.push_back(equality_addr); // Address to result. // Jump away if not the same type. codegen.opcodes.push_back(GDScriptFunction::OPCODE_JUMP_IF_NOT); codegen.opcodes.push_back(equality_addr); r_patch_addresses.push_back(codegen.opcodes.size()); codegen.opcodes.push_back(0); // Will be replaced. // Store pattern length in constant map. int dict_length_addr = codegen.get_constant_pos(p_pattern->rest_used ? p_pattern->dictionary.size() - 1 : p_pattern->dictionary.size()); // Get user's dictionary length. int value_length_addr = slevel++; codegen.alloc_stack(slevel); codegen.opcodes.push_back(GDScriptFunction::OPCODE_CALL_BUILT_IN); codegen.opcodes.push_back(GDScriptFunctions::LEN); codegen.opcodes.push_back(1); // One argument. codegen.opcodes.push_back(p_value_addr); // Argument is the value we want to test. codegen.opcodes.push_back(value_length_addr); // Address to result. // Test length compatibility. codegen.opcodes.push_back(GDScriptFunction::OPCODE_OPERATOR); codegen.opcodes.push_back(p_pattern->rest_used ? Variant::OP_GREATER_EQUAL : Variant::OP_EQUAL); codegen.opcodes.push_back(value_length_addr); codegen.opcodes.push_back(dict_length_addr); codegen.opcodes.push_back(equality_addr); // Address to result. // Jump away if length is not compatible. codegen.opcodes.push_back(GDScriptFunction::OPCODE_JUMP_IF_NOT); codegen.opcodes.push_back(equality_addr); r_patch_addresses.push_back(codegen.opcodes.size()); codegen.opcodes.push_back(0); // Will be replaced. // Evaluate element by element. for (int i = 0; i < p_pattern->dictionary.size(); i++) { const GDScriptParser::PatternNode::Pair &element = p_pattern->dictionary[i]; if (element.value_pattern && element.value_pattern->pattern_type == GDScriptParser::PatternNode::PT_REST) { // Ignore rest pattern. continue; } int stlevel = p_stack_level; Vector element_block_patches; // I want to internal patterns try the next element instead of going to the block. // Get the pattern key. int pattern_key_addr = _parse_expression(codegen, element.key, stlevel); if (pattern_key_addr < 0) { return ERR_PARSE_ERROR; } if ((pattern_key_addr >> GDScriptFunction::ADDR_BITS & GDScriptFunction::ADDR_TYPE_STACK) == GDScriptFunction::ADDR_TYPE_STACK) { stlevel++; codegen.alloc_stack(stlevel); } // Create stack slot for test result. int test_result = stlevel++; test_result |= GDScriptFunction::ADDR_TYPE_STACK << GDScriptFunction::ADDR_BITS; codegen.alloc_stack(stlevel); // Check if pattern key exists in user's dictionary. codegen.opcodes.push_back(GDScriptFunction::OPCODE_CALL_RETURN); codegen.opcodes.push_back(1); // Argument count. codegen.opcodes.push_back(p_value_addr); // Base (user dictionary). codegen.opcodes.push_back(codegen.get_name_map_pos("has")); // Function name. codegen.opcodes.push_back(pattern_key_addr); // Argument (pattern key). codegen.opcodes.push_back(test_result); // Return address. // Jump away if key doesn't exist. codegen.opcodes.push_back(GDScriptFunction::OPCODE_JUMP_IF_NOT); codegen.opcodes.push_back(test_result); r_patch_addresses.push_back(codegen.opcodes.size()); codegen.opcodes.push_back(0); // Will be replaced. if (element.value_pattern != nullptr) { // Get actual value from user dictionary. int value_addr = stlevel++; codegen.alloc_stack(stlevel); codegen.opcodes.push_back(GDScriptFunction::OPCODE_GET); codegen.opcodes.push_back(p_value_addr); // Source. codegen.opcodes.push_back(pattern_key_addr); // Index. codegen.opcodes.push_back(value_addr); // Destination. // Also get type of value. int value_type_addr = stlevel++; value_type_addr |= GDScriptFunction::ADDR_TYPE_STACK << GDScriptFunction::ADDR_BITS; codegen.alloc_stack(stlevel); codegen.opcodes.push_back(GDScriptFunction::OPCODE_CALL_BUILT_IN); codegen.opcodes.push_back(GDScriptFunctions::TYPE_OF); codegen.opcodes.push_back(1); // One argument. codegen.opcodes.push_back(value_addr); // Argument is the value we want to test. codegen.opcodes.push_back(value_type_addr); // Address to result. // Try the pattern inside the value. Error err = _parse_match_pattern(codegen, element.value_pattern, stlevel, value_addr, value_type_addr, r_bound_variables, r_patch_addresses, element_block_patches); if (err != OK) { return err; } } // Patch jumps to block to try the next element. for (int j = 0; j < element_block_patches.size(); j++) { codegen.opcodes.write[element_block_patches[j]] = codegen.opcodes.size(); } } // Jump to the actual block since it matches. This is needed to take multi-pattern into account. // Also here for the case of empty dictionaries. codegen.opcodes.push_back(GDScriptFunction::OPCODE_JUMP); r_block_patch_address.push_back(codegen.opcodes.size()); codegen.opcodes.push_back(0); // Will be replaced. } break; case GDScriptParser::PatternNode::PT_REST: // Do nothing. break; case GDScriptParser::PatternNode::PT_WILDCARD: // This matches anything so just do the jump. codegen.opcodes.push_back(GDScriptFunction::OPCODE_JUMP); r_block_patch_address.push_back(codegen.opcodes.size()); codegen.opcodes.push_back(0); // Will be replaced. } return OK; } Error GDScriptCompiler::_parse_block(CodeGen &codegen, const GDScriptParser::SuiteNode *p_block, int p_stack_level, int p_break_addr, int p_continue_addr) { codegen.push_stack_identifiers(); int new_identifiers = 0; codegen.current_line = p_block->start_line; for (int i = 0; i < p_block->statements.size(); i++) { const GDScriptParser::Node *s = p_block->statements[i]; #ifdef DEBUG_ENABLED // Add a newline before each statement, since the debugger needs those. codegen.opcodes.push_back(GDScriptFunction::OPCODE_LINE); codegen.opcodes.push_back(s->start_line); codegen.current_line = s->start_line; #endif switch (s->type) { case GDScriptParser::Node::MATCH: { const GDScriptParser::MatchNode *match = static_cast(s); int slevel = p_stack_level; // First, let's save the addres of the value match. int temp_addr = _parse_expression(codegen, match->test, slevel); if (temp_addr < 0) { return ERR_PARSE_ERROR; } if ((temp_addr >> GDScriptFunction::ADDR_BITS & GDScriptFunction::ADDR_TYPE_STACK) == GDScriptFunction::ADDR_TYPE_STACK) { slevel++; codegen.alloc_stack(slevel); } // Then, let's save the type of the value in the stack too, so we can reuse for later comparisons. int type_addr = slevel++; type_addr |= GDScriptFunction::ADDR_TYPE_STACK << GDScriptFunction::ADDR_BITS; codegen.alloc_stack(slevel); codegen.opcodes.push_back(GDScriptFunction::OPCODE_CALL_BUILT_IN); codegen.opcodes.push_back(GDScriptFunctions::TYPE_OF); codegen.opcodes.push_back(1); // One argument. codegen.opcodes.push_back(temp_addr); // Argument is the value we want to test. codegen.opcodes.push_back(type_addr); // Address to result. Vector patch_match_end; // Will patch the jump to the end of match. // Now we can actually start testing. // For each branch. for (int j = 0; j < match->branches.size(); j++) { const GDScriptParser::MatchBranchNode *branch = match->branches[j]; int bound_variables = 0; codegen.push_stack_identifiers(); // Create an extra block around for binds. #ifdef DEBUG_ENABLED // Add a newline before each branch, since the debugger needs those. codegen.opcodes.push_back(GDScriptFunction::OPCODE_LINE); codegen.opcodes.push_back(s->start_line); codegen.current_line = s->start_line; #endif Vector patch_addrs; // Will patch with end of pattern to jump. Vector block_patch_addrs; // Will patch with start of block to jump. // For each pattern in branch. for (int k = 0; k < branch->patterns.size(); k++) { if (k > 0) { // Patch jumps per pattern to allow for multipattern. If a pattern fails it just tries the next. for (int l = 0; l < patch_addrs.size(); l++) { codegen.opcodes.write[patch_addrs[l]] = codegen.opcodes.size(); } patch_addrs.clear(); } Error err = _parse_match_pattern(codegen, branch->patterns[k], slevel, temp_addr, type_addr, bound_variables, patch_addrs, block_patch_addrs); if (err != OK) { return err; } } // Patch jumps to the block. for (int k = 0; k < block_patch_addrs.size(); k++) { codegen.opcodes.write[block_patch_addrs[k]] = codegen.opcodes.size(); } // Leave space for bound variables. slevel += bound_variables; codegen.alloc_stack(slevel); // Parse the branch block. _parse_block(codegen, branch->block, slevel, p_break_addr, p_continue_addr); // Jump to end of match. codegen.opcodes.push_back(GDScriptFunction::OPCODE_JUMP); patch_match_end.push_back(codegen.opcodes.size()); codegen.opcodes.push_back(0); // Will be patched. // Patch the addresses of last pattern to jump to the end of the branch, into the next one. for (int k = 0; k < patch_addrs.size(); k++) { codegen.opcodes.write[patch_addrs[k]] = codegen.opcodes.size(); } codegen.pop_stack_identifiers(); // Get out of extra block. } // Patch the addresses to jump to the end of the match statement. for (int j = 0; j < patch_match_end.size(); j++) { codegen.opcodes.write[patch_match_end[j]] = codegen.opcodes.size(); } } break; case GDScriptParser::Node::IF: { const GDScriptParser::IfNode *if_n = static_cast(s); int ret2 = _parse_expression(codegen, if_n->condition, p_stack_level, false); if (ret2 < 0) { return ERR_PARSE_ERROR; } codegen.opcodes.push_back(GDScriptFunction::OPCODE_JUMP_IF_NOT); codegen.opcodes.push_back(ret2); int else_addr = codegen.opcodes.size(); codegen.opcodes.push_back(0); //temporary Error err = _parse_block(codegen, if_n->true_block, p_stack_level, p_break_addr, p_continue_addr); if (err) { return err; } if (if_n->false_block) { codegen.opcodes.push_back(GDScriptFunction::OPCODE_JUMP); int end_addr = codegen.opcodes.size(); codegen.opcodes.push_back(0); codegen.opcodes.write[else_addr] = codegen.opcodes.size(); Error err2 = _parse_block(codegen, if_n->false_block, p_stack_level, p_break_addr, p_continue_addr); if (err2) { return err2; } codegen.opcodes.write[end_addr] = codegen.opcodes.size(); } else { //end without else codegen.opcodes.write[else_addr] = codegen.opcodes.size(); } } break; case GDScriptParser::Node::FOR: { const GDScriptParser::ForNode *for_n = static_cast(s); int slevel = p_stack_level; int iter_stack_pos = slevel; int iterator_pos = (slevel++) | (GDScriptFunction::ADDR_TYPE_STACK << GDScriptFunction::ADDR_BITS); int counter_pos = (slevel++) | (GDScriptFunction::ADDR_TYPE_STACK << GDScriptFunction::ADDR_BITS); int container_pos = (slevel++) | (GDScriptFunction::ADDR_TYPE_STACK << GDScriptFunction::ADDR_BITS); codegen.alloc_stack(slevel); codegen.push_stack_identifiers(); codegen.add_stack_identifier(for_n->variable->name, iter_stack_pos); int ret2 = _parse_expression(codegen, for_n->list, slevel, false); if (ret2 < 0) { return ERR_COMPILATION_FAILED; } //assign container codegen.opcodes.push_back(GDScriptFunction::OPCODE_ASSIGN); codegen.opcodes.push_back(container_pos); codegen.opcodes.push_back(ret2); //begin loop codegen.opcodes.push_back(GDScriptFunction::OPCODE_ITERATE_BEGIN); codegen.opcodes.push_back(counter_pos); codegen.opcodes.push_back(container_pos); codegen.opcodes.push_back(codegen.opcodes.size() + 4); codegen.opcodes.push_back(iterator_pos); codegen.opcodes.push_back(GDScriptFunction::OPCODE_JUMP); //skip code for next codegen.opcodes.push_back(codegen.opcodes.size() + 8); //break loop int break_pos = codegen.opcodes.size(); codegen.opcodes.push_back(GDScriptFunction::OPCODE_JUMP); //skip code for next codegen.opcodes.push_back(0); //skip code for next //next loop int continue_pos = codegen.opcodes.size(); codegen.opcodes.push_back(GDScriptFunction::OPCODE_ITERATE); codegen.opcodes.push_back(counter_pos); codegen.opcodes.push_back(container_pos); codegen.opcodes.push_back(break_pos); codegen.opcodes.push_back(iterator_pos); Error err = _parse_block(codegen, for_n->loop, slevel, break_pos, continue_pos); if (err) { return err; } codegen.opcodes.push_back(GDScriptFunction::OPCODE_JUMP); codegen.opcodes.push_back(continue_pos); codegen.opcodes.write[break_pos + 1] = codegen.opcodes.size(); codegen.pop_stack_identifiers(); } break; case GDScriptParser::Node::WHILE: { const GDScriptParser::WhileNode *while_n = static_cast(s); codegen.opcodes.push_back(GDScriptFunction::OPCODE_JUMP); codegen.opcodes.push_back(codegen.opcodes.size() + 3); int break_addr = codegen.opcodes.size(); codegen.opcodes.push_back(GDScriptFunction::OPCODE_JUMP); codegen.opcodes.push_back(0); int continue_addr = codegen.opcodes.size(); int ret2 = _parse_expression(codegen, while_n->condition, p_stack_level, false); if (ret2 < 0) { return ERR_PARSE_ERROR; } codegen.opcodes.push_back(GDScriptFunction::OPCODE_JUMP_IF_NOT); codegen.opcodes.push_back(ret2); codegen.opcodes.push_back(break_addr); Error err = _parse_block(codegen, while_n->loop, p_stack_level, break_addr, continue_addr); if (err) { return err; } codegen.opcodes.push_back(GDScriptFunction::OPCODE_JUMP); codegen.opcodes.push_back(continue_addr); codegen.opcodes.write[break_addr + 1] = codegen.opcodes.size(); } break; case GDScriptParser::Node::BREAK: { if (p_break_addr < 0) { _set_error("'break'' not within loop", s); return ERR_COMPILATION_FAILED; } codegen.opcodes.push_back(GDScriptFunction::OPCODE_JUMP); codegen.opcodes.push_back(p_break_addr); } break; case GDScriptParser::Node::CONTINUE: { if (p_continue_addr < 0) { _set_error("'continue' not within loop", s); return ERR_COMPILATION_FAILED; } codegen.opcodes.push_back(GDScriptFunction::OPCODE_JUMP); codegen.opcodes.push_back(p_continue_addr); } break; case GDScriptParser::Node::RETURN: { const GDScriptParser::ReturnNode *return_n = static_cast(s); int ret2; if (return_n->return_value != nullptr) { ret2 = _parse_expression(codegen, return_n->return_value, p_stack_level, false); if (ret2 < 0) { return ERR_PARSE_ERROR; } } else { ret2 = GDScriptFunction::ADDR_TYPE_NIL << GDScriptFunction::ADDR_BITS; } codegen.opcodes.push_back(GDScriptFunction::OPCODE_RETURN); codegen.opcodes.push_back(ret2); } break; case GDScriptParser::Node::ASSERT: { #ifdef DEBUG_ENABLED // try subblocks const GDScriptParser::AssertNode *as = static_cast(s); int ret2 = _parse_expression(codegen, as->condition, p_stack_level, false); if (ret2 < 0) { return ERR_PARSE_ERROR; } int message_ret = 0; if (as->message) { message_ret = _parse_expression(codegen, as->message, p_stack_level + 1, false); if (message_ret < 0) { return ERR_PARSE_ERROR; } } codegen.opcodes.push_back(GDScriptFunction::OPCODE_ASSERT); codegen.opcodes.push_back(ret2); codegen.opcodes.push_back(message_ret); #endif } break; case GDScriptParser::Node::BREAKPOINT: { #ifdef DEBUG_ENABLED // try subblocks codegen.opcodes.push_back(GDScriptFunction::OPCODE_BREAKPOINT); #endif } break; case GDScriptParser::Node::VARIABLE: { const GDScriptParser::VariableNode *lv = static_cast(s); // since we are using properties now for most class access, allow shadowing of class members to make user's life easier. // //if (_is_class_member_property(codegen, lv->name)) { // _set_error("Name for local variable '" + String(lv->name) + "' can't shadow class property of the same name.", lv); // return ERR_ALREADY_EXISTS; //} codegen.add_stack_identifier(lv->identifier->name, p_stack_level++); codegen.alloc_stack(p_stack_level); new_identifiers++; if (lv->initializer != nullptr) { int dst_address = codegen.stack_identifiers[lv->identifier->name]; dst_address |= GDScriptFunction::ADDR_TYPE_STACK_VARIABLE << GDScriptFunction::ADDR_BITS; int src_address = _parse_expression(codegen, lv->initializer, p_stack_level); if (src_address < 0) { return ERR_PARSE_ERROR; } if (!_generate_typed_assign(codegen, src_address, dst_address, _gdtype_from_datatype(lv->get_datatype()), lv->initializer->get_datatype())) { return ERR_PARSE_ERROR; } } } break; case GDScriptParser::Node::CONSTANT: { // Local constants. const GDScriptParser::ConstantNode *lc = static_cast(s); if (!lc->initializer->is_constant) { _set_error("Local constant must have a constant value as initializer.", lc->initializer); return ERR_PARSE_ERROR; } codegen.local_named_constants[lc->identifier->name] = codegen.get_constant_pos(lc->initializer->reduced_value); } break; case GDScriptParser::Node::PASS: // Nothing to do. break; default: { //expression if (s->is_expression()) { int ret2 = _parse_expression(codegen, static_cast(s), p_stack_level, true); if (ret2 < 0) { return ERR_PARSE_ERROR; } } else { ERR_FAIL_V_MSG(ERR_INVALID_DATA, "Bug in bytecode compiler, unexpected node in parse tree while parsing statement."); //unreachable code } } break; } } codegen.pop_stack_identifiers(); return OK; } Error GDScriptCompiler::_parse_function(GDScript *p_script, const GDScriptParser::ClassNode *p_class, const GDScriptParser::FunctionNode *p_func, bool p_for_ready) { Vector bytecode; CodeGen codegen; codegen.class_node = p_class; codegen.script = p_script; codegen.function_node = p_func; codegen.stack_max = 0; codegen.current_line = 0; codegen.call_max = 0; codegen.debug_stack = EngineDebugger::is_active(); Vector argnames; int stack_level = 0; int optional_parameters = 0; if (p_func) { for (int i = 0; i < p_func->parameters.size(); i++) { // since we are using properties now for most class access, allow shadowing of class members to make user's life easier. // //if (_is_class_member_property(p_script, p_func->arguments[i])) { // _set_error("Name for argument '" + String(p_func->arguments[i]) + "' can't shadow class property of the same name.", p_func); // return ERR_ALREADY_EXISTS; //} codegen.add_stack_identifier(p_func->parameters[i]->identifier->name, i); #ifdef TOOLS_ENABLED argnames.push_back(p_func->parameters[i]->identifier->name); #endif if (p_func->parameters[i]->default_value != nullptr) { optional_parameters++; } } stack_level = p_func->parameters.size(); } codegen.alloc_stack(stack_level); /* Parse initializer -if applies- */ bool is_implicit_initializer = !p_for_ready && !p_func; bool is_initializer = p_func && String(p_func->identifier->name) == GDScriptLanguage::get_singleton()->strings._init; if (is_implicit_initializer) { // Initialize class fields. for (int i = 0; i < p_class->members.size(); i++) { if (p_class->members[i].type != GDScriptParser::ClassNode::Member::VARIABLE) { continue; } const GDScriptParser::VariableNode *field = p_class->members[i].variable; if (field->onready) { // Only initialize in _ready. continue; } if (field->initializer) { // Emit proper line change. codegen.opcodes.push_back(GDScriptFunction::OPCODE_LINE); codegen.opcodes.push_back(field->initializer->start_line); int src_address = _parse_expression(codegen, field->initializer, stack_level, false, true); if (src_address < 0) { return ERR_PARSE_ERROR; } int dst_address = codegen.script->member_indices[field->identifier->name].index; dst_address |= GDScriptFunction::ADDR_TYPE_MEMBER << GDScriptFunction::ADDR_BITS; if (!_generate_typed_assign(codegen, src_address, dst_address, _gdtype_from_datatype(field->get_datatype()), field->initializer->get_datatype())) { return ERR_PARSE_ERROR; } } } } if (p_for_ready || (p_func && String(p_func->identifier->name) == "_ready")) { // Initialize class fields on ready. for (int i = 0; i < p_class->members.size(); i++) { if (p_class->members[i].type != GDScriptParser::ClassNode::Member::VARIABLE) { continue; } const GDScriptParser::VariableNode *field = p_class->members[i].variable; if (!field->onready) { continue; } if (field->initializer) { // Emit proper line change. codegen.opcodes.push_back(GDScriptFunction::OPCODE_LINE); codegen.opcodes.push_back(field->initializer->start_line); int src_address = _parse_expression(codegen, field->initializer, stack_level, false, true); if (src_address < 0) { return ERR_PARSE_ERROR; } int dst_address = codegen.script->member_indices[field->identifier->name].index; dst_address |= GDScriptFunction::ADDR_TYPE_MEMBER << GDScriptFunction::ADDR_BITS; if (!_generate_typed_assign(codegen, src_address, dst_address, _gdtype_from_datatype(field->get_datatype()), field->initializer->get_datatype())) { return ERR_PARSE_ERROR; } } } } /* Parse default argument code -if applies- */ Vector defarg_addr; StringName func_name; if (p_func) { if (optional_parameters > 0) { codegen.opcodes.push_back(GDScriptFunction::OPCODE_JUMP_TO_DEF_ARGUMENT); defarg_addr.push_back(codegen.opcodes.size()); for (int i = p_func->parameters.size() - optional_parameters; i < p_func->parameters.size(); i++) { int src_addr = _parse_expression(codegen, p_func->parameters[i]->default_value, stack_level, true); if (src_addr < 0) { return ERR_PARSE_ERROR; } int dst_addr = codegen.stack_identifiers[p_func->parameters[i]->identifier->name] | (GDScriptFunction::ADDR_TYPE_STACK_VARIABLE << GDScriptFunction::ADDR_BITS); if (!_generate_typed_assign(codegen, src_addr, dst_addr, _gdtype_from_datatype(p_func->parameters[i]->get_datatype()), p_func->parameters[i]->default_value->get_datatype())) { return ERR_PARSE_ERROR; } defarg_addr.push_back(codegen.opcodes.size()); } defarg_addr.invert(); } func_name = p_func->identifier->name; codegen.function_name = func_name; Error err = _parse_block(codegen, p_func->body, stack_level); if (err) { return err; } } else { if (p_for_ready) { func_name = "_ready"; } else { func_name = "@implicit_new"; } } codegen.function_name = func_name; codegen.opcodes.push_back(GDScriptFunction::OPCODE_END); /* if (String(p_func->name)=="") { //initializer func gdfunc = &p_script->initializer; */ //} else { //regular func p_script->member_functions[func_name] = memnew(GDScriptFunction); GDScriptFunction *gdfunc = p_script->member_functions[func_name]; //} if (p_func) { gdfunc->_static = p_func->is_static; gdfunc->rpc_mode = p_func->rpc_mode; gdfunc->argument_types.resize(p_func->parameters.size()); for (int i = 0; i < p_func->parameters.size(); i++) { gdfunc->argument_types.write[i] = _gdtype_from_datatype(p_func->parameters[i]->get_datatype()); } gdfunc->return_type = _gdtype_from_datatype(p_func->get_datatype()); } else { gdfunc->_static = false; gdfunc->rpc_mode = MultiplayerAPI::RPC_MODE_DISABLED; gdfunc->return_type = GDScriptDataType(); gdfunc->return_type.has_type = true; gdfunc->return_type.kind = GDScriptDataType::BUILTIN; gdfunc->return_type.builtin_type = Variant::NIL; } #ifdef TOOLS_ENABLED gdfunc->arg_names = argnames; #endif //constants if (codegen.constant_map.size()) { gdfunc->_constant_count = codegen.constant_map.size(); gdfunc->constants.resize(codegen.constant_map.size()); gdfunc->_constants_ptr = gdfunc->constants.ptrw(); const Variant *K = nullptr; while ((K = codegen.constant_map.next(K))) { int idx = codegen.constant_map[*K]; gdfunc->constants.write[idx] = *K; } } else { gdfunc->_constants_ptr = nullptr; gdfunc->_constant_count = 0; } //global names if (codegen.name_map.size()) { gdfunc->global_names.resize(codegen.name_map.size()); gdfunc->_global_names_ptr = &gdfunc->global_names[0]; for (Map::Element *E = codegen.name_map.front(); E; E = E->next()) { gdfunc->global_names.write[E->get()] = E->key(); } gdfunc->_global_names_count = gdfunc->global_names.size(); } else { gdfunc->_global_names_ptr = nullptr; gdfunc->_global_names_count = 0; } #ifdef TOOLS_ENABLED // Named globals if (codegen.named_globals.size()) { gdfunc->named_globals.resize(codegen.named_globals.size()); gdfunc->_named_globals_ptr = gdfunc->named_globals.ptr(); for (int i = 0; i < codegen.named_globals.size(); i++) { gdfunc->named_globals.write[i] = codegen.named_globals[i]; } gdfunc->_named_globals_count = gdfunc->named_globals.size(); } #endif if (codegen.opcodes.size()) { gdfunc->code = codegen.opcodes; gdfunc->_code_ptr = &gdfunc->code[0]; gdfunc->_code_size = codegen.opcodes.size(); } else { gdfunc->_code_ptr = nullptr; gdfunc->_code_size = 0; } if (defarg_addr.size()) { gdfunc->default_arguments = defarg_addr; gdfunc->_default_arg_count = defarg_addr.size() - 1; gdfunc->_default_arg_ptr = &gdfunc->default_arguments[0]; } else { gdfunc->_default_arg_count = 0; gdfunc->_default_arg_ptr = nullptr; } gdfunc->_argument_count = p_func ? p_func->parameters.size() : 0; gdfunc->_stack_size = codegen.stack_max; gdfunc->_call_size = codegen.call_max; gdfunc->name = func_name; #ifdef DEBUG_ENABLED if (EngineDebugger::is_active()) { String signature; //path if (p_script->get_path() != String()) { signature += p_script->get_path(); } //loc if (p_func) { signature += "::" + itos(p_func->body->start_line); } else { signature += "::0"; } //function and class if (p_class->identifier) { signature += "::" + String(p_class->identifier->name) + "." + String(func_name); } else { signature += "::" + String(func_name); } gdfunc->profile.signature = signature; } #endif gdfunc->_script = p_script; gdfunc->source = source; #ifdef DEBUG_ENABLED { gdfunc->func_cname = (String(source) + " - " + String(func_name)).utf8(); gdfunc->_func_cname = gdfunc->func_cname.get_data(); } #endif if (p_func) { gdfunc->_initial_line = p_func->start_line; #ifdef TOOLS_ENABLED p_script->member_lines[func_name] = p_func->start_line; #endif } else { gdfunc->_initial_line = 0; } if (codegen.debug_stack) { gdfunc->stack_debug = codegen.stack_debug; } if (is_initializer) { p_script->initializer = gdfunc; } if (is_implicit_initializer) { p_script->implicit_initializer = gdfunc; } return OK; } Error GDScriptCompiler::_parse_setter_getter(GDScript *p_script, const GDScriptParser::ClassNode *p_class, const GDScriptParser::VariableNode *p_variable, bool p_is_setter) { Vector bytecode; CodeGen codegen; codegen.class_node = p_class; codegen.script = p_script; codegen.function_node = nullptr; codegen.stack_max = 0; codegen.current_line = 0; codegen.call_max = 0; codegen.debug_stack = EngineDebugger::is_active(); Vector argnames; int stack_level = 0; if (p_is_setter) { codegen.add_stack_identifier(p_variable->setter_parameter->name, stack_level++); argnames.push_back(p_variable->setter_parameter->name); } codegen.alloc_stack(stack_level); StringName func_name; if (p_is_setter) { func_name = "@" + p_variable->identifier->name + "_setter"; } else { func_name = "@" + p_variable->identifier->name + "_getter"; } codegen.function_name = func_name; Error err = _parse_block(codegen, p_is_setter ? p_variable->setter : p_variable->getter, stack_level); if (err != OK) { return err; } codegen.opcodes.push_back(GDScriptFunction::OPCODE_END); p_script->member_functions[func_name] = memnew(GDScriptFunction); GDScriptFunction *gdfunc = p_script->member_functions[func_name]; gdfunc->_static = false; gdfunc->rpc_mode = p_variable->rpc_mode; gdfunc->argument_types.resize(p_is_setter ? 1 : 0); gdfunc->return_type = _gdtype_from_datatype(p_variable->get_datatype()); #ifdef TOOLS_ENABLED gdfunc->arg_names = argnames; #endif // TODO: Unify this with function compiler. //constants if (codegen.constant_map.size()) { gdfunc->_constant_count = codegen.constant_map.size(); gdfunc->constants.resize(codegen.constant_map.size()); gdfunc->_constants_ptr = gdfunc->constants.ptrw(); const Variant *K = nullptr; while ((K = codegen.constant_map.next(K))) { int idx = codegen.constant_map[*K]; gdfunc->constants.write[idx] = *K; } } else { gdfunc->_constants_ptr = nullptr; gdfunc->_constant_count = 0; } //global names if (codegen.name_map.size()) { gdfunc->global_names.resize(codegen.name_map.size()); gdfunc->_global_names_ptr = &gdfunc->global_names[0]; for (Map::Element *E = codegen.name_map.front(); E; E = E->next()) { gdfunc->global_names.write[E->get()] = E->key(); } gdfunc->_global_names_count = gdfunc->global_names.size(); } else { gdfunc->_global_names_ptr = nullptr; gdfunc->_global_names_count = 0; } #ifdef TOOLS_ENABLED // Named globals if (codegen.named_globals.size()) { gdfunc->named_globals.resize(codegen.named_globals.size()); gdfunc->_named_globals_ptr = gdfunc->named_globals.ptr(); for (int i = 0; i < codegen.named_globals.size(); i++) { gdfunc->named_globals.write[i] = codegen.named_globals[i]; } gdfunc->_named_globals_count = gdfunc->named_globals.size(); } #endif gdfunc->code = codegen.opcodes; gdfunc->_code_ptr = &gdfunc->code[0]; gdfunc->_code_size = codegen.opcodes.size(); gdfunc->_default_arg_count = 0; gdfunc->_default_arg_ptr = nullptr; gdfunc->_argument_count = argnames.size(); gdfunc->_stack_size = codegen.stack_max; gdfunc->_call_size = codegen.call_max; gdfunc->name = func_name; #ifdef DEBUG_ENABLED if (EngineDebugger::is_active()) { String signature; //path if (p_script->get_path() != String()) { signature += p_script->get_path(); } //loc signature += "::" + itos(p_is_setter ? p_variable->setter->start_line : p_variable->getter->start_line); //function and class if (p_class->identifier) { signature += "::" + String(p_class->identifier->name) + "." + String(func_name); } else { signature += "::" + String(func_name); } gdfunc->profile.signature = signature; } #endif gdfunc->_script = p_script; gdfunc->source = source; #ifdef DEBUG_ENABLED { gdfunc->func_cname = (String(source) + " - " + String(func_name)).utf8(); gdfunc->_func_cname = gdfunc->func_cname.get_data(); } #endif gdfunc->_initial_line = p_is_setter ? p_variable->setter->start_line : p_variable->getter->start_line; #ifdef TOOLS_ENABLED p_script->member_lines[func_name] = gdfunc->_initial_line; #endif if (codegen.debug_stack) { gdfunc->stack_debug = codegen.stack_debug; } return OK; } Error GDScriptCompiler::_parse_class_level(GDScript *p_script, const GDScriptParser::ClassNode *p_class, bool p_keep_state) { parsing_classes.insert(p_script); if (p_class->outer && p_class->outer->outer) { // Owner is not root if (!parsed_classes.has(p_script->_owner)) { if (parsing_classes.has(p_script->_owner)) { _set_error("Cyclic class reference for '" + String(p_class->identifier->name) + "'.", p_class); return ERR_PARSE_ERROR; } Error err = _parse_class_level(p_script->_owner, p_class->outer, p_keep_state); if (err) { return err; } } } p_script->native = Ref(); p_script->base = Ref(); p_script->_base = nullptr; p_script->members.clear(); p_script->constants.clear(); for (Map::Element *E = p_script->member_functions.front(); E; E = E->next()) { memdelete(E->get()); } p_script->member_functions.clear(); p_script->member_indices.clear(); p_script->member_info.clear(); p_script->_signals.clear(); p_script->initializer = nullptr; p_script->tool = parser->is_tool(); p_script->name = p_class->identifier ? p_class->identifier->name : ""; Ref native; GDScriptDataType base_type = _gdtype_from_datatype(p_class->base_type); // Inheritance switch (base_type.kind) { case GDScriptDataType::NATIVE: { int native_idx = GDScriptLanguage::get_singleton()->get_global_map()[base_type.native_type]; native = GDScriptLanguage::get_singleton()->get_global_array()[native_idx]; ERR_FAIL_COND_V(native.is_null(), ERR_BUG); p_script->native = native; } break; case GDScriptDataType::GDSCRIPT: { Ref base = base_type.script_type; p_script->base = base; p_script->_base = base.ptr(); if (p_class->base_type.kind == GDScriptParser::DataType::CLASS && p_class->base_type.class_type != nullptr) { if (!parsed_classes.has(p_script->_base)) { if (parsing_classes.has(p_script->_base)) { String class_name = p_class->identifier ? p_class->identifier->name : "
"; _set_error("Cyclic class reference for '" + class_name + "'.", p_class); return ERR_PARSE_ERROR; } Error err = _parse_class_level(p_script->_base, p_class->base_type.class_type, p_keep_state); if (err) { return err; } } } p_script->member_indices = base->member_indices; } break; default: { _set_error("Parser bug: invalid inheritance.", p_class); return ERR_BUG; } break; } for (int i = 0; i < p_class->members.size(); i++) { const GDScriptParser::ClassNode::Member &member = p_class->members[i]; switch (member.type) { case GDScriptParser::ClassNode::Member::VARIABLE: { const GDScriptParser::VariableNode *variable = member.variable; StringName name = variable->identifier->name; GDScript::MemberInfo minfo; minfo.index = p_script->member_indices.size(); switch (variable->property) { case GDScriptParser::VariableNode::PROP_NONE: break; // Nothing to do. case GDScriptParser::VariableNode::PROP_SETGET: if (variable->setter_pointer != nullptr) { minfo.setter = variable->setter_pointer->name; } if (variable->getter_pointer != nullptr) { minfo.getter = variable->getter_pointer->name; } break; case GDScriptParser::VariableNode::PROP_INLINE: if (variable->setter != nullptr) { minfo.setter = "@" + variable->identifier->name + "_setter"; } if (variable->getter != nullptr) { minfo.getter = "@" + variable->identifier->name + "_getter"; } break; } minfo.rpc_mode = variable->rpc_mode; minfo.data_type = _gdtype_from_datatype(variable->get_datatype()); PropertyInfo prop_info = minfo.data_type; prop_info.name = name; PropertyInfo export_info = variable->export_info; if (variable->exported) { if (!minfo.data_type.has_type) { prop_info.type = export_info.type; prop_info.class_name = export_info.class_name; } prop_info.hint = export_info.hint; prop_info.hint_string = export_info.hint_string; prop_info.usage = export_info.usage; #ifdef TOOLS_ENABLED if (variable->initializer != nullptr && variable->initializer->type == GDScriptParser::Node::LITERAL) { p_script->member_default_values[name] = static_cast(variable->initializer)->value; } #endif } else { prop_info.usage = PROPERTY_USAGE_SCRIPT_VARIABLE; } p_script->member_info[name] = prop_info; p_script->member_indices[name] = minfo; p_script->members.insert(name); #ifdef TOOLS_ENABLED p_script->member_lines[name] = variable->start_line; #endif } break; case GDScriptParser::ClassNode::Member::CONSTANT: { const GDScriptParser::ConstantNode *constant = member.constant; StringName name = constant->identifier->name; ERR_CONTINUE(constant->initializer->type != GDScriptParser::Node::LITERAL); const GDScriptParser::LiteralNode *literal = static_cast(constant->initializer); p_script->constants.insert(name, literal->value); #ifdef TOOLS_ENABLED p_script->member_lines[name] = constant->start_line; #endif } break; case GDScriptParser::ClassNode::Member::ENUM_VALUE: { const GDScriptParser::EnumNode::Value &enum_value = member.enum_value; StringName name = enum_value.identifier->name; p_script->constants.insert(name, enum_value.value); #ifdef TOOLS_ENABLED p_script->member_lines[name] = enum_value.identifier->start_line; #endif } break; case GDScriptParser::ClassNode::Member::SIGNAL: { const GDScriptParser::SignalNode *signal = member.signal; StringName name = signal->identifier->name; GDScript *c = p_script; while (c) { if (c->_signals.has(name)) { _set_error("Signal '" + name + "' redefined (in current or parent class)", p_class); return ERR_ALREADY_EXISTS; } if (c->base.is_valid()) { c = c->base.ptr(); } else { c = nullptr; } } if (native.is_valid()) { if (ClassDB::has_signal(native->get_name(), name)) { _set_error("Signal '" + name + "' redefined (original in native class '" + String(native->get_name()) + "')", p_class); return ERR_ALREADY_EXISTS; } } Vector parameters_names; parameters_names.resize(signal->parameters.size()); for (int j = 0; j < signal->parameters.size(); j++) { parameters_names.write[j] = signal->parameters[j]->identifier->name; } p_script->_signals[name] = parameters_names; } break; case GDScriptParser::ClassNode::Member::ENUM: { const GDScriptParser::EnumNode *enum_n = member.m_enum; // TODO: Make enums not be just a dictionary? Dictionary new_enum; for (int j = 0; j < enum_n->values.size(); j++) { int value = enum_n->values[j].value; // Needs to be string because Variant::get will convert to String. new_enum[String(enum_n->values[j].identifier->name)] = value; } p_script->constants.insert(enum_n->identifier->name, new_enum); #ifdef TOOLS_ENABLED p_script->member_lines[enum_n->identifier->name] = enum_n->start_line; #endif } break; default: break; // Nothing to do here. } } parsed_classes.insert(p_script); parsing_classes.erase(p_script); //parse sub-classes for (int i = 0; i < p_class->members.size(); i++) { const GDScriptParser::ClassNode::Member &member = p_class->members[i]; if (member.type != member.CLASS) { continue; } const GDScriptParser::ClassNode *inner_class = member.m_class; StringName name = inner_class->identifier->name; Ref &subclass = p_script->subclasses[name]; GDScript *subclass_ptr = subclass.ptr(); // Subclass might still be parsing, just skip it if (!parsed_classes.has(subclass_ptr) && !parsing_classes.has(subclass_ptr)) { Error err = _parse_class_level(subclass_ptr, inner_class, p_keep_state); if (err) { return err; } } #ifdef TOOLS_ENABLED p_script->member_lines[name] = inner_class->start_line; #endif p_script->constants.insert(name, subclass); //once parsed, goes to the list of constants } return OK; } Error GDScriptCompiler::_parse_class_blocks(GDScript *p_script, const GDScriptParser::ClassNode *p_class, bool p_keep_state) { //parse methods bool has_ready = false; for (int i = 0; i < p_class->members.size(); i++) { const GDScriptParser::ClassNode::Member &member = p_class->members[i]; if (member.type == member.FUNCTION) { const GDScriptParser::FunctionNode *function = member.function; if (!has_ready && function->identifier->name == "_ready") { has_ready = true; } Error err = _parse_function(p_script, p_class, function); if (err) { return err; } } else if (member.type == member.VARIABLE) { const GDScriptParser::VariableNode *variable = member.variable; if (variable->property == GDScriptParser::VariableNode::PROP_INLINE) { if (variable->setter != nullptr) { Error err = _parse_setter_getter(p_script, p_class, variable, true); if (err) { return err; } } if (variable->getter != nullptr) { Error err = _parse_setter_getter(p_script, p_class, variable, false); if (err) { return err; } } } } } { // Create an implicit constructor in any case. Error err = _parse_function(p_script, p_class, nullptr); if (err) { return err; } } if (!has_ready && p_class->onready_used) { //create a _ready constructor Error err = _parse_function(p_script, p_class, nullptr, true); if (err) { return err; } } #ifdef DEBUG_ENABLED //validate instances if keeping state if (p_keep_state) { for (Set::Element *E = p_script->instances.front(); E;) { Set::Element *N = E->next(); ScriptInstance *si = E->get()->get_script_instance(); if (si->is_placeholder()) { #ifdef TOOLS_ENABLED PlaceHolderScriptInstance *psi = static_cast(si); if (p_script->is_tool()) { //re-create as an instance p_script->placeholders.erase(psi); //remove placeholder GDScriptInstance *instance = memnew(GDScriptInstance); instance->base_ref = Object::cast_to(E->get()); instance->members.resize(p_script->member_indices.size()); instance->script = Ref(p_script); instance->owner = E->get(); //needed for hot reloading for (Map::Element *F = p_script->member_indices.front(); F; F = F->next()) { instance->member_indices_cache[F->key()] = F->get().index; } instance->owner->set_script_instance(instance); /* STEP 2, INITIALIZE AND CONSTRUCT */ Callable::CallError ce; p_script->initializer->call(instance, nullptr, 0, ce); if (ce.error != Callable::CallError::CALL_OK) { //well, tough luck, not goinna do anything here } } #endif } else { GDScriptInstance *gi = static_cast(si); gi->reload_members(); } E = N; } } #endif for (int i = 0; i < p_class->members.size(); i++) { if (p_class->members[i].type != GDScriptParser::ClassNode::Member::CLASS) { continue; } const GDScriptParser::ClassNode *inner_class = p_class->members[i].m_class; StringName name = inner_class->identifier->name; GDScript *subclass = p_script->subclasses[name].ptr(); Error err = _parse_class_blocks(subclass, inner_class, p_keep_state); if (err) { return err; } } p_script->valid = true; return OK; } void GDScriptCompiler::_make_scripts(GDScript *p_script, const GDScriptParser::ClassNode *p_class, bool p_keep_state) { Map> old_subclasses; if (p_keep_state) { old_subclasses = p_script->subclasses; } p_script->subclasses.clear(); for (int i = 0; i < p_class->members.size(); i++) { if (p_class->members[i].type != GDScriptParser::ClassNode::Member::CLASS) { continue; } const GDScriptParser::ClassNode *inner_class = p_class->members[i].m_class; StringName name = inner_class->identifier->name; Ref subclass; String fully_qualified_name = p_script->fully_qualified_name + "::" + name; if (old_subclasses.has(name)) { subclass = old_subclasses[name]; } else { Ref orphan_subclass = GDScriptLanguage::get_singleton()->get_orphan_subclass(fully_qualified_name); if (orphan_subclass.is_valid()) { subclass = orphan_subclass; } else { subclass.instance(); } } subclass->_owner = p_script; subclass->fully_qualified_name = fully_qualified_name; p_script->subclasses.insert(name, subclass); _make_scripts(subclass.ptr(), inner_class, false); } } Error GDScriptCompiler::compile(const GDScriptParser *p_parser, GDScript *p_script, bool p_keep_state) { err_line = -1; err_column = -1; error = ""; parser = p_parser; main_script = p_script; const GDScriptParser::ClassNode *root = parser->get_tree(); source = p_script->get_path(); // The best fully qualified name for a base level script is its file path p_script->fully_qualified_name = p_script->path; // Create scripts for subclasses beforehand so they can be referenced _make_scripts(p_script, root, p_keep_state); p_script->_owner = nullptr; Error err = _parse_class_level(p_script, root, p_keep_state); if (err) { return err; } err = _parse_class_blocks(p_script, root, p_keep_state); if (err) { return err; } return GDScriptCache::finish_compiling(p_script->get_path()); } String GDScriptCompiler::get_error() const { return error; } int GDScriptCompiler::get_error_line() const { return err_line; } int GDScriptCompiler::get_error_column() const { return err_column; } GDScriptCompiler::GDScriptCompiler() { }