/** * @file lang_pawn.cpp * Special functions for pawn stuff * * @author Ben Gardner * @license GPL v2+ */ #include "lang_pawn.h" #include "chunk_list.h" #include "ChunkStack.h" #include "language_tools.h" #include "prototypes.h" #include "uncrustify.h" #include "uncrustify_types.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_t *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_t *pawn_process_line(chunk_t *start); //! We are on a level 0 function proto of def static chunk_t *pawn_mark_function0(chunk_t *start, chunk_t *fcn); /** * follows a variable definition at level 0 until the end. * Adds a semicolon at the end, if needed. */ static chunk_t *pawn_process_variable(chunk_t *start); static chunk_t *pawn_process_func_def(chunk_t *pc); chunk_t *pawn_add_vsemi_after(chunk_t *pc) { LOG_FUNC_ENTRY(); if (chunk_is_token(pc, CT_VSEMICOLON) || chunk_is_token(pc, CT_SEMICOLON)) { return(pc); } chunk_t *next = chunk_get_next_nc(pc); if ( next != nullptr && (chunk_is_token(next, CT_VSEMICOLON) || chunk_is_token(next, CT_SEMICOLON))) { return(pc); } chunk_t chunk = *pc; set_chunk_type(&chunk, CT_VSEMICOLON); set_chunk_parent(&chunk, CT_NONE); chunk.str = options::mod_pawn_semicolon() ? ";" : ""; chunk.column += pc->len(); LOG_FMT(LPVSEMI, "%s: Added VSEMI on line %zu, prev='%s' [%s]\n", __func__, pc->orig_line, pc->text(), get_token_name(pc->type)); return(chunk_add_after(&chunk, pc)); } void pawn_scrub_vsemi(void) { LOG_FUNC_ENTRY(); log_rule_B("mod_pawn_semicolon"); if (!options::mod_pawn_semicolon()) { return; } for (chunk_t *pc = chunk_get_head(); pc != nullptr; pc = chunk_get_next(pc)) { if (pc->type != CT_VSEMICOLON) { continue; } chunk_t *prev = chunk_get_prev_ncnl(pc); if (chunk_is_token(prev, CT_BRACE_CLOSE)) { if ( get_chunk_parent_type(prev) == CT_IF || get_chunk_parent_type(prev) == CT_ELSE || get_chunk_parent_type(prev) == CT_SWITCH || get_chunk_parent_type(prev) == CT_CASE || get_chunk_parent_type(prev) == CT_WHILE_OF_DO) { pc->str.clear(); } } } } static bool pawn_continued(chunk_t *pc, size_t br_level) { LOG_FUNC_ENTRY(); if (pc == nullptr) { return(false); } if ( pc->level > br_level || chunk_is_token(pc, CT_ARITH) || chunk_is_token(pc, CT_SHIFT) || chunk_is_token(pc, CT_CARET) || chunk_is_token(pc, CT_QUESTION) || chunk_is_token(pc, CT_BOOL) || chunk_is_token(pc, CT_ASSIGN) || chunk_is_token(pc, CT_COMMA) || chunk_is_token(pc, CT_COMPARE) || chunk_is_token(pc, CT_IF) || chunk_is_token(pc, CT_ELSE) || chunk_is_token(pc, CT_DO) || chunk_is_token(pc, CT_SWITCH) || chunk_is_token(pc, CT_WHILE) || chunk_is_token(pc, CT_BRACE_OPEN) || chunk_is_token(pc, CT_VBRACE_OPEN) || chunk_is_token(pc, CT_FPAREN_OPEN) || get_chunk_parent_type(pc) == CT_IF || get_chunk_parent_type(pc) == CT_ELSE || get_chunk_parent_type(pc) == CT_ELSEIF || get_chunk_parent_type(pc) == CT_DO || get_chunk_parent_type(pc) == CT_FOR || get_chunk_parent_type(pc) == CT_SWITCH || get_chunk_parent_type(pc) == CT_WHILE || get_chunk_parent_type(pc) == CT_FUNC_DEF || get_chunk_parent_type(pc) == CT_ENUM || pc->flags.test_any(PCF_IN_ENUM | PCF_IN_STRUCT) || chunk_is_str(pc, ":", 1) || chunk_is_str(pc, "+", 1) || chunk_is_str(pc, "-", 1)) { return(true); } return(false); } // pawn_continued void pawn_prescan(void) { LOG_FUNC_ENTRY(); /* * Start at the beginning and step through the entire file, and clean up * any questionable stuff */ bool did_nl = true; chunk_t *pc = chunk_get_head(); while (pc != nullptr) { if ( did_nl && pc->type != CT_PREPROC && !chunk_is_newline(pc) && pc->level == 0) { // pc now points to the start of a line pc = pawn_process_line(pc); } // note that continued lines are ignored if (pc != nullptr) { did_nl = (chunk_is_token(pc, CT_NEWLINE)); } pc = chunk_get_next_nc(pc); } } static chunk_t *pawn_process_line(chunk_t *start) { LOG_FUNC_ENTRY(); //LOG_FMT(LSYS, "%s: %d - %s\n", __func__, // start->orig_line, start->text()); if (chunk_is_token(start, CT_NEW) || chunk_is_str(start, "const", 5)) { return(pawn_process_variable(start)); } // if a open paren is found before an assign, then this is a function chunk_t *fcn = nullptr; if (chunk_is_token(start, CT_WORD)) { fcn = start; } chunk_t *pc = start; while ( ((pc = chunk_get_next_nc(pc)) != nullptr) && !chunk_is_str(pc, "(", 1) && pc->type != CT_ASSIGN && pc->type != CT_NEWLINE) { if ( pc->level == 0 && ( chunk_is_token(pc, CT_FUNCTION) || chunk_is_token(pc, CT_WORD) || chunk_is_token(pc, CT_OPERATOR_VAL))) { fcn = pc; } } if (pc != nullptr) { if (chunk_is_token(pc, CT_ASSIGN)) { return(pawn_process_variable(pc)); } } if (fcn != nullptr) { //LOG_FMT(LSYS, "FUNCTION: %s\n", fcn->text()); return(pawn_mark_function0(start, fcn)); } if (chunk_is_token(start, CT_ENUM)) { pc = chunk_get_next_type(start, CT_BRACE_CLOSE, start->level); return(pc); } //LOG_FMT(LSYS, "%s: Don't understand line %d, starting with '%s' [%s]\n", // __func__, start->orig_line, start->text(), get_token_name(start->type)); return(start); } // pawn_process_line static chunk_t *pawn_process_variable(chunk_t *start) { LOG_FUNC_ENTRY(); chunk_t *prev = nullptr; chunk_t *pc = start; while ((pc = chunk_get_next_nc(pc)) != nullptr) { if ( chunk_is_token(pc, CT_NEWLINE) && prev != nullptr // Issue 2586 && !pawn_continued(prev, start->level)) { if (prev->type != CT_VSEMICOLON && prev->type != CT_SEMICOLON) { pawn_add_vsemi_after(prev); } break; } prev = pc; } return(pc); } void pawn_add_virtual_semicolons(void) { LOG_FUNC_ENTRY(); // Add Pawn virtual semicolons if (language_is_set(LANG_PAWN)) { chunk_t *prev = nullptr; chunk_t *pc = chunk_get_head(); while ((pc = chunk_get_next(pc)) != nullptr) { if ( !chunk_is_comment(pc) && !chunk_is_newline(pc) && pc->type != CT_VBRACE_CLOSE && pc->type != CT_VBRACE_OPEN) { prev = pc; } if ( prev == nullptr || ( pc->type != CT_NEWLINE && pc->type != CT_BRACE_CLOSE && pc->type != CT_VBRACE_CLOSE)) { continue; } // we just hit a newline and we have a previous token if ( !prev->flags.test(PCF_IN_PREPROC) && !prev->flags.test_any(PCF_IN_ENUM | PCF_IN_STRUCT) && prev->type != CT_VSEMICOLON && prev->type != CT_SEMICOLON && !pawn_continued(prev, prev->brace_level)) { pawn_add_vsemi_after(prev); prev = nullptr; } } } } static chunk_t *pawn_mark_function0(chunk_t *start, chunk_t *fcn) { LOG_FUNC_ENTRY(); // handle prototypes if (start == fcn) { chunk_t *last = chunk_get_next_type(fcn, CT_PAREN_CLOSE, fcn->level); last = chunk_get_next(last); if (chunk_is_token(last, CT_SEMICOLON)) { LOG_FMT(LPFUNC, "%s: %zu] '%s' proto due to semicolon\n", __func__, fcn->orig_line, fcn->text()); set_chunk_type(fcn, CT_FUNC_PROTO); return(last); } } else { if (chunk_is_token(start, CT_FORWARD) || chunk_is_token(start, CT_NATIVE)) { LOG_FMT(LPFUNC, "%s: %zu] '%s' [%s] proto due to %s\n", __func__, fcn->orig_line, fcn->text(), get_token_name(fcn->type), get_token_name(start->type)); set_chunk_type(fcn, CT_FUNC_PROTO); return(chunk_get_next_nc(fcn)); } } // Not a prototype, so it must be a function def return(pawn_process_func_def(fcn)); } static chunk_t *pawn_process_func_def(chunk_t *pc) { LOG_FUNC_ENTRY(); // We are on a function definition set_chunk_type(pc, CT_FUNC_DEF); LOG_FMT(LPFUNC, "%s: %zu:%zu %s\n", __func__, pc->orig_line, pc->orig_col, 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_t *clp = chunk_get_next_str(pc, ")", 1, 0); chunk_t *last = chunk_get_next_ncnl(clp); if (last != nullptr) { LOG_FMT(LPFUNC, "%s: %zu] last is '%s' [%s]\n", __func__, last->orig_line, last->text(), get_token_name(last->type)); } // See if there is a state clause after the function if (last != nullptr && chunk_is_str(last, "<", 1)) { LOG_FMT(LPFUNC, "%s: %zu] '%s' has state angle open %s\n", __func__, pc->orig_line, pc->text(), get_token_name(last->type)); set_chunk_type(last, CT_ANGLE_OPEN); set_chunk_parent(last, CT_FUNC_DEF); while ( ((last = chunk_get_next(last)) != nullptr) && !chunk_is_str(last, ">", 1)) { // do nothing just search, TODO: use search_chunk } if (last != nullptr) { LOG_FMT(LPFUNC, "%s: %zu] '%s' has state angle close %s\n", __func__, pc->orig_line, pc->text(), get_token_name(last->type)); set_chunk_type(last, CT_ANGLE_CLOSE); set_chunk_parent(last, CT_FUNC_DEF); } last = chunk_get_next_ncnl(last); } if (last == nullptr) { return(last); } if (chunk_is_token(last, CT_BRACE_OPEN)) { set_chunk_parent(last, CT_FUNC_DEF); last = chunk_get_next_type(last, CT_BRACE_CLOSE, last->level); if (last != nullptr) { set_chunk_parent(last, CT_FUNC_DEF); } } else { LOG_FMT(LPFUNC, "%s: %zu] '%s' fdef: expected brace open: %s\n", __func__, pc->orig_line, pc->text(), get_token_name(last->type)); // do not insert a vbrace before a preproc if (last->flags.test(PCF_IN_PREPROC)) { return(last); } chunk_t chunk = *last; chunk.str.clear(); set_chunk_type(&chunk, CT_VBRACE_OPEN); set_chunk_parent(&chunk, CT_FUNC_DEF); chunk_t *prev = chunk_add_before(&chunk, last); last = prev; // find the next newline at level 0 prev = chunk_get_next_ncnl(prev); do { LOG_FMT(LPFUNC, "%s:%zu] check %s, level %zu\n", __func__, prev->orig_line, get_token_name(prev->type), prev->level); if (chunk_is_token(prev, CT_NEWLINE) && prev->level == 0) { chunk_t *next = chunk_get_next_ncnl(prev); if ( next != nullptr && next->type != CT_ELSE && next->type != CT_WHILE_OF_DO) { break; } } prev->level++; prev->brace_level++; last = prev; } while ((prev = chunk_get_next(prev)) != nullptr); if (last != nullptr) { LOG_FMT(LPFUNC, "%s:%zu] ended on %s, level %zu\n", __func__, last->orig_line, get_token_name(last->type), last->level); } chunk = *last; chunk.str.clear(); set_chunk_type(&chunk, CT_VBRACE_CLOSE); set_chunk_parent(&chunk, CT_FUNC_DEF); chunk.column += last->len(); chunk.level = 0; chunk.brace_level = 0; last = chunk_add_after(&chunk, last); } return(last); } // pawn_process_func_def chunk_t *pawn_check_vsemicolon(chunk_t *pc) { LOG_FUNC_ENTRY(); // Grab the open VBrace chunk_t *vb_open = chunk_get_prev_type(pc, CT_VBRACE_OPEN, -1); /* * 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->level + 1) -- ie, in () or [] * - it is something that needs a continuation * + arith, assign, bool, comma, compare */ chunk_t *prev = chunk_get_prev_ncnl(pc); if ( prev == nullptr || prev == vb_open || prev->flags.test(PCF_IN_PREPROC) || pawn_continued(prev, vb_open->level + 1)) { if (prev != nullptr) { LOG_FMT(LPVSEMI, "%s: no VSEMI on line %zu, prev='%s' [%s]\n", __func__, prev->orig_line, prev->text(), get_token_name(prev->type)); } return(pc); } return(pawn_add_vsemi_after(prev)); }