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 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); var tokens = Tokenizer.Tokenize(fileName, file, out var tokenizerDiagnostics); foreach (var diagnostic in tokenizerDiagnostics) DiagnosticFormatter.Print(diagnostic, Console.Error); if (tokens == null) return 1; var ast = Parser.Parse(fileName, tokens, out var parserDiagnostics); foreach (var diagnostic in parserDiagnostics) DiagnosticFormatter.Print(diagnostic, Console.Error); if (ast == null) return 1; moduleGraphBuilder.AddAst(ast); asts.Add(ast); } var moduleGraph = moduleGraphBuilder.Build(out var moduleGraphDiagnostics); foreach (var diagnostic in moduleGraphDiagnostics) DiagnosticFormatter.Print(diagnostic, Console.Error); if (moduleGraph == null) return 1; var functions = new List(); foreach (var ast in asts) { foreach (var func in ast.Definitions.OfType()) { var typedFunction = TypeChecker.CheckFunction(ast.FileName, ast.ModuleName.Ident, func, moduleGraph, out var typeCheckerDiagnostics); foreach (var diagnostic in typeCheckerDiagnostics) DiagnosticFormatter.Print(diagnostic, Console.Error); if (typedFunction == null) return 1; functions.Add(typedFunction); } } var output = Generator.Emit(functions, moduleGraph, compileLib); if (Directory.Exists(".build")) { CleanDirectory(".build"); } else { Directory.CreateDirectory(".build"); } 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(); } return 0; 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, 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, }); File.WriteAllText(".build/manifest.json", serialized); 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"); 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(Manifest Manifest, string ArchivePath);