From d0548015cfb1efd246d8ecae01d1b7d69d6be234 Mon Sep 17 00:00:00 2001 From: LPeter1997 Date: Wed, 21 Aug 2024 19:33:57 +0200 Subject: [PATCH] Repl (#428) * Started cobbling together a REPL API * Update ReplSession.cs * Update ReplSession.cs * Update ReplSession.cs * Update ReplSession.cs * Update ReplSession.cs * Update ReplSession.cs * Update ReplSession.cs * Integrated the parser into the REPL * Update Parser.cs * Update Parser.cs * Lexer trivia bug * Update Program.cs * Update Parser.cs * Update ReplSession.cs * Handling import declarations * Working context lookup * Update SemanticTestsBase.cs * Merged execution result structures * Generalized alias, better global imports * Mostly reworked session management * Working thing * Added compiler mode flags * Update ReplSession.cs * Added convenience imports * Added shadowing * Added the ability to do relative ranges * Fixes * Create BasicSessionTest.cs * Update BasicSessionTest.cs * More tests, fix * More tests, fix * Cleanup * Update Draco.Repl.csproj * Create install_repl.ps1 * Update publish.yaml * Misc fixes * Update SyntaxDiagnosticTable.cs * Update ReplSession.cs * Update SyntaxTree.cs * Update ReplContext.cs * Update ReplSession.cs * Update Parser.cs * Added complete entry detection * Update DetectOverpeekSourceReader.cs * Update ReplSession.cs * Update ReplSession.cs * Fix * Cleanup * Main conflicts * Update publish.yaml * Update Diagnostic_Builder.cs * Update MemberCompletionProvider.cs * Update SemanticModel.cs --------- Co-authored-by: lucyelle --- .github/workflows/publish.yaml | 5 + scripts/install_repl.ps1 | 14 + src/Draco.Compiler.Cli/Program.cs | 22 +- src/Draco.Compiler.DevHost/Program.cs | 2 +- .../Repl/BasicSessionTest.cs | 107 +++++++ .../Semantics/SemanticTestsBase.cs | 2 +- src/Draco.Compiler.Tests/Syntax/LexerTests.cs | 28 ++ .../Api/CodeCompletion/CompletionService.cs | 13 + .../ExpressionCompletionProvider.cs | 10 +- .../MemberCompletionProvider.cs | 13 +- .../Api/CodeFixes/CodeFixService.cs | 11 + src/Draco.Compiler/Api/Compilation.cs | 92 +++++- src/Draco.Compiler/Api/CompilationFlags.cs | 22 ++ .../Api/Diagnostics/Diagnostic.cs | 26 ++ .../Api/Diagnostics/Diagnostic_Builder.cs | 10 + .../Api/Diagnostics/RelativeLocation.cs | 21 ++ src/Draco.Compiler/Api/GlobalImports.cs | 19 ++ .../Api/Scripting/ExecutionResult.cs | 29 ++ .../Api/Scripting/ReplSession.cs | 267 ++++++++++++++++++ .../Api/Scripting/ScriptingEngine.cs | 37 +-- .../Api/Semantics/SemanticModel.cs | 30 +- src/Draco.Compiler/Api/Semantics/Symbol.cs | 14 +- src/Draco.Compiler/Api/Syntax/SourceSpan.cs | 7 + .../Api/Syntax/SyntaxPosition.cs | 9 + src/Draco.Compiler/Api/Syntax/SyntaxRange.cs | 9 + src/Draco.Compiler/Api/Syntax/SyntaxTree.cs | 12 +- .../Internal/Binding/BinderCache.cs | 1 + .../Internal/Binding/BinderFacts.cs | 6 +- .../Internal/Binding/Binder_Lookup.cs | 1 - .../Internal/Binding/GlobalImportsBinder.cs | 77 +++++ .../Internal/Binding/IntrinsicsBinder.cs | 2 +- .../Internal/Binding/LookupResult.cs | 6 +- .../Internal/Declarations/DeclarationTable.cs | 1 - .../Extractors/XmlDocumentationExtractor.cs | 2 +- .../OptimizingIr/FunctionBodyCodegen.cs | 1 + .../Model/NewDelegateInstruction.cs | 1 - .../Scripting/DetectOverpeekSourceReader.cs | 49 ++++ .../Internal/Scripting/ReplContext.cs | 96 +++++++ .../Internal/Symbols/AliasSymbol.cs | 20 ++ .../Internal/Symbols/FunctionSymbol.cs | 1 + .../Internal/Symbols/Metadata/TypeProvider.cs | 10 +- src/Draco.Compiler/Internal/Symbols/Symbol.cs | 6 + .../Internal/Symbols/SymbolVisitor.cs | 8 +- .../Synthetized/SynthetizedAliasSymbol.cs | 10 + .../Synthetized/SynthetizedTypeAliasSymbol.cs | 10 - .../Internal/Symbols/TypeAliasSymbol.cs | 20 -- .../Internal/Symbols/TypeSymbol.cs | 2 +- .../Internal/Symbols/VariableSymbol.cs | 1 + .../Internal/Symbols/WellKnownTypes.cs | 6 +- src/Draco.Compiler/Internal/Syntax/Lexer.cs | 3 +- src/Draco.Compiler/Internal/Syntax/Parser.cs | 73 ++++- .../Internal/Syntax/SourceReader.cs | 48 ++++ .../Internal/Syntax/SyntaxDiagnosticTable.cs | 20 +- .../DracoLanguageServer.cs | 16 +- src/Draco.Repl/Draco.Repl.csproj | 19 ++ src/Draco.Repl/Program.cs | 49 ++++ src/Draco.sln | 12 +- 57 files changed, 1240 insertions(+), 168 deletions(-) create mode 100644 scripts/install_repl.ps1 create mode 100644 src/Draco.Compiler.Tests/Repl/BasicSessionTest.cs create mode 100644 src/Draco.Compiler/Api/CompilationFlags.cs create mode 100644 src/Draco.Compiler/Api/Diagnostics/RelativeLocation.cs create mode 100644 src/Draco.Compiler/Api/GlobalImports.cs create mode 100644 src/Draco.Compiler/Api/Scripting/ExecutionResult.cs create mode 100644 src/Draco.Compiler/Api/Scripting/ReplSession.cs create mode 100644 src/Draco.Compiler/Internal/Binding/GlobalImportsBinder.cs create mode 100644 src/Draco.Compiler/Internal/Scripting/DetectOverpeekSourceReader.cs create mode 100644 src/Draco.Compiler/Internal/Scripting/ReplContext.cs create mode 100644 src/Draco.Compiler/Internal/Symbols/AliasSymbol.cs create mode 100644 src/Draco.Compiler/Internal/Symbols/Synthetized/SynthetizedAliasSymbol.cs delete mode 100644 src/Draco.Compiler/Internal/Symbols/Synthetized/SynthetizedTypeAliasSymbol.cs delete mode 100644 src/Draco.Compiler/Internal/Symbols/TypeAliasSymbol.cs create mode 100644 src/Draco.Repl/Draco.Repl.csproj create mode 100644 src/Draco.Repl/Program.cs diff --git a/.github/workflows/publish.yaml b/.github/workflows/publish.yaml index 3e1a7f94e..3201cb82f 100644 --- a/.github/workflows/publish.yaml +++ b/.github/workflows/publish.yaml @@ -6,6 +6,9 @@ on: deploy-sdk: type: boolean description: Deploy components of the SDK (Compiler, Toolset, Sdk, ProjectTemplates) + deploy-repl: + type: boolean + description: Deploy components of the REPL (Compiler, Repl) deploy-langserver: type: boolean description: Deploy the language server (Compiler, LanguageServer, Lsp) @@ -58,11 +61,13 @@ jobs: run: | cd src $sdkProjects = "Compiler", "Compiler.Toolset", "Sdk", "ProjectTemplates" + $replProjects = "Compiler", "Repl" $langserverProjects = "Compiler", "LanguageServer", "Lsp", "JsonRpc" $debugadapterProjects = "DebugAdapter", "Dap", "JsonRpc" $projects = @() if ($${{ github.event.inputs.deploy-sdk }}) { $projects += $sdkProjects; } + if ($${{ github.event.inputs.deploy-repl }}) { $projects += $replProjects; } if ($${{ github.event.inputs.deploy-langserver }}) { $projects += $langserverProjects; } if ($${{ github.event.inputs.deploy-debugadapter }}) { $projects += $debugadapterProjects; } diff --git a/scripts/install_repl.ps1 b/scripts/install_repl.ps1 new file mode 100644 index 000000000..087323bfc --- /dev/null +++ b/scripts/install_repl.ps1 @@ -0,0 +1,14 @@ +<# + .Description + Install the Draco Repl tool globally from source. + This helps testing repl changes while development. +#> + +$ErrorActionPreference = "Stop" +Push-Location $PSScriptRoot +dotnet pack ../src/Draco.Repl --configuration Debug --output . +if ((dotnet tool list --global) -match "Draco.Repl") { + dotnet tool uninstall --global Draco.Repl +} +dotnet tool install --global --add-source . Draco.Repl +Pop-Location diff --git a/src/Draco.Compiler.Cli/Program.cs b/src/Draco.Compiler.Cli/Program.cs index 1002d8917..11eba06a8 100644 --- a/src/Draco.Compiler.Cli/Program.cs +++ b/src/Draco.Compiler.Cli/Program.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using System.Collections.Immutable; using System.CommandLine; using System.CommandLine.Parsing; @@ -138,7 +139,7 @@ private static void RunCommand(FileInfo[] input, DirectoryInfo? rootModule, File var execResult = ScriptingEngine.Execute(compilation); if (!EmitDiagnostics(execResult, msbuildDiags)) { - Console.WriteLine($"Result: {execResult.Result}"); + Console.WriteLine($"Result: {execResult.Value}"); } } @@ -196,32 +197,23 @@ private static ImmutableArray GetSyntaxTrees(params FileInfo[] input private static bool EmitDiagnostics(EmitResult result, bool msbuildDiags) { if (result.Success) return false; - foreach (var diag in result.Diagnostics) - { - Console.Error.WriteLine(msbuildDiags ? MakeMsbuildDiag(diag) : diag.ToString()); - } + WriteDiagnostics(result.Diagnostics, msbuildDiags); return true; } private static bool EmitDiagnostics(ExecutionResult result, bool msbuildDiags) { if (result.Success) return false; - foreach (var diag in result.Diagnostics) - { - Console.Error.WriteLine(msbuildDiags ? MakeMsbuildDiag(diag) : diag.ToString()); - } + WriteDiagnostics(result.Diagnostics, msbuildDiags); return true; } - private static string MakeMsbuildDiag(Diagnostic original) + private static void WriteDiagnostics(IEnumerable diagnostics, bool msbuildDiags) { - var file = string.Empty; - if (!original.Location.IsNone && original.Location.SourceText.Path is not null) + foreach (var diag in diagnostics) { - var range = original.Location.Range!.Value; - file = $"{original.Location.SourceText.Path.OriginalString}({range.Start.Line + 1},{range.Start.Column + 1},{range.End.Line + 1},{range.End.Column + 1})"; + Console.Error.WriteLine(msbuildDiags ? diag.ToMsbuildString() : diag.ToString()); } - return $"{file} : {original.Severity.ToString().ToLower()} {original.Template.Code} : {original.Message}"; } private static (string Path, string Name) ExtractOutputPathAndName(FileInfo outputInfo) diff --git a/src/Draco.Compiler.DevHost/Program.cs b/src/Draco.Compiler.DevHost/Program.cs index c3fc18d89..a01fdd768 100644 --- a/src/Draco.Compiler.DevHost/Program.cs +++ b/src/Draco.Compiler.DevHost/Program.cs @@ -133,7 +133,7 @@ private static void RunCommand(FileInfo[] input, DirectoryInfo? rootModule, File var execResult = ScriptingEngine.Execute(compilation); if (!EmitDiagnostics(execResult)) { - Console.WriteLine($"Result: {execResult.Result}"); + Console.WriteLine($"Result: {execResult.Value}"); } } diff --git a/src/Draco.Compiler.Tests/Repl/BasicSessionTest.cs b/src/Draco.Compiler.Tests/Repl/BasicSessionTest.cs new file mode 100644 index 000000000..17695ac35 --- /dev/null +++ b/src/Draco.Compiler.Tests/Repl/BasicSessionTest.cs @@ -0,0 +1,107 @@ +using Draco.Compiler.Api; +using Draco.Compiler.Api.Scripting; +using static Basic.Reference.Assemblies.Net80; + +namespace Draco.Compiler.Tests.Repl; + +public sealed class BasicSessionTest +{ + private static IEnumerable BclReferences => ReferenceInfos.All + .Select(r => MetadataReference.FromPeStream(new MemoryStream(r.ImageBytes))); + + [InlineData("1 + 2", 3)] + [InlineData("2 < 3 < 4", true)] + [InlineData("\"asd\" + \"def\"", "asddef")] + [InlineData("\"1 + 2 = \\{1 + 2}\"", "1 + 2 = 3")] + [Theory] + public void BasicExpressions(string input, object? output) + { + var replSession = new ReplSession([.. BclReferences]); + + var ms = new MemoryStream(); + + var writer = new StreamWriter(ms); + writer.WriteLine(input); + writer.Flush(); + + ms.Position = 0; + var reader = new StreamReader(ms); + + var result = replSession.Evaluate(reader); + + Assert.True(result.Success); + Assert.Equal(output, result.Value); + } + + [Fact] + public void ComplexSession1() => AssertSequence( + ("var x = 3;", null), + ("var y = 4;", null), + ("var z = x + y;", null), + ("z", 7), + ("var x = 5;", null), + ("x", 5), + ("z", 7)); + + [Fact] + public void ComplexSession2() => AssertSequence( + ("func add(x: int32, y: int32): int32 = x + y;", null), + ("add(1, 2);", null), + ("add(1, 2)", 3)); + + [Fact] + public void ComplexSession3() => AssertSequence( + ("func id(x: T): T = x;", null), + ("id(1)", 1), + ("id(\"asd\")", "asd")); + + [Fact] + public void ComplexSession4() => AssertSequence( + ("var x = 4;", null), + ("func foo(): int32 = x;", null), + ("foo()", 4), + ("var x = 5;", null), + ("foo()", 4), + ("func foo(): int32 = x;", null), + ("foo()", 5)); + + [Fact] + public void ComplexSession5() => AssertSequence( + ("import System.Collections.Generic;", null), + ("var l = List();", null), + ("l.Add(1);", null), + ("l.Add(2);", null), + ("l.Add(3);", null), + ("l.Count", 3)); + + private static void AssertSequence(params (string Code, object? Value)[] pairs) + { + var results = ExecuteSequence(pairs.Select(p => p.Code)); + foreach (var (expected, result) in pairs.Select(p => p.Value).Zip(results)) + { + Assert.True(result.Success); + Assert.Equal(expected, result.Value); + } + } + + private static IEnumerable> ExecuteSequence(IEnumerable inputs) + { + var replSession = new ReplSession([.. BclReferences]); + + var ms = new MemoryStream(); + var reader = new StreamReader(ms); + var writer = new StreamWriter(ms); + + foreach (var input in inputs) + { + var pos = ms.Position; + + writer.WriteLine(input); + writer.Flush(); + + ms.Position = pos; + + yield return replSession.Evaluate(reader); + } + } +} diff --git a/src/Draco.Compiler.Tests/Semantics/SemanticTestsBase.cs b/src/Draco.Compiler.Tests/Semantics/SemanticTestsBase.cs index da6c8c3fb..910679bef 100644 --- a/src/Draco.Compiler.Tests/Semantics/SemanticTestsBase.cs +++ b/src/Draco.Compiler.Tests/Semantics/SemanticTestsBase.cs @@ -30,7 +30,7 @@ private protected static TMember GetMemberSymbol(Symbol parent, string private protected static Symbol GetMetadataSymbol(Compilation compilation, string? assemblyName, params string[] path) { assemblyName ??= TestUtilities.DefaultAssemblyName; - var asm = compilation.MetadataAssemblies.Values.Single(a => a.AssemblyName.Name == assemblyName); + var asm = compilation.MetadataAssemblies.Single(a => a.AssemblyName.Name == assemblyName); return asm.RootNamespace.Lookup([.. path]).First(); } diff --git a/src/Draco.Compiler.Tests/Syntax/LexerTests.cs b/src/Draco.Compiler.Tests/Syntax/LexerTests.cs index 11fdbbad8..407755b05 100644 --- a/src/Draco.Compiler.Tests/Syntax/LexerTests.cs +++ b/src/Draco.Compiler.Tests/Syntax/LexerTests.cs @@ -164,6 +164,34 @@ public void TestLineString() this.AssertNoTriviaOrDiagnostics(); } + [Fact] + [Trait("Feature", "Strings")] + public void TestTriviaAfterLineString() + { + var ws = " "; + var text = $""" + "Abc"{ws} + + """; + this.Lex(text); + + this.AssertNextToken(TokenKind.LineStringStart, "\""); + this.AssertNoTriviaOrDiagnostics(); + + this.AssertNextToken(TokenKind.StringContent, "Abc", "Abc"); + this.AssertNoTriviaOrDiagnostics(); + + this.AssertNextToken(TokenKind.LineStringEnd, "\""); + this.AssertDiagnostics(); + this.AssertLeadingTrivia(); + this.AssertTrailingTrivia( + (TriviaKind.Whitespace, ws), + (TriviaKind.Newline, "\n")); + + this.AssertNextToken(TokenKind.EndOfInput); + this.AssertNoTriviaOrDiagnostics(); + } + [Fact] [Trait("Feature", "Strings")] public void TestUnclosedLineString() diff --git a/src/Draco.Compiler/Api/CodeCompletion/CompletionService.cs b/src/Draco.Compiler/Api/CodeCompletion/CompletionService.cs index f67dc0ba8..d0f6967a1 100644 --- a/src/Draco.Compiler/Api/CodeCompletion/CompletionService.cs +++ b/src/Draco.Compiler/Api/CodeCompletion/CompletionService.cs @@ -11,6 +11,19 @@ namespace Draco.Compiler.Api.CodeCompletion; /// public sealed class CompletionService { + /// + /// Creates a new with the default s. + /// + /// A new with default providers. + public static CompletionService CreateDefault() + { + var service = new CompletionService(); + service.AddProvider(new KeywordCompletionProvider()); + service.AddProvider(new ExpressionCompletionProvider()); + service.AddProvider(new MemberCompletionProvider()); + return service; + } + private readonly List providers = []; /// diff --git a/src/Draco.Compiler/Api/CodeCompletion/ExpressionCompletionProvider.cs b/src/Draco.Compiler/Api/CodeCompletion/ExpressionCompletionProvider.cs index 7f47ca278..56a1e497f 100644 --- a/src/Draco.Compiler/Api/CodeCompletion/ExpressionCompletionProvider.cs +++ b/src/Draco.Compiler/Api/CodeCompletion/ExpressionCompletionProvider.cs @@ -34,23 +34,23 @@ public override ImmutableArray GetCompletionItems( private static CompletionItem? GetCompletionItem( SourceText source, ImmutableArray symbols, CompletionContext currentContexts, SourceSpan span) => symbols.First() switch { - TypeSymbol or TypeAliasSymbol when currentContexts.HasFlag(CompletionContext.Expression) - || currentContexts.HasFlag(CompletionContext.Type) => + ITypeSymbol or IAliasSymbol when currentContexts.HasFlag(CompletionContext.Expression) + || currentContexts.HasFlag(CompletionContext.Type) => CompletionItem.Create(source, symbols.First().Name, span, symbols, CompletionKind.Class), IVariableSymbol when currentContexts.HasFlag(CompletionContext.Expression) => CompletionItem.Create(source, symbols.First().Name, span, symbols, CompletionKind.Variable), - PropertySymbol when currentContexts.HasFlag(CompletionContext.Expression) => + IPropertySymbol when currentContexts.HasFlag(CompletionContext.Expression) => CompletionItem.Create(source, symbols.First().Name, span, symbols, CompletionKind.Property), // We need the type context here for qualified type references - ModuleSymbol when currentContexts.HasFlag(CompletionContext.Expression) + IModuleSymbol when currentContexts.HasFlag(CompletionContext.Expression) || currentContexts.HasFlag(CompletionContext.Type) || currentContexts.HasFlag(CompletionContext.Import) => CompletionItem.Create(source, symbols.First().Name, span, symbols, CompletionKind.Module), - FunctionSymbol fun when !fun.IsSpecialName && currentContexts.HasFlag(CompletionContext.Expression) => + IFunctionSymbol fun when !fun.IsSpecialName && currentContexts.HasFlag(CompletionContext.Expression) => CompletionItem.Create(source, symbols.First().Name, span, symbols, CompletionKind.Function), _ => null, diff --git a/src/Draco.Compiler/Api/CodeCompletion/MemberCompletionProvider.cs b/src/Draco.Compiler/Api/CodeCompletion/MemberCompletionProvider.cs index 260ac7e80..59a7a43c4 100644 --- a/src/Draco.Compiler/Api/CodeCompletion/MemberCompletionProvider.cs +++ b/src/Draco.Compiler/Api/CodeCompletion/MemberCompletionProvider.cs @@ -3,7 +3,6 @@ using System.Linq; using Draco.Compiler.Api.Semantics; using Draco.Compiler.Api.Syntax; -using Draco.Compiler.Internal.Symbols.Source; namespace Draco.Compiler.Api.CodeCompletion; @@ -80,22 +79,22 @@ public static bool TryDeconstructMemberAccess(SyntaxNode? node, [MaybeNullWhen(f private static CompletionItem? GetCompletionItem( SourceText source, ImmutableArray symbols, CompletionContext currentContexts, SourceSpan span) => symbols.First() switch { - TypeSymbol when currentContexts.HasFlag(CompletionContext.Type) + ITypeSymbol when currentContexts.HasFlag(CompletionContext.Type) || currentContexts.HasFlag(CompletionContext.Expression) => CompletionItem.Create(source, symbols.First().Name, span, symbols, CompletionKind.Class), - ModuleSymbol when currentContexts.HasFlag(CompletionContext.Type) - || currentContexts.HasFlag(CompletionContext.Expression) - || currentContexts.HasFlag(CompletionContext.Import) => + IModuleSymbol when currentContexts.HasFlag(CompletionContext.Type) + || currentContexts.HasFlag(CompletionContext.Expression) + || currentContexts.HasFlag(CompletionContext.Import) => CompletionItem.Create(source, symbols.First().Name, span, symbols, CompletionKind.Module), IVariableSymbol when currentContexts.HasFlag(CompletionContext.Expression) => CompletionItem.Create(source, symbols.First().Name, span, symbols, CompletionKind.Variable), - PropertySymbol when currentContexts.HasFlag(CompletionContext.Expression) => + IPropertySymbol when currentContexts.HasFlag(CompletionContext.Expression) => CompletionItem.Create(source, symbols.First().Name, span, symbols, CompletionKind.Property), - FunctionSymbol fun when !fun.IsSpecialName && currentContexts.HasFlag(CompletionContext.Expression) => + IFunctionSymbol fun when !fun.IsSpecialName && currentContexts.HasFlag(CompletionContext.Expression) => CompletionItem.Create(source, symbols.First().Name, span, symbols, CompletionKind.Function), _ => null, diff --git a/src/Draco.Compiler/Api/CodeFixes/CodeFixService.cs b/src/Draco.Compiler/Api/CodeFixes/CodeFixService.cs index 1ad612728..a958062d6 100644 --- a/src/Draco.Compiler/Api/CodeFixes/CodeFixService.cs +++ b/src/Draco.Compiler/Api/CodeFixes/CodeFixService.cs @@ -11,6 +11,17 @@ namespace Draco.Compiler.Api.CodeFixes; /// public sealed class CodeFixService { + /// + /// Creates a new with the default s. + /// + /// A new with default providers. + public static CodeFixService CreateDefault() + { + var service = new CodeFixService(); + service.AddProvider(new ImportCodeFixProvider()); + return service; + } + private readonly List providers = []; /// diff --git a/src/Draco.Compiler/Api/Compilation.cs b/src/Draco.Compiler/Api/Compilation.cs index 77231445d..829adecd6 100644 --- a/src/Draco.Compiler/Api/Compilation.cs +++ b/src/Draco.Compiler/Api/Compilation.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Concurrent; +using System.Collections.Generic; using System.Collections.Immutable; using System.IO; using System.Linq; @@ -47,6 +48,7 @@ public sealed class Compilation : IBinderProvider /// /// The s to compile. /// The s the compiler references. + /// Special compiler flags. /// The path of the root module. /// The output path. /// The output assembly name. @@ -54,15 +56,46 @@ public sealed class Compilation : IBinderProvider public static Compilation Create( ImmutableArray syntaxTrees, ImmutableArray? metadataReferences = null, + CompilationFlags flags = CompilationFlags.None, string? rootModulePath = null, string? outputPath = null, string? assemblyName = null) => new( + flags: flags, syntaxTrees: syntaxTrees, metadataReferences: metadataReferences, rootModulePath: rootModulePath, outputPath: outputPath, assemblyName: assemblyName); + /// + /// Constructs a . + /// + /// The s to compile. + /// Special compiler flags. + /// The global imports for the compilation. + /// The s the compiler references. + /// The path of the root module. + /// The output path. + /// The output assembly name. + /// The constructed . + internal static Compilation Create( + ImmutableArray syntaxTrees, + CompilationFlags flags, + GlobalImports globalImports, + ImmutableArray? metadataReferences = null, + string? rootModulePath = null, + string? outputPath = null, + string? assemblyName = null, + IReadOnlyDictionary? metadataAssemblies = null) => new( + syntaxTrees: syntaxTrees, + metadataReferences: metadataReferences, + flags: flags, + globalImports: globalImports, + rootModulePath: rootModulePath, + outputPath: outputPath, + assemblyName: assemblyName, + metadataAssemblies: metadataAssemblies); + /// /// All messages in the . /// @@ -72,6 +105,11 @@ public static Compilation Create( .Concat(this.GlobalDiagnosticBag) .ToImmutableArray(); + /// + /// Special settings flags. + /// + public CompilationFlags Flags { get; } + /// /// The trees that are being compiled. /// @@ -97,6 +135,21 @@ public static Compilation Create( /// public string AssemblyName { get; } + /// + /// Global imports for the compilation. + /// + public GlobalImports GlobalImports { get; } + + /// + /// The metadata assemblies this compilation references. + /// + internal IEnumerable MetadataAssemblies => this + .MetadataReferences + .Select(this.GetMetadataAssembly); + + // TODO: Ugly API, anything nicer? + internal IReadOnlyDictionary MetadataAssembliesDict => this.metadataAssemblies; + // TODO: Currently this does NOT include the sources, which might make merging same package names // invalid between metadata and source. For now we don't care. /// @@ -106,13 +159,6 @@ public static Compilation Create( LazyInitializer.EnsureInitialized(ref this.rootModule, this.BuildRootModule); private ModuleSymbol? rootModule; - /// - /// The metadata assemblies this compilation references. - /// - internal ImmutableDictionary MetadataAssemblies => - LazyInitializer.EnsureInitialized(ref this.metadataAssemblies, this.BuildMetadataAssemblies); - private ImmutableDictionary? metadataAssemblies; - /// /// The top-level source module symbol of the compilation. /// @@ -145,29 +191,34 @@ public static Compilation Create( private readonly BinderCache binderCache; private readonly ConcurrentDictionary semanticModels = new(); + private readonly Dictionary metadataAssemblies = []; // Main ctor with all state private Compilation( ImmutableArray syntaxTrees, ImmutableArray? metadataReferences, + CompilationFlags flags = CompilationFlags.None, + GlobalImports? globalImports = null, string? rootModulePath = null, string? outputPath = null, string? assemblyName = null, ModuleSymbol? rootModule = null, - ImmutableDictionary? metadataAssemblies = null, + IReadOnlyDictionary? metadataAssemblies = null, ModuleSymbol? sourceModule = null, DeclarationTable? declarationTable = null, WellKnownTypes? wellKnownTypes = null, TypeProvider? typeProvider = null, BinderCache? binderCache = null) { + this.Flags = flags; this.SyntaxTrees = syntaxTrees; this.MetadataReferences = metadataReferences ?? []; this.RootModulePath = Path.TrimEndingDirectorySeparator(rootModulePath ?? string.Empty); this.OutputPath = outputPath ?? "."; + this.GlobalImports = globalImports ?? default; this.AssemblyName = assemblyName ?? "output"; this.rootModule = rootModule; - this.metadataAssemblies = metadataAssemblies; + this.metadataAssemblies = metadataAssemblies?.ToDictionary(kv => kv.Key, kv => kv.Value) ?? []; this.sourceModule = sourceModule; this.declarationTable = declarationTable; this.WellKnownTypes = wellKnownTypes ?? new WellKnownTypes(this); @@ -334,16 +385,29 @@ internal Binder GetBinder(Symbol symbol) Binder IBinderProvider.GetBinder(SyntaxNode syntax) => this.GetBinder(syntax); Binder IBinderProvider.GetBinder(Symbol symbol) => this.GetBinder(symbol); + private MetadataAssemblySymbol GetMetadataAssembly(MetadataReference metadataReference) + { + if (!this.metadataAssemblies.TryGetValue(metadataReference, out var metadataAssembly)) + { + // NOTE: In case the dict is carried on into another compilation, + // the metadata compilation will have an outdated ref to the compilation + // I don't know if this will cause any problems in the future + metadataAssembly = new MetadataAssemblySymbol( + this, + metadataReference.MetadataReader, + metadataReference.Documentation); + this.metadataAssemblies.Add(metadataReference, metadataAssembly); + } + return metadataAssembly; + } + private DeclarationTable BuildDeclarationTable() => new(this); private ModuleSymbol BuildSourceModule() => new SourceModuleSymbol(this, null, this.DeclarationTable.MergedRoot); - private ImmutableDictionary BuildMetadataAssemblies() => this.MetadataReferences - .ToImmutableDictionary( - r => r, - r => new MetadataAssemblySymbol(this, r.MetadataReader, r.Documentation)); private ModuleSymbol BuildRootModule() => new MergedModuleSymbol( containingSymbol: null, name: string.Empty, - modules: this.MetadataAssemblies.Values + modules: this + .MetadataAssemblies .Cast() .Append(this.SourceModule) .ToImmutableArray()); diff --git a/src/Draco.Compiler/Api/CompilationFlags.cs b/src/Draco.Compiler/Api/CompilationFlags.cs new file mode 100644 index 000000000..c3b97dcaa --- /dev/null +++ b/src/Draco.Compiler/Api/CompilationFlags.cs @@ -0,0 +1,22 @@ +using System; + +namespace Draco.Compiler.Api; + +/// +/// Special settings flags for the compiler. +/// +[Flags] +public enum CompilationFlags +{ + /// + /// No special settings. + /// + None = 0, + + /// + /// All defined symbols will be public in the compilation. + /// + /// This can be used by things like the REPL to omit visibility. + /// + ImplicitPublicSymbols = 1 << 0, +} diff --git a/src/Draco.Compiler/Api/Diagnostics/Diagnostic.cs b/src/Draco.Compiler/Api/Diagnostics/Diagnostic.cs index fb67da5dd..c4591d26f 100644 --- a/src/Draco.Compiler/Api/Diagnostics/Diagnostic.cs +++ b/src/Draco.Compiler/Api/Diagnostics/Diagnostic.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Immutable; using System.Text; +using Draco.Compiler.Api.Syntax; namespace Draco.Compiler.Api.Diagnostics; @@ -100,6 +101,16 @@ private Diagnostic( this.RelatedInformation = relatedInformation; } + /// + /// Transforms this diagnostic to have relative positioning to a given syntax node. + /// + /// The syntax node to be relative to. + /// The transformed diagnostic. + internal Diagnostic RelativeTo(SyntaxNode syntax) => this + .ToBuilder() + .WithLocation(new RelativeLocation(syntax, this.Location)) + .Build(); + public override string ToString() { var sb = new StringBuilder(); @@ -115,4 +126,19 @@ public override string ToString() sb.Append(": ").Append(this.Message); return sb.ToString(); } + + /// + /// Converts this diagnostic to the MSBuild error format. + /// + /// The MSBuild error format of this diagnostic. + public string ToMsbuildString() + { + var file = string.Empty; + if (!this.Location.IsNone && this.Location.SourceText.Path is not null) + { + var range = this.Location.Range!.Value; + file = $"{this.Location.SourceText.Path.OriginalString}({range.Start.Line + 1},{range.Start.Column + 1},{range.End.Line + 1},{range.End.Column + 1})"; + } + return $"{file} : {this.Severity.ToString().ToLower()} {this.Template.Code} : {this.Message}"; + } } diff --git a/src/Draco.Compiler/Api/Diagnostics/Diagnostic_Builder.cs b/src/Draco.Compiler/Api/Diagnostics/Diagnostic_Builder.cs index b27a75699..84c519074 100644 --- a/src/Draco.Compiler/Api/Diagnostics/Diagnostic_Builder.cs +++ b/src/Draco.Compiler/Api/Diagnostics/Diagnostic_Builder.cs @@ -13,6 +13,16 @@ public sealed partial class Diagnostic /// A new, empty diagnostic builder. public static Builder CreateBuilder() => new(); + /// + /// Converts this diagnostic to a builder. + /// + /// The builder containing this diagnostic's data. + public Builder ToBuilder() => CreateBuilder() + .WithTemplate(this.Template) + .WithFormatArgs(this.FormatArgs) + .WithLocation(this.Location) + .WithRelatedInformation(this.RelatedInformation); + /// /// A builder type for . /// diff --git a/src/Draco.Compiler/Api/Diagnostics/RelativeLocation.cs b/src/Draco.Compiler/Api/Diagnostics/RelativeLocation.cs new file mode 100644 index 000000000..882b3a3db --- /dev/null +++ b/src/Draco.Compiler/Api/Diagnostics/RelativeLocation.cs @@ -0,0 +1,21 @@ +using Draco.Compiler.Api.Syntax; + +namespace Draco.Compiler.Api.Diagnostics; + +/// +/// Represents a location relative to some syntax element. +/// +internal sealed class RelativeLocation(SyntaxNode relativeTo, Location originalLocation) : Location +{ + public override SourceText SourceText => originalLocation.SourceText; + public override SourceSpan? Span => originalLocation.Span?.RelativeTo(relativeTo.Span.Start); + + public override string ToString() + { + var range = this.Range; + if (range is null) return originalLocation.ToString()!; + + var position = range.Value.Start; + return $"at line {position.Line + 1}, character {position.Column + 1}"; + } +} diff --git a/src/Draco.Compiler/Api/GlobalImports.cs b/src/Draco.Compiler/Api/GlobalImports.cs new file mode 100644 index 000000000..6892a3470 --- /dev/null +++ b/src/Draco.Compiler/Api/GlobalImports.cs @@ -0,0 +1,19 @@ +using System.Collections.Immutable; + +namespace Draco.Compiler.Api; + +/// +/// Represents global import data that can be fed to a . +/// +/// All modules that should be implicitly imported. +/// All symbol paths that should be implicitly aliased to a given name in global namespace. +public readonly record struct GlobalImports( + ImmutableArray ModuleImports, + ImmutableArray<(string Name, string FullPath)> ImportAliases) +{ + /// + /// True, if this is a default or empty structure. + /// + public bool IsDefault => this.ModuleImports.IsDefaultOrEmpty + && this.ImportAliases.IsDefaultOrEmpty; +} diff --git a/src/Draco.Compiler/Api/Scripting/ExecutionResult.cs b/src/Draco.Compiler/Api/Scripting/ExecutionResult.cs new file mode 100644 index 000000000..72896e314 --- /dev/null +++ b/src/Draco.Compiler/Api/Scripting/ExecutionResult.cs @@ -0,0 +1,29 @@ +using System.Collections.Immutable; +using Draco.Compiler.Api.Diagnostics; + +namespace Draco.Compiler.Api.Scripting; + +/// +/// Utility factory methods for creating instances. +/// +internal static class ExecutionResult +{ + public static ExecutionResult Fail(ImmutableArray diagnostics) => + new(Success: false, Value: default, Diagnostics: diagnostics); + + public static ExecutionResult Success( + TResult result, ImmutableArray? diagnostics = null) => + new(Success: true, Value: result, Diagnostics: diagnostics ?? []); +} + +/// +/// The result type of script execution. +/// +/// The expected result type. +/// True, if the execution was successful without errors. +/// The resulting value of the execution. +/// The s produced during execution. +public readonly record struct ExecutionResult( + bool Success, + TResult? Value, + ImmutableArray Diagnostics); diff --git a/src/Draco.Compiler/Api/Scripting/ReplSession.cs b/src/Draco.Compiler/Api/Scripting/ReplSession.cs new file mode 100644 index 000000000..a6bd5a0a8 --- /dev/null +++ b/src/Draco.Compiler/Api/Scripting/ReplSession.cs @@ -0,0 +1,267 @@ +using System; +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Diagnostics; +using System.IO; +using System.Linq; +using System.Reflection; +using Draco.Compiler.Api.Syntax; +using Draco.Compiler.Internal.Scripting; +using Draco.Compiler.Internal.Syntax; +using static Draco.Compiler.Api.Syntax.SyntaxFactory; +using DeclarationSyntax = Draco.Compiler.Api.Syntax.DeclarationSyntax; +using ExpressionSyntax = Draco.Compiler.Api.Syntax.ExpressionSyntax; +using ImportDeclarationSyntax = Draco.Compiler.Api.Syntax.ImportDeclarationSyntax; +using ImportPathSyntax = Draco.Compiler.Api.Syntax.ImportPathSyntax; +using MemberImportPathSyntax = Draco.Compiler.Api.Syntax.MemberImportPathSyntax; +using RootImportPathSyntax = Draco.Compiler.Api.Syntax.RootImportPathSyntax; +using StatementSyntax = Draco.Compiler.Api.Syntax.StatementSyntax; +using SyntaxNode = Draco.Compiler.Api.Syntax.SyntaxNode; + +namespace Draco.Compiler.Api.Scripting; + +/// +/// Represents an interactive REPL session. +/// +public sealed class ReplSession +{ + /// + /// Checks if the given text is a complete entry to be parsed by the REPL. + /// + /// The text to check. + /// True, if is a complete entry. + public static bool IsCompleteEntry(string text) + { + // We add a newline to make sure we don't peek past with trailing trivia if not needed + text = string.Concat(text, Environment.NewLine); + var reader = new DetectOverpeekSourceReader(SourceReader.From(text)); + _ = ParseReplEntry(reader); + return !reader.HasOverpeeked; + } + + private readonly record struct HistoryEntry(Compilation Compilation, Assembly Assembly); + + private const string EvalFunctionName = ".eval"; + + private readonly List previousEntries = []; + private readonly ReplContext context = new(); + + public ReplSession(ImmutableArray metadataReferences) + { + foreach (var reference in metadataReferences) this.context.AddMetadataReference(reference); + } + + /// + /// Adds global imports to the session. + /// + /// The import paths to add. + public void AddImports(params string[] importPaths) => + this.AddImports(importPaths.AsEnumerable()); + + /// + /// Adds global imports to the session. + /// + /// The import paths to add. + public void AddImports(IEnumerable importPaths) + { + foreach (var path in importPaths) this.context.AddImport(path); + } + + /// + /// Evaluates the given source code. + /// + /// The source code to evaluate. + /// The execution result. + public ExecutionResult Evaluate(string text) => + this.Evaluate(SourceReader.From(text)); + + /// + /// Evaluates the given source code. + /// + /// The reader to read input from. + /// The execution result. + public ExecutionResult Evaluate(TextReader reader) => + this.Evaluate(SourceReader.From(reader)); + + /// + /// Evaluates the given syntax node. + /// + /// The node to evaluate. + /// The execution result. + public ExecutionResult Evaluate(SyntaxNode node) => this.Evaluate(node); + + /// + /// Evaluates the given source code. + /// + /// The result type expected. + /// The source code to evaluate. + /// The execution result. + public ExecutionResult Evaluate(string text) => + this.Evaluate(SourceReader.From(text)); + + /// + /// Evaluates the given source code. + /// + /// The result type expected. + /// The text reader to read input from. + /// The execution result. + public ExecutionResult Evaluate(TextReader reader) => + this.Evaluate(SourceReader.From(reader)); + + /// + /// Evaluates the given source code. + /// + /// The result type expected. + /// The source reader to read input from. + /// The execution result. + internal ExecutionResult Evaluate(ISourceReader sourceReader) + { + var tree = ParseReplEntry(sourceReader); + + // Check for syntax errors + if (tree.HasErrors) + { + return ExecutionResult.Fail(tree.Diagnostics.ToImmutableArray()); + } + + // Actually evaluate + return this.Evaluate(tree.Root); + } + + /// + /// Evaluates the given syntax node. + /// + /// The result type expected. + /// The node to evaluate. + /// The execution result. + public ExecutionResult Evaluate(SyntaxNode node) + { + // Check for imports + if (node is ImportDeclarationSyntax import) + { + this.context.AddImport(ExtractImportPath(import.Path)); + return ExecutionResult.Success(default(TResult)!); + } + + // Translate to a runnable function + var decl = node switch + { + ExpressionSyntax expr => this.ToDeclaration(expr), + StatementSyntax stmt => this.ToDeclaration(stmt), + DeclarationSyntax d => d, + _ => throw new ArgumentOutOfRangeException(nameof(node)), + }; + + // Wrap in a tree + var tree = this.ToSyntaxTree(decl); + + // Find the relocated node in the tree, we need this to shift diagnostics + var relocatedNode = node switch + { + ExpressionSyntax expr => tree.FindInChildren() as SyntaxNode, + StatementSyntax stmt => tree.FindInChildren(), + DeclarationSyntax d => tree.FindInChildren(1), + _ => throw new ArgumentOutOfRangeException(nameof(node)), + }; + + // Make compilation + var compilation = this.MakeCompilation(tree); + + // Emit the assembly + var peStream = new MemoryStream(); + var result = compilation.Emit(peStream: peStream); + + // Transform all the diagnostics + var diagnostics = result.Diagnostics + .Select(d => d.RelativeTo(relocatedNode)) + .ToImmutableArray(); + + // Check for errors + if (!result.Success) return ExecutionResult.Fail(diagnostics); + + // If it was a declaration, track it + if (node is DeclarationSyntax) + { + var semanticModel = compilation.GetSemanticModel(tree); + var symbol = semanticModel.GetDeclaredSymbolInternal(relocatedNode); + if (symbol is not null) this.context.AddSymbol(symbol); + } + + // We need to load the assembly in the current context + peStream.Position = 0; + var assembly = this.context.LoadAssembly(peStream); + + // Stash it for future use + this.previousEntries.Add(new HistoryEntry(Compilation: compilation, Assembly: assembly)); + + // Register the metadata reference + this.context.AddMetadataReference(MetadataReference.FromAssembly(assembly)); + + // Retrieve the main module + var mainModule = assembly.GetType(compilation.RootModulePath); + Debug.Assert(mainModule is not null); + + // Run the eval function + var eval = mainModule.GetMethod(EvalFunctionName); + if (eval is not null) + { + var value = (TResult?)eval.Invoke(null, null); + return ExecutionResult.Success(value!, diagnostics); + } + + // This happens with declarations, nothing to run + return ExecutionResult.Success(default(TResult)!, diagnostics); + } + + // func .eval(): object = decl; + private DeclarationSyntax ToDeclaration(ExpressionSyntax expr) => FunctionDeclaration( + EvalFunctionName, + ParameterList(), + NameType("object"), + InlineFunctionBody(expr)); + + // func .eval() = stmt; + private DeclarationSyntax ToDeclaration(StatementSyntax stmt) => FunctionDeclaration( + EvalFunctionName, + ParameterList(), + null, + InlineFunctionBody(StatementExpression(stmt))); + + private SyntaxTree ToSyntaxTree(DeclarationSyntax decl) => SyntaxTree.Create(CompilationUnit(decl)); + + private Compilation MakeCompilation(SyntaxTree tree) => Compilation.Create( + syntaxTrees: [tree], + metadataReferences: this.context.MetadataReferences, + flags: CompilationFlags.ImplicitPublicSymbols, + globalImports: this.context.GlobalImports, + rootModulePath: $"Context{this.previousEntries.Count}", + assemblyName: $"ReplAssembly{this.previousEntries.Count}", + metadataAssemblies: this.previousEntries.Count == 0 + ? null + : this.previousEntries[^1].Compilation.MetadataAssembliesDict); + + private static SyntaxTree ParseReplEntry(ISourceReader sourceReader) + { + var syntaxDiagnostics = new SyntaxDiagnosticTable(); + + // Construct a lexer + var lexer = new Lexer(sourceReader, syntaxDiagnostics); + // Construct a token source + var tokenSource = TokenSource.From(lexer); + // Construct a parser + var parser = new Parser(tokenSource, syntaxDiagnostics, parserMode: ParserMode.Repl); + // Parse a repl entry + var node = parser.ParseReplEntry(); + // Make it into a tree + var tree = SyntaxTree.Create(node); + + return tree; + } + + private static string ExtractImportPath(ImportPathSyntax path) => path switch + { + RootImportPathSyntax root => root.Name.Text, + MemberImportPathSyntax member => $"{ExtractImportPath(member.Accessed)}.{member.Member.Text}", + _ => throw new ArgumentOutOfRangeException(nameof(path)), + }; +} diff --git a/src/Draco.Compiler/Api/Scripting/ScriptingEngine.cs b/src/Draco.Compiler/Api/Scripting/ScriptingEngine.cs index 5454d1e91..f17b12367 100644 --- a/src/Draco.Compiler/Api/Scripting/ScriptingEngine.cs +++ b/src/Draco.Compiler/Api/Scripting/ScriptingEngine.cs @@ -1,4 +1,3 @@ -using System.Collections.Immutable; using System.IO; using System.Reflection; using Draco.Compiler.Api.Diagnostics; @@ -6,18 +5,6 @@ namespace Draco.Compiler.Api.Scripting; -/// -/// The result type of script execution. -/// -/// The expected result type. -/// True, if the execution was successful without errors. -/// The result of execution. -/// The s produced during execution. -public readonly record struct ExecutionResult( - bool Success, - TResult? Result, - ImmutableArray Diagnostics); - /// /// Exposes a scripting API. /// @@ -28,8 +15,7 @@ public static class ScriptingEngine /// /// The to execute. /// The result of the execution. - public static ExecutionResult Execute( - Compilation compilation) => + public static ExecutionResult Execute(Compilation compilation) => Execute(compilation); /// @@ -38,20 +24,13 @@ public static class ScriptingEngine /// The expected result type. /// The to execute. /// The result of the execution. - public static ExecutionResult Execute( - Compilation compilation) + public static ExecutionResult Execute(Compilation compilation) { using var peStream = new MemoryStream(); var emitResult = compilation.Emit(peStream: peStream); // Check emission results - if (!emitResult.Success) - { - return new( - Success: false, - Result: default, - Diagnostics: emitResult.Diagnostics); - } + if (!emitResult.Success) return ExecutionResult.Fail(emitResult.Diagnostics); // Load emitted bytes as assembly peStream.Position = 0; @@ -65,16 +44,10 @@ public static ExecutionResult Execute( var diag = Diagnostic.Create( template: CodegenErrors.NoMainMethod, location: Location.None); - return new( - Success: false, - Result: default, - Diagnostics: [diag]); + return ExecutionResult.Fail([diag]); } var result = (TResult?)mainMethod.Invoke(null, []); - return new( - Success: true, - Result: result, - Diagnostics: []); + return ExecutionResult.Success(result!); } } diff --git a/src/Draco.Compiler/Api/Semantics/SemanticModel.cs b/src/Draco.Compiler/Api/Semantics/SemanticModel.cs index f5806aef4..77dfe7294 100644 --- a/src/Draco.Compiler/Api/Semantics/SemanticModel.cs +++ b/src/Draco.Compiler/Api/Semantics/SemanticModel.cs @@ -137,9 +137,13 @@ public ImmutableArray GetAllDefinedSymbols(SyntaxNode node) /// The tree that is asked for the defined . /// The defined by , or null if it does not /// declared any. - public ISymbol? GetDeclaredSymbol(SyntaxNode syntax) + public ISymbol? GetDeclaredSymbol(SyntaxNode syntax) => this + .GetDeclaredSymbolInternal(syntax) + ?.ToApiSymbol(); + + internal Symbol? GetDeclaredSymbolInternal(SyntaxNode syntax) { - if (this.symbolMap.TryGetValue(syntax, out var existing)) return existing.ToApiSymbol(); + if (this.symbolMap.TryGetValue(syntax, out var existing)) return existing; // Get enclosing context var binder = this.GetBinder(syntax); @@ -150,14 +154,14 @@ public ImmutableArray GetAllDefinedSymbols(SyntaxNode node) case SourceFunctionSymbol func: { // This is just the function itself - if (func.DeclaringSyntax == syntax) return containingSymbol.ToApiSymbol(); + if (func.DeclaringSyntax == syntax) return containingSymbol; // Could be a generic parameter if (syntax is GenericParameterSyntax genericParam) { var paramSymbol = containingSymbol.GenericParameters .FirstOrDefault(p => p.DeclaringSyntax == syntax); - return paramSymbol?.ToApiSymbol(); + return paramSymbol; } // Bind the function contents @@ -166,17 +170,17 @@ public ImmutableArray GetAllDefinedSymbols(SyntaxNode node) // Look up inside the binder var symbol = binder.DeclaredSymbols .SingleOrDefault(sym => sym.DeclaringSyntax == syntax); - return symbol?.ToApiSymbol(); + return symbol; } case SourceModuleSymbol module: { // The module itself - if (module.DeclaringSyntaxes.Contains(syntax)) return containingSymbol.ToApiSymbol(); + if (module.DeclaringSyntaxes.Contains(syntax)) return containingSymbol; // Just search for the corresponding syntax var symbol = module.Members .SingleOrDefault(sym => sym.DeclaringSyntax == syntax); - return symbol?.ToApiSymbol(); + return symbol; } default: return null; @@ -189,7 +193,11 @@ public ImmutableArray GetAllDefinedSymbols(SyntaxNode node) /// The tree that is asked for the referenced . /// The referenced by , or null /// if it does not reference any. - public ISymbol? GetReferencedSymbol(SyntaxNode syntax) + public ISymbol? GetReferencedSymbol(SyntaxNode syntax) => this + .GetReferencedSymbolInternal(syntax) + ?.ToApiSymbol(); + + internal Symbol? GetReferencedSymbolInternal(SyntaxNode syntax) { if (syntax is ImportPathSyntax) { @@ -211,10 +219,10 @@ public ImmutableArray GetAllDefinedSymbols(SyntaxNode node) .SingleOrDefault(); // Not found in path if (pathSymbol is null) return null; - return pathSymbol.ToApiSymbol(); + return pathSymbol; } - if (this.symbolMap.TryGetValue(syntax, out var existing)) return existing.ToApiSymbol(); + if (this.symbolMap.TryGetValue(syntax, out var existing)) return existing; // Get enclosing context var binder = this.GetBinder(syntax); @@ -241,7 +249,7 @@ public ImmutableArray GetAllDefinedSymbols(SyntaxNode node) // Attempt to retrieve this.symbolMap.TryGetValue(syntax, out var symbol); - return symbol?.ToApiSymbol(); + return symbol; } private ImportBinder GetImportBinder(SyntaxNode syntax) diff --git a/src/Draco.Compiler/Api/Semantics/Symbol.cs b/src/Draco.Compiler/Api/Semantics/Symbol.cs index ef5d0e99c..e7563626a 100644 --- a/src/Draco.Compiler/Api/Semantics/Symbol.cs +++ b/src/Draco.Compiler/Api/Semantics/Symbol.cs @@ -176,14 +176,14 @@ public interface ITypeSymbol : ISymbol, IMemberSymbol } /// -/// Represents a type alias symbol. +/// Represents an alias symbol. /// -public interface ITypeAliasSymbol : ISymbol, IMemberSymbol +public interface IAliasSymbol : ISymbol, IMemberSymbol { /// - /// The type this alias substitutes. + /// The symbol this alias substitutes. /// - public ITypeSymbol Substitution { get; } + public ISymbol Substitution { get; } } /// @@ -302,12 +302,12 @@ internal sealed class TypeSymbol(Internal.Symbols.TypeSymbol type) public bool IsStatic => this.Symbol.IsStatic; } -internal sealed class TypeAliasSymbol(Internal.Symbols.TypeAliasSymbol type) - : SymbolBase(type), ITypeAliasSymbol +internal sealed class AliasSymbol(Internal.Symbols.AliasSymbol type) + : SymbolBase(type), IAliasSymbol { public bool IsStatic => this.Symbol.IsStatic; - public ITypeSymbol Substitution => this.Symbol.Substitution.ToApiSymbol(); + public ISymbol Substitution => this.Symbol.Substitution.ToApiSymbol(); } internal sealed class TypeParameterSymbol(Internal.Symbols.TypeParameterSymbol type) diff --git a/src/Draco.Compiler/Api/Syntax/SourceSpan.cs b/src/Draco.Compiler/Api/Syntax/SourceSpan.cs index a0c3a0271..652bfab70 100644 --- a/src/Draco.Compiler/Api/Syntax/SourceSpan.cs +++ b/src/Draco.Compiler/Api/Syntax/SourceSpan.cs @@ -12,6 +12,13 @@ public readonly record struct SourceSpan(int Start, int Length) /// public int End => this.Start + this.Length; + /// + /// Transforms this span to be relative to the given start index. + /// + /// The start index to make this span relative to. + /// A new span that is relative to . + public SourceSpan RelativeTo(int start) => new(this.Start - start, this.Length); + /// /// Checks if this span contains the given index. /// diff --git a/src/Draco.Compiler/Api/Syntax/SyntaxPosition.cs b/src/Draco.Compiler/Api/Syntax/SyntaxPosition.cs index 034bba6b2..09530e551 100644 --- a/src/Draco.Compiler/Api/Syntax/SyntaxPosition.cs +++ b/src/Draco.Compiler/Api/Syntax/SyntaxPosition.cs @@ -9,6 +9,15 @@ namespace Draco.Compiler.Api.Syntax; /// The 0-based column number. public readonly record struct SyntaxPosition(int Line, int Column) : IComparable { + /// + /// Computes the relative position relative to a starting point. + /// + /// The starting point. + /// The position relative to . + public SyntaxPosition RelativeTo(SyntaxPosition start) => start.Line == this.Line + ? new(Line: 0, Column: this.Column - start.Column) + : new(Line: this.Line - start.Line, Column: this.Column); + public int CompareTo(SyntaxPosition other) { var lineCmp = this.Line.CompareTo(other.Line); diff --git a/src/Draco.Compiler/Api/Syntax/SyntaxRange.cs b/src/Draco.Compiler/Api/Syntax/SyntaxRange.cs index 1fdc0e6d0..a6c8e4b89 100644 --- a/src/Draco.Compiler/Api/Syntax/SyntaxRange.cs +++ b/src/Draco.Compiler/Api/Syntax/SyntaxRange.cs @@ -9,6 +9,15 @@ public readonly record struct SyntaxRange(SyntaxPosition Start, SyntaxPosition E { public static readonly SyntaxRange Empty = default; + /// + /// Computes the relative range relative to a starting point. + /// + /// The starting point. + /// The range relative to . + public SyntaxRange RelativeTo(SyntaxPosition start) => new( + Start: this.Start.RelativeTo(start), + End: this.End.RelativeTo(start)); + /// /// Constructs a range from a starting position and length. /// diff --git a/src/Draco.Compiler/Api/Syntax/SyntaxTree.cs b/src/Draco.Compiler/Api/Syntax/SyntaxTree.cs index d21a19eae..6416b4a97 100644 --- a/src/Draco.Compiler/Api/Syntax/SyntaxTree.cs +++ b/src/Draco.Compiler/Api/Syntax/SyntaxTree.cs @@ -22,12 +22,15 @@ public sealed class SyntaxTree /// A new with and . public static SyntaxTree Create(SyntaxNode root, string? path = null) => Create(root.Green, path); - internal static SyntaxTree Create(Internal.Syntax.SyntaxNode root, string? path = null) => new( + internal static SyntaxTree Create( + Internal.Syntax.SyntaxNode root, + string? path = null, + SyntaxDiagnosticTable syntaxDiagnostics = default) => new( sourceText: SourceText.FromText( path: path is null ? null : new Uri(path), text: root.ToCode().AsMemory()), greenRoot: root, - syntaxDiagnostics: new()); + syntaxDiagnostics: syntaxDiagnostics); /// /// Parses the given text into a with . @@ -66,6 +69,11 @@ public static SyntaxTree Parse(SourceText source) LazyInitializer.EnsureInitialized(ref this.root, () => this.GreenRoot.ToRedNode(this, null, 0)); private SyntaxNode? root; + /// + /// True, if the tree has any errors. + /// + public bool HasErrors => this.Diagnostics.Any(d => d.Severity == DiagnosticSeverity.Error); + /// /// All messages that were produced during parsing this syntax tree. /// diff --git a/src/Draco.Compiler/Internal/Binding/BinderCache.cs b/src/Draco.Compiler/Internal/Binding/BinderCache.cs index 1fc9cf03e..502169677 100644 --- a/src/Draco.Compiler/Internal/Binding/BinderCache.cs +++ b/src/Draco.Compiler/Internal/Binding/BinderCache.cs @@ -44,6 +44,7 @@ public Binder GetBinder(SyntaxNode syntax) private Binder BuildCompilationUnitBinder(CompilationUnitSyntax syntax) { var binder = new IntrinsicsBinder(this.compilation) as Binder; + if (!this.compilation.GlobalImports.IsDefault) binder = new GlobalImportsBinder(binder); binder = new ModuleBinder(binder, this.compilation.RootModule); binder = new ModuleBinder(binder, this.compilation.GetModuleForSyntaxTree(syntax.Tree)); binder = WrapInImportBinder(binder, syntax); diff --git a/src/Draco.Compiler/Internal/Binding/BinderFacts.cs b/src/Draco.Compiler/Internal/Binding/BinderFacts.cs index e3e0e7fd0..7ac98fab5 100644 --- a/src/Draco.Compiler/Internal/Binding/BinderFacts.cs +++ b/src/Draco.Compiler/Internal/Binding/BinderFacts.cs @@ -43,7 +43,7 @@ or PropertySymbol or FunctionSymbol or ModuleSymbol or TypeSymbol - or TypeAliasSymbol; + or AliasSymbol; /// /// Checks, if a given symbol can be referenced in a type-context. @@ -53,7 +53,7 @@ or TypeSymbol public static bool IsTypeSymbol(Symbol symbol) => symbol is TypeSymbol or ModuleSymbol - or TypeAliasSymbol; + or AliasSymbol; /// /// Checks, if a given symbol can be referenced in a label-context. @@ -69,7 +69,7 @@ public static bool IsLabelSymbol(Symbol symbol) => symbol /// The symbol to check. /// True, if can be referenced in a non-type value context. public static bool IsNonTypeValueSymbol(Symbol symbol) => symbol - is not (TypeSymbol or TypeAliasSymbol) + is not TypeSymbol && IsValueSymbol(symbol); /// diff --git a/src/Draco.Compiler/Internal/Binding/Binder_Lookup.cs b/src/Draco.Compiler/Internal/Binding/Binder_Lookup.cs index 5364a301e..ad9c1818a 100644 --- a/src/Draco.Compiler/Internal/Binding/Binder_Lookup.cs +++ b/src/Draco.Compiler/Internal/Binding/Binder_Lookup.cs @@ -1,7 +1,6 @@ using System; using System.Collections.Generic; using System.Collections.Immutable; -using System.Diagnostics; using System.Linq; using Draco.Compiler.Api.Syntax; using Draco.Compiler.Internal.Diagnostics; diff --git a/src/Draco.Compiler/Internal/Binding/GlobalImportsBinder.cs b/src/Draco.Compiler/Internal/Binding/GlobalImportsBinder.cs new file mode 100644 index 000000000..adc94183d --- /dev/null +++ b/src/Draco.Compiler/Internal/Binding/GlobalImportsBinder.cs @@ -0,0 +1,77 @@ +using System; +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Linq; +using Draco.Compiler.Api; +using Draco.Compiler.Api.Syntax; +using Draco.Compiler.Internal.Symbols; +using Draco.Compiler.Internal.Symbols.Synthetized; + +namespace Draco.Compiler.Internal.Binding; + +/// +/// Binds the globally imported symbols. +/// +internal sealed class GlobalImportsBinder : Binder +{ + public override IEnumerable DeclaredSymbols => + InterlockedUtils.InitializeDefault(ref this.delcaredSymbols, this.BuildDeclaredSymbols); + private ImmutableArray delcaredSymbols; + + public GlobalImportsBinder(Compilation compilation) + : base(compilation) + { + } + + public GlobalImportsBinder(Binder parent) + : base(parent) + { + } + + internal override void LookupLocal(LookupResult result, string name, ref LookupFlags flags, Predicate allowSymbol, SyntaxNode? currentReference) + { + foreach (var symbol in this.DeclaredSymbols) + { + if (symbol.Name != name) continue; + if (!allowSymbol(symbol)) continue; + result.Add(symbol); + } + } + + private ImmutableArray BuildDeclaredSymbols() + { + var globalImports = this.Compilation.GlobalImports; + + var result = ImmutableArray.CreateBuilder(); + + if (!globalImports.ModuleImports.IsDefaultOrEmpty) + { + foreach (var path in globalImports.ModuleImports) + { + var parts = path.Split('.', StringSplitOptions.TrimEntries).ToImmutableArray(); + var symbol = this.Compilation.RootModule.Lookup(parts).SingleOrDefault(); + if (symbol is not ModuleSymbol module) + { + throw new InvalidOperationException($"the path '{path}' is invalid for global imports"); + } + result.AddRange(module.Members); + } + } + + if (!globalImports.ImportAliases.IsDefaultOrEmpty) + { + foreach (var (aliasName, aliasPath) in globalImports.ImportAliases) + { + var parts = aliasPath.Split('.', StringSplitOptions.TrimEntries).ToImmutableArray(); + var symbol = this.Compilation.RootModule.Lookup(parts).SingleOrDefault(); + if (symbol is null) + { + throw new InvalidOperationException($"no symbol found to alias as '{aliasName}' for path '{aliasPath}'"); + } + result.Add(new SynthetizedAliasSymbol(aliasName, symbol)); + } + } + + return result.ToImmutable(); + } +} diff --git a/src/Draco.Compiler/Internal/Binding/IntrinsicsBinder.cs b/src/Draco.Compiler/Internal/Binding/IntrinsicsBinder.cs index 75ab668af..2e9d76feb 100644 --- a/src/Draco.Compiler/Internal/Binding/IntrinsicsBinder.cs +++ b/src/Draco.Compiler/Internal/Binding/IntrinsicsBinder.cs @@ -7,7 +7,7 @@ namespace Draco.Compiler.Internal.Binding; /// -/// Binds compiler-intrinsic symbols +/// Binds compiler-intrinsic symbols. /// internal sealed class IntrinsicsBinder : Binder { diff --git a/src/Draco.Compiler/Internal/Binding/LookupResult.cs b/src/Draco.Compiler/Internal/Binding/LookupResult.cs index e2d965a78..70e53ce76 100644 --- a/src/Draco.Compiler/Internal/Binding/LookupResult.cs +++ b/src/Draco.Compiler/Internal/Binding/LookupResult.cs @@ -71,10 +71,10 @@ public bool Add(Symbol symbol) else { // Can be anything - if (symbol is TypeAliasSymbol typeAlias) + if (symbol is AliasSymbol alias) { - // Unwrap type alias - symbol = typeAlias.Substitution; + // Unwrap alias + symbol = alias.Substitution; } this.symbols.Add(symbol); return true; diff --git a/src/Draco.Compiler/Internal/Declarations/DeclarationTable.cs b/src/Draco.Compiler/Internal/Declarations/DeclarationTable.cs index 2c75f17fe..a567b8fc9 100644 --- a/src/Draco.Compiler/Internal/Declarations/DeclarationTable.cs +++ b/src/Draco.Compiler/Internal/Declarations/DeclarationTable.cs @@ -1,4 +1,3 @@ -using System.Collections.Generic; using System.Collections.Immutable; using System.Linq; using System.Threading; diff --git a/src/Draco.Compiler/Internal/Documentation/Extractors/XmlDocumentationExtractor.cs b/src/Draco.Compiler/Internal/Documentation/Extractors/XmlDocumentationExtractor.cs index 3721cbbbc..f7a292838 100644 --- a/src/Draco.Compiler/Internal/Documentation/Extractors/XmlDocumentationExtractor.cs +++ b/src/Draco.Compiler/Internal/Documentation/Extractors/XmlDocumentationExtractor.cs @@ -103,7 +103,7 @@ private ReferenceDocumentationElement ConstructReference(XmlNode node) } private Symbol? GetSymbolFromDocumentationName(string documentationName) => - this.Assembly.Compilation.MetadataAssemblies.Values + this.Assembly.Compilation.MetadataAssemblies .Select(x => x.RootNamespace.LookupByPrefixedDocumentationName(documentationName)) .OfType() .FirstOrDefault(); diff --git a/src/Draco.Compiler/Internal/OptimizingIr/FunctionBodyCodegen.cs b/src/Draco.Compiler/Internal/OptimizingIr/FunctionBodyCodegen.cs index a6face801..411d6d281 100644 --- a/src/Draco.Compiler/Internal/OptimizingIr/FunctionBodyCodegen.cs +++ b/src/Draco.Compiler/Internal/OptimizingIr/FunctionBodyCodegen.cs @@ -466,6 +466,7 @@ private void PatchStoreSource(IInstruction storeInstr, TypeSymbol targetType, IO public override IOperand VisitReturnExpression(BoundReturnExpression node) { var operand = this.Compile(node.Value); + operand = this.BoxIfNeeded(this.procedure.ReturnType, operand); this.Write(Ret(operand)); this.DetachBlock(); return default!; diff --git a/src/Draco.Compiler/Internal/OptimizingIr/Model/NewDelegateInstruction.cs b/src/Draco.Compiler/Internal/OptimizingIr/Model/NewDelegateInstruction.cs index fb9fa50ed..cc8140fee 100644 --- a/src/Draco.Compiler/Internal/OptimizingIr/Model/NewDelegateInstruction.cs +++ b/src/Draco.Compiler/Internal/OptimizingIr/Model/NewDelegateInstruction.cs @@ -1,5 +1,4 @@ using System.Collections.Generic; -using System.Linq; using Draco.Compiler.Internal.Symbols; namespace Draco.Compiler.Internal.OptimizingIr.Model; diff --git a/src/Draco.Compiler/Internal/Scripting/DetectOverpeekSourceReader.cs b/src/Draco.Compiler/Internal/Scripting/DetectOverpeekSourceReader.cs new file mode 100644 index 000000000..6c813b264 --- /dev/null +++ b/src/Draco.Compiler/Internal/Scripting/DetectOverpeekSourceReader.cs @@ -0,0 +1,49 @@ +using System; +using Draco.Compiler.Internal.Syntax; + +namespace Draco.Compiler.Internal.Scripting; + +/// +/// A special source reader that flags overpeeking, meaning it will signal +/// if the parser tries to peek after the passed in end of source. +/// +internal sealed class DetectOverpeekSourceReader(ISourceReader underlying) : ISourceReader +{ + // We report false to encourage the parser to peek freely initially, + // but once it has overpeeked, we will terminate this source as well to + // prevent an infinite parse loop. + public bool IsEnd => this.HasOverpeeked; + + public int Position + { + get => underlying.Position; + set => underlying.Position = value; + } + + /// + /// True, if the parser has overpeeked. + /// + public bool HasOverpeeked { get; private set; } + + public ReadOnlyMemory Advance(int amount = 1) => underlying.Advance(amount); + + public bool TryPeek(int offset, out char result) + { + if (!underlying.TryPeek(offset, out result)) + { + this.HasOverpeeked = true; + return false; + } + return true; + } + + public char Peek(int offset = 0, char @default = '\0') + { + if (!this.TryPeek(offset, out var result)) + { + this.HasOverpeeked = true; + return @default; + } + return result; + } +} diff --git a/src/Draco.Compiler/Internal/Scripting/ReplContext.cs b/src/Draco.Compiler/Internal/Scripting/ReplContext.cs new file mode 100644 index 000000000..178c970a1 --- /dev/null +++ b/src/Draco.Compiler/Internal/Scripting/ReplContext.cs @@ -0,0 +1,96 @@ +using System.Collections.Generic; +using System.Collections.Immutable; +using System.IO; +using System.Reflection; +using System.Runtime.Loader; +using Draco.Compiler.Api; +using Draco.Compiler.Internal.Symbols; + +namespace Draco.Compiler.Internal.Scripting; + +/// +/// Represents the execution context of the REPL. +/// This is a mutable class, keeping track of state and evolving with the REPL session. +/// +internal sealed class ReplContext +{ + /// + /// The global imports for the context to be used in the compilation. + /// + public GlobalImports GlobalImports => new( + this.globalImports.ToImmutable(), + this.globalAliases.ToImmutable()); + + /// + /// The metadata references for the context to be used in the compilation. + /// + public ImmutableArray MetadataReferences => this.metadataReferences.ToImmutable(); + + // Symbols and imports + private readonly ImmutableArray.Builder globalImports = ImmutableArray.CreateBuilder(); + private readonly ImmutableArray<(string Name, string FullPath)>.Builder globalAliases + = ImmutableArray.CreateBuilder<(string Name, string FullPath)>(); + + // Metadata references + private readonly ImmutableArray.Builder metadataReferences + = ImmutableArray.CreateBuilder(); + + // Assembly loading + private readonly AssemblyLoadContext assemblyLoadContext; + private readonly Dictionary loadedAssemblies = []; + + public ReplContext() + { + this.assemblyLoadContext = new AssemblyLoadContext(null, isCollectible: true); + this.assemblyLoadContext.Resolving += this.LoadContextResolving; + } + + /// + /// Adds a global import to the context. + /// + /// The import path to add. + public void AddImport(string path) => this.globalImports.Add(path); + + /// + /// Adds a symbol to be accessible in the context. + /// Might shadows earlier symbols added. + /// + /// The symbol to add to the context. + public void AddSymbol(Symbol symbol) + { + // We remove shadowed symbols + // NOTE: For simplicity and to not cross compilation borders, we remove by name + this.globalAliases.RemoveAll(s => s.Name == symbol.Name); + + // Add the new symbol + this.globalAliases.Add((symbol.Name, symbol.MetadataFullName)); + } + + /// + /// Adds a metadata reference to the context. + /// + /// The metadata reference to add. + public void AddMetadataReference(MetadataReference metadataReference) => + this.metadataReferences.Add(metadataReference); + + /// + /// Loads the given assembly into the memory context. + /// + /// The stream to load the assembly from. + /// The loaded assembly. + public Assembly LoadAssembly(Stream stream) + { + var assembly = this.assemblyLoadContext.LoadFromStream(stream); + var assemblyName = assembly.GetName().Name; + // TODO: This is a bad way to compare assemblies + if (assemblyName is not null) this.loadedAssemblies.Add(assemblyName, assembly); + return assembly; + } + + private Assembly? LoadContextResolving(AssemblyLoadContext context, AssemblyName name) + { + if (name.Name is null) return null; + // TODO: This is a bad way to compare assemblies + return this.loadedAssemblies.TryGetValue(name.Name, out var assembly) ? assembly : null; + } +} diff --git a/src/Draco.Compiler/Internal/Symbols/AliasSymbol.cs b/src/Draco.Compiler/Internal/Symbols/AliasSymbol.cs new file mode 100644 index 000000000..45c9eb5c1 --- /dev/null +++ b/src/Draco.Compiler/Internal/Symbols/AliasSymbol.cs @@ -0,0 +1,20 @@ +using Draco.Compiler.Api.Semantics; + +namespace Draco.Compiler.Internal.Symbols; + +/// +/// Alias for a symbol. +/// +internal abstract class AliasSymbol : Symbol, IMemberSymbol +{ + public bool IsStatic => true; + + /// + /// The symbol being aliased. + /// + public abstract Symbol Substitution { get; } + + public override void Accept(SymbolVisitor visitor) => visitor.VisitAlias(this); + public override TResult Accept(SymbolVisitor visitor) => visitor.VisitAlias(this); + public override ISymbol ToApiSymbol() => new Api.Semantics.AliasSymbol(this); +} diff --git a/src/Draco.Compiler/Internal/Symbols/FunctionSymbol.cs b/src/Draco.Compiler/Internal/Symbols/FunctionSymbol.cs index 062f0f5d1..fe5e34384 100644 --- a/src/Draco.Compiler/Internal/Symbols/FunctionSymbol.cs +++ b/src/Draco.Compiler/Internal/Symbols/FunctionSymbol.cs @@ -108,6 +108,7 @@ public delegate IOperand CodegenDelegate( public override Api.Semantics.Visibility Visibility => this.DeclaringSyntax switch { + _ when this.ImplicitPublicEnabled => Api.Semantics.Visibility.Public, FunctionDeclarationSyntax funcDecl => GetVisibilityFromTokenKind(funcDecl.VisibilityModifier?.Kind), _ => Api.Semantics.Visibility.Internal, }; diff --git a/src/Draco.Compiler/Internal/Symbols/Metadata/TypeProvider.cs b/src/Draco.Compiler/Internal/Symbols/Metadata/TypeProvider.cs index 4e6a62c82..abf465d30 100644 --- a/src/Draco.Compiler/Internal/Symbols/Metadata/TypeProvider.cs +++ b/src/Draco.Compiler/Internal/Symbols/Metadata/TypeProvider.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.Collections.Immutable; using System.Linq; +using System.Reflection; using System.Reflection.Metadata; using Draco.Compiler.Api; using Draco.Compiler.Internal.Symbols.Synthetized; @@ -193,7 +194,7 @@ private TypeSymbol BuildTypeFromReference(MetadataReader reader, TypeReferenceHa // TODO: If we don't have the assembly report error var assemblyName = reader.GetAssemblyReference((AssemblyReferenceHandle)resolutionScope).GetAssemblyName(); - var assembly = compilation.MetadataAssemblies.Values.Single(x => x.AssemblyName.FullName == assemblyName.FullName); + var assembly = compilation.MetadataAssemblies.Single(x => AssemblyNamesEqual(x.AssemblyName, assemblyName)); return assembly.RootNamespace.Lookup([.. parts]).OfType().Single(); } @@ -221,4 +222,11 @@ private static CacheKey BuildCacheKey(MetadataReader reader, TypeReferenceHandle private static string ConcatenateNamespaceAndName(string? @namespace, string name) => string.IsNullOrWhiteSpace(@namespace) ? name : $"{@namespace}.{name}"; + + // NOTE: For some reason we had to disregard public key token, otherwise some weird type referencing + // case in the REPL with lists threw an exception + // TODO: Could it be that we don't write the public key token in the type refs we use? + private static bool AssemblyNamesEqual(AssemblyName a, AssemblyName b) => + a.Name == b.Name + && a.Version == b.Version; } diff --git a/src/Draco.Compiler/Internal/Symbols/Symbol.cs b/src/Draco.Compiler/Internal/Symbols/Symbol.cs index 487cb026e..5cac6cf6e 100644 --- a/src/Draco.Compiler/Internal/Symbols/Symbol.cs +++ b/src/Draco.Compiler/Internal/Symbols/Symbol.cs @@ -173,6 +173,12 @@ public virtual string MetadataFullName /// public virtual ImmutableArray GenericArguments => []; + /// + /// True, if the compilation has implicit public symbol declarations enabled. + /// + protected bool ImplicitPublicEnabled => + this.DeclaringCompilation?.Flags.HasFlag(Api.CompilationFlags.ImplicitPublicSymbols) == true; + /// /// Checks if this symbol can be shadowed by symbol. /// diff --git a/src/Draco.Compiler/Internal/Symbols/SymbolVisitor.cs b/src/Draco.Compiler/Internal/Symbols/SymbolVisitor.cs index 1e9c26984..c15b3bb2c 100644 --- a/src/Draco.Compiler/Internal/Symbols/SymbolVisitor.cs +++ b/src/Draco.Compiler/Internal/Symbols/SymbolVisitor.cs @@ -18,9 +18,9 @@ public virtual void VisitType(TypeSymbol typeSymbol) foreach (var member in typeSymbol.Members) member.Accept(this); } - public virtual void VisitTypeAlias(TypeAliasSymbol typeAliasSymbol) + public virtual void VisitAlias(AliasSymbol aliasSymbol) { - typeAliasSymbol.Substitution.Accept(this); + aliasSymbol.Substitution.Accept(this); } public virtual void VisitTypeParameter(TypeParameterSymbol typeParameterSymbol) @@ -73,9 +73,9 @@ public virtual TResult VisitType(TypeSymbol typeSymbol) return default!; } - public virtual TResult VisitTypeAlias(TypeAliasSymbol typeAliasSymbol) + public virtual TResult VisitAlias(AliasSymbol aliasSymbol) { - typeAliasSymbol.Substitution.Accept(this); + aliasSymbol.Substitution.Accept(this); return default!; } diff --git a/src/Draco.Compiler/Internal/Symbols/Synthetized/SynthetizedAliasSymbol.cs b/src/Draco.Compiler/Internal/Symbols/Synthetized/SynthetizedAliasSymbol.cs new file mode 100644 index 000000000..6d9a1749e --- /dev/null +++ b/src/Draco.Compiler/Internal/Symbols/Synthetized/SynthetizedAliasSymbol.cs @@ -0,0 +1,10 @@ +namespace Draco.Compiler.Internal.Symbols.Synthetized; + +/// +/// An alias defined by the compiler. +/// +internal sealed class SynthetizedAliasSymbol(string name, Symbol substitution) : AliasSymbol +{ + public override string Name { get; } = name; + public override Symbol Substitution { get; } = substitution; +} diff --git a/src/Draco.Compiler/Internal/Symbols/Synthetized/SynthetizedTypeAliasSymbol.cs b/src/Draco.Compiler/Internal/Symbols/Synthetized/SynthetizedTypeAliasSymbol.cs deleted file mode 100644 index fcb20b2cd..000000000 --- a/src/Draco.Compiler/Internal/Symbols/Synthetized/SynthetizedTypeAliasSymbol.cs +++ /dev/null @@ -1,10 +0,0 @@ -namespace Draco.Compiler.Internal.Symbols.Synthetized; - -/// -/// A type-alias defined by the compiler. -/// -internal sealed class SynthetizedTypeAliasSymbol(string name, TypeSymbol substitution) : TypeAliasSymbol -{ - public override string Name { get; } = name; - public override TypeSymbol Substitution { get; } = substitution; -} diff --git a/src/Draco.Compiler/Internal/Symbols/TypeAliasSymbol.cs b/src/Draco.Compiler/Internal/Symbols/TypeAliasSymbol.cs deleted file mode 100644 index 8a865a95f..000000000 --- a/src/Draco.Compiler/Internal/Symbols/TypeAliasSymbol.cs +++ /dev/null @@ -1,20 +0,0 @@ -using Draco.Compiler.Api.Semantics; - -namespace Draco.Compiler.Internal.Symbols; - -/// -/// Alias for a type. Not a real type itself. -/// -internal abstract class TypeAliasSymbol : Symbol, IMemberSymbol -{ - public bool IsStatic => true; - - /// - /// The type being aliased. - /// - public abstract TypeSymbol Substitution { get; } - - public override void Accept(SymbolVisitor visitor) => visitor.VisitTypeAlias(this); - public override TResult Accept(SymbolVisitor visitor) => visitor.VisitTypeAlias(this); - public override ISymbol ToApiSymbol() => new Api.Semantics.TypeAliasSymbol(this); -} diff --git a/src/Draco.Compiler/Internal/Symbols/TypeSymbol.cs b/src/Draco.Compiler/Internal/Symbols/TypeSymbol.cs index dd3c7f80c..2183bcc47 100644 --- a/src/Draco.Compiler/Internal/Symbols/TypeSymbol.cs +++ b/src/Draco.Compiler/Internal/Symbols/TypeSymbol.cs @@ -161,7 +161,7 @@ private ImmutableArray BuildMembers() .OfType() .FirstOrDefault(f => !f.IsStatic && f.Name == "Invoke"); } - + private ImmutableArray BuildBaseTypes() => GraphTraversal.DepthFirst( start: this, getNeighbors: s => s.ImmediateBaseTypes, diff --git a/src/Draco.Compiler/Internal/Symbols/VariableSymbol.cs b/src/Draco.Compiler/Internal/Symbols/VariableSymbol.cs index 144e98107..899c1f0b1 100644 --- a/src/Draco.Compiler/Internal/Symbols/VariableSymbol.cs +++ b/src/Draco.Compiler/Internal/Symbols/VariableSymbol.cs @@ -16,6 +16,7 @@ internal abstract partial class VariableSymbol : Symbol, ITypedSymbol public override Api.Semantics.Visibility Visibility => this.DeclaringSyntax switch { + _ when this.ImplicitPublicEnabled => Api.Semantics.Visibility.Public, VariableDeclarationSyntax varDecl => GetVisibilityFromTokenKind(varDecl.VisibilityModifier?.Kind), _ => Api.Semantics.Visibility.Internal, }; diff --git a/src/Draco.Compiler/Internal/Symbols/WellKnownTypes.cs b/src/Draco.Compiler/Internal/Symbols/WellKnownTypes.cs index 3a3d236c9..fae67bfba 100644 --- a/src/Draco.Compiler/Internal/Symbols/WellKnownTypes.cs +++ b/src/Draco.Compiler/Internal/Symbols/WellKnownTypes.cs @@ -117,7 +117,7 @@ private IEnumerable GenerateWellKnownTypes() FromAllocating((codegen, target, operands) => codegen.Write(Call(target, this.SystemString_Concat, operands)))); } - private static SynthetizedTypeAliasSymbol Alias(string name, TypeSymbol type) => + private static SynthetizedAliasSymbol Alias(string name, TypeSymbol type) => new(name, type); #endregion @@ -193,13 +193,13 @@ public MetadataTypeSymbol GetTypeFromAssembly(MetadataAssemblySymbol assembly, I assembly.Lookup(path).OfType().Single(); private MetadataAssemblySymbol GetAssemblyWithAssemblyName(AssemblyName name) => - compilation.MetadataAssemblies.Values.Single(asm => AssemblyNameComparer.Full.Equals(asm.AssemblyName, name)); + compilation.MetadataAssemblies.Single(asm => AssemblyNameComparer.Full.Equals(asm.AssemblyName, name)); private MetadataAssemblySymbol GetAssemblyWithNameAndToken(string name, byte[] token) { var assemblyName = new AssemblyName() { Name = name }; assemblyName.SetPublicKeyToken(token); - return compilation.MetadataAssemblies.Values + return compilation.MetadataAssemblies .SingleOrDefault(asm => AssemblyNameComparer.NameAndToken.Equals(asm.AssemblyName, assemblyName)) ?? throw new InvalidOperationException($"Failed to locate assembly with name '{name}' and public key token '{BitConverter.ToString(token)}'."); } diff --git a/src/Draco.Compiler/Internal/Syntax/Lexer.cs b/src/Draco.Compiler/Internal/Syntax/Lexer.cs index d2bc20be9..7cefe4d0a 100644 --- a/src/Draco.Compiler/Internal/Syntax/Lexer.cs +++ b/src/Draco.Compiler/Internal/Syntax/Lexer.cs @@ -144,7 +144,8 @@ public SyntaxToken Lex() case ModeKind.MultiLineString: this.LexString(); // If we are starting interpolation, we can consume trailing trivia - if (this.tokenBuilder.Kind == TokenKind.InterpolationStart) this.ParseTrailingTriviaList(); + if (this.tokenBuilder.Kind == TokenKind.InterpolationStart + || this.tokenBuilder.Kind == TokenKind.LineStringEnd) this.ParseTrailingTriviaList(); break; default: diff --git a/src/Draco.Compiler/Internal/Syntax/Parser.cs b/src/Draco.Compiler/Internal/Syntax/Parser.cs index d55352926..1b12d15cd 100644 --- a/src/Draco.Compiler/Internal/Syntax/Parser.cs +++ b/src/Draco.Compiler/Internal/Syntax/Parser.cs @@ -9,10 +9,29 @@ namespace Draco.Compiler.Internal.Syntax; +/// +/// The different parser modes. +/// +internal enum ParserMode +{ + /// + /// The default parsing mode. + /// + None, + + /// + /// A mode that bails out as early as possible for expression parsing when a newline is encountered. + /// + Repl, +} + /// /// Parses a sequence of s into a . /// -internal sealed class Parser(ITokenSource tokenSource, SyntaxDiagnosticTable diagnostics) +internal sealed class Parser( + ITokenSource tokenSource, + SyntaxDiagnosticTable diagnostics, + ParserMode parserMode = ParserMode.None) { /// /// The different declaration contexts. @@ -116,7 +135,7 @@ private ExpressionParserDelegate BinaryLeft(params TokenKind[] operators) => lev { // We unroll left-associativity into a loop var result = this.ParseExpression(level + 1); - while (true) + while (!this.CanBailOut(result)) { var opKind = this.Peek(); if (!operators.Contains(opKind)) break; @@ -137,6 +156,7 @@ private ExpressionParserDelegate BinaryRight(params TokenKind[] operators) => le { // Right-associativity is simply right-recursion var result = this.ParseExpression(level + 1); + if (this.CanBailOut(result)) return result; var opKind = this.Peek(); if (operators.Contains(this.Peek())) { @@ -233,6 +253,44 @@ public CompilationUnitSyntax ParseCompilationUnit() return new(decls.ToSyntaxList(), end); } + /// + /// Parses a REPL entry. + /// + /// The parsed . + public SyntaxNode ParseReplEntry() + { + var visibility = this.ParseVisibilityModifier(); + if (visibility is not null) + { + // Must be a declaration + return this.ParseDeclaration(); + } + + // NOTE: We don't use IsDeclarationStarter here to avoid peeking for a label + if (declarationStarters.Contains(this.Peek())) + { + // Must be a declaration + return this.ParseDeclaration(); + } + + // Either an expression or a statement + // We can start by parsing an expression + var expr = this.ParseExpression(); + + // If there is a newline in the last token of the expression, we can assume it's an expression + if (this.CanBailOut(expr)) return expr; + + // We can peek for a semicolon + if (this.Matches(TokenKind.Semicolon, out var semicolon)) + { + // It's a statement + return new ExpressionStatementSyntax(expr, semicolon); + } + + // It's an expression + return expr; + } + /// /// Parses a global-level declaration. /// @@ -924,6 +982,8 @@ private ExpressionSyntax ParsePseudoStatementLevelExpression(int level) private ExpressionSyntax ParseRelationalLevelExpression(int level) { var left = this.ParseExpression(level + 1); + if (this.CanBailOut(left)) return left; + var comparisons = SyntaxList.CreateBuilder(); while (true) { @@ -932,6 +992,7 @@ private ExpressionSyntax ParseRelationalLevelExpression(int level) var op = this.Advance(); var right = this.ParseExpression(level + 1); comparisons.Add(new(op, right)); + if (this.CanBailOut(right)) break; } return comparisons.Count == 0 ? left @@ -941,7 +1002,7 @@ private ExpressionSyntax ParseRelationalLevelExpression(int level) private ExpressionSyntax ParseCallLevelExpression(int level) { var result = this.ParseExpression(level + 1); - while (true) + while (!this.CanBailOut(result)) { var peek = this.Peek(); if (peek == TokenKind.ParenOpen) @@ -1301,6 +1362,12 @@ private SeparatedSyntaxList ParseSeparatedSyntaxList( private SyntaxToken? ParseVisibilityModifier() => IsVisibilityModifier(this.Peek()) ? this.Advance() : null; + private bool CanBailOut(SyntaxNode node) + { + if (parserMode != ParserMode.Repl) return false; + return node.LastToken?.TrailingTrivia.Any(t => t.Kind == TriviaKind.Newline) == true; + } + // Token-level operators /// diff --git a/src/Draco.Compiler/Internal/Syntax/SourceReader.cs b/src/Draco.Compiler/Internal/Syntax/SourceReader.cs index 7b8da6ef4..315f49633 100644 --- a/src/Draco.Compiler/Internal/Syntax/SourceReader.cs +++ b/src/Draco.Compiler/Internal/Syntax/SourceReader.cs @@ -1,4 +1,6 @@ using System; +using System.Collections.Generic; +using System.IO; namespace Draco.Compiler.Internal.Syntax; @@ -79,6 +81,45 @@ public bool TryPeek(int offset, out char result) } } + private sealed class TextReaderSourceReader(TextReader reader) : ISourceReader + { + public bool IsEnd => !this.TryPeek(0, out _); + public int Position { get; set; } + + private readonly List peekBuffer = []; + + public ReadOnlyMemory Advance(int amount = 1) + { + if (amount == 0) return ReadOnlyMemory.Empty; + + this.TryPeek(amount - 1, out _); + var result = this.peekBuffer.GetRange(0, amount).ToArray(); + this.peekBuffer.RemoveRange(0, amount); + this.Position += amount; + return result; + } + + public char Peek(int offset = 0, char @default = '\0') => this.TryPeek(offset, out var result) + ? result + : @default; + + public bool TryPeek(int offset, out char result) + { + while (offset >= this.peekBuffer.Count) + { + var read = reader.Read(); + if (read == -1) + { + result = default; + return false; + } + this.peekBuffer.Add((char)read); + } + result = this.peekBuffer[offset]; + return true; + } + } + /// /// Constructs an from a . /// @@ -92,4 +133,11 @@ public bool TryPeek(int offset, out char result) /// The source text as a . /// An reading . public static ISourceReader From(string source) => new MemorySourceReader(source.AsMemory()); + + /// + /// Constructs an from a . + /// + /// The to read from. + /// An reading from . + public static ISourceReader From(TextReader reader) => new TextReaderSourceReader(reader); } diff --git a/src/Draco.Compiler/Internal/Syntax/SyntaxDiagnosticTable.cs b/src/Draco.Compiler/Internal/Syntax/SyntaxDiagnosticTable.cs index 885a5e56a..9a8ca37c6 100644 --- a/src/Draco.Compiler/Internal/Syntax/SyntaxDiagnosticTable.cs +++ b/src/Draco.Compiler/Internal/Syntax/SyntaxDiagnosticTable.cs @@ -1,5 +1,5 @@ +using System; using System.Collections.Generic; -using System.Collections.Immutable; using System.Linq; using System.Runtime.CompilerServices; using Draco.Compiler.Api.Diagnostics; @@ -12,6 +12,18 @@ namespace Draco.Compiler.Internal.Syntax; /// internal readonly struct SyntaxDiagnosticTable { + /// + /// True, if there are any errors in the diagnostics table. + /// + public bool HasErrors => !this.IsDefault && this.diagnostics + .SelectMany(diags => diags.Value) + .Any(d => d.Info.Severity == DiagnosticSeverity.Error); + + /// + /// True, if this table is a default, uninitialized instance. + /// + public bool IsDefault => this.diagnostics is null; + private readonly ConditionalWeakTable> diagnostics = []; public SyntaxDiagnosticTable() @@ -31,9 +43,8 @@ public IEnumerable Get(Api.Syntax.SyntaxNode node) => /// /// The to retrieve the messages for. /// All messages for . - public IReadOnlyCollection Get(SyntaxNode node) => this.diagnostics.TryGetValue(node, out var diagnostics) - ? diagnostics - : ImmutableArray.Empty; + public IReadOnlyCollection Get(SyntaxNode node) => + (!this.IsDefault && this.diagnostics.TryGetValue(node, out var diagnostics)) ? diagnostics : []; /// /// Adds a to the given . @@ -53,6 +64,7 @@ public void AddRange(SyntaxNode node, IEnumerable diagnost private List GetDiagnosticList(SyntaxNode node) { + if (this.IsDefault) throw new InvalidOperationException("cannot add diagnostics to a default table"); if (!this.diagnostics.TryGetValue(node, out var diagnosticList)) { diagnosticList = []; diff --git a/src/Draco.LanguageServer/DracoLanguageServer.cs b/src/Draco.LanguageServer/DracoLanguageServer.cs index 7aaca54bf..e1ba0f0e0 100644 --- a/src/Draco.LanguageServer/DracoLanguageServer.cs +++ b/src/Draco.LanguageServer/DracoLanguageServer.cs @@ -39,9 +39,9 @@ internal sealed partial class DracoLanguageServer : ILanguageServer private Uri rootUri; private volatile Compilation compilation; - private readonly CompletionService completionService; - private readonly SignatureService signatureService; - private readonly CodeFixService codeFixService; + private readonly CompletionService completionService = CompletionService.CreateDefault(); + private readonly SignatureService signatureService = new(); + private readonly CodeFixService codeFixService = CodeFixService.CreateDefault(); public DracoLanguageServer(ILanguageClient client) { @@ -53,16 +53,6 @@ public DracoLanguageServer(ILanguageClient client) this.compilation = Compilation.Create( syntaxTrees: [], metadataReferences: []); - - this.completionService = new CompletionService(); - this.completionService.AddProvider(new KeywordCompletionProvider()); - this.completionService.AddProvider(new ExpressionCompletionProvider()); - this.completionService.AddProvider(new MemberCompletionProvider()); - - this.signatureService = new SignatureService(); - - this.codeFixService = new CodeFixService(); - this.codeFixService.AddProvider(new ImportCodeFixProvider()); } private async Task CreateCompilation() diff --git a/src/Draco.Repl/Draco.Repl.csproj b/src/Draco.Repl/Draco.Repl.csproj new file mode 100644 index 000000000..71136d098 --- /dev/null +++ b/src/Draco.Repl/Draco.Repl.csproj @@ -0,0 +1,19 @@ + + + + Exe + + + true + dracorepl + + + + + + + + + + + diff --git a/src/Draco.Repl/Program.cs b/src/Draco.Repl/Program.cs new file mode 100644 index 000000000..5c6abaa9b --- /dev/null +++ b/src/Draco.Repl/Program.cs @@ -0,0 +1,49 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using Draco.Compiler.Api; +using Draco.Compiler.Api.Scripting; +using static Basic.Reference.Assemblies.Net80; + +namespace Draco.Repl; + +internal static class Program +{ + // TODO: Temporary until we find out how we can inherit everything from the host + private static IEnumerable BclReferences => ReferenceInfos.All + .Select(r => MetadataReference.FromPeStream(new MemoryStream(r.ImageBytes))); + + internal static void Main(string[] args) + { + var session = new ReplSession([.. BclReferences]); + session.AddImports( + "System", + "System.Collections.Generic", + "System.Linq"); + + while (true) + { + Console.Write("> "); + var result = session.Evaluate(Console.In); + PrintResult(result); + } + } + + private static void PrintResult(ExecutionResult result) + { + if (result.Success) + { + Console.WriteLine(result.Value); + } + else + { + var oldColor = Console.ForegroundColor; + Console.ForegroundColor = ConsoleColor.Red; + + foreach (var diagnostic in result.Diagnostics) Console.WriteLine(diagnostic); + + Console.ForegroundColor = oldColor; + } + } +} diff --git a/src/Draco.sln b/src/Draco.sln index e6c20e5e6..e63f6a0eb 100644 --- a/src/Draco.sln +++ b/src/Draco.sln @@ -47,11 +47,13 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Draco.Debugger.Tests", "Dra EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Draco.Chr", "Draco.Chr\Draco.Chr.csproj", "{E8DF7730-54AE-45F1-9D36-809B444A5F7A}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Draco.Chr.Tests", "Draco.Chr.Tests\Draco.Chr.Tests.csproj", "{88839EDB-4B29-4A96-A7C7-D9904EA9C988}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Draco.Chr.Tests", "Draco.Chr.Tests\Draco.Chr.Tests.csproj", "{88839EDB-4B29-4A96-A7C7-D9904EA9C988}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Draco.Compiler.DevHost", "Draco.Compiler.DevHost\Draco.Compiler.DevHost.csproj", "{B209F6B3-9B49-4269-824A-1EEC35B39C92}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Draco.ProjectSystem", "Draco.ProjectSystem\Draco.ProjectSystem.csproj", "{5A813218-38B9-4B7D-A352-47978EBB0554}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Draco.ProjectSystem", "Draco.ProjectSystem\Draco.ProjectSystem.csproj", "{5A813218-38B9-4B7D-A352-47978EBB0554}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Draco.Repl", "Draco.Repl\Draco.Repl.csproj", "{C0ED09B0-D26C-4CC7-8F76-E2167F7B3FD3}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution @@ -192,6 +194,12 @@ Global {5A813218-38B9-4B7D-A352-47978EBB0554}.Nuget|Any CPU.Build.0 = Debug|Any CPU {5A813218-38B9-4B7D-A352-47978EBB0554}.Release|Any CPU.ActiveCfg = Release|Any CPU {5A813218-38B9-4B7D-A352-47978EBB0554}.Release|Any CPU.Build.0 = Release|Any CPU + {C0ED09B0-D26C-4CC7-8F76-E2167F7B3FD3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {C0ED09B0-D26C-4CC7-8F76-E2167F7B3FD3}.Debug|Any CPU.Build.0 = Debug|Any CPU + {C0ED09B0-D26C-4CC7-8F76-E2167F7B3FD3}.Nuget|Any CPU.ActiveCfg = Debug|Any CPU + {C0ED09B0-D26C-4CC7-8F76-E2167F7B3FD3}.Nuget|Any CPU.Build.0 = Debug|Any CPU + {C0ED09B0-D26C-4CC7-8F76-E2167F7B3FD3}.Release|Any CPU.ActiveCfg = Release|Any CPU + {C0ED09B0-D26C-4CC7-8F76-E2167F7B3FD3}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE