...
This commit is contained in:
29
src/compiler/NubLang.CLI/Archive.cs
Normal file
29
src/compiler/NubLang.CLI/Archive.cs
Normal file
@@ -0,0 +1,29 @@
|
||||
using System.Diagnostics;
|
||||
|
||||
namespace NubLang.CLI;
|
||||
|
||||
public static class Archive
|
||||
{
|
||||
public static async Task<bool> Invoke(string fileName, params IEnumerable<string> objectFiles)
|
||||
{
|
||||
using var process = new Process();
|
||||
process.StartInfo = new ProcessStartInfo("ar", ["rcs", fileName, ..objectFiles])
|
||||
{
|
||||
UseShellExecute = false,
|
||||
RedirectStandardOutput = true,
|
||||
RedirectStandardError = true,
|
||||
CreateNoWindow = true
|
||||
};
|
||||
|
||||
process.Start();
|
||||
await process.WaitForExitAsync();
|
||||
|
||||
var errors = await process.StandardError.ReadToEndAsync();
|
||||
if (!string.IsNullOrWhiteSpace(errors))
|
||||
{
|
||||
await Console.Error.WriteLineAsync("ar error when archiving:\n" + errors);
|
||||
}
|
||||
|
||||
return process.ExitCode == 0;
|
||||
}
|
||||
}
|
||||
71
src/compiler/NubLang.CLI/GCC.cs
Normal file
71
src/compiler/NubLang.CLI/GCC.cs
Normal file
@@ -0,0 +1,71 @@
|
||||
using System.Diagnostics;
|
||||
|
||||
namespace NubLang.CLI;
|
||||
|
||||
public static class GCC
|
||||
{
|
||||
public static async Task<bool> Assemble(string asm, string objPath)
|
||||
{
|
||||
var dir = Path.GetDirectoryName(objPath);
|
||||
if (dir != null)
|
||||
{
|
||||
Directory.CreateDirectory(dir);
|
||||
}
|
||||
|
||||
using var process = new Process();
|
||||
process.StartInfo = new ProcessStartInfo("gcc", ["-xassembler", "-c", "-o", objPath, "-"])
|
||||
{
|
||||
UseShellExecute = false,
|
||||
RedirectStandardInput = true,
|
||||
RedirectStandardOutput = true,
|
||||
RedirectStandardError = true,
|
||||
CreateNoWindow = true
|
||||
};
|
||||
|
||||
process.Start();
|
||||
|
||||
await process.StandardInput.WriteAsync(asm);
|
||||
process.StandardInput.Close();
|
||||
|
||||
await process.WaitForExitAsync();
|
||||
|
||||
var errors = await process.StandardError.ReadToEndAsync();
|
||||
if (!string.IsNullOrWhiteSpace(errors))
|
||||
{
|
||||
await Console.Error.WriteLineAsync("gcc error when assembling:\n" + errors);
|
||||
}
|
||||
|
||||
return process.ExitCode == 0;
|
||||
}
|
||||
|
||||
public static async Task<bool> Link(string executablePath, params IEnumerable<string> objectFiles)
|
||||
{
|
||||
var dir = Path.GetDirectoryName(executablePath);
|
||||
if (dir != null)
|
||||
{
|
||||
Directory.CreateDirectory(dir);
|
||||
}
|
||||
|
||||
using var process = new Process();
|
||||
process.StartInfo = new ProcessStartInfo("gcc", ["-nostartfiles", "-o", executablePath, ..objectFiles])
|
||||
{
|
||||
UseShellExecute = false,
|
||||
RedirectStandardInput = true,
|
||||
RedirectStandardOutput = true,
|
||||
RedirectStandardError = true,
|
||||
CreateNoWindow = true
|
||||
};
|
||||
|
||||
process.Start();
|
||||
|
||||
await process.WaitForExitAsync();
|
||||
|
||||
var errors = await process.StandardError.ReadToEndAsync();
|
||||
if (!string.IsNullOrWhiteSpace(errors))
|
||||
{
|
||||
await Console.Error.WriteLineAsync("gcc error when linking:\n" + errors);
|
||||
}
|
||||
|
||||
return process.ExitCode == 0;
|
||||
}
|
||||
}
|
||||
38
src/compiler/NubLang.CLI/HexString.cs
Normal file
38
src/compiler/NubLang.CLI/HexString.cs
Normal file
@@ -0,0 +1,38 @@
|
||||
namespace NubLang.CLI;
|
||||
|
||||
internal static class HexString
|
||||
{
|
||||
private static readonly HashSet<string> _used = [];
|
||||
|
||||
private static readonly char[] StringChars = "0123456789abcdef".ToArray();
|
||||
|
||||
public static string CreateUnique(int length)
|
||||
{
|
||||
string hex;
|
||||
|
||||
while (true)
|
||||
{
|
||||
hex = Create(length);
|
||||
if (_used.Add(hex))
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return hex;
|
||||
}
|
||||
|
||||
public static string Create(int length)
|
||||
{
|
||||
var rand = new Random();
|
||||
var hexString = "";
|
||||
|
||||
for (var i = 0; i < length; i++)
|
||||
{
|
||||
var randIndex = rand.Next(0, StringChars.Length);
|
||||
hexString += StringChars[randIndex];
|
||||
}
|
||||
|
||||
return hexString;
|
||||
}
|
||||
}
|
||||
25
src/compiler/NubLang.CLI/NubLang.CLI.csproj
Normal file
25
src/compiler/NubLang.CLI/NubLang.CLI.csproj
Normal file
@@ -0,0 +1,25 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<AssemblyName>nubc</AssemblyName>
|
||||
<OutputType>Exe</OutputType>
|
||||
<TargetFramework>net9.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
<PublishAot>true</PublishAot>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\NubLang\NubLang.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<None Remove="runtime\libruntime_x64.a" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<None Remove="assets\libruntime_x64.a" />
|
||||
<EmbeddedResource Include="assets\libruntime_x64.a" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
9
src/compiler/NubLang.CLI/Options.cs
Normal file
9
src/compiler/NubLang.CLI/Options.cs
Normal file
@@ -0,0 +1,9 @@
|
||||
namespace NubLang.CLI;
|
||||
|
||||
public class Options
|
||||
{
|
||||
public string? CustomRuntime { get; set; }
|
||||
public string? OutputPath { get; set; }
|
||||
public bool Link { get; set; } = true;
|
||||
public List<string> Files { get; } = [];
|
||||
}
|
||||
215
src/compiler/NubLang.CLI/Program.cs
Normal file
215
src/compiler/NubLang.CLI/Program.cs
Normal file
@@ -0,0 +1,215 @@
|
||||
using System.Reflection;
|
||||
using NubLang.CLI;
|
||||
using NubLang;
|
||||
using NubLang.Diagnostics;
|
||||
using NubLang.Generation.QBE;
|
||||
using NubLang.Syntax.Binding.Node;
|
||||
using NubLang.Syntax.Parsing;
|
||||
using NubLang.Syntax.Parsing.Node;
|
||||
using NubLang.Syntax.Tokenization;
|
||||
using Binder = NubLang.Syntax.Binding.Binder;
|
||||
|
||||
const string BIN_DIR = "bin";
|
||||
const string INT_DIR = "bin-int";
|
||||
var INT_BUILTIN_DIR = Path.Join(INT_DIR, "builtin");
|
||||
var INT_OBJECT_DIR = Path.Join(INT_DIR, "obj");
|
||||
var INT_DEBUG_DIR = Path.Join(INT_DIR, "debug");
|
||||
|
||||
if (Directory.Exists(INT_DIR))
|
||||
{
|
||||
Directory.Delete(INT_DIR, true);
|
||||
}
|
||||
|
||||
Directory.CreateDirectory(BIN_DIR);
|
||||
Directory.CreateDirectory(INT_BUILTIN_DIR);
|
||||
Directory.CreateDirectory(INT_OBJECT_DIR);
|
||||
Directory.CreateDirectory(INT_DEBUG_DIR);
|
||||
|
||||
var options = new Options();
|
||||
|
||||
for (var i = 0; i < args.Length; i++)
|
||||
{
|
||||
var arg = args[i];
|
||||
if (arg == "-r")
|
||||
{
|
||||
i++;
|
||||
if (i >= args.Length - 1)
|
||||
{
|
||||
Console.Error.WriteLine($"{arg} must be followed by a value");
|
||||
return 1;
|
||||
}
|
||||
|
||||
options.CustomRuntime = args[i];
|
||||
}
|
||||
else if (arg == "-o")
|
||||
{
|
||||
i++;
|
||||
if (i >= args.Length - 1)
|
||||
{
|
||||
Console.Error.WriteLine($"{arg} must be followed by a value");
|
||||
return 1;
|
||||
}
|
||||
|
||||
options.OutputPath = args[i];
|
||||
}
|
||||
else if (arg == "-c")
|
||||
{
|
||||
i++;
|
||||
options.Link = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
options.Files.Add(arg);
|
||||
}
|
||||
}
|
||||
|
||||
var diagnostics = new List<Diagnostic>();
|
||||
var syntaxTrees = new Dictionary<string, SyntaxTree>();
|
||||
|
||||
foreach (var file in options.Files)
|
||||
{
|
||||
if (!File.Exists(file))
|
||||
{
|
||||
Console.Error.WriteLine($"File '{file}' does not exist");
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var file in options.Files)
|
||||
{
|
||||
var content = File.ReadAllText(file);
|
||||
var sourceText = new SourceText(file, content);
|
||||
|
||||
var tokenizer = new Tokenizer(sourceText);
|
||||
var tokenizeResult = tokenizer.Tokenize(out var tokenizerDiagnostics);
|
||||
diagnostics.AddRange(tokenizerDiagnostics);
|
||||
|
||||
var parser = new Parser(tokenizeResult);
|
||||
var syntaxTree = parser.Parse();
|
||||
diagnostics.AddRange(syntaxTree.Diagnostics);
|
||||
|
||||
syntaxTrees[file] = syntaxTree;
|
||||
}
|
||||
|
||||
var definitionTable = new DefinitionTable(syntaxTrees.Values);
|
||||
|
||||
var boundSyntaxTrees = new Dictionary<string, BoundSyntaxTree>();
|
||||
|
||||
foreach (var (file, syntaxTree) in syntaxTrees)
|
||||
{
|
||||
var binder = new Binder(syntaxTree, definitionTable);
|
||||
var boundSyntaxTree = binder.Bind();
|
||||
diagnostics.AddRange(boundSyntaxTree.Diagnostics);
|
||||
boundSyntaxTrees[file] = boundSyntaxTree;
|
||||
}
|
||||
|
||||
foreach (var diagnostic in diagnostics)
|
||||
{
|
||||
Console.Error.WriteLine(diagnostic.FormatANSI());
|
||||
}
|
||||
|
||||
if (diagnostics.Any(diagnostic => diagnostic.Severity == DiagnosticSeverity.Error))
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
|
||||
var boundDefinitionTable = new BoundDefinitionTable(boundSyntaxTrees.Values);
|
||||
|
||||
var objectFiles = new List<string>();
|
||||
|
||||
foreach (var file in options.Files)
|
||||
{
|
||||
var outFileName = $"{HexString.CreateUnique(8)}_{Path.GetFileNameWithoutExtension(file)}";
|
||||
|
||||
var generator = new QBEGenerator(boundSyntaxTrees[file], boundDefinitionTable, file);
|
||||
var ssa = generator.Emit();
|
||||
File.WriteAllText(Path.Join(INT_DEBUG_DIR, $"{outFileName}.ssa"), ssa);
|
||||
|
||||
var asm = await QBE.Invoke(ssa);
|
||||
if (asm == null)
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
|
||||
File.WriteAllText(Path.Join(INT_DEBUG_DIR, $"{outFileName}.s"), asm);
|
||||
|
||||
var fileName = Path.Join(INT_OBJECT_DIR, $"{outFileName}.o");
|
||||
var asmSuccess = await GCC.Assemble(asm, fileName);
|
||||
if (!asmSuccess)
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
|
||||
objectFiles.Add(fileName);
|
||||
}
|
||||
|
||||
if (options.CustomRuntime == null)
|
||||
{
|
||||
try
|
||||
{
|
||||
objectFiles.Add(await CreateBuiltinRuntime());
|
||||
}
|
||||
catch (RuntimeCreationException e)
|
||||
{
|
||||
Console.Error.WriteLine(e.Message);
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!File.Exists(options.CustomRuntime))
|
||||
{
|
||||
Console.Error.WriteLine($"file '{options.CustomRuntime}' does not exist'");
|
||||
return 1;
|
||||
}
|
||||
|
||||
objectFiles.Add(options.CustomRuntime);
|
||||
}
|
||||
|
||||
if (options.Link)
|
||||
{
|
||||
var outPath = options.OutputPath ?? Path.Join(BIN_DIR, "out");
|
||||
var linkResult = await GCC.Link(outPath, objectFiles);
|
||||
if (!linkResult)
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
var outPath = options.OutputPath ?? Path.Join(BIN_DIR, "out.a");
|
||||
var archiveResult = await Archive.Invoke(outPath, objectFiles);
|
||||
if (!archiveResult)
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
||||
async Task<string> CreateBuiltinRuntime()
|
||||
{
|
||||
const string RUNTIME_NAME = "libruntime_x64.a";
|
||||
|
||||
var resources = Assembly.GetExecutingAssembly().GetManifestResourceNames();
|
||||
var runtime = resources.First(r => r.EndsWith(RUNTIME_NAME));
|
||||
|
||||
await using var reader = Assembly.GetExecutingAssembly().GetManifestResourceStream(runtime);
|
||||
|
||||
if (reader == null)
|
||||
{
|
||||
throw new RuntimeCreationException($"Cannot open stream to '{RUNTIME_NAME}'");
|
||||
}
|
||||
|
||||
var runtimePath = Path.Combine(INT_BUILTIN_DIR, RUNTIME_NAME);
|
||||
await using var writer = new FileStream(runtimePath, FileMode.Create);
|
||||
|
||||
reader.CopyTo(writer);
|
||||
|
||||
return runtimePath;
|
||||
}
|
||||
|
||||
namespace NubLang.CLI
|
||||
{
|
||||
internal class RuntimeCreationException(string message) : Exception(message);
|
||||
}
|
||||
36
src/compiler/NubLang.CLI/QBE.cs
Normal file
36
src/compiler/NubLang.CLI/QBE.cs
Normal file
@@ -0,0 +1,36 @@
|
||||
using System.Diagnostics;
|
||||
|
||||
namespace NubLang.CLI;
|
||||
|
||||
public static class QBE
|
||||
{
|
||||
public static async Task<string?> Invoke(string ssa)
|
||||
{
|
||||
using var process = new Process();
|
||||
process.StartInfo = new ProcessStartInfo("qbe")
|
||||
{
|
||||
UseShellExecute = false,
|
||||
RedirectStandardInput = true,
|
||||
RedirectStandardOutput = true,
|
||||
RedirectStandardError = true,
|
||||
CreateNoWindow = true
|
||||
};
|
||||
|
||||
process.Start();
|
||||
|
||||
await process.StandardInput.WriteAsync(ssa);
|
||||
process.StandardInput.Close();
|
||||
|
||||
await process.WaitForExitAsync();
|
||||
|
||||
var errors = await process.StandardError.ReadToEndAsync();
|
||||
if (!string.IsNullOrWhiteSpace(errors))
|
||||
{
|
||||
await Console.Error.WriteLineAsync("qbe error:\n" + errors);
|
||||
}
|
||||
|
||||
var asm = await process.StandardOutput.ReadToEndAsync();
|
||||
|
||||
return process.ExitCode == 0 ? asm : null;
|
||||
}
|
||||
}
|
||||
BIN
src/compiler/NubLang.CLI/assets/libruntime_x64.a
Normal file
BIN
src/compiler/NubLang.CLI/assets/libruntime_x64.a
Normal file
Binary file not shown.
Reference in New Issue
Block a user