/** * @file lang_pawn.cpp * Special functions for pawn stuff * * @author Ben Gardner * @license GPL v2+ */ #include "lang_pawn.h" #include "prototypes.h" using namespace uncrustify; /** * Checks to see if a token continues a statement to the next line. * We need to check for 'open' braces/paren/etc because the level doesn't * change until the token after the open. */ static bool pawn_continued(Chunk *pc, size_t br_level); /** * Functions prototypes and definitions can only appear in level 0. * * Function prototypes start with "native", "forward", or are just a function * with a trailing semicolon instead of a open brace (or something else) * * somefunc(params) <-- def * stock somefunc(params) <-- def * somefunc(params); <-- proto * forward somefunc(params) <-- proto * native somefunc[rect](params) <-- proto * * Functions start with 'stock', 'static', 'public', or '@' (on level 0) * * Variable definitions start with 'stock', 'static', 'new', or 'public'. */ static Chunk *pawn_process_line(Chunk *start); //! We are on a level 0 function proto of def static Chunk *pawn_mark_function0(Chunk *start, Chunk *fcn); /** * follows a variable definition at level 0 until the end. * Adds a semicolon at the end, if needed. */ static Chunk *pawn_process_variable(Chunk *start); static Chunk *pawn_process_func_def(Chunk *pc); Chunk *pawn_add_vsemi_after(Chunk *pc) { LOG_FUNC_ENTRY(); if (pc->IsSemicolon()) { return(pc); } Chunk *next = pc->GetNextNc(); if ( next->IsNotNullChunk() && next->IsSemicolon()) { return(pc); } Chunk chunk = *pc; chunk.SetType(CT_VSEMICOLON); chunk.SetParentType(CT_NONE); chunk.Str() = options::mod_pawn_semicolon() ? ";" : ""; chunk.SetColumn(pc->GetColumn() + pc->Len()); LOG_FMT(LPVSEMI, "%s: Added VSEMI on line %zu, prev='%s' [%s]\n", __func__, pc->GetOrigLine(), pc->Text(), get_token_name(pc->GetType())); return(chunk.CopyAndAddAfter(pc)); } void pawn_scrub_vsemi() { constexpr static auto LCURRENT = LPVSEMI; LOG_FUNC_ENTRY(); log_rule_B("mod_pawn_semicolon"); if (!options::mod_pawn_semicolon()) { return; } for (Chunk *pc = Chunk::GetHead(); pc->IsNotNullChunk(); pc = pc->GetNext()) { if (pc->IsNot(CT_VSEMICOLON)) { continue; } Chunk *prev = pc->GetPrevNcNnl(); if (prev->Is(CT_BRACE_CLOSE)) { if ( prev->GetParentType() == CT_IF || prev->GetParentType() == CT_ELSE || prev->GetParentType() == CT_SWITCH || prev->GetParentType() == CT_CASE || prev->GetParentType() == CT_WHILE_OF_DO) { pc->Str().clear(); } } } } static bool pawn_continued(Chunk *pc, size_t br_level) { LOG_FUNC_ENTRY(); if (pc->IsNullChunk()) { return(false); } if ( pc->GetLevel() > br_level || pc->Is(CT_ARITH) || pc->Is(CT_SHIFT) || pc->Is(CT_CARET) || pc->Is(CT_QUESTION) || pc->Is(CT_BOOL) || pc->Is(CT_ASSIGN) || pc->Is(CT_COMMA) || pc->Is(CT_COMPARE) || pc->Is(CT_IF) || pc->Is(CT_ELSE) || pc->Is(CT_DO) || pc->Is(CT_SWITCH) || pc->Is(CT_WHILE) || pc->Is(CT_BRACE_OPEN) || pc->Is(CT_VBRACE_OPEN) || pc->Is(CT_FPAREN_OPEN) || pc->GetParentType() == CT_IF || pc->GetParentType() == CT_ELSE || pc->GetParentType() == CT_ELSEIF || pc->GetParentType() == CT_DO || pc->GetParentType() == CT_FOR || pc->GetParentType() == CT_SWITCH || pc->GetParentType() == CT_WHILE || pc->GetParentType() == CT_FUNC_DEF || pc->GetParentType() == CT_ENUM || pc->GetFlags().test_any(PCF_IN_ENUM | PCF_IN_STRUCT) || pc->IsString(":") || pc->IsString("+") || pc->IsString("-")) { return(true); } return(false); } // pawn_continued void pawn_prescan() { LOG_FUNC_ENTRY(); /* * Start at the beginning and step through the entire file, and clean up * any questionable stuff */ bool did_nl = true; Chunk *pc = Chunk::GetHead(); while (pc->IsNotNullChunk()) { if ( did_nl && pc->IsNot(CT_PREPROC) && !pc->IsNewline() && pc->GetLevel() == 0) { // pc now points to the start of a line pc = pawn_process_line(pc); } // note that continued lines are ignored if (pc->IsNotNullChunk()) { did_nl = (pc->Is(CT_NEWLINE)); } pc = pc->GetNextNc(); } } static Chunk *pawn_process_line(Chunk *start) { LOG_FUNC_ENTRY(); //LOG_FMT(LSYS, "%s: %d - %s\n", __func__, // start->GetOrigLine(), start->Text()); if ( start->Is(CT_NEW) || start->IsString("const")) { return(pawn_process_variable(start)); } // if a open paren is found before an assign, then this is a function Chunk *fcn = Chunk::NullChunkPtr; if (start->Is(CT_WORD)) { fcn = start; } Chunk *pc = start; while ( ((pc = pc->GetNextNc())->IsNotNullChunk()) && !pc->IsString("(") && pc->IsNot(CT_ASSIGN) && pc->IsNot(CT_NEWLINE)) { if ( pc->GetLevel() == 0 && ( pc->Is(CT_FUNCTION) || pc->Is(CT_WORD) || pc->Is(CT_OPERATOR_VAL))) { fcn = pc; } } if (pc->IsNotNullChunk()) { if (pc->Is(CT_ASSIGN)) { return(pawn_process_variable(pc)); } } if (fcn->IsNotNullChunk()) { //LOG_FMT(LSYS, "FUNCTION: %s\n", fcn->Text()); return(pawn_mark_function0(start, fcn)); } if (start->Is(CT_ENUM)) { pc = start->GetNextType(CT_BRACE_CLOSE, start->GetLevel()); return(pc); } //LOG_FMT(LSYS, "%s: Don't understand line %d, starting with '%s' [%s]\n", // __func__, start->GetOrigLine(), start->Text(), get_token_name(start->GetType())); return(start); } // pawn_process_line static Chunk *pawn_process_variable(Chunk *start) { LOG_FUNC_ENTRY(); Chunk *pc = Chunk::NullChunkPtr; if (start->IsNotNullChunk()) { pc = start; } Chunk *prev = Chunk::NullChunkPtr; while ((pc = pc->GetNextNc())->IsNotNullChunk()) { if ( pc->Is(CT_NEWLINE) && prev->IsNotNullChunk() && !pawn_continued(prev, start->GetLevel())) { if (!prev->IsSemicolon()) { pawn_add_vsemi_after(prev); } break; } prev = pc; } return(pc); } void pawn_add_virtual_semicolons() { LOG_FUNC_ENTRY(); // Add Pawn virtual semicolons if (language_is_set(LANG_PAWN)) { Chunk *prev = Chunk::NullChunkPtr; Chunk *pc = Chunk::GetHead(); while ((pc = pc->GetNext())->IsNotNullChunk()) { if ( !pc->IsCommentOrNewline() && !pc->IsVBrace()) { prev = pc; } if ( prev->IsNullChunk() || ( pc->IsNot(CT_NEWLINE) && !pc->IsBraceClose())) { continue; } // we just hit a newline and we have a previous token if ( !prev->TestFlags(PCF_IN_PREPROC) && !prev->GetFlags().test_any(PCF_IN_ENUM | PCF_IN_STRUCT) && !prev->IsSemicolon() && !pawn_continued(prev, prev->GetBraceLevel())) { pawn_add_vsemi_after(prev); prev = Chunk::NullChunkPtr; } } } } // pawn_add_virtual_semicolons static Chunk *pawn_mark_function0(Chunk *start, Chunk *fcn) { LOG_FUNC_ENTRY(); // handle prototypes if (start == fcn) { Chunk *last = fcn->GetNextType(CT_PAREN_CLOSE, fcn->GetLevel())->GetNext(); if (last->Is(CT_SEMICOLON)) { LOG_FMT(LPFUNC, "%s: %zu] '%s' proto due to semicolon\n", __func__, fcn->GetOrigLine(), fcn->Text()); fcn->SetType(CT_FUNC_PROTO); return(last); } } else { if ( start->Is(CT_FORWARD) || start->Is(CT_NATIVE)) { LOG_FMT(LPFUNC, "%s: %zu] '%s' [%s] proto due to %s\n", __func__, fcn->GetOrigLine(), fcn->Text(), get_token_name(fcn->GetType()), get_token_name(start->GetType())); fcn->SetType(CT_FUNC_PROTO); return(fcn->GetNextNc()); } } // Not a prototype, so it must be a function def return(pawn_process_func_def(fcn)); } static Chunk *pawn_process_func_def(Chunk *pc) { LOG_FUNC_ENTRY(); // We are on a function definition pc->SetType(CT_FUNC_DEF); LOG_FMT(LPFUNC, "%s: %zu:%zu %s\n", __func__, pc->GetOrigLine(), pc->GetOrigCol(), pc->Text()); /* * If we don't have a brace open right after the close fparen, then * we need to add virtual braces around the function body. */ Chunk *clp = pc->GetNextString(")", 1, 0); Chunk *last = clp->GetNextNcNnl(); if (last->IsNotNullChunk()) { LOG_FMT(LPFUNC, "%s: %zu] last is '%s' [%s]\n", __func__, last->GetOrigLine(), last->Text(), get_token_name(last->GetType())); } // See if there is a state clause after the function if ( last->IsNotNullChunk() && last->IsString("<")) { LOG_FMT(LPFUNC, "%s: %zu] '%s' has state angle open %s\n", __func__, pc->GetOrigLine(), pc->Text(), get_token_name(last->GetType())); last->SetType(CT_ANGLE_OPEN); last->SetParentType(CT_FUNC_DEF); while ( ((last = last->GetNext())->IsNotNullChunk()) && !last->IsString(">")) { // do nothing just search, TODO: use search_chunk } if (last->IsNotNullChunk()) { LOG_FMT(LPFUNC, "%s: %zu] '%s' has state angle close %s\n", __func__, pc->GetOrigLine(), pc->Text(), get_token_name(last->GetType())); last->SetType(CT_ANGLE_CLOSE); last->SetParentType(CT_FUNC_DEF); } last = last->GetNextNcNnl(); } if (last->IsNullChunk()) { return(last); } if (last->Is(CT_BRACE_OPEN)) { last->SetParentType(CT_FUNC_DEF); last = last->GetNextType(CT_BRACE_CLOSE, last->GetLevel()); if (last->IsNotNullChunk()) { last->SetParentType(CT_FUNC_DEF); } } else { LOG_FMT(LPFUNC, "%s: %zu] '%s' fdef: expected brace open: %s\n", __func__, pc->GetOrigLine(), pc->Text(), get_token_name(last->GetType())); // do not insert a vbrace before a preproc if (last->TestFlags(PCF_IN_PREPROC)) { return(last); } Chunk chunk = *last; chunk.Str().clear(); chunk.SetType(CT_VBRACE_OPEN); chunk.SetParentType(CT_FUNC_DEF); Chunk *prev = chunk.CopyAndAddBefore(last); last = prev; // find the next newline at level 0 prev = prev->GetNextNcNnl(); do { LOG_FMT(LPFUNC, "%s:%zu] check %s, level %zu\n", __func__, prev->GetOrigLine(), get_token_name(prev->GetType()), prev->GetLevel()); if ( prev->Is(CT_NEWLINE) && prev->GetLevel() == 0) { Chunk *next = prev->GetNextNcNnl(); if ( next->IsNotNullChunk() && next->IsNot(CT_ELSE) && next->IsNot(CT_WHILE_OF_DO)) { break; } } prev->SetLevel(prev->GetLevel() + 1); prev->SetBraceLevel(prev->GetBraceLevel() + 1); last = prev; } while ((prev = prev->GetNext())->IsNotNullChunk()); if (last->IsNotNullChunk()) { LOG_FMT(LPFUNC, "%s:%zu] ended on %s, level %zu\n", __func__, last->GetOrigLine(), get_token_name(last->GetType()), last->GetLevel()); } chunk = *last; chunk.Str().clear(); chunk.SetType(CT_VBRACE_CLOSE); chunk.SetParentType(CT_FUNC_DEF); chunk.SetColumn(chunk.GetColumn() + last->Len()); chunk.SetLevel(0); chunk.SetBraceLevel(0); last = chunk.CopyAndAddAfter(last); } return(last); } // pawn_process_func_def Chunk *pawn_check_vsemicolon(Chunk *pc) { LOG_FUNC_ENTRY(); // Grab the open VBrace Chunk *vb_open = pc->GetPrevType(CT_VBRACE_OPEN); /* * Grab the item before the newline * Don't do anything if: * - the only thing previous is the V-Brace open * - in a preprocessor * - level > (vb_open->GetLevel() + 1) -- ie, in () or [] * - it is something that needs a continuation * + arith, assign, bool, comma, compare */ Chunk *prev = pc->GetPrevNcNnl(); if ( prev->IsNullChunk() || prev == vb_open || prev->TestFlags(PCF_IN_PREPROC) || pawn_continued(prev, vb_open->GetLevel() + 1)) { if (prev->IsNotNullChunk()) { LOG_FMT(LPVSEMI, "%s: no VSEMI on line %zu, prev='%s' [%s]\n", __func__, prev->GetOrigLine(), prev->Text(), get_token_name(prev->GetType())); } return(pc); } return(pawn_add_vsemi_after(prev)); }