-
-
Notifications
You must be signed in to change notification settings - Fork 183
Expand file tree
/
Copy pathPdeIssueEmitter.java
More file actions
356 lines (301 loc) · 12.4 KB
/
Copy pathPdeIssueEmitter.java
File metadata and controls
356 lines (301 loc) · 12.4 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
/* -*- mode: java; c-basic-offset: 2; indent-tabs-mode: nil -*- */
/*
Part of the Processing project - http://processing.org
Copyright (c) 2019 The Processing Foundation
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License version 2
as published by the Free Software Foundation.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software Foundation,
Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
package processing.mode.java.preproc;
import org.antlr.v4.runtime.BaseErrorListener;
import org.antlr.v4.runtime.RecognitionException;
import org.antlr.v4.runtime.Recognizer;
import java.util.Optional;
/**
* ANTLR error listener to inform a preprocess issue listener when syntax errors are encountered.
*
* <p>
* A {BaseErrorListener} which looks for syntax errors reported by ANTLR and converts them to
* {PdePreprocessIssue}s that are consumable by a {PdePreprocessIssueListener}. It does this by
* running the {PreprocessIssueMessageSimplifier} to generate a more user-friendly error message
* before informing the provided listener.
* </p>
*/
public class PdeIssueEmitter extends BaseErrorListener {
private final PdePreprocessIssueListener listener;
private final Optional<SourceEmitter> sourceMaybe;
/**
* Create a new issue emitter.
*
* <p>
* Create a new issue emitter when access to the processing sketch source is not available.
* Note that this will not allow some error beautification and, if sketch source is available,
* use other constructor.
* </p>
*
* @param newListener The listener to inform when encountering a syntax error.
*/
public PdeIssueEmitter(PdePreprocessIssueListener newListener) {
listener = newListener;
sourceMaybe = Optional.empty();
}
/**
* Create a new issue emitter.
*
* @param newListener The listener to inform when encountering a syntax error.
* @param newSourceEmitter The sketch source to use when helping beautify certain syntax error
* messages.
*/
public PdeIssueEmitter(PdePreprocessIssueListener newListener, SourceEmitter newSourceEmitter) {
listener = newListener;
sourceMaybe = Optional.of(newSourceEmitter);
}
@Override
public void syntaxError(Recognizer<?, ?> recognizer, Object offendingSymbol, int line,
int charPositionInLine, String msg, RecognitionException e) {
PreprocessIssueMessageSimplifier facade = PreprocessIssueMessageSimplifier.get();
IssueMessageSimplification simplification = facade.simplify(msg);
IssueLocation issueLocation;
if (sourceMaybe.isPresent()) {
issueLocation = IssueLocationFactory.getLineWithOffset(
simplification,
line,
charPositionInLine,
sourceMaybe.get().getSource()
);
} else {
issueLocation = new IssueLocation(line, charPositionInLine);
}
listener.onIssue(new PdePreprocessIssue(
issueLocation.getLine(),
issueLocation.getCharPosition(),
simplification.getMessage()
));
}
/**
* Simple interface for strategy which can emit the full body of a processing sketch.
*/
public static interface SourceEmitter {
/**
* Get the full body of the processing sketch.
*
* @return String processing sketch source code across all tabs.
*/
String getSource();
}
/**
* Interface for listener that responds to issues reported by the preprocessor.
*/
public static interface PdePreprocessIssueListener {
/**
* Callback to invoke when an issue is encountered in preprocesing.
*
* @param issue Description of the issue.
*/
void onIssue(PdePreprocessIssue issue);
}
/**
* ================================
* == Supporting data structures ==
* ================================
*/
/**
* Data structure describing an issue simplification or explanation.
*
* <p>
* Data structure describing an edit that was made to an error message or warning to be shown to
* the user based on a series of rules that attempts to make error messages easier to understand
* for the user.
* </p>
*/
public static class IssueMessageSimplification {
private final String message;
private final boolean attributeToPriorToken;
/**
* Create a new issue message simplification.
*
* <p>
* Create a new issue message simplification that leaves the token attribution alone (the token
* on which the error was reported will be the same before error message simplification).
* </p>
*
* @param newMessage The message to show to the user.
*/
public IssueMessageSimplification(String newMessage) {
message = newMessage;
attributeToPriorToken = false;
}
/**
* Create a new issue message simplification.
*
* <p>
* Create a new issue message simplification. Note that there is an option to have the error
* attributed to the "prior token". This is helpful, for example, when a semicolon is missing.
* The error is generated on the token after the line on which the semicolon was omitted so,
* while the error technically emerges on the next line, it is better for the user for it to
* appear earlier. Specifically, it is most sensible for it to appear on the "prior token".
* </p>
*
* @param newMessage The message to show to the user.
* @param newAttributeToPriorToken Boolean flag indicating if the error should be shown on the
* token prior to the one on which the error was originally generated. True if the error should
* be attributed to the prior token. False otherwise.
*/
public IssueMessageSimplification(String newMessage, boolean newAttributeToPriorToken) {
message = newMessage;
attributeToPriorToken = newAttributeToPriorToken;
}
/**
* Get the error message text that should be shown to the user.
*
* @return The error message text that should be shown to the user.
*/
public String getMessage() {
return message;
}
/**
* Flag indicating if the error should be attributed to the prior token.
*
* @return True if the error should be attributed to the prior non-skip token (not whitepsace or
* comment). This is useful when a mistake on a prior line like omitted semicolon causes an
* error on a later line but one wants error highlighting closer to the mistake itself. False
* if the error should be attributed to the original offending token.
*/
public boolean getAttributeToPriorToken() {
return attributeToPriorToken;
}
}
/**
* Data structure describing where an issue occurred.
*/
public static class IssueLocation {
private final int line;
private final int charPosition;
/**
* Create a new issue location structure.
*
* @param newLine The line (1-indexed) where the issue occurred. This should be in the global file
* generated by the preprocessor and not relative to the start of the tab.
* @param newCharPosition The position on the line.
*/
public IssueLocation(int newLine, int newCharPosition) {
line = newLine;
charPosition = newCharPosition;
}
/**
* Get the 1-indexed line on which this error occurred.
*
* @return The line on which this error occurred. Note that this will be relative to the global
* file generated by the preprocessor and not relative to the start of the tab.
*/
public int getLine() {
return line;
}
/**
* The the position of the error within the line.
*
* @return The number of characters including whitespace from the start of the line at which the
* error occurred.
*/
public int getCharPosition() {
return charPosition;
}
}
/**
* =====================
* == Utility classes ==
* =====================
*/
/**
* Utility that can help clean up where in source an issue should be reported.
*
* <p>
* For some errors, the location of the "mistake" does not appear close to where the actual error
* is generated. For example, consider omitting a semicolon. Though the "mistake" is arguably on
* the line on which a semicolon is forgotten, the grammatical error appears in the first
* non-skip token after the omitted character. This means that the issue shown to the user may
* be far away from the line they would want to edit. This utility helps determine if an issue
* requires a new location and, if so, where the location should be.
* </p>
*/
public static class IssueLocationFactory {
/**
* Determine where an issue should be reported.
*
* @param simplification The issue simplification generated from {PreprocessIssueMessageSimplifier}.
* @param originalLine The original line (1 indexed) on which the issue was reported.
* @param originalOffset The original number of characters from the start of the line where the
* the issue was reported.
* @param source The full concatenated source of the sketch being built.
* @param lineCount The total
* @return The new location where the issue should be reported. This may be identical to the
* original location if the issue was not moved.
*/
public static IssueLocation getLineWithOffset(IssueMessageSimplification simplification,
int originalLine, int originalOffset, String source) {
// Determine if the issue should be relocated
boolean shouldAttributeToPrior = simplification.getAttributeToPriorToken();
shouldAttributeToPrior = shouldAttributeToPrior && originalLine != 0;
if (!shouldAttributeToPrior) {
return new IssueLocation(originalLine, originalOffset);
}
// Find the code prior the issue
String priorCode = getContentsUpToLine(source, originalLine);
// Find the token immediately prior to the issue
PreprocessIssueMessageSimplifier.PriorTokenFinder finder = new PreprocessIssueMessageSimplifier.PriorTokenFinder();
int charPos = priorCode.length();
while (!finder.isDone() && charPos > 0) {
charPos--;
finder.step(priorCode.charAt(charPos));
}
// Find the location offset depending on if the prior token could be found
Optional<Integer> foundStartOfMatchMaybe = finder.getTokenPositionMaybe();
int startOfMatch;
int linesOffset;
if (foundStartOfMatchMaybe.isPresent()) {
startOfMatch = priorCode.length() - foundStartOfMatchMaybe.get();
String contentsOfMatch = priorCode.substring(startOfMatch);
linesOffset = SourceUtil.getCount(contentsOfMatch, "\n");
} else {
startOfMatch = priorCode.length();
linesOffset = 0;
}
// Apply the location offset and highlight to the end of the line
String contentsPriorToMatch = priorCode.substring(0, startOfMatch);
int newLine = originalLine - linesOffset;
int lengthIncludingLine = contentsPriorToMatch.length();
int lengthExcludingLine = contentsPriorToMatch.lastIndexOf('\n');
int lineLength = lengthIncludingLine - lengthExcludingLine;
int col = lineLength - 1; // highlight from start of line to end
// Build the new issue location
return new IssueLocation(newLine, col);
}
/**
* Get all of the contents of source leading up to a line.
*
* @param source The full concatenated sketch source.
* @param endLineExclusive The line up to which code should be returned. Note that this is an
* "exclusive" boundary. Code from this line itself will not be included.
* @return All of the sketch code leading up to but not including the line given.
*/
private static String getContentsUpToLine(String source, int endLineExclusive) {
int line = 0;
int stringCursor = 0;
int strLength = source.length();
while (line < endLineExclusive-1 && stringCursor < strLength) {
if (source.charAt(stringCursor) == '\n') {
line++;
}
stringCursor++;
}
return source.substring(0, stringCursor);
}
}
}