...
This commit is contained in:
9
src/Nub.Lang.Syntax/Parsing/CompilationUnit.cs
Normal file
9
src/Nub.Lang.Syntax/Parsing/CompilationUnit.cs
Normal file
@@ -0,0 +1,9 @@
|
||||
using Nub.Lang.Syntax.Parsing.Definitions;
|
||||
|
||||
namespace Nub.Lang.Syntax.Parsing;
|
||||
|
||||
public class CompilationUnit(string @namespace, List<DefinitionNode> definitions)
|
||||
{
|
||||
public string Namespace { get; } = @namespace;
|
||||
public List<DefinitionNode> Definitions { get; } = definitions;
|
||||
}
|
||||
10
src/Nub.Lang.Syntax/Parsing/Definitions/DefinitionNode.cs
Normal file
10
src/Nub.Lang.Syntax/Parsing/Definitions/DefinitionNode.cs
Normal file
@@ -0,0 +1,10 @@
|
||||
using Nub.Lang.Common;
|
||||
using Nub.Lang.Syntax.Tokenization;
|
||||
|
||||
namespace Nub.Lang.Syntax.Parsing.Definitions;
|
||||
|
||||
public abstract class DefinitionNode(IReadOnlyList<Token> tokens, Optional<string> documentation, string @namespace) : Node(tokens)
|
||||
{
|
||||
public Optional<string> Documentation { get; } = documentation;
|
||||
public string Namespace { get; set; } = @namespace;
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
using Nub.Lang.Common;
|
||||
using Nub.Lang.Syntax.Parsing.Statements;
|
||||
using Nub.Lang.Syntax.Tokenization;
|
||||
using Nub.Lang.Syntax.Typing;
|
||||
|
||||
namespace Nub.Lang.Syntax.Parsing.Definitions;
|
||||
|
||||
public class FuncParameter(string name, NubType type)
|
||||
{
|
||||
public string Name { get; } = name;
|
||||
public NubType Type { get; } = type;
|
||||
|
||||
public override string ToString() => $"{Name}: {Type}";
|
||||
}
|
||||
|
||||
public interface IFuncSignature
|
||||
{
|
||||
public string Name { get; }
|
||||
public List<FuncParameter> Parameters { get; }
|
||||
public NubType ReturnType { get; }
|
||||
|
||||
public string ToString() => $"{Name}({string.Join(", ", Parameters.Select(p => p.ToString()))}){": " + ReturnType}";
|
||||
}
|
||||
|
||||
public class LocalFuncDefinitionNode(IReadOnlyList<Token> tokens, Optional<string> documentation, string @namespace, string name, List<FuncParameter> parameters, BlockNode body, NubType returnType, bool exported) : DefinitionNode(tokens, documentation, @namespace), IFuncSignature
|
||||
{
|
||||
public string Name { get; } = name;
|
||||
public List<FuncParameter> Parameters { get; } = parameters;
|
||||
public BlockNode Body { get; } = body;
|
||||
public NubType ReturnType { get; } = returnType;
|
||||
public bool Exported { get; } = exported;
|
||||
|
||||
public override string ToString() => $"{Name}({string.Join(", ", Parameters.Select(p => p.ToString()))}){": " + ReturnType}";
|
||||
}
|
||||
|
||||
public class ExternFuncDefinitionNode(IReadOnlyList<Token> tokens, Optional<string> documentation, string @namespace, string name, string callName, List<FuncParameter> parameters, NubType returnType) : DefinitionNode(tokens, documentation, @namespace), IFuncSignature
|
||||
{
|
||||
public string Name { get; } = name;
|
||||
public string CallName { get; } = callName;
|
||||
public List<FuncParameter> Parameters { get; } = parameters;
|
||||
public NubType ReturnType { get; } = returnType;
|
||||
|
||||
public override string ToString() => $"{Name}({string.Join(", ", Parameters.Select(p => p.ToString()))}){": " + ReturnType}";
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
using Nub.Lang.Common;
|
||||
using Nub.Lang.Syntax.Parsing.Expressions;
|
||||
using Nub.Lang.Syntax.Tokenization;
|
||||
using Nub.Lang.Syntax.Typing;
|
||||
|
||||
namespace Nub.Lang.Syntax.Parsing.Definitions;
|
||||
|
||||
public class StructField(string name, NubType type, Optional<ExpressionNode> value)
|
||||
{
|
||||
public string Name { get; } = name;
|
||||
public NubType Type { get; } = type;
|
||||
public Optional<ExpressionNode> Value { get; } = value;
|
||||
}
|
||||
|
||||
public class StructDefinitionNode(IReadOnlyList<Token> tokens, Optional<string> documentation, string @namespace, string name, List<StructField> fields) : DefinitionNode(tokens, documentation, @namespace)
|
||||
{
|
||||
public string Name { get; } = name;
|
||||
public List<StructField> Fields { get; } = fields;
|
||||
}
|
||||
8
src/Nub.Lang.Syntax/Parsing/Expressions/AddressOfNode.cs
Normal file
8
src/Nub.Lang.Syntax/Parsing/Expressions/AddressOfNode.cs
Normal file
@@ -0,0 +1,8 @@
|
||||
using Nub.Lang.Syntax.Tokenization;
|
||||
|
||||
namespace Nub.Lang.Syntax.Parsing.Expressions;
|
||||
|
||||
public class AddressOfNode(IReadOnlyList<Token> tokens, LValueNode expression) : ExpressionNode(tokens)
|
||||
{
|
||||
public LValueNode Expression { get; } = expression;
|
||||
}
|
||||
13
src/Nub.Lang.Syntax/Parsing/Expressions/AnonymousFuncNode.cs
Normal file
13
src/Nub.Lang.Syntax/Parsing/Expressions/AnonymousFuncNode.cs
Normal file
@@ -0,0 +1,13 @@
|
||||
using Nub.Lang.Syntax.Parsing.Definitions;
|
||||
using Nub.Lang.Syntax.Parsing.Statements;
|
||||
using Nub.Lang.Syntax.Tokenization;
|
||||
using Nub.Lang.Syntax.Typing;
|
||||
|
||||
namespace Nub.Lang.Syntax.Parsing.Expressions;
|
||||
|
||||
public class AnonymousFuncNode(IReadOnlyList<Token> tokens, List<FuncParameter> parameters, BlockNode body, NubType returnType) : ExpressionNode(tokens)
|
||||
{
|
||||
public List<FuncParameter> Parameters { get; } = parameters;
|
||||
public BlockNode Body { get; } = body;
|
||||
public NubType ReturnType { get; } = returnType;
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
using Nub.Lang.Syntax.Tokenization;
|
||||
|
||||
namespace Nub.Lang.Syntax.Parsing.Expressions;
|
||||
|
||||
public class ArrayIndexAccessNode(IReadOnlyList<Token> tokens, ExpressionNode array, ExpressionNode index) : LValueNode(tokens)
|
||||
{
|
||||
public ExpressionNode Array { get; } = array;
|
||||
public ExpressionNode Index { get; } = index;
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
using Nub.Lang.Syntax.Tokenization;
|
||||
using Nub.Lang.Syntax.Typing;
|
||||
|
||||
namespace Nub.Lang.Syntax.Parsing.Expressions;
|
||||
|
||||
public class ArrayInitializerNode(IReadOnlyList<Token> tokens, ExpressionNode capacity, NubType elementType) : ExpressionNode(tokens)
|
||||
{
|
||||
public ExpressionNode Capacity { get; } = capacity;
|
||||
public NubType ElementType { get; } = elementType;
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
using Nub.Lang.Syntax.Tokenization;
|
||||
|
||||
namespace Nub.Lang.Syntax.Parsing.Expressions;
|
||||
|
||||
public class BinaryExpressionNode(IReadOnlyList<Token> tokens, ExpressionNode left, BinaryExpressionOperator @operator, ExpressionNode right) : ExpressionNode(tokens)
|
||||
{
|
||||
public ExpressionNode Left { get; } = left;
|
||||
public BinaryExpressionOperator Operator { get; } = @operator;
|
||||
public ExpressionNode Right { get; } = right;
|
||||
}
|
||||
|
||||
public enum BinaryExpressionOperator
|
||||
{
|
||||
Equal,
|
||||
NotEqual,
|
||||
GreaterThan,
|
||||
GreaterThanOrEqual,
|
||||
LessThan,
|
||||
LessThanOrEqual,
|
||||
Plus,
|
||||
Minus,
|
||||
Multiply,
|
||||
Divide
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
using Nub.Lang.Syntax.Tokenization;
|
||||
|
||||
namespace Nub.Lang.Syntax.Parsing.Expressions;
|
||||
|
||||
public class DereferenceNode(IReadOnlyList<Token> tokens, ExpressionNode expression) : LValueNode(tokens)
|
||||
{
|
||||
public ExpressionNode Expression { get; } = expression;
|
||||
}
|
||||
16
src/Nub.Lang.Syntax/Parsing/Expressions/ExpressionNode.cs
Normal file
16
src/Nub.Lang.Syntax/Parsing/Expressions/ExpressionNode.cs
Normal file
@@ -0,0 +1,16 @@
|
||||
using Nub.Lang.Syntax.Tokenization;
|
||||
using Nub.Lang.Syntax.Typing;
|
||||
|
||||
namespace Nub.Lang.Syntax.Parsing.Expressions;
|
||||
|
||||
public abstract class ExpressionNode(IReadOnlyList<Token> tokens) : Node(tokens)
|
||||
{
|
||||
private NubType? _type;
|
||||
public NubType Type
|
||||
{
|
||||
get => _type ?? throw new Exception("Tried to access expression type before type was populated");
|
||||
set => _type = value;
|
||||
}
|
||||
}
|
||||
|
||||
public abstract class LValueNode(IReadOnlyList<Token> tokens) : ExpressionNode(tokens);
|
||||
@@ -0,0 +1,10 @@
|
||||
using Nub.Lang.Syntax.Tokenization;
|
||||
using Nub.Lang.Syntax.Typing;
|
||||
|
||||
namespace Nub.Lang.Syntax.Parsing.Expressions;
|
||||
|
||||
public class FixedArrayInitializerNode(IReadOnlyList<Token> tokens, NubType elementType, int capacity) : ExpressionNode(tokens)
|
||||
{
|
||||
public NubType ElementType { get; } = elementType;
|
||||
public int Capacity { get; } = capacity;
|
||||
}
|
||||
11
src/Nub.Lang.Syntax/Parsing/Expressions/FuncCallNode.cs
Normal file
11
src/Nub.Lang.Syntax/Parsing/Expressions/FuncCallNode.cs
Normal file
@@ -0,0 +1,11 @@
|
||||
using Nub.Lang.Syntax.Tokenization;
|
||||
|
||||
namespace Nub.Lang.Syntax.Parsing.Expressions;
|
||||
|
||||
public class FuncCallNode(IReadOnlyList<Token> tokens, ExpressionNode expression, List<ExpressionNode> parameters) : ExpressionNode(tokens)
|
||||
{
|
||||
public ExpressionNode Expression = expression;
|
||||
public List<ExpressionNode> Parameters { get; } = parameters;
|
||||
|
||||
public override string ToString() => $"{Expression}({string.Join(", ", Parameters)})";
|
||||
}
|
||||
12
src/Nub.Lang.Syntax/Parsing/Expressions/IdentifierNode.cs
Normal file
12
src/Nub.Lang.Syntax/Parsing/Expressions/IdentifierNode.cs
Normal file
@@ -0,0 +1,12 @@
|
||||
using Nub.Lang.Common;
|
||||
using Nub.Lang.Syntax.Tokenization;
|
||||
|
||||
namespace Nub.Lang.Syntax.Parsing.Expressions;
|
||||
|
||||
public class IdentifierNode(IReadOnlyList<Token> tokens, Optional<string> @namespace, string name) : LValueNode(tokens)
|
||||
{
|
||||
public Optional<string> Namespace { get; } = @namespace;
|
||||
public string Name { get; } = name;
|
||||
|
||||
public override string ToString() => Namespace.HasValue ? $"{Namespace.Value}::{Name}" : Name;
|
||||
}
|
||||
9
src/Nub.Lang.Syntax/Parsing/Expressions/LiteralNode.cs
Normal file
9
src/Nub.Lang.Syntax/Parsing/Expressions/LiteralNode.cs
Normal file
@@ -0,0 +1,9 @@
|
||||
using Nub.Lang.Syntax.Tokenization;
|
||||
|
||||
namespace Nub.Lang.Syntax.Parsing.Expressions;
|
||||
|
||||
public class LiteralNode(IReadOnlyList<Token> tokens, string literal, LiteralKind kind) : ExpressionNode(tokens)
|
||||
{
|
||||
public string Literal { get; } = literal;
|
||||
public LiteralKind Kind { get; } = kind;
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
using Nub.Lang.Syntax.Tokenization;
|
||||
|
||||
namespace Nub.Lang.Syntax.Parsing.Expressions;
|
||||
|
||||
public class MemberAccessNode(IReadOnlyList<Token> tokens, ExpressionNode expression, string member) : LValueNode(tokens)
|
||||
{
|
||||
public ExpressionNode Expression { get; } = expression;
|
||||
public string Member { get; } = member;
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
using Nub.Lang.Syntax.Tokenization;
|
||||
using Nub.Lang.Syntax.Typing;
|
||||
|
||||
namespace Nub.Lang.Syntax.Parsing.Expressions;
|
||||
|
||||
public class StructInitializerNode(IReadOnlyList<Token> tokens, NubStructType structType, Dictionary<string, ExpressionNode> initializers) : ExpressionNode(tokens)
|
||||
{
|
||||
public NubStructType StructType { get; } = structType;
|
||||
public Dictionary<string, ExpressionNode> Initializers { get; } = initializers;
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
using Nub.Lang.Syntax.Tokenization;
|
||||
|
||||
namespace Nub.Lang.Syntax.Parsing.Expressions;
|
||||
|
||||
public class UnaryExpressionNode(IReadOnlyList<Token> tokens, UnaryExpressionOperator @operator, ExpressionNode operand) : ExpressionNode(tokens)
|
||||
{
|
||||
public UnaryExpressionOperator Operator { get; } = @operator;
|
||||
public ExpressionNode Operand { get; } = operand;
|
||||
}
|
||||
|
||||
public enum UnaryExpressionOperator
|
||||
{
|
||||
Negate,
|
||||
Invert
|
||||
}
|
||||
8
src/Nub.Lang.Syntax/Parsing/Node.cs
Normal file
8
src/Nub.Lang.Syntax/Parsing/Node.cs
Normal file
@@ -0,0 +1,8 @@
|
||||
using Nub.Lang.Syntax.Tokenization;
|
||||
|
||||
namespace Nub.Lang.Syntax.Parsing;
|
||||
|
||||
public abstract class Node(IReadOnlyList<Token> tokens)
|
||||
{
|
||||
public IReadOnlyList<Token> Tokens { get; set; } = tokens;
|
||||
}
|
||||
937
src/Nub.Lang.Syntax/Parsing/Parser.cs
Normal file
937
src/Nub.Lang.Syntax/Parsing/Parser.cs
Normal file
@@ -0,0 +1,937 @@
|
||||
using System.Diagnostics;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using Nub.Lang.Common;
|
||||
using Nub.Lang.Syntax.Diagnostics;
|
||||
using Nub.Lang.Syntax.Parsing.Definitions;
|
||||
using Nub.Lang.Syntax.Parsing.Expressions;
|
||||
using Nub.Lang.Syntax.Parsing.Statements;
|
||||
using Nub.Lang.Syntax.Tokenization;
|
||||
using Nub.Lang.Syntax.Typing;
|
||||
|
||||
namespace Nub.Lang.Syntax.Parsing;
|
||||
|
||||
public static class Parser
|
||||
{
|
||||
private static string _namespace = null!;
|
||||
private static List<Diagnostic> _diagnostics = [];
|
||||
private static List<Token> _tokens = [];
|
||||
private static int _index;
|
||||
|
||||
public static DiagnosticsResult<CompilationUnit?> ParseFile(List<Token> tokens)
|
||||
{
|
||||
_tokens = tokens;
|
||||
_namespace = null!;
|
||||
_diagnostics = [];
|
||||
_index = 0;
|
||||
|
||||
try
|
||||
{
|
||||
ExpectSymbol(Symbol.Namespace);
|
||||
var @namespace = ExpectIdentifier();
|
||||
_namespace = @namespace.Value;
|
||||
}
|
||||
catch (ParseException ex)
|
||||
{
|
||||
_diagnostics.Add(ex.Diagnostic);
|
||||
return new DiagnosticsResult<CompilationUnit?>(_diagnostics, null);
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
List<DefinitionNode> definitions = [];
|
||||
|
||||
while (Peek().HasValue)
|
||||
{
|
||||
var definition = ParseDefinition();
|
||||
definitions.Add(definition);
|
||||
}
|
||||
|
||||
return new DiagnosticsResult<CompilationUnit?>(_diagnostics, new CompilationUnit(_namespace, definitions));
|
||||
}
|
||||
catch (ParseException ex)
|
||||
{
|
||||
_diagnostics.Add(ex.Diagnostic);
|
||||
RecoverToNextDefinition();
|
||||
}
|
||||
|
||||
return new DiagnosticsResult<CompilationUnit?>(_diagnostics, null);
|
||||
}
|
||||
|
||||
private static DefinitionNode ParseDefinition()
|
||||
{
|
||||
var startIndex = _index;
|
||||
List<ModifierToken> modifiers = [];
|
||||
|
||||
List<string> documentationParts = [];
|
||||
while (_index < _tokens.Count && _tokens[_index] is DocumentationToken commentToken)
|
||||
{
|
||||
documentationParts.Add(commentToken.Documentation);
|
||||
_index++;
|
||||
}
|
||||
|
||||
var documentation = documentationParts.Count == 0 ? null : string.Join('\n', documentationParts);
|
||||
|
||||
while (TryExpectModifier(out var modifier))
|
||||
{
|
||||
modifiers.Add(modifier);
|
||||
}
|
||||
|
||||
var keyword = ExpectSymbol();
|
||||
return keyword.Symbol switch
|
||||
{
|
||||
Symbol.Func => ParseFuncDefinition(startIndex, modifiers, Optional.OfNullable(documentation)),
|
||||
Symbol.Struct => ParseStruct(startIndex, modifiers, Optional.OfNullable(documentation)),
|
||||
_ => throw new ParseException(Diagnostic
|
||||
.Error($"Expected 'func' or 'struct', but found '{keyword.Symbol}'")
|
||||
.WithHelp("Valid definition keywords are 'func' and 'struct'")
|
||||
.At(keyword)
|
||||
.Build())
|
||||
};
|
||||
}
|
||||
|
||||
private static DefinitionNode ParseFuncDefinition(int startIndex, List<ModifierToken> modifiers, Optional<string> documentation)
|
||||
{
|
||||
var name = ExpectIdentifier();
|
||||
List<FuncParameter> parameters = [];
|
||||
|
||||
ExpectSymbol(Symbol.OpenParen);
|
||||
|
||||
while (!TryExpectSymbol(Symbol.CloseParen))
|
||||
{
|
||||
parameters.Add(ParseFuncParameter());
|
||||
|
||||
if (!TryExpectSymbol(Symbol.Comma) && Peek().TryGetValue(out var token) && token is not SymbolToken { Symbol: Symbol.CloseParen })
|
||||
{
|
||||
_diagnostics.Add(Diagnostic
|
||||
.Warning("Missing comma between function parameters")
|
||||
.WithHelp("Add a ',' to separate parameters")
|
||||
.At(token)
|
||||
.Build());
|
||||
}
|
||||
}
|
||||
|
||||
var returnType = TryExpectSymbol(Symbol.Colon) ? ParseType() : new NubVoidType();
|
||||
|
||||
var isExtern = modifiers.RemoveAll(x => x.Modifier == Modifier.Extern) > 0;
|
||||
if (isExtern)
|
||||
{
|
||||
if (modifiers.Count != 0)
|
||||
{
|
||||
throw new ParseException(Diagnostic
|
||||
.Error($"Invalid modifier for extern function: {modifiers[0].Modifier}")
|
||||
.WithHelp($"Extern functions cannot use the '{modifiers[0].Modifier}' modifier")
|
||||
.At(modifiers[0])
|
||||
.Build());
|
||||
}
|
||||
|
||||
var callName = name.Value;
|
||||
|
||||
if (TryExpectSymbol(Symbol.Calls))
|
||||
{
|
||||
callName = ExpectIdentifier().Value;
|
||||
}
|
||||
|
||||
return new ExternFuncDefinitionNode(GetTokensForNode(startIndex), documentation, _namespace, name.Value, callName, parameters, returnType);
|
||||
}
|
||||
|
||||
var body = ParseBlock();
|
||||
var exported = modifiers.RemoveAll(x => x.Modifier == Modifier.Export) > 0;
|
||||
|
||||
if (modifiers.Count != 0)
|
||||
{
|
||||
throw new ParseException(Diagnostic
|
||||
.Error($"Invalid modifiers for function: {modifiers[0].Modifier}")
|
||||
.WithHelp($"Functions cannot use the '{modifiers[0].Modifier}' modifier")
|
||||
.At(modifiers[0])
|
||||
.Build());
|
||||
}
|
||||
|
||||
return new LocalFuncDefinitionNode(GetTokensForNode(startIndex), documentation, _namespace, name.Value, parameters, body, returnType, exported);
|
||||
}
|
||||
|
||||
private static StructDefinitionNode ParseStruct(int startIndex, List<ModifierToken> _, Optional<string> documentation)
|
||||
{
|
||||
var name = ExpectIdentifier().Value;
|
||||
|
||||
ExpectSymbol(Symbol.OpenBrace);
|
||||
|
||||
List<StructField> variables = [];
|
||||
|
||||
while (!TryExpectSymbol(Symbol.CloseBrace))
|
||||
{
|
||||
var variableName = ExpectIdentifier().Value;
|
||||
ExpectSymbol(Symbol.Colon);
|
||||
var variableType = ParseType();
|
||||
|
||||
var variableValue = Optional<ExpressionNode>.Empty();
|
||||
|
||||
if (TryExpectSymbol(Symbol.Assign))
|
||||
{
|
||||
variableValue = ParseExpression();
|
||||
}
|
||||
|
||||
variables.Add(new StructField(variableName, variableType, variableValue));
|
||||
}
|
||||
|
||||
return new StructDefinitionNode(GetTokensForNode(startIndex), documentation, _namespace, name, variables);
|
||||
}
|
||||
|
||||
private static FuncParameter ParseFuncParameter()
|
||||
{
|
||||
var name = ExpectIdentifier();
|
||||
ExpectSymbol(Symbol.Colon);
|
||||
var type = ParseType();
|
||||
|
||||
return new FuncParameter(name.Value, type);
|
||||
}
|
||||
|
||||
private static StatementNode ParseStatement()
|
||||
{
|
||||
var startIndex = _index;
|
||||
if (!Peek().TryGetValue(out var token))
|
||||
{
|
||||
throw new ParseException(Diagnostic
|
||||
.Error("Unexpected end of file while parsing statement")
|
||||
.At(_tokens.Last())
|
||||
.Build());
|
||||
}
|
||||
|
||||
if (token is SymbolToken symbol)
|
||||
{
|
||||
switch (symbol.Symbol)
|
||||
{
|
||||
case Symbol.Return:
|
||||
return ParseReturn(startIndex);
|
||||
case Symbol.If:
|
||||
return ParseIf(startIndex);
|
||||
case Symbol.While:
|
||||
return ParseWhile(startIndex);
|
||||
case Symbol.Let:
|
||||
return ParseVariableDeclaration(startIndex);
|
||||
case Symbol.Break:
|
||||
return ParseBreak(startIndex);
|
||||
case Symbol.Continue:
|
||||
return ParseContinue(startIndex);
|
||||
}
|
||||
}
|
||||
|
||||
return ParseStatementExpression(startIndex);
|
||||
}
|
||||
|
||||
private static StatementNode ParseStatementExpression(int startIndex)
|
||||
{
|
||||
var expr = ParseExpression();
|
||||
|
||||
if (Peek().TryGetValue(out var token))
|
||||
{
|
||||
if (token is SymbolToken symbol)
|
||||
{
|
||||
switch (symbol.Symbol)
|
||||
{
|
||||
case Symbol.Assign:
|
||||
{
|
||||
switch (expr)
|
||||
{
|
||||
case MemberAccessNode memberAccess:
|
||||
{
|
||||
Next();
|
||||
var value = ParseExpression();
|
||||
return new MemberAssignmentNode(GetTokensForNode(startIndex), memberAccess, value);
|
||||
}
|
||||
case ArrayIndexAccessNode arrayIndexAccess:
|
||||
{
|
||||
Next();
|
||||
var value = ParseExpression();
|
||||
return new ArrayIndexAssignmentNode(GetTokensForNode(startIndex), arrayIndexAccess, value);
|
||||
}
|
||||
case IdentifierNode identifier:
|
||||
{
|
||||
Next();
|
||||
var value = ParseExpression();
|
||||
return new VariableAssignmentNode(GetTokensForNode(startIndex), identifier, value);
|
||||
}
|
||||
case DereferenceNode dereference:
|
||||
{
|
||||
Next();
|
||||
var value = ParseExpression();
|
||||
return new DereferenceAssignmentNode(GetTokensForNode(startIndex), dereference, value);
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return new StatementExpressionNode(GetTokensForNode(startIndex), expr);
|
||||
}
|
||||
|
||||
private static VariableDeclarationNode ParseVariableDeclaration(int startIndex)
|
||||
{
|
||||
ExpectSymbol(Symbol.Let);
|
||||
var name = ExpectIdentifier().Value;
|
||||
var type = Optional<NubType>.Empty();
|
||||
if (TryExpectSymbol(Symbol.Colon))
|
||||
{
|
||||
type = ParseType();
|
||||
}
|
||||
|
||||
var value = Optional<ExpressionNode>.Empty();
|
||||
if (TryExpectSymbol(Symbol.Assign))
|
||||
{
|
||||
value = ParseExpression();
|
||||
}
|
||||
|
||||
return new VariableDeclarationNode(GetTokensForNode(startIndex), name, type, value);
|
||||
}
|
||||
|
||||
private static StatementNode ParseBreak(int startIndex)
|
||||
{
|
||||
ExpectSymbol(Symbol.Break);
|
||||
Next();
|
||||
return new BreakNode(GetTokensForNode(startIndex));
|
||||
}
|
||||
|
||||
private static StatementNode ParseContinue(int startIndex)
|
||||
{
|
||||
ExpectSymbol(Symbol.Continue);
|
||||
return new ContinueNode(GetTokensForNode(startIndex));
|
||||
}
|
||||
|
||||
private static ReturnNode ParseReturn(int startIndex)
|
||||
{
|
||||
ExpectSymbol(Symbol.Return);
|
||||
var value = Optional<ExpressionNode>.Empty();
|
||||
if (!TryExpectSymbol(Symbol.Semicolon))
|
||||
{
|
||||
value = ParseExpression();
|
||||
}
|
||||
|
||||
return new ReturnNode(GetTokensForNode(startIndex), value);
|
||||
}
|
||||
|
||||
private static IfNode ParseIf(int startIndex)
|
||||
{
|
||||
ExpectSymbol(Symbol.If);
|
||||
var condition = ParseExpression();
|
||||
var body = ParseBlock();
|
||||
|
||||
var elseStatement = Optional<Variant<IfNode, BlockNode>>.Empty();
|
||||
if (TryExpectSymbol(Symbol.Else))
|
||||
{
|
||||
var newStartIndex = _index;
|
||||
elseStatement = TryExpectSymbol(Symbol.If)
|
||||
? (Variant<IfNode, BlockNode>)ParseIf(newStartIndex)
|
||||
: (Variant<IfNode, BlockNode>)ParseBlock();
|
||||
}
|
||||
|
||||
return new IfNode(GetTokensForNode(startIndex), condition, body, elseStatement);
|
||||
}
|
||||
|
||||
private static WhileNode ParseWhile(int startIndex)
|
||||
{
|
||||
ExpectSymbol(Symbol.While);
|
||||
var condition = ParseExpression();
|
||||
var body = ParseBlock();
|
||||
return new WhileNode(GetTokensForNode(startIndex), condition, body);
|
||||
}
|
||||
|
||||
private static ExpressionNode ParseExpression(int precedence = 0)
|
||||
{
|
||||
var startIndex = _index;
|
||||
var left = ParsePrimaryExpression();
|
||||
|
||||
while (true)
|
||||
{
|
||||
var token = Peek();
|
||||
if (!token.HasValue || token.Value is not SymbolToken symbolToken || !TryGetBinaryOperator(symbolToken.Symbol, out var op) ||
|
||||
GetBinaryOperatorPrecedence(op.Value) < precedence)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
Next();
|
||||
var right = ParseExpression(GetBinaryOperatorPrecedence(op.Value) + 1);
|
||||
|
||||
left = new BinaryExpressionNode(GetTokensForNode(startIndex), left, op.Value, right);
|
||||
}
|
||||
|
||||
return left;
|
||||
}
|
||||
|
||||
private static int GetBinaryOperatorPrecedence(BinaryExpressionOperator binaryExpressionOperator)
|
||||
{
|
||||
return binaryExpressionOperator switch
|
||||
{
|
||||
BinaryExpressionOperator.Multiply => 3,
|
||||
BinaryExpressionOperator.Divide => 3,
|
||||
BinaryExpressionOperator.Plus => 2,
|
||||
BinaryExpressionOperator.Minus => 2,
|
||||
BinaryExpressionOperator.GreaterThan => 1,
|
||||
BinaryExpressionOperator.GreaterThanOrEqual => 1,
|
||||
BinaryExpressionOperator.LessThan => 1,
|
||||
BinaryExpressionOperator.LessThanOrEqual => 1,
|
||||
BinaryExpressionOperator.Equal => 0,
|
||||
BinaryExpressionOperator.NotEqual => 0,
|
||||
_ => throw new ArgumentOutOfRangeException(nameof(binaryExpressionOperator), binaryExpressionOperator, null)
|
||||
};
|
||||
}
|
||||
|
||||
private static bool TryGetBinaryOperator(Symbol symbol, [NotNullWhen(true)] out BinaryExpressionOperator? binaryExpressionOperator)
|
||||
{
|
||||
switch (symbol)
|
||||
{
|
||||
case Symbol.Equal:
|
||||
binaryExpressionOperator = BinaryExpressionOperator.Equal;
|
||||
return true;
|
||||
case Symbol.NotEqual:
|
||||
binaryExpressionOperator = BinaryExpressionOperator.NotEqual;
|
||||
return true;
|
||||
case Symbol.LessThan:
|
||||
binaryExpressionOperator = BinaryExpressionOperator.LessThan;
|
||||
return true;
|
||||
case Symbol.LessThanOrEqual:
|
||||
binaryExpressionOperator = BinaryExpressionOperator.LessThanOrEqual;
|
||||
return true;
|
||||
case Symbol.GreaterThan:
|
||||
binaryExpressionOperator = BinaryExpressionOperator.GreaterThan;
|
||||
return true;
|
||||
case Symbol.GreaterThanOrEqual:
|
||||
binaryExpressionOperator = BinaryExpressionOperator.GreaterThanOrEqual;
|
||||
return true;
|
||||
case Symbol.Plus:
|
||||
binaryExpressionOperator = BinaryExpressionOperator.Plus;
|
||||
return true;
|
||||
case Symbol.Minus:
|
||||
binaryExpressionOperator = BinaryExpressionOperator.Minus;
|
||||
return true;
|
||||
case Symbol.Star:
|
||||
binaryExpressionOperator = BinaryExpressionOperator.Multiply;
|
||||
return true;
|
||||
case Symbol.ForwardSlash:
|
||||
binaryExpressionOperator = BinaryExpressionOperator.Divide;
|
||||
return true;
|
||||
default:
|
||||
binaryExpressionOperator = null;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private static ExpressionNode ParsePrimaryExpression()
|
||||
{
|
||||
var startIndex = _index;
|
||||
ExpressionNode expr;
|
||||
|
||||
var token = ExpectToken();
|
||||
switch (token)
|
||||
{
|
||||
case LiteralToken literal:
|
||||
{
|
||||
expr = new LiteralNode(GetTokensForNode(startIndex), literal.Value, literal.Kind);
|
||||
break;
|
||||
}
|
||||
case IdentifierToken identifier:
|
||||
{
|
||||
var @namespace = Optional<string>.Empty();
|
||||
var name = identifier.Value;
|
||||
if (TryExpectSymbol(Symbol.DoubleColon))
|
||||
{
|
||||
@namespace = identifier.Value;
|
||||
name = ExpectIdentifier().Value;
|
||||
}
|
||||
|
||||
expr = new IdentifierNode(GetTokensForNode(startIndex), @namespace, name);
|
||||
break;
|
||||
}
|
||||
case SymbolToken symbolToken:
|
||||
{
|
||||
switch (symbolToken.Symbol)
|
||||
{
|
||||
case Symbol.Func:
|
||||
{
|
||||
List<FuncParameter> parameters = [];
|
||||
ExpectSymbol(Symbol.OpenParen);
|
||||
while (!TryExpectSymbol(Symbol.CloseParen))
|
||||
{
|
||||
var parameter = ParseFuncParameter();
|
||||
parameters.Add(parameter);
|
||||
if (!TryExpectSymbol(Symbol.Comma) && Peek().TryGetValue(out var nextToken) && nextToken is not SymbolToken { Symbol: Symbol.CloseParen })
|
||||
{
|
||||
_diagnostics.Add(Diagnostic
|
||||
.Warning("Missing comma between function arguments")
|
||||
.WithHelp("Add a ',' to separate arguments")
|
||||
.At(nextToken)
|
||||
.Build());
|
||||
}
|
||||
}
|
||||
|
||||
var returnType = TryExpectSymbol(Symbol.Colon) ? ParseType() : new NubVoidType();
|
||||
|
||||
var body = ParseBlock();
|
||||
|
||||
expr = new AnonymousFuncNode(GetTokensForNode(startIndex), parameters, body, returnType);
|
||||
break;
|
||||
}
|
||||
case Symbol.OpenParen:
|
||||
{
|
||||
var expression = ParseExpression();
|
||||
ExpectSymbol(Symbol.CloseParen);
|
||||
expr = expression;
|
||||
break;
|
||||
}
|
||||
case Symbol.Ampersand:
|
||||
{
|
||||
var expression = ParsePrimaryExpression();
|
||||
if (expression is not LValueNode lValue)
|
||||
{
|
||||
throw new ParseException(Diagnostic
|
||||
.Error("& symbol can only be used on lvalues")
|
||||
.At(expression)
|
||||
.Build());
|
||||
}
|
||||
|
||||
expr = new AddressOfNode(GetTokensForNode(startIndex), lValue);
|
||||
break;
|
||||
}
|
||||
case Symbol.Minus:
|
||||
{
|
||||
var expression = ParsePrimaryExpression();
|
||||
expr = new UnaryExpressionNode(GetTokensForNode(startIndex), UnaryExpressionOperator.Negate, expression);
|
||||
break;
|
||||
}
|
||||
case Symbol.Bang:
|
||||
{
|
||||
var expression = ParsePrimaryExpression();
|
||||
expr = new UnaryExpressionNode(GetTokensForNode(startIndex), UnaryExpressionOperator.Invert, expression);
|
||||
break;
|
||||
}
|
||||
case Symbol.OpenBracket:
|
||||
{
|
||||
if (Peek().TryGetValue(out var capacityToken) && capacityToken is LiteralToken { Kind: LiteralKind.Integer } literalToken)
|
||||
{
|
||||
var capacity = int.Parse(literalToken.Value);
|
||||
Next();
|
||||
ExpectSymbol(Symbol.CloseBracket);
|
||||
var elementType = ParseType();
|
||||
|
||||
if (capacity > 0)
|
||||
{
|
||||
expr = new FixedArrayInitializerNode(GetTokensForNode(startIndex), elementType, capacity);
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new ParseException(Diagnostic
|
||||
.Error("Fixed array size must be a positive integer")
|
||||
.WithHelp("Use a positive integer literal for the array size")
|
||||
.At(literalToken)
|
||||
.Build());
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
var capacity = ParseExpression();
|
||||
ExpectSymbol(Symbol.CloseBracket);
|
||||
var type = ParseType();
|
||||
|
||||
expr = new ArrayInitializerNode(GetTokensForNode(startIndex), capacity, type);
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
case Symbol.Alloc:
|
||||
{
|
||||
var type = ParseType();
|
||||
Dictionary<string, ExpressionNode> initializers = [];
|
||||
ExpectSymbol(Symbol.OpenBrace);
|
||||
while (!TryExpectSymbol(Symbol.CloseBrace))
|
||||
{
|
||||
var name = ExpectIdentifier().Value;
|
||||
ExpectSymbol(Symbol.Assign);
|
||||
var value = ParseExpression();
|
||||
initializers.Add(name, value);
|
||||
}
|
||||
|
||||
if (type is not NubStructType structType)
|
||||
{
|
||||
throw new ParseException(Diagnostic
|
||||
.Error($"Cannot alloc type '{type}'")
|
||||
.At(symbolToken)
|
||||
.Build());
|
||||
}
|
||||
|
||||
expr = new StructInitializerNode(GetTokensForNode(startIndex), structType, initializers);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
{
|
||||
throw new ParseException(Diagnostic
|
||||
.Error($"Unexpected symbol '{symbolToken.Symbol}' in expression")
|
||||
.WithHelp("Expected literal, identifier, or '(' to start expression")
|
||||
.At(symbolToken)
|
||||
.Build());
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
default:
|
||||
{
|
||||
throw new ParseException(Diagnostic
|
||||
.Error($"Unexpected token '{token.GetType().Name}' in expression")
|
||||
.WithHelp("Expected literal, identifier, or parenthesized expression")
|
||||
.At(token)
|
||||
.Build());
|
||||
}
|
||||
}
|
||||
|
||||
return ParsePostfixOperators(startIndex, expr);
|
||||
}
|
||||
|
||||
private static ExpressionNode ParsePostfixOperators(int startIndex, ExpressionNode expr)
|
||||
{
|
||||
while (true)
|
||||
{
|
||||
if (TryExpectSymbol(Symbol.Caret))
|
||||
{
|
||||
expr = new DereferenceNode(GetTokensForNode(startIndex), expr);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (TryExpectSymbol(Symbol.Period))
|
||||
{
|
||||
var structMember = ExpectIdentifier().Value;
|
||||
expr = new MemberAccessNode(GetTokensForNode(startIndex), expr, structMember);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (TryExpectSymbol(Symbol.OpenBracket))
|
||||
{
|
||||
var index = ParseExpression();
|
||||
ExpectSymbol(Symbol.CloseBracket);
|
||||
expr = new ArrayIndexAccessNode(GetTokensForNode(startIndex), expr, index);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (TryExpectSymbol(Symbol.OpenParen))
|
||||
{
|
||||
var parameters = new List<ExpressionNode>();
|
||||
while (!TryExpectSymbol(Symbol.CloseParen))
|
||||
{
|
||||
parameters.Add(ParseExpression());
|
||||
if (!TryExpectSymbol(Symbol.Comma) && Peek().TryGetValue(out var nextToken) && nextToken is not SymbolToken { Symbol: Symbol.CloseParen })
|
||||
{
|
||||
_diagnostics.Add(Diagnostic
|
||||
.Warning("Missing comma between function arguments")
|
||||
.WithHelp("Add a ',' to separate arguments")
|
||||
.At(nextToken)
|
||||
.Build());
|
||||
}
|
||||
}
|
||||
|
||||
expr = new FuncCallNode(GetTokensForNode(startIndex), expr, parameters);
|
||||
continue;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
return expr;
|
||||
}
|
||||
|
||||
private static BlockNode ParseBlock()
|
||||
{
|
||||
var startIndex = _index;
|
||||
ExpectSymbol(Symbol.OpenBrace);
|
||||
List<StatementNode> statements = [];
|
||||
while (Peek().HasValue && !TryExpectSymbol(Symbol.CloseBrace))
|
||||
{
|
||||
try
|
||||
{
|
||||
statements.Add(ParseStatement());
|
||||
}
|
||||
catch (ParseException ex)
|
||||
{
|
||||
_diagnostics.Add(ex.Diagnostic);
|
||||
RecoverToNextStatement();
|
||||
}
|
||||
}
|
||||
|
||||
return new BlockNode(GetTokensForNode(startIndex), statements);
|
||||
}
|
||||
|
||||
private static NubType ParseType()
|
||||
{
|
||||
if (TryExpectIdentifier(out var name))
|
||||
{
|
||||
if (name.Value == "any")
|
||||
{
|
||||
return new NubAnyType();
|
||||
}
|
||||
|
||||
if (name.Value == "void")
|
||||
{
|
||||
return new NubVoidType();
|
||||
}
|
||||
|
||||
if (NubPrimitiveType.TryParse(name.Value, out var primitiveTypeKind))
|
||||
{
|
||||
return new NubPrimitiveType(primitiveTypeKind.Value);
|
||||
}
|
||||
|
||||
var @namespace = _namespace;
|
||||
if (TryExpectSymbol(Symbol.DoubleColon))
|
||||
{
|
||||
@namespace = ExpectIdentifier().Value;
|
||||
}
|
||||
|
||||
if (@namespace == null)
|
||||
{
|
||||
throw new ParseException(Diagnostic
|
||||
.Error($"Struct '{name.Value}' does not belong to a namespace")
|
||||
.WithHelp("Make sure you have specified a namespace at the top of the file")
|
||||
.At(name)
|
||||
.Build());
|
||||
}
|
||||
|
||||
return new NubStructType(@namespace , name.Value);
|
||||
}
|
||||
|
||||
if (TryExpectSymbol(Symbol.Caret))
|
||||
{
|
||||
var baseType = ParseType();
|
||||
return new NubPointerType(baseType);
|
||||
}
|
||||
|
||||
if (TryExpectSymbol(Symbol.Func))
|
||||
{
|
||||
ExpectSymbol(Symbol.OpenParen);
|
||||
List<NubType> parameters = [];
|
||||
while (!TryExpectSymbol(Symbol.CloseParen))
|
||||
{
|
||||
var parameter = ParseType();
|
||||
parameters.Add(parameter);
|
||||
if (!TryExpectSymbol(Symbol.Comma) && Peek().TryGetValue(out var nextToken) && nextToken is not SymbolToken { Symbol: Symbol.CloseParen })
|
||||
{
|
||||
_diagnostics.Add(Diagnostic
|
||||
.Warning("Missing comma between func type arguments")
|
||||
.WithHelp("Add a ',' to separate arguments")
|
||||
.At(nextToken)
|
||||
.Build());
|
||||
}
|
||||
}
|
||||
|
||||
var returnType = TryExpectSymbol(Symbol.Colon) ? ParseType() : new NubVoidType();
|
||||
|
||||
return new NubFuncType(returnType, parameters);
|
||||
}
|
||||
|
||||
if (TryExpectSymbol(Symbol.OpenBracket))
|
||||
{
|
||||
if (Peek().TryGetValue(out var token) && token is LiteralToken { Kind: LiteralKind.Integer, Value: var sizeValue })
|
||||
{
|
||||
Next();
|
||||
ExpectSymbol(Symbol.CloseBracket);
|
||||
var baseType = ParseType();
|
||||
|
||||
var size = int.Parse(sizeValue);
|
||||
|
||||
if (size > 0)
|
||||
{
|
||||
return new NubFixedArrayType(baseType, size);
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new UnreachableException();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
ExpectSymbol(Symbol.CloseBracket);
|
||||
var baseType = ParseType();
|
||||
return new NubArrayType(baseType);
|
||||
}
|
||||
}
|
||||
|
||||
if (!Peek().TryGetValue(out var peekToken))
|
||||
{
|
||||
throw new ParseException(Diagnostic
|
||||
.Error("Unexpected end of file while parsing type")
|
||||
.WithHelp("Expected a type name")
|
||||
.At(_tokens.Last())
|
||||
.Build());
|
||||
}
|
||||
|
||||
throw new ParseException(Diagnostic
|
||||
.Error("Invalid type syntax")
|
||||
.WithHelp("Expected type name, '^' for pointer, or '[]' for array")
|
||||
.At(peekToken)
|
||||
.Build());
|
||||
}
|
||||
|
||||
private static Token ExpectToken()
|
||||
{
|
||||
if (!Peek().TryGetValue(out var token))
|
||||
{
|
||||
throw new ParseException(Diagnostic
|
||||
.Error("Unexpected end of file")
|
||||
.WithHelp("Expected more tokens to complete the syntax")
|
||||
.At(_tokens.Last())
|
||||
.Build());
|
||||
}
|
||||
|
||||
Next();
|
||||
return token;
|
||||
}
|
||||
|
||||
private static SymbolToken ExpectSymbol()
|
||||
{
|
||||
var token = ExpectToken();
|
||||
if (token is not SymbolToken symbol)
|
||||
{
|
||||
throw new ParseException(Diagnostic
|
||||
.Error($"Expected symbol, but found {token.GetType().Name}")
|
||||
.WithHelp("This position requires a symbol like '(', ')', '{', '}', etc.")
|
||||
.At(token)
|
||||
.Build());
|
||||
}
|
||||
|
||||
return symbol;
|
||||
}
|
||||
|
||||
private static void ExpectSymbol(Symbol expectedSymbol)
|
||||
{
|
||||
var token = ExpectSymbol();
|
||||
if (token.Symbol != expectedSymbol)
|
||||
{
|
||||
throw new ParseException(Diagnostic
|
||||
.Error($"Expected '{expectedSymbol}', but found '{token.Symbol}'")
|
||||
.WithHelp($"Insert '{expectedSymbol}' here")
|
||||
.At(token)
|
||||
.Build());
|
||||
}
|
||||
}
|
||||
|
||||
private static bool TryExpectSymbol(Symbol symbol)
|
||||
{
|
||||
if (Peek() is { Value: SymbolToken symbolToken } && symbolToken.Symbol == symbol)
|
||||
{
|
||||
Next();
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private static bool TryExpectModifier([NotNullWhen(true)] out ModifierToken? modifier)
|
||||
{
|
||||
if (Peek() is { Value: ModifierToken modifierToken })
|
||||
{
|
||||
modifier = modifierToken;
|
||||
Next();
|
||||
return true;
|
||||
}
|
||||
|
||||
modifier = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
private static bool TryExpectIdentifier([NotNullWhen(true)] out IdentifierToken? identifier)
|
||||
{
|
||||
if (Peek() is { Value: IdentifierToken identifierToken })
|
||||
{
|
||||
identifier = identifierToken;
|
||||
Next();
|
||||
return true;
|
||||
}
|
||||
|
||||
identifier = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
private static IdentifierToken ExpectIdentifier()
|
||||
{
|
||||
var token = ExpectToken();
|
||||
if (token is not IdentifierToken identifier)
|
||||
{
|
||||
throw new ParseException(Diagnostic
|
||||
.Error($"Expected identifier, but found {token.GetType().Name}")
|
||||
.WithHelp("Provide a valid identifier name here")
|
||||
.At(token)
|
||||
.Build());
|
||||
}
|
||||
|
||||
return identifier;
|
||||
}
|
||||
|
||||
private static void RecoverToNextDefinition()
|
||||
{
|
||||
while (Peek().HasValue)
|
||||
{
|
||||
var token = Peek().Value;
|
||||
if (token is SymbolToken { Symbol: Symbol.Func or Symbol.Struct } or ModifierToken)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
Next();
|
||||
}
|
||||
}
|
||||
|
||||
private static void RecoverToNextStatement()
|
||||
{
|
||||
while (Peek().TryGetValue(out var token))
|
||||
{
|
||||
if (token is SymbolToken { Symbol: Symbol.CloseBrace } or IdentifierToken or SymbolToken
|
||||
{
|
||||
Symbol: Symbol.Return or Symbol.If or Symbol.While or Symbol.Let or Symbol.Break or Symbol.Continue
|
||||
})
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
Next();
|
||||
}
|
||||
}
|
||||
|
||||
private static Optional<Token> Peek(int offset = 0)
|
||||
{
|
||||
var peekIndex = _index + offset;
|
||||
while (peekIndex < _tokens.Count && _tokens[peekIndex] is DocumentationToken)
|
||||
{
|
||||
peekIndex++;
|
||||
}
|
||||
|
||||
if (peekIndex < _tokens.Count)
|
||||
{
|
||||
return _tokens[peekIndex];
|
||||
}
|
||||
|
||||
return Optional<Token>.Empty();
|
||||
}
|
||||
|
||||
private static void Next()
|
||||
{
|
||||
while (_index < _tokens.Count && _tokens[_index] is DocumentationToken)
|
||||
{
|
||||
_index++;
|
||||
}
|
||||
|
||||
_index++;
|
||||
}
|
||||
|
||||
private static IReadOnlyList<Token> GetTokensForNode(int startIndex)
|
||||
{
|
||||
return _tokens[startIndex..Math.Min(_index, _tokens.Count - 1)];
|
||||
}
|
||||
}
|
||||
|
||||
public class ParseException : Exception
|
||||
{
|
||||
public Diagnostic Diagnostic { get; }
|
||||
|
||||
public ParseException(Diagnostic diagnostic) : base(diagnostic.Message)
|
||||
{
|
||||
Diagnostic = diagnostic;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
using Nub.Lang.Syntax.Parsing.Expressions;
|
||||
using Nub.Lang.Syntax.Tokenization;
|
||||
|
||||
namespace Nub.Lang.Syntax.Parsing.Statements;
|
||||
|
||||
public class ArrayIndexAssignmentNode(IReadOnlyList<Token> tokens, ArrayIndexAccessNode arrayIndexAccess, ExpressionNode value) : StatementNode(tokens)
|
||||
{
|
||||
public ArrayIndexAccessNode ArrayIndexAccess { get; } = arrayIndexAccess;
|
||||
public ExpressionNode Value { get; } = value;
|
||||
}
|
||||
8
src/Nub.Lang.Syntax/Parsing/Statements/BlockNode.cs
Normal file
8
src/Nub.Lang.Syntax/Parsing/Statements/BlockNode.cs
Normal file
@@ -0,0 +1,8 @@
|
||||
using Nub.Lang.Syntax.Tokenization;
|
||||
|
||||
namespace Nub.Lang.Syntax.Parsing.Statements;
|
||||
|
||||
public class BlockNode(IReadOnlyList<Token> tokens, List<StatementNode> statements) : Node(tokens)
|
||||
{
|
||||
public List<StatementNode> Statements { get; } = statements;
|
||||
}
|
||||
5
src/Nub.Lang.Syntax/Parsing/Statements/BreakNode.cs
Normal file
5
src/Nub.Lang.Syntax/Parsing/Statements/BreakNode.cs
Normal file
@@ -0,0 +1,5 @@
|
||||
using Nub.Lang.Syntax.Tokenization;
|
||||
|
||||
namespace Nub.Lang.Syntax.Parsing.Statements;
|
||||
|
||||
public class BreakNode(IReadOnlyList<Token> tokens) : StatementNode(tokens);
|
||||
5
src/Nub.Lang.Syntax/Parsing/Statements/ContinueNode.cs
Normal file
5
src/Nub.Lang.Syntax/Parsing/Statements/ContinueNode.cs
Normal file
@@ -0,0 +1,5 @@
|
||||
using Nub.Lang.Syntax.Tokenization;
|
||||
|
||||
namespace Nub.Lang.Syntax.Parsing.Statements;
|
||||
|
||||
public class ContinueNode(IReadOnlyList<Token> tokens) : StatementNode(tokens);
|
||||
@@ -0,0 +1,10 @@
|
||||
using Nub.Lang.Syntax.Parsing.Expressions;
|
||||
using Nub.Lang.Syntax.Tokenization;
|
||||
|
||||
namespace Nub.Lang.Syntax.Parsing.Statements;
|
||||
|
||||
public class DereferenceAssignmentNode(IReadOnlyList<Token> tokens, DereferenceNode dereference, ExpressionNode value) : StatementNode(tokens)
|
||||
{
|
||||
public DereferenceNode Dereference { get; } = dereference;
|
||||
public ExpressionNode Value { get; } = value;
|
||||
}
|
||||
12
src/Nub.Lang.Syntax/Parsing/Statements/IfNode.cs
Normal file
12
src/Nub.Lang.Syntax/Parsing/Statements/IfNode.cs
Normal file
@@ -0,0 +1,12 @@
|
||||
using Nub.Lang.Common;
|
||||
using Nub.Lang.Syntax.Parsing.Expressions;
|
||||
using Nub.Lang.Syntax.Tokenization;
|
||||
|
||||
namespace Nub.Lang.Syntax.Parsing.Statements;
|
||||
|
||||
public class IfNode(IReadOnlyList<Token> tokens, ExpressionNode condition, BlockNode body, Optional<Variant<IfNode, BlockNode>> @else) : StatementNode(tokens)
|
||||
{
|
||||
public ExpressionNode Condition { get; } = condition;
|
||||
public BlockNode Body { get; } = body;
|
||||
public Optional<Variant<IfNode, BlockNode>> Else { get; } = @else;
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
using Nub.Lang.Syntax.Parsing.Expressions;
|
||||
using Nub.Lang.Syntax.Tokenization;
|
||||
|
||||
namespace Nub.Lang.Syntax.Parsing.Statements;
|
||||
|
||||
public class MemberAssignmentNode(IReadOnlyList<Token> tokens, MemberAccessNode expression, ExpressionNode value) : StatementNode(tokens)
|
||||
{
|
||||
public MemberAccessNode MemberAccess { get; } = expression;
|
||||
public ExpressionNode Value { get; } = value;
|
||||
}
|
||||
10
src/Nub.Lang.Syntax/Parsing/Statements/ReturnNode.cs
Normal file
10
src/Nub.Lang.Syntax/Parsing/Statements/ReturnNode.cs
Normal file
@@ -0,0 +1,10 @@
|
||||
using Nub.Lang.Common;
|
||||
using Nub.Lang.Syntax.Parsing.Expressions;
|
||||
using Nub.Lang.Syntax.Tokenization;
|
||||
|
||||
namespace Nub.Lang.Syntax.Parsing.Statements;
|
||||
|
||||
public class ReturnNode(IReadOnlyList<Token> tokens, Optional<ExpressionNode> value) : StatementNode(tokens)
|
||||
{
|
||||
public Optional<ExpressionNode> Value { get; } = value;
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
using Nub.Lang.Syntax.Parsing.Expressions;
|
||||
using Nub.Lang.Syntax.Tokenization;
|
||||
|
||||
namespace Nub.Lang.Syntax.Parsing.Statements;
|
||||
|
||||
public class StatementExpressionNode(IReadOnlyList<Token> tokens, ExpressionNode expression) : StatementNode(tokens)
|
||||
{
|
||||
public ExpressionNode Expression { get; } = expression;
|
||||
}
|
||||
5
src/Nub.Lang.Syntax/Parsing/Statements/StatementNode.cs
Normal file
5
src/Nub.Lang.Syntax/Parsing/Statements/StatementNode.cs
Normal file
@@ -0,0 +1,5 @@
|
||||
using Nub.Lang.Syntax.Tokenization;
|
||||
|
||||
namespace Nub.Lang.Syntax.Parsing.Statements;
|
||||
|
||||
public abstract class StatementNode(IReadOnlyList<Token> tokens) : Node(tokens);
|
||||
@@ -0,0 +1,10 @@
|
||||
using Nub.Lang.Syntax.Parsing.Expressions;
|
||||
using Nub.Lang.Syntax.Tokenization;
|
||||
|
||||
namespace Nub.Lang.Syntax.Parsing.Statements;
|
||||
|
||||
public class VariableAssignmentNode(IReadOnlyList<Token> tokens, IdentifierNode identifier, ExpressionNode value) : StatementNode(tokens)
|
||||
{
|
||||
public IdentifierNode Identifier { get; } = identifier;
|
||||
public ExpressionNode Value { get; } = value;
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
using Nub.Lang.Common;
|
||||
using Nub.Lang.Syntax.Parsing.Expressions;
|
||||
using Nub.Lang.Syntax.Tokenization;
|
||||
using Nub.Lang.Syntax.Typing;
|
||||
|
||||
namespace Nub.Lang.Syntax.Parsing.Statements;
|
||||
|
||||
public class VariableDeclarationNode(IReadOnlyList<Token> tokens, string name, Optional<NubType> explicitType, Optional<ExpressionNode> value) : StatementNode(tokens)
|
||||
{
|
||||
public string Name { get; } = name;
|
||||
public Optional<NubType> ExplicitType { get; } = explicitType;
|
||||
public Optional<ExpressionNode> Value { get; } = value;
|
||||
}
|
||||
10
src/Nub.Lang.Syntax/Parsing/Statements/WhileNode.cs
Normal file
10
src/Nub.Lang.Syntax/Parsing/Statements/WhileNode.cs
Normal file
@@ -0,0 +1,10 @@
|
||||
using Nub.Lang.Syntax.Parsing.Expressions;
|
||||
using Nub.Lang.Syntax.Tokenization;
|
||||
|
||||
namespace Nub.Lang.Syntax.Parsing.Statements;
|
||||
|
||||
public class WhileNode(IReadOnlyList<Token> tokens, ExpressionNode condition, BlockNode body) : StatementNode(tokens)
|
||||
{
|
||||
public ExpressionNode Condition { get; } = condition;
|
||||
public BlockNode Body { get; } = body;
|
||||
}
|
||||
Reference in New Issue
Block a user