File indexing completed on 2024-03-24 04:38:41
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