/** * @file chunk.cpp * Manages and navigates the list of chunks. * * @author Ben Gardner * @license GPL v2+ */ #include "chunk.h" #include "ListManager.h" #include "prototypes.h" #include "space.h" static ChunkListManager gChunkList; // global chunk list /* * Chunk class methods */ // Null Chunk Chunk Chunk::NullChunk(true); Chunk *const Chunk::NullChunkPtr(&Chunk::NullChunk); void Chunk::CopyFrom(const Chunk &o) { m_type = o.m_type; m_parentType = o.m_parentType; m_origLine = o.m_origLine; m_origCol = o.m_origCol; m_origColEnd = o.m_origColEnd; m_origPrevSp = o.m_origPrevSp; m_column = o.m_column; m_columnIndent = o.m_columnIndent; m_nlCount = o.m_nlCount; m_nlColumn = o.m_nlColumn; m_level = o.m_level; m_braceLevel = o.m_braceLevel; m_ppLevel = o.m_ppLevel; m_afterTab = o.m_afterTab; m_flags = o.m_flags; m_alignmentData = o.m_alignmentData; m_indentationData = o.m_indentationData; m_next = Chunk::NullChunkPtr; m_prev = Chunk::NullChunkPtr; m_parent = Chunk::NullChunkPtr; m_str = o.m_str; m_trackingList = o.m_trackingList; } void Chunk::Reset() { m_type = CT_NONE; m_parentType = CT_NONE; m_origLine = 0; m_origCol = 0; m_origColEnd = 0; m_origPrevSp = 0; m_column = 0; m_columnIndent = 0; m_nlCount = 0; m_nlColumn = 0; m_level = 0; m_braceLevel = 0; m_ppLevel = 999; // use a big value to find some errors m_afterTab = false; m_flags = PCF_NONE; memset(&m_alignmentData, 0, sizeof(m_alignmentData)); m_alignmentData.next = NullChunkPtr; m_alignmentData.start = NullChunkPtr; m_alignmentData.ref = NullChunkPtr; memset(&m_indentationData, 0, sizeof(m_indentationData)); m_next = Chunk::NullChunkPtr; m_prev = Chunk::NullChunkPtr; m_parent = Chunk::NullChunkPtr; // for debugging purpose only m_str.clear(); m_trackingList = nullptr; } const char *Chunk::ElidedText(char *for_the_copy) const { const char *test_it = Text(); size_t test_it_length = strlen(test_it); size_t truncate_value = uncrustify::options::debug_truncate(); if (truncate_value != 0) { if (test_it_length > truncate_value) { memset(for_the_copy, 0, 1000); if (test_it_length < truncate_value + 30) { strncpy(for_the_copy, test_it, truncate_value - 30); for_the_copy[truncate_value - 30] = 0; } else { strncpy(for_the_copy, test_it, truncate_value); for_the_copy[truncate_value] = 0; } char *message = strcat(for_the_copy, " ... "); return(message); } else { return(test_it); } } return(test_it); } Chunk *Chunk::GetNext(const E_Scope scope) const { if (scope == E_Scope::ALL) { return(m_next); } Chunk *pc = m_next; if (TestFlags(PCF_IN_PREPROC)) { // If in a preproc, return a null chunk if trying to leave if (!pc->TestFlags(PCF_IN_PREPROC)) { return(NullChunkPtr); } return(pc); } // Not in a preproc, skip any preproc while ( pc->IsNotNullChunk() && pc->TestFlags(PCF_IN_PREPROC)) { pc = pc->m_next; } return(pc); } // Chunk::GetNext Chunk *Chunk::GetPrev(const E_Scope scope) const { if (scope == E_Scope::ALL) { return(m_prev); } Chunk *pc = m_prev; if (TestFlags(PCF_IN_PREPROC)) { // If in a preproc, return a null chunk if trying to leave if (!pc->TestFlags(PCF_IN_PREPROC)) { return(NullChunkPtr); } return(pc); } // Not in a preproc, skip any preproc while ( pc->IsNotNullChunk() && pc->TestFlags(PCF_IN_PREPROC)) { pc = pc->m_prev; } return(pc); } // Chunk::GetPrev static void chunk_log(Chunk *pc, const char *text); Chunk *Chunk::GetHead() { return(gChunkList.GetHead()); } Chunk *Chunk::GetTail() { return(gChunkList.GetTail()); } Chunk::T_SearchFnPtr Chunk::GetSearchFn(const E_Direction dir) { return((dir == E_Direction::FORWARD) ? &Chunk::GetNext : &Chunk::GetPrev); } Chunk *Chunk::Search(const T_CheckFnPtr checkFn, const E_Scope scope, const E_Direction dir, const bool cond) const { T_SearchFnPtr searchFnPtr = GetSearchFn(dir); Chunk *pc = const_cast(this); do // loop over the chunk list { pc = (pc->*searchFnPtr)(scope); // in either direction while } while ( pc->IsNotNullChunk() // the end of the list was not reached yet && ((pc->*checkFn)() != cond)); // and the demanded chunk was not found either return(pc); // the latest chunk is the searched one } bool Chunk::IsOnSameLine(const Chunk *end) const { if (IsNullChunk()) { return(false); } Chunk *tmp = GetNext(); while ( tmp->IsNotNullChunk() && tmp != end) { if (tmp->Is(CT_NEWLINE)) { return(false); } tmp = tmp->GetNext(); } return(true); } Chunk *Chunk::SearchTypeLevel(const E_Token type, const E_Scope scope, const E_Direction dir, const int level) const { T_SearchFnPtr searchFnPtr = GetSearchFn(dir); Chunk *pc = const_cast(this); do // loop over the chunk list { pc = (pc->*searchFnPtr)(scope); // in either direction while } while ( pc->IsNotNullChunk() // the end of the list was not reached yet && (!pc->IsTypeAndLevel(type, level))); // and the chunk was not found either return(pc); // the latest chunk is the searched one } Chunk *Chunk::SearchStringLevel(const char *str, const size_t len, int level, const E_Scope scope, const E_Direction dir) const { T_SearchFnPtr searchFnPtr = GetSearchFn(dir); Chunk *pc = const_cast(this); do // loop over the chunk list { pc = (pc->*searchFnPtr)(scope); // in either direction while } while ( pc->IsNotNullChunk() // the end of the list was not reached yet && !pc->IsStringAndLevel(str, len, true, level)); // and the demanded chunk was not found either return(pc); // the latest chunk is the searched one } Chunk *Chunk::SearchPpa(const T_CheckFnPtr checkFn, const bool cond) const { if (!TestFlags(PCF_IN_PREPROC)) { // if not in preprocessor, do a regular search return(Search(checkFn, E_Scope::ALL, E_Direction::FORWARD, cond)); } Chunk *pc = GetNext(); while (pc->IsNotNullChunk()) { if (!pc->TestFlags(PCF_IN_PREPROC)) { // Bail if we run off the end of the preprocessor directive, but return // the token because the caller may need to know where the search ended assert(pc->Is(CT_NEWLINE)); return(pc); } if (pc->Is(CT_NL_CONT)) { // Skip line continuation pc = pc->GetNext(); continue; } if ((pc->*checkFn)() == cond) { // Requested token was found return(pc); } pc = pc->GetNext(); } // Ran out of tokens return(Chunk::NullChunkPtr); } static void chunk_log_msg(Chunk *chunk, const log_sev_t log, const char *str) { LOG_FMT(log, "%s orig line is %zu, orig col is %zu, ", str, chunk->GetOrigLine(), chunk->GetOrigCol()); if (chunk->Is(CT_NEWLINE)) { LOG_FMT(log, ",\n"); } else if (chunk->Is(CT_VBRACE_OPEN)) { LOG_FMT(log, ",\n"); } else if (chunk->Is(CT_VBRACE_CLOSE)) { LOG_FMT(log, ",\n"); } else { LOG_FMT(log, "Text() is '%s', type is %s,\n", chunk->Text(), get_token_name(chunk->GetType())); } } static void chunk_log(Chunk *pc, const char *text) { if ( pc->IsNotNullChunk() && (cpd.unc_stage != unc_stage_e::TOKENIZE) && (cpd.unc_stage != unc_stage_e::CLEANUP)) { const log_sev_t log = LCHUNK; Chunk *prev = pc->GetPrev(); Chunk *next = pc->GetNext(); chunk_log_msg(pc, log, text); if ( prev->IsNotNullChunk() && next->IsNotNullChunk()) { chunk_log_msg(prev, log, " @ between"); chunk_log_msg(next, log, " and"); } else if (next->IsNotNullChunk()) { chunk_log_msg(next, log, " @ before"); } else if (prev->IsNotNullChunk()) { chunk_log_msg(prev, log, " @ after"); } LOG_FMT(log, " stage is %s", // Issue #3034 get_unc_stage_name(cpd.unc_stage)); log_func_stack_inline(log); } } void Chunk::Delete(Chunk * &pc) { gChunkList.Remove(pc); delete pc; pc = Chunk::NullChunkPtr; } void Chunk::MoveAfter(Chunk *ref) { LOG_FUNC_ENTRY(); if (ref == this) { return; } gChunkList.Remove(this); gChunkList.AddAfter(this, ref); // Adjust the original column m_column = ref->m_column + space_col_align(ref, this); m_origCol = m_column; m_origColEnd = m_origCol + Len(); } void Chunk::Swap(Chunk *other) { gChunkList.Swap(this, other); } bool Chunk::IsAddress() const { if ( IsNotNullChunk() && ( Is(CT_BYREF) || ( Len() == 1 && m_str[0] == '&' && IsNot(CT_OPERATOR_VAL)))) { Chunk *prevc = GetPrev(); if ( TestFlags(PCF_IN_TEMPLATE) && ( prevc->Is(CT_COMMA) || prevc->Is(CT_ANGLE_OPEN))) { return(false); } return(true); } return(false); } Chunk *Chunk::GetFirstChunkOnLine() const { Chunk *pc = const_cast(this); Chunk *first = pc; pc = pc->GetPrev(); while ( pc->IsNotNullChunk() && !pc->IsNewline()) { first = pc; pc = pc->GetPrev(); } return(first); } bool Chunk::IsLastChunkOnLine() const { if (this == Chunk::GetTail()) { return(true); } // if the next chunk is a newline then pc is the last chunk on its line if (GetNext()->Is(CT_NEWLINE)) { return(true); } return(false); } void Chunk::SwapLines(Chunk *other) { // to swap lines we need to find the first chunk of the lines Chunk *pc1 = GetFirstChunkOnLine(); Chunk *pc2 = other->GetFirstChunkOnLine(); if ( pc1->IsNullChunk() || pc2->IsNullChunk() || pc1 == pc2) { return; } /* * Example start: * ? - start1 - a1 - b1 - nl1 - ? - ref2 - start2 - a2 - b2 - nl2 - ? * ^- pc1 ^- pc2 */ Chunk *ref2 = pc2->GetPrev(); // Move the line started at pc2 before pc1 while ( pc2->IsNotNullChunk() && !pc2->IsNewline()) { Chunk *tmp = pc2->GetNext(); gChunkList.Remove(pc2); gChunkList.AddBefore(pc2, pc1); pc2 = tmp; } /* * Should now be: * ? - start2 - a2 - b2 - start1 - a1 - b1 - nl1 - ? - ref2 - nl2 - ? * ^- pc1 ^- pc2 */ // Now move the line started at pc1 after ref2 while ( pc1->IsNotNullChunk() && !pc1->IsNewline()) { Chunk *tmp = pc1->GetNext(); gChunkList.Remove(pc1); if (ref2->IsNotNullChunk()) { gChunkList.AddAfter(pc1, ref2); } else { gChunkList.AddHead(pc1); } ref2 = pc1; pc1 = tmp; } /* * Should now be: * ? - start2 - a2 - b2 - nl1 - ? - ref2 - start1 - a1 - b1 - nl2 - ? * ^- pc1 ^- pc2 */ /* * pc1 and pc2 should be the newlines for their lines. * swap the chunks and the new line count so that the spacing remains the same. */ if ( pc1->IsNotNullChunk() && pc2->IsNotNullChunk()) { size_t nlCount = pc1->GetNlCount(); pc1->SetNlCount(pc2->GetNlCount()); pc2->SetNlCount(nlCount); pc1->Swap(pc2); } } // Chunk::SwapLines void Chunk::SetResetFlags(PcfFlags resetBits, PcfFlags setBits) { if (IsNotNullChunk()) { LOG_FUNC_ENTRY(); const PcfFlags newFlags = (m_flags & ~resetBits) | setBits; if (m_flags != newFlags) { LOG_FMT(LSETFLG, "%s(%d): %016llx^%016llx=%016llx\n" "%s(%d): orig line is %zu, orig col is %zu, Text() is '%s', type is %s,", __func__, __LINE__, static_cast(m_flags), static_cast(m_flags ^ newFlags), static_cast(newFlags), __func__, __LINE__, m_origLine, m_origCol, Text(), get_token_name(m_type)); LOG_FMT(LSETFLG, " parent type is %s,\n", get_token_name(m_parentType)); log_func_stack_inline(LSETFLG); LOG_FMT(LSETFLG, " before: "); log_pcf_flags(LSETFLG, m_flags); LOG_FMT(LSETFLG, " after: "); log_pcf_flags(LSETFLG, newFlags); m_flags = newFlags; } } } void Chunk::SetType(const E_Token token) { LOG_FUNC_ENTRY(); if ( IsNullChunk() || m_type == token) { return; } LOG_FMT(LSETTYP, "%s(%d): orig line is %zu, orig col is %zu, Text() is ", __func__, __LINE__, m_origLine, m_origCol); if (token == CT_NEWLINE) { LOG_FMT(LSETTYP, "\n"); } else { LOG_FMT(LSETTYP, "'%s'\n", Text()); } LOG_FMT(LSETTYP, " type is %s, parent type is %s => new type is %s\n", get_token_name(m_type), get_token_name(m_parentType), get_token_name(token)); m_type = token; } void Chunk::SetParentType(const E_Token token) { LOG_FUNC_ENTRY(); if ( IsNullChunk() || m_parentType == token) { return; } LOG_FMT(LSETPAR, "%s(%d): orig line is %zu, orig col is %zu, Text() is ", __func__, __LINE__, m_origLine, m_origCol); if (token == CT_NEWLINE) { LOG_FMT(LSETPAR, "\n"); } else { LOG_FMT(LSETPAR, "'%s'\n", Text()); } LOG_FMT(LSETPAR, " type is %s, parent type is %s => new parent type is %s\n", get_token_name(m_type), get_token_name(m_parentType), get_token_name(token)); m_parentType = token; } Chunk *Chunk::CopyAndAdd(Chunk *pos, const E_Direction dir) const { #ifdef DEBUG // test if this chunk is properly set if (m_ppLevel == 999) { fprintf(stderr, "%s(%d): pp level is not set\n", __func__, __LINE__); log_func_stack_inline(LSETFLG); log_flush(true); exit(EX_SOFTWARE); } if (m_origLine == 0) { fprintf(stderr, "%s(%d): no line number\n", __func__, __LINE__); log_func_stack_inline(LSETFLG); log_flush(true); exit(EX_SOFTWARE); } if (m_origCol == 0) { fprintf(stderr, "%s(%d): no column number\n", __func__, __LINE__); log_func_stack_inline(LSETFLG); log_flush(true); exit(EX_SOFTWARE); } #endif /* DEBUG */ Chunk *pc = new Chunk(*this); if (pos->IsNotNullChunk()) { (dir == E_Direction::FORWARD) ? gChunkList.AddAfter(pc, pos) : gChunkList.AddBefore(pc, pos); } else { (dir == E_Direction::FORWARD) ? gChunkList.AddHead(pc) : gChunkList.AddTail(pc); } chunk_log(pc, "CopyAndAdd(A):"); return(pc); } // Chunk::CopyAndAdd Chunk *Chunk::GetNextNbsb() const { Chunk *pc = const_cast(this); while ( pc->Is(CT_TSQUARE) || pc->Is(CT_SQUARE_OPEN)) { if (pc->Is(CT_SQUARE_OPEN)) { pc = pc->GetClosingParen(); } pc = pc->GetNextNcNnl(); } return(pc); } Chunk *Chunk::GetPrevNbsb() const { Chunk *pc = const_cast(this); while ( pc->Is(CT_TSQUARE) || pc->Is(CT_SQUARE_CLOSE)) { if (pc->Is(CT_SQUARE_CLOSE)) { pc = pc->GetOpeningParen(); } pc = pc->GetPrevNcNnl(); } return(pc); } Chunk *Chunk::GetPpStart() const { if (!IsPreproc()) { return(Chunk::NullChunkPtr); } Chunk *pc = const_cast(this); while (pc->IsNot(CT_PREPROC)) { pc = pc->GetPrev(E_Scope::PREPROC); } return(pc); } Chunk *Chunk::SkipDcMember() const { LOG_FUNC_ENTRY(); Chunk *pc = const_cast(this); Chunk *nxt = pc->Is(CT_DC_MEMBER) ? pc : pc->GetNextNcNnl(E_Scope::ALL); while (nxt->Is(CT_DC_MEMBER)) { pc = nxt->GetNextNcNnl(E_Scope::ALL); if (pc->IsNullChunk()) { return(Chunk::NullChunkPtr); } nxt = pc->GetNextNcNnl(E_Scope::ALL); } return(pc); } int Chunk::ComparePosition(const Chunk *other) const { if (m_origLine < other->m_origLine) { return(-1); } else if (m_origLine == other->m_origLine) { if (m_origCol < other->m_origCol) { return(-1); } else if (m_origCol == other->m_origCol) { return(0); } } return(1); } bool Chunk::IsOCForinOpenParen() const { if ( language_is_set(LANG_OC) && Is(CT_SPAREN_OPEN) && GetPrevNcNnl()->Is(CT_FOR)) { Chunk *nxt = const_cast(this); while ( nxt->IsNotNullChunk() && nxt->IsNot(CT_SPAREN_CLOSE) && nxt->IsNot(CT_IN)) { nxt = nxt->GetNextNcNnl(); } if (nxt->Is(CT_IN)) { return(true); } } return(false); } bool Chunk::IsStringAndLevel(const char *str, const size_t len, bool caseSensitive, const int level) const { return( ( level < 0 || m_level == static_cast(level)) && Len() == len // the length is as expected && ( ( caseSensitive && memcmp(Text(), str, len) == 0) || ( !caseSensitive && strncasecmp(Text(), str, len) == 0))); // the strings are equal } Chunk *Chunk::GetClosingParen(E_Scope scope) const { if ( Is(CT_PAREN_OPEN) || Is(CT_SPAREN_OPEN) || Is(CT_FPAREN_OPEN) || Is(CT_TPAREN_OPEN) || Is(CT_BRACE_OPEN) || Is(CT_VBRACE_OPEN) || Is(CT_ANGLE_OPEN) || Is(CT_SQUARE_OPEN)) { return(GetNextType((E_Token)(m_type + 1), m_level, scope)); } return(const_cast(this)); } Chunk *Chunk::GetOpeningParen(E_Scope scope) const { if ( Is(CT_PAREN_CLOSE) || Is(CT_SPAREN_CLOSE) || Is(CT_FPAREN_CLOSE) || Is(CT_TPAREN_CLOSE) || Is(CT_BRACE_CLOSE) || Is(CT_VBRACE_CLOSE) || Is(CT_ANGLE_CLOSE) || Is(CT_SQUARE_CLOSE)) { return(GetPrevType((E_Token)(m_type - 1), m_level, scope)); } return(const_cast(this)); } bool Chunk::IsCppInheritanceAccessSpecifier() const { return( language_is_set(LANG_CPP) && ( Is(CT_ACCESS) || Is(CT_QUALIFIER)) && ( IsString("private") || IsString("protected") || IsString("public"))); } bool Chunk::IsColon() const { return( Is(CT_ACCESS_COLON) || Is(CT_ASM_COLON) || Is(CT_BIT_COLON) || Is(CT_CASE_COLON) || Is(CT_CLASS_COLON) || Is(CT_COLON) || Is(CT_COND_COLON) || Is(CT_CONSTR_COLON) || Is(CT_CS_SQ_COLON) || Is(CT_D_ARRAY_COLON) || Is(CT_ENUM_COLON) || Is(CT_FOR_COLON) || Is(CT_LABEL_COLON) || Is(CT_OC_COLON) || Is(CT_OC_DICT_COLON) || Is(CT_TAG_COLON) || Is(CT_WHERE_COLON)); } bool Chunk::IsDoxygenComment() const { if (!IsComment()) { return(false); } if (Len() < 3) { return(false); } // check the third character const char *sComment = Text(); return( (sComment[2] == '/') || (sComment[2] == '!') || (sComment[2] == '@')); } bool Chunk::IsTypeDefinition() const { return( Is(CT_TYPE) || Is(CT_PTR_TYPE) || Is(CT_BYREF) || Is(CT_DC_MEMBER) || Is(CT_QUALIFIER) || Is(CT_STRUCT) || Is(CT_ENUM) || Is(CT_UNION)); } bool Chunk::IsNewlineBetween(const Chunk *other) const { Chunk *pc = const_cast(this); while (pc != other) { if (pc->IsNewline()) { return(true); } pc = pc->GetNext(); } return(false); } void shift_the_rest_of_the_line(Chunk *first) { // shift all the tokens in this line to the right Issue #3236 for (Chunk *temp = first; ; temp = temp->GetNext()) { temp->SetColumn(temp->GetColumn() + 1); // Issue #3236 temp->SetOrigCol(temp->GetOrigCol() + 1); // Issue #3236 temp->SetOrigColEnd(temp->GetOrigColEnd() + 1); // Issue #3236 if (temp->Is(CT_NEWLINE)) { break; } } } //shift_the_rest_of_the_line