| /** |
| * @file align_stack.cpp |
| * Manages a align stack, which is just a pair of chunk stacks. |
| * There can be at most 1 item per line in the stack. |
| * The seqnum is actually a line counter. |
| * |
| * @author Ben Gardner |
| * @license GPL v2+ |
| */ |
| #include "align_stack.h" |
| #include "prototypes.h" |
| #include "chunk_list.h" |
| |
| /** |
| * Resets the two ChunkLists and zeroes local vars. |
| * |
| * @param span The row span limit |
| * @param thresh The column threshold |
| */ |
| void AlignStack::Start(int span, int thresh) |
| { |
| LOG_FMT(LAS, "Start(%d, %d)\n", span, thresh); |
| |
| m_aligned.Reset(); |
| m_skipped.Reset(); |
| m_span = span; |
| m_thresh = thresh; |
| m_min_col = 9999; |
| m_max_col = 0; |
| m_nl_seqnum = 0; |
| m_seqnum = 0; |
| m_gap = 0; |
| m_right_align = false; |
| m_star_style = SS_IGNORE; |
| m_amp_style = SS_IGNORE; |
| } |
| |
| |
| /** |
| * Calls Add on all the skipped items |
| */ |
| void AlignStack::ReAddSkipped() |
| { |
| if (!m_skipped.Empty()) |
| { |
| /* Make a copy of the ChunkStack and clear m_skipped */ |
| m_scratch.Set(m_skipped); |
| m_skipped.Reset(); |
| |
| const ChunkStack::Entry *ce; |
| |
| /* Need to add them in order so that m_nl_seqnum is correct */ |
| for (int idx = 0; idx < m_scratch.Len(); idx++) |
| { |
| ce = m_scratch.Get(idx); |
| LOG_FMT(LAS, "ReAddSkipped [%d] - ", ce->m_seqnum); |
| Add(ce->m_pc, ce->m_seqnum); |
| } |
| |
| /* Check to see if we need to flush right away */ |
| NewLines(0); |
| } |
| } |
| |
| |
| /** |
| * Adds an entry to the appropriate stack. |
| * |
| * @param pc The chunk |
| * @param seqnum Optional seqnum (0=assign one) |
| */ |
| void AlignStack::Add(chunk_t *start, int seqnum) |
| { |
| LOG_FUNC_ENTRY(); |
| /* Assign a seqnum if needed */ |
| if (seqnum == 0) |
| { |
| seqnum = m_seqnum; |
| } |
| |
| chunk_t *ali; |
| chunk_t *ref; |
| chunk_t *tmp; |
| chunk_t *prev; |
| chunk_t *next; |
| |
| int col_adj = 0; /* Amount the column is shifted for 'dangle' mode */ |
| int tmp_col; |
| int endcol; |
| int gap; |
| |
| m_last_added = 0; |
| |
| /* Check threshold limits */ |
| if ((m_max_col == 0) || (m_thresh == 0) || |
| (((start->column + m_gap) <= (m_max_col + m_thresh)) && |
| (((start->column + m_gap) >= (m_max_col - m_thresh)) || |
| (start->column >= m_min_col)))) |
| { |
| /* we are adding it, so update the newline seqnum */ |
| if (seqnum > m_nl_seqnum) |
| { |
| m_nl_seqnum = seqnum; |
| } |
| |
| /** |
| * SS_IGNORE: no special handling of '*' or '&', only 'foo' is aligned |
| * void foo; // gap=5, 'foo' is aligned |
| * char * foo; // gap=3, 'foo' is aligned |
| * foomatic foo; // gap=1, 'foo' is aligned |
| * The gap is the columns between 'foo' and the previous token. |
| * [void - foo], ['*' - foo], etc |
| * |
| * SS_INCLUDE: - space between variable and '*' or '&' is eaten |
| * void foo; // gap=5, 'foo' is aligned |
| * char *foo; // gap=5, '*' is aligned |
| * foomatic foo; // gap=1, 'foo' is aligned |
| * The gap is the columns between the first '*' or '&' before foo |
| * and the previous token. [void - foo], [char - '*'], etc |
| * |
| * SS_DANGLE: - space between variable and '*' or '&' is eaten |
| * void foo; // gap=5 |
| * char *bar; // gap=5, as the '*' doesn't count |
| * foomatic foo; // gap=1 |
| * The gap is the columns between 'foo' and the chunk before the first |
| * '*' or '&'. [void - foo], [char - bar], etc |
| * |
| * If the gap < m_gap, then the column is bumped out by the difference. |
| * So, if m_gap is 2, then the above would be: |
| * SS_IGNORE: |
| * void foo; // gap=6 |
| * char * foo; // gap=4 |
| * foomatic foo; // gap=2 |
| * SS_INCLUDE: |
| * void foo; // gap=6 |
| * char *foo; // gap=6 |
| * foomatic foo; // gap=2 |
| * SS_DANGLE: |
| * void foo; // gap=6 |
| * char *bar; // gap=6, as the '*' doesn't count |
| * foomatic foo; // gap=2 |
| * Right aligned numbers: |
| * #define A -1 |
| * #define B 631 |
| * #define C 3 |
| * Left aligned numbers: |
| * #define A -1 |
| * #define B 631 |
| * #define C 3 |
| * |
| * In the code below, pc is set to the item that is aligned. |
| * In the above examples, that is 'foo', '*', '-', or 63. |
| * |
| * Ref is set to the last part of the type. |
| * In the above examples, that is 'void', 'char', 'foomatic', 'A', or 'B'. |
| * |
| * The '*' and '&' can float between the two. |
| * |
| * If align_on_tabstop=true, then SS_DANGLE is changed to SS_INCLUDE. |
| */ |
| |
| if (cpd.settings[UO_align_on_tabstop].b && (m_star_style == SS_DANGLE)) |
| { |
| m_star_style = SS_INCLUDE; |
| } |
| |
| /* Find ref. Back up to the real item that is aligned. */ |
| prev = start; |
| while (((prev = chunk_get_prev(prev)) != NULL) && |
| (chunk_is_star(prev) || |
| chunk_is_addr(prev) || |
| (prev->type == CT_TPAREN_OPEN))) |
| { |
| /* do nothing - we want prev when this exits */ |
| } |
| ref = prev; |
| if (chunk_is_newline(ref)) |
| { |
| ref = chunk_get_next(ref); |
| } |
| |
| /* Find the item that we are going to align. */ |
| ali = start; |
| if (m_star_style != SS_IGNORE) |
| { |
| /* back up to the first '*' preceding the token */ |
| prev = chunk_get_prev(ali); |
| while (chunk_is_star(prev)) |
| { |
| ali = prev; |
| prev = chunk_get_prev(ali); |
| } |
| if (chunk_is_token(prev, CT_TPAREN_OPEN)) |
| { |
| ali = prev; |
| prev = chunk_get_prev(ali); |
| } |
| } |
| if (m_amp_style != SS_IGNORE) |
| { |
| /* back up to the first '&' preceding the token */ |
| prev = chunk_get_prev(ali); |
| while (chunk_is_addr(prev)) |
| { |
| ali = prev; |
| prev = chunk_get_prev(ali); |
| } |
| } |
| |
| /* Tighten down the spacing between ref and start */ |
| if (!cpd.settings[UO_align_keep_extra_space].b) |
| { |
| tmp_col = ref->column; |
| tmp = ref; |
| while (tmp != start) |
| { |
| next = chunk_get_next(tmp); |
| tmp_col += space_col_align(tmp, next); |
| if (next->column != tmp_col) |
| { |
| align_to_column(next, tmp_col); |
| } |
| tmp = next; |
| } |
| } |
| |
| /* Set the column adjust and gap */ |
| col_adj = 0; |
| gap = 0; |
| if (ref != ali) |
| { |
| gap = ali->column - (ref->column + ref->len()); |
| } |
| tmp = ali; |
| if (chunk_is_token(tmp, CT_TPAREN_OPEN)) |
| { |
| tmp = chunk_get_next(tmp); |
| } |
| if ((chunk_is_star(tmp) && (m_star_style == SS_DANGLE)) || |
| (chunk_is_addr(tmp) && (m_amp_style == SS_DANGLE))) |
| { |
| col_adj = start->column - ali->column; |
| gap = start->column - (ref->column + ref->len()); |
| } |
| |
| /* See if this pushes out the max_col */ |
| endcol = ali->column + col_adj; |
| if (gap < m_gap) |
| { |
| endcol += m_gap - gap; |
| } |
| |
| // LOG_FMT(LSYS, "[%p] line %d pc='%s' [%s] col:%d ali='%s' [%s] col:%d ref='%s' [%s] col:%d col_adj=%d endcol=%d, ss=%d as=%d, gap=%d\n", |
| // this, |
| // start->orig_line, |
| // start->str.c_str(), get_token_name(start->type), start->column, |
| // ali->str.c_str(), get_token_name(ali->type), ali->column, |
| // ref->str.c_str(), get_token_name(ref->type), ref->column, |
| // col_adj, endcol, m_star_style, m_amp_style, gap); |
| |
| ali->align.col_adj = col_adj; |
| ali->align.ref = ref; |
| ali->align.start = start; |
| m_aligned.Push_Back(ali, seqnum); |
| m_last_added = 1; |
| |
| LOG_FMT(LAS, "Add-[%s]: line %d, col %d, adj %d : ref=[%s] endcol=%d\n", |
| ali->str.c_str(), ali->orig_line, ali->column, ali->align.col_adj, |
| ref->str.c_str(), endcol); |
| |
| if (m_min_col > endcol) |
| { |
| m_min_col = endcol; |
| } |
| |
| if (endcol > m_max_col) |
| { |
| LOG_FMT(LAS, "Add-aligned [%d/%d/%d]: line %d, col %d : max_col old %d, new %d - min_col %d\n", |
| seqnum, m_nl_seqnum, m_seqnum, |
| ali->orig_line, ali->column, m_max_col, endcol, m_min_col); |
| m_max_col = endcol; |
| |
| /** |
| * If there were any entries that were skipped, re-add them as they |
| * may now be within the threshold |
| */ |
| if (!m_skipped.Empty()) |
| { |
| ReAddSkipped(); |
| } |
| } |
| else |
| { |
| LOG_FMT(LAS, "Add-aligned [%d/%d/%d]: line %d, col %d : col %d <= %d - min_col %d\n", |
| seqnum, m_nl_seqnum, m_seqnum, |
| ali->orig_line, ali->column, endcol, m_max_col, m_min_col); |
| } |
| } |
| else |
| { |
| /* The threshold check failed, so add it to the skipped list */ |
| m_skipped.Push_Back(start, seqnum); |
| m_last_added = 2; |
| |
| LOG_FMT(LAS, "Add-skipped [%d/%d/%d]: line %d, col %d <= %d + %d\n", |
| seqnum, m_nl_seqnum, m_seqnum, |
| start->orig_line, start->column, m_max_col, m_thresh); |
| } |
| } |
| |
| |
| /** |
| * Adds some newline and calls Flush() if needed |
| */ |
| void AlignStack::NewLines(int cnt) |
| { |
| if (!m_aligned.Empty()) |
| { |
| m_seqnum += cnt; |
| if (m_seqnum > (m_nl_seqnum + m_span)) |
| { |
| LOG_FMT(LAS, "Newlines<%d>-", cnt); |
| Flush(); |
| } |
| else |
| { |
| LOG_FMT(LAS, "Newlines<%d>\n", cnt); |
| } |
| } |
| } |
| |
| |
| /** |
| * Aligns all the stuff in m_aligned. |
| * Re-adds 'newer' items in m_skipped. |
| */ |
| void AlignStack::Flush() |
| { |
| int last_seqnum = 0; |
| int idx; |
| int tmp_col; |
| const ChunkStack::Entry *ce = NULL; |
| chunk_t *pc; |
| |
| LOG_FMT(LAS, "Flush (min=%d, max=%d)\n", m_min_col, m_max_col); |
| |
| m_last_added = 0; |
| m_max_col = 0; |
| |
| /* Recalculate the max_col - it may have shifted since the last Add() */ |
| for (idx = 0; idx < m_aligned.Len(); idx++) |
| { |
| pc = m_aligned.Get(idx)->m_pc; |
| |
| /* Set the column adjust and gap */ |
| int col_adj = 0; |
| int gap = 0; |
| if (pc != pc->align.ref) |
| { |
| gap = pc->column - (pc->align.ref->column + pc->align.ref->len()); |
| } |
| chunk_t *tmp = pc; |
| if (tmp->type == CT_TPAREN_OPEN) |
| { |
| tmp = chunk_get_next(tmp); |
| } |
| if ((chunk_is_star(tmp) && (m_star_style == SS_DANGLE)) || |
| (chunk_is_addr(tmp) && (m_amp_style == SS_DANGLE))) |
| { |
| col_adj = pc->align.start->column - pc->column; |
| gap = pc->align.start->column - (pc->align.ref->column + pc->align.ref->len()); |
| } |
| if (m_right_align) |
| { |
| /* Adjust the width for signed numbers */ |
| int start_len = pc->align.start->len(); |
| if (pc->align.start->type == CT_NEG) |
| { |
| tmp = chunk_get_next(pc->align.start); |
| if ((tmp != NULL) && (tmp->type == CT_NUMBER)) |
| { |
| start_len += tmp->len(); |
| } |
| } |
| col_adj += start_len; |
| } |
| |
| pc->align.col_adj = col_adj; |
| |
| /* See if this pushes out the max_col */ |
| int endcol = pc->column + col_adj; |
| if (gap < m_gap) |
| { |
| endcol += m_gap - gap; |
| } |
| if (endcol > m_max_col) |
| { |
| m_max_col = endcol; |
| } |
| } |
| |
| if (cpd.settings[UO_align_on_tabstop].b && (m_aligned.Len() > 1)) |
| { |
| m_max_col = align_tab_column(m_max_col); |
| } |
| |
| for (idx = 0; idx < m_aligned.Len(); idx++) |
| { |
| ce = m_aligned.Get(idx); |
| pc = ce->m_pc; |
| |
| tmp_col = m_max_col - pc->align.col_adj; |
| if (idx == 0) |
| { |
| if (m_skip_first && (pc->column != tmp_col)) |
| { |
| LOG_FMT(LAS, "%s: %d:%d dropping first item due to skip_first\n", __func__, |
| pc->orig_line, pc->orig_col); |
| m_skip_first = false; |
| m_aligned.Pop_Front(); |
| Flush(); |
| m_skip_first = true; |
| return; |
| } |
| pc->flags |= PCF_ALIGN_START; |
| |
| pc->align.right_align = m_right_align; |
| pc->align.amp_style = (int)m_amp_style; |
| pc->align.star_style = (int)m_star_style; |
| } |
| pc->align.gap = m_gap; |
| pc->align.next = m_aligned.GetChunk(idx + 1); |
| |
| /* Indent the token, taking col_adj into account */ |
| LOG_FMT(LAS, "%s: line %d: '%s' to col %d (adj=%d)\n", __func__, |
| pc->orig_line, pc->str.c_str(), tmp_col, pc->align.col_adj); |
| align_to_column(pc, tmp_col); |
| } |
| |
| if (ce != NULL) |
| { |
| last_seqnum = ce->m_seqnum; |
| m_aligned.Reset(); |
| } |
| m_min_col = 9999; |
| m_max_col = 0; |
| |
| if (m_skipped.Empty()) |
| { |
| /* Nothing was skipped, sync the seqnums */ |
| m_nl_seqnum = m_seqnum; |
| } |
| else |
| { |
| /* Remove all items with seqnum < last_seqnum */ |
| for (idx = 0; idx < m_skipped.Len(); idx++) |
| { |
| if (m_skipped.Get(idx)->m_seqnum < last_seqnum) |
| { |
| m_skipped.Zap(idx); |
| } |
| } |
| m_skipped.Collapse(); |
| |
| /* Add all items from the skipped list */ |
| ReAddSkipped(); |
| } |
| } |
| |
| |
| /** |
| * Resets the stack, discarding anything that was previously added |
| */ |
| void AlignStack::Reset() |
| { |
| m_aligned.Reset(); |
| m_skipped.Reset(); |
| } |
| |
| |
| /** |
| * Aligns everything else and resets the lists. |
| */ |
| void AlignStack::End() |
| { |
| if (!m_aligned.Empty()) |
| { |
| LOG_FMT(LAS, "End-"); |
| Flush(); |
| } |
| |
| m_aligned.Reset(); |
| m_skipped.Reset(); |
| } |