diff --git a/src/libs/qmljs/qmljs-lib.pri b/src/libs/qmljs/qmljs-lib.pri index e0057cae72d3aa854b9f4d80de91aa8ce08bd9f8..7b5adadcd2bc4e6de4465533deb64c346387fb5e 100644 --- a/src/libs/qmljs/qmljs-lib.pri +++ b/src/libs/qmljs/qmljs-lib.pri @@ -18,7 +18,8 @@ HEADERS += \ $$PWD/qmljsinterpreter.h \ $$PWD/qmljslink.h \ $$PWD/qmljscheck.h \ - $$PWD/qmljsscopebuilder.h + $$PWD/qmljsscopebuilder.h \ + $$PWD/qmljslineinfo.h SOURCES += \ $$PWD/qmljsbind.cpp \ @@ -28,7 +29,8 @@ SOURCES += \ $$PWD/qmljsinterpreter.cpp \ $$PWD/qmljslink.cpp \ $$PWD/qmljscheck.cpp \ - $$PWD/qmljsscopebuilder.cpp + $$PWD/qmljsscopebuilder.cpp \ + $$PWD/qmljslineinfo.cpp contains(QT, gui) { SOURCES += $$PWD/qmljsindenter.cpp diff --git a/src/libs/qmljs/qmljsindenter.cpp b/src/libs/qmljs/qmljsindenter.cpp index 7e1e50183c9861d03c20654d92287bdb672e1bbe..9162f4d4fe11886a2dd44c287f99d3f181cc1e01 100644 --- a/src/libs/qmljs/qmljsindenter.cpp +++ b/src/libs/qmljs/qmljsindenter.cpp @@ -73,19 +73,17 @@ using namespace QmlJS; /* - 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. + Saves and restores the state of the global linizer. This enables + backtracking. - For example, the indenter never considers more than BigRoof lines - backwards when looking for the start of a C-style comment. + Identical to the defines in qmljslineinfo.cpp */ -const int QmlJSIndenter::SmallRoof = 40; -const int QmlJSIndenter::BigRoof = 400; +#define YY_SAVE() LinizerState savedState = yyLinizerState +#define YY_RESTORE() yyLinizerState = savedState + QmlJSIndenter::QmlJSIndenter() - : braceX(QRegExp(QLatin1String("^\\s*\\}\\s*(?:else|catch)\\b"))) - , caseOrDefault(QRegExp(QLatin1String( + : caseOrDefault(QRegExp(QLatin1String( "\\s*(?:" "case\\b[^:]+|" "default)" @@ -110,19 +108,6 @@ QmlJSIndenter::QmlJSIndenter() ppIndentSize = 4; ppContinuationIndentSize = 8; ppCommentOffset = 2; - - /* - 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. - */ - - // shorthands - yyLine = 0; - yyBraceDepth = 0; - yyLeftBraceFollows = 0; } QmlJSIndenter::~QmlJSIndenter() @@ -140,21 +125,6 @@ void QmlJSIndenter::setIndentSize(int size) ppContinuationIndentSize = 2 * size; } -/* - Returns the first non-space character in the string t, or - QChar() if the string is made only of white space. -*/ -QChar QmlJSIndenter::firstNonWhiteSpace(const QString &t) const -{ - int i = 0; - while (i < t.length()) { - if (!t.at(i).isSpace()) - return t.at(i); - i++; - } - return QChar(); -} - /* Returns true if string t is made only of white space; otherwise returns false. @@ -205,102 +175,6 @@ void QmlJSIndenter::eraseChar(QString &t, int k, QChar ch) const t[k] = ch; } -/* - Removes some nefast constructs from a code line and returns the - resulting line. -*/ -QString QmlJSIndenter::trimmedCodeLine(const QString &t) -{ - Scanner scanner; - - QTextBlock currentLine = yyLinizerState.iter; - int startState = qMax(0, currentLine.previous().userState()) & 0xff; - - yyLinizerState.tokens = scanner(t, startState); - QString trimmed; - int previousTokenEnd = 0; - foreach (const Token &token, yyLinizerState.tokens) { - trimmed.append(t.midRef(previousTokenEnd, token.begin() - previousTokenEnd)); - - if (token.is(Token::String)) { - for (int i = 0; i < token.length; ++i) - trimmed.append(QLatin1Char('X')); - - } else if (token.is(Token::Comment)) { - for (int i = 0; i < token.length; ++i) - trimmed.append(QLatin1Char(' ')); - - } else { - trimmed.append(tokenText(token)); - } - - previousTokenEnd = token.end(); - } - - int index = yyLinizerState.tokens.size() - 1; - for (; index != -1; --index) { - const Token &token = yyLinizerState.tokens.at(index); - if (token.isNot(Token::Comment)) - break; - } - - bool isBinding = false; - foreach (const Token &token, yyLinizerState.tokens) { - if (token.is(Token::Colon)) { - isBinding = true; - break; - } - } - - if (index != -1) { - const Token &last = yyLinizerState.tokens.at(index); - bool needSemicolon = false; - - switch (last.kind) { - case Token::LeftParenthesis: - case Token::LeftBrace: - case Token::LeftBracket: - case Token::Semicolon: - case Token::Delimiter: - break; - - case Token::RightParenthesis: - case Token::RightBrace: - case Token::RightBracket: - if (isBinding) - needSemicolon = true; - break; - - case Token::String: - case Token::Number: - case Token::Colon: - case Token::Comma: - needSemicolon = true; - break; - - case Token::Identifier: - needSemicolon = true; - break; - - case Token::Keyword: - if (tokenText(last) != QLatin1String("else")) - needSemicolon = true; - break; - - default: - break; - } // end of switch - - if (needSemicolon) { - const Token sc(trimmed.size(), 1, Token::Semicolon); - yyLinizerState.tokens.append(sc); - trimmed.append(QLatin1Char(';')); - } - } - - return trimmed; -} - /* Returns '(' if the last parenthesis is opening, ')' if it is closing, and QChar() if there are no parentheses in t. @@ -320,31 +194,6 @@ QChar QmlJSIndenter::lastParen() const return QChar(); } -bool QmlJSIndenter::hasUnclosedParenOrBracket() const -{ - int closedParen = 0; - int closedBracket = 0; - for (int index = yyLinizerState.tokens.size() - 1; index != -1; --index) { - const Token &token = yyLinizerState.tokens.at(index); - - if (token.is(Token::RightParenthesis)) { - closedParen++; - } else if (token.is(Token::RightBracket)) { - closedBracket++; - } else if (token.is(Token::LeftParenthesis)) { - closedParen--; - if (closedParen < 0) - return true; - } else if (token.is(Token::LeftBracket)) { - closedBracket--; - if (closedBracket < 0) - return true; - } - } - - return false; -} - /* Returns true if typedIn the same as okayCh or is null; otherwise returns false. @@ -354,127 +203,6 @@ bool QmlJSIndenter::okay(QChar typedIn, QChar okayCh) const return typedIn == QChar() || typedIn == okayCh; } -Token QmlJSIndenter::lastToken() const -{ - for (int index = yyLinizerState.tokens.size() - 1; index != -1; --index) { - const Token &token = yyLinizerState.tokens.at(index); - - if (token.isNot(Token::Comment)) - return token; - } - - return Token(); -} - -QStringRef QmlJSIndenter::tokenText(const Token &token) const -{ - return yyLinizerState.line.midRef(token.offset, token.length); -} - -/* - 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. -*/ -bool QmlJSIndenter::readLine() -{ - int k; - - yyLinizerState.leftBraceFollows = - (firstNonWhiteSpace(yyLinizerState.line) == QLatin1Char('{')); - - do { - if (yyLinizerState.iter == yyProgram.firstBlock()) { - yyLinizerState.line.clear(); - return false; - } - - yyLinizerState.iter = yyLinizerState.iter.previous(); - yyLinizerState.line = yyLinizerState.iter.text(); - - yyLinizerState.line = trimmedCodeLine(yyLinizerState.line); - - /* - Remove trailing spaces. - */ - k = yyLinizerState.line.length(); - while (k > 0 && yyLinizerState.line.at(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(']') - - 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. -*/ -void QmlJSIndenter::startLinizer() -{ - yyLinizerState.braceDepth = 0; - yyLinizerState.pendingRightBrace = false; - - yyLine = &yyLinizerState.line; - yyBraceDepth = &yyLinizerState.braceDepth; - yyLeftBraceFollows = &yyLinizerState.leftBraceFollows; - - yyLinizerState.iter = yyProgram.lastBlock(); - yyLinizerState.iter = yyLinizerState.iter.previous(); - yyLinizerState.line = yyLinizerState.iter.text(); - 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. -*/ -bool QmlJSIndenter::bottomLineStartsInMultilineComment() -{ - QTextBlock currentLine = yyProgram.lastBlock().previous(); - QTextBlock previousLine = currentLine.previous(); - - int startState = qMax(0, previousLine.userState()) & 0xff; - if (startState > 0) - return true; - - 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 @@ -498,208 +226,6 @@ int QmlJSIndenter::indentWhenBottomLineStartsInMultiLineComment() return indentOfLine(blockText); } -/* - 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; -*/ -bool QmlJSIndenter::matchBracelessControlStatement() -{ - int delimDepth = 0; - - if (! yyLinizerState.tokens.isEmpty()) { - Token tk = lastToken(); - - if (tk.is(Token::Keyword) && tokenText(tk) == QLatin1String("else")) - return true; - - else if (tk.isNot(Token::RightParenthesis)) - return false; - } - - for (int i = 0; i < SmallRoof; i++) { - for (int tokenIndex = yyLinizerState.tokens.size() - 1; tokenIndex != -1; --tokenIndex) { - const Token &token = yyLinizerState.tokens.at(tokenIndex); - - switch (token.kind) { - default: - break; - - case Token::Comment: - // skip comments - break; - - case Token::RightParenthesis: - ++delimDepth; - break; - - case Token::LeftBrace: - case Token::RightBrace: - case Token::Semicolon: - /* - 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 (token.kind != Token::Semicolon || delimDepth == 0) - return false; - break; - - - case Token::LeftParenthesis: - --delimDepth; - - if (delimDepth == 0 && tokenIndex > 0) { - const Token &tk = yyLinizerState.tokens.at(tokenIndex - 1); - - if (tk.is(Token::Keyword)) { - const QStringRef text = tokenText(tk); - - /* - We have - - if-like (x) - y - - "if (x)" is not part of the statement - "y". - */ - - - if (tk.length == 5 && text == QLatin1String("catch")) - return true; - - else if (tk.length == 2 && text == QLatin1String("do")) - return true; - - else if (tk.length == 3 && text == QLatin1String("for")) - return true; - - else if (tk.length == 2 && text == QLatin1String("if")) - return true; - - else if (tk.length == 5 && text == QLatin1String("while")) - return true; - - else if (tk.length == 4 && text == QLatin1String("with")) - return true; - } - } - - if (delimDepth == -1) { - /* - We have - - if ((1 + - 2) - - and not - - if (1 + - 2) - */ - return false; - } - break; - - } // end of switch - } - - 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 -*/ -bool QmlJSIndenter::isUnfinishedLine() -{ - bool unf = false; - - YY_SAVE(); - - if (yyLine->isEmpty()) - return false; - - const QChar lastCh = yyLine->at(yyLine->length() - 1); - - if (QString::fromLatin1("{};[]").indexOf(lastCh) == -1) { - /* - It doesn't end with ';' or similar. If it's not an "if (x)", it must be an unfinished line. - */ - unf = ! matchBracelessControlStatement(); - - if (unf && lastCh == QLatin1Char(')')) - unf = false; - - } else if (lastCh == QLatin1Char(';')) { - if (hasUnclosedParenOrBracket()) { - /* - Exception: - - for (int i = 1; i < 10; - */ - unf = true; - - // ### This only checks one line back. - } else if (readLine() && yyLine->endsWith(QLatin1String(";")) && hasUnclosedParenOrBracket()) { - /* - Exception: - - for (int i = 1; - i < 10; - */ - unf = true; - } - } - - YY_RESTORE(); - return unf; -} - -/* - Returns true if yyLine is a continuation line; otherwise returns - false. -*/ -bool QmlJSIndenter::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. diff --git a/src/libs/qmljs/qmljsindenter.h b/src/libs/qmljs/qmljsindenter.h index f63d08106fd17bdc3e6e83c87fb61c0cc73057ca..ec91c7c571f331e0c4bb0c2db5410ffd6da2704b 100644 --- a/src/libs/qmljs/qmljsindenter.h +++ b/src/libs/qmljs/qmljsindenter.h @@ -32,6 +32,7 @@ #include <qmljs/qmljs_global.h> #include <qmljs/qmljsscanner.h> +#include <qmljs/qmljslineinfo.h> #include <QtCore/QRegExp> #include <QtCore/QStringList> @@ -39,7 +40,7 @@ namespace QmlJS { -class QMLJS_EXPORT QmlJSIndenter +class QMLJS_EXPORT QmlJSIndenter : public LineInfo { Q_DISABLE_COPY(QmlJSIndenter) @@ -51,43 +52,20 @@ public: void setIndentSize(int size); int indentForBottomLine(QTextBlock firstBlock, QTextBlock lastBlock, QChar typedIn); - QChar firstNonWhiteSpace(const QString &t) const; private: - static const int SmallRoof; - static const int BigRoof; - bool isOnlyWhiteSpace(const QString &t) const; int columnForIndex(const QString &t, int index) const; int indentOfLine(const QString &t) const; - QString trimmedCodeLine(const QString &t); void eraseChar(QString &t, int k, QChar ch) const; QChar lastParen() const; - bool hasUnclosedParenOrBracket() const; bool okay(QChar typedIn, QChar okayCh) const; - /* - 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. - */ - - bool readLine(); - void startLinizer(); - bool bottomLineStartsInMultilineComment(); int indentWhenBottomLineStartsInMultiLineComment(); - bool matchBracelessControlStatement(); - bool isUnfinishedLine(); - bool isContinuationLine(); int indentForContinuationLine(); int indentForStandaloneLine(); - Token lastToken() const; - QStringRef tokenText(const Token &token) const; - private: int ppHardwareTabSize; int ppIndentSize; @@ -95,45 +73,6 @@ private: int ppCommentOffset; private: - struct LinizerState - { - LinizerState() - : braceDepth(0), - leftBraceFollows(false), - pendingRightBrace(false) - { } - - int braceDepth; - bool leftBraceFollows; - bool pendingRightBrace; - QString line; - QList<Token> tokens; - QTextBlock iter; - }; - - class Program - { - public: - Program() {} - Program(QTextBlock begin, QTextBlock end) - : begin(begin), end(end) {} - - QTextBlock firstBlock() const { return begin; } - QTextBlock lastBlock() const { return end; } - - private: - QTextBlock begin, end; - }; - - Program yyProgram; - LinizerState yyLinizerState; - - // shorthands - const QString *yyLine; - const int *yyBraceDepth; - const bool *yyLeftBraceFollows; - - QRegExp braceX; QRegExp caseOrDefault; }; diff --git a/src/libs/qmljs/qmljslineinfo.cpp b/src/libs/qmljs/qmljslineinfo.cpp new file mode 100644 index 0000000000000000000000000000000000000000..17113a391c02f9eefa07b20b29ee6be44ff2a42b --- /dev/null +++ b/src/libs/qmljs/qmljslineinfo.cpp @@ -0,0 +1,575 @@ +/************************************************************************** +** +** This file is part of Qt Creator +** +** Copyright (c) 2010 Nokia Corporation and/or its subsidiary(-ies). +** +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** Commercial Usage +** +** Licensees holding valid Qt Commercial licenses may use this file in +** accordance with the Qt Commercial License Agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and Nokia. +** +** 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. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at http://qt.nokia.com/contact. +** +**************************************************************************/ + +/* + This file is a self-contained interactive indenter for Qt Script. + + The general problem of indenting a 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 <qmljs/qmljslineinfo.h> +#include <qmljs/qmljsscanner.h> + +#include <QtDebug> + +using namespace QmlJS; + +/* + 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. +*/ +const int LineInfo::SmallRoof = 40; +const int LineInfo::BigRoof = 400; + +LineInfo::LineInfo() + : braceX(QRegExp(QLatin1String("^\\s*\\}\\s*(?:else|catch)\\b"))) +{ + /* + 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. + */ + + // shorthands + yyLine = 0; + yyBraceDepth = 0; + yyLeftBraceFollows = 0; +} + +LineInfo::~LineInfo() +{ +} + +/* + Returns the first non-space character in the string t, or + QChar() if the string is made only of white space. +*/ +QChar LineInfo::firstNonWhiteSpace(const QString &t) const +{ + int i = 0; + while (i < t.length()) { + if (!t.at(i).isSpace()) + return t.at(i); + i++; + } + return QChar(); +} + +/* + Removes some nefast constructs from a code line and returns the + resulting line. +*/ +QString LineInfo::trimmedCodeLine(const QString &t) +{ + Scanner scanner; + + QTextBlock currentLine = yyLinizerState.iter; + int startState = qMax(0, currentLine.previous().userState()) & 0xff; + + yyLinizerState.tokens = scanner(t, startState); + QString trimmed; + int previousTokenEnd = 0; + foreach (const Token &token, yyLinizerState.tokens) { + trimmed.append(t.midRef(previousTokenEnd, token.begin() - previousTokenEnd)); + + if (token.is(Token::String)) { + for (int i = 0; i < token.length; ++i) + trimmed.append(QLatin1Char('X')); + + } else if (token.is(Token::Comment)) { + for (int i = 0; i < token.length; ++i) + trimmed.append(QLatin1Char(' ')); + + } else { + trimmed.append(tokenText(token)); + } + + previousTokenEnd = token.end(); + } + + int index = yyLinizerState.tokens.size() - 1; + for (; index != -1; --index) { + const Token &token = yyLinizerState.tokens.at(index); + if (token.isNot(Token::Comment)) + break; + } + + bool isBinding = false; + foreach (const Token &token, yyLinizerState.tokens) { + if (token.is(Token::Colon)) { + isBinding = true; + break; + } + } + + if (index != -1) { + const Token &last = yyLinizerState.tokens.at(index); + bool needSemicolon = false; + + switch (last.kind) { + case Token::LeftParenthesis: + case Token::LeftBrace: + case Token::LeftBracket: + case Token::Semicolon: + case Token::Delimiter: + break; + + case Token::RightParenthesis: + case Token::RightBrace: + case Token::RightBracket: + if (isBinding) + needSemicolon = true; + break; + + case Token::String: + case Token::Number: + case Token::Colon: + case Token::Comma: + needSemicolon = true; + break; + + case Token::Identifier: + needSemicolon = true; + break; + + case Token::Keyword: + if (tokenText(last) != QLatin1String("else")) + needSemicolon = true; + break; + + default: + break; + } // end of switch + + if (needSemicolon) { + const Token sc(trimmed.size(), 1, Token::Semicolon); + yyLinizerState.tokens.append(sc); + trimmed.append(QLatin1Char(';')); + } + } + + return trimmed; +} + +bool LineInfo::hasUnclosedParenOrBracket() const +{ + int closedParen = 0; + int closedBracket = 0; + for (int index = yyLinizerState.tokens.size() - 1; index != -1; --index) { + const Token &token = yyLinizerState.tokens.at(index); + + if (token.is(Token::RightParenthesis)) { + closedParen++; + } else if (token.is(Token::RightBracket)) { + closedBracket++; + } else if (token.is(Token::LeftParenthesis)) { + closedParen--; + if (closedParen < 0) + return true; + } else if (token.is(Token::LeftBracket)) { + closedBracket--; + if (closedBracket < 0) + return true; + } + } + + return false; +} + +Token LineInfo::lastToken() const +{ + for (int index = yyLinizerState.tokens.size() - 1; index != -1; --index) { + const Token &token = yyLinizerState.tokens.at(index); + + if (token.isNot(Token::Comment)) + return token; + } + + return Token(); +} + +QStringRef LineInfo::tokenText(const Token &token) const +{ + return yyLinizerState.line.midRef(token.offset, token.length); +} + +/* + 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. +*/ +bool LineInfo::readLine() +{ + int k; + + yyLinizerState.leftBraceFollows = + (firstNonWhiteSpace(yyLinizerState.line) == QLatin1Char('{')); + + do { + if (yyLinizerState.iter == yyProgram.firstBlock()) { + yyLinizerState.line.clear(); + return false; + } + + yyLinizerState.iter = yyLinizerState.iter.previous(); + yyLinizerState.line = yyLinizerState.iter.text(); + + yyLinizerState.line = trimmedCodeLine(yyLinizerState.line); + + /* + Remove trailing spaces. + */ + k = yyLinizerState.line.length(); + while (k > 0 && yyLinizerState.line.at(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(']') - + 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. +*/ +void LineInfo::startLinizer() +{ + yyLinizerState.braceDepth = 0; + yyLinizerState.pendingRightBrace = false; + + yyLine = &yyLinizerState.line; + yyBraceDepth = &yyLinizerState.braceDepth; + yyLeftBraceFollows = &yyLinizerState.leftBraceFollows; + + yyLinizerState.iter = yyProgram.lastBlock(); + yyLinizerState.iter = yyLinizerState.iter.previous(); + yyLinizerState.line = yyLinizerState.iter.text(); + 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. +*/ +bool LineInfo::bottomLineStartsInMultilineComment() +{ + QTextBlock currentLine = yyProgram.lastBlock().previous(); + QTextBlock previousLine = currentLine.previous(); + + int startState = qMax(0, previousLine.userState()) & 0xff; + if (startState > 0) + return true; + + return false; +} + +/* + 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; +*/ +bool LineInfo::matchBracelessControlStatement() +{ + int delimDepth = 0; + + if (! yyLinizerState.tokens.isEmpty()) { + Token tk = lastToken(); + + if (tk.is(Token::Keyword) && tokenText(tk) == QLatin1String("else")) + return true; + + else if (tk.isNot(Token::RightParenthesis)) + return false; + } + + for (int i = 0; i < SmallRoof; i++) { + for (int tokenIndex = yyLinizerState.tokens.size() - 1; tokenIndex != -1; --tokenIndex) { + const Token &token = yyLinizerState.tokens.at(tokenIndex); + + switch (token.kind) { + default: + break; + + case Token::Comment: + // skip comments + break; + + case Token::RightParenthesis: + ++delimDepth; + break; + + case Token::LeftBrace: + case Token::RightBrace: + case Token::Semicolon: + /* + 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 (token.kind != Token::Semicolon || delimDepth == 0) + return false; + break; + + + case Token::LeftParenthesis: + --delimDepth; + + if (delimDepth == 0 && tokenIndex > 0) { + const Token &tk = yyLinizerState.tokens.at(tokenIndex - 1); + + if (tk.is(Token::Keyword)) { + const QStringRef text = tokenText(tk); + + /* + We have + + if-like (x) + y + + "if (x)" is not part of the statement + "y". + */ + + + if (tk.length == 5 && text == QLatin1String("catch")) + return true; + + else if (tk.length == 2 && text == QLatin1String("do")) + return true; + + else if (tk.length == 3 && text == QLatin1String("for")) + return true; + + else if (tk.length == 2 && text == QLatin1String("if")) + return true; + + else if (tk.length == 5 && text == QLatin1String("while")) + return true; + + else if (tk.length == 4 && text == QLatin1String("with")) + return true; + } + } + + if (delimDepth == -1) { + /* + We have + + if ((1 + + 2) + + and not + + if (1 + + 2) + */ + return false; + } + break; + + } // end of switch + } + + 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 +*/ +bool LineInfo::isUnfinishedLine() +{ + bool unf = false; + + YY_SAVE(); + + if (yyLine->isEmpty()) + return false; + + const QChar lastCh = yyLine->at(yyLine->length() - 1); + + if (QString::fromLatin1("{};[]").indexOf(lastCh) == -1) { + /* + It doesn't end with ';' or similar. If it's not an "if (x)", it must be an unfinished line. + */ + unf = ! matchBracelessControlStatement(); + + if (unf && lastCh == QLatin1Char(')')) + unf = false; + + } else if (lastCh == QLatin1Char(';')) { + if (hasUnclosedParenOrBracket()) { + /* + Exception: + + for (int i = 1; i < 10; + */ + unf = true; + + // ### This only checks one line back. + } else if (readLine() && yyLine->endsWith(QLatin1String(";")) && hasUnclosedParenOrBracket()) { + /* + Exception: + + for (int i = 1; + i < 10; + */ + unf = true; + } + } + + YY_RESTORE(); + return unf; +} + +/* + Returns true if yyLine is a continuation line; otherwise returns + false. +*/ +bool LineInfo::isContinuationLine() +{ + bool cont = false; + + YY_SAVE(); + if (readLine()) + cont = isUnfinishedLine(); + YY_RESTORE(); + return cont; +} + + +void LineInfo::initialize(QTextBlock begin, QTextBlock end) +{ + if (begin == end) + return; + + yyProgram = Program(begin, end); + startLinizer(); +} + diff --git a/src/libs/qmljs/qmljslineinfo.h b/src/libs/qmljs/qmljslineinfo.h new file mode 100644 index 0000000000000000000000000000000000000000..2112e313f0a890b4ec3e0b49cc6012c7d9a75db5 --- /dev/null +++ b/src/libs/qmljs/qmljslineinfo.h @@ -0,0 +1,126 @@ +/************************************************************************** +** +** This file is part of Qt Creator +** +** Copyright (c) 2010 Nokia Corporation and/or its subsidiary(-ies). +** +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** Commercial Usage +** +** Licensees holding valid Qt Commercial licenses may use this file in +** accordance with the Qt Commercial License Agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and Nokia. +** +** 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. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at http://qt.nokia.com/contact. +** +**************************************************************************/ + +#ifndef QMLJSLINEINFO_H +#define QMLJSLINEINFO_H + +#include <qmljs/qmljs_global.h> +#include <qmljs/qmljsscanner.h> + +#include <QtCore/QRegExp> +#include <QtCore/QStringList> +#include <QtGui/QTextBlock> +#include <QtGui/QTextCursor> + +namespace QmlJS { + +class QMLJS_EXPORT LineInfo +{ + Q_DISABLE_COPY(LineInfo) + +public: + LineInfo(); + virtual ~LineInfo(); + + bool isUnfinishedLine(); + bool isContinuationLine(); + + void initialize(QTextBlock begin, QTextBlock end); + +protected: + static const int SmallRoof; + static const int BigRoof; + + QString trimmedCodeLine(const QString &t); + QChar firstNonWhiteSpace(const QString &t) const; + + bool hasUnclosedParenOrBracket() const; + + /* + 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. + */ + + bool readLine(); + void startLinizer(); + bool bottomLineStartsInMultilineComment(); + bool matchBracelessControlStatement(); + + Token lastToken() const; + QStringRef tokenText(const Token &token) const; + +protected: + struct LinizerState + { + LinizerState() + : braceDepth(0), + leftBraceFollows(false), + pendingRightBrace(false) + { } + + int braceDepth; + bool leftBraceFollows; + bool pendingRightBrace; + QString line; + QList<Token> tokens; + QTextBlock iter; + }; + + class Program + { + public: + Program() {} + Program(QTextBlock begin, QTextBlock end) + : begin(begin), end(end) {} + + QTextBlock firstBlock() const { return begin; } + QTextBlock lastBlock() const { return end; } + + private: + QTextBlock begin, end; + }; + + Program yyProgram; + LinizerState yyLinizerState; + + // shorthands + const QString *yyLine; + const int *yyBraceDepth; + const bool *yyLeftBraceFollows; + + QRegExp braceX; +}; + +} // namespace QmlJS + +#endif // QMLJSLINEINFO_H +