diff --git a/compiler/Generator.cs b/compiler/Generator.cs index e2b097f..3572588 100644 --- a/compiler/Generator.cs +++ b/compiler/Generator.cs @@ -2,11 +2,11 @@ namespace Compiler; -public sealed class Generator(List functions, ModuleGraph moduleGraph) +public sealed class Generator(List functions, ModuleGraph moduleGraph, bool compileLib) { - public static string Emit(List functions, ModuleGraph moduleGraph) + public static string Emit(List functions, ModuleGraph moduleGraph, bool compileLib) { - return new Generator(functions, moduleGraph).Emit(); + return new Generator(functions, moduleGraph, compileLib).Emit(); } private readonly IndentedTextWriter writer = new(); @@ -79,10 +79,10 @@ public sealed class Generator(List functions, ModuleGra writer.WriteLine(); - var main = functions.FirstOrDefault(x => x.Module == "main" && x.Name.Ident == "main"); - - if (main != null) + if (!compileLib) { + var main = functions.First(x => x.Module == "main" && x.Name.Ident == "main"); + writer.WriteLine($$""" int main(int argc, char *argv[]) { diff --git a/compiler/ModuleGraph.cs b/compiler/ModuleGraph.cs index 7791036..cfb8837 100644 --- a/compiler/ModuleGraph.cs +++ b/compiler/ModuleGraph.cs @@ -18,6 +18,13 @@ public class ModuleGraph(Dictionary modules) return module != null; } + public Manifest CreateManifest() + { + return new Manifest(1, GetModules().Select(x => x.CreateManifestModule()).ToList()); + } + + public record Manifest(int Version, IReadOnlyList Modules); + public sealed class Module(string name) { public string Name { get; } = name; @@ -72,37 +79,105 @@ public class ModuleGraph(Dictionary modules) return false; } - public void AddCustomType(string name, NubType type, bool exported) + public void AddCustomType(string name, CustomTypeInfo info) { - customTypes.Add(name, new CustomTypeInfo(type, exported)); + customTypes.Add(name, info); } - public void AddIdentifier(string name, NubType type, bool exported) + public void AddIdentifier(string name, IdentifierInfo info) { - identifierTypes.Add(name, new IdentifierInfo(type, exported)); + identifierTypes.Add(name, info); } - private record CustomTypeInfo(NubType Type, bool Exported); - private record IdentifierInfo(NubType Type, bool Exported); + public ManifestModule CreateManifestModule() + { + var manifestCustomTypes = customTypes.ToDictionary(x => x.Key, x => x.Value.CreateManifestCustomTypeInfo()); + var manifestIdentifiers = identifierTypes.ToDictionary(x => x.Key, x => x.Value.CreateManifestIdentifierInfo()); + return new ManifestModule(Name, manifestCustomTypes, manifestIdentifiers); + } + + public enum Source + { + Ast, + Lib, + } + + public class CustomTypeInfo(NubType type, bool exported, Source source) + { + public NubType Type { get; } = type; + public bool Exported { get; } = exported; + public Source Source { get; } = source; + + public ManifestCustomTypeInfo CreateManifestCustomTypeInfo() + { + return new ManifestCustomTypeInfo(Type, Exported); + } + } + + public class IdentifierInfo(NubType type, bool exported, Source source) + { + public NubType Type { get; } = type; + public bool Exported { get; } = exported; + public Source Source { get; } = source; + + public ManifestIdentifierInfo CreateManifestIdentifierInfo() + { + return new ManifestIdentifierInfo(Type, Exported); + } + } + + public record ManifestModule(string Name, Dictionary CustomTypes, Dictionary Identifiers); + public record ManifestCustomTypeInfo(NubType Type, bool Exported); + public record ManifestIdentifierInfo(NubType Type, bool Exported); } public class Builder { private readonly List asts = []; + private readonly List manifests = []; public void AddAst(Ast ast) { asts.Add(ast); } + public void AddManifest(Manifest manifest) + { + manifests.Add(manifest); + } + public ModuleGraph? Build(out List diagnostics) { diagnostics = []; - var astModuleCache = new Dictionary(); var modules = new Dictionary(); - // First pass: Register modules + // First pass: Register libraries + foreach (var manifest in manifests) + { + foreach (var manifestModule in manifest.Modules) + { + if (!modules.TryGetValue(manifestModule.Name, out var module)) + { + module = new Module(manifestModule.Name); + modules.Add(manifestModule.Name, module); + } + + foreach (var customType in manifestModule.CustomTypes) + { + module.AddCustomType(customType.Key, new Module.CustomTypeInfo(customType.Value.Type, customType.Value.Exported, Module.Source.Lib)); + } + + foreach (var customType in manifestModule.Identifiers) + { + module.AddIdentifier(customType.Key, new Module.IdentifierInfo(customType.Value.Type, customType.Value.Exported, Module.Source.Lib)); + } + } + } + + var astModuleCache = new Dictionary(); + + // First pass: Register modules from ast foreach (var ast in asts) { if (!modules.ContainsKey(ast.ModuleName.Ident)) @@ -119,7 +194,11 @@ public class ModuleGraph(Dictionary modules) var module = astModuleCache[ast]; foreach (var structDef in ast.Definitions.OfType()) - module.AddCustomType(structDef.Name.Ident, new NubTypeStruct(module.Name, structDef.Name.Ident, structDef.Packed), structDef.Exported); + { + var type = new NubTypeStruct(module.Name, structDef.Name.Ident, structDef.Packed); + var info = new Module.CustomTypeInfo(type, structDef.Exported, Module.Source.Ast); + module.AddCustomType(structDef.Name.Ident, info); + } } // Third pass: Resolve struct fields @@ -147,13 +226,15 @@ public class ModuleGraph(Dictionary modules) var parameters = funcDef.Parameters.Select(x => ResolveType(x.Type, module.Name)).ToList(); var returnType = ResolveType(funcDef.ReturnType, module.Name); var funcType = NubTypeFunc.Get(parameters, returnType); - module.AddIdentifier(funcDef.Name.Ident, funcType, funcDef.Exported); + var info = new Module.IdentifierInfo(funcType, funcDef.Exported, Module.Source.Ast); + module.AddIdentifier(funcDef.Name.Ident, info); } foreach (var globalVariable in ast.Definitions.OfType()) { var type = ResolveType(globalVariable.Type, module.Name); - module.AddIdentifier(globalVariable.Name.Ident, type, globalVariable.Exported); + var info = new Module.IdentifierInfo(type, globalVariable.Exported, Module.Source.Ast); + module.AddIdentifier(globalVariable.Name.Ident, info); } } diff --git a/compiler/NubType.cs b/compiler/NubType.cs index 51f98e9..96c415a 100644 --- a/compiler/NubType.cs +++ b/compiler/NubType.cs @@ -5,6 +5,7 @@ namespace Compiler; public abstract class NubType { public abstract override string ToString(); + public string GetSignature() => TypeMangler.Encode(this); } public sealed class NubTypeVoid : NubType @@ -214,7 +215,7 @@ static class TypeMangler break; case NubTypeStruct st: - sb.Append($"T({st.Module}::{st.Name})").Append(st.Module).Append("::").Append(st.Name).Append(')'); + sb.Append($"T({st.Module}::{st.Name})"); break; case NubTypeFunc fn: @@ -239,7 +240,7 @@ static class Hashing public static ulong Fnv1a64(string text) { const ulong offset = 14695981039346656037UL; - const ulong prime = 1099511628211UL; + const ulong prime = 1099511628211UL; ulong hash = offset; foreach (var c in Encoding.UTF8.GetBytes(text)) diff --git a/compiler/Program.cs b/compiler/Program.cs index bb74939..8fb08f9 100644 --- a/compiler/Program.cs +++ b/compiler/Program.cs @@ -1,10 +1,77 @@ using System.Diagnostics; +using System.IO.Compression; +using System.Text.Json; using Compiler; +var nubFiles = new List(); +var libFiles = new List(); +var compileLib = false; + +for (int i = 0; i < args.Length; i++) +{ + string arg = args[i]; + + if (arg.StartsWith("--type=")) + { + var value = arg.Split("--type=")[1]; + switch (value) + { + case "lib": + compileLib = true; + break; + case "exe": + compileLib = false; + break; + default: + DiagnosticFormatter.Print(Diagnostic.Error("Type must be 'exe' or 'lib'").Build(), Console.Error); + return 1; + } + } + else if (arg.EndsWith(".nub")) + { + nubFiles.Add(arg); + } + else if (arg.EndsWith(".nublib")) + { + libFiles.Add(arg); + } + else if (arg == "--help") + { + Console.WriteLine(""" + Usage: nubc [options] + + Options: + --type=exe Compile the input files into an executable (default) + --type=lib Compile the input files into a library + --help Show this help message + + Files: + *.nub Nub source files to compile + *.nublib Precompiled Nub libraries to link + + Example: + nubc --type=exe main.nub utils.nub math.nublib + """); + } + else + { + DiagnosticFormatter.Print(Diagnostic.Error($"Unrecognized option '{arg}'").Build(), Console.Error); + return 1; + } +} + var moduleGraphBuilder = ModuleGraph.Create(); var asts = new List(); +var archivePaths = new List(); -foreach (var fileName in args) +foreach (var libPath in libFiles) +{ + var lib = ReadNublib(libPath); + archivePaths.Add(lib.ArchivePath); + moduleGraphBuilder.AddManifest(lib.Manifest); +} + +foreach (var fileName in nubFiles) { var file = File.ReadAllText(fileName); @@ -54,13 +121,97 @@ foreach (var ast in asts) } } -var output = Generator.Emit(functions, moduleGraph); +var output = Generator.Emit(functions, moduleGraph, compileLib); -Directory.Delete(".build", recursive: true); -Directory.CreateDirectory(".build"); +if (Directory.Exists(".build")) +{ + CleanDirectory(".build"); +} +else +{ + Directory.CreateDirectory(".build"); +} -File.WriteAllText(".build/out.c", output); +if (compileLib) +{ + File.WriteAllText(".build/out.c", output); + Process.Start("gcc", ["-Og", "-fvisibility=hidden", "-fno-builtin", "-c", "-o", ".build/out.o", ".build/out.c", .. archivePaths]).WaitForExit(); + Process.Start("ar", ["rcs", ".build/out.a", ".build/out.o"]).WaitForExit(); + WriteNublib(".build/out.nublib", ".build/out.a", moduleGraph.CreateManifest()); +} +else +{ + File.WriteAllText(".build/out.c", output); + Process.Start("gcc", ["-Og", "-fvisibility=hidden", "-fno-builtin", "-o", ".build/out", ".build/out.c", .. archivePaths]).WaitForExit(); +} -Process.Start("gcc", ["-Og", "-g", "-o", ".build/out", ".build/out.c", "-fvisibility=hidden","-fno-builtin"]); +return 0; -return 0; \ No newline at end of file +static void CleanDirectory(string dirName) +{ + var dir = new DirectoryInfo(dirName); + + foreach (var file in dir.GetFiles()) + { + file.Delete(); + } + + foreach (var subdir in dir.GetDirectories()) + { + CleanDirectory(subdir.FullName); + subdir.Delete(); + } +} + +static void WriteNublib(string outputPath, string archivePath, ModuleGraph.Manifest manifest) +{ + using var fs = new FileStream(outputPath, FileMode.Create); + using var zip = new ZipArchive(fs, ZipArchiveMode.Create); + + var serialized = JsonSerializer.Serialize(manifest, new JsonSerializerOptions + { + PropertyNamingPolicy = JsonNamingPolicy.CamelCase, + WriteIndented = true, + }); + + var manifestEntry = zip.CreateEntry("manifest.json"); + using (var writer = new StreamWriter(manifestEntry.Open())) + { + writer.Write(JsonSerializer.Serialize(manifest)); + } + + var archiveEntry = zip.CreateEntry("lib.a"); + + using var entryStream = archiveEntry.Open(); + using var fileStream = File.OpenRead(archivePath); + + fileStream.CopyTo(entryStream); +} + +static NublibLoadResult ReadNublib(string nublibPath) +{ + using var fs = new FileStream(nublibPath, FileMode.Open, FileAccess.Read); + using var zip = new ZipArchive(fs, ZipArchiveMode.Read); + + var manifestEntry = zip.GetEntry("manifest.json") ?? throw new FileNotFoundException("Manifest not found in nublib", "manifest.json"); + + ModuleGraph.Manifest manifest; + using (var reader = new StreamReader(manifestEntry.Open())) + { + var json = reader.ReadToEnd(); + manifest = JsonSerializer.Deserialize(json) ?? throw new InvalidDataException("Failed to deserialize manifest.json"); + } + + var archiveEntry = zip.Entries.FirstOrDefault(e => e.Name.EndsWith(".a")) ?? throw new FileNotFoundException("Archive not found in nublib", "*.a"); + + string tempArchivePath = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString("N") + ".a"); + using (var entryStream = archiveEntry.Open()) + using (var tempFile = File.Create(tempArchivePath)) + { + entryStream.CopyTo(tempFile); + } + + return new NublibLoadResult(manifest, tempArchivePath); +} + +public record NublibLoadResult(ModuleGraph.Manifest Manifest, string ArchivePath); diff --git a/compiler/TypeChecker.cs b/compiler/TypeChecker.cs index 40d6798..a89d96b 100644 --- a/compiler/TypeChecker.cs +++ b/compiler/TypeChecker.cs @@ -20,6 +20,8 @@ public sealed class TypeChecker(string fileName, string moduleName, NodeDefiniti foreach (var parameter in function.Parameters) { + scope.DeclareIdentifier(parameter.Name.Ident, ResolveType(parameter.Type)); + try { parameters.Add(CheckDefinitionFuncParameter(parameter)); diff --git a/examples/build.sh b/examples/build.sh new file mode 100755 index 0000000..9c7684e --- /dev/null +++ b/examples/build.sh @@ -0,0 +1,11 @@ +pushd math + +dotnet run --project ../../compiler math.nub --type=lib + +popd + +pushd program + +dotnet run --project ../../compiler main.nub ../math/.build/out.nublib + +popd \ No newline at end of file diff --git a/examples/math/.build/out.a b/examples/math/.build/out.a new file mode 100644 index 0000000..1032a05 Binary files /dev/null and b/examples/math/.build/out.a differ diff --git a/examples/math/.build/out.c b/examples/math/.build/out.c new file mode 100644 index 0000000..787fadd --- /dev/null +++ b/examples/math/.build/out.c @@ -0,0 +1,24 @@ +#include +#include +#include +#include +#include + +struct nub_core_string +{ + const char *data; + int length; +}; + + + +int32_t nub_math_add_cc3fc9d68812b10d(int32_t, int32_t); + + +int32_t nub_math_add_cc3fc9d68812b10d(int32_t a, int32_t b) +{ + { + return (a + b); + } +} + diff --git a/examples/math/.build/out.nublib b/examples/math/.build/out.nublib new file mode 100644 index 0000000..0ef4938 Binary files /dev/null and b/examples/math/.build/out.nublib differ diff --git a/examples/math/.build/out.o b/examples/math/.build/out.o new file mode 100644 index 0000000..f8e2349 Binary files /dev/null and b/examples/math/.build/out.o differ diff --git a/examples/math/math.nub b/examples/math/math.nub new file mode 100644 index 0000000..d1ca09c --- /dev/null +++ b/examples/math/math.nub @@ -0,0 +1,6 @@ +module math + +export func add(a: i32 b: i32): i32 +{ + return a + b +} \ No newline at end of file diff --git a/examples/program/main.nub b/examples/program/main.nub new file mode 100644 index 0000000..99f5d86 --- /dev/null +++ b/examples/program/main.nub @@ -0,0 +1,6 @@ +module main + +func main(): i32 +{ + return math::add(1, 2) +} \ No newline at end of file