154 lines
4.4 KiB
C#
154 lines
4.4 KiB
C#
namespace Compiler;
|
|
|
|
public sealed class Diagnostic(DiagnosticSeverity severity, string message, string? help, FileInfo? file)
|
|
{
|
|
public static DiagnosticBuilder Info(string message) => new DiagnosticBuilder(DiagnosticSeverity.Info, message);
|
|
public static DiagnosticBuilder Warning(string message) => new DiagnosticBuilder(DiagnosticSeverity.Warning, message);
|
|
public static DiagnosticBuilder Error(string message) => new DiagnosticBuilder(DiagnosticSeverity.Error, message);
|
|
|
|
public readonly DiagnosticSeverity Severity = severity;
|
|
public readonly string Message = message;
|
|
public readonly string? Help = help;
|
|
public readonly FileInfo? File = file;
|
|
}
|
|
|
|
public sealed class DiagnosticBuilder(DiagnosticSeverity severity, string message)
|
|
{
|
|
private FileInfo? file;
|
|
private string? help;
|
|
|
|
public DiagnosticBuilder At(string fileName, int line, int column, int length)
|
|
{
|
|
file = new FileInfo(fileName, line, column, length);
|
|
return this;
|
|
}
|
|
|
|
public DiagnosticBuilder At(string fileName, Token? token)
|
|
{
|
|
if (token != null)
|
|
{
|
|
At(fileName, token.Line, token.Column, token.Length);
|
|
}
|
|
|
|
return this;
|
|
}
|
|
|
|
public DiagnosticBuilder At(string fileName, Node? node)
|
|
{
|
|
if (node != null && node.Tokens.Count != 0)
|
|
{
|
|
// todo(nub31): Calculate length based on last token
|
|
At(fileName, node.Tokens[0]);
|
|
}
|
|
|
|
return this;
|
|
}
|
|
|
|
public DiagnosticBuilder WithHelp(string helpMessage)
|
|
{
|
|
help = helpMessage;
|
|
return this;
|
|
}
|
|
|
|
public Diagnostic Build()
|
|
{
|
|
return new Diagnostic(severity, message, help, file);
|
|
}
|
|
}
|
|
|
|
public sealed class FileInfo(string file, int line, int column, int length)
|
|
{
|
|
public readonly string File = file;
|
|
public readonly int Line = line;
|
|
public readonly int Column = column;
|
|
public readonly int Length = length;
|
|
}
|
|
|
|
public enum DiagnosticSeverity
|
|
{
|
|
Info,
|
|
Warning,
|
|
Error,
|
|
}
|
|
|
|
public sealed class CompileException(Diagnostic diagnostic) : Exception
|
|
{
|
|
public readonly Diagnostic Diagnostic = diagnostic;
|
|
}
|
|
|
|
public static class DiagnosticFormatter
|
|
{
|
|
public static void Print(Diagnostic diagnostic, TextWriter writer)
|
|
{
|
|
var (label, color) = diagnostic.Severity switch
|
|
{
|
|
DiagnosticSeverity.Info => ("info", Ansi.Cyan),
|
|
DiagnosticSeverity.Warning => ("warning", Ansi.Yellow),
|
|
DiagnosticSeverity.Error => ("error", Ansi.Red),
|
|
_ => ("unknown", Ansi.Reset),
|
|
};
|
|
|
|
writer.Write(color);
|
|
writer.Write(label);
|
|
writer.Write(Ansi.Reset);
|
|
writer.Write(": ");
|
|
writer.WriteLine(diagnostic.Message);
|
|
|
|
if (diagnostic.File is null)
|
|
return;
|
|
|
|
var file = diagnostic.File;
|
|
var lineNumberWidth = diagnostic.File.Line.ToString().Length;
|
|
|
|
writer.WriteLine($" {new string(' ', lineNumberWidth)}{file.File}:{file.Line}:{file.Column}");
|
|
writer.WriteLine($"{new string(' ', lineNumberWidth)} | ");
|
|
|
|
var sourceLine = TryReadLine(file.File, file.Line);
|
|
if (sourceLine != null)
|
|
{
|
|
writer.Write($"{file.Line.ToString().PadLeft(lineNumberWidth)} | ");
|
|
writer.WriteLine(sourceLine);
|
|
|
|
writer.Write(new string(' ', lineNumberWidth));
|
|
writer.Write(" | ");
|
|
writer.Write(new string(' ', file.Column - 1));
|
|
writer.Write(color);
|
|
writer.Write(new string('^', Math.Max(1, file.Length)));
|
|
writer.WriteLine(Ansi.Reset);
|
|
}
|
|
|
|
writer.WriteLine($"{new string(' ', lineNumberWidth)} |");
|
|
|
|
if (!string.IsNullOrWhiteSpace(diagnostic.Help))
|
|
{
|
|
writer.WriteLine($" = help: {diagnostic.Help}");
|
|
}
|
|
}
|
|
|
|
private static string? TryReadLine(string file, int line)
|
|
{
|
|
try
|
|
{
|
|
using var reader = new StreamReader(file);
|
|
for (var i = 1; i < line; i++)
|
|
{
|
|
if (reader.ReadLine() == null)
|
|
return null;
|
|
}
|
|
|
|
return reader.ReadLine();
|
|
}
|
|
catch
|
|
{
|
|
return null;
|
|
}
|
|
}
|
|
|
|
private static class Ansi
|
|
{
|
|
public const string Reset = "\e[0m";
|
|
public const string Red = "\e[31m";
|
|
public const string Yellow = "\e[33m";
|
|
public const string Cyan = "\e[36m";
|
|
}
|
|
} |