diff --git a/compiler/.gitignore b/compiler/.gitignore
new file mode 100644
index 0000000..c6cc67a
--- /dev/null
+++ b/compiler/.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/compiler/.idea/.idea.Compiler/.idea/.gitignore b/compiler/.idea/.idea.Compiler/.idea/.gitignore
new file mode 100644
index 0000000..cda1cf4
--- /dev/null
+++ b/compiler/.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/compiler/.idea/.idea.Compiler/.idea/.name b/compiler/.idea/.idea.Compiler/.idea/.name
new file mode 100644
index 0000000..b92b7a3
--- /dev/null
+++ b/compiler/.idea/.idea.Compiler/.idea/.name
@@ -0,0 +1 @@
+Compiler
\ No newline at end of file
diff --git a/compiler/.idea/.idea.Compiler/.idea/encodings.xml b/compiler/.idea/.idea.Compiler/.idea/encodings.xml
new file mode 100644
index 0000000..df87cf9
--- /dev/null
+++ b/compiler/.idea/.idea.Compiler/.idea/encodings.xml
@@ -0,0 +1,4 @@
+
+
+
+
\ No newline at end of file
diff --git a/compiler/.idea/.idea.Compiler/.idea/indexLayout.xml b/compiler/.idea/.idea.Compiler/.idea/indexLayout.xml
new file mode 100644
index 0000000..2135b43
--- /dev/null
+++ b/compiler/.idea/.idea.Compiler/.idea/indexLayout.xml
@@ -0,0 +1,10 @@
+
+
+
+
+ Runtime
+
+
+
+
+
\ No newline at end of file
diff --git a/compiler/.idea/.idea.Compiler/.idea/vcs.xml b/compiler/.idea/.idea.Compiler/.idea/vcs.xml
new file mode 100644
index 0000000..6c0b863
--- /dev/null
+++ b/compiler/.idea/.idea.Compiler/.idea/vcs.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/compiler/Compiler.sln b/compiler/Compiler.sln
new file mode 100644
index 0000000..292d374
--- /dev/null
+++ b/compiler/Compiler.sln
@@ -0,0 +1,22 @@
+
+Microsoft Visual Studio Solution File, Format Version 12.00
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NubLang", "NubLang\NubLang.csproj", "{5047E21F-590D-4CB3-AFF3-064316485009}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NubLang.CLI", "NubLang.CLI\NubLang.CLI.csproj", "{A22F17ED-FA17-45AB-92BA-CD02C28B3524}"
+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
+ EndGlobalSection
+EndGlobal
diff --git a/compiler/NubLang.CLI/Archive.cs b/compiler/NubLang.CLI/Archive.cs
new file mode 100644
index 0000000..59919c5
--- /dev/null
+++ b/compiler/NubLang.CLI/Archive.cs
@@ -0,0 +1,29 @@
+using System.Diagnostics;
+
+namespace NubLang.CLI;
+
+public static class Archive
+{
+ public static async Task Invoke(string fileName, params IEnumerable objectFiles)
+ {
+ using var process = new Process();
+ process.StartInfo = new ProcessStartInfo("ar", ["rcs", fileName, ..objectFiles])
+ {
+ UseShellExecute = false,
+ RedirectStandardOutput = true,
+ RedirectStandardError = true,
+ CreateNoWindow = true
+ };
+
+ process.Start();
+ await process.WaitForExitAsync();
+
+ var errors = await process.StandardError.ReadToEndAsync();
+ if (!string.IsNullOrWhiteSpace(errors))
+ {
+ await Console.Error.WriteLineAsync("ar error when archiving:\n" + errors);
+ }
+
+ return process.ExitCode == 0;
+ }
+}
\ No newline at end of file
diff --git a/compiler/NubLang.CLI/GCC.cs b/compiler/NubLang.CLI/GCC.cs
new file mode 100644
index 0000000..51519ab
--- /dev/null
+++ b/compiler/NubLang.CLI/GCC.cs
@@ -0,0 +1,30 @@
+using System.Diagnostics;
+
+namespace NubLang.CLI;
+
+public static class GCC
+{
+ public static async Task Assemble(string asmPath, string outPath)
+ {
+ using var process = new Process();
+ process.StartInfo = new ProcessStartInfo("x86_64-elf-as", ["-nostartfiles", "-o", outPath, asmPath])
+ {
+ UseShellExecute = false,
+ RedirectStandardOutput = true,
+ RedirectStandardError = true,
+ CreateNoWindow = true
+ };
+
+ process.Start();
+
+ await process.WaitForExitAsync();
+
+ var errors = await process.StandardError.ReadToEndAsync();
+ if (!string.IsNullOrWhiteSpace(errors))
+ {
+ await Console.Error.WriteLineAsync("gcc error when assembling:\n" + errors);
+ }
+
+ return process.ExitCode == 0;
+ }
+}
\ No newline at end of file
diff --git a/compiler/NubLang.CLI/NubLang.CLI.csproj b/compiler/NubLang.CLI/NubLang.CLI.csproj
new file mode 100644
index 0000000..0550d0f
--- /dev/null
+++ b/compiler/NubLang.CLI/NubLang.CLI.csproj
@@ -0,0 +1,16 @@
+
+
+
+ nubc
+ Exe
+ net9.0
+ enable
+ enable
+ true
+
+
+
+
+
+
+
diff --git a/compiler/NubLang.CLI/Options.cs b/compiler/NubLang.CLI/Options.cs
new file mode 100644
index 0000000..acef2bc
--- /dev/null
+++ b/compiler/NubLang.CLI/Options.cs
@@ -0,0 +1,9 @@
+using NubLang.Code;
+
+namespace NubLang.CLI;
+
+public class Options
+{
+ public string? OutputPath { get; set; }
+ public List Files { get; } = [];
+}
\ No newline at end of file
diff --git a/compiler/NubLang.CLI/Program.cs b/compiler/NubLang.CLI/Program.cs
new file mode 100644
index 0000000..8421840
--- /dev/null
+++ b/compiler/NubLang.CLI/Program.cs
@@ -0,0 +1,138 @@
+using System.Reflection;
+using NubLang.CLI;
+using NubLang.Code;
+using NubLang.Diagnostics;
+using NubLang.Generation.QBE;
+using NubLang.Parsing;
+using NubLang.Parsing.Syntax;
+using NubLang.Tokenization;
+using NubLang.TypeChecking;
+using Module = NubLang.TypeChecking.Module;
+
+var options = new Options();
+
+for (var i = 0; i < args.Length; i++)
+{
+ var arg = args[i];
+ switch (arg)
+ {
+ case "-o":
+ {
+ ++i;
+ if (i >= args.Length)
+ {
+ return 1;
+ }
+
+ options.OutputPath = args[i];
+ break;
+ }
+ default:
+ {
+ options.Files.Add(new SourceFile(arg));
+ break;
+ }
+ }
+}
+
+foreach (var file in options.Files)
+{
+ if (!File.Exists(file.Path))
+ {
+ Console.Error.WriteLine($"File '{file}' does not exist");
+ return 1;
+ }
+}
+
+var diagnostics = new List();
+
+var syntaxTrees = new List();
+foreach (var file in options.Files)
+{
+ var tokenizer = new Tokenizer(file);
+ var tokens = tokenizer.Tokenize().ToList();
+ diagnostics.AddRange(tokenizer.GetDiagnostics());
+
+ var parser = new Parser();
+ var syntaxTree = parser.Parse(tokens);
+
+ diagnostics.AddRange(parser.GetDiagnostics());
+
+ syntaxTrees.Add(syntaxTree);
+}
+
+var moduleSignatures = ModuleSignature.CollectFromSyntaxTrees(syntaxTrees);
+var modules = Module.CollectFromSyntaxTrees(syntaxTrees);
+
+var typedModules = new List();
+
+foreach (var module in modules)
+{
+ var typeChecker = new TypeChecker(module, moduleSignatures);
+ var typedModule = typeChecker.CheckModule();
+ diagnostics.AddRange(typeChecker.GetDiagnostics());
+ typedModules.Add(typedModule);
+}
+
+foreach (var diagnostic in diagnostics)
+{
+ Console.Error.WriteLine(diagnostic.FormatANSI());
+}
+
+if (diagnostics.Any(diagnostic => diagnostic.Severity == DiagnosticSeverity.Error))
+{
+ return 1;
+}
+
+var objectFiles = new List();
+
+for (var i = 0; i < typedModules.Count; i++)
+{
+ var typedModule = typedModules[i];
+ var outFileName = Path.Combine(".build", Path.ChangeExtension(options.Files[i].Path, null));
+
+ var outFileDir = Path.GetDirectoryName(outFileName);
+ if (!string.IsNullOrEmpty(outFileDir))
+ {
+ Directory.CreateDirectory(outFileDir);
+ }
+
+ var generator = new QBEGenerator(typedModule, moduleSignatures);
+ var ssa = generator.Emit();
+
+ var ssaFilePath = Path.ChangeExtension(outFileName, "ssa");
+ File.WriteAllText(ssaFilePath, ssa);
+
+ var asmFilePath = Path.ChangeExtension(outFileName, "s");
+ var qbeSuccess = await QBE.Invoke(ssaFilePath, asmFilePath);
+
+ if (!qbeSuccess)
+ {
+ return 1;
+ }
+
+ var objFilePath = Path.ChangeExtension(outFileName, "o");
+ var asmSuccess = await GCC.Assemble(asmFilePath, objFilePath);
+
+ if (!asmSuccess)
+ {
+ return 1;
+ }
+
+ objectFiles.Add(objFilePath);
+}
+
+var outPath = options.OutputPath ?? Path.Combine("out.a");
+var outDir = Path.GetDirectoryName(outPath);
+if (!string.IsNullOrEmpty(outDir))
+{
+ Directory.CreateDirectory(outDir);
+}
+
+var archiveResult = await Archive.Invoke(outPath, objectFiles);
+if (!archiveResult)
+{
+ return 1;
+}
+
+return 0;
\ No newline at end of file
diff --git a/compiler/NubLang.CLI/QBE.cs b/compiler/NubLang.CLI/QBE.cs
new file mode 100644
index 0000000..52e37c5
--- /dev/null
+++ b/compiler/NubLang.CLI/QBE.cs
@@ -0,0 +1,30 @@
+using System.Diagnostics;
+
+namespace NubLang.CLI;
+
+public static class QBE
+{
+ public static async Task Invoke(string ssaPath, string outPath)
+ {
+ using var process = new Process();
+ process.StartInfo = new ProcessStartInfo("qbe", ["-o", outPath, ssaPath])
+ {
+ UseShellExecute = false,
+ RedirectStandardOutput = true,
+ RedirectStandardError = true,
+ CreateNoWindow = true
+ };
+
+ process.Start();
+
+ await process.WaitForExitAsync();
+
+ var errors = await process.StandardError.ReadToEndAsync();
+ if (!string.IsNullOrWhiteSpace(errors))
+ {
+ await Console.Error.WriteLineAsync("qbe error:\n" + errors);
+ }
+
+ return process.ExitCode == 0;
+ }
+}
\ No newline at end of file
diff --git a/compiler/NubLang/Code/SourceFile.cs b/compiler/NubLang/Code/SourceFile.cs
new file mode 100644
index 0000000..48370c4
--- /dev/null
+++ b/compiler/NubLang/Code/SourceFile.cs
@@ -0,0 +1,41 @@
+namespace NubLang.Code;
+
+public class SourceFile
+{
+ private string? _content;
+
+ public SourceFile(string path)
+ {
+ Path = path ?? throw new ArgumentNullException(nameof(path));
+ }
+
+ public string Path { get; }
+
+ public string GetText() => _content ??= File.ReadAllText(Path);
+ public override string ToString() => Path;
+
+ public override bool Equals(object? obj)
+ {
+ return obj is SourceFile other && other.Path == Path;
+ }
+
+ public override int GetHashCode()
+ {
+ return HashCode.Combine(typeof(SourceFile), Path);
+ }
+
+ public static bool operator ==(SourceFile? left, SourceFile? right) => Equals(left, right);
+ public static bool operator !=(SourceFile? left, SourceFile? right) => !Equals(left, right);
+}
+
+public class SourceFileSpan
+{
+ public SourceFileSpan(SourceFile sourceFile, SourceSpan span)
+ {
+ SourceFile = sourceFile;
+ Span = span;
+ }
+
+ public SourceFile SourceFile { get; }
+ public SourceSpan Span { get; }
+}
\ No newline at end of file
diff --git a/compiler/NubLang/Code/SourceLocation.cs b/compiler/NubLang/Code/SourceLocation.cs
new file mode 100644
index 0000000..38bf900
--- /dev/null
+++ b/compiler/NubLang/Code/SourceLocation.cs
@@ -0,0 +1,42 @@
+namespace NubLang.Code;
+
+public readonly struct SourceLocation : IEquatable
+{
+ public static SourceLocation Zero => new(0, 0);
+
+ public SourceLocation(int line, int column)
+ {
+ Line = line;
+ Column = column;
+ }
+
+ public int Line { get; }
+ public int Column { get; }
+
+ public override string ToString()
+ {
+ return $"{Line}:{Column}";
+ }
+
+ public override bool Equals(object? obj)
+ {
+ return obj is SourceLocation other && Equals(other);
+ }
+
+ public bool Equals(SourceLocation other)
+ {
+ return Line == other.Line && Column == other.Column;
+ }
+
+ public override int GetHashCode()
+ {
+ return HashCode.Combine(typeof(SourceLocation), Line, Column);
+ }
+
+ public static bool operator ==(SourceLocation left, SourceLocation right) => Equals(left, right);
+ public static bool operator !=(SourceLocation left, SourceLocation right) => !Equals(left, right);
+ public static bool operator <(SourceLocation left, SourceLocation right) => left.Line < right.Line || (left.Line == right.Line && left.Column < right.Column);
+ public static bool operator >(SourceLocation left, SourceLocation right) => left.Line > right.Line || (left.Line == right.Line && left.Column > right.Column);
+ public static bool operator <=(SourceLocation left, SourceLocation right) => left.Line <= right.Line || (left.Line == right.Line && left.Column <= right.Column);
+ public static bool operator >=(SourceLocation left, SourceLocation right) => left.Line >= right.Line || (left.Line == right.Line && left.Column >= right.Column);
+}
\ No newline at end of file
diff --git a/compiler/NubLang/Code/SourceSpan.cs b/compiler/NubLang/Code/SourceSpan.cs
new file mode 100644
index 0000000..3e7a89b
--- /dev/null
+++ b/compiler/NubLang/Code/SourceSpan.cs
@@ -0,0 +1,57 @@
+namespace NubLang.Code;
+
+public readonly struct SourceSpan : IEquatable
+{
+ public static SourceSpan Zero => new(SourceLocation.Zero, SourceLocation.Zero);
+
+ public static SourceSpan Merge(params IEnumerable spans)
+ {
+ var spanArray = spans as SourceSpan[] ?? spans.ToArray();
+
+ if (spanArray.Length == 0)
+ {
+ return Zero;
+ }
+
+ var minStart = spanArray.Min(s => s.Start);
+ var maxEnd = spanArray.Max(s => s.End);
+
+ return new SourceSpan(minStart, maxEnd);
+ }
+
+ public SourceSpan(SourceLocation start, SourceLocation end)
+ {
+ if (start > end)
+ {
+ throw new ArgumentException("Start location cannot be after end location");
+ }
+
+ Start = start;
+ End = end;
+ }
+
+ public SourceLocation Start { get; }
+ public SourceLocation End { get; }
+
+ public override string ToString()
+ {
+ if (Start == End)
+ {
+ return $"{Start}";
+ }
+
+ if (Start.Line == End.Line)
+ {
+ return Start.Column == End.Column ? $"{Start}" : $"{Start.Line}:{Start.Column}-{End.Column}";
+ }
+
+ return $"{Start}-{End}";
+ }
+
+ public bool Equals(SourceSpan other) => Start == other.Start && End == other.End;
+ public override bool Equals(object? obj) => obj is SourceSpan other && Equals(other);
+ public override int GetHashCode() => HashCode.Combine(typeof(SourceSpan), Start, End);
+
+ public static bool operator ==(SourceSpan left, SourceSpan right) => Equals(left, right);
+ public static bool operator !=(SourceSpan left, SourceSpan right) => !Equals(left, right);
+}
\ No newline at end of file
diff --git a/compiler/NubLang/Diagnostics/ConsoleColors.cs b/compiler/NubLang/Diagnostics/ConsoleColors.cs
new file mode 100644
index 0000000..1a6f7a3
--- /dev/null
+++ b/compiler/NubLang/Diagnostics/ConsoleColors.cs
@@ -0,0 +1,56 @@
+namespace NubLang.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;
+ }
+}
\ No newline at end of file
diff --git a/compiler/NubLang/Diagnostics/Diagnostic.cs b/compiler/NubLang/Diagnostics/Diagnostic.cs
new file mode 100644
index 0000000..b1fdd5f
--- /dev/null
+++ b/compiler/NubLang/Diagnostics/Diagnostic.cs
@@ -0,0 +1,325 @@
+using System.Text;
+using NubLang.Code;
+using NubLang.Parsing.Syntax;
+using NubLang.Tokenization;
+
+namespace NubLang.Diagnostics;
+
+public class Diagnostic
+{
+ public class DiagnosticBuilder
+ {
+ private readonly DiagnosticSeverity _severity;
+ private readonly string _message;
+ private SourceFileSpan? _fileSpan;
+ private string? _help;
+
+ public DiagnosticBuilder(DiagnosticSeverity severity, string message)
+ {
+ _severity = severity;
+ _message = message;
+ }
+
+ public DiagnosticBuilder At(SyntaxNode? node)
+ {
+ if (node != null)
+ {
+ var first = node.Tokens.FirstOrDefault();
+ if (first?.FileSpan != null)
+ {
+ var span = SourceSpan.Merge(node.Tokens.Select(x => x.FileSpan?.Span ?? SourceSpan.Zero));
+ At(new SourceFileSpan(first.FileSpan.SourceFile, span));
+ }
+ }
+
+ return this;
+ }
+
+ public DiagnosticBuilder At(Token? token)
+ {
+ if (token != null)
+ {
+ At(token.FileSpan);
+ }
+
+ return this;
+ }
+
+ public DiagnosticBuilder At(SourceFileSpan? fileSpan)
+ {
+ if (fileSpan != null)
+ {
+ _fileSpan = fileSpan;
+ }
+
+ return this;
+ }
+
+ public DiagnosticBuilder WithHelp(string help)
+ {
+ _help = help;
+ return this;
+ }
+
+ public Diagnostic Build() => new(_severity, _message, _help, _fileSpan);
+ }
+
+ 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 string? Help { get; }
+ public SourceFileSpan? FileSpan { get; }
+
+ private Diagnostic(DiagnosticSeverity severity, string message, string? help, SourceFileSpan? fileSpan)
+ {
+ Severity = severity;
+ Message = message;
+ Help = help;
+ FileSpan = fileSpan;
+ }
+
+ public string FormatANSI()
+ {
+ var sb = new StringBuilder();
+
+ sb.Append(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),
+ _ => ConsoleColors.Colorize("unknown", ConsoleColors.Bold + ConsoleColors.White)
+ });
+
+ if (FileSpan != null)
+ {
+ sb.Append(ConsoleColors.Colorize($" at {FileSpan.SourceFile.Path}:{FileSpan.Span}", ConsoleColors.Faint));
+ }
+
+ sb.Append(": ");
+ sb.Append(ConsoleColors.Colorize(Message, ConsoleColors.BrightWhite));
+
+ if (FileSpan != null)
+ {
+ sb.AppendLine();
+ var text = FileSpan.SourceFile.GetText();
+
+ var lines = text.Split('\n');
+
+ var startLine = FileSpan.Span.Start.Line;
+ var endLine = FileSpan.Span.End.Line;
+
+ const int CONTEXT_LINES = 3;
+
+ var contextStartLine = Math.Max(1, startLine - CONTEXT_LINES);
+ var contextEndLine = Math.Min(lines.Length, endLine + CONTEXT_LINES);
+
+ var numberPadding = contextEndLine.ToString().Length;
+ var codePadding = lines.Skip(contextStartLine - 1).Take(contextEndLine - contextStartLine).Max(x => x.Length);
+
+ sb.Append('╭');
+ sb.Append(new string('─', numberPadding + 2));
+ sb.Append('┬');
+ sb.Append(new string('─', codePadding + 2));
+ sb.Append('╮');
+ sb.AppendLine();
+
+ var tokenizer = new Tokenizer(FileSpan.SourceFile);
+ var tokens = tokenizer.Tokenize().ToList();
+
+ for (var i = contextStartLine; i <= contextEndLine; i++)
+ {
+ var line = lines[i - 1];
+
+ sb.Append("│ ");
+ sb.Append(i.ToString().PadRight(numberPadding));
+ sb.Append(" │ ");
+ sb.Append(ApplySyntaxHighlighting(line.PadRight(codePadding), i, tokens));
+ sb.Append(" │");
+ sb.AppendLine();
+
+ if (i >= startLine && i <= endLine)
+ {
+ var markerStartColumn = 1;
+ var markerEndColumn = line.Length + 1;
+
+ if (i == startLine)
+ {
+ markerStartColumn = FileSpan.Span.Start.Column;
+ }
+
+ if (i == endLine)
+ {
+ markerEndColumn = FileSpan.Span.End.Column;
+ }
+
+ var markerLength = markerEndColumn - markerStartColumn;
+ var marker = new string('^', markerLength);
+
+ var markerColor = Severity switch
+ {
+ DiagnosticSeverity.Info => ConsoleColors.Blue,
+ DiagnosticSeverity.Warning => ConsoleColors.Yellow,
+ DiagnosticSeverity.Error => ConsoleColors.Red,
+ _ => ConsoleColors.White
+ };
+
+ sb.Append("│ ");
+ sb.Append(new string(' ', numberPadding));
+ sb.Append(" │ ");
+ sb.Append(new string(' ', markerStartColumn - 1));
+ sb.Append(ConsoleColors.Colorize(marker, markerColor));
+ sb.Append(new string(' ', codePadding - markerEndColumn + 1));
+ sb.Append(" │");
+ sb.AppendLine();
+ }
+ }
+
+ sb.Append('╰');
+ sb.Append(new string('─', numberPadding + 2));
+ sb.Append('┴');
+ sb.Append(new string('─', codePadding + 2));
+ sb.Append('╯');
+ }
+
+ if (Help != null)
+ {
+ sb.AppendLine();
+ sb.Append(ConsoleColors.Colorize($"help: {Help}", ConsoleColors.Cyan));
+ }
+
+ return sb.ToString();
+ }
+
+ private static string ApplySyntaxHighlighting(string line, int lineNumber, List tokens)
+ {
+ var sb = new StringBuilder();
+ var lineTokens = tokens
+ .Where(t => t.FileSpan.Span.Start.Line == lineNumber)
+ .OrderBy(t => t.FileSpan.Span.Start.Column)
+ .ToList();
+
+ if (lineTokens.Count == 0)
+ {
+ return line;
+ }
+
+ var currentColumn = 1;
+
+ foreach (var token in lineTokens)
+ {
+ var tokenStart = token.FileSpan.Span.Start.Column;
+ var tokenEnd = token.FileSpan.Span.End.Column;
+
+ if (tokenStart > currentColumn)
+ {
+ var beforeToken = line.Substring(currentColumn - 1, tokenStart - currentColumn);
+ sb.Append(beforeToken);
+ }
+
+ var tokenLength = tokenEnd - tokenStart;
+ if (tokenStart - 1 + tokenLength <= line.Length)
+ {
+ var tokenText = line.Substring(tokenStart - 1, tokenLength);
+
+ var coloredToken = ColorizeToken(token, tokenText);
+ sb.Append(coloredToken);
+ }
+
+ currentColumn = tokenEnd;
+ }
+
+ if (currentColumn <= line.Length)
+ {
+ var remaining = line[(currentColumn - 1)..];
+ sb.Append(remaining);
+ }
+
+ return sb.ToString();
+ }
+
+ private static string ColorizeToken(Token token, string tokenText)
+ {
+ switch (token)
+ {
+ case IdentifierToken:
+ {
+ return ConsoleColors.Colorize(tokenText, ConsoleColors.BrightWhite);
+ }
+ case LiteralToken literal:
+ {
+ if (literal.Kind == LiteralKind.String)
+ {
+ return ConsoleColors.Colorize(tokenText, ConsoleColors.Green);
+ }
+
+ return ConsoleColors.Colorize(tokenText, ConsoleColors.Magenta);
+ }
+ case SymbolToken symbolToken:
+ {
+ switch (symbolToken.Symbol)
+ {
+ case Symbol.Func:
+ case Symbol.Return:
+ case Symbol.If:
+ case Symbol.Else:
+ case Symbol.While:
+ case Symbol.Break:
+ case Symbol.Continue:
+ case Symbol.Struct:
+ case Symbol.Let:
+ case Symbol.Calls:
+ case Symbol.Interface:
+ case Symbol.For:
+ case Symbol.Extern:
+ {
+ return ConsoleColors.Colorize(tokenText, ConsoleColors.Bold + ConsoleColors.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 ConsoleColors.Colorize(tokenText, ConsoleColors.Yellow);
+ }
+ case Symbol.Colon:
+ case Symbol.OpenParen:
+ case Symbol.CloseParen:
+ case Symbol.OpenBrace:
+ case Symbol.CloseBrace:
+ case Symbol.OpenBracket:
+ case Symbol.CloseBracket:
+ case Symbol.Comma:
+ case Symbol.Period:
+ case Symbol.Semi:
+ {
+ return ConsoleColors.Colorize(tokenText, ConsoleColors.BrightBlack);
+ }
+ }
+
+ break;
+ }
+ }
+
+ return tokenText;
+ }
+}
+
+public enum DiagnosticSeverity
+{
+ Info,
+ Warning,
+ Error
+}
\ No newline at end of file
diff --git a/compiler/NubLang/Generation/QBE/QBEGenerator.cs b/compiler/NubLang/Generation/QBE/QBEGenerator.cs
new file mode 100644
index 0000000..6040310
--- /dev/null
+++ b/compiler/NubLang/Generation/QBE/QBEGenerator.cs
@@ -0,0 +1,1439 @@
+using System.Diagnostics;
+using System.Globalization;
+using System.Text;
+using NubLang.Tokenization;
+using NubLang.TypeChecking;
+using NubLang.TypeChecking.Node;
+
+namespace NubLang.Generation.QBE;
+
+public class QBEGenerator
+{
+ private readonly QBEWriter _writer;
+ private readonly TypedModule _module;
+ private readonly IReadOnlyDictionary _moduleSignatures;
+
+ private readonly List _cStringLiterals = [];
+ private readonly List _stringLiterals = [];
+ private readonly Stack _breakLabels = [];
+ private readonly Stack _continueLabels = [];
+ private int _tmpIndex;
+ private int _labelIndex;
+ private int _cStringLiteralIndex;
+ private int _stringLiteralIndex;
+ private bool _codeIsReachable = true;
+
+ public QBEGenerator(TypedModule module, IReadOnlyDictionary moduleSignatures)
+ {
+ _module = module;
+ _moduleSignatures = moduleSignatures;
+ _writer = new QBEWriter();
+ }
+
+ public string Emit()
+ {
+ _cStringLiterals.Clear();
+ _stringLiterals.Clear();
+ _breakLabels.Clear();
+ _continueLabels.Clear();
+ _tmpIndex = 0;
+ _labelIndex = 0;
+ _cStringLiteralIndex = 0;
+ _stringLiteralIndex = 0;
+ _codeIsReachable = true;
+
+ foreach (var (module, signature) in _moduleSignatures)
+ {
+ foreach (var structType in signature.StructTypes)
+ {
+ EmitStructType(module, structType);
+ _writer.NewLine();
+ }
+ }
+
+ foreach (var structDef in _module.Definitions.OfType())
+ {
+ EmitStructDefinition(structDef);
+ _writer.NewLine();
+ }
+
+ foreach (var funcDef in _module.Definitions.OfType())
+ {
+ EmitFuncDefinition(funcDef);
+ _writer.NewLine();
+ }
+
+ // foreach (var structDef in _module.Definitions.OfType().Where(x => x.InterfaceImplementations.Count > 0))
+ // {
+ // _writer.Write($"data {StructVtableName(_module.Name, structDef.Name)} = {{ ");
+ //
+ // foreach (var interfaceImplementation in structDef.InterfaceImplementations)
+ // {
+ // var interfaceDef = _definitionTable.LookupInterface(interfaceImplementation.Name);
+ // foreach (var func in interfaceDef.Functions)
+ // {
+ // _writer.Write($"l {StructFuncName(_module.Name, structDef.Name, func.Name)}, ");
+ // }
+ // }
+ //
+ // _writer.WriteLine("}");
+ // }
+
+ foreach (var cStringLiteral in _cStringLiterals)
+ {
+ _writer.WriteLine($"data {cStringLiteral.Name} = {{ b \"{cStringLiteral.Value}\", b 0 }}");
+ }
+
+ foreach (var stringLiteral in _stringLiterals)
+ {
+ var bytes = Encoding.UTF8.GetBytes(stringLiteral.Value).Select(b => $"b {b}");
+ _writer.WriteLine($"data {stringLiteral.Name} = {{ l {stringLiteral.Value.Length}, {string.Join(", ", bytes)} }}");
+ }
+
+ return _writer.ToString();
+ }
+
+ private static string QBEAssign(TypeNode type)
+ {
+ if (type.IsSimpleType(out var simpleType, out _))
+ {
+ return simpleType.StorageSize switch
+ {
+ StorageSize.I8 or StorageSize.U8 or StorageSize.I16 or StorageSize.U16 or StorageSize.I32 or StorageSize.U32 => "=w",
+ StorageSize.I64 or StorageSize.U64 => "=l",
+ StorageSize.F32 => "=s",
+ StorageSize.F64 => "=d",
+ _ => throw new ArgumentOutOfRangeException(nameof(simpleType.StorageSize))
+ };
+ }
+
+ return "=l";
+ }
+
+ private void EmitStore(TypeNode type, string value, string destination)
+ {
+ string store;
+
+ if (type.IsSimpleType(out var simpleType, out _))
+ {
+ store = simpleType.StorageSize switch
+ {
+ StorageSize.I8 or StorageSize.U8 => "storeb",
+ StorageSize.I16 or StorageSize.U16 => "storeh",
+ StorageSize.I32 or StorageSize.U32 => "storew",
+ StorageSize.I64 or StorageSize.U64 => "storel",
+ StorageSize.F32 => "stores",
+ StorageSize.F64 => "stored",
+ _ => throw new ArgumentOutOfRangeException(nameof(simpleType.StorageSize))
+ };
+ }
+ else
+ {
+ store = "storel";
+ }
+
+ _writer.Indented($"{store} {value}, {destination}");
+ }
+
+ private string EmitLoad(TypeNode type, string from)
+ {
+ string load;
+
+ if (type.IsSimpleType(out var simpleType, out _))
+ {
+ load = simpleType.StorageSize switch
+ {
+ StorageSize.I64 or StorageSize.U64 => "loadl",
+ StorageSize.I32 or StorageSize.U32 => "loadw",
+ StorageSize.I16 => "loadsh",
+ StorageSize.I8 => "loadsb",
+ StorageSize.U16 => "loaduh",
+ StorageSize.U8 => "loadub",
+ StorageSize.F64 => "loadd",
+ StorageSize.F32 => "loads",
+ _ => throw new ArgumentOutOfRangeException(nameof(simpleType.StorageSize))
+ };
+ }
+ else
+ {
+ load = "loadl";
+ }
+
+ var into = TmpName();
+
+ _writer.Indented($"{into} {QBEAssign(type)} {load} {from}");
+
+ return into;
+ }
+
+ private void EmitMemset(string destination, int value, string length)
+ {
+ var count = TmpName();
+ _writer.Indented($"{count} =l copy 0");
+
+ var loopLabel = LabelName();
+ _writer.WriteLine(loopLabel);
+
+ var continueLabel = LabelName();
+ var doneLabel = LabelName();
+ var condition = TmpName();
+ _writer.Indented($"{condition} =w cultl {count}, {length}");
+ _writer.Indented($"jnz {condition}, {continueLabel}, {doneLabel}");
+
+ _writer.WriteLine(continueLabel);
+
+ var destinationAddress = TmpName();
+ _writer.Indented($"{destinationAddress} =l add {destination}, {count}");
+
+ _writer.Indented($"storeb {value}, {destinationAddress}");
+
+ _writer.Indented($"{count} =l add {count}, 1");
+ _writer.Indented($"jmp {loopLabel}");
+
+ _writer.WriteLine(doneLabel);
+ }
+
+ private void EmitMemcpy(string source, string destination, string length)
+ {
+ var count = TmpName();
+ _writer.Indented($"{count} =l copy 0");
+
+ var loopLabel = LabelName();
+ _writer.WriteLine(loopLabel);
+
+ var continueLabel = LabelName();
+ var doneLabel = LabelName();
+ var condition = TmpName();
+ _writer.Indented($"{condition} =w cultl {count}, {length}");
+ _writer.Indented($"jnz {condition}, {continueLabel}, {doneLabel}");
+
+ _writer.WriteLine(continueLabel);
+
+ var sourceAddress = TmpName();
+ _writer.Indented($"{sourceAddress} =l add {source}, {count}");
+
+ var destinationAddress = TmpName();
+ _writer.Indented($"{destinationAddress} =l add {destination}, {count}");
+
+ var value = TmpName();
+ _writer.Indented($"{value} =w loadub {sourceAddress}");
+ _writer.Indented($"storeb {value}, {destinationAddress}");
+
+ _writer.Indented($"{count} =l add {count}, 1");
+ _writer.Indented($"jmp {loopLabel}");
+
+ _writer.WriteLine(doneLabel);
+ }
+
+ private string EmitArraySizeInBytes(ArrayTypeNode type, string array)
+ {
+ var size = TmpName();
+ _writer.Indented($"{size} =l loadl {array}");
+ _writer.Indented($"{size} =l mul {size}, {SizeOf(type.ElementType)}");
+ _writer.Indented($"{size} =l add {size}, 8");
+ return size;
+ }
+
+ private string EmitCStringSizeInBytes(string cstring)
+ {
+ var count = TmpName();
+ _writer.Indented($"{count} =l copy 0");
+
+ var loopLabel = LabelName();
+ _writer.WriteLine(loopLabel);
+
+ var address = TmpName();
+ _writer.Indented($"{address} =l add {cstring}, {count}");
+
+ var value = TmpName();
+ _writer.Indented($"{value} =w loadub {address}");
+
+ var notZeroLabel = LabelName();
+ var zeroLabel = LabelName();
+ _writer.Indented($"jnz {value}, {notZeroLabel}, {zeroLabel}");
+ _writer.WriteLine(notZeroLabel);
+ _writer.Indented($"{count} =l add {count}, 1");
+ _writer.Indented($"jmp {loopLabel}");
+ _writer.WriteLine(zeroLabel);
+
+ return count;
+ }
+
+ private string EmitStringSizeInBytes(string nubstring)
+ {
+ var size = TmpName();
+ _writer.Indented($"{size} =l loadl {nubstring}");
+ _writer.Indented($"{size} =l add {size}, 8");
+ return size;
+ }
+
+ private void EmitCopyInto(ExpressionNode source, string destination)
+ {
+ // Simple types are passed in registers and can therefore just be stored
+ if (source.Type.IsSimpleType(out var simpleType, out var complexType))
+ {
+ var value = EmitExpression(source);
+ EmitStore(simpleType, value, destination);
+ return;
+ }
+
+ // Structs and interfaces has known sizes at compile time
+ if (complexType is StructTypeNode or InterfaceTypeNode)
+ {
+ var value = EmitExpression(source);
+ _writer.Indented($"blit {value}, {destination}, {SizeOf(complexType)}");
+ }
+ // The rest of the complex types has unknown sizes
+ else
+ {
+ var value = EmitExpression(source);
+ var size = complexType switch
+ {
+ ArrayTypeNode arrayType => EmitArraySizeInBytes(arrayType, value),
+ CStringTypeNode => EmitCStringSizeInBytes(value),
+ StringTypeNode => EmitStringSizeInBytes(value),
+ _ => throw new ArgumentOutOfRangeException(nameof(source.Type))
+ };
+
+ var buffer = TmpName();
+ _writer.Indented($"{buffer} =l alloc8 {size}");
+ EmitMemcpy(value, buffer, size);
+ EmitStore(complexType, buffer, destination);
+ }
+ }
+
+ private string EmitCopy(ExpressionNode source)
+ {
+ // Allowlist for types which are safe to not copy
+ if (source is ArrayInitializerNode or StructInitializerNode or ConvertToInterfaceNode or LiteralNode)
+ {
+ return EmitExpression(source);
+ }
+
+ // Simple types are passed in registers and therefore always copied
+ if (source.Type.IsSimpleType(out _, out var complexType))
+ {
+ return EmitExpression(source);
+ }
+
+ // For the rest, we figure out the size of the type and shallow copy them
+ var value = EmitExpression(source);
+ var destination = TmpName();
+
+ // Structs and interfaces has known sizes at compile time
+ if (complexType is StructTypeNode or InterfaceTypeNode)
+ {
+ var size = SizeOf(complexType);
+ _writer.Indented($"{destination} =l alloc8 {size}");
+ _writer.Indented($"blit {value}, {destination}, {size}");
+ }
+ // The rest of the complex types has unknown sizes
+ else
+ {
+ var size = complexType switch
+ {
+ ArrayTypeNode arrayType => EmitArraySizeInBytes(arrayType, value),
+ CStringTypeNode => EmitCStringSizeInBytes(value),
+ StringTypeNode => EmitStringSizeInBytes(value),
+ _ => throw new ArgumentOutOfRangeException(nameof(source.Type))
+ };
+
+ _writer.Indented($"{destination} =l alloc8 {size}");
+ EmitMemcpy(value, destination, size);
+ }
+
+ return destination;
+ }
+
+ // Utility to create QBE type names for function parameters and return types
+ private string FuncQBETypeName(TypeNode type)
+ {
+ if (type.IsSimpleType(out var simpleType, out var complexType))
+ {
+ return simpleType.StorageSize switch
+ {
+ StorageSize.I64 or StorageSize.U64 => "l",
+ StorageSize.I32 or StorageSize.U32 => "w",
+ StorageSize.I16 => "sh",
+ StorageSize.I8 => "sb",
+ StorageSize.U16 => "uh",
+ StorageSize.U8 => "ub",
+ StorageSize.F64 => "d",
+ StorageSize.F32 => "s",
+ _ => throw new ArgumentOutOfRangeException()
+ };
+ }
+
+ if (complexType is StructTypeNode structType)
+ {
+ return StructTypeName(structType.Module, structType.Name);
+ }
+
+ return "l";
+ }
+
+ private void EmitFuncDefinition(FuncNode funcDef)
+ {
+ if (funcDef.Body == null) return;
+
+ _labelIndex = 0;
+ _tmpIndex = 0;
+
+ _writer.Write("export function ");
+
+ if (funcDef.Signature.ReturnType is not VoidTypeNode)
+ {
+ _writer.Write(FuncQBETypeName(funcDef.Signature.ReturnType) + ' ');
+ }
+
+ _writer.Write(FuncName(_module.Name, funcDef.Name));
+
+ _writer.Write("(");
+ foreach (var parameter in funcDef.Signature.Parameters)
+ {
+ _writer.Write(FuncQBETypeName(parameter.Type) + $" %{parameter.Name}");
+ }
+
+ _writer.WriteLine(") {");
+ _writer.WriteLine("@start");
+
+ EmitBlock(funcDef.Body);
+
+ // Implicit return for void functions if no explicit return has been set
+ if (funcDef.Signature.ReturnType is VoidTypeNode && funcDef.Body.Statements.LastOrDefault() is not ReturnNode)
+ {
+ _writer.Indented("ret");
+ }
+
+ _writer.WriteLine("}");
+ }
+
+ private void EmitStructDefinition(StructNode structDef)
+ {
+ var type = TypeResolver.ResolveStructType(_module.Name, structDef.Name, _moduleSignatures);
+
+ // _writer.WriteLine($"export function {StructCtorName(_module.Name, structDef.Name)}() {{");
+ // _writer.WriteLine("@start");
+ // _writer.Indented($"%struct =l alloc8 {SizeOf(type)}");
+ // // todo(nub31): Finish constructor
+ // _writer.Indented("ret %struct");
+ // _writer.WriteLine("}");
+
+ foreach (var function in structDef.Functions)
+ {
+ _labelIndex = 0;
+ _tmpIndex = 0;
+
+ _writer.NewLine();
+ _writer.Write("export function ");
+
+ if (function.Signature.ReturnType is not VoidTypeNode)
+ {
+ _writer.Write(FuncQBETypeName(function.Signature.ReturnType) + ' ');
+ }
+
+ _writer.Write(StructFuncName(_module.Name, structDef.Name, function.Name));
+
+ _writer.Write("(l %this, ");
+ foreach (var parameter in function.Signature.Parameters)
+ {
+ _writer.Write(FuncQBETypeName(parameter.Type) + $" %{parameter.Name}, ");
+ }
+
+ _writer.WriteLine(") {");
+ _writer.WriteLine("@start");
+
+ EmitBlock(function.Body);
+
+ // Implicit return for void functions if no explicit return has been set
+ if (function.Signature.ReturnType is VoidTypeNode && function.Body.Statements.LastOrDefault() is not ReturnNode)
+ {
+ _writer.Indented("ret");
+ }
+
+ _writer.WriteLine("}");
+ }
+ }
+
+ private void EmitStructType(string module, StructTypeNode structType)
+ {
+ _writer.WriteLine($"type {StructTypeName(module, structType.Name)} = {{ ");
+
+ foreach (var field in structType.Fields)
+ {
+ _writer.Indented($"{StructDefQBEType(field.Type)},");
+ }
+
+ _writer.WriteLine("}");
+ return;
+
+ string StructDefQBEType(TypeNode type)
+ {
+ if (type.IsSimpleType(out var simpleType, out var complexType))
+ {
+ return simpleType.StorageSize switch
+ {
+ StorageSize.I64 or StorageSize.U64 => "l",
+ StorageSize.I32 or StorageSize.U32 => "w",
+ StorageSize.I16 or StorageSize.U16 => "h",
+ StorageSize.I8 or StorageSize.U8 => "b",
+ StorageSize.F64 => "d",
+ StorageSize.F32 => "s",
+ _ => throw new ArgumentOutOfRangeException()
+ };
+ }
+
+ if (complexType is StructTypeNode childStructType)
+ {
+ return StructTypeName(childStructType.Module, childStructType.Name);
+ }
+
+ return "l";
+ }
+ }
+
+ private void EmitBlock(BlockNode block)
+ {
+ foreach (var statement in block.Statements)
+ {
+ if (_codeIsReachable)
+ {
+ EmitStatement(statement);
+ }
+ }
+
+ _codeIsReachable = true;
+ }
+
+ private void EmitStatement(StatementNode statement)
+ {
+ // var tokens = statement.Tokens.ToArray();
+ // if (tokens.Length != 0)
+ // {
+ // _writer.WriteLine($"dbgloc {tokens[0].FileSpan.Span.Start.Line}");
+ // }
+
+ switch (statement)
+ {
+ case AssignmentNode assignment:
+ EmitAssignment(assignment);
+ break;
+ case BreakNode:
+ EmitBreak();
+ break;
+ case ContinueNode:
+ EmitContinue();
+ break;
+ case IfNode ifStatement:
+ EmitIf(ifStatement);
+ break;
+ case ReturnNode @return:
+ EmitReturn(@return);
+ break;
+ case StatementExpressionNode statementExpression:
+ EmitExpression(statementExpression.Expression);
+ break;
+ case VariableDeclarationNode variableDeclaration:
+ EmitVariableDeclaration(variableDeclaration);
+ break;
+ case WhileNode whileStatement:
+ EmitWhile(whileStatement);
+ break;
+ default:
+ throw new ArgumentOutOfRangeException(nameof(statement));
+ }
+ }
+
+ private void EmitAssignment(AssignmentNode assignment)
+ {
+ EmitCopyInto(assignment.Value, EmitAddressOfLValue(assignment.Target));
+ }
+
+ private void EmitBreak()
+ {
+ _writer.Indented($"jmp {_breakLabels.Peek()}");
+ _codeIsReachable = false;
+ }
+
+ private void EmitContinue()
+ {
+ _writer.Indented($"jmp {_continueLabels.Peek()}");
+ _codeIsReachable = false;
+ }
+
+ private void EmitIf(IfNode ifStatement)
+ {
+ var trueLabel = LabelName();
+ var falseLabel = LabelName();
+ var endLabel = LabelName();
+
+ var result = EmitExpression(ifStatement.Condition);
+ _writer.Indented($"jnz {result}, {trueLabel}, {falseLabel}");
+ _writer.WriteLine(trueLabel);
+ EmitBlock(ifStatement.Body);
+ _writer.Indented($"jmp {endLabel}");
+ _writer.WriteLine(falseLabel);
+ if (ifStatement.Else.HasValue)
+ {
+ ifStatement.Else.Value.Match(EmitIf, EmitBlock);
+ }
+
+ _writer.WriteLine(endLabel);
+ }
+
+ private void EmitReturn(ReturnNode @return)
+ {
+ if (@return.Value.HasValue)
+ {
+ var result = EmitExpression(@return.Value.Value);
+ _writer.Indented($"ret {result}");
+ }
+ else
+ {
+ _writer.Indented("ret");
+ }
+ }
+
+ private void EmitVariableDeclaration(VariableDeclarationNode variableDeclaration)
+ {
+ var name = $"%{variableDeclaration.Name}";
+ _writer.Indented($"{name} =l alloc8 {SizeOf(variableDeclaration.Type)}");
+
+ if (variableDeclaration.Assignment.HasValue)
+ {
+ EmitCopyInto(variableDeclaration.Assignment.Value, name);
+ }
+ }
+
+ private void EmitWhile(WhileNode whileStatement)
+ {
+ var conditionLabel = LabelName();
+ var iterationLabel = LabelName();
+ var endLabel = LabelName();
+
+ _breakLabels.Push(endLabel);
+ _continueLabels.Push(conditionLabel);
+
+ _writer.Indented($"jmp {conditionLabel}");
+ _writer.WriteLine(iterationLabel);
+ EmitBlock(whileStatement.Body);
+ _writer.WriteLine(conditionLabel);
+ var result = EmitExpression(whileStatement.Condition);
+ _writer.Indented($"jnz {result}, {iterationLabel}, {endLabel}");
+ _writer.WriteLine(endLabel);
+
+ _continueLabels.Pop();
+ _breakLabels.Pop();
+ }
+
+ private string EmitExpression(ExpressionNode expression)
+ {
+ // var tokens = expression.Tokens.ToArray();
+ // if (tokens.Length != 0)
+ // {
+ // _writer.WriteLine($"dbgloc {tokens[0].FileSpan.Span.Start.Line}");
+ // }
+
+ return expression switch
+ {
+ ArrayInitializerNode arrayInitializer => EmitArrayInitializer(arrayInitializer),
+ StructInitializerNode structInitializer => EmitStructInitializer(structInitializer),
+ AddressOfNode addressOf => EmitAddressOf(addressOf),
+ DereferenceNode dereference => EmitDereference(dereference),
+ BinaryExpressionNode binary => EmitBinaryExpression(binary),
+ FuncCallNode funcCall => EmitFuncCall(funcCall),
+ InterfaceFuncCallNode interfaceFuncCall => EmitInterfaceFuncCall(interfaceFuncCall),
+ ConvertToInterfaceNode convertToInterface => EmitConvertToInterface(convertToInterface),
+ ConvertIntNode convertInt => EmitConvertInt(convertInt),
+ ConvertFloatNode convertFloat => EmitConvertFloat(convertFloat),
+ VariableIdentifierNode identifier => EmitVariableIdentifier(identifier),
+ FuncIdentifierNode funcIdentifier => EmitFuncIdentifier(funcIdentifier),
+ FuncParameterIdentifierNode funcParameterIdentifier => EmitParameterFuncIdentifier(funcParameterIdentifier),
+ LiteralNode literal => EmitLiteral(literal),
+ UnaryExpressionNode unaryExpression => EmitUnaryExpression(unaryExpression),
+ StructFieldAccessNode structFieldAccess => EmitStructFieldAccess(structFieldAccess),
+ StructFuncCallNode structFuncCall => EmitStructFuncCall(structFuncCall),
+ ArrayIndexAccessNode arrayIndex => EmitArrayIndexAccess(arrayIndex),
+ _ => throw new ArgumentOutOfRangeException(nameof(expression))
+ };
+ }
+
+ private string EmitFuncIdentifier(FuncIdentifierNode funcIdent)
+ {
+ // todo(nub31): Support for extern funcs
+ return FuncName(funcIdent.Module, funcIdent.Name);
+ }
+
+ private string EmitVariableIdentifier(VariableIdentifierNode variableIdent)
+ {
+ var address = EmitAddressOfVariableIdent(variableIdent);
+
+ return variableIdent.Type.IsSimpleType(out _, out _)
+ ? EmitLoad(variableIdent.Type, address)
+ : address;
+ }
+
+ private string EmitParameterFuncIdentifier(FuncParameterIdentifierNode funcParameterIdent)
+ {
+ return "%" + funcParameterIdent.Name;
+ }
+
+ private string EmitArrayIndexAccess(ArrayIndexAccessNode arrayIndexAccess)
+ {
+ // var address = EmitAddressOfArrayIndexAccess(arrayIndexAccess);
+ // if (arrayIndexAccess.Type is StructTypeNode)
+ // {
+ // return address;
+ // }
+ //
+ // return EmitLoad(arrayIndexAccess.Type, address);
+ throw new NotImplementedException();
+ }
+
+ private string EmitArrayInitializer(ArrayInitializerNode arrayInitializer)
+ {
+ var capacity = EmitExpression(arrayInitializer.Capacity);
+ var elementSize = SizeOf(arrayInitializer.ElementType);
+
+ var capacityInBytes = TmpName();
+ _writer.Indented($"{capacityInBytes} =l mul {capacity}, {elementSize}");
+ var totalSize = TmpName();
+ _writer.Indented($"{totalSize} =l add {capacityInBytes}, 8");
+
+ var arrayPointer = TmpName();
+ _writer.Indented($"{arrayPointer} =l alloc8 {totalSize}");
+ _writer.Indented($"storel {capacity}, {arrayPointer}");
+
+ var dataPointer = TmpName();
+ _writer.Indented($"{dataPointer} =l add {arrayPointer}, 8");
+ EmitMemset(dataPointer, 0, capacityInBytes);
+
+ return arrayPointer;
+ }
+
+ private string EmitDereference(DereferenceNode dereference)
+ {
+ var address = EmitExpression(dereference.Expression);
+ if (dereference.Type is StructTypeNode)
+ {
+ return address;
+ }
+
+ return EmitLoad(dereference.Type, address);
+ }
+
+ private string EmitAddressOf(AddressOfNode addressOf)
+ {
+ return EmitAddressOfLValue(addressOf.LValue);
+ }
+
+ private string EmitAddressOfLValue(LValueExpressionNode addressOf)
+ {
+ return addressOf switch
+ {
+ ArrayIndexAccessNode arrayIndexAccess => EmitAddressOfArrayIndexAccess(arrayIndexAccess),
+ StructFieldAccessNode structFieldAccess => EmitAddressOfStructFieldAccess(structFieldAccess),
+ VariableIdentifierNode variableIdent => EmitAddressOfVariableIdent(variableIdent),
+ _ => throw new ArgumentOutOfRangeException(nameof(addressOf))
+ };
+ }
+
+ private string EmitAddressOfArrayIndexAccess(ArrayIndexAccessNode arrayIndexAccess)
+ {
+ var array = EmitExpression(arrayIndexAccess.Target);
+ var index = EmitExpression(arrayIndexAccess.Index);
+
+ var elementType = ((ArrayTypeNode)arrayIndexAccess.Target.Type).ElementType;
+
+ var offset = TmpName();
+ _writer.Indented($"{offset} =l mul {index}, {SizeOf(elementType)}");
+ _writer.Indented($"{offset} =l add {offset}, 8");
+ _writer.Indented($"{offset} =l add {array}, {offset}");
+ return offset;
+ }
+
+ private string EmitAddressOfStructFieldAccess(StructFieldAccessNode structFieldAccess)
+ {
+ var target = EmitExpression(structFieldAccess.Target);
+
+ var structType = TypeResolver.ResolveStructType(structFieldAccess.StructType.Module, structFieldAccess.StructType.Name, _moduleSignatures);
+ var offset = OffsetOf(structType, structFieldAccess.Field);
+
+ var address = TmpName();
+ _writer.Indented($"{address} =l add {target}, {offset}");
+ return address;
+ }
+
+ private string EmitAddressOfVariableIdent(VariableIdentifierNode variableIdent)
+ {
+ return "%" + variableIdent.Name;
+ }
+
+ private string EmitBinaryExpression(BinaryExpressionNode binaryExpression)
+ {
+ var left = EmitExpression(binaryExpression.Left);
+ var right = EmitExpression(binaryExpression.Right);
+
+ var outputName = TmpName();
+
+ var instruction = EmitBinaryInstructionForOperator(binaryExpression.Operator, binaryExpression.Left.Type);
+
+ _writer.Indented($"{outputName} {QBEAssign(binaryExpression.Left.Type)} {instruction} {left}, {right}");
+ return outputName;
+ }
+
+ private static string EmitBinaryInstructionForOperator(BinaryOperator op, TypeNode type)
+ {
+ return op switch
+ {
+ BinaryOperator.RightShift => type switch
+ {
+ IntTypeNode { Signed: true } => "sar",
+ IntTypeNode { Signed: false } => "shr",
+ _ => throw new NotSupportedException($"Right shift not supported for type '{type}'")
+ },
+ BinaryOperator.BitwiseAnd => "and",
+ BinaryOperator.BitwiseOr => "or",
+ BinaryOperator.BitwiseXor => "xor",
+ BinaryOperator.LeftShift => "shl",
+ BinaryOperator.Divide => type switch
+ {
+ IntTypeNode { Signed: true } => "div",
+ IntTypeNode { Signed: false } => "udiv",
+ FloatTypeNode => "div",
+ _ => throw new NotSupportedException($"Division not supported for type '{type}'")
+ },
+ BinaryOperator.Modulo => type switch
+ {
+ IntTypeNode { Signed: true } => "rem",
+ IntTypeNode { Signed: false } => "urem",
+ _ => throw new NotSupportedException($"Modulo not supported for type '{type}'")
+ },
+ BinaryOperator.Plus => "add",
+ BinaryOperator.Minus => "sub",
+ BinaryOperator.Multiply => "mul",
+ BinaryOperator.Equal => type switch
+ {
+ IntTypeNode intType => intType.Width switch
+ {
+ <= 32 => "ceqw",
+ 64 => "ceql",
+ _ => throw new ArgumentOutOfRangeException()
+ },
+ FloatTypeNode floatType => floatType.Width switch
+ {
+ 32 => "ceqs",
+ 64 => "ceqd",
+ _ => throw new ArgumentOutOfRangeException()
+ },
+ _ => throw new NotSupportedException($"Equality comparison not supported for type '{type}'")
+ },
+ BinaryOperator.NotEqual => type switch
+ {
+ IntTypeNode intType => intType.Width switch
+ {
+ <= 32 => "cnew",
+ 64 => "cnel",
+ _ => throw new ArgumentOutOfRangeException()
+ },
+ FloatTypeNode floatType => floatType.Width switch
+ {
+ 32 => "cnes",
+ 64 => "cned",
+ _ => throw new ArgumentOutOfRangeException()
+ },
+ _ => throw new NotSupportedException($"Inequality comparison not supported for type '{type}'")
+ },
+ BinaryOperator.LessThan => type switch
+ {
+ IntTypeNode { Signed: true } intType => intType.Width switch
+ {
+ <= 32 => "csltw",
+ 64 => "csltl",
+ _ => throw new ArgumentOutOfRangeException()
+ },
+ IntTypeNode { Signed: false } intType => intType.Width switch
+ {
+ <= 32 => "cultw",
+ 64 => "cultl",
+ _ => throw new ArgumentOutOfRangeException()
+ },
+ FloatTypeNode floatType => floatType.Width switch
+ {
+ 32 => "clts",
+ 64 => "cltd",
+ _ => throw new ArgumentOutOfRangeException()
+ },
+ _ => throw new NotSupportedException($"Less than comparison not supported for type '{type}'")
+ },
+ BinaryOperator.LessThanOrEqual => type switch
+ {
+ IntTypeNode { Signed: true } intType => intType.Width switch
+ {
+ <= 32 => "cslew",
+ 64 => "cslel",
+ _ => throw new ArgumentOutOfRangeException()
+ },
+ IntTypeNode { Signed: false } intType => intType.Width switch
+ {
+ <= 32 => "culew",
+ 64 => "culel",
+ _ => throw new ArgumentOutOfRangeException()
+ },
+ FloatTypeNode floatType => floatType.Width switch
+ {
+ 32 => "cles",
+ 64 => "cled",
+ _ => throw new ArgumentOutOfRangeException()
+ },
+ _ => throw new NotSupportedException($"Less than or equal comparison not supported for type '{type}'")
+ },
+ BinaryOperator.GreaterThan => type switch
+ {
+ IntTypeNode { Signed: true } intType => intType.Width switch
+ {
+ <= 32 => "csgtw",
+ 64 => "csgtl",
+ _ => throw new ArgumentOutOfRangeException()
+ },
+ IntTypeNode { Signed: false } intType => intType.Width switch
+ {
+ <= 32 => "cugtw",
+ 64 => "cugtl",
+ _ => throw new ArgumentOutOfRangeException()
+ },
+ FloatTypeNode floatType => floatType.Width switch
+ {
+ 32 => "cgts",
+ 64 => "cgtd",
+ _ => throw new ArgumentOutOfRangeException()
+ },
+ _ => throw new NotSupportedException($"Greater than comparison not supported for type '{type}'")
+ },
+ BinaryOperator.GreaterThanOrEqual => type switch
+ {
+ IntTypeNode { Signed: true } intType => intType.Width switch
+ {
+ <= 32 => "csgew",
+ 64 => "csgel",
+ _ => throw new ArgumentOutOfRangeException()
+ },
+ IntTypeNode { Signed: false } intType => intType.Width switch
+ {
+ <= 32 => "cugew",
+ 64 => "cugel",
+ _ => throw new ArgumentOutOfRangeException()
+ },
+ FloatTypeNode floatType => floatType.Width switch
+ {
+ 32 => "cges",
+ 64 => "cged",
+ _ => throw new ArgumentOutOfRangeException()
+ },
+ _ => throw new NotSupportedException($"Greater than or equal comparison not supported for type '{type}'")
+ },
+ // todo(nub31): Implement short circuiting
+ BinaryOperator.LogicalAnd => "and",
+ BinaryOperator.LogicalOr => "or",
+ _ => throw new ArgumentOutOfRangeException(nameof(op))
+ };
+ }
+
+ private string EmitLiteral(LiteralNode literal)
+ {
+ switch (literal.Kind)
+ {
+ case LiteralKind.Integer:
+ {
+ if (literal.Type is FloatTypeNode { Width: 32 })
+ {
+ var value = float.Parse(literal.Value, CultureInfo.InvariantCulture);
+ var bits = BitConverter.SingleToInt32Bits(value);
+ return bits.ToString();
+ }
+
+ if (literal.Type is FloatTypeNode { Width: 64 })
+ {
+ var value = double.Parse(literal.Value, CultureInfo.InvariantCulture);
+ var bits = BitConverter.DoubleToInt64Bits(value);
+ return bits.ToString();
+ }
+
+ if (literal.Type is IntTypeNode)
+ {
+ return literal.Value;
+ }
+
+ break;
+ }
+ case LiteralKind.Float:
+ {
+ if (literal.Type is IntTypeNode)
+ {
+ return literal.Value.Split(".").First();
+ }
+
+ if (literal.Type is FloatTypeNode { Width: 32 })
+ {
+ var value = float.Parse(literal.Value, CultureInfo.InvariantCulture);
+ var bits = BitConverter.SingleToInt32Bits(value);
+ return bits.ToString();
+ }
+
+ if (literal.Type is FloatTypeNode { Width: 64 })
+ {
+ var value = double.Parse(literal.Value, CultureInfo.InvariantCulture);
+ var bits = BitConverter.DoubleToInt64Bits(value);
+ return bits.ToString();
+ }
+
+ break;
+ }
+ case LiteralKind.String:
+ {
+ if (literal.Type is StringTypeNode)
+ {
+ var stringLiteral = new StringLiteral(literal.Value, StringName());
+ _stringLiterals.Add(stringLiteral);
+ return stringLiteral.Name;
+ }
+
+ if (literal.Type is CStringTypeNode)
+ {
+ var cStringLiteral = new CStringLiteral(literal.Value, CStringName());
+ _cStringLiterals.Add(cStringLiteral);
+ return cStringLiteral.Name;
+ }
+
+ break;
+ }
+ case LiteralKind.Bool:
+ {
+ if (literal.Type is BoolTypeNode)
+ {
+ return bool.Parse(literal.Value) ? "1" : "0";
+ }
+
+ break;
+ }
+ }
+
+ throw new NotSupportedException($"Cannot create literal of kind '{literal.Kind}' for type {literal.Type}");
+ }
+
+ private string EmitStructInitializer(StructInitializerNode structInitializer)
+ {
+ var destination = TmpName();
+ var size = SizeOf(structInitializer.StructType);
+ _writer.Indented($"{destination} =l alloc8 {size}");
+
+ foreach (var (field, value) in structInitializer.Initializers)
+ {
+ var offset = TmpName();
+ _writer.Indented($"{offset} =l add {destination}, {OffsetOf(structInitializer.StructType, field)}");
+ EmitCopyInto(value, offset);
+ }
+
+ return destination;
+ }
+
+ private string EmitUnaryExpression(UnaryExpressionNode unaryExpression)
+ {
+ var operand = EmitExpression(unaryExpression.Operand);
+ var outputName = TmpName();
+
+ switch (unaryExpression.Operator)
+ {
+ case UnaryOperator.Negate:
+ {
+ switch (unaryExpression.Operand.Type)
+ {
+ case IntTypeNode { Signed: true, Width: 64 }:
+ _writer.Indented($"{outputName} =l neg {operand}");
+ return outputName;
+ case IntTypeNode { Signed: true, Width: 8 or 16 or 32 }:
+ _writer.Indented($"{outputName} =w neg {operand}");
+ return outputName;
+ case FloatTypeNode { Width: 64 }:
+ _writer.Indented($"{outputName} =d neg {operand}");
+ return outputName;
+ case FloatTypeNode { Width: 32 }:
+ _writer.Indented($"{outputName} =s neg {operand}");
+ return outputName;
+ }
+
+ break;
+ }
+ case UnaryOperator.Invert:
+ {
+ switch (unaryExpression.Operand.Type)
+ {
+ case BoolTypeNode:
+ _writer.Indented($"{outputName} =w xor {operand}, 1");
+ return outputName;
+ }
+
+ break;
+ }
+ default:
+ {
+ throw new ArgumentOutOfRangeException();
+ }
+ }
+
+ throw new NotSupportedException($"Unary operator {unaryExpression.Operator} for type {unaryExpression.Operand.Type} not supported");
+ }
+
+ private string EmitStructFieldAccess(StructFieldAccessNode structFieldAccess)
+ {
+ var address = EmitAddressOfStructFieldAccess(structFieldAccess);
+ if (structFieldAccess.Type is StructTypeNode)
+ {
+ return address;
+ }
+
+ return EmitLoad(structFieldAccess.Type, address);
+ }
+
+ private string EmitStructFuncCall(StructFuncCallNode structFuncCall)
+ {
+ var func = StructFuncName(structFuncCall.StructType.Module, structFuncCall.StructType.Name, structFuncCall.Name);
+
+ var thisParameter = EmitExpression(structFuncCall.StructExpression);
+
+ List parameterStrings = [$"l {thisParameter}"];
+
+ foreach (var parameter in structFuncCall.Parameters)
+ {
+ var copy = EmitCopy(parameter);
+ parameterStrings.Add($"{FuncQBETypeName(parameter.Type)} {copy}");
+ }
+
+ if (structFuncCall.Type is VoidTypeNode)
+ {
+ _writer.Indented($"call {func}({string.Join(", ", parameterStrings)})");
+ return string.Empty;
+ }
+ else
+ {
+ var outputName = TmpName();
+ _writer.Indented($"{outputName} {QBEAssign(structFuncCall.Type)} call {func}({string.Join(", ", parameterStrings)})");
+ return outputName;
+ }
+ }
+
+ private string EmitInterfaceFuncCall(InterfaceFuncCallNode interfaceFuncCall)
+ {
+ var target = EmitExpression(interfaceFuncCall.InterfaceExpression);
+
+ var functionIndex = interfaceFuncCall.InterfaceType.Functions.ToList().FindIndex(x => x.Name == interfaceFuncCall.Name);
+ var offset = functionIndex * 8;
+
+ var vtable = TmpName();
+ _writer.Indented($"{vtable} =l loadl {target}");
+
+ var funcOffset = TmpName();
+ _writer.Indented($"{funcOffset} =l add {vtable}, {offset}");
+
+ var func = TmpName();
+ _writer.Indented($"{func} =l loadl {funcOffset}");
+
+ var data = TmpName();
+ _writer.Indented($"{data} =l add {target}, 8");
+ _writer.Indented($"{data} =l loadl {data}");
+
+ List parameterStrings = [$"l {data}"];
+
+ foreach (var parameter in interfaceFuncCall.Parameters)
+ {
+ var copy = EmitCopy(parameter);
+ parameterStrings.Add($"{FuncQBETypeName(parameter.Type)} {copy}");
+ }
+
+ if (interfaceFuncCall.Type is VoidTypeNode)
+ {
+ _writer.Indented($"call {func}({string.Join(", ", parameterStrings)})");
+ return string.Empty;
+ }
+ else
+ {
+ var outputName = TmpName();
+ _writer.Indented($"{outputName} {QBEAssign(interfaceFuncCall.Type)} call {func}({string.Join(", ", parameterStrings)})");
+ return outputName;
+ }
+ }
+
+ private string EmitConvertToInterface(ConvertToInterfaceNode convertToInterface)
+ {
+ var implementation = EmitExpression(convertToInterface.Implementation);
+
+ var vtableOffset = 0;
+ foreach (var interfaceImplementation in convertToInterface.StructType.InterfaceImplementations)
+ {
+ if (interfaceImplementation == convertToInterface.InterfaceType)
+ {
+ break;
+ }
+
+ vtableOffset += interfaceImplementation.Functions.Count * 8;
+ }
+
+ var destination = TmpName();
+ _writer.Indented($"{destination} =l alloc8 {SizeOf(convertToInterface.InterfaceType)}");
+
+ var interfaceVtablePointer = TmpName();
+ _writer.Indented($"{interfaceVtablePointer} =l add {StructVtableName(convertToInterface.StructType.Module, convertToInterface.StructType.Name)}, {vtableOffset}");
+ _writer.Indented($"storel {interfaceVtablePointer}, {destination}");
+
+ var objectPointer = TmpName();
+ _writer.Indented($"{objectPointer} =l add {destination}, 8");
+ _writer.Indented($"storel {implementation}, {objectPointer}");
+
+ return destination;
+ }
+
+ private string EmitConvertInt(ConvertIntNode convertInt)
+ {
+ var value = EmitExpression(convertInt.Value);
+
+ if (convertInt.ValueType.Width >= convertInt.TargetType.Width)
+ {
+ return value;
+ }
+
+ var method = convertInt.ValueType.Signed switch
+ {
+ true => convertInt.ValueType.Width switch
+ {
+ 8 => "extsb",
+ 16 => "extsh",
+ 32 => "extsw",
+ _ => throw new ArgumentOutOfRangeException()
+ },
+ false => convertInt.ValueType.Width switch
+ {
+ 8 => "extub",
+ 16 => "extuh",
+ 32 => "extuw",
+ _ => throw new ArgumentOutOfRangeException()
+ }
+ };
+
+ var result = TmpName();
+ _writer.Indented($"{result} {QBEAssign(convertInt.TargetType)} {method} {value}");
+ return result;
+ }
+
+ private string EmitConvertFloat(ConvertFloatNode convertFloat)
+ {
+ var value = EmitExpression(convertFloat.Value);
+
+ if (convertFloat.ValueType.Width == convertFloat.TargetType.Width)
+ {
+ return value;
+ }
+
+ var method = convertFloat.ValueType.Width switch
+ {
+ 32 => "exts",
+ 64 => "truncd",
+ _ => throw new ArgumentOutOfRangeException()
+ };
+
+ var result = TmpName();
+ _writer.Indented($"{result} {QBEAssign(convertFloat.TargetType)} {method} {value}");
+ return result;
+ }
+
+ private string EmitFuncCall(FuncCallNode funcCall)
+ {
+ var funcPointer = EmitExpression(funcCall.Expression);
+
+ var parameterStrings = new List();
+
+ foreach (var parameter in funcCall.Parameters)
+ {
+ var copy = EmitCopy(parameter);
+ parameterStrings.Add($"{FuncQBETypeName(parameter.Type)} {copy}");
+ }
+
+ if (funcCall.Type is VoidTypeNode)
+ {
+ _writer.Indented($"call {funcPointer}({string.Join(", ", parameterStrings)})");
+ return string.Empty;
+ }
+ else
+ {
+ var outputName = TmpName();
+ _writer.Indented($"{outputName} {QBEAssign(funcCall.Type)} call {funcPointer}({string.Join(", ", parameterStrings)})");
+ return outputName;
+ }
+ }
+
+ private static int SizeOf(TypeNode type)
+ {
+ return type switch
+ {
+ SimpleTypeNode simple => simple.StorageSize switch
+ {
+ StorageSize.Void => 0,
+ StorageSize.I8 or StorageSize.U8 => 1,
+ StorageSize.I16 or StorageSize.U16 => 2,
+ StorageSize.I32 or StorageSize.U32 or StorageSize.F32 => 4,
+ StorageSize.I64 or StorageSize.U64 or StorageSize.F64 => 8,
+ _ => throw new ArgumentOutOfRangeException(nameof(type), $"Unknown storage size: {simple.StorageSize}")
+ },
+ CStringTypeNode => 8,
+ StringTypeNode => 8,
+ ArrayTypeNode => 8,
+ StructTypeNode structType => CalculateStructSize(structType),
+ InterfaceTypeNode => 16,
+ _ => throw new ArgumentOutOfRangeException(nameof(type), $"Unknown type: {type.GetType()}")
+ };
+ }
+
+ private static int CalculateStructSize(StructTypeNode structType)
+ {
+ var offset = 0;
+
+ var fields = new List(structType.Fields.Count);
+
+ foreach (var field in structType.Fields)
+ {
+ var fieldAlignment = AlignmentOf(field.Type);
+ offset = AlignTo(offset, fieldAlignment);
+ offset += SizeOf(field.Type);
+ }
+
+ var structAlignment = CalculateStructAlignment(structType);
+ return AlignTo(offset, structAlignment);
+ }
+
+ private static int AlignmentOf(TypeNode type)
+ {
+ return type switch
+ {
+ SimpleTypeNode simple => simple.StorageSize switch
+ {
+ StorageSize.Void => 1,
+ StorageSize.I8 or StorageSize.U8 => 1,
+ StorageSize.I16 or StorageSize.U16 => 2,
+ StorageSize.I32 or StorageSize.U32 or StorageSize.F32 => 4,
+ StorageSize.I64 or StorageSize.U64 or StorageSize.F64 => 8,
+ _ => throw new ArgumentOutOfRangeException(nameof(type), $"Unknown storage size: {simple.StorageSize}")
+ },
+ CStringTypeNode => 8,
+ StringTypeNode => 8,
+ ArrayTypeNode => 8,
+ StructTypeNode structType => CalculateStructAlignment(structType),
+ InterfaceTypeNode => 8,
+ _ => throw new ArgumentOutOfRangeException(nameof(type), $"Unknown type: {type.GetType()}")
+ };
+ }
+
+ private static int CalculateStructAlignment(StructTypeNode structType)
+ {
+ var maxAlignment = 1;
+
+ if (structType.InterfaceImplementations.Any())
+ {
+ maxAlignment = Math.Max(maxAlignment, 8);
+ }
+
+ foreach (var field in structType.Fields)
+ {
+ var fieldAlignment = AlignmentOf(field.Type);
+ maxAlignment = Math.Max(maxAlignment, fieldAlignment);
+ }
+
+ return maxAlignment;
+ }
+
+ private static int AlignTo(int offset, int alignment)
+ {
+ return (offset + alignment - 1) & ~(alignment - 1);
+ }
+
+ private static int OffsetOf(StructTypeNode structDef, string member)
+ {
+ var offset = 0;
+
+ foreach (var field in structDef.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");
+ }
+
+ #region Naming utilities
+
+ private string TmpName()
+ {
+ return $"%t{++_tmpIndex}";
+ }
+
+ private string LabelName()
+ {
+ return $"@l{++_labelIndex}";
+ }
+
+ private string CStringName()
+ {
+ return $"$cstring{++_cStringLiteralIndex}";
+ }
+
+ private string StringName()
+ {
+ return $"$string{++_stringLiteralIndex}";
+ }
+
+ private string FuncName(string module, string name)
+ {
+ var type = TypeResolver.ResolveFunctionType(module, name, _moduleSignatures);
+ var symbol = type.ExternSymbol ?? $"{module}.{name}";
+ return "$" + symbol;
+ }
+
+ private string StructTypeName(string module, string name)
+ {
+ return $":{module}.{name}";
+ }
+
+ private string StructFuncName(string module, string structName, string funcName)
+ {
+ return $"${module}.{structName}_func.{funcName}";
+ }
+
+ private string StructCtorName(string module, string structName)
+ {
+ return $"${module}.{structName}_ctor";
+ }
+
+ private string StructVtableName(string module, string structName)
+ {
+ return $"${module}.{structName}_vtable";
+ }
+
+ #endregion
+}
+
+public class StringLiteral(string value, string name)
+{
+ public string Value { get; } = value;
+ public string Name { get; } = name;
+}
+
+public class CStringLiteral(string value, string name)
+{
+ public string Value { get; } = value;
+ public string Name { get; } = name;
+}
\ No newline at end of file
diff --git a/compiler/NubLang/Generation/QBE/QBEWriter.cs b/compiler/NubLang/Generation/QBE/QBEWriter.cs
new file mode 100644
index 0000000..2ff8668
--- /dev/null
+++ b/compiler/NubLang/Generation/QBE/QBEWriter.cs
@@ -0,0 +1,39 @@
+using System.Text;
+
+namespace NubLang.Generation.QBE;
+
+internal class QBEWriter
+{
+ private readonly StringBuilder _builder = new();
+
+ public void Indented(string value)
+ {
+ _builder.Append('\t');
+ _builder.AppendLine(value);
+ }
+
+ public void Comment(string comment)
+ {
+ _builder.AppendLine("# " + comment);
+ }
+
+ public void WriteLine(string text)
+ {
+ _builder.AppendLine(text);
+ }
+
+ public void Write(string text)
+ {
+ _builder.Append(text);
+ }
+
+ public void NewLine()
+ {
+ _builder.AppendLine();
+ }
+
+ public override string ToString()
+ {
+ return _builder.ToString();
+ }
+}
\ No newline at end of file
diff --git a/compiler/NubLang/NubLang.csproj b/compiler/NubLang/NubLang.csproj
new file mode 100644
index 0000000..b682a68
--- /dev/null
+++ b/compiler/NubLang/NubLang.csproj
@@ -0,0 +1,10 @@
+
+
+
+ net9.0
+ enable
+ enable
+ true
+
+
+
diff --git a/compiler/NubLang/Optional.cs b/compiler/NubLang/Optional.cs
new file mode 100644
index 0000000..a467b5b
--- /dev/null
+++ b/compiler/NubLang/Optional.cs
@@ -0,0 +1,82 @@
+using System.Diagnostics.CodeAnalysis;
+
+namespace NubLang;
+
+public static class Optional
+{
+ public static Optional Empty() => new();
+
+ ///
+ /// Alias for creating an Optional which allows for implicit types
+ ///
+ public static Optional OfNullable(TValue? value)
+ {
+ return value ?? Optional.Empty();
+ }
+
+ ///
+ /// Converts a nullable type to an Optional
+ ///
+ public static Optional ToOptional(this TValue? value)
+ {
+ return OfNullable(value);
+ }
+}
+
+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/compiler/NubLang/Parsing/Parser.cs b/compiler/NubLang/Parsing/Parser.cs
new file mode 100644
index 0000000..3766e34
--- /dev/null
+++ b/compiler/NubLang/Parsing/Parser.cs
@@ -0,0 +1,883 @@
+using System.Diagnostics.CodeAnalysis;
+using NubLang.Diagnostics;
+using NubLang.Parsing.Syntax;
+using NubLang.Tokenization;
+
+namespace NubLang.Parsing;
+
+public sealed class Parser
+{
+ private readonly List _diagnostics = [];
+ private IReadOnlyList _tokens = [];
+ private int _tokenIndex;
+ private string _moduleName = string.Empty;
+
+ private Token? CurrentToken => _tokenIndex < _tokens.Count ? _tokens[_tokenIndex] : null;
+ private bool HasToken => CurrentToken != null;
+
+ public IReadOnlyList GetDiagnostics()
+ {
+ return _diagnostics;
+ }
+
+ public SyntaxTree Parse(IReadOnlyList tokens)
+ {
+ _diagnostics.Clear();
+ _tokens = tokens;
+ _tokenIndex = 0;
+ _moduleName = string.Empty;
+
+ var metadata = ParseMetadata();
+ var definitions = ParseDefinitions();
+
+ return new SyntaxTree(definitions, metadata);
+ }
+
+ private SyntaxTreeMetadata ParseMetadata()
+ {
+ var imports = new List();
+
+ try
+ {
+ ExpectSymbol(Symbol.Module);
+ _moduleName = ExpectLiteral(LiteralKind.String).Value;
+
+ while (TryExpectSymbol(Symbol.Import))
+ {
+ imports.Add(ExpectIdentifier().Value);
+ }
+ }
+ catch (ParseException e)
+ {
+ _diagnostics.Add(e.Diagnostic);
+ while (HasToken)
+ {
+ if (CurrentToken is SymbolToken { Symbol: Symbol.Module or Symbol.Import })
+ {
+ break;
+ }
+
+ Next();
+ }
+ }
+
+ return new SyntaxTreeMetadata(_moduleName, imports);
+ }
+
+ private List ParseDefinitions()
+ {
+ var definitions = new List();
+
+ while (HasToken)
+ {
+ try
+ {
+ var startIndex = _tokenIndex;
+ var exported = TryExpectSymbol(Symbol.Export);
+
+ if (TryExpectSymbol(Symbol.Extern))
+ {
+ var externSymbol = ExpectLiteral(LiteralKind.String).Value;
+ ExpectSymbol(Symbol.Func);
+ definitions.Add(ParseFunc(startIndex, exported, externSymbol));
+ continue;
+ }
+
+ var keyword = ExpectSymbol();
+ var definition = keyword.Symbol switch
+ {
+ Symbol.Func => ParseFunc(startIndex, exported, null),
+ Symbol.Struct => ParseStruct(startIndex, exported),
+ Symbol.Interface => ParseInterface(startIndex, exported),
+ _ => throw new ParseException(Diagnostic
+ .Error($"Expected 'func', 'struct' or 'interface' but found '{keyword.Symbol}'")
+ .WithHelp("Valid definition keywords are 'func', 'struct' and 'interface'")
+ .At(keyword)
+ .Build())
+ };
+
+ definitions.Add(definition);
+ }
+ catch (ParseException e)
+ {
+ _diagnostics.Add(e.Diagnostic);
+ while (HasToken)
+ {
+ if (CurrentToken is SymbolToken { Symbol: Symbol.Extern or Symbol.Func or Symbol.Struct or Symbol.Interface })
+ {
+ break;
+ }
+
+ Next();
+ }
+ }
+ }
+
+ return definitions;
+ }
+
+ private FuncSignatureSyntax ParseFuncSignature()
+ {
+ var startIndex = _tokenIndex;
+ List parameters = [];
+
+ ExpectSymbol(Symbol.OpenParen);
+
+ while (!TryExpectSymbol(Symbol.CloseParen))
+ {
+ parameters.Add(ParseFuncParameter());
+
+ if (!TryExpectSymbol(Symbol.Comma))
+ {
+ ExpectSymbol(Symbol.CloseParen);
+ break;
+ }
+ }
+
+ var returnType = TryExpectSymbol(Symbol.Colon) ? ParseType() : new VoidTypeSyntax([]);
+
+ return new FuncSignatureSyntax(GetTokens(startIndex), parameters, returnType);
+ }
+
+ private FuncParameterSyntax ParseFuncParameter()
+ {
+ var startIndex = _tokenIndex;
+ var name = ExpectIdentifier();
+ ExpectSymbol(Symbol.Colon);
+ var type = ParseType();
+
+ return new FuncParameterSyntax(GetTokens(startIndex), name.Value, type);
+ }
+
+ private FuncSyntax ParseFunc(int startIndex, bool exported, string? externSymbol)
+ {
+ var name = ExpectIdentifier();
+ var signature = ParseFuncSignature();
+
+ BlockSyntax? body = null;
+ if (CurrentToken is SymbolToken { Symbol: Symbol.OpenBrace })
+ {
+ Next();
+ body = ParseBlock();
+ }
+
+ return new FuncSyntax(GetTokens(startIndex), name.Value, exported, externSymbol, signature, body);
+ }
+
+ private DefinitionSyntax ParseStruct(int startIndex, bool exported)
+ {
+ var name = ExpectIdentifier();
+ var interfaceImplementations = new List();
+
+ if (TryExpectSymbol(Symbol.Colon))
+ {
+ do
+ {
+ var interfaceType = ParseType();
+ interfaceImplementations.Add(interfaceType);
+ } while (TryExpectSymbol(Symbol.Comma));
+ }
+
+ ExpectSymbol(Symbol.OpenBrace);
+
+ List fields = [];
+ List funcs = [];
+
+ var fieldIndex = 0;
+
+ while (!TryExpectSymbol(Symbol.CloseBrace))
+ {
+ var memberStartIndex = _tokenIndex;
+
+ if (TryExpectSymbol(Symbol.Func))
+ {
+ var funcName = ExpectIdentifier().Value;
+ var funcSignature = ParseFuncSignature();
+ var funcBody = ParseBlock();
+
+ funcs.Add(new StructFuncSyntax(GetTokens(memberStartIndex), funcName, funcSignature, funcBody));
+ }
+ else
+ {
+ var fieldName = ExpectIdentifier().Value;
+ ExpectSymbol(Symbol.Colon);
+ var fieldType = ParseType();
+
+ var fieldValue = Optional.Empty();
+
+ if (TryExpectSymbol(Symbol.Assign))
+ {
+ fieldValue = ParseExpression();
+ }
+
+ fields.Add(new StructFieldSyntax(GetTokens(memberStartIndex), fieldIndex++, fieldName, fieldType, fieldValue));
+ }
+ }
+
+ return new StructSyntax(GetTokens(startIndex), name.Value, exported, fields, funcs, interfaceImplementations);
+ }
+
+ private InterfaceSyntax ParseInterface(int startIndex, bool exported)
+ {
+ var name = ExpectIdentifier();
+
+ ExpectSymbol(Symbol.OpenBrace);
+
+ List functions = [];
+
+ while (!TryExpectSymbol(Symbol.CloseBrace))
+ {
+ var funcStartIndex = _tokenIndex;
+
+ ExpectSymbol(Symbol.Func);
+
+ var funcName = ExpectIdentifier().Value;
+ var signature = ParseFuncSignature();
+
+ functions.Add(new InterfaceFuncSyntax(GetTokens(funcStartIndex), funcName, signature));
+ }
+
+ return new InterfaceSyntax(GetTokens(startIndex), name.Value, exported, functions);
+ }
+
+ private StatementSyntax ParseStatement()
+ {
+ if (CurrentToken is SymbolToken symbol)
+ {
+ switch (symbol.Symbol)
+ {
+ case Symbol.Return:
+ return ParseReturn();
+ case Symbol.If:
+ return ParseIf();
+ case Symbol.While:
+ return ParseWhile();
+ case Symbol.Let:
+ return ParseVariableDeclaration();
+ case Symbol.Break:
+ return ParseBreak();
+ case Symbol.Continue:
+ return ParseContinue();
+ }
+ }
+
+ return ParseStatementExpression();
+ }
+
+ private StatementSyntax ParseStatementExpression()
+ {
+ var startIndex = _tokenIndex;
+ var expr = ParseExpression();
+
+ if (TryExpectSymbol(Symbol.Assign))
+ {
+ var value = ParseExpression();
+ return new AssignmentSyntax(GetTokens(startIndex), expr, value);
+ }
+
+ return new StatementExpressionSyntax(GetTokens(startIndex), expr);
+ }
+
+ private VariableDeclarationSyntax ParseVariableDeclaration()
+ {
+ var startIndex = _tokenIndex;
+ ExpectSymbol(Symbol.Let);
+ var name = ExpectIdentifier().Value;
+
+ var explicitType = Optional.Empty();
+ if (TryExpectSymbol(Symbol.Colon))
+ {
+ explicitType = ParseType();
+ }
+
+ var assignment = Optional.Empty();
+ if (TryExpectSymbol(Symbol.Assign))
+ {
+ assignment = ParseExpression();
+ }
+
+ return new VariableDeclarationSyntax(GetTokens(startIndex), name, explicitType, assignment);
+ }
+
+ private StatementSyntax ParseBreak()
+ {
+ var startIndex = _tokenIndex;
+ ExpectSymbol(Symbol.Break);
+ return new BreakSyntax(GetTokens(startIndex));
+ }
+
+ private StatementSyntax ParseContinue()
+ {
+ var startIndex = _tokenIndex;
+ ExpectSymbol(Symbol.Continue);
+ return new ContinueSyntax(GetTokens(startIndex));
+ }
+
+ private ReturnSyntax ParseReturn()
+ {
+ var startIndex = _tokenIndex;
+ ExpectSymbol(Symbol.Return);
+
+ var value = Optional.Empty();
+
+ if (!TryExpectSymbol(Symbol.Semi))
+ {
+ value = ParseExpression();
+ }
+
+ return new ReturnSyntax(GetTokens(startIndex), value);
+ }
+
+ private IfSyntax ParseIf()
+ {
+ var startIndex = _tokenIndex;
+ ExpectSymbol(Symbol.If);
+ var condition = ParseExpression();
+ var body = ParseBlock();
+
+ var elseStatement = Optional>.Empty();
+ if (TryExpectSymbol(Symbol.Else))
+ {
+ elseStatement = TryExpectSymbol(Symbol.If)
+ ? (Variant)ParseIf()
+ : (Variant)ParseBlock();
+ }
+
+ return new IfSyntax(GetTokens(startIndex), condition, body, elseStatement);
+ }
+
+ private WhileSyntax ParseWhile()
+ {
+ var startIndex = _tokenIndex;
+ ExpectSymbol(Symbol.While);
+ var condition = ParseExpression();
+ var body = ParseBlock();
+ return new WhileSyntax(GetTokens(startIndex), condition, body);
+ }
+
+ private ExpressionSyntax ParseExpression(int precedence = 0)
+ {
+ var startIndex = _tokenIndex;
+ var left = ParsePrimaryExpression();
+
+ while (CurrentToken is SymbolToken symbolToken && TryGetBinaryOperator(symbolToken.Symbol, out var op) && GetBinaryOperatorPrecedence(op.Value) >= precedence)
+ {
+ Next();
+ var right = ParseExpression(GetBinaryOperatorPrecedence(op.Value) + 1);
+ left = new BinaryExpressionSyntax(GetTokens(startIndex), left, op.Value, right);
+ }
+
+ return left;
+ }
+
+ private static int GetBinaryOperatorPrecedence(BinaryOperatorSyntax operatorSyntax)
+ {
+ return operatorSyntax switch
+ {
+ BinaryOperatorSyntax.Multiply => 10,
+ BinaryOperatorSyntax.Divide => 10,
+ BinaryOperatorSyntax.Modulo => 10,
+
+ BinaryOperatorSyntax.Plus => 9,
+ BinaryOperatorSyntax.Minus => 9,
+
+ BinaryOperatorSyntax.LeftShift => 8,
+ BinaryOperatorSyntax.RightShift => 8,
+
+ BinaryOperatorSyntax.GreaterThan => 7,
+ BinaryOperatorSyntax.GreaterThanOrEqual => 7,
+ BinaryOperatorSyntax.LessThan => 7,
+ BinaryOperatorSyntax.LessThanOrEqual => 7,
+
+ BinaryOperatorSyntax.Equal => 7,
+ BinaryOperatorSyntax.NotEqual => 7,
+
+ BinaryOperatorSyntax.BitwiseAnd => 6,
+ BinaryOperatorSyntax.BitwiseXor => 5,
+ BinaryOperatorSyntax.BitwiseOr => 4,
+
+ BinaryOperatorSyntax.LogicalAnd => 3,
+ BinaryOperatorSyntax.LogicalOr => 2,
+
+ _ => throw new ArgumentOutOfRangeException(nameof(operatorSyntax), operatorSyntax, null)
+ };
+ }
+
+ private bool TryGetBinaryOperator(Symbol symbol, [NotNullWhen(true)] out BinaryOperatorSyntax? binaryExpressionOperator)
+ {
+ switch (symbol)
+ {
+ case Symbol.Equal:
+ binaryExpressionOperator = BinaryOperatorSyntax.Equal;
+ return true;
+ case Symbol.NotEqual:
+ binaryExpressionOperator = BinaryOperatorSyntax.NotEqual;
+ return true;
+ case Symbol.LessThan:
+ binaryExpressionOperator = BinaryOperatorSyntax.LessThan;
+ return true;
+ case Symbol.LessThanOrEqual:
+ binaryExpressionOperator = BinaryOperatorSyntax.LessThanOrEqual;
+ return true;
+ case Symbol.GreaterThan:
+ binaryExpressionOperator = BinaryOperatorSyntax.GreaterThan;
+ return true;
+ case Symbol.GreaterThanOrEqual:
+ binaryExpressionOperator = BinaryOperatorSyntax.GreaterThanOrEqual;
+ return true;
+ case Symbol.And:
+ binaryExpressionOperator = BinaryOperatorSyntax.LogicalAnd;
+ return true;
+ case Symbol.Or:
+ binaryExpressionOperator = BinaryOperatorSyntax.LogicalOr;
+ return true;
+ case Symbol.Plus:
+ binaryExpressionOperator = BinaryOperatorSyntax.Plus;
+ return true;
+ case Symbol.Minus:
+ binaryExpressionOperator = BinaryOperatorSyntax.Minus;
+ return true;
+ case Symbol.Star:
+ binaryExpressionOperator = BinaryOperatorSyntax.Multiply;
+ return true;
+ case Symbol.ForwardSlash:
+ binaryExpressionOperator = BinaryOperatorSyntax.Divide;
+ return true;
+ case Symbol.Percent:
+ binaryExpressionOperator = BinaryOperatorSyntax.Modulo;
+ return true;
+ case Symbol.LeftShift:
+ binaryExpressionOperator = BinaryOperatorSyntax.LeftShift;
+ return true;
+ case Symbol.RightShift:
+ binaryExpressionOperator = BinaryOperatorSyntax.RightShift;
+ return true;
+ case Symbol.Ampersand:
+ binaryExpressionOperator = BinaryOperatorSyntax.BitwiseAnd;
+ return true;
+ case Symbol.Pipe:
+ binaryExpressionOperator = BinaryOperatorSyntax.BitwiseOr;
+ return true;
+ case Symbol.Caret:
+ binaryExpressionOperator = BinaryOperatorSyntax.BitwiseXor;
+ return true;
+ default:
+ binaryExpressionOperator = null;
+ return false;
+ }
+ }
+
+ private ExpressionSyntax ParsePrimaryExpression()
+ {
+ var startIndex = _tokenIndex;
+ var token = ExpectToken();
+ var expr = token switch
+ {
+ LiteralToken literal => new LiteralSyntax(GetTokens(startIndex), literal.Value, literal.Kind),
+ IdentifierToken identifier => new IdentifierSyntax(GetTokens(startIndex), Optional.Empty(), identifier.Value),
+ SymbolToken symbolToken => symbolToken.Symbol switch
+ {
+ Symbol.OpenParen => ParseParenthesizedExpression(),
+ Symbol.Minus => new UnaryExpressionSyntax(GetTokens(startIndex), UnaryOperatorSyntax.Negate, ParsePrimaryExpression()),
+ Symbol.Bang => new UnaryExpressionSyntax(GetTokens(startIndex), UnaryOperatorSyntax.Invert, ParsePrimaryExpression()),
+ Symbol.OpenBracket => ParseArrayInitializer(startIndex),
+ Symbol.OpenBrace => new StructInitializerSyntax(GetTokens(startIndex), Optional.Empty(), ParseStructInitializerBody()),
+ Symbol.Struct => ParseStructInitializer(startIndex),
+ _ => throw new ParseException(Diagnostic
+ .Error($"Unexpected symbol '{symbolToken.Symbol}' in expression")
+ .WithHelp("Expected '(', '-', '!', '[' or '{'")
+ .At(symbolToken)
+ .Build())
+ },
+ _ => throw new ParseException(Diagnostic
+ .Error($"Unexpected token '{token.GetType().Name}' in expression")
+ .WithHelp("Expected literal, identifier, or parenthesized expression")
+ .At(token)
+ .Build())
+ };
+
+ return ParsePostfixOperators(expr);
+ }
+
+ private ExpressionSyntax ParseParenthesizedExpression()
+ {
+ var expression = ParseExpression();
+ ExpectSymbol(Symbol.CloseParen);
+ return expression;
+ }
+
+ private ExpressionSyntax ParsePostfixOperators(ExpressionSyntax expr)
+ {
+ var startIndex = _tokenIndex;
+ while (HasToken)
+ {
+ if (TryExpectSymbol(Symbol.Ampersand))
+ {
+ expr = new AddressOfSyntax(GetTokens(startIndex), expr);
+ continue;
+ }
+
+ if (TryExpectSymbol(Symbol.Caret))
+ {
+ expr = new DereferenceSyntax(GetTokens(startIndex), expr);
+ continue;
+ }
+
+ if (TryExpectSymbol(Symbol.Period))
+ {
+ var member = ExpectIdentifier().Value;
+ if (TryExpectSymbol(Symbol.OpenParen))
+ {
+ var parameters = new List();
+
+ while (!TryExpectSymbol(Symbol.CloseParen))
+ {
+ parameters.Add(ParseExpression());
+ if (!TryExpectSymbol(Symbol.Comma))
+ {
+ ExpectSymbol(Symbol.CloseParen);
+ break;
+ }
+ }
+
+ expr = new DotFuncCallSyntax(GetTokens(startIndex), member, expr, parameters);
+ continue;
+ }
+
+ expr = new StructFieldAccessSyntax(GetTokens(startIndex), expr, member);
+ continue;
+ }
+
+ if (TryExpectSymbol(Symbol.OpenBracket))
+ {
+ var index = ParseExpression();
+ ExpectSymbol(Symbol.CloseBracket);
+ expr = new ArrayIndexAccessSyntax(GetTokens(startIndex), expr, index);
+ continue;
+ }
+
+ if (TryExpectSymbol(Symbol.OpenParen))
+ {
+ var parameters = new List();
+
+ while (!TryExpectSymbol(Symbol.CloseParen))
+ {
+ parameters.Add(ParseExpression());
+ if (!TryExpectSymbol(Symbol.Comma))
+ {
+ ExpectSymbol(Symbol.CloseParen);
+ break;
+ }
+ }
+
+ expr = new FuncCallSyntax(GetTokens(startIndex), expr, parameters);
+ continue;
+ }
+
+ break;
+ }
+
+ return expr;
+ }
+
+ private ArrayInitializerSyntax ParseArrayInitializer(int startIndex)
+ {
+ var capacity = ParseExpression();
+ ExpectSymbol(Symbol.CloseBracket);
+ var type = ParseType();
+ return new ArrayInitializerSyntax(GetTokens(startIndex), capacity, type);
+ }
+
+ private StructInitializerSyntax ParseStructInitializer(int startIndex)
+ {
+ var type = Optional.Empty();
+ if (!TryExpectSymbol(Symbol.OpenBrace))
+ {
+ type = ParseType();
+ ExpectSymbol(Symbol.OpenBrace);
+ }
+
+ var initializers = ParseStructInitializerBody();
+
+ return new StructInitializerSyntax(GetTokens(startIndex), type, initializers);
+ }
+
+ private Dictionary ParseStructInitializerBody()
+ {
+ Dictionary initializers = [];
+ while (!TryExpectSymbol(Symbol.CloseBrace))
+ {
+ var name = ExpectIdentifier().Value;
+ ExpectSymbol(Symbol.Assign);
+ var value = ParseExpression();
+ initializers.Add(name, value);
+ }
+
+ return initializers;
+ }
+
+ private BlockSyntax ParseBlock()
+ {
+ var startIndex = _tokenIndex;
+ List statements = [];
+ while (!TryExpectSymbol(Symbol.CloseBrace))
+ {
+ try
+ {
+ statements.Add(ParseStatement());
+ }
+ catch (ParseException ex)
+ {
+ _diagnostics.Add(ex.Diagnostic);
+ Next();
+ }
+ }
+
+ return new BlockSyntax(GetTokens(startIndex), statements);
+ }
+
+ private TypeSyntax ParseType()
+ {
+ var startIndex = _tokenIndex;
+ if (TryExpectIdentifier(out var name))
+ {
+ if (name.Value[0] == 'u' && int.TryParse(name.Value[1..], out var size))
+ {
+ if (size is not 8 and not 16 and not 32 and not 64)
+ {
+ throw new ParseException(Diagnostic
+ .Error("Arbitrary uint size is not supported")
+ .WithHelp("Use u8, u16, u32 or u64")
+ .At(name)
+ .Build());
+ }
+
+ return new IntTypeSyntax(GetTokens(startIndex), false, size);
+ }
+
+ if (name.Value[0] == 'i' && int.TryParse(name.Value[1..], out size))
+ {
+ if (size is not 8 and not 16 and not 32 and not 64)
+ {
+ throw new ParseException(Diagnostic
+ .Error("Arbitrary int size is not supported")
+ .WithHelp("Use i8, i16, i32 or i64")
+ .At(name)
+ .Build());
+ }
+
+ return new IntTypeSyntax(GetTokens(startIndex), true, size);
+ }
+
+ if (name.Value[0] == 'f' && int.TryParse(name.Value[1..], out size))
+ {
+ if (size is not 32 and not 64)
+ {
+ throw new ParseException(Diagnostic
+ .Error("Arbitrary float size is not supported")
+ .WithHelp("Use f32 or f64")
+ .At(name)
+ .Build());
+ }
+
+ return new FloatTypeSyntax(GetTokens(startIndex), size);
+ }
+
+ return name.Value switch
+ {
+ "void" => new VoidTypeSyntax(GetTokens(startIndex)),
+ "string" => new StringTypeSyntax(GetTokens(startIndex)),
+ "cstring" => new CStringTypeSyntax(GetTokens(startIndex)),
+ "bool" => new BoolTypeSyntax(GetTokens(startIndex)),
+ _ => new CustomTypeSyntax(GetTokens(startIndex), _moduleName, name.Value)
+ };
+ }
+
+ if (TryExpectSymbol(Symbol.Caret))
+ {
+ var baseType = ParseType();
+ return new PointerTypeSyntax(GetTokens(startIndex), baseType);
+ }
+
+ if (TryExpectSymbol(Symbol.Func))
+ {
+ ExpectSymbol(Symbol.OpenParen);
+
+ List parameters = [];
+ while (!TryExpectSymbol(Symbol.CloseParen))
+ {
+ parameters.Add(ParseType());
+ if (!TryExpectSymbol(Symbol.Comma))
+ {
+ ExpectSymbol(Symbol.CloseParen);
+ break;
+ }
+ }
+
+ var returnType = TryExpectSymbol(Symbol.Colon)
+ ? ParseType()
+ : new VoidTypeSyntax([]);
+
+ return new FuncTypeSyntax(GetTokens(startIndex), parameters, returnType);
+ }
+
+ if (TryExpectSymbol(Symbol.OpenBracket))
+ {
+ ExpectSymbol(Symbol.CloseBracket);
+ var baseType = ParseType();
+ return new ArrayTypeSyntax(GetTokens(startIndex), baseType);
+ }
+
+ throw new ParseException(Diagnostic
+ .Error("Invalid type syntax")
+ .WithHelp("Expected type name, '^' for pointer, or '[]' for array")
+ .At(CurrentToken)
+ .Build());
+ }
+
+ private Token ExpectToken()
+ {
+ if (!HasToken)
+ {
+ throw new ParseException(Diagnostic
+ .Error("Unexpected end of file")
+ .WithHelp("Expected more tokens to complete the syntax")
+ .At(_tokens[^1])
+ .Build());
+ }
+
+ var token = CurrentToken!;
+ Next();
+ return token;
+ }
+
+ private 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 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 bool TryExpectSymbol(Symbol symbol)
+ {
+ if (CurrentToken is SymbolToken symbolToken && symbolToken.Symbol == symbol)
+ {
+ Next();
+ return true;
+ }
+
+ return false;
+ }
+
+ private bool TryExpectIdentifier([NotNullWhen(true)] out IdentifierToken? identifier)
+ {
+ if (CurrentToken is IdentifierToken identifierToken)
+ {
+ identifier = identifierToken;
+ Next();
+ return true;
+ }
+
+ identifier = null;
+ return false;
+ }
+
+ private 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 LiteralToken ExpectLiteral()
+ {
+ var token = ExpectToken();
+ if (token is not LiteralToken identifier)
+ {
+ throw new ParseException(Diagnostic
+ .Error($"Expected literal, but found {token.GetType().Name}")
+ .WithHelp("Provide a valid literal name here")
+ .At(token)
+ .Build());
+ }
+
+ return identifier;
+ }
+
+ private LiteralToken ExpectLiteral(LiteralKind kind)
+ {
+ var literal = ExpectLiteral();
+ if (literal.Kind != kind)
+ {
+ throw new ParseException(Diagnostic
+ .Error($"Expected {kind} literal, but found {literal.Kind}")
+ .WithHelp($"Provide a {kind} literal name here")
+ .At(literal)
+ .Build());
+ }
+
+ return literal;
+ }
+
+ private bool TryExpectLiteral(LiteralKind kind, [NotNullWhen(true)] out LiteralToken? literal)
+ {
+ if (CurrentToken is LiteralToken identifierToken)
+ {
+ literal = identifierToken;
+ Next();
+ return true;
+ }
+
+ literal = null;
+ return false;
+ }
+
+ private void Next()
+ {
+ _tokenIndex++;
+ }
+
+ private IEnumerable GetTokens(int tokenStartIndex)
+ {
+ return _tokens.Skip(tokenStartIndex).Take(_tokenIndex - tokenStartIndex);
+ }
+}
+
+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/compiler/NubLang/Parsing/Syntax/DefinitionSyntax.cs b/compiler/NubLang/Parsing/Syntax/DefinitionSyntax.cs
new file mode 100644
index 0000000..2c220ac
--- /dev/null
+++ b/compiler/NubLang/Parsing/Syntax/DefinitionSyntax.cs
@@ -0,0 +1,21 @@
+using NubLang.Tokenization;
+
+namespace NubLang.Parsing.Syntax;
+
+public abstract record DefinitionSyntax(IEnumerable Tokens, string Name, bool Exported) : SyntaxNode(Tokens);
+
+public record FuncParameterSyntax(IEnumerable Tokens, string Name, TypeSyntax Type) : SyntaxNode(Tokens);
+
+public record FuncSignatureSyntax(IEnumerable Tokens, IReadOnlyList Parameters, TypeSyntax ReturnType) : SyntaxNode(Tokens);
+
+public record FuncSyntax(IEnumerable Tokens, string Name, bool Exported, string? ExternSymbol, FuncSignatureSyntax Signature, BlockSyntax? Body) : DefinitionSyntax(Tokens, Name, Exported);
+
+public record StructFieldSyntax(IEnumerable Tokens, int Index, string Name, TypeSyntax Type, Optional Value) : SyntaxNode(Tokens);
+
+public record StructFuncSyntax(IEnumerable Tokens, string Name, FuncSignatureSyntax Signature, BlockSyntax Body) : SyntaxNode(Tokens);
+
+public record StructSyntax(IEnumerable Tokens, string Name, bool Exported, IReadOnlyList Fields, IReadOnlyList Functions, IReadOnlyList InterfaceImplementations) : DefinitionSyntax(Tokens, Name, Exported);
+
+public record InterfaceFuncSyntax(IEnumerable Tokens, string Name, FuncSignatureSyntax Signature) : SyntaxNode(Tokens);
+
+public record InterfaceSyntax(IEnumerable Tokens, string Name, bool Exported, IReadOnlyList Functions) : DefinitionSyntax(Tokens, Name, Exported);
\ No newline at end of file
diff --git a/compiler/NubLang/Parsing/Syntax/ExpressionSyntax.cs b/compiler/NubLang/Parsing/Syntax/ExpressionSyntax.cs
new file mode 100644
index 0000000..f5f4a30
--- /dev/null
+++ b/compiler/NubLang/Parsing/Syntax/ExpressionSyntax.cs
@@ -0,0 +1,57 @@
+using NubLang.Tokenization;
+
+namespace NubLang.Parsing.Syntax;
+
+public enum UnaryOperatorSyntax
+{
+ Negate,
+ Invert
+}
+
+public enum BinaryOperatorSyntax
+{
+ Equal,
+ NotEqual,
+ GreaterThan,
+ GreaterThanOrEqual,
+ LessThan,
+ LessThanOrEqual,
+ LogicalAnd,
+ LogicalOr,
+ Plus,
+ Minus,
+ Multiply,
+ Divide,
+ Modulo,
+ LeftShift,
+ RightShift,
+ BitwiseAnd,
+ BitwiseXor,
+ BitwiseOr,
+}
+
+public abstract record ExpressionSyntax(IEnumerable Tokens) : SyntaxNode(Tokens);
+
+public record BinaryExpressionSyntax(IEnumerable Tokens, ExpressionSyntax Left, BinaryOperatorSyntax Operator, ExpressionSyntax Right) : ExpressionSyntax(Tokens);
+
+public record UnaryExpressionSyntax(IEnumerable Tokens, UnaryOperatorSyntax Operator, ExpressionSyntax Operand) : ExpressionSyntax(Tokens);
+
+public record FuncCallSyntax(IEnumerable Tokens, ExpressionSyntax Expression, IReadOnlyList Parameters) : ExpressionSyntax(Tokens);
+
+public record DotFuncCallSyntax(IEnumerable Tokens, string Name, ExpressionSyntax ThisParameter, IReadOnlyList Parameters) : ExpressionSyntax(Tokens);
+
+public record IdentifierSyntax(IEnumerable Tokens, Optional Module, string Name) : ExpressionSyntax(Tokens);
+
+public record ArrayInitializerSyntax(IEnumerable Tokens, ExpressionSyntax Capacity, TypeSyntax ElementType) : ExpressionSyntax(Tokens);
+
+public record ArrayIndexAccessSyntax(IEnumerable Tokens, ExpressionSyntax Target, ExpressionSyntax Index) : ExpressionSyntax(Tokens);
+
+public record AddressOfSyntax(IEnumerable Tokens, ExpressionSyntax Expression) : ExpressionSyntax(Tokens);
+
+public record LiteralSyntax(IEnumerable Tokens, string Value, LiteralKind Kind) : ExpressionSyntax(Tokens);
+
+public record StructFieldAccessSyntax(IEnumerable Tokens, ExpressionSyntax Target, string Member) : ExpressionSyntax(Tokens);
+
+public record StructInitializerSyntax(IEnumerable Tokens, Optional StructType, Dictionary Initializers) : ExpressionSyntax(Tokens);
+
+public record DereferenceSyntax(IEnumerable Tokens, ExpressionSyntax Expression) : ExpressionSyntax(Tokens);
\ No newline at end of file
diff --git a/compiler/NubLang/Parsing/Syntax/StatementSyntax.cs b/compiler/NubLang/Parsing/Syntax/StatementSyntax.cs
new file mode 100644
index 0000000..acf1d8c
--- /dev/null
+++ b/compiler/NubLang/Parsing/Syntax/StatementSyntax.cs
@@ -0,0 +1,21 @@
+using NubLang.Tokenization;
+
+namespace NubLang.Parsing.Syntax;
+
+public abstract record StatementSyntax(IEnumerable Tokens) : SyntaxNode(Tokens);
+
+public record StatementExpressionSyntax(IEnumerable Tokens, ExpressionSyntax Expression) : StatementSyntax(Tokens);
+
+public record ReturnSyntax(IEnumerable Tokens, Optional Value) : StatementSyntax(Tokens);
+
+public record AssignmentSyntax(IEnumerable Tokens, ExpressionSyntax Target, ExpressionSyntax Value) : StatementSyntax(Tokens);
+
+public record IfSyntax(IEnumerable Tokens, ExpressionSyntax Condition, BlockSyntax Body, Optional> Else) : StatementSyntax(Tokens);
+
+public record VariableDeclarationSyntax(IEnumerable Tokens, string Name, Optional ExplicitType, Optional Assignment) : StatementSyntax(Tokens);
+
+public record ContinueSyntax(IEnumerable Tokens) : StatementSyntax(Tokens);
+
+public record BreakSyntax(IEnumerable Tokens) : StatementSyntax(Tokens);
+
+public record WhileSyntax(IEnumerable Tokens, ExpressionSyntax Condition, BlockSyntax Body) : StatementSyntax(Tokens);
\ No newline at end of file
diff --git a/compiler/NubLang/Parsing/Syntax/SyntaxNode.cs b/compiler/NubLang/Parsing/Syntax/SyntaxNode.cs
new file mode 100644
index 0000000..7435d18
--- /dev/null
+++ b/compiler/NubLang/Parsing/Syntax/SyntaxNode.cs
@@ -0,0 +1,11 @@
+using NubLang.Tokenization;
+
+namespace NubLang.Parsing.Syntax;
+
+public abstract record SyntaxNode(IEnumerable Tokens);
+
+public record SyntaxTreeMetadata(string? ModuleName, IReadOnlyList Imports);
+
+public record SyntaxTree(IReadOnlyList Definitions, SyntaxTreeMetadata Metadata);
+
+public record BlockSyntax(IEnumerable Tokens, IReadOnlyList Statements) : SyntaxNode(Tokens);
\ No newline at end of file
diff --git a/compiler/NubLang/Parsing/Syntax/TypeSyntax.cs b/compiler/NubLang/Parsing/Syntax/TypeSyntax.cs
new file mode 100644
index 0000000..29ff911
--- /dev/null
+++ b/compiler/NubLang/Parsing/Syntax/TypeSyntax.cs
@@ -0,0 +1,25 @@
+using NubLang.Tokenization;
+
+namespace NubLang.Parsing.Syntax;
+
+public abstract record TypeSyntax(IEnumerable Tokens) : SyntaxNode(Tokens);
+
+public record FuncTypeSyntax(IEnumerable Tokens, IReadOnlyList Parameters, TypeSyntax ReturnType) : TypeSyntax(Tokens);
+
+public record PointerTypeSyntax(IEnumerable Tokens, TypeSyntax BaseType) : TypeSyntax(Tokens);
+
+public record VoidTypeSyntax(IEnumerable Tokens) : TypeSyntax(Tokens);
+
+public record IntTypeSyntax(IEnumerable Tokens, bool Signed, int Width) : TypeSyntax(Tokens);
+
+public record FloatTypeSyntax(IEnumerable Tokens, int Width) : TypeSyntax(Tokens);
+
+public record BoolTypeSyntax(IEnumerable Tokens) : TypeSyntax(Tokens);
+
+public record StringTypeSyntax(IEnumerable Tokens) : TypeSyntax(Tokens);
+
+public record CStringTypeSyntax(IEnumerable Tokens) : TypeSyntax(Tokens);
+
+public record ArrayTypeSyntax(IEnumerable Tokens, TypeSyntax BaseType) : TypeSyntax(Tokens);
+
+public record CustomTypeSyntax(IEnumerable Tokens, string Module, string Name) : TypeSyntax(Tokens);
\ No newline at end of file
diff --git a/compiler/NubLang/Tokenization/Token.cs b/compiler/NubLang/Tokenization/Token.cs
new file mode 100644
index 0000000..f5ec1cd
--- /dev/null
+++ b/compiler/NubLang/Tokenization/Token.cs
@@ -0,0 +1,82 @@
+using NubLang.Code;
+
+namespace NubLang.Tokenization;
+
+public abstract class Token(SourceFileSpan fileSpan)
+{
+ public SourceFileSpan FileSpan { get; } = fileSpan;
+}
+
+public class IdentifierToken(SourceFileSpan fileSpan, string value) : Token(fileSpan)
+{
+ public string Value { get; } = value;
+}
+
+public class LiteralToken(SourceFileSpan fileSpan, LiteralKind kind, string value) : Token(fileSpan)
+{
+ public LiteralKind Kind { get; } = kind;
+ public string Value { get; } = value;
+}
+
+public enum LiteralKind
+{
+ Integer,
+ Float,
+ String,
+ Bool
+}
+
+public class SymbolToken(SourceFileSpan fileSpan, Symbol symbol) : Token(fileSpan)
+{
+ public Symbol Symbol { get; } = symbol;
+}
+
+public enum Symbol
+{
+ Func,
+ Return,
+ If,
+ Else,
+ While,
+ Break,
+ Continue,
+ Colon,
+ OpenParen,
+ CloseParen,
+ OpenBrace,
+ CloseBrace,
+ OpenBracket,
+ CloseBracket,
+ Comma,
+ Period,
+ Assign,
+ Bang,
+ Equal,
+ NotEqual,
+ LessThan,
+ LessThanOrEqual,
+ GreaterThan,
+ GreaterThanOrEqual,
+ Plus,
+ Minus,
+ Star,
+ ForwardSlash,
+ Struct,
+ Caret,
+ Ampersand,
+ Let,
+ Calls,
+ Interface,
+ For,
+ Extern,
+ Semi,
+ Percent,
+ LeftShift,
+ RightShift,
+ Pipe,
+ And,
+ Or,
+ Module,
+ Import,
+ Export,
+}
\ No newline at end of file
diff --git a/compiler/NubLang/Tokenization/Tokenizer.cs b/compiler/NubLang/Tokenization/Tokenizer.cs
new file mode 100644
index 0000000..c629bf9
--- /dev/null
+++ b/compiler/NubLang/Tokenization/Tokenizer.cs
@@ -0,0 +1,266 @@
+using NubLang.Code;
+using NubLang.Diagnostics;
+
+namespace NubLang.Tokenization;
+
+public sealed class Tokenizer
+{
+ private static readonly Dictionary Keywords = new()
+ {
+ ["func"] = Symbol.Func,
+ ["if"] = Symbol.If,
+ ["else"] = Symbol.Else,
+ ["while"] = Symbol.While,
+ ["break"] = Symbol.Break,
+ ["continue"] = Symbol.Continue,
+ ["return"] = Symbol.Return,
+ ["struct"] = Symbol.Struct,
+ ["let"] = Symbol.Let,
+ ["calls"] = Symbol.Calls,
+ ["interface"] = Symbol.Interface,
+ ["for"] = Symbol.For,
+ ["extern"] = Symbol.Extern,
+ ["module"] = Symbol.Module,
+ ["export"] = Symbol.Export,
+ ["import"] = Symbol.Import,
+ };
+
+ private static readonly Dictionary Symbols = new()
+ {
+ [['=', '=']] = Symbol.Equal,
+ [['!', '=']] = Symbol.NotEqual,
+ [['<', '=']] = Symbol.LessThanOrEqual,
+ [['>', '=']] = Symbol.GreaterThanOrEqual,
+ [['<', '<']] = Symbol.LeftShift,
+ [['>', '>']] = Symbol.RightShift,
+ [['&', '&']] = Symbol.And,
+ [['|', '|']] = Symbol.Or,
+ [[':']] = 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,
+ [[';']] = Symbol.Semi,
+ [['%']] = Symbol.Percent,
+ [['|']] = Symbol.Pipe,
+ };
+
+ private static readonly (char[] Pattern, Symbol Symbol)[] OrderedSymbols = Symbols
+ .OrderByDescending(kvp => kvp.Key.Length)
+ .Select(kvp => (kvp.Key, kvp.Value))
+ .ToArray();
+
+ private readonly SourceFile _sourceFile;
+ private readonly List _diagnostics = [];
+ private int _index;
+
+ public Tokenizer(SourceFile sourceFile)
+ {
+ _sourceFile = sourceFile;
+ }
+
+ public IReadOnlyList GetDiagnostics() => _diagnostics;
+
+ public IEnumerable Tokenize()
+ {
+ _index = 0;
+
+ while (Peek().TryGetValue(out var current))
+ {
+ if (char.IsWhiteSpace(current))
+ {
+ Next();
+ continue;
+ }
+
+ if (current == '/' && Peek(1).TryGetValue(out var nextChar) && nextChar == '/')
+ {
+ while (Peek().TryGetValue(out var ch) && ch != '\n')
+ {
+ Next();
+ }
+
+ continue;
+ }
+
+ var tokenStartIndex = _index;
+
+ 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))
+ {
+ yield return new SymbolToken(GetSourceFileSpan(tokenStartIndex), keywordSymbol);
+ continue;
+ }
+
+ if (buffer is "true" or "false")
+ {
+ yield return new LiteralToken(GetSourceFileSpan(tokenStartIndex), LiteralKind.Bool, buffer);
+ continue;
+ }
+
+ yield return new IdentifierToken(GetSourceFileSpan(tokenStartIndex), buffer);
+ continue;
+ }
+
+ 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;
+ }
+ }
+
+ yield return new LiteralToken(GetSourceFileSpan(tokenStartIndex), isFloat ? LiteralKind.Float : LiteralKind.Integer, buffer);
+ continue;
+ }
+
+ 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();
+ }
+
+ yield return new LiteralToken(GetSourceFileSpan(tokenStartIndex), LiteralKind.String, buffer);
+ continue;
+ }
+
+ var foundMatch = false;
+ foreach (var (pattern, symbol) in OrderedSymbols)
+ {
+ for (var i = 0; i < pattern.Length; i++)
+ {
+ var c = Peek(i);
+ if (!c.HasValue || c.Value != pattern[i]) break;
+
+ if (i == pattern.Length - 1)
+ {
+ for (var j = 0; j <= i; j++)
+ {
+ Next();
+ }
+
+ yield return new SymbolToken(GetSourceFileSpan(tokenStartIndex), symbol);
+ foundMatch = true;
+ break;
+ }
+ }
+
+ if (foundMatch)
+ {
+ break;
+ }
+ }
+
+ if (foundMatch)
+ {
+ continue;
+ }
+
+ _diagnostics.Add(Diagnostic.Error($"Unknown token '{current}'").At(GetSourceFileSpan(tokenStartIndex)).Build());
+ Next();
+ }
+ }
+
+ private Optional Peek(int offset = 0)
+ {
+ if (_index + offset < _sourceFile.GetText().Length)
+ {
+ return _sourceFile.GetText()[_index + offset];
+ }
+
+ return Optional.Empty();
+ }
+
+ private void Next()
+ {
+ _index++;
+ }
+
+ private SourceFileSpan GetSourceFileSpan(int tokenStartIndex)
+ {
+ var start = CalculateSourceLocation(tokenStartIndex);
+ var end = CalculateSourceLocation(_index);
+ return new SourceFileSpan(_sourceFile, new SourceSpan(start, end));
+ }
+
+ private SourceLocation CalculateSourceLocation(int index)
+ {
+ var line = 1;
+ var column = 1;
+
+ for (var i = 0; i < index && i < _sourceFile.GetText().Length; i++)
+ {
+ if (_sourceFile.GetText()[i] == '\n')
+ {
+ line++;
+ column = 1;
+ }
+ else
+ {
+ column++;
+ }
+ }
+
+ return new SourceLocation(line, column);
+ }
+}
\ No newline at end of file
diff --git a/compiler/NubLang/TypeChecking/Module.cs b/compiler/NubLang/TypeChecking/Module.cs
new file mode 100644
index 0000000..5a39eeb
--- /dev/null
+++ b/compiler/NubLang/TypeChecking/Module.cs
@@ -0,0 +1,171 @@
+using NubLang.Parsing.Syntax;
+using NubLang.TypeChecking.Node;
+
+namespace NubLang.TypeChecking;
+
+public class Module
+{
+ public static IReadOnlyList CollectFromSyntaxTrees(IReadOnlyList syntaxTrees)
+ {
+ var modules = new Dictionary();
+
+ foreach (var syntaxTree in syntaxTrees)
+ {
+ var name = syntaxTree.Metadata.ModuleName;
+ if (name == null)
+ {
+ continue;
+ }
+
+ if (!modules.TryGetValue(name, out var module))
+ {
+ module = new Module(name, syntaxTree.Metadata.Imports);
+ modules[name] = module;
+ }
+
+ foreach (var definition in syntaxTree.Definitions)
+ {
+ module.AddDefinition(definition);
+ }
+ }
+
+ return modules.Values.ToList();
+ }
+
+ private readonly List _definitions = [];
+
+ public Module(string name, IReadOnlyList imports)
+ {
+ Name = name;
+ Imports = imports;
+ }
+
+ public string Name { get; }
+ public IReadOnlyList Imports { get; }
+
+ public IReadOnlyList Definitions => _definitions;
+
+ private void AddDefinition(DefinitionSyntax syntax)
+ {
+ _definitions.Add(syntax);
+ }
+}
+
+public class TypedModule
+{
+ public TypedModule(string name, IReadOnlyList definitions)
+ {
+ Name = name;
+ Definitions = definitions;
+ }
+
+ public string Name { get; }
+ public IReadOnlyList Definitions { get; }
+}
+
+public class ModuleSignature
+{
+ public static IReadOnlyDictionary CollectFromSyntaxTrees(IReadOnlyList syntaxTrees)
+ {
+ var modules = new Dictionary();
+
+ foreach (var syntaxTree in syntaxTrees)
+ {
+ var moduleName = syntaxTree.Metadata.ModuleName;
+ if (moduleName == null)
+ {
+ continue;
+ }
+
+ if (!modules.TryGetValue(moduleName, out var module))
+ {
+ module = new ModuleSignature();
+ modules[moduleName] = module;
+ }
+
+ foreach (var def in syntaxTree.Definitions)
+ {
+ if (!def.Exported) continue;
+
+ switch (def)
+ {
+ case FuncSyntax funcDef:
+ {
+ var parameters = funcDef.Signature.Parameters.Select(p => TypeResolver.ResolveType(p.Type, modules)).ToList();
+ var returnType = TypeResolver.ResolveType(funcDef.Signature.ReturnType, modules);
+
+ var type = new FuncTypeNode(parameters, returnType, funcDef.ExternSymbol);
+ module._functions.Add(funcDef.Name, type);
+ break;
+ }
+ case InterfaceSyntax interfaceDef:
+ {
+ var functions = new List();
+ for (var i = 0; i < interfaceDef.Functions.Count; i++)
+ {
+ var function = interfaceDef.Functions[i];
+ var parameters = function.Signature.Parameters.Select(p => TypeResolver.ResolveType(p.Type, modules)).ToList();
+ var returnType = TypeResolver.ResolveType(function.Signature.ReturnType, modules);
+ functions.Add(new InterfaceTypeFunc(function.Name, new FuncTypeNode(parameters, returnType), i));
+ }
+
+ var type = new InterfaceTypeNode(moduleName, interfaceDef.Name, functions);
+ module._interfaces.Add(type);
+ break;
+ }
+ case StructSyntax structDef:
+ {
+ var fields = new List();
+ foreach (var field in structDef.Fields)
+ {
+ fields.Add(new StructTypeField(field.Name, TypeResolver.ResolveType(field.Type, modules), field.Index, field.Value.HasValue));
+ }
+
+ var functions = new List();
+ foreach (var function in structDef.Functions)
+ {
+ var parameters = function.Signature.Parameters.Select(p => TypeResolver.ResolveType(p.Type, modules)).ToList();
+ var returnType = TypeResolver.ResolveType(function.Signature.ReturnType, modules);
+ functions.Add(new StructTypeFunc(function.Name, new FuncTypeNode(parameters, returnType)));
+ }
+
+ var interfaceImplementations = new List();
+ foreach (var interfaceImplementation in structDef.InterfaceImplementations)
+ {
+ if (interfaceImplementation is not CustomTypeSyntax customType)
+ {
+ throw new Exception("Interface implementation is not a custom type");
+ }
+
+ var resolvedType = TypeResolver.ResolveCustomType(customType.Module, customType.Name, modules);
+ if (resolvedType is not InterfaceTypeNode interfaceType)
+ {
+ throw new Exception("Interface implementation is not a interface");
+ }
+
+ interfaceImplementations.Add(interfaceType);
+ }
+
+ var type = new StructTypeNode(moduleName, structDef.Name, fields, functions, interfaceImplementations);
+ module._structs.Add(type);
+ break;
+ }
+ default:
+ {
+ throw new ArgumentOutOfRangeException(nameof(def));
+ }
+ }
+ }
+ }
+
+ return modules;
+ }
+
+ private readonly List _structs = [];
+ private readonly List _interfaces = [];
+ private readonly Dictionary _functions = [];
+
+ public IReadOnlyList StructTypes => _structs;
+ public IReadOnlyList InterfaceTypes => _interfaces;
+ public IReadOnlyDictionary Functions => _functions;
+}
\ No newline at end of file
diff --git a/compiler/NubLang/TypeChecking/Node/DefinitionNode.cs b/compiler/NubLang/TypeChecking/Node/DefinitionNode.cs
new file mode 100644
index 0000000..2169f79
--- /dev/null
+++ b/compiler/NubLang/TypeChecking/Node/DefinitionNode.cs
@@ -0,0 +1,19 @@
+namespace NubLang.TypeChecking.Node;
+
+public abstract record DefinitionNode : Node;
+
+public record FuncParameterNode(string Name, TypeNode Type) : Node;
+
+public record FuncSignatureNode(IReadOnlyList Parameters, TypeNode ReturnType) : Node;
+
+public record FuncNode(string Name, FuncSignatureNode Signature, BlockNode? Body) : DefinitionNode;
+
+public record StructFieldNode(int Index, string Name, TypeNode Type, Optional Value) : Node;
+
+public record StructFuncNode(string Name, FuncSignatureNode Signature, BlockNode Body) : Node;
+
+public record StructNode(string Name, IReadOnlyList Fields, IReadOnlyList Functions, IReadOnlyList InterfaceImplementations) : DefinitionNode;
+
+public record InterfaceFuncNode(string Name, FuncSignatureNode Signature) : Node;
+
+public record InterfaceNode(string Name, IReadOnlyList Functions) : DefinitionNode;
\ No newline at end of file
diff --git a/compiler/NubLang/TypeChecking/Node/ExpressionNode.cs b/compiler/NubLang/TypeChecking/Node/ExpressionNode.cs
new file mode 100644
index 0000000..df480cd
--- /dev/null
+++ b/compiler/NubLang/TypeChecking/Node/ExpressionNode.cs
@@ -0,0 +1,72 @@
+using NubLang.Tokenization;
+
+namespace NubLang.TypeChecking.Node;
+
+public enum UnaryOperator
+{
+ Negate,
+ Invert
+}
+
+public enum BinaryOperator
+{
+ Equal,
+ NotEqual,
+ GreaterThan,
+ GreaterThanOrEqual,
+ LessThan,
+ LessThanOrEqual,
+ LogicalAnd,
+ LogicalOr,
+ Plus,
+ Minus,
+ Multiply,
+ Divide,
+ Modulo,
+ LeftShift,
+ RightShift,
+ BitwiseAnd,
+ BitwiseXor,
+ BitwiseOr
+}
+
+public abstract record ExpressionNode(TypeNode Type) : Node;
+
+public abstract record LValueExpressionNode(TypeNode Type) : RValueExpressionNode(Type);
+public abstract record RValueExpressionNode(TypeNode Type) : ExpressionNode(Type);
+
+public record BinaryExpressionNode(TypeNode Type, ExpressionNode Left, BinaryOperator Operator, ExpressionNode Right) : RValueExpressionNode(Type);
+
+public record UnaryExpressionNode(TypeNode Type, UnaryOperator Operator, ExpressionNode Operand) : RValueExpressionNode(Type);
+
+public record FuncCallNode(TypeNode Type, ExpressionNode Expression, IReadOnlyList Parameters) : RValueExpressionNode(Type);
+
+public record StructFuncCallNode(TypeNode Type, string Name, StructTypeNode StructType, ExpressionNode StructExpression, IReadOnlyList Parameters) : RValueExpressionNode(Type);
+
+public record InterfaceFuncCallNode(TypeNode Type, string Name, InterfaceTypeNode InterfaceType, ExpressionNode InterfaceExpression, IReadOnlyList Parameters) : RValueExpressionNode(Type);
+
+public record VariableIdentifierNode(TypeNode Type, string Name) : LValueExpressionNode(Type);
+
+public record FuncParameterIdentifierNode(TypeNode Type, string Name) : RValueExpressionNode(Type);
+
+public record FuncIdentifierNode(TypeNode Type, string Module, string Name) : RValueExpressionNode(Type);
+
+public record ArrayInitializerNode(TypeNode Type, ExpressionNode Capacity, TypeNode ElementType) : RValueExpressionNode(Type);
+
+public record ArrayIndexAccessNode(TypeNode Type, ExpressionNode Target, ExpressionNode Index) : LValueExpressionNode(Type);
+
+public record AddressOfNode(TypeNode Type, LValueExpressionNode LValue) : RValueExpressionNode(Type);
+
+public record LiteralNode(TypeNode Type, string Value, LiteralKind Kind) : RValueExpressionNode(Type);
+
+public record StructFieldAccessNode(TypeNode Type, StructTypeNode StructType, ExpressionNode Target, string Field) : LValueExpressionNode(Type);
+
+public record StructInitializerNode(StructTypeNode StructType, Dictionary Initializers) : RValueExpressionNode(StructType);
+
+public record DereferenceNode(TypeNode Type, ExpressionNode Expression) : RValueExpressionNode(Type);
+
+public record ConvertToInterfaceNode(TypeNode Type, InterfaceTypeNode InterfaceType, StructTypeNode StructType, ExpressionNode Implementation) : RValueExpressionNode(Type);
+
+public record ConvertIntNode(TypeNode Type, ExpressionNode Value, IntTypeNode ValueType, IntTypeNode TargetType) : RValueExpressionNode(Type);
+
+public record ConvertFloatNode(TypeNode Type, ExpressionNode Value, FloatTypeNode ValueType, FloatTypeNode TargetType) : RValueExpressionNode(Type);
diff --git a/compiler/NubLang/TypeChecking/Node/Node.cs b/compiler/NubLang/TypeChecking/Node/Node.cs
new file mode 100644
index 0000000..4b75960
--- /dev/null
+++ b/compiler/NubLang/TypeChecking/Node/Node.cs
@@ -0,0 +1,5 @@
+namespace NubLang.TypeChecking.Node;
+
+public abstract record Node;
+
+public record BlockNode(IReadOnlyList Statements) : Node;
\ No newline at end of file
diff --git a/compiler/NubLang/TypeChecking/Node/StatementNode.cs b/compiler/NubLang/TypeChecking/Node/StatementNode.cs
new file mode 100644
index 0000000..bd58462
--- /dev/null
+++ b/compiler/NubLang/TypeChecking/Node/StatementNode.cs
@@ -0,0 +1,19 @@
+namespace NubLang.TypeChecking.Node;
+
+public record StatementNode : Node;
+
+public record StatementExpressionNode(ExpressionNode Expression) : StatementNode;
+
+public record ReturnNode(Optional Value) : StatementNode;
+
+public record AssignmentNode(LValueExpressionNode Target, ExpressionNode Value) : StatementNode;
+
+public record IfNode(ExpressionNode Condition, BlockNode Body, Optional> Else) : StatementNode;
+
+public record VariableDeclarationNode(string Name, Optional Assignment, TypeNode Type) : StatementNode;
+
+public record ContinueNode : StatementNode;
+
+public record BreakNode : StatementNode;
+
+public record WhileNode(ExpressionNode Condition, BlockNode Body) : StatementNode;
\ No newline at end of file
diff --git a/compiler/NubLang/TypeChecking/Node/TypeNode.cs b/compiler/NubLang/TypeChecking/Node/TypeNode.cs
new file mode 100644
index 0000000..121d533
--- /dev/null
+++ b/compiler/NubLang/TypeChecking/Node/TypeNode.cs
@@ -0,0 +1,233 @@
+using System.Diagnostics.CodeAnalysis;
+
+namespace NubLang.TypeChecking.Node;
+
+public abstract class TypeNode : IEquatable
+{
+ public bool IsSimpleType([NotNullWhen(true)] out SimpleTypeNode? simpleType, [NotNullWhen(false)] out ComplexTypeNode? complexType)
+ {
+ if (this is SimpleTypeNode st)
+ {
+ complexType = null;
+ simpleType = st;
+ return true;
+ }
+
+ if (this is ComplexTypeNode ct)
+ {
+ complexType = ct;
+ simpleType = null;
+ return false;
+ }
+
+ throw new ArgumentException($"Type {this} is not a simple type nor a complex type");
+ }
+
+ public override bool Equals(object? obj) => obj is TypeNode other && Equals(other);
+
+ public abstract bool Equals(TypeNode? other);
+ public abstract override int GetHashCode();
+ public abstract override string ToString();
+
+ public static bool operator ==(TypeNode? left, TypeNode? right) => Equals(left, right);
+ public static bool operator !=(TypeNode? left, TypeNode? right) => !Equals(left, right);
+}
+
+public enum StorageSize
+{
+ Void,
+ I8,
+ I16,
+ I32,
+ I64,
+ U8,
+ U16,
+ U32,
+ U64,
+ F32,
+ F64
+}
+
+public abstract class SimpleTypeNode : TypeNode
+{
+ public abstract StorageSize StorageSize { get; }
+}
+
+#region Simple types
+
+public class IntTypeNode(bool signed, int width) : SimpleTypeNode
+{
+ public bool Signed { get; } = signed;
+ public int Width { get; } = width;
+
+ public override StorageSize StorageSize => Signed switch
+ {
+ true => Width switch
+ {
+ 8 => StorageSize.I8,
+ 16 => StorageSize.I16,
+ 32 => StorageSize.I32,
+ 64 => StorageSize.I64,
+ _ => throw new ArgumentOutOfRangeException(nameof(Width))
+ },
+ false => Width switch
+ {
+ 8 => StorageSize.U8,
+ 16 => StorageSize.U16,
+ 32 => StorageSize.U32,
+ 64 => StorageSize.U64,
+ _ => throw new ArgumentOutOfRangeException(nameof(Width))
+ }
+ };
+
+ public override string ToString() => $"{(Signed ? "i" : "u")}{Width}";
+ public override bool Equals(TypeNode? other) => other is IntTypeNode @int && @int.Width == Width && @int.Signed == Signed;
+ public override int GetHashCode() => HashCode.Combine(typeof(IntTypeNode), Signed, Width);
+}
+
+public class FloatTypeNode(int width) : SimpleTypeNode
+{
+ public int Width { get; } = width;
+
+ public override StorageSize StorageSize => Width switch
+ {
+ 32 => StorageSize.F32,
+ 64 => StorageSize.F64,
+ _ => throw new ArgumentOutOfRangeException(nameof(Width))
+ };
+
+ public override string ToString() => $"f{Width}";
+ public override bool Equals(TypeNode? other) => other is FloatTypeNode @int && @int.Width == Width;
+ public override int GetHashCode() => HashCode.Combine(typeof(FloatTypeNode), Width);
+}
+
+public class BoolTypeNode : SimpleTypeNode
+{
+ public override StorageSize StorageSize => StorageSize.U8;
+
+ public override string ToString() => "bool";
+ public override bool Equals(TypeNode? other) => other is BoolTypeNode;
+ public override int GetHashCode() => HashCode.Combine(typeof(BoolTypeNode));
+}
+
+public class FuncTypeNode(IReadOnlyList parameters, TypeNode returnType, string? externSymbol = null) : SimpleTypeNode
+{
+ public string? ExternSymbol { get; } = externSymbol;
+ public IReadOnlyList Parameters { get; } = parameters;
+ public TypeNode ReturnType { get; } = returnType;
+
+ public override StorageSize StorageSize => StorageSize.U64;
+
+ public override string ToString() => $"func({string.Join(", ", Parameters)}): {ReturnType}";
+ public override bool Equals(TypeNode? other) => other is FuncTypeNode func && ReturnType.Equals(func.ReturnType) && Parameters.SequenceEqual(func.Parameters);
+
+ public override int GetHashCode()
+ {
+ var hash = new HashCode();
+ hash.Add(typeof(FuncTypeNode));
+ hash.Add(ReturnType);
+ foreach (var param in Parameters)
+ {
+ hash.Add(param);
+ }
+
+ return hash.ToHashCode();
+ }
+}
+
+public class PointerTypeNode(TypeNode baseType) : SimpleTypeNode
+{
+ public TypeNode BaseType { get; } = baseType;
+
+ public override StorageSize StorageSize => StorageSize.U64;
+
+ public override string ToString() => "^" + BaseType;
+ public override bool Equals(TypeNode? other) => other is PointerTypeNode pointer && BaseType.Equals(pointer.BaseType);
+ public override int GetHashCode() => HashCode.Combine(typeof(PointerTypeNode), BaseType);
+}
+
+public class VoidTypeNode : SimpleTypeNode
+{
+ public override StorageSize StorageSize => StorageSize.Void;
+
+ public override string ToString() => "void";
+ public override bool Equals(TypeNode? other) => other is VoidTypeNode;
+ public override int GetHashCode() => HashCode.Combine(typeof(VoidTypeNode));
+}
+
+#endregion
+
+public abstract class ComplexTypeNode : TypeNode;
+
+#region Complex types
+
+public class CStringTypeNode : ComplexTypeNode
+{
+ public override string ToString() => "cstring";
+ public override bool Equals(TypeNode? other) => other is CStringTypeNode;
+ public override int GetHashCode() => HashCode.Combine(typeof(CStringTypeNode));
+}
+
+public class StringTypeNode : ComplexTypeNode
+{
+ public override string ToString() => "string";
+ public override bool Equals(TypeNode? other) => other is StringTypeNode;
+ public override int GetHashCode() => HashCode.Combine(typeof(StringTypeNode));
+}
+
+public class StructTypeField(string name, TypeNode type, int index, bool hasDefaultValue)
+{
+ public string Name { get; } = name;
+ public TypeNode Type { get; } = type;
+ public int Index { get; } = index;
+ public bool HasDefaultValue { get; } = hasDefaultValue;
+}
+
+public class StructTypeFunc(string name, FuncTypeNode type)
+{
+ public string Name { get; } = name;
+ public FuncTypeNode Type { get; } = type;
+}
+
+public class StructTypeNode(string module, string name, IReadOnlyList fields, IReadOnlyList functions, IReadOnlyList interfaceImplementations) : ComplexTypeNode
+{
+ public string Module { get; } = module;
+ public string Name { get; } = name;
+ public IReadOnlyList Fields { get; set; } = fields;
+ public IReadOnlyList Functions { get; set; } = functions;
+ public IReadOnlyList InterfaceImplementations { get; set; } = interfaceImplementations;
+
+ public override string ToString() => Name;
+ public override bool Equals(TypeNode? other) => other is StructTypeNode structType && Name == structType.Name && Module == structType.Module;
+ public override int GetHashCode() => HashCode.Combine(typeof(StructTypeNode), Name);
+}
+
+public class InterfaceTypeFunc(string name, FuncTypeNode type, int index)
+{
+ public string Name { get; } = name;
+ public FuncTypeNode Type { get; } = type;
+ public int Index { get; } = index;
+}
+
+public class InterfaceTypeNode(string module, string name, IReadOnlyList functions) : ComplexTypeNode
+{
+ public string Module { get; } = module;
+ public string Name { get; } = name;
+ public IReadOnlyList Functions { get; set; } = functions;
+
+ public override string ToString() => Name;
+ public override bool Equals(TypeNode? other) => other is InterfaceTypeNode interfaceType && Name == interfaceType.Name && Module == interfaceType.Module;
+ public override int GetHashCode() => HashCode.Combine(typeof(InterfaceTypeNode), Name);
+}
+
+public class ArrayTypeNode(TypeNode elementType) : ComplexTypeNode
+{
+ public TypeNode ElementType { get; } = elementType;
+
+ public override string ToString() => "[]" + ElementType;
+
+ public override bool Equals(TypeNode? other) => other is ArrayTypeNode array && ElementType.Equals(array.ElementType);
+ public override int GetHashCode() => HashCode.Combine(typeof(ArrayTypeNode), ElementType);
+}
+
+#endregion
\ No newline at end of file
diff --git a/compiler/NubLang/TypeChecking/TypeChecker.cs b/compiler/NubLang/TypeChecking/TypeChecker.cs
new file mode 100644
index 0000000..972a6d7
--- /dev/null
+++ b/compiler/NubLang/TypeChecking/TypeChecker.cs
@@ -0,0 +1,456 @@
+using NubLang.Diagnostics;
+using NubLang.Parsing.Syntax;
+using NubLang.Tokenization;
+using NubLang.TypeChecking.Node;
+
+namespace NubLang.TypeChecking;
+
+public sealed class TypeChecker
+{
+ private readonly Module _currentModule;
+ private readonly IReadOnlyDictionary _moduleSignatures;
+
+ private readonly Stack _scopes = [];
+ private readonly Stack _funcReturnTypes = [];
+ private readonly List _diagnostics = [];
+
+ private Scope Scope => _scopes.Peek();
+
+ public TypeChecker(Module currentModule, IReadOnlyDictionary moduleSignatures)
+ {
+ _currentModule = currentModule;
+ _moduleSignatures = moduleSignatures.Where(x => currentModule.Imports.Contains(x.Key) || _currentModule.Name == x.Key).ToDictionary();
+ }
+
+ public IReadOnlyList GetDiagnostics() => _diagnostics;
+
+ public TypedModule CheckModule()
+ {
+ _diagnostics.Clear();
+ _scopes.Clear();
+
+ var definitions = new List();
+
+ foreach (var definition in _currentModule.Definitions)
+ {
+ try
+ {
+ definitions.Add(CheckDefinition(definition));
+ }
+ catch (TypeCheckerException e)
+ {
+ _diagnostics.Add(e.Diagnostic);
+ }
+ }
+
+ return new TypedModule(_currentModule.Name, definitions);
+ }
+
+ private DefinitionNode CheckDefinition(DefinitionSyntax node)
+ {
+ return node switch
+ {
+ InterfaceSyntax definition => CheckInterfaceDefinition(definition),
+ FuncSyntax definition => CheckFuncDefinition(definition),
+ StructSyntax definition => CheckStructDefinition(definition),
+ _ => throw new ArgumentOutOfRangeException(nameof(node))
+ };
+ }
+
+ private InterfaceNode CheckInterfaceDefinition(InterfaceSyntax node)
+ {
+ throw new NotImplementedException();
+ }
+
+ private StructNode CheckStructDefinition(StructSyntax node)
+ {
+ var fields = new List();
+ foreach (var field in node.Fields)
+ {
+ var value = Optional.Empty();
+ if (field.Value.HasValue)
+ {
+ value = CheckExpression(field.Value.Value);
+ }
+
+ fields.Add(new StructFieldNode(field.Index, field.Name, ResolveType(field.Type), value));
+ }
+
+ var functions = new List();
+ foreach (var function in node.Functions)
+ {
+ var scope = new Scope();
+ // todo(nub31): Add this parameter
+ foreach (var parameter in function.Signature.Parameters)
+ {
+ scope.Declare(new Identifier(parameter.Name, ResolveType(parameter.Type), IdentifierKind.FunctionParameter));
+ }
+
+ _funcReturnTypes.Push(ResolveType(function.Signature.ReturnType));
+ var body = CheckBlock(function.Body, scope);
+ _funcReturnTypes.Pop();
+ functions.Add(new StructFuncNode(function.Name, CheckFuncSignature(function.Signature), body));
+ }
+
+ var interfaceImplementations = new List();
+ foreach (var interfaceImplementation in node.InterfaceImplementations)
+ {
+ var type = ResolveType(interfaceImplementation);
+ if (type is not InterfaceTypeNode interfaceType)
+ {
+ _diagnostics.Add(Diagnostic.Error($"Struct {node.Name} cannot implement non-struct type {interfaceImplementation}").At(interfaceImplementation).Build());
+ continue;
+ }
+
+ interfaceImplementations.Add(interfaceType);
+ }
+
+ return new StructNode(node.Name, fields, functions, interfaceImplementations);
+ }
+
+ private FuncNode CheckFuncDefinition(FuncSyntax node)
+ {
+ var scope = new Scope();
+ foreach (var parameter in node.Signature.Parameters)
+ {
+ scope.Declare(new Identifier(parameter.Name, ResolveType(parameter.Type), IdentifierKind.FunctionParameter));
+ }
+
+ BlockNode? body = null;
+ if (node.Body != null)
+ {
+ _funcReturnTypes.Push(ResolveType(node.Signature.ReturnType));
+ body = CheckBlock(node.Body, scope);
+ _funcReturnTypes.Pop();
+ }
+
+ return new FuncNode(node.Name, CheckFuncSignature(node.Signature), body);
+ }
+
+ private StatementNode CheckStatement(StatementSyntax node)
+ {
+ return node switch
+ {
+ AssignmentSyntax statement => CheckAssignment(statement),
+ BreakSyntax => new BreakNode(),
+ ContinueSyntax => new ContinueNode(),
+ IfSyntax statement => CheckIf(statement),
+ ReturnSyntax statement => CheckReturn(statement),
+ StatementExpressionSyntax statement => CheckStatementExpression(statement),
+ VariableDeclarationSyntax statement => CheckVariableDeclaration(statement),
+ WhileSyntax statement => CheckWhile(statement),
+ _ => throw new ArgumentOutOfRangeException(nameof(node))
+ };
+ }
+
+ private StatementNode CheckAssignment(AssignmentSyntax statement)
+ {
+ throw new NotImplementedException();
+ }
+
+ private IfNode CheckIf(IfSyntax statement)
+ {
+ throw new NotImplementedException();
+ }
+
+ private ReturnNode CheckReturn(ReturnSyntax statement)
+ {
+ var value = Optional.Empty();
+
+ if (statement.Value.HasValue)
+ {
+ value = CheckExpression(statement.Value.Value, _funcReturnTypes.Peek());
+ }
+
+ return new ReturnNode(value);
+ }
+
+ private StatementExpressionNode CheckStatementExpression(StatementExpressionSyntax statement)
+ {
+ return new StatementExpressionNode(CheckExpression(statement.Expression));
+ }
+
+ private VariableDeclarationNode CheckVariableDeclaration(VariableDeclarationSyntax statement)
+ {
+ TypeNode? type = null;
+ ExpressionNode? assignmentNode = null;
+
+ if (statement.ExplicitType.TryGetValue(out var explicitType))
+ {
+ type = ResolveType(explicitType);
+ }
+
+ if (statement.Assignment.TryGetValue(out var assignment))
+ {
+ assignmentNode = CheckExpression(assignment, type);
+ type ??= assignmentNode.Type;
+ }
+
+ if (type == null)
+ {
+ throw new TypeCheckerException(Diagnostic.Error($"Cannot infer type of variable {statement.Name}").At(statement).Build());
+ }
+
+ Scope.Declare(new Identifier(statement.Name, type, IdentifierKind.Variable));
+
+ return new VariableDeclarationNode(statement.Name, Optional.OfNullable(assignmentNode), type);
+ }
+
+ private WhileNode CheckWhile(WhileSyntax statement)
+ {
+ throw new NotImplementedException();
+ }
+
+ private FuncSignatureNode CheckFuncSignature(FuncSignatureSyntax statement)
+ {
+ var parameters = new List();
+ foreach (var parameter in statement.Parameters)
+ {
+ parameters.Add(new FuncParameterNode(parameter.Name, ResolveType(parameter.Type)));
+ }
+
+ return new FuncSignatureNode(parameters, ResolveType(statement.ReturnType));
+ }
+
+ private ExpressionNode CheckExpression(ExpressionSyntax node, TypeNode? expectedType = null)
+ {
+ var result = node switch
+ {
+ AddressOfSyntax expression => CheckAddressOf(expression),
+ ArrayIndexAccessSyntax expression => CheckArrayIndexAccess(expression),
+ ArrayInitializerSyntax expression => CheckArrayInitializer(expression),
+ BinaryExpressionSyntax expression => CheckBinaryExpression(expression),
+ DereferenceSyntax expression => CheckDereference(expression),
+ DotFuncCallSyntax expression => CheckDotFuncCall(expression),
+ FuncCallSyntax expression => CheckFuncCall(expression),
+ IdentifierSyntax expression => CheckIdentifier(expression),
+ LiteralSyntax expression => CheckLiteral(expression, expectedType),
+ StructFieldAccessSyntax expression => CheckStructFieldAccess(expression),
+ StructInitializerSyntax expression => CheckStructInitializer(expression, expectedType),
+ UnaryExpressionSyntax expression => CheckUnaryExpression(expression),
+ _ => throw new ArgumentOutOfRangeException(nameof(node))
+ };
+
+ if (expectedType == null || result.Type == expectedType)
+ {
+ return result;
+ }
+
+ if (result.Type is StructTypeNode structType && expectedType is InterfaceTypeNode interfaceType)
+ {
+ return new ConvertToInterfaceNode(interfaceType, interfaceType, structType, result);
+ }
+
+ if (result.Type is IntTypeNode sourceIntType && expectedType is IntTypeNode targetIntType)
+ {
+ if (sourceIntType.Signed == targetIntType.Signed && sourceIntType.Width < targetIntType.Width)
+ {
+ return new ConvertIntNode(targetIntType, result, sourceIntType, targetIntType);
+ }
+ }
+
+ if (result.Type is FloatTypeNode sourceFloatType && expectedType is FloatTypeNode targetFloatType)
+ {
+ if (sourceFloatType.Width < targetFloatType.Width)
+ {
+ return new ConvertFloatNode(targetFloatType, result, sourceFloatType, targetFloatType);
+ }
+ }
+
+ throw new TypeCheckerException(Diagnostic.Error($"Cannot convert {result.Type} to {expectedType}").At(node).Build());
+ }
+
+ private AddressOfNode CheckAddressOf(AddressOfSyntax expression)
+ {
+ throw new NotImplementedException();
+ }
+
+ private ArrayIndexAccessNode CheckArrayIndexAccess(ArrayIndexAccessSyntax expression)
+ {
+ throw new NotImplementedException();
+ }
+
+ private ArrayInitializerNode CheckArrayInitializer(ArrayInitializerSyntax expression)
+ {
+ throw new NotImplementedException();
+ }
+
+ private BinaryExpressionNode CheckBinaryExpression(BinaryExpressionSyntax expression)
+ {
+ throw new NotImplementedException();
+ }
+
+ private DereferenceNode CheckDereference(DereferenceSyntax expression)
+ {
+ throw new NotImplementedException();
+ }
+
+ private FuncCallNode CheckFuncCall(FuncCallSyntax expression)
+ {
+ var accessor = CheckExpression(expression.Expression);
+ if (accessor.Type is not FuncTypeNode funcType)
+ {
+ throw new TypeCheckerException(Diagnostic.Error($"Cannot call non-function type {accessor.Type}").At(expression.Expression).Build());
+ }
+
+ if (expression.Parameters.Count != funcType.Parameters.Count)
+ {
+ throw new TypeCheckerException(Diagnostic.Error($"Function {funcType} expects {funcType.Parameters} but got {expression.Parameters.Count} parameters").At(expression.Expression).Build());
+ }
+
+ var parameters = new List();
+ for (var i = 0; i < expression.Parameters.Count; i++)
+ {
+ var parameter = expression.Parameters[i];
+ var expectedType = funcType.Parameters[i];
+
+ var parameterExpression = CheckExpression(parameter, expectedType);
+ if (parameterExpression.Type != expectedType)
+ {
+ throw new Exception($"Parameter {i + 1} does not match the type {expectedType} for function {funcType}");
+ }
+
+ parameters.Add(parameterExpression);
+ }
+
+ return new FuncCallNode(funcType.ReturnType, accessor, parameters);
+ }
+
+ private ExpressionNode CheckDotFuncCall(DotFuncCallSyntax expression)
+ {
+ throw new NotImplementedException();
+ }
+
+ private ExpressionNode CheckIdentifier(IdentifierSyntax expression)
+ {
+ // If the identifier does not have a module specified, first check if a local variable or function parameter with that identifier exists
+ if (!expression.Module.TryGetValue(out var moduleName))
+ {
+ var scopeIdent = Scope.Lookup(expression.Name);
+ if (scopeIdent != null)
+ {
+ switch (scopeIdent.Kind)
+ {
+ case IdentifierKind.Variable:
+ {
+ return new VariableIdentifierNode(scopeIdent.Type, expression.Name);
+ }
+ case IdentifierKind.FunctionParameter:
+ {
+ return new FuncParameterIdentifierNode(scopeIdent.Type, expression.Name);
+ }
+ default:
+ {
+ throw new ArgumentOutOfRangeException();
+ }
+ }
+ }
+ }
+
+ moduleName ??= _currentModule.Name;
+ if (_moduleSignatures.TryGetValue(moduleName, out var module))
+ {
+ if (module.Functions.TryGetValue(expression.Name, out var function))
+ {
+ return new FuncIdentifierNode(function, moduleName, expression.Name);
+ }
+ }
+
+ throw new TypeCheckerException(Diagnostic.Error($"Identifier {expression.Name} not found").At(expression).Build());
+ }
+
+ private LiteralNode CheckLiteral(LiteralSyntax expression, TypeNode? expectedType)
+ {
+ // todo(nub31): Check if the types can actually be represented as another one. For example, an int should be passed when a string is expected
+ var type = expectedType ?? expression.Kind switch
+ {
+ LiteralKind.Integer => new IntTypeNode(true, 64),
+ LiteralKind.Float => new FloatTypeNode(64),
+ LiteralKind.String => new StringTypeNode(),
+ LiteralKind.Bool => new BoolTypeNode(),
+ _ => throw new ArgumentOutOfRangeException()
+ };
+
+ return new LiteralNode(type, expression.Value, expression.Kind);
+ }
+
+ private StructFieldAccessNode CheckStructFieldAccess(StructFieldAccessSyntax expression)
+ {
+ throw new NotImplementedException();
+ }
+
+ private StructInitializerNode CheckStructInitializer(StructInitializerSyntax expression, TypeNode? expectedType)
+ {
+ throw new NotImplementedException();
+ }
+
+ private UnaryExpressionNode CheckUnaryExpression(UnaryExpressionSyntax expression)
+ {
+ throw new NotImplementedException();
+ }
+
+ private BlockNode CheckBlock(BlockSyntax node, Scope? scope = null)
+ {
+ var statements = new List();
+
+ _scopes.Push(scope ?? Scope.SubScope());
+
+ foreach (var statement in node.Statements)
+ {
+ statements.Add(CheckStatement(statement));
+ }
+
+ _scopes.Pop();
+
+ return new BlockNode(statements);
+ }
+
+ private TypeNode ResolveType(TypeSyntax fieldType)
+ {
+ return TypeResolver.ResolveType(fieldType, _moduleSignatures);
+ }
+}
+
+public enum IdentifierKind
+{
+ Variable,
+ FunctionParameter
+}
+
+public record Identifier(string Name, TypeNode Type, IdentifierKind Kind);
+
+public class Scope(Scope? parent = null)
+{
+ private readonly List _variables = [];
+
+ public Identifier? Lookup(string name)
+ {
+ var variable = _variables.FirstOrDefault(x => x.Name == name);
+ if (variable != null)
+ {
+ return variable;
+ }
+
+ return parent?.Lookup(name);
+ }
+
+ public void Declare(Identifier identifier)
+ {
+ _variables.Add(identifier);
+ }
+
+ public Scope SubScope()
+ {
+ return new Scope(this);
+ }
+}
+
+public class TypeCheckerException : Exception
+{
+ public Diagnostic Diagnostic { get; }
+
+ public TypeCheckerException(Diagnostic diagnostic) : base(diagnostic.Message)
+ {
+ Diagnostic = diagnostic;
+ }
+}
\ No newline at end of file
diff --git a/compiler/NubLang/TypeResolver.cs b/compiler/NubLang/TypeResolver.cs
new file mode 100644
index 0000000..6bbdec2
--- /dev/null
+++ b/compiler/NubLang/TypeResolver.cs
@@ -0,0 +1,95 @@
+using NubLang.Parsing.Syntax;
+using NubLang.TypeChecking;
+using NubLang.TypeChecking.Node;
+
+namespace NubLang;
+
+public static class TypeResolver
+{
+ public static TypeNode ResolveType(TypeSyntax type, IReadOnlyDictionary modules)
+ {
+ return type switch
+ {
+ BoolTypeSyntax => new BoolTypeNode(),
+ CStringTypeSyntax => new CStringTypeNode(),
+ IntTypeSyntax i => new IntTypeNode(i.Signed, i.Width),
+ CustomTypeSyntax c => ResolveCustomType(c.Module, c.Name, modules),
+ FloatTypeSyntax f => new FloatTypeNode(f.Width),
+ FuncTypeSyntax func => new FuncTypeNode(func.Parameters.Select(x => ResolveType(x, modules)).ToList(), ResolveType(func.ReturnType, modules)),
+ ArrayTypeSyntax arr => new ArrayTypeNode(ResolveType(arr.BaseType, modules)),
+ PointerTypeSyntax ptr => new PointerTypeNode(ResolveType(ptr.BaseType, modules)),
+ StringTypeSyntax => new StringTypeNode(),
+ VoidTypeSyntax => new VoidTypeNode(),
+ _ => throw new NotSupportedException($"Unknown type syntax: {type}")
+ };
+ }
+
+ public static TypeNode ResolveCustomType(string moduleName, string typeName, IReadOnlyDictionary modules)
+ {
+ if (!modules.TryGetValue(moduleName, out var module))
+ {
+ throw new Exception("Module not found: " + moduleName);
+ }
+
+ var structType = module.StructTypes.FirstOrDefault(x => x.Name == typeName);
+ if (structType != null)
+ {
+ return structType;
+ }
+
+ var interfaceType = module.InterfaceTypes.FirstOrDefault(x => x.Name == typeName);
+ if (interfaceType != null)
+ {
+ return interfaceType;
+ }
+
+ throw new Exception($"Type {typeName} not found in module {moduleName}");
+ }
+
+ public static StructTypeNode ResolveStructType(string moduleName, string structName, IReadOnlyDictionary modules)
+ {
+ if (!modules.TryGetValue(moduleName, out var module))
+ {
+ throw new Exception("Module not found: " + moduleName);
+ }
+
+ var structType = module.StructTypes.FirstOrDefault(x => x.Name == structName);
+ if (structType != null)
+ {
+ return structType;
+ }
+
+ throw new Exception($"Struct type {structName} not found in module {moduleName}");
+ }
+
+ public static InterfaceTypeNode ResolveInterfaceType(string moduleName, string interfaceName, IReadOnlyDictionary modules)
+ {
+ if (!modules.TryGetValue(moduleName, out var module))
+ {
+ throw new Exception("Module not found: " + moduleName);
+ }
+
+ var structType = module.InterfaceTypes.FirstOrDefault(x => x.Name == interfaceName);
+ if (structType != null)
+ {
+ return structType;
+ }
+
+ throw new Exception($"Interface type {interfaceName} not found in module {moduleName}");
+ }
+
+ public static FuncTypeNode ResolveFunctionType(string moduleName, string funcName, IReadOnlyDictionary modules)
+ {
+ if (!modules.TryGetValue(moduleName, out var module))
+ {
+ throw new Exception("Module not found: " + moduleName);
+ }
+
+ if (module.Functions.TryGetValue(funcName, out var funcType))
+ {
+ return funcType;
+ }
+
+ throw new Exception($"Function type {funcName} not found in module {moduleName}");
+ }
+}
\ No newline at end of file
diff --git a/compiler/NubLang/Variant.cs b/compiler/NubLang/Variant.cs
new file mode 100644
index 0000000..c9898b8
--- /dev/null
+++ b/compiler/NubLang/Variant.cs
@@ -0,0 +1,75 @@
+using System.Diagnostics.CodeAnalysis;
+
+namespace NubLang;
+
+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 bool IsCase1([NotNullWhen(true)] out T1? value)
+ {
+ if (_value is T1 converted)
+ {
+ value = converted;
+ return true;
+ }
+
+ value = default;
+ return false;
+ }
+
+ public bool IsCase2([NotNullWhen(true)] out T2? value)
+ {
+ if (_value is T2 converted)
+ {
+ value = converted;
+ return true;
+ }
+
+ value = default;
+ return false;
+ }
+
+ 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/example/main.nub b/example/main.nub
new file mode 100644
index 0000000..1586d8c
--- /dev/null
+++ b/example/main.nub
@@ -0,0 +1,9 @@
+module "main"
+
+extern "puts" func puts(text: cstring)
+
+extern "main" func main(args: []cstring): i64
+{
+ puts("test")
+ return 0
+}
diff --git a/example/x86_64.s b/example/x86_64.s
new file mode 100644
index 0000000..cded159
--- /dev/null
+++ b/example/x86_64.s
@@ -0,0 +1,10 @@
+.intel_syntax noprefix
+
+.text
+.globl _start
+_start:
+ mov rdi, rsp
+ call main.main
+ mov rdi, rax
+ mov rax, 60
+ syscall
diff --git a/vscode/.gitignore b/vscode/.gitignore
new file mode 100644
index 0000000..c5e82d7
--- /dev/null
+++ b/vscode/.gitignore
@@ -0,0 +1 @@
+bin
\ No newline at end of file
diff --git a/vscode/language-configuration.json b/vscode/language-configuration.json
new file mode 100644
index 0000000..538ffc1
--- /dev/null
+++ b/vscode/language-configuration.json
@@ -0,0 +1,29 @@
+{
+ "comments": {
+ "lineComment": "//",
+ "blockComment": ["/*", "*/"]
+ },
+ "brackets": [
+ ["{", "}"],
+ ["[", "]"],
+ ["(", ")"]
+ ],
+ "autoClosingPairs": [
+ ["{", "}"],
+ ["[", "]"],
+ ["(", ")"],
+ ["\"", "\""],
+ ["'", "'"]
+ ],
+ "surroundingPairs": [
+ ["{", "}"],
+ ["[", "]"],
+ ["(", ")"],
+ ["\"", "\""],
+ ["'", "'"]
+ ],
+ "indentationRules": {
+ "increaseIndentPattern": "^((?!\\/\\*).)*(\\{[^}\"'`]*|\\([^)\"'`]*|\\[[^\\]\"'`]*)$",
+ "decreaseIndentPattern": "^((?!.*?\\/\\*).*\\*/)?\\s*[\\}\\]\\)].*$"
+ }
+}
diff --git a/vscode/makefile b/vscode/makefile
new file mode 100644
index 0000000..f2b1655
--- /dev/null
+++ b/vscode/makefile
@@ -0,0 +1,6 @@
+vscode:
+ mkdir -p bin
+ npx vsce package -o bin/nub-lang.vsix
+
+clean:
+ rm -r bin
\ No newline at end of file
diff --git a/vscode/package.json b/vscode/package.json
new file mode 100644
index 0000000..a988628
--- /dev/null
+++ b/vscode/package.json
@@ -0,0 +1,40 @@
+{
+ "name": "nub-lang",
+ "displayName": "Nub Language Support",
+ "description": "Syntax highlighting for Nub programming language",
+ "version": "0.0.0",
+ "publisher": "nub31",
+ "repository": {
+ "type": "git",
+ "url": "https://git.oliste.no/nub31/nub-lang"
+ },
+ "license": "MIT",
+ "engines": {
+ "vscode": "^1.74.0"
+ },
+ "categories": [
+ "Programming Languages"
+ ],
+ "contributes": {
+ "languages": [
+ {
+ "id": "nub",
+ "aliases": [
+ "Nub",
+ "nub"
+ ],
+ "extensions": [
+ ".nub"
+ ],
+ "configuration": "./language-configuration.json"
+ }
+ ],
+ "grammars": [
+ {
+ "language": "nub",
+ "scopeName": "source.nub",
+ "path": "./syntaxes/nub.tmLanguage.json"
+ }
+ ]
+ }
+}
diff --git a/vscode/syntaxes/nub.tmLanguage.json b/vscode/syntaxes/nub.tmLanguage.json
new file mode 100644
index 0000000..4a4780c
--- /dev/null
+++ b/vscode/syntaxes/nub.tmLanguage.json
@@ -0,0 +1,300 @@
+{
+ "$schema": "https://raw.githubusercontent.com/martinring/tmlanguage/master/tmlanguage.json",
+ "name": "Nub",
+ "scopeName": "source.nub",
+ "patterns": [
+ {
+ "include": "#comments"
+ },
+ {
+ "include": "#keywords"
+ },
+ {
+ "include": "#modifiers"
+ },
+ {
+ "include": "#types"
+ },
+ {
+ "include": "#strings"
+ },
+ {
+ "include": "#numbers"
+ },
+ {
+ "include": "#operators"
+ },
+ {
+ "include": "#function-definition"
+ },
+ {
+ "include": "#struct-definition"
+ },
+ {
+ "include": "#function-call"
+ },
+ {
+ "include": "#identifiers"
+ }
+ ],
+ "repository": {
+ "comments": {
+ "patterns": [
+ {
+ "name": "comment.line.double-slash.nub",
+ "begin": "//",
+ "end": "$"
+ },
+ {
+ "name": "comment.block.nub",
+ "begin": "/\\*",
+ "end": "\\*/"
+ }
+ ]
+ },
+ "keywords": {
+ "patterns": [
+ {
+ "name": "keyword.control.nub",
+ "match": "\\b(if|else|while|break|continue|return|let)\\b"
+ },
+ {
+ "name": "keyword.other.nub",
+ "match": "\\b(namespace|func|struct|interface)\\b"
+ }
+ ]
+ },
+ "modifiers": {
+ "patterns": [
+ {
+ "name": "storage.modifier.nub",
+ "match": "\\b(export|extern|calls)\\b"
+ }
+ ]
+ },
+ "types": {
+ "patterns": [
+ {
+ "include": "#function-type"
+ },
+ {
+ "name": "storage.type.primitive.nub",
+ "match": "\\b(i8|i16|i32|i64|u8|u16|u32|u64|f32|f64|bool|string|cstring|void|any)\\b"
+ },
+ {
+ "name": "storage.type.array.nub",
+ "match": "\\[\\]"
+ },
+ {
+ "name": "storage.type.pointer.nub",
+ "match": "\\^"
+ }
+ ]
+ },
+ "function-type": {
+ "patterns": [
+ {
+ "begin": "\\b(func)\\s*\\(",
+ "beginCaptures": {
+ "1": {
+ "name": "storage.type.function.nub"
+ }
+ },
+ "end": "(?<=\\))(?:\\s*:\\s*([^\\s,;{}()]+))?",
+ "endCaptures": {
+ "1": {
+ "name": "storage.type.nub"
+ }
+ },
+ "patterns": [
+ {
+ "include": "#function-type-parameters"
+ }
+ ]
+ }
+ ]
+ },
+ "function-type-parameters": {
+ "patterns": [
+ {
+ "match": "\\.\\.\\.",
+ "name": "keyword.operator.variadic.nub"
+ },
+ {
+ "include": "#types"
+ },
+ {
+ "match": ",",
+ "name": "punctuation.separator.nub"
+ }
+ ]
+ },
+ "strings": {
+ "patterns": [
+ {
+ "name": "string.quoted.double.nub",
+ "begin": "\"",
+ "end": "\"",
+ "patterns": [
+ {
+ "name": "constant.character.escape.nub",
+ "match": "\\\\(n|t|r|\\\\|\"|')"
+ },
+ {
+ "name": "constant.character.escape.nub",
+ "match": "\\\\[0-7]{1,3}"
+ },
+ {
+ "name": "constant.character.escape.nub",
+ "match": "\\\\x[0-9A-Fa-f]{1,2}"
+ }
+ ]
+ },
+ {
+ "name": "string.quoted.single.nub",
+ "begin": "'",
+ "end": "'",
+ "patterns": [
+ {
+ "name": "constant.character.escape.nub",
+ "match": "\\\\(n|t|r|\\\\|\"|')"
+ }
+ ]
+ }
+ ]
+ },
+ "numbers": {
+ "patterns": [
+ {
+ "name": "constant.numeric.float.nub",
+ "match": "\\b\\d+\\.\\d*([eE][+-]?\\d+)?[fF]?\\b"
+ },
+ {
+ "name": "constant.numeric.integer.decimal.nub",
+ "match": "\\b\\d+\\b"
+ },
+ {
+ "name": "constant.numeric.integer.hexadecimal.nub",
+ "match": "\\b0[xX][0-9A-Fa-f]+\\b"
+ },
+ {
+ "name": "constant.numeric.integer.binary.nub",
+ "match": "\\b0[bB][01]+\\b"
+ }
+ ]
+ },
+ "operators": {
+ "patterns": [
+ {
+ "name": "keyword.operator.assignment.nub",
+ "match": "="
+ },
+ {
+ "name": "keyword.operator.comparison.nub",
+ "match": "(==|!=|<=|>=|<|>)"
+ },
+ {
+ "name": "keyword.operator.arithmetic.nub",
+ "match": "(\\+|\\-|\\*|/)"
+ },
+ {
+ "name": "keyword.operator.logical.nub",
+ "match": "(&&|\\|\\||!)"
+ },
+ {
+ "name": "keyword.operator.address.nub",
+ "match": "&"
+ },
+ {
+ "name": "keyword.operator.dereference.nub",
+ "match": "\\^"
+ },
+ {
+ "name": "keyword.operator.member-access.nub",
+ "match": "\\."
+ }
+ ]
+ },
+ "function-definition": {
+ "patterns": [
+ {
+ "begin": "\\b(global\\s+|extern\\s+)?(func)\\s+([a-zA-Z_][a-zA-Z0-9_]*)\\s*\\(",
+ "beginCaptures": {
+ "1": {
+ "name": "storage.modifier.nub"
+ },
+ "2": {
+ "name": "keyword.other.nub"
+ },
+ "3": {
+ "name": "entity.name.function.nub"
+ }
+ },
+ "end": "\\)",
+ "patterns": [
+ {
+ "include": "#function-parameters"
+ }
+ ]
+ }
+ ]
+ },
+ "struct-definition": {
+ "patterns": [
+ {
+ "match": "\\b(struct)\\s+([a-zA-Z_][a-zA-Z0-9_]*)\\b",
+ "captures": {
+ "1": {
+ "name": "keyword.other.nub"
+ },
+ "2": {
+ "name": "entity.name.type.struct.nub"
+ }
+ }
+ }
+ ]
+ },
+ "function-parameters": {
+ "patterns": [
+ {
+ "match": "\\.\\.\\.",
+ "name": "keyword.operator.variadic.nub"
+ },
+ {
+ "match": "([a-zA-Z_][a-zA-Z0-9_]*)\\s*:\\s*",
+ "captures": {
+ "1": {
+ "name": "variable.parameter.nub"
+ }
+ }
+ },
+ {
+ "include": "#types"
+ },
+ {
+ "include": "#identifiers"
+ }
+ ]
+ },
+ "function-call": {
+ "patterns": [
+ {
+ "match": "([a-zA-Z_][a-zA-Z0-9_]*)\\s*(?=\\()",
+ "captures": {
+ "1": {
+ "name": "entity.name.function.call.nub"
+ }
+ }
+ }
+ ]
+ },
+ "identifiers": {
+ "patterns": [
+ {
+ "name": "variable.other.nub",
+ "match": "\\b[a-zA-Z_][a-zA-Z0-9_]*\\b"
+ }
+ ]
+ }
+ }
+}
\ No newline at end of file