/**
 * @file brace_cleanup.cpp
 * Determines the brace level and paren level.
 * Inserts virtual braces as needed.
 * Handles all that preprocessor stuff.
 *
 * @author  Ben Gardner
 * @license GPL v2+
 */

#include "brace_cleanup.h"

#include "flag_parens.h"
#include "keywords.h"
#include "lang_pawn.h"
#include "parsing_frame_stack.h"
#include "prototypes.h"

#include <stdexcept>            // to get std::invalid_argument

constexpr static auto LCURRENT = LBC;

using namespace uncrustify;

using std::invalid_argument;
using std::string;
using std::to_string;
using std::stringstream;


struct BraceState
{
   ParsingFrameStack frames;
   E_Token           in_preproc = CT_NONE;
   int               pp_level   = 0;
   bool              consumed   = false;
};

/**
 * Called when a statement was just closed and the pse_tos was just
 * decremented.
 *
 * - if the TOS is now VBRACE, insert a CT_VBRACE_CLOSE and recurse.
 * - if the TOS is a complex statement, call handle_complex_close()
 *
 * @retval true   done with this chunk
 * @retval false  keep processing
 */
static bool close_statement(ParsingFrame &frm, Chunk *pc, const BraceState &braceState);


static size_t preproc_start(BraceState &braceState, ParsingFrame &frm, Chunk *pc);


static void print_stack(log_sev_t logsev, const char *str, const ParsingFrame &frm);


/**
 * pc is a CT_WHILE.
 * Scan backwards to see if we find a brace/vbrace with the parent set to CT_DO
 */
static bool maybe_while_of_do(Chunk *pc);


/**
 * @param after  determines: true  - insert_vbrace_close_after(pc, frm)
 *                           false - insert_vbrace_open_before(pc, frm)
 */
static Chunk *insert_vbrace(Chunk *pc, bool after, const ParsingFrame &frm);

#define insert_vbrace_close_after(pc, frm)    insert_vbrace(pc, true, frm)
#define insert_vbrace_open_before(pc, frm)    insert_vbrace(pc, false, frm)

static void parse_cleanup(BraceState &braceState, ParsingFrame &frm, Chunk *pc);


/**
 * Checks the progression of complex statements.
 * - checks for else after if
 * - checks for if after else
 * - checks for while after do
 * - checks for open brace in BRACE2 and BRACE_DO stages, inserts open VBRACE
 * - checks for open paren in PAREN1 and BRACE2 stages, complains
 *
 * @param frm  The parse frame
 * @param pc   The current chunk
 *
 * @return true - done with this chunk, false - keep processing
 */
static bool check_complex_statements(ParsingFrame &frm, Chunk *pc, const BraceState &braceState);


/**
 * Handles a close paren or brace - just progress the stage, if the end
 * of the statement is hit, call close_statement()
 *
 * @param frm  The parse frame
 * @param pc   The current chunk
 *
 * @return true - done with this chunk, false - keep processing
 */
static bool handle_complex_close(ParsingFrame &frm, Chunk *pc, const BraceState &braceState);


//! We're on a 'namespace' skip the word and then set the parent of the braces.
static void mark_namespace(Chunk *pns);


static size_t preproc_start(BraceState &braceState, ParsingFrame &frm, Chunk *pc)
{
   LOG_FUNC_ENTRY();
   const size_t pp_level = braceState.pp_level;

   Chunk        *next = pc->GetNextNcNnl();

   if (next->IsNullChunk())
   {
      return(pp_level);
   }
   // Get the type of preprocessor and handle it
   braceState.in_preproc = next->GetType();

   // If we are not in a define, check for #if, #else, #endif, etc
   if (braceState.in_preproc != CT_PP_DEFINE)
   {
      int pp_indent = braceState.frames.check(frm, braceState.pp_level, pc);
      return(pp_indent);
   }
   // else push the frame stack
   braceState.frames.push(frm);

   // a preproc body starts a new, blank frame
   frm = ParsingFrame();
   frm.SetParenLevel(1);
   frm.SetBraceLevel(1);

   // TODO: not sure about the next 3 lines
   frm.push(Chunk::NullChunkPtr, __func__, __LINE__);
   frm.top().SetOpenToken(CT_PP_DEFINE);

   return(pp_level);
}


static void print_stack(log_sev_t logsev, const char *str,
                        const ParsingFrame &frm)
{
   LOG_FUNC_ENTRY();

   if (!log_sev_on(logsev))
   {
      return;
   }
   log_fmt(logsev, "%s(%d): str is '%s'", __func__, __LINE__, str);

   for (size_t idx = 1; idx < frm.size(); idx++)
   {
      if (frm.at(idx).GetStage() != E_BraceStage::NONE)
      {
         LOG_FMT(logsev, " [%s - %u]", get_token_name(frm.at(idx).GetOpenToken()),
                 (unsigned int)frm.at(idx).GetStage());
      }
      else
      {
         LOG_FMT(logsev, " [%s]", get_token_name(frm.at(idx).GetOpenToken()));
      }
   }

   log_fmt(logsev, "\n");
}


//TODO: This can be cleaned up and simplified - we can look both forward and backward!
void brace_cleanup()
{
   LOG_FUNC_ENTRY();

   BraceState   braceState;
   ParsingFrame frm;
   Chunk        *pc = Chunk::GetHead();

   while (pc->IsNotNullChunk())
   {
      LOG_CHUNK(LTOK, pc);

      // Check for leaving a #define body
      if (  braceState.in_preproc != CT_NONE
         && !pc->TestFlags(PCF_IN_PREPROC))
      {
         if (braceState.in_preproc == CT_PP_DEFINE)
         {
            // out of the #define body, restore the frame
            size_t brace_level = frm.GetBraceLevel();

            if (  options::pp_warn_unbalanced_if()
               && brace_level != 1)
            {
               LOG_FMT(LWARN, "%s(%d): orig line is %zu, unbalanced #define block braces, out-level is %zu\n",
                       __func__, __LINE__, pc->GetOrigLine(), brace_level);
            }
            braceState.frames.pop(frm);
         }
         braceState.in_preproc = CT_NONE;
      }
      // Check for a preprocessor start
      size_t pp_level;

      if (pc->Is(CT_PREPROC))
      {
         pp_level = preproc_start(braceState, frm, pc);
      }
      else
      {
         pp_level = braceState.pp_level;
      }
      LOG_FMT(LTOK, "%s(%d): pp level is %zu\n",
              __func__, __LINE__, pp_level);

      // Do before assigning stuff from the frame
      if (  language_is_set(LANG_PAWN)
         && frm.top().GetOpenToken() == CT_VBRACE_OPEN
         && pc->Is(CT_NEWLINE))
      {
         pc = pawn_check_vsemicolon(pc);

         if (pc->IsNullChunk())
         {
            return;
         }
      }

      // Issue #1813
      if (pc->Is(CT_NAMESPACE))
      {
         mark_namespace(pc);
      }
      // Assume the level won't change
      pc->SetLevel(frm.GetParenLevel());
      pc->SetBraceLevel(frm.GetBraceLevel());
      pc->SetPpLevel(pp_level);

      /*
       * #define bodies get the full formatting treatment
       * Also need to pass in the initial '#' to close out any virtual braces.
       */
      if (  !pc->IsCommentOrNewline()
         && !pc->Is(CT_ATTRIBUTE)
         && !pc->Is(CT_IGNORED)            // Issue #2279
         && (  braceState.in_preproc == CT_PP_DEFINE
            || braceState.in_preproc == CT_NONE))
      {
         braceState.consumed = false;
         parse_cleanup(braceState, frm, pc);
         print_stack(LBCSAFTER, (pc->Is(CT_VBRACE_CLOSE)) ? "Virt-}\n" : pc->GetStr().c_str(), frm);
      }
      pc = pc->GetNext();
   }
} // brace_cleanup


static bool maybe_while_of_do(Chunk *pc)
{
   LOG_FUNC_ENTRY();

   Chunk *prev = pc->GetPrevNcNnl();

   if (  prev->IsNullChunk()
      || !prev->TestFlags(PCF_IN_PREPROC))
   {
      return(false);
   }

   // Find the chunk before the preprocessor
   while (  prev->IsNullChunk()
         && prev->TestFlags(PCF_IN_PREPROC))
   {
      prev = prev->GetPrevNcNnl();
   }

   if (  (  prev->Is(CT_VBRACE_CLOSE)
         || prev->Is(CT_BRACE_CLOSE))
      && prev->GetParentType() == CT_DO)
   {
      return(true);
   }
   return(false);
}


/**
 * At the heart of this algorithm are two stacks.
 * There is the Paren Stack (PS) and the Frame stack.
 *
 * The PS (m_parenStack in the code) keeps track of braces, parens,
 * if/else/switch/do/while/etc items -- anything that is nestable.
 * Complex statements go through stages.
 * Take this simple if statement as an example:
 *   if ( x ) { x--; }
 *
 * The stack would change like so: 'token' stack afterwards
 * 'if' [IF - 1]
 * '('  [IF - 1] [PAREN OPEN]
 * 'x'  [IF - 1] [PAREN OPEN]
 * ')'  [IF - 2]       <- note that the state was incremented
 * '{'  [IF - 2] [BRACE OPEN]
 * 'x'  [IF - 2] [BRACE OPEN]
 * '--' [IF - 2] [BRACE OPEN]
 * ';'  [IF - 2] [BRACE OPEN]
 * '}'  [IF - 3]
 *                             <- lack of else kills the IF, closes statement
 *
 * Virtual braces example:
 *   if ( x ) x--; else x++;
 *
 * 'if'   [IF - 1]
 * '('    [IF - 1] [PAREN OPEN]
 * 'x'    [IF - 1] [PAREN OPEN]
 * ')'    [IF - 2]
 * 'x'    [IF - 2] [VBRACE OPEN]   <- VBrace open inserted before because '{' was not next
 * '--'   [IF - 2] [VBRACE OPEN]
 * ';'    [IF - 3]                 <- VBrace close inserted after semicolon
 * 'else' [ELSE - 0]               <- IF changed into ELSE
 * 'x'    [ELSE - 0] [VBRACE OPEN] <- lack of '{' -> VBrace
 * '++'   [ELSE - 0] [VBRACE OPEN]
 * ';'    [ELSE - 0]               <- VBrace close inserted after semicolon
 *                                 <- ELSE removed after statement close
 *
 * The m_parenStack stack is kept on a frame stack.
 * The frame stack is need for languages that support preprocessors (C, C++, C#)
 * that can arbitrarily change code flow. It also isolates #define macros so
 * that they are indented independently and do not affect the rest of the program.
 *
 * When an #if is hit, a copy of the current frame is push on the frame stack.
 * When an #else/#elif is hit, a copy of the current stack is pushed under the
 * #if frame and the original (pre-#if) frame is copied to the current frame.
 * When #endif is hit, the top frame is popped.
 * This has the following effects:
 *  - a simple #if / #endif does not affect program flow
 *  - #if / #else /#endif - continues from the #if clause
 *
 * When a #define is entered, the current frame is pushed and cleared.
 * When a #define is exited, the frame is popped.
 */
static void parse_cleanup(BraceState &braceState, ParsingFrame &frm, Chunk *pc)
{
   LOG_FUNC_ENTRY();

   LOG_FMT(LTOK, "%s(%d): orig line is %zu, orig col is %zu, type is %s, tos is %zu, TOS.type is %s, TOS.stage is %s, ",
           __func__, __LINE__, pc->GetOrigLine(), pc->GetOrigCol(), get_token_name(pc->GetType()),
           frm.size() - 1, get_token_name(frm.top().GetOpenToken()),
           get_brace_stage_name(frm.top().GetStage()));
   log_pcf_flags(LTOK, pc->GetFlags());

   // Mark statement starts
   LOG_FMT(LTOK, "%s(%d): orig line is %zu, type is %s, Text() is '%s'\n",
           __func__, __LINE__, pc->GetOrigLine(), get_token_name(pc->GetType()), pc->Text());
   LOG_FMT(LTOK, "%s(%d): frame statement count is %zu, expression count is %zu\n",
           __func__, __LINE__, frm.GetStmtCount(), frm.GetExprCount());

   if (  (  frm.GetStmtCount() == 0
         || frm.GetExprCount() == 0)
      && !pc->IsSemicolon()
      && pc->IsNot(CT_BRACE_CLOSE)
      && pc->IsNot(CT_VBRACE_CLOSE)
      && !pc->IsString(")")
      && !pc->IsString("]"))
   {
      pc->SetFlagBits(PCF_EXPR_START | ((frm.GetStmtCount() == 0) ? PCF_STMT_START : PCF_NONE));
      LOG_FMT(LSTMT, "%s(%d): orig line is %zu, 1.marked '%s' as %s, start statement count is %zu, expression count is %zu\n",
              __func__, __LINE__, pc->GetOrigLine(), pc->Text(),
              pc->TestFlags(PCF_STMT_START) ? "statement" : "expression", frm.GetStmtCount(),
              frm.GetExprCount());
      pc->TestFlags(PCF_STMT_START) ? log_ruleStart("start statement", pc) : log_ruleStart("start expression", pc);
   }
   frm.SetStmtCount(frm.GetStmtCount() + 1);
   frm.SetExprCount(frm.GetExprCount() + 1);
   LOG_FMT(LTOK, "%s(%d): frame statement count is %zu, expression count is %zu\n",
           __func__, __LINE__, frm.GetStmtCount(), frm.GetExprCount());

   if (frm.GetSParenCount() > 0)
   {
      pc->SetFlagBits(PCF_IN_SPAREN);

      // Mark everything in the for statement
      for (int tmp = static_cast<int>(frm.size()) - 2; tmp >= 0; tmp--)
      {
         if (frm.at(tmp).GetOpenToken() == CT_FOR)
         {
            pc->SetFlagBits(PCF_IN_FOR);
            break;
         }
      }

      // Mark the parent on semicolons in for() statements
      if (  pc->Is(CT_SEMICOLON)
         && frm.size() > 2
         && frm.prev().GetOpenToken() == CT_FOR)
      {
         pc->SetParentType(CT_FOR);
      }
   }

   // Check the progression of complex statements
   if (  frm.top().GetStage() != E_BraceStage::NONE
      && !pc->Is(CT_AUTORELEASEPOOL)
      && check_complex_statements(frm, pc, braceState))
   {
      return;
   }

   /*
    * Check for a virtual brace statement close due to a semicolon.
    * The virtual brace will get handled the next time through.
    * The semicolon isn't handled at all.
    * TODO: may need to float VBRACE past comments until newline?
    */
   if (frm.top().GetOpenToken() == CT_VBRACE_OPEN)
   {
      if (pc->IsSemicolon())
      {
         braceState.consumed = true;
         close_statement(frm, pc, braceState);
      }
      else if (  language_is_set(LANG_PAWN)
              && pc->Is(CT_BRACE_CLOSE))
      {
         close_statement(frm, pc, braceState);
      }
      else if (  language_is_set(LANG_D)
              && pc->Is(CT_BRACE_CLOSE))
      {
         close_statement(frm, pc, braceState);
      }
   }

   // Handle close parenthesis, vbrace, brace, and square
   if (  pc->Is(CT_PAREN_CLOSE)
      || pc->Is(CT_BRACE_CLOSE)
      || pc->Is(CT_VBRACE_CLOSE)
      || pc->Is(CT_ANGLE_CLOSE)
      || pc->Is(CT_MACRO_CLOSE)
      || pc->Is(CT_SQUARE_CLOSE))
   {
      // Change CT_PAREN_CLOSE into CT_SPAREN_CLOSE or CT_FPAREN_CLOSE
      if (  pc->Is(CT_PAREN_CLOSE)
         && (  (frm.top().GetOpenToken() == CT_FPAREN_OPEN)
            || (frm.top().GetOpenToken() == CT_SPAREN_OPEN)))
      {
         // TODO: fix enum hack
         pc->SetType(static_cast<E_Token>(frm.top().GetOpenToken() + 1));

         if (pc->Is(CT_SPAREN_CLOSE))
         {
            frm.SetSParenCount(frm.GetSParenCount() - 1);
            pc->ResetFlagBits(PCF_IN_SPAREN);
         }
      }

      // Make sure the open / close match
      if (pc->IsNot((E_Token)(frm.top().GetOpenToken() + 1)))
      {
         if (pc->TestFlags(PCF_IN_PREPROC))                // Issue #3113, #3283
         {
            // do nothing
         }
         else
         {
            LOG_FMT(LWARN, "%s(%d): pc orig line is %zu, orig col is %zu, Text() is '%s', type is %s\n",
                    __func__, __LINE__, pc->GetOrigLine(), pc->GetOrigCol(), pc->Text(), get_token_name(pc->GetType()));
            const ParenStackEntry &AA = frm.top();                // Issue #3055

            if (AA.GetOpenToken() != CT_EOF)
            {
               LOG_FMT(LWARN, "%s(%d): (frm.top().type + 1) is %s\n",
                       __func__, __LINE__, get_token_name((E_Token)(frm.top().GetOpenToken() + 1)));
            }

            if (  frm.top().GetOpenToken() != CT_EOF
               && frm.top().GetOpenToken() != CT_PP_DEFINE)
            {
               LOG_FMT(LWARN, "%s(%d): File: %s, orig line is %zu, orig col is %zu, Error: Unexpected '%s' for '%s', which was on line %zu\n",
                       __func__, __LINE__, cpd.filename.c_str(), pc->GetOrigLine(), pc->GetOrigCol(),
                       pc->Text(), get_token_name(frm.top().GetOpenChunk()->GetType()),
                       frm.top().GetOpenChunk()->GetOrigLine());
               print_stack(LBCSPOP, "=Error  ", frm);
               exit(EXIT_FAILURE);
            }
         }
      }
      else
      {
         braceState.consumed = true;

         // Copy the parent, update the parenthesis/brace levels
         pc->SetParentType(frm.top().GetParent());
         frm.SetParenLevel(frm.GetParenLevel() - 1);

         if (  pc->Is(CT_BRACE_CLOSE)
            || pc->Is(CT_VBRACE_CLOSE)
            || pc->Is(CT_MACRO_CLOSE))
         {
            frm.SetBraceLevel(frm.GetBraceLevel() - 1);
            LOG_FMT(LBCSPOP, "%s(%d): frame brace level decreased to %zu",
                    __func__, __LINE__, frm.GetBraceLevel());
            log_pcf_flags(LBCSPOP, pc->GetFlags());
         }
         pc->SetLevel(frm.GetParenLevel());
         pc->SetBraceLevel(frm.GetBraceLevel());

         // Pop the entry
         LOG_FMT(LBCSPOP, "%s(%d): pc orig line is %zu, orig col is %zu, Text() is '%s', type is %s\n",
                 __func__, __LINE__, pc->GetOrigLine(), pc->GetOrigCol(), pc->Text(), get_token_name(pc->GetType()));
         frm.pop(__func__, __LINE__, pc);
         print_stack(LBCSPOP, "-Close  ", frm);

         if (  frm.top().GetStage() == E_BraceStage::NONE
            && (  pc->Is(CT_VBRACE_CLOSE)
               || pc->Is(CT_BRACE_CLOSE)
               || pc->Is(CT_SEMICOLON))
            && frm.top().GetOpenChunk()->Is(CT_VBRACE_OPEN))
         {
            // frames for functions are not created as they are for an if
            // this here is a hackish solution to close a vbrace of a block that
            // contains the function
            frm.push(Chunk::NullChunkPtr, __func__, __LINE__);                                     // <- dummy frame for the function
            frm.top().SetStage(E_BraceStage::BRACE2);
         }

         // See if we are in a complex statement
         if (frm.top().GetStage() != E_BraceStage::NONE)
         {
            handle_complex_close(frm, pc, braceState);
         }
      }
   }

   /*
    * In this state, we expect a semicolon, but we'll also hit the closing
    * sparen, so we need to check braceState.consumed to see if the close sparen
    * was already handled.
    */
   if (frm.top().GetStage() == E_BraceStage::WOD_SEMI)
   {
      if (braceState.consumed)
      {
         /*
          * If consumed, then we are on the close sparen.
          * PAWN: Check the next chunk for a semicolon. If it isn't, then
          * add a virtual semicolon, which will get handled on the next pass.
          */
         if (language_is_set(LANG_PAWN))
         {
            Chunk *tmp = pc->GetNextNcNnl();

            if (!tmp->IsSemicolon())
            {
               pawn_add_vsemi_after(pc);
            }
         }
      }
      else
      {
         // Complain if this ISN'T a semicolon, but close out WHILE_OF_DO anyway
         if (pc->IsSemicolon())
         {
            braceState.consumed = true;
            pc->SetParentType(CT_WHILE_OF_DO);
         }
         else
         {
            LOG_FMT(LWARN, "%s: %s(%d): %zu: Error: Expected a semicolon for WHILE_OF_DO, but got '%s'\n",
                    cpd.filename.c_str(), __func__, __LINE__, pc->GetOrigLine(),
                    get_token_name(pc->GetType()));
            exit(EX_SOFTWARE);
         }
         handle_complex_close(frm, pc, braceState);
      }
   }
   // Get the parent type for brace and parenthesis open
   E_Token parentType = pc->GetParentType();

   if (  pc->Is(CT_PAREN_OPEN)
      || pc->Is(CT_FPAREN_OPEN)
      || pc->Is(CT_SPAREN_OPEN)
      || pc->Is(CT_BRACE_OPEN))
   {
      Chunk *prev = pc->GetPrevNcNnl();

      if (prev->IsNotNullChunk())
      {
         if (  pc->Is(CT_PAREN_OPEN)
            || pc->Is(CT_FPAREN_OPEN)
            || pc->Is(CT_SPAREN_OPEN))
         {
            // Set the parent for parenthesis and change parenthesis type
            if (  prev->Is(CT_IF)
               || prev->Is(CT_CONSTEXPR)
               || prev->Is(CT_ELSEIF)
               || prev->Is(CT_WHILE)
               || prev->Is(CT_WHILE_OF_DO)
               || prev->Is(CT_DO)
               || prev->Is(CT_FOR)
               || prev->Is(CT_SWITCH)
               || prev->Is(CT_CATCH)
               || prev->Is(CT_SYNCHRONIZED)
               || prev->Is(CT_D_VERSION)
               || prev->Is(CT_D_VERSION_IF)
               || prev->Is(CT_D_SCOPE)
               || prev->Is(CT_D_SCOPE_IF))
            {
               pc->SetType(CT_SPAREN_OPEN);
               parentType = frm.top().GetOpenToken();
               frm.SetSParenCount(frm.GetSParenCount() + 1);
            }
            else if (prev->Is(CT_FUNCTION))
            {
               pc->SetType(CT_FPAREN_OPEN);
               parentType = CT_FUNCTION;
            }
            // NS_ENUM and NS_OPTIONS are followed by a (type, name) pair
            else if (  prev->Is(CT_ENUM)
                    && language_is_set(LANG_OC))
            {
               // Treat both as CT_ENUM since the syntax is identical
               pc->SetType(CT_FPAREN_OPEN);
               parentType = CT_ENUM;
            }
            else if (prev->Is(CT_DECLSPEC))  // Issue 1289
            {
               parentType = CT_DECLSPEC;
            }
            // else: no need to set parent
         }
         else  // must be CT_BRACE_OPEN
         {
            // Set the parent for open braces
            if (frm.top().GetStage() != E_BraceStage::NONE)
            {
               parentType = frm.top().GetOpenToken();
            }
            else if (  prev->Is(CT_ASSIGN)
                    && (prev->GetStr()[0] == '='))
            {
               parentType = CT_ASSIGN;
            }
            else if (  prev->Is(CT_RETURN)
                    && language_is_set(LANG_CPP))
            {
               parentType = CT_RETURN;
            }
            // Carry through CT_ENUM parent in NS_ENUM (type, name) {
            // only to help the vim command }
            else if (  prev->Is(CT_FPAREN_CLOSE)
                    && language_is_set(LANG_OC)
                    && prev->GetParentType() == CT_ENUM)
            {
               parentType = CT_ENUM;
            }
            else if (prev->Is(CT_FPAREN_CLOSE))
            {
               parentType = CT_FUNCTION;
            }
            // else: no need to set parent
         }
      }
   }

   /*
    * Adjust the level for opens & create a stack entry
    * Note that CT_VBRACE_OPEN has already been handled.
    */
   if (  pc->Is(CT_BRACE_OPEN)
      || pc->Is(CT_PAREN_OPEN)
      || pc->Is(CT_FPAREN_OPEN)
      || pc->Is(CT_SPAREN_OPEN)
      || pc->Is(CT_ANGLE_OPEN)
      || pc->Is(CT_MACRO_OPEN)
      || pc->Is(CT_SQUARE_OPEN))
   {
      frm.SetParenLevel(frm.GetParenLevel() + 1);

      if (  pc->Is(CT_BRACE_OPEN)
         || pc->Is(CT_MACRO_OPEN))
      {
         // Issue #1813
         bool single = false;

         if (pc->GetParentType() == CT_NAMESPACE)
         {
            LOG_FMT(LBCSPOP, "%s(%d): parent type is NAMESPACE\n",
                    __func__, __LINE__);
            Chunk *tmp = frm.top().GetOpenChunk();

            if (tmp->GetParentType() == CT_NAMESPACE)
            {
               LOG_FMT(LBCSPOP, "%s(%d): tmp->GetParentType() is NAMESPACE\n",
                       __func__, __LINE__);

               log_rule_B("indent_namespace");
               log_rule_B("indent_namespace_single_indent");

               if (  options::indent_namespace()
                  && options::indent_namespace_single_indent())
               {
                  LOG_FMT(LBCSPOP, "%s(%d): Options are SINGLE\n",
                          __func__, __LINE__);
                  single = true;
               }
            }
         }
         LOG_FMT(LBCSPOP, "%s(%d): pc orig line is %zu, orig col is %zu, Text() is '%s', type is %s, parent type is %s\n",
                 __func__, __LINE__, pc->GetOrigLine(), pc->GetOrigCol(), pc->Text(), get_token_name(pc->GetType()), get_token_name(pc->GetParentType()));

         if (!single)
         {
            frm.SetBraceLevel(frm.GetBraceLevel() + 1);
            LOG_FMT(LBCSPOP, "%s(%d): frame brace level increased to %zu\n",
                    __func__, __LINE__, frm.GetBraceLevel());
         }
      }
      frm.push(pc, __func__, __LINE__);
      frm.top().SetParent(parentType);
      pc->SetParentType(parentType);
   }
   // Issue #2281

   if (  pc->Is(CT_BRACE_OPEN)
      && pc->GetParentType() == CT_SWITCH)
   {
      size_t idx = frm.size();
      LOG_FMT(LBCSPOP, "%s(%d): idx is %zu\n",
              __func__, __LINE__, idx);
      Chunk *saved = frm.at(idx - 2).GetOpenChunk();

      if (saved->IsNotNullChunk())
      {
         // set parent member
         pc->SetParent(saved);
      }
   }

   if (  pc->Is(CT_CASE)
      || pc->Is(CT_DEFAULT))
   {
      Chunk *prev = pc->GetPrevNcNnl();         // Issue #3176

      if (  pc->Is(CT_CASE)
         || (  pc->Is(CT_DEFAULT)
            && prev->IsNot(CT_ASSIGN)))
      {
         // it is a CT_DEFAULT from a switch
         LOG_FMT(LBCSPOP, "%s(%d): pc orig line is %zu, orig col is %zu\n",
                 __func__, __LINE__, pc->GetOrigLine(), pc->GetOrigCol());
         pc->SetParentType(CT_SWITCH);
         size_t idx = frm.size();
         LOG_FMT(LBCSPOP, "%s(%d): idx is %zu\n",
                 __func__, __LINE__, idx);
         Chunk *saved = frm.at(idx - 2).GetOpenChunk();

         if (saved->IsNotNullChunk())
         {
            // set parent member
            pc->SetParent(saved);
         }
      }
   }

   if (pc->Is(CT_BREAK))
   {
      LOG_FMT(LBCSPOP, "%s(%d): pc orig line is %zu, orig col is %zu\n",
              __func__, __LINE__, pc->GetOrigLine(), pc->GetOrigCol());
      size_t idx = frm.size();
      LOG_FMT(LBCSPOP, "%s(%d): idx is %zu\n",
              __func__, __LINE__, idx);
      Chunk *saved = frm.at(idx - 2).GetOpenChunk();

      if (saved->IsNotNullChunk())
      {
         // set parent member
         pc->SetParent(saved);
      }
   }
   const pattern_class_e patcls = get_token_pattern_class(pc->GetType());

   /*
    * Create a stack entry for complex statements:
    * if, elseif, switch, for, while, synchronized, using, lock, with,
    * version, CT_D_SCOPE_IF
    */
   if (patcls == pattern_class_e::BRACED)
   {
      frm.push(pc, __func__, __LINE__, (pc->Is(CT_DO) ? E_BraceStage::BRACE_DO
                    : E_BraceStage::BRACE2));
      // "+ComplexBraced"
   }
   else if (patcls == pattern_class_e::PBRACED)
   {
      E_BraceStage bs = E_BraceStage::PAREN1;

      if (  pc->Is(CT_WHILE)
         && maybe_while_of_do(pc))
      {
         pc->SetType(CT_WHILE_OF_DO);
         bs = E_BraceStage::WOD_PAREN;
      }
      frm.push(pc, __func__, __LINE__, bs);
      // "+ComplexParenBraced"
   }
   else if (patcls == pattern_class_e::OPBRACED)
   {
      frm.push(pc, __func__, __LINE__, E_BraceStage::OP_PAREN1);
      // "+ComplexOpParenBraced");
   }
   else if (patcls == pattern_class_e::ELSE)
   {
      frm.push(pc, __func__, __LINE__, E_BraceStage::ELSEIF);
      // "+ComplexElse");
   }

   /*
    * Mark simple statement/expression starts
    *  - after { or }
    *  - after ';', but not if the paren stack top is a paren
    *  - after '(' that has a parent type of CT_FOR
    */
   if (  pc->Is(CT_SQUARE_OPEN)
      || (  pc->Is(CT_BRACE_OPEN)
         && pc->GetParentType() != CT_ASSIGN)
      || pc->Is(CT_BRACE_CLOSE)
      || pc->Is(CT_VBRACE_CLOSE)
      || (  pc->Is(CT_SPAREN_OPEN)
         && pc->GetParentType() == CT_FOR)
      || pc->Is(CT_COLON)
      || pc->Is(CT_OC_END)
      || (  pc->IsSemicolon()
         && frm.top().GetOpenToken() != CT_PAREN_OPEN
         && frm.top().GetOpenToken() != CT_FPAREN_OPEN
         && frm.top().GetOpenToken() != CT_SPAREN_OPEN)
      || pc->Is(CT_MACRO))                         // Issue #2742
   {
      LOG_FMT(LSTMT, "%s(%d): orig line is %zu, reset1 stmt on '%s'\n",
              __func__, __LINE__, pc->GetOrigLine(), pc->Text());
      frm.SetStmtCount(0);
      frm.SetExprCount(0);
      LOG_FMT(LTOK, "%s(%d): frame statement count is %zu, expression count is %zu\n",
              __func__, __LINE__, frm.GetStmtCount(), frm.GetExprCount());
   }
   // Mark expression starts
   LOG_FMT(LSTMT, "%s(%d): Mark expression starts: orig line is %zu, orig col is %zu, Text() is '%s'\n",
           __func__, __LINE__, pc->GetOrigLine(), pc->GetOrigCol(), pc->Text());
   Chunk *tmp = pc->GetNextNcNnl();

   if (  pc->Is(CT_ARITH)
      || pc->Is(CT_SHIFT)
      || pc->Is(CT_ASSIGN)
      || pc->Is(CT_CASE)
      || pc->Is(CT_COMPARE)
      || (  pc->Is(CT_STAR)
         && tmp->IsNot(CT_STAR))
      || pc->Is(CT_BOOL)
      || pc->Is(CT_MINUS)
      || pc->Is(CT_PLUS)
      || pc->Is(CT_CARET)
      || pc->Is(CT_ANGLE_OPEN)
      || pc->Is(CT_ANGLE_CLOSE)
      || pc->Is(CT_RETURN)
      || pc->Is(CT_THROW)
      || pc->Is(CT_GOTO)
      || pc->Is(CT_CONTINUE)
      || pc->Is(CT_PAREN_OPEN)
      || pc->Is(CT_FPAREN_OPEN)
      || pc->Is(CT_SPAREN_OPEN)
      || pc->Is(CT_BRACE_OPEN)
      || pc->IsSemicolon()
      || pc->Is(CT_COMMA)
      || pc->Is(CT_NOT)
      || pc->Is(CT_INV)
      || pc->Is(CT_COLON)
      || pc->Is(CT_QUESTION))
   {
      frm.SetExprCount(0);
      LOG_FMT(LSTMT, "%s(%d): orig line is %zu, orig col is %zu, reset expr on '%s'\n",
              __func__, __LINE__, pc->GetOrigLine(), pc->GetOrigCol(), pc->Text());
   }
} // parse_cleanup


static bool check_complex_statements(ParsingFrame &frm, Chunk *pc, const BraceState &braceState)
{
   LOG_FUNC_ENTRY();

   E_BraceStage atest = frm.top().GetStage();

   LOG_FMT(LBCSPOP, "%s(%d): atest is %s\n",
           __func__, __LINE__, get_brace_stage_name(atest));

   // Turn an optional parenthesis into either a real parenthesis or a brace
   if (frm.top().GetStage() == E_BraceStage::OP_PAREN1)
   {
      frm.top().SetStage(pc->IsNot(CT_PAREN_OPEN)
                        ? E_BraceStage::BRACE2
                        : E_BraceStage::PAREN1);
      LOG_FMT(LBCSPOP, "%s(%d): frm.top().stage is now %s\n",
              __func__, __LINE__, get_brace_stage_name(frm.top().GetStage()));
   }

   // Check for CT_ELSE after CT_IF
   while (frm.top().GetStage() == E_BraceStage::ELSE)
   {
      if (pc->Is(CT_ELSE))
      {
         // Replace CT_IF with CT_ELSE on the stack & we are done
         frm.top().SetOpenToken(CT_ELSE);
         frm.top().SetStage(E_BraceStage::ELSEIF);
         print_stack(LBCSSWAP, "=Swap   ", frm);

         return(true);
      }
      // Remove the CT_IF and close the statement
      LOG_FMT(LBCSPOP, "%s(%d): pc orig line is %zu, orig col is %zu, Text() is '%s', type is %s\n",
              __func__, __LINE__, pc->GetOrigLine(), pc->GetOrigCol(), pc->Text(), get_token_name(pc->GetType()));
      frm.pop(__func__, __LINE__, pc);
      print_stack(LBCSPOP, "-IF-CCS ", frm);

      if (close_statement(frm, pc, braceState))
      {
         return(true);
      }
   }

   // Check for CT_IF after CT_ELSE
   if (frm.top().GetStage() == E_BraceStage::ELSEIF)
   {
      log_rule_B("indent_else_if");

      if (  pc->Is(CT_IF)
         && (  !options::indent_else_if()
            || !pc->GetPrevNc()->IsNewline()))
      {
         // Replace CT_ELSE with CT_IF
         pc->SetType(CT_ELSEIF);
         frm.top().SetOpenToken(CT_ELSEIF);
         frm.top().SetStage(E_BraceStage::PAREN1);
         return(true);
      }
      // Jump to the 'expecting brace' stage
      frm.top().SetStage(E_BraceStage::BRACE2);
   }

   // Check for CT_CATCH or CT_FINALLY after CT_TRY or CT_CATCH
   while (frm.top().GetStage() == E_BraceStage::CATCH)
   {
      if (  pc->Is(CT_CATCH)
         || pc->Is(CT_FINALLY))
      {
         // Replace CT_TRY with CT_CATCH or CT_FINALLY on the stack & we are done
         frm.top().SetOpenToken(pc->GetType());

         if (language_is_set(LANG_CS))
         {
            frm.top().SetStage((pc->Is(CT_CATCH)) ? E_BraceStage::CATCH_WHEN : E_BraceStage::BRACE2);
         }
         else
         {
            // historically this used OP_PAREN1; however, to my knowledge the expression after a catch clause
            // is only optional for C# which has been handled above; therefore, this should now always expect
            // a parenthetical expression after the catch keyword and brace after the finally keyword
            frm.top().SetStage((pc->Is(CT_CATCH)) ? E_BraceStage::PAREN1 : E_BraceStage::BRACE2);
         }
         print_stack(LBCSSWAP, "=Swap   ", frm);

         return(true);
      }
      // Remove the CT_TRY and close the statement
      LOG_FMT(LBCSPOP, "%s(%d): pc orig line is %zu, orig col is %zu, Text() is '%s', type is %s\n",
              __func__, __LINE__, pc->GetOrigLine(), pc->GetOrigCol(), pc->Text(), get_token_name(pc->GetType()));
      frm.pop(__func__, __LINE__, pc);
      print_stack(LBCSPOP, "-TRY-CCS ", frm);

      if (close_statement(frm, pc, braceState))
      {
         return(true);
      }
   }

   // Check for optional parenthesis and optional CT_WHEN after CT_CATCH
   if (frm.top().GetStage() == E_BraceStage::CATCH_WHEN)
   {
      if (pc->Is(CT_PAREN_OPEN)) // this is for the paren after "catch"
      {
         // Replace CT_PAREN_OPEN with CT_SPAREN_OPEN
         pc->SetType(CT_SPAREN_OPEN);
         frm.top().SetOpenToken(pc->GetType());
         frm.top().SetStage(E_BraceStage::PAREN1);

         return(false);
      }

      if (pc->Is(CT_WHEN))
      {
         frm.top().SetOpenToken(pc->GetType());
         frm.top().SetStage(E_BraceStage::OP_PAREN1);

         return(true);
      }

      if (pc->Is(CT_BRACE_OPEN))
      {
         frm.top().SetStage(E_BraceStage::BRACE2);

         return(false);
      }
   }

   // Check for CT_WHILE after the CT_DO
   if (frm.top().GetStage() == E_BraceStage::WHILE)
   {
      if (pc->Is(CT_WHILE))
      {
         pc->SetType(CT_WHILE_OF_DO);
         frm.top().SetOpenToken(CT_WHILE_OF_DO);
         frm.top().SetStage(E_BraceStage::WOD_PAREN);

         return(true);
      }
      LOG_FMT(LWARN, "%s(%d): %s, orig line is %zu, Error: Expected 'while', got '%s'\n",
              __func__, __LINE__, cpd.filename.c_str(), pc->GetOrigLine(),
              pc->Text());
      LOG_FMT(LBCSPOP, "%s(%d): pc orig line is %zu, orig col is %zu, Text() is '%s', type is %s\n",
              __func__, __LINE__, pc->GetOrigLine(), pc->GetOrigCol(), pc->Text(), get_token_name(pc->GetType()));
      frm.pop(__func__, __LINE__, pc);
      print_stack(LBCSPOP, "-Error  ", frm);
      exit(EX_SOFTWARE);
   }
   // Insert a CT_VBRACE_OPEN, if needed
   // but not in a preprocessor
   atest = frm.top().GetStage();

   if (  pc->IsNot(CT_BRACE_OPEN)
      && !pc->TestFlags(PCF_IN_PREPROC)
      && (  (frm.top().GetStage() == E_BraceStage::BRACE2)
         || (frm.top().GetStage() == E_BraceStage::BRACE_DO)))
   {
      log_rule_B("indent_using_block");

      if (  language_is_set(LANG_CS)
         && pc->Is(CT_USING_STMT)
         && (!options::indent_using_block()))
      {
         // don't indent the using block
      }
      else
      {
         const E_Token parentType = frm.top().GetOpenToken();

         Chunk         *vbrace = insert_vbrace_open_before(pc, frm);
         vbrace->SetParentType(parentType);

         frm.SetParenLevel(frm.GetParenLevel() + 1);
         frm.SetBraceLevel(frm.GetBraceLevel() + 1);
         LOG_FMT(LBCSPOP, "%s(%d): frame brace level increased to %zu\n",
                 __func__, __LINE__, frm.GetBraceLevel());
         log_pcf_flags(LBCSPOP, pc->GetFlags());

         frm.push(vbrace, __func__, __LINE__, E_BraceStage::NONE);
         // "+VBrace");

         frm.top().SetParent(parentType);

         // update the level of pc
         pc->SetLevel(frm.GetParenLevel());
         pc->SetBraceLevel(frm.GetBraceLevel());

         // Mark as a start of a statement
         frm.SetStmtCount(0);
         frm.SetExprCount(0);
         LOG_FMT(LTOK, "%s(%d): frame statement count is %zu, expression count is %zu\n",
                 __func__, __LINE__, frm.GetStmtCount(), frm.GetExprCount());
         pc->SetFlagBits(PCF_STMT_START | PCF_EXPR_START);
         frm.SetStmtCount(1);
         frm.SetExprCount(1);
         LOG_FMT(LSTMT, "%s(%d): orig line is %zu, 2.marked '%s' as stmt start\n",
                 __func__, __LINE__, pc->GetOrigLine(), pc->Text());
      }
   }

   // Check for "constexpr" after CT_IF or CT_ELSEIF
   if (  frm.top().GetStage() == E_BraceStage::PAREN1
      && (  frm.top().GetOpenToken() == CT_IF
         || frm.top().GetOpenToken() == CT_ELSEIF)
      && pc->Is(CT_CONSTEXPR))
   {
      return(false);
   }

   // Verify open parenthesis in complex statement
   if (  pc->IsNot(CT_PAREN_OPEN)
      && (  (frm.top().GetStage() == E_BraceStage::PAREN1)
         || (frm.top().GetStage() == E_BraceStage::WOD_PAREN)))
   {
      LOG_FMT(LWARN, "%s(%d): %s, orig line is %zu, Error: Expected '(', got '%s' for '%s'\n",
              __func__, __LINE__, cpd.filename.c_str(), pc->GetOrigLine(), pc->Text(),
              get_token_name(frm.top().GetOpenToken()));

      // Throw out the complex statement
      LOG_FMT(LBCSPOP, "%s(%d): pc orig line is %zu, orig col is %zu, Text() is '%s', type is %s\n",
              __func__, __LINE__, pc->GetOrigLine(), pc->GetOrigCol(), pc->Text(), get_token_name(pc->GetType()));
      frm.pop(__func__, __LINE__, pc);
      print_stack(LBCSPOP, "-Error  ", frm);
      exit(EX_SOFTWARE);
   }
   return(false);
} // check_complex_statements


static bool handle_complex_close(ParsingFrame &frm, Chunk *pc, const BraceState &braceState)
{
   LOG_FUNC_ENTRY();

   if (frm.top().GetStage() == E_BraceStage::PAREN1)
   {
      if (pc->GetNext()->GetType() == CT_WHEN)
      {
         frm.top().SetOpenToken(pc->GetType());
         frm.top().SetStage(E_BraceStage::CATCH_WHEN);

         return(true);
      }
      // PAREN1 always => BRACE2
      frm.top().SetStage(E_BraceStage::BRACE2);
   }
   else if (frm.top().GetStage() == E_BraceStage::BRACE2)
   {
      // BRACE2: IF => ELSE, anything else => close
      if (  (frm.top().GetOpenToken() == CT_IF)
         || (frm.top().GetOpenToken() == CT_ELSEIF))
      {
         frm.top().SetStage(E_BraceStage::ELSE);

         // If the next chunk isn't CT_ELSE, close the statement
         Chunk *next = pc->GetNextNcNnl();

         if (  next->IsNullChunk()
            || next->IsNot(CT_ELSE))
         {
            LOG_FMT(LBCSPOP, "%s(%d): no CT_ELSE, pc orig line is %zu, orig col is %zu, Text() is '%s', type is %s\n",
                    __func__, __LINE__, pc->GetOrigLine(), pc->GetOrigCol(), pc->Text(), get_token_name(pc->GetType()));
            frm.pop(__func__, __LINE__, pc);
            print_stack(LBCSPOP, "-IF-HCS ", frm);

            return(close_statement(frm, pc, braceState));
         }
      }
      else if (  (frm.top().GetOpenToken() == CT_TRY)
              || (frm.top().GetOpenToken() == CT_CATCH))
      {
         frm.top().SetStage(E_BraceStage::CATCH);

         // If the next chunk isn't CT_CATCH or CT_FINALLY, close the statement
         Chunk *next = pc->GetNextNcNnl();

         if (  next->IsNot(CT_CATCH)
            && next->IsNot(CT_FINALLY))
         {
            LOG_FMT(LBCSPOP, "%s(%d): pc orig line is %zu, orig col is %zu, Text() is '%s', type is %s\n",
                    __func__, __LINE__, pc->GetOrigLine(), pc->GetOrigCol(), pc->Text(), get_token_name(pc->GetType()));
            frm.pop(__func__, __LINE__, pc);
            print_stack(LBCSPOP, "-TRY-HCS ", frm);

            return(close_statement(frm, pc, braceState));
         }
      }
      else
      {
         LOG_FMT(LNOTE, "%s(%d): close_statement on %s E_BraceStage::BRACE2\n",
                 __func__, __LINE__, get_token_name(frm.top().GetOpenToken()));
         LOG_FMT(LBCSPOP, "%s(%d): pc orig line is %zu, orig col is %zu, Text() is '%s', type is %s\n",
                 __func__, __LINE__, pc->GetOrigLine(), pc->GetOrigCol(), pc->Text(), get_token_name(pc->GetType()));
         frm.pop(__func__, __LINE__, pc);
         print_stack(LBCSPOP, "-HCC B2 ", frm);

         return(close_statement(frm, pc, braceState));
      }
   }
   else if (frm.top().GetStage() == E_BraceStage::BRACE_DO)
   {
      frm.top().SetStage(E_BraceStage::WHILE);
   }
   else if (frm.top().GetStage() == E_BraceStage::WOD_PAREN)
   {
      LOG_FMT(LNOTE, "%s(%d): close_statement on %s E_BraceStage::WOD_PAREN\n",
              __func__, __LINE__, get_token_name(frm.top().GetOpenToken()));
      frm.top().SetStage(E_BraceStage::WOD_SEMI);
      print_stack(LBCSPOP, "-HCC WoDP ", frm);
   }
   else if (frm.top().GetStage() == E_BraceStage::WOD_SEMI)
   {
      LOG_FMT(LNOTE, "%s(%d): close_statement on %s E_BraceStage::WOD_SEMI\n",
              __func__, __LINE__, get_token_name(frm.top().GetOpenToken()));
      LOG_FMT(LBCSPOP, "%s(%d): pc orig line is %zu, orig col is %zu, Text() is '%s', type is %s\n",
              __func__, __LINE__, pc->GetOrigLine(), pc->GetOrigCol(), pc->Text(), get_token_name(pc->GetType()));
      frm.pop(__func__, __LINE__, pc);
      print_stack(LBCSPOP, "-HCC WoDS ", frm);

      return(close_statement(frm, pc, braceState));
   }
   else
   {
      // PROBLEM
      LOG_FMT(LWARN, "%s(%d): %s:%zu Error: TOS.type='%s' TOS.stage=%u\n",
              __func__, __LINE__, cpd.filename.c_str(), pc->GetOrigLine(),
              get_token_name(frm.top().GetOpenToken()),
              (unsigned int)frm.top().GetStage());
      exit(EX_SOFTWARE);
   }
   return(false);
} // handle_complex_close


static void mark_namespace(Chunk *pns)
{
   LOG_FUNC_ENTRY();
   // Issue #1813
   Chunk *br_close;
   bool  is_using = false;

   Chunk *pc = pns->GetPrevNcNnl();

   if (pc->Is(CT_USING))
   {
      is_using = true;
      pns->SetParentType(CT_USING);
   }
   pc = pns->GetNextNcNnl();

   while (pc->IsNotNullChunk())
   {
      pc->SetParentType(CT_NAMESPACE);

      if (pc->IsNot(CT_BRACE_OPEN))
      {
         if (pc->Is(CT_SEMICOLON))
         {
            if (is_using)
            {
               pc->SetParentType(CT_USING);
            }
            return;
         }
         pc = pc->GetNextNcNnl();
         continue;
      }
      log_rule_B("indent_namespace_limit");

      if (  (options::indent_namespace_limit() > 0)
         && ((br_close = pc->GetClosingParen())->IsNotNullChunk()))
      {
         // br_close->GetOrigLine() is always >= pc->GetOrigLine();
         size_t numberOfLines = br_close->GetOrigLine() - pc->GetOrigLine() - 1;                 // Issue #2345
         LOG_FMT(LTOK, "%s(%d): br_close orig line is %zu, pc orig line is %zu\n",
                 __func__, __LINE__, br_close->GetOrigLine(), pc->GetOrigLine());
         LOG_FMT(LTOK, "%s(%d): numberOfLines is %zu, indent_namespace_limit() is %d\n",
                 __func__, __LINE__, numberOfLines, options::indent_namespace_limit());

         log_rule_B("indent_namespace_limit");

         if (numberOfLines > options::indent_namespace_limit())
         {
            LOG_FMT(LTOK, "%s(%d): PCF_LONG_BLOCK is set\n", __func__, __LINE__);
            pc->SetFlagBits(PCF_LONG_BLOCK);
            br_close->SetFlagBits(PCF_LONG_BLOCK);
         }
      }
      flag_parens(pc, PCF_IN_NAMESPACE, CT_NONE, CT_NAMESPACE, false);
      return;
   }
} // mark_namespace


static Chunk *insert_vbrace(Chunk *pc, bool after, const ParsingFrame &frm)
{
   LOG_FUNC_ENTRY();

   Chunk chunk;

   chunk.SetParentType(frm.top().GetOpenToken());
   chunk.SetOrigLine(pc->GetOrigLine());
   chunk.SetLevel(frm.GetParenLevel());
   chunk.SetPpLevel(frm.GetPpLevel());
   chunk.SetBraceLevel(frm.GetBraceLevel());
   chunk.SetFlags(pc->GetFlags() & PCF_COPY_FLAGS);
   chunk.Str() = "";

   if (after)
   {
      chunk.SetOrigCol(pc->GetOrigCol());
      chunk.SetType(CT_VBRACE_CLOSE);
      return(chunk.CopyAndAddAfter(pc));
   }
   Chunk *ref = pc->GetPrev();

   if (ref->IsNullChunk())
   {
      return(Chunk::NullChunkPtr);
   }

   if (!ref->TestFlags(PCF_IN_PREPROC))
   {
      chunk.ResetFlagBits(PCF_IN_PREPROC);
   }
   bool ref_is_comment = ref->IsComment();      // Issue #3351

   while (ref->IsCommentOrNewline())
   {
      ref->SetLevel(ref->GetLevel() + 1);
      ref->SetBraceLevel(ref->GetBraceLevel() + 1);
      ref = ref->GetPrev();
   }

   if (ref->IsNullChunk())
   {
      return(Chunk::NullChunkPtr);
   }

   // Don't back into a preprocessor
   if (  !pc->TestFlags(PCF_IN_PREPROC)
      && ref->TestFlags(PCF_IN_PREPROC))
   {
      if (ref->Is(CT_PREPROC_BODY))
      {
         while (  ref->IsNotNullChunk()
               && ref->TestFlags(PCF_IN_PREPROC))
         {
            ref = ref->GetPrev();
         }
      }
      else
      {
         ref = ref->GetNext();

         if (ref->Is(CT_COMMENT)) // Issue #3034
         {
            ref = ref->GetNextNc();
         }
      }
   }

   if (ref_is_comment)                                      // Issue #3351
   {
      ref = ref->GetNext();
   }

   if (ref->IsNullChunk())
   {
      return(Chunk::NullChunkPtr);
   }
   chunk.SetOrigLine(ref->GetOrigLine());
   chunk.SetOrigCol(ref->GetOrigCol());
   chunk.SetColumn(ref->GetColumn() + ref->Len() + 1);
   chunk.SetPpLevel(ref->GetPpLevel());                         // Issue #3055
   chunk.SetType(CT_VBRACE_OPEN);

   return(chunk.CopyAndAddAfter(ref));
} // insert_vbrace


bool close_statement(ParsingFrame &frm, Chunk *pc, const BraceState &braceState)
{
   LOG_FUNC_ENTRY();

   if (pc->IsNullChunk())
   {
      throw invalid_argument(string(__func__) + ":" + to_string(__LINE__)
                             + "args cannot be null chunk");
   }
   LOG_FMT(LTOK, "%s(%d): orig line is %zu, type is %s, '%s' type is %s, stage is %u\n",
           __func__, __LINE__, pc->GetOrigLine(),
           get_token_name(pc->GetType()), pc->Text(),
           get_token_name(frm.top().GetOpenToken()),
           (unsigned int)frm.top().GetStage());

   if (braceState.consumed)
   {
      frm.SetStmtCount(0);
      frm.SetExprCount(0);
      LOG_FMT(LSTMT, "%s(%d): orig line is %zu> reset2 stmt on '%s'\n",
              __func__, __LINE__, pc->GetOrigLine(), pc->Text());
   }
   /*
    * Insert a CT_VBRACE_CLOSE, if needed:
    * If we are in a virtual brace and we are not ON a CT_VBRACE_CLOSE add one
    */
   Chunk *vbc = pc;

   if (frm.top().GetOpenToken() == CT_VBRACE_OPEN)
   {
      // If the current token has already been consumed, then add after it
      if (braceState.consumed)
      {
         insert_vbrace_close_after(pc, frm);
      }
      else
      {
         // otherwise, add before it and consume the vbrace
         vbc = pc->GetPrevNcNnl();

         frm.SetParenLevel(frm.GetParenLevel() - 1);
         frm.SetBraceLevel(frm.GetBraceLevel() - 1);
         vbc = insert_vbrace_close_after(vbc, frm);
         vbc->SetParentType(frm.top().GetParent());

         LOG_FMT(LBCSPOP, "%s(%d): frame brace level decreased to %zu\n",
                 __func__, __LINE__, frm.GetBraceLevel());
         log_pcf_flags(LBCSPOP, pc->GetFlags());
         LOG_FMT(LBCSPOP, "%s(%d): pc orig line is %zu, orig col is %zu, Text() is '%s', type is %s\n",
                 __func__, __LINE__, pc->GetOrigLine(), pc->GetOrigCol(), pc->Text(), get_token_name(pc->GetType()));
         frm.pop(__func__, __LINE__, pc);

         // Update the token level
         pc->SetLevel(frm.GetParenLevel());
         pc->SetBraceLevel(frm.GetBraceLevel());

         print_stack(LBCSPOP, "-CS VB  ", frm);

         // And repeat the close
         close_statement(frm, pc, braceState);
         return(true);
      }
   }

   // See if we are done with a complex statement
   if (frm.top().GetStage() != E_BraceStage::NONE)
   {
      if (handle_complex_close(frm, vbc, braceState))
      {
         return(true);
      }
   }
   return(false);
} // close_statement
