kde-extraapps/kdevplatform/language/duchain/stringhelpers.cpp
2015-07-26 14:23:17 +03:00

631 lines
15 KiB
C++

/*
Copyright 2007 David Nolden <david.nolden.kdevelop@art-master.de>
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Library General Public
License version 2 as published by the Free Software Foundation.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Library General Public License for more details.
You should have received a copy of the GNU Library General Public License
along with this library; see the file COPYING.LIB. If not, write to
the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
Boston, MA 02110-1301, USA.
*/
#include "stringhelpers.h"
#include "safetycounter.h"
#include <QString>
#include <QStringList>
#include <interfaces/icore.h>
#include <interfaces/ilanguagecontroller.h>
#include <interfaces/icompletionsettings.h>
namespace KDevelop
{
class ParamIteratorPrivate
{
public:
QString m_prefix;
QString m_source;
QString m_parens;
int m_cur;
int m_curEnd;
int m_end;
int next() const
{
return findCommaOrEnd( m_source, m_cur, m_parens[ 1 ] );
}
};
bool parenFits( QChar c1, QChar c2 )
{
if( c1 == '<' && c2 == '>' ) return true;
else if( c1 == '(' && c2 == ')' ) return true;
else if( c1 == '[' && c2 == ']' ) return true;
else if( c1 == '{' && c2 == '}' ) return true;
else
return false;
}
int findClose( const QString& str , int pos )
{
int depth = 0;
QList<QChar> st;
QChar last = ' ';
for( int a = pos; a < (int)str.length(); a++)
{
switch(str[a].unicode()) {
case '<':
case '(':
case '[':
case '{':
st.push_front( str[a] );
depth++;
break;
case '>':
if( last == '-' ) break;
case ')':
case ']':
case '}':
if( !st.isEmpty() && parenFits(st.front(), str[a]) )
{
depth--;
st.pop_front();
}
break;
case '"':
last = str[a];
a++;
while( a < (int)str.length() && (str[a] != '"' || last == '\\'))
{
last = str[a];
a++;
}
continue;
break;
case '\'':
last = str[a];
a++;
while( a < (int)str.length() && (str[a] != '\'' || last == '\\'))
{
last = str[a];
a++;
}
continue;
break;
}
last = str[a];
if( depth == 0 )
{
return a;
}
}
return -1;
}
int findCommaOrEnd( const QString& str , int pos, QChar validEnd)
{
for( int a = pos; a < (int)str.length(); a++)
{
switch(str[a].unicode())
{
case '"':
case '(':
case '[':
case '{':
case '<':
a = findClose( str, a );
if( a == -1 ) return str.length();
break;
case ')':
case ']':
case '}':
case '>':
if( validEnd != ' ' && validEnd != str[a] )
continue;
case ',':
return a;
}
}
return str.length();
}
QString reverse( const QString& str ) {
QString ret;
int len = str.length();
for( int a = len-1; a >= 0; --a ) {
switch(str[a].unicode()) {
case '(':
ret += ')';
continue;
case '[':
ret += ']';
continue;
case '{':
ret += '}';
continue;
case '<':
ret += '>';
continue;
case ')':
ret += '(';
continue;
case ']':
ret += '[';
continue;
case '}':
ret += '{';
continue;
case '>':
ret += '<';
continue;
default:
ret += str[a];
continue;
}
}
return ret;
}
///@todo this hackery sucks
QString escapeForBracketMatching(QString str) {
str.replace("<<", "$&");
str.replace(">>", "$$");
str.replace("\\\"", "$!");
str.replace("->", "$?");
return str;
}
QString escapeFromBracketMatching(QString str) {
str.replace("$&", "<<");
str.replace("$$", ">>");
str.replace("$!", "\\\"");
str.replace("$?", "->");
return str;
}
void skipFunctionArguments(QString str, QStringList& skippedArguments, int& argumentsStart ) {
QString withStrings = escapeForBracketMatching(str);
str = escapeForBracketMatching(clearStrings(str));
//Blank out everything that can confuse the bracket-matching algorithm
QString reversed = reverse( str.left(argumentsStart) );
QString withStringsReversed = reverse( withStrings.left(argumentsStart) );
//Now we should decrease argumentStart at the end by the count of steps we go right until we find the beginning of the function
SafetyCounter s( 1000 );
int pos = 0;
int len = reversed.length();
//we are searching for an opening-brace, but the reversion has also reversed the brace
while( pos < len && s ) {
int lastPos = pos;
pos = KDevelop::findCommaOrEnd( reversed, pos ) ;
if( pos > lastPos ) {
QString arg = reverse( withStringsReversed.mid(lastPos, pos-lastPos) ).trimmed();
if( !arg.isEmpty() )
skippedArguments.push_front( escapeFromBracketMatching(arg) ); //We are processing the reversed reverseding, so push to front
}
if( reversed[pos] == ')' || reversed[pos] == '>' )
break;
else
++pos;
}
if( !s ) {
kDebug() << "skipFunctionArguments: Safety-counter triggered";
}
argumentsStart -= pos;
}
QString reduceWhiteSpace(QString str) {
str = str.trimmed();
QString ret;
QChar spaceChar = ' ';
bool hadSpace = false;
for( int a = 0; a < str.length(); a++ ) {
if( str[a].isSpace() ) {
hadSpace = true;
} else {
if( hadSpace ) {
hadSpace = false;
ret += spaceChar;
}
ret += str[a];
}
}
return ret;
}
void fillString( QString& str, int start, int end, QChar replacement ) {
for( int a = start; a < end; a++) str[a] = replacement;
}
QString stripFinalWhitespace(QString str) {
for( int a = str.length() - 1; a >= 0; --a ) {
if( !str[a].isSpace() )
return str.left( a+1 );
}
return QString();
}
QString clearComments( QString str, QChar replacement ) {
QString withoutStrings = clearStrings(str, '$');
int pos = -1, newlinePos = -1, endCommentPos = -1, nextPos = -1, dest = -1;
while ( (pos = str.indexOf('/', pos + 1)) != -1 )
{
newlinePos = withoutStrings.indexOf('\n', pos);
if (withoutStrings[pos + 1] == '/')
{
//C style comment
dest = newlinePos == -1 ? str.length() : newlinePos;
fillString(str, pos, dest, replacement);
pos = dest;
}
else if (withoutStrings[pos + 1] == '*')
{
//CPP style comment
endCommentPos = withoutStrings.indexOf("*/", pos + 2);
if (endCommentPos != -1)
endCommentPos += 2;
dest = endCommentPos == -1 ? str.length() : endCommentPos;
while (pos < dest)
{
nextPos = (dest > newlinePos && newlinePos != -1) ? newlinePos : dest;
fillString(str, pos, nextPos, replacement);
pos = nextPos;
if (pos == newlinePos)
{
++pos; //Keep newlines intact, skip them
newlinePos = withoutStrings.indexOf('\n', pos + 1);
}
}
}
}
return str;
}
QString clearStrings( QString str, QChar replacement ) {
bool inString = false;
for(int pos = 0; pos < str.length(); ++pos) {
//Skip cpp comments
if(!inString && pos + 1 < str.length() && str[pos] == '/' && str[pos+1] == '*')
{
pos += 2;
while(pos + 1 < str.length()) {
if (str[pos] == '*' && str[pos + 1] == '/') {
++pos;
break;
}
++pos;
}
}
//Skip cstyle comments
if(!inString && pos + 1 < str.length() && str[pos] == '/' && str[pos+1] == '/')
{
pos += 2;
while(pos < str.length() && str[pos] != '\n') {
++pos;
}
}
//Skip a character a la 'b'
if(!inString && str[pos] == '\'' && pos + 3 <= str.length())
{
//skip the opening '
str[pos] = replacement;
++pos;
if(str[pos] == '\\')
{
//Skip an escape character
str[pos] = replacement;
++pos;
}
//Skip the actual character
str[pos] = replacement;
++pos;
//Skip the closing '
if(pos < str.length() && str[pos] == '\'')
{
str[pos] = replacement;
}
continue;
}
bool intoString = false;
if(str[pos] == '"' && !inString)
intoString = true;
if(inString || intoString) {
if(inString) {
if(str[pos] == '"')
inString = false;
}else{
inString = true;
}
bool skip = false;
if(str[pos] == '\\')
skip = true;
str[pos] = replacement;
if(skip) {
++pos;
if(pos < str.length())
str[pos] = replacement;
}
}
}
return str;
}
static inline bool isWhite( QChar c ) {
return c.isSpace();
}
static inline bool isWhite( char c ) {
return QChar(c).isSpace();
}
void rStrip( const QString& str, QString& from ) {
if( str.isEmpty() ) return;
int i = 0;
int ip = from.length();
int s = from.length();
for( int a = s-1; a >= 0; a-- ) {
if( isWhite( from[a] ) ) {
continue;
} else {
if( from[a] == str[i] ) {
i++;
ip = a;
if( i == (int)str.length() ) break;
} else {
break;
}
}
}
if( ip != (int)from.length() ) from = from.left( ip );
}
void strip( const QString& str, QString& from ) {
if( str.isEmpty() ) return;
int i = 0;
int ip = 0;
int s = from.length();
for( int a = 0; a < s; a++ ) {
if( isWhite( from[a] ) ) {
continue;
} else {
if( from[a] == str[i] ) {
i++;
ip = a+1;
if( i == (int)str.length() ) break;
} else {
break;
}
}
}
if( ip ) from = from.mid( ip );
}
int rStrip( const QByteArray& str, QByteArray& from ) {
if( str.isEmpty() ) return 0;
int i = 0;
int ip = from.length();
int s = from.length();
for( int a = s-1; a >= 0; a-- ) {
if( isWhite( from[a] ) ) { ///@todo Check whether this can cause problems in utf-8, as only one real character is treated!
continue;
} else {
if( from[a] == str[i] ) {
i++;
ip = a;
if( i == (int)str.length() ) break;
} else {
break;
}
}
}
if( ip != (int)from.length() ) from = from.left( ip );
return s - from.length();
}
int strip( const QByteArray& str, QByteArray& from ) {
if( str.isEmpty() ) return 0;
int i = 0;
int ip = 0;
int s = from.length();
for( int a = 0; a < s; a++ ) {
if( isWhite( from[a] ) ) { ///@todo Check whether this can cause problems in utf-8, as only one real character is treated!
continue;
} else {
if( from[a] == str[i] ) {
i++;
ip = a+1;
if( i == (int)str.length() ) break;
} else {
break;
}
}
}
if( ip ) from = from.mid( ip );
return s - from.length();
}
QString formatComment( const QString& comment ) {
QString ret;
QStringList lines = comment.split( '\n', QString::KeepEmptyParts );
if ( !lines.isEmpty() ) {
QStringList::iterator it = lines.begin();
QStringList::iterator eit = lines.end();
// remove common leading chars from the beginning of lines
for( ; it != eit; ++it ) {
strip( "///", *it );
strip( "//", *it );
strip( "**", *it );
rStrip( "/**", *it );
}
ret = lines.join( "\n" );
}
return ret.trimmed();
}
QByteArray formatComment( const QByteArray& comment ) {
QByteArray ret;
QList<QByteArray> lines = comment.split( '\n' );
if ( !lines.isEmpty() ) {
QList<QByteArray>::iterator it = lines.begin();
QList<QByteArray>::iterator eit = lines.end();
// remove common leading chars from the beginning of lines
for( ; it != eit; ++it ) {
strip( "///", *it );
strip( "//", *it );
strip( "**", *it );
rStrip( "/**", *it );
}
foreach(const QByteArray& line, lines) {
if(!ret.isEmpty())
ret += '\n';
ret += line;
}
}
return ret.trimmed();
}
ParamIterator::~ParamIterator()
{
delete d;
}
ParamIterator::ParamIterator( QString parens, QString source, int offset ) : d(new ParamIteratorPrivate)
{
d->m_source = source;
d->m_parens = parens;
d->m_cur = offset;
d->m_curEnd = offset;
d->m_end = d->m_source.length();
///The whole search should be stopped when: A) The end-sign is found on the top-level B) A closing-brace of parameters was found
int parenBegin = d->m_source.indexOf( parens[ 0 ], offset );
//Search for an interrupting end-sign that comes before the found paren-begin
int foundEnd = -1;
if( parens.length() > 2 ) {
foundEnd = d->m_source.indexOf( parens[2], offset );
if( foundEnd > parenBegin && parenBegin != -1 )
foundEnd = -1;
}
if( foundEnd != -1 ) {
//We have to stop the search, because we found an interrupting end-sign before the opening-paren
d->m_prefix = d->m_source.mid( offset, foundEnd - offset );
d->m_curEnd = d->m_end = d->m_cur = foundEnd;
} else {
if( parenBegin != -1 ) {
//We have a valid prefix before an opening-paren. Take the prefix, and start iterating parameters.
d->m_prefix = d->m_source.mid( offset, parenBegin - offset );
d->m_cur = parenBegin + 1;
d->m_curEnd = d->next();
if( d->m_curEnd == d->m_source.length() ) {
//The paren was not closed. It might be an identifier like "operator<", so count everything as prefix.
d->m_prefix = d->m_source.mid(offset);
d->m_curEnd = d->m_end = d->m_cur = d->m_source.length();
}
} else {
//We have neither found an ending-character, nor an opening-paren, so take the whole input and end
d->m_prefix = d->m_source.mid(offset);
d->m_curEnd = d->m_end = d->m_cur = d->m_source.length();
}
}
}
ParamIterator& ParamIterator::operator ++()
{
if( d->m_source[d->m_curEnd] == d->m_parens[1] ) {
//We have reached the end-paren. Stop iterating.
d->m_cur = d->m_end = d->m_curEnd + 1;
} else {
//Iterate on through parameters
d->m_cur = d->m_curEnd + 1;
if ( d->m_cur < ( int ) d->m_source.length() )
{
d->m_curEnd = d->next();
}
}
return *this;
}
QString ParamIterator::operator *()
{
return d->m_source.mid( d->m_cur, d->m_curEnd - d->m_cur ).trimmed();
}
ParamIterator::operator bool() const
{
return d->m_cur < ( int ) d->m_end;
}
QString ParamIterator::prefix() const
{
return d->m_prefix;
}
uint ParamIterator::position() const {
return (uint)d->m_cur;
}
}