using System.Diagnostics; using System.Reflection; using Nub.Lang.Common; using Nub.Lang.Generation.QBE; using Nub.Lang.Syntax; using Nub.Lang.Syntax.Parsing; using Nub.Lang.Syntax.Tokenization; using Nub.Lang.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; List compilationUnits = []; Dictionary sourceTexts = []; List objectFiles = []; 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}"; try { 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"); await InvokeAssembler(asmPath, objPath); objectFiles.Add(objPath); } catch (Exception ex) { Console.Error.WriteLine($"Error processing {sourceTexts[compilationUnit].Path}: {ex.Message}"); return 1; } } var assembly = Assembly.GetExecutingAssembly(); var runtimeResources = assembly.GetManifestResourceNames().Where(name => name.EndsWith(".s", StringComparison.OrdinalIgnoreCase)); foreach (var resourceName in runtimeResources) { try { 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); await InvokeAssembler(asmPath, objPath); objectFiles.Add(objPath); File.Delete(asmPath); } catch (Exception ex) { Console.Error.WriteLine($"Error processing runtime resource {resourceName}: {ex.Message}"); return 1; } } try { var outputPath = Path.Combine(BIN_DIR, "out"); await InvokeLinker(objectFiles, outputPath); } catch (Exception ex) { Console.Error.WriteLine($"Error linking: {ex.Message}"); return 1; } return 0; 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 asProcess = new Process(); asProcess.StartInfo = new ProcessStartInfo("gcc", ["-c", asmPath, "-o", objPath]) { UseShellExecute = false, RedirectStandardOutput = true, RedirectStandardError = true, CreateNoWindow = true }; asProcess.Start(); var asOutput = await asProcess.StandardOutput.ReadToEndAsync(); var asErrors = await asProcess.StandardError.ReadToEndAsync(); await asProcess.WaitForExitAsync(); if (asProcess.ExitCode != 0) { throw new Exception($"Assembler errors:\n{asErrors}"); } } 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(); var gccOutput = await gccProcess.StandardOutput.ReadToEndAsync(); var gccErrors = await gccProcess.StandardError.ReadToEndAsync(); await gccProcess.WaitForExitAsync(); if (gccProcess.ExitCode != 0) { throw new Exception($"Linker errors:\n{gccErrors}"); } }