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