Implement compilation
This commit is contained in:
parent
b3c15aa1a9
commit
b2c6f8cadd
@ -1,4 +1,5 @@
|
|||||||
using Parser.Binding;
|
using Parser.Binding;
|
||||||
|
using Parser.Emitting;
|
||||||
|
|
||||||
namespace Parser
|
namespace Parser
|
||||||
{
|
{
|
||||||
@ -16,6 +17,13 @@ namespace Parser
|
|||||||
return new Compilation(syntaxTree);
|
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()
|
private BoundProgram GetBoundProgram()
|
||||||
{
|
{
|
||||||
return Binder.BindProgram(_syntaxTree);
|
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>
|
</PropertyGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="System.Collections.Immutable" Version="1.5.0" />
|
<PackageReference Include="System.Collections.Immutable" Version="1.5.0" />
|
||||||
|
<PackageReference Include="Mono.Cecil" Version="0.11.2" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
</Project>
|
</Project>
|
@ -23,7 +23,9 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Repl", "Repl\Repl.csproj",
|
|||||||
EndProject
|
EndProject
|
||||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MApplication", "MApplication\MApplication.csproj", "{A7EE271C-8822-43EA-BA13-5D6D5DC5B581}"
|
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MApplication", "MApplication\MApplication.csproj", "{A7EE271C-8822-43EA-BA13-5D6D5DC5B581}"
|
||||||
EndProject
|
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
|
EndProject
|
||||||
Global
|
Global
|
||||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
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}.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.ActiveCfg = Release|Any CPU
|
||||||
{C2447F0B-733D-4755-A104-5B82E24D3F47}.Release|Any CPU.Build.0 = 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
|
EndGlobalSection
|
||||||
GlobalSection(SolutionProperties) = preSolution
|
GlobalSection(SolutionProperties) = preSolution
|
||||||
HideSolutionNode = FALSE
|
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
|
namespace cmi
|
||||||
{
|
{
|
||||||
|
|
||||||
class Program
|
class Program
|
||||||
{
|
{
|
||||||
static void Main(string[] args)
|
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