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