Compare commits
103 Commits
c3d64c4ea9
...
dev
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
aa5a494d39 | ||
|
|
bc65c3f4fd | ||
|
|
bdeb2c4d73 | ||
|
|
48760dcdda | ||
|
|
63d0eb660b | ||
|
|
918d25f8c8 | ||
|
|
6e631ba1ba | ||
|
|
832184053f | ||
|
|
2ab20652f8 | ||
|
|
6507624e90 | ||
|
|
83255980d7 | ||
|
|
1a1dc1389d | ||
|
|
4210ad878b | ||
|
|
32042d0769 | ||
|
|
dd44e3edc4 | ||
|
|
24d41d3dcb | ||
|
|
c9bf212aa2 | ||
|
|
0e099d0baf | ||
|
|
0be4e35628 | ||
|
|
7dd635fa91 | ||
|
|
693b119781 | ||
|
|
2b7eb56895 | ||
|
|
5f4a4172bf | ||
|
|
9da370e387 | ||
|
|
faf506f409 | ||
|
|
16d27c76ab | ||
|
|
270be1f740 | ||
|
|
da2fa81c39 | ||
|
|
677c7dea9e | ||
|
|
6ba05d00d7 | ||
|
|
d58e006be4 | ||
|
|
84627dde45 | ||
|
|
e7aad861d3 | ||
|
|
ecb628c4c2 | ||
|
|
e5227f7a99 | ||
|
|
73ecbf8e9b | ||
|
|
e512650440 | ||
|
|
272ea33616 | ||
|
|
b7dc77cb1c | ||
|
|
3323d760e8 | ||
|
|
aa5bf0b568 | ||
|
|
660166221c | ||
|
|
49734544e6 | ||
|
|
cb4aeb9c01 | ||
|
|
d771396bd4 | ||
|
|
7b1e202424 | ||
|
|
282dd0502a | ||
|
|
3ebaa67b6d | ||
|
|
35867ffa28 | ||
|
|
76efc84984 | ||
|
|
583c01201f | ||
|
|
1511d5d2b8 | ||
|
|
caa3b378b3 | ||
|
|
cbe27c0ae8 | ||
|
|
4d0f7c715f | ||
|
|
c342b2e102 | ||
|
|
ee485e4119 | ||
|
|
21a095f691 | ||
|
|
0a5b39b217 | ||
|
|
ab2f5750dc | ||
|
|
b45a0fa8cc | ||
|
|
7daccba9f3 | ||
|
|
924bdae89b | ||
|
|
5364b0420a | ||
|
|
c65fbeba13 | ||
|
|
9d8d8f255a | ||
|
|
576abe1240 | ||
|
|
d0c31ad17f | ||
|
|
7872a4b6b8 | ||
|
|
6ae10d5f90 | ||
|
|
d3e2dcede8 | ||
|
|
88fe03c048 | ||
|
|
9ccdd5f835 | ||
|
|
b7cfdd2519 | ||
|
|
d409bb4d92 | ||
|
|
00a172b922 | ||
|
|
ea3d374831 | ||
|
|
ab9bd6fd05 | ||
|
|
96670b1201 | ||
|
|
9fb9c50a0b | ||
|
|
89a827fdef | ||
|
|
f035499ba7 | ||
|
|
3e3757a402 | ||
|
|
1d63bd6389 | ||
|
|
1a3cf3c4fd | ||
|
|
5105a689df | ||
|
|
bd7e3fc931 | ||
| 3b89b3d9bb | |||
| 3db412a060 | |||
| b31a2d01c6 | |||
| 6f03e2203f | |||
| e20f6cd7af | |||
| 00714ea4b0 | |||
| 4761cd1f83 | |||
| 423ec4c798 | |||
| 38f55d8e7c | |||
| 1a5742fc4f | |||
| 4c201c4085 | |||
| e77c7028b9 | |||
| 26d365cf4f | |||
| f2ea00b34d | |||
| cb2411a7eb | |||
| 3b75e62aa7 |
1
.gitignore
vendored
1
.gitignore
vendored
@@ -1 +0,0 @@
|
|||||||
.vscode
|
|
||||||
@@ -1,166 +0,0 @@
|
|||||||
#!/usr/bin/env python3
|
|
||||||
|
|
||||||
import sys
|
|
||||||
import os
|
|
||||||
import clang.cindex
|
|
||||||
from clang.cindex import CursorKind, TypeKind, Type
|
|
||||||
|
|
||||||
|
|
||||||
def map_type(clang_type: Type):
|
|
||||||
canonical = clang_type.get_canonical()
|
|
||||||
kind = canonical.kind
|
|
||||||
spelling = (
|
|
||||||
canonical.spelling.replace("const ", "")
|
|
||||||
.replace("volatile ", "")
|
|
||||||
.replace("restrict ", "")
|
|
||||||
.replace("struct ", "")
|
|
||||||
.replace("union ", "")
|
|
||||||
)
|
|
||||||
|
|
||||||
if kind == TypeKind.POINTER:
|
|
||||||
pointee = canonical.get_pointee()
|
|
||||||
if pointee.kind == TypeKind.RECORD:
|
|
||||||
decl = pointee.get_declaration()
|
|
||||||
if not decl.is_definition():
|
|
||||||
return "^void"
|
|
||||||
|
|
||||||
if pointee.kind == TypeKind.FUNCTIONPROTO:
|
|
||||||
arg_types = []
|
|
||||||
|
|
||||||
for arg in pointee.get_canonical().argument_types():
|
|
||||||
arg_types.append(map_type(arg))
|
|
||||||
|
|
||||||
mapped_return = map_type(pointee.get_canonical().get_result())
|
|
||||||
args_str = ", ".join(arg_types)
|
|
||||||
|
|
||||||
return f"func({args_str}): {mapped_return}"
|
|
||||||
|
|
||||||
return f"^{map_type(pointee)}"
|
|
||||||
|
|
||||||
if kind == TypeKind.CONSTANTARRAY:
|
|
||||||
element_type = canonical.get_array_element_type()
|
|
||||||
size = canonical.get_array_size()
|
|
||||||
return f"[{size}]{map_type(element_type)}"
|
|
||||||
|
|
||||||
if kind == TypeKind.INCOMPLETEARRAY:
|
|
||||||
element_type = canonical.get_array_element_type()
|
|
||||||
return f"[?]{map_type(element_type)}"
|
|
||||||
|
|
||||||
if kind == TypeKind.FUNCTIONPROTO or kind == TypeKind.FUNCTIONNOPROTO:
|
|
||||||
arg_types = []
|
|
||||||
|
|
||||||
for arg in canonical.argument_types():
|
|
||||||
arg_types.append(map_type(arg))
|
|
||||||
|
|
||||||
mapped_return = map_type(canonical.get_result())
|
|
||||||
args_str = ", ".join(arg_types)
|
|
||||||
|
|
||||||
return f"func({args_str}): {mapped_return}"
|
|
||||||
|
|
||||||
if kind == TypeKind.VOID:
|
|
||||||
return "void"
|
|
||||||
|
|
||||||
if kind == TypeKind.BOOL:
|
|
||||||
return "bool"
|
|
||||||
|
|
||||||
if kind in [TypeKind.CHAR_S, TypeKind.SCHAR]:
|
|
||||||
return "i8"
|
|
||||||
if kind == TypeKind.CHAR_U or kind == TypeKind.UCHAR:
|
|
||||||
return "u8"
|
|
||||||
if kind == TypeKind.SHORT:
|
|
||||||
return "i16"
|
|
||||||
if kind == TypeKind.USHORT:
|
|
||||||
return "u16"
|
|
||||||
if kind == TypeKind.INT:
|
|
||||||
return "i32"
|
|
||||||
if kind == TypeKind.UINT:
|
|
||||||
return "u32"
|
|
||||||
if kind in [TypeKind.LONG, TypeKind.LONGLONG]:
|
|
||||||
return "i64"
|
|
||||||
if kind in [TypeKind.ULONG, TypeKind.ULONGLONG]:
|
|
||||||
return "u64"
|
|
||||||
|
|
||||||
if kind == TypeKind.FLOAT:
|
|
||||||
return "f32"
|
|
||||||
if kind == TypeKind.DOUBLE or kind == TypeKind.LONGDOUBLE:
|
|
||||||
return "f64"
|
|
||||||
|
|
||||||
if kind == TypeKind.RECORD:
|
|
||||||
return spelling
|
|
||||||
|
|
||||||
raise Exception(f"Unresolved type: {spelling}")
|
|
||||||
|
|
||||||
|
|
||||||
if len(sys.argv) != 2:
|
|
||||||
print("Usage: python3 generate.py [path to header]", file=sys.stderr)
|
|
||||||
sys.exit(1)
|
|
||||||
|
|
||||||
filename = sys.argv[1]
|
|
||||||
|
|
||||||
index = clang.cindex.Index.create()
|
|
||||||
|
|
||||||
tu = index.parse(filename, ["-x", "c", "-std=c23", "-I/usr/include"])
|
|
||||||
|
|
||||||
if tu.diagnostics:
|
|
||||||
for diag in tu.diagnostics:
|
|
||||||
if diag.severity >= clang.cindex.Diagnostic.Error:
|
|
||||||
print(f"Error: {diag.spelling}", file=sys.stderr)
|
|
||||||
|
|
||||||
print(f'module "{os.path.basename(filename).split(".")[0]}"')
|
|
||||||
print()
|
|
||||||
|
|
||||||
seen_structs = []
|
|
||||||
|
|
||||||
for cursor in tu.cursor.walk_preorder():
|
|
||||||
if cursor.location.file and cursor.location.file.name != filename:
|
|
||||||
continue
|
|
||||||
|
|
||||||
if cursor.kind == CursorKind.FUNCTION_DECL:
|
|
||||||
name = cursor.spelling
|
|
||||||
return_type = map_type(cursor.result_type)
|
|
||||||
|
|
||||||
params = []
|
|
||||||
for arg in cursor.get_arguments():
|
|
||||||
param_name = arg.spelling
|
|
||||||
param_type = map_type(arg.type)
|
|
||||||
params.append(f"{param_name}: {param_type}")
|
|
||||||
|
|
||||||
params_str = ", ".join(params)
|
|
||||||
|
|
||||||
print(f'export extern "{name}" func {name}({params_str}): {return_type}')
|
|
||||||
|
|
||||||
elif cursor.kind == CursorKind.STRUCT_DECL:
|
|
||||||
if cursor.get_usr() in seen_structs:
|
|
||||||
continue
|
|
||||||
|
|
||||||
seen_structs.append(cursor.get_usr())
|
|
||||||
|
|
||||||
if cursor.is_definition():
|
|
||||||
name = cursor.spelling
|
|
||||||
print(f"export struct {name}")
|
|
||||||
print("{")
|
|
||||||
for field in cursor.get_children():
|
|
||||||
if field.kind == CursorKind.FIELD_DECL:
|
|
||||||
field_name = field.spelling
|
|
||||||
field_type = map_type(field.type)
|
|
||||||
print(f" {field_name}: {field_type}")
|
|
||||||
else:
|
|
||||||
raise Exception(
|
|
||||||
f"Unsupported child of struct: {field.spelling}: {field.kind}"
|
|
||||||
)
|
|
||||||
print("}")
|
|
||||||
|
|
||||||
elif cursor.kind == CursorKind.ENUM_DECL:
|
|
||||||
name = cursor.spelling
|
|
||||||
print(f"export enum {name} : u32")
|
|
||||||
print("{")
|
|
||||||
for field in cursor.get_children():
|
|
||||||
if field.kind == CursorKind.ENUM_CONSTANT_DECL:
|
|
||||||
field_name = field.spelling
|
|
||||||
field_value = field.enum_value
|
|
||||||
print(f" {field_name} = {field_value}")
|
|
||||||
else:
|
|
||||||
raise Exception(
|
|
||||||
f"Unsupported child of enum: {field.spelling}: {field.kind}"
|
|
||||||
)
|
|
||||||
print("}")
|
|
||||||
38
compiler/.gitignore
vendored
38
compiler/.gitignore
vendored
@@ -1,34 +1,4 @@
|
|||||||
# Common IntelliJ Platform excludes
|
.idea
|
||||||
|
bin
|
||||||
# User specific
|
obj
|
||||||
**/.idea/**/workspace.xml
|
.build
|
||||||
**/.idea/**/tasks.xml
|
|
||||||
**/.idea/shelf/*
|
|
||||||
**/.idea/dictionaries
|
|
||||||
**/.idea/httpRequests/
|
|
||||||
|
|
||||||
# Sensitive or high-churn files
|
|
||||||
**/.idea/**/dataSources/
|
|
||||||
**/.idea/**/dataSources.ids
|
|
||||||
**/.idea/**/dataSources.xml
|
|
||||||
**/.idea/**/dataSources.local.xml
|
|
||||||
**/.idea/**/sqlDataSources.xml
|
|
||||||
**/.idea/**/dynamic.xml
|
|
||||||
|
|
||||||
# Rider
|
|
||||||
# Rider auto-generates .iml files, and contentModel.xml
|
|
||||||
**/.idea/**/*.iml
|
|
||||||
**/.idea/**/contentModel.xml
|
|
||||||
**/.idea/**/modules.xml
|
|
||||||
|
|
||||||
*.suo
|
|
||||||
*.user
|
|
||||||
.vs/
|
|
||||||
[Bb]in/
|
|
||||||
[Oo]bj/
|
|
||||||
_UpgradeReport_Files/
|
|
||||||
[Pp]ackages/
|
|
||||||
|
|
||||||
Thumbs.db
|
|
||||||
Desktop.ini
|
|
||||||
.DS_Store
|
|
||||||
13
compiler/.idea/.idea.Compiler/.idea/.gitignore
generated
vendored
13
compiler/.idea/.idea.Compiler/.idea/.gitignore
generated
vendored
@@ -1,13 +0,0 @@
|
|||||||
# Default ignored files
|
|
||||||
/shelf/
|
|
||||||
/workspace.xml
|
|
||||||
# Rider ignored files
|
|
||||||
/modules.xml
|
|
||||||
/projectSettingsUpdater.xml
|
|
||||||
/contentModel.xml
|
|
||||||
/.idea.Compiler.iml
|
|
||||||
# Editor-based HTTP Client requests
|
|
||||||
/httpRequests/
|
|
||||||
# Datasource local storage ignored files
|
|
||||||
/dataSources/
|
|
||||||
/dataSources.local.xml
|
|
||||||
1
compiler/.idea/.idea.Compiler/.idea/.name
generated
1
compiler/.idea/.idea.Compiler/.idea/.name
generated
@@ -1 +0,0 @@
|
|||||||
Compiler
|
|
||||||
@@ -1,4 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<project version="4">
|
|
||||||
<component name="Encoding" addBOMForNewFiles="with BOM under Windows, with no BOM otherwise" />
|
|
||||||
</project>
|
|
||||||
10
compiler/.idea/.idea.Compiler/.idea/indexLayout.xml
generated
10
compiler/.idea/.idea.Compiler/.idea/indexLayout.xml
generated
@@ -1,10 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<project version="4">
|
|
||||||
<component name="UserContentModel">
|
|
||||||
<attachedFolders>
|
|
||||||
<Path>Runtime</Path>
|
|
||||||
</attachedFolders>
|
|
||||||
<explicitIncludes />
|
|
||||||
<explicitExcludes />
|
|
||||||
</component>
|
|
||||||
</project>
|
|
||||||
6
compiler/.idea/.idea.Compiler/.idea/vcs.xml
generated
6
compiler/.idea/.idea.Compiler/.idea/vcs.xml
generated
@@ -1,6 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<project version="4">
|
|
||||||
<component name="VcsDirectoryMappings">
|
|
||||||
<mapping directory="$PROJECT_DIR$/.." vcs="Git" />
|
|
||||||
</component>
|
|
||||||
</project>
|
|
||||||
@@ -1,14 +1,10 @@
|
|||||||
<Project Sdk="Microsoft.NET.Sdk">
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
|
<OutputType>Exe</OutputType>
|
||||||
<TargetFramework>net9.0</TargetFramework>
|
<TargetFramework>net9.0</TargetFramework>
|
||||||
<ImplicitUsings>enable</ImplicitUsings>
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
<Nullable>enable</Nullable>
|
<Nullable>enable</Nullable>
|
||||||
<IsAotCompatible>true</IsAotCompatible>
|
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
|
||||||
<PackageReference Include="LLVMSharp" Version="20.1.2" />
|
|
||||||
</ItemGroup>
|
|
||||||
|
|
||||||
</Project>
|
</Project>
|
||||||
@@ -1,28 +0,0 @@
|
|||||||
|
|
||||||
Microsoft Visual Studio Solution File, Format Version 12.00
|
|
||||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NubLang", "NubLang\NubLang.csproj", "{5047E21F-590D-4CB3-AFF3-064316485009}"
|
|
||||||
EndProject
|
|
||||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NubLang.CLI", "NubLang.CLI\NubLang.CLI.csproj", "{A22F17ED-FA17-45AB-92BA-CD02C28B3524}"
|
|
||||||
EndProject
|
|
||||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NubLang.LSP", "NubLang.LSP\NubLang.LSP.csproj", "{07968F84-0C2E-4D2E-8905-DC8A6140B4C0}"
|
|
||||||
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
|
|
||||||
{07968F84-0C2E-4D2E-8905-DC8A6140B4C0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
|
||||||
{07968F84-0C2E-4D2E-8905-DC8A6140B4C0}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
|
||||||
{07968F84-0C2E-4D2E-8905-DC8A6140B4C0}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
|
||||||
{07968F84-0C2E-4D2E-8905-DC8A6140B4C0}.Release|Any CPU.Build.0 = Release|Any CPU
|
|
||||||
EndGlobalSection
|
|
||||||
EndGlobal
|
|
||||||
173
compiler/Diagnostic.cs
Normal file
173
compiler/Diagnostic.cs
Normal file
@@ -0,0 +1,173 @@
|
|||||||
|
namespace Compiler;
|
||||||
|
|
||||||
|
public class Diagnostic
|
||||||
|
{
|
||||||
|
public static Builder Info(string message) => new Builder(DiagnosticSeverity.Info, message);
|
||||||
|
public static Builder Warning(string message) => new Builder(DiagnosticSeverity.Warning, message);
|
||||||
|
public static Builder Error(string message) => new Builder(DiagnosticSeverity.Error, message);
|
||||||
|
|
||||||
|
private Diagnostic(DiagnosticSeverity severity, string message, string? help, FileInfo? file)
|
||||||
|
{
|
||||||
|
Severity = severity;
|
||||||
|
Message = message;
|
||||||
|
Help = help;
|
||||||
|
File = file;
|
||||||
|
}
|
||||||
|
|
||||||
|
public class FileInfo(string file, int line, int column, int length)
|
||||||
|
{
|
||||||
|
public string File { get; } = file;
|
||||||
|
public int Line { get; } = line;
|
||||||
|
public int Column { get; } = column;
|
||||||
|
public int Length { get; } = length;
|
||||||
|
}
|
||||||
|
|
||||||
|
public DiagnosticSeverity Severity { get; }
|
||||||
|
public string Message { get; }
|
||||||
|
public string? Help { get; }
|
||||||
|
public FileInfo? File { get; }
|
||||||
|
|
||||||
|
public enum DiagnosticSeverity
|
||||||
|
{
|
||||||
|
Info,
|
||||||
|
Warning,
|
||||||
|
Error,
|
||||||
|
}
|
||||||
|
|
||||||
|
public class Builder(DiagnosticSeverity severity, string message)
|
||||||
|
{
|
||||||
|
private FileInfo? file;
|
||||||
|
private string? help;
|
||||||
|
|
||||||
|
public Builder At(string fileName, int line, int column, int length)
|
||||||
|
{
|
||||||
|
file = new FileInfo(fileName, line, column, length);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Builder At(string fileName, Token? token)
|
||||||
|
{
|
||||||
|
if (token != null)
|
||||||
|
{
|
||||||
|
At(fileName, token.Line, token.Column, token.Length);
|
||||||
|
}
|
||||||
|
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Builder At(string fileName, Node? node)
|
||||||
|
{
|
||||||
|
if (node != null && node.Tokens.Count != 0)
|
||||||
|
{
|
||||||
|
// todo(nub31): Calculate length based on last token
|
||||||
|
At(fileName, node.Tokens[0]);
|
||||||
|
}
|
||||||
|
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Builder At(string fileName, TypedNode? node)
|
||||||
|
{
|
||||||
|
if (node != null && node.Tokens.Count != 0)
|
||||||
|
{
|
||||||
|
// todo(nub31): Calculate length based on last token
|
||||||
|
At(fileName, node.Tokens[0]);
|
||||||
|
}
|
||||||
|
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Builder WithHelp(string helpMessage)
|
||||||
|
{
|
||||||
|
help = helpMessage;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Diagnostic Build()
|
||||||
|
{
|
||||||
|
return new Diagnostic(severity, message, help, file);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class CompileException(Diagnostic diagnostic) : Exception
|
||||||
|
{
|
||||||
|
public Diagnostic Diagnostic { get; } = diagnostic;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class DiagnosticFormatter
|
||||||
|
{
|
||||||
|
public static void Print(Diagnostic diagnostic, TextWriter writer)
|
||||||
|
{
|
||||||
|
var (label, color) = diagnostic.Severity switch
|
||||||
|
{
|
||||||
|
Diagnostic.DiagnosticSeverity.Info => ("info", Ansi.Cyan),
|
||||||
|
Diagnostic.DiagnosticSeverity.Warning => ("warning", Ansi.Yellow),
|
||||||
|
Diagnostic.DiagnosticSeverity.Error => ("error", Ansi.Red),
|
||||||
|
_ => ("unknown", Ansi.Reset),
|
||||||
|
};
|
||||||
|
|
||||||
|
writer.Write(color);
|
||||||
|
writer.Write(label);
|
||||||
|
writer.Write(Ansi.Reset);
|
||||||
|
writer.Write(": ");
|
||||||
|
writer.WriteLine(diagnostic.Message);
|
||||||
|
|
||||||
|
if (diagnostic.File is null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
var file = diagnostic.File;
|
||||||
|
var lineNumberWidth = diagnostic.File.Line.ToString().Length;
|
||||||
|
|
||||||
|
writer.WriteLine($" {new string(' ', lineNumberWidth)}{file.File}:{file.Line}:{file.Column}");
|
||||||
|
writer.WriteLine($"{new string(' ', lineNumberWidth)} | ");
|
||||||
|
|
||||||
|
var sourceLine = TryReadLine(file.File, file.Line);
|
||||||
|
if (sourceLine != null)
|
||||||
|
{
|
||||||
|
writer.Write($"{file.Line.ToString().PadLeft(lineNumberWidth)} | ");
|
||||||
|
writer.WriteLine(sourceLine);
|
||||||
|
|
||||||
|
writer.Write(new string(' ', lineNumberWidth));
|
||||||
|
writer.Write(" | ");
|
||||||
|
writer.Write(new string(' ', file.Column - 1));
|
||||||
|
writer.Write(color);
|
||||||
|
writer.Write(new string('^', Math.Max(1, file.Length)));
|
||||||
|
writer.WriteLine(Ansi.Reset);
|
||||||
|
}
|
||||||
|
|
||||||
|
writer.WriteLine($"{new string(' ', lineNumberWidth)} |");
|
||||||
|
|
||||||
|
if (!string.IsNullOrWhiteSpace(diagnostic.Help))
|
||||||
|
{
|
||||||
|
writer.WriteLine($" = help: {diagnostic.Help}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string? TryReadLine(string file, int line)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
using var reader = new StreamReader(file);
|
||||||
|
for (var i = 1; i < line; i++)
|
||||||
|
{
|
||||||
|
if (reader.ReadLine() == null)
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return reader.ReadLine();
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class Ansi
|
||||||
|
{
|
||||||
|
public const string Reset = "\e[0m";
|
||||||
|
public const string Red = "\e[31m";
|
||||||
|
public const string Yellow = "\e[33m";
|
||||||
|
public const string Cyan = "\e[36m";
|
||||||
|
}
|
||||||
|
}
|
||||||
1136
compiler/Generator.cs
Normal file
1136
compiler/Generator.cs
Normal file
File diff suppressed because it is too large
Load Diff
446
compiler/ModuleGraph.cs
Normal file
446
compiler/ModuleGraph.cs
Normal file
@@ -0,0 +1,446 @@
|
|||||||
|
using System.Diagnostics;
|
||||||
|
using System.Diagnostics.CodeAnalysis;
|
||||||
|
|
||||||
|
namespace Compiler;
|
||||||
|
|
||||||
|
public class ModuleGraph
|
||||||
|
{
|
||||||
|
public static Builder CreateBuilder() => new();
|
||||||
|
|
||||||
|
private ModuleGraph(Dictionary<string, Module> modules)
|
||||||
|
{
|
||||||
|
this.modules = modules;
|
||||||
|
}
|
||||||
|
|
||||||
|
private readonly Dictionary<string, Module> modules;
|
||||||
|
|
||||||
|
public List<Module> GetModules()
|
||||||
|
{
|
||||||
|
return modules.Values.ToList();
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool TryResolveIdentifier(string moduleName, string identifierName, bool searchPrivate, [NotNullWhen(true)] out Module.IdentifierInfo? info)
|
||||||
|
{
|
||||||
|
if (!TryResolveModule(moduleName, out var module))
|
||||||
|
{
|
||||||
|
info = null;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!module.TryResolveIdentifier(identifierName, searchPrivate, out info))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool TryResolveType(string moduleName, string typeName, bool searchPrivate, [NotNullWhen(true)] out Module.TypeInfo? info)
|
||||||
|
{
|
||||||
|
if (!TryResolveModule(moduleName, out var module))
|
||||||
|
{
|
||||||
|
info = null;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!module.TryResolveType(typeName, searchPrivate, out info))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool TryResolveModule(string moduleName, [NotNullWhen(true)] out Module? module)
|
||||||
|
{
|
||||||
|
module = modules.GetValueOrDefault(moduleName);
|
||||||
|
return module != null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public class Builder
|
||||||
|
{
|
||||||
|
private readonly List<Ast> asts = [];
|
||||||
|
private readonly List<Manifest> manifests = [];
|
||||||
|
|
||||||
|
public void AddAst(Ast ast)
|
||||||
|
{
|
||||||
|
asts.Add(ast);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void AddManifest(Manifest manifest)
|
||||||
|
{
|
||||||
|
manifests.Add(manifest);
|
||||||
|
}
|
||||||
|
|
||||||
|
public ModuleGraph? Build(out List<Diagnostic> diagnostics)
|
||||||
|
{
|
||||||
|
diagnostics = [];
|
||||||
|
|
||||||
|
var modules = new Dictionary<string, Module>();
|
||||||
|
|
||||||
|
foreach (var manifest in manifests)
|
||||||
|
{
|
||||||
|
foreach (var (moduleName, manifestModule) in manifest.Modules)
|
||||||
|
{
|
||||||
|
var module = GetOrCreateModule(moduleName);
|
||||||
|
|
||||||
|
foreach (var (name, type) in manifestModule.Types)
|
||||||
|
{
|
||||||
|
switch (type)
|
||||||
|
{
|
||||||
|
case Manifest.Module.TypeInfoStruct s:
|
||||||
|
{
|
||||||
|
var info = new Module.TypeInfoStruct(Module.DefinitionSource.Imported, s.Exported, s.Packed);
|
||||||
|
var fields = s.Fields.Select(x => new Module.TypeInfoStruct.Field(x.Name, x.Type)).ToList();
|
||||||
|
info.SetFields(fields);
|
||||||
|
module.AddType(name, info);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case Manifest.Module.TypeInfoEnum e:
|
||||||
|
{
|
||||||
|
var info = new Module.TypeInfoEnum(Module.DefinitionSource.Imported, e.Exported);
|
||||||
|
var variants = e.Variants.Select(v => new Module.TypeInfoEnum.Variant(v.Name, v.Type)).ToList();
|
||||||
|
info.SetVariants(variants);
|
||||||
|
module.AddType(name, info);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
throw new ArgumentOutOfRangeException(nameof(type));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var (name, identifier) in manifestModule.Identifiers)
|
||||||
|
{
|
||||||
|
module.AddIdentifier(name, new Module.IdentifierInfo(Module.DefinitionSource.Imported, identifier.Exported, identifier.Extern, identifier.Type, identifier.MangledName));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var ast in asts)
|
||||||
|
{
|
||||||
|
var module = GetOrCreateModule(ast.ModuleName.Ident);
|
||||||
|
|
||||||
|
foreach (var structDef in ast.Definitions.OfType<NodeDefinitionStruct>())
|
||||||
|
{
|
||||||
|
module.AddType(structDef.Name.Ident, new Module.TypeInfoStruct(Module.DefinitionSource.Internal, structDef.Exported, structDef.Packed));
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var enumDef in ast.Definitions.OfType<NodeDefinitionEnum>())
|
||||||
|
{
|
||||||
|
module.AddType(enumDef.Name.Ident, new Module.TypeInfoEnum(Module.DefinitionSource.Internal, enumDef.Exported));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var ast in asts)
|
||||||
|
{
|
||||||
|
var module = GetOrCreateModule(ast.ModuleName.Ident);
|
||||||
|
|
||||||
|
foreach (var structDef in ast.Definitions.OfType<NodeDefinitionStruct>())
|
||||||
|
{
|
||||||
|
if (!module.TryResolveType(structDef.Name.Ident, true, out var typeInfo))
|
||||||
|
throw new UnreachableException($"{nameof(typeInfo)} should always be registered");
|
||||||
|
|
||||||
|
if (typeInfo is Module.TypeInfoStruct structType)
|
||||||
|
{
|
||||||
|
var fields = structDef.Fields.Select(f => new Module.TypeInfoStruct.Field(f.Name.Ident, ResolveType(f.Type, module.Name))).ToList();
|
||||||
|
structType.SetFields(fields);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var enumDef in ast.Definitions.OfType<NodeDefinitionEnum>())
|
||||||
|
{
|
||||||
|
if (!module.TryResolveType(enumDef.Name.Ident, true, out var typeInfo))
|
||||||
|
throw new UnreachableException($"{nameof(typeInfo)} should always be registered");
|
||||||
|
|
||||||
|
if (typeInfo is Module.TypeInfoEnum enumType)
|
||||||
|
{
|
||||||
|
var variants = enumDef.Variants.Select(v => new Module.TypeInfoEnum.Variant(v.Name.Ident, v.Type == null ? null : ResolveType(v.Type, module.Name))).ToList();
|
||||||
|
enumType.SetVariants(variants);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var ast in asts)
|
||||||
|
{
|
||||||
|
var module = GetOrCreateModule(ast.ModuleName.Ident);
|
||||||
|
|
||||||
|
foreach (var funcDef in ast.Definitions.OfType<NodeDefinitionFunc>())
|
||||||
|
{
|
||||||
|
var parameters = funcDef.Parameters.Select(x => ResolveType(x.Type, module.Name)).ToList();
|
||||||
|
var returnType = funcDef.ReturnType == null ? NubTypeVoid.Instance : ResolveType(funcDef.ReturnType, module.Name);
|
||||||
|
var funcType = NubTypeFunc.Get(parameters, returnType);
|
||||||
|
var info = new Module.IdentifierInfo(Module.DefinitionSource.Internal, funcDef.Exported, false, funcType, NameMangler.Mangle(module.Name, funcDef.Name.Ident, funcType));
|
||||||
|
module.AddIdentifier(funcDef.Name.Ident, info);
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var funcDef in ast.Definitions.OfType<NodeDefinitionExternFunc>())
|
||||||
|
{
|
||||||
|
var parameters = funcDef.Parameters.Select(x => ResolveType(x.Type, module.Name)).ToList();
|
||||||
|
var returnType = funcDef.ReturnType == null ? NubTypeVoid.Instance : ResolveType(funcDef.ReturnType, module.Name);
|
||||||
|
var funcType = NubTypeFunc.Get(parameters, returnType);
|
||||||
|
var info = new Module.IdentifierInfo(Module.DefinitionSource.Internal, funcDef.Exported, true, funcType, funcDef.Name.Ident);
|
||||||
|
module.AddIdentifier(funcDef.Name.Ident, info);
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var globalVariable in ast.Definitions.OfType<NodeDefinitionGlobalVariable>())
|
||||||
|
{
|
||||||
|
var type = ResolveType(globalVariable.Type, module.Name);
|
||||||
|
var info = new Module.IdentifierInfo(Module.DefinitionSource.Internal, globalVariable.Exported, false, type, NameMangler.Mangle(module.Name, globalVariable.Name.Ident, type));
|
||||||
|
module.AddIdentifier(globalVariable.Name.Ident, info);
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var globalVariable in ast.Definitions.OfType<NodeDefinitionExternGlobalVariable>())
|
||||||
|
{
|
||||||
|
var type = ResolveType(globalVariable.Type, module.Name);
|
||||||
|
var info = new Module.IdentifierInfo(Module.DefinitionSource.Internal, globalVariable.Exported, true, type, NameMangler.Mangle(module.Name, globalVariable.Name.Ident, type));
|
||||||
|
module.AddIdentifier(globalVariable.Name.Ident, info);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (diagnostics.Any(x => x.Severity == Diagnostic.DiagnosticSeverity.Error))
|
||||||
|
return null;
|
||||||
|
|
||||||
|
return new ModuleGraph(modules);
|
||||||
|
|
||||||
|
NubType ResolveType(NodeType node, string currentModule)
|
||||||
|
{
|
||||||
|
return node switch
|
||||||
|
{
|
||||||
|
NodeTypeBool => NubTypeBool.Instance,
|
||||||
|
NodeTypeNamed type => ResolveNamedType(type, currentModule),
|
||||||
|
NodeTypeAnonymousStruct type => NubTypeAnonymousStruct.Get(type.Fields.Select(x => new NubTypeAnonymousStruct.Field(x.Name.Ident, ResolveType(x.Type, currentModule))).ToList()),
|
||||||
|
NodeTypeFunc type => NubTypeFunc.Get(type.Parameters.Select(x => ResolveType(x, currentModule)).ToList(), ResolveType(type.ReturnType, currentModule)),
|
||||||
|
NodeTypePointer type => NubTypePointer.Get(ResolveType(type.To, currentModule)),
|
||||||
|
NodeTypeSInt type => NubTypeSInt.Get(type.Width),
|
||||||
|
NodeTypeUInt type => NubTypeUInt.Get(type.Width),
|
||||||
|
NodeTypeString => NubTypeString.Instance,
|
||||||
|
NodeTypeChar => NubTypeChar.Instance,
|
||||||
|
NodeTypeVoid => NubTypeVoid.Instance,
|
||||||
|
NodeTypeArray type => NubTypeArray.Get(ResolveType(type.ElementType, currentModule)),
|
||||||
|
_ => throw new ArgumentOutOfRangeException(nameof(node))
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
NubType ResolveNamedType(NodeTypeNamed type, string currentModule)
|
||||||
|
{
|
||||||
|
return type.Sections.Count switch
|
||||||
|
{
|
||||||
|
3 => ResolveThreePartType(type.Sections[0], type.Sections[1], type.Sections[2], currentModule),
|
||||||
|
2 => ResolveTwoPartType(type.Sections[0], type.Sections[1], currentModule),
|
||||||
|
1 => ResolveOnePartType(type.Sections[0], currentModule),
|
||||||
|
_ => throw BasicError("Invalid type name")
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
NubType ResolveThreePartType(TokenIdent first, TokenIdent second, TokenIdent third, string currentModule)
|
||||||
|
{
|
||||||
|
var module = ResolveModule(first);
|
||||||
|
if (!module.TryResolveType(second.Ident, currentModule == module.Name, out var typeInfo))
|
||||||
|
throw BasicError($"Named type '{module.Name}::{second.Ident}' not found");
|
||||||
|
|
||||||
|
if (typeInfo is not Module.TypeInfoEnum enumInfo)
|
||||||
|
throw BasicError($"'{module.Name}::{second.Ident}' is not an enum");
|
||||||
|
|
||||||
|
var variant = enumInfo.Variants.FirstOrDefault(v => v.Name == third.Ident);
|
||||||
|
if (variant == null)
|
||||||
|
throw BasicError($"Enum '{module.Name}::{second.Ident}' does not have a variant named '{third.Ident}'");
|
||||||
|
|
||||||
|
return NubTypeEnumVariant.Get(NubTypeEnum.Get(module.Name, second.Ident), third.Ident);
|
||||||
|
}
|
||||||
|
|
||||||
|
NubType ResolveTwoPartType(TokenIdent first, TokenIdent second, string currentModule)
|
||||||
|
{
|
||||||
|
if (TryResolveEnumVariant(currentModule, first.Ident, second.Ident, out var variant))
|
||||||
|
return variant;
|
||||||
|
|
||||||
|
var module = ResolveModule(first);
|
||||||
|
if (!module.TryResolveType(second.Ident, currentModule == module.Name, out var typeInfo))
|
||||||
|
throw BasicError($"Named type '{module.Name}::{second.Ident}' not found");
|
||||||
|
|
||||||
|
return typeInfo switch
|
||||||
|
{
|
||||||
|
Module.TypeInfoStruct => NubTypeStruct.Get(module.Name, second.Ident),
|
||||||
|
Module.TypeInfoEnum => NubTypeEnum.Get(module.Name, second.Ident),
|
||||||
|
_ => throw new ArgumentOutOfRangeException(nameof(typeInfo))
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
NubType ResolveOnePartType(TokenIdent name, string currentModule)
|
||||||
|
{
|
||||||
|
if (!modules.TryGetValue(currentModule, out var module))
|
||||||
|
throw BasicError($"Module '{currentModule}' not found");
|
||||||
|
|
||||||
|
if (!module.TryResolveType(name.Ident, true, out var typeInfo))
|
||||||
|
throw BasicError($"Named type '{module.Name}::{name.Ident}' not found");
|
||||||
|
|
||||||
|
return typeInfo switch
|
||||||
|
{
|
||||||
|
Module.TypeInfoStruct => NubTypeStruct.Get(module.Name, name.Ident),
|
||||||
|
Module.TypeInfoEnum => NubTypeEnum.Get(module.Name, name.Ident),
|
||||||
|
_ => throw new ArgumentOutOfRangeException(nameof(typeInfo))
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
Module ResolveModule(TokenIdent name)
|
||||||
|
{
|
||||||
|
if (!modules.TryGetValue(name.Ident, out var module))
|
||||||
|
throw BasicError($"Module '{name.Ident}' not found");
|
||||||
|
|
||||||
|
return module;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool TryResolveEnumVariant(string moduleName, string enumName, string variantName, [NotNullWhen(true)] out NubType? result)
|
||||||
|
{
|
||||||
|
result = null;
|
||||||
|
|
||||||
|
if (!modules.TryGetValue(moduleName, out var module))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
if (!module.TryResolveType(enumName, true, out var typeInfo))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
if (typeInfo is not Module.TypeInfoEnum enumInfo)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
var variant = enumInfo.Variants.FirstOrDefault(v => v.Name == variantName);
|
||||||
|
if (variant == null)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
result = NubTypeEnumVariant.Get(
|
||||||
|
NubTypeEnum.Get(moduleName, enumName),
|
||||||
|
variantName);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
Exception BasicError(string message)
|
||||||
|
{
|
||||||
|
return new CompileException(Diagnostic.Error(message).Build());
|
||||||
|
}
|
||||||
|
|
||||||
|
Module GetOrCreateModule(string name)
|
||||||
|
{
|
||||||
|
if (!modules.TryGetValue(name, out var module))
|
||||||
|
{
|
||||||
|
module = new Module(name);
|
||||||
|
modules.Add(name, module);
|
||||||
|
}
|
||||||
|
|
||||||
|
return module;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class Module(string name)
|
||||||
|
{
|
||||||
|
public string Name { get; } = name;
|
||||||
|
|
||||||
|
private Dictionary<string, TypeInfo> types = new();
|
||||||
|
private Dictionary<string, IdentifierInfo> identifiers = new();
|
||||||
|
|
||||||
|
public IReadOnlyDictionary<string, TypeInfo> GetTypes() => types;
|
||||||
|
public IReadOnlyDictionary<string, IdentifierInfo> GetIdentifiers() => identifiers;
|
||||||
|
|
||||||
|
public bool TryResolveType(string name, bool searchPrivate, [NotNullWhen(true)] out TypeInfo? customType)
|
||||||
|
{
|
||||||
|
var info = types.GetValueOrDefault(name);
|
||||||
|
if (info == null)
|
||||||
|
{
|
||||||
|
customType = null;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (searchPrivate || info.Source == DefinitionSource.Internal)
|
||||||
|
{
|
||||||
|
customType = info;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
customType = null;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool TryResolveIdentifier(string name, bool searchPrivate, [NotNullWhen(true)] out IdentifierInfo? identifierType)
|
||||||
|
{
|
||||||
|
var info = identifiers.GetValueOrDefault(name);
|
||||||
|
if (info == null)
|
||||||
|
{
|
||||||
|
identifierType = null;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (searchPrivate || info.Source == DefinitionSource.Internal)
|
||||||
|
{
|
||||||
|
identifierType = info;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
identifierType = null;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void AddType(string name, TypeInfo info)
|
||||||
|
{
|
||||||
|
types.Add(name, info);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void AddIdentifier(string name, IdentifierInfo info)
|
||||||
|
{
|
||||||
|
identifiers.Add(name, info);
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum DefinitionSource
|
||||||
|
{
|
||||||
|
Internal,
|
||||||
|
Imported,
|
||||||
|
}
|
||||||
|
|
||||||
|
public class IdentifierInfo(DefinitionSource source, bool exported, bool @extern, NubType type, string mangledName)
|
||||||
|
{
|
||||||
|
public DefinitionSource Source { get; } = source;
|
||||||
|
public bool Exported { get; } = exported;
|
||||||
|
public bool Extern { get; } = @extern;
|
||||||
|
public NubType Type { get; } = type;
|
||||||
|
public string MangledName { get; } = mangledName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public abstract class TypeInfo(DefinitionSource source, bool exported)
|
||||||
|
{
|
||||||
|
public DefinitionSource Source { get; } = source;
|
||||||
|
public bool Exported { get; } = exported;
|
||||||
|
}
|
||||||
|
|
||||||
|
public class TypeInfoStruct(DefinitionSource source, bool exported, bool packed) : TypeInfo(source, exported)
|
||||||
|
{
|
||||||
|
private IReadOnlyList<Field>? fields;
|
||||||
|
|
||||||
|
public bool Packed { get; } = packed;
|
||||||
|
public IReadOnlyList<Field> Fields => fields ?? throw new InvalidOperationException("Fields has not been set yet");
|
||||||
|
|
||||||
|
public void SetFields(IReadOnlyList<Field> fields)
|
||||||
|
{
|
||||||
|
this.fields = fields;
|
||||||
|
}
|
||||||
|
|
||||||
|
public class Field(string name, NubType type)
|
||||||
|
{
|
||||||
|
public string Name { get; } = name;
|
||||||
|
public NubType Type { get; } = type;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class TypeInfoEnum(DefinitionSource source, bool exported) : TypeInfo(source, exported)
|
||||||
|
{
|
||||||
|
private IReadOnlyList<Variant>? variants;
|
||||||
|
|
||||||
|
public IReadOnlyList<Variant> Variants => variants ?? throw new InvalidOperationException("Fields has not been set yet");
|
||||||
|
|
||||||
|
public void SetVariants(IReadOnlyList<Variant> variants)
|
||||||
|
{
|
||||||
|
this.variants = variants;
|
||||||
|
}
|
||||||
|
|
||||||
|
public class Variant(string name, NubType? type)
|
||||||
|
{
|
||||||
|
public string Name { get; } = name;
|
||||||
|
public NubType? Type { get; } = type;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,16 +0,0 @@
|
|||||||
<Project Sdk="Microsoft.NET.Sdk">
|
|
||||||
|
|
||||||
<PropertyGroup>
|
|
||||||
<AssemblyName>nubc</AssemblyName>
|
|
||||||
<OutputType>Exe</OutputType>
|
|
||||||
<TargetFramework>net9.0</TargetFramework>
|
|
||||||
<ImplicitUsings>enable</ImplicitUsings>
|
|
||||||
<Nullable>enable</Nullable>
|
|
||||||
<PublishAot>true</PublishAot>
|
|
||||||
</PropertyGroup>
|
|
||||||
|
|
||||||
<ItemGroup>
|
|
||||||
<ProjectReference Include="..\NubLang\NubLang.csproj" />
|
|
||||||
</ItemGroup>
|
|
||||||
|
|
||||||
</Project>
|
|
||||||
@@ -1,89 +0,0 @@
|
|||||||
using NubLang.Ast;
|
|
||||||
using NubLang.Diagnostics;
|
|
||||||
using NubLang.Generation;
|
|
||||||
using NubLang.Modules;
|
|
||||||
using NubLang.Syntax;
|
|
||||||
|
|
||||||
var diagnostics = new List<Diagnostic>();
|
|
||||||
var syntaxTrees = new List<SyntaxTree>();
|
|
||||||
|
|
||||||
var tokenizer = new Tokenizer();
|
|
||||||
var parser = new Parser();
|
|
||||||
var generator = new LlvmSharpGenerator();
|
|
||||||
|
|
||||||
foreach (var file in args)
|
|
||||||
{
|
|
||||||
var tokens = tokenizer.Tokenize(file, File.ReadAllText(file));
|
|
||||||
diagnostics.AddRange(tokenizer.Diagnostics);
|
|
||||||
|
|
||||||
var syntaxTree = parser.Parse(tokens);
|
|
||||||
diagnostics.AddRange(parser.Diagnostics);
|
|
||||||
|
|
||||||
syntaxTrees.Add(syntaxTree);
|
|
||||||
}
|
|
||||||
|
|
||||||
foreach (var diagnostic in diagnostics)
|
|
||||||
{
|
|
||||||
Console.Error.WriteLine(diagnostic.FormatANSI());
|
|
||||||
}
|
|
||||||
|
|
||||||
if (diagnostics.Any(diagnostic => diagnostic.Severity == DiagnosticSeverity.Error))
|
|
||||||
{
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
diagnostics.Clear();
|
|
||||||
|
|
||||||
ModuleRepository moduleRepository;
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
moduleRepository = ModuleRepository.Create(syntaxTrees);
|
|
||||||
}
|
|
||||||
catch (CompileException e)
|
|
||||||
{
|
|
||||||
Console.Error.WriteLine(e.Diagnostic.FormatANSI());
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
var compilationUnits = new List<List<TopLevelNode>>();
|
|
||||||
|
|
||||||
for (var i = 0; i < args.Length; i++)
|
|
||||||
{
|
|
||||||
var typeChecker = new TypeChecker();
|
|
||||||
var compilationUnit = typeChecker.Check(syntaxTrees[i], moduleRepository);
|
|
||||||
|
|
||||||
compilationUnits.Add(compilationUnit);
|
|
||||||
diagnostics.AddRange(typeChecker.Diagnostics);
|
|
||||||
}
|
|
||||||
|
|
||||||
foreach (var diagnostic in diagnostics)
|
|
||||||
{
|
|
||||||
Console.Error.WriteLine(diagnostic.FormatANSI());
|
|
||||||
}
|
|
||||||
|
|
||||||
if (diagnostics.Any(diagnostic => diagnostic.Severity == DiagnosticSeverity.Error))
|
|
||||||
{
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
diagnostics.Clear();
|
|
||||||
|
|
||||||
Directory.CreateDirectory(".build");
|
|
||||||
|
|
||||||
for (var i = 0; i < args.Length; i++)
|
|
||||||
{
|
|
||||||
var file = args[i];
|
|
||||||
var compilationUnit = compilationUnits[i];
|
|
||||||
|
|
||||||
var directory = Path.GetDirectoryName(file);
|
|
||||||
if (!string.IsNullOrWhiteSpace(directory))
|
|
||||||
{
|
|
||||||
Directory.CreateDirectory(Path.Combine(".build", directory));
|
|
||||||
}
|
|
||||||
|
|
||||||
var path = Path.Combine(".build", Path.ChangeExtension(file, "ll"));
|
|
||||||
generator.Emit(compilationUnit, moduleRepository, file, path);
|
|
||||||
}
|
|
||||||
|
|
||||||
return 0;
|
|
||||||
@@ -1,114 +0,0 @@
|
|||||||
using NubLang.Ast;
|
|
||||||
using NubLang.Syntax;
|
|
||||||
using OmniSharp.Extensions.LanguageServer.Protocol.Models;
|
|
||||||
using Range = OmniSharp.Extensions.LanguageServer.Protocol.Models.Range;
|
|
||||||
|
|
||||||
namespace NubLang.LSP;
|
|
||||||
|
|
||||||
public static class AstExtensions
|
|
||||||
{
|
|
||||||
public static Location ToLocation(this Node node)
|
|
||||||
{
|
|
||||||
if (node.Tokens.Count == 0)
|
|
||||||
{
|
|
||||||
return new Location();
|
|
||||||
}
|
|
||||||
|
|
||||||
return new Location
|
|
||||||
{
|
|
||||||
Uri = node.Tokens.First().Span.SourcePath,
|
|
||||||
Range = new Range(node.Tokens.First().Span.StartLine - 1, node.Tokens.First().Span.StartColumn - 1, node.Tokens.Last().Span.EndLine - 1, node.Tokens.Last().Span.EndColumn - 1)
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
public static Location ToLocation(this Token token)
|
|
||||||
{
|
|
||||||
return new Location
|
|
||||||
{
|
|
||||||
Uri = token.Span.SourcePath,
|
|
||||||
Range = new Range(token.Span.StartLine - 1, token.Span.StartColumn - 1, token.Span.EndLine - 1, token.Span.EndColumn - 1)
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
public static bool ContainsPosition(this Token token, int line, int character)
|
|
||||||
{
|
|
||||||
var startLine = token.Span.StartLine - 1;
|
|
||||||
var startChar = token.Span.StartColumn - 1;
|
|
||||||
var endLine = token.Span.EndLine - 1;
|
|
||||||
var endChar = token.Span.EndColumn - 1;
|
|
||||||
|
|
||||||
if (line < startLine || line > endLine) return false;
|
|
||||||
|
|
||||||
if (line > startLine && line < endLine) return true;
|
|
||||||
|
|
||||||
if (startLine == endLine)
|
|
||||||
{
|
|
||||||
return character >= startChar && character <= endChar;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (line == startLine)
|
|
||||||
{
|
|
||||||
return character >= startChar;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (line == endLine)
|
|
||||||
{
|
|
||||||
return character <= endChar;
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static bool ContainsPosition(this Node node, int line, int character)
|
|
||||||
{
|
|
||||||
if (node.Tokens.Count == 0)
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
var span = node.Tokens.First().Span;
|
|
||||||
|
|
||||||
var startLine = span.StartLine - 1;
|
|
||||||
var startChar = span.StartColumn - 1;
|
|
||||||
var endLine = span.EndLine - 1;
|
|
||||||
var endChar = span.EndColumn - 1;
|
|
||||||
|
|
||||||
if (line < startLine || line > endLine) return false;
|
|
||||||
|
|
||||||
if (line > startLine && line < endLine) return true;
|
|
||||||
|
|
||||||
if (startLine == endLine)
|
|
||||||
{
|
|
||||||
return character >= startChar && character <= endChar;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (line == startLine)
|
|
||||||
{
|
|
||||||
return character >= startChar;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (line == endLine)
|
|
||||||
{
|
|
||||||
return character <= endChar;
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static FuncNode? FunctionAtPosition(this List<TopLevelNode> compilationUnit, int line, int character)
|
|
||||||
{
|
|
||||||
return compilationUnit
|
|
||||||
.OfType<FuncNode>()
|
|
||||||
.FirstOrDefault(x => x.ContainsPosition(line, character));
|
|
||||||
}
|
|
||||||
|
|
||||||
public static Node? DeepestNodeAtPosition(this List<TopLevelNode> compilationUnit, int line, int character)
|
|
||||||
{
|
|
||||||
return compilationUnit
|
|
||||||
.SelectMany(x => x.DescendantsAndSelf())
|
|
||||||
.Where(n => n.ContainsPosition(line, character))
|
|
||||||
.OrderBy(n => n.Tokens.First().Span.StartLine)
|
|
||||||
.ThenBy(n => n.Tokens.First().Span.StartColumn)
|
|
||||||
.LastOrDefault();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,188 +0,0 @@
|
|||||||
using NubLang.Ast;
|
|
||||||
using NubLang.Modules;
|
|
||||||
using OmniSharp.Extensions.LanguageServer.Protocol.Client.Capabilities;
|
|
||||||
using OmniSharp.Extensions.LanguageServer.Protocol.Document;
|
|
||||||
using OmniSharp.Extensions.LanguageServer.Protocol.Models;
|
|
||||||
|
|
||||||
namespace NubLang.LSP;
|
|
||||||
|
|
||||||
internal class CompletionHandler(WorkspaceManager workspaceManager) : CompletionHandlerBase
|
|
||||||
{
|
|
||||||
private readonly CompletionItem[] _definitionSnippets =
|
|
||||||
[
|
|
||||||
new()
|
|
||||||
{
|
|
||||||
Kind = CompletionItemKind.Keyword,
|
|
||||||
Label = "func",
|
|
||||||
InsertTextFormat = InsertTextFormat.Snippet,
|
|
||||||
InsertText = "func ${1:name}(${2:params})\n{\n $0\n}",
|
|
||||||
},
|
|
||||||
new()
|
|
||||||
{
|
|
||||||
Kind = CompletionItemKind.Keyword,
|
|
||||||
Label = "struct",
|
|
||||||
InsertTextFormat = InsertTextFormat.Snippet,
|
|
||||||
InsertText = "struct ${1:name}\n{\n $0\n}",
|
|
||||||
},
|
|
||||||
new()
|
|
||||||
{
|
|
||||||
Kind = CompletionItemKind.Keyword,
|
|
||||||
Label = "module",
|
|
||||||
InsertTextFormat = InsertTextFormat.Snippet,
|
|
||||||
InsertText = "module \"$0\"",
|
|
||||||
}
|
|
||||||
];
|
|
||||||
|
|
||||||
private readonly CompletionItem[] _statementSnippets =
|
|
||||||
[
|
|
||||||
new()
|
|
||||||
{
|
|
||||||
Kind = CompletionItemKind.Keyword,
|
|
||||||
Label = "let",
|
|
||||||
InsertTextFormat = InsertTextFormat.Snippet,
|
|
||||||
InsertText = "let ${1:name} = $0",
|
|
||||||
},
|
|
||||||
new()
|
|
||||||
{
|
|
||||||
Kind = CompletionItemKind.Keyword,
|
|
||||||
Label = "if",
|
|
||||||
InsertTextFormat = InsertTextFormat.Snippet,
|
|
||||||
InsertText = "if ${1:condition}\n{\n $0\n}",
|
|
||||||
},
|
|
||||||
new()
|
|
||||||
{
|
|
||||||
Kind = CompletionItemKind.Keyword,
|
|
||||||
Label = "else if",
|
|
||||||
InsertTextFormat = InsertTextFormat.Snippet,
|
|
||||||
InsertText = "else if ${1:condition}\n{\n $0\n}",
|
|
||||||
},
|
|
||||||
new()
|
|
||||||
{
|
|
||||||
Kind = CompletionItemKind.Keyword,
|
|
||||||
Label = "else",
|
|
||||||
InsertTextFormat = InsertTextFormat.Snippet,
|
|
||||||
InsertText = "else\n{\n $0\n}",
|
|
||||||
},
|
|
||||||
new()
|
|
||||||
{
|
|
||||||
Kind = CompletionItemKind.Keyword,
|
|
||||||
Label = "while",
|
|
||||||
InsertTextFormat = InsertTextFormat.Snippet,
|
|
||||||
InsertText = "while ${1:condition}\n{\n $0\n}",
|
|
||||||
},
|
|
||||||
new()
|
|
||||||
{
|
|
||||||
Kind = CompletionItemKind.Keyword,
|
|
||||||
Label = "for",
|
|
||||||
InsertTextFormat = InsertTextFormat.Snippet,
|
|
||||||
InsertText = "for ${1:name}, ${2:index} in ${3:array}\n{\n $0\n}",
|
|
||||||
},
|
|
||||||
new()
|
|
||||||
{
|
|
||||||
Kind = CompletionItemKind.Keyword,
|
|
||||||
Label = "return",
|
|
||||||
InsertTextFormat = InsertTextFormat.Snippet,
|
|
||||||
InsertText = "return $0",
|
|
||||||
},
|
|
||||||
new()
|
|
||||||
{
|
|
||||||
Kind = CompletionItemKind.Keyword,
|
|
||||||
Label = "defer",
|
|
||||||
InsertTextFormat = InsertTextFormat.Snippet,
|
|
||||||
InsertText = "defer $0",
|
|
||||||
}
|
|
||||||
];
|
|
||||||
|
|
||||||
protected override CompletionRegistrationOptions CreateRegistrationOptions(CompletionCapability capability, ClientCapabilities clientCapabilities)
|
|
||||||
{
|
|
||||||
return new CompletionRegistrationOptions();
|
|
||||||
}
|
|
||||||
|
|
||||||
public override Task<CompletionList> Handle(CompletionParams request, CancellationToken cancellationToken)
|
|
||||||
{
|
|
||||||
return Task.FromResult(HandleSync(request, cancellationToken));
|
|
||||||
}
|
|
||||||
|
|
||||||
private CompletionList HandleSync(CompletionParams request, CancellationToken cancellationToken)
|
|
||||||
{
|
|
||||||
var completions = new List<CompletionItem>();
|
|
||||||
|
|
||||||
var compilationUnit = workspaceManager.GetTopLevelNodes(request.TextDocument.Uri.GetFileSystemPath());
|
|
||||||
|
|
||||||
var repository = workspaceManager.GetModuleRepository();
|
|
||||||
|
|
||||||
var function = compilationUnit
|
|
||||||
.OfType<FuncNode>()
|
|
||||||
.FirstOrDefault(x => x.Body != null && x.Body.ContainsPosition(request.Position.Line, request.Position.Character));
|
|
||||||
|
|
||||||
if (function != null)
|
|
||||||
{
|
|
||||||
completions.AddRange(_statementSnippets);
|
|
||||||
|
|
||||||
foreach (var module in repository.GetAll())
|
|
||||||
{
|
|
||||||
foreach (var prototype in module.FunctionPrototypes)
|
|
||||||
{
|
|
||||||
var parameterStrings = new List<string>();
|
|
||||||
foreach (var (index, parameter) in prototype.Parameters.Index())
|
|
||||||
{
|
|
||||||
parameterStrings.AddRange($"${{{index + 1}:{parameter.NameToken.Value}}}");
|
|
||||||
}
|
|
||||||
|
|
||||||
var isCurrentModule = false;
|
|
||||||
var moduleDecl = compilationUnit.OfType<ModuleNode>().FirstOrDefault();
|
|
||||||
if (moduleDecl != null)
|
|
||||||
{
|
|
||||||
if (moduleDecl.NameToken.Value == module.Name)
|
|
||||||
{
|
|
||||||
isCurrentModule = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
completions.Add(new CompletionItem
|
|
||||||
{
|
|
||||||
Kind = CompletionItemKind.Function,
|
|
||||||
Label = isCurrentModule ? prototype.NameToken.Value : $"{module.Name}::{prototype.NameToken.Value}",
|
|
||||||
InsertTextFormat = InsertTextFormat.Snippet,
|
|
||||||
InsertText = $"{(isCurrentModule ? "" : $"{module.Name}::")}{prototype.NameToken.Value}({string.Join(", ", parameterStrings)})",
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
foreach (var parameter in function.Prototype.Parameters)
|
|
||||||
{
|
|
||||||
completions.Add(new CompletionItem
|
|
||||||
{
|
|
||||||
Kind = CompletionItemKind.Variable,
|
|
||||||
Label = parameter.NameToken.Value,
|
|
||||||
InsertText = parameter.NameToken.Value,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
var variables = function.Body!
|
|
||||||
.Descendants()
|
|
||||||
.OfType<VariableDeclarationNode>();
|
|
||||||
|
|
||||||
foreach (var variable in variables)
|
|
||||||
{
|
|
||||||
completions.Add(new CompletionItem
|
|
||||||
{
|
|
||||||
Kind = CompletionItemKind.Variable,
|
|
||||||
Label = variable.NameToken.Value,
|
|
||||||
InsertText = variable.NameToken.Value,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
completions.AddRange(_definitionSnippets);
|
|
||||||
}
|
|
||||||
|
|
||||||
return new CompletionList(completions, false);
|
|
||||||
}
|
|
||||||
|
|
||||||
public override Task<CompletionItem> Handle(CompletionItem request, CancellationToken cancellationToken)
|
|
||||||
{
|
|
||||||
return Task.FromResult(new CompletionItem());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,85 +0,0 @@
|
|||||||
using NubLang.Ast;
|
|
||||||
using OmniSharp.Extensions.LanguageServer.Protocol.Client.Capabilities;
|
|
||||||
using OmniSharp.Extensions.LanguageServer.Protocol.Document;
|
|
||||||
using OmniSharp.Extensions.LanguageServer.Protocol.Models;
|
|
||||||
|
|
||||||
namespace NubLang.LSP;
|
|
||||||
|
|
||||||
internal class DefinitionHandler(WorkspaceManager workspaceManager) : DefinitionHandlerBase
|
|
||||||
{
|
|
||||||
protected override DefinitionRegistrationOptions CreateRegistrationOptions(DefinitionCapability capability, ClientCapabilities clientCapabilities)
|
|
||||||
{
|
|
||||||
return new DefinitionRegistrationOptions();
|
|
||||||
}
|
|
||||||
|
|
||||||
public override Task<LocationOrLocationLinks?> Handle(DefinitionParams request, CancellationToken cancellationToken)
|
|
||||||
{
|
|
||||||
return Task.FromResult(HandleSync(request, cancellationToken));
|
|
||||||
}
|
|
||||||
|
|
||||||
private LocationOrLocationLinks? HandleSync(DefinitionParams request, CancellationToken cancellationToken)
|
|
||||||
{
|
|
||||||
var uri = request.TextDocument.Uri;
|
|
||||||
var topLevelNodes = workspaceManager.GetTopLevelNodes(uri.GetFileSystemPath());
|
|
||||||
|
|
||||||
var line = request.Position.Line;
|
|
||||||
var character = request.Position.Character;
|
|
||||||
|
|
||||||
var node = topLevelNodes.DeepestNodeAtPosition(line, character);
|
|
||||||
|
|
||||||
switch (node)
|
|
||||||
{
|
|
||||||
case VariableIdentifierNode variableIdentifierNode:
|
|
||||||
{
|
|
||||||
var funcNode = topLevelNodes.FunctionAtPosition(line, character);
|
|
||||||
|
|
||||||
var parameter = funcNode?.Prototype.Parameters.FirstOrDefault(x => x.NameToken.Value == variableIdentifierNode.NameToken.Value);
|
|
||||||
if (parameter != null)
|
|
||||||
{
|
|
||||||
return new LocationOrLocationLinks(parameter.NameToken.ToLocation());
|
|
||||||
}
|
|
||||||
|
|
||||||
var variable = funcNode?.Body?
|
|
||||||
.Descendants()
|
|
||||||
.OfType<VariableDeclarationNode>()
|
|
||||||
.FirstOrDefault(x => x.NameToken.Value == variableIdentifierNode.NameToken.Value);
|
|
||||||
|
|
||||||
if (variable != null)
|
|
||||||
{
|
|
||||||
return new LocationOrLocationLinks(variable.NameToken.ToLocation());
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
case LocalFuncIdentifierNode localFuncIdentifierNode:
|
|
||||||
{
|
|
||||||
var funcNode = topLevelNodes.OfType<FuncNode>().FirstOrDefault(x => x.NameToken.Value == localFuncIdentifierNode.NameToken.Value);
|
|
||||||
if (funcNode != null)
|
|
||||||
{
|
|
||||||
return new LocationOrLocationLinks(funcNode.NameToken.ToLocation());
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
case ModuleFuncIdentifierNode localFuncIdentifierNode:
|
|
||||||
{
|
|
||||||
var repository = workspaceManager.GetModuleRepository();
|
|
||||||
if (!repository.TryGet(localFuncIdentifierNode.ModuleToken, out var module))
|
|
||||||
{
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (module.TryResolveFunc(localFuncIdentifierNode.NameToken, out var func, out _))
|
|
||||||
{
|
|
||||||
return new LocationOrLocationLinks(func.NameToken.ToLocation());
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
{
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,44 +0,0 @@
|
|||||||
using OmniSharp.Extensions.LanguageServer.Protocol;
|
|
||||||
using OmniSharp.Extensions.LanguageServer.Protocol.Document;
|
|
||||||
using OmniSharp.Extensions.LanguageServer.Protocol.Models;
|
|
||||||
using OmniSharp.Extensions.LanguageServer.Protocol.Server;
|
|
||||||
using Range = OmniSharp.Extensions.LanguageServer.Protocol.Models.Range;
|
|
||||||
|
|
||||||
namespace NubLang.LSP;
|
|
||||||
|
|
||||||
public class DiagnosticsPublisher
|
|
||||||
{
|
|
||||||
private readonly ILanguageServerFacade _server;
|
|
||||||
|
|
||||||
public DiagnosticsPublisher(ILanguageServerFacade server)
|
|
||||||
{
|
|
||||||
_server = server;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Publish(DocumentUri uri, IEnumerable<Diagnostics.Diagnostic> diagnostics)
|
|
||||||
{
|
|
||||||
_server.TextDocument.PublishDiagnostics(new PublishDiagnosticsParams
|
|
||||||
{
|
|
||||||
Uri = uri,
|
|
||||||
Diagnostics = new Container<Diagnostic>(diagnostics.Select(MapDiagnostic))
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
private static Diagnostic MapDiagnostic(Diagnostics.Diagnostic nubDiagnostic)
|
|
||||||
{
|
|
||||||
return new Diagnostic
|
|
||||||
{
|
|
||||||
Severity = nubDiagnostic.Severity switch
|
|
||||||
{
|
|
||||||
Diagnostics.DiagnosticSeverity.Info => DiagnosticSeverity.Information,
|
|
||||||
Diagnostics.DiagnosticSeverity.Warning => DiagnosticSeverity.Warning,
|
|
||||||
Diagnostics.DiagnosticSeverity.Error => DiagnosticSeverity.Error,
|
|
||||||
_ => null
|
|
||||||
},
|
|
||||||
Message = $"{nubDiagnostic.Message}\n{(nubDiagnostic.Help == null ? "" : $"help: {nubDiagnostic.Help}")}",
|
|
||||||
Range = nubDiagnostic.Span.HasValue
|
|
||||||
? new Range(nubDiagnostic.Span.Value.StartLine - 1, nubDiagnostic.Span.Value.StartColumn - 1, nubDiagnostic.Span.Value.EndLine - 1, nubDiagnostic.Span.Value.EndColumn - 1)
|
|
||||||
: new Range(),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,203 +0,0 @@
|
|||||||
using System.Globalization;
|
|
||||||
using NubLang.Ast;
|
|
||||||
using NubLang.Modules;
|
|
||||||
using NubLang.Types;
|
|
||||||
using OmniSharp.Extensions.LanguageServer.Protocol.Client.Capabilities;
|
|
||||||
using OmniSharp.Extensions.LanguageServer.Protocol.Document;
|
|
||||||
using OmniSharp.Extensions.LanguageServer.Protocol.Models;
|
|
||||||
|
|
||||||
namespace NubLang.LSP;
|
|
||||||
|
|
||||||
internal class HoverHandler(WorkspaceManager workspaceManager) : HoverHandlerBase
|
|
||||||
{
|
|
||||||
protected override HoverRegistrationOptions CreateRegistrationOptions(HoverCapability capability, ClientCapabilities clientCapabilities)
|
|
||||||
{
|
|
||||||
return new HoverRegistrationOptions
|
|
||||||
{
|
|
||||||
DocumentSelector = TextDocumentSelector.ForLanguage("nub")
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
public override Task<Hover?> Handle(HoverParams request, CancellationToken cancellationToken)
|
|
||||||
{
|
|
||||||
return Task.FromResult(HandleSync(request, cancellationToken));
|
|
||||||
}
|
|
||||||
|
|
||||||
private Hover? HandleSync(HoverParams request, CancellationToken cancellationToken)
|
|
||||||
{
|
|
||||||
var topLevelNodes = workspaceManager.GetTopLevelNodes(request.TextDocument.Uri.GetFileSystemPath());
|
|
||||||
|
|
||||||
var moduleDecl = topLevelNodes.OfType<ModuleNode>().FirstOrDefault();
|
|
||||||
if (moduleDecl == null)
|
|
||||||
{
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
var moduleRepository = workspaceManager.GetModuleRepository();
|
|
||||||
if (!moduleRepository.TryGet(moduleDecl.NameToken, out var module))
|
|
||||||
{
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
var line = request.Position.Line;
|
|
||||||
var character = request.Position.Character;
|
|
||||||
|
|
||||||
var hoveredNode = topLevelNodes.DeepestNodeAtPosition(line, character);
|
|
||||||
|
|
||||||
if (hoveredNode == null)
|
|
||||||
{
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
var message = CreateMessage(hoveredNode, moduleRepository, module, line, character);
|
|
||||||
if (message == null)
|
|
||||||
{
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
return new Hover
|
|
||||||
{
|
|
||||||
Contents = new MarkedStringsOrMarkupContent(new MarkupContent
|
|
||||||
{
|
|
||||||
Value = message,
|
|
||||||
Kind = MarkupKind.Markdown,
|
|
||||||
})
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
private static string? CreateMessage(Node hoveredNode, ModuleRepository repository, ModuleRepository.Module currentModule, int line, int character)
|
|
||||||
{
|
|
||||||
return hoveredNode switch
|
|
||||||
{
|
|
||||||
FuncNode funcNode => CreateFuncPrototypeMessage(funcNode.Prototype),
|
|
||||||
FuncPrototypeNode funcPrototypeNode => CreateFuncPrototypeMessage(funcPrototypeNode),
|
|
||||||
LocalFuncIdentifierNode funcIdentifierNode => CreateLocalFuncIdentifierMessage(funcIdentifierNode, currentModule),
|
|
||||||
ModuleFuncIdentifierNode funcIdentifierNode => CreateModuleFuncIdentifierMessage(funcIdentifierNode, repository),
|
|
||||||
FuncParameterNode funcParameterNode => CreateTypeNameMessage("Function parameter", funcParameterNode.NameToken.Value, funcParameterNode.Type),
|
|
||||||
VariableIdentifierNode variableIdentifierNode => CreateTypeNameMessage("Variable", variableIdentifierNode.NameToken.Value, variableIdentifierNode.Type),
|
|
||||||
VariableDeclarationNode variableDeclarationNode => CreateTypeNameMessage("Variable declaration", variableDeclarationNode.NameToken.Value, variableDeclarationNode.Type),
|
|
||||||
StructFieldAccessNode structFieldAccessNode => CreateTypeNameMessage("Struct field", $"{structFieldAccessNode.Target.Type}.{structFieldAccessNode.FieldToken.Value}", structFieldAccessNode.Type),
|
|
||||||
CStringLiteralNode cStringLiteralNode => CreateLiteralMessage(cStringLiteralNode.Type, '"' + cStringLiteralNode.Value + '"'),
|
|
||||||
StringLiteralNode stringLiteralNode => CreateLiteralMessage(stringLiteralNode.Type, '"' + stringLiteralNode.Value + '"'),
|
|
||||||
BoolLiteralNode boolLiteralNode => CreateLiteralMessage(boolLiteralNode.Type, boolLiteralNode.Value.ToString()),
|
|
||||||
Float32LiteralNode float32LiteralNode => CreateLiteralMessage(float32LiteralNode.Type, float32LiteralNode.Value.ToString(CultureInfo.InvariantCulture)),
|
|
||||||
Float64LiteralNode float64LiteralNode => CreateLiteralMessage(float64LiteralNode.Type, float64LiteralNode.Value.ToString(CultureInfo.InvariantCulture)),
|
|
||||||
I8LiteralNode i8LiteralNode => CreateLiteralMessage(i8LiteralNode.Type, i8LiteralNode.Value.ToString()),
|
|
||||||
I16LiteralNode i16LiteralNode => CreateLiteralMessage(i16LiteralNode.Type, i16LiteralNode.Value.ToString()),
|
|
||||||
I32LiteralNode i32LiteralNode => CreateLiteralMessage(i32LiteralNode.Type, i32LiteralNode.Value.ToString()),
|
|
||||||
I64LiteralNode i64LiteralNode => CreateLiteralMessage(i64LiteralNode.Type, i64LiteralNode.Value.ToString()),
|
|
||||||
U8LiteralNode u8LiteralNode => CreateLiteralMessage(u8LiteralNode.Type, u8LiteralNode.Value.ToString()),
|
|
||||||
U16LiteralNode u16LiteralNode => CreateLiteralMessage(u16LiteralNode.Type, u16LiteralNode.Value.ToString()),
|
|
||||||
U32LiteralNode u32LiteralNode => CreateLiteralMessage(u32LiteralNode.Type, u32LiteralNode.Value.ToString()),
|
|
||||||
U64LiteralNode u64LiteralNode => CreateLiteralMessage(u64LiteralNode.Type, u64LiteralNode.Value.ToString()),
|
|
||||||
StructInitializerNode structInitializerNode => CreateStructInitializerMessage(structInitializerNode, line, character),
|
|
||||||
// Expressions can have a generic fallback showing the resulting type
|
|
||||||
ExpressionNode expressionNode => $"""
|
|
||||||
**Expression** `{expressionNode.GetType().Name}`
|
|
||||||
```nub
|
|
||||||
{expressionNode.Type}
|
|
||||||
```
|
|
||||||
""",
|
|
||||||
BlockNode => null,
|
|
||||||
_ => hoveredNode.GetType().Name
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
private static string CreateStructInitializerMessage(StructInitializerNode structInitializerNode, int line, int character)
|
|
||||||
{
|
|
||||||
var hoveredInitializerName = structInitializerNode
|
|
||||||
.Initializers
|
|
||||||
.Select(x => x.Key)
|
|
||||||
.FirstOrDefault(x => x.ContainsPosition(line, character));
|
|
||||||
|
|
||||||
var structType = (NubStructType)structInitializerNode.Type;
|
|
||||||
|
|
||||||
if (hoveredInitializerName != null)
|
|
||||||
{
|
|
||||||
var field = structType.Fields.FirstOrDefault(x => x.Name == hoveredInitializerName.Value);
|
|
||||||
if (field != null)
|
|
||||||
{
|
|
||||||
return $"""
|
|
||||||
**Field** in `{structType}`
|
|
||||||
```nub
|
|
||||||
{hoveredInitializerName.Value}: {field.Type}
|
|
||||||
```
|
|
||||||
""";
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
return $"""
|
|
||||||
**Field** in `{structType}`
|
|
||||||
```nub
|
|
||||||
// Field not found
|
|
||||||
```
|
|
||||||
""";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return $"**Struct initializer** `{structType}`";
|
|
||||||
}
|
|
||||||
|
|
||||||
private static string CreateLiteralMessage(NubType type, string value)
|
|
||||||
{
|
|
||||||
return $"""
|
|
||||||
**Literal** `{type}`
|
|
||||||
```nub
|
|
||||||
{value}: {type}
|
|
||||||
```
|
|
||||||
""";
|
|
||||||
}
|
|
||||||
|
|
||||||
private static string CreateTypeNameMessage(string description, string name, NubType type)
|
|
||||||
{
|
|
||||||
return $"""
|
|
||||||
**{description}** `{name}`
|
|
||||||
```nub
|
|
||||||
{name}: {type}
|
|
||||||
```
|
|
||||||
""";
|
|
||||||
}
|
|
||||||
|
|
||||||
private static string CreateLocalFuncIdentifierMessage(LocalFuncIdentifierNode funcIdentifierNode, ModuleRepository.Module currentModule)
|
|
||||||
{
|
|
||||||
if (!currentModule.TryResolveFunc(funcIdentifierNode.NameToken, out var func, out _))
|
|
||||||
{
|
|
||||||
return $"""
|
|
||||||
**Function** `{funcIdentifierNode.NameToken.Value}`
|
|
||||||
```nub
|
|
||||||
// Declaration not found
|
|
||||||
```
|
|
||||||
""";
|
|
||||||
}
|
|
||||||
|
|
||||||
return CreateFuncPrototypeMessage(func);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static string CreateModuleFuncIdentifierMessage(ModuleFuncIdentifierNode funcIdentifierNode, ModuleRepository repository)
|
|
||||||
{
|
|
||||||
if (!repository.TryGet(funcIdentifierNode.ModuleToken, out var module) || !module.TryResolveFunc(funcIdentifierNode.NameToken, out var func, out _))
|
|
||||||
{
|
|
||||||
return $"""
|
|
||||||
**Function** `{funcIdentifierNode.ModuleToken.Value}::{funcIdentifierNode.NameToken.Value}`
|
|
||||||
```nub
|
|
||||||
// Declaration not found
|
|
||||||
```
|
|
||||||
""";
|
|
||||||
}
|
|
||||||
|
|
||||||
return CreateFuncPrototypeMessage(func);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static string CreateFuncPrototypeMessage(FuncPrototypeNode funcPrototypeNode)
|
|
||||||
{
|
|
||||||
var parameterText = string.Join(", ", funcPrototypeNode.Parameters.Select(x => $"{x.NameToken.Value}: {x.Type}"));
|
|
||||||
var externText = funcPrototypeNode.ExternSymbolToken != null ? $"extern \"{funcPrototypeNode.ExternSymbolToken.Value}\" " : "";
|
|
||||||
|
|
||||||
return $"""
|
|
||||||
**Function** `{funcPrototypeNode.NameToken.Value}`
|
|
||||||
```nub
|
|
||||||
{externText}func {funcPrototypeNode.NameToken.Value}({parameterText}): {funcPrototypeNode.ReturnType}
|
|
||||||
```
|
|
||||||
""";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,21 +0,0 @@
|
|||||||
<Project Sdk="Microsoft.NET.Sdk">
|
|
||||||
|
|
||||||
<PropertyGroup>
|
|
||||||
<AssemblyName>nublsp</AssemblyName>
|
|
||||||
<OutputType>Exe</OutputType>
|
|
||||||
<TargetFramework>net9.0</TargetFramework>
|
|
||||||
<ImplicitUsings>enable</ImplicitUsings>
|
|
||||||
<Nullable>enable</Nullable>
|
|
||||||
<SelfContained>true</SelfContained>
|
|
||||||
<PublishSingleFile>true</PublishSingleFile>
|
|
||||||
</PropertyGroup>
|
|
||||||
|
|
||||||
<ItemGroup>
|
|
||||||
<PackageReference Include="OmniSharp.Extensions.LanguageServer" Version="0.19.9" />
|
|
||||||
</ItemGroup>
|
|
||||||
|
|
||||||
<ItemGroup>
|
|
||||||
<ProjectReference Include="..\NubLang\NubLang.csproj" />
|
|
||||||
</ItemGroup>
|
|
||||||
|
|
||||||
</Project>
|
|
||||||
@@ -1,24 +0,0 @@
|
|||||||
using Microsoft.Extensions.DependencyInjection;
|
|
||||||
using Microsoft.Extensions.Logging;
|
|
||||||
using NubLang.LSP;
|
|
||||||
using OmniSharp.Extensions.LanguageServer.Server;
|
|
||||||
|
|
||||||
var server = await LanguageServer.From(options => options
|
|
||||||
.WithInput(Console.OpenStandardInput())
|
|
||||||
.WithOutput(Console.OpenStandardOutput())
|
|
||||||
.WithServices(services =>
|
|
||||||
{
|
|
||||||
services.AddSingleton<DiagnosticsPublisher>();
|
|
||||||
services.AddSingleton<WorkspaceManager>();
|
|
||||||
})
|
|
||||||
.ConfigureLogging(x => x
|
|
||||||
.AddLanguageProtocolLogging()
|
|
||||||
.SetMinimumLevel(LogLevel.Debug))
|
|
||||||
.WithHandler<TextDocumentSyncHandler>()
|
|
||||||
.WithHandler<HoverHandler>()
|
|
||||||
.WithHandler<CompletionHandler>()
|
|
||||||
.WithHandler<DefinitionHandler>()
|
|
||||||
.WithHandler<SetRootPathCommandHandler>()
|
|
||||||
);
|
|
||||||
|
|
||||||
await server.WaitForExit;
|
|
||||||
@@ -1,31 +0,0 @@
|
|||||||
using MediatR;
|
|
||||||
using OmniSharp.Extensions.LanguageServer.Protocol.Client.Capabilities;
|
|
||||||
using OmniSharp.Extensions.LanguageServer.Protocol.Models;
|
|
||||||
using OmniSharp.Extensions.LanguageServer.Protocol.Workspace;
|
|
||||||
|
|
||||||
namespace NubLang.LSP;
|
|
||||||
|
|
||||||
public class SetRootPathCommandHandler(WorkspaceManager workspaceManager) : ExecuteCommandHandlerBase
|
|
||||||
{
|
|
||||||
protected override ExecuteCommandRegistrationOptions CreateRegistrationOptions(ExecuteCommandCapability capability, ClientCapabilities clientCapabilities)
|
|
||||||
{
|
|
||||||
return new ExecuteCommandRegistrationOptions
|
|
||||||
{
|
|
||||||
Commands = new Container<string>("nub.setRootPath")
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
public override Task<Unit> Handle(ExecuteCommandParams request, CancellationToken cancellationToken)
|
|
||||||
{
|
|
||||||
if (request is { Command: "nub.setRootPath", Arguments.Count: > 0 })
|
|
||||||
{
|
|
||||||
var newRoot = request.Arguments[0].ToString();
|
|
||||||
if (!string.IsNullOrEmpty(newRoot))
|
|
||||||
{
|
|
||||||
workspaceManager.SetRootPath(newRoot);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return Unit.Task;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,44 +0,0 @@
|
|||||||
using MediatR;
|
|
||||||
using OmniSharp.Extensions.LanguageServer.Protocol;
|
|
||||||
using OmniSharp.Extensions.LanguageServer.Protocol.Client.Capabilities;
|
|
||||||
using OmniSharp.Extensions.LanguageServer.Protocol.Document;
|
|
||||||
using OmniSharp.Extensions.LanguageServer.Protocol.Models;
|
|
||||||
|
|
||||||
namespace NubLang.LSP;
|
|
||||||
|
|
||||||
internal class TextDocumentSyncHandler(WorkspaceManager workspaceManager) : TextDocumentSyncHandlerBase
|
|
||||||
{
|
|
||||||
public override TextDocumentAttributes GetTextDocumentAttributes(DocumentUri uri)
|
|
||||||
{
|
|
||||||
return new TextDocumentAttributes(uri, "nub");
|
|
||||||
}
|
|
||||||
|
|
||||||
public override Task<Unit> Handle(DidOpenTextDocumentParams request, CancellationToken cancellationToken)
|
|
||||||
{
|
|
||||||
workspaceManager.Update();
|
|
||||||
return Unit.Task;
|
|
||||||
}
|
|
||||||
|
|
||||||
public override Task<Unit> Handle(DidChangeTextDocumentParams request, CancellationToken cancellationToken)
|
|
||||||
{
|
|
||||||
workspaceManager.Update();
|
|
||||||
return Unit.Task;
|
|
||||||
}
|
|
||||||
|
|
||||||
public override Task<Unit> Handle(DidSaveTextDocumentParams request, CancellationToken cancellationToken)
|
|
||||||
{
|
|
||||||
workspaceManager.Update();
|
|
||||||
return Unit.Task;
|
|
||||||
}
|
|
||||||
|
|
||||||
public override Task<Unit> Handle(DidCloseTextDocumentParams request, CancellationToken cancellationToken)
|
|
||||||
{
|
|
||||||
workspaceManager.Update();
|
|
||||||
return Unit.Task;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override TextDocumentSyncRegistrationOptions CreateRegistrationOptions(TextSynchronizationCapability capability, ClientCapabilities clientCapabilities)
|
|
||||||
{
|
|
||||||
return new TextDocumentSyncRegistrationOptions();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,68 +0,0 @@
|
|||||||
using NubLang.Ast;
|
|
||||||
using NubLang.Diagnostics;
|
|
||||||
using NubLang.Modules;
|
|
||||||
using NubLang.Syntax;
|
|
||||||
|
|
||||||
namespace NubLang.LSP;
|
|
||||||
|
|
||||||
public class WorkspaceManager(DiagnosticsPublisher diagnosticsPublisher)
|
|
||||||
{
|
|
||||||
private record Unit(SyntaxTree SyntaxTree, DateTimeOffset FileTimestamp, List<Diagnostic> Diagnostics);
|
|
||||||
|
|
||||||
private readonly Tokenizer _tokenizer = new();
|
|
||||||
private readonly Parser _parser = new();
|
|
||||||
private readonly TypeChecker _typeChecker = new();
|
|
||||||
private string? _rootPath;
|
|
||||||
private readonly Dictionary<string, Unit> _units = [];
|
|
||||||
private readonly Dictionary<string, List<TopLevelNode>> _possiblyOutdatedTopLevelNodes = [];
|
|
||||||
private ModuleRepository _repository = new([]);
|
|
||||||
|
|
||||||
public void SetRootPath(string rootPath)
|
|
||||||
{
|
|
||||||
_rootPath = rootPath;
|
|
||||||
Update();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Update()
|
|
||||||
{
|
|
||||||
if (_rootPath == null) return;
|
|
||||||
var files = Directory.GetFiles(_rootPath, "*.nub", SearchOption.AllDirectories);
|
|
||||||
foreach (var file in files)
|
|
||||||
{
|
|
||||||
var lastUpdated = File.GetLastWriteTimeUtc(file);
|
|
||||||
var unit = _units.GetValueOrDefault(file);
|
|
||||||
if (unit == null || lastUpdated > unit.FileTimestamp)
|
|
||||||
{
|
|
||||||
_units[file] = Update(file, lastUpdated);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
_repository = ModuleRepository.Create(_units.Select(x => x.Value.SyntaxTree).ToList());
|
|
||||||
|
|
||||||
foreach (var (file, unit) in _units)
|
|
||||||
{
|
|
||||||
var topLevelNodes = _typeChecker.Check(unit.SyntaxTree, _repository);
|
|
||||||
_possiblyOutdatedTopLevelNodes[file] = topLevelNodes;
|
|
||||||
diagnosticsPublisher.Publish(file, [..unit.Diagnostics, .._typeChecker.Diagnostics]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private Unit Update(string file, DateTimeOffset lastUpdated)
|
|
||||||
{
|
|
||||||
var text = File.ReadAllText(file);
|
|
||||||
var tokens = _tokenizer.Tokenize(file, text);
|
|
||||||
var syntaxTree = _parser.Parse(tokens);
|
|
||||||
|
|
||||||
return new Unit(syntaxTree, lastUpdated, [.._tokenizer.Diagnostics, .._parser.Diagnostics]);
|
|
||||||
}
|
|
||||||
|
|
||||||
public List<TopLevelNode> GetTopLevelNodes(string path)
|
|
||||||
{
|
|
||||||
return _possiblyOutdatedTopLevelNodes.GetValueOrDefault(path, []);
|
|
||||||
}
|
|
||||||
|
|
||||||
public ModuleRepository GetModuleRepository()
|
|
||||||
{
|
|
||||||
return _repository;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,635 +0,0 @@
|
|||||||
using NubLang.Syntax;
|
|
||||||
using NubLang.Types;
|
|
||||||
|
|
||||||
namespace NubLang.Ast;
|
|
||||||
|
|
||||||
public abstract class Node(List<Token> tokens)
|
|
||||||
{
|
|
||||||
public List<Token> Tokens { get; } = tokens;
|
|
||||||
|
|
||||||
public abstract IEnumerable<Node> Children();
|
|
||||||
|
|
||||||
public IEnumerable<Node> Descendants()
|
|
||||||
{
|
|
||||||
foreach (var child in Children())
|
|
||||||
{
|
|
||||||
foreach (var descendant in child.DescendantsAndSelf())
|
|
||||||
{
|
|
||||||
yield return descendant;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public IEnumerable<Node> DescendantsAndSelf()
|
|
||||||
{
|
|
||||||
yield return this;
|
|
||||||
foreach (var descendant in Descendants())
|
|
||||||
{
|
|
||||||
yield return descendant;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public abstract class TopLevelNode(List<Token> tokens) : Node(tokens);
|
|
||||||
|
|
||||||
public class ModuleNode(List<Token> tokens, IdentifierToken nameToken) : TopLevelNode(tokens)
|
|
||||||
{
|
|
||||||
public IdentifierToken NameToken { get; } = nameToken;
|
|
||||||
|
|
||||||
public override IEnumerable<Node> Children()
|
|
||||||
{
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#region Definitions
|
|
||||||
|
|
||||||
public abstract class DefinitionNode(List<Token> tokens, IdentifierToken nameToken) : TopLevelNode(tokens)
|
|
||||||
{
|
|
||||||
public IdentifierToken NameToken { get; } = nameToken;
|
|
||||||
}
|
|
||||||
|
|
||||||
public class FuncParameterNode(List<Token> tokens, IdentifierToken nameToken, NubType type) : Node(tokens)
|
|
||||||
{
|
|
||||||
public IdentifierToken NameToken { get; } = nameToken;
|
|
||||||
public NubType Type { get; } = type;
|
|
||||||
|
|
||||||
public override IEnumerable<Node> Children()
|
|
||||||
{
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public class FuncPrototypeNode(List<Token> tokens, IdentifierToken nameToken, StringLiteralToken? externSymbolToken, List<FuncParameterNode> parameters, NubType returnType) : Node(tokens)
|
|
||||||
{
|
|
||||||
public IdentifierToken NameToken { get; } = nameToken;
|
|
||||||
public StringLiteralToken? ExternSymbolToken { get; } = externSymbolToken;
|
|
||||||
public List<FuncParameterNode> Parameters { get; } = parameters;
|
|
||||||
public NubType ReturnType { get; } = returnType;
|
|
||||||
|
|
||||||
public override IEnumerable<Node> Children()
|
|
||||||
{
|
|
||||||
return Parameters;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public class FuncNode(List<Token> tokens, FuncPrototypeNode prototype, BlockNode? body) : DefinitionNode(tokens, prototype.NameToken)
|
|
||||||
{
|
|
||||||
public FuncPrototypeNode Prototype { get; } = prototype;
|
|
||||||
public BlockNode? Body { get; } = body;
|
|
||||||
|
|
||||||
public override IEnumerable<Node> Children()
|
|
||||||
{
|
|
||||||
yield return Prototype;
|
|
||||||
if (Body != null)
|
|
||||||
{
|
|
||||||
yield return Body;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public class StructFieldNode(List<Token> tokens, IdentifierToken nameToken, NubType type, ExpressionNode? value) : Node(tokens)
|
|
||||||
{
|
|
||||||
public IdentifierToken NameToken { get; } = nameToken;
|
|
||||||
public NubType Type { get; } = type;
|
|
||||||
public ExpressionNode? Value { get; } = value;
|
|
||||||
|
|
||||||
public override IEnumerable<Node> Children()
|
|
||||||
{
|
|
||||||
if (Value != null)
|
|
||||||
{
|
|
||||||
yield return Value;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public class StructNode(List<Token> tokens, IdentifierToken name, NubStructType structType, bool packed, List<StructFieldNode> fields) : DefinitionNode(tokens, name)
|
|
||||||
{
|
|
||||||
public NubStructType StructType { get; } = structType;
|
|
||||||
public bool Packed { get; } = packed;
|
|
||||||
public List<StructFieldNode> Fields { get; } = fields;
|
|
||||||
|
|
||||||
public override IEnumerable<Node> Children()
|
|
||||||
{
|
|
||||||
foreach (var field in Fields)
|
|
||||||
{
|
|
||||||
yield return field;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#endregion
|
|
||||||
|
|
||||||
#region Statements
|
|
||||||
|
|
||||||
public abstract class StatementNode(List<Token> tokens) : Node(tokens);
|
|
||||||
|
|
||||||
public class BlockNode(List<Token> tokens, List<StatementNode> statements) : StatementNode(tokens)
|
|
||||||
{
|
|
||||||
public List<StatementNode> Statements { get; } = statements;
|
|
||||||
|
|
||||||
public override IEnumerable<Node> Children()
|
|
||||||
{
|
|
||||||
return Statements;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public class StatementFuncCallNode(List<Token> tokens, FuncCallNode funcCall) : StatementNode(tokens)
|
|
||||||
{
|
|
||||||
public FuncCallNode FuncCall { get; } = funcCall;
|
|
||||||
|
|
||||||
public override IEnumerable<Node> Children()
|
|
||||||
{
|
|
||||||
yield return FuncCall;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public class ReturnNode(List<Token> tokens, ExpressionNode? value) : StatementNode(tokens)
|
|
||||||
{
|
|
||||||
public ExpressionNode? Value { get; } = value;
|
|
||||||
|
|
||||||
public override IEnumerable<Node> Children()
|
|
||||||
{
|
|
||||||
if (Value != null) yield return Value;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public class AssignmentNode(List<Token> tokens, ExpressionNode target, ExpressionNode value) : StatementNode(tokens)
|
|
||||||
{
|
|
||||||
public ExpressionNode Target { get; } = target;
|
|
||||||
public ExpressionNode Value { get; } = value;
|
|
||||||
|
|
||||||
public override IEnumerable<Node> Children()
|
|
||||||
{
|
|
||||||
yield return Target;
|
|
||||||
yield return Value;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public class IfNode(List<Token> tokens, ExpressionNode condition, BlockNode body, Variant<IfNode, BlockNode>? @else) : StatementNode(tokens)
|
|
||||||
{
|
|
||||||
public ExpressionNode Condition { get; } = condition;
|
|
||||||
public BlockNode Body { get; } = body;
|
|
||||||
public Variant<IfNode, BlockNode>? Else { get; } = @else;
|
|
||||||
|
|
||||||
public override IEnumerable<Node> Children()
|
|
||||||
{
|
|
||||||
yield return Condition;
|
|
||||||
yield return Body;
|
|
||||||
if (Else.HasValue)
|
|
||||||
{
|
|
||||||
yield return Else.Value.Match<Node>(x => x, x => x);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public class VariableDeclarationNode(List<Token> tokens, IdentifierToken nameToken, ExpressionNode? assignment, NubType type) : StatementNode(tokens)
|
|
||||||
{
|
|
||||||
public IdentifierToken NameToken { get; } = nameToken;
|
|
||||||
public ExpressionNode? Assignment { get; } = assignment;
|
|
||||||
public NubType Type { get; } = type;
|
|
||||||
|
|
||||||
public override IEnumerable<Node> Children()
|
|
||||||
{
|
|
||||||
if (Assignment != null) yield return Assignment;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public class ContinueNode(List<Token> tokens) : StatementNode(tokens)
|
|
||||||
{
|
|
||||||
public override IEnumerable<Node> Children()
|
|
||||||
{
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public class BreakNode(List<Token> tokens) : StatementNode(tokens)
|
|
||||||
{
|
|
||||||
public override IEnumerable<Node> Children()
|
|
||||||
{
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public class WhileNode(List<Token> tokens, ExpressionNode condition, BlockNode body) : StatementNode(tokens)
|
|
||||||
{
|
|
||||||
public ExpressionNode Condition { get; } = condition;
|
|
||||||
public BlockNode Body { get; } = body;
|
|
||||||
|
|
||||||
public override IEnumerable<Node> Children()
|
|
||||||
{
|
|
||||||
yield return Condition;
|
|
||||||
yield return Body;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public class ForSliceNode(List<Token> tokens, IdentifierToken elementNameToken, IdentifierToken? indexNameToken, ExpressionNode target, BlockNode body) : StatementNode(tokens)
|
|
||||||
{
|
|
||||||
public IdentifierToken ElementNameToken { get; } = elementNameToken;
|
|
||||||
public IdentifierToken? IndexNameToken { get; } = indexNameToken;
|
|
||||||
public ExpressionNode Target { get; } = target;
|
|
||||||
public BlockNode Body { get; } = body;
|
|
||||||
|
|
||||||
public override IEnumerable<Node> Children()
|
|
||||||
{
|
|
||||||
yield return Target;
|
|
||||||
yield return Body;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public class ForConstArrayNode(List<Token> tokens, IdentifierToken elementNameToken, IdentifierToken? indexNameToken, ExpressionNode target, BlockNode body) : StatementNode(tokens)
|
|
||||||
{
|
|
||||||
public IdentifierToken ElementNameToken { get; } = elementNameToken;
|
|
||||||
public IdentifierToken? IndexNameToken { get; } = indexNameToken;
|
|
||||||
public ExpressionNode Target { get; } = target;
|
|
||||||
public BlockNode Body { get; } = body;
|
|
||||||
|
|
||||||
public override IEnumerable<Node> Children()
|
|
||||||
{
|
|
||||||
yield return Target;
|
|
||||||
yield return Body;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public class DeferNode(List<Token> tokens, StatementNode statement) : StatementNode(tokens)
|
|
||||||
{
|
|
||||||
public StatementNode Statement { get; } = statement;
|
|
||||||
|
|
||||||
public override IEnumerable<Node> Children()
|
|
||||||
{
|
|
||||||
yield return Statement;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#endregion
|
|
||||||
|
|
||||||
#region Expressions
|
|
||||||
|
|
||||||
public enum UnaryOperator
|
|
||||||
{
|
|
||||||
Negate,
|
|
||||||
Invert
|
|
||||||
}
|
|
||||||
|
|
||||||
public enum BinaryOperator
|
|
||||||
{
|
|
||||||
Equal,
|
|
||||||
NotEqual,
|
|
||||||
GreaterThan,
|
|
||||||
GreaterThanOrEqual,
|
|
||||||
LessThan,
|
|
||||||
LessThanOrEqual,
|
|
||||||
LogicalAnd,
|
|
||||||
LogicalOr,
|
|
||||||
Plus,
|
|
||||||
Minus,
|
|
||||||
Multiply,
|
|
||||||
Divide,
|
|
||||||
Modulo,
|
|
||||||
LeftShift,
|
|
||||||
RightShift,
|
|
||||||
BitwiseAnd,
|
|
||||||
BitwiseXor,
|
|
||||||
BitwiseOr
|
|
||||||
}
|
|
||||||
|
|
||||||
public abstract class ExpressionNode(List<Token> tokens, NubType type) : Node(tokens)
|
|
||||||
{
|
|
||||||
public NubType Type { get; } = type;
|
|
||||||
}
|
|
||||||
|
|
||||||
public abstract class LValue(List<Token> tokens, NubType type) : ExpressionNode(tokens, type);
|
|
||||||
|
|
||||||
public abstract class RValue(List<Token> tokens, NubType type) : ExpressionNode(tokens, type);
|
|
||||||
|
|
||||||
public class StringLiteralNode(List<Token> tokens, string value) : RValue(tokens, new NubStringType())
|
|
||||||
{
|
|
||||||
public string Value { get; } = value;
|
|
||||||
|
|
||||||
public override IEnumerable<Node> Children()
|
|
||||||
{
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public class CStringLiteralNode(List<Token> tokens, string value) : RValue(tokens, new NubPointerType(new NubIntType(true, 8)))
|
|
||||||
{
|
|
||||||
public string Value { get; } = value;
|
|
||||||
|
|
||||||
public override IEnumerable<Node> Children()
|
|
||||||
{
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public class I8LiteralNode(List<Token> tokens, sbyte value) : RValue(tokens, new NubIntType(true, 8))
|
|
||||||
{
|
|
||||||
public sbyte Value { get; } = value;
|
|
||||||
|
|
||||||
public override IEnumerable<Node> Children()
|
|
||||||
{
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public class I16LiteralNode(List<Token> tokens, short value) : RValue(tokens, new NubIntType(true, 16))
|
|
||||||
{
|
|
||||||
public short Value { get; } = value;
|
|
||||||
|
|
||||||
public override IEnumerable<Node> Children()
|
|
||||||
{
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public class I32LiteralNode(List<Token> tokens, int value) : RValue(tokens, new NubIntType(true, 32))
|
|
||||||
{
|
|
||||||
public int Value { get; } = value;
|
|
||||||
|
|
||||||
public override IEnumerable<Node> Children()
|
|
||||||
{
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public class I64LiteralNode(List<Token> tokens, long value) : RValue(tokens, new NubIntType(true, 64))
|
|
||||||
{
|
|
||||||
public long Value { get; } = value;
|
|
||||||
|
|
||||||
public override IEnumerable<Node> Children()
|
|
||||||
{
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public class U8LiteralNode(List<Token> tokens, byte value) : RValue(tokens, new NubIntType(false, 8))
|
|
||||||
{
|
|
||||||
public byte Value { get; } = value;
|
|
||||||
|
|
||||||
public override IEnumerable<Node> Children()
|
|
||||||
{
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public class U16LiteralNode(List<Token> tokens, ushort value) : RValue(tokens, new NubIntType(false, 16))
|
|
||||||
{
|
|
||||||
public ushort Value { get; } = value;
|
|
||||||
|
|
||||||
public override IEnumerable<Node> Children()
|
|
||||||
{
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public class U32LiteralNode(List<Token> tokens, uint value) : RValue(tokens, new NubIntType(false, 32))
|
|
||||||
{
|
|
||||||
public uint Value { get; } = value;
|
|
||||||
|
|
||||||
public override IEnumerable<Node> Children()
|
|
||||||
{
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public class U64LiteralNode(List<Token> tokens, ulong value) : RValue(tokens, new NubIntType(false, 64))
|
|
||||||
{
|
|
||||||
public ulong Value { get; } = value;
|
|
||||||
|
|
||||||
public override IEnumerable<Node> Children()
|
|
||||||
{
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public class Float32LiteralNode(List<Token> tokens, float value) : RValue(tokens, new NubFloatType(32))
|
|
||||||
{
|
|
||||||
public float Value { get; } = value;
|
|
||||||
|
|
||||||
public override IEnumerable<Node> Children()
|
|
||||||
{
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public class Float64LiteralNode(List<Token> tokens, double value) : RValue(tokens, new NubFloatType(64))
|
|
||||||
{
|
|
||||||
public double Value { get; } = value;
|
|
||||||
|
|
||||||
public override IEnumerable<Node> Children()
|
|
||||||
{
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public class BoolLiteralNode(List<Token> tokens, bool value) : RValue(tokens, new NubBoolType())
|
|
||||||
{
|
|
||||||
public bool Value { get; } = value;
|
|
||||||
|
|
||||||
public override IEnumerable<Node> Children()
|
|
||||||
{
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public class BinaryExpressionNode(List<Token> tokens, NubType type, ExpressionNode left, BinaryOperator @operator, ExpressionNode right) : RValue(tokens, type)
|
|
||||||
{
|
|
||||||
public ExpressionNode Left { get; } = left;
|
|
||||||
public BinaryOperator Operator { get; } = @operator;
|
|
||||||
public ExpressionNode Right { get; } = right;
|
|
||||||
|
|
||||||
public override IEnumerable<Node> Children()
|
|
||||||
{
|
|
||||||
yield return Left;
|
|
||||||
yield return Right;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public class UnaryExpressionNode(List<Token> tokens, NubType type, UnaryOperator @operator, ExpressionNode operand) : RValue(tokens, type)
|
|
||||||
{
|
|
||||||
public UnaryOperator Operator { get; } = @operator;
|
|
||||||
public ExpressionNode Operand { get; } = operand;
|
|
||||||
|
|
||||||
public override IEnumerable<Node> Children()
|
|
||||||
{
|
|
||||||
yield return Operand;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public class FuncCallNode(List<Token> tokens, NubType type, ExpressionNode expression, List<ExpressionNode> parameters) : RValue(tokens, type)
|
|
||||||
{
|
|
||||||
public ExpressionNode Expression { get; } = expression;
|
|
||||||
public List<ExpressionNode> Parameters { get; } = parameters;
|
|
||||||
|
|
||||||
public override IEnumerable<Node> Children()
|
|
||||||
{
|
|
||||||
yield return Expression;
|
|
||||||
foreach (var expressionNode in Parameters)
|
|
||||||
{
|
|
||||||
yield return expressionNode;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public class VariableIdentifierNode(List<Token> tokens, NubType type, IdentifierToken nameToken) : LValue(tokens, type)
|
|
||||||
{
|
|
||||||
public IdentifierToken NameToken { get; } = nameToken;
|
|
||||||
|
|
||||||
public override IEnumerable<Node> Children()
|
|
||||||
{
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public class LocalFuncIdentifierNode(List<Token> tokens, NubType type, IdentifierToken nameToken, StringLiteralToken? externSymbolToken) : RValue(tokens, type)
|
|
||||||
{
|
|
||||||
public IdentifierToken NameToken { get; } = nameToken;
|
|
||||||
public StringLiteralToken? ExternSymbolToken { get; } = externSymbolToken;
|
|
||||||
|
|
||||||
public override IEnumerable<Node> Children()
|
|
||||||
{
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public class ModuleFuncIdentifierNode(List<Token> tokens, NubType type, IdentifierToken moduleToken, IdentifierToken nameToken, StringLiteralToken? externSymbolToken) : RValue(tokens, type)
|
|
||||||
{
|
|
||||||
public IdentifierToken ModuleToken { get; } = moduleToken;
|
|
||||||
public IdentifierToken NameToken { get; } = nameToken;
|
|
||||||
public StringLiteralToken? ExternSymbolToken { get; } = externSymbolToken;
|
|
||||||
|
|
||||||
public override IEnumerable<Node> Children()
|
|
||||||
{
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public class ArrayIndexAccessNode(List<Token> tokens, NubType type, ExpressionNode target, ExpressionNode index) : LValue(tokens, type)
|
|
||||||
{
|
|
||||||
public ExpressionNode Target { get; } = target;
|
|
||||||
public ExpressionNode Index { get; } = index;
|
|
||||||
|
|
||||||
public override IEnumerable<Node> Children()
|
|
||||||
{
|
|
||||||
yield return Target;
|
|
||||||
yield return Index;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public class ConstArrayIndexAccessNode(List<Token> tokens, NubType type, ExpressionNode target, ExpressionNode index) : LValue(tokens, type)
|
|
||||||
{
|
|
||||||
public ExpressionNode Target { get; } = target;
|
|
||||||
public ExpressionNode Index { get; } = index;
|
|
||||||
|
|
||||||
public override IEnumerable<Node> Children()
|
|
||||||
{
|
|
||||||
yield return Target;
|
|
||||||
yield return Index;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public class SliceIndexAccessNode(List<Token> tokens, NubType type, ExpressionNode target, ExpressionNode index) : LValue(tokens, type)
|
|
||||||
{
|
|
||||||
public ExpressionNode Target { get; } = target;
|
|
||||||
public ExpressionNode Index { get; } = index;
|
|
||||||
|
|
||||||
public override IEnumerable<Node> Children()
|
|
||||||
{
|
|
||||||
yield return Target;
|
|
||||||
yield return Index;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public class AddressOfNode(List<Token> tokens, NubType type, ExpressionNode target) : RValue(tokens, type)
|
|
||||||
{
|
|
||||||
public ExpressionNode Target { get; } = target;
|
|
||||||
|
|
||||||
public override IEnumerable<Node> Children()
|
|
||||||
{
|
|
||||||
yield return Target;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public class StructFieldAccessNode(List<Token> tokens, NubType type, ExpressionNode target, IdentifierToken fieldToken) : LValue(tokens, type)
|
|
||||||
{
|
|
||||||
public ExpressionNode Target { get; } = target;
|
|
||||||
public IdentifierToken FieldToken { get; } = fieldToken;
|
|
||||||
|
|
||||||
public override IEnumerable<Node> Children()
|
|
||||||
{
|
|
||||||
yield return Target;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public class DereferenceNode(List<Token> tokens, NubType type, ExpressionNode target) : LValue(tokens, type)
|
|
||||||
{
|
|
||||||
public ExpressionNode Target { get; } = target;
|
|
||||||
|
|
||||||
public override IEnumerable<Node> Children()
|
|
||||||
{
|
|
||||||
yield return Target;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public class SizeNode(List<Token> tokens, NubType targetType) : RValue(tokens, new NubIntType(false, 64))
|
|
||||||
{
|
|
||||||
public NubType TargetType { get; } = targetType;
|
|
||||||
|
|
||||||
public override IEnumerable<Node> Children()
|
|
||||||
{
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public class CastNode(List<Token> tokens, NubType type, ExpressionNode value, CastNode.Conversion conversionType) : RValue(tokens, type)
|
|
||||||
{
|
|
||||||
public enum Conversion
|
|
||||||
{
|
|
||||||
IntToInt,
|
|
||||||
FloatToFloat,
|
|
||||||
IntToFloat,
|
|
||||||
FloatToInt,
|
|
||||||
|
|
||||||
PointerToPointer,
|
|
||||||
PointerToUInt64,
|
|
||||||
UInt64ToPointer,
|
|
||||||
|
|
||||||
ConstArrayToArray,
|
|
||||||
ConstArrayToSlice,
|
|
||||||
|
|
||||||
StringToCString
|
|
||||||
}
|
|
||||||
|
|
||||||
public ExpressionNode Value { get; } = value;
|
|
||||||
public Conversion ConversionType { get; } = conversionType;
|
|
||||||
|
|
||||||
public override IEnumerable<Node> Children()
|
|
||||||
{
|
|
||||||
yield return Value;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public class StructInitializerNode(List<Token> tokens, NubType type, Dictionary<IdentifierToken, ExpressionNode> initializers) : LValue(tokens, type)
|
|
||||||
{
|
|
||||||
public Dictionary<IdentifierToken, ExpressionNode> Initializers { get; } = initializers;
|
|
||||||
|
|
||||||
public override IEnumerable<Node> Children()
|
|
||||||
{
|
|
||||||
foreach (var initializer in Initializers)
|
|
||||||
{
|
|
||||||
yield return initializer.Value;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public class ConstArrayInitializerNode(List<Token> tokens, NubType type, List<ExpressionNode> values) : LValue(tokens, type)
|
|
||||||
{
|
|
||||||
public List<ExpressionNode> Values { get; } = values;
|
|
||||||
|
|
||||||
public override IEnumerable<Node> Children()
|
|
||||||
{
|
|
||||||
return Values;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#endregion
|
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -1,11 +0,0 @@
|
|||||||
namespace NubLang.Diagnostics;
|
|
||||||
|
|
||||||
public class CompileException : Exception
|
|
||||||
{
|
|
||||||
public Diagnostic Diagnostic { get; }
|
|
||||||
|
|
||||||
public CompileException(Diagnostic diagnostic) : base(diagnostic.Message)
|
|
||||||
{
|
|
||||||
Diagnostic = diagnostic;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,426 +0,0 @@
|
|||||||
using System.Text;
|
|
||||||
using NubLang.Syntax;
|
|
||||||
|
|
||||||
namespace NubLang.Diagnostics;
|
|
||||||
|
|
||||||
public class Diagnostic
|
|
||||||
{
|
|
||||||
public class DiagnosticBuilder
|
|
||||||
{
|
|
||||||
private readonly DiagnosticSeverity _severity;
|
|
||||||
private readonly string _message;
|
|
||||||
private SourceSpan? _span;
|
|
||||||
private string? _help;
|
|
||||||
private List<Token>? _tokens;
|
|
||||||
|
|
||||||
public DiagnosticBuilder(DiagnosticSeverity severity, string message)
|
|
||||||
{
|
|
||||||
_severity = severity;
|
|
||||||
_message = message;
|
|
||||||
}
|
|
||||||
|
|
||||||
public DiagnosticBuilder At(SyntaxNode? node, List<Token>? tokens = null)
|
|
||||||
{
|
|
||||||
if (tokens != null)
|
|
||||||
{
|
|
||||||
_tokens = tokens;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (node != null)
|
|
||||||
{
|
|
||||||
var first = node.Tokens.FirstOrDefault();
|
|
||||||
if (first != null)
|
|
||||||
{
|
|
||||||
_span = SourceSpan.Merge(node.Tokens.Select(x => x.Span));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public DiagnosticBuilder At(Token? token, List<Token>? tokens = null)
|
|
||||||
{
|
|
||||||
if (tokens != null)
|
|
||||||
{
|
|
||||||
_tokens = tokens;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (token != null)
|
|
||||||
{
|
|
||||||
At(token.Span);
|
|
||||||
}
|
|
||||||
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public DiagnosticBuilder At(SourceSpan? span)
|
|
||||||
{
|
|
||||||
if (span != null)
|
|
||||||
{
|
|
||||||
_span = span;
|
|
||||||
}
|
|
||||||
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
// public DiagnosticBuilder At(string filePath, int line, int column)
|
|
||||||
// {
|
|
||||||
// _span = new SourceSpan(filePath, new SourceLocation(line, column), new SourceLocation(line, column));
|
|
||||||
// return this;
|
|
||||||
// }
|
|
||||||
|
|
||||||
public DiagnosticBuilder WithHelp(string help)
|
|
||||||
{
|
|
||||||
_help = help;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Diagnostic Build() => new(_severity, _message, _help, _span, _tokens);
|
|
||||||
}
|
|
||||||
|
|
||||||
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);
|
|
||||||
|
|
||||||
private readonly List<Token>? _tokens;
|
|
||||||
|
|
||||||
public DiagnosticSeverity Severity { get; }
|
|
||||||
public string Message { get; }
|
|
||||||
public string? Help { get; }
|
|
||||||
public SourceSpan? Span { get; }
|
|
||||||
|
|
||||||
private Diagnostic(DiagnosticSeverity severity, string message, string? help, SourceSpan? span, List<Token>? tokens)
|
|
||||||
{
|
|
||||||
_tokens = tokens;
|
|
||||||
Severity = severity;
|
|
||||||
Message = message;
|
|
||||||
Help = help;
|
|
||||||
Span = span;
|
|
||||||
}
|
|
||||||
|
|
||||||
public string FormatANSI()
|
|
||||||
{
|
|
||||||
var sb = new StringBuilder();
|
|
||||||
|
|
||||||
sb.Append(Severity switch
|
|
||||||
{
|
|
||||||
DiagnosticSeverity.Error => ConsoleColors.Colorize("error", ConsoleColors.Bold + ConsoleColors.Red),
|
|
||||||
DiagnosticSeverity.Warning => ConsoleColors.Colorize("warning", ConsoleColors.Bold + ConsoleColors.Yellow),
|
|
||||||
DiagnosticSeverity.Info => ConsoleColors.Colorize("info", ConsoleColors.Bold + ConsoleColors.Blue),
|
|
||||||
_ => ConsoleColors.Colorize("unknown", ConsoleColors.Bold + ConsoleColors.White)
|
|
||||||
});
|
|
||||||
|
|
||||||
if (Span.HasValue)
|
|
||||||
{
|
|
||||||
sb.Append(ConsoleColors.Colorize($" at {Span.Value}", ConsoleColors.Faint));
|
|
||||||
}
|
|
||||||
|
|
||||||
sb.Append(": ");
|
|
||||||
sb.Append(ConsoleColors.Colorize(Message, ConsoleColors.BrightWhite));
|
|
||||||
|
|
||||||
if (Span.HasValue)
|
|
||||||
{
|
|
||||||
sb.AppendLine();
|
|
||||||
var text = Span.Value.Source;
|
|
||||||
|
|
||||||
var lines = text.Split('\n');
|
|
||||||
|
|
||||||
var startLine = Span.Value.StartLine;
|
|
||||||
var endLine = Span.Value.EndLine;
|
|
||||||
|
|
||||||
const int CONTEXT_LINES = 3;
|
|
||||||
|
|
||||||
var contextStartLine = Math.Max(1, startLine - CONTEXT_LINES);
|
|
||||||
var contextEndLine = Math.Min(lines.Length, endLine + CONTEXT_LINES);
|
|
||||||
|
|
||||||
var numberPadding = contextEndLine.ToString().Length;
|
|
||||||
|
|
||||||
var codePadding = 0;
|
|
||||||
for (var i = contextStartLine - 1; i < contextEndLine && i < lines.Length; i++)
|
|
||||||
{
|
|
||||||
var lineLength = lines[i].Length;
|
|
||||||
if (lineLength > codePadding)
|
|
||||||
{
|
|
||||||
codePadding = lineLength;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
sb.Append('╭');
|
|
||||||
sb.Append(new string('─', numberPadding + 2));
|
|
||||||
sb.Append('┬');
|
|
||||||
sb.Append(new string('─', codePadding + 2));
|
|
||||||
sb.Append('╮');
|
|
||||||
sb.AppendLine();
|
|
||||||
|
|
||||||
for (var i = contextStartLine; i <= contextEndLine; i++)
|
|
||||||
{
|
|
||||||
var line = lines[i - 1];
|
|
||||||
|
|
||||||
sb.Append("│ ");
|
|
||||||
sb.Append(i.ToString().PadRight(numberPadding));
|
|
||||||
sb.Append(" │ ");
|
|
||||||
if (_tokens != null)
|
|
||||||
{
|
|
||||||
sb.Append(ApplySyntaxHighlighting(line.PadRight(codePadding), i, _tokens));
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
sb.Append(line.PadRight(codePadding));
|
|
||||||
}
|
|
||||||
|
|
||||||
sb.Append(" │");
|
|
||||||
sb.AppendLine();
|
|
||||||
|
|
||||||
if (i >= startLine && i <= endLine)
|
|
||||||
{
|
|
||||||
var markerStartColumn = 1;
|
|
||||||
var markerEndColumn = line.Length + 1;
|
|
||||||
|
|
||||||
if (i == startLine)
|
|
||||||
{
|
|
||||||
markerStartColumn = Span.Value.StartColumn;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (i == endLine)
|
|
||||||
{
|
|
||||||
markerEndColumn = Span.Value.EndColumn;
|
|
||||||
}
|
|
||||||
|
|
||||||
var markerLength = markerEndColumn - markerStartColumn;
|
|
||||||
var marker = new string('^', markerLength);
|
|
||||||
|
|
||||||
var markerColor = Severity switch
|
|
||||||
{
|
|
||||||
DiagnosticSeverity.Info => ConsoleColors.Blue,
|
|
||||||
DiagnosticSeverity.Warning => ConsoleColors.Yellow,
|
|
||||||
DiagnosticSeverity.Error => ConsoleColors.Red,
|
|
||||||
_ => ConsoleColors.White
|
|
||||||
};
|
|
||||||
|
|
||||||
sb.Append("│ ");
|
|
||||||
sb.Append(new string(' ', numberPadding));
|
|
||||||
sb.Append(" │ ");
|
|
||||||
sb.Append(new string(' ', markerStartColumn - 1));
|
|
||||||
sb.Append(ConsoleColors.Colorize(marker, markerColor));
|
|
||||||
sb.Append(new string(' ', codePadding - (markerStartColumn - 1) - markerLength));
|
|
||||||
sb.Append(" │");
|
|
||||||
sb.AppendLine();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
sb.Append('╰');
|
|
||||||
sb.Append(new string('─', numberPadding + 2));
|
|
||||||
sb.Append('┴');
|
|
||||||
sb.Append(new string('─', codePadding + 2));
|
|
||||||
sb.Append('╯');
|
|
||||||
}
|
|
||||||
|
|
||||||
if (Help != null)
|
|
||||||
{
|
|
||||||
sb.AppendLine();
|
|
||||||
sb.Append(ConsoleColors.Colorize($"help: {Help}", ConsoleColors.Cyan));
|
|
||||||
}
|
|
||||||
|
|
||||||
return sb.ToString();
|
|
||||||
}
|
|
||||||
|
|
||||||
private static string ApplySyntaxHighlighting(string line, int lineNumber, List<Token> tokens)
|
|
||||||
{
|
|
||||||
var sb = new StringBuilder();
|
|
||||||
var lineTokens = tokens
|
|
||||||
.Where(t => t.Span.StartLine == lineNumber)
|
|
||||||
.OrderBy(t => t.Span.StartColumn)
|
|
||||||
.ToList();
|
|
||||||
|
|
||||||
if (lineTokens.Count == 0)
|
|
||||||
{
|
|
||||||
return line;
|
|
||||||
}
|
|
||||||
|
|
||||||
var currentColumn = 1;
|
|
||||||
|
|
||||||
foreach (var token in lineTokens)
|
|
||||||
{
|
|
||||||
if (token is WhitespaceToken) continue;
|
|
||||||
|
|
||||||
var tokenStart = token.Span.StartColumn;
|
|
||||||
var tokenEnd = token.Span.EndColumn;
|
|
||||||
|
|
||||||
if (tokenStart > currentColumn && currentColumn - 1 < line.Length)
|
|
||||||
{
|
|
||||||
var beforeLength = Math.Min(tokenStart - currentColumn, line.Length - (currentColumn - 1));
|
|
||||||
if (beforeLength > 0)
|
|
||||||
{
|
|
||||||
var beforeToken = line.Substring(currentColumn - 1, beforeLength);
|
|
||||||
sb.Append(beforeToken);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var tokenLength = tokenEnd - tokenStart;
|
|
||||||
if (tokenStart >= 1 && tokenStart - 1 < line.Length && tokenLength > 0)
|
|
||||||
{
|
|
||||||
var availableLength = line.Length - (tokenStart - 1);
|
|
||||||
var actualLength = Math.Min(tokenLength, availableLength);
|
|
||||||
|
|
||||||
if (actualLength > 0)
|
|
||||||
{
|
|
||||||
var tokenText = line.Substring(tokenStart - 1, actualLength);
|
|
||||||
var coloredToken = ColorizeToken(token, tokenText);
|
|
||||||
sb.Append(coloredToken);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
currentColumn = tokenEnd;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (currentColumn - 1 < line.Length)
|
|
||||||
{
|
|
||||||
var remaining = line[(currentColumn - 1)..];
|
|
||||||
sb.Append(remaining);
|
|
||||||
}
|
|
||||||
|
|
||||||
return sb.ToString();
|
|
||||||
}
|
|
||||||
|
|
||||||
private static string ColorizeToken(Token token, string tokenText)
|
|
||||||
{
|
|
||||||
switch (token)
|
|
||||||
{
|
|
||||||
case CommentToken:
|
|
||||||
{
|
|
||||||
return ConsoleColors.Colorize(tokenText, ConsoleColors.Green);
|
|
||||||
}
|
|
||||||
case IdentifierToken:
|
|
||||||
{
|
|
||||||
return ConsoleColors.Colorize(tokenText, ConsoleColors.BrightWhite);
|
|
||||||
}
|
|
||||||
case StringLiteralToken:
|
|
||||||
{
|
|
||||||
return ConsoleColors.Colorize(tokenText, ConsoleColors.Green);
|
|
||||||
}
|
|
||||||
case IntLiteralToken:
|
|
||||||
case FloatLiteralToken:
|
|
||||||
case BoolLiteralToken:
|
|
||||||
{
|
|
||||||
return ConsoleColors.Colorize(tokenText, ConsoleColors.Magenta);
|
|
||||||
}
|
|
||||||
case SymbolToken symbolToken:
|
|
||||||
{
|
|
||||||
switch (symbolToken.Symbol)
|
|
||||||
{
|
|
||||||
case Symbol.Func:
|
|
||||||
case Symbol.Return:
|
|
||||||
case Symbol.If:
|
|
||||||
case Symbol.Else:
|
|
||||||
case Symbol.While:
|
|
||||||
case Symbol.Break:
|
|
||||||
case Symbol.Continue:
|
|
||||||
case Symbol.Struct:
|
|
||||||
case Symbol.Let:
|
|
||||||
case Symbol.Extern:
|
|
||||||
case Symbol.For:
|
|
||||||
case Symbol.In:
|
|
||||||
{
|
|
||||||
return ConsoleColors.Colorize(tokenText, ConsoleColors.Bold + ConsoleColors.Blue);
|
|
||||||
}
|
|
||||||
case Symbol.Assign:
|
|
||||||
case Symbol.Bang:
|
|
||||||
case Symbol.Equal:
|
|
||||||
case Symbol.NotEqual:
|
|
||||||
case Symbol.LessThan:
|
|
||||||
case Symbol.LessThanOrEqual:
|
|
||||||
case Symbol.GreaterThan:
|
|
||||||
case Symbol.GreaterThanOrEqual:
|
|
||||||
case Symbol.Plus:
|
|
||||||
case Symbol.Minus:
|
|
||||||
case Symbol.Star:
|
|
||||||
case Symbol.ForwardSlash:
|
|
||||||
case Symbol.Caret:
|
|
||||||
case Symbol.Ampersand:
|
|
||||||
{
|
|
||||||
return ConsoleColors.Colorize(tokenText, ConsoleColors.Yellow);
|
|
||||||
}
|
|
||||||
case Symbol.Colon:
|
|
||||||
case Symbol.OpenParen:
|
|
||||||
case Symbol.CloseParen:
|
|
||||||
case Symbol.OpenBrace:
|
|
||||||
case Symbol.CloseBrace:
|
|
||||||
case Symbol.OpenBracket:
|
|
||||||
case Symbol.CloseBracket:
|
|
||||||
case Symbol.Comma:
|
|
||||||
case Symbol.Period:
|
|
||||||
case Symbol.Semi:
|
|
||||||
{
|
|
||||||
return ConsoleColors.Colorize(tokenText, ConsoleColors.BrightBlack);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return tokenText;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public enum DiagnosticSeverity
|
|
||||||
{
|
|
||||||
Info,
|
|
||||||
Warning,
|
|
||||||
Error
|
|
||||||
}
|
|
||||||
|
|
||||||
public static class ConsoleColors
|
|
||||||
{
|
|
||||||
public const string Reset = "\e[0m";
|
|
||||||
public const string Bold = "\e[1m";
|
|
||||||
public const string Faint = "\e[2m";
|
|
||||||
public const string Italic = "\e[3m";
|
|
||||||
public const string Underline = "\e[4m";
|
|
||||||
public const string SlowBlink = "\e[5m";
|
|
||||||
public const string RapidBlink = "\e[6m";
|
|
||||||
public const string SwapBgAndFg = "\e[7m";
|
|
||||||
public const string Conceal = "\e[8m";
|
|
||||||
public const string CrossedOut = "\e[9m";
|
|
||||||
|
|
||||||
public const string DefaultFont = "\e[10m";
|
|
||||||
public const string AltFont1 = "\e[11m";
|
|
||||||
public const string AltFont2 = "\e[12m";
|
|
||||||
public const string AltFont3 = "\e[13m";
|
|
||||||
public const string AltFont4 = "\e[14m";
|
|
||||||
public const string AltFont5 = "\e[15m";
|
|
||||||
public const string AltFont6 = "\e[16m";
|
|
||||||
public const string AltFont7 = "\e[17m";
|
|
||||||
public const string AltFont8 = "\e[18m";
|
|
||||||
public const string AltFont9 = "\e[19m";
|
|
||||||
|
|
||||||
public const string Black = "\e[30m";
|
|
||||||
public const string Red = "\e[31m";
|
|
||||||
public const string Green = "\e[32m";
|
|
||||||
public const string Yellow = "\e[33m";
|
|
||||||
public const string Blue = "\e[34m";
|
|
||||||
public const string Magenta = "\e[35m";
|
|
||||||
public const string Cyan = "\e[36m";
|
|
||||||
public const string White = "\e[37m";
|
|
||||||
|
|
||||||
public const string BrightBlack = "\e[90m";
|
|
||||||
public const string BrightRed = "\e[91m";
|
|
||||||
public const string BrightGreen = "\e[92m";
|
|
||||||
public const string BrightYellow = "\e[93m";
|
|
||||||
public const string BrightBlue = "\e[94m";
|
|
||||||
public const string BrightMagenta = "\e[95m";
|
|
||||||
public const string BrightCyan = "\e[96m";
|
|
||||||
public const string BrightWhite = "\e[97m";
|
|
||||||
|
|
||||||
private static bool IsColorSupported()
|
|
||||||
{
|
|
||||||
var term = Environment.GetEnvironmentVariable("TERM");
|
|
||||||
var colorTerm = Environment.GetEnvironmentVariable("COLORTERM");
|
|
||||||
return !string.IsNullOrEmpty(term) || !string.IsNullOrEmpty(colorTerm) || !Console.IsOutputRedirected;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static string Colorize(string text, string color)
|
|
||||||
{
|
|
||||||
return IsColorSupported() ? $"{color}{text}{Reset}" : text;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,56 +0,0 @@
|
|||||||
namespace NubLang.Diagnostics;
|
|
||||||
|
|
||||||
public readonly struct SourceSpan
|
|
||||||
{
|
|
||||||
private readonly int _startIndex;
|
|
||||||
private readonly int _endIndex;
|
|
||||||
|
|
||||||
public static SourceSpan Merge(params IEnumerable<SourceSpan> spans)
|
|
||||||
{
|
|
||||||
var spanArray = spans as SourceSpan[] ?? spans.ToArray();
|
|
||||||
if (spanArray.Length == 0)
|
|
||||||
{
|
|
||||||
return new SourceSpan(string.Empty, string.Empty, 0, 0, 0, 0, 0, 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
var first = spanArray.MinBy(x => x._startIndex);
|
|
||||||
var last = spanArray.MaxBy(x => x._endIndex);
|
|
||||||
|
|
||||||
return new SourceSpan(first.SourcePath, first.Source, first._startIndex, last._endIndex, first.StartLine, last.EndLine, first.StartColumn, last.EndColumn);
|
|
||||||
}
|
|
||||||
|
|
||||||
public SourceSpan(string sourcePath, string source, int startIndex, int endIndex, int startLine, int startColumn, int endLine, int endColumn)
|
|
||||||
{
|
|
||||||
_startIndex = startIndex;
|
|
||||||
_endIndex = endIndex;
|
|
||||||
SourcePath = sourcePath;
|
|
||||||
Source = source;
|
|
||||||
StartLine = startLine;
|
|
||||||
StartColumn = startColumn;
|
|
||||||
EndLine = endLine;
|
|
||||||
EndColumn = endColumn;
|
|
||||||
}
|
|
||||||
|
|
||||||
public int StartLine { get; }
|
|
||||||
public int StartColumn { get; }
|
|
||||||
public int EndLine { get; }
|
|
||||||
public int EndColumn { get; }
|
|
||||||
|
|
||||||
public string SourcePath { get; }
|
|
||||||
public string Source { get; }
|
|
||||||
|
|
||||||
public override string ToString()
|
|
||||||
{
|
|
||||||
if (StartLine == EndLine && StartColumn == EndColumn)
|
|
||||||
{
|
|
||||||
return $"{SourcePath}:{StartColumn}:{StartColumn}";
|
|
||||||
}
|
|
||||||
|
|
||||||
if (StartLine == EndLine)
|
|
||||||
{
|
|
||||||
return $"{SourcePath}:{StartLine}:{StartColumn}-{EndColumn}";
|
|
||||||
}
|
|
||||||
|
|
||||||
return $"{SourcePath}:{StartLine}:{StartColumn}-{EndLine}:{EndColumn}";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,70 +0,0 @@
|
|||||||
using System.Text;
|
|
||||||
|
|
||||||
namespace NubLang.Generation;
|
|
||||||
|
|
||||||
internal class IndentedTextWriter
|
|
||||||
{
|
|
||||||
private readonly StringBuilder _builder = new();
|
|
||||||
private int _indentLevel;
|
|
||||||
|
|
||||||
public IDisposable Indent()
|
|
||||||
{
|
|
||||||
_indentLevel++;
|
|
||||||
return new IndentScope(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void WriteLine(string text)
|
|
||||||
{
|
|
||||||
WriteIndent();
|
|
||||||
_builder.AppendLine(text);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Write(string text)
|
|
||||||
{
|
|
||||||
WriteIndent();
|
|
||||||
_builder.Append(text);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void WriteLine()
|
|
||||||
{
|
|
||||||
_builder.AppendLine();
|
|
||||||
}
|
|
||||||
|
|
||||||
public override string ToString()
|
|
||||||
{
|
|
||||||
return _builder.ToString();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void WriteIndent()
|
|
||||||
{
|
|
||||||
if (_builder.Length > 0)
|
|
||||||
{
|
|
||||||
var lastChar = _builder[^1];
|
|
||||||
if (lastChar != '\n' && lastChar != '\r')
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
for (var i = 0; i < _indentLevel; i++)
|
|
||||||
{
|
|
||||||
_builder.Append(" ");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private class IndentScope : IDisposable
|
|
||||||
{
|
|
||||||
private readonly IndentedTextWriter _writer;
|
|
||||||
private bool _disposed;
|
|
||||||
|
|
||||||
public IndentScope(IndentedTextWriter writer)
|
|
||||||
{
|
|
||||||
_writer = writer;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Dispose()
|
|
||||||
{
|
|
||||||
if (_disposed) return;
|
|
||||||
_writer._indentLevel--;
|
|
||||||
_disposed = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,781 +0,0 @@
|
|||||||
using System.Text;
|
|
||||||
using LLVMSharp.Interop;
|
|
||||||
using NubLang.Ast;
|
|
||||||
using NubLang.Modules;
|
|
||||||
using NubLang.Types;
|
|
||||||
|
|
||||||
namespace NubLang.Generation;
|
|
||||||
|
|
||||||
public class LlvmSharpGenerator
|
|
||||||
{
|
|
||||||
private string _module = string.Empty;
|
|
||||||
private LLVMContextRef _context;
|
|
||||||
private LLVMModuleRef _llvmModule;
|
|
||||||
private LLVMBuilderRef _builder;
|
|
||||||
private readonly Dictionary<string, LLVMTypeRef> _structTypes = new();
|
|
||||||
private readonly Dictionary<string, LLVMValueRef> _functions = new();
|
|
||||||
private readonly Dictionary<string, LLVMValueRef> _locals = new();
|
|
||||||
private readonly Stack<(LLVMBasicBlockRef breakBlock, LLVMBasicBlockRef continueBlock)> _loopStack = new();
|
|
||||||
|
|
||||||
public void Emit(List<TopLevelNode> topLevelNodes, ModuleRepository repository, string sourceFileName, string outputPath)
|
|
||||||
{
|
|
||||||
_module = topLevelNodes.OfType<ModuleNode>().First().NameToken.Value;
|
|
||||||
|
|
||||||
_context = LLVMContextRef.Global;
|
|
||||||
_llvmModule = _context.CreateModuleWithName(sourceFileName);
|
|
||||||
_llvmModule.Target = "x86_64-pc-linux-gnu";
|
|
||||||
_llvmModule.DataLayout = "e-m:e-p270:32:32-p271:32:32-p272:64:64-i64:64-i128:128-f80:128-n8:16:32:64-S128";
|
|
||||||
|
|
||||||
_builder = _context.CreateBuilder();
|
|
||||||
|
|
||||||
_structTypes.Clear();
|
|
||||||
_functions.Clear();
|
|
||||||
_locals.Clear();
|
|
||||||
_loopStack.Clear();
|
|
||||||
|
|
||||||
var stringType = _context.CreateNamedStruct("nub.string");
|
|
||||||
stringType.StructSetBody([LLVMTypeRef.Int64, LLVMTypeRef.CreatePointer(LLVMTypeRef.Int8, 0)], false);
|
|
||||||
_structTypes["nub.string"] = stringType;
|
|
||||||
|
|
||||||
foreach (var module in repository.GetAll())
|
|
||||||
{
|
|
||||||
foreach (var structType in module.StructTypes)
|
|
||||||
{
|
|
||||||
var structName = StructName(structType.Module, structType.Name);
|
|
||||||
var llvmStructType = _context.CreateNamedStruct(structName);
|
|
||||||
_structTypes[structName] = llvmStructType;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
foreach (var module in repository.GetAll())
|
|
||||||
{
|
|
||||||
foreach (var structType in module.StructTypes)
|
|
||||||
{
|
|
||||||
var structName = StructName(structType.Module, structType.Name);
|
|
||||||
var llvmStructType = _structTypes[structName];
|
|
||||||
var fieldTypes = structType.Fields.Select(f => MapType(f.Type)).ToArray();
|
|
||||||
llvmStructType.StructSetBody(fieldTypes, false);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
foreach (var module in repository.GetAll())
|
|
||||||
{
|
|
||||||
foreach (var prototype in module.FunctionPrototypes)
|
|
||||||
{
|
|
||||||
CreateFunctionDeclaration(prototype, module.Name);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
foreach (var structNode in topLevelNodes.OfType<StructNode>())
|
|
||||||
{
|
|
||||||
EmitStructConstructor(structNode);
|
|
||||||
}
|
|
||||||
|
|
||||||
foreach (var funcNode in topLevelNodes.OfType<FuncNode>())
|
|
||||||
{
|
|
||||||
if (funcNode.Body != null)
|
|
||||||
{
|
|
||||||
EmitFunction(funcNode);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!_llvmModule.TryVerify(LLVMVerifierFailureAction.LLVMPrintMessageAction, out var error))
|
|
||||||
{
|
|
||||||
// throw new Exception($"LLVM module verification failed: {error}");
|
|
||||||
}
|
|
||||||
|
|
||||||
_llvmModule.PrintToFile(outputPath);
|
|
||||||
|
|
||||||
_builder.Dispose();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void CreateFunctionDeclaration(FuncPrototypeNode prototype, string moduleName)
|
|
||||||
{
|
|
||||||
var funcName = FuncName(moduleName, prototype.NameToken.Value, prototype.ExternSymbolToken?.Value);
|
|
||||||
|
|
||||||
var paramTypes = prototype.Parameters.Select(p => MapType(p.Type)).ToArray();
|
|
||||||
var returnType = MapType(prototype.ReturnType);
|
|
||||||
|
|
||||||
var funcType = LLVMTypeRef.CreateFunction(returnType, paramTypes);
|
|
||||||
var func = _llvmModule.AddFunction(funcName, funcType);
|
|
||||||
|
|
||||||
func.FunctionCallConv = (uint)LLVMCallConv.LLVMCCallConv;
|
|
||||||
|
|
||||||
for (var i = 0; i < prototype.Parameters.Count; i++)
|
|
||||||
{
|
|
||||||
func.GetParam((uint)i).Name = prototype.Parameters[i].NameToken.Value;
|
|
||||||
}
|
|
||||||
|
|
||||||
_functions[funcName] = func;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void EmitStructConstructor(StructNode structNode)
|
|
||||||
{
|
|
||||||
var structType = _structTypes[StructName(_module, structNode.NameToken.Value)];
|
|
||||||
var ptrType = LLVMTypeRef.CreatePointer(structType, 0);
|
|
||||||
|
|
||||||
var funcType = LLVMTypeRef.CreateFunction(LLVMTypeRef.Void, [ptrType]);
|
|
||||||
var funcName = StructConstructorName(_module, structNode.NameToken.Value);
|
|
||||||
var func = _llvmModule.AddFunction(funcName, funcType);
|
|
||||||
func.FunctionCallConv = (uint)LLVMCallConv.LLVMCCallConv;
|
|
||||||
|
|
||||||
var entryBlock = func.AppendBasicBlock("entry");
|
|
||||||
_builder.PositionAtEnd(entryBlock);
|
|
||||||
|
|
||||||
var selfParam = func.GetParam(0);
|
|
||||||
selfParam.Name = "self";
|
|
||||||
|
|
||||||
_locals.Clear();
|
|
||||||
|
|
||||||
foreach (var field in structNode.Fields)
|
|
||||||
{
|
|
||||||
if (field.Value != null)
|
|
||||||
{
|
|
||||||
var index = structNode.StructType.GetFieldIndex(field.NameToken.Value);
|
|
||||||
var fieldPtr = _builder.BuildStructGEP2(structType, selfParam, (uint)index);
|
|
||||||
EmitExpressionInto(field.Value, fieldPtr);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
_builder.BuildRetVoid();
|
|
||||||
_functions[funcName] = func;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void EmitFunction(FuncNode funcNode)
|
|
||||||
{
|
|
||||||
var funcName = FuncName(_module, funcNode.Prototype.NameToken.Value, funcNode.Prototype.ExternSymbolToken?.Value);
|
|
||||||
var func = _functions[funcName];
|
|
||||||
|
|
||||||
var entryBlock = func.AppendBasicBlock("entry");
|
|
||||||
_builder.PositionAtEnd(entryBlock);
|
|
||||||
|
|
||||||
_locals.Clear();
|
|
||||||
|
|
||||||
for (uint i = 0; i < funcNode.Prototype.Parameters.Count; i++)
|
|
||||||
{
|
|
||||||
var param = func.GetParam(i);
|
|
||||||
var paramNode = funcNode.Prototype.Parameters[(int)i];
|
|
||||||
var alloca = _builder.BuildAlloca(MapType(paramNode.Type), paramNode.NameToken.Value);
|
|
||||||
_builder.BuildStore(param, alloca);
|
|
||||||
_locals[paramNode.NameToken.Value] = alloca;
|
|
||||||
}
|
|
||||||
|
|
||||||
EmitBlock(funcNode.Body!);
|
|
||||||
|
|
||||||
if (funcNode.Prototype.ReturnType is NubVoidType)
|
|
||||||
{
|
|
||||||
if (_builder.InsertBlock.Terminator.Handle == IntPtr.Zero)
|
|
||||||
{
|
|
||||||
_builder.BuildRetVoid();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void EmitBlock(BlockNode blockNode)
|
|
||||||
{
|
|
||||||
foreach (var statement in blockNode.Statements)
|
|
||||||
{
|
|
||||||
EmitStatement(statement);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void EmitStatement(StatementNode statement)
|
|
||||||
{
|
|
||||||
switch (statement)
|
|
||||||
{
|
|
||||||
case AssignmentNode assignment:
|
|
||||||
EmitAssignment(assignment);
|
|
||||||
break;
|
|
||||||
case BlockNode block:
|
|
||||||
EmitBlock(block);
|
|
||||||
break;
|
|
||||||
case BreakNode:
|
|
||||||
EmitBreak();
|
|
||||||
break;
|
|
||||||
case ContinueNode:
|
|
||||||
EmitContinue();
|
|
||||||
break;
|
|
||||||
case IfNode ifNode:
|
|
||||||
EmitIf(ifNode);
|
|
||||||
break;
|
|
||||||
case ReturnNode returnNode:
|
|
||||||
EmitReturn(returnNode);
|
|
||||||
break;
|
|
||||||
case StatementFuncCallNode funcCall:
|
|
||||||
EmitExpression(funcCall.FuncCall);
|
|
||||||
break;
|
|
||||||
case VariableDeclarationNode varDecl:
|
|
||||||
EmitVariableDeclaration(varDecl);
|
|
||||||
break;
|
|
||||||
case WhileNode whileNode:
|
|
||||||
EmitWhile(whileNode);
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
throw new NotImplementedException($"Statement type {statement.GetType()} not implemented");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void EmitAssignment(AssignmentNode assignment)
|
|
||||||
{
|
|
||||||
var targetPtr = EmitExpression(assignment.Target, asLValue: true);
|
|
||||||
var value = EmitExpression(assignment.Value);
|
|
||||||
_builder.BuildStore(value, targetPtr);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void EmitBreak()
|
|
||||||
{
|
|
||||||
var (breakBlock, _) = _loopStack.Peek();
|
|
||||||
_builder.BuildBr(breakBlock);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void EmitContinue()
|
|
||||||
{
|
|
||||||
var (_, continueBlock) = _loopStack.Peek();
|
|
||||||
_builder.BuildBr(continueBlock);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void EmitIf(IfNode ifNode)
|
|
||||||
{
|
|
||||||
var condition = EmitExpression(ifNode.Condition);
|
|
||||||
|
|
||||||
var func = _builder.InsertBlock.Parent;
|
|
||||||
var thenBlock = func.AppendBasicBlock("if.then");
|
|
||||||
var elseBlock = ifNode.Else.HasValue ? func.AppendBasicBlock("if.else") : default;
|
|
||||||
var endBlock = func.AppendBasicBlock("if.end");
|
|
||||||
|
|
||||||
_builder.BuildCondBr(condition, thenBlock, ifNode.Else.HasValue ? elseBlock : endBlock);
|
|
||||||
|
|
||||||
_builder.PositionAtEnd(thenBlock);
|
|
||||||
EmitBlock(ifNode.Body);
|
|
||||||
if (_builder.InsertBlock.Terminator.Handle == IntPtr.Zero)
|
|
||||||
{
|
|
||||||
_builder.BuildBr(endBlock);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (ifNode.Else.HasValue)
|
|
||||||
{
|
|
||||||
_builder.PositionAtEnd(elseBlock);
|
|
||||||
ifNode.Else.Value.Match(EmitIf, EmitBlock);
|
|
||||||
if (_builder.InsertBlock.Terminator.Handle == IntPtr.Zero)
|
|
||||||
{
|
|
||||||
_builder.BuildBr(endBlock);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
_builder.PositionAtEnd(endBlock);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void EmitReturn(ReturnNode returnNode)
|
|
||||||
{
|
|
||||||
if (returnNode.Value != null)
|
|
||||||
{
|
|
||||||
var value = EmitExpression(returnNode.Value);
|
|
||||||
_builder.BuildRet(value);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
_builder.BuildRetVoid();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void EmitVariableDeclaration(VariableDeclarationNode varDecl)
|
|
||||||
{
|
|
||||||
var alloca = _builder.BuildAlloca(MapType(varDecl.Type), varDecl.NameToken.Value);
|
|
||||||
_locals[varDecl.NameToken.Value] = alloca;
|
|
||||||
|
|
||||||
if (varDecl.Assignment != null)
|
|
||||||
{
|
|
||||||
EmitExpressionInto(varDecl.Assignment, alloca);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void EmitWhile(WhileNode whileNode)
|
|
||||||
{
|
|
||||||
var func = _builder.InsertBlock.Parent;
|
|
||||||
var condBlock = func.AppendBasicBlock("while.cond");
|
|
||||||
var bodyBlock = func.AppendBasicBlock("while.body");
|
|
||||||
var endBlock = func.AppendBasicBlock("while.end");
|
|
||||||
|
|
||||||
_loopStack.Push((endBlock, condBlock));
|
|
||||||
|
|
||||||
_builder.BuildBr(condBlock);
|
|
||||||
|
|
||||||
_builder.PositionAtEnd(condBlock);
|
|
||||||
var condition = EmitExpression(whileNode.Condition);
|
|
||||||
_builder.BuildCondBr(condition, bodyBlock, endBlock);
|
|
||||||
|
|
||||||
_builder.PositionAtEnd(bodyBlock);
|
|
||||||
EmitBlock(whileNode.Body);
|
|
||||||
if (_builder.InsertBlock.Terminator.Handle == IntPtr.Zero)
|
|
||||||
{
|
|
||||||
_builder.BuildBr(condBlock);
|
|
||||||
}
|
|
||||||
|
|
||||||
_loopStack.Pop();
|
|
||||||
|
|
||||||
_builder.PositionAtEnd(endBlock);
|
|
||||||
}
|
|
||||||
|
|
||||||
private LLVMValueRef EmitExpression(ExpressionNode expr, bool asLValue = false)
|
|
||||||
{
|
|
||||||
var result = expr switch
|
|
||||||
{
|
|
||||||
StringLiteralNode stringLiteralNode => EmitStringLiteral(stringLiteralNode),
|
|
||||||
CStringLiteralNode cStringLiteralNode => EmitCStringLiteral(cStringLiteralNode),
|
|
||||||
BoolLiteralNode b => LLVMValueRef.CreateConstInt(LLVMTypeRef.Int1, b.Value ? 1UL : 0UL),
|
|
||||||
I8LiteralNode i => LLVMValueRef.CreateConstInt(LLVMTypeRef.Int8, (ulong)i.Value, true),
|
|
||||||
I16LiteralNode i => LLVMValueRef.CreateConstInt(LLVMTypeRef.Int16, (ulong)i.Value, true),
|
|
||||||
I32LiteralNode i => LLVMValueRef.CreateConstInt(LLVMTypeRef.Int32, (ulong)i.Value, true),
|
|
||||||
I64LiteralNode i => LLVMValueRef.CreateConstInt(LLVMTypeRef.Int64, (ulong)i.Value, true),
|
|
||||||
U8LiteralNode u => LLVMValueRef.CreateConstInt(LLVMTypeRef.Int8, u.Value),
|
|
||||||
U16LiteralNode u => LLVMValueRef.CreateConstInt(LLVMTypeRef.Int16, u.Value),
|
|
||||||
U32LiteralNode u => LLVMValueRef.CreateConstInt(LLVMTypeRef.Int32, u.Value),
|
|
||||||
U64LiteralNode u => LLVMValueRef.CreateConstInt(LLVMTypeRef.Int64, u.Value),
|
|
||||||
Float32LiteralNode f => LLVMValueRef.CreateConstReal(LLVMTypeRef.Float, f.Value),
|
|
||||||
Float64LiteralNode f => LLVMValueRef.CreateConstReal(LLVMTypeRef.Double, f.Value),
|
|
||||||
|
|
||||||
VariableIdentifierNode v => EmitVariableIdentifier(v),
|
|
||||||
LocalFuncIdentifierNode localFuncIdentifierNode => EmitLocalFuncIdentifier(localFuncIdentifierNode),
|
|
||||||
ModuleFuncIdentifierNode moduleFuncIdentifierNode => EmitModuleFuncIdentifier(moduleFuncIdentifierNode),
|
|
||||||
|
|
||||||
BinaryExpressionNode bin => EmitBinaryExpression(bin),
|
|
||||||
UnaryExpressionNode unary => EmitUnaryExpression(unary),
|
|
||||||
|
|
||||||
StructFieldAccessNode field => EmitStructFieldAccess(field),
|
|
||||||
ConstArrayIndexAccessNode arr => EmitConstArrayIndexAccess(arr),
|
|
||||||
SliceIndexAccessNode sliceIndexAccessNode => EmitSliceIndexAccess(sliceIndexAccessNode),
|
|
||||||
ArrayIndexAccessNode arrayIndexAccessNode => EmitArrayIndexAccess(arrayIndexAccessNode),
|
|
||||||
|
|
||||||
ConstArrayInitializerNode constArrayInitializerNode => EmitConstArrayInitializer(constArrayInitializerNode),
|
|
||||||
StructInitializerNode structInitializerNode => EmitStructInitializer(structInitializerNode),
|
|
||||||
|
|
||||||
AddressOfNode addr => EmitAddressOf(addr),
|
|
||||||
DereferenceNode deref => EmitDereference(deref),
|
|
||||||
|
|
||||||
FuncCallNode funcCall => EmitFuncCall(funcCall),
|
|
||||||
CastNode cast => EmitCast(cast),
|
|
||||||
SizeNode size => LLVMValueRef.CreateConstInt(LLVMTypeRef.Int64, size.TargetType.GetSize()),
|
|
||||||
|
|
||||||
_ => throw new ArgumentOutOfRangeException(nameof(expr), expr, null)
|
|
||||||
};
|
|
||||||
|
|
||||||
if (expr is LValue)
|
|
||||||
{
|
|
||||||
if (asLValue)
|
|
||||||
{
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
return _builder.BuildLoad2(MapType(expr.Type), result);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (asLValue)
|
|
||||||
{
|
|
||||||
throw new InvalidOperationException($"Expression of type {expr.GetType().Name} is not an lvalue and cannot be used where an address is required");
|
|
||||||
}
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void EmitExpressionInto(ExpressionNode expr, LLVMValueRef destPtr)
|
|
||||||
{
|
|
||||||
switch (expr)
|
|
||||||
{
|
|
||||||
case StructInitializerNode structInit:
|
|
||||||
EmitStructInitializer(structInit, destPtr);
|
|
||||||
return;
|
|
||||||
case ConstArrayInitializerNode arrayInit:
|
|
||||||
EmitConstArrayInitializer(arrayInit, destPtr);
|
|
||||||
return;
|
|
||||||
default:
|
|
||||||
{
|
|
||||||
var result = EmitExpression(expr);
|
|
||||||
_builder.BuildStore(result, destPtr);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private LLVMValueRef EmitStringLiteral(StringLiteralNode stringLiteralNode)
|
|
||||||
{
|
|
||||||
var strValue = stringLiteralNode.Value;
|
|
||||||
var length = (ulong)Encoding.UTF8.GetByteCount(strValue);
|
|
||||||
var globalStr = _builder.BuildGlobalStringPtr(strValue);
|
|
||||||
var llvmStringType = MapType(stringLiteralNode.Type);
|
|
||||||
|
|
||||||
var strAlloca = _builder.BuildAlloca(llvmStringType);
|
|
||||||
|
|
||||||
var lengthPtr = _builder.BuildStructGEP2(llvmStringType, strAlloca, 0);
|
|
||||||
var lengthConst = LLVMValueRef.CreateConstInt(LLVMTypeRef.Int64, length);
|
|
||||||
_builder.BuildStore(lengthConst, lengthPtr);
|
|
||||||
|
|
||||||
var dataPtr = _builder.BuildStructGEP2(llvmStringType, strAlloca, 1);
|
|
||||||
_builder.BuildStore(globalStr, dataPtr);
|
|
||||||
|
|
||||||
return _builder.BuildLoad2(llvmStringType, strAlloca);
|
|
||||||
}
|
|
||||||
|
|
||||||
private LLVMValueRef EmitCStringLiteral(CStringLiteralNode cStringLiteralNode)
|
|
||||||
{
|
|
||||||
return _builder.BuildGlobalStringPtr(cStringLiteralNode.Value);
|
|
||||||
}
|
|
||||||
|
|
||||||
private LLVMValueRef EmitVariableIdentifier(VariableIdentifierNode v)
|
|
||||||
{
|
|
||||||
return _locals[v.NameToken.Value];
|
|
||||||
}
|
|
||||||
|
|
||||||
private LLVMValueRef EmitLocalFuncIdentifier(LocalFuncIdentifierNode localFuncIdentifierNode)
|
|
||||||
{
|
|
||||||
return _functions[FuncName(_module, localFuncIdentifierNode.NameToken.Value, localFuncIdentifierNode.ExternSymbolToken?.Value)];
|
|
||||||
}
|
|
||||||
|
|
||||||
private LLVMValueRef EmitModuleFuncIdentifier(ModuleFuncIdentifierNode moduleFuncIdentifierNode)
|
|
||||||
{
|
|
||||||
return _functions[FuncName(moduleFuncIdentifierNode.ModuleToken.Value, moduleFuncIdentifierNode.NameToken.Value, moduleFuncIdentifierNode.ExternSymbolToken?.Value)];
|
|
||||||
}
|
|
||||||
|
|
||||||
private LLVMValueRef EmitBinaryExpression(BinaryExpressionNode bin)
|
|
||||||
{
|
|
||||||
var left = EmitExpression(bin.Left);
|
|
||||||
var right = EmitExpression(bin.Right);
|
|
||||||
|
|
||||||
var leftType = bin.Left.Type;
|
|
||||||
|
|
||||||
var result = bin.Operator switch
|
|
||||||
{
|
|
||||||
BinaryOperator.Plus when leftType is NubIntType => _builder.BuildAdd(left, right),
|
|
||||||
BinaryOperator.Plus when leftType is NubFloatType => _builder.BuildFAdd(left, right),
|
|
||||||
|
|
||||||
BinaryOperator.Minus when leftType is NubIntType => _builder.BuildSub(left, right),
|
|
||||||
BinaryOperator.Minus when leftType is NubFloatType => _builder.BuildFSub(left, right),
|
|
||||||
|
|
||||||
BinaryOperator.Multiply when leftType is NubIntType => _builder.BuildMul(left, right),
|
|
||||||
BinaryOperator.Multiply when leftType is NubFloatType => _builder.BuildFMul(left, right),
|
|
||||||
|
|
||||||
BinaryOperator.Divide when leftType is NubIntType intType => intType.Signed ? _builder.BuildSDiv(left, right) : _builder.BuildUDiv(left, right),
|
|
||||||
BinaryOperator.Divide when leftType is NubFloatType => _builder.BuildFDiv(left, right),
|
|
||||||
|
|
||||||
BinaryOperator.Modulo when leftType is NubIntType intType => intType.Signed ? _builder.BuildSRem(left, right) : _builder.BuildURem(left, right),
|
|
||||||
BinaryOperator.Modulo when leftType is NubFloatType => _builder.BuildFRem(left, right),
|
|
||||||
|
|
||||||
BinaryOperator.LogicalAnd => _builder.BuildAnd(left, right),
|
|
||||||
BinaryOperator.LogicalOr => _builder.BuildOr(left, right),
|
|
||||||
|
|
||||||
BinaryOperator.Equal when leftType is NubIntType or NubBoolType or NubPointerType => _builder.BuildICmp(LLVMIntPredicate.LLVMIntEQ, left, right),
|
|
||||||
BinaryOperator.Equal when leftType is NubFloatType => _builder.BuildFCmp(LLVMRealPredicate.LLVMRealOEQ, left, right),
|
|
||||||
|
|
||||||
BinaryOperator.NotEqual when leftType is NubIntType or NubBoolType or NubPointerType => _builder.BuildICmp(LLVMIntPredicate.LLVMIntNE, left, right),
|
|
||||||
BinaryOperator.NotEqual when leftType is NubFloatType => _builder.BuildFCmp(LLVMRealPredicate.LLVMRealONE, left, right),
|
|
||||||
|
|
||||||
BinaryOperator.GreaterThan when leftType is NubIntType intType => _builder.BuildICmp(intType.Signed ? LLVMIntPredicate.LLVMIntSGT : LLVMIntPredicate.LLVMIntUGT, left, right),
|
|
||||||
BinaryOperator.GreaterThan when leftType is NubFloatType => _builder.BuildFCmp(LLVMRealPredicate.LLVMRealOGT, left, right),
|
|
||||||
|
|
||||||
BinaryOperator.GreaterThanOrEqual when leftType is NubIntType intType => _builder.BuildICmp(intType.Signed ? LLVMIntPredicate.LLVMIntSGE : LLVMIntPredicate.LLVMIntUGE, left, right),
|
|
||||||
BinaryOperator.GreaterThanOrEqual when leftType is NubFloatType => _builder.BuildFCmp(LLVMRealPredicate.LLVMRealOGE, left, right),
|
|
||||||
|
|
||||||
BinaryOperator.LessThan when leftType is NubIntType intType => _builder.BuildICmp(intType.Signed ? LLVMIntPredicate.LLVMIntSLT : LLVMIntPredicate.LLVMIntULT, left, right),
|
|
||||||
BinaryOperator.LessThan when leftType is NubFloatType => _builder.BuildFCmp(LLVMRealPredicate.LLVMRealOLT, left, right),
|
|
||||||
|
|
||||||
BinaryOperator.LessThanOrEqual when leftType is NubIntType intType => _builder.BuildICmp(intType.Signed ? LLVMIntPredicate.LLVMIntSLE : LLVMIntPredicate.LLVMIntULE, left, right),
|
|
||||||
BinaryOperator.LessThanOrEqual when leftType is NubFloatType => _builder.BuildFCmp(LLVMRealPredicate.LLVMRealOLE, left, right),
|
|
||||||
|
|
||||||
BinaryOperator.LeftShift => _builder.BuildShl(left, right),
|
|
||||||
BinaryOperator.RightShift when leftType is NubIntType intType => intType.Signed ? _builder.BuildAShr(left, right) : _builder.BuildLShr(left, right),
|
|
||||||
|
|
||||||
BinaryOperator.BitwiseAnd => _builder.BuildAnd(left, right),
|
|
||||||
BinaryOperator.BitwiseXor => _builder.BuildXor(left, right),
|
|
||||||
BinaryOperator.BitwiseOr => _builder.BuildOr(left, right),
|
|
||||||
|
|
||||||
_ => throw new ArgumentOutOfRangeException(nameof(bin.Operator))
|
|
||||||
};
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
private LLVMValueRef EmitUnaryExpression(UnaryExpressionNode unary)
|
|
||||||
{
|
|
||||||
var operand = EmitExpression(unary.Operand);
|
|
||||||
|
|
||||||
var result = unary.Operator switch
|
|
||||||
{
|
|
||||||
UnaryOperator.Negate when unary.Operand.Type is NubIntType => _builder.BuildNeg(operand),
|
|
||||||
UnaryOperator.Negate when unary.Operand.Type is NubFloatType => _builder.BuildFNeg(operand),
|
|
||||||
UnaryOperator.Invert => _builder.BuildXor(operand, LLVMValueRef.CreateConstInt(LLVMTypeRef.Int1, 1)),
|
|
||||||
_ => throw new ArgumentOutOfRangeException(nameof(unary.Operator))
|
|
||||||
};
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
private LLVMValueRef EmitFuncCall(FuncCallNode funcCall)
|
|
||||||
{
|
|
||||||
var funcPtr = EmitExpression(funcCall.Expression);
|
|
||||||
var args = funcCall.Parameters.Select(x => EmitExpression(x)).ToArray();
|
|
||||||
return _builder.BuildCall2(MapType(funcCall.Expression.Type), funcPtr, args, funcCall.Type is NubVoidType ? "" : "call");
|
|
||||||
}
|
|
||||||
|
|
||||||
private LLVMValueRef EmitStructFieldAccess(StructFieldAccessNode field)
|
|
||||||
{
|
|
||||||
var target = EmitExpression(field.Target, asLValue: true);
|
|
||||||
var structType = (NubStructType)field.Target.Type;
|
|
||||||
var index = structType.GetFieldIndex(field.FieldToken.Value);
|
|
||||||
|
|
||||||
var llvmStructType = _structTypes[StructName(structType.Module, structType.Name)];
|
|
||||||
return _builder.BuildStructGEP2(llvmStructType, target, (uint)index);
|
|
||||||
}
|
|
||||||
|
|
||||||
private LLVMValueRef EmitConstArrayIndexAccess(ConstArrayIndexAccessNode constArrayIndexAccessNode)
|
|
||||||
{
|
|
||||||
var arrayPtr = EmitExpression(constArrayIndexAccessNode.Target, asLValue: true);
|
|
||||||
var index = EmitExpression(constArrayIndexAccessNode.Index);
|
|
||||||
var indices = new[] { LLVMValueRef.CreateConstInt(LLVMTypeRef.Int32, 0), index };
|
|
||||||
return _builder.BuildInBoundsGEP2(MapType(constArrayIndexAccessNode.Target.Type), arrayPtr, indices);
|
|
||||||
}
|
|
||||||
|
|
||||||
private LLVMValueRef EmitSliceIndexAccess(SliceIndexAccessNode sliceIndexAccessNode)
|
|
||||||
{
|
|
||||||
var slicePtr = EmitExpression(sliceIndexAccessNode.Target, asLValue: true);
|
|
||||||
var index = EmitExpression(sliceIndexAccessNode.Index);
|
|
||||||
|
|
||||||
var sliceType = (NubSliceType)sliceIndexAccessNode.Target.Type;
|
|
||||||
var llvmSliceType = MapType(sliceType);
|
|
||||||
var elementType = MapType(sliceType.ElementType);
|
|
||||||
|
|
||||||
var dataPtrPtr = _builder.BuildStructGEP2(llvmSliceType, slicePtr, 1);
|
|
||||||
var dataPtr = _builder.BuildLoad2(LLVMTypeRef.CreatePointer(elementType, 0), dataPtrPtr);
|
|
||||||
return _builder.BuildInBoundsGEP2(elementType, dataPtr, [index]);
|
|
||||||
}
|
|
||||||
|
|
||||||
private LLVMValueRef EmitArrayIndexAccess(ArrayIndexAccessNode arrayIndexAccessNode)
|
|
||||||
{
|
|
||||||
var arrayPtr = EmitExpression(arrayIndexAccessNode.Target);
|
|
||||||
var index = EmitExpression(arrayIndexAccessNode.Index);
|
|
||||||
return _builder.BuildGEP2(MapType(arrayIndexAccessNode.Target.Type), arrayPtr, [index]);
|
|
||||||
}
|
|
||||||
|
|
||||||
private LLVMValueRef EmitConstArrayInitializer(ConstArrayInitializerNode constArrayInitializerNode, LLVMValueRef? destination = null)
|
|
||||||
{
|
|
||||||
var arrayType = (NubConstArrayType)constArrayInitializerNode.Type;
|
|
||||||
var llvmType = MapType(arrayType);
|
|
||||||
|
|
||||||
destination ??= _builder.BuildAlloca(llvmType);
|
|
||||||
|
|
||||||
for (var i = 0; i < constArrayInitializerNode.Values.Count; i++)
|
|
||||||
{
|
|
||||||
var indices = new[]
|
|
||||||
{
|
|
||||||
LLVMValueRef.CreateConstInt(LLVMTypeRef.Int32, 0),
|
|
||||||
LLVMValueRef.CreateConstInt(LLVMTypeRef.Int32, (ulong)i)
|
|
||||||
};
|
|
||||||
|
|
||||||
var elementPtr = _builder.BuildInBoundsGEP2(llvmType, destination.Value, indices);
|
|
||||||
|
|
||||||
EmitExpressionInto(constArrayInitializerNode.Values[i], elementPtr);
|
|
||||||
}
|
|
||||||
|
|
||||||
return destination.Value;
|
|
||||||
}
|
|
||||||
|
|
||||||
private LLVMValueRef EmitStructInitializer(StructInitializerNode structInitializerNode, LLVMValueRef? destination = null)
|
|
||||||
{
|
|
||||||
var type = (NubStructType)structInitializerNode.Type;
|
|
||||||
var llvmType = MapType(type);
|
|
||||||
|
|
||||||
destination ??= _builder.BuildAlloca(llvmType);
|
|
||||||
|
|
||||||
var constructorType = LLVMTypeRef.CreateFunction(LLVMTypeRef.Void, [LLVMTypeRef.CreatePointer(_structTypes[StructName(type.Module, type.Name)], 0)]);
|
|
||||||
var constructor = _functions[StructConstructorName(type.Module, type.Name)];
|
|
||||||
_builder.BuildCall2(constructorType, constructor, [destination.Value]);
|
|
||||||
|
|
||||||
foreach (var (name, value) in structInitializerNode.Initializers)
|
|
||||||
{
|
|
||||||
var fieldIndex = type.GetFieldIndex(name.Value);
|
|
||||||
var fieldPtr = _builder.BuildStructGEP2(llvmType, destination.Value, (uint)fieldIndex);
|
|
||||||
EmitExpressionInto(value, fieldPtr);
|
|
||||||
}
|
|
||||||
|
|
||||||
return destination.Value;
|
|
||||||
}
|
|
||||||
|
|
||||||
private LLVMValueRef EmitAddressOf(AddressOfNode addr)
|
|
||||||
{
|
|
||||||
return EmitExpression(addr.Target, asLValue: true);
|
|
||||||
}
|
|
||||||
|
|
||||||
private LLVMValueRef EmitDereference(DereferenceNode deref)
|
|
||||||
{
|
|
||||||
return EmitExpression(deref.Target, asLValue: false);
|
|
||||||
}
|
|
||||||
|
|
||||||
private LLVMValueRef EmitCast(CastNode castNode)
|
|
||||||
{
|
|
||||||
return castNode.ConversionType switch
|
|
||||||
{
|
|
||||||
CastNode.Conversion.IntToInt => EmitIntToIntCast(castNode),
|
|
||||||
CastNode.Conversion.FloatToFloat => EmitFloatToFloatCast(castNode),
|
|
||||||
CastNode.Conversion.IntToFloat => EmitIntToFloatCast(castNode),
|
|
||||||
CastNode.Conversion.FloatToInt => EmitFloatToIntCast(castNode),
|
|
||||||
CastNode.Conversion.PointerToPointer or CastNode.Conversion.PointerToUInt64 or CastNode.Conversion.UInt64ToPointer => _builder.BuildIntToPtr(EmitExpression(castNode.Value), MapType(castNode.Type)),
|
|
||||||
CastNode.Conversion.ConstArrayToSlice => EmitConstArrayToSliceCast(castNode),
|
|
||||||
CastNode.Conversion.ConstArrayToArray => EmitConstArrayToArrayCast(castNode),
|
|
||||||
CastNode.Conversion.StringToCString => EmitStringToCStringCast(castNode),
|
|
||||||
_ => throw new ArgumentOutOfRangeException(nameof(castNode.ConversionType))
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
private LLVMValueRef EmitIntToIntCast(CastNode castNode)
|
|
||||||
{
|
|
||||||
var sourceInt = (NubIntType)castNode.Value.Type;
|
|
||||||
var targetInt = (NubIntType)castNode.Type;
|
|
||||||
var source = EmitExpression(castNode.Value);
|
|
||||||
|
|
||||||
if (sourceInt.Width < targetInt.Width)
|
|
||||||
{
|
|
||||||
return sourceInt.Signed
|
|
||||||
? _builder.BuildSExt(source, MapType(targetInt))
|
|
||||||
: _builder.BuildZExt(source, MapType(targetInt));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (sourceInt.Width > targetInt.Width)
|
|
||||||
{
|
|
||||||
return _builder.BuildTrunc(source, MapType(targetInt));
|
|
||||||
}
|
|
||||||
|
|
||||||
return _builder.BuildBitCast(source, MapType(targetInt));
|
|
||||||
}
|
|
||||||
|
|
||||||
private LLVMValueRef EmitFloatToFloatCast(CastNode castNode)
|
|
||||||
{
|
|
||||||
var sourceFloat = (NubFloatType)castNode.Value.Type;
|
|
||||||
var targetFloat = (NubFloatType)castNode.Type;
|
|
||||||
var source = EmitExpression(castNode.Value);
|
|
||||||
|
|
||||||
return sourceFloat.Width < targetFloat.Width
|
|
||||||
? _builder.BuildFPExt(source, MapType(castNode.Type))
|
|
||||||
: _builder.BuildFPTrunc(source, MapType(castNode.Type));
|
|
||||||
}
|
|
||||||
|
|
||||||
private LLVMValueRef EmitIntToFloatCast(CastNode castNode)
|
|
||||||
{
|
|
||||||
var sourceInt = (NubIntType)castNode.Value.Type;
|
|
||||||
var source = EmitExpression(castNode.Value);
|
|
||||||
|
|
||||||
return sourceInt.Signed
|
|
||||||
? _builder.BuildSIToFP(source, MapType(castNode.Type))
|
|
||||||
: _builder.BuildUIToFP(source, MapType(castNode.Type));
|
|
||||||
}
|
|
||||||
|
|
||||||
private LLVMValueRef EmitFloatToIntCast(CastNode castNode)
|
|
||||||
{
|
|
||||||
var targetInt = (NubIntType)castNode.Type;
|
|
||||||
var source = EmitExpression(castNode.Value);
|
|
||||||
|
|
||||||
return targetInt.Signed
|
|
||||||
? _builder.BuildFPToSI(source, MapType(targetInt))
|
|
||||||
: _builder.BuildFPToUI(source, MapType(targetInt));
|
|
||||||
}
|
|
||||||
|
|
||||||
private LLVMValueRef EmitConstArrayToSliceCast(CastNode castNode)
|
|
||||||
{
|
|
||||||
var sourceArrayType = (NubConstArrayType)castNode.Value.Type;
|
|
||||||
var targetSliceType = (NubSliceType)castNode.Type;
|
|
||||||
var source = EmitExpression(castNode.Value, asLValue: true);
|
|
||||||
|
|
||||||
var llvmArrayType = MapType(sourceArrayType);
|
|
||||||
var llvmSliceType = MapType(targetSliceType);
|
|
||||||
|
|
||||||
var indices = new[]
|
|
||||||
{
|
|
||||||
LLVMValueRef.CreateConstInt(LLVMTypeRef.Int32, 0),
|
|
||||||
LLVMValueRef.CreateConstInt(LLVMTypeRef.Int32, 0)
|
|
||||||
};
|
|
||||||
|
|
||||||
var firstElementPtr = _builder.BuildInBoundsGEP2(llvmArrayType, source, indices);
|
|
||||||
|
|
||||||
var slicePtr = _builder.BuildAlloca(llvmSliceType);
|
|
||||||
|
|
||||||
var lengthPtr = _builder.BuildStructGEP2(llvmSliceType, slicePtr, 0);
|
|
||||||
var length = LLVMValueRef.CreateConstInt(LLVMTypeRef.Int64, sourceArrayType.Size);
|
|
||||||
_builder.BuildStore(length, lengthPtr);
|
|
||||||
|
|
||||||
var dataPtrPtr = _builder.BuildStructGEP2(llvmSliceType, slicePtr, 1);
|
|
||||||
_builder.BuildStore(firstElementPtr, dataPtrPtr);
|
|
||||||
|
|
||||||
return _builder.BuildLoad2(llvmSliceType, slicePtr);
|
|
||||||
}
|
|
||||||
|
|
||||||
private LLVMValueRef EmitConstArrayToArrayCast(CastNode castNode)
|
|
||||||
{
|
|
||||||
var sourceArrayType = (NubConstArrayType)castNode.Value.Type;
|
|
||||||
var source = EmitExpression(castNode.Value, asLValue: true);
|
|
||||||
|
|
||||||
var indices = new[]
|
|
||||||
{
|
|
||||||
LLVMValueRef.CreateConstInt(LLVMTypeRef.Int32, 0),
|
|
||||||
LLVMValueRef.CreateConstInt(LLVMTypeRef.Int32, 0)
|
|
||||||
};
|
|
||||||
|
|
||||||
return _builder.BuildInBoundsGEP2(MapType(sourceArrayType), source, indices);
|
|
||||||
}
|
|
||||||
|
|
||||||
private LLVMValueRef EmitStringToCStringCast(CastNode castNode)
|
|
||||||
{
|
|
||||||
var source = EmitExpression(castNode.Value, asLValue: true);
|
|
||||||
var dataPtrPtr = _builder.BuildStructGEP2(MapType(castNode.Value.Type), source, 1);
|
|
||||||
return _builder.BuildLoad2(LLVMTypeRef.CreatePointer(LLVMTypeRef.Int8, 0), dataPtrPtr);
|
|
||||||
}
|
|
||||||
|
|
||||||
private LLVMTypeRef MapType(NubType type)
|
|
||||||
{
|
|
||||||
return type switch
|
|
||||||
{
|
|
||||||
NubBoolType => LLVMTypeRef.Int1,
|
|
||||||
NubIntType intType => LLVMTypeRef.CreateInt((uint)intType.Width),
|
|
||||||
NubFloatType floatType => floatType.Width == 32 ? LLVMTypeRef.Float : LLVMTypeRef.Double,
|
|
||||||
NubFuncType funcType => LLVMTypeRef.CreateFunction(MapType(funcType.ReturnType), funcType.Parameters.Select(MapType).ToArray()),
|
|
||||||
NubPointerType ptrType => LLVMTypeRef.CreatePointer(MapType(ptrType.BaseType), 0),
|
|
||||||
NubSliceType nubSliceType => MapSliceType(nubSliceType),
|
|
||||||
NubStringType => _structTypes["nub.string"],
|
|
||||||
NubArrayType arrType => LLVMTypeRef.CreatePointer(MapType(arrType.ElementType), 0),
|
|
||||||
NubConstArrayType constArr => LLVMTypeRef.CreateArray(MapType(constArr.ElementType), (uint)constArr.Size),
|
|
||||||
NubStructType structType => _structTypes[StructName(structType.Module, structType.Name)],
|
|
||||||
NubVoidType => LLVMTypeRef.Void,
|
|
||||||
_ => throw new ArgumentOutOfRangeException(nameof(type), type, null)
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
private LLVMTypeRef MapSliceType(NubSliceType nubSliceType)
|
|
||||||
{
|
|
||||||
var mangledName = NameMangler.Mangle(nubSliceType.ElementType);
|
|
||||||
var name = $"nub.slice.{mangledName}";
|
|
||||||
if (!_structTypes.TryGetValue(name, out var type))
|
|
||||||
{
|
|
||||||
type = _context.CreateNamedStruct(name);
|
|
||||||
type.StructSetBody([LLVMTypeRef.Int64, LLVMTypeRef.CreatePointer(MapType(nubSliceType.ElementType), 0)], false);
|
|
||||||
_structTypes[name] = type;
|
|
||||||
}
|
|
||||||
|
|
||||||
return type;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static string StructName(string module, string name)
|
|
||||||
{
|
|
||||||
return $"struct.{module}.{name}";
|
|
||||||
}
|
|
||||||
|
|
||||||
private static string StructConstructorName(string module, string name)
|
|
||||||
{
|
|
||||||
return $"{StructName(module, name)}.new";
|
|
||||||
}
|
|
||||||
|
|
||||||
private static string FuncName(string module, string name, string? externSymbol)
|
|
||||||
{
|
|
||||||
if (externSymbol != null)
|
|
||||||
{
|
|
||||||
return externSymbol;
|
|
||||||
}
|
|
||||||
|
|
||||||
return $"{module}.{name}";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,308 +0,0 @@
|
|||||||
using System.Diagnostics.CodeAnalysis;
|
|
||||||
using NubLang.Ast;
|
|
||||||
using NubLang.Diagnostics;
|
|
||||||
using NubLang.Syntax;
|
|
||||||
using NubLang.Types;
|
|
||||||
|
|
||||||
namespace NubLang.Modules;
|
|
||||||
|
|
||||||
public sealed class ModuleRepository
|
|
||||||
{
|
|
||||||
public static ModuleRepository Create(List<SyntaxTree> syntaxTrees)
|
|
||||||
{
|
|
||||||
var structTypes = new Dictionary<(string module, string name), NubStructType>();
|
|
||||||
var enumTypes = new Dictionary<(string module, string name), NubIntType>();
|
|
||||||
|
|
||||||
foreach (var syntaxTree in syntaxTrees)
|
|
||||||
{
|
|
||||||
var module = syntaxTree.TopLevelSyntaxNodes.OfType<ModuleSyntax>().FirstOrDefault();
|
|
||||||
if (module == null)
|
|
||||||
{
|
|
||||||
throw new CompileException(Diagnostic.Error("Module declaration missing").WithHelp("module \"main\"").Build());
|
|
||||||
}
|
|
||||||
|
|
||||||
foreach (var structSyntax in syntaxTree.TopLevelSyntaxNodes.OfType<StructSyntax>())
|
|
||||||
{
|
|
||||||
// note(nub31): Since not all struct types are registered yet, we cannot register field types as they might reference unregistered structs
|
|
||||||
var key = (module.NameToken.Value, structSyntax.NameToken.Value);
|
|
||||||
structTypes.Add(key, new NubStructType(module.NameToken.Value, structSyntax.NameToken.Value, structSyntax.Packed, []));
|
|
||||||
}
|
|
||||||
|
|
||||||
foreach (var enumSyntax in syntaxTree.TopLevelSyntaxNodes.OfType<EnumSyntax>())
|
|
||||||
{
|
|
||||||
NubIntType? underlyingType = null;
|
|
||||||
if (enumSyntax.Type != null)
|
|
||||||
{
|
|
||||||
if (enumSyntax.Type is not IntTypeSyntax intType)
|
|
||||||
{
|
|
||||||
throw new CompileException(Diagnostic.Error("Underlying type of enum must be an integer type").At(enumSyntax.Type).Build());
|
|
||||||
}
|
|
||||||
|
|
||||||
underlyingType = new NubIntType(intType.Signed, intType.Width);
|
|
||||||
}
|
|
||||||
|
|
||||||
underlyingType ??= new NubIntType(false, 64);
|
|
||||||
|
|
||||||
var key = (module.NameToken.Value, enumSyntax.NameToken.Value);
|
|
||||||
enumTypes.Add(key, underlyingType);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// note(nub31): Since all struct types are now registered, we can safely resolve the field types
|
|
||||||
foreach (var syntaxTree in syntaxTrees)
|
|
||||||
{
|
|
||||||
var module = syntaxTree.TopLevelSyntaxNodes.OfType<ModuleSyntax>().FirstOrDefault();
|
|
||||||
if (module == null)
|
|
||||||
{
|
|
||||||
throw new CompileException(Diagnostic.Error("Module declaration missing").WithHelp("module \"main\"").Build());
|
|
||||||
}
|
|
||||||
|
|
||||||
foreach (var structSyntax in syntaxTree.TopLevelSyntaxNodes.OfType<StructSyntax>())
|
|
||||||
{
|
|
||||||
var key = (module.NameToken.Value, structSyntax.NameToken.Value);
|
|
||||||
|
|
||||||
structTypes[key].Fields = structSyntax.Fields
|
|
||||||
.Select(x => new NubStructFieldType(x.NameToken.Value, ResolveType(x.Type, module.NameToken.Value), x.Value != null))
|
|
||||||
.ToList();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var modules = new Dictionary<string, Module>();
|
|
||||||
|
|
||||||
foreach (var syntaxTree in syntaxTrees)
|
|
||||||
{
|
|
||||||
var moduleDecl = syntaxTree.TopLevelSyntaxNodes.OfType<ModuleSyntax>().FirstOrDefault();
|
|
||||||
if (moduleDecl == null)
|
|
||||||
{
|
|
||||||
throw new CompileException(Diagnostic.Error("Module declaration missing").WithHelp("module \"main\"").Build());
|
|
||||||
}
|
|
||||||
|
|
||||||
var functionPrototypes = new List<FuncPrototypeNode>();
|
|
||||||
|
|
||||||
foreach (var funcSyntax in syntaxTree.TopLevelSyntaxNodes.OfType<FuncSyntax>())
|
|
||||||
{
|
|
||||||
var returnType = ResolveType(funcSyntax.Prototype.ReturnType, moduleDecl.NameToken.Value);
|
|
||||||
var parameters = funcSyntax.Prototype.Parameters.Select(x => new FuncParameterNode(x.Tokens, x.NameToken, ResolveType(x.Type, moduleDecl.NameToken.Value))).ToList();
|
|
||||||
functionPrototypes.Add(new FuncPrototypeNode(funcSyntax.Prototype.Tokens, funcSyntax.Prototype.NameToken, funcSyntax.Prototype.ExternSymbolToken, parameters, returnType));
|
|
||||||
}
|
|
||||||
|
|
||||||
var module = new Module
|
|
||||||
{
|
|
||||||
Name = moduleDecl.NameToken.Value,
|
|
||||||
StructTypes = structTypes.Where(x => x.Key.module == moduleDecl.NameToken.Value).Select(x => x.Value).ToList(),
|
|
||||||
EnumTypes = enumTypes
|
|
||||||
.Where(x => x.Key.module == moduleDecl.NameToken.Value)
|
|
||||||
.ToDictionary(x => x.Key.name, x => x.Value),
|
|
||||||
FunctionPrototypes = functionPrototypes
|
|
||||||
};
|
|
||||||
|
|
||||||
modules.Add(moduleDecl.NameToken.Value, module);
|
|
||||||
}
|
|
||||||
|
|
||||||
return new ModuleRepository(modules);
|
|
||||||
|
|
||||||
NubType ResolveType(TypeSyntax type, string currentModule)
|
|
||||||
{
|
|
||||||
return type switch
|
|
||||||
{
|
|
||||||
ArrayTypeSyntax arr => new NubArrayType(ResolveType(arr.BaseType, currentModule)),
|
|
||||||
BoolTypeSyntax => new NubBoolType(),
|
|
||||||
IntTypeSyntax i => new NubIntType(i.Signed, i.Width),
|
|
||||||
FloatTypeSyntax f => new NubFloatType(f.Width),
|
|
||||||
FuncTypeSyntax func => new NubFuncType(func.Parameters.Select(x => ResolveType(x, currentModule)).ToList(), ResolveType(func.ReturnType, currentModule)),
|
|
||||||
SliceTypeSyntax slice => new NubSliceType(ResolveType(slice.BaseType, currentModule)),
|
|
||||||
ConstArrayTypeSyntax arr => new NubConstArrayType(ResolveType(arr.BaseType, currentModule), arr.Size),
|
|
||||||
PointerTypeSyntax ptr => new NubPointerType(ResolveType(ptr.BaseType, currentModule)),
|
|
||||||
StringTypeSyntax => new NubStringType(),
|
|
||||||
CustomTypeSyntax c => ResolveCustomType(c, currentModule),
|
|
||||||
VoidTypeSyntax => new NubVoidType(),
|
|
||||||
_ => throw new NotSupportedException($"Unknown type syntax: {type}")
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
NubType ResolveCustomType(CustomTypeSyntax customType, string currentModule)
|
|
||||||
{
|
|
||||||
var customTypeKey = (customType.ModuleToken?.Value ?? currentModule, customType.NameToken.Value);
|
|
||||||
|
|
||||||
var resolvedStructType = structTypes.GetValueOrDefault(customTypeKey);
|
|
||||||
if (resolvedStructType != null)
|
|
||||||
{
|
|
||||||
return resolvedStructType;
|
|
||||||
}
|
|
||||||
|
|
||||||
var resolvedEnumType = enumTypes.GetValueOrDefault(customTypeKey);
|
|
||||||
if (resolvedEnumType != null)
|
|
||||||
{
|
|
||||||
return resolvedEnumType;
|
|
||||||
}
|
|
||||||
|
|
||||||
throw new CompileException(Diagnostic
|
|
||||||
.Error($"Type {customType.NameToken.Value} not found in module {customType.ModuleToken?.Value ?? currentModule}")
|
|
||||||
.At(customType)
|
|
||||||
.Build());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public ModuleRepository(Dictionary<string, Module> modules)
|
|
||||||
{
|
|
||||||
_modules = modules;
|
|
||||||
}
|
|
||||||
|
|
||||||
private readonly Dictionary<string, Module> _modules;
|
|
||||||
|
|
||||||
public Module Get(IdentifierToken ident)
|
|
||||||
{
|
|
||||||
var module = _modules.GetValueOrDefault(ident.Value);
|
|
||||||
if (module == null)
|
|
||||||
{
|
|
||||||
throw new CompileException(Diagnostic.Error($"Module {ident.Value} was not found").At(ident).Build());
|
|
||||||
}
|
|
||||||
|
|
||||||
return module;
|
|
||||||
}
|
|
||||||
|
|
||||||
public bool TryGet(IdentifierToken ident, [NotNullWhen(true)] out Module? module)
|
|
||||||
{
|
|
||||||
module = _modules.GetValueOrDefault(ident.Value);
|
|
||||||
return module != null;
|
|
||||||
}
|
|
||||||
|
|
||||||
public bool TryGet(string name, [NotNullWhen(true)] out Module? module)
|
|
||||||
{
|
|
||||||
module = _modules.GetValueOrDefault(name);
|
|
||||||
return module != null;
|
|
||||||
}
|
|
||||||
|
|
||||||
public List<Module> GetAll()
|
|
||||||
{
|
|
||||||
return _modules.Values.ToList();
|
|
||||||
}
|
|
||||||
|
|
||||||
public sealed class Module
|
|
||||||
{
|
|
||||||
public required string Name { get; init; }
|
|
||||||
public required List<FuncPrototypeNode> FunctionPrototypes { get; init; } = [];
|
|
||||||
public required List<NubStructType> StructTypes { get; init; } = [];
|
|
||||||
public required Dictionary<string, NubIntType> EnumTypes { get; init; } = [];
|
|
||||||
|
|
||||||
public bool TryResolveFunc(string name, [NotNullWhen(true)] out FuncPrototypeNode? value, [NotNullWhen(false)] out Diagnostic? diagnostic)
|
|
||||||
{
|
|
||||||
value = FunctionPrototypes.FirstOrDefault(x => x.NameToken.Value == name);
|
|
||||||
|
|
||||||
if (value == null)
|
|
||||||
{
|
|
||||||
value = null;
|
|
||||||
diagnostic = Diagnostic.Error($"Func {name} not found in module {Name}").Build();
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
diagnostic = null;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
public bool TryResolveFunc(IdentifierToken name, [NotNullWhen(true)] out FuncPrototypeNode? value, [NotNullWhen(false)] out Diagnostic? diagnostic)
|
|
||||||
{
|
|
||||||
value = FunctionPrototypes.FirstOrDefault(x => x.NameToken.Value == name.Value);
|
|
||||||
|
|
||||||
if (value == null)
|
|
||||||
{
|
|
||||||
value = null;
|
|
||||||
diagnostic = Diagnostic.Error($"Func {name.Value} not found in module {Name}").At(name).Build();
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
diagnostic = null;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
public FuncPrototypeNode ResolveFunc(IdentifierToken name)
|
|
||||||
{
|
|
||||||
if (!TryResolveFunc(name, out var value, out var diagnostic))
|
|
||||||
{
|
|
||||||
throw new CompileException(diagnostic);
|
|
||||||
}
|
|
||||||
|
|
||||||
return value;
|
|
||||||
}
|
|
||||||
|
|
||||||
public bool TryResolveStruct(string name, [NotNullWhen(true)] out NubStructType? value, [NotNullWhen(false)] out Diagnostic? diagnostic)
|
|
||||||
{
|
|
||||||
value = StructTypes.FirstOrDefault(x => x.Name == name);
|
|
||||||
|
|
||||||
if (value == null)
|
|
||||||
{
|
|
||||||
value = null;
|
|
||||||
diagnostic = Diagnostic.Error($"Struct {name} not found in module {Name}").Build();
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
diagnostic = null;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
public bool TryResolveStruct(IdentifierToken name, [NotNullWhen(true)] out NubStructType? value, [NotNullWhen(false)] out Diagnostic? diagnostic)
|
|
||||||
{
|
|
||||||
value = StructTypes.FirstOrDefault(x => x.Name == name.Value);
|
|
||||||
|
|
||||||
if (value == null)
|
|
||||||
{
|
|
||||||
value = null;
|
|
||||||
diagnostic = Diagnostic.Error($"Struct {name.Value} not found in module {Name}").At(name).Build();
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
diagnostic = null;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
public NubStructType ResolveStruct(IdentifierToken name)
|
|
||||||
{
|
|
||||||
if (!TryResolveStruct(name, out var value, out var diagnostic))
|
|
||||||
{
|
|
||||||
throw new CompileException(diagnostic);
|
|
||||||
}
|
|
||||||
|
|
||||||
return value;
|
|
||||||
}
|
|
||||||
|
|
||||||
public bool TryResolveEnum(string name, [NotNullWhen(true)] out NubIntType? value, [NotNullWhen(false)] out Diagnostic? diagnostic)
|
|
||||||
{
|
|
||||||
value = EnumTypes.GetValueOrDefault(name);
|
|
||||||
|
|
||||||
if (value == null)
|
|
||||||
{
|
|
||||||
value = null;
|
|
||||||
diagnostic = Diagnostic.Error($"Enum {name} not found in module {Name}").Build();
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
diagnostic = null;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
public bool TryResolveEnum(IdentifierToken name, [NotNullWhen(true)] out NubIntType? value, [NotNullWhen(false)] out Diagnostic? diagnostic)
|
|
||||||
{
|
|
||||||
value = EnumTypes.GetValueOrDefault(name.Value);
|
|
||||||
|
|
||||||
if (value == null)
|
|
||||||
{
|
|
||||||
value = null;
|
|
||||||
diagnostic = Diagnostic.Error($"Enum {name.Value} not found in module {Name}").At(name).Build();
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
diagnostic = null;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
public NubIntType ResolveEnum(IdentifierToken name)
|
|
||||||
{
|
|
||||||
if (!TryResolveEnum(name, out var value, out var diagnostic))
|
|
||||||
{
|
|
||||||
throw new CompileException(diagnostic);
|
|
||||||
}
|
|
||||||
|
|
||||||
return value;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,956 +0,0 @@
|
|||||||
using System.Diagnostics.CodeAnalysis;
|
|
||||||
using NubLang.Diagnostics;
|
|
||||||
|
|
||||||
namespace NubLang.Syntax;
|
|
||||||
|
|
||||||
public sealed class Parser
|
|
||||||
{
|
|
||||||
private List<Token> _tokens = [];
|
|
||||||
private int _tokenIndex;
|
|
||||||
|
|
||||||
private Token? CurrentToken => _tokenIndex < _tokens.Count ? _tokens[_tokenIndex] : null;
|
|
||||||
|
|
||||||
private bool HasTrailingWhitespace(Token token)
|
|
||||||
{
|
|
||||||
var index = _tokens.IndexOf(token);
|
|
||||||
return index + 1 < _tokens.Count && _tokens[index + 1] is WhitespaceToken or CommentToken;
|
|
||||||
}
|
|
||||||
|
|
||||||
private bool HasLeadingWhitespace(Token token)
|
|
||||||
{
|
|
||||||
var index = _tokens.IndexOf(token);
|
|
||||||
return index - 1 < _tokens.Count && _tokens[index - 1] is WhitespaceToken or CommentToken;
|
|
||||||
}
|
|
||||||
|
|
||||||
private bool HasToken => CurrentToken != null;
|
|
||||||
|
|
||||||
public List<Diagnostic> Diagnostics { get; set; } = [];
|
|
||||||
|
|
||||||
public SyntaxTree Parse(List<Token> tokens)
|
|
||||||
{
|
|
||||||
_tokens = tokens;
|
|
||||||
_tokenIndex = 0;
|
|
||||||
|
|
||||||
Diagnostics = [];
|
|
||||||
|
|
||||||
var topLevelSyntaxNodes = new List<TopLevelSyntaxNode>();
|
|
||||||
|
|
||||||
while (HasToken)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
var startIndex = _tokenIndex;
|
|
||||||
|
|
||||||
var exported = TryExpectSymbol(Symbol.Export);
|
|
||||||
var packed = TryExpectSymbol(Symbol.Packed);
|
|
||||||
|
|
||||||
if (TryExpectSymbol(Symbol.Extern))
|
|
||||||
{
|
|
||||||
var externSymbol = ExpectStringLiteral();
|
|
||||||
ExpectSymbol(Symbol.Func);
|
|
||||||
topLevelSyntaxNodes.Add(ParseFunc(startIndex, exported, externSymbol));
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
var keyword = ExpectSymbol();
|
|
||||||
TopLevelSyntaxNode definition = keyword.Symbol switch
|
|
||||||
{
|
|
||||||
Symbol.Module => ParseModule(startIndex),
|
|
||||||
Symbol.Func => ParseFunc(startIndex, exported, null),
|
|
||||||
Symbol.Struct => ParseStruct(startIndex, exported, packed),
|
|
||||||
Symbol.Enum => ParseEnum(startIndex, exported),
|
|
||||||
_ => throw new CompileException(Diagnostic
|
|
||||||
.Error($"Expected 'func', 'struct', 'enum', 'import' or 'module' but found '{keyword.Symbol}'")
|
|
||||||
.WithHelp("Valid top level statements are 'func', 'struct', 'enum', 'import' and 'module'")
|
|
||||||
.At(keyword, _tokens)
|
|
||||||
.Build())
|
|
||||||
};
|
|
||||||
|
|
||||||
topLevelSyntaxNodes.Add(definition);
|
|
||||||
}
|
|
||||||
catch (CompileException e)
|
|
||||||
{
|
|
||||||
Diagnostics.Add(e.Diagnostic);
|
|
||||||
while (HasToken)
|
|
||||||
{
|
|
||||||
if (CurrentToken is SymbolToken { Symbol: Symbol.Extern or Symbol.Func or Symbol.Struct })
|
|
||||||
{
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
Next();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return new SyntaxTree(topLevelSyntaxNodes, _tokens);
|
|
||||||
}
|
|
||||||
|
|
||||||
private ModuleSyntax ParseModule(int startIndex)
|
|
||||||
{
|
|
||||||
var name = ExpectIdentifier();
|
|
||||||
return new ModuleSyntax(GetTokens(startIndex), name);
|
|
||||||
}
|
|
||||||
|
|
||||||
private FuncParameterSyntax ParseFuncParameter()
|
|
||||||
{
|
|
||||||
var startIndex = _tokenIndex;
|
|
||||||
var name = ExpectIdentifier();
|
|
||||||
ExpectSymbol(Symbol.Colon);
|
|
||||||
var type = ParseType();
|
|
||||||
|
|
||||||
return new FuncParameterSyntax(GetTokens(startIndex), name, type);
|
|
||||||
}
|
|
||||||
|
|
||||||
private FuncSyntax ParseFunc(int startIndex, bool exported, StringLiteralToken? externSymbol)
|
|
||||||
{
|
|
||||||
var name = ExpectIdentifier();
|
|
||||||
List<FuncParameterSyntax> parameters = [];
|
|
||||||
|
|
||||||
ExpectSymbol(Symbol.OpenParen);
|
|
||||||
|
|
||||||
while (!TryExpectSymbol(Symbol.CloseParen))
|
|
||||||
{
|
|
||||||
parameters.Add(ParseFuncParameter());
|
|
||||||
|
|
||||||
if (!TryExpectSymbol(Symbol.Comma))
|
|
||||||
{
|
|
||||||
ExpectSymbol(Symbol.CloseParen);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var returnType = TryExpectSymbol(Symbol.Colon) ? ParseType() : new VoidTypeSyntax([]);
|
|
||||||
|
|
||||||
var prototype = new FuncPrototypeSyntax(GetTokens(startIndex), name, exported, externSymbol, parameters, returnType);
|
|
||||||
|
|
||||||
BlockSyntax? body = null;
|
|
||||||
var bodyStartIndex = _tokenIndex;
|
|
||||||
if (TryExpectSymbol(Symbol.OpenBrace))
|
|
||||||
{
|
|
||||||
body = ParseBlock(bodyStartIndex);
|
|
||||||
}
|
|
||||||
|
|
||||||
return new FuncSyntax(GetTokens(startIndex), prototype, body);
|
|
||||||
}
|
|
||||||
|
|
||||||
private StructSyntax ParseStruct(int startIndex, bool exported, bool packed)
|
|
||||||
{
|
|
||||||
var name = ExpectIdentifier();
|
|
||||||
|
|
||||||
ExpectSymbol(Symbol.OpenBrace);
|
|
||||||
|
|
||||||
List<StructFieldSyntax> fields = [];
|
|
||||||
|
|
||||||
while (!TryExpectSymbol(Symbol.CloseBrace))
|
|
||||||
{
|
|
||||||
var memberStartIndex = _tokenIndex;
|
|
||||||
|
|
||||||
var fieldName = ExpectIdentifier();
|
|
||||||
ExpectSymbol(Symbol.Colon);
|
|
||||||
var fieldType = ParseType();
|
|
||||||
|
|
||||||
ExpressionSyntax? fieldValue = null;
|
|
||||||
|
|
||||||
if (TryExpectSymbol(Symbol.Assign))
|
|
||||||
{
|
|
||||||
fieldValue = ParseExpression();
|
|
||||||
}
|
|
||||||
|
|
||||||
fields.Add(new StructFieldSyntax(GetTokens(memberStartIndex), fieldName, fieldType, fieldValue));
|
|
||||||
}
|
|
||||||
|
|
||||||
return new StructSyntax(GetTokens(startIndex), name, exported, packed, fields);
|
|
||||||
}
|
|
||||||
|
|
||||||
private EnumSyntax ParseEnum(int startIndex, bool exported)
|
|
||||||
{
|
|
||||||
var name = ExpectIdentifier();
|
|
||||||
|
|
||||||
TypeSyntax? type = null;
|
|
||||||
|
|
||||||
if (TryExpectSymbol(Symbol.Colon))
|
|
||||||
{
|
|
||||||
type = ParseType();
|
|
||||||
}
|
|
||||||
|
|
||||||
List<EnumFieldSyntax> fields = [];
|
|
||||||
|
|
||||||
ExpectSymbol(Symbol.OpenBrace);
|
|
||||||
|
|
||||||
while (!TryExpectSymbol(Symbol.CloseBrace))
|
|
||||||
{
|
|
||||||
var memberStartIndex = _tokenIndex;
|
|
||||||
var fieldName = ExpectIdentifier();
|
|
||||||
IntLiteralToken? value = null;
|
|
||||||
|
|
||||||
if (TryExpectSymbol(Symbol.Assign))
|
|
||||||
{
|
|
||||||
if (!TryExpectIntLiteral(out var intLiteralToken))
|
|
||||||
{
|
|
||||||
throw new CompileException(Diagnostic
|
|
||||||
.Error("Value of enum field must be an integer literal")
|
|
||||||
.At(CurrentToken, _tokens)
|
|
||||||
.Build());
|
|
||||||
}
|
|
||||||
|
|
||||||
value = intLiteralToken;
|
|
||||||
}
|
|
||||||
|
|
||||||
fields.Add(new EnumFieldSyntax(GetTokens(memberStartIndex), fieldName, value));
|
|
||||||
}
|
|
||||||
|
|
||||||
return new EnumSyntax(GetTokens(startIndex), name, exported, type, fields);
|
|
||||||
}
|
|
||||||
|
|
||||||
private StatementSyntax ParseStatement()
|
|
||||||
{
|
|
||||||
var startIndex = _tokenIndex;
|
|
||||||
|
|
||||||
if (CurrentToken is SymbolToken symbolToken)
|
|
||||||
{
|
|
||||||
switch (symbolToken.Symbol)
|
|
||||||
{
|
|
||||||
case Symbol.OpenBrace:
|
|
||||||
Next();
|
|
||||||
return ParseBlock(startIndex);
|
|
||||||
case Symbol.Return:
|
|
||||||
Next();
|
|
||||||
return ParseReturn(startIndex);
|
|
||||||
case Symbol.If:
|
|
||||||
Next();
|
|
||||||
return ParseIf(startIndex);
|
|
||||||
case Symbol.While:
|
|
||||||
Next();
|
|
||||||
return ParseWhile(startIndex);
|
|
||||||
case Symbol.For:
|
|
||||||
Next();
|
|
||||||
return ParseFor(startIndex);
|
|
||||||
case Symbol.Let:
|
|
||||||
Next();
|
|
||||||
return ParseVariableDeclaration(startIndex);
|
|
||||||
case Symbol.Defer:
|
|
||||||
Next();
|
|
||||||
return ParseDefer(startIndex);
|
|
||||||
case Symbol.Break:
|
|
||||||
Next();
|
|
||||||
return new BreakSyntax(GetTokens(startIndex));
|
|
||||||
case Symbol.Continue:
|
|
||||||
Next();
|
|
||||||
return new ContinueSyntax(GetTokens(startIndex));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var expr = ParseExpression();
|
|
||||||
|
|
||||||
if (TryExpectSymbol(Symbol.Assign))
|
|
||||||
{
|
|
||||||
var value = ParseExpression();
|
|
||||||
return new AssignmentSyntax(GetTokens(startIndex), expr, value);
|
|
||||||
}
|
|
||||||
|
|
||||||
return new StatementExpressionSyntax(GetTokens(startIndex), expr);
|
|
||||||
}
|
|
||||||
|
|
||||||
private VariableDeclarationSyntax ParseVariableDeclaration(int startIndex)
|
|
||||||
{
|
|
||||||
var name = ExpectIdentifier();
|
|
||||||
|
|
||||||
TypeSyntax? explicitType = null;
|
|
||||||
if (TryExpectSymbol(Symbol.Colon))
|
|
||||||
{
|
|
||||||
explicitType = ParseType();
|
|
||||||
}
|
|
||||||
|
|
||||||
ExpressionSyntax? assignment = null;
|
|
||||||
if (TryExpectSymbol(Symbol.Assign))
|
|
||||||
{
|
|
||||||
assignment = ParseExpression();
|
|
||||||
}
|
|
||||||
|
|
||||||
return new VariableDeclarationSyntax(GetTokens(startIndex), name, explicitType, assignment);
|
|
||||||
}
|
|
||||||
|
|
||||||
private DeferSyntax ParseDefer(int startIndex)
|
|
||||||
{
|
|
||||||
var statement = ParseStatement();
|
|
||||||
return new DeferSyntax(GetTokens(startIndex), statement);
|
|
||||||
}
|
|
||||||
|
|
||||||
private ReturnSyntax ParseReturn(int startIndex)
|
|
||||||
{
|
|
||||||
ExpressionSyntax? value = null;
|
|
||||||
|
|
||||||
if (!TryExpectSymbol(Symbol.Semi))
|
|
||||||
{
|
|
||||||
value = ParseExpression();
|
|
||||||
}
|
|
||||||
|
|
||||||
return new ReturnSyntax(GetTokens(startIndex), value);
|
|
||||||
}
|
|
||||||
|
|
||||||
private IfSyntax ParseIf(int startIndex)
|
|
||||||
{
|
|
||||||
var condition = ParseExpression();
|
|
||||||
var body = ParseBlock();
|
|
||||||
|
|
||||||
Variant<IfSyntax, BlockSyntax>? elseStatement = null;
|
|
||||||
|
|
||||||
var elseStartIndex = _tokenIndex;
|
|
||||||
if (TryExpectSymbol(Symbol.Else))
|
|
||||||
{
|
|
||||||
if (TryExpectSymbol(Symbol.If))
|
|
||||||
{
|
|
||||||
elseStatement = (Variant<IfSyntax, BlockSyntax>)ParseIf(elseStartIndex);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
elseStatement = (Variant<IfSyntax, BlockSyntax>)ParseBlock();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return new IfSyntax(GetTokens(startIndex), condition, body, elseStatement);
|
|
||||||
}
|
|
||||||
|
|
||||||
private WhileSyntax ParseWhile(int startIndex)
|
|
||||||
{
|
|
||||||
var condition = ParseExpression();
|
|
||||||
var body = ParseBlock();
|
|
||||||
return new WhileSyntax(GetTokens(startIndex), condition, body);
|
|
||||||
}
|
|
||||||
|
|
||||||
private ForSyntax ParseFor(int startIndex)
|
|
||||||
{
|
|
||||||
var itemName = ExpectIdentifier();
|
|
||||||
IdentifierToken? indexName = null;
|
|
||||||
|
|
||||||
if (TryExpectSymbol(Symbol.Comma))
|
|
||||||
{
|
|
||||||
indexName = ExpectIdentifier();
|
|
||||||
}
|
|
||||||
|
|
||||||
ExpectSymbol(Symbol.In);
|
|
||||||
var target = ParseExpression();
|
|
||||||
var body = ParseBlock();
|
|
||||||
|
|
||||||
return new ForSyntax(GetTokens(startIndex), itemName, indexName, target, body);
|
|
||||||
}
|
|
||||||
|
|
||||||
private ExpressionSyntax ParseExpression(int precedence = 0)
|
|
||||||
{
|
|
||||||
var startIndex = _tokenIndex;
|
|
||||||
var left = ParsePrimaryExpression();
|
|
||||||
|
|
||||||
while (CurrentToken is SymbolToken symbolToken && HasLeadingWhitespace(symbolToken) && HasTrailingWhitespace(symbolToken) && TryGetBinaryOperator(symbolToken.Symbol, out var op) && GetBinaryOperatorPrecedence(op.Value) >= precedence)
|
|
||||||
{
|
|
||||||
Next();
|
|
||||||
var right = ParseExpression(GetBinaryOperatorPrecedence(op.Value) + 1);
|
|
||||||
left = new BinaryExpressionSyntax(GetTokens(startIndex), left, op.Value, right);
|
|
||||||
}
|
|
||||||
|
|
||||||
return left;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static int GetBinaryOperatorPrecedence(BinaryOperatorSyntax operatorSyntax)
|
|
||||||
{
|
|
||||||
return operatorSyntax switch
|
|
||||||
{
|
|
||||||
BinaryOperatorSyntax.Multiply => 10,
|
|
||||||
BinaryOperatorSyntax.Divide => 10,
|
|
||||||
BinaryOperatorSyntax.Modulo => 10,
|
|
||||||
|
|
||||||
BinaryOperatorSyntax.Plus => 9,
|
|
||||||
BinaryOperatorSyntax.Minus => 9,
|
|
||||||
|
|
||||||
BinaryOperatorSyntax.LeftShift => 8,
|
|
||||||
BinaryOperatorSyntax.RightShift => 8,
|
|
||||||
|
|
||||||
BinaryOperatorSyntax.GreaterThan => 7,
|
|
||||||
BinaryOperatorSyntax.GreaterThanOrEqual => 7,
|
|
||||||
BinaryOperatorSyntax.LessThan => 7,
|
|
||||||
BinaryOperatorSyntax.LessThanOrEqual => 7,
|
|
||||||
|
|
||||||
BinaryOperatorSyntax.Equal => 7,
|
|
||||||
BinaryOperatorSyntax.NotEqual => 7,
|
|
||||||
|
|
||||||
BinaryOperatorSyntax.BitwiseAnd => 6,
|
|
||||||
BinaryOperatorSyntax.BitwiseXor => 5,
|
|
||||||
BinaryOperatorSyntax.BitwiseOr => 4,
|
|
||||||
|
|
||||||
BinaryOperatorSyntax.LogicalAnd => 3,
|
|
||||||
BinaryOperatorSyntax.LogicalOr => 2,
|
|
||||||
|
|
||||||
_ => throw new ArgumentOutOfRangeException(nameof(operatorSyntax), operatorSyntax, null)
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
private bool TryGetBinaryOperator(Symbol symbol, [NotNullWhen(true)] out BinaryOperatorSyntax? binaryExpressionOperator)
|
|
||||||
{
|
|
||||||
switch (symbol)
|
|
||||||
{
|
|
||||||
case Symbol.Equal:
|
|
||||||
binaryExpressionOperator = BinaryOperatorSyntax.Equal;
|
|
||||||
return true;
|
|
||||||
case Symbol.NotEqual:
|
|
||||||
binaryExpressionOperator = BinaryOperatorSyntax.NotEqual;
|
|
||||||
return true;
|
|
||||||
case Symbol.LessThan:
|
|
||||||
binaryExpressionOperator = BinaryOperatorSyntax.LessThan;
|
|
||||||
return true;
|
|
||||||
case Symbol.LessThanOrEqual:
|
|
||||||
binaryExpressionOperator = BinaryOperatorSyntax.LessThanOrEqual;
|
|
||||||
return true;
|
|
||||||
case Symbol.GreaterThan:
|
|
||||||
binaryExpressionOperator = BinaryOperatorSyntax.GreaterThan;
|
|
||||||
return true;
|
|
||||||
case Symbol.GreaterThanOrEqual:
|
|
||||||
binaryExpressionOperator = BinaryOperatorSyntax.GreaterThanOrEqual;
|
|
||||||
return true;
|
|
||||||
case Symbol.And:
|
|
||||||
binaryExpressionOperator = BinaryOperatorSyntax.LogicalAnd;
|
|
||||||
return true;
|
|
||||||
case Symbol.Or:
|
|
||||||
binaryExpressionOperator = BinaryOperatorSyntax.LogicalOr;
|
|
||||||
return true;
|
|
||||||
case Symbol.Plus:
|
|
||||||
binaryExpressionOperator = BinaryOperatorSyntax.Plus;
|
|
||||||
return true;
|
|
||||||
case Symbol.Minus:
|
|
||||||
binaryExpressionOperator = BinaryOperatorSyntax.Minus;
|
|
||||||
return true;
|
|
||||||
case Symbol.Star:
|
|
||||||
binaryExpressionOperator = BinaryOperatorSyntax.Multiply;
|
|
||||||
return true;
|
|
||||||
case Symbol.ForwardSlash:
|
|
||||||
binaryExpressionOperator = BinaryOperatorSyntax.Divide;
|
|
||||||
return true;
|
|
||||||
case Symbol.Percent:
|
|
||||||
binaryExpressionOperator = BinaryOperatorSyntax.Modulo;
|
|
||||||
return true;
|
|
||||||
case Symbol.LeftShift:
|
|
||||||
binaryExpressionOperator = BinaryOperatorSyntax.LeftShift;
|
|
||||||
return true;
|
|
||||||
case Symbol.RightShift:
|
|
||||||
binaryExpressionOperator = BinaryOperatorSyntax.RightShift;
|
|
||||||
return true;
|
|
||||||
case Symbol.Ampersand:
|
|
||||||
binaryExpressionOperator = BinaryOperatorSyntax.BitwiseAnd;
|
|
||||||
return true;
|
|
||||||
case Symbol.Pipe:
|
|
||||||
binaryExpressionOperator = BinaryOperatorSyntax.BitwiseOr;
|
|
||||||
return true;
|
|
||||||
case Symbol.Tilde:
|
|
||||||
binaryExpressionOperator = BinaryOperatorSyntax.BitwiseXor;
|
|
||||||
return true;
|
|
||||||
default:
|
|
||||||
binaryExpressionOperator = null;
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private ExpressionSyntax ParsePrimaryExpression()
|
|
||||||
{
|
|
||||||
var startIndex = _tokenIndex;
|
|
||||||
var token = ExpectToken();
|
|
||||||
var expr = token switch
|
|
||||||
{
|
|
||||||
BoolLiteralToken boolLiteral => new BoolLiteralSyntax(GetTokens(startIndex), boolLiteral),
|
|
||||||
StringLiteralToken stringLiteral => new StringLiteralSyntax(GetTokens(startIndex), stringLiteral),
|
|
||||||
FloatLiteralToken floatLiteral => new FloatLiteralSyntax(GetTokens(startIndex), floatLiteral),
|
|
||||||
IntLiteralToken intLiteral => new IntLiteralSyntax(GetTokens(startIndex), intLiteral),
|
|
||||||
IdentifierToken identifier => ParseIdentifier(startIndex, identifier),
|
|
||||||
SymbolToken symbolToken => symbolToken.Symbol switch
|
|
||||||
{
|
|
||||||
Symbol.Caret => ParseAddressOf(startIndex),
|
|
||||||
Symbol.OpenParen => ParseParenthesizedExpression(),
|
|
||||||
Symbol.Minus => ParseUnaryNegate(startIndex),
|
|
||||||
Symbol.Bang => ParseUnaryInvert(startIndex),
|
|
||||||
Symbol.OpenBracket => ParseArrayInitializer(startIndex),
|
|
||||||
Symbol.OpenBrace => ParseUnnamedStructInitializer(startIndex),
|
|
||||||
Symbol.Struct => ParseStructInitializer(startIndex),
|
|
||||||
Symbol.At => ParseBuiltinFunction(startIndex),
|
|
||||||
_ => throw new CompileException(Diagnostic
|
|
||||||
.Error($"Unexpected symbol '{symbolToken.Symbol}' in expression")
|
|
||||||
.WithHelp("Expected '(', '-', '!', '[' or '{'")
|
|
||||||
.At(symbolToken, _tokens)
|
|
||||||
.Build())
|
|
||||||
},
|
|
||||||
_ => throw new CompileException(Diagnostic
|
|
||||||
.Error($"Unexpected token '{token.GetType().Name}' in expression")
|
|
||||||
.WithHelp("Expected literal, identifier, or parenthesized expression")
|
|
||||||
.At(token, _tokens)
|
|
||||||
.Build())
|
|
||||||
};
|
|
||||||
|
|
||||||
return ParsePostfixOperators(expr);
|
|
||||||
}
|
|
||||||
|
|
||||||
private ExpressionSyntax ParsePostfixOperators(ExpressionSyntax expr)
|
|
||||||
{
|
|
||||||
if (CurrentToken == null || HasLeadingWhitespace(CurrentToken))
|
|
||||||
{
|
|
||||||
return expr;
|
|
||||||
}
|
|
||||||
|
|
||||||
var startIndex = _tokenIndex;
|
|
||||||
while (HasToken)
|
|
||||||
{
|
|
||||||
if (TryExpectSymbol(Symbol.Caret))
|
|
||||||
{
|
|
||||||
expr = new DereferenceSyntax(GetTokens(startIndex), expr);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (TryExpectSymbol(Symbol.Period))
|
|
||||||
{
|
|
||||||
var member = ExpectIdentifier();
|
|
||||||
expr = new MemberAccessSyntax(GetTokens(startIndex), expr, member);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (TryExpectSymbol(Symbol.OpenBracket))
|
|
||||||
{
|
|
||||||
var index = ParseExpression();
|
|
||||||
ExpectSymbol(Symbol.CloseBracket);
|
|
||||||
expr = new ArrayIndexAccessSyntax(GetTokens(startIndex), expr, index);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (TryExpectSymbol(Symbol.OpenParen))
|
|
||||||
{
|
|
||||||
var parameters = new List<ExpressionSyntax>();
|
|
||||||
|
|
||||||
while (!TryExpectSymbol(Symbol.CloseParen))
|
|
||||||
{
|
|
||||||
parameters.Add(ParseExpression());
|
|
||||||
if (!TryExpectSymbol(Symbol.Comma))
|
|
||||||
{
|
|
||||||
ExpectSymbol(Symbol.CloseParen);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
expr = new FuncCallSyntax(GetTokens(startIndex), expr, parameters);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
return expr;
|
|
||||||
}
|
|
||||||
|
|
||||||
private ExpressionSyntax ParseParenthesizedExpression()
|
|
||||||
{
|
|
||||||
var expression = ParseExpression();
|
|
||||||
ExpectSymbol(Symbol.CloseParen);
|
|
||||||
return expression;
|
|
||||||
}
|
|
||||||
|
|
||||||
private AddressOfSyntax ParseAddressOf(int startIndex)
|
|
||||||
{
|
|
||||||
var expression = ParsePrimaryExpression();
|
|
||||||
return new AddressOfSyntax(GetTokens(startIndex), expression);
|
|
||||||
}
|
|
||||||
|
|
||||||
private UnaryExpressionSyntax ParseUnaryInvert(int startIndex)
|
|
||||||
{
|
|
||||||
var expression = ParsePrimaryExpression();
|
|
||||||
return new UnaryExpressionSyntax(GetTokens(startIndex), UnaryOperatorSyntax.Invert, expression);
|
|
||||||
}
|
|
||||||
|
|
||||||
private UnaryExpressionSyntax ParseUnaryNegate(int startIndex)
|
|
||||||
{
|
|
||||||
var expression = ParsePrimaryExpression();
|
|
||||||
return new UnaryExpressionSyntax(GetTokens(startIndex), UnaryOperatorSyntax.Negate, expression);
|
|
||||||
}
|
|
||||||
|
|
||||||
private ExpressionSyntax ParseBuiltinFunction(int startIndex)
|
|
||||||
{
|
|
||||||
var name = ExpectIdentifier();
|
|
||||||
ExpectSymbol(Symbol.OpenParen);
|
|
||||||
|
|
||||||
switch (name.Value)
|
|
||||||
{
|
|
||||||
case "size":
|
|
||||||
{
|
|
||||||
var type = ParseType();
|
|
||||||
ExpectSymbol(Symbol.CloseParen);
|
|
||||||
return new SizeSyntax(GetTokens(startIndex), type);
|
|
||||||
}
|
|
||||||
case "cast":
|
|
||||||
{
|
|
||||||
var expression = ParseExpression();
|
|
||||||
ExpectSymbol(Symbol.CloseParen);
|
|
||||||
return new CastSyntax(GetTokens(startIndex), expression);
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
{
|
|
||||||
throw new CompileException(Diagnostic.Error($"Unknown builtin {name.Value}").At(name).Build());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private ExpressionSyntax ParseIdentifier(int startIndex, IdentifierToken identifier)
|
|
||||||
{
|
|
||||||
if (TryExpectSymbol(Symbol.DoubleColon))
|
|
||||||
{
|
|
||||||
var name = ExpectIdentifier();
|
|
||||||
return new ModuleIdentifierSyntax(GetTokens(startIndex), identifier, name);
|
|
||||||
}
|
|
||||||
|
|
||||||
return new LocalIdentifierSyntax(GetTokens(startIndex), identifier);
|
|
||||||
}
|
|
||||||
|
|
||||||
private ExpressionSyntax ParseArrayInitializer(int startIndex)
|
|
||||||
{
|
|
||||||
var values = new List<ExpressionSyntax>();
|
|
||||||
while (!TryExpectSymbol(Symbol.CloseBracket))
|
|
||||||
{
|
|
||||||
values.Add(ParseExpression());
|
|
||||||
if (!TryExpectSymbol(Symbol.Comma))
|
|
||||||
{
|
|
||||||
ExpectSymbol(Symbol.CloseBracket);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return new ArrayInitializerSyntax(GetTokens(startIndex), values);
|
|
||||||
}
|
|
||||||
|
|
||||||
private StructInitializerSyntax ParseStructInitializer(int startIndex)
|
|
||||||
{
|
|
||||||
TypeSyntax? type = null;
|
|
||||||
if (!TryExpectSymbol(Symbol.OpenBrace))
|
|
||||||
{
|
|
||||||
type = ParseType();
|
|
||||||
ExpectSymbol(Symbol.OpenBrace);
|
|
||||||
}
|
|
||||||
|
|
||||||
var initializers = ParseStructInitializerBody();
|
|
||||||
|
|
||||||
return new StructInitializerSyntax(GetTokens(startIndex), type, initializers);
|
|
||||||
}
|
|
||||||
|
|
||||||
private StructInitializerSyntax ParseUnnamedStructInitializer(int startIndex)
|
|
||||||
{
|
|
||||||
var body = ParseStructInitializerBody();
|
|
||||||
return new StructInitializerSyntax(GetTokens(startIndex), null, body);
|
|
||||||
}
|
|
||||||
|
|
||||||
private Dictionary<IdentifierToken, ExpressionSyntax> ParseStructInitializerBody()
|
|
||||||
{
|
|
||||||
Dictionary<IdentifierToken, ExpressionSyntax> initializers = [];
|
|
||||||
while (!TryExpectSymbol(Symbol.CloseBrace))
|
|
||||||
{
|
|
||||||
var name = ExpectIdentifier();
|
|
||||||
ExpectSymbol(Symbol.Assign);
|
|
||||||
var value = ParseExpression();
|
|
||||||
initializers.Add(name, value);
|
|
||||||
}
|
|
||||||
|
|
||||||
return initializers;
|
|
||||||
}
|
|
||||||
|
|
||||||
private BlockSyntax ParseBlock()
|
|
||||||
{
|
|
||||||
var startIndex = _tokenIndex;
|
|
||||||
ExpectSymbol(Symbol.OpenBrace);
|
|
||||||
return ParseBlock(startIndex);
|
|
||||||
}
|
|
||||||
|
|
||||||
private BlockSyntax ParseBlock(int startIndex)
|
|
||||||
{
|
|
||||||
List<StatementSyntax> statements = [];
|
|
||||||
|
|
||||||
while (!TryExpectSymbol(Symbol.CloseBrace))
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
statements.Add(ParseStatement());
|
|
||||||
}
|
|
||||||
catch (CompileException ex)
|
|
||||||
{
|
|
||||||
Diagnostics.Add(ex.Diagnostic);
|
|
||||||
if (HasToken)
|
|
||||||
{
|
|
||||||
Next();
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return new BlockSyntax(GetTokens(startIndex), statements);
|
|
||||||
}
|
|
||||||
|
|
||||||
private TypeSyntax ParseType()
|
|
||||||
{
|
|
||||||
var startIndex = _tokenIndex;
|
|
||||||
if (TryExpectIdentifier(out var name))
|
|
||||||
{
|
|
||||||
if (name.Value[0] == 'u' && ulong.TryParse(name.Value[1..], out var size))
|
|
||||||
{
|
|
||||||
if (size is not 8 and not 16 and not 32 and not 64)
|
|
||||||
{
|
|
||||||
throw new CompileException(Diagnostic
|
|
||||||
.Error("Arbitrary uint size is not supported")
|
|
||||||
.WithHelp("Use u8, u16, u32 or u64")
|
|
||||||
.At(name, _tokens)
|
|
||||||
.Build());
|
|
||||||
}
|
|
||||||
|
|
||||||
return new IntTypeSyntax(GetTokens(startIndex), false, size);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (name.Value[0] == 'i' && ulong.TryParse(name.Value[1..], out size))
|
|
||||||
{
|
|
||||||
if (size is not 8 and not 16 and not 32 and not 64)
|
|
||||||
{
|
|
||||||
throw new CompileException(Diagnostic
|
|
||||||
.Error("Arbitrary int size is not supported")
|
|
||||||
.WithHelp("Use i8, i16, i32 or i64")
|
|
||||||
.At(name, _tokens)
|
|
||||||
.Build());
|
|
||||||
}
|
|
||||||
|
|
||||||
return new IntTypeSyntax(GetTokens(startIndex), true, size);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (name.Value[0] == 'f' && ulong.TryParse(name.Value[1..], out size))
|
|
||||||
{
|
|
||||||
if (size is not 32 and not 64)
|
|
||||||
{
|
|
||||||
throw new CompileException(Diagnostic
|
|
||||||
.Error("Arbitrary float size is not supported")
|
|
||||||
.WithHelp("Use f32 or f64")
|
|
||||||
.At(name, _tokens)
|
|
||||||
.Build());
|
|
||||||
}
|
|
||||||
|
|
||||||
return new FloatTypeSyntax(GetTokens(startIndex), size);
|
|
||||||
}
|
|
||||||
|
|
||||||
switch (name.Value)
|
|
||||||
{
|
|
||||||
case "void":
|
|
||||||
return new VoidTypeSyntax(GetTokens(startIndex));
|
|
||||||
case "string":
|
|
||||||
return new StringTypeSyntax(GetTokens(startIndex));
|
|
||||||
case "bool":
|
|
||||||
return new BoolTypeSyntax(GetTokens(startIndex));
|
|
||||||
default:
|
|
||||||
{
|
|
||||||
IdentifierToken? module = null;
|
|
||||||
|
|
||||||
if (TryExpectSymbol(Symbol.DoubleColon))
|
|
||||||
{
|
|
||||||
var customTypeName = ExpectIdentifier();
|
|
||||||
module = name;
|
|
||||||
name = customTypeName;
|
|
||||||
}
|
|
||||||
|
|
||||||
return new CustomTypeSyntax(GetTokens(startIndex), module, name);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (TryExpectSymbol(Symbol.Caret))
|
|
||||||
{
|
|
||||||
var baseType = ParseType();
|
|
||||||
return new PointerTypeSyntax(GetTokens(startIndex), baseType);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (TryExpectSymbol(Symbol.Func))
|
|
||||||
{
|
|
||||||
ExpectSymbol(Symbol.OpenParen);
|
|
||||||
|
|
||||||
List<TypeSyntax> parameters = [];
|
|
||||||
while (!TryExpectSymbol(Symbol.CloseParen))
|
|
||||||
{
|
|
||||||
parameters.Add(ParseType());
|
|
||||||
if (!TryExpectSymbol(Symbol.Comma))
|
|
||||||
{
|
|
||||||
ExpectSymbol(Symbol.CloseParen);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var returnType = TryExpectSymbol(Symbol.Colon)
|
|
||||||
? ParseType()
|
|
||||||
: new VoidTypeSyntax([]);
|
|
||||||
|
|
||||||
return new FuncTypeSyntax(GetTokens(startIndex), parameters, returnType);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (TryExpectSymbol(Symbol.OpenBracket))
|
|
||||||
{
|
|
||||||
if (TryExpectIntLiteral(out var intLiteral))
|
|
||||||
{
|
|
||||||
ExpectSymbol(Symbol.CloseBracket);
|
|
||||||
var baseType = ParseType();
|
|
||||||
return new ConstArrayTypeSyntax(GetTokens(startIndex), baseType, intLiteral.AsU64);
|
|
||||||
}
|
|
||||||
else if (TryExpectSymbol(Symbol.QuestionMark))
|
|
||||||
{
|
|
||||||
ExpectSymbol(Symbol.CloseBracket);
|
|
||||||
var baseType = ParseType();
|
|
||||||
return new ArrayTypeSyntax(GetTokens(startIndex), baseType);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
ExpectSymbol(Symbol.CloseBracket);
|
|
||||||
var baseType = ParseType();
|
|
||||||
return new SliceTypeSyntax(GetTokens(startIndex), baseType);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
throw new CompileException(Diagnostic
|
|
||||||
.Error("Invalid type syntax")
|
|
||||||
.WithHelp("Expected type name, '^' for pointer, or '[]' for array")
|
|
||||||
.At(CurrentToken, _tokens)
|
|
||||||
.Build());
|
|
||||||
}
|
|
||||||
|
|
||||||
private Token ExpectToken()
|
|
||||||
{
|
|
||||||
if (!HasToken)
|
|
||||||
{
|
|
||||||
throw new CompileException(Diagnostic
|
|
||||||
.Error("Unexpected end of file")
|
|
||||||
.WithHelp("Expected more tokens to complete the syntax")
|
|
||||||
.At(_tokens[^1], _tokens)
|
|
||||||
.Build());
|
|
||||||
}
|
|
||||||
|
|
||||||
var token = CurrentToken!;
|
|
||||||
Next();
|
|
||||||
return token;
|
|
||||||
}
|
|
||||||
|
|
||||||
private SymbolToken ExpectSymbol()
|
|
||||||
{
|
|
||||||
var token = ExpectToken();
|
|
||||||
if (token is not SymbolToken symbol)
|
|
||||||
{
|
|
||||||
throw new CompileException(Diagnostic
|
|
||||||
.Error($"Expected symbol, but found {token.GetType().Name}")
|
|
||||||
.WithHelp("This position requires a symbol like '(', ')', '{', '}', etc.")
|
|
||||||
.At(token, _tokens)
|
|
||||||
.Build());
|
|
||||||
}
|
|
||||||
|
|
||||||
return symbol;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void ExpectSymbol(Symbol expectedSymbol)
|
|
||||||
{
|
|
||||||
var token = ExpectSymbol();
|
|
||||||
if (token.Symbol != expectedSymbol)
|
|
||||||
{
|
|
||||||
throw new CompileException(Diagnostic
|
|
||||||
.Error($"Expected '{expectedSymbol}', but found '{token.Symbol}'")
|
|
||||||
.WithHelp($"Insert '{expectedSymbol}' here")
|
|
||||||
.At(token, _tokens)
|
|
||||||
.Build());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private bool TryExpectSymbol(out Symbol symbol)
|
|
||||||
{
|
|
||||||
if (CurrentToken is SymbolToken symbolToken)
|
|
||||||
{
|
|
||||||
Next();
|
|
||||||
symbol = symbolToken.Symbol;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
symbol = default;
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
private bool TryExpectSymbol(Symbol symbol)
|
|
||||||
{
|
|
||||||
if (CurrentToken is SymbolToken symbolToken && symbolToken.Symbol == symbol)
|
|
||||||
{
|
|
||||||
Next();
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
private bool TryExpectIdentifier([NotNullWhen(true)] out IdentifierToken? identifier)
|
|
||||||
{
|
|
||||||
if (CurrentToken is IdentifierToken identifierToken)
|
|
||||||
{
|
|
||||||
identifier = identifierToken;
|
|
||||||
Next();
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
identifier = null;
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
private IdentifierToken ExpectIdentifier()
|
|
||||||
{
|
|
||||||
var token = ExpectToken();
|
|
||||||
if (token is not IdentifierToken identifier)
|
|
||||||
{
|
|
||||||
throw new CompileException(Diagnostic
|
|
||||||
.Error($"Expected identifier, but found {token.GetType().Name}")
|
|
||||||
.WithHelp("Provide a valid identifier name here")
|
|
||||||
.At(token, _tokens)
|
|
||||||
.Build());
|
|
||||||
}
|
|
||||||
|
|
||||||
return identifier;
|
|
||||||
}
|
|
||||||
|
|
||||||
private bool TryExpectIntLiteral([NotNullWhen(true)] out IntLiteralToken? stringLiteral)
|
|
||||||
{
|
|
||||||
if (CurrentToken is IntLiteralToken token)
|
|
||||||
{
|
|
||||||
stringLiteral = token;
|
|
||||||
Next();
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
stringLiteral = null;
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
private StringLiteralToken ExpectStringLiteral()
|
|
||||||
{
|
|
||||||
var token = ExpectToken();
|
|
||||||
if (token is not StringLiteralToken identifier)
|
|
||||||
{
|
|
||||||
throw new CompileException(Diagnostic
|
|
||||||
.Error($"Expected string literal, but found {token.GetType().Name}")
|
|
||||||
.WithHelp("Provide a valid string literal")
|
|
||||||
.At(token, _tokens)
|
|
||||||
.Build());
|
|
||||||
}
|
|
||||||
|
|
||||||
return identifier;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void Next()
|
|
||||||
{
|
|
||||||
_tokenIndex++;
|
|
||||||
while (_tokenIndex < _tokens.Count && _tokens[_tokenIndex] is WhitespaceToken or CommentToken)
|
|
||||||
{
|
|
||||||
_tokenIndex++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private List<Token> GetTokens(int tokenStartIndex)
|
|
||||||
{
|
|
||||||
return _tokens.Skip(tokenStartIndex).Take(_tokenIndex - tokenStartIndex).ToList();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public record SyntaxTree(List<TopLevelSyntaxNode> TopLevelSyntaxNodes, List<Token> Tokens);
|
|
||||||
@@ -1,151 +0,0 @@
|
|||||||
namespace NubLang.Syntax;
|
|
||||||
|
|
||||||
public abstract record SyntaxNode(List<Token> Tokens);
|
|
||||||
|
|
||||||
#region Definitions
|
|
||||||
|
|
||||||
public record TopLevelSyntaxNode(List<Token> Tokens) : SyntaxNode(Tokens);
|
|
||||||
|
|
||||||
public record ModuleSyntax(List<Token> Tokens, IdentifierToken NameToken) : TopLevelSyntaxNode(Tokens);
|
|
||||||
|
|
||||||
public abstract record DefinitionSyntax(List<Token> Tokens, IdentifierToken NameToken, bool Exported) : TopLevelSyntaxNode(Tokens);
|
|
||||||
|
|
||||||
public record FuncParameterSyntax(List<Token> Tokens, IdentifierToken NameToken, TypeSyntax Type) : SyntaxNode(Tokens);
|
|
||||||
|
|
||||||
public record FuncPrototypeSyntax(List<Token> Tokens, IdentifierToken NameToken, bool Exported, StringLiteralToken? ExternSymbolToken, List<FuncParameterSyntax> Parameters, TypeSyntax ReturnType) : SyntaxNode(Tokens);
|
|
||||||
|
|
||||||
public record FuncSyntax(List<Token> Tokens, FuncPrototypeSyntax Prototype, BlockSyntax? Body) : DefinitionSyntax(Tokens, Prototype.NameToken, Prototype.Exported);
|
|
||||||
|
|
||||||
public record StructFieldSyntax(List<Token> Tokens, IdentifierToken NameToken, TypeSyntax Type, ExpressionSyntax? Value) : SyntaxNode(Tokens);
|
|
||||||
|
|
||||||
public record StructSyntax(List<Token> Tokens, IdentifierToken NameToken, bool Exported, bool Packed, List<StructFieldSyntax> Fields) : DefinitionSyntax(Tokens, NameToken, Exported);
|
|
||||||
|
|
||||||
public record EnumFieldSyntax(List<Token> Tokens, IdentifierToken NameToken, IntLiteralToken? ValueToken) : SyntaxNode(Tokens);
|
|
||||||
|
|
||||||
public record EnumSyntax(List<Token> Tokens, IdentifierToken NameToken, bool Exported, TypeSyntax? Type, List<EnumFieldSyntax> Fields) : DefinitionSyntax(Tokens, NameToken, Exported);
|
|
||||||
|
|
||||||
public enum UnaryOperatorSyntax
|
|
||||||
{
|
|
||||||
Negate,
|
|
||||||
Invert
|
|
||||||
}
|
|
||||||
|
|
||||||
public enum BinaryOperatorSyntax
|
|
||||||
{
|
|
||||||
Equal,
|
|
||||||
NotEqual,
|
|
||||||
GreaterThan,
|
|
||||||
GreaterThanOrEqual,
|
|
||||||
LessThan,
|
|
||||||
LessThanOrEqual,
|
|
||||||
LogicalAnd,
|
|
||||||
LogicalOr,
|
|
||||||
Plus,
|
|
||||||
Minus,
|
|
||||||
Multiply,
|
|
||||||
Divide,
|
|
||||||
Modulo,
|
|
||||||
LeftShift,
|
|
||||||
RightShift,
|
|
||||||
BitwiseAnd,
|
|
||||||
BitwiseXor,
|
|
||||||
BitwiseOr,
|
|
||||||
}
|
|
||||||
|
|
||||||
#endregion
|
|
||||||
|
|
||||||
#region Statements
|
|
||||||
|
|
||||||
public abstract record StatementSyntax(List<Token> Tokens) : SyntaxNode(Tokens);
|
|
||||||
|
|
||||||
public record BlockSyntax(List<Token> Tokens, List<StatementSyntax> Statements) : StatementSyntax(Tokens);
|
|
||||||
|
|
||||||
public record StatementExpressionSyntax(List<Token> Tokens, ExpressionSyntax Expression) : StatementSyntax(Tokens);
|
|
||||||
|
|
||||||
public record ReturnSyntax(List<Token> Tokens, ExpressionSyntax? Value) : StatementSyntax(Tokens);
|
|
||||||
|
|
||||||
public record AssignmentSyntax(List<Token> Tokens, ExpressionSyntax Target, ExpressionSyntax Value) : StatementSyntax(Tokens);
|
|
||||||
|
|
||||||
public record IfSyntax(List<Token> Tokens, ExpressionSyntax Condition, BlockSyntax Body, Variant<IfSyntax, BlockSyntax>? Else) : StatementSyntax(Tokens);
|
|
||||||
|
|
||||||
public record VariableDeclarationSyntax(List<Token> Tokens, IdentifierToken NameToken, TypeSyntax? ExplicitType, ExpressionSyntax? Assignment) : StatementSyntax(Tokens);
|
|
||||||
|
|
||||||
public record ContinueSyntax(List<Token> Tokens) : StatementSyntax(Tokens);
|
|
||||||
|
|
||||||
public record BreakSyntax(List<Token> Tokens) : StatementSyntax(Tokens);
|
|
||||||
|
|
||||||
public record DeferSyntax(List<Token> Tokens, StatementSyntax Statement) : StatementSyntax(Tokens);
|
|
||||||
|
|
||||||
public record WhileSyntax(List<Token> Tokens, ExpressionSyntax Condition, BlockSyntax Body) : StatementSyntax(Tokens);
|
|
||||||
|
|
||||||
public record ForSyntax(List<Token> Tokens, IdentifierToken ElementNameToken, IdentifierToken? IndexNameToken, ExpressionSyntax Target, BlockSyntax Body) : StatementSyntax(Tokens);
|
|
||||||
|
|
||||||
#endregion
|
|
||||||
|
|
||||||
#region Expressions
|
|
||||||
|
|
||||||
public abstract record ExpressionSyntax(List<Token> Tokens) : SyntaxNode(Tokens);
|
|
||||||
|
|
||||||
public record BinaryExpressionSyntax(List<Token> Tokens, ExpressionSyntax Left, BinaryOperatorSyntax Operator, ExpressionSyntax Right) : ExpressionSyntax(Tokens);
|
|
||||||
|
|
||||||
public record UnaryExpressionSyntax(List<Token> Tokens, UnaryOperatorSyntax Operator, ExpressionSyntax Operand) : ExpressionSyntax(Tokens);
|
|
||||||
|
|
||||||
public record FuncCallSyntax(List<Token> Tokens, ExpressionSyntax Expression, List<ExpressionSyntax> Parameters) : ExpressionSyntax(Tokens);
|
|
||||||
|
|
||||||
public record LocalIdentifierSyntax(List<Token> Tokens, IdentifierToken NameToken) : ExpressionSyntax(Tokens);
|
|
||||||
|
|
||||||
public record ModuleIdentifierSyntax(List<Token> Tokens, IdentifierToken ModuleToken, IdentifierToken NameToken) : ExpressionSyntax(Tokens);
|
|
||||||
|
|
||||||
public record ArrayInitializerSyntax(List<Token> Tokens, List<ExpressionSyntax> Values) : ExpressionSyntax(Tokens);
|
|
||||||
|
|
||||||
public record ArrayIndexAccessSyntax(List<Token> Tokens, ExpressionSyntax Target, ExpressionSyntax Index) : ExpressionSyntax(Tokens);
|
|
||||||
|
|
||||||
public record AddressOfSyntax(List<Token> Tokens, ExpressionSyntax Target) : ExpressionSyntax(Tokens);
|
|
||||||
|
|
||||||
public record IntLiteralSyntax(List<Token> Tokens, IntLiteralToken Token) : ExpressionSyntax(Tokens);
|
|
||||||
|
|
||||||
public record StringLiteralSyntax(List<Token> Tokens, StringLiteralToken Token) : ExpressionSyntax(Tokens);
|
|
||||||
|
|
||||||
public record BoolLiteralSyntax(List<Token> Tokens, BoolLiteralToken Token) : ExpressionSyntax(Tokens);
|
|
||||||
|
|
||||||
public record FloatLiteralSyntax(List<Token> Tokens, FloatLiteralToken Token) : ExpressionSyntax(Tokens);
|
|
||||||
|
|
||||||
public record MemberAccessSyntax(List<Token> Tokens, ExpressionSyntax Target, IdentifierToken MemberToken) : ExpressionSyntax(Tokens);
|
|
||||||
|
|
||||||
public record StructInitializerSyntax(List<Token> Tokens, TypeSyntax? StructType, Dictionary<IdentifierToken, ExpressionSyntax> Initializers) : ExpressionSyntax(Tokens);
|
|
||||||
|
|
||||||
public record DereferenceSyntax(List<Token> Tokens, ExpressionSyntax Target) : ExpressionSyntax(Tokens);
|
|
||||||
|
|
||||||
public record SizeSyntax(List<Token> Tokens, TypeSyntax Type) : ExpressionSyntax(Tokens);
|
|
||||||
|
|
||||||
public record CastSyntax(List<Token> Tokens, ExpressionSyntax Value) : ExpressionSyntax(Tokens);
|
|
||||||
|
|
||||||
#endregion
|
|
||||||
|
|
||||||
#region Types
|
|
||||||
|
|
||||||
public abstract record TypeSyntax(List<Token> Tokens) : SyntaxNode(Tokens);
|
|
||||||
|
|
||||||
public record FuncTypeSyntax(List<Token> Tokens, List<TypeSyntax> Parameters, TypeSyntax ReturnType) : TypeSyntax(Tokens);
|
|
||||||
|
|
||||||
public record PointerTypeSyntax(List<Token> Tokens, TypeSyntax BaseType) : TypeSyntax(Tokens);
|
|
||||||
|
|
||||||
public record VoidTypeSyntax(List<Token> Tokens) : TypeSyntax(Tokens);
|
|
||||||
|
|
||||||
public record IntTypeSyntax(List<Token> Tokens, bool Signed, ulong Width) : TypeSyntax(Tokens);
|
|
||||||
|
|
||||||
public record FloatTypeSyntax(List<Token> Tokens, ulong Width) : TypeSyntax(Tokens);
|
|
||||||
|
|
||||||
public record BoolTypeSyntax(List<Token> Tokens) : TypeSyntax(Tokens);
|
|
||||||
|
|
||||||
public record StringTypeSyntax(List<Token> Tokens) : TypeSyntax(Tokens);
|
|
||||||
|
|
||||||
public record SliceTypeSyntax(List<Token> Tokens, TypeSyntax BaseType) : TypeSyntax(Tokens);
|
|
||||||
|
|
||||||
public record ArrayTypeSyntax(List<Token> Tokens, TypeSyntax BaseType) : TypeSyntax(Tokens);
|
|
||||||
|
|
||||||
public record ConstArrayTypeSyntax(List<Token> Tokens, TypeSyntax BaseType, ulong Size) : TypeSyntax(Tokens);
|
|
||||||
|
|
||||||
public record CustomTypeSyntax(List<Token> Tokens, IdentifierToken? ModuleToken, IdentifierToken NameToken) : TypeSyntax(Tokens);
|
|
||||||
|
|
||||||
#endregion
|
|
||||||
@@ -1,223 +0,0 @@
|
|||||||
using NubLang.Diagnostics;
|
|
||||||
|
|
||||||
namespace NubLang.Syntax;
|
|
||||||
|
|
||||||
public abstract class Token(SourceSpan span)
|
|
||||||
{
|
|
||||||
public SourceSpan Span { get; } = span;
|
|
||||||
}
|
|
||||||
|
|
||||||
public class WhitespaceToken(SourceSpan span) : Token(span);
|
|
||||||
|
|
||||||
public class CommentToken(SourceSpan span, string comment) : Token(span)
|
|
||||||
{
|
|
||||||
public string Comment { get; } = comment;
|
|
||||||
|
|
||||||
public override string ToString()
|
|
||||||
{
|
|
||||||
return "// " + Comment;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public class IdentifierToken(SourceSpan span, string value) : Token(span)
|
|
||||||
{
|
|
||||||
public string Value { get; } = value;
|
|
||||||
|
|
||||||
public override string ToString()
|
|
||||||
{
|
|
||||||
return Value;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public class IntLiteralToken(SourceSpan span, string value, int @base) : Token(span)
|
|
||||||
{
|
|
||||||
public string Value { get; } = value;
|
|
||||||
public int Base { get; } = @base;
|
|
||||||
|
|
||||||
private string GetNumericValue()
|
|
||||||
{
|
|
||||||
return Base switch
|
|
||||||
{
|
|
||||||
2 when Value.StartsWith("0b", StringComparison.OrdinalIgnoreCase) => Value[2..],
|
|
||||||
8 when Value.StartsWith("0o", StringComparison.OrdinalIgnoreCase) => Value[2..],
|
|
||||||
16 when Value.StartsWith("0x", StringComparison.OrdinalIgnoreCase) => Value[2..],
|
|
||||||
_ => Value
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
public ulong AsU64 => Convert.ToUInt64(GetNumericValue(), Base);
|
|
||||||
public long AsI64 => Convert.ToInt64(GetNumericValue(), Base);
|
|
||||||
public uint AsU32 => Convert.ToUInt32(GetNumericValue(), Base);
|
|
||||||
public int AsI32 => Convert.ToInt32(GetNumericValue(), Base);
|
|
||||||
public ushort AsU16 => Convert.ToUInt16(GetNumericValue(), Base);
|
|
||||||
public short AsI16 => Convert.ToInt16(GetNumericValue(), Base);
|
|
||||||
public byte AsU8 => Convert.ToByte(GetNumericValue(), Base);
|
|
||||||
public sbyte AsI8 => Convert.ToSByte(GetNumericValue(), Base);
|
|
||||||
|
|
||||||
public float AsF32 => Convert.ToSingle(AsI32);
|
|
||||||
public double AsF64 => Convert.ToDouble(AsI64);
|
|
||||||
|
|
||||||
public override string ToString()
|
|
||||||
{
|
|
||||||
return Value;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public class StringLiteralToken(SourceSpan span, string value) : Token(span)
|
|
||||||
{
|
|
||||||
public string Value { get; } = value;
|
|
||||||
|
|
||||||
public override string ToString()
|
|
||||||
{
|
|
||||||
return $"\"{Value}\"";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public class BoolLiteralToken(SourceSpan span, bool value) : Token(span)
|
|
||||||
{
|
|
||||||
public bool Value { get; } = value;
|
|
||||||
|
|
||||||
public override string ToString()
|
|
||||||
{
|
|
||||||
return Value ? "true" : "false";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public class FloatLiteralToken(SourceSpan span, string value) : Token(span)
|
|
||||||
{
|
|
||||||
public string Value { get; } = value;
|
|
||||||
|
|
||||||
public float AsF32 => Convert.ToSingle(Value);
|
|
||||||
public double AsF64 => Convert.ToDouble(Value);
|
|
||||||
|
|
||||||
public override string ToString()
|
|
||||||
{
|
|
||||||
return Value;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public enum Symbol
|
|
||||||
{
|
|
||||||
// Default
|
|
||||||
None,
|
|
||||||
|
|
||||||
// Control
|
|
||||||
If,
|
|
||||||
Else,
|
|
||||||
While,
|
|
||||||
For,
|
|
||||||
In,
|
|
||||||
Break,
|
|
||||||
Continue,
|
|
||||||
Return,
|
|
||||||
Let,
|
|
||||||
Defer,
|
|
||||||
|
|
||||||
// Declaration
|
|
||||||
Func,
|
|
||||||
Struct,
|
|
||||||
Enum,
|
|
||||||
Module,
|
|
||||||
|
|
||||||
// Modifier
|
|
||||||
Extern,
|
|
||||||
Packed,
|
|
||||||
Export,
|
|
||||||
|
|
||||||
Colon,
|
|
||||||
DoubleColon,
|
|
||||||
OpenParen,
|
|
||||||
CloseParen,
|
|
||||||
OpenBrace,
|
|
||||||
CloseBrace,
|
|
||||||
OpenBracket,
|
|
||||||
CloseBracket,
|
|
||||||
Comma,
|
|
||||||
Period,
|
|
||||||
Assign,
|
|
||||||
Bang,
|
|
||||||
Equal,
|
|
||||||
NotEqual,
|
|
||||||
LessThan,
|
|
||||||
LessThanOrEqual,
|
|
||||||
GreaterThan,
|
|
||||||
GreaterThanOrEqual,
|
|
||||||
Plus,
|
|
||||||
Minus,
|
|
||||||
Star,
|
|
||||||
ForwardSlash,
|
|
||||||
Caret,
|
|
||||||
Tilde,
|
|
||||||
Ampersand,
|
|
||||||
Semi,
|
|
||||||
Percent,
|
|
||||||
LeftShift,
|
|
||||||
RightShift,
|
|
||||||
Pipe,
|
|
||||||
And,
|
|
||||||
Or,
|
|
||||||
At,
|
|
||||||
QuestionMark,
|
|
||||||
}
|
|
||||||
|
|
||||||
public class SymbolToken(SourceSpan span, Symbol symbol) : Token(span)
|
|
||||||
{
|
|
||||||
public Symbol Symbol { get; } = symbol;
|
|
||||||
|
|
||||||
public override string ToString()
|
|
||||||
{
|
|
||||||
return Symbol switch
|
|
||||||
{
|
|
||||||
Symbol.Func => "func",
|
|
||||||
Symbol.If => "if",
|
|
||||||
Symbol.Else => "else",
|
|
||||||
Symbol.While => "while",
|
|
||||||
Symbol.For => "for",
|
|
||||||
Symbol.In => "in",
|
|
||||||
Symbol.Break => "break",
|
|
||||||
Symbol.Continue => "continue",
|
|
||||||
Symbol.Return => "return",
|
|
||||||
Symbol.Struct => "struct",
|
|
||||||
Symbol.Let => "let",
|
|
||||||
Symbol.Extern => "extern",
|
|
||||||
Symbol.Module => "module",
|
|
||||||
Symbol.Export => "export",
|
|
||||||
Symbol.Defer => "defer",
|
|
||||||
Symbol.Enum => "enum",
|
|
||||||
Symbol.Equal => "==",
|
|
||||||
Symbol.NotEqual => "!=",
|
|
||||||
Symbol.LessThanOrEqual => "<=",
|
|
||||||
Symbol.GreaterThanOrEqual => ">=",
|
|
||||||
Symbol.LeftShift => "<<",
|
|
||||||
Symbol.RightShift => ">>",
|
|
||||||
Symbol.And => "&&",
|
|
||||||
Symbol.Or => "||",
|
|
||||||
Symbol.DoubleColon => "::",
|
|
||||||
Symbol.Colon => ":",
|
|
||||||
Symbol.OpenParen => "(",
|
|
||||||
Symbol.CloseParen => ")",
|
|
||||||
Symbol.OpenBrace => "{",
|
|
||||||
Symbol.CloseBrace => "}",
|
|
||||||
Symbol.OpenBracket => "[",
|
|
||||||
Symbol.CloseBracket => "]",
|
|
||||||
Symbol.Comma => ",",
|
|
||||||
Symbol.Period => ".",
|
|
||||||
Symbol.Assign => "=",
|
|
||||||
Symbol.LessThan => "<",
|
|
||||||
Symbol.GreaterThan => ">",
|
|
||||||
Symbol.Plus => "+",
|
|
||||||
Symbol.Minus => "-",
|
|
||||||
Symbol.Star => "*",
|
|
||||||
Symbol.ForwardSlash => "/",
|
|
||||||
Symbol.Bang => "!",
|
|
||||||
Symbol.Caret => "^",
|
|
||||||
Symbol.Ampersand => "&",
|
|
||||||
Symbol.Semi => ";",
|
|
||||||
Symbol.Percent => "%",
|
|
||||||
Symbol.Pipe => "|",
|
|
||||||
Symbol.At => "@",
|
|
||||||
Symbol.QuestionMark => "?",
|
|
||||||
_ => "none",
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,409 +0,0 @@
|
|||||||
using NubLang.Diagnostics;
|
|
||||||
|
|
||||||
namespace NubLang.Syntax;
|
|
||||||
|
|
||||||
public sealed class Tokenizer
|
|
||||||
{
|
|
||||||
private string _fileName = null!;
|
|
||||||
private string _content = null!;
|
|
||||||
private int _index;
|
|
||||||
private int _line = 1;
|
|
||||||
private int _column = 1;
|
|
||||||
|
|
||||||
public List<Diagnostic> Diagnostics { get; set; } = new(16);
|
|
||||||
|
|
||||||
public List<Token> Tokenize(string fileName, string content)
|
|
||||||
{
|
|
||||||
_fileName = fileName;
|
|
||||||
_content = content;
|
|
||||||
|
|
||||||
Diagnostics = [];
|
|
||||||
|
|
||||||
_index = 0;
|
|
||||||
_line = 1;
|
|
||||||
_column = 1;
|
|
||||||
|
|
||||||
var tokens = new List<Token>();
|
|
||||||
|
|
||||||
while (_index < _content.Length)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
tokens.Add(ParseToken());
|
|
||||||
}
|
|
||||||
catch (CompileException e)
|
|
||||||
{
|
|
||||||
Diagnostics.Add(e.Diagnostic);
|
|
||||||
Next();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return tokens;
|
|
||||||
}
|
|
||||||
|
|
||||||
private Token ParseToken()
|
|
||||||
{
|
|
||||||
var indexStart = _index;
|
|
||||||
var lineStart = _line;
|
|
||||||
var columnStart = _column;
|
|
||||||
|
|
||||||
if (char.IsWhiteSpace(_content[_index]))
|
|
||||||
{
|
|
||||||
while (_index < _content.Length && char.IsWhiteSpace(_content[_index]))
|
|
||||||
{
|
|
||||||
Next();
|
|
||||||
}
|
|
||||||
|
|
||||||
return new WhitespaceToken(CreateSpan(indexStart, lineStart, columnStart));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (_content[_index] == '/' && _index + 1 < _content.Length && _content[_index + 1] == '/')
|
|
||||||
{
|
|
||||||
var startIndex = _index;
|
|
||||||
|
|
||||||
Next(2);
|
|
||||||
while (_index < _content.Length && _content[_index] != '\n')
|
|
||||||
{
|
|
||||||
Next();
|
|
||||||
}
|
|
||||||
|
|
||||||
return new CommentToken(CreateSpan(indexStart, lineStart, columnStart), _content.AsSpan(startIndex, _index - startIndex).ToString());
|
|
||||||
}
|
|
||||||
|
|
||||||
if (char.IsDigit(_content[_index]))
|
|
||||||
{
|
|
||||||
return ParseNumber(indexStart, lineStart, columnStart);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (_content[_index] == '"')
|
|
||||||
{
|
|
||||||
return ParseString(indexStart, lineStart, columnStart);
|
|
||||||
}
|
|
||||||
|
|
||||||
// note(nub31): Look for keywords (longest first in case a keyword fits partially in a larger keyword)
|
|
||||||
for (var i = 8; i >= 1; i--)
|
|
||||||
{
|
|
||||||
if (TryMatchSymbol(i, indexStart, lineStart, columnStart, out var token))
|
|
||||||
{
|
|
||||||
return token;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (char.IsLetter(_content[_index]) || _content[_index] == '_')
|
|
||||||
{
|
|
||||||
return ParseIdentifier(indexStart, lineStart, columnStart);
|
|
||||||
}
|
|
||||||
|
|
||||||
throw new CompileException(Diagnostic.Error($"Unknown token '{_content[_index]}'").Build());
|
|
||||||
}
|
|
||||||
|
|
||||||
private Token ParseNumber(int indexStart, int lineStart, int columnStart)
|
|
||||||
{
|
|
||||||
var start = _index;
|
|
||||||
var current = _content[_index];
|
|
||||||
|
|
||||||
// note(nub31): 0xFFFFFF
|
|
||||||
if (current == '0' && _index + 1 < _content.Length && _content[_index + 1] == 'x')
|
|
||||||
{
|
|
||||||
Next(2);
|
|
||||||
var digitStart = _index;
|
|
||||||
|
|
||||||
while (_index < _content.Length && Uri.IsHexDigit(_content[_index]))
|
|
||||||
{
|
|
||||||
Next();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (_index == digitStart)
|
|
||||||
{
|
|
||||||
throw new CompileException(Diagnostic
|
|
||||||
.Error("Invalid hex literal, no digits found")
|
|
||||||
.At(CreateSpan(_index, _line, _column))
|
|
||||||
.Build());
|
|
||||||
}
|
|
||||||
|
|
||||||
return new IntLiteralToken(
|
|
||||||
CreateSpan(indexStart, lineStart, columnStart),
|
|
||||||
_content.Substring(start, _index - start),
|
|
||||||
16);
|
|
||||||
}
|
|
||||||
|
|
||||||
// note(nub31): 0b11001100
|
|
||||||
if (current == '0' && _index + 1 < _content.Length && _content[_index + 1] == 'b')
|
|
||||||
{
|
|
||||||
Next(2);
|
|
||||||
var digitStart = _index;
|
|
||||||
|
|
||||||
while (_index < _content.Length && (_content[_index] == '0' || _content[_index] == '1'))
|
|
||||||
{
|
|
||||||
Next();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (_index == digitStart)
|
|
||||||
{
|
|
||||||
throw new CompileException(Diagnostic
|
|
||||||
.Error("Invalid binary literal, no digits found")
|
|
||||||
.At(CreateSpan(_index, _line, _column))
|
|
||||||
.Build());
|
|
||||||
}
|
|
||||||
|
|
||||||
return new IntLiteralToken(
|
|
||||||
CreateSpan(indexStart, lineStart, columnStart),
|
|
||||||
_content.Substring(start, _index - start),
|
|
||||||
2);
|
|
||||||
}
|
|
||||||
|
|
||||||
// note(nub31): 23/23.5
|
|
||||||
var isFloat = false;
|
|
||||||
while (_index < _content.Length)
|
|
||||||
{
|
|
||||||
var next = _content[_index];
|
|
||||||
|
|
||||||
if (next == '.')
|
|
||||||
{
|
|
||||||
if (isFloat)
|
|
||||||
{
|
|
||||||
throw new CompileException(Diagnostic
|
|
||||||
.Error("More than one period found in float literal")
|
|
||||||
.At(CreateSpan(_index, _line, _column))
|
|
||||||
.Build());
|
|
||||||
}
|
|
||||||
|
|
||||||
isFloat = true;
|
|
||||||
Next();
|
|
||||||
}
|
|
||||||
else if (char.IsDigit(next))
|
|
||||||
{
|
|
||||||
Next();
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var buffer = _content.Substring(start, _index - start);
|
|
||||||
|
|
||||||
return isFloat
|
|
||||||
? new FloatLiteralToken(CreateSpan(indexStart, lineStart, columnStart), buffer)
|
|
||||||
: new IntLiteralToken(CreateSpan(indexStart, lineStart, columnStart), buffer, 10);
|
|
||||||
}
|
|
||||||
|
|
||||||
private StringLiteralToken ParseString(int indexStart, int lineStart, int columnStart)
|
|
||||||
{
|
|
||||||
Next();
|
|
||||||
var start = _index;
|
|
||||||
|
|
||||||
while (true)
|
|
||||||
{
|
|
||||||
if (_index >= _content.Length)
|
|
||||||
{
|
|
||||||
throw new CompileException(Diagnostic
|
|
||||||
.Error("Unclosed string literal")
|
|
||||||
.At(CreateSpan(_index, _line, _column))
|
|
||||||
.Build());
|
|
||||||
}
|
|
||||||
|
|
||||||
var next = _content[_index];
|
|
||||||
|
|
||||||
if (next == '\n')
|
|
||||||
{
|
|
||||||
throw new CompileException(Diagnostic
|
|
||||||
.Error("Unclosed string literal (newline found)")
|
|
||||||
.At(CreateSpan(_index, _line, _column))
|
|
||||||
.Build());
|
|
||||||
}
|
|
||||||
|
|
||||||
if (next == '"')
|
|
||||||
{
|
|
||||||
var buffer = _content.Substring(start, _index - start);
|
|
||||||
Next();
|
|
||||||
return new StringLiteralToken(CreateSpan(indexStart, lineStart, columnStart), buffer);
|
|
||||||
}
|
|
||||||
|
|
||||||
Next();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private bool TryMatchSymbol(int length, int indexStart, int lineStart, int columnStart, out Token token)
|
|
||||||
{
|
|
||||||
token = null!;
|
|
||||||
|
|
||||||
if (_index + length > _content.Length)
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
var span = _content.AsSpan(_index, length);
|
|
||||||
|
|
||||||
if (span is "true")
|
|
||||||
{
|
|
||||||
Next(4);
|
|
||||||
token = new BoolLiteralToken(CreateSpan(indexStart, lineStart, columnStart), true);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (span is "false")
|
|
||||||
{
|
|
||||||
Next(5);
|
|
||||||
token = new BoolLiteralToken(CreateSpan(indexStart, lineStart, columnStart), false);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
var symbol = length switch
|
|
||||||
{
|
|
||||||
8 => span switch
|
|
||||||
{
|
|
||||||
"continue" => Symbol.Continue,
|
|
||||||
_ => Symbol.None
|
|
||||||
},
|
|
||||||
6 => span switch
|
|
||||||
{
|
|
||||||
"return" => Symbol.Return,
|
|
||||||
"struct" => Symbol.Struct,
|
|
||||||
"extern" => Symbol.Extern,
|
|
||||||
"packed" => Symbol.Packed,
|
|
||||||
"module" => Symbol.Module,
|
|
||||||
"export" => Symbol.Export,
|
|
||||||
_ => Symbol.None
|
|
||||||
},
|
|
||||||
5 => span switch
|
|
||||||
{
|
|
||||||
"break" => Symbol.Break,
|
|
||||||
"while" => Symbol.While,
|
|
||||||
"defer" => Symbol.Defer,
|
|
||||||
_ => Symbol.None
|
|
||||||
},
|
|
||||||
4 => span switch
|
|
||||||
{
|
|
||||||
"func" => Symbol.Func,
|
|
||||||
"else" => Symbol.Else,
|
|
||||||
"enum" => Symbol.Enum,
|
|
||||||
_ => Symbol.None
|
|
||||||
},
|
|
||||||
3 => span switch
|
|
||||||
{
|
|
||||||
"for" => Symbol.For,
|
|
||||||
"let" => Symbol.Let,
|
|
||||||
_ => Symbol.None
|
|
||||||
},
|
|
||||||
2 => span switch
|
|
||||||
{
|
|
||||||
"if" => Symbol.If,
|
|
||||||
"in" => Symbol.In,
|
|
||||||
"==" => Symbol.Equal,
|
|
||||||
"!=" => Symbol.NotEqual,
|
|
||||||
"<=" => Symbol.LessThanOrEqual,
|
|
||||||
">=" => Symbol.GreaterThanOrEqual,
|
|
||||||
"<<" => Symbol.LeftShift,
|
|
||||||
">>" => Symbol.RightShift,
|
|
||||||
"&&" => Symbol.And,
|
|
||||||
"||" => Symbol.Or,
|
|
||||||
"::" => Symbol.DoubleColon,
|
|
||||||
_ => Symbol.None
|
|
||||||
},
|
|
||||||
1 => span[0] switch
|
|
||||||
{
|
|
||||||
':' => Symbol.Colon,
|
|
||||||
'(' => Symbol.OpenParen,
|
|
||||||
')' => Symbol.CloseParen,
|
|
||||||
'{' => Symbol.OpenBrace,
|
|
||||||
'}' => Symbol.CloseBrace,
|
|
||||||
'[' => Symbol.OpenBracket,
|
|
||||||
']' => Symbol.CloseBracket,
|
|
||||||
',' => Symbol.Comma,
|
|
||||||
'.' => Symbol.Period,
|
|
||||||
'=' => Symbol.Assign,
|
|
||||||
'<' => Symbol.LessThan,
|
|
||||||
'>' => Symbol.GreaterThan,
|
|
||||||
'+' => Symbol.Plus,
|
|
||||||
'-' => Symbol.Minus,
|
|
||||||
'*' => Symbol.Star,
|
|
||||||
'/' => Symbol.ForwardSlash,
|
|
||||||
'!' => Symbol.Bang,
|
|
||||||
'^' => Symbol.Caret,
|
|
||||||
'&' => Symbol.Ampersand,
|
|
||||||
';' => Symbol.Semi,
|
|
||||||
'%' => Symbol.Percent,
|
|
||||||
'|' => Symbol.Pipe,
|
|
||||||
'@' => Symbol.At,
|
|
||||||
'?' => Symbol.QuestionMark,
|
|
||||||
'~' => Symbol.Tilde,
|
|
||||||
_ => Symbol.None
|
|
||||||
},
|
|
||||||
_ => Symbol.None
|
|
||||||
};
|
|
||||||
|
|
||||||
if (symbol != Symbol.None)
|
|
||||||
{
|
|
||||||
var isAlphaKeyword = char.IsLetter(span[0]);
|
|
||||||
if (isAlphaKeyword)
|
|
||||||
{
|
|
||||||
var nextIdx = _index + length;
|
|
||||||
if (nextIdx < _content.Length)
|
|
||||||
{
|
|
||||||
var nextChar = _content[nextIdx];
|
|
||||||
if (char.IsLetterOrDigit(nextChar) || nextChar == '_')
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Next(length);
|
|
||||||
token = new SymbolToken(CreateSpan(indexStart, lineStart, columnStart), symbol);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
private IdentifierToken ParseIdentifier(int indexStart, int lineStart, int columnStart)
|
|
||||||
{
|
|
||||||
var start = _index;
|
|
||||||
|
|
||||||
while (_index < _content.Length)
|
|
||||||
{
|
|
||||||
var ch = _content[_index];
|
|
||||||
if (char.IsLetterOrDigit(ch) || ch == '_')
|
|
||||||
{
|
|
||||||
Next();
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return new IdentifierToken(CreateSpan(indexStart, lineStart, columnStart), _content.Substring(start, _index - start));
|
|
||||||
}
|
|
||||||
|
|
||||||
private SourceSpan CreateSpan(int indexStart, int lineStart, int columnStart)
|
|
||||||
{
|
|
||||||
return new SourceSpan(_fileName, _content, indexStart, Math.Min(_index, _content.Length), lineStart, columnStart, _line, _column);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void Next(int count = 1)
|
|
||||||
{
|
|
||||||
for (var i = 0; i < count; i++)
|
|
||||||
{
|
|
||||||
if (_index < _content.Length)
|
|
||||||
{
|
|
||||||
if (_content[_index] == '\n')
|
|
||||||
{
|
|
||||||
_line += 1;
|
|
||||||
_column = 1;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
_column++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
_column++;
|
|
||||||
}
|
|
||||||
|
|
||||||
_index++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,267 +0,0 @@
|
|||||||
using System.Security.Cryptography;
|
|
||||||
using System.Text;
|
|
||||||
|
|
||||||
namespace NubLang.Types;
|
|
||||||
|
|
||||||
public abstract class NubType : IEquatable<NubType>
|
|
||||||
{
|
|
||||||
public abstract ulong GetSize();
|
|
||||||
public abstract ulong GetAlignment();
|
|
||||||
public abstract bool IsAggregate();
|
|
||||||
|
|
||||||
public override bool Equals(object? obj) => obj is NubType other && Equals(other);
|
|
||||||
public abstract bool Equals(NubType? other);
|
|
||||||
|
|
||||||
public abstract override int GetHashCode();
|
|
||||||
public abstract override string ToString();
|
|
||||||
|
|
||||||
public static bool operator ==(NubType? left, NubType? right) => Equals(left, right);
|
|
||||||
public static bool operator !=(NubType? left, NubType? right) => !Equals(left, right);
|
|
||||||
}
|
|
||||||
|
|
||||||
public class NubVoidType : NubType
|
|
||||||
{
|
|
||||||
public override ulong GetSize() => 8;
|
|
||||||
public override ulong GetAlignment() => 8;
|
|
||||||
public override bool IsAggregate() => false;
|
|
||||||
|
|
||||||
public override string ToString() => "void";
|
|
||||||
public override bool Equals(NubType? other) => other is NubVoidType;
|
|
||||||
public override int GetHashCode() => HashCode.Combine(typeof(NubVoidType));
|
|
||||||
}
|
|
||||||
|
|
||||||
public sealed class NubIntType(bool signed, ulong width) : NubType
|
|
||||||
{
|
|
||||||
public bool Signed { get; } = signed;
|
|
||||||
public ulong Width { get; } = width;
|
|
||||||
|
|
||||||
public override ulong GetSize() => Width / 8;
|
|
||||||
public override ulong GetAlignment() => Width / 8;
|
|
||||||
public override bool IsAggregate() => false;
|
|
||||||
|
|
||||||
public override string ToString() => $"{(Signed ? "i" : "u")}{Width}";
|
|
||||||
public override bool Equals(NubType? other) => other is NubIntType @int && @int.Width == Width && @int.Signed == Signed;
|
|
||||||
public override int GetHashCode() => HashCode.Combine(typeof(NubIntType), Signed, Width);
|
|
||||||
}
|
|
||||||
|
|
||||||
public sealed class NubFloatType(ulong width) : NubType
|
|
||||||
{
|
|
||||||
public ulong Width { get; } = width;
|
|
||||||
|
|
||||||
public override ulong GetSize() => Width / 8;
|
|
||||||
public override ulong GetAlignment() => Width / 8;
|
|
||||||
public override bool IsAggregate() => false;
|
|
||||||
|
|
||||||
public override string ToString() => $"f{Width}";
|
|
||||||
public override bool Equals(NubType? other) => other is NubFloatType @float && @float.Width == Width;
|
|
||||||
public override int GetHashCode() => HashCode.Combine(typeof(NubFloatType), Width);
|
|
||||||
}
|
|
||||||
|
|
||||||
public class NubBoolType : NubType
|
|
||||||
{
|
|
||||||
public override ulong GetSize() => 1;
|
|
||||||
public override ulong GetAlignment() => 1;
|
|
||||||
public override bool IsAggregate() => false;
|
|
||||||
|
|
||||||
public override string ToString() => "bool";
|
|
||||||
public override bool Equals(NubType? other) => other is NubBoolType;
|
|
||||||
public override int GetHashCode() => HashCode.Combine(typeof(NubBoolType));
|
|
||||||
}
|
|
||||||
|
|
||||||
public sealed class NubPointerType(NubType baseType) : NubType
|
|
||||||
{
|
|
||||||
public NubType BaseType { get; } = baseType;
|
|
||||||
|
|
||||||
public override ulong GetSize() => 8;
|
|
||||||
public override ulong GetAlignment() => 8;
|
|
||||||
public override bool IsAggregate() => false;
|
|
||||||
|
|
||||||
public override string ToString() => "^" + BaseType;
|
|
||||||
public override bool Equals(NubType? other) => other is NubPointerType pointer && BaseType.Equals(pointer.BaseType);
|
|
||||||
public override int GetHashCode() => HashCode.Combine(typeof(NubPointerType), BaseType);
|
|
||||||
}
|
|
||||||
|
|
||||||
public class NubFuncType(List<NubType> parameters, NubType returnType) : NubType
|
|
||||||
{
|
|
||||||
public List<NubType> Parameters { get; } = parameters;
|
|
||||||
public NubType ReturnType { get; } = returnType;
|
|
||||||
|
|
||||||
public override ulong GetSize() => 8;
|
|
||||||
public override ulong GetAlignment() => 8;
|
|
||||||
public override bool IsAggregate() => false;
|
|
||||||
|
|
||||||
public override string ToString() => $"func({string.Join(", ", Parameters)}): {ReturnType}";
|
|
||||||
public override bool Equals(NubType? other) => other is NubFuncType func && ReturnType.Equals(func.ReturnType) && Parameters.SequenceEqual(func.Parameters);
|
|
||||||
|
|
||||||
public override int GetHashCode()
|
|
||||||
{
|
|
||||||
var hash = new HashCode();
|
|
||||||
hash.Add(typeof(NubFuncType));
|
|
||||||
hash.Add(ReturnType);
|
|
||||||
foreach (var param in Parameters)
|
|
||||||
{
|
|
||||||
hash.Add(param);
|
|
||||||
}
|
|
||||||
|
|
||||||
return hash.ToHashCode();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public class NubStructType(string module, string name, bool packed, List<NubStructFieldType> fields) : NubType
|
|
||||||
{
|
|
||||||
public string Module { get; } = module;
|
|
||||||
public string Name { get; } = name;
|
|
||||||
public bool Packed { get; } = packed;
|
|
||||||
public List<NubStructFieldType> Fields { get; set; } = fields;
|
|
||||||
|
|
||||||
public int GetFieldIndex(string name)
|
|
||||||
{
|
|
||||||
return Fields.FindIndex(x => x.Name == name);
|
|
||||||
}
|
|
||||||
|
|
||||||
public Dictionary<string, ulong> GetFieldOffsets()
|
|
||||||
{
|
|
||||||
var offsets = new Dictionary<string, ulong>();
|
|
||||||
ulong offset = 0;
|
|
||||||
|
|
||||||
foreach (var field in Fields)
|
|
||||||
{
|
|
||||||
var alignment = Packed ? 1 : field.Type.GetAlignment();
|
|
||||||
if (!Packed)
|
|
||||||
{
|
|
||||||
var padding = (alignment - offset % alignment) % alignment;
|
|
||||||
offset += padding;
|
|
||||||
}
|
|
||||||
|
|
||||||
offsets[field.Name] = offset;
|
|
||||||
offset += field.Type.GetSize();
|
|
||||||
}
|
|
||||||
|
|
||||||
return offsets;
|
|
||||||
}
|
|
||||||
|
|
||||||
public override ulong GetSize()
|
|
||||||
{
|
|
||||||
var offsets = GetFieldOffsets();
|
|
||||||
if (Fields.Count == 0)
|
|
||||||
{
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
var lastField = Fields.Last();
|
|
||||||
var size = offsets[lastField.Name] + lastField.Type.GetSize();
|
|
||||||
|
|
||||||
if (!Packed)
|
|
||||||
{
|
|
||||||
var structAlignment = GetAlignment();
|
|
||||||
var padding = (structAlignment - size % structAlignment) % structAlignment;
|
|
||||||
size += padding;
|
|
||||||
}
|
|
||||||
|
|
||||||
return size;
|
|
||||||
}
|
|
||||||
|
|
||||||
public override ulong GetAlignment()
|
|
||||||
{
|
|
||||||
if (Fields.Count == 0)
|
|
||||||
return 1;
|
|
||||||
|
|
||||||
return Packed ? 1 : Fields.Max(f => f.Type.GetAlignment());
|
|
||||||
}
|
|
||||||
|
|
||||||
public override bool IsAggregate() => true;
|
|
||||||
|
|
||||||
public override string ToString() => $"{Module}::{Name}";
|
|
||||||
public override bool Equals(NubType? other) => other is NubStructType structType && Name == structType.Name && Module == structType.Module;
|
|
||||||
public override int GetHashCode() => HashCode.Combine(typeof(NubStructType), Module, Name);
|
|
||||||
}
|
|
||||||
|
|
||||||
public class NubStructFieldType(string name, NubType type, bool hasDefaultValue)
|
|
||||||
{
|
|
||||||
public string Name { get; } = name;
|
|
||||||
public NubType Type { get; } = type;
|
|
||||||
public bool HasDefaultValue { get; } = hasDefaultValue;
|
|
||||||
}
|
|
||||||
|
|
||||||
public class NubSliceType(NubType elementType) : NubType
|
|
||||||
{
|
|
||||||
public NubType ElementType { get; } = elementType;
|
|
||||||
|
|
||||||
public override ulong GetSize() => 16; // note(nub31): Fat pointer
|
|
||||||
public override ulong GetAlignment() => 8;
|
|
||||||
public override bool IsAggregate() => true;
|
|
||||||
|
|
||||||
public override string ToString() => "[]" + ElementType;
|
|
||||||
public override bool Equals(NubType? other) => other is NubSliceType slice && ElementType.Equals(slice.ElementType);
|
|
||||||
public override int GetHashCode() => HashCode.Combine(typeof(NubSliceType), ElementType);
|
|
||||||
}
|
|
||||||
|
|
||||||
public class NubConstArrayType(NubType elementType, ulong size) : NubType
|
|
||||||
{
|
|
||||||
public NubType ElementType { get; } = elementType;
|
|
||||||
public ulong Size { get; } = size;
|
|
||||||
|
|
||||||
public override ulong GetSize() => ElementType.GetSize() * Size;
|
|
||||||
public override ulong GetAlignment() => ElementType.GetAlignment();
|
|
||||||
public override bool IsAggregate() => true;
|
|
||||||
|
|
||||||
public override string ToString() => $"[{Size}]{ElementType}";
|
|
||||||
public override bool Equals(NubType? other) => other is NubConstArrayType array && ElementType.Equals(array.ElementType) && Size == array.Size;
|
|
||||||
public override int GetHashCode() => HashCode.Combine(typeof(NubConstArrayType), ElementType, Size);
|
|
||||||
}
|
|
||||||
|
|
||||||
public class NubArrayType(NubType elementType) : NubType
|
|
||||||
{
|
|
||||||
public NubType ElementType { get; } = elementType;
|
|
||||||
|
|
||||||
public override ulong GetSize() => 8;
|
|
||||||
public override ulong GetAlignment() => 8;
|
|
||||||
public override bool IsAggregate() => false; // note(nub31): Just a pointer
|
|
||||||
|
|
||||||
public override string ToString() => $"[?]{ElementType}";
|
|
||||||
public override bool Equals(NubType? other) => other is NubArrayType array && ElementType.Equals(array.ElementType);
|
|
||||||
public override int GetHashCode() => HashCode.Combine(typeof(NubArrayType), ElementType);
|
|
||||||
}
|
|
||||||
|
|
||||||
public class NubStringType : NubType
|
|
||||||
{
|
|
||||||
public override ulong GetSize() => 16; // note(nub31): Fat pointer
|
|
||||||
public override ulong GetAlignment() => 8;
|
|
||||||
public override bool IsAggregate() => true;
|
|
||||||
|
|
||||||
public override string ToString() => "string";
|
|
||||||
public override bool Equals(NubType? other) => other is NubStringType;
|
|
||||||
public override int GetHashCode() => HashCode.Combine(typeof(NubStringType));
|
|
||||||
}
|
|
||||||
|
|
||||||
public static class NameMangler
|
|
||||||
{
|
|
||||||
public static string Mangle(params IEnumerable<NubType> types)
|
|
||||||
{
|
|
||||||
var readable = string.Join(":", types.Select(EncodeType));
|
|
||||||
return ComputeShortHash(readable);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static string EncodeType(NubType node) => node switch
|
|
||||||
{
|
|
||||||
NubVoidType => "V",
|
|
||||||
NubBoolType => "B",
|
|
||||||
NubIntType i => (i.Signed ? "I" : "U") + i.Width,
|
|
||||||
NubFloatType f => "F" + f.Width,
|
|
||||||
NubStringType => "S",
|
|
||||||
NubArrayType a => $"A({EncodeType(a.ElementType)})",
|
|
||||||
NubConstArrayType ca => $"CA({EncodeType(ca.ElementType)})",
|
|
||||||
NubSliceType a => $"SL{EncodeType(a.ElementType)}()",
|
|
||||||
NubPointerType p => $"P({EncodeType(p.BaseType)})",
|
|
||||||
NubFuncType fn => $"FN({string.Join(":", fn.Parameters.Select(EncodeType))}:{EncodeType(fn.ReturnType)})",
|
|
||||||
NubStructType st => $"ST({st.Module}:{st.Name})",
|
|
||||||
_ => throw new NotSupportedException($"Cannot encode type: {node}")
|
|
||||||
};
|
|
||||||
|
|
||||||
private static string ComputeShortHash(string input)
|
|
||||||
{
|
|
||||||
var bytes = Encoding.UTF8.GetBytes(input);
|
|
||||||
var hash = SHA256.HashData(bytes);
|
|
||||||
return Convert.ToHexString(hash[..8]).ToLower();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,31 +0,0 @@
|
|||||||
namespace NubLang;
|
|
||||||
|
|
||||||
public static class Utils
|
|
||||||
{
|
|
||||||
public static int LevenshteinDistance(string a, string b)
|
|
||||||
{
|
|
||||||
if (a == b) return 0;
|
|
||||||
if (a.Length == 0) return b.Length;
|
|
||||||
if (b.Length == 0) return a.Length;
|
|
||||||
|
|
||||||
var costs = new int[b.Length + 1];
|
|
||||||
for (int j = 0; j <= b.Length; j++) costs[j] = j;
|
|
||||||
|
|
||||||
for (int i = 1; i <= a.Length; i++)
|
|
||||||
{
|
|
||||||
costs[0] = i;
|
|
||||||
int prevCost = i - 1;
|
|
||||||
for (int j = 1; j <= b.Length; j++)
|
|
||||||
{
|
|
||||||
int currentCost = costs[j];
|
|
||||||
costs[j] = Math.Min(
|
|
||||||
Math.Min(costs[j - 1] + 1, costs[j] + 1),
|
|
||||||
prevCost + (a[i - 1] == b[j - 1] ? 0 : 1)
|
|
||||||
);
|
|
||||||
prevCost = currentCost;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return costs[b.Length];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,75 +0,0 @@
|
|||||||
using System.Diagnostics.CodeAnalysis;
|
|
||||||
|
|
||||||
namespace NubLang;
|
|
||||||
|
|
||||||
public readonly struct Variant<T1, T2> where T1 : notnull where T2 : notnull
|
|
||||||
{
|
|
||||||
public Variant()
|
|
||||||
{
|
|
||||||
throw new InvalidOperationException("Variant must be initialized with a value");
|
|
||||||
}
|
|
||||||
|
|
||||||
public Variant(T1 value)
|
|
||||||
{
|
|
||||||
_value = value;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Variant(T2 value)
|
|
||||||
{
|
|
||||||
_value = value;
|
|
||||||
}
|
|
||||||
|
|
||||||
private readonly object _value;
|
|
||||||
|
|
||||||
public void Match(Action<T1> on1, Action<T2> on2)
|
|
||||||
{
|
|
||||||
switch (_value)
|
|
||||||
{
|
|
||||||
case T1 v1:
|
|
||||||
on1(v1);
|
|
||||||
break;
|
|
||||||
case T2 v2:
|
|
||||||
on2(v2);
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
throw new InvalidCastException();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public T Match<T>(Func<T1, T> on1, Func<T2, T> on2)
|
|
||||||
{
|
|
||||||
return _value switch
|
|
||||||
{
|
|
||||||
T1 v1 => on1(v1),
|
|
||||||
T2 v2 => on2(v2),
|
|
||||||
_ => throw new InvalidCastException()
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
public bool IsCase1([NotNullWhen(true)] out T1? value)
|
|
||||||
{
|
|
||||||
if (_value is T1 converted)
|
|
||||||
{
|
|
||||||
value = converted;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
value = default;
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
public bool IsCase2([NotNullWhen(true)] out T2? value)
|
|
||||||
{
|
|
||||||
if (_value is T2 converted)
|
|
||||||
{
|
|
||||||
value = converted;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
value = default;
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static implicit operator Variant<T1, T2>(T1 value) => new(value);
|
|
||||||
public static implicit operator Variant<T1, T2>(T2 value) => new(value);
|
|
||||||
}
|
|
||||||
110
compiler/NubLib.cs
Normal file
110
compiler/NubLib.cs
Normal file
@@ -0,0 +1,110 @@
|
|||||||
|
using System.IO.Compression;
|
||||||
|
using System.Text.Json;
|
||||||
|
using System.Text.Json.Serialization;
|
||||||
|
|
||||||
|
namespace Compiler;
|
||||||
|
|
||||||
|
public class NubLib
|
||||||
|
{
|
||||||
|
private static readonly JsonSerializerOptions JsonOptions = new()
|
||||||
|
{
|
||||||
|
PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
|
||||||
|
WriteIndented = true,
|
||||||
|
};
|
||||||
|
|
||||||
|
public static void Pack(string outputPath, string archivePath, Manifest manifest)
|
||||||
|
{
|
||||||
|
using var fs = new FileStream(outputPath, FileMode.Create);
|
||||||
|
using var zip = new ZipArchive(fs, ZipArchiveMode.Create);
|
||||||
|
|
||||||
|
var manifestEntry = zip.CreateEntry("manifest.json");
|
||||||
|
using (var writer = new StreamWriter(manifestEntry.Open()))
|
||||||
|
{
|
||||||
|
var serialized = JsonSerializer.Serialize(manifest, JsonOptions);
|
||||||
|
|
||||||
|
writer.Write(serialized);
|
||||||
|
}
|
||||||
|
|
||||||
|
var archiveEntry = zip.CreateEntry("lib.a");
|
||||||
|
|
||||||
|
using var entryStream = archiveEntry.Open();
|
||||||
|
using var fileStream = File.OpenRead(archivePath);
|
||||||
|
|
||||||
|
fileStream.CopyTo(entryStream);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static NubLibLoadResult Unpack(string nublibPath)
|
||||||
|
{
|
||||||
|
using var fs = new FileStream(nublibPath, FileMode.Open, FileAccess.Read);
|
||||||
|
using var zip = new ZipArchive(fs, ZipArchiveMode.Read);
|
||||||
|
|
||||||
|
var manifestEntry = zip.GetEntry("manifest.json") ?? throw new FileNotFoundException("Manifest not found in nublib", "manifest.json");
|
||||||
|
|
||||||
|
Manifest manifest;
|
||||||
|
using (var reader = new StreamReader(manifestEntry.Open()))
|
||||||
|
{
|
||||||
|
var json = reader.ReadToEnd();
|
||||||
|
manifest = JsonSerializer.Deserialize<Manifest>(json, JsonOptions) ?? throw new InvalidDataException("Failed to deserialize manifest.json");
|
||||||
|
}
|
||||||
|
|
||||||
|
var archiveEntry = zip.Entries.FirstOrDefault(e => e.Name.EndsWith(".a")) ?? throw new FileNotFoundException("Archive not found in nublib", "*.a");
|
||||||
|
|
||||||
|
string tempArchivePath = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString("N") + ".a");
|
||||||
|
using (var entryStream = archiveEntry.Open())
|
||||||
|
using (var tempFile = File.Create(tempArchivePath))
|
||||||
|
{
|
||||||
|
entryStream.CopyTo(tempFile);
|
||||||
|
}
|
||||||
|
|
||||||
|
return new NubLibLoadResult(manifest, tempArchivePath);
|
||||||
|
}
|
||||||
|
|
||||||
|
public record NubLibLoadResult(Manifest Manifest, string ArchivePath);
|
||||||
|
}
|
||||||
|
|
||||||
|
public record Manifest(Dictionary<string, Manifest.Module> Modules)
|
||||||
|
{
|
||||||
|
public static Manifest Create(ModuleGraph moduleGraph)
|
||||||
|
{
|
||||||
|
var modules = new Dictionary<string, Module>();
|
||||||
|
|
||||||
|
foreach (var module in moduleGraph.GetModules())
|
||||||
|
{
|
||||||
|
var types = module.GetTypes().ToDictionary(x => x.Key, x => ConvertType(x.Value));
|
||||||
|
var identifiers = module.GetIdentifiers().ToDictionary(x => x.Key, x => new Module.IdentifierInfo(x.Value.Type, x.Value.Exported, x.Value.Extern, x.Value.MangledName));
|
||||||
|
modules[module.Name] = new Module(types, identifiers);
|
||||||
|
}
|
||||||
|
|
||||||
|
return new Manifest(modules);
|
||||||
|
|
||||||
|
static Module.TypeInfo ConvertType(Compiler.Module.TypeInfo typeInfo)
|
||||||
|
{
|
||||||
|
return typeInfo switch
|
||||||
|
{
|
||||||
|
Compiler.Module.TypeInfoStruct s => new Module.TypeInfoStruct(s.Exported, s.Packed, s.Fields.Select(x => new Module.TypeInfoStruct.Field(x.Name, x.Type)).ToList()),
|
||||||
|
Compiler.Module.TypeInfoEnum e => new Module.TypeInfoEnum(e.Exported, e.Variants.Select(v => new Module.TypeInfoEnum.Variant(v.Name, v.Type)).ToList()),
|
||||||
|
_ => throw new ArgumentOutOfRangeException(nameof(typeInfo))
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public record Module(Dictionary<string, Module.TypeInfo> Types, Dictionary<string, Module.IdentifierInfo> Identifiers)
|
||||||
|
{
|
||||||
|
public record IdentifierInfo(NubType Type, bool Exported, bool Extern, string MangledName);
|
||||||
|
|
||||||
|
[JsonDerivedType(typeof(TypeInfoStruct), "struct")]
|
||||||
|
[JsonDerivedType(typeof(TypeInfoEnum), "enum")]
|
||||||
|
public abstract record TypeInfo(bool Exported);
|
||||||
|
|
||||||
|
public record TypeInfoStruct(bool Exported, bool Packed, IReadOnlyList<TypeInfoStruct.Field> Fields) : TypeInfo(Exported)
|
||||||
|
{
|
||||||
|
public record Field(string Name, NubType Type);
|
||||||
|
}
|
||||||
|
|
||||||
|
public record TypeInfoEnum(bool Exported, IReadOnlyList<TypeInfoEnum.Variant> Variants) : TypeInfo(Exported)
|
||||||
|
{
|
||||||
|
public record Variant(string Name, NubType? Type);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
708
compiler/NubType.cs
Normal file
708
compiler/NubType.cs
Normal file
@@ -0,0 +1,708 @@
|
|||||||
|
using System.Text;
|
||||||
|
using System.Text.Json;
|
||||||
|
using System.Text.Json.Serialization;
|
||||||
|
|
||||||
|
namespace Compiler;
|
||||||
|
|
||||||
|
[JsonConverter(typeof(NubTypeJsonConverter))]
|
||||||
|
public abstract class NubType
|
||||||
|
{
|
||||||
|
public abstract override string ToString();
|
||||||
|
|
||||||
|
[Obsolete("Use IsAssignableTo instead of ==", error: true)]
|
||||||
|
public static bool operator ==(NubType? a, NubType? b) => throw new InvalidOperationException("Use IsAssignableTo");
|
||||||
|
|
||||||
|
[Obsolete("Use IsAssignableTo instead of ==", error: true)]
|
||||||
|
public static bool operator !=(NubType? a, NubType? b) => throw new InvalidOperationException("Use IsAssignableTo");
|
||||||
|
|
||||||
|
public bool IsAssignableTo(NubType target)
|
||||||
|
{
|
||||||
|
return (this, target) switch
|
||||||
|
{
|
||||||
|
(NubTypeEnumVariant variant, NubTypeEnum targetEnum) => ReferenceEquals(variant.EnumType, targetEnum),
|
||||||
|
_ => ReferenceEquals(this, target),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class NubTypeVoid : NubType
|
||||||
|
{
|
||||||
|
public static readonly NubTypeVoid Instance = new();
|
||||||
|
|
||||||
|
private NubTypeVoid()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public override string ToString() => "void";
|
||||||
|
}
|
||||||
|
|
||||||
|
public class NubTypeUInt : NubType
|
||||||
|
{
|
||||||
|
private static readonly Dictionary<int, NubTypeUInt> Cache = new();
|
||||||
|
|
||||||
|
public static NubTypeUInt Get(int width)
|
||||||
|
{
|
||||||
|
if (!Cache.TryGetValue(width, out var type))
|
||||||
|
Cache[width] = type = new NubTypeUInt(width);
|
||||||
|
|
||||||
|
return type;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int Width { get; }
|
||||||
|
|
||||||
|
private NubTypeUInt(int width)
|
||||||
|
{
|
||||||
|
Width = width;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override string ToString() => $"u{Width}";
|
||||||
|
}
|
||||||
|
|
||||||
|
public class NubTypeSInt : NubType
|
||||||
|
{
|
||||||
|
private static readonly Dictionary<int, NubTypeSInt> Cache = new();
|
||||||
|
|
||||||
|
public static NubTypeSInt Get(int width)
|
||||||
|
{
|
||||||
|
if (!Cache.TryGetValue(width, out var type))
|
||||||
|
Cache[width] = type = new NubTypeSInt(width);
|
||||||
|
|
||||||
|
return type;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int Width { get; }
|
||||||
|
|
||||||
|
private NubTypeSInt(int width)
|
||||||
|
{
|
||||||
|
Width = width;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override string ToString() => $"i{Width}";
|
||||||
|
}
|
||||||
|
|
||||||
|
public class NubTypeBool : NubType
|
||||||
|
{
|
||||||
|
public static readonly NubTypeBool Instance = new();
|
||||||
|
|
||||||
|
private NubTypeBool()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public override string ToString() => "bool";
|
||||||
|
}
|
||||||
|
|
||||||
|
public class NubTypeString : NubType
|
||||||
|
{
|
||||||
|
public static readonly NubTypeString Instance = new();
|
||||||
|
|
||||||
|
private NubTypeString()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public override string ToString() => "string";
|
||||||
|
}
|
||||||
|
|
||||||
|
public class NubTypeChar : NubType
|
||||||
|
{
|
||||||
|
public static readonly NubTypeChar Instance = new();
|
||||||
|
|
||||||
|
private NubTypeChar()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public override string ToString() => "char";
|
||||||
|
}
|
||||||
|
|
||||||
|
public class NubTypeStruct : NubType
|
||||||
|
{
|
||||||
|
private static readonly Dictionary<(string Module, string Name), NubTypeStruct> Cache = new();
|
||||||
|
|
||||||
|
public static NubTypeStruct Get(string module, string name)
|
||||||
|
{
|
||||||
|
if (!Cache.TryGetValue((module, name), out var structType))
|
||||||
|
Cache[(module, name)] = structType = new NubTypeStruct(module, name);
|
||||||
|
|
||||||
|
return structType;
|
||||||
|
}
|
||||||
|
|
||||||
|
private NubTypeStruct(string module, string name)
|
||||||
|
{
|
||||||
|
Module = module;
|
||||||
|
Name = name;
|
||||||
|
}
|
||||||
|
|
||||||
|
public string Module { get; }
|
||||||
|
public string Name { get; }
|
||||||
|
|
||||||
|
public override string ToString() => $"{Module}::{Name}";
|
||||||
|
}
|
||||||
|
|
||||||
|
public class NubTypeAnonymousStruct : NubType
|
||||||
|
{
|
||||||
|
private static readonly Dictionary<Signature, NubTypeAnonymousStruct> Cache = new();
|
||||||
|
|
||||||
|
public static NubTypeAnonymousStruct Get(List<Field> fields)
|
||||||
|
{
|
||||||
|
var sig = new Signature(fields);
|
||||||
|
|
||||||
|
if (!Cache.TryGetValue(sig, out var func))
|
||||||
|
Cache[sig] = func = new NubTypeAnonymousStruct(fields);
|
||||||
|
|
||||||
|
return func;
|
||||||
|
}
|
||||||
|
|
||||||
|
private NubTypeAnonymousStruct(IReadOnlyList<Field> fields)
|
||||||
|
{
|
||||||
|
Fields = fields;
|
||||||
|
}
|
||||||
|
|
||||||
|
public IReadOnlyList<Field> Fields { get; }
|
||||||
|
|
||||||
|
public override string ToString() => $"{{ {string.Join(", ", Fields.Select(x => $"{x.Name}: {x.Type}"))} }}";
|
||||||
|
|
||||||
|
public class Field(string name, NubType type)
|
||||||
|
{
|
||||||
|
public string Name { get; } = name;
|
||||||
|
public NubType Type { get; } = type;
|
||||||
|
}
|
||||||
|
|
||||||
|
private record Signature(IReadOnlyList<Field> Fields);
|
||||||
|
}
|
||||||
|
|
||||||
|
public class NubTypeEnum : NubType
|
||||||
|
{
|
||||||
|
private static readonly Dictionary<(string Module, string Name), NubTypeEnum> Cache = new();
|
||||||
|
|
||||||
|
public static NubTypeEnum Get(string module, string name)
|
||||||
|
{
|
||||||
|
if (!Cache.TryGetValue((module, name), out var enumType))
|
||||||
|
Cache[(module, name)] = enumType = new NubTypeEnum(module, name);
|
||||||
|
|
||||||
|
return enumType;
|
||||||
|
}
|
||||||
|
|
||||||
|
private NubTypeEnum(string module, string name)
|
||||||
|
{
|
||||||
|
Module = module;
|
||||||
|
Name = name;
|
||||||
|
}
|
||||||
|
|
||||||
|
public string Module { get; }
|
||||||
|
public string Name { get; }
|
||||||
|
|
||||||
|
public override string ToString() => $"{Module}::{Name}";
|
||||||
|
}
|
||||||
|
|
||||||
|
public class NubTypeEnumVariant : NubType
|
||||||
|
{
|
||||||
|
private static readonly Dictionary<(NubTypeEnum EnumType, string Variant), NubTypeEnumVariant> Cache = new();
|
||||||
|
|
||||||
|
public static NubTypeEnumVariant Get(NubTypeEnum enumType, string variant)
|
||||||
|
{
|
||||||
|
if (!Cache.TryGetValue((enumType, variant), out var variantType))
|
||||||
|
Cache[(enumType, variant)] = variantType = new NubTypeEnumVariant(enumType, variant);
|
||||||
|
|
||||||
|
return variantType;
|
||||||
|
}
|
||||||
|
|
||||||
|
private NubTypeEnumVariant(NubTypeEnum enumType, string variant)
|
||||||
|
{
|
||||||
|
EnumType = enumType;
|
||||||
|
Variant = variant;
|
||||||
|
}
|
||||||
|
|
||||||
|
public NubTypeEnum EnumType { get; }
|
||||||
|
public string Variant { get; }
|
||||||
|
|
||||||
|
public override string ToString() => $"{EnumType}.{Variant}";
|
||||||
|
}
|
||||||
|
|
||||||
|
public class NubTypePointer : NubType
|
||||||
|
{
|
||||||
|
private static readonly Dictionary<NubType, NubTypePointer> Cache = new();
|
||||||
|
|
||||||
|
public static NubTypePointer Get(NubType to)
|
||||||
|
{
|
||||||
|
if (!Cache.TryGetValue(to, out var ptr))
|
||||||
|
Cache[to] = ptr = new NubTypePointer(to);
|
||||||
|
|
||||||
|
return ptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
public NubType To { get; }
|
||||||
|
|
||||||
|
private NubTypePointer(NubType to)
|
||||||
|
{
|
||||||
|
To = to;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override string ToString() => $"^{To}";
|
||||||
|
}
|
||||||
|
|
||||||
|
public class NubTypeFunc : NubType
|
||||||
|
{
|
||||||
|
private static readonly Dictionary<Signature, NubTypeFunc> Cache = new();
|
||||||
|
|
||||||
|
public static NubTypeFunc Get(List<NubType> parameters, NubType returnType)
|
||||||
|
{
|
||||||
|
var sig = new Signature(parameters, returnType);
|
||||||
|
|
||||||
|
if (!Cache.TryGetValue(sig, out var func))
|
||||||
|
Cache[sig] = func = new NubTypeFunc(parameters, returnType);
|
||||||
|
|
||||||
|
return func;
|
||||||
|
}
|
||||||
|
|
||||||
|
public IReadOnlyList<NubType> Parameters { get; }
|
||||||
|
public NubType ReturnType { get; }
|
||||||
|
|
||||||
|
private NubTypeFunc(List<NubType> parameters, NubType returnType)
|
||||||
|
{
|
||||||
|
Parameters = parameters;
|
||||||
|
ReturnType = returnType;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override string ToString() => $"func({string.Join(' ', Parameters)}): {ReturnType}";
|
||||||
|
|
||||||
|
private record Signature(IReadOnlyList<NubType> Parameters, NubType ReturnType);
|
||||||
|
}
|
||||||
|
|
||||||
|
public class NubTypeArray : NubType
|
||||||
|
{
|
||||||
|
private static readonly Dictionary<NubType, NubTypeArray> Cache = new();
|
||||||
|
|
||||||
|
public static NubTypeArray Get(NubType to)
|
||||||
|
{
|
||||||
|
if (!Cache.TryGetValue(to, out var ptr))
|
||||||
|
Cache[to] = ptr = new NubTypeArray(to);
|
||||||
|
|
||||||
|
return ptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
public NubType ElementType { get; }
|
||||||
|
|
||||||
|
private NubTypeArray(NubType elementType)
|
||||||
|
{
|
||||||
|
ElementType = elementType;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override string ToString() => $"[]{ElementType}";
|
||||||
|
}
|
||||||
|
|
||||||
|
public class TypeEncoder
|
||||||
|
{
|
||||||
|
public static string Encode(NubType type)
|
||||||
|
{
|
||||||
|
return new TypeEncoder().EncodeRoot(type);
|
||||||
|
}
|
||||||
|
|
||||||
|
private TypeEncoder()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
private string EncodeRoot(NubType type)
|
||||||
|
{
|
||||||
|
var sb = new StringBuilder();
|
||||||
|
EncodeType(sb, type);
|
||||||
|
return sb.ToString();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void EncodeType(StringBuilder sb, NubType type)
|
||||||
|
{
|
||||||
|
switch (type)
|
||||||
|
{
|
||||||
|
case NubTypeVoid:
|
||||||
|
sb.Append('V');
|
||||||
|
break;
|
||||||
|
|
||||||
|
case NubTypeBool:
|
||||||
|
sb.Append('B');
|
||||||
|
break;
|
||||||
|
|
||||||
|
case NubTypeUInt u:
|
||||||
|
sb.Append($"U(");
|
||||||
|
sb.Append(u.Width);
|
||||||
|
sb.Append(')');
|
||||||
|
break;
|
||||||
|
|
||||||
|
case NubTypeSInt s:
|
||||||
|
sb.Append($"I(");
|
||||||
|
sb.Append(s.Width);
|
||||||
|
sb.Append(')');
|
||||||
|
break;
|
||||||
|
|
||||||
|
case NubTypeString:
|
||||||
|
sb.Append('S');
|
||||||
|
break;
|
||||||
|
|
||||||
|
case NubTypeChar:
|
||||||
|
sb.Append('C');
|
||||||
|
break;
|
||||||
|
|
||||||
|
case NubTypePointer p:
|
||||||
|
sb.Append("P(");
|
||||||
|
EncodeType(sb, p.To);
|
||||||
|
sb.Append(')');
|
||||||
|
break;
|
||||||
|
|
||||||
|
case NubTypeStruct st:
|
||||||
|
sb.Append("TN(");
|
||||||
|
sb.Append(st.Module);
|
||||||
|
sb.Append(':');
|
||||||
|
sb.Append(st.Name);
|
||||||
|
sb.Append(')');
|
||||||
|
break;
|
||||||
|
|
||||||
|
case NubTypeEnum e:
|
||||||
|
sb.Append("EN(");
|
||||||
|
sb.Append(e.Module);
|
||||||
|
sb.Append(':');
|
||||||
|
sb.Append(e.Name);
|
||||||
|
sb.Append(')');
|
||||||
|
break;
|
||||||
|
|
||||||
|
case NubTypeEnumVariant ev:
|
||||||
|
sb.Append("EV(");
|
||||||
|
sb.Append(ev.EnumType.Module);
|
||||||
|
sb.Append(':');
|
||||||
|
sb.Append(ev.EnumType.Name);
|
||||||
|
sb.Append(':');
|
||||||
|
sb.Append(ev.Variant);
|
||||||
|
sb.Append(')');
|
||||||
|
break;
|
||||||
|
|
||||||
|
case NubTypeFunc fn:
|
||||||
|
sb.Append("F(");
|
||||||
|
for (int i = 0; i < fn.Parameters.Count; i++)
|
||||||
|
{
|
||||||
|
EncodeType(sb, fn.Parameters[i]);
|
||||||
|
}
|
||||||
|
EncodeType(sb, fn.ReturnType);
|
||||||
|
sb.Append(')');
|
||||||
|
break;
|
||||||
|
|
||||||
|
case NubTypeAnonymousStruct s:
|
||||||
|
sb.Append("TA(");
|
||||||
|
foreach (var field in s.Fields)
|
||||||
|
{
|
||||||
|
sb.Append(field.Name);
|
||||||
|
sb.Append(':');
|
||||||
|
EncodeType(sb, field.Type);
|
||||||
|
}
|
||||||
|
sb.Append(')');
|
||||||
|
break;
|
||||||
|
|
||||||
|
case NubTypeArray a:
|
||||||
|
sb.Append("A(");
|
||||||
|
EncodeType(sb, a.ElementType);
|
||||||
|
sb.Append(')');
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
throw new NotSupportedException(type.GetType().Name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private class Definition(int index)
|
||||||
|
{
|
||||||
|
public int Index { get; } = index;
|
||||||
|
public string? Encoded { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class TypeDecoder
|
||||||
|
{
|
||||||
|
public static NubType Decode(string encoded)
|
||||||
|
{
|
||||||
|
return new TypeDecoder(encoded).DecodeType();
|
||||||
|
}
|
||||||
|
|
||||||
|
private TypeDecoder(string encoded)
|
||||||
|
{
|
||||||
|
this.encoded = encoded;
|
||||||
|
}
|
||||||
|
|
||||||
|
private readonly string encoded;
|
||||||
|
private int index = 0;
|
||||||
|
|
||||||
|
private NubType DecodeType()
|
||||||
|
{
|
||||||
|
var start = Consume();
|
||||||
|
return start switch
|
||||||
|
{
|
||||||
|
'V' => NubTypeVoid.Instance,
|
||||||
|
'B' => NubTypeBool.Instance,
|
||||||
|
'U' => DecodeUInt(),
|
||||||
|
'I' => DecodeSInt(),
|
||||||
|
'S' => NubTypeString.Instance,
|
||||||
|
'C' => NubTypeChar.Instance,
|
||||||
|
'P' => DecodePointer(),
|
||||||
|
'F' => DecodeFunc(),
|
||||||
|
'T' => DecodeStruct(),
|
||||||
|
'E' => DecodeEnum(),
|
||||||
|
'A' => DecodeArray(),
|
||||||
|
_ => throw new Exception($"'{start}' is not a valid start to a type")
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private NubTypeUInt DecodeUInt()
|
||||||
|
{
|
||||||
|
Expect('(');
|
||||||
|
var width = ExpectInt();
|
||||||
|
Expect(')');
|
||||||
|
return NubTypeUInt.Get(width);
|
||||||
|
}
|
||||||
|
|
||||||
|
private NubTypeSInt DecodeSInt()
|
||||||
|
{
|
||||||
|
Expect('(');
|
||||||
|
var width = ExpectInt();
|
||||||
|
Expect(')');
|
||||||
|
return NubTypeSInt.Get(width);
|
||||||
|
}
|
||||||
|
|
||||||
|
private NubTypePointer DecodePointer()
|
||||||
|
{
|
||||||
|
Expect('(');
|
||||||
|
var to = DecodeType();
|
||||||
|
Expect(')');
|
||||||
|
return NubTypePointer.Get(to);
|
||||||
|
}
|
||||||
|
|
||||||
|
private NubTypeFunc DecodeFunc()
|
||||||
|
{
|
||||||
|
var types = new List<NubType>();
|
||||||
|
|
||||||
|
Expect('(');
|
||||||
|
while (!TryExpect(')'))
|
||||||
|
{
|
||||||
|
types.Add(DecodeType());
|
||||||
|
}
|
||||||
|
|
||||||
|
return NubTypeFunc.Get(types.Take(types.Count - 1).ToList(), types.Last());
|
||||||
|
}
|
||||||
|
|
||||||
|
private NubType DecodeStruct()
|
||||||
|
{
|
||||||
|
|
||||||
|
if (TryExpect('A'))
|
||||||
|
{
|
||||||
|
var sb = new StringBuilder();
|
||||||
|
var fields = new List<NubTypeAnonymousStruct.Field>();
|
||||||
|
|
||||||
|
Expect('(');
|
||||||
|
while (!TryExpect(')'))
|
||||||
|
{
|
||||||
|
while (!TryExpect(':'))
|
||||||
|
{
|
||||||
|
sb.Append(Consume());
|
||||||
|
}
|
||||||
|
|
||||||
|
var name = sb.ToString();
|
||||||
|
sb.Clear();
|
||||||
|
|
||||||
|
var type = DecodeType();
|
||||||
|
|
||||||
|
fields.Add(new NubTypeAnonymousStruct.Field(name, type));
|
||||||
|
}
|
||||||
|
|
||||||
|
return NubTypeAnonymousStruct.Get(fields);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (TryExpect('N'))
|
||||||
|
{
|
||||||
|
var sb = new StringBuilder();
|
||||||
|
|
||||||
|
Expect('(');
|
||||||
|
while (!TryExpect(':'))
|
||||||
|
sb.Append(Consume());
|
||||||
|
|
||||||
|
var module = sb.ToString();
|
||||||
|
sb.Clear();
|
||||||
|
|
||||||
|
while (!TryExpect(')'))
|
||||||
|
sb.Append(Consume());
|
||||||
|
|
||||||
|
var name = sb.ToString();
|
||||||
|
|
||||||
|
return NubTypeStruct.Get(module, name);
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new Exception("Expected 'A' or 'N'");
|
||||||
|
}
|
||||||
|
|
||||||
|
private NubType DecodeEnum()
|
||||||
|
{
|
||||||
|
var sb = new StringBuilder();
|
||||||
|
|
||||||
|
if (TryExpect('V'))
|
||||||
|
{
|
||||||
|
Expect('(');
|
||||||
|
while (!TryExpect(':'))
|
||||||
|
sb.Append(Consume());
|
||||||
|
|
||||||
|
var module = sb.ToString();
|
||||||
|
sb.Clear();
|
||||||
|
|
||||||
|
while (!TryExpect(':'))
|
||||||
|
sb.Append(Consume());
|
||||||
|
|
||||||
|
var name = sb.ToString();
|
||||||
|
|
||||||
|
while (!TryExpect(')'))
|
||||||
|
sb.Append(Consume());
|
||||||
|
|
||||||
|
var variant = sb.ToString();
|
||||||
|
|
||||||
|
return NubTypeEnumVariant.Get(NubTypeEnum.Get(module, name), variant);
|
||||||
|
}
|
||||||
|
else if (TryExpect('N'))
|
||||||
|
{
|
||||||
|
Expect('(');
|
||||||
|
while (!TryExpect(':'))
|
||||||
|
sb.Append(Consume());
|
||||||
|
|
||||||
|
var module = sb.ToString();
|
||||||
|
sb.Clear();
|
||||||
|
|
||||||
|
while (!TryExpect(')'))
|
||||||
|
sb.Append(Consume());
|
||||||
|
|
||||||
|
var name = sb.ToString();
|
||||||
|
|
||||||
|
return NubTypeEnum.Get(module, name);
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new Exception($"Expected 'V' or 'N'");
|
||||||
|
}
|
||||||
|
|
||||||
|
private NubTypeArray DecodeArray()
|
||||||
|
{
|
||||||
|
Expect('(');
|
||||||
|
var elementType = DecodeType();
|
||||||
|
Expect(')');
|
||||||
|
return NubTypeArray.Get(elementType);
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool TryPeek(out char c)
|
||||||
|
{
|
||||||
|
if (index >= encoded.Length)
|
||||||
|
{
|
||||||
|
c = '\0';
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
c = encoded[index];
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool TryConsume(out char c)
|
||||||
|
{
|
||||||
|
if (index >= encoded.Length)
|
||||||
|
{
|
||||||
|
c = '\0';
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
c = encoded[index];
|
||||||
|
index += 1;
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private char Consume()
|
||||||
|
{
|
||||||
|
if (!TryConsume(out var c))
|
||||||
|
throw new Exception("Unexpected end of string");
|
||||||
|
|
||||||
|
return c;
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool TryExpect(char c)
|
||||||
|
{
|
||||||
|
if (index >= encoded.Length)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
if (encoded[index] != c)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
Consume();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void Expect(char c)
|
||||||
|
{
|
||||||
|
if (!TryExpect(c))
|
||||||
|
throw new Exception($"Expected '{c}'");
|
||||||
|
}
|
||||||
|
|
||||||
|
private int ExpectInt()
|
||||||
|
{
|
||||||
|
var buf = string.Empty;
|
||||||
|
|
||||||
|
while (TryPeek(out var c))
|
||||||
|
{
|
||||||
|
if (!char.IsDigit(c))
|
||||||
|
break;
|
||||||
|
|
||||||
|
buf += Consume();
|
||||||
|
}
|
||||||
|
|
||||||
|
return int.Parse(buf);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class NubTypeJsonConverter : JsonConverter<NubType>
|
||||||
|
{
|
||||||
|
public override NubType Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
|
||||||
|
{
|
||||||
|
return TypeDecoder.Decode(reader.GetString()!);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void Write(Utf8JsonWriter writer, NubType value, JsonSerializerOptions options)
|
||||||
|
{
|
||||||
|
writer.WriteStringValue(TypeEncoder.Encode(value));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class Hashing
|
||||||
|
{
|
||||||
|
public static ulong Fnv1a64(string text)
|
||||||
|
{
|
||||||
|
const ulong offset = 14695981039346656037UL;
|
||||||
|
const ulong prime = 1099511628211UL;
|
||||||
|
|
||||||
|
ulong hash = offset;
|
||||||
|
foreach (var c in Encoding.UTF8.GetBytes(text))
|
||||||
|
{
|
||||||
|
hash ^= c;
|
||||||
|
hash *= prime;
|
||||||
|
}
|
||||||
|
|
||||||
|
return hash;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class NameMangler
|
||||||
|
{
|
||||||
|
public static string Mangle(string module, string name, NubType type)
|
||||||
|
{
|
||||||
|
var canonical = TypeEncoder.Encode(type);
|
||||||
|
var hash = Hashing.Fnv1a64(canonical);
|
||||||
|
|
||||||
|
return $"nub_{Sanitize(module)}_{Sanitize(name)}_{hash:x16}";
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string Sanitize(string s)
|
||||||
|
{
|
||||||
|
var sb = new StringBuilder(s.Length);
|
||||||
|
foreach (var c in s)
|
||||||
|
{
|
||||||
|
if (char.IsLetterOrDigit(c))
|
||||||
|
sb.Append(c);
|
||||||
|
else
|
||||||
|
sb.Append('_');
|
||||||
|
}
|
||||||
|
return sb.ToString();
|
||||||
|
}
|
||||||
|
}
|
||||||
1101
compiler/Parser.cs
Normal file
1101
compiler/Parser.cs
Normal file
File diff suppressed because it is too large
Load Diff
179
compiler/Program.cs
Normal file
179
compiler/Program.cs
Normal file
@@ -0,0 +1,179 @@
|
|||||||
|
using System.Diagnostics;
|
||||||
|
using Compiler;
|
||||||
|
|
||||||
|
var nubFiles = new List<string>();
|
||||||
|
var libFiles = new List<string>();
|
||||||
|
var compileLib = false;
|
||||||
|
|
||||||
|
for (int i = 0; i < args.Length; i++)
|
||||||
|
{
|
||||||
|
string arg = args[i];
|
||||||
|
|
||||||
|
if (arg.StartsWith("--type="))
|
||||||
|
{
|
||||||
|
var value = arg.Split("--type=")[1];
|
||||||
|
switch (value)
|
||||||
|
{
|
||||||
|
case "lib":
|
||||||
|
compileLib = true;
|
||||||
|
break;
|
||||||
|
case "exe":
|
||||||
|
compileLib = false;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
DiagnosticFormatter.Print(Diagnostic.Error("Type must be 'exe' or 'lib'").Build(), Console.Error);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (arg.EndsWith(".nub"))
|
||||||
|
{
|
||||||
|
nubFiles.Add(arg);
|
||||||
|
}
|
||||||
|
else if (arg.EndsWith(".nublib"))
|
||||||
|
{
|
||||||
|
libFiles.Add(arg);
|
||||||
|
}
|
||||||
|
else if (arg == "--help")
|
||||||
|
{
|
||||||
|
Console.WriteLine("""
|
||||||
|
Usage: nubc [options] <files>
|
||||||
|
|
||||||
|
Options:
|
||||||
|
--type=exe Compile the input files into an executable (default)
|
||||||
|
--type=lib Compile the input files into a library
|
||||||
|
--help Show this help message
|
||||||
|
|
||||||
|
Files:
|
||||||
|
*.nub Nub source files to compile
|
||||||
|
*.nublib Precompiled Nub libraries to link
|
||||||
|
|
||||||
|
Example:
|
||||||
|
nubc --type=exe main.nub utils.nub math.nublib
|
||||||
|
""");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
DiagnosticFormatter.Print(Diagnostic.Error($"Unrecognized option '{arg}'").Build(), Console.Error);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var moduleGraphBuilder = ModuleGraph.CreateBuilder();
|
||||||
|
var asts = new List<Ast>();
|
||||||
|
var archivePaths = new List<string>();
|
||||||
|
|
||||||
|
foreach (var libPath in libFiles)
|
||||||
|
{
|
||||||
|
var lib = NubLib.Unpack(libPath);
|
||||||
|
archivePaths.Add(lib.ArchivePath);
|
||||||
|
moduleGraphBuilder.AddManifest(lib.Manifest);
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var fileName in nubFiles)
|
||||||
|
{
|
||||||
|
var file = File.ReadAllText(fileName);
|
||||||
|
|
||||||
|
var tokens = Tokenizer.Tokenize(fileName, file, out var tokenizerDiagnostics);
|
||||||
|
|
||||||
|
foreach (var diagnostic in tokenizerDiagnostics)
|
||||||
|
DiagnosticFormatter.Print(diagnostic, Console.Error);
|
||||||
|
|
||||||
|
if (tokens == null)
|
||||||
|
return 1;
|
||||||
|
|
||||||
|
var ast = Parser.Parse(fileName, tokens, out var parserDiagnostics);
|
||||||
|
|
||||||
|
foreach (var diagnostic in parserDiagnostics)
|
||||||
|
DiagnosticFormatter.Print(diagnostic, Console.Error);
|
||||||
|
|
||||||
|
if (ast == null)
|
||||||
|
return 1;
|
||||||
|
|
||||||
|
moduleGraphBuilder.AddAst(ast);
|
||||||
|
asts.Add(ast);
|
||||||
|
}
|
||||||
|
|
||||||
|
var moduleGraph = moduleGraphBuilder.Build(out var moduleGraphDiagnostics);
|
||||||
|
|
||||||
|
foreach (var diagnostic in moduleGraphDiagnostics)
|
||||||
|
DiagnosticFormatter.Print(diagnostic, Console.Error);
|
||||||
|
|
||||||
|
if (moduleGraph == null)
|
||||||
|
return 1;
|
||||||
|
|
||||||
|
var functions = new List<TypedNodeDefinitionFunc>();
|
||||||
|
|
||||||
|
foreach (var ast in asts)
|
||||||
|
{
|
||||||
|
foreach (var func in ast.Definitions.OfType<NodeDefinitionFunc>())
|
||||||
|
{
|
||||||
|
var typedFunction = TypeChecker.CheckFunction(ast.FileName, ast.ModuleName.Ident, func, moduleGraph, out var typeCheckerDiagnostics);
|
||||||
|
|
||||||
|
foreach (var diagnostic in typeCheckerDiagnostics)
|
||||||
|
DiagnosticFormatter.Print(diagnostic, Console.Error);
|
||||||
|
|
||||||
|
if (typedFunction == null)
|
||||||
|
return 1;
|
||||||
|
|
||||||
|
functions.Add(typedFunction);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Directory.Exists(".build"))
|
||||||
|
{
|
||||||
|
CleanDirectory(".build");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Directory.CreateDirectory(".build");
|
||||||
|
}
|
||||||
|
|
||||||
|
string? entryPoint = null;
|
||||||
|
|
||||||
|
if (!compileLib)
|
||||||
|
{
|
||||||
|
if (!moduleGraph.TryResolveIdentifier("main", "main", true, out var info) || info.Type is not NubTypeFunc entryPointType)
|
||||||
|
{
|
||||||
|
DiagnosticFormatter.Print(Diagnostic.Error("func main::main(): i32 is not defined. If you wanted to compile as a library, specify --type=lib").Build(), Console.Error);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!entryPointType.ReturnType.IsAssignableTo(NubTypeSInt.Get(32)))
|
||||||
|
{
|
||||||
|
DiagnosticFormatter.Print(Diagnostic.Error($"Entrypoint must return an i32 (currently '{entryPointType.ReturnType}')").Build(), Console.Error);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
entryPoint = info.MangledName;
|
||||||
|
}
|
||||||
|
|
||||||
|
var outFile = Generator.Emit(functions, moduleGraph, entryPoint);
|
||||||
|
|
||||||
|
if (compileLib)
|
||||||
|
{
|
||||||
|
Process.Start("gcc", ["-Og", "-g", "-Wall", "-Werror", "-c", "-o", ".build/out.o", outFile, .. archivePaths]).WaitForExit();
|
||||||
|
Process.Start("ar", ["rcs", ".build/out.a", ".build/out.o"]).WaitForExit();
|
||||||
|
NubLib.Pack(".build/out.nublib", ".build/out.a", Manifest.Create(moduleGraph));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Process.Start("gcc", ["-Og", "-g", "-Wall", "-Werror", "-o", ".build/out", outFile, .. archivePaths]).WaitForExit();
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
static void CleanDirectory(string dirName)
|
||||||
|
{
|
||||||
|
var dir = new DirectoryInfo(dirName);
|
||||||
|
|
||||||
|
foreach (var file in dir.GetFiles())
|
||||||
|
{
|
||||||
|
file.Delete();
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var subdir in dir.GetDirectories())
|
||||||
|
{
|
||||||
|
CleanDirectory(subdir.FullName);
|
||||||
|
subdir.Delete();
|
||||||
|
}
|
||||||
|
}
|
||||||
643
compiler/Tokenizer.cs
Normal file
643
compiler/Tokenizer.cs
Normal file
@@ -0,0 +1,643 @@
|
|||||||
|
using System.Numerics;
|
||||||
|
using System.Text;
|
||||||
|
|
||||||
|
namespace Compiler;
|
||||||
|
|
||||||
|
public class Tokenizer
|
||||||
|
{
|
||||||
|
public static List<Token>? Tokenize(string fileName, string contents, out List<Diagnostic> diagnostics)
|
||||||
|
{
|
||||||
|
return new Tokenizer(fileName, contents).Tokenize(out diagnostics);
|
||||||
|
}
|
||||||
|
|
||||||
|
private Tokenizer(string fileName, string contents)
|
||||||
|
{
|
||||||
|
this.fileName = fileName;
|
||||||
|
this.contents = contents;
|
||||||
|
}
|
||||||
|
|
||||||
|
private readonly string fileName;
|
||||||
|
private readonly string contents;
|
||||||
|
private int index;
|
||||||
|
private int line = 1;
|
||||||
|
private int column = 1;
|
||||||
|
|
||||||
|
private List<Token>? Tokenize(out List<Diagnostic> diagnostics)
|
||||||
|
{
|
||||||
|
var tokens = new List<Token>();
|
||||||
|
diagnostics = [];
|
||||||
|
|
||||||
|
while (true)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (!TryPeek(out var c))
|
||||||
|
break;
|
||||||
|
|
||||||
|
if (char.IsWhiteSpace(c))
|
||||||
|
{
|
||||||
|
Consume();
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (c == '/' && Peek(1) == '/')
|
||||||
|
{
|
||||||
|
Consume();
|
||||||
|
Consume();
|
||||||
|
while (TryPeek(out c) && c != '\n')
|
||||||
|
Consume();
|
||||||
|
|
||||||
|
Consume();
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
tokens.Add(ParseToken());
|
||||||
|
}
|
||||||
|
catch (CompileException e)
|
||||||
|
{
|
||||||
|
diagnostics.Add(e.Diagnostic);
|
||||||
|
// Skip current token if parsing failed, this prevents an infinite loop when ParseToken fails before consuming any tokens
|
||||||
|
TryConsume(out _);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (diagnostics.Any(x => x.Severity == Diagnostic.DiagnosticSeverity.Error))
|
||||||
|
return null;
|
||||||
|
|
||||||
|
return tokens;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Token ParseToken()
|
||||||
|
{
|
||||||
|
var startColumn = column;
|
||||||
|
var c = Peek()!.Value;
|
||||||
|
|
||||||
|
if (char.IsDigit(c))
|
||||||
|
{
|
||||||
|
switch (c)
|
||||||
|
{
|
||||||
|
case '0' when Peek(1) is 'x':
|
||||||
|
{
|
||||||
|
Consume();
|
||||||
|
Consume();
|
||||||
|
|
||||||
|
var parsed = BigInteger.Zero;
|
||||||
|
var seenDigit = false;
|
||||||
|
|
||||||
|
while (TryPeek(out c))
|
||||||
|
{
|
||||||
|
if (c == '_')
|
||||||
|
{
|
||||||
|
Consume();
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!char.IsAsciiHexDigit(c))
|
||||||
|
break;
|
||||||
|
|
||||||
|
seenDigit = true;
|
||||||
|
parsed <<= 4;
|
||||||
|
|
||||||
|
Consume();
|
||||||
|
parsed += c switch
|
||||||
|
{
|
||||||
|
>= '0' and <= '9' => c - '0',
|
||||||
|
>= 'a' and <= 'f' => c - 'a' + 10,
|
||||||
|
>= 'A' and <= 'F' => c - 'A' + 10,
|
||||||
|
_ => 0
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!seenDigit)
|
||||||
|
throw new CompileException(Diagnostic.Error("Expected hexadecimal digits after 0x").At(fileName, line, startColumn, column - startColumn).Build());
|
||||||
|
|
||||||
|
return new TokenIntLiteral(line, startColumn, column - startColumn, parsed);
|
||||||
|
}
|
||||||
|
case '0' when Peek(1) is 'b':
|
||||||
|
{
|
||||||
|
Consume();
|
||||||
|
Consume();
|
||||||
|
|
||||||
|
var parsed = BigInteger.Zero;
|
||||||
|
var seenDigit = false;
|
||||||
|
|
||||||
|
while (TryPeek(out c))
|
||||||
|
{
|
||||||
|
if (c == '_')
|
||||||
|
{
|
||||||
|
Consume();
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (c is not '0' and not '1')
|
||||||
|
break;
|
||||||
|
|
||||||
|
seenDigit = true;
|
||||||
|
parsed <<= 1;
|
||||||
|
|
||||||
|
if (Consume() == '1')
|
||||||
|
parsed += BigInteger.One;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!seenDigit)
|
||||||
|
throw new CompileException(Diagnostic.Error("Expected binary digits after 0b").At(fileName, line, startColumn, column - startColumn).Build());
|
||||||
|
|
||||||
|
return new TokenIntLiteral(line, startColumn, column - startColumn, parsed);
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
{
|
||||||
|
var parsed = BigInteger.Zero;
|
||||||
|
|
||||||
|
while (TryPeek(out c))
|
||||||
|
{
|
||||||
|
if (c == '_')
|
||||||
|
{
|
||||||
|
Consume();
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!char.IsDigit(c))
|
||||||
|
break;
|
||||||
|
|
||||||
|
parsed *= 10;
|
||||||
|
parsed += Consume() - '0';
|
||||||
|
}
|
||||||
|
|
||||||
|
return new TokenIntLiteral(line, startColumn, column - startColumn, parsed);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (c)
|
||||||
|
{
|
||||||
|
case '"':
|
||||||
|
{
|
||||||
|
Consume();
|
||||||
|
var buf = new StringBuilder();
|
||||||
|
|
||||||
|
while (true)
|
||||||
|
{
|
||||||
|
if (!TryPeek(out c))
|
||||||
|
throw new CompileException(Diagnostic.Error("Unterminated string literal").At(fileName, line, column, 0).Build());
|
||||||
|
|
||||||
|
if (c == '"')
|
||||||
|
break;
|
||||||
|
|
||||||
|
if (c == '\n')
|
||||||
|
throw new CompileException(Diagnostic.Error("Unterminated string literal").At(fileName, line, column, 1).Build());
|
||||||
|
|
||||||
|
buf.Append(Consume());
|
||||||
|
}
|
||||||
|
|
||||||
|
Consume();
|
||||||
|
return new TokenStringLiteral(line, startColumn, column - startColumn, buf.ToString());
|
||||||
|
}
|
||||||
|
|
||||||
|
case '{':
|
||||||
|
{
|
||||||
|
Consume();
|
||||||
|
return new TokenSymbol(line, startColumn, column - startColumn, Symbol.OpenCurly);
|
||||||
|
}
|
||||||
|
case '}':
|
||||||
|
{
|
||||||
|
Consume();
|
||||||
|
return new TokenSymbol(line, startColumn, column - startColumn, Symbol.CloseCurly);
|
||||||
|
}
|
||||||
|
case '[':
|
||||||
|
{
|
||||||
|
Consume();
|
||||||
|
return new TokenSymbol(line, startColumn, column - startColumn, Symbol.OpenSquare);
|
||||||
|
}
|
||||||
|
case ']':
|
||||||
|
{
|
||||||
|
Consume();
|
||||||
|
return new TokenSymbol(line, startColumn, column - startColumn, Symbol.CloseSquare);
|
||||||
|
}
|
||||||
|
case '(':
|
||||||
|
{
|
||||||
|
Consume();
|
||||||
|
return new TokenSymbol(line, startColumn, column - startColumn, Symbol.OpenParen);
|
||||||
|
}
|
||||||
|
case ')':
|
||||||
|
{
|
||||||
|
Consume();
|
||||||
|
return new TokenSymbol(line, startColumn, column - startColumn, Symbol.CloseParen);
|
||||||
|
}
|
||||||
|
case ',':
|
||||||
|
{
|
||||||
|
Consume();
|
||||||
|
return new TokenSymbol(line, startColumn, column - startColumn, Symbol.Comma);
|
||||||
|
}
|
||||||
|
case '.':
|
||||||
|
{
|
||||||
|
Consume();
|
||||||
|
return new TokenSymbol(line, startColumn, column - startColumn, Symbol.Period);
|
||||||
|
}
|
||||||
|
case ':' when Peek(1) is ':':
|
||||||
|
{
|
||||||
|
Consume();
|
||||||
|
Consume();
|
||||||
|
return new TokenSymbol(line, startColumn, column - startColumn, Symbol.ColonColon);
|
||||||
|
}
|
||||||
|
case ':':
|
||||||
|
{
|
||||||
|
Consume();
|
||||||
|
return new TokenSymbol(line, startColumn, column - startColumn, Symbol.Colon);
|
||||||
|
}
|
||||||
|
case '^':
|
||||||
|
{
|
||||||
|
Consume();
|
||||||
|
return new TokenSymbol(line, startColumn, column - startColumn, Symbol.Caret);
|
||||||
|
}
|
||||||
|
case '!' when Peek(1) is '=':
|
||||||
|
{
|
||||||
|
Consume();
|
||||||
|
Consume();
|
||||||
|
return new TokenSymbol(line, startColumn, column - startColumn, Symbol.BangEqual);
|
||||||
|
}
|
||||||
|
case '!':
|
||||||
|
{
|
||||||
|
Consume();
|
||||||
|
return new TokenSymbol(line, startColumn, column - startColumn, Symbol.Bang);
|
||||||
|
}
|
||||||
|
case '=' when Peek(1) is '=':
|
||||||
|
{
|
||||||
|
Consume();
|
||||||
|
Consume();
|
||||||
|
return new TokenSymbol(line, startColumn, column - startColumn, Symbol.EqualEqual);
|
||||||
|
}
|
||||||
|
case '=':
|
||||||
|
{
|
||||||
|
Consume();
|
||||||
|
return new TokenSymbol(line, startColumn, column - startColumn, Symbol.Equal);
|
||||||
|
}
|
||||||
|
case '<' when Peek(1) is '<':
|
||||||
|
{
|
||||||
|
Consume();
|
||||||
|
Consume();
|
||||||
|
return new TokenSymbol(line, startColumn, column - startColumn, Symbol.LessThanLessThan);
|
||||||
|
}
|
||||||
|
case '<' when Peek(1) is '=':
|
||||||
|
{
|
||||||
|
Consume();
|
||||||
|
Consume();
|
||||||
|
return new TokenSymbol(line, startColumn, column - startColumn, Symbol.LessThanEqual);
|
||||||
|
}
|
||||||
|
case '<':
|
||||||
|
{
|
||||||
|
Consume();
|
||||||
|
return new TokenSymbol(line, startColumn, column - startColumn, Symbol.LessThan);
|
||||||
|
}
|
||||||
|
case '>' when Peek(1) is '>':
|
||||||
|
{
|
||||||
|
Consume();
|
||||||
|
Consume();
|
||||||
|
return new TokenSymbol(line, startColumn, column - startColumn, Symbol.GreaterThanGreaterThan);
|
||||||
|
}
|
||||||
|
case '>' when Peek(1) is '=':
|
||||||
|
{
|
||||||
|
Consume();
|
||||||
|
Consume();
|
||||||
|
return new TokenSymbol(line, startColumn, column - startColumn, Symbol.GreaterThanEqual);
|
||||||
|
}
|
||||||
|
case '>':
|
||||||
|
{
|
||||||
|
Consume();
|
||||||
|
return new TokenSymbol(line, startColumn, column - startColumn, Symbol.GreaterThan);
|
||||||
|
}
|
||||||
|
case '+' when Peek(1) is '=':
|
||||||
|
{
|
||||||
|
Consume();
|
||||||
|
Consume();
|
||||||
|
return new TokenSymbol(line, startColumn, column - startColumn, Symbol.PlusEqual);
|
||||||
|
}
|
||||||
|
case '+':
|
||||||
|
{
|
||||||
|
Consume();
|
||||||
|
return new TokenSymbol(line, startColumn, column - startColumn, Symbol.Plus);
|
||||||
|
}
|
||||||
|
case '-' when Peek(1) is '=':
|
||||||
|
{
|
||||||
|
Consume();
|
||||||
|
Consume();
|
||||||
|
return new TokenSymbol(line, startColumn, column - startColumn, Symbol.MinusEqual);
|
||||||
|
}
|
||||||
|
case '-':
|
||||||
|
{
|
||||||
|
Consume();
|
||||||
|
return new TokenSymbol(line, startColumn, column - startColumn, Symbol.Minus);
|
||||||
|
}
|
||||||
|
case '*' when Peek(1) is '=':
|
||||||
|
{
|
||||||
|
Consume();
|
||||||
|
Consume();
|
||||||
|
return new TokenSymbol(line, startColumn, column - startColumn, Symbol.StarEqual);
|
||||||
|
}
|
||||||
|
case '*':
|
||||||
|
{
|
||||||
|
Consume();
|
||||||
|
return new TokenSymbol(line, startColumn, column - startColumn, Symbol.Star);
|
||||||
|
}
|
||||||
|
case '/' when Peek(1) is '=':
|
||||||
|
{
|
||||||
|
Consume();
|
||||||
|
Consume();
|
||||||
|
return new TokenSymbol(line, startColumn, column - startColumn, Symbol.ForwardSlashEqual);
|
||||||
|
}
|
||||||
|
case '/':
|
||||||
|
{
|
||||||
|
Consume();
|
||||||
|
return new TokenSymbol(line, startColumn, column - startColumn, Symbol.ForwardSlash);
|
||||||
|
}
|
||||||
|
case '%' when Peek(1) is '=':
|
||||||
|
{
|
||||||
|
Consume();
|
||||||
|
Consume();
|
||||||
|
return new TokenSymbol(line, startColumn, column - startColumn, Symbol.PercentEqual);
|
||||||
|
}
|
||||||
|
case '%':
|
||||||
|
{
|
||||||
|
Consume();
|
||||||
|
return new TokenSymbol(line, startColumn, column - startColumn, Symbol.Percent);
|
||||||
|
}
|
||||||
|
case '&' when Peek(1) is '&':
|
||||||
|
{
|
||||||
|
Consume();
|
||||||
|
Consume();
|
||||||
|
return new TokenSymbol(line, startColumn, column - startColumn, Symbol.AmpersandAmpersand);
|
||||||
|
}
|
||||||
|
case '&':
|
||||||
|
{
|
||||||
|
Consume();
|
||||||
|
return new TokenSymbol(line, startColumn, column - startColumn, Symbol.Ampersand);
|
||||||
|
}
|
||||||
|
case '|' when Peek(1) is '|':
|
||||||
|
{
|
||||||
|
Consume();
|
||||||
|
Consume();
|
||||||
|
return new TokenSymbol(line, startColumn, column - startColumn, Symbol.PipePipe);
|
||||||
|
}
|
||||||
|
case '|':
|
||||||
|
{
|
||||||
|
Consume();
|
||||||
|
return new TokenSymbol(line, startColumn, column - startColumn, Symbol.Pipe);
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
{
|
||||||
|
if (char.IsLetter(c) || c == '_')
|
||||||
|
{
|
||||||
|
var buf = new StringBuilder();
|
||||||
|
|
||||||
|
while (TryPeek(out c) && (char.IsLetterOrDigit(c) || c == '_'))
|
||||||
|
buf.Append(Consume());
|
||||||
|
|
||||||
|
var value = buf.ToString();
|
||||||
|
|
||||||
|
return value switch
|
||||||
|
{
|
||||||
|
"func" => new TokenKeyword(line, startColumn, column - startColumn, Keyword.Func),
|
||||||
|
"struct" => new TokenKeyword(line, startColumn, column - startColumn, Keyword.Struct),
|
||||||
|
"packed" => new TokenKeyword(line, startColumn, column - startColumn, Keyword.Packed),
|
||||||
|
"enum" => new TokenKeyword(line, startColumn, column - startColumn, Keyword.Enum),
|
||||||
|
"new" => new TokenKeyword(line, startColumn, column - startColumn, Keyword.New),
|
||||||
|
"match" => new TokenKeyword(line, startColumn, column - startColumn, Keyword.Match),
|
||||||
|
"let" => new TokenKeyword(line, startColumn, column - startColumn, Keyword.Let),
|
||||||
|
"if" => new TokenKeyword(line, startColumn, column - startColumn, Keyword.If),
|
||||||
|
"else" => new TokenKeyword(line, startColumn, column - startColumn, Keyword.Else),
|
||||||
|
"while" => new TokenKeyword(line, startColumn, column - startColumn, Keyword.While),
|
||||||
|
"for" => new TokenKeyword(line, startColumn, column - startColumn, Keyword.For),
|
||||||
|
"in" => new TokenKeyword(line, startColumn, column - startColumn, Keyword.In),
|
||||||
|
"return" => new TokenKeyword(line, startColumn, column - startColumn, Keyword.Return),
|
||||||
|
"module" => new TokenKeyword(line, startColumn, column - startColumn, Keyword.Module),
|
||||||
|
"export" => new TokenKeyword(line, startColumn, column - startColumn, Keyword.Export),
|
||||||
|
"extern" => new TokenKeyword(line, startColumn, column - startColumn, Keyword.Extern),
|
||||||
|
"true" => new TokenBoolLiteral(line, startColumn, column - startColumn, true),
|
||||||
|
"false" => new TokenBoolLiteral(line, startColumn, column - startColumn, false),
|
||||||
|
_ => new TokenIdent(line, startColumn, column - startColumn, value)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new CompileException(Diagnostic.Error($"Unexpected character '{c}'").At(fileName, line, column, 1).Build());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool TryConsume(out char c)
|
||||||
|
{
|
||||||
|
if (index >= contents.Length)
|
||||||
|
{
|
||||||
|
c = '\0';
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
c = contents[index];
|
||||||
|
|
||||||
|
if (c == '\n')
|
||||||
|
{
|
||||||
|
line += 1;
|
||||||
|
column = 1;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
column += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
index += 1;
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private char Consume()
|
||||||
|
{
|
||||||
|
if (!TryConsume(out var c))
|
||||||
|
throw new CompileException(Diagnostic.Error("Unexpected end of file").At(fileName, line, column, 0).Build());
|
||||||
|
|
||||||
|
return c;
|
||||||
|
}
|
||||||
|
|
||||||
|
private char? Peek(int offset = 0)
|
||||||
|
{
|
||||||
|
if (index + offset >= contents.Length)
|
||||||
|
return null;
|
||||||
|
|
||||||
|
return contents[index + offset];
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool TryPeek(out char c)
|
||||||
|
{
|
||||||
|
if (index >= contents.Length)
|
||||||
|
{
|
||||||
|
c = '\0';
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
c = contents[index];
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public abstract class Token(int line, int column, int length)
|
||||||
|
{
|
||||||
|
public int Line { get; } = line;
|
||||||
|
public int Column { get; } = column;
|
||||||
|
public int Length { get; } = length;
|
||||||
|
}
|
||||||
|
|
||||||
|
public class TokenIdent(int line, int column, int length, string ident) : Token(line, column, length)
|
||||||
|
{
|
||||||
|
public string Ident { get; } = ident;
|
||||||
|
}
|
||||||
|
|
||||||
|
public class TokenIntLiteral(int line, int column, int length, BigInteger value) : Token(line, column, length)
|
||||||
|
{
|
||||||
|
public BigInteger Value { get; } = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public class TokenStringLiteral(int line, int column, int length, string value) : Token(line, column, length)
|
||||||
|
{
|
||||||
|
public string Value { get; } = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public class TokenBoolLiteral(int line, int column, int length, bool value) : Token(line, column, length)
|
||||||
|
{
|
||||||
|
public bool Value { get; } = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum Symbol
|
||||||
|
{
|
||||||
|
OpenCurly,
|
||||||
|
CloseCurly,
|
||||||
|
OpenParen,
|
||||||
|
CloseParen,
|
||||||
|
OpenSquare,
|
||||||
|
CloseSquare,
|
||||||
|
Comma,
|
||||||
|
Period,
|
||||||
|
Colon,
|
||||||
|
ColonColon,
|
||||||
|
Caret,
|
||||||
|
Bang,
|
||||||
|
Equal,
|
||||||
|
EqualEqual,
|
||||||
|
BangEqual,
|
||||||
|
LessThan,
|
||||||
|
LessThanLessThan,
|
||||||
|
LessThanEqual,
|
||||||
|
GreaterThan,
|
||||||
|
GreaterThanGreaterThan,
|
||||||
|
GreaterThanEqual,
|
||||||
|
Plus,
|
||||||
|
PlusEqual,
|
||||||
|
Minus,
|
||||||
|
MinusEqual,
|
||||||
|
Star,
|
||||||
|
StarEqual,
|
||||||
|
ForwardSlash,
|
||||||
|
ForwardSlashEqual,
|
||||||
|
Percent,
|
||||||
|
PercentEqual,
|
||||||
|
Ampersand,
|
||||||
|
AmpersandAmpersand,
|
||||||
|
Pipe,
|
||||||
|
PipePipe,
|
||||||
|
}
|
||||||
|
|
||||||
|
public class TokenSymbol(int line, int column, int length, Symbol symbol) : Token(line, column, length)
|
||||||
|
{
|
||||||
|
public Symbol Symbol { get; } = symbol;
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum Keyword
|
||||||
|
{
|
||||||
|
Func,
|
||||||
|
Struct,
|
||||||
|
Packed,
|
||||||
|
Enum,
|
||||||
|
New,
|
||||||
|
Match,
|
||||||
|
Let,
|
||||||
|
If,
|
||||||
|
Else,
|
||||||
|
While,
|
||||||
|
For,
|
||||||
|
In,
|
||||||
|
Return,
|
||||||
|
Module,
|
||||||
|
Export,
|
||||||
|
Extern,
|
||||||
|
}
|
||||||
|
|
||||||
|
public class TokenKeyword(int line, int column, int length, Keyword keyword) : Token(line, column, length)
|
||||||
|
{
|
||||||
|
public Keyword Keyword { get; } = keyword;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class TokenExtensions
|
||||||
|
{
|
||||||
|
public static string AsString(this Symbol symbol)
|
||||||
|
{
|
||||||
|
return symbol switch
|
||||||
|
{
|
||||||
|
Symbol.OpenCurly => "{",
|
||||||
|
Symbol.CloseCurly => "}",
|
||||||
|
Symbol.OpenParen => "(",
|
||||||
|
Symbol.CloseParen => ")",
|
||||||
|
Symbol.OpenSquare => "[",
|
||||||
|
Symbol.CloseSquare => "]",
|
||||||
|
Symbol.Comma => ",",
|
||||||
|
Symbol.Period => ".",
|
||||||
|
Symbol.Colon => ":",
|
||||||
|
Symbol.ColonColon => "::",
|
||||||
|
Symbol.Caret => "^",
|
||||||
|
Symbol.Bang => "!",
|
||||||
|
Symbol.Equal => "=",
|
||||||
|
Symbol.EqualEqual => "==",
|
||||||
|
Symbol.BangEqual => "!=",
|
||||||
|
Symbol.LessThan => "<",
|
||||||
|
Symbol.LessThanLessThan => "<<",
|
||||||
|
Symbol.LessThanEqual => "<=",
|
||||||
|
Symbol.GreaterThan => ">",
|
||||||
|
Symbol.GreaterThanGreaterThan => ">>",
|
||||||
|
Symbol.GreaterThanEqual => ">=",
|
||||||
|
Symbol.Plus => "+",
|
||||||
|
Symbol.PlusEqual => "+=",
|
||||||
|
Symbol.Minus => "-",
|
||||||
|
Symbol.MinusEqual => "-=",
|
||||||
|
Symbol.Star => "*",
|
||||||
|
Symbol.StarEqual => "*=",
|
||||||
|
Symbol.ForwardSlash => "/",
|
||||||
|
Symbol.ForwardSlashEqual => "/=",
|
||||||
|
Symbol.Percent => "%",
|
||||||
|
Symbol.PercentEqual => "%=",
|
||||||
|
Symbol.Ampersand => "&",
|
||||||
|
Symbol.AmpersandAmpersand => "&&",
|
||||||
|
Symbol.Pipe => "|",
|
||||||
|
Symbol.PipePipe => "||",
|
||||||
|
_ => throw new ArgumentOutOfRangeException(nameof(symbol), symbol, null)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public static string AsString(this Keyword symbol)
|
||||||
|
{
|
||||||
|
return symbol switch
|
||||||
|
{
|
||||||
|
Keyword.Func => "func",
|
||||||
|
Keyword.Struct => "struct",
|
||||||
|
Keyword.Packed => "packed",
|
||||||
|
Keyword.Enum => "enum",
|
||||||
|
Keyword.New => "new",
|
||||||
|
Keyword.Match => "enum",
|
||||||
|
Keyword.Let => "let",
|
||||||
|
Keyword.If => "if",
|
||||||
|
Keyword.Else => "else",
|
||||||
|
Keyword.While => "while",
|
||||||
|
Keyword.For => "for",
|
||||||
|
Keyword.In => "in",
|
||||||
|
Keyword.Return => "return",
|
||||||
|
Keyword.Module => "module",
|
||||||
|
Keyword.Export => "export",
|
||||||
|
Keyword.Extern => "extern",
|
||||||
|
_ => throw new ArgumentOutOfRangeException(nameof(symbol), symbol, null)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
1106
compiler/TypeChecker.cs
Normal file
1106
compiler/TypeChecker.cs
Normal file
File diff suppressed because it is too large
Load Diff
2
examples/.gitignore
vendored
2
examples/.gitignore
vendored
@@ -1,3 +1 @@
|
|||||||
.build
|
.build
|
||||||
out.a
|
|
||||||
out
|
|
||||||
21
examples/build
Executable file
21
examples/build
Executable file
@@ -0,0 +1,21 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
set -e
|
||||||
|
|
||||||
|
SCRIPT_DIR=$(dirname "$0")
|
||||||
|
|
||||||
|
pushd $SCRIPT_DIR
|
||||||
|
pushd ../compiler
|
||||||
|
dotnet build -c Release
|
||||||
|
popd
|
||||||
|
|
||||||
|
pushd core
|
||||||
|
time ../../compiler/bin/Release/net9.0/Compiler sys.nub print.nub --type=lib
|
||||||
|
popd
|
||||||
|
|
||||||
|
pushd program
|
||||||
|
time ../../compiler/bin/Release/net9.0/Compiler main.nub ../core/.build/out.nublib
|
||||||
|
popd
|
||||||
|
|
||||||
|
./program/.build/out
|
||||||
|
popd
|
||||||
10
examples/core/print.nub
Normal file
10
examples/core/print.nub
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
module core
|
||||||
|
|
||||||
|
export func print(text: string) {
|
||||||
|
sys::write(0, text.ptr, text.length)
|
||||||
|
}
|
||||||
|
|
||||||
|
export func println(text: string) {
|
||||||
|
print(text)
|
||||||
|
print("\n")
|
||||||
|
}
|
||||||
5
examples/core/sys.nub
Normal file
5
examples/core/sys.nub
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
module sys
|
||||||
|
|
||||||
|
export extern func read(fd: u32, buf: ^char, count: u64): i64
|
||||||
|
export extern func write(fd: u32, buf: ^char, count: u64): i64
|
||||||
|
export extern func open(fileName: ^char, flags: i32, mode: u16): i64
|
||||||
@@ -1,6 +0,0 @@
|
|||||||
#!/bin/bash
|
|
||||||
|
|
||||||
set -euo pipefail
|
|
||||||
|
|
||||||
obj=$(nubc main.nub)
|
|
||||||
clang $obj -o .build/out
|
|
||||||
@@ -1,9 +0,0 @@
|
|||||||
module main
|
|
||||||
|
|
||||||
extern "puts" func puts(text: ^i8)
|
|
||||||
|
|
||||||
extern "main" func main(argc: i64, argv: [?]^i8): i64
|
|
||||||
{
|
|
||||||
puts("Hello, World!")
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
@@ -1,6 +0,0 @@
|
|||||||
#!/bin/bash
|
|
||||||
|
|
||||||
set -euo pipefail
|
|
||||||
|
|
||||||
nubc main.nub
|
|
||||||
clang .build/main.ll -o .build/out
|
|
||||||
@@ -1,14 +0,0 @@
|
|||||||
module main
|
|
||||||
|
|
||||||
extern "puts" func puts(text: ^i8)
|
|
||||||
|
|
||||||
struct Test {
|
|
||||||
test: ^i8 = "test1"
|
|
||||||
}
|
|
||||||
|
|
||||||
extern "main" func main(argc: i64, argv: [?]^i8)
|
|
||||||
{
|
|
||||||
let x = "test"
|
|
||||||
|
|
||||||
puts(x)
|
|
||||||
}
|
|
||||||
32
examples/program/main.nub
Normal file
32
examples/program/main.nub
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
module main
|
||||||
|
|
||||||
|
struct Human {
|
||||||
|
name: string
|
||||||
|
age: i32
|
||||||
|
}
|
||||||
|
|
||||||
|
enum Message {
|
||||||
|
Quit
|
||||||
|
Say: string
|
||||||
|
}
|
||||||
|
|
||||||
|
func main(): i32 {
|
||||||
|
|
||||||
|
let x = new string("test".ptr) + " " + "uwu"
|
||||||
|
core::println(x)
|
||||||
|
|
||||||
|
let messages: []Message = [new Message::Say("first"), new Message::Quit]
|
||||||
|
|
||||||
|
for message in messages {
|
||||||
|
match message {
|
||||||
|
Say msg {
|
||||||
|
core::println(msg)
|
||||||
|
}
|
||||||
|
Quit {
|
||||||
|
core::println("quit")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0
|
||||||
|
}
|
||||||
@@ -1,6 +0,0 @@
|
|||||||
#!/bin/bash
|
|
||||||
|
|
||||||
set -euo pipefail
|
|
||||||
|
|
||||||
obj=$(nubc main.nub generated/raylib.nub)
|
|
||||||
clang $obj raylib-5.5_linux_amd64/lib/libraylib.a -lm -o .build/out
|
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -1,51 +0,0 @@
|
|||||||
module main
|
|
||||||
|
|
||||||
extern "main" func main(argc: i64, argv: [?]^i8): i64
|
|
||||||
{
|
|
||||||
raylib::SetConfigFlags(raylib::ConfigFlags.FLAG_VSYNC_HINT | raylib::ConfigFlags.FLAG_WINDOW_RESIZABLE)
|
|
||||||
|
|
||||||
raylib::InitWindow(1600, 900, "Hi from nub-lang")
|
|
||||||
defer raylib::CloseWindow()
|
|
||||||
|
|
||||||
raylib::SetTargetFPS(240)
|
|
||||||
|
|
||||||
let width: i32 = 150
|
|
||||||
let height: i32 = 150
|
|
||||||
|
|
||||||
let x: i32 = raylib::GetScreenWidth() / 2 - (width / 2)
|
|
||||||
let y: i32 = raylib::GetScreenHeight() / 2 - (height / 2)
|
|
||||||
|
|
||||||
let direction: raylib::Vector2 = { x = 1 y = 1 }
|
|
||||||
let speed: f32 = 250
|
|
||||||
|
|
||||||
let bgColor: raylib::Color = { r = 0 g = 0 b = 0 a = 255 }
|
|
||||||
let color: raylib::Color = { r = 255 g = 255 b = 255 a = 255 }
|
|
||||||
|
|
||||||
while !raylib::WindowShouldClose()
|
|
||||||
{
|
|
||||||
if x <= 0 {
|
|
||||||
direction.x = 1
|
|
||||||
} else if x + width >= raylib::GetScreenWidth() {
|
|
||||||
direction.x = -1
|
|
||||||
}
|
|
||||||
|
|
||||||
if y <= 0 {
|
|
||||||
direction.y = 1
|
|
||||||
} else if y + height >= raylib::GetScreenHeight() {
|
|
||||||
direction.y = -1
|
|
||||||
}
|
|
||||||
|
|
||||||
x = x + @cast(direction.x * speed * raylib::GetFrameTime())
|
|
||||||
y = y + @cast(direction.y * speed * raylib::GetFrameTime())
|
|
||||||
|
|
||||||
raylib::BeginDrawing()
|
|
||||||
{
|
|
||||||
raylib::ClearBackground(bgColor)
|
|
||||||
raylib::DrawFPS(10, 10)
|
|
||||||
raylib::DrawRectangle(x, y, width, height, color)
|
|
||||||
}
|
|
||||||
raylib::EndDrawing()
|
|
||||||
}
|
|
||||||
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -1,16 +0,0 @@
|
|||||||
Copyright (c) 2013-2024 Ramon Santamaria (@raysan5)
|
|
||||||
|
|
||||||
This software is provided "as-is", without any express or implied warranty. In no event
|
|
||||||
will the authors be held liable for any damages arising from the use of this software.
|
|
||||||
|
|
||||||
Permission is granted to anyone to use this software for any purpose, including commercial
|
|
||||||
applications, and to alter it and redistribute it freely, subject to the following restrictions:
|
|
||||||
|
|
||||||
1. The origin of this software must not be misrepresented; you must not claim that you
|
|
||||||
wrote the original software. If you use this software in a product, an acknowledgment
|
|
||||||
in the product documentation would be appreciated but is not required.
|
|
||||||
|
|
||||||
2. Altered source versions must be plainly marked as such, and must not be misrepresented
|
|
||||||
as being the original software.
|
|
||||||
|
|
||||||
3. This notice may not be removed or altered from any source distribution.
|
|
||||||
@@ -1,150 +0,0 @@
|
|||||||
<img align="left" style="width:260px" src="https://github.com/raysan5/raylib/blob/master/logo/raylib_logo_animation.gif" width="288px">
|
|
||||||
|
|
||||||
**raylib is a simple and easy-to-use library to enjoy videogames programming.**
|
|
||||||
|
|
||||||
raylib is highly inspired by Borland BGI graphics lib and by XNA framework and it's especially well suited for prototyping, tooling, graphical applications, embedded systems and education.
|
|
||||||
|
|
||||||
*NOTE for ADVENTURERS: raylib is a programming library to enjoy videogames programming; no fancy interface, no visual helpers, no debug button... just coding in the most pure spartan-programmers way.*
|
|
||||||
|
|
||||||
Ready to learn? Jump to [code examples!](https://www.raylib.com/examples.html)
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
<br>
|
|
||||||
|
|
||||||
[](https://github.com/raysan5/raylib/releases)
|
|
||||||
[](https://github.com/raysan5/raylib/stargazers)
|
|
||||||
[](https://github.com/raysan5/raylib/commits/master)
|
|
||||||
[](https://github.com/sponsors/raysan5)
|
|
||||||
[](https://repology.org/project/raylib/versions)
|
|
||||||
[](LICENSE)
|
|
||||||
|
|
||||||
[](https://discord.gg/raylib)
|
|
||||||
[](https://www.reddit.com/r/raylib/)
|
|
||||||
[](https://www.youtube.com/c/raylib)
|
|
||||||
[](https://www.twitch.tv/raysan5)
|
|
||||||
|
|
||||||
[](https://github.com/raysan5/raylib/actions?query=workflow%3AWindows)
|
|
||||||
[](https://github.com/raysan5/raylib/actions?query=workflow%3ALinux)
|
|
||||||
[](https://github.com/raysan5/raylib/actions?query=workflow%3AmacOS)
|
|
||||||
[](https://github.com/raysan5/raylib/actions?query=workflow%3AWebAssembly)
|
|
||||||
|
|
||||||
[](https://github.com/raysan5/raylib/actions?query=workflow%3ACMakeBuilds)
|
|
||||||
[](https://github.com/raysan5/raylib/actions/workflows/windows_examples.yml)
|
|
||||||
[](https://github.com/raysan5/raylib/actions/workflows/linux_examples.yml)
|
|
||||||
|
|
||||||
features
|
|
||||||
--------
|
|
||||||
- **NO external dependencies**, all required libraries are [bundled into raylib](https://github.com/raysan5/raylib/tree/master/src/external)
|
|
||||||
- Multiple platforms supported: **Windows, Linux, MacOS, RPI, Android, HTML5... and more!**
|
|
||||||
- Written in plain C code (C99) using PascalCase/camelCase notation
|
|
||||||
- Hardware accelerated with OpenGL (**1.1, 2.1, 3.3, 4.3, ES 2.0, ES 3.0**)
|
|
||||||
- **Unique OpenGL abstraction layer** (usable as standalone module): [rlgl](https://github.com/raysan5/raylib/blob/master/src/rlgl.h)
|
|
||||||
- Multiple **Fonts** formats supported (TTF, OTF, FNT, BDF, sprite fonts)
|
|
||||||
- Multiple texture formats supported, including **compressed formats** (DXT, ETC, ASTC)
|
|
||||||
- **Full 3D support**, including 3D Shapes, Models, Billboards, Heightmaps and more!
|
|
||||||
- Flexible Materials system, supporting classic maps and **PBR maps**
|
|
||||||
- **Animated 3D models** supported (skeletal bones animation) (IQM, M3D, glTF)
|
|
||||||
- Shaders support, including model shaders and **postprocessing** shaders
|
|
||||||
- **Powerful math module** for Vector, Matrix and Quaternion operations: [raymath](https://github.com/raysan5/raylib/blob/master/src/raymath.h)
|
|
||||||
- Audio loading and playing with streaming support (WAV, QOA, OGG, MP3, FLAC, XM, MOD)
|
|
||||||
- **VR stereo rendering** support with configurable HMD device parameters
|
|
||||||
- Huge examples collection with [+140 code examples](https://github.com/raysan5/raylib/tree/master/examples)!
|
|
||||||
- Bindings to [+70 programming languages](https://github.com/raysan5/raylib/blob/master/BINDINGS.md)!
|
|
||||||
- **Free and open source**
|
|
||||||
|
|
||||||
basic example
|
|
||||||
--------------
|
|
||||||
This is a basic raylib example, it creates a window and draws the text `"Congrats! You created your first window!"` in the middle of the screen. Check this example [running live on web here](https://www.raylib.com/examples/core/loader.html?name=core_basic_window).
|
|
||||||
```c
|
|
||||||
#include "raylib.h"
|
|
||||||
|
|
||||||
int main(void)
|
|
||||||
{
|
|
||||||
InitWindow(800, 450, "raylib [core] example - basic window");
|
|
||||||
|
|
||||||
while (!WindowShouldClose())
|
|
||||||
{
|
|
||||||
BeginDrawing();
|
|
||||||
ClearBackground(RAYWHITE);
|
|
||||||
DrawText("Congrats! You created your first window!", 190, 200, 20, LIGHTGRAY);
|
|
||||||
EndDrawing();
|
|
||||||
}
|
|
||||||
|
|
||||||
CloseWindow();
|
|
||||||
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
build and installation
|
|
||||||
----------------------
|
|
||||||
|
|
||||||
raylib binary releases for Windows, Linux, macOS, Android and HTML5 are available at the [Github Releases page](https://github.com/raysan5/raylib/releases).
|
|
||||||
|
|
||||||
raylib is also available via multiple package managers on multiple OS distributions.
|
|
||||||
|
|
||||||
#### Installing and building raylib on multiple platforms
|
|
||||||
|
|
||||||
[raylib Wiki](https://github.com/raysan5/raylib/wiki#development-platforms) contains detailed instructions on building and usage on multiple platforms.
|
|
||||||
|
|
||||||
- [Working on Windows](https://github.com/raysan5/raylib/wiki/Working-on-Windows)
|
|
||||||
- [Working on macOS](https://github.com/raysan5/raylib/wiki/Working-on-macOS)
|
|
||||||
- [Working on GNU Linux](https://github.com/raysan5/raylib/wiki/Working-on-GNU-Linux)
|
|
||||||
- [Working on Chrome OS](https://github.com/raysan5/raylib/wiki/Working-on-Chrome-OS)
|
|
||||||
- [Working on FreeBSD](https://github.com/raysan5/raylib/wiki/Working-on-FreeBSD)
|
|
||||||
- [Working on Raspberry Pi](https://github.com/raysan5/raylib/wiki/Working-on-Raspberry-Pi)
|
|
||||||
- [Working for Android](https://github.com/raysan5/raylib/wiki/Working-for-Android)
|
|
||||||
- [Working for Web (HTML5)](https://github.com/raysan5/raylib/wiki/Working-for-Web-(HTML5))
|
|
||||||
- [Working anywhere with CMake](https://github.com/raysan5/raylib/wiki/Working-with-CMake)
|
|
||||||
|
|
||||||
*Note that the Wiki is open for edit, if you find some issues while building raylib for your target platform, feel free to edit the Wiki or open an issue related to it.*
|
|
||||||
|
|
||||||
#### Setup raylib with multiple IDEs
|
|
||||||
|
|
||||||
raylib has been developed on Windows platform using [Notepad++](https://notepad-plus-plus.org/) and [MinGW GCC](https://www.mingw-w64.org/) compiler but it can be used with other IDEs on multiple platforms.
|
|
||||||
|
|
||||||
[Projects directory](https://github.com/raysan5/raylib/tree/master/projects) contains several ready-to-use **project templates** to build raylib and code examples with multiple IDEs.
|
|
||||||
|
|
||||||
*Note that there are lots of IDEs supported, some of the provided templates could require some review, so please, if you find some issue with a template or you think they could be improved, feel free to send a PR or open a related issue.*
|
|
||||||
|
|
||||||
learning and docs
|
|
||||||
------------------
|
|
||||||
|
|
||||||
raylib is designed to be learned using [the examples](https://github.com/raysan5/raylib/tree/master/examples) as the main reference. There is no standard API documentation but there is a [**cheatsheet**](https://www.raylib.com/cheatsheet/cheatsheet.html) containing all the functions available on the library a short description of each one of them, input parameters and result value names should be intuitive enough to understand how each function works.
|
|
||||||
|
|
||||||
Some additional documentation about raylib design can be found in [raylib GitHub Wiki](https://github.com/raysan5/raylib/wiki). Here are the relevant links:
|
|
||||||
|
|
||||||
- [raylib cheatsheet](https://www.raylib.com/cheatsheet/cheatsheet.html)
|
|
||||||
- [raylib architecture](https://github.com/raysan5/raylib/wiki/raylib-architecture)
|
|
||||||
- [raylib library design](https://github.com/raysan5/raylib/wiki)
|
|
||||||
- [raylib examples collection](https://github.com/raysan5/raylib/tree/master/examples)
|
|
||||||
- [raylib games collection](https://github.com/raysan5/raylib-games)
|
|
||||||
|
|
||||||
|
|
||||||
contact and networks
|
|
||||||
---------------------
|
|
||||||
|
|
||||||
raylib is present in several networks and raylib community is growing everyday. If you are using raylib and enjoying it, feel free to join us in any of these networks. The most active network is our [Discord server](https://discord.gg/raylib)! :)
|
|
||||||
|
|
||||||
- Webpage: [https://www.raylib.com](https://www.raylib.com)
|
|
||||||
- Discord: [https://discord.gg/raylib](https://discord.gg/raylib)
|
|
||||||
- Twitter: [https://www.twitter.com/raysan5](https://www.twitter.com/raysan5)
|
|
||||||
- Twitch: [https://www.twitch.tv/raysan5](https://www.twitch.tv/raysan5)
|
|
||||||
- Reddit: [https://www.reddit.com/r/raylib](https://www.reddit.com/r/raylib)
|
|
||||||
- Patreon: [https://www.patreon.com/raylib](https://www.patreon.com/raylib)
|
|
||||||
- YouTube: [https://www.youtube.com/channel/raylib](https://www.youtube.com/c/raylib)
|
|
||||||
|
|
||||||
contributors
|
|
||||||
------------
|
|
||||||
|
|
||||||
<a href="https://github.com/raysan5/raylib/graphs/contributors">
|
|
||||||
<img src="https://contrib.rocks/image?repo=raysan5/raylib&max=500&columns=20&anon=1" />
|
|
||||||
</a>
|
|
||||||
|
|
||||||
license
|
|
||||||
-------
|
|
||||||
|
|
||||||
raylib is licensed under an unmodified zlib/libpng license, which is an OSI-certified, BSD-like license that allows static linking with closed source software. Check [LICENSE](LICENSE) for further details.
|
|
||||||
|
|
||||||
raylib uses internally some libraries for window/graphics/inputs management and also to support different file formats loading, all those libraries are embedded with and are available in [src/external](https://github.com/raysan5/raylib/tree/master/src/external) directory. Check [raylib dependencies LICENSES](https://github.com/raysan5/raylib/wiki/raylib-dependencies) on [raylib Wiki](https://github.com/raysan5/raylib/wiki) for details.
|
|
||||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
Binary file not shown.
@@ -1 +0,0 @@
|
|||||||
libraylib.so.550
|
|
||||||
Binary file not shown.
@@ -1 +0,0 @@
|
|||||||
libraylib.so.5.5.0
|
|
||||||
1
runtime/.gitignore
vendored
1
runtime/.gitignore
vendored
@@ -1 +0,0 @@
|
|||||||
.build
|
|
||||||
@@ -1,6 +0,0 @@
|
|||||||
#!/bin/bash
|
|
||||||
|
|
||||||
set -euo pipefail
|
|
||||||
|
|
||||||
mkdir -p .build
|
|
||||||
clang -c runtime.c -o .build/runtime.o
|
|
||||||
@@ -1,49 +0,0 @@
|
|||||||
#include "ref.h"
|
|
||||||
#include <string.h>
|
|
||||||
#include <stdlib.h>
|
|
||||||
#include <stdio.h>
|
|
||||||
|
|
||||||
void *rc_alloc(size_t size, void (*destructor)(void *self))
|
|
||||||
{
|
|
||||||
printf("rc_alloc %zu bytes\n", size);
|
|
||||||
ref_header *header = malloc(sizeof(ref_header) + size);
|
|
||||||
memset(header, 0, size);
|
|
||||||
if (!header)
|
|
||||||
{
|
|
||||||
exit(69);
|
|
||||||
}
|
|
||||||
|
|
||||||
header->ref_count = 1;
|
|
||||||
header->destructor = destructor;
|
|
||||||
|
|
||||||
return (void *)(header + 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
void rc_retain(void *obj)
|
|
||||||
{
|
|
||||||
if (!obj)
|
|
||||||
return;
|
|
||||||
|
|
||||||
printf("rc_retain\n");
|
|
||||||
ref_header *header = ((ref_header *)obj) - 1;
|
|
||||||
header->ref_count++;
|
|
||||||
}
|
|
||||||
|
|
||||||
void rc_release(void *obj)
|
|
||||||
{
|
|
||||||
if (!obj)
|
|
||||||
return;
|
|
||||||
|
|
||||||
ref_header *header = ((ref_header *)obj) - 1;
|
|
||||||
printf("rc_release\n");
|
|
||||||
if (--header->ref_count == 0)
|
|
||||||
{
|
|
||||||
if (header->destructor)
|
|
||||||
{
|
|
||||||
header->destructor(obj);
|
|
||||||
}
|
|
||||||
|
|
||||||
free(header);
|
|
||||||
printf("rc_free\n");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,13 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
|
|
||||||
#include <stddef.h>
|
|
||||||
|
|
||||||
typedef struct
|
|
||||||
{
|
|
||||||
int ref_count;
|
|
||||||
void (*destructor)(void *self);
|
|
||||||
} ref_header;
|
|
||||||
|
|
||||||
void *rc_alloc(size_t size, void (*destructor)(void *self));
|
|
||||||
void rc_retain(void *obj);
|
|
||||||
void rc_release(void *obj);
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
#include "ref.c"
|
|
||||||
4
vscode-lsp/.gitignore
vendored
4
vscode-lsp/.gitignore
vendored
@@ -1,4 +0,0 @@
|
|||||||
node_modules
|
|
||||||
out
|
|
||||||
nub-*.vsix
|
|
||||||
server
|
|
||||||
17
vscode-lsp/.vscode/launch.json
vendored
17
vscode-lsp/.vscode/launch.json
vendored
@@ -1,17 +0,0 @@
|
|||||||
{
|
|
||||||
"version": "0.2.0",
|
|
||||||
"configurations": [
|
|
||||||
{
|
|
||||||
"name": "Run Extension",
|
|
||||||
"type": "extensionHost",
|
|
||||||
"request": "launch",
|
|
||||||
"args": [
|
|
||||||
"--extensionDevelopmentPath=${workspaceFolder}"
|
|
||||||
],
|
|
||||||
"outFiles": [
|
|
||||||
"${workspaceFolder}/out/**/*.js"
|
|
||||||
],
|
|
||||||
"preLaunchTask": "${defaultBuildTask}"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
8
vscode-lsp/.vscode/settings.json
vendored
8
vscode-lsp/.vscode/settings.json
vendored
@@ -1,8 +0,0 @@
|
|||||||
{
|
|
||||||
"files.exclude": {},
|
|
||||||
"search.exclude": {
|
|
||||||
"out": true,
|
|
||||||
"node_modules": true,
|
|
||||||
},
|
|
||||||
"typescript.tsc.autoDetect": "off"
|
|
||||||
}
|
|
||||||
18
vscode-lsp/.vscode/tasks.json
vendored
18
vscode-lsp/.vscode/tasks.json
vendored
@@ -1,18 +0,0 @@
|
|||||||
{
|
|
||||||
"version": "2.0.0",
|
|
||||||
"tasks": [
|
|
||||||
{
|
|
||||||
"type": "npm",
|
|
||||||
"script": "watch",
|
|
||||||
"problemMatcher": "$tsc-watch",
|
|
||||||
"isBackground": true,
|
|
||||||
"presentation": {
|
|
||||||
"reveal": "never"
|
|
||||||
},
|
|
||||||
"group": {
|
|
||||||
"kind": "build",
|
|
||||||
"isDefault": true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
@@ -1,69 +0,0 @@
|
|||||||
{
|
|
||||||
"comments": {
|
|
||||||
"lineComment": {
|
|
||||||
"comment": "//"
|
|
||||||
},
|
|
||||||
"blockComment": [
|
|
||||||
"/*",
|
|
||||||
"*/"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"brackets": [
|
|
||||||
[
|
|
||||||
"{",
|
|
||||||
"}"
|
|
||||||
],
|
|
||||||
[
|
|
||||||
"[",
|
|
||||||
"]"
|
|
||||||
],
|
|
||||||
[
|
|
||||||
"(",
|
|
||||||
")"
|
|
||||||
]
|
|
||||||
],
|
|
||||||
"autoClosingPairs": [
|
|
||||||
{
|
|
||||||
"open": "{",
|
|
||||||
"close": "}"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"open": "[",
|
|
||||||
"close": "]"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"open": "(",
|
|
||||||
"close": ")"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"open": "\"",
|
|
||||||
"close": "\""
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"open": "'",
|
|
||||||
"close": "'"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"surroundingPairs": [
|
|
||||||
[
|
|
||||||
"{",
|
|
||||||
"}"
|
|
||||||
],
|
|
||||||
[
|
|
||||||
"[",
|
|
||||||
"]"
|
|
||||||
],
|
|
||||||
[
|
|
||||||
"(",
|
|
||||||
")"
|
|
||||||
],
|
|
||||||
[
|
|
||||||
"\"",
|
|
||||||
"\""
|
|
||||||
],
|
|
||||||
[
|
|
||||||
"'",
|
|
||||||
"'"
|
|
||||||
]
|
|
||||||
]
|
|
||||||
}
|
|
||||||
4811
vscode-lsp/package-lock.json
generated
4811
vscode-lsp/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -1,63 +0,0 @@
|
|||||||
{
|
|
||||||
"name": "nub",
|
|
||||||
"displayName": "Nub Language Support",
|
|
||||||
"description": "Language server client for nub lang",
|
|
||||||
"version": "0.0.1",
|
|
||||||
"publisher": "nub31",
|
|
||||||
"repository": {
|
|
||||||
"type": "git",
|
|
||||||
"url": "https://git.oliste.no/nub31/nub-lang"
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"vscode": "^1.105.0"
|
|
||||||
},
|
|
||||||
"categories": [
|
|
||||||
"Programming Languages"
|
|
||||||
],
|
|
||||||
"main": "./out/extension.js",
|
|
||||||
"files": [
|
|
||||||
"out",
|
|
||||||
"server",
|
|
||||||
"syntaxes",
|
|
||||||
"language-configuration.json"
|
|
||||||
],
|
|
||||||
"contributes": {
|
|
||||||
"languages": [
|
|
||||||
{
|
|
||||||
"id": "nub",
|
|
||||||
"extensions": [
|
|
||||||
".nub"
|
|
||||||
],
|
|
||||||
"configuration": "./language-configuration.json"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"commands": [
|
|
||||||
{
|
|
||||||
"command": "nub.setRootPath",
|
|
||||||
"title": "Set root path"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"grammars": [
|
|
||||||
{
|
|
||||||
"language": "nub",
|
|
||||||
"scopeName": "source.nub",
|
|
||||||
"path": "./syntaxes/nub.tmLanguage.json"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"scripts": {
|
|
||||||
"build": "esbuild src/extension.ts --bundle --platform=node --outfile=out/extension.js --external:vscode",
|
|
||||||
"update-lsp": "mkdir -p server && dotnet publish -c Release ../compiler/NubLang.LSP/NubLang.LSP.csproj && cp ../compiler/NubLang.LSP/bin/Release/net9.0/linux-x64/publish/nublsp server/",
|
|
||||||
"package": "npm run update-lsp && npm run build && vsce package --skip-license"
|
|
||||||
},
|
|
||||||
"devDependencies": {
|
|
||||||
"@types/node": "22.x",
|
|
||||||
"@types/vscode": "^1.105.0",
|
|
||||||
"@vscode/vsce": "^3.6.2",
|
|
||||||
"esbuild": "^0.25.11",
|
|
||||||
"typescript": "^5.9.3"
|
|
||||||
},
|
|
||||||
"dependencies": {
|
|
||||||
"vscode-languageclient": "^9.0.1"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,78 +0,0 @@
|
|||||||
import path from 'path';
|
|
||||||
import vscode from 'vscode';
|
|
||||||
import { LanguageClient, TransportKind } from 'vscode-languageclient/node';
|
|
||||||
|
|
||||||
let client: LanguageClient;
|
|
||||||
|
|
||||||
export async function activate(context: vscode.ExtensionContext) {
|
|
||||||
const serverExecutable = path.join(context.asAbsolutePath('server'), "nublsp");
|
|
||||||
|
|
||||||
client = new LanguageClient(
|
|
||||||
'nub',
|
|
||||||
'nub lsp client',
|
|
||||||
{
|
|
||||||
run: {
|
|
||||||
command: serverExecutable,
|
|
||||||
transport: TransportKind.stdio,
|
|
||||||
},
|
|
||||||
debug: {
|
|
||||||
command: serverExecutable,
|
|
||||||
transport: TransportKind.stdio,
|
|
||||||
args: ['--debug'],
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
documentSelector: [
|
|
||||||
{ scheme: 'file', language: 'nub' },
|
|
||||||
{ scheme: 'file', pattern: '**/*.nub' }
|
|
||||||
],
|
|
||||||
synchronize: {
|
|
||||||
fileEvents: vscode.workspace.createFileSystemWatcher('**/.clientrc')
|
|
||||||
}
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
vscode.commands.registerCommand('nub.setRootPath', setRootPath);
|
|
||||||
|
|
||||||
client.start();
|
|
||||||
|
|
||||||
const choice = await vscode.window.showInformationMessage(
|
|
||||||
'Do you want to set the root directory for the project',
|
|
||||||
'Yes',
|
|
||||||
'No'
|
|
||||||
);
|
|
||||||
|
|
||||||
if (choice === 'Yes') {
|
|
||||||
await setRootPath();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export function deactivate(): Thenable<void> | undefined {
|
|
||||||
if (!client) {
|
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
return client.stop();
|
|
||||||
}
|
|
||||||
|
|
||||||
async function setRootPath() {
|
|
||||||
if (!client) return;
|
|
||||||
|
|
||||||
const folder = await vscode.window.showOpenDialog({
|
|
||||||
canSelectFolders: true,
|
|
||||||
canSelectFiles: false,
|
|
||||||
canSelectMany: false,
|
|
||||||
openLabel: 'Select root location'
|
|
||||||
});
|
|
||||||
|
|
||||||
if (folder && folder.length > 0) {
|
|
||||||
const newRoot = folder[0].fsPath;
|
|
||||||
|
|
||||||
await client.sendRequest('workspace/executeCommand', {
|
|
||||||
command: 'nub.setRootPath',
|
|
||||||
arguments: [newRoot]
|
|
||||||
});
|
|
||||||
|
|
||||||
vscode.window.showInformationMessage(`Root path set to: ${newRoot}`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,304 +0,0 @@
|
|||||||
{
|
|
||||||
"$schema": "https://raw.githubusercontent.com/martinring/tmlanguage/master/tmlanguage.json",
|
|
||||||
"name": "nub",
|
|
||||||
"scopeName": "source.nub",
|
|
||||||
"patterns": [
|
|
||||||
{
|
|
||||||
"include": "#comments"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"include": "#keywords"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"include": "#modifiers"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"include": "#types"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"include": "#strings"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"include": "#numbers"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"include": "#operators"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"include": "#function-definition"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"include": "#struct-definition"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"include": "#function-call"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"include": "#identifiers"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"repository": {
|
|
||||||
"comments": {
|
|
||||||
"patterns": [
|
|
||||||
{
|
|
||||||
"name": "comment.line.double-slash.nub",
|
|
||||||
"begin": "//",
|
|
||||||
"end": "$"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "comment.block.nub",
|
|
||||||
"begin": "/\\*",
|
|
||||||
"end": "\\*/"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"keywords": {
|
|
||||||
"patterns": [
|
|
||||||
{
|
|
||||||
"name": "keyword.control.nub",
|
|
||||||
"match": "\\b(if|else|while|for|in|break|continue|return|let|defer)\\b"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "keyword.other.nub",
|
|
||||||
"match": "\\b(func|struct|module|import)\\b"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"modifiers": {
|
|
||||||
"patterns": [
|
|
||||||
{
|
|
||||||
"name": "storage.modifier.nub",
|
|
||||||
"match": "\\b(export|extern)\\b"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"types": {
|
|
||||||
"patterns": [
|
|
||||||
{
|
|
||||||
"include": "#function-type"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "storage.type.primitive.nub",
|
|
||||||
"match": "\\b(i8|i16|i32|i64|u8|u16|u32|u64|f32|f64|bool|string|cstring|void|any)\\b"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "storage.type.array.nub",
|
|
||||||
"match": "\\[\\]"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "storage.type.pointer.nub",
|
|
||||||
"match": "\\^"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"function-type": {
|
|
||||||
"patterns": [
|
|
||||||
{
|
|
||||||
"begin": "\\b(func)\\s*\\(",
|
|
||||||
"beginCaptures": {
|
|
||||||
"1": {
|
|
||||||
"name": "storage.type.function.nub"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"end": "(?<=\\))(?:\\s*:\\s*([^\\s,;{}()]+))?",
|
|
||||||
"endCaptures": {
|
|
||||||
"1": {
|
|
||||||
"name": "storage.type.nub"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"patterns": [
|
|
||||||
{
|
|
||||||
"include": "#function-type-parameters"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"function-type-parameters": {
|
|
||||||
"patterns": [
|
|
||||||
{
|
|
||||||
"match": "\\.\\.\\.",
|
|
||||||
"name": "keyword.operator.variadic.nub"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"include": "#types"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"match": ",",
|
|
||||||
"name": "punctuation.separator.nub"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"strings": {
|
|
||||||
"patterns": [
|
|
||||||
{
|
|
||||||
"name": "string.quoted.double.nub",
|
|
||||||
"begin": "\"",
|
|
||||||
"end": "\"",
|
|
||||||
"patterns": [
|
|
||||||
{
|
|
||||||
"name": "constant.character.escape.nub",
|
|
||||||
"match": "\\\\(n|t|r|\\\\|\"|')"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "constant.character.escape.nub",
|
|
||||||
"match": "\\\\[0-7]{1,3}"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "constant.character.escape.nub",
|
|
||||||
"match": "\\\\x[0-9A-Fa-f]{1,2}"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "string.quoted.single.nub",
|
|
||||||
"begin": "'",
|
|
||||||
"end": "'",
|
|
||||||
"patterns": [
|
|
||||||
{
|
|
||||||
"name": "constant.character.escape.nub",
|
|
||||||
"match": "\\\\(n|t|r|\\\\|\"|')"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"numbers": {
|
|
||||||
"patterns": [
|
|
||||||
{
|
|
||||||
"name": "constant.numeric.float.nub",
|
|
||||||
"match": "\\b\\d+\\.\\d*([eE][+-]?\\d+)?[fF]?\\b"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "constant.numeric.integer.decimal.nub",
|
|
||||||
"match": "\\b\\d+\\b"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "constant.numeric.integer.hexadecimal.nub",
|
|
||||||
"match": "\\b0[xX][0-9A-Fa-f]+\\b"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "constant.numeric.integer.binary.nub",
|
|
||||||
"match": "\\b0[bB][01]+\\b"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"operators": {
|
|
||||||
"patterns": [
|
|
||||||
{
|
|
||||||
"name": "keyword.operator.assignment.nub",
|
|
||||||
"match": "="
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "keyword.operator.comparison.nub",
|
|
||||||
"match": "(==|!=|<=|>=|<|>)"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "keyword.operator.arithmetic.nub",
|
|
||||||
"match": "(\\+|\\-|\\*|/)"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "keyword.operator.logical.nub",
|
|
||||||
"match": "(&&|\\|\\||!)"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "keyword.operator.address.nub",
|
|
||||||
"match": "&"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "keyword.operator.dereference.nub",
|
|
||||||
"match": "\\^"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "keyword.operator.member-access.nub",
|
|
||||||
"match": "\\."
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "keyword.operator.module-access.nub",
|
|
||||||
"match": "::"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"function-definition": {
|
|
||||||
"patterns": [
|
|
||||||
{
|
|
||||||
"begin": "\\b(export\\s+|extern\\s+)?(func)\\s+([a-zA-Z_][a-zA-Z0-9_]*)\\s*\\(",
|
|
||||||
"beginCaptures": {
|
|
||||||
"1": {
|
|
||||||
"name": "storage.modifier.nub"
|
|
||||||
},
|
|
||||||
"2": {
|
|
||||||
"name": "keyword.other.nub"
|
|
||||||
},
|
|
||||||
"3": {
|
|
||||||
"name": "entity.name.function.nub"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"end": "\\)",
|
|
||||||
"patterns": [
|
|
||||||
{
|
|
||||||
"include": "#function-parameters"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"struct-definition": {
|
|
||||||
"patterns": [
|
|
||||||
{
|
|
||||||
"match": "\\b(struct)\\s+([a-zA-Z_][a-zA-Z0-9_]*)\\b",
|
|
||||||
"captures": {
|
|
||||||
"1": {
|
|
||||||
"name": "keyword.other.nub"
|
|
||||||
},
|
|
||||||
"2": {
|
|
||||||
"name": "entity.name.type.struct.nub"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"function-parameters": {
|
|
||||||
"patterns": [
|
|
||||||
{
|
|
||||||
"match": "\\.\\.\\.",
|
|
||||||
"name": "keyword.operator.variadic.nub"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"match": "([a-zA-Z_][a-zA-Z0-9_]*)\\s*:\\s*",
|
|
||||||
"captures": {
|
|
||||||
"1": {
|
|
||||||
"name": "variable.parameter.nub"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"include": "#types"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"include": "#identifiers"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"function-call": {
|
|
||||||
"patterns": [
|
|
||||||
{
|
|
||||||
"match": "([a-zA-Z_][a-zA-Z0-9_]*)\\s*(?=\\()",
|
|
||||||
"captures": {
|
|
||||||
"1": {
|
|
||||||
"name": "entity.name.function.call.nub"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"identifiers": {
|
|
||||||
"patterns": [
|
|
||||||
{
|
|
||||||
"name": "variable.other.nub",
|
|
||||||
"match": "\\b[a-zA-Z_][a-zA-Z0-9_]*\\b"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,15 +0,0 @@
|
|||||||
{
|
|
||||||
"compilerOptions": {
|
|
||||||
"module": "Node16",
|
|
||||||
"target": "ES2022",
|
|
||||||
"outDir": "out",
|
|
||||||
"lib": [
|
|
||||||
"ES2022"
|
|
||||||
],
|
|
||||||
"sourceMap": true,
|
|
||||||
"rootDir": "src",
|
|
||||||
"strict": true,
|
|
||||||
"noImplicitReturns": true,
|
|
||||||
"noFallthroughCasesInSwitch": true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Reference in New Issue
Block a user