Files
nub-lang/compiler/Program.cs
nub31 ab2f5750dc ...
2026-02-10 23:31:44 +01:00

218 lines
6.3 KiB
C#

using System.Diagnostics;
using System.IO.Compression;
using System.Text.Json;
using Compiler;
var nubFiles = new List<string>();
var libFiles = new List<string>();
var compileLib = false;
for (int i = 0; i < args.Length; i++)
{
string arg = args[i];
if (arg.StartsWith("--type="))
{
var value = arg.Split("--type=")[1];
switch (value)
{
case "lib":
compileLib = true;
break;
case "exe":
compileLib = false;
break;
default:
DiagnosticFormatter.Print(Diagnostic.Error("Type must be 'exe' or 'lib'").Build(), Console.Error);
return 1;
}
}
else if (arg.EndsWith(".nub"))
{
nubFiles.Add(arg);
}
else if (arg.EndsWith(".nublib"))
{
libFiles.Add(arg);
}
else if (arg == "--help")
{
Console.WriteLine("""
Usage: nubc [options] <files>
Options:
--type=exe Compile the input files into an executable (default)
--type=lib Compile the input files into a library
--help Show this help message
Files:
*.nub Nub source files to compile
*.nublib Precompiled Nub libraries to link
Example:
nubc --type=exe main.nub utils.nub math.nublib
""");
}
else
{
DiagnosticFormatter.Print(Diagnostic.Error($"Unrecognized option '{arg}'").Build(), Console.Error);
return 1;
}
}
var moduleGraphBuilder = ModuleGraph.CreateBuilder();
var asts = new List<Ast>();
var archivePaths = new List<string>();
foreach (var libPath in libFiles)
{
var lib = ReadNublib(libPath);
archivePaths.Add(lib.ArchivePath);
moduleGraphBuilder.AddManifest(lib.Manifest);
}
foreach (var fileName in nubFiles)
{
var file = File.ReadAllText(fileName);
var tokens = Tokenizer.Tokenize(fileName, file, out var tokenizerDiagnostics);
foreach (var diagnostic in tokenizerDiagnostics)
DiagnosticFormatter.Print(diagnostic, Console.Error);
if (tokens == null)
return 1;
var ast = Parser.Parse(fileName, tokens, out var parserDiagnostics);
foreach (var diagnostic in parserDiagnostics)
DiagnosticFormatter.Print(diagnostic, Console.Error);
if (ast == null)
return 1;
moduleGraphBuilder.AddAst(ast);
asts.Add(ast);
}
var moduleGraph = moduleGraphBuilder.Build(out var moduleGraphDiagnostics);
foreach (var diagnostic in moduleGraphDiagnostics)
DiagnosticFormatter.Print(diagnostic, Console.Error);
if (moduleGraph == null)
return 1;
var functions = new List<TypedNodeDefinitionFunc>();
foreach (var ast in asts)
{
foreach (var func in ast.Definitions.OfType<NodeDefinitionFunc>())
{
var typedFunction = TypeChecker.CheckFunction(ast.FileName, ast.ModuleName.Ident, func, moduleGraph, out var typeCheckerDiagnostics);
foreach (var diagnostic in typeCheckerDiagnostics)
DiagnosticFormatter.Print(diagnostic, Console.Error);
if (typedFunction == null)
return 1;
functions.Add(typedFunction);
}
}
var output = Generator.Emit(functions, moduleGraph, compileLib);
if (Directory.Exists(".build"))
{
CleanDirectory(".build");
}
else
{
Directory.CreateDirectory(".build");
}
if (compileLib)
{
File.WriteAllText(".build/out.c", output);
Process.Start("gcc", ["-Og", "-fvisibility=hidden", "-fno-builtin", "-c", "-o", ".build/out.o", ".build/out.c", .. archivePaths]).WaitForExit();
Process.Start("ar", ["rcs", ".build/out.a", ".build/out.o"]).WaitForExit();
WriteNublib(".build/out.nublib", ".build/out.a", moduleGraph.CreateManifest());
}
else
{
File.WriteAllText(".build/out.c", output);
Process.Start("gcc", ["-Og", "-fvisibility=hidden", "-fno-builtin", "-o", ".build/out", ".build/out.c", .. archivePaths]).WaitForExit();
}
return 0;
static void CleanDirectory(string dirName)
{
var dir = new DirectoryInfo(dirName);
foreach (var file in dir.GetFiles())
{
file.Delete();
}
foreach (var subdir in dir.GetDirectories())
{
CleanDirectory(subdir.FullName);
subdir.Delete();
}
}
static void WriteNublib(string outputPath, string archivePath, Manifest manifest)
{
using var fs = new FileStream(outputPath, FileMode.Create);
using var zip = new ZipArchive(fs, ZipArchiveMode.Create);
var serialized = JsonSerializer.Serialize(manifest, new JsonSerializerOptions
{
PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
WriteIndented = true,
});
var manifestEntry = zip.CreateEntry("manifest.json");
using (var writer = new StreamWriter(manifestEntry.Open()))
{
writer.Write(serialized);
}
var archiveEntry = zip.CreateEntry("lib.a");
using var entryStream = archiveEntry.Open();
using var fileStream = File.OpenRead(archivePath);
fileStream.CopyTo(entryStream);
}
static NublibLoadResult ReadNublib(string nublibPath)
{
using var fs = new FileStream(nublibPath, FileMode.Open, FileAccess.Read);
using var zip = new ZipArchive(fs, ZipArchiveMode.Read);
var manifestEntry = zip.GetEntry("manifest.json") ?? throw new FileNotFoundException("Manifest not found in nublib", "manifest.json");
Manifest manifest;
using (var reader = new StreamReader(manifestEntry.Open()))
{
var json = reader.ReadToEnd();
manifest = JsonSerializer.Deserialize<Manifest>(json) ?? throw new InvalidDataException("Failed to deserialize manifest.json");
}
var archiveEntry = zip.Entries.FirstOrDefault(e => e.Name.EndsWith(".a")) ?? throw new FileNotFoundException("Archive not found in nublib", "*.a");
string tempArchivePath = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString("N") + ".a");
using (var entryStream = archiveEntry.Open())
using (var tempFile = File.Create(tempArchivePath))
{
entryStream.CopyTo(tempFile);
}
return new NublibLoadResult(manifest, tempArchivePath);
}
public record NublibLoadResult(Manifest Manifest, string ArchivePath);