mirror of
https://github.com/godotengine/godot.git
synced 2024-11-22 04:06:14 +00:00
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:
parent
818bfbc5b5
commit
5d6e853806
File diff suppressed because it is too large
Load Diff
@ -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;
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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);
|
||||
|
283
modules/gdscript/gdscript_analyzer.cpp
Normal file
283
modules/gdscript/gdscript_analyzer.cpp
Normal 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;
|
||||
}
|
52
modules/gdscript/gdscript_analyzer.h
Normal file
52
modules/gdscript/gdscript_analyzer.h
Normal 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
@ -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
@ -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) {
|
||||
|
@ -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
@ -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
|
||||
|
201
modules/gdscript/gdscript_warning.cpp
Normal file
201
modules/gdscript/gdscript_warning.cpp
Normal 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
|
84
modules/gdscript/gdscript_warning.h
Normal file
84
modules/gdscript/gdscript_warning.h
Normal 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
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
Loading…
Reference in New Issue
Block a user