1 module zua.diagnostic;
2 import zua.parser.lexer;
3 import std.algorithm.sorting;
4 import std.array;
5 import std.typecons;
6 import std.algorithm;
7 
8 /** Determines what type of diagnostic it represents */
9 enum DiagnosticType {
10 	Error,
11 	Warning,
12 	Info
13 }
14 
15 /** Describes how a quickfix should be performed on a range */
16 struct QuickfixRange {
17 
18 	/** Describes the range in the *original* document to modify */
19 	size_t from;
20 	size_t to; /// ditto
21 
22 	/** The code to replace the given range with */
23 	string replaceWith;
24 
25 	/** Create a new quickfix range */
26 	this(size_t from, size_t to, string replaceWith) {
27 		this.from = from;
28 		this.to = to;
29 		this.replaceWith = replaceWith;
30 	}
31 
32 	/** Create a new quickfix range */
33 	this(Token token, string replaceWith) {
34 		this.from = token.index;
35 		this.to = token.index + token.rawValue.length;
36 		this.replaceWith = replaceWith;
37 	}
38 
39 	/** Create a new quickfix range */
40 	this(Token from, Token to, string replaceWith) {
41 		this.from = from.index;
42 		this.to = to.index + to.rawValue.length;
43 		this.replaceWith = replaceWith;
44 	}
45 
46 	int opCmp(ref const QuickfixRange other) const {
47 		if (from < other.from) {
48 			return -1;
49 		}
50 		else if (from > other.from) {
51 			return 1;
52 		}
53 		else {
54 			return 0;
55 		}
56 	}
57 
58 	bool opEquals(ref const QuickfixRange other) const {
59 		return from == other.from;
60 	}
61 
62 	size_t toHash() const nothrow @safe {
63 		return typeid(from).getHash(&from);
64 	}
65 
66 }
67 
68 /** Describes a quickfix option */
69 struct Quickfix {
70 
71 	/** A message describing the fix */
72 	string message;
73 
74 	/** A list of quickfix range operations to apply. Ranges may not overlap */
75 	const(QuickfixRange)[] ranges;
76 
77 }
78 
79 /** Stores a diagnostic message */
80 struct Diagnostic {
81 
82 	/** Determines what type of diagnostic it represents */
83 	DiagnosticType type;
84 
85 	/** The diagonstic message */
86 	string message;
87 
88 	/** The ranges of indices that this diagnostic message partains to */
89 	size_t[2][] ranges;
90 
91 	/** Provides a list of possible quickfixes */
92 	const(Quickfix)[] quickfix;
93 
94 	/** Create a new diagnostic message */
95 	this(DiagnosticType type, string message) {
96 		this.type = type;
97 		this.message = message;
98 	}
99 
100 	/** Add a range of indices to this diagnostic message */
101 	void add(size_t from, size_t to) {
102 		ranges ~= [from, to];
103 	}
104 
105 	/** Add a single index to this diagnostic message */
106 	void add(size_t at) {
107 		ranges ~= [at, at];
108 	}
109 
110 	/** Add a single token to this diagnostic message */
111 	void add(Token token) {
112 		add(token.index, token.index + token.rawValue.length);
113 	}
114 
115 	/** Add a range of tokens to this diagnostic message, inclusive */
116 	void add(Token from, Token to) {
117 		add(from.index, to.index + to.rawValue.length);
118 	}
119 
120 }
121 
122 /** Return the modified source code after the application of a quickfix */
123 string apply(Quickfix fix, string source) {
124 	QuickfixRange[] ranges = fix.ranges.dup;
125 	sort!"a > b"(ranges);
126 
127 	foreach (range; ranges) {
128 		source = source[0..range.from] ~ range.replaceWith ~ source[range.to..$];
129 	}
130 
131 	return source;
132 }
133 
134 /** Return the zero-indexed line number and local index from absolute index */
135 auto decodeIndex(size_t index, string source) {
136 	Tuple!(size_t, "line", size_t, "index") result;
137 
138 	index = min(source.length, index);
139 
140 	size_t currIndex = 0;
141 
142 	const lines = source.split('\n');
143 	foreach (lineNum; 0..lines.length) {
144 		const line = lines[lineNum];
145 		if (index >= currIndex && index <= currIndex + line.length) {
146 			result.line = lineNum;
147 			result.index = index - currIndex;
148 			break;
149 		}
150 
151 		currIndex += line.length + 1;
152 	}
153 
154 	return result;
155 }
156 
157 unittest {
158 	const string source = "ABCDEF";
159 
160 	const Quickfix fix = {
161 		message: "",
162 		ranges: [QuickfixRange(1, 2, ".."), QuickfixRange(4, 5, "::")]
163 	};
164 
165 	assert(fix.apply(source) == "A..CD::F");
166 }