This commit is contained in:
nub31
2025-11-03 17:10:15 +01:00
parent 7d49bf43b7
commit 47fef6bc9f
7 changed files with 175 additions and 210 deletions

View File

@@ -16,8 +16,8 @@ public static class AstExtensions
return new Location return new Location
{ {
Uri = node.Tokens.First().Span.FilePath, Uri = node.Tokens.First().Span.SourcePath,
Range = new Range(node.Tokens.First().Span.Start.Line - 1, node.Tokens.First().Span.Start.Column - 1, node.Tokens.Last().Span.End.Line - 1, node.Tokens.Last().Span.End.Column - 1) Range = new Range(node.Tokens.First().Span.StartLine - 1, node.Tokens.First().Span.StartColumn - 1, node.Tokens.Last().Span.EndLine - 1, node.Tokens.Last().Span.EndColumn - 1)
}; };
} }
@@ -25,20 +25,17 @@ public static class AstExtensions
{ {
return new Location return new Location
{ {
Uri = token.Span.FilePath, Uri = token.Span.SourcePath,
Range = new Range(token.Span.Start.Line - 1, token.Span.Start.Column - 1, token.Span.End.Line - 1, token.Span.End.Column - 1) Range = new Range(token.Span.StartLine - 1, token.Span.StartColumn - 1, token.Span.EndLine - 1, token.Span.EndColumn - 1)
}; };
} }
public static bool ContainsPosition(this Token token, int line, int character) public static bool ContainsPosition(this Token token, int line, int character)
{ {
var start = token.Span.Start; var startLine = token.Span.StartLine - 1;
var end = token.Span.End; var startChar = token.Span.StartColumn - 1;
var endLine = token.Span.EndLine - 1;
var startLine = start.Line - 1; var endChar = token.Span.EndColumn - 1;
var startChar = start.Column - 1;
var endLine = end.Line - 1;
var endChar = end.Column - 1;
if (line < startLine || line > endLine) return false; if (line < startLine || line > endLine) return false;
@@ -69,13 +66,12 @@ public static class AstExtensions
return false; return false;
} }
var start = node.Tokens.First().Span.Start; var span = node.Tokens.First().Span;
var end = node.Tokens.Last().Span.End;
var startLine = start.Line - 1; var startLine = span.StartLine - 1;
var startChar = start.Column - 1; var startChar = span.StartColumn - 1;
var endLine = end.Line - 1; var endLine = span.EndLine - 1;
var endChar = end.Column - 1; var endChar = span.EndColumn - 1;
if (line < startLine || line > endLine) return false; if (line < startLine || line > endLine) return false;
@@ -111,8 +107,8 @@ public static class AstExtensions
return compilationUnit return compilationUnit
.SelectMany(x => x.DescendantsAndSelf()) .SelectMany(x => x.DescendantsAndSelf())
.Where(n => n.ContainsPosition(line, character)) .Where(n => n.ContainsPosition(line, character))
.OrderBy(n => n.Tokens.First().Span.Start.Line) .OrderBy(n => n.Tokens.First().Span.StartLine)
.ThenBy(n => n.Tokens.First().Span.Start.Column) .ThenBy(n => n.Tokens.First().Span.StartColumn)
.LastOrDefault(); .LastOrDefault();
} }
} }

View File

@@ -37,7 +37,7 @@ public class DiagnosticsPublisher
}, },
Message = $"{nubDiagnostic.Message}\n{(nubDiagnostic.Help == null ? "" : $"help: {nubDiagnostic.Help}")}", Message = $"{nubDiagnostic.Message}\n{(nubDiagnostic.Help == null ? "" : $"help: {nubDiagnostic.Help}")}",
Range = nubDiagnostic.Span.HasValue Range = nubDiagnostic.Span.HasValue
? new Range(nubDiagnostic.Span.Value.Start.Line - 1, nubDiagnostic.Span.Value.Start.Column - 1, nubDiagnostic.Span.Value.End.Line - 1, nubDiagnostic.Span.Value.End.Column - 1) ? new Range(nubDiagnostic.Span.Value.StartLine - 1, nubDiagnostic.Span.Value.StartColumn - 1, nubDiagnostic.Span.Value.EndLine - 1, nubDiagnostic.Span.Value.EndColumn - 1)
: new Range(), : new Range(),
}; };
} }

View File

@@ -122,7 +122,7 @@ public sealed class TypeChecker
{ {
throw new CompileException(Diagnostic throw new CompileException(Diagnostic
.Error($"Type {value.Type} is not assignable to {field.Type} for field {field.NameToken.Value}") .Error($"Type {value.Type} is not assignable to {field.Type} for field {field.NameToken.Value}")
.At(field) .At(field, _syntaxTree.Tokens)
.Build()); .Build());
} }
} }
@@ -144,7 +144,7 @@ public sealed class TypeChecker
{ {
throw new CompileException(Diagnostic throw new CompileException(Diagnostic
.Error($"Cannot assign {value.Type} to {target.Type}") .Error($"Cannot assign {value.Type} to {target.Type}")
.At(statement.Value) .At(statement.Value, _syntaxTree.Tokens)
.Build()); .Build());
} }
@@ -184,7 +184,7 @@ public sealed class TypeChecker
return expression switch return expression switch
{ {
FuncCallNode funcCall => new StatementFuncCallNode(statement.Tokens, funcCall), FuncCallNode funcCall => new StatementFuncCallNode(statement.Tokens, funcCall),
_ => throw new CompileException(Diagnostic.Error("Expressions statements can only be function calls").At(statement).Build()) _ => throw new CompileException(Diagnostic.Error("Expressions statements can only be function calls").At(statement, _syntaxTree.Tokens).Build())
}; };
} }
@@ -210,7 +210,7 @@ public sealed class TypeChecker
{ {
throw new CompileException(Diagnostic throw new CompileException(Diagnostic
.Error($"Cannot assign {assignmentNode.Type} to variable of type {type}") .Error($"Cannot assign {assignmentNode.Type} to variable of type {type}")
.At(statement.Assignment) .At(statement.Assignment, _syntaxTree.Tokens)
.Build()); .Build());
} }
} }
@@ -219,7 +219,7 @@ public sealed class TypeChecker
{ {
throw new CompileException(Diagnostic throw new CompileException(Diagnostic
.Error($"Cannot infer type of variable {statement.NameToken.Value}") .Error($"Cannot infer type of variable {statement.NameToken.Value}")
.At(statement) .At(statement, _syntaxTree.Tokens)
.Build()); .Build());
} }
@@ -274,7 +274,7 @@ public sealed class TypeChecker
{ {
throw new CompileException(Diagnostic throw new CompileException(Diagnostic
.Error($"Cannot iterate over type {target.Type} which does not have size information") .Error($"Cannot iterate over type {target.Type} which does not have size information")
.At(forSyntax.Target) .At(forSyntax.Target, _syntaxTree.Tokens)
.Build()); .Build());
} }
} }
@@ -337,7 +337,7 @@ public sealed class TypeChecker
{ {
throw new CompileException(Diagnostic throw new CompileException(Diagnostic
.Error("Unable to infer target type of cast") .Error("Unable to infer target type of cast")
.At(expression) .At(expression, _syntaxTree.Tokens)
.WithHelp("Specify target type where value is used") .WithHelp("Specify target type where value is used")
.Build()); .Build());
} }
@@ -348,7 +348,7 @@ public sealed class TypeChecker
{ {
Diagnostics.Add(Diagnostic Diagnostics.Add(Diagnostic
.Warning("Target type of cast is same as the value. Cast is unnecessary") .Warning("Target type of cast is same as the value. Cast is unnecessary")
.At(expression) .At(expression, _syntaxTree.Tokens)
.Build()); .Build());
return value; return value;
@@ -452,7 +452,7 @@ public sealed class TypeChecker
{ {
throw new CompileException(Diagnostic throw new CompileException(Diagnostic
.Error("Array indexer must be of type int") .Error("Array indexer must be of type int")
.At(expression.Index) .At(expression.Index, _syntaxTree.Tokens)
.Build()); .Build());
} }
@@ -463,7 +463,7 @@ public sealed class TypeChecker
NubArrayType arrayType => new ArrayIndexAccessNode(expression.Tokens, arrayType.ElementType, target, index), NubArrayType arrayType => new ArrayIndexAccessNode(expression.Tokens, arrayType.ElementType, target, index),
NubConstArrayType constArrayType => new ConstArrayIndexAccessNode(expression.Tokens, constArrayType.ElementType, target, index), NubConstArrayType constArrayType => new ConstArrayIndexAccessNode(expression.Tokens, constArrayType.ElementType, target, index),
NubSliceType sliceType => new SliceIndexAccessNode(expression.Tokens, sliceType.ElementType, target, index), NubSliceType sliceType => new SliceIndexAccessNode(expression.Tokens, sliceType.ElementType, target, index),
_ => throw new CompileException(Diagnostic.Error($"Cannot use array indexer on type {target.Type}").At(expression).Build()) _ => throw new CompileException(Diagnostic.Error($"Cannot use array indexer on type {target.Type}").At(expression, _syntaxTree.Tokens).Build())
}; };
} }
@@ -490,7 +490,7 @@ public sealed class TypeChecker
{ {
throw new CompileException(Diagnostic throw new CompileException(Diagnostic
.Error("Unable to infer type of array initializer") .Error("Unable to infer type of array initializer")
.At(expression) .At(expression, _syntaxTree.Tokens)
.WithHelp("Provide a type for a variable assignment") .WithHelp("Provide a type for a variable assignment")
.Build()); .Build());
} }
@@ -503,7 +503,7 @@ public sealed class TypeChecker
{ {
throw new CompileException(Diagnostic throw new CompileException(Diagnostic
.Error("Value in array initializer is not the same as the array type") .Error("Value in array initializer is not the same as the array type")
.At(valueExpression) .At(valueExpression, _syntaxTree.Tokens)
.Build()); .Build());
} }
@@ -548,7 +548,7 @@ public sealed class TypeChecker
{ {
throw new CompileException(Diagnostic throw new CompileException(Diagnostic
.Error("Equal and not equal operators must must be used with int, float or bool types") .Error("Equal and not equal operators must must be used with int, float or bool types")
.At(expression.Left) .At(expression.Left, _syntaxTree.Tokens)
.Build()); .Build());
} }
@@ -557,7 +557,7 @@ public sealed class TypeChecker
{ {
throw new CompileException(Diagnostic throw new CompileException(Diagnostic
.Error($"Expected type {left.Type} from left side of binary expression, but got {right.Type}") .Error($"Expected type {left.Type} from left side of binary expression, but got {right.Type}")
.At(expression.Right) .At(expression.Right, _syntaxTree.Tokens)
.Build()); .Build());
} }
@@ -573,7 +573,7 @@ public sealed class TypeChecker
{ {
throw new CompileException(Diagnostic throw new CompileException(Diagnostic
.Error("Greater than and less than operators must must be used with int or float types") .Error("Greater than and less than operators must must be used with int or float types")
.At(expression.Left) .At(expression.Left, _syntaxTree.Tokens)
.Build()); .Build());
} }
@@ -582,7 +582,7 @@ public sealed class TypeChecker
{ {
throw new CompileException(Diagnostic throw new CompileException(Diagnostic
.Error($"Expected type {left.Type} from left side of binary expression, but got {right.Type}") .Error($"Expected type {left.Type} from left side of binary expression, but got {right.Type}")
.At(expression.Right) .At(expression.Right, _syntaxTree.Tokens)
.Build()); .Build());
} }
@@ -596,7 +596,7 @@ public sealed class TypeChecker
{ {
throw new CompileException(Diagnostic throw new CompileException(Diagnostic
.Error("Logical and/or must must be used with bool types") .Error("Logical and/or must must be used with bool types")
.At(expression.Left) .At(expression.Left, _syntaxTree.Tokens)
.Build()); .Build());
} }
@@ -605,7 +605,7 @@ public sealed class TypeChecker
{ {
throw new CompileException(Diagnostic throw new CompileException(Diagnostic
.Error($"Expected type {left.Type} from left side of binary expression, but got {right.Type}") .Error($"Expected type {left.Type} from left side of binary expression, but got {right.Type}")
.At(expression.Right) .At(expression.Right, _syntaxTree.Tokens)
.Build()); .Build());
} }
@@ -618,7 +618,7 @@ public sealed class TypeChecker
{ {
throw new CompileException(Diagnostic throw new CompileException(Diagnostic
.Error("The plus operator must only be used with int and float types") .Error("The plus operator must only be used with int and float types")
.At(expression.Left) .At(expression.Left, _syntaxTree.Tokens)
.Build()); .Build());
} }
@@ -643,7 +643,7 @@ public sealed class TypeChecker
{ {
throw new CompileException(Diagnostic throw new CompileException(Diagnostic
.Error("Math operators must be used with int or float types") .Error("Math operators must be used with int or float types")
.At(expression.Left) .At(expression.Left, _syntaxTree.Tokens)
.Build()); .Build());
} }
@@ -652,7 +652,7 @@ public sealed class TypeChecker
{ {
throw new CompileException(Diagnostic throw new CompileException(Diagnostic
.Error($"Expected type {left.Type} from left side of binary expression, but got {right.Type}") .Error($"Expected type {left.Type} from left side of binary expression, but got {right.Type}")
.At(expression.Right) .At(expression.Right, _syntaxTree.Tokens)
.Build()); .Build());
} }
@@ -666,7 +666,7 @@ public sealed class TypeChecker
{ {
throw new CompileException(Diagnostic throw new CompileException(Diagnostic
.Error("Bitwise operators must be used with int types") .Error("Bitwise operators must be used with int types")
.At(expression.Left) .At(expression.Left, _syntaxTree.Tokens)
.Build()); .Build());
} }
@@ -675,7 +675,7 @@ public sealed class TypeChecker
{ {
throw new CompileException(Diagnostic throw new CompileException(Diagnostic
.Error("Bitwise operators must be used with int types") .Error("Bitwise operators must be used with int types")
.At(expression.Right) .At(expression.Right, _syntaxTree.Tokens)
.Build()); .Build());
} }
@@ -690,7 +690,7 @@ public sealed class TypeChecker
{ {
throw new CompileException(Diagnostic throw new CompileException(Diagnostic
.Error("Bitwise operators must be used with int types") .Error("Bitwise operators must be used with int types")
.At(expression.Left) .At(expression.Left, _syntaxTree.Tokens)
.Build()); .Build());
} }
@@ -699,7 +699,7 @@ public sealed class TypeChecker
{ {
throw new CompileException(Diagnostic throw new CompileException(Diagnostic
.Error($"Expected type {left.Type} from left side of binary expression, but got {right.Type}") .Error($"Expected type {left.Type} from left side of binary expression, but got {right.Type}")
.At(expression.Right) .At(expression.Right, _syntaxTree.Tokens)
.Build()); .Build());
} }
@@ -723,7 +723,7 @@ public sealed class TypeChecker
{ {
throw new CompileException(Diagnostic throw new CompileException(Diagnostic
.Error("Negation operator must be used with signed integer or float types") .Error("Negation operator must be used with signed integer or float types")
.At(expression) .At(expression, _syntaxTree.Tokens)
.Build()); .Build());
} }
@@ -736,7 +736,7 @@ public sealed class TypeChecker
{ {
throw new CompileException(Diagnostic throw new CompileException(Diagnostic
.Error("Invert operator must be used with booleans") .Error("Invert operator must be used with booleans")
.At(expression) .At(expression, _syntaxTree.Tokens)
.Build()); .Build());
} }
@@ -756,7 +756,7 @@ public sealed class TypeChecker
{ {
throw new CompileException(Diagnostic throw new CompileException(Diagnostic
.Error($"Cannot dereference non-pointer type {target.Type}") .Error($"Cannot dereference non-pointer type {target.Type}")
.At(expression) .At(expression, _syntaxTree.Tokens)
.Build()); .Build());
} }
@@ -768,14 +768,14 @@ public sealed class TypeChecker
var accessor = CheckExpression(expression.Expression); var accessor = CheckExpression(expression.Expression);
if (accessor.Type is not NubFuncType funcType) if (accessor.Type is not NubFuncType funcType)
{ {
throw new CompileException(Diagnostic.Error($"Cannot call non-function type {accessor.Type}").At(expression.Expression).Build()); throw new CompileException(Diagnostic.Error($"Cannot call non-function type {accessor.Type}").At(expression.Expression, _syntaxTree.Tokens).Build());
} }
if (expression.Parameters.Count != funcType.Parameters.Count) if (expression.Parameters.Count != funcType.Parameters.Count)
{ {
throw new CompileException(Diagnostic throw new CompileException(Diagnostic
.Error($"Function {funcType} expects {funcType.Parameters.Count} parameters but got {expression.Parameters.Count}") .Error($"Function {funcType} expects {funcType.Parameters.Count} parameters but got {expression.Parameters.Count}")
.At(expression.Parameters.LastOrDefault(expression)) .At(expression.Parameters.LastOrDefault(expression), _syntaxTree.Tokens)
.Build()); .Build());
} }
@@ -790,7 +790,7 @@ public sealed class TypeChecker
{ {
throw new CompileException(Diagnostic throw new CompileException(Diagnostic
.Error($"Parameter {i + 1} does not match the type {expectedParameterType} for function {funcType}") .Error($"Parameter {i + 1} does not match the type {expectedParameterType} for function {funcType}")
.At(parameter) .At(parameter, _syntaxTree.Tokens)
.Build()); .Build());
} }
@@ -817,7 +817,7 @@ public sealed class TypeChecker
throw new CompileException(Diagnostic throw new CompileException(Diagnostic
.Error($"There is no identifier named {expression.NameToken.Value}") .Error($"There is no identifier named {expression.NameToken.Value}")
.At(expression) .At(expression, _syntaxTree.Tokens)
.Build()); .Build());
} }
@@ -834,7 +834,7 @@ public sealed class TypeChecker
throw new CompileException(Diagnostic throw new CompileException(Diagnostic
.Error($"Module {expression.ModuleToken.Value} does not export a member named {expression.NameToken.Value}") .Error($"Module {expression.ModuleToken.Value} does not export a member named {expression.NameToken.Value}")
.At(expression) .At(expression, _syntaxTree.Tokens)
.Build()); .Build());
} }
} }
@@ -909,7 +909,7 @@ public sealed class TypeChecker
{ {
throw new CompileException(Diagnostic throw new CompileException(Diagnostic
.Error($"Struct {target.Type} does not have a field with the name {expression.MemberToken.Value}") .Error($"Struct {target.Type} does not have a field with the name {expression.MemberToken.Value}")
.At(expression) .At(expression, _syntaxTree.Tokens)
.Build()); .Build());
} }
@@ -919,7 +919,7 @@ public sealed class TypeChecker
{ {
throw new CompileException(Diagnostic throw new CompileException(Diagnostic
.Error($"Cannot access struct member {expression.MemberToken.Value} on type {target.Type}") .Error($"Cannot access struct member {expression.MemberToken.Value} on type {target.Type}")
.At(expression) .At(expression, _syntaxTree.Tokens)
.Build()); .Build());
} }
} }
@@ -950,7 +950,7 @@ public sealed class TypeChecker
throw new CompileException(Diagnostic throw new CompileException(Diagnostic
.Error("Cannot get implicit type of struct") .Error("Cannot get implicit type of struct")
.WithHelp("Specify struct type with struct {type_name} syntax") .WithHelp("Specify struct type with struct {type_name} syntax")
.At(expression) .At(expression, _syntaxTree.Tokens)
.Build()); .Build());
} }
@@ -963,7 +963,7 @@ public sealed class TypeChecker
{ {
Diagnostics.AddRange(Diagnostic Diagnostics.AddRange(Diagnostic
.Error($"Struct {structType.Name} does not have a field named {initializer.Key}") .Error($"Struct {structType.Name} does not have a field named {initializer.Key}")
.At(initializer.Value) .At(initializer.Value, _syntaxTree.Tokens)
.Build()); .Build());
continue; continue;
@@ -981,7 +981,7 @@ public sealed class TypeChecker
{ {
Diagnostics.Add(Diagnostic Diagnostics.Add(Diagnostic
.Warning($"Fields {string.Join(", ", missingFields)} are not initialized") .Warning($"Fields {string.Join(", ", missingFields)} are not initialized")
.At(expression) .At(expression, _syntaxTree.Tokens)
.Build()); .Build());
} }
@@ -1065,7 +1065,7 @@ public sealed class TypeChecker
throw new CompileException(Diagnostic throw new CompileException(Diagnostic
.Error($"Type {customType.NameToken.Value} not found in module {module.Name}") .Error($"Type {customType.NameToken.Value} not found in module {module.Name}")
.At(customType) .At(customType, _syntaxTree.Tokens)
.Build()); .Build());
} }

View File

@@ -11,6 +11,7 @@ public class Diagnostic
private readonly string _message; private readonly string _message;
private SourceSpan? _span; private SourceSpan? _span;
private string? _help; private string? _help;
private List<Token>? _tokens;
public DiagnosticBuilder(DiagnosticSeverity severity, string message) public DiagnosticBuilder(DiagnosticSeverity severity, string message)
{ {
@@ -18,18 +19,32 @@ public class Diagnostic
_message = message; _message = message;
} }
public DiagnosticBuilder At(SyntaxNode? node) public DiagnosticBuilder At(SyntaxNode? node, List<Token>? tokens = null)
{ {
if (tokens != null)
{
_tokens = tokens;
}
if (node != null) if (node != null)
{ {
_span = SourceSpan.Merge(node.Tokens.Select(x => x.Span)); var first = node.Tokens.FirstOrDefault();
if (first != null)
{
_span = SourceSpan.Merge(node.Tokens.Select(x => x.Span));
}
} }
return this; return this;
} }
public DiagnosticBuilder At(Token? token) public DiagnosticBuilder At(Token? token, List<Token>? tokens = null)
{ {
if (tokens != null)
{
_tokens = tokens;
}
if (token != null) if (token != null)
{ {
At(token.Span); At(token.Span);
@@ -48,11 +63,11 @@ public class Diagnostic
return this; return this;
} }
public DiagnosticBuilder At(string filePath, int line, int column) // public DiagnosticBuilder At(string filePath, int line, int column)
{ // {
_span = new SourceSpan(filePath, new SourceLocation(line, column), new SourceLocation(line, column)); // _span = new SourceSpan(filePath, new SourceLocation(line, column), new SourceLocation(line, column));
return this; // return this;
} // }
public DiagnosticBuilder WithHelp(string help) public DiagnosticBuilder WithHelp(string help)
{ {
@@ -60,20 +75,23 @@ public class Diagnostic
return this; return this;
} }
public Diagnostic Build() => new(_severity, _message, _help, _span); public Diagnostic Build() => new(_severity, _message, _help, _span, _tokens);
} }
public static DiagnosticBuilder Error(string message) => new(DiagnosticSeverity.Error, message); public static DiagnosticBuilder Error(string message) => new(DiagnosticSeverity.Error, message);
public static DiagnosticBuilder Warning(string message) => new(DiagnosticSeverity.Warning, message); public static DiagnosticBuilder Warning(string message) => new(DiagnosticSeverity.Warning, message);
public static DiagnosticBuilder Info(string message) => new(DiagnosticSeverity.Info, message); public static DiagnosticBuilder Info(string message) => new(DiagnosticSeverity.Info, message);
private readonly List<Token>? _tokens;
public DiagnosticSeverity Severity { get; } public DiagnosticSeverity Severity { get; }
public string Message { get; } public string Message { get; }
public string? Help { get; } public string? Help { get; }
public SourceSpan? Span { get; } public SourceSpan? Span { get; }
private Diagnostic(DiagnosticSeverity severity, string message, string? help, SourceSpan? span) private Diagnostic(DiagnosticSeverity severity, string message, string? help, SourceSpan? span, List<Token>? tokens)
{ {
_tokens = tokens;
Severity = severity; Severity = severity;
Message = message; Message = message;
Help = help; Help = help;
@@ -103,15 +121,12 @@ public class Diagnostic
if (Span.HasValue) if (Span.HasValue)
{ {
sb.AppendLine(); sb.AppendLine();
var text = File.ReadAllText(Span.Value.FilePath); var text = Span.Value.Source;
var tokenizer = new Tokenizer();
var tokens = tokenizer.Tokenize(Span.Value.FilePath, text);
var lines = text.Split('\n'); var lines = text.Split('\n');
var startLine = Span.Value.Start.Line; var startLine = Span.Value.StartLine;
var endLine = Span.Value.End.Line; var endLine = Span.Value.EndLine;
const int CONTEXT_LINES = 3; const int CONTEXT_LINES = 3;
@@ -144,8 +159,15 @@ public class Diagnostic
sb.Append("│ "); sb.Append("│ ");
sb.Append(i.ToString().PadRight(numberPadding)); sb.Append(i.ToString().PadRight(numberPadding));
sb.Append(" │ "); sb.Append(" │ ");
sb.Append(ApplySyntaxHighlighting(line.PadRight(codePadding), i, tokens)); if (_tokens != null)
// sb.Append(line.PadRight(codePadding)); {
sb.Append(ApplySyntaxHighlighting(line.PadRight(codePadding), i, _tokens));
}
else
{
sb.Append(line.PadRight(codePadding));
}
sb.Append(" │"); sb.Append(" │");
sb.AppendLine(); sb.AppendLine();
@@ -156,12 +178,12 @@ public class Diagnostic
if (i == startLine) if (i == startLine)
{ {
markerStartColumn = Span.Value.Start.Column; markerStartColumn = Span.Value.StartColumn;
} }
if (i == endLine) if (i == endLine)
{ {
markerEndColumn = Span.Value.End.Column; markerEndColumn = Span.Value.EndColumn;
} }
var markerLength = markerEndColumn - markerStartColumn; var markerLength = markerEndColumn - markerStartColumn;
@@ -206,8 +228,8 @@ public class Diagnostic
{ {
var sb = new StringBuilder(); var sb = new StringBuilder();
var lineTokens = tokens var lineTokens = tokens
.Where(t => t.Span.Start.Line == lineNumber) .Where(t => t.Span.StartLine == lineNumber)
.OrderBy(t => t.Span.Start.Column) .OrderBy(t => t.Span.StartColumn)
.ToList(); .ToList();
if (lineTokens.Count == 0) if (lineTokens.Count == 0)
@@ -219,8 +241,10 @@ public class Diagnostic
foreach (var token in lineTokens) foreach (var token in lineTokens)
{ {
var tokenStart = token.Span.Start.Column; if (token is WhitespaceToken) continue;
var tokenEnd = token.Span.End.Column;
var tokenStart = token.Span.StartColumn;
var tokenEnd = token.Span.EndColumn;
if (tokenStart > currentColumn && currentColumn - 1 < line.Length) if (tokenStart > currentColumn && currentColumn - 1 < line.Length)
{ {

View File

@@ -1,112 +1,56 @@
namespace NubLang.Diagnostics; namespace NubLang.Diagnostics;
public readonly struct SourceSpan : IEquatable<SourceSpan>, IComparable<SourceSpan> public readonly struct SourceSpan
{ {
private readonly int _startIndex;
private readonly int _endIndex;
public static SourceSpan Merge(params IEnumerable<SourceSpan> spans) public static SourceSpan Merge(params IEnumerable<SourceSpan> spans)
{ {
var spanArray = spans as SourceSpan[] ?? spans.ToArray(); var spanArray = spans as SourceSpan[] ?? spans.ToArray();
if (spanArray.Length == 0) if (spanArray.Length == 0)
{ {
return new SourceSpan(string.Empty, new SourceLocation(0, 0), new SourceLocation(0, 0)); return new SourceSpan(string.Empty, string.Empty, 0, 0, 0, 0, 0, 0);
} }
var minStart = spanArray.Min(s => s.Start); var first = spanArray.MinBy(x => x._startIndex);
var maxEnd = spanArray.Max(s => s.End); var last = spanArray.MaxBy(x => x._endIndex);
return new SourceSpan(spanArray[0].FilePath, minStart, maxEnd); return new SourceSpan(first.SourcePath, first.Source, first._startIndex, last._endIndex, first.StartLine, last.EndLine, first.StartColumn, last.EndColumn);
} }
public SourceSpan(string filePath, SourceLocation start, SourceLocation end) public SourceSpan(string sourcePath, string source, int startIndex, int endIndex, int startLine, int startColumn, int endLine, int endColumn)
{ {
if (start > end) _startIndex = startIndex;
{ _endIndex = endIndex;
throw new ArgumentException("Start location cannot be after end location"); SourcePath = sourcePath;
} Source = source;
StartLine = startLine;
FilePath = filePath; StartColumn = startColumn;
Start = start; EndLine = endLine;
End = end; EndColumn = endColumn;
} }
public string FilePath { get; } public int StartLine { get; }
public SourceLocation Start { get; } public int StartColumn { get; }
public SourceLocation End { get; } public int EndLine { get; }
public int EndColumn { get; }
public string SourcePath { get; }
public string Source { get; }
public override string ToString() public override string ToString()
{ {
if (Start == End) if (StartLine == EndLine && StartColumn == EndColumn)
{ {
return $"{FilePath}:{Start}"; return $"{SourcePath}:{StartColumn}:{StartColumn}";
} }
if (Start.Line == End.Line) if (StartLine == EndLine)
{ {
return Start.Column == End.Column ? $"{FilePath}:{Start}" : $"{FilePath}:{Start.Line}:{Start.Column}-{End.Column}"; return $"{SourcePath}:{StartLine}:{StartColumn}-{EndColumn}";
} }
return $"{FilePath}:{Start}-{End}"; return $"{SourcePath}:{StartLine}:{StartColumn}-{EndLine}:{EndColumn}";
}
public bool Equals(SourceSpan other) => Start == other.Start && End == other.End;
public override bool Equals(object? obj) => obj is SourceSpan other && Equals(other);
public override int GetHashCode() => HashCode.Combine(typeof(SourceSpan), Start, End);
public static bool operator ==(SourceSpan left, SourceSpan right) => Equals(left, right);
public static bool operator !=(SourceSpan left, SourceSpan right) => !Equals(left, right);
public static bool operator <(SourceSpan left, SourceSpan right) => left.CompareTo(right) < 0;
public static bool operator <=(SourceSpan left, SourceSpan right) => left.CompareTo(right) <= 0;
public static bool operator >(SourceSpan left, SourceSpan right) => left.CompareTo(right) > 0;
public static bool operator >=(SourceSpan left, SourceSpan right) => left.CompareTo(right) >= 0;
public int CompareTo(SourceSpan other)
{
var startComparison = Start.CompareTo(other.Start);
return startComparison != 0 ? startComparison : End.CompareTo(other.End);
}
}
public readonly struct SourceLocation : IEquatable<SourceLocation>, IComparable<SourceLocation>
{
public SourceLocation(int line, int column)
{
Line = line;
Column = column;
}
public int Line { get; }
public int Column { get; }
public override string ToString()
{
return $"{Line}:{Column}";
}
public override bool Equals(object? obj)
{
return obj is SourceLocation other && Equals(other);
}
public bool Equals(SourceLocation other)
{
return Line == other.Line && Column == other.Column;
}
public override int GetHashCode()
{
return HashCode.Combine(typeof(SourceLocation), Line, Column);
}
public static bool operator ==(SourceLocation left, SourceLocation right) => Equals(left, right);
public static bool operator !=(SourceLocation left, SourceLocation right) => !Equals(left, right);
public static bool operator <(SourceLocation left, SourceLocation right) => left.Line < right.Line || (left.Line == right.Line && left.Column < right.Column);
public static bool operator >(SourceLocation left, SourceLocation right) => left.Line > right.Line || (left.Line == right.Line && left.Column > right.Column);
public static bool operator <=(SourceLocation left, SourceLocation right) => left.Line <= right.Line || (left.Line == right.Line && left.Column <= right.Column);
public static bool operator >=(SourceLocation left, SourceLocation right) => left.Line >= right.Line || (left.Line == right.Line && left.Column >= right.Column);
public int CompareTo(SourceLocation other)
{
var lineComparison = Line.CompareTo(other.Line);
return lineComparison != 0 ? lineComparison : Column.CompareTo(other.Column);
} }
} }

View File

@@ -62,7 +62,7 @@ public sealed class Parser
_ => throw new CompileException(Diagnostic _ => throw new CompileException(Diagnostic
.Error($"Expected 'func', 'struct', 'enum', 'import' or 'module' but found '{keyword.Symbol}'") .Error($"Expected 'func', 'struct', 'enum', 'import' or 'module' but found '{keyword.Symbol}'")
.WithHelp("Valid top level statements are 'func', 'struct', 'enum', 'import' and 'module'") .WithHelp("Valid top level statements are 'func', 'struct', 'enum', 'import' and 'module'")
.At(keyword) .At(keyword, _tokens)
.Build()) .Build())
}; };
@@ -83,7 +83,7 @@ public sealed class Parser
} }
} }
return new SyntaxTree(topLevelSyntaxNodes); return new SyntaxTree(topLevelSyntaxNodes, _tokens);
} }
private ModuleSyntax ParseModule(int startIndex) private ModuleSyntax ParseModule(int startIndex)
@@ -190,7 +190,7 @@ public sealed class Parser
{ {
throw new CompileException(Diagnostic throw new CompileException(Diagnostic
.Error("Value of enum field must be an integer literal") .Error("Value of enum field must be an integer literal")
.At(CurrentToken) .At(CurrentToken, _tokens)
.Build()); .Build());
} }
@@ -461,7 +461,7 @@ public sealed class Parser
IdentifierToken identifier => ParseIdentifier(startIndex, identifier), IdentifierToken identifier => ParseIdentifier(startIndex, identifier),
SymbolToken symbolToken => symbolToken.Symbol switch SymbolToken symbolToken => symbolToken.Symbol switch
{ {
Symbol.Ampersand => ParseAddressOf(startIndex), Symbol.Caret => ParseAddressOf(startIndex),
Symbol.OpenParen => ParseParenthesizedExpression(), Symbol.OpenParen => ParseParenthesizedExpression(),
Symbol.Minus => ParseUnaryNegate(startIndex), Symbol.Minus => ParseUnaryNegate(startIndex),
Symbol.Bang => ParseUnaryInvert(startIndex), Symbol.Bang => ParseUnaryInvert(startIndex),
@@ -472,13 +472,13 @@ public sealed class Parser
_ => throw new CompileException(Diagnostic _ => throw new CompileException(Diagnostic
.Error($"Unexpected symbol '{symbolToken.Symbol}' in expression") .Error($"Unexpected symbol '{symbolToken.Symbol}' in expression")
.WithHelp("Expected '(', '-', '!', '[' or '{'") .WithHelp("Expected '(', '-', '!', '[' or '{'")
.At(symbolToken) .At(symbolToken, _tokens)
.Build()) .Build())
}, },
_ => throw new CompileException(Diagnostic _ => throw new CompileException(Diagnostic
.Error($"Unexpected token '{token.GetType().Name}' in expression") .Error($"Unexpected token '{token.GetType().Name}' in expression")
.WithHelp("Expected literal, identifier, or parenthesized expression") .WithHelp("Expected literal, identifier, or parenthesized expression")
.At(token) .At(token, _tokens)
.Build()) .Build())
}; };
@@ -698,7 +698,7 @@ public sealed class Parser
throw new CompileException(Diagnostic throw new CompileException(Diagnostic
.Error("Arbitrary uint size is not supported") .Error("Arbitrary uint size is not supported")
.WithHelp("Use u8, u16, u32 or u64") .WithHelp("Use u8, u16, u32 or u64")
.At(name) .At(name, _tokens)
.Build()); .Build());
} }
@@ -712,7 +712,7 @@ public sealed class Parser
throw new CompileException(Diagnostic throw new CompileException(Diagnostic
.Error("Arbitrary int size is not supported") .Error("Arbitrary int size is not supported")
.WithHelp("Use i8, i16, i32 or i64") .WithHelp("Use i8, i16, i32 or i64")
.At(name) .At(name, _tokens)
.Build()); .Build());
} }
@@ -726,7 +726,7 @@ public sealed class Parser
throw new CompileException(Diagnostic throw new CompileException(Diagnostic
.Error("Arbitrary float size is not supported") .Error("Arbitrary float size is not supported")
.WithHelp("Use f32 or f64") .WithHelp("Use f32 or f64")
.At(name) .At(name, _tokens)
.Build()); .Build());
} }
@@ -810,7 +810,7 @@ public sealed class Parser
throw new CompileException(Diagnostic throw new CompileException(Diagnostic
.Error("Invalid type syntax") .Error("Invalid type syntax")
.WithHelp("Expected type name, '^' for pointer, or '[]' for array") .WithHelp("Expected type name, '^' for pointer, or '[]' for array")
.At(CurrentToken) .At(CurrentToken, _tokens)
.Build()); .Build());
} }
@@ -821,7 +821,7 @@ public sealed class Parser
throw new CompileException(Diagnostic throw new CompileException(Diagnostic
.Error("Unexpected end of file") .Error("Unexpected end of file")
.WithHelp("Expected more tokens to complete the syntax") .WithHelp("Expected more tokens to complete the syntax")
.At(_tokens[^1]) .At(_tokens[^1], _tokens)
.Build()); .Build());
} }
@@ -838,7 +838,7 @@ public sealed class Parser
throw new CompileException(Diagnostic throw new CompileException(Diagnostic
.Error($"Expected symbol, but found {token.GetType().Name}") .Error($"Expected symbol, but found {token.GetType().Name}")
.WithHelp("This position requires a symbol like '(', ')', '{', '}', etc.") .WithHelp("This position requires a symbol like '(', ')', '{', '}', etc.")
.At(token) .At(token, _tokens)
.Build()); .Build());
} }
@@ -853,7 +853,7 @@ public sealed class Parser
throw new CompileException(Diagnostic throw new CompileException(Diagnostic
.Error($"Expected '{expectedSymbol}', but found '{token.Symbol}'") .Error($"Expected '{expectedSymbol}', but found '{token.Symbol}'")
.WithHelp($"Insert '{expectedSymbol}' here") .WithHelp($"Insert '{expectedSymbol}' here")
.At(token) .At(token, _tokens)
.Build()); .Build());
} }
} }
@@ -903,7 +903,7 @@ public sealed class Parser
throw new CompileException(Diagnostic throw new CompileException(Diagnostic
.Error($"Expected identifier, but found {token.GetType().Name}") .Error($"Expected identifier, but found {token.GetType().Name}")
.WithHelp("Provide a valid identifier name here") .WithHelp("Provide a valid identifier name here")
.At(token) .At(token, _tokens)
.Build()); .Build());
} }
@@ -931,7 +931,7 @@ public sealed class Parser
throw new CompileException(Diagnostic throw new CompileException(Diagnostic
.Error($"Expected string literal, but found {token.GetType().Name}") .Error($"Expected string literal, but found {token.GetType().Name}")
.WithHelp("Provide a valid string literal") .WithHelp("Provide a valid string literal")
.At(token) .At(token, _tokens)
.Build()); .Build());
} }
@@ -953,4 +953,4 @@ public sealed class Parser
} }
} }
public record SyntaxTree(List<TopLevelSyntaxNode> TopLevelSyntaxNodes); public record SyntaxTree(List<TopLevelSyntaxNode> TopLevelSyntaxNodes, List<Token> Tokens);

View File

@@ -43,6 +43,7 @@ public sealed class Tokenizer
private Token ParseToken() private Token ParseToken()
{ {
var indexStart = _index;
var lineStart = _line; var lineStart = _line;
var columnStart = _column; var columnStart = _column;
@@ -53,7 +54,7 @@ public sealed class Tokenizer
Next(); Next();
} }
return new WhitespaceToken(CreateSpan(lineStart, columnStart)); return new WhitespaceToken(CreateSpan(indexStart, lineStart, columnStart));
} }
if (_content[_index] == '/' && _index + 1 < _content.Length && _content[_index + 1] == '/') if (_content[_index] == '/' && _index + 1 < _content.Length && _content[_index + 1] == '/')
@@ -66,23 +67,23 @@ public sealed class Tokenizer
Next(); Next();
} }
return new CommentToken(CreateSpan(lineStart, columnStart), _content.AsSpan(startIndex, _index - startIndex).ToString()); return new CommentToken(CreateSpan(indexStart, lineStart, columnStart), _content.AsSpan(startIndex, _index - startIndex).ToString());
} }
if (char.IsDigit(_content[_index])) if (char.IsDigit(_content[_index]))
{ {
return ParseNumber(lineStart, columnStart); return ParseNumber(indexStart, lineStart, columnStart);
} }
if (_content[_index] == '"') if (_content[_index] == '"')
{ {
return ParseString(lineStart, columnStart); return ParseString(indexStart, lineStart, columnStart);
} }
// note(nub31): Look for keywords (longest first in case a keyword fits partially in a larger keyword) // note(nub31): Look for keywords (longest first in case a keyword fits partially in a larger keyword)
for (var i = 8; i >= 1; i--) for (var i = 8; i >= 1; i--)
{ {
if (TryMatchSymbol(i, lineStart, columnStart, out var token)) if (TryMatchSymbol(i, indexStart, lineStart, columnStart, out var token))
{ {
return token; return token;
} }
@@ -90,13 +91,13 @@ public sealed class Tokenizer
if (char.IsLetter(_content[_index]) || _content[_index] == '_') if (char.IsLetter(_content[_index]) || _content[_index] == '_')
{ {
return ParseIdentifier(lineStart, columnStart); return ParseIdentifier(indexStart, lineStart, columnStart);
} }
throw new CompileException(Diagnostic.Error($"Unknown token '{_content[_index]}'").Build()); throw new CompileException(Diagnostic.Error($"Unknown token '{_content[_index]}'").Build());
} }
private Token ParseNumber(int lineStart, int columnStart) private Token ParseNumber(int indexStart, int lineStart, int columnStart)
{ {
var start = _index; var start = _index;
var current = _content[_index]; var current = _content[_index];
@@ -116,12 +117,12 @@ public sealed class Tokenizer
{ {
throw new CompileException(Diagnostic throw new CompileException(Diagnostic
.Error("Invalid hex literal, no digits found") .Error("Invalid hex literal, no digits found")
.At(_fileName, _line, _column) .At(CreateSpan(_index, _line, _column))
.Build()); .Build());
} }
return new IntLiteralToken( return new IntLiteralToken(
CreateSpan(lineStart, columnStart), CreateSpan(indexStart, lineStart, columnStart),
_content.Substring(start, _index - start), _content.Substring(start, _index - start),
16); 16);
} }
@@ -141,12 +142,12 @@ public sealed class Tokenizer
{ {
throw new CompileException(Diagnostic throw new CompileException(Diagnostic
.Error("Invalid binary literal, no digits found") .Error("Invalid binary literal, no digits found")
.At(_fileName, _line, _column) .At(CreateSpan(_index, _line, _column))
.Build()); .Build());
} }
return new IntLiteralToken( return new IntLiteralToken(
CreateSpan(lineStart, columnStart), CreateSpan(indexStart, lineStart, columnStart),
_content.Substring(start, _index - start), _content.Substring(start, _index - start),
2); 2);
} }
@@ -163,7 +164,7 @@ public sealed class Tokenizer
{ {
throw new CompileException(Diagnostic throw new CompileException(Diagnostic
.Error("More than one period found in float literal") .Error("More than one period found in float literal")
.At(_fileName, _line, _column) .At(CreateSpan(_index, _line, _column))
.Build()); .Build());
} }
@@ -183,11 +184,11 @@ public sealed class Tokenizer
var buffer = _content.Substring(start, _index - start); var buffer = _content.Substring(start, _index - start);
return isFloat return isFloat
? new FloatLiteralToken(CreateSpan(lineStart, columnStart), buffer) ? new FloatLiteralToken(CreateSpan(indexStart, lineStart, columnStart), buffer)
: new IntLiteralToken(CreateSpan(lineStart, columnStart), buffer, 10); : new IntLiteralToken(CreateSpan(indexStart, lineStart, columnStart), buffer, 10);
} }
private StringLiteralToken ParseString(int lineStart, int columnStart) private StringLiteralToken ParseString(int indexStart, int lineStart, int columnStart)
{ {
Next(); Next();
var start = _index; var start = _index;
@@ -198,7 +199,7 @@ public sealed class Tokenizer
{ {
throw new CompileException(Diagnostic throw new CompileException(Diagnostic
.Error("Unclosed string literal") .Error("Unclosed string literal")
.At(_fileName, _line, _column) .At(CreateSpan(_index, _line, _column))
.Build()); .Build());
} }
@@ -208,7 +209,7 @@ public sealed class Tokenizer
{ {
throw new CompileException(Diagnostic throw new CompileException(Diagnostic
.Error("Unclosed string literal (newline found)") .Error("Unclosed string literal (newline found)")
.At(_fileName, _line, _column) .At(CreateSpan(_index, _line, _column))
.Build()); .Build());
} }
@@ -216,14 +217,14 @@ public sealed class Tokenizer
{ {
var buffer = _content.Substring(start, _index - start); var buffer = _content.Substring(start, _index - start);
Next(); Next();
return new StringLiteralToken(CreateSpan(lineStart, columnStart), buffer); return new StringLiteralToken(CreateSpan(indexStart, lineStart, columnStart), buffer);
} }
Next(); Next();
} }
} }
private bool TryMatchSymbol(int length, int lineStart, int columnStart, out Token token) private bool TryMatchSymbol(int length, int indexStart, int lineStart, int columnStart, out Token token)
{ {
token = null!; token = null!;
@@ -237,14 +238,14 @@ public sealed class Tokenizer
if (span is "true") if (span is "true")
{ {
Next(4); Next(4);
token = new BoolLiteralToken(CreateSpan(lineStart, columnStart), true); token = new BoolLiteralToken(CreateSpan(indexStart, lineStart, columnStart), true);
return true; return true;
} }
if (span is "false") if (span is "false")
{ {
Next(5); Next(5);
token = new BoolLiteralToken(CreateSpan(lineStart, columnStart), false); token = new BoolLiteralToken(CreateSpan(indexStart, lineStart, columnStart), false);
return true; return true;
} }
@@ -349,14 +350,14 @@ public sealed class Tokenizer
} }
Next(length); Next(length);
token = new SymbolToken(CreateSpan(lineStart, columnStart), symbol); token = new SymbolToken(CreateSpan(indexStart, lineStart, columnStart), symbol);
return true; return true;
} }
return false; return false;
} }
private IdentifierToken ParseIdentifier(int lineStart, int columnStart) private IdentifierToken ParseIdentifier(int indexStart, int lineStart, int columnStart)
{ {
var start = _index; var start = _index;
@@ -373,12 +374,12 @@ public sealed class Tokenizer
} }
} }
return new IdentifierToken(CreateSpan(lineStart, columnStart), _content.Substring(start, _index - start)); return new IdentifierToken(CreateSpan(indexStart, lineStart, columnStart), _content.Substring(start, _index - start));
} }
private SourceSpan CreateSpan(int lineStart, int columnStart) private SourceSpan CreateSpan(int indexStart, int lineStart, int columnStart)
{ {
return new SourceSpan(_fileName, new SourceLocation(lineStart, columnStart), new SourceLocation(_line, _column)); return new SourceSpan(_fileName, _content, indexStart, Math.Min(_index, _content.Length), lineStart, columnStart, _line, _column);
} }
private void Next(int count = 1) private void Next(int count = 1)