commit
3811fb919e
|
@ -294,7 +294,8 @@ public:
|
|||
|
||||
/* EDITOR FUNCTIONS */
|
||||
struct Warning {
|
||||
int line;
|
||||
int start_line = -1, end_line = -1;
|
||||
int leftmost_column = -1, rightmost_column = -1;
|
||||
int code;
|
||||
String string_code;
|
||||
String message;
|
||||
|
|
|
@ -1778,6 +1778,7 @@ CodeTextEditor::CodeTextEditor() {
|
|||
cs.push_back("(");
|
||||
cs.push_back("=");
|
||||
cs.push_back("$");
|
||||
cs.push_back("@");
|
||||
text_editor->set_completion(true, cs);
|
||||
idle->connect("timeout", callable_mp(this, &CodeTextEditor::_text_changed_idle_timeout));
|
||||
|
||||
|
|
|
@ -494,7 +494,7 @@ void ScriptTextEditor::_validate_script() {
|
|||
ScriptLanguage::Warning w = E->get();
|
||||
|
||||
Dictionary ignore_meta;
|
||||
ignore_meta["line"] = w.line;
|
||||
ignore_meta["line"] = w.start_line;
|
||||
ignore_meta["code"] = w.string_code.to_lower();
|
||||
warnings_panel->push_cell();
|
||||
warnings_panel->push_meta(ignore_meta);
|
||||
|
@ -506,9 +506,9 @@ void ScriptTextEditor::_validate_script() {
|
|||
warnings_panel->pop(); // Cell.
|
||||
|
||||
warnings_panel->push_cell();
|
||||
warnings_panel->push_meta(w.line - 1);
|
||||
warnings_panel->push_meta(w.start_line - 1);
|
||||
warnings_panel->push_color(warnings_panel->get_theme_color("warning_color", "Editor"));
|
||||
warnings_panel->add_text(TTR("Line") + " " + itos(w.line));
|
||||
warnings_panel->add_text(TTR("Line") + " " + itos(w.start_line));
|
||||
warnings_panel->add_text(" (" + w.string_code + "):");
|
||||
warnings_panel->pop(); // Color.
|
||||
warnings_panel->pop(); // Meta goto.
|
||||
|
|
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,11 @@
|
|||
#include "core/os/file_access.h"
|
||||
#include "core/os/os.h"
|
||||
#include "core/project_settings.h"
|
||||
#include "gdscript_analyzer.h"
|
||||
#include "gdscript_cache.h"
|
||||
#include "gdscript_compiler.h"
|
||||
#include "gdscript_parser.h"
|
||||
#include "gdscript_warning.h"
|
||||
|
||||
///////////////////////////
|
||||
|
||||
|
@ -79,6 +83,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 +116,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 +127,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 +411,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 +424,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 +434,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 +458,36 @@ 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);
|
||||
Variant default_value;
|
||||
if (member.variable->initializer && member.variable->initializer->is_constant) {
|
||||
default_value = member.variable->initializer->reduced_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 +598,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);
|
||||
|
@ -585,7 +641,7 @@ Error GDScript::reload(bool p_keep_state) {
|
|||
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);
|
||||
EngineDebugger::get_script_debugger()->send_error("", get_path(), warning.start_line, warning.get_name(), warning.get_message(), ERR_HANDLER_WARNING, si);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
@ -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) {
|
||||
|
@ -1055,6 +1040,8 @@ GDScript::~GDScript() {
|
|||
memdelete(E->get());
|
||||
}
|
||||
|
||||
GDScriptCache::remove_script(get_path());
|
||||
|
||||
_save_orphaned_subclasses();
|
||||
|
||||
#ifdef DEBUG_ENABLED
|
||||
|
@ -1152,6 +1139,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 +1317,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 +1841,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 +1864,7 @@ void GDScriptLanguage::get_reserved_words(List<String> *p_words) const {
|
|||
// functions
|
||||
"as",
|
||||
"assert",
|
||||
"await",
|
||||
"breakpoint",
|
||||
"class",
|
||||
"class_name",
|
||||
|
@ -1856,15 +1872,13 @@ void GDScriptLanguage::get_reserved_words(List<String> *p_words) const {
|
|||
"is",
|
||||
"func",
|
||||
"preload",
|
||||
"setget",
|
||||
"signal",
|
||||
"tool",
|
||||
"super",
|
||||
"trait",
|
||||
"yield",
|
||||
// var
|
||||
"const",
|
||||
"enum",
|
||||
"export",
|
||||
"onready",
|
||||
"static",
|
||||
"var",
|
||||
// control flow
|
||||
|
@ -1878,12 +1892,6 @@ void GDScriptLanguage::get_reserved_words(List<String> *p_words) const {
|
|||
"return",
|
||||
"match",
|
||||
"while",
|
||||
"remote",
|
||||
"master",
|
||||
"puppet",
|
||||
"remotesync",
|
||||
"mastersync",
|
||||
"puppetsync",
|
||||
nullptr
|
||||
};
|
||||
|
||||
|
@ -1914,10 +1922,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 +1940,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 +1957,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 +1989,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 +2001,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);
|
||||
|
@ -2204,7 +2045,7 @@ GDScriptLanguage::GDScriptLanguage() {
|
|||
GLOBAL_DEF("debug/gdscript/completion/autocomplete_setters_and_getters", false);
|
||||
for (int i = 0; i < (int)GDScriptWarning::WARNING_MAX; i++) {
|
||||
String warning = GDScriptWarning::get_name_from_code((GDScriptWarning::Code)i).to_lower();
|
||||
bool default_enabled = !warning.begins_with("unsafe_") && i != GDScriptWarning::UNUSED_CLASS_VARIABLE;
|
||||
bool default_enabled = !warning.begins_with("unsafe_");
|
||||
GLOBAL_DEF("debug/gdscript/warnings/" + warning, default_enabled);
|
||||
}
|
||||
#endif // DEBUG_ENABLED
|
||||
|
@ -2242,36 +2083,28 @@ RES ResourceFormatLoaderGDScript::load(const String &p_path, const String &p_ori
|
|||
*r_error = ERR_FILE_CANT_OPEN;
|
||||
}
|
||||
|
||||
GDScript *script = memnew(GDScript);
|
||||
Error err;
|
||||
Ref<GDScript> script = GDScriptCache::get_full_script(p_path, err);
|
||||
|
||||
Ref<GDScript> scriptres(script);
|
||||
// TODO: Reintroduce binary and encrypted scripts.
|
||||
|
||||
if (p_path.ends_with(".gde") || p_path.ends_with(".gdc")) {
|
||||
script->set_script_path(p_original_path); // script needs this.
|
||||
script->set_path(p_original_path);
|
||||
Error err = script->load_byte_code(p_path);
|
||||
ERR_FAIL_COND_V_MSG(err != OK, RES(), "Cannot load byte code from file '" + p_path + "'.");
|
||||
|
||||
} else {
|
||||
Error err = script->load_source_code(p_path);
|
||||
ERR_FAIL_COND_V_MSG(err != OK, RES(), "Cannot load source code from file '" + p_path + "'.");
|
||||
|
||||
script->set_script_path(p_original_path); // script needs this.
|
||||
script->set_path(p_original_path);
|
||||
|
||||
script->reload();
|
||||
if (script.is_null()) {
|
||||
// Don't fail loading because of parsing error.
|
||||
script.instance();
|
||||
}
|
||||
|
||||
if (r_error) {
|
||||
*r_error = OK;
|
||||
}
|
||||
|
||||
return scriptres;
|
||||
return script;
|
||||
}
|
||||
|
||||
void ResourceFormatLoaderGDScript::get_recognized_extensions(List<String> *p_extensions) const {
|
||||
p_extensions->push_back("gd");
|
||||
p_extensions->push_back("gdc");
|
||||
p_extensions->push_back("gde");
|
||||
// TODO: Reintroduce binary and encrypted scripts.
|
||||
// p_extensions->push_back("gdc");
|
||||
// p_extensions->push_back("gde");
|
||||
}
|
||||
|
||||
bool ResourceFormatLoaderGDScript::handles_type(const String &p_type) const {
|
||||
|
@ -2280,7 +2113,8 @@ bool ResourceFormatLoaderGDScript::handles_type(const String &p_type) const {
|
|||
|
||||
String ResourceFormatLoaderGDScript::get_resource_type(const String &p_path) const {
|
||||
String el = p_path.get_extension().to_lower();
|
||||
if (el == "gd" || el == "gdc" || el == "gde") {
|
||||
// TODO: Reintroduce binary and encrypted scripts.
|
||||
if (el == "gd" /*|| el == "gdc" || el == "gde"*/) {
|
||||
return "GDScript";
|
||||
}
|
||||
return "";
|
||||
|
@ -2296,7 +2130,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);
|
||||
|
@ -301,52 +303,6 @@ public:
|
|||
~GDScriptInstance();
|
||||
};
|
||||
|
||||
#ifdef DEBUG_ENABLED
|
||||
struct GDScriptWarning {
|
||||
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;
|
||||
Vector<String> symbols;
|
||||
int line = -1;
|
||||
|
||||
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);
|
||||
|
||||
GDScriptWarning() {}
|
||||
};
|
||||
#endif // DEBUG_ENABLED
|
||||
|
||||
class GDScriptLanguage : public ScriptLanguage {
|
||||
friend class GDScriptFunctionState;
|
||||
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,118 @@
|
|||
/*************************************************************************/
|
||||
/* 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 "core/object.h"
|
||||
#include "core/reference.h"
|
||||
#include "core/set.h"
|
||||
#include "gdscript_cache.h"
|
||||
#include "gdscript_parser.h"
|
||||
|
||||
class GDScriptAnalyzer {
|
||||
GDScriptParser *parser = nullptr;
|
||||
HashMap<String, Ref<GDScriptParserRef>> depended_parsers;
|
||||
|
||||
Error resolve_inheritance(GDScriptParser::ClassNode *p_class, bool p_recursive = true);
|
||||
GDScriptParser::DataType resolve_datatype(GDScriptParser::TypeNode *p_type);
|
||||
|
||||
void decide_suite_type(GDScriptParser::Node *p_suite, GDScriptParser::Node *p_statement);
|
||||
|
||||
// This traverses the tree to resolve all TypeNodes.
|
||||
Error resolve_program();
|
||||
|
||||
void resolve_annotation(GDScriptParser::AnnotationNode *p_annotation);
|
||||
void resolve_class_interface(GDScriptParser::ClassNode *p_class);
|
||||
void resolve_class_body(GDScriptParser::ClassNode *p_class);
|
||||
void resolve_function_signature(GDScriptParser::FunctionNode *p_function);
|
||||
void resolve_function_body(GDScriptParser::FunctionNode *p_function);
|
||||
void resolve_node(GDScriptParser::Node *p_node);
|
||||
void resolve_suite(GDScriptParser::SuiteNode *p_suite);
|
||||
void resolve_if(GDScriptParser::IfNode *p_if);
|
||||
void resolve_for(GDScriptParser::ForNode *p_for);
|
||||
void resolve_while(GDScriptParser::WhileNode *p_while);
|
||||
void resolve_variable(GDScriptParser::VariableNode *p_variable);
|
||||
void resolve_constant(GDScriptParser::ConstantNode *p_constant);
|
||||
void resolve_assert(GDScriptParser::AssertNode *p_assert);
|
||||
void resolve_match(GDScriptParser::MatchNode *p_match);
|
||||
void resolve_match_branch(GDScriptParser::MatchBranchNode *p_match_branch, GDScriptParser::ExpressionNode *p_match_test);
|
||||
void resolve_match_pattern(GDScriptParser::PatternNode *p_match_pattern, GDScriptParser::ExpressionNode *p_match_test);
|
||||
void resolve_pararameter(GDScriptParser::ParameterNode *p_parameter);
|
||||
void resolve_return(GDScriptParser::ReturnNode *p_return);
|
||||
|
||||
// Reduction functions.
|
||||
void reduce_expression(GDScriptParser::ExpressionNode *p_expression);
|
||||
void reduce_array(GDScriptParser::ArrayNode *p_array);
|
||||
void reduce_assignment(GDScriptParser::AssignmentNode *p_assignment);
|
||||
void reduce_await(GDScriptParser::AwaitNode *p_await);
|
||||
void reduce_binary_op(GDScriptParser::BinaryOpNode *p_binary_op);
|
||||
void reduce_call(GDScriptParser::CallNode *p_call, bool is_await = false);
|
||||
void reduce_cast(GDScriptParser::CastNode *p_cast);
|
||||
void reduce_dictionary(GDScriptParser::DictionaryNode *p_dictionary);
|
||||
void reduce_get_node(GDScriptParser::GetNodeNode *p_get_node);
|
||||
void reduce_identifier(GDScriptParser::IdentifierNode *p_identifier, bool can_be_builtin = false);
|
||||
void reduce_identifier_from_base(GDScriptParser::IdentifierNode *p_identifier, GDScriptParser::DataType *p_base = nullptr);
|
||||
void reduce_literal(GDScriptParser::LiteralNode *p_literal);
|
||||
void reduce_preload(GDScriptParser::PreloadNode *p_preload);
|
||||
void reduce_self(GDScriptParser::SelfNode *p_self);
|
||||
void reduce_subscript(GDScriptParser::SubscriptNode *p_subscript);
|
||||
void reduce_ternary_op(GDScriptParser::TernaryOpNode *p_ternary_op);
|
||||
void reduce_unary_op(GDScriptParser::UnaryOpNode *p_unary_op);
|
||||
|
||||
// Helpers.
|
||||
GDScriptParser::DataType type_from_variant(const Variant &p_value);
|
||||
GDScriptParser::DataType type_from_metatype(const GDScriptParser::DataType &p_meta_type) const;
|
||||
GDScriptParser::DataType type_from_property(const PropertyInfo &p_property) const;
|
||||
GDScriptParser::DataType make_global_class_meta_type(const StringName &p_class_name);
|
||||
bool get_function_signature(GDScriptParser::Node *p_source, GDScriptParser::DataType base_type, const StringName &p_function, GDScriptParser::DataType &r_return_type, List<GDScriptParser::DataType> &r_par_types, int &r_default_arg_count, bool &r_static, bool &r_vararg);
|
||||
bool function_signature_from_info(const MethodInfo &p_info, GDScriptParser::DataType &r_return_type, List<GDScriptParser::DataType> &r_par_types, int &r_default_arg_count, bool &r_static, bool &r_vararg);
|
||||
bool validate_call_arg(const List<GDScriptParser::DataType> &p_par_types, int p_default_args_count, bool p_is_vararg, const GDScriptParser::CallNode *p_call);
|
||||
bool validate_call_arg(const MethodInfo &p_method, const GDScriptParser::CallNode *p_call);
|
||||
GDScriptParser::DataType get_operation_type(Variant::Operator p_operation, const GDScriptParser::DataType &p_a, const GDScriptParser::DataType &p_b, bool &r_valid);
|
||||
bool is_type_compatible(const GDScriptParser::DataType &p_target, const GDScriptParser::DataType &p_source, bool p_allow_implicit_conversion = false) const;
|
||||
void push_error(const String &p_message, const GDScriptParser::Node *p_origin);
|
||||
void mark_node_unsafe(const GDScriptParser::Node *p_node);
|
||||
bool class_exists(const StringName &p_class);
|
||||
Ref<GDScriptParserRef> get_parser_for(const String &p_path);
|
||||
#ifdef DEBUG_ENABLED
|
||||
bool is_shadowing(GDScriptParser::IdentifierNode *p_local, const String &p_context);
|
||||
#endif
|
||||
|
||||
public:
|
||||
Error resolve_inheritance();
|
||||
Error resolve_interface();
|
||||
Error resolve_body();
|
||||
Error analyze();
|
||||
|
||||
GDScriptAnalyzer(GDScriptParser *p_parser);
|
||||
};
|
||||
|
||||
#endif // GDSCRIPT_ANALYZER_H
|
|
@ -0,0 +1,244 @@
|
|||
/*************************************************************************/
|
||||
/* gdscript_cache.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_cache.h"
|
||||
|
||||
#include "core/os/file_access.h"
|
||||
#include "core/vector.h"
|
||||
#include "gdscript.h"
|
||||
#include "gdscript_analyzer.h"
|
||||
#include "gdscript_parser.h"
|
||||
|
||||
bool GDScriptParserRef::is_valid() const {
|
||||
return parser != nullptr;
|
||||
}
|
||||
|
||||
GDScriptParserRef::Status GDScriptParserRef::get_status() const {
|
||||
return status;
|
||||
}
|
||||
|
||||
GDScriptParser *GDScriptParserRef::get_parser() const {
|
||||
return parser;
|
||||
}
|
||||
|
||||
Error GDScriptParserRef::raise_status(Status p_new_status) {
|
||||
ERR_FAIL_COND_V(parser == nullptr, ERR_INVALID_DATA);
|
||||
|
||||
Error result = OK;
|
||||
|
||||
while (p_new_status > status) {
|
||||
switch (status) {
|
||||
case EMPTY:
|
||||
result = parser->parse(GDScriptCache::get_source_code(path), path, false);
|
||||
status = PARSED;
|
||||
break;
|
||||
case PARSED: {
|
||||
analyzer = memnew(GDScriptAnalyzer(parser));
|
||||
Error inheritance_result = analyzer->resolve_inheritance();
|
||||
status = INHERITANCE_SOLVED;
|
||||
if (result == OK) {
|
||||
result = inheritance_result;
|
||||
}
|
||||
} break;
|
||||
case INHERITANCE_SOLVED: {
|
||||
Error interface_result = analyzer->resolve_interface();
|
||||
status = INTERFACE_SOLVED;
|
||||
if (result == OK) {
|
||||
result = interface_result;
|
||||
}
|
||||
} break;
|
||||
case INTERFACE_SOLVED: {
|
||||
Error body_result = analyzer->resolve_body();
|
||||
status = FULLY_SOLVED;
|
||||
if (result == OK) {
|
||||
result = body_result;
|
||||
}
|
||||
} break;
|
||||
case FULLY_SOLVED: {
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
GDScriptParserRef::~GDScriptParserRef() {
|
||||
if (parser != nullptr) {
|
||||
memdelete(parser);
|
||||
}
|
||||
if (analyzer != nullptr) {
|
||||
memdelete(analyzer);
|
||||
}
|
||||
MutexLock(GDScriptCache::singleton->lock);
|
||||
GDScriptCache::singleton->parser_map.erase(path);
|
||||
}
|
||||
|
||||
GDScriptCache *GDScriptCache::singleton = nullptr;
|
||||
|
||||
void GDScriptCache::remove_script(const String &p_path) {
|
||||
MutexLock(singleton->lock);
|
||||
singleton->shallow_gdscript_cache.erase(p_path);
|
||||
singleton->full_gdscript_cache.erase(p_path);
|
||||
}
|
||||
|
||||
Ref<GDScriptParserRef> GDScriptCache::get_parser(const String &p_path, GDScriptParserRef::Status p_status, Error &r_error, const String &p_owner) {
|
||||
MutexLock(singleton->lock);
|
||||
Ref<GDScriptParserRef> ref;
|
||||
if (p_owner != String()) {
|
||||
singleton->dependencies[p_owner].insert(p_path);
|
||||
}
|
||||
if (singleton->parser_map.has(p_path)) {
|
||||
ref = singleton->parser_map[p_path];
|
||||
} else {
|
||||
GDScriptParser *parser = memnew(GDScriptParser);
|
||||
ref.instance();
|
||||
ref->parser = parser;
|
||||
ref->path = p_path;
|
||||
singleton->parser_map[p_path] = ref;
|
||||
ref->unreference();
|
||||
}
|
||||
|
||||
r_error = ref->raise_status(p_status);
|
||||
|
||||
return ref;
|
||||
}
|
||||
|
||||
String GDScriptCache::get_source_code(const String &p_path) {
|
||||
Vector<uint8_t> source_file;
|
||||
Error err;
|
||||
FileAccessRef f = FileAccess::open(p_path, FileAccess::READ, &err);
|
||||
if (err) {
|
||||
ERR_FAIL_COND_V(err, "");
|
||||
}
|
||||
|
||||
int len = f->get_len();
|
||||
source_file.resize(len + 1);
|
||||
int r = f->get_buffer(source_file.ptrw(), len);
|
||||
f->close();
|
||||
ERR_FAIL_COND_V(r != len, "");
|
||||
source_file.write[len] = 0;
|
||||
|
||||
String source;
|
||||
if (source.parse_utf8((const char *)source_file.ptr())) {
|
||||
ERR_FAIL_V_MSG("", "Script '" + p_path + "' contains invalid unicode (UTF-8), so it was not loaded. Please ensure that scripts are saved in valid UTF-8 unicode.");
|
||||
}
|
||||
return source;
|
||||
}
|
||||
|
||||
Ref<GDScript> GDScriptCache::get_shallow_script(const String &p_path, const String &p_owner) {
|
||||
MutexLock(singleton->lock);
|
||||
if (p_owner != String()) {
|
||||
singleton->dependencies[p_owner].insert(p_path);
|
||||
}
|
||||
if (singleton->full_gdscript_cache.has(p_path)) {
|
||||
return singleton->full_gdscript_cache[p_path];
|
||||
}
|
||||
if (singleton->shallow_gdscript_cache.has(p_path)) {
|
||||
return singleton->shallow_gdscript_cache[p_path];
|
||||
}
|
||||
|
||||
Ref<GDScript> script;
|
||||
script.instance();
|
||||
script->set_path(p_path, true);
|
||||
script->set_script_path(p_path);
|
||||
script->load_source_code(p_path);
|
||||
|
||||
singleton->shallow_gdscript_cache[p_path] = script;
|
||||
// The one in cache is not a hard reference: if the script dies somewhere else it's fine.
|
||||
// Scripts remove themselves from cache when they die.
|
||||
script->unreference();
|
||||
return script;
|
||||
}
|
||||
|
||||
Ref<GDScript> GDScriptCache::get_full_script(const String &p_path, Error &r_error, const String &p_owner) {
|
||||
MutexLock(singleton->lock);
|
||||
|
||||
if (p_owner != String()) {
|
||||
singleton->dependencies[p_owner].insert(p_path);
|
||||
}
|
||||
|
||||
r_error = OK;
|
||||
if (singleton->full_gdscript_cache.has(p_path)) {
|
||||
return singleton->full_gdscript_cache[p_path];
|
||||
}
|
||||
Ref<GDScript> script = get_shallow_script(p_path);
|
||||
|
||||
r_error = script->load_source_code(p_path);
|
||||
|
||||
if (r_error) {
|
||||
return script;
|
||||
}
|
||||
|
||||
r_error = script->reload();
|
||||
if (r_error) {
|
||||
return script;
|
||||
}
|
||||
|
||||
singleton->full_gdscript_cache[p_path] = script;
|
||||
singleton->shallow_gdscript_cache.erase(p_path);
|
||||
|
||||
return script;
|
||||
}
|
||||
|
||||
Error GDScriptCache::finish_compiling(const String &p_owner) {
|
||||
// Mark this as compiled.
|
||||
Ref<GDScript> script = get_shallow_script(p_owner);
|
||||
singleton->full_gdscript_cache[p_owner] = script;
|
||||
singleton->shallow_gdscript_cache.erase(p_owner);
|
||||
|
||||
Set<String> depends = singleton->dependencies[p_owner];
|
||||
|
||||
Error err = OK;
|
||||
for (const Set<String>::Element *E = depends.front(); E != nullptr; E = E->next()) {
|
||||
Error this_err = OK;
|
||||
// No need to save the script. We assume it's already referenced in the owner.
|
||||
get_full_script(E->get(), this_err);
|
||||
|
||||
if (this_err != OK) {
|
||||
err = this_err;
|
||||
}
|
||||
}
|
||||
|
||||
singleton->dependencies.erase(p_owner);
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
GDScriptCache::GDScriptCache() {
|
||||
singleton = this;
|
||||
}
|
||||
|
||||
GDScriptCache::~GDScriptCache() {
|
||||
parser_map.clear();
|
||||
shallow_gdscript_cache.clear();
|
||||
full_gdscript_cache.clear();
|
||||
singleton = nullptr;
|
||||
}
|
|
@ -0,0 +1,97 @@
|
|||
/*************************************************************************/
|
||||
/* gdscript_cache.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_CACHE_H
|
||||
#define GDSCRIPT_CACHE_H
|
||||
|
||||
#include "core/hash_map.h"
|
||||
#include "core/os/mutex.h"
|
||||
#include "core/reference.h"
|
||||
#include "core/set.h"
|
||||
#include "gdscript.h"
|
||||
|
||||
class GDScriptAnalyzer;
|
||||
class GDScriptParser;
|
||||
|
||||
class GDScriptParserRef : public Reference {
|
||||
public:
|
||||
enum Status {
|
||||
EMPTY,
|
||||
PARSED,
|
||||
INHERITANCE_SOLVED,
|
||||
INTERFACE_SOLVED,
|
||||
FULLY_SOLVED,
|
||||
};
|
||||
|
||||
private:
|
||||
GDScriptParser *parser = nullptr;
|
||||
GDScriptAnalyzer *analyzer = nullptr;
|
||||
Status status = EMPTY;
|
||||
String path;
|
||||
|
||||
friend class GDScriptCache;
|
||||
|
||||
public:
|
||||
bool is_valid() const;
|
||||
Status get_status() const;
|
||||
GDScriptParser *get_parser() const;
|
||||
Error raise_status(Status p_new_status);
|
||||
|
||||
GDScriptParserRef() {}
|
||||
~GDScriptParserRef();
|
||||
};
|
||||
|
||||
class GDScriptCache {
|
||||
// String key is full path.
|
||||
HashMap<String, Ref<GDScriptParserRef>> parser_map;
|
||||
HashMap<String, Ref<GDScript>> shallow_gdscript_cache;
|
||||
HashMap<String, Ref<GDScript>> full_gdscript_cache;
|
||||
HashMap<String, Set<String>> dependencies;
|
||||
|
||||
friend class GDScript;
|
||||
friend class GDScriptParserRef;
|
||||
|
||||
static GDScriptCache *singleton;
|
||||
|
||||
Mutex lock;
|
||||
static void remove_script(const String &p_path);
|
||||
|
||||
public:
|
||||
static Ref<GDScriptParserRef> get_parser(const String &p_path, GDScriptParserRef::Status status, Error &r_error, const String &p_owner = String());
|
||||
static String get_source_code(const String &p_path);
|
||||
static Ref<GDScript> get_shallow_script(const String &p_path, const String &p_owner = String());
|
||||
static Ref<GDScript> get_full_script(const String &p_path, Error &r_error, const String &p_owner = String());
|
||||
static Error finish_compiling(const String &p_owner);
|
||||
|
||||
GDScriptCache();
|
||||
~GDScriptCache();
|
||||
};
|
||||
|
||||
#endif // GDSCRIPT_CACHE_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 {
|
||||
|
@ -44,6 +45,7 @@ class GDScriptCompiler {
|
|||
GDScript *script;
|
||||
const GDScriptParser::ClassNode *class_node;
|
||||
const GDScriptParser::FunctionNode *function_node;
|
||||
StringName function_name;
|
||||
bool debug_stack;
|
||||
|
||||
List<Map<StringName, int>> stack_id_stack;
|
||||
|
@ -52,6 +54,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 +114,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,15 +143,19 @@ 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);
|
||||
bool _generate_typed_assign(CodeGen &codegen, int p_src_address, int p_dst_address, const GDScriptDataType &p_datatype, const GDScriptParser::DataType &p_value_type);
|
||||
|
||||
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_setter_getter(GDScript *p_script, const GDScriptParser::ClassNode *p_class, const GDScriptParser::VariableNode *p_variable, bool p_is_setter);
|
||||
Error _parse_class_level(GDScript *p_script, const GDScriptParser::ClassNode *p_class, bool p_keep_state);
|
||||
Error _parse_class_blocks(GDScript *p_script, const GDScriptParser::ClassNode *p_class, bool p_keep_state);
|
||||
void _make_scripts(GDScript *p_script, const GDScriptParser::ClassNode *p_class, bool p_keep_state);
|
||||
|
@ -156,6 +163,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,
|
||||
|
|
|
@ -146,12 +146,14 @@ void GDScriptFunctions::call(Function p_func, const Variant **p_args, int p_arg_
|
|||
if (p_arg_count < m_count) { \
|
||||
r_error.error = Callable::CallError::CALL_ERROR_TOO_FEW_ARGUMENTS; \
|
||||
r_error.argument = m_count; \
|
||||
r_error.expected = m_count; \
|
||||
r_ret = Variant(); \
|
||||
return; \
|
||||
} \
|
||||
if (p_arg_count > m_count) { \
|
||||
r_error.error = Callable::CallError::CALL_ERROR_TOO_MANY_ARGUMENTS; \
|
||||
r_error.argument = m_count; \
|
||||
r_error.expected = m_count; \
|
||||
r_ret = Variant(); \
|
||||
return; \
|
||||
}
|
||||
|
@ -897,6 +899,7 @@ void GDScriptFunctions::call(Function p_func, const Variant **p_args, int p_arg_
|
|||
case 0: {
|
||||
r_error.error = Callable::CallError::CALL_ERROR_TOO_FEW_ARGUMENTS;
|
||||
r_error.argument = 1;
|
||||
r_error.expected = 1;
|
||||
r_ret = Variant();
|
||||
|
||||
} break;
|
||||
|
@ -1001,6 +1004,7 @@ void GDScriptFunctions::call(Function p_func, const Variant **p_args, int p_arg_
|
|||
default: {
|
||||
r_error.error = Callable::CallError::CALL_ERROR_TOO_MANY_ARGUMENTS;
|
||||
r_error.argument = 3;
|
||||
r_error.expected = 3;
|
||||
r_ret = Variant();
|
||||
|
||||
} break;
|
||||
|
|
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,221 @@
|
|||
#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
|
||||
enum CursorPlace {
|
||||
CURSOR_NONE,
|
||||
CURSOR_BEGINNING,
|
||||
CURSOR_MIDDLE,
|
||||
CURSOR_END,
|
||||
};
|
||||
|
||||
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,
|
||||
TRAIT,
|
||||
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.
|
||||
int cursor_position = -1;
|
||||
CursorPlace cursor_place = CURSOR_NONE;
|
||||
String source;
|
||||
|
||||
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 = -1, column = -1;
|
||||
int cursor_line = -1, cursor_column = -1;
|
||||
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);
|
||||
Token make_literal(const Variant &p_literal);
|
||||
Token make_identifier(const StringName &p_identifier);
|
||||
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);
|
||||
bool is_past_cursor() const;
|
||||
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
|
||||
|
|
|
@ -0,0 +1,210 @@
|
|||
/*************************************************************************/
|
||||
/* 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"
|
||||
|
||||
#include "core/variant.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 UNUSED_LOCAL_CONSTANT: {
|
||||
CHECK_SYMBOLS(1);
|
||||
return "The local constant '" + 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(4);
|
||||
return vformat(R"(The local %s "%s" is shadowing an already-declared %s at line %s.)", symbols[0], symbols[1], symbols[2], symbols[3]);
|
||||
} break;
|
||||
case SHADOWED_VARIABLE_BASE_CLASS: {
|
||||
CHECK_SYMBOLS(4);
|
||||
return vformat(R"(The local %s "%s" is shadowing an already-declared %s at the base class "%s".)", symbols[0], symbols[1], symbols[2], symbols[3]);
|
||||
} break;
|
||||
case UNUSED_PRIVATE_CLASS_VARIABLE: {
|
||||
CHECK_SYMBOLS(1);
|
||||
return "The class variable '" + symbols[0] + "' is declared but never used in the script.";
|
||||
} break;
|
||||
case UNUSED_PARAMETER: {
|
||||
CHECK_SYMBOLS(2);
|
||||
return "The parameter '" + 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 UNREACHABLE_PATTERN: {
|
||||
return "Unreachable pattern (pattern after wildcard or bind).";
|
||||
} 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 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 ASSERT_ALWAYS_TRUE: {
|
||||
return "Assert statement is redundant because the expression is always true.";
|
||||
}
|
||||
case ASSERT_ALWAYS_FALSE: {
|
||||
return "Assert statement will raise an error because the expression is always false.";
|
||||
}
|
||||
case REDUNDANT_AWAIT: {
|
||||
return R"("await" keyword not needed in this case, because the expression isn't a coroutine nor a signal.)";
|
||||
}
|
||||
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",
|
||||
"UNUSED_LOCAL_CONSTANT",
|
||||
"SHADOWED_VARIABLE",
|
||||
"SHADOWED_VARIABLE_BASE_CLASS",
|
||||
"UNUSED_PRIVATE_CLASS_VARIABLE",
|
||||
"UNUSED_PARAMETER",
|
||||
"UNREACHABLE_CODE",
|
||||
"UNREACHABLE_PATTERN",
|
||||
"STANDALONE_EXPRESSION",
|
||||
"VOID_ASSIGNMENT",
|
||||
"NARROWING_CONVERSION",
|
||||
"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",
|
||||
"ASSERT_ALWAYS_TRUE",
|
||||
"ASSERT_ALWAYS_FALSE",
|
||||
"REDUNDANT_AWAIT",
|
||||
};
|
||||
|
||||
static_assert((sizeof(names) / sizeof(*names)) == WARNING_MAX, "Amount of warning types don't match the amount of warning names.");
|
||||
|
||||
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
|
|
@ -0,0 +1,87 @@
|
|||
/*************************************************************************/
|
||||
/* 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.
|
||||
UNUSED_LOCAL_CONSTANT, // Local constant is declared but never used.
|
||||
SHADOWED_VARIABLE, // Variable name shadowed by other variable in same class.
|
||||
SHADOWED_VARIABLE_BASE_CLASS, // Variable name shadowed by other variable in some base class.
|
||||
UNUSED_PRIVATE_CLASS_VARIABLE, // Class variable is declared private ("_" prefix) but never used in the file.
|
||||
UNUSED_PARAMETER, // Function parameter is never used.
|
||||
UNREACHABLE_CODE, // Code after a return statement.
|
||||
UNREACHABLE_PATTERN, // Pattern in a match statement after a catch all pattern (wildcard or bind).
|
||||
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.
|
||||
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.
|
||||
ASSERT_ALWAYS_TRUE, // Expression for assert argument is always true.
|
||||
ASSERT_ALWAYS_FALSE, // Expression for assert argument is always false.
|
||||
REDUNDANT_AWAIT, // await is used but expression is synchronous (not a signal nor a coroutine).
|
||||
WARNING_MAX,
|
||||
};
|
||||
|
||||
Code code = WARNING_MAX;
|
||||
int start_line = -1, end_line = -1;
|
||||
int leftmost_column = -1, rightmost_column = -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
|
|
@ -31,6 +31,7 @@
|
|||
#include "gdscript_extend_parser.h"
|
||||
|
||||
#include "../gdscript.h"
|
||||
#include "../gdscript_analyzer.h"
|
||||
#include "core/io/json.h"
|
||||
#include "gdscript_language_protocol.h"
|
||||
#include "gdscript_workspace.h"
|
||||
|
@ -38,15 +39,17 @@
|
|||
void ExtendGDScriptParser::update_diagnostics() {
|
||||
diagnostics.clear();
|
||||
|
||||
if (has_error()) {
|
||||
const List<ParserError> &errors = get_errors();
|
||||
for (const List<ParserError>::Element *E = errors.front(); E != nullptr; E = E->next()) {
|
||||
const ParserError &error = E->get();
|
||||
lsp::Diagnostic diagnostic;
|
||||
diagnostic.severity = lsp::DiagnosticSeverity::Error;
|
||||
diagnostic.message = get_error();
|
||||
diagnostic.message = error.message;
|
||||
diagnostic.source = "gdscript";
|
||||
diagnostic.code = -1;
|
||||
lsp::Range range;
|
||||
lsp::Position pos;
|
||||
int line = LINE_NUMBER_TO_INDEX(get_error_line());
|
||||
int line = LINE_NUMBER_TO_INDEX(error.line);
|
||||
const String &line_text = get_lines()[line];
|
||||
pos.line = line;
|
||||
pos.character = line_text.length() - line_text.strip_edges(true, false).length();
|
||||
|
@ -67,7 +70,7 @@ void ExtendGDScriptParser::update_diagnostics() {
|
|||
diagnostic.code = warning.code;
|
||||
lsp::Range range;
|
||||
lsp::Position pos;
|
||||
int line = LINE_NUMBER_TO_INDEX(warning.line);
|
||||
int line = LINE_NUMBER_TO_INDEX(warning.start_line);
|
||||
const String &line_text = get_lines()[line];
|
||||
pos.line = line;
|
||||
pos.character = line_text.length() - line_text.strip_edges(true, false).length();
|
||||
|
@ -82,7 +85,7 @@ void ExtendGDScriptParser::update_diagnostics() {
|
|||
void ExtendGDScriptParser::update_symbols() {
|
||||
members.clear();
|
||||
|
||||
const GDScriptParser::Node *head = get_parse_tree();
|
||||
const GDScriptParser::Node *head = get_tree();
|
||||
if (const GDScriptParser::ClassNode *gdclass = dynamic_cast<const GDScriptParser::ClassNode *>(head)) {
|
||||
parse_class_symbol(gdclass, class_symbol);
|
||||
|
||||
|
@ -106,15 +109,15 @@ void ExtendGDScriptParser::update_symbols() {
|
|||
void ExtendGDScriptParser::update_document_links(const String &p_code) {
|
||||
document_links.clear();
|
||||
|
||||
GDScriptTokenizerText tokenizer;
|
||||
GDScriptTokenizer tokenizer;
|
||||
FileAccessRef fs = FileAccess::create(FileAccess::ACCESS_RESOURCES);
|
||||
tokenizer.set_code(p_code);
|
||||
tokenizer.set_source_code(p_code);
|
||||
while (true) {
|
||||
GDScriptTokenizerText::Token token = tokenizer.get_token();
|
||||
if (token == GDScriptTokenizer::TK_EOF || token == GDScriptTokenizer::TK_ERROR) {
|
||||
GDScriptTokenizer::Token token = tokenizer.scan();
|
||||
if (token.type == GDScriptTokenizer::Token::TK_EOF) {
|
||||
break;
|
||||
} else if (token == GDScriptTokenizer::TK_CONSTANT) {
|
||||
const Variant &const_val = tokenizer.get_token_constant();
|
||||
} else if (token.type == GDScriptTokenizer::Token::LITERAL) {
|
||||
const Variant &const_val = token.literal;
|
||||
if (const_val.get_type() == Variant::STRING) {
|
||||
String path = const_val;
|
||||
bool exists = fs->file_exists(path);
|
||||
|
@ -126,15 +129,14 @@ void ExtendGDScriptParser::update_document_links(const String &p_code) {
|
|||
String value = const_val;
|
||||
lsp::DocumentLink link;
|
||||
link.target = GDScriptLanguageProtocol::get_singleton()->get_workspace()->get_file_uri(path);
|
||||
link.range.start.line = LINE_NUMBER_TO_INDEX(tokenizer.get_token_line());
|
||||
link.range.end.line = link.range.start.line;
|
||||
link.range.end.character = LINE_NUMBER_TO_INDEX(tokenizer.get_token_column());
|
||||
link.range.start.character = link.range.end.character - value.length();
|
||||
link.range.start.line = LINE_NUMBER_TO_INDEX(token.start_line);
|
||||
link.range.end.line = LINE_NUMBER_TO_INDEX(token.end_line);
|
||||
link.range.start.character = LINE_NUMBER_TO_INDEX(token.start_column);
|
||||
link.range.end.character = LINE_NUMBER_TO_INDEX(token.end_column);
|
||||
document_links.push_back(link);
|
||||
}
|
||||
}
|
||||
}
|
||||
tokenizer.advance();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -144,219 +146,238 @@ void ExtendGDScriptParser::parse_class_symbol(const GDScriptParser::ClassNode *p
|
|||
r_symbol.uri = uri;
|
||||
r_symbol.script_path = path;
|
||||
r_symbol.children.clear();
|
||||
r_symbol.name = p_class->name;
|
||||
r_symbol.name = p_class->identifier != nullptr ? String(p_class->identifier->name) : String();
|
||||
if (r_symbol.name.empty()) {
|
||||
r_symbol.name = path.get_file();
|
||||
}
|
||||
r_symbol.kind = lsp::SymbolKind::Class;
|
||||
r_symbol.deprecated = false;
|
||||
r_symbol.range.start.line = LINE_NUMBER_TO_INDEX(p_class->line);
|
||||
r_symbol.range.start.character = p_class->column;
|
||||
r_symbol.range.start.line = LINE_NUMBER_TO_INDEX(p_class->start_line);
|
||||
r_symbol.range.start.character = LINE_NUMBER_TO_INDEX(p_class->start_column);
|
||||
r_symbol.range.end.line = LINE_NUMBER_TO_INDEX(p_class->end_line);
|
||||
r_symbol.selectionRange.start.line = r_symbol.range.start.line;
|
||||
r_symbol.detail = "class " + r_symbol.name;
|
||||
bool is_root_class = &r_symbol == &class_symbol;
|
||||
r_symbol.documentation = parse_documentation(is_root_class ? 0 : LINE_NUMBER_TO_INDEX(p_class->line), is_root_class);
|
||||
r_symbol.documentation = parse_documentation(is_root_class ? 0 : LINE_NUMBER_TO_INDEX(p_class->start_line), is_root_class);
|
||||
|
||||
for (int i = 0; i < p_class->variables.size(); ++i) {
|
||||
const GDScriptParser::ClassNode::Member &m = p_class->variables[i];
|
||||
for (int i = 0; i < p_class->members.size(); i++) {
|
||||
const ClassNode::Member &m = p_class->members[i];
|
||||
|
||||
lsp::DocumentSymbol symbol;
|
||||
symbol.name = m.identifier;
|
||||
symbol.kind = lsp::SymbolKind::Variable;
|
||||
symbol.deprecated = false;
|
||||
const int line = LINE_NUMBER_TO_INDEX(m.line);
|
||||
symbol.range.start.line = line;
|
||||
symbol.range.start.character = lines[line].length() - lines[line].strip_edges(true, false).length();
|
||||
symbol.range.end.line = line;
|
||||
symbol.range.end.character = lines[line].length();
|
||||
symbol.selectionRange.start.line = symbol.range.start.line;
|
||||
if (m._export.type != Variant::NIL) {
|
||||
symbol.detail += "export ";
|
||||
}
|
||||
symbol.detail += "var " + m.identifier;
|
||||
if (m.data_type.kind != GDScriptParser::DataType::UNRESOLVED) {
|
||||
symbol.detail += ": " + m.data_type.to_string();
|
||||
}
|
||||
if (m.default_value.get_type() != Variant::NIL) {
|
||||
symbol.detail += " = " + JSON::print(m.default_value);
|
||||
}
|
||||
|
||||
symbol.documentation = parse_documentation(line);
|
||||
symbol.uri = uri;
|
||||
symbol.script_path = path;
|
||||
|
||||
r_symbol.children.push_back(symbol);
|
||||
}
|
||||
|
||||
for (int i = 0; i < p_class->_signals.size(); ++i) {
|
||||
const GDScriptParser::ClassNode::Signal &signal = p_class->_signals[i];
|
||||
|
||||
lsp::DocumentSymbol symbol;
|
||||
symbol.name = signal.name;
|
||||
symbol.kind = lsp::SymbolKind::Event;
|
||||
symbol.deprecated = false;
|
||||
const int line = LINE_NUMBER_TO_INDEX(signal.line);
|
||||
symbol.range.start.line = line;
|
||||
symbol.range.start.character = lines[line].length() - lines[line].strip_edges(true, false).length();
|
||||
symbol.range.end.line = symbol.range.start.line;
|
||||
symbol.range.end.character = lines[line].length();
|
||||
symbol.selectionRange.start.line = symbol.range.start.line;
|
||||
symbol.documentation = parse_documentation(line);
|
||||
symbol.uri = uri;
|
||||
symbol.script_path = path;
|
||||
symbol.detail = "signal " + signal.name + "(";
|
||||
for (int j = 0; j < signal.arguments.size(); j++) {
|
||||
if (j > 0) {
|
||||
symbol.detail += ", ";
|
||||
}
|
||||
symbol.detail += signal.arguments[j];
|
||||
}
|
||||
symbol.detail += ")";
|
||||
|
||||
r_symbol.children.push_back(symbol);
|
||||
}
|
||||
|
||||
for (Map<StringName, GDScriptParser::ClassNode::Constant>::Element *E = p_class->constant_expressions.front(); E; E = E->next()) {
|
||||
lsp::DocumentSymbol symbol;
|
||||
const GDScriptParser::ClassNode::Constant &c = E->value();
|
||||
const GDScriptParser::ConstantNode *node = dynamic_cast<const GDScriptParser::ConstantNode *>(c.expression);
|
||||
ERR_FAIL_COND(!node);
|
||||
symbol.name = E->key();
|
||||
symbol.kind = lsp::SymbolKind::Constant;
|
||||
symbol.deprecated = false;
|
||||
const int line = LINE_NUMBER_TO_INDEX(E->get().expression->line);
|
||||
symbol.range.start.line = line;
|
||||
symbol.range.start.character = E->get().expression->column;
|
||||
symbol.range.end.line = symbol.range.start.line;
|
||||
symbol.range.end.character = lines[line].length();
|
||||
symbol.selectionRange.start.line = symbol.range.start.line;
|
||||
symbol.documentation = parse_documentation(line);
|
||||
symbol.uri = uri;
|
||||
symbol.script_path = path;
|
||||
|
||||
symbol.detail = "const " + symbol.name;
|
||||
if (c.type.kind != GDScriptParser::DataType::UNRESOLVED) {
|
||||
symbol.detail += ": " + c.type.to_string();
|
||||
}
|
||||
|
||||
String value_text;
|
||||
if (node->value.get_type() == Variant::OBJECT) {
|
||||
RES res = node->value;
|
||||
if (res.is_valid() && !res->get_path().empty()) {
|
||||
value_text = "preload(\"" + res->get_path() + "\")";
|
||||
if (symbol.documentation.empty()) {
|
||||
if (Map<String, ExtendGDScriptParser *>::Element *S = GDScriptLanguageProtocol::get_singleton()->get_workspace()->scripts.find(res->get_path())) {
|
||||
symbol.documentation = S->get()->class_symbol.documentation;
|
||||
}
|
||||
switch (m.type) {
|
||||
case ClassNode::Member::VARIABLE: {
|
||||
lsp::DocumentSymbol symbol;
|
||||
symbol.name = m.variable->identifier->name;
|
||||
symbol.kind = lsp::SymbolKind::Variable;
|
||||
symbol.deprecated = false;
|
||||
symbol.range.start.line = LINE_NUMBER_TO_INDEX(m.variable->start_line);
|
||||
symbol.range.start.character = LINE_NUMBER_TO_INDEX(m.variable->start_column);
|
||||
symbol.range.end.line = LINE_NUMBER_TO_INDEX(m.variable->end_line);
|
||||
symbol.range.end.character = LINE_NUMBER_TO_INDEX(m.variable->end_column);
|
||||
symbol.selectionRange.start.line = symbol.range.start.line;
|
||||
if (m.variable->exported) {
|
||||
symbol.detail += "@export ";
|
||||
}
|
||||
} else {
|
||||
value_text = JSON::print(node->value);
|
||||
}
|
||||
} else {
|
||||
value_text = JSON::print(node->value);
|
||||
symbol.detail += "var " + m.variable->identifier->name;
|
||||
if (m.get_datatype().is_hard_type()) {
|
||||
symbol.detail += ": " + m.get_datatype().to_string();
|
||||
}
|
||||
if (m.variable->initializer != nullptr && m.variable->initializer->is_constant) {
|
||||
symbol.detail += " = " + JSON::print(m.variable->initializer->reduced_value);
|
||||
}
|
||||
|
||||
symbol.documentation = parse_documentation(LINE_NUMBER_TO_INDEX(m.variable->start_line));
|
||||
symbol.uri = uri;
|
||||
symbol.script_path = path;
|
||||
|
||||
r_symbol.children.push_back(symbol);
|
||||
} break;
|
||||
case ClassNode::Member::CONSTANT: {
|
||||
lsp::DocumentSymbol symbol;
|
||||
|
||||
symbol.name = m.constant->identifier->name;
|
||||
symbol.kind = lsp::SymbolKind::Constant;
|
||||
symbol.deprecated = false;
|
||||
symbol.range.start.line = LINE_NUMBER_TO_INDEX(m.constant->start_line);
|
||||
symbol.range.start.character = LINE_NUMBER_TO_INDEX(m.constant->start_column);
|
||||
symbol.range.end.line = LINE_NUMBER_TO_INDEX(m.constant->end_line);
|
||||
symbol.range.end.character = LINE_NUMBER_TO_INDEX(m.constant->start_column);
|
||||
symbol.selectionRange.start.line = LINE_NUMBER_TO_INDEX(m.constant->start_line);
|
||||
symbol.documentation = parse_documentation(LINE_NUMBER_TO_INDEX(m.constant->start_line));
|
||||
symbol.uri = uri;
|
||||
symbol.script_path = path;
|
||||
|
||||
symbol.detail = "const " + symbol.name;
|
||||
if (m.constant->get_datatype().is_hard_type()) {
|
||||
symbol.detail += ": " + m.constant->get_datatype().to_string();
|
||||
}
|
||||
|
||||
const Variant &default_value = m.constant->initializer->reduced_value;
|
||||
String value_text;
|
||||
if (default_value.get_type() == Variant::OBJECT) {
|
||||
RES res = default_value;
|
||||
if (res.is_valid() && !res->get_path().empty()) {
|
||||
value_text = "preload(\"" + res->get_path() + "\")";
|
||||
if (symbol.documentation.empty()) {
|
||||
if (Map<String, ExtendGDScriptParser *>::Element *S = GDScriptLanguageProtocol::get_singleton()->get_workspace()->scripts.find(res->get_path())) {
|
||||
symbol.documentation = S->get()->class_symbol.documentation;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
value_text = JSON::print(default_value);
|
||||
}
|
||||
} else {
|
||||
value_text = JSON::print(default_value);
|
||||
}
|
||||
if (!value_text.empty()) {
|
||||
symbol.detail += " = " + value_text;
|
||||
}
|
||||
|
||||
r_symbol.children.push_back(symbol);
|
||||
} break;
|
||||
case ClassNode::Member::ENUM_VALUE: {
|
||||
lsp::DocumentSymbol symbol;
|
||||
|
||||
symbol.name = m.constant->identifier->name;
|
||||
symbol.kind = lsp::SymbolKind::EnumMember;
|
||||
symbol.deprecated = false;
|
||||
symbol.range.start.line = LINE_NUMBER_TO_INDEX(m.enum_value.line);
|
||||
symbol.range.start.character = LINE_NUMBER_TO_INDEX(m.enum_value.leftmost_column);
|
||||
symbol.range.end.line = LINE_NUMBER_TO_INDEX(m.enum_value.line);
|
||||
symbol.range.end.character = LINE_NUMBER_TO_INDEX(m.enum_value.rightmost_column);
|
||||
symbol.selectionRange.start.line = LINE_NUMBER_TO_INDEX(m.enum_value.line);
|
||||
symbol.documentation = parse_documentation(LINE_NUMBER_TO_INDEX(m.enum_value.line));
|
||||
symbol.uri = uri;
|
||||
symbol.script_path = path;
|
||||
|
||||
symbol.detail = symbol.name + " = " + itos(m.enum_value.value);
|
||||
|
||||
r_symbol.children.push_back(symbol);
|
||||
} break;
|
||||
case ClassNode::Member::SIGNAL: {
|
||||
lsp::DocumentSymbol symbol;
|
||||
symbol.name = m.signal->identifier->name;
|
||||
symbol.kind = lsp::SymbolKind::Event;
|
||||
symbol.deprecated = false;
|
||||
symbol.range.start.line = LINE_NUMBER_TO_INDEX(m.signal->start_line);
|
||||
symbol.range.start.character = LINE_NUMBER_TO_INDEX(m.signal->start_column);
|
||||
symbol.range.end.line = LINE_NUMBER_TO_INDEX(m.signal->end_line);
|
||||
symbol.range.end.character = LINE_NUMBER_TO_INDEX(m.signal->end_column);
|
||||
symbol.selectionRange.start.line = symbol.range.start.line;
|
||||
symbol.documentation = parse_documentation(LINE_NUMBER_TO_INDEX(m.signal->start_line));
|
||||
symbol.uri = uri;
|
||||
symbol.script_path = path;
|
||||
symbol.detail = "signal " + String(m.signal->identifier->name) + "(";
|
||||
for (int j = 0; j < m.signal->parameters.size(); j++) {
|
||||
if (j > 0) {
|
||||
symbol.detail += ", ";
|
||||
}
|
||||
symbol.detail += m.signal->parameters[i]->identifier->name;
|
||||
}
|
||||
symbol.detail += ")";
|
||||
|
||||
r_symbol.children.push_back(symbol);
|
||||
} break;
|
||||
case ClassNode::Member::ENUM: {
|
||||
lsp::DocumentSymbol symbol;
|
||||
symbol.kind = lsp::SymbolKind::Enum;
|
||||
symbol.range.start.line = LINE_NUMBER_TO_INDEX(m.m_enum->start_line);
|
||||
symbol.range.start.character = LINE_NUMBER_TO_INDEX(m.m_enum->start_column);
|
||||
symbol.range.end.line = LINE_NUMBER_TO_INDEX(m.m_enum->end_line);
|
||||
symbol.range.end.character = LINE_NUMBER_TO_INDEX(m.m_enum->end_column);
|
||||
symbol.selectionRange.start.line = symbol.range.start.line;
|
||||
symbol.documentation = parse_documentation(LINE_NUMBER_TO_INDEX(m.m_enum->start_line));
|
||||
symbol.uri = uri;
|
||||
symbol.script_path = path;
|
||||
|
||||
symbol.detail = "enum " + String(m.m_enum->identifier->name) + "{";
|
||||
for (int j = 0; j < m.m_enum->values.size(); j++) {
|
||||
if (j > 0) {
|
||||
symbol.detail += ", ";
|
||||
}
|
||||
symbol.detail += String(m.m_enum->values[j].identifier->name) + " = " + itos(m.m_enum->values[j].value);
|
||||
}
|
||||
symbol.detail += "}";
|
||||
r_symbol.children.push_back(symbol);
|
||||
} break;
|
||||
case ClassNode::Member::FUNCTION: {
|
||||
lsp::DocumentSymbol symbol;
|
||||
parse_function_symbol(m.function, symbol);
|
||||
r_symbol.children.push_back(symbol);
|
||||
} break;
|
||||
case ClassNode::Member::CLASS: {
|
||||
lsp::DocumentSymbol symbol;
|
||||
parse_class_symbol(m.m_class, symbol);
|
||||
r_symbol.children.push_back(symbol);
|
||||
} break;
|
||||
case ClassNode::Member::UNDEFINED:
|
||||
break; // Unreachable.
|
||||
}
|
||||
if (!value_text.empty()) {
|
||||
symbol.detail += " = " + value_text;
|
||||
}
|
||||
|
||||
r_symbol.children.push_back(symbol);
|
||||
}
|
||||
|
||||
for (int i = 0; i < p_class->functions.size(); ++i) {
|
||||
const GDScriptParser::FunctionNode *func = p_class->functions[i];
|
||||
lsp::DocumentSymbol symbol;
|
||||
parse_function_symbol(func, symbol);
|
||||
r_symbol.children.push_back(symbol);
|
||||
}
|
||||
|
||||
for (int i = 0; i < p_class->static_functions.size(); ++i) {
|
||||
const GDScriptParser::FunctionNode *func = p_class->static_functions[i];
|
||||
lsp::DocumentSymbol symbol;
|
||||
parse_function_symbol(func, symbol);
|
||||
r_symbol.children.push_back(symbol);
|
||||
}
|
||||
|
||||
for (int i = 0; i < p_class->subclasses.size(); ++i) {
|
||||
const GDScriptParser::ClassNode *subclass = p_class->subclasses[i];
|
||||
lsp::DocumentSymbol symbol;
|
||||
parse_class_symbol(subclass, symbol);
|
||||
r_symbol.children.push_back(symbol);
|
||||
}
|
||||
}
|
||||
|
||||
void ExtendGDScriptParser::parse_function_symbol(const GDScriptParser::FunctionNode *p_func, lsp::DocumentSymbol &r_symbol) {
|
||||
const String uri = get_uri();
|
||||
|
||||
r_symbol.name = p_func->name;
|
||||
r_symbol.name = p_func->identifier->name;
|
||||
r_symbol.kind = lsp::SymbolKind::Function;
|
||||
r_symbol.detail = "func " + p_func->name + "(";
|
||||
r_symbol.detail = "func " + String(p_func->identifier->name) + "(";
|
||||
r_symbol.deprecated = false;
|
||||
const int line = LINE_NUMBER_TO_INDEX(p_func->line);
|
||||
r_symbol.range.start.line = line;
|
||||
r_symbol.range.start.character = p_func->column;
|
||||
r_symbol.range.end.line = MAX(p_func->body->end_line - 2, r_symbol.range.start.line);
|
||||
r_symbol.range.end.character = lines[r_symbol.range.end.line].length();
|
||||
r_symbol.range.start.line = LINE_NUMBER_TO_INDEX(p_func->start_line);
|
||||
r_symbol.range.start.character = LINE_NUMBER_TO_INDEX(p_func->start_column);
|
||||
r_symbol.range.end.line = LINE_NUMBER_TO_INDEX(p_func->start_line);
|
||||
r_symbol.range.end.character = LINE_NUMBER_TO_INDEX(p_func->end_column);
|
||||
r_symbol.selectionRange.start.line = r_symbol.range.start.line;
|
||||
r_symbol.documentation = parse_documentation(line);
|
||||
r_symbol.documentation = parse_documentation(LINE_NUMBER_TO_INDEX(p_func->start_line));
|
||||
r_symbol.uri = uri;
|
||||
r_symbol.script_path = path;
|
||||
|
||||
String arguments;
|
||||
for (int i = 0; i < p_func->arguments.size(); i++) {
|
||||
String parameters;
|
||||
for (int i = 0; i < p_func->parameters.size(); i++) {
|
||||
const ParameterNode *parameter = p_func->parameters[i];
|
||||
lsp::DocumentSymbol symbol;
|
||||
symbol.kind = lsp::SymbolKind::Variable;
|
||||
symbol.name = p_func->arguments[i];
|
||||
symbol.range.start.line = LINE_NUMBER_TO_INDEX(p_func->body->line);
|
||||
symbol.range.start.character = p_func->body->column;
|
||||
symbol.range.end = symbol.range.start;
|
||||
symbol.name = parameter->identifier->name;
|
||||
symbol.range.start.line = LINE_NUMBER_TO_INDEX(parameter->start_line);
|
||||
symbol.range.start.character = LINE_NUMBER_TO_INDEX(parameter->start_line);
|
||||
symbol.range.end.line = LINE_NUMBER_TO_INDEX(parameter->end_line);
|
||||
symbol.range.end.character = LINE_NUMBER_TO_INDEX(parameter->end_column);
|
||||
symbol.uri = uri;
|
||||
symbol.script_path = path;
|
||||
r_symbol.children.push_back(symbol);
|
||||
if (i > 0) {
|
||||
arguments += ", ";
|
||||
parameters += ", ";
|
||||
}
|
||||
arguments += String(p_func->arguments[i]);
|
||||
if (p_func->argument_types[i].kind != GDScriptParser::DataType::UNRESOLVED) {
|
||||
arguments += ": " + p_func->argument_types[i].to_string();
|
||||
parameters += String(parameter->identifier->name);
|
||||
if (parameter->get_datatype().is_hard_type()) {
|
||||
parameters += ": " + parameter->get_datatype().to_string();
|
||||
}
|
||||
int default_value_idx = i - (p_func->arguments.size() - p_func->default_values.size());
|
||||
if (default_value_idx >= 0) {
|
||||
const GDScriptParser::ConstantNode *const_node = dynamic_cast<const GDScriptParser::ConstantNode *>(p_func->default_values[default_value_idx]);
|
||||
if (const_node == nullptr) {
|
||||
const GDScriptParser::OperatorNode *operator_node = dynamic_cast<const GDScriptParser::OperatorNode *>(p_func->default_values[default_value_idx]);
|
||||
if (operator_node) {
|
||||
const_node = dynamic_cast<const GDScriptParser::ConstantNode *>(operator_node->next);
|
||||
}
|
||||
}
|
||||
|
||||
if (const_node) {
|
||||
String value = JSON::print(const_node->value);
|
||||
arguments += " = " + value;
|
||||
}
|
||||
if (parameter->default_value != nullptr) {
|
||||
String value = JSON::print(parameter->default_value->reduced_value);
|
||||
parameters += " = " + value;
|
||||
}
|
||||
}
|
||||
r_symbol.detail += arguments + ")";
|
||||
if (p_func->return_type.kind != GDScriptParser::DataType::UNRESOLVED) {
|
||||
r_symbol.detail += " -> " + p_func->return_type.to_string();
|
||||
r_symbol.detail += parameters + ")";
|
||||
if (p_func->get_datatype().is_hard_type()) {
|
||||
r_symbol.detail += " -> " + p_func->get_datatype().to_string();
|
||||
}
|
||||
|
||||
for (const Map<StringName, LocalVarNode *>::Element *E = p_func->body->variables.front(); E; E = E->next()) {
|
||||
for (int i = 0; i < p_func->body->locals.size(); i++) {
|
||||
const SuiteNode::Local &local = p_func->body->locals[i];
|
||||
lsp::DocumentSymbol symbol;
|
||||
const GDScriptParser::LocalVarNode *var = E->value();
|
||||
symbol.name = E->key();
|
||||
symbol.kind = lsp::SymbolKind::Variable;
|
||||
symbol.range.start.line = LINE_NUMBER_TO_INDEX(E->get()->line);
|
||||
symbol.range.start.character = E->get()->column;
|
||||
symbol.range.end.line = symbol.range.start.line;
|
||||
symbol.range.end.character = lines[symbol.range.end.line].length();
|
||||
symbol.name = local.name;
|
||||
symbol.kind = local.type == SuiteNode::Local::CONSTANT ? lsp::SymbolKind::Constant : lsp::SymbolKind::Variable;
|
||||
symbol.range.start.line = LINE_NUMBER_TO_INDEX(local.start_line);
|
||||
symbol.range.start.character = LINE_NUMBER_TO_INDEX(local.start_column);
|
||||
symbol.range.end.line = LINE_NUMBER_TO_INDEX(local.end_line);
|
||||
symbol.range.end.character = LINE_NUMBER_TO_INDEX(local.end_column);
|
||||
symbol.uri = uri;
|
||||
symbol.script_path = path;
|
||||
symbol.detail = "var " + symbol.name;
|
||||
if (var->datatype.kind != GDScriptParser::DataType::UNRESOLVED) {
|
||||
symbol.detail += ": " + var->datatype.to_string();
|
||||
symbol.detail = SuiteNode::Local::CONSTANT ? "const " : "var ";
|
||||
symbol.detail += symbol.name;
|
||||
if (local.get_datatype().is_hard_type()) {
|
||||
symbol.detail += ": " + local.get_datatype().to_string();
|
||||
}
|
||||
symbol.documentation = parse_documentation(line);
|
||||
symbol.documentation = parse_documentation(LINE_NUMBER_TO_INDEX(local.start_line));
|
||||
r_symbol.children.push_back(symbol);
|
||||
}
|
||||
}
|
||||
|
@ -624,34 +645,24 @@ const Array &ExtendGDScriptParser::get_member_completions() {
|
|||
Dictionary ExtendGDScriptParser::dump_function_api(const GDScriptParser::FunctionNode *p_func) const {
|
||||
Dictionary func;
|
||||
ERR_FAIL_NULL_V(p_func, func);
|
||||
func["name"] = p_func->name;
|
||||
func["return_type"] = p_func->return_type.to_string();
|
||||
func["name"] = p_func->identifier->name;
|
||||
func["return_type"] = p_func->get_datatype().to_string();
|
||||
func["rpc_mode"] = p_func->rpc_mode;
|
||||
Array arguments;
|
||||
for (int i = 0; i < p_func->arguments.size(); i++) {
|
||||
Array parameters;
|
||||
for (int i = 0; i < p_func->parameters.size(); i++) {
|
||||
Dictionary arg;
|
||||
arg["name"] = p_func->arguments[i];
|
||||
arg["type"] = p_func->argument_types[i].to_string();
|
||||
int default_value_idx = i - (p_func->arguments.size() - p_func->default_values.size());
|
||||
if (default_value_idx >= 0) {
|
||||
const GDScriptParser::ConstantNode *const_node = dynamic_cast<const GDScriptParser::ConstantNode *>(p_func->default_values[default_value_idx]);
|
||||
if (const_node == nullptr) {
|
||||
const GDScriptParser::OperatorNode *operator_node = dynamic_cast<const GDScriptParser::OperatorNode *>(p_func->default_values[default_value_idx]);
|
||||
if (operator_node) {
|
||||
const_node = dynamic_cast<const GDScriptParser::ConstantNode *>(operator_node->next);
|
||||
}
|
||||
}
|
||||
if (const_node) {
|
||||
arg["default_value"] = const_node->value;
|
||||
}
|
||||
arg["name"] = p_func->parameters[i]->identifier->name;
|
||||
arg["type"] = p_func->parameters[i]->get_datatype().to_string();
|
||||
if (p_func->parameters[i]->default_value != nullptr) {
|
||||
arg["default_value"] = p_func->parameters[i]->default_value->reduced_value;
|
||||
}
|
||||
arguments.push_back(arg);
|
||||
parameters.push_back(arg);
|
||||
}
|
||||
if (const lsp::DocumentSymbol *symbol = get_symbol_defined_at_line(LINE_NUMBER_TO_INDEX(p_func->line))) {
|
||||
if (const lsp::DocumentSymbol *symbol = get_symbol_defined_at_line(LINE_NUMBER_TO_INDEX(p_func->start_line))) {
|
||||
func["signature"] = symbol->detail;
|
||||
func["description"] = symbol->documentation;
|
||||
}
|
||||
func["arguments"] = arguments;
|
||||
func["arguments"] = parameters;
|
||||
return func;
|
||||
}
|
||||
|
||||
|
@ -660,91 +671,117 @@ Dictionary ExtendGDScriptParser::dump_class_api(const GDScriptParser::ClassNode
|
|||
|
||||
ERR_FAIL_NULL_V(p_class, class_api);
|
||||
|
||||
class_api["name"] = String(p_class->name);
|
||||
class_api["name"] = p_class->identifier != nullptr ? String(p_class->identifier->name) : String();
|
||||
class_api["path"] = path;
|
||||
Array extends_class;
|
||||
for (int i = 0; i < p_class->extends_class.size(); i++) {
|
||||
extends_class.append(String(p_class->extends_class[i]));
|
||||
for (int i = 0; i < p_class->extends.size(); i++) {
|
||||
extends_class.append(String(p_class->extends[i]));
|
||||
}
|
||||
class_api["extends_class"] = extends_class;
|
||||
class_api["extends_file"] = String(p_class->extends_file);
|
||||
class_api["extends_file"] = String(p_class->extends_path);
|
||||
class_api["icon"] = String(p_class->icon_path);
|
||||
|
||||
if (const lsp::DocumentSymbol *symbol = get_symbol_defined_at_line(LINE_NUMBER_TO_INDEX(p_class->line))) {
|
||||
if (const lsp::DocumentSymbol *symbol = get_symbol_defined_at_line(LINE_NUMBER_TO_INDEX(p_class->start_line))) {
|
||||
class_api["signature"] = symbol->detail;
|
||||
class_api["description"] = symbol->documentation;
|
||||
}
|
||||
|
||||
Array subclasses;
|
||||
for (int i = 0; i < p_class->subclasses.size(); i++) {
|
||||
subclasses.push_back(dump_class_api(p_class->subclasses[i]));
|
||||
}
|
||||
class_api["sub_classes"] = subclasses;
|
||||
|
||||
Array nested_classes;
|
||||
Array constants;
|
||||
for (Map<StringName, GDScriptParser::ClassNode::Constant>::Element *E = p_class->constant_expressions.front(); E; E = E->next()) {
|
||||
const GDScriptParser::ClassNode::Constant &c = E->value();
|
||||
const GDScriptParser::ConstantNode *node = dynamic_cast<const GDScriptParser::ConstantNode *>(c.expression);
|
||||
ERR_FAIL_COND_V(!node, class_api);
|
||||
|
||||
Dictionary api;
|
||||
api["name"] = E->key();
|
||||
api["value"] = node->value;
|
||||
api["data_type"] = node->datatype.to_string();
|
||||
if (const lsp::DocumentSymbol *symbol = get_symbol_defined_at_line(LINE_NUMBER_TO_INDEX(node->line))) {
|
||||
api["signature"] = symbol->detail;
|
||||
api["description"] = symbol->documentation;
|
||||
}
|
||||
constants.push_back(api);
|
||||
}
|
||||
class_api["constants"] = constants;
|
||||
|
||||
Array members;
|
||||
for (int i = 0; i < p_class->variables.size(); ++i) {
|
||||
const GDScriptParser::ClassNode::Member &m = p_class->variables[i];
|
||||
Dictionary api;
|
||||
api["name"] = m.identifier;
|
||||
api["data_type"] = m.data_type.to_string();
|
||||
api["default_value"] = m.default_value;
|
||||
api["setter"] = String(m.setter);
|
||||
api["getter"] = String(m.getter);
|
||||
api["export"] = m._export.type != Variant::NIL;
|
||||
if (const lsp::DocumentSymbol *symbol = get_symbol_defined_at_line(LINE_NUMBER_TO_INDEX(m.line))) {
|
||||
api["signature"] = symbol->detail;
|
||||
api["description"] = symbol->documentation;
|
||||
}
|
||||
members.push_back(api);
|
||||
}
|
||||
class_api["members"] = members;
|
||||
|
||||
Array signals;
|
||||
for (int i = 0; i < p_class->_signals.size(); ++i) {
|
||||
const GDScriptParser::ClassNode::Signal &signal = p_class->_signals[i];
|
||||
Dictionary api;
|
||||
api["name"] = signal.name;
|
||||
Array args;
|
||||
for (int j = 0; j < signal.arguments.size(); j++) {
|
||||
args.append(signal.arguments[j]);
|
||||
}
|
||||
api["arguments"] = args;
|
||||
if (const lsp::DocumentSymbol *symbol = get_symbol_defined_at_line(LINE_NUMBER_TO_INDEX(signal.line))) {
|
||||
api["signature"] = symbol->detail;
|
||||
api["description"] = symbol->documentation;
|
||||
}
|
||||
signals.push_back(api);
|
||||
}
|
||||
class_api["signals"] = signals;
|
||||
|
||||
Array methods;
|
||||
for (int i = 0; i < p_class->functions.size(); ++i) {
|
||||
methods.append(dump_function_api(p_class->functions[i]));
|
||||
}
|
||||
class_api["methods"] = methods;
|
||||
|
||||
Array static_functions;
|
||||
for (int i = 0; i < p_class->static_functions.size(); ++i) {
|
||||
static_functions.append(dump_function_api(p_class->static_functions[i]));
|
||||
|
||||
for (int i = 0; i < p_class->members.size(); i++) {
|
||||
const ClassNode::Member &m = p_class->members[i];
|
||||
switch (m.type) {
|
||||
case ClassNode::Member::CLASS:
|
||||
nested_classes.push_back(dump_class_api(m.m_class));
|
||||
break;
|
||||
case ClassNode::Member::CONSTANT: {
|
||||
Dictionary api;
|
||||
api["name"] = m.constant->identifier->name;
|
||||
api["value"] = m.constant->initializer->reduced_value;
|
||||
api["data_type"] = m.constant->get_datatype().to_string();
|
||||
if (const lsp::DocumentSymbol *symbol = get_symbol_defined_at_line(LINE_NUMBER_TO_INDEX(m.constant->start_line))) {
|
||||
api["signature"] = symbol->detail;
|
||||
api["description"] = symbol->documentation;
|
||||
}
|
||||
constants.push_back(api);
|
||||
} break;
|
||||
case ClassNode::Member::ENUM_VALUE: {
|
||||
Dictionary api;
|
||||
api["name"] = m.enum_value.identifier->name;
|
||||
api["value"] = m.enum_value.value;
|
||||
api["data_type"] = m.get_datatype().to_string();
|
||||
if (const lsp::DocumentSymbol *symbol = get_symbol_defined_at_line(LINE_NUMBER_TO_INDEX(m.enum_value.line))) {
|
||||
api["signature"] = symbol->detail;
|
||||
api["description"] = symbol->documentation;
|
||||
}
|
||||
constants.push_back(api);
|
||||
} break;
|
||||
case ClassNode::Member::ENUM: {
|
||||
Dictionary enum_dict;
|
||||
for (int j = 0; j < m.m_enum->values.size(); i++) {
|
||||
enum_dict[m.m_enum->values[i].identifier->name] = m.m_enum->values[i].value;
|
||||
}
|
||||
|
||||
Dictionary api;
|
||||
api["name"] = m.m_enum->identifier->name;
|
||||
api["value"] = enum_dict;
|
||||
api["data_type"] = m.get_datatype().to_string();
|
||||
if (const lsp::DocumentSymbol *symbol = get_symbol_defined_at_line(LINE_NUMBER_TO_INDEX(m.m_enum->start_line))) {
|
||||
api["signature"] = symbol->detail;
|
||||
api["description"] = symbol->documentation;
|
||||
}
|
||||
constants.push_back(api);
|
||||
} break;
|
||||
case ClassNode::Member::VARIABLE: {
|
||||
Dictionary api;
|
||||
api["name"] = m.variable->identifier->name;
|
||||
api["data_type"] = m.variable->get_datatype().to_string();
|
||||
api["default_value"] = m.variable->initializer != nullptr ? m.variable->initializer->reduced_value : Variant();
|
||||
api["setter"] = m.variable->setter ? ("@" + String(m.variable->identifier->name) + "_setter") : (m.variable->setter_pointer != nullptr ? String(m.variable->setter_pointer->name) : String());
|
||||
api["getter"] = m.variable->getter ? ("@" + String(m.variable->identifier->name) + "_getter") : (m.variable->getter_pointer != nullptr ? String(m.variable->getter_pointer->name) : String());
|
||||
api["export"] = m.variable->exported;
|
||||
if (const lsp::DocumentSymbol *symbol = get_symbol_defined_at_line(LINE_NUMBER_TO_INDEX(m.variable->start_line))) {
|
||||
api["signature"] = symbol->detail;
|
||||
api["description"] = symbol->documentation;
|
||||
}
|
||||
members.push_back(api);
|
||||
} break;
|
||||
case ClassNode::Member::SIGNAL: {
|
||||
Dictionary api;
|
||||
api["name"] = m.signal->identifier->name;
|
||||
Array pars;
|
||||
for (int j = 0; j < m.signal->parameters.size(); j++) {
|
||||
pars.append(String(m.signal->parameters[i]->identifier->name));
|
||||
}
|
||||
api["arguments"] = pars;
|
||||
if (const lsp::DocumentSymbol *symbol = get_symbol_defined_at_line(LINE_NUMBER_TO_INDEX(m.signal->start_line))) {
|
||||
api["signature"] = symbol->detail;
|
||||
api["description"] = symbol->documentation;
|
||||
}
|
||||
signals.push_back(api);
|
||||
} break;
|
||||
case ClassNode::Member::FUNCTION: {
|
||||
if (m.function->is_static) {
|
||||
static_functions.append(dump_function_api(m.function));
|
||||
} else {
|
||||
methods.append(dump_function_api(m.function));
|
||||
}
|
||||
} break;
|
||||
case ClassNode::Member::UNDEFINED:
|
||||
break; // Unreachable.
|
||||
}
|
||||
}
|
||||
|
||||
class_api["sub_classes"] = nested_classes;
|
||||
class_api["constants"] = constants;
|
||||
class_api["members"] = members;
|
||||
class_api["signals"] = signals;
|
||||
class_api["methods"] = methods;
|
||||
class_api["static_functions"] = static_functions;
|
||||
|
||||
return class_api;
|
||||
|
@ -752,7 +789,7 @@ Dictionary ExtendGDScriptParser::dump_class_api(const GDScriptParser::ClassNode
|
|||
|
||||
Dictionary ExtendGDScriptParser::generate_api() const {
|
||||
Dictionary api;
|
||||
const GDScriptParser::Node *head = get_parse_tree();
|
||||
const GDScriptParser::Node *head = get_tree();
|
||||
if (const GDScriptParser::ClassNode *gdclass = dynamic_cast<const GDScriptParser::ClassNode *>(head)) {
|
||||
api = dump_class_api(gdclass);
|
||||
}
|
||||
|
@ -763,7 +800,11 @@ Error ExtendGDScriptParser::parse(const String &p_code, const String &p_path) {
|
|||
path = p_path;
|
||||
lines = p_code.split("\n");
|
||||
|
||||
Error err = GDScriptParser::parse(p_code, p_path.get_base_dir(), false, p_path, false, nullptr, false);
|
||||
Error err = GDScriptParser::parse(p_code, p_path, false);
|
||||
if (err == OK) {
|
||||
GDScriptAnalyzer analyzer(this);
|
||||
err = analyzer.analyze();
|
||||
}
|
||||
update_diagnostics();
|
||||
update_symbols();
|
||||
update_document_links(p_code);
|
||||
|
|
|
@ -118,7 +118,7 @@ void GDScriptWorkspace::reload_all_workspace_scripts() {
|
|||
Map<String, ExtendGDScriptParser *>::Element *S = parse_results.find(path);
|
||||
String err_msg = "Failed parse script " + path;
|
||||
if (S) {
|
||||
err_msg += "\n" + S->get()->get_error();
|
||||
err_msg += "\n" + S->get()->get_errors()[0].message;
|
||||
}
|
||||
ERR_CONTINUE_MSG(err != OK, err_msg);
|
||||
}
|
||||
|
|
|
@ -35,11 +35,13 @@
|
|||
#include "core/os/dir_access.h"
|
||||
#include "core/os/file_access.h"
|
||||
#include "gdscript.h"
|
||||
#include "gdscript_cache.h"
|
||||
#include "gdscript_tokenizer.h"
|
||||
|
||||
GDScriptLanguage *script_language_gd = nullptr;
|
||||
Ref<ResourceFormatLoaderGDScript> resource_loader_gd;
|
||||
Ref<ResourceFormatSaverGDScript> resource_saver_gd;
|
||||
GDScriptCache *gdscript_cache = nullptr;
|
||||
|
||||
#ifdef TOOLS_ENABLED
|
||||
|
||||
|
@ -76,64 +78,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;
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -171,6 +117,8 @@ void register_gdscript_types() {
|
|||
resource_saver_gd.instance();
|
||||
ResourceSaver::add_resource_format_saver(resource_saver_gd);
|
||||
|
||||
gdscript_cache = memnew(GDScriptCache);
|
||||
|
||||
#ifdef TOOLS_ENABLED
|
||||
EditorNode::add_init_callback(_editor_init);
|
||||
|
||||
|
@ -182,6 +130,10 @@ void register_gdscript_types() {
|
|||
void unregister_gdscript_types() {
|
||||
ScriptServer::unregister_language(script_language_gd);
|
||||
|
||||
if (gdscript_cache) {
|
||||
memdelete(gdscript_cache);
|
||||
}
|
||||
|
||||
if (script_language_gd) {
|
||||
memdelete(script_language_gd);
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue