/****************************************************************************+ −
**+ −
** Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies).+ −
** All rights reserved.+ −
** Contact: Nokia Corporation (qt-info@nokia.com)+ −
**+ −
** This file is part of the tools applications of the Qt Toolkit.+ −
**+ −
** $QT_BEGIN_LICENSE:LGPL$+ −
** No Commercial Usage+ −
** This file contains pre-release code and may not be distributed.+ −
** You may use this file in accordance with the terms and conditions+ −
** contained in the Technology Preview License Agreement accompanying+ −
** this package.+ −
**+ −
** GNU Lesser General Public License Usage+ −
** Alternatively, this file may be used under the terms of the GNU Lesser+ −
** General Public License version 2.1 as published by the Free Software+ −
** Foundation and appearing in the file LICENSE.LGPL included in the+ −
** packaging of this file. Please review the following information to+ −
** ensure the GNU Lesser General Public License version 2.1 requirements+ −
** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.+ −
**+ −
** In addition, as a special exception, Nokia gives you certain additional+ −
** rights. These rights are described in the Nokia Qt LGPL Exception+ −
** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.+ −
**+ −
** If you have questions regarding the use of this file, please contact+ −
** Nokia at qt-info@nokia.com.+ −
**+ −
**+ −
**+ −
**+ −
**+ −
**+ −
**+ −
**+ −
** $QT_END_LICENSE$+ −
**+ −
****************************************************************************/+ −
+ −
/*+ −
This file is a self-contained interactive indenter for C++ and Qt+ −
Script.+ −
+ −
The general problem of indenting a C++ program is ill posed. On+ −
the one hand, an indenter has to analyze programs written in a+ −
free-form formal language that is best described in terms of+ −
tokens, not characters, not lines. On the other hand, indentation+ −
applies to lines and white space characters matter, and otherwise+ −
the programs to indent are formally invalid in general, as they+ −
are begin edited.+ −
+ −
The approach taken here works line by line. We receive a program+ −
consisting of N lines or more, and we want to compute the+ −
indentation appropriate for the Nth line. Lines beyond the Nth+ −
lines are of no concern to us, so for simplicity we pretend the+ −
program has exactly N lines and we call the Nth line the "bottom+ −
line". Typically, we have to indent the bottom line when it's+ −
still empty, so we concentrate our analysis on the N - 1 lines+ −
that precede.+ −
+ −
By inspecting the (N - 1)-th line, the (N - 2)-th line, ...+ −
backwards, we determine the kind of the bottom line and indent it+ −
accordingly.+ −
+ −
* The bottom line is a comment line. See+ −
bottomLineStartsInCComment() and+ −
indentWhenBottomLineStartsInCComment().+ −
* The bottom line is a continuation line. See isContinuationLine()+ −
and indentForContinuationLine().+ −
* The bottom line is a standalone line. See+ −
indentForStandaloneLine().+ −
+ −
Certain tokens that influence the indentation, notably braces,+ −
are looked for in the lines. This is done by simple string+ −
comparison, without a real tokenizer. Confusing constructs such+ −
as comments and string literals are removed beforehand.+ −
*/+ −
+ −
#include <qregexp.h>+ −
#include <qstringlist.h>+ −
+ −
QT_BEGIN_NAMESPACE+ −
+ −
/* qmake ignore Q_OBJECT */+ −
+ −
/*+ −
The indenter avoids getting stuck in almost infinite loops by+ −
imposing arbitrary limits on the number of lines it analyzes when+ −
looking for a construct.+ −
+ −
For example, the indenter never considers more than BigRoof lines+ −
backwards when looking for the start of a C-style comment.+ −
*/+ −
static const int SmallRoof = 40;+ −
static const int BigRoof = 400;+ −
+ −
/*+ −
The indenter supports a few parameters:+ −
+ −
* ppHardwareTabSize is the size of a '\t' in your favorite editor.+ −
* ppIndentSize is the size of an indentation, or software tab+ −
size.+ −
* ppContinuationIndentSize is the extra indent for a continuation+ −
line, when there is nothing to align against on the previous+ −
line.+ −
* ppCommentOffset is the indentation within a C-style comment,+ −
when it cannot be picked up.+ −
*/+ −
+ −
static int ppHardwareTabSize = 8;+ −
static int ppIndentSize = 4;+ −
static int ppContinuationIndentSize = 8;+ −
+ −
static const int ppCommentOffset = 2;+ −
+ −
void setTabSize( int size )+ −
{+ −
ppHardwareTabSize = size;+ −
}+ −
+ −
void setIndentSize( int size )+ −
{+ −
ppIndentSize = size;+ −
ppContinuationIndentSize = 2 * size;+ −
}+ −
+ −
static QRegExp *literal = 0;+ −
static QRegExp *label = 0;+ −
static QRegExp *inlineCComment = 0;+ −
static QRegExp *braceX = 0;+ −
static QRegExp *iflikeKeyword = 0;+ −
+ −
/*+ −
Returns the first non-space character in the string t, or+ −
QChar::Null if the string is made only of white space.+ −
*/+ −
static QChar firstNonWhiteSpace( const QString& t )+ −
{+ −
int i = 0;+ −
while ( i < (int) t.length() ) {+ −
if ( !t[i].isSpace() )+ −
return t[i];+ −
i++;+ −
}+ −
return QChar::Null;+ −
}+ −
+ −
/*+ −
Returns true if string t is made only of white space; otherwise+ −
returns false.+ −
*/+ −
static bool isOnlyWhiteSpace( const QString& t )+ −
{+ −
return firstNonWhiteSpace( t ).isNull();+ −
}+ −
+ −
/*+ −
Assuming string t is a line, returns the column number of a given+ −
index. Column numbers and index are identical for strings that don't+ −
contain '\t's.+ −
*/+ −
int columnForIndex( const QString& t, int index )+ −
{+ −
int col = 0;+ −
if ( index > (int) t.length() )+ −
index = t.length();+ −
+ −
for ( int i = 0; i < index; i++ ) {+ −
if ( t[i] == QChar('\t') ) {+ −
col = ( (col / ppHardwareTabSize) + 1 ) * ppHardwareTabSize;+ −
} else {+ −
col++;+ −
}+ −
}+ −
return col;+ −
}+ −
+ −
/*+ −
Returns the indentation size of string t.+ −
*/+ −
int indentOfLine( const QString& t )+ −
{+ −
return columnForIndex( t, t.indexOf(firstNonWhiteSpace(t)) );+ −
}+ −
+ −
/*+ −
Replaces t[k] by ch, unless t[k] is '\t'. Tab characters are better+ −
left alone since they break the "index equals column" rule. No+ −
provisions are taken against '\n' or '\r', which shouldn't occur in+ −
t anyway.+ −
*/+ −
static inline void eraseChar( QString& t, int k, QChar ch )+ −
{+ −
if ( t[k] != '\t' )+ −
t[k] = ch;+ −
}+ −
+ −
/*+ −
Removes some nefast constructs from a code line and returns the+ −
resulting line.+ −
*/+ −
static QString trimmedCodeLine( const QString& t )+ −
{+ −
QString trimmed = t;+ −
int k;+ −
+ −
/*+ −
Replace character and string literals by X's, since they may+ −
contain confusing characters (such as '{' and ';'). "Hello!" is+ −
replaced by XXXXXXXX. The literals are rigourously of the same+ −
length before and after; otherwise, we would break alignment of+ −
continuation lines.+ −
*/+ −
k = 0;+ −
while ( (k = trimmed.indexOf(*literal, k)) != -1 ) {+ −
for ( int i = 0; i < literal->matchedLength(); i++ )+ −
eraseChar( trimmed, k + i, 'X' );+ −
k += literal->matchedLength();+ −
}+ −
+ −
/*+ −
Replace inline C-style comments by spaces. Other comments are+ −
handled elsewhere.+ −
*/+ −
k = 0;+ −
while ( (k = trimmed.indexOf(*inlineCComment, k)) != -1 ) {+ −
for ( int i = 0; i < inlineCComment->matchedLength(); i++ )+ −
eraseChar( trimmed, k + i, ' ' );+ −
k += inlineCComment->matchedLength();+ −
}+ −
+ −
/*+ −
Replace goto and switch labels by whitespace, but be careful+ −
with this case:+ −
+ −
foo1: bar1;+ −
bar2;+ −
*/+ −
while ( trimmed.lastIndexOf(':') != -1 && trimmed.indexOf(*label) != -1 ) {+ −
QString cap1 = label->cap( 1 );+ −
int pos1 = label->pos( 1 );+ −
int stop = cap1.length();+ −
+ −
if ( pos1 + stop < (int) trimmed.length() && ppIndentSize < stop )+ −
stop = ppIndentSize;+ −
+ −
int i = 0;+ −
while ( i < stop ) {+ −
eraseChar( trimmed, pos1 + i, ' ' );+ −
i++;+ −
}+ −
while ( i < (int) cap1.length() ) {+ −
eraseChar( trimmed, pos1 + i, ';' );+ −
i++;+ −
}+ −
}+ −
+ −
/*+ −
Remove C++-style comments.+ −
*/+ −
k = trimmed.indexOf( "//" );+ −
if ( k != -1 )+ −
trimmed.truncate( k );+ −
+ −
return trimmed;+ −
}+ −
+ −
/*+ −
Returns '(' if the last parenthesis is opening, ')' if it is+ −
closing, and QChar::Null if there are no parentheses in t.+ −
*/+ −
static inline QChar lastParen( const QString& t )+ −
{+ −
int i = t.length();+ −
while ( i > 0 ) {+ −
i--;+ −
if ( t[i] == QChar('(') || t[i] == QChar(')') )+ −
return t[i];+ −
}+ −
return QChar::Null;+ −
}+ −
+ −
/*+ −
Returns true if typedIn the same as okayCh or is null; otherwise+ −
returns false.+ −
*/+ −
static inline bool okay( QChar typedIn, QChar okayCh )+ −
{+ −
return typedIn == QChar::Null || typedIn == okayCh;+ −
}+ −
+ −
/*+ −
The "linizer" is a group of functions and variables to iterate+ −
through the source code of the program to indent. The program is+ −
given as a list of strings, with the bottom line being the line+ −
to indent. The actual program might contain extra lines, but+ −
those are uninteresting and not passed over to us.+ −
*/+ −
+ −
struct LinizerState+ −
{+ −
QString line;+ −
int braceDepth;+ −
bool leftBraceFollows;+ −
+ −
QStringList::ConstIterator iter;+ −
bool inCComment;+ −
bool pendingRightBrace;+ −
};+ −
+ −
static QStringList *yyProgram = 0;+ −
static LinizerState *yyLinizerState = 0;+ −
+ −
// shorthands+ −
static const QString *yyLine = 0;+ −
static const int *yyBraceDepth = 0;+ −
static const bool *yyLeftBraceFollows = 0;+ −
+ −
/*+ −
Saves and restores the state of the global linizer. This enables+ −
backtracking.+ −
*/+ −
#define YY_SAVE() \+ −
LinizerState savedState = *yyLinizerState+ −
#define YY_RESTORE() \+ −
*yyLinizerState = savedState+ −
+ −
/*+ −
Advances to the previous line in yyProgram and update yyLine+ −
accordingly. yyLine is cleaned from comments and other damageable+ −
constructs. Empty lines are skipped.+ −
*/+ −
static bool readLine()+ −
{+ −
int k;+ −
+ −
yyLinizerState->leftBraceFollows =+ −
( firstNonWhiteSpace(yyLinizerState->line) == QChar('{') );+ −
+ −
do {+ −
if ( yyLinizerState->iter == yyProgram->begin() ) {+ −
yyLinizerState->line.clear();+ −
return false;+ −
}+ −
+ −
--yyLinizerState->iter;+ −
yyLinizerState->line = *yyLinizerState->iter;+ −
+ −
yyLinizerState->line = trimmedCodeLine( yyLinizerState->line );+ −
+ −
/*+ −
Remove C-style comments that span multiple lines. If the+ −
bottom line starts in a C-style comment, we are not aware+ −
of that and eventually yyLine will contain a slash-aster.+ −
+ −
Notice that both if's can be executed, since+ −
yyLinizerState->inCComment is potentially set to false in+ −
the first if. The order of the if's is also important.+ −
*/+ −
+ −
if ( yyLinizerState->inCComment ) {+ −
QString slashAster( "/*" );+ −
+ −
k = yyLinizerState->line.indexOf( slashAster );+ −
if ( k == -1 ) {+ −
yyLinizerState->line.clear();+ −
} else {+ −
yyLinizerState->line.truncate( k );+ −
yyLinizerState->inCComment = false;+ −
}+ −
}+ −
+ −
if ( !yyLinizerState->inCComment ) {+ −
QString asterSlash( "*/" );+ −
+ −
k = yyLinizerState->line.indexOf( asterSlash );+ −
if ( k != -1 ) {+ −
for ( int i = 0; i < k + 2; i++ )+ −
eraseChar( yyLinizerState->line, i, ' ' );+ −
yyLinizerState->inCComment = true;+ −
}+ −
}+ −
+ −
/*+ −
Remove preprocessor directives.+ −
*/+ −
k = 0;+ −
while ( k < (int) yyLinizerState->line.length() ) {+ −
QChar ch = yyLinizerState->line[k];+ −
if ( ch == QChar('#') ) {+ −
yyLinizerState->line.clear();+ −
} else if ( !ch.isSpace() ) {+ −
break;+ −
}+ −
k++;+ −
}+ −
+ −
/*+ −
Remove trailing spaces.+ −
*/+ −
k = yyLinizerState->line.length();+ −
while ( k > 0 && yyLinizerState->line[k - 1].isSpace() )+ −
k--;+ −
yyLinizerState->line.truncate( k );+ −
+ −
/*+ −
'}' increment the brace depth and '{' decrements it and not+ −
the other way around, as we are parsing backwards.+ −
*/+ −
yyLinizerState->braceDepth +=+ −
yyLinizerState->line.count( '}' ) -+ −
yyLinizerState->line.count( '{' );+ −
+ −
/*+ −
We use a dirty trick for+ −
+ −
} else ...+ −
+ −
We don't count the '}' yet, so that it's more or less+ −
equivalent to the friendly construct+ −
+ −
}+ −
else ...+ −
*/+ −
if ( yyLinizerState->pendingRightBrace )+ −
yyLinizerState->braceDepth++;+ −
yyLinizerState->pendingRightBrace =+ −
( yyLinizerState->line.indexOf(*braceX) == 0 );+ −
if ( yyLinizerState->pendingRightBrace )+ −
yyLinizerState->braceDepth--;+ −
} while ( yyLinizerState->line.isEmpty() );+ −
+ −
return true;+ −
}+ −
+ −
/*+ −
Resets the linizer to its initial state, with yyLine containing the+ −
line above the bottom line of the program.+ −
*/+ −
static void startLinizer()+ −
{+ −
yyLinizerState->braceDepth = 0;+ −
yyLinizerState->inCComment = false;+ −
yyLinizerState->pendingRightBrace = false;+ −
+ −
yyLine = &yyLinizerState->line;+ −
yyBraceDepth = &yyLinizerState->braceDepth;+ −
yyLeftBraceFollows = &yyLinizerState->leftBraceFollows;+ −
+ −
yyLinizerState->iter = yyProgram->end();+ −
--yyLinizerState->iter;+ −
yyLinizerState->line = *yyLinizerState->iter;+ −
readLine();+ −
}+ −
+ −
/*+ −
Returns true if the start of the bottom line of yyProgram (and+ −
potentially the whole line) is part of a C-style comment;+ −
otherwise returns false.+ −
*/+ −
static bool bottomLineStartsInCComment()+ −
{+ −
QString slashAster( "/*" );+ −
QString asterSlash( "*/" );+ −
+ −
/*+ −
We could use the linizer here, but that would slow us down+ −
terribly. We are better to trim only the code lines we need.+ −
*/+ −
QStringList::ConstIterator p = yyProgram->end();+ −
--p; // skip bottom line+ −
+ −
for ( int i = 0; i < BigRoof; i++ ) {+ −
if ( p == yyProgram->begin() )+ −
return false;+ −
--p;+ −
+ −
if ( (*p).indexOf(slashAster) != -1 || (*p).indexOf(asterSlash) != -1 ) {+ −
QString trimmed = trimmedCodeLine( *p );+ −
+ −
if ( trimmed.indexOf(slashAster) != -1 ) {+ −
return true;+ −
} else if ( trimmed.indexOf(asterSlash) != -1 ) {+ −
return false;+ −
}+ −
}+ −
}+ −
return false;+ −
}+ −
+ −
/*+ −
Returns the recommended indent for the bottom line of yyProgram+ −
assuming that it starts in a C-style comment, a condition that is+ −
tested elsewhere.+ −
+ −
Essentially, we're trying to align against some text on the+ −
previous line.+ −
*/+ −
static int indentWhenBottomLineStartsInCComment()+ −
{+ −
int k = yyLine->lastIndexOf( "/*" );+ −
if ( k == -1 ) {+ −
/*+ −
We found a normal text line in a comment. Align the+ −
bottom line with the text on this line.+ −
*/+ −
return indentOfLine( *yyLine );+ −
} else {+ −
/*+ −
The C-style comment starts on this line. If there is+ −
text on the same line, align with it. Otherwise, align+ −
with the slash-aster plus a given offset.+ −
*/+ −
int indent = columnForIndex( *yyLine, k );+ −
k += 2;+ −
while ( k < (int) yyLine->length() ) {+ −
if ( !(*yyLine)[k].isSpace() )+ −
return columnForIndex( *yyLine, k );+ −
k++;+ −
}+ −
return indent + ppCommentOffset;+ −
}+ −
}+ −
+ −
/*+ −
A function called match...() modifies the linizer state. If it+ −
returns true, yyLine is the top line of the matched construct;+ −
otherwise, the linizer is left in an unknown state.+ −
+ −
A function called is...() keeps the linizer state intact.+ −
*/+ −
+ −
/*+ −
Returns true if the current line (and upwards) forms a braceless+ −
control statement; otherwise returns false.+ −
+ −
The first line of the following example is a "braceless control+ −
statement":+ −
+ −
if ( x )+ −
y;+ −
*/+ −
static bool matchBracelessControlStatement()+ −
{+ −
int delimDepth = 0;+ −
+ −
if ( yyLine->endsWith("else") )+ −
return true;+ −
+ −
if ( !yyLine->endsWith(")") )+ −
return false;+ −
+ −
for ( int i = 0; i < SmallRoof; i++ ) {+ −
int j = yyLine->length();+ −
while ( j > 0 ) {+ −
j--;+ −
QChar ch = (*yyLine)[j];+ −
+ −
switch ( ch.unicode() ) {+ −
case ')':+ −
delimDepth++;+ −
break;+ −
case '(':+ −
delimDepth--;+ −
if ( delimDepth == 0 ) {+ −
if ( yyLine->indexOf(*iflikeKeyword) != -1 ) {+ −
/*+ −
We have+ −
+ −
if ( x )+ −
y+ −
+ −
"if ( x )" is not part of the statement+ −
"y".+ −
*/+ −
return true;+ −
}+ −
}+ −
if ( delimDepth == -1 ) {+ −
/*+ −
We have+ −
+ −
if ( (1 ++ −
2)+ −
+ −
and not+ −
+ −
if ( 1 ++ −
2 )+ −
*/+ −
return false;+ −
}+ −
break;+ −
case '{':+ −
case '}':+ −
case ';':+ −
/*+ −
We met a statement separator, but not where we+ −
expected it. What follows is probably a weird+ −
continuation line. Be careful with ';' in for,+ −
though.+ −
*/+ −
if ( ch != QChar(';') || delimDepth == 0 )+ −
return false;+ −
}+ −
}+ −
+ −
if ( !readLine() )+ −
break;+ −
}+ −
return false;+ −
}+ −
+ −
/*+ −
Returns true if yyLine is an unfinished line; otherwise returns+ −
false.+ −
+ −
In many places we'll use the terms "standalone line", "unfinished+ −
line" and "continuation line". The meaning of these should be+ −
evident from this code example:+ −
+ −
a = b; // standalone line+ −
c = d + // unfinished line+ −
e + // unfinished continuation line+ −
f + // unfinished continuation line+ −
g; // continuation line+ −
*/+ −
static bool isUnfinishedLine()+ −
{+ −
bool unf = false;+ −
+ −
YY_SAVE();+ −
+ −
if ( yyLine->isEmpty() )+ −
return false;+ −
+ −
QChar lastCh = (*yyLine)[(int) yyLine->length() - 1];+ −
if ( QString("{};").indexOf(lastCh) == -1 && !yyLine->endsWith("...") ) {+ −
/*+ −
It doesn't end with ';' or similar. If it's neither+ −
"Q_OBJECT" nor "if ( x )", it must be an unfinished line.+ −
*/+ −
unf = ( yyLine->indexOf("Q_OBJECT") == -1 &&+ −
!matchBracelessControlStatement() );+ −
} else if ( lastCh == QChar(';') ) {+ −
if ( lastParen(*yyLine) == QChar('(') ) {+ −
/*+ −
Exception:+ −
+ −
for ( int i = 1; i < 10;+ −
*/+ −
unf = true;+ −
} else if ( readLine() && yyLine->endsWith(";") &&+ −
lastParen(*yyLine) == QChar('(') ) {+ −
/*+ −
Exception:+ −
+ −
for ( int i = 1;+ −
i < 10;+ −
*/+ −
unf = true;+ −
}+ −
}+ −
+ −
YY_RESTORE();+ −
return unf;+ −
}+ −
+ −
/*+ −
Returns true if yyLine is a continuation line; otherwise returns+ −
false.+ −
*/+ −
static bool isContinuationLine()+ −
{+ −
bool cont = false;+ −
+ −
YY_SAVE();+ −
if ( readLine() )+ −
cont = isUnfinishedLine();+ −
YY_RESTORE();+ −
return cont;+ −
}+ −
+ −
/*+ −
Returns the recommended indent for the bottom line of yyProgram,+ −
assuming it's a continuation line.+ −
+ −
We're trying to align the continuation line against some parenthesis+ −
or other bracked left opened on a previous line, or some interesting+ −
operator such as '='.+ −
*/+ −
static int indentForContinuationLine()+ −
{+ −
int braceDepth = 0;+ −
int delimDepth = 0;+ −
+ −
bool leftBraceFollowed = *yyLeftBraceFollows;+ −
+ −
for ( int i = 0; i < SmallRoof; i++ ) {+ −
int hook = -1;+ −
+ −
int j = yyLine->length();+ −
while ( j > 0 && hook < 0 ) {+ −
j--;+ −
QChar ch = (*yyLine)[j];+ −
+ −
switch ( ch.unicode() ) {+ −
case ')':+ −
case ']':+ −
delimDepth++;+ −
break;+ −
case '}':+ −
braceDepth++;+ −
break;+ −
case '(':+ −
case '[':+ −
delimDepth--;+ −
/*+ −
An unclosed delimiter is a good place to align at,+ −
at least for some styles (including Qt's).+ −
*/+ −
if ( delimDepth == -1 )+ −
hook = j;+ −
break;+ −
case '{':+ −
braceDepth--;+ −
/*+ −
A left brace followed by other stuff on the same+ −
line is typically for an enum or an initializer.+ −
Such a brace must be treated just like the other+ −
delimiters.+ −
*/+ −
if ( braceDepth == -1 ) {+ −
if ( j < (int) yyLine->length() - 1 ) {+ −
hook = j;+ −
} else {+ −
return 0; // shouldn't happen+ −
}+ −
}+ −
break;+ −
case '=':+ −
/*+ −
An equal sign is a very natural alignment hook+ −
because it's usually the operator with the lowest+ −
precedence in statements it appears in. Case in+ −
point:+ −
+ −
int x = 1 ++ −
2;+ −
+ −
However, we have to beware of constructs such as+ −
default arguments and explicit enum constant+ −
values:+ −
+ −
void foo( int x = 0,+ −
int y = 0 );+ −
+ −
And not+ −
+ −
void foo( int x = 0,+ −
int y = 0 );+ −
+ −
These constructs are caracterized by a ',' at the+ −
end of the unfinished lines or by unbalanced+ −
parentheses.+ −
*/+ −
if ( QString("!=<>").indexOf((*yyLine)[j - 1]) == -1 &&+ −
(*yyLine)[j + 1] != '=' ) {+ −
if ( braceDepth == 0 && delimDepth == 0 &&+ −
j < (int) yyLine->length() - 1 &&+ −
!yyLine->endsWith(",") &&+ −
(yyLine->contains('(') == yyLine->contains(')')) )+ −
hook = j;+ −
}+ −
}+ −
}+ −
+ −
if ( hook >= 0 ) {+ −
/*+ −
Yes, we have a delimiter or an operator to align+ −
against! We don't really align against it, but rather+ −
against the following token, if any. In this example,+ −
the following token is "11":+ −
+ −
int x = ( 11 ++ −
2 );+ −
+ −
If there is no such token, we use a continuation indent:+ −
+ −
static QRegExp foo( QString(+ −
"foo foo foo foo foo foo foo foo foo") );+ −
*/+ −
hook++;+ −
while ( hook < (int) yyLine->length() ) {+ −
if ( !(*yyLine)[hook].isSpace() )+ −
return columnForIndex( *yyLine, hook );+ −
hook++;+ −
}+ −
return indentOfLine( *yyLine ) + ppContinuationIndentSize;+ −
}+ −
+ −
if ( braceDepth != 0 )+ −
break;+ −
+ −
/*+ −
The line's delimiters are balanced. It looks like a+ −
continuation line or something.+ −
*/+ −
if ( delimDepth == 0 ) {+ −
if ( leftBraceFollowed ) {+ −
/*+ −
We have+ −
+ −
int main()+ −
{+ −
+ −
or+ −
+ −
Bar::Bar()+ −
: Foo( x )+ −
{+ −
+ −
The "{" should be flush left.+ −
*/+ −
if ( !isContinuationLine() )+ −
return indentOfLine( *yyLine );+ −
} else if ( isContinuationLine() || yyLine->endsWith(",") ) {+ −
/*+ −
We have+ −
+ −
x = a ++ −
b ++ −
c;+ −
+ −
or+ −
+ −
int t[] = {+ −
1, 2, 3,+ −
4, 5, 6+ −
+ −
The "c;" should fall right under the "b +", and the+ −
"4, 5, 6" right under the "1, 2, 3,".+ −
*/+ −
return indentOfLine( *yyLine );+ −
} else {+ −
/*+ −
We have+ −
+ −
stream << 1 ++ −
2;+ −
+ −
We could, but we don't, try to analyze which+ −
operator has precedence over which and so on, to+ −
obtain the excellent result+ −
+ −
stream << 1 ++ −
2;+ −
+ −
We do have a special trick above for the assignment+ −
operator above, though.+ −
*/+ −
return indentOfLine( *yyLine ) + ppContinuationIndentSize;+ −
}+ −
}+ −
+ −
if ( !readLine() )+ −
break;+ −
}+ −
return 0;+ −
}+ −
+ −
/*+ −
Returns the recommended indent for the bottom line of yyProgram if+ −
that line is standalone (or should be indented likewise).+ −
+ −
Indenting a standalone line is tricky, mostly because of braceless+ −
control statements. Grossly, we are looking backwards for a special+ −
line, a "hook line", that we can use as a starting point to indent,+ −
and then modify the indentation level according to the braces met+ −
along the way to that hook.+ −
+ −
Let's consider a few examples. In all cases, we want to indent the+ −
bottom line.+ −
+ −
Example 1:+ −
+ −
x = 1;+ −
y = 2;+ −
+ −
The hook line is "x = 1;". We met 0 opening braces and 0 closing+ −
braces. Therefore, "y = 2;" inherits the indent of "x = 1;".+ −
+ −
Example 2:+ −
+ −
if ( x ) {+ −
y;+ −
+ −
The hook line is "if ( x ) {". No matter what precedes it, "y;" has+ −
to be indented one level deeper than the hook line, since we met one+ −
opening brace along the way.+ −
+ −
Example 3:+ −
+ −
if ( a )+ −
while ( b ) {+ −
c;+ −
}+ −
d;+ −
+ −
To indent "d;" correctly, we have to go as far as the "if ( a )".+ −
Compare with+ −
+ −
if ( a ) {+ −
while ( b ) {+ −
c;+ −
}+ −
d;+ −
+ −
Still, we're striving to go back as little as possible to+ −
accommodate people with irregular indentation schemes. A hook line+ −
near at hand is much more reliable than a remote one.+ −
*/+ −
static int indentForStandaloneLine()+ −
{+ −
for ( int i = 0; i < SmallRoof; i++ ) {+ −
if ( !*yyLeftBraceFollows ) {+ −
YY_SAVE();+ −
+ −
if ( matchBracelessControlStatement() ) {+ −
/*+ −
The situation is this, and we want to indent "z;":+ −
+ −
if ( x &&+ −
y )+ −
z;+ −
+ −
yyLine is "if ( x &&".+ −
*/+ −
return indentOfLine( *yyLine ) + ppIndentSize;+ −
}+ −
YY_RESTORE();+ −
}+ −
+ −
if ( yyLine->endsWith(";") || yyLine->contains('{') ) {+ −
/*+ −
The situation is possibly this, and we want to indent+ −
"z;":+ −
+ −
while ( x )+ −
y;+ −
z;+ −
+ −
We return the indent of "while ( x )". In place of "y;",+ −
any arbitrarily complex compound statement can appear.+ −
*/+ −
+ −
if ( *yyBraceDepth > 0 ) {+ −
do {+ −
if ( !readLine() )+ −
break;+ −
} while ( *yyBraceDepth > 0 );+ −
}+ −
+ −
LinizerState hookState;+ −
+ −
while ( isContinuationLine() )+ −
readLine();+ −
hookState = *yyLinizerState;+ −
+ −
readLine();+ −
if ( *yyBraceDepth <= 0 ) {+ −
do {+ −
if ( !matchBracelessControlStatement() )+ −
break;+ −
hookState = *yyLinizerState;+ −
} while ( readLine() );+ −
}+ −
+ −
*yyLinizerState = hookState;+ −
+ −
while ( isContinuationLine() )+ −
readLine();+ −
+ −
/*+ −
Never trust lines containing only '{' or '}', as some+ −
people (Richard M. Stallman) format them weirdly.+ −
*/+ −
if ( yyLine->trimmed().length() > 1 )+ −
return indentOfLine( *yyLine ) - *yyBraceDepth * ppIndentSize;+ −
}+ −
+ −
if ( !readLine() )+ −
return -*yyBraceDepth * ppIndentSize;+ −
}+ −
return 0;+ −
}+ −
+ −
/*+ −
Constructs global variables used by the indenter.+ −
*/+ −
static void initializeIndenter()+ −
{+ −
literal = new QRegExp( "([\"'])(?:\\\\.|[^\\\\])*\\1" );+ −
literal->setMinimal( true );+ −
label = new QRegExp(+ −
"^\\s*((?:case\\b([^:]|::)+|[a-zA-Z_0-9]+)(?:\\s+slots)?:)(?!:)" );+ −
inlineCComment = new QRegExp( "/\\*.*\\*/" );+ −
inlineCComment->setMinimal( true );+ −
braceX = new QRegExp( "^\\s*\\}\\s*(?:else|catch)\\b" );+ −
iflikeKeyword = new QRegExp( "\\b(?:catch|do|for|if|while)\\b" );+ −
+ −
yyLinizerState = new LinizerState;+ −
}+ −
+ −
/*+ −
Destroys global variables used by the indenter.+ −
*/+ −
static void terminateIndenter()+ −
{+ −
delete literal;+ −
delete label;+ −
delete inlineCComment;+ −
delete braceX;+ −
delete iflikeKeyword;+ −
delete yyLinizerState;+ −
}+ −
+ −
/*+ −
Returns the recommended indent for the bottom line of program.+ −
Unless null, typedIn stores the character of yyProgram that+ −
triggered reindentation.+ −
+ −
This function works better if typedIn is set properly; it is+ −
slightly more conservative if typedIn is completely wild, and+ −
slighly more liberal if typedIn is always null. The user might be+ −
annoyed by the liberal behavior.+ −
*/+ −
int indentForBottomLine( const QStringList& program, QChar typedIn )+ −
{+ −
if ( program.isEmpty() )+ −
return 0;+ −
+ −
initializeIndenter();+ −
+ −
yyProgram = new QStringList( program );+ −
startLinizer();+ −
+ −
const QString& bottomLine = program.last();+ −
QChar firstCh = firstNonWhiteSpace( bottomLine );+ −
int indent;+ −
+ −
if ( bottomLineStartsInCComment() ) {+ −
/*+ −
The bottom line starts in a C-style comment. Indent it+ −
smartly, unless the user has already played around with it,+ −
in which case it's better to leave her stuff alone.+ −
*/+ −
if ( isOnlyWhiteSpace(bottomLine) ) {+ −
indent = indentWhenBottomLineStartsInCComment();+ −
} else {+ −
indent = indentOfLine( bottomLine );+ −
}+ −
} else if ( okay(typedIn, '#') && firstCh == QChar('#') ) {+ −
/*+ −
Preprocessor directives go flush left.+ −
*/+ −
indent = 0;+ −
} else {+ −
if ( isUnfinishedLine() ) {+ −
indent = indentForContinuationLine();+ −
} else {+ −
indent = indentForStandaloneLine();+ −
}+ −
+ −
if ( okay(typedIn, '}') && firstCh == QChar('}') ) {+ −
/*+ −
A closing brace is one level more to the left than the+ −
code it follows.+ −
*/+ −
indent -= ppIndentSize;+ −
} else if ( okay(typedIn, ':') ) {+ −
QRegExp caseLabel(+ −
"\\s*(?:case\\b(?:[^:]|::)+"+ −
"|(?:public|protected|private|signals|default)(?:\\s+slots)?\\s*"+ −
")?:.*" );+ −
+ −
if ( caseLabel.exactMatch(bottomLine) ) {+ −
/*+ −
Move a case label (or the ':' in front of a+ −
constructor initialization list) one level to the+ −
left, but only if the user did not play around with+ −
it yet. Some users have exotic tastes in the+ −
matter, and most users probably are not patient+ −
enough to wait for the final ':' to format their+ −
code properly.+ −
+ −
We don't attempt the same for goto labels, as the+ −
user is probably the middle of "foo::bar". (Who+ −
uses goto, anyway?)+ −
*/+ −
if ( indentOfLine(bottomLine) <= indent )+ −
indent -= ppIndentSize;+ −
else+ −
indent = indentOfLine( bottomLine );+ −
}+ −
}+ −
}+ −
delete yyProgram;+ −
terminateIndenter();+ −
return qMax( 0, indent );+ −
}+ −
+ −
QT_END_NAMESPACE+ −
+ −
#ifdef Q_TEST_YYINDENT+ −
/*+ −
Test driver.+ −
*/+ −
+ −
#include <qfile.h>+ −
#include <qtextstream.h>+ −
+ −
#include <errno.h>+ −
+ −
QT_BEGIN_NAMESPACE+ −
+ −
static QString fileContents( const QString& fileName )+ −
{+ −
QFile f( fileName );+ −
if ( !f.open(QFile::ReadOnly) ) {+ −
qWarning( "yyindent error: Cannot open file '%s' for reading: %s",+ −
fileName.toLatin1().data(), strerror(errno) );+ −
return QString();+ −
}+ −
+ −
QTextStream t( &f );+ −
QString contents = t.read();+ −
f.close();+ −
if ( contents.isEmpty() )+ −
qWarning( "yyindent error: File '%s' is empty", fileName.toLatin1().data() );+ −
return contents;+ −
}+ −
+ −
QT_END_NAMESPACE+ −
+ −
int main( int argc, char **argv )+ −
{+ −
QT_USE_NAMESPACE+ −
+ −
if ( argc != 2 ) {+ −
qWarning( "usage: yyindent file.cpp" );+ −
return 1;+ −
}+ −
+ −
QString code = fileContents( argv[1] );+ −
QStringList program = QStringList::split( '\n', code, true );+ −
QStringList p;+ −
QString out;+ −
+ −
while ( !program.isEmpty() && program.last().trimmed().isEmpty() )+ −
program.remove( program.fromLast() );+ −
+ −
QStringList::ConstIterator line = program.begin();+ −
while ( line != program.end() ) {+ −
p.push_back( *line );+ −
QChar typedIn = firstNonWhiteSpace( *line );+ −
if ( p.last().endsWith(":") )+ −
typedIn = ':';+ −
+ −
int indent = indentForBottomLine( p, typedIn );+ −
+ −
if ( !(*line).trimmed().isEmpty() ) {+ −
for ( int j = 0; j < indent; j++ )+ −
out += " ";+ −
out += (*line).trimmed();+ −
}+ −
out += "\n";+ −
++line;+ −
}+ −
+ −
while ( out.endsWith("\n") )+ −
out.truncate( out.length() - 1 );+ −
+ −
printf( "%s\n", out.toLatin1().data() );+ −
return 0;+ −
}+ −
+ −
#endif // Q_TEST_YYINDENT+ −