1
0

initial commit

This commit is contained in:
2026-03-04 07:59:35 +01:00
commit 3ceb0e4884
27 changed files with 2280 additions and 0 deletions

View File

@@ -0,0 +1,84 @@
namespace AnchorCli.Hashline;
/// <summary>
/// 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.
/// </summary>
internal static class HashlineEncoder
{
/// <summary>
/// Computes the 2-char hex Hashline tag for a single line.
/// </summary>
/// <param name="line">Raw line content (without newline).</param>
/// <param name="lineNumber">1-indexed line number within the file.</param>
public static string ComputeHash(ReadOnlySpan<char> 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");
}
/// <summary>
/// Encodes all lines with Hashline prefixes.
/// </summary>
/// <param name="lines">All lines of the file (without trailing newlines).</param>
/// <returns>A single string with each line prefixed as "{n}:{hash}|{content}\n".</returns>
public static string Encode(string[] lines) => Encode(lines, startLine: 1, endLine: lines.Length);
/// <summary>
/// Encodes a window of lines with Hashline prefixes.
/// Line numbers in the output reflect their true position in the file.
/// </summary>
/// <param name="lines">All lines of the file.</param>
/// <param name="startLine">1-indexed first line to include (inclusive).</param>
/// <param name="endLine">1-indexed last line to include (inclusive). Clamped to file length.</param>
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();
}
/// <summary>
/// Computes a short file-level fingerprint: XOR of all per-line hashes (as bytes).
/// Useful for cheap full-file staleness checks.
/// </summary>
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");
}
}