diff options
author | Ignacio Etcheverry <ignalfonsore@gmail.com> | 2021-03-06 00:12:42 +0100 |
---|---|---|
committer | Ignacio Etcheverry <ignalfonsore@gmail.com> | 2021-03-06 21:50:32 +0100 |
commit | e2afe700f62aaa503daed0519ff88b0aa1a559c1 (patch) | |
tree | 8bedb525e7a671531138d5f15ad2b27c97069a58 /modules/mono/editor | |
parent | d4191e48c5f4af920e87a70fe3c5ae9219a3332b (diff) |
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.
Diffstat (limited to 'modules/mono/editor')
27 files changed, 434 insertions, 1200 deletions
diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.NET.Sdk.sln b/modules/mono/editor/Godot.NET.Sdk/Godot.NET.Sdk.sln index 56c0cb7703..d1868f52ef 100644 --- a/modules/mono/editor/Godot.NET.Sdk/Godot.NET.Sdk.sln +++ b/modules/mono/editor/Godot.NET.Sdk/Godot.NET.Sdk.sln @@ -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 diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.NET.Sdk/Godot.NET.Sdk.csproj b/modules/mono/editor/Godot.NET.Sdk/Godot.NET.Sdk/Godot.NET.Sdk.csproj index 8304d9e321..ef8add0ba8 100644 --- a/modules/mono/editor/Godot.NET.Sdk/Godot.NET.Sdk/Godot.NET.Sdk.csproj +++ b/modules/mono/editor/Godot.NET.Sdk/Godot.NET.Sdk/Godot.NET.Sdk.csproj @@ -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> - </PropertyGroup> - <PropertyGroup> - <NuspecFile>Godot.NET.Sdk.nuspec</NuspecFile> - <GenerateNuspecDependsOn>$(GenerateNuspecDependsOn);SetNuSpecProperties</GenerateNuspecDependsOn> + <!-- Exclude target framework from the package dependencies as we don't include the build output --> + <SuppressDependenciesWhenPacking>true</SuppressDependenciesWhenPacking> + <IncludeBuildOutput>false</IncludeBuildOutput> </PropertyGroup> - <Target Name="ReadGodotNETSdkVersion" BeforeTargets="BeforeBuild;BeforeRebuild;CoreCompile"> - <PropertyGroup> - <PackageVersion>$([System.IO.File]::ReadAllText('$(ProjectDir)Godot.NET.Sdk_PackageVersion.txt').Trim())</PackageVersion> - </PropertyGroup> - </Target> + <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="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> diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.NET.Sdk/Godot.NET.Sdk.nuspec b/modules/mono/editor/Godot.NET.Sdk/Godot.NET.Sdk/Godot.NET.Sdk.nuspec deleted file mode 100644 index ba68a4da43..0000000000 --- a/modules/mono/editor/Godot.NET.Sdk/Godot.NET.Sdk/Godot.NET.Sdk.nuspec +++ /dev/null @@ -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> diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.NET.Sdk/Godot.NET.Sdk_PackageVersion.txt b/modules/mono/editor/Godot.NET.Sdk/Godot.NET.Sdk/Godot.NET.Sdk_PackageVersion.txt deleted file mode 100644 index 34749489b9..0000000000 --- a/modules/mono/editor/Godot.NET.Sdk/Godot.NET.Sdk/Godot.NET.Sdk_PackageVersion.txt +++ /dev/null @@ -1 +0,0 @@ -4.0.0-dev3 diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.NET.Sdk/Sdk/Sdk.props b/modules/mono/editor/Godot.NET.Sdk/Godot.NET.Sdk/Sdk/Sdk.props index 5febcf3175..0128f5c706 100644 --- a/modules/mono/editor/Godot.NET.Sdk/Godot.NET.Sdk/Sdk/Sdk.props +++ b/modules/mono/editor/Godot.NET.Sdk/Godot.NET.Sdk/Sdk/Sdk.props @@ -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: diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.NET.Sdk/Sdk/Sdk.targets b/modules/mono/editor/Godot.NET.Sdk/Godot.NET.Sdk/Sdk/Sdk.targets index f5afd75505..92e299d2f3 100644 --- a/modules/mono/editor/Godot.NET.Sdk/Godot.NET.Sdk/Sdk/Sdk.targets +++ b/modules/mono/editor/Godot.NET.Sdk/Godot.NET.Sdk/Sdk/Sdk.targets @@ -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> diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Sample/Bar.cs b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Sample/Bar.cs new file mode 100644 index 0000000000..5eaebc4474 --- /dev/null +++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Sample/Bar.cs @@ -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 + { + } +} diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Sample/Foo.cs b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Sample/Foo.cs new file mode 100644 index 0000000000..21a5bfe560 --- /dev/null +++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Sample/Foo.cs @@ -0,0 +1,11 @@ +namespace Godot.SourceGenerators.Sample +{ + partial class Foo : Godot.Object + { + } + + // Foo again in the same file + partial class Foo + { + } +} diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Sample/Godot.SourceGenerators.Sample.csproj b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Sample/Godot.SourceGenerators.Sample.csproj new file mode 100644 index 0000000000..24f7909861 --- /dev/null +++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Sample/Godot.SourceGenerators.Sample.csproj @@ -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> diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/Common.cs b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/Common.cs new file mode 100644 index 0000000000..4867c986e6 --- /dev/null +++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/Common.cs @@ -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)); + } + } +} diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ExtensionMethods.cs b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ExtensionMethods.cs new file mode 100644 index 0000000000..c3e74822d5 --- /dev/null +++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ExtensionMethods.cs @@ -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); + } +} diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/Godot.SourceGenerators.csproj b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/Godot.SourceGenerators.csproj new file mode 100644 index 0000000000..224d7e5b5a --- /dev/null +++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/Godot.SourceGenerators.csproj @@ -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> diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/Godot.SourceGenerators.props b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/Godot.SourceGenerators.props new file mode 100644 index 0000000000..f9b47ad5b1 --- /dev/null +++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/Godot.SourceGenerators.props @@ -0,0 +1,7 @@ +<Project> + <ItemGroup> + <!-- $(GodotProjectDir) is defined by Godot.NET.Sdk --> + <CompilerVisibleProperty Include="GodotProjectDir" /> + <CompilerVisibleProperty Include="GodotScriptPathAttributeGenerator" /> + </ItemGroup> +</Project> diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/GodotClasses.cs b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/GodotClasses.cs new file mode 100644 index 0000000000..29e41d155a --- /dev/null +++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/GodotClasses.cs @@ -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"; + } +} diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptPathAttributeGenerator.cs b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptPathAttributeGenerator.cs new file mode 100644 index 0000000000..150e59e414 --- /dev/null +++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptPathAttributeGenerator.cs @@ -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()); + } + } +} diff --git a/modules/mono/editor/GodotTools/GodotTools.ProjectEditor/ProjectUtils.cs b/modules/mono/editor/GodotTools/GodotTools.ProjectEditor/ProjectUtils.cs index 4e2c0f17cc..cdac9acb25 100644 --- a/modules/mono/editor/GodotTools/GodotTools.ProjectEditor/ProjectUtils.cs +++ b/modules/mono/editor/GodotTools/GodotTools.ProjectEditor/ProjectUtils.cs @@ -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; diff --git a/modules/mono/editor/GodotTools/GodotTools.Shared/GenerateGodotNupkgsVersions.targets b/modules/mono/editor/GodotTools/GodotTools.Shared/GenerateGodotNupkgsVersions.targets index 1d382dcb43..aab2d73bdd 100644 --- a/modules/mono/editor/GodotTools/GodotTools.Shared/GenerateGodotNupkgsVersions.targets +++ b/modules/mono/editor/GodotTools/GodotTools.Shared/GenerateGodotNupkgsVersions.targets @@ -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> diff --git a/modules/mono/editor/GodotTools/GodotTools/Build/BuildManager.cs b/modules/mono/editor/GodotTools/GodotTools/Build/BuildManager.cs index b96b0c8175..2b6f972529 100644 --- a/modules/mono/editor/GodotTools/GodotTools/Build/BuildManager.cs +++ b/modules/mono/editor/GodotTools/GodotTools/Build/BuildManager.cs @@ -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 diff --git a/modules/mono/editor/GodotTools/GodotTools/Build/MSBuildPanel.cs b/modules/mono/editor/GodotTools/GodotTools/Build/MSBuildPanel.cs index 708ec73454..ed69c2b833 100644 --- a/modules/mono/editor/GodotTools/GodotTools/Build/MSBuildPanel.cs +++ b/modules/mono/editor/GodotTools/GodotTools/Build/MSBuildPanel.cs @@ -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 diff --git a/modules/mono/editor/GodotTools/GodotTools/Build/NuGetUtils.cs b/modules/mono/editor/GodotTools/GodotTools/Build/NuGetUtils.cs index 793ef7fd71..16dd1c8c6b 100644 --- a/modules/mono/editor/GodotTools/GodotTools/Build/NuGetUtils.cs +++ b/modules/mono/editor/GodotTools/GodotTools/Build/NuGetUtils.cs @@ -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), }; } } diff --git a/modules/mono/editor/GodotTools/GodotTools/CsProjOperations.cs b/modules/mono/editor/GodotTools/GodotTools/CsProjOperations.cs index 1d800b8151..e43f10804d 100644 --- a/modules/mono/editor/GodotTools/GodotTools/CsProjOperations.cs +++ b/modules/mono/editor/GodotTools/GodotTools/CsProjOperations.cs @@ -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); - } } } diff --git a/modules/mono/editor/GodotTools/GodotTools/Export/ExportPlugin.cs b/modules/mono/editor/GodotTools/GodotTools/Export/ExportPlugin.cs index e9bb701562..270be8b6bf 100644 --- a/modules/mono/editor/GodotTools/GodotTools/Export/ExportPlugin.cs +++ b/modules/mono/editor/GodotTools/GodotTools/Export/ExportPlugin.cs @@ -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"); diff --git a/modules/mono/editor/GodotTools/GodotTools/Internals/Internal.cs b/modules/mono/editor/GodotTools/GodotTools/Internals/Internal.cs index 7e5049e4b7..77370090ec 100644 --- a/modules/mono/editor/GodotTools/GodotTools/Internals/Internal.cs +++ b/modules/mono/editor/GodotTools/GodotTools/Internals/Internal.cs @@ -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(); @@ -101,9 +96,6 @@ namespace GodotTools.Internals 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(); [MethodImpl(MethodImplOptions.InternalCall)] diff --git a/modules/mono/editor/GodotTools/GodotTools/Internals/ScriptClassParser.cs b/modules/mono/editor/GodotTools/GodotTools/Internals/ScriptClassParser.cs deleted file mode 100644 index c72a84c513..0000000000 --- a/modules/mono/editor/GodotTools/GodotTools/Internals/ScriptClassParser.cs +++ /dev/null @@ -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; - } - } -} diff --git a/modules/mono/editor/editor_internal_calls.cpp b/modules/mono/editor/editor_internal_calls.cpp index 667e4a3879..21efd58938 100644 --- a/modules/mono/editor/editor_internal_calls.cpp +++ b/modules/mono/editor/editor_internal_calls.cpp @@ -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); diff --git a/modules/mono/editor/script_class_parser.cpp b/modules/mono/editor/script_class_parser.cpp deleted file mode 100644 index e81cbe4ebd..0000000000 --- a/modules/mono/editor/script_class_parser.cpp +++ /dev/null @@ -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; -} diff --git a/modules/mono/editor/script_class_parser.h b/modules/mono/editor/script_class_parser.h deleted file mode 100644 index 75a46bb4e5..0000000000 --- a/modules/mono/editor/script_class_parser.h +++ /dev/null @@ -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 |