source: trunk/tools/designer/editor/yyindent.cpp

Last change on this file was 197, checked in by rudi, 14 years ago

Added QtDesigner

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