New GDScript tokenizer and parser

Sometimes to fix something you have to break it first.

This get GDScript mostly working with the new tokenizer and parser but
a lot of things isn't working yet. It compiles and it's usable, and that
should be enough for now.

Don't worry: other huge commits will come after this.
This commit is contained in:
George Marques 2020-05-01 19:14:56 -03:00
parent 818bfbc5b5
commit 5d6e853806
No known key found for this signature in database
GPG Key ID: 046BD46A3201E43D
29 changed files with 7906 additions and 16496 deletions

File diff suppressed because it is too large Load Diff

View File

@ -288,7 +288,7 @@ Dictionary GDScriptSyntaxHighlighter::_get_line_syntax_highlighting(int p_line)
if (str[k] == '(') {
in_function_name = true;
} else if (previous_text == GDScriptTokenizer::get_token_name(GDScriptTokenizer::TK_PR_VAR)) {
} else if (previous_text == GDScriptTokenizer::get_token_name(GDScriptTokenizer::Token::VAR)) {
in_variable_declaration = true;
}
}
@ -357,7 +357,7 @@ Dictionary GDScriptSyntaxHighlighter::_get_line_syntax_highlighting(int p_line)
} else if (in_function_name) {
next_type = FUNCTION;
if (previous_text == GDScriptTokenizer::get_token_name(GDScriptTokenizer::TK_PR_FUNCTION)) {
if (previous_text == GDScriptTokenizer::get_token_name(GDScriptTokenizer::Token::FUNC)) {
color = function_definition_color;
} else {
color = function_color;

View File

@ -39,7 +39,9 @@
#include "core/os/file_access.h"
#include "core/os/os.h"
#include "core/project_settings.h"
#include "gdscript_analyzer.h"
#include "gdscript_compiler.h"
#include "gdscript_parser.h"
///////////////////////////
@ -79,6 +81,17 @@ Object *GDScriptNativeClass::instance() {
return ClassDB::instance(name);
}
void GDScript::_super_implicit_constructor(GDScript *p_script, GDScriptInstance *p_instance, Callable::CallError &r_error) {
GDScript *base = p_script->_base;
if (base != nullptr) {
_super_implicit_constructor(base, p_instance, r_error);
if (r_error.error != Callable::CallError::CALL_OK) {
return;
}
}
p_script->implicit_initializer->call(p_instance, nullptr, 0, r_error);
}
GDScriptInstance *GDScript::_create_instance(const Variant **p_args, int p_argcount, Object *p_owner, bool p_isref, Callable::CallError &r_error) {
/* STEP 1, CREATE */
@ -101,10 +114,8 @@ GDScriptInstance *GDScript::_create_instance(const Variant **p_args, int p_argco
MutexLock lock(GDScriptLanguage::singleton->lock);
instances.insert(instance->owner);
}
if (p_argcount < 0) {
return instance;
}
initializer->call(instance, p_args, p_argcount, r_error);
_super_implicit_constructor(this, instance, r_error);
if (r_error.error != Callable::CallError::CALL_OK) {
instance->script = Ref<GDScript>();
instance->owner->set_script_instance(nullptr);
@ -114,6 +125,22 @@ GDScriptInstance *GDScript::_create_instance(const Variant **p_args, int p_argco
}
ERR_FAIL_V_MSG(nullptr, "Error constructing a GDScriptInstance.");
}
if (p_argcount < 0) {
return instance;
}
if (initializer != nullptr) {
initializer->call(instance, p_args, p_argcount, r_error);
if (r_error.error != Callable::CallError::CALL_OK) {
instance->script = Ref<GDScript>();
instance->owner->set_script_instance(nullptr);
{
MutexLock lock(GDScriptLanguage::singleton->lock);
instances.erase(p_owner);
}
ERR_FAIL_V_MSG(nullptr, "Error constructing a GDScriptInstance.");
}
}
//@TODO make thread safe
return instance;
}
@ -382,13 +409,11 @@ bool GDScript::_update_exports(bool *r_err, bool p_recursive_call) {
}
GDScriptParser parser;
Error err = parser.parse(source, basedir, true, path);
GDScriptAnalyzer analyzer(&parser);
Error err = parser.parse(source, path, false);
if (err == OK) {
const GDScriptParser::Node *root = parser.get_parse_tree();
ERR_FAIL_COND_V(root->type != GDScriptParser::Node::TYPE_CLASS, false);
const GDScriptParser::ClassNode *c = static_cast<const GDScriptParser::ClassNode *>(root);
if (err == OK && analyzer.analyze() == OK) {
const GDScriptParser::ClassNode *c = parser.get_tree();
if (base_cache.is_valid()) {
base_cache->inheriters_cache.erase(get_instance_id());
@ -397,8 +422,8 @@ bool GDScript::_update_exports(bool *r_err, bool p_recursive_call) {
if (c->extends_used) {
String path = "";
if (String(c->extends_file) != "" && String(c->extends_file) != get_path()) {
path = c->extends_file;
if (String(c->extends_path) != "" && String(c->extends_path) != get_path()) {
path = c->extends_path;
if (path.is_rel_path()) {
String base = get_path();
if (base == "" || base.is_rel_path()) {
@ -407,8 +432,8 @@ bool GDScript::_update_exports(bool *r_err, bool p_recursive_call) {
path = base.get_base_dir().plus_file(path);
}
}
} else if (c->extends_class.size() != 0) {
String base = c->extends_class[0];
} else if (c->extends.size() != 0) {
const StringName &base = c->extends[0];
if (ScriptServer::is_global_class(base)) {
path = ScriptServer::get_global_class_path(base);
@ -431,20 +456,37 @@ bool GDScript::_update_exports(bool *r_err, bool p_recursive_call) {
members_cache.clear();
member_default_values_cache.clear();
for (int i = 0; i < c->variables.size(); i++) {
if (c->variables[i]._export.type == Variant::NIL) {
continue;
}
members_cache.push_back(c->variables[i]._export);
member_default_values_cache[c->variables[i].identifier] = c->variables[i].default_value;
}
_signals.clear();
for (int i = 0; i < c->_signals.size(); i++) {
_signals[c->_signals[i].name] = c->_signals[i].arguments;
for (int i = 0; i < c->members.size(); i++) {
const GDScriptParser::ClassNode::Member &member = c->members[i];
switch (member.type) {
case GDScriptParser::ClassNode::Member::VARIABLE: {
if (!member.variable->exported) {
continue;
}
members_cache.push_back(member.variable->export_info);
// FIXME: Get variable's default value in non-literal cases.
Variant default_value;
if (member.variable->initializer != nullptr && member.variable->initializer->type == GDScriptParser::Node::LITERAL) {
default_value = static_cast<const GDScriptParser::LiteralNode *>(member.variable->initializer)->value;
}
member_default_values_cache[member.variable->identifier->name] = default_value;
} break;
case GDScriptParser::ClassNode::Member::SIGNAL: {
// TODO: Cache this in parser to avoid loops like this.
Vector<StringName> parameters_names;
parameters_names.resize(member.signal->parameters.size());
for (int j = 0; j < member.signal->parameters.size(); j++) {
parameters_names.write[j] = member.signal->parameters[j]->identifier->name;
}
_signals[member.signal->identifier->name] = parameters_names;
} break;
default:
break; // Nothing.
}
}
} else {
placeholder_fallback_enabled = true;
@ -555,16 +597,29 @@ Error GDScript::reload(bool p_keep_state) {
valid = false;
GDScriptParser parser;
Error err = parser.parse(source, basedir, false, path);
Error err = parser.parse(source, path, false);
if (err) {
if (EngineDebugger::is_active()) {
GDScriptLanguage::get_singleton()->debug_break_parse(get_path(), parser.get_error_line(), "Parser Error: " + parser.get_error());
GDScriptLanguage::get_singleton()->debug_break_parse(get_path(), parser.get_errors().front()->get().line, "Parser Error: " + parser.get_errors().front()->get().message);
}
_err_print_error("GDScript::reload", path.empty() ? "built-in" : (const char *)path.utf8().get_data(), parser.get_error_line(), ("Parse Error: " + parser.get_error()).utf8().get_data(), ERR_HANDLER_SCRIPT);
// TODO: Show all error messages.
_err_print_error("GDScript::reload", path.empty() ? "built-in" : (const char *)path.utf8().get_data(), parser.get_errors().front()->get().line, ("Parse Error: " + parser.get_errors().front()->get().message).utf8().get_data(), ERR_HANDLER_SCRIPT);
ERR_FAIL_V(ERR_PARSE_ERROR);
}
bool can_run = ScriptServer::is_scripting_enabled() || parser.is_tool_script();
GDScriptAnalyzer analyzer(&parser);
err = analyzer.analyze();
if (err) {
if (EngineDebugger::is_active()) {
GDScriptLanguage::get_singleton()->debug_break_parse(get_path(), parser.get_errors().front()->get().line, "Parser Error: " + parser.get_errors().front()->get().message);
}
// TODO: Show all error messages.
_err_print_error("GDScript::reload", path.empty() ? "built-in" : (const char *)path.utf8().get_data(), parser.get_errors().front()->get().line, ("Parse Error: " + parser.get_errors().front()->get().message).utf8().get_data(), ERR_HANDLER_SCRIPT);
ERR_FAIL_V(ERR_PARSE_ERROR);
}
bool can_run = ScriptServer::is_scripting_enabled() || parser.is_tool();
GDScriptCompiler compiler;
err = compiler.compile(&parser, this, p_keep_state);
@ -581,13 +636,14 @@ Error GDScript::reload(bool p_keep_state) {
}
}
#ifdef DEBUG_ENABLED
for (const List<GDScriptWarning>::Element *E = parser.get_warnings().front(); E; E = E->next()) {
const GDScriptWarning &warning = E->get();
if (EngineDebugger::is_active()) {
Vector<ScriptLanguage::StackInfo> si;
EngineDebugger::get_script_debugger()->send_error("", get_path(), warning.line, warning.get_name(), warning.get_message(), ERR_HANDLER_WARNING, si);
}
}
// FIXME: Add warnings.
// for (const List<GDScriptWarning>::Element *E = parser.get_warnings().front(); E; E = E->next()) {
// const GDScriptWarning &warning = E->get();
// if (EngineDebugger::is_active()) {
// Vector<ScriptLanguage::StackInfo> si;
// EngineDebugger::get_script_debugger()->send_error("", get_path(), warning.line, warning.get_name(), warning.get_message(), ERR_HANDLER_WARNING, si);
// }
// }
#endif
valid = true;
@ -753,83 +809,12 @@ void GDScript::_bind_methods() {
}
Vector<uint8_t> GDScript::get_as_byte_code() const {
GDScriptTokenizerBuffer tokenizer;
return tokenizer.parse_code_string(source);
return Vector<uint8_t>();
};
// TODO: Fully remove this. There's not this kind of "bytecode" anymore.
Error GDScript::load_byte_code(const String &p_path) {
Vector<uint8_t> bytecode;
if (p_path.ends_with("gde")) {
FileAccess *fa = FileAccess::open(p_path, FileAccess::READ);
ERR_FAIL_COND_V(!fa, ERR_CANT_OPEN);
FileAccessEncrypted *fae = memnew(FileAccessEncrypted);
ERR_FAIL_COND_V(!fae, ERR_CANT_OPEN);
Vector<uint8_t> key;
key.resize(32);
for (int i = 0; i < key.size(); i++) {
key.write[i] = script_encryption_key[i];
}
Error err = fae->open_and_parse(fa, key, FileAccessEncrypted::MODE_READ);
if (err) {
fa->close();
memdelete(fa);
memdelete(fae);
ERR_FAIL_COND_V(err, err);
}
bytecode.resize(fae->get_len());
fae->get_buffer(bytecode.ptrw(), bytecode.size());
fae->close();
memdelete(fae);
} else {
bytecode = FileAccess::get_file_as_array(p_path);
}
ERR_FAIL_COND_V(bytecode.size() == 0, ERR_PARSE_ERROR);
path = p_path;
String basedir = path;
if (basedir == "") {
basedir = get_path();
}
if (basedir != "") {
basedir = basedir.get_base_dir();
}
valid = false;
GDScriptParser parser;
Error err = parser.parse_bytecode(bytecode, basedir, get_path());
if (err) {
_err_print_error("GDScript::load_byte_code", path.empty() ? "built-in" : (const char *)path.utf8().get_data(), parser.get_error_line(), ("Parse Error: " + parser.get_error()).utf8().get_data(), ERR_HANDLER_SCRIPT);
ERR_FAIL_V(ERR_PARSE_ERROR);
}
GDScriptCompiler compiler;
err = compiler.compile(&parser, this);
if (err) {
_err_print_error("GDScript::load_byte_code", path.empty() ? "built-in" : (const char *)path.utf8().get_data(), compiler.get_error_line(), ("Compile Error: " + compiler.get_error()).utf8().get_data(), ERR_HANDLER_SCRIPT);
ERR_FAIL_V(ERR_COMPILATION_FAILED);
}
valid = true;
for (Map<StringName, Ref<GDScript>>::Element *E = subclasses.front(); E; E = E->next()) {
_set_subclass_path(E->get(), path);
}
_init_rpc_methods_properties();
return OK;
return ERR_COMPILATION_FAILED;
}
Error GDScript::load_source_code(const String &p_path) {
@ -1152,6 +1137,32 @@ bool GDScriptInstance::get(const StringName &p_name, Variant &r_ret) const {
}
}
{
// Signals.
const GDScript *sl = sptr;
while (sl) {
const Map<StringName, Vector<StringName>>::Element *E = sl->_signals.find(p_name);
if (E) {
r_ret = Signal(this->owner, E->key());
return true; //index found
}
sl = sl->_base;
}
}
{
// Methods.
const GDScript *sl = sptr;
while (sl) {
const Map<StringName, GDScriptFunction *>::Element *E = sl->member_functions.find(p_name);
if (E) {
r_ret = Callable(this->owner, E->key());
return true; //index found
}
sl = sl->_base;
}
}
{
const Map<StringName, GDScriptFunction *>::Element *E = sptr->member_functions.find(GDScriptLanguage::get_singleton()->strings._get);
if (E) {
@ -1304,6 +1315,7 @@ void GDScriptInstance::call_multilevel(const StringName &p_method, const Variant
Map<StringName, GDScriptFunction *>::Element *E = sptr->member_functions.find(p_method);
if (E) {
E->get()->call(this, p_args, p_argcount, ce);
return;
}
sptr = sptr->_base;
}
@ -1827,6 +1839,7 @@ void GDScriptLanguage::frame() {
/* EDITOR FUNCTIONS */
void GDScriptLanguage::get_reserved_words(List<String> *p_words) const {
// TODO: Add annotations here?
static const char *_reserved_words[] = {
// operators
"and",
@ -1849,6 +1862,7 @@ void GDScriptLanguage::get_reserved_words(List<String> *p_words) const {
// functions
"as",
"assert",
"await",
"breakpoint",
"class",
"class_name",
@ -1856,15 +1870,11 @@ void GDScriptLanguage::get_reserved_words(List<String> *p_words) const {
"is",
"func",
"preload",
"setget",
"signal",
"tool",
"yield",
// var
"const",
"enum",
"export",
"onready",
"static",
"var",
// control flow
@ -1878,12 +1888,6 @@ void GDScriptLanguage::get_reserved_words(List<String> *p_words) const {
"return",
"match",
"while",
"remote",
"master",
"puppet",
"remotesync",
"mastersync",
"puppetsync",
nullptr
};
@ -1914,10 +1918,11 @@ String GDScriptLanguage::get_global_class_name(const String &p_path, String *r_b
String source = f->get_as_utf8_string();
GDScriptParser parser;
parser.parse(source, p_path.get_base_dir(), true, p_path, false, nullptr, true);
err = parser.parse(source, p_path, false);
if (parser.get_parse_tree() && parser.get_parse_tree()->type == GDScriptParser::Node::TYPE_CLASS) {
const GDScriptParser::ClassNode *c = static_cast<const GDScriptParser::ClassNode *>(parser.get_parse_tree());
// TODO: Simplify this code by using the analyzer to get full inheritance.
if (err == OK) {
const GDScriptParser::ClassNode *c = parser.get_tree();
if (r_icon_path) {
if (c->icon_path.empty() || c->icon_path.is_abs_path()) {
*r_icon_path = c->icon_path;
@ -1931,15 +1936,15 @@ String GDScriptLanguage::get_global_class_name(const String &p_path, String *r_b
GDScriptParser subparser;
while (subclass) {
if (subclass->extends_used) {
if (subclass->extends_file) {
if (subclass->extends_class.size() == 0) {
get_global_class_name(subclass->extends_file, r_base_type);
if (!subclass->extends_path.empty()) {
if (subclass->extends.size() == 0) {
get_global_class_name(subclass->extends_path, r_base_type);
subclass = nullptr;
break;
} else {
Vector<StringName> extend_classes = subclass->extends_class;
Vector<StringName> extend_classes = subclass->extends;
FileAccessRef subfile = FileAccess::open(subclass->extends_file, FileAccess::READ);
FileAccessRef subfile = FileAccess::open(subclass->extends_path, FileAccess::READ);
if (!subfile) {
break;
}
@ -1948,25 +1953,26 @@ String GDScriptLanguage::get_global_class_name(const String &p_path, String *r_b
if (subsource.empty()) {
break;
}
String subpath = subclass->extends_file;
String subpath = subclass->extends_path;
if (subpath.is_rel_path()) {
subpath = path.get_base_dir().plus_file(subpath).simplify_path();
}
if (OK != subparser.parse(subsource, subpath.get_base_dir(), true, subpath, false, nullptr, true)) {
if (OK != subparser.parse(subsource, subpath, false)) {
break;
}
path = subpath;
if (!subparser.get_parse_tree() || subparser.get_parse_tree()->type != GDScriptParser::Node::TYPE_CLASS) {
break;
}
subclass = static_cast<const GDScriptParser::ClassNode *>(subparser.get_parse_tree());
subclass = subparser.get_tree();
while (extend_classes.size() > 0) {
bool found = false;
for (int i = 0; i < subclass->subclasses.size(); i++) {
const GDScriptParser::ClassNode *inner_class = subclass->subclasses[i];
if (inner_class->name == extend_classes[0]) {
for (int i = 0; i < subclass->members.size(); i++) {
if (subclass->members[i].type != GDScriptParser::ClassNode::Member::CLASS) {
continue;
}
const GDScriptParser::ClassNode *inner_class = subclass->members[i].m_class;
if (inner_class->identifier->name == extend_classes[0]) {
extend_classes.remove(0);
found = true;
subclass = inner_class;
@ -1979,8 +1985,8 @@ String GDScriptLanguage::get_global_class_name(const String &p_path, String *r_b
}
}
}
} else if (subclass->extends_class.size() == 1) {
*r_base_type = subclass->extends_class[0];
} else if (subclass->extends.size() == 1) {
*r_base_type = subclass->extends[0];
subclass = nullptr;
} else {
break;
@ -1991,181 +1997,12 @@ String GDScriptLanguage::get_global_class_name(const String &p_path, String *r_b
}
}
}
return c->name;
return c->identifier != nullptr ? String(c->identifier->name) : String();
}
return String();
}
#ifdef DEBUG_ENABLED
String GDScriptWarning::get_message() const {
#define CHECK_SYMBOLS(m_amount) ERR_FAIL_COND_V(symbols.size() < m_amount, String());
switch (code) {
case UNASSIGNED_VARIABLE_OP_ASSIGN: {
CHECK_SYMBOLS(1);
return "Using assignment with operation but the variable '" + symbols[0] + "' was not previously assigned a value.";
} break;
case UNASSIGNED_VARIABLE: {
CHECK_SYMBOLS(1);
return "The variable '" + symbols[0] + "' was used but never assigned a value.";
} break;
case UNUSED_VARIABLE: {
CHECK_SYMBOLS(1);
return "The local variable '" + symbols[0] + "' is declared but never used in the block. If this is intended, prefix it with an underscore: '_" + symbols[0] + "'";
} break;
case SHADOWED_VARIABLE: {
CHECK_SYMBOLS(2);
return "The local variable '" + symbols[0] + "' is shadowing an already-defined variable at line " + symbols[1] + ".";
} break;
case UNUSED_CLASS_VARIABLE: {
CHECK_SYMBOLS(1);
return "The class variable '" + symbols[0] + "' is declared but never used in the script.";
} break;
case UNUSED_ARGUMENT: {
CHECK_SYMBOLS(2);
return "The argument '" + symbols[1] + "' is never used in the function '" + symbols[0] + "'. If this is intended, prefix it with an underscore: '_" + symbols[1] + "'";
} break;
case UNREACHABLE_CODE: {
CHECK_SYMBOLS(1);
return "Unreachable code (statement after return) in function '" + symbols[0] + "()'.";
} break;
case STANDALONE_EXPRESSION: {
return "Standalone expression (the line has no effect).";
} break;
case VOID_ASSIGNMENT: {
CHECK_SYMBOLS(1);
return "Assignment operation, but the function '" + symbols[0] + "()' returns void.";
} break;
case NARROWING_CONVERSION: {
return "Narrowing conversion (float is converted to int and loses precision).";
} break;
case FUNCTION_MAY_YIELD: {
CHECK_SYMBOLS(1);
return "Assigned variable is typed but the function '" + symbols[0] + "()' may yield and return a GDScriptFunctionState instead.";
} break;
case VARIABLE_CONFLICTS_FUNCTION: {
CHECK_SYMBOLS(1);
return "Variable declaration of '" + symbols[0] + "' conflicts with a function of the same name.";
} break;
case FUNCTION_CONFLICTS_VARIABLE: {
CHECK_SYMBOLS(1);
return "Function declaration of '" + symbols[0] + "()' conflicts with a variable of the same name.";
} break;
case FUNCTION_CONFLICTS_CONSTANT: {
CHECK_SYMBOLS(1);
return "Function declaration of '" + symbols[0] + "()' conflicts with a constant of the same name.";
} break;
case INCOMPATIBLE_TERNARY: {
return "Values of the ternary conditional are not mutually compatible.";
} break;
case UNUSED_SIGNAL: {
CHECK_SYMBOLS(1);
return "The signal '" + symbols[0] + "' is declared but never emitted.";
} break;
case RETURN_VALUE_DISCARDED: {
CHECK_SYMBOLS(1);
return "The function '" + symbols[0] + "()' returns a value, but this value is never used.";
} break;
case PROPERTY_USED_AS_FUNCTION: {
CHECK_SYMBOLS(2);
return "The method '" + symbols[0] + "()' was not found in base '" + symbols[1] + "' but there's a property with the same name. Did you mean to access it?";
} break;
case CONSTANT_USED_AS_FUNCTION: {
CHECK_SYMBOLS(2);
return "The method '" + symbols[0] + "()' was not found in base '" + symbols[1] + "' but there's a constant with the same name. Did you mean to access it?";
} break;
case FUNCTION_USED_AS_PROPERTY: {
CHECK_SYMBOLS(2);
return "The property '" + symbols[0] + "' was not found in base '" + symbols[1] + "' but there's a method with the same name. Did you mean to call it?";
} break;
case INTEGER_DIVISION: {
return "Integer division, decimal part will be discarded.";
} break;
case UNSAFE_PROPERTY_ACCESS: {
CHECK_SYMBOLS(2);
return "The property '" + symbols[0] + "' is not present on the inferred type '" + symbols[1] + "' (but may be present on a subtype).";
} break;
case UNSAFE_METHOD_ACCESS: {
CHECK_SYMBOLS(2);
return "The method '" + symbols[0] + "' is not present on the inferred type '" + symbols[1] + "' (but may be present on a subtype).";
} break;
case UNSAFE_CAST: {
CHECK_SYMBOLS(1);
return "The value is cast to '" + symbols[0] + "' but has an unknown type.";
} break;
case UNSAFE_CALL_ARGUMENT: {
CHECK_SYMBOLS(4);
return "The argument '" + symbols[0] + "' of the function '" + symbols[1] + "' requires a the subtype '" + symbols[2] + "' but the supertype '" + symbols[3] + "' was provided";
} break;
case DEPRECATED_KEYWORD: {
CHECK_SYMBOLS(2);
return "The '" + symbols[0] + "' keyword is deprecated and will be removed in a future release, please replace its uses by '" + symbols[1] + "'.";
} break;
case STANDALONE_TERNARY: {
return "Standalone ternary conditional operator: the return value is being discarded.";
}
case WARNING_MAX:
break; // Can't happen, but silences warning
}
ERR_FAIL_V_MSG(String(), "Invalid GDScript warning code: " + get_name_from_code(code) + ".");
#undef CHECK_SYMBOLS
}
String GDScriptWarning::get_name() const {
return get_name_from_code(code);
}
String GDScriptWarning::get_name_from_code(Code p_code) {
ERR_FAIL_COND_V(p_code < 0 || p_code >= WARNING_MAX, String());
static const char *names[] = {
"UNASSIGNED_VARIABLE",
"UNASSIGNED_VARIABLE_OP_ASSIGN",
"UNUSED_VARIABLE",
"SHADOWED_VARIABLE",
"UNUSED_CLASS_VARIABLE",
"UNUSED_ARGUMENT",
"UNREACHABLE_CODE",
"STANDALONE_EXPRESSION",
"VOID_ASSIGNMENT",
"NARROWING_CONVERSION",
"FUNCTION_MAY_YIELD",
"VARIABLE_CONFLICTS_FUNCTION",
"FUNCTION_CONFLICTS_VARIABLE",
"FUNCTION_CONFLICTS_CONSTANT",
"INCOMPATIBLE_TERNARY",
"UNUSED_SIGNAL",
"RETURN_VALUE_DISCARDED",
"PROPERTY_USED_AS_FUNCTION",
"CONSTANT_USED_AS_FUNCTION",
"FUNCTION_USED_AS_PROPERTY",
"INTEGER_DIVISION",
"UNSAFE_PROPERTY_ACCESS",
"UNSAFE_METHOD_ACCESS",
"UNSAFE_CAST",
"UNSAFE_CALL_ARGUMENT",
"DEPRECATED_KEYWORD",
"STANDALONE_TERNARY",
nullptr
};
return names[(int)p_code];
}
GDScriptWarning::Code GDScriptWarning::get_code_from_name(const String &p_name) {
for (int i = 0; i < WARNING_MAX; i++) {
if (get_name_from_code((Code)i) == p_name) {
return (Code)i;
}
}
ERR_FAIL_V_MSG(WARNING_MAX, "Invalid GDScript warning name: " + p_name);
}
#endif // DEBUG_ENABLED
GDScriptLanguage::GDScriptLanguage() {
calls = 0;
ERR_FAIL_COND(singleton);
@ -2296,7 +2133,7 @@ void ResourceFormatLoaderGDScript::get_dependencies(const String &p_path, List<S
}
GDScriptParser parser;
if (OK != parser.parse(source, p_path.get_base_dir(), true, p_path, false, nullptr, true)) {
if (OK != parser.parse(source, p_path, false)) {
return;
}

View File

@ -104,7 +104,8 @@ class GDScript : public Script {
#endif
Map<StringName, PropertyInfo> member_info;
GDScriptFunction *initializer; //direct pointer to _init , faster to locate
GDScriptFunction *implicit_initializer = nullptr;
GDScriptFunction *initializer = nullptr; //direct pointer to new , faster to locate
int subclass_count;
Set<Object *> instances;
@ -117,6 +118,7 @@ class GDScript : public Script {
SelfList<GDScriptFunctionState>::List pending_func_states;
void _super_implicit_constructor(GDScript *p_script, GDScriptInstance *p_instance, Callable::CallError &r_error);
GDScriptInstance *_create_instance(const Variant **p_args, int p_argcount, Object *p_owner, bool p_isref, Callable::CallError &r_error);
void _set_subclass_path(Ref<GDScript> &p_sc, const String &p_path);

View File

@ -0,0 +1,283 @@
/*************************************************************************/
/* gdscript_analyzer.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_analyzer.h"
#include "core/class_db.h"
#include "core/hash_map.h"
#include "core/io/resource_loader.h"
#include "core/script_language.h"
Error GDScriptAnalyzer::resolve_inheritance(GDScriptParser::ClassNode *p_class, bool p_recursive) {
GDScriptParser::DataType result;
if (p_class->base_type.is_set()) {
// Already resolved
return OK;
}
if (!p_class->extends_used) {
result.type_source = GDScriptParser::DataType::ANNOTATED_INFERRED;
result.kind = GDScriptParser::DataType::NATIVE;
result.native_type = "Reference";
} else {
result.type_source = GDScriptParser::DataType::ANNOTATED_EXPLICIT;
GDScriptParser::DataType base;
if (!p_class->extends_path.empty()) {
base.type_source = GDScriptParser::DataType::ANNOTATED_EXPLICIT;
base.kind = GDScriptParser::DataType::CLASS;
// TODO: Don't load the script here to avoid the issue with cycles.
base.script_type = ResourceLoader::load(p_class->extends_path);
if (base.script_type.is_null() || !base.script_type->is_valid()) {
parser->push_error(vformat(R"(Could not load the parent script at "%s".)", p_class->extends_path));
}
// TODO: Get this from cache singleton.
base.gdscript_type = nullptr;
} else {
const StringName &name = p_class->extends[0];
base.type_source = GDScriptParser::DataType::ANNOTATED_EXPLICIT;
if (ScriptServer::is_global_class(name)) {
base.kind = GDScriptParser::DataType::CLASS;
// TODO: Get this from cache singleton.
base.gdscript_type = nullptr;
// TODO: Try singletons (create main unified source for those).
} else if (p_class->members_indices.has(name)) {
GDScriptParser::ClassNode::Member member = p_class->get_member(name);
if (member.type == member.CLASS) {
base.kind = GDScriptParser::DataType::CLASS;
base.gdscript_type = member.m_class;
} else if (member.type == member.CONSTANT) {
// FIXME: This could also be a native type or preloaded GDScript.
base.kind = GDScriptParser::DataType::CLASS;
base.gdscript_type = nullptr;
}
} else {
if (ClassDB::class_exists(name)) {
base.kind = GDScriptParser::DataType::NATIVE;
base.native_type = name;
}
}
}
// TODO: Extends with attributes (A.B.C).
result = base;
}
if (!result.is_set()) {
// TODO: More specific error messages.
parser->push_error(vformat(R"(Could not resolve inheritance for class "%s".)", p_class->identifier == nullptr ? "<main>" : p_class->identifier->name), p_class);
return ERR_PARSE_ERROR;
}
p_class->set_datatype(result);
if (p_recursive) {
for (int i = 0; i < p_class->members.size(); i++) {
if (p_class->members[i].type == GDScriptParser::ClassNode::Member::CLASS) {
resolve_inheritance(p_class->members[i].m_class, true);
}
}
}
return OK;
}
Error GDScriptAnalyzer::resolve_inheritance() {
return resolve_inheritance(parser->head);
}
// TODO: Move this to a central location (maybe core?).
static HashMap<StringName, StringName> underscore_map;
static const char *underscore_classes[] = {
"ClassDB",
"Directory",
"Engine",
"File",
"Geometry",
"GodotSharp",
"JSON",
"Marshalls",
"Mutex",
"OS",
"ResourceLoader",
"ResourceSaver",
"Semaphore",
"Thread",
"VisualScriptEditor",
nullptr,
};
static StringName get_real_class_name(const StringName &p_source) {
if (underscore_map.empty()) {
const char **class_name = underscore_classes;
while (*class_name != nullptr) {
underscore_map[*class_name] = String("_") + *class_name;
class_name++;
}
}
if (underscore_map.has(p_source)) {
return underscore_map[p_source];
}
return p_source;
}
GDScriptParser::DataType GDScriptAnalyzer::resolve_datatype(const GDScriptParser::TypeNode *p_type) {
GDScriptParser::DataType result;
if (p_type == nullptr) {
return result;
}
result.type_source = result.ANNOTATED_EXPLICIT;
if (p_type->type_base == nullptr) {
// void.
result.kind = GDScriptParser::DataType::BUILTIN;
result.builtin_type = Variant::NIL;
return result;
}
StringName first = p_type->type_base->name;
if (GDScriptParser::get_builtin_type(first) != Variant::NIL) {
// Built-in types.
// FIXME: I'm probably using this wrong here (well, I'm not really using it). Specifier *includes* the base the type.
if (p_type->type_specifier != nullptr) {
parser->push_error(R"(Built-in types don't contain subtypes.)", p_type->type_specifier);
return GDScriptParser::DataType();
}
result.kind = GDScriptParser::DataType::BUILTIN;
result.builtin_type = GDScriptParser::get_builtin_type(first);
} else if (ClassDB::class_exists(get_real_class_name(first))) {
// Native engine classes.
if (p_type->type_specifier != nullptr) {
parser->push_error(R"(Engine classes don't contain subtypes.)", p_type->type_specifier);
return GDScriptParser::DataType();
}
result.kind = GDScriptParser::DataType::NATIVE;
result.native_type = first;
} else if (ScriptServer::is_global_class(first)) {
// Global class_named classes.
// TODO: Global classes and singletons.
parser->push_error("GDScript analyzer: global class type not implemented.", p_type);
ERR_FAIL_V_MSG(GDScriptParser::DataType(), "GDScript analyzer: global class type not implemented.");
} else {
// Classes in current scope.
GDScriptParser::ClassNode *script_class = parser->current_class;
bool found = false;
while (!found && script_class != nullptr) {
if (script_class->members_indices.has(first)) {
GDScriptParser::ClassNode::Member member = script_class->members[script_class->members_indices[first]];
switch (member.type) {
case GDScriptParser::ClassNode::Member::CLASS:
result.kind = GDScriptParser::DataType::CLASS;
result.gdscript_type = member.m_class;
found = true;
break;
default:
// TODO: Get constants as types, disallow others explicitly.
parser->push_error(vformat(R"("%s" is a %s but does not contain a type.)", first, member.get_type_name()), p_type);
return GDScriptParser::DataType();
}
}
script_class = script_class->outer;
}
parser->push_error(vformat(R"("%s" is not a valid type.)", first), p_type);
return GDScriptParser::DataType();
}
// TODO: Allow subtypes.
if (p_type->type_specifier != nullptr) {
parser->push_error(R"(Subtypes are not yet supported.)", p_type->type_specifier);
return GDScriptParser::DataType();
}
return result;
}
Error GDScriptAnalyzer::resolve_datatypes(GDScriptParser::ClassNode *p_class) {
GDScriptParser::ClassNode *previous_class = parser->current_class;
parser->current_class = p_class;
for (int i = 0; i < p_class->members.size(); i++) {
GDScriptParser::ClassNode::Member member = p_class->members[i];
switch (member.type) {
case GDScriptParser::ClassNode::Member::VARIABLE: {
GDScriptParser::DataType datatype = resolve_datatype(member.variable->datatype_specifier);
if (datatype.is_set()) {
member.variable->set_datatype(datatype);
if (member.variable->export_info.hint == PROPERTY_HINT_TYPE_STRING) {
// @export annotation.
switch (datatype.kind) {
case GDScriptParser::DataType::BUILTIN:
member.variable->export_info.hint_string = Variant::get_type_name(datatype.builtin_type);
break;
case GDScriptParser::DataType::NATIVE:
if (ClassDB::is_parent_class(get_real_class_name(datatype.native_type), "Resource")) {
member.variable->export_info.hint_string = get_real_class_name(datatype.native_type);
} else {
parser->push_error(R"(Export type can only be built-in or a resource.)", member.variable);
}
break;
default:
// TODO: Allow custom user resources.
parser->push_error(R"(Export type can only be built-in or a resource.)", member.variable);
break;
}
}
}
break;
}
default:
// TODO
break;
}
}
parser->current_class = previous_class;
return parser->errors.size() > 0 ? ERR_PARSE_ERROR : OK;
}
Error GDScriptAnalyzer::analyze() {
parser->errors.clear();
Error err = resolve_inheritance(parser->head);
if (err) {
return err;
}
return resolve_datatypes(parser->head);
}
GDScriptAnalyzer::GDScriptAnalyzer(GDScriptParser *p_parser) {
parser = p_parser;
}

View File

@ -0,0 +1,52 @@
/*************************************************************************/
/* gdscript_analyzer.h */
/*************************************************************************/
/* 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. */
/*************************************************************************/
#ifndef GDSCRIPT_ANALYZER_H
#define GDSCRIPT_ANALYZER_H
#include "gdscript_parser.h"
class GDScriptAnalyzer {
GDScriptParser *parser = nullptr;
Error resolve_inheritance(GDScriptParser::ClassNode *p_class, bool p_recursive = true);
GDScriptParser::DataType resolve_datatype(const GDScriptParser::TypeNode *p_type);
// This traverses the tree to resolve all TypeNodes.
Error resolve_datatypes(GDScriptParser::ClassNode *p_class);
public:
Error resolve_inheritance();
Error analyze();
GDScriptAnalyzer(GDScriptParser *p_parser);
};
#endif // GDSCRIPT_ANALYZER_H

File diff suppressed because it is too large Load Diff

View File

@ -33,6 +33,7 @@
#include "core/set.h"
#include "gdscript.h"
#include "gdscript_function.h"
#include "gdscript_parser.h"
class GDScriptCompiler {
@ -52,6 +53,7 @@ class GDScriptCompiler {
List<GDScriptFunction::StackDebug> stack_debug;
List<Map<StringName, int>> block_identifier_stack;
Map<StringName, int> block_identifiers;
Map<StringName, int> local_named_constants;
void add_stack_identifier(const StringName &p_id, int p_stackpos) {
stack_identifiers[p_id] = p_stackpos;
@ -111,11 +113,11 @@ class GDScriptCompiler {
int get_constant_pos(const Variant &p_constant) {
if (constant_map.has(p_constant)) {
return constant_map[p_constant];
return constant_map[p_constant] | (GDScriptFunction::ADDR_TYPE_LOCAL_CONSTANT << GDScriptFunction::ADDR_BITS);
}
int pos = constant_map.size();
constant_map[p_constant] = pos;
return pos;
return pos | (GDScriptFunction::ADDR_TYPE_LOCAL_CONSTANT << GDScriptFunction::ADDR_BITS);
}
Vector<int> opcodes;
@ -140,14 +142,16 @@ class GDScriptCompiler {
void _set_error(const String &p_error, const GDScriptParser::Node *p_node);
bool _create_unary_operator(CodeGen &codegen, const GDScriptParser::OperatorNode *on, Variant::Operator op, int p_stack_level);
bool _create_binary_operator(CodeGen &codegen, const GDScriptParser::OperatorNode *on, Variant::Operator op, int p_stack_level, bool p_initializer = false, int p_index_addr = 0);
bool _create_unary_operator(CodeGen &codegen, const GDScriptParser::UnaryOpNode *on, Variant::Operator op, int p_stack_level);
bool _create_binary_operator(CodeGen &codegen, const GDScriptParser::BinaryOpNode *on, Variant::Operator op, int p_stack_level, bool p_initializer = false, int p_index_addr = 0);
bool _create_binary_operator(CodeGen &codegen, const GDScriptParser::ExpressionNode *p_left_operand, const GDScriptParser::ExpressionNode *p_right_operand, Variant::Operator op, int p_stack_level, bool p_initializer = false, int p_index_addr = 0);
GDScriptDataType _gdtype_from_datatype(const GDScriptParser::DataType &p_datatype) const;
int _parse_assign_right_expression(CodeGen &codegen, const GDScriptParser::OperatorNode *p_expression, int p_stack_level, int p_index_addr = 0);
int _parse_expression(CodeGen &codegen, const GDScriptParser::Node *p_expression, int p_stack_level, bool p_root = false, bool p_initializer = false, int p_index_addr = 0);
Error _parse_block(CodeGen &codegen, const GDScriptParser::BlockNode *p_block, int p_stack_level = 0, int p_break_addr = -1, int p_continue_addr = -1);
int _parse_assign_right_expression(CodeGen &codegen, const GDScriptParser::AssignmentNode *p_assignment, int p_stack_level, int p_index_addr = 0);
int _parse_expression(CodeGen &codegen, const GDScriptParser::ExpressionNode *p_expression, int p_stack_level, bool p_root = false, bool p_initializer = false, int p_index_addr = 0);
Error _parse_match_pattern(CodeGen &codegen, const GDScriptParser::PatternNode *p_pattern, int p_stack_level, int p_value_addr, int p_type_addr, int &r_bound_variables, Vector<int> &r_patch_addresses, Vector<int> &r_block_patch_address);
Error _parse_block(CodeGen &codegen, const GDScriptParser::SuiteNode *p_block, int p_stack_level = 0, int p_break_addr = -1, int p_continue_addr = -1);
Error _parse_function(GDScript *p_script, const GDScriptParser::ClassNode *p_class, const GDScriptParser::FunctionNode *p_func, bool p_for_ready = false);
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);
@ -156,6 +160,7 @@ class GDScriptCompiler {
int err_column;
StringName source;
String error;
bool within_await = false;
public:
Error compile(const GDScriptParser *p_parser, GDScript *p_script, bool p_keep_state = false);

File diff suppressed because it is too large Load Diff

View File

@ -210,12 +210,12 @@ String GDScriptFunction::_get_call_error(const Callable::CallError &p_err, const
&&OPCODE_CONSTRUCT_DICTIONARY, \
&&OPCODE_CALL, \
&&OPCODE_CALL_RETURN, \
&&OPCODE_CALL_ASYNC, \
&&OPCODE_CALL_BUILT_IN, \
&&OPCODE_CALL_SELF, \
&&OPCODE_CALL_SELF_BASE, \
&&OPCODE_YIELD, \
&&OPCODE_YIELD_SIGNAL, \
&&OPCODE_YIELD_RESUME, \
&&OPCODE_AWAIT, \
&&OPCODE_AWAIT_RESUME, \
&&OPCODE_JUMP, \
&&OPCODE_JUMP_IF, \
&&OPCODE_JUMP_IF_NOT, \
@ -227,7 +227,8 @@ String GDScriptFunction::_get_call_error(const Callable::CallError &p_err, const
&&OPCODE_BREAKPOINT, \
&&OPCODE_LINE, \
&&OPCODE_END \
};
}; \
static_assert((sizeof(switch_table_ops) / sizeof(switch_table_ops[0]) == (OPCODE_END + 1)), "Opcodes in jump table aren't the same as opcodes in enum.");
#define OPCODE(m_op) \
m_op:
@ -280,7 +281,7 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a
int line = _initial_line;
if (p_state) {
//use existing (supplied) state (yielded)
//use existing (supplied) state (awaited)
stack = (Variant *)p_state->stack.ptr();
call_args = (Variant **)&p_state->stack.ptr()[sizeof(Variant) * p_state->stack_size]; //ptr() to avoid bounds check
line = p_state->line;
@ -411,7 +412,7 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a
profile.frame_call_count++;
}
bool exit_ok = false;
bool yielded = false;
bool awaited = false;
#endif
#ifdef DEBUG_ENABLED
@ -1006,10 +1007,14 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a
}
DISPATCH_OPCODE;
OPCODE(OPCODE_CALL_ASYNC)
OPCODE(OPCODE_CALL_RETURN)
OPCODE(OPCODE_CALL) {
CHECK_SPACE(4);
bool call_ret = _code_ptr[ip] == OPCODE_CALL_RETURN;
bool call_ret = _code_ptr[ip] != OPCODE_CALL;
#ifdef DEBUG_ENABLED
bool call_async = _code_ptr[ip] == OPCODE_CALL_ASYNC;
#endif
int argc = _code_ptr[ip + 1];
GET_VARIANT_PTR(base, 2);
@ -1040,6 +1045,22 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a
if (call_ret) {
GET_VARIANT_PTR(ret, argc);
base->call_ptr(*methodname, (const Variant **)argptrs, argc, ret, err);
#ifdef DEBUG_ENABLED
if (!call_async && ret->get_type() == Variant::OBJECT) {
// Check if getting a function state without await.
bool was_freed = false;
Object *obj = ret->get_validated_object_with_check(was_freed);
if (was_freed) {
err_text = "Got a freed object as a result of the call.";
OPCODE_BREAK;
}
if (obj->is_class_ptr(GDScriptFunctionState::get_class_ptr_static())) {
err_text = R"(Trying to call an async function without "await".)";
OPCODE_BREAK;
}
}
#endif
} else {
base->call_ptr(*methodname, (const Variant **)argptrs, argc, nullptr, err);
}
@ -1192,105 +1213,96 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a
}
DISPATCH_OPCODE;
OPCODE(OPCODE_YIELD)
OPCODE(OPCODE_YIELD_SIGNAL) {
int ipofs = 1;
if (_code_ptr[ip] == OPCODE_YIELD_SIGNAL) {
CHECK_SPACE(4);
ipofs += 2;
} else {
CHECK_SPACE(2);
}
OPCODE(OPCODE_AWAIT) {
int ipofs = 2;
CHECK_SPACE(3);
Ref<GDScriptFunctionState> gdfs = memnew(GDScriptFunctionState);
gdfs->function = this;
//do the oneshot connect
GET_VARIANT_PTR(argobj, 1);
Signal sig;
bool is_signal = true;
gdfs->state.stack.resize(alloca_size);
//copy variant stack
for (int i = 0; i < _stack_size; i++) {
memnew_placement(&gdfs->state.stack.write[sizeof(Variant) * i], Variant(stack[i]));
}
gdfs->state.stack_size = _stack_size;
gdfs->state.self = self;
gdfs->state.alloca_size = alloca_size;
gdfs->state.ip = ip + ipofs;
gdfs->state.line = line;
gdfs->state.script = _script;
{
MutexLock lock(GDScriptLanguage::get_singleton()->lock);
_script->pending_func_states.add(&gdfs->scripts_list);
if (p_instance) {
gdfs->state.instance = p_instance;
p_instance->pending_func_states.add(&gdfs->instances_list);
Variant result = *argobj;
if (argobj->get_type() == Variant::OBJECT) {
bool was_freed = false;
Object *obj = argobj->get_validated_object_with_check(was_freed);
if (was_freed) {
err_text = "Trying to await on a freed object.";
OPCODE_BREAK;
}
// Is this even possible to be null at this point?
if (obj) {
if (obj->is_class_ptr(GDScriptFunctionState::get_class_ptr_static())) {
static StringName completed = _scs_create("completed");
result = Signal(obj, completed);
}
}
}
if (result.get_type() != Variant::SIGNAL) {
ip += 4; // Skip OPCODE_AWAIT_RESUME and its data.
// The stack pointer should be the same, so we don't need to set a return value.
is_signal = false;
} else {
gdfs->state.instance = nullptr;
sig = result;
}
}
if (is_signal) {
Ref<GDScriptFunctionState> gdfs = memnew(GDScriptFunctionState);
gdfs->function = this;
gdfs->state.stack.resize(alloca_size);
//copy variant stack
for (int i = 0; i < _stack_size; i++) {
memnew_placement(&gdfs->state.stack.write[sizeof(Variant) * i], Variant(stack[i]));
}
gdfs->state.stack_size = _stack_size;
gdfs->state.self = self;
gdfs->state.alloca_size = alloca_size;
gdfs->state.ip = ip + ipofs;
gdfs->state.line = line;
gdfs->state.script = _script;
{
MutexLock lock(GDScriptLanguage::get_singleton()->lock);
_script->pending_func_states.add(&gdfs->scripts_list);
if (p_instance) {
gdfs->state.instance = p_instance;
p_instance->pending_func_states.add(&gdfs->instances_list);
} else {
gdfs->state.instance = nullptr;
}
}
#ifdef DEBUG_ENABLED
gdfs->state.function_name = name;
gdfs->state.script_path = _script->get_path();
gdfs->state.function_name = name;
gdfs->state.script_path = _script->get_path();
#endif
gdfs->state.defarg = defarg;
gdfs->function = this;
gdfs->state.defarg = defarg;
gdfs->function = this;
retvalue = gdfs;
retvalue = gdfs;
if (_code_ptr[ip] == OPCODE_YIELD_SIGNAL) {
//do the oneshot connect
GET_VARIANT_PTR(argobj, 1);
GET_VARIANT_PTR(argname, 2);
#ifdef DEBUG_ENABLED
if (argobj->get_type() != Variant::OBJECT) {
err_text = "First argument of yield() not of type object.";
OPCODE_BREAK;
}
if (argname->get_type() != Variant::STRING) {
err_text = "Second argument of yield() not a string (for signal name).";
OPCODE_BREAK;
}
#endif
#ifdef DEBUG_ENABLED
bool was_freed;
Object *obj = argobj->get_validated_object_with_check(was_freed);
String signal = argname->operator String();
if (was_freed) {
err_text = "First argument of yield() is a previously freed instance.";
OPCODE_BREAK;
}
if (!obj) {
err_text = "First argument of yield() is null.";
OPCODE_BREAK;
}
if (signal.length() == 0) {
err_text = "Second argument of yield() is an empty string (for signal name).";
OPCODE_BREAK;
}
Error err = obj->connect_compat(signal, gdfs.ptr(), "_signal_callback", varray(gdfs), Object::CONNECT_ONESHOT);
Error err = sig.connect(Callable(gdfs.ptr(), "_signal_callback"), varray(gdfs), Object::CONNECT_ONESHOT);
if (err != OK) {
err_text = "Error connecting to signal: " + signal + " during yield().";
err_text = "Error connecting to signal: " + sig.get_name() + " during await.";
OPCODE_BREAK;
}
#else
Object *obj = argobj->operator Object *();
String signal = argname->operator String();
obj->connect_compat(signal, gdfs.ptr(), "_signal_callback", varray(gdfs), Object::CONNECT_ONESHOT);
#endif
}
#ifdef DEBUG_ENABLED
exit_ok = true;
yielded = true;
exit_ok = true;
awaited = true;
#endif
OPCODE_BREAK;
OPCODE_BREAK;
}
}
DISPATCH_OPCODE; // Needed for synchronous calls (when result is immediately available).
OPCODE(OPCODE_YIELD_RESUME) {
OPCODE(OPCODE_AWAIT_RESUME) {
CHECK_SPACE(2);
#ifdef DEBUG_ENABLED
if (!p_state) {
@ -1556,11 +1568,11 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a
GDScriptLanguage::get_singleton()->script_frame_time += time_taken - function_call_time;
}
// Check if this is the last time the function is resuming from yield
// Will be true if never yielded as well
// Check if this is the last time the function is resuming from await
// Will be true if never awaited as well
// When it's the last resume it will postpone the exit from stack,
// so the debugger knows which function triggered the resume of the next function (if any)
if (!p_state || yielded) {
if (!p_state || awaited) {
if (EngineDebugger::is_active()) {
GDScriptLanguage::get_singleton()->exit_function();
}
@ -1786,14 +1798,14 @@ Variant GDScriptFunctionState::resume(const Variant &p_arg) {
if (!scripts_list.in_list()) {
#ifdef DEBUG_ENABLED
ERR_FAIL_V_MSG(Variant(), "Resumed function '" + state.function_name + "()' after yield, but script is gone. At script: " + state.script_path + ":" + itos(state.line));
ERR_FAIL_V_MSG(Variant(), "Resumed function '" + state.function_name + "()' after await, but script is gone. At script: " + state.script_path + ":" + itos(state.line));
#else
return Variant();
#endif
}
if (state.instance && !instances_list.in_list()) {
#ifdef DEBUG_ENABLED
ERR_FAIL_V_MSG(Variant(), "Resumed function '" + state.function_name + "()' after yield, but class instance is gone. At script: " + state.script_path + ":" + itos(state.line));
ERR_FAIL_V_MSG(Variant(), "Resumed function '" + state.function_name + "()' after await, but class instance is gone. At script: " + state.script_path + ":" + itos(state.line));
#else
return Variant();
#endif
@ -1810,7 +1822,7 @@ Variant GDScriptFunctionState::resume(const Variant &p_arg) {
bool completed = true;
// If the return value is a GDScriptFunctionState reference,
// then the function did yield again after resuming.
// then the function did awaited again after resuming.
if (ret.is_ref()) {
GDScriptFunctionState *gdfs = Object::cast_to<GDScriptFunctionState>(ret);
if (gdfs && gdfs->function == function) {

View File

@ -180,12 +180,12 @@ public:
OPCODE_CONSTRUCT_DICTIONARY,
OPCODE_CALL,
OPCODE_CALL_RETURN,
OPCODE_CALL_ASYNC,
OPCODE_CALL_BUILT_IN,
OPCODE_CALL_SELF,
OPCODE_CALL_SELF_BASE,
OPCODE_YIELD,
OPCODE_YIELD_SIGNAL,
OPCODE_YIELD_RESUME,
OPCODE_AWAIT,
OPCODE_AWAIT_RESUME,
OPCODE_JUMP,
OPCODE_JUMP_IF,
OPCODE_JUMP_IF_NOT,

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -31,268 +31,209 @@
#ifndef GDSCRIPT_TOKENIZER_H
#define GDSCRIPT_TOKENIZER_H
#include "core/pair.h"
#include "core/list.h"
#include "core/set.h"
#include "core/string_name.h"
#include "core/ustring.h"
#include "core/variant.h"
#include "core/vmap.h"
#include "gdscript_functions.h"
#include "core/vector.h"
class GDScriptTokenizer {
public:
enum Token {
TK_EMPTY,
TK_IDENTIFIER,
TK_CONSTANT,
TK_SELF,
TK_BUILT_IN_TYPE,
TK_BUILT_IN_FUNC,
TK_OP_IN,
TK_OP_EQUAL,
TK_OP_NOT_EQUAL,
TK_OP_LESS,
TK_OP_LESS_EQUAL,
TK_OP_GREATER,
TK_OP_GREATER_EQUAL,
TK_OP_AND,
TK_OP_OR,
TK_OP_NOT,
TK_OP_ADD,
TK_OP_SUB,
TK_OP_MUL,
TK_OP_DIV,
TK_OP_MOD,
TK_OP_SHIFT_LEFT,
TK_OP_SHIFT_RIGHT,
TK_OP_ASSIGN,
TK_OP_ASSIGN_ADD,
TK_OP_ASSIGN_SUB,
TK_OP_ASSIGN_MUL,
TK_OP_ASSIGN_DIV,
TK_OP_ASSIGN_MOD,
TK_OP_ASSIGN_SHIFT_LEFT,
TK_OP_ASSIGN_SHIFT_RIGHT,
TK_OP_ASSIGN_BIT_AND,
TK_OP_ASSIGN_BIT_OR,
TK_OP_ASSIGN_BIT_XOR,
TK_OP_BIT_AND,
TK_OP_BIT_OR,
TK_OP_BIT_XOR,
TK_OP_BIT_INVERT,
//TK_OP_PLUS_PLUS,
//TK_OP_MINUS_MINUS,
TK_CF_IF,
TK_CF_ELIF,
TK_CF_ELSE,
TK_CF_FOR,
TK_CF_WHILE,
TK_CF_BREAK,
TK_CF_CONTINUE,
TK_CF_PASS,
TK_CF_RETURN,
TK_CF_MATCH,
TK_PR_FUNCTION,
TK_PR_CLASS,
TK_PR_CLASS_NAME,
TK_PR_EXTENDS,
TK_PR_IS,
TK_PR_ONREADY,
TK_PR_TOOL,
TK_PR_STATIC,
TK_PR_EXPORT,
TK_PR_SETGET,
TK_PR_CONST,
TK_PR_VAR,
TK_PR_AS,
TK_PR_VOID,
TK_PR_ENUM,
TK_PR_PRELOAD,
TK_PR_ASSERT,
TK_PR_YIELD,
TK_PR_SIGNAL,
TK_PR_BREAKPOINT,
TK_PR_REMOTE,
TK_PR_MASTER,
TK_PR_PUPPET,
TK_PR_REMOTESYNC,
TK_PR_MASTERSYNC,
TK_PR_PUPPETSYNC,
TK_BRACKET_OPEN,
TK_BRACKET_CLOSE,
TK_CURLY_BRACKET_OPEN,
TK_CURLY_BRACKET_CLOSE,
TK_PARENTHESIS_OPEN,
TK_PARENTHESIS_CLOSE,
TK_COMMA,
TK_SEMICOLON,
TK_PERIOD,
TK_QUESTION_MARK,
TK_COLON,
TK_DOLLAR,
TK_FORWARD_ARROW,
TK_NEWLINE,
TK_CONST_PI,
TK_CONST_TAU,
TK_WILDCARD,
TK_CONST_INF,
TK_CONST_NAN,
TK_ERROR,
TK_EOF,
TK_CURSOR, //used for code completion
TK_MAX
};
protected:
enum StringMode {
STRING_SINGLE_QUOTE,
STRING_DOUBLE_QUOTE,
STRING_MULTILINE
};
static const char *token_names[TK_MAX];
public:
static const char *get_token_name(Token p_token);
bool is_token_literal(int p_offset = 0, bool variable_safe = false) const;
StringName get_token_literal(int p_offset = 0) const;
virtual const Variant &get_token_constant(int p_offset = 0) const = 0;
virtual Token get_token(int p_offset = 0) const = 0;
virtual StringName get_token_identifier(int p_offset = 0) const = 0;
virtual GDScriptFunctions::Function get_token_built_in_func(int p_offset = 0) const = 0;
virtual Variant::Type get_token_type(int p_offset = 0) const = 0;
virtual int get_token_line(int p_offset = 0) const = 0;
virtual int get_token_column(int p_offset = 0) const = 0;
virtual int get_token_line_indent(int p_offset = 0) const = 0;
virtual int get_token_line_tab_indent(int p_offset = 0) const = 0;
virtual String get_token_error(int p_offset = 0) const = 0;
virtual void advance(int p_amount = 1) = 0;
#ifdef DEBUG_ENABLED
virtual const Vector<Pair<int, String>> &get_warning_skips() const = 0;
virtual const Set<String> &get_warning_global_skips() const = 0;
virtual bool is_ignoring_warnings() const = 0;
#endif // DEBUG_ENABLED
virtual ~GDScriptTokenizer() {}
};
class GDScriptTokenizerText : public GDScriptTokenizer {
enum {
MAX_LOOKAHEAD = 4,
TK_RB_SIZE = MAX_LOOKAHEAD * 2 + 1
};
struct TokenData {
Token type;
StringName identifier; //for identifier types
Variant constant; //for constant types
union {
Variant::Type vtype; //for type types
GDScriptFunctions::Function func; //function for built in functions
int warning_code; //for warning skip
struct Token {
enum Type {
EMPTY,
// Basic
ANNOTATION,
IDENTIFIER,
LITERAL,
// Comparison
LESS,
LESS_EQUAL,
GREATER,
GREATER_EQUAL,
EQUAL_EQUAL,
BANG_EQUAL,
// Logical
AND,
OR,
NOT,
AMPERSAND_AMPERSAND,
PIPE_PIPE,
BANG,
// Bitwise
AMPERSAND,
PIPE,
TILDE,
CARET,
LESS_LESS,
GREATER_GREATER,
// Math
PLUS,
MINUS,
STAR,
SLASH,
PERCENT,
// Assignment
EQUAL,
PLUS_EQUAL,
MINUS_EQUAL,
STAR_EQUAL,
SLASH_EQUAL,
PERCENT_EQUAL,
LESS_LESS_EQUAL,
GREATER_GREATER_EQUAL,
AMPERSAND_EQUAL,
PIPE_EQUAL,
CARET_EQUAL,
// Control flow
IF,
ELIF,
ELSE,
FOR,
WHILE,
BREAK,
CONTINUE,
PASS,
RETURN,
MATCH,
// Keywords
AS,
ASSERT,
AWAIT,
BREAKPOINT,
CLASS,
CLASS_NAME,
CONST,
ENUM,
EXTENDS,
FUNC,
IN,
IS,
NAMESPACE,
PRELOAD,
SELF,
SIGNAL,
STATIC,
SUPER,
VAR,
VOID,
YIELD,
// Punctuation
BRACKET_OPEN,
BRACKET_CLOSE,
BRACE_OPEN,
BRACE_CLOSE,
PARENTHESIS_OPEN,
PARENTHESIS_CLOSE,
COMMA,
SEMICOLON,
PERIOD,
PERIOD_PERIOD,
COLON,
DOLLAR,
FORWARD_ARROW,
UNDERSCORE,
// Whitespace
NEWLINE,
INDENT,
DEDENT,
// Constants
CONST_PI,
CONST_TAU,
CONST_INF,
CONST_NAN,
// Error message improvement
VCS_CONFLICT_MARKER,
BACKTICK,
QUESTION_MARK,
// Special
ERROR,
TK_EOF, // "EOF" is reserved
TK_MAX
};
int line, col;
TokenData() {
type = TK_EMPTY;
line = col = 0;
vtype = Variant::NIL;
Type type = EMPTY;
Variant literal;
int start_line = 0, end_line = 0, start_column = 0, end_column = 0;
int leftmost_column = 0, rightmost_column = 0; // Column span for multiline tokens.
const char *get_name() const;
// TODO: Allow some keywords as identifiers?
bool is_identifier() const { return type == IDENTIFIER; }
StringName get_identifier() const { return literal; }
Token(Type p_type) {
type = p_type;
}
Token() {
type = EMPTY;
}
};
void _make_token(Token p_type);
void _make_newline(int p_indentation = 0, int p_tabs = 0);
void _make_identifier(const StringName &p_identifier);
void _make_built_in_func(GDScriptFunctions::Function p_func);
void _make_constant(const Variant &p_constant);
void _make_type(const Variant::Type &p_type);
void _make_error(const String &p_error);
private:
String source;
const CharType *_source = nullptr;
const CharType *_current = nullptr;
int line = 0, column = 0;
int cursor_line = 0, cursor_column = 0;
int tab_size = 4;
String code;
int len;
int code_pos;
const CharType *_code;
int line;
int column;
TokenData tk_rb[TK_RB_SIZE * 2 + 1];
int tk_rb_pos;
String last_error;
bool error_flag;
// Keep track of multichar tokens.
const CharType *_start = nullptr;
int start_line = 0, start_column = 0;
int leftmost_column = 0, rightmost_column = 0;
#ifdef DEBUG_ENABLED
Vector<Pair<int, String>> warning_skips;
Set<String> warning_global_skips;
bool ignore_warnings;
#endif // DEBUG_ENABLED
// Info cache.
bool line_continuation = false; // Whether this line is a continuation of the previous, like when using '\'.
bool multiline_mode = false;
List<Token> error_stack;
bool pending_newline = false;
Token last_newline;
int pending_indents = 0;
List<int> indent_stack;
List<CharType> paren_stack;
CharType indent_char = '\0';
int position = 0;
int length = 0;
void _advance();
_FORCE_INLINE_ bool _is_at_end() { return position >= length; }
_FORCE_INLINE_ CharType _peek(int p_offset = 0) { return position + p_offset >= 0 && position + p_offset < length ? _current[p_offset] : '\0'; }
int indent_level() const { return indent_stack.size(); }
bool has_error() const { return !error_stack.empty(); }
Token pop_error();
CharType _advance();
void _skip_whitespace();
void check_indent();
Token make_error(const String &p_message);
void push_error(const String &p_message);
void push_error(const Token &p_error);
Token make_paren_error(CharType p_paren);
Token make_token(Token::Type p_type) const;
Token make_literal(const Variant &p_literal) const;
Token make_identifier(const StringName &p_identifier) const;
Token check_vcs_marker(CharType p_test, Token::Type p_double_type);
void push_paren(CharType p_char);
bool pop_paren(CharType p_expected);
void newline(bool p_make_token);
Token number();
Token potential_identifier();
Token string();
Token annotation();
public:
void set_code(const String &p_code);
virtual Token get_token(int p_offset = 0) const;
virtual StringName get_token_identifier(int p_offset = 0) const;
virtual GDScriptFunctions::Function get_token_built_in_func(int p_offset = 0) const;
virtual Variant::Type get_token_type(int p_offset = 0) const;
virtual int get_token_line(int p_offset = 0) const;
virtual int get_token_column(int p_offset = 0) const;
virtual int get_token_line_indent(int p_offset = 0) const;
virtual int get_token_line_tab_indent(int p_offset = 0) const;
virtual const Variant &get_token_constant(int p_offset = 0) const;
virtual String get_token_error(int p_offset = 0) const;
virtual void advance(int p_amount = 1);
#ifdef DEBUG_ENABLED
virtual const Vector<Pair<int, String>> &get_warning_skips() const { return warning_skips; }
virtual const Set<String> &get_warning_global_skips() const { return warning_global_skips; }
virtual bool is_ignoring_warnings() const { return ignore_warnings; }
#endif // DEBUG_ENABLED
Token scan();
void set_source_code(const String &p_source_code);
int get_cursor_line() const;
int get_cursor_column() const;
void set_cursor_position(int p_line, int p_column);
void set_multiline_mode(bool p_state);
static String get_token_name(Token::Type p_token_type);
GDScriptTokenizer();
};
class GDScriptTokenizerBuffer : public GDScriptTokenizer {
enum {
TOKEN_BYTE_MASK = 0x80,
TOKEN_BITS = 8,
TOKEN_MASK = (1 << TOKEN_BITS) - 1,
TOKEN_LINE_BITS = 24,
TOKEN_LINE_MASK = (1 << TOKEN_LINE_BITS) - 1,
};
Vector<StringName> identifiers;
Vector<Variant> constants;
VMap<uint32_t, uint32_t> lines;
Vector<uint32_t> tokens;
Variant nil;
int token;
public:
Error set_code_buffer(const Vector<uint8_t> &p_buffer);
static Vector<uint8_t> parse_code_string(const String &p_code);
virtual Token get_token(int p_offset = 0) const;
virtual StringName get_token_identifier(int p_offset = 0) const;
virtual GDScriptFunctions::Function get_token_built_in_func(int p_offset = 0) const;
virtual Variant::Type get_token_type(int p_offset = 0) const;
virtual int get_token_line(int p_offset = 0) const;
virtual int get_token_column(int p_offset = 0) const;
virtual int get_token_line_indent(int p_offset = 0) const;
virtual int get_token_line_tab_indent(int p_offset = 0) const { return 0; }
virtual const Variant &get_token_constant(int p_offset = 0) const;
virtual String get_token_error(int p_offset = 0) const;
virtual void advance(int p_amount = 1);
#ifdef DEBUG_ENABLED
virtual const Vector<Pair<int, String>> &get_warning_skips() const {
static Vector<Pair<int, String>> v;
return v;
}
virtual const Set<String> &get_warning_global_skips() const {
static Set<String> s;
return s;
}
virtual bool is_ignoring_warnings() const { return true; }
#endif // DEBUG_ENABLED
GDScriptTokenizerBuffer();
};
#endif // GDSCRIPT_TOKENIZER_H
#endif

View File

@ -0,0 +1,201 @@
/*************************************************************************/
/* gdscript_warning.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_warning.h"
#ifdef DEBUG_ENABLED
String GDScriptWarning::get_message() const {
#define CHECK_SYMBOLS(m_amount) ERR_FAIL_COND_V(symbols.size() < m_amount, String());
switch (code) {
case UNASSIGNED_VARIABLE_OP_ASSIGN: {
CHECK_SYMBOLS(1);
return "Using assignment with operation but the variable '" + symbols[0] + "' was not previously assigned a value.";
} break;
case UNASSIGNED_VARIABLE: {
CHECK_SYMBOLS(1);
return "The variable '" + symbols[0] + "' was used but never assigned a value.";
} break;
case UNUSED_VARIABLE: {
CHECK_SYMBOLS(1);
return "The local variable '" + symbols[0] + "' is declared but never used in the block. If this is intended, prefix it with an underscore: '_" + symbols[0] + "'";
} break;
case SHADOWED_VARIABLE: {
CHECK_SYMBOLS(2);
return "The local variable '" + symbols[0] + "' is shadowing an already-defined variable at line " + symbols[1] + ".";
} break;
case UNUSED_CLASS_VARIABLE: {
CHECK_SYMBOLS(1);
return "The class variable '" + symbols[0] + "' is declared but never used in the script.";
} break;
case UNUSED_ARGUMENT: {
CHECK_SYMBOLS(2);
return "The argument '" + symbols[1] + "' is never used in the function '" + symbols[0] + "'. If this is intended, prefix it with an underscore: '_" + symbols[1] + "'";
} break;
case UNREACHABLE_CODE: {
CHECK_SYMBOLS(1);
return "Unreachable code (statement after return) in function '" + symbols[0] + "()'.";
} break;
case STANDALONE_EXPRESSION: {
return "Standalone expression (the line has no effect).";
} break;
case VOID_ASSIGNMENT: {
CHECK_SYMBOLS(1);
return "Assignment operation, but the function '" + symbols[0] + "()' returns void.";
} break;
case NARROWING_CONVERSION: {
return "Narrowing conversion (float is converted to int and loses precision).";
} break;
case FUNCTION_MAY_YIELD: {
CHECK_SYMBOLS(1);
return "Assigned variable is typed but the function '" + symbols[0] + "()' may yield and return a GDScriptFunctionState instead.";
} break;
case VARIABLE_CONFLICTS_FUNCTION: {
CHECK_SYMBOLS(1);
return "Variable declaration of '" + symbols[0] + "' conflicts with a function of the same name.";
} break;
case FUNCTION_CONFLICTS_VARIABLE: {
CHECK_SYMBOLS(1);
return "Function declaration of '" + symbols[0] + "()' conflicts with a variable of the same name.";
} break;
case FUNCTION_CONFLICTS_CONSTANT: {
CHECK_SYMBOLS(1);
return "Function declaration of '" + symbols[0] + "()' conflicts with a constant of the same name.";
} break;
case INCOMPATIBLE_TERNARY: {
return "Values of the ternary conditional are not mutually compatible.";
} break;
case UNUSED_SIGNAL: {
CHECK_SYMBOLS(1);
return "The signal '" + symbols[0] + "' is declared but never emitted.";
} break;
case RETURN_VALUE_DISCARDED: {
CHECK_SYMBOLS(1);
return "The function '" + symbols[0] + "()' returns a value, but this value is never used.";
} break;
case PROPERTY_USED_AS_FUNCTION: {
CHECK_SYMBOLS(2);
return "The method '" + symbols[0] + "()' was not found in base '" + symbols[1] + "' but there's a property with the same name. Did you mean to access it?";
} break;
case CONSTANT_USED_AS_FUNCTION: {
CHECK_SYMBOLS(2);
return "The method '" + symbols[0] + "()' was not found in base '" + symbols[1] + "' but there's a constant with the same name. Did you mean to access it?";
} break;
case FUNCTION_USED_AS_PROPERTY: {
CHECK_SYMBOLS(2);
return "The property '" + symbols[0] + "' was not found in base '" + symbols[1] + "' but there's a method with the same name. Did you mean to call it?";
} break;
case INTEGER_DIVISION: {
return "Integer division, decimal part will be discarded.";
} break;
case UNSAFE_PROPERTY_ACCESS: {
CHECK_SYMBOLS(2);
return "The property '" + symbols[0] + "' is not present on the inferred type '" + symbols[1] + "' (but may be present on a subtype).";
} break;
case UNSAFE_METHOD_ACCESS: {
CHECK_SYMBOLS(2);
return "The method '" + symbols[0] + "' is not present on the inferred type '" + symbols[1] + "' (but may be present on a subtype).";
} break;
case UNSAFE_CAST: {
CHECK_SYMBOLS(1);
return "The value is cast to '" + symbols[0] + "' but has an unknown type.";
} break;
case UNSAFE_CALL_ARGUMENT: {
CHECK_SYMBOLS(4);
return "The argument '" + symbols[0] + "' of the function '" + symbols[1] + "' requires a the subtype '" + symbols[2] + "' but the supertype '" + symbols[3] + "' was provided";
} break;
case DEPRECATED_KEYWORD: {
CHECK_SYMBOLS(2);
return "The '" + symbols[0] + "' keyword is deprecated and will be removed in a future release, please replace its uses by '" + symbols[1] + "'.";
} break;
case STANDALONE_TERNARY: {
return "Standalone ternary conditional operator: the return value is being discarded.";
}
case WARNING_MAX:
break; // Can't happen, but silences warning
}
ERR_FAIL_V_MSG(String(), "Invalid GDScript warning code: " + get_name_from_code(code) + ".");
#undef CHECK_SYMBOLS
}
String GDScriptWarning::get_name() const {
return get_name_from_code(code);
}
String GDScriptWarning::get_name_from_code(Code p_code) {
ERR_FAIL_COND_V(p_code < 0 || p_code >= WARNING_MAX, String());
static const char *names[] = {
"UNASSIGNED_VARIABLE",
"UNASSIGNED_VARIABLE_OP_ASSIGN",
"UNUSED_VARIABLE",
"SHADOWED_VARIABLE",
"UNUSED_CLASS_VARIABLE",
"UNUSED_ARGUMENT",
"UNREACHABLE_CODE",
"STANDALONE_EXPRESSION",
"VOID_ASSIGNMENT",
"NARROWING_CONVERSION",
"FUNCTION_MAY_YIELD",
"VARIABLE_CONFLICTS_FUNCTION",
"FUNCTION_CONFLICTS_VARIABLE",
"FUNCTION_CONFLICTS_CONSTANT",
"INCOMPATIBLE_TERNARY",
"UNUSED_SIGNAL",
"RETURN_VALUE_DISCARDED",
"PROPERTY_USED_AS_FUNCTION",
"CONSTANT_USED_AS_FUNCTION",
"FUNCTION_USED_AS_PROPERTY",
"INTEGER_DIVISION",
"UNSAFE_PROPERTY_ACCESS",
"UNSAFE_METHOD_ACCESS",
"UNSAFE_CAST",
"UNSAFE_CALL_ARGUMENT",
"DEPRECATED_KEYWORD",
"STANDALONE_TERNARY",
nullptr
};
return names[(int)p_code];
}
GDScriptWarning::Code GDScriptWarning::get_code_from_name(const String &p_name) {
for (int i = 0; i < WARNING_MAX; i++) {
if (get_name_from_code((Code)i) == p_name) {
return (Code)i;
}
}
ERR_FAIL_V_MSG(WARNING_MAX, "Invalid GDScript warning name: " + p_name);
}
#endif // DEBUG_ENABLED

View File

@ -0,0 +1,84 @@
/*************************************************************************/
/* gdscript_warning.h */
/*************************************************************************/
/* 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. */
/*************************************************************************/
#ifndef GDSCRIPT_WARNINGS
#define GDSCRIPT_WARNINGS
#ifdef DEBUG_ENABLED
#include "core/ustring.h"
#include "core/vector.h"
class GDScriptWarning {
public:
enum Code {
UNASSIGNED_VARIABLE, // Variable used but never assigned
UNASSIGNED_VARIABLE_OP_ASSIGN, // Variable never assigned but used in an assignment operation (+=, *=, etc)
UNUSED_VARIABLE, // Local variable is declared but never used
SHADOWED_VARIABLE, // Variable name shadowed by other variable
UNUSED_CLASS_VARIABLE, // Class variable is declared but never used in the file
UNUSED_ARGUMENT, // Function argument is never used
UNREACHABLE_CODE, // Code after a return statement
STANDALONE_EXPRESSION, // Expression not assigned to a variable
VOID_ASSIGNMENT, // Function returns void but it's assigned to a variable
NARROWING_CONVERSION, // Float value into an integer slot, precision is lost
FUNCTION_MAY_YIELD, // Typed assign of function call that yields (it may return a function state)
VARIABLE_CONFLICTS_FUNCTION, // Variable has the same name of a function
FUNCTION_CONFLICTS_VARIABLE, // Function has the same name of a variable
FUNCTION_CONFLICTS_CONSTANT, // Function has the same name of a constant
INCOMPATIBLE_TERNARY, // Possible values of a ternary if are not mutually compatible
UNUSED_SIGNAL, // Signal is defined but never emitted
RETURN_VALUE_DISCARDED, // Function call returns something but the value isn't used
PROPERTY_USED_AS_FUNCTION, // Function not found, but there's a property with the same name
CONSTANT_USED_AS_FUNCTION, // Function not found, but there's a constant with the same name
FUNCTION_USED_AS_PROPERTY, // Property not found, but there's a function with the same name
INTEGER_DIVISION, // Integer divide by integer, decimal part is discarded
UNSAFE_PROPERTY_ACCESS, // Property not found in the detected type (but can be in subtypes)
UNSAFE_METHOD_ACCESS, // Function not found in the detected type (but can be in subtypes)
UNSAFE_CAST, // Cast used in an unknown type
UNSAFE_CALL_ARGUMENT, // Function call argument is of a supertype of the require argument
DEPRECATED_KEYWORD, // The keyword is deprecated and should be replaced
STANDALONE_TERNARY, // Return value of ternary expression is discarded
WARNING_MAX,
};
Code code = WARNING_MAX;
int line = -1;
Vector<String> symbols;
String get_name() const;
String get_message() const;
static String get_name_from_code(Code p_code);
static Code get_code_from_name(const String &p_name);
};
#endif // DEBUG_ENABLED
#endif // GDSCRIPT_WARNINGS

View File

@ -28,6 +28,9 @@
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/*************************************************************************/
// FIXME: Reenable LSP.
#if 0
#include "gdscript_extend_parser.h"
#include "../gdscript.h"
@ -769,3 +772,5 @@ Error ExtendGDScriptParser::parse(const String &p_code, const String &p_path) {
update_document_links(p_code);
return err;
}
#endif

View File

@ -31,6 +31,9 @@
#ifndef GDSCRIPT_EXTEND_PARSER_H
#define GDSCRIPT_EXTEND_PARSER_H
// FIXME: Reenable LSP.
#if 0
#include "../gdscript_parser.h"
#include "core/variant.h"
#include "lsp.hpp"
@ -100,3 +103,5 @@ public:
};
#endif
#endif

View File

@ -28,6 +28,9 @@
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/*************************************************************************/
// FIXME: Reenable LSP.
#if 0
#include "gdscript_language_protocol.h"
#include "core/io/json.h"
@ -303,3 +306,5 @@ GDScriptLanguageProtocol::GDScriptLanguageProtocol() {
set_scope("workspace", workspace.ptr());
workspace->root = ProjectSettings::get_singleton()->get_resource_path();
}
#endif

View File

@ -31,6 +31,9 @@
#ifndef GDSCRIPT_PROTOCAL_SERVER_H
#define GDSCRIPT_PROTOCAL_SERVER_H
// FIXME: Reenable LSP.
#if 0
#include "core/io/stream_peer.h"
#include "core/io/stream_peer_tcp.h"
#include "core/io/tcp_server.h"
@ -109,3 +112,5 @@ public:
};
#endif
#endif

View File

@ -28,6 +28,9 @@
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/*************************************************************************/
// FIXME: Reenable LSP.
#if 0
#include "gdscript_language_server.h"
#include "core/os/file_access.h"
@ -114,3 +117,5 @@ void register_lsp_types() {
ClassDB::register_class<GDScriptTextDocument>();
ClassDB::register_class<GDScriptWorkspace>();
}
#endif

View File

@ -31,6 +31,9 @@
#ifndef GDSCRIPT_LANGUAGE_SERVER_H
#define GDSCRIPT_LANGUAGE_SERVER_H
// FIXME: Reenable LSP.
#if 0
#include "../gdscript_parser.h"
#include "editor/editor_plugin.h"
#include "gdscript_language_protocol.h"
@ -60,4 +63,6 @@ public:
void register_lsp_types();
#endif
#endif // GDSCRIPT_LANGUAGE_SERVER_H

View File

@ -38,6 +38,9 @@
#include "gdscript_language_protocol.h"
#include "servers/display_server.h"
// FIXME: Reenable LSP.
#if 0
void GDScriptTextDocument::_bind_methods() {
ClassDB::bind_method(D_METHOD("didOpen"), &GDScriptTextDocument::didOpen);
ClassDB::bind_method(D_METHOD("didChange"), &GDScriptTextDocument::didChange);
@ -437,3 +440,5 @@ Array GDScriptTextDocument::find_symbols(const lsp::TextDocumentPositionParams &
}
return arr;
}
#endif

View File

@ -31,6 +31,9 @@
#ifndef GDSCRIPT_TEXT_DOCUMENT_H
#define GDSCRIPT_TEXT_DOCUMENT_H
// FIXME: Reenable LSP.
#if 0
#include "core/os/file_access.h"
#include "core/reference.h"
#include "lsp.hpp"
@ -76,3 +79,5 @@ public:
};
#endif
#endif

View File

@ -28,6 +28,9 @@
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/*************************************************************************/
// FIXME: Reenable LSP.
#if 0
#include "gdscript_workspace.h"
#include "../gdscript.h"
@ -618,3 +621,5 @@ GDScriptWorkspace::~GDScriptWorkspace() {
remove_cache_parser(E->get());
}
}
#endif

View File

@ -31,6 +31,9 @@
#ifndef GDSCRIPT_WORKSPACE_H
#define GDSCRIPT_WORKSPACE_H
// FIXME: Reenable LSP.
#if 0
#include "../gdscript_parser.h"
#include "core/variant.h"
#include "editor/editor_file_system.h"
@ -95,3 +98,5 @@ public:
};
#endif
#endif

View File

@ -31,6 +31,9 @@
#ifndef GODOT_LSP_H
#define GODOT_LSP_H
// FIXME: Reenable LSP.
#if 0
#include "core/class_db.h"
#include "core/list.h"
#include "editor/doc_data.h"
@ -1785,3 +1788,5 @@ static String marked_documentation(const String &p_bbcode) {
} // namespace lsp
#endif
#endif

View File

@ -50,10 +50,13 @@ Ref<ResourceFormatSaverGDScript> resource_saver_gd;
#include "editor/gdscript_highlighter.h"
#include "editor/gdscript_translation_parser_plugin.h"
// FIXME: Reenable LSP.
#if 0
#ifndef GDSCRIPT_NO_LSP
#include "core/engine.h"
#include "language_server/gdscript_language_server.h"
#endif // !GDSCRIPT_NO_LSP
#endif
Ref<GDScriptEditorTranslationParserPlugin> gdscript_translation_parser_plugin;
@ -76,64 +79,8 @@ public:
return;
}
Vector<uint8_t> file = FileAccess::get_file_as_array(p_path);
if (file.empty()) {
return;
}
String txt;
txt.parse_utf8((const char *)file.ptr(), file.size());
file = GDScriptTokenizerBuffer::parse_code_string(txt);
if (!file.empty()) {
if (script_mode == EditorExportPreset::MODE_SCRIPT_ENCRYPTED) {
String tmp_path = EditorSettings::get_singleton()->get_cache_dir().plus_file("script.gde");
FileAccess *fa = FileAccess::open(tmp_path, FileAccess::WRITE);
Vector<uint8_t> key;
key.resize(32);
for (int i = 0; i < 32; i++) {
int v = 0;
if (i * 2 < script_key.length()) {
CharType ct = script_key[i * 2];
if (ct >= '0' && ct <= '9') {
ct = ct - '0';
} else if (ct >= 'a' && ct <= 'f') {
ct = 10 + ct - 'a';
}
v |= ct << 4;
}
if (i * 2 + 1 < script_key.length()) {
CharType ct = script_key[i * 2 + 1];
if (ct >= '0' && ct <= '9') {
ct = ct - '0';
} else if (ct >= 'a' && ct <= 'f') {
ct = 10 + ct - 'a';
}
v |= ct;
}
key.write[i] = v;
}
FileAccessEncrypted *fae = memnew(FileAccessEncrypted);
Error err = fae->open_and_parse(fa, key, FileAccessEncrypted::MODE_WRITE_AES256);
if (err == OK) {
fae->store_buffer(file.ptr(), file.size());
}
memdelete(fae);
file = FileAccess::get_file_as_array(tmp_path);
add_file(p_path.get_basename() + ".gde", file, true);
// Clean up temporary file.
DirAccess::remove_file_or_error(tmp_path);
} else {
add_file(p_path.get_basename() + ".gdc", file, true);
}
}
// TODO: Readd compiled/encrypted GDScript on export.
return;
}
};
@ -148,12 +95,15 @@ static void _editor_init() {
ScriptEditor::get_singleton()->register_syntax_highlighter(gdscript_syntax_highlighter);
#endif
// FIXME: Reenable LSP.
#if 0
#ifndef GDSCRIPT_NO_LSP
register_lsp_types();
GDScriptLanguageServer *lsp_plugin = memnew(GDScriptLanguageServer);
EditorNode::get_singleton()->add_editor_plugin(lsp_plugin);
Engine::get_singleton()->add_singleton(Engine::Singleton("GDScriptLanguageProtocol", GDScriptLanguageProtocol::get_singleton()));
#endif // !GDSCRIPT_NO_LSP
#endif
}
#endif // TOOLS_ENABLED