You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

346 lines
8.7 KiB

/**
* @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