tools/qdoc3/yyindent.cpp
changeset 0 1918ee327afb
child 4 3b1da2848fc7
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tools/qdoc3/yyindent.cpp	Mon Jan 11 14:00:40 2010 +0000
@@ -0,0 +1,1190 @@
+/****************************************************************************
+**
+** Copyright (C) 2009 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