This commit is contained in:
nub31
2025-07-06 20:56:56 +02:00
parent f15073cac4
commit c79985bc05
26 changed files with 94 additions and 170 deletions

View File

@@ -0,0 +1,643 @@
using System.Diagnostics;
using Common;
using NubLang.Diagnostics;
using NubLang.Syntax.Node;
using NubLang.Syntax.Tokenization;
using Node_UnaryExpressionNode = NubLang.Syntax.Node.UnaryExpressionNode;
namespace NubLang.Syntax.Binding;
// TODO: Currently anonymous function does not get a new scope
public sealed class Binder
{
private readonly SyntaxTree _syntaxTree;
private readonly DefinitionTable _definitionTable;
// TODO: Implement proper variable tracking and scoping
private Dictionary<string, BoundNubType> _variables = new();
private BoundNubType? _functionReturnType;
public Binder(SyntaxTree syntaxTree, DefinitionTable definitionTable)
{
_syntaxTree = syntaxTree;
_definitionTable = definitionTable;
}
public BoundSyntaxTree Bind()
{
_variables = [];
_functionReturnType = null;
var diagnostics = new List<Diagnostic>();
var topLevelNodes = new List<BoundTopLevelNode>();
foreach (var topLevel in _syntaxTree.TopLevelNodes)
{
try
{
topLevelNodes.Add(BindTopLevel(topLevel));
}
catch (BindException e)
{
diagnostics.Add(e.Diagnostic);
}
}
return new BoundSyntaxTree(_syntaxTree.Namespace, topLevelNodes, diagnostics);
}
private BoundTopLevelNode BindTopLevel(TopLevelNode node)
{
return node switch
{
ExternFuncNode topLevel => BindExternFuncDefinition(topLevel),
TraitImplNode topLevel => BindTraitImplementation(topLevel),
TraitNode topLevel => BindTraitDefinition(topLevel),
LocalFuncNode topLevel => BindLocalFuncDefinition(topLevel),
StructNode topLevel => BindStruct(topLevel),
_ => throw new ArgumentOutOfRangeException(nameof(node))
};
}
private BoundTraitImplNode BindTraitImplementation(TraitImplNode node)
{
_variables.Clear();
var functions = new List<BoundTraitFuncImplNode>();
foreach (var function in node.Functions)
{
var parameters = new List<BoundFuncParameterNode>();
foreach (var parameter in function.Parameters)
{
_variables[parameter.Name] = BindType(parameter.Type);
parameters.Add(new BoundFuncParameterNode(parameter.Tokens, parameter.Name, BindType(parameter.Type)));
}
functions.Add(new BoundTraitFuncImplNode(function.Tokens, function.Name, parameters, BindType(function.ReturnType), BindBlock(function.Body)));
}
return new BoundTraitImplNode(node.Tokens, node.Namespace, BindType(node.TraitType), BindType(node.ForType), functions);
}
private BoundTraitNode BindTraitDefinition(TraitNode node)
{
var functions = new List<BoundTraitFuncNode>();
foreach (var function in node.Functions)
{
var parameters = new List<BoundFuncParameterNode>();
foreach (var parameter in function.Parameters)
{
parameters.Add(new BoundFuncParameterNode(parameter.Tokens, parameter.Name, BindType(parameter.Type)));
}
functions.Add(new BoundTraitFuncNode(node.Tokens, function.Name, parameters, BindType(function.ReturnType)));
}
return new BoundTraitNode(node.Tokens, node.Namespace, node.Name, functions);
}
private BoundStructNode BindStruct(StructNode node)
{
var structFields = new List<BoundStructFieldNode>();
foreach (var field in node.Fields)
{
var value = Optional.Empty<BoundExpressionNode>();
if (field.Value.HasValue)
{
value = BindExpression(field.Value.Value, BindType(field.Type));
}
structFields.Add(new BoundStructFieldNode(field.Tokens, field.Index, field.Name, BindType(field.Type), value));
}
return new BoundStructNode(node.Tokens, node.Namespace, node.Name, structFields);
}
private BoundExternFuncNode BindExternFuncDefinition(ExternFuncNode node)
{
var parameters = new List<BoundFuncParameterNode>();
foreach (var parameter in node.Parameters)
{
parameters.Add(new BoundFuncParameterNode(parameter.Tokens, parameter.Name, BindType(parameter.Type)));
}
return new BoundExternFuncNode(node.Tokens, node.Namespace, node.Name, node.CallName, parameters, BindType(node.ReturnType));
}
private BoundLocalFuncNode BindLocalFuncDefinition(LocalFuncNode node)
{
_variables.Clear();
_functionReturnType = BindType(node.ReturnType);
var parameters = new List<BoundFuncParameterNode>();
foreach (var parameter in node.Parameters)
{
_variables[parameter.Name] = BindType(parameter.Type);
parameters.Add(new BoundFuncParameterNode(parameter.Tokens, parameter.Name, BindType(parameter.Type)));
}
var body = BindBlock(node.Body);
return new BoundLocalFuncNode(node.Tokens, node.Namespace, node.Name, parameters, body, BindType(node.ReturnType), node.Exported);
}
private BoundBlock BindBlock(BlockNode node)
{
var statements = new List<BoundStatementNode>();
foreach (var statement in node.Statements)
{
statements.Add(BindStatement(statement));
}
return new BoundBlock(node.Tokens, statements);
}
private BoundStatementNode BindStatement(StatementNode node)
{
return node switch
{
AssignmentNode statement => BindAssignment(statement),
BreakNode statement => BindBreak(statement),
ContinueNode statement => BindContinue(statement),
IfNode statement => BindIf(statement),
ReturnNode statement => BindReturn(statement),
StatementExpressionNode statement => BindStatementExpression(statement),
VariableDeclarationNode statement => BindVariableDeclaration(statement),
WhileNode statement => BindWhile(statement),
_ => throw new ArgumentOutOfRangeException(nameof(node))
};
}
private BoundStatementNode BindAssignment(AssignmentNode statement)
{
var expression = BindExpression(statement.Target);
var value = BindExpression(statement.Value, expression.Type);
return new BoundAssignmentNode(statement.Tokens, expression, value);
}
private BoundBreakNode BindBreak(BreakNode statement)
{
return new BoundBreakNode(statement.Tokens);
}
private BoundContinueNode BindContinue(ContinueNode statement)
{
return new BoundContinueNode(statement.Tokens);
}
private BoundIfNode BindIf(IfNode statement)
{
var elseStatement = Optional.Empty<Variant<BoundIfNode, BoundBlock>>();
if (statement.Else.HasValue)
{
elseStatement = statement.Else.Value.Match<Variant<BoundIfNode, BoundBlock>>
(
elseIf => BindIf(elseIf),
@else => BindBlock(@else)
);
}
return new BoundIfNode(statement.Tokens, BindExpression(statement.Condition, BoundNubPrimitiveType.Bool), BindBlock(statement.Body), elseStatement);
}
private BoundReturnNode BindReturn(ReturnNode statement)
{
var value = Optional.Empty<BoundExpressionNode>();
if (statement.Value.HasValue)
{
value = BindExpression(statement.Value.Value, _functionReturnType);
}
return new BoundReturnNode(statement.Tokens, value);
}
private BoundStatementExpressionNode BindStatementExpression(StatementExpressionNode statement)
{
return new BoundStatementExpressionNode(statement.Tokens, BindExpression(statement.Expression));
}
private BoundVariableDeclarationNode BindVariableDeclaration(VariableDeclarationNode statement)
{
BoundNubType? type = null;
if (statement.ExplicitType.HasValue)
{
type = BindType(statement.ExplicitType.Value);
}
var assignment = Optional<BoundExpressionNode>.Empty();
if (statement.Assignment.HasValue)
{
var boundValue = BindExpression(statement.Assignment.Value, type);
assignment = boundValue;
type = boundValue.Type;
}
if (type == null)
{
throw new NotImplementedException("Diagnostics not implemented");
}
_variables[statement.Name] = type;
return new BoundVariableDeclarationNode(statement.Tokens, statement.Name, assignment, type);
}
private BoundWhileNode BindWhile(WhileNode statement)
{
return new BoundWhileNode(statement.Tokens, BindExpression(statement.Condition, BoundNubPrimitiveType.Bool), BindBlock(statement.Body));
}
private BoundExpressionNode BindExpression(ExpressionNode node, BoundNubType? expectedType = null)
{
return node switch
{
AddressOfNode expression => BindAddressOf(expression),
AnonymousFuncNode expression => BindAnonymousFunc(expression),
ArrayIndexAccessNode expression => BindArrayIndexAccess(expression),
ArrayInitializerNode expression => BindArrayInitializer(expression),
BinaryExpressionNode expression => BindBinaryExpression(expression),
DereferenceNode expression => BindDereference(expression),
FuncCallNode expression => BindFuncCall(expression),
IdentifierNode expression => BindIdentifier(expression),
LiteralNode expression => BindLiteral(expression, expectedType),
MemberAccessNode expression => BindMemberAccess(expression),
StructInitializerNode expression => BindStructInitializer(expression),
Node_UnaryExpressionNode expression => BindUnaryExpression(expression),
_ => throw new ArgumentOutOfRangeException(nameof(node))
};
}
private BoundAddressOfNode BindAddressOf(AddressOfNode expression)
{
var inner = BindExpression(expression.Expression);
return new BoundAddressOfNode(expression.Tokens, new BoundNubPointerType(inner.Type), inner);
}
private BoundAnonymousFuncNode BindAnonymousFunc(AnonymousFuncNode expression)
{
var parameters = new List<BoundFuncParameterNode>();
foreach (var parameter in expression.Parameters)
{
parameters.Add(new BoundFuncParameterNode(parameter.Tokens, parameter.Name, BindType(parameter.Type)));
}
var body = BindBlock(expression.Body);
return new BoundAnonymousFuncNode(expression.Tokens, new BoundNubFuncType(BindType(expression.ReturnType), parameters.Select(x => x.Type).ToList()), parameters, body, BindType(expression.ReturnType));
}
private BoundArrayIndexAccessNode BindArrayIndexAccess(ArrayIndexAccessNode expression)
{
var boundArray = BindExpression(expression.Target);
var elementType = ((BoundNubArrayType)boundArray.Type).ElementType;
return new BoundArrayIndexAccessNode(expression.Tokens, elementType, boundArray, BindExpression(expression.Index, BoundNubPrimitiveType.U64));
}
private BoundArrayInitializerNode BindArrayInitializer(ArrayInitializerNode expression)
{
return new BoundArrayInitializerNode(expression.Tokens, new BoundNubArrayType(BindType(expression.ElementType)), BindExpression(expression.Capacity, BoundNubPrimitiveType.U64), BindType(expression.ElementType));
}
private BoundBinaryExpressionNode BindBinaryExpression(BinaryExpressionNode expression)
{
var boundLeft = BindExpression(expression.Left);
var boundRight = BindExpression(expression.Right, boundLeft.Type);
return new BoundBinaryExpressionNode(expression.Tokens, boundLeft.Type, boundLeft, expression.Operator, boundRight);
}
private BoundDereferenceNode BindDereference(DereferenceNode expression)
{
var boundExpression = BindExpression(expression.Expression);
var dereferencedType = ((BoundNubPointerType)boundExpression.Type).BaseType;
return new BoundDereferenceNode(expression.Tokens, dereferencedType, boundExpression);
}
private BoundFuncCallNode BindFuncCall(FuncCallNode expression)
{
var boundExpression = BindExpression(expression.Expression);
var funcType = (BoundNubFuncType)boundExpression.Type;
var parameters = new List<BoundExpressionNode>();
foreach (var (i, parameter) in expression.Parameters.Index())
{
if (i >= funcType.Parameters.Count)
{
throw new NotImplementedException("Diagnostics not implemented");
}
var expectedType = funcType.Parameters[i];
parameters.Add(BindExpression(parameter, expectedType));
}
return new BoundFuncCallNode(expression.Tokens, funcType.ReturnType, boundExpression, parameters);
}
private BoundExpressionNode BindIdentifier(IdentifierNode expression)
{
var @namespace = expression.Namespace.Or(_syntaxTree.Namespace);
var localFuncs = _definitionTable.LookupLocalFunc(@namespace, expression.Name).ToArray();
if (localFuncs.Length > 0)
{
if (localFuncs.Length > 1)
{
throw new BindException(Diagnostic.Error($"Extern func {expression.Namespace}::{expression.Name} has multiple definitions").Build());
}
var localFunc = localFuncs[0];
var type = new NubFuncType(localFunc.ReturnType, localFunc.Parameters.Select(p => p.Type).ToList());
return new BoundLocalFuncIdentNode(expression.Tokens, BindType(type), @namespace, expression.Name);
}
var externFuncs = _definitionTable.LookupExternFunc(@namespace, expression.Name).ToArray();
if (externFuncs.Length > 0)
{
if (externFuncs.Length > 1)
{
throw new BindException(Diagnostic.Error($"Extern func {expression.Namespace}::{expression.Name} has multiple definitions").Build());
}
var externFunc = externFuncs[0];
var type = new NubFuncType(externFunc.ReturnType, externFunc.Parameters.Select(p => p.Type).ToList());
return new BoundExternFuncIdentNode(expression.Tokens, BindType(type), @namespace, expression.Name);
}
if (!expression.Namespace.HasValue)
{
return new BoundVariableIdentNode(expression.Tokens, _variables[expression.Name], expression.Name);
}
throw new BindException(Diagnostic.Error($"No identifier with then name {(expression.Namespace.HasValue ? $"{expression.Namespace.Value}::" : "")}{expression.Name} exists").Build());
}
private BoundLiteralNode BindLiteral(LiteralNode expression, BoundNubType? expectedType = null)
{
var type = expectedType ?? expression.Kind switch
{
LiteralKind.Integer => BoundNubPrimitiveType.I64,
LiteralKind.Float => BoundNubPrimitiveType.F64,
LiteralKind.String => new BoundNubStringType(),
LiteralKind.Bool => BoundNubPrimitiveType.Bool,
_ => throw new ArgumentOutOfRangeException()
};
return new BoundLiteralNode(expression.Tokens, type, expression.Literal, expression.Kind);
}
private BoundExpressionNode BindMemberAccess(MemberAccessNode expression)
{
var boundExpression = BindExpression(expression.Target);
var traitFuncImpls = _definitionTable.LookupTraitFuncImpl(UnbindType(boundExpression.Type), expression.Member).ToArray();
if (traitFuncImpls.Length > 0)
{
if (traitFuncImpls.Length > 1)
{
throw new BindException(Diagnostic.Error($"Type {boundExpression.Type} implements multiple traits with the function {expression.Member}").Build());
}
var impl = traitFuncImpls[0];
var type = new BoundNubFuncType(BindType(impl.ReturnType), impl.Parameters.Select(p => BindType(p.Type)).ToList());
return new BoundTraitImplFuncAccessNode(expression.Tokens, type, boundExpression, expression.Member);
}
if (boundExpression.Type is BoundNubTraitType traitType)
{
var traits = _definitionTable.LookupTrait(traitType.Namespace, traitType.Name).ToArray();
if (traits.Length > 0)
{
if (traits.Length > 1)
{
throw new BindException(Diagnostic.Error($"Trait {traitType.Namespace}::{traitType.Name} has multiple definitions").Build());
}
var trait = traits[0];
var traitFuncs = _definitionTable.LookupTraitFunc(trait, expression.Member).ToArray();
if (traits.Length > 0)
{
if (traits.Length > 1)
{
throw new BindException(Diagnostic.Error($"Trait {trait.Namespace}::{trait.Name} has multiple functions with the name {expression.Member}").Build());
}
var traitFunc = traitFuncs[0];
var type = new BoundNubFuncType(BindType(traitFunc.ReturnType), traitFunc.Parameters.Select(p => BindType(p.Type)).ToList());
return new BoundTraitFuncAccessNode(expression.Tokens, type, traitType, boundExpression, expression.Member);
}
}
}
if (boundExpression.Type is BoundNubStructType structType)
{
var structs = _definitionTable.LookupStruct(structType.Namespace, structType.Name).ToArray();
if (structs.Length > 0)
{
if (structs.Length > 1)
{
throw new BindException(Diagnostic.Error($"Struct {structType.Namespace}::{structType.Name} has multiple definitions").Build());
}
var @struct = structs[0];
var fields = _definitionTable.LookupStructField(@struct, expression.Member).ToArray();
if (fields.Length > 0)
{
if (fields.Length > 1)
{
throw new BindException(Diagnostic.Error($"Struct {@struct.Namespace}::{@struct.Name} has multiple fields with the name {expression.Member}").Build());
}
var field = fields[0];
return new BoundStructFieldAccessNode(expression.Tokens, BindType(field.Type), structType, boundExpression, expression.Member);
}
}
}
throw new BindException(Diagnostic.Error($"{boundExpression.Type} does not have a member with the name {expression.Member}").Build());
}
private BoundStructInitializerNode BindStructInitializer(StructInitializerNode expression)
{
if (expression.StructType is not NubCustomType structType)
{
throw new BindException(Diagnostic.Error($"Cannot initialize non-struct type {expression.StructType}").Build());
}
var structs = _definitionTable.LookupStruct(structType.Namespace, structType.Name).ToArray();
if (structs.Length == 0)
{
throw new BindException(Diagnostic.Error($"Struct {structType.Namespace}::{structType.Name} is not defined").Build());
}
if (structs.Length > 1)
{
throw new BindException(Diagnostic.Error($"Struct {structType.Namespace}::{structType.Name} has multiple definitions").Build());
}
var @struct = structs[0];
var initializers = new Dictionary<string, BoundExpressionNode>();
foreach (var (field, initializer) in expression.Initializers)
{
var fields = _definitionTable.LookupStructField(@struct, field).ToArray();
if (fields.Length == 0)
{
throw new BindException(Diagnostic.Error($"Struct {@struct.Namespace}::{@struct.Name} does not have a field with the name {field}").Build());
}
if (fields.Length > 1)
{
throw new BindException(Diagnostic.Error($"Struct {@struct.Namespace}::{@struct.Name} has multiple fields with the name {field}").Build());
}
initializers[field] = BindExpression(initializer, BindType(fields[0].Type));
}
return new BoundStructInitializerNode(expression.Tokens, BindType(structType), new BoundNubStructType(@struct.Namespace, @struct.Name), initializers);
}
private BoundUnaryExpressionNode BindUnaryExpression(Node_UnaryExpressionNode expression)
{
var boundOperand = BindExpression(expression.Operand);
BoundNubType? type = null;
switch (expression.Operator)
{
case UnaryExpressionOperator.Negate:
{
boundOperand = BindExpression(expression.Operand, BoundNubPrimitiveType.I64);
if (boundOperand.Type.IsNumber)
{
type = boundOperand.Type;
}
break;
}
case UnaryExpressionOperator.Invert:
{
boundOperand = BindExpression(expression.Operand, BoundNubPrimitiveType.Bool);
type = new BoundNubPrimitiveType(PrimitiveTypeKind.Bool);
break;
}
}
if (type == null)
{
throw new NotImplementedException("Diagnostics not implemented");
}
return new BoundUnaryExpressionNode(expression.Tokens, type, expression.Operator, boundOperand);
}
private BoundNubType BindType(NubType type)
{
switch (type)
{
case NubCustomType customType:
{
var structs = _definitionTable.LookupStruct(customType.Namespace, customType.Name).ToArray();
if (structs.Length > 1)
{
throw new BindException(Diagnostic.Error($"Struct {type} has multiple definitions").Build());
}
var traits = _definitionTable.LookupTrait(customType.Namespace, customType.Name).ToArray();
if (traits.Length > 1)
{
throw new BindException(Diagnostic.Error($"Trait {type} has multiple definitions").Build());
}
if (structs.Length == 0 && traits.Length == 0)
{
throw new BindException(Diagnostic.Error($"Failed to resolve type {type} to a struct or trait").Build());
}
if (structs.Length > 0 && traits.Length > 0)
{
throw new BindException(Diagnostic.Error($"Unable to determine if type {type} is a struct or trait").WithHelp($"Make {type} is not defined as bot a struct and trait").Build());
}
if (structs.Length == 1)
{
return new BoundNubStructType(customType.Namespace, customType.Name);
}
if (traits.Length == 1)
{
return new BoundNubTraitType(customType.Namespace, customType.Name);
}
throw new UnreachableException();
}
case NubArrayType arrayType:
return new BoundNubArrayType(BindType(arrayType.ElementType));
case NubCStringType:
return new BoundNubCStringType();
case NubStringType:
return new BoundNubStringType();
case NubFuncType funcType:
return new BoundNubFuncType(BindType(funcType.ReturnType), funcType.Parameters.Select(BindType).ToList());
case NubPointerType pointerType:
return new BoundNubPointerType(BindType(pointerType.BaseType));
case NubPrimitiveType primitiveType:
return new BoundNubPrimitiveType(primitiveType.Kind);
case NubVoidType:
return new BoundNubVoidType();
default:
throw new ArgumentOutOfRangeException(nameof(type));
}
}
private NubType UnbindType(BoundNubType type)
{
return type switch
{
BoundNubArrayType arrayType => new NubArrayType(UnbindType(arrayType.ElementType)),
BoundNubCStringType => new NubCStringType(),
BoundNubStringType => new NubStringType(),
BoundNubStructType structType => new NubCustomType(structType.Namespace, structType.Name),
BoundNubTraitType traitType => new NubCustomType(traitType.Namespace, traitType.Name),
BoundNubFuncType funcType => new NubFuncType(UnbindType(funcType.ReturnType), funcType.Parameters.Select(UnbindType).ToList()),
BoundNubPointerType pointerType => new NubPointerType(UnbindType(pointerType.BaseType)),
BoundNubPrimitiveType primitiveType => new NubPrimitiveType(primitiveType.Kind),
BoundNubVoidType => new NubVoidType(),
_ => throw new ArgumentOutOfRangeException(nameof(type))
};
}
}
public class BindException : Exception
{
public Diagnostic Diagnostic { get; }
public BindException(Diagnostic diagnostic) : base(diagnostic.Message)
{
Diagnostic = diagnostic;
}
}

View File

@@ -0,0 +1,37 @@
using Common;
using NubLang.Syntax.Tokenization;
namespace NubLang.Syntax.Node;
public record FuncParameterNode(IEnumerable<Token> Tokens, string Name, NubType Type) : DefinitionNode(Tokens);
public record BoundFuncParameterNode(IEnumerable<Token> Tokens, string Name, BoundNubType Type) : DefinitionNode(Tokens);
public abstract record TopLevelNode(IEnumerable<Token> Tokens, string Namespace) : Node(Tokens);
public abstract record BoundTopLevelNode(IEnumerable<Token> Tokens, string Namespace) : BoundNode(Tokens);
public abstract record DefinitionNode(IEnumerable<Token> Tokens) : Node(Tokens);
public abstract record BoundDefinitionNode(IEnumerable<Token> Tokens) : BoundNode(Tokens);
public record LocalFuncNode(IEnumerable<Token> Tokens, string Namespace, string Name, List<FuncParameterNode> Parameters, BlockNode Body, NubType ReturnType, bool Exported) : TopLevelNode(Tokens, Namespace);
public record BoundLocalFuncNode(IEnumerable<Token> Tokens, string Namespace, string Name, List<BoundFuncParameterNode> Parameters, BoundBlock Body, BoundNubType ReturnType, bool Exported) : BoundTopLevelNode(Tokens, Namespace);
public record ExternFuncNode(IEnumerable<Token> Tokens, string Namespace, string Name, string CallName, List<FuncParameterNode> Parameters, NubType ReturnType) : TopLevelNode(Tokens, Namespace);
public record BoundExternFuncNode(IEnumerable<Token> Tokens, string Namespace, string Name, string CallName, List<BoundFuncParameterNode> Parameters, BoundNubType ReturnType) : BoundTopLevelNode(Tokens, Namespace);
public record StructFieldNode(IEnumerable<Token> Tokens, int Index, string Name, NubType Type, Optional<ExpressionNode> Value) : DefinitionNode(Tokens);
public record StructNode(IEnumerable<Token> Tokens, string Namespace, string Name, List<StructFieldNode> Fields) : TopLevelNode(Tokens, Namespace);
public record BoundStructFieldNode(IEnumerable<Token> Tokens, int Index, string Name, BoundNubType Type, Optional<BoundExpressionNode> Value) : BoundDefinitionNode(Tokens);
public record BoundStructNode(IEnumerable<Token> Tokens, string Namespace, string Name, List<BoundStructFieldNode> Fields) : BoundTopLevelNode(Tokens, Namespace);
public record TraitFuncNode(IEnumerable<Token> Tokens, string Name, List<FuncParameterNode> Parameters, NubType ReturnType) : DefinitionNode(Tokens);
public record TraitNode(IEnumerable<Token> Tokens, string Namespace, string Name, List<TraitFuncNode> Functions) : TopLevelNode(Tokens, Namespace);
public record BoundTraitFuncNode(IEnumerable<Token> Tokens, string Name, List<BoundFuncParameterNode> Parameters, BoundNubType ReturnType) : BoundDefinitionNode(Tokens);
public record BoundTraitNode(IEnumerable<Token> Tokens, string Namespace, string Name, List<BoundTraitFuncNode> Functions) : BoundTopLevelNode(Tokens, Namespace);
public record TraitFuncImplNode(IEnumerable<Token> Tokens, string Name, List<FuncParameterNode> Parameters, NubType ReturnType, BlockNode Body) : DefinitionNode(Tokens);
public record TraitImplNode(IEnumerable<Token> Tokens, string Namespace, NubType TraitType, NubType ForType, List<TraitFuncImplNode> Functions) : TopLevelNode(Tokens, Namespace);
public record BoundTraitFuncImplNode(IEnumerable<Token> Tokens, string Name, List<BoundFuncParameterNode> Parameters, BoundNubType ReturnType, BoundBlock Body) : BoundDefinitionNode(Tokens);
public record BoundTraitImplNode(IEnumerable<Token> Tokens, string Namespace, BoundNubType TraitType, BoundNubType ForType, List<BoundTraitFuncImplNode> Functions) : BoundTopLevelNode(Tokens, Namespace);

View File

@@ -0,0 +1,67 @@
using Common;
using NubLang.Syntax.Tokenization;
namespace NubLang.Syntax.Node;
public enum UnaryExpressionOperator
{
Negate,
Invert
}
public enum BinaryExpressionOperator
{
Equal,
NotEqual,
GreaterThan,
GreaterThanOrEqual,
LessThan,
LessThanOrEqual,
Plus,
Minus,
Multiply,
Divide
}
public abstract record ExpressionNode(IEnumerable<Token> Tokens) : Node(Tokens);
public abstract record BoundExpressionNode(IEnumerable<Token> Tokens, BoundNubType Type) : BoundNode(Tokens);
public record BinaryExpressionNode(IEnumerable<Token> Tokens, ExpressionNode Left, BinaryExpressionOperator Operator, ExpressionNode Right) : ExpressionNode(Tokens);
public record BoundBinaryExpressionNode(IEnumerable<Token> Tokens, BoundNubType Type, BoundExpressionNode Left, BinaryExpressionOperator Operator, BoundExpressionNode Right) : BoundExpressionNode(Tokens, Type);
public record UnaryExpressionNode(IEnumerable<Token> Tokens, UnaryExpressionOperator Operator, ExpressionNode Operand) : ExpressionNode(Tokens);
public record BoundUnaryExpressionNode(IEnumerable<Token> Tokens, BoundNubType Type, UnaryExpressionOperator Operator, BoundExpressionNode Operand) : BoundExpressionNode(Tokens, Type);
public record FuncCallNode(IEnumerable<Token> Tokens, ExpressionNode Expression, List<ExpressionNode> Parameters) : ExpressionNode(Tokens);
public record BoundFuncCallNode(IEnumerable<Token> Tokens, BoundNubType Type, BoundExpressionNode Expression, List<BoundExpressionNode> Parameters) : BoundExpressionNode(Tokens, Type);
public record IdentifierNode(IEnumerable<Token> Tokens, Optional<string> Namespace, string Name) : ExpressionNode(Tokens);
public record BoundVariableIdentNode(IEnumerable<Token> Tokens, BoundNubType Type, string Name) : BoundExpressionNode(Tokens, Type);
public record BoundLocalFuncIdentNode(IEnumerable<Token> Tokens, BoundNubType Type, string Namespace, string Name) : BoundExpressionNode(Tokens, Type);
public record BoundExternFuncIdentNode(IEnumerable<Token> Tokens, BoundNubType Type, string Namespace, string Name) : BoundExpressionNode(Tokens, Type);
public record ArrayInitializerNode(IEnumerable<Token> Tokens, ExpressionNode Capacity, NubType ElementType) : ExpressionNode(Tokens);
public record BoundArrayInitializerNode(IEnumerable<Token> Tokens, BoundNubType Type, BoundExpressionNode Capacity, BoundNubType ElementType) : BoundExpressionNode(Tokens, Type);
public record ArrayIndexAccessNode(IEnumerable<Token> Tokens, ExpressionNode Target, ExpressionNode Index) : ExpressionNode(Tokens);
public record BoundArrayIndexAccessNode(IEnumerable<Token> Tokens, BoundNubType Type, BoundExpressionNode Target, BoundExpressionNode Index) : BoundExpressionNode(Tokens, Type);
public record AnonymousFuncNode(IEnumerable<Token> Tokens, List<FuncParameterNode> Parameters, BlockNode Body, NubType ReturnType) : ExpressionNode(Tokens);
public record BoundAnonymousFuncNode(IEnumerable<Token> Tokens, BoundNubType Type, List<BoundFuncParameterNode> Parameters, BoundBlock Body, BoundNubType ReturnType) : BoundExpressionNode(Tokens, Type);
public record AddressOfNode(IEnumerable<Token> Tokens, ExpressionNode Expression) : ExpressionNode(Tokens);
public record BoundAddressOfNode(IEnumerable<Token> Tokens, BoundNubType Type, BoundExpressionNode Expression) : BoundExpressionNode(Tokens, Type);
public record LiteralNode(IEnumerable<Token> Tokens, string Literal, LiteralKind Kind) : ExpressionNode(Tokens);
public record BoundLiteralNode(IEnumerable<Token> Tokens, BoundNubType Type, string Literal, LiteralKind Kind) : BoundExpressionNode(Tokens, Type);
public record MemberAccessNode(IEnumerable<Token> Tokens, ExpressionNode Target, string Member) : ExpressionNode(Tokens);
public record BoundStructFieldAccessNode(IEnumerable<Token> Tokens, BoundNubType Type, BoundNubStructType StructType, BoundExpressionNode Target, string Field) : BoundExpressionNode(Tokens, Type);
public record BoundTraitImplFuncAccessNode(IEnumerable<Token> Tokens, BoundNubType Type, BoundExpressionNode Target, string FuncName) : BoundExpressionNode(Tokens, Type);
public record BoundTraitFuncAccessNode(IEnumerable<Token> Tokens, BoundNubType Type, BoundNubTraitType TraitType, BoundExpressionNode Target, string FuncName) : BoundExpressionNode(Tokens, Type);
public record StructInitializerNode(IEnumerable<Token> Tokens, NubType StructType, Dictionary<string, ExpressionNode> Initializers) : ExpressionNode(Tokens);
public record BoundStructInitializerNode(IEnumerable<Token> Tokens, BoundNubType Type, BoundNubStructType StructType, Dictionary<string, BoundExpressionNode> Initializers) : BoundExpressionNode(Tokens, Type);
public record DereferenceNode(IEnumerable<Token> Tokens, ExpressionNode Expression) : ExpressionNode(Tokens);
public record BoundDereferenceNode(IEnumerable<Token> Tokens, BoundNubType Type, BoundExpressionNode Expression) : BoundExpressionNode(Tokens, Type);

View File

@@ -0,0 +1,9 @@
using NubLang.Syntax.Tokenization;
namespace NubLang.Syntax.Node;
public abstract record Node(IEnumerable<Token> Tokens);
public abstract record BoundNode(IEnumerable<Token> Tokens);
public record BlockNode(IEnumerable<Token> Tokens, List<StatementNode> Statements) : Node(Tokens);
public record BoundBlock(IEnumerable<Token> Tokens, List<BoundStatementNode> Statements) : BoundNode(Tokens);

View File

@@ -0,0 +1,32 @@
using Common;
using NubLang.Syntax.Tokenization;
namespace NubLang.Syntax.Node;
public record StatementNode(IEnumerable<Token> Tokens) : Node(Tokens);
public record BoundStatementNode(IEnumerable<Token> Tokens) : BoundNode(Tokens);
public record StatementExpressionNode(IEnumerable<Token> Tokens, ExpressionNode Expression) : StatementNode(Tokens);
public record BoundStatementExpressionNode(IEnumerable<Token> Tokens, BoundExpressionNode Expression) : BoundStatementNode(Tokens);
public record ReturnNode(IEnumerable<Token> Tokens, Optional<ExpressionNode> Value) : StatementNode(Tokens);
public record BoundReturnNode(IEnumerable<Token> Tokens, Optional<BoundExpressionNode> Value) : BoundStatementNode(Tokens);
public record AssignmentNode(IEnumerable<Token> Tokens, ExpressionNode Target, ExpressionNode Value) : StatementNode(Tokens);
public record BoundAssignmentNode(IEnumerable<Token> Tokens, BoundExpressionNode Target, BoundExpressionNode Value) : BoundStatementNode(Tokens);
public record IfNode(IEnumerable<Token> Tokens, ExpressionNode Condition, BlockNode Body, Optional<Variant<IfNode, BlockNode>> Else) : StatementNode(Tokens);
public record BoundIfNode(IEnumerable<Token> Tokens, BoundExpressionNode Condition, BoundBlock Body, Optional<Variant<BoundIfNode, BoundBlock>> Else) : BoundStatementNode(Tokens);
public record VariableDeclarationNode(IEnumerable<Token> Tokens, string Name, Optional<NubType> ExplicitType, Optional<ExpressionNode> Assignment) : StatementNode(Tokens);
public record BoundVariableDeclarationNode(IEnumerable<Token> Tokens, string Name, Optional<BoundExpressionNode> Assignment, BoundNubType Type) : BoundStatementNode(Tokens);
public record ContinueNode(IEnumerable<Token> Tokens) : StatementNode(Tokens);
public record BoundContinueNode(IEnumerable<Token> Tokens) : BoundStatementNode(Tokens);
public record BreakNode(IEnumerable<Token> Tokens) : StatementNode(Tokens);
public record BoundBreakNode(IEnumerable<Token> Tokens) : BoundStatementNode(Tokens);
public record WhileNode(IEnumerable<Token> Tokens, ExpressionNode Condition, BlockNode Body) : StatementNode(Tokens);
public record BoundWhileNode(IEnumerable<Token> Tokens, BoundExpressionNode Condition, BoundBlock Body) : BoundStatementNode(Tokens);

View File

@@ -0,0 +1,6 @@
using NubLang.Diagnostics;
namespace NubLang.Syntax.Node;
public record SyntaxTree(string Namespace, IEnumerable<TopLevelNode> TopLevelNodes, IEnumerable<Diagnostic> Diagnostics);
public record BoundSyntaxTree(string Namespace, IEnumerable<BoundTopLevelNode> TopLevelNodes, IEnumerable<Diagnostic> Diagnostics);

View File

@@ -0,0 +1,909 @@
using System.Diagnostics.CodeAnalysis;
using Common;
using NubLang.Diagnostics;
using NubLang.Syntax.Node;
using NubLang.Syntax.Tokenization;
namespace NubLang.Syntax.Parsing;
public sealed class Parser
{
private string _namespace;
private readonly IEnumerable<Token> _tokens;
private readonly List<Diagnostic> _diagnostics = [];
private NubType? _functionReturnType;
private int _tokenIndex;
public Parser(IEnumerable<Token> tokens)
{
_namespace = "global";
_tokens = tokens;
}
public SyntaxTree Parse()
{
_diagnostics.Clear();
_functionReturnType = null;
_tokenIndex = 0;
if (TryExpectSymbol(Symbol.Namespace))
{
_namespace = ExpectIdentifier().Value;
}
List<TopLevelNode> topLevelNodes = [];
while (Peek().HasValue)
{
try
{
topLevelNodes.Add(ParseTopLevel());
}
catch (ParseException ex)
{
_diagnostics.Add(ex.Diagnostic);
RecoverToNextDefinition();
}
}
return new SyntaxTree(_namespace, topLevelNodes, _diagnostics);
}
private TopLevelNode ParseTopLevel()
{
var startIndex = _tokenIndex;
List<ModifierToken> modifiers = [];
while (TryExpectModifier(out var modifier))
{
modifiers.Add(modifier);
}
var keyword = ExpectSymbol();
var node = keyword.Symbol switch
{
Symbol.Func => ParseFunc(startIndex, modifiers),
Symbol.Struct => ParseStruct(startIndex),
Symbol.Trait => ParseTrait(startIndex),
Symbol.Impl => ParseImplementation(startIndex),
_ => throw new ParseException(Diagnostic
.Error($"Expected 'func' or 'struct', but found '{keyword.Symbol}'")
.WithHelp("Valid definition keywords are 'func' and 'struct'")
.At(keyword)
.Build())
};
if (modifiers.Count != 0)
{
throw new ParseException(Diagnostic
.Error($"Invalid modifiers: {string.Join(", ", modifiers.Select(x => x.Modifier))}")
.WithHelp($"Remove the following modifiers: {modifiers.Select(x => x.Modifier)}'")
.At(SourceSpan.Merge(modifiers.Select(x => x.Span)))
.Build());
}
return node;
}
private TopLevelNode ParseFunc(int startIndex, List<ModifierToken> modifiers)
{
var name = ExpectIdentifier();
List<FuncParameterNode> parameters = [];
ExpectSymbol(Symbol.OpenParen);
while (!TryExpectSymbol(Symbol.CloseParen))
{
parameters.Add(ParseFuncParameter());
if (!TryExpectSymbol(Symbol.Comma) && Peek().TryGetValue(out var token) && token is not SymbolToken { Symbol: Symbol.CloseParen })
{
_diagnostics.Add(Diagnostic
.Warning("Missing comma between function parameters")
.WithHelp("Add a ',' to separate parameters")
.At(token)
.Build());
}
}
var returnType = TryExpectSymbol(Symbol.Colon) ? ParseType() : new NubVoidType();
_functionReturnType = returnType;
var isExtern = modifiers.RemoveAll(x => x.Modifier == Modifier.Extern) > 0;
if (isExtern)
{
if (modifiers.Count != 0)
{
throw new ParseException(Diagnostic
.Error($"Invalid modifier for extern function: {modifiers[0].Modifier}")
.WithHelp($"Extern functions cannot use the '{modifiers[0].Modifier}' modifier")
.At(modifiers[0])
.Build());
}
var callName = name.Value;
if (TryExpectSymbol(Symbol.Calls))
{
callName = ExpectIdentifier().Value;
}
return new ExternFuncNode(GetTokensForNode(startIndex), _namespace, name.Value, callName, parameters, returnType);
}
var body = ParseBlock();
var exported = modifiers.RemoveAll(x => x.Modifier == Modifier.Export) > 0;
return new LocalFuncNode(GetTokensForNode(startIndex), _namespace, name.Value, parameters, body, returnType, exported);
}
private StructNode ParseStruct(int startIndex)
{
var name = ExpectIdentifier().Value;
ExpectSymbol(Symbol.OpenBrace);
List<StructFieldNode> variables = [];
var fieldIndex = 0;
while (!TryExpectSymbol(Symbol.CloseBrace))
{
var fieldStartIndex = _tokenIndex;
var variableName = ExpectIdentifier().Value;
ExpectSymbol(Symbol.Colon);
var variableType = ParseType();
var variableValue = Optional<ExpressionNode>.Empty();
if (TryExpectSymbol(Symbol.Assign))
{
variableValue = ParseExpression();
}
variables.Add(new StructFieldNode(GetTokensForNode(fieldStartIndex), fieldIndex++, variableName, variableType, variableValue));
}
return new StructNode(GetTokensForNode(startIndex), _namespace, name, variables);
}
private TraitNode ParseTrait(int startIndex)
{
var name = ExpectIdentifier().Value;
ExpectSymbol(Symbol.OpenBrace);
List<TraitFuncNode> functions = [];
while (!TryExpectSymbol(Symbol.CloseBrace))
{
var funcStartIndex = _tokenIndex;
ExpectSymbol(Symbol.Func);
var funcName = ExpectIdentifier().Value;
var parameters = new List<FuncParameterNode>();
ExpectSymbol(Symbol.OpenParen);
while (!TryExpectSymbol(Symbol.CloseParen))
{
var parameter = ParseFuncParameter();
parameters.Add(parameter);
if (!TryExpectSymbol(Symbol.Comma) && Peek().TryGetValue(out var nextToken) && nextToken is not SymbolToken { Symbol: Symbol.CloseParen })
{
_diagnostics.Add(Diagnostic
.Warning("Missing comma between function arguments")
.WithHelp("Add a ',' to separate arguments")
.At(nextToken)
.Build());
}
}
var returnType = TryExpectSymbol(Symbol.Colon) ? ParseType() : new NubVoidType();
functions.Add(new TraitFuncNode(GetTokensForNode(funcStartIndex), funcName, parameters, returnType));
}
return new TraitNode(GetTokensForNode(startIndex), _namespace, name, functions);
}
private TraitImplNode ParseImplementation(int startIndex)
{
var traitType = ParseType();
ExpectSymbol(Symbol.For);
var forType = ParseType();
List<TraitFuncImplNode> functions = [];
ExpectSymbol(Symbol.OpenBrace);
while (!TryExpectSymbol(Symbol.CloseBrace))
{
var funcStartIndex = _tokenIndex;
ExpectSymbol(Symbol.Func);
var functionName = ExpectIdentifier().Value;
var parameters = new List<FuncParameterNode>
{
new([], "this", forType)
};
ExpectSymbol(Symbol.OpenParen);
while (!TryExpectSymbol(Symbol.CloseParen))
{
parameters.Add(ParseFuncParameter());
if (!TryExpectSymbol(Symbol.Comma) && Peek().TryGetValue(out var token) && token is not SymbolToken { Symbol: Symbol.CloseParen })
{
_diagnostics.Add(Diagnostic
.Warning("Missing comma between function parameters")
.WithHelp("Add a ',' to separate parameters")
.At(token)
.Build());
}
}
var returnType = TryExpectSymbol(Symbol.Colon) ? ParseType() : new NubVoidType();
var body = ParseBlock();
functions.AddRange(new TraitFuncImplNode(GetTokensForNode(funcStartIndex), functionName, parameters, returnType, body));
}
return new TraitImplNode(GetTokensForNode(startIndex), _namespace, traitType, forType, functions);
}
private FuncParameterNode ParseFuncParameter()
{
var startIndex = _tokenIndex;
var name = ExpectIdentifier();
ExpectSymbol(Symbol.Colon);
var type = ParseType();
return new FuncParameterNode(GetTokensForNode(startIndex), name.Value, type);
}
private StatementNode ParseStatement()
{
var startIndex = _tokenIndex;
if (!Peek().TryGetValue(out var token))
{
throw new ParseException(Diagnostic
.Error("Unexpected end of file while parsing statement")
.At(_tokens.Last())
.Build());
}
if (token is SymbolToken symbol)
{
switch (symbol.Symbol)
{
case Symbol.Return:
return ParseReturn(startIndex);
case Symbol.If:
return ParseIf(startIndex);
case Symbol.While:
return ParseWhile(startIndex);
case Symbol.Let:
return ParseVariableDeclaration(startIndex);
case Symbol.Break:
return ParseBreak(startIndex);
case Symbol.Continue:
return ParseContinue(startIndex);
}
}
return ParseStatementExpression(startIndex);
}
private StatementNode ParseStatementExpression(int startIndex)
{
var expr = ParseExpression();
if (TryExpectSymbol(Symbol.Assign))
{
var value = ParseExpression();
return new AssignmentNode(GetTokensForNode(startIndex), expr, value);
}
return new StatementExpressionNode(GetTokensForNode(startIndex), expr);
}
private VariableDeclarationNode ParseVariableDeclaration(int startIndex)
{
ExpectSymbol(Symbol.Let);
var name = ExpectIdentifier().Value;
var explicitType = Optional<NubType>.Empty();
if (TryExpectSymbol(Symbol.Colon))
{
explicitType = ParseType();
}
var assignment = Optional<ExpressionNode>.Empty();
if (TryExpectSymbol(Symbol.Assign))
{
assignment = ParseExpression();
}
return new VariableDeclarationNode(GetTokensForNode(startIndex), name, explicitType, assignment);
}
private StatementNode ParseBreak(int startIndex)
{
ExpectSymbol(Symbol.Break);
Next();
return new BreakNode(GetTokensForNode(startIndex));
}
private StatementNode ParseContinue(int startIndex)
{
ExpectSymbol(Symbol.Continue);
return new ContinueNode(GetTokensForNode(startIndex));
}
private ReturnNode ParseReturn(int startIndex)
{
ExpectSymbol(Symbol.Return);
var value = Optional<ExpressionNode>.Empty();
if (_functionReturnType is not NubVoidType)
{
value = ParseExpression();
}
return new ReturnNode(GetTokensForNode(startIndex), value);
}
private IfNode ParseIf(int startIndex)
{
ExpectSymbol(Symbol.If);
var condition = ParseExpression();
var body = ParseBlock();
var elseStatement = Optional<Variant<IfNode, BlockNode>>.Empty();
if (TryExpectSymbol(Symbol.Else))
{
var newStartIndex = _tokenIndex;
elseStatement = TryExpectSymbol(Symbol.If)
? (Variant<IfNode, BlockNode>)ParseIf(newStartIndex)
: (Variant<IfNode, BlockNode>)ParseBlock();
}
return new IfNode(GetTokensForNode(startIndex), condition, body, elseStatement);
}
private WhileNode ParseWhile(int startIndex)
{
ExpectSymbol(Symbol.While);
var condition = ParseExpression();
var body = ParseBlock();
return new WhileNode(GetTokensForNode(startIndex), condition, body);
}
private ExpressionNode ParseExpression(int precedence = 0)
{
var startIndex = _tokenIndex;
var left = ParsePrimaryExpression();
while (true)
{
var token = Peek();
if (!token.HasValue || token.Value is not SymbolToken symbolToken || !TryGetBinaryOperator(symbolToken.Symbol, out var op) ||
GetBinaryOperatorPrecedence(op.Value) < precedence)
{
break;
}
Next();
var right = ParseExpression(GetBinaryOperatorPrecedence(op.Value) + 1);
left = new BinaryExpressionNode(GetTokensForNode(startIndex), left, op.Value, right);
}
return left;
}
private int GetBinaryOperatorPrecedence(BinaryExpressionOperator binaryExpressionOperator)
{
return binaryExpressionOperator switch
{
BinaryExpressionOperator.Multiply => 3,
BinaryExpressionOperator.Divide => 3,
BinaryExpressionOperator.Plus => 2,
BinaryExpressionOperator.Minus => 2,
BinaryExpressionOperator.GreaterThan => 1,
BinaryExpressionOperator.GreaterThanOrEqual => 1,
BinaryExpressionOperator.LessThan => 1,
BinaryExpressionOperator.LessThanOrEqual => 1,
BinaryExpressionOperator.Equal => 0,
BinaryExpressionOperator.NotEqual => 0,
_ => throw new ArgumentOutOfRangeException(nameof(binaryExpressionOperator), binaryExpressionOperator, null)
};
}
private bool TryGetBinaryOperator(Symbol symbol, [NotNullWhen(true)] out BinaryExpressionOperator? binaryExpressionOperator)
{
switch (symbol)
{
case Symbol.Equal:
binaryExpressionOperator = BinaryExpressionOperator.Equal;
return true;
case Symbol.NotEqual:
binaryExpressionOperator = BinaryExpressionOperator.NotEqual;
return true;
case Symbol.LessThan:
binaryExpressionOperator = BinaryExpressionOperator.LessThan;
return true;
case Symbol.LessThanOrEqual:
binaryExpressionOperator = BinaryExpressionOperator.LessThanOrEqual;
return true;
case Symbol.GreaterThan:
binaryExpressionOperator = BinaryExpressionOperator.GreaterThan;
return true;
case Symbol.GreaterThanOrEqual:
binaryExpressionOperator = BinaryExpressionOperator.GreaterThanOrEqual;
return true;
case Symbol.Plus:
binaryExpressionOperator = BinaryExpressionOperator.Plus;
return true;
case Symbol.Minus:
binaryExpressionOperator = BinaryExpressionOperator.Minus;
return true;
case Symbol.Star:
binaryExpressionOperator = BinaryExpressionOperator.Multiply;
return true;
case Symbol.ForwardSlash:
binaryExpressionOperator = BinaryExpressionOperator.Divide;
return true;
default:
binaryExpressionOperator = null;
return false;
}
}
private ExpressionNode ParsePrimaryExpression()
{
var startIndex = _tokenIndex;
ExpressionNode expr;
var token = ExpectToken();
switch (token)
{
case LiteralToken literal:
{
expr = new LiteralNode(GetTokensForNode(startIndex), literal.Value, literal.Kind);
break;
}
case IdentifierToken identifier:
{
var @namespace = Optional<string>.Empty();
var name = identifier.Value;
if (TryExpectSymbol(Symbol.DoubleColon))
{
@namespace = identifier.Value;
name = ExpectIdentifier().Value;
}
expr = new IdentifierNode(GetTokensForNode(startIndex), @namespace, name);
break;
}
case SymbolToken symbolToken:
{
switch (symbolToken.Symbol)
{
case Symbol.Func:
{
List<FuncParameterNode> parameters = [];
ExpectSymbol(Symbol.OpenParen);
while (!TryExpectSymbol(Symbol.CloseParen))
{
var parameter = ParseFuncParameter();
parameters.Add(parameter);
if (!TryExpectSymbol(Symbol.Comma) && Peek().TryGetValue(out var nextToken) && nextToken is not SymbolToken { Symbol: Symbol.CloseParen })
{
_diagnostics.Add(Diagnostic
.Warning("Missing comma between function arguments")
.WithHelp("Add a ',' to separate arguments")
.At(nextToken)
.Build());
}
}
var returnType = TryExpectSymbol(Symbol.Colon) ? ParseType() : new NubVoidType();
var body = ParseBlock();
expr = new AnonymousFuncNode(GetTokensForNode(startIndex), parameters, body, returnType);
break;
}
case Symbol.OpenParen:
{
var expression = ParseExpression();
ExpectSymbol(Symbol.CloseParen);
expr = expression;
break;
}
case Symbol.Minus:
{
var expression = ParsePrimaryExpression();
expr = new UnaryExpressionNode(GetTokensForNode(startIndex), UnaryExpressionOperator.Negate, expression);
break;
}
case Symbol.Bang:
{
var expression = ParsePrimaryExpression();
expr = new UnaryExpressionNode(GetTokensForNode(startIndex), UnaryExpressionOperator.Invert, expression);
break;
}
case Symbol.OpenBracket:
{
var capacity = ParseExpression();
ExpectSymbol(Symbol.CloseBracket);
var type = ParseType();
expr = new ArrayInitializerNode(GetTokensForNode(startIndex), capacity, type);
break;
}
case Symbol.Alloc:
{
var type = ParseType();
Dictionary<string, ExpressionNode> initializers = [];
ExpectSymbol(Symbol.OpenBrace);
while (!TryExpectSymbol(Symbol.CloseBrace))
{
var name = ExpectIdentifier().Value;
ExpectSymbol(Symbol.Assign);
var value = ParseExpression();
initializers.Add(name, value);
}
expr = new StructInitializerNode(GetTokensForNode(startIndex), type, initializers);
break;
}
default:
{
throw new ParseException(Diagnostic
.Error($"Unexpected symbol '{symbolToken.Symbol}' in expression")
.WithHelp("Expected literal, identifier, or '(' to start expression")
.At(symbolToken)
.Build());
}
}
break;
}
default:
{
throw new ParseException(Diagnostic
.Error($"Unexpected token '{token.GetType().Name}' in expression")
.WithHelp("Expected literal, identifier, or parenthesized expression")
.At(token)
.Build());
}
}
return ParsePostfixOperators(startIndex, expr);
}
private ExpressionNode ParsePostfixOperators(int startIndex, ExpressionNode expr)
{
while (true)
{
if (TryExpectSymbol(Symbol.Ampersand))
{
expr = new AddressOfNode(GetTokensForNode(startIndex), expr);
break;
}
if (TryExpectSymbol(Symbol.Caret))
{
expr = new DereferenceNode(GetTokensForNode(startIndex), expr);
continue;
}
if (TryExpectSymbol(Symbol.Period))
{
var structMember = ExpectIdentifier().Value;
expr = new MemberAccessNode(GetTokensForNode(startIndex), expr, structMember);
continue;
}
if (TryExpectSymbol(Symbol.OpenBracket))
{
var index = ParseExpression();
ExpectSymbol(Symbol.CloseBracket);
expr = new ArrayIndexAccessNode(GetTokensForNode(startIndex), expr, index);
continue;
}
if (TryExpectSymbol(Symbol.OpenParen))
{
var parameters = new List<ExpressionNode>();
while (!TryExpectSymbol(Symbol.CloseParen))
{
parameters.Add(ParseExpression());
if (!TryExpectSymbol(Symbol.Comma) && Peek().TryGetValue(out var nextToken) && nextToken is not SymbolToken { Symbol: Symbol.CloseParen })
{
_diagnostics.Add(Diagnostic
.Warning("Missing comma between function arguments")
.WithHelp("Add a ',' to separate arguments")
.At(nextToken)
.Build());
}
}
expr = new FuncCallNode(GetTokensForNode(startIndex), expr, parameters);
continue;
}
break;
}
return expr;
}
private BlockNode ParseBlock()
{
var startIndex = _tokenIndex;
ExpectSymbol(Symbol.OpenBrace);
List<StatementNode> statements = [];
while (Peek().HasValue && !TryExpectSymbol(Symbol.CloseBrace))
{
try
{
statements.Add(ParseStatement());
}
catch (ParseException ex)
{
_diagnostics.Add(ex.Diagnostic);
RecoverToNextStatement();
}
}
return new BlockNode(GetTokensForNode(startIndex), statements);
}
private NubType ParseType()
{
if (TryExpectIdentifier(out var name))
{
return name.Value switch
{
"void" => new NubVoidType(),
"string" => new NubStringType(),
"cstring" => new NubCStringType(),
"i64" => new NubPrimitiveType(PrimitiveTypeKind.I64),
"i32" => new NubPrimitiveType(PrimitiveTypeKind.I32),
"i16" => new NubPrimitiveType(PrimitiveTypeKind.I16),
"i8" => new NubPrimitiveType(PrimitiveTypeKind.I8),
"u64" => new NubPrimitiveType(PrimitiveTypeKind.U64),
"u32" => new NubPrimitiveType(PrimitiveTypeKind.U32),
"u16" => new NubPrimitiveType(PrimitiveTypeKind.U16),
"u8" => new NubPrimitiveType(PrimitiveTypeKind.U8),
"f64" => new NubPrimitiveType(PrimitiveTypeKind.F64),
"f32" => new NubPrimitiveType(PrimitiveTypeKind.F32),
"bool" => new NubPrimitiveType(PrimitiveTypeKind.Bool),
_ => ParseCustomType()
};
NubCustomType ParseCustomType()
{
var @namespace = _namespace;
if (TryExpectSymbol(Symbol.DoubleColon))
{
@namespace = ExpectIdentifier().Value;
}
return new NubCustomType(@namespace, name.Value);
}
}
if (TryExpectSymbol(Symbol.Caret))
{
var baseType = ParseType();
return new NubPointerType(baseType);
}
if (TryExpectSymbol(Symbol.Func))
{
ExpectSymbol(Symbol.OpenParen);
List<NubType> parameters = [];
while (!TryExpectSymbol(Symbol.CloseParen))
{
var parameter = ParseType();
parameters.Add(parameter);
if (!TryExpectSymbol(Symbol.Comma) && Peek().TryGetValue(out var nextToken) && nextToken is not SymbolToken { Symbol: Symbol.CloseParen })
{
_diagnostics.Add(Diagnostic
.Warning("Missing comma between func type arguments")
.WithHelp("Add a ',' to separate arguments")
.At(nextToken)
.Build());
}
}
var returnType = TryExpectSymbol(Symbol.Colon) ? ParseType() : new NubVoidType();
return new NubFuncType(returnType, parameters);
}
if (TryExpectSymbol(Symbol.OpenBracket))
{
ExpectSymbol(Symbol.CloseBracket);
var baseType = ParseType();
return new NubArrayType(baseType);
}
if (!Peek().TryGetValue(out var peekToken))
{
throw new ParseException(Diagnostic
.Error("Unexpected end of file while parsing type")
.WithHelp("Expected a type name")
.At(_tokens.Last())
.Build());
}
throw new ParseException(Diagnostic
.Error("Invalid type Syntax")
.WithHelp("Expected type name, '^' for pointer, or '[]' for array")
.At(peekToken)
.Build());
}
private Token ExpectToken()
{
if (!Peek().TryGetValue(out var token))
{
throw new ParseException(Diagnostic
.Error("Unexpected end of file")
.WithHelp("Expected more tokens to complete the Syntax")
.At(_tokens.Last())
.Build());
}
Next();
return token;
}
private SymbolToken ExpectSymbol()
{
var token = ExpectToken();
if (token is not SymbolToken symbol)
{
throw new ParseException(Diagnostic
.Error($"Expected symbol, but found {token.GetType().Name}")
.WithHelp("This position requires a symbol like '(', ')', '{', '}', etc.")
.At(token)
.Build());
}
return symbol;
}
private void ExpectSymbol(Symbol expectedSymbol)
{
var token = ExpectSymbol();
if (token.Symbol != expectedSymbol)
{
throw new ParseException(Diagnostic
.Error($"Expected '{expectedSymbol}', but found '{token.Symbol}'")
.WithHelp($"Insert '{expectedSymbol}' here")
.At(token)
.Build());
}
}
private bool TryExpectSymbol(Symbol symbol)
{
if (Peek() is { Value: SymbolToken symbolToken } && symbolToken.Symbol == symbol)
{
Next();
return true;
}
return false;
}
private bool TryExpectModifier([NotNullWhen(true)] out ModifierToken? modifier)
{
if (Peek() is { Value: ModifierToken modifierToken })
{
modifier = modifierToken;
Next();
return true;
}
modifier = null;
return false;
}
private bool TryExpectIdentifier([NotNullWhen(true)] out IdentifierToken? identifier)
{
if (Peek() is { Value: IdentifierToken identifierToken })
{
identifier = identifierToken;
Next();
return true;
}
identifier = null;
return false;
}
private IdentifierToken ExpectIdentifier()
{
var token = ExpectToken();
if (token is not IdentifierToken identifier)
{
throw new ParseException(Diagnostic
.Error($"Expected identifier, but found {token.GetType().Name}")
.WithHelp("Provide a valid identifier name here")
.At(token)
.Build());
}
return identifier;
}
private void RecoverToNextDefinition()
{
while (Peek().HasValue)
{
var token = Peek().Value;
if (token is SymbolToken { Symbol: Symbol.Func or Symbol.Struct or Symbol.Trait or Symbol.Impl } or ModifierToken)
{
break;
}
Next();
}
}
private void RecoverToNextStatement()
{
while (Peek().TryGetValue(out var token))
{
if (token is SymbolToken { Symbol: Symbol.CloseBrace } or IdentifierToken or SymbolToken
{
Symbol: Symbol.Return or Symbol.If or Symbol.While or Symbol.Let or Symbol.Break or Symbol.Continue
})
{
break;
}
Next();
}
}
private Optional<Token> Peek(int offset = 0)
{
var peekIndex = _tokenIndex + offset;
if (peekIndex < _tokens.Count())
{
return _tokens.ElementAt(peekIndex);
}
return Optional<Token>.Empty();
}
private void Next()
{
_tokenIndex++;
}
private IEnumerable<Token> GetTokensForNode(int startIndex)
{
return _tokens.Skip(startIndex).Take(Math.Min(_tokenIndex, _tokens.Count() - 1) - startIndex);
}
}
public class ParseException : Exception
{
public Diagnostic Diagnostic { get; }
public ParseException(Diagnostic diagnostic) : base(diagnostic.Message)
{
Diagnostic = diagnostic;
}
}

View File

@@ -0,0 +1,6 @@
namespace NubLang.Syntax.Tokenization;
public class IdentifierToken(SourceSpan span, string value) : Token(span)
{
public string Value { get; } = value;
}

View File

@@ -0,0 +1,15 @@
namespace NubLang.Syntax.Tokenization;
public class LiteralToken(SourceSpan span, LiteralKind kind, string value) : Token(span)
{
public LiteralKind Kind { get; } = kind;
public string Value { get; } = value;
}
public enum LiteralKind
{
Integer,
Float,
String,
Bool
}

View File

@@ -0,0 +1,12 @@
namespace NubLang.Syntax.Tokenization;
public class ModifierToken(SourceSpan span, Modifier modifier) : Token(span)
{
public Modifier Modifier { get; } = modifier;
}
public enum Modifier
{
Extern,
Export
}

View File

@@ -0,0 +1,49 @@
namespace NubLang.Syntax.Tokenization;
public class SymbolToken(SourceSpan span, Symbol symbol) : Token(span)
{
public Symbol Symbol { get; } = symbol;
}
public enum Symbol
{
Func,
Return,
If,
Else,
While,
Break,
Continue,
Colon,
OpenParen,
CloseParen,
OpenBrace,
CloseBrace,
OpenBracket,
CloseBracket,
Comma,
Period,
Assign,
Bang,
Equal,
NotEqual,
LessThan,
LessThanOrEqual,
GreaterThan,
GreaterThanOrEqual,
Plus,
Minus,
Star,
ForwardSlash,
Struct,
Caret,
Ampersand,
DoubleColon,
Namespace,
Let,
Alloc,
Calls,
Trait,
Impl,
For
}

View File

@@ -0,0 +1,6 @@
namespace NubLang.Syntax.Tokenization;
public abstract class Token(SourceSpan span)
{
public SourceSpan Span { get; } = span;
}

View File

@@ -0,0 +1,274 @@
using Common;
using NubLang.Diagnostics;
namespace NubLang.Syntax.Tokenization;
public static class Tokenizer
{
private static readonly Dictionary<string, Symbol> Keywords = new()
{
["namespace"] = Symbol.Namespace,
["func"] = Symbol.Func,
["if"] = Symbol.If,
["else"] = Symbol.Else,
["while"] = Symbol.While,
["break"] = Symbol.Break,
["continue"] = Symbol.Continue,
["return"] = Symbol.Return,
["alloc"] = Symbol.Alloc,
["struct"] = Symbol.Struct,
["let"] = Symbol.Let,
["calls"] = Symbol.Calls,
["trait"] = Symbol.Trait,
["impl"] = Symbol.Impl,
["for"] = Symbol.For,
};
private static readonly Dictionary<string, Modifier> Modifiers = new()
{
["export"] = Modifier.Export,
["extern"] = Modifier.Extern,
};
private static readonly Dictionary<char[], Symbol> Chians = new()
{
[['=', '=']] = Symbol.Equal,
[['!', '=']] = Symbol.NotEqual,
[['<', '=']] = Symbol.LessThanOrEqual,
[['>', '=']] = Symbol.GreaterThanOrEqual,
[[':', ':']] = Symbol.DoubleColon,
};
private static readonly Dictionary<char, Symbol> Chars = new()
{
[':'] = Symbol.Colon,
['('] = Symbol.OpenParen,
[')'] = Symbol.CloseParen,
['{'] = Symbol.OpenBrace,
['}'] = Symbol.CloseBrace,
['['] = Symbol.OpenBracket,
[']'] = Symbol.CloseBracket,
[','] = Symbol.Comma,
['.'] = Symbol.Period,
['='] = Symbol.Assign,
['<'] = Symbol.LessThan,
['>'] = Symbol.GreaterThan,
['+'] = Symbol.Plus,
['-'] = Symbol.Minus,
['*'] = Symbol.Star,
['/'] = Symbol.ForwardSlash,
['!'] = Symbol.Bang,
['^'] = Symbol.Caret,
['&'] = Symbol.Ampersand,
};
private static SourceText _sourceText;
private static int _index;
public static IEnumerable<Token> Tokenize(SourceText sourceText, out IEnumerable<Diagnostic> diagnostics)
{
_sourceText = sourceText;
_index = 0;
List<Token> tokens = [];
while (ParseToken().TryGetValue(out var token))
{
tokens.Add(token);
}
// TODO: Implement diagnostics
diagnostics = [];
return tokens;
}
private static Optional<Token> ParseToken()
{
var startIndex = _index;
if (!Peek().TryGetValue(out var current))
{
return Optional<Token>.Empty();
}
if (Peek().TryGetValue(out var character) && char.IsWhiteSpace(character))
{
Next();
return ParseToken();
}
if (current == '/' && Peek(1).TryGetValue(out var nextChar) && nextChar == '/')
{
Next();
Next();
while (Peek().TryGetValue(out var ch) && ch != '\n')
{
Next();
}
return ParseToken();
}
if (char.IsLetter(current) || current == '_')
{
var buffer = string.Empty;
while (Peek().TryGetValue(out var next) && (char.IsLetterOrDigit(next) || next == '_'))
{
buffer += next;
Next();
}
if (Keywords.TryGetValue(buffer, out var keywordSymbol))
{
return new SymbolToken(CreateSpan(startIndex), keywordSymbol);
}
if (Modifiers.TryGetValue(buffer, out var modifer))
{
return new ModifierToken(CreateSpan(startIndex), modifer);
}
if (buffer is "true" or "false")
{
return new LiteralToken(CreateSpan(startIndex), LiteralKind.Bool, buffer);
}
return new IdentifierToken(CreateSpan(startIndex), buffer);
}
if (char.IsDigit(current))
{
var isFloat = false;
var buffer = string.Empty;
while (Peek().TryGetValue(out var next))
{
if (next == '.')
{
if (isFloat)
{
throw new Exception("More than one period found in float literal");
}
isFloat = true;
buffer += next;
Next();
}
else if (char.IsDigit(next))
{
buffer += next;
Next();
}
else
{
break;
}
}
return new LiteralToken(CreateSpan(startIndex), isFloat ? LiteralKind.Float : LiteralKind.Integer, buffer);
}
// TODO: Revisit this
foreach (var chain in Chians)
{
if (current != chain.Key[0]) continue;
for (var i = 1; i < chain.Key.Length; i++)
{
var c = Peek(i);
if (!c.HasValue || c.Value != chain.Key[i]) break;
if (i == chain.Key.Length - 1)
{
for (var j = 0; j <= i; j++)
{
Next();
}
return new SymbolToken(CreateSpan(startIndex), chain.Value);
}
}
}
if (Chars.TryGetValue(current, out var charSymbol))
{
Next();
return new SymbolToken(CreateSpan(startIndex), charSymbol);
}
if (current == '"')
{
Next();
var buffer = string.Empty;
while (true)
{
if (!Peek().TryGetValue(out var next))
{
throw new Exception("Unclosed string literal");
}
if (next == '"')
{
Next();
break;
}
buffer += next;
Next();
}
return new LiteralToken(CreateSpan(startIndex), LiteralKind.String, buffer);
}
throw new Exception($"Unknown character {current}");
}
private static SourceLocation CreateLocation(int index)
{
var line = 1;
var column = 1;
for (var i = 0; i < Math.Min(index, _sourceText.Content.Length - 1); i++)
{
if (_sourceText.Content[i] == '\n')
{
column = 1;
line += 1;
}
else
{
column += 1;
}
}
return new SourceLocation(line, column);
}
private static SourceSpan CreateSpan(int startIndex)
{
return new SourceSpan(_sourceText, CreateLocation(startIndex), CreateLocation(_index));
}
private static Optional<char> Peek(int offset = 0)
{
if (_index + offset < _sourceText.Content.Length)
{
return _sourceText.Content[_index + offset];
}
return Optional<char>.Empty();
}
private static Optional<char> Next()
{
if (_index < _sourceText.Content.Length)
{
return _sourceText.Content[_index++];
}
_index++;
return Optional<char>.Empty();
}
}