...
This commit is contained in:
2
run.sh
2
run.sh
@@ -1,4 +1,4 @@
|
|||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
set -e
|
set -e
|
||||||
dotnet run --project src/CLI/CLI.csproj example/main.nub example/c.nub
|
dotnet run --project src/compilation/CLI/CLI.csproj example/main.nub example/c.nub
|
||||||
./bin/out
|
./bin/out
|
||||||
34
src/.gitignore
vendored
34
src/.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/.idea/.idea.Compiler/.idea/.gitignore
generated
vendored
13
src/.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/.idea/.idea.Compiler/.idea/.name
generated
1
src/.idea/.idea.Compiler/.idea/.name
generated
@@ -1 +0,0 @@
|
|||||||
Compiler
|
|
||||||
4
src/.idea/.idea.Compiler/.idea/encodings.xml
generated
4
src/.idea/.idea.Compiler/.idea/encodings.xml
generated
@@ -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>
|
|
||||||
8
src/.idea/.idea.Compiler/.idea/indexLayout.xml
generated
8
src/.idea/.idea.Compiler/.idea/indexLayout.xml
generated
@@ -1,8 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<project version="4">
|
|
||||||
<component name="UserContentModel">
|
|
||||||
<attachedFolders />
|
|
||||||
<explicitIncludes />
|
|
||||||
<explicitExcludes />
|
|
||||||
</component>
|
|
||||||
</project>
|
|
||||||
6
src/.idea/.idea.Compiler/.idea/vcs.xml
generated
6
src/.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,25 +0,0 @@
|
|||||||
<Project Sdk="Microsoft.NET.Sdk">
|
|
||||||
|
|
||||||
<PropertyGroup>
|
|
||||||
<AssemblyName>nub</AssemblyName>
|
|
||||||
<OutputType>Exe</OutputType>
|
|
||||||
<TargetFramework>net9.0</TargetFramework>
|
|
||||||
<ImplicitUsings>enable</ImplicitUsings>
|
|
||||||
<Nullable>enable</Nullable>
|
|
||||||
<PublishAot>true</PublishAot>
|
|
||||||
</PropertyGroup>
|
|
||||||
|
|
||||||
<ItemGroup>
|
|
||||||
<ProjectReference Include="..\Generation\Generation.csproj" />
|
|
||||||
<ProjectReference Include="..\Syntax\Syntax.csproj" />
|
|
||||||
</ItemGroup>
|
|
||||||
|
|
||||||
<ItemGroup>
|
|
||||||
<EmbeddedResource Include="Runtime\entry.s" />
|
|
||||||
<EmbeddedResource Include="Runtime\nub_mem.s" />
|
|
||||||
<EmbeddedResource Include="Runtime\nub_panic.s" />
|
|
||||||
<EmbeddedResource Include="Runtime\nub_cstring.s" />
|
|
||||||
<EmbeddedResource Include="Runtime\nub_string.s" />
|
|
||||||
</ItemGroup>
|
|
||||||
|
|
||||||
</Project>
|
|
||||||
@@ -1,52 +0,0 @@
|
|||||||
using System.Diagnostics;
|
|
||||||
|
|
||||||
namespace CLI;
|
|
||||||
|
|
||||||
public static class GCC
|
|
||||||
{
|
|
||||||
public static async Task<bool> Assemble(string asmPath, string objPath)
|
|
||||||
{
|
|
||||||
using var gccProcess = new Process();
|
|
||||||
gccProcess.StartInfo = new ProcessStartInfo("gcc", ["-g", "-c", asmPath, "-o", objPath])
|
|
||||||
{
|
|
||||||
UseShellExecute = false,
|
|
||||||
RedirectStandardOutput = true,
|
|
||||||
RedirectStandardError = true,
|
|
||||||
CreateNoWindow = true
|
|
||||||
};
|
|
||||||
|
|
||||||
gccProcess.Start();
|
|
||||||
await gccProcess.WaitForExitAsync();
|
|
||||||
|
|
||||||
var gccErrors = await gccProcess.StandardError.ReadToEndAsync();
|
|
||||||
if (!string.IsNullOrWhiteSpace(gccErrors))
|
|
||||||
{
|
|
||||||
await Console.Error.WriteLineAsync("gcc error when assembling:\n" + gccErrors);
|
|
||||||
}
|
|
||||||
|
|
||||||
return gccProcess.ExitCode == 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static async Task<bool> Link(List<string> objectFiles, string outputPath)
|
|
||||||
{
|
|
||||||
using var gccProcess = new Process();
|
|
||||||
gccProcess.StartInfo = new ProcessStartInfo("gcc", ["-g", "-nostartfiles", "-o", outputPath, ..objectFiles])
|
|
||||||
{
|
|
||||||
UseShellExecute = false,
|
|
||||||
RedirectStandardOutput = true,
|
|
||||||
RedirectStandardError = true,
|
|
||||||
CreateNoWindow = true
|
|
||||||
};
|
|
||||||
|
|
||||||
gccProcess.Start();
|
|
||||||
await gccProcess.WaitForExitAsync();
|
|
||||||
|
|
||||||
var gccErrors = await gccProcess.StandardError.ReadToEndAsync();
|
|
||||||
if (!string.IsNullOrWhiteSpace(gccErrors))
|
|
||||||
{
|
|
||||||
await Console.Error.WriteLineAsync("gcc error when linking:\n" + gccErrors);
|
|
||||||
}
|
|
||||||
|
|
||||||
return gccProcess.ExitCode == 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,152 +0,0 @@
|
|||||||
using System.Diagnostics;
|
|
||||||
using System.Reflection;
|
|
||||||
using CLI;
|
|
||||||
using Generation.QBE;
|
|
||||||
using Syntax;
|
|
||||||
using Syntax.Diagnostics;
|
|
||||||
using Syntax.Parsing;
|
|
||||||
using Syntax.Tokenization;
|
|
||||||
using Syntax.Typing;
|
|
||||||
using Binder = Syntax.Typing.Binder;
|
|
||||||
|
|
||||||
const string BIN_DIR = "bin";
|
|
||||||
const string BIN_INT_DIR = "bin-int";
|
|
||||||
|
|
||||||
if (Directory.Exists(BIN_DIR))
|
|
||||||
{
|
|
||||||
Directory.Delete(BIN_DIR, true);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (Directory.Exists(BIN_INT_DIR))
|
|
||||||
{
|
|
||||||
Directory.Delete(BIN_INT_DIR, true);
|
|
||||||
}
|
|
||||||
|
|
||||||
Directory.CreateDirectory(BIN_DIR);
|
|
||||||
Directory.CreateDirectory(BIN_INT_DIR);
|
|
||||||
|
|
||||||
var diagnostics = new List<Diagnostic>();
|
|
||||||
var syntaxTrees = new List<SyntaxTree>();
|
|
||||||
|
|
||||||
foreach (var file in args)
|
|
||||||
{
|
|
||||||
if (!File.Exists(file))
|
|
||||||
{
|
|
||||||
Console.Error.WriteLine($"File '{file}' does not exist");
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
foreach (var file in args)
|
|
||||||
{
|
|
||||||
var content = File.ReadAllText(file);
|
|
||||||
var sourceText = new SourceText(file, content);
|
|
||||||
|
|
||||||
var tokenizeResult = Tokenizer.Tokenize(sourceText, out var tokenizerDiagnostics);
|
|
||||||
diagnostics.AddRange(tokenizerDiagnostics);
|
|
||||||
|
|
||||||
var syntaxTree = Parser.ParseFile(tokenizeResult, file, out var parseDiagnostics);
|
|
||||||
diagnostics.AddRange(parseDiagnostics);
|
|
||||||
|
|
||||||
if (syntaxTree != null)
|
|
||||||
{
|
|
||||||
syntaxTrees.Add(syntaxTree);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
throw new Exception();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var definitionTable = new DefinitionTable(syntaxTrees);
|
|
||||||
|
|
||||||
var boundSyntaxTrees = new List<BoundSyntaxTree>();
|
|
||||||
|
|
||||||
foreach (var syntaxTree in syntaxTrees)
|
|
||||||
{
|
|
||||||
boundSyntaxTrees.Add(Binder.Bind(syntaxTree, definitionTable));
|
|
||||||
}
|
|
||||||
|
|
||||||
var boundDefinitionTable = new BoundDefinitionTable(boundSyntaxTrees);
|
|
||||||
|
|
||||||
foreach (var diagnostic in diagnostics)
|
|
||||||
{
|
|
||||||
Console.Error.WriteLine(diagnostic.FormatANSI());
|
|
||||||
}
|
|
||||||
|
|
||||||
if (diagnostics.Any(diagnostic => diagnostic.Severity == DiagnosticSeverity.Error))
|
|
||||||
{
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
var objectFiles = new List<string>();
|
|
||||||
|
|
||||||
foreach (var boundSyntaxTree in boundSyntaxTrees)
|
|
||||||
{
|
|
||||||
var relativeFilePath = Path.GetRelativePath(Environment.CurrentDirectory, boundSyntaxTree.FilePath);
|
|
||||||
var outputPath = Path.Combine(BIN_INT_DIR, "program", relativeFilePath);
|
|
||||||
|
|
||||||
var outputDirectory = Path.GetDirectoryName(outputPath);
|
|
||||||
Debug.Assert(!string.IsNullOrWhiteSpace(outputDirectory));
|
|
||||||
Directory.CreateDirectory(outputDirectory);
|
|
||||||
|
|
||||||
var ssa = QBEGenerator.Emit(boundSyntaxTree, boundDefinitionTable);
|
|
||||||
var ssaPath = Path.ChangeExtension(outputPath, "ssa");
|
|
||||||
File.WriteAllText(ssaPath, ssa);
|
|
||||||
|
|
||||||
var asm = await QBE.Invoke(ssa);
|
|
||||||
if (asm == null)
|
|
||||||
{
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
var asmPath = Path.ChangeExtension(outputPath, "s");
|
|
||||||
await File.WriteAllTextAsync(asmPath, asm);
|
|
||||||
|
|
||||||
var objPath = Path.ChangeExtension(outputPath, "o");
|
|
||||||
var asmSuccess = await GCC.Assemble(asmPath, objPath);
|
|
||||||
if (!asmSuccess)
|
|
||||||
{
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
objectFiles.Add(objPath);
|
|
||||||
}
|
|
||||||
|
|
||||||
var assembly = Assembly.GetExecutingAssembly();
|
|
||||||
var runtimeResources = assembly
|
|
||||||
.GetManifestResourceNames()
|
|
||||||
.Where(name => name.EndsWith(".s", StringComparison.OrdinalIgnoreCase));
|
|
||||||
|
|
||||||
foreach (var resourceName in runtimeResources)
|
|
||||||
{
|
|
||||||
await using var stream = assembly.GetManifestResourceStream(resourceName);
|
|
||||||
if (stream == null)
|
|
||||||
{
|
|
||||||
Console.Error.WriteLine($"Could not load embedded resource {resourceName}");
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
using var reader = new StreamReader(stream);
|
|
||||||
var asm = await reader.ReadToEndAsync();
|
|
||||||
|
|
||||||
var outputDirectory = Path.Combine(BIN_INT_DIR, "runtime");
|
|
||||||
Directory.CreateDirectory(outputDirectory);
|
|
||||||
|
|
||||||
var fileName = resourceName.Split('.').Reverse().Skip(1).First();
|
|
||||||
|
|
||||||
var asmPath = Path.Combine(outputDirectory, fileName + ".s");
|
|
||||||
await File.WriteAllTextAsync(asmPath, asm);
|
|
||||||
|
|
||||||
var objPath = Path.Combine(outputDirectory, fileName + ".o");
|
|
||||||
var asmSuccess = await GCC.Assemble(asmPath, objPath);
|
|
||||||
if (!asmSuccess)
|
|
||||||
{
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
objectFiles.Add(objPath);
|
|
||||||
}
|
|
||||||
|
|
||||||
var linkSuccess = await GCC.Link(objectFiles, Path.Combine(BIN_DIR, "out"));
|
|
||||||
return linkSuccess ? 0 : 1;
|
|
||||||
@@ -1,37 +0,0 @@
|
|||||||
using System.Diagnostics;
|
|
||||||
|
|
||||||
namespace CLI;
|
|
||||||
|
|
||||||
public static class QBE
|
|
||||||
{
|
|
||||||
public static async Task<string?> Invoke(string ssa)
|
|
||||||
{
|
|
||||||
using var qbeProcess = new Process();
|
|
||||||
qbeProcess.StartInfo = new ProcessStartInfo
|
|
||||||
{
|
|
||||||
FileName = "qbe",
|
|
||||||
UseShellExecute = false,
|
|
||||||
RedirectStandardInput = true,
|
|
||||||
RedirectStandardOutput = true,
|
|
||||||
RedirectStandardError = true,
|
|
||||||
CreateNoWindow = true
|
|
||||||
};
|
|
||||||
|
|
||||||
qbeProcess.Start();
|
|
||||||
|
|
||||||
await qbeProcess.StandardInput.WriteAsync(ssa);
|
|
||||||
qbeProcess.StandardInput.Close();
|
|
||||||
|
|
||||||
await qbeProcess.WaitForExitAsync();
|
|
||||||
|
|
||||||
var qbeErrors = await qbeProcess.StandardError.ReadToEndAsync();
|
|
||||||
if (!string.IsNullOrWhiteSpace(qbeErrors))
|
|
||||||
{
|
|
||||||
await Console.Error.WriteLineAsync("qbe error:\n" + qbeErrors);
|
|
||||||
}
|
|
||||||
|
|
||||||
var asm = await qbeProcess.StandardOutput.ReadToEndAsync();
|
|
||||||
|
|
||||||
return qbeProcess.ExitCode == 0 ? asm : null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,12 +0,0 @@
|
|||||||
.intel_syntax noprefix
|
|
||||||
|
|
||||||
.equ SYS_EXIT, 60
|
|
||||||
|
|
||||||
.text
|
|
||||||
.globl _start
|
|
||||||
_start:
|
|
||||||
mov rdi, rsp
|
|
||||||
call main
|
|
||||||
mov rdi, rax
|
|
||||||
mov rax, SYS_EXIT
|
|
||||||
syscall
|
|
||||||
@@ -1,17 +0,0 @@
|
|||||||
.intel_syntax noprefix
|
|
||||||
|
|
||||||
.text
|
|
||||||
.globl nub_cstring_length
|
|
||||||
# func nub_cstring_length(string: cstring): u64
|
|
||||||
nub_cstring_length:
|
|
||||||
xor rax, rax
|
|
||||||
|
|
||||||
count_loop:
|
|
||||||
cmp byte ptr [rdi + rax], 0
|
|
||||||
je done
|
|
||||||
|
|
||||||
inc rax
|
|
||||||
jmp count_loop
|
|
||||||
|
|
||||||
done:
|
|
||||||
ret
|
|
||||||
@@ -1,27 +0,0 @@
|
|||||||
.intel_syntax noprefix
|
|
||||||
|
|
||||||
.text
|
|
||||||
.globl nub_memcpy
|
|
||||||
# func nub_memcpy(destination: ^u8, source: ^u8, count: u64): ^u8
|
|
||||||
nub_memcpy:
|
|
||||||
mov rcx, rdx
|
|
||||||
rep movsb
|
|
||||||
ret
|
|
||||||
|
|
||||||
.text
|
|
||||||
.globl nub_memset
|
|
||||||
# func nub_memset(destination: ^u8, value: i8, count: u64): ^u8
|
|
||||||
nub_memset:
|
|
||||||
push rdi
|
|
||||||
mov rcx, rdx
|
|
||||||
mov al, sil
|
|
||||||
test rcx, rcx
|
|
||||||
jz memset_done
|
|
||||||
memset_loop:
|
|
||||||
mov BYTE PTR [rdi], al
|
|
||||||
inc rdi
|
|
||||||
dec rcx
|
|
||||||
jnz memset_loop
|
|
||||||
memset_done:
|
|
||||||
pop rax
|
|
||||||
ret
|
|
||||||
@@ -1,47 +0,0 @@
|
|||||||
.intel_syntax noprefix
|
|
||||||
|
|
||||||
.equ NUB_PANIC_ERROR_CODE, 101
|
|
||||||
|
|
||||||
.equ SYS_WRITE, 1
|
|
||||||
.equ SYS_EXIT, 60
|
|
||||||
|
|
||||||
.equ FD_STDIN, 0
|
|
||||||
.equ FD_STDOUT, 1
|
|
||||||
.equ FD_STDERR, 2
|
|
||||||
|
|
||||||
.data
|
|
||||||
.align 8
|
|
||||||
array_oob_msg:
|
|
||||||
.ascii "Index is out of bounds of array\n"
|
|
||||||
|
|
||||||
.data
|
|
||||||
.align 8
|
|
||||||
oom_msg:
|
|
||||||
.ascii "Out of memory\n"
|
|
||||||
|
|
||||||
.text
|
|
||||||
.globl nub_panic
|
|
||||||
nub_panic:
|
|
||||||
mov rax, SYS_EXIT
|
|
||||||
mov rdi, NUB_PANIC_ERROR_CODE
|
|
||||||
syscall
|
|
||||||
|
|
||||||
.text
|
|
||||||
.globl nub_panic_array_oob
|
|
||||||
nub_panic_array_oob:
|
|
||||||
mov rax, SYS_WRITE
|
|
||||||
mov rdi, FD_STDERR
|
|
||||||
lea rsi, [rip + array_oob_msg]
|
|
||||||
mov rdx, 32
|
|
||||||
syscall
|
|
||||||
call nub_panic
|
|
||||||
|
|
||||||
.text
|
|
||||||
.globl nub_panic_oom
|
|
||||||
nub_panic_oom:
|
|
||||||
mov rax, SYS_WRITE
|
|
||||||
mov rdi, FD_STDERR
|
|
||||||
lea rsi, [rip + oom_msg]
|
|
||||||
mov rdx, 14
|
|
||||||
syscall
|
|
||||||
call nub_panic
|
|
||||||
@@ -1,32 +0,0 @@
|
|||||||
.intel_syntax noprefix
|
|
||||||
|
|
||||||
.text
|
|
||||||
.globl nub_string_length
|
|
||||||
# func nub_string_length(string: string): u64
|
|
||||||
nub_string_length:
|
|
||||||
mov rsi, [rdi] # Length of string in bytes
|
|
||||||
|
|
||||||
add rdi, 8 # Start of bytes
|
|
||||||
xor rax, rax # Character count
|
|
||||||
xor rdx, rdx # Current byte position
|
|
||||||
|
|
||||||
test rsi, rsi
|
|
||||||
jz _done
|
|
||||||
|
|
||||||
_count_loop:
|
|
||||||
cmp rdx, rsi
|
|
||||||
jge _done
|
|
||||||
|
|
||||||
mov dl, [rdi + rdx]
|
|
||||||
and dl, 0b11000000
|
|
||||||
cmp dl, 0b10000000
|
|
||||||
je _skip_byte
|
|
||||||
|
|
||||||
inc rax
|
|
||||||
|
|
||||||
_skip_byte:
|
|
||||||
inc rdx
|
|
||||||
jmp _count_loop
|
|
||||||
|
|
||||||
_done:
|
|
||||||
ret
|
|
||||||
@@ -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,77 +0,0 @@
|
|||||||
using System.Diagnostics.CodeAnalysis;
|
|
||||||
|
|
||||||
namespace Common;
|
|
||||||
|
|
||||||
public readonly struct Optional
|
|
||||||
{
|
|
||||||
public static Optional<TValue> Empty<TValue>() => new();
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Alias for creating an Optional<TValue> which allows for implicit types
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="value"></param>
|
|
||||||
/// <typeparam name="TValue"></typeparam>
|
|
||||||
/// <returns></returns>
|
|
||||||
public static Optional<TValue> OfNullable<TValue>(TValue? value)
|
|
||||||
{
|
|
||||||
return value ?? Optional<TValue>.Empty();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public readonly struct Optional<TValue>
|
|
||||||
{
|
|
||||||
public static Optional<TValue> Empty() => new();
|
|
||||||
|
|
||||||
public static Optional<TValue> OfNullable(TValue? value)
|
|
||||||
{
|
|
||||||
return value ?? Empty();
|
|
||||||
}
|
|
||||||
|
|
||||||
public Optional()
|
|
||||||
{
|
|
||||||
Value = default;
|
|
||||||
HasValue = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Optional(TValue value)
|
|
||||||
{
|
|
||||||
Value = value;
|
|
||||||
HasValue = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
public TValue? Value { get; }
|
|
||||||
|
|
||||||
[MemberNotNullWhen(true, nameof(Value))]
|
|
||||||
public bool HasValue { get; }
|
|
||||||
|
|
||||||
|
|
||||||
[MemberNotNullWhen(true, nameof(Value))]
|
|
||||||
public bool TryGetValue([NotNullWhen(true)] out TValue? value)
|
|
||||||
{
|
|
||||||
if (HasValue)
|
|
||||||
{
|
|
||||||
value = Value;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
value = default;
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
public TValue GetValue()
|
|
||||||
{
|
|
||||||
return Value ?? throw new InvalidOperationException("Value is not set");
|
|
||||||
}
|
|
||||||
|
|
||||||
public static implicit operator Optional<TValue>(TValue value) => new(value);
|
|
||||||
|
|
||||||
public TValue Or(TValue other)
|
|
||||||
{
|
|
||||||
if (HasValue)
|
|
||||||
{
|
|
||||||
return Value;
|
|
||||||
}
|
|
||||||
|
|
||||||
return other;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,49 +0,0 @@
|
|||||||
namespace Common;
|
|
||||||
|
|
||||||
public readonly struct Variant<T1, T2> where T1 : notnull where T2 : notnull
|
|
||||||
{
|
|
||||||
public Variant()
|
|
||||||
{
|
|
||||||
throw new InvalidOperationException("Variant must be initialized with a value");
|
|
||||||
}
|
|
||||||
|
|
||||||
public Variant(T1 value)
|
|
||||||
{
|
|
||||||
_value = value;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Variant(T2 value)
|
|
||||||
{
|
|
||||||
_value = value;
|
|
||||||
}
|
|
||||||
|
|
||||||
private readonly object _value;
|
|
||||||
|
|
||||||
public void Match(Action<T1> on1, Action<T2> on2)
|
|
||||||
{
|
|
||||||
switch (_value)
|
|
||||||
{
|
|
||||||
case T1 v1:
|
|
||||||
on1(v1);
|
|
||||||
break;
|
|
||||||
case T2 v2:
|
|
||||||
on2(v2);
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
throw new InvalidCastException();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public T Match<T>(Func<T1, T> on1, Func<T2, T> on2)
|
|
||||||
{
|
|
||||||
return _value switch
|
|
||||||
{
|
|
||||||
T1 v1 => on1(v1),
|
|
||||||
T2 v2 => on2(v2),
|
|
||||||
_ => throw new InvalidCastException()
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
public static implicit operator Variant<T1, T2>(T1 value) => new(value);
|
|
||||||
public static implicit operator Variant<T1, T2>(T2 value) => new(value);
|
|
||||||
}
|
|
||||||
@@ -1,34 +0,0 @@
|
|||||||
|
|
||||||
Microsoft Visual Studio Solution File, Format Version 12.00
|
|
||||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Syntax", "Syntax\Syntax.csproj", "{5047E21F-590D-4CB3-AFF3-064316485009}"
|
|
||||||
EndProject
|
|
||||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CLI", "CLI\CLI.csproj", "{A22F17ED-FA17-45AB-92BA-CD02C28B3524}"
|
|
||||||
EndProject
|
|
||||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Generation", "Generation\Generation.csproj", "{F903F1B9-69A6-4522-B483-81A4B072C8B1}"
|
|
||||||
EndProject
|
|
||||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Common", "Common\Common.csproj", "{91ECE034-32D4-48E6-A905-5F95DB95A3D4}"
|
|
||||||
EndProject
|
|
||||||
Global
|
|
||||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
|
||||||
Debug|Any CPU = Debug|Any CPU
|
|
||||||
Release|Any CPU = Release|Any CPU
|
|
||||||
EndGlobalSection
|
|
||||||
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
|
||||||
{5047E21F-590D-4CB3-AFF3-064316485009}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
|
||||||
{5047E21F-590D-4CB3-AFF3-064316485009}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
|
||||||
{5047E21F-590D-4CB3-AFF3-064316485009}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
|
||||||
{5047E21F-590D-4CB3-AFF3-064316485009}.Release|Any CPU.Build.0 = Release|Any CPU
|
|
||||||
{A22F17ED-FA17-45AB-92BA-CD02C28B3524}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
|
||||||
{A22F17ED-FA17-45AB-92BA-CD02C28B3524}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
|
||||||
{A22F17ED-FA17-45AB-92BA-CD02C28B3524}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
|
||||||
{A22F17ED-FA17-45AB-92BA-CD02C28B3524}.Release|Any CPU.Build.0 = Release|Any CPU
|
|
||||||
{F903F1B9-69A6-4522-B483-81A4B072C8B1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
|
||||||
{F903F1B9-69A6-4522-B483-81A4B072C8B1}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
|
||||||
{F903F1B9-69A6-4522-B483-81A4B072C8B1}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
|
||||||
{F903F1B9-69A6-4522-B483-81A4B072C8B1}.Release|Any CPU.Build.0 = Release|Any CPU
|
|
||||||
{91ECE034-32D4-48E6-A905-5F95DB95A3D4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
|
||||||
{91ECE034-32D4-48E6-A905-5F95DB95A3D4}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
|
||||||
{91ECE034-32D4-48E6-A905-5F95DB95A3D4}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
|
||||||
{91ECE034-32D4-48E6-A905-5F95DB95A3D4}.Release|Any CPU.Build.0 = Release|Any CPU
|
|
||||||
EndGlobalSection
|
|
||||||
EndGlobal
|
|
||||||
@@ -1,15 +0,0 @@
|
|||||||
<Project Sdk="Microsoft.NET.Sdk">
|
|
||||||
|
|
||||||
<PropertyGroup>
|
|
||||||
<TargetFramework>net9.0</TargetFramework>
|
|
||||||
<ImplicitUsings>enable</ImplicitUsings>
|
|
||||||
<Nullable>enable</Nullable>
|
|
||||||
<IsAotCompatible>true</IsAotCompatible>
|
|
||||||
</PropertyGroup>
|
|
||||||
|
|
||||||
<ItemGroup>
|
|
||||||
<ProjectReference Include="..\Common\Common.csproj" />
|
|
||||||
<ProjectReference Include="..\Syntax\Syntax.csproj" />
|
|
||||||
</ItemGroup>
|
|
||||||
|
|
||||||
</Project>
|
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -1,85 +0,0 @@
|
|||||||
using Common;
|
|
||||||
using Syntax.Parsing;
|
|
||||||
using Syntax.Parsing.Node;
|
|
||||||
using Syntax.Typing;
|
|
||||||
using Syntax.Typing.BoundNode;
|
|
||||||
|
|
||||||
namespace Syntax;
|
|
||||||
|
|
||||||
public class DefinitionTable
|
|
||||||
{
|
|
||||||
private readonly IEnumerable<SyntaxTree> _syntaxTrees;
|
|
||||||
|
|
||||||
public DefinitionTable(IEnumerable<SyntaxTree> syntaxTrees)
|
|
||||||
{
|
|
||||||
_syntaxTrees = syntaxTrees;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Optional<FuncDefinition> LookupFunc(string @namespace, string name)
|
|
||||||
{
|
|
||||||
var definition = _syntaxTrees
|
|
||||||
.Where(c => c.Namespace == @namespace)
|
|
||||||
.SelectMany(c => c.Definitions)
|
|
||||||
.OfType<FuncDefinition>()
|
|
||||||
.SingleOrDefault(f => f.Name == name);
|
|
||||||
|
|
||||||
return Optional.OfNullable(definition);
|
|
||||||
}
|
|
||||||
|
|
||||||
public Optional<StructDefinitionNode> LookupStruct(string @namespace, string name)
|
|
||||||
{
|
|
||||||
var definition = _syntaxTrees
|
|
||||||
.Where(c => c.Namespace == @namespace)
|
|
||||||
.SelectMany(c => c.Definitions)
|
|
||||||
.OfType<StructDefinitionNode>()
|
|
||||||
.SingleOrDefault(f => f.Name == name);
|
|
||||||
|
|
||||||
return Optional.OfNullable(definition);
|
|
||||||
}
|
|
||||||
|
|
||||||
public IEnumerable<StructDefinitionNode> GetStructs()
|
|
||||||
{
|
|
||||||
return _syntaxTrees
|
|
||||||
.SelectMany(c => c.Definitions)
|
|
||||||
.OfType<StructDefinitionNode>();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public class BoundDefinitionTable
|
|
||||||
{
|
|
||||||
private readonly IEnumerable<BoundSyntaxTree> _syntaxTrees;
|
|
||||||
|
|
||||||
public BoundDefinitionTable(IEnumerable<BoundSyntaxTree> syntaxTrees)
|
|
||||||
{
|
|
||||||
_syntaxTrees = syntaxTrees;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Optional<BoundFuncDefinition> LookupFunc(string @namespace, string name)
|
|
||||||
{
|
|
||||||
var definition = _syntaxTrees
|
|
||||||
.Where(c => c.Namespace == @namespace)
|
|
||||||
.SelectMany(c => c.Definitions)
|
|
||||||
.OfType<BoundFuncDefinition>()
|
|
||||||
.SingleOrDefault(f => f.Name == name);
|
|
||||||
|
|
||||||
return Optional.OfNullable(definition);
|
|
||||||
}
|
|
||||||
|
|
||||||
public Optional<BoundStructDefinitionNode> LookupStruct(string @namespace, string name)
|
|
||||||
{
|
|
||||||
var definition = _syntaxTrees
|
|
||||||
.Where(c => c.Namespace == @namespace)
|
|
||||||
.SelectMany(c => c.Definitions)
|
|
||||||
.OfType<BoundStructDefinitionNode>()
|
|
||||||
.SingleOrDefault(f => f.Name == name);
|
|
||||||
|
|
||||||
return Optional.OfNullable(definition);
|
|
||||||
}
|
|
||||||
|
|
||||||
public IEnumerable<BoundStructDefinitionNode> GetStructs()
|
|
||||||
{
|
|
||||||
return _syntaxTrees
|
|
||||||
.SelectMany(c => c.Definitions)
|
|
||||||
.OfType<BoundStructDefinitionNode>();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,177 +0,0 @@
|
|||||||
using System.Text;
|
|
||||||
using Syntax.Tokenization;
|
|
||||||
|
|
||||||
namespace Syntax.Diagnostics;
|
|
||||||
|
|
||||||
public static class ConsoleColors
|
|
||||||
{
|
|
||||||
public const string Reset = "\e[0m";
|
|
||||||
public const string Bold = "\e[1m";
|
|
||||||
public const string Faint = "\e[2m";
|
|
||||||
public const string Italic = "\e[3m";
|
|
||||||
public const string Underline = "\e[4m";
|
|
||||||
public const string SlowBlink = "\e[5m";
|
|
||||||
public const string RapidBlink = "\e[6m";
|
|
||||||
public const string SwapBgAndFg = "\e[7m";
|
|
||||||
public const string Conceal = "\e[8m";
|
|
||||||
public const string CrossedOut = "\e[9m";
|
|
||||||
|
|
||||||
public const string DefaultFont = "\e[10m";
|
|
||||||
public const string AltFont1 = "\e[11m";
|
|
||||||
public const string AltFont2 = "\e[12m";
|
|
||||||
public const string AltFont3 = "\e[13m";
|
|
||||||
public const string AltFont4 = "\e[14m";
|
|
||||||
public const string AltFont5 = "\e[15m";
|
|
||||||
public const string AltFont6 = "\e[16m";
|
|
||||||
public const string AltFont7 = "\e[17m";
|
|
||||||
public const string AltFont8 = "\e[18m";
|
|
||||||
public const string AltFont9 = "\e[19m";
|
|
||||||
|
|
||||||
public const string Black = "\e[30m";
|
|
||||||
public const string Red = "\e[31m";
|
|
||||||
public const string Green = "\e[32m";
|
|
||||||
public const string Yellow = "\e[33m";
|
|
||||||
public const string Blue = "\e[34m";
|
|
||||||
public const string Magenta = "\e[35m";
|
|
||||||
public const string Cyan = "\e[36m";
|
|
||||||
public const string White = "\e[37m";
|
|
||||||
|
|
||||||
public const string BrightBlack = "\e[90m";
|
|
||||||
public const string BrightRed = "\e[91m";
|
|
||||||
public const string BrightGreen = "\e[92m";
|
|
||||||
public const string BrightYellow = "\e[93m";
|
|
||||||
public const string BrightBlue = "\e[94m";
|
|
||||||
public const string BrightMagenta = "\e[95m";
|
|
||||||
public const string BrightCyan = "\e[96m";
|
|
||||||
public const string BrightWhite = "\e[97m";
|
|
||||||
|
|
||||||
private static bool IsColorSupported()
|
|
||||||
{
|
|
||||||
var term = Environment.GetEnvironmentVariable("TERM");
|
|
||||||
var colorTerm = Environment.GetEnvironmentVariable("COLORTERM");
|
|
||||||
return !string.IsNullOrEmpty(term) || !string.IsNullOrEmpty(colorTerm) || !Console.IsOutputRedirected;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static string Colorize(string text, string color)
|
|
||||||
{
|
|
||||||
return IsColorSupported() ? $"{color}{text}{Reset}" : text;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static string GetTokenColor(Token token)
|
|
||||||
{
|
|
||||||
switch (token)
|
|
||||||
{
|
|
||||||
case DocumentationToken:
|
|
||||||
return Faint;
|
|
||||||
case IdentifierToken:
|
|
||||||
return White;
|
|
||||||
case LiteralToken literal:
|
|
||||||
return literal.Kind switch
|
|
||||||
{
|
|
||||||
LiteralKind.String => Green,
|
|
||||||
LiteralKind.Integer or LiteralKind.Float => BrightBlue,
|
|
||||||
LiteralKind.Bool => Blue,
|
|
||||||
_ => White
|
|
||||||
};
|
|
||||||
case ModifierToken:
|
|
||||||
return White;
|
|
||||||
case SymbolToken symbol:
|
|
||||||
switch (symbol.Symbol)
|
|
||||||
{
|
|
||||||
case Symbol.If:
|
|
||||||
case Symbol.Else:
|
|
||||||
case Symbol.While:
|
|
||||||
case Symbol.Break:
|
|
||||||
case Symbol.Continue:
|
|
||||||
case Symbol.Return:
|
|
||||||
return Magenta;
|
|
||||||
case Symbol.Func:
|
|
||||||
case Symbol.Struct:
|
|
||||||
case Symbol.Namespace:
|
|
||||||
case Symbol.Let:
|
|
||||||
case Symbol.Alloc:
|
|
||||||
return Blue;
|
|
||||||
case Symbol.Assign:
|
|
||||||
case Symbol.Bang:
|
|
||||||
case Symbol.Equal:
|
|
||||||
case Symbol.NotEqual:
|
|
||||||
case Symbol.LessThan:
|
|
||||||
case Symbol.LessThanOrEqual:
|
|
||||||
case Symbol.GreaterThan:
|
|
||||||
case Symbol.GreaterThanOrEqual:
|
|
||||||
case Symbol.Plus:
|
|
||||||
case Symbol.Minus:
|
|
||||||
case Symbol.Star:
|
|
||||||
case Symbol.ForwardSlash:
|
|
||||||
case Symbol.Caret:
|
|
||||||
case Symbol.Ampersand:
|
|
||||||
return White;
|
|
||||||
case Symbol.Semicolon:
|
|
||||||
case Symbol.Colon:
|
|
||||||
case Symbol.Comma:
|
|
||||||
case Symbol.Period:
|
|
||||||
case Symbol.DoubleColon:
|
|
||||||
return Faint;
|
|
||||||
case Symbol.OpenParen:
|
|
||||||
case Symbol.CloseParen:
|
|
||||||
case Symbol.OpenBrace:
|
|
||||||
case Symbol.CloseBrace:
|
|
||||||
case Symbol.OpenBracket:
|
|
||||||
case Symbol.CloseBracket:
|
|
||||||
return Yellow;
|
|
||||||
default:
|
|
||||||
return White;
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
return White;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static string ColorizeSource(string source)
|
|
||||||
{
|
|
||||||
var sourceText = new SourceText(string.Empty, source);
|
|
||||||
var tokens = Tokenizer.Tokenize(sourceText, out _);
|
|
||||||
var result = new StringBuilder();
|
|
||||||
var lastCharIndex = 0;
|
|
||||||
|
|
||||||
foreach (var token in tokens)
|
|
||||||
{
|
|
||||||
var tokenStartIndex = GetCharacterIndex(sourceText, token.Span.Start);
|
|
||||||
var tokenEndIndex = GetCharacterIndex(sourceText, token.Span.End);
|
|
||||||
|
|
||||||
if (tokenStartIndex > lastCharIndex)
|
|
||||||
{
|
|
||||||
var between = sourceText.Content.Substring(lastCharIndex, tokenStartIndex - lastCharIndex);
|
|
||||||
result.Append(between);
|
|
||||||
}
|
|
||||||
|
|
||||||
var tokenText = sourceText.Content.Substring(tokenStartIndex, tokenEndIndex - tokenStartIndex);
|
|
||||||
|
|
||||||
result.Append(Colorize(tokenText, GetTokenColor(token)));
|
|
||||||
lastCharIndex = tokenEndIndex;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (lastCharIndex < sourceText.Content.Length)
|
|
||||||
{
|
|
||||||
var remaining = sourceText.Content[lastCharIndex..];
|
|
||||||
result.Append(Colorize(remaining, Faint));
|
|
||||||
}
|
|
||||||
|
|
||||||
return result.ToString();
|
|
||||||
}
|
|
||||||
|
|
||||||
private static int GetCharacterIndex(SourceText sourceText, SourceLocation location)
|
|
||||||
{
|
|
||||||
var lines = sourceText.Content.Split('\n');
|
|
||||||
var index = 0;
|
|
||||||
|
|
||||||
for (var i = 0; i < location.Line - 1 && i < lines.Length; i++)
|
|
||||||
{
|
|
||||||
index += lines[i].Length + 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
index += location.Column - 1;
|
|
||||||
|
|
||||||
return Math.Min(index, sourceText.Content.Length);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,216 +0,0 @@
|
|||||||
using System.Text;
|
|
||||||
using Syntax.Parsing;
|
|
||||||
using Syntax.Parsing.Node;
|
|
||||||
using Syntax.Tokenization;
|
|
||||||
|
|
||||||
namespace Syntax.Diagnostics;
|
|
||||||
|
|
||||||
public class Diagnostic
|
|
||||||
{
|
|
||||||
public class DiagnosticBuilder
|
|
||||||
{
|
|
||||||
private readonly DiagnosticSeverity _severity;
|
|
||||||
private readonly string _message;
|
|
||||||
private string? _help;
|
|
||||||
private SourceSpan? _sourceSpan;
|
|
||||||
|
|
||||||
public DiagnosticBuilder(DiagnosticSeverity severity, string message)
|
|
||||||
{
|
|
||||||
_severity = severity;
|
|
||||||
_message = message;
|
|
||||||
}
|
|
||||||
|
|
||||||
public DiagnosticBuilder At(Token token)
|
|
||||||
{
|
|
||||||
_sourceSpan = token.Span;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public DiagnosticBuilder At(Node node)
|
|
||||||
{
|
|
||||||
_sourceSpan = SourceSpan.Merge(node.Tokens.Select(t => t.Span));
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public DiagnosticBuilder At(SourceSpan span)
|
|
||||||
{
|
|
||||||
_sourceSpan = span;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public DiagnosticBuilder WithHelp(string help)
|
|
||||||
{
|
|
||||||
_help = help;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Diagnostic Build() => new(_severity, _message, _sourceSpan, _help);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static DiagnosticBuilder Error(string message) => new(DiagnosticSeverity.Error, message);
|
|
||||||
public static DiagnosticBuilder Warning(string message) => new(DiagnosticSeverity.Warning, message);
|
|
||||||
public static DiagnosticBuilder Info(string message) => new(DiagnosticSeverity.Info, message);
|
|
||||||
|
|
||||||
public DiagnosticSeverity Severity { get; }
|
|
||||||
public string Message { get; }
|
|
||||||
public SourceSpan? Span { get; }
|
|
||||||
public string? Help { get; }
|
|
||||||
|
|
||||||
private Diagnostic(DiagnosticSeverity severity, string message, SourceSpan? span, string? help)
|
|
||||||
{
|
|
||||||
Severity = severity;
|
|
||||||
Message = message;
|
|
||||||
Span = span;
|
|
||||||
Help = help;
|
|
||||||
}
|
|
||||||
|
|
||||||
public string FormatANSI()
|
|
||||||
{
|
|
||||||
var sb = new StringBuilder();
|
|
||||||
|
|
||||||
var severityText = GetSeverityText(Severity);
|
|
||||||
sb.Append(severityText);
|
|
||||||
|
|
||||||
if (Span.HasValue)
|
|
||||||
{
|
|
||||||
var locationText = $" at {Span.Value.Text.Path}:{Span}";
|
|
||||||
sb.Append(ConsoleColors.Colorize(locationText, ConsoleColors.Faint));
|
|
||||||
}
|
|
||||||
|
|
||||||
sb.Append(": ");
|
|
||||||
sb.Append(ConsoleColors.Colorize(Message, ConsoleColors.BrightWhite));
|
|
||||||
|
|
||||||
if (Span.HasValue)
|
|
||||||
{
|
|
||||||
sb.AppendLine();
|
|
||||||
AppendSourceContext(sb, Span.Value, Severity);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!string.IsNullOrEmpty(Help))
|
|
||||||
{
|
|
||||||
sb.AppendLine();
|
|
||||||
sb.Append(ConsoleColors.Colorize($"help: {Help}", ConsoleColors.Cyan));
|
|
||||||
}
|
|
||||||
|
|
||||||
return sb.ToString();
|
|
||||||
}
|
|
||||||
|
|
||||||
private static string GetSeverityText(DiagnosticSeverity severity)
|
|
||||||
{
|
|
||||||
return severity switch
|
|
||||||
{
|
|
||||||
DiagnosticSeverity.Error => ConsoleColors.Colorize("error", ConsoleColors.Bold + ConsoleColors.Red),
|
|
||||||
DiagnosticSeverity.Warning => ConsoleColors.Colorize("warning", ConsoleColors.Bold + ConsoleColors.Yellow),
|
|
||||||
DiagnosticSeverity.Info => ConsoleColors.Colorize("info", ConsoleColors.Bold + ConsoleColors.Blue),
|
|
||||||
_ => throw new ArgumentOutOfRangeException(nameof(severity), severity, "Unknown diagnostic severity")
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void AppendSourceContext(StringBuilder sb, SourceSpan span, DiagnosticSeverity severity)
|
|
||||||
{
|
|
||||||
var lines = span.Text.Content.Split('\n');
|
|
||||||
var startLine = span.Start.Line;
|
|
||||||
var endLine = span.End.Line;
|
|
||||||
|
|
||||||
const int contextLines = 3;
|
|
||||||
|
|
||||||
var lineNumWidth = Math.Min(endLine + contextLines, lines.Length).ToString().Length;
|
|
||||||
|
|
||||||
var contextStart = Math.Max(1, startLine - contextLines);
|
|
||||||
var contextEnd = Math.Min(lines.Length, endLine + contextLines);
|
|
||||||
|
|
||||||
var contextWidth = 0;
|
|
||||||
for (var i = contextStart; i <= contextEnd; i++)
|
|
||||||
{
|
|
||||||
if (lines[i - 1].Length > contextWidth)
|
|
||||||
{
|
|
||||||
contextWidth = lines[i - 1].Length;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
sb.AppendLine(ConsoleColors.Colorize('╭' + new string('─', lineNumWidth + 2) + '┬' + new string('─', contextWidth + 2) + '╮', ConsoleColors.Faint));
|
|
||||||
|
|
||||||
for (var lineNum = contextStart; lineNum < startLine; lineNum++)
|
|
||||||
{
|
|
||||||
AppendContextLine(sb, lineNum, lines[lineNum - 1], lineNumWidth, contextWidth);
|
|
||||||
}
|
|
||||||
|
|
||||||
for (var lineNum = startLine; lineNum <= endLine; lineNum++)
|
|
||||||
{
|
|
||||||
AppendContextLine(sb, lineNum, lines[lineNum - 1], lineNumWidth, contextWidth);
|
|
||||||
AppendErrorIndicators(sb, span, lineNum, lines[lineNum - 1], lineNumWidth, contextWidth, severity);
|
|
||||||
}
|
|
||||||
|
|
||||||
for (var lineNum = endLine + 1; lineNum <= contextEnd; lineNum++)
|
|
||||||
{
|
|
||||||
AppendContextLine(sb, lineNum, lines[lineNum - 1], lineNumWidth, contextWidth);
|
|
||||||
}
|
|
||||||
|
|
||||||
sb.Append(ConsoleColors.Colorize('╰' + new string('─', lineNumWidth + 2) + '┴' + new string('─', contextWidth + 2) + '╯', ConsoleColors.Faint));
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void AppendContextLine(StringBuilder sb, int lineNum, string line, int lineNumWidth, int contextWidth)
|
|
||||||
{
|
|
||||||
sb.Append(ConsoleColors.Colorize('│' + " ", ConsoleColors.Faint));
|
|
||||||
var lineNumStr = lineNum.ToString().PadLeft(lineNumWidth);
|
|
||||||
sb.Append(ConsoleColors.Colorize(lineNumStr, ConsoleColors.Faint));
|
|
||||||
sb.Append(ConsoleColors.Colorize(" │ ", ConsoleColors.Faint));
|
|
||||||
sb.Append(ConsoleColors.ColorizeSource(line.PadRight(contextWidth)));
|
|
||||||
sb.Append(ConsoleColors.Colorize(" " + '│', ConsoleColors.Faint));
|
|
||||||
sb.AppendLine();
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void AppendErrorIndicators(StringBuilder sb, SourceSpan span, int lineNum, string line, int lineNumWidth, int contextWidth, DiagnosticSeverity severity)
|
|
||||||
{
|
|
||||||
var color = severity switch
|
|
||||||
{
|
|
||||||
DiagnosticSeverity.Info => ConsoleColors.Blue,
|
|
||||||
DiagnosticSeverity.Warning => ConsoleColors.Yellow,
|
|
||||||
DiagnosticSeverity.Error => ConsoleColors.Red,
|
|
||||||
_ => throw new ArgumentOutOfRangeException(nameof(severity), severity, null)
|
|
||||||
};
|
|
||||||
|
|
||||||
sb.Append(ConsoleColors.Colorize('│' + " ", ConsoleColors.Faint));
|
|
||||||
sb.Append(new string(' ', lineNumWidth));
|
|
||||||
sb.Append(ConsoleColors.Colorize(" │ ", ConsoleColors.Faint));
|
|
||||||
var indicators = GetIndicatorsForLine(span, lineNum, line);
|
|
||||||
sb.Append(ConsoleColors.Colorize(indicators.PadRight(contextWidth), color));
|
|
||||||
sb.Append(ConsoleColors.Colorize(" " + '│', ConsoleColors.Faint));
|
|
||||||
sb.AppendLine();
|
|
||||||
}
|
|
||||||
|
|
||||||
private static string GetIndicatorsForLine(SourceSpan span, int lineNum, string line)
|
|
||||||
{
|
|
||||||
const char indicator = '^';
|
|
||||||
|
|
||||||
if (lineNum == span.Start.Line && lineNum == span.End.Line)
|
|
||||||
{
|
|
||||||
var startCol = Math.Max(0, span.Start.Column - 1);
|
|
||||||
var endCol = Math.Min(line.Length, span.End.Column - 1);
|
|
||||||
var length = Math.Max(1, endCol - startCol);
|
|
||||||
|
|
||||||
return new string(' ', startCol) + new string(indicator, length);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (lineNum == span.Start.Line)
|
|
||||||
{
|
|
||||||
var startCol = Math.Max(0, span.Start.Column - 1);
|
|
||||||
return new string(' ', startCol) + new string(indicator, Math.Max(0, line.Length - startCol));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (lineNum == span.End.Line)
|
|
||||||
{
|
|
||||||
var endCol = Math.Min(line.Length, span.End.Column - 1);
|
|
||||||
return new string(indicator, Math.Max(0, endCol));
|
|
||||||
}
|
|
||||||
|
|
||||||
return new string(indicator, line.Length);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public enum DiagnosticSeverity
|
|
||||||
{
|
|
||||||
Info,
|
|
||||||
Warning,
|
|
||||||
Error
|
|
||||||
}
|
|
||||||
@@ -1,15 +0,0 @@
|
|||||||
using Common;
|
|
||||||
using Syntax.Tokenization;
|
|
||||||
using Syntax.Typing;
|
|
||||||
|
|
||||||
namespace Syntax.Parsing.Node;
|
|
||||||
|
|
||||||
public abstract record DefinitionNode(IEnumerable<Token> Tokens, Optional<string> Documentation, string Namespace) : Node(Tokens);
|
|
||||||
|
|
||||||
public record FuncParameter(string Name, NubType Type);
|
|
||||||
public abstract record FuncDefinition(IEnumerable<Token> Tokens, Optional<string> Documentation, string Namespace, string Name, List<FuncParameter> Parameters, NubType ReturnType) : DefinitionNode(Tokens, Documentation, Namespace);
|
|
||||||
public record LocalFuncDefinitionNode(IEnumerable<Token> Tokens, Optional<string> Documentation, string Namespace, string Name, List<FuncParameter> Parameters, BlockNode Body, NubType ReturnType, bool Exported) : FuncDefinition(Tokens, Documentation, Namespace, Name, Parameters, ReturnType);
|
|
||||||
public record ExternFuncDefinitionNode(IEnumerable<Token> Tokens, Optional<string> Documentation, string Namespace, string Name, string CallName, List<FuncParameter> Parameters, NubType ReturnType) : FuncDefinition(Tokens, Documentation, Namespace, Name, Parameters, ReturnType);
|
|
||||||
|
|
||||||
public record StructField(string Name, NubType Type, Optional<ExpressionNode> Value);
|
|
||||||
public record StructDefinitionNode(IEnumerable<Token> Tokens, Optional<string> Documentation, string Namespace, string Name, List<StructField> Fields) : DefinitionNode(Tokens, Documentation, Namespace);
|
|
||||||
@@ -1,44 +0,0 @@
|
|||||||
using Common;
|
|
||||||
using Syntax.Tokenization;
|
|
||||||
using Syntax.Typing;
|
|
||||||
|
|
||||||
namespace Syntax.Parsing.Node;
|
|
||||||
|
|
||||||
public abstract record ExpressionNode(IEnumerable<Token> Tokens) : Node(Tokens);
|
|
||||||
public abstract record LValueNode(IEnumerable<Token> Tokens) : ExpressionNode(Tokens);
|
|
||||||
|
|
||||||
public record BinaryExpressionNode(IEnumerable<Token> Tokens, ExpressionNode Left, BinaryExpressionOperator Operator, ExpressionNode Right) : ExpressionNode(Tokens);
|
|
||||||
|
|
||||||
public enum BinaryExpressionOperator
|
|
||||||
{
|
|
||||||
Equal,
|
|
||||||
NotEqual,
|
|
||||||
GreaterThan,
|
|
||||||
GreaterThanOrEqual,
|
|
||||||
LessThan,
|
|
||||||
LessThanOrEqual,
|
|
||||||
Plus,
|
|
||||||
Minus,
|
|
||||||
Multiply,
|
|
||||||
Divide
|
|
||||||
}
|
|
||||||
|
|
||||||
public record UnaryExpressionNode(IEnumerable<Token> Tokens, UnaryExpressionOperator Operator, ExpressionNode Operand) : ExpressionNode(Tokens);
|
|
||||||
|
|
||||||
public enum UnaryExpressionOperator
|
|
||||||
{
|
|
||||||
Negate,
|
|
||||||
Invert
|
|
||||||
}
|
|
||||||
|
|
||||||
public record FuncCallNode(IEnumerable<Token> Tokens, ExpressionNode Expression, List<ExpressionNode> Parameters) : ExpressionNode(Tokens);
|
|
||||||
public record IdentifierNode(IEnumerable<Token> Tokens, Optional<string> Namespace, string Name) : LValueNode(Tokens);
|
|
||||||
public record ArrayInitializerNode(IEnumerable<Token> Tokens, ExpressionNode Capacity, NubType ElementType) : ExpressionNode(Tokens);
|
|
||||||
public record ArrayIndexAccessNode(IEnumerable<Token> Tokens, ExpressionNode Array, ExpressionNode Index) : LValueNode(Tokens);
|
|
||||||
public record AnonymousFuncNode(IEnumerable<Token> Tokens, List<FuncParameter> Parameters, BlockNode Body, NubType ReturnType) : ExpressionNode(Tokens);
|
|
||||||
public record AddressOfNode(IEnumerable<Token> Tokens, LValueNode Expression) : ExpressionNode(Tokens);
|
|
||||||
public record FixedArrayInitializerNode(IEnumerable<Token> Tokens, NubType ElementType, int Capacity) : ExpressionNode(Tokens);
|
|
||||||
public record LiteralNode(IEnumerable<Token> Tokens, string Literal, LiteralKind Kind) : ExpressionNode(Tokens);
|
|
||||||
public record MemberAccessNode(IEnumerable<Token> Tokens, ExpressionNode Expression, string Member) : ExpressionNode(Tokens);
|
|
||||||
public record StructInitializerNode(IEnumerable<Token> Tokens, NubStructType StructType, Dictionary<string, ExpressionNode> Initializers) : ExpressionNode(Tokens);
|
|
||||||
public record DereferenceNode(IEnumerable<Token> Tokens, ExpressionNode Expression) : LValueNode(Tokens);
|
|
||||||
@@ -1,6 +0,0 @@
|
|||||||
using Syntax.Tokenization;
|
|
||||||
|
|
||||||
namespace Syntax.Parsing.Node;
|
|
||||||
|
|
||||||
public abstract record Node(IEnumerable<Token> Tokens);
|
|
||||||
public record BlockNode(IEnumerable<Token> Tokens, List<StatementNode> Statements) : Node(Tokens);
|
|
||||||
@@ -1,18 +0,0 @@
|
|||||||
using Common;
|
|
||||||
using Syntax.Tokenization;
|
|
||||||
using Syntax.Typing;
|
|
||||||
|
|
||||||
namespace Syntax.Parsing.Node;
|
|
||||||
|
|
||||||
public record StatementNode(IEnumerable<Token> Tokens) : Node(Tokens);
|
|
||||||
public record StatementExpressionNode(IEnumerable<Token> Tokens, ExpressionNode Expression) : StatementNode(Tokens);
|
|
||||||
public record ReturnNode(IEnumerable<Token> Tokens, Optional<ExpressionNode> Value) : StatementNode(Tokens);
|
|
||||||
public record MemberAssignmentNode(IEnumerable<Token> Tokens, MemberAccessNode MemberAccess, ExpressionNode Value) : StatementNode(Tokens);
|
|
||||||
public record IfNode(IEnumerable<Token> Tokens, ExpressionNode Condition, BlockNode Body, Optional<Variant<IfNode, BlockNode>> Else) : StatementNode(Tokens);
|
|
||||||
public record DereferenceAssignmentNode(IEnumerable<Token> Tokens, DereferenceNode Dereference, ExpressionNode Value) : StatementNode(Tokens);
|
|
||||||
public record VariableAssignmentNode(IEnumerable<Token> Tokens, IdentifierNode Identifier, ExpressionNode Value) : StatementNode(Tokens);
|
|
||||||
public record VariableDeclarationNode(IEnumerable<Token> Tokens, string Name, NubType Type) : StatementNode(Tokens);
|
|
||||||
public record ContinueNode(IEnumerable<Token> Tokens) : StatementNode(Tokens);
|
|
||||||
public record BreakNode(IEnumerable<Token> Tokens) : StatementNode(Tokens);
|
|
||||||
public record ArrayIndexAssignmentNode(IEnumerable<Token> Tokens, ArrayIndexAccessNode ArrayIndexAccess, ExpressionNode Value) : StatementNode(Tokens);
|
|
||||||
public record WhileNode(IEnumerable<Token> Tokens, ExpressionNode Condition, BlockNode Body) : StatementNode(Tokens);
|
|
||||||
@@ -1,939 +0,0 @@
|
|||||||
using System.Diagnostics;
|
|
||||||
using System.Diagnostics.CodeAnalysis;
|
|
||||||
using Common;
|
|
||||||
using Syntax.Diagnostics;
|
|
||||||
using Syntax.Parsing.Node;
|
|
||||||
using Syntax.Tokenization;
|
|
||||||
using Syntax.Typing;
|
|
||||||
|
|
||||||
namespace Syntax.Parsing;
|
|
||||||
|
|
||||||
public static class Parser
|
|
||||||
{
|
|
||||||
private static string _namespace = null!;
|
|
||||||
private static List<Diagnostic> _diagnostics = [];
|
|
||||||
private static IEnumerable<Token> _tokens = [];
|
|
||||||
private static int _index;
|
|
||||||
|
|
||||||
public static SyntaxTree? ParseFile(IEnumerable<Token> tokens, string filePath, out IEnumerable<Diagnostic> diagnostics)
|
|
||||||
{
|
|
||||||
_tokens = tokens;
|
|
||||||
_namespace = null!;
|
|
||||||
_diagnostics = [];
|
|
||||||
_index = 0;
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
ExpectSymbol(Symbol.Namespace);
|
|
||||||
var @namespace = ExpectIdentifier();
|
|
||||||
_namespace = @namespace.Value;
|
|
||||||
}
|
|
||||||
catch (ParseException ex)
|
|
||||||
{
|
|
||||||
_diagnostics.Add(ex.Diagnostic);
|
|
||||||
diagnostics = _diagnostics;
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
List<DefinitionNode> definitions = [];
|
|
||||||
|
|
||||||
while (Peek().HasValue)
|
|
||||||
{
|
|
||||||
var definition = ParseDefinition();
|
|
||||||
definitions.Add(definition);
|
|
||||||
}
|
|
||||||
|
|
||||||
diagnostics = _diagnostics;
|
|
||||||
return new SyntaxTree(filePath, _namespace, definitions);
|
|
||||||
}
|
|
||||||
catch (ParseException ex)
|
|
||||||
{
|
|
||||||
_diagnostics.Add(ex.Diagnostic);
|
|
||||||
RecoverToNextDefinition();
|
|
||||||
}
|
|
||||||
|
|
||||||
diagnostics = _diagnostics;
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static DefinitionNode ParseDefinition()
|
|
||||||
{
|
|
||||||
var startIndex = _index;
|
|
||||||
List<ModifierToken> modifiers = [];
|
|
||||||
|
|
||||||
List<string> documentationParts = [];
|
|
||||||
while (_index < _tokens.Count() && _tokens.ElementAt(_index) is DocumentationToken commentToken)
|
|
||||||
{
|
|
||||||
documentationParts.Add(commentToken.Documentation);
|
|
||||||
_index++;
|
|
||||||
}
|
|
||||||
|
|
||||||
var documentation = documentationParts.Count == 0 ? null : string.Join('\n', documentationParts);
|
|
||||||
|
|
||||||
while (TryExpectModifier(out var modifier))
|
|
||||||
{
|
|
||||||
modifiers.Add(modifier);
|
|
||||||
}
|
|
||||||
|
|
||||||
var keyword = ExpectSymbol();
|
|
||||||
return keyword.Symbol switch
|
|
||||||
{
|
|
||||||
Symbol.Func => ParseFuncDefinition(startIndex, modifiers, Optional.OfNullable(documentation)),
|
|
||||||
Symbol.Struct => ParseStruct(startIndex, modifiers, Optional.OfNullable(documentation)),
|
|
||||||
_ => throw new ParseException(Diagnostic
|
|
||||||
.Error($"Expected 'func' or 'struct', but found '{keyword.Symbol}'")
|
|
||||||
.WithHelp("Valid definition keywords are 'func' and 'struct'")
|
|
||||||
.At(keyword)
|
|
||||||
.Build())
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
private static DefinitionNode ParseFuncDefinition(int startIndex, List<ModifierToken> modifiers, Optional<string> documentation)
|
|
||||||
{
|
|
||||||
var name = ExpectIdentifier();
|
|
||||||
List<FuncParameter> parameters = [];
|
|
||||||
|
|
||||||
ExpectSymbol(Symbol.OpenParen);
|
|
||||||
|
|
||||||
while (!TryExpectSymbol(Symbol.CloseParen))
|
|
||||||
{
|
|
||||||
parameters.Add(ParseFuncParameter());
|
|
||||||
|
|
||||||
if (!TryExpectSymbol(Symbol.Comma) && Peek().TryGetValue(out var token) && token is not SymbolToken { Symbol: Symbol.CloseParen })
|
|
||||||
{
|
|
||||||
_diagnostics.Add(Diagnostic
|
|
||||||
.Warning("Missing comma between function parameters")
|
|
||||||
.WithHelp("Add a ',' to separate parameters")
|
|
||||||
.At(token)
|
|
||||||
.Build());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var returnType = TryExpectSymbol(Symbol.Colon) ? ParseType() : new NubVoidType();
|
|
||||||
|
|
||||||
var isExtern = modifiers.RemoveAll(x => x.Modifier == Modifier.Extern) > 0;
|
|
||||||
if (isExtern)
|
|
||||||
{
|
|
||||||
if (modifiers.Count != 0)
|
|
||||||
{
|
|
||||||
throw new ParseException(Diagnostic
|
|
||||||
.Error($"Invalid modifier for extern function: {modifiers[0].Modifier}")
|
|
||||||
.WithHelp($"Extern functions cannot use the '{modifiers[0].Modifier}' modifier")
|
|
||||||
.At(modifiers[0])
|
|
||||||
.Build());
|
|
||||||
}
|
|
||||||
|
|
||||||
var callName = name.Value;
|
|
||||||
|
|
||||||
if (TryExpectSymbol(Symbol.Calls))
|
|
||||||
{
|
|
||||||
callName = ExpectIdentifier().Value;
|
|
||||||
}
|
|
||||||
|
|
||||||
return new ExternFuncDefinitionNode(GetTokensForNode(startIndex), documentation, _namespace, name.Value, callName, parameters, returnType);
|
|
||||||
}
|
|
||||||
|
|
||||||
var body = ParseBlock();
|
|
||||||
var exported = modifiers.RemoveAll(x => x.Modifier == Modifier.Export) > 0;
|
|
||||||
|
|
||||||
if (modifiers.Count != 0)
|
|
||||||
{
|
|
||||||
throw new ParseException(Diagnostic
|
|
||||||
.Error($"Invalid modifiers for function: {modifiers[0].Modifier}")
|
|
||||||
.WithHelp($"Functions cannot use the '{modifiers[0].Modifier}' modifier")
|
|
||||||
.At(modifiers[0])
|
|
||||||
.Build());
|
|
||||||
}
|
|
||||||
|
|
||||||
return new LocalFuncDefinitionNode(GetTokensForNode(startIndex), documentation, _namespace, name.Value, parameters, body, returnType, exported);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static StructDefinitionNode ParseStruct(int startIndex, List<ModifierToken> _, Optional<string> documentation)
|
|
||||||
{
|
|
||||||
var name = ExpectIdentifier().Value;
|
|
||||||
|
|
||||||
ExpectSymbol(Symbol.OpenBrace);
|
|
||||||
|
|
||||||
List<StructField> variables = [];
|
|
||||||
|
|
||||||
while (!TryExpectSymbol(Symbol.CloseBrace))
|
|
||||||
{
|
|
||||||
var variableName = ExpectIdentifier().Value;
|
|
||||||
ExpectSymbol(Symbol.Colon);
|
|
||||||
var variableType = ParseType();
|
|
||||||
|
|
||||||
var variableValue = Optional<ExpressionNode>.Empty();
|
|
||||||
|
|
||||||
if (TryExpectSymbol(Symbol.Assign))
|
|
||||||
{
|
|
||||||
variableValue = ParseExpression();
|
|
||||||
}
|
|
||||||
|
|
||||||
variables.Add(new StructField(variableName, variableType, variableValue));
|
|
||||||
}
|
|
||||||
|
|
||||||
return new StructDefinitionNode(GetTokensForNode(startIndex), documentation, _namespace, name, variables);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static FuncParameter ParseFuncParameter()
|
|
||||||
{
|
|
||||||
var name = ExpectIdentifier();
|
|
||||||
ExpectSymbol(Symbol.Colon);
|
|
||||||
var type = ParseType();
|
|
||||||
|
|
||||||
return new FuncParameter(name.Value, type);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static StatementNode ParseStatement()
|
|
||||||
{
|
|
||||||
var startIndex = _index;
|
|
||||||
if (!Peek().TryGetValue(out var token))
|
|
||||||
{
|
|
||||||
throw new ParseException(Diagnostic
|
|
||||||
.Error("Unexpected end of file while parsing statement")
|
|
||||||
.At(_tokens.Last())
|
|
||||||
.Build());
|
|
||||||
}
|
|
||||||
|
|
||||||
if (token is SymbolToken symbol)
|
|
||||||
{
|
|
||||||
switch (symbol.Symbol)
|
|
||||||
{
|
|
||||||
case Symbol.Return:
|
|
||||||
return ParseReturn(startIndex);
|
|
||||||
case Symbol.If:
|
|
||||||
return ParseIf(startIndex);
|
|
||||||
case Symbol.While:
|
|
||||||
return ParseWhile(startIndex);
|
|
||||||
case Symbol.Let:
|
|
||||||
return ParseVariableDeclaration(startIndex);
|
|
||||||
case Symbol.Break:
|
|
||||||
return ParseBreak(startIndex);
|
|
||||||
case Symbol.Continue:
|
|
||||||
return ParseContinue(startIndex);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return ParseStatementExpression(startIndex);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static StatementNode ParseStatementExpression(int startIndex)
|
|
||||||
{
|
|
||||||
var expr = ParseExpression();
|
|
||||||
|
|
||||||
if (Peek().TryGetValue(out var token))
|
|
||||||
{
|
|
||||||
if (token is SymbolToken symbol)
|
|
||||||
{
|
|
||||||
switch (symbol.Symbol)
|
|
||||||
{
|
|
||||||
case Symbol.Assign:
|
|
||||||
{
|
|
||||||
switch (expr)
|
|
||||||
{
|
|
||||||
case MemberAccessNode memberAccess:
|
|
||||||
{
|
|
||||||
Next();
|
|
||||||
var value = ParseExpression();
|
|
||||||
return new MemberAssignmentNode(GetTokensForNode(startIndex), memberAccess, value);
|
|
||||||
}
|
|
||||||
case ArrayIndexAccessNode arrayIndexAccess:
|
|
||||||
{
|
|
||||||
Next();
|
|
||||||
var value = ParseExpression();
|
|
||||||
return new ArrayIndexAssignmentNode(GetTokensForNode(startIndex), arrayIndexAccess, value);
|
|
||||||
}
|
|
||||||
case IdentifierNode identifier:
|
|
||||||
{
|
|
||||||
Next();
|
|
||||||
var value = ParseExpression();
|
|
||||||
return new VariableAssignmentNode(GetTokensForNode(startIndex), identifier, value);
|
|
||||||
}
|
|
||||||
case DereferenceNode dereference:
|
|
||||||
{
|
|
||||||
Next();
|
|
||||||
var value = ParseExpression();
|
|
||||||
return new DereferenceAssignmentNode(GetTokensForNode(startIndex), dereference, value);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return new StatementExpressionNode(GetTokensForNode(startIndex), expr);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static VariableDeclarationNode ParseVariableDeclaration(int startIndex)
|
|
||||||
{
|
|
||||||
ExpectSymbol(Symbol.Let);
|
|
||||||
var name = ExpectIdentifier().Value;
|
|
||||||
ExpectSymbol(Symbol.Colon);
|
|
||||||
var type = ParseType();
|
|
||||||
|
|
||||||
return new VariableDeclarationNode(GetTokensForNode(startIndex), name, type);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static StatementNode ParseBreak(int startIndex)
|
|
||||||
{
|
|
||||||
ExpectSymbol(Symbol.Break);
|
|
||||||
Next();
|
|
||||||
return new BreakNode(GetTokensForNode(startIndex));
|
|
||||||
}
|
|
||||||
|
|
||||||
private static StatementNode ParseContinue(int startIndex)
|
|
||||||
{
|
|
||||||
ExpectSymbol(Symbol.Continue);
|
|
||||||
return new ContinueNode(GetTokensForNode(startIndex));
|
|
||||||
}
|
|
||||||
|
|
||||||
private static ReturnNode ParseReturn(int startIndex)
|
|
||||||
{
|
|
||||||
ExpectSymbol(Symbol.Return);
|
|
||||||
var value = Optional<ExpressionNode>.Empty();
|
|
||||||
if (!TryExpectSymbol(Symbol.Semicolon))
|
|
||||||
{
|
|
||||||
value = ParseExpression();
|
|
||||||
}
|
|
||||||
|
|
||||||
return new ReturnNode(GetTokensForNode(startIndex), value);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static IfNode ParseIf(int startIndex)
|
|
||||||
{
|
|
||||||
ExpectSymbol(Symbol.If);
|
|
||||||
var condition = ParseExpression();
|
|
||||||
var body = ParseBlock();
|
|
||||||
|
|
||||||
var elseStatement = Optional<Variant<IfNode, BlockNode>>.Empty();
|
|
||||||
if (TryExpectSymbol(Symbol.Else))
|
|
||||||
{
|
|
||||||
var newStartIndex = _index;
|
|
||||||
elseStatement = TryExpectSymbol(Symbol.If)
|
|
||||||
? (Variant<IfNode, BlockNode>)ParseIf(newStartIndex)
|
|
||||||
: (Variant<IfNode, BlockNode>)ParseBlock();
|
|
||||||
}
|
|
||||||
|
|
||||||
return new IfNode(GetTokensForNode(startIndex), condition, body, elseStatement);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static WhileNode ParseWhile(int startIndex)
|
|
||||||
{
|
|
||||||
ExpectSymbol(Symbol.While);
|
|
||||||
var condition = ParseExpression();
|
|
||||||
var body = ParseBlock();
|
|
||||||
return new WhileNode(GetTokensForNode(startIndex), condition, body);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static ExpressionNode ParseExpression(int precedence = 0)
|
|
||||||
{
|
|
||||||
var startIndex = _index;
|
|
||||||
var left = ParsePrimaryExpression();
|
|
||||||
|
|
||||||
while (true)
|
|
||||||
{
|
|
||||||
var token = Peek();
|
|
||||||
if (!token.HasValue || token.Value is not SymbolToken symbolToken || !TryGetBinaryOperator(symbolToken.Symbol, out var op) ||
|
|
||||||
GetBinaryOperatorPrecedence(op.Value) < precedence)
|
|
||||||
{
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
Next();
|
|
||||||
var right = ParseExpression(GetBinaryOperatorPrecedence(op.Value) + 1);
|
|
||||||
|
|
||||||
left = new BinaryExpressionNode(GetTokensForNode(startIndex), left, op.Value, right);
|
|
||||||
}
|
|
||||||
|
|
||||||
return left;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static int GetBinaryOperatorPrecedence(BinaryExpressionOperator binaryExpressionOperator)
|
|
||||||
{
|
|
||||||
return binaryExpressionOperator switch
|
|
||||||
{
|
|
||||||
BinaryExpressionOperator.Multiply => 3,
|
|
||||||
BinaryExpressionOperator.Divide => 3,
|
|
||||||
BinaryExpressionOperator.Plus => 2,
|
|
||||||
BinaryExpressionOperator.Minus => 2,
|
|
||||||
BinaryExpressionOperator.GreaterThan => 1,
|
|
||||||
BinaryExpressionOperator.GreaterThanOrEqual => 1,
|
|
||||||
BinaryExpressionOperator.LessThan => 1,
|
|
||||||
BinaryExpressionOperator.LessThanOrEqual => 1,
|
|
||||||
BinaryExpressionOperator.Equal => 0,
|
|
||||||
BinaryExpressionOperator.NotEqual => 0,
|
|
||||||
_ => throw new ArgumentOutOfRangeException(nameof(binaryExpressionOperator), binaryExpressionOperator, null)
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
private static bool TryGetBinaryOperator(Symbol symbol, [NotNullWhen(true)] out BinaryExpressionOperator? binaryExpressionOperator)
|
|
||||||
{
|
|
||||||
switch (symbol)
|
|
||||||
{
|
|
||||||
case Symbol.Equal:
|
|
||||||
binaryExpressionOperator = BinaryExpressionOperator.Equal;
|
|
||||||
return true;
|
|
||||||
case Symbol.NotEqual:
|
|
||||||
binaryExpressionOperator = BinaryExpressionOperator.NotEqual;
|
|
||||||
return true;
|
|
||||||
case Symbol.LessThan:
|
|
||||||
binaryExpressionOperator = BinaryExpressionOperator.LessThan;
|
|
||||||
return true;
|
|
||||||
case Symbol.LessThanOrEqual:
|
|
||||||
binaryExpressionOperator = BinaryExpressionOperator.LessThanOrEqual;
|
|
||||||
return true;
|
|
||||||
case Symbol.GreaterThan:
|
|
||||||
binaryExpressionOperator = BinaryExpressionOperator.GreaterThan;
|
|
||||||
return true;
|
|
||||||
case Symbol.GreaterThanOrEqual:
|
|
||||||
binaryExpressionOperator = BinaryExpressionOperator.GreaterThanOrEqual;
|
|
||||||
return true;
|
|
||||||
case Symbol.Plus:
|
|
||||||
binaryExpressionOperator = BinaryExpressionOperator.Plus;
|
|
||||||
return true;
|
|
||||||
case Symbol.Minus:
|
|
||||||
binaryExpressionOperator = BinaryExpressionOperator.Minus;
|
|
||||||
return true;
|
|
||||||
case Symbol.Star:
|
|
||||||
binaryExpressionOperator = BinaryExpressionOperator.Multiply;
|
|
||||||
return true;
|
|
||||||
case Symbol.ForwardSlash:
|
|
||||||
binaryExpressionOperator = BinaryExpressionOperator.Divide;
|
|
||||||
return true;
|
|
||||||
default:
|
|
||||||
binaryExpressionOperator = null;
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static ExpressionNode ParsePrimaryExpression()
|
|
||||||
{
|
|
||||||
var startIndex = _index;
|
|
||||||
ExpressionNode expr;
|
|
||||||
|
|
||||||
var token = ExpectToken();
|
|
||||||
switch (token)
|
|
||||||
{
|
|
||||||
case LiteralToken literal:
|
|
||||||
{
|
|
||||||
expr = new LiteralNode(GetTokensForNode(startIndex), literal.Value, literal.Kind);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case IdentifierToken identifier:
|
|
||||||
{
|
|
||||||
var @namespace = Optional<string>.Empty();
|
|
||||||
var name = identifier.Value;
|
|
||||||
if (TryExpectSymbol(Symbol.DoubleColon))
|
|
||||||
{
|
|
||||||
@namespace = identifier.Value;
|
|
||||||
name = ExpectIdentifier().Value;
|
|
||||||
}
|
|
||||||
|
|
||||||
expr = new IdentifierNode(GetTokensForNode(startIndex), @namespace, name);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case SymbolToken symbolToken:
|
|
||||||
{
|
|
||||||
switch (symbolToken.Symbol)
|
|
||||||
{
|
|
||||||
case Symbol.Func:
|
|
||||||
{
|
|
||||||
List<FuncParameter> parameters = [];
|
|
||||||
ExpectSymbol(Symbol.OpenParen);
|
|
||||||
while (!TryExpectSymbol(Symbol.CloseParen))
|
|
||||||
{
|
|
||||||
var parameter = ParseFuncParameter();
|
|
||||||
parameters.Add(parameter);
|
|
||||||
if (!TryExpectSymbol(Symbol.Comma) && Peek().TryGetValue(out var nextToken) && nextToken is not SymbolToken { Symbol: Symbol.CloseParen })
|
|
||||||
{
|
|
||||||
_diagnostics.Add(Diagnostic
|
|
||||||
.Warning("Missing comma between function arguments")
|
|
||||||
.WithHelp("Add a ',' to separate arguments")
|
|
||||||
.At(nextToken)
|
|
||||||
.Build());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var returnType = TryExpectSymbol(Symbol.Colon) ? ParseType() : new NubVoidType();
|
|
||||||
|
|
||||||
var body = ParseBlock();
|
|
||||||
|
|
||||||
expr = new AnonymousFuncNode(GetTokensForNode(startIndex), parameters, body, returnType);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case Symbol.OpenParen:
|
|
||||||
{
|
|
||||||
var expression = ParseExpression();
|
|
||||||
ExpectSymbol(Symbol.CloseParen);
|
|
||||||
expr = expression;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case Symbol.Ampersand:
|
|
||||||
{
|
|
||||||
var expression = ParsePrimaryExpression();
|
|
||||||
if (expression is not LValueNode lValue)
|
|
||||||
{
|
|
||||||
throw new ParseException(Diagnostic
|
|
||||||
.Error("& symbol can only be used on lvalues")
|
|
||||||
.At(expression)
|
|
||||||
.Build());
|
|
||||||
}
|
|
||||||
|
|
||||||
expr = new AddressOfNode(GetTokensForNode(startIndex), lValue);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case Symbol.Minus:
|
|
||||||
{
|
|
||||||
var expression = ParsePrimaryExpression();
|
|
||||||
expr = new UnaryExpressionNode(GetTokensForNode(startIndex), UnaryExpressionOperator.Negate, expression);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case Symbol.Bang:
|
|
||||||
{
|
|
||||||
var expression = ParsePrimaryExpression();
|
|
||||||
expr = new UnaryExpressionNode(GetTokensForNode(startIndex), UnaryExpressionOperator.Invert, expression);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case Symbol.OpenBracket:
|
|
||||||
{
|
|
||||||
if (Peek().TryGetValue(out var capacityToken) && capacityToken is LiteralToken { Kind: LiteralKind.Integer } literalToken)
|
|
||||||
{
|
|
||||||
var capacity = int.Parse(literalToken.Value);
|
|
||||||
Next();
|
|
||||||
ExpectSymbol(Symbol.CloseBracket);
|
|
||||||
var elementType = ParseType();
|
|
||||||
|
|
||||||
if (capacity > 0)
|
|
||||||
{
|
|
||||||
expr = new FixedArrayInitializerNode(GetTokensForNode(startIndex), elementType, capacity);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
throw new ParseException(Diagnostic
|
|
||||||
.Error("Fixed array size must be a positive integer")
|
|
||||||
.WithHelp("Use a positive integer literal for the array size")
|
|
||||||
.At(literalToken)
|
|
||||||
.Build());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
var capacity = ParseExpression();
|
|
||||||
ExpectSymbol(Symbol.CloseBracket);
|
|
||||||
var type = ParseType();
|
|
||||||
|
|
||||||
expr = new ArrayInitializerNode(GetTokensForNode(startIndex), capacity, type);
|
|
||||||
}
|
|
||||||
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case Symbol.Alloc:
|
|
||||||
{
|
|
||||||
var type = ParseType();
|
|
||||||
Dictionary<string, ExpressionNode> initializers = [];
|
|
||||||
ExpectSymbol(Symbol.OpenBrace);
|
|
||||||
while (!TryExpectSymbol(Symbol.CloseBrace))
|
|
||||||
{
|
|
||||||
var name = ExpectIdentifier().Value;
|
|
||||||
ExpectSymbol(Symbol.Assign);
|
|
||||||
var value = ParseExpression();
|
|
||||||
initializers.Add(name, value);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (type is not NubStructType structType)
|
|
||||||
{
|
|
||||||
throw new ParseException(Diagnostic
|
|
||||||
.Error($"Cannot alloc type '{type}'")
|
|
||||||
.At(symbolToken)
|
|
||||||
.Build());
|
|
||||||
}
|
|
||||||
|
|
||||||
expr = new StructInitializerNode(GetTokensForNode(startIndex), structType, initializers);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
{
|
|
||||||
throw new ParseException(Diagnostic
|
|
||||||
.Error($"Unexpected symbol '{symbolToken.Symbol}' in expression")
|
|
||||||
.WithHelp("Expected literal, identifier, or '(' to start expression")
|
|
||||||
.At(symbolToken)
|
|
||||||
.Build());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
{
|
|
||||||
throw new ParseException(Diagnostic
|
|
||||||
.Error($"Unexpected token '{token.GetType().Name}' in expression")
|
|
||||||
.WithHelp("Expected literal, identifier, or parenthesized expression")
|
|
||||||
.At(token)
|
|
||||||
.Build());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return ParsePostfixOperators(startIndex, expr);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static ExpressionNode ParsePostfixOperators(int startIndex, ExpressionNode expr)
|
|
||||||
{
|
|
||||||
while (true)
|
|
||||||
{
|
|
||||||
if (TryExpectSymbol(Symbol.Caret))
|
|
||||||
{
|
|
||||||
expr = new DereferenceNode(GetTokensForNode(startIndex), expr);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (TryExpectSymbol(Symbol.Period))
|
|
||||||
{
|
|
||||||
var structMember = ExpectIdentifier().Value;
|
|
||||||
expr = new MemberAccessNode(GetTokensForNode(startIndex), expr, structMember);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (TryExpectSymbol(Symbol.OpenBracket))
|
|
||||||
{
|
|
||||||
var index = ParseExpression();
|
|
||||||
ExpectSymbol(Symbol.CloseBracket);
|
|
||||||
expr = new ArrayIndexAccessNode(GetTokensForNode(startIndex), expr, index);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (TryExpectSymbol(Symbol.OpenParen))
|
|
||||||
{
|
|
||||||
var parameters = new List<ExpressionNode>();
|
|
||||||
while (!TryExpectSymbol(Symbol.CloseParen))
|
|
||||||
{
|
|
||||||
parameters.Add(ParseExpression());
|
|
||||||
if (!TryExpectSymbol(Symbol.Comma) && Peek().TryGetValue(out var nextToken) && nextToken is not SymbolToken { Symbol: Symbol.CloseParen })
|
|
||||||
{
|
|
||||||
_diagnostics.Add(Diagnostic
|
|
||||||
.Warning("Missing comma between function arguments")
|
|
||||||
.WithHelp("Add a ',' to separate arguments")
|
|
||||||
.At(nextToken)
|
|
||||||
.Build());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
expr = new FuncCallNode(GetTokensForNode(startIndex), expr, parameters);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
return expr;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static BlockNode ParseBlock()
|
|
||||||
{
|
|
||||||
var startIndex = _index;
|
|
||||||
ExpectSymbol(Symbol.OpenBrace);
|
|
||||||
List<StatementNode> statements = [];
|
|
||||||
while (Peek().HasValue && !TryExpectSymbol(Symbol.CloseBrace))
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
statements.Add(ParseStatement());
|
|
||||||
}
|
|
||||||
catch (ParseException ex)
|
|
||||||
{
|
|
||||||
_diagnostics.Add(ex.Diagnostic);
|
|
||||||
RecoverToNextStatement();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return new BlockNode(GetTokensForNode(startIndex), statements);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static NubType ParseType()
|
|
||||||
{
|
|
||||||
if (TryExpectIdentifier(out var name))
|
|
||||||
{
|
|
||||||
if (name.Value == "any")
|
|
||||||
{
|
|
||||||
return new NubAnyType();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (name.Value == "void")
|
|
||||||
{
|
|
||||||
return new NubVoidType();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (name.Value == "string")
|
|
||||||
{
|
|
||||||
return new NubStringType();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (name.Value == "cstring")
|
|
||||||
{
|
|
||||||
return new NubCStringType();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (NubPrimitiveType.TryParse(name.Value, out var primitiveTypeKind))
|
|
||||||
{
|
|
||||||
return new NubPrimitiveType(primitiveTypeKind.Value);
|
|
||||||
}
|
|
||||||
|
|
||||||
var @namespace = _namespace;
|
|
||||||
if (TryExpectSymbol(Symbol.DoubleColon))
|
|
||||||
{
|
|
||||||
@namespace = ExpectIdentifier().Value;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (@namespace == null)
|
|
||||||
{
|
|
||||||
throw new ParseException(Diagnostic
|
|
||||||
.Error($"Struct '{name.Value}' does not belong to a namespace")
|
|
||||||
.WithHelp("Make sure you have specified a namespace at the top of the file")
|
|
||||||
.At(name)
|
|
||||||
.Build());
|
|
||||||
}
|
|
||||||
|
|
||||||
return new NubStructType(@namespace , name.Value);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (TryExpectSymbol(Symbol.Caret))
|
|
||||||
{
|
|
||||||
var baseType = ParseType();
|
|
||||||
return new NubPointerType(baseType);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (TryExpectSymbol(Symbol.Func))
|
|
||||||
{
|
|
||||||
ExpectSymbol(Symbol.OpenParen);
|
|
||||||
List<NubType> parameters = [];
|
|
||||||
while (!TryExpectSymbol(Symbol.CloseParen))
|
|
||||||
{
|
|
||||||
var parameter = ParseType();
|
|
||||||
parameters.Add(parameter);
|
|
||||||
if (!TryExpectSymbol(Symbol.Comma) && Peek().TryGetValue(out var nextToken) && nextToken is not SymbolToken { Symbol: Symbol.CloseParen })
|
|
||||||
{
|
|
||||||
_diagnostics.Add(Diagnostic
|
|
||||||
.Warning("Missing comma between func type arguments")
|
|
||||||
.WithHelp("Add a ',' to separate arguments")
|
|
||||||
.At(nextToken)
|
|
||||||
.Build());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var returnType = TryExpectSymbol(Symbol.Colon) ? ParseType() : new NubVoidType();
|
|
||||||
|
|
||||||
return new NubFuncType(returnType, parameters);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (TryExpectSymbol(Symbol.OpenBracket))
|
|
||||||
{
|
|
||||||
if (Peek().TryGetValue(out var token) && token is LiteralToken { Kind: LiteralKind.Integer, Value: var sizeValue })
|
|
||||||
{
|
|
||||||
Next();
|
|
||||||
ExpectSymbol(Symbol.CloseBracket);
|
|
||||||
var baseType = ParseType();
|
|
||||||
|
|
||||||
var size = int.Parse(sizeValue);
|
|
||||||
|
|
||||||
if (size > 0)
|
|
||||||
{
|
|
||||||
return new NubFixedArrayType(baseType, size);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
throw new UnreachableException();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
ExpectSymbol(Symbol.CloseBracket);
|
|
||||||
var baseType = ParseType();
|
|
||||||
return new NubArrayType(baseType);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!Peek().TryGetValue(out var peekToken))
|
|
||||||
{
|
|
||||||
throw new ParseException(Diagnostic
|
|
||||||
.Error("Unexpected end of file while parsing type")
|
|
||||||
.WithHelp("Expected a type name")
|
|
||||||
.At(_tokens.Last())
|
|
||||||
.Build());
|
|
||||||
}
|
|
||||||
|
|
||||||
throw new ParseException(Diagnostic
|
|
||||||
.Error("Invalid type Syntax")
|
|
||||||
.WithHelp("Expected type name, '^' for pointer, or '[]' for array")
|
|
||||||
.At(peekToken)
|
|
||||||
.Build());
|
|
||||||
}
|
|
||||||
|
|
||||||
private static Token ExpectToken()
|
|
||||||
{
|
|
||||||
if (!Peek().TryGetValue(out var token))
|
|
||||||
{
|
|
||||||
throw new ParseException(Diagnostic
|
|
||||||
.Error("Unexpected end of file")
|
|
||||||
.WithHelp("Expected more tokens to complete the Syntax")
|
|
||||||
.At(_tokens.Last())
|
|
||||||
.Build());
|
|
||||||
}
|
|
||||||
|
|
||||||
Next();
|
|
||||||
return token;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static SymbolToken ExpectSymbol()
|
|
||||||
{
|
|
||||||
var token = ExpectToken();
|
|
||||||
if (token is not SymbolToken symbol)
|
|
||||||
{
|
|
||||||
throw new ParseException(Diagnostic
|
|
||||||
.Error($"Expected symbol, but found {token.GetType().Name}")
|
|
||||||
.WithHelp("This position requires a symbol like '(', ')', '{', '}', etc.")
|
|
||||||
.At(token)
|
|
||||||
.Build());
|
|
||||||
}
|
|
||||||
|
|
||||||
return symbol;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void ExpectSymbol(Symbol expectedSymbol)
|
|
||||||
{
|
|
||||||
var token = ExpectSymbol();
|
|
||||||
if (token.Symbol != expectedSymbol)
|
|
||||||
{
|
|
||||||
throw new ParseException(Diagnostic
|
|
||||||
.Error($"Expected '{expectedSymbol}', but found '{token.Symbol}'")
|
|
||||||
.WithHelp($"Insert '{expectedSymbol}' here")
|
|
||||||
.At(token)
|
|
||||||
.Build());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static bool TryExpectSymbol(Symbol symbol)
|
|
||||||
{
|
|
||||||
if (Peek() is { Value: SymbolToken symbolToken } && symbolToken.Symbol == symbol)
|
|
||||||
{
|
|
||||||
Next();
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static bool TryExpectModifier([NotNullWhen(true)] out ModifierToken? modifier)
|
|
||||||
{
|
|
||||||
if (Peek() is { Value: ModifierToken modifierToken })
|
|
||||||
{
|
|
||||||
modifier = modifierToken;
|
|
||||||
Next();
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
modifier = null;
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static bool TryExpectIdentifier([NotNullWhen(true)] out IdentifierToken? identifier)
|
|
||||||
{
|
|
||||||
if (Peek() is { Value: IdentifierToken identifierToken })
|
|
||||||
{
|
|
||||||
identifier = identifierToken;
|
|
||||||
Next();
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
identifier = null;
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static IdentifierToken ExpectIdentifier()
|
|
||||||
{
|
|
||||||
var token = ExpectToken();
|
|
||||||
if (token is not IdentifierToken identifier)
|
|
||||||
{
|
|
||||||
throw new ParseException(Diagnostic
|
|
||||||
.Error($"Expected identifier, but found {token.GetType().Name}")
|
|
||||||
.WithHelp("Provide a valid identifier name here")
|
|
||||||
.At(token)
|
|
||||||
.Build());
|
|
||||||
}
|
|
||||||
|
|
||||||
return identifier;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void RecoverToNextDefinition()
|
|
||||||
{
|
|
||||||
while (Peek().HasValue)
|
|
||||||
{
|
|
||||||
var token = Peek().Value;
|
|
||||||
if (token is SymbolToken { Symbol: Symbol.Func or Symbol.Struct } or ModifierToken)
|
|
||||||
{
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
Next();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void RecoverToNextStatement()
|
|
||||||
{
|
|
||||||
while (Peek().TryGetValue(out var token))
|
|
||||||
{
|
|
||||||
if (token is SymbolToken { Symbol: Symbol.CloseBrace } or IdentifierToken or SymbolToken
|
|
||||||
{
|
|
||||||
Symbol: Symbol.Return or Symbol.If or Symbol.While or Symbol.Let or Symbol.Break or Symbol.Continue
|
|
||||||
})
|
|
||||||
{
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
Next();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static Optional<Token> Peek(int offset = 0)
|
|
||||||
{
|
|
||||||
var peekIndex = _index + offset;
|
|
||||||
while (peekIndex < _tokens.Count() && _tokens.ElementAt(peekIndex) is DocumentationToken)
|
|
||||||
{
|
|
||||||
peekIndex++;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (peekIndex < _tokens.Count())
|
|
||||||
{
|
|
||||||
return _tokens.ElementAt(peekIndex);
|
|
||||||
}
|
|
||||||
|
|
||||||
return Optional<Token>.Empty();
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void Next()
|
|
||||||
{
|
|
||||||
while (_index < _tokens.Count() && _tokens.ElementAt(_index) is DocumentationToken)
|
|
||||||
{
|
|
||||||
_index++;
|
|
||||||
}
|
|
||||||
|
|
||||||
_index++;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static IEnumerable<Token> GetTokensForNode(int startIndex)
|
|
||||||
{
|
|
||||||
return _tokens.Skip(startIndex).Take(Math.Min(_index, _tokens.Count() - 1) - startIndex);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public class ParseException : Exception
|
|
||||||
{
|
|
||||||
public Diagnostic Diagnostic { get; }
|
|
||||||
|
|
||||||
public ParseException(Diagnostic diagnostic) : base(diagnostic.Message)
|
|
||||||
{
|
|
||||||
Diagnostic = diagnostic;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
using Syntax.Parsing.Node;
|
|
||||||
|
|
||||||
namespace Syntax.Parsing;
|
|
||||||
|
|
||||||
public record SyntaxTree(string FilePath, string Namespace, List<DefinitionNode> Definitions);
|
|
||||||
@@ -1,274 +0,0 @@
|
|||||||
using System.Diagnostics.CodeAnalysis;
|
|
||||||
|
|
||||||
namespace Syntax;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Represents a location in source code with line and column information.
|
|
||||||
/// Lines and columns are 1-based to match typical editor conventions.
|
|
||||||
/// </summary>
|
|
||||||
public readonly struct SourceLocation : IEquatable<SourceLocation>, IComparable<SourceLocation>
|
|
||||||
{
|
|
||||||
public SourceLocation(int line, int column)
|
|
||||||
{
|
|
||||||
if (line < 1)
|
|
||||||
{
|
|
||||||
throw new ArgumentOutOfRangeException(nameof(line), "Line must be >= 1");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (column < 1)
|
|
||||||
{
|
|
||||||
throw new ArgumentOutOfRangeException(nameof(column), "Column must be >= 1");
|
|
||||||
}
|
|
||||||
|
|
||||||
Line = line;
|
|
||||||
Column = column;
|
|
||||||
}
|
|
||||||
|
|
||||||
public int Line { get; }
|
|
||||||
public int Column { get; }
|
|
||||||
|
|
||||||
public int CompareTo(SourceLocation other)
|
|
||||||
{
|
|
||||||
var lineComparison = Line.CompareTo(other.Line);
|
|
||||||
if (lineComparison == 0)
|
|
||||||
{
|
|
||||||
return Column.CompareTo(other.Column);
|
|
||||||
}
|
|
||||||
|
|
||||||
return lineComparison;
|
|
||||||
}
|
|
||||||
|
|
||||||
public override string ToString()
|
|
||||||
{
|
|
||||||
return $"{Line}:{Column}";
|
|
||||||
}
|
|
||||||
|
|
||||||
public bool Equals(SourceLocation other)
|
|
||||||
{
|
|
||||||
return Line == other.Line && Column == other.Column;
|
|
||||||
}
|
|
||||||
|
|
||||||
public override bool Equals([NotNullWhen(true)] object? obj)
|
|
||||||
{
|
|
||||||
return obj is SourceLocation other && Equals(other);
|
|
||||||
}
|
|
||||||
|
|
||||||
public override int GetHashCode()
|
|
||||||
{
|
|
||||||
return HashCode.Combine(Line, Column);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static bool operator ==(SourceLocation left, SourceLocation right) => left.Equals(right);
|
|
||||||
public static bool operator !=(SourceLocation left, SourceLocation right) => !left.Equals(right);
|
|
||||||
public static bool operator <(SourceLocation left, SourceLocation right) => left.CompareTo(right) < 0;
|
|
||||||
public static bool operator >(SourceLocation left, SourceLocation right) => left.CompareTo(right) > 0;
|
|
||||||
public static bool operator <=(SourceLocation left, SourceLocation right) => left.CompareTo(right) <= 0;
|
|
||||||
public static bool operator >=(SourceLocation left, SourceLocation right) => left.CompareTo(right) >= 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Represents source text with a name (typically filename) and content.
|
|
||||||
/// Equality is based on both name and content for better semantics.
|
|
||||||
/// </summary>
|
|
||||||
public struct SourceText : IEquatable<SourceText>
|
|
||||||
{
|
|
||||||
private int _lines = -1;
|
|
||||||
|
|
||||||
public SourceText(string path, string content)
|
|
||||||
{
|
|
||||||
Path = path ?? throw new ArgumentNullException(nameof(path));
|
|
||||||
Content = content ?? throw new ArgumentNullException(nameof(content));
|
|
||||||
}
|
|
||||||
|
|
||||||
public string Path { get; }
|
|
||||||
public string Content { get; }
|
|
||||||
|
|
||||||
public int LineCount()
|
|
||||||
{
|
|
||||||
if (_lines == -1)
|
|
||||||
{
|
|
||||||
_lines = Content.Split('\n').Length + 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
return _lines;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets a specific line from the source text (1-based).
|
|
||||||
/// </summary>
|
|
||||||
public string GetLine(int lineNumber)
|
|
||||||
{
|
|
||||||
if (lineNumber < 1)
|
|
||||||
{
|
|
||||||
throw new ArgumentOutOfRangeException(nameof(lineNumber));
|
|
||||||
}
|
|
||||||
|
|
||||||
var lines = Content.Split('\n');
|
|
||||||
return lineNumber <= lines.Length ? lines[lineNumber - 1] : string.Empty;
|
|
||||||
}
|
|
||||||
|
|
||||||
public bool Equals(SourceText other)
|
|
||||||
{
|
|
||||||
return Path == other.Path && Content == other.Content;
|
|
||||||
}
|
|
||||||
|
|
||||||
public override bool Equals([NotNullWhen(true)] object? obj)
|
|
||||||
{
|
|
||||||
return obj is SourceText other && Equals(other);
|
|
||||||
}
|
|
||||||
|
|
||||||
public override int GetHashCode()
|
|
||||||
{
|
|
||||||
return HashCode.Combine(Path, Content);
|
|
||||||
}
|
|
||||||
|
|
||||||
public override string ToString()
|
|
||||||
{
|
|
||||||
return Path;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static bool operator ==(SourceText left, SourceText right) => left.Equals(right);
|
|
||||||
public static bool operator !=(SourceText left, SourceText right) => !left.Equals(right);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Represents a span of source code from a start to end location within a source text.
|
|
||||||
/// </summary>
|
|
||||||
public readonly struct SourceSpan : IEquatable<SourceSpan>
|
|
||||||
{
|
|
||||||
public SourceSpan(SourceText text, SourceLocation start, SourceLocation end)
|
|
||||||
{
|
|
||||||
if (start > end)
|
|
||||||
{
|
|
||||||
throw new ArgumentException("Start location cannot be after end location");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (end.Line > text.LineCount() || end.Line == text.LineCount() && end.Column > text.GetLine(text.LineCount()).Length + 1)
|
|
||||||
{
|
|
||||||
throw new ArgumentException("End location cannot be after text end location");
|
|
||||||
}
|
|
||||||
|
|
||||||
Text = text;
|
|
||||||
Start = start;
|
|
||||||
End = end;
|
|
||||||
}
|
|
||||||
|
|
||||||
public SourceText Text { get; }
|
|
||||||
public SourceLocation Start { get; }
|
|
||||||
public SourceLocation End { get; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets whether this span represents a single point (start == end).
|
|
||||||
/// </summary>
|
|
||||||
public bool IsEmpty => Start == End;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets whether this span is contained within a single line.
|
|
||||||
/// </summary>
|
|
||||||
public bool IsSingleLine => Start.Line == End.Line;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the text content covered by this span.
|
|
||||||
/// </summary>
|
|
||||||
public string GetText()
|
|
||||||
{
|
|
||||||
if (IsEmpty)
|
|
||||||
{
|
|
||||||
return string.Empty;
|
|
||||||
}
|
|
||||||
|
|
||||||
var lines = Text.Content.Split('\n');
|
|
||||||
|
|
||||||
if (IsSingleLine)
|
|
||||||
{
|
|
||||||
var line = lines[Start.Line - 1];
|
|
||||||
var startCol = Math.Min(Start.Column - 1, line.Length);
|
|
||||||
var endCol = Math.Min(End.Column - 1, line.Length);
|
|
||||||
return line.Substring(startCol, Math.Max(0, endCol - startCol));
|
|
||||||
}
|
|
||||||
|
|
||||||
var result = new List<string>();
|
|
||||||
for (var i = Start.Line - 1; i < Math.Min(End.Line, lines.Length); i++)
|
|
||||||
{
|
|
||||||
var line = lines[i];
|
|
||||||
if (i == Start.Line - 1)
|
|
||||||
{
|
|
||||||
result.Add(line[Math.Min(Start.Column - 1, line.Length)..]);
|
|
||||||
}
|
|
||||||
else if (i == End.Line - 1)
|
|
||||||
{
|
|
||||||
result.Add(line[..Math.Min(End.Column - 1, line.Length)]);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
result.Add(line);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return string.Join("\n", result);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Merges multiple source spans from the same file into a single span.
|
|
||||||
/// The result spans from the earliest start to the latest end.
|
|
||||||
/// </summary>
|
|
||||||
public static SourceSpan Merge(IEnumerable<SourceSpan> spans)
|
|
||||||
{
|
|
||||||
var spanArray = spans.ToArray();
|
|
||||||
if (spanArray.Length == 0)
|
|
||||||
{
|
|
||||||
throw new ArgumentException("Cannot merge empty collection of spans", nameof(spans));
|
|
||||||
}
|
|
||||||
|
|
||||||
var firstText = spanArray[0].Text;
|
|
||||||
if (spanArray.Any(s => !s.Text.Equals(firstText)))
|
|
||||||
{
|
|
||||||
throw new ArgumentException("All spans must be from the same source text", nameof(spans));
|
|
||||||
}
|
|
||||||
|
|
||||||
var minStart = spanArray.Min(s => s.Start);
|
|
||||||
var maxEnd = spanArray.Max(s => s.End);
|
|
||||||
|
|
||||||
return new SourceSpan(firstText, minStart, maxEnd);
|
|
||||||
}
|
|
||||||
|
|
||||||
public override string ToString()
|
|
||||||
{
|
|
||||||
if (IsEmpty)
|
|
||||||
{
|
|
||||||
return $"{Start}";
|
|
||||||
}
|
|
||||||
|
|
||||||
if (IsSingleLine)
|
|
||||||
{
|
|
||||||
return Start.Column == End.Column ? $"{Start}" : $"{Start.Line}:{Start.Column}-{End.Column}";
|
|
||||||
}
|
|
||||||
|
|
||||||
return $"{Start}-{End}";
|
|
||||||
}
|
|
||||||
|
|
||||||
public bool Equals(SourceSpan other)
|
|
||||||
{
|
|
||||||
return Text.Equals(other.Text) && Start.Equals(other.Start) && End.Equals(other.End);
|
|
||||||
}
|
|
||||||
|
|
||||||
public override bool Equals([NotNullWhen(true)] object? obj)
|
|
||||||
{
|
|
||||||
return obj is SourceSpan other && Equals(other);
|
|
||||||
}
|
|
||||||
|
|
||||||
public override int GetHashCode()
|
|
||||||
{
|
|
||||||
return HashCode.Combine(Text, Start, End);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static bool operator ==(SourceSpan left, SourceSpan right)
|
|
||||||
{
|
|
||||||
return left.Equals(right);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static bool operator !=(SourceSpan left, SourceSpan right)
|
|
||||||
{
|
|
||||||
return !left.Equals(right);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,14 +0,0 @@
|
|||||||
<Project Sdk="Microsoft.NET.Sdk">
|
|
||||||
|
|
||||||
<PropertyGroup>
|
|
||||||
<TargetFramework>net9.0</TargetFramework>
|
|
||||||
<ImplicitUsings>enable</ImplicitUsings>
|
|
||||||
<Nullable>enable</Nullable>
|
|
||||||
<IsAotCompatible>true</IsAotCompatible>
|
|
||||||
</PropertyGroup>
|
|
||||||
|
|
||||||
<ItemGroup>
|
|
||||||
<ProjectReference Include="..\Common\Common.csproj" />
|
|
||||||
</ItemGroup>
|
|
||||||
|
|
||||||
</Project>
|
|
||||||
@@ -1,6 +0,0 @@
|
|||||||
namespace Syntax.Tokenization;
|
|
||||||
|
|
||||||
public class DocumentationToken(SourceSpan span, string documentation) : Token(span)
|
|
||||||
{
|
|
||||||
public string Documentation { get; } = documentation;
|
|
||||||
}
|
|
||||||
@@ -1,6 +0,0 @@
|
|||||||
namespace Syntax.Tokenization;
|
|
||||||
|
|
||||||
public class IdentifierToken(SourceSpan span, string value) : Token(span)
|
|
||||||
{
|
|
||||||
public string Value { get; } = value;
|
|
||||||
}
|
|
||||||
@@ -1,15 +0,0 @@
|
|||||||
namespace Syntax.Tokenization;
|
|
||||||
|
|
||||||
public class LiteralToken(SourceSpan span, LiteralKind kind, string value) : Token(span)
|
|
||||||
{
|
|
||||||
public LiteralKind Kind { get; } = kind;
|
|
||||||
public string Value { get; } = value;
|
|
||||||
}
|
|
||||||
|
|
||||||
public enum LiteralKind
|
|
||||||
{
|
|
||||||
Integer,
|
|
||||||
Float,
|
|
||||||
String,
|
|
||||||
Bool
|
|
||||||
}
|
|
||||||
@@ -1,12 +0,0 @@
|
|||||||
namespace Syntax.Tokenization;
|
|
||||||
|
|
||||||
public class ModifierToken(SourceSpan span, Modifier modifier) : Token(span)
|
|
||||||
{
|
|
||||||
public Modifier Modifier { get; } = modifier;
|
|
||||||
}
|
|
||||||
|
|
||||||
public enum Modifier
|
|
||||||
{
|
|
||||||
Extern,
|
|
||||||
Export
|
|
||||||
}
|
|
||||||
@@ -1,47 +0,0 @@
|
|||||||
namespace Syntax.Tokenization;
|
|
||||||
|
|
||||||
public class SymbolToken(SourceSpan span, Symbol symbol) : Token(span)
|
|
||||||
{
|
|
||||||
public Symbol Symbol { get; } = symbol;
|
|
||||||
}
|
|
||||||
|
|
||||||
public enum Symbol
|
|
||||||
{
|
|
||||||
Func,
|
|
||||||
Return,
|
|
||||||
If,
|
|
||||||
Else,
|
|
||||||
While,
|
|
||||||
Break,
|
|
||||||
Continue,
|
|
||||||
Semicolon,
|
|
||||||
Colon,
|
|
||||||
OpenParen,
|
|
||||||
CloseParen,
|
|
||||||
OpenBrace,
|
|
||||||
CloseBrace,
|
|
||||||
OpenBracket,
|
|
||||||
CloseBracket,
|
|
||||||
Comma,
|
|
||||||
Period,
|
|
||||||
Assign,
|
|
||||||
Bang,
|
|
||||||
Equal,
|
|
||||||
NotEqual,
|
|
||||||
LessThan,
|
|
||||||
LessThanOrEqual,
|
|
||||||
GreaterThan,
|
|
||||||
GreaterThanOrEqual,
|
|
||||||
Plus,
|
|
||||||
Minus,
|
|
||||||
Star,
|
|
||||||
ForwardSlash,
|
|
||||||
Struct,
|
|
||||||
Caret,
|
|
||||||
Ampersand,
|
|
||||||
DoubleColon,
|
|
||||||
Namespace,
|
|
||||||
Let,
|
|
||||||
Alloc,
|
|
||||||
Calls
|
|
||||||
}
|
|
||||||
@@ -1,6 +0,0 @@
|
|||||||
namespace Syntax.Tokenization;
|
|
||||||
|
|
||||||
public abstract class Token(SourceSpan span)
|
|
||||||
{
|
|
||||||
public SourceSpan Span { get; } = span;
|
|
||||||
}
|
|
||||||
@@ -1,283 +0,0 @@
|
|||||||
using Common;
|
|
||||||
using Syntax.Diagnostics;
|
|
||||||
|
|
||||||
namespace Syntax.Tokenization;
|
|
||||||
|
|
||||||
public static class Tokenizer
|
|
||||||
{
|
|
||||||
private static readonly Dictionary<string, Symbol> Keywords = new()
|
|
||||||
{
|
|
||||||
["namespace"] = Symbol.Namespace,
|
|
||||||
["func"] = Symbol.Func,
|
|
||||||
["if"] = Symbol.If,
|
|
||||||
["else"] = Symbol.Else,
|
|
||||||
["while"] = Symbol.While,
|
|
||||||
["break"] = Symbol.Break,
|
|
||||||
["continue"] = Symbol.Continue,
|
|
||||||
["return"] = Symbol.Return,
|
|
||||||
["alloc"] = Symbol.Alloc,
|
|
||||||
["struct"] = Symbol.Struct,
|
|
||||||
["let"] = Symbol.Let,
|
|
||||||
["calls"] = Symbol.Calls,
|
|
||||||
};
|
|
||||||
|
|
||||||
private static readonly Dictionary<string, Modifier> Modifiers = new()
|
|
||||||
{
|
|
||||||
["export"] = Modifier.Export,
|
|
||||||
["extern"] = Modifier.Extern,
|
|
||||||
};
|
|
||||||
|
|
||||||
private static readonly Dictionary<char[], Symbol> Chians = new()
|
|
||||||
{
|
|
||||||
[['=', '=']] = Symbol.Equal,
|
|
||||||
[['!', '=']] = Symbol.NotEqual,
|
|
||||||
[['<', '=']] = Symbol.LessThanOrEqual,
|
|
||||||
[['>', '=']] = Symbol.GreaterThanOrEqual,
|
|
||||||
[[':', ':']] = Symbol.DoubleColon,
|
|
||||||
};
|
|
||||||
|
|
||||||
private static readonly Dictionary<char, Symbol> Chars = new()
|
|
||||||
{
|
|
||||||
[';'] = Symbol.Semicolon,
|
|
||||||
[':'] = Symbol.Colon,
|
|
||||||
['('] = Symbol.OpenParen,
|
|
||||||
[')'] = Symbol.CloseParen,
|
|
||||||
['{'] = Symbol.OpenBrace,
|
|
||||||
['}'] = Symbol.CloseBrace,
|
|
||||||
['['] = Symbol.OpenBracket,
|
|
||||||
[']'] = Symbol.CloseBracket,
|
|
||||||
[','] = Symbol.Comma,
|
|
||||||
['.'] = Symbol.Period,
|
|
||||||
['='] = Symbol.Assign,
|
|
||||||
['<'] = Symbol.LessThan,
|
|
||||||
['>'] = Symbol.GreaterThan,
|
|
||||||
['+'] = Symbol.Plus,
|
|
||||||
['-'] = Symbol.Minus,
|
|
||||||
['*'] = Symbol.Star,
|
|
||||||
['/'] = Symbol.ForwardSlash,
|
|
||||||
['!'] = Symbol.Bang,
|
|
||||||
['^'] = Symbol.Caret,
|
|
||||||
['&'] = Symbol.Ampersand,
|
|
||||||
};
|
|
||||||
|
|
||||||
private static SourceText _sourceText;
|
|
||||||
private static int _index;
|
|
||||||
|
|
||||||
public static IEnumerable<Token> Tokenize(SourceText sourceText, out IEnumerable<Diagnostic> diagnostics)
|
|
||||||
{
|
|
||||||
_sourceText = sourceText;
|
|
||||||
_index = 0;
|
|
||||||
|
|
||||||
List<Token> tokens = [];
|
|
||||||
while (ParseToken().TryGetValue(out var token))
|
|
||||||
{
|
|
||||||
tokens.Add(token);
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: Implement diagnostics
|
|
||||||
diagnostics = [];
|
|
||||||
return tokens;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void ConsumeWhitespace()
|
|
||||||
{
|
|
||||||
while (Peek().TryGetValue(out var character) && char.IsWhiteSpace(character))
|
|
||||||
{
|
|
||||||
Next();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static Optional<Token> ParseToken()
|
|
||||||
{
|
|
||||||
ConsumeWhitespace();
|
|
||||||
var startIndex = _index;
|
|
||||||
|
|
||||||
if (!Peek().TryGetValue(out var current))
|
|
||||||
{
|
|
||||||
return Optional<Token>.Empty();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (current == '/' && Peek(1).TryGetValue(out var nextChar) && nextChar == '/')
|
|
||||||
{
|
|
||||||
Next();
|
|
||||||
Next();
|
|
||||||
|
|
||||||
if (Peek().TryGetValue(out var thirdChar) && thirdChar == '/')
|
|
||||||
{
|
|
||||||
Next();
|
|
||||||
var buffer = string.Empty;
|
|
||||||
while (Peek().TryGetValue(out var character) && character != '\n')
|
|
||||||
{
|
|
||||||
buffer += character;
|
|
||||||
Next();
|
|
||||||
}
|
|
||||||
|
|
||||||
Next();
|
|
||||||
return new DocumentationToken(CreateSpan(startIndex), buffer);
|
|
||||||
}
|
|
||||||
|
|
||||||
while (Peek().TryGetValue(out var character) && character != '\n')
|
|
||||||
{
|
|
||||||
Next();
|
|
||||||
}
|
|
||||||
|
|
||||||
Next();
|
|
||||||
return ParseToken();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (char.IsLetter(current) || current == '_')
|
|
||||||
{
|
|
||||||
var buffer = string.Empty;
|
|
||||||
|
|
||||||
while (Peek().TryGetValue(out var next) && (char.IsLetterOrDigit(next) || next == '_'))
|
|
||||||
{
|
|
||||||
buffer += next;
|
|
||||||
Next();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (Keywords.TryGetValue(buffer, out var keywordSymbol))
|
|
||||||
{
|
|
||||||
return new SymbolToken(CreateSpan(startIndex), keywordSymbol);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (Modifiers.TryGetValue(buffer, out var modifer))
|
|
||||||
{
|
|
||||||
return new ModifierToken(CreateSpan(startIndex), modifer);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (buffer is "true" or "false")
|
|
||||||
{
|
|
||||||
return new LiteralToken(CreateSpan(startIndex), LiteralKind.Bool, buffer);
|
|
||||||
}
|
|
||||||
|
|
||||||
return new IdentifierToken(CreateSpan(startIndex), buffer);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (char.IsDigit(current))
|
|
||||||
{
|
|
||||||
var isFloat = false;
|
|
||||||
var buffer = string.Empty;
|
|
||||||
|
|
||||||
while (Peek().TryGetValue(out var next))
|
|
||||||
{
|
|
||||||
if (next == '.')
|
|
||||||
{
|
|
||||||
if (isFloat)
|
|
||||||
{
|
|
||||||
throw new Exception("More than one period found in float literal");
|
|
||||||
}
|
|
||||||
|
|
||||||
isFloat = true;
|
|
||||||
buffer += next;
|
|
||||||
Next();
|
|
||||||
}
|
|
||||||
else if (char.IsDigit(next))
|
|
||||||
{
|
|
||||||
buffer += next;
|
|
||||||
Next();
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return new LiteralToken(CreateSpan(startIndex), isFloat ? LiteralKind.Float : LiteralKind.Integer, buffer);
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: Revisit this
|
|
||||||
foreach (var chain in Chians)
|
|
||||||
{
|
|
||||||
if (current != chain.Key[0]) continue;
|
|
||||||
|
|
||||||
for (var i = 1; i < chain.Key.Length; i++)
|
|
||||||
{
|
|
||||||
var c = Peek(i);
|
|
||||||
if (!c.HasValue || c.Value != chain.Key[i]) break;
|
|
||||||
|
|
||||||
if (i == chain.Key.Length - 1)
|
|
||||||
{
|
|
||||||
for (var j = 0; j <= i; j++)
|
|
||||||
{
|
|
||||||
Next();
|
|
||||||
}
|
|
||||||
|
|
||||||
return new SymbolToken(CreateSpan(startIndex), chain.Value);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (Chars.TryGetValue(current, out var charSymbol))
|
|
||||||
{
|
|
||||||
Next();
|
|
||||||
return new SymbolToken(CreateSpan(startIndex), charSymbol);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (current == '"')
|
|
||||||
{
|
|
||||||
Next();
|
|
||||||
var buffer = string.Empty;
|
|
||||||
|
|
||||||
while (true)
|
|
||||||
{
|
|
||||||
if (!Peek().TryGetValue(out var next))
|
|
||||||
{
|
|
||||||
throw new Exception("Unclosed string literal");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (next == '"')
|
|
||||||
{
|
|
||||||
Next();
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
buffer += next;
|
|
||||||
Next();
|
|
||||||
}
|
|
||||||
|
|
||||||
return new LiteralToken(CreateSpan(startIndex), LiteralKind.String, buffer);
|
|
||||||
}
|
|
||||||
|
|
||||||
throw new Exception($"Unknown character {current}");
|
|
||||||
}
|
|
||||||
|
|
||||||
private static SourceLocation CreateLocation(int index)
|
|
||||||
{
|
|
||||||
var line = 1;
|
|
||||||
var column = 1;
|
|
||||||
for (var i = 0; i < Math.Min(index, _sourceText.Content.Length - 1); i++)
|
|
||||||
{
|
|
||||||
if (_sourceText.Content[i] == '\n')
|
|
||||||
{
|
|
||||||
column = 1;
|
|
||||||
line += 1;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
column += 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return new SourceLocation(line, column);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static SourceSpan CreateSpan(int startIndex)
|
|
||||||
{
|
|
||||||
return new SourceSpan(_sourceText, CreateLocation(startIndex), CreateLocation(_index));
|
|
||||||
}
|
|
||||||
|
|
||||||
private static Optional<char> Peek(int offset = 0)
|
|
||||||
{
|
|
||||||
if (_index + offset < _sourceText.Content.Length)
|
|
||||||
{
|
|
||||||
return _sourceText.Content[_index + offset];
|
|
||||||
}
|
|
||||||
|
|
||||||
return Optional<char>.Empty();
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void Next()
|
|
||||||
{
|
|
||||||
_index++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,463 +0,0 @@
|
|||||||
using Common;
|
|
||||||
using Syntax.Parsing;
|
|
||||||
using Syntax.Parsing.Node;
|
|
||||||
using Syntax.Tokenization;
|
|
||||||
using Syntax.Typing.BoundNode;
|
|
||||||
using UnaryExpressionNode = Syntax.Parsing.Node.UnaryExpressionNode;
|
|
||||||
|
|
||||||
namespace Syntax.Typing;
|
|
||||||
|
|
||||||
// TODO: Currently anonymous function does not get a new scope
|
|
||||||
public static class Binder
|
|
||||||
{
|
|
||||||
private static SyntaxTree _syntaxTree = null!;
|
|
||||||
private static DefinitionTable _definitionTable = null!;
|
|
||||||
|
|
||||||
private static Dictionary<string, NubType> _variables = new();
|
|
||||||
private static NubType? _funcReturnType;
|
|
||||||
|
|
||||||
public static BoundSyntaxTree Bind(SyntaxTree syntaxTree, DefinitionTable definitionTable)
|
|
||||||
{
|
|
||||||
_syntaxTree = syntaxTree;
|
|
||||||
_definitionTable = definitionTable;
|
|
||||||
|
|
||||||
_variables = [];
|
|
||||||
_funcReturnType = null;
|
|
||||||
|
|
||||||
var definitions = new List<BoundDefinitionNode>();
|
|
||||||
|
|
||||||
foreach (var definition in syntaxTree.Definitions)
|
|
||||||
{
|
|
||||||
definitions.Add(BindDefinition(definition));
|
|
||||||
}
|
|
||||||
|
|
||||||
return new BoundSyntaxTree(syntaxTree.FilePath, syntaxTree.Namespace, definitions);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static BoundDefinitionNode BindDefinition(DefinitionNode node)
|
|
||||||
{
|
|
||||||
return node switch
|
|
||||||
{
|
|
||||||
ExternFuncDefinitionNode definition => BindExternFuncDefinition(definition),
|
|
||||||
LocalFuncDefinitionNode definition => BindLocalFuncDefinition(definition),
|
|
||||||
StructDefinitionNode definition => BindStruct(definition),
|
|
||||||
_ => throw new ArgumentOutOfRangeException(nameof(node))
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
private static BoundStructDefinitionNode BindStruct(StructDefinitionNode node)
|
|
||||||
{
|
|
||||||
var defOpt = _definitionTable.LookupStruct(node.Namespace, node.Name);
|
|
||||||
if (!defOpt.TryGetValue(out var definition))
|
|
||||||
{
|
|
||||||
throw new NotImplementedException("Diagnostics not implemented");
|
|
||||||
}
|
|
||||||
|
|
||||||
var structFields = new List<BoundStructField>();
|
|
||||||
|
|
||||||
foreach (var structField in node.Fields)
|
|
||||||
{
|
|
||||||
var value = Optional.Empty<BoundExpressionNode>();
|
|
||||||
|
|
||||||
if (structField.Value.HasValue)
|
|
||||||
{
|
|
||||||
var definitionField = definition.Fields.FirstOrDefault(f => f.Name == structField.Name);
|
|
||||||
if (definitionField == null)
|
|
||||||
{
|
|
||||||
throw new NotImplementedException("Diagnostics not implemented");
|
|
||||||
}
|
|
||||||
|
|
||||||
value = BindExpression(structField.Value.Value, definitionField.Type);
|
|
||||||
}
|
|
||||||
|
|
||||||
structFields.Add(new BoundStructField(structField.Name, structField.Type, value));
|
|
||||||
}
|
|
||||||
|
|
||||||
return new BoundStructDefinitionNode(node.Tokens, node.Documentation, node.Namespace, node.Name, structFields);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static BoundExternFuncDefinitionNode BindExternFuncDefinition(ExternFuncDefinitionNode node)
|
|
||||||
{
|
|
||||||
var parameters = new List<BoundFuncParameter>();
|
|
||||||
|
|
||||||
foreach (var parameter in node.Parameters)
|
|
||||||
{
|
|
||||||
parameters.Add(new BoundFuncParameter(parameter.Name, parameter.Type));
|
|
||||||
}
|
|
||||||
|
|
||||||
return new BoundExternFuncDefinitionNode(node.Tokens, node.Documentation, node.Namespace, node.Name, node.CallName, parameters, node.ReturnType);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static BoundLocalFuncDefinitionNode BindLocalFuncDefinition(LocalFuncDefinitionNode node)
|
|
||||||
{
|
|
||||||
_variables.Clear();
|
|
||||||
_funcReturnType = node.ReturnType;
|
|
||||||
|
|
||||||
var parameters = new List<BoundFuncParameter>();
|
|
||||||
|
|
||||||
foreach (var parameter in node.Parameters)
|
|
||||||
{
|
|
||||||
parameters.Add(new BoundFuncParameter(parameter.Name, parameter.Type));
|
|
||||||
_variables[parameter.Name] = parameter.Type;
|
|
||||||
}
|
|
||||||
|
|
||||||
var body = BindBlock(node.Body);
|
|
||||||
|
|
||||||
return new BoundLocalFuncDefinitionNode(node.Tokens, node.Documentation, node.Namespace, node.Name, parameters, body, node.ReturnType, node.Exported);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static BoundBlockNode BindBlock(BlockNode node)
|
|
||||||
{
|
|
||||||
var statements = new List<BoundStatementNode>();
|
|
||||||
|
|
||||||
foreach (var statement in node.Statements)
|
|
||||||
{
|
|
||||||
statements.Add(BindStatement(statement));
|
|
||||||
}
|
|
||||||
|
|
||||||
return new BoundBlockNode(node.Tokens, statements);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static BoundStatementNode BindStatement(StatementNode node)
|
|
||||||
{
|
|
||||||
return node switch
|
|
||||||
{
|
|
||||||
ArrayIndexAssignmentNode statement => BindArrayIndex(statement),
|
|
||||||
BreakNode statement => BindBreak(statement),
|
|
||||||
ContinueNode statement => BindContinue(statement),
|
|
||||||
DereferenceAssignmentNode statement => BindDereferenceAssignment(statement),
|
|
||||||
IfNode statement => BindIf(statement),
|
|
||||||
MemberAssignmentNode statement => BindMemberAssignment(statement),
|
|
||||||
ReturnNode statement => BindReturn(statement),
|
|
||||||
StatementExpressionNode statement => BindStatementExpression(statement),
|
|
||||||
VariableAssignmentNode statement => BindVariableAssignment(statement),
|
|
||||||
VariableDeclarationNode statement => BindVariableDeclaration(statement),
|
|
||||||
WhileNode statement => BindWhile(statement),
|
|
||||||
_ => throw new ArgumentOutOfRangeException(nameof(node))
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
private static BoundArrayIndexAssignmentNode BindArrayIndex(ArrayIndexAssignmentNode statement)
|
|
||||||
{
|
|
||||||
var boundArrayIndex = BindArrayIndexAccess(statement.ArrayIndexAccess);
|
|
||||||
var elementType = ((NubArrayType)boundArrayIndex.Type).ElementType;
|
|
||||||
return new BoundArrayIndexAssignmentNode(statement.Tokens, boundArrayIndex, BindExpression(statement.Value, elementType));
|
|
||||||
}
|
|
||||||
|
|
||||||
private static BoundBreakNode BindBreak(BreakNode statement)
|
|
||||||
{
|
|
||||||
return new BoundBreakNode(statement.Tokens);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static BoundContinueNode BindContinue(ContinueNode statement)
|
|
||||||
{
|
|
||||||
return new BoundContinueNode(statement.Tokens);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static BoundDereferenceAssignmentNode BindDereferenceAssignment(DereferenceAssignmentNode statement)
|
|
||||||
{
|
|
||||||
var boundDereference = BindDereference(statement.Dereference);
|
|
||||||
var dereferenceBaseType = ((NubPointerType)boundDereference.Type).BaseType;
|
|
||||||
return new BoundDereferenceAssignmentNode(statement.Tokens, boundDereference, BindExpression(statement.Value, dereferenceBaseType));
|
|
||||||
}
|
|
||||||
|
|
||||||
private static BoundIfNode BindIf(IfNode statement)
|
|
||||||
{
|
|
||||||
var elseStatement = Optional.Empty<Variant<BoundIfNode, BoundBlockNode>>();
|
|
||||||
|
|
||||||
if (statement.Else.HasValue)
|
|
||||||
{
|
|
||||||
elseStatement = statement.Else.Value.Match<Variant<BoundIfNode, BoundBlockNode>>
|
|
||||||
(
|
|
||||||
elseIf => BindIf(elseIf),
|
|
||||||
@else => BindBlock(@else)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return new BoundIfNode(statement.Tokens, BindExpression(statement.Condition, NubPrimitiveType.Bool), BindBlock(statement.Body), elseStatement);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static BoundMemberAssignmentNode BindMemberAssignment(MemberAssignmentNode statement)
|
|
||||||
{
|
|
||||||
var boundMemberAccess = BindMemberAccess(statement.MemberAccess);
|
|
||||||
var elementType = ((NubArrayType)boundMemberAccess.Type).ElementType;
|
|
||||||
return new BoundMemberAssignmentNode(statement.Tokens, boundMemberAccess, BindExpression(statement.Value, elementType));
|
|
||||||
}
|
|
||||||
|
|
||||||
private static BoundReturnNode BindReturn(ReturnNode statement)
|
|
||||||
{
|
|
||||||
var value = Optional.Empty<BoundExpressionNode>();
|
|
||||||
|
|
||||||
if (statement.Value.HasValue)
|
|
||||||
{
|
|
||||||
value = BindExpression(statement.Value.Value, _funcReturnType);
|
|
||||||
}
|
|
||||||
|
|
||||||
return new BoundReturnNode(statement.Tokens, value);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static BoundStatementExpressionNode BindStatementExpression(StatementExpressionNode statement)
|
|
||||||
{
|
|
||||||
return new BoundStatementExpressionNode(statement.Tokens, BindExpression(statement.Expression));
|
|
||||||
}
|
|
||||||
|
|
||||||
private static BoundVariableAssignmentNode BindVariableAssignment(VariableAssignmentNode statement)
|
|
||||||
{
|
|
||||||
var variableType = _variables[statement.Identifier.Name];
|
|
||||||
return new BoundVariableAssignmentNode(statement.Tokens, BindIdentifier(statement.Identifier), BindExpression(statement.Value, variableType));
|
|
||||||
}
|
|
||||||
|
|
||||||
private static BoundVariableDeclarationNode BindVariableDeclaration(VariableDeclarationNode statement)
|
|
||||||
{
|
|
||||||
_variables[statement.Name] = statement.Type;
|
|
||||||
return new BoundVariableDeclarationNode(statement.Tokens, statement.Name, statement.Type);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static BoundWhileNode BindWhile(WhileNode statement)
|
|
||||||
{
|
|
||||||
return new BoundWhileNode(statement.Tokens, BindExpression(statement.Condition, NubPrimitiveType.Bool), BindBlock(statement.Body));
|
|
||||||
}
|
|
||||||
|
|
||||||
private static BoundExpressionNode BindExpression(ExpressionNode node, NubType? expectedType = null)
|
|
||||||
{
|
|
||||||
return node switch
|
|
||||||
{
|
|
||||||
AddressOfNode expression => BindAddressOf(expression),
|
|
||||||
AnonymousFuncNode expression => BindAnonymousFunc(expression),
|
|
||||||
ArrayIndexAccessNode expression => BindArrayIndexAccess(expression),
|
|
||||||
ArrayInitializerNode expression => BindArrayInitializer(expression),
|
|
||||||
BinaryExpressionNode expression => BindBinaryExpression(expression),
|
|
||||||
DereferenceNode expression => BindDereference(expression),
|
|
||||||
FixedArrayInitializerNode expression => BindFixedArrayInitializer(expression),
|
|
||||||
FuncCallNode expression => BindFuncCall(expression),
|
|
||||||
IdentifierNode expression => BindIdentifier(expression),
|
|
||||||
LiteralNode expression => BindLiteral(expression, expectedType),
|
|
||||||
MemberAccessNode expression => BindMemberAccess(expression),
|
|
||||||
StructInitializerNode expression => BindStructInitializer(expression),
|
|
||||||
UnaryExpressionNode expression => BindUnaryExpression(expression),
|
|
||||||
_ => throw new ArgumentOutOfRangeException(nameof(node))
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
private static BoundAddressOfNode BindAddressOf(AddressOfNode expression)
|
|
||||||
{
|
|
||||||
var inner = (BoundLValueNode)BindExpression(expression.Expression);
|
|
||||||
return new BoundAddressOfNode(expression.Tokens, new NubPointerType(inner.Type), inner);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static BoundAnonymousFuncNode BindAnonymousFunc(AnonymousFuncNode expression)
|
|
||||||
{
|
|
||||||
var parameters = new List<BoundFuncParameter>();
|
|
||||||
var parameterTypes = new List<NubType>();
|
|
||||||
|
|
||||||
foreach (var parameter in expression.Parameters)
|
|
||||||
{
|
|
||||||
var boundParameter = new BoundFuncParameter(parameter.Name, parameter.Type);
|
|
||||||
parameters.Add(boundParameter);
|
|
||||||
parameterTypes.Add(boundParameter.Type);
|
|
||||||
}
|
|
||||||
|
|
||||||
var body = BindBlock(expression.Body);
|
|
||||||
|
|
||||||
return new BoundAnonymousFuncNode(expression.Tokens, new NubFuncType(expression.ReturnType, parameterTypes), parameters, body, expression.ReturnType);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static BoundArrayIndexAccessNode BindArrayIndexAccess(ArrayIndexAccessNode expression)
|
|
||||||
{
|
|
||||||
var boundArray = BindExpression(expression.Array);
|
|
||||||
var elementType = ((NubArrayType)boundArray.Type).ElementType;
|
|
||||||
return new BoundArrayIndexAccessNode(expression.Tokens, elementType, boundArray, BindExpression(expression.Index, NubPrimitiveType.U64));
|
|
||||||
}
|
|
||||||
|
|
||||||
private static BoundArrayInitializerNode BindArrayInitializer(ArrayInitializerNode expression)
|
|
||||||
{
|
|
||||||
return new BoundArrayInitializerNode(expression.Tokens, new NubArrayType(expression.ElementType), BindExpression(expression.Capacity, NubPrimitiveType.U64),
|
|
||||||
expression.ElementType);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static BoundBinaryExpressionNode BindBinaryExpression(BinaryExpressionNode expression)
|
|
||||||
{
|
|
||||||
var boundLeft = BindExpression(expression.Left);
|
|
||||||
var boundRight = BindExpression(expression.Right, boundLeft.Type);
|
|
||||||
return new BoundBinaryExpressionNode(expression.Tokens, boundLeft.Type, boundLeft, expression.Operator, boundRight);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static BoundDereferenceNode BindDereference(DereferenceNode expression)
|
|
||||||
{
|
|
||||||
var boundExpression = BindExpression(expression.Expression);
|
|
||||||
var dereferencedType = ((NubPointerType)boundExpression.Type).BaseType;
|
|
||||||
return new BoundDereferenceNode(expression.Tokens, dereferencedType, boundExpression);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static BoundFixedArrayInitializerNode BindFixedArrayInitializer(FixedArrayInitializerNode expression)
|
|
||||||
{
|
|
||||||
return new BoundFixedArrayInitializerNode(expression.Tokens, new NubArrayType(expression.ElementType), expression.ElementType, expression.Capacity);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static BoundFuncCallNode BindFuncCall(FuncCallNode expression)
|
|
||||||
{
|
|
||||||
var boundExpression = BindExpression(expression.Expression);
|
|
||||||
|
|
||||||
var funcType = (NubFuncType)boundExpression.Type;
|
|
||||||
var returnType = ((NubFuncType)boundExpression.Type).ReturnType;
|
|
||||||
|
|
||||||
var parameters = new List<BoundExpressionNode>();
|
|
||||||
|
|
||||||
foreach (var (i, parameter) in expression.Parameters.Index())
|
|
||||||
{
|
|
||||||
if (i >= funcType.Parameters.Count)
|
|
||||||
{
|
|
||||||
throw new NotImplementedException("Diagnostics not implemented");
|
|
||||||
}
|
|
||||||
|
|
||||||
var expectedType = funcType.Parameters[i];
|
|
||||||
|
|
||||||
parameters.Add(BindExpression(parameter, expectedType));
|
|
||||||
}
|
|
||||||
|
|
||||||
return new BoundFuncCallNode(expression.Tokens, returnType, boundExpression, parameters);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static BoundIdentifierNode BindIdentifier(IdentifierNode expression)
|
|
||||||
{
|
|
||||||
NubType? type = null;
|
|
||||||
|
|
||||||
var definition = _definitionTable.LookupFunc(expression.Namespace.Or(_syntaxTree.Namespace), expression.Name);
|
|
||||||
if (definition.HasValue)
|
|
||||||
{
|
|
||||||
type = new NubFuncType(definition.Value.ReturnType, definition.Value.Parameters.Select(p => p.Type).ToList());
|
|
||||||
}
|
|
||||||
|
|
||||||
if (type == null && !expression.Namespace.HasValue)
|
|
||||||
{
|
|
||||||
type = _variables[expression.Name];
|
|
||||||
}
|
|
||||||
|
|
||||||
if (type == null)
|
|
||||||
{
|
|
||||||
throw new NotImplementedException("Diagnostics not implemented");
|
|
||||||
}
|
|
||||||
|
|
||||||
return new BoundIdentifierNode(expression.Tokens, type, expression.Namespace, expression.Name);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static BoundLiteralNode BindLiteral(LiteralNode expression, NubType? expectedType = null)
|
|
||||||
{
|
|
||||||
var type = expectedType ?? expression.Kind switch
|
|
||||||
{
|
|
||||||
LiteralKind.Integer => NubPrimitiveType.I64,
|
|
||||||
LiteralKind.Float => NubPrimitiveType.F64,
|
|
||||||
LiteralKind.String => new NubStringType(),
|
|
||||||
LiteralKind.Bool => NubPrimitiveType.Bool,
|
|
||||||
_ => throw new ArgumentOutOfRangeException()
|
|
||||||
};
|
|
||||||
|
|
||||||
return new BoundLiteralNode(expression.Tokens, type, expression.Literal, expression.Kind);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static BoundMemberAccessNode BindMemberAccess(MemberAccessNode expression)
|
|
||||||
{
|
|
||||||
var boundExpression = BindExpression(expression.Expression);
|
|
||||||
|
|
||||||
NubType? type = null;
|
|
||||||
|
|
||||||
switch (boundExpression.Type)
|
|
||||||
{
|
|
||||||
case NubArrayType:
|
|
||||||
case NubStringType:
|
|
||||||
case NubCStringType:
|
|
||||||
{
|
|
||||||
if (expression.Member == "count")
|
|
||||||
{
|
|
||||||
type = NubPrimitiveType.U64;
|
|
||||||
}
|
|
||||||
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case NubStructType structType:
|
|
||||||
{
|
|
||||||
var defOpt = _definitionTable.LookupStruct(structType.Namespace, structType.Name);
|
|
||||||
if (!defOpt.TryGetValue(out var definition))
|
|
||||||
{
|
|
||||||
throw new NotImplementedException("Diagnostics not implemented");
|
|
||||||
}
|
|
||||||
|
|
||||||
var field = definition.Fields.FirstOrDefault(f => f.Name == expression.Member);
|
|
||||||
if (field == null)
|
|
||||||
{
|
|
||||||
throw new NotImplementedException("Diagnostics not implemented");
|
|
||||||
}
|
|
||||||
|
|
||||||
type = field.Type;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (type == null)
|
|
||||||
{
|
|
||||||
throw new NotImplementedException("Diagnostics not implemented");
|
|
||||||
}
|
|
||||||
|
|
||||||
return new BoundMemberAccessNode(expression.Tokens, type, boundExpression, expression.Member);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static BoundStructInitializerNode BindStructInitializer(StructInitializerNode expression)
|
|
||||||
{
|
|
||||||
var defOpt = _definitionTable.LookupStruct(expression.StructType.Namespace, expression.StructType.Name);
|
|
||||||
if (!defOpt.TryGetValue(out var definition))
|
|
||||||
{
|
|
||||||
throw new NotImplementedException("Diagnostics not implemented");
|
|
||||||
}
|
|
||||||
|
|
||||||
var initializers = new Dictionary<string, BoundExpressionNode>();
|
|
||||||
|
|
||||||
foreach (var (member, initializer) in expression.Initializers)
|
|
||||||
{
|
|
||||||
var definitionField = definition.Fields.FirstOrDefault(x => x.Name == member);
|
|
||||||
if (definitionField == null)
|
|
||||||
{
|
|
||||||
throw new NotImplementedException("Diagnostics not implemented");
|
|
||||||
}
|
|
||||||
|
|
||||||
initializers[member] = BindExpression(initializer, definitionField.Type);
|
|
||||||
}
|
|
||||||
|
|
||||||
return new BoundStructInitializerNode(expression.Tokens, expression.StructType, expression.StructType, initializers);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static BoundUnaryExpressionNode BindUnaryExpression(UnaryExpressionNode expression)
|
|
||||||
{
|
|
||||||
var boundOperand = BindExpression(expression.Operand);
|
|
||||||
|
|
||||||
NubType? type = null;
|
|
||||||
|
|
||||||
switch (expression.Operator)
|
|
||||||
{
|
|
||||||
case UnaryExpressionOperator.Negate:
|
|
||||||
{
|
|
||||||
boundOperand = BindExpression(expression.Operand, NubPrimitiveType.I64);
|
|
||||||
|
|
||||||
if (boundOperand.Type.IsNumber)
|
|
||||||
{
|
|
||||||
type = boundOperand.Type;
|
|
||||||
}
|
|
||||||
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case UnaryExpressionOperator.Invert:
|
|
||||||
{
|
|
||||||
boundOperand = BindExpression(expression.Operand, NubPrimitiveType.Bool);
|
|
||||||
|
|
||||||
type = new NubPrimitiveType(PrimitiveTypeKind.Bool);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (type == null)
|
|
||||||
{
|
|
||||||
throw new NotImplementedException("Diagnostics not implemented");
|
|
||||||
}
|
|
||||||
|
|
||||||
return new BoundUnaryExpressionNode(expression.Tokens, type, expression.Operator, boundOperand);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,14 +0,0 @@
|
|||||||
using Common;
|
|
||||||
using Syntax.Tokenization;
|
|
||||||
|
|
||||||
namespace Syntax.Typing.BoundNode;
|
|
||||||
|
|
||||||
public abstract record BoundDefinitionNode(IEnumerable<Token> Tokens, Optional<string> Documentation, string Namespace) : BoundNode(Tokens);
|
|
||||||
|
|
||||||
public record BoundFuncParameter(string Name, NubType Type);
|
|
||||||
public abstract record BoundFuncDefinition(IEnumerable<Token> Tokens, Optional<string> Documentation, string Namespace, string Name, List<BoundFuncParameter> Parameters, NubType ReturnType) : BoundDefinitionNode(Tokens, Documentation, Namespace);
|
|
||||||
public record BoundLocalFuncDefinitionNode(IEnumerable<Token> Tokens, Optional<string> Documentation, string Namespace, string Name, List<BoundFuncParameter> Parameters, BoundBlockNode Body, NubType ReturnType, bool Exported) : BoundFuncDefinition(Tokens, Documentation, Namespace, Name, Parameters, ReturnType);
|
|
||||||
public record BoundExternFuncDefinitionNode(IEnumerable<Token> Tokens, Optional<string> Documentation, string Namespace, string Name, string CallName, List<BoundFuncParameter> Parameters, NubType ReturnType) : BoundFuncDefinition(Tokens, Documentation, Namespace, Name, Parameters, ReturnType);
|
|
||||||
|
|
||||||
public record BoundStructField(string Name, NubType Type, Optional<BoundExpressionNode> Value);
|
|
||||||
public record BoundStructDefinitionNode(IEnumerable<Token> Tokens, Optional<string> Documentation, string Namespace, string Name, List<BoundStructField> Fields) : BoundDefinitionNode(Tokens, Documentation, Namespace);
|
|
||||||
@@ -1,21 +0,0 @@
|
|||||||
using Common;
|
|
||||||
using Syntax.Parsing.Node;
|
|
||||||
using Syntax.Tokenization;
|
|
||||||
|
|
||||||
namespace Syntax.Typing.BoundNode;
|
|
||||||
|
|
||||||
public abstract record BoundExpressionNode(IEnumerable<Token> Tokens, NubType Type) : BoundNode(Tokens);
|
|
||||||
public abstract record BoundLValueNode(IEnumerable<Token> Tokens, NubType Type) : BoundExpressionNode(Tokens, Type);
|
|
||||||
public record BoundBinaryExpressionNode(IEnumerable<Token> Tokens, NubType Type, BoundExpressionNode Left, BinaryExpressionOperator Operator, BoundExpressionNode Right) : BoundExpressionNode(Tokens, Type);
|
|
||||||
public record BoundUnaryExpressionNode(IEnumerable<Token> Tokens, NubType Type, UnaryExpressionOperator Operator, BoundExpressionNode Operand) : BoundExpressionNode(Tokens, Type);
|
|
||||||
public record BoundFuncCallNode(IEnumerable<Token> Tokens, NubType Type, BoundExpressionNode Expression, List<BoundExpressionNode> Parameters) : BoundExpressionNode(Tokens, Type);
|
|
||||||
public record BoundIdentifierNode(IEnumerable<Token> Tokens, NubType Type, Optional<string> Namespace, string Name) : BoundLValueNode(Tokens, Type);
|
|
||||||
public record BoundArrayInitializerNode(IEnumerable<Token> Tokens, NubType Type, BoundExpressionNode Capacity, NubType ElementType) : BoundExpressionNode(Tokens, Type);
|
|
||||||
public record BoundArrayIndexAccessNode(IEnumerable<Token> Tokens, NubType Type, BoundExpressionNode Array, BoundExpressionNode Index) : BoundLValueNode(Tokens, Type);
|
|
||||||
public record BoundAnonymousFuncNode(IEnumerable<Token> Tokens, NubType Type, List<BoundFuncParameter> Parameters, BoundBlockNode Body, NubType ReturnType) : BoundExpressionNode(Tokens, Type);
|
|
||||||
public record BoundAddressOfNode(IEnumerable<Token> Tokens, NubType Type, BoundLValueNode Expression) : BoundExpressionNode(Tokens, Type);
|
|
||||||
public record BoundFixedArrayInitializerNode(IEnumerable<Token> Tokens, NubType Type, NubType ElementType, int Capacity) : BoundExpressionNode(Tokens, Type);
|
|
||||||
public record BoundLiteralNode(IEnumerable<Token> Tokens, NubType Type, string Literal, LiteralKind Kind) : BoundExpressionNode(Tokens, Type);
|
|
||||||
public record BoundMemberAccessNode(IEnumerable<Token> Tokens, NubType Type, BoundExpressionNode Expression, string Member) : BoundExpressionNode(Tokens, Type);
|
|
||||||
public record BoundStructInitializerNode(IEnumerable<Token> Tokens, NubType Type, NubStructType StructType, Dictionary<string, BoundExpressionNode> Initializers) : BoundExpressionNode(Tokens, Type);
|
|
||||||
public record BoundDereferenceNode(IEnumerable<Token> Tokens, NubType Type, BoundExpressionNode Expression) : BoundLValueNode(Tokens, Type);
|
|
||||||
@@ -1,6 +0,0 @@
|
|||||||
using Syntax.Tokenization;
|
|
||||||
|
|
||||||
namespace Syntax.Typing.BoundNode;
|
|
||||||
|
|
||||||
public abstract record BoundNode(IEnumerable<Token> Tokens);
|
|
||||||
public record BoundBlockNode(IEnumerable<Token> Tokens, List<BoundStatementNode> Statements) : BoundNode(Tokens);
|
|
||||||
@@ -1,17 +0,0 @@
|
|||||||
using Common;
|
|
||||||
using Syntax.Tokenization;
|
|
||||||
|
|
||||||
namespace Syntax.Typing.BoundNode;
|
|
||||||
|
|
||||||
public record BoundStatementNode(IEnumerable<Token> Tokens) : BoundNode(Tokens);
|
|
||||||
public record BoundStatementExpressionNode(IEnumerable<Token> Tokens, BoundExpressionNode Expression) : BoundStatementNode(Tokens);
|
|
||||||
public record BoundReturnNode(IEnumerable<Token> Tokens, Optional<BoundExpressionNode> Value) : BoundStatementNode(Tokens);
|
|
||||||
public record BoundMemberAssignmentNode(IEnumerable<Token> Tokens, BoundMemberAccessNode MemberAccess, BoundExpressionNode Value) : BoundStatementNode(Tokens);
|
|
||||||
public record BoundIfNode(IEnumerable<Token> Tokens, BoundExpressionNode Condition, BoundBlockNode Body, Optional<Variant<BoundIfNode, BoundBlockNode>> Else) : BoundStatementNode(Tokens);
|
|
||||||
public record BoundDereferenceAssignmentNode(IEnumerable<Token> Tokens, BoundDereferenceNode Dereference, BoundExpressionNode Value) : BoundStatementNode(Tokens);
|
|
||||||
public record BoundVariableAssignmentNode(IEnumerable<Token> Tokens, BoundIdentifierNode Identifier, BoundExpressionNode Value) : BoundStatementNode(Tokens);
|
|
||||||
public record BoundVariableDeclarationNode(IEnumerable<Token> Tokens, string Name, NubType Type) : BoundStatementNode(Tokens);
|
|
||||||
public record BoundContinueNode(IEnumerable<Token> Tokens) : BoundStatementNode(Tokens);
|
|
||||||
public record BoundBreakNode(IEnumerable<Token> Tokens) : BoundStatementNode(Tokens);
|
|
||||||
public record BoundArrayIndexAssignmentNode(IEnumerable<Token> Tokens, BoundArrayIndexAccessNode ArrayIndexAccess, BoundExpressionNode Value) : BoundStatementNode(Tokens);
|
|
||||||
public record BoundWhileNode(IEnumerable<Token> Tokens, BoundExpressionNode Condition, BoundBlockNode Body) : BoundStatementNode(Tokens);
|
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
using Syntax.Typing.BoundNode;
|
|
||||||
|
|
||||||
namespace Syntax.Typing;
|
|
||||||
|
|
||||||
public record BoundSyntaxTree(string FilePath, string Namespace, List<BoundDefinitionNode> Definitions);
|
|
||||||
@@ -1,303 +0,0 @@
|
|||||||
using System.Diagnostics.CodeAnalysis;
|
|
||||||
|
|
||||||
namespace Syntax.Typing;
|
|
||||||
|
|
||||||
public abstract class NubType
|
|
||||||
{
|
|
||||||
public bool IsInteger => this is NubPrimitiveType
|
|
||||||
{
|
|
||||||
Kind: PrimitiveTypeKind.I8
|
|
||||||
or PrimitiveTypeKind.I16
|
|
||||||
or PrimitiveTypeKind.I32
|
|
||||||
or PrimitiveTypeKind.I64
|
|
||||||
or PrimitiveTypeKind.U8
|
|
||||||
or PrimitiveTypeKind.U16
|
|
||||||
or PrimitiveTypeKind.U32
|
|
||||||
or PrimitiveTypeKind.U64
|
|
||||||
};
|
|
||||||
|
|
||||||
public bool IsFloat32 => this is NubPrimitiveType
|
|
||||||
{
|
|
||||||
Kind: PrimitiveTypeKind.F32
|
|
||||||
};
|
|
||||||
|
|
||||||
public bool IsFloat64 => this is NubPrimitiveType
|
|
||||||
{
|
|
||||||
Kind: PrimitiveTypeKind.F64
|
|
||||||
};
|
|
||||||
|
|
||||||
public bool IsNumber => IsFloat32 || IsFloat64 || IsInteger;
|
|
||||||
|
|
||||||
public bool IsVoid => this is NubVoidType;
|
|
||||||
|
|
||||||
public bool IsString => this is NubStringType;
|
|
||||||
|
|
||||||
public bool IsCString => this is NubCStringType;
|
|
||||||
|
|
||||||
public bool IsBool => this is NubPrimitiveType
|
|
||||||
{
|
|
||||||
Kind: PrimitiveTypeKind.Bool
|
|
||||||
};
|
|
||||||
|
|
||||||
public abstract override bool Equals(object? obj);
|
|
||||||
public abstract override int GetHashCode();
|
|
||||||
public abstract override string ToString();
|
|
||||||
}
|
|
||||||
|
|
||||||
public class NubCStringType : NubType
|
|
||||||
{
|
|
||||||
public override bool Equals(object? obj)
|
|
||||||
{
|
|
||||||
return obj is NubCStringType;
|
|
||||||
}
|
|
||||||
|
|
||||||
public override int GetHashCode()
|
|
||||||
{
|
|
||||||
return "cstring".GetHashCode();
|
|
||||||
}
|
|
||||||
|
|
||||||
public override string ToString()
|
|
||||||
{
|
|
||||||
return "cstring";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public class NubStringType : NubType
|
|
||||||
{
|
|
||||||
public override bool Equals(object? obj)
|
|
||||||
{
|
|
||||||
return obj is NubStringType;
|
|
||||||
}
|
|
||||||
|
|
||||||
public override int GetHashCode()
|
|
||||||
{
|
|
||||||
return "string".GetHashCode();
|
|
||||||
}
|
|
||||||
|
|
||||||
public override string ToString()
|
|
||||||
{
|
|
||||||
return "string";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public class NubFuncType(NubType returnType, List<NubType> parameters) : NubType
|
|
||||||
{
|
|
||||||
public NubType ReturnType { get; } = returnType;
|
|
||||||
public List<NubType> Parameters { get; } = parameters;
|
|
||||||
|
|
||||||
public override bool Equals(object? obj)
|
|
||||||
{
|
|
||||||
return obj is NubFuncType other && other.ReturnType.Equals(ReturnType) && other.Parameters.SequenceEqual(Parameters);
|
|
||||||
}
|
|
||||||
|
|
||||||
public override int GetHashCode()
|
|
||||||
{
|
|
||||||
return HashCode.Combine(ReturnType, Parameters);
|
|
||||||
}
|
|
||||||
|
|
||||||
public override string ToString()
|
|
||||||
{
|
|
||||||
return $"func({string.Join(", ", Parameters)}): {ReturnType}";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public class NubStructType(string @namespace, string name) : NubType
|
|
||||||
{
|
|
||||||
public string Namespace { get; } = @namespace;
|
|
||||||
public string Name { get; } = name;
|
|
||||||
|
|
||||||
public override bool Equals(object? obj)
|
|
||||||
{
|
|
||||||
return obj is NubStructType other && other.Namespace == Namespace && other.Name == Name;
|
|
||||||
}
|
|
||||||
|
|
||||||
public override int GetHashCode()
|
|
||||||
{
|
|
||||||
return HashCode.Combine(Namespace, Name);
|
|
||||||
}
|
|
||||||
|
|
||||||
public override string ToString()
|
|
||||||
{
|
|
||||||
return $"{Namespace}::{Name}";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public class NubPointerType(NubType baseType) : NubType
|
|
||||||
{
|
|
||||||
public NubType BaseType { get; } = baseType;
|
|
||||||
|
|
||||||
public override bool Equals(object? obj)
|
|
||||||
{
|
|
||||||
return obj is NubPointerType other && BaseType.Equals(other.BaseType);
|
|
||||||
}
|
|
||||||
|
|
||||||
public override int GetHashCode()
|
|
||||||
{
|
|
||||||
return HashCode.Combine(BaseType);
|
|
||||||
}
|
|
||||||
|
|
||||||
public override string ToString()
|
|
||||||
{
|
|
||||||
return "^" + BaseType;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public class NubArrayType(NubType elementType) : NubType
|
|
||||||
{
|
|
||||||
public NubType ElementType { get; } = elementType;
|
|
||||||
|
|
||||||
public override bool Equals(object? obj)
|
|
||||||
{
|
|
||||||
if (obj is NubArrayType other)
|
|
||||||
{
|
|
||||||
return ElementType.Equals(other.ElementType);
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
public override int GetHashCode()
|
|
||||||
{
|
|
||||||
return HashCode.Combine(ElementType);
|
|
||||||
}
|
|
||||||
|
|
||||||
public override string ToString()
|
|
||||||
{
|
|
||||||
return "[]" + ElementType;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public class NubFixedArrayType(NubType elementType, int capacity) : NubType
|
|
||||||
{
|
|
||||||
public NubType ElementType { get; } = elementType;
|
|
||||||
public int Capacity { get; } = capacity;
|
|
||||||
|
|
||||||
public override string ToString() => $"[{Capacity}]{ElementType}";
|
|
||||||
|
|
||||||
public override bool Equals(object? obj)
|
|
||||||
{
|
|
||||||
return obj is NubFixedArrayType other && ElementType.Equals(other.ElementType) && Capacity == other.Capacity;
|
|
||||||
}
|
|
||||||
|
|
||||||
public override int GetHashCode()
|
|
||||||
{
|
|
||||||
return HashCode.Combine(ElementType, Capacity);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public class NubAnyType : NubType
|
|
||||||
{
|
|
||||||
public override string ToString() => "any";
|
|
||||||
|
|
||||||
public override bool Equals(object? obj)
|
|
||||||
{
|
|
||||||
return obj is NubAnyType;
|
|
||||||
}
|
|
||||||
|
|
||||||
public override int GetHashCode()
|
|
||||||
{
|
|
||||||
return "any".GetHashCode();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public class NubVoidType : NubType
|
|
||||||
{
|
|
||||||
public override string ToString() => "void";
|
|
||||||
|
|
||||||
public override bool Equals(object? obj)
|
|
||||||
{
|
|
||||||
return obj is NubVoidType;
|
|
||||||
}
|
|
||||||
|
|
||||||
public override int GetHashCode()
|
|
||||||
{
|
|
||||||
return GetType().GetHashCode();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public class NubPrimitiveType(PrimitiveTypeKind kind) : NubType
|
|
||||||
{
|
|
||||||
public PrimitiveTypeKind Kind { get; } = kind;
|
|
||||||
|
|
||||||
public static NubPrimitiveType I64 => new(PrimitiveTypeKind.I64);
|
|
||||||
public static NubPrimitiveType I32 => new(PrimitiveTypeKind.I32);
|
|
||||||
public static NubPrimitiveType I16 => new(PrimitiveTypeKind.I16);
|
|
||||||
public static NubPrimitiveType I8 => new(PrimitiveTypeKind.I8);
|
|
||||||
|
|
||||||
public static NubPrimitiveType U64 => new(PrimitiveTypeKind.U64);
|
|
||||||
public static NubPrimitiveType U32 => new(PrimitiveTypeKind.U32);
|
|
||||||
public static NubPrimitiveType U16 => new(PrimitiveTypeKind.U16);
|
|
||||||
public static NubPrimitiveType U8 => new(PrimitiveTypeKind.U8);
|
|
||||||
|
|
||||||
public static NubPrimitiveType F64 => new(PrimitiveTypeKind.F64);
|
|
||||||
public static NubPrimitiveType F32 => new(PrimitiveTypeKind.F32);
|
|
||||||
|
|
||||||
public static NubPrimitiveType Bool => new(PrimitiveTypeKind.Bool);
|
|
||||||
|
|
||||||
public static bool TryParse(string s, [NotNullWhen(true)] out PrimitiveTypeKind? kind)
|
|
||||||
{
|
|
||||||
kind = s switch
|
|
||||||
{
|
|
||||||
"i64" => PrimitiveTypeKind.I64,
|
|
||||||
"i32" => PrimitiveTypeKind.I32,
|
|
||||||
"i16" => PrimitiveTypeKind.I16,
|
|
||||||
"i8" => PrimitiveTypeKind.I8,
|
|
||||||
"u64" => PrimitiveTypeKind.U64,
|
|
||||||
"u32" => PrimitiveTypeKind.U32,
|
|
||||||
"u16" => PrimitiveTypeKind.U16,
|
|
||||||
"u8" => PrimitiveTypeKind.U8,
|
|
||||||
"f64" => PrimitiveTypeKind.F64,
|
|
||||||
"f32" => PrimitiveTypeKind.F32,
|
|
||||||
"bool" => PrimitiveTypeKind.Bool,
|
|
||||||
_ => null
|
|
||||||
};
|
|
||||||
|
|
||||||
return kind != null;
|
|
||||||
}
|
|
||||||
|
|
||||||
public override bool Equals(object? obj)
|
|
||||||
{
|
|
||||||
return obj is NubPrimitiveType other && other.Kind == Kind;
|
|
||||||
}
|
|
||||||
|
|
||||||
public override int GetHashCode()
|
|
||||||
{
|
|
||||||
return HashCode.Combine(Kind);
|
|
||||||
}
|
|
||||||
|
|
||||||
public override string ToString()
|
|
||||||
{
|
|
||||||
return Kind switch
|
|
||||||
{
|
|
||||||
PrimitiveTypeKind.I8 => "i8",
|
|
||||||
PrimitiveTypeKind.I16 => "i16",
|
|
||||||
PrimitiveTypeKind.I32 => "i32",
|
|
||||||
PrimitiveTypeKind.I64 => "i64",
|
|
||||||
|
|
||||||
PrimitiveTypeKind.U8 => "u8",
|
|
||||||
PrimitiveTypeKind.U16 => "u16",
|
|
||||||
PrimitiveTypeKind.U32 => "u32",
|
|
||||||
PrimitiveTypeKind.U64 => "u64",
|
|
||||||
|
|
||||||
PrimitiveTypeKind.F32 => "f32",
|
|
||||||
PrimitiveTypeKind.F64 => "f64",
|
|
||||||
|
|
||||||
PrimitiveTypeKind.Bool => "bool",
|
|
||||||
_ => throw new ArgumentOutOfRangeException(nameof(kind), Kind, null)
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public enum PrimitiveTypeKind
|
|
||||||
{
|
|
||||||
I64,
|
|
||||||
I32,
|
|
||||||
I16,
|
|
||||||
I8,
|
|
||||||
U64,
|
|
||||||
U32,
|
|
||||||
U16,
|
|
||||||
U8,
|
|
||||||
F64,
|
|
||||||
F32,
|
|
||||||
Bool
|
|
||||||
}
|
|
||||||
@@ -1,633 +0,0 @@
|
|||||||
// using System.Diagnostics;
|
|
||||||
// using Syntax.Diagnostics;
|
|
||||||
// using Syntax.Parsing;
|
|
||||||
// using Syntax.Parsing.Node;
|
|
||||||
// using Syntax.Tokenization;
|
|
||||||
//
|
|
||||||
// namespace Syntax.Typing;
|
|
||||||
//
|
|
||||||
// public static class TypeChecker
|
|
||||||
// {
|
|
||||||
// private static SyntaxTree _syntaxTree = null!;
|
|
||||||
// private static DefinitionTable _definitionTable = null!;
|
|
||||||
//
|
|
||||||
// private static Dictionary<string, NubType> _variables = new();
|
|
||||||
// private static List<Diagnostic> _diagnostics = [];
|
|
||||||
// private static NubType? _currentFunctionReturnType;
|
|
||||||
// private static Queue<AnonymousFuncNode> _anonymousFunctions = [];
|
|
||||||
//
|
|
||||||
// public static void Check(SyntaxTree syntaxTree, DefinitionTable definitionTable, out IEnumerable<Diagnostic> diagnostics)
|
|
||||||
// {
|
|
||||||
// _syntaxTree = syntaxTree;
|
|
||||||
// _definitionTable = definitionTable;
|
|
||||||
//
|
|
||||||
// _variables = new Dictionary<string, NubType>();
|
|
||||||
// _diagnostics = [];
|
|
||||||
// _currentFunctionReturnType = null;
|
|
||||||
// _anonymousFunctions = [];
|
|
||||||
//
|
|
||||||
// foreach (var structDef in syntaxTree.Definitions.OfType<StructDefinitionNode>())
|
|
||||||
// {
|
|
||||||
// CheckStructDef(structDef);
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// foreach (var funcDef in syntaxTree.Definitions.OfType<LocalFuncDefinitionNode>())
|
|
||||||
// {
|
|
||||||
// CheckFuncDef(funcDef.Parameters, funcDef.Body, funcDef.ReturnType);
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// while (_anonymousFunctions.TryDequeue(out var func))
|
|
||||||
// {
|
|
||||||
// CheckFuncDef(func.Parameters, func.Body, func.ReturnType);
|
|
||||||
//
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// diagnostics = _diagnostics;
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// private static void CheckStructDef(StructDefinitionNode structDef)
|
|
||||||
// {
|
|
||||||
// var fields = new Dictionary<string, NubType>();
|
|
||||||
// foreach (var field in structDef.Fields)
|
|
||||||
// {
|
|
||||||
// if (fields.ContainsKey(field.Name))
|
|
||||||
// {
|
|
||||||
// ReportError($"Duplicate field '{field.Name}' in struct '{structDef.Name}'", structDef);
|
|
||||||
// continue;
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// if (field.Value.HasValue)
|
|
||||||
// {
|
|
||||||
// var fieldType = CheckExpression(field.Value.Value, field.Type);
|
|
||||||
// if (fieldType != null && !fieldType.Equals(field.Type))
|
|
||||||
// {
|
|
||||||
// ReportError("Default field initializer does not match the defined type", field.Value.Value);
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// fields[field.Name] = field.Type;
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// private static void CheckFuncDef(List<FuncParameter> parameters, BlockNode body, NubType returnType)
|
|
||||||
// {
|
|
||||||
// _variables.Clear();
|
|
||||||
// _currentFunctionReturnType = returnType;
|
|
||||||
//
|
|
||||||
// foreach (var param in parameters)
|
|
||||||
// {
|
|
||||||
// _variables[param.Name] = param.Type;
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// CheckBlock(body);
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// private static void CheckBlock(BlockNode block)
|
|
||||||
// {
|
|
||||||
// foreach (var statement in block.Statements)
|
|
||||||
// {
|
|
||||||
// CheckStatement(statement);
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// private static void CheckStatement(StatementNode statement)
|
|
||||||
// {
|
|
||||||
// switch (statement)
|
|
||||||
// {
|
|
||||||
// case ArrayIndexAssignmentNode arrayIndexAssignment:
|
|
||||||
// CheckArrayIndexAssignment(arrayIndexAssignment);
|
|
||||||
// break;
|
|
||||||
// case VariableAssignmentNode variableAssignment:
|
|
||||||
// CheckVariableAssignment(variableAssignment);
|
|
||||||
// break;
|
|
||||||
// case VariableDeclarationNode variableDeclaration:
|
|
||||||
// CheckVariableVariableDeclaration(variableDeclaration);
|
|
||||||
// break;
|
|
||||||
// case IfNode ifNode:
|
|
||||||
// CheckIf(ifNode);
|
|
||||||
// break;
|
|
||||||
// case MemberAssignmentNode memberAssignment:
|
|
||||||
// CheckMemberAssignment(memberAssignment);
|
|
||||||
// break;
|
|
||||||
// case WhileNode whileNode:
|
|
||||||
// CheckWhile(whileNode);
|
|
||||||
// break;
|
|
||||||
// case ReturnNode returnNode:
|
|
||||||
// CheckReturn(returnNode);
|
|
||||||
// break;
|
|
||||||
// case StatementExpressionNode statementExpression:
|
|
||||||
// CheckExpression(statementExpression.Expression);
|
|
||||||
// break;
|
|
||||||
// case BreakNode:
|
|
||||||
// case ContinueNode:
|
|
||||||
// break;
|
|
||||||
// case DereferenceAssignmentNode dereferenceAssignment:
|
|
||||||
// CheckDereferenceAssignment(dereferenceAssignment);
|
|
||||||
// break;
|
|
||||||
// default:
|
|
||||||
// ReportError($"Unsupported statement type: {statement.GetType().Name}", statement);
|
|
||||||
// break;
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// private static void CheckMemberAssignment(MemberAssignmentNode memberAssignment)
|
|
||||||
// {
|
|
||||||
// var memberType = CheckExpression(memberAssignment.MemberAccess);
|
|
||||||
// if (memberType == null) return;
|
|
||||||
// var valueType = CheckExpression(memberAssignment.Value, memberType);
|
|
||||||
// if (valueType == null) return;
|
|
||||||
//
|
|
||||||
// if (!NubType.IsCompatibleWith(memberType, valueType))
|
|
||||||
// {
|
|
||||||
// ReportError($"'{valueType}' is not assignable to member of type '{memberType}'", memberAssignment);
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// private static void CheckArrayIndexAssignment(ArrayIndexAssignmentNode arrayIndexAssignment)
|
|
||||||
// {
|
|
||||||
// var itemType = CheckExpression(arrayIndexAssignment.ArrayIndexAccess);
|
|
||||||
// if (itemType == null) return;
|
|
||||||
// var valueType = CheckExpression(arrayIndexAssignment.Value, itemType);
|
|
||||||
// if (valueType == null) return;
|
|
||||||
//
|
|
||||||
// if (!NubType.IsCompatibleWith(itemType, valueType))
|
|
||||||
// {
|
|
||||||
// ReportError($"'{valueType}' is not assignable to array of type '{itemType}'", arrayIndexAssignment);
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// private static void CheckVariableAssignment(VariableAssignmentNode variableAssignment)
|
|
||||||
// {
|
|
||||||
// if (!_variables.TryGetValue(variableAssignment.Identifier.Name, out var variable))
|
|
||||||
// {
|
|
||||||
// ReportError($"Variable '{variableAssignment.Identifier}' is not declared", variableAssignment);
|
|
||||||
// return;
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// var valueType = CheckExpression(variableAssignment.Value, variable);
|
|
||||||
// if (valueType == null) return;
|
|
||||||
//
|
|
||||||
// if (!NubType.IsCompatibleWith(variableAssignment.Value.Type, variable))
|
|
||||||
// {
|
|
||||||
// ReportError($"Cannot assign expression of type '{variableAssignment.Value.Type}' to variable '{variableAssignment.Identifier}' with type '{variable}'", variableAssignment);
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// private static void CheckVariableVariableDeclaration(VariableDeclarationNode variableDeclaration)
|
|
||||||
// {
|
|
||||||
// if (_variables.TryGetValue(variableDeclaration.Name, out var variable))
|
|
||||||
// {
|
|
||||||
// ReportError($"Cannot redeclare variable '{variable}'", variableDeclaration);
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// _variables[variableDeclaration.Name] = variableDeclaration.Type;
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// private static NubType? CheckDereference(DereferenceNode dereference)
|
|
||||||
// {
|
|
||||||
// var exprType = CheckExpression(dereference.Expression);
|
|
||||||
// if (exprType == null) return null;
|
|
||||||
//
|
|
||||||
// if (exprType is not NubPointerType nubPointerType)
|
|
||||||
// {
|
|
||||||
// ReportError($"Cannot dereference a non-pointer type {exprType}", dereference);
|
|
||||||
// return null;
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// return nubPointerType.BaseType;
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// private static NubType CheckFixedInitializerArray(FixedArrayInitializerNode fixedArrayInitializer)
|
|
||||||
// {
|
|
||||||
// return new NubFixedArrayType(fixedArrayInitializer.ElementType, fixedArrayInitializer.Capacity);
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// private static NubType? CheckFuncCall(FuncCallNode funcCall)
|
|
||||||
// {
|
|
||||||
// var identType = CheckExpression(funcCall.Expression);
|
|
||||||
// if (identType is not NubFuncType funcType)
|
|
||||||
// {
|
|
||||||
// ReportError("Cannot call function on non-function type", funcCall);
|
|
||||||
// return null;
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// if (funcCall.Parameters.Count != funcType.Parameters.Count)
|
|
||||||
// {
|
|
||||||
// ReportError($"{funcType} expects {funcType.Parameters.Count} arguments, but was called with {funcType.Parameters.Count} arguments", funcCall);
|
|
||||||
// return null;
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// for (var i = 0; i < funcCall.Parameters.Count; i++)
|
|
||||||
// {
|
|
||||||
// var parameter = funcCall.Parameters[i];
|
|
||||||
// var parameterType = CheckExpression(parameter);
|
|
||||||
// if (parameterType == null) return null;
|
|
||||||
//
|
|
||||||
// if (!NubType.IsCompatibleWith(parameterType, funcType.Parameters[i]))
|
|
||||||
// {
|
|
||||||
// ReportError($"'{parameterType}' does not match expected type {funcType.Parameters[i]}", funcCall);
|
|
||||||
// return null;
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// return funcType.ReturnType;
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// private static void CheckIf(IfNode ifNode)
|
|
||||||
// {
|
|
||||||
// var conditionType = CheckExpression(ifNode.Condition, NubPrimitiveType.Bool);
|
|
||||||
// if (conditionType != null && !conditionType.Equals(NubPrimitiveType.Bool))
|
|
||||||
// {
|
|
||||||
// ReportError($"If condition must be a boolean expression, got '{conditionType}'", ifNode.Condition);
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// CheckBlock(ifNode.Body);
|
|
||||||
//
|
|
||||||
// if (ifNode.Else.HasValue)
|
|
||||||
// {
|
|
||||||
// var elseValue = ifNode.Else.Value;
|
|
||||||
// elseValue.Match(CheckIf, CheckBlock);
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// private static void CheckWhile(WhileNode whileNode)
|
|
||||||
// {
|
|
||||||
// var conditionType = CheckExpression(whileNode.Condition, NubPrimitiveType.Bool);
|
|
||||||
// if (conditionType != null && !conditionType.Equals(NubPrimitiveType.Bool))
|
|
||||||
// {
|
|
||||||
// ReportError($"While condition must be a boolean expression, got '{conditionType}'", whileNode.Condition);
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// CheckBlock(whileNode.Body);
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// private static void CheckReturn(ReturnNode returnNode)
|
|
||||||
// {
|
|
||||||
// if (returnNode.Value.HasValue)
|
|
||||||
// {
|
|
||||||
// var returnType = CheckExpression(returnNode.Value.Value, _currentFunctionReturnType);
|
|
||||||
// if (returnType == null) return;
|
|
||||||
//
|
|
||||||
// if (_currentFunctionReturnType == null)
|
|
||||||
// {
|
|
||||||
// ReportError("Cannot return a value from a function with no return type", returnNode.Value.Value);
|
|
||||||
// return;
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// if (!NubType.IsCompatibleWith(returnType, _currentFunctionReturnType))
|
|
||||||
// {
|
|
||||||
// ReportError($"Return value of type '{returnType}' is not compatible with function return type '{_currentFunctionReturnType}'", returnNode.Value.Value);
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// else if (_currentFunctionReturnType != null)
|
|
||||||
// {
|
|
||||||
// ReportError($"Function must return a value of type '{_currentFunctionReturnType}'", returnNode);
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// private static void CheckDereferenceAssignment(DereferenceAssignmentNode dereferenceAssignment)
|
|
||||||
// {
|
|
||||||
// var dereferenceType = CheckExpression(dereferenceAssignment.Dereference);
|
|
||||||
// if (dereferenceType == null) return;
|
|
||||||
// var valueType = CheckExpression(dereferenceAssignment.Value, dereferenceType);
|
|
||||||
// if (valueType == null) return;
|
|
||||||
//
|
|
||||||
// if (!NubType.IsCompatibleWith(dereferenceType, valueType))
|
|
||||||
// {
|
|
||||||
// ReportError($"'{valueType}' is not assignable to type '{dereferenceType}'", dereferenceAssignment);
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// private static NubType? CheckExpression(ExpressionNode expression, NubType? expectedType = null)
|
|
||||||
// {
|
|
||||||
// var resultType = expression switch
|
|
||||||
// {
|
|
||||||
// AddressOfNode addressOf => CheckAddressOf(addressOf),
|
|
||||||
// AnonymousFuncNode anonymousFunc => CheckAnonymousFunc(anonymousFunc),
|
|
||||||
// ArrayIndexAccessNode arrayIndex => CheckArrayIndex(arrayIndex),
|
|
||||||
// ArrayInitializerNode arrayInitializer => CheckArrayInitializer(arrayInitializer),
|
|
||||||
// LiteralNode literal => CheckLiteral(literal, expectedType),
|
|
||||||
// IdentifierNode identifier => CheckIdentifier(identifier),
|
|
||||||
// BinaryExpressionNode binaryExpr => CheckBinaryExpression(binaryExpr),
|
|
||||||
// DereferenceNode dereference => CheckDereference(dereference),
|
|
||||||
// FixedArrayInitializerNode fixedArray => CheckFixedInitializerArray(fixedArray),
|
|
||||||
// FuncCallNode funcCallExpr => CheckFuncCall(funcCallExpr),
|
|
||||||
// StructInitializerNode structInit => CheckStructInitializer(structInit),
|
|
||||||
// UnaryExpressionNode unaryExpression => CheckUnaryExpression(unaryExpression),
|
|
||||||
// MemberAccessNode memberAccess => CheckMemberAccess(memberAccess),
|
|
||||||
// _ => throw new UnreachableException()
|
|
||||||
// };
|
|
||||||
//
|
|
||||||
// if (resultType != null)
|
|
||||||
// {
|
|
||||||
// expression.Type = resultType;
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// return resultType;
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// private static NubType CheckAnonymousFunc(AnonymousFuncNode anonymousFunc)
|
|
||||||
// {
|
|
||||||
// _anonymousFunctions.Enqueue(anonymousFunc);
|
|
||||||
// return new NubFuncType(anonymousFunc.ReturnType, anonymousFunc.Parameters.Select(p => p.Type).ToList());
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// private static NubType? CheckLiteral(LiteralNode literal, NubType? expectedType = null)
|
|
||||||
// {
|
|
||||||
// if (expectedType != null)
|
|
||||||
// {
|
|
||||||
// if (expectedType.IsNumber && literal.Kind is not LiteralKind.Integer and not LiteralKind.Float)
|
|
||||||
// {
|
|
||||||
// ReportError("Expression expects a numeric literal", literal);
|
|
||||||
// return null;
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// if (expectedType.IsInteger && literal.Kind == LiteralKind.Float)
|
|
||||||
// {
|
|
||||||
// if (literal.Kind == LiteralKind.Float)
|
|
||||||
// {
|
|
||||||
// ReportWarning("Float used in integer context. Everything after the '.' will be ignored", literal);
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// return expectedType;
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// return literal.Kind switch
|
|
||||||
// {
|
|
||||||
// LiteralKind.Integer => NubPrimitiveType.I64,
|
|
||||||
// LiteralKind.Float => NubPrimitiveType.F64,
|
|
||||||
// LiteralKind.String => new NubCStringType(),
|
|
||||||
// LiteralKind.Bool => NubPrimitiveType.Bool,
|
|
||||||
// _ => throw new ArgumentOutOfRangeException()
|
|
||||||
// };
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// private static NubType? CheckArrayIndex(ArrayIndexAccessNode arrayIndexAccess)
|
|
||||||
// {
|
|
||||||
// var expressionType = CheckExpression(arrayIndexAccess.Array);
|
|
||||||
// if (expressionType == null) return null;
|
|
||||||
// var indexType = CheckExpression(arrayIndexAccess.Index, NubPrimitiveType.U64);
|
|
||||||
// if (indexType is { IsInteger: false })
|
|
||||||
// {
|
|
||||||
// ReportError("Array index type must be a number", arrayIndexAccess.Index);
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// if (expressionType is NubArrayType arrayType)
|
|
||||||
// {
|
|
||||||
// return arrayType.ElementType;
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// if (expressionType is NubFixedArrayType fixedArrayType)
|
|
||||||
// {
|
|
||||||
// return fixedArrayType.ElementType;
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// ReportError($"Cannot access index of non-array type {expressionType}", arrayIndexAccess.Array);
|
|
||||||
// return null;
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// private static NubType CheckArrayInitializer(ArrayInitializerNode arrayInitializer)
|
|
||||||
// {
|
|
||||||
// var capacityType = CheckExpression(arrayInitializer.Capacity, NubPrimitiveType.U64);
|
|
||||||
// if (capacityType is { IsInteger: false })
|
|
||||||
// {
|
|
||||||
// ReportError("Array capacity type must be an integer", arrayInitializer.Capacity);
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// return new NubArrayType(arrayInitializer.ElementType);
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// private static NubType? CheckIdentifier(IdentifierNode identifier)
|
|
||||||
// {
|
|
||||||
// var definition = _definitionTable.LookupFunc(identifier.Namespace.Or(_syntaxTree.Namespace), identifier.Name);
|
|
||||||
// if (definition.HasValue)
|
|
||||||
// {
|
|
||||||
// return new NubFuncType(definition.Value.ReturnType, definition.Value.Parameters.Select(p => p.Type).ToList());
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// if (!identifier.Namespace.HasValue)
|
|
||||||
// {
|
|
||||||
// return _variables[identifier.Name];
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// ReportError($"Identifier '{identifier}' not found", identifier);
|
|
||||||
// return null;
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// private static NubType? CheckAddressOf(AddressOfNode addressOf)
|
|
||||||
// {
|
|
||||||
// var exprType = CheckExpression(addressOf.Expression);
|
|
||||||
// if (exprType == null) return null;
|
|
||||||
//
|
|
||||||
// return new NubPointerType(exprType);
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// private static NubType? CheckBinaryExpression(BinaryExpressionNode binaryExpr)
|
|
||||||
// {
|
|
||||||
// var leftType = CheckExpression(binaryExpr.Left);
|
|
||||||
// var rightType = CheckExpression(binaryExpr.Right);
|
|
||||||
//
|
|
||||||
// if (leftType == null || rightType == null) return null;
|
|
||||||
//
|
|
||||||
// if (!leftType.Equals(rightType))
|
|
||||||
// {
|
|
||||||
// ReportError($"Left '{leftType}' and right '{rightType}' side of the binary expression must be the same type", binaryExpr);
|
|
||||||
// return null;
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// switch (binaryExpr.Operator)
|
|
||||||
// {
|
|
||||||
// case BinaryExpressionOperator.Equal:
|
|
||||||
// case BinaryExpressionOperator.NotEqual:
|
|
||||||
// return NubPrimitiveType.Bool;
|
|
||||||
// case BinaryExpressionOperator.GreaterThan:
|
|
||||||
// case BinaryExpressionOperator.GreaterThanOrEqual:
|
|
||||||
// case BinaryExpressionOperator.LessThan:
|
|
||||||
// case BinaryExpressionOperator.LessThanOrEqual:
|
|
||||||
// if (!IsNumeric(leftType))
|
|
||||||
// {
|
|
||||||
// ReportError($"Comparison operators require numeric operands, got '{leftType}' and '{rightType}'", binaryExpr);
|
|
||||||
// return null;
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// return NubPrimitiveType.Bool;
|
|
||||||
// case BinaryExpressionOperator.Plus:
|
|
||||||
// case BinaryExpressionOperator.Minus:
|
|
||||||
// case BinaryExpressionOperator.Multiply:
|
|
||||||
// case BinaryExpressionOperator.Divide:
|
|
||||||
// if (!IsNumeric(leftType))
|
|
||||||
// {
|
|
||||||
// ReportError($"Arithmetic operators require numeric operands, got '{leftType}' and '{rightType}'", binaryExpr);
|
|
||||||
// return null;
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// return leftType;
|
|
||||||
// default:
|
|
||||||
// ReportError($"Unsupported binary operator: {binaryExpr.Operator}", binaryExpr);
|
|
||||||
// return null;
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// private static NubType? CheckStructInitializer(StructInitializerNode structInit)
|
|
||||||
// {
|
|
||||||
// var initialized = new HashSet<string>();
|
|
||||||
//
|
|
||||||
// var defOpt = _definitionTable.LookupStruct(structInit.StructType.Namespace, structInit.StructType.Name);
|
|
||||||
// if (!defOpt.TryGetValue(out var definition))
|
|
||||||
// {
|
|
||||||
// ReportError($"Struct type '{structInit.StructType.Name}' is not defined", structInit);
|
|
||||||
// return null;
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// foreach (var initializer in structInit.Initializers)
|
|
||||||
// {
|
|
||||||
// var definitionField = definition.Fields.FirstOrDefault(f => f.Name == initializer.Key);
|
|
||||||
// if (definitionField == null)
|
|
||||||
// {
|
|
||||||
// ReportError($"Field '{initializer.Key}' does not exist in struct '{structInit.StructType.Name}'", initializer.Value);
|
|
||||||
// continue;
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// var initializerType = CheckExpression(initializer.Value, definitionField.Type);
|
|
||||||
// if (initializerType != null && !NubType.IsCompatibleWith(initializerType, definitionField.Type))
|
|
||||||
// {
|
|
||||||
// ReportError($"Cannot initialize field '{initializer.Key}' of type '{definitionField.Type}' with expression of type '{initializerType}'", initializer.Value);
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// initialized.Add(initializer.Key);
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// foreach (var field in definition.Fields.Where(f => f.Value.HasValue))
|
|
||||||
// {
|
|
||||||
// initialized.Add(field.Name);
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// foreach (var field in definition.Fields)
|
|
||||||
// {
|
|
||||||
// if (!initialized.Contains(field.Name))
|
|
||||||
// {
|
|
||||||
// ReportError($"Struct field '{field.Name}' is not initialized on type '{structInit.StructType.Name}'", structInit);
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// return structInit.StructType;
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// private static NubType? CheckUnaryExpression(UnaryExpressionNode unaryExpression)
|
|
||||||
// {
|
|
||||||
// var operandType = CheckExpression(unaryExpression.Operand);
|
|
||||||
// if (operandType == null) return null;
|
|
||||||
//
|
|
||||||
// switch (unaryExpression.Operator)
|
|
||||||
// {
|
|
||||||
// case UnaryExpressionOperator.Negate:
|
|
||||||
// {
|
|
||||||
// if (operandType.Equals(NubPrimitiveType.I8) ||
|
|
||||||
// operandType.Equals(NubPrimitiveType.I16) ||
|
|
||||||
// operandType.Equals(NubPrimitiveType.I32) ||
|
|
||||||
// operandType.Equals(NubPrimitiveType.I64) ||
|
|
||||||
// operandType.Equals(NubPrimitiveType.F32) ||
|
|
||||||
// operandType.Equals(NubPrimitiveType.F64))
|
|
||||||
// {
|
|
||||||
// return operandType;
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// ReportError($"Cannot negate non-numeric type {operandType}", unaryExpression.Operand);
|
|
||||||
// return null;
|
|
||||||
// }
|
|
||||||
// case UnaryExpressionOperator.Invert:
|
|
||||||
// {
|
|
||||||
// if (!operandType.Equals(NubPrimitiveType.Bool))
|
|
||||||
// {
|
|
||||||
// ReportError($"Cannot invert non-boolean type {operandType}", unaryExpression.Operand);
|
|
||||||
// return null;
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// return operandType;
|
|
||||||
// }
|
|
||||||
// default:
|
|
||||||
// {
|
|
||||||
// ReportError($"Unsupported unary operator: {unaryExpression.Operator}", unaryExpression);
|
|
||||||
// return null;
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// private static NubType? CheckMemberAccess(MemberAccessNode memberAccess)
|
|
||||||
// {
|
|
||||||
// var expressionType = CheckExpression(memberAccess.Expression);
|
|
||||||
// if (expressionType == null) return null;
|
|
||||||
//
|
|
||||||
// switch (expressionType)
|
|
||||||
// {
|
|
||||||
// case NubArrayType:
|
|
||||||
// {
|
|
||||||
// if (memberAccess.Member == "count")
|
|
||||||
// {
|
|
||||||
// return NubPrimitiveType.I64;
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// break;
|
|
||||||
// }
|
|
||||||
// case NubStructType structType:
|
|
||||||
// {
|
|
||||||
// var defOpt = _definitionTable.LookupStruct(structType.Namespace, structType.Name);
|
|
||||||
// if (!defOpt.TryGetValue(out var definition))
|
|
||||||
// {
|
|
||||||
// ReportError($"Struct type '{structType.Name}' is not defined", memberAccess);
|
|
||||||
// return null;
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// var field = definition.Fields.FirstOrDefault(f => f.Name == memberAccess.Member);
|
|
||||||
// if (field == null)
|
|
||||||
// {
|
|
||||||
// ReportError($"Field '{memberAccess.Member}' does not exist in struct '{structType.Name}'", memberAccess);
|
|
||||||
// return null;
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// return field.Type;
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// ReportError($"Cannot access member '{memberAccess.Member}' on type '{expressionType}'", memberAccess);
|
|
||||||
// return null;
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// private static void ReportError(string message, Node node)
|
|
||||||
// {
|
|
||||||
// var diagnostic = Diagnostic.Error(message).At(node).Build();
|
|
||||||
// _diagnostics.Add(diagnostic);
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// private static void ReportWarning(string message, Node node)
|
|
||||||
// {
|
|
||||||
// var diagnostic = Diagnostic.Warning(message).At(node).Build();
|
|
||||||
// _diagnostics.Add(diagnostic);
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// private static bool IsNumeric(NubType type)
|
|
||||||
// {
|
|
||||||
// if (type is not NubPrimitiveType primitiveType)
|
|
||||||
// {
|
|
||||||
// return false;
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// switch (primitiveType.Kind)
|
|
||||||
// {
|
|
||||||
// case PrimitiveTypeKind.I8:
|
|
||||||
// case PrimitiveTypeKind.I16:
|
|
||||||
// case PrimitiveTypeKind.I32:
|
|
||||||
// case PrimitiveTypeKind.I64:
|
|
||||||
// case PrimitiveTypeKind.U8:
|
|
||||||
// case PrimitiveTypeKind.U16:
|
|
||||||
// case PrimitiveTypeKind.U32:
|
|
||||||
// case PrimitiveTypeKind.U64:
|
|
||||||
// case PrimitiveTypeKind.F32:
|
|
||||||
// case PrimitiveTypeKind.F64:
|
|
||||||
// return true;
|
|
||||||
// default:
|
|
||||||
// return false;
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
Reference in New Issue
Block a user