| /** |
| * @file output.cpp |
| * Does all the output & comment formatting. |
| * |
| * $Id: output.cpp 510 2006-09-20 01:14:56Z bengardner $ |
| */ |
| |
| #include "uncrustify_types.h" |
| #include "prototypes.h" |
| #include "chunk_list.h" |
| #include <cstring> |
| #include <cstdlib> |
| |
| |
| |
| void add_char(char ch) |
| { |
| /* convert a newline into the LF/CRLF/CR sequence */ |
| if (ch == '\n') |
| { |
| fputs(cpd.newline, cpd.fout); |
| cpd.column = 1; |
| cpd.did_newline = 1; |
| } |
| else |
| { |
| fputc(ch, cpd.fout); |
| if (ch == '\t') |
| { |
| cpd.column = next_tab_column(cpd.column); |
| } |
| else |
| { |
| cpd.column++; |
| } |
| } |
| } |
| |
| void add_text(const char *text) |
| { |
| char ch; |
| |
| while ((ch = *text) != 0) |
| { |
| text++; |
| add_char(ch); |
| } |
| } |
| |
| void add_text_len(const char *text, int len) |
| { |
| while (len-- > 0) |
| { |
| add_char(*text); |
| text++; |
| } |
| } |
| |
| |
| /** |
| * Advance to a specific column |
| * cpd.column is the current column |
| * |
| * @param column The column to advance to |
| */ |
| void output_to_column(int column, bool allow_tabs) |
| { |
| int nc; |
| |
| cpd.did_newline = 0; |
| if (allow_tabs) |
| { |
| /* tab out as far as possible and then use spaces */ |
| while ((nc = next_tab_column(cpd.column)) <= column) |
| { |
| add_text("\t"); |
| } |
| } |
| /* space out the final bit */ |
| while (cpd.column < column) |
| { |
| add_text(" "); |
| } |
| } |
| |
| void output_indent(int column, int brace_col) |
| { |
| if ((cpd.column == 1) && (cpd.settings[UO_indent_with_tabs].n != 0)) |
| { |
| if (cpd.settings[UO_indent_with_tabs].n == 2) |
| { |
| brace_col = column; |
| } |
| |
| /* tab out as far as possible and then use spaces */ |
| int nc; |
| while ((nc = next_tab_column(cpd.column)) <= brace_col) |
| { |
| add_text("\t"); |
| } |
| } |
| |
| /* space out the rest */ |
| while (cpd.column < column) |
| { |
| add_text(" "); |
| } |
| } |
| |
| |
| |
| void output_parsed(FILE *pfile) |
| { |
| chunk_t *pc; |
| int cnt; |
| |
| output_options(pfile); |
| output_defines(pfile); |
| output_types(pfile); |
| |
| fprintf(pfile, "-=====-\n"); |
| fprintf(pfile, "Line Tag Parent Columns Br/Lvl/pp Flg Nl Text"); |
| for (pc = chunk_get_head(); pc != NULL; pc = chunk_get_next(pc)) |
| { |
| fprintf(pfile, "\n%3d> %13.13s[%13.13s][%2d/%2d/%2d][%d/%d/%d][%6x][%d-%d]", |
| pc->orig_line, get_token_name(pc->type), |
| get_token_name(pc->parent_type), |
| pc->column, pc->orig_col, pc->orig_col_end, |
| pc->brace_level, pc->level, pc->pp_level, |
| pc->flags, pc->nl_count, pc->after_tab); |
| |
| if ((pc->type != CT_NEWLINE) && (pc->len != 0)) |
| { |
| for (cnt = 0; cnt < pc->column; cnt++) |
| { |
| fprintf(pfile, " "); |
| } |
| fprintf(pfile, "%.*s", pc->len, pc->str); |
| } |
| } |
| fprintf(pfile, "\n-=====-\n"); |
| fflush(pfile); |
| } |
| |
| void output_options(FILE *pfile) |
| { |
| int idx; |
| const option_map_value *ptr; |
| |
| fprintf(pfile, "-== Options ==-\n"); |
| for (idx = 0; idx < UO_option_count; idx++) |
| { |
| ptr = get_option_name(idx); |
| if (ptr != NULL) |
| { |
| if (ptr->type == AT_BOOL) |
| { |
| fprintf(pfile, "%3d) %32s = %s\n", |
| ptr->id, ptr->name, |
| cpd.settings[ptr->id].b ? "True" : "False"); |
| } |
| else if (ptr->type == AT_IARF) |
| { |
| fprintf(pfile, "%3d) %32s = %s\n", |
| ptr->id, ptr->name, |
| (cpd.settings[ptr->id].a == AV_ADD) ? "Add" : |
| (cpd.settings[ptr->id].a == AV_REMOVE) ? "Remove" : |
| (cpd.settings[ptr->id].a == AV_FORCE) ? "Force" : "Ignore"); |
| } |
| else if (ptr->type == AT_LINE) |
| { |
| fprintf(pfile, "%3d) %32s = %s\n", |
| ptr->id, ptr->name, |
| (cpd.settings[ptr->id].le == LE_AUTO) ? "Auto" : |
| (cpd.settings[ptr->id].le == LE_LF) ? "LF" : |
| (cpd.settings[ptr->id].le == LE_CRLF) ? "CRLF" : |
| (cpd.settings[ptr->id].le == LE_CR) ? "CR" : "???"); |
| } |
| else /* AT_NUM */ |
| { |
| fprintf(pfile, "%3d) %32s = %d\n", |
| ptr->id, ptr->name, cpd.settings[ptr->id].n); |
| } |
| } |
| } |
| } |
| |
| /** |
| * This renders the chunk list to a file. |
| */ |
| void output_text(FILE *pfile) |
| { |
| chunk_t *pc; |
| chunk_t *prev; |
| int cnt; |
| int lvlcol; |
| bool allow_tabs; |
| |
| cpd.fout = pfile; |
| |
| for (pc = chunk_get_head(); pc != NULL; pc = chunk_get_next(pc)) |
| { |
| if (pc->type == CT_NEWLINE) |
| { |
| for (cnt = 0; cnt < pc->nl_count; cnt++) |
| { |
| add_char('\n'); |
| } |
| cpd.did_newline = 1; |
| cpd.column = 1; |
| LOG_FMT(LOUTIND, " xx\n"); |
| } |
| else if (pc->type == CT_COMMENT_MULTI) |
| { |
| output_comment_multi(pc); |
| } |
| else if (pc->type == CT_COMMENT_CPP) |
| { |
| pc = output_comment_cpp(pc); |
| } |
| else if (pc->len == 0) |
| { |
| /* don't do anything for non-visible stuff */ |
| LOG_FMT(LOUTIND, " <%d> -", pc->column); |
| } |
| else |
| { |
| /* indent to the 'level' first */ |
| if (cpd.did_newline) |
| { |
| if (cpd.settings[UO_indent_with_tabs].n == 1) |
| { |
| lvlcol = 1 + (pc->brace_level * cpd.settings[UO_indent_columns].n); |
| if ((pc->column >= lvlcol) && (lvlcol > 1)) |
| { |
| output_to_column(lvlcol, true); |
| } |
| } |
| allow_tabs = (cpd.settings[UO_indent_with_tabs].n == 2) || |
| (chunk_is_comment(pc) && |
| (cpd.settings[UO_indent_with_tabs].n != 0)); |
| |
| LOG_FMT(LOUTIND, " %d> col %d/%d - ", pc->orig_line, pc->column, cpd.column); |
| } |
| else |
| { |
| /* not the first item on a line */ |
| if (cpd.settings[UO_align_keep_tabs].b) |
| { |
| allow_tabs = pc->after_tab; |
| } |
| else |
| { |
| prev = chunk_get_prev(pc); |
| allow_tabs = (cpd.settings[UO_align_with_tabs].b && |
| ((pc->flags & PCF_WAS_ALIGNED) != 0) && |
| (((pc->column - 1) % cpd.settings[UO_output_tab_size].n) == 0) && |
| ((prev->column + prev->len + 1) != pc->column)); |
| } |
| LOG_FMT(LOUTIND, " %d -", pc->column); |
| } |
| |
| output_to_column(pc->column, allow_tabs); |
| add_text_len(pc->str, pc->len); |
| cpd.did_newline = chunk_is_newline(pc); |
| } |
| } |
| } |
| |
| |
| /** |
| * Given a multi-line comemnt block that starts in column X, figure out how |
| * much subsequent lines should be indented. |
| * |
| * The answer is either 0 or 1. |
| * |
| * The decision is based on: |
| * - the first line length |
| * - the second line leader length |
| * - the last line length |
| * |
| * If the first and last line are the same length and don't contain any alnum |
| * chars and (the first line len > 2 or the second leader is the same as the |
| * first line length), then the indent is 0. |
| * |
| * If the leader on the second line is 1 wide or missing, then the indent is 1. |
| * |
| * Otherwise, the indent is 0. |
| * |
| * @param str The comment string |
| * @param len Length of the comment |
| * @param start_col Starting column |
| * @return 0 or 1 |
| */ |
| static int calculate_comment_body_indent(const char *str, int len, int start_col) |
| { |
| int idx = 0; |
| int first_len = 0; |
| int last_len = 0; |
| int width = 0; |
| |
| /* find the last line length */ |
| for (idx = len - 1; idx > 0; idx--) |
| { |
| if ((str[idx] == '\n') || (str[idx] == '\r')) |
| { |
| idx++; |
| while ((idx < len) && ((str[idx] == ' ') || (str[idx] == '\t'))) |
| { |
| idx++; |
| } |
| last_len = len - idx; |
| break; |
| } |
| } |
| |
| /* find the first line length */ |
| for (idx = 0; idx < len; idx++) |
| { |
| if ((str[idx] == '\n') || (str[idx] == '\r')) |
| { |
| first_len = idx; |
| while ((str[first_len - 1] == ' ') || (str[first_len - 1] == '\t')) |
| { |
| first_len--; |
| } |
| |
| /* handle DOS endings */ |
| if ((str[idx] == '\r') && (str[idx + 1] == '\n')) |
| { |
| idx++; |
| } |
| idx++; |
| break; |
| } |
| } |
| |
| /* Scan the second line */ |
| width = 0; |
| for ( /* nada */; idx < len; idx++) |
| { |
| if ((str[idx] == ' ') || (str[idx] == '\t')) |
| { |
| if (width > 0) |
| { |
| break; |
| } |
| continue; |
| } |
| if ((str[idx] == '\n') || (str[idx] == '\r')) |
| { |
| /* Done with second line */ |
| break; |
| } |
| |
| /* Count the leading chars */ |
| if ((str[idx] == '*') || |
| (str[idx] == '|') || |
| (str[idx] == '\\') || |
| (str[idx] == '#') || |
| (str[idx] == '+')) |
| { |
| width++; |
| } |
| else |
| { |
| break; |
| } |
| } |
| |
| //LOG_FMT(LSYS, "%s: first=%d last=%d width=%d\n", __func__, first_len, last_len, width); |
| |
| /*TODO: make the first_len minimum (4) configurable? */ |
| if ((first_len == last_len) && ((first_len > 4) || first_len == width)) |
| { |
| return(0); |
| } |
| |
| return((width == 2) ? 0 : 1); |
| } |
| |
| /** |
| * Outputs the CPP comment at pc. |
| * CPP comment combining is done here |
| * |
| * @return the last chunk output'd |
| */ |
| chunk_t *output_comment_cpp(chunk_t *first) |
| { |
| int col = first->column; |
| int col_br = 1 + (first->brace_level * cpd.settings[UO_indent_columns].n); |
| |
| /* Make sure we have at least one space past the last token */ |
| if (first->parent_type == CT_COMMENT_END) |
| { |
| chunk_t *prev = chunk_get_prev(first); |
| if (prev != NULL) |
| { |
| int col_min = prev->column + prev->len + 1; |
| if (col < col_min) |
| { |
| col = col_min; |
| } |
| } |
| } |
| |
| /* Bump out to the column */ |
| output_indent(col, col_br); |
| |
| if (!cpd.settings[UO_cmt_cpp_to_c].b) |
| { |
| add_text_len(first->str, first->len); |
| return(first); |
| } |
| |
| /* If we are grouping, see if there is something to group */ |
| bool combined = false; |
| if (cpd.settings[UO_cmt_cpp_group].b) |
| { |
| /* next is a newline by definition */ |
| chunk_t *next = chunk_get_next(first); |
| if ((next != NULL) && (next->nl_count == 1)) |
| { |
| next = chunk_get_next(next); |
| |
| /** |
| * Only combine the next comment if they are both at indent level or |
| * the second one is NOT at indent or less |
| * |
| * A trailing comment cannot be combined with a comment at indent |
| * level or less |
| */ |
| if ((next != NULL) && |
| (next->type == CT_COMMENT_CPP) && |
| (((next->column == 1) && (first->column == 1)) || |
| ((next->column == col_br) && (first->column == col_br)) || |
| ((next->column > col_br) && (first->parent_type == CT_COMMENT_END)))) |
| { |
| combined = true; |
| } |
| } |
| } |
| |
| if (!combined) |
| { |
| /* nothing to group: just output a single line */ |
| add_text_len("/*", 2); |
| if ((first->str[2] != ' ') && (first->str[2] != '\t')) |
| { |
| add_char(' '); |
| } |
| add_text_len(&first->str[2], first->len - 2); |
| add_text_len(" */", 3); |
| return(first); |
| } |
| |
| chunk_t *pc = first; |
| chunk_t *last = first; |
| |
| /* Output the first line */ |
| add_text_len("/*", 2); |
| if (combined && cpd.settings[UO_cmt_cpp_nl_start].b) |
| { |
| /* I suppose someone more clever could do this without a goto or |
| * repeating too much code... |
| */ |
| goto cpp_newline; |
| } |
| goto cpp_addline; |
| |
| /* Output combined lines */ |
| while ((pc = chunk_get_next(pc)) != NULL) |
| { |
| if ((pc->type == CT_NEWLINE) && (pc->nl_count == 1)) |
| { |
| continue; |
| } |
| if (pc->type != CT_COMMENT_CPP) |
| { |
| break; |
| } |
| if (((pc->column == 1) && (first->column == 1)) || |
| ((pc->column == col_br) && (first->column == col_br)) || |
| ((pc->column > col_br) && (first->parent_type == CT_COMMENT_END))) |
| { |
| last = pc; |
| cpp_newline: |
| add_char('\n'); |
| output_indent(col, col_br); |
| add_char(' '); |
| add_char(cpd.settings[UO_cmt_star_cont].b ? '*' : ' '); |
| cpp_addline: |
| if ((pc->str[2] != ' ') && (pc->str[2] != '\t')) |
| { |
| add_char(' '); |
| } |
| add_text_len(&pc->str[2], pc->len - 2); |
| } |
| } |
| |
| if (cpd.settings[UO_cmt_cpp_nl_end].b) |
| { |
| add_char('\n'); |
| output_indent(col, col_br); |
| } |
| add_text_len(" */", 3); |
| return(last); |
| } |
| |
| void output_comment_multi(chunk_t *pc) |
| { |
| int cmt_col = pc->column; |
| const char *cmt_str; |
| int remaining; |
| char ch; |
| chunk_t *prev; |
| char line[1024]; |
| int line_len; |
| int line_count = 0; |
| int ccol; |
| int col_diff = 0; |
| int xtra = 1; |
| |
| prev = chunk_get_prev(pc); |
| if ((prev != NULL) && (prev->type != CT_NEWLINE)) |
| { |
| cmt_col = pc->orig_col; |
| } |
| else |
| { |
| col_diff = pc->orig_col - pc->column; |
| } |
| |
| // fprintf(stderr, "Indenting1 line %d to col %d (orig=%d) col_diff=%d\n", |
| // pc->orig_line, cmt_col, pc->orig_col, col_diff); |
| |
| xtra = calculate_comment_body_indent(pc->str, pc->len, pc->column); |
| |
| ccol = 1; |
| remaining = pc->len; |
| cmt_str = pc->str; |
| line_len = 0; |
| while (remaining > 0) |
| { |
| ch = *cmt_str; |
| cmt_str++; |
| remaining--; |
| |
| /* handle the CRLF and CR endings. convert both to LF */ |
| if (ch == '\r') |
| { |
| ch = '\n'; |
| if (*cmt_str == '\n') |
| { |
| cmt_str++; |
| remaining--; |
| } |
| } |
| |
| /* Find the start column */ |
| if (line_len == 0) |
| { |
| if (ch == ' ') |
| { |
| ccol++; |
| continue; |
| } |
| else if (ch == '\t') |
| { |
| ccol = calc_next_tab_column(ccol, cpd.settings[UO_input_tab_size].n); |
| continue; |
| } |
| else |
| { |
| //fprintf(stderr, "%d] Text starts in col %d\n", line_count, ccol); |
| } |
| } |
| |
| line[line_len++] = ch; |
| |
| /* If we just hit an end of line OR we just hit end-of-comment... */ |
| if ((ch == '\n') || (remaining == 0)) |
| { |
| line_count++; |
| |
| /* strip trailing tabs and spaces before the newline */ |
| if (ch == '\n') |
| { |
| line_len--; |
| while ((line_len > 0) && |
| ((line[line_len - 1] == ' ') || |
| (line[line_len - 1] == '\t'))) |
| { |
| line_len--; |
| } |
| line[line_len++] = ch; |
| } |
| line[line_len] = 0; |
| |
| if (line_count == 1) |
| { |
| /* this is the first line - add unchanged */ |
| |
| /*TODO: need to support indent_with_tabs mode 1 */ |
| output_to_column(cmt_col, cpd.settings[UO_indent_with_tabs].b); |
| add_text_len(line, line_len); |
| } |
| else |
| { |
| /* This is not the first line, so we need to indent to the |
| * correct column. |
| */ |
| ccol -= col_diff; |
| if (ccol < cmt_col) |
| { |
| ccol = cmt_col; |
| } |
| |
| if (line[0] == '\n') |
| { |
| /* Emtpy line - just a '\n' */ |
| if (cpd.settings[UO_cmt_star_cont].b) |
| { |
| output_to_column(cmt_col, cpd.settings[UO_indent_with_tabs].b); |
| add_text((xtra == 1) ? " *" : "*"); |
| } |
| add_char('\n'); |
| } |
| else |
| { |
| /* If this doesn't start with a '*' or '|' */ |
| if ((line[0] != '*') && (line[0] != '|') && (line[0] != '#') && |
| (line[0] != '\\') && (line[0] != '+')) |
| { |
| output_to_column(cmt_col, cpd.settings[UO_indent_with_tabs].b); |
| if (cpd.settings[UO_cmt_star_cont].b) |
| { |
| add_text((xtra == 1) ? " * " : "* "); |
| } |
| else |
| { |
| add_text(" "); |
| } |
| output_to_column(ccol, cpd.settings[UO_indent_with_tabs].b); |
| } |
| else |
| { |
| output_to_column(cmt_col + xtra, cpd.settings[UO_indent_with_tabs].b); |
| } |
| add_text_len(line, line_len); |
| } |
| } |
| line_len = 0; |
| ccol = 1; |
| } |
| } |
| } |