File indexing completed on 2024-04-21 15:25:36

0001 /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
0002  *
0003  *   Copyright (C) 2006-2010 by Jim Pattee <jimp03@email.com>
0004  *   Copyright (C) 1998-2002 by Tal Davidson
0005  *   <http://www.gnu.org/licenses/lgpl-3.0.html>
0006  *
0007  *   This file is a part of Artistic Style - an indentation and
0008  *   reformatting tool for C, C++, C# and Java source files.
0009  *   <http://astyle.sourceforge.net>
0010  *
0011  *   Artistic Style is free software: you can redistribute it and/or modify
0012  *   it under the terms of the GNU Lesser General Public License as published
0013  *   by the Free Software Foundation, either version 3 of the License, or
0014  *   (at your option) any later version.
0015  *
0016  *   Artistic Style is distributed in the hope that it will be useful,
0017  *   but WITHOUT ANY WARRANTY; without even the implied warranty of
0018  *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
0019  *   GNU Lesser General Public License for more details.
0020  *
0021  *   You should have received a copy of the GNU Lesser General Public License
0022  *   along with Artistic Style.  If not, see <http://www.gnu.org/licenses/>.
0023  *
0024  * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
0025  */
0026 
0027 #include "astyle.h"
0028 
0029 
0030 namespace astyle
0031 {
0032 
0033 // ---------------------------- functions for ASEnhancer Class -------------------------------------
0034 
0035 /**
0036  * ASEnhancer constructor
0037  */
0038 ASEnhancer::ASEnhancer()
0039 {
0040     // the following prevents warning messages with cppcheck
0041     // it will NOT compile if activated
0042 //  init();
0043 }
0044 
0045 /**
0046  * Destructor of ASEnhancer
0047  */
0048 ASEnhancer::~ASEnhancer()
0049 {
0050 }
0051 
0052 /**
0053  * initialize the ASEnhancer.
0054  *
0055  * init() is called each time an ASFormatter object is initialized.
0056  */
0057 void ASEnhancer::init(int fileType,
0058                       int _indentLength,
0059                       string _indentString,
0060                       bool _caseIndent,
0061                       bool _emptyLineFill)
0062 {
0063     // formatting variables from ASFormatter and ASBeautifier
0064     ASBase::init(fileType);
0065     indentLength = _indentLength;
0066     if (_indentString == "\t")
0067         useTabs = true;
0068     else
0069         useTabs = false;
0070 
0071     caseIndent    = _caseIndent;
0072     emptyLineFill = _emptyLineFill;
0073     quoteChar = '\'';
0074 
0075     // unindent variables
0076     lineNumber = 0;
0077     bracketCount = 0;
0078     isInComment = false;
0079     isInQuote = false;
0080     switchDepth = 0;
0081     lookingForCaseBracket = false;
0082     unindentNextLine = false;
0083 
0084     // switch struct and vector
0085     sw.switchBracketCount = 0;
0086     sw.unindentDepth = 0;
0087     sw.unindentCase = false;
0088     swVector.clear();
0089 
0090     // other variables
0091     nextLineIsEventIndent = false;
0092     isInEventTable = false;
0093     nextLineIsDeclareIndent = false;
0094     isInDeclareSection = false;
0095 }
0096 
0097 /**
0098  * additional formatting for line of source code.
0099  * every line of source code in a source code file should be sent
0100  *     one after the other to this function.
0101  * indents event tables
0102  * unindents the case blocks
0103  *
0104  * @param line       the original formatted line will be updated if necessary.
0105  */
0106 void ASEnhancer::enhance(string &line, bool isInSQL)
0107 {
0108     bool isSpecialChar = false;         // is a backslash escape character
0109 
0110     lineNumber++;
0111 
0112     // check for beginning of event table
0113     if (nextLineIsEventIndent)
0114     {
0115         isInEventTable = true;
0116         nextLineIsEventIndent = false;
0117     }
0118 
0119     // check for beginning of SQL declare section
0120     if (nextLineIsDeclareIndent)
0121     {
0122         isInDeclareSection = true;
0123         nextLineIsDeclareIndent = false;
0124     }
0125 
0126     if (line.length() == 0
0127             && ! isInEventTable
0128             && ! isInDeclareSection
0129             && ! emptyLineFill)
0130         return;
0131 
0132     // test for unindent on attached brackets
0133     if (unindentNextLine)
0134     {
0135         sw.unindentDepth++;
0136         sw.unindentCase = true;
0137         unindentNextLine = false;
0138     }
0139 
0140     // parse characters in the current line.
0141 
0142     for (size_t i = 0; i < line.length(); i++)
0143     {
0144         char ch = line[i];
0145 
0146         // bypass whitespace
0147         if (isWhiteSpace(ch))
0148             continue;
0149 
0150         // handle special characters (i.e. backslash+character such as \n, \t, ...)
0151         if (isSpecialChar)
0152         {
0153             isSpecialChar = false;
0154             continue;
0155         }
0156         if (!(isInComment) && line.compare(i, 2, "\\\\") == 0)
0157         {
0158             i++;
0159             continue;
0160         }
0161         if (!(isInComment) && ch == '\\')
0162         {
0163             isSpecialChar = true;
0164             continue;
0165         }
0166 
0167         // handle quotes (such as 'x' and "Hello Dolly")
0168         if (!isInComment && (ch == '"' || ch == '\''))
0169         {
0170             if (!isInQuote)
0171             {
0172                 quoteChar = ch;
0173                 isInQuote = true;
0174             }
0175             else if (quoteChar == ch)
0176             {
0177                 isInQuote = false;
0178                 continue;
0179             }
0180         }
0181 
0182         if (isInQuote)
0183             continue;
0184 
0185         // handle comments
0186 
0187         if (!(isInComment) && line.compare(i, 2, "//") == 0)
0188         {
0189             // check for windows line markers
0190             if (line.compare(i + 2, 1, "\xf0") > 0)
0191                 lineNumber--;
0192             break;                 // finished with the line
0193         }
0194         else if (!(isInComment) && line.compare(i, 2, "/*") == 0)
0195         {
0196             isInComment = true;
0197             i++;
0198             continue;
0199         }
0200         else if ((isInComment) && line.compare(i, 2, "*/") == 0)
0201         {
0202             isInComment = false;
0203             i++;
0204             continue;
0205         }
0206 
0207         if (isInComment)
0208             continue;
0209 
0210         // if we have reached this far then we are NOT in a comment or string of special characters
0211 
0212         if (line[i] == '{')
0213             bracketCount++;
0214 
0215         if (line[i] == '}')
0216             bracketCount--;
0217 
0218         bool isPotentialKeyword = isCharPotentialHeader(line, i);
0219 
0220         // ----------------  process event tables  --------------------------------------
0221 
0222         if (isPotentialKeyword)
0223         {
0224             if (findKeyword(line, i, "BEGIN_EVENT_TABLE")
0225                     || findKeyword(line, i, "BEGIN_MESSAGE_MAP"))
0226             {
0227                 nextLineIsEventIndent = true;
0228                 break;
0229             }
0230 
0231             if (findKeyword(line, i, "END_EVENT_TABLE")
0232                     || findKeyword(line, i, "END_MESSAGE_MAP"))
0233             {
0234                 isInEventTable = false;
0235                 break;
0236             }
0237         }
0238 
0239         // ----------------  process SQL  -----------------------------------------------
0240 
0241         if (isInSQL)
0242         {
0243             if (isBeginDeclareSectionSQL(line, i))
0244                 nextLineIsDeclareIndent = true;
0245             if (isEndDeclareSectionSQL(line, i))
0246                 isInDeclareSection = false;
0247             break;
0248         }
0249 
0250         // ----------------  process switch statements  ---------------------------------
0251 
0252         if (isPotentialKeyword && findKeyword(line, i, "switch"))
0253         {
0254             switchDepth++;
0255             swVector.push_back(sw);                         // save current variables
0256             sw.switchBracketCount = 0;
0257             sw.unindentCase = false;                        // don't clear case until end of switch
0258             i += 5;                                         // bypass switch statement
0259             continue;
0260         }
0261 
0262         // just want unindented switch statements from this point
0263 
0264         if (caseIndent || switchDepth == 0)
0265         {
0266             // bypass the entire word
0267             if (isPotentialKeyword)
0268             {
0269                 string name = getCurrentWord(line, i);
0270                 i += name.length() - 1;
0271             }
0272             continue;
0273         }
0274 
0275         i = processSwitchBlock(line, i);
0276 
0277     }   // end of for loop * end of for loop * end of for loop * end of for loop
0278 
0279     if (isInEventTable || isInDeclareSection)
0280     {
0281         if (line[0] != '#')
0282             indentLine(line, 1);
0283     }
0284 
0285     if (sw.unindentDepth > 0)
0286         unindentLine(line, sw.unindentDepth);
0287 }
0288 
0289 /**
0290  * find the colon following a 'case' statement
0291  *
0292  * @param line          a reference to the line.
0293  * @param i             the line index of the case statement.
0294  * @return              the line index of the colon.
0295  */
0296 size_t ASEnhancer::findCaseColon(string  &line, size_t caseIndex) const
0297 {
0298     size_t i = caseIndex;
0299     bool isInQuote = false;
0300     char quoteChar = ' ';
0301     for (; i < line.length(); i++)
0302     {
0303         if (isInQuote)
0304         {
0305             if (line[i] == '\\')
0306             {
0307                 i++;
0308                 continue;
0309             }
0310             else if (line[i] == quoteChar)          // check ending quote
0311             {
0312                 isInQuote = false;
0313                 quoteChar = ' ';
0314                 continue;
0315             }
0316             else
0317             {
0318                 continue;                           // must close quote before continuing
0319             }
0320         }
0321         if (line[i] == '\'' || line[i] == '\"')     // check opening quote
0322         {
0323             isInQuote = true;
0324             quoteChar = line[i];
0325             continue;
0326         }
0327         if (line[i] == ':')
0328         {
0329             if ((i + 1 < line.length()) && (line[i + 1] == ':'))
0330                 i++;                                // bypass scope resolution operator
0331             else
0332                 break;                              // found it
0333         }
0334     }
0335     return i;
0336 }
0337 
0338 /**
0339  * indent a line by a given number of tabsets
0340  *    by inserting leading whitespace to the line argument.
0341  *
0342  * @param line          a reference to the line to indent.
0343  * @param unindent      the number of tabsets to insert.
0344  * @return              the number of characters inserted.
0345  */
0346 int ASEnhancer::indentLine(string  &line, int indent) const
0347 {
0348     if (line.length() == 0
0349             && ! emptyLineFill)
0350         return 0;
0351 
0352     size_t charsToInsert;                       // number of chars to insert
0353 
0354     if (useTabs)                                // if formatted with tabs
0355     {
0356         charsToInsert = indent;                 // tabs to insert
0357         line.insert((size_t) 0, charsToInsert, '\t');    // insert the tabs
0358     }
0359     else
0360     {
0361         charsToInsert = indent * indentLength;  // compute chars to insert
0362         line.insert((size_t)0, charsToInsert, ' ');     // insert the spaces
0363     }
0364 
0365     return charsToInsert;
0366 }
0367 
0368 /**
0369  * check for SQL "BEGIN DECLARE SECTION".
0370  * must compare case insensitive and allow any spacing between words.
0371  *
0372  * @param line          a reference to the line to indent.
0373  * @param index         the current line index.
0374  * @return              true if a hit.
0375  */
0376 bool ASEnhancer::isBeginDeclareSectionSQL(string  &line, size_t index) const
0377 {
0378     string word;
0379     size_t hits = 0;
0380     size_t i;
0381     for (i = index; i < line.length(); i++)
0382     {
0383         i = line.find_first_not_of(" \t", i);
0384         if (i == string::npos)
0385             return false;
0386         if (line[i] == ';')
0387             break;
0388         if (!isCharPotentialHeader(line, i))
0389             continue;
0390         word = getCurrentWord(line, i);
0391         for (size_t j = 0; j < word.length(); j++)
0392             word[j] = (char) toupper(word[j]);
0393         if (word == "EXEC" || word == "SQL")
0394         {
0395             i += word.length() - 1;
0396             continue;
0397         }
0398         if (word == "DECLARE" || word == "SECTION")
0399         {
0400             hits++;
0401             i += word.length() - 1;
0402             continue;
0403         }
0404         if (word == "BEGIN")
0405         {
0406             hits++;
0407             i += word.length() - 1;
0408             continue;
0409         }
0410         return false;
0411     }
0412     if (hits == 3)
0413         return true;
0414     return false;
0415 }
0416 
0417 /**
0418  * check for SQL "END DECLARE SECTION".
0419  * must compare case insensitive and allow any spacing between words.
0420  *
0421  * @param line          a reference to the line to indent.
0422  * @param index         the current line index.
0423  * @return              true if a hit.
0424  */
0425 bool ASEnhancer::isEndDeclareSectionSQL(string  &line, size_t index) const
0426 {
0427     string word;
0428     size_t hits = 0;
0429     size_t i;
0430     for (i = index; i < line.length(); i++)
0431     {
0432         i = line.find_first_not_of(" \t", i);
0433         if (i == string::npos)
0434             return false;
0435         if (line[i] == ';')
0436             break;
0437         if (!isCharPotentialHeader(line, i))
0438             continue;
0439         word = getCurrentWord(line, i);
0440         for (size_t j = 0; j < word.length(); j++)
0441             word[j] = (char) toupper(word[j]);
0442         if (word == "EXEC" || word == "SQL")
0443         {
0444             i += word.length() - 1;
0445             continue;
0446         }
0447         if (word == "DECLARE" || word == "SECTION")
0448         {
0449             hits++;
0450             i += word.length() - 1;
0451             continue;
0452         }
0453         if (word == "END")
0454         {
0455             hits++;
0456             i += word.length() - 1;
0457             continue;
0458         }
0459         return false;
0460     }
0461     if (hits == 3)
0462         return true;
0463     return false;
0464 }
0465 
0466 /**
0467  * process the character at the current index in a switch block.
0468  *
0469  * @param line          a reference to the line to indent.
0470  * @param index         the current line index.
0471  * @return              the new line index.
0472  */
0473 size_t ASEnhancer::processSwitchBlock(string  &line, size_t index)
0474 {
0475     size_t i = index;
0476     bool isPotentialKeyword = isCharPotentialHeader(line, i);
0477 
0478     if (line[i] == '{')
0479     {
0480         sw.switchBracketCount++;
0481         if (lookingForCaseBracket)                      // if 1st after case statement
0482         {
0483             sw.unindentCase = true;                     // unindenting this case
0484             sw.unindentDepth++;
0485             lookingForCaseBracket = false;              // not looking now
0486         }
0487         return i;
0488     }
0489     lookingForCaseBracket = false;                      // no opening bracket, don't indent
0490 
0491     if (line[i] == '}')                                 // if close bracket
0492     {
0493         sw.switchBracketCount--;
0494         if (sw.switchBracketCount == 0)                 // if end of switch statement
0495         {
0496             switchDepth--;                              // one less switch
0497             sw = swVector.back();                       // restore sw struct
0498             swVector.pop_back();                        // remove last entry from stack
0499         }
0500         return i;
0501     }
0502 
0503     if (isPotentialKeyword
0504             && (findKeyword(line, i, "case") || findKeyword(line, i, "default")))
0505     {
0506         if (sw.unindentCase)                            // if unindented last case
0507         {
0508             sw.unindentCase = false;                    // stop unindenting previous case
0509             sw.unindentDepth--;                         // reduce depth
0510         }
0511 
0512         i = findCaseColon(line, i);
0513 
0514         i++;
0515         for (; i < line.length(); i++)                  // bypass whitespace
0516         {
0517             if (!isWhiteSpace(line[i]))
0518                 break;
0519         }
0520         if (i < line.length())
0521         {
0522             if (line[i] == '{')
0523             {
0524                 sw.switchBracketCount++;
0525                 unindentNextLine = true;
0526                 return i;
0527             }
0528         }
0529         lookingForCaseBracket = true;                   // bracket must be on next line
0530         i--;                                            // need to check for comments
0531         return i;
0532     }
0533     if (isPotentialKeyword)
0534     {
0535         string name = getCurrentWord(line, i);          // bypass the entire name
0536         i += name.length() - 1;
0537     }
0538     return i;
0539 }
0540 
0541 /**
0542  * unindent a line by a given number of tabsets
0543  *    by erasing the leading whitespace from the line argument.
0544  *
0545  * @param line          a reference to the line to unindent.
0546  * @param unindent      the number of tabsets to erase.
0547  * @return              the number of characters erased.
0548  */
0549 int ASEnhancer::unindentLine(string  &line, int unindent) const
0550 {
0551     size_t whitespace = line.find_first_not_of(" \t");
0552 
0553     if (whitespace == string::npos)         // if line is blank
0554         whitespace = line.length();         // must remove padding, if any
0555 
0556     if (whitespace == 0)
0557         return 0;
0558 
0559     size_t charsToErase;                    // number of chars to erase
0560 
0561     if (useTabs)                            // if formatted with tabs
0562     {
0563         charsToErase = unindent;            // tabs to erase
0564         if (charsToErase <= whitespace)     // if there is enough whitespace
0565             line.erase(0, charsToErase);    // erase the tabs
0566         else
0567             charsToErase = 0;
0568     }
0569     else
0570     {
0571         charsToErase = unindent * indentLength; // compute chars to erase
0572         if (charsToErase <= whitespace)         // if there is enough whitespace
0573             line.erase(0, charsToErase);        // erase the spaces
0574         else
0575             charsToErase = 0;
0576     }
0577 
0578     return charsToErase;
0579 }
0580 
0581 
0582 }   // end namespace astyle