Error messages complete
This commit is contained in:
@@ -38,7 +38,7 @@ public class Diagnostic
|
||||
{
|
||||
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;
|
||||
@@ -85,16 +85,9 @@ public class Diagnostic
|
||||
{
|
||||
var sb = new StringBuilder();
|
||||
|
||||
var severityText = 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),
|
||||
_ => "diagnostic"
|
||||
};
|
||||
|
||||
|
||||
var severityText = GetSeverityText(Severity);
|
||||
sb.Append(severityText);
|
||||
|
||||
if (SourceFile.HasValue)
|
||||
{
|
||||
var locationText = $" at {SourceFile.Value.Path}:{Span}";
|
||||
@@ -119,59 +112,72 @@ public class Diagnostic
|
||||
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 startLine = span.Start.Line;
|
||||
var endLine = span.End.Line;
|
||||
|
||||
const int CONTEXT_LINES = 3;
|
||||
const int contextLines = 3;
|
||||
|
||||
var maxLineNum = Math.Min(endLine + CONTEXT_LINES, lines.Length);
|
||||
var lineNumWidth = maxLineNum.ToString().Length;
|
||||
|
||||
var contextStart = Math.Max(1, startLine - CONTEXT_LINES);
|
||||
for (var lineNum = contextStart; lineNum < startLine; lineNum++)
|
||||
if (startLine < 1 || startLine > lines.Length || endLine < 1 || endLine > lines.Length)
|
||||
{
|
||||
if (lineNum <= lines.Length)
|
||||
{
|
||||
AppendContextLine(sb, lineNum, lines[lineNum - 1], lineNumWidth);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
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];
|
||||
AppendContextLine(sb, 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++)
|
||||
{
|
||||
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);
|
||||
|
||||
sb.Append(ConsoleColors.Colorize(lineNumStr, ConsoleColors.Gray));
|
||||
sb.Append(ConsoleColors.Colorize(" | ", ConsoleColors.Gray));
|
||||
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));
|
||||
var indicators = GetIndicatorsForLine(span, lineNum, line);
|
||||
sb.AppendLine(ConsoleColors.Colorize(indicators, ConsoleColors.Red));
|
||||
}
|
||||
|
||||
const char ERROR_INDICATOR = '^';
|
||||
|
||||
string indicators;
|
||||
private static string GetIndicatorsForLine(SourceSpan span, int lineNum, string line)
|
||||
{
|
||||
const char indicator = '^';
|
||||
|
||||
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 length = Math.Max(1, endCol - startCol);
|
||||
|
||||
var spaces = new string(' ', startCol);
|
||||
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);
|
||||
return new string(' ', startCol) + new string(indicator, 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);
|
||||
}
|
||||
}
|
||||
@@ -1,3 +1,4 @@
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using Nub.Lang.Frontend.Lexing;
|
||||
using Nub.Lang.Frontend.Parsing;
|
||||
|
||||
@@ -9,13 +10,10 @@ public readonly struct SourceFile(string path, string 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 Column { get; } = column;
|
||||
public int Index { get; } = index;
|
||||
|
||||
public override string ToString() => $"{Line}:{Column}";
|
||||
}
|
||||
|
||||
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 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
|
||||
{
|
||||
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)
|
||||
{
|
||||
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 column = 1;
|
||||
var lineStarts = GetLineStarts(content);
|
||||
|
||||
for (var i = 0; i < index && i < content.Length; i++)
|
||||
var line = Array.BinarySearch(lineStarts, index);
|
||||
if (line < 0)
|
||||
{
|
||||
if (content[i] == '\n')
|
||||
{
|
||||
line++;
|
||||
column = 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
column++;
|
||||
}
|
||||
line = ~line - 1;
|
||||
}
|
||||
|
||||
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)
|
||||
@@ -65,7 +89,7 @@ public static class SourceLocationCalculator
|
||||
{
|
||||
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];
|
||||
|
||||
@@ -569,9 +569,10 @@ public class Parser
|
||||
|
||||
if (!Peek().TryGetValue(out var token))
|
||||
{
|
||||
throw new ParseException(Diagnostic.Error("Unexpected end of file while parsing type")
|
||||
.At(_tokens.Last().SourceFile, SourceLocationCalculator.GetSpan(_tokens.Last()))
|
||||
throw new ParseException(Diagnostic
|
||||
.Error("Unexpected end of file while parsing type")
|
||||
.WithHelp("Expected a type name")
|
||||
.At(_tokens.Last().SourceFile, SourceLocationCalculator.GetSpan(_tokens.Last()))
|
||||
.Build());
|
||||
}
|
||||
|
||||
@@ -586,9 +587,10 @@ public class Parser
|
||||
{
|
||||
if (!Peek().TryGetValue(out var token))
|
||||
{
|
||||
throw new ParseException(Diagnostic.Error("Unexpected end of file")
|
||||
.At(_tokens.Last().SourceFile, SourceLocationCalculator.GetSpan(_tokens.Last()))
|
||||
throw new ParseException(Diagnostic
|
||||
.Error("Unexpected end of file")
|
||||
.WithHelp("Expected more tokens to complete the syntax")
|
||||
.At(_tokens.Last().SourceFile, SourceLocationCalculator.GetSpan(_tokens.Last()))
|
||||
.Build());
|
||||
}
|
||||
|
||||
@@ -669,6 +671,7 @@ public class Parser
|
||||
throw new ParseException(Diagnostic
|
||||
.Error($"Expected identifier, but found {token.GetType().Name}")
|
||||
.WithHelp("Provide a valid identifier name here")
|
||||
.At(token)
|
||||
.Build());
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user