Implement compilation
This commit is contained in:
parent
b3c15aa1a9
commit
b2c6f8cadd
@ -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
161
Parser/Emitting/Emitter.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
@ -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>
|
@ -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
60
cmc/Program.cs
Normal 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
18
cmc/cmc.csproj
Normal 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>
|
@ -4,7 +4,6 @@ using System.IO;
|
||||
|
||||
namespace cmi
|
||||
{
|
||||
|
||||
class Program
|
||||
{
|
||||
static void Main(string[] args)
|
||||
|
7
examples/Directory.Build.props
Normal file
7
examples/Directory.Build.props
Normal file
@ -0,0 +1,7 @@
|
||||
<Project>
|
||||
|
||||
<PropertyGroup>
|
||||
<DefaultLanguageSourceExtension>.m</DefaultLanguageSourceExtension>
|
||||
</PropertyGroup>
|
||||
|
||||
</Project>
|
16
examples/Directory.Build.targets
Normal file
16
examples/Directory.Build.targets
Normal 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 "$(MSBuildThisFileDirectory)\..\cmc\cmc.csproj" -- @(Compile->'"%(Identity)"', ' ') /o "@(IntermediateAssembly)" @(ReferencePath->'/r "%(Identity)"', ' ')"
|
||||
WorkingDirectory="$(MSBuildProjectDirectory)" />
|
||||
</Target>
|
||||
|
||||
</Project>
|
8
examples/helloworld/helloworld.cmproj
Normal file
8
examples/helloworld/helloworld.cmproj
Normal file
@ -0,0 +1,8 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
<TargetFramework>net5.0</TargetFramework>
|
||||
</PropertyGroup>
|
||||
|
||||
</Project>
|
Loading…
x
Reference in New Issue
Block a user