/** * @file rewrite_infinite_loops.cpp * * @author Alex Henrie * @license GPL v2+ */ #include "rewrite_infinite_loops.h" #include "chunk.h" #include "newlines.h" #include "uncrustify.h" using namespace uncrustify; static bool for_needs_rewrite(Chunk *pc, E_Token desired_type) { // The 'for' statement needs to be rewritten if `for(;;)` is not the // preferred syntax for infinite loops and this 'for' is an infinite loop // with no extra tokens (such as inline comments). if (desired_type == CT_FOR) { return(false); } pc = pc->GetNext(); if (!pc->Is(CT_SPAREN_OPEN)) { return(false); } pc = pc->GetNext(); if (!pc->Is(CT_SEMICOLON)) { return(false); } pc = pc->GetNext(); if (!pc->Is(CT_SEMICOLON)) { return(false); } pc = pc->GetNext(); if (!pc->Is(CT_SPAREN_CLOSE)) { return(false); } return(true); } static bool while_needs_rewrite(Chunk *keyword, E_Token desired_type, const char *desired_condition) { // The 'while' statement needs to be rewritten if it has only the tokens that // are strictly necessary (keyword, condition, two parentheses, and semicolon // if do-while) and either the keyword or the condition needs to be changed. Chunk *oparen = keyword->GetNext(); Chunk *condition = oparen->GetNext(); Chunk *cparen = condition->GetNext(); if (!oparen->Is(CT_SPAREN_OPEN)) { return(false); } if ( strcmp(condition->Text(), "true") != 0 && strcmp(condition->Text(), "1") != 0) { return(false); } if (!cparen->Is(CT_SPAREN_CLOSE)) { return(false); } if (keyword->Is(CT_WHILE_OF_DO)) { Chunk *semicolon = cparen->GetNext(); if (!semicolon->Is(CT_SEMICOLON)) { return(false); } } if (!keyword->Is(desired_type)) { return(true); } if ( strcmp(condition->Text(), "true") == 0 && strcmp(desired_condition, "true") != 0) { return(true); } if ( strcmp(condition->Text(), "1") == 0 && strcmp(desired_condition, "1") != 0) { return(true); } return(false); } // while_needs_rewrite void rewrite_loop_keyword(Chunk *keyword, E_Token new_type) { keyword->SetType(new_type); switch (new_type) { case CT_DO: keyword->SetOrigColEnd(keyword->GetOrigColEnd() + strlen("do") - keyword->Len()); keyword->Str() = "do"; break; case CT_WHILE: case CT_WHILE_OF_DO: keyword->SetOrigColEnd(keyword->GetOrigColEnd() + strlen("while") - keyword->Len()); keyword->Str() = "while"; break; case CT_FOR: keyword->SetOrigColEnd(keyword->GetOrigColEnd() + strlen("for") - keyword->Len()); keyword->Str() = "for"; break; default: break; } } static void move_one_token(Chunk * &source, Chunk * &destination, E_Token parent_type) { Chunk *next_source = source->GetNext(); // Place the source token immediately after the destination token, without // any whitespace. source->MoveAfter(destination); source->SetColumn(destination->GetColumn() + destination->Len()); source->SetOrigCol(destination->GetOrigCol() + destination->Len()); source->SetOrigColEnd(source->GetOrigColEnd() + source->Len()); source->SetOrigPrevSp(0); source->SetParentType(parent_type); destination = source; source = next_source; } static void rewrite_loop_condition(Chunk * &source, Chunk * &destination, E_Token desired_type, const char *desired_condition) { // Move the opening parenthesis move_one_token(source, destination, desired_type); // Move the condition if (desired_type == CT_FOR) { source->SetType(CT_SEMICOLON); source->SetParentType(CT_FOR); source->Str() = ";"; move_one_token(source, destination, desired_type); destination = (destination)->CopyAndAddAfter(destination); } else { source->SetType(CT_WORD); source->Str() = desired_condition; move_one_token(source, destination, desired_type); } // If converting a 'for' to a 'while', delete the second semicolon if (source->Is(CT_SEMICOLON)) { Chunk *next_source = source->GetNext(); Chunk::Delete(source); source = next_source; } // Move the closing parenthesis move_one_token(source, destination, desired_type); } void rewrite_loop_in_place(Chunk *keyword, E_Token desired_type, const char *desired_condition) { Chunk *top = keyword->GetNext(); Chunk *bottom = keyword; rewrite_loop_keyword(keyword, desired_type); rewrite_loop_condition(top, bottom, desired_type, desired_condition); } static Chunk *find_start_brace(Chunk *pc) { while (!pc->IsBraceOpen()) { pc = pc->GetNextNcNnl(); } return(pc); } void rewrite_infinite_loops() { LOG_FUNC_ENTRY(); E_Token desired_type; const char *desired_condition; switch (options::mod_infinite_loop()) { case 1: // for(;;) desired_type = CT_FOR; desired_condition = nullptr; break; case 2: // while(true) desired_type = CT_WHILE; desired_condition = "true"; break; case 3: // do...while(true) desired_type = CT_WHILE_OF_DO; desired_condition = "true"; break; case 4: // while(1) desired_type = CT_WHILE; desired_condition = "1"; break; case 5: // do...while(1) desired_type = CT_WHILE_OF_DO; desired_condition = "1"; break; default: return; } for (Chunk *pc = Chunk::GetHead(); pc->IsNotNullChunk(); pc = pc->GetNextNcNnl()) { if (pc->Is(CT_DO)) { Chunk *start_brace = find_start_brace(pc); Chunk *end_brace = start_brace->GetClosingParen(); Chunk *while_keyword = end_brace->GetNextNcNnl(); if ( !while_keyword->Is(CT_WHILE_OF_DO) || !while_needs_rewrite(while_keyword, desired_type, desired_condition)) { continue; } if (desired_type == CT_WHILE_OF_DO) { // Change the loop condition rewrite_loop_in_place(while_keyword, desired_type, desired_condition); // Update the braces' parent types start_brace->SetParentType(CT_DO); end_brace->SetParentType(CT_DO); } else { Chunk *top = pc; Chunk *bottom = while_keyword->GetNext(); // Change the 'do' at the top of the loop to a 'for' or a 'while' rewrite_loop_keyword(top, desired_type); // Delete the 'while' at the bottom of the loop Chunk::Delete(while_keyword); // Move the rest of the tokens from the bottom to the top rewrite_loop_condition(bottom, top, desired_type, desired_condition); // Delete the final semicolon Chunk::Delete(bottom); // Update the braces' parent types start_brace->SetParentType(desired_type); end_brace->SetParentType(desired_type); } } else if ( ( pc->Is(CT_WHILE) && while_needs_rewrite(pc, desired_type, desired_condition)) || ( pc->Is(CT_FOR) && for_needs_rewrite(pc, desired_type))) { Chunk *start_brace = find_start_brace(pc); Chunk *end_brace = start_brace->GetClosingParen(); if (desired_type == CT_WHILE_OF_DO) { Chunk *top = pc; Chunk *bottom = end_brace; if (bottom->Is(CT_VBRACE_CLOSE)) { // Insert a new line before the new 'while' keyword newline_add_before(bottom); } // Add a 'while' at the bottom of the loop bottom = top->CopyAndAddAfter(bottom); rewrite_loop_keyword(bottom, CT_WHILE_OF_DO); // Change the 'while' at the top of the loop to a 'do' rewrite_loop_keyword(top, CT_DO); top = top->GetNext(); // Move the tokens from the top to the bottom rewrite_loop_condition(top, bottom, desired_type, desired_condition); // Add the final semicolon bottom = bottom->CopyAndAddAfter(bottom); bottom->SetType(CT_SEMICOLON); bottom->Str() = ";"; // Update the braces' parent types start_brace->SetParentType(CT_DO); end_brace->SetParentType(CT_DO); } else { // Change 'for' to 'while' or vice-versa rewrite_loop_in_place(pc, desired_type, desired_condition); // Update the braces' parent types start_brace->SetParentType(desired_type); end_brace->SetParentType(desired_type); } } } } // rewrite_infinite_loops