This commit is contained in:
nub31
2025-09-11 21:22:25 +02:00
parent 73baf3d73b
commit 5fecfeba43
54 changed files with 10 additions and 5443 deletions

1
example/.gitignore vendored
View File

@@ -1,2 +1,3 @@
.build
out.a
out

View File

@@ -1,18 +1,16 @@
CC = gcc
NUBC = ../src/compiler/NubLang.CLI/bin/Debug/net9.0/nubc
NUBC = ../compiler/NubLang.CLI/bin/Debug/net9.0/nubc
.build/out: .build/out.a
$(CC) -ffreestanding -g -o .build/out .build/out.a
out: out.a
gcc -nostartfiles -o out x86_64.s out.a
rm out.a
.build/out.a: $(NUBC) src/main.nub
$(NUBC) src/main.nub
out.a: $(NUBC) main.nub
$(NUBC) main.nub
.PHONY: $(NUBC)
$(NUBC):
dotnet build ../src/compiler/NubLang.CLI/NubLang.CLI.csproj
run: .build/out
./.build/out
dotnet build ../compiler/NubLang.CLI/NubLang.CLI.csproj
clean:
@rm -r .build 2>/dev/null || true
@rm out 2>/dev/null || true
@rm out.a 2>/dev/null || true

View File

@@ -1,15 +0,0 @@
module main
extern func puts(text: cstring)
struct Test
{
}
func main(args: []cstring): i64
{
puts("test")
return 0
}

View File

@@ -1,34 +0,0 @@
# 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

View File

@@ -1,13 +0,0 @@
# 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

@@ -1 +0,0 @@
Compiler

View File

@@ -1,4 +0,0 @@
<?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

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

View File

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

View File

@@ -1,22 +0,0 @@

Microsoft Visual Studio Solution File, Format Version 12.00
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NubLang", "NubLang\NubLang.csproj", "{5047E21F-590D-4CB3-AFF3-064316485009}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NubLang.CLI", "NubLang.CLI\NubLang.CLI.csproj", "{A22F17ED-FA17-45AB-92BA-CD02C28B3524}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{5047E21F-590D-4CB3-AFF3-064316485009}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{5047E21F-590D-4CB3-AFF3-064316485009}.Debug|Any CPU.Build.0 = Debug|Any CPU
{5047E21F-590D-4CB3-AFF3-064316485009}.Release|Any CPU.ActiveCfg = Release|Any CPU
{5047E21F-590D-4CB3-AFF3-064316485009}.Release|Any CPU.Build.0 = Release|Any CPU
{A22F17ED-FA17-45AB-92BA-CD02C28B3524}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{A22F17ED-FA17-45AB-92BA-CD02C28B3524}.Debug|Any CPU.Build.0 = Debug|Any CPU
{A22F17ED-FA17-45AB-92BA-CD02C28B3524}.Release|Any CPU.ActiveCfg = Release|Any CPU
{A22F17ED-FA17-45AB-92BA-CD02C28B3524}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
EndGlobal

View File

@@ -1,29 +0,0 @@
using System.Diagnostics;
namespace NubLang.CLI;
public static class Archive
{
public static async Task<bool> Invoke(string fileName, params IEnumerable<string> objectFiles)
{
using var process = new Process();
process.StartInfo = new ProcessStartInfo("ar", ["rcs", fileName, ..objectFiles])
{
UseShellExecute = false,
RedirectStandardOutput = true,
RedirectStandardError = true,
CreateNoWindow = true
};
process.Start();
await process.WaitForExitAsync();
var errors = await process.StandardError.ReadToEndAsync();
if (!string.IsNullOrWhiteSpace(errors))
{
await Console.Error.WriteLineAsync("ar error when archiving:\n" + errors);
}
return process.ExitCode == 0;
}
}

View File

@@ -1,30 +0,0 @@
using System.Diagnostics;
namespace NubLang.CLI;
public static class GCC
{
public static async Task<bool> Assemble(string asmPath, string outPath)
{
using var process = new Process();
process.StartInfo = new ProcessStartInfo("gcc", ["-xassembler", "-g", "-c", "-o", outPath, asmPath])
{
UseShellExecute = false,
RedirectStandardOutput = true,
RedirectStandardError = true,
CreateNoWindow = true
};
process.Start();
await process.WaitForExitAsync();
var errors = await process.StandardError.ReadToEndAsync();
if (!string.IsNullOrWhiteSpace(errors))
{
await Console.Error.WriteLineAsync("gcc error when assembling:\n" + errors);
}
return process.ExitCode == 0;
}
}

View File

@@ -1,23 +0,0 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<AssemblyName>nubc</AssemblyName>
<OutputType>Exe</OutputType>
<TargetFramework>net9.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<PublishAot>true</PublishAot>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\NubLang\NubLang.csproj" />
</ItemGroup>
<ItemGroup>
<None Remove="assets\runtime.o" />
<EmbeddedResource Include="assets\runtime.o" />
<None Remove="assets\x64.o" />
<EmbeddedResource Include="assets\x64.o" />
</ItemGroup>
</Project>

View File

@@ -1,9 +0,0 @@
using NubLang.Code;
namespace NubLang.CLI;
public class Options
{
public string? OutputPath { get; set; }
public List<SourceFile> Files { get; } = [];
}

View File

@@ -1,170 +0,0 @@
using System.Reflection;
using NubLang.CLI;
using NubLang.Code;
using NubLang.Diagnostics;
using NubLang.Generation.QBE;
using NubLang.Parsing;
using NubLang.Parsing.Syntax;
using NubLang.Tokenization;
using NubLang.TypeChecking;
using Module = NubLang.TypeChecking.Module;
var options = new Options();
for (var i = 0; i < args.Length; i++)
{
var arg = args[i];
switch (arg)
{
case "-o":
{
++i;
if (i >= args.Length)
{
return 1;
}
options.OutputPath = args[i];
break;
}
default:
{
options.Files.Add(new SourceFile(arg));
break;
}
}
}
foreach (var file in options.Files)
{
if (!File.Exists(file.Path))
{
Console.Error.WriteLine($"File '{file}' does not exist");
return 1;
}
}
var diagnostics = new List<Diagnostic>();
var syntaxTrees = new List<SyntaxTree>();
foreach (var file in options.Files)
{
var tokenizer = new Tokenizer(file);
var tokens = tokenizer.Tokenize().ToList();
diagnostics.AddRange(tokenizer.GetDiagnostics());
var parser = new Parser();
var syntaxTree = parser.Parse(tokens);
diagnostics.AddRange(parser.GetDiagnostics());
syntaxTrees.Add(syntaxTree);
}
var moduleSignatures = ModuleSignature.CollectFromSyntaxTrees(syntaxTrees);
var modules = Module.CollectFromSyntaxTrees(syntaxTrees);
var typedModules = new List<TypedModule>();
foreach (var module in modules)
{
var typeChecker = new TypeChecker(module, moduleSignatures);
var typedModule = typeChecker.CheckModule();
diagnostics.AddRange(typeChecker.GetDiagnostics());
typedModules.Add(typedModule);
}
foreach (var diagnostic in diagnostics)
{
Console.Error.WriteLine(diagnostic.FormatANSI());
}
if (diagnostics.Any(diagnostic => diagnostic.Severity == DiagnosticSeverity.Error))
{
return 1;
}
var objectFiles = new List<string>();
for (var i = 0; i < typedModules.Count; i++)
{
var typedModule = typedModules[i];
var outFileName = Path.Combine(".build", "code", Path.ChangeExtension(options.Files[i].Path, null));
var outFileDir = Path.GetDirectoryName(outFileName);
if (!string.IsNullOrEmpty(outFileDir))
{
Directory.CreateDirectory(outFileDir);
}
var generator = new QBEGenerator(typedModule, moduleSignatures);
var ssa = generator.Emit();
var ssaFilePath = Path.ChangeExtension(outFileName, "ssa");
File.WriteAllText(ssaFilePath, ssa);
var asmFilePath = Path.ChangeExtension(outFileName, "s");
var qbeSuccess = await QBE.Invoke(ssaFilePath, asmFilePath);
if (!qbeSuccess)
{
return 1;
}
var objFilePath = Path.ChangeExtension(outFileName, "o");
var asmSuccess = await GCC.Assemble(asmFilePath, objFilePath);
if (!asmSuccess)
{
return 1;
}
objectFiles.Add(objFilePath);
}
var resources = Assembly.GetExecutingAssembly().GetManifestResourceNames();
string[] runtimeObjects = ["runtime.o", "x64.o"];
foreach (var runtimeObject in runtimeObjects)
{
var runtime = resources.First(r => r.EndsWith(runtimeObject));
await using var reader = Assembly
.GetExecutingAssembly()
.GetManifestResourceStream(runtime);
if (reader == null)
{
Console.Error.WriteLine($"Cannot open read stream to '{runtimeObject}'");
return 1;
}
var runtimePath = Path.Combine(".build", "runtime", runtimeObject);
var runtimeDir = Path.GetDirectoryName(runtimePath);
if (!string.IsNullOrEmpty(runtimeDir))
{
Directory.CreateDirectory(runtimeDir);
}
await using var writer = new FileStream(runtimePath, FileMode.Create);
reader.CopyTo(writer);
objectFiles.Add(runtimePath);
}
var outPath = options.OutputPath ?? Path.Combine(".build", "out.a");
var outDir = Path.GetDirectoryName(outPath);
if (!string.IsNullOrEmpty(outDir))
{
Directory.CreateDirectory(outDir);
}
var archiveResult = await Archive.Invoke(outPath, objectFiles);
if (!archiveResult)
{
return 1;
}
return 0;

View File

@@ -1,30 +0,0 @@
using System.Diagnostics;
namespace NubLang.CLI;
public static class QBE
{
public static async Task<bool> Invoke(string ssaPath, string outPath)
{
using var process = new Process();
process.StartInfo = new ProcessStartInfo("qbe", ["-o", outPath, ssaPath])
{
UseShellExecute = false,
RedirectStandardOutput = true,
RedirectStandardError = true,
CreateNoWindow = true
};
process.Start();
await process.WaitForExitAsync();
var errors = await process.StandardError.ReadToEndAsync();
if (!string.IsNullOrWhiteSpace(errors))
{
await Console.Error.WriteLineAsync("qbe error:\n" + errors);
}
return process.ExitCode == 0;
}
}

View File

@@ -1,41 +0,0 @@
namespace NubLang.Code;
public class SourceFile
{
private string? _content;
public SourceFile(string path)
{
Path = path ?? throw new ArgumentNullException(nameof(path));
}
public string Path { get; }
public string GetText() => _content ??= File.ReadAllText(Path);
public override string ToString() => Path;
public override bool Equals(object? obj)
{
return obj is SourceFile other && other.Path == Path;
}
public override int GetHashCode()
{
return HashCode.Combine(typeof(SourceFile), Path);
}
public static bool operator ==(SourceFile? left, SourceFile? right) => Equals(left, right);
public static bool operator !=(SourceFile? left, SourceFile? right) => !Equals(left, right);
}
public class SourceFileSpan
{
public SourceFileSpan(SourceFile sourceFile, SourceSpan span)
{
SourceFile = sourceFile;
Span = span;
}
public SourceFile SourceFile { get; }
public SourceSpan Span { get; }
}

View File

@@ -1,42 +0,0 @@
namespace NubLang.Code;
public readonly struct SourceLocation : IEquatable<SourceLocation>
{
public static SourceLocation Zero => new(0, 0);
public SourceLocation(int line, int column)
{
Line = line;
Column = column;
}
public int Line { get; }
public int Column { get; }
public override string ToString()
{
return $"{Line}:{Column}";
}
public override bool Equals(object? obj)
{
return obj is SourceLocation other && Equals(other);
}
public bool Equals(SourceLocation other)
{
return Line == other.Line && Column == other.Column;
}
public override int GetHashCode()
{
return HashCode.Combine(typeof(SourceLocation), Line, Column);
}
public static bool operator ==(SourceLocation left, SourceLocation right) => Equals(left, right);
public static bool operator !=(SourceLocation left, SourceLocation right) => !Equals(left, right);
public static bool operator <(SourceLocation left, SourceLocation right) => left.Line < right.Line || (left.Line == right.Line && left.Column < right.Column);
public static bool operator >(SourceLocation left, SourceLocation right) => left.Line > right.Line || (left.Line == right.Line && left.Column > right.Column);
public static bool operator <=(SourceLocation left, SourceLocation right) => left.Line <= right.Line || (left.Line == right.Line && left.Column <= right.Column);
public static bool operator >=(SourceLocation left, SourceLocation right) => left.Line >= right.Line || (left.Line == right.Line && left.Column >= right.Column);
}

View File

@@ -1,57 +0,0 @@
namespace NubLang.Code;
public readonly struct SourceSpan : IEquatable<SourceSpan>
{
public static SourceSpan Zero => new(SourceLocation.Zero, SourceLocation.Zero);
public static SourceSpan Merge(params IEnumerable<SourceSpan> spans)
{
var spanArray = spans as SourceSpan[] ?? spans.ToArray();
if (spanArray.Length == 0)
{
return Zero;
}
var minStart = spanArray.Min(s => s.Start);
var maxEnd = spanArray.Max(s => s.End);
return new SourceSpan(minStart, maxEnd);
}
public SourceSpan(SourceLocation start, SourceLocation end)
{
if (start > end)
{
throw new ArgumentException("Start location cannot be after end location");
}
Start = start;
End = end;
}
public SourceLocation Start { get; }
public SourceLocation End { get; }
public override string ToString()
{
if (Start == End)
{
return $"{Start}";
}
if (Start.Line == End.Line)
{
return Start.Column == End.Column ? $"{Start}" : $"{Start.Line}:{Start.Column}-{End.Column}";
}
return $"{Start}-{End}";
}
public bool Equals(SourceSpan other) => Start == other.Start && End == other.End;
public override bool Equals(object? obj) => obj is SourceSpan other && Equals(other);
public override int GetHashCode() => HashCode.Combine(typeof(SourceSpan), Start, End);
public static bool operator ==(SourceSpan left, SourceSpan right) => Equals(left, right);
public static bool operator !=(SourceSpan left, SourceSpan right) => !Equals(left, right);
}

View File

@@ -1,56 +0,0 @@
namespace NubLang.Diagnostics;
public static class ConsoleColors
{
public const string Reset = "\e[0m";
public const string Bold = "\e[1m";
public const string Faint = "\e[2m";
public const string Italic = "\e[3m";
public const string Underline = "\e[4m";
public const string SlowBlink = "\e[5m";
public const string RapidBlink = "\e[6m";
public const string SwapBgAndFg = "\e[7m";
public const string Conceal = "\e[8m";
public const string CrossedOut = "\e[9m";
public const string DefaultFont = "\e[10m";
public const string AltFont1 = "\e[11m";
public const string AltFont2 = "\e[12m";
public const string AltFont3 = "\e[13m";
public const string AltFont4 = "\e[14m";
public const string AltFont5 = "\e[15m";
public const string AltFont6 = "\e[16m";
public const string AltFont7 = "\e[17m";
public const string AltFont8 = "\e[18m";
public const string AltFont9 = "\e[19m";
public const string Black = "\e[30m";
public const string Red = "\e[31m";
public const string Green = "\e[32m";
public const string Yellow = "\e[33m";
public const string Blue = "\e[34m";
public const string Magenta = "\e[35m";
public const string Cyan = "\e[36m";
public const string White = "\e[37m";
public const string BrightBlack = "\e[90m";
public const string BrightRed = "\e[91m";
public const string BrightGreen = "\e[92m";
public const string BrightYellow = "\e[93m";
public const string BrightBlue = "\e[94m";
public const string BrightMagenta = "\e[95m";
public const string BrightCyan = "\e[96m";
public const string BrightWhite = "\e[97m";
private static bool IsColorSupported()
{
var term = Environment.GetEnvironmentVariable("TERM");
var colorTerm = Environment.GetEnvironmentVariable("COLORTERM");
return !string.IsNullOrEmpty(term) || !string.IsNullOrEmpty(colorTerm) || !Console.IsOutputRedirected;
}
public static string Colorize(string text, string color)
{
return IsColorSupported() ? $"{color}{text}{Reset}" : text;
}
}

View File

@@ -1,325 +0,0 @@
using System.Text;
using NubLang.Code;
using NubLang.Parsing.Syntax;
using NubLang.Tokenization;
namespace NubLang.Diagnostics;
public class Diagnostic
{
public class DiagnosticBuilder
{
private readonly DiagnosticSeverity _severity;
private readonly string _message;
private SourceFileSpan? _fileSpan;
private string? _help;
public DiagnosticBuilder(DiagnosticSeverity severity, string message)
{
_severity = severity;
_message = message;
}
public DiagnosticBuilder At(SyntaxNode? node)
{
if (node != null)
{
var first = node.Tokens.FirstOrDefault();
if (first?.FileSpan != null)
{
var span = SourceSpan.Merge(node.Tokens.Select(x => x.FileSpan?.Span ?? SourceSpan.Zero));
At(new SourceFileSpan(first.FileSpan.SourceFile, span));
}
}
return this;
}
public DiagnosticBuilder At(Token? token)
{
if (token != null)
{
At(token.FileSpan);
}
return this;
}
public DiagnosticBuilder At(SourceFileSpan? fileSpan)
{
if (fileSpan != null)
{
_fileSpan = fileSpan;
}
return this;
}
public DiagnosticBuilder WithHelp(string help)
{
_help = help;
return this;
}
public Diagnostic Build() => new(_severity, _message, _help, _fileSpan);
}
public static DiagnosticBuilder Error(string message) => new(DiagnosticSeverity.Error, message);
public static DiagnosticBuilder Warning(string message) => new(DiagnosticSeverity.Warning, message);
public static DiagnosticBuilder Info(string message) => new(DiagnosticSeverity.Info, message);
public DiagnosticSeverity Severity { get; }
public string Message { get; }
public string? Help { get; }
public SourceFileSpan? FileSpan { get; }
private Diagnostic(DiagnosticSeverity severity, string message, string? help, SourceFileSpan? fileSpan)
{
Severity = severity;
Message = message;
Help = help;
FileSpan = fileSpan;
}
public string FormatANSI()
{
var sb = new StringBuilder();
sb.Append(Severity switch
{
DiagnosticSeverity.Error => ConsoleColors.Colorize("error", ConsoleColors.Bold + ConsoleColors.Red),
DiagnosticSeverity.Warning => ConsoleColors.Colorize("warning", ConsoleColors.Bold + ConsoleColors.Yellow),
DiagnosticSeverity.Info => ConsoleColors.Colorize("info", ConsoleColors.Bold + ConsoleColors.Blue),
_ => ConsoleColors.Colorize("unknown", ConsoleColors.Bold + ConsoleColors.White)
});
if (FileSpan != null)
{
sb.Append(ConsoleColors.Colorize($" at {FileSpan.SourceFile.Path}:{FileSpan.Span}", ConsoleColors.Faint));
}
sb.Append(": ");
sb.Append(ConsoleColors.Colorize(Message, ConsoleColors.BrightWhite));
if (FileSpan != null)
{
sb.AppendLine();
var text = FileSpan.SourceFile.GetText();
var lines = text.Split('\n');
var startLine = FileSpan.Span.Start.Line;
var endLine = FileSpan.Span.End.Line;
const int CONTEXT_LINES = 3;
var contextStartLine = Math.Max(1, startLine - CONTEXT_LINES);
var contextEndLine = Math.Min(lines.Length, endLine + CONTEXT_LINES);
var numberPadding = contextEndLine.ToString().Length;
var codePadding = lines.Skip(contextStartLine - 1).Take(contextEndLine - contextStartLine).Max(x => x.Length);
sb.Append('╭');
sb.Append(new string('─', numberPadding + 2));
sb.Append('┬');
sb.Append(new string('─', codePadding + 2));
sb.Append('╮');
sb.AppendLine();
var tokenizer = new Tokenizer(FileSpan.SourceFile);
var tokens = tokenizer.Tokenize().ToList();
for (var i = contextStartLine; i <= contextEndLine; i++)
{
var line = lines[i - 1];
sb.Append("│ ");
sb.Append(i.ToString().PadRight(numberPadding));
sb.Append(" │ ");
sb.Append(ApplySyntaxHighlighting(line.PadRight(codePadding), i, tokens));
sb.Append(" │");
sb.AppendLine();
if (i >= startLine && i <= endLine)
{
var markerStartColumn = 1;
var markerEndColumn = line.Length + 1;
if (i == startLine)
{
markerStartColumn = FileSpan.Span.Start.Column;
}
if (i == endLine)
{
markerEndColumn = FileSpan.Span.End.Column;
}
var markerLength = markerEndColumn - markerStartColumn;
var marker = new string('^', markerLength);
var markerColor = Severity switch
{
DiagnosticSeverity.Info => ConsoleColors.Blue,
DiagnosticSeverity.Warning => ConsoleColors.Yellow,
DiagnosticSeverity.Error => ConsoleColors.Red,
_ => ConsoleColors.White
};
sb.Append("│ ");
sb.Append(new string(' ', numberPadding));
sb.Append(" │ ");
sb.Append(new string(' ', markerStartColumn - 1));
sb.Append(ConsoleColors.Colorize(marker, markerColor));
sb.Append(new string(' ', codePadding - markerEndColumn + 1));
sb.Append(" │");
sb.AppendLine();
}
}
sb.Append('╰');
sb.Append(new string('─', numberPadding + 2));
sb.Append('┴');
sb.Append(new string('─', codePadding + 2));
sb.Append('╯');
}
if (Help != null)
{
sb.AppendLine();
sb.Append(ConsoleColors.Colorize($"help: {Help}", ConsoleColors.Cyan));
}
return sb.ToString();
}
private static string ApplySyntaxHighlighting(string line, int lineNumber, List<Token> tokens)
{
var sb = new StringBuilder();
var lineTokens = tokens
.Where(t => t.FileSpan.Span.Start.Line == lineNumber)
.OrderBy(t => t.FileSpan.Span.Start.Column)
.ToList();
if (lineTokens.Count == 0)
{
return line;
}
var currentColumn = 1;
foreach (var token in lineTokens)
{
var tokenStart = token.FileSpan.Span.Start.Column;
var tokenEnd = token.FileSpan.Span.End.Column;
if (tokenStart > currentColumn)
{
var beforeToken = line.Substring(currentColumn - 1, tokenStart - currentColumn);
sb.Append(beforeToken);
}
var tokenLength = tokenEnd - tokenStart;
if (tokenStart - 1 + tokenLength <= line.Length)
{
var tokenText = line.Substring(tokenStart - 1, tokenLength);
var coloredToken = ColorizeToken(token, tokenText);
sb.Append(coloredToken);
}
currentColumn = tokenEnd;
}
if (currentColumn <= line.Length)
{
var remaining = line[(currentColumn - 1)..];
sb.Append(remaining);
}
return sb.ToString();
}
private static string ColorizeToken(Token token, string tokenText)
{
switch (token)
{
case IdentifierToken:
{
return ConsoleColors.Colorize(tokenText, ConsoleColors.BrightWhite);
}
case LiteralToken literal:
{
if (literal.Kind == LiteralKind.String)
{
return ConsoleColors.Colorize(tokenText, ConsoleColors.Green);
}
return ConsoleColors.Colorize(tokenText, ConsoleColors.Magenta);
}
case SymbolToken symbolToken:
{
switch (symbolToken.Symbol)
{
case Symbol.Func:
case Symbol.Return:
case Symbol.If:
case Symbol.Else:
case Symbol.While:
case Symbol.Break:
case Symbol.Continue:
case Symbol.Struct:
case Symbol.Let:
case Symbol.Calls:
case Symbol.Interface:
case Symbol.For:
case Symbol.Extern:
{
return ConsoleColors.Colorize(tokenText, ConsoleColors.Bold + ConsoleColors.Blue);
}
case Symbol.Assign:
case Symbol.Bang:
case Symbol.Equal:
case Symbol.NotEqual:
case Symbol.LessThan:
case Symbol.LessThanOrEqual:
case Symbol.GreaterThan:
case Symbol.GreaterThanOrEqual:
case Symbol.Plus:
case Symbol.Minus:
case Symbol.Star:
case Symbol.ForwardSlash:
case Symbol.Caret:
case Symbol.Ampersand:
{
return ConsoleColors.Colorize(tokenText, ConsoleColors.Yellow);
}
case Symbol.Colon:
case Symbol.OpenParen:
case Symbol.CloseParen:
case Symbol.OpenBrace:
case Symbol.CloseBrace:
case Symbol.OpenBracket:
case Symbol.CloseBracket:
case Symbol.Comma:
case Symbol.Period:
case Symbol.Semi:
{
return ConsoleColors.Colorize(tokenText, ConsoleColors.BrightBlack);
}
}
break;
}
}
return tokenText;
}
}
public enum DiagnosticSeverity
{
Info,
Warning,
Error
}

File diff suppressed because it is too large Load Diff

View File

@@ -1,39 +0,0 @@
using System.Text;
namespace NubLang.Generation.QBE;
internal class QBEWriter
{
private readonly StringBuilder _builder = new();
public void Indented(string value)
{
_builder.Append('\t');
_builder.AppendLine(value);
}
public void Comment(string comment)
{
_builder.AppendLine("# " + comment);
}
public void WriteLine(string text)
{
_builder.AppendLine(text);
}
public void Write(string text)
{
_builder.Append(text);
}
public void NewLine()
{
_builder.AppendLine();
}
public override string ToString()
{
return _builder.ToString();
}
}

View File

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

View File

@@ -1,82 +0,0 @@
using System.Diagnostics.CodeAnalysis;
namespace NubLang;
public static class Optional
{
public static Optional<TValue> Empty<TValue>() => new();
/// <summary>
/// Alias for creating an Optional which allows for implicit types
/// </summary>
public static Optional<TValue> OfNullable<TValue>(TValue? value)
{
return value ?? Optional<TValue>.Empty();
}
/// <summary>
/// Converts a nullable type to an Optional
/// </summary>
public static Optional<TValue> ToOptional<TValue>(this TValue? value)
{
return OfNullable(value);
}
}
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

@@ -1,856 +0,0 @@
using System.Diagnostics.CodeAnalysis;
using NubLang.Diagnostics;
using NubLang.Parsing.Syntax;
using NubLang.Tokenization;
namespace NubLang.Parsing;
public sealed class Parser
{
private readonly List<Diagnostic> _diagnostics = [];
private IReadOnlyList<Token> _tokens = [];
private int _tokenIndex;
private string _moduleName = string.Empty;
private Token? CurrentToken => _tokenIndex < _tokens.Count ? _tokens[_tokenIndex] : null;
private bool HasToken => CurrentToken != null;
public IReadOnlyList<Diagnostic> GetDiagnostics()
{
return _diagnostics;
}
public SyntaxTree Parse(IReadOnlyList<Token> tokens)
{
_diagnostics.Clear();
_tokens = tokens;
_tokenIndex = 0;
_moduleName = string.Empty;
var metadata = ParseMetadata();
var definitions = ParseDefinitions();
return new SyntaxTree(definitions, metadata);
}
private SyntaxTreeMetadata ParseMetadata()
{
var imports = new List<string>();
try
{
ExpectSymbol(Symbol.Module);
_moduleName = ExpectIdentifier().Value;
while (TryExpectSymbol(Symbol.Import))
{
imports.Add(ExpectIdentifier().Value);
}
}
catch (ParseException e)
{
_diagnostics.Add(e.Diagnostic);
while (HasToken)
{
if (CurrentToken is SymbolToken { Symbol: Symbol.Module or Symbol.Import })
{
break;
}
Next();
}
}
return new SyntaxTreeMetadata(_moduleName, imports);
}
private List<DefinitionSyntax> ParseDefinitions()
{
var definitions = new List<DefinitionSyntax>();
while (HasToken)
{
try
{
var startIndex = _tokenIndex;
var keyword = ExpectSymbol();
var definition = keyword.Symbol switch
{
Symbol.Extern => ParseExtern(startIndex),
Symbol.Func => ParseFunc(startIndex),
Symbol.Struct => ParseStruct(startIndex),
Symbol.Interface => ParseInterface(startIndex),
_ => throw new ParseException(Diagnostic
.Error($"Expected 'extern', 'func', 'struct' or 'interface' but found '{keyword.Symbol}'")
.WithHelp("Valid definition keywords are 'extern', 'func', 'struct' and 'interface'")
.At(keyword)
.Build())
};
definitions.Add(definition);
}
catch (ParseException e)
{
_diagnostics.Add(e.Diagnostic);
while (HasToken)
{
if (CurrentToken is SymbolToken { Symbol: Symbol.Extern or Symbol.Func or Symbol.Struct or Symbol.Interface })
{
break;
}
Next();
}
}
}
return definitions;
}
private FuncSignatureSyntax ParseFuncSignature()
{
var startIndex = _tokenIndex;
List<FuncParameterSyntax> parameters = [];
ExpectSymbol(Symbol.OpenParen);
while (!TryExpectSymbol(Symbol.CloseParen))
{
parameters.Add(ParseFuncParameter());
if (!TryExpectSymbol(Symbol.Comma))
{
ExpectSymbol(Symbol.CloseParen);
break;
}
}
var returnType = TryExpectSymbol(Symbol.Colon) ? ParseType() : new VoidTypeSyntax([]);
return new FuncSignatureSyntax(GetTokens(startIndex), parameters, returnType);
}
private FuncParameterSyntax ParseFuncParameter()
{
var startIndex = _tokenIndex;
var name = ExpectIdentifier();
ExpectSymbol(Symbol.Colon);
var type = ParseType();
return new FuncParameterSyntax(GetTokens(startIndex), name.Value, type);
}
private DefinitionSyntax ParseExtern(int startIndex)
{
var keyword = ExpectSymbol();
return keyword.Symbol switch
{
Symbol.Func => ParseExternFunc(startIndex),
_ => throw new ParseException(Diagnostic
.Error($"Unexpected symbol {keyword.Symbol} after extern declaration")
.At(keyword)
.Build())
};
}
private ExternFuncSyntax ParseExternFunc(int startIndex)
{
var name = ExpectIdentifier();
var callName = name.Value;
if (TryExpectSymbol(Symbol.Calls))
{
callName = ExpectIdentifier().Value;
}
var signature = ParseFuncSignature();
return new ExternFuncSyntax(GetTokens(startIndex), name.Value, callName, signature);
}
private FuncSyntax ParseFunc(int startIndex)
{
var name = ExpectIdentifier();
var signature = ParseFuncSignature();
var body = ParseBlock();
return new FuncSyntax(GetTokens(startIndex), name.Value, signature, body);
}
private DefinitionSyntax ParseStruct(int startIndex)
{
var name = ExpectIdentifier();
var interfaceImplementations = new List<TypeSyntax>();
if (TryExpectSymbol(Symbol.Colon))
{
do
{
var interfaceType = ParseType();
interfaceImplementations.Add(interfaceType);
} while (TryExpectSymbol(Symbol.Comma));
}
ExpectSymbol(Symbol.OpenBrace);
List<StructFieldSyntax> fields = [];
List<StructFuncSyntax> funcs = [];
var fieldIndex = 0;
while (!TryExpectSymbol(Symbol.CloseBrace))
{
var memberStartIndex = _tokenIndex;
if (TryExpectSymbol(Symbol.Func))
{
var funcName = ExpectIdentifier().Value;
var funcSignature = ParseFuncSignature();
var funcBody = ParseBlock();
funcs.Add(new StructFuncSyntax(GetTokens(memberStartIndex), funcName, funcSignature, funcBody));
}
else
{
var fieldName = ExpectIdentifier().Value;
ExpectSymbol(Symbol.Colon);
var fieldType = ParseType();
var fieldValue = Optional<ExpressionSyntax>.Empty();
if (TryExpectSymbol(Symbol.Assign))
{
fieldValue = ParseExpression();
}
fields.Add(new StructFieldSyntax(GetTokens(memberStartIndex), fieldIndex++, fieldName, fieldType, fieldValue));
}
}
return new StructSyntax(GetTokens(startIndex), name.Value, fields, funcs, interfaceImplementations);
}
private InterfaceSyntax ParseInterface(int startIndex)
{
var name = ExpectIdentifier();
ExpectSymbol(Symbol.OpenBrace);
List<InterfaceFuncSyntax> functions = [];
while (!TryExpectSymbol(Symbol.CloseBrace))
{
var funcStartIndex = _tokenIndex;
ExpectSymbol(Symbol.Func);
var funcName = ExpectIdentifier().Value;
var signature = ParseFuncSignature();
functions.Add(new InterfaceFuncSyntax(GetTokens(funcStartIndex), funcName, signature));
}
return new InterfaceSyntax(GetTokens(startIndex), name.Value, functions);
}
private StatementSyntax ParseStatement()
{
if (CurrentToken is SymbolToken symbol)
{
switch (symbol.Symbol)
{
case Symbol.Return:
return ParseReturn();
case Symbol.If:
return ParseIf();
case Symbol.While:
return ParseWhile();
case Symbol.Let:
return ParseVariableDeclaration();
case Symbol.Break:
return ParseBreak();
case Symbol.Continue:
return ParseContinue();
}
}
return ParseStatementExpression();
}
private StatementSyntax ParseStatementExpression()
{
var startIndex = _tokenIndex;
var expr = ParseExpression();
if (TryExpectSymbol(Symbol.Assign))
{
var value = ParseExpression();
return new AssignmentSyntax(GetTokens(startIndex), expr, value);
}
return new StatementExpressionSyntax(GetTokens(startIndex), expr);
}
private VariableDeclarationSyntax ParseVariableDeclaration()
{
var startIndex = _tokenIndex;
ExpectSymbol(Symbol.Let);
var name = ExpectIdentifier().Value;
var explicitType = Optional<TypeSyntax>.Empty();
if (TryExpectSymbol(Symbol.Colon))
{
explicitType = ParseType();
}
var assignment = Optional<ExpressionSyntax>.Empty();
if (TryExpectSymbol(Symbol.Assign))
{
assignment = ParseExpression();
}
return new VariableDeclarationSyntax(GetTokens(startIndex), name, explicitType, assignment);
}
private StatementSyntax ParseBreak()
{
var startIndex = _tokenIndex;
ExpectSymbol(Symbol.Break);
return new BreakSyntax(GetTokens(startIndex));
}
private StatementSyntax ParseContinue()
{
var startIndex = _tokenIndex;
ExpectSymbol(Symbol.Continue);
return new ContinueSyntax(GetTokens(startIndex));
}
private ReturnSyntax ParseReturn()
{
var startIndex = _tokenIndex;
ExpectSymbol(Symbol.Return);
var value = Optional<ExpressionSyntax>.Empty();
if (!TryExpectSymbol(Symbol.Semi))
{
value = ParseExpression();
}
return new ReturnSyntax(GetTokens(startIndex), value);
}
private IfSyntax ParseIf()
{
var startIndex = _tokenIndex;
ExpectSymbol(Symbol.If);
var condition = ParseExpression();
var body = ParseBlock();
var elseStatement = Optional<Variant<IfSyntax, BlockSyntax>>.Empty();
if (TryExpectSymbol(Symbol.Else))
{
elseStatement = TryExpectSymbol(Symbol.If)
? (Variant<IfSyntax, BlockSyntax>)ParseIf()
: (Variant<IfSyntax, BlockSyntax>)ParseBlock();
}
return new IfSyntax(GetTokens(startIndex), condition, body, elseStatement);
}
private WhileSyntax ParseWhile()
{
var startIndex = _tokenIndex;
ExpectSymbol(Symbol.While);
var condition = ParseExpression();
var body = ParseBlock();
return new WhileSyntax(GetTokens(startIndex), condition, body);
}
private ExpressionSyntax ParseExpression(int precedence = 0)
{
var startIndex = _tokenIndex;
var left = ParsePrimaryExpression();
while (CurrentToken is SymbolToken symbolToken && TryGetBinaryOperator(symbolToken.Symbol, out var op) && GetBinaryOperatorPrecedence(op.Value) >= precedence)
{
Next();
var right = ParseExpression(GetBinaryOperatorPrecedence(op.Value) + 1);
left = new BinaryExpressionSyntax(GetTokens(startIndex), left, op.Value, right);
}
return left;
}
private static int GetBinaryOperatorPrecedence(BinaryOperatorSyntax operatorSyntax)
{
return operatorSyntax switch
{
BinaryOperatorSyntax.Multiply => 10,
BinaryOperatorSyntax.Divide => 10,
BinaryOperatorSyntax.Modulo => 10,
BinaryOperatorSyntax.Plus => 9,
BinaryOperatorSyntax.Minus => 9,
BinaryOperatorSyntax.LeftShift => 8,
BinaryOperatorSyntax.RightShift => 8,
BinaryOperatorSyntax.GreaterThan => 7,
BinaryOperatorSyntax.GreaterThanOrEqual => 7,
BinaryOperatorSyntax.LessThan => 7,
BinaryOperatorSyntax.LessThanOrEqual => 7,
BinaryOperatorSyntax.Equal => 7,
BinaryOperatorSyntax.NotEqual => 7,
BinaryOperatorSyntax.BitwiseAnd => 6,
BinaryOperatorSyntax.BitwiseXor => 5,
BinaryOperatorSyntax.BitwiseOr => 4,
BinaryOperatorSyntax.LogicalAnd => 3,
BinaryOperatorSyntax.LogicalOr => 2,
_ => throw new ArgumentOutOfRangeException(nameof(operatorSyntax), operatorSyntax, null)
};
}
private bool TryGetBinaryOperator(Symbol symbol, [NotNullWhen(true)] out BinaryOperatorSyntax? binaryExpressionOperator)
{
switch (symbol)
{
case Symbol.Equal:
binaryExpressionOperator = BinaryOperatorSyntax.Equal;
return true;
case Symbol.NotEqual:
binaryExpressionOperator = BinaryOperatorSyntax.NotEqual;
return true;
case Symbol.LessThan:
binaryExpressionOperator = BinaryOperatorSyntax.LessThan;
return true;
case Symbol.LessThanOrEqual:
binaryExpressionOperator = BinaryOperatorSyntax.LessThanOrEqual;
return true;
case Symbol.GreaterThan:
binaryExpressionOperator = BinaryOperatorSyntax.GreaterThan;
return true;
case Symbol.GreaterThanOrEqual:
binaryExpressionOperator = BinaryOperatorSyntax.GreaterThanOrEqual;
return true;
case Symbol.And:
binaryExpressionOperator = BinaryOperatorSyntax.LogicalAnd;
return true;
case Symbol.Or:
binaryExpressionOperator = BinaryOperatorSyntax.LogicalOr;
return true;
case Symbol.Plus:
binaryExpressionOperator = BinaryOperatorSyntax.Plus;
return true;
case Symbol.Minus:
binaryExpressionOperator = BinaryOperatorSyntax.Minus;
return true;
case Symbol.Star:
binaryExpressionOperator = BinaryOperatorSyntax.Multiply;
return true;
case Symbol.ForwardSlash:
binaryExpressionOperator = BinaryOperatorSyntax.Divide;
return true;
case Symbol.Percent:
binaryExpressionOperator = BinaryOperatorSyntax.Modulo;
return true;
case Symbol.LeftShift:
binaryExpressionOperator = BinaryOperatorSyntax.LeftShift;
return true;
case Symbol.RightShift:
binaryExpressionOperator = BinaryOperatorSyntax.RightShift;
return true;
case Symbol.Ampersand:
binaryExpressionOperator = BinaryOperatorSyntax.BitwiseAnd;
return true;
case Symbol.Pipe:
binaryExpressionOperator = BinaryOperatorSyntax.BitwiseOr;
return true;
case Symbol.Caret:
binaryExpressionOperator = BinaryOperatorSyntax.BitwiseXor;
return true;
default:
binaryExpressionOperator = null;
return false;
}
}
private ExpressionSyntax ParsePrimaryExpression()
{
var startIndex = _tokenIndex;
var token = ExpectToken();
var expr = token switch
{
LiteralToken literal => new LiteralSyntax(GetTokens(startIndex), literal.Value, literal.Kind),
IdentifierToken identifier => new IdentifierSyntax(GetTokens(startIndex), Optional<string>.Empty(), identifier.Value),
SymbolToken symbolToken => symbolToken.Symbol switch
{
Symbol.OpenParen => ParseParenthesizedExpression(),
Symbol.Minus => new UnaryExpressionSyntax(GetTokens(startIndex), UnaryOperatorSyntax.Negate, ParsePrimaryExpression()),
Symbol.Bang => new UnaryExpressionSyntax(GetTokens(startIndex), UnaryOperatorSyntax.Invert, ParsePrimaryExpression()),
Symbol.OpenBracket => ParseArrayInitializer(startIndex),
Symbol.OpenBrace => new StructInitializerSyntax(GetTokens(startIndex), Optional<TypeSyntax>.Empty(), ParseStructInitializerBody()),
Symbol.Struct => ParseStructInitializer(startIndex),
_ => throw new ParseException(Diagnostic
.Error($"Unexpected symbol '{symbolToken.Symbol}' in expression")
.WithHelp("Expected '(', '-', '!', '[' or '{'")
.At(symbolToken)
.Build())
},
_ => throw new ParseException(Diagnostic
.Error($"Unexpected token '{token.GetType().Name}' in expression")
.WithHelp("Expected literal, identifier, or parenthesized expression")
.At(token)
.Build())
};
return ParsePostfixOperators(expr);
}
private ExpressionSyntax ParseParenthesizedExpression()
{
var expression = ParseExpression();
ExpectSymbol(Symbol.CloseParen);
return expression;
}
private ExpressionSyntax ParsePostfixOperators(ExpressionSyntax expr)
{
var startIndex = _tokenIndex;
while (HasToken)
{
if (TryExpectSymbol(Symbol.Ampersand))
{
expr = new AddressOfSyntax(GetTokens(startIndex), expr);
continue;
}
if (TryExpectSymbol(Symbol.Caret))
{
expr = new DereferenceSyntax(GetTokens(startIndex), expr);
continue;
}
if (TryExpectSymbol(Symbol.Period))
{
var member = ExpectIdentifier().Value;
if (TryExpectSymbol(Symbol.OpenParen))
{
var parameters = new List<ExpressionSyntax>();
while (!TryExpectSymbol(Symbol.CloseParen))
{
parameters.Add(ParseExpression());
if (!TryExpectSymbol(Symbol.Comma))
{
ExpectSymbol(Symbol.CloseParen);
break;
}
}
expr = new DotFuncCallSyntax(GetTokens(startIndex), member, expr, parameters);
continue;
}
expr = new StructFieldAccessSyntax(GetTokens(startIndex), expr, member);
continue;
}
if (TryExpectSymbol(Symbol.OpenBracket))
{
var index = ParseExpression();
ExpectSymbol(Symbol.CloseBracket);
expr = new ArrayIndexAccessSyntax(GetTokens(startIndex), expr, index);
continue;
}
if (TryExpectSymbol(Symbol.OpenParen))
{
var parameters = new List<ExpressionSyntax>();
while (!TryExpectSymbol(Symbol.CloseParen))
{
parameters.Add(ParseExpression());
if (!TryExpectSymbol(Symbol.Comma))
{
ExpectSymbol(Symbol.CloseParen);
break;
}
}
expr = new FuncCallSyntax(GetTokens(startIndex), expr, parameters);
continue;
}
break;
}
return expr;
}
private ArrayInitializerSyntax ParseArrayInitializer(int startIndex)
{
var capacity = ParseExpression();
ExpectSymbol(Symbol.CloseBracket);
var type = ParseType();
return new ArrayInitializerSyntax(GetTokens(startIndex), capacity, type);
}
private StructInitializerSyntax ParseStructInitializer(int startIndex)
{
var type = Optional.Empty<TypeSyntax>();
if (!TryExpectSymbol(Symbol.OpenBrace))
{
type = ParseType();
ExpectSymbol(Symbol.OpenBrace);
}
var initializers = ParseStructInitializerBody();
return new StructInitializerSyntax(GetTokens(startIndex), type, initializers);
}
private Dictionary<string, ExpressionSyntax> ParseStructInitializerBody()
{
Dictionary<string, ExpressionSyntax> initializers = [];
while (!TryExpectSymbol(Symbol.CloseBrace))
{
var name = ExpectIdentifier().Value;
ExpectSymbol(Symbol.Assign);
var value = ParseExpression();
initializers.Add(name, value);
}
return initializers;
}
private BlockSyntax ParseBlock()
{
var startIndex = _tokenIndex;
ExpectSymbol(Symbol.OpenBrace);
List<StatementSyntax> statements = [];
while (!TryExpectSymbol(Symbol.CloseBrace))
{
try
{
statements.Add(ParseStatement());
}
catch (ParseException ex)
{
_diagnostics.Add(ex.Diagnostic);
Next();
}
}
return new BlockSyntax(GetTokens(startIndex), statements);
}
private TypeSyntax ParseType()
{
var startIndex = _tokenIndex;
if (TryExpectIdentifier(out var name))
{
if (name.Value[0] == 'u' && int.TryParse(name.Value[1..], out var size))
{
if (size is not 8 and not 16 and not 32 and not 64)
{
throw new ParseException(Diagnostic
.Error("Arbitrary uint size is not supported")
.WithHelp("Use u8, u16, u32 or u64")
.At(name)
.Build());
}
return new IntTypeSyntax(GetTokens(startIndex), false, size);
}
if (name.Value[0] == 'i' && int.TryParse(name.Value[1..], out size))
{
if (size is not 8 and not 16 and not 32 and not 64)
{
throw new ParseException(Diagnostic
.Error("Arbitrary int size is not supported")
.WithHelp("Use i8, i16, i32 or i64")
.At(name)
.Build());
}
return new IntTypeSyntax(GetTokens(startIndex), true, size);
}
if (name.Value[0] == 'f' && int.TryParse(name.Value[1..], out size))
{
if (size is not 32 and not 64)
{
throw new ParseException(Diagnostic
.Error("Arbitrary float size is not supported")
.WithHelp("Use f32 or f64")
.At(name)
.Build());
}
return new FloatTypeSyntax(GetTokens(startIndex), size);
}
return name.Value switch
{
"void" => new VoidTypeSyntax(GetTokens(startIndex)),
"string" => new StringTypeSyntax(GetTokens(startIndex)),
"cstring" => new CStringTypeSyntax(GetTokens(startIndex)),
"bool" => new BoolTypeSyntax(GetTokens(startIndex)),
_ => new CustomTypeSyntax(GetTokens(startIndex), _moduleName, name.Value)
};
}
if (TryExpectSymbol(Symbol.Caret))
{
var baseType = ParseType();
return new PointerTypeSyntax(GetTokens(startIndex), baseType);
}
if (TryExpectSymbol(Symbol.Func))
{
ExpectSymbol(Symbol.OpenParen);
List<TypeSyntax> parameters = [];
while (!TryExpectSymbol(Symbol.CloseParen))
{
parameters.Add(ParseType());
if (!TryExpectSymbol(Symbol.Comma))
{
ExpectSymbol(Symbol.CloseParen);
break;
}
}
var returnType = TryExpectSymbol(Symbol.Colon)
? ParseType()
: new VoidTypeSyntax([]);
return new FuncTypeSyntax(GetTokens(startIndex), parameters, returnType);
}
if (TryExpectSymbol(Symbol.OpenBracket))
{
ExpectSymbol(Symbol.CloseBracket);
var baseType = ParseType();
return new ArrayTypeSyntax(GetTokens(startIndex), baseType);
}
throw new ParseException(Diagnostic
.Error("Invalid type syntax")
.WithHelp("Expected type name, '^' for pointer, or '[]' for array")
.At(CurrentToken)
.Build());
}
private Token ExpectToken()
{
if (!HasToken)
{
throw new ParseException(Diagnostic
.Error("Unexpected end of file")
.WithHelp("Expected more tokens to complete the syntax")
.At(_tokens[^1])
.Build());
}
var token = CurrentToken!;
Next();
return token;
}
private SymbolToken ExpectSymbol()
{
var token = ExpectToken();
if (token is not SymbolToken symbol)
{
throw new ParseException(Diagnostic
.Error($"Expected symbol, but found {token.GetType().Name}")
.WithHelp("This position requires a symbol like '(', ')', '{', '}', etc.")
.At(token)
.Build());
}
return symbol;
}
private void ExpectSymbol(Symbol expectedSymbol)
{
var token = ExpectSymbol();
if (token.Symbol != expectedSymbol)
{
throw new ParseException(Diagnostic
.Error($"Expected '{expectedSymbol}', but found '{token.Symbol}'")
.WithHelp($"Insert '{expectedSymbol}' here")
.At(token)
.Build());
}
}
private bool TryExpectSymbol(Symbol symbol)
{
if (CurrentToken is SymbolToken symbolToken && symbolToken.Symbol == symbol)
{
Next();
return true;
}
return false;
}
private bool TryExpectIdentifier([NotNullWhen(true)] out IdentifierToken? identifier)
{
if (CurrentToken is IdentifierToken identifierToken)
{
identifier = identifierToken;
Next();
return true;
}
identifier = null;
return false;
}
private IdentifierToken ExpectIdentifier()
{
var token = ExpectToken();
if (token is not IdentifierToken identifier)
{
throw new ParseException(Diagnostic
.Error($"Expected identifier, but found {token.GetType().Name}")
.WithHelp("Provide a valid identifier name here")
.At(token)
.Build());
}
return identifier;
}
private void Next()
{
_tokenIndex++;
}
private IEnumerable<Token> GetTokens(int tokenStartIndex)
{
return _tokens.Skip(tokenStartIndex).Take(_tokenIndex - tokenStartIndex);
}
}
public class ParseException : Exception
{
public Diagnostic Diagnostic { get; }
public ParseException(Diagnostic diagnostic) : base(diagnostic.Message)
{
Diagnostic = diagnostic;
}
}

View File

@@ -1,24 +0,0 @@
using NubLang.Tokenization;
namespace NubLang.Parsing.Syntax;
// todo(nub31): Check export modifier instead of harcoding true
public abstract record DefinitionSyntax(IEnumerable<Token> Tokens, string Name, bool Exported = true) : SyntaxNode(Tokens);
public record FuncParameterSyntax(IEnumerable<Token> Tokens, string Name, TypeSyntax Type) : SyntaxNode(Tokens);
public record FuncSignatureSyntax(IEnumerable<Token> Tokens, IReadOnlyList<FuncParameterSyntax> Parameters, TypeSyntax ReturnType) : SyntaxNode(Tokens);
public record FuncSyntax(IEnumerable<Token> Tokens, string Name, FuncSignatureSyntax Signature, BlockSyntax Body) : DefinitionSyntax(Tokens, Name);
public record ExternFuncSyntax(IEnumerable<Token> Tokens, string Name, string CallName, FuncSignatureSyntax Signature) : DefinitionSyntax(Tokens, Name);
public record StructFieldSyntax(IEnumerable<Token> Tokens, int Index, string Name, TypeSyntax Type, Optional<ExpressionSyntax> Value) : SyntaxNode(Tokens);
public record StructFuncSyntax(IEnumerable<Token> Tokens, string Name, FuncSignatureSyntax Signature, BlockSyntax Body) : SyntaxNode(Tokens);
public record StructSyntax(IEnumerable<Token> Tokens, string Name, IReadOnlyList<StructFieldSyntax> Fields, IReadOnlyList<StructFuncSyntax> Functions, IReadOnlyList<TypeSyntax> InterfaceImplementations) : DefinitionSyntax(Tokens, Name);
public record InterfaceFuncSyntax(IEnumerable<Token> Tokens, string Name, FuncSignatureSyntax Signature) : SyntaxNode(Tokens);
public record InterfaceSyntax(IEnumerable<Token> Tokens, string Name, IReadOnlyList<InterfaceFuncSyntax> Functions) : DefinitionSyntax(Tokens, Name);

View File

@@ -1,57 +0,0 @@
using NubLang.Tokenization;
namespace NubLang.Parsing.Syntax;
public enum UnaryOperatorSyntax
{
Negate,
Invert
}
public enum BinaryOperatorSyntax
{
Equal,
NotEqual,
GreaterThan,
GreaterThanOrEqual,
LessThan,
LessThanOrEqual,
LogicalAnd,
LogicalOr,
Plus,
Minus,
Multiply,
Divide,
Modulo,
LeftShift,
RightShift,
BitwiseAnd,
BitwiseXor,
BitwiseOr,
}
public abstract record ExpressionSyntax(IEnumerable<Token> Tokens) : SyntaxNode(Tokens);
public record BinaryExpressionSyntax(IEnumerable<Token> Tokens, ExpressionSyntax Left, BinaryOperatorSyntax Operator, ExpressionSyntax Right) : ExpressionSyntax(Tokens);
public record UnaryExpressionSyntax(IEnumerable<Token> Tokens, UnaryOperatorSyntax Operator, ExpressionSyntax Operand) : ExpressionSyntax(Tokens);
public record FuncCallSyntax(IEnumerable<Token> Tokens, ExpressionSyntax Expression, IReadOnlyList<ExpressionSyntax> Parameters) : ExpressionSyntax(Tokens);
public record DotFuncCallSyntax(IEnumerable<Token> Tokens, string Name, ExpressionSyntax ThisParameter, IReadOnlyList<ExpressionSyntax> Parameters) : ExpressionSyntax(Tokens);
public record IdentifierSyntax(IEnumerable<Token> Tokens, Optional<string> Module, string Name) : ExpressionSyntax(Tokens);
public record ArrayInitializerSyntax(IEnumerable<Token> Tokens, ExpressionSyntax Capacity, TypeSyntax ElementType) : ExpressionSyntax(Tokens);
public record ArrayIndexAccessSyntax(IEnumerable<Token> Tokens, ExpressionSyntax Target, ExpressionSyntax Index) : ExpressionSyntax(Tokens);
public record AddressOfSyntax(IEnumerable<Token> Tokens, ExpressionSyntax Expression) : ExpressionSyntax(Tokens);
public record LiteralSyntax(IEnumerable<Token> Tokens, string Value, LiteralKind Kind) : ExpressionSyntax(Tokens);
public record StructFieldAccessSyntax(IEnumerable<Token> Tokens, ExpressionSyntax Target, string Member) : ExpressionSyntax(Tokens);
public record StructInitializerSyntax(IEnumerable<Token> Tokens, Optional<TypeSyntax> StructType, Dictionary<string, ExpressionSyntax> Initializers) : ExpressionSyntax(Tokens);
public record DereferenceSyntax(IEnumerable<Token> Tokens, ExpressionSyntax Expression) : ExpressionSyntax(Tokens);

View File

@@ -1,21 +0,0 @@
using NubLang.Tokenization;
namespace NubLang.Parsing.Syntax;
public abstract record StatementSyntax(IEnumerable<Token> Tokens) : SyntaxNode(Tokens);
public record StatementExpressionSyntax(IEnumerable<Token> Tokens, ExpressionSyntax Expression) : StatementSyntax(Tokens);
public record ReturnSyntax(IEnumerable<Token> Tokens, Optional<ExpressionSyntax> Value) : StatementSyntax(Tokens);
public record AssignmentSyntax(IEnumerable<Token> Tokens, ExpressionSyntax Target, ExpressionSyntax Value) : StatementSyntax(Tokens);
public record IfSyntax(IEnumerable<Token> Tokens, ExpressionSyntax Condition, BlockSyntax Body, Optional<Variant<IfSyntax, BlockSyntax>> Else) : StatementSyntax(Tokens);
public record VariableDeclarationSyntax(IEnumerable<Token> Tokens, string Name, Optional<TypeSyntax> ExplicitType, Optional<ExpressionSyntax> Assignment) : StatementSyntax(Tokens);
public record ContinueSyntax(IEnumerable<Token> Tokens) : StatementSyntax(Tokens);
public record BreakSyntax(IEnumerable<Token> Tokens) : StatementSyntax(Tokens);
public record WhileSyntax(IEnumerable<Token> Tokens, ExpressionSyntax Condition, BlockSyntax Body) : StatementSyntax(Tokens);

View File

@@ -1,11 +0,0 @@
using NubLang.Tokenization;
namespace NubLang.Parsing.Syntax;
public abstract record SyntaxNode(IEnumerable<Token> Tokens);
public record SyntaxTreeMetadata(string? ModuleName, IReadOnlyList<string> Imports);
public record SyntaxTree(IReadOnlyList<DefinitionSyntax> Definitions, SyntaxTreeMetadata Metadata);
public record BlockSyntax(IEnumerable<Token> Tokens, IReadOnlyList<StatementSyntax> Statements) : SyntaxNode(Tokens);

View File

@@ -1,25 +0,0 @@
using NubLang.Tokenization;
namespace NubLang.Parsing.Syntax;
public abstract record TypeSyntax(IEnumerable<Token> Tokens) : SyntaxNode(Tokens);
public record FuncTypeSyntax(IEnumerable<Token> Tokens, IReadOnlyList<TypeSyntax> Parameters, TypeSyntax ReturnType) : TypeSyntax(Tokens);
public record PointerTypeSyntax(IEnumerable<Token> Tokens, TypeSyntax BaseType) : TypeSyntax(Tokens);
public record VoidTypeSyntax(IEnumerable<Token> Tokens) : TypeSyntax(Tokens);
public record IntTypeSyntax(IEnumerable<Token> Tokens, bool Signed, int Width) : TypeSyntax(Tokens);
public record FloatTypeSyntax(IEnumerable<Token> Tokens, int Width) : TypeSyntax(Tokens);
public record BoolTypeSyntax(IEnumerable<Token> Tokens) : TypeSyntax(Tokens);
public record StringTypeSyntax(IEnumerable<Token> Tokens) : TypeSyntax(Tokens);
public record CStringTypeSyntax(IEnumerable<Token> Tokens) : TypeSyntax(Tokens);
public record ArrayTypeSyntax(IEnumerable<Token> Tokens, TypeSyntax BaseType) : TypeSyntax(Tokens);
public record CustomTypeSyntax(IEnumerable<Token> Tokens, string Module, string Name) : TypeSyntax(Tokens);

View File

@@ -1,81 +0,0 @@
using NubLang.Code;
namespace NubLang.Tokenization;
public abstract class Token(SourceFileSpan fileSpan)
{
public SourceFileSpan FileSpan { get; } = fileSpan;
}
public class IdentifierToken(SourceFileSpan fileSpan, string value) : Token(fileSpan)
{
public string Value { get; } = value;
}
public class LiteralToken(SourceFileSpan fileSpan, LiteralKind kind, string value) : Token(fileSpan)
{
public LiteralKind Kind { get; } = kind;
public string Value { get; } = value;
}
public enum LiteralKind
{
Integer,
Float,
String,
Bool
}
public class SymbolToken(SourceFileSpan fileSpan, Symbol symbol) : Token(fileSpan)
{
public Symbol Symbol { get; } = symbol;
}
public enum Symbol
{
Func,
Return,
If,
Else,
While,
Break,
Continue,
Colon,
OpenParen,
CloseParen,
OpenBrace,
CloseBrace,
OpenBracket,
CloseBracket,
Comma,
Period,
Assign,
Bang,
Equal,
NotEqual,
LessThan,
LessThanOrEqual,
GreaterThan,
GreaterThanOrEqual,
Plus,
Minus,
Star,
ForwardSlash,
Struct,
Caret,
Ampersand,
Let,
Calls,
Interface,
For,
Extern,
Semi,
Percent,
LeftShift,
RightShift,
Pipe,
And,
Or,
Module,
Import,
}

View File

@@ -1,264 +0,0 @@
using NubLang.Code;
using NubLang.Diagnostics;
namespace NubLang.Tokenization;
public sealed class Tokenizer
{
private static readonly Dictionary<string, Symbol> Keywords = new()
{
["func"] = Symbol.Func,
["if"] = Symbol.If,
["else"] = Symbol.Else,
["while"] = Symbol.While,
["break"] = Symbol.Break,
["continue"] = Symbol.Continue,
["return"] = Symbol.Return,
["struct"] = Symbol.Struct,
["let"] = Symbol.Let,
["calls"] = Symbol.Calls,
["interface"] = Symbol.Interface,
["for"] = Symbol.For,
["extern"] = Symbol.Extern,
["module"] = Symbol.Module,
};
private static readonly Dictionary<char[], Symbol> Symbols = new()
{
[['=', '=']] = Symbol.Equal,
[['!', '=']] = Symbol.NotEqual,
[['<', '=']] = Symbol.LessThanOrEqual,
[['>', '=']] = Symbol.GreaterThanOrEqual,
[['<', '<']] = Symbol.LeftShift,
[['>', '>']] = Symbol.RightShift,
[['&', '&']] = Symbol.And,
[['|', '|']] = Symbol.Or,
[[':']] = Symbol.Colon,
[['(']] = Symbol.OpenParen,
[[')']] = Symbol.CloseParen,
[['{']] = Symbol.OpenBrace,
[['}']] = Symbol.CloseBrace,
[['[']] = Symbol.OpenBracket,
[[']']] = Symbol.CloseBracket,
[[',']] = Symbol.Comma,
[['.']] = Symbol.Period,
[['=']] = Symbol.Assign,
[['<']] = Symbol.LessThan,
[['>']] = Symbol.GreaterThan,
[['+']] = Symbol.Plus,
[['-']] = Symbol.Minus,
[['*']] = Symbol.Star,
[['/']] = Symbol.ForwardSlash,
[['!']] = Symbol.Bang,
[['^']] = Symbol.Caret,
[['&']] = Symbol.Ampersand,
[[';']] = Symbol.Semi,
[['%']] = Symbol.Percent,
[['|']] = Symbol.Pipe,
};
private static readonly (char[] Pattern, Symbol Symbol)[] OrderedSymbols = Symbols
.OrderByDescending(kvp => kvp.Key.Length)
.Select(kvp => (kvp.Key, kvp.Value))
.ToArray();
private readonly SourceFile _sourceFile;
private readonly List<Diagnostic> _diagnostics = [];
private int _index;
public Tokenizer(SourceFile sourceFile)
{
_sourceFile = sourceFile;
}
public IReadOnlyList<Diagnostic> GetDiagnostics() => _diagnostics;
public IEnumerable<Token> Tokenize()
{
_index = 0;
while (Peek().TryGetValue(out var current))
{
if (char.IsWhiteSpace(current))
{
Next();
continue;
}
if (current == '/' && Peek(1).TryGetValue(out var nextChar) && nextChar == '/')
{
while (Peek().TryGetValue(out var ch) && ch != '\n')
{
Next();
}
continue;
}
var tokenStartIndex = _index;
if (char.IsLetter(current) || current == '_')
{
var buffer = string.Empty;
while (Peek().TryGetValue(out var next) && (char.IsLetterOrDigit(next) || next == '_'))
{
buffer += next;
Next();
}
if (Keywords.TryGetValue(buffer, out var keywordSymbol))
{
yield return new SymbolToken(GetSourceFileSpan(tokenStartIndex), keywordSymbol);
continue;
}
if (buffer is "true" or "false")
{
yield return new LiteralToken(GetSourceFileSpan(tokenStartIndex), LiteralKind.Bool, buffer);
continue;
}
yield return new IdentifierToken(GetSourceFileSpan(tokenStartIndex), buffer);
continue;
}
if (char.IsDigit(current))
{
var isFloat = false;
var buffer = string.Empty;
while (Peek().TryGetValue(out var next))
{
if (next == '.')
{
if (isFloat)
{
throw new Exception("More than one period found in float literal");
}
isFloat = true;
buffer += next;
Next();
}
else if (char.IsDigit(next))
{
buffer += next;
Next();
}
else
{
break;
}
}
yield return new LiteralToken(GetSourceFileSpan(tokenStartIndex), isFloat ? LiteralKind.Float : LiteralKind.Integer, buffer);
continue;
}
if (current == '"')
{
Next();
var buffer = string.Empty;
while (true)
{
if (!Peek().TryGetValue(out var next))
{
throw new Exception("Unclosed string literal");
}
if (next == '"')
{
Next();
break;
}
buffer += next;
Next();
}
yield return new LiteralToken(GetSourceFileSpan(tokenStartIndex), LiteralKind.String, buffer);
continue;
}
var foundMatch = false;
foreach (var (pattern, symbol) in OrderedSymbols)
{
for (var i = 0; i < pattern.Length; i++)
{
var c = Peek(i);
if (!c.HasValue || c.Value != pattern[i]) break;
if (i == pattern.Length - 1)
{
for (var j = 0; j <= i; j++)
{
Next();
}
yield return new SymbolToken(GetSourceFileSpan(tokenStartIndex), symbol);
foundMatch = true;
break;
}
}
if (foundMatch)
{
break;
}
}
if (foundMatch)
{
continue;
}
_diagnostics.Add(Diagnostic.Error($"Unknown token '{current}'").At(GetSourceFileSpan(tokenStartIndex)).Build());
Next();
}
}
private Optional<char> Peek(int offset = 0)
{
if (_index + offset < _sourceFile.GetText().Length)
{
return _sourceFile.GetText()[_index + offset];
}
return Optional<char>.Empty();
}
private void Next()
{
_index++;
}
private SourceFileSpan GetSourceFileSpan(int tokenStartIndex)
{
var start = CalculateSourceLocation(tokenStartIndex);
var end = CalculateSourceLocation(_index);
return new SourceFileSpan(_sourceFile, new SourceSpan(start, end));
}
private SourceLocation CalculateSourceLocation(int index)
{
var line = 1;
var column = 1;
for (var i = 0; i < index && i < _sourceFile.GetText().Length; i++)
{
if (_sourceFile.GetText()[i] == '\n')
{
line++;
column = 1;
}
else
{
column++;
}
}
return new SourceLocation(line, column);
}
}

View File

@@ -1,179 +0,0 @@
using NubLang.Parsing.Syntax;
using NubLang.TypeChecking.Node;
namespace NubLang.TypeChecking;
public class Module
{
public static IReadOnlyList<Module> CollectFromSyntaxTrees(IReadOnlyList<SyntaxTree> syntaxTrees)
{
var modules = new Dictionary<string, Module>();
foreach (var syntaxTree in syntaxTrees)
{
var name = syntaxTree.Metadata.ModuleName;
if (name == null)
{
continue;
}
if (!modules.TryGetValue(name, out var module))
{
module = new Module(name, syntaxTree.Metadata.Imports);
modules[name] = module;
}
foreach (var definition in syntaxTree.Definitions)
{
module.AddDefinition(definition);
}
}
return modules.Values.ToList();
}
private readonly List<DefinitionSyntax> _definitions = [];
public Module(string name, IReadOnlyList<string> imports)
{
Name = name;
Imports = imports;
}
public string Name { get; }
public IReadOnlyList<string> Imports { get; }
public IReadOnlyList<DefinitionSyntax> Definitions => _definitions;
private void AddDefinition(DefinitionSyntax syntax)
{
_definitions.Add(syntax);
}
}
public class TypedModule
{
public TypedModule(string name, IReadOnlyList<DefinitionNode> definitions)
{
Name = name;
Definitions = definitions;
}
public string Name { get; }
public IReadOnlyList<DefinitionNode> Definitions { get; }
}
public class ModuleSignature
{
public static IReadOnlyDictionary<string, ModuleSignature> CollectFromSyntaxTrees(IReadOnlyList<SyntaxTree> syntaxTrees)
{
var modules = new Dictionary<string, ModuleSignature>();
foreach (var syntaxTree in syntaxTrees)
{
var moduleName = syntaxTree.Metadata.ModuleName;
if (moduleName == null)
{
continue;
}
if (!modules.TryGetValue(moduleName, out var module))
{
module = new ModuleSignature();
modules[moduleName] = module;
}
foreach (var def in syntaxTree.Definitions)
{
if (def.Exported)
{
switch (def)
{
case ExternFuncSyntax externFuncDef:
{
var parameters = externFuncDef.Signature.Parameters.Select(p => TypeResolver.ResolveType(p.Type, modules)).ToList();
var returnType = TypeResolver.ResolveType(externFuncDef.Signature.ReturnType, modules);
var type = new FuncTypeNode(parameters, returnType);
module._functions.Add(externFuncDef.Name, type);
break;
}
case FuncSyntax funcDef:
{
var parameters = funcDef.Signature.Parameters.Select(p => TypeResolver.ResolveType(p.Type, modules)).ToList();
var returnType = TypeResolver.ResolveType(funcDef.Signature.ReturnType, modules);
var type = new FuncTypeNode(parameters, returnType);
module._functions.Add(funcDef.Name, type);
break;
}
case InterfaceSyntax interfaceDef:
{
var functions = new List<InterfaceTypeFunc>();
for (var i = 0; i < interfaceDef.Functions.Count; i++)
{
var function = interfaceDef.Functions[i];
var parameters = function.Signature.Parameters.Select(p => TypeResolver.ResolveType(p.Type, modules)).ToList();
var returnType = TypeResolver.ResolveType(function.Signature.ReturnType, modules);
functions.Add(new InterfaceTypeFunc(function.Name, new FuncTypeNode(parameters, returnType), i));
}
var type = new InterfaceTypeNode(moduleName, interfaceDef.Name, functions);
module._interfaces.Add(type);
break;
}
case StructSyntax structDef:
{
var fields = new List<StructTypeField>();
foreach (var field in structDef.Fields)
{
fields.Add(new StructTypeField(field.Name, TypeResolver.ResolveType(field.Type, modules), field.Index, field.Value.HasValue));
}
var functions = new List<StructTypeFunc>();
foreach (var function in structDef.Functions)
{
var parameters = function.Signature.Parameters.Select(p => TypeResolver.ResolveType(p.Type, modules)).ToList();
var returnType = TypeResolver.ResolveType(function.Signature.ReturnType, modules);
functions.Add(new StructTypeFunc(function.Name, new FuncTypeNode(parameters, returnType)));
}
var interfaceImplementations = new List<InterfaceTypeNode>();
foreach (var interfaceImplementation in structDef.InterfaceImplementations)
{
if (interfaceImplementation is not CustomTypeSyntax customType)
{
throw new Exception("Interface implementation is not a custom type");
}
var resolvedType = TypeResolver.ResolveCustomType(customType.Module, customType.Name, modules);
if (resolvedType is not InterfaceTypeNode interfaceType)
{
throw new Exception("Interface implementation is not a interface");
}
interfaceImplementations.Add(interfaceType);
}
var type = new StructTypeNode(moduleName, structDef.Name, fields, functions, interfaceImplementations);
module._structs.Add(type);
break;
}
default:
{
throw new ArgumentOutOfRangeException(nameof(def));
}
}
}
}
}
return modules;
}
private readonly List<StructTypeNode> _structs = [];
private readonly List<InterfaceTypeNode> _interfaces = [];
private readonly Dictionary<string, FuncTypeNode> _functions = [];
public IReadOnlyList<StructTypeNode> StructTypes => _structs;
public IReadOnlyList<InterfaceTypeNode> InterfaceTypes => _interfaces;
public IReadOnlyDictionary<string, FuncTypeNode> Functions => _functions;
}

View File

@@ -1,21 +0,0 @@
namespace NubLang.TypeChecking.Node;
public abstract record DefinitionNode : Node;
public record FuncParameterNode(string Name, TypeNode Type) : Node;
public record FuncSignatureNode(IReadOnlyList<FuncParameterNode> Parameters, TypeNode ReturnType) : Node;
public record LocalFuncNode(string Name, FuncSignatureNode Signature, BlockNode Body) : DefinitionNode;
public record ExternFuncNode(string Name, string CallName, FuncSignatureNode Signature) : DefinitionNode;
public record StructFieldNode(int Index, string Name, TypeNode Type, Optional<ExpressionNode> Value) : Node;
public record StructFuncNode(string Name, FuncSignatureNode Signature, BlockNode Body) : Node;
public record StructNode(string Name, IReadOnlyList<StructFieldNode> Fields, IReadOnlyList<StructFuncNode> Functions, IReadOnlyList<InterfaceTypeNode> InterfaceImplementations) : DefinitionNode;
public record InterfaceFuncNode(string Name, FuncSignatureNode Signature) : Node;
public record InterfaceNode(string Name, IReadOnlyList<InterfaceFuncNode> Functions) : DefinitionNode;

View File

@@ -1,72 +0,0 @@
using NubLang.Tokenization;
namespace NubLang.TypeChecking.Node;
public enum UnaryOperator
{
Negate,
Invert
}
public enum BinaryOperator
{
Equal,
NotEqual,
GreaterThan,
GreaterThanOrEqual,
LessThan,
LessThanOrEqual,
LogicalAnd,
LogicalOr,
Plus,
Minus,
Multiply,
Divide,
Modulo,
LeftShift,
RightShift,
BitwiseAnd,
BitwiseXor,
BitwiseOr
}
public abstract record ExpressionNode(TypeNode Type) : Node;
public abstract record LValueExpressionNode(TypeNode Type) : RValueExpressionNode(Type);
public abstract record RValueExpressionNode(TypeNode Type) : ExpressionNode(Type);
public record BinaryExpressionNode(TypeNode Type, ExpressionNode Left, BinaryOperator Operator, ExpressionNode Right) : RValueExpressionNode(Type);
public record UnaryExpressionNode(TypeNode Type, UnaryOperator Operator, ExpressionNode Operand) : RValueExpressionNode(Type);
public record FuncCallNode(TypeNode Type, ExpressionNode Expression, IReadOnlyList<ExpressionNode> Parameters) : RValueExpressionNode(Type);
public record StructFuncCallNode(TypeNode Type, string Name, StructTypeNode StructType, ExpressionNode StructExpression, IReadOnlyList<ExpressionNode> Parameters) : RValueExpressionNode(Type);
public record InterfaceFuncCallNode(TypeNode Type, string Name, InterfaceTypeNode InterfaceType, ExpressionNode InterfaceExpression, IReadOnlyList<ExpressionNode> Parameters) : RValueExpressionNode(Type);
public record VariableIdentifierNode(TypeNode Type, string Name) : LValueExpressionNode(Type);
public record FuncParameterIdentifierNode(TypeNode Type, string Name) : RValueExpressionNode(Type);
public record FuncIdentifierNode(TypeNode Type, string Module, string Name) : RValueExpressionNode(Type);
public record ArrayInitializerNode(TypeNode Type, ExpressionNode Capacity, TypeNode ElementType) : RValueExpressionNode(Type);
public record ArrayIndexAccessNode(TypeNode Type, ExpressionNode Target, ExpressionNode Index) : LValueExpressionNode(Type);
public record AddressOfNode(TypeNode Type, LValueExpressionNode LValue) : RValueExpressionNode(Type);
public record LiteralNode(TypeNode Type, string Value, LiteralKind Kind) : RValueExpressionNode(Type);
public record StructFieldAccessNode(TypeNode Type, StructTypeNode StructType, ExpressionNode Target, string Field) : LValueExpressionNode(Type);
public record StructInitializerNode(StructTypeNode StructType, Dictionary<string, ExpressionNode> Initializers) : RValueExpressionNode(StructType);
public record DereferenceNode(TypeNode Type, ExpressionNode Expression) : RValueExpressionNode(Type);
public record ConvertToInterfaceNode(TypeNode Type, InterfaceTypeNode InterfaceType, StructTypeNode StructType, ExpressionNode Implementation) : RValueExpressionNode(Type);
public record ConvertIntNode(TypeNode Type, ExpressionNode Value, IntTypeNode ValueType, IntTypeNode TargetType) : RValueExpressionNode(Type);
public record ConvertFloatNode(TypeNode Type, ExpressionNode Value, FloatTypeNode ValueType, FloatTypeNode TargetType) : RValueExpressionNode(Type);

View File

@@ -1,5 +0,0 @@
namespace NubLang.TypeChecking.Node;
public abstract record Node;
public record BlockNode(IReadOnlyList<StatementNode> Statements) : Node;

View File

@@ -1,19 +0,0 @@
namespace NubLang.TypeChecking.Node;
public record StatementNode : Node;
public record StatementExpressionNode(ExpressionNode Expression) : StatementNode;
public record ReturnNode(Optional<ExpressionNode> Value) : StatementNode;
public record AssignmentNode(LValueExpressionNode Target, ExpressionNode Value) : StatementNode;
public record IfNode(ExpressionNode Condition, BlockNode Body, Optional<Variant<IfNode, BlockNode>> Else) : StatementNode;
public record VariableDeclarationNode(string Name, Optional<ExpressionNode> Assignment, TypeNode Type) : StatementNode;
public record ContinueNode : StatementNode;
public record BreakNode : StatementNode;
public record WhileNode(ExpressionNode Condition, BlockNode Body) : StatementNode;

View File

@@ -1,232 +0,0 @@
using System.Diagnostics.CodeAnalysis;
namespace NubLang.TypeChecking.Node;
public abstract class TypeNode : IEquatable<TypeNode>
{
public bool IsSimpleType([NotNullWhen(true)] out SimpleTypeNode? simpleType, [NotNullWhen(false)] out ComplexTypeNode? complexType)
{
if (this is SimpleTypeNode st)
{
complexType = null;
simpleType = st;
return true;
}
if (this is ComplexTypeNode ct)
{
complexType = ct;
simpleType = null;
return false;
}
throw new ArgumentException($"Type {this} is not a simple type nor a complex type");
}
public override bool Equals(object? obj) => obj is TypeNode other && Equals(other);
public abstract bool Equals(TypeNode? other);
public abstract override int GetHashCode();
public abstract override string ToString();
public static bool operator ==(TypeNode? left, TypeNode? right) => Equals(left, right);
public static bool operator !=(TypeNode? left, TypeNode? right) => !Equals(left, right);
}
public enum StorageSize
{
Void,
I8,
I16,
I32,
I64,
U8,
U16,
U32,
U64,
F32,
F64
}
public abstract class SimpleTypeNode : TypeNode
{
public abstract StorageSize StorageSize { get; }
}
#region Simple types
public class IntTypeNode(bool signed, int width) : SimpleTypeNode
{
public bool Signed { get; } = signed;
public int Width { get; } = width;
public override StorageSize StorageSize => Signed switch
{
true => Width switch
{
8 => StorageSize.I8,
16 => StorageSize.I16,
32 => StorageSize.I32,
64 => StorageSize.I64,
_ => throw new ArgumentOutOfRangeException(nameof(Width))
},
false => Width switch
{
8 => StorageSize.U8,
16 => StorageSize.U16,
32 => StorageSize.U32,
64 => StorageSize.U64,
_ => throw new ArgumentOutOfRangeException(nameof(Width))
}
};
public override string ToString() => $"{(Signed ? "i" : "u")}{Width}";
public override bool Equals(TypeNode? other) => other is IntTypeNode @int && @int.Width == Width && @int.Signed == Signed;
public override int GetHashCode() => HashCode.Combine(typeof(IntTypeNode), Signed, Width);
}
public class FloatTypeNode(int width) : SimpleTypeNode
{
public int Width { get; } = width;
public override StorageSize StorageSize => Width switch
{
32 => StorageSize.F32,
64 => StorageSize.F64,
_ => throw new ArgumentOutOfRangeException(nameof(Width))
};
public override string ToString() => $"f{Width}";
public override bool Equals(TypeNode? other) => other is FloatTypeNode @int && @int.Width == Width;
public override int GetHashCode() => HashCode.Combine(typeof(FloatTypeNode), Width);
}
public class BoolTypeNode : SimpleTypeNode
{
public override StorageSize StorageSize => StorageSize.U8;
public override string ToString() => "bool";
public override bool Equals(TypeNode? other) => other is BoolTypeNode;
public override int GetHashCode() => HashCode.Combine(typeof(BoolTypeNode));
}
public class FuncTypeNode(IReadOnlyList<TypeNode> parameters, TypeNode returnType) : SimpleTypeNode
{
public IReadOnlyList<TypeNode> Parameters { get; } = parameters;
public TypeNode ReturnType { get; } = returnType;
public override StorageSize StorageSize => StorageSize.U64;
public override string ToString() => $"func({string.Join(", ", Parameters)}): {ReturnType}";
public override bool Equals(TypeNode? other) => other is FuncTypeNode func && ReturnType.Equals(func.ReturnType) && Parameters.SequenceEqual(func.Parameters);
public override int GetHashCode()
{
var hash = new HashCode();
hash.Add(typeof(FuncTypeNode));
hash.Add(ReturnType);
foreach (var param in Parameters)
{
hash.Add(param);
}
return hash.ToHashCode();
}
}
public class PointerTypeNode(TypeNode baseType) : SimpleTypeNode
{
public TypeNode BaseType { get; } = baseType;
public override StorageSize StorageSize => StorageSize.U64;
public override string ToString() => "^" + BaseType;
public override bool Equals(TypeNode? other) => other is PointerTypeNode pointer && BaseType.Equals(pointer.BaseType);
public override int GetHashCode() => HashCode.Combine(typeof(PointerTypeNode), BaseType);
}
public class VoidTypeNode : SimpleTypeNode
{
public override StorageSize StorageSize => StorageSize.Void;
public override string ToString() => "void";
public override bool Equals(TypeNode? other) => other is VoidTypeNode;
public override int GetHashCode() => HashCode.Combine(typeof(VoidTypeNode));
}
#endregion
public abstract class ComplexTypeNode : TypeNode;
#region Complex types
public class CStringTypeNode : ComplexTypeNode
{
public override string ToString() => "cstring";
public override bool Equals(TypeNode? other) => other is CStringTypeNode;
public override int GetHashCode() => HashCode.Combine(typeof(CStringTypeNode));
}
public class StringTypeNode : ComplexTypeNode
{
public override string ToString() => "string";
public override bool Equals(TypeNode? other) => other is StringTypeNode;
public override int GetHashCode() => HashCode.Combine(typeof(StringTypeNode));
}
public class StructTypeField(string name, TypeNode type, int index, bool hasDefaultValue)
{
public string Name { get; } = name;
public TypeNode Type { get; } = type;
public int Index { get; } = index;
public bool HasDefaultValue { get; } = hasDefaultValue;
}
public class StructTypeFunc(string name, FuncTypeNode type)
{
public string Name { get; } = name;
public FuncTypeNode Type { get; } = type;
}
public class StructTypeNode(string module, string name, IReadOnlyList<StructTypeField> fields, IReadOnlyList<StructTypeFunc> functions, IReadOnlyList<InterfaceTypeNode> interfaceImplementations) : ComplexTypeNode
{
public string Module { get; } = module;
public string Name { get; } = name;
public IReadOnlyList<StructTypeField> Fields { get; set; } = fields;
public IReadOnlyList<StructTypeFunc> Functions { get; set; } = functions;
public IReadOnlyList<InterfaceTypeNode> InterfaceImplementations { get; set; } = interfaceImplementations;
public override string ToString() => Name;
public override bool Equals(TypeNode? other) => other is StructTypeNode structType && Name == structType.Name && Module == structType.Module;
public override int GetHashCode() => HashCode.Combine(typeof(StructTypeNode), Name);
}
public class InterfaceTypeFunc(string name, FuncTypeNode type, int index)
{
public string Name { get; } = name;
public FuncTypeNode Type { get; } = type;
public int Index { get; } = index;
}
public class InterfaceTypeNode(string module, string name, IReadOnlyList<InterfaceTypeFunc> functions) : ComplexTypeNode
{
public string Module { get; } = module;
public string Name { get; } = name;
public IReadOnlyList<InterfaceTypeFunc> Functions { get; set; } = functions;
public override string ToString() => Name;
public override bool Equals(TypeNode? other) => other is InterfaceTypeNode interfaceType && Name == interfaceType.Name && Module == interfaceType.Module;
public override int GetHashCode() => HashCode.Combine(typeof(InterfaceTypeNode), Name);
}
public class ArrayTypeNode(TypeNode elementType) : ComplexTypeNode
{
public TypeNode ElementType { get; } = elementType;
public override string ToString() => "[]" + ElementType;
public override bool Equals(TypeNode? other) => other is ArrayTypeNode array && ElementType.Equals(array.ElementType);
public override int GetHashCode() => HashCode.Combine(typeof(ArrayTypeNode), ElementType);
}
#endregion

View File

@@ -1,457 +0,0 @@
using NubLang.Diagnostics;
using NubLang.Parsing.Syntax;
using NubLang.Tokenization;
using NubLang.TypeChecking.Node;
namespace NubLang.TypeChecking;
public sealed class TypeChecker
{
private readonly Module _currentModule;
private readonly IReadOnlyDictionary<string, ModuleSignature> _moduleSignatures;
private readonly Stack<Scope> _scopes = [];
private readonly Stack<TypeNode> _funcReturnTypes = [];
private readonly List<Diagnostic> _diagnostics = [];
private Scope Scope => _scopes.Peek();
public TypeChecker(Module currentModule, IReadOnlyDictionary<string, ModuleSignature> moduleSignatures)
{
_currentModule = currentModule;
_moduleSignatures = moduleSignatures.Where(x => currentModule.Imports.Contains(x.Key) || _currentModule.Name == x.Key).ToDictionary();
}
public IReadOnlyList<Diagnostic> GetDiagnostics() => _diagnostics;
public TypedModule CheckModule()
{
_diagnostics.Clear();
_scopes.Clear();
var definitions = new List<DefinitionNode>();
foreach (var definition in _currentModule.Definitions)
{
try
{
definitions.Add(CheckDefinition(definition));
}
catch (TypeCheckerException e)
{
_diagnostics.Add(e.Diagnostic);
}
}
return new TypedModule(_currentModule.Name, definitions);
}
private DefinitionNode CheckDefinition(DefinitionSyntax node)
{
return node switch
{
ExternFuncSyntax definition => CheckExternFuncDefinition(definition),
InterfaceSyntax definition => CheckInterfaceDefinition(definition),
FuncSyntax definition => CheckLocalFuncDefinition(definition),
StructSyntax definition => CheckStructDefinition(definition),
_ => throw new ArgumentOutOfRangeException(nameof(node))
};
}
private InterfaceNode CheckInterfaceDefinition(InterfaceSyntax node)
{
throw new NotImplementedException();
}
private StructNode CheckStructDefinition(StructSyntax node)
{
var fields = new List<StructFieldNode>();
foreach (var field in node.Fields)
{
var value = Optional.Empty<ExpressionNode>();
if (field.Value.HasValue)
{
value = CheckExpression(field.Value.Value);
}
fields.Add(new StructFieldNode(field.Index, field.Name, ResolveType(field.Type), value));
}
var functions = new List<StructFuncNode>();
foreach (var function in node.Functions)
{
var scope = new Scope();
// todo(nub31): Add this parameter
foreach (var parameter in function.Signature.Parameters)
{
scope.Declare(new Identifier(parameter.Name, ResolveType(parameter.Type), IdentifierKind.FunctionParameter));
}
_funcReturnTypes.Push(ResolveType(function.Signature.ReturnType));
var body = CheckBlock(function.Body, scope);
_funcReturnTypes.Pop();
functions.Add(new StructFuncNode(function.Name, CheckFuncSignature(function.Signature), body));
}
var interfaceImplementations = new List<InterfaceTypeNode>();
foreach (var interfaceImplementation in node.InterfaceImplementations)
{
var type = ResolveType(interfaceImplementation);
if (type is not InterfaceTypeNode interfaceType)
{
_diagnostics.Add(Diagnostic.Error($"Struct {node.Name} cannot implement non-struct type {interfaceImplementation}").At(interfaceImplementation).Build());
continue;
}
interfaceImplementations.Add(interfaceType);
}
return new StructNode(node.Name, fields, functions, interfaceImplementations);
}
private ExternFuncNode CheckExternFuncDefinition(ExternFuncSyntax node)
{
return new ExternFuncNode(node.Name, node.CallName, CheckFuncSignature(node.Signature));
}
private LocalFuncNode CheckLocalFuncDefinition(FuncSyntax node)
{
var scope = new Scope();
foreach (var parameter in node.Signature.Parameters)
{
scope.Declare(new Identifier(parameter.Name, ResolveType(parameter.Type), IdentifierKind.FunctionParameter));
}
_funcReturnTypes.Push(ResolveType(node.Signature.ReturnType));
var body = CheckBlock(node.Body, scope);
_funcReturnTypes.Pop();
return new LocalFuncNode(node.Name, CheckFuncSignature(node.Signature), body);
}
private StatementNode CheckStatement(StatementSyntax node)
{
return node switch
{
AssignmentSyntax statement => CheckAssignment(statement),
BreakSyntax => new BreakNode(),
ContinueSyntax => new ContinueNode(),
IfSyntax statement => CheckIf(statement),
ReturnSyntax statement => CheckReturn(statement),
StatementExpressionSyntax statement => CheckStatementExpression(statement),
VariableDeclarationSyntax statement => CheckVariableDeclaration(statement),
WhileSyntax statement => CheckWhile(statement),
_ => throw new ArgumentOutOfRangeException(nameof(node))
};
}
private StatementNode CheckAssignment(AssignmentSyntax statement)
{
throw new NotImplementedException();
}
private IfNode CheckIf(IfSyntax statement)
{
throw new NotImplementedException();
}
private ReturnNode CheckReturn(ReturnSyntax statement)
{
var value = Optional.Empty<ExpressionNode>();
if (statement.Value.HasValue)
{
value = CheckExpression(statement.Value.Value, _funcReturnTypes.Peek());
}
return new ReturnNode(value);
}
private StatementExpressionNode CheckStatementExpression(StatementExpressionSyntax statement)
{
return new StatementExpressionNode(CheckExpression(statement.Expression));
}
private VariableDeclarationNode CheckVariableDeclaration(VariableDeclarationSyntax statement)
{
TypeNode? type = null;
ExpressionNode? assignmentNode = null;
if (statement.ExplicitType.TryGetValue(out var explicitType))
{
type = ResolveType(explicitType);
}
if (statement.Assignment.TryGetValue(out var assignment))
{
assignmentNode = CheckExpression(assignment, type);
type ??= assignmentNode.Type;
}
if (type == null)
{
throw new TypeCheckerException(Diagnostic.Error($"Cannot infer type of variable {statement.Name}").At(statement).Build());
}
Scope.Declare(new Identifier(statement.Name, type, IdentifierKind.Variable));
return new VariableDeclarationNode(statement.Name, Optional.OfNullable(assignmentNode), type);
}
private WhileNode CheckWhile(WhileSyntax statement)
{
throw new NotImplementedException();
}
private FuncSignatureNode CheckFuncSignature(FuncSignatureSyntax statement)
{
var parameters = new List<FuncParameterNode>();
foreach (var parameter in statement.Parameters)
{
parameters.Add(new FuncParameterNode(parameter.Name, ResolveType(parameter.Type)));
}
return new FuncSignatureNode(parameters, ResolveType(statement.ReturnType));
}
private ExpressionNode CheckExpression(ExpressionSyntax node, TypeNode? expectedType = null)
{
var result = node switch
{
AddressOfSyntax expression => CheckAddressOf(expression),
ArrayIndexAccessSyntax expression => CheckArrayIndexAccess(expression),
ArrayInitializerSyntax expression => CheckArrayInitializer(expression),
BinaryExpressionSyntax expression => CheckBinaryExpression(expression),
DereferenceSyntax expression => CheckDereference(expression),
DotFuncCallSyntax expression => CheckDotFuncCall(expression),
FuncCallSyntax expression => CheckFuncCall(expression),
IdentifierSyntax expression => CheckIdentifier(expression),
LiteralSyntax expression => CheckLiteral(expression, expectedType),
StructFieldAccessSyntax expression => CheckStructFieldAccess(expression),
StructInitializerSyntax expression => CheckStructInitializer(expression, expectedType),
UnaryExpressionSyntax expression => CheckUnaryExpression(expression),
_ => throw new ArgumentOutOfRangeException(nameof(node))
};
if (expectedType == null || result.Type == expectedType)
{
return result;
}
if (result.Type is StructTypeNode structType && expectedType is InterfaceTypeNode interfaceType)
{
return new ConvertToInterfaceNode(interfaceType, interfaceType, structType, result);
}
if (result.Type is IntTypeNode sourceIntType && expectedType is IntTypeNode targetIntType)
{
if (sourceIntType.Signed == targetIntType.Signed && sourceIntType.Width < targetIntType.Width)
{
return new ConvertIntNode(targetIntType, result, sourceIntType, targetIntType);
}
}
if (result.Type is FloatTypeNode sourceFloatType && expectedType is FloatTypeNode targetFloatType)
{
if (sourceFloatType.Width < targetFloatType.Width)
{
return new ConvertFloatNode(targetFloatType, result, sourceFloatType, targetFloatType);
}
}
throw new TypeCheckerException(Diagnostic.Error($"Cannot convert {result.Type} to {expectedType}").At(node).Build());
}
private AddressOfNode CheckAddressOf(AddressOfSyntax expression)
{
throw new NotImplementedException();
}
private ArrayIndexAccessNode CheckArrayIndexAccess(ArrayIndexAccessSyntax expression)
{
throw new NotImplementedException();
}
private ArrayInitializerNode CheckArrayInitializer(ArrayInitializerSyntax expression)
{
throw new NotImplementedException();
}
private BinaryExpressionNode CheckBinaryExpression(BinaryExpressionSyntax expression)
{
throw new NotImplementedException();
}
private DereferenceNode CheckDereference(DereferenceSyntax expression)
{
throw new NotImplementedException();
}
private FuncCallNode CheckFuncCall(FuncCallSyntax expression)
{
var accessor = CheckExpression(expression.Expression);
if (accessor.Type is not FuncTypeNode funcType)
{
throw new TypeCheckerException(Diagnostic.Error($"Cannot call non-function type {accessor.Type}").At(expression.Expression).Build());
}
if (expression.Parameters.Count != funcType.Parameters.Count)
{
throw new TypeCheckerException(Diagnostic.Error($"Function {funcType} expects {funcType.Parameters} but got {expression.Parameters.Count} parameters").At(expression.Expression).Build());
}
var parameters = new List<ExpressionNode>();
for (var i = 0; i < expression.Parameters.Count; i++)
{
var parameter = expression.Parameters[i];
var expectedType = funcType.Parameters[i];
var parameterExpression = CheckExpression(parameter, expectedType);
if (parameterExpression.Type != expectedType)
{
throw new Exception($"Parameter {i + 1} does not match the type {expectedType} for function {funcType}");
}
parameters.Add(parameterExpression);
}
return new FuncCallNode(funcType.ReturnType, accessor, parameters);
}
private ExpressionNode CheckDotFuncCall(DotFuncCallSyntax expression)
{
throw new NotImplementedException();
}
private ExpressionNode CheckIdentifier(IdentifierSyntax expression)
{
// If the identifier does not have a module specified, first check if a local variable or function parameter with that identifier exists
if (!expression.Module.TryGetValue(out var moduleName))
{
var scopeIdent = Scope.Lookup(expression.Name);
if (scopeIdent != null)
{
switch (scopeIdent.Kind)
{
case IdentifierKind.Variable:
{
return new VariableIdentifierNode(scopeIdent.Type, expression.Name);
}
case IdentifierKind.FunctionParameter:
{
return new FuncParameterIdentifierNode(scopeIdent.Type, expression.Name);
}
default:
{
throw new ArgumentOutOfRangeException();
}
}
}
}
moduleName ??= _currentModule.Name;
if (_moduleSignatures.TryGetValue(moduleName, out var module))
{
if (module.Functions.TryGetValue(expression.Name, out var function))
{
return new FuncIdentifierNode(function, moduleName, expression.Name);
}
}
throw new TypeCheckerException(Diagnostic.Error($"Identifier {expression.Name} not found").At(expression).Build());
}
private LiteralNode CheckLiteral(LiteralSyntax expression, TypeNode? expectedType)
{
// todo(nub31): Check if the types can actually be represented as another one. For example, an int should be passed when a string is expected
var type = expectedType ?? expression.Kind switch
{
LiteralKind.Integer => new IntTypeNode(true, 64),
LiteralKind.Float => new FloatTypeNode(64),
LiteralKind.String => new StringTypeNode(),
LiteralKind.Bool => new BoolTypeNode(),
_ => throw new ArgumentOutOfRangeException()
};
return new LiteralNode(type, expression.Value, expression.Kind);
}
private StructFieldAccessNode CheckStructFieldAccess(StructFieldAccessSyntax expression)
{
throw new NotImplementedException();
}
private StructInitializerNode CheckStructInitializer(StructInitializerSyntax expression, TypeNode? expectedType)
{
throw new NotImplementedException();
}
private UnaryExpressionNode CheckUnaryExpression(UnaryExpressionSyntax expression)
{
throw new NotImplementedException();
}
private BlockNode CheckBlock(BlockSyntax node, Scope? scope = null)
{
var statements = new List<StatementNode>();
_scopes.Push(scope ?? Scope.SubScope());
foreach (var statement in node.Statements)
{
statements.Add(CheckStatement(statement));
}
_scopes.Pop();
return new BlockNode(statements);
}
private TypeNode ResolveType(TypeSyntax fieldType)
{
return TypeResolver.ResolveType(fieldType, _moduleSignatures);
}
}
public enum IdentifierKind
{
Variable,
FunctionParameter
}
public record Identifier(string Name, TypeNode Type, IdentifierKind Kind);
public class Scope(Scope? parent = null)
{
private readonly List<Identifier> _variables = [];
public Identifier? Lookup(string name)
{
var variable = _variables.FirstOrDefault(x => x.Name == name);
if (variable != null)
{
return variable;
}
return parent?.Lookup(name);
}
public void Declare(Identifier identifier)
{
_variables.Add(identifier);
}
public Scope SubScope()
{
return new Scope(this);
}
}
public class TypeCheckerException : Exception
{
public Diagnostic Diagnostic { get; }
public TypeCheckerException(Diagnostic diagnostic) : base(diagnostic.Message)
{
Diagnostic = diagnostic;
}
}

View File

@@ -1,80 +0,0 @@
using NubLang.Parsing.Syntax;
using NubLang.TypeChecking;
using NubLang.TypeChecking.Node;
namespace NubLang;
public static class TypeResolver
{
public static TypeNode ResolveType(TypeSyntax type, IReadOnlyDictionary<string, ModuleSignature> modules)
{
return type switch
{
BoolTypeSyntax => new BoolTypeNode(),
CStringTypeSyntax => new CStringTypeNode(),
IntTypeSyntax i => new IntTypeNode(i.Signed, i.Width),
CustomTypeSyntax c => ResolveCustomType(c.Module, c.Name, modules),
FloatTypeSyntax f => new FloatTypeNode(f.Width),
FuncTypeSyntax func => new FuncTypeNode(func.Parameters.Select(x => ResolveType(x, modules)).ToList(), ResolveType(func.ReturnType, modules)),
ArrayTypeSyntax arr => new ArrayTypeNode(ResolveType(arr.BaseType, modules)),
PointerTypeSyntax ptr => new PointerTypeNode(ResolveType(ptr.BaseType, modules)),
StringTypeSyntax => new StringTypeNode(),
VoidTypeSyntax => new VoidTypeNode(),
_ => throw new NotSupportedException($"Unknown type syntax: {type}")
};
}
public static TypeNode ResolveCustomType(string moduleName, string typeName, IReadOnlyDictionary<string, ModuleSignature> modules)
{
if (!modules.TryGetValue(moduleName, out var module))
{
throw new Exception("Module not found: " + moduleName);
}
var structType = module.StructTypes.FirstOrDefault(x => x.Name == typeName);
if (structType != null)
{
return structType;
}
var interfaceType = module.InterfaceTypes.FirstOrDefault(x => x.Name == typeName);
if (interfaceType != null)
{
return interfaceType;
}
throw new Exception($"Type {typeName} not found in module {moduleName}");
}
public static StructTypeNode ResolveStructType(string moduleName, string structName, IReadOnlyDictionary<string, ModuleSignature> modules)
{
if (!modules.TryGetValue(moduleName, out var module))
{
throw new Exception("Module not found: " + moduleName);
}
var structType = module.StructTypes.FirstOrDefault(x => x.Name == structName);
if (structType != null)
{
return structType;
}
throw new Exception($"Struct type {structName} not found in module {moduleName}");
}
public static InterfaceTypeNode ResolveInterfaceType(string moduleName, string structName, IReadOnlyDictionary<string, ModuleSignature> modules)
{
if (!modules.TryGetValue(moduleName, out var module))
{
throw new Exception("Module not found: " + moduleName);
}
var structType = module.InterfaceTypes.FirstOrDefault(x => x.Name == structName);
if (structType != null)
{
return structType;
}
throw new Exception($"Interface type {structName} not found in module {moduleName}");
}
}

View File

@@ -1,75 +0,0 @@
using System.Diagnostics.CodeAnalysis;
namespace NubLang;
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 bool IsCase1([NotNullWhen(true)] out T1? value)
{
if (_value is T1 converted)
{
value = converted;
return true;
}
value = default;
return false;
}
public bool IsCase2([NotNullWhen(true)] out T2? value)
{
if (_value is T2 converted)
{
value = converted;
return true;
}
value = default;
return false;
}
public static implicit operator Variant<T1, T2>(T1 value) => new(value);
public static implicit operator Variant<T1, T2>(T2 value) => new(value);
}

View File

@@ -1,17 +0,0 @@
IndentWidth: 4
# Pointer formatting
DerivePointerAlignment: false
PointerAlignment: Left
# Function formatting
AllowShortFunctionsOnASingleLine: None
# Control how short statements are placed
AllowShortBlocksOnASingleLine: Never
AllowShortIfStatementsOnASingleLine: Never
AllowShortLoopsOnASingleLine: false
SeparateDefinitionBlocks: Always
BreakBeforeBraces: Stroustrup

View File

@@ -1 +0,0 @@
build

View File

@@ -1,14 +0,0 @@
CC ?= x86_64-linux-gnu-gcc
TARGET ?= x64
CFLAGS = -Wall -Werror -Wextra -g
libruntime: build/runtime.o
$(CC) $(CFLAGS) -c targets/$(TARGET).s -o build/$(TARGET).o
build/runtime.o: runtime/runtime.c
mkdir -p build
$(CC) $(CFLAGS) -c runtime/runtime.c -o build/runtime.o
clean:
rm -r build

View File

@@ -1,47 +0,0 @@
#include <stdint.h>
typedef struct nub_string {
uint64_t size;
uint8_t buffer[];
} nub_string_t;
uint64_t nub_cstring_length(const char* string)
{
uint64_t len = 0;
while (string[len] != '\0') {
len++;
}
return len;
}
uint64_t nub_string_length(const nub_string_t* string)
{
uint64_t count = 0;
uint64_t i = 0;
while (i < string->size) {
uint8_t byte = string->buffer[i];
if ((byte & 0b11000000) != 0b10000000) {
count++;
}
i++;
}
return count;
}
void nub_memcpy(uint8_t* source, uint8_t* destination, uint64_t length)
{
for (uint64_t i = 0; i < length; i++) {
destination[i] = source[i];
}
}
void nub_memset(uint8_t* destination, uint8_t value, uint64_t count)
{
for (uint64_t i = 0; i < count; i++) {
destination[i] = value;
}
}

View File

@@ -1,10 +0,0 @@
.intel_syntax noprefix
.text
.globl _start
_start:
mov rdi, rsp
call main
mov rdi, rax
mov rax, 60
syscall

View File

@@ -1 +0,0 @@
bin

View File

@@ -1,29 +0,0 @@
{
"comments": {
"lineComment": "//",
"blockComment": ["/*", "*/"]
},
"brackets": [
["{", "}"],
["[", "]"],
["(", ")"]
],
"autoClosingPairs": [
["{", "}"],
["[", "]"],
["(", ")"],
["\"", "\""],
["'", "'"]
],
"surroundingPairs": [
["{", "}"],
["[", "]"],
["(", ")"],
["\"", "\""],
["'", "'"]
],
"indentationRules": {
"increaseIndentPattern": "^((?!\\/\\*).)*(\\{[^}\"'`]*|\\([^)\"'`]*|\\[[^\\]\"'`]*)$",
"decreaseIndentPattern": "^((?!.*?\\/\\*).*\\*/)?\\s*[\\}\\]\\)].*$"
}
}

View File

@@ -1,6 +0,0 @@
vscode:
mkdir -p bin
npx vsce package -o bin/nub-lang.vsix
clean:
rm -r bin

View File

@@ -1,40 +0,0 @@
{
"name": "nub-lang",
"displayName": "Nub Language Support",
"description": "Syntax highlighting for Nub programming language",
"version": "0.0.0",
"publisher": "nub31",
"repository": {
"type": "git",
"url": "https://git.oliste.no/nub31/nub-lang"
},
"license": "MIT",
"engines": {
"vscode": "^1.74.0"
},
"categories": [
"Programming Languages"
],
"contributes": {
"languages": [
{
"id": "nub",
"aliases": [
"Nub",
"nub"
],
"extensions": [
".nub"
],
"configuration": "./language-configuration.json"
}
],
"grammars": [
{
"language": "nub",
"scopeName": "source.nub",
"path": "./syntaxes/nub.tmLanguage.json"
}
]
}
}

View File

@@ -1,300 +0,0 @@
{
"$schema": "https://raw.githubusercontent.com/martinring/tmlanguage/master/tmlanguage.json",
"name": "Nub",
"scopeName": "source.nub",
"patterns": [
{
"include": "#comments"
},
{
"include": "#keywords"
},
{
"include": "#modifiers"
},
{
"include": "#types"
},
{
"include": "#strings"
},
{
"include": "#numbers"
},
{
"include": "#operators"
},
{
"include": "#function-definition"
},
{
"include": "#struct-definition"
},
{
"include": "#function-call"
},
{
"include": "#identifiers"
}
],
"repository": {
"comments": {
"patterns": [
{
"name": "comment.line.double-slash.nub",
"begin": "//",
"end": "$"
},
{
"name": "comment.block.nub",
"begin": "/\\*",
"end": "\\*/"
}
]
},
"keywords": {
"patterns": [
{
"name": "keyword.control.nub",
"match": "\\b(if|else|while|break|continue|return|let)\\b"
},
{
"name": "keyword.other.nub",
"match": "\\b(namespace|func|struct|interface)\\b"
}
]
},
"modifiers": {
"patterns": [
{
"name": "storage.modifier.nub",
"match": "\\b(export|extern|calls)\\b"
}
]
},
"types": {
"patterns": [
{
"include": "#function-type"
},
{
"name": "storage.type.primitive.nub",
"match": "\\b(i8|i16|i32|i64|u8|u16|u32|u64|f32|f64|bool|string|cstring|void|any)\\b"
},
{
"name": "storage.type.array.nub",
"match": "\\[\\]"
},
{
"name": "storage.type.pointer.nub",
"match": "\\^"
}
]
},
"function-type": {
"patterns": [
{
"begin": "\\b(func)\\s*\\(",
"beginCaptures": {
"1": {
"name": "storage.type.function.nub"
}
},
"end": "(?<=\\))(?:\\s*:\\s*([^\\s,;{}()]+))?",
"endCaptures": {
"1": {
"name": "storage.type.nub"
}
},
"patterns": [
{
"include": "#function-type-parameters"
}
]
}
]
},
"function-type-parameters": {
"patterns": [
{
"match": "\\.\\.\\.",
"name": "keyword.operator.variadic.nub"
},
{
"include": "#types"
},
{
"match": ",",
"name": "punctuation.separator.nub"
}
]
},
"strings": {
"patterns": [
{
"name": "string.quoted.double.nub",
"begin": "\"",
"end": "\"",
"patterns": [
{
"name": "constant.character.escape.nub",
"match": "\\\\(n|t|r|\\\\|\"|')"
},
{
"name": "constant.character.escape.nub",
"match": "\\\\[0-7]{1,3}"
},
{
"name": "constant.character.escape.nub",
"match": "\\\\x[0-9A-Fa-f]{1,2}"
}
]
},
{
"name": "string.quoted.single.nub",
"begin": "'",
"end": "'",
"patterns": [
{
"name": "constant.character.escape.nub",
"match": "\\\\(n|t|r|\\\\|\"|')"
}
]
}
]
},
"numbers": {
"patterns": [
{
"name": "constant.numeric.float.nub",
"match": "\\b\\d+\\.\\d*([eE][+-]?\\d+)?[fF]?\\b"
},
{
"name": "constant.numeric.integer.decimal.nub",
"match": "\\b\\d+\\b"
},
{
"name": "constant.numeric.integer.hexadecimal.nub",
"match": "\\b0[xX][0-9A-Fa-f]+\\b"
},
{
"name": "constant.numeric.integer.binary.nub",
"match": "\\b0[bB][01]+\\b"
}
]
},
"operators": {
"patterns": [
{
"name": "keyword.operator.assignment.nub",
"match": "="
},
{
"name": "keyword.operator.comparison.nub",
"match": "(==|!=|<=|>=|<|>)"
},
{
"name": "keyword.operator.arithmetic.nub",
"match": "(\\+|\\-|\\*|/)"
},
{
"name": "keyword.operator.logical.nub",
"match": "(&&|\\|\\||!)"
},
{
"name": "keyword.operator.address.nub",
"match": "&"
},
{
"name": "keyword.operator.dereference.nub",
"match": "\\^"
},
{
"name": "keyword.operator.member-access.nub",
"match": "\\."
}
]
},
"function-definition": {
"patterns": [
{
"begin": "\\b(global\\s+|extern\\s+)?(func)\\s+([a-zA-Z_][a-zA-Z0-9_]*)\\s*\\(",
"beginCaptures": {
"1": {
"name": "storage.modifier.nub"
},
"2": {
"name": "keyword.other.nub"
},
"3": {
"name": "entity.name.function.nub"
}
},
"end": "\\)",
"patterns": [
{
"include": "#function-parameters"
}
]
}
]
},
"struct-definition": {
"patterns": [
{
"match": "\\b(struct)\\s+([a-zA-Z_][a-zA-Z0-9_]*)\\b",
"captures": {
"1": {
"name": "keyword.other.nub"
},
"2": {
"name": "entity.name.type.struct.nub"
}
}
}
]
},
"function-parameters": {
"patterns": [
{
"match": "\\.\\.\\.",
"name": "keyword.operator.variadic.nub"
},
{
"match": "([a-zA-Z_][a-zA-Z0-9_]*)\\s*:\\s*",
"captures": {
"1": {
"name": "variable.parameter.nub"
}
}
},
{
"include": "#types"
},
{
"include": "#identifiers"
}
]
},
"function-call": {
"patterns": [
{
"match": "([a-zA-Z_][a-zA-Z0-9_]*)\\s*(?=\\()",
"captures": {
"1": {
"name": "entity.name.function.call.nub"
}
}
}
]
},
"identifiers": {
"patterns": [
{
"name": "variable.other.nub",
"match": "\\b[a-zA-Z_][a-zA-Z0-9_]*\\b"
}
]
}
}
}