236 lines
6.3 KiB
C#
236 lines
6.3 KiB
C#
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 <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;
|
|
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 = 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<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 errors:\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 errors:\n{gccErrors}");
|
|
}
|
|
} |