diff options
author | George Marques <george@gmarqu.es> | 2021-03-28 11:03:13 -0300 |
---|---|---|
committer | George Marques <george@gmarqu.es> | 2021-04-28 11:09:38 -0300 |
commit | c201b212c7d188c15de78c4d4150e9d7337b6983 (patch) | |
tree | a66e15980bebbe5322e458c10457889ff4d515e8 | |
parent | 3155368093875e644b1adbfa29bb584134c52a89 (diff) |
GDScript: Implement lambdas compilation and runtime
-rw-r--r-- | modules/gdscript/gdscript.h | 1 | ||||
-rw-r--r-- | modules/gdscript/gdscript_analyzer.cpp | 35 | ||||
-rw-r--r-- | modules/gdscript/gdscript_analyzer.h | 1 | ||||
-rw-r--r-- | modules/gdscript/gdscript_byte_codegen.cpp | 23 | ||||
-rw-r--r-- | modules/gdscript/gdscript_byte_codegen.h | 15 | ||||
-rw-r--r-- | modules/gdscript/gdscript_codegen.h | 1 | ||||
-rw-r--r-- | modules/gdscript/gdscript_compiler.cpp | 89 | ||||
-rw-r--r-- | modules/gdscript/gdscript_compiler.h | 2 | ||||
-rw-r--r-- | modules/gdscript/gdscript_disassembler.cpp | 21 | ||||
-rw-r--r-- | modules/gdscript/gdscript_function.cpp | 4 | ||||
-rw-r--r-- | modules/gdscript/gdscript_function.h | 4 | ||||
-rw-r--r-- | modules/gdscript/gdscript_lambda_callable.cpp | 95 | ||||
-rw-r--r-- | modules/gdscript/gdscript_lambda_callable.h | 65 | ||||
-rw-r--r-- | modules/gdscript/gdscript_parser.cpp | 6 | ||||
-rw-r--r-- | modules/gdscript/gdscript_parser.h | 3 | ||||
-rw-r--r-- | modules/gdscript/gdscript_vm.cpp | 38 |
16 files changed, 364 insertions, 39 deletions
diff --git a/modules/gdscript/gdscript.h b/modules/gdscript/gdscript.h index 12c909fd4f..98da5ad4cb 100644 --- a/modules/gdscript/gdscript.h +++ b/modules/gdscript/gdscript.h @@ -270,6 +270,7 @@ public: class GDScriptInstance : public ScriptInstance { friend class GDScript; friend class GDScriptFunction; + friend class GDScriptLambdaCallable; friend class GDScriptCompiler; friend struct GDScriptUtilityFunctionsDefinitions; diff --git a/modules/gdscript/gdscript_analyzer.cpp b/modules/gdscript/gdscript_analyzer.cpp index 3780f6e23c..db554c1ab0 100644 --- a/modules/gdscript/gdscript_analyzer.cpp +++ b/modules/gdscript/gdscript_analyzer.cpp @@ -2101,6 +2101,8 @@ void GDScriptAnalyzer::reduce_call(GDScriptParser::CallNode *p_call, bool is_awa if (is_self && parser->current_function != nullptr && parser->current_function->is_static && !is_static) { push_error(vformat(R"*(Cannot call non-static function "%s()" from static function "%s()".)*", p_call->function_name, parser->current_function->identifier->name), p_call->callee); + } else if (is_self && !is_static && !lambda_stack.is_empty()) { + push_error(vformat(R"*(Cannot call non-static function "%s()" from a lambda function.)*", p_call->function_name), p_call->callee); } call_type = return_type; @@ -2223,6 +2225,8 @@ void GDScriptAnalyzer::reduce_get_node(GDScriptParser::GetNodeNode *p_get_node) if (!ClassDB::is_parent_class(GDScriptParser::get_real_class_name(parser->current_class->base_type.native_type), result.native_type)) { push_error(R"*(Cannot use shorthand "get_node()" notation ("$") on a class that isn't a node.)*", p_get_node); + } else if (!lambda_stack.is_empty()) { + push_error(R"*(Cannot use shorthand "get_node()" notation ("$") inside a lambda. Use a captured variable instead.)*", p_get_node); } p_get_node->set_datatype(result); @@ -2614,6 +2618,30 @@ void GDScriptAnalyzer::reduce_lambda(GDScriptParser::LambdaNode *p_lambda) { resolve_suite(p_lambda->function->body); + int captures_amount = p_lambda->captures.size(); + if (captures_amount > 0) { + // Create space for lambda parameters. + // At the beginning to not mess with optional parameters. + int param_count = p_lambda->function->parameters.size(); + p_lambda->function->parameters.resize(param_count + captures_amount); + for (int i = param_count - 1; i >= 0; i--) { + p_lambda->function->parameters.write[i + captures_amount] = p_lambda->function->parameters[i]; + p_lambda->function->parameters_indices[p_lambda->function->parameters[i]->identifier->name] = i + captures_amount; + } + + // Add captures as extra parameters at the beginning. + for (int i = 0; i < p_lambda->captures.size(); i++) { + GDScriptParser::IdentifierNode *capture = p_lambda->captures[i]; + GDScriptParser::ParameterNode *capture_param = parser->alloc_node<GDScriptParser::ParameterNode>(); + capture_param->identifier = capture; + capture_param->usages = capture->usages; + capture_param->set_datatype(capture->get_datatype()); + + p_lambda->function->parameters.write[i] = capture_param; + p_lambda->function->parameters_indices[capture->name] = i; + } + } + lambda_stack.pop_back(); parser->current_function = previous_function; } @@ -3577,13 +3605,6 @@ Ref<GDScriptParserRef> GDScriptAnalyzer::get_parser_for(const String &p_path) { return ref; } -const GDScriptParser::LambdaNode *GDScriptAnalyzer::get_current_lambda() const { - if (lambda_stack.size()) { - return lambda_stack.back()->get(); - } - return nullptr; -} - Error GDScriptAnalyzer::resolve_inheritance() { return resolve_inheritance(parser->head); } diff --git a/modules/gdscript/gdscript_analyzer.h b/modules/gdscript/gdscript_analyzer.h index 7f7e2b8949..aabf407c76 100644 --- a/modules/gdscript/gdscript_analyzer.h +++ b/modules/gdscript/gdscript_analyzer.h @@ -111,7 +111,6 @@ class GDScriptAnalyzer { void mark_node_unsafe(const GDScriptParser::Node *p_node); bool class_exists(const StringName &p_class) const; Ref<GDScriptParserRef> get_parser_for(const String &p_path); - const GDScriptParser::LambdaNode *get_current_lambda() const; #ifdef DEBUG_ENABLED bool is_shadowing(GDScriptParser::IdentifierNode *p_local, const String &p_context); #endif diff --git a/modules/gdscript/gdscript_byte_codegen.cpp b/modules/gdscript/gdscript_byte_codegen.cpp index 89c5f5482b..0da99ccee3 100644 --- a/modules/gdscript/gdscript_byte_codegen.cpp +++ b/modules/gdscript/gdscript_byte_codegen.cpp @@ -383,6 +383,18 @@ GDScriptFunction *GDScriptByteCodeGenerator::write_end() { function->_methods_count = 0; } + if (lambdas_map.size()) { + function->lambdas.resize(lambdas_map.size()); + function->_lambdas_ptr = function->lambdas.ptrw(); + function->_lambdas_count = lambdas_map.size(); + for (const Map<GDScriptFunction *, int>::Element *E = lambdas_map.front(); E; E = E->next()) { + function->lambdas.write[E->get()] = E->key(); + } + } else { + function->_lambdas_ptr = nullptr; + function->_lambdas_count = 0; + } + if (debug_stack) { function->stack_debug = stack_debug; } @@ -1118,6 +1130,17 @@ void GDScriptByteCodeGenerator::write_call_script_function(const Address &p_targ append(p_function_name); } +void GDScriptByteCodeGenerator::write_lambda(const Address &p_target, GDScriptFunction *p_function, const Vector<Address> &p_captures) { + append(GDScriptFunction::OPCODE_CREATE_LAMBDA, 1 + p_captures.size()); + for (int i = 0; i < p_captures.size(); i++) { + append(p_captures[i]); + } + + append(p_target); + append(p_captures.size()); + append(p_function); +} + void GDScriptByteCodeGenerator::write_construct(const Address &p_target, Variant::Type p_type, const Vector<Address> &p_arguments) { // Try to find an appropriate constructor. bool all_have_type = true; diff --git a/modules/gdscript/gdscript_byte_codegen.h b/modules/gdscript/gdscript_byte_codegen.h index 17d681d7bb..c060476f39 100644 --- a/modules/gdscript/gdscript_byte_codegen.h +++ b/modules/gdscript/gdscript_byte_codegen.h @@ -93,6 +93,7 @@ class GDScriptByteCodeGenerator : public GDScriptCodeGenerator { Map<Variant::ValidatedUtilityFunction, int> utilities_map; Map<GDScriptUtilityFunctions::FunctionPtr, int> gds_utilities_map; Map<MethodBind *, int> method_bind_map; + Map<GDScriptFunction *, int> lambdas_map; // Lists since these can be nested. List<int> if_jmp_addrs; @@ -293,6 +294,15 @@ class GDScriptByteCodeGenerator : public GDScriptCodeGenerator { return pos; } + int get_lambda_function_pos(GDScriptFunction *p_lambda_function) { + if (lambdas_map.has(p_lambda_function)) { + return lambdas_map[p_lambda_function]; + } + int pos = lambdas_map.size(); + lambdas_map[p_lambda_function] = pos; + return pos; + } + void alloc_ptrcall(int p_params) { if (p_params >= ptrcall_max) { ptrcall_max = p_params; @@ -386,6 +396,10 @@ class GDScriptByteCodeGenerator : public GDScriptCodeGenerator { opcodes.push_back(get_method_bind_pos(p_method)); } + void append(GDScriptFunction *p_lambda_function) { + opcodes.push_back(get_lambda_function_pos(p_lambda_function)); + } + void patch_jump(int p_address) { opcodes.write[p_address] = opcodes.size(); } @@ -452,6 +466,7 @@ public: virtual void write_call_self(const Address &p_target, const StringName &p_function_name, const Vector<Address> &p_arguments) override; virtual void write_call_self_async(const Address &p_target, const StringName &p_function_name, const Vector<Address> &p_arguments) override; virtual void write_call_script_function(const Address &p_target, const Address &p_base, const StringName &p_function_name, const Vector<Address> &p_arguments) override; + virtual void write_lambda(const Address &p_target, GDScriptFunction *p_function, const Vector<Address> &p_captures) override; virtual void write_construct(const Address &p_target, Variant::Type p_type, const Vector<Address> &p_arguments) override; virtual void write_construct_array(const Address &p_target, const Vector<Address> &p_arguments) override; virtual void write_construct_typed_array(const Address &p_target, const GDScriptDataType &p_element_type, const Vector<Address> &p_arguments) override; diff --git a/modules/gdscript/gdscript_codegen.h b/modules/gdscript/gdscript_codegen.h index b377beefdb..ae9a8ede5e 100644 --- a/modules/gdscript/gdscript_codegen.h +++ b/modules/gdscript/gdscript_codegen.h @@ -127,6 +127,7 @@ public: virtual void write_call_self(const Address &p_target, const StringName &p_function_name, const Vector<Address> &p_arguments) = 0; virtual void write_call_self_async(const Address &p_target, const StringName &p_function_name, const Vector<Address> &p_arguments) = 0; virtual void write_call_script_function(const Address &p_target, const Address &p_base, const StringName &p_function_name, const Vector<Address> &p_arguments) = 0; + virtual void write_lambda(const Address &p_target, GDScriptFunction *p_function, const Vector<Address> &p_captures) = 0; virtual void write_construct(const Address &p_target, Variant::Type p_type, const Vector<Address> &p_arguments) = 0; virtual void write_construct_array(const Address &p_target, const Vector<Address> &p_arguments) = 0; virtual void write_construct_typed_array(const Address &p_target, const GDScriptDataType &p_element_type, const Vector<Address> &p_arguments) = 0; diff --git a/modules/gdscript/gdscript_compiler.cpp b/modules/gdscript/gdscript_compiler.cpp index 9b718db7cf..37ce8ae2cb 100644 --- a/modules/gdscript/gdscript_compiler.cpp +++ b/modules/gdscript/gdscript_compiler.cpp @@ -1091,6 +1091,34 @@ GDScriptCodeGenerator::Address GDScriptCompiler::_parse_expression(CodeGen &code } return GDScriptCodeGenerator::Address(); // Assignment does not return a value. } break; + case GDScriptParser::Node::LAMBDA: { + const GDScriptParser::LambdaNode *lambda = static_cast<const GDScriptParser::LambdaNode *>(p_expression); + GDScriptCodeGenerator::Address result = codegen.add_temporary(_gdtype_from_datatype(lambda->get_datatype())); + + Vector<GDScriptCodeGenerator::Address> captures; + captures.resize(lambda->captures.size()); + for (int i = 0; i < lambda->captures.size(); i++) { + captures.write[i] = _parse_expression(codegen, r_error, lambda->captures[i]); + if (r_error) { + return GDScriptCodeGenerator::Address(); + } + } + + GDScriptFunction *function = _parse_function(r_error, codegen.script, codegen.class_node, lambda->function, false, true); + if (r_error) { + return GDScriptCodeGenerator::Address(); + } + + gen->write_lambda(result, function, captures); + + for (int i = 0; i < captures.size(); i++) { + if (captures[i].mode == GDScriptCodeGenerator::Address::TEMPORARY) { + gen->pop_temporary(); + } + } + + return result; + } break; default: { ERR_FAIL_V_MSG(GDScriptCodeGenerator::Address(), "Bug in bytecode compiler, unexpected node in parse tree while parsing expression."); // Unreachable code. } break; @@ -1804,8 +1832,8 @@ Error GDScriptCompiler::_parse_block(CodeGen &codegen, const GDScriptParser::Sui return OK; } -Error GDScriptCompiler::_parse_function(GDScript *p_script, const GDScriptParser::ClassNode *p_class, const GDScriptParser::FunctionNode *p_func, bool p_for_ready) { - Error error = OK; +GDScriptFunction *GDScriptCompiler::_parse_function(Error &r_error, GDScript *p_script, const GDScriptParser::ClassNode *p_class, const GDScriptParser::FunctionNode *p_func, bool p_for_ready, bool p_for_lambda) { + r_error = OK; CodeGen codegen; codegen.generator = memnew(GDScriptByteCodeGenerator); @@ -1822,7 +1850,11 @@ Error GDScriptCompiler::_parse_function(GDScript *p_script, const GDScriptParser return_type.builtin_type = Variant::NIL; if (p_func) { - func_name = p_func->identifier->name; + if (p_func->identifier) { + func_name = p_func->identifier->name; + } else { + func_name = "<anonymous lambda>"; + } is_static = p_func->is_static; rpc_mode = p_func->rpc_mode; return_type = _gdtype_from_datatype(p_func->get_datatype(), p_script); @@ -1853,11 +1885,11 @@ Error GDScriptCompiler::_parse_function(GDScript *p_script, const GDScriptParser } // 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; - bool is_for_ready = p_for_ready || (p_func && String(p_func->identifier->name) == "_ready"); + bool is_implicit_initializer = !p_for_ready && !p_func && !p_for_lambda; + bool is_initializer = p_func && !p_for_lambda && String(p_func->identifier->name) == GDScriptLanguage::get_singleton()->strings._init; + bool is_for_ready = p_for_ready || (p_func && !p_for_lambda && String(p_func->identifier->name) == "_ready"); - if (is_implicit_initializer || is_for_ready) { + if (!p_for_lambda && (is_implicit_initializer || is_for_ready)) { // Initialize class fields. for (int i = 0; i < p_class->members.size(); i++) { if (p_class->members[i].type != GDScriptParser::ClassNode::Member::VARIABLE) { @@ -1884,10 +1916,10 @@ Error GDScriptCompiler::_parse_function(GDScript *p_script, const GDScriptParser codegen.generator->write_construct_array(dst_address, Vector<GDScriptCodeGenerator::Address>()); } } - GDScriptCodeGenerator::Address src_address = _parse_expression(codegen, error, field->initializer, false, true); - if (error) { + GDScriptCodeGenerator::Address src_address = _parse_expression(codegen, r_error, field->initializer, false, true); + if (r_error) { memdelete(codegen.generator); - return error; + return nullptr; } codegen.generator->write_assign(dst_address, src_address); @@ -1914,10 +1946,10 @@ Error GDScriptCompiler::_parse_function(GDScript *p_script, const GDScriptParser codegen.generator->start_parameters(); for (int i = p_func->parameters.size() - optional_parameters; i < p_func->parameters.size(); i++) { const GDScriptParser::ParameterNode *parameter = p_func->parameters[i]; - GDScriptCodeGenerator::Address src_addr = _parse_expression(codegen, error, parameter->default_value, true); - if (error) { + GDScriptCodeGenerator::Address src_addr = _parse_expression(codegen, r_error, parameter->default_value, true); + if (r_error) { memdelete(codegen.generator); - return error; + return nullptr; } GDScriptCodeGenerator::Address dst_addr = codegen.parameters[parameter->identifier->name]; codegen.generator->write_assign_default_parameter(dst_addr, src_addr); @@ -1928,10 +1960,10 @@ Error GDScriptCompiler::_parse_function(GDScript *p_script, const GDScriptParser codegen.generator->end_parameters(); } - Error err = _parse_block(codegen, p_func->body); - if (err) { + r_error = _parse_block(codegen, p_func->body); + if (r_error) { memdelete(codegen.generator); - return err; + return nullptr; } } @@ -1957,6 +1989,10 @@ Error GDScriptCompiler::_parse_function(GDScript *p_script, const GDScriptParser signature += "::" + String(func_name); } + if (p_for_lambda) { + signature += "(lambda)"; + } + codegen.generator->set_signature(signature); } #endif @@ -1964,8 +2000,10 @@ Error GDScriptCompiler::_parse_function(GDScript *p_script, const GDScriptParser if (p_func) { codegen.generator->set_initial_line(p_func->start_line); #ifdef TOOLS_ENABLED - p_script->member_lines[func_name] = p_func->start_line; - p_script->doc_functions[func_name] = p_func->doc_description; + if (!p_for_lambda) { + p_script->member_lines[func_name] = p_func->start_line; + p_script->doc_functions[func_name] = p_func->doc_description; + } #endif } else { codegen.generator->set_initial_line(0); @@ -1994,11 +2032,13 @@ Error GDScriptCompiler::_parse_function(GDScript *p_script, const GDScriptParser #endif } - p_script->member_functions[func_name] = gd_function; + if (!p_for_lambda) { + p_script->member_functions[func_name] = gd_function; + } memdelete(codegen.generator); - return OK; + return gd_function; } Error GDScriptCompiler::_parse_setter_getter(GDScript *p_script, const GDScriptParser::ClassNode *p_class, const GDScriptParser::VariableNode *p_variable, bool p_is_setter) { @@ -2391,7 +2431,8 @@ Error GDScriptCompiler::_parse_class_blocks(GDScript *p_script, const GDScriptPa if (!has_ready && function->identifier->name == "_ready") { has_ready = true; } - Error err = _parse_function(p_script, p_class, function); + Error err = OK; + _parse_function(err, p_script, p_class, function); if (err) { return err; } @@ -2416,7 +2457,8 @@ Error GDScriptCompiler::_parse_class_blocks(GDScript *p_script, const GDScriptPa { // Create an implicit constructor in any case. - Error err = _parse_function(p_script, p_class, nullptr); + Error err = OK; + _parse_function(err, p_script, p_class, nullptr); if (err) { return err; } @@ -2424,7 +2466,8 @@ Error GDScriptCompiler::_parse_class_blocks(GDScript *p_script, const GDScriptPa if (!has_ready && p_class->onready_used) { //create a _ready constructor - Error err = _parse_function(p_script, p_class, nullptr, true); + Error err = OK; + _parse_function(err, p_script, p_class, nullptr, true); if (err) { return err; } diff --git a/modules/gdscript/gdscript_compiler.h b/modules/gdscript/gdscript_compiler.h index c405eadb07..7d5bee93ac 100644 --- a/modules/gdscript/gdscript_compiler.h +++ b/modules/gdscript/gdscript_compiler.h @@ -128,7 +128,7 @@ class GDScriptCompiler { GDScriptCodeGenerator::Address _parse_match_pattern(CodeGen &codegen, Error &r_error, const GDScriptParser::PatternNode *p_pattern, const GDScriptCodeGenerator::Address &p_value_addr, const GDScriptCodeGenerator::Address &p_type_addr, const GDScriptCodeGenerator::Address &p_previous_test, bool p_is_first, bool p_is_nested); void _add_locals_in_block(CodeGen &codegen, const GDScriptParser::SuiteNode *p_block); Error _parse_block(CodeGen &codegen, const GDScriptParser::SuiteNode *p_block, bool p_add_locals = true); - Error _parse_function(GDScript *p_script, const GDScriptParser::ClassNode *p_class, const GDScriptParser::FunctionNode *p_func, bool p_for_ready = false); + GDScriptFunction *_parse_function(Error &r_error, GDScript *p_script, const GDScriptParser::ClassNode *p_class, const GDScriptParser::FunctionNode *p_func, bool p_for_ready = false, bool p_for_lambda = false); Error _parse_setter_getter(GDScript *p_script, const GDScriptParser::ClassNode *p_class, const GDScriptParser::VariableNode *p_variable, bool p_is_setter); Error _parse_class_level(GDScript *p_script, const GDScriptParser::ClassNode *p_class, bool p_keep_state); Error _parse_class_blocks(GDScript *p_script, const GDScriptParser::ClassNode *p_class, bool p_keep_state); diff --git a/modules/gdscript/gdscript_disassembler.cpp b/modules/gdscript/gdscript_disassembler.cpp index 0d0afcc741..789af57b4c 100644 --- a/modules/gdscript/gdscript_disassembler.cpp +++ b/modules/gdscript/gdscript_disassembler.cpp @@ -721,7 +721,7 @@ void GDScriptFunction::disassemble(const Vector<String> &p_code_lines) const { text += "await "; text += DADDR(1); - incr += 2; + incr = 2; } break; case OPCODE_AWAIT_RESUME: { text += "await resume "; @@ -729,6 +729,25 @@ void GDScriptFunction::disassemble(const Vector<String> &p_code_lines) const { incr = 2; } break; + case OPCODE_CREATE_LAMBDA: { + int captures_count = _code_ptr[ip + 1 + instr_var_args]; + GDScriptFunction *lambda = _lambdas_ptr[_code_ptr[ip + 2 + instr_var_args]]; + + text += DADDR(1 + captures_count); + text += "create lambda from "; + text += lambda->name.operator String(); + text += "function, captures ("; + + for (int i = 0; i < captures_count; i++) { + if (i > 0) { + text += ", "; + } + text += DADDR(1 + i); + } + text += ")"; + + incr = 3 + captures_count; + } break; case OPCODE_JUMP: { text += "jump "; text += itos(_code_ptr[ip + 1]); diff --git a/modules/gdscript/gdscript_function.cpp b/modules/gdscript/gdscript_function.cpp index 7b37aa40a2..78399114a5 100644 --- a/modules/gdscript/gdscript_function.cpp +++ b/modules/gdscript/gdscript_function.cpp @@ -150,6 +150,10 @@ GDScriptFunction::GDScriptFunction() { } GDScriptFunction::~GDScriptFunction() { + for (int i = 0; i < lambdas.size(); i++) { + memdelete(lambdas[i]); + } + #ifdef DEBUG_ENABLED MutexLock lock(GDScriptLanguage::get_singleton()->lock); diff --git a/modules/gdscript/gdscript_function.h b/modules/gdscript/gdscript_function.h index fbec734a28..70b62ced6d 100644 --- a/modules/gdscript/gdscript_function.h +++ b/modules/gdscript/gdscript_function.h @@ -301,6 +301,7 @@ public: OPCODE_CALL_PTRCALL_PACKED_COLOR_ARRAY, OPCODE_AWAIT, OPCODE_AWAIT_RESUME, + OPCODE_CREATE_LAMBDA, OPCODE_JUMP, OPCODE_JUMP_IF, OPCODE_JUMP_IF_NOT, @@ -459,6 +460,8 @@ private: const GDScriptUtilityFunctions::FunctionPtr *_gds_utilities_ptr = nullptr; int _methods_count = 0; MethodBind **_methods_ptr = nullptr; + int _lambdas_count = 0; + GDScriptFunction **_lambdas_ptr = nullptr; const int *_code_ptr = nullptr; int _code_size = 0; int _argument_count = 0; @@ -488,6 +491,7 @@ private: Vector<Variant::ValidatedUtilityFunction> utilities; Vector<GDScriptUtilityFunctions::FunctionPtr> gds_utilities; Vector<MethodBind *> methods; + Vector<GDScriptFunction *> lambdas; Vector<int> code; Vector<GDScriptDataType> argument_types; GDScriptDataType return_type; diff --git a/modules/gdscript/gdscript_lambda_callable.cpp b/modules/gdscript/gdscript_lambda_callable.cpp new file mode 100644 index 0000000000..0bc109b6e1 --- /dev/null +++ b/modules/gdscript/gdscript_lambda_callable.cpp @@ -0,0 +1,95 @@ +/*************************************************************************/ +/* gdscript_lambda_callable.cpp */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2021 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_lambda_callable.h" + +#include "core/templates/hashfuncs.h" +#include "gdscript.h" + +bool GDScriptLambdaCallable::compare_equal(const CallableCustom *p_a, const CallableCustom *p_b) { + // Lambda callables are only compared by reference. + return p_a == p_b; +} + +bool GDScriptLambdaCallable::compare_less(const CallableCustom *p_a, const CallableCustom *p_b) { + // Lambda callables are only compared by reference. + return p_a < p_b; +} + +uint32_t GDScriptLambdaCallable::hash() const { + return h; +} + +String GDScriptLambdaCallable::get_as_text() const { + if (function->get_name() != StringName()) { + return function->get_name().operator String() + "(lambda)"; + } + return "(anonymous lambda)"; +} + +CallableCustom::CompareEqualFunc GDScriptLambdaCallable::get_compare_equal_func() const { + return compare_equal; +} + +CallableCustom::CompareLessFunc GDScriptLambdaCallable::get_compare_less_func() const { + return compare_less; +} + +ObjectID GDScriptLambdaCallable::get_object() const { + return script->get_instance_id(); +} + +void GDScriptLambdaCallable::call(const Variant **p_arguments, int p_argcount, Variant &r_return_value, Callable::CallError &r_call_error) const { + int captures_amount = captures.size(); + + if (captures_amount > 0) { + Vector<const Variant *> args; + args.resize(p_argcount + captures_amount); + for (int i = 0; i < captures_amount; i++) { + args.write[i] = &captures[i]; + } + for (int i = 0; i < p_argcount; i++) { + args.write[i + captures_amount] = p_arguments[i]; + } + + r_return_value = function->call(nullptr, args.ptrw(), args.size(), r_call_error); + r_call_error.argument -= captures_amount; + } else { + r_return_value = function->call(nullptr, p_arguments, p_argcount, r_call_error); + } +} + +GDScriptLambdaCallable::GDScriptLambdaCallable(Ref<GDScript> p_script, GDScriptFunction *p_function, const Vector<Variant> &p_captures) { + script = p_script; + function = p_function; + captures = p_captures; + + h = (uint32_t)hash_djb2_one_64((uint64_t)this); +} diff --git a/modules/gdscript/gdscript_lambda_callable.h b/modules/gdscript/gdscript_lambda_callable.h new file mode 100644 index 0000000000..357c845250 --- /dev/null +++ b/modules/gdscript/gdscript_lambda_callable.h @@ -0,0 +1,65 @@ +/*************************************************************************/ +/* gdscript_lambda_callable.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2021 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. */ +/*************************************************************************/ + +#ifndef GDSCRIPT_LAMBDA_CALLABLE +#define GDSCRIPT_LAMBDA_CALLABLE + +#include "core/object/reference.h" +#include "core/templates/vector.h" +#include "core/variant/callable.h" +#include "core/variant/variant.h" + +class GDScript; +class GDScriptFunction; +class GDScriptInstance; + +class GDScriptLambdaCallable : public CallableCustom { + GDScriptFunction *function = nullptr; + Ref<GDScript> script; + uint32_t h; + + Vector<Variant> captures; + + static bool compare_equal(const CallableCustom *p_a, const CallableCustom *p_b); + static bool compare_less(const CallableCustom *p_a, const CallableCustom *p_b); + +public: + uint32_t hash() const override; + String get_as_text() const override; + CompareEqualFunc get_compare_equal_func() const override; + CompareLessFunc get_compare_less_func() const override; + ObjectID get_object() const override; + void call(const Variant **p_arguments, int p_argcount, Variant &r_return_value, Callable::CallError &r_call_error) const override; + + GDScriptLambdaCallable(Ref<GDScript> p_script, GDScriptFunction *p_function, const Vector<Variant> &p_captures); + virtual ~GDScriptLambdaCallable() = default; +}; + +#endif // GDSCRIPT_LAMBDA_CALLABLE diff --git a/modules/gdscript/gdscript_parser.cpp b/modules/gdscript/gdscript_parser.cpp index 475c590e8a..d4fe88a822 100644 --- a/modules/gdscript/gdscript_parser.cpp +++ b/modules/gdscript/gdscript_parser.cpp @@ -4032,11 +4032,11 @@ void GDScriptParser::TreePrinter::print_if(IfNode *p_if, bool p_is_elif) { void GDScriptParser::TreePrinter::print_lambda(LambdaNode *p_lambda) { print_function(p_lambda->function, "Lambda"); push_text("| captures [ "); - for (const Map<StringName, IdentifierNode *>::Element *E = p_lambda->captures.front(); E; E = E->next()) { - push_text(E->key().operator String()); - if (E->next()) { + for (int i = 0; i < p_lambda->captures.size(); i++) { + if (i > 0) { push_text(" , "); } + push_text(p_lambda->captures[i]->name.operator String()); } push_line(" ]"); } diff --git a/modules/gdscript/gdscript_parser.h b/modules/gdscript/gdscript_parser.h index a988d7a6b5..b1b29a7bd1 100644 --- a/modules/gdscript/gdscript_parser.h +++ b/modules/gdscript/gdscript_parser.h @@ -796,7 +796,8 @@ public: struct LambdaNode : public ExpressionNode { FunctionNode *function = nullptr; FunctionNode *parent_function = nullptr; - Map<StringName, IdentifierNode *> captures; + Vector<IdentifierNode *> captures; + Map<StringName, int> captures_indices; bool has_name() const { return function && function->identifier; diff --git a/modules/gdscript/gdscript_vm.cpp b/modules/gdscript/gdscript_vm.cpp index b47a4eb992..4757ec6ca9 100644 --- a/modules/gdscript/gdscript_vm.cpp +++ b/modules/gdscript/gdscript_vm.cpp @@ -33,6 +33,7 @@ #include "core/core_string_names.h" #include "core/os/os.h" #include "gdscript.h" +#include "gdscript_lambda_callable.h" Variant *GDScriptFunction::_get_variant(int p_address, GDScriptInstance *p_instance, Variant *p_stack, String &r_error) const { int address = p_address & ADDR_MASK; @@ -232,6 +233,7 @@ String GDScriptFunction::_get_call_error(const Callable::CallError &p_err, const &&OPCODE_CALL_PTRCALL_PACKED_COLOR_ARRAY, \ &&OPCODE_AWAIT, \ &&OPCODE_AWAIT_RESUME, \ + &&OPCODE_CREATE_LAMBDA, \ &&OPCODE_JUMP, \ &&OPCODE_JUMP_IF, \ &&OPCODE_JUMP_IF_NOT, \ @@ -1452,13 +1454,17 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a if (err.error != Callable::CallError::CALL_OK) { String methodstr = *methodname; String basestr = _get_var_type(base); + bool is_callable = false; if (methodstr == "call") { - if (argc >= 1) { + if (argc >= 1 && base->get_type() != Variant::CALLABLE) { methodstr = String(*argptrs[0]) + " (via call)"; if (err.error == Callable::CallError::CALL_ERROR_INVALID_ARGUMENT) { err.argument += 1; } + } else { + methodstr = base->operator String() + " (Callable)"; + is_callable = true; } } else if (methodstr == "free") { if (err.error == Callable::CallError::CALL_ERROR_INVALID_METHOD) { @@ -1478,7 +1484,7 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a } } } - err_text = _get_call_error(err, "function '" + methodstr + "' in base '" + basestr + "'", (const Variant **)argptrs); + err_text = _get_call_error(err, "function '" + methodstr + (is_callable ? "" : "' in base '" + basestr) + "'", (const Variant **)argptrs); OPCODE_BREAK; } #endif @@ -2057,6 +2063,34 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a } DISPATCH_OPCODE; + OPCODE(OPCODE_CREATE_LAMBDA) { + CHECK_SPACE(2 + instr_arg_count); + + ip += instr_arg_count; + + int captures_count = _code_ptr[ip + 1]; + GD_ERR_BREAK(captures_count < 0); + + int lambda_index = _code_ptr[ip + 2]; + GD_ERR_BREAK(lambda_index < 0 || lambda_index >= _lambdas_count); + GDScriptFunction *lambda = _lambdas_ptr[lambda_index]; + + Vector<Variant> captures; + captures.resize(captures_count); + for (int i = 0; i < captures_count; i++) { + GET_INSTRUCTION_ARG(arg, i); + captures.write[i] = *arg; + } + + GDScriptLambdaCallable *callable = memnew(GDScriptLambdaCallable(Ref<GDScript>(script), lambda, captures)); + + GET_INSTRUCTION_ARG(result, captures_count); + *result = Callable(callable); + + ip += 3; + } + DISPATCH_OPCODE; + OPCODE(OPCODE_JUMP) { CHECK_SPACE(2); int to = _code_ptr[ip + 1]; |