#include <sys/cdefs.h>
__RCSID("$NetBSD: io.c,v 1.237 2025/01/04 10:28:08 rillig Exp $");
#include <err.h>
#include <stdio.h>
#include "indent.h"
struct input_state in = {
.token_end_line = 1,
};
struct output_state out;
enum indent_enabled indent_enabled;
static int out_ind;
static unsigned newlines = 2;
static unsigned buffered_newlines;
static int paren_indent;
static void
inp_read_next_line(void)
{
buf_clear(&in.line);
for (;;) {
int ch = getc(in.f);
if (ch == EOF) {
if (indent_enabled == indent_on) {
buf_add_char(&in.line, ' ');
buf_add_char(&in.line, '\n');
}
had_eof = true;
break;
}
if (ch != '\0')
buf_add_char(&in.line, (char)ch);
if (ch == '\n')
break;
}
buf_terminate(&in.line);
in.p = in.line.s;
}
void
inp_read_line(void)
{
if (indent_enabled == indent_on)
buf_clear(&out.indent_off_text);
buf_add_chars(&out.indent_off_text, in.line.s, in.line.len);
inp_read_next_line();
}
void
inp_skip(void)
{
in.p++;
if ((size_t)(in.p - in.line.s) >= in.line.len)
inp_read_line();
}
char
inp_next(void)
{
char ch = in.p[0];
inp_skip();
return ch;
}
static void
add_buffered_newline(void)
{
buffered_newlines++;
newlines++;
out_ind = 0;
}
static void
write_buffered_newlines(void)
{
for (; buffered_newlines > 0; buffered_newlines--) {
if (fputc('\n', output) == EOF)
err(1, "cannot write output");
debug_println("write_newline");
}
}
static void
write_range(const char *s, size_t len)
{
write_buffered_newlines();
if (fwrite(s, 1, len, output) != len)
err(1, "cannot write output");
debug_printf("write_range ");
debug_vis_range(s, len);
debug_println("");
for (size_t i = 0; i < len; i++)
newlines = s[i] == '\n' ? newlines + 1 : 0;
out_ind = ind_add(out_ind, s, len);
}
static void
write_indent(int new_ind)
{
write_buffered_newlines();
int ind = out_ind;
if (opt.use_tabs) {
int n = new_ind / opt.tabsize - ind / opt.tabsize;
if (n > 0) {
ind = ind - ind % opt.tabsize + n * opt.tabsize;
while (n-- > 0)
if (fputc('\t', output) == EOF)
err(1, "cannot write output");
newlines = 0;
}
}
for (; ind < new_ind; ind++) {
if (fputc(' ', output) == EOF)
err(1, "cannot write output");
newlines = 0;
}
debug_println("write_indent %d", ind);
out_ind = ind;
}
static bool
want_blank_line(void)
{
debug_println("%s: %s -> %s", __func__,
line_kind_name[out.prev_line_kind], line_kind_name[out.line_kind]);
debug_blank_line();
if (((ps.blank_line_after_decl && ps.declaration == decl_no)
|| ps.badp == badp_yes)
&& (lab.len > 0 || code.len > 0)) {
ps.blank_line_after_decl = false;
ps.badp = badp_none;
return true;
}
if (opt.blank_line_around_conditional_compilation) {
if (out.prev_line_kind != lk_pre_if
&& out.line_kind == lk_pre_if)
return true;
if (out.prev_line_kind == lk_pre_endif
&& out.line_kind != lk_pre_endif)
return true;
}
if (opt.blank_line_after_proc && out.prev_line_kind == lk_func_end
&& out.line_kind != lk_pre_endif && out.line_kind != lk_pre_other)
return true;
if (opt.blank_line_before_block_comment
&& out.line_kind == lk_block_comment)
return true;
return false;
}
static bool
is_blank_line_optional(void)
{
if (out.prev_line_kind == lk_stmt_head)
return newlines >= 1;
if (ps.psyms.len >= 3)
return newlines >= 2;
return newlines >= 3;
}
static int
compute_case_label_indent(void)
{
size_t i = ps.psyms.len - 1;
while (i > 0 && ps.psyms.sym[i] != psym_switch_expr)
i--;
float case_ind = (float)ps.psyms.ind_level[i] + opt.case_indent;
return (int)(case_ind * (float)opt.indent_size);
}
int
compute_label_indent(void)
{
if (out.line_kind == lk_case_or_default)
return compute_case_label_indent();
if (lab.s[0] == '#')
return 0;
return opt.indent_size * (ps.ind_level - 2);
}
static void
output_line_label(void)
{
write_indent(compute_label_indent());
write_range(lab.s, lab.len);
}
static int
compute_lined_up_code_indent(int base_ind)
{
int ind = paren_indent;
int overflow = ind_add(ind, code.s, code.len) - opt.max_line_length;
if (overflow >= 0
&& ind_add(base_ind, code.s, code.len) < opt.max_line_length) {
ind -= 2 + overflow;
if (ind < base_ind)
ind = base_ind;
}
if (ps.extra_expr_indent != eei_no
&& ind == base_ind + opt.indent_size)
ind += opt.continuation_indent;
return ind;
}
int
compute_code_indent(void)
{
int base_ind = ps.ind_level * opt.indent_size;
if (ps.ind_paren_level == 0) {
if (ps.line_is_stmt_cont)
return base_ind + opt.continuation_indent;
return base_ind;
}
if (opt.lineup_to_parens) {
if (opt.lineup_to_parens_always)
return paren_indent;
return compute_lined_up_code_indent(base_ind);
}
int rel_ind = opt.continuation_indent * ps.ind_paren_level;
if (ps.extra_expr_indent != eei_no && rel_ind == opt.indent_size)
rel_ind += opt.continuation_indent;
return base_ind + rel_ind;
}
static void
output_line_code(void)
{
int target_ind = compute_code_indent();
for (size_t i = 0; i < ps.paren.len; i++) {
int paren_ind = ps.paren.item[i].indent;
if (paren_ind >= 0) {
ps.paren.item[i].indent =
-1 - (paren_ind + target_ind);
debug_println(
"setting paren_indents[%zu] from %d to %d "
"for column %d",
i, paren_ind,
ps.paren.item[i].indent, target_ind + 1);
}
}
if (lab.len > 0 && target_ind <= out_ind)
write_range(" ", 1);
write_indent(target_ind);
write_range(code.s, code.len);
}
static void
output_comment(void)
{
int target_ind = ps.comment_ind;
const char *p;
if (ps.comment_cont)
target_ind += ps.comment_shift;
ps.comment_cont = true;
for (p = com.s; *p == '\t'; p++)
target_ind += opt.tabsize;
for (; target_ind < 0; p++) {
if (*p == ' ')
target_ind++;
else if (*p == '\t')
target_ind = next_tab(target_ind);
else {
target_ind = 0;
break;
}
}
if (out_ind > target_ind)
add_buffered_newline();
while (com.s + com.len > p && ch_isspace(com.s[com.len - 1]))
com.len--;
buf_terminate(&com);
write_indent(target_ind);
write_range(p, com.len - (size_t)(p - com.s));
}
static void
output_indented_line(void)
{
if (lab.len == 0 && code.len == 0 && com.len == 0)
out.line_kind = lk_blank;
if (want_blank_line() && newlines < 2 && out.line_kind != lk_blank)
add_buffered_newline();
if (ps.ind_level == 0)
ps.line_is_stmt_cont = false;
if (opt.blank_line_after_decl && ps.declaration == decl_end
&& ps.psyms.len > 2) {
ps.declaration = decl_no;
ps.blank_line_after_decl = true;
}
if (opt.swallow_optional_blank_lines
&& out.line_kind == lk_blank
&& is_blank_line_optional())
return;
if (lab.len > 0)
output_line_label();
if (code.len > 0)
output_line_code();
if (com.len > 0)
output_comment();
add_buffered_newline();
if (out.line_kind != lk_blank)
write_buffered_newlines();
out.prev_line_kind = out.line_kind;
}
static bool
is_stmt_cont(void)
{
if (ps.psyms.len >= 2
&& ps.psyms.sym[ps.psyms.len - 2] == psym_lbrace_enum
&& ps.prev_lsym == lsym_comma
&& ps.paren.len == 0)
return false;
return ps.in_stmt_or_decl
&& (!ps.in_decl || ps.in_init)
&& ps.init_level == 0;
}
static void
prepare_next_line(void)
{
ps.line_has_decl = ps.in_decl;
ps.line_has_func_def = false;
ps.line_is_stmt_cont = is_stmt_cont();
ps.decl_indent_done = false;
if (ps.extra_expr_indent == eei_last)
ps.extra_expr_indent = eei_no;
if (!(ps.psyms.sym[ps.psyms.len - 1] == psym_if_expr_stmt_else
&& ps.paren.len > 0))
ps.ind_level = ps.ind_level_follow;
ps.ind_paren_level = (int)ps.paren.len;
ps.want_blank = false;
if ((ps.badp == badp_seen_lbrace || ps.badp == badp_seen_decl)
&& !ps.in_decl)
ps.badp = badp_yes;
if (ps.paren.len > 0) {
paren_indent = -1 - ps.paren.item[ps.paren.len - 1].indent;
debug_println("paren_indent is now %d", paren_indent);
}
out.line_kind = lk_other;
}
void
output_line(void)
{
debug_blank_line();
debug_buffers(__func__);
if (indent_enabled == indent_on)
output_indented_line();
else if (indent_enabled == indent_last_off_line) {
indent_enabled = indent_on;
write_range(out.indent_off_text.s, out.indent_off_text.len);
buf_clear(&out.indent_off_text);
}
buf_clear(&lab);
buf_clear(&code);
buf_clear(&com);
prepare_next_line();
}
void
finish_output(void)
{
output_line();
if (indent_enabled != indent_on) {
indent_enabled = indent_last_off_line;
output_line();
}
if (fflush(output) != 0)
err(1, "output file");
}