From b2c6f8cadd67035b15e40c3d1f4e3ca370136c8a Mon Sep 17 00:00:00 2001 From: Alexander Luzgarev Date: Thu, 16 Jul 2020 16:28:49 +0200 Subject: [PATCH] Implement compilation --- Parser/Compilation.cs | 8 ++ Parser/Emitting/Emitter.cs | 161 ++++++++++++++++++++++++++ Parser/Parser.csproj | 1 + Solution.sln | 8 +- cmc/Program.cs | 60 ++++++++++ cmc/cmc.csproj | 18 +++ cmi/Program.cs | 1 - examples/Directory.Build.props | 7 ++ examples/Directory.Build.targets | 16 +++ examples/helloworld/helloworld.cmproj | 8 ++ 10 files changed, 286 insertions(+), 2 deletions(-) create mode 100644 Parser/Emitting/Emitter.cs create mode 100644 cmc/Program.cs create mode 100644 cmc/cmc.csproj create mode 100644 examples/Directory.Build.props create mode 100644 examples/Directory.Build.targets create mode 100644 examples/helloworld/helloworld.cmproj diff --git a/Parser/Compilation.cs b/Parser/Compilation.cs index d566e7e..1a9756f 100644 --- a/Parser/Compilation.cs +++ b/Parser/Compilation.cs @@ -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); diff --git a/Parser/Emitting/Emitter.cs b/Parser/Emitting/Emitter.cs new file mode 100644 index 0000000..01b68e0 --- /dev/null +++ b/Parser/Emitting/Emitter.cs @@ -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 _knownTypes = new Dictionary(); + + private static TypeReference ResolveAndImportType( + string typeName, + List 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 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(); + + 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); + } + } +} diff --git a/Parser/Parser.csproj b/Parser/Parser.csproj index acfb9dc..d9ed05a 100644 --- a/Parser/Parser.csproj +++ b/Parser/Parser.csproj @@ -7,5 +7,6 @@ + \ No newline at end of file diff --git a/Solution.sln b/Solution.sln index 56fc479..69dedfa 100644 --- a/Solution.sln +++ b/Solution.sln @@ -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 diff --git a/cmc/Program.cs b/cmc/Program.cs new file mode 100644 index 0000000..d1e1762 --- /dev/null +++ b/cmc/Program.cs @@ -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(); + var outputPath = (string?)null; + var moduleName = (string?)null; + var helpRequested = false; + var sourcePaths = new List(); + var options = new OptionSet + { + "usage: cmc [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; + } + } +} diff --git a/cmc/cmc.csproj b/cmc/cmc.csproj new file mode 100644 index 0000000..a6b36b8 --- /dev/null +++ b/cmc/cmc.csproj @@ -0,0 +1,18 @@ + + + + Exe + net5.0 + enable + preview + + + + + + + + + + + diff --git a/cmi/Program.cs b/cmi/Program.cs index f1f0042..c0b3932 100644 --- a/cmi/Program.cs +++ b/cmi/Program.cs @@ -4,7 +4,6 @@ using System.IO; namespace cmi { - class Program { static void Main(string[] args) diff --git a/examples/Directory.Build.props b/examples/Directory.Build.props new file mode 100644 index 0000000..2876fae --- /dev/null +++ b/examples/Directory.Build.props @@ -0,0 +1,7 @@ + + + + .m + + + \ No newline at end of file diff --git a/examples/Directory.Build.targets b/examples/Directory.Build.targets new file mode 100644 index 0000000..e75f061 --- /dev/null +++ b/examples/Directory.Build.targets @@ -0,0 +1,16 @@ + + + + + + + + + + + + + diff --git a/examples/helloworld/helloworld.cmproj b/examples/helloworld/helloworld.cmproj new file mode 100644 index 0000000..2082704 --- /dev/null +++ b/examples/helloworld/helloworld.cmproj @@ -0,0 +1,8 @@ + + + + Exe + net5.0 + + +