blob: 964e67af04580e776a519e6ac6e8c33da86122d1 [file] [log] [blame]
/**
* @file width.cpp
* Limits line width.
*
* @author Ben Gardner
* @license GPL v2+
*/
#include "uncrustify_types.h"
#include "chunk_list.h"
#include "prototypes.h"
#include <cstdlib>
static void split_line(chunk_t *pc);
static void split_fcn_params(chunk_t *start);
static void split_fcn_params_full(chunk_t *start);
static void split_for_stmt(chunk_t *start);
static_inline bool is_past_width(chunk_t *pc)
{
// allow char to sit at last column by subtracting 1
return((pc->column + pc->len() - 1) > cpd.settings[UO_code_width].n);
}
/**
* Split right after the chunk
*/
static void split_before_chunk(chunk_t *pc)
{
LOG_FUNC_ENTRY();
LOG_FMT(LSPLIT, "%s: %s\n", __func__, pc->str.c_str());
if (!chunk_is_newline(pc) &&
!chunk_is_newline(chunk_get_prev(pc)))
{
newline_add_before(pc);
// reindent needs to include the indent_continue value and was off by one
reindent_line(pc, pc->brace_level * cpd.settings[UO_indent_columns].n +
abs(cpd.settings[UO_indent_continue].n) + 1);
cpd.changes++;
}
}
/**
* Step forward until a token goes beyond the limit and then call split_line()
* to split the line at or before that point.
*/
void do_code_width(void)
{
LOG_FUNC_ENTRY();
chunk_t *pc;
LOG_FMT(LSPLIT, "%s\n", __func__);
for (pc = chunk_get_head(); pc != NULL; pc = chunk_get_next(pc))
{
if (!chunk_is_newline(pc) &&
!chunk_is_comment(pc) &&
(pc->type != CT_SPACE) &&
is_past_width(pc))
{
split_line(pc);
}
}
}
struct cw_entry
{
chunk_t *pc;
int pri;
};
struct token_pri
{
c_token_t tok;
int pri;
};
static const token_pri pri_table[] =
{
{ CT_SEMICOLON, 1 },
{ CT_COMMA, 2 },
{ CT_BOOL, 3 },
{ CT_COMPARE, 4 },
{ CT_ARITH, 5 },
{ CT_CARET, 6 },
{ CT_ASSIGN, 7 },
{ CT_STRING, 8 },
{ CT_FOR_COLON, 9 },
//{ CT_DC_MEMBER, 10 },
//{ CT_MEMBER, 10 },
{ CT_QUESTION, 20 }, // allow break in ? : for ls_code_width
{ CT_COND_COLON, 20 },
{ CT_FPAREN_OPEN, 21 }, // break after function open paren not followed by close paren
{ CT_QUALIFIER, 25 },
{ CT_CLASS, 25 },
{ CT_STRUCT, 25 },
{ CT_TYPE, 25 },
{ CT_TYPENAME, 25 },
{ CT_VOLATILE, 25 },
};
static int get_split_pri(c_token_t tok)
{
int idx;
for (idx = 0; idx < (int)ARRAY_SIZE(pri_table); idx++)
{
if (pri_table[idx].tok == tok)
{
return(pri_table[idx].pri);
}
}
return(0);
}
/**
* Checks to see if pc is a better spot to split.
* This should only be called going BACKWARDS (ie prev)
* A lower level wins
*
* Splitting Preference:
* - semicolon
* - comma
* - boolean op
* - comparison
* - arithmetic op
* - assignment
* - concatenated strings
* - ? :
* - function open paren not followed by close paren
*/
static void try_split_here(cw_entry& ent, chunk_t *pc)
{
LOG_FUNC_ENTRY();
chunk_t *next;
chunk_t *prev;
int pc_pri = get_split_pri(pc->type);
if (pc_pri == 0)
{
return;
}
/* Can't split after a newline */
prev = chunk_get_prev(pc);
if ((prev == NULL) || (chunk_is_newline(prev) && (pc->type != CT_STRING)))
{
return;
}
/* Can't split a function without arguments */
if (pc->type == CT_FPAREN_OPEN)
{
next = chunk_get_next(pc);
if (next->type == CT_FPAREN_CLOSE)
{
return;
}
}
/* Only split concatenated strings */
if (pc->type == CT_STRING)
{
next = chunk_get_next(pc);
if (next->type != CT_STRING)
{
return;
}
}
/* keep common groupings unless ls_code_width */
if (!cpd.settings[UO_ls_code_width].b && (pc_pri >= 20))
{
return;
}
/* don't break after last term of a qualified type */
if (pc_pri == 25)
{
next = chunk_get_next(pc);
if ((next->type != CT_WORD) && (get_split_pri(next->type) != 25))
{
return;
}
}
/* Check levels first */
bool change = false;
if ((ent.pc == NULL) || (pc->level < ent.pc->level))
{
change = true;
}
else
{
if ((pc->level >= ent.pc->level) &&
(pc_pri < ent.pri))
{
change = true;
}
}
if (change)
{
ent.pc = pc;
ent.pri = pc_pri;
}
}
/**
* Scan backwards to find the most appropriate spot to split the line
* and insert a newline.
*
* See if this needs special function handling.
* Scan backwards and find the best token for the split.
*
* @param start The first chunk that exceeded the limit
*/
static void split_line(chunk_t *start)
{
LOG_FUNC_ENTRY();
LOG_FMT(LSPLIT, "%s: line %d, col %d token:%s[%s] (IN_FUNC=%d) ",
__func__, start->orig_line, start->column, start->str.c_str(),
get_token_name(start->type),
(start->flags & (PCF_IN_FCN_DEF | PCF_IN_FCN_CALL)) != 0);
/**
* break at maximum line length if ls_code_width is true
*/
if (cpd.settings[UO_ls_code_width].b)
{
}
/* Check to see if we are in a for statement */
else if ((start->flags & PCF_IN_FOR) != 0)
{
LOG_FMT(LSPLIT, " ** FOR SPLIT **\n");
split_for_stmt(start);
if (!is_past_width(start))
{
return;
}
LOG_FMT(LSPLIT, "%s: for split didn't work\n", __func__);
}
/* If this is in a function call or prototype, split on commas or right
* after the open paren
*/
else if (((start->flags & PCF_IN_FCN_DEF) != 0) ||
((start->level == (start->brace_level + 1)) &&
((start->flags & PCF_IN_FCN_CALL) != 0)))
{
LOG_FMT(LSPLIT, " ** FUNC SPLIT **\n");
if (cpd.settings[UO_ls_func_split_full].b)
{
split_fcn_params_full(start);
if (!is_past_width(start))
{
return;
}
}
split_fcn_params(start);
return;
}
/**
* Try to find the best spot to split the line
*/
cw_entry ent;
memset(&ent, 0, sizeof(ent));
chunk_t *pc = start;
chunk_t *prev;
while (((pc = chunk_get_prev(pc)) != NULL) && !chunk_is_newline(pc))
{
if (pc->type != CT_SPACE)
{
try_split_here(ent, pc);
/* break at maximum line length */
if ((ent.pc != NULL) && (cpd.settings[UO_ls_code_width].b))
break;
}
}
if (ent.pc == NULL)
{
LOG_FMT(LSPLIT, "%s: TRY_SPLIT yielded NO SOLUTION for line %d at %s [%s]\n",
__func__, start->orig_line, start->str.c_str(), get_token_name(start->type));
}
else
{
LOG_FMT(LSPLIT, "%s: TRY_SPLIT yielded '%s' [%s] on line %d\n", __func__,
ent.pc->str.c_str(), get_token_name(ent.pc->type), ent.pc->orig_line);
}
/* Break before the token instead of after it according to the pos_xxx rules */
if (((chunk_is_token(ent.pc, CT_ARITH) || chunk_is_token(ent.pc, CT_CARET)) &&
(cpd.settings[UO_pos_arith].tp & TP_LEAD)) ||
(chunk_is_token(ent.pc, CT_ASSIGN) &&
(cpd.settings[UO_pos_assign].tp & TP_LEAD)) ||
(chunk_is_token(ent.pc, CT_COMPARE) &&
(cpd.settings[UO_pos_compare].tp & TP_LEAD)) ||
((chunk_is_token(ent.pc, CT_COND_COLON) ||
chunk_is_token(ent.pc, CT_QUESTION)) &&
(cpd.settings[UO_pos_conditional].tp & TP_LEAD)) ||
(chunk_is_token(ent.pc, CT_BOOL) &&
(cpd.settings[UO_pos_bool].tp & TP_LEAD)))
{
pc = ent.pc;
}
else
{
pc = chunk_get_next(ent.pc);
}
if (pc == NULL)
{
pc = start;
/* Don't break before a close, comma, or colon */
if ((start->type == CT_PAREN_CLOSE) ||
(start->type == CT_PAREN_OPEN) ||
(start->type == CT_FPAREN_CLOSE) ||
(start->type == CT_FPAREN_OPEN) ||
(start->type == CT_SPAREN_CLOSE) ||
(start->type == CT_SPAREN_OPEN) ||
(start->type == CT_ANGLE_CLOSE) ||
(start->type == CT_BRACE_CLOSE) ||
(start->type == CT_COMMA) ||
(start->type == CT_SEMICOLON) ||
(start->type == CT_VSEMICOLON) ||
(start->len() == 0))
{
LOG_FMT(LSPLIT, " ** NO GO **\n");
/*TODO: Add in logic to handle 'hard' limits by backing up a token */
return;
}
}
/* add a newline before pc */
prev = chunk_get_prev(pc);
if ((prev != NULL) && !chunk_is_newline(pc) && !chunk_is_newline(prev))
{
int plen = (pc->len() < 5) ? pc->len() : 5;
int slen = (start->len() < 5) ? start->len() : 5;
LOG_FMT(LSPLIT, " '%.*s' [%s], started on token '%.*s' [%s]\n",
plen, pc->str.c_str(), get_token_name(pc->type),
slen, start->str.c_str(), get_token_name(start->type));
split_before_chunk(pc);
}
}
/**
* A for statement is too long.
* Step backwards and forwards to find the semicolons
* Try splitting at the semicolons first.
* If that doesn't work, then look for a comma at paren level.
* If that doesn't work, then look for an assignment at paren level.
* If that doesn't work, then give up.
*/
static void split_for_stmt(chunk_t *start)
{
LOG_FUNC_ENTRY();
int count = 0;
int max_cnt = cpd.settings[UO_ls_for_split_full].b ? 2 : 1;
chunk_t *st[2];
chunk_t *pc;
chunk_t *open_paren = NULL;
int nl_cnt = 0;
LOG_FMT(LSPLIT, "%s: starting on %s, line %d\n",
__func__, start->str.c_str(), start->orig_line);
/* Find the open paren so we know the level and count newlines */
pc = start;
while ((pc = chunk_get_prev(pc)) != NULL)
{
if (pc->type == CT_SPAREN_OPEN)
{
open_paren = pc;
break;
}
if (pc->nl_count > 0)
{
nl_cnt += pc->nl_count;
}
}
if (open_paren == NULL)
{
LOG_FMT(LSYS, "No open paren\n");
return;
}
/* see if we started on the semicolon */
pc = start;
if ((pc->type == CT_SEMICOLON) && (pc->parent_type == CT_FOR))
{
st[count++] = pc;
}
/* first scan backwards for the semicolons */
while ((count < max_cnt) && ((pc = chunk_get_prev(pc)) != NULL) &&
(pc->flags & PCF_IN_SPAREN))
{
if ((pc->type == CT_SEMICOLON) && (pc->parent_type == CT_FOR))
{
st[count++] = pc;
}
}
/* And now scan forward */
pc = start;
while ((count < max_cnt) && ((pc = chunk_get_next(pc)) != NULL) &&
(pc->flags & PCF_IN_SPAREN))
{
if ((pc->type == CT_SEMICOLON) && (pc->parent_type == CT_FOR))
{
st[count++] = pc;
}
}
while (--count >= 0)
{
LOG_FMT(LSPLIT, "%s: split before %s\n", __func__, st[count]->str.c_str());
split_before_chunk(chunk_get_next(st[count]));
}
if (!is_past_width(start) || (nl_cnt > 0))
{
return;
}
/* Still past width, check for commas at paren level */
pc = open_paren;
while ((pc = chunk_get_next(pc)) != start)
{
if ((pc->type == CT_COMMA) && (pc->level == (open_paren->level + 1)))
{
split_before_chunk(chunk_get_next(pc));
if (!is_past_width(pc))
{
return;
}
}
}
/* Still past width, check for a assignments at paren level */
pc = open_paren;
while ((pc = chunk_get_next(pc)) != start)
{
if ((pc->type == CT_ASSIGN) && (pc->level == (open_paren->level + 1)))
{
split_before_chunk(chunk_get_next(pc));
if (!is_past_width(pc))
{
return;
}
}
}
/* Oh, well. We tried. */
}
/**
* Splits the parameters at every comma that is at the fparen level.
*
* @param start the offending token
*/
static void split_fcn_params_full(chunk_t *start)
{
LOG_FUNC_ENTRY();
LOG_FMT(LSPLIT, "%s", __func__);
chunk_t *fpo;
chunk_t *pc;
/* Find the opening fparen */
fpo = start;
while (((fpo = chunk_get_prev(fpo)) != NULL) &&
(fpo->type != CT_FPAREN_OPEN))
{
/* do nothing */
}
/* Now break after every comma */
pc = fpo;
while ((pc = chunk_get_next_ncnl(pc)) != NULL)
{
if (pc->level <= fpo->level)
{
break;
}
if ((pc->level == (fpo->level + 1)) && (pc->type == CT_COMMA))
{
split_before_chunk(chunk_get_next(pc));
}
}
}
/**
* Figures out where to split a function def/proto/call
*
* For fcn protos and defs. Also fcn calls where level == brace_level:
* - find the open fparen
* + if it doesn't have a newline right after it
* * see if all parameters will fit individually after the paren
* * if not, throw a newline after the open paren & return
* - scan backwards to the open fparen or comma
* + if there isn't a newline after that item, add one & return
* + otherwise, add a newline before the start token
*
* @param start the offending token
* @return the token that should have a newline
* inserted before it
*/
static void split_fcn_params(chunk_t *start)
{
LOG_FUNC_ENTRY();
LOG_FMT(LSPLIT, " %s: ", __func__);
chunk_t *prev;
chunk_t *fpo;
chunk_t *pc;
int cur_width = 0;
int last_col = -1;
int min_col;
/* Find the opening fparen */
fpo = start;
while (((fpo = chunk_get_prev(fpo)) != NULL) &&
(fpo->type != CT_FPAREN_OPEN))
{
/* do nothing */
}
pc = chunk_get_next_ncnl(fpo);
min_col = pc->column;
LOG_FMT(LSPLIT, " mincol=%d, max_width=%d ",
min_col, cpd.settings[UO_code_width].n - min_col);
while (pc != NULL)
{
if (chunk_is_newline(pc))
{
cur_width = 0;
last_col = -1;
}
else
{
if (last_col < 0)
{
last_col = pc->column;
}
cur_width += (pc->column - last_col) + pc->len();
last_col = pc->column + pc->len();
if ((pc->type == CT_COMMA) ||
(pc->type == CT_FPAREN_CLOSE))
{
cur_width--;
LOG_FMT(LSPLIT, " width=%d ", cur_width);
if (((last_col - 1) > cpd.settings[UO_code_width].n) ||
(pc->type == CT_FPAREN_CLOSE))
{
break;
}
}
}
pc = chunk_get_next(pc);
}
/* back up until the prev is a comma */
prev = pc;
while ((prev = chunk_get_prev(prev)) != NULL)
{
if (chunk_is_newline(prev) || (prev->type == CT_COMMA))
{
break;
}
last_col -= pc->len();
if (prev->type == CT_FPAREN_OPEN)
{
pc = chunk_get_next(prev);
if (!cpd.settings[UO_indent_paren_nl].b)
{
min_col = pc->brace_level * cpd.settings[UO_indent_columns].n + 1;
if (cpd.settings[UO_indent_continue].n == 0)
{
min_col += cpd.settings[UO_indent_columns].n;
}
else
{
min_col += abs(cpd.settings[UO_indent_continue].n);
}
}
/* Don't split "()" */
if (pc->type != c_token_t(prev->type + 1))
{
break;
}
}
}
if ((prev != NULL) && !chunk_is_newline(prev))
{
LOG_FMT(LSPLIT, " -- ended on [%s] -- ", get_token_name(prev->type));
pc = chunk_get_next(prev);
newline_add_before(pc);
reindent_line(pc, min_col);
cpd.changes++;
}
}