Merge pull request #43890 from vnen/gdscript-builtin-functions-refactor

GDScript: Refactor builtin functions
This commit is contained in:
Rémi Verschelde 2020-12-15 20:51:38 +01:00 committed by GitHub
commit abfc528439
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
17 changed files with 1085 additions and 2116 deletions

View file

@ -2122,8 +2122,11 @@ void GDScriptLanguage::get_reserved_words(List<String> *p_words) const {
w++;
}
for (int i = 0; i < GDScriptFunctions::FUNC_MAX; i++) {
p_words->push_back(GDScriptFunctions::get_func_name(GDScriptFunctions::Function(i)));
List<StringName> functions;
GDScriptUtilityFunctions::get_function_list(&functions);
for (const List<StringName>::Element *E = functions.front(); E; E = E->next()) {
p_words->push_back(String(E->get()));
}
}

View file

@ -72,8 +72,8 @@ class GDScript : public Script {
friend class GDScriptFunction;
friend class GDScriptAnalyzer;
friend class GDScriptCompiler;
friend class GDScriptFunctions;
friend class GDScriptLanguage;
friend struct GDScriptUtilityFunctionsDefinitions;
Ref<GDScriptNativeClass> native;
Ref<GDScript> base;
@ -270,8 +270,8 @@ public:
class GDScriptInstance : public ScriptInstance {
friend class GDScript;
friend class GDScriptFunction;
friend class GDScriptFunctions;
friend class GDScriptCompiler;
friend struct GDScriptUtilityFunctionsDefinitions;
ObjectID owner_id;
Object *owner;

View file

@ -37,6 +37,7 @@
#include "core/os/file_access.h"
#include "core/templates/hash_map.h"
#include "gdscript.h"
#include "gdscript_utility_functions.h"
// TODO: Move this to a central location (maybe core?).
static HashMap<StringName, StringName> underscore_map;
@ -72,6 +73,39 @@ static StringName get_real_class_name(const StringName &p_source) {
return p_source;
}
static MethodInfo info_from_utility_func(const StringName &p_function) {
ERR_FAIL_COND_V(!Variant::has_utility_function(p_function), MethodInfo());
MethodInfo info(p_function);
if (Variant::has_utility_function_return_value(p_function)) {
info.return_val.type = Variant::get_utility_function_return_type(p_function);
if (info.return_val.type == Variant::NIL) {
info.return_val.usage |= PROPERTY_USAGE_NIL_IS_VARIANT;
}
}
if (Variant::is_utility_function_vararg(p_function)) {
info.flags |= METHOD_FLAG_VARARG;
} else {
for (int i = 0; i < Variant::get_utility_function_argument_count(p_function); i++) {
PropertyInfo pi;
#ifdef DEBUG_METHODS_ENABLED
pi.name = Variant::get_utility_function_argument_name(p_function, i);
#else
pi.name = "arg" + itos(i + 1);
#endif
pi.type = Variant::get_utility_function_argument_type(p_function, i);
if (pi.type == Variant::NIL) {
pi.usage |= PROPERTY_USAGE_NIL_IS_VARIANT;
}
info.arguments.push_back(pi);
}
}
return info;
}
void GDScriptAnalyzer::cleanup() {
underscore_map.clear();
}
@ -1701,7 +1735,6 @@ void GDScriptAnalyzer::reduce_call(GDScriptParser::CallNode *p_call, bool is_awa
// Call to name directly.
StringName function_name = p_call->function_name;
Variant::Type builtin_type = GDScriptParser::get_builtin_type(function_name);
GDScriptFunctions::Function builtin_function = GDScriptParser::get_builtin_function(function_name);
if (builtin_type < Variant::VARIANT_MAX) {
// Is a builtin constructor.
@ -1843,10 +1876,10 @@ void GDScriptAnalyzer::reduce_call(GDScriptParser::CallNode *p_call, bool is_awa
}
p_call->set_datatype(call_type);
return;
} else if (builtin_function < GDScriptFunctions::FUNC_MAX) {
MethodInfo function_info = GDScriptFunctions::get_info(builtin_function);
} else if (GDScriptUtilityFunctions::function_exists(function_name)) {
MethodInfo function_info = GDScriptUtilityFunctions::get_function_info(function_name);
if (all_is_constant && GDScriptFunctions::is_deterministic(builtin_function)) {
if (all_is_constant && GDScriptUtilityFunctions::is_function_constant(function_name)) {
// Can call on compilation.
Vector<const Variant *> args;
for (int i = 0; i < p_call->arguments.size(); i++) {
@ -1855,23 +1888,65 @@ void GDScriptAnalyzer::reduce_call(GDScriptParser::CallNode *p_call, bool is_awa
Variant value;
Callable::CallError err;
GDScriptFunctions::call(builtin_function, (const Variant **)args.ptr(), args.size(), value, err);
GDScriptUtilityFunctions::get_function(function_name)(&value, (const Variant **)args.ptr(), args.size(), err);
switch (err.error) {
case Callable::CallError::CALL_ERROR_INVALID_ARGUMENT: {
PropertyInfo wrong_arg = function_info.arguments[err.argument];
push_error(vformat(R"*(Invalid argument for "%s()" function: argument %d should be %s but is %s.)*", GDScriptFunctions::get_func_name(builtin_function), err.argument + 1,
push_error(vformat(R"*(Invalid argument for "%s()" function: argument %d should be %s but is %s.)*", function_name, err.argument + 1,
type_from_property(wrong_arg).to_string(), p_call->arguments[err.argument]->get_datatype().to_string()),
p_call->arguments[err.argument]);
} break;
case Callable::CallError::CALL_ERROR_INVALID_METHOD:
push_error(vformat(R"(Invalid call for function "%s".)", GDScriptFunctions::get_func_name(builtin_function)), p_call);
push_error(vformat(R"(Invalid call for function "%s".)", function_name), p_call);
break;
case Callable::CallError::CALL_ERROR_TOO_MANY_ARGUMENTS:
push_error(vformat(R"*(Too many arguments for "%s()" call. Expected at most %d but received %d.)*", GDScriptFunctions::get_func_name(builtin_function), err.expected, p_call->arguments.size()), p_call);
push_error(vformat(R"*(Too many arguments for "%s()" call. Expected at most %d but received %d.)*", function_name, err.expected, p_call->arguments.size()), p_call);
break;
case Callable::CallError::CALL_ERROR_TOO_FEW_ARGUMENTS:
push_error(vformat(R"*(Too few arguments for "%s()" call. Expected at least %d but received %d.)*", GDScriptFunctions::get_func_name(builtin_function), err.expected, p_call->arguments.size()), p_call);
push_error(vformat(R"*(Too few arguments for "%s()" call. Expected at least %d but received %d.)*", function_name, err.expected, p_call->arguments.size()), p_call);
break;
case Callable::CallError::CALL_ERROR_INSTANCE_IS_NULL:
break; // Can't happen in a builtin constructor.
case Callable::CallError::CALL_OK:
p_call->is_constant = true;
p_call->reduced_value = value;
break;
}
} else {
validate_call_arg(function_info, p_call);
}
p_call->set_datatype(type_from_property(function_info.return_val));
return;
} else if (Variant::has_utility_function(function_name)) {
MethodInfo function_info = info_from_utility_func(function_name);
if (all_is_constant && Variant::get_utility_function_type(function_name) == Variant::UTILITY_FUNC_TYPE_MATH) {
// Can call on compilation.
Vector<const Variant *> args;
for (int i = 0; i < p_call->arguments.size(); i++) {
args.push_back(&(p_call->arguments[i]->reduced_value));
}
Variant value;
Callable::CallError err;
Variant::call_utility_function(function_name, &value, (const Variant **)args.ptr(), args.size(), err);
switch (err.error) {
case Callable::CallError::CALL_ERROR_INVALID_ARGUMENT: {
PropertyInfo wrong_arg = function_info.arguments[err.argument];
push_error(vformat(R"*(Invalid argument for "%s()" function: argument %d should be %s but is %s.)*", function_name, err.argument + 1,
type_from_property(wrong_arg).to_string(), p_call->arguments[err.argument]->get_datatype().to_string()),
p_call->arguments[err.argument]);
} break;
case Callable::CallError::CALL_ERROR_INVALID_METHOD:
push_error(vformat(R"(Invalid call for function "%s".)", function_name), p_call);
break;
case Callable::CallError::CALL_ERROR_TOO_MANY_ARGUMENTS:
push_error(vformat(R"*(Too many arguments for "%s()" call. Expected at most %d but received %d.)*", function_name, err.expected, p_call->arguments.size()), p_call);
break;
case Callable::CallError::CALL_ERROR_TOO_FEW_ARGUMENTS:
push_error(vformat(R"*(Too few arguments for "%s()" call. Expected at least %d but received %d.)*", function_name, err.expected, p_call->arguments.size()), p_call);
break;
case Callable::CallError::CALL_ERROR_INSTANCE_IS_NULL:
break; // Can't happen in a builtin constructor.
@ -2385,7 +2460,7 @@ void GDScriptAnalyzer::reduce_identifier(GDScriptParser::IdentifierNode *p_ident
// Not found.
// Check if it's a builtin function.
if (parser->get_builtin_function(name) < GDScriptFunctions::FUNC_MAX) {
if (GDScriptUtilityFunctions::function_exists(name)) {
push_error(vformat(R"(Built-in function "%s" cannot be used as an identifier.)", name), p_identifier);
} else {
push_error(vformat(R"(Identifier "%s" not declared in the current scope.)", name), p_identifier);

View file

@ -283,6 +283,30 @@ GDScriptFunction *GDScriptByteCodeGenerator::write_end() {
function->_constructors_count = 0;
}
if (utilities_map.size()) {
function->utilities.resize(utilities_map.size());
function->_utilities_ptr = function->utilities.ptr();
function->_utilities_count = utilities_map.size();
for (const Map<Variant::ValidatedUtilityFunction, int>::Element *E = utilities_map.front(); E; E = E->next()) {
function->utilities.write[E->get()] = E->key();
}
} else {
function->_utilities_ptr = nullptr;
function->_utilities_count = 0;
}
if (gds_utilities_map.size()) {
function->gds_utilities.resize(gds_utilities_map.size());
function->_gds_utilities_ptr = function->gds_utilities.ptr();
function->_gds_utilities_count = gds_utilities_map.size();
for (const Map<GDScriptUtilityFunctions::FunctionPtr, int>::Element *E = gds_utilities_map.front(); E; E = E->next()) {
function->gds_utilities.write[E->get()] = E->key();
}
} else {
function->_gds_utilities_ptr = nullptr;
function->_gds_utilities_count = 0;
}
if (method_bind_map.size()) {
function->methods.resize(method_bind_map.size());
function->_methods_ptr = function->methods.ptrw();
@ -704,8 +728,8 @@ void GDScriptByteCodeGenerator::write_call_async(const Address &p_target, const
append(p_function_name);
}
void GDScriptByteCodeGenerator::write_call_builtin(const Address &p_target, GDScriptFunctions::Function p_function, const Vector<Address> &p_arguments) {
append(GDScriptFunction::OPCODE_CALL_BUILT_IN, 1 + p_arguments.size());
void GDScriptByteCodeGenerator::write_call_gdscript_utility(const Address &p_target, GDScriptUtilityFunctions::FunctionPtr p_function, const Vector<Address> &p_arguments) {
append(GDScriptFunction::OPCODE_CALL_GDSCRIPT_UTILITY, 1 + p_arguments.size());
for (int i = 0; i < p_arguments.size(); i++) {
append(p_arguments[i]);
}
@ -714,6 +738,41 @@ void GDScriptByteCodeGenerator::write_call_builtin(const Address &p_target, GDSc
append(p_function);
}
void GDScriptByteCodeGenerator::write_call_utility(const Address &p_target, const StringName &p_function, const Vector<Address> &p_arguments) {
bool is_validated = true;
if (Variant::is_utility_function_vararg(p_function)) {
is_validated = true; // Vararg works fine with any argument, since they can be any type.
} else if (p_arguments.size() == Variant::get_utility_function_argument_count(p_function)) {
bool all_types_exact = true;
for (int i = 0; i < p_arguments.size(); i++) {
if (!IS_BUILTIN_TYPE(p_arguments[i], Variant::get_utility_function_argument_type(p_function, i))) {
all_types_exact = false;
break;
}
}
is_validated = all_types_exact;
}
if (is_validated) {
append(GDScriptFunction::OPCODE_CALL_UTILITY_VALIDATED, 1 + p_arguments.size());
for (int i = 0; i < p_arguments.size(); i++) {
append(p_arguments[i]);
}
append(p_target);
append(p_arguments.size());
append(Variant::get_validated_utility_function(p_function));
} else {
append(GDScriptFunction::OPCODE_CALL_UTILITY, 1 + p_arguments.size());
for (int i = 0; i < p_arguments.size(); i++) {
append(p_arguments[i]);
}
append(p_target);
append(p_arguments.size());
append(p_function);
}
}
void GDScriptByteCodeGenerator::write_call_builtin_type(const Address &p_target, const Address &p_base, Variant::Type p_type, const StringName &p_method, const Vector<Address> &p_arguments) {
bool is_validated = false;

View file

@ -34,6 +34,7 @@
#include "gdscript_codegen.h"
#include "gdscript_function.h"
#include "gdscript_utility_functions.h"
class GDScriptByteCodeGenerator : public GDScriptCodeGenerator {
bool ended = false;
@ -76,6 +77,8 @@ class GDScriptByteCodeGenerator : public GDScriptCodeGenerator {
Map<Variant::ValidatedIndexedGetter, int> indexed_getters_map;
Map<Variant::ValidatedBuiltInMethod, int> builtin_method_map;
Map<Variant::ValidatedConstructor, int> constructors_map;
Map<Variant::ValidatedUtilityFunction, int> utilities_map;
Map<GDScriptUtilityFunctions::FunctionPtr, int> gds_utilities_map;
Map<MethodBind *, int> method_bind_map;
// Lists since these can be nested.
@ -241,6 +244,24 @@ class GDScriptByteCodeGenerator : public GDScriptCodeGenerator {
return pos;
}
int get_utility_pos(const Variant::ValidatedUtilityFunction p_utility) {
if (utilities_map.has(p_utility)) {
return utilities_map[p_utility];
}
int pos = utilities_map.size();
utilities_map[p_utility] = pos;
return pos;
}
int get_gds_utility_pos(const GDScriptUtilityFunctions::FunctionPtr p_gds_utility) {
if (gds_utilities_map.has(p_gds_utility)) {
return gds_utilities_map[p_gds_utility];
}
int pos = gds_utilities_map.size();
gds_utilities_map[p_gds_utility] = pos;
return pos;
}
int get_method_bind_pos(MethodBind *p_method) {
if (method_bind_map.has(p_method)) {
return method_bind_map[p_method];
@ -346,6 +367,14 @@ class GDScriptByteCodeGenerator : public GDScriptCodeGenerator {
opcodes.push_back(get_constructor_pos(p_constructor));
}
void append(const Variant::ValidatedUtilityFunction p_utility) {
opcodes.push_back(get_utility_pos(p_utility));
}
void append(const GDScriptUtilityFunctions::FunctionPtr p_gds_utility) {
opcodes.push_back(get_gds_utility_pos(p_gds_utility));
}
void append(MethodBind *p_method) {
opcodes.push_back(get_method_bind_pos(p_method));
}
@ -406,7 +435,8 @@ public:
virtual void write_call(const Address &p_target, const Address &p_base, const StringName &p_function_name, const Vector<Address> &p_arguments) override;
virtual void write_super_call(const Address &p_target, const StringName &p_function_name, const Vector<Address> &p_arguments) override;
virtual void write_call_async(const Address &p_target, const Address &p_base, const StringName &p_function_name, const Vector<Address> &p_arguments) override;
virtual void write_call_builtin(const Address &p_target, GDScriptFunctions::Function p_function, const Vector<Address> &p_arguments) override;
virtual void write_call_utility(const Address &p_target, const StringName &p_function, const Vector<Address> &p_arguments) override;
virtual void write_call_gdscript_utility(const Address &p_target, GDScriptUtilityFunctions::FunctionPtr p_function, const Vector<Address> &p_arguments) override;
virtual void write_call_builtin_type(const Address &p_target, const Address &p_base, Variant::Type p_type, const StringName &p_method, const Vector<Address> &p_arguments) override;
virtual void write_call_method_bind(const Address &p_target, const Address &p_base, MethodBind *p_method, const Vector<Address> &p_arguments) override;
virtual void write_call_ptrcall(const Address &p_target, const Address &p_base, MethodBind *p_method, const Vector<Address> &p_arguments) override;

View file

@ -35,7 +35,7 @@
#include "core/string/string_name.h"
#include "core/variant/variant.h"
#include "gdscript_function.h"
#include "gdscript_functions.h"
#include "gdscript_utility_functions.h"
class GDScriptCodeGenerator {
public:
@ -127,7 +127,8 @@ public:
virtual void write_call(const Address &p_target, const Address &p_base, const StringName &p_function_name, const Vector<Address> &p_arguments) = 0;
virtual void write_super_call(const Address &p_target, const StringName &p_function_name, const Vector<Address> &p_arguments) = 0;
virtual void write_call_async(const Address &p_target, const Address &p_base, const StringName &p_function_name, const Vector<Address> &p_arguments) = 0;
virtual void write_call_builtin(const Address &p_target, GDScriptFunctions::Function p_function, const Vector<Address> &p_arguments) = 0;
virtual void write_call_utility(const Address &p_target, const StringName &p_function, const Vector<Address> &p_arguments) = 0;
virtual void write_call_gdscript_utility(const Address &p_target, GDScriptUtilityFunctions::FunctionPtr p_function, const Vector<Address> &p_arguments) = 0;
virtual void write_call_builtin_type(const Address &p_target, const Address &p_base, Variant::Type p_type, const StringName &p_method, const Vector<Address> &p_arguments) = 0;
virtual void write_call_method_bind(const Address &p_target, const Address &p_base, MethodBind *p_method, const Vector<Address> &p_arguments) = 0;
virtual void write_call_ptrcall(const Address &p_target, const Address &p_base, MethodBind *p_method, const Vector<Address> &p_arguments) = 0;

View file

@ -33,6 +33,7 @@
#include "gdscript.h"
#include "gdscript_byte_codegen.h"
#include "gdscript_cache.h"
#include "gdscript_utility_functions.h"
bool GDScriptCompiler::_is_class_member_property(CodeGen &codegen, const StringName &p_name) {
if (codegen.function_node && codegen.function_node->is_static) {
@ -456,15 +457,17 @@ GDScriptCodeGenerator::Address GDScriptCompiler::_parse_expression(CodeGen &code
arguments.push_back(arg);
}
if (!call->is_super && call->callee->type == GDScriptParser::Node::IDENTIFIER && GDScriptParser::get_builtin_type(static_cast<GDScriptParser::IdentifierNode *>(call->callee)->name) != Variant::VARIANT_MAX) {
if (!call->is_super && call->callee->type == GDScriptParser::Node::IDENTIFIER && GDScriptParser::get_builtin_type(call->function_name) != Variant::VARIANT_MAX) {
// Construct a built-in type.
Variant::Type vtype = GDScriptParser::get_builtin_type(static_cast<GDScriptParser::IdentifierNode *>(call->callee)->name);
gen->write_construct(result, vtype, arguments);
} else if (!call->is_super && call->callee->type == GDScriptParser::Node::IDENTIFIER && GDScriptParser::get_builtin_function(static_cast<GDScriptParser::IdentifierNode *>(call->callee)->name) != GDScriptFunctions::FUNC_MAX) {
// Built-in function.
GDScriptFunctions::Function func = GDScriptParser::get_builtin_function(static_cast<GDScriptParser::IdentifierNode *>(call->callee)->name);
gen->write_call_builtin(result, func, arguments);
} else if (!call->is_super && call->callee->type == GDScriptParser::Node::IDENTIFIER && Variant::has_utility_function(call->function_name)) {
// Variant utility function.
gen->write_call_utility(result, call->function_name, arguments);
} else if (!call->is_super && call->callee->type == GDScriptParser::Node::IDENTIFIER && GDScriptUtilityFunctions::function_exists(call->function_name)) {
// GDScript utility function.
gen->write_call_gdscript_utility(result, GDScriptUtilityFunctions::get_function(call->function_name), arguments);
} else {
// Regular function.
const GDScriptParser::ExpressionNode *callee = call->callee;
@ -1135,7 +1138,7 @@ GDScriptCodeGenerator::Address GDScriptCompiler::_parse_match_pattern(CodeGen &c
// Evaluate expression type.
Vector<GDScriptCodeGenerator::Address> typeof_args;
typeof_args.push_back(expr_addr);
codegen.generator->write_call_builtin(result_addr, GDScriptFunctions::TYPE_OF, typeof_args);
codegen.generator->write_call_utility(result_addr, "typeof", typeof_args);
// Check type equality.
codegen.generator->write_binary_operator(result_addr, Variant::OP_EQUAL, p_type_addr, result_addr);
@ -1199,7 +1202,7 @@ GDScriptCodeGenerator::Address GDScriptCompiler::_parse_match_pattern(CodeGen &c
GDScriptCodeGenerator::Address value_length_addr = codegen.add_temporary(temp_type);
Vector<GDScriptCodeGenerator::Address> len_args;
len_args.push_back(p_value_addr);
codegen.generator->write_call_builtin(value_length_addr, GDScriptFunctions::LEN, len_args);
codegen.generator->write_call_gdscript_utility(value_length_addr, GDScriptUtilityFunctions::get_function("len"), len_args);
// Test length compatibility.
temp_type.builtin_type = Variant::BOOL;
@ -1253,7 +1256,7 @@ GDScriptCodeGenerator::Address GDScriptCompiler::_parse_match_pattern(CodeGen &c
// Also get type of element.
Vector<GDScriptCodeGenerator::Address> typeof_args;
typeof_args.push_back(element_addr);
codegen.generator->write_call_builtin(element_type_addr, GDScriptFunctions::TYPE_OF, typeof_args);
codegen.generator->write_call_utility(element_type_addr, "typeof", typeof_args);
// Try the pattern inside the element.
test_addr = _parse_match_pattern(codegen, r_error, p_pattern->array[i], element_addr, element_type_addr, p_previous_test, false, true);
@ -1298,7 +1301,7 @@ GDScriptCodeGenerator::Address GDScriptCompiler::_parse_match_pattern(CodeGen &c
GDScriptCodeGenerator::Address value_length_addr = codegen.add_temporary(temp_type);
Vector<GDScriptCodeGenerator::Address> func_args;
func_args.push_back(p_value_addr);
codegen.generator->write_call_builtin(value_length_addr, GDScriptFunctions::LEN, func_args);
codegen.generator->write_call_gdscript_utility(value_length_addr, GDScriptUtilityFunctions::get_function("len"), func_args);
// Test length compatibility.
temp_type.builtin_type = Variant::BOOL;
@ -1367,7 +1370,7 @@ GDScriptCodeGenerator::Address GDScriptCompiler::_parse_match_pattern(CodeGen &c
// Also get type of value.
func_args.clear();
func_args.push_back(element_addr);
codegen.generator->write_call_builtin(element_type_addr, GDScriptFunctions::TYPE_OF, func_args);
codegen.generator->write_call_utility(element_type_addr, "typeof", func_args);
// Try the pattern inside the value.
test_addr = _parse_match_pattern(codegen, r_error, element.value_pattern, element_addr, element_type_addr, test_addr, false, true);
@ -1500,7 +1503,7 @@ Error GDScriptCompiler::_parse_block(CodeGen &codegen, const GDScriptParser::Sui
Vector<GDScriptCodeGenerator::Address> typeof_args;
typeof_args.push_back(value);
gen->write_call_builtin(type, GDScriptFunctions::TYPE_OF, typeof_args);
gen->write_call_utility(type, "typeof", typeof_args);
// Now we can actually start testing.
// For each branch.

View file

@ -34,7 +34,6 @@
#include "core/string/string_builder.h"
#include "gdscript.h"
#include "gdscript_functions.h"
static String _get_variant_string(const Variant &p_variant) {
String txt;
@ -607,13 +606,49 @@ void GDScriptFunction::disassemble(const Vector<String> &p_code_lines) const {
incr = 5 + argc;
} break;
case OPCODE_CALL_BUILT_IN: {
text += "call-built-in ";
case OPCODE_CALL_UTILITY: {
text += "call-utility ";
int argc = _code_ptr[ip + 1 + instr_var_args];
text += DADDR(1 + argc) + " = ";
text += GDScriptFunctions::get_func_name(GDScriptFunctions::Function(_code_ptr[ip + 2 + instr_var_args]));
text += _global_names_ptr[_code_ptr[ip + 2 + instr_var_args]];
text += "(";
for (int i = 0; i < argc; i++) {
if (i > 0)
text += ", ";
text += DADDR(1 + i);
}
text += ")";
incr = 4 + argc;
} break;
case OPCODE_CALL_UTILITY_VALIDATED: {
text += "call-utility ";
int argc = _code_ptr[ip + 1 + instr_var_args];
text += DADDR(1 + argc) + " = ";
text += "<unkown function>";
text += "(";
for (int i = 0; i < argc; i++) {
if (i > 0)
text += ", ";
text += DADDR(1 + i);
}
text += ")";
incr = 4 + argc;
} break;
case OPCODE_CALL_GDSCRIPT_UTILITY: {
text += "call-gscript-utility ";
int argc = _code_ptr[ip + 1 + instr_var_args];
text += DADDR(1 + argc) + " = ";
text += "<unknown function>";
text += "(";
for (int i = 0; i < argc; i++) {

View file

@ -37,6 +37,7 @@
#include "gdscript_compiler.h"
#include "gdscript_parser.h"
#include "gdscript_tokenizer.h"
#include "gdscript_utility_functions.h"
#ifdef TOOLS_ENABLED
#include "core/config/project_settings.h"
@ -411,11 +412,14 @@ void GDScriptLanguage::get_recognized_extensions(List<String> *p_extensions) con
}
void GDScriptLanguage::get_public_functions(List<MethodInfo> *p_functions) const {
for (int i = 0; i < GDScriptFunctions::FUNC_MAX; i++) {
p_functions->push_back(GDScriptFunctions::get_info(GDScriptFunctions::Function(i)));
List<StringName> functions;
GDScriptUtilityFunctions::get_function_list(&functions);
for (const List<StringName>::Element *E = functions.front(); E; E = E->next()) {
p_functions->push_back(GDScriptUtilityFunctions::get_function_info(E->get()));
}
//not really "functions", but..
// Not really "functions", but show in documentation.
{
MethodInfo mi;
mi.name = "preload";
@ -1030,9 +1034,12 @@ static void _find_identifiers(GDScriptParser::CompletionContext &p_context, bool
_find_identifiers_in_class(p_context.current_class, p_only_functions, (!p_context.current_function || p_context.current_function->is_static), false, r_result, p_recursion_depth + 1);
}
for (int i = 0; i < GDScriptFunctions::FUNC_MAX; i++) {
MethodInfo function = GDScriptFunctions::get_info(GDScriptFunctions::Function(i));
ScriptCodeCompletionOption option(String(GDScriptFunctions::get_func_name(GDScriptFunctions::Function(i))), ScriptCodeCompletionOption::KIND_FUNCTION);
List<StringName> functions;
GDScriptUtilityFunctions::get_function_list(&functions);
for (const List<StringName>::Element *E = functions.front(); E; E = E->next()) {
MethodInfo function = GDScriptUtilityFunctions::get_function_info(E->get());
ScriptCodeCompletionOption option(String(E->get()), ScriptCodeCompletionOption::KIND_FUNCTION);
if (function.arguments.size() || (function.flags & METHOD_FLAG_VARARG)) {
option.insert_text += "(";
} else {
@ -1288,8 +1295,8 @@ static bool _guess_expression_type(GDScriptParser::CompletionContext &p_context,
r_type.type.builtin_type = GDScriptParser::get_builtin_type(call->function_name);
found = true;
break;
} else if (GDScriptParser::get_builtin_function(call->function_name) < GDScriptFunctions::FUNC_MAX) {
MethodInfo mi = GDScriptFunctions::get_info(GDScriptParser::get_builtin_function(call->function_name));
} else if (GDScriptUtilityFunctions::function_exists(call->function_name)) {
MethodInfo mi = GDScriptUtilityFunctions::get_function_info(call->function_name);
r_type = _type_from_property(mi.return_val);
found = true;
break;
@ -2342,8 +2349,8 @@ static void _find_call_arguments(GDScriptParser::CompletionContext &p_context, c
GDScriptCompletionIdentifier connect_base;
if (GDScriptParser::get_builtin_function(call->function_name) < GDScriptFunctions::FUNC_MAX) {
MethodInfo info = GDScriptFunctions::get_info(GDScriptParser::get_builtin_function(call->function_name));
if (GDScriptUtilityFunctions::function_exists(call->function_name)) {
MethodInfo info = GDScriptUtilityFunctions::get_function_info(call->function_name);
r_arghint = _make_arguments_hint(info, p_argidx);
return;
} else if (GDScriptParser::get_builtin_type(call->function_name) < Variant::VARIANT_MAX) {
@ -2971,13 +2978,11 @@ Error GDScriptLanguage::lookup_code(const String &p_code, const String &p_symbol
}
}
for (int i = 0; i < GDScriptFunctions::FUNC_MAX; i++) {
if (GDScriptFunctions::get_func_name(GDScriptFunctions::Function(i)) == p_symbol) {
r_result.type = ScriptLanguage::LookupResult::RESULT_CLASS_METHOD;
r_result.class_name = "@GDScript";
r_result.class_member = p_symbol;
return OK;
}
if (GDScriptUtilityFunctions::function_exists(p_symbol)) {
r_result.type = ScriptLanguage::LookupResult::RESULT_CLASS_METHOD;
r_result.class_name = "@GDScript";
r_result.class_member = p_symbol;
return OK;
}
if ("PI" == p_symbol || "TAU" == p_symbol || "INF" == p_symbol || "NAN" == p_symbol) {

View file

@ -38,6 +38,7 @@
#include "core/templates/pair.h"
#include "core/templates/self_list.h"
#include "core/variant/variant.h"
#include "gdscript_utility_functions.h"
class GDScriptInstance;
class GDScript;
@ -190,7 +191,9 @@ public:
OPCODE_CALL,
OPCODE_CALL_RETURN,
OPCODE_CALL_ASYNC,
OPCODE_CALL_BUILT_IN,
OPCODE_CALL_UTILITY,
OPCODE_CALL_UTILITY_VALIDATED,
OPCODE_CALL_GDSCRIPT_UTILITY,
OPCODE_CALL_BUILTIN_TYPE_VALIDATED,
OPCODE_CALL_SELF_BASE,
OPCODE_CALL_METHOD_BIND,
@ -344,6 +347,10 @@ private:
const Variant::ValidatedBuiltInMethod *_builtin_methods_ptr = nullptr;
int _constructors_count = 0;
const Variant::ValidatedConstructor *_constructors_ptr = nullptr;
int _utilities_count = 0;
const Variant::ValidatedUtilityFunction *_utilities_ptr = nullptr;
int _gds_utilities_count = 0;
const GDScriptUtilityFunctions::FunctionPtr *_gds_utilities_ptr = nullptr;
int _methods_count = 0;
MethodBind **_methods_ptr = nullptr;
const int *_code_ptr = nullptr;
@ -372,6 +379,8 @@ private:
Vector<Variant::ValidatedIndexedGetter> indexed_getters;
Vector<Variant::ValidatedBuiltInMethod> builtin_methods;
Vector<Variant::ValidatedConstructor> constructors;
Vector<Variant::ValidatedUtilityFunction> utilities;
Vector<GDScriptUtilityFunctions::FunctionPtr> gds_utilities;
Vector<MethodBind *> methods;
Vector<int> code;
Vector<GDScriptDataType> argument_types;

File diff suppressed because it is too large Load diff

View file

@ -98,15 +98,6 @@ void GDScriptParser::cleanup() {
builtin_types.clear();
}
GDScriptFunctions::Function GDScriptParser::get_builtin_function(const StringName &p_name) {
for (int i = 0; i < GDScriptFunctions::FUNC_MAX; i++) {
if (p_name == GDScriptFunctions::get_func_name(GDScriptFunctions::Function(i))) {
return GDScriptFunctions::Function(i);
}
}
return GDScriptFunctions::FUNC_MAX;
}
void GDScriptParser::get_annotation_list(List<MethodInfo> *r_annotations) const {
List<StringName> keys;
valid_annotations.get_key_list(&keys);
@ -2553,7 +2544,7 @@ GDScriptParser::ExpressionNode *GDScriptParser::parse_call(ExpressionNode *p_pre
// Arguments.
CompletionType ct = COMPLETION_CALL_ARGUMENTS;
if (get_builtin_function(call->function_name) == GDScriptFunctions::RESOURCE_LOAD) {
if (call->function_name == "load") {
ct = COMPLETION_RESOURCE_PATH;
}
push_completion_call(call);

View file

@ -43,7 +43,6 @@
#include "core/templates/vector.h"
#include "core/variant/variant.h"
#include "gdscript_cache.h"
#include "gdscript_functions.h"
#include "gdscript_tokenizer.h"
#ifdef DEBUG_ENABLED
@ -1314,7 +1313,6 @@ public:
ClassNode *get_tree() const { return head; }
bool is_tool() const { return _is_tool; }
static Variant::Type get_builtin_type(const StringName &p_type);
static GDScriptFunctions::Function get_builtin_function(const StringName &p_name);
CompletionContext get_completion_context() const { return completion_context; }
CompletionCall get_completion_call() const { return completion_call; }

View file

@ -0,0 +1,718 @@
/*************************************************************************/
/* gdscript_utility_functions.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_utility_functions.h"
#include "core/io/resource_loader.h"
#include "core/object/class_db.h"
#include "core/object/method_bind.h"
#include "core/object/object.h"
#include "core/templates/oa_hash_map.h"
#include "core/templates/vector.h"
#include "gdscript.h"
#ifdef DEBUG_ENABLED
#define VALIDATE_ARG_COUNT(m_count) \
if (p_arg_count < m_count) { \
r_error.error = Callable::CallError::CALL_ERROR_TOO_FEW_ARGUMENTS; \
r_error.argument = m_count; \
r_error.expected = m_count; \
*r_ret = Variant(); \
return; \
} \
if (p_arg_count > m_count) { \
r_error.error = Callable::CallError::CALL_ERROR_TOO_MANY_ARGUMENTS; \
r_error.argument = m_count; \
r_error.expected = m_count; \
*r_ret = Variant(); \
return; \
}
#define VALIDATE_ARG_INT(m_arg) \
if (p_args[m_arg]->get_type() != Variant::INT) { \
r_error.error = Callable::CallError::CALL_ERROR_INVALID_ARGUMENT; \
r_error.argument = m_arg; \
r_error.expected = Variant::INT; \
*r_ret = Variant(); \
return; \
}
#define VALIDATE_ARG_NUM(m_arg) \
if (!p_args[m_arg]->is_num()) { \
r_error.error = Callable::CallError::CALL_ERROR_INVALID_ARGUMENT; \
r_error.argument = m_arg; \
r_error.expected = Variant::FLOAT; \
*r_ret = Variant(); \
return; \
}
#else
#define VALIDATE_ARG_COUNT(m_count)
#define VALIDATE_ARG_INT(m_arg)
#define VALIDATE_ARG_NUM(m_arg)
#endif
struct GDScriptUtilityFunctionsDefinitions {
static inline void convert(Variant *r_ret, const Variant **p_args, int p_arg_count, Callable::CallError &r_error) {
VALIDATE_ARG_COUNT(2);
VALIDATE_ARG_INT(1);
int type = *p_args[1];
if (type < 0 || type >= Variant::VARIANT_MAX) {
*r_ret = RTR("Invalid type argument to convert(), use TYPE_* constants.");
r_error.error = Callable::CallError::CALL_ERROR_INVALID_ARGUMENT;
r_error.argument = 0;
r_error.expected = Variant::INT;
return;
} else {
Variant::construct(Variant::Type(type), *r_ret, p_args, 1, r_error);
}
}
static inline void type_exists(Variant *r_ret, const Variant **p_args, int p_arg_count, Callable::CallError &r_error) {
VALIDATE_ARG_COUNT(1);
*r_ret = ClassDB::class_exists(*p_args[0]);
}
static inline void _char(Variant *r_ret, const Variant **p_args, int p_arg_count, Callable::CallError &r_error) {
VALIDATE_ARG_COUNT(1);
VALIDATE_ARG_INT(0);
char32_t result[2] = { *p_args[0], 0 };
*r_ret = String(result);
}
static inline void str(Variant *r_ret, const Variant **p_args, int p_arg_count, Callable::CallError &r_error) {
if (p_arg_count < 1) {
r_error.error = Callable::CallError::CALL_ERROR_TOO_FEW_ARGUMENTS;
r_error.argument = 1;
*r_ret = Variant();
return;
}
String str;
for (int i = 0; i < p_arg_count; i++) {
String os = p_args[i]->operator String();
if (i == 0) {
str = os;
} else {
str += os;
}
}
*r_ret = str;
}
static inline void range(Variant *r_ret, const Variant **p_args, int p_arg_count, Callable::CallError &r_error) {
switch (p_arg_count) {
case 0: {
r_error.error = Callable::CallError::CALL_ERROR_TOO_FEW_ARGUMENTS;
r_error.argument = 1;
r_error.expected = 1;
*r_ret = Variant();
} break;
case 1: {
VALIDATE_ARG_NUM(0);
int count = *p_args[0];
Array arr;
if (count <= 0) {
*r_ret = arr;
return;
}
Error err = arr.resize(count);
if (err != OK) {
r_error.error = Callable::CallError::CALL_ERROR_INVALID_METHOD;
*r_ret = Variant();
return;
}
for (int i = 0; i < count; i++) {
arr[i] = i;
}
*r_ret = arr;
} break;
case 2: {
VALIDATE_ARG_NUM(0);
VALIDATE_ARG_NUM(1);
int from = *p_args[0];
int to = *p_args[1];
Array arr;
if (from >= to) {
*r_ret = arr;
return;
}
Error err = arr.resize(to - from);
if (err != OK) {
r_error.error = Callable::CallError::CALL_ERROR_INVALID_METHOD;
*r_ret = Variant();
return;
}
for (int i = from; i < to; i++) {
arr[i - from] = i;
}
*r_ret = arr;
} break;
case 3: {
VALIDATE_ARG_NUM(0);
VALIDATE_ARG_NUM(1);
VALIDATE_ARG_NUM(2);
int from = *p_args[0];
int to = *p_args[1];
int incr = *p_args[2];
if (incr == 0) {
*r_ret = RTR("Step argument is zero!");
r_error.error = Callable::CallError::CALL_ERROR_INVALID_METHOD;
return;
}
Array arr;
if (from >= to && incr > 0) {
*r_ret = arr;
return;
}
if (from <= to && incr < 0) {
*r_ret = arr;
return;
}
// Calculate how many.
int count = 0;
if (incr > 0) {
count = ((to - from - 1) / incr) + 1;
} else {
count = ((from - to - 1) / -incr) + 1;
}
Error err = arr.resize(count);
if (err != OK) {
r_error.error = Callable::CallError::CALL_ERROR_INVALID_METHOD;
*r_ret = Variant();
return;
}
if (incr > 0) {
int idx = 0;
for (int i = from; i < to; i += incr) {
arr[idx++] = i;
}
} else {
int idx = 0;
for (int i = from; i > to; i += incr) {
arr[idx++] = i;
}
}
*r_ret = arr;
} break;
default: {
r_error.error = Callable::CallError::CALL_ERROR_TOO_MANY_ARGUMENTS;
r_error.argument = 3;
r_error.expected = 3;
*r_ret = Variant();
} break;
}
}
static inline void load(Variant *r_ret, const Variant **p_args, int p_arg_count, Callable::CallError &r_error) {
VALIDATE_ARG_COUNT(1);
if (p_args[0]->get_type() != Variant::STRING) {
r_error.error = Callable::CallError::CALL_ERROR_INVALID_ARGUMENT;
r_error.argument = 0;
r_error.expected = Variant::STRING;
*r_ret = Variant();
} else {
*r_ret = ResourceLoader::load(*p_args[0]);
}
}
static inline void inst2dict(Variant *r_ret, const Variant **p_args, int p_arg_count, Callable::CallError &r_error) {
VALIDATE_ARG_COUNT(1);
if (p_args[0]->get_type() == Variant::NIL) {
*r_ret = Variant();
} else if (p_args[0]->get_type() != Variant::OBJECT) {
r_error.error = Callable::CallError::CALL_ERROR_INVALID_ARGUMENT;
r_error.argument = 0;
*r_ret = Variant();
} else {
Object *obj = *p_args[0];
if (!obj) {
*r_ret = Variant();
} else if (!obj->get_script_instance() || obj->get_script_instance()->get_language() != GDScriptLanguage::get_singleton()) {
r_error.error = Callable::CallError::CALL_ERROR_INVALID_ARGUMENT;
r_error.argument = 0;
r_error.expected = Variant::DICTIONARY;
*r_ret = RTR("Not a script with an instance");
return;
} else {
GDScriptInstance *ins = static_cast<GDScriptInstance *>(obj->get_script_instance());
Ref<GDScript> base = ins->get_script();
if (base.is_null()) {
r_error.error = Callable::CallError::CALL_ERROR_INVALID_ARGUMENT;
r_error.argument = 0;
r_error.expected = Variant::DICTIONARY;
*r_ret = RTR("Not based on a script");
return;
}
GDScript *p = base.ptr();
Vector<StringName> sname;
while (p->_owner) {
sname.push_back(p->name);
p = p->_owner;
}
sname.invert();
if (!p->path.is_resource_file()) {
r_error.error = Callable::CallError::CALL_ERROR_INVALID_ARGUMENT;
r_error.argument = 0;
r_error.expected = Variant::DICTIONARY;
*r_ret = Variant();
*r_ret = RTR("Not based on a resource file");
return;
}
NodePath cp(sname, Vector<StringName>(), false);
Dictionary d;
d["@subpath"] = cp;
d["@path"] = p->get_path();
for (Map<StringName, GDScript::MemberInfo>::Element *E = base->member_indices.front(); E; E = E->next()) {
if (!d.has(E->key())) {
d[E->key()] = ins->members[E->get().index];
}
}
*r_ret = d;
}
}
}
static inline void dict2inst(Variant *r_ret, const Variant **p_args, int p_arg_count, Callable::CallError &r_error) {
VALIDATE_ARG_COUNT(1);
if (p_args[0]->get_type() != Variant::DICTIONARY) {
r_error.error = Callable::CallError::CALL_ERROR_INVALID_ARGUMENT;
r_error.argument = 0;
r_error.expected = Variant::DICTIONARY;
*r_ret = Variant();
return;
}
Dictionary d = *p_args[0];
if (!d.has("@path")) {
r_error.error = Callable::CallError::CALL_ERROR_INVALID_ARGUMENT;
r_error.argument = 0;
r_error.expected = Variant::OBJECT;
*r_ret = RTR("Invalid instance dictionary format (missing @path)");
return;
}
Ref<Script> scr = ResourceLoader::load(d["@path"]);
if (!scr.is_valid()) {
r_error.error = Callable::CallError::CALL_ERROR_INVALID_ARGUMENT;
r_error.argument = 0;
r_error.expected = Variant::OBJECT;
*r_ret = RTR("Invalid instance dictionary format (can't load script at @path)");
return;
}
Ref<GDScript> gdscr = scr;
if (!gdscr.is_valid()) {
r_error.error = Callable::CallError::CALL_ERROR_INVALID_ARGUMENT;
r_error.argument = 0;
r_error.expected = Variant::OBJECT;
*r_ret = Variant();
*r_ret = RTR("Invalid instance dictionary format (invalid script at @path)");
return;
}
NodePath sub;
if (d.has("@subpath")) {
sub = d["@subpath"];
}
for (int i = 0; i < sub.get_name_count(); i++) {
gdscr = gdscr->subclasses[sub.get_name(i)];
if (!gdscr.is_valid()) {
r_error.error = Callable::CallError::CALL_ERROR_INVALID_ARGUMENT;
r_error.argument = 0;
r_error.expected = Variant::OBJECT;
*r_ret = Variant();
*r_ret = RTR("Invalid instance dictionary (invalid subclasses)");
return;
}
}
*r_ret = gdscr->_new(nullptr, -1 /*skip initializer*/, r_error);
if (r_error.error != Callable::CallError::CALL_OK) {
*r_ret = Variant();
return;
}
GDScriptInstance *ins = static_cast<GDScriptInstance *>(static_cast<Object *>(*r_ret)->get_script_instance());
Ref<GDScript> gd_ref = ins->get_script();
for (Map<StringName, GDScript::MemberInfo>::Element *E = gd_ref->member_indices.front(); E; E = E->next()) {
if (d.has(E->key())) {
ins->members.write[E->get().index] = d[E->key()];
}
}
}
static inline void Color8(Variant *r_ret, const Variant **p_args, int p_arg_count, Callable::CallError &r_error) {
if (p_arg_count < 3) {
r_error.error = Callable::CallError::CALL_ERROR_TOO_FEW_ARGUMENTS;
r_error.argument = 3;
*r_ret = Variant();
return;
}
if (p_arg_count > 4) {
r_error.error = Callable::CallError::CALL_ERROR_TOO_MANY_ARGUMENTS;
r_error.argument = 4;
*r_ret = Variant();
return;
}
VALIDATE_ARG_INT(0);
VALIDATE_ARG_INT(1);
VALIDATE_ARG_INT(2);
Color color((int64_t)*p_args[0] / 255.0f, (int64_t)*p_args[1] / 255.0f, (int64_t)*p_args[2] / 255.0f);
if (p_arg_count == 4) {
VALIDATE_ARG_INT(3);
color.a = (int64_t)*p_args[3] / 255.0f;
}
*r_ret = color;
}
static inline void print_debug(Variant *r_ret, const Variant **p_args, int p_arg_count, Callable::CallError &r_error) {
String str;
for (int i = 0; i < p_arg_count; i++) {
str += p_args[i]->operator String();
}
ScriptLanguage *script = GDScriptLanguage::get_singleton();
if (script->debug_get_stack_level_count() > 0) {
str += "\n At: " + script->debug_get_stack_level_source(0) + ":" + itos(script->debug_get_stack_level_line(0)) + ":" + script->debug_get_stack_level_function(0) + "()";
}
print_line(str);
*r_ret = Variant();
}
static inline void print_stack(Variant *r_ret, const Variant **p_args, int p_arg_count, Callable::CallError &r_error) {
VALIDATE_ARG_COUNT(0);
ScriptLanguage *script = GDScriptLanguage::get_singleton();
for (int i = 0; i < script->debug_get_stack_level_count(); i++) {
print_line("Frame " + itos(i) + " - " + script->debug_get_stack_level_source(i) + ":" + itos(script->debug_get_stack_level_line(i)) + " in function '" + script->debug_get_stack_level_function(i) + "'");
};
}
static inline void get_stack(Variant *r_ret, const Variant **p_args, int p_arg_count, Callable::CallError &r_error) {
VALIDATE_ARG_COUNT(0);
ScriptLanguage *script = GDScriptLanguage::get_singleton();
Array ret;
for (int i = 0; i < script->debug_get_stack_level_count(); i++) {
Dictionary frame;
frame["source"] = script->debug_get_stack_level_source(i);
frame["function"] = script->debug_get_stack_level_function(i);
frame["line"] = script->debug_get_stack_level_line(i);
ret.push_back(frame);
};
*r_ret = ret;
}
static inline void len(Variant *r_ret, const Variant **p_args, int p_arg_count, Callable::CallError &r_error) {
VALIDATE_ARG_COUNT(1);
switch (p_args[0]->get_type()) {
case Variant::STRING: {
String d = *p_args[0];
*r_ret = d.length();
} break;
case Variant::DICTIONARY: {
Dictionary d = *p_args[0];
*r_ret = d.size();
} break;
case Variant::ARRAY: {
Array d = *p_args[0];
*r_ret = d.size();
} break;
case Variant::PACKED_BYTE_ARRAY: {
Vector<uint8_t> d = *p_args[0];
*r_ret = d.size();
} break;
case Variant::PACKED_INT32_ARRAY: {
Vector<int32_t> d = *p_args[0];
*r_ret = d.size();
} break;
case Variant::PACKED_INT64_ARRAY: {
Vector<int64_t> d = *p_args[0];
*r_ret = d.size();
} break;
case Variant::PACKED_FLOAT32_ARRAY: {
Vector<float> d = *p_args[0];
*r_ret = d.size();
} break;
case Variant::PACKED_FLOAT64_ARRAY: {
Vector<double> d = *p_args[0];
*r_ret = d.size();
} break;
case Variant::PACKED_STRING_ARRAY: {
Vector<String> d = *p_args[0];
*r_ret = d.size();
} break;
case Variant::PACKED_VECTOR2_ARRAY: {
Vector<Vector2> d = *p_args[0];
*r_ret = d.size();
} break;
case Variant::PACKED_VECTOR3_ARRAY: {
Vector<Vector3> d = *p_args[0];
*r_ret = d.size();
} break;
case Variant::PACKED_COLOR_ARRAY: {
Vector<Color> d = *p_args[0];
*r_ret = d.size();
} break;
default: {
r_error.error = Callable::CallError::CALL_ERROR_INVALID_ARGUMENT;
r_error.argument = 0;
r_error.expected = Variant::NIL;
*r_ret = vformat(RTR("Value of type '%s' can't provide a length."), Variant::get_type_name(p_args[0]->get_type()));
}
}
}
};
struct GDScriptUtilityFunctionInfo {
GDScriptUtilityFunctions::FunctionPtr function;
MethodInfo info;
bool is_constant = false;
};
static OAHashMap<StringName, GDScriptUtilityFunctionInfo> utility_function_table;
static List<StringName> utility_function_name_table;
static void _register_function(const String &p_name, const MethodInfo &p_method_info, GDScriptUtilityFunctions::FunctionPtr p_function, bool p_is_const) {
StringName sname(p_name);
ERR_FAIL_COND(utility_function_table.has(sname));
GDScriptUtilityFunctionInfo function;
function.function = p_function;
function.info = p_method_info;
function.is_constant = p_is_const;
utility_function_table.insert(sname, function);
utility_function_name_table.push_back(sname);
}
#define REGISTER_FUNC(m_func, m_is_const, m_return_type, ...) \
{ \
String name(#m_func); \
if (name.begins_with("_")) { \
name = name.substr(1, name.length() - 1); \
} \
MethodInfo info = MethodInfo(name, __VA_ARGS__); \
info.return_val.type = m_return_type; \
_register_function(name, info, GDScriptUtilityFunctionsDefinitions::m_func, m_is_const); \
}
#define REGISTER_FUNC_NO_ARGS(m_func, m_is_const, m_return_type) \
{ \
String name(#m_func); \
if (name.begins_with("_")) { \
name = name.substr(1, name.length() - 1); \
} \
MethodInfo info = MethodInfo(name); \
info.return_val.type = m_return_type; \
_register_function(name, info, GDScriptUtilityFunctionsDefinitions::m_func, m_is_const); \
}
#define REGISTER_VARARG_FUNC(m_func, m_is_const, m_return_type) \
{ \
String name(#m_func); \
if (name.begins_with("_")) { \
name = name.substr(1, name.length() - 1); \
} \
MethodInfo info = MethodInfo(name); \
info.return_val.type = m_return_type; \
info.flags |= METHOD_FLAG_VARARG; \
_register_function(name, info, GDScriptUtilityFunctionsDefinitions::m_func, m_is_const); \
}
#define REGISTER_VARIANT_FUNC(m_func, m_is_const, ...) \
{ \
String name(#m_func); \
if (name.begins_with("_")) { \
name = name.substr(1, name.length() - 1); \
} \
MethodInfo info = MethodInfo(name, __VA_ARGS__); \
info.return_val.type = Variant::NIL; \
info.return_val.usage |= PROPERTY_USAGE_NIL_IS_VARIANT; \
_register_function(name, info, GDScriptUtilityFunctionsDefinitions::m_func, m_is_const); \
}
#define REGISTER_CLASS_FUNC(m_func, m_is_const, m_return_type, ...) \
{ \
String name(#m_func); \
if (name.begins_with("_")) { \
name = name.substr(1, name.length() - 1); \
} \
MethodInfo info = MethodInfo(name, __VA_ARGS__); \
info.return_val.type = Variant::OBJECT; \
info.return_val.hint = PROPERTY_HINT_RESOURCE_TYPE; \
info.return_val.class_name = m_return_type; \
_register_function(name, info, GDScriptUtilityFunctionsDefinitions::m_func, m_is_const); \
}
#define REGISTER_FUNC_DEF(m_func, m_is_const, m_default, m_return_type, ...) \
{ \
String name(#m_func); \
if (name.begins_with("_")) { \
name = name.substr(1, name.length() - 1); \
} \
MethodInfo info = MethodInfo(name, __VA_ARGS__); \
info.return_val.type = m_return_type; \
info.default_arguments.push_back(m_default); \
_register_function(name, info, GDScriptUtilityFunctionsDefinitions::m_func, m_is_const); \
}
#define ARG(m_name, m_type) \
PropertyInfo(m_type, m_name)
#define VARARG(m_name) \
PropertyInfo(Variant::NIL, m_name, PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_NIL_IS_VARIANT)
void GDScriptUtilityFunctions::register_functions() {
REGISTER_VARIANT_FUNC(convert, true, VARARG("what"), ARG("type", Variant::INT));
REGISTER_FUNC(type_exists, true, Variant::BOOL, ARG("type", Variant::STRING_NAME));
REGISTER_FUNC(_char, true, Variant::STRING, ARG("char", Variant::INT));
REGISTER_VARARG_FUNC(str, true, Variant::STRING);
REGISTER_VARARG_FUNC(range, false, Variant::ARRAY);
REGISTER_CLASS_FUNC(load, false, "Resource", ARG("path", Variant::STRING));
REGISTER_FUNC(inst2dict, false, Variant::DICTIONARY, ARG("instance", Variant::OBJECT));
REGISTER_FUNC(dict2inst, false, Variant::OBJECT, ARG("dictionary", Variant::DICTIONARY));
REGISTER_FUNC_DEF(Color8, true, 255, Variant::COLOR, ARG("r8", Variant::INT), ARG("g8", Variant::INT), ARG("b8", Variant::INT), ARG("a8", Variant::INT));
REGISTER_VARARG_FUNC(print_debug, false, Variant::NIL);
REGISTER_FUNC_NO_ARGS(print_stack, false, Variant::NIL);
REGISTER_FUNC_NO_ARGS(get_stack, false, Variant::ARRAY);
REGISTER_FUNC(len, true, Variant::INT, VARARG("var"));
}
void GDScriptUtilityFunctions::unregister_functions() {
utility_function_name_table.clear();
utility_function_table.clear();
}
GDScriptUtilityFunctions::FunctionPtr GDScriptUtilityFunctions::get_function(const StringName &p_function) {
GDScriptUtilityFunctionInfo *info = utility_function_table.lookup_ptr(p_function);
ERR_FAIL_COND_V(!info, nullptr);
return info->function;
}
bool GDScriptUtilityFunctions::has_function_return_value(const StringName &p_function) {
GDScriptUtilityFunctionInfo *info = utility_function_table.lookup_ptr(p_function);
ERR_FAIL_COND_V(!info, false);
return info->info.return_val.type != Variant::NIL || bool(info->info.return_val.usage & PROPERTY_USAGE_NIL_IS_VARIANT);
}
Variant::Type GDScriptUtilityFunctions::get_function_return_type(const StringName &p_function) {
GDScriptUtilityFunctionInfo *info = utility_function_table.lookup_ptr(p_function);
ERR_FAIL_COND_V(!info, Variant::NIL);
return info->info.return_val.type;
}
StringName GDScriptUtilityFunctions::get_function_return_class(const StringName &p_function) {
GDScriptUtilityFunctionInfo *info = utility_function_table.lookup_ptr(p_function);
ERR_FAIL_COND_V(!info, StringName());
return info->info.return_val.class_name;
}
Variant::Type GDScriptUtilityFunctions::get_function_argument_type(const StringName &p_function, int p_arg) {
GDScriptUtilityFunctionInfo *info = utility_function_table.lookup_ptr(p_function);
ERR_FAIL_COND_V(!info, Variant::NIL);
ERR_FAIL_COND_V(p_arg >= info->info.arguments.size(), Variant::NIL);
return info->info.arguments[p_arg].type;
}
int GDScriptUtilityFunctions::get_function_argument_count(const StringName &p_function, int p_arg) {
GDScriptUtilityFunctionInfo *info = utility_function_table.lookup_ptr(p_function);
ERR_FAIL_COND_V(!info, 0);
return info->info.arguments.size();
}
bool GDScriptUtilityFunctions::is_function_vararg(const StringName &p_function) {
GDScriptUtilityFunctionInfo *info = utility_function_table.lookup_ptr(p_function);
ERR_FAIL_COND_V(!info, false);
return (bool)(info->info.flags & METHOD_FLAG_VARARG);
}
bool GDScriptUtilityFunctions::is_function_constant(const StringName &p_function) {
GDScriptUtilityFunctionInfo *info = utility_function_table.lookup_ptr(p_function);
ERR_FAIL_COND_V(!info, false);
return info->is_constant;
}
bool GDScriptUtilityFunctions::function_exists(const StringName &p_function) {
return utility_function_table.has(p_function);
}
void GDScriptUtilityFunctions::get_function_list(List<StringName> *r_functions) {
for (const List<StringName>::Element *E = utility_function_name_table.front(); E; E = E->next()) {
r_functions->push_back(E->get());
}
}
MethodInfo GDScriptUtilityFunctions::get_function_info(const StringName &p_function) {
GDScriptUtilityFunctionInfo *info = utility_function_table.lookup_ptr(p_function);
ERR_FAIL_COND_V(!info, MethodInfo());
return info->info;
}

View file

@ -1,5 +1,5 @@
/*************************************************************************/
/* gdscript_functions.h */
/* gdscript_utility_functions.h */
/*************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
@ -28,110 +28,31 @@
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/*************************************************************************/
#ifndef GDSCRIPT_FUNCTIONS_H
#define GDSCRIPT_FUNCTIONS_H
#ifndef GDSCRIPT_UTILITY_FUNCTIONS_H
#define GDSCRIPT_UTILITY_FUNCTIONS_H
#include "core/string/string_name.h"
#include "core/variant/variant.h"
class GDScriptFunctions {
class GDScriptUtilityFunctions {
public:
enum Function {
MATH_SIN,
MATH_COS,
MATH_TAN,
MATH_SINH,
MATH_COSH,
MATH_TANH,
MATH_ASIN,
MATH_ACOS,
MATH_ATAN,
MATH_ATAN2,
MATH_SQRT,
MATH_FMOD,
MATH_FPOSMOD,
MATH_POSMOD,
MATH_FLOOR,
MATH_CEIL,
MATH_ROUND,
MATH_ABS,
MATH_SIGN,
MATH_POW,
MATH_LOG,
MATH_EXP,
MATH_ISNAN,
MATH_ISINF,
MATH_ISEQUALAPPROX,
MATH_ISZEROAPPROX,
MATH_EASE,
MATH_STEP_DECIMALS,
MATH_STEPIFY,
MATH_LERP,
MATH_LERP_ANGLE,
MATH_INVERSE_LERP,
MATH_RANGE_LERP,
MATH_SMOOTHSTEP,
MATH_MOVE_TOWARD,
MATH_DECTIME,
MATH_RANDOMIZE,
MATH_RANDI,
MATH_RANDF,
MATH_RANDF_RANGE,
MATH_RANDI_RANGE,
MATH_SEED,
MATH_RANDSEED,
MATH_DEG2RAD,
MATH_RAD2DEG,
MATH_LINEAR2DB,
MATH_DB2LINEAR,
MATH_POLAR2CARTESIAN,
MATH_CARTESIAN2POLAR,
MATH_WRAP,
MATH_WRAPF,
LOGIC_MAX,
LOGIC_MIN,
LOGIC_CLAMP,
LOGIC_NEAREST_PO2,
OBJ_WEAKREF,
TYPE_CONVERT,
TYPE_OF,
TYPE_EXISTS,
TEXT_CHAR,
TEXT_ORD,
TEXT_STR,
TEXT_PRINT,
TEXT_PRINT_TABBED,
TEXT_PRINT_SPACED,
TEXT_PRINTERR,
TEXT_PRINTRAW,
TEXT_PRINT_DEBUG,
PUSH_ERROR,
PUSH_WARNING,
VAR_TO_STR,
STR_TO_VAR,
VAR_TO_BYTES,
BYTES_TO_VAR,
GEN_RANGE,
RESOURCE_LOAD,
INST2DICT,
DICT2INST,
VALIDATE_JSON,
PARSE_JSON,
TO_JSON,
HASH,
COLOR8,
COLORN,
PRINT_STACK,
GET_STACK,
INSTANCE_FROM_ID,
LEN,
IS_INSTANCE_VALID,
FUNC_MAX
};
typedef void (*FunctionPtr)(Variant *r_ret, const Variant **p_args, int p_arg_count, Callable::CallError &r_error);
static const char *get_func_name(Function p_func);
static void call(Function p_func, const Variant **p_args, int p_arg_count, Variant &r_ret, Callable::CallError &r_error);
static bool is_deterministic(Function p_func);
static MethodInfo get_info(Function p_func);
static FunctionPtr get_function(const StringName &p_function);
static bool has_function_return_value(const StringName &p_function);
static Variant::Type get_function_return_type(const StringName &p_function);
static StringName get_function_return_class(const StringName &p_function);
static Variant::Type get_function_argument_type(const StringName &p_function, int p_arg);
static int get_function_argument_count(const StringName &p_function, int p_arg);
static bool is_function_vararg(const StringName &p_function);
static bool is_function_constant(const StringName &p_function);
static bool function_exists(const StringName &p_function);
static void get_function_list(List<StringName> *r_functions);
static MethodInfo get_function_info(const StringName &p_function);
static void register_functions();
static void unregister_functions();
};
#endif // GDSCRIPT_FUNCTIONS_H
#endif // GDSCRIPT_UTILITY_FUNCTIONS_H

View file

@ -33,7 +33,6 @@
#include "core/core_string_names.h"
#include "core/os/os.h"
#include "gdscript.h"
#include "gdscript_functions.h"
Variant *GDScriptFunction::_get_variant(int p_address, GDScriptInstance *p_instance, GDScript *p_script, Variant &self, Variant &static_ref, Variant *p_stack, String &r_error) const {
int address = p_address & ADDR_MASK;
@ -220,7 +219,9 @@ String GDScriptFunction::_get_call_error(const Callable::CallError &p_err, const
&&OPCODE_CALL, \
&&OPCODE_CALL_RETURN, \
&&OPCODE_CALL_ASYNC, \
&&OPCODE_CALL_BUILT_IN, \
&&OPCODE_CALL_UTILITY, \
&&OPCODE_CALL_UTILITY_VALIDATED, \
&&OPCODE_CALL_GDSCRIPT_UTILITY, \
&&OPCODE_CALL_BUILTIN_TYPE_VALIDATED, \
&&OPCODE_CALL_SELF_BASE, \
&&OPCODE_CALL_METHOD_BIND, \
@ -1749,7 +1750,7 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a
}
DISPATCH_OPCODE;
OPCODE(OPCODE_CALL_BUILT_IN) {
OPCODE(OPCODE_CALL_UTILITY) {
CHECK_SPACE(3 + instr_arg_count);
ip += instr_arg_count;
@ -1757,22 +1758,80 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a
int argc = _code_ptr[ip + 1];
GD_ERR_BREAK(argc < 0);
GDScriptFunctions::Function func = GDScriptFunctions::Function(_code_ptr[ip + 2]);
GD_ERR_BREAK(_code_ptr[ip + 2] < 0 || _code_ptr[ip + 2] >= _global_names_count);
StringName function = _global_names_ptr[_code_ptr[ip + 2]];
Variant **argptrs = instruction_args;
GET_INSTRUCTION_ARG(dst, argc);
Callable::CallError err;
GDScriptFunctions::call(func, (const Variant **)argptrs, argc, *dst, err);
Variant::call_utility_function(function, dst, (const Variant **)argptrs, argc, err);
#ifdef DEBUG_ENABLED
if (err.error != Callable::CallError::CALL_OK) {
String methodstr = GDScriptFunctions::get_func_name(func);
String methodstr = function;
if (dst->get_type() == Variant::STRING) {
//call provided error string
err_text = "Error calling built-in function '" + methodstr + "': " + String(*dst);
// Call provided error string.
err_text = "Error calling utility function '" + methodstr + "': " + String(*dst);
} else {
err_text = _get_call_error(err, "built-in function '" + methodstr + "'", (const Variant **)argptrs);
err_text = _get_call_error(err, "utility function '" + methodstr + "'", (const Variant **)argptrs);
}
OPCODE_BREAK;
}
#endif
ip += 3;
}
DISPATCH_OPCODE;
OPCODE(OPCODE_CALL_UTILITY_VALIDATED) {
CHECK_SPACE(3 + instr_arg_count);
ip += instr_arg_count;
int argc = _code_ptr[ip + 1];
GD_ERR_BREAK(argc < 0);
GD_ERR_BREAK(_code_ptr[ip + 2] < 0 || _code_ptr[ip + 2] >= _utilities_count);
Variant::ValidatedUtilityFunction function = _utilities_ptr[_code_ptr[ip + 2]];
Variant **argptrs = instruction_args;
GET_INSTRUCTION_ARG(dst, argc);
function(dst, (const Variant **)argptrs, argc);
ip += 3;
}
DISPATCH_OPCODE;
OPCODE(OPCODE_CALL_GDSCRIPT_UTILITY) {
CHECK_SPACE(3 + instr_arg_count);
ip += instr_arg_count;
int argc = _code_ptr[ip + 1];
GD_ERR_BREAK(argc < 0);
GD_ERR_BREAK(_code_ptr[ip + 2] < 0 || _code_ptr[ip + 2] >= _gds_utilities_count);
GDScriptUtilityFunctions::FunctionPtr function = _gds_utilities_ptr[_code_ptr[ip + 2]];
Variant **argptrs = instruction_args;
GET_INSTRUCTION_ARG(dst, argc);
Callable::CallError err;
function(dst, (const Variant **)argptrs, argc, err);
#ifdef DEBUG_ENABLED
if (err.error != Callable::CallError::CALL_OK) {
// TODO: Add this information in debug.
String methodstr = "<unkown function>";
if (dst->get_type() == Variant::STRING) {
// Call provided error string.
err_text = "Error calling GDScript utility function '" + methodstr + "': " + String(*dst);
} else {
err_text = _get_call_error(err, "GDScript utility function '" + methodstr + "'", (const Variant **)argptrs);
}
OPCODE_BREAK;
}

View file

@ -38,6 +38,7 @@
#include "gdscript_analyzer.h"
#include "gdscript_cache.h"
#include "gdscript_tokenizer.h"
#include "gdscript_utility_functions.h"
#ifdef TESTS_ENABLED
#include "tests/test_gdscript.h"
@ -130,6 +131,8 @@ void register_gdscript_types() {
gdscript_translation_parser_plugin.instance();
EditorTranslationParser::get_singleton()->add_parser(gdscript_translation_parser_plugin, EditorTranslationParser::STANDARD);
#endif // TOOLS_ENABLED
GDScriptUtilityFunctions::register_functions();
}
void unregister_gdscript_types() {
@ -156,6 +159,7 @@ void unregister_gdscript_types() {
GDScriptParser::cleanup();
GDScriptAnalyzer::cleanup();
GDScriptUtilityFunctions::unregister_functions();
}
#ifdef TESTS_ENABLED