1 /** Conversions from string/file offset to line and column.
2 	See_Also: https://gcc.gnu.org/codingconventions.html#Diagnostics
3     See_Also: https://clang.llvm.org/diagnostics.html
4  */
5 module nxt.line_column;
6 
7 import nxt.path : FilePath;
8 
9 public import nxt.offset : Offset;
10 
11 @safe:
12 
13 /++ Source Line.
14  +/
15 alias Line = uint;
16 
17 /++ Source Column.
18  +/
19 alias Column = uint;
20 
21 /** Line and column, both 0-based byte offsets.
22  *
23  * Uses 32-bit unsigned precision for line and column offet, for now, like
24  * tree-sitter does.
25  */
26 pure nothrow @nogc struct LineColumn {
27 	Line line;					///< 0-based line byte offset.
28 	Column column;				///< 0-based column byte offset.
29 }
30 
31 /** Convert byte offset `offset` in `txt` to (line, column) byte offsets.
32  *
33  * The returned line byte offset and column byte offsets both start at zero.
34  *
35  * TODO: extend to support UTF-8 in column offset.
36  * TODO: Move to Phobos std.txting?
37  */
38 LineColumn scanLineColumnToOffset(in char[] txt, in Offset offset) pure nothrow @nogc {
39 	// find 0-based column offset
40 	size_t c = offset.sz;	  // cursor offset
41 	while (c != 0) {
42 		if (txt[c - 1] == '\n' ||
43 			txt[c - 1] == '\r')
44 			break;
45 		c -= 1;
46 	}
47 	// `c` is now at beginning of line
48 
49 	/+ TODO: count UTF-8 chars in `txt[c .. offset.sz]` +/
50 	const column = offset.sz - c; // column byte offset
51 
52 	// find 0-based line offset
53 	size_t lineCounter = 0;
54 	while (c != 0) {
55 		c -= 1;
56 		if (txt[c] == '\n') {
57 			if (c != 0 && txt[c - 1] == '\r') // DOS-style line ending "\r\n"
58 				c -= 1;
59 			else {} // Unix-style line ending "\n"
60 			lineCounter += 1;
61 		} else if (txt[c] == '\r') // Old Mac-style line ending "\r"
62 			lineCounter += 1;
63 		else {}				// no line ending at `c`
64 	}
65 
66 	return typeof(return)(cast(Line)lineCounter,
67 						  cast(Column)column);
68 }
69 
70 ///
71 pure nothrow @safe @nogc unittest {
72 	auto x = "\nx\n y\rz";
73 	assert(x.length == 7);
74 	assert(x.scanLineColumnToOffset(Offset(0)) == LineColumn(0, 0));
75 	assert(x.scanLineColumnToOffset(Offset(1)) == LineColumn(1, 0));
76 	assert(x.scanLineColumnToOffset(Offset(2)) == LineColumn(1, 1));
77 	assert(x.scanLineColumnToOffset(Offset(3)) == LineColumn(2, 0));
78 	assert(x.scanLineColumnToOffset(Offset(4)) == LineColumn(2, 1));
79 	assert(x.scanLineColumnToOffset(Offset(6)) == LineColumn(3, 0));
80 	assert(x.scanLineColumnToOffset(Offset(7)) == LineColumn(3, 1));
81 }
82 
83 version (unittest) {
84 	import nxt.debugio;
85 }