Error messages complete

This commit is contained in:
nub31
2025-05-25 01:27:11 +02:00
parent fac2c95d05
commit e2b868c442
3 changed files with 106 additions and 79 deletions

View File

@@ -38,7 +38,7 @@ public class Diagnostic
{ {
if (!node.Tokens.Any()) if (!node.Tokens.Any())
{ {
throw new ArgumentException("Node has no tokens"); throw new ArgumentException("Node has no tokens", nameof(node));
} }
_sourceFile = node.Tokens[0].SourceFile; _sourceFile = node.Tokens[0].SourceFile;
@@ -85,22 +85,15 @@ public class Diagnostic
{ {
var sb = new StringBuilder(); var sb = new StringBuilder();
var severityText = Severity switch var severityText = GetSeverityText(Severity);
{
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),
_ => "diagnostic"
};
sb.Append(severityText); sb.Append(severityText);
if (SourceFile.HasValue) if (SourceFile.HasValue)
{ {
var locationText = $" at {SourceFile.Value.Path}:{Span}"; var locationText = $" at {SourceFile.Value.Path}:{Span}";
sb.Append(ConsoleColors.Colorize(locationText, ConsoleColors.Gray)); sb.Append(ConsoleColors.Colorize(locationText, ConsoleColors.Gray));
} }
sb.Append(": "); sb.Append(": ");
sb.AppendLine(ConsoleColors.Colorize(Message, ConsoleColors.BrightWhite)); sb.AppendLine(ConsoleColors.Colorize(Message, ConsoleColors.BrightWhite));
@@ -119,59 +112,72 @@ public class Diagnostic
return sb.ToString(); return sb.ToString();
} }
private void AppendSourceContext(StringBuilder sb, SourceFile sourceFile, SourceSpan span) 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, SourceFile sourceFile, SourceSpan span)
{ {
var lines = sourceFile.Content.Split('\n'); var lines = sourceFile.Content.Split('\n');
var startLine = span.Start.Line; var startLine = span.Start.Line;
var endLine = span.End.Line; var endLine = span.End.Line;
const int CONTEXT_LINES = 3; const int contextLines = 3;
var maxLineNum = Math.Min(endLine + CONTEXT_LINES, lines.Length); if (startLine < 1 || startLine > lines.Length || endLine < 1 || endLine > lines.Length)
var lineNumWidth = maxLineNum.ToString().Length;
var contextStart = Math.Max(1, startLine - CONTEXT_LINES);
for (var lineNum = contextStart; lineNum < startLine; lineNum++)
{ {
if (lineNum <= lines.Length) return;
{
AppendContextLine(sb, lineNum, lines[lineNum - 1], lineNumWidth);
}
} }
for (var lineNum = startLine; lineNum <= endLine && lineNum <= lines.Length; lineNum++) var maxLineNum = Math.Min(endLine + contextLines, lines.Length);
var lineNumWidth = maxLineNum.ToString().Length;
var contextStart = Math.Max(1, startLine - contextLines);
for (var lineNum = contextStart; lineNum < startLine; lineNum++)
{
AppendContextLine(sb, lineNum, lines[lineNum - 1], lineNumWidth);
}
for (var lineNum = startLine; lineNum <= endLine; lineNum++)
{ {
var line = lines[lineNum - 1]; var line = lines[lineNum - 1];
AppendContextLine(sb, lineNum, line, lineNumWidth); AppendContextLine(sb, lineNum, line, lineNumWidth);
AppendErrorIndicators(sb, span, lineNum, line, lineNumWidth); AppendErrorIndicators(sb, span, lineNum, line, lineNumWidth);
} }
var contextEnd = Math.Min(lines.Length, endLine + CONTEXT_LINES); var contextEnd = Math.Min(lines.Length, endLine + contextLines);
for (var lineNum = endLine + 1; lineNum <= contextEnd; lineNum++) for (var lineNum = endLine + 1; lineNum <= contextEnd; lineNum++)
{ {
if (lineNum <= lines.Length) AppendContextLine(sb, lineNum, lines[lineNum - 1], lineNumWidth);
{
AppendContextLine(sb, lineNum, lines[lineNum - 1], lineNumWidth);
}
} }
} }
private void AppendContextLine(StringBuilder sb, int lineNum, string line, int lineNumWidth) private static void AppendContextLine(StringBuilder sb, int lineNum, string line, int lineNumWidth)
{ {
var lineNumStr = lineNum.ToString().PadLeft(lineNumWidth); var lineNumStr = lineNum.ToString().PadLeft(lineNumWidth);
sb.Append(ConsoleColors.Colorize(lineNumStr, ConsoleColors.Gray)); sb.Append(ConsoleColors.Colorize(lineNumStr, ConsoleColors.Gray));
sb.Append(ConsoleColors.Colorize(" | ", ConsoleColors.Gray)); sb.Append(ConsoleColors.Colorize(" | ", ConsoleColors.Gray));
sb.AppendLine(line); sb.AppendLine(line);
} }
private void AppendErrorIndicators(StringBuilder sb, SourceSpan span, int lineNum, string line, int lineNumWidth) private static void AppendErrorIndicators(StringBuilder sb, SourceSpan span, int lineNum, string line, int lineNumWidth)
{ {
sb.Append(new string(' ', lineNumWidth + 3)); sb.Append(new string(' ', lineNumWidth + 3));
var indicators = GetIndicatorsForLine(span, lineNum, line);
sb.AppendLine(ConsoleColors.Colorize(indicators, ConsoleColors.Red));
}
const char ERROR_INDICATOR = '^'; private static string GetIndicatorsForLine(SourceSpan span, int lineNum, string line)
{
string indicators; const char indicator = '^';
if (lineNum == span.Start.Line && lineNum == span.End.Line) if (lineNum == span.Start.Line && lineNum == span.End.Line)
{ {
@@ -179,27 +185,21 @@ public class Diagnostic
var endCol = Math.Min(line.Length, span.End.Column - 1); var endCol = Math.Min(line.Length, span.End.Column - 1);
var length = Math.Max(1, endCol - startCol); var length = Math.Max(1, endCol - startCol);
var spaces = new string(' ', startCol); return new string(' ', startCol) + new string(indicator, length);
var carets = new string(ERROR_INDICATOR, length);
indicators = spaces + carets;
}
else if (lineNum == span.Start.Line)
{
var startCol = Math.Max(0, span.Start.Column - 1);
var spaces = new string(' ', startCol);
var carets = new string(ERROR_INDICATOR, line.Length - startCol);
indicators = spaces + carets;
}
else if (lineNum == span.End.Line)
{
var endCol = Math.Min(line.Length, span.End.Column - 1);
indicators = new string(ERROR_INDICATOR, endCol);
}
else
{
indicators = new string(ERROR_INDICATOR, line.Length);
} }
sb.AppendLine(ConsoleColors.Colorize(indicators, ConsoleColors.Red)); 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);
} }
} }

View File

@@ -1,3 +1,4 @@
using System.Diagnostics.CodeAnalysis;
using Nub.Lang.Frontend.Lexing; using Nub.Lang.Frontend.Lexing;
using Nub.Lang.Frontend.Parsing; using Nub.Lang.Frontend.Parsing;
@@ -9,13 +10,10 @@ public readonly struct SourceFile(string path, string content)
public string Content { get; } = content; public string Content { get; } = content;
} }
public readonly struct SourceLocation(int line, int column, int index) public readonly struct SourceLocation(int line, int column)
{ {
public int Line { get; } = line; public int Line { get; } = line;
public int Column { get; } = column; public int Column { get; } = column;
public int Index { get; } = index;
public override string ToString() => $"{Line}:{Column}";
} }
public readonly struct SourceSpan(SourceLocation start, SourceLocation end) public readonly struct SourceSpan(SourceLocation start, SourceLocation end)
@@ -23,35 +21,61 @@ public readonly struct SourceSpan(SourceLocation start, SourceLocation end)
public SourceLocation Start { get; } = start; public SourceLocation Start { get; } = start;
public SourceLocation End { get; } = end; public SourceLocation End { get; } = end;
public override string ToString() => $"{Start}-{End}"; public override string ToString()
{
if (Start.Line == End.Line)
{
if (Start.Column == End.Column)
{
return $"{Start.Line}:{Start.Column}";
}
return $"{Start.Line}:{Start.Column}-{End.Column}";
}
return $"{Start.Line}:{Start.Column}-{End.Line}:{End.Column}";
}
} }
public static class SourceLocationCalculator public static class SourceLocationCalculator
{ {
private static int[] GetLineStarts(string content)
{
var lineStarts = new List<int> { 0 };
for (var i = 0; i < content.Length; i++)
{
if (content[i] == '\n')
{
lineStarts.Add(i + 1);
}
}
return lineStarts.ToArray();
}
private static SourceLocation IndexToLocation(string content, int index) private static SourceLocation IndexToLocation(string content, int index)
{ {
if (index < 0 || index > content.Length) if (index < 0 || index > content.Length)
{ {
throw new ArgumentOutOfRangeException(nameof(index)); throw new ArgumentOutOfRangeException(nameof(index), $"Index {index} is out of range for content of length {content.Length}");
} }
var line = 1; var lineStarts = GetLineStarts(content);
var column = 1;
for (var i = 0; i < index && i < content.Length; i++) var line = Array.BinarySearch(lineStarts, index);
if (line < 0)
{ {
if (content[i] == '\n') line = ~line - 1;
{
line++;
column = 1;
}
else
{
column++;
}
} }
return new SourceLocation(line, column, index); if (line < lineStarts.Length - 1 && lineStarts[line + 1] == index && index < content.Length && content[index] == '\n')
{
line++;
}
var column = index - lineStarts[line] + 1;
return new SourceLocation(line + 1, column);
} }
public static SourceSpan GetSpan(Token token) public static SourceSpan GetSpan(Token token)
@@ -65,7 +89,7 @@ public static class SourceLocationCalculator
{ {
if (!node.Tokens.Any()) if (!node.Tokens.Any())
{ {
throw new ArgumentException("Node has no tokens"); throw new ArgumentException("Node has no tokens", nameof(node));
} }
var firstToken = node.Tokens[0]; var firstToken = node.Tokens[0];
@@ -76,4 +100,4 @@ public static class SourceLocationCalculator
return new SourceSpan(start, end); return new SourceSpan(start, end);
} }
} }

View File

@@ -569,9 +569,10 @@ public class Parser
if (!Peek().TryGetValue(out var token)) if (!Peek().TryGetValue(out var token))
{ {
throw new ParseException(Diagnostic.Error("Unexpected end of file while parsing type") throw new ParseException(Diagnostic
.At(_tokens.Last().SourceFile, SourceLocationCalculator.GetSpan(_tokens.Last())) .Error("Unexpected end of file while parsing type")
.WithHelp("Expected a type name") .WithHelp("Expected a type name")
.At(_tokens.Last().SourceFile, SourceLocationCalculator.GetSpan(_tokens.Last()))
.Build()); .Build());
} }
@@ -586,9 +587,10 @@ public class Parser
{ {
if (!Peek().TryGetValue(out var token)) if (!Peek().TryGetValue(out var token))
{ {
throw new ParseException(Diagnostic.Error("Unexpected end of file") throw new ParseException(Diagnostic
.At(_tokens.Last().SourceFile, SourceLocationCalculator.GetSpan(_tokens.Last())) .Error("Unexpected end of file")
.WithHelp("Expected more tokens to complete the syntax") .WithHelp("Expected more tokens to complete the syntax")
.At(_tokens.Last().SourceFile, SourceLocationCalculator.GetSpan(_tokens.Last()))
.Build()); .Build());
} }
@@ -669,6 +671,7 @@ public class Parser
throw new ParseException(Diagnostic throw new ParseException(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)
.Build()); .Build());
} }