diff --git a/src/Draco.Compiler.DevHost/Draco.Compiler.DevHost.csproj b/src/Draco.Compiler.DevHost/Draco.Compiler.DevHost.csproj new file mode 100644 index 000000000..4783564fd --- /dev/null +++ b/src/Draco.Compiler.DevHost/Draco.Compiler.DevHost.csproj @@ -0,0 +1,18 @@ + + + + Exe + net7.0 + enable + + + + + + + + + + + + diff --git a/src/Draco.Compiler.DevHost/Program.cs b/src/Draco.Compiler.DevHost/Program.cs new file mode 100644 index 000000000..8053a7650 --- /dev/null +++ b/src/Draco.Compiler.DevHost/Program.cs @@ -0,0 +1,226 @@ +using System; +using System.Collections.Generic; +using System.Collections.Immutable; +using System.CommandLine; +using System.IO; +using System.Linq; +using Draco.Compiler.Api; +using Draco.Compiler.Api.Diagnostics; +using Draco.Compiler.Api.Scripting; +using Draco.Compiler.Api.Syntax; +using static Basic.Reference.Assemblies.Net70; + +namespace Draco.Compiler.DevHost; + +internal class Program +{ + private static IEnumerable BclReferences => Basic.Reference.Assemblies.Net70.ReferenceInfos.All + .Select(r => MetadataReference.FromPeStream(new MemoryStream(r.ImageBytes))); + + internal static int Main(string[] args) => + ConfigureCommands().Invoke(args); + + private static RootCommand ConfigureCommands() + { + var fileArgument = new Argument("source file", description: "The Draco source file"); + var outputOption = new Option(new string[] { "-o", "--output" }, () => new FileInfo("output"), "Specifies the output file"); + var optionalOutputOption = new Option(new string[] { "-o", "--output" }, () => null, "Specifies the (optional) output file"); + var referencesOption = new Option(new string[] { "-r", "--reference" }, Array.Empty, "Specifies additional assembly references to use when compiling"); + var filesArgument = new Argument("source files", Array.Empty, "Specifies draco source files that should be compiled"); + var rootModuleOption = new Option(new string[] { "-m", "--root-module" }, () => null, "Specifies the root module folder of the compiled files"); + + // Compile + + var compileCommand = new Command("compile", "Compiles the Draco program") + { + filesArgument, + outputOption, + rootModuleOption, + referencesOption, + }; + compileCommand.SetHandler(CompileCommand, filesArgument, outputOption, rootModuleOption, referencesOption); + + // Run + + var runCommand = new Command("run", "Runs the Draco program") + { + filesArgument, + rootModuleOption, + referencesOption, + }; + runCommand.SetHandler(RunCommand, filesArgument, rootModuleOption, referencesOption); + + // IR code + + var irCommand = new Command("ir", "Generates the intermediate-representation of the Draco program") + { + filesArgument, + rootModuleOption, + optionalOutputOption, + }; + irCommand.SetHandler(IrCommand, filesArgument, rootModuleOption, optionalOutputOption); + + // Symbol tree + + var symbolsCommand = new Command("symbols", "Prints the symbol-tree of the program") + { + filesArgument, + rootModuleOption, + optionalOutputOption, + }; + symbolsCommand.SetHandler(SymbolsCommand, filesArgument, rootModuleOption, optionalOutputOption); + + // Declaration tree + + var declarationsCommand = new Command("declarations", "Prints the declarations-tree of the program") + { + filesArgument, + rootModuleOption, + optionalOutputOption, + }; + declarationsCommand.SetHandler(DeclarationsCommand, filesArgument, rootModuleOption, optionalOutputOption); + + // Formatting + + var formatCommand = new Command("format", "Formats contents of the specified Draco file") + { + fileArgument, + optionalOutputOption, + }; + formatCommand.SetHandler(FormatCommand, fileArgument, optionalOutputOption); + + return new RootCommand("CLI for the Draco compiler") + { + compileCommand, + runCommand, + irCommand, + symbolsCommand, + declarationsCommand, + formatCommand + }; + } + + private static void CompileCommand(FileInfo[] input, FileInfo output, DirectoryInfo? rootModule, FileInfo[] references) + { + var syntaxTrees = GetSyntaxTrees(input); + var (path, name) = ExtractOutputPathAndName(output); + var compilation = Compilation.Create( + syntaxTrees: syntaxTrees, + metadataReferences: references + .Select(r => MetadataReference.FromPeStream(r.OpenRead())) + .Concat(BclReferences) + .ToImmutableArray(), + rootModulePath: rootModule?.FullName, + outputPath: path, + assemblyName: name); + using var peStream = new FileStream(Path.ChangeExtension(output.FullName, ".dll"), FileMode.OpenOrCreate); + using var pdbStream = new FileStream(Path.ChangeExtension(output.FullName, ".pdb"), FileMode.OpenOrCreate); + var emitResult = compilation.Emit( + peStream: peStream, + pdbStream: pdbStream); + EmitDiagnostics(emitResult); + } + + private static void RunCommand(FileInfo[] input, DirectoryInfo? rootModule, FileInfo[] references) + { + var syntaxTrees = GetSyntaxTrees(input); + var compilation = Compilation.Create( + syntaxTrees: syntaxTrees, + metadataReferences: references + .Select(r => MetadataReference.FromPeStream(r.OpenRead())) + .Concat(BclReferences) + .ToImmutableArray(), + rootModulePath: rootModule?.FullName); + var execResult = ScriptingEngine.Execute(compilation); + if (!EmitDiagnostics(execResult)) + { + Console.WriteLine($"Result: {execResult.Result}"); + } + } + + private static void IrCommand(FileInfo[] input, DirectoryInfo? rootModule, FileInfo? output) + { + var syntaxTrees = GetSyntaxTrees(input); + var compilation = Compilation.Create( + syntaxTrees: syntaxTrees, + // TODO: Add references from CLI? + metadataReferences: BclReferences.ToImmutableArray(), + rootModulePath: rootModule?.FullName); + using var irStream = OpenOutputOrStdout(output); + var emitResult = compilation.Emit(irStream: irStream); + EmitDiagnostics(emitResult); + } + + private static void SymbolsCommand(FileInfo[] input, DirectoryInfo? rootModule, FileInfo? output) + { + var syntaxTrees = GetSyntaxTrees(input); + var compilation = Compilation.Create( + syntaxTrees: syntaxTrees, + rootModulePath: rootModule?.FullName); + using var symbolsStream = OpenOutputOrStdout(output); + var emitResult = compilation.Emit(symbolTreeStream: symbolsStream); + EmitDiagnostics(emitResult); + } + + private static void DeclarationsCommand(FileInfo[] input, DirectoryInfo? rootModule, FileInfo? output) + { + var syntaxTrees = GetSyntaxTrees(input); + var compilation = Compilation.Create( + syntaxTrees: syntaxTrees, + rootModulePath: rootModule?.FullName); + using var declarationStream = OpenOutputOrStdout(output); + var emitResult = compilation.Emit(declarationTreeStream: declarationStream); + EmitDiagnostics(emitResult); + } + + private static void FormatCommand(FileInfo input, FileInfo? output) + { + var syntaxTree = GetSyntaxTrees(input).First(); + using var outputStream = OpenOutputOrStdout(output); + new StreamWriter(outputStream).Write(syntaxTree.Format().ToString()); + } + + private static ImmutableArray GetSyntaxTrees(params FileInfo[] input) + { + var result = ImmutableArray.CreateBuilder(); + foreach (var file in input) + { + var sourceText = SourceText.FromFile(file.FullName); + result.Add(SyntaxTree.Parse(sourceText)); + } + return result.ToImmutable(); + } + + private static bool EmitDiagnostics(EmitResult result) + { + if (result.Success) return false; + foreach (var diag in result.Diagnostics) + { + Console.Error.WriteLine(diag.ToString()); + } + return true; + } + + private static bool EmitDiagnostics(ExecutionResult result) + { + if (result.Success) return false; + foreach (var diag in result.Diagnostics) + { + Console.Error.WriteLine(diag.ToString()); + } + return true; + } + + private static (string Path, string Name) ExtractOutputPathAndName(FileInfo outputInfo) + { + var outputPath = outputInfo.FullName; + var path = Path.GetDirectoryName(outputPath) ?? string.Empty; + path = Path.GetFullPath(path); + var name = Path.GetFileNameWithoutExtension(outputPath) ?? string.Empty; + return (path, name); + } + + private static Stream OpenOutputOrStdout(FileInfo? output) => output is null + ? Console.OpenStandardOutput() + : output.Open(FileMode.OpenOrCreate, FileAccess.Write); +} diff --git a/src/Draco.Compiler/Api/Syntax/SyntaxFacts.cs b/src/Draco.Compiler/Api/Syntax/SyntaxFacts.cs index d51a7bde5..26ed66ea8 100644 --- a/src/Draco.Compiler/Api/Syntax/SyntaxFacts.cs +++ b/src/Draco.Compiler/Api/Syntax/SyntaxFacts.cs @@ -18,8 +18,10 @@ public static class SyntaxFacts TokenKind.EndOfInput => string.Empty, TokenKind.InterpolationEnd => "}", TokenKind.KeywordAnd => "and", + TokenKind.KeywordClass => "class", TokenKind.KeywordElse => "else", TokenKind.KeywordFalse => "false", + TokenKind.KeywordField => "field", TokenKind.KeywordFor => "for", TokenKind.KeywordFunc => "func", TokenKind.KeywordGoto => "goto", @@ -36,6 +38,7 @@ public static class SyntaxFacts TokenKind.KeywordReturn => "return", TokenKind.KeywordTrue => "true", TokenKind.KeywordVal => "val", + TokenKind.KeywordValue => "value", TokenKind.KeywordVar => "var", TokenKind.KeywordWhile => "while", TokenKind.ParenOpen => "(", diff --git a/src/Draco.Compiler/Api/Syntax/TokenKind.cs b/src/Draco.Compiler/Api/Syntax/TokenKind.cs index 9c6c533e5..0b5236ad7 100644 --- a/src/Draco.Compiler/Api/Syntax/TokenKind.cs +++ b/src/Draco.Compiler/Api/Syntax/TokenKind.cs @@ -80,6 +80,11 @@ public enum TokenKind /// KeywordAnd, + /// + /// The keyword 'class'. + /// + KeywordClass, + /// /// The keyword 'else'. /// @@ -90,6 +95,11 @@ public enum TokenKind /// KeywordFalse, + /// + /// The keyword 'field'. + /// + KeywordField, + /// /// The keyword 'for'. /// @@ -170,6 +180,11 @@ public enum TokenKind /// KeywordVal, + /// + /// The keyword 'value'. + /// + KeywordValue, + /// /// The keyword 'var'. /// diff --git a/src/Draco.Compiler/Internal/Binding/LocalBinder.cs b/src/Draco.Compiler/Internal/Binding/LocalBinder.cs index 2435a7d4c..29f2f6e8d 100644 --- a/src/Draco.Compiler/Internal/Binding/LocalBinder.cs +++ b/src/Draco.Compiler/Internal/Binding/LocalBinder.cs @@ -171,7 +171,7 @@ private void Build() private Symbol? BuildSymbol(SyntaxNode syntax, int localCount) => syntax switch { FunctionDeclarationSyntax function => new SourceFunctionSymbol(this.ContainingSymbol, function), - ParameterSyntax parameter => new SourceParameterSymbol(this.ContainingSymbol, parameter), + ParameterSyntax parameter => new SourceParameterSymbol((FunctionSymbol)this.ContainingSymbol, parameter), VariableDeclarationSyntax variable => new SourceLocalSymbol(this.ContainingSymbol, new TypeVariable(localCount), variable), LabelDeclarationSyntax label => new SourceLabelSymbol(this.ContainingSymbol, label), _ => null, diff --git a/src/Draco.Compiler/Internal/Codegen/CilCodegen.cs b/src/Draco.Compiler/Internal/Codegen/CilCodegen.cs index 2cdc36031..4555eb8d8 100644 --- a/src/Draco.Compiler/Internal/Codegen/CilCodegen.cs +++ b/src/Draco.Compiler/Internal/Codegen/CilCodegen.cs @@ -73,7 +73,9 @@ public CilCodegen(MetadataCodegen metadataCodegen, IProcedure procedure) private EntityHandle GetHandle(Symbol symbol) => this.metadataCodegen.GetEntityHandle(symbol); // TODO: Parameters don't handle unit yet, it introduces some signature problems - private int GetParameterIndex(ParameterSymbol parameter) => this.procedure.GetParameterIndex(parameter); + private int GetParameterIndex(ParameterSymbol parameter) => parameter.IsThis + ? 0 + : this.procedure.GetParameterIndex(parameter) + (parameter.ContainingSymbol.IsStatic ? 0 : 1); private AllocatedLocal? GetAllocatedLocal(LocalSymbol local) { diff --git a/src/Draco.Compiler/Internal/Codegen/MetadataCodegen.cs b/src/Draco.Compiler/Internal/Codegen/MetadataCodegen.cs index 00eb7f82b..30ae31c64 100644 --- a/src/Draco.Compiler/Internal/Codegen/MetadataCodegen.cs +++ b/src/Draco.Compiler/Internal/Codegen/MetadataCodegen.cs @@ -7,6 +7,7 @@ using System.Reflection.Metadata; using System.Reflection.Metadata.Ecma335; using System.Reflection.PortableExecutable; +using System.Xml.Linq; using Draco.Compiler.Api; using Draco.Compiler.Internal.OptimizingIr.Model; using Draco.Compiler.Internal.Symbols; @@ -379,15 +380,27 @@ private void EncodeAssembly() } } - private void EncodeModule(IModule module, TypeDefinitionHandle? parentModule = null, int fieldIndex = 1, int procIndex = 1) + private void EncodeModule(IModule module) { - var currentFieldIndex = fieldIndex; - var currentProcIndex = procIndex; + var fieldIndex = 1; + var procIndex = 1; + this.EncodeModule(module, parent: null, fieldIndex: ref fieldIndex, procIndex: ref procIndex); + } + + private void EncodeModule( + IModule module, + TypeDefinitionHandle? parent, + ref int fieldIndex, + ref int procIndex) + { + var startFieldIndex = fieldIndex; + var startProcIndex = procIndex; + // Go through globals foreach (var global in module.Globals) { this.EncodeGlobal(global); - currentFieldIndex++; + ++fieldIndex; } // Go through procedures @@ -401,22 +414,17 @@ private void EncodeModule(IModule module, TypeDefinitionHandle? parentModule = n // If this is the entry point, save it if (ReferenceEquals(this.assembly.EntryPoint, procedure)) this.EntryPointHandle = handle; - currentProcIndex++; + + ++procIndex; } // Compile global initializer too - this.EncodeProcedure(module.GlobalInitializer, specialName: ".cctor"); - currentProcIndex++; + this.EncodeProcedure(module.GlobalInitializer); + ++procIndex; - TypeAttributes visibility; - if (module.Symbol.Visibility == Api.Semantics.Visibility.Public) - { - visibility = parentModule is not null ? TypeAttributes.NestedPublic : TypeAttributes.Public; - } - else - { - visibility = parentModule is not null ? TypeAttributes.NestedAssembly : TypeAttributes.NotPublic; - } + var visibility = module.Symbol.Visibility == Api.Semantics.Visibility.Public + ? (parent is not null ? TypeAttributes.NestedPublic : TypeAttributes.Public) + : (parent is not null ? TypeAttributes.NestedAssembly : TypeAttributes.NotPublic); var attributes = visibility | TypeAttributes.Class | TypeAttributes.AutoLayout | TypeAttributes.BeforeFieldInit | TypeAttributes.Abstract | TypeAttributes.Sealed; var name = string.IsNullOrEmpty(module.Name) ? CompilerConstants.DefaultModuleName : module.Name; @@ -427,28 +435,107 @@ private void EncodeModule(IModule module, TypeDefinitionHandle? parentModule = n @namespace: default, name: name, baseType: this.systemObjectReference, - fieldList: MetadataTokens.FieldDefinitionHandle(fieldIndex), - methodList: MetadataTokens.MethodDefinitionHandle(procIndex)); + fieldList: MetadataTokens.FieldDefinitionHandle(startFieldIndex), + methodList: MetadataTokens.MethodDefinitionHandle(startProcIndex)); // If this isn't top level module, we specify nested relationship - if (parentModule is not null) this.MetadataBuilder.AddNestedType(createdModule, parentModule.Value); + if (parent is not null) this.MetadataBuilder.AddNestedType(createdModule, parent.Value); + + // We encode every class + foreach (var @class in module.Classes.Values) + { + this.EncodeClass(@class, parent: createdModule, fieldIndex: ref fieldIndex, procIndex: ref procIndex); + } // We encode every submodule foreach (var subModule in module.Submodules.Values) { - this.EncodeModule(subModule, createdModule, currentFieldIndex, currentProcIndex); + this.EncodeModule(subModule, parent: createdModule, fieldIndex: ref fieldIndex, procIndex: ref procIndex); } } - private FieldDefinitionHandle EncodeGlobal(GlobalSymbol global) + private TypeDefinitionHandle EncodeClass( + IClass @class, + TypeDefinitionHandle? parent, + ref int fieldIndex, + ref int procIndex) { - var visibility = global.Visibility switch + var startFieldIndex = fieldIndex; + var startProcIndex = procIndex; + + // TODO: Go through the rest of the members + + // Build up attributes + var visibility = @class.Symbol.Visibility == Api.Semantics.Visibility.Public + ? (parent is not null ? TypeAttributes.NestedPublic : TypeAttributes.Public) + : (parent is not null ? TypeAttributes.NestedAssembly : TypeAttributes.NotPublic); + var attributes = visibility | TypeAttributes.Class | TypeAttributes.AutoLayout | TypeAttributes.BeforeFieldInit | TypeAttributes.Sealed; + if (@class.Symbol.IsValueType) attributes |= TypeAttributes.SequentialLayout; + + // Create the type + var createdClass = this.AddTypeDefinition( + attributes: attributes, + @namespace: default, + name: @class.Name, + baseType: @class.Symbol.BaseType is null + ? this.systemObjectReference + : (TypeReferenceHandle)this.GetEntityHandle(@class.Symbol.BaseType), + fieldList: MetadataTokens.FieldDefinitionHandle(startFieldIndex), + methodList: MetadataTokens.MethodDefinitionHandle(startProcIndex)); + + // Properties + PropertyDefinitionHandle? firstProperty = null; + var propertyHandleMap = new Dictionary(); + foreach (var prop in @class.Properties) + { + var propHandle = this.EncodeProperty(createdClass, prop); + firstProperty ??= propHandle; + propertyHandleMap.Add(prop, propHandle); + } + if (firstProperty is not null) this.MetadataBuilder.AddPropertyMap(createdClass, firstProperty.Value); + + // Procedures + foreach (var proc in @class.Procedures.Values) + { + var handle = this.EncodeProcedure(proc); + ++procIndex; + + if (proc.Symbol is IPropertyAccessorSymbol propAccessor) + { + // This is an accessor + var isGetter = propAccessor.Property.Getter == propAccessor; + this.MetadataBuilder.AddMethodSemantics( + association: propertyHandleMap[propAccessor.Property], + semantics: isGetter ? MethodSemanticsAttributes.Getter : MethodSemanticsAttributes.Setter, + methodDefinition: handle); + } + } + + // Fields + foreach (var field in @class.Fields) { - Api.Semantics.Visibility.Public => FieldAttributes.Public, - Api.Semantics.Visibility.Internal => FieldAttributes.Assembly, - Api.Semantics.Visibility.Private => FieldAttributes.Private, - _ => throw new ArgumentOutOfRangeException(nameof(global.Visibility)), - }; + this.EncodeField(field); + ++fieldIndex; + } + + // If this is a valuetype without fields, we add .pack 0 and .size 1 + if (@class.Symbol.IsValueType && @class.Fields.Count == 0) + { + this.MetadataBuilder.AddTypeLayout( + type: createdClass, + packingSize: 0, + size: 1); + } + + // If this isn't top level module, we specify nested relationship + if (parent is not null) this.MetadataBuilder.AddNestedType(createdClass, parent.Value); + + return createdClass; + } + + private FieldDefinitionHandle EncodeGlobal(GlobalSymbol global) + { + var visibility = GetFieldVisibility(global.Visibility); // Definition return this.AddFieldDefinition( @@ -457,15 +544,21 @@ private FieldDefinitionHandle EncodeGlobal(GlobalSymbol global) signature: this.EncodeGlobalSignature(global)); } - private MethodDefinitionHandle EncodeProcedure(IProcedure procedure, string? specialName = null) + private FieldDefinitionHandle EncodeField(FieldSymbol field) { - var visibility = procedure.Symbol.Visibility switch - { - Api.Semantics.Visibility.Public => MethodAttributes.Public, - Api.Semantics.Visibility.Internal => MethodAttributes.Assembly, - Api.Semantics.Visibility.Private => MethodAttributes.Private, - _ => throw new ArgumentOutOfRangeException(nameof(procedure.Symbol.Visibility)), - }; + var visibility = GetFieldVisibility(field.Visibility); + var mutability = field.IsMutable ? default : FieldAttributes.InitOnly; + + // Definition + return this.AddFieldDefinition( + attributes: visibility | mutability, + name: field.Name, + signature: this.EncodeFieldSignature(field)); + } + + private MethodDefinitionHandle EncodeProcedure(IProcedure procedure) + { + var visibility = GetMethodVisibility(procedure.Symbol.Visibility); // Encode instructions var cilCodegen = new CilCodegen(this, procedure); @@ -489,10 +582,9 @@ private MethodDefinitionHandle EncodeProcedure(IProcedure procedure, string? spe hasDynamicStackAllocation: false); // Determine attributes - var attributes = MethodAttributes.Static | MethodAttributes.HideBySig; - attributes |= specialName is null - ? visibility - : MethodAttributes.Private | MethodAttributes.SpecialName | MethodAttributes.RTSpecialName; + var attributes = MethodAttributes.HideBySig | visibility; + if (procedure.Symbol.IsStatic) attributes |= MethodAttributes.Static; + if (procedure.Symbol.IsSpecialName) attributes |= MethodAttributes.SpecialName | MethodAttributes.RTSpecialName; // Parameters var parameterList = this.NextParameterHandle; @@ -508,7 +600,7 @@ private MethodDefinitionHandle EncodeProcedure(IProcedure procedure, string? spe var definitionHandle = this.MetadataBuilder.AddMethodDefinition( attributes: attributes, implAttributes: MethodImplAttributes.IL, - name: this.GetOrAddString(specialName ?? procedure.Name), + name: this.GetOrAddString(procedure.Name), signature: this.EncodeProcedureSignature(procedure), bodyOffset: methodBodyOffset, parameterList: parameterList); @@ -530,14 +622,36 @@ private MethodDefinitionHandle EncodeProcedure(IProcedure procedure, string? spe return definitionHandle; } + private PropertyDefinitionHandle EncodeProperty(TypeDefinitionHandle declaringType, PropertySymbol prop) + { + return this.MetadataBuilder.AddProperty( + attributes: PropertyAttributes.None, + name: this.GetOrAddString(prop.Name), + signature: this.EncodeBlob(e => + { + e + .PropertySignature(isInstanceProperty: !prop.IsStatic) + .Parameters(0, out var returnType, out _); + this.EncodeReturnType(returnType, prop.Type); + })); + } + private BlobHandle EncodeGlobalSignature(GlobalSymbol global) => this.EncodeBlob(e => this.EncodeSignatureType(e.Field().Type(), global.Type)); + private BlobHandle EncodeFieldSignature(FieldSymbol field) => + this.EncodeBlob(e => this.EncodeSignatureType(e.Field().Type(), field.Type)); + private BlobHandle EncodeProcedureSignature(IProcedure procedure) => this.EncodeBlob(e => { e - .MethodSignature(genericParameterCount: procedure.Generics.Count) - .Parameters(procedure.Parameters.Count, out var retEncoder, out var paramsEncoder); + .MethodSignature( + genericParameterCount: procedure.Generics.Count, + isInstanceMethod: !procedure.Symbol.IsStatic) + .Parameters( + procedure.Parameters.Count, + out var retEncoder, + out var paramsEncoder); this.EncodeReturnType(retEncoder, procedure.ReturnType); foreach (var param in procedure.Parameters) { @@ -670,6 +784,17 @@ public void EncodeSignatureType(SignatureTypeEncoder encoder, TypeSymbol type) return; } + if (type is SourceClassSymbol sourceClass) + { + encoder.Type( + type: this.GetOrAddTypeReference( + parent: this.GetEntityHandle(sourceClass.ContainingSymbol), + @namespace: null, + name: sourceClass.MetadataName), + isValueType: sourceClass.IsValueType); + return; + } + // TODO throw new NotImplementedException(); } @@ -693,4 +818,20 @@ private void WritePe(Stream peStream) var contentId = peBuilder.Serialize(peBlob); peBlob.WriteContentTo(peStream); } + + private static FieldAttributes GetFieldVisibility(Api.Semantics.Visibility visibility) => visibility switch + { + Api.Semantics.Visibility.Public => FieldAttributes.Public, + Api.Semantics.Visibility.Internal => FieldAttributes.Assembly, + Api.Semantics.Visibility.Private => FieldAttributes.Private, + _ => throw new ArgumentOutOfRangeException(nameof(visibility)), + }; + + private static MethodAttributes GetMethodVisibility(Api.Semantics.Visibility visibility) => visibility switch + { + Api.Semantics.Visibility.Public => MethodAttributes.Public, + Api.Semantics.Visibility.Internal => MethodAttributes.Assembly, + Api.Semantics.Visibility.Private => MethodAttributes.Private, + _ => throw new ArgumentOutOfRangeException(nameof(visibility)), + }; } diff --git a/src/Draco.Compiler/Internal/Declarations/ClassDeclaration.cs b/src/Draco.Compiler/Internal/Declarations/ClassDeclaration.cs new file mode 100644 index 000000000..0f1b63721 --- /dev/null +++ b/src/Draco.Compiler/Internal/Declarations/ClassDeclaration.cs @@ -0,0 +1,54 @@ +using System; +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Linq; +using Draco.Compiler.Api.Syntax; + +namespace Draco.Compiler.Internal.Declarations; + +/// +/// A class declaration. +/// +internal sealed class ClassDeclaration : Declaration +{ + /// + /// The syntax of the declaration. + /// + public ClassDeclarationSyntax Syntax { get; } + + public override ImmutableArray Children => + InterlockedUtils.InitializeDefault(ref this.children, this.BuildChildren); + private ImmutableArray children; + + public override IEnumerable DeclaringSyntaxes + { + get + { + yield return this.Syntax; + } + } + + public ClassDeclaration(ClassDeclarationSyntax syntax) + : base(syntax.Name.Text) + { + this.Syntax = syntax; + } + + private ImmutableArray BuildChildren() + { + if (this.Syntax.Body is not BlockClassBodySyntax block) return ImmutableArray.Empty; + + return block.Declarations.Select(this.BuildChild).OfType().ToImmutableArray(); + } + + // TODO: More entries to handle + private Declaration? BuildChild(SyntaxNode node) => node switch + { + // NOTE: We ignore import declarations in the declaration tree, unlike Roslyn + // We handle import declarations during constructing the binders + // Since we allow for imports in local scopes too, this is the most sensible choice + ImportDeclarationSyntax => null, + UnexpectedDeclarationSyntax => null, + _ => throw new ArgumentOutOfRangeException(nameof(node)), + }; +} diff --git a/src/Draco.Compiler/Internal/Declarations/SingleModuleDeclaration.cs b/src/Draco.Compiler/Internal/Declarations/SingleModuleDeclaration.cs index 57bc68146..93067f721 100644 --- a/src/Draco.Compiler/Internal/Declarations/SingleModuleDeclaration.cs +++ b/src/Draco.Compiler/Internal/Declarations/SingleModuleDeclaration.cs @@ -52,6 +52,7 @@ private ImmutableArray BuildChildren() => VariableDeclarationSyntax var => new GlobalDeclaration(var), FunctionDeclarationSyntax func => new FunctionDeclaration(func), ModuleDeclarationSyntax module => new SingleModuleDeclaration(module.Name.Text, this.Path.Append(module.Name.Text), module), + ClassDeclarationSyntax cls => new ClassDeclaration(cls), UnexpectedDeclarationSyntax => null, _ => throw new ArgumentOutOfRangeException(nameof(node)), }; diff --git a/src/Draco.Compiler/Internal/OptimizingIr/ClassCodegen.cs b/src/Draco.Compiler/Internal/OptimizingIr/ClassCodegen.cs new file mode 100644 index 000000000..679ea255a --- /dev/null +++ b/src/Draco.Compiler/Internal/OptimizingIr/ClassCodegen.cs @@ -0,0 +1,65 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Draco.Compiler.Api; +using Draco.Compiler.Internal.BoundTree; +using Draco.Compiler.Internal.Lowering; +using Draco.Compiler.Internal.OptimizingIr.Model; +using Draco.Compiler.Internal.Symbols; + +namespace Draco.Compiler.Internal.OptimizingIr; + +/// +/// Generates IR code for a class. +/// +internal sealed class ClassCodegen : SymbolVisitor +{ + private Compilation Compilation => this.moduleCodegen.Compilation; + private bool EmitSequencePoints => this.moduleCodegen.EmitSequencePoints; + + private readonly ModuleCodegen moduleCodegen; + private readonly Class @class; + + public ClassCodegen(ModuleCodegen moduleCodegen, Class @class) + { + this.moduleCodegen = moduleCodegen; + this.@class = @class; + } + + // TODO: Copypasta from ModuleCodegen + public override void VisitFunction(FunctionSymbol functionSymbol) + { + if (functionSymbol.Body is null) return; + + // Add procedure + var procedure = this.@class.DefineProcedure(functionSymbol); + + // Create the body + var body = this.RewriteBody(functionSymbol.Body); + // Yank out potential local functions and closures + var (bodyWithoutLocalFunctions, localFunctions) = ClosureRewriter.Rewrite(body); + // Compile it + var bodyCodegen = new FunctionBodyCodegen(this.Compilation, procedure); + bodyWithoutLocalFunctions.Accept(bodyCodegen); + + // Compile the local functions + foreach (var localFunc in localFunctions) this.VisitFunction(localFunc); + } + + public override void VisitField(FieldSymbol fieldSymbol) + { + // No-op, the Class model reads it up from the symbol + } + + // TODO: Copypasta from ModuleCodegen + // TODO: Except we check for syntax not being null because we can have synthetized symbols + private BoundNode RewriteBody(BoundNode body) + { + // If needed, inject sequence points + if (body.Syntax is not null && this.EmitSequencePoints) body = SequencePointInjector.Inject(body); + // Desugar it + return body.Accept(new LocalRewriter(this.Compilation)); + } +} diff --git a/src/Draco.Compiler/Internal/OptimizingIr/FunctionBodyCodegen.cs b/src/Draco.Compiler/Internal/OptimizingIr/FunctionBodyCodegen.cs index 6e11b55bf..27b2bb5e0 100644 --- a/src/Draco.Compiler/Internal/OptimizingIr/FunctionBodyCodegen.cs +++ b/src/Draco.Compiler/Internal/OptimizingIr/FunctionBodyCodegen.cs @@ -70,10 +70,16 @@ private Module GetDefiningModule(Symbol symbol) private int DefineLocal(LocalSymbol local) => this.procedure.DefineLocal(local); public Register DefineRegister(TypeSymbol type) => this.procedure.DefineRegister(type); - private Procedure SynthetizeProcedure(FunctionSymbol func) + private FunctionSymbol SynthetizeProcedure(FunctionSymbol func) { Debug.Assert(func.Body is not null); + // Only synthetize, if this is the relevant place for it + if (func.ContainingSymbol != this.procedure.Symbol.ContainingSymbol) return func; + + // TODO: This might not be necessarily correct, as we might be within a class + // and not a module + // We handle synthetized functions a bit specially, as they are not part of our symbol // tree, so we compile them, in case they have not yet been var compiledAlready = this.procedure.DeclaringModule.Procedures.ContainsKey(func); @@ -83,7 +89,7 @@ private Procedure SynthetizeProcedure(FunctionSymbol func) var codegen = new FunctionBodyCodegen(this.compilation, proc); func.Body.Accept(codegen); } - return proc; + return func; } private static bool NeedsBoxing(TypeSymbol targetType, TypeSymbol sourceType) @@ -505,7 +511,7 @@ public override IOperand VisitFunctionGroupExpression(BoundFunctionGroupExpressi // Generic functions FunctionInstanceSymbol i => this.TranslateFunctionInstanceSymbol(i), // Functions with synthetized body - FunctionSymbol f when f.DeclaringSyntax is null && f.Body is not null => this.SynthetizeProcedure(f).Symbol, + FunctionSymbol f when f.DeclaringSyntax is null && f.Body is not null => this.SynthetizeProcedure(f), // Functions with inline codegen FunctionSymbol f when f.Codegen is not null => f, // Source functions diff --git a/src/Draco.Compiler/Internal/OptimizingIr/Model/Class.cs b/src/Draco.Compiler/Internal/OptimizingIr/Model/Class.cs new file mode 100644 index 000000000..98a1914b5 --- /dev/null +++ b/src/Draco.Compiler/Internal/OptimizingIr/Model/Class.cs @@ -0,0 +1,73 @@ +using System; +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Draco.Compiler.Internal.Symbols; + +namespace Draco.Compiler.Internal.OptimizingIr.Model; + +internal sealed class Class : IClass +{ + public TypeSymbol Symbol { get; } + + public string Name => this.Symbol.Name; + + public Class? DeclaringClass { get; } + IClass? IClass.DeclaringClass => this.DeclaringClass; + + public Module DeclaringModule { get; } + IModule IClass.DeclaringModule => this.DeclaringModule; + + public Assembly Assembly => this.DeclaringModule.Assembly; + IAssembly IClass.Assembly => this.Assembly; + + public IReadOnlyList Generics => this.Symbol.GenericParameters; + + public IReadOnlyList Fields => InterlockedUtils.InitializeDefault( + ref this.fields, + () => this.Symbol.DefinedMembers.OfType().ToImmutableArray()); + private ImmutableArray fields; + + public IReadOnlyList Properties => InterlockedUtils.InitializeDefault( + ref this.properties, + () => this.Symbol.DefinedMembers.OfType().ToImmutableArray()); + private ImmutableArray properties; + + public IReadOnlyDictionary Procedures => this.procedures; + + private readonly Dictionary procedures = new(); + + public Class(Module declaringModule, Class? declaringClass, TypeSymbol symbol) + { + this.DeclaringModule = declaringModule; + this.DeclaringClass = declaringClass; + this.Symbol = symbol; + } + + public Procedure DefineProcedure(FunctionSymbol functionSymbol) + { + if (!this.procedures.TryGetValue(functionSymbol, out var result)) + { + result = new Procedure(this.DeclaringModule, functionSymbol); + this.procedures.Add(functionSymbol, result); + } + return (Procedure)result; + } + + public override string ToString() + { + var result = new StringBuilder(); + + // TODO: Modifiers + + result.AppendLine($"class {this.Name} {{"); + + // TODO: add members + + result.Append('}'); + + return result.ToString(); + } +} diff --git a/src/Draco.Compiler/Internal/OptimizingIr/Model/IClass.cs b/src/Draco.Compiler/Internal/OptimizingIr/Model/IClass.cs new file mode 100644 index 000000000..f38fc9016 --- /dev/null +++ b/src/Draco.Compiler/Internal/OptimizingIr/Model/IClass.cs @@ -0,0 +1,63 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Draco.Compiler.Internal.Symbols; + +namespace Draco.Compiler.Internal.OptimizingIr.Model; + +/// +/// Read-only interface of a class. +/// +internal interface IClass +{ + /// + /// The symbol that corresponds to this class. + /// + public TypeSymbol Symbol { get; } + + /// + /// The name of this class. + /// + public string Name { get; } + + /// + /// The parent class this class is defined in, if any. + /// + public IClass? DeclaringClass { get; } + + /// + /// The module this class is defined in. + /// + public IModule DeclaringModule { get; } + + /// + /// The assembly this class is defined in. + /// + public IAssembly Assembly { get; } + + /// + /// The generic parameters on this class. + /// + public IReadOnlyList Generics { get; } + + // TODO: Base class + // TODO: Interfaces? (we might wanna keep them external) + // TODO: Nested classes + + /// + /// The fields defined on this class. + /// + public IReadOnlyList Fields { get; } + + /// + /// The properties defined on this class. + /// + public IReadOnlyList Properties { get; } + + /// + /// The procedures defined on this class. + /// + public IReadOnlyDictionary Procedures { get; } +} diff --git a/src/Draco.Compiler/Internal/OptimizingIr/Model/IModule.cs b/src/Draco.Compiler/Internal/OptimizingIr/Model/IModule.cs index fe07e6f71..507429490 100644 --- a/src/Draco.Compiler/Internal/OptimizingIr/Model/IModule.cs +++ b/src/Draco.Compiler/Internal/OptimizingIr/Model/IModule.cs @@ -33,6 +33,11 @@ internal interface IModule /// public IReadOnlyDictionary Submodules { get; } + /// + /// The compiled classes within this module. + /// + public IReadOnlyDictionary Classes { get; } + /// /// The globals within this module. /// diff --git a/src/Draco.Compiler/Internal/OptimizingIr/Model/IProcedure.cs b/src/Draco.Compiler/Internal/OptimizingIr/Model/IProcedure.cs index 9e263f768..c06e2b031 100644 --- a/src/Draco.Compiler/Internal/OptimizingIr/Model/IProcedure.cs +++ b/src/Draco.Compiler/Internal/OptimizingIr/Model/IProcedure.cs @@ -18,6 +18,7 @@ internal interface IProcedure /// public string Name { get; } + // TODO: Declaring class? /// /// The module this procedure is defined in. /// diff --git a/src/Draco.Compiler/Internal/OptimizingIr/Model/Module.cs b/src/Draco.Compiler/Internal/OptimizingIr/Model/Module.cs index 8e92024be..81dd01ade 100644 --- a/src/Draco.Compiler/Internal/OptimizingIr/Model/Module.cs +++ b/src/Draco.Compiler/Internal/OptimizingIr/Model/Module.cs @@ -17,6 +17,7 @@ internal sealed class Module : IModule public string Name => this.Symbol.Name; public IReadOnlyDictionary Submodules => this.submodules; + public IReadOnlyDictionary Classes => this.classes; public IReadOnlySet Globals => this.globals; @@ -32,6 +33,7 @@ internal sealed class Module : IModule IModule? IModule.Parent => this.Parent; private readonly HashSet globals = new(); + private readonly Dictionary classes = new(); private readonly Dictionary procedures = new(); private readonly Dictionary submodules = new(); @@ -39,7 +41,7 @@ public Module(ModuleSymbol symbol, Assembly assembly, Module? Parent) { this.Symbol = symbol; this.GlobalInitializer = this.DefineProcedure(new IntrinsicFunctionSymbol( - name: "", + name: ".cctor", paramTypes: Enumerable.Empty(), returnType: WellKnownTypes.Unit)); this.Assembly = assembly; @@ -69,6 +71,16 @@ public Procedure DefineProcedure(FunctionSymbol functionSymbol) return (Procedure)result; } + public Class DefineClass(TypeSymbol typeSymbol) + { + if (!this.classes.TryGetValue(typeSymbol, out var result)) + { + result = new Class(this, null, typeSymbol); + this.classes.Add(typeSymbol, result); + } + return (Class)result; + } + public Module DefineModule(ModuleSymbol moduleSymbol) { if (!this.submodules.TryGetValue(moduleSymbol, out var result)) @@ -82,12 +94,24 @@ public Module DefineModule(ModuleSymbol moduleSymbol) public override string ToString() { var result = new StringBuilder(); - result.AppendLine($"module {this.Symbol.Name}"); + result.AppendLine($"module {this.Symbol.Name} {{"); result.AppendJoin(Environment.NewLine, this.globals); - if (this.globals.Count > 0 && this.procedures.Count > 1) result.Append(doubleNewline); - result.AppendJoin(doubleNewline, this.procedures.Values); - if (this.procedures.Count > 0 && this.submodules.Count > 0) result.Append(doubleNewline); - result.AppendJoin(doubleNewline, this.submodules.Values); + + var haveNewline = this.globals.Count == 0; + void PrintComponents(IEnumerable components) + { + if (!components.Any()) return; + if (!haveNewline) result!.Append(doubleNewline); + result!.AppendJoin(doubleNewline, components); + haveNewline = false; + } + + PrintComponents(this.procedures.Values); + PrintComponents(this.classes.Values); + PrintComponents(this.submodules.Values); + + if (!haveNewline) result.AppendLine(); + result.Append('}'); return result.ToString(); } } diff --git a/src/Draco.Compiler/Internal/OptimizingIr/Model/Procedure.cs b/src/Draco.Compiler/Internal/OptimizingIr/Model/Procedure.cs index 05bc8ebb6..a1962f9fe 100644 --- a/src/Draco.Compiler/Internal/OptimizingIr/Model/Procedure.cs +++ b/src/Draco.Compiler/Internal/OptimizingIr/Model/Procedure.cs @@ -13,7 +13,6 @@ internal sealed class Procedure : IProcedure { public FunctionSymbol Symbol { get; } public string Name => this.Symbol.Name; - public TypeSymbol? Type => this.Symbol.Type; public Module DeclaringModule { get; } IModule IProcedure.DeclaringModule => this.DeclaringModule; public Assembly Assembly => this.DeclaringModule.Assembly; diff --git a/src/Draco.Compiler/Internal/OptimizingIr/ModuleCodegen.cs b/src/Draco.Compiler/Internal/OptimizingIr/ModuleCodegen.cs index e9b824545..548e879f9 100644 --- a/src/Draco.Compiler/Internal/OptimizingIr/ModuleCodegen.cs +++ b/src/Draco.Compiler/Internal/OptimizingIr/ModuleCodegen.cs @@ -14,17 +14,25 @@ namespace Draco.Compiler.Internal.OptimizingIr; /// internal sealed class ModuleCodegen : SymbolVisitor { + /// + /// The compilation the codegen generates for. + /// + public Compilation Compilation { get; } + + /// + /// True, if sequence points should be emitted. + /// + public bool EmitSequencePoints { get; } + private readonly FunctionBodyCodegen globalInitializer; - private readonly Compilation compilation; private readonly Module module; - private readonly bool emitSequencePoints; public ModuleCodegen(Compilation compilation, Module module, bool emitSequencePoints) { - this.compilation = compilation; + this.Compilation = compilation; this.module = module; - this.globalInitializer = new(this.compilation, module.GlobalInitializer); - this.emitSequencePoints = emitSequencePoints; + this.globalInitializer = new(this.Compilation, module.GlobalInitializer); + this.EmitSequencePoints = emitSequencePoints; } private void Complete() @@ -34,6 +42,18 @@ private void Complete() this.globalInitializer.Write(Ret(default(Void))); } + public override void VisitType(TypeSymbol typeSymbol) + { + if (typeSymbol is not SourceClassSymbol sourceClass) return; + + // Add it to the module + var @class = this.module.DefineClass(sourceClass); + + // Invoke codegen + var classCodegen = new ClassCodegen(this, @class); + sourceClass.Accept(classCodegen); + } + public override void VisitGlobal(GlobalSymbol globalSymbol) { if (globalSymbol is not SourceGlobalSymbol sourceGlobal) return; @@ -59,17 +79,17 @@ public override void VisitGlobal(GlobalSymbol globalSymbol) public override void VisitFunction(FunctionSymbol functionSymbol) { - if (functionSymbol is not SourceFunctionSymbol sourceFunction) return; + if (functionSymbol.Body is null) return; // Add procedure var procedure = this.module.DefineProcedure(functionSymbol); // Create the body - var body = this.RewriteBody(sourceFunction.Body); + var body = this.RewriteBody(functionSymbol.Body); // Yank out potential local functions and closures var (bodyWithoutLocalFunctions, localFunctions) = ClosureRewriter.Rewrite(body); // Compile it - var bodyCodegen = new FunctionBodyCodegen(this.compilation, procedure); + var bodyCodegen = new FunctionBodyCodegen(this.Compilation, procedure); bodyWithoutLocalFunctions.Accept(bodyCodegen); // Compile the local functions @@ -81,7 +101,7 @@ public override void VisitModule(ModuleSymbol moduleSymbol) foreach (var subModuleSymbol in moduleSymbol.Members.OfType()) { var module = this.module.DefineModule(subModuleSymbol); - var moduleCodegen = new ModuleCodegen(this.compilation, module, this.emitSequencePoints); + var moduleCodegen = new ModuleCodegen(this.Compilation, module, this.EmitSequencePoints); subModuleSymbol.Accept(moduleCodegen); } @@ -95,8 +115,8 @@ public override void VisitModule(ModuleSymbol moduleSymbol) private BoundNode RewriteBody(BoundNode body) { // If needed, inject sequence points - if (this.emitSequencePoints) body = SequencePointInjector.Inject(body); + if (this.EmitSequencePoints) body = SequencePointInjector.Inject(body); // Desugar it - return body.Accept(new LocalRewriter(this.compilation)); + return body.Accept(new LocalRewriter(this.Compilation)); } } diff --git a/src/Draco.Compiler/Internal/Symbols/FunctionSymbol.cs b/src/Draco.Compiler/Internal/Symbols/FunctionSymbol.cs index 062f0f5d1..11f732c5b 100644 --- a/src/Draco.Compiler/Internal/Symbols/FunctionSymbol.cs +++ b/src/Draco.Compiler/Internal/Symbols/FunctionSymbol.cs @@ -74,6 +74,13 @@ public delegate IOperand CodegenDelegate( _ => throw new System.ArgumentOutOfRangeException(nameof(token)), }; + /// + /// The receiver of this function, if it has one. + /// + public TypeSymbol? Receiver => this.IsStatic + ? null + : this.ContainingSymbol as TypeSymbol; + /// /// The parameters of this function. /// diff --git a/src/Draco.Compiler/Internal/Symbols/ParameterSymbol.cs b/src/Draco.Compiler/Internal/Symbols/ParameterSymbol.cs index 06ce1dfd2..e88c876c3 100644 --- a/src/Draco.Compiler/Internal/Symbols/ParameterSymbol.cs +++ b/src/Draco.Compiler/Internal/Symbols/ParameterSymbol.cs @@ -16,6 +16,11 @@ internal abstract partial class ParameterSymbol : LocalSymbol /// public virtual bool IsVariadic => false; + /// + /// True, if this is the "this" parameter. + /// + public virtual bool IsThis => false; + public override bool IsMutable => false; // NOTE: Override for covariant return type diff --git a/src/Draco.Compiler/Internal/Symbols/Source/SourceAutoPropertySymbol.cs b/src/Draco.Compiler/Internal/Symbols/Source/SourceAutoPropertySymbol.cs new file mode 100644 index 000000000..ab78e3ae4 --- /dev/null +++ b/src/Draco.Compiler/Internal/Symbols/Source/SourceAutoPropertySymbol.cs @@ -0,0 +1,70 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using Draco.Compiler.Api; +using Draco.Compiler.Api.Syntax; +using Draco.Compiler.Internal.Binding; +using Draco.Compiler.Internal.Symbols.Synthetized; + +namespace Draco.Compiler.Internal.Symbols.Source; + +/// +/// An auto-prop coming from source. +/// +internal sealed class SourceAutoPropertySymbol : PropertySymbol, ISourceSymbol +{ + public override TypeSymbol ContainingSymbol { get; } + + public override TypeSymbol Type => this.BindTypeIfNeeded(this.DeclaringCompilation!); + private TypeSymbol? type; + + public override FunctionSymbol Getter => LazyInitializer.EnsureInitialized(ref this.getter, this.BuildGetter); + private FunctionSymbol? getter; + + public override FunctionSymbol? Setter => this.Modifiers.Keyword.Kind == TokenKind.KeywordVal + ? null + : InterlockedUtils.InitializeMaybeNull(ref this.setter, this.BuildSetter); + private FunctionSymbol? setter; + + /// + /// The backing field of this auto-prop. + /// + public FieldSymbol BackingField => LazyInitializer.EnsureInitialized(ref this.backingField, this.BuildBackingField); + private FieldSymbol? backingField; + + public override string Name => this.DeclaringSyntax.Parameter.Name.Text; + public override bool IsIndexer => false; + public override bool IsStatic => false; + public override Api.Semantics.Visibility Visibility => GetVisibilityFromTokenKind(this.Modifiers.VisibilityModifier?.Kind); + + // TODO: Not necessarily this type, only for primary constructors + public override PrimaryConstructorParameterSyntax DeclaringSyntax { get; } + private PrimaryConstructorParameterMemberModifiersSyntax Modifiers => this.DeclaringSyntax.MemberModifiers!; + + public SourceAutoPropertySymbol(TypeSymbol containingSymbol, PrimaryConstructorParameterSyntax declaringSyntax) + { + this.ContainingSymbol = containingSymbol; + this.DeclaringSyntax = declaringSyntax; + } + + public void Bind(IBinderProvider binderProvider) + { + this.BindTypeIfNeeded(binderProvider); + } + + private TypeSymbol BindTypeIfNeeded(IBinderProvider binderProvider) => + LazyInitializer.EnsureInitialized(ref this.type, () => this.BindType(binderProvider)); + + private TypeSymbol BindType(IBinderProvider binderProvider) + { + var binder = binderProvider.GetBinder(this.DeclaringSyntax); + return binder.BindTypeToTypeSymbol(this.DeclaringSyntax.Parameter.Type, binderProvider.DiagnosticBag); + } + + private FunctionSymbol BuildGetter() => new AutoPropertyGetterSymbol(this.ContainingSymbol, this); + private FunctionSymbol? BuildSetter() => new AutoPropertySetterSymbol(this.ContainingSymbol, this); + private FieldSymbol BuildBackingField() => new AutoPropertyBackingFieldSymbol(this.ContainingSymbol, this); +} diff --git a/src/Draco.Compiler/Internal/Symbols/Source/SourceClassSymbol.cs b/src/Draco.Compiler/Internal/Symbols/Source/SourceClassSymbol.cs new file mode 100644 index 000000000..f41fb22e1 --- /dev/null +++ b/src/Draco.Compiler/Internal/Symbols/Source/SourceClassSymbol.cs @@ -0,0 +1,131 @@ +using System; +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Linq; +using System.Threading; +using Draco.Compiler.Api.Syntax; +using Draco.Compiler.Internal.Declarations; +using Draco.Compiler.Internal.Documentation; +using Draco.Compiler.Internal.Documentation.Extractors; +using Draco.Compiler.Internal.Symbols.Synthetized; + +namespace Draco.Compiler.Internal.Symbols.Source; + +/// +/// Represents a class from source code. +/// +internal sealed class SourceClassSymbol : TypeSymbol +{ + public override IEnumerable DefinedMembers => + InterlockedUtils.InitializeDefault(ref this.definedMembers, this.BuildMembers); + private ImmutableArray definedMembers; + + public override string Name => this.DeclaringSyntax.Name.Text; + + public override Api.Semantics.Visibility Visibility => + GetVisibilityFromTokenKind(this.DeclaringSyntax.VisibilityModifier?.Kind); + + public override ImmutableArray GenericParameters => + InterlockedUtils.InitializeDefault(ref this.genericParameters, this.BuildGenericParameters); + private ImmutableArray genericParameters; + + public override ImmutableArray ImmediateBaseTypes => + InterlockedUtils.InitializeDefault(ref this.immediateBaseTypes, this.BuildImmediateBaseTypes); + private ImmutableArray immediateBaseTypes; + + public override bool IsValueType => this.DeclaringSyntax.ValueModifier is not null; + + public override Symbol ContainingSymbol { get; } + + public override ClassDeclarationSyntax DeclaringSyntax => this.declaration.Syntax; + + public override SymbolDocumentation Documentation => LazyInitializer.EnsureInitialized(ref this.documentation, this.BuildDocumentation); + private SymbolDocumentation? documentation; + + private readonly ClassDeclaration declaration; + + public SourceClassSymbol(Symbol containingSymbol, ClassDeclaration declaration) + { + this.ContainingSymbol = containingSymbol; + this.declaration = declaration; + } + + public override string ToString() => this.DeclaringSyntax.Name.Text; + + private ImmutableArray BuildGenericParameters() + { + var genericParams = this.DeclaringSyntax.Generics; + if (genericParams is null) return ImmutableArray.Empty; + + return genericParams.Parameters.Values + .Select(syntax => new SourceTypeParameterSymbol(this, syntax)) + .Cast() + .ToImmutableArray(); + } + + private ImmutableArray BuildImmediateBaseTypes() + { + var result = ImmutableArray.CreateBuilder(); + if (this.IsValueType) + { + result.Add(this.DeclaringCompilation!.WellKnownTypes.SystemValueType); + } + else + { + result.Add(this.DeclaringCompilation!.WellKnownTypes.SystemObject); + } + // Done + return result.ToImmutable(); + } + + // TODO: Check for illegal shadowing + private ImmutableArray BuildMembers() + { + var result = ImmutableArray.CreateBuilder(); + + if (this.DeclaringSyntax.PrimaryConstructor is null) + { + // We only synthetize a default ctor, if this is a reference type + if (!this.IsValueType) + { + // TODO: Check for secondary constructors + // If there is no constructor, add a default one + result.Add(new DefaultConstructorSymbol(this)); + } + } + else + { + // We have a primary constructor, add it + result.Add(new SourcePrimaryConstructorSymbol(this, this.DeclaringSyntax.PrimaryConstructor)); + + // Also check for fields/props + foreach (var param in this.DeclaringSyntax.PrimaryConstructor.ParameterList.Values) + { + // Skip non-members + if (param.MemberModifiers is null) continue; + + if (param.MemberModifiers.FieldModifier is null) + { + // Property + var prop = new SourceAutoPropertySymbol(this, param); + // Add property, getter, setter and backing field + result.Add(prop); + result.Add(prop.Getter); + if (prop.Setter is not null) result.Add(prop.Setter); + result.Add(prop.BackingField); + } + else + { + // Add field + result.Add(new SourceFieldSymbol(this, param)); + } + } + } + + // Done + return result.ToImmutable(); + } + + private SymbolDocumentation BuildDocumentation() => + MarkdownDocumentationExtractor.Extract(this); +} diff --git a/src/Draco.Compiler/Internal/Symbols/Source/SourceFieldSymbol.cs b/src/Draco.Compiler/Internal/Symbols/Source/SourceFieldSymbol.cs new file mode 100644 index 000000000..1f2fca0e8 --- /dev/null +++ b/src/Draco.Compiler/Internal/Symbols/Source/SourceFieldSymbol.cs @@ -0,0 +1,48 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using Draco.Compiler.Api.Syntax; +using Draco.Compiler.Internal.Binding; + +namespace Draco.Compiler.Internal.Symbols.Source; + +/// +/// Represents a field from source code. +/// +internal sealed class SourceFieldSymbol : FieldSymbol, ISourceSymbol +{ + public override TypeSymbol ContainingSymbol { get; } + + public override TypeSymbol Type => this.BindTypeIfNeeded(this.DeclaringCompilation!); + private TypeSymbol? type; + + public override string Name => this.DeclaringSyntax.Parameter.Name.Text; + public override bool IsMutable => this.DeclaringSyntax.MemberModifiers!.Keyword.Kind == TokenKind.KeywordVar; + public override Api.Semantics.Visibility Visibility => GetVisibilityFromTokenKind(this.DeclaringSyntax.MemberModifiers!.VisibilityModifier?.Kind); + + // TODO: This is not general, currently only works for fields declared in primary constructors + public override PrimaryConstructorParameterSyntax DeclaringSyntax { get; } + + public SourceFieldSymbol(TypeSymbol containingSymbol, PrimaryConstructorParameterSyntax declaringSyntax) + { + this.ContainingSymbol = containingSymbol; + this.DeclaringSyntax = declaringSyntax; + } + + public void Bind(IBinderProvider binderProvider) + { + this.BindTypeIfNeeded(binderProvider); + } + + private TypeSymbol BindTypeIfNeeded(IBinderProvider binderProvider) => + LazyInitializer.EnsureInitialized(ref this.type, () => this.BindType(binderProvider)); + + private TypeSymbol BindType(IBinderProvider binderProvider) + { + var binder = binderProvider.GetBinder(this.DeclaringSyntax); + return binder.BindTypeToTypeSymbol(this.DeclaringSyntax.Parameter.Type, binderProvider.DiagnosticBag); + } +} diff --git a/src/Draco.Compiler/Internal/Symbols/Source/SourceModuleSymbol.cs b/src/Draco.Compiler/Internal/Symbols/Source/SourceModuleSymbol.cs index decbeec98..25f3eb157 100644 --- a/src/Draco.Compiler/Internal/Symbols/Source/SourceModuleSymbol.cs +++ b/src/Draco.Compiler/Internal/Symbols/Source/SourceModuleSymbol.cs @@ -11,6 +11,7 @@ using Draco.Compiler.Internal.Declarations; using Draco.Compiler.Internal.Documentation; using Draco.Compiler.Internal.Documentation.Extractors; +using Draco.Compiler.Internal.Symbols.Synthetized; namespace Draco.Compiler.Internal.Symbols.Source; @@ -73,39 +74,57 @@ private ImmutableArray BindMembers(IBinderProvider binderProvider) // Syntax-declaration foreach (var declaration in this.declaration.Children) { - var member = this.BuildMember(declaration); - var earlierMember = result.FirstOrDefault(s => s.Name == member.Name); - result.Add(member); - - // We chech for illegal shadowing - if (earlierMember is null) continue; - - // Overloading is legal - if (member is FunctionSymbol && earlierMember is FunctionSymbol) continue; - - // Illegal - var syntax = member.DeclaringSyntax; - Debug.Assert(syntax is not null); - binderProvider.DiagnosticBag.Add(Diagnostic.Create( - template: SymbolResolutionErrors.IllegalShadowing, - location: syntax.Location, - formatArgs: member.Name)); + var members = this.BuildMember(declaration); + foreach (var member in members) + { + var earlierMember = result.FirstOrDefault(s => s.Name == member.Name); + result.Add(member); + + // Overloading is legal, shadowing is checked by the functions themselves + if (member is FunctionSymbol && earlierMember is FunctionSymbol) continue; + + // We chech for illegal shadowing + if (earlierMember is null) continue; + if (!earlierMember.CanBeShadowedBy(member)) continue; + + // Illegal + var syntax = member.DeclaringSyntax; + Debug.Assert(syntax is not null); + binderProvider.DiagnosticBag.Add(Diagnostic.Create( + template: SymbolResolutionErrors.IllegalShadowing, + location: syntax.Location, + formatArgs: member.Name)); + } } return result.ToImmutable(); } - private Symbol BuildMember(Declaration declaration) => declaration switch + private IEnumerable BuildMember(Declaration declaration) => declaration switch { - FunctionDeclaration f => this.BuildFunction(f), - GlobalDeclaration g => this.BuildGlobal(g), - MergedModuleDeclaration m => this.BuildModule(m), + FunctionDeclaration f => new[] { this.BuildFunction(f) }, + GlobalDeclaration g => new[] { this.BuildGlobal(g) }, + MergedModuleDeclaration m => new[] { this.BuildModule(m) }, + ClassDeclaration c => this.BuildClass(c), _ => throw new ArgumentOutOfRangeException(nameof(declaration)), }; private FunctionSymbol BuildFunction(FunctionDeclaration declaration) => new SourceFunctionSymbol(this, declaration); private GlobalSymbol BuildGlobal(GlobalDeclaration declaration) => new SourceGlobalSymbol(this, declaration); private ModuleSymbol BuildModule(MergedModuleDeclaration declaration) => new SourceModuleSymbol(this.DeclaringCompilation, this, declaration); + private IEnumerable BuildClass(ClassDeclaration declaration) + { + var result = new List(); + var classSymbol = new SourceClassSymbol(this, declaration); + result.Add(classSymbol); + // Add constructor functions + foreach (var ctor in classSymbol.Constructors) + { + var ctorSymbol = new ConstructorFunctionSymbol(ctor); + result.Add(ctorSymbol); + } + return result; + } private SymbolDocumentation BuildDocumentation() => MarkdownDocumentationExtractor.Extract(this); diff --git a/src/Draco.Compiler/Internal/Symbols/Source/SourcePrimaryConstructorSymbol.cs b/src/Draco.Compiler/Internal/Symbols/Source/SourcePrimaryConstructorSymbol.cs new file mode 100644 index 000000000..1ce8561e8 --- /dev/null +++ b/src/Draco.Compiler/Internal/Symbols/Source/SourcePrimaryConstructorSymbol.cs @@ -0,0 +1,153 @@ +using System; +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Linq; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using Draco.Compiler.Api.Diagnostics; +using Draco.Compiler.Api.Syntax; +using Draco.Compiler.Internal.Binding; +using Draco.Compiler.Internal.BoundTree; +using Draco.Compiler.Internal.Symbols.Synthetized; +using static Draco.Compiler.Internal.BoundTree.BoundTreeFactory; + +namespace Draco.Compiler.Internal.Symbols.Source; + +/// +/// Primary constructor defined in-source. +/// +internal sealed class SourcePrimaryConstructorSymbol : FunctionSymbol, ISourceSymbol +{ + public override TypeSymbol ContainingSymbol { get; } + + public override string Name => ".ctor"; + public override TypeSymbol ReturnType => WellKnownTypes.Unit; + public override bool IsStatic => false; + public override bool IsSpecialName => true; + public override bool IsConstructor => true; + public override Api.Semantics.Visibility Visibility => GetVisibilityFromTokenKind(this.DeclaringSyntax.VisibilityModifier?.Kind); + + public override ImmutableArray Parameters => this.BindParametersIfNeeded(this.DeclaringCompilation!); + private ImmutableArray parameters; + + public override PrimaryConstructorSyntax DeclaringSyntax { get; } + + public override BoundStatement Body => LazyInitializer.EnsureInitialized(ref this.body, this.BuildBody); + private BoundStatement? body; + + public SourcePrimaryConstructorSymbol(TypeSymbol containingSymbol, PrimaryConstructorSyntax declaringSyntax) + { + this.ContainingSymbol = containingSymbol; + this.DeclaringSyntax = declaringSyntax; + } + + public void Bind(IBinderProvider binderProvider) + { + this.BindParametersIfNeeded(binderProvider); + // Force binding of parameters, as the type is lazy too + foreach (var param in this.Parameters.Cast()) param.Bind(binderProvider); + } + + private ImmutableArray BindParametersIfNeeded(IBinderProvider binderProvider) => + InterlockedUtils.InitializeDefault(ref this.parameters, () => this.BindParameters(binderProvider)); + + // TODO: Copypaste from SourceFunctionSymbol + private ImmutableArray BindParameters(IBinderProvider binderProvider) + { + var parameterSyntaxes = this.DeclaringSyntax.ParameterList.Values + .Select(v => v.Parameter) + .ToList(); + var parameters = ImmutableArray.CreateBuilder(); + + for (var i = 0; i < parameterSyntaxes.Count; ++i) + { + var parameterSyntax = parameterSyntaxes[i]; + var parameterName = parameterSyntax.Name.Text; + + var usedBefore = parameters.Any(p => p.Name == parameterName); + if (usedBefore) + { + // NOTE: We only report later duplicates, no need to report the first instance + binderProvider.DiagnosticBag.Add(Diagnostic.Create( + template: SymbolResolutionErrors.IllegalShadowing, + location: parameterSyntax.Location, + formatArgs: parameterName)); + } + + if (parameterSyntax.Variadic is not null && i != parameterSyntaxes.Count - 1) + { + binderProvider.DiagnosticBag.Add(Diagnostic.Create( + template: SymbolResolutionErrors.VariadicParameterNotLast, + location: parameterSyntax.Location, + formatArgs: parameterName)); + } + + var parameter = new SourceParameterSymbol(this, parameterSyntax); + parameters.Add(parameter); + } + + return parameters.ToImmutable(); + } + + private BoundStatement BuildBody() + { + var thisSymbol = new SynthetizedThisParameterSymbol(this); + var statements = ImmutableArray.CreateBuilder(); + + // Call base constructor + if (!this.ContainingSymbol.IsValueType) + { + // We have a base type, call base constructor + var defaultCtor = this.ContainingSymbol.BaseType!.Constructors + .FirstOrDefault(ctor => ctor.Parameters.Length == 0); + // TODO: Error if base has no default constructor? + if (defaultCtor is null) throw new NotImplementedException(); + statements.Add(ExpressionStatement(CallExpression( + receiver: ParameterExpression(thisSymbol), + method: defaultCtor, + arguments: ImmutableArray.Empty))); + } + + // Member initialization + foreach (var (paramSyntax, paramSymbol) in this.DeclaringSyntax.ParameterList.Values.Zip(this.Parameters)) + { + // Skip non-members + if (paramSyntax.MemberModifiers is null) continue; + + var isField = paramSyntax.MemberModifiers.FieldModifier is not null; + var name = paramSyntax.Parameter.Name.Text; + + // Search for the field to initialize + var memberSymbol = isField + ? this.ContainingSymbol.DefinedMembers + .OfType() + .First(m => m.Name == name) + : this.ContainingSymbol.DefinedMembers + .OfType() + .First(m => m.Name == name) + .BackingField; + + // TODO: If a property has a setter, we probably want to call that instead for + // potential side-effects + // Build the initializer + var initializer = ExpressionStatement(AssignmentExpression( + compoundOperator: null, + left: FieldLvalue( + receiver: ParameterExpression(thisSymbol), + field: memberSymbol), + right: ParameterExpression(paramSymbol))); + + // Add it + statements.Add(initializer); + } + + // Add a return statement + statements.Add(ExpressionStatement(ReturnExpression(BoundUnitExpression.Default))); + + return ExpressionStatement(BlockExpression( + locals: ImmutableArray.Empty, + statements: statements.ToImmutable(), + value: BoundUnitExpression.Default)); + } +} diff --git a/src/Draco.Compiler/Internal/Symbols/Synthetized/ArrayConstructorSymbol.cs b/src/Draco.Compiler/Internal/Symbols/Synthetized/ArrayConstructorSymbol.cs index 59a2ad478..ff0173d68 100644 --- a/src/Draco.Compiler/Internal/Symbols/Synthetized/ArrayConstructorSymbol.cs +++ b/src/Draco.Compiler/Internal/Symbols/Synthetized/ArrayConstructorSymbol.cs @@ -1,6 +1,7 @@ using System.Collections.Immutable; using System.Linq; using System.Threading; +using Draco.Compiler.Internal.BoundTree; using static Draco.Compiler.Internal.OptimizingIr.InstructionFactory; namespace Draco.Compiler.Internal.Symbols.Synthetized; diff --git a/src/Draco.Compiler/Internal/Symbols/Synthetized/AutoPropertyBackingFieldSymbol.cs b/src/Draco.Compiler/Internal/Symbols/Synthetized/AutoPropertyBackingFieldSymbol.cs new file mode 100644 index 000000000..a35c60096 --- /dev/null +++ b/src/Draco.Compiler/Internal/Symbols/Synthetized/AutoPropertyBackingFieldSymbol.cs @@ -0,0 +1,28 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Draco.Compiler.Internal.Symbols.Synthetized; + +/// +/// Auto-generated backing field for an auto-property. +/// +internal sealed class AutoPropertyBackingFieldSymbol : FieldSymbol +{ + public override TypeSymbol ContainingSymbol { get; } + + public override TypeSymbol Type => this.Property.Type; + public override bool IsMutable => this.Property.Setter is not null; + public override string Name => $"<{this.Property.Name}>_BackingField"; + public override Api.Semantics.Visibility Visibility => Api.Semantics.Visibility.Private; + + public PropertySymbol Property { get; } + + public AutoPropertyBackingFieldSymbol(TypeSymbol containingSymbol, PropertySymbol property) + { + this.ContainingSymbol = containingSymbol; + this.Property = property; + } +} diff --git a/src/Draco.Compiler/Internal/Symbols/Synthetized/AutoPropertyGetterSymbol.cs b/src/Draco.Compiler/Internal/Symbols/Synthetized/AutoPropertyGetterSymbol.cs new file mode 100644 index 000000000..7b59876ec --- /dev/null +++ b/src/Draco.Compiler/Internal/Symbols/Synthetized/AutoPropertyGetterSymbol.cs @@ -0,0 +1,47 @@ +using System; +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Linq; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using Draco.Compiler.Internal.BoundTree; +using Draco.Compiler.Internal.Symbols.Source; +using static Draco.Compiler.Internal.BoundTree.BoundTreeFactory; + +namespace Draco.Compiler.Internal.Symbols.Synthetized; + +/// +/// An auto-generated getter for an auto-property. +/// +internal sealed class AutoPropertyGetterSymbol : FunctionSymbol, IPropertyAccessorSymbol +{ + public override TypeSymbol ContainingSymbol { get; } + + public override string Name => $"{this.Property.Name}_Getter"; + public override bool IsStatic => this.Property.IsStatic; + public override Api.Semantics.Visibility Visibility => this.Property.Visibility; + + public override ImmutableArray Parameters => ImmutableArray.Empty; + public override TypeSymbol ReturnType => this.Property.Type; + + public override BoundStatement Body => LazyInitializer.EnsureInitialized(ref this.body, this.BuildBody); + private BoundStatement? body; + + PropertySymbol IPropertyAccessorSymbol.Property => this.Property; + public SourceAutoPropertySymbol Property { get; } + + public AutoPropertyGetterSymbol(TypeSymbol containingSymbol, SourceAutoPropertySymbol property) + { + this.ContainingSymbol = containingSymbol; + this.Property = property; + } + + private BoundStatement BuildBody() => ExpressionStatement(BlockExpression( + locals: ImmutableArray.Empty, + statements: ImmutableArray.Create( + ExpressionStatement(ReturnExpression(FieldExpression( + receiver: ParameterExpression(new SynthetizedThisParameterSymbol(this)), + field: this.Property.BackingField)))), + value: BoundUnitExpression.Default)); +} diff --git a/src/Draco.Compiler/Internal/Symbols/Synthetized/AutoPropertySetterSymbol.cs b/src/Draco.Compiler/Internal/Symbols/Synthetized/AutoPropertySetterSymbol.cs new file mode 100644 index 000000000..6022e4551 --- /dev/null +++ b/src/Draco.Compiler/Internal/Symbols/Synthetized/AutoPropertySetterSymbol.cs @@ -0,0 +1,56 @@ +using System; +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Linq; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using Draco.Compiler.Internal.BoundTree; +using Draco.Compiler.Internal.Symbols.Source; +using static Draco.Compiler.Internal.BoundTree.BoundTreeFactory; + +namespace Draco.Compiler.Internal.Symbols.Synthetized; + +/// +/// An auto-generated setter for an auto-property. +/// +internal sealed class AutoPropertySetterSymbol : FunctionSymbol, IPropertyAccessorSymbol +{ + public override TypeSymbol ContainingSymbol { get; } + + public override string Name => $"{this.Property.Name}_Setter"; + public override bool IsStatic => this.Property.IsStatic; + public override Api.Semantics.Visibility Visibility => this.Property.Visibility; + + public override ImmutableArray Parameters => InterlockedUtils.InitializeDefault(ref this.parameters, this.BuildParameters); + private ImmutableArray parameters; + + public override TypeSymbol ReturnType => WellKnownTypes.Unit; + + public override BoundStatement Body => LazyInitializer.EnsureInitialized(ref this.body, this.BuildBody); + private BoundStatement? body; + + PropertySymbol IPropertyAccessorSymbol.Property => this.Property; + public SourceAutoPropertySymbol Property { get; } + + public AutoPropertySetterSymbol(TypeSymbol containingSymbol, SourceAutoPropertySymbol property) + { + this.ContainingSymbol = containingSymbol; + this.Property = property; + } + + private ImmutableArray BuildParameters() => + ImmutableArray.Create(new SynthetizedParameterSymbol(this, "value", this.Property.Type)); + + private BoundStatement BuildBody() => ExpressionStatement(BlockExpression( + locals: ImmutableArray.Empty, + statements: ImmutableArray.Create( + ExpressionStatement(AssignmentExpression( + compoundOperator: null, + left: FieldLvalue( + receiver: ParameterExpression(new SynthetizedThisParameterSymbol(this)), + field: this.Property.BackingField), + right: ParameterExpression(this.Parameters[0]))), + ExpressionStatement(ReturnExpression(BoundUnitExpression.Default))), + value: BoundUnitExpression.Default)); +} diff --git a/src/Draco.Compiler/Internal/Symbols/Synthetized/DefaultConstructorSymbol.cs b/src/Draco.Compiler/Internal/Symbols/Synthetized/DefaultConstructorSymbol.cs new file mode 100644 index 000000000..e296f3119 --- /dev/null +++ b/src/Draco.Compiler/Internal/Symbols/Synthetized/DefaultConstructorSymbol.cs @@ -0,0 +1,59 @@ +using System; +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Draco.Compiler.Internal.BoundTree; +using static Draco.Compiler.Internal.BoundTree.BoundTreeFactory; + +namespace Draco.Compiler.Internal.Symbols.Synthetized; + +/// +/// A default constructor for in-source types. +/// +internal sealed class DefaultConstructorSymbol : FunctionSymbol +{ + public override TypeSymbol ContainingSymbol { get; } + + public override string Name => ".ctor"; + public override TypeSymbol ReturnType => WellKnownTypes.Unit; + public override bool IsStatic => false; + public override bool IsSpecialName => true; + public override bool IsConstructor => true; + public override Api.Semantics.Visibility Visibility => Api.Semantics.Visibility.Public; + + public override ImmutableArray Parameters => ImmutableArray.Empty; + + public override BoundStatement Body => this.body ??= this.BuildBody(); + private BoundStatement? body; + + public DefaultConstructorSymbol(TypeSymbol containingSymbol) + { + this.ContainingSymbol = containingSymbol; + } + + private BoundStatement BuildBody() + { + if (this.ContainingSymbol.BaseType is null) + { + // No base type, no need to call base constructor + return ExpressionStatement(ReturnExpression(UnitExpression())); + } + + // We have a base type, call base constructor + var defaultCtor = this.ContainingSymbol.BaseType.Constructors + .FirstOrDefault(ctor => ctor.Parameters.Length == 0); + // TODO: Error if base has no default constructor? + if (defaultCtor is null) throw new NotImplementedException(); + return ExpressionStatement(BlockExpression( + locals: ImmutableArray.Empty, + statements: ImmutableArray.Create( + ExpressionStatement(CallExpression( + receiver: ParameterExpression(new SynthetizedThisParameterSymbol(this)), + method: defaultCtor, + arguments: ImmutableArray.Empty)), + ExpressionStatement(ReturnExpression(BoundUnitExpression.Default))), + value: BoundUnitExpression.Default)); + } +} diff --git a/src/Draco.Compiler/Internal/Symbols/Synthetized/SynthetizedThisParameterSymbol.cs b/src/Draco.Compiler/Internal/Symbols/Synthetized/SynthetizedThisParameterSymbol.cs new file mode 100644 index 000000000..f936e53fc --- /dev/null +++ b/src/Draco.Compiler/Internal/Symbols/Synthetized/SynthetizedThisParameterSymbol.cs @@ -0,0 +1,34 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading; +using System.Threading.Tasks; + +namespace Draco.Compiler.Internal.Symbols.Synthetized; + +/// +/// Represents the "this" parameter of a function generated by the compiler. +/// +internal sealed class SynthetizedThisParameterSymbol : ParameterSymbol +{ + public override FunctionSymbol ContainingSymbol { get; } + public override string Name => "this"; + public override bool IsThis => true; + + public override TypeSymbol Type => LazyInitializer.EnsureInitialized(ref this.type, this.BuildType); + private TypeSymbol? type; + + public SynthetizedThisParameterSymbol(FunctionSymbol containingSymbol) + { + this.ContainingSymbol = containingSymbol; + } + + private TypeSymbol BuildType() + { + var thisType = (TypeSymbol)this.ContainingSymbol.ContainingSymbol!; + return thisType.IsValueType + ? new ReferenceTypeSymbol(thisType) + : thisType; + } +} diff --git a/src/Draco.Compiler/Internal/Syntax/Lexer.cs b/src/Draco.Compiler/Internal/Syntax/Lexer.cs index 5f6ba98ac..44cc92d19 100644 --- a/src/Draco.Compiler/Internal/Syntax/Lexer.cs +++ b/src/Draco.Compiler/Internal/Syntax/Lexer.cs @@ -322,8 +322,10 @@ Unit TakeWithText(TokenKind tokenKind, int length) var tokenKind = ident switch { var _ when ident.Span.SequenceEqual("and") => TokenKind.KeywordAnd, + var _ when ident.Span.SequenceEqual("class") => TokenKind.KeywordClass, var _ when ident.Span.SequenceEqual("else") => TokenKind.KeywordElse, var _ when ident.Span.SequenceEqual("false") => TokenKind.KeywordFalse, + var _ when ident.Span.SequenceEqual("field") => TokenKind.KeywordField, var _ when ident.Span.SequenceEqual("for") => TokenKind.KeywordFor, var _ when ident.Span.SequenceEqual("func") => TokenKind.KeywordFunc, var _ when ident.Span.SequenceEqual("goto") => TokenKind.KeywordGoto, @@ -340,6 +342,7 @@ var _ when ident.Span.SequenceEqual("rem") => TokenKind.KeywordRem, var _ when ident.Span.SequenceEqual("return") => TokenKind.KeywordReturn, var _ when ident.Span.SequenceEqual("true") => TokenKind.KeywordTrue, var _ when ident.Span.SequenceEqual("val") => TokenKind.KeywordVal, + var _ when ident.Span.SequenceEqual("value") => TokenKind.KeywordValue, var _ when ident.Span.SequenceEqual("var") => TokenKind.KeywordVar, var _ when ident.Span.SequenceEqual("while") => TokenKind.KeywordWhile, _ => TokenKind.Identifier, diff --git a/src/Draco.Compiler/Internal/Syntax/Parser.cs b/src/Draco.Compiler/Internal/Syntax/Parser.cs index b1e1f2d8a..50691eb1e 100644 --- a/src/Draco.Compiler/Internal/Syntax/Parser.cs +++ b/src/Draco.Compiler/Internal/Syntax/Parser.cs @@ -153,6 +153,8 @@ private ExpressionParserDelegate BinaryRight(params TokenKind[] operators) => le private static readonly TokenKind[] declarationStarters = new[] { TokenKind.KeywordImport, + TokenKind.KeywordValue, + TokenKind.KeywordClass, TokenKind.KeywordFunc, TokenKind.KeywordModule, TokenKind.KeywordVar, @@ -263,6 +265,15 @@ private DeclarationSyntax ParseDeclaration(DeclarationContext context) case TokenKind.KeywordImport: return this.ParseImportDeclaration(modifier); + case TokenKind.KeywordValue: + { + var valueKeyword = this.Expect(TokenKind.KeywordValue); + return this.ParseClassDeclaration(visibility: modifier, valueType: valueKeyword); + } + + case TokenKind.KeywordClass: + return this.ParseClassDeclaration(visibility: modifier, valueType: null); + case TokenKind.KeywordFunc: return this.ParseFunctionDeclaration(modifier); @@ -394,9 +405,161 @@ private VariableDeclarationSyntax ParseVariableDeclaration(SyntaxToken? visibili return new VariableDeclarationSyntax(visibility, keyword, identifier, type, assignment, semicolon); } + /// + /// Parses a class declaration. + /// + /// Optional visibility modifier token. + /// Optional valuetype modifier token. + /// The parsed . + private ClassDeclarationSyntax ParseClassDeclaration(SyntaxToken? visibility, SyntaxToken? valueType) + { + // Class keyword and name of the class + var classKeyword = this.Expect(TokenKind.KeywordClass); + var name = this.Expect(TokenKind.Identifier); + + // Optional generics + var generics = null as GenericParameterListSyntax; + if (this.Peek() == TokenKind.LessThan) generics = this.ParseGenericParameterList(); + + var primaryCtor = null as PrimaryConstructorSyntax; + switch (this.Peek()) + { + case TokenKind.ParenOpen: + case TokenKind.KeywordPublic: + case TokenKind.KeywordInternal: + primaryCtor = this.ParsePrimaryConstructor(); + break; + } + + var body = this.ParseClassBody(); + + return new ClassDeclarationSyntax( + visibility, + valueType, + classKeyword, + name, + generics, + primaryCtor, + body); + } + + /// + /// Parses a primary constructor, including the visibility modifier and parentheses. + /// + /// The parsed . + private PrimaryConstructorSyntax ParsePrimaryConstructor() + { + var visibility = this.ParseVisibilityModifier(); + + var openParen = this.Expect(TokenKind.ParenOpen); + var ctorParameters = this.ParseSeparatedSyntaxList( + elementParser: this.ParsePrimaryConstructorParameter, + separatorKind: TokenKind.Comma, + stopKind: TokenKind.ParenClose); + var closeParen = this.Expect(TokenKind.ParenClose); + + return new PrimaryConstructorSyntax( + visibility, + openParen, + ctorParameters, + closeParen); + } + + /// + /// Parses a primary constructor parameter. + /// + /// The parsed . + private PrimaryConstructorParameterSyntax ParsePrimaryConstructorParameter() + { + var memberModifiers = this.ParsePrimaryConstructorParameterMemberModifiers(); + var param = this.ParseParameter(); + return new(memberModifiers, param); + } + + /// + /// Parses the modifiers of a primary constructor parameter that could make it a member. + /// Can return null, if the parameter is not a member. + /// + /// The parsed , or null + /// if the parameter is not a member. + private PrimaryConstructorParameterMemberModifiersSyntax? ParsePrimaryConstructorParameterMemberModifiers() + { + var visibilityModifier = this.ParseVisibilityModifier(); + this.Matches(TokenKind.KeywordField, out var fieldToken); + + var peek = this.Peek(); + if (peek is TokenKind.KeywordVar or TokenKind.KeywordVal) + { + var keyword = this.Advance(); + return new PrimaryConstructorParameterMemberModifiersSyntax( + visibilityModifier, + fieldToken, + keyword); + } + else if (visibilityModifier is not null || fieldToken is not null) + { + // TODO: Error + throw new NotImplementedException(); + } + else + { + return null; + } + } + + /// + /// Parses the body of a class. + /// + /// The parsed . + private ClassBodySyntax ParseClassBody() + { + switch (this.Peek()) + { + case TokenKind.Semicolon: + return this.ParseEmptyClassBody(); + case TokenKind.CurlyOpen: + return this.ParseBlockClassBody(); + default: + // TODO + throw new NotImplementedException(); + } + } + + /// + /// Parses an empty class body, which is just a semicolon. + /// + /// The parsed . + private EmptyClassBodySyntax ParseEmptyClassBody() + { + var semicolon = this.Expect(TokenKind.Semicolon); + return new EmptyClassBodySyntax(semicolon); + } + + /// + /// Parses a block class body declared with curly braces. + /// + /// The parsed . + private BlockClassBodySyntax ParseBlockClassBody() + { + var openBrace = this.Expect(TokenKind.CurlyOpen); + var decls = SyntaxList.CreateBuilder(); + while (true) + { + // Break on the end of the block + if (this.Peek() is TokenKind.EndOfInput or TokenKind.CurlyClose) break; + + // Parse a declaration + var decl = this.ParseDeclaration(DeclarationContext.Global); + decls.Add(decl); + } + var closeBrace = this.Expect(TokenKind.CurlyClose); + return new(openBrace, decls.ToSyntaxList(), closeBrace); + } + /// /// Parses a function declaration. /// + /// Optional visibility modifier token. /// The parsed . private FunctionDeclarationSyntax ParseFunctionDeclaration(SyntaxToken? visibility) { diff --git a/src/Draco.Compiler/Internal/Syntax/Syntax.xml b/src/Draco.Compiler/Internal/Syntax/Syntax.xml index 0c4538da6..e100c5eaf 100644 --- a/src/Draco.Compiler/Internal/Syntax/Syntax.xml +++ b/src/Draco.Compiler/Internal/Syntax/Syntax.xml @@ -233,6 +233,195 @@ + + + A class declaration. + + + + + The visibility modifier keyword possibly starting the declaration. + + + + + + + + + A modifier for making a class a value-type. + + + + + + + + The 'class' keyword starting the delcaration. + + + + + + + + The name of the declared class. + + + + + + + + The list of generic parameters, in case the class introduces generics. + + + + + + The primary constructor of the class. + + + + + + The body of the class. + + + + + + + A primary constructor declaration. + + + + + The optional visibility modifier keyword of the constructor. + + + + + + + + + The opening parenthesis before the parameter list. + + + + + + + + The parameters this constructor declares. + + + + + + The closing parenthesis after the parameter list. + + + + + + + + + A single parameter declaration in a primary constructor parameter list. + + + + + Optional modifiers for the parameter, in case it is manifested as a member. + + + + + The parameter declaration. + + + + + + + Modifiers for a primary constructor parameter, in case it is manifested as a member. + + + + + The optional visibility modifier keyword of the member. + + + + + + + + The optional field modifier keyword to mark a field instead of a property. + + + + + + + The keyword introducing the variable, either 'var' or 'val'. + + + + + + + + + + A class declaration body. + + + + + + A semicolon-terminated empty class body. + + + + + The semicolon terminating the class body. + + + + + + + + + A block class body with multiple declarations within braces. + + + + + The opening brace token. + + + + + + + + All declaration syntaxes within the class. + + + + + + The closing brace token. + + + + + + A function declaration.