1 /** Conversions from string/file offset to line and column.
2  *
3  * See_Also: https://clang.llvm.org/diagnostics.html
4  */
5 module nxt.line_column;
6 
7 void diagnoseGNUStyle(Args...)(scope const(char)[] tag,
8                                scope const(char)[] path,
9                                in LineColumn lc,
10                                Args args) @safe
11 {
12     import std.stdio : writeln;
13     debug writeln(path,
14                   ":", lc.line + 1, // line offset starts at 1
15                   ":", lc.column,   // column counter starts at 0
16                   ": ", tag, ": ", args);
17 }
18 
19 /** Line and column, both 0-based offsets.
20  *
21  * Uses 32-bit unsigned precision for line and column offet, for now, like tree-sitter does.
22  */
23 @safe pure nothrow @nogc struct LineColumn
24 {
25     uint line;					///< 0-based line offset.
26     uint column;				///< 0-based column offset.
27 }
28 
29 /** Convert byte offset `offset` at `source` to line and column.
30  *
31  * TODO: extend to support UTF-8 in column offset.
32  * TODO: Move to Phobos std.string?
33  */
34 LineColumn offsetLineColumn(scope const char[] source, in size_t offset) @safe pure nothrow @nogc
35 {
36     // find 0-based column offset
37     size_t cursor = offset;      // cursor
38     while (cursor != 0)
39     {
40         if (cursor >= 1)
41             if (source[cursor - 1] == '\n' || // TODO: extend to support UTF-8
42                 source[cursor - 1] == '\r')   // TODO: extend to support UTF-8
43                 break;
44         cursor -= 1;
45     }
46     // cursor is not at beginning of line
47 
48     const column = offset - cursor; // column byte offset
49 
50     // find 0-based line offset
51     size_t lineCounter = 0;
52     while (cursor != 0)
53     {
54         if (source[cursor - 1] == '\n' ||
55             source[cursor - 1] == '\r')
56         {
57             cursor -= 1;
58             if (cursor != 0 &&
59                 (source[cursor - 1] == '\r')) // DOS-style line ending "\r\n"
60                 cursor -= 1;
61             else            // UNIX-style line ending "\n"
62             {
63 				// nothing is needed
64             }
65             lineCounter += 1;
66         }
67         else                // no line ending at cursor
68             cursor -= 1;
69     }
70 
71     return typeof(return)(cast(uint)lineCounter,
72                           cast(uint)column);
73 }
74 
75 ///
76 @safe pure nothrow @nogc unittest
77 {
78     auto x = "\nx\n y";
79     assert(x.length == 5);
80     assert(x.offsetLineColumn(0) == LineColumn(0, 0));
81     assert(x.offsetLineColumn(1) == LineColumn(1, 0));
82     assert(x.offsetLineColumn(2) == LineColumn(1, 1));
83     assert(x.offsetLineColumn(3) == LineColumn(2, 0));
84     assert(x.offsetLineColumn(4) == LineColumn(2, 1));
85 }
86 
87 version(unittest)
88 {
89     import nxt.dbgio;
90 }