Warning: That file was not part of the compilation database. It may have many parsing errors.
1 | /**************************************************************************** |
---|---|
2 | ** |
3 | ** Copyright (C) 2014 Digia Plc and/or its subsidiary(-ies). |
4 | ** Contact: http://www.qt-project.org/legal |
5 | ** |
6 | ** This file is part of the tools applications of the Qt Toolkit. |
7 | ** |
8 | ** $QT_BEGIN_LICENSE:LGPL$ |
9 | ** Commercial License Usage |
10 | ** Licensees holding valid commercial Qt licenses may use this file in |
11 | ** accordance with the commercial license agreement provided with the |
12 | ** Software or, alternatively, in accordance with the terms contained in |
13 | ** a written agreement between you and Digia. For licensing terms and |
14 | ** conditions see http://qt.digia.com/licensing. For further information |
15 | ** use the contact form at http://qt.digia.com/contact-us. |
16 | ** |
17 | ** GNU Lesser General Public License Usage |
18 | ** Alternatively, this file may be used under the terms of the GNU Lesser |
19 | ** General Public License version 2.1 as published by the Free Software |
20 | ** Foundation and appearing in the file LICENSE.LGPL included in the |
21 | ** packaging of this file. Please review the following information to |
22 | ** ensure the GNU Lesser General Public License version 2.1 requirements |
23 | ** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. |
24 | ** |
25 | ** In addition, as a special exception, Digia gives you certain additional |
26 | ** rights. These rights are described in the Digia Qt LGPL Exception |
27 | ** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. |
28 | ** |
29 | ** GNU General Public License Usage |
30 | ** Alternatively, this file may be used under the terms of the GNU |
31 | ** General Public License version 3.0 as published by the Free Software |
32 | ** Foundation and appearing in the file LICENSE.GPL included in the |
33 | ** packaging of this file. Please review the following information to |
34 | ** ensure the GNU General Public License version 3.0 requirements will be |
35 | ** met: http://www.gnu.org/copyleft/gpl.html. |
36 | ** |
37 | ** |
38 | ** $QT_END_LICENSE$ |
39 | ** |
40 | ****************************************************************************/ |
41 | |
42 | /* |
43 | This file is a self-contained interactive indenter for C++ and Qt |
44 | Script. |
45 | |
46 | The general problem of indenting a C++ program is ill posed. On |
47 | the one hand, an indenter has to analyze programs written in a |
48 | free-form formal language that is best described in terms of |
49 | tokens, not characters, not lines. On the other hand, indentation |
50 | applies to lines and white space characters matter, and otherwise |
51 | the programs to indent are formally invalid in general, as they |
52 | are begin edited. |
53 | |
54 | The approach taken here works line by line. We receive a program |
55 | consisting of N lines or more, and we want to compute the |
56 | indentation appropriate for the Nth line. Lines beyond the Nth |
57 | lines are of no concern to us, so for simplicity we pretend the |
58 | program has exactly N lines and we call the Nth line the "bottom |
59 | line". Typically, we have to indent the bottom line when it's |
60 | still empty, so we concentrate our analysis on the N - 1 lines |
61 | that precede. |
62 | |
63 | By inspecting the (N - 1)-th line, the (N - 2)-th line, ... |
64 | backwards, we determine the kind of the bottom line and indent it |
65 | accordingly. |
66 | |
67 | * The bottom line is a comment line. See |
68 | bottomLineStartsInCComment() and |
69 | indentWhenBottomLineStartsInCComment(). |
70 | * The bottom line is a continuation line. See isContinuationLine() |
71 | and indentForContinuationLine(). |
72 | * The bottom line is a standalone line. See |
73 | indentForStandaloneLine(). |
74 | |
75 | Certain tokens that influence the indentation, notably braces, |
76 | are looked for in the lines. This is done by simple string |
77 | comparison, without a real tokenizer. Confusing constructs such |
78 | as comments and string literals are removed beforehand. |
79 | */ |
80 | |
81 | #include <qregexp.h> |
82 | #include <qstringlist.h> |
83 | |
84 | QT_BEGIN_NAMESPACE |
85 | |
86 | /* qmake ignore Q_OBJECT */ |
87 | |
88 | /* |
89 | The indenter avoids getting stuck in almost infinite loops by |
90 | imposing arbitrary limits on the number of lines it analyzes when |
91 | looking for a construct. |
92 | |
93 | For example, the indenter never considers more than BigRoof lines |
94 | backwards when looking for the start of a C-style comment. |
95 | */ |
96 | static const int SmallRoof = 40; |
97 | static const int BigRoof = 400; |
98 | |
99 | /* |
100 | The indenter supports a few parameters: |
101 | |
102 | * ppHardwareTabSize is the size of a '\t' in your favorite editor. |
103 | * ppIndentSize is the size of an indentation, or software tab |
104 | size. |
105 | * ppContinuationIndentSize is the extra indent for a continuation |
106 | line, when there is nothing to align against on the previous |
107 | line. |
108 | * ppCommentOffset is the indentation within a C-style comment, |
109 | when it cannot be picked up. |
110 | */ |
111 | |
112 | static int ppHardwareTabSize = 8; |
113 | static int ppIndentSize = 4; |
114 | static int ppContinuationIndentSize = 8; |
115 | |
116 | static const int ppCommentOffset = 2; |
117 | |
118 | void setTabSize( int size ) |
119 | { |
120 | ppHardwareTabSize = size; |
121 | } |
122 | |
123 | void setIndentSize( int size ) |
124 | { |
125 | ppIndentSize = size; |
126 | ppContinuationIndentSize = 2 * size; |
127 | } |
128 | |
129 | static QRegExp *literal = 0; |
130 | static QRegExp *label = 0; |
131 | static QRegExp *inlineCComment = 0; |
132 | static QRegExp *braceX = 0; |
133 | static QRegExp *iflikeKeyword = 0; |
134 | |
135 | /* |
136 | Returns the first non-space character in the string t, or |
137 | QChar::Null if the string is made only of white space. |
138 | */ |
139 | static QChar firstNonWhiteSpace( const QString& t ) |
140 | { |
141 | int i = 0; |
142 | while ( i < (int) t.length() ) { |
143 | if ( !t[i].isSpace() ) |
144 | return t[i]; |
145 | i++; |
146 | } |
147 | return QChar::Null; |
148 | } |
149 | |
150 | /* |
151 | Returns true if string t is made only of white space; otherwise |
152 | returns false. |
153 | */ |
154 | static bool isOnlyWhiteSpace( const QString& t ) |
155 | { |
156 | return firstNonWhiteSpace( t ).isNull(); |
157 | } |
158 | |
159 | /* |
160 | Assuming string t is a line, returns the column number of a given |
161 | index. Column numbers and index are identical for strings that don't |
162 | contain '\t's. |
163 | */ |
164 | int columnForIndex( const QString& t, int index ) |
165 | { |
166 | int col = 0; |
167 | if ( index > (int) t.length() ) |
168 | index = t.length(); |
169 | |
170 | for ( int i = 0; i < index; i++ ) { |
171 | if ( t[i] == QChar( |
172 | col = ( (col / ppHardwareTabSize) + 1 ) * ppHardwareTabSize; |
173 | } else { |
174 | col++; |
175 | } |
176 | } |
177 | return col; |
178 | } |
179 | |
180 | /* |
181 | Returns the indentation size of string t. |
182 | */ |
183 | int indentOfLine( const QString& t ) |
184 | { |
185 | return columnForIndex( t, t.indexOf(firstNonWhiteSpace(t)) ); |
186 | } |
187 | |
188 | /* |
189 | Replaces t[k] by ch, unless t[k] is '\t'. Tab characters are better |
190 | left alone since they break the "index equals column" rule. No |
191 | provisions are taken against '\n' or '\r', which shouldn't occur in |
192 | t anyway. |
193 | */ |
194 | static inline void eraseChar( QString& t, int k, QChar ch ) |
195 | { |
196 | if ( t[k] != |
197 | t[k] = ch; |
198 | } |
199 | |
200 | /* |
201 | Removes some nefast constructs from a code line and returns the |
202 | resulting line. |
203 | */ |
204 | static QString trimmedCodeLine( const QString& t ) |
205 | { |
206 | QString trimmed = t; |
207 | int k; |
208 | |
209 | /* |
210 | Replace character and string literals by X's, since they may |
211 | contain confusing characters (such as '{' and ';'). "Hello!" is |
212 | replaced by XXXXXXXX. The literals are rigourously of the same |
213 | length before and after; otherwise, we would break alignment of |
214 | continuation lines. |
215 | */ |
216 | k = 0; |
217 | while ( (k = trimmed.indexOf(*literal, k)) != -1 ) { |
218 | for ( int i = 0; i < literal->matchedLength(); i++ ) |
219 | eraseChar( trimmed, k + i, |
220 | k += literal->matchedLength(); |
221 | } |
222 | |
223 | /* |
224 | Replace inline C-style comments by spaces. Other comments are |
225 | handled elsewhere. |
226 | */ |
227 | k = 0; |
228 | while ( (k = trimmed.indexOf(*inlineCComment, k)) != -1 ) { |
229 | for ( int i = 0; i < inlineCComment->matchedLength(); i++ ) |
230 | eraseChar( trimmed, k + i, |
231 | k += inlineCComment->matchedLength(); |
232 | } |
233 | |
234 | /* |
235 | Replace goto and switch labels by whitespace, but be careful |
236 | with this case: |
237 | |
238 | foo1: bar1; |
239 | bar2; |
240 | */ |
241 | while ( trimmed.lastIndexOf( |
242 | QString cap1 = label->cap( 1 ); |
243 | int pos1 = label->pos( 1 ); |
244 | int stop = cap1.length(); |
245 | |
246 | if ( pos1 + stop < (int) trimmed.length() && ppIndentSize < stop ) |
247 | stop = ppIndentSize; |
248 | |
249 | int i = 0; |
250 | while ( i < stop ) { |
251 | eraseChar( trimmed, pos1 + i, |
252 | i++; |
253 | } |
254 | while ( i < (int) cap1.length() ) { |
255 | eraseChar( trimmed, pos1 + i, |
256 | i++; |
257 | } |
258 | } |
259 | |
260 | /* |
261 | Remove C++-style comments. |
262 | */ |
263 | k = trimmed.indexOf( "//"); |
264 | if ( k != -1 ) |
265 | trimmed.truncate( k ); |
266 | |
267 | return trimmed; |
268 | } |
269 | |
270 | /* |
271 | Returns '(' if the last parenthesis is opening, ')' if it is |
272 | closing, and QChar::Null if there are no parentheses in t. |
273 | */ |
274 | static inline QChar lastParen( const QString& t ) |
275 | { |
276 | int i = t.length(); |
277 | while ( i > 0 ) { |
278 | i--; |
279 | if ( t[i] == QChar( |
280 | return t[i]; |
281 | } |
282 | return QChar::Null; |
283 | } |
284 | |
285 | /* |
286 | Returns true if typedIn the same as okayCh or is null; otherwise |
287 | returns false. |
288 | */ |
289 | static inline bool okay( QChar typedIn, QChar okayCh ) |
290 | { |
291 | return typedIn == QChar::Null || typedIn == okayCh; |
292 | } |
293 | |
294 | /* |
295 | The "linizer" is a group of functions and variables to iterate |
296 | through the source code of the program to indent. The program is |
297 | given as a list of strings, with the bottom line being the line |
298 | to indent. The actual program might contain extra lines, but |
299 | those are uninteresting and not passed over to us. |
300 | */ |
301 | |
302 | struct LinizerState |
303 | { |
304 | QString line; |
305 | int braceDepth; |
306 | bool leftBraceFollows; |
307 | |
308 | QStringList::ConstIterator iter; |
309 | bool inCComment; |
310 | bool pendingRightBrace; |
311 | }; |
312 | |
313 | static QStringList *yyProgram = 0; |
314 | static LinizerState *yyLinizerState = 0; |
315 | |
316 | // shorthands |
317 | static const QString *yyLine = 0; |
318 | static const int *yyBraceDepth = 0; |
319 | static const bool *yyLeftBraceFollows = 0; |
320 | |
321 | /* |
322 | Saves and restores the state of the global linizer. This enables |
323 | backtracking. |
324 | */ |
325 | #define YY_SAVE() \ |
326 | LinizerState savedState = *yyLinizerState |
327 | #define YY_RESTORE() \ |
328 | *yyLinizerState = savedState |
329 | |
330 | /* |
331 | Advances to the previous line in yyProgram and update yyLine |
332 | accordingly. yyLine is cleaned from comments and other damageable |
333 | constructs. Empty lines are skipped. |
334 | */ |
335 | static bool readLine() |
336 | { |
337 | int k; |
338 | |
339 | yyLinizerState->leftBraceFollows = |
340 | ( firstNonWhiteSpace(yyLinizerState->line) == QChar( |
341 | |
342 | do { |
343 | if ( yyLinizerState->iter == yyProgram->begin() ) { |
344 | yyLinizerState->line.clear(); |
345 | return false; |
346 | } |
347 | |
348 | --yyLinizerState->iter; |
349 | yyLinizerState->line = *yyLinizerState->iter; |
350 | |
351 | yyLinizerState->line = trimmedCodeLine( yyLinizerState->line ); |
352 | |
353 | /* |
354 | Remove C-style comments that span multiple lines. If the |
355 | bottom line starts in a C-style comment, we are not aware |
356 | of that and eventually yyLine will contain a slash-aster. |
357 | |
358 | Notice that both if's can be executed, since |
359 | yyLinizerState->inCComment is potentially set to false in |
360 | the first if. The order of the if's is also important. |
361 | */ |
362 | |
363 | if ( yyLinizerState->inCComment ) { |
364 | QString slashAster( "/*"); |
365 | |
366 | k = yyLinizerState->line.indexOf( slashAster ); |
367 | if ( k == -1 ) { |
368 | yyLinizerState->line.clear(); |
369 | } else { |
370 | yyLinizerState->line.truncate( k ); |
371 | yyLinizerState->inCComment = false; |
372 | } |
373 | } |
374 | |
375 | if ( !yyLinizerState->inCComment ) { |
376 | QString asterSlash( "*/"); |
377 | |
378 | k = yyLinizerState->line.indexOf( asterSlash ); |
379 | if ( k != -1 ) { |
380 | for ( int i = 0; i < k + 2; i++ ) |
381 | eraseChar( yyLinizerState->line, i, |
382 | yyLinizerState->inCComment = true; |
383 | } |
384 | } |
385 | |
386 | /* |
387 | Remove preprocessor directives. |
388 | */ |
389 | k = 0; |
390 | while ( k < (int) yyLinizerState->line.length() ) { |
391 | QChar ch = yyLinizerState->line[k]; |
392 | if ( ch == QChar( |
393 | yyLinizerState->line.clear(); |
394 | } else if ( !ch.isSpace() ) { |
395 | break; |
396 | } |
397 | k++; |
398 | } |
399 | |
400 | /* |
401 | Remove trailing spaces. |
402 | */ |
403 | k = yyLinizerState->line.length(); |
404 | while ( k > 0 && yyLinizerState->line[k - 1].isSpace() ) |
405 | k--; |
406 | yyLinizerState->line.truncate( k ); |
407 | |
408 | /* |
409 | '}' increment the brace depth and '{' decrements it and not |
410 | the other way around, as we are parsing backwards. |
411 | */ |
412 | yyLinizerState->braceDepth += |
413 | yyLinizerState->line.count( |
414 | yyLinizerState->line.count( |
415 | |
416 | /* |
417 | We use a dirty trick for |
418 | |
419 | } else ... |
420 | |
421 | We don't count the '}' yet, so that it's more or less |
422 | equivalent to the friendly construct |
423 | |
424 | } |
425 | else ... |
426 | */ |
427 | if ( yyLinizerState->pendingRightBrace ) |
428 | yyLinizerState->braceDepth++; |
429 | yyLinizerState->pendingRightBrace = |
430 | ( yyLinizerState->line.indexOf(*braceX) == 0 ); |
431 | if ( yyLinizerState->pendingRightBrace ) |
432 | yyLinizerState->braceDepth--; |
433 | } while ( yyLinizerState->line.isEmpty() ); |
434 | |
435 | return true; |
436 | } |
437 | |
438 | /* |
439 | Resets the linizer to its initial state, with yyLine containing the |
440 | line above the bottom line of the program. |
441 | */ |
442 | static void startLinizer() |
443 | { |
444 | yyLinizerState->braceDepth = 0; |
445 | yyLinizerState->inCComment = false; |
446 | yyLinizerState->pendingRightBrace = false; |
447 | |
448 | yyLine = &yyLinizerState->line; |
449 | yyBraceDepth = &yyLinizerState->braceDepth; |
450 | yyLeftBraceFollows = &yyLinizerState->leftBraceFollows; |
451 | |
452 | yyLinizerState->iter = yyProgram->end(); |
453 | --yyLinizerState->iter; |
454 | yyLinizerState->line = *yyLinizerState->iter; |
455 | readLine(); |
456 | } |
457 | |
458 | /* |
459 | Returns true if the start of the bottom line of yyProgram (and |
460 | potentially the whole line) is part of a C-style comment; |
461 | otherwise returns false. |
462 | */ |
463 | static bool bottomLineStartsInCComment() |
464 | { |
465 | QString slashAster( "/*"); |
466 | QString asterSlash( "*/"); |
467 | |
468 | /* |
469 | We could use the linizer here, but that would slow us down |
470 | terribly. We are better to trim only the code lines we need. |
471 | */ |
472 | QStringList::ConstIterator p = yyProgram->end(); |
473 | --p; // skip bottom line |
474 | |
475 | for ( int i = 0; i < BigRoof; i++ ) { |
476 | if ( p == yyProgram->begin() ) |
477 | return false; |
478 | --p; |
479 | |
480 | if ( (*p).indexOf(slashAster) != -1 || (*p).indexOf(asterSlash) != -1 ) { |
481 | QString trimmed = trimmedCodeLine( *p ); |
482 | |
483 | if ( trimmed.indexOf(slashAster) != -1 ) { |
484 | return true; |
485 | } else if ( trimmed.indexOf(asterSlash) != -1 ) { |
486 | return false; |
487 | } |
488 | } |
489 | } |
490 | return false; |
491 | } |
492 | |
493 | /* |
494 | Returns the recommended indent for the bottom line of yyProgram |
495 | assuming that it starts in a C-style comment, a condition that is |
496 | tested elsewhere. |
497 | |
498 | Essentially, we're trying to align against some text on the |
499 | previous line. |
500 | */ |
501 | static int indentWhenBottomLineStartsInCComment() |
502 | { |
503 | int k = yyLine->lastIndexOf( "/*"); |
504 | if ( k == -1 ) { |
505 | /* |
506 | We found a normal text line in a comment. Align the |
507 | bottom line with the text on this line. |
508 | */ |
509 | return indentOfLine( *yyLine ); |
510 | } else { |
511 | /* |
512 | The C-style comment starts on this line. If there is |
513 | text on the same line, align with it. Otherwise, align |
514 | with the slash-aster plus a given offset. |
515 | */ |
516 | int indent = columnForIndex( *yyLine, k ); |
517 | k += 2; |
518 | while ( k < (int) yyLine->length() ) { |
519 | if ( !(*yyLine)[k].isSpace() ) |
520 | return columnForIndex( *yyLine, k ); |
521 | k++; |
522 | } |
523 | return indent + ppCommentOffset; |
524 | } |
525 | } |
526 | |
527 | /* |
528 | A function called match...() modifies the linizer state. If it |
529 | returns true, yyLine is the top line of the matched construct; |
530 | otherwise, the linizer is left in an unknown state. |
531 | |
532 | A function called is...() keeps the linizer state intact. |
533 | */ |
534 | |
535 | /* |
536 | Returns true if the current line (and upwards) forms a braceless |
537 | control statement; otherwise returns false. |
538 | |
539 | The first line of the following example is a "braceless control |
540 | statement": |
541 | |
542 | if ( x ) |
543 | y; |
544 | */ |
545 | static bool matchBracelessControlStatement() |
546 | { |
547 | int delimDepth = 0; |
548 | |
549 | if ( yyLine->endsWith("else") ) |
550 | return true; |
551 | |
552 | if ( !yyLine->endsWith(")") ) |
553 | return false; |
554 | |
555 | for ( int i = 0; i < SmallRoof; i++ ) { |
556 | int j = yyLine->length(); |
557 | while ( j > 0 ) { |
558 | j--; |
559 | QChar ch = (*yyLine)[j]; |
560 | |
561 | switch ( ch.unicode() ) { |
562 | case |
563 | delimDepth++; |
564 | break; |
565 | case |
566 | delimDepth--; |
567 | if ( delimDepth == 0 ) { |
568 | if ( yyLine->indexOf(*iflikeKeyword) != -1 ) { |
569 | /* |
570 | We have |
571 | |
572 | if ( x ) |
573 | y |
574 | |
575 | "if ( x )" is not part of the statement |
576 | "y". |
577 | */ |
578 | return true; |
579 | } |
580 | } |
581 | if ( delimDepth == -1 ) { |
582 | /* |
583 | We have |
584 | |
585 | if ( (1 + |
586 | 2) |
587 | |
588 | and not |
589 | |
590 | if ( 1 + |
591 | 2 ) |
592 | */ |
593 | return false; |
594 | } |
595 | break; |
596 | case |
597 | case |
598 | case |
599 | /* |
600 | We met a statement separator, but not where we |
601 | expected it. What follows is probably a weird |
602 | continuation line. Be careful with ';' in for, |
603 | though. |
604 | */ |
605 | if ( ch != QChar( |
606 | return false; |
607 | } |
608 | } |
609 | |
610 | if ( !readLine() ) |
611 | break; |
612 | } |
613 | return false; |
614 | } |
615 | |
616 | /* |
617 | Returns true if yyLine is an unfinished line; otherwise returns |
618 | false. |
619 | |
620 | In many places we'll use the terms "standalone line", "unfinished |
621 | line" and "continuation line". The meaning of these should be |
622 | evident from this code example: |
623 | |
624 | a = b; // standalone line |
625 | c = d + // unfinished line |
626 | e + // unfinished continuation line |
627 | f + // unfinished continuation line |
628 | g; // continuation line |
629 | */ |
630 | static bool isUnfinishedLine() |
631 | { |
632 | bool unf = false; |
633 | |
634 | YY_SAVE(); |
635 | |
636 | if ( yyLine->isEmpty() ) |
637 | return false; |
638 | |
639 | QChar lastCh = (*yyLine)[(int) yyLine->length() - 1]; |
640 | if ( QString("{};").indexOf(lastCh) == -1 && !yyLine->endsWith( "...") ) { |
641 | /* |
642 | It doesn't end with ';' or similar. If it's neither |
643 | "Q_OBJECT" nor "if ( x )", it must be an unfinished line. |
644 | */ |
645 | unf = ( yyLine->indexOf("Q_OBJECT") == -1 && |
646 | !matchBracelessControlStatement() ); |
647 | } else if ( lastCh == QChar( |
648 | if ( lastParen(*yyLine) == QChar( |
649 | /* |
650 | Exception: |
651 | |
652 | for ( int i = 1; i < 10; |
653 | */ |
654 | unf = true; |
655 | } else if ( readLine() && yyLine->endsWith(";") && |
656 | lastParen(*yyLine) == QChar( |
657 | /* |
658 | Exception: |
659 | |
660 | for ( int i = 1; |
661 | i < 10; |
662 | */ |
663 | unf = true; |
664 | } |
665 | } |
666 | |
667 | YY_RESTORE(); |
668 | return unf; |
669 | } |
670 | |
671 | /* |
672 | Returns true if yyLine is a continuation line; otherwise returns |
673 | false. |
674 | */ |
675 | static bool isContinuationLine() |
676 | { |
677 | bool cont = false; |
678 | |
679 | YY_SAVE(); |
680 | if ( readLine() ) |
681 | cont = isUnfinishedLine(); |
682 | YY_RESTORE(); |
683 | return cont; |
684 | } |
685 | |
686 | /* |
687 | Returns the recommended indent for the bottom line of yyProgram, |
688 | assuming it's a continuation line. |
689 | |
690 | We're trying to align the continuation line against some parenthesis |
691 | or other bracked left opened on a previous line, or some interesting |
692 | operator such as '='. |
693 | */ |
694 | static int indentForContinuationLine() |
695 | { |
696 | int braceDepth = 0; |
697 | int delimDepth = 0; |
698 | |
699 | bool leftBraceFollowed = *yyLeftBraceFollows; |
700 | |
701 | for ( int i = 0; i < SmallRoof; i++ ) { |
702 | int hook = -1; |
703 | |
704 | int j = yyLine->length(); |
705 | while ( j > 0 && hook < 0 ) { |
706 | j--; |
707 | QChar ch = (*yyLine)[j]; |
708 | |
709 | switch ( ch.unicode() ) { |
710 | case |
711 | case |
712 | delimDepth++; |
713 | break; |
714 | case |
715 | braceDepth++; |
716 | break; |
717 | case |
718 | case |
719 | delimDepth--; |
720 | /* |
721 | An unclosed delimiter is a good place to align at, |
722 | at least for some styles (including Qt's). |
723 | */ |
724 | if ( delimDepth == -1 ) |
725 | hook = j; |
726 | break; |
727 | case |
728 | braceDepth--; |
729 | /* |
730 | A left brace followed by other stuff on the same |
731 | line is typically for an enum or an initializer. |
732 | Such a brace must be treated just like the other |
733 | delimiters. |
734 | */ |
735 | if ( braceDepth == -1 ) { |
736 | if ( j < (int) yyLine->length() - 1 ) { |
737 | hook = j; |
738 | } else { |
739 | return 0; // shouldn't happen |
740 | } |
741 | } |
742 | break; |
743 | case |
744 | /* |
745 | An equal sign is a very natural alignment hook |
746 | because it's usually the operator with the lowest |
747 | precedence in statements it appears in. Case in |
748 | point: |
749 | |
750 | int x = 1 + |
751 | 2; |
752 | |
753 | However, we have to beware of constructs such as |
754 | default arguments and explicit enum constant |
755 | values: |
756 | |
757 | void foo( int x = 0, |
758 | int y = 0 ); |
759 | |
760 | And not |
761 | |
762 | void foo( int x = 0, |
763 | int y = 0 ); |
764 | |
765 | These constructs are caracterized by a ',' at the |
766 | end of the unfinished lines or by unbalanced |
767 | parentheses. |
768 | */ |
769 | if ( QString("!=<>").indexOf((*yyLine)[j - 1]) == -1 && |
770 | (*yyLine)[j + 1] != |
771 | if ( braceDepth == 0 && delimDepth == 0 && |
772 | j < (int) yyLine->length() - 1 && |
773 | !yyLine->endsWith(",") && |
774 | (yyLine->contains( |
775 | hook = j; |
776 | } |
777 | } |
778 | } |
779 | |
780 | if ( hook >= 0 ) { |
781 | /* |
782 | Yes, we have a delimiter or an operator to align |
783 | against! We don't really align against it, but rather |
784 | against the following token, if any. In this example, |
785 | the following token is "11": |
786 | |
787 | int x = ( 11 + |
788 | 2 ); |
789 | |
790 | If there is no such token, we use a continuation indent: |
791 | |
792 | static QRegExp foo( QString( |
793 | "foo foo foo foo foo foo foo foo foo") ); |
794 | */ |
795 | hook++; |
796 | while ( hook < (int) yyLine->length() ) { |
797 | if ( !(*yyLine)[hook].isSpace() ) |
798 | return columnForIndex( *yyLine, hook ); |
799 | hook++; |
800 | } |
801 | return indentOfLine( *yyLine ) + ppContinuationIndentSize; |
802 | } |
803 | |
804 | if ( braceDepth != 0 ) |
805 | break; |
806 | |
807 | /* |
808 | The line's delimiters are balanced. It looks like a |
809 | continuation line or something. |
810 | */ |
811 | if ( delimDepth == 0 ) { |
812 | if ( leftBraceFollowed ) { |
813 | /* |
814 | We have |
815 | |
816 | int main() |
817 | { |
818 | |
819 | or |
820 | |
821 | Bar::Bar() |
822 | : Foo( x ) |
823 | { |
824 | |
825 | The "{" should be flush left. |
826 | */ |
827 | if ( !isContinuationLine() ) |
828 | return indentOfLine( *yyLine ); |
829 | } else if ( isContinuationLine() || yyLine->endsWith(",") ) { |
830 | /* |
831 | We have |
832 | |
833 | x = a + |
834 | b + |
835 | c; |
836 | |
837 | or |
838 | |
839 | int t[] = { |
840 | 1, 2, 3, |
841 | 4, 5, 6 |
842 | |
843 | The "c;" should fall right under the "b +", and the |
844 | "4, 5, 6" right under the "1, 2, 3,". |
845 | */ |
846 | return indentOfLine( *yyLine ); |
847 | } else { |
848 | /* |
849 | We have |
850 | |
851 | stream << 1 + |
852 | 2; |
853 | |
854 | We could, but we don't, try to analyze which |
855 | operator has precedence over which and so on, to |
856 | obtain the excellent result |
857 | |
858 | stream << 1 + |
859 | 2; |
860 | |
861 | We do have a special trick above for the assignment |
862 | operator above, though. |
863 | */ |
864 | return indentOfLine( *yyLine ) + ppContinuationIndentSize; |
865 | } |
866 | } |
867 | |
868 | if ( !readLine() ) |
869 | break; |
870 | } |
871 | return 0; |
872 | } |
873 | |
874 | /* |
875 | Returns the recommended indent for the bottom line of yyProgram if |
876 | that line is standalone (or should be indented likewise). |
877 | |
878 | Indenting a standalone line is tricky, mostly because of braceless |
879 | control statements. Grossly, we are looking backwards for a special |
880 | line, a "hook line", that we can use as a starting point to indent, |
881 | and then modify the indentation level according to the braces met |
882 | along the way to that hook. |
883 | |
884 | Let's consider a few examples. In all cases, we want to indent the |
885 | bottom line. |
886 | |
887 | Example 1: |
888 | |
889 | x = 1; |
890 | y = 2; |
891 | |
892 | The hook line is "x = 1;". We met 0 opening braces and 0 closing |
893 | braces. Therefore, "y = 2;" inherits the indent of "x = 1;". |
894 | |
895 | Example 2: |
896 | |
897 | if ( x ) { |
898 | y; |
899 | |
900 | The hook line is "if ( x ) {". No matter what precedes it, "y;" has |
901 | to be indented one level deeper than the hook line, since we met one |
902 | opening brace along the way. |
903 | |
904 | Example 3: |
905 | |
906 | if ( a ) |
907 | while ( b ) { |
908 | c; |
909 | } |
910 | d; |
911 | |
912 | To indent "d;" correctly, we have to go as far as the "if ( a )". |
913 | Compare with |
914 | |
915 | if ( a ) { |
916 | while ( b ) { |
917 | c; |
918 | } |
919 | d; |
920 | |
921 | Still, we're striving to go back as little as possible to |
922 | accommodate people with irregular indentation schemes. A hook line |
923 | near at hand is much more reliable than a remote one. |
924 | */ |
925 | static int indentForStandaloneLine() |
926 | { |
927 | for ( int i = 0; i < SmallRoof; i++ ) { |
928 | if ( !*yyLeftBraceFollows ) { |
929 | YY_SAVE(); |
930 | |
931 | if ( matchBracelessControlStatement() ) { |
932 | /* |
933 | The situation is this, and we want to indent "z;": |
934 | |
935 | if ( x && |
936 | y ) |
937 | z; |
938 | |
939 | yyLine is "if ( x &&". |
940 | */ |
941 | return indentOfLine( *yyLine ) + ppIndentSize; |
942 | } |
943 | YY_RESTORE(); |
944 | } |
945 | |
946 | if ( yyLine->endsWith(";") || yyLine->contains( |
947 | /* |
948 | The situation is possibly this, and we want to indent |
949 | "z;": |
950 | |
951 | while ( x ) |
952 | y; |
953 | z; |
954 | |
955 | We return the indent of "while ( x )". In place of "y;", |
956 | any arbitrarily complex compound statement can appear. |
957 | */ |
958 | |
959 | if ( *yyBraceDepth > 0 ) { |
960 | do { |
961 | if ( !readLine() ) |
962 | break; |
963 | } while ( *yyBraceDepth > 0 ); |
964 | } |
965 | |
966 | LinizerState hookState; |
967 | |
968 | while ( isContinuationLine() ) |
969 | readLine(); |
970 | hookState = *yyLinizerState; |
971 | |
972 | readLine(); |
973 | if ( *yyBraceDepth <= 0 ) { |
974 | do { |
975 | if ( !matchBracelessControlStatement() ) |
976 | break; |
977 | hookState = *yyLinizerState; |
978 | } while ( readLine() ); |
979 | } |
980 | |
981 | *yyLinizerState = hookState; |
982 | |
983 | while ( isContinuationLine() ) |
984 | readLine(); |
985 | |
986 | /* |
987 | Never trust lines containing only '{' or '}', as some |
988 | people (Richard M. Stallman) format them weirdly. |
989 | */ |
990 | if ( yyLine->trimmed().length() > 1 ) |
991 | return indentOfLine( *yyLine ) - *yyBraceDepth * ppIndentSize; |
992 | } |
993 | |
994 | if ( !readLine() ) |
995 | return -*yyBraceDepth * ppIndentSize; |
996 | } |
997 | return 0; |
998 | } |
999 | |
1000 | /* |
1001 | Constructs global variables used by the indenter. |
1002 | */ |
1003 | static void initializeIndenter() |
1004 | { |
1005 | literal = new QRegExp( "([\"'])(?:\\\\.|[^\\\\])*\\1"); |
1006 | literal->setMinimal( true ); |
1007 | label = new QRegExp( |
1008 | "^\\s*((?:case\\b([^:]|::)+|[a-zA-Z_0-9]+)(?:\\s+slots)?:)(?!:)"); |
1009 | inlineCComment = new QRegExp( "/\\*.*\\*/"); |
1010 | inlineCComment->setMinimal( true ); |
1011 | braceX = new QRegExp( "^\\s*\\}\\s*(?:else|catch)\\b"); |
1012 | iflikeKeyword = new QRegExp( "\\b(?:catch|do|for|if|while)\\b"); |
1013 | |
1014 | yyLinizerState = new LinizerState; |
1015 | } |
1016 | |
1017 | /* |
1018 | Destroys global variables used by the indenter. |
1019 | */ |
1020 | static void terminateIndenter() |
1021 | { |
1022 | delete literal; |
1023 | delete label; |
1024 | delete inlineCComment; |
1025 | delete braceX; |
1026 | delete iflikeKeyword; |
1027 | delete yyLinizerState; |
1028 | } |
1029 | |
1030 | /* |
1031 | Returns the recommended indent for the bottom line of program. |
1032 | Unless null, typedIn stores the character of yyProgram that |
1033 | triggered reindentation. |
1034 | |
1035 | This function works better if typedIn is set properly; it is |
1036 | slightly more conservative if typedIn is completely wild, and |
1037 | slighly more liberal if typedIn is always null. The user might be |
1038 | annoyed by the liberal behavior. |
1039 | */ |
1040 | int indentForBottomLine( const QStringList& program, QChar typedIn ) |
1041 | { |
1042 | if ( program.isEmpty() ) |
1043 | return 0; |
1044 | |
1045 | initializeIndenter(); |
1046 | |
1047 | yyProgram = new QStringList( program ); |
1048 | startLinizer(); |
1049 | |
1050 | const QString& bottomLine = program.last(); |
1051 | QChar firstCh = firstNonWhiteSpace( bottomLine ); |
1052 | int indent; |
1053 | |
1054 | if ( bottomLineStartsInCComment() ) { |
1055 | /* |
1056 | The bottom line starts in a C-style comment. Indent it |
1057 | smartly, unless the user has already played around with it, |
1058 | in which case it's better to leave her stuff alone. |
1059 | */ |
1060 | if ( isOnlyWhiteSpace(bottomLine) ) { |
1061 | indent = indentWhenBottomLineStartsInCComment(); |
1062 | } else { |
1063 | indent = indentOfLine( bottomLine ); |
1064 | } |
1065 | } else if ( okay(typedIn, |
1066 | /* |
1067 | Preprocessor directives go flush left. |
1068 | */ |
1069 | indent = 0; |
1070 | } else { |
1071 | if ( isUnfinishedLine() ) { |
1072 | indent = indentForContinuationLine(); |
1073 | } else { |
1074 | indent = indentForStandaloneLine(); |
1075 | } |
1076 | |
1077 | if ( okay(typedIn, |
1078 | /* |
1079 | A closing brace is one level more to the left than the |
1080 | code it follows. |
1081 | */ |
1082 | indent -= ppIndentSize; |
1083 | } else if ( okay(typedIn, |
1084 | QRegExp caseLabel( |
1085 | "\\s*(?:case\\b(?:[^:]|::)+" |
1086 | "|(?:public|protected|private|signals|default)(?:\\s+slots)?\\s*" |
1087 | ")?:.*"); |
1088 | |
1089 | if ( caseLabel.exactMatch(bottomLine) ) { |
1090 | /* |
1091 | Move a case label (or the ':' in front of a |
1092 | constructor initialization list) one level to the |
1093 | left, but only if the user did not play around with |
1094 | it yet. Some users have exotic tastes in the |
1095 | matter, and most users probably are not patient |
1096 | enough to wait for the final ':' to format their |
1097 | code properly. |
1098 | |
1099 | We don't attempt the same for goto labels, as the |
1100 | user is probably the middle of "foo::bar". (Who |
1101 | uses goto, anyway?) |
1102 | */ |
1103 | if ( indentOfLine(bottomLine) <= indent ) |
1104 | indent -= ppIndentSize; |
1105 | else |
1106 | indent = indentOfLine( bottomLine ); |
1107 | } |
1108 | } |
1109 | } |
1110 | delete yyProgram; |
1111 | terminateIndenter(); |
1112 | return qMax( 0, indent ); |
1113 | } |
1114 | |
1115 | QT_END_NAMESPACE |
1116 | |
1117 | #ifdef Q_TEST_YYINDENT |
1118 | /* |
1119 | Test driver. |
1120 | */ |
1121 | |
1122 | #include <qfile.h> |
1123 | #include <qtextstream.h> |
1124 | |
1125 | #include <errno.h> |
1126 | |
1127 | QT_BEGIN_NAMESPACE |
1128 | |
1129 | static QString fileContents( const QString& fileName ) |
1130 | { |
1131 | QFile f( fileName ); |
1132 | if ( !f.open(QFile::ReadOnly) ) { |
1133 | qWarning( "yyindent error: Cannot open file '%s' for reading: %s", |
1134 | fileName.toLatin1().data(), strerror(errno) ); |
1135 | return QString(); |
1136 | } |
1137 | |
1138 | QTextStream t( &f ); |
1139 | QString contents = t.read(); |
1140 | f.close(); |
1141 | if ( contents.isEmpty() ) |
1142 | qWarning( "yyindent error: File '%s' is empty", fileName.toLatin1().data() ); |
1143 | return contents; |
1144 | } |
1145 | |
1146 | QT_END_NAMESPACE |
1147 | |
1148 | int main( int argc, char **argv ) |
1149 | { |
1150 | QT_USE_NAMESPACE |
1151 | |
1152 | if ( argc != 2 ) { |
1153 | qWarning( "usage: yyindent file.cpp"); |
1154 | return 1; |
1155 | } |
1156 | |
1157 | QString code = fileContents( argv[1] ); |
1158 | QStringList program = QStringList::split( |
1159 | QStringList p; |
1160 | QString out; |
1161 | |
1162 | while ( !program.isEmpty() && program.last().trimmed().isEmpty() ) |
1163 | program.remove( program.fromLast() ); |
1164 | |
1165 | QStringList::ConstIterator line = program.begin(); |
1166 | while ( line != program.end() ) { |
1167 | p.push_back( *line ); |
1168 | QChar typedIn = firstNonWhiteSpace( *line ); |
1169 | if ( p.last().endsWith(":") ) |
1170 | typedIn = |
1171 | |
1172 | int indent = indentForBottomLine( p, typedIn ); |
1173 | |
1174 | if ( !(*line).trimmed().isEmpty() ) { |
1175 | for ( int j = 0; j < indent; j++ ) |
1176 | out += " "; |
1177 | out += (*line).trimmed(); |
1178 | } |
1179 | out += "\n"; |
1180 | ++line; |
1181 | } |
1182 | |
1183 | while ( out.endsWith("\n") ) |
1184 | out.truncate( out.length() - 1 ); |
1185 | |
1186 | printf( "%s\n", out.toLatin1().data() ); |
1187 | return 0; |
1188 | } |
1189 | |
1190 | #endif // Q_TEST_YYINDENT |
1191 |
Warning: That file was not part of the compilation database. It may have many parsing errors.