namespace AnchorCli.Hashline; /// /// Encodes file lines with Hashline anchors in the format: {lineNumber}:{hash}|{content} /// /// Hash algorithm: Adler-8 with a position seed. /// raw = ((sum_of_bytes % 251) ^ (count_of_bytes % 251)) & 0xFF /// final = raw ^ (lineNumber & 0xFF) /// /// The position seed ensures duplicate lines produce different tags, /// preventing ambiguous anchor resolution on large files. /// internal static class HashlineEncoder { /// /// Computes the 2-char hex Hashline tag for a single line. /// /// Raw line content (without newline). /// 1-indexed line number within the file. public static string ComputeHash(ReadOnlySpan line, int lineNumber) { int sum = 0; int count = line.Length; foreach (char c in line) { // Use the lower byte of each character — sufficient for source code sum += c & 0xFF; } int raw = ((sum % 251) ^ (count % 251)) & 0xFF; int final = raw ^ (lineNumber & 0xFF); return final.ToString("x2"); } /// /// Encodes all lines with Hashline prefixes. /// /// All lines of the file (without trailing newlines). /// A single string with each line prefixed as "{n}:{hash}|{content}\n". public static string Encode(string[] lines) => Encode(lines, startLine: 1, endLine: lines.Length); /// /// Encodes a window of lines with Hashline prefixes. /// Line numbers in the output reflect their true position in the file. /// /// All lines of the file. /// 1-indexed first line to include (inclusive). /// 1-indexed last line to include (inclusive). Clamped to file length. public static string Encode(string[] lines, int startLine, int endLine) { if (lines.Length == 0) return string.Empty; startLine = Math.Max(1, startLine); endLine = Math.Min(lines.Length, endLine <= 0 ? lines.Length : endLine); var sb = new System.Text.StringBuilder(); for (int i = startLine; i <= endLine; i++) { string line = lines[i - 1]; string hash = ComputeHash(line.AsSpan(), i); sb.Append(i).Append(':').Append(hash).Append('|').AppendLine(line); } return sb.ToString(); } /// /// Computes a short file-level fingerprint: XOR of all per-line hashes (as bytes). /// Useful for cheap full-file staleness checks. /// public static string FileFingerprint(string[] lines) { int fp = 0; for (int i = 0; i < lines.Length; i++) { string hash = ComputeHash(lines[i].AsSpan(), i + 1); fp ^= Convert.ToInt32(hash, 16); } return fp.ToString("x2"); } }