...
This commit is contained in:
34
compiler/.gitignore
vendored
Normal file
34
compiler/.gitignore
vendored
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
# Common IntelliJ Platform excludes
|
||||||
|
|
||||||
|
# User specific
|
||||||
|
**/.idea/**/workspace.xml
|
||||||
|
**/.idea/**/tasks.xml
|
||||||
|
**/.idea/shelf/*
|
||||||
|
**/.idea/dictionaries
|
||||||
|
**/.idea/httpRequests/
|
||||||
|
|
||||||
|
# Sensitive or high-churn files
|
||||||
|
**/.idea/**/dataSources/
|
||||||
|
**/.idea/**/dataSources.ids
|
||||||
|
**/.idea/**/dataSources.xml
|
||||||
|
**/.idea/**/dataSources.local.xml
|
||||||
|
**/.idea/**/sqlDataSources.xml
|
||||||
|
**/.idea/**/dynamic.xml
|
||||||
|
|
||||||
|
# Rider
|
||||||
|
# Rider auto-generates .iml files, and contentModel.xml
|
||||||
|
**/.idea/**/*.iml
|
||||||
|
**/.idea/**/contentModel.xml
|
||||||
|
**/.idea/**/modules.xml
|
||||||
|
|
||||||
|
*.suo
|
||||||
|
*.user
|
||||||
|
.vs/
|
||||||
|
[Bb]in/
|
||||||
|
[Oo]bj/
|
||||||
|
_UpgradeReport_Files/
|
||||||
|
[Pp]ackages/
|
||||||
|
|
||||||
|
Thumbs.db
|
||||||
|
Desktop.ini
|
||||||
|
.DS_Store
|
||||||
13
compiler/.idea/.idea.Compiler/.idea/.gitignore
generated
vendored
Normal file
13
compiler/.idea/.idea.Compiler/.idea/.gitignore
generated
vendored
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
# Default ignored files
|
||||||
|
/shelf/
|
||||||
|
/workspace.xml
|
||||||
|
# Rider ignored files
|
||||||
|
/modules.xml
|
||||||
|
/projectSettingsUpdater.xml
|
||||||
|
/contentModel.xml
|
||||||
|
/.idea.Compiler.iml
|
||||||
|
# Editor-based HTTP Client requests
|
||||||
|
/httpRequests/
|
||||||
|
# Datasource local storage ignored files
|
||||||
|
/dataSources/
|
||||||
|
/dataSources.local.xml
|
||||||
1
compiler/.idea/.idea.Compiler/.idea/.name
generated
Normal file
1
compiler/.idea/.idea.Compiler/.idea/.name
generated
Normal file
@@ -0,0 +1 @@
|
|||||||
|
Compiler
|
||||||
4
compiler/.idea/.idea.Compiler/.idea/encodings.xml
generated
Normal file
4
compiler/.idea/.idea.Compiler/.idea/encodings.xml
generated
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="Encoding" addBOMForNewFiles="with BOM under Windows, with no BOM otherwise" />
|
||||||
|
</project>
|
||||||
10
compiler/.idea/.idea.Compiler/.idea/indexLayout.xml
generated
Normal file
10
compiler/.idea/.idea.Compiler/.idea/indexLayout.xml
generated
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="UserContentModel">
|
||||||
|
<attachedFolders>
|
||||||
|
<Path>Runtime</Path>
|
||||||
|
</attachedFolders>
|
||||||
|
<explicitIncludes />
|
||||||
|
<explicitExcludes />
|
||||||
|
</component>
|
||||||
|
</project>
|
||||||
6
compiler/.idea/.idea.Compiler/.idea/vcs.xml
generated
Normal file
6
compiler/.idea/.idea.Compiler/.idea/vcs.xml
generated
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="VcsDirectoryMappings">
|
||||||
|
<mapping directory="$PROJECT_DIR$/.." vcs="Git" />
|
||||||
|
</component>
|
||||||
|
</project>
|
||||||
22
compiler/Compiler.sln
Normal file
22
compiler/Compiler.sln
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
|
||||||
|
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||||
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NubLang", "NubLang\NubLang.csproj", "{5047E21F-590D-4CB3-AFF3-064316485009}"
|
||||||
|
EndProject
|
||||||
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NubLang.CLI", "NubLang.CLI\NubLang.CLI.csproj", "{A22F17ED-FA17-45AB-92BA-CD02C28B3524}"
|
||||||
|
EndProject
|
||||||
|
Global
|
||||||
|
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||||
|
Debug|Any CPU = Debug|Any CPU
|
||||||
|
Release|Any CPU = Release|Any CPU
|
||||||
|
EndGlobalSection
|
||||||
|
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||||
|
{5047E21F-590D-4CB3-AFF3-064316485009}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{5047E21F-590D-4CB3-AFF3-064316485009}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{5047E21F-590D-4CB3-AFF3-064316485009}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{5047E21F-590D-4CB3-AFF3-064316485009}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
{A22F17ED-FA17-45AB-92BA-CD02C28B3524}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{A22F17ED-FA17-45AB-92BA-CD02C28B3524}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{A22F17ED-FA17-45AB-92BA-CD02C28B3524}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{A22F17ED-FA17-45AB-92BA-CD02C28B3524}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
EndGlobalSection
|
||||||
|
EndGlobal
|
||||||
29
compiler/NubLang.CLI/Archive.cs
Normal file
29
compiler/NubLang.CLI/Archive.cs
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
30
compiler/NubLang.CLI/GCC.cs
Normal file
30
compiler/NubLang.CLI/GCC.cs
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
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("x86_64-elf-as", ["-nostartfiles", "-o", outPath, asmPath])
|
||||||
|
{
|
||||||
|
UseShellExecute = false,
|
||||||
|
RedirectStandardOutput = true,
|
||||||
|
RedirectStandardError = true,
|
||||||
|
CreateNoWindow = true
|
||||||
|
};
|
||||||
|
|
||||||
|
process.Start();
|
||||||
|
|
||||||
|
await process.WaitForExitAsync();
|
||||||
|
|
||||||
|
var errors = await process.StandardError.ReadToEndAsync();
|
||||||
|
if (!string.IsNullOrWhiteSpace(errors))
|
||||||
|
{
|
||||||
|
await Console.Error.WriteLineAsync("gcc error when assembling:\n" + errors);
|
||||||
|
}
|
||||||
|
|
||||||
|
return process.ExitCode == 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
16
compiler/NubLang.CLI/NubLang.CLI.csproj
Normal file
16
compiler/NubLang.CLI/NubLang.CLI.csproj
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
<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>
|
||||||
|
|
||||||
|
</Project>
|
||||||
9
compiler/NubLang.CLI/Options.cs
Normal file
9
compiler/NubLang.CLI/Options.cs
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
using NubLang.Code;
|
||||||
|
|
||||||
|
namespace NubLang.CLI;
|
||||||
|
|
||||||
|
public class Options
|
||||||
|
{
|
||||||
|
public string? OutputPath { get; set; }
|
||||||
|
public List<SourceFile> Files { get; } = [];
|
||||||
|
}
|
||||||
138
compiler/NubLang.CLI/Program.cs
Normal file
138
compiler/NubLang.CLI/Program.cs
Normal file
@@ -0,0 +1,138 @@
|
|||||||
|
using System.Reflection;
|
||||||
|
using NubLang.CLI;
|
||||||
|
using NubLang.Code;
|
||||||
|
using NubLang.Diagnostics;
|
||||||
|
using NubLang.Generation.QBE;
|
||||||
|
using NubLang.Parsing;
|
||||||
|
using NubLang.Parsing.Syntax;
|
||||||
|
using NubLang.Tokenization;
|
||||||
|
using NubLang.TypeChecking;
|
||||||
|
using Module = NubLang.TypeChecking.Module;
|
||||||
|
|
||||||
|
var options = new Options();
|
||||||
|
|
||||||
|
for (var i = 0; i < args.Length; i++)
|
||||||
|
{
|
||||||
|
var arg = args[i];
|
||||||
|
switch (arg)
|
||||||
|
{
|
||||||
|
case "-o":
|
||||||
|
{
|
||||||
|
++i;
|
||||||
|
if (i >= args.Length)
|
||||||
|
{
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
options.OutputPath = args[i];
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
{
|
||||||
|
options.Files.Add(new SourceFile(arg));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var file in options.Files)
|
||||||
|
{
|
||||||
|
if (!File.Exists(file.Path))
|
||||||
|
{
|
||||||
|
Console.Error.WriteLine($"File '{file}' does not exist");
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var diagnostics = new List<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", Path.ChangeExtension(options.Files[i].Path, null));
|
||||||
|
|
||||||
|
var outFileDir = Path.GetDirectoryName(outFileName);
|
||||||
|
if (!string.IsNullOrEmpty(outFileDir))
|
||||||
|
{
|
||||||
|
Directory.CreateDirectory(outFileDir);
|
||||||
|
}
|
||||||
|
|
||||||
|
var generator = new QBEGenerator(typedModule, moduleSignatures);
|
||||||
|
var ssa = generator.Emit();
|
||||||
|
|
||||||
|
var ssaFilePath = Path.ChangeExtension(outFileName, "ssa");
|
||||||
|
File.WriteAllText(ssaFilePath, ssa);
|
||||||
|
|
||||||
|
var asmFilePath = Path.ChangeExtension(outFileName, "s");
|
||||||
|
var qbeSuccess = await QBE.Invoke(ssaFilePath, asmFilePath);
|
||||||
|
|
||||||
|
if (!qbeSuccess)
|
||||||
|
{
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
var objFilePath = Path.ChangeExtension(outFileName, "o");
|
||||||
|
var asmSuccess = await GCC.Assemble(asmFilePath, objFilePath);
|
||||||
|
|
||||||
|
if (!asmSuccess)
|
||||||
|
{
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
objectFiles.Add(objFilePath);
|
||||||
|
}
|
||||||
|
|
||||||
|
var outPath = options.OutputPath ?? Path.Combine("out.a");
|
||||||
|
var outDir = Path.GetDirectoryName(outPath);
|
||||||
|
if (!string.IsNullOrEmpty(outDir))
|
||||||
|
{
|
||||||
|
Directory.CreateDirectory(outDir);
|
||||||
|
}
|
||||||
|
|
||||||
|
var archiveResult = await Archive.Invoke(outPath, objectFiles);
|
||||||
|
if (!archiveResult)
|
||||||
|
{
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
30
compiler/NubLang.CLI/QBE.cs
Normal file
30
compiler/NubLang.CLI/QBE.cs
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
41
compiler/NubLang/Code/SourceFile.cs
Normal file
41
compiler/NubLang/Code/SourceFile.cs
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
namespace NubLang.Code;
|
||||||
|
|
||||||
|
public class SourceFile
|
||||||
|
{
|
||||||
|
private string? _content;
|
||||||
|
|
||||||
|
public SourceFile(string path)
|
||||||
|
{
|
||||||
|
Path = path ?? throw new ArgumentNullException(nameof(path));
|
||||||
|
}
|
||||||
|
|
||||||
|
public string Path { get; }
|
||||||
|
|
||||||
|
public string GetText() => _content ??= File.ReadAllText(Path);
|
||||||
|
public override string ToString() => Path;
|
||||||
|
|
||||||
|
public override bool Equals(object? obj)
|
||||||
|
{
|
||||||
|
return obj is SourceFile other && other.Path == Path;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override int GetHashCode()
|
||||||
|
{
|
||||||
|
return HashCode.Combine(typeof(SourceFile), Path);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static bool operator ==(SourceFile? left, SourceFile? right) => Equals(left, right);
|
||||||
|
public static bool operator !=(SourceFile? left, SourceFile? right) => !Equals(left, right);
|
||||||
|
}
|
||||||
|
|
||||||
|
public class SourceFileSpan
|
||||||
|
{
|
||||||
|
public SourceFileSpan(SourceFile sourceFile, SourceSpan span)
|
||||||
|
{
|
||||||
|
SourceFile = sourceFile;
|
||||||
|
Span = span;
|
||||||
|
}
|
||||||
|
|
||||||
|
public SourceFile SourceFile { get; }
|
||||||
|
public SourceSpan Span { get; }
|
||||||
|
}
|
||||||
42
compiler/NubLang/Code/SourceLocation.cs
Normal file
42
compiler/NubLang/Code/SourceLocation.cs
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
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);
|
||||||
|
}
|
||||||
57
compiler/NubLang/Code/SourceSpan.cs
Normal file
57
compiler/NubLang/Code/SourceSpan.cs
Normal file
@@ -0,0 +1,57 @@
|
|||||||
|
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);
|
||||||
|
}
|
||||||
56
compiler/NubLang/Diagnostics/ConsoleColors.cs
Normal file
56
compiler/NubLang/Diagnostics/ConsoleColors.cs
Normal file
@@ -0,0 +1,56 @@
|
|||||||
|
namespace NubLang.Diagnostics;
|
||||||
|
|
||||||
|
public static class ConsoleColors
|
||||||
|
{
|
||||||
|
public const string Reset = "\e[0m";
|
||||||
|
public const string Bold = "\e[1m";
|
||||||
|
public const string Faint = "\e[2m";
|
||||||
|
public const string Italic = "\e[3m";
|
||||||
|
public const string Underline = "\e[4m";
|
||||||
|
public const string SlowBlink = "\e[5m";
|
||||||
|
public const string RapidBlink = "\e[6m";
|
||||||
|
public const string SwapBgAndFg = "\e[7m";
|
||||||
|
public const string Conceal = "\e[8m";
|
||||||
|
public const string CrossedOut = "\e[9m";
|
||||||
|
|
||||||
|
public const string DefaultFont = "\e[10m";
|
||||||
|
public const string AltFont1 = "\e[11m";
|
||||||
|
public const string AltFont2 = "\e[12m";
|
||||||
|
public const string AltFont3 = "\e[13m";
|
||||||
|
public const string AltFont4 = "\e[14m";
|
||||||
|
public const string AltFont5 = "\e[15m";
|
||||||
|
public const string AltFont6 = "\e[16m";
|
||||||
|
public const string AltFont7 = "\e[17m";
|
||||||
|
public const string AltFont8 = "\e[18m";
|
||||||
|
public const string AltFont9 = "\e[19m";
|
||||||
|
|
||||||
|
public const string Black = "\e[30m";
|
||||||
|
public const string Red = "\e[31m";
|
||||||
|
public const string Green = "\e[32m";
|
||||||
|
public const string Yellow = "\e[33m";
|
||||||
|
public const string Blue = "\e[34m";
|
||||||
|
public const string Magenta = "\e[35m";
|
||||||
|
public const string Cyan = "\e[36m";
|
||||||
|
public const string White = "\e[37m";
|
||||||
|
|
||||||
|
public const string BrightBlack = "\e[90m";
|
||||||
|
public const string BrightRed = "\e[91m";
|
||||||
|
public const string BrightGreen = "\e[92m";
|
||||||
|
public const string BrightYellow = "\e[93m";
|
||||||
|
public const string BrightBlue = "\e[94m";
|
||||||
|
public const string BrightMagenta = "\e[95m";
|
||||||
|
public const string BrightCyan = "\e[96m";
|
||||||
|
public const string BrightWhite = "\e[97m";
|
||||||
|
|
||||||
|
private static bool IsColorSupported()
|
||||||
|
{
|
||||||
|
var term = Environment.GetEnvironmentVariable("TERM");
|
||||||
|
var colorTerm = Environment.GetEnvironmentVariable("COLORTERM");
|
||||||
|
return !string.IsNullOrEmpty(term) || !string.IsNullOrEmpty(colorTerm) || !Console.IsOutputRedirected;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static string Colorize(string text, string color)
|
||||||
|
{
|
||||||
|
return IsColorSupported() ? $"{color}{text}{Reset}" : text;
|
||||||
|
}
|
||||||
|
}
|
||||||
325
compiler/NubLang/Diagnostics/Diagnostic.cs
Normal file
325
compiler/NubLang/Diagnostics/Diagnostic.cs
Normal file
@@ -0,0 +1,325 @@
|
|||||||
|
using System.Text;
|
||||||
|
using NubLang.Code;
|
||||||
|
using NubLang.Parsing.Syntax;
|
||||||
|
using NubLang.Tokenization;
|
||||||
|
|
||||||
|
namespace NubLang.Diagnostics;
|
||||||
|
|
||||||
|
public class Diagnostic
|
||||||
|
{
|
||||||
|
public class DiagnosticBuilder
|
||||||
|
{
|
||||||
|
private readonly DiagnosticSeverity _severity;
|
||||||
|
private readonly string _message;
|
||||||
|
private SourceFileSpan? _fileSpan;
|
||||||
|
private string? _help;
|
||||||
|
|
||||||
|
public DiagnosticBuilder(DiagnosticSeverity severity, string message)
|
||||||
|
{
|
||||||
|
_severity = severity;
|
||||||
|
_message = message;
|
||||||
|
}
|
||||||
|
|
||||||
|
public DiagnosticBuilder At(SyntaxNode? node)
|
||||||
|
{
|
||||||
|
if (node != null)
|
||||||
|
{
|
||||||
|
var first = node.Tokens.FirstOrDefault();
|
||||||
|
if (first?.FileSpan != null)
|
||||||
|
{
|
||||||
|
var span = SourceSpan.Merge(node.Tokens.Select(x => x.FileSpan?.Span ?? SourceSpan.Zero));
|
||||||
|
At(new SourceFileSpan(first.FileSpan.SourceFile, span));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public DiagnosticBuilder At(Token? token)
|
||||||
|
{
|
||||||
|
if (token != null)
|
||||||
|
{
|
||||||
|
At(token.FileSpan);
|
||||||
|
}
|
||||||
|
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public DiagnosticBuilder At(SourceFileSpan? fileSpan)
|
||||||
|
{
|
||||||
|
if (fileSpan != null)
|
||||||
|
{
|
||||||
|
_fileSpan = fileSpan;
|
||||||
|
}
|
||||||
|
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public DiagnosticBuilder WithHelp(string help)
|
||||||
|
{
|
||||||
|
_help = help;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Diagnostic Build() => new(_severity, _message, _help, _fileSpan);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static DiagnosticBuilder Error(string message) => new(DiagnosticSeverity.Error, message);
|
||||||
|
public static DiagnosticBuilder Warning(string message) => new(DiagnosticSeverity.Warning, message);
|
||||||
|
public static DiagnosticBuilder Info(string message) => new(DiagnosticSeverity.Info, message);
|
||||||
|
|
||||||
|
public DiagnosticSeverity Severity { get; }
|
||||||
|
public string Message { get; }
|
||||||
|
public string? Help { get; }
|
||||||
|
public SourceFileSpan? FileSpan { get; }
|
||||||
|
|
||||||
|
private Diagnostic(DiagnosticSeverity severity, string message, string? help, SourceFileSpan? fileSpan)
|
||||||
|
{
|
||||||
|
Severity = severity;
|
||||||
|
Message = message;
|
||||||
|
Help = help;
|
||||||
|
FileSpan = fileSpan;
|
||||||
|
}
|
||||||
|
|
||||||
|
public string FormatANSI()
|
||||||
|
{
|
||||||
|
var sb = new StringBuilder();
|
||||||
|
|
||||||
|
sb.Append(Severity switch
|
||||||
|
{
|
||||||
|
DiagnosticSeverity.Error => ConsoleColors.Colorize("error", ConsoleColors.Bold + ConsoleColors.Red),
|
||||||
|
DiagnosticSeverity.Warning => ConsoleColors.Colorize("warning", ConsoleColors.Bold + ConsoleColors.Yellow),
|
||||||
|
DiagnosticSeverity.Info => ConsoleColors.Colorize("info", ConsoleColors.Bold + ConsoleColors.Blue),
|
||||||
|
_ => ConsoleColors.Colorize("unknown", ConsoleColors.Bold + ConsoleColors.White)
|
||||||
|
});
|
||||||
|
|
||||||
|
if (FileSpan != null)
|
||||||
|
{
|
||||||
|
sb.Append(ConsoleColors.Colorize($" at {FileSpan.SourceFile.Path}:{FileSpan.Span}", ConsoleColors.Faint));
|
||||||
|
}
|
||||||
|
|
||||||
|
sb.Append(": ");
|
||||||
|
sb.Append(ConsoleColors.Colorize(Message, ConsoleColors.BrightWhite));
|
||||||
|
|
||||||
|
if (FileSpan != null)
|
||||||
|
{
|
||||||
|
sb.AppendLine();
|
||||||
|
var text = FileSpan.SourceFile.GetText();
|
||||||
|
|
||||||
|
var lines = text.Split('\n');
|
||||||
|
|
||||||
|
var startLine = FileSpan.Span.Start.Line;
|
||||||
|
var endLine = FileSpan.Span.End.Line;
|
||||||
|
|
||||||
|
const int CONTEXT_LINES = 3;
|
||||||
|
|
||||||
|
var contextStartLine = Math.Max(1, startLine - CONTEXT_LINES);
|
||||||
|
var contextEndLine = Math.Min(lines.Length, endLine + CONTEXT_LINES);
|
||||||
|
|
||||||
|
var numberPadding = contextEndLine.ToString().Length;
|
||||||
|
var codePadding = lines.Skip(contextStartLine - 1).Take(contextEndLine - contextStartLine).Max(x => x.Length);
|
||||||
|
|
||||||
|
sb.Append('╭');
|
||||||
|
sb.Append(new string('─', numberPadding + 2));
|
||||||
|
sb.Append('┬');
|
||||||
|
sb.Append(new string('─', codePadding + 2));
|
||||||
|
sb.Append('╮');
|
||||||
|
sb.AppendLine();
|
||||||
|
|
||||||
|
var tokenizer = new Tokenizer(FileSpan.SourceFile);
|
||||||
|
var tokens = tokenizer.Tokenize().ToList();
|
||||||
|
|
||||||
|
for (var i = contextStartLine; i <= contextEndLine; i++)
|
||||||
|
{
|
||||||
|
var line = lines[i - 1];
|
||||||
|
|
||||||
|
sb.Append("│ ");
|
||||||
|
sb.Append(i.ToString().PadRight(numberPadding));
|
||||||
|
sb.Append(" │ ");
|
||||||
|
sb.Append(ApplySyntaxHighlighting(line.PadRight(codePadding), i, tokens));
|
||||||
|
sb.Append(" │");
|
||||||
|
sb.AppendLine();
|
||||||
|
|
||||||
|
if (i >= startLine && i <= endLine)
|
||||||
|
{
|
||||||
|
var markerStartColumn = 1;
|
||||||
|
var markerEndColumn = line.Length + 1;
|
||||||
|
|
||||||
|
if (i == startLine)
|
||||||
|
{
|
||||||
|
markerStartColumn = FileSpan.Span.Start.Column;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (i == endLine)
|
||||||
|
{
|
||||||
|
markerEndColumn = FileSpan.Span.End.Column;
|
||||||
|
}
|
||||||
|
|
||||||
|
var markerLength = markerEndColumn - markerStartColumn;
|
||||||
|
var marker = new string('^', markerLength);
|
||||||
|
|
||||||
|
var markerColor = Severity switch
|
||||||
|
{
|
||||||
|
DiagnosticSeverity.Info => ConsoleColors.Blue,
|
||||||
|
DiagnosticSeverity.Warning => ConsoleColors.Yellow,
|
||||||
|
DiagnosticSeverity.Error => ConsoleColors.Red,
|
||||||
|
_ => ConsoleColors.White
|
||||||
|
};
|
||||||
|
|
||||||
|
sb.Append("│ ");
|
||||||
|
sb.Append(new string(' ', numberPadding));
|
||||||
|
sb.Append(" │ ");
|
||||||
|
sb.Append(new string(' ', markerStartColumn - 1));
|
||||||
|
sb.Append(ConsoleColors.Colorize(marker, markerColor));
|
||||||
|
sb.Append(new string(' ', codePadding - markerEndColumn + 1));
|
||||||
|
sb.Append(" │");
|
||||||
|
sb.AppendLine();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
sb.Append('╰');
|
||||||
|
sb.Append(new string('─', numberPadding + 2));
|
||||||
|
sb.Append('┴');
|
||||||
|
sb.Append(new string('─', codePadding + 2));
|
||||||
|
sb.Append('╯');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Help != null)
|
||||||
|
{
|
||||||
|
sb.AppendLine();
|
||||||
|
sb.Append(ConsoleColors.Colorize($"help: {Help}", ConsoleColors.Cyan));
|
||||||
|
}
|
||||||
|
|
||||||
|
return sb.ToString();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string ApplySyntaxHighlighting(string line, int lineNumber, List<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
|
||||||
|
}
|
||||||
1439
compiler/NubLang/Generation/QBE/QBEGenerator.cs
Normal file
1439
compiler/NubLang/Generation/QBE/QBEGenerator.cs
Normal file
File diff suppressed because it is too large
Load Diff
39
compiler/NubLang/Generation/QBE/QBEWriter.cs
Normal file
39
compiler/NubLang/Generation/QBE/QBEWriter.cs
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
using System.Text;
|
||||||
|
|
||||||
|
namespace NubLang.Generation.QBE;
|
||||||
|
|
||||||
|
internal class QBEWriter
|
||||||
|
{
|
||||||
|
private readonly StringBuilder _builder = new();
|
||||||
|
|
||||||
|
public void Indented(string value)
|
||||||
|
{
|
||||||
|
_builder.Append('\t');
|
||||||
|
_builder.AppendLine(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Comment(string comment)
|
||||||
|
{
|
||||||
|
_builder.AppendLine("# " + comment);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void WriteLine(string text)
|
||||||
|
{
|
||||||
|
_builder.AppendLine(text);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Write(string text)
|
||||||
|
{
|
||||||
|
_builder.Append(text);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void NewLine()
|
||||||
|
{
|
||||||
|
_builder.AppendLine();
|
||||||
|
}
|
||||||
|
|
||||||
|
public override string ToString()
|
||||||
|
{
|
||||||
|
return _builder.ToString();
|
||||||
|
}
|
||||||
|
}
|
||||||
10
compiler/NubLang/NubLang.csproj
Normal file
10
compiler/NubLang/NubLang.csproj
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<TargetFramework>net9.0</TargetFramework>
|
||||||
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
|
<Nullable>enable</Nullable>
|
||||||
|
<IsAotCompatible>true</IsAotCompatible>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
</Project>
|
||||||
82
compiler/NubLang/Optional.cs
Normal file
82
compiler/NubLang/Optional.cs
Normal file
@@ -0,0 +1,82 @@
|
|||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
883
compiler/NubLang/Parsing/Parser.cs
Normal file
883
compiler/NubLang/Parsing/Parser.cs
Normal file
@@ -0,0 +1,883 @@
|
|||||||
|
using System.Diagnostics.CodeAnalysis;
|
||||||
|
using NubLang.Diagnostics;
|
||||||
|
using NubLang.Parsing.Syntax;
|
||||||
|
using NubLang.Tokenization;
|
||||||
|
|
||||||
|
namespace NubLang.Parsing;
|
||||||
|
|
||||||
|
public sealed class Parser
|
||||||
|
{
|
||||||
|
private readonly List<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 = ExpectLiteral(LiteralKind.String).Value;
|
||||||
|
|
||||||
|
while (TryExpectSymbol(Symbol.Import))
|
||||||
|
{
|
||||||
|
imports.Add(ExpectIdentifier().Value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (ParseException e)
|
||||||
|
{
|
||||||
|
_diagnostics.Add(e.Diagnostic);
|
||||||
|
while (HasToken)
|
||||||
|
{
|
||||||
|
if (CurrentToken is SymbolToken { Symbol: Symbol.Module or Symbol.Import })
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
Next();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return new SyntaxTreeMetadata(_moduleName, imports);
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<DefinitionSyntax> ParseDefinitions()
|
||||||
|
{
|
||||||
|
var definitions = new List<DefinitionSyntax>();
|
||||||
|
|
||||||
|
while (HasToken)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var startIndex = _tokenIndex;
|
||||||
|
var exported = TryExpectSymbol(Symbol.Export);
|
||||||
|
|
||||||
|
if (TryExpectSymbol(Symbol.Extern))
|
||||||
|
{
|
||||||
|
var externSymbol = ExpectLiteral(LiteralKind.String).Value;
|
||||||
|
ExpectSymbol(Symbol.Func);
|
||||||
|
definitions.Add(ParseFunc(startIndex, exported, externSymbol));
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
var keyword = ExpectSymbol();
|
||||||
|
var definition = keyword.Symbol switch
|
||||||
|
{
|
||||||
|
Symbol.Func => ParseFunc(startIndex, exported, null),
|
||||||
|
Symbol.Struct => ParseStruct(startIndex, exported),
|
||||||
|
Symbol.Interface => ParseInterface(startIndex, exported),
|
||||||
|
_ => throw new ParseException(Diagnostic
|
||||||
|
.Error($"Expected 'func', 'struct' or 'interface' but found '{keyword.Symbol}'")
|
||||||
|
.WithHelp("Valid definition keywords are 'func', 'struct' and 'interface'")
|
||||||
|
.At(keyword)
|
||||||
|
.Build())
|
||||||
|
};
|
||||||
|
|
||||||
|
definitions.Add(definition);
|
||||||
|
}
|
||||||
|
catch (ParseException e)
|
||||||
|
{
|
||||||
|
_diagnostics.Add(e.Diagnostic);
|
||||||
|
while (HasToken)
|
||||||
|
{
|
||||||
|
if (CurrentToken is SymbolToken { Symbol: Symbol.Extern or Symbol.Func or Symbol.Struct or Symbol.Interface })
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
Next();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return definitions;
|
||||||
|
}
|
||||||
|
|
||||||
|
private FuncSignatureSyntax ParseFuncSignature()
|
||||||
|
{
|
||||||
|
var startIndex = _tokenIndex;
|
||||||
|
List<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 FuncSyntax ParseFunc(int startIndex, bool exported, string? externSymbol)
|
||||||
|
{
|
||||||
|
var name = ExpectIdentifier();
|
||||||
|
var signature = ParseFuncSignature();
|
||||||
|
|
||||||
|
BlockSyntax? body = null;
|
||||||
|
if (CurrentToken is SymbolToken { Symbol: Symbol.OpenBrace })
|
||||||
|
{
|
||||||
|
Next();
|
||||||
|
body = ParseBlock();
|
||||||
|
}
|
||||||
|
|
||||||
|
return new FuncSyntax(GetTokens(startIndex), name.Value, exported, externSymbol, signature, body);
|
||||||
|
}
|
||||||
|
|
||||||
|
private DefinitionSyntax ParseStruct(int startIndex, bool exported)
|
||||||
|
{
|
||||||
|
var name = ExpectIdentifier();
|
||||||
|
var interfaceImplementations = new List<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, exported, fields, funcs, interfaceImplementations);
|
||||||
|
}
|
||||||
|
|
||||||
|
private InterfaceSyntax ParseInterface(int startIndex, bool exported)
|
||||||
|
{
|
||||||
|
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, exported, functions);
|
||||||
|
}
|
||||||
|
|
||||||
|
private StatementSyntax ParseStatement()
|
||||||
|
{
|
||||||
|
if (CurrentToken is SymbolToken symbol)
|
||||||
|
{
|
||||||
|
switch (symbol.Symbol)
|
||||||
|
{
|
||||||
|
case Symbol.Return:
|
||||||
|
return ParseReturn();
|
||||||
|
case Symbol.If:
|
||||||
|
return ParseIf();
|
||||||
|
case Symbol.While:
|
||||||
|
return ParseWhile();
|
||||||
|
case Symbol.Let:
|
||||||
|
return ParseVariableDeclaration();
|
||||||
|
case Symbol.Break:
|
||||||
|
return ParseBreak();
|
||||||
|
case Symbol.Continue:
|
||||||
|
return ParseContinue();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return ParseStatementExpression();
|
||||||
|
}
|
||||||
|
|
||||||
|
private StatementSyntax ParseStatementExpression()
|
||||||
|
{
|
||||||
|
var startIndex = _tokenIndex;
|
||||||
|
var expr = ParseExpression();
|
||||||
|
|
||||||
|
if (TryExpectSymbol(Symbol.Assign))
|
||||||
|
{
|
||||||
|
var value = ParseExpression();
|
||||||
|
return new AssignmentSyntax(GetTokens(startIndex), expr, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
return new StatementExpressionSyntax(GetTokens(startIndex), expr);
|
||||||
|
}
|
||||||
|
|
||||||
|
private VariableDeclarationSyntax ParseVariableDeclaration()
|
||||||
|
{
|
||||||
|
var startIndex = _tokenIndex;
|
||||||
|
ExpectSymbol(Symbol.Let);
|
||||||
|
var name = ExpectIdentifier().Value;
|
||||||
|
|
||||||
|
var explicitType = Optional<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;
|
||||||
|
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 LiteralToken ExpectLiteral()
|
||||||
|
{
|
||||||
|
var token = ExpectToken();
|
||||||
|
if (token is not LiteralToken identifier)
|
||||||
|
{
|
||||||
|
throw new ParseException(Diagnostic
|
||||||
|
.Error($"Expected literal, but found {token.GetType().Name}")
|
||||||
|
.WithHelp("Provide a valid literal name here")
|
||||||
|
.At(token)
|
||||||
|
.Build());
|
||||||
|
}
|
||||||
|
|
||||||
|
return identifier;
|
||||||
|
}
|
||||||
|
|
||||||
|
private LiteralToken ExpectLiteral(LiteralKind kind)
|
||||||
|
{
|
||||||
|
var literal = ExpectLiteral();
|
||||||
|
if (literal.Kind != kind)
|
||||||
|
{
|
||||||
|
throw new ParseException(Diagnostic
|
||||||
|
.Error($"Expected {kind} literal, but found {literal.Kind}")
|
||||||
|
.WithHelp($"Provide a {kind} literal name here")
|
||||||
|
.At(literal)
|
||||||
|
.Build());
|
||||||
|
}
|
||||||
|
|
||||||
|
return literal;
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool TryExpectLiteral(LiteralKind kind, [NotNullWhen(true)] out LiteralToken? literal)
|
||||||
|
{
|
||||||
|
if (CurrentToken is LiteralToken identifierToken)
|
||||||
|
{
|
||||||
|
literal = identifierToken;
|
||||||
|
Next();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
literal = null;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void Next()
|
||||||
|
{
|
||||||
|
_tokenIndex++;
|
||||||
|
}
|
||||||
|
|
||||||
|
private IEnumerable<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;
|
||||||
|
}
|
||||||
|
}
|
||||||
21
compiler/NubLang/Parsing/Syntax/DefinitionSyntax.cs
Normal file
21
compiler/NubLang/Parsing/Syntax/DefinitionSyntax.cs
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
using NubLang.Tokenization;
|
||||||
|
|
||||||
|
namespace NubLang.Parsing.Syntax;
|
||||||
|
|
||||||
|
public abstract record DefinitionSyntax(IEnumerable<Token> Tokens, string Name, bool Exported) : 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, bool Exported, string? ExternSymbol, FuncSignatureSyntax Signature, BlockSyntax? Body) : DefinitionSyntax(Tokens, Name, Exported);
|
||||||
|
|
||||||
|
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, bool Exported, IReadOnlyList<StructFieldSyntax> Fields, IReadOnlyList<StructFuncSyntax> Functions, IReadOnlyList<TypeSyntax> InterfaceImplementations) : DefinitionSyntax(Tokens, Name, Exported);
|
||||||
|
|
||||||
|
public record InterfaceFuncSyntax(IEnumerable<Token> Tokens, string Name, FuncSignatureSyntax Signature) : SyntaxNode(Tokens);
|
||||||
|
|
||||||
|
public record InterfaceSyntax(IEnumerable<Token> Tokens, string Name, bool Exported, IReadOnlyList<InterfaceFuncSyntax> Functions) : DefinitionSyntax(Tokens, Name, Exported);
|
||||||
57
compiler/NubLang/Parsing/Syntax/ExpressionSyntax.cs
Normal file
57
compiler/NubLang/Parsing/Syntax/ExpressionSyntax.cs
Normal file
@@ -0,0 +1,57 @@
|
|||||||
|
using NubLang.Tokenization;
|
||||||
|
|
||||||
|
namespace NubLang.Parsing.Syntax;
|
||||||
|
|
||||||
|
public enum UnaryOperatorSyntax
|
||||||
|
{
|
||||||
|
Negate,
|
||||||
|
Invert
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum BinaryOperatorSyntax
|
||||||
|
{
|
||||||
|
Equal,
|
||||||
|
NotEqual,
|
||||||
|
GreaterThan,
|
||||||
|
GreaterThanOrEqual,
|
||||||
|
LessThan,
|
||||||
|
LessThanOrEqual,
|
||||||
|
LogicalAnd,
|
||||||
|
LogicalOr,
|
||||||
|
Plus,
|
||||||
|
Minus,
|
||||||
|
Multiply,
|
||||||
|
Divide,
|
||||||
|
Modulo,
|
||||||
|
LeftShift,
|
||||||
|
RightShift,
|
||||||
|
BitwiseAnd,
|
||||||
|
BitwiseXor,
|
||||||
|
BitwiseOr,
|
||||||
|
}
|
||||||
|
|
||||||
|
public abstract record ExpressionSyntax(IEnumerable<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);
|
||||||
21
compiler/NubLang/Parsing/Syntax/StatementSyntax.cs
Normal file
21
compiler/NubLang/Parsing/Syntax/StatementSyntax.cs
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
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);
|
||||||
11
compiler/NubLang/Parsing/Syntax/SyntaxNode.cs
Normal file
11
compiler/NubLang/Parsing/Syntax/SyntaxNode.cs
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
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);
|
||||||
25
compiler/NubLang/Parsing/Syntax/TypeSyntax.cs
Normal file
25
compiler/NubLang/Parsing/Syntax/TypeSyntax.cs
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
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);
|
||||||
82
compiler/NubLang/Tokenization/Token.cs
Normal file
82
compiler/NubLang/Tokenization/Token.cs
Normal file
@@ -0,0 +1,82 @@
|
|||||||
|
using NubLang.Code;
|
||||||
|
|
||||||
|
namespace NubLang.Tokenization;
|
||||||
|
|
||||||
|
public abstract class Token(SourceFileSpan fileSpan)
|
||||||
|
{
|
||||||
|
public SourceFileSpan FileSpan { get; } = fileSpan;
|
||||||
|
}
|
||||||
|
|
||||||
|
public class IdentifierToken(SourceFileSpan fileSpan, string value) : Token(fileSpan)
|
||||||
|
{
|
||||||
|
public string Value { get; } = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public class LiteralToken(SourceFileSpan fileSpan, LiteralKind kind, string value) : Token(fileSpan)
|
||||||
|
{
|
||||||
|
public LiteralKind Kind { get; } = kind;
|
||||||
|
public string Value { get; } = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum LiteralKind
|
||||||
|
{
|
||||||
|
Integer,
|
||||||
|
Float,
|
||||||
|
String,
|
||||||
|
Bool
|
||||||
|
}
|
||||||
|
|
||||||
|
public class SymbolToken(SourceFileSpan fileSpan, Symbol symbol) : Token(fileSpan)
|
||||||
|
{
|
||||||
|
public Symbol Symbol { get; } = symbol;
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum Symbol
|
||||||
|
{
|
||||||
|
Func,
|
||||||
|
Return,
|
||||||
|
If,
|
||||||
|
Else,
|
||||||
|
While,
|
||||||
|
Break,
|
||||||
|
Continue,
|
||||||
|
Colon,
|
||||||
|
OpenParen,
|
||||||
|
CloseParen,
|
||||||
|
OpenBrace,
|
||||||
|
CloseBrace,
|
||||||
|
OpenBracket,
|
||||||
|
CloseBracket,
|
||||||
|
Comma,
|
||||||
|
Period,
|
||||||
|
Assign,
|
||||||
|
Bang,
|
||||||
|
Equal,
|
||||||
|
NotEqual,
|
||||||
|
LessThan,
|
||||||
|
LessThanOrEqual,
|
||||||
|
GreaterThan,
|
||||||
|
GreaterThanOrEqual,
|
||||||
|
Plus,
|
||||||
|
Minus,
|
||||||
|
Star,
|
||||||
|
ForwardSlash,
|
||||||
|
Struct,
|
||||||
|
Caret,
|
||||||
|
Ampersand,
|
||||||
|
Let,
|
||||||
|
Calls,
|
||||||
|
Interface,
|
||||||
|
For,
|
||||||
|
Extern,
|
||||||
|
Semi,
|
||||||
|
Percent,
|
||||||
|
LeftShift,
|
||||||
|
RightShift,
|
||||||
|
Pipe,
|
||||||
|
And,
|
||||||
|
Or,
|
||||||
|
Module,
|
||||||
|
Import,
|
||||||
|
Export,
|
||||||
|
}
|
||||||
266
compiler/NubLang/Tokenization/Tokenizer.cs
Normal file
266
compiler/NubLang/Tokenization/Tokenizer.cs
Normal file
@@ -0,0 +1,266 @@
|
|||||||
|
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,
|
||||||
|
["export"] = Symbol.Export,
|
||||||
|
["import"] = Symbol.Import,
|
||||||
|
};
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
171
compiler/NubLang/TypeChecking/Module.cs
Normal file
171
compiler/NubLang/TypeChecking/Module.cs
Normal file
@@ -0,0 +1,171 @@
|
|||||||
|
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) continue;
|
||||||
|
|
||||||
|
switch (def)
|
||||||
|
{
|
||||||
|
case FuncSyntax funcDef:
|
||||||
|
{
|
||||||
|
var parameters = funcDef.Signature.Parameters.Select(p => TypeResolver.ResolveType(p.Type, modules)).ToList();
|
||||||
|
var returnType = TypeResolver.ResolveType(funcDef.Signature.ReturnType, modules);
|
||||||
|
|
||||||
|
var type = new FuncTypeNode(parameters, returnType, funcDef.ExternSymbol);
|
||||||
|
module._functions.Add(funcDef.Name, type);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case InterfaceSyntax interfaceDef:
|
||||||
|
{
|
||||||
|
var functions = new List<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;
|
||||||
|
}
|
||||||
19
compiler/NubLang/TypeChecking/Node/DefinitionNode.cs
Normal file
19
compiler/NubLang/TypeChecking/Node/DefinitionNode.cs
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
namespace NubLang.TypeChecking.Node;
|
||||||
|
|
||||||
|
public abstract record DefinitionNode : Node;
|
||||||
|
|
||||||
|
public record FuncParameterNode(string Name, TypeNode Type) : Node;
|
||||||
|
|
||||||
|
public record FuncSignatureNode(IReadOnlyList<FuncParameterNode> Parameters, TypeNode ReturnType) : Node;
|
||||||
|
|
||||||
|
public record FuncNode(string Name, FuncSignatureNode Signature, BlockNode? Body) : 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;
|
||||||
72
compiler/NubLang/TypeChecking/Node/ExpressionNode.cs
Normal file
72
compiler/NubLang/TypeChecking/Node/ExpressionNode.cs
Normal file
@@ -0,0 +1,72 @@
|
|||||||
|
using NubLang.Tokenization;
|
||||||
|
|
||||||
|
namespace NubLang.TypeChecking.Node;
|
||||||
|
|
||||||
|
public enum UnaryOperator
|
||||||
|
{
|
||||||
|
Negate,
|
||||||
|
Invert
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum BinaryOperator
|
||||||
|
{
|
||||||
|
Equal,
|
||||||
|
NotEqual,
|
||||||
|
GreaterThan,
|
||||||
|
GreaterThanOrEqual,
|
||||||
|
LessThan,
|
||||||
|
LessThanOrEqual,
|
||||||
|
LogicalAnd,
|
||||||
|
LogicalOr,
|
||||||
|
Plus,
|
||||||
|
Minus,
|
||||||
|
Multiply,
|
||||||
|
Divide,
|
||||||
|
Modulo,
|
||||||
|
LeftShift,
|
||||||
|
RightShift,
|
||||||
|
BitwiseAnd,
|
||||||
|
BitwiseXor,
|
||||||
|
BitwiseOr
|
||||||
|
}
|
||||||
|
|
||||||
|
public abstract record ExpressionNode(TypeNode Type) : Node;
|
||||||
|
|
||||||
|
public abstract record LValueExpressionNode(TypeNode Type) : RValueExpressionNode(Type);
|
||||||
|
public abstract record RValueExpressionNode(TypeNode Type) : ExpressionNode(Type);
|
||||||
|
|
||||||
|
public record BinaryExpressionNode(TypeNode Type, ExpressionNode Left, BinaryOperator Operator, ExpressionNode Right) : RValueExpressionNode(Type);
|
||||||
|
|
||||||
|
public record UnaryExpressionNode(TypeNode Type, UnaryOperator Operator, ExpressionNode Operand) : RValueExpressionNode(Type);
|
||||||
|
|
||||||
|
public record FuncCallNode(TypeNode Type, ExpressionNode Expression, IReadOnlyList<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);
|
||||||
5
compiler/NubLang/TypeChecking/Node/Node.cs
Normal file
5
compiler/NubLang/TypeChecking/Node/Node.cs
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
namespace NubLang.TypeChecking.Node;
|
||||||
|
|
||||||
|
public abstract record Node;
|
||||||
|
|
||||||
|
public record BlockNode(IReadOnlyList<StatementNode> Statements) : Node;
|
||||||
19
compiler/NubLang/TypeChecking/Node/StatementNode.cs
Normal file
19
compiler/NubLang/TypeChecking/Node/StatementNode.cs
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
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;
|
||||||
233
compiler/NubLang/TypeChecking/Node/TypeNode.cs
Normal file
233
compiler/NubLang/TypeChecking/Node/TypeNode.cs
Normal file
@@ -0,0 +1,233 @@
|
|||||||
|
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, string? externSymbol = null) : SimpleTypeNode
|
||||||
|
{
|
||||||
|
public string? ExternSymbol { get; } = externSymbol;
|
||||||
|
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
|
||||||
456
compiler/NubLang/TypeChecking/TypeChecker.cs
Normal file
456
compiler/NubLang/TypeChecking/TypeChecker.cs
Normal file
@@ -0,0 +1,456 @@
|
|||||||
|
using NubLang.Diagnostics;
|
||||||
|
using NubLang.Parsing.Syntax;
|
||||||
|
using NubLang.Tokenization;
|
||||||
|
using NubLang.TypeChecking.Node;
|
||||||
|
|
||||||
|
namespace NubLang.TypeChecking;
|
||||||
|
|
||||||
|
public sealed class TypeChecker
|
||||||
|
{
|
||||||
|
private readonly Module _currentModule;
|
||||||
|
private readonly IReadOnlyDictionary<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
|
||||||
|
{
|
||||||
|
InterfaceSyntax definition => CheckInterfaceDefinition(definition),
|
||||||
|
FuncSyntax definition => CheckFuncDefinition(definition),
|
||||||
|
StructSyntax definition => CheckStructDefinition(definition),
|
||||||
|
_ => throw new ArgumentOutOfRangeException(nameof(node))
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private InterfaceNode CheckInterfaceDefinition(InterfaceSyntax node)
|
||||||
|
{
|
||||||
|
throw new NotImplementedException();
|
||||||
|
}
|
||||||
|
|
||||||
|
private StructNode CheckStructDefinition(StructSyntax node)
|
||||||
|
{
|
||||||
|
var fields = new List<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 FuncNode CheckFuncDefinition(FuncSyntax node)
|
||||||
|
{
|
||||||
|
var scope = new Scope();
|
||||||
|
foreach (var parameter in node.Signature.Parameters)
|
||||||
|
{
|
||||||
|
scope.Declare(new Identifier(parameter.Name, ResolveType(parameter.Type), IdentifierKind.FunctionParameter));
|
||||||
|
}
|
||||||
|
|
||||||
|
BlockNode? body = null;
|
||||||
|
if (node.Body != null)
|
||||||
|
{
|
||||||
|
_funcReturnTypes.Push(ResolveType(node.Signature.ReturnType));
|
||||||
|
body = CheckBlock(node.Body, scope);
|
||||||
|
_funcReturnTypes.Pop();
|
||||||
|
}
|
||||||
|
|
||||||
|
return new FuncNode(node.Name, CheckFuncSignature(node.Signature), body);
|
||||||
|
}
|
||||||
|
|
||||||
|
private StatementNode CheckStatement(StatementSyntax node)
|
||||||
|
{
|
||||||
|
return node switch
|
||||||
|
{
|
||||||
|
AssignmentSyntax statement => CheckAssignment(statement),
|
||||||
|
BreakSyntax => new BreakNode(),
|
||||||
|
ContinueSyntax => new ContinueNode(),
|
||||||
|
IfSyntax statement => CheckIf(statement),
|
||||||
|
ReturnSyntax statement => CheckReturn(statement),
|
||||||
|
StatementExpressionSyntax statement => CheckStatementExpression(statement),
|
||||||
|
VariableDeclarationSyntax statement => CheckVariableDeclaration(statement),
|
||||||
|
WhileSyntax statement => CheckWhile(statement),
|
||||||
|
_ => throw new ArgumentOutOfRangeException(nameof(node))
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private StatementNode CheckAssignment(AssignmentSyntax statement)
|
||||||
|
{
|
||||||
|
throw new NotImplementedException();
|
||||||
|
}
|
||||||
|
|
||||||
|
private IfNode CheckIf(IfSyntax statement)
|
||||||
|
{
|
||||||
|
throw new NotImplementedException();
|
||||||
|
}
|
||||||
|
|
||||||
|
private ReturnNode CheckReturn(ReturnSyntax statement)
|
||||||
|
{
|
||||||
|
var value = Optional.Empty<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;
|
||||||
|
}
|
||||||
|
}
|
||||||
95
compiler/NubLang/TypeResolver.cs
Normal file
95
compiler/NubLang/TypeResolver.cs
Normal file
@@ -0,0 +1,95 @@
|
|||||||
|
using NubLang.Parsing.Syntax;
|
||||||
|
using NubLang.TypeChecking;
|
||||||
|
using NubLang.TypeChecking.Node;
|
||||||
|
|
||||||
|
namespace NubLang;
|
||||||
|
|
||||||
|
public static class TypeResolver
|
||||||
|
{
|
||||||
|
public static TypeNode ResolveType(TypeSyntax type, IReadOnlyDictionary<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 interfaceName, 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 == interfaceName);
|
||||||
|
if (structType != null)
|
||||||
|
{
|
||||||
|
return structType;
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new Exception($"Interface type {interfaceName} not found in module {moduleName}");
|
||||||
|
}
|
||||||
|
|
||||||
|
public static FuncTypeNode ResolveFunctionType(string moduleName, string funcName, IReadOnlyDictionary<string, ModuleSignature> modules)
|
||||||
|
{
|
||||||
|
if (!modules.TryGetValue(moduleName, out var module))
|
||||||
|
{
|
||||||
|
throw new Exception("Module not found: " + moduleName);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (module.Functions.TryGetValue(funcName, out var funcType))
|
||||||
|
{
|
||||||
|
return funcType;
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new Exception($"Function type {funcName} not found in module {moduleName}");
|
||||||
|
}
|
||||||
|
}
|
||||||
75
compiler/NubLang/Variant.cs
Normal file
75
compiler/NubLang/Variant.cs
Normal file
@@ -0,0 +1,75 @@
|
|||||||
|
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);
|
||||||
|
}
|
||||||
9
example/main.nub
Normal file
9
example/main.nub
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
module "main"
|
||||||
|
|
||||||
|
extern "puts" func puts(text: cstring)
|
||||||
|
|
||||||
|
extern "main" func main(args: []cstring): i64
|
||||||
|
{
|
||||||
|
puts("test")
|
||||||
|
return 0
|
||||||
|
}
|
||||||
10
example/x86_64.s
Normal file
10
example/x86_64.s
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
.intel_syntax noprefix
|
||||||
|
|
||||||
|
.text
|
||||||
|
.globl _start
|
||||||
|
_start:
|
||||||
|
mov rdi, rsp
|
||||||
|
call main.main
|
||||||
|
mov rdi, rax
|
||||||
|
mov rax, 60
|
||||||
|
syscall
|
||||||
1
vscode/.gitignore
vendored
Normal file
1
vscode/.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
bin
|
||||||
29
vscode/language-configuration.json
Normal file
29
vscode/language-configuration.json
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
{
|
||||||
|
"comments": {
|
||||||
|
"lineComment": "//",
|
||||||
|
"blockComment": ["/*", "*/"]
|
||||||
|
},
|
||||||
|
"brackets": [
|
||||||
|
["{", "}"],
|
||||||
|
["[", "]"],
|
||||||
|
["(", ")"]
|
||||||
|
],
|
||||||
|
"autoClosingPairs": [
|
||||||
|
["{", "}"],
|
||||||
|
["[", "]"],
|
||||||
|
["(", ")"],
|
||||||
|
["\"", "\""],
|
||||||
|
["'", "'"]
|
||||||
|
],
|
||||||
|
"surroundingPairs": [
|
||||||
|
["{", "}"],
|
||||||
|
["[", "]"],
|
||||||
|
["(", ")"],
|
||||||
|
["\"", "\""],
|
||||||
|
["'", "'"]
|
||||||
|
],
|
||||||
|
"indentationRules": {
|
||||||
|
"increaseIndentPattern": "^((?!\\/\\*).)*(\\{[^}\"'`]*|\\([^)\"'`]*|\\[[^\\]\"'`]*)$",
|
||||||
|
"decreaseIndentPattern": "^((?!.*?\\/\\*).*\\*/)?\\s*[\\}\\]\\)].*$"
|
||||||
|
}
|
||||||
|
}
|
||||||
6
vscode/makefile
Normal file
6
vscode/makefile
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
vscode:
|
||||||
|
mkdir -p bin
|
||||||
|
npx vsce package -o bin/nub-lang.vsix
|
||||||
|
|
||||||
|
clean:
|
||||||
|
rm -r bin
|
||||||
40
vscode/package.json
Normal file
40
vscode/package.json
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
{
|
||||||
|
"name": "nub-lang",
|
||||||
|
"displayName": "Nub Language Support",
|
||||||
|
"description": "Syntax highlighting for Nub programming language",
|
||||||
|
"version": "0.0.0",
|
||||||
|
"publisher": "nub31",
|
||||||
|
"repository": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://git.oliste.no/nub31/nub-lang"
|
||||||
|
},
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"vscode": "^1.74.0"
|
||||||
|
},
|
||||||
|
"categories": [
|
||||||
|
"Programming Languages"
|
||||||
|
],
|
||||||
|
"contributes": {
|
||||||
|
"languages": [
|
||||||
|
{
|
||||||
|
"id": "nub",
|
||||||
|
"aliases": [
|
||||||
|
"Nub",
|
||||||
|
"nub"
|
||||||
|
],
|
||||||
|
"extensions": [
|
||||||
|
".nub"
|
||||||
|
],
|
||||||
|
"configuration": "./language-configuration.json"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"grammars": [
|
||||||
|
{
|
||||||
|
"language": "nub",
|
||||||
|
"scopeName": "source.nub",
|
||||||
|
"path": "./syntaxes/nub.tmLanguage.json"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
300
vscode/syntaxes/nub.tmLanguage.json
Normal file
300
vscode/syntaxes/nub.tmLanguage.json
Normal file
@@ -0,0 +1,300 @@
|
|||||||
|
{
|
||||||
|
"$schema": "https://raw.githubusercontent.com/martinring/tmlanguage/master/tmlanguage.json",
|
||||||
|
"name": "Nub",
|
||||||
|
"scopeName": "source.nub",
|
||||||
|
"patterns": [
|
||||||
|
{
|
||||||
|
"include": "#comments"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"include": "#keywords"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"include": "#modifiers"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"include": "#types"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"include": "#strings"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"include": "#numbers"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"include": "#operators"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"include": "#function-definition"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"include": "#struct-definition"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"include": "#function-call"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"include": "#identifiers"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"repository": {
|
||||||
|
"comments": {
|
||||||
|
"patterns": [
|
||||||
|
{
|
||||||
|
"name": "comment.line.double-slash.nub",
|
||||||
|
"begin": "//",
|
||||||
|
"end": "$"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "comment.block.nub",
|
||||||
|
"begin": "/\\*",
|
||||||
|
"end": "\\*/"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"keywords": {
|
||||||
|
"patterns": [
|
||||||
|
{
|
||||||
|
"name": "keyword.control.nub",
|
||||||
|
"match": "\\b(if|else|while|break|continue|return|let)\\b"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "keyword.other.nub",
|
||||||
|
"match": "\\b(namespace|func|struct|interface)\\b"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"modifiers": {
|
||||||
|
"patterns": [
|
||||||
|
{
|
||||||
|
"name": "storage.modifier.nub",
|
||||||
|
"match": "\\b(export|extern|calls)\\b"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"types": {
|
||||||
|
"patterns": [
|
||||||
|
{
|
||||||
|
"include": "#function-type"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "storage.type.primitive.nub",
|
||||||
|
"match": "\\b(i8|i16|i32|i64|u8|u16|u32|u64|f32|f64|bool|string|cstring|void|any)\\b"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "storage.type.array.nub",
|
||||||
|
"match": "\\[\\]"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "storage.type.pointer.nub",
|
||||||
|
"match": "\\^"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"function-type": {
|
||||||
|
"patterns": [
|
||||||
|
{
|
||||||
|
"begin": "\\b(func)\\s*\\(",
|
||||||
|
"beginCaptures": {
|
||||||
|
"1": {
|
||||||
|
"name": "storage.type.function.nub"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"end": "(?<=\\))(?:\\s*:\\s*([^\\s,;{}()]+))?",
|
||||||
|
"endCaptures": {
|
||||||
|
"1": {
|
||||||
|
"name": "storage.type.nub"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"patterns": [
|
||||||
|
{
|
||||||
|
"include": "#function-type-parameters"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"function-type-parameters": {
|
||||||
|
"patterns": [
|
||||||
|
{
|
||||||
|
"match": "\\.\\.\\.",
|
||||||
|
"name": "keyword.operator.variadic.nub"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"include": "#types"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"match": ",",
|
||||||
|
"name": "punctuation.separator.nub"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"strings": {
|
||||||
|
"patterns": [
|
||||||
|
{
|
||||||
|
"name": "string.quoted.double.nub",
|
||||||
|
"begin": "\"",
|
||||||
|
"end": "\"",
|
||||||
|
"patterns": [
|
||||||
|
{
|
||||||
|
"name": "constant.character.escape.nub",
|
||||||
|
"match": "\\\\(n|t|r|\\\\|\"|')"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "constant.character.escape.nub",
|
||||||
|
"match": "\\\\[0-7]{1,3}"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "constant.character.escape.nub",
|
||||||
|
"match": "\\\\x[0-9A-Fa-f]{1,2}"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "string.quoted.single.nub",
|
||||||
|
"begin": "'",
|
||||||
|
"end": "'",
|
||||||
|
"patterns": [
|
||||||
|
{
|
||||||
|
"name": "constant.character.escape.nub",
|
||||||
|
"match": "\\\\(n|t|r|\\\\|\"|')"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"numbers": {
|
||||||
|
"patterns": [
|
||||||
|
{
|
||||||
|
"name": "constant.numeric.float.nub",
|
||||||
|
"match": "\\b\\d+\\.\\d*([eE][+-]?\\d+)?[fF]?\\b"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "constant.numeric.integer.decimal.nub",
|
||||||
|
"match": "\\b\\d+\\b"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "constant.numeric.integer.hexadecimal.nub",
|
||||||
|
"match": "\\b0[xX][0-9A-Fa-f]+\\b"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "constant.numeric.integer.binary.nub",
|
||||||
|
"match": "\\b0[bB][01]+\\b"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"operators": {
|
||||||
|
"patterns": [
|
||||||
|
{
|
||||||
|
"name": "keyword.operator.assignment.nub",
|
||||||
|
"match": "="
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "keyword.operator.comparison.nub",
|
||||||
|
"match": "(==|!=|<=|>=|<|>)"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "keyword.operator.arithmetic.nub",
|
||||||
|
"match": "(\\+|\\-|\\*|/)"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "keyword.operator.logical.nub",
|
||||||
|
"match": "(&&|\\|\\||!)"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "keyword.operator.address.nub",
|
||||||
|
"match": "&"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "keyword.operator.dereference.nub",
|
||||||
|
"match": "\\^"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "keyword.operator.member-access.nub",
|
||||||
|
"match": "\\."
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"function-definition": {
|
||||||
|
"patterns": [
|
||||||
|
{
|
||||||
|
"begin": "\\b(global\\s+|extern\\s+)?(func)\\s+([a-zA-Z_][a-zA-Z0-9_]*)\\s*\\(",
|
||||||
|
"beginCaptures": {
|
||||||
|
"1": {
|
||||||
|
"name": "storage.modifier.nub"
|
||||||
|
},
|
||||||
|
"2": {
|
||||||
|
"name": "keyword.other.nub"
|
||||||
|
},
|
||||||
|
"3": {
|
||||||
|
"name": "entity.name.function.nub"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"end": "\\)",
|
||||||
|
"patterns": [
|
||||||
|
{
|
||||||
|
"include": "#function-parameters"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"struct-definition": {
|
||||||
|
"patterns": [
|
||||||
|
{
|
||||||
|
"match": "\\b(struct)\\s+([a-zA-Z_][a-zA-Z0-9_]*)\\b",
|
||||||
|
"captures": {
|
||||||
|
"1": {
|
||||||
|
"name": "keyword.other.nub"
|
||||||
|
},
|
||||||
|
"2": {
|
||||||
|
"name": "entity.name.type.struct.nub"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"function-parameters": {
|
||||||
|
"patterns": [
|
||||||
|
{
|
||||||
|
"match": "\\.\\.\\.",
|
||||||
|
"name": "keyword.operator.variadic.nub"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"match": "([a-zA-Z_][a-zA-Z0-9_]*)\\s*:\\s*",
|
||||||
|
"captures": {
|
||||||
|
"1": {
|
||||||
|
"name": "variable.parameter.nub"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"include": "#types"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"include": "#identifiers"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"function-call": {
|
||||||
|
"patterns": [
|
||||||
|
{
|
||||||
|
"match": "([a-zA-Z_][a-zA-Z0-9_]*)\\s*(?=\\()",
|
||||||
|
"captures": {
|
||||||
|
"1": {
|
||||||
|
"name": "entity.name.function.call.nub"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"identifiers": {
|
||||||
|
"patterns": [
|
||||||
|
{
|
||||||
|
"name": "variable.other.nub",
|
||||||
|
"match": "\\b[a-zA-Z_][a-zA-Z0-9_]*\\b"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user