Parse C# script namespace and class
- Added a very simple parser that can extract the namespace and class name of a C# script.
This commit is contained in:
parent
c6e2873605
commit
1aac95a737
14 changed files with 891 additions and 53 deletions
|
@ -32,6 +32,7 @@
|
||||||
|
|
||||||
#include <mono/metadata/threads.h>
|
#include <mono/metadata/threads.h>
|
||||||
|
|
||||||
|
#include "core/io/json.h"
|
||||||
#include "core/os/file_access.h"
|
#include "core/os/file_access.h"
|
||||||
#include "core/os/os.h"
|
#include "core/os/os.h"
|
||||||
#include "core/os/thread.h"
|
#include "core/os/thread.h"
|
||||||
|
@ -42,7 +43,6 @@
|
||||||
#include "editor/csharp_project.h"
|
#include "editor/csharp_project.h"
|
||||||
#include "editor/editor_node.h"
|
#include "editor/editor_node.h"
|
||||||
#include "editor/godotsharp_editor.h"
|
#include "editor/godotsharp_editor.h"
|
||||||
#include "utils/string_utils.h"
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#include "godotsharp_dirs.h"
|
#include "godotsharp_dirs.h"
|
||||||
|
@ -50,6 +50,7 @@
|
||||||
#include "mono_gd/gd_mono_marshal.h"
|
#include "mono_gd/gd_mono_marshal.h"
|
||||||
#include "signal_awaiter_utils.h"
|
#include "signal_awaiter_utils.h"
|
||||||
#include "utils/macros.h"
|
#include "utils/macros.h"
|
||||||
|
#include "utils/string_utils.h"
|
||||||
#include "utils/thread_local.h"
|
#include "utils/thread_local.h"
|
||||||
|
|
||||||
#define CACHED_STRING_NAME(m_var) (CSharpLanguage::get_singleton()->get_string_names().m_var)
|
#define CACHED_STRING_NAME(m_var) (CSharpLanguage::get_singleton()->get_string_names().m_var)
|
||||||
|
@ -370,6 +371,7 @@ bool CSharpLanguage::supports_builtin_mode() const {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#ifdef TOOLS_ENABLED
|
||||||
static String variant_type_to_managed_name(const String &p_var_type_name) {
|
static String variant_type_to_managed_name(const String &p_var_type_name) {
|
||||||
|
|
||||||
if (p_var_type_name.empty())
|
if (p_var_type_name.empty())
|
||||||
|
@ -432,8 +434,7 @@ static String variant_type_to_managed_name(const String &p_var_type_name) {
|
||||||
return p_var_type_name;
|
return p_var_type_name;
|
||||||
}
|
}
|
||||||
|
|
||||||
String CSharpLanguage::make_function(const String &p_class, const String &p_name, const PoolStringArray &p_args) const {
|
String CSharpLanguage::make_function(const String &, const String &p_name, const PoolStringArray &p_args) const {
|
||||||
#ifdef TOOLS_ENABLED
|
|
||||||
// FIXME
|
// FIXME
|
||||||
// - Due to Godot's API limitation this just appends the function to the end of the file
|
// - Due to Godot's API limitation this just appends the function to the end of the file
|
||||||
// - Use fully qualified name if there is ambiguity
|
// - Use fully qualified name if there is ambiguity
|
||||||
|
@ -449,10 +450,12 @@ String CSharpLanguage::make_function(const String &p_class, const String &p_name
|
||||||
s += ")\n{\n // Replace with function body.\n}\n";
|
s += ")\n{\n // Replace with function body.\n}\n";
|
||||||
|
|
||||||
return s;
|
return s;
|
||||||
#else
|
|
||||||
return String();
|
|
||||||
#endif
|
|
||||||
}
|
}
|
||||||
|
#else
|
||||||
|
String CSharpLanguage::make_function(const String &, const String &, const PoolStringArray &) const {
|
||||||
|
return String();
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
String CSharpLanguage::_get_indentation() const {
|
String CSharpLanguage::_get_indentation() const {
|
||||||
#ifdef TOOLS_ENABLED
|
#ifdef TOOLS_ENABLED
|
||||||
|
@ -822,6 +825,49 @@ void CSharpLanguage::reload_assemblies_if_needed(bool p_soft_reload) {
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
void CSharpLanguage::project_assembly_loaded() {
|
||||||
|
|
||||||
|
scripts_metadata.clear();
|
||||||
|
|
||||||
|
String scripts_metadata_filename = "scripts_metadata.";
|
||||||
|
|
||||||
|
#ifdef TOOLS_ENABLED
|
||||||
|
scripts_metadata_filename += Engine::get_singleton()->is_editor_hint() ? "editor" : "editor_player";
|
||||||
|
#else
|
||||||
|
#ifdef DEBUG_ENABLED
|
||||||
|
scripts_metadata_filename += "debug";
|
||||||
|
#else
|
||||||
|
scripts_metadata_filename += "release";
|
||||||
|
#endif
|
||||||
|
#endif
|
||||||
|
|
||||||
|
String scripts_metadata_path = GodotSharpDirs::get_res_metadata_dir().plus_file(scripts_metadata_filename);
|
||||||
|
|
||||||
|
if (FileAccess::exists(scripts_metadata_path)) {
|
||||||
|
String old_json;
|
||||||
|
|
||||||
|
Error ferr = read_all_file_utf8(scripts_metadata_path, old_json);
|
||||||
|
ERR_FAIL_COND(ferr != OK);
|
||||||
|
|
||||||
|
Variant old_dict_var;
|
||||||
|
String err_str;
|
||||||
|
int err_line;
|
||||||
|
Error json_err = JSON::parse(old_json, old_dict_var, err_str, err_line);
|
||||||
|
if (json_err != OK) {
|
||||||
|
ERR_PRINTS("Failed to parse metadata file: '" + err_str + "' (" + String::num_int64(err_line) + ")");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
scripts_metadata = old_dict_var.operator Dictionary();
|
||||||
|
|
||||||
|
print_verbose("Successfully loaded scripts metadata");
|
||||||
|
} else {
|
||||||
|
if (!Engine::get_singleton()->is_editor_hint()) {
|
||||||
|
ERR_PRINT("Missing scripts metadata file");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void CSharpLanguage::get_recognized_extensions(List<String> *p_extensions) const {
|
void CSharpLanguage::get_recognized_extensions(List<String> *p_extensions) const {
|
||||||
|
|
||||||
p_extensions->push_back("cs");
|
p_extensions->push_back("cs");
|
||||||
|
@ -2208,10 +2254,27 @@ bool CSharpScript::can_instance() const {
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#ifdef TOOLS_ENABLED
|
#ifdef TOOLS_ENABLED
|
||||||
return valid && (tool || ScriptServer::is_scripting_enabled());
|
bool extra_cond = tool || ScriptServer::is_scripting_enabled();
|
||||||
#else
|
#else
|
||||||
return valid;
|
bool extra_cond = true;
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
// FIXME Need to think this through better.
|
||||||
|
// For tool scripts, this will never fire if the class is not found. That's because we
|
||||||
|
// don't know if it's a tool script if we can't find the class to access the attributes.
|
||||||
|
if (extra_cond && !script_class) {
|
||||||
|
if (GDMono::get_singleton()->get_project_assembly() == NULL) {
|
||||||
|
// The project assembly is not loaded
|
||||||
|
ERR_EXPLAIN("Cannot instance script because the project assembly is not loaded. Script: " + get_path());
|
||||||
|
ERR_FAIL_V(NULL);
|
||||||
|
} else {
|
||||||
|
// The project assembly is loaded, but the class could not found
|
||||||
|
ERR_EXPLAIN("Cannot instance script because the class '" + name + "' could not be found. Script: " + get_path());
|
||||||
|
ERR_FAIL_V(NULL);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return valid && extra_cond;
|
||||||
}
|
}
|
||||||
|
|
||||||
StringName CSharpScript::get_instance_base_type() const {
|
StringName CSharpScript::get_instance_base_type() const {
|
||||||
|
@ -2317,20 +2380,6 @@ ScriptInstance *CSharpScript::instance_create(Object *p_this) {
|
||||||
CRASH_COND(!valid);
|
CRASH_COND(!valid);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
if (!script_class) {
|
|
||||||
if (GDMono::get_singleton()->get_project_assembly() == NULL) {
|
|
||||||
// The project assembly is not loaded
|
|
||||||
ERR_EXPLAIN("Cannot instance script because the project assembly is not loaded. Script: " + get_path());
|
|
||||||
ERR_FAIL_V(NULL);
|
|
||||||
} else {
|
|
||||||
// The project assembly is loaded, but the class could not found
|
|
||||||
ERR_EXPLAIN("Cannot instance script because the class '" + name + "' could not be found. Script: " + get_path());
|
|
||||||
ERR_FAIL_V(NULL);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ERR_FAIL_COND_V(!valid, NULL);
|
|
||||||
|
|
||||||
if (native) {
|
if (native) {
|
||||||
String native_name = native->get_name();
|
String native_name = native->get_name();
|
||||||
if (!ClassDB::is_parent_class(p_this->get_class_name(), native_name)) {
|
if (!ClassDB::is_parent_class(p_this->get_class_name(), native_name)) {
|
||||||
|
@ -2418,7 +2467,23 @@ Error CSharpScript::reload(bool p_keep_state) {
|
||||||
GDMonoAssembly *project_assembly = GDMono::get_singleton()->get_project_assembly();
|
GDMonoAssembly *project_assembly = GDMono::get_singleton()->get_project_assembly();
|
||||||
|
|
||||||
if (project_assembly) {
|
if (project_assembly) {
|
||||||
script_class = project_assembly->get_object_derived_class(name);
|
const Variant *script_metadata_var = CSharpLanguage::get_singleton()->get_scripts_metadata().getptr(get_path());
|
||||||
|
if (script_metadata_var) {
|
||||||
|
Dictionary script_metadata = script_metadata_var->operator Dictionary()["class"];
|
||||||
|
const Variant *namespace_ = script_metadata.getptr("namespace");
|
||||||
|
const Variant *class_name = script_metadata.getptr("class_name");
|
||||||
|
ERR_FAIL_NULL_V(namespace_, ERR_BUG);
|
||||||
|
ERR_FAIL_NULL_V(class_name, ERR_BUG);
|
||||||
|
GDMonoClass *klass = project_assembly->get_class(namespace_->operator String(), class_name->operator String());
|
||||||
|
if (klass) {
|
||||||
|
bool obj_type = CACHED_CLASS(GodotObject)->is_assignable_from(klass);
|
||||||
|
ERR_FAIL_COND_V(!obj_type, ERR_BUG);
|
||||||
|
script_class = klass;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Missing script metadata. Fallback to legacy method
|
||||||
|
script_class = project_assembly->get_object_derived_class(name);
|
||||||
|
}
|
||||||
|
|
||||||
valid = script_class != NULL;
|
valid = script_class != NULL;
|
||||||
|
|
||||||
|
@ -2546,29 +2611,14 @@ int CSharpScript::get_member_line(const StringName &p_member) const {
|
||||||
|
|
||||||
Error CSharpScript::load_source_code(const String &p_path) {
|
Error CSharpScript::load_source_code(const String &p_path) {
|
||||||
|
|
||||||
PoolVector<uint8_t> sourcef;
|
Error ferr = read_all_file_utf8(p_path, source);
|
||||||
Error err;
|
if (ferr != OK) {
|
||||||
FileAccess *f = FileAccess::open(p_path, FileAccess::READ, &err);
|
if (ferr == ERR_INVALID_DATA) {
|
||||||
ERR_FAIL_COND_V(err != OK, err);
|
ERR_EXPLAIN("Script '" + p_path + "' contains invalid unicode (utf-8), so it was not loaded. Please ensure that scripts are saved in valid utf-8 unicode.");
|
||||||
|
}
|
||||||
int len = f->get_len();
|
ERR_FAIL_V(ferr);
|
||||||
sourcef.resize(len + 1);
|
|
||||||
PoolVector<uint8_t>::Write w = sourcef.write();
|
|
||||||
int r = f->get_buffer(w.ptr(), len);
|
|
||||||
f->close();
|
|
||||||
memdelete(f);
|
|
||||||
ERR_FAIL_COND_V(r != len, ERR_CANT_OPEN);
|
|
||||||
w[len] = 0;
|
|
||||||
|
|
||||||
String s;
|
|
||||||
if (s.parse_utf8((const char *)w.ptr())) {
|
|
||||||
|
|
||||||
ERR_EXPLAIN("Script '" + p_path + "' contains invalid unicode (utf-8), so it was not loaded. Please ensure that scripts are saved in valid utf-8 unicode.");
|
|
||||||
ERR_FAIL_V(ERR_INVALID_DATA);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
source = s;
|
|
||||||
|
|
||||||
#ifdef TOOLS_ENABLED
|
#ifdef TOOLS_ENABLED
|
||||||
source_changed_cache = true;
|
source_changed_cache = true;
|
||||||
#endif
|
#endif
|
||||||
|
|
|
@ -275,6 +275,8 @@ class CSharpLanguage : public ScriptLanguage {
|
||||||
|
|
||||||
int lang_idx;
|
int lang_idx;
|
||||||
|
|
||||||
|
Dictionary scripts_metadata;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
StringNameCache string_names;
|
StringNameCache string_names;
|
||||||
|
|
||||||
|
@ -295,6 +297,10 @@ public:
|
||||||
void reload_assemblies_if_needed(bool p_soft_reload);
|
void reload_assemblies_if_needed(bool p_soft_reload);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
void project_assembly_loaded();
|
||||||
|
|
||||||
|
_FORCE_INLINE_ const Dictionary &get_scripts_metadata() { return scripts_metadata; }
|
||||||
|
|
||||||
virtual String get_name() const;
|
virtual String get_name() const;
|
||||||
|
|
||||||
/* LANGUAGE FUNCTIONS */
|
/* LANGUAGE FUNCTIONS */
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
using System;
|
using System.Collections.Generic;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
|
using DotNet.Globbing;
|
||||||
using Microsoft.Build.Construction;
|
using Microsoft.Build.Construction;
|
||||||
|
|
||||||
namespace GodotSharpTools.Project
|
namespace GodotSharpTools.Project
|
||||||
|
@ -10,8 +11,61 @@ namespace GodotSharpTools.Project
|
||||||
{
|
{
|
||||||
var dir = Directory.GetParent(projectPath).FullName;
|
var dir = Directory.GetParent(projectPath).FullName;
|
||||||
var root = ProjectRootElement.Open(projectPath);
|
var root = ProjectRootElement.Open(projectPath);
|
||||||
if (root.AddItemChecked(itemType, include.RelativeToPath(dir).Replace("/", "\\")))
|
var normalizedInclude = include.RelativeToPath(dir).Replace("/", "\\");
|
||||||
|
|
||||||
|
if (root.AddItemChecked(itemType, normalizedInclude))
|
||||||
root.Save();
|
root.Save();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static string[] GetAllFilesRecursive(string rootDirectory, string mask)
|
||||||
|
{
|
||||||
|
string[] files = Directory.GetFiles(rootDirectory, mask, SearchOption.AllDirectories);
|
||||||
|
|
||||||
|
// We want relative paths
|
||||||
|
for (int i = 0; i < files.Length; i++) {
|
||||||
|
files[i] = files[i].RelativeToPath(rootDirectory);
|
||||||
|
}
|
||||||
|
|
||||||
|
return files;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static string[] GetIncludeFiles(string projectPath, string itemType)
|
||||||
|
{
|
||||||
|
var result = new List<string>();
|
||||||
|
var existingFiles = GetAllFilesRecursive(Path.GetDirectoryName(projectPath), "*.cs");
|
||||||
|
|
||||||
|
GlobOptions globOptions = new GlobOptions();
|
||||||
|
globOptions.Evaluation.CaseInsensitive = false;
|
||||||
|
|
||||||
|
var root = ProjectRootElement.Open(projectPath);
|
||||||
|
|
||||||
|
foreach (var itemGroup in root.ItemGroups)
|
||||||
|
{
|
||||||
|
if (itemGroup.Condition.Length != 0)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
foreach (var item in itemGroup.Items)
|
||||||
|
{
|
||||||
|
if (item.ItemType != itemType)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
string normalizedInclude = item.Include.NormalizePath();
|
||||||
|
|
||||||
|
var glob = Glob.Parse(normalizedInclude, globOptions);
|
||||||
|
|
||||||
|
// TODO Check somehow if path has no blog to avoid the following loop...
|
||||||
|
|
||||||
|
foreach (var existingFile in existingFiles)
|
||||||
|
{
|
||||||
|
if (glob.IsMatch(existingFile))
|
||||||
|
{
|
||||||
|
result.Add(existingFile);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return result.ToArray();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -30,11 +30,15 @@
|
||||||
|
|
||||||
#include "csharp_project.h"
|
#include "csharp_project.h"
|
||||||
|
|
||||||
|
#include "core/io/json.h"
|
||||||
#include "core/os/os.h"
|
#include "core/os/os.h"
|
||||||
#include "core/project_settings.h"
|
#include "core/project_settings.h"
|
||||||
|
|
||||||
|
#include "../csharp_script.h"
|
||||||
#include "../mono_gd/gd_mono_class.h"
|
#include "../mono_gd/gd_mono_class.h"
|
||||||
#include "../mono_gd/gd_mono_marshal.h"
|
#include "../mono_gd/gd_mono_marshal.h"
|
||||||
|
#include "../utils/string_utils.h"
|
||||||
|
#include "script_class_parser.h"
|
||||||
|
|
||||||
namespace CSharpProject {
|
namespace CSharpProject {
|
||||||
|
|
||||||
|
@ -118,4 +122,109 @@ void add_item(const String &p_project_path, const String &p_item_type, const Str
|
||||||
ERR_FAIL();
|
ERR_FAIL();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Error generate_scripts_metadata(const String &p_project_path, const String &p_output_path) {
|
||||||
|
|
||||||
|
_GDMONO_SCOPE_DOMAIN_(TOOLS_DOMAIN)
|
||||||
|
|
||||||
|
GDMonoClass *project_utils = GDMono::get_singleton()->get_editor_tools_assembly()->get_class("GodotSharpTools.Project", "ProjectUtils");
|
||||||
|
|
||||||
|
void *args[2] = {
|
||||||
|
GDMonoMarshal::mono_string_from_godot(p_project_path),
|
||||||
|
GDMonoMarshal::mono_string_from_godot("Compile")
|
||||||
|
};
|
||||||
|
|
||||||
|
MonoException *exc = NULL;
|
||||||
|
MonoArray *ret = (MonoArray *)project_utils->get_method("GetIncludeFiles", 2)->invoke_raw(NULL, args, &exc);
|
||||||
|
|
||||||
|
if (exc) {
|
||||||
|
GDMonoUtils::debug_print_unhandled_exception(exc);
|
||||||
|
ERR_FAIL_V(FAILED);
|
||||||
|
}
|
||||||
|
|
||||||
|
PoolStringArray project_files = GDMonoMarshal::mono_array_to_PoolStringArray(ret);
|
||||||
|
PoolStringArray::Read r = project_files.read();
|
||||||
|
|
||||||
|
Dictionary old_dict = CSharpLanguage::get_singleton()->get_scripts_metadata();
|
||||||
|
Dictionary new_dict;
|
||||||
|
|
||||||
|
for (int i = 0; i < project_files.size(); i++) {
|
||||||
|
const String &project_file = ("res://" + r[i]).simplify_path();
|
||||||
|
|
||||||
|
uint64_t modified_time = FileAccess::get_modified_time(project_file);
|
||||||
|
|
||||||
|
const Variant *old_file_var = old_dict.getptr(project_file);
|
||||||
|
if (old_file_var) {
|
||||||
|
Dictionary old_file_dict = old_file_var->operator Dictionary();
|
||||||
|
|
||||||
|
if (old_file_dict["modified_time"].operator uint64_t() == modified_time) {
|
||||||
|
// No changes so no need to parse again
|
||||||
|
new_dict[project_file] = old_file_dict;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ScriptClassParser scp;
|
||||||
|
Error err = scp.parse_file(project_file);
|
||||||
|
if (err != OK) {
|
||||||
|
ERR_EXPLAIN("Failed to determine namespace and class for script: " + project_file);
|
||||||
|
ERR_FAIL_V(err);
|
||||||
|
}
|
||||||
|
|
||||||
|
Vector<ScriptClassParser::ClassDecl> classes = scp.get_classes();
|
||||||
|
|
||||||
|
bool found = false;
|
||||||
|
Dictionary class_dict;
|
||||||
|
|
||||||
|
String search_name = project_file.get_file().get_basename();
|
||||||
|
|
||||||
|
for (int j = 0; j < classes.size(); j++) {
|
||||||
|
const ScriptClassParser::ClassDecl &class_decl = classes[j];
|
||||||
|
|
||||||
|
if (class_decl.base.size() == 0)
|
||||||
|
continue; // Does not inherit nor implement anything, so it can't be a script class
|
||||||
|
|
||||||
|
String class_cmp;
|
||||||
|
|
||||||
|
if (class_decl.nested) {
|
||||||
|
class_cmp = class_decl.name.get_slice(".", class_decl.name.get_slice_count(".") - 1);
|
||||||
|
} else {
|
||||||
|
class_cmp = class_decl.name;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (class_cmp != search_name)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
class_dict["namespace"] = class_decl.namespace_;
|
||||||
|
class_dict["class_name"] = class_decl.name;
|
||||||
|
class_dict["nested"] = class_decl.nested;
|
||||||
|
|
||||||
|
found = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (found) {
|
||||||
|
Dictionary file_dict;
|
||||||
|
file_dict["modified_time"] = modified_time;
|
||||||
|
file_dict["class"] = class_dict;
|
||||||
|
new_dict[project_file] = file_dict;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (new_dict.size()) {
|
||||||
|
String json = JSON::print(new_dict, "", false);
|
||||||
|
|
||||||
|
Error ferr;
|
||||||
|
FileAccess *f = FileAccess::open(p_output_path, FileAccess::WRITE, &ferr);
|
||||||
|
ERR_EXPLAIN("Cannot open file for writing: " + p_output_path);
|
||||||
|
ERR_FAIL_COND_V(ferr != OK, ferr);
|
||||||
|
f->store_string(json);
|
||||||
|
f->flush();
|
||||||
|
f->close();
|
||||||
|
memdelete(f);
|
||||||
|
}
|
||||||
|
|
||||||
|
return OK;
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace CSharpProject
|
} // namespace CSharpProject
|
||||||
|
|
|
@ -40,6 +40,9 @@ String generate_editor_api_project(const String &p_dir, const String &p_core_dll
|
||||||
String generate_game_project(const String &p_dir, const String &p_name, const Vector<String> &p_files = Vector<String>());
|
String generate_game_project(const String &p_dir, const String &p_name, const Vector<String> &p_files = Vector<String>());
|
||||||
|
|
||||||
void add_item(const String &p_project_path, const String &p_item_type, const String &p_include);
|
void add_item(const String &p_project_path, const String &p_item_type, const String &p_include);
|
||||||
|
|
||||||
|
Error generate_scripts_metadata(const String &p_project_path, const String &p_output_path);
|
||||||
|
|
||||||
} // namespace CSharpProject
|
} // namespace CSharpProject
|
||||||
|
|
||||||
#endif // CSHARP_PROJECT_H
|
#endif // CSHARP_PROJECT_H
|
||||||
|
|
|
@ -39,6 +39,7 @@
|
||||||
#include "../mono_gd/gd_mono_marshal.h"
|
#include "../mono_gd/gd_mono_marshal.h"
|
||||||
#include "../utils/path_utils.h"
|
#include "../utils/path_utils.h"
|
||||||
#include "bindings_generator.h"
|
#include "bindings_generator.h"
|
||||||
|
#include "csharp_project.h"
|
||||||
#include "godotsharp_editor.h"
|
#include "godotsharp_editor.h"
|
||||||
|
|
||||||
#define PROP_NAME_MSBUILD_MONO "MSBuild (Mono)"
|
#define PROP_NAME_MSBUILD_MONO "MSBuild (Mono)"
|
||||||
|
@ -268,7 +269,7 @@ bool GodotSharpBuilds::copy_api_assembly(const String &p_src_dir, const String &
|
||||||
if (!FileAccess::exists(assembly_dst) ||
|
if (!FileAccess::exists(assembly_dst) ||
|
||||||
FileAccess::get_modified_time(assembly_src) > FileAccess::get_modified_time(assembly_dst) ||
|
FileAccess::get_modified_time(assembly_src) > FileAccess::get_modified_time(assembly_dst) ||
|
||||||
GDMono::get_singleton()->metadata_is_api_assembly_invalidated(p_api_type)) {
|
GDMono::get_singleton()->metadata_is_api_assembly_invalidated(p_api_type)) {
|
||||||
DirAccess *da = DirAccess::create(DirAccess::ACCESS_FILESYSTEM);
|
DirAccessRef da = DirAccess::create(DirAccess::ACCESS_FILESYSTEM);
|
||||||
|
|
||||||
String xml_file = p_assembly_name + ".xml";
|
String xml_file = p_assembly_name + ".xml";
|
||||||
if (da->copy(p_src_dir.plus_file(xml_file), p_dst_dir.plus_file(xml_file)) != OK)
|
if (da->copy(p_src_dir.plus_file(xml_file), p_dst_dir.plus_file(xml_file)) != OK)
|
||||||
|
@ -280,8 +281,6 @@ bool GodotSharpBuilds::copy_api_assembly(const String &p_src_dir, const String &
|
||||||
|
|
||||||
Error err = da->copy(assembly_src, assembly_dst);
|
Error err = da->copy(assembly_src, assembly_dst);
|
||||||
|
|
||||||
memdelete(da);
|
|
||||||
|
|
||||||
if (err != OK) {
|
if (err != OK) {
|
||||||
show_build_error_dialog("Failed to copy " + assembly_file);
|
show_build_error_dialog("Failed to copy " + assembly_file);
|
||||||
return false;
|
return false;
|
||||||
|
@ -398,6 +397,18 @@ bool GodotSharpBuilds::build_project_blocking(const String &p_config) {
|
||||||
|
|
||||||
bool GodotSharpBuilds::editor_build_callback() {
|
bool GodotSharpBuilds::editor_build_callback() {
|
||||||
|
|
||||||
|
String scripts_metadata_path_editor = GodotSharpDirs::get_res_metadata_dir().plus_file("scripts_metadata.editor");
|
||||||
|
String scripts_metadata_path_player = GodotSharpDirs::get_res_metadata_dir().plus_file("scripts_metadata.editor_player");
|
||||||
|
|
||||||
|
Error metadata_err = CSharpProject::generate_scripts_metadata(GodotSharpDirs::get_project_csproj_path(), scripts_metadata_path_editor);
|
||||||
|
ERR_FAIL_COND_V(metadata_err != OK, false);
|
||||||
|
|
||||||
|
DirAccessRef da = DirAccess::create(DirAccess::ACCESS_RESOURCES);
|
||||||
|
Error copy_err = da->copy(scripts_metadata_path_editor, scripts_metadata_path_player);
|
||||||
|
|
||||||
|
ERR_EXPLAIN("Failed to copy scripts metadata file");
|
||||||
|
ERR_FAIL_COND_V(copy_err != OK, false);
|
||||||
|
|
||||||
return build_project_blocking("Tools");
|
return build_project_blocking("Tools");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -37,6 +37,7 @@
|
||||||
#include "../godotsharp_dirs.h"
|
#include "../godotsharp_dirs.h"
|
||||||
#include "../mono_gd/gd_mono_class.h"
|
#include "../mono_gd/gd_mono_class.h"
|
||||||
#include "../mono_gd/gd_mono_marshal.h"
|
#include "../mono_gd/gd_mono_marshal.h"
|
||||||
|
#include "csharp_project.h"
|
||||||
#include "godotsharp_builds.h"
|
#include "godotsharp_builds.h"
|
||||||
|
|
||||||
static MonoString *godot_icall_GodotSharpExport_GetTemplatesDir() {
|
static MonoString *godot_icall_GodotSharpExport_GetTemplatesDir() {
|
||||||
|
@ -86,6 +87,12 @@ void GodotSharpExport::_export_begin(const Set<String> &p_features, bool p_debug
|
||||||
|
|
||||||
String build_config = p_debug ? "Debug" : "Release";
|
String build_config = p_debug ? "Debug" : "Release";
|
||||||
|
|
||||||
|
String scripts_metadata_path = GodotSharpDirs::get_res_metadata_dir().plus_file("scripts_metadata." + String(p_debug ? "debug" : "release"));
|
||||||
|
Error metadata_err = CSharpProject::generate_scripts_metadata(GodotSharpDirs::get_project_csproj_path(), scripts_metadata_path);
|
||||||
|
ERR_FAIL_COND(metadata_err != OK);
|
||||||
|
|
||||||
|
ERR_FAIL_COND(!_add_file(scripts_metadata_path, scripts_metadata_path));
|
||||||
|
|
||||||
ERR_FAIL_COND(!GodotSharpBuilds::build_project_blocking(build_config));
|
ERR_FAIL_COND(!GodotSharpBuilds::build_project_blocking(build_config));
|
||||||
|
|
||||||
// Add dependency assemblies
|
// Add dependency assemblies
|
||||||
|
@ -124,7 +131,7 @@ void GodotSharpExport::_export_begin(const Set<String> &p_features, bool p_debug
|
||||||
for (Map<String, String>::Element *E = dependencies.front(); E; E = E->next()) {
|
for (Map<String, String>::Element *E = dependencies.front(); E; E = E->next()) {
|
||||||
String depend_src_path = E->value();
|
String depend_src_path = E->value();
|
||||||
String depend_dst_path = GodotSharpDirs::get_res_assemblies_dir().plus_file(depend_src_path.get_file());
|
String depend_dst_path = GodotSharpDirs::get_res_assemblies_dir().plus_file(depend_src_path.get_file());
|
||||||
ERR_FAIL_COND(!_add_assembly(depend_src_path, depend_dst_path));
|
ERR_FAIL_COND(!_add_file(depend_src_path, depend_dst_path));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Mono specific export template extras (data dir)
|
// Mono specific export template extras (data dir)
|
||||||
|
@ -155,7 +162,7 @@ void GodotSharpExport::_export_begin(const Set<String> &p_features, bool p_debug
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bool GodotSharpExport::_add_assembly(const String &p_src_path, const String &p_dst_path) {
|
bool GodotSharpExport::_add_file(const String &p_src_path, const String &p_dst_path, bool p_remap) {
|
||||||
|
|
||||||
FileAccessRef f = FileAccess::open(p_src_path, FileAccess::READ);
|
FileAccessRef f = FileAccess::open(p_src_path, FileAccess::READ);
|
||||||
ERR_FAIL_COND_V(!f, false);
|
ERR_FAIL_COND_V(!f, false);
|
||||||
|
@ -164,7 +171,7 @@ bool GodotSharpExport::_add_assembly(const String &p_src_path, const String &p_d
|
||||||
data.resize(f->get_len());
|
data.resize(f->get_len());
|
||||||
f->get_buffer(data.ptrw(), data.size());
|
f->get_buffer(data.ptrw(), data.size());
|
||||||
|
|
||||||
add_file(p_dst_path, data, false);
|
add_file(p_dst_path, data, p_remap);
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
|
@ -41,7 +41,7 @@ class GodotSharpExport : public EditorExportPlugin {
|
||||||
|
|
||||||
MonoAssemblyName *aname_prealloc;
|
MonoAssemblyName *aname_prealloc;
|
||||||
|
|
||||||
bool _add_assembly(const String &p_src_path, const String &p_dst_path);
|
bool _add_file(const String &p_src_path, const String &p_dst_path, bool p_remap = false);
|
||||||
|
|
||||||
Error _get_assembly_dependencies(GDMonoAssembly *p_assembly, const Vector<String> &p_search_dirs, Map<String, String> &r_dependencies);
|
Error _get_assembly_dependencies(GDMonoAssembly *p_assembly, const Vector<String> &p_search_dirs, Map<String, String> &r_dependencies);
|
||||||
|
|
||||||
|
|
|
@ -31,6 +31,8 @@
|
||||||
#include "mono_bottom_panel.h"
|
#include "mono_bottom_panel.h"
|
||||||
|
|
||||||
#include "../csharp_script.h"
|
#include "../csharp_script.h"
|
||||||
|
#include "../godotsharp_dirs.h"
|
||||||
|
#include "csharp_project.h"
|
||||||
#include "godotsharp_editor.h"
|
#include "godotsharp_editor.h"
|
||||||
|
|
||||||
MonoBottomPanel *MonoBottomPanel::singleton = NULL;
|
MonoBottomPanel *MonoBottomPanel::singleton = NULL;
|
||||||
|
@ -148,6 +150,10 @@ void MonoBottomPanel::_errors_toggled(bool p_pressed) {
|
||||||
|
|
||||||
void MonoBottomPanel::_build_project_pressed() {
|
void MonoBottomPanel::_build_project_pressed() {
|
||||||
|
|
||||||
|
String scripts_metadata_path = GodotSharpDirs::get_res_metadata_dir().plus_file("scripts_metadata.editor");
|
||||||
|
Error metadata_err = CSharpProject::generate_scripts_metadata(GodotSharpDirs::get_project_csproj_path(), scripts_metadata_path);
|
||||||
|
ERR_FAIL_COND(metadata_err != OK);
|
||||||
|
|
||||||
GodotSharpBuilds::get_singleton()->build_project_blocking("Tools");
|
GodotSharpBuilds::get_singleton()->build_project_blocking("Tools");
|
||||||
|
|
||||||
MonoReloadNode::get_singleton()->restart_reload_timer();
|
MonoReloadNode::get_singleton()->restart_reload_timer();
|
||||||
|
|
486
modules/mono/editor/script_class_parser.cpp
Normal file
486
modules/mono/editor/script_class_parser.cpp
Normal file
|
@ -0,0 +1,486 @@
|
||||||
|
#include "script_class_parser.h"
|
||||||
|
|
||||||
|
#include "core/map.h"
|
||||||
|
#include "core/os/os.h"
|
||||||
|
|
||||||
|
#include "../utils/string_utils.h"
|
||||||
|
|
||||||
|
ScriptClassParser::Token ScriptClassParser::get_token() {
|
||||||
|
|
||||||
|
while (true) {
|
||||||
|
switch (code[idx]) {
|
||||||
|
case '\n': {
|
||||||
|
line++;
|
||||||
|
idx++;
|
||||||
|
break;
|
||||||
|
};
|
||||||
|
case 0: {
|
||||||
|
return TK_EOF;
|
||||||
|
} break;
|
||||||
|
case '{': {
|
||||||
|
idx++;
|
||||||
|
return TK_CURLY_BRACKET_OPEN;
|
||||||
|
};
|
||||||
|
case '}': {
|
||||||
|
idx++;
|
||||||
|
return TK_CURLY_BRACKET_CLOSE;
|
||||||
|
};
|
||||||
|
case '[': {
|
||||||
|
idx++;
|
||||||
|
return TK_BRACKET_OPEN;
|
||||||
|
};
|
||||||
|
case ']': {
|
||||||
|
idx++;
|
||||||
|
return TK_BRACKET_CLOSE;
|
||||||
|
};
|
||||||
|
case '<': {
|
||||||
|
idx++;
|
||||||
|
return TK_OP_LESS;
|
||||||
|
};
|
||||||
|
case '>': {
|
||||||
|
idx++;
|
||||||
|
return TK_OP_GREATER;
|
||||||
|
};
|
||||||
|
case ':': {
|
||||||
|
idx++;
|
||||||
|
return TK_COLON;
|
||||||
|
};
|
||||||
|
case ',': {
|
||||||
|
idx++;
|
||||||
|
return TK_COMMA;
|
||||||
|
};
|
||||||
|
case '.': {
|
||||||
|
idx++;
|
||||||
|
return TK_PERIOD;
|
||||||
|
};
|
||||||
|
case '#': {
|
||||||
|
//compiler directive
|
||||||
|
while (code[idx] != '\n' && code[idx] != 0) {
|
||||||
|
idx++;
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
} break;
|
||||||
|
case '/': {
|
||||||
|
switch (code[idx + 1]) {
|
||||||
|
case '*': { // block comment
|
||||||
|
idx += 2;
|
||||||
|
while (true) {
|
||||||
|
if (code[idx] == 0) {
|
||||||
|
error_str = "Unterminated comment";
|
||||||
|
error = true;
|
||||||
|
return TK_ERROR;
|
||||||
|
} else if (code[idx] == '*' && code[idx + 1] == '/') {
|
||||||
|
idx += 2;
|
||||||
|
break;
|
||||||
|
} else if (code[idx] == '\n') {
|
||||||
|
line++;
|
||||||
|
}
|
||||||
|
|
||||||
|
idx++;
|
||||||
|
}
|
||||||
|
|
||||||
|
} break;
|
||||||
|
case '/': { // line comment skip
|
||||||
|
while (code[idx] != '\n' && code[idx] != 0) {
|
||||||
|
idx++;
|
||||||
|
}
|
||||||
|
|
||||||
|
} break;
|
||||||
|
default: {
|
||||||
|
value = "/";
|
||||||
|
idx++;
|
||||||
|
return TK_SYMBOL;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
continue; // a comment
|
||||||
|
} break;
|
||||||
|
case '\'':
|
||||||
|
case '"': {
|
||||||
|
bool verbatim = idx != 0 && code[idx - 1] == '@';
|
||||||
|
|
||||||
|
CharType begin_str = code[idx];
|
||||||
|
idx++;
|
||||||
|
String tk_string = String();
|
||||||
|
while (true) {
|
||||||
|
if (code[idx] == 0) {
|
||||||
|
error_str = "Unterminated String";
|
||||||
|
error = true;
|
||||||
|
return TK_ERROR;
|
||||||
|
} else if (code[idx] == begin_str) {
|
||||||
|
if (verbatim && code[idx + 1] == '"') { // `""` is verbatim string's `\"`
|
||||||
|
idx += 2; // skip next `"` as well
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
idx += 1;
|
||||||
|
break;
|
||||||
|
} else if (code[idx] == '\\' && !verbatim) {
|
||||||
|
//escaped characters...
|
||||||
|
idx++;
|
||||||
|
CharType next = code[idx];
|
||||||
|
if (next == 0) {
|
||||||
|
error_str = "Unterminated String";
|
||||||
|
error = true;
|
||||||
|
return TK_ERROR;
|
||||||
|
}
|
||||||
|
CharType res = 0;
|
||||||
|
|
||||||
|
switch (next) {
|
||||||
|
case 'b': res = 8; break;
|
||||||
|
case 't': res = 9; break;
|
||||||
|
case 'n': res = 10; break;
|
||||||
|
case 'f': res = 12; break;
|
||||||
|
case 'r':
|
||||||
|
res = 13;
|
||||||
|
break;
|
||||||
|
case '\"': res = '\"'; break;
|
||||||
|
case '\\':
|
||||||
|
res = '\\';
|
||||||
|
break;
|
||||||
|
default: {
|
||||||
|
res = next;
|
||||||
|
} break;
|
||||||
|
}
|
||||||
|
|
||||||
|
tk_string += res;
|
||||||
|
|
||||||
|
} else {
|
||||||
|
if (code[idx] == '\n')
|
||||||
|
line++;
|
||||||
|
tk_string += code[idx];
|
||||||
|
}
|
||||||
|
idx++;
|
||||||
|
}
|
||||||
|
|
||||||
|
value = tk_string;
|
||||||
|
|
||||||
|
return TK_STRING;
|
||||||
|
} break;
|
||||||
|
default: {
|
||||||
|
if (code[idx] <= 32) {
|
||||||
|
idx++;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((code[idx] >= 33 && code[idx] <= 47) || (code[idx] >= 58 && code[idx] <= 63) || (code[idx] >= 91 && code[idx] <= 94) || code[idx] == 96 || (code[idx] >= 123 && code[idx] <= 127)) {
|
||||||
|
value = String::chr(code[idx]);
|
||||||
|
idx++;
|
||||||
|
return TK_SYMBOL;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (code[idx] == '-' || (code[idx] >= '0' && code[idx] <= '9')) {
|
||||||
|
//a number
|
||||||
|
const CharType *rptr;
|
||||||
|
double number = String::to_double(&code[idx], &rptr);
|
||||||
|
idx += (rptr - &code[idx]);
|
||||||
|
value = number;
|
||||||
|
return TK_NUMBER;
|
||||||
|
|
||||||
|
} else if ((code[idx] == '@' && code[idx + 1] != '"') || code[idx] == '_' || (code[idx] >= 'A' && code[idx] <= 'Z') || (code[idx] >= 'a' && code[idx] <= 'z') || code[idx] > 127) {
|
||||||
|
String id;
|
||||||
|
|
||||||
|
id += code[idx];
|
||||||
|
idx++;
|
||||||
|
|
||||||
|
while (code[idx] == '_' || (code[idx] >= 'A' && code[idx] <= 'Z') || (code[idx] >= 'a' && code[idx] <= 'z') || (code[idx] >= '0' && code[idx] <= '9') || code[idx] > 127) {
|
||||||
|
id += code[idx];
|
||||||
|
idx++;
|
||||||
|
}
|
||||||
|
|
||||||
|
value = id;
|
||||||
|
return TK_IDENTIFIER;
|
||||||
|
} else if (code[idx] == '@' && code[idx + 1] == '"') {
|
||||||
|
// begin of verbatim string
|
||||||
|
idx++;
|
||||||
|
} else {
|
||||||
|
error_str = "Unexpected character.";
|
||||||
|
error = true;
|
||||||
|
return TK_ERROR;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Error ScriptClassParser::_skip_type_parameters() {
|
||||||
|
|
||||||
|
Token tk;
|
||||||
|
|
||||||
|
while (true) {
|
||||||
|
tk = get_token();
|
||||||
|
|
||||||
|
if (tk == TK_IDENTIFIER) {
|
||||||
|
tk = get_token();
|
||||||
|
|
||||||
|
if (tk == TK_OP_LESS) {
|
||||||
|
Error err = _skip_type_parameters();
|
||||||
|
if (err)
|
||||||
|
return err;
|
||||||
|
continue;
|
||||||
|
} else if (tk != TK_COMMA) {
|
||||||
|
error_str = "Unexpected token: " + itos(tk);
|
||||||
|
error = true;
|
||||||
|
return ERR_PARSE_ERROR;
|
||||||
|
}
|
||||||
|
} else if (tk == TK_OP_LESS) {
|
||||||
|
error_str = "Expected identifier before `<`.";
|
||||||
|
error = true;
|
||||||
|
return ERR_PARSE_ERROR;
|
||||||
|
} else if (tk == TK_OP_GREATER) {
|
||||||
|
return OK;
|
||||||
|
} else {
|
||||||
|
error_str = "Unexpected token: " + itos(tk);
|
||||||
|
error = true;
|
||||||
|
return ERR_PARSE_ERROR;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Error ScriptClassParser::_parse_class_base(Vector<String> &r_base) {
|
||||||
|
|
||||||
|
Token tk;
|
||||||
|
|
||||||
|
while (true) {
|
||||||
|
tk = get_token();
|
||||||
|
|
||||||
|
if (tk == TK_IDENTIFIER) {
|
||||||
|
bool generic = false;
|
||||||
|
|
||||||
|
String name = value;
|
||||||
|
|
||||||
|
tk = get_token();
|
||||||
|
|
||||||
|
if (tk == TK_OP_LESS) {
|
||||||
|
generic = true;
|
||||||
|
Error err = _skip_type_parameters();
|
||||||
|
if (err)
|
||||||
|
return err;
|
||||||
|
} else if (tk == TK_COMMA) {
|
||||||
|
Error err = _parse_class_base(r_base);
|
||||||
|
if (err)
|
||||||
|
return err;
|
||||||
|
} else if (tk != TK_CURLY_BRACKET_OPEN) {
|
||||||
|
error_str = "Unexpected token: " + itos(tk);
|
||||||
|
error = true;
|
||||||
|
return ERR_PARSE_ERROR;
|
||||||
|
}
|
||||||
|
|
||||||
|
r_base.push_back(!generic ? name : String()); // no generics, please
|
||||||
|
|
||||||
|
return OK;
|
||||||
|
} else {
|
||||||
|
error_str = "Unexpected token: " + itos(tk);
|
||||||
|
error = true;
|
||||||
|
return ERR_PARSE_ERROR;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Error ScriptClassParser::_parse_namespace_name(String &r_name, int &r_curly_stack) {
|
||||||
|
|
||||||
|
Token tk = get_token();
|
||||||
|
|
||||||
|
if (tk == TK_IDENTIFIER) {
|
||||||
|
r_name += String(value);
|
||||||
|
} else {
|
||||||
|
error_str = "Unexpected token: " + itos(tk);
|
||||||
|
error = true;
|
||||||
|
return ERR_PARSE_ERROR;
|
||||||
|
}
|
||||||
|
|
||||||
|
tk = get_token();
|
||||||
|
|
||||||
|
if (tk == TK_PERIOD) {
|
||||||
|
r_name += ".";
|
||||||
|
return _parse_namespace_name(r_name, r_curly_stack);
|
||||||
|
} else if (tk == TK_CURLY_BRACKET_OPEN) {
|
||||||
|
r_curly_stack++;
|
||||||
|
return OK;
|
||||||
|
} else {
|
||||||
|
error_str = "Unexpected token: " + itos(tk);
|
||||||
|
error = true;
|
||||||
|
return ERR_PARSE_ERROR;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Error ScriptClassParser::parse(const String &p_code) {
|
||||||
|
|
||||||
|
code = p_code;
|
||||||
|
idx = 0;
|
||||||
|
line = 0;
|
||||||
|
error_str = String();
|
||||||
|
error = false;
|
||||||
|
value = Variant();
|
||||||
|
classes.clear();
|
||||||
|
|
||||||
|
Token tk = get_token();
|
||||||
|
|
||||||
|
Map<int, NameDecl> name_stack;
|
||||||
|
int curly_stack = 0;
|
||||||
|
int type_curly_stack = 0;
|
||||||
|
|
||||||
|
while (!error && tk != TK_EOF) {
|
||||||
|
if (tk == TK_IDENTIFIER && String(value) == "class") {
|
||||||
|
tk = get_token();
|
||||||
|
|
||||||
|
if (tk == TK_IDENTIFIER) {
|
||||||
|
String name = value;
|
||||||
|
int at_level = type_curly_stack;
|
||||||
|
|
||||||
|
ClassDecl class_decl;
|
||||||
|
|
||||||
|
for (Map<int, NameDecl>::Element *E = name_stack.front(); E; E = E->next()) {
|
||||||
|
const NameDecl &name_decl = E->value();
|
||||||
|
|
||||||
|
if (name_decl.type == NameDecl::NAMESPACE_DECL) {
|
||||||
|
if (E != name_stack.front())
|
||||||
|
class_decl.namespace_ += ".";
|
||||||
|
class_decl.namespace_ += name_decl.name;
|
||||||
|
} else {
|
||||||
|
class_decl.name += name_decl.name + ".";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class_decl.name += name;
|
||||||
|
class_decl.nested = type_curly_stack > 0;
|
||||||
|
|
||||||
|
bool generic = false;
|
||||||
|
|
||||||
|
while (true) {
|
||||||
|
tk = get_token();
|
||||||
|
|
||||||
|
if (tk == TK_COLON) {
|
||||||
|
Error err = _parse_class_base(class_decl.base);
|
||||||
|
if (err)
|
||||||
|
return err;
|
||||||
|
|
||||||
|
curly_stack++;
|
||||||
|
type_curly_stack++;
|
||||||
|
|
||||||
|
break;
|
||||||
|
} else if (tk == TK_CURLY_BRACKET_OPEN) {
|
||||||
|
curly_stack++;
|
||||||
|
type_curly_stack++;
|
||||||
|
break;
|
||||||
|
} else if (tk == TK_OP_LESS && !generic) {
|
||||||
|
generic = true;
|
||||||
|
|
||||||
|
Error err = _skip_type_parameters();
|
||||||
|
if (err)
|
||||||
|
return err;
|
||||||
|
} else {
|
||||||
|
error_str = "Unexpected token: " + itos(tk);
|
||||||
|
error = true;
|
||||||
|
return ERR_PARSE_ERROR;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
NameDecl name_decl;
|
||||||
|
name_decl.name = name;
|
||||||
|
name_decl.type = NameDecl::CLASS_DECL;
|
||||||
|
name_stack[at_level] = name_decl;
|
||||||
|
|
||||||
|
if (!generic) { // no generics, thanks
|
||||||
|
classes.push_back(class_decl);
|
||||||
|
} else if (OS::get_singleton()->is_stdout_verbose()) {
|
||||||
|
String full_name = class_decl.namespace_;
|
||||||
|
if (full_name.length())
|
||||||
|
full_name += ".";
|
||||||
|
full_name += class_decl.name;
|
||||||
|
OS::get_singleton()->print(String("Ignoring generic class declaration: " + class_decl.name).utf8());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if (tk == TK_IDENTIFIER && String(value) == "struct") {
|
||||||
|
String name;
|
||||||
|
int at_level = type_curly_stack;
|
||||||
|
while (true) {
|
||||||
|
tk = get_token();
|
||||||
|
if (tk == TK_IDENTIFIER && name.empty()) {
|
||||||
|
name = String(value);
|
||||||
|
} else if (tk == TK_CURLY_BRACKET_OPEN) {
|
||||||
|
if (name.empty()) {
|
||||||
|
error_str = "Expected identifier after keyword `struct`. Found `{`.";
|
||||||
|
error = true;
|
||||||
|
return ERR_PARSE_ERROR;
|
||||||
|
}
|
||||||
|
|
||||||
|
curly_stack++;
|
||||||
|
type_curly_stack++;
|
||||||
|
break;
|
||||||
|
} else if (tk == TK_EOF) {
|
||||||
|
error_str = "Expected `{` after struct decl. Found `EOF`.";
|
||||||
|
error = true;
|
||||||
|
return ERR_PARSE_ERROR;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
NameDecl name_decl;
|
||||||
|
name_decl.name = name;
|
||||||
|
name_decl.type = NameDecl::STRUCT_DECL;
|
||||||
|
name_stack[at_level] = name_decl;
|
||||||
|
} else if (tk == TK_IDENTIFIER && String(value) == "namespace") {
|
||||||
|
if (type_curly_stack > 0) {
|
||||||
|
error_str = "Found namespace nested inside type.";
|
||||||
|
error = true;
|
||||||
|
return ERR_PARSE_ERROR;
|
||||||
|
}
|
||||||
|
|
||||||
|
String name;
|
||||||
|
int at_level = curly_stack;
|
||||||
|
|
||||||
|
Error err = _parse_namespace_name(name, curly_stack);
|
||||||
|
if (err)
|
||||||
|
return err;
|
||||||
|
|
||||||
|
NameDecl name_decl;
|
||||||
|
name_decl.name = name;
|
||||||
|
name_decl.type = NameDecl::NAMESPACE_DECL;
|
||||||
|
name_stack[at_level] = name_decl;
|
||||||
|
} else if (tk == TK_CURLY_BRACKET_OPEN) {
|
||||||
|
curly_stack++;
|
||||||
|
} else if (tk == TK_CURLY_BRACKET_CLOSE) {
|
||||||
|
curly_stack--;
|
||||||
|
if (name_stack.has(curly_stack)) {
|
||||||
|
if (name_stack[curly_stack].type != NameDecl::NAMESPACE_DECL)
|
||||||
|
type_curly_stack--;
|
||||||
|
name_stack.erase(curly_stack);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
tk = get_token();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!error && tk == TK_EOF && curly_stack > 0) {
|
||||||
|
error_str = "Reached EOF with missing close curly brackets.";
|
||||||
|
error = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (error)
|
||||||
|
return ERR_PARSE_ERROR;
|
||||||
|
|
||||||
|
return OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
Error ScriptClassParser::parse_file(const String &p_filepath) {
|
||||||
|
|
||||||
|
String source;
|
||||||
|
|
||||||
|
Error ferr = read_all_file_utf8(p_filepath, source);
|
||||||
|
if (ferr != OK) {
|
||||||
|
if (ferr == ERR_INVALID_DATA) {
|
||||||
|
ERR_EXPLAIN("File '" + p_filepath + "' contains invalid unicode (utf-8), so it was not loaded. Please ensure that scripts are saved in valid utf-8 unicode.");
|
||||||
|
}
|
||||||
|
ERR_FAIL_V(ferr);
|
||||||
|
}
|
||||||
|
|
||||||
|
return parse(source);
|
||||||
|
}
|
||||||
|
|
||||||
|
String ScriptClassParser::get_error() {
|
||||||
|
return error_str;
|
||||||
|
}
|
||||||
|
|
||||||
|
Vector<ScriptClassParser::ClassDecl> ScriptClassParser::get_classes() {
|
||||||
|
return classes;
|
||||||
|
}
|
74
modules/mono/editor/script_class_parser.h
Normal file
74
modules/mono/editor/script_class_parser.h
Normal file
|
@ -0,0 +1,74 @@
|
||||||
|
#ifndef SCRIPT_CLASS_PARSER_H
|
||||||
|
#define SCRIPT_CLASS_PARSER_H
|
||||||
|
|
||||||
|
#include "core/ustring.h"
|
||||||
|
#include "core/variant.h"
|
||||||
|
#include "core/vector.h"
|
||||||
|
|
||||||
|
class ScriptClassParser {
|
||||||
|
|
||||||
|
public:
|
||||||
|
struct NameDecl {
|
||||||
|
enum Type {
|
||||||
|
NAMESPACE_DECL,
|
||||||
|
CLASS_DECL,
|
||||||
|
STRUCT_DECL
|
||||||
|
};
|
||||||
|
|
||||||
|
String name;
|
||||||
|
Type type;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct ClassDecl {
|
||||||
|
String name;
|
||||||
|
String namespace_;
|
||||||
|
Vector<String> base;
|
||||||
|
bool nested;
|
||||||
|
bool has_script_attr;
|
||||||
|
};
|
||||||
|
|
||||||
|
private:
|
||||||
|
String code;
|
||||||
|
int idx;
|
||||||
|
int line;
|
||||||
|
String error_str;
|
||||||
|
bool error;
|
||||||
|
Variant value;
|
||||||
|
|
||||||
|
Vector<ClassDecl> classes;
|
||||||
|
|
||||||
|
enum Token {
|
||||||
|
TK_BRACKET_OPEN,
|
||||||
|
TK_BRACKET_CLOSE,
|
||||||
|
TK_CURLY_BRACKET_OPEN,
|
||||||
|
TK_CURLY_BRACKET_CLOSE,
|
||||||
|
TK_PERIOD,
|
||||||
|
TK_COLON,
|
||||||
|
TK_COMMA,
|
||||||
|
TK_SYMBOL,
|
||||||
|
TK_IDENTIFIER,
|
||||||
|
TK_STRING,
|
||||||
|
TK_NUMBER,
|
||||||
|
TK_OP_LESS,
|
||||||
|
TK_OP_GREATER,
|
||||||
|
TK_EOF,
|
||||||
|
TK_ERROR
|
||||||
|
};
|
||||||
|
|
||||||
|
Token get_token();
|
||||||
|
|
||||||
|
Error _skip_type_parameters();
|
||||||
|
|
||||||
|
Error _parse_class_base(Vector<String> &r_base);
|
||||||
|
Error _parse_namespace_name(String &r_name, int &r_curly_stack);
|
||||||
|
|
||||||
|
public:
|
||||||
|
Error parse(const String &p_code);
|
||||||
|
Error parse_file(const String &p_filepath);
|
||||||
|
|
||||||
|
String get_error();
|
||||||
|
|
||||||
|
Vector<ClassDecl> get_classes();
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // SCRIPT_CLASS_PARSER_H
|
|
@ -582,6 +582,8 @@ bool GDMono::_load_project_assembly() {
|
||||||
|
|
||||||
if (success) {
|
if (success) {
|
||||||
mono_assembly_set_main(project_assembly->get_assembly());
|
mono_assembly_set_main(project_assembly->get_assembly());
|
||||||
|
|
||||||
|
CSharpLanguage::get_singleton()->project_assembly_loaded();
|
||||||
} else {
|
} else {
|
||||||
if (OS::get_singleton()->is_stdout_verbose())
|
if (OS::get_singleton()->is_stdout_verbose())
|
||||||
print_error("Mono: Failed to load project assembly");
|
print_error("Mono: Failed to load project assembly");
|
||||||
|
|
|
@ -30,6 +30,8 @@
|
||||||
|
|
||||||
#include "string_utils.h"
|
#include "string_utils.h"
|
||||||
|
|
||||||
|
#include "core/os/file_access.h"
|
||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
|
|
||||||
int sfind(const String &p_text, int p_from) {
|
int sfind(const String &p_text, int p_from) {
|
||||||
|
@ -128,6 +130,7 @@ String sformat(const String &p_text, const Variant &p1, const Variant &p2, const
|
||||||
return new_string;
|
return new_string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#ifdef TOOLS_ENABLED
|
||||||
bool is_csharp_keyword(const String &p_name) {
|
bool is_csharp_keyword(const String &p_name) {
|
||||||
|
|
||||||
// Reserved keywords
|
// Reserved keywords
|
||||||
|
@ -156,3 +159,28 @@ bool is_csharp_keyword(const String &p_name) {
|
||||||
String escape_csharp_keyword(const String &p_name) {
|
String escape_csharp_keyword(const String &p_name) {
|
||||||
return is_csharp_keyword(p_name) ? "@" + p_name : p_name;
|
return is_csharp_keyword(p_name) ? "@" + p_name : p_name;
|
||||||
}
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
Error read_all_file_utf8(const String &p_path, String &r_content) {
|
||||||
|
PoolVector<uint8_t> sourcef;
|
||||||
|
Error err;
|
||||||
|
FileAccess *f = FileAccess::open(p_path, FileAccess::READ, &err);
|
||||||
|
ERR_FAIL_COND_V(err != OK, err);
|
||||||
|
|
||||||
|
int len = f->get_len();
|
||||||
|
sourcef.resize(len + 1);
|
||||||
|
PoolVector<uint8_t>::Write w = sourcef.write();
|
||||||
|
int r = f->get_buffer(w.ptr(), len);
|
||||||
|
f->close();
|
||||||
|
memdelete(f);
|
||||||
|
ERR_FAIL_COND_V(r != len, ERR_CANT_OPEN);
|
||||||
|
w[len] = 0;
|
||||||
|
|
||||||
|
String source;
|
||||||
|
if (source.parse_utf8((const char *)w.ptr())) {
|
||||||
|
ERR_FAIL_V(ERR_INVALID_DATA);
|
||||||
|
}
|
||||||
|
|
||||||
|
r_content = source;
|
||||||
|
return OK;
|
||||||
|
}
|
||||||
|
|
|
@ -42,4 +42,6 @@ bool is_csharp_keyword(const String &p_name);
|
||||||
String escape_csharp_keyword(const String &p_name);
|
String escape_csharp_keyword(const String &p_name);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
Error read_all_file_utf8(const String &p_path, String &r_content);
|
||||||
|
|
||||||
#endif // STRING_FORMAT_H
|
#endif // STRING_FORMAT_H
|
||||||
|
|
Loading…
Reference in a new issue