...
This commit is contained in:
1
example/.gitignore
vendored
1
example/.gitignore
vendored
@@ -1,2 +1,3 @@
|
|||||||
.build
|
.build
|
||||||
|
out.a
|
||||||
out
|
out
|
||||||
@@ -1,18 +1,16 @@
|
|||||||
CC = gcc
|
NUBC = ../compiler/NubLang.CLI/bin/Debug/net9.0/nubc
|
||||||
NUBC = ../src/compiler/NubLang.CLI/bin/Debug/net9.0/nubc
|
|
||||||
|
|
||||||
.build/out: .build/out.a
|
out: out.a
|
||||||
$(CC) -ffreestanding -g -o .build/out .build/out.a
|
gcc -nostartfiles -o out x86_64.s out.a
|
||||||
|
rm out.a
|
||||||
|
|
||||||
.build/out.a: $(NUBC) src/main.nub
|
out.a: $(NUBC) main.nub
|
||||||
$(NUBC) src/main.nub
|
$(NUBC) main.nub
|
||||||
|
|
||||||
|
.PHONY: $(NUBC)
|
||||||
$(NUBC):
|
$(NUBC):
|
||||||
dotnet build ../src/compiler/NubLang.CLI/NubLang.CLI.csproj
|
dotnet build ../compiler/NubLang.CLI/NubLang.CLI.csproj
|
||||||
|
|
||||||
run: .build/out
|
|
||||||
./.build/out
|
|
||||||
|
|
||||||
clean:
|
clean:
|
||||||
@rm -r .build 2>/dev/null || true
|
@rm -r .build 2>/dev/null || true
|
||||||
@rm out 2>/dev/null || true
|
@rm out.a 2>/dev/null || true
|
||||||
|
|||||||
@@ -1,15 +0,0 @@
|
|||||||
module main
|
|
||||||
|
|
||||||
extern func puts(text: cstring)
|
|
||||||
|
|
||||||
struct Test
|
|
||||||
{
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
func main(args: []cstring): i64
|
|
||||||
{
|
|
||||||
puts("test")
|
|
||||||
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
34
src/compiler/.gitignore
vendored
34
src/compiler/.gitignore
vendored
@@ -1,34 +0,0 @@
|
|||||||
# Common IntelliJ Platform excludes
|
|
||||||
|
|
||||||
# User specific
|
|
||||||
**/.idea/**/workspace.xml
|
|
||||||
**/.idea/**/tasks.xml
|
|
||||||
**/.idea/shelf/*
|
|
||||||
**/.idea/dictionaries
|
|
||||||
**/.idea/httpRequests/
|
|
||||||
|
|
||||||
# Sensitive or high-churn files
|
|
||||||
**/.idea/**/dataSources/
|
|
||||||
**/.idea/**/dataSources.ids
|
|
||||||
**/.idea/**/dataSources.xml
|
|
||||||
**/.idea/**/dataSources.local.xml
|
|
||||||
**/.idea/**/sqlDataSources.xml
|
|
||||||
**/.idea/**/dynamic.xml
|
|
||||||
|
|
||||||
# Rider
|
|
||||||
# Rider auto-generates .iml files, and contentModel.xml
|
|
||||||
**/.idea/**/*.iml
|
|
||||||
**/.idea/**/contentModel.xml
|
|
||||||
**/.idea/**/modules.xml
|
|
||||||
|
|
||||||
*.suo
|
|
||||||
*.user
|
|
||||||
.vs/
|
|
||||||
[Bb]in/
|
|
||||||
[Oo]bj/
|
|
||||||
_UpgradeReport_Files/
|
|
||||||
[Pp]ackages/
|
|
||||||
|
|
||||||
Thumbs.db
|
|
||||||
Desktop.ini
|
|
||||||
.DS_Store
|
|
||||||
13
src/compiler/.idea/.idea.Compiler/.idea/.gitignore
generated
vendored
13
src/compiler/.idea/.idea.Compiler/.idea/.gitignore
generated
vendored
@@ -1,13 +0,0 @@
|
|||||||
# Default ignored files
|
|
||||||
/shelf/
|
|
||||||
/workspace.xml
|
|
||||||
# Rider ignored files
|
|
||||||
/modules.xml
|
|
||||||
/projectSettingsUpdater.xml
|
|
||||||
/contentModel.xml
|
|
||||||
/.idea.Compiler.iml
|
|
||||||
# Editor-based HTTP Client requests
|
|
||||||
/httpRequests/
|
|
||||||
# Datasource local storage ignored files
|
|
||||||
/dataSources/
|
|
||||||
/dataSources.local.xml
|
|
||||||
1
src/compiler/.idea/.idea.Compiler/.idea/.name
generated
1
src/compiler/.idea/.idea.Compiler/.idea/.name
generated
@@ -1 +0,0 @@
|
|||||||
Compiler
|
|
||||||
@@ -1,4 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<project version="4">
|
|
||||||
<component name="Encoding" addBOMForNewFiles="with BOM under Windows, with no BOM otherwise" />
|
|
||||||
</project>
|
|
||||||
@@ -1,10 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<project version="4">
|
|
||||||
<component name="UserContentModel">
|
|
||||||
<attachedFolders>
|
|
||||||
<Path>Runtime</Path>
|
|
||||||
</attachedFolders>
|
|
||||||
<explicitIncludes />
|
|
||||||
<explicitExcludes />
|
|
||||||
</component>
|
|
||||||
</project>
|
|
||||||
6
src/compiler/.idea/.idea.Compiler/.idea/vcs.xml
generated
6
src/compiler/.idea/.idea.Compiler/.idea/vcs.xml
generated
@@ -1,6 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<project version="4">
|
|
||||||
<component name="VcsDirectoryMappings">
|
|
||||||
<mapping directory="$PROJECT_DIR$/../.." vcs="Git" />
|
|
||||||
</component>
|
|
||||||
</project>
|
|
||||||
@@ -1,22 +0,0 @@
|
|||||||
|
|
||||||
Microsoft Visual Studio Solution File, Format Version 12.00
|
|
||||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NubLang", "NubLang\NubLang.csproj", "{5047E21F-590D-4CB3-AFF3-064316485009}"
|
|
||||||
EndProject
|
|
||||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NubLang.CLI", "NubLang.CLI\NubLang.CLI.csproj", "{A22F17ED-FA17-45AB-92BA-CD02C28B3524}"
|
|
||||||
EndProject
|
|
||||||
Global
|
|
||||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
|
||||||
Debug|Any CPU = Debug|Any CPU
|
|
||||||
Release|Any CPU = Release|Any CPU
|
|
||||||
EndGlobalSection
|
|
||||||
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
|
||||||
{5047E21F-590D-4CB3-AFF3-064316485009}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
|
||||||
{5047E21F-590D-4CB3-AFF3-064316485009}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
|
||||||
{5047E21F-590D-4CB3-AFF3-064316485009}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
|
||||||
{5047E21F-590D-4CB3-AFF3-064316485009}.Release|Any CPU.Build.0 = Release|Any CPU
|
|
||||||
{A22F17ED-FA17-45AB-92BA-CD02C28B3524}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
|
||||||
{A22F17ED-FA17-45AB-92BA-CD02C28B3524}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
|
||||||
{A22F17ED-FA17-45AB-92BA-CD02C28B3524}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
|
||||||
{A22F17ED-FA17-45AB-92BA-CD02C28B3524}.Release|Any CPU.Build.0 = Release|Any CPU
|
|
||||||
EndGlobalSection
|
|
||||||
EndGlobal
|
|
||||||
@@ -1,29 +0,0 @@
|
|||||||
using System.Diagnostics;
|
|
||||||
|
|
||||||
namespace NubLang.CLI;
|
|
||||||
|
|
||||||
public static class Archive
|
|
||||||
{
|
|
||||||
public static async Task<bool> Invoke(string fileName, params IEnumerable<string> objectFiles)
|
|
||||||
{
|
|
||||||
using var process = new Process();
|
|
||||||
process.StartInfo = new ProcessStartInfo("ar", ["rcs", fileName, ..objectFiles])
|
|
||||||
{
|
|
||||||
UseShellExecute = false,
|
|
||||||
RedirectStandardOutput = true,
|
|
||||||
RedirectStandardError = true,
|
|
||||||
CreateNoWindow = true
|
|
||||||
};
|
|
||||||
|
|
||||||
process.Start();
|
|
||||||
await process.WaitForExitAsync();
|
|
||||||
|
|
||||||
var errors = await process.StandardError.ReadToEndAsync();
|
|
||||||
if (!string.IsNullOrWhiteSpace(errors))
|
|
||||||
{
|
|
||||||
await Console.Error.WriteLineAsync("ar error when archiving:\n" + errors);
|
|
||||||
}
|
|
||||||
|
|
||||||
return process.ExitCode == 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,30 +0,0 @@
|
|||||||
using System.Diagnostics;
|
|
||||||
|
|
||||||
namespace NubLang.CLI;
|
|
||||||
|
|
||||||
public static class GCC
|
|
||||||
{
|
|
||||||
public static async Task<bool> Assemble(string asmPath, string outPath)
|
|
||||||
{
|
|
||||||
using var process = new Process();
|
|
||||||
process.StartInfo = new ProcessStartInfo("gcc", ["-xassembler", "-g", "-c", "-o", outPath, asmPath])
|
|
||||||
{
|
|
||||||
UseShellExecute = false,
|
|
||||||
RedirectStandardOutput = true,
|
|
||||||
RedirectStandardError = true,
|
|
||||||
CreateNoWindow = true
|
|
||||||
};
|
|
||||||
|
|
||||||
process.Start();
|
|
||||||
|
|
||||||
await process.WaitForExitAsync();
|
|
||||||
|
|
||||||
var errors = await process.StandardError.ReadToEndAsync();
|
|
||||||
if (!string.IsNullOrWhiteSpace(errors))
|
|
||||||
{
|
|
||||||
await Console.Error.WriteLineAsync("gcc error when assembling:\n" + errors);
|
|
||||||
}
|
|
||||||
|
|
||||||
return process.ExitCode == 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,23 +0,0 @@
|
|||||||
<Project Sdk="Microsoft.NET.Sdk">
|
|
||||||
|
|
||||||
<PropertyGroup>
|
|
||||||
<AssemblyName>nubc</AssemblyName>
|
|
||||||
<OutputType>Exe</OutputType>
|
|
||||||
<TargetFramework>net9.0</TargetFramework>
|
|
||||||
<ImplicitUsings>enable</ImplicitUsings>
|
|
||||||
<Nullable>enable</Nullable>
|
|
||||||
<PublishAot>true</PublishAot>
|
|
||||||
</PropertyGroup>
|
|
||||||
|
|
||||||
<ItemGroup>
|
|
||||||
<ProjectReference Include="..\NubLang\NubLang.csproj" />
|
|
||||||
</ItemGroup>
|
|
||||||
|
|
||||||
<ItemGroup>
|
|
||||||
<None Remove="assets\runtime.o" />
|
|
||||||
<EmbeddedResource Include="assets\runtime.o" />
|
|
||||||
<None Remove="assets\x64.o" />
|
|
||||||
<EmbeddedResource Include="assets\x64.o" />
|
|
||||||
</ItemGroup>
|
|
||||||
|
|
||||||
</Project>
|
|
||||||
@@ -1,9 +0,0 @@
|
|||||||
using NubLang.Code;
|
|
||||||
|
|
||||||
namespace NubLang.CLI;
|
|
||||||
|
|
||||||
public class Options
|
|
||||||
{
|
|
||||||
public string? OutputPath { get; set; }
|
|
||||||
public List<SourceFile> Files { get; } = [];
|
|
||||||
}
|
|
||||||
@@ -1,170 +0,0 @@
|
|||||||
using System.Reflection;
|
|
||||||
using NubLang.CLI;
|
|
||||||
using NubLang.Code;
|
|
||||||
using NubLang.Diagnostics;
|
|
||||||
using NubLang.Generation.QBE;
|
|
||||||
using NubLang.Parsing;
|
|
||||||
using NubLang.Parsing.Syntax;
|
|
||||||
using NubLang.Tokenization;
|
|
||||||
using NubLang.TypeChecking;
|
|
||||||
using Module = NubLang.TypeChecking.Module;
|
|
||||||
|
|
||||||
var options = new Options();
|
|
||||||
|
|
||||||
for (var i = 0; i < args.Length; i++)
|
|
||||||
{
|
|
||||||
var arg = args[i];
|
|
||||||
switch (arg)
|
|
||||||
{
|
|
||||||
case "-o":
|
|
||||||
{
|
|
||||||
++i;
|
|
||||||
if (i >= args.Length)
|
|
||||||
{
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
options.OutputPath = args[i];
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
{
|
|
||||||
options.Files.Add(new SourceFile(arg));
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
foreach (var file in options.Files)
|
|
||||||
{
|
|
||||||
if (!File.Exists(file.Path))
|
|
||||||
{
|
|
||||||
Console.Error.WriteLine($"File '{file}' does not exist");
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var diagnostics = new List<Diagnostic>();
|
|
||||||
|
|
||||||
var syntaxTrees = new List<SyntaxTree>();
|
|
||||||
foreach (var file in options.Files)
|
|
||||||
{
|
|
||||||
var tokenizer = new Tokenizer(file);
|
|
||||||
var tokens = tokenizer.Tokenize().ToList();
|
|
||||||
diagnostics.AddRange(tokenizer.GetDiagnostics());
|
|
||||||
|
|
||||||
var parser = new Parser();
|
|
||||||
var syntaxTree = parser.Parse(tokens);
|
|
||||||
|
|
||||||
diagnostics.AddRange(parser.GetDiagnostics());
|
|
||||||
|
|
||||||
syntaxTrees.Add(syntaxTree);
|
|
||||||
}
|
|
||||||
|
|
||||||
var moduleSignatures = ModuleSignature.CollectFromSyntaxTrees(syntaxTrees);
|
|
||||||
var modules = Module.CollectFromSyntaxTrees(syntaxTrees);
|
|
||||||
|
|
||||||
var typedModules = new List<TypedModule>();
|
|
||||||
|
|
||||||
foreach (var module in modules)
|
|
||||||
{
|
|
||||||
var typeChecker = new TypeChecker(module, moduleSignatures);
|
|
||||||
var typedModule = typeChecker.CheckModule();
|
|
||||||
diagnostics.AddRange(typeChecker.GetDiagnostics());
|
|
||||||
typedModules.Add(typedModule);
|
|
||||||
}
|
|
||||||
|
|
||||||
foreach (var diagnostic in diagnostics)
|
|
||||||
{
|
|
||||||
Console.Error.WriteLine(diagnostic.FormatANSI());
|
|
||||||
}
|
|
||||||
|
|
||||||
if (diagnostics.Any(diagnostic => diagnostic.Severity == DiagnosticSeverity.Error))
|
|
||||||
{
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
var objectFiles = new List<string>();
|
|
||||||
|
|
||||||
for (var i = 0; i < typedModules.Count; i++)
|
|
||||||
{
|
|
||||||
var typedModule = typedModules[i];
|
|
||||||
var outFileName = Path.Combine(".build", "code", Path.ChangeExtension(options.Files[i].Path, null));
|
|
||||||
|
|
||||||
var outFileDir = Path.GetDirectoryName(outFileName);
|
|
||||||
if (!string.IsNullOrEmpty(outFileDir))
|
|
||||||
{
|
|
||||||
Directory.CreateDirectory(outFileDir);
|
|
||||||
}
|
|
||||||
|
|
||||||
var generator = new QBEGenerator(typedModule, moduleSignatures);
|
|
||||||
var ssa = generator.Emit();
|
|
||||||
|
|
||||||
var ssaFilePath = Path.ChangeExtension(outFileName, "ssa");
|
|
||||||
File.WriteAllText(ssaFilePath, ssa);
|
|
||||||
|
|
||||||
var asmFilePath = Path.ChangeExtension(outFileName, "s");
|
|
||||||
var qbeSuccess = await QBE.Invoke(ssaFilePath, asmFilePath);
|
|
||||||
|
|
||||||
if (!qbeSuccess)
|
|
||||||
{
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
var objFilePath = Path.ChangeExtension(outFileName, "o");
|
|
||||||
var asmSuccess = await GCC.Assemble(asmFilePath, objFilePath);
|
|
||||||
|
|
||||||
if (!asmSuccess)
|
|
||||||
{
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
objectFiles.Add(objFilePath);
|
|
||||||
}
|
|
||||||
|
|
||||||
var resources = Assembly.GetExecutingAssembly().GetManifestResourceNames();
|
|
||||||
|
|
||||||
string[] runtimeObjects = ["runtime.o", "x64.o"];
|
|
||||||
|
|
||||||
foreach (var runtimeObject in runtimeObjects)
|
|
||||||
{
|
|
||||||
var runtime = resources.First(r => r.EndsWith(runtimeObject));
|
|
||||||
|
|
||||||
await using var reader = Assembly
|
|
||||||
.GetExecutingAssembly()
|
|
||||||
.GetManifestResourceStream(runtime);
|
|
||||||
|
|
||||||
if (reader == null)
|
|
||||||
{
|
|
||||||
Console.Error.WriteLine($"Cannot open read stream to '{runtimeObject}'");
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
var runtimePath = Path.Combine(".build", "runtime", runtimeObject);
|
|
||||||
var runtimeDir = Path.GetDirectoryName(runtimePath);
|
|
||||||
if (!string.IsNullOrEmpty(runtimeDir))
|
|
||||||
{
|
|
||||||
Directory.CreateDirectory(runtimeDir);
|
|
||||||
}
|
|
||||||
|
|
||||||
await using var writer = new FileStream(runtimePath, FileMode.Create);
|
|
||||||
|
|
||||||
reader.CopyTo(writer);
|
|
||||||
|
|
||||||
objectFiles.Add(runtimePath);
|
|
||||||
}
|
|
||||||
|
|
||||||
var outPath = options.OutputPath ?? Path.Combine(".build", "out.a");
|
|
||||||
var outDir = Path.GetDirectoryName(outPath);
|
|
||||||
if (!string.IsNullOrEmpty(outDir))
|
|
||||||
{
|
|
||||||
Directory.CreateDirectory(outDir);
|
|
||||||
}
|
|
||||||
|
|
||||||
var archiveResult = await Archive.Invoke(outPath, objectFiles);
|
|
||||||
if (!archiveResult)
|
|
||||||
{
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
return 0;
|
|
||||||
@@ -1,30 +0,0 @@
|
|||||||
using System.Diagnostics;
|
|
||||||
|
|
||||||
namespace NubLang.CLI;
|
|
||||||
|
|
||||||
public static class QBE
|
|
||||||
{
|
|
||||||
public static async Task<bool> Invoke(string ssaPath, string outPath)
|
|
||||||
{
|
|
||||||
using var process = new Process();
|
|
||||||
process.StartInfo = new ProcessStartInfo("qbe", ["-o", outPath, ssaPath])
|
|
||||||
{
|
|
||||||
UseShellExecute = false,
|
|
||||||
RedirectStandardOutput = true,
|
|
||||||
RedirectStandardError = true,
|
|
||||||
CreateNoWindow = true
|
|
||||||
};
|
|
||||||
|
|
||||||
process.Start();
|
|
||||||
|
|
||||||
await process.WaitForExitAsync();
|
|
||||||
|
|
||||||
var errors = await process.StandardError.ReadToEndAsync();
|
|
||||||
if (!string.IsNullOrWhiteSpace(errors))
|
|
||||||
{
|
|
||||||
await Console.Error.WriteLineAsync("qbe error:\n" + errors);
|
|
||||||
}
|
|
||||||
|
|
||||||
return process.ExitCode == 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Binary file not shown.
Binary file not shown.
@@ -1,41 +0,0 @@
|
|||||||
namespace NubLang.Code;
|
|
||||||
|
|
||||||
public class SourceFile
|
|
||||||
{
|
|
||||||
private string? _content;
|
|
||||||
|
|
||||||
public SourceFile(string path)
|
|
||||||
{
|
|
||||||
Path = path ?? throw new ArgumentNullException(nameof(path));
|
|
||||||
}
|
|
||||||
|
|
||||||
public string Path { get; }
|
|
||||||
|
|
||||||
public string GetText() => _content ??= File.ReadAllText(Path);
|
|
||||||
public override string ToString() => Path;
|
|
||||||
|
|
||||||
public override bool Equals(object? obj)
|
|
||||||
{
|
|
||||||
return obj is SourceFile other && other.Path == Path;
|
|
||||||
}
|
|
||||||
|
|
||||||
public override int GetHashCode()
|
|
||||||
{
|
|
||||||
return HashCode.Combine(typeof(SourceFile), Path);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static bool operator ==(SourceFile? left, SourceFile? right) => Equals(left, right);
|
|
||||||
public static bool operator !=(SourceFile? left, SourceFile? right) => !Equals(left, right);
|
|
||||||
}
|
|
||||||
|
|
||||||
public class SourceFileSpan
|
|
||||||
{
|
|
||||||
public SourceFileSpan(SourceFile sourceFile, SourceSpan span)
|
|
||||||
{
|
|
||||||
SourceFile = sourceFile;
|
|
||||||
Span = span;
|
|
||||||
}
|
|
||||||
|
|
||||||
public SourceFile SourceFile { get; }
|
|
||||||
public SourceSpan Span { get; }
|
|
||||||
}
|
|
||||||
@@ -1,42 +0,0 @@
|
|||||||
namespace NubLang.Code;
|
|
||||||
|
|
||||||
public readonly struct SourceLocation : IEquatable<SourceLocation>
|
|
||||||
{
|
|
||||||
public static SourceLocation Zero => new(0, 0);
|
|
||||||
|
|
||||||
public SourceLocation(int line, int column)
|
|
||||||
{
|
|
||||||
Line = line;
|
|
||||||
Column = column;
|
|
||||||
}
|
|
||||||
|
|
||||||
public int Line { get; }
|
|
||||||
public int Column { get; }
|
|
||||||
|
|
||||||
public override string ToString()
|
|
||||||
{
|
|
||||||
return $"{Line}:{Column}";
|
|
||||||
}
|
|
||||||
|
|
||||||
public override bool Equals(object? obj)
|
|
||||||
{
|
|
||||||
return obj is SourceLocation other && Equals(other);
|
|
||||||
}
|
|
||||||
|
|
||||||
public bool Equals(SourceLocation other)
|
|
||||||
{
|
|
||||||
return Line == other.Line && Column == other.Column;
|
|
||||||
}
|
|
||||||
|
|
||||||
public override int GetHashCode()
|
|
||||||
{
|
|
||||||
return HashCode.Combine(typeof(SourceLocation), Line, Column);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static bool operator ==(SourceLocation left, SourceLocation right) => Equals(left, right);
|
|
||||||
public static bool operator !=(SourceLocation left, SourceLocation right) => !Equals(left, right);
|
|
||||||
public static bool operator <(SourceLocation left, SourceLocation right) => left.Line < right.Line || (left.Line == right.Line && left.Column < right.Column);
|
|
||||||
public static bool operator >(SourceLocation left, SourceLocation right) => left.Line > right.Line || (left.Line == right.Line && left.Column > right.Column);
|
|
||||||
public static bool operator <=(SourceLocation left, SourceLocation right) => left.Line <= right.Line || (left.Line == right.Line && left.Column <= right.Column);
|
|
||||||
public static bool operator >=(SourceLocation left, SourceLocation right) => left.Line >= right.Line || (left.Line == right.Line && left.Column >= right.Column);
|
|
||||||
}
|
|
||||||
@@ -1,57 +0,0 @@
|
|||||||
namespace NubLang.Code;
|
|
||||||
|
|
||||||
public readonly struct SourceSpan : IEquatable<SourceSpan>
|
|
||||||
{
|
|
||||||
public static SourceSpan Zero => new(SourceLocation.Zero, SourceLocation.Zero);
|
|
||||||
|
|
||||||
public static SourceSpan Merge(params IEnumerable<SourceSpan> spans)
|
|
||||||
{
|
|
||||||
var spanArray = spans as SourceSpan[] ?? spans.ToArray();
|
|
||||||
|
|
||||||
if (spanArray.Length == 0)
|
|
||||||
{
|
|
||||||
return Zero;
|
|
||||||
}
|
|
||||||
|
|
||||||
var minStart = spanArray.Min(s => s.Start);
|
|
||||||
var maxEnd = spanArray.Max(s => s.End);
|
|
||||||
|
|
||||||
return new SourceSpan(minStart, maxEnd);
|
|
||||||
}
|
|
||||||
|
|
||||||
public SourceSpan(SourceLocation start, SourceLocation end)
|
|
||||||
{
|
|
||||||
if (start > end)
|
|
||||||
{
|
|
||||||
throw new ArgumentException("Start location cannot be after end location");
|
|
||||||
}
|
|
||||||
|
|
||||||
Start = start;
|
|
||||||
End = end;
|
|
||||||
}
|
|
||||||
|
|
||||||
public SourceLocation Start { get; }
|
|
||||||
public SourceLocation End { get; }
|
|
||||||
|
|
||||||
public override string ToString()
|
|
||||||
{
|
|
||||||
if (Start == End)
|
|
||||||
{
|
|
||||||
return $"{Start}";
|
|
||||||
}
|
|
||||||
|
|
||||||
if (Start.Line == End.Line)
|
|
||||||
{
|
|
||||||
return Start.Column == End.Column ? $"{Start}" : $"{Start.Line}:{Start.Column}-{End.Column}";
|
|
||||||
}
|
|
||||||
|
|
||||||
return $"{Start}-{End}";
|
|
||||||
}
|
|
||||||
|
|
||||||
public bool Equals(SourceSpan other) => Start == other.Start && End == other.End;
|
|
||||||
public override bool Equals(object? obj) => obj is SourceSpan other && Equals(other);
|
|
||||||
public override int GetHashCode() => HashCode.Combine(typeof(SourceSpan), Start, End);
|
|
||||||
|
|
||||||
public static bool operator ==(SourceSpan left, SourceSpan right) => Equals(left, right);
|
|
||||||
public static bool operator !=(SourceSpan left, SourceSpan right) => !Equals(left, right);
|
|
||||||
}
|
|
||||||
@@ -1,56 +0,0 @@
|
|||||||
namespace NubLang.Diagnostics;
|
|
||||||
|
|
||||||
public static class ConsoleColors
|
|
||||||
{
|
|
||||||
public const string Reset = "\e[0m";
|
|
||||||
public const string Bold = "\e[1m";
|
|
||||||
public const string Faint = "\e[2m";
|
|
||||||
public const string Italic = "\e[3m";
|
|
||||||
public const string Underline = "\e[4m";
|
|
||||||
public const string SlowBlink = "\e[5m";
|
|
||||||
public const string RapidBlink = "\e[6m";
|
|
||||||
public const string SwapBgAndFg = "\e[7m";
|
|
||||||
public const string Conceal = "\e[8m";
|
|
||||||
public const string CrossedOut = "\e[9m";
|
|
||||||
|
|
||||||
public const string DefaultFont = "\e[10m";
|
|
||||||
public const string AltFont1 = "\e[11m";
|
|
||||||
public const string AltFont2 = "\e[12m";
|
|
||||||
public const string AltFont3 = "\e[13m";
|
|
||||||
public const string AltFont4 = "\e[14m";
|
|
||||||
public const string AltFont5 = "\e[15m";
|
|
||||||
public const string AltFont6 = "\e[16m";
|
|
||||||
public const string AltFont7 = "\e[17m";
|
|
||||||
public const string AltFont8 = "\e[18m";
|
|
||||||
public const string AltFont9 = "\e[19m";
|
|
||||||
|
|
||||||
public const string Black = "\e[30m";
|
|
||||||
public const string Red = "\e[31m";
|
|
||||||
public const string Green = "\e[32m";
|
|
||||||
public const string Yellow = "\e[33m";
|
|
||||||
public const string Blue = "\e[34m";
|
|
||||||
public const string Magenta = "\e[35m";
|
|
||||||
public const string Cyan = "\e[36m";
|
|
||||||
public const string White = "\e[37m";
|
|
||||||
|
|
||||||
public const string BrightBlack = "\e[90m";
|
|
||||||
public const string BrightRed = "\e[91m";
|
|
||||||
public const string BrightGreen = "\e[92m";
|
|
||||||
public const string BrightYellow = "\e[93m";
|
|
||||||
public const string BrightBlue = "\e[94m";
|
|
||||||
public const string BrightMagenta = "\e[95m";
|
|
||||||
public const string BrightCyan = "\e[96m";
|
|
||||||
public const string BrightWhite = "\e[97m";
|
|
||||||
|
|
||||||
private static bool IsColorSupported()
|
|
||||||
{
|
|
||||||
var term = Environment.GetEnvironmentVariable("TERM");
|
|
||||||
var colorTerm = Environment.GetEnvironmentVariable("COLORTERM");
|
|
||||||
return !string.IsNullOrEmpty(term) || !string.IsNullOrEmpty(colorTerm) || !Console.IsOutputRedirected;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static string Colorize(string text, string color)
|
|
||||||
{
|
|
||||||
return IsColorSupported() ? $"{color}{text}{Reset}" : text;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,325 +0,0 @@
|
|||||||
using System.Text;
|
|
||||||
using NubLang.Code;
|
|
||||||
using NubLang.Parsing.Syntax;
|
|
||||||
using NubLang.Tokenization;
|
|
||||||
|
|
||||||
namespace NubLang.Diagnostics;
|
|
||||||
|
|
||||||
public class Diagnostic
|
|
||||||
{
|
|
||||||
public class DiagnosticBuilder
|
|
||||||
{
|
|
||||||
private readonly DiagnosticSeverity _severity;
|
|
||||||
private readonly string _message;
|
|
||||||
private SourceFileSpan? _fileSpan;
|
|
||||||
private string? _help;
|
|
||||||
|
|
||||||
public DiagnosticBuilder(DiagnosticSeverity severity, string message)
|
|
||||||
{
|
|
||||||
_severity = severity;
|
|
||||||
_message = message;
|
|
||||||
}
|
|
||||||
|
|
||||||
public DiagnosticBuilder At(SyntaxNode? node)
|
|
||||||
{
|
|
||||||
if (node != null)
|
|
||||||
{
|
|
||||||
var first = node.Tokens.FirstOrDefault();
|
|
||||||
if (first?.FileSpan != null)
|
|
||||||
{
|
|
||||||
var span = SourceSpan.Merge(node.Tokens.Select(x => x.FileSpan?.Span ?? SourceSpan.Zero));
|
|
||||||
At(new SourceFileSpan(first.FileSpan.SourceFile, span));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public DiagnosticBuilder At(Token? token)
|
|
||||||
{
|
|
||||||
if (token != null)
|
|
||||||
{
|
|
||||||
At(token.FileSpan);
|
|
||||||
}
|
|
||||||
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public DiagnosticBuilder At(SourceFileSpan? fileSpan)
|
|
||||||
{
|
|
||||||
if (fileSpan != null)
|
|
||||||
{
|
|
||||||
_fileSpan = fileSpan;
|
|
||||||
}
|
|
||||||
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public DiagnosticBuilder WithHelp(string help)
|
|
||||||
{
|
|
||||||
_help = help;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Diagnostic Build() => new(_severity, _message, _help, _fileSpan);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static DiagnosticBuilder Error(string message) => new(DiagnosticSeverity.Error, message);
|
|
||||||
public static DiagnosticBuilder Warning(string message) => new(DiagnosticSeverity.Warning, message);
|
|
||||||
public static DiagnosticBuilder Info(string message) => new(DiagnosticSeverity.Info, message);
|
|
||||||
|
|
||||||
public DiagnosticSeverity Severity { get; }
|
|
||||||
public string Message { get; }
|
|
||||||
public string? Help { get; }
|
|
||||||
public SourceFileSpan? FileSpan { get; }
|
|
||||||
|
|
||||||
private Diagnostic(DiagnosticSeverity severity, string message, string? help, SourceFileSpan? fileSpan)
|
|
||||||
{
|
|
||||||
Severity = severity;
|
|
||||||
Message = message;
|
|
||||||
Help = help;
|
|
||||||
FileSpan = fileSpan;
|
|
||||||
}
|
|
||||||
|
|
||||||
public string FormatANSI()
|
|
||||||
{
|
|
||||||
var sb = new StringBuilder();
|
|
||||||
|
|
||||||
sb.Append(Severity switch
|
|
||||||
{
|
|
||||||
DiagnosticSeverity.Error => ConsoleColors.Colorize("error", ConsoleColors.Bold + ConsoleColors.Red),
|
|
||||||
DiagnosticSeverity.Warning => ConsoleColors.Colorize("warning", ConsoleColors.Bold + ConsoleColors.Yellow),
|
|
||||||
DiagnosticSeverity.Info => ConsoleColors.Colorize("info", ConsoleColors.Bold + ConsoleColors.Blue),
|
|
||||||
_ => ConsoleColors.Colorize("unknown", ConsoleColors.Bold + ConsoleColors.White)
|
|
||||||
});
|
|
||||||
|
|
||||||
if (FileSpan != null)
|
|
||||||
{
|
|
||||||
sb.Append(ConsoleColors.Colorize($" at {FileSpan.SourceFile.Path}:{FileSpan.Span}", ConsoleColors.Faint));
|
|
||||||
}
|
|
||||||
|
|
||||||
sb.Append(": ");
|
|
||||||
sb.Append(ConsoleColors.Colorize(Message, ConsoleColors.BrightWhite));
|
|
||||||
|
|
||||||
if (FileSpan != null)
|
|
||||||
{
|
|
||||||
sb.AppendLine();
|
|
||||||
var text = FileSpan.SourceFile.GetText();
|
|
||||||
|
|
||||||
var lines = text.Split('\n');
|
|
||||||
|
|
||||||
var startLine = FileSpan.Span.Start.Line;
|
|
||||||
var endLine = FileSpan.Span.End.Line;
|
|
||||||
|
|
||||||
const int CONTEXT_LINES = 3;
|
|
||||||
|
|
||||||
var contextStartLine = Math.Max(1, startLine - CONTEXT_LINES);
|
|
||||||
var contextEndLine = Math.Min(lines.Length, endLine + CONTEXT_LINES);
|
|
||||||
|
|
||||||
var numberPadding = contextEndLine.ToString().Length;
|
|
||||||
var codePadding = lines.Skip(contextStartLine - 1).Take(contextEndLine - contextStartLine).Max(x => x.Length);
|
|
||||||
|
|
||||||
sb.Append('╭');
|
|
||||||
sb.Append(new string('─', numberPadding + 2));
|
|
||||||
sb.Append('┬');
|
|
||||||
sb.Append(new string('─', codePadding + 2));
|
|
||||||
sb.Append('╮');
|
|
||||||
sb.AppendLine();
|
|
||||||
|
|
||||||
var tokenizer = new Tokenizer(FileSpan.SourceFile);
|
|
||||||
var tokens = tokenizer.Tokenize().ToList();
|
|
||||||
|
|
||||||
for (var i = contextStartLine; i <= contextEndLine; i++)
|
|
||||||
{
|
|
||||||
var line = lines[i - 1];
|
|
||||||
|
|
||||||
sb.Append("│ ");
|
|
||||||
sb.Append(i.ToString().PadRight(numberPadding));
|
|
||||||
sb.Append(" │ ");
|
|
||||||
sb.Append(ApplySyntaxHighlighting(line.PadRight(codePadding), i, tokens));
|
|
||||||
sb.Append(" │");
|
|
||||||
sb.AppendLine();
|
|
||||||
|
|
||||||
if (i >= startLine && i <= endLine)
|
|
||||||
{
|
|
||||||
var markerStartColumn = 1;
|
|
||||||
var markerEndColumn = line.Length + 1;
|
|
||||||
|
|
||||||
if (i == startLine)
|
|
||||||
{
|
|
||||||
markerStartColumn = FileSpan.Span.Start.Column;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (i == endLine)
|
|
||||||
{
|
|
||||||
markerEndColumn = FileSpan.Span.End.Column;
|
|
||||||
}
|
|
||||||
|
|
||||||
var markerLength = markerEndColumn - markerStartColumn;
|
|
||||||
var marker = new string('^', markerLength);
|
|
||||||
|
|
||||||
var markerColor = Severity switch
|
|
||||||
{
|
|
||||||
DiagnosticSeverity.Info => ConsoleColors.Blue,
|
|
||||||
DiagnosticSeverity.Warning => ConsoleColors.Yellow,
|
|
||||||
DiagnosticSeverity.Error => ConsoleColors.Red,
|
|
||||||
_ => ConsoleColors.White
|
|
||||||
};
|
|
||||||
|
|
||||||
sb.Append("│ ");
|
|
||||||
sb.Append(new string(' ', numberPadding));
|
|
||||||
sb.Append(" │ ");
|
|
||||||
sb.Append(new string(' ', markerStartColumn - 1));
|
|
||||||
sb.Append(ConsoleColors.Colorize(marker, markerColor));
|
|
||||||
sb.Append(new string(' ', codePadding - markerEndColumn + 1));
|
|
||||||
sb.Append(" │");
|
|
||||||
sb.AppendLine();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
sb.Append('╰');
|
|
||||||
sb.Append(new string('─', numberPadding + 2));
|
|
||||||
sb.Append('┴');
|
|
||||||
sb.Append(new string('─', codePadding + 2));
|
|
||||||
sb.Append('╯');
|
|
||||||
}
|
|
||||||
|
|
||||||
if (Help != null)
|
|
||||||
{
|
|
||||||
sb.AppendLine();
|
|
||||||
sb.Append(ConsoleColors.Colorize($"help: {Help}", ConsoleColors.Cyan));
|
|
||||||
}
|
|
||||||
|
|
||||||
return sb.ToString();
|
|
||||||
}
|
|
||||||
|
|
||||||
private static string ApplySyntaxHighlighting(string line, int lineNumber, List<Token> tokens)
|
|
||||||
{
|
|
||||||
var sb = new StringBuilder();
|
|
||||||
var lineTokens = tokens
|
|
||||||
.Where(t => t.FileSpan.Span.Start.Line == lineNumber)
|
|
||||||
.OrderBy(t => t.FileSpan.Span.Start.Column)
|
|
||||||
.ToList();
|
|
||||||
|
|
||||||
if (lineTokens.Count == 0)
|
|
||||||
{
|
|
||||||
return line;
|
|
||||||
}
|
|
||||||
|
|
||||||
var currentColumn = 1;
|
|
||||||
|
|
||||||
foreach (var token in lineTokens)
|
|
||||||
{
|
|
||||||
var tokenStart = token.FileSpan.Span.Start.Column;
|
|
||||||
var tokenEnd = token.FileSpan.Span.End.Column;
|
|
||||||
|
|
||||||
if (tokenStart > currentColumn)
|
|
||||||
{
|
|
||||||
var beforeToken = line.Substring(currentColumn - 1, tokenStart - currentColumn);
|
|
||||||
sb.Append(beforeToken);
|
|
||||||
}
|
|
||||||
|
|
||||||
var tokenLength = tokenEnd - tokenStart;
|
|
||||||
if (tokenStart - 1 + tokenLength <= line.Length)
|
|
||||||
{
|
|
||||||
var tokenText = line.Substring(tokenStart - 1, tokenLength);
|
|
||||||
|
|
||||||
var coloredToken = ColorizeToken(token, tokenText);
|
|
||||||
sb.Append(coloredToken);
|
|
||||||
}
|
|
||||||
|
|
||||||
currentColumn = tokenEnd;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (currentColumn <= line.Length)
|
|
||||||
{
|
|
||||||
var remaining = line[(currentColumn - 1)..];
|
|
||||||
sb.Append(remaining);
|
|
||||||
}
|
|
||||||
|
|
||||||
return sb.ToString();
|
|
||||||
}
|
|
||||||
|
|
||||||
private static string ColorizeToken(Token token, string tokenText)
|
|
||||||
{
|
|
||||||
switch (token)
|
|
||||||
{
|
|
||||||
case IdentifierToken:
|
|
||||||
{
|
|
||||||
return ConsoleColors.Colorize(tokenText, ConsoleColors.BrightWhite);
|
|
||||||
}
|
|
||||||
case LiteralToken literal:
|
|
||||||
{
|
|
||||||
if (literal.Kind == LiteralKind.String)
|
|
||||||
{
|
|
||||||
return ConsoleColors.Colorize(tokenText, ConsoleColors.Green);
|
|
||||||
}
|
|
||||||
|
|
||||||
return ConsoleColors.Colorize(tokenText, ConsoleColors.Magenta);
|
|
||||||
}
|
|
||||||
case SymbolToken symbolToken:
|
|
||||||
{
|
|
||||||
switch (symbolToken.Symbol)
|
|
||||||
{
|
|
||||||
case Symbol.Func:
|
|
||||||
case Symbol.Return:
|
|
||||||
case Symbol.If:
|
|
||||||
case Symbol.Else:
|
|
||||||
case Symbol.While:
|
|
||||||
case Symbol.Break:
|
|
||||||
case Symbol.Continue:
|
|
||||||
case Symbol.Struct:
|
|
||||||
case Symbol.Let:
|
|
||||||
case Symbol.Calls:
|
|
||||||
case Symbol.Interface:
|
|
||||||
case Symbol.For:
|
|
||||||
case Symbol.Extern:
|
|
||||||
{
|
|
||||||
return ConsoleColors.Colorize(tokenText, ConsoleColors.Bold + ConsoleColors.Blue);
|
|
||||||
}
|
|
||||||
case Symbol.Assign:
|
|
||||||
case Symbol.Bang:
|
|
||||||
case Symbol.Equal:
|
|
||||||
case Symbol.NotEqual:
|
|
||||||
case Symbol.LessThan:
|
|
||||||
case Symbol.LessThanOrEqual:
|
|
||||||
case Symbol.GreaterThan:
|
|
||||||
case Symbol.GreaterThanOrEqual:
|
|
||||||
case Symbol.Plus:
|
|
||||||
case Symbol.Minus:
|
|
||||||
case Symbol.Star:
|
|
||||||
case Symbol.ForwardSlash:
|
|
||||||
case Symbol.Caret:
|
|
||||||
case Symbol.Ampersand:
|
|
||||||
{
|
|
||||||
return ConsoleColors.Colorize(tokenText, ConsoleColors.Yellow);
|
|
||||||
}
|
|
||||||
case Symbol.Colon:
|
|
||||||
case Symbol.OpenParen:
|
|
||||||
case Symbol.CloseParen:
|
|
||||||
case Symbol.OpenBrace:
|
|
||||||
case Symbol.CloseBrace:
|
|
||||||
case Symbol.OpenBracket:
|
|
||||||
case Symbol.CloseBracket:
|
|
||||||
case Symbol.Comma:
|
|
||||||
case Symbol.Period:
|
|
||||||
case Symbol.Semi:
|
|
||||||
{
|
|
||||||
return ConsoleColors.Colorize(tokenText, ConsoleColors.BrightBlack);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return tokenText;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public enum DiagnosticSeverity
|
|
||||||
{
|
|
||||||
Info,
|
|
||||||
Warning,
|
|
||||||
Error
|
|
||||||
}
|
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -1,39 +0,0 @@
|
|||||||
using System.Text;
|
|
||||||
|
|
||||||
namespace NubLang.Generation.QBE;
|
|
||||||
|
|
||||||
internal class QBEWriter
|
|
||||||
{
|
|
||||||
private readonly StringBuilder _builder = new();
|
|
||||||
|
|
||||||
public void Indented(string value)
|
|
||||||
{
|
|
||||||
_builder.Append('\t');
|
|
||||||
_builder.AppendLine(value);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Comment(string comment)
|
|
||||||
{
|
|
||||||
_builder.AppendLine("# " + comment);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void WriteLine(string text)
|
|
||||||
{
|
|
||||||
_builder.AppendLine(text);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Write(string text)
|
|
||||||
{
|
|
||||||
_builder.Append(text);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void NewLine()
|
|
||||||
{
|
|
||||||
_builder.AppendLine();
|
|
||||||
}
|
|
||||||
|
|
||||||
public override string ToString()
|
|
||||||
{
|
|
||||||
return _builder.ToString();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,10 +0,0 @@
|
|||||||
<Project Sdk="Microsoft.NET.Sdk">
|
|
||||||
|
|
||||||
<PropertyGroup>
|
|
||||||
<TargetFramework>net9.0</TargetFramework>
|
|
||||||
<ImplicitUsings>enable</ImplicitUsings>
|
|
||||||
<Nullable>enable</Nullable>
|
|
||||||
<IsAotCompatible>true</IsAotCompatible>
|
|
||||||
</PropertyGroup>
|
|
||||||
|
|
||||||
</Project>
|
|
||||||
@@ -1,82 +0,0 @@
|
|||||||
using System.Diagnostics.CodeAnalysis;
|
|
||||||
|
|
||||||
namespace NubLang;
|
|
||||||
|
|
||||||
public static class Optional
|
|
||||||
{
|
|
||||||
public static Optional<TValue> Empty<TValue>() => new();
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Alias for creating an Optional which allows for implicit types
|
|
||||||
/// </summary>
|
|
||||||
public static Optional<TValue> OfNullable<TValue>(TValue? value)
|
|
||||||
{
|
|
||||||
return value ?? Optional<TValue>.Empty();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Converts a nullable type to an Optional
|
|
||||||
/// </summary>
|
|
||||||
public static Optional<TValue> ToOptional<TValue>(this TValue? value)
|
|
||||||
{
|
|
||||||
return OfNullable(value);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public readonly struct Optional<TValue>
|
|
||||||
{
|
|
||||||
public static Optional<TValue> Empty() => new();
|
|
||||||
|
|
||||||
public static Optional<TValue> OfNullable(TValue? value)
|
|
||||||
{
|
|
||||||
return value ?? Empty();
|
|
||||||
}
|
|
||||||
|
|
||||||
public Optional()
|
|
||||||
{
|
|
||||||
Value = default;
|
|
||||||
HasValue = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Optional(TValue value)
|
|
||||||
{
|
|
||||||
Value = value;
|
|
||||||
HasValue = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
public TValue? Value { get; }
|
|
||||||
|
|
||||||
[MemberNotNullWhen(true, nameof(Value))]
|
|
||||||
public bool HasValue { get; }
|
|
||||||
|
|
||||||
|
|
||||||
[MemberNotNullWhen(true, nameof(Value))]
|
|
||||||
public bool TryGetValue([NotNullWhen(true)] out TValue? value)
|
|
||||||
{
|
|
||||||
if (HasValue)
|
|
||||||
{
|
|
||||||
value = Value;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
value = default;
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
public TValue GetValue()
|
|
||||||
{
|
|
||||||
return Value ?? throw new InvalidOperationException("Value is not set");
|
|
||||||
}
|
|
||||||
|
|
||||||
public static implicit operator Optional<TValue>(TValue value) => new(value);
|
|
||||||
|
|
||||||
public TValue Or(TValue other)
|
|
||||||
{
|
|
||||||
if (HasValue)
|
|
||||||
{
|
|
||||||
return Value;
|
|
||||||
}
|
|
||||||
|
|
||||||
return other;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,856 +0,0 @@
|
|||||||
using System.Diagnostics.CodeAnalysis;
|
|
||||||
using NubLang.Diagnostics;
|
|
||||||
using NubLang.Parsing.Syntax;
|
|
||||||
using NubLang.Tokenization;
|
|
||||||
|
|
||||||
namespace NubLang.Parsing;
|
|
||||||
|
|
||||||
public sealed class Parser
|
|
||||||
{
|
|
||||||
private readonly List<Diagnostic> _diagnostics = [];
|
|
||||||
private IReadOnlyList<Token> _tokens = [];
|
|
||||||
private int _tokenIndex;
|
|
||||||
private string _moduleName = string.Empty;
|
|
||||||
|
|
||||||
private Token? CurrentToken => _tokenIndex < _tokens.Count ? _tokens[_tokenIndex] : null;
|
|
||||||
private bool HasToken => CurrentToken != null;
|
|
||||||
|
|
||||||
public IReadOnlyList<Diagnostic> GetDiagnostics()
|
|
||||||
{
|
|
||||||
return _diagnostics;
|
|
||||||
}
|
|
||||||
|
|
||||||
public SyntaxTree Parse(IReadOnlyList<Token> tokens)
|
|
||||||
{
|
|
||||||
_diagnostics.Clear();
|
|
||||||
_tokens = tokens;
|
|
||||||
_tokenIndex = 0;
|
|
||||||
_moduleName = string.Empty;
|
|
||||||
|
|
||||||
var metadata = ParseMetadata();
|
|
||||||
var definitions = ParseDefinitions();
|
|
||||||
|
|
||||||
return new SyntaxTree(definitions, metadata);
|
|
||||||
}
|
|
||||||
|
|
||||||
private SyntaxTreeMetadata ParseMetadata()
|
|
||||||
{
|
|
||||||
var imports = new List<string>();
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
ExpectSymbol(Symbol.Module);
|
|
||||||
_moduleName = ExpectIdentifier().Value;
|
|
||||||
|
|
||||||
while (TryExpectSymbol(Symbol.Import))
|
|
||||||
{
|
|
||||||
imports.Add(ExpectIdentifier().Value);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch (ParseException e)
|
|
||||||
{
|
|
||||||
_diagnostics.Add(e.Diagnostic);
|
|
||||||
while (HasToken)
|
|
||||||
{
|
|
||||||
if (CurrentToken is SymbolToken { Symbol: Symbol.Module or Symbol.Import })
|
|
||||||
{
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
Next();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return new SyntaxTreeMetadata(_moduleName, imports);
|
|
||||||
}
|
|
||||||
|
|
||||||
private List<DefinitionSyntax> ParseDefinitions()
|
|
||||||
{
|
|
||||||
var definitions = new List<DefinitionSyntax>();
|
|
||||||
|
|
||||||
while (HasToken)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
var startIndex = _tokenIndex;
|
|
||||||
var keyword = ExpectSymbol();
|
|
||||||
var definition = keyword.Symbol switch
|
|
||||||
{
|
|
||||||
Symbol.Extern => ParseExtern(startIndex),
|
|
||||||
Symbol.Func => ParseFunc(startIndex),
|
|
||||||
Symbol.Struct => ParseStruct(startIndex),
|
|
||||||
Symbol.Interface => ParseInterface(startIndex),
|
|
||||||
_ => throw new ParseException(Diagnostic
|
|
||||||
.Error($"Expected 'extern', 'func', 'struct' or 'interface' but found '{keyword.Symbol}'")
|
|
||||||
.WithHelp("Valid definition keywords are 'extern', 'func', 'struct' and 'interface'")
|
|
||||||
.At(keyword)
|
|
||||||
.Build())
|
|
||||||
};
|
|
||||||
|
|
||||||
definitions.Add(definition);
|
|
||||||
}
|
|
||||||
catch (ParseException e)
|
|
||||||
{
|
|
||||||
_diagnostics.Add(e.Diagnostic);
|
|
||||||
while (HasToken)
|
|
||||||
{
|
|
||||||
if (CurrentToken is SymbolToken { Symbol: Symbol.Extern or Symbol.Func or Symbol.Struct or Symbol.Interface })
|
|
||||||
{
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
Next();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return definitions;
|
|
||||||
}
|
|
||||||
|
|
||||||
private FuncSignatureSyntax ParseFuncSignature()
|
|
||||||
{
|
|
||||||
var startIndex = _tokenIndex;
|
|
||||||
List<FuncParameterSyntax> parameters = [];
|
|
||||||
|
|
||||||
ExpectSymbol(Symbol.OpenParen);
|
|
||||||
|
|
||||||
while (!TryExpectSymbol(Symbol.CloseParen))
|
|
||||||
{
|
|
||||||
parameters.Add(ParseFuncParameter());
|
|
||||||
|
|
||||||
if (!TryExpectSymbol(Symbol.Comma))
|
|
||||||
{
|
|
||||||
ExpectSymbol(Symbol.CloseParen);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var returnType = TryExpectSymbol(Symbol.Colon) ? ParseType() : new VoidTypeSyntax([]);
|
|
||||||
|
|
||||||
return new FuncSignatureSyntax(GetTokens(startIndex), parameters, returnType);
|
|
||||||
}
|
|
||||||
|
|
||||||
private FuncParameterSyntax ParseFuncParameter()
|
|
||||||
{
|
|
||||||
var startIndex = _tokenIndex;
|
|
||||||
var name = ExpectIdentifier();
|
|
||||||
ExpectSymbol(Symbol.Colon);
|
|
||||||
var type = ParseType();
|
|
||||||
|
|
||||||
return new FuncParameterSyntax(GetTokens(startIndex), name.Value, type);
|
|
||||||
}
|
|
||||||
|
|
||||||
private DefinitionSyntax ParseExtern(int startIndex)
|
|
||||||
{
|
|
||||||
var keyword = ExpectSymbol();
|
|
||||||
|
|
||||||
return keyword.Symbol switch
|
|
||||||
{
|
|
||||||
Symbol.Func => ParseExternFunc(startIndex),
|
|
||||||
_ => throw new ParseException(Diagnostic
|
|
||||||
.Error($"Unexpected symbol {keyword.Symbol} after extern declaration")
|
|
||||||
.At(keyword)
|
|
||||||
.Build())
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
private ExternFuncSyntax ParseExternFunc(int startIndex)
|
|
||||||
{
|
|
||||||
var name = ExpectIdentifier();
|
|
||||||
|
|
||||||
var callName = name.Value;
|
|
||||||
|
|
||||||
if (TryExpectSymbol(Symbol.Calls))
|
|
||||||
{
|
|
||||||
callName = ExpectIdentifier().Value;
|
|
||||||
}
|
|
||||||
|
|
||||||
var signature = ParseFuncSignature();
|
|
||||||
|
|
||||||
return new ExternFuncSyntax(GetTokens(startIndex), name.Value, callName, signature);
|
|
||||||
}
|
|
||||||
|
|
||||||
private FuncSyntax ParseFunc(int startIndex)
|
|
||||||
{
|
|
||||||
var name = ExpectIdentifier();
|
|
||||||
var signature = ParseFuncSignature();
|
|
||||||
var body = ParseBlock();
|
|
||||||
|
|
||||||
return new FuncSyntax(GetTokens(startIndex), name.Value, signature, body);
|
|
||||||
}
|
|
||||||
|
|
||||||
private DefinitionSyntax ParseStruct(int startIndex)
|
|
||||||
{
|
|
||||||
var name = ExpectIdentifier();
|
|
||||||
var interfaceImplementations = new List<TypeSyntax>();
|
|
||||||
|
|
||||||
if (TryExpectSymbol(Symbol.Colon))
|
|
||||||
{
|
|
||||||
do
|
|
||||||
{
|
|
||||||
var interfaceType = ParseType();
|
|
||||||
interfaceImplementations.Add(interfaceType);
|
|
||||||
} while (TryExpectSymbol(Symbol.Comma));
|
|
||||||
}
|
|
||||||
|
|
||||||
ExpectSymbol(Symbol.OpenBrace);
|
|
||||||
|
|
||||||
List<StructFieldSyntax> fields = [];
|
|
||||||
List<StructFuncSyntax> funcs = [];
|
|
||||||
|
|
||||||
var fieldIndex = 0;
|
|
||||||
|
|
||||||
while (!TryExpectSymbol(Symbol.CloseBrace))
|
|
||||||
{
|
|
||||||
var memberStartIndex = _tokenIndex;
|
|
||||||
|
|
||||||
if (TryExpectSymbol(Symbol.Func))
|
|
||||||
{
|
|
||||||
var funcName = ExpectIdentifier().Value;
|
|
||||||
var funcSignature = ParseFuncSignature();
|
|
||||||
var funcBody = ParseBlock();
|
|
||||||
|
|
||||||
funcs.Add(new StructFuncSyntax(GetTokens(memberStartIndex), funcName, funcSignature, funcBody));
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
var fieldName = ExpectIdentifier().Value;
|
|
||||||
ExpectSymbol(Symbol.Colon);
|
|
||||||
var fieldType = ParseType();
|
|
||||||
|
|
||||||
var fieldValue = Optional<ExpressionSyntax>.Empty();
|
|
||||||
|
|
||||||
if (TryExpectSymbol(Symbol.Assign))
|
|
||||||
{
|
|
||||||
fieldValue = ParseExpression();
|
|
||||||
}
|
|
||||||
|
|
||||||
fields.Add(new StructFieldSyntax(GetTokens(memberStartIndex), fieldIndex++, fieldName, fieldType, fieldValue));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return new StructSyntax(GetTokens(startIndex), name.Value, fields, funcs, interfaceImplementations);
|
|
||||||
}
|
|
||||||
|
|
||||||
private InterfaceSyntax ParseInterface(int startIndex)
|
|
||||||
{
|
|
||||||
var name = ExpectIdentifier();
|
|
||||||
|
|
||||||
ExpectSymbol(Symbol.OpenBrace);
|
|
||||||
|
|
||||||
List<InterfaceFuncSyntax> functions = [];
|
|
||||||
|
|
||||||
while (!TryExpectSymbol(Symbol.CloseBrace))
|
|
||||||
{
|
|
||||||
var funcStartIndex = _tokenIndex;
|
|
||||||
|
|
||||||
ExpectSymbol(Symbol.Func);
|
|
||||||
|
|
||||||
var funcName = ExpectIdentifier().Value;
|
|
||||||
var signature = ParseFuncSignature();
|
|
||||||
|
|
||||||
functions.Add(new InterfaceFuncSyntax(GetTokens(funcStartIndex), funcName, signature));
|
|
||||||
}
|
|
||||||
|
|
||||||
return new InterfaceSyntax(GetTokens(startIndex), name.Value, functions);
|
|
||||||
}
|
|
||||||
|
|
||||||
private StatementSyntax ParseStatement()
|
|
||||||
{
|
|
||||||
if (CurrentToken is SymbolToken symbol)
|
|
||||||
{
|
|
||||||
switch (symbol.Symbol)
|
|
||||||
{
|
|
||||||
case Symbol.Return:
|
|
||||||
return ParseReturn();
|
|
||||||
case Symbol.If:
|
|
||||||
return ParseIf();
|
|
||||||
case Symbol.While:
|
|
||||||
return ParseWhile();
|
|
||||||
case Symbol.Let:
|
|
||||||
return ParseVariableDeclaration();
|
|
||||||
case Symbol.Break:
|
|
||||||
return ParseBreak();
|
|
||||||
case Symbol.Continue:
|
|
||||||
return ParseContinue();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return ParseStatementExpression();
|
|
||||||
}
|
|
||||||
|
|
||||||
private StatementSyntax ParseStatementExpression()
|
|
||||||
{
|
|
||||||
var startIndex = _tokenIndex;
|
|
||||||
var expr = ParseExpression();
|
|
||||||
|
|
||||||
if (TryExpectSymbol(Symbol.Assign))
|
|
||||||
{
|
|
||||||
var value = ParseExpression();
|
|
||||||
return new AssignmentSyntax(GetTokens(startIndex), expr, value);
|
|
||||||
}
|
|
||||||
|
|
||||||
return new StatementExpressionSyntax(GetTokens(startIndex), expr);
|
|
||||||
}
|
|
||||||
|
|
||||||
private VariableDeclarationSyntax ParseVariableDeclaration()
|
|
||||||
{
|
|
||||||
var startIndex = _tokenIndex;
|
|
||||||
ExpectSymbol(Symbol.Let);
|
|
||||||
var name = ExpectIdentifier().Value;
|
|
||||||
|
|
||||||
var explicitType = Optional<TypeSyntax>.Empty();
|
|
||||||
if (TryExpectSymbol(Symbol.Colon))
|
|
||||||
{
|
|
||||||
explicitType = ParseType();
|
|
||||||
}
|
|
||||||
|
|
||||||
var assignment = Optional<ExpressionSyntax>.Empty();
|
|
||||||
if (TryExpectSymbol(Symbol.Assign))
|
|
||||||
{
|
|
||||||
assignment = ParseExpression();
|
|
||||||
}
|
|
||||||
|
|
||||||
return new VariableDeclarationSyntax(GetTokens(startIndex), name, explicitType, assignment);
|
|
||||||
}
|
|
||||||
|
|
||||||
private StatementSyntax ParseBreak()
|
|
||||||
{
|
|
||||||
var startIndex = _tokenIndex;
|
|
||||||
ExpectSymbol(Symbol.Break);
|
|
||||||
return new BreakSyntax(GetTokens(startIndex));
|
|
||||||
}
|
|
||||||
|
|
||||||
private StatementSyntax ParseContinue()
|
|
||||||
{
|
|
||||||
var startIndex = _tokenIndex;
|
|
||||||
ExpectSymbol(Symbol.Continue);
|
|
||||||
return new ContinueSyntax(GetTokens(startIndex));
|
|
||||||
}
|
|
||||||
|
|
||||||
private ReturnSyntax ParseReturn()
|
|
||||||
{
|
|
||||||
var startIndex = _tokenIndex;
|
|
||||||
ExpectSymbol(Symbol.Return);
|
|
||||||
|
|
||||||
var value = Optional<ExpressionSyntax>.Empty();
|
|
||||||
|
|
||||||
if (!TryExpectSymbol(Symbol.Semi))
|
|
||||||
{
|
|
||||||
value = ParseExpression();
|
|
||||||
}
|
|
||||||
|
|
||||||
return new ReturnSyntax(GetTokens(startIndex), value);
|
|
||||||
}
|
|
||||||
|
|
||||||
private IfSyntax ParseIf()
|
|
||||||
{
|
|
||||||
var startIndex = _tokenIndex;
|
|
||||||
ExpectSymbol(Symbol.If);
|
|
||||||
var condition = ParseExpression();
|
|
||||||
var body = ParseBlock();
|
|
||||||
|
|
||||||
var elseStatement = Optional<Variant<IfSyntax, BlockSyntax>>.Empty();
|
|
||||||
if (TryExpectSymbol(Symbol.Else))
|
|
||||||
{
|
|
||||||
elseStatement = TryExpectSymbol(Symbol.If)
|
|
||||||
? (Variant<IfSyntax, BlockSyntax>)ParseIf()
|
|
||||||
: (Variant<IfSyntax, BlockSyntax>)ParseBlock();
|
|
||||||
}
|
|
||||||
|
|
||||||
return new IfSyntax(GetTokens(startIndex), condition, body, elseStatement);
|
|
||||||
}
|
|
||||||
|
|
||||||
private WhileSyntax ParseWhile()
|
|
||||||
{
|
|
||||||
var startIndex = _tokenIndex;
|
|
||||||
ExpectSymbol(Symbol.While);
|
|
||||||
var condition = ParseExpression();
|
|
||||||
var body = ParseBlock();
|
|
||||||
return new WhileSyntax(GetTokens(startIndex), condition, body);
|
|
||||||
}
|
|
||||||
|
|
||||||
private ExpressionSyntax ParseExpression(int precedence = 0)
|
|
||||||
{
|
|
||||||
var startIndex = _tokenIndex;
|
|
||||||
var left = ParsePrimaryExpression();
|
|
||||||
|
|
||||||
while (CurrentToken is SymbolToken symbolToken && TryGetBinaryOperator(symbolToken.Symbol, out var op) && GetBinaryOperatorPrecedence(op.Value) >= precedence)
|
|
||||||
{
|
|
||||||
Next();
|
|
||||||
var right = ParseExpression(GetBinaryOperatorPrecedence(op.Value) + 1);
|
|
||||||
left = new BinaryExpressionSyntax(GetTokens(startIndex), left, op.Value, right);
|
|
||||||
}
|
|
||||||
|
|
||||||
return left;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static int GetBinaryOperatorPrecedence(BinaryOperatorSyntax operatorSyntax)
|
|
||||||
{
|
|
||||||
return operatorSyntax switch
|
|
||||||
{
|
|
||||||
BinaryOperatorSyntax.Multiply => 10,
|
|
||||||
BinaryOperatorSyntax.Divide => 10,
|
|
||||||
BinaryOperatorSyntax.Modulo => 10,
|
|
||||||
|
|
||||||
BinaryOperatorSyntax.Plus => 9,
|
|
||||||
BinaryOperatorSyntax.Minus => 9,
|
|
||||||
|
|
||||||
BinaryOperatorSyntax.LeftShift => 8,
|
|
||||||
BinaryOperatorSyntax.RightShift => 8,
|
|
||||||
|
|
||||||
BinaryOperatorSyntax.GreaterThan => 7,
|
|
||||||
BinaryOperatorSyntax.GreaterThanOrEqual => 7,
|
|
||||||
BinaryOperatorSyntax.LessThan => 7,
|
|
||||||
BinaryOperatorSyntax.LessThanOrEqual => 7,
|
|
||||||
|
|
||||||
BinaryOperatorSyntax.Equal => 7,
|
|
||||||
BinaryOperatorSyntax.NotEqual => 7,
|
|
||||||
|
|
||||||
BinaryOperatorSyntax.BitwiseAnd => 6,
|
|
||||||
BinaryOperatorSyntax.BitwiseXor => 5,
|
|
||||||
BinaryOperatorSyntax.BitwiseOr => 4,
|
|
||||||
|
|
||||||
BinaryOperatorSyntax.LogicalAnd => 3,
|
|
||||||
BinaryOperatorSyntax.LogicalOr => 2,
|
|
||||||
|
|
||||||
_ => throw new ArgumentOutOfRangeException(nameof(operatorSyntax), operatorSyntax, null)
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
private bool TryGetBinaryOperator(Symbol symbol, [NotNullWhen(true)] out BinaryOperatorSyntax? binaryExpressionOperator)
|
|
||||||
{
|
|
||||||
switch (symbol)
|
|
||||||
{
|
|
||||||
case Symbol.Equal:
|
|
||||||
binaryExpressionOperator = BinaryOperatorSyntax.Equal;
|
|
||||||
return true;
|
|
||||||
case Symbol.NotEqual:
|
|
||||||
binaryExpressionOperator = BinaryOperatorSyntax.NotEqual;
|
|
||||||
return true;
|
|
||||||
case Symbol.LessThan:
|
|
||||||
binaryExpressionOperator = BinaryOperatorSyntax.LessThan;
|
|
||||||
return true;
|
|
||||||
case Symbol.LessThanOrEqual:
|
|
||||||
binaryExpressionOperator = BinaryOperatorSyntax.LessThanOrEqual;
|
|
||||||
return true;
|
|
||||||
case Symbol.GreaterThan:
|
|
||||||
binaryExpressionOperator = BinaryOperatorSyntax.GreaterThan;
|
|
||||||
return true;
|
|
||||||
case Symbol.GreaterThanOrEqual:
|
|
||||||
binaryExpressionOperator = BinaryOperatorSyntax.GreaterThanOrEqual;
|
|
||||||
return true;
|
|
||||||
case Symbol.And:
|
|
||||||
binaryExpressionOperator = BinaryOperatorSyntax.LogicalAnd;
|
|
||||||
return true;
|
|
||||||
case Symbol.Or:
|
|
||||||
binaryExpressionOperator = BinaryOperatorSyntax.LogicalOr;
|
|
||||||
return true;
|
|
||||||
case Symbol.Plus:
|
|
||||||
binaryExpressionOperator = BinaryOperatorSyntax.Plus;
|
|
||||||
return true;
|
|
||||||
case Symbol.Minus:
|
|
||||||
binaryExpressionOperator = BinaryOperatorSyntax.Minus;
|
|
||||||
return true;
|
|
||||||
case Symbol.Star:
|
|
||||||
binaryExpressionOperator = BinaryOperatorSyntax.Multiply;
|
|
||||||
return true;
|
|
||||||
case Symbol.ForwardSlash:
|
|
||||||
binaryExpressionOperator = BinaryOperatorSyntax.Divide;
|
|
||||||
return true;
|
|
||||||
case Symbol.Percent:
|
|
||||||
binaryExpressionOperator = BinaryOperatorSyntax.Modulo;
|
|
||||||
return true;
|
|
||||||
case Symbol.LeftShift:
|
|
||||||
binaryExpressionOperator = BinaryOperatorSyntax.LeftShift;
|
|
||||||
return true;
|
|
||||||
case Symbol.RightShift:
|
|
||||||
binaryExpressionOperator = BinaryOperatorSyntax.RightShift;
|
|
||||||
return true;
|
|
||||||
case Symbol.Ampersand:
|
|
||||||
binaryExpressionOperator = BinaryOperatorSyntax.BitwiseAnd;
|
|
||||||
return true;
|
|
||||||
case Symbol.Pipe:
|
|
||||||
binaryExpressionOperator = BinaryOperatorSyntax.BitwiseOr;
|
|
||||||
return true;
|
|
||||||
case Symbol.Caret:
|
|
||||||
binaryExpressionOperator = BinaryOperatorSyntax.BitwiseXor;
|
|
||||||
return true;
|
|
||||||
default:
|
|
||||||
binaryExpressionOperator = null;
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private ExpressionSyntax ParsePrimaryExpression()
|
|
||||||
{
|
|
||||||
var startIndex = _tokenIndex;
|
|
||||||
var token = ExpectToken();
|
|
||||||
var expr = token switch
|
|
||||||
{
|
|
||||||
LiteralToken literal => new LiteralSyntax(GetTokens(startIndex), literal.Value, literal.Kind),
|
|
||||||
IdentifierToken identifier => new IdentifierSyntax(GetTokens(startIndex), Optional<string>.Empty(), identifier.Value),
|
|
||||||
SymbolToken symbolToken => symbolToken.Symbol switch
|
|
||||||
{
|
|
||||||
Symbol.OpenParen => ParseParenthesizedExpression(),
|
|
||||||
Symbol.Minus => new UnaryExpressionSyntax(GetTokens(startIndex), UnaryOperatorSyntax.Negate, ParsePrimaryExpression()),
|
|
||||||
Symbol.Bang => new UnaryExpressionSyntax(GetTokens(startIndex), UnaryOperatorSyntax.Invert, ParsePrimaryExpression()),
|
|
||||||
Symbol.OpenBracket => ParseArrayInitializer(startIndex),
|
|
||||||
Symbol.OpenBrace => new StructInitializerSyntax(GetTokens(startIndex), Optional<TypeSyntax>.Empty(), ParseStructInitializerBody()),
|
|
||||||
Symbol.Struct => ParseStructInitializer(startIndex),
|
|
||||||
_ => throw new ParseException(Diagnostic
|
|
||||||
.Error($"Unexpected symbol '{symbolToken.Symbol}' in expression")
|
|
||||||
.WithHelp("Expected '(', '-', '!', '[' or '{'")
|
|
||||||
.At(symbolToken)
|
|
||||||
.Build())
|
|
||||||
},
|
|
||||||
_ => throw new ParseException(Diagnostic
|
|
||||||
.Error($"Unexpected token '{token.GetType().Name}' in expression")
|
|
||||||
.WithHelp("Expected literal, identifier, or parenthesized expression")
|
|
||||||
.At(token)
|
|
||||||
.Build())
|
|
||||||
};
|
|
||||||
|
|
||||||
return ParsePostfixOperators(expr);
|
|
||||||
}
|
|
||||||
|
|
||||||
private ExpressionSyntax ParseParenthesizedExpression()
|
|
||||||
{
|
|
||||||
var expression = ParseExpression();
|
|
||||||
ExpectSymbol(Symbol.CloseParen);
|
|
||||||
return expression;
|
|
||||||
}
|
|
||||||
|
|
||||||
private ExpressionSyntax ParsePostfixOperators(ExpressionSyntax expr)
|
|
||||||
{
|
|
||||||
var startIndex = _tokenIndex;
|
|
||||||
while (HasToken)
|
|
||||||
{
|
|
||||||
if (TryExpectSymbol(Symbol.Ampersand))
|
|
||||||
{
|
|
||||||
expr = new AddressOfSyntax(GetTokens(startIndex), expr);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (TryExpectSymbol(Symbol.Caret))
|
|
||||||
{
|
|
||||||
expr = new DereferenceSyntax(GetTokens(startIndex), expr);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (TryExpectSymbol(Symbol.Period))
|
|
||||||
{
|
|
||||||
var member = ExpectIdentifier().Value;
|
|
||||||
if (TryExpectSymbol(Symbol.OpenParen))
|
|
||||||
{
|
|
||||||
var parameters = new List<ExpressionSyntax>();
|
|
||||||
|
|
||||||
while (!TryExpectSymbol(Symbol.CloseParen))
|
|
||||||
{
|
|
||||||
parameters.Add(ParseExpression());
|
|
||||||
if (!TryExpectSymbol(Symbol.Comma))
|
|
||||||
{
|
|
||||||
ExpectSymbol(Symbol.CloseParen);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
expr = new DotFuncCallSyntax(GetTokens(startIndex), member, expr, parameters);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
expr = new StructFieldAccessSyntax(GetTokens(startIndex), expr, member);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (TryExpectSymbol(Symbol.OpenBracket))
|
|
||||||
{
|
|
||||||
var index = ParseExpression();
|
|
||||||
ExpectSymbol(Symbol.CloseBracket);
|
|
||||||
expr = new ArrayIndexAccessSyntax(GetTokens(startIndex), expr, index);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (TryExpectSymbol(Symbol.OpenParen))
|
|
||||||
{
|
|
||||||
var parameters = new List<ExpressionSyntax>();
|
|
||||||
|
|
||||||
while (!TryExpectSymbol(Symbol.CloseParen))
|
|
||||||
{
|
|
||||||
parameters.Add(ParseExpression());
|
|
||||||
if (!TryExpectSymbol(Symbol.Comma))
|
|
||||||
{
|
|
||||||
ExpectSymbol(Symbol.CloseParen);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
expr = new FuncCallSyntax(GetTokens(startIndex), expr, parameters);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
return expr;
|
|
||||||
}
|
|
||||||
|
|
||||||
private ArrayInitializerSyntax ParseArrayInitializer(int startIndex)
|
|
||||||
{
|
|
||||||
var capacity = ParseExpression();
|
|
||||||
ExpectSymbol(Symbol.CloseBracket);
|
|
||||||
var type = ParseType();
|
|
||||||
return new ArrayInitializerSyntax(GetTokens(startIndex), capacity, type);
|
|
||||||
}
|
|
||||||
|
|
||||||
private StructInitializerSyntax ParseStructInitializer(int startIndex)
|
|
||||||
{
|
|
||||||
var type = Optional.Empty<TypeSyntax>();
|
|
||||||
if (!TryExpectSymbol(Symbol.OpenBrace))
|
|
||||||
{
|
|
||||||
type = ParseType();
|
|
||||||
ExpectSymbol(Symbol.OpenBrace);
|
|
||||||
}
|
|
||||||
|
|
||||||
var initializers = ParseStructInitializerBody();
|
|
||||||
|
|
||||||
return new StructInitializerSyntax(GetTokens(startIndex), type, initializers);
|
|
||||||
}
|
|
||||||
|
|
||||||
private Dictionary<string, ExpressionSyntax> ParseStructInitializerBody()
|
|
||||||
{
|
|
||||||
Dictionary<string, ExpressionSyntax> initializers = [];
|
|
||||||
while (!TryExpectSymbol(Symbol.CloseBrace))
|
|
||||||
{
|
|
||||||
var name = ExpectIdentifier().Value;
|
|
||||||
ExpectSymbol(Symbol.Assign);
|
|
||||||
var value = ParseExpression();
|
|
||||||
initializers.Add(name, value);
|
|
||||||
}
|
|
||||||
|
|
||||||
return initializers;
|
|
||||||
}
|
|
||||||
|
|
||||||
private BlockSyntax ParseBlock()
|
|
||||||
{
|
|
||||||
var startIndex = _tokenIndex;
|
|
||||||
ExpectSymbol(Symbol.OpenBrace);
|
|
||||||
List<StatementSyntax> statements = [];
|
|
||||||
while (!TryExpectSymbol(Symbol.CloseBrace))
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
statements.Add(ParseStatement());
|
|
||||||
}
|
|
||||||
catch (ParseException ex)
|
|
||||||
{
|
|
||||||
_diagnostics.Add(ex.Diagnostic);
|
|
||||||
Next();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return new BlockSyntax(GetTokens(startIndex), statements);
|
|
||||||
}
|
|
||||||
|
|
||||||
private TypeSyntax ParseType()
|
|
||||||
{
|
|
||||||
var startIndex = _tokenIndex;
|
|
||||||
if (TryExpectIdentifier(out var name))
|
|
||||||
{
|
|
||||||
if (name.Value[0] == 'u' && int.TryParse(name.Value[1..], out var size))
|
|
||||||
{
|
|
||||||
if (size is not 8 and not 16 and not 32 and not 64)
|
|
||||||
{
|
|
||||||
throw new ParseException(Diagnostic
|
|
||||||
.Error("Arbitrary uint size is not supported")
|
|
||||||
.WithHelp("Use u8, u16, u32 or u64")
|
|
||||||
.At(name)
|
|
||||||
.Build());
|
|
||||||
}
|
|
||||||
|
|
||||||
return new IntTypeSyntax(GetTokens(startIndex), false, size);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (name.Value[0] == 'i' && int.TryParse(name.Value[1..], out size))
|
|
||||||
{
|
|
||||||
if (size is not 8 and not 16 and not 32 and not 64)
|
|
||||||
{
|
|
||||||
throw new ParseException(Diagnostic
|
|
||||||
.Error("Arbitrary int size is not supported")
|
|
||||||
.WithHelp("Use i8, i16, i32 or i64")
|
|
||||||
.At(name)
|
|
||||||
.Build());
|
|
||||||
}
|
|
||||||
|
|
||||||
return new IntTypeSyntax(GetTokens(startIndex), true, size);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (name.Value[0] == 'f' && int.TryParse(name.Value[1..], out size))
|
|
||||||
{
|
|
||||||
if (size is not 32 and not 64)
|
|
||||||
{
|
|
||||||
throw new ParseException(Diagnostic
|
|
||||||
.Error("Arbitrary float size is not supported")
|
|
||||||
.WithHelp("Use f32 or f64")
|
|
||||||
.At(name)
|
|
||||||
.Build());
|
|
||||||
}
|
|
||||||
|
|
||||||
return new FloatTypeSyntax(GetTokens(startIndex), size);
|
|
||||||
}
|
|
||||||
|
|
||||||
return name.Value switch
|
|
||||||
{
|
|
||||||
"void" => new VoidTypeSyntax(GetTokens(startIndex)),
|
|
||||||
"string" => new StringTypeSyntax(GetTokens(startIndex)),
|
|
||||||
"cstring" => new CStringTypeSyntax(GetTokens(startIndex)),
|
|
||||||
"bool" => new BoolTypeSyntax(GetTokens(startIndex)),
|
|
||||||
_ => new CustomTypeSyntax(GetTokens(startIndex), _moduleName, name.Value)
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
if (TryExpectSymbol(Symbol.Caret))
|
|
||||||
{
|
|
||||||
var baseType = ParseType();
|
|
||||||
return new PointerTypeSyntax(GetTokens(startIndex), baseType);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (TryExpectSymbol(Symbol.Func))
|
|
||||||
{
|
|
||||||
ExpectSymbol(Symbol.OpenParen);
|
|
||||||
|
|
||||||
List<TypeSyntax> parameters = [];
|
|
||||||
while (!TryExpectSymbol(Symbol.CloseParen))
|
|
||||||
{
|
|
||||||
parameters.Add(ParseType());
|
|
||||||
if (!TryExpectSymbol(Symbol.Comma))
|
|
||||||
{
|
|
||||||
ExpectSymbol(Symbol.CloseParen);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var returnType = TryExpectSymbol(Symbol.Colon)
|
|
||||||
? ParseType()
|
|
||||||
: new VoidTypeSyntax([]);
|
|
||||||
|
|
||||||
return new FuncTypeSyntax(GetTokens(startIndex), parameters, returnType);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (TryExpectSymbol(Symbol.OpenBracket))
|
|
||||||
{
|
|
||||||
ExpectSymbol(Symbol.CloseBracket);
|
|
||||||
var baseType = ParseType();
|
|
||||||
return new ArrayTypeSyntax(GetTokens(startIndex), baseType);
|
|
||||||
}
|
|
||||||
|
|
||||||
throw new ParseException(Diagnostic
|
|
||||||
.Error("Invalid type syntax")
|
|
||||||
.WithHelp("Expected type name, '^' for pointer, or '[]' for array")
|
|
||||||
.At(CurrentToken)
|
|
||||||
.Build());
|
|
||||||
}
|
|
||||||
|
|
||||||
private Token ExpectToken()
|
|
||||||
{
|
|
||||||
if (!HasToken)
|
|
||||||
{
|
|
||||||
throw new ParseException(Diagnostic
|
|
||||||
.Error("Unexpected end of file")
|
|
||||||
.WithHelp("Expected more tokens to complete the syntax")
|
|
||||||
.At(_tokens[^1])
|
|
||||||
.Build());
|
|
||||||
}
|
|
||||||
|
|
||||||
var token = CurrentToken!;
|
|
||||||
Next();
|
|
||||||
return token;
|
|
||||||
}
|
|
||||||
|
|
||||||
private SymbolToken ExpectSymbol()
|
|
||||||
{
|
|
||||||
var token = ExpectToken();
|
|
||||||
if (token is not SymbolToken symbol)
|
|
||||||
{
|
|
||||||
throw new ParseException(Diagnostic
|
|
||||||
.Error($"Expected symbol, but found {token.GetType().Name}")
|
|
||||||
.WithHelp("This position requires a symbol like '(', ')', '{', '}', etc.")
|
|
||||||
.At(token)
|
|
||||||
.Build());
|
|
||||||
}
|
|
||||||
|
|
||||||
return symbol;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void ExpectSymbol(Symbol expectedSymbol)
|
|
||||||
{
|
|
||||||
var token = ExpectSymbol();
|
|
||||||
if (token.Symbol != expectedSymbol)
|
|
||||||
{
|
|
||||||
throw new ParseException(Diagnostic
|
|
||||||
.Error($"Expected '{expectedSymbol}', but found '{token.Symbol}'")
|
|
||||||
.WithHelp($"Insert '{expectedSymbol}' here")
|
|
||||||
.At(token)
|
|
||||||
.Build());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private bool TryExpectSymbol(Symbol symbol)
|
|
||||||
{
|
|
||||||
if (CurrentToken is SymbolToken symbolToken && symbolToken.Symbol == symbol)
|
|
||||||
{
|
|
||||||
Next();
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
private bool TryExpectIdentifier([NotNullWhen(true)] out IdentifierToken? identifier)
|
|
||||||
{
|
|
||||||
if (CurrentToken is IdentifierToken identifierToken)
|
|
||||||
{
|
|
||||||
identifier = identifierToken;
|
|
||||||
Next();
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
identifier = null;
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
private IdentifierToken ExpectIdentifier()
|
|
||||||
{
|
|
||||||
var token = ExpectToken();
|
|
||||||
if (token is not IdentifierToken identifier)
|
|
||||||
{
|
|
||||||
throw new ParseException(Diagnostic
|
|
||||||
.Error($"Expected identifier, but found {token.GetType().Name}")
|
|
||||||
.WithHelp("Provide a valid identifier name here")
|
|
||||||
.At(token)
|
|
||||||
.Build());
|
|
||||||
}
|
|
||||||
|
|
||||||
return identifier;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void Next()
|
|
||||||
{
|
|
||||||
_tokenIndex++;
|
|
||||||
}
|
|
||||||
|
|
||||||
private IEnumerable<Token> GetTokens(int tokenStartIndex)
|
|
||||||
{
|
|
||||||
return _tokens.Skip(tokenStartIndex).Take(_tokenIndex - tokenStartIndex);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public class ParseException : Exception
|
|
||||||
{
|
|
||||||
public Diagnostic Diagnostic { get; }
|
|
||||||
|
|
||||||
public ParseException(Diagnostic diagnostic) : base(diagnostic.Message)
|
|
||||||
{
|
|
||||||
Diagnostic = diagnostic;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,24 +0,0 @@
|
|||||||
using NubLang.Tokenization;
|
|
||||||
|
|
||||||
namespace NubLang.Parsing.Syntax;
|
|
||||||
|
|
||||||
// todo(nub31): Check export modifier instead of harcoding true
|
|
||||||
public abstract record DefinitionSyntax(IEnumerable<Token> Tokens, string Name, bool Exported = true) : SyntaxNode(Tokens);
|
|
||||||
|
|
||||||
public record FuncParameterSyntax(IEnumerable<Token> Tokens, string Name, TypeSyntax Type) : SyntaxNode(Tokens);
|
|
||||||
|
|
||||||
public record FuncSignatureSyntax(IEnumerable<Token> Tokens, IReadOnlyList<FuncParameterSyntax> Parameters, TypeSyntax ReturnType) : SyntaxNode(Tokens);
|
|
||||||
|
|
||||||
public record FuncSyntax(IEnumerable<Token> Tokens, string Name, FuncSignatureSyntax Signature, BlockSyntax Body) : DefinitionSyntax(Tokens, Name);
|
|
||||||
|
|
||||||
public record ExternFuncSyntax(IEnumerable<Token> Tokens, string Name, string CallName, FuncSignatureSyntax Signature) : DefinitionSyntax(Tokens, Name);
|
|
||||||
|
|
||||||
public record StructFieldSyntax(IEnumerable<Token> Tokens, int Index, string Name, TypeSyntax Type, Optional<ExpressionSyntax> Value) : SyntaxNode(Tokens);
|
|
||||||
|
|
||||||
public record StructFuncSyntax(IEnumerable<Token> Tokens, string Name, FuncSignatureSyntax Signature, BlockSyntax Body) : SyntaxNode(Tokens);
|
|
||||||
|
|
||||||
public record StructSyntax(IEnumerable<Token> Tokens, string Name, IReadOnlyList<StructFieldSyntax> Fields, IReadOnlyList<StructFuncSyntax> Functions, IReadOnlyList<TypeSyntax> InterfaceImplementations) : DefinitionSyntax(Tokens, Name);
|
|
||||||
|
|
||||||
public record InterfaceFuncSyntax(IEnumerable<Token> Tokens, string Name, FuncSignatureSyntax Signature) : SyntaxNode(Tokens);
|
|
||||||
|
|
||||||
public record InterfaceSyntax(IEnumerable<Token> Tokens, string Name, IReadOnlyList<InterfaceFuncSyntax> Functions) : DefinitionSyntax(Tokens, Name);
|
|
||||||
@@ -1,57 +0,0 @@
|
|||||||
using NubLang.Tokenization;
|
|
||||||
|
|
||||||
namespace NubLang.Parsing.Syntax;
|
|
||||||
|
|
||||||
public enum UnaryOperatorSyntax
|
|
||||||
{
|
|
||||||
Negate,
|
|
||||||
Invert
|
|
||||||
}
|
|
||||||
|
|
||||||
public enum BinaryOperatorSyntax
|
|
||||||
{
|
|
||||||
Equal,
|
|
||||||
NotEqual,
|
|
||||||
GreaterThan,
|
|
||||||
GreaterThanOrEqual,
|
|
||||||
LessThan,
|
|
||||||
LessThanOrEqual,
|
|
||||||
LogicalAnd,
|
|
||||||
LogicalOr,
|
|
||||||
Plus,
|
|
||||||
Minus,
|
|
||||||
Multiply,
|
|
||||||
Divide,
|
|
||||||
Modulo,
|
|
||||||
LeftShift,
|
|
||||||
RightShift,
|
|
||||||
BitwiseAnd,
|
|
||||||
BitwiseXor,
|
|
||||||
BitwiseOr,
|
|
||||||
}
|
|
||||||
|
|
||||||
public abstract record ExpressionSyntax(IEnumerable<Token> Tokens) : SyntaxNode(Tokens);
|
|
||||||
|
|
||||||
public record BinaryExpressionSyntax(IEnumerable<Token> Tokens, ExpressionSyntax Left, BinaryOperatorSyntax Operator, ExpressionSyntax Right) : ExpressionSyntax(Tokens);
|
|
||||||
|
|
||||||
public record UnaryExpressionSyntax(IEnumerable<Token> Tokens, UnaryOperatorSyntax Operator, ExpressionSyntax Operand) : ExpressionSyntax(Tokens);
|
|
||||||
|
|
||||||
public record FuncCallSyntax(IEnumerable<Token> Tokens, ExpressionSyntax Expression, IReadOnlyList<ExpressionSyntax> Parameters) : ExpressionSyntax(Tokens);
|
|
||||||
|
|
||||||
public record DotFuncCallSyntax(IEnumerable<Token> Tokens, string Name, ExpressionSyntax ThisParameter, IReadOnlyList<ExpressionSyntax> Parameters) : ExpressionSyntax(Tokens);
|
|
||||||
|
|
||||||
public record IdentifierSyntax(IEnumerable<Token> Tokens, Optional<string> Module, string Name) : ExpressionSyntax(Tokens);
|
|
||||||
|
|
||||||
public record ArrayInitializerSyntax(IEnumerable<Token> Tokens, ExpressionSyntax Capacity, TypeSyntax ElementType) : ExpressionSyntax(Tokens);
|
|
||||||
|
|
||||||
public record ArrayIndexAccessSyntax(IEnumerable<Token> Tokens, ExpressionSyntax Target, ExpressionSyntax Index) : ExpressionSyntax(Tokens);
|
|
||||||
|
|
||||||
public record AddressOfSyntax(IEnumerable<Token> Tokens, ExpressionSyntax Expression) : ExpressionSyntax(Tokens);
|
|
||||||
|
|
||||||
public record LiteralSyntax(IEnumerable<Token> Tokens, string Value, LiteralKind Kind) : ExpressionSyntax(Tokens);
|
|
||||||
|
|
||||||
public record StructFieldAccessSyntax(IEnumerable<Token> Tokens, ExpressionSyntax Target, string Member) : ExpressionSyntax(Tokens);
|
|
||||||
|
|
||||||
public record StructInitializerSyntax(IEnumerable<Token> Tokens, Optional<TypeSyntax> StructType, Dictionary<string, ExpressionSyntax> Initializers) : ExpressionSyntax(Tokens);
|
|
||||||
|
|
||||||
public record DereferenceSyntax(IEnumerable<Token> Tokens, ExpressionSyntax Expression) : ExpressionSyntax(Tokens);
|
|
||||||
@@ -1,21 +0,0 @@
|
|||||||
using NubLang.Tokenization;
|
|
||||||
|
|
||||||
namespace NubLang.Parsing.Syntax;
|
|
||||||
|
|
||||||
public abstract record StatementSyntax(IEnumerable<Token> Tokens) : SyntaxNode(Tokens);
|
|
||||||
|
|
||||||
public record StatementExpressionSyntax(IEnumerable<Token> Tokens, ExpressionSyntax Expression) : StatementSyntax(Tokens);
|
|
||||||
|
|
||||||
public record ReturnSyntax(IEnumerable<Token> Tokens, Optional<ExpressionSyntax> Value) : StatementSyntax(Tokens);
|
|
||||||
|
|
||||||
public record AssignmentSyntax(IEnumerable<Token> Tokens, ExpressionSyntax Target, ExpressionSyntax Value) : StatementSyntax(Tokens);
|
|
||||||
|
|
||||||
public record IfSyntax(IEnumerable<Token> Tokens, ExpressionSyntax Condition, BlockSyntax Body, Optional<Variant<IfSyntax, BlockSyntax>> Else) : StatementSyntax(Tokens);
|
|
||||||
|
|
||||||
public record VariableDeclarationSyntax(IEnumerable<Token> Tokens, string Name, Optional<TypeSyntax> ExplicitType, Optional<ExpressionSyntax> Assignment) : StatementSyntax(Tokens);
|
|
||||||
|
|
||||||
public record ContinueSyntax(IEnumerable<Token> Tokens) : StatementSyntax(Tokens);
|
|
||||||
|
|
||||||
public record BreakSyntax(IEnumerable<Token> Tokens) : StatementSyntax(Tokens);
|
|
||||||
|
|
||||||
public record WhileSyntax(IEnumerable<Token> Tokens, ExpressionSyntax Condition, BlockSyntax Body) : StatementSyntax(Tokens);
|
|
||||||
@@ -1,11 +0,0 @@
|
|||||||
using NubLang.Tokenization;
|
|
||||||
|
|
||||||
namespace NubLang.Parsing.Syntax;
|
|
||||||
|
|
||||||
public abstract record SyntaxNode(IEnumerable<Token> Tokens);
|
|
||||||
|
|
||||||
public record SyntaxTreeMetadata(string? ModuleName, IReadOnlyList<string> Imports);
|
|
||||||
|
|
||||||
public record SyntaxTree(IReadOnlyList<DefinitionSyntax> Definitions, SyntaxTreeMetadata Metadata);
|
|
||||||
|
|
||||||
public record BlockSyntax(IEnumerable<Token> Tokens, IReadOnlyList<StatementSyntax> Statements) : SyntaxNode(Tokens);
|
|
||||||
@@ -1,25 +0,0 @@
|
|||||||
using NubLang.Tokenization;
|
|
||||||
|
|
||||||
namespace NubLang.Parsing.Syntax;
|
|
||||||
|
|
||||||
public abstract record TypeSyntax(IEnumerable<Token> Tokens) : SyntaxNode(Tokens);
|
|
||||||
|
|
||||||
public record FuncTypeSyntax(IEnumerable<Token> Tokens, IReadOnlyList<TypeSyntax> Parameters, TypeSyntax ReturnType) : TypeSyntax(Tokens);
|
|
||||||
|
|
||||||
public record PointerTypeSyntax(IEnumerable<Token> Tokens, TypeSyntax BaseType) : TypeSyntax(Tokens);
|
|
||||||
|
|
||||||
public record VoidTypeSyntax(IEnumerable<Token> Tokens) : TypeSyntax(Tokens);
|
|
||||||
|
|
||||||
public record IntTypeSyntax(IEnumerable<Token> Tokens, bool Signed, int Width) : TypeSyntax(Tokens);
|
|
||||||
|
|
||||||
public record FloatTypeSyntax(IEnumerable<Token> Tokens, int Width) : TypeSyntax(Tokens);
|
|
||||||
|
|
||||||
public record BoolTypeSyntax(IEnumerable<Token> Tokens) : TypeSyntax(Tokens);
|
|
||||||
|
|
||||||
public record StringTypeSyntax(IEnumerable<Token> Tokens) : TypeSyntax(Tokens);
|
|
||||||
|
|
||||||
public record CStringTypeSyntax(IEnumerable<Token> Tokens) : TypeSyntax(Tokens);
|
|
||||||
|
|
||||||
public record ArrayTypeSyntax(IEnumerable<Token> Tokens, TypeSyntax BaseType) : TypeSyntax(Tokens);
|
|
||||||
|
|
||||||
public record CustomTypeSyntax(IEnumerable<Token> Tokens, string Module, string Name) : TypeSyntax(Tokens);
|
|
||||||
@@ -1,81 +0,0 @@
|
|||||||
using NubLang.Code;
|
|
||||||
|
|
||||||
namespace NubLang.Tokenization;
|
|
||||||
|
|
||||||
public abstract class Token(SourceFileSpan fileSpan)
|
|
||||||
{
|
|
||||||
public SourceFileSpan FileSpan { get; } = fileSpan;
|
|
||||||
}
|
|
||||||
|
|
||||||
public class IdentifierToken(SourceFileSpan fileSpan, string value) : Token(fileSpan)
|
|
||||||
{
|
|
||||||
public string Value { get; } = value;
|
|
||||||
}
|
|
||||||
|
|
||||||
public class LiteralToken(SourceFileSpan fileSpan, LiteralKind kind, string value) : Token(fileSpan)
|
|
||||||
{
|
|
||||||
public LiteralKind Kind { get; } = kind;
|
|
||||||
public string Value { get; } = value;
|
|
||||||
}
|
|
||||||
|
|
||||||
public enum LiteralKind
|
|
||||||
{
|
|
||||||
Integer,
|
|
||||||
Float,
|
|
||||||
String,
|
|
||||||
Bool
|
|
||||||
}
|
|
||||||
|
|
||||||
public class SymbolToken(SourceFileSpan fileSpan, Symbol symbol) : Token(fileSpan)
|
|
||||||
{
|
|
||||||
public Symbol Symbol { get; } = symbol;
|
|
||||||
}
|
|
||||||
|
|
||||||
public enum Symbol
|
|
||||||
{
|
|
||||||
Func,
|
|
||||||
Return,
|
|
||||||
If,
|
|
||||||
Else,
|
|
||||||
While,
|
|
||||||
Break,
|
|
||||||
Continue,
|
|
||||||
Colon,
|
|
||||||
OpenParen,
|
|
||||||
CloseParen,
|
|
||||||
OpenBrace,
|
|
||||||
CloseBrace,
|
|
||||||
OpenBracket,
|
|
||||||
CloseBracket,
|
|
||||||
Comma,
|
|
||||||
Period,
|
|
||||||
Assign,
|
|
||||||
Bang,
|
|
||||||
Equal,
|
|
||||||
NotEqual,
|
|
||||||
LessThan,
|
|
||||||
LessThanOrEqual,
|
|
||||||
GreaterThan,
|
|
||||||
GreaterThanOrEqual,
|
|
||||||
Plus,
|
|
||||||
Minus,
|
|
||||||
Star,
|
|
||||||
ForwardSlash,
|
|
||||||
Struct,
|
|
||||||
Caret,
|
|
||||||
Ampersand,
|
|
||||||
Let,
|
|
||||||
Calls,
|
|
||||||
Interface,
|
|
||||||
For,
|
|
||||||
Extern,
|
|
||||||
Semi,
|
|
||||||
Percent,
|
|
||||||
LeftShift,
|
|
||||||
RightShift,
|
|
||||||
Pipe,
|
|
||||||
And,
|
|
||||||
Or,
|
|
||||||
Module,
|
|
||||||
Import,
|
|
||||||
}
|
|
||||||
@@ -1,264 +0,0 @@
|
|||||||
using NubLang.Code;
|
|
||||||
using NubLang.Diagnostics;
|
|
||||||
|
|
||||||
namespace NubLang.Tokenization;
|
|
||||||
|
|
||||||
public sealed class Tokenizer
|
|
||||||
{
|
|
||||||
private static readonly Dictionary<string, Symbol> Keywords = new()
|
|
||||||
{
|
|
||||||
["func"] = Symbol.Func,
|
|
||||||
["if"] = Symbol.If,
|
|
||||||
["else"] = Symbol.Else,
|
|
||||||
["while"] = Symbol.While,
|
|
||||||
["break"] = Symbol.Break,
|
|
||||||
["continue"] = Symbol.Continue,
|
|
||||||
["return"] = Symbol.Return,
|
|
||||||
["struct"] = Symbol.Struct,
|
|
||||||
["let"] = Symbol.Let,
|
|
||||||
["calls"] = Symbol.Calls,
|
|
||||||
["interface"] = Symbol.Interface,
|
|
||||||
["for"] = Symbol.For,
|
|
||||||
["extern"] = Symbol.Extern,
|
|
||||||
["module"] = Symbol.Module,
|
|
||||||
};
|
|
||||||
|
|
||||||
private static readonly Dictionary<char[], Symbol> Symbols = new()
|
|
||||||
{
|
|
||||||
[['=', '=']] = Symbol.Equal,
|
|
||||||
[['!', '=']] = Symbol.NotEqual,
|
|
||||||
[['<', '=']] = Symbol.LessThanOrEqual,
|
|
||||||
[['>', '=']] = Symbol.GreaterThanOrEqual,
|
|
||||||
[['<', '<']] = Symbol.LeftShift,
|
|
||||||
[['>', '>']] = Symbol.RightShift,
|
|
||||||
[['&', '&']] = Symbol.And,
|
|
||||||
[['|', '|']] = Symbol.Or,
|
|
||||||
[[':']] = Symbol.Colon,
|
|
||||||
[['(']] = Symbol.OpenParen,
|
|
||||||
[[')']] = Symbol.CloseParen,
|
|
||||||
[['{']] = Symbol.OpenBrace,
|
|
||||||
[['}']] = Symbol.CloseBrace,
|
|
||||||
[['[']] = Symbol.OpenBracket,
|
|
||||||
[[']']] = Symbol.CloseBracket,
|
|
||||||
[[',']] = Symbol.Comma,
|
|
||||||
[['.']] = Symbol.Period,
|
|
||||||
[['=']] = Symbol.Assign,
|
|
||||||
[['<']] = Symbol.LessThan,
|
|
||||||
[['>']] = Symbol.GreaterThan,
|
|
||||||
[['+']] = Symbol.Plus,
|
|
||||||
[['-']] = Symbol.Minus,
|
|
||||||
[['*']] = Symbol.Star,
|
|
||||||
[['/']] = Symbol.ForwardSlash,
|
|
||||||
[['!']] = Symbol.Bang,
|
|
||||||
[['^']] = Symbol.Caret,
|
|
||||||
[['&']] = Symbol.Ampersand,
|
|
||||||
[[';']] = Symbol.Semi,
|
|
||||||
[['%']] = Symbol.Percent,
|
|
||||||
[['|']] = Symbol.Pipe,
|
|
||||||
};
|
|
||||||
|
|
||||||
private static readonly (char[] Pattern, Symbol Symbol)[] OrderedSymbols = Symbols
|
|
||||||
.OrderByDescending(kvp => kvp.Key.Length)
|
|
||||||
.Select(kvp => (kvp.Key, kvp.Value))
|
|
||||||
.ToArray();
|
|
||||||
|
|
||||||
private readonly SourceFile _sourceFile;
|
|
||||||
private readonly List<Diagnostic> _diagnostics = [];
|
|
||||||
private int _index;
|
|
||||||
|
|
||||||
public Tokenizer(SourceFile sourceFile)
|
|
||||||
{
|
|
||||||
_sourceFile = sourceFile;
|
|
||||||
}
|
|
||||||
|
|
||||||
public IReadOnlyList<Diagnostic> GetDiagnostics() => _diagnostics;
|
|
||||||
|
|
||||||
public IEnumerable<Token> Tokenize()
|
|
||||||
{
|
|
||||||
_index = 0;
|
|
||||||
|
|
||||||
while (Peek().TryGetValue(out var current))
|
|
||||||
{
|
|
||||||
if (char.IsWhiteSpace(current))
|
|
||||||
{
|
|
||||||
Next();
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (current == '/' && Peek(1).TryGetValue(out var nextChar) && nextChar == '/')
|
|
||||||
{
|
|
||||||
while (Peek().TryGetValue(out var ch) && ch != '\n')
|
|
||||||
{
|
|
||||||
Next();
|
|
||||||
}
|
|
||||||
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
var tokenStartIndex = _index;
|
|
||||||
|
|
||||||
if (char.IsLetter(current) || current == '_')
|
|
||||||
{
|
|
||||||
var buffer = string.Empty;
|
|
||||||
|
|
||||||
while (Peek().TryGetValue(out var next) && (char.IsLetterOrDigit(next) || next == '_'))
|
|
||||||
{
|
|
||||||
buffer += next;
|
|
||||||
Next();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (Keywords.TryGetValue(buffer, out var keywordSymbol))
|
|
||||||
{
|
|
||||||
yield return new SymbolToken(GetSourceFileSpan(tokenStartIndex), keywordSymbol);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (buffer is "true" or "false")
|
|
||||||
{
|
|
||||||
yield return new LiteralToken(GetSourceFileSpan(tokenStartIndex), LiteralKind.Bool, buffer);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
yield return new IdentifierToken(GetSourceFileSpan(tokenStartIndex), buffer);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (char.IsDigit(current))
|
|
||||||
{
|
|
||||||
var isFloat = false;
|
|
||||||
var buffer = string.Empty;
|
|
||||||
|
|
||||||
while (Peek().TryGetValue(out var next))
|
|
||||||
{
|
|
||||||
if (next == '.')
|
|
||||||
{
|
|
||||||
if (isFloat)
|
|
||||||
{
|
|
||||||
throw new Exception("More than one period found in float literal");
|
|
||||||
}
|
|
||||||
|
|
||||||
isFloat = true;
|
|
||||||
buffer += next;
|
|
||||||
Next();
|
|
||||||
}
|
|
||||||
else if (char.IsDigit(next))
|
|
||||||
{
|
|
||||||
buffer += next;
|
|
||||||
Next();
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
yield return new LiteralToken(GetSourceFileSpan(tokenStartIndex), isFloat ? LiteralKind.Float : LiteralKind.Integer, buffer);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (current == '"')
|
|
||||||
{
|
|
||||||
Next();
|
|
||||||
var buffer = string.Empty;
|
|
||||||
|
|
||||||
while (true)
|
|
||||||
{
|
|
||||||
if (!Peek().TryGetValue(out var next))
|
|
||||||
{
|
|
||||||
throw new Exception("Unclosed string literal");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (next == '"')
|
|
||||||
{
|
|
||||||
Next();
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
buffer += next;
|
|
||||||
Next();
|
|
||||||
}
|
|
||||||
|
|
||||||
yield return new LiteralToken(GetSourceFileSpan(tokenStartIndex), LiteralKind.String, buffer);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
var foundMatch = false;
|
|
||||||
foreach (var (pattern, symbol) in OrderedSymbols)
|
|
||||||
{
|
|
||||||
for (var i = 0; i < pattern.Length; i++)
|
|
||||||
{
|
|
||||||
var c = Peek(i);
|
|
||||||
if (!c.HasValue || c.Value != pattern[i]) break;
|
|
||||||
|
|
||||||
if (i == pattern.Length - 1)
|
|
||||||
{
|
|
||||||
for (var j = 0; j <= i; j++)
|
|
||||||
{
|
|
||||||
Next();
|
|
||||||
}
|
|
||||||
|
|
||||||
yield return new SymbolToken(GetSourceFileSpan(tokenStartIndex), symbol);
|
|
||||||
foundMatch = true;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (foundMatch)
|
|
||||||
{
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (foundMatch)
|
|
||||||
{
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
_diagnostics.Add(Diagnostic.Error($"Unknown token '{current}'").At(GetSourceFileSpan(tokenStartIndex)).Build());
|
|
||||||
Next();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private Optional<char> Peek(int offset = 0)
|
|
||||||
{
|
|
||||||
if (_index + offset < _sourceFile.GetText().Length)
|
|
||||||
{
|
|
||||||
return _sourceFile.GetText()[_index + offset];
|
|
||||||
}
|
|
||||||
|
|
||||||
return Optional<char>.Empty();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void Next()
|
|
||||||
{
|
|
||||||
_index++;
|
|
||||||
}
|
|
||||||
|
|
||||||
private SourceFileSpan GetSourceFileSpan(int tokenStartIndex)
|
|
||||||
{
|
|
||||||
var start = CalculateSourceLocation(tokenStartIndex);
|
|
||||||
var end = CalculateSourceLocation(_index);
|
|
||||||
return new SourceFileSpan(_sourceFile, new SourceSpan(start, end));
|
|
||||||
}
|
|
||||||
|
|
||||||
private SourceLocation CalculateSourceLocation(int index)
|
|
||||||
{
|
|
||||||
var line = 1;
|
|
||||||
var column = 1;
|
|
||||||
|
|
||||||
for (var i = 0; i < index && i < _sourceFile.GetText().Length; i++)
|
|
||||||
{
|
|
||||||
if (_sourceFile.GetText()[i] == '\n')
|
|
||||||
{
|
|
||||||
line++;
|
|
||||||
column = 1;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
column++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return new SourceLocation(line, column);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,179 +0,0 @@
|
|||||||
using NubLang.Parsing.Syntax;
|
|
||||||
using NubLang.TypeChecking.Node;
|
|
||||||
|
|
||||||
namespace NubLang.TypeChecking;
|
|
||||||
|
|
||||||
public class Module
|
|
||||||
{
|
|
||||||
public static IReadOnlyList<Module> CollectFromSyntaxTrees(IReadOnlyList<SyntaxTree> syntaxTrees)
|
|
||||||
{
|
|
||||||
var modules = new Dictionary<string, Module>();
|
|
||||||
|
|
||||||
foreach (var syntaxTree in syntaxTrees)
|
|
||||||
{
|
|
||||||
var name = syntaxTree.Metadata.ModuleName;
|
|
||||||
if (name == null)
|
|
||||||
{
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!modules.TryGetValue(name, out var module))
|
|
||||||
{
|
|
||||||
module = new Module(name, syntaxTree.Metadata.Imports);
|
|
||||||
modules[name] = module;
|
|
||||||
}
|
|
||||||
|
|
||||||
foreach (var definition in syntaxTree.Definitions)
|
|
||||||
{
|
|
||||||
module.AddDefinition(definition);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return modules.Values.ToList();
|
|
||||||
}
|
|
||||||
|
|
||||||
private readonly List<DefinitionSyntax> _definitions = [];
|
|
||||||
|
|
||||||
public Module(string name, IReadOnlyList<string> imports)
|
|
||||||
{
|
|
||||||
Name = name;
|
|
||||||
Imports = imports;
|
|
||||||
}
|
|
||||||
|
|
||||||
public string Name { get; }
|
|
||||||
public IReadOnlyList<string> Imports { get; }
|
|
||||||
|
|
||||||
public IReadOnlyList<DefinitionSyntax> Definitions => _definitions;
|
|
||||||
|
|
||||||
private void AddDefinition(DefinitionSyntax syntax)
|
|
||||||
{
|
|
||||||
_definitions.Add(syntax);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public class TypedModule
|
|
||||||
{
|
|
||||||
public TypedModule(string name, IReadOnlyList<DefinitionNode> definitions)
|
|
||||||
{
|
|
||||||
Name = name;
|
|
||||||
Definitions = definitions;
|
|
||||||
}
|
|
||||||
|
|
||||||
public string Name { get; }
|
|
||||||
public IReadOnlyList<DefinitionNode> Definitions { get; }
|
|
||||||
}
|
|
||||||
|
|
||||||
public class ModuleSignature
|
|
||||||
{
|
|
||||||
public static IReadOnlyDictionary<string, ModuleSignature> CollectFromSyntaxTrees(IReadOnlyList<SyntaxTree> syntaxTrees)
|
|
||||||
{
|
|
||||||
var modules = new Dictionary<string, ModuleSignature>();
|
|
||||||
|
|
||||||
foreach (var syntaxTree in syntaxTrees)
|
|
||||||
{
|
|
||||||
var moduleName = syntaxTree.Metadata.ModuleName;
|
|
||||||
if (moduleName == null)
|
|
||||||
{
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!modules.TryGetValue(moduleName, out var module))
|
|
||||||
{
|
|
||||||
module = new ModuleSignature();
|
|
||||||
modules[moduleName] = module;
|
|
||||||
}
|
|
||||||
|
|
||||||
foreach (var def in syntaxTree.Definitions)
|
|
||||||
{
|
|
||||||
if (def.Exported)
|
|
||||||
{
|
|
||||||
switch (def)
|
|
||||||
{
|
|
||||||
case ExternFuncSyntax externFuncDef:
|
|
||||||
{
|
|
||||||
var parameters = externFuncDef.Signature.Parameters.Select(p => TypeResolver.ResolveType(p.Type, modules)).ToList();
|
|
||||||
var returnType = TypeResolver.ResolveType(externFuncDef.Signature.ReturnType, modules);
|
|
||||||
var type = new FuncTypeNode(parameters, returnType);
|
|
||||||
module._functions.Add(externFuncDef.Name, type);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case FuncSyntax funcDef:
|
|
||||||
{
|
|
||||||
var parameters = funcDef.Signature.Parameters.Select(p => TypeResolver.ResolveType(p.Type, modules)).ToList();
|
|
||||||
var returnType = TypeResolver.ResolveType(funcDef.Signature.ReturnType, modules);
|
|
||||||
var type = new FuncTypeNode(parameters, returnType);
|
|
||||||
module._functions.Add(funcDef.Name, type);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case InterfaceSyntax interfaceDef:
|
|
||||||
{
|
|
||||||
var functions = new List<InterfaceTypeFunc>();
|
|
||||||
for (var i = 0; i < interfaceDef.Functions.Count; i++)
|
|
||||||
{
|
|
||||||
var function = interfaceDef.Functions[i];
|
|
||||||
var parameters = function.Signature.Parameters.Select(p => TypeResolver.ResolveType(p.Type, modules)).ToList();
|
|
||||||
var returnType = TypeResolver.ResolveType(function.Signature.ReturnType, modules);
|
|
||||||
functions.Add(new InterfaceTypeFunc(function.Name, new FuncTypeNode(parameters, returnType), i));
|
|
||||||
}
|
|
||||||
|
|
||||||
var type = new InterfaceTypeNode(moduleName, interfaceDef.Name, functions);
|
|
||||||
module._interfaces.Add(type);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case StructSyntax structDef:
|
|
||||||
{
|
|
||||||
var fields = new List<StructTypeField>();
|
|
||||||
foreach (var field in structDef.Fields)
|
|
||||||
{
|
|
||||||
fields.Add(new StructTypeField(field.Name, TypeResolver.ResolveType(field.Type, modules), field.Index, field.Value.HasValue));
|
|
||||||
}
|
|
||||||
|
|
||||||
var functions = new List<StructTypeFunc>();
|
|
||||||
foreach (var function in structDef.Functions)
|
|
||||||
{
|
|
||||||
var parameters = function.Signature.Parameters.Select(p => TypeResolver.ResolveType(p.Type, modules)).ToList();
|
|
||||||
var returnType = TypeResolver.ResolveType(function.Signature.ReturnType, modules);
|
|
||||||
functions.Add(new StructTypeFunc(function.Name, new FuncTypeNode(parameters, returnType)));
|
|
||||||
}
|
|
||||||
|
|
||||||
var interfaceImplementations = new List<InterfaceTypeNode>();
|
|
||||||
foreach (var interfaceImplementation in structDef.InterfaceImplementations)
|
|
||||||
{
|
|
||||||
if (interfaceImplementation is not CustomTypeSyntax customType)
|
|
||||||
{
|
|
||||||
throw new Exception("Interface implementation is not a custom type");
|
|
||||||
}
|
|
||||||
|
|
||||||
var resolvedType = TypeResolver.ResolveCustomType(customType.Module, customType.Name, modules);
|
|
||||||
if (resolvedType is not InterfaceTypeNode interfaceType)
|
|
||||||
{
|
|
||||||
throw new Exception("Interface implementation is not a interface");
|
|
||||||
}
|
|
||||||
|
|
||||||
interfaceImplementations.Add(interfaceType);
|
|
||||||
}
|
|
||||||
|
|
||||||
var type = new StructTypeNode(moduleName, structDef.Name, fields, functions, interfaceImplementations);
|
|
||||||
module._structs.Add(type);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
{
|
|
||||||
throw new ArgumentOutOfRangeException(nameof(def));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return modules;
|
|
||||||
}
|
|
||||||
|
|
||||||
private readonly List<StructTypeNode> _structs = [];
|
|
||||||
private readonly List<InterfaceTypeNode> _interfaces = [];
|
|
||||||
private readonly Dictionary<string, FuncTypeNode> _functions = [];
|
|
||||||
|
|
||||||
public IReadOnlyList<StructTypeNode> StructTypes => _structs;
|
|
||||||
public IReadOnlyList<InterfaceTypeNode> InterfaceTypes => _interfaces;
|
|
||||||
public IReadOnlyDictionary<string, FuncTypeNode> Functions => _functions;
|
|
||||||
}
|
|
||||||
@@ -1,21 +0,0 @@
|
|||||||
namespace NubLang.TypeChecking.Node;
|
|
||||||
|
|
||||||
public abstract record DefinitionNode : Node;
|
|
||||||
|
|
||||||
public record FuncParameterNode(string Name, TypeNode Type) : Node;
|
|
||||||
|
|
||||||
public record FuncSignatureNode(IReadOnlyList<FuncParameterNode> Parameters, TypeNode ReturnType) : Node;
|
|
||||||
|
|
||||||
public record LocalFuncNode(string Name, FuncSignatureNode Signature, BlockNode Body) : DefinitionNode;
|
|
||||||
|
|
||||||
public record ExternFuncNode(string Name, string CallName, FuncSignatureNode Signature) : DefinitionNode;
|
|
||||||
|
|
||||||
public record StructFieldNode(int Index, string Name, TypeNode Type, Optional<ExpressionNode> Value) : Node;
|
|
||||||
|
|
||||||
public record StructFuncNode(string Name, FuncSignatureNode Signature, BlockNode Body) : Node;
|
|
||||||
|
|
||||||
public record StructNode(string Name, IReadOnlyList<StructFieldNode> Fields, IReadOnlyList<StructFuncNode> Functions, IReadOnlyList<InterfaceTypeNode> InterfaceImplementations) : DefinitionNode;
|
|
||||||
|
|
||||||
public record InterfaceFuncNode(string Name, FuncSignatureNode Signature) : Node;
|
|
||||||
|
|
||||||
public record InterfaceNode(string Name, IReadOnlyList<InterfaceFuncNode> Functions) : DefinitionNode;
|
|
||||||
@@ -1,72 +0,0 @@
|
|||||||
using NubLang.Tokenization;
|
|
||||||
|
|
||||||
namespace NubLang.TypeChecking.Node;
|
|
||||||
|
|
||||||
public enum UnaryOperator
|
|
||||||
{
|
|
||||||
Negate,
|
|
||||||
Invert
|
|
||||||
}
|
|
||||||
|
|
||||||
public enum BinaryOperator
|
|
||||||
{
|
|
||||||
Equal,
|
|
||||||
NotEqual,
|
|
||||||
GreaterThan,
|
|
||||||
GreaterThanOrEqual,
|
|
||||||
LessThan,
|
|
||||||
LessThanOrEqual,
|
|
||||||
LogicalAnd,
|
|
||||||
LogicalOr,
|
|
||||||
Plus,
|
|
||||||
Minus,
|
|
||||||
Multiply,
|
|
||||||
Divide,
|
|
||||||
Modulo,
|
|
||||||
LeftShift,
|
|
||||||
RightShift,
|
|
||||||
BitwiseAnd,
|
|
||||||
BitwiseXor,
|
|
||||||
BitwiseOr
|
|
||||||
}
|
|
||||||
|
|
||||||
public abstract record ExpressionNode(TypeNode Type) : Node;
|
|
||||||
|
|
||||||
public abstract record LValueExpressionNode(TypeNode Type) : RValueExpressionNode(Type);
|
|
||||||
public abstract record RValueExpressionNode(TypeNode Type) : ExpressionNode(Type);
|
|
||||||
|
|
||||||
public record BinaryExpressionNode(TypeNode Type, ExpressionNode Left, BinaryOperator Operator, ExpressionNode Right) : RValueExpressionNode(Type);
|
|
||||||
|
|
||||||
public record UnaryExpressionNode(TypeNode Type, UnaryOperator Operator, ExpressionNode Operand) : RValueExpressionNode(Type);
|
|
||||||
|
|
||||||
public record FuncCallNode(TypeNode Type, ExpressionNode Expression, IReadOnlyList<ExpressionNode> Parameters) : RValueExpressionNode(Type);
|
|
||||||
|
|
||||||
public record StructFuncCallNode(TypeNode Type, string Name, StructTypeNode StructType, ExpressionNode StructExpression, IReadOnlyList<ExpressionNode> Parameters) : RValueExpressionNode(Type);
|
|
||||||
|
|
||||||
public record InterfaceFuncCallNode(TypeNode Type, string Name, InterfaceTypeNode InterfaceType, ExpressionNode InterfaceExpression, IReadOnlyList<ExpressionNode> Parameters) : RValueExpressionNode(Type);
|
|
||||||
|
|
||||||
public record VariableIdentifierNode(TypeNode Type, string Name) : LValueExpressionNode(Type);
|
|
||||||
|
|
||||||
public record FuncParameterIdentifierNode(TypeNode Type, string Name) : RValueExpressionNode(Type);
|
|
||||||
|
|
||||||
public record FuncIdentifierNode(TypeNode Type, string Module, string Name) : RValueExpressionNode(Type);
|
|
||||||
|
|
||||||
public record ArrayInitializerNode(TypeNode Type, ExpressionNode Capacity, TypeNode ElementType) : RValueExpressionNode(Type);
|
|
||||||
|
|
||||||
public record ArrayIndexAccessNode(TypeNode Type, ExpressionNode Target, ExpressionNode Index) : LValueExpressionNode(Type);
|
|
||||||
|
|
||||||
public record AddressOfNode(TypeNode Type, LValueExpressionNode LValue) : RValueExpressionNode(Type);
|
|
||||||
|
|
||||||
public record LiteralNode(TypeNode Type, string Value, LiteralKind Kind) : RValueExpressionNode(Type);
|
|
||||||
|
|
||||||
public record StructFieldAccessNode(TypeNode Type, StructTypeNode StructType, ExpressionNode Target, string Field) : LValueExpressionNode(Type);
|
|
||||||
|
|
||||||
public record StructInitializerNode(StructTypeNode StructType, Dictionary<string, ExpressionNode> Initializers) : RValueExpressionNode(StructType);
|
|
||||||
|
|
||||||
public record DereferenceNode(TypeNode Type, ExpressionNode Expression) : RValueExpressionNode(Type);
|
|
||||||
|
|
||||||
public record ConvertToInterfaceNode(TypeNode Type, InterfaceTypeNode InterfaceType, StructTypeNode StructType, ExpressionNode Implementation) : RValueExpressionNode(Type);
|
|
||||||
|
|
||||||
public record ConvertIntNode(TypeNode Type, ExpressionNode Value, IntTypeNode ValueType, IntTypeNode TargetType) : RValueExpressionNode(Type);
|
|
||||||
|
|
||||||
public record ConvertFloatNode(TypeNode Type, ExpressionNode Value, FloatTypeNode ValueType, FloatTypeNode TargetType) : RValueExpressionNode(Type);
|
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
namespace NubLang.TypeChecking.Node;
|
|
||||||
|
|
||||||
public abstract record Node;
|
|
||||||
|
|
||||||
public record BlockNode(IReadOnlyList<StatementNode> Statements) : Node;
|
|
||||||
@@ -1,19 +0,0 @@
|
|||||||
namespace NubLang.TypeChecking.Node;
|
|
||||||
|
|
||||||
public record StatementNode : Node;
|
|
||||||
|
|
||||||
public record StatementExpressionNode(ExpressionNode Expression) : StatementNode;
|
|
||||||
|
|
||||||
public record ReturnNode(Optional<ExpressionNode> Value) : StatementNode;
|
|
||||||
|
|
||||||
public record AssignmentNode(LValueExpressionNode Target, ExpressionNode Value) : StatementNode;
|
|
||||||
|
|
||||||
public record IfNode(ExpressionNode Condition, BlockNode Body, Optional<Variant<IfNode, BlockNode>> Else) : StatementNode;
|
|
||||||
|
|
||||||
public record VariableDeclarationNode(string Name, Optional<ExpressionNode> Assignment, TypeNode Type) : StatementNode;
|
|
||||||
|
|
||||||
public record ContinueNode : StatementNode;
|
|
||||||
|
|
||||||
public record BreakNode : StatementNode;
|
|
||||||
|
|
||||||
public record WhileNode(ExpressionNode Condition, BlockNode Body) : StatementNode;
|
|
||||||
@@ -1,232 +0,0 @@
|
|||||||
using System.Diagnostics.CodeAnalysis;
|
|
||||||
|
|
||||||
namespace NubLang.TypeChecking.Node;
|
|
||||||
|
|
||||||
public abstract class TypeNode : IEquatable<TypeNode>
|
|
||||||
{
|
|
||||||
public bool IsSimpleType([NotNullWhen(true)] out SimpleTypeNode? simpleType, [NotNullWhen(false)] out ComplexTypeNode? complexType)
|
|
||||||
{
|
|
||||||
if (this is SimpleTypeNode st)
|
|
||||||
{
|
|
||||||
complexType = null;
|
|
||||||
simpleType = st;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this is ComplexTypeNode ct)
|
|
||||||
{
|
|
||||||
complexType = ct;
|
|
||||||
simpleType = null;
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
throw new ArgumentException($"Type {this} is not a simple type nor a complex type");
|
|
||||||
}
|
|
||||||
|
|
||||||
public override bool Equals(object? obj) => obj is TypeNode other && Equals(other);
|
|
||||||
|
|
||||||
public abstract bool Equals(TypeNode? other);
|
|
||||||
public abstract override int GetHashCode();
|
|
||||||
public abstract override string ToString();
|
|
||||||
|
|
||||||
public static bool operator ==(TypeNode? left, TypeNode? right) => Equals(left, right);
|
|
||||||
public static bool operator !=(TypeNode? left, TypeNode? right) => !Equals(left, right);
|
|
||||||
}
|
|
||||||
|
|
||||||
public enum StorageSize
|
|
||||||
{
|
|
||||||
Void,
|
|
||||||
I8,
|
|
||||||
I16,
|
|
||||||
I32,
|
|
||||||
I64,
|
|
||||||
U8,
|
|
||||||
U16,
|
|
||||||
U32,
|
|
||||||
U64,
|
|
||||||
F32,
|
|
||||||
F64
|
|
||||||
}
|
|
||||||
|
|
||||||
public abstract class SimpleTypeNode : TypeNode
|
|
||||||
{
|
|
||||||
public abstract StorageSize StorageSize { get; }
|
|
||||||
}
|
|
||||||
|
|
||||||
#region Simple types
|
|
||||||
|
|
||||||
public class IntTypeNode(bool signed, int width) : SimpleTypeNode
|
|
||||||
{
|
|
||||||
public bool Signed { get; } = signed;
|
|
||||||
public int Width { get; } = width;
|
|
||||||
|
|
||||||
public override StorageSize StorageSize => Signed switch
|
|
||||||
{
|
|
||||||
true => Width switch
|
|
||||||
{
|
|
||||||
8 => StorageSize.I8,
|
|
||||||
16 => StorageSize.I16,
|
|
||||||
32 => StorageSize.I32,
|
|
||||||
64 => StorageSize.I64,
|
|
||||||
_ => throw new ArgumentOutOfRangeException(nameof(Width))
|
|
||||||
},
|
|
||||||
false => Width switch
|
|
||||||
{
|
|
||||||
8 => StorageSize.U8,
|
|
||||||
16 => StorageSize.U16,
|
|
||||||
32 => StorageSize.U32,
|
|
||||||
64 => StorageSize.U64,
|
|
||||||
_ => throw new ArgumentOutOfRangeException(nameof(Width))
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
public override string ToString() => $"{(Signed ? "i" : "u")}{Width}";
|
|
||||||
public override bool Equals(TypeNode? other) => other is IntTypeNode @int && @int.Width == Width && @int.Signed == Signed;
|
|
||||||
public override int GetHashCode() => HashCode.Combine(typeof(IntTypeNode), Signed, Width);
|
|
||||||
}
|
|
||||||
|
|
||||||
public class FloatTypeNode(int width) : SimpleTypeNode
|
|
||||||
{
|
|
||||||
public int Width { get; } = width;
|
|
||||||
|
|
||||||
public override StorageSize StorageSize => Width switch
|
|
||||||
{
|
|
||||||
32 => StorageSize.F32,
|
|
||||||
64 => StorageSize.F64,
|
|
||||||
_ => throw new ArgumentOutOfRangeException(nameof(Width))
|
|
||||||
};
|
|
||||||
|
|
||||||
public override string ToString() => $"f{Width}";
|
|
||||||
public override bool Equals(TypeNode? other) => other is FloatTypeNode @int && @int.Width == Width;
|
|
||||||
public override int GetHashCode() => HashCode.Combine(typeof(FloatTypeNode), Width);
|
|
||||||
}
|
|
||||||
|
|
||||||
public class BoolTypeNode : SimpleTypeNode
|
|
||||||
{
|
|
||||||
public override StorageSize StorageSize => StorageSize.U8;
|
|
||||||
|
|
||||||
public override string ToString() => "bool";
|
|
||||||
public override bool Equals(TypeNode? other) => other is BoolTypeNode;
|
|
||||||
public override int GetHashCode() => HashCode.Combine(typeof(BoolTypeNode));
|
|
||||||
}
|
|
||||||
|
|
||||||
public class FuncTypeNode(IReadOnlyList<TypeNode> parameters, TypeNode returnType) : SimpleTypeNode
|
|
||||||
{
|
|
||||||
public IReadOnlyList<TypeNode> Parameters { get; } = parameters;
|
|
||||||
public TypeNode ReturnType { get; } = returnType;
|
|
||||||
|
|
||||||
public override StorageSize StorageSize => StorageSize.U64;
|
|
||||||
|
|
||||||
public override string ToString() => $"func({string.Join(", ", Parameters)}): {ReturnType}";
|
|
||||||
public override bool Equals(TypeNode? other) => other is FuncTypeNode func && ReturnType.Equals(func.ReturnType) && Parameters.SequenceEqual(func.Parameters);
|
|
||||||
|
|
||||||
public override int GetHashCode()
|
|
||||||
{
|
|
||||||
var hash = new HashCode();
|
|
||||||
hash.Add(typeof(FuncTypeNode));
|
|
||||||
hash.Add(ReturnType);
|
|
||||||
foreach (var param in Parameters)
|
|
||||||
{
|
|
||||||
hash.Add(param);
|
|
||||||
}
|
|
||||||
|
|
||||||
return hash.ToHashCode();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public class PointerTypeNode(TypeNode baseType) : SimpleTypeNode
|
|
||||||
{
|
|
||||||
public TypeNode BaseType { get; } = baseType;
|
|
||||||
|
|
||||||
public override StorageSize StorageSize => StorageSize.U64;
|
|
||||||
|
|
||||||
public override string ToString() => "^" + BaseType;
|
|
||||||
public override bool Equals(TypeNode? other) => other is PointerTypeNode pointer && BaseType.Equals(pointer.BaseType);
|
|
||||||
public override int GetHashCode() => HashCode.Combine(typeof(PointerTypeNode), BaseType);
|
|
||||||
}
|
|
||||||
|
|
||||||
public class VoidTypeNode : SimpleTypeNode
|
|
||||||
{
|
|
||||||
public override StorageSize StorageSize => StorageSize.Void;
|
|
||||||
|
|
||||||
public override string ToString() => "void";
|
|
||||||
public override bool Equals(TypeNode? other) => other is VoidTypeNode;
|
|
||||||
public override int GetHashCode() => HashCode.Combine(typeof(VoidTypeNode));
|
|
||||||
}
|
|
||||||
|
|
||||||
#endregion
|
|
||||||
|
|
||||||
public abstract class ComplexTypeNode : TypeNode;
|
|
||||||
|
|
||||||
#region Complex types
|
|
||||||
|
|
||||||
public class CStringTypeNode : ComplexTypeNode
|
|
||||||
{
|
|
||||||
public override string ToString() => "cstring";
|
|
||||||
public override bool Equals(TypeNode? other) => other is CStringTypeNode;
|
|
||||||
public override int GetHashCode() => HashCode.Combine(typeof(CStringTypeNode));
|
|
||||||
}
|
|
||||||
|
|
||||||
public class StringTypeNode : ComplexTypeNode
|
|
||||||
{
|
|
||||||
public override string ToString() => "string";
|
|
||||||
public override bool Equals(TypeNode? other) => other is StringTypeNode;
|
|
||||||
public override int GetHashCode() => HashCode.Combine(typeof(StringTypeNode));
|
|
||||||
}
|
|
||||||
|
|
||||||
public class StructTypeField(string name, TypeNode type, int index, bool hasDefaultValue)
|
|
||||||
{
|
|
||||||
public string Name { get; } = name;
|
|
||||||
public TypeNode Type { get; } = type;
|
|
||||||
public int Index { get; } = index;
|
|
||||||
public bool HasDefaultValue { get; } = hasDefaultValue;
|
|
||||||
}
|
|
||||||
|
|
||||||
public class StructTypeFunc(string name, FuncTypeNode type)
|
|
||||||
{
|
|
||||||
public string Name { get; } = name;
|
|
||||||
public FuncTypeNode Type { get; } = type;
|
|
||||||
}
|
|
||||||
|
|
||||||
public class StructTypeNode(string module, string name, IReadOnlyList<StructTypeField> fields, IReadOnlyList<StructTypeFunc> functions, IReadOnlyList<InterfaceTypeNode> interfaceImplementations) : ComplexTypeNode
|
|
||||||
{
|
|
||||||
public string Module { get; } = module;
|
|
||||||
public string Name { get; } = name;
|
|
||||||
public IReadOnlyList<StructTypeField> Fields { get; set; } = fields;
|
|
||||||
public IReadOnlyList<StructTypeFunc> Functions { get; set; } = functions;
|
|
||||||
public IReadOnlyList<InterfaceTypeNode> InterfaceImplementations { get; set; } = interfaceImplementations;
|
|
||||||
|
|
||||||
public override string ToString() => Name;
|
|
||||||
public override bool Equals(TypeNode? other) => other is StructTypeNode structType && Name == structType.Name && Module == structType.Module;
|
|
||||||
public override int GetHashCode() => HashCode.Combine(typeof(StructTypeNode), Name);
|
|
||||||
}
|
|
||||||
|
|
||||||
public class InterfaceTypeFunc(string name, FuncTypeNode type, int index)
|
|
||||||
{
|
|
||||||
public string Name { get; } = name;
|
|
||||||
public FuncTypeNode Type { get; } = type;
|
|
||||||
public int Index { get; } = index;
|
|
||||||
}
|
|
||||||
|
|
||||||
public class InterfaceTypeNode(string module, string name, IReadOnlyList<InterfaceTypeFunc> functions) : ComplexTypeNode
|
|
||||||
{
|
|
||||||
public string Module { get; } = module;
|
|
||||||
public string Name { get; } = name;
|
|
||||||
public IReadOnlyList<InterfaceTypeFunc> Functions { get; set; } = functions;
|
|
||||||
|
|
||||||
public override string ToString() => Name;
|
|
||||||
public override bool Equals(TypeNode? other) => other is InterfaceTypeNode interfaceType && Name == interfaceType.Name && Module == interfaceType.Module;
|
|
||||||
public override int GetHashCode() => HashCode.Combine(typeof(InterfaceTypeNode), Name);
|
|
||||||
}
|
|
||||||
|
|
||||||
public class ArrayTypeNode(TypeNode elementType) : ComplexTypeNode
|
|
||||||
{
|
|
||||||
public TypeNode ElementType { get; } = elementType;
|
|
||||||
|
|
||||||
public override string ToString() => "[]" + ElementType;
|
|
||||||
|
|
||||||
public override bool Equals(TypeNode? other) => other is ArrayTypeNode array && ElementType.Equals(array.ElementType);
|
|
||||||
public override int GetHashCode() => HashCode.Combine(typeof(ArrayTypeNode), ElementType);
|
|
||||||
}
|
|
||||||
|
|
||||||
#endregion
|
|
||||||
@@ -1,457 +0,0 @@
|
|||||||
using NubLang.Diagnostics;
|
|
||||||
using NubLang.Parsing.Syntax;
|
|
||||||
using NubLang.Tokenization;
|
|
||||||
using NubLang.TypeChecking.Node;
|
|
||||||
|
|
||||||
namespace NubLang.TypeChecking;
|
|
||||||
|
|
||||||
public sealed class TypeChecker
|
|
||||||
{
|
|
||||||
private readonly Module _currentModule;
|
|
||||||
private readonly IReadOnlyDictionary<string, ModuleSignature> _moduleSignatures;
|
|
||||||
|
|
||||||
private readonly Stack<Scope> _scopes = [];
|
|
||||||
private readonly Stack<TypeNode> _funcReturnTypes = [];
|
|
||||||
private readonly List<Diagnostic> _diagnostics = [];
|
|
||||||
|
|
||||||
private Scope Scope => _scopes.Peek();
|
|
||||||
|
|
||||||
public TypeChecker(Module currentModule, IReadOnlyDictionary<string, ModuleSignature> moduleSignatures)
|
|
||||||
{
|
|
||||||
_currentModule = currentModule;
|
|
||||||
_moduleSignatures = moduleSignatures.Where(x => currentModule.Imports.Contains(x.Key) || _currentModule.Name == x.Key).ToDictionary();
|
|
||||||
}
|
|
||||||
|
|
||||||
public IReadOnlyList<Diagnostic> GetDiagnostics() => _diagnostics;
|
|
||||||
|
|
||||||
public TypedModule CheckModule()
|
|
||||||
{
|
|
||||||
_diagnostics.Clear();
|
|
||||||
_scopes.Clear();
|
|
||||||
|
|
||||||
var definitions = new List<DefinitionNode>();
|
|
||||||
|
|
||||||
foreach (var definition in _currentModule.Definitions)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
definitions.Add(CheckDefinition(definition));
|
|
||||||
}
|
|
||||||
catch (TypeCheckerException e)
|
|
||||||
{
|
|
||||||
_diagnostics.Add(e.Diagnostic);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return new TypedModule(_currentModule.Name, definitions);
|
|
||||||
}
|
|
||||||
|
|
||||||
private DefinitionNode CheckDefinition(DefinitionSyntax node)
|
|
||||||
{
|
|
||||||
return node switch
|
|
||||||
{
|
|
||||||
ExternFuncSyntax definition => CheckExternFuncDefinition(definition),
|
|
||||||
InterfaceSyntax definition => CheckInterfaceDefinition(definition),
|
|
||||||
FuncSyntax definition => CheckLocalFuncDefinition(definition),
|
|
||||||
StructSyntax definition => CheckStructDefinition(definition),
|
|
||||||
_ => throw new ArgumentOutOfRangeException(nameof(node))
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
private InterfaceNode CheckInterfaceDefinition(InterfaceSyntax node)
|
|
||||||
{
|
|
||||||
throw new NotImplementedException();
|
|
||||||
}
|
|
||||||
|
|
||||||
private StructNode CheckStructDefinition(StructSyntax node)
|
|
||||||
{
|
|
||||||
var fields = new List<StructFieldNode>();
|
|
||||||
foreach (var field in node.Fields)
|
|
||||||
{
|
|
||||||
var value = Optional.Empty<ExpressionNode>();
|
|
||||||
if (field.Value.HasValue)
|
|
||||||
{
|
|
||||||
value = CheckExpression(field.Value.Value);
|
|
||||||
}
|
|
||||||
|
|
||||||
fields.Add(new StructFieldNode(field.Index, field.Name, ResolveType(field.Type), value));
|
|
||||||
}
|
|
||||||
|
|
||||||
var functions = new List<StructFuncNode>();
|
|
||||||
foreach (var function in node.Functions)
|
|
||||||
{
|
|
||||||
var scope = new Scope();
|
|
||||||
// todo(nub31): Add this parameter
|
|
||||||
foreach (var parameter in function.Signature.Parameters)
|
|
||||||
{
|
|
||||||
scope.Declare(new Identifier(parameter.Name, ResolveType(parameter.Type), IdentifierKind.FunctionParameter));
|
|
||||||
}
|
|
||||||
|
|
||||||
_funcReturnTypes.Push(ResolveType(function.Signature.ReturnType));
|
|
||||||
var body = CheckBlock(function.Body, scope);
|
|
||||||
_funcReturnTypes.Pop();
|
|
||||||
functions.Add(new StructFuncNode(function.Name, CheckFuncSignature(function.Signature), body));
|
|
||||||
}
|
|
||||||
|
|
||||||
var interfaceImplementations = new List<InterfaceTypeNode>();
|
|
||||||
foreach (var interfaceImplementation in node.InterfaceImplementations)
|
|
||||||
{
|
|
||||||
var type = ResolveType(interfaceImplementation);
|
|
||||||
if (type is not InterfaceTypeNode interfaceType)
|
|
||||||
{
|
|
||||||
_diagnostics.Add(Diagnostic.Error($"Struct {node.Name} cannot implement non-struct type {interfaceImplementation}").At(interfaceImplementation).Build());
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
interfaceImplementations.Add(interfaceType);
|
|
||||||
}
|
|
||||||
|
|
||||||
return new StructNode(node.Name, fields, functions, interfaceImplementations);
|
|
||||||
}
|
|
||||||
|
|
||||||
private ExternFuncNode CheckExternFuncDefinition(ExternFuncSyntax node)
|
|
||||||
{
|
|
||||||
return new ExternFuncNode(node.Name, node.CallName, CheckFuncSignature(node.Signature));
|
|
||||||
}
|
|
||||||
|
|
||||||
private LocalFuncNode CheckLocalFuncDefinition(FuncSyntax node)
|
|
||||||
{
|
|
||||||
var scope = new Scope();
|
|
||||||
foreach (var parameter in node.Signature.Parameters)
|
|
||||||
{
|
|
||||||
scope.Declare(new Identifier(parameter.Name, ResolveType(parameter.Type), IdentifierKind.FunctionParameter));
|
|
||||||
}
|
|
||||||
|
|
||||||
_funcReturnTypes.Push(ResolveType(node.Signature.ReturnType));
|
|
||||||
var body = CheckBlock(node.Body, scope);
|
|
||||||
_funcReturnTypes.Pop();
|
|
||||||
return new LocalFuncNode(node.Name, CheckFuncSignature(node.Signature), body);
|
|
||||||
}
|
|
||||||
|
|
||||||
private StatementNode CheckStatement(StatementSyntax node)
|
|
||||||
{
|
|
||||||
return node switch
|
|
||||||
{
|
|
||||||
AssignmentSyntax statement => CheckAssignment(statement),
|
|
||||||
BreakSyntax => new BreakNode(),
|
|
||||||
ContinueSyntax => new ContinueNode(),
|
|
||||||
IfSyntax statement => CheckIf(statement),
|
|
||||||
ReturnSyntax statement => CheckReturn(statement),
|
|
||||||
StatementExpressionSyntax statement => CheckStatementExpression(statement),
|
|
||||||
VariableDeclarationSyntax statement => CheckVariableDeclaration(statement),
|
|
||||||
WhileSyntax statement => CheckWhile(statement),
|
|
||||||
_ => throw new ArgumentOutOfRangeException(nameof(node))
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
private StatementNode CheckAssignment(AssignmentSyntax statement)
|
|
||||||
{
|
|
||||||
throw new NotImplementedException();
|
|
||||||
}
|
|
||||||
|
|
||||||
private IfNode CheckIf(IfSyntax statement)
|
|
||||||
{
|
|
||||||
throw new NotImplementedException();
|
|
||||||
}
|
|
||||||
|
|
||||||
private ReturnNode CheckReturn(ReturnSyntax statement)
|
|
||||||
{
|
|
||||||
var value = Optional.Empty<ExpressionNode>();
|
|
||||||
|
|
||||||
if (statement.Value.HasValue)
|
|
||||||
{
|
|
||||||
value = CheckExpression(statement.Value.Value, _funcReturnTypes.Peek());
|
|
||||||
}
|
|
||||||
|
|
||||||
return new ReturnNode(value);
|
|
||||||
}
|
|
||||||
|
|
||||||
private StatementExpressionNode CheckStatementExpression(StatementExpressionSyntax statement)
|
|
||||||
{
|
|
||||||
return new StatementExpressionNode(CheckExpression(statement.Expression));
|
|
||||||
}
|
|
||||||
|
|
||||||
private VariableDeclarationNode CheckVariableDeclaration(VariableDeclarationSyntax statement)
|
|
||||||
{
|
|
||||||
TypeNode? type = null;
|
|
||||||
ExpressionNode? assignmentNode = null;
|
|
||||||
|
|
||||||
if (statement.ExplicitType.TryGetValue(out var explicitType))
|
|
||||||
{
|
|
||||||
type = ResolveType(explicitType);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (statement.Assignment.TryGetValue(out var assignment))
|
|
||||||
{
|
|
||||||
assignmentNode = CheckExpression(assignment, type);
|
|
||||||
type ??= assignmentNode.Type;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (type == null)
|
|
||||||
{
|
|
||||||
throw new TypeCheckerException(Diagnostic.Error($"Cannot infer type of variable {statement.Name}").At(statement).Build());
|
|
||||||
}
|
|
||||||
|
|
||||||
Scope.Declare(new Identifier(statement.Name, type, IdentifierKind.Variable));
|
|
||||||
|
|
||||||
return new VariableDeclarationNode(statement.Name, Optional.OfNullable(assignmentNode), type);
|
|
||||||
}
|
|
||||||
|
|
||||||
private WhileNode CheckWhile(WhileSyntax statement)
|
|
||||||
{
|
|
||||||
throw new NotImplementedException();
|
|
||||||
}
|
|
||||||
|
|
||||||
private FuncSignatureNode CheckFuncSignature(FuncSignatureSyntax statement)
|
|
||||||
{
|
|
||||||
var parameters = new List<FuncParameterNode>();
|
|
||||||
foreach (var parameter in statement.Parameters)
|
|
||||||
{
|
|
||||||
parameters.Add(new FuncParameterNode(parameter.Name, ResolveType(parameter.Type)));
|
|
||||||
}
|
|
||||||
|
|
||||||
return new FuncSignatureNode(parameters, ResolveType(statement.ReturnType));
|
|
||||||
}
|
|
||||||
|
|
||||||
private ExpressionNode CheckExpression(ExpressionSyntax node, TypeNode? expectedType = null)
|
|
||||||
{
|
|
||||||
var result = node switch
|
|
||||||
{
|
|
||||||
AddressOfSyntax expression => CheckAddressOf(expression),
|
|
||||||
ArrayIndexAccessSyntax expression => CheckArrayIndexAccess(expression),
|
|
||||||
ArrayInitializerSyntax expression => CheckArrayInitializer(expression),
|
|
||||||
BinaryExpressionSyntax expression => CheckBinaryExpression(expression),
|
|
||||||
DereferenceSyntax expression => CheckDereference(expression),
|
|
||||||
DotFuncCallSyntax expression => CheckDotFuncCall(expression),
|
|
||||||
FuncCallSyntax expression => CheckFuncCall(expression),
|
|
||||||
IdentifierSyntax expression => CheckIdentifier(expression),
|
|
||||||
LiteralSyntax expression => CheckLiteral(expression, expectedType),
|
|
||||||
StructFieldAccessSyntax expression => CheckStructFieldAccess(expression),
|
|
||||||
StructInitializerSyntax expression => CheckStructInitializer(expression, expectedType),
|
|
||||||
UnaryExpressionSyntax expression => CheckUnaryExpression(expression),
|
|
||||||
_ => throw new ArgumentOutOfRangeException(nameof(node))
|
|
||||||
};
|
|
||||||
|
|
||||||
if (expectedType == null || result.Type == expectedType)
|
|
||||||
{
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (result.Type is StructTypeNode structType && expectedType is InterfaceTypeNode interfaceType)
|
|
||||||
{
|
|
||||||
return new ConvertToInterfaceNode(interfaceType, interfaceType, structType, result);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (result.Type is IntTypeNode sourceIntType && expectedType is IntTypeNode targetIntType)
|
|
||||||
{
|
|
||||||
if (sourceIntType.Signed == targetIntType.Signed && sourceIntType.Width < targetIntType.Width)
|
|
||||||
{
|
|
||||||
return new ConvertIntNode(targetIntType, result, sourceIntType, targetIntType);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (result.Type is FloatTypeNode sourceFloatType && expectedType is FloatTypeNode targetFloatType)
|
|
||||||
{
|
|
||||||
if (sourceFloatType.Width < targetFloatType.Width)
|
|
||||||
{
|
|
||||||
return new ConvertFloatNode(targetFloatType, result, sourceFloatType, targetFloatType);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
throw new TypeCheckerException(Diagnostic.Error($"Cannot convert {result.Type} to {expectedType}").At(node).Build());
|
|
||||||
}
|
|
||||||
|
|
||||||
private AddressOfNode CheckAddressOf(AddressOfSyntax expression)
|
|
||||||
{
|
|
||||||
throw new NotImplementedException();
|
|
||||||
}
|
|
||||||
|
|
||||||
private ArrayIndexAccessNode CheckArrayIndexAccess(ArrayIndexAccessSyntax expression)
|
|
||||||
{
|
|
||||||
throw new NotImplementedException();
|
|
||||||
}
|
|
||||||
|
|
||||||
private ArrayInitializerNode CheckArrayInitializer(ArrayInitializerSyntax expression)
|
|
||||||
{
|
|
||||||
throw new NotImplementedException();
|
|
||||||
}
|
|
||||||
|
|
||||||
private BinaryExpressionNode CheckBinaryExpression(BinaryExpressionSyntax expression)
|
|
||||||
{
|
|
||||||
throw new NotImplementedException();
|
|
||||||
}
|
|
||||||
|
|
||||||
private DereferenceNode CheckDereference(DereferenceSyntax expression)
|
|
||||||
{
|
|
||||||
throw new NotImplementedException();
|
|
||||||
}
|
|
||||||
|
|
||||||
private FuncCallNode CheckFuncCall(FuncCallSyntax expression)
|
|
||||||
{
|
|
||||||
var accessor = CheckExpression(expression.Expression);
|
|
||||||
if (accessor.Type is not FuncTypeNode funcType)
|
|
||||||
{
|
|
||||||
throw new TypeCheckerException(Diagnostic.Error($"Cannot call non-function type {accessor.Type}").At(expression.Expression).Build());
|
|
||||||
}
|
|
||||||
|
|
||||||
if (expression.Parameters.Count != funcType.Parameters.Count)
|
|
||||||
{
|
|
||||||
throw new TypeCheckerException(Diagnostic.Error($"Function {funcType} expects {funcType.Parameters} but got {expression.Parameters.Count} parameters").At(expression.Expression).Build());
|
|
||||||
}
|
|
||||||
|
|
||||||
var parameters = new List<ExpressionNode>();
|
|
||||||
for (var i = 0; i < expression.Parameters.Count; i++)
|
|
||||||
{
|
|
||||||
var parameter = expression.Parameters[i];
|
|
||||||
var expectedType = funcType.Parameters[i];
|
|
||||||
|
|
||||||
var parameterExpression = CheckExpression(parameter, expectedType);
|
|
||||||
if (parameterExpression.Type != expectedType)
|
|
||||||
{
|
|
||||||
throw new Exception($"Parameter {i + 1} does not match the type {expectedType} for function {funcType}");
|
|
||||||
}
|
|
||||||
|
|
||||||
parameters.Add(parameterExpression);
|
|
||||||
}
|
|
||||||
|
|
||||||
return new FuncCallNode(funcType.ReturnType, accessor, parameters);
|
|
||||||
}
|
|
||||||
|
|
||||||
private ExpressionNode CheckDotFuncCall(DotFuncCallSyntax expression)
|
|
||||||
{
|
|
||||||
throw new NotImplementedException();
|
|
||||||
}
|
|
||||||
|
|
||||||
private ExpressionNode CheckIdentifier(IdentifierSyntax expression)
|
|
||||||
{
|
|
||||||
// If the identifier does not have a module specified, first check if a local variable or function parameter with that identifier exists
|
|
||||||
if (!expression.Module.TryGetValue(out var moduleName))
|
|
||||||
{
|
|
||||||
var scopeIdent = Scope.Lookup(expression.Name);
|
|
||||||
if (scopeIdent != null)
|
|
||||||
{
|
|
||||||
switch (scopeIdent.Kind)
|
|
||||||
{
|
|
||||||
case IdentifierKind.Variable:
|
|
||||||
{
|
|
||||||
return new VariableIdentifierNode(scopeIdent.Type, expression.Name);
|
|
||||||
}
|
|
||||||
case IdentifierKind.FunctionParameter:
|
|
||||||
{
|
|
||||||
return new FuncParameterIdentifierNode(scopeIdent.Type, expression.Name);
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
{
|
|
||||||
throw new ArgumentOutOfRangeException();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
moduleName ??= _currentModule.Name;
|
|
||||||
if (_moduleSignatures.TryGetValue(moduleName, out var module))
|
|
||||||
{
|
|
||||||
if (module.Functions.TryGetValue(expression.Name, out var function))
|
|
||||||
{
|
|
||||||
return new FuncIdentifierNode(function, moduleName, expression.Name);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
throw new TypeCheckerException(Diagnostic.Error($"Identifier {expression.Name} not found").At(expression).Build());
|
|
||||||
}
|
|
||||||
|
|
||||||
private LiteralNode CheckLiteral(LiteralSyntax expression, TypeNode? expectedType)
|
|
||||||
{
|
|
||||||
// todo(nub31): Check if the types can actually be represented as another one. For example, an int should be passed when a string is expected
|
|
||||||
var type = expectedType ?? expression.Kind switch
|
|
||||||
{
|
|
||||||
LiteralKind.Integer => new IntTypeNode(true, 64),
|
|
||||||
LiteralKind.Float => new FloatTypeNode(64),
|
|
||||||
LiteralKind.String => new StringTypeNode(),
|
|
||||||
LiteralKind.Bool => new BoolTypeNode(),
|
|
||||||
_ => throw new ArgumentOutOfRangeException()
|
|
||||||
};
|
|
||||||
|
|
||||||
return new LiteralNode(type, expression.Value, expression.Kind);
|
|
||||||
}
|
|
||||||
|
|
||||||
private StructFieldAccessNode CheckStructFieldAccess(StructFieldAccessSyntax expression)
|
|
||||||
{
|
|
||||||
throw new NotImplementedException();
|
|
||||||
}
|
|
||||||
|
|
||||||
private StructInitializerNode CheckStructInitializer(StructInitializerSyntax expression, TypeNode? expectedType)
|
|
||||||
{
|
|
||||||
throw new NotImplementedException();
|
|
||||||
}
|
|
||||||
|
|
||||||
private UnaryExpressionNode CheckUnaryExpression(UnaryExpressionSyntax expression)
|
|
||||||
{
|
|
||||||
throw new NotImplementedException();
|
|
||||||
}
|
|
||||||
|
|
||||||
private BlockNode CheckBlock(BlockSyntax node, Scope? scope = null)
|
|
||||||
{
|
|
||||||
var statements = new List<StatementNode>();
|
|
||||||
|
|
||||||
_scopes.Push(scope ?? Scope.SubScope());
|
|
||||||
|
|
||||||
foreach (var statement in node.Statements)
|
|
||||||
{
|
|
||||||
statements.Add(CheckStatement(statement));
|
|
||||||
}
|
|
||||||
|
|
||||||
_scopes.Pop();
|
|
||||||
|
|
||||||
return new BlockNode(statements);
|
|
||||||
}
|
|
||||||
|
|
||||||
private TypeNode ResolveType(TypeSyntax fieldType)
|
|
||||||
{
|
|
||||||
return TypeResolver.ResolveType(fieldType, _moduleSignatures);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public enum IdentifierKind
|
|
||||||
{
|
|
||||||
Variable,
|
|
||||||
FunctionParameter
|
|
||||||
}
|
|
||||||
|
|
||||||
public record Identifier(string Name, TypeNode Type, IdentifierKind Kind);
|
|
||||||
|
|
||||||
public class Scope(Scope? parent = null)
|
|
||||||
{
|
|
||||||
private readonly List<Identifier> _variables = [];
|
|
||||||
|
|
||||||
public Identifier? Lookup(string name)
|
|
||||||
{
|
|
||||||
var variable = _variables.FirstOrDefault(x => x.Name == name);
|
|
||||||
if (variable != null)
|
|
||||||
{
|
|
||||||
return variable;
|
|
||||||
}
|
|
||||||
|
|
||||||
return parent?.Lookup(name);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Declare(Identifier identifier)
|
|
||||||
{
|
|
||||||
_variables.Add(identifier);
|
|
||||||
}
|
|
||||||
|
|
||||||
public Scope SubScope()
|
|
||||||
{
|
|
||||||
return new Scope(this);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public class TypeCheckerException : Exception
|
|
||||||
{
|
|
||||||
public Diagnostic Diagnostic { get; }
|
|
||||||
|
|
||||||
public TypeCheckerException(Diagnostic diagnostic) : base(diagnostic.Message)
|
|
||||||
{
|
|
||||||
Diagnostic = diagnostic;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,80 +0,0 @@
|
|||||||
using NubLang.Parsing.Syntax;
|
|
||||||
using NubLang.TypeChecking;
|
|
||||||
using NubLang.TypeChecking.Node;
|
|
||||||
|
|
||||||
namespace NubLang;
|
|
||||||
|
|
||||||
public static class TypeResolver
|
|
||||||
{
|
|
||||||
public static TypeNode ResolveType(TypeSyntax type, IReadOnlyDictionary<string, ModuleSignature> modules)
|
|
||||||
{
|
|
||||||
return type switch
|
|
||||||
{
|
|
||||||
BoolTypeSyntax => new BoolTypeNode(),
|
|
||||||
CStringTypeSyntax => new CStringTypeNode(),
|
|
||||||
IntTypeSyntax i => new IntTypeNode(i.Signed, i.Width),
|
|
||||||
CustomTypeSyntax c => ResolveCustomType(c.Module, c.Name, modules),
|
|
||||||
FloatTypeSyntax f => new FloatTypeNode(f.Width),
|
|
||||||
FuncTypeSyntax func => new FuncTypeNode(func.Parameters.Select(x => ResolveType(x, modules)).ToList(), ResolveType(func.ReturnType, modules)),
|
|
||||||
ArrayTypeSyntax arr => new ArrayTypeNode(ResolveType(arr.BaseType, modules)),
|
|
||||||
PointerTypeSyntax ptr => new PointerTypeNode(ResolveType(ptr.BaseType, modules)),
|
|
||||||
StringTypeSyntax => new StringTypeNode(),
|
|
||||||
VoidTypeSyntax => new VoidTypeNode(),
|
|
||||||
_ => throw new NotSupportedException($"Unknown type syntax: {type}")
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
public static TypeNode ResolveCustomType(string moduleName, string typeName, IReadOnlyDictionary<string, ModuleSignature> modules)
|
|
||||||
{
|
|
||||||
if (!modules.TryGetValue(moduleName, out var module))
|
|
||||||
{
|
|
||||||
throw new Exception("Module not found: " + moduleName);
|
|
||||||
}
|
|
||||||
|
|
||||||
var structType = module.StructTypes.FirstOrDefault(x => x.Name == typeName);
|
|
||||||
if (structType != null)
|
|
||||||
{
|
|
||||||
return structType;
|
|
||||||
}
|
|
||||||
|
|
||||||
var interfaceType = module.InterfaceTypes.FirstOrDefault(x => x.Name == typeName);
|
|
||||||
if (interfaceType != null)
|
|
||||||
{
|
|
||||||
return interfaceType;
|
|
||||||
}
|
|
||||||
|
|
||||||
throw new Exception($"Type {typeName} not found in module {moduleName}");
|
|
||||||
}
|
|
||||||
|
|
||||||
public static StructTypeNode ResolveStructType(string moduleName, string structName, IReadOnlyDictionary<string, ModuleSignature> modules)
|
|
||||||
{
|
|
||||||
if (!modules.TryGetValue(moduleName, out var module))
|
|
||||||
{
|
|
||||||
throw new Exception("Module not found: " + moduleName);
|
|
||||||
}
|
|
||||||
|
|
||||||
var structType = module.StructTypes.FirstOrDefault(x => x.Name == structName);
|
|
||||||
if (structType != null)
|
|
||||||
{
|
|
||||||
return structType;
|
|
||||||
}
|
|
||||||
|
|
||||||
throw new Exception($"Struct type {structName} not found in module {moduleName}");
|
|
||||||
}
|
|
||||||
|
|
||||||
public static InterfaceTypeNode ResolveInterfaceType(string moduleName, string structName, IReadOnlyDictionary<string, ModuleSignature> modules)
|
|
||||||
{
|
|
||||||
if (!modules.TryGetValue(moduleName, out var module))
|
|
||||||
{
|
|
||||||
throw new Exception("Module not found: " + moduleName);
|
|
||||||
}
|
|
||||||
|
|
||||||
var structType = module.InterfaceTypes.FirstOrDefault(x => x.Name == structName);
|
|
||||||
if (structType != null)
|
|
||||||
{
|
|
||||||
return structType;
|
|
||||||
}
|
|
||||||
|
|
||||||
throw new Exception($"Interface type {structName} not found in module {moduleName}");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,75 +0,0 @@
|
|||||||
using System.Diagnostics.CodeAnalysis;
|
|
||||||
|
|
||||||
namespace NubLang;
|
|
||||||
|
|
||||||
public readonly struct Variant<T1, T2> where T1 : notnull where T2 : notnull
|
|
||||||
{
|
|
||||||
public Variant()
|
|
||||||
{
|
|
||||||
throw new InvalidOperationException("Variant must be initialized with a value");
|
|
||||||
}
|
|
||||||
|
|
||||||
public Variant(T1 value)
|
|
||||||
{
|
|
||||||
_value = value;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Variant(T2 value)
|
|
||||||
{
|
|
||||||
_value = value;
|
|
||||||
}
|
|
||||||
|
|
||||||
private readonly object _value;
|
|
||||||
|
|
||||||
public void Match(Action<T1> on1, Action<T2> on2)
|
|
||||||
{
|
|
||||||
switch (_value)
|
|
||||||
{
|
|
||||||
case T1 v1:
|
|
||||||
on1(v1);
|
|
||||||
break;
|
|
||||||
case T2 v2:
|
|
||||||
on2(v2);
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
throw new InvalidCastException();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public T Match<T>(Func<T1, T> on1, Func<T2, T> on2)
|
|
||||||
{
|
|
||||||
return _value switch
|
|
||||||
{
|
|
||||||
T1 v1 => on1(v1),
|
|
||||||
T2 v2 => on2(v2),
|
|
||||||
_ => throw new InvalidCastException()
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
public bool IsCase1([NotNullWhen(true)] out T1? value)
|
|
||||||
{
|
|
||||||
if (_value is T1 converted)
|
|
||||||
{
|
|
||||||
value = converted;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
value = default;
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
public bool IsCase2([NotNullWhen(true)] out T2? value)
|
|
||||||
{
|
|
||||||
if (_value is T2 converted)
|
|
||||||
{
|
|
||||||
value = converted;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
value = default;
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static implicit operator Variant<T1, T2>(T1 value) => new(value);
|
|
||||||
public static implicit operator Variant<T1, T2>(T2 value) => new(value);
|
|
||||||
}
|
|
||||||
@@ -1,17 +0,0 @@
|
|||||||
IndentWidth: 4
|
|
||||||
|
|
||||||
# Pointer formatting
|
|
||||||
DerivePointerAlignment: false
|
|
||||||
PointerAlignment: Left
|
|
||||||
|
|
||||||
# Function formatting
|
|
||||||
AllowShortFunctionsOnASingleLine: None
|
|
||||||
|
|
||||||
# Control how short statements are placed
|
|
||||||
AllowShortBlocksOnASingleLine: Never
|
|
||||||
AllowShortIfStatementsOnASingleLine: Never
|
|
||||||
AllowShortLoopsOnASingleLine: false
|
|
||||||
|
|
||||||
SeparateDefinitionBlocks: Always
|
|
||||||
|
|
||||||
BreakBeforeBraces: Stroustrup
|
|
||||||
1
src/runtime/.gitignore
vendored
1
src/runtime/.gitignore
vendored
@@ -1 +0,0 @@
|
|||||||
build
|
|
||||||
@@ -1,14 +0,0 @@
|
|||||||
CC ?= x86_64-linux-gnu-gcc
|
|
||||||
TARGET ?= x64
|
|
||||||
|
|
||||||
CFLAGS = -Wall -Werror -Wextra -g
|
|
||||||
|
|
||||||
libruntime: build/runtime.o
|
|
||||||
$(CC) $(CFLAGS) -c targets/$(TARGET).s -o build/$(TARGET).o
|
|
||||||
|
|
||||||
build/runtime.o: runtime/runtime.c
|
|
||||||
mkdir -p build
|
|
||||||
$(CC) $(CFLAGS) -c runtime/runtime.c -o build/runtime.o
|
|
||||||
|
|
||||||
clean:
|
|
||||||
rm -r build
|
|
||||||
@@ -1,47 +0,0 @@
|
|||||||
#include <stdint.h>
|
|
||||||
|
|
||||||
typedef struct nub_string {
|
|
||||||
uint64_t size;
|
|
||||||
uint8_t buffer[];
|
|
||||||
} nub_string_t;
|
|
||||||
|
|
||||||
uint64_t nub_cstring_length(const char* string)
|
|
||||||
{
|
|
||||||
uint64_t len = 0;
|
|
||||||
while (string[len] != '\0') {
|
|
||||||
len++;
|
|
||||||
}
|
|
||||||
return len;
|
|
||||||
}
|
|
||||||
|
|
||||||
uint64_t nub_string_length(const nub_string_t* string)
|
|
||||||
{
|
|
||||||
uint64_t count = 0;
|
|
||||||
uint64_t i = 0;
|
|
||||||
|
|
||||||
while (i < string->size) {
|
|
||||||
uint8_t byte = string->buffer[i];
|
|
||||||
|
|
||||||
if ((byte & 0b11000000) != 0b10000000) {
|
|
||||||
count++;
|
|
||||||
}
|
|
||||||
|
|
||||||
i++;
|
|
||||||
}
|
|
||||||
|
|
||||||
return count;
|
|
||||||
}
|
|
||||||
|
|
||||||
void nub_memcpy(uint8_t* source, uint8_t* destination, uint64_t length)
|
|
||||||
{
|
|
||||||
for (uint64_t i = 0; i < length; i++) {
|
|
||||||
destination[i] = source[i];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void nub_memset(uint8_t* destination, uint8_t value, uint64_t count)
|
|
||||||
{
|
|
||||||
for (uint64_t i = 0; i < count; i++) {
|
|
||||||
destination[i] = value;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,10 +0,0 @@
|
|||||||
.intel_syntax noprefix
|
|
||||||
|
|
||||||
.text
|
|
||||||
.globl _start
|
|
||||||
_start:
|
|
||||||
mov rdi, rsp
|
|
||||||
call main
|
|
||||||
mov rdi, rax
|
|
||||||
mov rax, 60
|
|
||||||
syscall
|
|
||||||
1
src/vscode/.gitignore
vendored
1
src/vscode/.gitignore
vendored
@@ -1 +0,0 @@
|
|||||||
bin
|
|
||||||
@@ -1,29 +0,0 @@
|
|||||||
{
|
|
||||||
"comments": {
|
|
||||||
"lineComment": "//",
|
|
||||||
"blockComment": ["/*", "*/"]
|
|
||||||
},
|
|
||||||
"brackets": [
|
|
||||||
["{", "}"],
|
|
||||||
["[", "]"],
|
|
||||||
["(", ")"]
|
|
||||||
],
|
|
||||||
"autoClosingPairs": [
|
|
||||||
["{", "}"],
|
|
||||||
["[", "]"],
|
|
||||||
["(", ")"],
|
|
||||||
["\"", "\""],
|
|
||||||
["'", "'"]
|
|
||||||
],
|
|
||||||
"surroundingPairs": [
|
|
||||||
["{", "}"],
|
|
||||||
["[", "]"],
|
|
||||||
["(", ")"],
|
|
||||||
["\"", "\""],
|
|
||||||
["'", "'"]
|
|
||||||
],
|
|
||||||
"indentationRules": {
|
|
||||||
"increaseIndentPattern": "^((?!\\/\\*).)*(\\{[^}\"'`]*|\\([^)\"'`]*|\\[[^\\]\"'`]*)$",
|
|
||||||
"decreaseIndentPattern": "^((?!.*?\\/\\*).*\\*/)?\\s*[\\}\\]\\)].*$"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,6 +0,0 @@
|
|||||||
vscode:
|
|
||||||
mkdir -p bin
|
|
||||||
npx vsce package -o bin/nub-lang.vsix
|
|
||||||
|
|
||||||
clean:
|
|
||||||
rm -r bin
|
|
||||||
@@ -1,40 +0,0 @@
|
|||||||
{
|
|
||||||
"name": "nub-lang",
|
|
||||||
"displayName": "Nub Language Support",
|
|
||||||
"description": "Syntax highlighting for Nub programming language",
|
|
||||||
"version": "0.0.0",
|
|
||||||
"publisher": "nub31",
|
|
||||||
"repository": {
|
|
||||||
"type": "git",
|
|
||||||
"url": "https://git.oliste.no/nub31/nub-lang"
|
|
||||||
},
|
|
||||||
"license": "MIT",
|
|
||||||
"engines": {
|
|
||||||
"vscode": "^1.74.0"
|
|
||||||
},
|
|
||||||
"categories": [
|
|
||||||
"Programming Languages"
|
|
||||||
],
|
|
||||||
"contributes": {
|
|
||||||
"languages": [
|
|
||||||
{
|
|
||||||
"id": "nub",
|
|
||||||
"aliases": [
|
|
||||||
"Nub",
|
|
||||||
"nub"
|
|
||||||
],
|
|
||||||
"extensions": [
|
|
||||||
".nub"
|
|
||||||
],
|
|
||||||
"configuration": "./language-configuration.json"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"grammars": [
|
|
||||||
{
|
|
||||||
"language": "nub",
|
|
||||||
"scopeName": "source.nub",
|
|
||||||
"path": "./syntaxes/nub.tmLanguage.json"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,300 +0,0 @@
|
|||||||
{
|
|
||||||
"$schema": "https://raw.githubusercontent.com/martinring/tmlanguage/master/tmlanguage.json",
|
|
||||||
"name": "Nub",
|
|
||||||
"scopeName": "source.nub",
|
|
||||||
"patterns": [
|
|
||||||
{
|
|
||||||
"include": "#comments"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"include": "#keywords"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"include": "#modifiers"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"include": "#types"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"include": "#strings"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"include": "#numbers"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"include": "#operators"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"include": "#function-definition"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"include": "#struct-definition"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"include": "#function-call"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"include": "#identifiers"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"repository": {
|
|
||||||
"comments": {
|
|
||||||
"patterns": [
|
|
||||||
{
|
|
||||||
"name": "comment.line.double-slash.nub",
|
|
||||||
"begin": "//",
|
|
||||||
"end": "$"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "comment.block.nub",
|
|
||||||
"begin": "/\\*",
|
|
||||||
"end": "\\*/"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"keywords": {
|
|
||||||
"patterns": [
|
|
||||||
{
|
|
||||||
"name": "keyword.control.nub",
|
|
||||||
"match": "\\b(if|else|while|break|continue|return|let)\\b"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "keyword.other.nub",
|
|
||||||
"match": "\\b(namespace|func|struct|interface)\\b"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"modifiers": {
|
|
||||||
"patterns": [
|
|
||||||
{
|
|
||||||
"name": "storage.modifier.nub",
|
|
||||||
"match": "\\b(export|extern|calls)\\b"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"types": {
|
|
||||||
"patterns": [
|
|
||||||
{
|
|
||||||
"include": "#function-type"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "storage.type.primitive.nub",
|
|
||||||
"match": "\\b(i8|i16|i32|i64|u8|u16|u32|u64|f32|f64|bool|string|cstring|void|any)\\b"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "storage.type.array.nub",
|
|
||||||
"match": "\\[\\]"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "storage.type.pointer.nub",
|
|
||||||
"match": "\\^"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"function-type": {
|
|
||||||
"patterns": [
|
|
||||||
{
|
|
||||||
"begin": "\\b(func)\\s*\\(",
|
|
||||||
"beginCaptures": {
|
|
||||||
"1": {
|
|
||||||
"name": "storage.type.function.nub"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"end": "(?<=\\))(?:\\s*:\\s*([^\\s,;{}()]+))?",
|
|
||||||
"endCaptures": {
|
|
||||||
"1": {
|
|
||||||
"name": "storage.type.nub"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"patterns": [
|
|
||||||
{
|
|
||||||
"include": "#function-type-parameters"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"function-type-parameters": {
|
|
||||||
"patterns": [
|
|
||||||
{
|
|
||||||
"match": "\\.\\.\\.",
|
|
||||||
"name": "keyword.operator.variadic.nub"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"include": "#types"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"match": ",",
|
|
||||||
"name": "punctuation.separator.nub"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"strings": {
|
|
||||||
"patterns": [
|
|
||||||
{
|
|
||||||
"name": "string.quoted.double.nub",
|
|
||||||
"begin": "\"",
|
|
||||||
"end": "\"",
|
|
||||||
"patterns": [
|
|
||||||
{
|
|
||||||
"name": "constant.character.escape.nub",
|
|
||||||
"match": "\\\\(n|t|r|\\\\|\"|')"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "constant.character.escape.nub",
|
|
||||||
"match": "\\\\[0-7]{1,3}"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "constant.character.escape.nub",
|
|
||||||
"match": "\\\\x[0-9A-Fa-f]{1,2}"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "string.quoted.single.nub",
|
|
||||||
"begin": "'",
|
|
||||||
"end": "'",
|
|
||||||
"patterns": [
|
|
||||||
{
|
|
||||||
"name": "constant.character.escape.nub",
|
|
||||||
"match": "\\\\(n|t|r|\\\\|\"|')"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"numbers": {
|
|
||||||
"patterns": [
|
|
||||||
{
|
|
||||||
"name": "constant.numeric.float.nub",
|
|
||||||
"match": "\\b\\d+\\.\\d*([eE][+-]?\\d+)?[fF]?\\b"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "constant.numeric.integer.decimal.nub",
|
|
||||||
"match": "\\b\\d+\\b"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "constant.numeric.integer.hexadecimal.nub",
|
|
||||||
"match": "\\b0[xX][0-9A-Fa-f]+\\b"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "constant.numeric.integer.binary.nub",
|
|
||||||
"match": "\\b0[bB][01]+\\b"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"operators": {
|
|
||||||
"patterns": [
|
|
||||||
{
|
|
||||||
"name": "keyword.operator.assignment.nub",
|
|
||||||
"match": "="
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "keyword.operator.comparison.nub",
|
|
||||||
"match": "(==|!=|<=|>=|<|>)"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "keyword.operator.arithmetic.nub",
|
|
||||||
"match": "(\\+|\\-|\\*|/)"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "keyword.operator.logical.nub",
|
|
||||||
"match": "(&&|\\|\\||!)"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "keyword.operator.address.nub",
|
|
||||||
"match": "&"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "keyword.operator.dereference.nub",
|
|
||||||
"match": "\\^"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "keyword.operator.member-access.nub",
|
|
||||||
"match": "\\."
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"function-definition": {
|
|
||||||
"patterns": [
|
|
||||||
{
|
|
||||||
"begin": "\\b(global\\s+|extern\\s+)?(func)\\s+([a-zA-Z_][a-zA-Z0-9_]*)\\s*\\(",
|
|
||||||
"beginCaptures": {
|
|
||||||
"1": {
|
|
||||||
"name": "storage.modifier.nub"
|
|
||||||
},
|
|
||||||
"2": {
|
|
||||||
"name": "keyword.other.nub"
|
|
||||||
},
|
|
||||||
"3": {
|
|
||||||
"name": "entity.name.function.nub"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"end": "\\)",
|
|
||||||
"patterns": [
|
|
||||||
{
|
|
||||||
"include": "#function-parameters"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"struct-definition": {
|
|
||||||
"patterns": [
|
|
||||||
{
|
|
||||||
"match": "\\b(struct)\\s+([a-zA-Z_][a-zA-Z0-9_]*)\\b",
|
|
||||||
"captures": {
|
|
||||||
"1": {
|
|
||||||
"name": "keyword.other.nub"
|
|
||||||
},
|
|
||||||
"2": {
|
|
||||||
"name": "entity.name.type.struct.nub"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"function-parameters": {
|
|
||||||
"patterns": [
|
|
||||||
{
|
|
||||||
"match": "\\.\\.\\.",
|
|
||||||
"name": "keyword.operator.variadic.nub"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"match": "([a-zA-Z_][a-zA-Z0-9_]*)\\s*:\\s*",
|
|
||||||
"captures": {
|
|
||||||
"1": {
|
|
||||||
"name": "variable.parameter.nub"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"include": "#types"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"include": "#identifiers"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"function-call": {
|
|
||||||
"patterns": [
|
|
||||||
{
|
|
||||||
"match": "([a-zA-Z_][a-zA-Z0-9_]*)\\s*(?=\\()",
|
|
||||||
"captures": {
|
|
||||||
"1": {
|
|
||||||
"name": "entity.name.function.call.nub"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"identifiers": {
|
|
||||||
"patterns": [
|
|
||||||
{
|
|
||||||
"name": "variable.other.nub",
|
|
||||||
"match": "\\b[a-zA-Z_][a-zA-Z0-9_]*\\b"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Reference in New Issue
Block a user