diff options
author | Lars Knoll <lars.knoll@nokia.com> | 2009-03-23 09:18:55 (GMT) |
---|---|---|
committer | Simon Hausmann <simon.hausmann@nokia.com> | 2009-03-23 09:18:55 (GMT) |
commit | e5fcad302d86d316390c6b0f62759a067313e8a9 (patch) | |
tree | c2afbf6f1066b6ce261f14341cf6d310e5595bc1 /tools/qdoc3/yyindent.cpp | |
download | Qt-e5fcad302d86d316390c6b0f62759a067313e8a9.zip Qt-e5fcad302d86d316390c6b0f62759a067313e8a9.tar.gz Qt-e5fcad302d86d316390c6b0f62759a067313e8a9.tar.bz2 |
Long live Qt 4.5!
Diffstat (limited to 'tools/qdoc3/yyindent.cpp')
-rw-r--r-- | tools/qdoc3/yyindent.cpp | 1190 |
1 files changed, 1190 insertions, 0 deletions
diff --git a/tools/qdoc3/yyindent.cpp b/tools/qdoc3/yyindent.cpp new file mode 100644 index 0000000..bbca39b --- /dev/null +++ b/tools/qdoc3/yyindent.cpp @@ -0,0 +1,1190 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (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 either Technology Preview License Agreement or the +** Beta Release License Agreement. +** +** 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.0, included in the file LGPL_EXCEPTION.txt in this +** package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@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 Trolltech'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 |