diff --git a/src/compilation/.gitignore b/src/compilation/.gitignore
new file mode 100644
index 0000000..c6cc67a
--- /dev/null
+++ b/src/compilation/.gitignore
@@ -0,0 +1,34 @@
+# Common IntelliJ Platform excludes
+
+# User specific
+**/.idea/**/workspace.xml
+**/.idea/**/tasks.xml
+**/.idea/shelf/*
+**/.idea/dictionaries
+**/.idea/httpRequests/
+
+# Sensitive or high-churn files
+**/.idea/**/dataSources/
+**/.idea/**/dataSources.ids
+**/.idea/**/dataSources.xml
+**/.idea/**/dataSources.local.xml
+**/.idea/**/sqlDataSources.xml
+**/.idea/**/dynamic.xml
+
+# Rider
+# Rider auto-generates .iml files, and contentModel.xml
+**/.idea/**/*.iml
+**/.idea/**/contentModel.xml
+**/.idea/**/modules.xml
+
+*.suo
+*.user
+.vs/
+[Bb]in/
+[Oo]bj/
+_UpgradeReport_Files/
+[Pp]ackages/
+
+Thumbs.db
+Desktop.ini
+.DS_Store
\ No newline at end of file
diff --git a/src/compilation/.idea/.idea.Compiler/.idea/.gitignore b/src/compilation/.idea/.idea.Compiler/.idea/.gitignore
new file mode 100644
index 0000000..cda1cf4
--- /dev/null
+++ b/src/compilation/.idea/.idea.Compiler/.idea/.gitignore
@@ -0,0 +1,13 @@
+# Default ignored files
+/shelf/
+/workspace.xml
+# Rider ignored files
+/modules.xml
+/projectSettingsUpdater.xml
+/contentModel.xml
+/.idea.Compiler.iml
+# Editor-based HTTP Client requests
+/httpRequests/
+# Datasource local storage ignored files
+/dataSources/
+/dataSources.local.xml
diff --git a/src/compilation/.idea/.idea.Compiler/.idea/.name b/src/compilation/.idea/.idea.Compiler/.idea/.name
new file mode 100644
index 0000000..b92b7a3
--- /dev/null
+++ b/src/compilation/.idea/.idea.Compiler/.idea/.name
@@ -0,0 +1 @@
+Compiler
\ No newline at end of file
diff --git a/src/compilation/.idea/.idea.Compiler/.idea/encodings.xml b/src/compilation/.idea/.idea.Compiler/.idea/encodings.xml
new file mode 100644
index 0000000..df87cf9
--- /dev/null
+++ b/src/compilation/.idea/.idea.Compiler/.idea/encodings.xml
@@ -0,0 +1,4 @@
+
+
+
+
\ No newline at end of file
diff --git a/src/compilation/.idea/.idea.Compiler/.idea/indexLayout.xml b/src/compilation/.idea/.idea.Compiler/.idea/indexLayout.xml
new file mode 100644
index 0000000..2135b43
--- /dev/null
+++ b/src/compilation/.idea/.idea.Compiler/.idea/indexLayout.xml
@@ -0,0 +1,10 @@
+
+
+
+
+ Runtime
+
+
+
+
+
\ No newline at end of file
diff --git a/src/compilation/.idea/.idea.Compiler/.idea/vcs.xml b/src/compilation/.idea/.idea.Compiler/.idea/vcs.xml
new file mode 100644
index 0000000..6c0b863
--- /dev/null
+++ b/src/compilation/.idea/.idea.Compiler/.idea/vcs.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/compilation/CLI/CLI.csproj b/src/compilation/CLI/CLI.csproj
new file mode 100644
index 0000000..c8dff8b
--- /dev/null
+++ b/src/compilation/CLI/CLI.csproj
@@ -0,0 +1,25 @@
+
+
+
+ nub
+ Exe
+ net9.0
+ enable
+ enable
+ true
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/compilation/CLI/GCC.cs b/src/compilation/CLI/GCC.cs
new file mode 100644
index 0000000..445814c
--- /dev/null
+++ b/src/compilation/CLI/GCC.cs
@@ -0,0 +1,52 @@
+using System.Diagnostics;
+
+namespace CLI;
+
+public static class GCC
+{
+ public static async Task Assemble(string asmPath, string objPath)
+ {
+ using var gccProcess = new Process();
+ gccProcess.StartInfo = new ProcessStartInfo("gcc", ["-g", "-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))
+ {
+ await Console.Error.WriteLineAsync("gcc error when assembling:\n" + gccErrors);
+ }
+
+ return gccProcess.ExitCode == 0;
+ }
+
+ public static async Task Link(List objectFiles, string outputPath)
+ {
+ using var gccProcess = new Process();
+ gccProcess.StartInfo = new ProcessStartInfo("gcc", ["-g", "-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))
+ {
+ await Console.Error.WriteLineAsync("gcc error when linking:\n" + gccErrors);
+ }
+
+ return gccProcess.ExitCode == 0;
+ }
+}
diff --git a/src/compilation/CLI/Program.cs b/src/compilation/CLI/Program.cs
new file mode 100644
index 0000000..f62895f
--- /dev/null
+++ b/src/compilation/CLI/Program.cs
@@ -0,0 +1,152 @@
+using System.Diagnostics;
+using System.Reflection;
+using CLI;
+using Generation.QBE;
+using Syntax;
+using Syntax.Diagnostics;
+using Syntax.Parsing;
+using Syntax.Tokenization;
+using Syntax.Typing;
+using Binder = Syntax.Typing.Binder;
+
+const string BIN_DIR = "bin";
+const string BIN_INT_DIR = "bin-int";
+
+if (Directory.Exists(BIN_DIR))
+{
+ Directory.Delete(BIN_DIR, true);
+}
+
+if (Directory.Exists(BIN_INT_DIR))
+{
+ Directory.Delete(BIN_INT_DIR, true);
+}
+
+Directory.CreateDirectory(BIN_DIR);
+Directory.CreateDirectory(BIN_INT_DIR);
+
+var diagnostics = new List();
+var syntaxTrees = new List();
+
+foreach (var file in args)
+{
+ if (!File.Exists(file))
+ {
+ Console.Error.WriteLine($"File '{file}' does not exist");
+ return 1;
+ }
+}
+
+foreach (var file in args)
+{
+ var content = File.ReadAllText(file);
+ var sourceText = new SourceText(file, content);
+
+ var tokenizeResult = Tokenizer.Tokenize(sourceText, out var tokenizerDiagnostics);
+ diagnostics.AddRange(tokenizerDiagnostics);
+
+ var syntaxTree = Parser.ParseFile(tokenizeResult, file, out var parseDiagnostics);
+ diagnostics.AddRange(parseDiagnostics);
+
+ if (syntaxTree != null)
+ {
+ syntaxTrees.Add(syntaxTree);
+ }
+ else
+ {
+ throw new Exception();
+ }
+}
+
+var definitionTable = new DefinitionTable(syntaxTrees);
+
+var boundSyntaxTrees = new List();
+
+foreach (var syntaxTree in syntaxTrees)
+{
+ boundSyntaxTrees.Add(Binder.Bind(syntaxTree, definitionTable));
+}
+
+var boundDefinitionTable = new BoundDefinitionTable(boundSyntaxTrees);
+
+foreach (var diagnostic in diagnostics)
+{
+ Console.Error.WriteLine(diagnostic.FormatANSI());
+}
+
+if (diagnostics.Any(diagnostic => diagnostic.Severity == DiagnosticSeverity.Error))
+{
+ return 1;
+}
+
+var objectFiles = new List();
+
+foreach (var boundSyntaxTree in boundSyntaxTrees)
+{
+ var relativeFilePath = Path.GetRelativePath(Environment.CurrentDirectory, boundSyntaxTree.FilePath);
+ var outputPath = Path.Combine(BIN_INT_DIR, "program", relativeFilePath);
+
+ var outputDirectory = Path.GetDirectoryName(outputPath);
+ Debug.Assert(!string.IsNullOrWhiteSpace(outputDirectory));
+ Directory.CreateDirectory(outputDirectory);
+
+ var ssa = QBEGenerator.Emit(boundSyntaxTree, boundDefinitionTable);
+ var ssaPath = Path.ChangeExtension(outputPath, "ssa");
+ File.WriteAllText(ssaPath, ssa);
+
+ var asm = await QBE.Invoke(ssa);
+ if (asm == null)
+ {
+ return 1;
+ }
+
+ var asmPath = Path.ChangeExtension(outputPath, "s");
+ await File.WriteAllTextAsync(asmPath, asm);
+
+ var objPath = Path.ChangeExtension(outputPath, "o");
+ var asmSuccess = await GCC.Assemble(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($"Could not load embedded resource {resourceName}");
+ return 1;
+ }
+
+ using var reader = new StreamReader(stream);
+ var asm = await reader.ReadToEndAsync();
+
+ var outputDirectory = Path.Combine(BIN_INT_DIR, "runtime");
+ Directory.CreateDirectory(outputDirectory);
+
+ var fileName = resourceName.Split('.').Reverse().Skip(1).First();
+
+ var asmPath = Path.Combine(outputDirectory, fileName + ".s");
+ await File.WriteAllTextAsync(asmPath, asm);
+
+ var objPath = Path.Combine(outputDirectory, fileName + ".o");
+ var asmSuccess = await GCC.Assemble(asmPath, objPath);
+ if (!asmSuccess)
+ {
+ return 1;
+ }
+
+ objectFiles.Add(objPath);
+}
+
+var linkSuccess = await GCC.Link(objectFiles, Path.Combine(BIN_DIR, "out"));
+return linkSuccess ? 0 : 1;
\ No newline at end of file
diff --git a/src/compilation/CLI/QBE.cs b/src/compilation/CLI/QBE.cs
new file mode 100644
index 0000000..c592e12
--- /dev/null
+++ b/src/compilation/CLI/QBE.cs
@@ -0,0 +1,37 @@
+using System.Diagnostics;
+
+namespace CLI;
+
+public static class QBE
+{
+ public static async Task Invoke(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();
+
+ await qbeProcess.WaitForExitAsync();
+
+ var qbeErrors = await qbeProcess.StandardError.ReadToEndAsync();
+ if (!string.IsNullOrWhiteSpace(qbeErrors))
+ {
+ await Console.Error.WriteLineAsync("qbe error:\n" + qbeErrors);
+ }
+
+ var asm = await qbeProcess.StandardOutput.ReadToEndAsync();
+
+ return qbeProcess.ExitCode == 0 ? asm : null;
+ }
+}
diff --git a/src/compilation/CLI/Runtime/entry.s b/src/compilation/CLI/Runtime/entry.s
new file mode 100644
index 0000000..5ea2a6a
--- /dev/null
+++ b/src/compilation/CLI/Runtime/entry.s
@@ -0,0 +1,12 @@
+.intel_syntax noprefix
+
+.equ SYS_EXIT, 60
+
+.text
+.globl _start
+_start:
+ mov rdi, rsp
+ call main
+ mov rdi, rax
+ mov rax, SYS_EXIT
+ syscall
diff --git a/src/compilation/CLI/Runtime/nub_cstring.s b/src/compilation/CLI/Runtime/nub_cstring.s
new file mode 100644
index 0000000..8acef4f
--- /dev/null
+++ b/src/compilation/CLI/Runtime/nub_cstring.s
@@ -0,0 +1,17 @@
+.intel_syntax noprefix
+
+.text
+.globl nub_cstring_length
+# func nub_cstring_length(string: cstring): u64
+nub_cstring_length:
+ xor rax, rax
+
+count_loop:
+ cmp byte ptr [rdi + rax], 0
+ je done
+
+ inc rax
+ jmp count_loop
+
+done:
+ ret
diff --git a/src/compilation/CLI/Runtime/nub_mem.s b/src/compilation/CLI/Runtime/nub_mem.s
new file mode 100644
index 0000000..27cf71c
--- /dev/null
+++ b/src/compilation/CLI/Runtime/nub_mem.s
@@ -0,0 +1,27 @@
+.intel_syntax noprefix
+
+.text
+.globl nub_memcpy
+# func nub_memcpy(destination: ^u8, source: ^u8, count: u64): ^u8
+nub_memcpy:
+ mov rcx, rdx
+ rep movsb
+ ret
+
+.text
+.globl nub_memset
+# func nub_memset(destination: ^u8, value: i8, count: u64): ^u8
+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
diff --git a/src/compilation/CLI/Runtime/nub_panic.s b/src/compilation/CLI/Runtime/nub_panic.s
new file mode 100644
index 0000000..51332e2
--- /dev/null
+++ b/src/compilation/CLI/Runtime/nub_panic.s
@@ -0,0 +1,47 @@
+.intel_syntax noprefix
+
+.equ NUB_PANIC_ERROR_CODE, 101
+
+.equ SYS_WRITE, 1
+.equ SYS_EXIT, 60
+
+.equ FD_STDIN, 0
+.equ FD_STDOUT, 1
+.equ FD_STDERR, 2
+
+.data
+.align 8
+array_oob_msg:
+ .ascii "Index is out of bounds of array\n"
+
+.data
+.align 8
+oom_msg:
+ .ascii "Out of memory\n"
+
+.text
+.globl nub_panic
+nub_panic:
+ mov rax, SYS_EXIT
+ mov rdi, NUB_PANIC_ERROR_CODE
+ syscall
+
+.text
+.globl nub_panic_array_oob
+nub_panic_array_oob:
+ mov rax, SYS_WRITE
+ mov rdi, FD_STDERR
+ lea rsi, [rip + array_oob_msg]
+ mov rdx, 32
+ syscall
+ call nub_panic
+
+.text
+.globl nub_panic_oom
+nub_panic_oom:
+ mov rax, SYS_WRITE
+ mov rdi, FD_STDERR
+ lea rsi, [rip + oom_msg]
+ mov rdx, 14
+ syscall
+ call nub_panic
diff --git a/src/compilation/CLI/Runtime/nub_string.s b/src/compilation/CLI/Runtime/nub_string.s
new file mode 100644
index 0000000..dccbd5d
--- /dev/null
+++ b/src/compilation/CLI/Runtime/nub_string.s
@@ -0,0 +1,32 @@
+.intel_syntax noprefix
+
+.text
+.globl nub_string_length
+# func nub_string_length(string: string): u64
+nub_string_length:
+ mov rsi, [rdi] # Length of string in bytes
+
+ add rdi, 8 # Start of bytes
+ xor rax, rax # Character count
+ xor rdx, rdx # Current byte position
+
+ test rsi, rsi
+ jz _done
+
+_count_loop:
+ cmp rdx, rsi
+ jge _done
+
+ mov dl, [rdi + rdx]
+ and dl, 0b11000000
+ cmp dl, 0b10000000
+ je _skip_byte
+
+ inc rax
+
+_skip_byte:
+ inc rdx
+ jmp _count_loop
+
+_done:
+ ret
diff --git a/src/compilation/Common/Common.csproj b/src/compilation/Common/Common.csproj
new file mode 100644
index 0000000..b682a68
--- /dev/null
+++ b/src/compilation/Common/Common.csproj
@@ -0,0 +1,10 @@
+
+
+
+ net9.0
+ enable
+ enable
+ true
+
+
+
diff --git a/src/compilation/Common/Optional.cs b/src/compilation/Common/Optional.cs
new file mode 100644
index 0000000..5cd9f29
--- /dev/null
+++ b/src/compilation/Common/Optional.cs
@@ -0,0 +1,77 @@
+using System.Diagnostics.CodeAnalysis;
+
+namespace Common;
+
+public readonly struct Optional
+{
+ public static Optional Empty() => new();
+
+ ///
+ /// Alias for creating an Optional<TValue> which allows for implicit types
+ ///
+ ///
+ ///
+ ///
+ public static Optional OfNullable(TValue? value)
+ {
+ return value ?? Optional.Empty();
+ }
+}
+
+public readonly struct Optional
+{
+ public static Optional Empty() => new();
+
+ public static Optional OfNullable(TValue? value)
+ {
+ return value ?? Empty();
+ }
+
+ public Optional()
+ {
+ Value = default;
+ HasValue = false;
+ }
+
+ public Optional(TValue value)
+ {
+ Value = value;
+ HasValue = true;
+ }
+
+ public TValue? Value { get; }
+
+ [MemberNotNullWhen(true, nameof(Value))]
+ public bool HasValue { get; }
+
+
+ [MemberNotNullWhen(true, nameof(Value))]
+ public bool TryGetValue([NotNullWhen(true)] out TValue? value)
+ {
+ if (HasValue)
+ {
+ value = Value;
+ return true;
+ }
+
+ value = default;
+ return false;
+ }
+
+ public TValue GetValue()
+ {
+ return Value ?? throw new InvalidOperationException("Value is not set");
+ }
+
+ public static implicit operator Optional(TValue value) => new(value);
+
+ public TValue Or(TValue other)
+ {
+ if (HasValue)
+ {
+ return Value;
+ }
+
+ return other;
+ }
+}
\ No newline at end of file
diff --git a/src/compilation/Common/Variant.cs b/src/compilation/Common/Variant.cs
new file mode 100644
index 0000000..541b111
--- /dev/null
+++ b/src/compilation/Common/Variant.cs
@@ -0,0 +1,49 @@
+namespace Common;
+
+public readonly struct Variant where T1 : notnull where T2 : notnull
+{
+ public Variant()
+ {
+ throw new InvalidOperationException("Variant must be initialized with a value");
+ }
+
+ public Variant(T1 value)
+ {
+ _value = value;
+ }
+
+ public Variant(T2 value)
+ {
+ _value = value;
+ }
+
+ private readonly object _value;
+
+ public void Match(Action on1, Action on2)
+ {
+ switch (_value)
+ {
+ case T1 v1:
+ on1(v1);
+ break;
+ case T2 v2:
+ on2(v2);
+ break;
+ default:
+ throw new InvalidCastException();
+ }
+ }
+
+ public T Match(Func on1, Func on2)
+ {
+ return _value switch
+ {
+ T1 v1 => on1(v1),
+ T2 v2 => on2(v2),
+ _ => throw new InvalidCastException()
+ };
+ }
+
+ public static implicit operator Variant(T1 value) => new(value);
+ public static implicit operator Variant(T2 value) => new(value);
+}
\ No newline at end of file
diff --git a/src/compilation/Compiler.sln b/src/compilation/Compiler.sln
new file mode 100644
index 0000000..702f726
--- /dev/null
+++ b/src/compilation/Compiler.sln
@@ -0,0 +1,34 @@
+
+Microsoft Visual Studio Solution File, Format Version 12.00
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Syntax", "Syntax\Syntax.csproj", "{5047E21F-590D-4CB3-AFF3-064316485009}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CLI", "CLI\CLI.csproj", "{A22F17ED-FA17-45AB-92BA-CD02C28B3524}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Generation", "Generation\Generation.csproj", "{F903F1B9-69A6-4522-B483-81A4B072C8B1}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Common", "Common\Common.csproj", "{91ECE034-32D4-48E6-A905-5F95DB95A3D4}"
+EndProject
+Global
+ GlobalSection(SolutionConfigurationPlatforms) = preSolution
+ Debug|Any CPU = Debug|Any CPU
+ Release|Any CPU = Release|Any CPU
+ EndGlobalSection
+ GlobalSection(ProjectConfigurationPlatforms) = postSolution
+ {5047E21F-590D-4CB3-AFF3-064316485009}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {5047E21F-590D-4CB3-AFF3-064316485009}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {5047E21F-590D-4CB3-AFF3-064316485009}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {5047E21F-590D-4CB3-AFF3-064316485009}.Release|Any CPU.Build.0 = Release|Any CPU
+ {A22F17ED-FA17-45AB-92BA-CD02C28B3524}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {A22F17ED-FA17-45AB-92BA-CD02C28B3524}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {A22F17ED-FA17-45AB-92BA-CD02C28B3524}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {A22F17ED-FA17-45AB-92BA-CD02C28B3524}.Release|Any CPU.Build.0 = Release|Any CPU
+ {F903F1B9-69A6-4522-B483-81A4B072C8B1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {F903F1B9-69A6-4522-B483-81A4B072C8B1}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {F903F1B9-69A6-4522-B483-81A4B072C8B1}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {F903F1B9-69A6-4522-B483-81A4B072C8B1}.Release|Any CPU.Build.0 = Release|Any CPU
+ {91ECE034-32D4-48E6-A905-5F95DB95A3D4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {91ECE034-32D4-48E6-A905-5F95DB95A3D4}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {91ECE034-32D4-48E6-A905-5F95DB95A3D4}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {91ECE034-32D4-48E6-A905-5F95DB95A3D4}.Release|Any CPU.Build.0 = Release|Any CPU
+ EndGlobalSection
+EndGlobal
diff --git a/src/compilation/Generation/Generation.csproj b/src/compilation/Generation/Generation.csproj
new file mode 100644
index 0000000..fbe4e73
--- /dev/null
+++ b/src/compilation/Generation/Generation.csproj
@@ -0,0 +1,15 @@
+
+
+
+ net9.0
+ enable
+ enable
+ true
+
+
+
+
+
+
+
+
diff --git a/src/compilation/Generation/QBE/QBEGenerator.cs b/src/compilation/Generation/QBE/QBEGenerator.cs
new file mode 100644
index 0000000..d8850c6
--- /dev/null
+++ b/src/compilation/Generation/QBE/QBEGenerator.cs
@@ -0,0 +1,1433 @@
+using System.Diagnostics;
+using System.Globalization;
+using System.Text;
+using Syntax;
+using Syntax.Parsing.Node;
+using Syntax.Tokenization;
+using Syntax.Typing;
+using Syntax.Typing.BoundNode;
+
+namespace Generation.QBE;
+
+public static class QBEGenerator
+{
+ private static BoundSyntaxTree _syntaxTree = null!;
+ private static BoundDefinitionTable _definitionTable = null!;
+
+ private static StringBuilder _builder = new();
+ private static List _cStringLiterals = [];
+ private static List _stringLiterals = [];
+ private static Stack _breakLabels = [];
+ private static Stack _continueLabels = [];
+ private static Queue<(BoundAnonymousFuncNode Func, string Name)> _anonymousFunctions = [];
+ private static Stack _variables = [];
+ private static Stack _variableScopes = [];
+ private static int _variableIndex;
+ private static int _labelIndex;
+ private static int _anonymousFuncIndex;
+ private static int _cStringLiteralIndex;
+ private static int _stringLiteralIndex;
+ private static bool _codeIsReachable = true;
+
+ public static string Emit(BoundSyntaxTree syntaxTree, BoundDefinitionTable definitionTable)
+ {
+ _syntaxTree = syntaxTree;
+ _definitionTable = definitionTable;
+
+ _builder = new StringBuilder();
+ _cStringLiterals = [];
+ _stringLiterals = [];
+ _breakLabels = [];
+ _continueLabels = [];
+ _anonymousFunctions = [];
+ _variables = [];
+ _variableScopes = [];
+ _variableIndex = 0;
+ _labelIndex = 0;
+ _anonymousFuncIndex = 0;
+ _cStringLiteralIndex = 0;
+ _stringLiteralIndex = 0;
+ _codeIsReachable = true;
+
+ foreach (var structDef in _definitionTable.GetStructs())
+ {
+ EmitStructDefinition(structDef);
+ _builder.AppendLine();
+ }
+
+ foreach (var funcDef in _syntaxTree.Definitions.OfType())
+ {
+ EmitFuncDefinition(FuncName(funcDef), funcDef.Parameters, funcDef.ReturnType, funcDef.Body, funcDef.Exported);
+ _builder.AppendLine();
+ }
+
+ while (_anonymousFunctions.TryDequeue(out var anon))
+ {
+ EmitFuncDefinition(anon.Name, anon.Func.Parameters, anon.Func.ReturnType, anon.Func.Body, false);
+ _builder.AppendLine();
+ }
+
+ foreach (var cStringLiteral in _cStringLiterals)
+ {
+ _builder.AppendLine($"data {cStringLiteral.Name} = {{ b \"{cStringLiteral.Value}\", b 0 }}");
+ }
+
+ foreach (var stringLiteral in _stringLiterals)
+ {
+ var bytes = Encoding.UTF8.GetBytes(stringLiteral.Value).Select(b => $"b {b}");
+ _builder.AppendLine($"data {stringLiteral.Name} = {{ l {stringLiteral.Value.Length}, {string.Join(", ", bytes)} }}");
+ }
+
+ return _builder.ToString();
+ }
+
+ private static string VarName()
+ {
+ return $"%v{++_variableIndex}";
+ }
+
+ private static string LabelName()
+ {
+ return $"@l{++_labelIndex}";
+ }
+
+ private static string CStringName()
+ {
+ return $"$cstring{++_cStringLiteralIndex}";
+ }
+
+ private static string StringName()
+ {
+ return $"$string{++_stringLiteralIndex}";
+ }
+
+ private static string FuncName(BoundFuncDefinition funcDef)
+ {
+ return funcDef switch
+ {
+ BoundExternFuncDefinitionNode externFuncDefinition => $"${externFuncDefinition.CallName}",
+ BoundLocalFuncDefinitionNode localFuncDefinition => localFuncDefinition.Exported
+ ? $"${localFuncDefinition.Name}"
+ : $"${localFuncDefinition.Namespace}_{localFuncDefinition.Name}",
+ _ => throw new ArgumentOutOfRangeException(nameof(funcDef))
+ };
+ }
+
+ private static string StructName(BoundStructDefinitionNode structDef)
+ {
+ return $":{structDef.Namespace}_{structDef.Name}";
+ }
+
+ private static string QBEStore(NubType type)
+ {
+ return type switch
+ {
+ NubArrayType => "storel",
+ NubPointerType => "storel",
+ NubPrimitiveType primitiveType => primitiveType.Kind switch
+ {
+ PrimitiveTypeKind.I64 => "storel",
+ PrimitiveTypeKind.I32 => "storew",
+ PrimitiveTypeKind.I16 => "storeh",
+ PrimitiveTypeKind.I8 => "storeb",
+ PrimitiveTypeKind.U64 => "storel",
+ PrimitiveTypeKind.U32 => "storew",
+ PrimitiveTypeKind.U16 => "storeh",
+ PrimitiveTypeKind.U8 => "storeb",
+ PrimitiveTypeKind.F64 => "stored",
+ PrimitiveTypeKind.F32 => "stores",
+ PrimitiveTypeKind.Bool => "storew",
+ _ => throw new ArgumentOutOfRangeException()
+ },
+ NubStructType => "storel",
+ NubFixedArrayType => "storel",
+ NubFuncType => "storel",
+ NubCStringType => "storel",
+ NubStringType => "storel",
+ _ => throw new NotSupportedException($"'{type}' type cannot be used in store instructions")
+ };
+ }
+
+ private static string QBELoad(NubType type)
+ {
+ return type switch
+ {
+ NubArrayType => "loadl",
+ NubPointerType => "loadl",
+ NubPrimitiveType primitiveType => primitiveType.Kind switch
+ {
+ PrimitiveTypeKind.I64 => "loadl",
+ PrimitiveTypeKind.I32 => "loadw",
+ PrimitiveTypeKind.I16 => "loadsh",
+ PrimitiveTypeKind.I8 => "loadsb",
+ PrimitiveTypeKind.U64 => "loadl",
+ PrimitiveTypeKind.U32 => "loadw",
+ PrimitiveTypeKind.U16 => "loaduh",
+ PrimitiveTypeKind.U8 => "loadub",
+ PrimitiveTypeKind.F64 => "loadd",
+ PrimitiveTypeKind.F32 => "loads",
+ PrimitiveTypeKind.Bool => "loadw",
+ _ => throw new ArgumentOutOfRangeException()
+ },
+ NubStructType => "loadl",
+ NubFixedArrayType => "loadl",
+ NubFuncType => "loadl",
+ NubCStringType => "loadl",
+ NubStringType => "loadl",
+ _ => throw new NotSupportedException($"'{type}' type cannot be used in load instructions")
+ };
+ }
+
+ private static string QBEAssign(NubType type)
+ {
+ return type switch
+ {
+ NubArrayType => "=l",
+ NubPointerType => "=l",
+ NubPrimitiveType primitiveType => primitiveType.Kind switch
+ {
+ PrimitiveTypeKind.I64 => "=l",
+ PrimitiveTypeKind.I32 => "=w",
+ PrimitiveTypeKind.I16 => "=w",
+ PrimitiveTypeKind.I8 => "=w",
+ PrimitiveTypeKind.U64 => "=l",
+ PrimitiveTypeKind.U32 => "=w",
+ PrimitiveTypeKind.U16 => "=w",
+ PrimitiveTypeKind.U8 => "=w",
+ PrimitiveTypeKind.F64 => "=d",
+ PrimitiveTypeKind.F32 => "=s",
+ PrimitiveTypeKind.Bool => "=w",
+ _ => throw new ArgumentOutOfRangeException()
+ },
+ NubStructType => "=l",
+ NubFixedArrayType => "=l",
+ NubFuncType => "=l",
+ NubCStringType => "=l",
+ NubStringType => "=l",
+ _ => throw new NotSupportedException($"'{type}' type cannot be used in variables")
+ };
+ }
+
+ private static int AlignmentOf(NubType type)
+ {
+ switch (type)
+ {
+ case NubPrimitiveType primitiveType:
+ {
+ return primitiveType.Kind switch
+ {
+ PrimitiveTypeKind.I64 or PrimitiveTypeKind.U64 or PrimitiveTypeKind.F64 => 8,
+ PrimitiveTypeKind.I32 or PrimitiveTypeKind.U32 or PrimitiveTypeKind.F32 or PrimitiveTypeKind.Bool => 4,
+ PrimitiveTypeKind.I16 or PrimitiveTypeKind.U16 => 2,
+ PrimitiveTypeKind.I8 or PrimitiveTypeKind.U8 => 1,
+ _ => throw new ArgumentOutOfRangeException()
+ };
+ }
+ case NubStructType nubStructType:
+ {
+ var definition = _definitionTable.LookupStruct(nubStructType.Namespace, nubStructType.Name).GetValue();
+ return definition.Fields.Max(f => AlignmentOf(f.Type));
+ }
+ case NubPointerType:
+ case NubArrayType:
+ case NubCStringType:
+ case NubStringType:
+ case NubFuncType:
+ {
+ return 8;
+ }
+ case NubFixedArrayType nubFixedArrayType:
+ {
+ return AlignmentOf(nubFixedArrayType.ElementType);
+ }
+ default:
+ {
+ throw new ArgumentOutOfRangeException();
+ }
+ }
+ }
+
+ private static int AlignTo(int offset, int alignment)
+ {
+ return (offset + alignment - 1) & ~(alignment - 1);
+ }
+
+ private static int SizeOf(NubType type)
+ {
+ switch (type)
+ {
+ case NubPrimitiveType primitiveType:
+ {
+ return primitiveType.Kind switch
+ {
+ PrimitiveTypeKind.I64 or PrimitiveTypeKind.U64 or PrimitiveTypeKind.F64 => 8,
+ PrimitiveTypeKind.I32 or PrimitiveTypeKind.U32 or PrimitiveTypeKind.F32 or PrimitiveTypeKind.Bool => 4,
+ PrimitiveTypeKind.I16 or PrimitiveTypeKind.U16 => 2,
+ PrimitiveTypeKind.I8 or PrimitiveTypeKind.U8 => 1,
+ _ => throw new ArgumentOutOfRangeException()
+ };
+ }
+ case NubStructType nubStructType:
+ {
+ var definition = _definitionTable.LookupStruct(nubStructType.Namespace, nubStructType.Name).GetValue();
+
+ var size = 0;
+ var maxAlignment = 1;
+
+ foreach (var field in definition.Fields)
+ {
+ var fieldAlignment = AlignmentOf(field.Type);
+ maxAlignment = Math.Max(maxAlignment, fieldAlignment);
+
+ size = AlignTo(size, fieldAlignment);
+ size += SizeOf(field.Type);
+ }
+
+ size = AlignTo(size, maxAlignment);
+
+ return size;
+ }
+ case NubPointerType:
+ case NubArrayType:
+ case NubCStringType:
+ case NubStringType:
+ case NubFuncType:
+ {
+ return 8;
+ }
+ case NubFixedArrayType nubFixedArrayType:
+ {
+ return SizeOf(nubFixedArrayType.ElementType) * nubFixedArrayType.Capacity + 8;
+ }
+ default:
+ {
+ throw new ArgumentOutOfRangeException();
+ }
+ }
+ }
+
+ private static int OffsetOf(BoundStructDefinitionNode structDefinition, string member)
+ {
+ var offset = 0;
+
+ foreach (var field in structDefinition.Fields)
+ {
+ if (field.Name == member)
+ {
+ return offset;
+ }
+
+ var fieldAlignment = AlignmentOf(field.Type);
+
+ offset = AlignTo(offset, fieldAlignment);
+ offset += SizeOf(field.Type);
+ }
+
+ throw new UnreachableException($"Member '{member}' not found in struct");
+ }
+
+ private static bool IsPointerType(NubType type)
+ {
+ if (type.IsVoid)
+ {
+ throw new InvalidOperationException($"{nameof(IsPointerType)} should not be called on void types");
+ }
+
+ return type is NubStructType or NubFixedArrayType;
+ }
+
+ private static void EmitFuncDefinition(string name, List parameters, NubType returnType, BoundBlockNode body, bool exported)
+ {
+ _variables.Clear();
+ _variableScopes.Clear();
+
+ if (exported)
+ {
+ _builder.Append("export ");
+ }
+
+ _builder.Append("function ");
+ if (returnType is not NubVoidType)
+ {
+ _builder.Append(returnType switch
+ {
+ NubArrayType => "l",
+ NubPointerType => "l",
+ NubPrimitiveType primitiveType => primitiveType.Kind switch
+ {
+ PrimitiveTypeKind.I64 => "l",
+ PrimitiveTypeKind.I32 => "w",
+ PrimitiveTypeKind.I16 => "sh",
+ PrimitiveTypeKind.I8 => "sb",
+ PrimitiveTypeKind.U64 => "l",
+ PrimitiveTypeKind.U32 => "w",
+ PrimitiveTypeKind.U16 => "uh",
+ PrimitiveTypeKind.U8 => "ub",
+ PrimitiveTypeKind.F64 => "d",
+ PrimitiveTypeKind.F32 => "s",
+ PrimitiveTypeKind.Bool => "w",
+ _ => throw new ArgumentOutOfRangeException()
+ },
+ NubStructType structType => StructName(_definitionTable.LookupStruct(structType.Namespace, structType.Name).GetValue()),
+ NubFixedArrayType => "l",
+ NubFuncType => "l",
+ NubCStringType => "l",
+ NubStringType => "l",
+ _ => throw new NotSupportedException($"'{returnType}' type cannot be used as a function return type")
+ });
+ _builder.Append(' ');
+ }
+
+ _builder.Append(name);
+
+ var parameterStrings = parameters.Select(parameter =>
+ {
+ var qbeType = parameter.Type switch
+ {
+ NubArrayType => "l",
+ NubPointerType => "l",
+ NubPrimitiveType primitiveType => primitiveType.Kind switch
+ {
+ PrimitiveTypeKind.I64 => "l",
+ PrimitiveTypeKind.I32 => "w",
+ PrimitiveTypeKind.I16 => "sh",
+ PrimitiveTypeKind.I8 => "sb",
+ PrimitiveTypeKind.U64 => "l",
+ PrimitiveTypeKind.U32 => "w",
+ PrimitiveTypeKind.U16 => "uh",
+ PrimitiveTypeKind.U8 => "ub",
+ PrimitiveTypeKind.F64 => "d",
+ PrimitiveTypeKind.F32 => "s",
+ PrimitiveTypeKind.Bool => "w",
+ _ => throw new ArgumentOutOfRangeException()
+ },
+ NubStructType structType => StructName(_definitionTable.LookupStruct(structType.Namespace, structType.Name).GetValue()),
+ NubFixedArrayType => "l",
+ NubFuncType => "l",
+ NubCStringType => "l",
+ NubStringType => "l",
+ _ => throw new NotSupportedException($"'{parameter.Type}' type cannot be used as a function parameter type")
+ };
+
+ return $"{qbeType} %{parameter.Name}";
+ });
+
+ _builder.AppendLine($"({string.Join(", ", parameterStrings)}) {{");
+ _builder.AppendLine("@start");
+
+ List parameterVars = [];
+
+ foreach (var parameter in parameters)
+ {
+ var parameterName = "%" + parameter.Name;
+
+ if (parameter.Type is NubPrimitiveType primitiveType)
+ {
+ switch (primitiveType.Kind)
+ {
+ case PrimitiveTypeKind.I16:
+ parameterName = VarName();
+ _builder.AppendLine($" {parameterName} =w extsh %{parameter.Name}");
+ break;
+ case PrimitiveTypeKind.I8:
+ parameterName = VarName();
+ _builder.AppendLine($" {parameterName} =w extsb %{parameter.Name}");
+ break;
+ case PrimitiveTypeKind.U16:
+ parameterName = VarName();
+ _builder.AppendLine($" {parameterName} =w extuh %{parameter.Name}");
+ break;
+ case PrimitiveTypeKind.U8:
+ parameterName = VarName();
+ _builder.AppendLine($" {parameterName} =w extub %{parameter.Name}");
+ break;
+ }
+ }
+
+ parameterVars.Add(new Variable(parameter.Name, new Val(parameterName, parameter.Type, ValKind.Immediate)));
+ }
+
+ EmitBlock(body, parameterVars);
+
+ if (body.Statements.LastOrDefault() is not BoundReturnNode)
+ {
+ if (returnType is NubVoidType)
+ {
+ _builder.AppendLine(" ret");
+ }
+ }
+
+ _builder.AppendLine("}");
+ }
+
+ private static void EmitStructDefinition(BoundStructDefinitionNode structDefinition)
+ {
+ _builder.Append($"type {StructName(structDefinition)} = {{ ");
+ foreach (var structDefinitionField in structDefinition.Fields)
+ {
+ var qbeType = structDefinitionField.Type switch
+ {
+ NubArrayType => "l",
+ NubPointerType => "l",
+ NubPrimitiveType primitiveType => primitiveType.Kind switch
+ {
+ PrimitiveTypeKind.I64 => "l",
+ PrimitiveTypeKind.I32 => "w",
+ PrimitiveTypeKind.I16 => "h",
+ PrimitiveTypeKind.I8 => "b",
+ PrimitiveTypeKind.U64 => "l",
+ PrimitiveTypeKind.U32 => "w",
+ PrimitiveTypeKind.U16 => "h",
+ PrimitiveTypeKind.U8 => "b",
+ PrimitiveTypeKind.F64 => "d",
+ PrimitiveTypeKind.F32 => "s",
+ PrimitiveTypeKind.Bool => "w",
+ _ => throw new ArgumentOutOfRangeException()
+ },
+ NubStructType structType => StructName(_definitionTable.LookupStruct(structType.Namespace, structType.Name).GetValue()),
+ NubFixedArrayType fixedArrayType => $"b {SizeOf(fixedArrayType)}",
+ NubFuncType => "l",
+ NubCStringType => "l",
+ NubStringType => "l",
+ _ => throw new NotSupportedException($"'{structDefinitionField.Type}' type cannot be used in structs")
+ };
+ _builder.Append(qbeType + ", ");
+ }
+
+ _builder.AppendLine("}");
+ }
+
+ private static void EmitStatement(BoundStatementNode statement)
+ {
+ switch (statement)
+ {
+ case BoundArrayIndexAssignmentNode arrayIndexAssignment:
+ EmitArrayIndexAssignment(arrayIndexAssignment);
+ break;
+ case BoundBreakNode:
+ EmitBreak();
+ break;
+ case BoundContinueNode:
+ EmitContinue();
+ break;
+ case BoundDereferenceAssignmentNode dereferenceAssignment:
+ EmitDereferenceAssignment(dereferenceAssignment);
+ break;
+ case BoundIfNode ifStatement:
+ EmitIf(ifStatement);
+ break;
+ case BoundMemberAssignmentNode memberAssignment:
+ EmitMemberAssignment(memberAssignment);
+ break;
+ case BoundReturnNode @return:
+ EmitReturn(@return);
+ break;
+ case BoundStatementExpressionNode statementExpression:
+ EmitExpression(statementExpression.Expression);
+ break;
+ case BoundVariableDeclarationNode variableDeclaration:
+ EmitVariableDeclaration(variableDeclaration);
+ break;
+ case BoundVariableAssignmentNode variableAssignment:
+ EmitVariableAssignment(variableAssignment);
+ break;
+ case BoundWhileNode whileStatement:
+ EmitWhile(whileStatement);
+ break;
+ default:
+ throw new ArgumentOutOfRangeException(nameof(statement));
+ }
+ }
+
+ private static void EmitArrayIndexAssignment(BoundArrayIndexAssignmentNode arrayIndexAssignment)
+ {
+ var array = EmitUnwrap(EmitExpression(arrayIndexAssignment.ArrayIndexAccess.Array));
+ var index = EmitUnwrap(EmitExpression(arrayIndexAssignment.ArrayIndexAccess.Index));
+ EmitArrayBoundsCheck(array, index);
+ var value = EmitUnwrap(EmitExpression(arrayIndexAssignment.Value));
+
+ switch (arrayIndexAssignment.ArrayIndexAccess.Array.Type)
+ {
+ case NubArrayType arrayType:
+ {
+ var pointer = VarName();
+ _builder.AppendLine($" {pointer} =l mul {index}, {SizeOf(arrayType.ElementType)}");
+ _builder.AppendLine($" {pointer} =l add {pointer}, 8");
+ _builder.AppendLine($" {pointer} =l add {array}, {pointer}");
+
+ EmitCopy(arrayType.ElementType, value, pointer);
+ break;
+ }
+ case NubFixedArrayType fixedArrayType:
+ {
+ var pointer = VarName();
+ _builder.AppendLine($" {pointer} =l mul {index}, {SizeOf(fixedArrayType.ElementType)}");
+ _builder.AppendLine($" {pointer} =l add {pointer}, 8");
+ _builder.AppendLine($" {pointer} =l add {array}, {pointer}");
+
+ EmitCopy(fixedArrayType.ElementType, value, pointer);
+ break;
+ }
+ default:
+ {
+ throw new ArgumentOutOfRangeException();
+ }
+ }
+ }
+
+ private static void EmitBlock(BoundBlockNode block, List? variables = null)
+ {
+ _variableScopes.Push(_variables.Count);
+ if (variables != null)
+ {
+ foreach (var variable in variables)
+ {
+ _variables.Push(variable);
+ }
+ }
+
+ foreach (var statement in block.Statements.Where(_ => _codeIsReachable))
+ {
+ EmitStatement(statement);
+ }
+
+ var count = _variableScopes.Pop();
+ while (_variableScopes.Count > count)
+ {
+ _variableScopes.Pop();
+ }
+
+ _codeIsReachable = true;
+ }
+
+ private static void EmitBreak()
+ {
+ _builder.AppendLine($" jmp {_breakLabels.Peek()}");
+ _codeIsReachable = false;
+ }
+
+ private static void EmitContinue()
+ {
+ _builder.AppendLine($" jmp {_continueLabels.Peek()}");
+ _codeIsReachable = false;
+ }
+
+ private static void EmitDereferenceAssignment(BoundDereferenceAssignmentNode dereferenceAssignment)
+ {
+ var pointer = EmitUnwrap(EmitExpression(dereferenceAssignment.Dereference.Expression));
+ var value = EmitUnwrap(EmitExpression(dereferenceAssignment.Value));
+ EmitCopy(dereferenceAssignment.Value.Type, value, pointer);
+ }
+
+ private static void EmitIf(BoundIfNode ifStatement)
+ {
+ var trueLabel = LabelName();
+ var falseLabel = LabelName();
+ var endLabel = LabelName();
+
+ var result = EmitUnwrap(EmitExpression(ifStatement.Condition));
+ _builder.AppendLine($" jnz {result}, {trueLabel}, {falseLabel}");
+ _builder.AppendLine(trueLabel);
+ EmitBlock(ifStatement.Body);
+ _builder.AppendLine($" jmp {endLabel}");
+ _builder.AppendLine(falseLabel);
+ if (ifStatement.Else.HasValue)
+ {
+ ifStatement.Else.Value.Match
+ (
+ elseIfNode => EmitIf(elseIfNode),
+ elseNode => EmitBlock(elseNode)
+ );
+ }
+
+ _builder.AppendLine(endLabel);
+ }
+
+ private static void EmitMemberAssignment(BoundMemberAssignmentNode memberAssignment)
+ {
+ var structType = memberAssignment.MemberAccess.Expression.Type as NubStructType;
+ Debug.Assert(structType != null);
+
+ var structDefinition = _definitionTable.LookupStruct(structType.Namespace, structType.Name).GetValue();
+ var offset = OffsetOf(structDefinition, memberAssignment.MemberAccess.Member);
+
+ var item = EmitUnwrap(EmitExpression(memberAssignment.MemberAccess.Expression));
+ var pointer = VarName();
+
+ _builder.AppendLine($" {pointer} =l add {item}, {offset}");
+
+ var value = EmitUnwrap(EmitExpression(memberAssignment.Value));
+
+ EmitCopy(memberAssignment.Value.Type, value, pointer);
+ }
+
+ private static void EmitReturn(BoundReturnNode @return)
+ {
+ if (@return.Value.HasValue)
+ {
+ var result = EmitUnwrap(EmitExpression(@return.Value.Value));
+ _builder.AppendLine($" ret {result}");
+ }
+ else
+ {
+ _builder.AppendLine(" ret");
+ }
+ }
+
+ private static void EmitVariableDeclaration(BoundVariableDeclarationNode variableDeclaration)
+ {
+ var tmp = VarName();
+ _builder.AppendLine($" {tmp} =l alloc8 {SizeOf(variableDeclaration.Type)}");
+ _variables.Push(new Variable(variableDeclaration.Name, new Val(tmp, variableDeclaration.Type, ValKind.Pointer)));
+ }
+
+ private static void EmitVariableAssignment(BoundVariableAssignmentNode variableAssignment)
+ {
+ var value = EmitUnwrap(EmitExpression(variableAssignment.Value));
+ var variable = _variables.Single(x => x.Name == variableAssignment.Identifier.Name);
+ EmitCopy(variableAssignment.Value.Type, value, variable.Val.Name);
+ }
+
+ private static void EmitWhile(BoundWhileNode whileStatement)
+ {
+ var conditionLabel = LabelName();
+ var iterationLabel = LabelName();
+ var endLabel = LabelName();
+
+ _breakLabels.Push(endLabel);
+ _continueLabels.Push(conditionLabel);
+
+ _builder.AppendLine($" jmp {conditionLabel}");
+ _builder.AppendLine(iterationLabel);
+ EmitBlock(whileStatement.Body);
+ _builder.AppendLine(conditionLabel);
+ var result = EmitUnwrap(EmitExpression(whileStatement.Condition));
+ _builder.AppendLine($" jnz {result}, {iterationLabel}, {endLabel}");
+ _builder.AppendLine(endLabel);
+
+ _continueLabels.Pop();
+ _breakLabels.Pop();
+ }
+
+ private static Val EmitExpression(BoundExpressionNode expression)
+ {
+ return expression switch
+ {
+ BoundAddressOfNode addressOf => EmitAddressOf(addressOf),
+ BoundAnonymousFuncNode anonymousFunc => EmitAnonymousFunc(anonymousFunc),
+ BoundArrayIndexAccessNode arrayIndex => EmitArrayIndexAccess(arrayIndex),
+ BoundArrayInitializerNode arrayInitializer => EmitArrayInitializer(arrayInitializer),
+ BoundBinaryExpressionNode binaryExpression => EmitBinaryExpression(binaryExpression),
+ BoundDereferenceNode dereference => EmitDereference(dereference),
+ BoundFixedArrayInitializerNode fixedArrayInitializer => EmitFixedArrayInitializer(fixedArrayInitializer),
+ BoundFuncCallNode funcCallExpression => EmitFuncCall(funcCallExpression),
+ BoundIdentifierNode identifier => EmitIdentifier(identifier),
+ BoundLiteralNode literal => EmitLiteral(literal),
+ BoundStructInitializerNode structInitializer => EmitStructInitializer(structInitializer),
+ BoundUnaryExpressionNode unaryExpression => EmitUnaryExpression(unaryExpression),
+ BoundMemberAccessNode memberAccess => EmitMemberAccess(memberAccess),
+ _ => throw new ArgumentOutOfRangeException(nameof(expression))
+ };
+ }
+
+ private static Val EmitAnonymousFunc(BoundAnonymousFuncNode anonymousFunc)
+ {
+ var name = $"$anon_func{++_anonymousFuncIndex}";
+ _anonymousFunctions.Enqueue((anonymousFunc, name));
+ return new Val(name, anonymousFunc.Type, ValKind.Func);
+ }
+
+ private static string EmitArrayIndexPointer(BoundArrayIndexAccessNode arrayIndexAccess)
+ {
+ var array = EmitUnwrap(EmitExpression(arrayIndexAccess.Array));
+ var index = EmitUnwrap(EmitExpression(arrayIndexAccess.Index));
+ EmitArrayBoundsCheck(array, index);
+
+ var elementType = arrayIndexAccess.Array.Type switch
+ {
+ NubArrayType arrayType => arrayType.ElementType,
+ NubFixedArrayType fixedArrayType => fixedArrayType.ElementType,
+ _ => throw new ArgumentOutOfRangeException()
+ };
+
+ var pointer = VarName();
+ _builder.AppendLine($" {pointer} =l mul {index}, {SizeOf(elementType)}");
+ _builder.AppendLine($" {pointer} =l add {pointer}, 8");
+ _builder.AppendLine($" {pointer} =l add {array}, {pointer}");
+ return pointer;
+ }
+
+ private static Val EmitArrayIndexAccess(BoundArrayIndexAccessNode arrayIndexAccess)
+ {
+ var pointer = EmitArrayIndexPointer(arrayIndexAccess);
+ var outputName = VarName();
+ _builder.AppendLine($" {outputName} {QBEAssign(arrayIndexAccess.Type)} {QBELoad(arrayIndexAccess.Type)} {pointer}");
+ return new Val(outputName, arrayIndexAccess.Type, ValKind.Immediate);
+ }
+
+ private static void EmitArrayBoundsCheck(string array, string index)
+ {
+ var count = VarName();
+ _builder.AppendLine($" {count} =l loadl {array}");
+
+ var isNegative = VarName();
+ _builder.AppendLine($" {isNegative} =w csltl {index}, 0");
+
+ var isOob = VarName();
+ _builder.AppendLine($" {isOob} =w csgel {index}, {count}");
+
+ var anyOob = VarName();
+ _builder.AppendLine($" {anyOob} =w or {isNegative}, {isOob}");
+
+ var oobLabel = LabelName();
+ var notOobLabel = LabelName();
+ _builder.AppendLine($" jnz {anyOob}, {oobLabel}, {notOobLabel}");
+
+ _builder.AppendLine(oobLabel);
+ _builder.AppendLine($" call $nub_panic_array_oob()");
+
+ _builder.AppendLine(notOobLabel);
+ }
+
+ private static Val EmitArrayInitializer(BoundArrayInitializerNode arrayInitializer)
+ {
+ var capacity = EmitUnwrap(EmitExpression(arrayInitializer.Capacity));
+ var elementSize = SizeOf(arrayInitializer.ElementType);
+
+ var capacityInBytes = VarName();
+ _builder.AppendLine($" {capacityInBytes} =l mul {capacity}, {elementSize}");
+ var totalSize = VarName();
+ _builder.AppendLine($" {totalSize} =l add {capacityInBytes}, 8");
+
+ var arrayPointer = VarName();
+ _builder.AppendLine($" {arrayPointer} =l alloc8 {totalSize}");
+ _builder.AppendLine($" storel {capacity}, {arrayPointer}");
+
+ var dataPointer = VarName();
+ _builder.AppendLine($" {dataPointer} =l add {arrayPointer}, 8");
+ _builder.AppendLine($" call $nub_memset(l {dataPointer}, w 0, l {capacityInBytes})");
+
+ return new Val(arrayPointer, arrayInitializer.Type, ValKind.Immediate);
+ }
+
+ private static Val EmitDereference(BoundDereferenceNode dereference)
+ {
+ var result = EmitUnwrap(EmitExpression(dereference.Expression));
+ var outputName = VarName();
+ _builder.AppendLine($" {outputName} {QBEAssign(dereference.Type)} {QBELoad(dereference.Type)} {result}");
+ return new Val(outputName, dereference.Type, ValKind.Immediate);
+ }
+
+ private static Val EmitAddressOf(BoundAddressOfNode addressOf)
+ {
+ switch (addressOf.Expression)
+ {
+ case BoundArrayIndexAccessNode arrayIndexAccess:
+ {
+ var pointer = EmitArrayIndexPointer(arrayIndexAccess);
+ return new Val(pointer, addressOf.Type, ValKind.Immediate);
+ }
+ case BoundDereferenceNode dereference:
+ {
+ return EmitExpression(dereference.Expression);
+ }
+ case BoundIdentifierNode identifier:
+ {
+ if (identifier.Namespace.HasValue)
+ {
+ throw new NotSupportedException("There is nothing to address in another namespace");
+ }
+
+ return _variables.Single(x => x.Name == identifier.Name).Val;
+ }
+ default:
+ {
+ throw new ArgumentOutOfRangeException();
+ }
+ }
+ }
+
+ private static Val EmitBinaryExpression(BoundBinaryExpressionNode binaryExpression)
+ {
+ var left = EmitUnwrap(EmitExpression(binaryExpression.Left));
+ var right = EmitUnwrap(EmitExpression(binaryExpression.Right));
+ var outputName = VarName();
+ var output = new Val(outputName, binaryExpression.Type, ValKind.Immediate);
+
+ switch (binaryExpression.Operator)
+ {
+ case BinaryExpressionOperator.Equal:
+ {
+ if (binaryExpression.Left.Type.Equals(NubPrimitiveType.I64))
+ {
+ _builder.AppendLine($" {outputName} =w ceql {left}, {right}");
+ return output;
+ }
+
+ if (binaryExpression.Left.Type.Equals(NubPrimitiveType.I32))
+ {
+ _builder.AppendLine($" {outputName} =w ceqw {left}, {right}");
+ return output;
+ }
+
+ if (binaryExpression.Left.Type.Equals(NubPrimitiveType.Bool))
+ {
+ _builder.AppendLine($" {outputName} =w ceqw {left}, {right}");
+ return output;
+ }
+
+ break;
+ }
+ case BinaryExpressionOperator.NotEqual:
+ {
+ if (binaryExpression.Left.Type.Equals(NubPrimitiveType.I64))
+ {
+ _builder.AppendLine($" {outputName} =w cnel {left}, {right}");
+ return output;
+ }
+
+ if (binaryExpression.Left.Type.Equals(NubPrimitiveType.I32))
+ {
+ _builder.AppendLine($" {outputName} =w cnew {left}, {right}");
+ return output;
+ }
+
+ if (binaryExpression.Left.Type.Equals(NubPrimitiveType.Bool))
+ {
+ _builder.AppendLine($" {outputName} =w cnew {left}, {right}");
+ return output;
+ }
+
+ break;
+ }
+ case BinaryExpressionOperator.GreaterThan:
+ {
+ if (binaryExpression.Left.Type.Equals(NubPrimitiveType.I64))
+ {
+ _builder.AppendLine($" {outputName} =w csgtl {left}, {right}");
+ return output;
+ }
+
+ if (binaryExpression.Left.Type.Equals(NubPrimitiveType.I32))
+ {
+ _builder.AppendLine($" {outputName} =w csgtw {left}, {right}");
+ return output;
+ }
+
+ if (binaryExpression.Left.Type.Equals(NubPrimitiveType.Bool))
+ {
+ _builder.AppendLine($" {outputName} =w csgtw {left}, {right}");
+ return output;
+ }
+
+ break;
+ }
+ case BinaryExpressionOperator.GreaterThanOrEqual:
+ {
+ if (binaryExpression.Left.Type.Equals(NubPrimitiveType.I64))
+ {
+ _builder.AppendLine($" {outputName} =w csgel {left}, {right}");
+ return output;
+ }
+
+ if (binaryExpression.Left.Type.Equals(NubPrimitiveType.I32))
+ {
+ _builder.AppendLine($" {outputName} =w csgew {left}, {right}");
+ return output;
+ }
+
+ if (binaryExpression.Left.Type.Equals(NubPrimitiveType.Bool))
+ {
+ _builder.AppendLine($" {outputName} =w csgew {left}, {right}");
+ return output;
+ }
+
+ break;
+ }
+ case BinaryExpressionOperator.LessThan:
+ {
+ if (binaryExpression.Left.Type.Equals(NubPrimitiveType.I64))
+ {
+ _builder.AppendLine($" {outputName} =w csltl {left}, {right}");
+ return output;
+ }
+
+ if (binaryExpression.Left.Type.Equals(NubPrimitiveType.I32))
+ {
+ _builder.AppendLine($" {outputName} =w csltw {left}, {right}");
+ return output;
+ }
+
+ if (binaryExpression.Left.Type.Equals(NubPrimitiveType.Bool))
+ {
+ _builder.AppendLine($" {outputName} =w csltw {left}, {right}");
+ return output;
+ }
+
+ break;
+ }
+ case BinaryExpressionOperator.LessThanOrEqual:
+ {
+ if (binaryExpression.Left.Type.Equals(NubPrimitiveType.I64))
+ {
+ _builder.AppendLine($" {outputName} =w cslel {left}, {right}");
+ return output;
+ }
+
+ if (binaryExpression.Left.Type.Equals(NubPrimitiveType.I32))
+ {
+ _builder.AppendLine($" {outputName} =w cslew {left}, {right}");
+ return output;
+ }
+
+ if (binaryExpression.Left.Type.Equals(NubPrimitiveType.Bool))
+ {
+ _builder.AppendLine($" {outputName} =w cslew {left}, {right}");
+ return output;
+ }
+
+ break;
+ }
+ case BinaryExpressionOperator.Plus:
+ {
+ if (binaryExpression.Left.Type.Equals(NubPrimitiveType.I64))
+ {
+ _builder.AppendLine($" {outputName} =l add {left}, {right}");
+ return output;
+ }
+
+ if (binaryExpression.Left.Type.Equals(NubPrimitiveType.I32))
+ {
+ _builder.AppendLine($" {outputName} =w add {left}, {right}");
+ return output;
+ }
+
+ break;
+ }
+ case BinaryExpressionOperator.Minus:
+ {
+ if (binaryExpression.Left.Type.Equals(NubPrimitiveType.I64))
+ {
+ _builder.AppendLine($" {outputName} =l sub {left}, {right}");
+ return output;
+ }
+
+ if (binaryExpression.Left.Type.Equals(NubPrimitiveType.I32))
+ {
+ _builder.AppendLine($" {outputName} =w sub {left}, {right}");
+ return output;
+ }
+
+ break;
+ }
+ case BinaryExpressionOperator.Multiply:
+ {
+ if (binaryExpression.Left.Type.Equals(NubPrimitiveType.I64))
+ {
+ _builder.AppendLine($" {outputName} =l mul {left}, {right}");
+ return output;
+ }
+
+ if (binaryExpression.Left.Type.Equals(NubPrimitiveType.I32))
+ {
+ _builder.AppendLine($" {outputName} =w mul {left}, {right}");
+ return output;
+ }
+
+ break;
+ }
+ case BinaryExpressionOperator.Divide:
+ {
+ if (binaryExpression.Left.Type.Equals(NubPrimitiveType.I64))
+ {
+ _builder.AppendLine($" {outputName} =l div {left}, {right}");
+ return output;
+ }
+
+ if (binaryExpression.Left.Type.Equals(NubPrimitiveType.I32))
+ {
+ _builder.AppendLine($" {outputName} =w div {left}, {right}");
+ return output;
+ }
+
+ break;
+ }
+ default:
+ {
+ throw new ArgumentOutOfRangeException();
+ }
+ }
+
+ throw new NotSupportedException($"Binary operator {binaryExpression.Operator} for types {binaryExpression.Left.Type} and {binaryExpression.Right.Type} not supported");
+ }
+
+ private static Val EmitIdentifier(BoundIdentifierNode identifier)
+ {
+ if (_definitionTable.LookupFunc(identifier.Namespace.Or(_syntaxTree.Namespace), identifier.Name).TryGetValue(out var func))
+ {
+ return new Val(FuncName(func), identifier.Type, ValKind.Func);
+ }
+
+ if (!identifier.Namespace.HasValue)
+ {
+ return _variables.Single(v => v.Name == identifier.Name).Val;
+ }
+
+ throw new UnreachableException();
+ }
+
+ private static Val EmitLiteral(BoundLiteralNode literal)
+ {
+ switch (literal.Kind)
+ {
+ case LiteralKind.Integer:
+ {
+ if (literal.Type.IsFloat32)
+ {
+ var value = float.Parse(literal.Literal, CultureInfo.InvariantCulture);
+ var bits = BitConverter.SingleToInt32Bits(value);
+ return new Val(bits.ToString(), literal.Type, ValKind.Immediate);
+ }
+
+ if (literal.Type.IsFloat64)
+ {
+ var value = double.Parse(literal.Literal, CultureInfo.InvariantCulture);
+ var bits = BitConverter.DoubleToInt64Bits(value);
+ return new Val(bits.ToString(), literal.Type, ValKind.Immediate);
+ }
+
+ if (literal.Type.IsInteger)
+ {
+ return new Val(literal.Literal, literal.Type, ValKind.Immediate);
+ }
+ break;
+ }
+ case LiteralKind.Float:
+ {
+ if (literal.Type.IsInteger)
+ {
+ return new Val(literal.Literal.Split(".").First(), literal.Type, ValKind.Immediate);
+ }
+
+ if (literal.Type.IsFloat32)
+ {
+ var value = float.Parse(literal.Literal, CultureInfo.InvariantCulture);
+ var bits = BitConverter.SingleToInt32Bits(value);
+ return new Val(bits.ToString(), literal.Type, ValKind.Immediate);
+ }
+
+ if (literal.Type.IsFloat64)
+ {
+ var value = double.Parse(literal.Literal, CultureInfo.InvariantCulture);
+ var bits = BitConverter.DoubleToInt64Bits(value);
+ return new Val(bits.ToString(), literal.Type, ValKind.Immediate);
+ }
+ break;
+ }
+ case LiteralKind.String:
+ {
+ if (literal.Type.IsString)
+ {
+ var stringLiteral = new StringLiteral(literal.Literal, StringName());
+ _stringLiterals.Add(stringLiteral);
+ return new Val(stringLiteral.Name, literal.Type, ValKind.Immediate);
+ }
+
+ if (literal.Type.IsCString)
+ {
+ var cStringLiteral = new CStringLiteral(literal.Literal, CStringName());
+ _cStringLiterals.Add(cStringLiteral);
+ return new Val(cStringLiteral.Name, literal.Type, ValKind.Immediate);
+ }
+ break;
+ }
+ case LiteralKind.Bool:
+ {
+ if (literal.Type.IsBool)
+ {
+ return new Val(bool.Parse(literal.Literal) ? "1" : "0", literal.Type, ValKind.Immediate);
+ }
+ break;
+ }
+ }
+
+ throw new NotSupportedException($"Cannot create literal of kind '{literal.Kind}' for type {literal.Type}");
+ }
+
+ private static Val EmitStructInitializer(BoundStructInitializerNode structInitializer)
+ {
+ var structDefinition = _definitionTable.LookupStruct(structInitializer.StructType.Namespace, structInitializer.StructType.Name).GetValue();
+
+ var output = VarName();
+ var size = SizeOf(structInitializer.StructType);
+ _builder.AppendLine($" {output} =l alloc8 {size}");
+
+ foreach (var field in structDefinition.Fields)
+ {
+ var offset = OffsetOf(structDefinition, field.Name);
+
+ if (structInitializer.Initializers.TryGetValue(field.Name, out var fieldValue))
+ {
+ var value = EmitUnwrap(EmitExpression(fieldValue));
+ var pointer = VarName();
+ _builder.AppendLine($" {pointer} =l add {output}, {offset}");
+ EmitCopy(field.Type, value, pointer);
+ }
+ else if (field.Value.HasValue)
+ {
+ var value = EmitUnwrap(EmitExpression(field.Value.Value));
+ var pointer = VarName();
+ _builder.AppendLine($" {pointer} =l add {output}, {offset}");
+ EmitCopy(field.Type, value, pointer);
+ }
+ else
+ {
+ Debug.Assert(false);
+ }
+ }
+
+ return new Val(output, structInitializer.StructType, ValKind.Immediate);
+ }
+
+ private static Val EmitUnaryExpression(BoundUnaryExpressionNode unaryExpression)
+ {
+ var operand = EmitUnwrap(EmitExpression(unaryExpression.Operand));
+ var outputName = VarName();
+
+ switch (unaryExpression.Operator)
+ {
+ case UnaryExpressionOperator.Negate:
+ {
+ switch (unaryExpression.Operand.Type)
+ {
+ case NubPrimitiveType { Kind: PrimitiveTypeKind.I64 }:
+ _builder.AppendLine($" {outputName} =l neg {operand}");
+ return new Val(outputName, unaryExpression.Type, ValKind.Immediate);
+ case NubPrimitiveType { Kind: PrimitiveTypeKind.I32 or PrimitiveTypeKind.I16 or PrimitiveTypeKind.I8 }:
+ _builder.AppendLine($" {outputName} =w neg {operand}");
+ return new Val(outputName, unaryExpression.Type, ValKind.Immediate);
+ case NubPrimitiveType { Kind: PrimitiveTypeKind.F64 }:
+ _builder.AppendLine($" {outputName} =d neg {operand}");
+ return new Val(outputName, unaryExpression.Type, ValKind.Immediate);
+ case NubPrimitiveType { Kind: PrimitiveTypeKind.F32 }:
+ _builder.AppendLine($" {outputName} =s neg {operand}");
+ return new Val(outputName, unaryExpression.Type, ValKind.Immediate);
+ }
+
+ break;
+ }
+ case UnaryExpressionOperator.Invert:
+ {
+ switch (unaryExpression.Operand.Type)
+ {
+ case NubPrimitiveType { Kind: PrimitiveTypeKind.Bool }:
+ _builder.AppendLine($" {outputName} =w xor {operand}, 1");
+ return new Val(outputName, unaryExpression.Type, ValKind.Immediate);
+ }
+
+ break;
+ }
+ default:
+ {
+ throw new ArgumentOutOfRangeException();
+ }
+ }
+
+ throw new NotSupportedException($"Unary operator {unaryExpression.Operator} for type {unaryExpression.Operand.Type} not supported");
+ }
+
+ private static Val EmitMemberAccess(BoundMemberAccessNode memberAccess)
+ {
+ var item = EmitUnwrap(EmitExpression(memberAccess.Expression));
+ var output = VarName();
+
+ switch (memberAccess.Expression.Type)
+ {
+ case NubArrayType:
+ {
+ if (memberAccess.Member == "count")
+ {
+ _builder.AppendLine($" {output} =l loadl {item}");
+ break;
+ }
+
+ throw new UnreachableException();
+ }
+ case NubStringType:
+ {
+ _builder.AppendLine($" {output} =l call $nub_string_length(l {item})");
+ break;
+ }
+ case NubCStringType:
+ {
+ _builder.AppendLine($" {output} =l call $nub_cstring_length(l {item})");
+ break;
+ }
+ case NubStructType structType:
+ {
+ var structDefinition = _definitionTable.LookupStruct(structType.Namespace, structType.Name).GetValue();
+ var offset = OffsetOf(structDefinition, memberAccess.Member);
+
+ var offsetName = VarName();
+ _builder.AppendLine($" {offsetName} =l add {item}, {offset}");
+ _builder.AppendLine($" {output} {QBEAssign(memberAccess.Type)} {QBELoad(memberAccess.Type)} {item}");
+ break;
+ }
+ default:
+ {
+ throw new ArgumentOutOfRangeException();
+ }
+ }
+
+ return new Val(output, memberAccess.Type, ValKind.Immediate);
+ }
+
+ private static Val EmitFixedArrayInitializer(BoundFixedArrayInitializerNode fixedArrayInitializer)
+ {
+ var totalSize = SizeOf(fixedArrayInitializer.Type);
+ var outputName = VarName();
+ _builder.AppendLine($" {outputName} =l alloc8 {totalSize}");
+
+ _builder.AppendLine($" storel {fixedArrayInitializer.Capacity}, {outputName}");
+
+ var dataPtr = VarName();
+ _builder.AppendLine($" {dataPtr} =l add {outputName}, 8");
+
+ var dataSize = totalSize - 8;
+ _builder.AppendLine($" call $nub_memset(l {dataPtr}, w 0, l {dataSize})");
+
+ return new Val(outputName, fixedArrayInitializer.Type, ValKind.Immediate);
+ }
+
+ private static Val EmitFuncCall(BoundFuncCallNode funcCall)
+ {
+ var funcType = (NubFuncType)funcCall.Expression.Type;
+
+ var parameterStrings = new List();
+
+ for (var i = 0; i < funcCall.Parameters.Count; i++)
+ {
+ var parameter = funcCall.Parameters[i];
+ var result = EmitUnwrap(EmitExpression(parameter));
+
+ var qbeType = parameter.Type switch
+ {
+ NubArrayType => "l",
+ NubPointerType => "l",
+ NubPrimitiveType pointerType => pointerType.Kind switch
+ {
+ PrimitiveTypeKind.I64 => "l",
+ PrimitiveTypeKind.I32 => "w",
+ PrimitiveTypeKind.I16 => "sh",
+ PrimitiveTypeKind.I8 => "sb",
+ PrimitiveTypeKind.U64 => "l",
+ PrimitiveTypeKind.U32 => "w",
+ PrimitiveTypeKind.U16 => "uh",
+ PrimitiveTypeKind.U8 => "ub",
+ PrimitiveTypeKind.F64 => "d",
+ PrimitiveTypeKind.F32 => "s",
+ PrimitiveTypeKind.Bool => "w",
+ _ => throw new ArgumentOutOfRangeException()
+ },
+ NubStructType structType => StructName(_definitionTable.LookupStruct(structType.Namespace, structType.Name).GetValue()),
+ NubFixedArrayType => "l",
+ NubFuncType => "l",
+ NubCStringType => "l",
+ NubStringType => "l",
+ _ => throw new NotSupportedException($"'{parameter.Type}' type cannot be used in function calls")
+ };
+ parameterStrings.Add($"{qbeType} {result}");
+ }
+
+ var funcPointer = EmitUnwrap(EmitExpression(funcCall.Expression));
+
+ if (funcType.ReturnType is not NubVoidType)
+ {
+ var outputName = VarName();
+ _builder.AppendLine($" {outputName} {QBEAssign(funcCall.Type)} call {funcPointer}({string.Join(", ", parameterStrings)})");
+ return new Val(outputName, funcCall.Type, ValKind.Immediate);
+ }
+ else
+ {
+ _builder.AppendLine($" call {funcPointer}({string.Join(", ", parameterStrings)})");
+ return new Val(string.Empty, funcCall.Type, ValKind.Immediate);
+ }
+ }
+
+ private static void EmitCopy(NubType type, string sourcePtr, string destinationPtr)
+ {
+ if (SizeOf(type) > 8)
+ {
+ _builder.AppendLine($" blit {sourcePtr}, {destinationPtr}, {SizeOf(type)}");
+ }
+ else
+ {
+ _builder.AppendLine($" {QBEStore(type)} {sourcePtr}, {destinationPtr}");
+ }
+ }
+
+ private static string EmitUnwrap(Val val)
+ {
+ if (val.Type.IsVoid)
+ {
+ throw new InvalidOperationException("Cannot unwrap temporary of void type");
+ }
+
+ switch (val.Kind)
+ {
+ case ValKind.Func:
+ case ValKind.Immediate:
+ return val.Name;
+ case ValKind.Pointer:
+ if (IsPointerType(val.Type))
+ {
+ return val.Name;
+ }
+ else
+ {
+ var result = VarName();
+ _builder.AppendLine($" {result} {QBEAssign(val.Type)} {QBELoad(val.Type)} {val.Name}");
+ return result;
+ }
+ default:
+ throw new ArgumentOutOfRangeException();
+ }
+ }
+}
+
+internal class StringLiteral(string value, string name)
+{
+ public string Value { get; } = value;
+ public string Name { get; } = name;
+}
+
+internal class CStringLiteral(string value, string name)
+{
+ public string Value { get; } = value;
+ public string Name { get; } = name;
+}
+
+internal class Variable(string name, Val val)
+{
+ public string Name { get; } = name;
+ public Val Val { get; } = val;
+}
+
+internal class Val(string name, NubType type, ValKind kind)
+{
+ public string Name { get; } = name;
+ public NubType Type { get; } = type;
+ public ValKind Kind { get; } = kind;
+
+ public override string ToString()
+ {
+ throw new InvalidOperationException();
+ }
+}
+
+internal enum ValKind
+{
+ Func,
+ Pointer,
+ Immediate
+}
\ No newline at end of file
diff --git a/src/compilation/Syntax/DefinitionTable.cs b/src/compilation/Syntax/DefinitionTable.cs
new file mode 100644
index 0000000..4df8f31
--- /dev/null
+++ b/src/compilation/Syntax/DefinitionTable.cs
@@ -0,0 +1,85 @@
+using Common;
+using Syntax.Parsing;
+using Syntax.Parsing.Node;
+using Syntax.Typing;
+using Syntax.Typing.BoundNode;
+
+namespace Syntax;
+
+public class DefinitionTable
+{
+ private readonly IEnumerable _syntaxTrees;
+
+ public DefinitionTable(IEnumerable syntaxTrees)
+ {
+ _syntaxTrees = syntaxTrees;
+ }
+
+ public Optional LookupFunc(string @namespace, string name)
+ {
+ var definition = _syntaxTrees
+ .Where(c => c.Namespace == @namespace)
+ .SelectMany(c => c.Definitions)
+ .OfType()
+ .SingleOrDefault(f => f.Name == name);
+
+ return Optional.OfNullable(definition);
+ }
+
+ public Optional LookupStruct(string @namespace, string name)
+ {
+ var definition = _syntaxTrees
+ .Where(c => c.Namespace == @namespace)
+ .SelectMany(c => c.Definitions)
+ .OfType()
+ .SingleOrDefault(f => f.Name == name);
+
+ return Optional.OfNullable(definition);
+ }
+
+ public IEnumerable GetStructs()
+ {
+ return _syntaxTrees
+ .SelectMany(c => c.Definitions)
+ .OfType();
+ }
+}
+
+public class BoundDefinitionTable
+{
+ private readonly IEnumerable _syntaxTrees;
+
+ public BoundDefinitionTable(IEnumerable syntaxTrees)
+ {
+ _syntaxTrees = syntaxTrees;
+ }
+
+ public Optional LookupFunc(string @namespace, string name)
+ {
+ var definition = _syntaxTrees
+ .Where(c => c.Namespace == @namespace)
+ .SelectMany(c => c.Definitions)
+ .OfType()
+ .SingleOrDefault(f => f.Name == name);
+
+ return Optional.OfNullable(definition);
+ }
+
+ public Optional LookupStruct(string @namespace, string name)
+ {
+ var definition = _syntaxTrees
+ .Where(c => c.Namespace == @namespace)
+ .SelectMany(c => c.Definitions)
+ .OfType()
+ .SingleOrDefault(f => f.Name == name);
+
+ return Optional.OfNullable(definition);
+ }
+
+ public IEnumerable GetStructs()
+ {
+ return _syntaxTrees
+ .SelectMany(c => c.Definitions)
+ .OfType();
+ }
+}
\ No newline at end of file
diff --git a/src/compilation/Syntax/Diagnostics/ConsoleColors.cs b/src/compilation/Syntax/Diagnostics/ConsoleColors.cs
new file mode 100644
index 0000000..887a2fa
--- /dev/null
+++ b/src/compilation/Syntax/Diagnostics/ConsoleColors.cs
@@ -0,0 +1,177 @@
+using System.Text;
+using Syntax.Tokenization;
+
+namespace Syntax.Diagnostics;
+
+public static class ConsoleColors
+{
+ public const string Reset = "\e[0m";
+ public const string Bold = "\e[1m";
+ public const string Faint = "\e[2m";
+ public const string Italic = "\e[3m";
+ public const string Underline = "\e[4m";
+ public const string SlowBlink = "\e[5m";
+ public const string RapidBlink = "\e[6m";
+ public const string SwapBgAndFg = "\e[7m";
+ public const string Conceal = "\e[8m";
+ public const string CrossedOut = "\e[9m";
+
+ public const string DefaultFont = "\e[10m";
+ public const string AltFont1 = "\e[11m";
+ public const string AltFont2 = "\e[12m";
+ public const string AltFont3 = "\e[13m";
+ public const string AltFont4 = "\e[14m";
+ public const string AltFont5 = "\e[15m";
+ public const string AltFont6 = "\e[16m";
+ public const string AltFont7 = "\e[17m";
+ public const string AltFont8 = "\e[18m";
+ public const string AltFont9 = "\e[19m";
+
+ public const string Black = "\e[30m";
+ public const string Red = "\e[31m";
+ public const string Green = "\e[32m";
+ public const string Yellow = "\e[33m";
+ public const string Blue = "\e[34m";
+ public const string Magenta = "\e[35m";
+ public const string Cyan = "\e[36m";
+ public const string White = "\e[37m";
+
+ public const string BrightBlack = "\e[90m";
+ public const string BrightRed = "\e[91m";
+ public const string BrightGreen = "\e[92m";
+ public const string BrightYellow = "\e[93m";
+ public const string BrightBlue = "\e[94m";
+ public const string BrightMagenta = "\e[95m";
+ public const string BrightCyan = "\e[96m";
+ public const string BrightWhite = "\e[97m";
+
+ private static bool IsColorSupported()
+ {
+ var term = Environment.GetEnvironmentVariable("TERM");
+ var colorTerm = Environment.GetEnvironmentVariable("COLORTERM");
+ return !string.IsNullOrEmpty(term) || !string.IsNullOrEmpty(colorTerm) || !Console.IsOutputRedirected;
+ }
+
+ public static string Colorize(string text, string color)
+ {
+ return IsColorSupported() ? $"{color}{text}{Reset}" : text;
+ }
+
+ private static string GetTokenColor(Token token)
+ {
+ switch (token)
+ {
+ case DocumentationToken:
+ return Faint;
+ case IdentifierToken:
+ return White;
+ case LiteralToken literal:
+ return literal.Kind switch
+ {
+ LiteralKind.String => Green,
+ LiteralKind.Integer or LiteralKind.Float => BrightBlue,
+ LiteralKind.Bool => Blue,
+ _ => White
+ };
+ case ModifierToken:
+ return White;
+ case SymbolToken symbol:
+ switch (symbol.Symbol)
+ {
+ case Symbol.If:
+ case Symbol.Else:
+ case Symbol.While:
+ case Symbol.Break:
+ case Symbol.Continue:
+ case Symbol.Return:
+ return Magenta;
+ case Symbol.Func:
+ case Symbol.Struct:
+ case Symbol.Namespace:
+ case Symbol.Let:
+ case Symbol.Alloc:
+ return Blue;
+ case Symbol.Assign:
+ case Symbol.Bang:
+ case Symbol.Equal:
+ case Symbol.NotEqual:
+ case Symbol.LessThan:
+ case Symbol.LessThanOrEqual:
+ case Symbol.GreaterThan:
+ case Symbol.GreaterThanOrEqual:
+ case Symbol.Plus:
+ case Symbol.Minus:
+ case Symbol.Star:
+ case Symbol.ForwardSlash:
+ case Symbol.Caret:
+ case Symbol.Ampersand:
+ return White;
+ case Symbol.Semicolon:
+ case Symbol.Colon:
+ case Symbol.Comma:
+ case Symbol.Period:
+ case Symbol.DoubleColon:
+ return Faint;
+ case Symbol.OpenParen:
+ case Symbol.CloseParen:
+ case Symbol.OpenBrace:
+ case Symbol.CloseBrace:
+ case Symbol.OpenBracket:
+ case Symbol.CloseBracket:
+ return Yellow;
+ default:
+ return White;
+ }
+ default:
+ return White;
+ }
+ }
+
+ public static string ColorizeSource(string source)
+ {
+ var sourceText = new SourceText(string.Empty, source);
+ var tokens = Tokenizer.Tokenize(sourceText, out _);
+ var result = new StringBuilder();
+ var lastCharIndex = 0;
+
+ foreach (var token in tokens)
+ {
+ var tokenStartIndex = GetCharacterIndex(sourceText, token.Span.Start);
+ var tokenEndIndex = GetCharacterIndex(sourceText, token.Span.End);
+
+ if (tokenStartIndex > lastCharIndex)
+ {
+ var between = sourceText.Content.Substring(lastCharIndex, tokenStartIndex - lastCharIndex);
+ result.Append(between);
+ }
+
+ var tokenText = sourceText.Content.Substring(tokenStartIndex, tokenEndIndex - tokenStartIndex);
+
+ result.Append(Colorize(tokenText, GetTokenColor(token)));
+ lastCharIndex = tokenEndIndex;
+ }
+
+ if (lastCharIndex < sourceText.Content.Length)
+ {
+ var remaining = sourceText.Content[lastCharIndex..];
+ result.Append(Colorize(remaining, Faint));
+ }
+
+ return result.ToString();
+ }
+
+ private static int GetCharacterIndex(SourceText sourceText, SourceLocation location)
+ {
+ var lines = sourceText.Content.Split('\n');
+ var index = 0;
+
+ for (var i = 0; i < location.Line - 1 && i < lines.Length; i++)
+ {
+ index += lines[i].Length + 1;
+ }
+
+ index += location.Column - 1;
+
+ return Math.Min(index, sourceText.Content.Length);
+ }
+}
\ No newline at end of file
diff --git a/src/compilation/Syntax/Diagnostics/Diagnostic.cs b/src/compilation/Syntax/Diagnostics/Diagnostic.cs
new file mode 100644
index 0000000..4ba1ee3
--- /dev/null
+++ b/src/compilation/Syntax/Diagnostics/Diagnostic.cs
@@ -0,0 +1,216 @@
+using System.Text;
+using Syntax.Parsing;
+using Syntax.Parsing.Node;
+using Syntax.Tokenization;
+
+namespace Syntax.Diagnostics;
+
+public class Diagnostic
+{
+ public class DiagnosticBuilder
+ {
+ private readonly DiagnosticSeverity _severity;
+ private readonly string _message;
+ private string? _help;
+ private SourceSpan? _sourceSpan;
+
+ public DiagnosticBuilder(DiagnosticSeverity severity, string message)
+ {
+ _severity = severity;
+ _message = message;
+ }
+
+ public DiagnosticBuilder At(Token token)
+ {
+ _sourceSpan = token.Span;
+ return this;
+ }
+
+ public DiagnosticBuilder At(Node node)
+ {
+ _sourceSpan = SourceSpan.Merge(node.Tokens.Select(t => t.Span));
+ return this;
+ }
+
+ public DiagnosticBuilder At(SourceSpan span)
+ {
+ _sourceSpan = span;
+ return this;
+ }
+
+ public DiagnosticBuilder WithHelp(string help)
+ {
+ _help = help;
+ return this;
+ }
+
+ public Diagnostic Build() => new(_severity, _message, _sourceSpan, _help);
+ }
+
+ public static DiagnosticBuilder Error(string message) => new(DiagnosticSeverity.Error, message);
+ public static DiagnosticBuilder Warning(string message) => new(DiagnosticSeverity.Warning, message);
+ public static DiagnosticBuilder Info(string message) => new(DiagnosticSeverity.Info, message);
+
+ public DiagnosticSeverity Severity { get; }
+ public string Message { get; }
+ public SourceSpan? Span { get; }
+ public string? Help { get; }
+
+ private Diagnostic(DiagnosticSeverity severity, string message, SourceSpan? span, string? help)
+ {
+ Severity = severity;
+ Message = message;
+ Span = span;
+ Help = help;
+ }
+
+ public string FormatANSI()
+ {
+ var sb = new StringBuilder();
+
+ var severityText = GetSeverityText(Severity);
+ sb.Append(severityText);
+
+ if (Span.HasValue)
+ {
+ var locationText = $" at {Span.Value.Text.Path}:{Span}";
+ sb.Append(ConsoleColors.Colorize(locationText, ConsoleColors.Faint));
+ }
+
+ sb.Append(": ");
+ sb.Append(ConsoleColors.Colorize(Message, ConsoleColors.BrightWhite));
+
+ if (Span.HasValue)
+ {
+ sb.AppendLine();
+ AppendSourceContext(sb, Span.Value, Severity);
+ }
+
+ if (!string.IsNullOrEmpty(Help))
+ {
+ sb.AppendLine();
+ sb.Append(ConsoleColors.Colorize($"help: {Help}", ConsoleColors.Cyan));
+ }
+
+ return sb.ToString();
+ }
+
+ private static string GetSeverityText(DiagnosticSeverity severity)
+ {
+ return severity switch
+ {
+ DiagnosticSeverity.Error => ConsoleColors.Colorize("error", ConsoleColors.Bold + ConsoleColors.Red),
+ DiagnosticSeverity.Warning => ConsoleColors.Colorize("warning", ConsoleColors.Bold + ConsoleColors.Yellow),
+ DiagnosticSeverity.Info => ConsoleColors.Colorize("info", ConsoleColors.Bold + ConsoleColors.Blue),
+ _ => throw new ArgumentOutOfRangeException(nameof(severity), severity, "Unknown diagnostic severity")
+ };
+ }
+
+ private static void AppendSourceContext(StringBuilder sb, SourceSpan span, DiagnosticSeverity severity)
+ {
+ var lines = span.Text.Content.Split('\n');
+ var startLine = span.Start.Line;
+ var endLine = span.End.Line;
+
+ const int contextLines = 3;
+
+ var lineNumWidth = Math.Min(endLine + contextLines, lines.Length).ToString().Length;
+
+ var contextStart = Math.Max(1, startLine - contextLines);
+ var contextEnd = Math.Min(lines.Length, endLine + contextLines);
+
+ var contextWidth = 0;
+ for (var i = contextStart; i <= contextEnd; i++)
+ {
+ if (lines[i - 1].Length > contextWidth)
+ {
+ contextWidth = lines[i - 1].Length;
+ }
+ }
+
+ sb.AppendLine(ConsoleColors.Colorize('╭' + new string('─', lineNumWidth + 2) + '┬' + new string('─', contextWidth + 2) + '╮', ConsoleColors.Faint));
+
+ for (var lineNum = contextStart; lineNum < startLine; lineNum++)
+ {
+ AppendContextLine(sb, lineNum, lines[lineNum - 1], lineNumWidth, contextWidth);
+ }
+
+ for (var lineNum = startLine; lineNum <= endLine; lineNum++)
+ {
+ AppendContextLine(sb, lineNum, lines[lineNum - 1], lineNumWidth, contextWidth);
+ AppendErrorIndicators(sb, span, lineNum, lines[lineNum - 1], lineNumWidth, contextWidth, severity);
+ }
+
+ for (var lineNum = endLine + 1; lineNum <= contextEnd; lineNum++)
+ {
+ AppendContextLine(sb, lineNum, lines[lineNum - 1], lineNumWidth, contextWidth);
+ }
+
+ sb.Append(ConsoleColors.Colorize('╰' + new string('─', lineNumWidth + 2) + '┴' + new string('─', contextWidth + 2) + '╯', ConsoleColors.Faint));
+ }
+
+ private static void AppendContextLine(StringBuilder sb, int lineNum, string line, int lineNumWidth, int contextWidth)
+ {
+ sb.Append(ConsoleColors.Colorize('│' + " ", ConsoleColors.Faint));
+ var lineNumStr = lineNum.ToString().PadLeft(lineNumWidth);
+ sb.Append(ConsoleColors.Colorize(lineNumStr, ConsoleColors.Faint));
+ sb.Append(ConsoleColors.Colorize(" │ ", ConsoleColors.Faint));
+ sb.Append(ConsoleColors.ColorizeSource(line.PadRight(contextWidth)));
+ sb.Append(ConsoleColors.Colorize(" " + '│', ConsoleColors.Faint));
+ sb.AppendLine();
+ }
+
+ private static void AppendErrorIndicators(StringBuilder sb, SourceSpan span, int lineNum, string line, int lineNumWidth, int contextWidth, DiagnosticSeverity severity)
+ {
+ var color = severity switch
+ {
+ DiagnosticSeverity.Info => ConsoleColors.Blue,
+ DiagnosticSeverity.Warning => ConsoleColors.Yellow,
+ DiagnosticSeverity.Error => ConsoleColors.Red,
+ _ => throw new ArgumentOutOfRangeException(nameof(severity), severity, null)
+ };
+
+ sb.Append(ConsoleColors.Colorize('│' + " ", ConsoleColors.Faint));
+ sb.Append(new string(' ', lineNumWidth));
+ sb.Append(ConsoleColors.Colorize(" │ ", ConsoleColors.Faint));
+ var indicators = GetIndicatorsForLine(span, lineNum, line);
+ sb.Append(ConsoleColors.Colorize(indicators.PadRight(contextWidth), color));
+ sb.Append(ConsoleColors.Colorize(" " + '│', ConsoleColors.Faint));
+ sb.AppendLine();
+ }
+
+ private static string GetIndicatorsForLine(SourceSpan span, int lineNum, string line)
+ {
+ const char indicator = '^';
+
+ if (lineNum == span.Start.Line && lineNum == span.End.Line)
+ {
+ var startCol = Math.Max(0, span.Start.Column - 1);
+ var endCol = Math.Min(line.Length, span.End.Column - 1);
+ var length = Math.Max(1, endCol - startCol);
+
+ return new string(' ', startCol) + new string(indicator, length);
+ }
+
+ if (lineNum == span.Start.Line)
+ {
+ var startCol = Math.Max(0, span.Start.Column - 1);
+ return new string(' ', startCol) + new string(indicator, Math.Max(0, line.Length - startCol));
+ }
+
+ if (lineNum == span.End.Line)
+ {
+ var endCol = Math.Min(line.Length, span.End.Column - 1);
+ return new string(indicator, Math.Max(0, endCol));
+ }
+
+ return new string(indicator, line.Length);
+ }
+}
+
+public enum DiagnosticSeverity
+{
+ Info,
+ Warning,
+ Error
+}
\ No newline at end of file
diff --git a/src/compilation/Syntax/Parsing/Node/Definition.cs b/src/compilation/Syntax/Parsing/Node/Definition.cs
new file mode 100644
index 0000000..aec8f54
--- /dev/null
+++ b/src/compilation/Syntax/Parsing/Node/Definition.cs
@@ -0,0 +1,15 @@
+using Common;
+using Syntax.Tokenization;
+using Syntax.Typing;
+
+namespace Syntax.Parsing.Node;
+
+public abstract record DefinitionNode(IEnumerable Tokens, Optional Documentation, string Namespace) : Node(Tokens);
+
+public record FuncParameter(string Name, NubType Type);
+public abstract record FuncDefinition(IEnumerable Tokens, Optional Documentation, string Namespace, string Name, List Parameters, NubType ReturnType) : DefinitionNode(Tokens, Documentation, Namespace);
+public record LocalFuncDefinitionNode(IEnumerable Tokens, Optional Documentation, string Namespace, string Name, List Parameters, BlockNode Body, NubType ReturnType, bool Exported) : FuncDefinition(Tokens, Documentation, Namespace, Name, Parameters, ReturnType);
+public record ExternFuncDefinitionNode(IEnumerable Tokens, Optional Documentation, string Namespace, string Name, string CallName, List Parameters, NubType ReturnType) : FuncDefinition(Tokens, Documentation, Namespace, Name, Parameters, ReturnType);
+
+public record StructField(string Name, NubType Type, Optional Value);
+public record StructDefinitionNode(IEnumerable Tokens, Optional Documentation, string Namespace, string Name, List Fields) : DefinitionNode(Tokens, Documentation, Namespace);
diff --git a/src/compilation/Syntax/Parsing/Node/Expression.cs b/src/compilation/Syntax/Parsing/Node/Expression.cs
new file mode 100644
index 0000000..a71316b
--- /dev/null
+++ b/src/compilation/Syntax/Parsing/Node/Expression.cs
@@ -0,0 +1,44 @@
+using Common;
+using Syntax.Tokenization;
+using Syntax.Typing;
+
+namespace Syntax.Parsing.Node;
+
+public abstract record ExpressionNode(IEnumerable Tokens) : Node(Tokens);
+public abstract record LValueNode(IEnumerable Tokens) : ExpressionNode(Tokens);
+
+public record BinaryExpressionNode(IEnumerable Tokens, ExpressionNode Left, BinaryExpressionOperator Operator, ExpressionNode Right) : ExpressionNode(Tokens);
+
+public enum BinaryExpressionOperator
+{
+ Equal,
+ NotEqual,
+ GreaterThan,
+ GreaterThanOrEqual,
+ LessThan,
+ LessThanOrEqual,
+ Plus,
+ Minus,
+ Multiply,
+ Divide
+}
+
+public record UnaryExpressionNode(IEnumerable Tokens, UnaryExpressionOperator Operator, ExpressionNode Operand) : ExpressionNode(Tokens);
+
+public enum UnaryExpressionOperator
+{
+ Negate,
+ Invert
+}
+
+public record FuncCallNode(IEnumerable Tokens, ExpressionNode Expression, List Parameters) : ExpressionNode(Tokens);
+public record IdentifierNode(IEnumerable Tokens, Optional Namespace, string Name) : LValueNode(Tokens);
+public record ArrayInitializerNode(IEnumerable Tokens, ExpressionNode Capacity, NubType ElementType) : ExpressionNode(Tokens);
+public record ArrayIndexAccessNode(IEnumerable Tokens, ExpressionNode Array, ExpressionNode Index) : LValueNode(Tokens);
+public record AnonymousFuncNode(IEnumerable Tokens, List Parameters, BlockNode Body, NubType ReturnType) : ExpressionNode(Tokens);
+public record AddressOfNode(IEnumerable Tokens, LValueNode Expression) : ExpressionNode(Tokens);
+public record FixedArrayInitializerNode(IEnumerable Tokens, NubType ElementType, int Capacity) : ExpressionNode(Tokens);
+public record LiteralNode(IEnumerable Tokens, string Literal, LiteralKind Kind) : ExpressionNode(Tokens);
+public record MemberAccessNode(IEnumerable Tokens, ExpressionNode Expression, string Member) : ExpressionNode(Tokens);
+public record StructInitializerNode(IEnumerable Tokens, NubStructType StructType, Dictionary Initializers) : ExpressionNode(Tokens);
+public record DereferenceNode(IEnumerable Tokens, ExpressionNode Expression) : LValueNode(Tokens);
diff --git a/src/compilation/Syntax/Parsing/Node/Node.cs b/src/compilation/Syntax/Parsing/Node/Node.cs
new file mode 100644
index 0000000..5d4357e
--- /dev/null
+++ b/src/compilation/Syntax/Parsing/Node/Node.cs
@@ -0,0 +1,6 @@
+using Syntax.Tokenization;
+
+namespace Syntax.Parsing.Node;
+
+public abstract record Node(IEnumerable Tokens);
+public record BlockNode(IEnumerable Tokens, List Statements) : Node(Tokens);
diff --git a/src/compilation/Syntax/Parsing/Node/Statement.cs b/src/compilation/Syntax/Parsing/Node/Statement.cs
new file mode 100644
index 0000000..db506de
--- /dev/null
+++ b/src/compilation/Syntax/Parsing/Node/Statement.cs
@@ -0,0 +1,18 @@
+using Common;
+using Syntax.Tokenization;
+using Syntax.Typing;
+
+namespace Syntax.Parsing.Node;
+
+public record StatementNode(IEnumerable Tokens) : Node(Tokens);
+public record StatementExpressionNode(IEnumerable Tokens, ExpressionNode Expression) : StatementNode(Tokens);
+public record ReturnNode(IEnumerable Tokens, Optional Value) : StatementNode(Tokens);
+public record MemberAssignmentNode(IEnumerable Tokens, MemberAccessNode MemberAccess, ExpressionNode Value) : StatementNode(Tokens);
+public record IfNode(IEnumerable Tokens, ExpressionNode Condition, BlockNode Body, Optional> Else) : StatementNode(Tokens);
+public record DereferenceAssignmentNode(IEnumerable Tokens, DereferenceNode Dereference, ExpressionNode Value) : StatementNode(Tokens);
+public record VariableAssignmentNode(IEnumerable Tokens, IdentifierNode Identifier, ExpressionNode Value) : StatementNode(Tokens);
+public record VariableDeclarationNode(IEnumerable Tokens, string Name, NubType Type) : StatementNode(Tokens);
+public record ContinueNode(IEnumerable Tokens) : StatementNode(Tokens);
+public record BreakNode(IEnumerable Tokens) : StatementNode(Tokens);
+public record ArrayIndexAssignmentNode(IEnumerable Tokens, ArrayIndexAccessNode ArrayIndexAccess, ExpressionNode Value) : StatementNode(Tokens);
+public record WhileNode(IEnumerable Tokens, ExpressionNode Condition, BlockNode Body) : StatementNode(Tokens);
diff --git a/src/compilation/Syntax/Parsing/Parser.cs b/src/compilation/Syntax/Parsing/Parser.cs
new file mode 100644
index 0000000..546310f
--- /dev/null
+++ b/src/compilation/Syntax/Parsing/Parser.cs
@@ -0,0 +1,939 @@
+using System.Diagnostics;
+using System.Diagnostics.CodeAnalysis;
+using Common;
+using Syntax.Diagnostics;
+using Syntax.Parsing.Node;
+using Syntax.Tokenization;
+using Syntax.Typing;
+
+namespace Syntax.Parsing;
+
+public static class Parser
+{
+ private static string _namespace = null!;
+ private static List _diagnostics = [];
+ private static IEnumerable _tokens = [];
+ private static int _index;
+
+ public static SyntaxTree? ParseFile(IEnumerable tokens, string filePath, out IEnumerable diagnostics)
+ {
+ _tokens = tokens;
+ _namespace = null!;
+ _diagnostics = [];
+ _index = 0;
+
+ try
+ {
+ ExpectSymbol(Symbol.Namespace);
+ var @namespace = ExpectIdentifier();
+ _namespace = @namespace.Value;
+ }
+ catch (ParseException ex)
+ {
+ _diagnostics.Add(ex.Diagnostic);
+ diagnostics = _diagnostics;
+ return null;
+ }
+
+ try
+ {
+ List definitions = [];
+
+ while (Peek().HasValue)
+ {
+ var definition = ParseDefinition();
+ definitions.Add(definition);
+ }
+
+ diagnostics = _diagnostics;
+ return new SyntaxTree(filePath, _namespace, definitions);
+ }
+ catch (ParseException ex)
+ {
+ _diagnostics.Add(ex.Diagnostic);
+ RecoverToNextDefinition();
+ }
+
+ diagnostics = _diagnostics;
+ return null;
+ }
+
+ private static DefinitionNode ParseDefinition()
+ {
+ var startIndex = _index;
+ List modifiers = [];
+
+ List documentationParts = [];
+ while (_index < _tokens.Count() && _tokens.ElementAt(_index) is DocumentationToken commentToken)
+ {
+ documentationParts.Add(commentToken.Documentation);
+ _index++;
+ }
+
+ var documentation = documentationParts.Count == 0 ? null : string.Join('\n', documentationParts);
+
+ while (TryExpectModifier(out var modifier))
+ {
+ modifiers.Add(modifier);
+ }
+
+ var keyword = ExpectSymbol();
+ return keyword.Symbol switch
+ {
+ Symbol.Func => ParseFuncDefinition(startIndex, modifiers, Optional.OfNullable(documentation)),
+ Symbol.Struct => ParseStruct(startIndex, modifiers, Optional.OfNullable(documentation)),
+ _ => throw new ParseException(Diagnostic
+ .Error($"Expected 'func' or 'struct', but found '{keyword.Symbol}'")
+ .WithHelp("Valid definition keywords are 'func' and 'struct'")
+ .At(keyword)
+ .Build())
+ };
+ }
+
+ private static DefinitionNode ParseFuncDefinition(int startIndex, List modifiers, Optional documentation)
+ {
+ var name = ExpectIdentifier();
+ List parameters = [];
+
+ ExpectSymbol(Symbol.OpenParen);
+
+ while (!TryExpectSymbol(Symbol.CloseParen))
+ {
+ parameters.Add(ParseFuncParameter());
+
+ if (!TryExpectSymbol(Symbol.Comma) && Peek().TryGetValue(out var token) && token is not SymbolToken { Symbol: Symbol.CloseParen })
+ {
+ _diagnostics.Add(Diagnostic
+ .Warning("Missing comma between function parameters")
+ .WithHelp("Add a ',' to separate parameters")
+ .At(token)
+ .Build());
+ }
+ }
+
+ var returnType = TryExpectSymbol(Symbol.Colon) ? ParseType() : new NubVoidType();
+
+ var isExtern = modifiers.RemoveAll(x => x.Modifier == Modifier.Extern) > 0;
+ if (isExtern)
+ {
+ if (modifiers.Count != 0)
+ {
+ throw new ParseException(Diagnostic
+ .Error($"Invalid modifier for extern function: {modifiers[0].Modifier}")
+ .WithHelp($"Extern functions cannot use the '{modifiers[0].Modifier}' modifier")
+ .At(modifiers[0])
+ .Build());
+ }
+
+ var callName = name.Value;
+
+ if (TryExpectSymbol(Symbol.Calls))
+ {
+ callName = ExpectIdentifier().Value;
+ }
+
+ return new ExternFuncDefinitionNode(GetTokensForNode(startIndex), documentation, _namespace, name.Value, callName, parameters, returnType);
+ }
+
+ var body = ParseBlock();
+ var exported = modifiers.RemoveAll(x => x.Modifier == Modifier.Export) > 0;
+
+ if (modifiers.Count != 0)
+ {
+ throw new ParseException(Diagnostic
+ .Error($"Invalid modifiers for function: {modifiers[0].Modifier}")
+ .WithHelp($"Functions cannot use the '{modifiers[0].Modifier}' modifier")
+ .At(modifiers[0])
+ .Build());
+ }
+
+ return new LocalFuncDefinitionNode(GetTokensForNode(startIndex), documentation, _namespace, name.Value, parameters, body, returnType, exported);
+ }
+
+ private static StructDefinitionNode ParseStruct(int startIndex, List _, Optional documentation)
+ {
+ var name = ExpectIdentifier().Value;
+
+ ExpectSymbol(Symbol.OpenBrace);
+
+ List variables = [];
+
+ while (!TryExpectSymbol(Symbol.CloseBrace))
+ {
+ var variableName = ExpectIdentifier().Value;
+ ExpectSymbol(Symbol.Colon);
+ var variableType = ParseType();
+
+ var variableValue = Optional.Empty();
+
+ if (TryExpectSymbol(Symbol.Assign))
+ {
+ variableValue = ParseExpression();
+ }
+
+ variables.Add(new StructField(variableName, variableType, variableValue));
+ }
+
+ return new StructDefinitionNode(GetTokensForNode(startIndex), documentation, _namespace, name, variables);
+ }
+
+ private static FuncParameter ParseFuncParameter()
+ {
+ var name = ExpectIdentifier();
+ ExpectSymbol(Symbol.Colon);
+ var type = ParseType();
+
+ return new FuncParameter(name.Value, type);
+ }
+
+ private static StatementNode ParseStatement()
+ {
+ var startIndex = _index;
+ if (!Peek().TryGetValue(out var token))
+ {
+ throw new ParseException(Diagnostic
+ .Error("Unexpected end of file while parsing statement")
+ .At(_tokens.Last())
+ .Build());
+ }
+
+ if (token is SymbolToken symbol)
+ {
+ switch (symbol.Symbol)
+ {
+ case Symbol.Return:
+ return ParseReturn(startIndex);
+ case Symbol.If:
+ return ParseIf(startIndex);
+ case Symbol.While:
+ return ParseWhile(startIndex);
+ case Symbol.Let:
+ return ParseVariableDeclaration(startIndex);
+ case Symbol.Break:
+ return ParseBreak(startIndex);
+ case Symbol.Continue:
+ return ParseContinue(startIndex);
+ }
+ }
+
+ return ParseStatementExpression(startIndex);
+ }
+
+ private static StatementNode ParseStatementExpression(int startIndex)
+ {
+ var expr = ParseExpression();
+
+ if (Peek().TryGetValue(out var token))
+ {
+ if (token is SymbolToken symbol)
+ {
+ switch (symbol.Symbol)
+ {
+ case Symbol.Assign:
+ {
+ switch (expr)
+ {
+ case MemberAccessNode memberAccess:
+ {
+ Next();
+ var value = ParseExpression();
+ return new MemberAssignmentNode(GetTokensForNode(startIndex), memberAccess, value);
+ }
+ case ArrayIndexAccessNode arrayIndexAccess:
+ {
+ Next();
+ var value = ParseExpression();
+ return new ArrayIndexAssignmentNode(GetTokensForNode(startIndex), arrayIndexAccess, value);
+ }
+ case IdentifierNode identifier:
+ {
+ Next();
+ var value = ParseExpression();
+ return new VariableAssignmentNode(GetTokensForNode(startIndex), identifier, value);
+ }
+ case DereferenceNode dereference:
+ {
+ Next();
+ var value = ParseExpression();
+ return new DereferenceAssignmentNode(GetTokensForNode(startIndex), dereference, value);
+ }
+ }
+
+ break;
+ }
+ }
+ }
+ }
+
+ return new StatementExpressionNode(GetTokensForNode(startIndex), expr);
+ }
+
+ private static VariableDeclarationNode ParseVariableDeclaration(int startIndex)
+ {
+ ExpectSymbol(Symbol.Let);
+ var name = ExpectIdentifier().Value;
+ ExpectSymbol(Symbol.Colon);
+ var type = ParseType();
+
+ return new VariableDeclarationNode(GetTokensForNode(startIndex), name, type);
+ }
+
+ private static StatementNode ParseBreak(int startIndex)
+ {
+ ExpectSymbol(Symbol.Break);
+ Next();
+ return new BreakNode(GetTokensForNode(startIndex));
+ }
+
+ private static StatementNode ParseContinue(int startIndex)
+ {
+ ExpectSymbol(Symbol.Continue);
+ return new ContinueNode(GetTokensForNode(startIndex));
+ }
+
+ private static ReturnNode ParseReturn(int startIndex)
+ {
+ ExpectSymbol(Symbol.Return);
+ var value = Optional.Empty();
+ if (!TryExpectSymbol(Symbol.Semicolon))
+ {
+ value = ParseExpression();
+ }
+
+ return new ReturnNode(GetTokensForNode(startIndex), value);
+ }
+
+ private static IfNode ParseIf(int startIndex)
+ {
+ ExpectSymbol(Symbol.If);
+ var condition = ParseExpression();
+ var body = ParseBlock();
+
+ var elseStatement = Optional>.Empty();
+ if (TryExpectSymbol(Symbol.Else))
+ {
+ var newStartIndex = _index;
+ elseStatement = TryExpectSymbol(Symbol.If)
+ ? (Variant)ParseIf(newStartIndex)
+ : (Variant)ParseBlock();
+ }
+
+ return new IfNode(GetTokensForNode(startIndex), condition, body, elseStatement);
+ }
+
+ private static WhileNode ParseWhile(int startIndex)
+ {
+ ExpectSymbol(Symbol.While);
+ var condition = ParseExpression();
+ var body = ParseBlock();
+ return new WhileNode(GetTokensForNode(startIndex), condition, body);
+ }
+
+ private static ExpressionNode ParseExpression(int precedence = 0)
+ {
+ var startIndex = _index;
+ var left = ParsePrimaryExpression();
+
+ while (true)
+ {
+ var token = Peek();
+ if (!token.HasValue || token.Value is not SymbolToken symbolToken || !TryGetBinaryOperator(symbolToken.Symbol, out var op) ||
+ GetBinaryOperatorPrecedence(op.Value) < precedence)
+ {
+ break;
+ }
+
+ Next();
+ var right = ParseExpression(GetBinaryOperatorPrecedence(op.Value) + 1);
+
+ left = new BinaryExpressionNode(GetTokensForNode(startIndex), left, op.Value, right);
+ }
+
+ return left;
+ }
+
+ private static int GetBinaryOperatorPrecedence(BinaryExpressionOperator binaryExpressionOperator)
+ {
+ return binaryExpressionOperator switch
+ {
+ BinaryExpressionOperator.Multiply => 3,
+ BinaryExpressionOperator.Divide => 3,
+ BinaryExpressionOperator.Plus => 2,
+ BinaryExpressionOperator.Minus => 2,
+ BinaryExpressionOperator.GreaterThan => 1,
+ BinaryExpressionOperator.GreaterThanOrEqual => 1,
+ BinaryExpressionOperator.LessThan => 1,
+ BinaryExpressionOperator.LessThanOrEqual => 1,
+ BinaryExpressionOperator.Equal => 0,
+ BinaryExpressionOperator.NotEqual => 0,
+ _ => throw new ArgumentOutOfRangeException(nameof(binaryExpressionOperator), binaryExpressionOperator, null)
+ };
+ }
+
+ private static bool TryGetBinaryOperator(Symbol symbol, [NotNullWhen(true)] out BinaryExpressionOperator? binaryExpressionOperator)
+ {
+ switch (symbol)
+ {
+ case Symbol.Equal:
+ binaryExpressionOperator = BinaryExpressionOperator.Equal;
+ return true;
+ case Symbol.NotEqual:
+ binaryExpressionOperator = BinaryExpressionOperator.NotEqual;
+ return true;
+ case Symbol.LessThan:
+ binaryExpressionOperator = BinaryExpressionOperator.LessThan;
+ return true;
+ case Symbol.LessThanOrEqual:
+ binaryExpressionOperator = BinaryExpressionOperator.LessThanOrEqual;
+ return true;
+ case Symbol.GreaterThan:
+ binaryExpressionOperator = BinaryExpressionOperator.GreaterThan;
+ return true;
+ case Symbol.GreaterThanOrEqual:
+ binaryExpressionOperator = BinaryExpressionOperator.GreaterThanOrEqual;
+ return true;
+ case Symbol.Plus:
+ binaryExpressionOperator = BinaryExpressionOperator.Plus;
+ return true;
+ case Symbol.Minus:
+ binaryExpressionOperator = BinaryExpressionOperator.Minus;
+ return true;
+ case Symbol.Star:
+ binaryExpressionOperator = BinaryExpressionOperator.Multiply;
+ return true;
+ case Symbol.ForwardSlash:
+ binaryExpressionOperator = BinaryExpressionOperator.Divide;
+ return true;
+ default:
+ binaryExpressionOperator = null;
+ return false;
+ }
+ }
+
+ private static ExpressionNode ParsePrimaryExpression()
+ {
+ var startIndex = _index;
+ ExpressionNode expr;
+
+ var token = ExpectToken();
+ switch (token)
+ {
+ case LiteralToken literal:
+ {
+ expr = new LiteralNode(GetTokensForNode(startIndex), literal.Value, literal.Kind);
+ break;
+ }
+ case IdentifierToken identifier:
+ {
+ var @namespace = Optional.Empty();
+ var name = identifier.Value;
+ if (TryExpectSymbol(Symbol.DoubleColon))
+ {
+ @namespace = identifier.Value;
+ name = ExpectIdentifier().Value;
+ }
+
+ expr = new IdentifierNode(GetTokensForNode(startIndex), @namespace, name);
+ break;
+ }
+ case SymbolToken symbolToken:
+ {
+ switch (symbolToken.Symbol)
+ {
+ case Symbol.Func:
+ {
+ List parameters = [];
+ ExpectSymbol(Symbol.OpenParen);
+ while (!TryExpectSymbol(Symbol.CloseParen))
+ {
+ var parameter = ParseFuncParameter();
+ parameters.Add(parameter);
+ if (!TryExpectSymbol(Symbol.Comma) && Peek().TryGetValue(out var nextToken) && nextToken is not SymbolToken { Symbol: Symbol.CloseParen })
+ {
+ _diagnostics.Add(Diagnostic
+ .Warning("Missing comma between function arguments")
+ .WithHelp("Add a ',' to separate arguments")
+ .At(nextToken)
+ .Build());
+ }
+ }
+
+ var returnType = TryExpectSymbol(Symbol.Colon) ? ParseType() : new NubVoidType();
+
+ var body = ParseBlock();
+
+ expr = new AnonymousFuncNode(GetTokensForNode(startIndex), parameters, body, returnType);
+ break;
+ }
+ case Symbol.OpenParen:
+ {
+ var expression = ParseExpression();
+ ExpectSymbol(Symbol.CloseParen);
+ expr = expression;
+ break;
+ }
+ case Symbol.Ampersand:
+ {
+ var expression = ParsePrimaryExpression();
+ if (expression is not LValueNode lValue)
+ {
+ throw new ParseException(Diagnostic
+ .Error("& symbol can only be used on lvalues")
+ .At(expression)
+ .Build());
+ }
+
+ expr = new AddressOfNode(GetTokensForNode(startIndex), lValue);
+ break;
+ }
+ case Symbol.Minus:
+ {
+ var expression = ParsePrimaryExpression();
+ expr = new UnaryExpressionNode(GetTokensForNode(startIndex), UnaryExpressionOperator.Negate, expression);
+ break;
+ }
+ case Symbol.Bang:
+ {
+ var expression = ParsePrimaryExpression();
+ expr = new UnaryExpressionNode(GetTokensForNode(startIndex), UnaryExpressionOperator.Invert, expression);
+ break;
+ }
+ case Symbol.OpenBracket:
+ {
+ if (Peek().TryGetValue(out var capacityToken) && capacityToken is LiteralToken { Kind: LiteralKind.Integer } literalToken)
+ {
+ var capacity = int.Parse(literalToken.Value);
+ Next();
+ ExpectSymbol(Symbol.CloseBracket);
+ var elementType = ParseType();
+
+ if (capacity > 0)
+ {
+ expr = new FixedArrayInitializerNode(GetTokensForNode(startIndex), elementType, capacity);
+ }
+ else
+ {
+ throw new ParseException(Diagnostic
+ .Error("Fixed array size must be a positive integer")
+ .WithHelp("Use a positive integer literal for the array size")
+ .At(literalToken)
+ .Build());
+ }
+ }
+ else
+ {
+ var capacity = ParseExpression();
+ ExpectSymbol(Symbol.CloseBracket);
+ var type = ParseType();
+
+ expr = new ArrayInitializerNode(GetTokensForNode(startIndex), capacity, type);
+ }
+
+ break;
+ }
+ case Symbol.Alloc:
+ {
+ var type = ParseType();
+ Dictionary initializers = [];
+ ExpectSymbol(Symbol.OpenBrace);
+ while (!TryExpectSymbol(Symbol.CloseBrace))
+ {
+ var name = ExpectIdentifier().Value;
+ ExpectSymbol(Symbol.Assign);
+ var value = ParseExpression();
+ initializers.Add(name, value);
+ }
+
+ if (type is not NubStructType structType)
+ {
+ throw new ParseException(Diagnostic
+ .Error($"Cannot alloc type '{type}'")
+ .At(symbolToken)
+ .Build());
+ }
+
+ expr = new StructInitializerNode(GetTokensForNode(startIndex), structType, initializers);
+ break;
+ }
+ default:
+ {
+ throw new ParseException(Diagnostic
+ .Error($"Unexpected symbol '{symbolToken.Symbol}' in expression")
+ .WithHelp("Expected literal, identifier, or '(' to start expression")
+ .At(symbolToken)
+ .Build());
+ }
+ }
+
+ break;
+ }
+ default:
+ {
+ throw new ParseException(Diagnostic
+ .Error($"Unexpected token '{token.GetType().Name}' in expression")
+ .WithHelp("Expected literal, identifier, or parenthesized expression")
+ .At(token)
+ .Build());
+ }
+ }
+
+ return ParsePostfixOperators(startIndex, expr);
+ }
+
+ private static ExpressionNode ParsePostfixOperators(int startIndex, ExpressionNode expr)
+ {
+ while (true)
+ {
+ if (TryExpectSymbol(Symbol.Caret))
+ {
+ expr = new DereferenceNode(GetTokensForNode(startIndex), expr);
+ continue;
+ }
+
+ if (TryExpectSymbol(Symbol.Period))
+ {
+ var structMember = ExpectIdentifier().Value;
+ expr = new MemberAccessNode(GetTokensForNode(startIndex), expr, structMember);
+ continue;
+ }
+
+ if (TryExpectSymbol(Symbol.OpenBracket))
+ {
+ var index = ParseExpression();
+ ExpectSymbol(Symbol.CloseBracket);
+ expr = new ArrayIndexAccessNode(GetTokensForNode(startIndex), expr, index);
+ continue;
+ }
+
+ if (TryExpectSymbol(Symbol.OpenParen))
+ {
+ var parameters = new List();
+ while (!TryExpectSymbol(Symbol.CloseParen))
+ {
+ parameters.Add(ParseExpression());
+ if (!TryExpectSymbol(Symbol.Comma) && Peek().TryGetValue(out var nextToken) && nextToken is not SymbolToken { Symbol: Symbol.CloseParen })
+ {
+ _diagnostics.Add(Diagnostic
+ .Warning("Missing comma between function arguments")
+ .WithHelp("Add a ',' to separate arguments")
+ .At(nextToken)
+ .Build());
+ }
+ }
+
+ expr = new FuncCallNode(GetTokensForNode(startIndex), expr, parameters);
+ continue;
+ }
+
+ break;
+ }
+
+ return expr;
+ }
+
+ private static BlockNode ParseBlock()
+ {
+ var startIndex = _index;
+ ExpectSymbol(Symbol.OpenBrace);
+ List statements = [];
+ while (Peek().HasValue && !TryExpectSymbol(Symbol.CloseBrace))
+ {
+ try
+ {
+ statements.Add(ParseStatement());
+ }
+ catch (ParseException ex)
+ {
+ _diagnostics.Add(ex.Diagnostic);
+ RecoverToNextStatement();
+ }
+ }
+
+ return new BlockNode(GetTokensForNode(startIndex), statements);
+ }
+
+ private static NubType ParseType()
+ {
+ if (TryExpectIdentifier(out var name))
+ {
+ if (name.Value == "any")
+ {
+ return new NubAnyType();
+ }
+
+ if (name.Value == "void")
+ {
+ return new NubVoidType();
+ }
+
+ if (name.Value == "string")
+ {
+ return new NubStringType();
+ }
+
+ if (name.Value == "cstring")
+ {
+ return new NubCStringType();
+ }
+
+ if (NubPrimitiveType.TryParse(name.Value, out var primitiveTypeKind))
+ {
+ return new NubPrimitiveType(primitiveTypeKind.Value);
+ }
+
+ var @namespace = _namespace;
+ if (TryExpectSymbol(Symbol.DoubleColon))
+ {
+ @namespace = ExpectIdentifier().Value;
+ }
+
+ if (@namespace == null)
+ {
+ throw new ParseException(Diagnostic
+ .Error($"Struct '{name.Value}' does not belong to a namespace")
+ .WithHelp("Make sure you have specified a namespace at the top of the file")
+ .At(name)
+ .Build());
+ }
+
+ return new NubStructType(@namespace , name.Value);
+ }
+
+ if (TryExpectSymbol(Symbol.Caret))
+ {
+ var baseType = ParseType();
+ return new NubPointerType(baseType);
+ }
+
+ if (TryExpectSymbol(Symbol.Func))
+ {
+ ExpectSymbol(Symbol.OpenParen);
+ List parameters = [];
+ while (!TryExpectSymbol(Symbol.CloseParen))
+ {
+ var parameter = ParseType();
+ parameters.Add(parameter);
+ if (!TryExpectSymbol(Symbol.Comma) && Peek().TryGetValue(out var nextToken) && nextToken is not SymbolToken { Symbol: Symbol.CloseParen })
+ {
+ _diagnostics.Add(Diagnostic
+ .Warning("Missing comma between func type arguments")
+ .WithHelp("Add a ',' to separate arguments")
+ .At(nextToken)
+ .Build());
+ }
+ }
+
+ var returnType = TryExpectSymbol(Symbol.Colon) ? ParseType() : new NubVoidType();
+
+ return new NubFuncType(returnType, parameters);
+ }
+
+ if (TryExpectSymbol(Symbol.OpenBracket))
+ {
+ if (Peek().TryGetValue(out var token) && token is LiteralToken { Kind: LiteralKind.Integer, Value: var sizeValue })
+ {
+ Next();
+ ExpectSymbol(Symbol.CloseBracket);
+ var baseType = ParseType();
+
+ var size = int.Parse(sizeValue);
+
+ if (size > 0)
+ {
+ return new NubFixedArrayType(baseType, size);
+ }
+ else
+ {
+ throw new UnreachableException();
+ }
+ }
+ else
+ {
+ ExpectSymbol(Symbol.CloseBracket);
+ var baseType = ParseType();
+ return new NubArrayType(baseType);
+ }
+ }
+
+ if (!Peek().TryGetValue(out var peekToken))
+ {
+ throw new ParseException(Diagnostic
+ .Error("Unexpected end of file while parsing type")
+ .WithHelp("Expected a type name")
+ .At(_tokens.Last())
+ .Build());
+ }
+
+ throw new ParseException(Diagnostic
+ .Error("Invalid type Syntax")
+ .WithHelp("Expected type name, '^' for pointer, or '[]' for array")
+ .At(peekToken)
+ .Build());
+}
+
+ private static Token ExpectToken()
+ {
+ if (!Peek().TryGetValue(out var token))
+ {
+ throw new ParseException(Diagnostic
+ .Error("Unexpected end of file")
+ .WithHelp("Expected more tokens to complete the Syntax")
+ .At(_tokens.Last())
+ .Build());
+ }
+
+ Next();
+ return token;
+ }
+
+ private static SymbolToken ExpectSymbol()
+ {
+ var token = ExpectToken();
+ if (token is not SymbolToken symbol)
+ {
+ throw new ParseException(Diagnostic
+ .Error($"Expected symbol, but found {token.GetType().Name}")
+ .WithHelp("This position requires a symbol like '(', ')', '{', '}', etc.")
+ .At(token)
+ .Build());
+ }
+
+ return symbol;
+ }
+
+ private static void ExpectSymbol(Symbol expectedSymbol)
+ {
+ var token = ExpectSymbol();
+ if (token.Symbol != expectedSymbol)
+ {
+ throw new ParseException(Diagnostic
+ .Error($"Expected '{expectedSymbol}', but found '{token.Symbol}'")
+ .WithHelp($"Insert '{expectedSymbol}' here")
+ .At(token)
+ .Build());
+ }
+ }
+
+ private static bool TryExpectSymbol(Symbol symbol)
+ {
+ if (Peek() is { Value: SymbolToken symbolToken } && symbolToken.Symbol == symbol)
+ {
+ Next();
+ return true;
+ }
+
+ return false;
+ }
+
+ private static bool TryExpectModifier([NotNullWhen(true)] out ModifierToken? modifier)
+ {
+ if (Peek() is { Value: ModifierToken modifierToken })
+ {
+ modifier = modifierToken;
+ Next();
+ return true;
+ }
+
+ modifier = null;
+ return false;
+ }
+
+ private static bool TryExpectIdentifier([NotNullWhen(true)] out IdentifierToken? identifier)
+ {
+ if (Peek() is { Value: IdentifierToken identifierToken })
+ {
+ identifier = identifierToken;
+ Next();
+ return true;
+ }
+
+ identifier = null;
+ return false;
+ }
+
+ private static IdentifierToken ExpectIdentifier()
+ {
+ var token = ExpectToken();
+ if (token is not IdentifierToken identifier)
+ {
+ throw new ParseException(Diagnostic
+ .Error($"Expected identifier, but found {token.GetType().Name}")
+ .WithHelp("Provide a valid identifier name here")
+ .At(token)
+ .Build());
+ }
+
+ return identifier;
+ }
+
+ private static void RecoverToNextDefinition()
+ {
+ while (Peek().HasValue)
+ {
+ var token = Peek().Value;
+ if (token is SymbolToken { Symbol: Symbol.Func or Symbol.Struct } or ModifierToken)
+ {
+ break;
+ }
+
+ Next();
+ }
+ }
+
+ private static void RecoverToNextStatement()
+ {
+ while (Peek().TryGetValue(out var token))
+ {
+ if (token is SymbolToken { Symbol: Symbol.CloseBrace } or IdentifierToken or SymbolToken
+ {
+ Symbol: Symbol.Return or Symbol.If or Symbol.While or Symbol.Let or Symbol.Break or Symbol.Continue
+ })
+ {
+ break;
+ }
+
+ Next();
+ }
+ }
+
+ private static Optional Peek(int offset = 0)
+ {
+ var peekIndex = _index + offset;
+ while (peekIndex < _tokens.Count() && _tokens.ElementAt(peekIndex) is DocumentationToken)
+ {
+ peekIndex++;
+ }
+
+ if (peekIndex < _tokens.Count())
+ {
+ return _tokens.ElementAt(peekIndex);
+ }
+
+ return Optional.Empty();
+ }
+
+ private static void Next()
+ {
+ while (_index < _tokens.Count() && _tokens.ElementAt(_index) is DocumentationToken)
+ {
+ _index++;
+ }
+
+ _index++;
+ }
+
+ private static IEnumerable GetTokensForNode(int startIndex)
+ {
+ return _tokens.Skip(startIndex).Take(Math.Min(_index, _tokens.Count() - 1) - startIndex);
+ }
+}
+
+public class ParseException : Exception
+{
+ public Diagnostic Diagnostic { get; }
+
+ public ParseException(Diagnostic diagnostic) : base(diagnostic.Message)
+ {
+ Diagnostic = diagnostic;
+ }
+}
\ No newline at end of file
diff --git a/src/compilation/Syntax/Parsing/SyntaxTree.cs b/src/compilation/Syntax/Parsing/SyntaxTree.cs
new file mode 100644
index 0000000..914a9ed
--- /dev/null
+++ b/src/compilation/Syntax/Parsing/SyntaxTree.cs
@@ -0,0 +1,5 @@
+using Syntax.Parsing.Node;
+
+namespace Syntax.Parsing;
+
+public record SyntaxTree(string FilePath, string Namespace, List Definitions);
diff --git a/src/compilation/Syntax/Source.cs b/src/compilation/Syntax/Source.cs
new file mode 100644
index 0000000..bfeb6af
--- /dev/null
+++ b/src/compilation/Syntax/Source.cs
@@ -0,0 +1,274 @@
+using System.Diagnostics.CodeAnalysis;
+
+namespace Syntax;
+
+///
+/// Represents a location in source code with line and column information.
+/// Lines and columns are 1-based to match typical editor conventions.
+///
+public readonly struct SourceLocation : IEquatable, IComparable
+{
+ public SourceLocation(int line, int column)
+ {
+ if (line < 1)
+ {
+ throw new ArgumentOutOfRangeException(nameof(line), "Line must be >= 1");
+ }
+
+ if (column < 1)
+ {
+ throw new ArgumentOutOfRangeException(nameof(column), "Column must be >= 1");
+ }
+
+ Line = line;
+ Column = column;
+ }
+
+ public int Line { get; }
+ public int Column { get; }
+
+ public int CompareTo(SourceLocation other)
+ {
+ var lineComparison = Line.CompareTo(other.Line);
+ if (lineComparison == 0)
+ {
+ return Column.CompareTo(other.Column);
+ }
+
+ return lineComparison;
+ }
+
+ public override string ToString()
+ {
+ return $"{Line}:{Column}";
+ }
+
+ public bool Equals(SourceLocation other)
+ {
+ return Line == other.Line && Column == other.Column;
+ }
+
+ public override bool Equals([NotNullWhen(true)] object? obj)
+ {
+ return obj is SourceLocation other && Equals(other);
+ }
+
+ public override int GetHashCode()
+ {
+ return HashCode.Combine(Line, Column);
+ }
+
+ public static bool operator ==(SourceLocation left, SourceLocation right) => left.Equals(right);
+ public static bool operator !=(SourceLocation left, SourceLocation right) => !left.Equals(right);
+ public static bool operator <(SourceLocation left, SourceLocation right) => left.CompareTo(right) < 0;
+ public static bool operator >(SourceLocation left, SourceLocation right) => left.CompareTo(right) > 0;
+ public static bool operator <=(SourceLocation left, SourceLocation right) => left.CompareTo(right) <= 0;
+ public static bool operator >=(SourceLocation left, SourceLocation right) => left.CompareTo(right) >= 0;
+}
+
+///
+/// Represents source text with a name (typically filename) and content.
+/// Equality is based on both name and content for better semantics.
+///
+public struct SourceText : IEquatable
+{
+ private int _lines = -1;
+
+ public SourceText(string path, string content)
+ {
+ Path = path ?? throw new ArgumentNullException(nameof(path));
+ Content = content ?? throw new ArgumentNullException(nameof(content));
+ }
+
+ public string Path { get; }
+ public string Content { get; }
+
+ public int LineCount()
+ {
+ if (_lines == -1)
+ {
+ _lines = Content.Split('\n').Length + 1;
+ }
+
+ return _lines;
+ }
+
+ ///
+ /// Gets a specific line from the source text (1-based).
+ ///
+ public string GetLine(int lineNumber)
+ {
+ if (lineNumber < 1)
+ {
+ throw new ArgumentOutOfRangeException(nameof(lineNumber));
+ }
+
+ var lines = Content.Split('\n');
+ return lineNumber <= lines.Length ? lines[lineNumber - 1] : string.Empty;
+ }
+
+ public bool Equals(SourceText other)
+ {
+ return Path == other.Path && Content == other.Content;
+ }
+
+ public override bool Equals([NotNullWhen(true)] object? obj)
+ {
+ return obj is SourceText other && Equals(other);
+ }
+
+ public override int GetHashCode()
+ {
+ return HashCode.Combine(Path, Content);
+ }
+
+ public override string ToString()
+ {
+ return Path;
+ }
+
+ public static bool operator ==(SourceText left, SourceText right) => left.Equals(right);
+ public static bool operator !=(SourceText left, SourceText right) => !left.Equals(right);
+}
+
+///
+/// Represents a span of source code from a start to end location within a source text.
+///
+public readonly struct SourceSpan : IEquatable
+{
+ public SourceSpan(SourceText text, SourceLocation start, SourceLocation end)
+ {
+ if (start > end)
+ {
+ throw new ArgumentException("Start location cannot be after end location");
+ }
+
+ if (end.Line > text.LineCount() || end.Line == text.LineCount() && end.Column > text.GetLine(text.LineCount()).Length + 1)
+ {
+ throw new ArgumentException("End location cannot be after text end location");
+ }
+
+ Text = text;
+ Start = start;
+ End = end;
+ }
+
+ public SourceText Text { get; }
+ public SourceLocation Start { get; }
+ public SourceLocation End { get; }
+
+ ///
+ /// Gets whether this span represents a single point (start == end).
+ ///
+ public bool IsEmpty => Start == End;
+
+ ///
+ /// Gets whether this span is contained within a single line.
+ ///
+ public bool IsSingleLine => Start.Line == End.Line;
+
+ ///
+ /// Gets the text content covered by this span.
+ ///
+ public string GetText()
+ {
+ if (IsEmpty)
+ {
+ return string.Empty;
+ }
+
+ var lines = Text.Content.Split('\n');
+
+ if (IsSingleLine)
+ {
+ var line = lines[Start.Line - 1];
+ var startCol = Math.Min(Start.Column - 1, line.Length);
+ var endCol = Math.Min(End.Column - 1, line.Length);
+ return line.Substring(startCol, Math.Max(0, endCol - startCol));
+ }
+
+ var result = new List();
+ for (var i = Start.Line - 1; i < Math.Min(End.Line, lines.Length); i++)
+ {
+ var line = lines[i];
+ if (i == Start.Line - 1)
+ {
+ result.Add(line[Math.Min(Start.Column - 1, line.Length)..]);
+ }
+ else if (i == End.Line - 1)
+ {
+ result.Add(line[..Math.Min(End.Column - 1, line.Length)]);
+ }
+ else
+ {
+ result.Add(line);
+ }
+ }
+
+ return string.Join("\n", result);
+ }
+
+ ///
+ /// Merges multiple source spans from the same file into a single span.
+ /// The result spans from the earliest start to the latest end.
+ ///
+ public static SourceSpan Merge(IEnumerable spans)
+ {
+ var spanArray = spans.ToArray();
+ if (spanArray.Length == 0)
+ {
+ throw new ArgumentException("Cannot merge empty collection of spans", nameof(spans));
+ }
+
+ var firstText = spanArray[0].Text;
+ if (spanArray.Any(s => !s.Text.Equals(firstText)))
+ {
+ throw new ArgumentException("All spans must be from the same source text", nameof(spans));
+ }
+
+ var minStart = spanArray.Min(s => s.Start);
+ var maxEnd = spanArray.Max(s => s.End);
+
+ return new SourceSpan(firstText, minStart, maxEnd);
+ }
+
+ public override string ToString()
+ {
+ if (IsEmpty)
+ {
+ return $"{Start}";
+ }
+
+ if (IsSingleLine)
+ {
+ return Start.Column == End.Column ? $"{Start}" : $"{Start.Line}:{Start.Column}-{End.Column}";
+ }
+
+ return $"{Start}-{End}";
+ }
+
+ public bool Equals(SourceSpan other)
+ {
+ return Text.Equals(other.Text) && Start.Equals(other.Start) && End.Equals(other.End);
+ }
+
+ public override bool Equals([NotNullWhen(true)] object? obj)
+ {
+ return obj is SourceSpan other && Equals(other);
+ }
+
+ public override int GetHashCode()
+ {
+ return HashCode.Combine(Text, Start, End);
+ }
+
+ public static bool operator ==(SourceSpan left, SourceSpan right)
+ {
+ return left.Equals(right);
+ }
+
+ public static bool operator !=(SourceSpan left, SourceSpan right)
+ {
+ return !left.Equals(right);
+ }
+}
\ No newline at end of file
diff --git a/src/compilation/Syntax/Syntax.csproj b/src/compilation/Syntax/Syntax.csproj
new file mode 100644
index 0000000..31addee
--- /dev/null
+++ b/src/compilation/Syntax/Syntax.csproj
@@ -0,0 +1,14 @@
+
+
+
+ net9.0
+ enable
+ enable
+ true
+
+
+
+
+
+
+
diff --git a/src/compilation/Syntax/Tokenization/DocumentationToken.cs b/src/compilation/Syntax/Tokenization/DocumentationToken.cs
new file mode 100644
index 0000000..54a0e51
--- /dev/null
+++ b/src/compilation/Syntax/Tokenization/DocumentationToken.cs
@@ -0,0 +1,6 @@
+namespace Syntax.Tokenization;
+
+public class DocumentationToken(SourceSpan span, string documentation) : Token(span)
+{
+ public string Documentation { get; } = documentation;
+}
\ No newline at end of file
diff --git a/src/compilation/Syntax/Tokenization/IdentifierToken.cs b/src/compilation/Syntax/Tokenization/IdentifierToken.cs
new file mode 100644
index 0000000..7d8c9e7
--- /dev/null
+++ b/src/compilation/Syntax/Tokenization/IdentifierToken.cs
@@ -0,0 +1,6 @@
+namespace Syntax.Tokenization;
+
+public class IdentifierToken(SourceSpan span, string value) : Token(span)
+{
+ public string Value { get; } = value;
+}
\ No newline at end of file
diff --git a/src/compilation/Syntax/Tokenization/LiteralToken.cs b/src/compilation/Syntax/Tokenization/LiteralToken.cs
new file mode 100644
index 0000000..f16671e
--- /dev/null
+++ b/src/compilation/Syntax/Tokenization/LiteralToken.cs
@@ -0,0 +1,15 @@
+namespace Syntax.Tokenization;
+
+public class LiteralToken(SourceSpan span, LiteralKind kind, string value) : Token(span)
+{
+ public LiteralKind Kind { get; } = kind;
+ public string Value { get; } = value;
+}
+
+public enum LiteralKind
+{
+ Integer,
+ Float,
+ String,
+ Bool
+}
\ No newline at end of file
diff --git a/src/compilation/Syntax/Tokenization/ModifierToken.cs b/src/compilation/Syntax/Tokenization/ModifierToken.cs
new file mode 100644
index 0000000..5312667
--- /dev/null
+++ b/src/compilation/Syntax/Tokenization/ModifierToken.cs
@@ -0,0 +1,12 @@
+namespace Syntax.Tokenization;
+
+public class ModifierToken(SourceSpan span, Modifier modifier) : Token(span)
+{
+ public Modifier Modifier { get; } = modifier;
+}
+
+public enum Modifier
+{
+ Extern,
+ Export
+}
\ No newline at end of file
diff --git a/src/compilation/Syntax/Tokenization/SymbolToken.cs b/src/compilation/Syntax/Tokenization/SymbolToken.cs
new file mode 100644
index 0000000..af31ca0
--- /dev/null
+++ b/src/compilation/Syntax/Tokenization/SymbolToken.cs
@@ -0,0 +1,47 @@
+namespace Syntax.Tokenization;
+
+public class SymbolToken(SourceSpan span, Symbol symbol) : Token(span)
+{
+ public Symbol Symbol { get; } = symbol;
+}
+
+public enum Symbol
+{
+ Func,
+ Return,
+ If,
+ Else,
+ While,
+ Break,
+ Continue,
+ Semicolon,
+ Colon,
+ OpenParen,
+ CloseParen,
+ OpenBrace,
+ CloseBrace,
+ OpenBracket,
+ CloseBracket,
+ Comma,
+ Period,
+ Assign,
+ Bang,
+ Equal,
+ NotEqual,
+ LessThan,
+ LessThanOrEqual,
+ GreaterThan,
+ GreaterThanOrEqual,
+ Plus,
+ Minus,
+ Star,
+ ForwardSlash,
+ Struct,
+ Caret,
+ Ampersand,
+ DoubleColon,
+ Namespace,
+ Let,
+ Alloc,
+ Calls
+}
\ No newline at end of file
diff --git a/src/compilation/Syntax/Tokenization/Token.cs b/src/compilation/Syntax/Tokenization/Token.cs
new file mode 100644
index 0000000..89f3f47
--- /dev/null
+++ b/src/compilation/Syntax/Tokenization/Token.cs
@@ -0,0 +1,6 @@
+namespace Syntax.Tokenization;
+
+public abstract class Token(SourceSpan span)
+{
+ public SourceSpan Span { get; } = span;
+}
\ No newline at end of file
diff --git a/src/compilation/Syntax/Tokenization/Tokenizer.cs b/src/compilation/Syntax/Tokenization/Tokenizer.cs
new file mode 100644
index 0000000..5a3784d
--- /dev/null
+++ b/src/compilation/Syntax/Tokenization/Tokenizer.cs
@@ -0,0 +1,283 @@
+using Common;
+using Syntax.Diagnostics;
+
+namespace Syntax.Tokenization;
+
+public static class Tokenizer
+{
+ private static readonly Dictionary Keywords = new()
+ {
+ ["namespace"] = Symbol.Namespace,
+ ["func"] = Symbol.Func,
+ ["if"] = Symbol.If,
+ ["else"] = Symbol.Else,
+ ["while"] = Symbol.While,
+ ["break"] = Symbol.Break,
+ ["continue"] = Symbol.Continue,
+ ["return"] = Symbol.Return,
+ ["alloc"] = Symbol.Alloc,
+ ["struct"] = Symbol.Struct,
+ ["let"] = Symbol.Let,
+ ["calls"] = Symbol.Calls,
+ };
+
+ private static readonly Dictionary Modifiers = new()
+ {
+ ["export"] = Modifier.Export,
+ ["extern"] = Modifier.Extern,
+ };
+
+ private static readonly Dictionary Chians = new()
+ {
+ [['=', '=']] = Symbol.Equal,
+ [['!', '=']] = Symbol.NotEqual,
+ [['<', '=']] = Symbol.LessThanOrEqual,
+ [['>', '=']] = Symbol.GreaterThanOrEqual,
+ [[':', ':']] = Symbol.DoubleColon,
+ };
+
+ private static readonly Dictionary Chars = new()
+ {
+ [';'] = Symbol.Semicolon,
+ [':'] = Symbol.Colon,
+ ['('] = Symbol.OpenParen,
+ [')'] = Symbol.CloseParen,
+ ['{'] = Symbol.OpenBrace,
+ ['}'] = Symbol.CloseBrace,
+ ['['] = Symbol.OpenBracket,
+ [']'] = Symbol.CloseBracket,
+ [','] = Symbol.Comma,
+ ['.'] = Symbol.Period,
+ ['='] = Symbol.Assign,
+ ['<'] = Symbol.LessThan,
+ ['>'] = Symbol.GreaterThan,
+ ['+'] = Symbol.Plus,
+ ['-'] = Symbol.Minus,
+ ['*'] = Symbol.Star,
+ ['/'] = Symbol.ForwardSlash,
+ ['!'] = Symbol.Bang,
+ ['^'] = Symbol.Caret,
+ ['&'] = Symbol.Ampersand,
+ };
+
+ private static SourceText _sourceText;
+ private static int _index;
+
+ public static IEnumerable Tokenize(SourceText sourceText, out IEnumerable diagnostics)
+ {
+ _sourceText = sourceText;
+ _index = 0;
+
+ List tokens = [];
+ while (ParseToken().TryGetValue(out var token))
+ {
+ tokens.Add(token);
+ }
+
+ // TODO: Implement diagnostics
+ diagnostics = [];
+ return tokens;
+ }
+
+ private static void ConsumeWhitespace()
+ {
+ while (Peek().TryGetValue(out var character) && char.IsWhiteSpace(character))
+ {
+ Next();
+ }
+ }
+
+ private static Optional ParseToken()
+ {
+ ConsumeWhitespace();
+ var startIndex = _index;
+
+ if (!Peek().TryGetValue(out var current))
+ {
+ return Optional.Empty();
+ }
+
+ if (current == '/' && Peek(1).TryGetValue(out var nextChar) && nextChar == '/')
+ {
+ Next();
+ Next();
+
+ if (Peek().TryGetValue(out var thirdChar) && thirdChar == '/')
+ {
+ Next();
+ var buffer = string.Empty;
+ while (Peek().TryGetValue(out var character) && character != '\n')
+ {
+ buffer += character;
+ Next();
+ }
+
+ Next();
+ return new DocumentationToken(CreateSpan(startIndex), buffer);
+ }
+
+ while (Peek().TryGetValue(out var character) && character != '\n')
+ {
+ Next();
+ }
+
+ Next();
+ return ParseToken();
+ }
+
+ if (char.IsLetter(current) || current == '_')
+ {
+ var buffer = string.Empty;
+
+ while (Peek().TryGetValue(out var next) && (char.IsLetterOrDigit(next) || next == '_'))
+ {
+ buffer += next;
+ Next();
+ }
+
+ if (Keywords.TryGetValue(buffer, out var keywordSymbol))
+ {
+ return new SymbolToken(CreateSpan(startIndex), keywordSymbol);
+ }
+
+ if (Modifiers.TryGetValue(buffer, out var modifer))
+ {
+ return new ModifierToken(CreateSpan(startIndex), modifer);
+ }
+
+ if (buffer is "true" or "false")
+ {
+ return new LiteralToken(CreateSpan(startIndex), LiteralKind.Bool, buffer);
+ }
+
+ return new IdentifierToken(CreateSpan(startIndex), buffer);
+ }
+
+ if (char.IsDigit(current))
+ {
+ var isFloat = false;
+ var buffer = string.Empty;
+
+ while (Peek().TryGetValue(out var next))
+ {
+ if (next == '.')
+ {
+ if (isFloat)
+ {
+ throw new Exception("More than one period found in float literal");
+ }
+
+ isFloat = true;
+ buffer += next;
+ Next();
+ }
+ else if (char.IsDigit(next))
+ {
+ buffer += next;
+ Next();
+ }
+ else
+ {
+ break;
+ }
+ }
+
+ return new LiteralToken(CreateSpan(startIndex), isFloat ? LiteralKind.Float : LiteralKind.Integer, buffer);
+ }
+
+ // TODO: Revisit this
+ foreach (var chain in Chians)
+ {
+ if (current != chain.Key[0]) continue;
+
+ for (var i = 1; i < chain.Key.Length; i++)
+ {
+ var c = Peek(i);
+ if (!c.HasValue || c.Value != chain.Key[i]) break;
+
+ if (i == chain.Key.Length - 1)
+ {
+ for (var j = 0; j <= i; j++)
+ {
+ Next();
+ }
+
+ return new SymbolToken(CreateSpan(startIndex), chain.Value);
+ }
+ }
+ }
+
+ if (Chars.TryGetValue(current, out var charSymbol))
+ {
+ Next();
+ return new SymbolToken(CreateSpan(startIndex), charSymbol);
+ }
+
+ if (current == '"')
+ {
+ Next();
+ var buffer = string.Empty;
+
+ while (true)
+ {
+ if (!Peek().TryGetValue(out var next))
+ {
+ throw new Exception("Unclosed string literal");
+ }
+
+ if (next == '"')
+ {
+ Next();
+ break;
+ }
+
+ buffer += next;
+ Next();
+ }
+
+ return new LiteralToken(CreateSpan(startIndex), LiteralKind.String, buffer);
+ }
+
+ throw new Exception($"Unknown character {current}");
+ }
+
+ private static SourceLocation CreateLocation(int index)
+ {
+ var line = 1;
+ var column = 1;
+ for (var i = 0; i < Math.Min(index, _sourceText.Content.Length - 1); i++)
+ {
+ if (_sourceText.Content[i] == '\n')
+ {
+ column = 1;
+ line += 1;
+ }
+ else
+ {
+ column += 1;
+ }
+ }
+
+ return new SourceLocation(line, column);
+ }
+
+ private static SourceSpan CreateSpan(int startIndex)
+ {
+ return new SourceSpan(_sourceText, CreateLocation(startIndex), CreateLocation(_index));
+ }
+
+ private static Optional Peek(int offset = 0)
+ {
+ if (_index + offset < _sourceText.Content.Length)
+ {
+ return _sourceText.Content[_index + offset];
+ }
+
+ return Optional.Empty();
+ }
+
+ private static void Next()
+ {
+ _index++;
+ }
+}
\ No newline at end of file
diff --git a/src/compilation/Syntax/Typing/Binder.cs b/src/compilation/Syntax/Typing/Binder.cs
new file mode 100644
index 0000000..306d40f
--- /dev/null
+++ b/src/compilation/Syntax/Typing/Binder.cs
@@ -0,0 +1,463 @@
+using Common;
+using Syntax.Parsing;
+using Syntax.Parsing.Node;
+using Syntax.Tokenization;
+using Syntax.Typing.BoundNode;
+using UnaryExpressionNode = Syntax.Parsing.Node.UnaryExpressionNode;
+
+namespace Syntax.Typing;
+
+// TODO: Currently anonymous function does not get a new scope
+public static class Binder
+{
+ private static SyntaxTree _syntaxTree = null!;
+ private static DefinitionTable _definitionTable = null!;
+
+ private static Dictionary _variables = new();
+ private static NubType? _funcReturnType;
+
+ public static BoundSyntaxTree Bind(SyntaxTree syntaxTree, DefinitionTable definitionTable)
+ {
+ _syntaxTree = syntaxTree;
+ _definitionTable = definitionTable;
+
+ _variables = [];
+ _funcReturnType = null;
+
+ var definitions = new List();
+
+ foreach (var definition in syntaxTree.Definitions)
+ {
+ definitions.Add(BindDefinition(definition));
+ }
+
+ return new BoundSyntaxTree(syntaxTree.FilePath, syntaxTree.Namespace, definitions);
+ }
+
+ private static BoundDefinitionNode BindDefinition(DefinitionNode node)
+ {
+ return node switch
+ {
+ ExternFuncDefinitionNode definition => BindExternFuncDefinition(definition),
+ LocalFuncDefinitionNode definition => BindLocalFuncDefinition(definition),
+ StructDefinitionNode definition => BindStruct(definition),
+ _ => throw new ArgumentOutOfRangeException(nameof(node))
+ };
+ }
+
+ private static BoundStructDefinitionNode BindStruct(StructDefinitionNode node)
+ {
+ var defOpt = _definitionTable.LookupStruct(node.Namespace, node.Name);
+ if (!defOpt.TryGetValue(out var definition))
+ {
+ throw new NotImplementedException("Diagnostics not implemented");
+ }
+
+ var structFields = new List();
+
+ foreach (var structField in node.Fields)
+ {
+ var value = Optional.Empty();
+
+ if (structField.Value.HasValue)
+ {
+ var definitionField = definition.Fields.FirstOrDefault(f => f.Name == structField.Name);
+ if (definitionField == null)
+ {
+ throw new NotImplementedException("Diagnostics not implemented");
+ }
+
+ value = BindExpression(structField.Value.Value, definitionField.Type);
+ }
+
+ structFields.Add(new BoundStructField(structField.Name, structField.Type, value));
+ }
+
+ return new BoundStructDefinitionNode(node.Tokens, node.Documentation, node.Namespace, node.Name, structFields);
+ }
+
+ private static BoundExternFuncDefinitionNode BindExternFuncDefinition(ExternFuncDefinitionNode node)
+ {
+ var parameters = new List();
+
+ foreach (var parameter in node.Parameters)
+ {
+ parameters.Add(new BoundFuncParameter(parameter.Name, parameter.Type));
+ }
+
+ return new BoundExternFuncDefinitionNode(node.Tokens, node.Documentation, node.Namespace, node.Name, node.CallName, parameters, node.ReturnType);
+ }
+
+ private static BoundLocalFuncDefinitionNode BindLocalFuncDefinition(LocalFuncDefinitionNode node)
+ {
+ _variables.Clear();
+ _funcReturnType = node.ReturnType;
+
+ var parameters = new List();
+
+ foreach (var parameter in node.Parameters)
+ {
+ parameters.Add(new BoundFuncParameter(parameter.Name, parameter.Type));
+ _variables[parameter.Name] = parameter.Type;
+ }
+
+ var body = BindBlock(node.Body);
+
+ return new BoundLocalFuncDefinitionNode(node.Tokens, node.Documentation, node.Namespace, node.Name, parameters, body, node.ReturnType, node.Exported);
+ }
+
+ private static BoundBlockNode BindBlock(BlockNode node)
+ {
+ var statements = new List();
+
+ foreach (var statement in node.Statements)
+ {
+ statements.Add(BindStatement(statement));
+ }
+
+ return new BoundBlockNode(node.Tokens, statements);
+ }
+
+ private static BoundStatementNode BindStatement(StatementNode node)
+ {
+ return node switch
+ {
+ ArrayIndexAssignmentNode statement => BindArrayIndex(statement),
+ BreakNode statement => BindBreak(statement),
+ ContinueNode statement => BindContinue(statement),
+ DereferenceAssignmentNode statement => BindDereferenceAssignment(statement),
+ IfNode statement => BindIf(statement),
+ MemberAssignmentNode statement => BindMemberAssignment(statement),
+ ReturnNode statement => BindReturn(statement),
+ StatementExpressionNode statement => BindStatementExpression(statement),
+ VariableAssignmentNode statement => BindVariableAssignment(statement),
+ VariableDeclarationNode statement => BindVariableDeclaration(statement),
+ WhileNode statement => BindWhile(statement),
+ _ => throw new ArgumentOutOfRangeException(nameof(node))
+ };
+ }
+
+ private static BoundArrayIndexAssignmentNode BindArrayIndex(ArrayIndexAssignmentNode statement)
+ {
+ var boundArrayIndex = BindArrayIndexAccess(statement.ArrayIndexAccess);
+ var elementType = ((NubArrayType)boundArrayIndex.Type).ElementType;
+ return new BoundArrayIndexAssignmentNode(statement.Tokens, boundArrayIndex, BindExpression(statement.Value, elementType));
+ }
+
+ private static BoundBreakNode BindBreak(BreakNode statement)
+ {
+ return new BoundBreakNode(statement.Tokens);
+ }
+
+ private static BoundContinueNode BindContinue(ContinueNode statement)
+ {
+ return new BoundContinueNode(statement.Tokens);
+ }
+
+ private static BoundDereferenceAssignmentNode BindDereferenceAssignment(DereferenceAssignmentNode statement)
+ {
+ var boundDereference = BindDereference(statement.Dereference);
+ var dereferenceBaseType = ((NubPointerType)boundDereference.Type).BaseType;
+ return new BoundDereferenceAssignmentNode(statement.Tokens, boundDereference, BindExpression(statement.Value, dereferenceBaseType));
+ }
+
+ private static BoundIfNode BindIf(IfNode statement)
+ {
+ var elseStatement = Optional.Empty>();
+
+ if (statement.Else.HasValue)
+ {
+ elseStatement = statement.Else.Value.Match>
+ (
+ elseIf => BindIf(elseIf),
+ @else => BindBlock(@else)
+ );
+ }
+
+ return new BoundIfNode(statement.Tokens, BindExpression(statement.Condition, NubPrimitiveType.Bool), BindBlock(statement.Body), elseStatement);
+ }
+
+ private static BoundMemberAssignmentNode BindMemberAssignment(MemberAssignmentNode statement)
+ {
+ var boundMemberAccess = BindMemberAccess(statement.MemberAccess);
+ var elementType = ((NubArrayType)boundMemberAccess.Type).ElementType;
+ return new BoundMemberAssignmentNode(statement.Tokens, boundMemberAccess, BindExpression(statement.Value, elementType));
+ }
+
+ private static BoundReturnNode BindReturn(ReturnNode statement)
+ {
+ var value = Optional.Empty();
+
+ if (statement.Value.HasValue)
+ {
+ value = BindExpression(statement.Value.Value, _funcReturnType);
+ }
+
+ return new BoundReturnNode(statement.Tokens, value);
+ }
+
+ private static BoundStatementExpressionNode BindStatementExpression(StatementExpressionNode statement)
+ {
+ return new BoundStatementExpressionNode(statement.Tokens, BindExpression(statement.Expression));
+ }
+
+ private static BoundVariableAssignmentNode BindVariableAssignment(VariableAssignmentNode statement)
+ {
+ var variableType = _variables[statement.Identifier.Name];
+ return new BoundVariableAssignmentNode(statement.Tokens, BindIdentifier(statement.Identifier), BindExpression(statement.Value, variableType));
+ }
+
+ private static BoundVariableDeclarationNode BindVariableDeclaration(VariableDeclarationNode statement)
+ {
+ _variables[statement.Name] = statement.Type;
+ return new BoundVariableDeclarationNode(statement.Tokens, statement.Name, statement.Type);
+ }
+
+ private static BoundWhileNode BindWhile(WhileNode statement)
+ {
+ return new BoundWhileNode(statement.Tokens, BindExpression(statement.Condition, NubPrimitiveType.Bool), BindBlock(statement.Body));
+ }
+
+ private static BoundExpressionNode BindExpression(ExpressionNode node, NubType? expectedType = null)
+ {
+ return node switch
+ {
+ AddressOfNode expression => BindAddressOf(expression),
+ AnonymousFuncNode expression => BindAnonymousFunc(expression),
+ ArrayIndexAccessNode expression => BindArrayIndexAccess(expression),
+ ArrayInitializerNode expression => BindArrayInitializer(expression),
+ BinaryExpressionNode expression => BindBinaryExpression(expression),
+ DereferenceNode expression => BindDereference(expression),
+ FixedArrayInitializerNode expression => BindFixedArrayInitializer(expression),
+ FuncCallNode expression => BindFuncCall(expression),
+ IdentifierNode expression => BindIdentifier(expression),
+ LiteralNode expression => BindLiteral(expression, expectedType),
+ MemberAccessNode expression => BindMemberAccess(expression),
+ StructInitializerNode expression => BindStructInitializer(expression),
+ UnaryExpressionNode expression => BindUnaryExpression(expression),
+ _ => throw new ArgumentOutOfRangeException(nameof(node))
+ };
+ }
+
+ private static BoundAddressOfNode BindAddressOf(AddressOfNode expression)
+ {
+ var inner = (BoundLValueNode)BindExpression(expression.Expression);
+ return new BoundAddressOfNode(expression.Tokens, new NubPointerType(inner.Type), inner);
+ }
+
+ private static BoundAnonymousFuncNode BindAnonymousFunc(AnonymousFuncNode expression)
+ {
+ var parameters = new List();
+ var parameterTypes = new List();
+
+ foreach (var parameter in expression.Parameters)
+ {
+ var boundParameter = new BoundFuncParameter(parameter.Name, parameter.Type);
+ parameters.Add(boundParameter);
+ parameterTypes.Add(boundParameter.Type);
+ }
+
+ var body = BindBlock(expression.Body);
+
+ return new BoundAnonymousFuncNode(expression.Tokens, new NubFuncType(expression.ReturnType, parameterTypes), parameters, body, expression.ReturnType);
+ }
+
+ private static BoundArrayIndexAccessNode BindArrayIndexAccess(ArrayIndexAccessNode expression)
+ {
+ var boundArray = BindExpression(expression.Array);
+ var elementType = ((NubArrayType)boundArray.Type).ElementType;
+ return new BoundArrayIndexAccessNode(expression.Tokens, elementType, boundArray, BindExpression(expression.Index, NubPrimitiveType.U64));
+ }
+
+ private static BoundArrayInitializerNode BindArrayInitializer(ArrayInitializerNode expression)
+ {
+ return new BoundArrayInitializerNode(expression.Tokens, new NubArrayType(expression.ElementType), BindExpression(expression.Capacity, NubPrimitiveType.U64),
+ expression.ElementType);
+ }
+
+ private static BoundBinaryExpressionNode BindBinaryExpression(BinaryExpressionNode expression)
+ {
+ var boundLeft = BindExpression(expression.Left);
+ var boundRight = BindExpression(expression.Right, boundLeft.Type);
+ return new BoundBinaryExpressionNode(expression.Tokens, boundLeft.Type, boundLeft, expression.Operator, boundRight);
+ }
+
+ private static BoundDereferenceNode BindDereference(DereferenceNode expression)
+ {
+ var boundExpression = BindExpression(expression.Expression);
+ var dereferencedType = ((NubPointerType)boundExpression.Type).BaseType;
+ return new BoundDereferenceNode(expression.Tokens, dereferencedType, boundExpression);
+ }
+
+ private static BoundFixedArrayInitializerNode BindFixedArrayInitializer(FixedArrayInitializerNode expression)
+ {
+ return new BoundFixedArrayInitializerNode(expression.Tokens, new NubArrayType(expression.ElementType), expression.ElementType, expression.Capacity);
+ }
+
+ private static BoundFuncCallNode BindFuncCall(FuncCallNode expression)
+ {
+ var boundExpression = BindExpression(expression.Expression);
+
+ var funcType = (NubFuncType)boundExpression.Type;
+ var returnType = ((NubFuncType)boundExpression.Type).ReturnType;
+
+ var parameters = new List();
+
+ foreach (var (i, parameter) in expression.Parameters.Index())
+ {
+ if (i >= funcType.Parameters.Count)
+ {
+ throw new NotImplementedException("Diagnostics not implemented");
+ }
+
+ var expectedType = funcType.Parameters[i];
+
+ parameters.Add(BindExpression(parameter, expectedType));
+ }
+
+ return new BoundFuncCallNode(expression.Tokens, returnType, boundExpression, parameters);
+ }
+
+ private static BoundIdentifierNode BindIdentifier(IdentifierNode expression)
+ {
+ NubType? type = null;
+
+ var definition = _definitionTable.LookupFunc(expression.Namespace.Or(_syntaxTree.Namespace), expression.Name);
+ if (definition.HasValue)
+ {
+ type = new NubFuncType(definition.Value.ReturnType, definition.Value.Parameters.Select(p => p.Type).ToList());
+ }
+
+ if (type == null && !expression.Namespace.HasValue)
+ {
+ type = _variables[expression.Name];
+ }
+
+ if (type == null)
+ {
+ throw new NotImplementedException("Diagnostics not implemented");
+ }
+
+ return new BoundIdentifierNode(expression.Tokens, type, expression.Namespace, expression.Name);
+ }
+
+ private static BoundLiteralNode BindLiteral(LiteralNode expression, NubType? expectedType = null)
+ {
+ var type = expectedType ?? expression.Kind switch
+ {
+ LiteralKind.Integer => NubPrimitiveType.I64,
+ LiteralKind.Float => NubPrimitiveType.F64,
+ LiteralKind.String => new NubStringType(),
+ LiteralKind.Bool => NubPrimitiveType.Bool,
+ _ => throw new ArgumentOutOfRangeException()
+ };
+
+ return new BoundLiteralNode(expression.Tokens, type, expression.Literal, expression.Kind);
+ }
+
+ private static BoundMemberAccessNode BindMemberAccess(MemberAccessNode expression)
+ {
+ var boundExpression = BindExpression(expression.Expression);
+
+ NubType? type = null;
+
+ switch (boundExpression.Type)
+ {
+ case NubArrayType:
+ case NubStringType:
+ case NubCStringType:
+ {
+ if (expression.Member == "count")
+ {
+ type = NubPrimitiveType.U64;
+ }
+
+ break;
+ }
+ case NubStructType structType:
+ {
+ var defOpt = _definitionTable.LookupStruct(structType.Namespace, structType.Name);
+ if (!defOpt.TryGetValue(out var definition))
+ {
+ throw new NotImplementedException("Diagnostics not implemented");
+ }
+
+ var field = definition.Fields.FirstOrDefault(f => f.Name == expression.Member);
+ if (field == null)
+ {
+ throw new NotImplementedException("Diagnostics not implemented");
+ }
+
+ type = field.Type;
+ break;
+ }
+ }
+
+ if (type == null)
+ {
+ throw new NotImplementedException("Diagnostics not implemented");
+ }
+
+ return new BoundMemberAccessNode(expression.Tokens, type, boundExpression, expression.Member);
+ }
+
+ private static BoundStructInitializerNode BindStructInitializer(StructInitializerNode expression)
+ {
+ var defOpt = _definitionTable.LookupStruct(expression.StructType.Namespace, expression.StructType.Name);
+ if (!defOpt.TryGetValue(out var definition))
+ {
+ throw new NotImplementedException("Diagnostics not implemented");
+ }
+
+ var initializers = new Dictionary();
+
+ foreach (var (member, initializer) in expression.Initializers)
+ {
+ var definitionField = definition.Fields.FirstOrDefault(x => x.Name == member);
+ if (definitionField == null)
+ {
+ throw new NotImplementedException("Diagnostics not implemented");
+ }
+
+ initializers[member] = BindExpression(initializer, definitionField.Type);
+ }
+
+ return new BoundStructInitializerNode(expression.Tokens, expression.StructType, expression.StructType, initializers);
+ }
+
+ private static BoundUnaryExpressionNode BindUnaryExpression(UnaryExpressionNode expression)
+ {
+ var boundOperand = BindExpression(expression.Operand);
+
+ NubType? type = null;
+
+ switch (expression.Operator)
+ {
+ case UnaryExpressionOperator.Negate:
+ {
+ boundOperand = BindExpression(expression.Operand, NubPrimitiveType.I64);
+
+ if (boundOperand.Type.IsNumber)
+ {
+ type = boundOperand.Type;
+ }
+
+ break;
+ }
+ case UnaryExpressionOperator.Invert:
+ {
+ boundOperand = BindExpression(expression.Operand, NubPrimitiveType.Bool);
+
+ type = new NubPrimitiveType(PrimitiveTypeKind.Bool);
+ break;
+ }
+ }
+
+ if (type == null)
+ {
+ throw new NotImplementedException("Diagnostics not implemented");
+ }
+
+ return new BoundUnaryExpressionNode(expression.Tokens, type, expression.Operator, boundOperand);
+ }
+}
\ No newline at end of file
diff --git a/src/compilation/Syntax/Typing/BoundNode/Definition.cs b/src/compilation/Syntax/Typing/BoundNode/Definition.cs
new file mode 100644
index 0000000..fcfa22c
--- /dev/null
+++ b/src/compilation/Syntax/Typing/BoundNode/Definition.cs
@@ -0,0 +1,14 @@
+using Common;
+using Syntax.Tokenization;
+
+namespace Syntax.Typing.BoundNode;
+
+public abstract record BoundDefinitionNode(IEnumerable Tokens, Optional Documentation, string Namespace) : BoundNode(Tokens);
+
+public record BoundFuncParameter(string Name, NubType Type);
+public abstract record BoundFuncDefinition(IEnumerable Tokens, Optional Documentation, string Namespace, string Name, List Parameters, NubType ReturnType) : BoundDefinitionNode(Tokens, Documentation, Namespace);
+public record BoundLocalFuncDefinitionNode(IEnumerable Tokens, Optional Documentation, string Namespace, string Name, List Parameters, BoundBlockNode Body, NubType ReturnType, bool Exported) : BoundFuncDefinition(Tokens, Documentation, Namespace, Name, Parameters, ReturnType);
+public record BoundExternFuncDefinitionNode(IEnumerable Tokens, Optional Documentation, string Namespace, string Name, string CallName, List Parameters, NubType ReturnType) : BoundFuncDefinition(Tokens, Documentation, Namespace, Name, Parameters, ReturnType);
+
+public record BoundStructField(string Name, NubType Type, Optional Value);
+public record BoundStructDefinitionNode(IEnumerable Tokens, Optional Documentation, string Namespace, string Name, List Fields) : BoundDefinitionNode(Tokens, Documentation, Namespace);
diff --git a/src/compilation/Syntax/Typing/BoundNode/Expression.cs b/src/compilation/Syntax/Typing/BoundNode/Expression.cs
new file mode 100644
index 0000000..f6f204f
--- /dev/null
+++ b/src/compilation/Syntax/Typing/BoundNode/Expression.cs
@@ -0,0 +1,21 @@
+using Common;
+using Syntax.Parsing.Node;
+using Syntax.Tokenization;
+
+namespace Syntax.Typing.BoundNode;
+
+public abstract record BoundExpressionNode(IEnumerable Tokens, NubType Type) : BoundNode(Tokens);
+public abstract record BoundLValueNode(IEnumerable Tokens, NubType Type) : BoundExpressionNode(Tokens, Type);
+public record BoundBinaryExpressionNode(IEnumerable Tokens, NubType Type, BoundExpressionNode Left, BinaryExpressionOperator Operator, BoundExpressionNode Right) : BoundExpressionNode(Tokens, Type);
+public record BoundUnaryExpressionNode(IEnumerable Tokens, NubType Type, UnaryExpressionOperator Operator, BoundExpressionNode Operand) : BoundExpressionNode(Tokens, Type);
+public record BoundFuncCallNode(IEnumerable Tokens, NubType Type, BoundExpressionNode Expression, List Parameters) : BoundExpressionNode(Tokens, Type);
+public record BoundIdentifierNode(IEnumerable Tokens, NubType Type, Optional Namespace, string Name) : BoundLValueNode(Tokens, Type);
+public record BoundArrayInitializerNode(IEnumerable Tokens, NubType Type, BoundExpressionNode Capacity, NubType ElementType) : BoundExpressionNode(Tokens, Type);
+public record BoundArrayIndexAccessNode(IEnumerable Tokens, NubType Type, BoundExpressionNode Array, BoundExpressionNode Index) : BoundLValueNode(Tokens, Type);
+public record BoundAnonymousFuncNode(IEnumerable Tokens, NubType Type, List Parameters, BoundBlockNode Body, NubType ReturnType) : BoundExpressionNode(Tokens, Type);
+public record BoundAddressOfNode(IEnumerable Tokens, NubType Type, BoundLValueNode Expression) : BoundExpressionNode(Tokens, Type);
+public record BoundFixedArrayInitializerNode(IEnumerable Tokens, NubType Type, NubType ElementType, int Capacity) : BoundExpressionNode(Tokens, Type);
+public record BoundLiteralNode(IEnumerable Tokens, NubType Type, string Literal, LiteralKind Kind) : BoundExpressionNode(Tokens, Type);
+public record BoundMemberAccessNode(IEnumerable Tokens, NubType Type, BoundExpressionNode Expression, string Member) : BoundExpressionNode(Tokens, Type);
+public record BoundStructInitializerNode(IEnumerable Tokens, NubType Type, NubStructType StructType, Dictionary Initializers) : BoundExpressionNode(Tokens, Type);
+public record BoundDereferenceNode(IEnumerable Tokens, NubType Type, BoundExpressionNode Expression) : BoundLValueNode(Tokens, Type);
diff --git a/src/compilation/Syntax/Typing/BoundNode/Node.cs b/src/compilation/Syntax/Typing/BoundNode/Node.cs
new file mode 100644
index 0000000..6030c31
--- /dev/null
+++ b/src/compilation/Syntax/Typing/BoundNode/Node.cs
@@ -0,0 +1,6 @@
+using Syntax.Tokenization;
+
+namespace Syntax.Typing.BoundNode;
+
+public abstract record BoundNode(IEnumerable Tokens);
+public record BoundBlockNode(IEnumerable Tokens, List Statements) : BoundNode(Tokens);
diff --git a/src/compilation/Syntax/Typing/BoundNode/Statement.cs b/src/compilation/Syntax/Typing/BoundNode/Statement.cs
new file mode 100644
index 0000000..151626a
--- /dev/null
+++ b/src/compilation/Syntax/Typing/BoundNode/Statement.cs
@@ -0,0 +1,17 @@
+using Common;
+using Syntax.Tokenization;
+
+namespace Syntax.Typing.BoundNode;
+
+public record BoundStatementNode(IEnumerable Tokens) : BoundNode(Tokens);
+public record BoundStatementExpressionNode(IEnumerable Tokens, BoundExpressionNode Expression) : BoundStatementNode(Tokens);
+public record BoundReturnNode(IEnumerable Tokens, Optional Value) : BoundStatementNode(Tokens);
+public record BoundMemberAssignmentNode(IEnumerable Tokens, BoundMemberAccessNode MemberAccess, BoundExpressionNode Value) : BoundStatementNode(Tokens);
+public record BoundIfNode(IEnumerable Tokens, BoundExpressionNode Condition, BoundBlockNode Body, Optional> Else) : BoundStatementNode(Tokens);
+public record BoundDereferenceAssignmentNode(IEnumerable Tokens, BoundDereferenceNode Dereference, BoundExpressionNode Value) : BoundStatementNode(Tokens);
+public record BoundVariableAssignmentNode(IEnumerable Tokens, BoundIdentifierNode Identifier, BoundExpressionNode Value) : BoundStatementNode(Tokens);
+public record BoundVariableDeclarationNode(IEnumerable Tokens, string Name, NubType Type) : BoundStatementNode(Tokens);
+public record BoundContinueNode(IEnumerable Tokens) : BoundStatementNode(Tokens);
+public record BoundBreakNode(IEnumerable Tokens) : BoundStatementNode(Tokens);
+public record BoundArrayIndexAssignmentNode(IEnumerable Tokens, BoundArrayIndexAccessNode ArrayIndexAccess, BoundExpressionNode Value) : BoundStatementNode(Tokens);
+public record BoundWhileNode(IEnumerable Tokens, BoundExpressionNode Condition, BoundBlockNode Body) : BoundStatementNode(Tokens);
diff --git a/src/compilation/Syntax/Typing/BoundSyntaxTree.cs b/src/compilation/Syntax/Typing/BoundSyntaxTree.cs
new file mode 100644
index 0000000..0dd20b5
--- /dev/null
+++ b/src/compilation/Syntax/Typing/BoundSyntaxTree.cs
@@ -0,0 +1,5 @@
+using Syntax.Typing.BoundNode;
+
+namespace Syntax.Typing;
+
+public record BoundSyntaxTree(string FilePath, string Namespace, List Definitions);
diff --git a/src/compilation/Syntax/Typing/NubType.cs b/src/compilation/Syntax/Typing/NubType.cs
new file mode 100644
index 0000000..1c89f69
--- /dev/null
+++ b/src/compilation/Syntax/Typing/NubType.cs
@@ -0,0 +1,303 @@
+using System.Diagnostics.CodeAnalysis;
+
+namespace Syntax.Typing;
+
+public abstract class NubType
+{
+ public bool IsInteger => this is NubPrimitiveType
+ {
+ Kind: PrimitiveTypeKind.I8
+ or PrimitiveTypeKind.I16
+ or PrimitiveTypeKind.I32
+ or PrimitiveTypeKind.I64
+ or PrimitiveTypeKind.U8
+ or PrimitiveTypeKind.U16
+ or PrimitiveTypeKind.U32
+ or PrimitiveTypeKind.U64
+ };
+
+ public bool IsFloat32 => this is NubPrimitiveType
+ {
+ Kind: PrimitiveTypeKind.F32
+ };
+
+ public bool IsFloat64 => this is NubPrimitiveType
+ {
+ Kind: PrimitiveTypeKind.F64
+ };
+
+ public bool IsNumber => IsFloat32 || IsFloat64 || IsInteger;
+
+ public bool IsVoid => this is NubVoidType;
+
+ public bool IsString => this is NubStringType;
+
+ public bool IsCString => this is NubCStringType;
+
+ public bool IsBool => this is NubPrimitiveType
+ {
+ Kind: PrimitiveTypeKind.Bool
+ };
+
+ public abstract override bool Equals(object? obj);
+ public abstract override int GetHashCode();
+ public abstract override string ToString();
+}
+
+public class NubCStringType : NubType
+{
+ public override bool Equals(object? obj)
+ {
+ return obj is NubCStringType;
+ }
+
+ public override int GetHashCode()
+ {
+ return "cstring".GetHashCode();
+ }
+
+ public override string ToString()
+ {
+ return "cstring";
+ }
+}
+
+public class NubStringType : NubType
+{
+ public override bool Equals(object? obj)
+ {
+ return obj is NubStringType;
+ }
+
+ public override int GetHashCode()
+ {
+ return "string".GetHashCode();
+ }
+
+ public override string ToString()
+ {
+ return "string";
+ }
+}
+
+public class NubFuncType(NubType returnType, List parameters) : NubType
+{
+ public NubType ReturnType { get; } = returnType;
+ public List Parameters { get; } = parameters;
+
+ public override bool Equals(object? obj)
+ {
+ return obj is NubFuncType other && other.ReturnType.Equals(ReturnType) && other.Parameters.SequenceEqual(Parameters);
+ }
+
+ public override int GetHashCode()
+ {
+ return HashCode.Combine(ReturnType, Parameters);
+ }
+
+ public override string ToString()
+ {
+ return $"func({string.Join(", ", Parameters)}): {ReturnType}";
+ }
+}
+
+public class NubStructType(string @namespace, string name) : NubType
+{
+ public string Namespace { get; } = @namespace;
+ public string Name { get; } = name;
+
+ public override bool Equals(object? obj)
+ {
+ return obj is NubStructType other && other.Namespace == Namespace && other.Name == Name;
+ }
+
+ public override int GetHashCode()
+ {
+ return HashCode.Combine(Namespace, Name);
+ }
+
+ public override string ToString()
+ {
+ return $"{Namespace}::{Name}";
+ }
+}
+
+public class NubPointerType(NubType baseType) : NubType
+{
+ public NubType BaseType { get; } = baseType;
+
+ public override bool Equals(object? obj)
+ {
+ return obj is NubPointerType other && BaseType.Equals(other.BaseType);
+ }
+
+ public override int GetHashCode()
+ {
+ return HashCode.Combine(BaseType);
+ }
+
+ public override string ToString()
+ {
+ return "^" + BaseType;
+ }
+}
+
+public class NubArrayType(NubType elementType) : NubType
+{
+ public NubType ElementType { get; } = elementType;
+
+ public override bool Equals(object? obj)
+ {
+ if (obj is NubArrayType other)
+ {
+ return ElementType.Equals(other.ElementType);
+ }
+ return false;
+ }
+
+ public override int GetHashCode()
+ {
+ return HashCode.Combine(ElementType);
+ }
+
+ public override string ToString()
+ {
+ return "[]" + ElementType;
+ }
+}
+
+public class NubFixedArrayType(NubType elementType, int capacity) : NubType
+{
+ public NubType ElementType { get; } = elementType;
+ public int Capacity { get; } = capacity;
+
+ public override string ToString() => $"[{Capacity}]{ElementType}";
+
+ public override bool Equals(object? obj)
+ {
+ return obj is NubFixedArrayType other && ElementType.Equals(other.ElementType) && Capacity == other.Capacity;
+ }
+
+ public override int GetHashCode()
+ {
+ return HashCode.Combine(ElementType, Capacity);
+ }
+}
+
+public class NubAnyType : NubType
+{
+ public override string ToString() => "any";
+
+ public override bool Equals(object? obj)
+ {
+ return obj is NubAnyType;
+ }
+
+ public override int GetHashCode()
+ {
+ return "any".GetHashCode();
+ }
+}
+
+public class NubVoidType : NubType
+{
+ public override string ToString() => "void";
+
+ public override bool Equals(object? obj)
+ {
+ return obj is NubVoidType;
+ }
+
+ public override int GetHashCode()
+ {
+ return GetType().GetHashCode();
+ }
+}
+
+public class NubPrimitiveType(PrimitiveTypeKind kind) : NubType
+{
+ public PrimitiveTypeKind Kind { get; } = kind;
+
+ public static NubPrimitiveType I64 => new(PrimitiveTypeKind.I64);
+ public static NubPrimitiveType I32 => new(PrimitiveTypeKind.I32);
+ public static NubPrimitiveType I16 => new(PrimitiveTypeKind.I16);
+ public static NubPrimitiveType I8 => new(PrimitiveTypeKind.I8);
+
+ public static NubPrimitiveType U64 => new(PrimitiveTypeKind.U64);
+ public static NubPrimitiveType U32 => new(PrimitiveTypeKind.U32);
+ public static NubPrimitiveType U16 => new(PrimitiveTypeKind.U16);
+ public static NubPrimitiveType U8 => new(PrimitiveTypeKind.U8);
+
+ public static NubPrimitiveType F64 => new(PrimitiveTypeKind.F64);
+ public static NubPrimitiveType F32 => new(PrimitiveTypeKind.F32);
+
+ public static NubPrimitiveType Bool => new(PrimitiveTypeKind.Bool);
+
+ public static bool TryParse(string s, [NotNullWhen(true)] out PrimitiveTypeKind? kind)
+ {
+ kind = s switch
+ {
+ "i64" => PrimitiveTypeKind.I64,
+ "i32" => PrimitiveTypeKind.I32,
+ "i16" => PrimitiveTypeKind.I16,
+ "i8" => PrimitiveTypeKind.I8,
+ "u64" => PrimitiveTypeKind.U64,
+ "u32" => PrimitiveTypeKind.U32,
+ "u16" => PrimitiveTypeKind.U16,
+ "u8" => PrimitiveTypeKind.U8,
+ "f64" => PrimitiveTypeKind.F64,
+ "f32" => PrimitiveTypeKind.F32,
+ "bool" => PrimitiveTypeKind.Bool,
+ _ => null
+ };
+
+ return kind != null;
+ }
+
+ public override bool Equals(object? obj)
+ {
+ return obj is NubPrimitiveType other && other.Kind == Kind;
+ }
+
+ public override int GetHashCode()
+ {
+ return HashCode.Combine(Kind);
+ }
+
+ public override string ToString()
+ {
+ return Kind switch
+ {
+ PrimitiveTypeKind.I8 => "i8",
+ PrimitiveTypeKind.I16 => "i16",
+ PrimitiveTypeKind.I32 => "i32",
+ PrimitiveTypeKind.I64 => "i64",
+
+ PrimitiveTypeKind.U8 => "u8",
+ PrimitiveTypeKind.U16 => "u16",
+ PrimitiveTypeKind.U32 => "u32",
+ PrimitiveTypeKind.U64 => "u64",
+
+ PrimitiveTypeKind.F32 => "f32",
+ PrimitiveTypeKind.F64 => "f64",
+
+ PrimitiveTypeKind.Bool => "bool",
+ _ => throw new ArgumentOutOfRangeException(nameof(kind), Kind, null)
+ };
+ }
+}
+
+public enum PrimitiveTypeKind
+{
+ I64,
+ I32,
+ I16,
+ I8,
+ U64,
+ U32,
+ U16,
+ U8,
+ F64,
+ F32,
+ Bool
+}
\ No newline at end of file
diff --git a/src/compilation/Syntax/Typing/TypeChecker.cs b/src/compilation/Syntax/Typing/TypeChecker.cs
new file mode 100644
index 0000000..e935a2b
--- /dev/null
+++ b/src/compilation/Syntax/Typing/TypeChecker.cs
@@ -0,0 +1,633 @@
+// using System.Diagnostics;
+// using Syntax.Diagnostics;
+// using Syntax.Parsing;
+// using Syntax.Parsing.Node;
+// using Syntax.Tokenization;
+//
+// namespace Syntax.Typing;
+//
+// public static class TypeChecker
+// {
+// private static SyntaxTree _syntaxTree = null!;
+// private static DefinitionTable _definitionTable = null!;
+//
+// private static Dictionary _variables = new();
+// private static List _diagnostics = [];
+// private static NubType? _currentFunctionReturnType;
+// private static Queue _anonymousFunctions = [];
+//
+// public static void Check(SyntaxTree syntaxTree, DefinitionTable definitionTable, out IEnumerable diagnostics)
+// {
+// _syntaxTree = syntaxTree;
+// _definitionTable = definitionTable;
+//
+// _variables = new Dictionary();
+// _diagnostics = [];
+// _currentFunctionReturnType = null;
+// _anonymousFunctions = [];
+//
+// foreach (var structDef in syntaxTree.Definitions.OfType())
+// {
+// CheckStructDef(structDef);
+// }
+//
+// foreach (var funcDef in syntaxTree.Definitions.OfType())
+// {
+// CheckFuncDef(funcDef.Parameters, funcDef.Body, funcDef.ReturnType);
+// }
+//
+// while (_anonymousFunctions.TryDequeue(out var func))
+// {
+// CheckFuncDef(func.Parameters, func.Body, func.ReturnType);
+//
+// }
+//
+// diagnostics = _diagnostics;
+// }
+//
+// private static void CheckStructDef(StructDefinitionNode structDef)
+// {
+// var fields = new Dictionary();
+// foreach (var field in structDef.Fields)
+// {
+// if (fields.ContainsKey(field.Name))
+// {
+// ReportError($"Duplicate field '{field.Name}' in struct '{structDef.Name}'", structDef);
+// continue;
+// }
+//
+// if (field.Value.HasValue)
+// {
+// var fieldType = CheckExpression(field.Value.Value, field.Type);
+// if (fieldType != null && !fieldType.Equals(field.Type))
+// {
+// ReportError("Default field initializer does not match the defined type", field.Value.Value);
+// }
+// }
+//
+// fields[field.Name] = field.Type;
+// }
+// }
+//
+// private static void CheckFuncDef(List parameters, BlockNode body, NubType returnType)
+// {
+// _variables.Clear();
+// _currentFunctionReturnType = returnType;
+//
+// foreach (var param in parameters)
+// {
+// _variables[param.Name] = param.Type;
+// }
+//
+// CheckBlock(body);
+// }
+//
+// private static void CheckBlock(BlockNode block)
+// {
+// foreach (var statement in block.Statements)
+// {
+// CheckStatement(statement);
+// }
+// }
+//
+// private static void CheckStatement(StatementNode statement)
+// {
+// switch (statement)
+// {
+// case ArrayIndexAssignmentNode arrayIndexAssignment:
+// CheckArrayIndexAssignment(arrayIndexAssignment);
+// break;
+// case VariableAssignmentNode variableAssignment:
+// CheckVariableAssignment(variableAssignment);
+// break;
+// case VariableDeclarationNode variableDeclaration:
+// CheckVariableVariableDeclaration(variableDeclaration);
+// break;
+// case IfNode ifNode:
+// CheckIf(ifNode);
+// break;
+// case MemberAssignmentNode memberAssignment:
+// CheckMemberAssignment(memberAssignment);
+// break;
+// case WhileNode whileNode:
+// CheckWhile(whileNode);
+// break;
+// case ReturnNode returnNode:
+// CheckReturn(returnNode);
+// break;
+// case StatementExpressionNode statementExpression:
+// CheckExpression(statementExpression.Expression);
+// break;
+// case BreakNode:
+// case ContinueNode:
+// break;
+// case DereferenceAssignmentNode dereferenceAssignment:
+// CheckDereferenceAssignment(dereferenceAssignment);
+// break;
+// default:
+// ReportError($"Unsupported statement type: {statement.GetType().Name}", statement);
+// break;
+// }
+// }
+//
+// private static void CheckMemberAssignment(MemberAssignmentNode memberAssignment)
+// {
+// var memberType = CheckExpression(memberAssignment.MemberAccess);
+// if (memberType == null) return;
+// var valueType = CheckExpression(memberAssignment.Value, memberType);
+// if (valueType == null) return;
+//
+// if (!NubType.IsCompatibleWith(memberType, valueType))
+// {
+// ReportError($"'{valueType}' is not assignable to member of type '{memberType}'", memberAssignment);
+// }
+// }
+//
+// private static void CheckArrayIndexAssignment(ArrayIndexAssignmentNode arrayIndexAssignment)
+// {
+// var itemType = CheckExpression(arrayIndexAssignment.ArrayIndexAccess);
+// if (itemType == null) return;
+// var valueType = CheckExpression(arrayIndexAssignment.Value, itemType);
+// if (valueType == null) return;
+//
+// if (!NubType.IsCompatibleWith(itemType, valueType))
+// {
+// ReportError($"'{valueType}' is not assignable to array of type '{itemType}'", arrayIndexAssignment);
+// }
+// }
+//
+// private static void CheckVariableAssignment(VariableAssignmentNode variableAssignment)
+// {
+// if (!_variables.TryGetValue(variableAssignment.Identifier.Name, out var variable))
+// {
+// ReportError($"Variable '{variableAssignment.Identifier}' is not declared", variableAssignment);
+// return;
+// }
+//
+// var valueType = CheckExpression(variableAssignment.Value, variable);
+// if (valueType == null) return;
+//
+// if (!NubType.IsCompatibleWith(variableAssignment.Value.Type, variable))
+// {
+// ReportError($"Cannot assign expression of type '{variableAssignment.Value.Type}' to variable '{variableAssignment.Identifier}' with type '{variable}'", variableAssignment);
+// }
+// }
+//
+// private static void CheckVariableVariableDeclaration(VariableDeclarationNode variableDeclaration)
+// {
+// if (_variables.TryGetValue(variableDeclaration.Name, out var variable))
+// {
+// ReportError($"Cannot redeclare variable '{variable}'", variableDeclaration);
+// }
+//
+// _variables[variableDeclaration.Name] = variableDeclaration.Type;
+// }
+//
+// private static NubType? CheckDereference(DereferenceNode dereference)
+// {
+// var exprType = CheckExpression(dereference.Expression);
+// if (exprType == null) return null;
+//
+// if (exprType is not NubPointerType nubPointerType)
+// {
+// ReportError($"Cannot dereference a non-pointer type {exprType}", dereference);
+// return null;
+// }
+//
+// return nubPointerType.BaseType;
+// }
+//
+// private static NubType CheckFixedInitializerArray(FixedArrayInitializerNode fixedArrayInitializer)
+// {
+// return new NubFixedArrayType(fixedArrayInitializer.ElementType, fixedArrayInitializer.Capacity);
+// }
+//
+// private static NubType? CheckFuncCall(FuncCallNode funcCall)
+// {
+// var identType = CheckExpression(funcCall.Expression);
+// if (identType is not NubFuncType funcType)
+// {
+// ReportError("Cannot call function on non-function type", funcCall);
+// return null;
+// }
+//
+// if (funcCall.Parameters.Count != funcType.Parameters.Count)
+// {
+// ReportError($"{funcType} expects {funcType.Parameters.Count} arguments, but was called with {funcType.Parameters.Count} arguments", funcCall);
+// return null;
+// }
+//
+// for (var i = 0; i < funcCall.Parameters.Count; i++)
+// {
+// var parameter = funcCall.Parameters[i];
+// var parameterType = CheckExpression(parameter);
+// if (parameterType == null) return null;
+//
+// if (!NubType.IsCompatibleWith(parameterType, funcType.Parameters[i]))
+// {
+// ReportError($"'{parameterType}' does not match expected type {funcType.Parameters[i]}", funcCall);
+// return null;
+// }
+// }
+//
+// return funcType.ReturnType;
+// }
+//
+// private static void CheckIf(IfNode ifNode)
+// {
+// var conditionType = CheckExpression(ifNode.Condition, NubPrimitiveType.Bool);
+// if (conditionType != null && !conditionType.Equals(NubPrimitiveType.Bool))
+// {
+// ReportError($"If condition must be a boolean expression, got '{conditionType}'", ifNode.Condition);
+// }
+//
+// CheckBlock(ifNode.Body);
+//
+// if (ifNode.Else.HasValue)
+// {
+// var elseValue = ifNode.Else.Value;
+// elseValue.Match(CheckIf, CheckBlock);
+// }
+// }
+//
+// private static void CheckWhile(WhileNode whileNode)
+// {
+// var conditionType = CheckExpression(whileNode.Condition, NubPrimitiveType.Bool);
+// if (conditionType != null && !conditionType.Equals(NubPrimitiveType.Bool))
+// {
+// ReportError($"While condition must be a boolean expression, got '{conditionType}'", whileNode.Condition);
+// }
+//
+// CheckBlock(whileNode.Body);
+// }
+//
+// private static void CheckReturn(ReturnNode returnNode)
+// {
+// if (returnNode.Value.HasValue)
+// {
+// var returnType = CheckExpression(returnNode.Value.Value, _currentFunctionReturnType);
+// if (returnType == null) return;
+//
+// if (_currentFunctionReturnType == null)
+// {
+// ReportError("Cannot return a value from a function with no return type", returnNode.Value.Value);
+// return;
+// }
+//
+// if (!NubType.IsCompatibleWith(returnType, _currentFunctionReturnType))
+// {
+// ReportError($"Return value of type '{returnType}' is not compatible with function return type '{_currentFunctionReturnType}'", returnNode.Value.Value);
+// }
+// }
+// else if (_currentFunctionReturnType != null)
+// {
+// ReportError($"Function must return a value of type '{_currentFunctionReturnType}'", returnNode);
+// }
+// }
+//
+// private static void CheckDereferenceAssignment(DereferenceAssignmentNode dereferenceAssignment)
+// {
+// var dereferenceType = CheckExpression(dereferenceAssignment.Dereference);
+// if (dereferenceType == null) return;
+// var valueType = CheckExpression(dereferenceAssignment.Value, dereferenceType);
+// if (valueType == null) return;
+//
+// if (!NubType.IsCompatibleWith(dereferenceType, valueType))
+// {
+// ReportError($"'{valueType}' is not assignable to type '{dereferenceType}'", dereferenceAssignment);
+// }
+// }
+//
+// private static NubType? CheckExpression(ExpressionNode expression, NubType? expectedType = null)
+// {
+// var resultType = expression switch
+// {
+// AddressOfNode addressOf => CheckAddressOf(addressOf),
+// AnonymousFuncNode anonymousFunc => CheckAnonymousFunc(anonymousFunc),
+// ArrayIndexAccessNode arrayIndex => CheckArrayIndex(arrayIndex),
+// ArrayInitializerNode arrayInitializer => CheckArrayInitializer(arrayInitializer),
+// LiteralNode literal => CheckLiteral(literal, expectedType),
+// IdentifierNode identifier => CheckIdentifier(identifier),
+// BinaryExpressionNode binaryExpr => CheckBinaryExpression(binaryExpr),
+// DereferenceNode dereference => CheckDereference(dereference),
+// FixedArrayInitializerNode fixedArray => CheckFixedInitializerArray(fixedArray),
+// FuncCallNode funcCallExpr => CheckFuncCall(funcCallExpr),
+// StructInitializerNode structInit => CheckStructInitializer(structInit),
+// UnaryExpressionNode unaryExpression => CheckUnaryExpression(unaryExpression),
+// MemberAccessNode memberAccess => CheckMemberAccess(memberAccess),
+// _ => throw new UnreachableException()
+// };
+//
+// if (resultType != null)
+// {
+// expression.Type = resultType;
+// }
+//
+// return resultType;
+// }
+//
+// private static NubType CheckAnonymousFunc(AnonymousFuncNode anonymousFunc)
+// {
+// _anonymousFunctions.Enqueue(anonymousFunc);
+// return new NubFuncType(anonymousFunc.ReturnType, anonymousFunc.Parameters.Select(p => p.Type).ToList());
+// }
+//
+// private static NubType? CheckLiteral(LiteralNode literal, NubType? expectedType = null)
+// {
+// if (expectedType != null)
+// {
+// if (expectedType.IsNumber && literal.Kind is not LiteralKind.Integer and not LiteralKind.Float)
+// {
+// ReportError("Expression expects a numeric literal", literal);
+// return null;
+// }
+//
+// if (expectedType.IsInteger && literal.Kind == LiteralKind.Float)
+// {
+// if (literal.Kind == LiteralKind.Float)
+// {
+// ReportWarning("Float used in integer context. Everything after the '.' will be ignored", literal);
+// }
+// }
+//
+// return expectedType;
+// }
+//
+// return literal.Kind switch
+// {
+// LiteralKind.Integer => NubPrimitiveType.I64,
+// LiteralKind.Float => NubPrimitiveType.F64,
+// LiteralKind.String => new NubCStringType(),
+// LiteralKind.Bool => NubPrimitiveType.Bool,
+// _ => throw new ArgumentOutOfRangeException()
+// };
+// }
+//
+// private static NubType? CheckArrayIndex(ArrayIndexAccessNode arrayIndexAccess)
+// {
+// var expressionType = CheckExpression(arrayIndexAccess.Array);
+// if (expressionType == null) return null;
+// var indexType = CheckExpression(arrayIndexAccess.Index, NubPrimitiveType.U64);
+// if (indexType is { IsInteger: false })
+// {
+// ReportError("Array index type must be a number", arrayIndexAccess.Index);
+// }
+//
+// if (expressionType is NubArrayType arrayType)
+// {
+// return arrayType.ElementType;
+// }
+//
+// if (expressionType is NubFixedArrayType fixedArrayType)
+// {
+// return fixedArrayType.ElementType;
+// }
+//
+// ReportError($"Cannot access index of non-array type {expressionType}", arrayIndexAccess.Array);
+// return null;
+// }
+//
+// private static NubType CheckArrayInitializer(ArrayInitializerNode arrayInitializer)
+// {
+// var capacityType = CheckExpression(arrayInitializer.Capacity, NubPrimitiveType.U64);
+// if (capacityType is { IsInteger: false })
+// {
+// ReportError("Array capacity type must be an integer", arrayInitializer.Capacity);
+// }
+//
+// return new NubArrayType(arrayInitializer.ElementType);
+// }
+//
+// private static NubType? CheckIdentifier(IdentifierNode identifier)
+// {
+// var definition = _definitionTable.LookupFunc(identifier.Namespace.Or(_syntaxTree.Namespace), identifier.Name);
+// if (definition.HasValue)
+// {
+// return new NubFuncType(definition.Value.ReturnType, definition.Value.Parameters.Select(p => p.Type).ToList());
+// }
+//
+// if (!identifier.Namespace.HasValue)
+// {
+// return _variables[identifier.Name];
+// }
+//
+// ReportError($"Identifier '{identifier}' not found", identifier);
+// return null;
+// }
+//
+// private static NubType? CheckAddressOf(AddressOfNode addressOf)
+// {
+// var exprType = CheckExpression(addressOf.Expression);
+// if (exprType == null) return null;
+//
+// return new NubPointerType(exprType);
+// }
+//
+// private static NubType? CheckBinaryExpression(BinaryExpressionNode binaryExpr)
+// {
+// var leftType = CheckExpression(binaryExpr.Left);
+// var rightType = CheckExpression(binaryExpr.Right);
+//
+// if (leftType == null || rightType == null) return null;
+//
+// if (!leftType.Equals(rightType))
+// {
+// ReportError($"Left '{leftType}' and right '{rightType}' side of the binary expression must be the same type", binaryExpr);
+// return null;
+// }
+//
+// switch (binaryExpr.Operator)
+// {
+// case BinaryExpressionOperator.Equal:
+// case BinaryExpressionOperator.NotEqual:
+// return NubPrimitiveType.Bool;
+// case BinaryExpressionOperator.GreaterThan:
+// case BinaryExpressionOperator.GreaterThanOrEqual:
+// case BinaryExpressionOperator.LessThan:
+// case BinaryExpressionOperator.LessThanOrEqual:
+// if (!IsNumeric(leftType))
+// {
+// ReportError($"Comparison operators require numeric operands, got '{leftType}' and '{rightType}'", binaryExpr);
+// return null;
+// }
+//
+// return NubPrimitiveType.Bool;
+// case BinaryExpressionOperator.Plus:
+// case BinaryExpressionOperator.Minus:
+// case BinaryExpressionOperator.Multiply:
+// case BinaryExpressionOperator.Divide:
+// if (!IsNumeric(leftType))
+// {
+// ReportError($"Arithmetic operators require numeric operands, got '{leftType}' and '{rightType}'", binaryExpr);
+// return null;
+// }
+//
+// return leftType;
+// default:
+// ReportError($"Unsupported binary operator: {binaryExpr.Operator}", binaryExpr);
+// return null;
+// }
+// }
+//
+// private static NubType? CheckStructInitializer(StructInitializerNode structInit)
+// {
+// var initialized = new HashSet();
+//
+// var defOpt = _definitionTable.LookupStruct(structInit.StructType.Namespace, structInit.StructType.Name);
+// if (!defOpt.TryGetValue(out var definition))
+// {
+// ReportError($"Struct type '{structInit.StructType.Name}' is not defined", structInit);
+// return null;
+// }
+//
+// foreach (var initializer in structInit.Initializers)
+// {
+// var definitionField = definition.Fields.FirstOrDefault(f => f.Name == initializer.Key);
+// if (definitionField == null)
+// {
+// ReportError($"Field '{initializer.Key}' does not exist in struct '{structInit.StructType.Name}'", initializer.Value);
+// continue;
+// }
+//
+// var initializerType = CheckExpression(initializer.Value, definitionField.Type);
+// if (initializerType != null && !NubType.IsCompatibleWith(initializerType, definitionField.Type))
+// {
+// ReportError($"Cannot initialize field '{initializer.Key}' of type '{definitionField.Type}' with expression of type '{initializerType}'", initializer.Value);
+// }
+//
+// initialized.Add(initializer.Key);
+// }
+//
+// foreach (var field in definition.Fields.Where(f => f.Value.HasValue))
+// {
+// initialized.Add(field.Name);
+// }
+//
+// foreach (var field in definition.Fields)
+// {
+// if (!initialized.Contains(field.Name))
+// {
+// ReportError($"Struct field '{field.Name}' is not initialized on type '{structInit.StructType.Name}'", structInit);
+// }
+// }
+//
+// return structInit.StructType;
+// }
+//
+// private static NubType? CheckUnaryExpression(UnaryExpressionNode unaryExpression)
+// {
+// var operandType = CheckExpression(unaryExpression.Operand);
+// if (operandType == null) return null;
+//
+// switch (unaryExpression.Operator)
+// {
+// case UnaryExpressionOperator.Negate:
+// {
+// if (operandType.Equals(NubPrimitiveType.I8) ||
+// operandType.Equals(NubPrimitiveType.I16) ||
+// operandType.Equals(NubPrimitiveType.I32) ||
+// operandType.Equals(NubPrimitiveType.I64) ||
+// operandType.Equals(NubPrimitiveType.F32) ||
+// operandType.Equals(NubPrimitiveType.F64))
+// {
+// return operandType;
+// }
+//
+// ReportError($"Cannot negate non-numeric type {operandType}", unaryExpression.Operand);
+// return null;
+// }
+// case UnaryExpressionOperator.Invert:
+// {
+// if (!operandType.Equals(NubPrimitiveType.Bool))
+// {
+// ReportError($"Cannot invert non-boolean type {operandType}", unaryExpression.Operand);
+// return null;
+// }
+//
+// return operandType;
+// }
+// default:
+// {
+// ReportError($"Unsupported unary operator: {unaryExpression.Operator}", unaryExpression);
+// return null;
+// }
+// }
+// }
+//
+// private static NubType? CheckMemberAccess(MemberAccessNode memberAccess)
+// {
+// var expressionType = CheckExpression(memberAccess.Expression);
+// if (expressionType == null) return null;
+//
+// switch (expressionType)
+// {
+// case NubArrayType:
+// {
+// if (memberAccess.Member == "count")
+// {
+// return NubPrimitiveType.I64;
+// }
+//
+// break;
+// }
+// case NubStructType structType:
+// {
+// var defOpt = _definitionTable.LookupStruct(structType.Namespace, structType.Name);
+// if (!defOpt.TryGetValue(out var definition))
+// {
+// ReportError($"Struct type '{structType.Name}' is not defined", memberAccess);
+// return null;
+// }
+//
+// var field = definition.Fields.FirstOrDefault(f => f.Name == memberAccess.Member);
+// if (field == null)
+// {
+// ReportError($"Field '{memberAccess.Member}' does not exist in struct '{structType.Name}'", memberAccess);
+// return null;
+// }
+//
+// return field.Type;
+// }
+// }
+//
+// ReportError($"Cannot access member '{memberAccess.Member}' on type '{expressionType}'", memberAccess);
+// return null;
+// }
+//
+// private static void ReportError(string message, Node node)
+// {
+// var diagnostic = Diagnostic.Error(message).At(node).Build();
+// _diagnostics.Add(diagnostic);
+// }
+//
+// private static void ReportWarning(string message, Node node)
+// {
+// var diagnostic = Diagnostic.Warning(message).At(node).Build();
+// _diagnostics.Add(diagnostic);
+// }
+//
+// private static bool IsNumeric(NubType type)
+// {
+// if (type is not NubPrimitiveType primitiveType)
+// {
+// return false;
+// }
+//
+// switch (primitiveType.Kind)
+// {
+// case PrimitiveTypeKind.I8:
+// case PrimitiveTypeKind.I16:
+// case PrimitiveTypeKind.I32:
+// case PrimitiveTypeKind.I64:
+// case PrimitiveTypeKind.U8:
+// case PrimitiveTypeKind.U16:
+// case PrimitiveTypeKind.U32:
+// case PrimitiveTypeKind.U64:
+// case PrimitiveTypeKind.F32:
+// case PrimitiveTypeKind.F64:
+// return true;
+// default:
+// return false;
+// }
+// }
+// }
\ No newline at end of file
diff --git a/src/runtime/entry.s b/src/runtime/entry.s
new file mode 100644
index 0000000..5ea2a6a
--- /dev/null
+++ b/src/runtime/entry.s
@@ -0,0 +1,12 @@
+.intel_syntax noprefix
+
+.equ SYS_EXIT, 60
+
+.text
+.globl _start
+_start:
+ mov rdi, rsp
+ call main
+ mov rdi, rax
+ mov rax, SYS_EXIT
+ syscall
diff --git a/src/runtime/runtime.c b/src/runtime/runtime.c
new file mode 100644
index 0000000..e2a4d24
--- /dev/null
+++ b/src/runtime/runtime.c
@@ -0,0 +1,17 @@
+#include
+
+#define NUB_PANIC_ERROR_CODE 101
+
+uint64_t nub_cstring_length() { return 0; }
+
+uint64_t nub_string_length() {}
+
+void nub_memcpy() {}
+
+void nub_memset() {}
+
+void nub_panic() {}
+
+void nub_panic_array_oob() {}
+
+void nub_panic_oom() {}
\ No newline at end of file