| /** |
| * @file lang_pawn.cpp |
| * Special functions for pawn stuff |
| * |
| * @author Ben Gardner |
| * @license GPL v2+ |
| */ |
| #include "uncrustify_types.h" |
| #include "chunk_list.h" |
| #include "ChunkStack.h" |
| #include "prototypes.h" |
| |
| static chunk_t *pawn_process_line(chunk_t *start); |
| static chunk_t *pawn_mark_function0(chunk_t *start, chunk_t *fcn); |
| 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 ((pc->type == CT_VSEMICOLON) || |
| (pc->type == CT_SEMICOLON)) |
| { |
| return(pc); |
| } |
| |
| chunk_t *next = chunk_get_next_nc(pc); |
| if ((next != NULL) && |
| ((next->type == CT_VSEMICOLON) || |
| (next->type == CT_SEMICOLON))) |
| { |
| return(pc); |
| } |
| chunk_t chunk; |
| |
| chunk = *pc; |
| chunk.type = CT_VSEMICOLON; |
| chunk.str = cpd.settings[UO_mod_pawn_semicolon].b ? ";" : ""; |
| chunk.column += pc->len(); |
| chunk.parent_type = CT_NONE; |
| |
| LOG_FMT(LPVSEMI, "%s: Added VSEMI on line %d, prev='%s' [%s]\n", |
| __func__, pc->orig_line, pc->str.c_str(), |
| get_token_name(pc->type)); |
| |
| return(chunk_add_after(&chunk, pc)); |
| } |
| |
| |
| /** |
| * Turns certain virtual semicolons invisible. |
| * - after a close brace with a parent of switch, case, else, if |
| */ |
| void pawn_scrub_vsemi(void) |
| { |
| LOG_FUNC_ENTRY(); |
| if (!cpd.settings[UO_mod_pawn_semicolon].b) |
| { |
| return; |
| } |
| |
| chunk_t *pc; |
| chunk_t *prev; |
| |
| for (pc = chunk_get_head(); pc != NULL; pc = chunk_get_next(pc)) |
| { |
| if (pc->type != CT_VSEMICOLON) |
| { |
| continue; |
| } |
| prev = chunk_get_prev_ncnl(pc); |
| if ((prev != NULL) && (prev->type == CT_BRACE_CLOSE)) |
| { |
| if ((prev->parent_type == CT_IF) || |
| (prev->parent_type == CT_ELSE) || |
| (prev->parent_type == CT_SWITCH) || |
| (prev->parent_type == CT_CASE) || |
| (prev->parent_type == CT_WHILE_OF_DO)) |
| { |
| pc->str.clear(); |
| } |
| } |
| } |
| } |
| |
| |
| /** |
| * 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, int br_level) |
| { |
| LOG_FUNC_ENTRY(); |
| if (pc == NULL) |
| { |
| return(false); |
| } |
| if ((pc->level > br_level) || |
| (pc->type == CT_ARITH) || |
| (pc->type == CT_CARET) || |
| (pc->type == CT_QUESTION) || |
| (pc->type == CT_BOOL) || |
| (pc->type == CT_ASSIGN) || |
| (pc->type == CT_COMMA) || |
| (pc->type == CT_COMPARE) || |
| (pc->type == CT_IF) || |
| (pc->type == CT_ELSE) || |
| (pc->type == CT_DO) || |
| (pc->type == CT_SWITCH) || |
| (pc->type == CT_WHILE) || |
| (pc->type == CT_BRACE_OPEN) || |
| (pc->type == CT_VBRACE_OPEN) || |
| (pc->type == CT_FPAREN_OPEN) || |
| (pc->parent_type == CT_IF) || |
| (pc->parent_type == CT_ELSE) || |
| (pc->parent_type == CT_ELSEIF) || |
| (pc->parent_type == CT_DO) || |
| (pc->parent_type == CT_FOR) || |
| (pc->parent_type == CT_SWITCH) || |
| (pc->parent_type == CT_WHILE) || |
| (pc->parent_type == CT_FUNC_DEF) || |
| (pc->parent_type == CT_ENUM) || |
| ((pc->flags & (PCF_IN_ENUM | PCF_IN_STRUCT)) != 0) || |
| chunk_is_str(pc, ":", 1) || |
| chunk_is_str(pc, "+", 1) || |
| chunk_is_str(pc, "-", 1)) |
| { |
| return(true); |
| } |
| return(false); |
| } |
| |
| |
| /** |
| * Does a scan of level 0 BEFORE stuff in combine.cpp is called. |
| * At this point, VSemis have been added only in VBraces. |
| * Otherwise, all level info is correct, except for unbraced functions. |
| * |
| * We are looking for unbraced functions. |
| */ |
| void pawn_prescan(void) |
| { |
| LOG_FUNC_ENTRY(); |
| /* Start at the beginning and step through the entire file, and clean up |
| * any questionable stuff |
| */ |
| |
| chunk_t *pc; |
| bool did_nl = true; |
| |
| pc = chunk_get_head(); |
| while (pc != NULL) |
| { |
| 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 != NULL) |
| { |
| did_nl = (pc->type == CT_NEWLINE); |
| } |
| |
| pc = chunk_get_next_nc(pc); |
| } |
| } |
| |
| |
| /** |
| * 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) |
| { |
| LOG_FUNC_ENTRY(); |
| chunk_t *pc; |
| chunk_t *fcn = NULL; |
| |
| //LOG_FMT(LSYS, "%s: %d - %s\n", __func__, |
| // start->orig_line, start->str.c_str()); |
| |
| if ((start->type == 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 */ |
| if (start->type == CT_WORD) |
| { |
| fcn = start; |
| } |
| pc = start; |
| while (((pc = chunk_get_next_nc(pc)) != NULL) && |
| !chunk_is_str(pc, "(", 1) && |
| (pc->type != CT_ASSIGN) && |
| (pc->type != CT_NEWLINE)) |
| { |
| if ((pc->level == 0) && |
| ((pc->type == CT_FUNCTION) || |
| (pc->type == CT_WORD) || |
| (pc->type == CT_OPERATOR_VAL))) |
| { |
| fcn = pc; |
| } |
| } |
| |
| if (pc != NULL) |
| { |
| if (pc->type == CT_ASSIGN) |
| { |
| return(pawn_process_variable(pc)); |
| } |
| } |
| |
| if (fcn != NULL) |
| { |
| //LOG_FMT(LSYS, "FUNCTION: %s\n", fcn->str.c_str()); |
| return(pawn_mark_function0(start, fcn)); |
| } |
| |
| if (start->type == 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->str.c_str(), get_token_name(start->type)); |
| return(start); |
| } |
| |
| |
| /** |
| * 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) |
| { |
| LOG_FUNC_ENTRY(); |
| chunk_t *prev = NULL; |
| chunk_t *pc = start; |
| |
| while ((pc = chunk_get_next_nc(pc)) != NULL) |
| { |
| if ((pc->type == CT_NEWLINE) && |
| !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(); |
| chunk_t *prev; |
| chunk_t *pc; |
| |
| /** Add Pawn virtual semicolons */ |
| prev = NULL; |
| if ((cpd.lang_flags & LANG_PAWN) != 0) |
| { |
| pc = chunk_get_head(); |
| while ((pc = chunk_get_next(pc)) != NULL) |
| { |
| if (!chunk_is_comment(pc) && |
| !chunk_is_newline(pc) && |
| (pc->type != CT_VBRACE_CLOSE) && |
| (pc->type != CT_VBRACE_OPEN)) |
| { |
| prev = pc; |
| } |
| if ((prev == NULL) || |
| ((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 & PCF_IN_PREPROC) == 0) && |
| ((prev->flags & (PCF_IN_ENUM | PCF_IN_STRUCT)) == 0) && |
| (prev->type != CT_VSEMICOLON) && |
| (prev->type != CT_SEMICOLON) && |
| !pawn_continued(prev, prev->brace_level)) |
| { |
| pawn_add_vsemi_after(prev); |
| prev = NULL; |
| } |
| } |
| } |
| } |
| |
| |
| /** |
| * We are on a level 0 function proto of def |
| */ |
| static chunk_t *pawn_mark_function0(chunk_t *start, chunk_t *fcn) |
| { |
| LOG_FUNC_ENTRY(); |
| chunk_t *last; |
| |
| /* handle prototypes */ |
| if (start == fcn) |
| { |
| last = chunk_get_next_type(fcn, CT_PAREN_CLOSE, fcn->level); |
| last = chunk_get_next(last); |
| if ((last != NULL) && (last->type == CT_SEMICOLON)) |
| { |
| LOG_FMT(LPFUNC, "%s: %d] '%s' proto due to semicolon\n", __func__, |
| fcn->orig_line, fcn->str.c_str()); |
| set_chunk_type(fcn, CT_FUNC_PROTO); |
| return(last); |
| } |
| } |
| else |
| { |
| if ((start->type == CT_FORWARD) || |
| (start->type == CT_NATIVE)) |
| { |
| LOG_FMT(LPFUNC, "%s: %d] '%s' [%s] proto due to %s\n", __func__, |
| fcn->orig_line, fcn->str.c_str(), |
| 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 */ |
| chunk_t *clp; |
| chunk_t *last; |
| chunk_t *next; |
| |
| set_chunk_type(pc, CT_FUNC_DEF); |
| |
| /* If we don't have a brace open right after the close fparen, then |
| * we need to add virtual braces around the function body. |
| */ |
| clp = chunk_get_next_str(pc, ")", 1, 0); |
| last = chunk_get_next_ncnl(clp); |
| |
| if (last != NULL) |
| { |
| LOG_FMT(LPFUNC, "%s: %d] last is '%s' [%s]\n", __func__, |
| last->orig_line, last->str.c_str(), get_token_name(last->type)); |
| } |
| |
| /* See if there is a state clause after the function */ |
| if ((last != NULL) && chunk_is_str(last, "<", 1)) |
| { |
| LOG_FMT(LPFUNC, "%s: %d] '%s' has state angle open %s\n", __func__, |
| pc->orig_line, pc->str.c_str(), 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)) != NULL) && |
| !chunk_is_str(last, ">", 1)) |
| { |
| } |
| |
| if (last != NULL) |
| { |
| LOG_FMT(LPFUNC, "%s: %d] '%s' has state angle close %s\n", __func__, |
| pc->orig_line, pc->str.c_str(), 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 == NULL) |
| { |
| return(last); |
| } |
| if (last->type == CT_BRACE_OPEN) |
| { |
| set_chunk_parent(last, CT_FUNC_DEF); |
| last = chunk_get_next_type(last, CT_BRACE_CLOSE, last->level); |
| if (last != NULL) |
| { |
| set_chunk_parent(last, CT_FUNC_DEF); |
| } |
| } |
| else |
| { |
| LOG_FMT(LPFUNC, "%s: %d] '%s' fdef: expected brace open: %s\n", __func__, |
| pc->orig_line, pc->str.c_str(), get_token_name(last->type)); |
| |
| chunk_t chunk; |
| chunk = *last; |
| chunk.str.clear(); |
| chunk.type = CT_VBRACE_OPEN; |
| chunk.parent_type = 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:%d] check %s, level %d\n", __func__, |
| prev->orig_line, get_token_name(prev->type), prev->level); |
| if ((prev->type == CT_NEWLINE) && |
| (prev->level == 0)) |
| { |
| next = chunk_get_next_ncnl(prev); |
| if ((next != NULL) && |
| (next->type != CT_ELSE) && |
| (next->type != CT_WHILE_OF_DO)) |
| { |
| break; |
| } |
| } |
| prev->level++; |
| prev->brace_level++; |
| last = prev; |
| } while ((prev = chunk_get_next(prev)) != NULL); |
| |
| if (last != NULL) |
| { |
| LOG_FMT(LPFUNC, "%s:%d] ended on %s, level %d\n", __func__, |
| last->orig_line, get_token_name(last->type), last->level); |
| } |
| |
| chunk = *last; |
| chunk.str.clear(); |
| chunk.column += last->len(); |
| chunk.type = CT_VBRACE_CLOSE; |
| chunk.level = 0; |
| chunk.brace_level = 0; |
| chunk.parent_type = CT_FUNC_DEF; |
| last = chunk_add_after(&chunk, last); |
| } |
| return(last); |
| } |
| |
| |
| /** |
| * We are in a virtual brace and hit a newline. |
| * If this should end the vbrace, then insert a VSEMICOLON and return that. |
| * |
| * @param pc The newline (CT_NEWLINE) |
| * @return Either the newline or the newly inserted virtual semicolon |
| */ |
| chunk_t *pawn_check_vsemicolon(chunk_t *pc) |
| { |
| LOG_FUNC_ENTRY(); |
| chunk_t *vb_open; |
| chunk_t *prev; |
| |
| /* Grab the open VBrace */ |
| 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 |
| */ |
| prev = chunk_get_prev_ncnl(pc); |
| if ((prev == NULL) || |
| (prev == vb_open) || |
| ((prev->flags & PCF_IN_PREPROC) != 0) || |
| pawn_continued(prev, vb_open->level + 1)) |
| { |
| if (prev != NULL) |
| { |
| LOG_FMT(LPVSEMI, "%s: no VSEMI on line %d, prev='%s' [%s]\n", |
| __func__, prev->orig_line, prev->str.c_str(), get_token_name(prev->type)); |
| } |
| return(pc); |
| } |
| |
| return(pawn_add_vsemi_after(prev)); |
| } |