...
This commit is contained in:
134
src/compiler/NubLang/DefinitionTable.cs
Normal file
134
src/compiler/NubLang/DefinitionTable.cs
Normal file
@@ -0,0 +1,134 @@
|
||||
using NubLang.Syntax.Node;
|
||||
|
||||
namespace NubLang;
|
||||
|
||||
public sealed class DefinitionTable
|
||||
{
|
||||
private readonly List<TopLevelNode> _topLevelNodes;
|
||||
|
||||
public DefinitionTable(IEnumerable<SyntaxTree> syntaxTrees)
|
||||
{
|
||||
_topLevelNodes = syntaxTrees.SelectMany(x => x.TopLevelNodes).ToList();
|
||||
}
|
||||
|
||||
public IEnumerable<LocalFuncNode> LookupLocalFunc(string @namespace, string name)
|
||||
{
|
||||
return _topLevelNodes
|
||||
.OfType<LocalFuncNode>()
|
||||
.Where(x => x.Namespace == @namespace && x.Name == name);
|
||||
}
|
||||
|
||||
public IEnumerable<ExternFuncNode> LookupExternFunc(string @namespace, string name)
|
||||
{
|
||||
return _topLevelNodes
|
||||
.OfType<ExternFuncNode>()
|
||||
.Where(x => x.Namespace == @namespace && x.Name == name);
|
||||
}
|
||||
|
||||
public IEnumerable<StructNode> LookupStruct(string @namespace, string name)
|
||||
{
|
||||
return _topLevelNodes
|
||||
.OfType<StructNode>()
|
||||
.Where(x => x.Namespace == @namespace && x.Name == name);
|
||||
}
|
||||
|
||||
public IEnumerable<StructFieldNode> LookupStructField(StructNode structNode, string field)
|
||||
{
|
||||
return structNode.Fields.Where(x => x.Name == field);
|
||||
}
|
||||
|
||||
public IEnumerable<TraitFuncImplNode> LookupTraitFuncImpl(NubType forType, string name)
|
||||
{
|
||||
return _topLevelNodes
|
||||
.OfType<TraitImplNode>()
|
||||
.Where(x => x.ForType == forType)
|
||||
.SelectMany(x => x.Functions)
|
||||
.Where(x => x.Name == name);
|
||||
}
|
||||
|
||||
public IEnumerable<TraitNode> LookupTrait(string @namespace, string name)
|
||||
{
|
||||
return _topLevelNodes
|
||||
.OfType<TraitNode>()
|
||||
.Where(x => x.Namespace == @namespace && x.Name == name);
|
||||
}
|
||||
|
||||
public IEnumerable<TraitFuncNode> LookupTraitFunc(TraitNode trait, string name)
|
||||
{
|
||||
return trait.Functions.Where(x => x.Name == name);
|
||||
}
|
||||
}
|
||||
|
||||
public sealed class BoundDefinitionTable
|
||||
{
|
||||
private readonly List<BoundTopLevelNode> _topLevelNodes;
|
||||
|
||||
public BoundDefinitionTable(IEnumerable<BoundSyntaxTree> syntaxTrees)
|
||||
{
|
||||
_topLevelNodes = syntaxTrees.SelectMany(x => x.TopLevelNodes).ToList();
|
||||
}
|
||||
|
||||
public BoundLocalFuncNode LookupLocalFunc(string @namespace, string name)
|
||||
{
|
||||
return _topLevelNodes
|
||||
.OfType<BoundLocalFuncNode>()
|
||||
.First(x => x.Namespace == @namespace && x.Name == name);
|
||||
}
|
||||
|
||||
public BoundExternFuncNode LookupExternFunc(string @namespace, string name)
|
||||
{
|
||||
return _topLevelNodes
|
||||
.OfType<BoundExternFuncNode>()
|
||||
.First(x => x.Namespace == @namespace && x.Name == name);
|
||||
}
|
||||
|
||||
public BoundStructNode LookupStruct(string @namespace, string name)
|
||||
{
|
||||
return _topLevelNodes
|
||||
.OfType<BoundStructNode>()
|
||||
.First(x => x.Namespace == @namespace && x.Name == name);
|
||||
}
|
||||
|
||||
public BoundStructFieldNode LookupStructField(BoundStructNode structNode, string field)
|
||||
{
|
||||
return structNode.Fields.First(x => x.Name == field);
|
||||
}
|
||||
|
||||
public IEnumerable<BoundTraitImplNode> LookupTraitImpls(BoundNubType itemType)
|
||||
{
|
||||
return _topLevelNodes
|
||||
.OfType<BoundTraitImplNode>()
|
||||
.Where(x => x.ForType == itemType);
|
||||
}
|
||||
|
||||
public BoundTraitFuncImplNode LookupTraitFuncImpl(BoundNubType forType, string name)
|
||||
{
|
||||
return _topLevelNodes
|
||||
.OfType<BoundTraitImplNode>()
|
||||
.Where(x => x.ForType == forType)
|
||||
.SelectMany(x => x.Functions)
|
||||
.First(x => x.Name == name);
|
||||
}
|
||||
|
||||
public BoundTraitNode LookupTrait(string @namespace, string name)
|
||||
{
|
||||
return _topLevelNodes
|
||||
.OfType<BoundTraitNode>()
|
||||
.First(x => x.Namespace == @namespace && x.Name == name);
|
||||
}
|
||||
|
||||
public BoundTraitFuncNode LookupTraitFunc(BoundTraitNode trait, string name)
|
||||
{
|
||||
return trait.Functions.First(x => x.Name == name);
|
||||
}
|
||||
|
||||
public IEnumerable<BoundStructNode> GetStructs()
|
||||
{
|
||||
return _topLevelNodes.OfType<BoundStructNode>();
|
||||
}
|
||||
|
||||
public IEnumerable<BoundTraitNode> GetTraits()
|
||||
{
|
||||
return _topLevelNodes.OfType<BoundTraitNode>();
|
||||
}
|
||||
}
|
||||
174
src/compiler/NubLang/Diagnostics/ConsoleColors.cs
Normal file
174
src/compiler/NubLang/Diagnostics/ConsoleColors.cs
Normal file
@@ -0,0 +1,174 @@
|
||||
using System.Text;
|
||||
using NubLang.Syntax.Tokenization;
|
||||
|
||||
namespace NubLang.Diagnostics;
|
||||
|
||||
public static class ConsoleColors
|
||||
{
|
||||
public const string Reset = "\e[0m";
|
||||
public const string Bold = "\e[1m";
|
||||
public const string Faint = "\e[2m";
|
||||
public const string Italic = "\e[3m";
|
||||
public const string Underline = "\e[4m";
|
||||
public const string SlowBlink = "\e[5m";
|
||||
public const string RapidBlink = "\e[6m";
|
||||
public const string SwapBgAndFg = "\e[7m";
|
||||
public const string Conceal = "\e[8m";
|
||||
public const string CrossedOut = "\e[9m";
|
||||
|
||||
public const string DefaultFont = "\e[10m";
|
||||
public const string AltFont1 = "\e[11m";
|
||||
public const string AltFont2 = "\e[12m";
|
||||
public const string AltFont3 = "\e[13m";
|
||||
public const string AltFont4 = "\e[14m";
|
||||
public const string AltFont5 = "\e[15m";
|
||||
public const string AltFont6 = "\e[16m";
|
||||
public const string AltFont7 = "\e[17m";
|
||||
public const string AltFont8 = "\e[18m";
|
||||
public const string AltFont9 = "\e[19m";
|
||||
|
||||
public const string Black = "\e[30m";
|
||||
public const string Red = "\e[31m";
|
||||
public const string Green = "\e[32m";
|
||||
public const string Yellow = "\e[33m";
|
||||
public const string Blue = "\e[34m";
|
||||
public const string Magenta = "\e[35m";
|
||||
public const string Cyan = "\e[36m";
|
||||
public const string White = "\e[37m";
|
||||
|
||||
public const string BrightBlack = "\e[90m";
|
||||
public const string BrightRed = "\e[91m";
|
||||
public const string BrightGreen = "\e[92m";
|
||||
public const string BrightYellow = "\e[93m";
|
||||
public const string BrightBlue = "\e[94m";
|
||||
public const string BrightMagenta = "\e[95m";
|
||||
public const string BrightCyan = "\e[96m";
|
||||
public const string BrightWhite = "\e[97m";
|
||||
|
||||
private static bool IsColorSupported()
|
||||
{
|
||||
var term = Environment.GetEnvironmentVariable("TERM");
|
||||
var colorTerm = Environment.GetEnvironmentVariable("COLORTERM");
|
||||
return !string.IsNullOrEmpty(term) || !string.IsNullOrEmpty(colorTerm) || !Console.IsOutputRedirected;
|
||||
}
|
||||
|
||||
public static string Colorize(string text, string color)
|
||||
{
|
||||
return IsColorSupported() ? $"{color}{text}{Reset}" : text;
|
||||
}
|
||||
|
||||
private static string GetTokenColor(Token token)
|
||||
{
|
||||
switch (token)
|
||||
{
|
||||
case IdentifierToken:
|
||||
return White;
|
||||
case LiteralToken literal:
|
||||
return literal.Kind switch
|
||||
{
|
||||
LiteralKind.String => Green,
|
||||
LiteralKind.Integer or LiteralKind.Float => BrightBlue,
|
||||
LiteralKind.Bool => Blue,
|
||||
_ => White
|
||||
};
|
||||
case ModifierToken:
|
||||
return White;
|
||||
case SymbolToken symbol:
|
||||
switch (symbol.Symbol)
|
||||
{
|
||||
case Symbol.If:
|
||||
case Symbol.Else:
|
||||
case Symbol.While:
|
||||
case Symbol.Break:
|
||||
case Symbol.Continue:
|
||||
case Symbol.Return:
|
||||
return Magenta;
|
||||
case Symbol.Func:
|
||||
case Symbol.Struct:
|
||||
case Symbol.Namespace:
|
||||
case Symbol.Let:
|
||||
case Symbol.Alloc:
|
||||
return Blue;
|
||||
case Symbol.Assign:
|
||||
case Symbol.Bang:
|
||||
case Symbol.Equal:
|
||||
case Symbol.NotEqual:
|
||||
case Symbol.LessThan:
|
||||
case Symbol.LessThanOrEqual:
|
||||
case Symbol.GreaterThan:
|
||||
case Symbol.GreaterThanOrEqual:
|
||||
case Symbol.Plus:
|
||||
case Symbol.Minus:
|
||||
case Symbol.Star:
|
||||
case Symbol.ForwardSlash:
|
||||
case Symbol.Caret:
|
||||
case Symbol.Ampersand:
|
||||
return White;
|
||||
case Symbol.Colon:
|
||||
case Symbol.Comma:
|
||||
case Symbol.Period:
|
||||
case Symbol.DoubleColon:
|
||||
return Faint;
|
||||
case Symbol.OpenParen:
|
||||
case Symbol.CloseParen:
|
||||
case Symbol.OpenBrace:
|
||||
case Symbol.CloseBrace:
|
||||
case Symbol.OpenBracket:
|
||||
case Symbol.CloseBracket:
|
||||
return Yellow;
|
||||
default:
|
||||
return White;
|
||||
}
|
||||
default:
|
||||
return White;
|
||||
}
|
||||
}
|
||||
|
||||
public static string ColorizeSource(string source)
|
||||
{
|
||||
var sourceText = new SourceText(string.Empty, source);
|
||||
var tokens = Tokenizer.Tokenize(sourceText, out _);
|
||||
var result = new StringBuilder();
|
||||
var lastCharIndex = 0;
|
||||
|
||||
foreach (var token in tokens)
|
||||
{
|
||||
var tokenStartIndex = GetCharacterIndex(sourceText, token.Span.Start);
|
||||
var tokenEndIndex = GetCharacterIndex(sourceText, token.Span.End);
|
||||
|
||||
if (tokenStartIndex > lastCharIndex)
|
||||
{
|
||||
var between = sourceText.Content.Substring(lastCharIndex, tokenStartIndex - lastCharIndex);
|
||||
result.Append(between);
|
||||
}
|
||||
|
||||
var tokenText = sourceText.Content.Substring(tokenStartIndex, tokenEndIndex - tokenStartIndex);
|
||||
|
||||
result.Append(Colorize(tokenText, GetTokenColor(token)));
|
||||
lastCharIndex = tokenEndIndex;
|
||||
}
|
||||
|
||||
if (lastCharIndex < sourceText.Content.Length)
|
||||
{
|
||||
var remaining = sourceText.Content[lastCharIndex..];
|
||||
result.Append(Colorize(remaining, Faint));
|
||||
}
|
||||
|
||||
return result.ToString();
|
||||
}
|
||||
|
||||
private static int GetCharacterIndex(SourceText sourceText, SourceLocation location)
|
||||
{
|
||||
var lines = sourceText.Content.Split('\n');
|
||||
var index = 0;
|
||||
|
||||
for (var i = 0; i < location.Line - 1 && i < lines.Length; i++)
|
||||
{
|
||||
index += lines[i].Length + 1;
|
||||
}
|
||||
|
||||
index += location.Column - 1;
|
||||
|
||||
return Math.Min(index, sourceText.Content.Length);
|
||||
}
|
||||
}
|
||||
215
src/compiler/NubLang/Diagnostics/Diagnostic.cs
Normal file
215
src/compiler/NubLang/Diagnostics/Diagnostic.cs
Normal file
@@ -0,0 +1,215 @@
|
||||
using System.Text;
|
||||
using NubLang.Syntax.Node;
|
||||
using NubLang.Syntax.Tokenization;
|
||||
|
||||
namespace NubLang.Diagnostics;
|
||||
|
||||
public class Diagnostic
|
||||
{
|
||||
public class DiagnosticBuilder
|
||||
{
|
||||
private readonly DiagnosticSeverity _severity;
|
||||
private readonly string _message;
|
||||
private string? _help;
|
||||
private SourceSpan? _sourceSpan;
|
||||
|
||||
public DiagnosticBuilder(DiagnosticSeverity severity, string message)
|
||||
{
|
||||
_severity = severity;
|
||||
_message = message;
|
||||
}
|
||||
|
||||
public DiagnosticBuilder At(Token token)
|
||||
{
|
||||
_sourceSpan = token.Span;
|
||||
return this;
|
||||
}
|
||||
|
||||
public DiagnosticBuilder At(Node node)
|
||||
{
|
||||
_sourceSpan = SourceSpan.Merge(node.Tokens.Select(t => t.Span));
|
||||
return this;
|
||||
}
|
||||
|
||||
public DiagnosticBuilder At(SourceSpan span)
|
||||
{
|
||||
_sourceSpan = span;
|
||||
return this;
|
||||
}
|
||||
|
||||
public DiagnosticBuilder WithHelp(string help)
|
||||
{
|
||||
_help = help;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Diagnostic Build() => new(_severity, _message, _sourceSpan, _help);
|
||||
}
|
||||
|
||||
public static DiagnosticBuilder Error(string message) => new(DiagnosticSeverity.Error, message);
|
||||
public static DiagnosticBuilder Warning(string message) => new(DiagnosticSeverity.Warning, message);
|
||||
public static DiagnosticBuilder Info(string message) => new(DiagnosticSeverity.Info, message);
|
||||
|
||||
public DiagnosticSeverity Severity { get; }
|
||||
public string Message { get; }
|
||||
public SourceSpan? Span { get; }
|
||||
public string? Help { get; }
|
||||
|
||||
private Diagnostic(DiagnosticSeverity severity, string message, SourceSpan? span, string? help)
|
||||
{
|
||||
Severity = severity;
|
||||
Message = message;
|
||||
Span = span;
|
||||
Help = help;
|
||||
}
|
||||
|
||||
public string FormatANSI()
|
||||
{
|
||||
var sb = new StringBuilder();
|
||||
|
||||
var severityText = GetSeverityText(Severity);
|
||||
sb.Append(severityText);
|
||||
|
||||
if (Span.HasValue)
|
||||
{
|
||||
var locationText = $" at {Span.Value.Text.Path}:{Span}";
|
||||
sb.Append(ConsoleColors.Colorize(locationText, ConsoleColors.Faint));
|
||||
}
|
||||
|
||||
sb.Append(": ");
|
||||
sb.Append(ConsoleColors.Colorize(Message, ConsoleColors.BrightWhite));
|
||||
|
||||
if (Span.HasValue)
|
||||
{
|
||||
sb.AppendLine();
|
||||
AppendSourceContext(sb, Span.Value, Severity);
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(Help))
|
||||
{
|
||||
sb.AppendLine();
|
||||
sb.Append(ConsoleColors.Colorize($"help: {Help}", ConsoleColors.Cyan));
|
||||
}
|
||||
|
||||
return sb.ToString();
|
||||
}
|
||||
|
||||
private static string GetSeverityText(DiagnosticSeverity severity)
|
||||
{
|
||||
return severity switch
|
||||
{
|
||||
DiagnosticSeverity.Error => ConsoleColors.Colorize("error", ConsoleColors.Bold + ConsoleColors.Red),
|
||||
DiagnosticSeverity.Warning => ConsoleColors.Colorize("warning", ConsoleColors.Bold + ConsoleColors.Yellow),
|
||||
DiagnosticSeverity.Info => ConsoleColors.Colorize("info", ConsoleColors.Bold + ConsoleColors.Blue),
|
||||
_ => throw new ArgumentOutOfRangeException(nameof(severity), severity, "Unknown diagnostic severity")
|
||||
};
|
||||
}
|
||||
|
||||
private static void AppendSourceContext(StringBuilder sb, SourceSpan span, DiagnosticSeverity severity)
|
||||
{
|
||||
var lines = span.Text.Content.Split('\n');
|
||||
var startLine = span.Start.Line;
|
||||
var endLine = span.End.Line;
|
||||
|
||||
const int contextLines = 3;
|
||||
|
||||
var lineNumWidth = Math.Min(endLine + contextLines, lines.Length).ToString().Length;
|
||||
|
||||
var contextStart = Math.Max(1, startLine - contextLines);
|
||||
var contextEnd = Math.Min(lines.Length, endLine + contextLines);
|
||||
|
||||
var contextWidth = 0;
|
||||
for (var i = contextStart; i <= contextEnd; i++)
|
||||
{
|
||||
if (lines[i - 1].Length > contextWidth)
|
||||
{
|
||||
contextWidth = lines[i - 1].Length;
|
||||
}
|
||||
}
|
||||
|
||||
sb.AppendLine(ConsoleColors.Colorize('╭' + new string('─', lineNumWidth + 2) + '┬' + new string('─', contextWidth + 2) + '╮', ConsoleColors.Faint));
|
||||
|
||||
for (var lineNum = contextStart; lineNum < startLine; lineNum++)
|
||||
{
|
||||
AppendContextLine(sb, lineNum, lines[lineNum - 1], lineNumWidth, contextWidth);
|
||||
}
|
||||
|
||||
for (var lineNum = startLine; lineNum <= endLine; lineNum++)
|
||||
{
|
||||
AppendContextLine(sb, lineNum, lines[lineNum - 1], lineNumWidth, contextWidth);
|
||||
AppendErrorIndicators(sb, span, lineNum, lines[lineNum - 1], lineNumWidth, contextWidth, severity);
|
||||
}
|
||||
|
||||
for (var lineNum = endLine + 1; lineNum <= contextEnd; lineNum++)
|
||||
{
|
||||
AppendContextLine(sb, lineNum, lines[lineNum - 1], lineNumWidth, contextWidth);
|
||||
}
|
||||
|
||||
sb.Append(ConsoleColors.Colorize('╰' + new string('─', lineNumWidth + 2) + '┴' + new string('─', contextWidth + 2) + '╯', ConsoleColors.Faint));
|
||||
}
|
||||
|
||||
private static void AppendContextLine(StringBuilder sb, int lineNum, string line, int lineNumWidth, int contextWidth)
|
||||
{
|
||||
sb.Append(ConsoleColors.Colorize('│' + " ", ConsoleColors.Faint));
|
||||
var lineNumStr = lineNum.ToString().PadLeft(lineNumWidth);
|
||||
sb.Append(ConsoleColors.Colorize(lineNumStr, ConsoleColors.Faint));
|
||||
sb.Append(ConsoleColors.Colorize(" │ ", ConsoleColors.Faint));
|
||||
sb.Append(ConsoleColors.ColorizeSource(line.PadRight(contextWidth)));
|
||||
sb.Append(ConsoleColors.Colorize(" " + '│', ConsoleColors.Faint));
|
||||
sb.AppendLine();
|
||||
}
|
||||
|
||||
private static void AppendErrorIndicators(StringBuilder sb, SourceSpan span, int lineNum, string line, int lineNumWidth, int contextWidth, DiagnosticSeverity severity)
|
||||
{
|
||||
var color = severity switch
|
||||
{
|
||||
DiagnosticSeverity.Info => ConsoleColors.Blue,
|
||||
DiagnosticSeverity.Warning => ConsoleColors.Yellow,
|
||||
DiagnosticSeverity.Error => ConsoleColors.Red,
|
||||
_ => throw new ArgumentOutOfRangeException(nameof(severity), severity, null)
|
||||
};
|
||||
|
||||
sb.Append(ConsoleColors.Colorize('│' + " ", ConsoleColors.Faint));
|
||||
sb.Append(new string(' ', lineNumWidth));
|
||||
sb.Append(ConsoleColors.Colorize(" │ ", ConsoleColors.Faint));
|
||||
var indicators = GetIndicatorsForLine(span, lineNum, line);
|
||||
sb.Append(ConsoleColors.Colorize(indicators.PadRight(contextWidth), color));
|
||||
sb.Append(ConsoleColors.Colorize(" " + '│', ConsoleColors.Faint));
|
||||
sb.AppendLine();
|
||||
}
|
||||
|
||||
private static string GetIndicatorsForLine(SourceSpan span, int lineNum, string line)
|
||||
{
|
||||
const char indicator = '^';
|
||||
|
||||
if (lineNum == span.Start.Line && lineNum == span.End.Line)
|
||||
{
|
||||
var startCol = Math.Max(0, span.Start.Column - 1);
|
||||
var endCol = Math.Min(line.Length, span.End.Column - 1);
|
||||
var length = Math.Max(1, endCol - startCol);
|
||||
|
||||
return new string(' ', startCol) + new string(indicator, length);
|
||||
}
|
||||
|
||||
if (lineNum == span.Start.Line)
|
||||
{
|
||||
var startCol = Math.Max(0, span.Start.Column - 1);
|
||||
return new string(' ', startCol) + new string(indicator, Math.Max(0, line.Length - startCol));
|
||||
}
|
||||
|
||||
if (lineNum == span.End.Line)
|
||||
{
|
||||
var endCol = Math.Min(line.Length, span.End.Column - 1);
|
||||
return new string(indicator, Math.Max(0, endCol));
|
||||
}
|
||||
|
||||
return new string(indicator, line.Length);
|
||||
}
|
||||
}
|
||||
|
||||
public enum DiagnosticSeverity
|
||||
{
|
||||
Info,
|
||||
Warning,
|
||||
Error
|
||||
}
|
||||
1285
src/compiler/NubLang/Generation/QBE/QBEGenerator.cs
Normal file
1285
src/compiler/NubLang/Generation/QBE/QBEGenerator.cs
Normal file
File diff suppressed because it is too large
Load Diff
80
src/compiler/NubLang/Generation/QBE/QBEWriter.cs
Normal file
80
src/compiler/NubLang/Generation/QBE/QBEWriter.cs
Normal file
@@ -0,0 +1,80 @@
|
||||
using System.Text;
|
||||
using NubLang.Syntax.Node;
|
||||
|
||||
namespace NubLang.Generation.QBE;
|
||||
|
||||
internal class QBEWriter
|
||||
{
|
||||
private readonly StringBuilder _builder = new();
|
||||
private int _currentLine = -1;
|
||||
|
||||
public QBEWriter(string debugFile)
|
||||
{
|
||||
_builder.AppendLine($"dbgfile \"{debugFile}\"");
|
||||
_builder.AppendLine();
|
||||
}
|
||||
|
||||
public void StartFunction(string signature)
|
||||
{
|
||||
_currentLine = -1;
|
||||
_builder.Append(signature);
|
||||
_builder.AppendLine(" {");
|
||||
_builder.AppendLine("@start");
|
||||
}
|
||||
|
||||
public void EndFunction()
|
||||
{
|
||||
_builder.AppendLine("}");
|
||||
}
|
||||
|
||||
private void WriteDebugLocation(SourceSpan span)
|
||||
{
|
||||
var line = span.Start.Line;
|
||||
|
||||
if (_currentLine != line)
|
||||
{
|
||||
_builder.AppendLine($" dbgloc {line}");
|
||||
_currentLine = line;
|
||||
}
|
||||
}
|
||||
|
||||
public void WriteDebugLocation(BoundNode node)
|
||||
{
|
||||
var firstToken = node.Tokens.FirstOrDefault();
|
||||
if (firstToken != null)
|
||||
{
|
||||
// WriteDebugLocation(firstToken.Span);
|
||||
}
|
||||
}
|
||||
|
||||
public void Indented(string value)
|
||||
{
|
||||
_builder.Append('\t');
|
||||
_builder.AppendLine(value);
|
||||
}
|
||||
|
||||
public void Comment(string comment)
|
||||
{
|
||||
_builder.AppendLine("# " + comment);
|
||||
}
|
||||
|
||||
public void WriteLine(string text)
|
||||
{
|
||||
_builder.AppendLine(text);
|
||||
}
|
||||
|
||||
public void Write(string text)
|
||||
{
|
||||
_builder.Append(text);
|
||||
}
|
||||
|
||||
public void NewLine()
|
||||
{
|
||||
_builder.AppendLine();
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return _builder.ToString();
|
||||
}
|
||||
}
|
||||
191
src/compiler/NubLang/NubBoundType.cs
Normal file
191
src/compiler/NubLang/NubBoundType.cs
Normal file
@@ -0,0 +1,191 @@
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
|
||||
namespace NubLang;
|
||||
|
||||
public abstract class BoundNubType : IEquatable<BoundNubType>
|
||||
{
|
||||
public bool Is8BitInteger => this is BoundNubPrimitiveType { Kind: PrimitiveTypeKind.I8 or PrimitiveTypeKind.U8 };
|
||||
public bool Is16BitInteger => this is BoundNubPrimitiveType { Kind: PrimitiveTypeKind.I16 or PrimitiveTypeKind.U16 };
|
||||
public bool Is32BitInteger => this is BoundNubPrimitiveType { Kind: PrimitiveTypeKind.I32 or PrimitiveTypeKind.U32 };
|
||||
public bool Is64BitInteger => this is BoundNubPrimitiveType { Kind: PrimitiveTypeKind.I64 or PrimitiveTypeKind.U64 };
|
||||
|
||||
public bool IsSignedInteger => this is BoundNubPrimitiveType { Kind: PrimitiveTypeKind.I8 or PrimitiveTypeKind.I16 or PrimitiveTypeKind.I32 or PrimitiveTypeKind.I64 };
|
||||
public bool IsUnsignedInteger => this is BoundNubPrimitiveType { Kind: PrimitiveTypeKind.U8 or PrimitiveTypeKind.U16 or PrimitiveTypeKind.U32 or PrimitiveTypeKind.U64 };
|
||||
public bool IsInteger => IsSignedInteger || IsUnsignedInteger;
|
||||
|
||||
public bool IsFloat32 => this is BoundNubPrimitiveType { Kind: PrimitiveTypeKind.F32 };
|
||||
public bool IsFloat64 => this is BoundNubPrimitiveType { Kind: PrimitiveTypeKind.F64 };
|
||||
public bool IsFloat => IsFloat32 || IsFloat64;
|
||||
|
||||
public bool IsNumber => IsFloat || IsInteger;
|
||||
|
||||
public bool IsVoid => this is BoundNubVoidType;
|
||||
public bool IsString => this is BoundNubStringType;
|
||||
public bool IsCString => this is BoundNubCStringType;
|
||||
public bool IsBool => this is BoundNubPrimitiveType { Kind: PrimitiveTypeKind.Bool };
|
||||
|
||||
public override bool Equals(object? obj) => obj is BoundNubType other && Equals(other);
|
||||
|
||||
public abstract bool Equals(BoundNubType? other);
|
||||
public abstract override int GetHashCode();
|
||||
public abstract override string ToString();
|
||||
|
||||
public static bool operator ==(BoundNubType? left, BoundNubType? right) => Equals(left, right);
|
||||
public static bool operator !=(BoundNubType? left, BoundNubType? right) => !Equals(left, right);
|
||||
}
|
||||
|
||||
public abstract class BoundNubComplexType : BoundNubType;
|
||||
|
||||
public abstract class BoundNubSimpleType : BoundNubType;
|
||||
|
||||
public class BoundNubCStringType : BoundNubComplexType
|
||||
{
|
||||
public override string ToString() => "cstring";
|
||||
public override bool Equals(BoundNubType? other) => other is BoundNubCStringType;
|
||||
public override int GetHashCode() => HashCode.Combine(typeof(BoundNubCStringType));
|
||||
}
|
||||
|
||||
public class BoundNubStringType : BoundNubComplexType
|
||||
{
|
||||
public override string ToString() => "string";
|
||||
public override bool Equals(BoundNubType? other) => other is BoundNubStringType;
|
||||
public override int GetHashCode() => HashCode.Combine(typeof(BoundNubStringType));
|
||||
}
|
||||
|
||||
public class BoundNubFuncType(BoundNubType returnType, List<BoundNubType> parameters) : BoundNubSimpleType
|
||||
{
|
||||
public BoundNubType ReturnType { get; } = returnType;
|
||||
public List<BoundNubType> Parameters { get; } = parameters;
|
||||
|
||||
public override string ToString() => $"func({string.Join(", ", Parameters)}): {ReturnType}";
|
||||
|
||||
public override bool Equals(BoundNubType? other) => other is BoundNubFuncType func && ReturnType.Equals(func.ReturnType) && Parameters.SequenceEqual(func.Parameters);
|
||||
|
||||
public override int GetHashCode()
|
||||
{
|
||||
var hash = new HashCode();
|
||||
hash.Add(typeof(BoundNubFuncType));
|
||||
hash.Add(ReturnType);
|
||||
foreach (var param in Parameters)
|
||||
{
|
||||
hash.Add(param);
|
||||
}
|
||||
|
||||
return hash.ToHashCode();
|
||||
}
|
||||
}
|
||||
|
||||
public class BoundNubStructType(string @namespace, string name) : BoundNubComplexType
|
||||
{
|
||||
public string Namespace { get; } = @namespace;
|
||||
public string Name { get; } = name;
|
||||
|
||||
public override string ToString() => $"{Namespace}::{Name}";
|
||||
|
||||
public override bool Equals(BoundNubType? other) => other is BoundNubStructType custom && Namespace == custom.Namespace && Name == custom.Name;
|
||||
public override int GetHashCode() => HashCode.Combine(typeof(BoundNubStructType), Namespace, Name);
|
||||
}
|
||||
|
||||
public class BoundNubTraitType(string @namespace, string name) : BoundNubComplexType
|
||||
{
|
||||
public string Namespace { get; } = @namespace;
|
||||
public string Name { get; } = name;
|
||||
|
||||
public override string ToString() => $"{Namespace}::{Name}";
|
||||
|
||||
public override bool Equals(BoundNubType? other) => other is BoundNubTraitType custom && Namespace == custom.Namespace && Name == custom.Name;
|
||||
public override int GetHashCode() => HashCode.Combine(typeof(BoundNubTraitType), Namespace, Name);
|
||||
}
|
||||
|
||||
public class BoundNubPointerType(BoundNubType baseType) : BoundNubSimpleType
|
||||
{
|
||||
public BoundNubType BaseType { get; } = baseType;
|
||||
|
||||
public override string ToString() => "^" + BaseType;
|
||||
|
||||
public override bool Equals(BoundNubType? other) => other is BoundNubPointerType pointer && BaseType.Equals(pointer.BaseType);
|
||||
public override int GetHashCode() => HashCode.Combine(typeof(BoundNubPointerType), BaseType);
|
||||
}
|
||||
|
||||
public class BoundNubArrayType(BoundNubType elementType) : BoundNubComplexType
|
||||
{
|
||||
public BoundNubType ElementType { get; } = elementType;
|
||||
|
||||
public override string ToString() => "[]" + ElementType;
|
||||
|
||||
public override bool Equals(BoundNubType? other) => other is BoundNubArrayType array && ElementType.Equals(array.ElementType);
|
||||
public override int GetHashCode() => HashCode.Combine(typeof(BoundNubArrayType), ElementType);
|
||||
}
|
||||
|
||||
public class BoundNubVoidType : BoundNubSimpleType
|
||||
{
|
||||
public override string ToString() => "void";
|
||||
public override bool Equals(BoundNubType? other) => other is BoundNubVoidType;
|
||||
public override int GetHashCode() => HashCode.Combine(typeof(BoundNubVoidType));
|
||||
}
|
||||
|
||||
public class BoundNubPrimitiveType(PrimitiveTypeKind kind) : BoundNubSimpleType
|
||||
{
|
||||
public PrimitiveTypeKind Kind { get; } = kind;
|
||||
|
||||
public static BoundNubPrimitiveType I64 => new(PrimitiveTypeKind.I64);
|
||||
public static BoundNubPrimitiveType I32 => new(PrimitiveTypeKind.I32);
|
||||
public static BoundNubPrimitiveType I16 => new(PrimitiveTypeKind.I16);
|
||||
public static BoundNubPrimitiveType I8 => new(PrimitiveTypeKind.I8);
|
||||
|
||||
public static BoundNubPrimitiveType U64 => new(PrimitiveTypeKind.U64);
|
||||
public static BoundNubPrimitiveType U32 => new(PrimitiveTypeKind.U32);
|
||||
public static BoundNubPrimitiveType U16 => new(PrimitiveTypeKind.U16);
|
||||
public static BoundNubPrimitiveType U8 => new(PrimitiveTypeKind.U8);
|
||||
|
||||
public static BoundNubPrimitiveType F64 => new(PrimitiveTypeKind.F64);
|
||||
public static BoundNubPrimitiveType F32 => new(PrimitiveTypeKind.F32);
|
||||
|
||||
public static BoundNubPrimitiveType Bool => new(PrimitiveTypeKind.Bool);
|
||||
|
||||
public static bool TryParse(string s, [NotNullWhen(true)] out PrimitiveTypeKind? kind)
|
||||
{
|
||||
kind = s switch
|
||||
{
|
||||
"i64" => PrimitiveTypeKind.I64,
|
||||
"i32" => PrimitiveTypeKind.I32,
|
||||
"i16" => PrimitiveTypeKind.I16,
|
||||
"i8" => PrimitiveTypeKind.I8,
|
||||
"u64" => PrimitiveTypeKind.U64,
|
||||
"u32" => PrimitiveTypeKind.U32,
|
||||
"u16" => PrimitiveTypeKind.U16,
|
||||
"u8" => PrimitiveTypeKind.U8,
|
||||
"f64" => PrimitiveTypeKind.F64,
|
||||
"f32" => PrimitiveTypeKind.F32,
|
||||
"bool" => PrimitiveTypeKind.Bool,
|
||||
_ => null
|
||||
};
|
||||
|
||||
return kind != null;
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return Kind switch
|
||||
{
|
||||
PrimitiveTypeKind.I8 => "i8",
|
||||
PrimitiveTypeKind.I16 => "i16",
|
||||
PrimitiveTypeKind.I32 => "i32",
|
||||
PrimitiveTypeKind.I64 => "i64",
|
||||
|
||||
PrimitiveTypeKind.U8 => "u8",
|
||||
PrimitiveTypeKind.U16 => "u16",
|
||||
PrimitiveTypeKind.U32 => "u32",
|
||||
PrimitiveTypeKind.U64 => "u64",
|
||||
|
||||
PrimitiveTypeKind.F32 => "f32",
|
||||
PrimitiveTypeKind.F64 => "f64",
|
||||
|
||||
PrimitiveTypeKind.Bool => "bool",
|
||||
_ => throw new ArgumentOutOfRangeException(nameof(kind), Kind, null)
|
||||
};
|
||||
}
|
||||
|
||||
public override bool Equals(BoundNubType? other) => other is BoundNubPrimitiveType primitive && primitive.Kind == Kind;
|
||||
public override int GetHashCode() => HashCode.Combine(typeof(BoundNubPrimitiveType), Kind.GetHashCode());
|
||||
}
|
||||
19
src/compiler/NubLang/NubLang.csproj
Normal file
19
src/compiler/NubLang/NubLang.csproj
Normal file
@@ -0,0 +1,19 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net9.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
<IsAotCompatible>true</IsAotCompatible>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\Common\Common.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Folder Include="Generation\" />
|
||||
<Folder Include="Syntax\" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
133
src/compiler/NubLang/NubType.cs
Normal file
133
src/compiler/NubLang/NubType.cs
Normal file
@@ -0,0 +1,133 @@
|
||||
namespace NubLang;
|
||||
|
||||
public abstract class NubType : IEquatable<NubType>
|
||||
{
|
||||
public override bool Equals(object? obj) => obj is NubType other && Equals(other);
|
||||
|
||||
public abstract bool Equals(NubType? other);
|
||||
public abstract override int GetHashCode();
|
||||
public abstract override string ToString();
|
||||
|
||||
public static bool operator ==(NubType? left, NubType? right) => Equals(left, right);
|
||||
public static bool operator !=(NubType? left, NubType? right) => !Equals(left, right);
|
||||
}
|
||||
|
||||
public class NubCStringType : NubType
|
||||
{
|
||||
public override string ToString() => "cstring";
|
||||
public override bool Equals(NubType? other) => other is NubCStringType;
|
||||
public override int GetHashCode() => HashCode.Combine(typeof(NubCStringType));
|
||||
}
|
||||
|
||||
public class NubStringType : NubType
|
||||
{
|
||||
public override string ToString() => "string";
|
||||
public override bool Equals(NubType? other) => other is NubStringType;
|
||||
public override int GetHashCode() => HashCode.Combine(typeof(NubStringType));
|
||||
}
|
||||
|
||||
public class NubFuncType(NubType returnType, List<NubType> parameters) : NubType
|
||||
{
|
||||
public NubType ReturnType { get; } = returnType;
|
||||
public List<NubType> Parameters { get; } = parameters;
|
||||
|
||||
public override string ToString() => $"func({string.Join(", ", Parameters)}): {ReturnType}";
|
||||
|
||||
public override bool Equals(NubType? other) => other is NubFuncType func && ReturnType.Equals(func.ReturnType) && Parameters.SequenceEqual(func.Parameters);
|
||||
|
||||
public override int GetHashCode()
|
||||
{
|
||||
var hash = new HashCode();
|
||||
hash.Add(typeof(NubFuncType));
|
||||
hash.Add(ReturnType);
|
||||
foreach (var param in Parameters)
|
||||
{
|
||||
hash.Add(param);
|
||||
}
|
||||
|
||||
return hash.ToHashCode();
|
||||
}
|
||||
}
|
||||
|
||||
public class NubCustomType(string @namespace, string name) : NubType
|
||||
{
|
||||
public string Namespace { get; } = @namespace;
|
||||
public string Name { get; } = name;
|
||||
|
||||
public override string ToString() => $"{Namespace}::{Name}";
|
||||
|
||||
public override bool Equals(NubType? other) => other is NubCustomType custom && Namespace == custom.Namespace && Name == custom.Name;
|
||||
public override int GetHashCode() => HashCode.Combine(typeof(NubCustomType), Namespace, Name);
|
||||
}
|
||||
|
||||
public class NubPointerType(NubType baseType) : NubType
|
||||
{
|
||||
public NubType BaseType { get; } = baseType;
|
||||
|
||||
public override string ToString() => "^" + BaseType;
|
||||
|
||||
public override bool Equals(NubType? other) => other is NubPointerType pointer && BaseType.Equals(pointer.BaseType);
|
||||
public override int GetHashCode() => HashCode.Combine(typeof(NubPointerType), BaseType);
|
||||
}
|
||||
|
||||
public class NubArrayType(NubType elementType) : NubType
|
||||
{
|
||||
public NubType ElementType { get; } = elementType;
|
||||
|
||||
public override string ToString() => "[]" + ElementType;
|
||||
|
||||
public override bool Equals(NubType? other) => other is NubArrayType array && ElementType.Equals(array.ElementType);
|
||||
public override int GetHashCode() => HashCode.Combine(typeof(NubArrayType), ElementType);
|
||||
}
|
||||
|
||||
public class NubVoidType : NubType
|
||||
{
|
||||
public override string ToString() => "void";
|
||||
public override bool Equals(NubType? other) => other is NubVoidType;
|
||||
public override int GetHashCode() => HashCode.Combine(typeof(NubVoidType));
|
||||
}
|
||||
|
||||
public class NubPrimitiveType(PrimitiveTypeKind kind) : NubType
|
||||
{
|
||||
public PrimitiveTypeKind Kind { get; } = kind;
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return Kind switch
|
||||
{
|
||||
PrimitiveTypeKind.I8 => "i8",
|
||||
PrimitiveTypeKind.I16 => "i16",
|
||||
PrimitiveTypeKind.I32 => "i32",
|
||||
PrimitiveTypeKind.I64 => "i64",
|
||||
|
||||
PrimitiveTypeKind.U8 => "u8",
|
||||
PrimitiveTypeKind.U16 => "u16",
|
||||
PrimitiveTypeKind.U32 => "u32",
|
||||
PrimitiveTypeKind.U64 => "u64",
|
||||
|
||||
PrimitiveTypeKind.F32 => "f32",
|
||||
PrimitiveTypeKind.F64 => "f64",
|
||||
|
||||
PrimitiveTypeKind.Bool => "bool",
|
||||
_ => throw new ArgumentOutOfRangeException(nameof(kind), Kind, null)
|
||||
};
|
||||
}
|
||||
|
||||
public override bool Equals(NubType? other) => other is NubPrimitiveType primitive && primitive.Kind == Kind;
|
||||
public override int GetHashCode() => HashCode.Combine(typeof(NubPrimitiveType), Kind.GetHashCode());
|
||||
}
|
||||
|
||||
public enum PrimitiveTypeKind
|
||||
{
|
||||
I64,
|
||||
I32,
|
||||
I16,
|
||||
I8,
|
||||
U64,
|
||||
U32,
|
||||
U16,
|
||||
U8,
|
||||
F64,
|
||||
F32,
|
||||
Bool
|
||||
}
|
||||
274
src/compiler/NubLang/Source.cs
Normal file
274
src/compiler/NubLang/Source.cs
Normal file
@@ -0,0 +1,274 @@
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
|
||||
namespace NubLang;
|
||||
|
||||
/// <summary>
|
||||
/// Represents a location in source code with line and column information.
|
||||
/// Lines and columns are 1-based to match typical editor conventions.
|
||||
/// </summary>
|
||||
public readonly struct SourceLocation : IEquatable<SourceLocation>, IComparable<SourceLocation>
|
||||
{
|
||||
public SourceLocation(int line, int column)
|
||||
{
|
||||
if (line < 1)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(nameof(line), "Line must be >= 1");
|
||||
}
|
||||
|
||||
if (column < 1)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(nameof(column), "Column must be >= 1");
|
||||
}
|
||||
|
||||
Line = line;
|
||||
Column = column;
|
||||
}
|
||||
|
||||
public int Line { get; }
|
||||
public int Column { get; }
|
||||
|
||||
public int CompareTo(SourceLocation other)
|
||||
{
|
||||
var lineComparison = Line.CompareTo(other.Line);
|
||||
if (lineComparison == 0)
|
||||
{
|
||||
return Column.CompareTo(other.Column);
|
||||
}
|
||||
|
||||
return lineComparison;
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return $"{Line}:{Column}";
|
||||
}
|
||||
|
||||
public bool Equals(SourceLocation other)
|
||||
{
|
||||
return Line == other.Line && Column == other.Column;
|
||||
}
|
||||
|
||||
public override bool Equals([NotNullWhen(true)] object? obj)
|
||||
{
|
||||
return obj is SourceLocation other && Equals(other);
|
||||
}
|
||||
|
||||
public override int GetHashCode()
|
||||
{
|
||||
return HashCode.Combine(Line, Column);
|
||||
}
|
||||
|
||||
public static bool operator ==(SourceLocation left, SourceLocation right) => left.Equals(right);
|
||||
public static bool operator !=(SourceLocation left, SourceLocation right) => !left.Equals(right);
|
||||
public static bool operator <(SourceLocation left, SourceLocation right) => left.CompareTo(right) < 0;
|
||||
public static bool operator >(SourceLocation left, SourceLocation right) => left.CompareTo(right) > 0;
|
||||
public static bool operator <=(SourceLocation left, SourceLocation right) => left.CompareTo(right) <= 0;
|
||||
public static bool operator >=(SourceLocation left, SourceLocation right) => left.CompareTo(right) >= 0;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Represents source text with a name (typically filename) and content.
|
||||
/// Equality is based on both name and content for better semantics.
|
||||
/// </summary>
|
||||
public struct SourceText : IEquatable<SourceText>
|
||||
{
|
||||
private int _lines = -1;
|
||||
|
||||
public SourceText(string path, string content)
|
||||
{
|
||||
Path = path ?? throw new ArgumentNullException(nameof(path));
|
||||
Content = content ?? throw new ArgumentNullException(nameof(content));
|
||||
}
|
||||
|
||||
public string Path { get; }
|
||||
public string Content { get; }
|
||||
|
||||
public int LineCount()
|
||||
{
|
||||
if (_lines == -1)
|
||||
{
|
||||
_lines = Content.Split('\n').Length + 1;
|
||||
}
|
||||
|
||||
return _lines;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a specific line from the source text (1-based).
|
||||
/// </summary>
|
||||
public string GetLine(int lineNumber)
|
||||
{
|
||||
if (lineNumber < 1)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(nameof(lineNumber));
|
||||
}
|
||||
|
||||
var lines = Content.Split('\n');
|
||||
return lineNumber <= lines.Length ? lines[lineNumber - 1] : string.Empty;
|
||||
}
|
||||
|
||||
public bool Equals(SourceText other)
|
||||
{
|
||||
return Path == other.Path && Content == other.Content;
|
||||
}
|
||||
|
||||
public override bool Equals([NotNullWhen(true)] object? obj)
|
||||
{
|
||||
return obj is SourceText other && Equals(other);
|
||||
}
|
||||
|
||||
public override int GetHashCode()
|
||||
{
|
||||
return HashCode.Combine(Path, Content);
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return Path;
|
||||
}
|
||||
|
||||
public static bool operator ==(SourceText left, SourceText right) => left.Equals(right);
|
||||
public static bool operator !=(SourceText left, SourceText right) => !left.Equals(right);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Represents a span of source code from a start to end location within a source text.
|
||||
/// </summary>
|
||||
public readonly struct SourceSpan : IEquatable<SourceSpan>
|
||||
{
|
||||
public SourceSpan(SourceText text, SourceLocation start, SourceLocation end)
|
||||
{
|
||||
if (start > end)
|
||||
{
|
||||
throw new ArgumentException("Start location cannot be after end location");
|
||||
}
|
||||
|
||||
if (end.Line > text.LineCount() || end.Line == text.LineCount() && end.Column > text.GetLine(text.LineCount()).Length + 1)
|
||||
{
|
||||
throw new ArgumentException("End location cannot be after text end location");
|
||||
}
|
||||
|
||||
Text = text;
|
||||
Start = start;
|
||||
End = end;
|
||||
}
|
||||
|
||||
public SourceText Text { get; }
|
||||
public SourceLocation Start { get; }
|
||||
public SourceLocation End { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets whether this span represents a single point (start == end).
|
||||
/// </summary>
|
||||
public bool IsEmpty => Start == End;
|
||||
|
||||
/// <summary>
|
||||
/// Gets whether this span is contained within a single line.
|
||||
/// </summary>
|
||||
public bool IsSingleLine => Start.Line == End.Line;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the text content covered by this span.
|
||||
/// </summary>
|
||||
public string GetText()
|
||||
{
|
||||
if (IsEmpty)
|
||||
{
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
var lines = Text.Content.Split('\n');
|
||||
|
||||
if (IsSingleLine)
|
||||
{
|
||||
var line = lines[Start.Line - 1];
|
||||
var startCol = Math.Min(Start.Column - 1, line.Length);
|
||||
var endCol = Math.Min(End.Column - 1, line.Length);
|
||||
return line.Substring(startCol, Math.Max(0, endCol - startCol));
|
||||
}
|
||||
|
||||
var result = new List<string>();
|
||||
for (var i = Start.Line - 1; i < Math.Min(End.Line, lines.Length); i++)
|
||||
{
|
||||
var line = lines[i];
|
||||
if (i == Start.Line - 1)
|
||||
{
|
||||
result.Add(line[Math.Min(Start.Column - 1, line.Length)..]);
|
||||
}
|
||||
else if (i == End.Line - 1)
|
||||
{
|
||||
result.Add(line[..Math.Min(End.Column - 1, line.Length)]);
|
||||
}
|
||||
else
|
||||
{
|
||||
result.Add(line);
|
||||
}
|
||||
}
|
||||
|
||||
return string.Join("\n", result);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Merges multiple source spans from the same file into a single span.
|
||||
/// The result spans from the earliest start to the latest end.
|
||||
/// </summary>
|
||||
public static SourceSpan Merge(IEnumerable<SourceSpan> spans)
|
||||
{
|
||||
var spanArray = spans.ToArray();
|
||||
if (spanArray.Length == 0)
|
||||
{
|
||||
throw new ArgumentException("Cannot merge empty collection of spans", nameof(spans));
|
||||
}
|
||||
|
||||
var firstText = spanArray[0].Text;
|
||||
if (spanArray.Any(s => !s.Text.Equals(firstText)))
|
||||
{
|
||||
throw new ArgumentException("All spans must be from the same source text", nameof(spans));
|
||||
}
|
||||
|
||||
var minStart = spanArray.Min(s => s.Start);
|
||||
var maxEnd = spanArray.Max(s => s.End);
|
||||
|
||||
return new SourceSpan(firstText, minStart, maxEnd);
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
if (IsEmpty)
|
||||
{
|
||||
return $"{Start}";
|
||||
}
|
||||
|
||||
if (IsSingleLine)
|
||||
{
|
||||
return Start.Column == End.Column ? $"{Start}" : $"{Start.Line}:{Start.Column}-{End.Column}";
|
||||
}
|
||||
|
||||
return $"{Start}-{End}";
|
||||
}
|
||||
|
||||
public bool Equals(SourceSpan other)
|
||||
{
|
||||
return Text.Equals(other.Text) && Start.Equals(other.Start) && End.Equals(other.End);
|
||||
}
|
||||
|
||||
public override bool Equals([NotNullWhen(true)] object? obj)
|
||||
{
|
||||
return obj is SourceSpan other && Equals(other);
|
||||
}
|
||||
|
||||
public override int GetHashCode()
|
||||
{
|
||||
return HashCode.Combine(Text, Start, End);
|
||||
}
|
||||
|
||||
public static bool operator ==(SourceSpan left, SourceSpan right)
|
||||
{
|
||||
return left.Equals(right);
|
||||
}
|
||||
|
||||
public static bool operator !=(SourceSpan left, SourceSpan right)
|
||||
{
|
||||
return !left.Equals(right);
|
||||
}
|
||||
}
|
||||
643
src/compiler/NubLang/Syntax/Binding/Binder.cs
Normal file
643
src/compiler/NubLang/Syntax/Binding/Binder.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
37
src/compiler/NubLang/Syntax/Node/Definition.cs
Normal file
37
src/compiler/NubLang/Syntax/Node/Definition.cs
Normal 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);
|
||||
67
src/compiler/NubLang/Syntax/Node/Expression.cs
Normal file
67
src/compiler/NubLang/Syntax/Node/Expression.cs
Normal 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);
|
||||
9
src/compiler/NubLang/Syntax/Node/Node.cs
Normal file
9
src/compiler/NubLang/Syntax/Node/Node.cs
Normal 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);
|
||||
32
src/compiler/NubLang/Syntax/Node/Statement.cs
Normal file
32
src/compiler/NubLang/Syntax/Node/Statement.cs
Normal 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);
|
||||
6
src/compiler/NubLang/Syntax/Node/SyntaxTree.cs
Normal file
6
src/compiler/NubLang/Syntax/Node/SyntaxTree.cs
Normal 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);
|
||||
909
src/compiler/NubLang/Syntax/Parsing/Parser.cs
Normal file
909
src/compiler/NubLang/Syntax/Parsing/Parser.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
namespace NubLang.Syntax.Tokenization;
|
||||
|
||||
public class IdentifierToken(SourceSpan span, string value) : Token(span)
|
||||
{
|
||||
public string Value { get; } = value;
|
||||
}
|
||||
15
src/compiler/NubLang/Syntax/Tokenization/LiteralToken.cs
Normal file
15
src/compiler/NubLang/Syntax/Tokenization/LiteralToken.cs
Normal 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
|
||||
}
|
||||
12
src/compiler/NubLang/Syntax/Tokenization/ModifierToken.cs
Normal file
12
src/compiler/NubLang/Syntax/Tokenization/ModifierToken.cs
Normal 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
|
||||
}
|
||||
49
src/compiler/NubLang/Syntax/Tokenization/SymbolToken.cs
Normal file
49
src/compiler/NubLang/Syntax/Tokenization/SymbolToken.cs
Normal 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
|
||||
}
|
||||
6
src/compiler/NubLang/Syntax/Tokenization/Token.cs
Normal file
6
src/compiler/NubLang/Syntax/Tokenization/Token.cs
Normal file
@@ -0,0 +1,6 @@
|
||||
namespace NubLang.Syntax.Tokenization;
|
||||
|
||||
public abstract class Token(SourceSpan span)
|
||||
{
|
||||
public SourceSpan Span { get; } = span;
|
||||
}
|
||||
274
src/compiler/NubLang/Syntax/Tokenization/Tokenizer.cs
Normal file
274
src/compiler/NubLang/Syntax/Tokenization/Tokenizer.cs
Normal 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();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user