Implement compilation

This commit is contained in:
Alexander Luzgarev 2020-07-16 16:28:49 +02:00
parent b3c15aa1a9
commit b2c6f8cadd
10 changed files with 286 additions and 2 deletions

View File

@ -1,4 +1,5 @@
using Parser.Binding;
using Parser.Emitting;
namespace Parser
{
@ -16,6 +17,13 @@ namespace Parser
return new Compilation(syntaxTree);
}
public void Emit(string[] references, string outputPath)
{
var emitter = new Emitter();
var boundProgram = GetBoundProgram();
emitter.Emit(boundProgram, references, outputPath);
}
private BoundProgram GetBoundProgram()
{
return Binder.BindProgram(_syntaxTree);

161
Parser/Emitting/Emitter.cs Normal file
View File

@ -0,0 +1,161 @@
using Mono.Cecil;
using Mono.Cecil.Cil;
using Parser.Binding;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
namespace Parser.Emitting
{
public class Emitter
{
private Dictionary<string, TypeReference> _knownTypes = new Dictionary<string, TypeReference>();
private static TypeReference ResolveAndImportType(
string typeName,
List<AssemblyDefinition> assemblies,
AssemblyDefinition assemblyDefinition)
{
var foundTypes = assemblies.SelectMany(a => a.Modules)
.SelectMany(m => m.Types)
.Where(t => t.FullName == typeName)
.ToArray();
if (foundTypes.Length == 1)
{
var typeReference = assemblyDefinition.MainModule.ImportReference(foundTypes[0]);
return typeReference;
}
else if (foundTypes.Length == 0)
{
throw new Exception($"Cannot find type {typeName}");
}
else
{
throw new Exception($"Ambiguous type {typeName}");
}
}
private static MethodReference ResolveAndImportMethod(
string typeName,
string methodName,
string[] parameterTypeNames,
List<AssemblyDefinition> assemblies,
AssemblyDefinition assemblyDefinition)
{
var foundTypes = assemblies.SelectMany(a => a.Modules)
.SelectMany(m => m.Types)
.Where(t => t.FullName == typeName)
.ToArray();
if (foundTypes.Length == 1)
{
var foundType = foundTypes[0];
var methods = foundType.Methods.Where(m => m.Name == methodName);
foreach (var method in methods)
{
if (method.Parameters.Count != parameterTypeNames.Length)
continue;
var allParametersMatch = true;
for (var i = 0; i < parameterTypeNames.Length; i++)
{
if (method.Parameters[i].ParameterType.FullName != parameterTypeNames[i])
{
allParametersMatch = false;
break;
}
}
if (!allParametersMatch)
continue;
return assemblyDefinition.MainModule.ImportReference(method);
}
throw new Exception($"Required method {typeName}.{methodName} not found.");
}
else if (foundTypes.Length == 0)
{
throw new Exception($"Required type {typeName} not found.");
}
else
{
throw new Exception($"Required type {typeName} is ambiguous.");
}
}
public void Emit(BoundProgram program, string[] references, string outputFileName)
{
var assemblies = new List<AssemblyDefinition>();
foreach (var reference in references)
{
try
{
var assembly = AssemblyDefinition.ReadAssembly(reference);
assemblies.Add(assembly);
}
catch (BadImageFormatException)
{
throw new Exception($"Invalid reference '{reference}'.");
}
}
var moduleName = Path.GetFileNameWithoutExtension(outputFileName);
var assemblyName = new AssemblyNameDefinition(
name: moduleName,
version: new Version(1, 0));
var assemblyDefinition = AssemblyDefinition.CreateAssembly(
assemblyName: assemblyName,
moduleName: moduleName,
kind: ModuleKind.Console);
var builtInTypes = new[]
{
"System.Object",
"System.Void"
};
// Resolve built-in types and methods.
foreach (var typeName in builtInTypes)
{
var typeReference = ResolveAndImportType(typeName, assemblies, assemblyDefinition);
_knownTypes.Add(typeName, typeReference);
}
var objectType = _knownTypes["System.Object"];
var voidType = _knownTypes["System.Void"];
var consoleWriteLineReference = ResolveAndImportMethod(
typeName: "System.Console",
methodName: "WriteLine",
parameterTypeNames: new[] { "System.Object" },
assemblies: assemblies,
assemblyDefinition: assemblyDefinition);
// Create type.
var typeDefinition = new TypeDefinition(
@namespace: "",
name: "Program",
attributes: TypeAttributes.Abstract | TypeAttributes.Sealed,
baseType: objectType);
assemblyDefinition.MainModule.Types.Add(typeDefinition);
// Create method.
var methodDefinition = new MethodDefinition(
name: "Main",
attributes: MethodAttributes.Static | MethodAttributes.Private,
returnType: voidType);
var ilProcessor = methodDefinition.Body.GetILProcessor();
ilProcessor.Emit(OpCodes.Ldstr, "Hello world!");
ilProcessor.Emit(OpCodes.Call, consoleWriteLineReference);
ilProcessor.Emit(OpCodes.Ret);
typeDefinition.Methods.Add(methodDefinition);
assemblyDefinition.EntryPoint = methodDefinition;
assemblyDefinition.Write(outputFileName);
}
}
}

View File

@ -7,5 +7,6 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="System.Collections.Immutable" Version="1.5.0" />
<PackageReference Include="Mono.Cecil" Version="0.11.2" />
</ItemGroup>
</Project>

View File

@ -23,7 +23,9 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Repl", "Repl\Repl.csproj",
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MApplication", "MApplication\MApplication.csproj", "{A7EE271C-8822-43EA-BA13-5D6D5DC5B581}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "cmi", "cmi\cmi.csproj", "{C2447F0B-733D-4755-A104-5B82E24D3F47}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "cmi", "cmi\cmi.csproj", "{C2447F0B-733D-4755-A104-5B82E24D3F47}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "cmc", "cmc\cmc.csproj", "{4200B289-ED2B-4C6F-AFDF-EC91FB3837B3}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
@ -63,6 +65,10 @@ Global
{C2447F0B-733D-4755-A104-5B82E24D3F47}.Debug|Any CPU.Build.0 = Debug|Any CPU
{C2447F0B-733D-4755-A104-5B82E24D3F47}.Release|Any CPU.ActiveCfg = Release|Any CPU
{C2447F0B-733D-4755-A104-5B82E24D3F47}.Release|Any CPU.Build.0 = Release|Any CPU
{4200B289-ED2B-4C6F-AFDF-EC91FB3837B3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{4200B289-ED2B-4C6F-AFDF-EC91FB3837B3}.Debug|Any CPU.Build.0 = Debug|Any CPU
{4200B289-ED2B-4C6F-AFDF-EC91FB3837B3}.Release|Any CPU.ActiveCfg = Release|Any CPU
{4200B289-ED2B-4C6F-AFDF-EC91FB3837B3}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE

60
cmc/Program.cs Normal file
View File

@ -0,0 +1,60 @@
using Parser;
using System;
using System.IO;
using Mono.Options;
using System.Collections.Generic;
namespace cmc
{
class Program
{
static int Main(string[] args)
{
var referencePaths = new List<string>();
var outputPath = (string?)null;
var moduleName = (string?)null;
var helpRequested = false;
var sourcePaths = new List<string>();
var options = new OptionSet
{
"usage: cmc <source-paths> [options]",
{ "r=", "The {path} of an assembly to reference", v => referencePaths.Add(v) },
{ "o=", "The output {path} of the assembly to create", v => outputPath = v },
{ "m=", "The {name} of the module", v => moduleName = v },
{ "?|h|help", "Prints help", v => helpRequested = true },
{ "<>", v => sourcePaths.Add(v) }
};
options.Parse(args);
if (helpRequested)
{
options.WriteOptionDescriptions(Console.Out);
return 0;
}
if (sourcePaths.Count > 1)
{
Console.Error.WriteLine("Cannot compile more than one file.");
return -1;
}
if (outputPath == null)
{
outputPath = Path.ChangeExtension(sourcePaths[0], ".exe");
}
if (moduleName == null)
{
moduleName = Path.GetFileNameWithoutExtension(outputPath);
}
var sourcePath = sourcePaths[0];
var text = File.ReadAllText(sourcePath);
var tree = SyntaxTree.Parse(text);
var compilation = Compilation.Create(tree);
compilation.Emit(referencePaths.ToArray(), outputPath);
return 0;
}
}
}

18
cmc/cmc.csproj Normal file
View File

@ -0,0 +1,18 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net5.0</TargetFramework>
<Nullable>enable</Nullable>
<LangVersion>preview</LangVersion>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\Parser\Parser.csproj" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Mono.Options" Version="6.6.0.161" />
</ItemGroup>
</Project>

View File

@ -4,7 +4,6 @@ using System.IO;
namespace cmi
{
class Program
{
static void Main(string[] args)

View File

@ -0,0 +1,7 @@
<Project>
<PropertyGroup>
<DefaultLanguageSourceExtension>.m</DefaultLanguageSourceExtension>
</PropertyGroup>
</Project>

View File

@ -0,0 +1,16 @@
<Project>
<Target Name="CreateManifestResourceNames" />
<Target Name="CoreCompile" DependsOnTargets="$(CoreCompileDependsOn)">
<ItemGroup>
<ReferencePath Remove="@(ReferencePath)"
Condition="'%(FileName)' != 'System.Console' AND
'%(FileName)' != 'System.Runtime'" />
</ItemGroup>
<Message Importance="high" Text="ReferencePath: @(ReferencePath)" />
<Exec Command="dotnet run --project &quot;$(MSBuildThisFileDirectory)\..\cmc\cmc.csproj&quot; -- @(Compile->'&quot;%(Identity)&quot;', ' ') /o &quot;@(IntermediateAssembly)&quot; @(ReferencePath->'/r &quot;%(Identity)&quot;', ' ')"
WorkingDirectory="$(MSBuildProjectDirectory)" />
</Target>
</Project>

View File

@ -0,0 +1,8 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net5.0</TargetFramework>
</PropertyGroup>
</Project>