This commit is contained in:
nub31
2025-06-26 11:58:45 +02:00
parent f40bbe4b0b
commit 7cd1ea1dce
49 changed files with 5746 additions and 0 deletions

34
src/compilation/.gitignore vendored Normal file
View File

@@ -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

13
src/compilation/.idea/.idea.Compiler/.idea/.gitignore generated vendored Normal file
View File

@@ -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

View File

@@ -0,0 +1 @@
Compiler

View File

@@ -0,0 +1,4 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="Encoding" addBOMForNewFiles="with BOM under Windows, with no BOM otherwise" />
</project>

View File

@@ -0,0 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="UserContentModel">
<attachedFolders>
<Path>Runtime</Path>
</attachedFolders>
<explicitIncludes />
<explicitExcludes />
</component>
</project>

View File

@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="$PROJECT_DIR$/.." vcs="Git" />
</component>
</project>

View File

@@ -0,0 +1,25 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<AssemblyName>nub</AssemblyName>
<OutputType>Exe</OutputType>
<TargetFramework>net9.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<PublishAot>true</PublishAot>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\Generation\Generation.csproj" />
<ProjectReference Include="..\Syntax\Syntax.csproj" />
</ItemGroup>
<ItemGroup>
<EmbeddedResource Include="Runtime\entry.s" />
<EmbeddedResource Include="Runtime\nub_mem.s" />
<EmbeddedResource Include="Runtime\nub_panic.s" />
<EmbeddedResource Include="Runtime\nub_cstring.s" />
<EmbeddedResource Include="Runtime\nub_string.s" />
</ItemGroup>
</Project>

View File

@@ -0,0 +1,52 @@
using System.Diagnostics;
namespace CLI;
public static class GCC
{
public static async Task<bool> 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<bool> Link(List<string> 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;
}
}

View File

@@ -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<Diagnostic>();
var syntaxTrees = new List<SyntaxTree>();
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<BoundSyntaxTree>();
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<string>();
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;

View File

@@ -0,0 +1,37 @@
using System.Diagnostics;
namespace CLI;
public static class QBE
{
public static async Task<string?> 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;
}
}

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -0,0 +1,10 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net9.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<IsAotCompatible>true</IsAotCompatible>
</PropertyGroup>
</Project>

View File

@@ -0,0 +1,77 @@
using System.Diagnostics.CodeAnalysis;
namespace Common;
public readonly struct Optional
{
public static Optional<TValue> Empty<TValue>() => new();
/// <summary>
/// Alias for creating an Optional&lt;TValue&gt; which allows for implicit types
/// </summary>
/// <param name="value"></param>
/// <typeparam name="TValue"></typeparam>
/// <returns></returns>
public static Optional<TValue> OfNullable<TValue>(TValue? value)
{
return value ?? Optional<TValue>.Empty();
}
}
public readonly struct Optional<TValue>
{
public static Optional<TValue> Empty() => new();
public static Optional<TValue> 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>(TValue value) => new(value);
public TValue Or(TValue other)
{
if (HasValue)
{
return Value;
}
return other;
}
}

View File

@@ -0,0 +1,49 @@
namespace Common;
public readonly struct Variant<T1, T2> 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<T1> on1, Action<T2> on2)
{
switch (_value)
{
case T1 v1:
on1(v1);
break;
case T2 v2:
on2(v2);
break;
default:
throw new InvalidCastException();
}
}
public T Match<T>(Func<T1, T> on1, Func<T2, T> on2)
{
return _value switch
{
T1 v1 => on1(v1),
T2 v2 => on2(v2),
_ => throw new InvalidCastException()
};
}
public static implicit operator Variant<T1, T2>(T1 value) => new(value);
public static implicit operator Variant<T1, T2>(T2 value) => new(value);
}

View File

@@ -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

View File

@@ -0,0 +1,15 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net9.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<IsAotCompatible>true</IsAotCompatible>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\Common\Common.csproj" />
<ProjectReference Include="..\Syntax\Syntax.csproj" />
</ItemGroup>
</Project>

File diff suppressed because it is too large Load Diff

View File

@@ -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<SyntaxTree> _syntaxTrees;
public DefinitionTable(IEnumerable<SyntaxTree> syntaxTrees)
{
_syntaxTrees = syntaxTrees;
}
public Optional<FuncDefinition> LookupFunc(string @namespace, string name)
{
var definition = _syntaxTrees
.Where(c => c.Namespace == @namespace)
.SelectMany(c => c.Definitions)
.OfType<FuncDefinition>()
.SingleOrDefault(f => f.Name == name);
return Optional.OfNullable(definition);
}
public Optional<StructDefinitionNode> LookupStruct(string @namespace, string name)
{
var definition = _syntaxTrees
.Where(c => c.Namespace == @namespace)
.SelectMany(c => c.Definitions)
.OfType<StructDefinitionNode>()
.SingleOrDefault(f => f.Name == name);
return Optional.OfNullable(definition);
}
public IEnumerable<StructDefinitionNode> GetStructs()
{
return _syntaxTrees
.SelectMany(c => c.Definitions)
.OfType<StructDefinitionNode>();
}
}
public class BoundDefinitionTable
{
private readonly IEnumerable<BoundSyntaxTree> _syntaxTrees;
public BoundDefinitionTable(IEnumerable<BoundSyntaxTree> syntaxTrees)
{
_syntaxTrees = syntaxTrees;
}
public Optional<BoundFuncDefinition> LookupFunc(string @namespace, string name)
{
var definition = _syntaxTrees
.Where(c => c.Namespace == @namespace)
.SelectMany(c => c.Definitions)
.OfType<BoundFuncDefinition>()
.SingleOrDefault(f => f.Name == name);
return Optional.OfNullable(definition);
}
public Optional<BoundStructDefinitionNode> LookupStruct(string @namespace, string name)
{
var definition = _syntaxTrees
.Where(c => c.Namespace == @namespace)
.SelectMany(c => c.Definitions)
.OfType<BoundStructDefinitionNode>()
.SingleOrDefault(f => f.Name == name);
return Optional.OfNullable(definition);
}
public IEnumerable<BoundStructDefinitionNode> GetStructs()
{
return _syntaxTrees
.SelectMany(c => c.Definitions)
.OfType<BoundStructDefinitionNode>();
}
}

View File

@@ -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);
}
}

View File

@@ -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
}

View File

@@ -0,0 +1,15 @@
using Common;
using Syntax.Tokenization;
using Syntax.Typing;
namespace Syntax.Parsing.Node;
public abstract record DefinitionNode(IEnumerable<Token> Tokens, Optional<string> Documentation, string Namespace) : Node(Tokens);
public record FuncParameter(string Name, NubType Type);
public abstract record FuncDefinition(IEnumerable<Token> Tokens, Optional<string> Documentation, string Namespace, string Name, List<FuncParameter> Parameters, NubType ReturnType) : DefinitionNode(Tokens, Documentation, Namespace);
public record LocalFuncDefinitionNode(IEnumerable<Token> Tokens, Optional<string> Documentation, string Namespace, string Name, List<FuncParameter> Parameters, BlockNode Body, NubType ReturnType, bool Exported) : FuncDefinition(Tokens, Documentation, Namespace, Name, Parameters, ReturnType);
public record ExternFuncDefinitionNode(IEnumerable<Token> Tokens, Optional<string> Documentation, string Namespace, string Name, string CallName, List<FuncParameter> Parameters, NubType ReturnType) : FuncDefinition(Tokens, Documentation, Namespace, Name, Parameters, ReturnType);
public record StructField(string Name, NubType Type, Optional<ExpressionNode> Value);
public record StructDefinitionNode(IEnumerable<Token> Tokens, Optional<string> Documentation, string Namespace, string Name, List<StructField> Fields) : DefinitionNode(Tokens, Documentation, Namespace);

View File

@@ -0,0 +1,44 @@
using Common;
using Syntax.Tokenization;
using Syntax.Typing;
namespace Syntax.Parsing.Node;
public abstract record ExpressionNode(IEnumerable<Token> Tokens) : Node(Tokens);
public abstract record LValueNode(IEnumerable<Token> Tokens) : ExpressionNode(Tokens);
public record BinaryExpressionNode(IEnumerable<Token> 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<Token> Tokens, UnaryExpressionOperator Operator, ExpressionNode Operand) : ExpressionNode(Tokens);
public enum UnaryExpressionOperator
{
Negate,
Invert
}
public record FuncCallNode(IEnumerable<Token> Tokens, ExpressionNode Expression, List<ExpressionNode> Parameters) : ExpressionNode(Tokens);
public record IdentifierNode(IEnumerable<Token> Tokens, Optional<string> Namespace, string Name) : LValueNode(Tokens);
public record ArrayInitializerNode(IEnumerable<Token> Tokens, ExpressionNode Capacity, NubType ElementType) : ExpressionNode(Tokens);
public record ArrayIndexAccessNode(IEnumerable<Token> Tokens, ExpressionNode Array, ExpressionNode Index) : LValueNode(Tokens);
public record AnonymousFuncNode(IEnumerable<Token> Tokens, List<FuncParameter> Parameters, BlockNode Body, NubType ReturnType) : ExpressionNode(Tokens);
public record AddressOfNode(IEnumerable<Token> Tokens, LValueNode Expression) : ExpressionNode(Tokens);
public record FixedArrayInitializerNode(IEnumerable<Token> Tokens, NubType ElementType, int Capacity) : ExpressionNode(Tokens);
public record LiteralNode(IEnumerable<Token> Tokens, string Literal, LiteralKind Kind) : ExpressionNode(Tokens);
public record MemberAccessNode(IEnumerable<Token> Tokens, ExpressionNode Expression, string Member) : ExpressionNode(Tokens);
public record StructInitializerNode(IEnumerable<Token> Tokens, NubStructType StructType, Dictionary<string, ExpressionNode> Initializers) : ExpressionNode(Tokens);
public record DereferenceNode(IEnumerable<Token> Tokens, ExpressionNode Expression) : LValueNode(Tokens);

View File

@@ -0,0 +1,6 @@
using Syntax.Tokenization;
namespace Syntax.Parsing.Node;
public abstract record Node(IEnumerable<Token> Tokens);
public record BlockNode(IEnumerable<Token> Tokens, List<StatementNode> Statements) : Node(Tokens);

View File

@@ -0,0 +1,18 @@
using Common;
using Syntax.Tokenization;
using Syntax.Typing;
namespace Syntax.Parsing.Node;
public record StatementNode(IEnumerable<Token> Tokens) : Node(Tokens);
public record StatementExpressionNode(IEnumerable<Token> Tokens, ExpressionNode Expression) : StatementNode(Tokens);
public record ReturnNode(IEnumerable<Token> Tokens, Optional<ExpressionNode> Value) : StatementNode(Tokens);
public record MemberAssignmentNode(IEnumerable<Token> Tokens, MemberAccessNode MemberAccess, ExpressionNode Value) : StatementNode(Tokens);
public record IfNode(IEnumerable<Token> Tokens, ExpressionNode Condition, BlockNode Body, Optional<Variant<IfNode, BlockNode>> Else) : StatementNode(Tokens);
public record DereferenceAssignmentNode(IEnumerable<Token> Tokens, DereferenceNode Dereference, ExpressionNode Value) : StatementNode(Tokens);
public record VariableAssignmentNode(IEnumerable<Token> Tokens, IdentifierNode Identifier, ExpressionNode Value) : StatementNode(Tokens);
public record VariableDeclarationNode(IEnumerable<Token> Tokens, string Name, NubType Type) : StatementNode(Tokens);
public record ContinueNode(IEnumerable<Token> Tokens) : StatementNode(Tokens);
public record BreakNode(IEnumerable<Token> Tokens) : StatementNode(Tokens);
public record ArrayIndexAssignmentNode(IEnumerable<Token> Tokens, ArrayIndexAccessNode ArrayIndexAccess, ExpressionNode Value) : StatementNode(Tokens);
public record WhileNode(IEnumerable<Token> Tokens, ExpressionNode Condition, BlockNode Body) : StatementNode(Tokens);

View File

@@ -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<Diagnostic> _diagnostics = [];
private static IEnumerable<Token> _tokens = [];
private static int _index;
public static SyntaxTree? ParseFile(IEnumerable<Token> tokens, string filePath, out IEnumerable<Diagnostic> 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<DefinitionNode> 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<ModifierToken> modifiers = [];
List<string> 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<ModifierToken> modifiers, Optional<string> documentation)
{
var name = ExpectIdentifier();
List<FuncParameter> 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<ModifierToken> _, Optional<string> documentation)
{
var name = ExpectIdentifier().Value;
ExpectSymbol(Symbol.OpenBrace);
List<StructField> variables = [];
while (!TryExpectSymbol(Symbol.CloseBrace))
{
var variableName = ExpectIdentifier().Value;
ExpectSymbol(Symbol.Colon);
var variableType = ParseType();
var variableValue = Optional<ExpressionNode>.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<ExpressionNode>.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<Variant<IfNode, BlockNode>>.Empty();
if (TryExpectSymbol(Symbol.Else))
{
var newStartIndex = _index;
elseStatement = TryExpectSymbol(Symbol.If)
? (Variant<IfNode, BlockNode>)ParseIf(newStartIndex)
: (Variant<IfNode, BlockNode>)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<string>.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<FuncParameter> 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<string, ExpressionNode> 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<ExpressionNode>();
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<StatementNode> 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<NubType> 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<Token> 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<Token>.Empty();
}
private static void Next()
{
while (_index < _tokens.Count() && _tokens.ElementAt(_index) is DocumentationToken)
{
_index++;
}
_index++;
}
private static IEnumerable<Token> 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;
}
}

View File

@@ -0,0 +1,5 @@
using Syntax.Parsing.Node;
namespace Syntax.Parsing;
public record SyntaxTree(string FilePath, string Namespace, List<DefinitionNode> Definitions);

View File

@@ -0,0 +1,274 @@
using System.Diagnostics.CodeAnalysis;
namespace Syntax;
/// <summary>
/// Represents a location in source code with line and column information.
/// Lines and columns are 1-based to match typical editor conventions.
/// </summary>
public readonly struct SourceLocation : IEquatable<SourceLocation>, IComparable<SourceLocation>
{
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;
}
/// <summary>
/// Represents source text with a name (typically filename) and content.
/// Equality is based on both name and content for better semantics.
/// </summary>
public struct SourceText : IEquatable<SourceText>
{
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;
}
/// <summary>
/// Gets a specific line from the source text (1-based).
/// </summary>
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);
}
/// <summary>
/// Represents a span of source code from a start to end location within a source text.
/// </summary>
public readonly struct SourceSpan : IEquatable<SourceSpan>
{
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; }
/// <summary>
/// Gets whether this span represents a single point (start == end).
/// </summary>
public bool IsEmpty => Start == End;
/// <summary>
/// Gets whether this span is contained within a single line.
/// </summary>
public bool IsSingleLine => Start.Line == End.Line;
/// <summary>
/// Gets the text content covered by this span.
/// </summary>
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<string>();
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);
}
/// <summary>
/// Merges multiple source spans from the same file into a single span.
/// The result spans from the earliest start to the latest end.
/// </summary>
public static SourceSpan Merge(IEnumerable<SourceSpan> 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);
}
}

View File

@@ -0,0 +1,14 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net9.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<IsAotCompatible>true</IsAotCompatible>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\Common\Common.csproj" />
</ItemGroup>
</Project>

View File

@@ -0,0 +1,6 @@
namespace Syntax.Tokenization;
public class DocumentationToken(SourceSpan span, string documentation) : Token(span)
{
public string Documentation { get; } = documentation;
}

View File

@@ -0,0 +1,6 @@
namespace Syntax.Tokenization;
public class IdentifierToken(SourceSpan span, string value) : Token(span)
{
public string Value { get; } = value;
}

View File

@@ -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
}

View File

@@ -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
}

View File

@@ -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
}

View File

@@ -0,0 +1,6 @@
namespace Syntax.Tokenization;
public abstract class Token(SourceSpan span)
{
public SourceSpan Span { get; } = span;
}

View File

@@ -0,0 +1,283 @@
using Common;
using Syntax.Diagnostics;
namespace Syntax.Tokenization;
public static class Tokenizer
{
private static readonly Dictionary<string, Symbol> 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<string, Modifier> Modifiers = new()
{
["export"] = Modifier.Export,
["extern"] = Modifier.Extern,
};
private static readonly Dictionary<char[], Symbol> Chians = new()
{
[['=', '=']] = Symbol.Equal,
[['!', '=']] = Symbol.NotEqual,
[['<', '=']] = Symbol.LessThanOrEqual,
[['>', '=']] = Symbol.GreaterThanOrEqual,
[[':', ':']] = Symbol.DoubleColon,
};
private static readonly Dictionary<char, Symbol> 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<Token> Tokenize(SourceText sourceText, out IEnumerable<Diagnostic> diagnostics)
{
_sourceText = sourceText;
_index = 0;
List<Token> 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<Token> ParseToken()
{
ConsumeWhitespace();
var startIndex = _index;
if (!Peek().TryGetValue(out var current))
{
return Optional<Token>.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<char> Peek(int offset = 0)
{
if (_index + offset < _sourceText.Content.Length)
{
return _sourceText.Content[_index + offset];
}
return Optional<char>.Empty();
}
private static void Next()
{
_index++;
}
}

View File

@@ -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<string, NubType> _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<BoundDefinitionNode>();
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<BoundStructField>();
foreach (var structField in node.Fields)
{
var value = Optional.Empty<BoundExpressionNode>();
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<BoundFuncParameter>();
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<BoundFuncParameter>();
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<BoundStatementNode>();
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<Variant<BoundIfNode, BoundBlockNode>>();
if (statement.Else.HasValue)
{
elseStatement = statement.Else.Value.Match<Variant<BoundIfNode, BoundBlockNode>>
(
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<BoundExpressionNode>();
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<BoundFuncParameter>();
var parameterTypes = new List<NubType>();
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<BoundExpressionNode>();
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<string, BoundExpressionNode>();
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);
}
}

View File

@@ -0,0 +1,14 @@
using Common;
using Syntax.Tokenization;
namespace Syntax.Typing.BoundNode;
public abstract record BoundDefinitionNode(IEnumerable<Token> Tokens, Optional<string> Documentation, string Namespace) : BoundNode(Tokens);
public record BoundFuncParameter(string Name, NubType Type);
public abstract record BoundFuncDefinition(IEnumerable<Token> Tokens, Optional<string> Documentation, string Namespace, string Name, List<BoundFuncParameter> Parameters, NubType ReturnType) : BoundDefinitionNode(Tokens, Documentation, Namespace);
public record BoundLocalFuncDefinitionNode(IEnumerable<Token> Tokens, Optional<string> Documentation, string Namespace, string Name, List<BoundFuncParameter> Parameters, BoundBlockNode Body, NubType ReturnType, bool Exported) : BoundFuncDefinition(Tokens, Documentation, Namespace, Name, Parameters, ReturnType);
public record BoundExternFuncDefinitionNode(IEnumerable<Token> Tokens, Optional<string> Documentation, string Namespace, string Name, string CallName, List<BoundFuncParameter> Parameters, NubType ReturnType) : BoundFuncDefinition(Tokens, Documentation, Namespace, Name, Parameters, ReturnType);
public record BoundStructField(string Name, NubType Type, Optional<BoundExpressionNode> Value);
public record BoundStructDefinitionNode(IEnumerable<Token> Tokens, Optional<string> Documentation, string Namespace, string Name, List<BoundStructField> Fields) : BoundDefinitionNode(Tokens, Documentation, Namespace);

View File

@@ -0,0 +1,21 @@
using Common;
using Syntax.Parsing.Node;
using Syntax.Tokenization;
namespace Syntax.Typing.BoundNode;
public abstract record BoundExpressionNode(IEnumerable<Token> Tokens, NubType Type) : BoundNode(Tokens);
public abstract record BoundLValueNode(IEnumerable<Token> Tokens, NubType Type) : BoundExpressionNode(Tokens, Type);
public record BoundBinaryExpressionNode(IEnumerable<Token> Tokens, NubType Type, BoundExpressionNode Left, BinaryExpressionOperator Operator, BoundExpressionNode Right) : BoundExpressionNode(Tokens, Type);
public record BoundUnaryExpressionNode(IEnumerable<Token> Tokens, NubType Type, UnaryExpressionOperator Operator, BoundExpressionNode Operand) : BoundExpressionNode(Tokens, Type);
public record BoundFuncCallNode(IEnumerable<Token> Tokens, NubType Type, BoundExpressionNode Expression, List<BoundExpressionNode> Parameters) : BoundExpressionNode(Tokens, Type);
public record BoundIdentifierNode(IEnumerable<Token> Tokens, NubType Type, Optional<string> Namespace, string Name) : BoundLValueNode(Tokens, Type);
public record BoundArrayInitializerNode(IEnumerable<Token> Tokens, NubType Type, BoundExpressionNode Capacity, NubType ElementType) : BoundExpressionNode(Tokens, Type);
public record BoundArrayIndexAccessNode(IEnumerable<Token> Tokens, NubType Type, BoundExpressionNode Array, BoundExpressionNode Index) : BoundLValueNode(Tokens, Type);
public record BoundAnonymousFuncNode(IEnumerable<Token> Tokens, NubType Type, List<BoundFuncParameter> Parameters, BoundBlockNode Body, NubType ReturnType) : BoundExpressionNode(Tokens, Type);
public record BoundAddressOfNode(IEnumerable<Token> Tokens, NubType Type, BoundLValueNode Expression) : BoundExpressionNode(Tokens, Type);
public record BoundFixedArrayInitializerNode(IEnumerable<Token> Tokens, NubType Type, NubType ElementType, int Capacity) : BoundExpressionNode(Tokens, Type);
public record BoundLiteralNode(IEnumerable<Token> Tokens, NubType Type, string Literal, LiteralKind Kind) : BoundExpressionNode(Tokens, Type);
public record BoundMemberAccessNode(IEnumerable<Token> Tokens, NubType Type, BoundExpressionNode Expression, string Member) : BoundExpressionNode(Tokens, Type);
public record BoundStructInitializerNode(IEnumerable<Token> Tokens, NubType Type, NubStructType StructType, Dictionary<string, BoundExpressionNode> Initializers) : BoundExpressionNode(Tokens, Type);
public record BoundDereferenceNode(IEnumerable<Token> Tokens, NubType Type, BoundExpressionNode Expression) : BoundLValueNode(Tokens, Type);

View File

@@ -0,0 +1,6 @@
using Syntax.Tokenization;
namespace Syntax.Typing.BoundNode;
public abstract record BoundNode(IEnumerable<Token> Tokens);
public record BoundBlockNode(IEnumerable<Token> Tokens, List<BoundStatementNode> Statements) : BoundNode(Tokens);

View File

@@ -0,0 +1,17 @@
using Common;
using Syntax.Tokenization;
namespace Syntax.Typing.BoundNode;
public record BoundStatementNode(IEnumerable<Token> Tokens) : BoundNode(Tokens);
public record BoundStatementExpressionNode(IEnumerable<Token> Tokens, BoundExpressionNode Expression) : BoundStatementNode(Tokens);
public record BoundReturnNode(IEnumerable<Token> Tokens, Optional<BoundExpressionNode> Value) : BoundStatementNode(Tokens);
public record BoundMemberAssignmentNode(IEnumerable<Token> Tokens, BoundMemberAccessNode MemberAccess, BoundExpressionNode Value) : BoundStatementNode(Tokens);
public record BoundIfNode(IEnumerable<Token> Tokens, BoundExpressionNode Condition, BoundBlockNode Body, Optional<Variant<BoundIfNode, BoundBlockNode>> Else) : BoundStatementNode(Tokens);
public record BoundDereferenceAssignmentNode(IEnumerable<Token> Tokens, BoundDereferenceNode Dereference, BoundExpressionNode Value) : BoundStatementNode(Tokens);
public record BoundVariableAssignmentNode(IEnumerable<Token> Tokens, BoundIdentifierNode Identifier, BoundExpressionNode Value) : BoundStatementNode(Tokens);
public record BoundVariableDeclarationNode(IEnumerable<Token> Tokens, string Name, NubType Type) : BoundStatementNode(Tokens);
public record BoundContinueNode(IEnumerable<Token> Tokens) : BoundStatementNode(Tokens);
public record BoundBreakNode(IEnumerable<Token> Tokens) : BoundStatementNode(Tokens);
public record BoundArrayIndexAssignmentNode(IEnumerable<Token> Tokens, BoundArrayIndexAccessNode ArrayIndexAccess, BoundExpressionNode Value) : BoundStatementNode(Tokens);
public record BoundWhileNode(IEnumerable<Token> Tokens, BoundExpressionNode Condition, BoundBlockNode Body) : BoundStatementNode(Tokens);

View File

@@ -0,0 +1,5 @@
using Syntax.Typing.BoundNode;
namespace Syntax.Typing;
public record BoundSyntaxTree(string FilePath, string Namespace, List<BoundDefinitionNode> Definitions);

View File

@@ -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<NubType> parameters) : NubType
{
public NubType ReturnType { get; } = returnType;
public List<NubType> 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
}

View File

@@ -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<string, NubType> _variables = new();
// private static List<Diagnostic> _diagnostics = [];
// private static NubType? _currentFunctionReturnType;
// private static Queue<AnonymousFuncNode> _anonymousFunctions = [];
//
// public static void Check(SyntaxTree syntaxTree, DefinitionTable definitionTable, out IEnumerable<Diagnostic> diagnostics)
// {
// _syntaxTree = syntaxTree;
// _definitionTable = definitionTable;
//
// _variables = new Dictionary<string, NubType>();
// _diagnostics = [];
// _currentFunctionReturnType = null;
// _anonymousFunctions = [];
//
// foreach (var structDef in syntaxTree.Definitions.OfType<StructDefinitionNode>())
// {
// CheckStructDef(structDef);
// }
//
// foreach (var funcDef in syntaxTree.Definitions.OfType<LocalFuncDefinitionNode>())
// {
// 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<string, NubType>();
// 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<FuncParameter> 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<string>();
//
// 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;
// }
// }
// }

12
src/runtime/entry.s Normal file
View File

@@ -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

17
src/runtime/runtime.c Normal file
View File

@@ -0,0 +1,17 @@
#include <stdint.h>
#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() {}