Add C# source generator for a new ScriptPath attribute
This source generator adds a newly introduced attribute, `ScriptPath` to all classes that: - Are top-level classes (not inner/nested). - Have the `partial` modifier. - Inherit `Godot.Object`. - The class name matches the file name. A build error is thrown if the generator finds a class that meets these conditions but is not declared `partial`, unless the class is annotated with the `DisableGodotGenerators` attribute. We also generate an `AssemblyHasScripts` assembly attribute which Godot uses to get all the script classes in the assembly, eliminating the need for Godot to search them. We can also avoid searching in assemblies that don't have this attribute. This will be good for performance in the future once we support multiple assemblies with Godot script classes. This is an example of what the generated code looks like: ``` using Godot; namespace Foo { [ScriptPathAttribute("res://Player.cs")] // Multiple partial declarations are allowed [ScriptPathAttribute("res://Foo/Player.cs")] partial class Player {} } [assembly:AssemblyHasScripts(new System.Type[] { typeof(Foo.Player) })] ``` The new attributes replace script metadata which we were generating by determining the namespace of script classes with a very simple parser. This fixes several issues with the old approach related to parser errors and conditional compilation. It also makes the task part of the MSBuild project build, rather than a separate step executed by the Godot editor.
This commit is contained in:
parent
d4191e48c5
commit
e2afe700f6
41 changed files with 661 additions and 1359 deletions
3
modules/mono/Directory.Build.props
Normal file
3
modules/mono/Directory.Build.props
Normal file
|
@ -0,0 +1,3 @@
|
|||
<Project>
|
||||
<Import Project="$(MSBuildThisFileDirectory)\SdkPackageVersions.props" />
|
||||
</Project>
|
6
modules/mono/SdkPackageVersions.props
Normal file
6
modules/mono/SdkPackageVersions.props
Normal file
|
@ -0,0 +1,6 @@
|
|||
<Project>
|
||||
<PropertyGroup>
|
||||
<PackageVersion_Godot_NET_Sdk>4.0.0-dev4</PackageVersion_Godot_NET_Sdk>
|
||||
<PackageVersion_Godot_SourceGenerators>4.0.0-dev1</PackageVersion_Godot_SourceGenerators>
|
||||
</PropertyGroup>
|
||||
</Project>
|
|
@ -21,6 +21,18 @@ def build_godot_net_sdk(source, target, env):
|
|||
# No need to copy targets. The Godot.NET.Sdk csproj takes care of copying them.
|
||||
|
||||
|
||||
def get_nupkgs_versions(props_file):
|
||||
import xml.etree.ElementTree as ET
|
||||
|
||||
tree = ET.parse(props_file)
|
||||
root = tree.getroot()
|
||||
|
||||
return {
|
||||
"Godot.NET.Sdk": root.find("./PropertyGroup/PackageVersion_Godot_NET_Sdk").text.strip(),
|
||||
"Godot.SourceGenerators": root.find("./PropertyGroup/PackageVersion_Godot_SourceGenerators").text.strip(),
|
||||
}
|
||||
|
||||
|
||||
def build(env_mono):
|
||||
assert env_mono["tools"]
|
||||
|
||||
|
@ -30,14 +42,12 @@ def build(env_mono):
|
|||
|
||||
module_dir = os.getcwd()
|
||||
|
||||
package_version_file = os.path.join(
|
||||
module_dir, "editor", "Godot.NET.Sdk", "Godot.NET.Sdk", "Godot.NET.Sdk_PackageVersion.txt"
|
||||
)
|
||||
nupkgs_versions = get_nupkgs_versions(os.path.join(module_dir, "SdkPackageVersions.props"))
|
||||
|
||||
with open(package_version_file, mode="r") as f:
|
||||
version = f.read().strip()
|
||||
|
||||
target_filenames = ["Godot.NET.Sdk.%s.nupkg" % version]
|
||||
target_filenames = [
|
||||
"Godot.NET.Sdk.%s.nupkg" % nupkgs_versions["Godot.NET.Sdk"],
|
||||
"Godot.SourceGenerators.%s.nupkg" % nupkgs_versions["Godot.SourceGenerators"],
|
||||
]
|
||||
|
||||
targets = [os.path.join(nupkgs_dir, filename) for filename in target_filenames]
|
||||
|
||||
|
|
|
@ -31,6 +31,7 @@
|
|||
#include "csharp_script.h"
|
||||
|
||||
#include <mono/metadata/threads.h>
|
||||
#include <mono/metadata/tokentype.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#include "core/config/project_settings.h"
|
||||
|
@ -1182,46 +1183,56 @@ void CSharpLanguage::reload_assemblies(bool p_soft_reload) {
|
|||
}
|
||||
#endif
|
||||
|
||||
void CSharpLanguage::_load_scripts_metadata() {
|
||||
scripts_metadata.clear();
|
||||
void CSharpLanguage::lookup_script_for_class(GDMonoClass *p_class) {
|
||||
if (!p_class->has_attribute(CACHED_CLASS(ScriptPathAttribute))) {
|
||||
return;
|
||||
}
|
||||
|
||||
String scripts_metadata_filename = "scripts_metadata.";
|
||||
MonoObject *attr = p_class->get_attribute(CACHED_CLASS(ScriptPathAttribute));
|
||||
String path = CACHED_FIELD(ScriptPathAttribute, path)->get_string_value(attr);
|
||||
|
||||
#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
|
||||
dotnet_script_lookup_map[path] = DotNetScriptLookupInfo(
|
||||
p_class->get_namespace(), p_class->get_name(), p_class);
|
||||
}
|
||||
|
||||
String scripts_metadata_path = GodotSharpDirs::get_res_metadata_dir().plus_file(scripts_metadata_filename);
|
||||
void CSharpLanguage::lookup_scripts_in_assembly(GDMonoAssembly *p_assembly) {
|
||||
if (p_assembly->has_attribute(CACHED_CLASS(AssemblyHasScriptsAttribute))) {
|
||||
MonoObject *attr = p_assembly->get_attribute(CACHED_CLASS(AssemblyHasScriptsAttribute));
|
||||
bool requires_lookup = CACHED_FIELD(AssemblyHasScriptsAttribute, requiresLookup)->get_bool_value(attr);
|
||||
|
||||
if (FileAccess::exists(scripts_metadata_path)) {
|
||||
String old_json;
|
||||
if (requires_lookup) {
|
||||
// This is supported for scenarios where specifying all types would be cumbersome,
|
||||
// such as when disabling C# source generators (for whatever reason) or when using a
|
||||
// language other than C# that has nothing similar to source generators to automate it.
|
||||
MonoImage *image = p_assembly->get_image();
|
||||
|
||||
Error ferr = read_all_file_utf8(scripts_metadata_path, old_json);
|
||||
int rows = mono_image_get_table_rows(image, MONO_TABLE_TYPEDEF);
|
||||
|
||||
ERR_FAIL_COND(ferr != OK);
|
||||
for (int i = 1; i < rows; i++) {
|
||||
// We don't search inner classes, only top-level.
|
||||
MonoClass *mono_class = mono_class_get(image, (i + 1) | MONO_TOKEN_TYPE_DEF);
|
||||
|
||||
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_PRINT("Failed to parse metadata file: '" + err_str + "' (" + String::num_int64(err_line) + ").");
|
||||
return;
|
||||
}
|
||||
if (!mono_class_is_assignable_from(CACHED_CLASS_RAW(GodotObject), mono_class)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
scripts_metadata = old_dict_var.operator Dictionary();
|
||||
scripts_metadata_invalidated = false;
|
||||
GDMonoClass *current = p_assembly->get_class(mono_class);
|
||||
if (current) {
|
||||
lookup_script_for_class(current);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// This is the most likely scenario as we use C# source generators
|
||||
MonoArray *script_types = (MonoArray *)CACHED_FIELD(AssemblyHasScriptsAttribute, scriptTypes)->get_value(attr);
|
||||
|
||||
print_verbose("Successfully loaded scripts metadata");
|
||||
} else {
|
||||
if (!Engine::get_singleton()->is_editor_hint()) {
|
||||
ERR_PRINT("Missing scripts metadata file.");
|
||||
int length = mono_array_length(script_types);
|
||||
|
||||
for (int i = 0; i < length; i++) {
|
||||
MonoReflectionType *reftype = mono_array_get(script_types, MonoReflectionType *, i);
|
||||
ManagedType type = ManagedType::from_reftype(reftype);
|
||||
ERR_CONTINUE(!type.type_class);
|
||||
lookup_script_for_class(type.type_class);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1300,7 +1311,7 @@ void CSharpLanguage::_on_scripts_domain_unloaded() {
|
|||
}
|
||||
#endif
|
||||
|
||||
scripts_metadata_invalidated = true;
|
||||
dotnet_script_lookup_map.clear();
|
||||
}
|
||||
|
||||
#ifdef TOOLS_ENABLED
|
||||
|
@ -3356,45 +3367,34 @@ Error CSharpScript::reload(bool p_keep_state) {
|
|||
|
||||
GD_MONO_SCOPE_THREAD_ATTACH;
|
||||
|
||||
GDMonoAssembly *project_assembly = GDMono::get_singleton()->get_project_assembly();
|
||||
const DotNetScriptLookupInfo *lookup_info =
|
||||
CSharpLanguage::get_singleton()->lookup_dotnet_script(get_path());
|
||||
|
||||
if (project_assembly) {
|
||||
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 && CACHED_CLASS(GodotObject)->is_assignable_from(klass)) {
|
||||
script_class = klass;
|
||||
}
|
||||
} else {
|
||||
// Missing script metadata. Fallback to legacy method
|
||||
script_class = project_assembly->get_object_derived_class(name);
|
||||
if (lookup_info) {
|
||||
GDMonoClass *klass = lookup_info->script_class;
|
||||
if (klass) {
|
||||
ERR_FAIL_COND_V(!CACHED_CLASS(GodotObject)->is_assignable_from(klass), FAILED);
|
||||
script_class = klass;
|
||||
}
|
||||
|
||||
valid = script_class != nullptr;
|
||||
|
||||
if (script_class) {
|
||||
#ifdef DEBUG_ENABLED
|
||||
print_verbose("Found class " + script_class->get_full_name() + " for script " + get_path());
|
||||
#endif
|
||||
|
||||
native = GDMonoUtils::get_class_native_base(script_class);
|
||||
|
||||
CRASH_COND(native == nullptr);
|
||||
|
||||
update_script_class_info(this);
|
||||
|
||||
_update_exports();
|
||||
}
|
||||
|
||||
return OK;
|
||||
}
|
||||
|
||||
return ERR_FILE_MISSING_DEPENDENCIES;
|
||||
valid = script_class != nullptr;
|
||||
|
||||
if (script_class) {
|
||||
#ifdef DEBUG_ENABLED
|
||||
print_verbose("Found class " + script_class->get_full_name() + " for script " + get_path());
|
||||
#endif
|
||||
|
||||
native = GDMonoUtils::get_class_native_base(script_class);
|
||||
|
||||
CRASH_COND(native == nullptr);
|
||||
|
||||
update_script_class_info(this);
|
||||
|
||||
_update_exports();
|
||||
}
|
||||
|
||||
return OK;
|
||||
}
|
||||
|
||||
ScriptLanguage *CSharpScript::get_language() const {
|
||||
|
|
|
@ -66,6 +66,18 @@ TScriptInstance *cast_script_instance(ScriptInstance *p_inst) {
|
|||
|
||||
#define CAST_CSHARP_INSTANCE(m_inst) (cast_script_instance<CSharpInstance, CSharpLanguage>(m_inst))
|
||||
|
||||
struct DotNetScriptLookupInfo {
|
||||
String class_namespace;
|
||||
String class_name;
|
||||
GDMonoClass *script_class = nullptr;
|
||||
|
||||
DotNetScriptLookupInfo() {} // Required by HashMap...
|
||||
|
||||
DotNetScriptLookupInfo(const String &p_class_namespace, const String &p_class_name, GDMonoClass *p_script_class) :
|
||||
class_namespace(p_class_namespace), class_name(p_class_name), script_class(p_script_class) {
|
||||
}
|
||||
};
|
||||
|
||||
class CSharpScript : public Script {
|
||||
GDCLASS(CSharpScript, Script);
|
||||
|
||||
|
@ -390,16 +402,15 @@ class CSharpLanguage : public ScriptLanguage {
|
|||
|
||||
int lang_idx = -1;
|
||||
|
||||
Dictionary scripts_metadata;
|
||||
bool scripts_metadata_invalidated = true;
|
||||
HashMap<String, DotNetScriptLookupInfo> dotnet_script_lookup_map;
|
||||
|
||||
void lookup_script_for_class(GDMonoClass *p_class);
|
||||
|
||||
// For debug_break and debug_break_parse
|
||||
int _debug_parse_err_line = -1;
|
||||
String _debug_parse_err_file;
|
||||
String _debug_error;
|
||||
|
||||
void _load_scripts_metadata();
|
||||
|
||||
friend class GDMono;
|
||||
void _on_scripts_domain_unloaded();
|
||||
|
||||
|
@ -436,19 +447,14 @@ public:
|
|||
void reload_assemblies(bool p_soft_reload);
|
||||
#endif
|
||||
|
||||
_FORCE_INLINE_ Dictionary get_scripts_metadata_or_nothing() {
|
||||
return scripts_metadata_invalidated ? Dictionary() : scripts_metadata;
|
||||
}
|
||||
|
||||
_FORCE_INLINE_ const Dictionary &get_scripts_metadata() {
|
||||
if (scripts_metadata_invalidated) {
|
||||
_load_scripts_metadata();
|
||||
}
|
||||
return scripts_metadata;
|
||||
}
|
||||
|
||||
_FORCE_INLINE_ ManagedCallableMiddleman *get_managed_callable_middleman() const { return managed_callable_middleman; }
|
||||
|
||||
void lookup_scripts_in_assembly(GDMonoAssembly *p_assembly);
|
||||
|
||||
const DotNetScriptLookupInfo *lookup_dotnet_script(const String &p_script_path) const {
|
||||
return dotnet_script_lookup_map.getptr(p_script_path);
|
||||
}
|
||||
|
||||
String get_name() const override;
|
||||
|
||||
/* LANGUAGE FUNCTIONS */
|
||||
|
|
|
@ -2,6 +2,12 @@
|
|||
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Godot.NET.Sdk", "Godot.NET.Sdk\Godot.NET.Sdk.csproj", "{31B00BFA-DEA1-42FA-A472-9E54A92A8A5F}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Godot.SourceGenerators", "Godot.SourceGenerators\Godot.SourceGenerators.csproj", "{32D31B23-2A45-4099-B4F5-95B4C8FF7D9F}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Godot.SourceGenerators.Sample", "Godot.SourceGenerators.Sample\Godot.SourceGenerators.Sample.csproj", "{7297A614-8DF5-43DE-9EAD-99671B26BD1F}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GodotSharp", "..\..\glue\GodotSharp\GodotSharp\GodotSharp.csproj", "{AEBF0036-DA76-4341-B651-A3F2856AB2FA}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|Any CPU = Debug|Any CPU
|
||||
|
@ -12,5 +18,17 @@ Global
|
|||
{31B00BFA-DEA1-42FA-A472-9E54A92A8A5F}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{31B00BFA-DEA1-42FA-A472-9E54A92A8A5F}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{31B00BFA-DEA1-42FA-A472-9E54A92A8A5F}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{32D31B23-2A45-4099-B4F5-95B4C8FF7D9F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{32D31B23-2A45-4099-B4F5-95B4C8FF7D9F}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{32D31B23-2A45-4099-B4F5-95B4C8FF7D9F}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{32D31B23-2A45-4099-B4F5-95B4C8FF7D9F}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{7297A614-8DF5-43DE-9EAD-99671B26BD1F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{7297A614-8DF5-43DE-9EAD-99671B26BD1F}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{7297A614-8DF5-43DE-9EAD-99671B26BD1F}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{7297A614-8DF5-43DE-9EAD-99671B26BD1F}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{AEBF0036-DA76-4341-B651-A3F2856AB2FA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{AEBF0036-DA76-4341-B651-A3F2856AB2FA}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{AEBF0036-DA76-4341-B651-A3F2856AB2FA}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{AEBF0036-DA76-4341-B651-A3F2856AB2FA}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
EndGlobalSection
|
||||
EndGlobal
|
||||
|
|
|
@ -8,43 +8,33 @@
|
|||
|
||||
<PackageId>Godot.NET.Sdk</PackageId>
|
||||
<Version>4.0.0</Version>
|
||||
<PackageProjectUrl>https://github.com/godotengine/godot/tree/master/modules/mono/editor/Godot.NET.Sdk</PackageProjectUrl>
|
||||
<PackageVersion>$(PackageVersion_Godot_NET_Sdk)</PackageVersion>
|
||||
<RepositoryUrl>https://github.com/godotengine/godot/tree/master/modules/mono/editor/Godot.NET.Sdk</RepositoryUrl>
|
||||
<PackageProjectUrl>$(RepositoryUrl)</PackageProjectUrl>
|
||||
<PackageType>MSBuildSdk</PackageType>
|
||||
<PackageTags>MSBuildSdk</PackageTags>
|
||||
<PackageLicenseExpression>MIT</PackageLicenseExpression>
|
||||
<GeneratePackageOnBuild>true</GeneratePackageOnBuild>
|
||||
|
||||
<!-- Exclude target framework from the package dependencies as we don't include the build output -->
|
||||
<SuppressDependenciesWhenPacking>true</SuppressDependenciesWhenPacking>
|
||||
<IncludeBuildOutput>false</IncludeBuildOutput>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup>
|
||||
<NuspecFile>Godot.NET.Sdk.nuspec</NuspecFile>
|
||||
<GenerateNuspecDependsOn>$(GenerateNuspecDependsOn);SetNuSpecProperties</GenerateNuspecDependsOn>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<!-- Package Sdk\Sdk.props and Sdk\Sdk.targets file -->
|
||||
<None Include="Sdk\Sdk.props" Pack="true" PackagePath="Sdk" Visible="false" />
|
||||
<None Include="Sdk\Sdk.targets" Pack="true" PackagePath="Sdk" Visible="false" />
|
||||
<!-- SdkPackageVersions.props -->
|
||||
|
||||
<Target Name="ReadGodotNETSdkVersion" BeforeTargets="BeforeBuild;BeforeRebuild;CoreCompile">
|
||||
<PropertyGroup>
|
||||
<PackageVersion>$([System.IO.File]::ReadAllText('$(ProjectDir)Godot.NET.Sdk_PackageVersion.txt').Trim())</PackageVersion>
|
||||
</PropertyGroup>
|
||||
</Target>
|
||||
|
||||
<Target Name="SetNuSpecProperties" Condition=" Exists('$(NuspecFile)') " DependsOnTargets="ReadGodotNETSdkVersion">
|
||||
<PropertyGroup>
|
||||
<NuspecProperties>
|
||||
id=$(PackageId);
|
||||
description=$(Description);
|
||||
authors=$(Authors);
|
||||
version=$(PackageVersion);
|
||||
packagetype=$(PackageType);
|
||||
tags=$(PackageTags);
|
||||
projecturl=$(PackageProjectUrl)
|
||||
</NuspecProperties>
|
||||
</PropertyGroup>
|
||||
</Target>
|
||||
<None Include="..\..\..\SdkPackageVersions.props" Pack="true" PackagePath="Sdk" Visible="false" />
|
||||
</ItemGroup>
|
||||
|
||||
<Target Name="CopyNupkgToSConsOutputDir" AfterTargets="Pack">
|
||||
<PropertyGroup>
|
||||
<GodotSourceRootPath>$(SolutionDir)\..\..\..\..\</GodotSourceRootPath>
|
||||
<GodotOutputDataDir>$(GodotSourceRootPath)\bin\GodotSharp\</GodotOutputDataDir>
|
||||
</PropertyGroup>
|
||||
<Copy SourceFiles="$(OutputPath)$(PackageId).$(PackageVersion).nupkg"
|
||||
DestinationFolder="$(GodotOutputDataDir)Tools\nupkgs\" />
|
||||
<Copy SourceFiles="$(PackageOutputPath)$(PackageId).$(PackageVersion).nupkg" DestinationFolder="$(GodotOutputDataDir)Tools\nupkgs\" />
|
||||
</Target>
|
||||
</Project>
|
||||
|
|
|
@ -1,22 +0,0 @@
|
|||
<?xml version="1.0" encoding="utf-8" ?>
|
||||
<package xmlns="http://schemas.microsoft.com/packaging/2011/10/nuspec.xsd">
|
||||
<metadata>
|
||||
<id>$id$</id>
|
||||
<version>$version$</version>
|
||||
<description>$description$</description>
|
||||
<authors>$authors$</authors>
|
||||
<owners>$authors$</owners>
|
||||
<projectUrl>$projecturl$</projectUrl>
|
||||
<requireLicenseAcceptance>false</requireLicenseAcceptance>
|
||||
<license type="expression">MIT</license>
|
||||
<licenseUrl>https://licenses.nuget.org/MIT</licenseUrl>
|
||||
<tags>$tags$</tags>
|
||||
<packageTypes>
|
||||
<packageType name="$packagetype$" />
|
||||
</packageTypes>
|
||||
<repository url="$projecturl$" />
|
||||
</metadata>
|
||||
<files>
|
||||
<file src="Sdk\**" target="Sdk" />
|
||||
</files>
|
||||
</package>
|
|
@ -1 +0,0 @@
|
|||
4.0.0-dev3
|
|
@ -1,4 +1,6 @@
|
|||
<Project>
|
||||
<Import Project="$(MSBuildThisFileDirectory)\SdkPackageVersions.props" />
|
||||
|
||||
<PropertyGroup>
|
||||
<!-- Determines if we should import Microsoft.NET.Sdk, if it wasn't already imported. -->
|
||||
<GodotSdkImportsMicrosoftNetSdk Condition=" '$(UsingMicrosoftNETSdk)' != 'true' ">true</GodotSdkImportsMicrosoftNetSdk>
|
||||
|
@ -94,6 +96,7 @@
|
|||
<DefineConstants>$(GodotDefineConstants);$(DefineConstants)</DefineConstants>
|
||||
</PropertyGroup>
|
||||
|
||||
<!-- Godot API references -->
|
||||
<ItemGroup>
|
||||
<!--
|
||||
TODO:
|
||||
|
|
|
@ -14,4 +14,9 @@
|
|||
-->
|
||||
<DefineConstants Condition=" '$(GodotRealTIsDouble)' == 'true' ">GODOT_REAL_T_IS_DOUBLE;$(DefineConstants)</DefineConstants>
|
||||
</PropertyGroup>
|
||||
|
||||
<!-- C# source generators -->
|
||||
<ItemGroup Condition=" '$(DisableImplicitGodotGeneratorReferences)' != 'true' ">
|
||||
<PackageReference Include="Godot.SourceGenerators" Version="$(PackageVersion_Godot_SourceGenerators)" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
|
|
|
@ -0,0 +1,15 @@
|
|||
namespace Godot.SourceGenerators.Sample
|
||||
{
|
||||
partial class Bar : Godot.Object
|
||||
{
|
||||
}
|
||||
|
||||
// Foo in another file
|
||||
partial class Foo
|
||||
{
|
||||
}
|
||||
|
||||
partial class NotSameNameAsFile : Godot.Object
|
||||
{
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
namespace Godot.SourceGenerators.Sample
|
||||
{
|
||||
partial class Foo : Godot.Object
|
||||
{
|
||||
}
|
||||
|
||||
// Foo again in the same file
|
||||
partial class Foo
|
||||
{
|
||||
}
|
||||
}
|
|
@ -0,0 +1,31 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>netstandard2.1</TargetFramework>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup>
|
||||
<!-- $(GodotProjectDir) would normally be defined by the Godot.NET.Sdk -->
|
||||
<GodotProjectDir>$(MSBuildProjectDirectory)</GodotProjectDir>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup>
|
||||
<!-- The emitted files are not part of the compilation nor design.
|
||||
They're only for peeking at the generated sources. Sometimes the
|
||||
emitted files get corrupted, but that won't break anything. -->
|
||||
<EmitCompilerGeneratedFiles>true</EmitCompilerGeneratedFiles>
|
||||
<CompilerGeneratedFilesOutputPath>$(BaseIntermediateOutputPath)\GeneratedFiles</CompilerGeneratedFilesOutputPath>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\..\glue\GodotSharp\GodotSharp\GodotSharp.csproj">
|
||||
<Private>False</Private>
|
||||
</ProjectReference>
|
||||
<ProjectReference Include="..\Godot.SourceGenerators\Godot.SourceGenerators.csproj" OutputItemType="Analyzer" ReferenceOutputAssembly="false" />
|
||||
</ItemGroup>
|
||||
|
||||
<!-- This file is imported automatically when using PackageReference to
|
||||
reference Godot.SourceGenerators, but not when using ProjectReference -->
|
||||
<Import Project="..\Godot.SourceGenerators\Godot.SourceGenerators.props" />
|
||||
|
||||
</Project>
|
|
@ -0,0 +1,33 @@
|
|||
using Microsoft.CodeAnalysis;
|
||||
using Microsoft.CodeAnalysis.CSharp.Syntax;
|
||||
|
||||
namespace Godot.SourceGenerators
|
||||
{
|
||||
public static class Common
|
||||
{
|
||||
public static void ReportNonPartialGodotScriptClass(
|
||||
GeneratorExecutionContext context,
|
||||
ClassDeclarationSyntax cds, INamedTypeSymbol symbol
|
||||
)
|
||||
{
|
||||
string message =
|
||||
"Missing partial modifier on declaration of type '" +
|
||||
$"{symbol.FullQualifiedName()}' which is a subclass of '{GodotClasses.Object}'";
|
||||
|
||||
string description = $"{message}. Subclasses of '{GodotClasses.Object}' must be " +
|
||||
"declared with the partial modifier or annotated with the " +
|
||||
$"attribute '{GodotClasses.DisableGodotGeneratorsAttr}'.";
|
||||
|
||||
context.ReportDiagnostic(Diagnostic.Create(
|
||||
new DiagnosticDescriptor(id: "GODOT-G0001",
|
||||
title: message,
|
||||
messageFormat: message,
|
||||
category: "Usage",
|
||||
DiagnosticSeverity.Error,
|
||||
isEnabledByDefault: true,
|
||||
description),
|
||||
cds.GetLocation(),
|
||||
cds.SyntaxTree.FilePath));
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,86 @@
|
|||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Microsoft.CodeAnalysis;
|
||||
using Microsoft.CodeAnalysis.CSharp;
|
||||
using Microsoft.CodeAnalysis.CSharp.Syntax;
|
||||
|
||||
namespace Godot.SourceGenerators
|
||||
{
|
||||
static class ExtensionMethods
|
||||
{
|
||||
public static bool TryGetGlobalAnalyzerProperty(
|
||||
this GeneratorExecutionContext context, string property, out string? value
|
||||
) => context.AnalyzerConfigOptions.GlobalOptions
|
||||
.TryGetValue("build_property." + property, out value);
|
||||
|
||||
private static bool InheritsFrom(this INamedTypeSymbol? symbol, string baseName)
|
||||
{
|
||||
if (symbol == null)
|
||||
return false;
|
||||
|
||||
while (true)
|
||||
{
|
||||
if (symbol.ToString() == baseName)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
if (symbol.BaseType != null)
|
||||
{
|
||||
symbol = symbol.BaseType;
|
||||
continue;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private static bool IsGodotScriptClass(
|
||||
this ClassDeclarationSyntax cds, Compilation compilation,
|
||||
out INamedTypeSymbol? symbol
|
||||
)
|
||||
{
|
||||
var sm = compilation.GetSemanticModel(cds.SyntaxTree);
|
||||
|
||||
var classTypeSymbol = sm.GetDeclaredSymbol(cds);
|
||||
|
||||
if (classTypeSymbol?.BaseType == null
|
||||
|| !classTypeSymbol.BaseType.InheritsFrom(GodotClasses.Object))
|
||||
{
|
||||
symbol = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
symbol = classTypeSymbol;
|
||||
return true;
|
||||
}
|
||||
|
||||
public static IEnumerable<(ClassDeclarationSyntax cds, INamedTypeSymbol symbol)> SelectGodotScriptClasses(
|
||||
this IEnumerable<ClassDeclarationSyntax> source,
|
||||
Compilation compilation
|
||||
)
|
||||
{
|
||||
foreach (var cds in source)
|
||||
{
|
||||
if (cds.IsGodotScriptClass(compilation, out var symbol))
|
||||
yield return (cds, symbol!);
|
||||
}
|
||||
}
|
||||
|
||||
public static bool IsPartial(this ClassDeclarationSyntax cds)
|
||||
=> cds.Modifiers.Any(SyntaxKind.PartialKeyword);
|
||||
|
||||
public static bool HasDisableGeneratorsAttribute(this INamedTypeSymbol symbol)
|
||||
=> symbol.GetAttributes().Any(attr =>
|
||||
attr.AttributeClass?.ToString() == GodotClasses.DisableGodotGeneratorsAttr);
|
||||
|
||||
private static SymbolDisplayFormat FullyQualifiedFormatOmitGlobal { get; } =
|
||||
SymbolDisplayFormat.FullyQualifiedFormat
|
||||
.WithGlobalNamespaceStyle(SymbolDisplayGlobalNamespaceStyle.Omitted);
|
||||
|
||||
public static string FullQualifiedName(this INamedTypeSymbol symbol)
|
||||
=> symbol.ToDisplayString(NullableFlowState.NotNull, FullyQualifiedFormatOmitGlobal);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,40 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<TargetFramework>netstandard2.0</TargetFramework>
|
||||
<LangVersion>8.0</LangVersion>
|
||||
<Nullable>enable</Nullable>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup>
|
||||
<Description>Core C# source generator for Godot projects.</Description>
|
||||
<Authors>Godot Engine contributors</Authors>
|
||||
|
||||
<PackageId>Godot.SourceGenerators</PackageId>
|
||||
<Version>4.0.0</Version>
|
||||
<PackageVersion>$(PackageVersion_Godot_SourceGenerators)</PackageVersion>
|
||||
<RepositoryUrl>https://github.com/godotengine/godot/tree/master/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators</RepositoryUrl>
|
||||
<PackageProjectUrl>$(RepositoryUrl)</PackageProjectUrl>
|
||||
<PackageLicenseExpression>MIT</PackageLicenseExpression>
|
||||
|
||||
<GeneratePackageOnBuild>true</GeneratePackageOnBuild> <!-- Generates a package at build -->
|
||||
<IncludeBuildOutput>false</IncludeBuildOutput> <!-- Do not include the generator as a lib dependency -->
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="3.8.0" PrivateAssets="all" />
|
||||
<PackageReference Include="Microsoft.CodeAnalysis.Analyzers" Version="3.3.1" PrivateAssets="all" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<!-- Package the generator in the analyzer directory of the nuget package -->
|
||||
<None Include="$(OutputPath)\$(AssemblyName).dll" Pack="true" PackagePath="analyzers/dotnet/cs" Visible="false" />
|
||||
|
||||
<!-- Package the props file -->
|
||||
<None Include="Godot.SourceGenerators.props" Pack="true" PackagePath="build" Visible="false" />
|
||||
</ItemGroup>
|
||||
|
||||
<Target Name="CopyNupkgToSConsOutputDir" AfterTargets="Pack">
|
||||
<PropertyGroup>
|
||||
<GodotSourceRootPath>$(SolutionDir)\..\..\..\..\</GodotSourceRootPath>
|
||||
<GodotOutputDataDir>$(GodotSourceRootPath)\bin\GodotSharp\</GodotOutputDataDir>
|
||||
</PropertyGroup>
|
||||
<Copy SourceFiles="$(PackageOutputPath)$(PackageId).$(PackageVersion).nupkg" DestinationFolder="$(GodotOutputDataDir)Tools\nupkgs\" />
|
||||
</Target>
|
||||
</Project>
|
|
@ -0,0 +1,7 @@
|
|||
<Project>
|
||||
<ItemGroup>
|
||||
<!-- $(GodotProjectDir) is defined by Godot.NET.Sdk -->
|
||||
<CompilerVisibleProperty Include="GodotProjectDir" />
|
||||
<CompilerVisibleProperty Include="GodotScriptPathAttributeGenerator" />
|
||||
</ItemGroup>
|
||||
</Project>
|
|
@ -0,0 +1,9 @@
|
|||
namespace Godot.SourceGenerators
|
||||
{
|
||||
public static class GodotClasses
|
||||
{
|
||||
public const string Object = "Godot.Object";
|
||||
public const string DisableGodotGeneratorsAttr = "Godot.DisableGodotGeneratorsAttribute";
|
||||
public const string AssemblyHasScriptsAttr = "Godot.AssemblyHasScriptsAttribute";
|
||||
}
|
||||
}
|
|
@ -0,0 +1,156 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using Microsoft.CodeAnalysis;
|
||||
using Microsoft.CodeAnalysis.CSharp.Syntax;
|
||||
using Microsoft.CodeAnalysis.Text;
|
||||
|
||||
namespace Godot.SourceGenerators
|
||||
{
|
||||
[Generator]
|
||||
public class ScriptPathAttributeGenerator : ISourceGenerator
|
||||
{
|
||||
public void Execute(GeneratorExecutionContext context)
|
||||
{
|
||||
if (context.TryGetGlobalAnalyzerProperty("GodotScriptPathAttributeGenerator", out string? toggle)
|
||||
&& toggle == "disabled")
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// NOTE: IsNullOrEmpty doesn't work well with nullable checks
|
||||
// ReSharper disable once ReplaceWithStringIsNullOrEmpty
|
||||
if (!context.TryGetGlobalAnalyzerProperty("GodotProjectDir", out string? godotProjectDir)
|
||||
|| godotProjectDir!.Length == 0)
|
||||
{
|
||||
throw new InvalidOperationException("Property 'GodotProjectDir' is null or empty.");
|
||||
}
|
||||
|
||||
var godotClasses = context.Compilation.SyntaxTrees
|
||||
.SelectMany(tree =>
|
||||
tree.GetRoot().DescendantNodes()
|
||||
.OfType<ClassDeclarationSyntax>()
|
||||
// Ignore inner classes
|
||||
.Where(cds => !(cds.Parent is ClassDeclarationSyntax))
|
||||
.SelectGodotScriptClasses(context.Compilation)
|
||||
// Report and skip non-partial classes
|
||||
.Where(x =>
|
||||
{
|
||||
if (x.cds.IsPartial() || x.symbol.HasDisableGeneratorsAttribute())
|
||||
return true;
|
||||
Common.ReportNonPartialGodotScriptClass(context, x.cds, x.symbol);
|
||||
return false;
|
||||
})
|
||||
)
|
||||
// Ignore classes whose name is not the same as the file name
|
||||
.Where(x => Path.GetFileNameWithoutExtension(x.cds.SyntaxTree.FilePath) == x.symbol.Name)
|
||||
.GroupBy(x => x.symbol)
|
||||
.ToDictionary(g => g.Key, g => g.Select(x => x.cds));
|
||||
|
||||
foreach (var godotClass in godotClasses)
|
||||
{
|
||||
VisitGodotScriptClass(context, godotProjectDir,
|
||||
symbol: godotClass.Key,
|
||||
classDeclarations: godotClass.Value);
|
||||
}
|
||||
|
||||
if (godotClasses.Count <= 0)
|
||||
return;
|
||||
|
||||
AddScriptTypesAssemblyAttr(context, godotClasses);
|
||||
}
|
||||
|
||||
private static void VisitGodotScriptClass(
|
||||
GeneratorExecutionContext context,
|
||||
string godotProjectDir,
|
||||
INamedTypeSymbol symbol,
|
||||
IEnumerable<ClassDeclarationSyntax> classDeclarations
|
||||
)
|
||||
{
|
||||
var attributesBuilder = new StringBuilder();
|
||||
|
||||
// Remember syntax trees for which we already added an attribute, to prevent unnecessary duplicates.
|
||||
var attributedTrees = new List<SyntaxTree>();
|
||||
|
||||
foreach (var cds in classDeclarations)
|
||||
{
|
||||
if (attributedTrees.Contains(cds.SyntaxTree))
|
||||
continue;
|
||||
|
||||
attributedTrees.Add(cds.SyntaxTree);
|
||||
|
||||
if (attributesBuilder.Length != 0)
|
||||
attributesBuilder.Append("\n ");
|
||||
|
||||
attributesBuilder.Append(@"[ScriptPathAttribute(""res://");
|
||||
attributesBuilder.Append(RelativeToDir(cds.SyntaxTree.FilePath, godotProjectDir));
|
||||
attributesBuilder.Append(@""")]");
|
||||
}
|
||||
|
||||
string classNs = symbol.ContainingNamespace.Name;
|
||||
string className = symbol.Name;
|
||||
|
||||
var source = $@"using Godot;
|
||||
namespace {classNs}
|
||||
{{
|
||||
{attributesBuilder}
|
||||
partial class {className}
|
||||
{{
|
||||
}}
|
||||
}}
|
||||
";
|
||||
context.AddSource(classNs + "." + className + "_ScriptPath_Generated",
|
||||
SourceText.From(source, Encoding.UTF8));
|
||||
}
|
||||
|
||||
private static void AddScriptTypesAssemblyAttr(GeneratorExecutionContext context,
|
||||
Dictionary<INamedTypeSymbol, IEnumerable<ClassDeclarationSyntax>> godotClasses)
|
||||
{
|
||||
var sourceBuilder = new StringBuilder();
|
||||
|
||||
sourceBuilder.Append("[assembly:");
|
||||
sourceBuilder.Append(GodotClasses.AssemblyHasScriptsAttr);
|
||||
sourceBuilder.Append("(new System.Type[] {");
|
||||
|
||||
bool first = true;
|
||||
|
||||
foreach (var godotClass in godotClasses)
|
||||
{
|
||||
var qualifiedName = godotClass.Key.ToDisplayString(
|
||||
NullableFlowState.NotNull, SymbolDisplayFormat.FullyQualifiedFormat);
|
||||
if (!first)
|
||||
sourceBuilder.Append(", ");
|
||||
first = false;
|
||||
sourceBuilder.Append("typeof(");
|
||||
sourceBuilder.Append(qualifiedName);
|
||||
sourceBuilder.Append(")");
|
||||
}
|
||||
|
||||
sourceBuilder.Append("})]\n");
|
||||
|
||||
context.AddSource("AssemblyScriptTypes_Generated",
|
||||
SourceText.From(sourceBuilder.ToString(), Encoding.UTF8));
|
||||
}
|
||||
|
||||
public void Initialize(GeneratorInitializationContext context)
|
||||
{
|
||||
}
|
||||
|
||||
private static string RelativeToDir(string path, string dir)
|
||||
{
|
||||
// Make sure the directory ends with a path separator
|
||||
dir = Path.Combine(dir, " ").TrimEnd();
|
||||
|
||||
if (Path.DirectorySeparatorChar == '\\')
|
||||
dir = dir.Replace("/", "\\") + "\\";
|
||||
|
||||
var fullPath = new Uri(Path.GetFullPath(path), UriKind.Absolute);
|
||||
var relRoot = new Uri(Path.GetFullPath(dir), UriKind.Absolute);
|
||||
|
||||
// MakeRelativeUri converts spaces to %20, hence why we need UnescapeDataString
|
||||
return Uri.UnescapeDataString(relRoot.MakeRelativeUri(fullPath).ToString());
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,11 +1,5 @@
|
|||
using System;
|
||||
using GodotTools.Core;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using Microsoft.Build.Construction;
|
||||
using Microsoft.Build.Globbing;
|
||||
|
||||
namespace GodotTools.ProjectEditor
|
||||
{
|
||||
|
@ -31,47 +25,6 @@ namespace GodotTools.ProjectEditor
|
|||
return root != null ? new MSBuildProject(root) : null;
|
||||
}
|
||||
|
||||
private static List<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 new List<string>(files);
|
||||
}
|
||||
|
||||
// NOTE: Assumes auto-including items. Only used by the scripts metadata generator, which will be replaced with source generators in the future.
|
||||
public static IEnumerable<string> GetIncludeFiles(string projectPath, string itemType)
|
||||
{
|
||||
var excluded = new List<string>();
|
||||
var includedFiles = GetAllFilesRecursive(Path.GetDirectoryName(projectPath), "*.cs");
|
||||
|
||||
var root = ProjectRootElement.Open(projectPath);
|
||||
Debug.Assert(root != null);
|
||||
|
||||
foreach (var item in root.Items)
|
||||
{
|
||||
if (string.IsNullOrEmpty(item.Condition))
|
||||
continue;
|
||||
|
||||
if (item.ItemType != itemType)
|
||||
continue;
|
||||
|
||||
string normalizedRemove = item.Remove.NormalizePath();
|
||||
|
||||
var glob = MSBuildGlob.Parse(normalizedRemove);
|
||||
excluded.AddRange(includedFiles.Where(includedFile => glob.IsMatch(includedFile)));
|
||||
}
|
||||
|
||||
includedFiles.RemoveAll(f => excluded.Contains(f));
|
||||
|
||||
return includedFiles;
|
||||
}
|
||||
|
||||
public static void MigrateToProjectSdksStyle(MSBuildProject project, string projectName)
|
||||
{
|
||||
var origRoot = project.Root;
|
||||
|
|
|
@ -3,7 +3,6 @@
|
|||
|
||||
<Target Name="SetPropertiesForGenerateGodotNupkgsVersions">
|
||||
<PropertyGroup>
|
||||
<GodotNETSdkPackageVersionFile>$(SolutionDir)..\Godot.NET.Sdk\Godot.NET.Sdk\Godot.NET.Sdk_PackageVersion.txt</GodotNETSdkPackageVersionFile>
|
||||
<GeneratedGodotNupkgsVersionsFile>$(IntermediateOutputPath)GodotNupkgsVersions.g.cs</GeneratedGodotNupkgsVersionsFile>
|
||||
</PropertyGroup>
|
||||
</Target>
|
||||
|
@ -18,13 +17,14 @@
|
|||
</Target>
|
||||
<Target Name="_GenerateGodotNupkgsVersionsFile"
|
||||
DependsOnTargets="SetPropertiesForGenerateGodotNupkgsVersions"
|
||||
Inputs="$(MSBuildProjectFile);@(GodotNETSdkPackageVersionFile)"
|
||||
Inputs="$(MSBuildProjectFile);$(MSBuildThisFileDirectory);$(MSBuildProjectFile)\..\..\..\SdkPackageVersions.props"
|
||||
Outputs="$(GeneratedGodotNupkgsVersionsFile)">
|
||||
<PropertyGroup>
|
||||
<GenerateGodotNupkgsVersionsCode><![CDATA[
|
||||
namespace $(RootNamespace) {
|
||||
public class GeneratedGodotNupkgsVersions {
|
||||
public const string GodotNETSdk = "$([System.IO.File]::ReadAllText('$(GodotNETSdkPackageVersionFile)').Trim())"%3b
|
||||
public const string GodotNETSdk = "$(PackageVersion_Godot_NET_Sdk)"%3b
|
||||
public const string GodotSourceGenerators = "$(PackageVersion_Godot_SourceGenerators)"%3b
|
||||
}
|
||||
}
|
||||
]]></GenerateGodotNupkgsVersionsCode>
|
||||
|
|
|
@ -218,43 +218,12 @@ namespace GodotTools.Build
|
|||
Godot.GD.PushError("Failed to setup Godot NuGet Offline Packages: " + e.Message);
|
||||
}
|
||||
|
||||
GenerateEditorScriptMetadata();
|
||||
|
||||
if (GodotSharpEditor.Instance.SkipBuildBeforePlaying)
|
||||
return true; // Requested play from an external editor/IDE which already built the project
|
||||
|
||||
return BuildProjectBlocking("Debug");
|
||||
}
|
||||
|
||||
// NOTE: This will be replaced with C# source generators in 4.0
|
||||
public static void GenerateEditorScriptMetadata()
|
||||
{
|
||||
string editorScriptsMetadataPath = Path.Combine(GodotSharpDirs.ResMetadataDir, "scripts_metadata.editor");
|
||||
string playerScriptsMetadataPath = Path.Combine(GodotSharpDirs.ResMetadataDir, "scripts_metadata.editor_player");
|
||||
|
||||
CsProjOperations.GenerateScriptsMetadata(GodotSharpDirs.ProjectCsProjPath, editorScriptsMetadataPath);
|
||||
|
||||
if (!File.Exists(editorScriptsMetadataPath))
|
||||
return;
|
||||
|
||||
try
|
||||
{
|
||||
File.Copy(editorScriptsMetadataPath, playerScriptsMetadataPath);
|
||||
}
|
||||
catch (IOException e)
|
||||
{
|
||||
throw new IOException("Failed to copy scripts metadata file.", innerException: e);
|
||||
}
|
||||
}
|
||||
|
||||
// NOTE: This will be replaced with C# source generators in 4.0
|
||||
public static string GenerateExportedGameScriptMetadata(bool isDebug)
|
||||
{
|
||||
string scriptsMetadataPath = Path.Combine(GodotSharpDirs.ResMetadataDir, $"scripts_metadata.{(isDebug ? "debug" : "release")}");
|
||||
CsProjOperations.GenerateScriptsMetadata(GodotSharpDirs.ProjectCsProjPath, scriptsMetadataPath);
|
||||
return scriptsMetadataPath;
|
||||
}
|
||||
|
||||
public static void Initialize()
|
||||
{
|
||||
// Build tool settings
|
||||
|
|
|
@ -43,8 +43,6 @@ namespace GodotTools.Build
|
|||
GD.PushError("Failed to setup Godot NuGet Offline Packages: " + e.Message);
|
||||
}
|
||||
|
||||
BuildManager.GenerateEditorScriptMetadata();
|
||||
|
||||
if (!BuildManager.BuildProjectBlocking("Debug"))
|
||||
return; // Build failed
|
||||
|
||||
|
@ -74,8 +72,6 @@ namespace GodotTools.Build
|
|||
GD.PushError("Failed to setup Godot NuGet Offline Packages: " + e.Message);
|
||||
}
|
||||
|
||||
BuildManager.GenerateEditorScriptMetadata();
|
||||
|
||||
if (!BuildManager.BuildProjectBlocking("Debug", targets: new[] {"Rebuild"}))
|
||||
return; // Build failed
|
||||
|
||||
|
|
|
@ -290,7 +290,8 @@ namespace GodotTools.Build
|
|||
|
||||
private static readonly (string packageId, string packageVersion)[] PackagesToAdd =
|
||||
{
|
||||
("Godot.NET.Sdk", GeneratedGodotNupkgsVersions.GodotNETSdk)
|
||||
("Godot.NET.Sdk", GeneratedGodotNupkgsVersions.GodotNETSdk),
|
||||
("Godot.SourceGenerators", GeneratedGodotNupkgsVersions.GodotSourceGenerators),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,11 +1,6 @@
|
|||
using Godot;
|
||||
using System;
|
||||
using System.Linq;
|
||||
using Godot.Collections;
|
||||
using GodotTools.Internals;
|
||||
using GodotTools.ProjectEditor;
|
||||
using File = GodotTools.Utils.File;
|
||||
using Directory = GodotTools.Utils.Directory;
|
||||
|
||||
namespace GodotTools
|
||||
{
|
||||
|
@ -23,86 +18,5 @@ namespace GodotTools
|
|||
return string.Empty;
|
||||
}
|
||||
}
|
||||
|
||||
private static readonly DateTime Epoch = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc);
|
||||
|
||||
private static ulong ConvertToTimestamp(this DateTime value)
|
||||
{
|
||||
TimeSpan elapsedTime = value - Epoch;
|
||||
return (ulong)elapsedTime.TotalSeconds;
|
||||
}
|
||||
|
||||
private static bool TryParseFileMetadata(string includeFile, ulong modifiedTime, out Dictionary fileMetadata)
|
||||
{
|
||||
fileMetadata = null;
|
||||
|
||||
var parseError = ScriptClassParser.ParseFile(includeFile, out var classes, out string errorStr);
|
||||
|
||||
if (parseError != Error.Ok)
|
||||
{
|
||||
GD.PushError($"Failed to determine namespace and class for script: {includeFile}. Parse error: {errorStr ?? parseError.ToString()}");
|
||||
return false;
|
||||
}
|
||||
|
||||
string searchName = System.IO.Path.GetFileNameWithoutExtension(includeFile);
|
||||
|
||||
var firstMatch = classes.FirstOrDefault(classDecl =>
|
||||
classDecl.BaseCount != 0 && // If it doesn't inherit anything, it can't be a Godot.Object.
|
||||
classDecl.SearchName == searchName // Filter by the name we're looking for
|
||||
);
|
||||
|
||||
if (firstMatch == null)
|
||||
return false; // Not found
|
||||
|
||||
fileMetadata = new Dictionary
|
||||
{
|
||||
["modified_time"] = $"{modifiedTime}",
|
||||
["class"] = new Dictionary
|
||||
{
|
||||
["namespace"] = firstMatch.Namespace,
|
||||
["class_name"] = firstMatch.Name,
|
||||
["nested"] = firstMatch.Nested
|
||||
}
|
||||
};
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public static void GenerateScriptsMetadata(string projectPath, string outputPath)
|
||||
{
|
||||
var metadataDict = Internal.GetScriptsMetadataOrNothing().Duplicate();
|
||||
|
||||
bool IsUpToDate(string includeFile, ulong modifiedTime)
|
||||
{
|
||||
return metadataDict.TryGetValue(includeFile, out var oldFileVar) &&
|
||||
ulong.TryParse(((Dictionary)oldFileVar)["modified_time"] as string,
|
||||
out ulong storedModifiedTime) && storedModifiedTime == modifiedTime;
|
||||
}
|
||||
|
||||
var outdatedFiles = ProjectUtils.GetIncludeFiles(projectPath, "Compile")
|
||||
.Select(path => ("res://" + path).SimplifyGodotPath())
|
||||
.ToDictionary(path => path, path => File.GetLastWriteTime(path).ConvertToTimestamp())
|
||||
.Where(pair => !IsUpToDate(includeFile: pair.Key, modifiedTime: pair.Value))
|
||||
.ToArray();
|
||||
|
||||
foreach (var pair in outdatedFiles)
|
||||
{
|
||||
metadataDict.Remove(pair.Key);
|
||||
|
||||
string includeFile = pair.Key;
|
||||
|
||||
if (TryParseFileMetadata(includeFile, modifiedTime: pair.Value, out var fileMetadata))
|
||||
metadataDict[includeFile] = fileMetadata;
|
||||
}
|
||||
|
||||
string json = metadataDict.Count <= 0 ? "{}" : JSON.Print(metadataDict);
|
||||
|
||||
string baseDir = outputPath.GetBaseDir();
|
||||
|
||||
if (!Directory.Exists(baseDir))
|
||||
Directory.CreateDirectory(baseDir);
|
||||
|
||||
File.WriteAllText(outputPath, json);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -157,9 +157,6 @@ namespace GodotTools.Export
|
|||
|
||||
string buildConfig = isDebug ? "ExportDebug" : "ExportRelease";
|
||||
|
||||
string scriptsMetadataPath = BuildManager.GenerateExportedGameScriptMetadata(isDebug);
|
||||
AddFile(scriptsMetadataPath, scriptsMetadataPath);
|
||||
|
||||
if (!BuildManager.BuildProjectBlocking(buildConfig, platform: platform))
|
||||
throw new Exception("Failed to build project");
|
||||
|
||||
|
|
|
@ -1,7 +1,5 @@
|
|||
using System;
|
||||
using System.Runtime.CompilerServices;
|
||||
using Godot;
|
||||
using Godot.Collections;
|
||||
using GodotTools.IdeMessaging.Requests;
|
||||
|
||||
namespace GodotTools.Internals
|
||||
|
@ -42,9 +40,6 @@ namespace GodotTools.Internals
|
|||
|
||||
public static void EditorNodeShowScriptScreen() => internal_EditorNodeShowScriptScreen();
|
||||
|
||||
public static Dictionary<string, object> GetScriptsMetadataOrNothing() =>
|
||||
internal_GetScriptsMetadataOrNothing(typeof(Dictionary<string, object>));
|
||||
|
||||
public static string MonoWindowsInstallRoot => internal_MonoWindowsInstallRoot();
|
||||
|
||||
public static void EditorRunPlay() => internal_EditorRunPlay();
|
||||
|
@ -100,9 +95,6 @@ namespace GodotTools.Internals
|
|||
[MethodImpl(MethodImplOptions.InternalCall)]
|
||||
private static extern void internal_EditorNodeShowScriptScreen();
|
||||
|
||||
[MethodImpl(MethodImplOptions.InternalCall)]
|
||||
private static extern Dictionary<string, object> internal_GetScriptsMetadataOrNothing(Type dictType);
|
||||
|
||||
[MethodImpl(MethodImplOptions.InternalCall)]
|
||||
private static extern string internal_MonoWindowsInstallRoot();
|
||||
|
||||
|
|
|
@ -1,61 +0,0 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Runtime.CompilerServices;
|
||||
using Godot;
|
||||
using Godot.Collections;
|
||||
|
||||
namespace GodotTools.Internals
|
||||
{
|
||||
public static class ScriptClassParser
|
||||
{
|
||||
public class ClassDecl
|
||||
{
|
||||
public string Name { get; }
|
||||
public string Namespace { get; }
|
||||
public bool Nested { get; }
|
||||
public long BaseCount { get; }
|
||||
|
||||
public string SearchName => Nested ?
|
||||
Name.Substring(Name.LastIndexOf(".", StringComparison.Ordinal) + 1) :
|
||||
Name;
|
||||
|
||||
public ClassDecl(string name, string @namespace, bool nested, long baseCount)
|
||||
{
|
||||
Name = name;
|
||||
Namespace = @namespace;
|
||||
Nested = nested;
|
||||
BaseCount = baseCount;
|
||||
}
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.InternalCall)]
|
||||
private static extern Error internal_ParseFile(string filePath, Array<Dictionary> classes, out string errorStr);
|
||||
|
||||
public static Error ParseFile(string filePath, out IEnumerable<ClassDecl> classes, out string errorStr)
|
||||
{
|
||||
var classesArray = new Array<Dictionary>();
|
||||
var error = internal_ParseFile(filePath, classesArray, out errorStr);
|
||||
if (error != Error.Ok)
|
||||
{
|
||||
classes = null;
|
||||
return error;
|
||||
}
|
||||
|
||||
var classesList = new List<ClassDecl>();
|
||||
|
||||
foreach (var classDeclDict in classesArray)
|
||||
{
|
||||
classesList.Add(new ClassDecl(
|
||||
(string)classDeclDict["name"],
|
||||
(string)classDeclDict["namespace"],
|
||||
(bool)classDeclDict["nested"],
|
||||
(long)classDeclDict["base_count"]
|
||||
));
|
||||
}
|
||||
|
||||
classes = classesList;
|
||||
|
||||
return Error.Ok;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -49,7 +49,6 @@
|
|||
#include "../utils/osx_utils.h"
|
||||
#include "code_completion.h"
|
||||
#include "godotsharp_export.h"
|
||||
#include "script_class_parser.h"
|
||||
|
||||
MonoString *godot_icall_GodotSharpDirs_ResDataDir() {
|
||||
return GDMonoMarshal::mono_string_from_godot(GodotSharpDirs::get_res_data_dir());
|
||||
|
@ -172,36 +171,6 @@ MonoBoolean godot_icall_EditorProgress_Step(MonoString *p_task, MonoString *p_st
|
|||
return EditorNode::progress_task_step(task, state, p_step, (bool)p_force_refresh);
|
||||
}
|
||||
|
||||
int32_t godot_icall_ScriptClassParser_ParseFile(MonoString *p_filepath, MonoObject *p_classes, MonoString **r_error_str) {
|
||||
*r_error_str = nullptr;
|
||||
|
||||
String filepath = GDMonoMarshal::mono_string_to_godot(p_filepath);
|
||||
|
||||
ScriptClassParser scp;
|
||||
Error err = scp.parse_file(filepath);
|
||||
if (err == OK) {
|
||||
Array classes = GDMonoMarshal::mono_object_to_variant(p_classes);
|
||||
const Vector<ScriptClassParser::ClassDecl> &class_decls = scp.get_classes();
|
||||
|
||||
for (int i = 0; i < class_decls.size(); i++) {
|
||||
const ScriptClassParser::ClassDecl &classDecl = class_decls[i];
|
||||
|
||||
Dictionary classDeclDict;
|
||||
classDeclDict["name"] = classDecl.name;
|
||||
classDeclDict["namespace"] = classDecl.namespace_;
|
||||
classDeclDict["nested"] = classDecl.nested;
|
||||
classDeclDict["base_count"] = classDecl.base.size();
|
||||
classes.push_back(classDeclDict);
|
||||
}
|
||||
} else {
|
||||
String error_str = scp.get_error();
|
||||
if (!error_str.is_empty()) {
|
||||
*r_error_str = GDMonoMarshal::mono_string_from_godot(error_str);
|
||||
}
|
||||
}
|
||||
return err;
|
||||
}
|
||||
|
||||
uint32_t godot_icall_ExportPlugin_GetExportedAssemblyDependencies(MonoObject *p_initial_assemblies,
|
||||
MonoString *p_build_config, MonoString *p_custom_bcl_dir, MonoObject *r_assembly_dependencies) {
|
||||
Dictionary initial_dependencies = GDMonoMarshal::mono_object_to_variant(p_initial_assemblies);
|
||||
|
@ -289,18 +258,6 @@ void godot_icall_Internal_EditorNodeShowScriptScreen() {
|
|||
EditorNode::get_singleton()->call("_editor_select", EditorNode::EDITOR_SCRIPT);
|
||||
}
|
||||
|
||||
MonoObject *godot_icall_Internal_GetScriptsMetadataOrNothing(MonoReflectionType *p_dict_reftype) {
|
||||
Dictionary maybe_metadata = CSharpLanguage::get_singleton()->get_scripts_metadata_or_nothing();
|
||||
|
||||
MonoType *dict_type = mono_reflection_type_get_type(p_dict_reftype);
|
||||
|
||||
int type_encoding = mono_type_get_type(dict_type);
|
||||
MonoClass *type_class_raw = mono_class_from_mono_type(dict_type);
|
||||
GDMonoClass *type_class = GDMono::get_singleton()->get_class(type_class_raw);
|
||||
|
||||
return GDMonoMarshal::variant_to_mono_object(maybe_metadata, ManagedType(type_encoding, type_class));
|
||||
}
|
||||
|
||||
MonoString *godot_icall_Internal_MonoWindowsInstallRoot() {
|
||||
#ifdef WINDOWS_ENABLED
|
||||
String install_root_dir = GDMono::get_singleton()->get_mono_reg_info().install_root_dir;
|
||||
|
@ -395,9 +352,6 @@ void register_editor_internal_calls() {
|
|||
GDMonoUtils::add_internal_call("GodotTools.Internals.EditorProgress::internal_Dispose", godot_icall_EditorProgress_Dispose);
|
||||
GDMonoUtils::add_internal_call("GodotTools.Internals.EditorProgress::internal_Step", godot_icall_EditorProgress_Step);
|
||||
|
||||
// ScriptClassParser
|
||||
GDMonoUtils::add_internal_call("GodotTools.Internals.ScriptClassParser::internal_ParseFile", godot_icall_ScriptClassParser_ParseFile);
|
||||
|
||||
// ExportPlugin
|
||||
GDMonoUtils::add_internal_call("GodotTools.Export.ExportPlugin::internal_GetExportedAssemblyDependencies", godot_icall_ExportPlugin_GetExportedAssemblyDependencies);
|
||||
|
||||
|
@ -416,7 +370,6 @@ void register_editor_internal_calls() {
|
|||
GDMonoUtils::add_internal_call("GodotTools.Internals.Internal::internal_EditorDebuggerNodeReloadScripts", godot_icall_Internal_EditorDebuggerNodeReloadScripts);
|
||||
GDMonoUtils::add_internal_call("GodotTools.Internals.Internal::internal_ScriptEditorEdit", godot_icall_Internal_ScriptEditorEdit);
|
||||
GDMonoUtils::add_internal_call("GodotTools.Internals.Internal::internal_EditorNodeShowScriptScreen", godot_icall_Internal_EditorNodeShowScriptScreen);
|
||||
GDMonoUtils::add_internal_call("GodotTools.Internals.Internal::internal_GetScriptsMetadataOrNothing", godot_icall_Internal_GetScriptsMetadataOrNothing);
|
||||
GDMonoUtils::add_internal_call("GodotTools.Internals.Internal::internal_MonoWindowsInstallRoot", godot_icall_Internal_MonoWindowsInstallRoot);
|
||||
GDMonoUtils::add_internal_call("GodotTools.Internals.Internal::internal_EditorRunPlay", godot_icall_Internal_EditorRunPlay);
|
||||
GDMonoUtils::add_internal_call("GodotTools.Internals.Internal::internal_EditorRunStop", godot_icall_Internal_EditorRunStop);
|
||||
|
|
|
@ -1,753 +0,0 @@
|
|||
/*************************************************************************/
|
||||
/* script_class_parser.cpp */
|
||||
/*************************************************************************/
|
||||
/* This file is part of: */
|
||||
/* GODOT ENGINE */
|
||||
/* https://godotengine.org */
|
||||
/*************************************************************************/
|
||||
/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */
|
||||
/* Copyright (c) 2014-2021 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 "script_class_parser.h"
|
||||
|
||||
#include "core/os/os.h"
|
||||
#include "core/templates/map.h"
|
||||
|
||||
#include "../utils/string_utils.h"
|
||||
|
||||
const char *ScriptClassParser::token_names[ScriptClassParser::TK_MAX] = {
|
||||
"[",
|
||||
"]",
|
||||
"{",
|
||||
"}",
|
||||
".",
|
||||
":",
|
||||
",",
|
||||
"Symbol",
|
||||
"Identifier",
|
||||
"String",
|
||||
"Number",
|
||||
"<",
|
||||
">",
|
||||
"EOF",
|
||||
"Error"
|
||||
};
|
||||
|
||||
String ScriptClassParser::get_token_name(ScriptClassParser::Token p_token) {
|
||||
ERR_FAIL_INDEX_V(p_token, TK_MAX, "<error>");
|
||||
return token_names[p_token];
|
||||
}
|
||||
|
||||
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] == '@';
|
||||
|
||||
char32_t 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++;
|
||||
char32_t next = code[idx];
|
||||
if (next == 0) {
|
||||
error_str = "Unterminated String";
|
||||
error = true;
|
||||
return TK_ERROR;
|
||||
}
|
||||
char32_t 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 char32_t *rptr;
|
||||
double number = String::to_float(&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_generic_type_params() {
|
||||
Token tk;
|
||||
|
||||
while (true) {
|
||||
tk = get_token();
|
||||
|
||||
if (tk == TK_IDENTIFIER) {
|
||||
tk = get_token();
|
||||
// Type specifications can end with "?" to denote nullable types, such as IList<int?>
|
||||
if (tk == TK_SYMBOL) {
|
||||
tk = get_token();
|
||||
if (value.operator String() != "?") {
|
||||
error_str = "Expected " + get_token_name(TK_IDENTIFIER) + ", found unexpected symbol '" + value + "'";
|
||||
error = true;
|
||||
return ERR_PARSE_ERROR;
|
||||
}
|
||||
if (tk != TK_OP_GREATER && tk != TK_COMMA) {
|
||||
error_str = "Nullable type symbol '?' is only allowed after an identifier, but found " + get_token_name(tk) + " next.";
|
||||
error = true;
|
||||
return ERR_PARSE_ERROR;
|
||||
}
|
||||
}
|
||||
|
||||
if (tk == TK_PERIOD) {
|
||||
while (true) {
|
||||
tk = get_token();
|
||||
|
||||
if (tk != TK_IDENTIFIER) {
|
||||
error_str = "Expected " + get_token_name(TK_IDENTIFIER) + ", found: " + get_token_name(tk);
|
||||
error = true;
|
||||
return ERR_PARSE_ERROR;
|
||||
}
|
||||
|
||||
tk = get_token();
|
||||
|
||||
if (tk != TK_PERIOD) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (tk == TK_OP_LESS) {
|
||||
Error err = _skip_generic_type_params();
|
||||
if (err) {
|
||||
return err;
|
||||
}
|
||||
tk = get_token();
|
||||
}
|
||||
|
||||
if (tk == TK_OP_GREATER) {
|
||||
return OK;
|
||||
} else if (tk != TK_COMMA) {
|
||||
error_str = "Unexpected token: " + get_token_name(tk);
|
||||
error = true;
|
||||
return ERR_PARSE_ERROR;
|
||||
}
|
||||
} else if (tk == TK_OP_LESS) {
|
||||
error_str = "Expected " + get_token_name(TK_IDENTIFIER) + ", found " + get_token_name(TK_OP_LESS);
|
||||
error = true;
|
||||
return ERR_PARSE_ERROR;
|
||||
} else if (tk == TK_OP_GREATER) {
|
||||
return OK;
|
||||
} else {
|
||||
error_str = "Unexpected token: " + get_token_name(tk);
|
||||
error = true;
|
||||
return ERR_PARSE_ERROR;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Error ScriptClassParser::_parse_type_full_name(String &r_full_name) {
|
||||
Token tk = get_token();
|
||||
|
||||
if (tk != TK_IDENTIFIER) {
|
||||
error_str = "Expected " + get_token_name(TK_IDENTIFIER) + ", found: " + get_token_name(tk);
|
||||
error = true;
|
||||
return ERR_PARSE_ERROR;
|
||||
}
|
||||
|
||||
r_full_name += String(value);
|
||||
|
||||
if (code[idx] == '<') {
|
||||
idx++;
|
||||
|
||||
// We don't mind if the base is generic, but we skip it any ways since this information is not needed
|
||||
Error err = _skip_generic_type_params();
|
||||
if (err) {
|
||||
return err;
|
||||
}
|
||||
}
|
||||
|
||||
if (code[idx] != '.') { // We only want to take the next token if it's a period
|
||||
return OK;
|
||||
}
|
||||
|
||||
tk = get_token();
|
||||
|
||||
CRASH_COND(tk != TK_PERIOD); // Assertion
|
||||
|
||||
r_full_name += ".";
|
||||
|
||||
return _parse_type_full_name(r_full_name);
|
||||
}
|
||||
|
||||
Error ScriptClassParser::_parse_class_base(Vector<String> &r_base) {
|
||||
String name;
|
||||
|
||||
Error err = _parse_type_full_name(name);
|
||||
if (err) {
|
||||
return err;
|
||||
}
|
||||
|
||||
Token tk = get_token();
|
||||
|
||||
if (tk == TK_COMMA) {
|
||||
err = _parse_class_base(r_base);
|
||||
if (err) {
|
||||
return err;
|
||||
}
|
||||
} else if (tk == TK_IDENTIFIER && String(value) == "where") {
|
||||
err = _parse_type_constraints();
|
||||
if (err) {
|
||||
return err;
|
||||
}
|
||||
|
||||
// An open curly bracket was parsed by _parse_type_constraints, so we can exit
|
||||
} else if (tk == TK_CURLY_BRACKET_OPEN) {
|
||||
// we are finished when we hit the open curly bracket
|
||||
} else {
|
||||
error_str = "Unexpected token: " + get_token_name(tk);
|
||||
error = true;
|
||||
return ERR_PARSE_ERROR;
|
||||
}
|
||||
|
||||
r_base.push_back(name);
|
||||
|
||||
return OK;
|
||||
}
|
||||
|
||||
Error ScriptClassParser::_parse_type_constraints() {
|
||||
Token tk = get_token();
|
||||
if (tk != TK_IDENTIFIER) {
|
||||
error_str = "Unexpected token: " + get_token_name(tk);
|
||||
error = true;
|
||||
return ERR_PARSE_ERROR;
|
||||
}
|
||||
|
||||
tk = get_token();
|
||||
if (tk != TK_COLON) {
|
||||
error_str = "Unexpected token: " + get_token_name(tk);
|
||||
error = true;
|
||||
return ERR_PARSE_ERROR;
|
||||
}
|
||||
|
||||
while (true) {
|
||||
tk = get_token();
|
||||
if (tk == TK_IDENTIFIER) {
|
||||
if (String(value) == "where") {
|
||||
return _parse_type_constraints();
|
||||
}
|
||||
|
||||
tk = get_token();
|
||||
if (tk == TK_PERIOD) {
|
||||
while (true) {
|
||||
tk = get_token();
|
||||
|
||||
if (tk != TK_IDENTIFIER) {
|
||||
error_str = "Expected " + get_token_name(TK_IDENTIFIER) + ", found: " + get_token_name(tk);
|
||||
error = true;
|
||||
return ERR_PARSE_ERROR;
|
||||
}
|
||||
|
||||
tk = get_token();
|
||||
|
||||
if (tk != TK_PERIOD) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (tk == TK_COMMA) {
|
||||
continue;
|
||||
} else if (tk == TK_IDENTIFIER && String(value) == "where") {
|
||||
return _parse_type_constraints();
|
||||
} else if (tk == TK_SYMBOL && String(value) == "(") {
|
||||
tk = get_token();
|
||||
if (tk != TK_SYMBOL || String(value) != ")") {
|
||||
error_str = "Unexpected token: " + get_token_name(tk);
|
||||
error = true;
|
||||
return ERR_PARSE_ERROR;
|
||||
}
|
||||
} else if (tk == TK_OP_LESS) {
|
||||
Error err = _skip_generic_type_params();
|
||||
if (err) {
|
||||
return err;
|
||||
}
|
||||
} else if (tk == TK_CURLY_BRACKET_OPEN) {
|
||||
return OK;
|
||||
} else {
|
||||
error_str = "Unexpected token: " + get_token_name(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: " + get_token_name(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: " + get_token_name(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) {
|
||||
String identifier = value;
|
||||
if (tk == TK_IDENTIFIER && (identifier == "class" || identifier == "struct")) {
|
||||
bool is_class = identifier == "class";
|
||||
|
||||
tk = get_token();
|
||||
|
||||
if (tk == TK_IDENTIFIER) {
|
||||
String name = value;
|
||||
int at_level = 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_generic_type_params();
|
||||
if (err) {
|
||||
return err;
|
||||
}
|
||||
} else if (tk == TK_IDENTIFIER && String(value) == "where") {
|
||||
Error err = _parse_type_constraints();
|
||||
if (err) {
|
||||
return err;
|
||||
}
|
||||
|
||||
// An open curly bracket was parsed by _parse_type_constraints, so we can exit
|
||||
curly_stack++;
|
||||
type_curly_stack++;
|
||||
break;
|
||||
} else {
|
||||
error_str = "Unexpected token: " + get_token_name(tk);
|
||||
error = true;
|
||||
return ERR_PARSE_ERROR;
|
||||
}
|
||||
}
|
||||
|
||||
NameDecl name_decl;
|
||||
name_decl.name = name;
|
||||
name_decl.type = is_class ? NameDecl::CLASS_DECL : NameDecl::STRUCT_DECL;
|
||||
name_stack[at_level] = name_decl;
|
||||
|
||||
if (is_class) {
|
||||
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("Ignoring generic class declaration: %s\n", full_name.utf8().get_data());
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if (tk == TK_IDENTIFIER && identifier == "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;
|
||||
}
|
||||
|
||||
static String get_preprocessor_directive(const String &p_line, int p_from) {
|
||||
CRASH_COND(p_line[p_from] != '#');
|
||||
p_from++;
|
||||
int i = p_from;
|
||||
while (i < p_line.length() && (p_line[i] == '_' || (p_line[i] >= 'A' && p_line[i] <= 'Z') ||
|
||||
(p_line[i] >= 'a' && p_line[i] <= 'z') || p_line[i] > 127)) {
|
||||
i++;
|
||||
}
|
||||
return p_line.substr(p_from, i - p_from);
|
||||
}
|
||||
|
||||
static void run_dummy_preprocessor(String &r_source, const String &p_filepath) {
|
||||
Vector<String> lines = r_source.split("\n", /* p_allow_empty: */ true);
|
||||
|
||||
bool *include_lines = memnew_arr(bool, lines.size());
|
||||
|
||||
int if_level = -1;
|
||||
Vector<bool> is_branch_being_compiled;
|
||||
|
||||
for (int i = 0; i < lines.size(); i++) {
|
||||
const String &line = lines[i];
|
||||
|
||||
const int line_len = line.length();
|
||||
|
||||
int j;
|
||||
for (j = 0; j < line_len; j++) {
|
||||
if (line[j] != ' ' && line[j] != '\t') {
|
||||
if (line[j] == '#') {
|
||||
// First non-whitespace char of the line is '#'
|
||||
include_lines[i] = false;
|
||||
|
||||
String directive = get_preprocessor_directive(line, j);
|
||||
|
||||
if (directive == "if") {
|
||||
if_level++;
|
||||
is_branch_being_compiled.push_back(if_level == 0 || is_branch_being_compiled[if_level - 1]);
|
||||
} else if (directive == "elif") {
|
||||
ERR_CONTINUE_MSG(if_level == -1, "Found unexpected '#elif' directive. File: '" + p_filepath + "'.");
|
||||
is_branch_being_compiled.write[if_level] = false;
|
||||
} else if (directive == "else") {
|
||||
ERR_CONTINUE_MSG(if_level == -1, "Found unexpected '#else' directive. File: '" + p_filepath + "'.");
|
||||
is_branch_being_compiled.write[if_level] = false;
|
||||
} else if (directive == "endif") {
|
||||
ERR_CONTINUE_MSG(if_level == -1, "Found unexpected '#endif' directive. File: '" + p_filepath + "'.");
|
||||
is_branch_being_compiled.remove(if_level);
|
||||
if_level--;
|
||||
}
|
||||
|
||||
break;
|
||||
} else {
|
||||
// First non-whitespace char of the line is not '#'
|
||||
include_lines[i] = if_level == -1 || is_branch_being_compiled[if_level];
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (j == line_len) {
|
||||
// Loop ended without finding a non-whitespace character.
|
||||
// Either the line was empty or it only contained whitespaces.
|
||||
include_lines[i] = if_level == -1 || is_branch_being_compiled[if_level];
|
||||
}
|
||||
}
|
||||
|
||||
r_source.clear();
|
||||
|
||||
// Custom join ignoring lines removed by the preprocessor
|
||||
for (int i = 0; i < lines.size(); i++) {
|
||||
if (i > 0 && include_lines[i - 1]) {
|
||||
r_source += '\n';
|
||||
}
|
||||
|
||||
if (include_lines[i]) {
|
||||
r_source += lines[i];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Error ScriptClassParser::parse_file(const String &p_filepath) {
|
||||
String source;
|
||||
|
||||
Error ferr = read_all_file_utf8(p_filepath, source);
|
||||
|
||||
ERR_FAIL_COND_V_MSG(ferr != OK, ferr,
|
||||
ferr == ERR_INVALID_DATA ?
|
||||
"File '" + p_filepath + "' contains invalid unicode (UTF-8), so it was not loaded."
|
||||
" Please ensure that scripts are saved in valid UTF-8 unicode." :
|
||||
"Failed to read file: '" + p_filepath + "'.");
|
||||
|
||||
run_dummy_preprocessor(source, p_filepath);
|
||||
|
||||
return parse(source);
|
||||
}
|
||||
|
||||
String ScriptClassParser::get_error() {
|
||||
return error_str;
|
||||
}
|
||||
|
||||
Vector<ScriptClassParser::ClassDecl> ScriptClassParser::get_classes() {
|
||||
return classes;
|
||||
}
|
|
@ -1,108 +0,0 @@
|
|||
/*************************************************************************/
|
||||
/* script_class_parser.h */
|
||||
/*************************************************************************/
|
||||
/* This file is part of: */
|
||||
/* GODOT ENGINE */
|
||||
/* https://godotengine.org */
|
||||
/*************************************************************************/
|
||||
/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */
|
||||
/* Copyright (c) 2014-2021 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 SCRIPT_CLASS_PARSER_H
|
||||
#define SCRIPT_CLASS_PARSER_H
|
||||
|
||||
#include "core/string/ustring.h"
|
||||
#include "core/templates/vector.h"
|
||||
#include "core/variant/variant.h"
|
||||
|
||||
class ScriptClassParser {
|
||||
public:
|
||||
struct NameDecl {
|
||||
enum Type {
|
||||
NAMESPACE_DECL,
|
||||
CLASS_DECL,
|
||||
STRUCT_DECL
|
||||
};
|
||||
|
||||
String name;
|
||||
Type type = NAMESPACE_DECL;
|
||||
};
|
||||
|
||||
struct ClassDecl {
|
||||
String name;
|
||||
String namespace_;
|
||||
Vector<String> base;
|
||||
bool nested = false;
|
||||
};
|
||||
|
||||
private:
|
||||
String code;
|
||||
int idx = 0;
|
||||
int line = 0;
|
||||
String error_str;
|
||||
bool error = false;
|
||||
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,
|
||||
TK_MAX
|
||||
};
|
||||
|
||||
static const char *token_names[TK_MAX];
|
||||
static String get_token_name(Token p_token);
|
||||
|
||||
Token get_token();
|
||||
|
||||
Error _skip_generic_type_params();
|
||||
|
||||
Error _parse_type_full_name(String &r_full_name);
|
||||
Error _parse_class_base(Vector<String> &r_base);
|
||||
Error _parse_type_constraints();
|
||||
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
|
|
@ -0,0 +1,22 @@
|
|||
using System;
|
||||
|
||||
namespace Godot
|
||||
{
|
||||
[AttributeUsage(AttributeTargets.Assembly)]
|
||||
public class AssemblyHasScriptsAttribute : Attribute
|
||||
{
|
||||
private readonly bool requiresLookup;
|
||||
private readonly System.Type[] scriptTypes;
|
||||
|
||||
public AssemblyHasScriptsAttribute()
|
||||
{
|
||||
requiresLookup = true;
|
||||
}
|
||||
|
||||
public AssemblyHasScriptsAttribute(System.Type[] scriptTypes)
|
||||
{
|
||||
requiresLookup = false;
|
||||
this.scriptTypes = scriptTypes;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,9 @@
|
|||
using System;
|
||||
|
||||
namespace Godot
|
||||
{
|
||||
[AttributeUsage(AttributeTargets.Class)]
|
||||
public class DisableGodotGeneratorsAttribute : Attribute
|
||||
{
|
||||
}
|
||||
}
|
|
@ -0,0 +1,15 @@
|
|||
using System;
|
||||
|
||||
namespace Godot
|
||||
{
|
||||
[AttributeUsage(AttributeTargets.Class, AllowMultiple = true)]
|
||||
public class ScriptPathAttribute : Attribute
|
||||
{
|
||||
private string path;
|
||||
|
||||
public ScriptPathAttribute(string path)
|
||||
{
|
||||
this.path = path;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -14,9 +14,12 @@
|
|||
<ItemGroup>
|
||||
<Compile Include="Core\AABB.cs" />
|
||||
<Compile Include="Core\Array.cs" />
|
||||
<Compile Include="Core\Attributes\AssemblyHasScriptsAttribute.cs" />
|
||||
<Compile Include="Core\Attributes\DisableGodotGeneratorsAttribute.cs" />
|
||||
<Compile Include="Core\Attributes\ExportAttribute.cs" />
|
||||
<Compile Include="Core\Attributes\GodotMethodAttribute.cs" />
|
||||
<Compile Include="Core\Attributes\RPCAttributes.cs" />
|
||||
<Compile Include="Core\Attributes\ScriptPathAttribute.cs" />
|
||||
<Compile Include="Core\Attributes\SignalAttribute.cs" />
|
||||
<Compile Include="Core\Attributes\ToolAttribute.cs" />
|
||||
<Compile Include="Core\Basis.cs" />
|
||||
|
|
|
@ -1006,6 +1006,7 @@ bool GDMono::_load_project_assembly() {
|
|||
|
||||
if (success) {
|
||||
mono_assembly_set_main(project_assembly->get_assembly());
|
||||
CSharpLanguage::get_singleton()->lookup_scripts_in_assembly(project_assembly);
|
||||
}
|
||||
|
||||
return success;
|
||||
|
|
|
@ -345,6 +345,45 @@ String GDMonoAssembly::get_path() const {
|
|||
return String::utf8(mono_image_get_filename(image));
|
||||
}
|
||||
|
||||
bool GDMonoAssembly::has_attribute(GDMonoClass *p_attr_class) {
|
||||
#ifdef DEBUG_ENABLED
|
||||
ERR_FAIL_NULL_V(p_attr_class, false);
|
||||
#endif
|
||||
|
||||
if (!attrs_fetched) {
|
||||
fetch_attributes();
|
||||
}
|
||||
|
||||
if (!attributes) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return mono_custom_attrs_has_attr(attributes, p_attr_class->get_mono_ptr());
|
||||
}
|
||||
|
||||
MonoObject *GDMonoAssembly::get_attribute(GDMonoClass *p_attr_class) {
|
||||
#ifdef DEBUG_ENABLED
|
||||
ERR_FAIL_NULL_V(p_attr_class, nullptr);
|
||||
#endif
|
||||
|
||||
if (!attrs_fetched) {
|
||||
fetch_attributes();
|
||||
}
|
||||
|
||||
if (!attributes) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
return mono_custom_attrs_get_attr(attributes, p_attr_class->get_mono_ptr());
|
||||
}
|
||||
|
||||
void GDMonoAssembly::fetch_attributes() {
|
||||
ERR_FAIL_COND(attributes != nullptr);
|
||||
|
||||
attributes = mono_custom_attrs_from_assembly(assembly);
|
||||
attrs_fetched = true;
|
||||
}
|
||||
|
||||
GDMonoClass *GDMonoAssembly::get_class(const StringName &p_namespace, const StringName &p_name) {
|
||||
ERR_FAIL_NULL_V(image, nullptr);
|
||||
|
||||
|
@ -390,70 +429,6 @@ GDMonoClass *GDMonoAssembly::get_class(MonoClass *p_mono_class) {
|
|||
return wrapped_class;
|
||||
}
|
||||
|
||||
GDMonoClass *GDMonoAssembly::get_object_derived_class(const StringName &p_class) {
|
||||
GDMonoClass *match = nullptr;
|
||||
|
||||
if (gdobject_class_cache_updated) {
|
||||
Map<StringName, GDMonoClass *>::Element *result = gdobject_class_cache.find(p_class);
|
||||
|
||||
if (result) {
|
||||
match = result->get();
|
||||
}
|
||||
} else {
|
||||
List<GDMonoClass *> nested_classes;
|
||||
|
||||
int rows = mono_image_get_table_rows(image, MONO_TABLE_TYPEDEF);
|
||||
|
||||
for (int i = 1; i < rows; i++) {
|
||||
MonoClass *mono_class = mono_class_get(image, (i + 1) | MONO_TOKEN_TYPE_DEF);
|
||||
|
||||
if (!mono_class_is_assignable_from(CACHED_CLASS_RAW(GodotObject), mono_class)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
GDMonoClass *current = get_class(mono_class);
|
||||
|
||||
if (!current) {
|
||||
continue;
|
||||
}
|
||||
|
||||
nested_classes.push_back(current);
|
||||
|
||||
if (!match && current->get_name() == p_class) {
|
||||
match = current;
|
||||
}
|
||||
|
||||
while (!nested_classes.is_empty()) {
|
||||
GDMonoClass *current_nested = nested_classes.front()->get();
|
||||
nested_classes.pop_front();
|
||||
|
||||
void *iter = nullptr;
|
||||
|
||||
while (true) {
|
||||
MonoClass *raw_nested = mono_class_get_nested_types(current_nested->get_mono_ptr(), &iter);
|
||||
|
||||
if (!raw_nested) {
|
||||
break;
|
||||
}
|
||||
|
||||
GDMonoClass *nested_class = get_class(raw_nested);
|
||||
|
||||
if (nested_class) {
|
||||
gdobject_class_cache.insert(nested_class->get_name(), nested_class);
|
||||
nested_classes.push_back(nested_class);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
gdobject_class_cache.insert(current->get_name(), current);
|
||||
}
|
||||
|
||||
gdobject_class_cache_updated = true;
|
||||
}
|
||||
|
||||
return match;
|
||||
}
|
||||
|
||||
GDMonoAssembly *GDMonoAssembly::load(const String &p_name, MonoAssemblyName *p_aname, bool p_refonly, const Vector<String> &p_search_dirs) {
|
||||
if (GDMono::get_singleton()->get_corlib_assembly() && (p_name == "mscorlib" || p_name == "mscorlib.dll")) {
|
||||
return GDMono::get_singleton()->get_corlib_assembly();
|
||||
|
|
|
@ -71,13 +71,13 @@ class GDMonoAssembly {
|
|||
MonoImage *image;
|
||||
MonoAssembly *assembly;
|
||||
|
||||
bool attrs_fetched = false;
|
||||
MonoCustomAttrInfo *attributes = nullptr;
|
||||
|
||||
#ifdef GD_MONO_HOT_RELOAD
|
||||
uint64_t modified_time = 0;
|
||||
#endif
|
||||
|
||||
bool gdobject_class_cache_updated = false;
|
||||
Map<StringName, GDMonoClass *> gdobject_class_cache;
|
||||
|
||||
HashMap<ClassKey, GDMonoClass *, ClassKey::Hasher> cached_classes;
|
||||
Map<MonoClass *, GDMonoClass *> cached_raw;
|
||||
|
||||
|
@ -111,11 +111,14 @@ public:
|
|||
|
||||
String get_path() const;
|
||||
|
||||
bool has_attribute(GDMonoClass *p_attr_class);
|
||||
MonoObject *get_attribute(GDMonoClass *p_attr_class);
|
||||
|
||||
void fetch_attributes();
|
||||
|
||||
GDMonoClass *get_class(const StringName &p_namespace, const StringName &p_name);
|
||||
GDMonoClass *get_class(MonoClass *p_mono_class);
|
||||
|
||||
GDMonoClass *get_object_derived_class(const StringName &p_class);
|
||||
|
||||
static String find_assembly(const String &p_name);
|
||||
|
||||
static void fill_search_dirs(Vector<String> &r_search_dirs, const String &p_custom_config = String(), const String &p_custom_bcl_dir = String());
|
||||
|
|
|
@ -148,6 +148,11 @@ void CachedData::clear_godot_api_cache() {
|
|||
class_PuppetSyncAttribute = nullptr;
|
||||
class_GodotMethodAttribute = nullptr;
|
||||
field_GodotMethodAttribute_methodName = nullptr;
|
||||
class_ScriptPathAttribute = nullptr;
|
||||
field_ScriptPathAttribute_path = nullptr;
|
||||
class_AssemblyHasScriptsAttribute = nullptr;
|
||||
field_AssemblyHasScriptsAttribute_requiresLookup = nullptr;
|
||||
field_AssemblyHasScriptsAttribute_scriptTypes = nullptr;
|
||||
|
||||
field_GodotObject_ptr = nullptr;
|
||||
field_StringName_ptr = nullptr;
|
||||
|
@ -272,6 +277,11 @@ void update_godot_api_cache() {
|
|||
CACHE_CLASS_AND_CHECK(PuppetSyncAttribute, GODOT_API_CLASS(PuppetSyncAttribute));
|
||||
CACHE_CLASS_AND_CHECK(GodotMethodAttribute, GODOT_API_CLASS(GodotMethodAttribute));
|
||||
CACHE_FIELD_AND_CHECK(GodotMethodAttribute, methodName, CACHED_CLASS(GodotMethodAttribute)->get_field("methodName"));
|
||||
CACHE_CLASS_AND_CHECK(ScriptPathAttribute, GODOT_API_CLASS(ScriptPathAttribute));
|
||||
CACHE_FIELD_AND_CHECK(ScriptPathAttribute, path, CACHED_CLASS(ScriptPathAttribute)->get_field("path"));
|
||||
CACHE_CLASS_AND_CHECK(AssemblyHasScriptsAttribute, GODOT_API_CLASS(AssemblyHasScriptsAttribute));
|
||||
CACHE_FIELD_AND_CHECK(AssemblyHasScriptsAttribute, requiresLookup, CACHED_CLASS(AssemblyHasScriptsAttribute)->get_field("requiresLookup"));
|
||||
CACHE_FIELD_AND_CHECK(AssemblyHasScriptsAttribute, scriptTypes, CACHED_CLASS(AssemblyHasScriptsAttribute)->get_field("scriptTypes"));
|
||||
|
||||
CACHE_FIELD_AND_CHECK(GodotObject, ptr, CACHED_CLASS(GodotObject)->get_field(BINDINGS_PTR_FIELD));
|
||||
CACHE_FIELD_AND_CHECK(StringName, ptr, CACHED_CLASS(StringName)->get_field(BINDINGS_PTR_FIELD));
|
||||
|
|
|
@ -119,6 +119,11 @@ struct CachedData {
|
|||
GDMonoClass *class_PuppetSyncAttribute;
|
||||
GDMonoClass *class_GodotMethodAttribute;
|
||||
GDMonoField *field_GodotMethodAttribute_methodName;
|
||||
GDMonoClass *class_ScriptPathAttribute;
|
||||
GDMonoField *field_ScriptPathAttribute_path;
|
||||
GDMonoClass *class_AssemblyHasScriptsAttribute;
|
||||
GDMonoField *field_AssemblyHasScriptsAttribute_requiresLookup;
|
||||
GDMonoField *field_AssemblyHasScriptsAttribute_scriptTypes;
|
||||
|
||||
GDMonoField *field_GodotObject_ptr;
|
||||
GDMonoField *field_StringName_ptr;
|
||||
|
|
Loading…
Reference in a new issue