...
This commit is contained in:
177
src/compiler/Syntax/Diagnostics/ConsoleColors.cs
Normal file
177
src/compiler/Syntax/Diagnostics/ConsoleColors.cs
Normal file
@@ -0,0 +1,177 @@
|
||||
using System.Text;
|
||||
using Syntax.Tokenization;
|
||||
|
||||
namespace Syntax.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 DocumentationToken:
|
||||
return Faint;
|
||||
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.Semicolon:
|
||||
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);
|
||||
}
|
||||
}
|
||||
216
src/compiler/Syntax/Diagnostics/Diagnostic.cs
Normal file
216
src/compiler/Syntax/Diagnostics/Diagnostic.cs
Normal file
@@ -0,0 +1,216 @@
|
||||
using System.Text;
|
||||
using Syntax.Parsing;
|
||||
using Syntax.Parsing.Node;
|
||||
using Syntax.Tokenization;
|
||||
|
||||
namespace Syntax.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
|
||||
}
|
||||
Reference in New Issue
Block a user