This repository has been archived on 2025-10-23. You can view files and clone it, but cannot push or open issues or pull requests.
Files
nub-lang-archive/src/CLI/Program.cs
nub31 3789c24eb9 ...
2025-06-13 00:26:37 +02:00

220 lines
5.9 KiB
C#

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 <input-dir>");
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<CompilationUnit>();
var sourceTexts = new Dictionary<CompilationUnit, SourceText>();
var objectFiles = new List<string>();
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<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<bool> 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<bool> 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();
await gccProcess.WaitForExitAsync();
var gccErrors = await gccProcess.StandardError.ReadToEndAsync();
if (!string.IsNullOrWhiteSpace(gccErrors))
{
Console.Error.WriteLine("gcc error:\n" + gccErrors);
}
return gccProcess.ExitCode == 0;
}