using System.Diagnostics; using System.Reflection; using Generation.QBE; using Syntax; using Syntax.Parsing; using Syntax.Tokenization; using Syntax.Typing; const string BIN_DIR = "bin"; const string BIN_INT_DIR = "bin-int"; if (args.Length != 1) { Console.Error.WriteLine("Usage: nub "); Console.Error.WriteLine("Example: nub src"); return 1; } var srcDir = Path.GetFullPath(args[0]); if (!Directory.Exists(srcDir)) { Console.Error.WriteLine($"Error: Input directory '{srcDir}' does not exist."); return 1; } Directory.CreateDirectory(BIN_INT_DIR); Directory.CreateDirectory(BIN_DIR); foreach (var file in Directory.GetFiles(BIN_INT_DIR)) { File.Delete(file); } foreach (var file in Directory.GetFiles(BIN_DIR)) { File.Delete(file); } var error = false; var compilationUnits = new List(); var sourceTexts = new Dictionary(); var objectFiles = new List(); foreach (var file in Directory.EnumerateFiles(srcDir, "*.nub", SearchOption.AllDirectories)) { var content = File.ReadAllText(file); var sourceText = new SourceText(file, content); var tokenizeResult = Tokenizer.Tokenize(sourceText); tokenizeResult.PrintAllDiagnostics(); error = error || tokenizeResult.HasErrors; var parseResult = Parser.ParseFile(tokenizeResult.Value); parseResult.PrintAllDiagnostics(); error = error || parseResult.HasErrors; if (parseResult.Value != null) { compilationUnits.Add(parseResult.Value); sourceTexts[parseResult.Value] = sourceText; } } if (error) { return 1; } var definitionTable = new DefinitionTable(compilationUnits); foreach (var compilationUnit in compilationUnits) { var typeCheckResult = TypeChecker.Check(compilationUnit, definitionTable); typeCheckResult.PrintAllDiagnostics(); error = error || typeCheckResult.HasErrors; } if (error) { return 1; } foreach (var compilationUnit in compilationUnits) { var ssaCode = QBEGenerator.Generate(compilationUnit, definitionTable); var sourceFileName = Path.GetFileNameWithoutExtension(sourceTexts[compilationUnit].Path); var sourceHash = Math.Abs(sourceTexts[compilationUnit].Path.GetHashCode()).ToString("x8"); var baseOutputName = $"{sourceFileName}_{sourceHash}"; var assemblyCode = await InvokeQBE(ssaCode); var asmPath = Path.Combine(BIN_INT_DIR, $"{baseOutputName}.s"); await File.WriteAllTextAsync(asmPath, assemblyCode); var objPath = Path.Combine(BIN_INT_DIR, $"{baseOutputName}.o"); var asmSuccess = await InvokeAssembler(asmPath, objPath); if (!asmSuccess) { return 1; } objectFiles.Add(objPath); } var assembly = Assembly.GetExecutingAssembly(); var runtimeResources = assembly .GetManifestResourceNames() .Where(name => name.EndsWith(".s", StringComparison.OrdinalIgnoreCase)); foreach (var resourceName in runtimeResources) { await using var stream = assembly.GetManifestResourceStream(resourceName); if (stream == null) { Console.Error.WriteLine($"Warning: Could not load embedded resource {resourceName}"); continue; } using var reader = new StreamReader(stream); var assemblyCode = await reader.ReadToEndAsync(); var fileName = resourceName.Split('.').Reverse().Skip(1).First(); var asmPath = Path.Combine(BIN_INT_DIR, $"runtime_{fileName}.s"); var objPath = Path.Combine(BIN_INT_DIR, $"runtime_{fileName}.o"); await File.WriteAllTextAsync(asmPath, assemblyCode); var asmSuccess = await InvokeAssembler(asmPath, objPath); if (!asmSuccess) { return 1; } objectFiles.Add(objPath); File.Delete(asmPath); } var outputPath = Path.Combine(BIN_DIR, "out"); var linkSuccess = await InvokeLinker(objectFiles, outputPath); return linkSuccess ? 0 : 1; static async Task InvokeQBE(string ssa) { using var qbeProcess = new Process(); qbeProcess.StartInfo = new ProcessStartInfo { FileName = "qbe", UseShellExecute = false, RedirectStandardInput = true, RedirectStandardOutput = true, RedirectStandardError = true, CreateNoWindow = true }; qbeProcess.Start(); await qbeProcess.StandardInput.WriteAsync(ssa); qbeProcess.StandardInput.Close(); var assemblyCode = await qbeProcess.StandardOutput.ReadToEndAsync(); var qbeErrors = await qbeProcess.StandardError.ReadToEndAsync(); await qbeProcess.WaitForExitAsync(); if (qbeProcess.ExitCode != 0) { throw new Exception($"QBE errors:\n{qbeErrors}"); } return assemblyCode; } static async Task InvokeAssembler(string asmPath, string objPath) { using var gccProcess = new Process(); gccProcess.StartInfo = new ProcessStartInfo("gcc", ["-c", asmPath, "-o", objPath]) { UseShellExecute = false, RedirectStandardOutput = true, RedirectStandardError = true, CreateNoWindow = true }; gccProcess.Start(); await gccProcess.WaitForExitAsync(); var gccErrors = await gccProcess.StandardError.ReadToEndAsync(); if (!string.IsNullOrWhiteSpace(gccErrors)) { Console.Error.WriteLine("gcc error:\n" + gccErrors); } return gccProcess.ExitCode == 0; } static async Task InvokeLinker(List objectFiles, string outputPath) { using var gccProcess = new Process(); gccProcess.StartInfo = new ProcessStartInfo("gcc", ["-nostartfiles", "-o", outputPath, ..objectFiles]) { UseShellExecute = false, RedirectStandardOutput = true, RedirectStandardError = true, CreateNoWindow = true }; gccProcess.Start(); await gccProcess.WaitForExitAsync(); var gccErrors = await gccProcess.StandardError.ReadToEndAsync(); if (!string.IsNullOrWhiteSpace(gccErrors)) { Console.Error.WriteLine("gcc error:\n" + gccErrors); } return gccProcess.ExitCode == 0; }