From 7f6a825c016e675a3a3be8c0c31a72d724fba3d1 Mon Sep 17 00:00:00 2001 From: nub31 Date: Thu, 12 Jun 2025 23:21:09 +0200 Subject: [PATCH] ... --- build.sh | 19 +--- src/lang/Nub.Lang.CLI/Program.cs | 190 +++++++++++++++++++++++++------ 2 files changed, 154 insertions(+), 55 deletions(-) diff --git a/build.sh b/build.sh index 4c6ec11..36086ba 100755 --- a/build.sh +++ b/build.sh @@ -1,19 +1,2 @@ #!/bin/bash -set -e - -dotnet build src/lang/Nub.Lang.CLI - -mkdir -p bin-int bin -rm -rf bin-int/* bin/* - -nub example - -find bin-int -name '*.s' | while read -r file; do - as "$file" -o "bin-int/$(basename "${file}" .s).o" -done - -find src/runtime -name '*.s' | while read -r file; do - as "$file" -o "bin-int/$(basename "${file}" .s).o" -done - -gcc -nostartfiles -o bin/out bin-int/*.o +dotnet run --project src/lang/Nub.Lang.CLI/ example \ No newline at end of file diff --git a/src/lang/Nub.Lang.CLI/Program.cs b/src/lang/Nub.Lang.CLI/Program.cs index f84926c..944d1ac 100644 --- a/src/lang/Nub.Lang.CLI/Program.cs +++ b/src/lang/Nub.Lang.CLI/Program.cs @@ -6,6 +6,9 @@ using Nub.Lang.Frontend.Lexing; using Nub.Lang.Frontend.Parsing; using Nub.Lang.Frontend.Typing; +const string BIN_DIR = "bin"; +const string BIN_INT_DIR = "bin-int"; + if (args.Length != 1) { Console.Error.WriteLine("Usage: nub "); @@ -21,17 +24,29 @@ if (!Directory.Exists(srcDir)) return 1; } -var error = false; +Directory.CreateDirectory(BIN_INT_DIR); +Directory.CreateDirectory(BIN_DIR); -Dictionary sourceTexts = []; +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 = Lexer.Tokenize(sourceText); tokenizeResult.PrintAllDiagnostics(); error = error || tokenizeResult.HasErrors; @@ -72,48 +87,149 @@ foreach (var compilationUnit in compilationUnits) var sourceFileName = Path.GetFileNameWithoutExtension(sourceTexts[compilationUnit].Path); var sourceHash = Math.Abs(sourceTexts[compilationUnit].Path.GetHashCode()).ToString("x8"); - var timestamp = DateTimeOffset.UtcNow.ToUnixTimeSeconds().ToString("x"); - var baseOutputName = $"{sourceFileName}_{sourceHash}_{timestamp}"; + var baseOutputName = $"{sourceFileName}_{sourceHash}"; try { - 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(ssaCode); - qbeProcess.StandardInput.Close(); - - var assemblyCode = await qbeProcess.StandardOutput.ReadToEndAsync(); - var qbeErrors = await qbeProcess.StandardError.ReadToEndAsync(); - - await qbeProcess.WaitForExitAsync(); - - if (qbeProcess.ExitCode != 0) - { - Console.Error.WriteLine($"QBE failed for {sourceTexts[compilationUnit].Path}:"); - Console.Error.WriteLine(qbeErrors); - error = true; - continue; - } - - var asmPath = Path.Combine("bin-int", $"{baseOutputName}.s"); + var assemblyCode = await InvokeQBE(ssaCode); + + var asmPath = Path.Combine(BIN_INT_DIR, $"{baseOutputName}.s"); await File.WriteAllTextAsync(asmPath, assemblyCode); + Console.Out.WriteLine($"Generated: {asmPath}"); + + var objPath = Path.Combine(BIN_INT_DIR, $"{baseOutputName}.o"); + await InvokeAssembler(asmPath, objPath); + objectFiles.Add(objPath); + Console.Out.WriteLine($"Assembled: {objPath}"); } catch (Exception ex) { - Console.Error.WriteLine($"Error invoking QBE for {sourceTexts[compilationUnit].Path}: {ex.Message}"); + Console.Error.WriteLine($"Error processing {sourceTexts[compilationUnit].Path}: {ex.Message}"); error = true; } } -return error ? 1 : 0; \ No newline at end of file +if (error) +{ + return 1; +} + +const string RUNTIME_DIR = "src/runtime"; +if (Directory.Exists(RUNTIME_DIR)) +{ + foreach (var runtimeFile in Directory.EnumerateFiles(RUNTIME_DIR, "*.s", SearchOption.AllDirectories)) + { + try + { + var runtimeFileName = Path.GetFileNameWithoutExtension(runtimeFile); + var objPath = Path.Combine(BIN_INT_DIR, $"runtime_{runtimeFileName}.o"); + + await InvokeAssembler(runtimeFile, objPath); + objectFiles.Add(objPath); + Console.Out.WriteLine($"Runtime assembled: {objPath}"); + } + catch (Exception ex) + { + Console.Error.WriteLine($"Error assembling runtime file {runtimeFile}: {ex.Message}"); + error = true; + } + } +} + +if (error) +{ + return 1; +} + +try +{ + var outputPath = Path.Combine(BIN_DIR, "out"); + await InvokeLinker(objectFiles, outputPath); + Console.Out.WriteLine($"Linked executable: {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 failed for {asmPath}:\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 failed:\n{gccErrors}"); + } +} \ No newline at end of file