restructure
This commit is contained in:
24
src/Nub.Lang.CLI/Nub.Lang.CLI.csproj
Normal file
24
src/Nub.Lang.CLI/Nub.Lang.CLI.csproj
Normal file
@@ -0,0 +1,24 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<AssemblyName>nub</AssemblyName>
|
||||
<OutputType>Exe</OutputType>
|
||||
<TargetFramework>net9.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
<PublishAot>true</PublishAot>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\Nub.Lang\Nub.Lang.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<EmbeddedResource Include="Runtime\entry.s" />
|
||||
<EmbeddedResource Include="Runtime\nub_memcpy.s" />
|
||||
<EmbeddedResource Include="Runtime\nub_memset.s" />
|
||||
<EmbeddedResource Include="Runtime\nub_panic.s" />
|
||||
<EmbeddedResource Include="Runtime\nub_strcmp.s" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
240
src/Nub.Lang.CLI/Program.cs
Normal file
240
src/Nub.Lang.CLI/Program.cs
Normal file
@@ -0,0 +1,240 @@
|
||||
using System.Diagnostics;
|
||||
using System.Reflection;
|
||||
using Nub.Lang;
|
||||
using Nub.Lang.Frontend;
|
||||
using Nub.Lang.Frontend.Generation;
|
||||
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>");
|
||||
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 = Lexer.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);
|
||||
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 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);
|
||||
Console.Out.WriteLine($"Runtime assembled: {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);
|
||||
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 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}");
|
||||
}
|
||||
}
|
||||
12
src/Nub.Lang.CLI/Runtime/entry.s
Normal file
12
src/Nub.Lang.CLI/Runtime/entry.s
Normal file
@@ -0,0 +1,12 @@
|
||||
.intel_syntax noprefix
|
||||
.extern main
|
||||
.section .text
|
||||
|
||||
.global _start
|
||||
_start:
|
||||
mov rdi, rsp
|
||||
# func main(args: []^string): i64
|
||||
call main
|
||||
mov rdi, rax
|
||||
mov rax, 60
|
||||
syscall
|
||||
20
src/Nub.Lang.CLI/Runtime/nub_memcpy.s
Normal file
20
src/Nub.Lang.CLI/Runtime/nub_memcpy.s
Normal file
@@ -0,0 +1,20 @@
|
||||
.intel_syntax noprefix
|
||||
.section .text
|
||||
|
||||
# func nub_memcpy(destination: ^u8, source: ^u8, count: u64): ^u8
|
||||
.global nub_memcpy
|
||||
nub_memcpy:
|
||||
push rdi
|
||||
mov rcx, rdx
|
||||
test rcx, rcx
|
||||
jz memcpy_done
|
||||
memcpy_loop:
|
||||
mov al, BYTE PTR [rsi]
|
||||
mov BYTE PTR [rdi], al
|
||||
inc rsi
|
||||
inc rdi
|
||||
dec rcx
|
||||
jnz memcpy_loop
|
||||
memcpy_done:
|
||||
pop rax
|
||||
ret
|
||||
19
src/Nub.Lang.CLI/Runtime/nub_memset.s
Normal file
19
src/Nub.Lang.CLI/Runtime/nub_memset.s
Normal file
@@ -0,0 +1,19 @@
|
||||
.intel_syntax noprefix
|
||||
.section .text
|
||||
|
||||
# func nub_memset(destination: ^u8, value: i8, count: u64): ^u8
|
||||
.global nub_memset
|
||||
nub_memset:
|
||||
push rdi
|
||||
mov rcx, rdx
|
||||
mov al, sil
|
||||
test rcx, rcx
|
||||
jz memset_done
|
||||
memset_loop:
|
||||
mov BYTE PTR [rdi], al
|
||||
inc rdi
|
||||
dec rcx
|
||||
jnz memset_loop
|
||||
memset_done:
|
||||
pop rax
|
||||
ret
|
||||
14
src/Nub.Lang.CLI/Runtime/nub_panic.s
Normal file
14
src/Nub.Lang.CLI/Runtime/nub_panic.s
Normal file
@@ -0,0 +1,14 @@
|
||||
.intel_syntax noprefix
|
||||
.section .text
|
||||
|
||||
# func nub_panic(message: ^u8, message_length: u64): void
|
||||
.global nub_panic
|
||||
nub_panic:
|
||||
mov rdx, rsi
|
||||
mov rsi, rdi
|
||||
mov rax, 1
|
||||
mov rdi, 2
|
||||
syscall
|
||||
mov rax, 60
|
||||
mov rdi, 101
|
||||
syscall
|
||||
22
src/Nub.Lang.CLI/Runtime/nub_strcmp.s
Normal file
22
src/Nub.Lang.CLI/Runtime/nub_strcmp.s
Normal file
@@ -0,0 +1,22 @@
|
||||
.intel_syntax noprefix
|
||||
.section .text
|
||||
|
||||
# func nub_strcmp(lhs: ^u8, rhs: ^u8): bool
|
||||
.global nub_strcmp
|
||||
nub_strcmp:
|
||||
xor rdx, rdx
|
||||
strcmp_loop:
|
||||
mov al, BYTE PTR [rsi + rdx]
|
||||
mov bl, BYTE PTR [rdi + rdx]
|
||||
inc rdx
|
||||
cmp al, bl
|
||||
jne strcmp_not_equal
|
||||
cmp al, 0
|
||||
je strcmp_equal
|
||||
jmp strcmp_loop
|
||||
strcmp_not_equal:
|
||||
mov rax, 0
|
||||
ret
|
||||
strcmp_equal:
|
||||
mov rax, 1
|
||||
ret
|
||||
Reference in New Issue
Block a user