using System.Diagnostics.CodeAnalysis; using Nub.Lang.Frontend.Lexing; using Nub.Lang.Frontend.Parsing; namespace Nub.Lang.Frontend.Diagnostics; public readonly struct SourceFile(string path, string content) { public string Path { get; } = path; public string Content { get; } = content; } public readonly struct SourceLocation(int line, int column) { public int Line { get; } = line; public int Column { get; } = column; } public readonly struct SourceSpan(SourceLocation start, SourceLocation end) { public SourceLocation Start { get; } = start; public SourceLocation End { get; } = 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 { 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), $"Index {index} is out of range for content of length {content.Length}"); } var lineStarts = GetLineStarts(content); var line = Array.BinarySearch(lineStarts, index); if (line < 0) { line = ~line - 1; } 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) { var start = IndexToLocation(token.SourceFile.Content, token.StartIndex); var end = IndexToLocation(token.SourceFile.Content, token.EndIndex); return new SourceSpan(start, end); } public static SourceSpan GetSpan(Node node) { if (!node.Tokens.Any()) { throw new ArgumentException("Node has no tokens", nameof(node)); } var firstToken = node.Tokens[0]; var lastToken = node.Tokens[^1]; var start = IndexToLocation(firstToken.SourceFile.Content, firstToken.StartIndex); var end = IndexToLocation(lastToken.SourceFile.Content, lastToken.EndIndex); return new SourceSpan(start, end); } }