...
This commit is contained in:
@@ -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 <input-dir>");
|
||||
@@ -21,17 +24,29 @@ if (!Directory.Exists(srcDir))
|
||||
return 1;
|
||||
}
|
||||
|
||||
var error = false;
|
||||
Directory.CreateDirectory(BIN_INT_DIR);
|
||||
Directory.CreateDirectory(BIN_DIR);
|
||||
|
||||
Dictionary<CompilationUnit, SourceText> 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<CompilationUnit> compilationUnits = [];
|
||||
Dictionary<CompilationUnit, SourceText> sourceTexts = [];
|
||||
List<string> 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;
|
||||
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<string> 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<string> 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}");
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user