|
1 /* |
|
2 * Copyright (c) 2009 Nokia Corporation and/or its subsidiary(-ies). |
|
3 * All rights reserved. |
|
4 * This component and the accompanying materials are made available |
|
5 * under the terms of the License "Eclipse Public License v1.0" |
|
6 * which accompanies this distribution, and is available |
|
7 * at the URL "http://www.eclipse.org/legal/epl-v10.html". |
|
8 * |
|
9 * Initial Contributors: |
|
10 * Nokia Corporation - initial contribution. |
|
11 * |
|
12 * Contributors: |
|
13 * |
|
14 * Description: |
|
15 * |
|
16 */ |
|
17 /** |
|
18 * |
|
19 */ |
|
20 package com.nokia.sdt.sourcegen.core; |
|
21 |
|
22 import com.nokia.cpp.internal.api.utils.core.Check; |
|
23 import com.nokia.cpp.internal.api.utils.core.Tuple; |
|
24 |
|
25 |
|
26 /** |
|
27 * Utilities for parsing source text. |
|
28 * |
|
29 * |
|
30 */ |
|
31 public class ParseUtils { |
|
32 |
|
33 /** Find insert position in the given range before the given character, flush |
|
34 * to the start of the line */ |
|
35 public static int getInsertPositionAtEnd(char[] text, int start, int length, char lastCh) { |
|
36 int end = start + length; |
|
37 int idx; |
|
38 for (idx = end - 1; idx >= start; idx--) { |
|
39 if (text[idx] == lastCh) { |
|
40 break; |
|
41 } |
|
42 } |
|
43 // now go back to a non-space character (probably a newline) |
|
44 while (idx > start && (text[idx-1] == '\t' || text[idx-1] == ' ')) { |
|
45 idx--; |
|
46 } |
|
47 if (idx > start && text[idx] != lastCh && text[idx-1] != '\n') |
|
48 idx++; |
|
49 |
|
50 //System.out.println("write at " +idx+": " + new String(text, idx, end - idx)); |
|
51 return idx; |
|
52 } |
|
53 |
|
54 /** Find insert position in the given range before the given character, flush |
|
55 * to the start of the line */ |
|
56 public static int getInsertPositionAtStart(char[] text, int start, int length, char firstCh) { |
|
57 int end = start + length; |
|
58 int idx; |
|
59 for (idx = start; idx < end; idx++) { |
|
60 if (text[idx] == firstCh) { |
|
61 break; |
|
62 } |
|
63 } |
|
64 // now go back to a non-space character (probably a newline) |
|
65 while (idx > start && (text[idx-1] == '\t' || text[idx-1] == ' ')) { |
|
66 idx--; |
|
67 } |
|
68 if (idx > start && text[idx] != firstCh && text[idx-1] != '\n') |
|
69 idx++; |
|
70 |
|
71 //System.out.println("write at " +idx+": " + new String(text, idx, end - idx)); |
|
72 return idx; |
|
73 } |
|
74 |
|
75 /** Find insert position in the given range after the given character, flush |
|
76 * to the first text in the line */ |
|
77 public static int getInsertPositionAfterStart(char[] text, int start, int length, char firstCh) { |
|
78 int end = start + length; |
|
79 int idx; |
|
80 for (idx = start; idx < end; idx++) { |
|
81 if (text[idx] == firstCh) { |
|
82 idx++; |
|
83 break; |
|
84 } |
|
85 } |
|
86 |
|
87 // skip to the first non-whitespace character |
|
88 while (idx < end && Character.isWhitespace(text[idx])) |
|
89 idx++; |
|
90 |
|
91 return idx; |
|
92 } |
|
93 |
|
94 /** |
|
95 * Get length of newline. |
|
96 * @param text |
|
97 * @param offset offset of a '\r' or '\n' character |
|
98 * @return length of sequence |
|
99 */ |
|
100 public static int newlineLengthBefore(char[] text, int offset) { |
|
101 if (offset <= 0) |
|
102 return 0; |
|
103 if (text[offset - 1] == '\r') |
|
104 return 1; |
|
105 else /* must be '\n' */ if (offset > 1 && text[offset - 2] == '\r') |
|
106 return 2; |
|
107 else |
|
108 return 1; |
|
109 } |
|
110 |
|
111 /** |
|
112 * Get length of newline. |
|
113 * @param text |
|
114 * @param offset of a '\r' or '\n' character |
|
115 * @return length of sequence |
|
116 */ |
|
117 public static int newlineLength(char[] text, int offset) { |
|
118 if (offset >= text.length) |
|
119 return 0; |
|
120 else if (text[offset] == '\n') |
|
121 return 1; |
|
122 else /* must be \r */ if (offset + 1 < text.length && text[offset + 1] == '\n') |
|
123 return 2; |
|
124 else |
|
125 return 1; |
|
126 } |
|
127 |
|
128 /** |
|
129 * Scan backwards to find the start of a line, |
|
130 * skipping any block comments (which may have their own |
|
131 * newlines embedded) |
|
132 * @param text |
|
133 * @param start |
|
134 * @return offset at start of line |
|
135 */ |
|
136 public static int skipToLogicalBeginningOfLineBackward(char[] text, int start, int limit) { |
|
137 while (start > limit) { |
|
138 if (text[start-1] == '\n' || text[start-1] == '\r') |
|
139 break; |
|
140 if (start - 1 > limit && text[start-2] == '*' && text[start-1] == '/') { |
|
141 // scan backward past block comment |
|
142 start --; |
|
143 while (start > limit) { |
|
144 if (text[start - 1] == '/' && text[start] == '*') |
|
145 break; |
|
146 start--; |
|
147 } |
|
148 } |
|
149 start--; |
|
150 } |
|
151 return start; |
|
152 } |
|
153 |
|
154 /** |
|
155 * Scan forward to find the end of a line (excluding terminators), |
|
156 * skipping any block comments (which may have their own |
|
157 * newlines embedded) |
|
158 * @param text |
|
159 * @param end current position from which to locate end of line |
|
160 * @param limit upper bound to search |
|
161 * @return offset of terminator at end of line |
|
162 */ |
|
163 public static int skipToLogicalEndOfLineForward(char[] text, int end, int limit) { |
|
164 while (end < limit) { |
|
165 if (text[end] == '\n' || text[end] == '\r') |
|
166 break; |
|
167 if (end + 1 < limit && text[end] == '/' && text[end+1] == '*') { |
|
168 // scan forward past block comment |
|
169 end += 2; |
|
170 while (end + 1 < limit) { |
|
171 if (text[end] == '*' && text[end + 1] == '/') |
|
172 break; |
|
173 end++; |
|
174 } |
|
175 end += 2; // skip '*' and '/' |
|
176 } else |
|
177 end++; |
|
178 } |
|
179 return end; |
|
180 } |
|
181 |
|
182 /** |
|
183 * Skip to and past the end of line |
|
184 * @param text |
|
185 * @param start |
|
186 * @param endIdx |
|
187 * @return position of next line |
|
188 */ |
|
189 public static int skipToNextLine(char[] text, int start, int endIdx) { |
|
190 while (start < endIdx) { |
|
191 if (text[start] == '\r') { |
|
192 if (start + 1 < endIdx && text[start + 1] == '\n') { |
|
193 start += 2; |
|
194 } else { |
|
195 start++; |
|
196 } |
|
197 break; |
|
198 } else if (text[start] == '\n') { |
|
199 start++; |
|
200 break; |
|
201 } else |
|
202 start++; |
|
203 } |
|
204 |
|
205 return start; |
|
206 } |
|
207 |
|
208 /** |
|
209 * Skip to and past the end of line |
|
210 * @param text |
|
211 * @param start |
|
212 * @return position of next line |
|
213 */ |
|
214 public static int skipToNextLine(CharSequence text, int start) { |
|
215 int endIdx = text.length(); |
|
216 while (start < endIdx) { |
|
217 char ch = text.charAt(start); |
|
218 if (ch == '\r') { |
|
219 if (start + 1 < endIdx && text.charAt(start + 1) == '\n') { |
|
220 start += 2; |
|
221 } else { |
|
222 start++; |
|
223 } |
|
224 break; |
|
225 } else if (ch == '\n') { |
|
226 start++; |
|
227 break; |
|
228 } else |
|
229 start++; |
|
230 } |
|
231 |
|
232 return start; |
|
233 } |
|
234 |
|
235 /** |
|
236 * Skip a newline if present |
|
237 * @param subText |
|
238 * @param i |
|
239 */ |
|
240 public static int skipIfNewLine(CharSequence subText, int i) { |
|
241 if (i < subText.length()) { |
|
242 char ch = subText.charAt(i); |
|
243 if (ch == '\r') { |
|
244 if (i + 1 < subText.length() && subText.charAt(i+1) == '\n') |
|
245 return i + 2; |
|
246 else |
|
247 return i + 1; |
|
248 } |
|
249 else if (ch == '\n') |
|
250 return i + 1; |
|
251 } |
|
252 return i; |
|
253 } |
|
254 |
|
255 /** |
|
256 * Skip a newline if present |
|
257 * @param subText |
|
258 * @param i incoming offset |
|
259 * @return new offset |
|
260 */ |
|
261 public static int skipIfNewLine(char[] text, int i) { |
|
262 if (i < text.length) { |
|
263 char ch = text[i]; |
|
264 if (ch == '\r') { |
|
265 if (i + 1 < text.length && text[i+1] == '\n') |
|
266 return i + 2; |
|
267 else |
|
268 return i + 1; |
|
269 } |
|
270 else if (ch == '\n') |
|
271 return i + 1; |
|
272 } |
|
273 return i; |
|
274 } |
|
275 |
|
276 /** |
|
277 * Scan forward to skip a comment, either a block comment or |
|
278 * a C++ single line comment.<p> |
|
279 * @param text |
|
280 * @param end current position from which to locate comments |
|
281 * @param limit upper bound to search |
|
282 * @param includeTrailingSpace if true, skip whitespace and include |
|
283 * any trailing newline; else, stop if no comment found on line. |
|
284 * @return new end offset |
|
285 */ |
|
286 public static int includeTrailingComment(char[] text, int end, int limit, boolean includeTrailingSpace) { |
|
287 int currentEoc = end; |
|
288 while (end < limit) { |
|
289 if (text[end] == '\n' || text[end] == '\r') { |
|
290 if (includeTrailingSpace) |
|
291 currentEoc = skipIfNewLine(text, end); |
|
292 break; |
|
293 } |
|
294 if (end + 1 < limit && text[end] == '/' && text[end+1] == '*') { |
|
295 // scan forward past block comment |
|
296 end += 2; |
|
297 while (end + 1 < limit) { |
|
298 if (text[end] == '*' && text[end + 1] == '/') |
|
299 break; |
|
300 end++; |
|
301 } |
|
302 end += 2; // skip '*' and '/' |
|
303 currentEoc = end; |
|
304 } else if (end + 1 < limit && text[end] == '/' && text[end+1] == '/') { |
|
305 // scan forward past end of line comment |
|
306 end += 2; |
|
307 while (end < limit && text[end] != '\r' && text[end] != '\n') |
|
308 end++; |
|
309 if (includeTrailingSpace) |
|
310 currentEoc = skipIfNewLine(text, end); |
|
311 else |
|
312 currentEoc = end; |
|
313 break; |
|
314 } else if (text[end] == ' ' || text[end] == '\t') |
|
315 end++; |
|
316 else |
|
317 break; |
|
318 } |
|
319 return currentEoc; |
|
320 } |
|
321 |
|
322 /** |
|
323 * Scan backward past any comments (single-line or block) and stop at the |
|
324 * beginning of a line. Do not include any comments that live at the end of |
|
325 * another line. Do not include blank lines if they do not have |
|
326 * comments preceding. Stop if any non-whitespace encountered. |
|
327 * |
|
328 * @param text |
|
329 * @param start |
|
330 * current position from which to search backward |
|
331 * @param limit |
|
332 * lower bound to search |
|
333 * @param includeLeadingSpace if true, include whitespace before any comment |
|
334 * @return offset of beginning of line |
|
335 */ |
|
336 public static int includeLeadingComment(char[] text, int start, int limit, boolean includeLeadingSpace) { |
|
337 // The algorithm works on a logical line-by-line basis, |
|
338 // where a logical line may be several lines spanned by |
|
339 // a block comment. |
|
340 // |
|
341 // We scan whitespace backward. Non-whitespace may be |
|
342 // '*/', which prompts us to find a the beginning, and |
|
343 // continue. Or it may be other text, in which case |
|
344 // we scan backward for '//'. |
|
345 // we temporarily skip to the beginning of the line |
|
346 // and verify that |
|
347 |
|
348 int currentLineStart = start; |
|
349 |
|
350 if (includeLeadingSpace) |
|
351 currentLineStart = skipOnlyWhitespaceToLineStart(text, currentLineStart, limit); |
|
352 |
|
353 while (start > limit) { |
|
354 int prevCommentStart = findPreviousComment(text, currentLineStart, limit, true); |
|
355 // this check includes prevCommentStart==-1 |
|
356 if (prevCommentStart >= limit) { |
|
357 currentLineStart = prevCommentStart; |
|
358 if (includeLeadingSpace) |
|
359 currentLineStart = skipOnlyWhitespaceToLineStart(text, currentLineStart, limit); |
|
360 } else |
|
361 break; |
|
362 } |
|
363 return currentLineStart; |
|
364 } |
|
365 |
|
366 /** |
|
367 * Find a comment preceding the cursor |
|
368 * @param text |
|
369 * @param currentLineStart |
|
370 * @param limit |
|
371 * @param mustBeAloneOnLine true: comment cannot have text preceding on line |
|
372 * @return |
|
373 */ |
|
374 private static int findPreviousComment(char[] text, int start, int limit, boolean mustBeAloneOnLine) { |
|
375 int currentBoc = -1; |
|
376 boolean lastWasBlockComment = false; |
|
377 while (start >= limit) { |
|
378 if (start == limit) { |
|
379 if (currentBoc >= limit) |
|
380 return currentBoc; |
|
381 else |
|
382 break; |
|
383 } |
|
384 start--; |
|
385 if (text[start] == '\r' || text[start] == '\n') { |
|
386 // were we waiting to verify that a comment was alone |
|
387 // on the line (preceded perhaps by other one-line or block comments?) |
|
388 if (currentBoc >= limit) |
|
389 return currentBoc; |
|
390 } else if (text[start] == ' ' || text[start] == '\t') { |
|
391 // skip spaces |
|
392 } else if (start > limit && text[start-1] == '*' && text[start] == '/') { |
|
393 // scan backward past block comment |
|
394 while (start > limit) { |
|
395 start--; |
|
396 if (text[start] == '/' && text[start+1] == '*') { |
|
397 break; |
|
398 } |
|
399 } |
|
400 currentBoc = start; |
|
401 lastWasBlockComment = true; |
|
402 if (!mustBeAloneOnLine) |
|
403 return currentBoc; |
|
404 } else { |
|
405 // not whitespace -- better be a single-line comment alone on the line |
|
406 int ptr = start; |
|
407 while (ptr >= limit) { |
|
408 if (text[ptr] == '/' && text[ptr - 1] == '/') { |
|
409 ptr--; |
|
410 // thee may be multiple one-line comments but we only want the last |
|
411 if (currentBoc == -1) { |
|
412 currentBoc = ptr; |
|
413 lastWasBlockComment = false; |
|
414 if (!mustBeAloneOnLine) |
|
415 return currentBoc; |
|
416 } |
|
417 break; |
|
418 } |
|
419 if (text[ptr] == '\r' || text[ptr] == '\n') { |
|
420 // oops, no comment at all on the line |
|
421 return -1; |
|
422 } |
|
423 ptr--; |
|
424 } |
|
425 start = ptr; |
|
426 } |
|
427 } |
|
428 |
|
429 return lastWasBlockComment ? currentBoc : -1; |
|
430 } |
|
431 |
|
432 /** |
|
433 * Skip to line start but only if only whitespace is encountered. |
|
434 * @param text |
|
435 * @param currentLineStart |
|
436 * @param limit |
|
437 * @return |
|
438 */ |
|
439 private static int skipOnlyWhitespaceToLineStart(char[] text, int start, int limit) { |
|
440 int orig = start; |
|
441 while (start > limit) { |
|
442 start--; |
|
443 if (text[start] == '\r' || text[start] == '\n') |
|
444 return start+1; |
|
445 if (text[start] != ' ' && text[start] != '\t') |
|
446 return orig; |
|
447 } |
|
448 return limit; |
|
449 } |
|
450 |
|
451 /** |
|
452 * Skip whitespace in text, including comments. |
|
453 * @return offset of non-whitespace or newline |
|
454 */ |
|
455 public static int skipWhitespaceForward(char[] text, int end, int limit) { |
|
456 while (end < limit) { |
|
457 if (end + 1 < limit && text[end] == '/' && text[end+1] == '*') { |
|
458 // scan forward past block comment |
|
459 end += 2; |
|
460 while (end + 1 < limit) { |
|
461 if (text[end] == '*' && text[end + 1] == '/') |
|
462 break; |
|
463 end++; |
|
464 } |
|
465 end += 2; // skip '*' and '/' |
|
466 } else if (end + 1 < limit && text[end] == '/' && text[end+1] == '/') { |
|
467 // scan forward past end of line comment |
|
468 end += 2; |
|
469 while (end < limit && text[end] != '\r' && text[end] != '\n') |
|
470 end++; |
|
471 break; |
|
472 } else if (text[end] == ' ' || text[end] == '\t') |
|
473 end++; |
|
474 else |
|
475 break; |
|
476 } |
|
477 return end; |
|
478 } |
|
479 |
|
480 /** |
|
481 * Match the given string in the array and return the updated position. |
|
482 * |
|
483 * @return new position or pos if not matched |
|
484 */ |
|
485 public static int matchText(String str, char[] text, int pos, int limit) { |
|
486 int i; |
|
487 for (i = 0; i < str.length(); i++) { |
|
488 if (pos + i >= limit || text[pos + i] != str.charAt(i)) |
|
489 return pos; |
|
490 } |
|
491 return pos + i; |
|
492 } |
|
493 |
|
494 /** |
|
495 * Search for a directive backwards. This is identified by |
|
496 * a hash mark abutting whitespace after a line start. |
|
497 * @param text |
|
498 * @param ptr upper barrier |
|
499 * @param limit lower barrier |
|
500 * @return offset or -1 |
|
501 */ |
|
502 public static int searchDirectiveBackwards(char[] text, int ptr, int limit) { |
|
503 char quote = 0; |
|
504 while (ptr > limit) { |
|
505 ptr--; |
|
506 if (quote == text[ptr]) { |
|
507 quote = 0; |
|
508 } else if (text[ptr] == '#') { |
|
509 int line = skipToLogicalBeginningOfLineBackward(text, ptr, limit); |
|
510 if (skipWhitespaceForward(text, line, ptr) == ptr) { |
|
511 return ptr; |
|
512 } |
|
513 } else if (text[ptr] == '\'' || text[ptr] == '#') |
|
514 quote = text[ptr]; |
|
515 } |
|
516 return -1; |
|
517 } |
|
518 |
|
519 /** |
|
520 * Search for a directive forwards. This is identified by |
|
521 * a hash mark abutting whitespace after a line start. |
|
522 * @param text |
|
523 * @param ptr current position (which may be the directive) |
|
524 * @param limit upper barrier |
|
525 * @return offset or -1 |
|
526 */ |
|
527 public static int searchDirectiveForwards(char[] text, int ptr, int limit) { |
|
528 while (ptr < limit) { |
|
529 ptr = skipToLogicalEndOfLineForward(text, ptr, limit); |
|
530 if (ptr < limit) { |
|
531 ptr += newlineLength(text, ptr); |
|
532 ptr = skipWhitespaceForward(text, ptr, limit); |
|
533 if (ptr < limit) { |
|
534 if (text[ptr] == '#') |
|
535 return ptr; |
|
536 } |
|
537 } |
|
538 } |
|
539 return -1; |
|
540 } |
|
541 |
|
542 /** |
|
543 * Find a named directive after the cursor and return its argument |
|
544 * with any comments and surrounding space removed. |
|
545 * @param name |
|
546 * @param start pointer to '#' |
|
547 * @param limit |
|
548 * @return tuple of (trimmed argument, directive start, directive end, |
|
549 * argument start, argument end) or null if no match |
|
550 */ |
|
551 public static Tuple findDirectiveAndArgument(String name, char[] text, int start, int limit) { |
|
552 int dirStart = -1, dirEnd = -1, argStart = -1, argEnd = -1; |
|
553 Check.checkArg(text[start] == '#'); |
|
554 start++; |
|
555 start = skipWhitespaceForward(text, start, limit); |
|
556 if (start + name.length() < limit) { |
|
557 for (int i = 0; i < name.length(); i++) |
|
558 if (text[start + i] != name.charAt(i)) |
|
559 return null; |
|
560 dirStart = start; |
|
561 start += name.length(); |
|
562 dirEnd = start; |
|
563 start = skipWhitespaceForward(text, start, limit); |
|
564 |
|
565 argStart = start; |
|
566 int ptr = start; |
|
567 while (ptr < limit) { |
|
568 if (text[ptr] == '\r' || text[ptr] == '\n') |
|
569 break; |
|
570 int newPtr = skipWhitespaceForward(text, ptr, limit); |
|
571 if (newPtr == ptr) |
|
572 ptr++; // not whitespace |
|
573 else |
|
574 ptr = newPtr; |
|
575 } |
|
576 |
|
577 argEnd = ptr; |
|
578 |
|
579 // there may be comments at EOL; ignore them when deciding what the directive |
|
580 // argument's range is |
|
581 while (true) { |
|
582 int prevComment = findPreviousComment(text, ptr, argStart, false); |
|
583 if (prevComment == -1) |
|
584 break; |
|
585 while (prevComment > argStart && Character.isWhitespace(text[prevComment-1])) |
|
586 prevComment--; |
|
587 argEnd = prevComment; |
|
588 ptr = argEnd; |
|
589 } |
|
590 |
|
591 String arg = new String(text, argStart, argEnd - argStart); |
|
592 //arg = CdtUtils.stripComments(arg).toString().trim(); |
|
593 return new Tuple(arg, dirStart, dirEnd, argStart, argEnd); |
|
594 } |
|
595 return null; |
|
596 } |
|
597 |
|
598 |
|
599 } |