Compare commits

...

39 Commits

Author SHA1 Message Date
nub31
c65fbeba13 ... 2026-02-10 23:03:27 +01:00
nub31
9d8d8f255a libs working 2026-02-10 22:43:03 +01:00
nub31
576abe1240 ... 2026-02-10 22:26:18 +01:00
nub31
d0c31ad17f global variables 2026-02-10 20:51:28 +01:00
nub31
7872a4b6b8 module exports and name mangling 2026-02-10 20:33:27 +01:00
nub31
6ae10d5f90 ... 2026-02-10 19:50:55 +01:00
nub31
d3e2dcede8 expression function calls 2026-02-09 22:30:25 +01:00
nub31
88fe03c048 ... 2026-02-09 22:03:08 +01:00
nub31
9ccdd5f835 tokenizer improvements 2026-02-09 22:03:00 +01:00
nub31
b7cfdd2519 Fix formatting 2026-02-09 21:39:09 +01:00
nub31
d409bb4d92 Type instancing 2026-02-09 21:32:37 +01:00
nub31
00a172b922 ... 2026-02-09 21:16:03 +01:00
nub31
ea3d374831 ... 2026-02-09 21:09:42 +01:00
nub31
ab9bd6fd05 ... 2026-02-09 20:49:09 +01:00
nub31
96670b1201 ... 2026-02-09 19:34:47 +01:00
nub31
9fb9c50a0b fix error message 2026-02-09 15:52:40 +01:00
nub31
89a827fdef struct resolution 2026-02-09 15:52:14 +01:00
nub31
f035499ba7 ... 2026-02-09 15:20:29 +01:00
nub31
3e3757a402 ... 2026-02-09 13:25:34 +01:00
nub31
1d63bd6389 ... 2026-02-09 12:59:43 +01:00
nub31
1a3cf3c4fd ... 2026-02-09 12:59:29 +01:00
nub31
5105a689df ... 2026-02-09 12:58:23 +01:00
nub31
bd7e3fc931 remove old stuff 2026-02-09 12:55:57 +01:00
3b89b3d9bb ... 2026-02-08 23:11:43 +01:00
3db412a060 Type checking basics 2026-02-08 23:05:50 +01:00
b31a2d01c6 ... 2026-02-08 20:26:11 +01:00
6f03e2203f diagnostic 2026-02-08 19:35:03 +01:00
e20f6cd7af diangostics 2026-02-08 19:24:06 +01:00
00714ea4b0 ... 2026-02-08 18:43:50 +01:00
4761cd1f83 ... 2026-02-08 17:55:15 +01:00
423ec4c798 ... 2026-02-08 16:18:41 +01:00
38f55d8e7c ... 2026-02-08 16:10:39 +01:00
1a5742fc4f ... 2026-02-08 15:53:00 +01:00
4c201c4085 ... 2026-02-08 15:45:53 +01:00
e77c7028b9 ... 2026-02-08 01:22:24 +01:00
26d365cf4f ... 2026-02-08 00:53:55 +01:00
f2ea00b34d ... 2026-02-08 00:21:38 +01:00
cb2411a7eb ... 2026-02-08 00:05:59 +01:00
3b75e62aa7 dev 2026-02-07 19:54:09 +01:00
81 changed files with 3695 additions and 25574 deletions

View File

@@ -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
View File

@@ -1,34 +1,4 @@
# Common IntelliJ Platform excludes
# User specific
**/.idea/**/workspace.xml
**/.idea/**/tasks.xml
**/.idea/shelf/*
**/.idea/dictionaries
**/.idea/httpRequests/
# Sensitive or high-churn files
**/.idea/**/dataSources/
**/.idea/**/dataSources.ids
**/.idea/**/dataSources.xml
**/.idea/**/dataSources.local.xml
**/.idea/**/sqlDataSources.xml
**/.idea/**/dynamic.xml
# Rider
# Rider auto-generates .iml files, and contentModel.xml
**/.idea/**/*.iml
**/.idea/**/contentModel.xml
**/.idea/**/modules.xml
*.suo
*.user
.vs/
[Bb]in/
[Oo]bj/
_UpgradeReport_Files/
[Pp]ackages/
Thumbs.db
Desktop.ini
.DS_Store
.idea
bin
obj
.build

View File

@@ -1,13 +0,0 @@
# Default ignored files
/shelf/
/workspace.xml
# Rider ignored files
/modules.xml
/projectSettingsUpdater.xml
/contentModel.xml
/.idea.Compiler.iml
# Editor-based HTTP Client requests
/httpRequests/
# Datasource local storage ignored files
/dataSources/
/dataSources.local.xml

View File

@@ -1 +0,0 @@
Compiler

View File

@@ -1,4 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="Encoding" addBOMForNewFiles="with BOM under Windows, with no BOM otherwise" />
</project>

View File

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

View File

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

View File

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

View File

@@ -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
View 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";
}
}

389
compiler/Generator.cs Normal file
View File

@@ -0,0 +1,389 @@
using System.Text;
namespace Compiler;
public class Generator
{
public static string Emit(List<TypedNodeDefinitionFunc> functions, ModuleGraph moduleGraph, bool compileLib)
{
return new Generator(functions, moduleGraph, compileLib).Emit();
}
private Generator(List<TypedNodeDefinitionFunc> functions, ModuleGraph moduleGraph, bool compileLib)
{
this.functions = functions;
this.moduleGraph = moduleGraph;
this.compileLib = compileLib;
}
private readonly List<TypedNodeDefinitionFunc> functions;
private readonly ModuleGraph moduleGraph;
private readonly bool compileLib;
private readonly IndentedTextWriter writer = new();
private readonly Dictionary<NubTypeStruct, string> structTypeNames = new();
private string Emit()
{
foreach (var (i, structType) in moduleGraph.GetModules().SelectMany(x => x.GetCustomTypes().OfType<NubTypeStruct>().Index()))
structTypeNames[structType] = $"s{i}";
writer.WriteLine("""
#include <float.h>
#include <stdarg.h>
#include <stddef.h>
#include <stdbool.h>
#include <stdint.h>
struct nub_core_string
{
const char *data;
int length;
};
""");
writer.WriteLine();
foreach (var typeName in structTypeNames)
writer.WriteLine($"struct {typeName.Value};");
writer.WriteLine();
foreach (var typeName in structTypeNames)
{
writer.Write("struct ");
if (typeName.Key.Packed)
{
writer.Write("__attribute__((__packed__)) ");
}
writer.WriteLine(typeName.Value);
writer.WriteLine("{");
using (writer.Indent())
{
foreach (var field in typeName.Key.Fields)
writer.WriteLine($"{CType(field.Type, field.Name)};");
}
writer.WriteLine("};");
}
writer.WriteLine();
foreach (var module in moduleGraph.GetModules())
{
foreach (var (name, type) in module.GetIdentifierTypes())
{
if (type is NubTypeFunc fn)
{
if (!functions.Any(x => x.GetMangledName() == SymbolNameGen.Exported(module.Name, name, type)))
writer.Write("extern ");
writer.WriteLine($"{CType(fn.ReturnType, SymbolNameGen.Exported(module.Name, name, type))}({string.Join(", ", fn.Parameters.Select(p => CType(p)))});");
}
else
{
writer.WriteLine($"{CType(type, SymbolNameGen.Exported(module.Name, name, type))};");
}
}
}
writer.WriteLine();
if (!compileLib)
{
var main = functions.First(x => x.Module == "main" && x.Name.Ident == "main");
writer.WriteLine($$"""
int main(int argc, char *argv[])
{
return {{main.GetMangledName()}}();
}
""");
}
writer.WriteLine();
foreach (var function in functions)
{
var parameters = function.Parameters.Select(x => CType(x.Type, x.Name.Ident));
writer.WriteLine($"{CType(function.ReturnType, function.GetMangledName())}({string.Join(", ", parameters)})");
writer.WriteLine("{");
using (writer.Indent())
{
EmitStatement(function.Body);
}
writer.WriteLine("}");
writer.WriteLine();
}
return writer.ToString();
}
private void EmitStatement(TypedNodeStatement node)
{
switch (node)
{
case TypedNodeStatementBlock statement:
EmitStatementBlock(statement);
break;
case TypedNodeStatementFuncCall statement:
EmitStatementFuncCall(statement);
break;
case TypedNodeStatementReturn statement:
EmitStatementReturn(statement);
break;
case TypedNodeStatementVariableDeclaration statement:
EmitStatementVariableDeclaration(statement);
break;
case TypedNodeStatementAssignment statement:
EmitStatementAssignment(statement);
break;
case TypedNodeStatementIf statement:
EmitStatementIf(statement);
break;
case TypedNodeStatementWhile statement:
EmitStatementWhile(statement);
break;
default:
throw new ArgumentOutOfRangeException(nameof(node), node, null);
}
}
private void EmitStatementBlock(TypedNodeStatementBlock node)
{
writer.WriteLine("{");
using (writer.Indent())
{
foreach (var statement in node.Statements)
EmitStatement(statement);
}
writer.WriteLine("}");
}
private void EmitStatementFuncCall(TypedNodeStatementFuncCall node)
{
var name = EmitExpression(node.Target);
var parameterValues = node.Parameters.Select(EmitExpression).ToList();
writer.WriteLine($"{name}({string.Join(", ", parameterValues)});");
}
private void EmitStatementReturn(TypedNodeStatementReturn statement)
{
var value = EmitExpression(statement.Value);
writer.WriteLine($"return {value};");
}
private void EmitStatementVariableDeclaration(TypedNodeStatementVariableDeclaration statement)
{
var value = EmitExpression(statement.Value);
writer.WriteLine($"{CType(statement.Type)} {statement.Name.Ident} = {value};");
}
private void EmitStatementAssignment(TypedNodeStatementAssignment statement)
{
var target = EmitExpression(statement.Target);
var value = EmitExpression(statement.Value);
writer.WriteLine($"{target} = {value};");
}
private void EmitStatementIf(TypedNodeStatementIf statement)
{
var condition = EmitExpression(statement.Condition);
writer.WriteLine($"if ({condition})");
writer.WriteLine("{");
using (writer.Indent())
{
EmitStatement(statement.ThenBlock);
}
writer.WriteLine("}");
if (statement.ElseBlock != null)
{
writer.Write("else");
if (statement.ElseBlock is TypedNodeStatementIf)
writer.Write(" ");
else
writer.WriteLine();
writer.WriteLine("{");
using (writer.Indent())
{
EmitStatement(statement.ElseBlock);
}
writer.WriteLine("}");
}
}
private void EmitStatementWhile(TypedNodeStatementWhile statement)
{
var condition = EmitExpression(statement.Condition);
writer.WriteLine($"while ({condition})");
writer.WriteLine("{");
using (writer.Indent())
{
EmitStatement(statement.Block);
}
writer.WriteLine("}");
}
private string EmitExpression(TypedNodeExpression node)
{
return node switch
{
TypedNodeExpressionBinary expression => EmitExpressionBinary(expression),
TypedNodeExpressionUnary expression => EmitExpressionUnary(expression),
TypedNodeExpressionBoolLiteral expression => expression.Value.Value ? "true" : "false",
TypedNodeExpressionIntLiteral expression => expression.Value.Value.ToString(),
TypedNodeExpressionStringLiteral expression => $"(struct nub_core_string){{ \"{expression.Value.Value}\", {expression.Value.Value.Length} }}",
TypedNodeExpressionStructLiteral expression => EmitExpressionStructLiteral(expression),
TypedNodeExpressionMemberAccess expression => EmitExpressionMemberAccess(expression),
TypedNodeExpressionLocalIdent expression => expression.Value.Ident,
TypedNodeExpressionModuleIdent expression => SymbolNameGen.Exported(expression.Module.Ident, expression.Value.Ident, expression.Type),
TypedNodeExpressionFuncCall expression => EmitExpressionFuncCall(expression),
_ => throw new ArgumentOutOfRangeException(nameof(node), node, null)
};
}
private string EmitExpressionBinary(TypedNodeExpressionBinary expression)
{
var left = EmitExpression(expression.Left);
var right = EmitExpression(expression.Right);
return expression.Operation switch
{
TypedNodeExpressionBinary.Op.Add => $"({left} + {right})",
TypedNodeExpressionBinary.Op.Subtract => $"({left} - {right})",
TypedNodeExpressionBinary.Op.Multiply => $"({left} * {right})",
TypedNodeExpressionBinary.Op.Divide => $"({left} / {right})",
TypedNodeExpressionBinary.Op.Modulo => $"({left} % {right})",
TypedNodeExpressionBinary.Op.Equal => $"({left} == {right})",
TypedNodeExpressionBinary.Op.NotEqual => $"({left} != {right})",
TypedNodeExpressionBinary.Op.LessThan => $"({left} < {right})",
TypedNodeExpressionBinary.Op.LessThanOrEqual => $"({left} <= {right})",
TypedNodeExpressionBinary.Op.GreaterThan => $"({left} > {right})",
TypedNodeExpressionBinary.Op.GreaterThanOrEqual => $"({left} >= {right})",
TypedNodeExpressionBinary.Op.LeftShift => $"({left} << {right})",
TypedNodeExpressionBinary.Op.RightShift => $"({left} >> {right})",
TypedNodeExpressionBinary.Op.LogicalAnd => $"({left} && {right})",
TypedNodeExpressionBinary.Op.LogicalOr => $"({left} || {right})",
_ => throw new ArgumentOutOfRangeException()
};
}
private string EmitExpressionUnary(TypedNodeExpressionUnary expression)
{
var target = EmitExpression(expression.Target);
return expression.Operation switch
{
TypedNodeExpressionUnary.Op.Negate => $"(-{target})",
TypedNodeExpressionUnary.Op.Invert => $"(!{target})",
_ => throw new ArgumentOutOfRangeException()
};
}
private string EmitExpressionStructLiteral(TypedNodeExpressionStructLiteral expression)
{
var initializerValues = new Dictionary<string, string>();
foreach (var initializer in expression.Initializers)
{
var values = EmitExpression(initializer.Value);
initializerValues[initializer.Name.Ident] = values;
}
var initializerStrings = initializerValues.Select(x => $".{x.Key} = {x.Value}");
return $"(struct {structTypeNames[(NubTypeStruct)expression.Type]}){{ {string.Join(", ", initializerStrings)} }}";
}
private string EmitExpressionMemberAccess(TypedNodeExpressionMemberAccess expression)
{
var target = EmitExpression(expression.Target);
return $"{target}.{expression.Name.Ident}";
}
private string EmitExpressionFuncCall(TypedNodeExpressionFuncCall expression)
{
var name = EmitExpression(expression.Target);
var parameterValues = expression.Parameters.Select(EmitExpression).ToList();
return $"{name}({string.Join(", ", parameterValues)})";
}
private string CType(NubType node, string? varName = null)
{
return node switch
{
NubTypeVoid => "void" + (varName != null ? $" {varName}" : ""),
NubTypeBool => "bool" + (varName != null ? $" {varName}" : ""),
NubTypeStruct type => $"struct {structTypeNames[type]}" + (varName != null ? $" {varName}" : ""),
NubTypeSInt type => $"int{type.Width}_t" + (varName != null ? $" {varName}" : ""),
NubTypeUInt type => $"uint{type.Width}_t" + (varName != null ? $" {varName}" : ""),
NubTypePointer type => CType(type.To) + (varName != null ? $" *{varName}" : "*"),
NubTypeString => "struct nub_core_string" + (varName != null ? $" {varName}" : ""),
NubTypeFunc type => $"{CType(type.ReturnType)} (*{varName})({string.Join(", ", type.Parameters.Select(p => CType(p)))})",
_ => throw new ArgumentOutOfRangeException(nameof(node), node, null)
};
}
}
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(IndentedTextWriter writer) : IDisposable
{
private bool disposed;
public void Dispose()
{
if (disposed) return;
writer.indentLevel--;
disposed = true;
}
}
}

285
compiler/ModuleGraph.cs Normal file
View File

@@ -0,0 +1,285 @@
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 TryResolveModule(string moduleName, [NotNullWhen(true)] out Module? module)
{
module = modules.GetValueOrDefault(moduleName);
return module != null;
}
public Manifest CreateManifest()
{
return new Manifest(1, GetModules().Select(x => x.CreateManifestModule()).ToList());
}
public class Module(string name)
{
public string Name { get; } = name;
private readonly Dictionary<string, CustomTypeInfo> customTypes = new();
private readonly Dictionary<string, IdentifierInfo> identifierTypes = new();
public IReadOnlyList<NubType> GetCustomTypes()
{
return customTypes.Values.Select(x => x.Type).ToList();
}
public IReadOnlyDictionary<string, NubType> GetIdentifierTypes()
{
return identifierTypes.ToDictionary(x => x.Key, x => x.Value.Type);
}
public bool TryResolveCustomType(string name, bool searchPrivate, [NotNullWhen(true)] out NubType? customType)
{
var info = customTypes.GetValueOrDefault(name);
if (info == null)
{
customType = null;
return false;
}
if (info.Exported || searchPrivate)
{
customType = info.Type;
return true;
}
customType = null;
return false;
}
public bool TryResolveIdentifierType(string name, bool searchPrivate, [NotNullWhen(true)] out NubType? identifierType)
{
var info = identifierTypes.GetValueOrDefault(name);
if (info == null)
{
identifierType = null;
return false;
}
if (info.Exported || searchPrivate)
{
identifierType = info.Type;
return true;
}
identifierType = null;
return false;
}
public void AddCustomType(string name, CustomTypeInfo info)
{
customTypes.Add(name, info);
}
public void AddIdentifier(string name, IdentifierInfo info)
{
identifierTypes.Add(name, info);
}
public ManifestModule CreateManifestModule()
{
var manifestCustomTypes = customTypes.ToDictionary(x => x.Key, x => x.Value.CreateManifestCustomTypeInfo());
var manifestIdentifiers = identifierTypes.ToDictionary(x => x.Key, x => x.Value.CreateManifestIdentifierInfo());
return new ManifestModule(Name, manifestCustomTypes, manifestIdentifiers);
}
public enum Source
{
Ast,
Lib,
}
public class CustomTypeInfo(NubType type, bool exported, Source source)
{
public NubType Type { get; } = type;
public bool Exported { get; } = exported;
public Source Source { get; } = source;
public ManifestCustomTypeInfo CreateManifestCustomTypeInfo()
{
return new ManifestCustomTypeInfo(TypeEncoder.Encode(Type), Exported);
}
}
public class IdentifierInfo(NubType type, bool exported, Source source)
{
public NubType Type { get; } = type;
public bool Exported { get; } = exported;
public Source Source { get; } = source;
public ManifestIdentifierInfo CreateManifestIdentifierInfo()
{
return new ManifestIdentifierInfo(TypeEncoder.Encode(Type), Exported);
}
}
}
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>();
// First pass: Register libraries
foreach (var manifest in manifests)
{
foreach (var manifestModule in manifest.Modules)
{
if (!modules.TryGetValue(manifestModule.Name, out var module))
{
module = new Module(manifestModule.Name);
modules.Add(manifestModule.Name, module);
}
foreach (var customType in manifestModule.CustomTypes)
{
var decoded = TypeDecoder.Decode(customType.Value.EncodedType);
module.AddCustomType(customType.Key, new Module.CustomTypeInfo(decoded, customType.Value.Exported, Module.Source.Lib));
}
foreach (var identifier in manifestModule.Identifiers)
{
var decoded = TypeDecoder.Decode(identifier.Value.EncodedType);
module.AddIdentifier(identifier.Key, new Module.IdentifierInfo(decoded, identifier.Value.Exported, Module.Source.Lib));
}
}
}
var astModuleCache = new Dictionary<Ast, Module>();
// First pass: Register modules from ast
foreach (var ast in asts)
{
if (!modules.ContainsKey(ast.ModuleName.Ident))
{
var module = new Module(ast.ModuleName.Ident);
modules.Add(ast.ModuleName.Ident, module);
astModuleCache[ast] = module;
}
}
// Second pass: Register struct types without fields
foreach (var ast in asts)
{
var module = astModuleCache[ast];
foreach (var structDef in ast.Definitions.OfType<NodeDefinitionStruct>())
{
var type = new NubTypeStruct(module.Name, structDef.Name.Ident, structDef.Packed);
var info = new Module.CustomTypeInfo(type, structDef.Exported, Module.Source.Ast);
module.AddCustomType(structDef.Name.Ident, info);
}
}
// Third pass: Resolve struct fields
foreach (var ast in asts)
{
var module = astModuleCache[ast];
foreach (var structDef in ast.Definitions.OfType<NodeDefinitionStruct>())
{
if (!module.TryResolveCustomType(structDef.Name.Ident, true, out var customType))
throw new UnreachableException($"{nameof(customType)} should always be registered");
var fields = structDef.Fields.Select(f => new NubTypeStruct.Field(f.Name.Ident, ResolveType(f.Type, module.Name))).ToList();
((NubTypeStruct)customType).ResolveFields(fields);
}
}
// Fourth pass: Register identifiers
foreach (var ast in asts)
{
var module = astModuleCache[ast];
foreach (var funcDef in ast.Definitions.OfType<NodeDefinitionFunc>())
{
var parameters = funcDef.Parameters.Select(x => ResolveType(x.Type, module.Name)).ToList();
var returnType = ResolveType(funcDef.ReturnType, module.Name);
var funcType = NubTypeFunc.Get(parameters, returnType);
var info = new Module.IdentifierInfo(funcType, funcDef.Exported, Module.Source.Ast);
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(type, globalVariable.Exported, Module.Source.Ast);
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,
NodeTypeCustom type => ResolveCustomType(type, currentModule),
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,
NodeTypeVoid => NubTypeVoid.Instance,
_ => throw new ArgumentOutOfRangeException(nameof(node))
};
}
NubType ResolveCustomType(NodeTypeCustom type, string currentModule)
{
var module = modules.GetValueOrDefault(type.Module.Ident);
if (module == null)
throw new CompileException(Diagnostic.Error($"Unknown module: {type.Module.Ident}").Build());
var includePrivate = currentModule == type.Module.Ident;
if (!module.TryResolveCustomType(type.Name.Ident, includePrivate, out var customType))
throw new CompileException(Diagnostic.Error($"Unknown custom type: {type.Module.Ident}::{type.Name.Ident}").Build());
return customType;
}
}
}
}
public record Manifest(int Version, IReadOnlyList<ManifestModule> Modules);
public record ManifestModule(string Name, Dictionary<string, ManifestCustomTypeInfo> CustomTypes, Dictionary<string, ManifestIdentifierInfo> Identifiers);
public record ManifestCustomTypeInfo(string EncodedType, bool Exported);
public record ManifestIdentifierInfo(string EncodedType, bool Exported);

View File

@@ -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>

View File

@@ -1,91 +0,0 @@
using System.Diagnostics;
using NubLang.Ast;
using NubLang.Diagnostics;
using NubLang.Generation;
using NubLang.Syntax;
var diagnostics = new List<Diagnostic>();
var syntaxTrees = new List<SyntaxTree>();
foreach (var file in args)
{
var tokenizer = new Tokenizer(file, File.ReadAllText(file));
tokenizer.Tokenize();
diagnostics.AddRange(tokenizer.Diagnostics);
var parser = new Parser();
var syntaxTree = parser.Parse(tokenizer.Tokens);
diagnostics.AddRange(parser.Diagnostics);
syntaxTrees.Add(syntaxTree);
}
var modules = Module.Collect(syntaxTrees);
var compilationUnits = new List<CompilationUnit>();
for (var i = 0; i < args.Length; i++)
{
var typeChecker = new TypeChecker(syntaxTrees[i], modules);
var compilationUnit = typeChecker.Check();
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;
}
var cPaths = new List<string>();
Directory.CreateDirectory(".build");
for (var i = 0; i < args.Length; i++)
{
var file = args[i];
var compilationUnit = compilationUnits[i];
var generator = new Generator(compilationUnit);
var directory = Path.GetDirectoryName(file);
if (!string.IsNullOrWhiteSpace(directory))
{
Directory.CreateDirectory(Path.Combine(".build", directory));
}
var path = Path.Combine(".build", Path.ChangeExtension(file, "c"));
File.WriteAllText(path, generator.Emit());
cPaths.Add(path);
}
var objectPaths = new List<string>();
foreach (var cPath in cPaths)
{
var objectPath = Path.ChangeExtension(cPath, "o");
using var compileProcess = Process.Start("clang", [
"-ffreestanding", "-std=c23",
"-g", "-c",
"-o", objectPath,
cPath,
]);
compileProcess.WaitForExit();
if (compileProcess.ExitCode != 0)
{
Console.Error.WriteLine($"clang failed with exit code {compileProcess.ExitCode}");
return 1;
}
objectPaths.Add(objectPath);
}
Console.Out.WriteLine(string.Join(' ', objectPaths));
return 0;

View File

@@ -1,77 +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.FilePath,
Range = new Range(node.Tokens.First().Span.Start.Line - 1, node.Tokens.First().Span.Start.Column - 1, node.Tokens.Last().Span.End.Line - 1, node.Tokens.Last().Span.End.Column - 1)
};
}
public static bool ContainsPosition(this Node node, int line, int character)
{
if (node.Tokens.Count == 0)
{
return false;
}
var start = node.Tokens.First().Span.Start;
var end = node.Tokens.Last().Span.End;
var startLine = start.Line - 1;
var startChar = start.Column - 1;
var endLine = end.Line - 1;
var endChar = end.Column - 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 CompilationUnit compilationUnit, int line, int character)
{
return compilationUnit
.Functions
.FirstOrDefault(x => x.ContainsPosition(line, character));
}
public static Node? DeepestNodeAtPosition(this CompilationUnit compilationUnit, int line, int character)
{
return compilationUnit.Functions
.SelectMany(x => x.DescendantsAndSelf())
.Where(n => n.ContainsPosition(line, character))
.OrderBy(n => n.Tokens.First().Span.Start.Line)
.ThenBy(n => n.Tokens.First().Span.Start.Column)
.LastOrDefault();
}
}

View File

@@ -1,180 +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 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\"",
},
new()
{
Kind = CompletionItemKind.Keyword,
Label = "import",
InsertTextFormat = InsertTextFormat.Snippet,
InsertText = "import \"$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 position = request.Position;
var uri = request.TextDocument.Uri;
var compilationUnit = workspaceManager.GetCompilationUnit(uri);
if (compilationUnit != null)
{
var function = compilationUnit.Functions.FirstOrDefault(x => x.Body != null && x.Body.ContainsPosition(position.Line, position.Character));
if (function != null)
{
completions.AddRange(_statementSnippets);
foreach (var prototype in compilationUnit.ImportedFunctions)
{
var parameterStrings = new List<string>();
foreach (var (index, parameter) in prototype.Parameters.Index())
{
parameterStrings.AddRange($"${{{index + 1}:{parameter.Name}}}");
}
completions.Add(new CompletionItem
{
Kind = CompletionItemKind.Function,
Label = $"{prototype.Module}::{prototype.Name}",
InsertTextFormat = InsertTextFormat.Snippet,
InsertText = $"{prototype.Module}::{prototype.Name}({string.Join(", ", parameterStrings)})",
});
}
foreach (var parameter in function.Prototype.Parameters)
{
completions.Add(new CompletionItem
{
Kind = CompletionItemKind.Variable,
Label = parameter.Name,
InsertText = parameter.Name,
});
}
var variables = function.Body!
.Descendants()
.OfType<VariableDeclarationNode>();
foreach (var variable in variables)
{
completions.Add(new CompletionItem
{
Kind = CompletionItemKind.Variable,
Label = variable.Name,
InsertText = variable.Name,
});
}
}
else
{
completions.AddRange(_definitionSnippets);
}
}
return new CompletionList(completions, false);
}
public override Task<CompletionItem> Handle(CompletionItem request, CancellationToken cancellationToken)
{
return Task.FromResult(new CompletionItem());
}
}

View File

@@ -1,74 +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 compilationUnit = workspaceManager.GetCompilationUnit(uri);
if (compilationUnit == null)
{
return null;
}
var line = request.Position.Line;
var character = request.Position.Character;
var node = compilationUnit.DeepestNodeAtPosition(line, character);
switch (node)
{
case VariableIdentifierNode variableIdentifierNode:
{
var function = compilationUnit.FunctionAtPosition(line, character);
var parameter = function?.Prototype.Parameters.FirstOrDefault(x => x.Name == variableIdentifierNode.Name);
if (parameter != null)
{
return new LocationOrLocationLinks(parameter.ToLocation());
}
var variable = function?.Body?
.Descendants()
.OfType<VariableDeclarationNode>()
.FirstOrDefault(x => x.Name == variableIdentifierNode.Name);
if (variable != null)
{
return new LocationOrLocationLinks(variable.ToLocation());
}
return null;
}
case FuncIdentifierNode funcIdentifierNode:
{
var prototype = compilationUnit.ImportedFunctions.FirstOrDefault(x => x.Module == funcIdentifierNode.Module && x.Name == funcIdentifierNode.Name);
if (prototype != null)
{
return new LocationOrLocationLinks(prototype.ToLocation());
}
return null;
}
default:
{
return null;
}
}
}
}

View File

@@ -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.Start.Line - 1, nubDiagnostic.Span.Value.Start.Column - 1, nubDiagnostic.Span.Value.End.Line - 1, nubDiagnostic.Span.Value.End.Column - 1)
: new Range(),
};
}
}

View File

@@ -1,142 +0,0 @@
using System.Globalization;
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 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 compilationUnit = workspaceManager.GetCompilationUnit(request.TextDocument.Uri);
if (compilationUnit == null)
{
return null;
}
var line = request.Position.Line;
var character = request.Position.Character;
var hoveredNode = compilationUnit.DeepestNodeAtPosition(line, character);
if (hoveredNode == null)
{
return null;
}
var message = CreateMessage(hoveredNode, compilationUnit);
if (message == null)
{
return null;
}
return new Hover
{
Contents = new MarkedStringsOrMarkupContent(new MarkupContent
{
Value = message,
Kind = MarkupKind.Markdown,
})
};
}
private static string? CreateMessage(Node hoveredNode, CompilationUnit compilationUnit)
{
return hoveredNode switch
{
FuncNode funcNode => CreateFuncPrototypeMessage(funcNode.Prototype),
FuncPrototypeNode funcPrototypeNode => CreateFuncPrototypeMessage(funcPrototypeNode),
FuncIdentifierNode funcIdentifierNode => CreateFuncIdentifierMessage(funcIdentifierNode, compilationUnit),
FuncParameterNode funcParameterNode => CreateTypeNameMessage("Function parameter", funcParameterNode.Name, funcParameterNode.Type),
VariableIdentifierNode variableIdentifierNode => CreateTypeNameMessage("Variable", variableIdentifierNode.Name, variableIdentifierNode.Type),
VariableDeclarationNode variableDeclarationNode => CreateTypeNameMessage("Variable declaration", variableDeclarationNode.Name, variableDeclarationNode.Type),
StructFieldAccessNode structFieldAccessNode => CreateTypeNameMessage("Struct field", $"{structFieldAccessNode.Target.Type}.{structFieldAccessNode.Field}", 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()),
// 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 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 CreateFuncIdentifierMessage(FuncIdentifierNode funcIdentifierNode, CompilationUnit compilationUnit)
{
var func = compilationUnit.ImportedFunctions.FirstOrDefault(x => x.Module == funcIdentifierNode.Module && x.Name == funcIdentifierNode.Name);
if (func == null)
{
return $"""
**Function** `{funcIdentifierNode.Module}::{funcIdentifierNode.Name}`
```nub
// Declaration not found
```
""";
}
return CreateFuncPrototypeMessage(func);
}
private static string CreateFuncPrototypeMessage(FuncPrototypeNode funcPrototypeNode)
{
var parameterText = string.Join(", ", funcPrototypeNode.Parameters.Select(x => $"{x.Name}: {x.Type}"));
var externText = funcPrototypeNode.ExternSymbol != null ? $"extern \"{funcPrototypeNode.ExternSymbol}\" " : "";
return $"""
**Function** `{funcPrototypeNode.Module}::{funcPrototypeNode.Name}`
```nub
{externText}func {funcPrototypeNode.Module}::{funcPrototypeNode.Name}({parameterText}): {funcPrototypeNode.ReturnType}
```
""";
}
}

View File

@@ -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>

View File

@@ -1,34 +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>()
.OnInitialize((server, request, ct) =>
{
var workspaceManager = server.GetRequiredService<WorkspaceManager>();
if (request.RootPath != null)
{
workspaceManager.Init(request.RootPath);
}
return Task.CompletedTask;
})
);
await server.WaitForExit;

View File

@@ -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.UpdateFile(request.TextDocument.Uri.GetFileSystemPath());
return Unit.Task;
}
public override Task<Unit> Handle(DidChangeTextDocumentParams request, CancellationToken cancellationToken)
{
workspaceManager.UpdateFile(request.TextDocument.Uri.GetFileSystemPath());
return Unit.Task;
}
public override Task<Unit> Handle(DidSaveTextDocumentParams request, CancellationToken cancellationToken)
{
workspaceManager.UpdateFile(request.TextDocument.Uri.GetFileSystemPath());
return Unit.Task;
}
public override Task<Unit> Handle(DidCloseTextDocumentParams request, CancellationToken cancellationToken)
{
workspaceManager.UpdateFile(request.TextDocument.Uri.GetFileSystemPath());
return Unit.Task;
}
protected override TextDocumentSyncRegistrationOptions CreateRegistrationOptions(TextSynchronizationCapability capability, ClientCapabilities clientCapabilities)
{
return new TextDocumentSyncRegistrationOptions();
}
}

View File

@@ -1,74 +0,0 @@
using NubLang.Ast;
using NubLang.Syntax;
using OmniSharp.Extensions.LanguageServer.Protocol;
namespace NubLang.LSP;
public class WorkspaceManager(DiagnosticsPublisher diagnosticsPublisher)
{
private readonly Dictionary<string, SyntaxTree> _syntaxTrees = new();
private readonly Dictionary<string, CompilationUnit> _compilationUnits = new();
public void Init(string rootPath)
{
var files = Directory.GetFiles(rootPath, "*.nub", SearchOption.AllDirectories);
foreach (var path in files)
{
var text = File.ReadAllText(path);
var tokenizer = new Tokenizer(path, text);
tokenizer.Tokenize();
diagnosticsPublisher.Publish(path, tokenizer.Diagnostics);
var parser = new Parser();
var parseResult = parser.Parse(tokenizer.Tokens);
diagnosticsPublisher.Publish(path, parser.Diagnostics);
_syntaxTrees[path] = parseResult;
}
foreach (var (fsPath, syntaxTree) in _syntaxTrees)
{
var modules = Module.Collect(_syntaxTrees.Select(x => x.Value).ToList());
var typeChecker = new TypeChecker(syntaxTree, modules);
var result = typeChecker.Check();
diagnosticsPublisher.Publish(fsPath, typeChecker.Diagnostics);
_compilationUnits[fsPath] = result;
}
}
public void UpdateFile(DocumentUri path)
{
var fsPath = path.GetFileSystemPath();
var text = File.ReadAllText(fsPath);
var tokenizer = new Tokenizer(fsPath, text);
tokenizer.Tokenize();
diagnosticsPublisher.Publish(path, tokenizer.Diagnostics);
var parser = new Parser();
var syntaxTree = parser.Parse(tokenizer.Tokens);
diagnosticsPublisher.Publish(path, parser.Diagnostics);
_syntaxTrees[fsPath] = syntaxTree;
var modules = Module.Collect(_syntaxTrees.Select(x => x.Value).ToList());
var typeChecker = new TypeChecker(syntaxTree, modules);
var result = typeChecker.Check();
diagnosticsPublisher.Publish(fsPath, typeChecker.Diagnostics);
_compilationUnits[fsPath] = result;
}
public void RemoveFile(DocumentUri path)
{
var fsPath = path.GetFileSystemPath();
_syntaxTrees.Remove(fsPath);
_compilationUnits.Remove(fsPath);
}
public CompilationUnit? GetCompilationUnit(DocumentUri path)
{
return _compilationUnits.GetValueOrDefault(path.GetFileSystemPath());
}
}

View File

@@ -1,15 +0,0 @@
namespace NubLang.Ast;
public sealed class CompilationUnit
{
public CompilationUnit(List<FuncNode> functions, List<NubStructType> importedStructTypes, List<FuncPrototypeNode> importedFunctions)
{
Functions = functions;
ImportedStructTypes = importedStructTypes;
ImportedFunctions = importedFunctions;
}
public List<FuncNode> Functions { get; }
public List<NubStructType> ImportedStructTypes { get; }
public List<FuncPrototypeNode> ImportedFunctions { get; }
}

View File

@@ -1,590 +0,0 @@
using NubLang.Syntax;
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;
}
}
}
#region Definitions
public abstract class DefinitionNode(List<Token> tokens, string module, string name) : Node(tokens)
{
public string Module { get; } = module;
public string Name { get; } = name;
}
public class FuncParameterNode(List<Token> tokens, string name, NubType type) : Node(tokens)
{
public string Name { get; } = name;
public NubType Type { get; } = type;
public override IEnumerable<Node> Children()
{
return [];
}
}
public class FuncPrototypeNode(List<Token> tokens, string module, string name, string? externSymbol, List<FuncParameterNode> parameters, NubType returnType) : Node(tokens)
{
public string Module { get; } = module;
public string Name { get; } = name;
public string? ExternSymbol { get; } = externSymbol;
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.Module, prototype.Name)
{
public FuncPrototypeNode Prototype { get; } = prototype;
public BlockNode? Body { get; } = body;
public override IEnumerable<Node> Children()
{
yield return Prototype;
if (Body != null)
{
yield return Body;
}
}
}
#endregion
#region Statements
public abstract class StatementNode(List<Token> tokens) : Node(tokens);
public abstract class TerminalStatementNode(List<Token> tokens) : StatementNode(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) : TerminalStatementNode(tokens)
{
public ExpressionNode? Value { get; } = value;
public override IEnumerable<Node> Children()
{
if (Value != null) yield return Value;
}
}
public class AssignmentNode(List<Token> tokens, LValueExpressionNode target, ExpressionNode value) : StatementNode(tokens)
{
public LValueExpressionNode 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, string name, ExpressionNode? assignment, NubType type) : StatementNode(tokens)
{
public string Name { get; } = name;
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) : TerminalStatementNode(tokens)
{
public override IEnumerable<Node> Children()
{
return [];
}
}
public class BreakNode(List<Token> tokens) : TerminalStatementNode(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, string elementName, string? indexName, ExpressionNode target, BlockNode body) : StatementNode(tokens)
{
public string ElementName { get; } = elementName;
public string? IndexName { get; } = indexName;
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, string elementName, string? indexName, ExpressionNode target, BlockNode body) : StatementNode(tokens)
{
public string ElementName { get; } = elementName;
public string? IndexName { get; } = indexName;
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 LValueExpressionNode(List<Token> tokens, NubType type) : ExpressionNode(tokens, type);
public abstract class RValueExpressionNode(List<Token> tokens, NubType type) : ExpressionNode(tokens, type);
public abstract class IntermediateExpression(List<Token> tokens) : ExpressionNode(tokens, new NubVoidType());
public class StringLiteralNode(List<Token> tokens, string value) : RValueExpressionNode(tokens, new NubStringType())
{
public string Value { get; } = value;
public override IEnumerable<Node> Children()
{
return [];
}
}
public class CStringLiteralNode(List<Token> tokens, string value) : RValueExpressionNode(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) : RValueExpressionNode(tokens, new NubIntType(true, 8))
{
public sbyte Value { get; } = value;
public override IEnumerable<Node> Children()
{
return [];
}
}
public class I16LiteralNode(List<Token> tokens, short value) : RValueExpressionNode(tokens, new NubIntType(true, 16))
{
public short Value { get; } = value;
public override IEnumerable<Node> Children()
{
return [];
}
}
public class I32LiteralNode(List<Token> tokens, int value) : RValueExpressionNode(tokens, new NubIntType(true, 32))
{
public int Value { get; } = value;
public override IEnumerable<Node> Children()
{
return [];
}
}
public class I64LiteralNode(List<Token> tokens, long value) : RValueExpressionNode(tokens, new NubIntType(true, 64))
{
public long Value { get; } = value;
public override IEnumerable<Node> Children()
{
return [];
}
}
public class U8LiteralNode(List<Token> tokens, byte value) : RValueExpressionNode(tokens, new NubIntType(false, 8))
{
public byte Value { get; } = value;
public override IEnumerable<Node> Children()
{
return [];
}
}
public class U16LiteralNode(List<Token> tokens, ushort value) : RValueExpressionNode(tokens, new NubIntType(false, 16))
{
public ushort Value { get; } = value;
public override IEnumerable<Node> Children()
{
return [];
}
}
public class U32LiteralNode(List<Token> tokens, uint value) : RValueExpressionNode(tokens, new NubIntType(false, 32))
{
public uint Value { get; } = value;
public override IEnumerable<Node> Children()
{
return [];
}
}
public class U64LiteralNode(List<Token> tokens, ulong value) : RValueExpressionNode(tokens, new NubIntType(false, 64))
{
public ulong Value { get; } = value;
public override IEnumerable<Node> Children()
{
return [];
}
}
public class Float32LiteralNode(List<Token> tokens, float value) : RValueExpressionNode(tokens, new NubFloatType(32))
{
public float Value { get; } = value;
public override IEnumerable<Node> Children()
{
return [];
}
}
public class Float64LiteralNode(List<Token> tokens, double value) : RValueExpressionNode(tokens, new NubFloatType(64))
{
public double Value { get; } = value;
public override IEnumerable<Node> Children()
{
return [];
}
}
public class BoolLiteralNode(List<Token> tokens, NubType type, bool value) : RValueExpressionNode(tokens, type)
{
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) : RValueExpressionNode(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) : RValueExpressionNode(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) : RValueExpressionNode(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, string name) : LValueExpressionNode(tokens, type)
{
public string Name { get; } = name;
public override IEnumerable<Node> Children()
{
return [];
}
}
public class FuncIdentifierNode(List<Token> tokens, NubType type, string module, string name, string? externSymbol) : RValueExpressionNode(tokens, type)
{
public string Module { get; } = module;
public string Name { get; } = name;
public string? ExternSymbol { get; } = externSymbol;
public override IEnumerable<Node> Children()
{
return [];
}
}
public class ArrayInitializerNode(List<Token> tokens, NubType type, List<ExpressionNode> values) : RValueExpressionNode(tokens, type)
{
public List<ExpressionNode> Values { get; } = values;
public override IEnumerable<Node> Children()
{
return Values;
}
}
public class ConstArrayInitializerNode(List<Token> tokens, NubType type, List<ExpressionNode> values) : RValueExpressionNode(tokens, type)
{
public List<ExpressionNode> Values { get; } = values;
public override IEnumerable<Node> Children()
{
return Values;
}
}
public class ArrayIndexAccessNode(List<Token> tokens, NubType type, ExpressionNode target, ExpressionNode index) : LValueExpressionNode(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) : LValueExpressionNode(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) : LValueExpressionNode(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, LValueExpressionNode lValue) : RValueExpressionNode(tokens, type)
{
public LValueExpressionNode LValue { get; } = lValue;
public override IEnumerable<Node> Children()
{
yield return LValue;
}
}
public class StructFieldAccessNode(List<Token> tokens, NubType type, ExpressionNode target, string field) : LValueExpressionNode(tokens, type)
{
public ExpressionNode Target { get; } = target;
public string Field { get; } = field;
public override IEnumerable<Node> Children()
{
yield return Target;
}
}
public class StructInitializerNode(List<Token> tokens, NubType type, Dictionary<string, ExpressionNode> initializers) : RValueExpressionNode(tokens, type)
{
public Dictionary<string, ExpressionNode> Initializers { get; } = initializers;
public override IEnumerable<Node> Children()
{
foreach (var initializer in Initializers)
{
yield return initializer.Value;
}
}
}
public class DereferenceNode(List<Token> tokens, NubType type, ExpressionNode target) : LValueExpressionNode(tokens, type)
{
public ExpressionNode Target { get; } = target;
public override IEnumerable<Node> Children()
{
yield return Target;
}
}
public class SizeNode(List<Token> tokens, NubType TargetType) : RValueExpressionNode(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) : RValueExpressionNode(tokens, type)
{
public ExpressionNode Value { get; } = value;
public override IEnumerable<Node> Children()
{
yield return Value;
}
}
public class EnumReferenceIntermediateNode(List<Token> tokens, string module, string name) : IntermediateExpression(tokens)
{
public string Module { get; } = module;
public string Name { get; } = name;
public override IEnumerable<Node> Children()
{
return [];
}
}
#endregion

View File

@@ -1,165 +0,0 @@
using System.Security.Cryptography;
using System.Text;
namespace NubLang.Ast;
public abstract class NubType : IEquatable<NubType>
{
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 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, int width) : NubType
{
public bool Signed { get; } = signed;
public int Width { get; } = width;
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(int width) : NubType
{
public int Width { get; } = width;
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 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 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 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, List<NubStructFieldType> fields) : NubType
{
public string Module { get; } = module;
public string Name { get; } = name;
public List<NubStructFieldType> Fields { get; set; } = fields;
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 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, long size) : NubType
{
public NubType ElementType { get; } = elementType;
public long Size { get; } = size;
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 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 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();
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -1,398 +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;
public DiagnosticBuilder(DiagnosticSeverity severity, string message)
{
_severity = severity;
_message = message;
}
public DiagnosticBuilder At(SyntaxNode? node)
{
if (node != null)
{
_span = SourceSpan.Merge(node.Tokens.Select(x => x.Span));
}
return this;
}
public DiagnosticBuilder At(Token? token)
{
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);
}
public static DiagnosticBuilder Error(string message) => new(DiagnosticSeverity.Error, message);
public static DiagnosticBuilder Warning(string message) => new(DiagnosticSeverity.Warning, message);
public static DiagnosticBuilder Info(string message) => new(DiagnosticSeverity.Info, message);
public DiagnosticSeverity Severity { get; }
public string Message { get; }
public string? Help { get; }
public SourceSpan? Span { get; }
private Diagnostic(DiagnosticSeverity severity, string message, string? help, SourceSpan? span)
{
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 = File.ReadAllText(Span.Value.FilePath);
var tokenizer = new Tokenizer(Span.Value.FilePath, text);
tokenizer.Tokenize();
var lines = text.Split('\n');
var startLine = Span.Value.Start.Line;
var endLine = Span.Value.End.Line;
const int CONTEXT_LINES = 3;
var contextStartLine = Math.Max(1, startLine - CONTEXT_LINES);
var contextEndLine = Math.Min(lines.Length, endLine + CONTEXT_LINES);
var numberPadding = contextEndLine.ToString().Length;
var codePadding = 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(" │ ");
sb.Append(ApplySyntaxHighlighting(line.PadRight(codePadding), i, tokenizer.Tokens));
// sb.Append(line.PadRight(codePadding));
sb.Append(" │");
sb.AppendLine();
if (i >= startLine && i <= endLine)
{
var markerStartColumn = 1;
var markerEndColumn = line.Length;
if (i == startLine)
{
markerStartColumn = Span.Value.Start.Column;
}
if (i == endLine)
{
markerEndColumn = Span.Value.End.Column;
}
var markerLength = markerEndColumn - markerStartColumn;
var marker = new string('^', markerLength);
var markerColor = Severity switch
{
DiagnosticSeverity.Info => ConsoleColors.Blue,
DiagnosticSeverity.Warning => ConsoleColors.Yellow,
DiagnosticSeverity.Error => ConsoleColors.Red,
_ => ConsoleColors.White
};
sb.Append("│ ");
sb.Append(new string(' ', numberPadding));
sb.Append(" │ ");
sb.Append(new string(' ', markerStartColumn - 1));
sb.Append(ConsoleColors.Colorize(marker, markerColor));
sb.Append(new string(' ', codePadding - (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.Start.Line == lineNumber)
.OrderBy(t => t.Span.Start.Column)
.ToList();
if (lineTokens.Count == 0)
{
return line;
}
var currentColumn = 1;
foreach (var token in lineTokens)
{
var tokenStart = token.Span.Start.Column;
var tokenEnd = token.Span.End.Column;
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 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;
}
}

View File

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

View File

@@ -1,97 +0,0 @@
using NubLang.Ast;
namespace NubLang.Generation;
public static class CType
{
public static string Create(NubType type, string? variableName = null, bool constArraysAsPointers = true)
{
return type switch
{
NubVoidType => "void" + (variableName != null ? $" {variableName}" : ""),
NubBoolType => "bool" + (variableName != null ? $" {variableName}" : ""),
NubIntType intType => CreateIntType(intType, variableName),
NubFloatType floatType => CreateFloatType(floatType, variableName),
NubPointerType ptr => CreatePointerType(ptr, variableName),
NubSliceType => "struct nub_slice" + (variableName != null ? $" {variableName}" : ""),
NubStringType => "struct nub_string" + (variableName != null ? $" {variableName}" : ""),
NubConstArrayType arr => CreateConstArrayType(arr, variableName, constArraysAsPointers),
NubArrayType arr => CreateArrayType(arr, variableName),
NubFuncType fn => CreateFuncType(fn, variableName),
NubStructType st => $"struct {st.Module}_{st.Name}_{NameMangler.Mangle(st)}" + (variableName != null ? $" {variableName}" : ""),
_ => throw new NotSupportedException($"C type generation not supported for: {type}")
};
}
private static string CreateIntType(NubIntType intType, string? varName)
{
var cType = intType.Width switch
{
8 => intType.Signed ? "char" : "unsigned char",
16 => intType.Signed ? "short" : "unsigned short",
32 => intType.Signed ? "int" : "unsigned int",
64 => intType.Signed ? "long long" : "unsigned long long",
_ => throw new NotSupportedException($"Unsupported integer width: {intType.Width}")
};
return cType + (varName != null ? $" {varName}" : "");
}
private static string CreateFloatType(NubFloatType floatType, string? varName)
{
var cType = floatType.Width switch
{
32 => "float",
64 => "double",
_ => throw new NotSupportedException($"Unsupported float width: {floatType.Width}")
};
return cType + (varName != null ? $" {varName}" : "");
}
private static string CreatePointerType(NubPointerType ptr, string? varName)
{
var baseType = Create(ptr.BaseType);
return baseType + "*" + (varName != null ? $" {varName}" : "");
}
private static string CreateConstArrayType(NubConstArrayType arr, string? varName, bool inStructDef)
{
var elementType = Create(arr.ElementType);
// Treat const arrays as pointers unless in a struct definition
if (!inStructDef)
{
return elementType + "*" + (varName != null ? $" {varName}" : "");
}
if (varName != null)
{
return $"{elementType} {varName}[{arr.Size}]";
}
return $"{elementType}[{arr.Size}]";
}
private static string CreateArrayType(NubArrayType arr, string? varName)
{
var elementType = Create(arr.ElementType);
return elementType + "*" + (varName != null ? $" {varName}" : "");
}
private static string CreateFuncType(NubFuncType fn, string? varName)
{
var returnType = Create(fn.ReturnType);
var parameters = string.Join(", ", fn.Parameters.Select(p => Create(p)));
if (string.IsNullOrEmpty(parameters))
{
parameters = "void";
}
if (varName != null)
{
return $"{returnType} (*{varName})({parameters})";
}
return $"{returnType} (*)({parameters})";
}
}

View File

@@ -1,590 +0,0 @@
using System.Diagnostics;
using System.Text;
using NubLang.Ast;
using NubLang.Syntax;
namespace NubLang.Generation;
public class Generator
{
private readonly CompilationUnit _compilationUnit;
private readonly IndentedTextWriter _writer;
private readonly Stack<List<DeferNode>> _deferStack = [];
private int _tmpIndex;
public Generator(CompilationUnit compilationUnit)
{
_compilationUnit = compilationUnit;
_writer = new IndentedTextWriter();
}
// todo(nub31): Handle name collisions
private string NewTmp()
{
return $"_t{++_tmpIndex}";
}
private static string FuncName(string module, string name, string? externSymbol)
{
return externSymbol ?? $"{module}_{name}";
}
public string Emit()
{
_writer.WriteLine("""
struct nub_string
{
unsigned long long length;
char *data;
};
struct nub_slice
{
unsigned long long length;
void *data;
};
""");
foreach (var structType in _compilationUnit.ImportedStructTypes)
{
_writer.WriteLine(CType.Create(structType));
_writer.WriteLine("{");
using (_writer.Indent())
{
foreach (var field in structType.Fields)
{
_writer.WriteLine($"{CType.Create(field.Type, field.Name, constArraysAsPointers: false)};");
}
}
_writer.WriteLine("};");
_writer.WriteLine();
}
// note(nub31): Forward declarations
foreach (var prototype in _compilationUnit.ImportedFunctions)
{
EmitLine(prototype.Tokens.FirstOrDefault());
var parameters = prototype.Parameters.Count != 0
? string.Join(", ", prototype.Parameters.Select(x => CType.Create(x.Type, x.Name)))
: "void";
var name = FuncName(prototype.Module, prototype.Name, prototype.ExternSymbol);
_writer.WriteLine($"{CType.Create(prototype.ReturnType, name)}({parameters});");
_writer.WriteLine();
}
// note(nub31): Normal functions
foreach (var funcNode in _compilationUnit.Functions)
{
if (funcNode.Body == null) continue;
EmitLine(funcNode.Tokens.FirstOrDefault());
var parameters = funcNode.Prototype.Parameters.Count != 0
? string.Join(", ", funcNode.Prototype.Parameters.Select(x => CType.Create(x.Type, x.Name)))
: "void";
var name = FuncName(funcNode.Module, funcNode.Name, funcNode.Prototype.ExternSymbol);
_writer.WriteLine($"{CType.Create(funcNode.Prototype.ReturnType, name)}({parameters})");
_writer.WriteLine("{");
using (_writer.Indent())
{
EmitBlock(funcNode.Body);
}
_writer.WriteLine("}");
_writer.WriteLine();
}
return _writer.ToString();
}
private void EmitStatement(StatementNode statementNode)
{
EmitLine(statementNode.Tokens.FirstOrDefault());
switch (statementNode)
{
case AssignmentNode assignmentNode:
EmitAssignment(assignmentNode);
break;
case BlockNode blockNode:
_writer.WriteLine("{");
using (_writer.Indent())
{
EmitBlock(blockNode);
}
_writer.WriteLine("}");
break;
case BreakNode breakNode:
EmitBreak(breakNode);
break;
case ContinueNode continueNode:
EmitContinue(continueNode);
break;
case DeferNode deferNode:
EmitDefer(deferNode);
break;
case ForConstArrayNode forConstArrayNode:
EmitForConstArray(forConstArrayNode);
break;
case ForSliceNode forSliceNode:
EmitForSlice(forSliceNode);
break;
case IfNode ifNode:
EmitIf(ifNode);
break;
case ReturnNode returnNode:
EmitReturn(returnNode);
break;
case StatementFuncCallNode statementFuncCallNode:
EmitStatementFuncCall(statementFuncCallNode);
break;
case VariableDeclarationNode variableDeclarationNode:
EmitVariableDeclaration(variableDeclarationNode);
break;
case WhileNode whileNode:
EmitWhile(whileNode);
break;
default:
throw new ArgumentOutOfRangeException(nameof(statementNode));
}
}
private void EmitLine(Token? token)
{
if (token == null) return;
var file = token.Span.FilePath;
var line = token.Span.Start.Line;
_writer.WriteLine($"#line {line} \"{file}\"");
}
private void EmitAssignment(AssignmentNode assignmentNode)
{
var target = EmitExpression(assignmentNode.Target);
var value = EmitExpression(assignmentNode.Value);
_writer.WriteLine($"{target} = {value};");
}
private void EmitBreak(BreakNode _)
{
// todo(nub31): Emit deferred statements
_writer.WriteLine("break;");
}
private void EmitContinue(ContinueNode _)
{
// todo(nub31): Emit deferred statements
_writer.WriteLine("continue;");
}
private void EmitDefer(DeferNode deferNode)
{
_deferStack.Peek().Add(deferNode);
}
private void EmitForSlice(ForSliceNode forSliceNode)
{
var targetType = (NubSliceType)forSliceNode.Target.Type;
var target = EmitExpression(forSliceNode.Target);
var indexName = forSliceNode.IndexName ?? NewTmp();
_writer.WriteLine($"for (unsigned long long {indexName} = 0; {indexName} < {target}.length; ++{indexName})");
_writer.WriteLine("{");
using (_writer.Indent())
{
_writer.WriteLine($"{CType.Create(targetType.ElementType, forSliceNode.ElementName)} = (({CType.Create(targetType.ElementType)}*){target}.data)[{indexName}];");
EmitBlock(forSliceNode.Body);
}
_writer.WriteLine("}");
}
private void EmitForConstArray(ForConstArrayNode forConstArrayNode)
{
var targetType = (NubConstArrayType)forConstArrayNode.Target.Type;
var target = EmitExpression(forConstArrayNode.Target);
var indexName = forConstArrayNode.IndexName ?? NewTmp();
_writer.WriteLine($"for (unsigned long long {indexName} = 0; {indexName} < {targetType.Size}; ++{indexName})");
_writer.WriteLine("{");
using (_writer.Indent())
{
_writer.WriteLine($"{CType.Create(targetType.ElementType, forConstArrayNode.ElementName)} = {target}[{indexName}];");
EmitBlock(forConstArrayNode.Body);
}
_writer.WriteLine("}");
}
private void EmitIf(IfNode ifNode, bool elseIf = false)
{
var condition = EmitExpression(ifNode.Condition);
_writer.WriteLine($"{(elseIf ? "else " : "")}if ({condition})");
_writer.WriteLine("{");
using (_writer.Indent())
{
EmitBlock(ifNode.Body);
}
_writer.WriteLine("}");
ifNode.Else?.Match
(
elseIfNode => EmitIf(elseIfNode, true),
elseNode =>
{
_writer.WriteLine("else");
_writer.WriteLine("{");
using (_writer.Indent())
{
EmitBlock(elseNode);
}
_writer.WriteLine("}");
}
);
}
private void EmitReturn(ReturnNode returnNode)
{
if (returnNode.Value == null)
{
var blockDefers = _deferStack.Peek();
for (var i = blockDefers.Count - 1; i >= 0; i--)
{
EmitStatement(blockDefers[i].Statement);
}
_writer.WriteLine("return;");
}
else
{
var returnValue = EmitExpression(returnNode.Value);
if (_deferStack.Peek().Count != 0)
{
var tmp = NewTmp();
_writer.WriteLine($"{CType.Create(returnNode.Value.Type, tmp)} = {returnValue};");
var blockDefers = _deferStack.Peek();
for (var i = blockDefers.Count - 1; i >= 0; i--)
{
EmitStatement(blockDefers[i].Statement);
}
EmitLine(returnNode.Tokens.FirstOrDefault());
_writer.WriteLine($"return {tmp};");
}
else
{
EmitLine(returnNode.Tokens.FirstOrDefault());
_writer.WriteLine($"return {returnValue};");
}
}
}
private void EmitStatementFuncCall(StatementFuncCallNode statementFuncCallNode)
{
var funcCall = EmitFuncCall(statementFuncCallNode.FuncCall);
_writer.WriteLine($"{funcCall};");
}
private void EmitVariableDeclaration(VariableDeclarationNode variableDeclarationNode)
{
if (variableDeclarationNode.Assignment != null)
{
var value = EmitExpression(variableDeclarationNode.Assignment);
_writer.WriteLine($"{CType.Create(variableDeclarationNode.Type, variableDeclarationNode.Name)} = {value};");
}
else
{
_writer.WriteLine($"{CType.Create(variableDeclarationNode.Type, variableDeclarationNode.Name)};");
}
}
private void EmitWhile(WhileNode whileNode)
{
var condition = EmitExpression(whileNode.Condition);
_writer.WriteLine($"while ({condition})");
_writer.WriteLine("{");
using (_writer.Indent())
{
EmitBlock(whileNode.Body);
}
_writer.WriteLine("}");
}
private string EmitExpression(ExpressionNode expressionNode)
{
if (expressionNode is IntermediateExpression)
{
throw new UnreachableException("Type checker fucked up");
}
var expr = expressionNode switch
{
ArrayIndexAccessNode arrayIndexAccessNode => EmitArrayIndexAccess(arrayIndexAccessNode),
ArrayInitializerNode arrayInitializerNode => EmitArrayInitializer(arrayInitializerNode),
BinaryExpressionNode binaryExpressionNode => EmitBinaryExpression(binaryExpressionNode),
BoolLiteralNode boolLiteralNode => boolLiteralNode.Value ? "true" : "false",
ConstArrayIndexAccessNode constArrayIndexAccessNode => EmitConstArrayIndexAccess(constArrayIndexAccessNode),
ConstArrayInitializerNode constArrayInitializerNode => EmitConstArrayInitializer(constArrayInitializerNode),
CStringLiteralNode cStringLiteralNode => $"\"{cStringLiteralNode.Value}\"",
DereferenceNode dereferenceNode => EmitDereference(dereferenceNode),
Float32LiteralNode float32LiteralNode => EmitFloat32Literal(float32LiteralNode),
Float64LiteralNode float64LiteralNode => EmitFloat64Literal(float64LiteralNode),
CastNode castNode => EmitCast(castNode),
FuncCallNode funcCallNode => EmitFuncCall(funcCallNode),
FuncIdentifierNode funcIdentifierNode => FuncName(funcIdentifierNode.Module, funcIdentifierNode.Name, funcIdentifierNode.ExternSymbol),
AddressOfNode addressOfNode => EmitAddressOf(addressOfNode),
SizeNode sizeBuiltinNode => $"sizeof({CType.Create(sizeBuiltinNode.TargetType)})",
SliceIndexAccessNode sliceIndexAccessNode => EmitSliceArrayIndexAccess(sliceIndexAccessNode),
StringLiteralNode stringLiteralNode => EmitStringLiteral(stringLiteralNode),
StructFieldAccessNode structFieldAccessNode => EmitStructFieldAccess(structFieldAccessNode),
StructInitializerNode structInitializerNode => EmitStructInitializer(structInitializerNode),
I8LiteralNode i8LiteralNode => EmitI8Literal(i8LiteralNode),
I16LiteralNode i16LiteralNode => EmitI16Literal(i16LiteralNode),
I32LiteralNode i32LiteralNode => EmitI32Literal(i32LiteralNode),
I64LiteralNode i64LiteralNode => EmitI64Literal(i64LiteralNode),
U8LiteralNode u8LiteralNode => EmitU8Literal(u8LiteralNode),
U16LiteralNode u16LiteralNode => EmitU16Literal(u16LiteralNode),
U32LiteralNode u32LiteralNode => EmitU32Literal(u32LiteralNode),
U64LiteralNode u64LiteralNode => EmitU64Literal(u64LiteralNode),
UnaryExpressionNode unaryExpressionNode => EmitUnaryExpression(unaryExpressionNode),
VariableIdentifierNode variableIdentifierNode => variableIdentifierNode.Name,
_ => throw new ArgumentOutOfRangeException(nameof(expressionNode))
};
return $"({expr})";
}
private string EmitArrayIndexAccess(ArrayIndexAccessNode arrayIndexAccessNode)
{
var target = EmitExpression(arrayIndexAccessNode.Target);
var index = EmitExpression(arrayIndexAccessNode.Index);
return $"{target}[{index}]";
}
private string EmitArrayInitializer(ArrayInitializerNode arrayInitializerNode)
{
var values = new List<string>();
foreach (var value in arrayInitializerNode.Values)
{
values.Add(EmitExpression(value));
}
var arrayType = (NubArrayType)arrayInitializerNode.Type;
return $"({CType.Create(arrayType.ElementType)}[]){{{string.Join(", ", values)}}}";
}
private string EmitBinaryExpression(BinaryExpressionNode binaryExpressionNode)
{
var left = EmitExpression(binaryExpressionNode.Left);
var right = EmitExpression(binaryExpressionNode.Right);
var op = binaryExpressionNode.Operator switch
{
BinaryOperator.Plus => "+",
BinaryOperator.Minus => "-",
BinaryOperator.Multiply => "*",
BinaryOperator.Divide => "/",
BinaryOperator.Modulo => "%",
BinaryOperator.Equal => "==",
BinaryOperator.NotEqual => "!=",
BinaryOperator.LessThan => "<",
BinaryOperator.LessThanOrEqual => "<=",
BinaryOperator.GreaterThan => ">",
BinaryOperator.GreaterThanOrEqual => ">=",
BinaryOperator.LogicalAnd => "&&",
BinaryOperator.LogicalOr => "||",
BinaryOperator.BitwiseAnd => "&",
BinaryOperator.BitwiseOr => "|",
BinaryOperator.BitwiseXor => "^",
BinaryOperator.LeftShift => "<<",
BinaryOperator.RightShift => ">>",
_ => throw new ArgumentOutOfRangeException()
};
return $"{left} {op} {right}";
}
private string EmitConstArrayIndexAccess(ConstArrayIndexAccessNode constArrayIndexAccessNode)
{
var target = EmitExpression(constArrayIndexAccessNode.Target);
var index = EmitExpression(constArrayIndexAccessNode.Index);
// todo(nub31): We can emit bounds checking here
return $"{target}[{index}]";
}
private string EmitConstArrayInitializer(ConstArrayInitializerNode arrayInitializerNode)
{
var values = new List<string>();
foreach (var value in arrayInitializerNode.Values)
{
values.Add(EmitExpression(value));
}
var arrayType = (NubConstArrayType)arrayInitializerNode.Type;
return $"({CType.Create(arrayType.ElementType)}[{arrayType.Size}]){{{string.Join(", ", values)}}}";
}
private string EmitDereference(DereferenceNode dereferenceNode)
{
var pointer = EmitExpression(dereferenceNode.Target);
return $"*{pointer}";
}
private string EmitFloat32Literal(Float32LiteralNode float32LiteralNode)
{
var str = float32LiteralNode.Value.ToString("G9", System.Globalization.CultureInfo.InvariantCulture);
if (!str.Contains('.') && !str.Contains('e') && !str.Contains('E'))
{
str += ".0";
}
return str + "f";
}
private string EmitFloat64Literal(Float64LiteralNode float64LiteralNode)
{
var str = float64LiteralNode.Value.ToString("G17", System.Globalization.CultureInfo.InvariantCulture);
if (!str.Contains('.') && !str.Contains('e') && !str.Contains('E'))
{
str += ".0";
}
return str;
}
private string EmitCast(CastNode castNode)
{
var value = EmitExpression(castNode.Value);
if (castNode is { Type: NubSliceType sliceType, Value.Type: NubConstArrayType arrayType })
{
return $"({CType.Create(sliceType)}){{.length = {arrayType.Size}, .data = (void*){value}}}";
}
return $"({CType.Create(castNode.Type)}){value}";
}
private string EmitFuncCall(FuncCallNode funcCallNode)
{
var name = EmitExpression(funcCallNode.Expression);
var parameterNames = funcCallNode.Parameters.Select(EmitExpression).ToList();
return $"{name}({string.Join(", ", parameterNames)})";
}
private string EmitAddressOf(AddressOfNode addressOfNode)
{
var value = EmitExpression(addressOfNode.LValue);
return $"&{value}";
}
private string EmitSliceArrayIndexAccess(SliceIndexAccessNode sliceIndexAccessNode)
{
var targetType = (NubSliceType)sliceIndexAccessNode.Target.Type;
var target = EmitExpression(sliceIndexAccessNode.Target);
var index = EmitExpression(sliceIndexAccessNode.Index);
// todo(nub31): We can emit bounds checking here
return $"(({CType.Create(targetType.ElementType)}*){target}.data)[{index}]";
}
private string EmitStringLiteral(StringLiteralNode stringLiteralNode)
{
var length = Encoding.UTF8.GetByteCount(stringLiteralNode.Value);
return $"(nub_string){{.length = {length}, .data = \"{stringLiteralNode.Value}\"}}";
}
private string EmitStructFieldAccess(StructFieldAccessNode structFieldAccessNode)
{
var structExpr = EmitExpression(structFieldAccessNode.Target);
return $"{structExpr}.{structFieldAccessNode.Field}";
}
private string EmitStructInitializer(StructInitializerNode structInitializerNode)
{
var initValues = new List<string>();
foreach (var initializer in structInitializerNode.Initializers)
{
var value = EmitExpression(initializer.Value);
initValues.Add($".{initializer.Key} = {value}");
}
var initString = initValues.Count == 0
? "0"
: string.Join(", ", initValues);
return $"({CType.Create(structInitializerNode.Type)}){{{initString}}}";
}
private string EmitI8Literal(I8LiteralNode i8LiteralNode)
{
return i8LiteralNode.Value.ToString();
}
private string EmitI16Literal(I16LiteralNode i16LiteralNode)
{
return i16LiteralNode.Value.ToString();
}
private string EmitI32Literal(I32LiteralNode i32LiteralNode)
{
return i32LiteralNode.Value.ToString();
}
private string EmitI64Literal(I64LiteralNode i64LiteralNode)
{
return i64LiteralNode.Value + "LL";
}
private string EmitU8Literal(U8LiteralNode u8LiteralNode)
{
return u8LiteralNode.Value.ToString();
}
private string EmitU16Literal(U16LiteralNode u16LiteralNode)
{
return u16LiteralNode.Value.ToString();
}
private string EmitU32Literal(U32LiteralNode u32LiteralNode)
{
return u32LiteralNode.Value.ToString();
}
private string EmitU64Literal(U64LiteralNode u64LiteralNode)
{
return u64LiteralNode.Value + "ULL";
}
private string EmitUnaryExpression(UnaryExpressionNode unaryExpressionNode)
{
var value = EmitExpression(unaryExpressionNode.Operand);
return unaryExpressionNode.Operator switch
{
UnaryOperator.Negate => $"-{value}",
UnaryOperator.Invert => $"!{value}",
_ => throw new ArgumentOutOfRangeException()
};
}
private void EmitBlock(BlockNode blockNode)
{
_deferStack.Push([]);
foreach (var statementNode in blockNode.Statements)
{
EmitStatement(statementNode);
}
var blockDefers = _deferStack.Pop();
for (var i = blockDefers.Count - 1; i >= 0; i--)
{
EmitStatement(blockDefers[i].Statement);
}
}
}

View File

@@ -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;
}
}
}

View File

@@ -1,47 +0,0 @@
namespace NubLang.Syntax;
public sealed class Module
{
public static Dictionary<string, Module> Collect(List<SyntaxTree> syntaxTrees)
{
var modules = new Dictionary<string, Module>();
foreach (var syntaxTree in syntaxTrees)
{
if (!modules.TryGetValue(syntaxTree.ModuleName, out var module))
{
module = new Module();
modules.Add(syntaxTree.ModuleName, module);
}
module._definitions.AddRange(syntaxTree.Definitions);
}
return modules;
}
private readonly List<DefinitionSyntax> _definitions = [];
public List<StructSyntax> Structs(bool includePrivate)
{
return _definitions
.OfType<StructSyntax>()
.Where(x => x.Exported || includePrivate)
.ToList();
}
public List<FuncSyntax> Functions(bool includePrivate)
{
return _definitions
.OfType<FuncSyntax>()
.Where(x => x.Exported || includePrivate)
.ToList();
}
public List<EnumSyntax> Enums(bool includePrivate)
{
return _definitions
.OfType<EnumSyntax>()
.Where(x => x.Exported || includePrivate)
.ToList();
}
}

View File

@@ -1,951 +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 HasToken => CurrentToken != null;
public List<Diagnostic> Diagnostics { get; } = [];
public SyntaxTree Parse(List<Token> tokens)
{
Diagnostics.Clear();
_tokens = tokens;
_tokenIndex = 0;
string? moduleName = null;
var imports = new List<string>();
var definitions = new List<DefinitionSyntax>();
while (HasToken)
{
try
{
var startIndex = _tokenIndex;
if (TryExpectSymbol(Symbol.Import))
{
var name = ExpectStringLiteral();
if (imports.Contains(name.Value))
{
Diagnostics.Add(Diagnostic
.Warning($"Module {name.Value} is imported twice")
.At(name)
.WithHelp($"Remove duplicate import \"{name.Value}\"")
.Build());
}
else
{
imports.Add(name.Value);
}
continue;
}
if (TryExpectSymbol(Symbol.Module))
{
if (moduleName != null)
{
throw new ParseException(Diagnostic
.Error("Module is declared more than once")
.At(CurrentToken)
.WithHelp("Remove duplicate module declaration")
.Build());
}
moduleName = ExpectStringLiteral().Value;
continue;
}
var exported = TryExpectSymbol(Symbol.Export);
if (TryExpectSymbol(Symbol.Extern))
{
var externSymbol = ExpectStringLiteral();
ExpectSymbol(Symbol.Func);
definitions.Add(ParseFunc(startIndex, exported, externSymbol.Value));
continue;
}
var keyword = ExpectSymbol();
DefinitionSyntax definition = keyword.Symbol switch
{
Symbol.Func => ParseFunc(startIndex, exported, null),
Symbol.Struct => ParseStruct(startIndex, exported),
Symbol.Enum => ParseEnum(startIndex, exported),
_ => throw new ParseException(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)
.Build())
};
definitions.Add(definition);
}
catch (ParseException e)
{
Diagnostics.Add(e.Diagnostic);
while (HasToken)
{
if (CurrentToken is SymbolToken { Symbol: Symbol.Extern or Symbol.Func or Symbol.Struct })
{
break;
}
Next();
}
}
}
return new SyntaxTree(definitions, moduleName ?? "default", imports);
}
private FuncParameterSyntax ParseFuncParameter()
{
var startIndex = _tokenIndex;
var name = ExpectIdentifier();
ExpectSymbol(Symbol.Colon);
var type = ParseType();
return new FuncParameterSyntax(GetTokens(startIndex), name.Value, type);
}
private FuncSyntax ParseFunc(int startIndex, bool exported, string? externSymbol)
{
var name = ExpectIdentifier();
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.Value, 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)
{
var name = ExpectIdentifier();
ExpectSymbol(Symbol.OpenBrace);
List<StructFieldSyntax> fields = [];
while (!TryExpectSymbol(Symbol.CloseBrace))
{
var memberStartIndex = _tokenIndex;
var fieldName = ExpectIdentifier().Value;
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.Value, exported, 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);
long value = -1;
while (!TryExpectSymbol(Symbol.CloseBrace))
{
var memberStartIndex = _tokenIndex;
var fieldName = ExpectIdentifier().Value;
long fieldValue;
if (TryExpectSymbol(Symbol.Assign))
{
if (!TryExpectIntLiteral(out var intLiteralToken))
{
throw new ParseException(Diagnostic
.Error("Value of enum field must be an integer literal")
.At(CurrentToken)
.Build());
}
fieldValue = Convert.ToInt64(intLiteralToken.Value, intLiteralToken.Base);
value = fieldValue;
}
else
{
fieldValue = value + 1;
value = fieldValue;
}
fields.Add(new EnumFieldSyntax(GetTokens(memberStartIndex), fieldName, fieldValue));
}
return new EnumSyntax(GetTokens(startIndex), name.Value, exported, type, fields);
}
private StatementSyntax ParseStatement()
{
var startIndex = _tokenIndex;
if (TryExpectSymbol(out var symbol))
{
switch (symbol)
{
case Symbol.OpenBrace:
return ParseBlock(startIndex);
case Symbol.Return:
return ParseReturn(startIndex);
case Symbol.If:
return ParseIf(startIndex);
case Symbol.While:
return ParseWhile(startIndex);
case Symbol.For:
return ParseFor(startIndex);
case Symbol.Let:
return ParseVariableDeclaration(startIndex);
case Symbol.Defer:
return ParseDefer(startIndex);
case Symbol.Break:
return new BreakSyntax(GetTokens(startIndex));
case Symbol.Continue:
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().Value;
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().Value;
string? indexName = null;
if (TryExpectSymbol(Symbol.Comma))
{
indexName = ExpectIdentifier().Value;
}
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 && TryGetBinaryOperator(symbolToken.Symbol, out var op) && GetBinaryOperatorPrecedence(op.Value) >= precedence)
{
Next();
var right = ParseExpression(GetBinaryOperatorPrecedence(op.Value) + 1);
left = new BinaryExpressionSyntax(GetTokens(startIndex), left, op.Value, right);
}
return left;
}
private static int GetBinaryOperatorPrecedence(BinaryOperatorSyntax operatorSyntax)
{
return operatorSyntax switch
{
BinaryOperatorSyntax.Multiply => 10,
BinaryOperatorSyntax.Divide => 10,
BinaryOperatorSyntax.Modulo => 10,
BinaryOperatorSyntax.Plus => 9,
BinaryOperatorSyntax.Minus => 9,
BinaryOperatorSyntax.LeftShift => 8,
BinaryOperatorSyntax.RightShift => 8,
BinaryOperatorSyntax.GreaterThan => 7,
BinaryOperatorSyntax.GreaterThanOrEqual => 7,
BinaryOperatorSyntax.LessThan => 7,
BinaryOperatorSyntax.LessThanOrEqual => 7,
BinaryOperatorSyntax.Equal => 7,
BinaryOperatorSyntax.NotEqual => 7,
BinaryOperatorSyntax.BitwiseAnd => 6,
BinaryOperatorSyntax.BitwiseXor => 5,
BinaryOperatorSyntax.BitwiseOr => 4,
BinaryOperatorSyntax.LogicalAnd => 3,
BinaryOperatorSyntax.LogicalOr => 2,
_ => throw new ArgumentOutOfRangeException(nameof(operatorSyntax), operatorSyntax, null)
};
}
private bool TryGetBinaryOperator(Symbol symbol, [NotNullWhen(true)] out BinaryOperatorSyntax? binaryExpressionOperator)
{
switch (symbol)
{
case Symbol.Equal:
binaryExpressionOperator = BinaryOperatorSyntax.Equal;
return true;
case Symbol.NotEqual:
binaryExpressionOperator = BinaryOperatorSyntax.NotEqual;
return true;
case Symbol.LessThan:
binaryExpressionOperator = BinaryOperatorSyntax.LessThan;
return true;
case Symbol.LessThanOrEqual:
binaryExpressionOperator = BinaryOperatorSyntax.LessThanOrEqual;
return true;
case Symbol.GreaterThan:
binaryExpressionOperator = BinaryOperatorSyntax.GreaterThan;
return true;
case Symbol.GreaterThanOrEqual:
binaryExpressionOperator = BinaryOperatorSyntax.GreaterThanOrEqual;
return true;
case Symbol.And:
binaryExpressionOperator = BinaryOperatorSyntax.LogicalAnd;
return true;
case Symbol.Or:
binaryExpressionOperator = BinaryOperatorSyntax.LogicalOr;
return true;
case Symbol.Plus:
binaryExpressionOperator = BinaryOperatorSyntax.Plus;
return true;
case Symbol.Minus:
binaryExpressionOperator = BinaryOperatorSyntax.Minus;
return true;
case Symbol.Star:
binaryExpressionOperator = BinaryOperatorSyntax.Multiply;
return true;
case Symbol.ForwardSlash:
binaryExpressionOperator = BinaryOperatorSyntax.Divide;
return true;
case Symbol.Percent:
binaryExpressionOperator = BinaryOperatorSyntax.Modulo;
return true;
case Symbol.LeftShift:
binaryExpressionOperator = BinaryOperatorSyntax.LeftShift;
return true;
case Symbol.RightShift:
binaryExpressionOperator = BinaryOperatorSyntax.RightShift;
return true;
case Symbol.Ampersand:
binaryExpressionOperator = BinaryOperatorSyntax.BitwiseAnd;
return true;
case Symbol.Pipe:
binaryExpressionOperator = BinaryOperatorSyntax.BitwiseOr;
return true;
case Symbol.Caret:
binaryExpressionOperator = BinaryOperatorSyntax.BitwiseXor;
return true;
default:
binaryExpressionOperator = null;
return false;
}
}
private ExpressionSyntax ParsePrimaryExpression()
{
var startIndex = _tokenIndex;
var token = ExpectToken();
var expr = token switch
{
BoolLiteralToken boolLiteral => new BoolLiteralSyntax(GetTokens(startIndex), boolLiteral.Value),
StringLiteralToken stringLiteral => new StringLiteralSyntax(GetTokens(startIndex), stringLiteral.Value),
FloatLiteralToken floatLiteral => new FloatLiteralSyntax(GetTokens(startIndex), floatLiteral.Value),
IntLiteralToken intLiteral => new IntLiteralSyntax(GetTokens(startIndex), intLiteral.Value, intLiteral.Base),
IdentifierToken identifier => ParseIdentifier(startIndex, identifier),
SymbolToken symbolToken => symbolToken.Symbol switch
{
Symbol.OpenParen => ParseParenthesizedExpression(),
Symbol.Minus => new UnaryExpressionSyntax(GetTokens(startIndex), UnaryOperatorSyntax.Negate, ParsePrimaryExpression()),
Symbol.Bang => new UnaryExpressionSyntax(GetTokens(startIndex), UnaryOperatorSyntax.Invert, ParsePrimaryExpression()),
Symbol.OpenBracket => ParseArrayInitializer(startIndex),
Symbol.OpenBrace => new StructInitializerSyntax(GetTokens(startIndex), null, ParseStructInitializerBody()),
Symbol.Struct => ParseStructInitializer(startIndex),
Symbol.At => ParseBuiltinFunction(startIndex),
_ => throw new ParseException(Diagnostic
.Error($"Unexpected symbol '{symbolToken.Symbol}' in expression")
.WithHelp("Expected '(', '-', '!', '[' or '{'")
.At(symbolToken)
.Build())
},
_ => throw new ParseException(Diagnostic
.Error($"Unexpected token '{token.GetType().Name}' in expression")
.WithHelp("Expected literal, identifier, or parenthesized expression")
.At(token)
.Build())
};
return ParsePostfixOperators(expr);
}
private ExpressionSyntax 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 ParseException(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.Value, name.Value);
}
return new LocalIdentifierSyntax(GetTokens(startIndex), identifier.Value);
}
private ExpressionSyntax ParseParenthesizedExpression()
{
var expression = ParseExpression();
ExpectSymbol(Symbol.CloseParen);
return expression;
}
private ExpressionSyntax ParsePostfixOperators(ExpressionSyntax expr)
{
var startIndex = _tokenIndex;
while (HasToken)
{
if (TryExpectSymbol(Symbol.Ampersand))
{
expr = new AddressOfSyntax(GetTokens(startIndex), expr);
continue;
}
if (TryExpectSymbol(Symbol.Caret))
{
expr = new DereferenceSyntax(GetTokens(startIndex), expr);
continue;
}
if (TryExpectSymbol(Symbol.Period))
{
var member = ExpectIdentifier().Value;
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 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 Dictionary<string, ExpressionSyntax> ParseStructInitializerBody()
{
Dictionary<string, ExpressionSyntax> initializers = [];
while (!TryExpectSymbol(Symbol.CloseBrace))
{
var name = ExpectIdentifier().Value;
ExpectSymbol(Symbol.Assign);
var value = ParseExpression();
initializers.Add(name, value);
}
return initializers;
}
private BlockSyntax ParseBlock()
{
var startIndex = _tokenIndex;
ExpectSymbol(Symbol.OpenBrace);
return ParseBlock(startIndex);
}
private BlockSyntax ParseBlock(int startIndex)
{
List<StatementSyntax> statements = [];
while (!TryExpectSymbol(Symbol.CloseBrace))
{
try
{
statements.Add(ParseStatement());
}
catch (ParseException 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' && int.TryParse(name.Value[1..], out var size))
{
if (size is not 8 and not 16 and not 32 and not 64)
{
throw new ParseException(Diagnostic
.Error("Arbitrary uint size is not supported")
.WithHelp("Use u8, u16, u32 or u64")
.At(name)
.Build());
}
return new IntTypeSyntax(GetTokens(startIndex), false, size);
}
if (name.Value[0] == 'i' && int.TryParse(name.Value[1..], out size))
{
if (size is not 8 and not 16 and not 32 and not 64)
{
throw new ParseException(Diagnostic
.Error("Arbitrary int size is not supported")
.WithHelp("Use i8, i16, i32 or i64")
.At(name)
.Build());
}
return new IntTypeSyntax(GetTokens(startIndex), true, size);
}
if (name.Value[0] == 'f' && int.TryParse(name.Value[1..], out size))
{
if (size is not 32 and not 64)
{
throw new ParseException(Diagnostic
.Error("Arbitrary float size is not supported")
.WithHelp("Use f32 or f64")
.At(name)
.Build());
}
return new FloatTypeSyntax(GetTokens(startIndex), size);
}
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:
{
string? module = null;
if (TryExpectSymbol(Symbol.DoubleColon))
{
var customTypeName = ExpectIdentifier();
module = name.Value;
name = customTypeName;
}
return new CustomTypeSyntax(GetTokens(startIndex), module, name.Value);
}
}
}
if (TryExpectSymbol(Symbol.Caret))
{
var baseType = ParseType();
return new PointerTypeSyntax(GetTokens(startIndex), baseType);
}
if (TryExpectSymbol(Symbol.Func))
{
ExpectSymbol(Symbol.OpenParen);
List<TypeSyntax> parameters = [];
while (!TryExpectSymbol(Symbol.CloseParen))
{
parameters.Add(ParseType());
if (!TryExpectSymbol(Symbol.Comma))
{
ExpectSymbol(Symbol.CloseParen);
break;
}
}
var returnType = TryExpectSymbol(Symbol.Colon)
? ParseType()
: new VoidTypeSyntax([]);
return new FuncTypeSyntax(GetTokens(startIndex), parameters, returnType);
}
if (TryExpectSymbol(Symbol.OpenBracket))
{
if (TryExpectIntLiteral(out var intLiteral))
{
ExpectSymbol(Symbol.CloseBracket);
var baseType = ParseType();
return new ConstArrayTypeSyntax(GetTokens(startIndex), baseType, Convert.ToInt64(intLiteral.Value, intLiteral.Base));
}
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 ParseException(Diagnostic
.Error("Invalid type syntax")
.WithHelp("Expected type name, '^' for pointer, or '[]' for array")
.At(CurrentToken)
.Build());
}
private Token ExpectToken()
{
if (!HasToken)
{
throw new ParseException(Diagnostic
.Error("Unexpected end of file")
.WithHelp("Expected more tokens to complete the syntax")
.At(_tokens[^1])
.Build());
}
var token = CurrentToken!;
Next();
return token;
}
private SymbolToken ExpectSymbol()
{
var token = ExpectToken();
if (token is not SymbolToken symbol)
{
throw new ParseException(Diagnostic
.Error($"Expected symbol, but found {token.GetType().Name}")
.WithHelp("This position requires a symbol like '(', ')', '{', '}', etc.")
.At(token)
.Build());
}
return symbol;
}
private void ExpectSymbol(Symbol expectedSymbol)
{
var token = ExpectSymbol();
if (token.Symbol != expectedSymbol)
{
throw new ParseException(Diagnostic
.Error($"Expected '{expectedSymbol}', but found '{token.Symbol}'")
.WithHelp($"Insert '{expectedSymbol}' here")
.At(token)
.Build());
}
}
private bool TryExpectSymbol(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 ParseException(Diagnostic
.Error($"Expected identifier, but found {token.GetType().Name}")
.WithHelp("Provide a valid identifier name here")
.At(token)
.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 ParseException(Diagnostic
.Error($"Expected string literal, but found {token.GetType().Name}")
.WithHelp("Provide a valid string literal")
.At(token)
.Build());
}
return identifier;
}
private void Next()
{
_tokenIndex++;
}
private List<Token> GetTokens(int tokenStartIndex)
{
return _tokens.Skip(tokenStartIndex).Take(_tokenIndex - tokenStartIndex).ToList();
}
}
public record SyntaxTree(List<DefinitionSyntax> Definitions, string ModuleName, List<string> Imports);
public class ParseException : Exception
{
public Diagnostic Diagnostic { get; }
public ParseException(Diagnostic diagnostic) : base(diagnostic.Message)
{
Diagnostic = diagnostic;
}
}

View File

@@ -1,147 +0,0 @@
namespace NubLang.Syntax;
public abstract record SyntaxNode(List<Token> Tokens);
#region Definitions
public abstract record DefinitionSyntax(List<Token> Tokens, string Name, bool Exported) : SyntaxNode(Tokens);
public record FuncParameterSyntax(List<Token> Tokens, string Name, TypeSyntax Type) : SyntaxNode(Tokens);
public record FuncPrototypeSyntax(List<Token> Tokens, string Name, bool Exported, string? ExternSymbol, List<FuncParameterSyntax> Parameters, TypeSyntax ReturnType) : SyntaxNode(Tokens);
public record FuncSyntax(List<Token> Tokens, FuncPrototypeSyntax Prototype, BlockSyntax? Body) : DefinitionSyntax(Tokens, Prototype.Name, Prototype.Exported);
public record StructFieldSyntax(List<Token> Tokens, string Name, TypeSyntax Type, ExpressionSyntax? Value) : SyntaxNode(Tokens);
public record StructSyntax(List<Token> Tokens, string Name, bool Exported, List<StructFieldSyntax> Fields) : DefinitionSyntax(Tokens, Name, Exported);
public record EnumFieldSyntax(List<Token> Tokens, string Name, long Value) : SyntaxNode(Tokens);
public record EnumSyntax(List<Token> Tokens, string Name, bool Exported, TypeSyntax? Type, List<EnumFieldSyntax> Fields) : DefinitionSyntax(Tokens, Name, 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, string Name, 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, string ElementName, string? IndexName, 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, string Name) : ExpressionSyntax(Tokens);
public record ModuleIdentifierSyntax(List<Token> Tokens, string Module, string Name) : 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, string Value, int Base) : ExpressionSyntax(Tokens);
public record StringLiteralSyntax(List<Token> Tokens, string Value) : ExpressionSyntax(Tokens);
public record BoolLiteralSyntax(List<Token> Tokens, bool Value) : ExpressionSyntax(Tokens);
public record FloatLiteralSyntax(List<Token> Tokens, string Value) : ExpressionSyntax(Tokens);
public record MemberAccessSyntax(List<Token> Tokens, ExpressionSyntax Target, string Member) : ExpressionSyntax(Tokens);
public record StructInitializerSyntax(List<Token> Tokens, TypeSyntax? StructType, Dictionary<string, 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, int Width) : TypeSyntax(Tokens);
public record FloatTypeSyntax(List<Token> Tokens, int 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, long Size) : TypeSyntax(Tokens);
public record CustomTypeSyntax(List<Token> Tokens, string? Module, string Name) : TypeSyntax(Tokens);
#endregion

View File

@@ -1,77 +0,0 @@
using NubLang.Diagnostics;
namespace NubLang.Syntax;
public enum Symbol
{
// Control
If,
Else,
While,
For,
In,
Break,
Continue,
Return,
Let,
Defer,
// Declaration
Func,
Struct,
Enum,
Import,
Module,
// Modifier
Extern,
Export,
Colon,
DoubleColon,
OpenParen,
CloseParen,
OpenBrace,
CloseBrace,
OpenBracket,
CloseBracket,
Comma,
Period,
Assign,
Bang,
Equal,
NotEqual,
LessThan,
LessThanOrEqual,
GreaterThan,
GreaterThanOrEqual,
Plus,
Minus,
Star,
ForwardSlash,
Caret,
Ampersand,
Semi,
Percent,
LeftShift,
RightShift,
Pipe,
And,
Or,
At,
QuestionMark,
}
public abstract record Token(SourceSpan Span);
public record IdentifierToken(SourceSpan Span, string Value) : Token(Span);
public record IntLiteralToken(SourceSpan Span, string Value, int Base) : Token(Span);
public record StringLiteralToken(SourceSpan Span, string Value) : Token(Span);
public record BoolLiteralToken(SourceSpan Span, bool Value) : Token(Span);
public record FloatLiteralToken(SourceSpan Span, string Value) : Token(Span);
public record SymbolToken(SourceSpan Span, Symbol Symbol) : Token(Span);

View File

@@ -1,331 +0,0 @@
using NubLang.Diagnostics;
namespace NubLang.Syntax;
public sealed class Tokenizer
{
private static readonly Dictionary<string, Symbol> Keywords = new()
{
["func"] = Symbol.Func,
["if"] = Symbol.If,
["else"] = Symbol.Else,
["while"] = Symbol.While,
["for"] = Symbol.For,
["in"] = Symbol.In,
["break"] = Symbol.Break,
["continue"] = Symbol.Continue,
["return"] = Symbol.Return,
["struct"] = Symbol.Struct,
["let"] = Symbol.Let,
["extern"] = Symbol.Extern,
["module"] = Symbol.Module,
["export"] = Symbol.Export,
["import"] = Symbol.Import,
["defer"] = Symbol.Defer,
["enum"] = Symbol.Enum,
};
private static readonly Dictionary<char[], Symbol> Symbols = new()
{
[['=', '=']] = Symbol.Equal,
[['!', '=']] = Symbol.NotEqual,
[['<', '=']] = Symbol.LessThanOrEqual,
[['>', '=']] = Symbol.GreaterThanOrEqual,
[['<', '<']] = Symbol.LeftShift,
[['>', '>']] = Symbol.RightShift,
[['&', '&']] = Symbol.And,
[['|', '|']] = Symbol.Or,
[[':', ':']] = Symbol.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,
};
private static readonly (char[] Pattern, Symbol Symbol)[] OrderedSymbols = Symbols
.OrderByDescending(kvp => kvp.Key.Length)
.Select(kvp => (kvp.Key, kvp.Value))
.ToArray();
private readonly string _fileName;
private readonly string _content;
private int _index = 0;
private int _line = 1;
private int _column = 1;
public Tokenizer(string fileName, string content)
{
_fileName = fileName;
_content = content;
}
public List<Diagnostic> Diagnostics { get; } = [];
public List<Token> Tokens { get; } = [];
public void Tokenize()
{
Diagnostics.Clear();
Tokens.Clear();
_index = 0;
_line = 1;
_column = 1;
while (Peek().HasValue)
{
try
{
var current = Peek()!.Value;
if (char.IsWhiteSpace(current))
{
if (current is '\n')
{
_line += 1;
// note(nub31): Next increments the column, so 0 is correct here
_column = 0;
}
Next();
continue;
}
if (current == '/' && Peek(1) == '/')
{
// note(nub31): Keep newline so next iteration increments the line counter
while (Peek() is not '\n')
{
Next();
}
continue;
}
Tokens.Add(ParseToken(current, _line, _column));
}
catch (TokenizerException e)
{
Diagnostics.Add(e.Diagnostic);
Next();
}
}
}
private Token ParseToken(char current, int lineStart, int columnStart)
{
if (char.IsLetter(current) || current == '_')
{
var buffer = string.Empty;
while (Peek() != null && (char.IsLetterOrDigit(Peek()!.Value) || Peek() == '_'))
{
buffer += Peek();
Next();
}
if (Keywords.TryGetValue(buffer, out var keywordSymbol))
{
return new SymbolToken(CreateSpan(lineStart, columnStart), keywordSymbol);
}
if (buffer is "true" or "false")
{
return new BoolLiteralToken(CreateSpan(lineStart, columnStart), Convert.ToBoolean(buffer));
}
return new IdentifierToken(CreateSpan(lineStart, columnStart), buffer);
}
if (char.IsDigit(current))
{
var buffer = string.Empty;
if (current == '0' && Peek(1) is 'x')
{
buffer += "0x";
Next();
Next();
while (Peek() != null && Uri.IsHexDigit(Peek()!.Value))
{
buffer += Peek()!.Value;
Next();
}
if (buffer.Length <= 2)
{
throw new TokenizerException(Diagnostic
.Error("Invalid hex literal, no digits found")
.At(_fileName, _line, _column)
.Build());
}
return new IntLiteralToken(CreateSpan(lineStart, columnStart), buffer, 16);
}
if (current == '0' && Peek(1) is 'b')
{
buffer += "0b";
Next();
Next();
while (Peek() != null && (Peek() == '0' || Peek() == '1'))
{
buffer += Peek()!.Value;
Next();
}
if (buffer.Length <= 2)
{
throw new TokenizerException(Diagnostic
.Error("Invalid binary literal, no digits found")
.At(_fileName, _line, _column)
.Build());
}
return new IntLiteralToken(CreateSpan(lineStart, columnStart), buffer, 2);
}
var isFloat = false;
while (Peek() != null)
{
var next = Peek()!.Value;
if (next == '.')
{
if (isFloat)
{
throw new TokenizerException(Diagnostic
.Error("More than one period found in float literal")
.At(_fileName, _line, _column)
.Build());
}
isFloat = true;
buffer += next;
Next();
}
else if (char.IsDigit(next))
{
buffer += next;
Next();
}
else
{
break;
}
}
if (isFloat)
{
return new FloatLiteralToken(CreateSpan(lineStart, columnStart), buffer);
}
else
{
return new IntLiteralToken(CreateSpan(lineStart, columnStart), buffer, 10);
}
}
if (current == '"')
{
Next();
var buffer = string.Empty;
while (true)
{
var next = Peek();
if (!next.HasValue)
{
throw new TokenizerException(Diagnostic
.Error("Unclosed string literal")
.At(_fileName, _line, _column)
.Build());
}
if (next is '\n')
{
_line += 1;
break;
}
if (next is '"')
{
Next();
break;
}
buffer += next;
Next();
}
return new StringLiteralToken(CreateSpan(lineStart, columnStart), buffer);
}
foreach (var (pattern, symbol) in OrderedSymbols)
{
for (var i = 0; i < pattern.Length; i++)
{
var c = Peek(i);
if (!c.HasValue || c.Value != pattern[i]) break;
if (i == pattern.Length - 1)
{
for (var j = 0; j <= i; j++)
{
Next();
}
return new SymbolToken(CreateSpan(lineStart, columnStart), symbol);
}
}
}
throw new TokenizerException(Diagnostic.Error($"Unknown token '{current}'").Build());
}
private SourceSpan CreateSpan(int lineStart, int columnStart)
{
return new SourceSpan(_fileName, new SourceLocation(lineStart, columnStart), new SourceLocation(_line, _column));
}
private char? Peek(int offset = 0)
{
if (_index + offset < _content.Length)
{
return _content[_index + offset];
}
return null;
}
private void Next()
{
_index += 1;
_column += 1;
}
}
public class TokenizerException : Exception
{
public Diagnostic Diagnostic { get; }
public TokenizerException(Diagnostic diagnostic) : base(diagnostic.Message)
{
Diagnostic = diagnostic;
}
}

View File

@@ -1,75 +0,0 @@
using System.Diagnostics.CodeAnalysis;
namespace NubLang;
public readonly struct Variant<T1, T2> where T1 : notnull where T2 : notnull
{
public Variant()
{
throw new InvalidOperationException("Variant must be initialized with a value");
}
public Variant(T1 value)
{
_value = value;
}
public Variant(T2 value)
{
_value = value;
}
private readonly object _value;
public void Match(Action<T1> on1, Action<T2> on2)
{
switch (_value)
{
case T1 v1:
on1(v1);
break;
case T2 v2:
on2(v2);
break;
default:
throw new InvalidCastException();
}
}
public T Match<T>(Func<T1, T> on1, Func<T2, T> on2)
{
return _value switch
{
T1 v1 => on1(v1),
T2 v2 => on2(v2),
_ => throw new InvalidCastException()
};
}
public bool IsCase1([NotNullWhen(true)] out T1? value)
{
if (_value is T1 converted)
{
value = converted;
return true;
}
value = default;
return false;
}
public bool IsCase2([NotNullWhen(true)] out T2? value)
{
if (_value is T2 converted)
{
value = converted;
return true;
}
value = default;
return false;
}
public static implicit operator Variant<T1, T2>(T1 value) => new(value);
public static implicit operator Variant<T1, T2>(T2 value) => new(value);
}

449
compiler/NubType.cs Normal file
View File

@@ -0,0 +1,449 @@
using System.Text;
namespace Compiler;
public abstract class NubType
{
public abstract override string ToString();
}
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 NubTypeStruct : NubType
{
public string Name { get; }
public string Module { get; }
public bool Packed { get; }
private IReadOnlyList<Field>? _resolvedFields;
public IReadOnlyList<Field> Fields => _resolvedFields ?? throw new InvalidOperationException();
public NubTypeStruct(string module, string name, bool packed)
{
Module = module;
Name = name;
Packed = packed;
}
public void ResolveFields(IReadOnlyList<Field> fields)
{
if (_resolvedFields != null)
throw new InvalidOperationException($"{Name} already resolved");
_resolvedFields = fields;
}
public override string ToString() => _resolvedFields == null ? $"struct {Module}::{Name} <unresolved>" : $"struct {Module}::{Name} {{ {string.Join(' ', Fields.Select(f => $"{f.Name}: {f.Type}"))} }}";
public class Field(string name, NubType type)
{
public string Name { get; } = name;
public NubType Type { get; } = type;
}
}
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; }
public string MangledName(string name, string module) => SymbolNameGen.Exported(name, module, this);
private NubTypeFunc(List<NubType> parameters, NubType returnType)
{
Parameters = parameters;
ReturnType = returnType;
}
public override string ToString() => $"func({string.Join(' ', Parameters)}): {ReturnType}";
private readonly record struct Signature(IReadOnlyList<NubType> Parameters, NubType ReturnType);
}
static class TypeEncoder
{
public static string Encode(NubType type)
{
var sb = new StringBuilder();
Encode(type, sb);
return sb.ToString();
}
private static void Encode(NubType type, StringBuilder sb)
{
switch (type)
{
case NubTypeVoid:
sb.Append('V');
break;
case NubTypeBool:
sb.Append('B');
break;
case NubTypeUInt u:
sb.Append("U(").Append(u.Width).Append(')');
break;
case NubTypeSInt s:
sb.Append("I(").Append(s.Width).Append(')');
break;
case NubTypeString:
sb.Append('S');
break;
case NubTypePointer p:
sb.Append("P(");
Encode(p.To, sb);
sb.Append(')');
break;
case NubTypeStruct st:
sb.Append("T(");
sb.Append(st.Module).Append("::").Append(st.Name);
sb.Append(',').Append(st.Packed ? '1' : '0');
sb.Append('{');
for (int i = 0; i < st.Fields.Count; i++)
{
var field = st.Fields[i];
sb.Append(field.Name).Append(':');
Encode(field.Type, sb);
if (i < st.Fields.Count - 1)
sb.Append(',');
}
sb.Append("})");
break;
case NubTypeFunc fn:
sb.Append("F(");
foreach (var parameter in fn.Parameters)
{
sb.Append(Encode(parameter));
sb.Append(',');
}
sb.Append(Encode(fn.ReturnType));
sb.Append(')');
break;
default:
throw new NotSupportedException(type.GetType().Name);
}
}
}
class TypeDecoder
{
public static NubType Decode(string encoded)
{
return new TypeDecoder(encoded).Decode();
}
private TypeDecoder(string encoded)
{
this.encoded = encoded;
}
private readonly string encoded;
private int pos;
private NubType Decode()
{
return Parse();
}
private NubType Parse()
{
if (pos >= encoded.Length)
throw new InvalidOperationException("Unexpected end of string");
char c = encoded[pos++];
return c switch
{
'V' => NubTypeVoid.Instance,
'B' => NubTypeBool.Instance,
'S' => NubTypeString.Instance,
'U' => ParseUInt(),
'I' => ParseSInt(),
'P' => ParsePointer(),
'T' => ParseStruct(),
'F' => ParseFunc(),
_ => throw new NotSupportedException($"Unknown type code '{c}' at position {pos - 1}")
};
}
private NubTypeUInt ParseUInt()
{
ExpectChar('(');
int width = ReadNumber();
ExpectChar(')');
return NubTypeUInt.Get(width);
}
private NubTypeSInt ParseSInt()
{
ExpectChar('(');
int width = ReadNumber();
ExpectChar(')');
return NubTypeSInt.Get(width);
}
private NubTypePointer ParsePointer()
{
ExpectChar('(');
var to = Parse();
ExpectChar(')');
return NubTypePointer.Get(to);
}
private NubTypeStruct ParseStruct()
{
ExpectChar('(');
int start = pos;
while (pos < encoded.Length && encoded[pos] != ',' && encoded[pos] != '{') pos++;
var fullName = encoded[start..pos];
var parts = fullName.Split("::");
if (parts.Length != 2)
throw new InvalidOperationException($"Invalid struct name: {fullName}");
string module = parts[0], name = parts[1];
bool packed = false;
if (encoded[pos] == ',')
{
pos += 1;
packed = encoded[pos += 1] == '1';
}
var st = new NubTypeStruct(module, name, packed);
ExpectChar('{');
var fields = new List<NubTypeStruct.Field>();
while (encoded[pos] != '}')
{
int nameStart = pos;
while (encoded[pos] != ':') pos += 1;
string fieldName = encoded[nameStart..pos];
pos += 1;
var fieldType = Parse();
fields.Add(new NubTypeStruct.Field(fieldName, fieldType));
if (encoded[pos] == ',') pos += 1;
}
ExpectChar('}');
ExpectChar(')');
st.ResolveFields(fields);
return st;
}
private NubTypeFunc ParseFunc()
{
ExpectChar('(');
var parameters = new List<NubType>();
while (true)
{
if (encoded[pos] == ')')
{
pos++;
break;
}
var param = Parse();
parameters.Add(param);
if (encoded[pos] == ',')
{
pos += 1;
}
else if (encoded[pos] == ')')
{
pos += 1;
break;
}
else
{
throw new InvalidOperationException($"Unexpected char '{encoded[pos]}' in function type at {pos}");
}
}
if (parameters.Count == 0)
throw new InvalidOperationException("Function must have a return type");
var returnType = parameters[^1];
var paramTypes = parameters.Take(parameters.Count - 1).ToList();
return NubTypeFunc.Get(paramTypes, returnType);
}
private void ExpectChar(char expected)
{
if (pos >= encoded.Length || encoded[pos] != expected)
throw new InvalidOperationException($"Expected '{expected}' at position {pos}");
pos += 1;
}
private int ReadNumber()
{
int start = pos;
while (pos < encoded.Length && char.IsDigit(encoded[pos])) pos += 1;
if (start == pos) throw new InvalidOperationException($"Expected number at position {start}");
return int.Parse(encoded[start..pos]);
}
}
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;
}
}
static class SymbolNameGen
{
public static string Exported(string module, string function, NubType type)
{
var canonical = TypeEncoder.Encode(type);
var hash = Hashing.Fnv1a64(canonical);
return $"nub_{Sanitize(module)}_{Sanitize(function)}_{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();
}
}

829
compiler/Parser.cs Normal file
View File

@@ -0,0 +1,829 @@
using System.Diagnostics.CodeAnalysis;
namespace Compiler;
public class Parser
{
public static Ast? Parse(string fileName, List<Token> tokens, out List<Diagnostic> diagnostics)
{
return new Parser(fileName, tokens).Parse(out diagnostics);
}
private Parser(string fileName, List<Token> tokens)
{
this.fileName = fileName;
this.tokens = tokens;
}
private readonly string fileName;
private readonly List<Token> tokens;
private int index;
private Ast? Parse(out List<Diagnostic> diagnostics)
{
var definitions = new List<NodeDefinition>();
diagnostics = [];
TokenIdent? moduleName = null;
try
{
ExpectKeyword(Keyword.Module);
moduleName = ExpectIdent();
while (Peek() != null)
{
definitions.Add(ParseDefinition());
}
}
catch (CompileException e)
{
diagnostics.Add(e.Diagnostic);
}
if (moduleName == null || diagnostics.Any(x => x.Severity == Diagnostic.DiagnosticSeverity.Error))
return null;
return new Ast(fileName, moduleName, definitions);
}
private NodeDefinition ParseDefinition()
{
var startIndex = index;
Dictionary<Keyword, TokenKeyword> modifiers = [];
while (true)
{
if (Peek() is TokenKeyword keyword)
{
switch (keyword.Keyword)
{
case Keyword.Export:
Next();
modifiers[Keyword.Export] = keyword;
break;
case Keyword.Packed:
Next();
modifiers[Keyword.Packed] = keyword;
break;
default:
goto modifier_done;
}
}
}
modifier_done:
if (TryExpectKeyword(Keyword.Func))
{
var exported = modifiers.Remove(Keyword.Export);
foreach (var modifier in modifiers)
// todo(nub31): Add to diagnostics instead of throwing
throw new CompileException(Diagnostic.Error("Invalid modifier for function").At(fileName, modifier.Value).Build());
var name = ExpectIdent();
var parameters = new List<NodeDefinitionFunc.Param>();
ExpectSymbol(Symbol.OpenParen);
while (!TryExpectSymbol(Symbol.CloseParen))
{
var paramStartIndex = index;
var parameterName = ExpectIdent();
ExpectSymbol(Symbol.Colon);
var parameterType = ParseType();
parameters.Add(new NodeDefinitionFunc.Param(TokensFrom(paramStartIndex), parameterName, parameterType));
}
ExpectSymbol(Symbol.Colon);
var returnType = ParseType();
var body = ParseStatement();
return new NodeDefinitionFunc(TokensFrom(startIndex), exported, name, parameters, body, returnType);
}
if (TryExpectKeyword(Keyword.Struct))
{
var exported = modifiers.Remove(Keyword.Export);
var packed = modifiers.Remove(Keyword.Packed);
foreach (var modifier in modifiers)
// todo(nub31): Add to diagnostics instead of throwing
throw new CompileException(Diagnostic.Error("Invalid modifier for struct").At(fileName, modifier.Value).Build());
var name = ExpectIdent();
var fields = new List<NodeDefinitionStruct.Field>();
ExpectSymbol(Symbol.OpenCurly);
while (!TryExpectSymbol(Symbol.CloseCurly))
{
var fieldStartIndex = index;
var fieldName = ExpectIdent();
ExpectSymbol(Symbol.Colon);
var fieldType = ParseType();
fields.Add(new NodeDefinitionStruct.Field(TokensFrom(fieldStartIndex), fieldName, fieldType));
}
return new NodeDefinitionStruct(TokensFrom(startIndex), exported, packed, name, fields);
}
if (TryExpectKeyword(Keyword.Let))
{
var exported = modifiers.Remove(Keyword.Export);
foreach (var modifier in modifiers)
// todo(nub31): Add to diagnostics instead of throwing
throw new CompileException(Diagnostic.Error("Invalid modifier for global variable").At(fileName, modifier.Value).Build());
var name = ExpectIdent();
ExpectSymbol(Symbol.Colon);
var type = ParseType();
return new NodeDefinitionGlobalVariable(TokensFrom(startIndex), exported, name, type);
}
throw new CompileException(Diagnostic.Error("Not a valid definition").At(fileName, Peek()).Build());
}
private NodeStatement ParseStatement()
{
var startIndex = index;
if (TryExpectSymbol(Symbol.OpenCurly))
{
var statements = new List<NodeStatement>();
while (!TryExpectSymbol(Symbol.CloseCurly))
statements.Add(ParseStatement());
return new NodeStatementBlock(TokensFrom(startIndex), statements);
}
if (TryExpectKeyword(Keyword.Return))
{
var value = ParseExpression();
return new NodeStatementReturn(TokensFrom(startIndex), value);
}
if (TryExpectKeyword(Keyword.Let))
{
var name = ExpectIdent();
ExpectSymbol(Symbol.Colon);
var type = ParseType();
ExpectSymbol(Symbol.Equal);
var value = ParseExpression();
return new NodeStatementVariableDeclaration(TokensFrom(startIndex), name, type, value);
}
if (TryExpectKeyword(Keyword.If))
{
var condition = ParseExpression();
var thenBlock = ParseStatement();
NodeStatement? elseBlock = null;
if (TryExpectKeyword(Keyword.Else))
elseBlock = ParseStatement();
return new NodeStatementIf(TokensFrom(startIndex), condition, thenBlock, elseBlock);
}
if (TryExpectKeyword(Keyword.While))
{
var condition = ParseExpression();
var thenBlock = ParseStatement();
return new NodeStatementWhile(TokensFrom(startIndex), condition, thenBlock);
}
var target = ParseExpression();
if (TryExpectSymbol(Symbol.Equal))
{
var value = ParseExpression();
return new NodeStatementAssignment(TokensFrom(startIndex), target, value);
}
return new NodeStatementExpression(TokensFrom(startIndex), target);
}
private NodeExpression ParseExpression(int minPrecedence = -1)
{
var startIndex = index;
var left = ParseExpressionLeaf();
while (TryPeekBinaryOperator(out var op) && GetPrecedence(op) >= minPrecedence)
{
Next();
var right = ParseExpression(GetPrecedence(op) + 1);
left = new NodeExpressionBinary(TokensFrom(startIndex), left, op, right);
}
return left;
}
private static int GetPrecedence(NodeExpressionBinary.Op operation)
{
return operation switch
{
NodeExpressionBinary.Op.Multiply => 10,
NodeExpressionBinary.Op.Divide => 10,
NodeExpressionBinary.Op.Modulo => 10,
NodeExpressionBinary.Op.Add => 9,
NodeExpressionBinary.Op.Subtract => 9,
NodeExpressionBinary.Op.LeftShift => 8,
NodeExpressionBinary.Op.RightShift => 8,
NodeExpressionBinary.Op.GreaterThan => 7,
NodeExpressionBinary.Op.GreaterThanOrEqual => 7,
NodeExpressionBinary.Op.LessThan => 7,
NodeExpressionBinary.Op.LessThanOrEqual => 7,
NodeExpressionBinary.Op.Equal => 7,
NodeExpressionBinary.Op.NotEqual => 7,
// NodeExpressionBinary.Op.BitwiseAnd => 6,
// NodeExpressionBinary.Op.BitwiseXor => 5,
// NodeExpressionBinary.Op.BitwiseOr => 4,
NodeExpressionBinary.Op.LogicalAnd => 3,
NodeExpressionBinary.Op.LogicalOr => 2,
_ => throw new ArgumentOutOfRangeException(nameof(operation), operation, null)
};
}
private NodeExpression ParseExpressionLeaf()
{
var startIndex = index;
NodeExpression expr;
if (TryExpectSymbol(Symbol.OpenParen))
{
var value = ParseExpression();
ExpectSymbol(Symbol.CloseParen);
expr = value;
}
else if (TryExpectSymbol(Symbol.Minus))
{
var target = ParseExpression();
expr = new NodeExpressionUnary(TokensFrom(startIndex), target, NodeExpressionUnary.Op.Negate);
}
else if (TryExpectSymbol(Symbol.Bang))
{
var target = ParseExpression();
expr = new NodeExpressionUnary(TokensFrom(startIndex), target, NodeExpressionUnary.Op.Invert);
}
else if (TryExpectIntLiteral(out var intLiteral))
{
expr = new NodeExpressionIntLiteral(TokensFrom(startIndex), intLiteral);
}
else if (TryExpectStringLiteral(out var stringLiteral))
{
expr = new NodeExpressionStringLiteral(TokensFrom(startIndex), stringLiteral);
}
else if (TryExpectBoolLiteral(out var boolLiteral))
{
expr = new NodeExpressionBoolLiteral(TokensFrom(startIndex), boolLiteral);
}
else if (TryExpectIdent(out var ident))
{
if (TryExpectSymbol(Symbol.ColonColon))
{
var name = ExpectIdent();
expr = new NodeExpressionModuleIdent(TokensFrom(startIndex), ident, name);
}
else
{
expr = new NodeExpressionLocalIdent(TokensFrom(startIndex), ident);
}
}
else if (TryExpectKeyword(Keyword.Struct))
{
var module = ExpectIdent();
ExpectSymbol(Symbol.ColonColon);
var name = ExpectIdent();
var initializers = new List<NodeExpressionStructLiteral.Initializer>();
ExpectSymbol(Symbol.OpenCurly);
while (!TryExpectSymbol(Symbol.CloseCurly))
{
var initializerStartIndex = startIndex;
var fieldName = ExpectIdent();
ExpectSymbol(Symbol.Equal);
var fieldValue = ParseExpression();
initializers.Add(new NodeExpressionStructLiteral.Initializer(TokensFrom(initializerStartIndex), fieldName, fieldValue));
}
expr = new NodeExpressionStructLiteral(TokensFrom(startIndex), module, name, initializers);
}
else
{
throw new CompileException(Diagnostic.Error("Expected start of expression").At(fileName, Peek()).Build());
}
while (true)
{
if (TryExpectSymbol(Symbol.Period))
{
var name = ExpectIdent();
expr = new NodeExpressionMemberAccess(TokensFrom(startIndex), expr, name);
}
else if (TryExpectSymbol(Symbol.OpenParen))
{
var parameters = new List<NodeExpression>();
while (!TryExpectSymbol(Symbol.CloseParen))
parameters.Add(ParseExpression());
expr = new NodeExpressionFuncCall(TokensFrom(startIndex), expr, parameters);
}
else
{
break;
}
}
return expr;
}
private NodeType ParseType()
{
var startIndex = index;
if (TryExpectSymbol(Symbol.Caret))
{
var to = ParseType();
return new NodeTypePointer(TokensFrom(startIndex), to);
}
if (TryExpectKeyword(Keyword.Func))
{
var parameters = new List<NodeType>();
ExpectSymbol(Symbol.OpenParen);
while (!TryExpectSymbol(Symbol.CloseParen))
{
parameters.Add(ParseType());
}
ExpectSymbol(Symbol.Colon);
var returnType = ParseType();
return new NodeTypeFunc(TokensFrom(startIndex), parameters, returnType);
}
if (TryExpectIdent(out var ident))
{
switch (ident.Ident)
{
case "void":
return new NodeTypeVoid(TokensFrom(startIndex));
case "string":
return new NodeTypeString(TokensFrom(startIndex));
case "bool":
return new NodeTypeBool(TokensFrom(startIndex));
case "i8":
return new NodeTypeSInt(TokensFrom(startIndex), 8);
case "i16":
return new NodeTypeSInt(TokensFrom(startIndex), 16);
case "i32":
return new NodeTypeSInt(TokensFrom(startIndex), 32);
case "i64":
return new NodeTypeSInt(TokensFrom(startIndex), 64);
case "u8":
return new NodeTypeUInt(TokensFrom(startIndex), 8);
case "u16":
return new NodeTypeUInt(TokensFrom(startIndex), 16);
case "u32":
return new NodeTypeUInt(TokensFrom(startIndex), 32);
case "u64":
return new NodeTypeUInt(TokensFrom(startIndex), 64);
default:
ExpectSymbol(Symbol.ColonColon);
var name = ExpectIdent();
return new NodeTypeCustom(TokensFrom(startIndex), ident, name);
}
}
throw new CompileException(Diagnostic.Error("Expected type").At(fileName, Peek()).Build());
}
private List<Token> TokensFrom(int startIndex)
{
return tokens.GetRange(startIndex, index - startIndex);
}
private void ExpectKeyword(Keyword keyword)
{
if (Peek() is TokenKeyword token && token.Keyword == keyword)
{
Next();
return;
}
throw new CompileException(Diagnostic.Error($"Expected '{keyword.AsString()}'").At(fileName, Peek()).Build());
}
private bool TryExpectKeyword(Keyword keyword)
{
if (Peek() is TokenKeyword token && token.Keyword == keyword)
{
Next();
return true;
}
return false;
}
private void ExpectSymbol(Symbol symbol)
{
if (Peek() is TokenSymbol token && token.Symbol == symbol)
{
Next();
return;
}
throw new CompileException(Diagnostic.Error($"Expected '{symbol.AsString()}'").At(fileName, Peek()).Build());
}
private bool TryExpectSymbol(Symbol symbol)
{
if (Peek() is TokenSymbol token && token.Symbol == symbol)
{
Next();
return true;
}
return false;
}
private TokenIdent ExpectIdent()
{
if (Peek() is TokenIdent token)
{
Next();
return token;
}
throw new CompileException(Diagnostic.Error("Expected identifier").At(fileName, Peek()).Build());
}
private bool TryExpectIdent([NotNullWhen(true)] out TokenIdent? ident)
{
if (Peek() is TokenIdent token)
{
Next();
ident = token;
return true;
}
ident = null;
return false;
}
private bool TryExpectIntLiteral([NotNullWhen(true)] out TokenIntLiteral? intLiteral)
{
if (Peek() is TokenIntLiteral token)
{
Next();
intLiteral = token;
return true;
}
intLiteral = null;
return false;
}
private bool TryExpectStringLiteral([NotNullWhen(true)] out TokenStringLiteral? stringLiteral)
{
if (Peek() is TokenStringLiteral token)
{
Next();
stringLiteral = token;
return true;
}
stringLiteral = null;
return false;
}
private bool TryExpectBoolLiteral([NotNullWhen(true)] out TokenBoolLiteral? boolLiteral)
{
if (Peek() is TokenBoolLiteral token)
{
Next();
boolLiteral = token;
return true;
}
boolLiteral = null;
return false;
}
private void Next()
{
if (index >= tokens.Count)
throw new CompileException(Diagnostic.Error("Unexpected end of tokens").At(fileName, Peek()).Build());
index += 1;
}
private Token? Peek(int offset = 0)
{
if (index + offset >= tokens.Count)
return null;
return tokens[index + offset];
}
private bool TryPeekBinaryOperator(out NodeExpressionBinary.Op op)
{
if (Peek() is not TokenSymbol token)
{
op = default;
return false;
}
switch (token.Symbol)
{
case Symbol.Plus:
op = NodeExpressionBinary.Op.Add;
return true;
case Symbol.Minus:
op = NodeExpressionBinary.Op.Subtract;
return true;
case Symbol.Star:
op = NodeExpressionBinary.Op.Multiply;
return true;
case Symbol.ForwardSlash:
op = NodeExpressionBinary.Op.Divide;
return true;
case Symbol.Percent:
op = NodeExpressionBinary.Op.Modulo;
return true;
case Symbol.BangEqual:
op = NodeExpressionBinary.Op.NotEqual;
return true;
case Symbol.EqualEqual:
op = NodeExpressionBinary.Op.Equal;
return true;
case Symbol.LessThan:
op = NodeExpressionBinary.Op.LessThan;
return true;
case Symbol.LessThanEqual:
op = NodeExpressionBinary.Op.LessThanOrEqual;
return true;
case Symbol.GreaterThan:
op = NodeExpressionBinary.Op.GreaterThan;
return true;
case Symbol.GreaterThanEqual:
op = NodeExpressionBinary.Op.GreaterThanOrEqual;
return true;
case Symbol.LessThanLessThan:
op = NodeExpressionBinary.Op.LeftShift;
return true;
case Symbol.GreaterThanGreaterThan:
op = NodeExpressionBinary.Op.RightShift;
return true;
case Symbol.AmpersandAmpersand:
op = NodeExpressionBinary.Op.LogicalAnd;
return true;
case Symbol.PipePipe:
op = NodeExpressionBinary.Op.LogicalOr;
return true;
default:
op = default;
return false;
}
}
}
public class Ast(string fileName, TokenIdent moduleName, List<NodeDefinition> definitions)
{
public string FileName { get; } = fileName;
public TokenIdent ModuleName { get; } = moduleName;
public List<NodeDefinition> Definitions { get; } = definitions;
}
public abstract class Node(List<Token> tokens)
{
public List<Token> Tokens { get; } = tokens;
}
public abstract class NodeDefinition(List<Token> tokens) : Node(tokens);
public class NodeDefinitionFunc(List<Token> tokens, bool exported, TokenIdent name, List<NodeDefinitionFunc.Param> parameters, NodeStatement body, NodeType returnType) : NodeDefinition(tokens)
{
public bool Exported { get; } = exported;
public TokenIdent Name { get; } = name;
public List<Param> Parameters { get; } = parameters;
public NodeStatement Body { get; } = body;
public NodeType ReturnType { get; } = returnType;
public class Param(List<Token> tokens, TokenIdent name, NodeType type) : Node(tokens)
{
public TokenIdent Name { get; } = name;
public NodeType Type { get; } = type;
}
}
public class NodeDefinitionStruct(List<Token> tokens, bool exported, bool packed, TokenIdent name, List<NodeDefinitionStruct.Field> fields) : NodeDefinition(tokens)
{
public bool Exported { get; } = exported;
public bool Packed { get; } = packed;
public TokenIdent Name { get; } = name;
public List<Field> Fields { get; } = fields;
public class Field(List<Token> tokens, TokenIdent name, NodeType type) : Node(tokens)
{
public TokenIdent Name { get; } = name;
public NodeType Type { get; } = type;
}
}
public class NodeDefinitionGlobalVariable(List<Token> tokens, bool exported, TokenIdent name, NodeType type) : NodeDefinition(tokens)
{
public bool Exported { get; } = exported;
public TokenIdent Name { get; } = name;
public NodeType Type { get; } = type;
}
public abstract class NodeStatement(List<Token> tokens) : Node(tokens);
public class NodeStatementBlock(List<Token> tokens, List<NodeStatement> statements) : NodeStatement(tokens)
{
public List<NodeStatement> Statements { get; } = statements;
}
public class NodeStatementExpression(List<Token> tokens, NodeExpression expression) : NodeStatement(tokens)
{
public NodeExpression Expression { get; } = expression;
}
public class NodeStatementReturn(List<Token> tokens, NodeExpression value) : NodeStatement(tokens)
{
public NodeExpression Value { get; } = value;
}
public class NodeStatementVariableDeclaration(List<Token> tokens, TokenIdent name, NodeType type, NodeExpression value) : NodeStatement(tokens)
{
public TokenIdent Name { get; } = name;
public NodeType Type { get; } = type;
public NodeExpression Value { get; } = value;
}
public class NodeStatementAssignment(List<Token> tokens, NodeExpression target, NodeExpression value) : NodeStatement(tokens)
{
public NodeExpression Target { get; } = target;
public NodeExpression Value { get; } = value;
}
public class NodeStatementIf(List<Token> tokens, NodeExpression condition, NodeStatement thenBlock, NodeStatement? elseBlock) : NodeStatement(tokens)
{
public NodeExpression Condition { get; } = condition;
public NodeStatement ThenBlock { get; } = thenBlock;
public NodeStatement? ElseBlock { get; } = elseBlock;
}
public class NodeStatementWhile(List<Token> tokens, NodeExpression condition, NodeStatement block) : NodeStatement(tokens)
{
public NodeExpression Condition { get; } = condition;
public NodeStatement Block { get; } = block;
}
public abstract class NodeExpression(List<Token> tokens) : Node(tokens);
public class NodeExpressionIntLiteral(List<Token> tokens, TokenIntLiteral value) : NodeExpression(tokens)
{
public TokenIntLiteral Value { get; } = value;
}
public class NodeExpressionStringLiteral(List<Token> tokens, TokenStringLiteral value) : NodeExpression(tokens)
{
public TokenStringLiteral Value { get; } = value;
}
public class NodeExpressionBoolLiteral(List<Token> tokens, TokenBoolLiteral value) : NodeExpression(tokens)
{
public TokenBoolLiteral Value { get; } = value;
}
public class NodeExpressionStructLiteral(List<Token> tokens, TokenIdent module, TokenIdent name, List<NodeExpressionStructLiteral.Initializer> initializers) : NodeExpression(tokens)
{
public TokenIdent Module { get; } = module;
public TokenIdent Name { get; } = name;
public List<Initializer> Initializers { get; } = initializers;
public class Initializer(List<Token> tokens, TokenIdent name, NodeExpression value) : Node(tokens)
{
public TokenIdent Name { get; } = name;
public NodeExpression Value { get; } = value;
}
}
public class NodeExpressionMemberAccess(List<Token> tokens, NodeExpression target, TokenIdent name) : NodeExpression(tokens)
{
public NodeExpression Target { get; } = target;
public TokenIdent Name { get; } = name;
}
public class NodeExpressionFuncCall(List<Token> tokens, NodeExpression target, List<NodeExpression> parameters) : NodeExpression(tokens)
{
public NodeExpression Target { get; } = target;
public List<NodeExpression> Parameters { get; } = parameters;
}
public class NodeExpressionLocalIdent(List<Token> tokens, TokenIdent value) : NodeExpression(tokens)
{
public TokenIdent Value { get; } = value;
}
public class NodeExpressionModuleIdent(List<Token> tokens, TokenIdent module, TokenIdent value) : NodeExpression(tokens)
{
public TokenIdent Module { get; } = module;
public TokenIdent Value { get; } = value;
}
public class NodeExpressionBinary(List<Token> tokens, NodeExpression left, NodeExpressionBinary.Op operation, NodeExpression right) : NodeExpression(tokens)
{
public NodeExpression Left { get; } = left;
public Op Operation { get; } = operation;
public NodeExpression Right { get; } = right;
public enum Op
{
Add,
Subtract,
Multiply,
Divide,
Modulo,
Equal,
NotEqual,
LessThan,
LessThanOrEqual,
GreaterThan,
GreaterThanOrEqual,
LeftShift,
RightShift,
// BitwiseAnd,
// BitwiseXor,
// BitwiseOr,
LogicalAnd,
LogicalOr,
}
}
public class NodeExpressionUnary(List<Token> tokens, NodeExpression target, NodeExpressionUnary.Op op) : NodeExpression(tokens)
{
public NodeExpression Target { get; } = target;
public Op Operation { get; } = op;
public enum Op
{
Negate,
Invert,
}
}
public abstract class NodeType(List<Token> tokens) : Node(tokens);
public class NodeTypeVoid(List<Token> tokens) : NodeType(tokens);
public class NodeTypeUInt(List<Token> tokens, int width) : NodeType(tokens)
{
public int Width { get; } = width;
}
public class NodeTypeSInt(List<Token> tokens, int width) : NodeType(tokens)
{
public int Width { get; } = width;
}
public class NodeTypeBool(List<Token> tokens) : NodeType(tokens);
public class NodeTypeString(List<Token> tokens) : NodeType(tokens);
public class NodeTypeCustom(List<Token> tokens, TokenIdent module, TokenIdent name) : NodeType(tokens)
{
public TokenIdent Module { get; } = module;
public TokenIdent Name { get; } = name;
}
public class NodeTypePointer(List<Token> tokens, NodeType to) : NodeType(tokens)
{
public NodeType To { get; } = to;
}
public class NodeTypeFunc(List<Token> tokens, List<NodeType> parameters, NodeType returnType) : NodeType(tokens)
{
public List<NodeType> Parameters { get; } = parameters;
public NodeType ReturnType { get; } = returnType;
}

219
compiler/Program.cs Normal file
View File

@@ -0,0 +1,219 @@
using System.Diagnostics;
using System.IO.Compression;
using System.Text.Json;
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 = ReadNublib(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);
}
}
var output = Generator.Emit(functions, moduleGraph, compileLib);
if (Directory.Exists(".build"))
{
CleanDirectory(".build");
}
else
{
Directory.CreateDirectory(".build");
}
if (compileLib)
{
File.WriteAllText(".build/out.c", output);
Process.Start("gcc", ["-Og", "-fvisibility=hidden", "-fno-builtin", "-c", "-o", ".build/out.o", ".build/out.c", .. archivePaths]).WaitForExit();
Process.Start("ar", ["rcs", ".build/out.a", ".build/out.o"]).WaitForExit();
WriteNublib(".build/out.nublib", ".build/out.a", moduleGraph.CreateManifest());
}
else
{
File.WriteAllText(".build/out.c", output);
Process.Start("gcc", ["-Og", "-fvisibility=hidden", "-fno-builtin", "-o", ".build/out", ".build/out.c", .. 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();
}
}
static void WriteNublib(string outputPath, string archivePath, Manifest manifest)
{
using var fs = new FileStream(outputPath, FileMode.Create);
using var zip = new ZipArchive(fs, ZipArchiveMode.Create);
var serialized = JsonSerializer.Serialize(manifest, new JsonSerializerOptions
{
PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
WriteIndented = true,
});
File.WriteAllText(".build/manifest.json", serialized);
var manifestEntry = zip.CreateEntry("manifest.json");
using (var writer = new StreamWriter(manifestEntry.Open()))
{
writer.Write(JsonSerializer.Serialize(manifest));
}
var archiveEntry = zip.CreateEntry("lib.a");
using var entryStream = archiveEntry.Open();
using var fileStream = File.OpenRead(archivePath);
fileStream.CopyTo(entryStream);
}
static NublibLoadResult ReadNublib(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) ?? 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);

611
compiler/Tokenizer.cs Normal file
View File

@@ -0,0 +1,611 @@
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.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),
"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),
"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),
"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,
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,
Let,
If,
Else,
While,
Return,
Module,
Export,
}
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.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.Let => "let",
Keyword.If => "if",
Keyword.Else => "else",
Keyword.While => "while",
Keyword.Return => "return",
Keyword.Module => "module",
Keyword.Export => "export",
_ => throw new ArgumentOutOfRangeException(nameof(symbol), symbol, null)
};
}
}

602
compiler/TypeChecker.cs Normal file
View File

@@ -0,0 +1,602 @@
namespace Compiler;
public class TypeChecker
{
public static TypedNodeDefinitionFunc? CheckFunction(string fileName, string moduleName, NodeDefinitionFunc function, ModuleGraph moduleGraph, out List<Diagnostic> diagnostics)
{
return new TypeChecker(fileName, moduleName, function, moduleGraph).CheckFunction(out diagnostics);
}
private TypeChecker(string fileName, string moduleName, NodeDefinitionFunc function, ModuleGraph moduleGraph)
{
this.fileName = fileName;
this.moduleName = moduleName;
this.function = function;
this.moduleGraph = moduleGraph;
}
private readonly string fileName;
private readonly string moduleName;
private readonly NodeDefinitionFunc function;
private readonly ModuleGraph moduleGraph;
private readonly Scope scope = new(null);
private TypedNodeDefinitionFunc? CheckFunction(out List<Diagnostic> diagnostics)
{
diagnostics = [];
var parameters = new List<TypedNodeDefinitionFunc.Param>();
var invalidParameter = false;
TypedNodeStatement? body = null;
NubType? returnType = null;
foreach (var parameter in function.Parameters)
{
scope.DeclareIdentifier(parameter.Name.Ident, ResolveType(parameter.Type));
try
{
parameters.Add(CheckDefinitionFuncParameter(parameter));
}
catch (CompileException e)
{
diagnostics.Add(e.Diagnostic);
invalidParameter = true;
}
}
try
{
body = CheckStatement(function.Body);
}
catch (CompileException e)
{
diagnostics.Add(e.Diagnostic);
}
try
{
returnType = ResolveType(function.ReturnType);
}
catch (CompileException e)
{
diagnostics.Add(e.Diagnostic);
}
if (body == null || returnType == null || invalidParameter)
return null;
return new TypedNodeDefinitionFunc(function.Tokens, moduleName, function.Name, parameters, body, returnType);
}
private TypedNodeDefinitionFunc.Param CheckDefinitionFuncParameter(NodeDefinitionFunc.Param node)
{
return new TypedNodeDefinitionFunc.Param(node.Tokens, node.Name, ResolveType(node.Type));
}
private TypedNodeStatement CheckStatement(NodeStatement node)
{
return node switch
{
NodeStatementAssignment statement => CheckStatementAssignment(statement),
NodeStatementBlock statement => CheckStatementBlock(statement),
NodeStatementExpression statement => CheckStatementExpression(statement),
NodeStatementIf statement => CheckStatementIf(statement),
NodeStatementReturn statement => CheckStatementReturn(statement),
NodeStatementVariableDeclaration statement => CheckStatementVariableDeclaration(statement),
NodeStatementWhile statement => CheckStatementWhile(statement),
_ => throw new ArgumentOutOfRangeException(nameof(node))
};
}
private TypedNodeStatementAssignment CheckStatementAssignment(NodeStatementAssignment statement)
{
return new TypedNodeStatementAssignment(statement.Tokens, CheckExpression(statement.Target), CheckExpression(statement.Value));
}
private TypedNodeStatementBlock CheckStatementBlock(NodeStatementBlock statement)
{
return new TypedNodeStatementBlock(statement.Tokens, statement.Statements.Select(CheckStatement).ToList());
}
private TypedNodeStatementFuncCall CheckStatementExpression(NodeStatementExpression statement)
{
if (statement.Expression is not NodeExpressionFuncCall funcCall)
throw new CompileException(Diagnostic.Error("Expected statement or function call").At(fileName, statement).Build());
return new TypedNodeStatementFuncCall(statement.Tokens, CheckExpression(funcCall.Target), funcCall.Parameters.Select(CheckExpression).ToList());
}
private TypedNodeStatementIf CheckStatementIf(NodeStatementIf statement)
{
return new TypedNodeStatementIf(statement.Tokens, CheckExpression(statement.Condition), CheckStatement(statement.ThenBlock), statement.ElseBlock == null ? null : CheckStatement(statement.ElseBlock));
}
private TypedNodeStatementReturn CheckStatementReturn(NodeStatementReturn statement)
{
return new TypedNodeStatementReturn(statement.Tokens, CheckExpression(statement.Value));
}
private TypedNodeStatementVariableDeclaration CheckStatementVariableDeclaration(NodeStatementVariableDeclaration statement)
{
var type = ResolveType(statement.Type);
var value = CheckExpression(statement.Value);
if (type != value.Type)
throw new CompileException(Diagnostic.Error("Type of variable does match type of assigned value").At(fileName, value).Build());
scope.DeclareIdentifier(statement.Name.Ident, type);
return new TypedNodeStatementVariableDeclaration(statement.Tokens, statement.Name, type, value);
}
private TypedNodeStatementWhile CheckStatementWhile(NodeStatementWhile statement)
{
return new TypedNodeStatementWhile(statement.Tokens, CheckExpression(statement.Condition), CheckStatement(statement.Block));
}
private TypedNodeExpression CheckExpression(NodeExpression node)
{
return node switch
{
NodeExpressionBinary expression => CheckExpressionBinary(expression),
NodeExpressionUnary expression => CheckExpressionUnary(expression),
NodeExpressionBoolLiteral expression => CheckExpressionBoolLiteral(expression),
NodeExpressionLocalIdent expression => CheckExpressionIdent(expression),
NodeExpressionModuleIdent expression => CheckExpressionModuleIdent(expression),
NodeExpressionIntLiteral expression => CheckExpressionIntLiteral(expression),
NodeExpressionMemberAccess expression => CheckExpressionMemberAccess(expression),
NodeExpressionFuncCall expression => CheckExpressionFuncCall(expression),
NodeExpressionStringLiteral expression => CheckExpressionStringLiteral(expression),
NodeExpressionStructLiteral expression => CheckExpressionStructLiteral(expression),
_ => throw new ArgumentOutOfRangeException(nameof(node))
};
}
private TypedNodeExpressionBinary CheckExpressionBinary(NodeExpressionBinary expression)
{
var left = CheckExpression(expression.Left);
var right = CheckExpression(expression.Right);
NubType type;
switch (expression.Operation)
{
case NodeExpressionBinary.Op.Add:
case NodeExpressionBinary.Op.Subtract:
case NodeExpressionBinary.Op.Multiply:
case NodeExpressionBinary.Op.Divide:
case NodeExpressionBinary.Op.Modulo:
{
if (left.Type is not NubTypeSInt and not NubTypeUInt)
throw new CompileException(Diagnostic.Error($"Unsupported type for left hand side arithmetic operation: {left.Type}").At(fileName, left).Build());
if (right.Type is not NubTypeSInt and not NubTypeUInt)
throw new CompileException(Diagnostic.Error($"Unsupported type for right hand side arithmetic operation: {right.Type}").At(fileName, right).Build());
type = left.Type;
break;
}
case NodeExpressionBinary.Op.LeftShift:
case NodeExpressionBinary.Op.RightShift:
{
if (left.Type is not NubTypeUInt)
throw new CompileException(Diagnostic.Error($"Unsupported type for left hand side of left/right shift operation: {left.Type}").At(fileName, left).Build());
if (right.Type is not NubTypeUInt)
throw new CompileException(Diagnostic.Error($"Unsupported type for right hand side of left/right shift operation: {right.Type}").At(fileName, right).Build());
type = left.Type;
break;
}
case NodeExpressionBinary.Op.Equal:
case NodeExpressionBinary.Op.NotEqual:
case NodeExpressionBinary.Op.LessThan:
case NodeExpressionBinary.Op.LessThanOrEqual:
case NodeExpressionBinary.Op.GreaterThan:
case NodeExpressionBinary.Op.GreaterThanOrEqual:
{
if (left.Type is not NubTypeSInt and not NubTypeUInt)
throw new CompileException(Diagnostic.Error($"Unsupported type for left hand side of comparison: {left.Type}").At(fileName, left).Build());
if (right.Type is not NubTypeSInt and not NubTypeUInt)
throw new CompileException(Diagnostic.Error($"Unsupported type for right hand side of comparison: {right.Type}").At(fileName, right).Build());
type = NubTypeBool.Instance;
break;
}
case NodeExpressionBinary.Op.LogicalAnd:
case NodeExpressionBinary.Op.LogicalOr:
{
if (left.Type is not NubTypeBool)
throw new CompileException(Diagnostic.Error($"Unsupported type for left hand side of logical operation: {left.Type}").At(fileName, left).Build());
if (right.Type is not NubTypeBool)
throw new CompileException(Diagnostic.Error($"Unsupported type for right hand side of logical operation: {right.Type}").At(fileName, right).Build());
type = NubTypeBool.Instance;
break;
}
default:
throw new ArgumentOutOfRangeException();
}
return new TypedNodeExpressionBinary(expression.Tokens, type, left, CheckExpressionBinaryOperation(expression.Operation), right);
}
private static TypedNodeExpressionBinary.Op CheckExpressionBinaryOperation(NodeExpressionBinary.Op op)
{
return op switch
{
NodeExpressionBinary.Op.Add => TypedNodeExpressionBinary.Op.Add,
NodeExpressionBinary.Op.Subtract => TypedNodeExpressionBinary.Op.Subtract,
NodeExpressionBinary.Op.Multiply => TypedNodeExpressionBinary.Op.Multiply,
NodeExpressionBinary.Op.Divide => TypedNodeExpressionBinary.Op.Divide,
NodeExpressionBinary.Op.Modulo => TypedNodeExpressionBinary.Op.Modulo,
NodeExpressionBinary.Op.Equal => TypedNodeExpressionBinary.Op.Equal,
NodeExpressionBinary.Op.NotEqual => TypedNodeExpressionBinary.Op.NotEqual,
NodeExpressionBinary.Op.LessThan => TypedNodeExpressionBinary.Op.LessThan,
NodeExpressionBinary.Op.LessThanOrEqual => TypedNodeExpressionBinary.Op.LessThanOrEqual,
NodeExpressionBinary.Op.GreaterThan => TypedNodeExpressionBinary.Op.GreaterThan,
NodeExpressionBinary.Op.GreaterThanOrEqual => TypedNodeExpressionBinary.Op.GreaterThanOrEqual,
NodeExpressionBinary.Op.LeftShift => TypedNodeExpressionBinary.Op.LeftShift,
NodeExpressionBinary.Op.RightShift => TypedNodeExpressionBinary.Op.RightShift,
NodeExpressionBinary.Op.LogicalAnd => TypedNodeExpressionBinary.Op.LogicalAnd,
NodeExpressionBinary.Op.LogicalOr => TypedNodeExpressionBinary.Op.LogicalOr,
_ => throw new ArgumentOutOfRangeException(nameof(op), op, null)
};
}
private TypedNodeExpressionUnary CheckExpressionUnary(NodeExpressionUnary expression)
{
var target = CheckExpression(expression.Target);
NubType type;
switch (expression.Operation)
{
case NodeExpressionUnary.Op.Negate:
{
if (target.Type is not NubTypeSInt and not NubTypeUInt)
throw new CompileException(Diagnostic.Error($"Unsupported type for negation: {target.Type}").At(fileName, target).Build());
type = target.Type;
break;
}
case NodeExpressionUnary.Op.Invert:
{
if (target.Type is not NubTypeBool)
throw new CompileException(Diagnostic.Error($"Unsupported type for inversion: {target.Type}").At(fileName, target).Build());
type = NubTypeBool.Instance;
break;
}
default:
throw new ArgumentOutOfRangeException();
}
return new TypedNodeExpressionUnary(expression.Tokens, type, target, CheckExpressionUnaryOperation(expression.Operation));
}
private static TypedNodeExpressionUnary.Op CheckExpressionUnaryOperation(NodeExpressionUnary.Op op)
{
return op switch
{
NodeExpressionUnary.Op.Negate => TypedNodeExpressionUnary.Op.Negate,
NodeExpressionUnary.Op.Invert => TypedNodeExpressionUnary.Op.Invert,
_ => throw new ArgumentOutOfRangeException(nameof(op), op, null)
};
}
private TypedNodeExpressionBoolLiteral CheckExpressionBoolLiteral(NodeExpressionBoolLiteral expression)
{
return new TypedNodeExpressionBoolLiteral(expression.Tokens, NubTypeBool.Instance, expression.Value);
}
private TypedNodeExpressionLocalIdent CheckExpressionIdent(NodeExpressionLocalIdent expression)
{
var type = scope.GetIdentifierType(expression.Value.Ident);
if (type == null)
throw new CompileException(Diagnostic.Error($"Identifier '{expression.Value.Ident}' is not declared").At(fileName, expression.Value).Build());
return new TypedNodeExpressionLocalIdent(expression.Tokens, type, expression.Value);
}
private TypedNodeExpressionModuleIdent CheckExpressionModuleIdent(NodeExpressionModuleIdent expression)
{
if (!moduleGraph.TryResolveModule(expression.Module.Ident, out var module))
throw new CompileException(Diagnostic.Error($"Module '{expression.Module.Ident}' not found").At(fileName, expression.Module).Build());
var includePrivate = expression.Module.Ident == moduleName;
if (!module.TryResolveIdentifierType(expression.Value.Ident, includePrivate, out var identifierType))
throw new CompileException(Diagnostic.Error($"Identifier '{expression.Module.Ident}::{expression.Value.Ident}' not found").At(fileName, expression.Value).Build());
return new TypedNodeExpressionModuleIdent(expression.Tokens, identifierType, expression.Module, expression.Value);
}
private TypedNodeExpressionIntLiteral CheckExpressionIntLiteral(NodeExpressionIntLiteral expression)
{
return new TypedNodeExpressionIntLiteral(expression.Tokens, NubTypeSInt.Get(32), expression.Value);
}
private TypedNodeExpressionMemberAccess CheckExpressionMemberAccess(NodeExpressionMemberAccess expression)
{
var target = CheckExpression(expression.Target);
if (target.Type is not NubTypeStruct structType)
throw new CompileException(Diagnostic.Error($"Cannot access member of non-struct type {target.Type}").At(fileName, target).Build());
var field = structType.Fields.FirstOrDefault(x => x.Name == expression.Name.Ident);
if (field == null)
throw new CompileException(Diagnostic.Error($"Struct '{target.Type}' does not have a field matching the name '{expression.Name.Ident}'").At(fileName, target).Build());
return new TypedNodeExpressionMemberAccess(expression.Tokens, field.Type, target, expression.Name);
}
private TypedNodeExpressionFuncCall CheckExpressionFuncCall(NodeExpressionFuncCall expression)
{
var target = CheckExpression(expression.Target);
if (target.Type is not NubTypeFunc funcType)
throw new CompileException(Diagnostic.Error($"Cannot invoke function call on type '{target.Type}'").At(fileName, target).Build());
var parameters = expression.Parameters.Select(CheckExpression).ToList();
return new TypedNodeExpressionFuncCall(expression.Tokens, funcType.ReturnType, target, parameters);
}
private TypedNodeExpressionStringLiteral CheckExpressionStringLiteral(NodeExpressionStringLiteral expression)
{
return new TypedNodeExpressionStringLiteral(expression.Tokens, NubTypeString.Instance, expression.Value);
}
private TypedNodeExpressionStructLiteral CheckExpressionStructLiteral(NodeExpressionStructLiteral expression)
{
if (!moduleGraph.TryResolveModule(expression.Module.Ident, out var module))
throw new CompileException(Diagnostic.Error($"Module '{expression.Module.Ident}' not found").At(fileName, expression.Module).Build());
var includePrivate = expression.Module.Ident == moduleName;
if (!module.TryResolveCustomType(expression.Name.Ident, includePrivate, out var customType))
throw new CompileException(Diagnostic.Error($"Struct '{expression.Module.Ident}::{expression.Name.Ident}' not found").At(fileName, expression.Name).Build());
if (customType is not NubTypeStruct structType)
throw new CompileException(Diagnostic.Error($"Cannot create struct literal of non-struct type '{expression.Module.Ident}::{expression.Name.Ident}'").At(fileName, expression.Name).Build());
var initializers = new List<TypedNodeExpressionStructLiteral.Initializer>();
foreach (var initializer in expression.Initializers)
{
var field = structType.Fields.FirstOrDefault(x => x.Name == initializer.Name.Ident);
if (field == null)
throw new CompileException(Diagnostic.Error($"Field '{initializer.Name.Ident}' does not exist on struct '{expression.Name.Ident}'").At(fileName, initializer.Name).Build());
var value = CheckExpression(initializer.Value);
if (value.Type != field.Type)
throw new CompileException(Diagnostic.Error($"Type of assignment ({value.Type}) does not match expected type of field '{field.Name}' ({field.Type})").At(fileName, initializer.Name).Build());
initializers.Add(new TypedNodeExpressionStructLiteral.Initializer(initializer.Tokens, initializer.Name, value));
}
return new TypedNodeExpressionStructLiteral(expression.Tokens, structType, initializers);
}
private NubType ResolveType(NodeType node)
{
return node switch
{
NodeTypeBool => NubTypeBool.Instance,
NodeTypeCustom type => ResolveCustomType(type),
NodeTypeFunc type => NubTypeFunc.Get(type.Parameters.Select(ResolveType).ToList(), ResolveType(type.ReturnType)),
NodeTypePointer type => NubTypePointer.Get(ResolveType(type.To)),
NodeTypeSInt type => NubTypeSInt.Get(type.Width),
NodeTypeUInt type => NubTypeUInt.Get(type.Width),
NodeTypeString => NubTypeString.Instance,
NodeTypeVoid => NubTypeVoid.Instance,
_ => throw new ArgumentOutOfRangeException(nameof(node))
};
}
private NubType ResolveCustomType(NodeTypeCustom type)
{
if (!moduleGraph.TryResolveModule(type.Module.Ident, out var module))
throw new CompileException(Diagnostic.Error($"Module '{type.Module.Ident}' not found").At(fileName, type.Module).Build());
var includePrivate = type.Module.Ident == moduleName;
if (!module.TryResolveCustomType(type.Name.Ident, includePrivate, out var customType))
throw new CompileException(Diagnostic.Error($"Custom type '{type.Module.Ident}::{type.Name.Ident}' not found").At(fileName, type.Name).Build());
return customType;
}
private class Scope(Scope? parent)
{
private readonly Dictionary<string, NubType> identifiers = new();
public void DeclareIdentifier(string name, NubType type)
{
identifiers.Add(name, type);
}
public NubType? GetIdentifierType(string name)
{
return identifiers.TryGetValue(name, out var type)
? type
: parent?.GetIdentifierType(name);
}
}
}
public abstract class TypedNode(List<Token> tokens)
{
public List<Token> Tokens { get; } = tokens;
}
public abstract class TypedNodeDefinition(List<Token> tokens, string module) : TypedNode(tokens)
{
public string Module { get; } = module;
}
public class TypedNodeDefinitionFunc(List<Token> tokens, string module, TokenIdent name, List<TypedNodeDefinitionFunc.Param> parameters, TypedNodeStatement body, NubType returnType) : TypedNodeDefinition(tokens, module)
{
public TokenIdent Name { get; } = name;
public List<Param> Parameters { get; } = parameters;
public TypedNodeStatement Body { get; } = body;
public NubType ReturnType { get; } = returnType;
public NubTypeFunc GetNubType()
{
return NubTypeFunc.Get(Parameters.Select(x => x.Type).ToList(), ReturnType);
}
public string GetMangledName()
{
return SymbolNameGen.Exported(Module, Name.Ident, GetNubType());
}
public class Param(List<Token> tokens, TokenIdent name, NubType type) : TypedNode(tokens)
{
public TokenIdent Name { get; } = name;
public NubType Type { get; } = type;
}
}
public abstract class TypedNodeStatement(List<Token> tokens) : TypedNode(tokens);
public class TypedNodeStatementBlock(List<Token> tokens, List<TypedNodeStatement> statements) : TypedNodeStatement(tokens)
{
public List<TypedNodeStatement> Statements { get; } = statements;
}
public class TypedNodeStatementFuncCall(List<Token> tokens, TypedNodeExpression target, List<TypedNodeExpression> parameters) : TypedNodeStatement(tokens)
{
public TypedNodeExpression Target { get; } = target;
public List<TypedNodeExpression> Parameters { get; } = parameters;
}
public class TypedNodeStatementReturn(List<Token> tokens, TypedNodeExpression value) : TypedNodeStatement(tokens)
{
public TypedNodeExpression Value { get; } = value;
}
public class TypedNodeStatementVariableDeclaration(List<Token> tokens, TokenIdent name, NubType type, TypedNodeExpression value) : TypedNodeStatement(tokens)
{
public TokenIdent Name { get; } = name;
public NubType Type { get; } = type;
public TypedNodeExpression Value { get; } = value;
}
public class TypedNodeStatementAssignment(List<Token> tokens, TypedNodeExpression target, TypedNodeExpression value) : TypedNodeStatement(tokens)
{
public TypedNodeExpression Target { get; } = target;
public TypedNodeExpression Value { get; } = value;
}
public class TypedNodeStatementIf(List<Token> tokens, TypedNodeExpression condition, TypedNodeStatement thenBlock, TypedNodeStatement? elseBlock) : TypedNodeStatement(tokens)
{
public TypedNodeExpression Condition { get; } = condition;
public TypedNodeStatement ThenBlock { get; } = thenBlock;
public TypedNodeStatement? ElseBlock { get; } = elseBlock;
}
public class TypedNodeStatementWhile(List<Token> tokens, TypedNodeExpression condition, TypedNodeStatement block) : TypedNodeStatement(tokens)
{
public TypedNodeExpression Condition { get; } = condition;
public TypedNodeStatement Block { get; } = block;
}
public abstract class TypedNodeExpression(List<Token> tokens, NubType type) : TypedNode(tokens)
{
public NubType Type { get; } = type;
}
public class TypedNodeExpressionIntLiteral(List<Token> tokens, NubType type, TokenIntLiteral value) : TypedNodeExpression(tokens, type)
{
public TokenIntLiteral Value { get; } = value;
}
public class TypedNodeExpressionStringLiteral(List<Token> tokens, NubType type, TokenStringLiteral value) : TypedNodeExpression(tokens, type)
{
public TokenStringLiteral Value { get; } = value;
}
public class TypedNodeExpressionBoolLiteral(List<Token> tokens, NubType type, TokenBoolLiteral value) : TypedNodeExpression(tokens, type)
{
public TokenBoolLiteral Value { get; } = value;
}
public class TypedNodeExpressionStructLiteral(List<Token> tokens, NubType type, List<TypedNodeExpressionStructLiteral.Initializer> initializers) : TypedNodeExpression(tokens, type)
{
public List<Initializer> Initializers { get; } = initializers;
public class Initializer(List<Token> tokens, TokenIdent name, TypedNodeExpression value) : Node(tokens)
{
public TokenIdent Name { get; } = name;
public TypedNodeExpression Value { get; } = value;
}
}
public class TypedNodeExpressionMemberAccess(List<Token> tokens, NubType type, TypedNodeExpression target, TokenIdent name) : TypedNodeExpression(tokens, type)
{
public TypedNodeExpression Target { get; } = target;
public TokenIdent Name { get; } = name;
}
public class TypedNodeExpressionFuncCall(List<Token> tokens, NubType type, TypedNodeExpression target, List<TypedNodeExpression> parameters) : TypedNodeExpression(tokens, type)
{
public TypedNodeExpression Target { get; } = target;
public List<TypedNodeExpression> Parameters { get; } = parameters;
}
public class TypedNodeExpressionLocalIdent(List<Token> tokens, NubType type, TokenIdent value) : TypedNodeExpression(tokens, type)
{
public TokenIdent Value { get; } = value;
}
public class TypedNodeExpressionModuleIdent(List<Token> tokens, NubType type, TokenIdent module, TokenIdent value) : TypedNodeExpression(tokens, type)
{
public TokenIdent Module { get; } = module;
public TokenIdent Value { get; } = value;
}
public class TypedNodeExpressionBinary(List<Token> tokens, NubType type, TypedNodeExpression left, TypedNodeExpressionBinary.Op operation, TypedNodeExpression right) : TypedNodeExpression(tokens, type)
{
public TypedNodeExpression Left { get; } = left;
public Op Operation { get; } = operation;
public TypedNodeExpression Right { get; } = right;
public enum Op
{
Add,
Subtract,
Multiply,
Divide,
Modulo,
Equal,
NotEqual,
LessThan,
LessThanOrEqual,
GreaterThan,
GreaterThanOrEqual,
LeftShift,
RightShift,
// BitwiseAnd,
// BitwiseXor,
// BitwiseOr,
LogicalAnd,
LogicalOr,
}
}
public class TypedNodeExpressionUnary(List<Token> tokens, NubType type, TypedNodeExpression target, TypedNodeExpressionUnary.Op op) : TypedNodeExpression(tokens, type)
{
public TypedNodeExpression Target { get; } = target;
public Op Operation { get; } = op;
public enum Op
{
Negate,
Invert,
}
}

3
examples/.gitignore vendored
View File

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

11
examples/build.sh Executable file
View File

@@ -0,0 +1,11 @@
pushd math
dotnet run --project ../../compiler math.nub --type=lib
popd
pushd program
dotnet run --project ../../compiler main.nub ../math/.build/out.nublib
popd

View File

@@ -1,6 +0,0 @@
#!/bin/bash
set -euo pipefail
obj=$(nubc main.nub)
clang $obj -o .build/out

View File

@@ -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
}

View File

@@ -0,0 +1,15 @@
{
"version": 1,
"modules": [
{
"name": "math",
"customTypes": {},
"identifiers": {
"add": {
"encodedType": "F(I(32),I(32),I(32))",
"exported": true
}
}
}
]
}

BIN
examples/math/.build/out.a Normal file

Binary file not shown.

View File

@@ -0,0 +1,24 @@
#include <float.h>
#include <stdarg.h>
#include <stddef.h>
#include <stdbool.h>
#include <stdint.h>
struct nub_core_string
{
const char *data;
int length;
};
int32_t nub_math_add_500748c2c6d70959(int32_t, int32_t);
int32_t nub_math_add_500748c2c6d70959(int32_t a, int32_t b)
{
{
return (a + b);
}
}

Binary file not shown.

BIN
examples/math/.build/out.o Normal file

Binary file not shown.

6
examples/math/math.nub Normal file
View File

@@ -0,0 +1,6 @@
module math
export func add(a: i32 b: i32): i32
{
return a + b
}

BIN
examples/program/.build/out Executable file

Binary file not shown.

View File

@@ -0,0 +1,29 @@
#include <float.h>
#include <stdarg.h>
#include <stddef.h>
#include <stdbool.h>
#include <stdint.h>
struct nub_core_string
{
const char *data;
int length;
};
extern int32_t nub_math_add_500748c2c6d70959(int32_t, int32_t);
int32_t nub_main_main_55882df37f903935();
int main(int argc, char *argv[])
{
return nub_main_main_55882df37f903935();
}
int32_t nub_main_main_55882df37f903935()
{
{
return nub_math_add_500748c2c6d70959(1, 2);
}
}

View File

@@ -0,0 +1,6 @@
module main
func main(): i32
{
return math::add(1 2)
}

View File

@@ -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

View File

@@ -1,55 +0,0 @@
import "raylib"
module "main"
extern "main" func main(argc: i64, argv: [?]^i8): i64
{
let uwu: []i32 = [1, 2]
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

View File

@@ -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.

View File

@@ -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>
[![GitHub Releases Downloads](https://img.shields.io/github/downloads/raysan5/raylib/total)](https://github.com/raysan5/raylib/releases)
[![GitHub Stars](https://img.shields.io/github/stars/raysan5/raylib?style=flat&label=stars)](https://github.com/raysan5/raylib/stargazers)
[![GitHub commits since tagged version](https://img.shields.io/github/commits-since/raysan5/raylib/5.0)](https://github.com/raysan5/raylib/commits/master)
[![GitHub Sponsors](https://img.shields.io/github/sponsors/raysan5?label=sponsors)](https://github.com/sponsors/raysan5)
[![Packaging Status](https://repology.org/badge/tiny-repos/raylib.svg)](https://repology.org/project/raylib/versions)
[![License](https://img.shields.io/badge/license-zlib%2Flibpng-blue.svg)](LICENSE)
[![Discord Members](https://img.shields.io/discord/426912293134270465.svg?label=Discord&logo=discord)](https://discord.gg/raylib)
[![Reddit Static Badge](https://img.shields.io/badge/-r%2Fraylib-red?style=flat&logo=reddit&label=reddit)](https://www.reddit.com/r/raylib/)
[![Youtube Subscribers](https://img.shields.io/youtube/channel/subscribers/UC8WIBkhYb5sBNqXO1mZ7WSQ?style=flat&label=Youtube&logo=youtube)](https://www.youtube.com/c/raylib)
[![Twitch Status](https://img.shields.io/twitch/status/raysan5?style=flat&label=Twitch&logo=twitch)](https://www.twitch.tv/raysan5)
[![Windows](https://github.com/raysan5/raylib/workflows/Windows/badge.svg)](https://github.com/raysan5/raylib/actions?query=workflow%3AWindows)
[![Linux](https://github.com/raysan5/raylib/workflows/Linux/badge.svg)](https://github.com/raysan5/raylib/actions?query=workflow%3ALinux)
[![macOS](https://github.com/raysan5/raylib/workflows/macOS/badge.svg)](https://github.com/raysan5/raylib/actions?query=workflow%3AmacOS)
[![WebAssembly](https://github.com/raysan5/raylib/workflows/WebAssembly/badge.svg)](https://github.com/raysan5/raylib/actions?query=workflow%3AWebAssembly)
[![CMakeBuilds](https://github.com/raysan5/raylib/workflows/CMakeBuilds/badge.svg)](https://github.com/raysan5/raylib/actions?query=workflow%3ACMakeBuilds)
[![Windows Examples](https://github.com/raysan5/raylib/actions/workflows/windows_examples.yml/badge.svg)](https://github.com/raysan5/raylib/actions/workflows/windows_examples.yml)
[![Linux Examples](https://github.com/raysan5/raylib/actions/workflows/linux_examples.yml/badge.svg)](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

View File

@@ -1 +0,0 @@
libraylib.so.550

View File

@@ -1 +0,0 @@
libraylib.so.5.5.0

32
examples/test/test.nub Normal file
View File

@@ -0,0 +1,32 @@
module main
let global: i32
func main(): i32 {
let x: i32 = 23
x = 24
if !true {
x = 49
return x
} else {
x = 3
}
let i: i32 = 0
x = 1 + 2 * 34
while i < 10 {
i = i + 1
x = i
}
let me: test::person = struct test::person { age = 21 name = "Oliver" }
x = test::do_something(me.name)
test::do_something(me.name)
main::global = 123
return main::global
}

10
examples/test/test2.nub Normal file
View File

@@ -0,0 +1,10 @@
module test
export packed struct person {
age: i32
name: string
}
export func do_something(name: string): i32 {
return 3
}

View File

@@ -1,4 +0,0 @@
node_modules
out
nub-*.vsix
server

View File

@@ -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}"
}
]
}

View File

@@ -1,8 +0,0 @@
{
"files.exclude": {},
"search.exclude": {
"out": true,
"node_modules": true,
},
"typescript.tsc.autoDetect": "off"
}

View File

@@ -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
}
}
]
}

View File

@@ -1,69 +0,0 @@
{
"comments": {
"lineComment": {
"comment": "//"
},
"blockComment": [
"/*",
"*/"
]
},
"brackets": [
[
"{",
"}"
],
[
"[",
"]"
],
[
"(",
")"
]
],
"autoClosingPairs": [
{
"open": "{",
"close": "}"
},
{
"open": "[",
"close": "]"
},
{
"open": "(",
"close": ")"
},
{
"open": "\"",
"close": "\""
},
{
"open": "'",
"close": "'"
}
],
"surroundingPairs": [
[
"{",
"}"
],
[
"[",
"]"
],
[
"(",
")"
],
[
"\"",
"\""
],
[
"'",
"'"
]
]
}

File diff suppressed because it is too large Load Diff

View File

@@ -1,57 +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"
}
],
"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"
}
}

View File

@@ -1,44 +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')
}
}
);
client.start();
}
export function deactivate(): Thenable<void> | undefined {
if (!client) {
return undefined;
}
return client.stop();
}

View File

@@ -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"
}
]
}
}
}

View File

@@ -1,15 +0,0 @@
{
"compilerOptions": {
"module": "Node16",
"target": "ES2022",
"outDir": "out",
"lib": [
"ES2022"
],
"sourceMap": true,
"rootDir": "src",
"strict": true,
"noImplicitReturns": true,
"noFallthroughCasesInSwitch": true
}
}