xref: /netbsd-src/usr.bin/indent/io.c (revision f68d28ccb5df2dfe20ddb2c038cc8840e47295bd)
1 /*	$NetBSD: io.c,v 1.187 2023/05/23 18:16:28 rillig Exp $	*/
2 
3 /*-
4  * SPDX-License-Identifier: BSD-4-Clause
5  *
6  * Copyright (c) 1985 Sun Microsystems, Inc.
7  * Copyright (c) 1980, 1993
8  *	The Regents of the University of California.  All rights reserved.
9  * All rights reserved.
10  *
11  * Redistribution and use in source and binary forms, with or without
12  * modification, are permitted provided that the following conditions
13  * are met:
14  * 1. Redistributions of source code must retain the above copyright
15  *    notice, this list of conditions and the following disclaimer.
16  * 2. Redistributions in binary form must reproduce the above copyright
17  *    notice, this list of conditions and the following disclaimer in the
18  *    documentation and/or other materials provided with the distribution.
19  * 3. All advertising materials mentioning features or use of this software
20  *    must display the following acknowledgement:
21  *	This product includes software developed by the University of
22  *	California, Berkeley and its contributors.
23  * 4. Neither the name of the University nor the names of its contributors
24  *    may be used to endorse or promote products derived from this software
25  *    without specific prior written permission.
26  *
27  * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
28  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
29  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
30  * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
31  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
32  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
33  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
34  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
35  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
36  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
37  * SUCH DAMAGE.
38  */
39 
40 #include <sys/cdefs.h>
41 __RCSID("$NetBSD: io.c,v 1.187 2023/05/23 18:16:28 rillig Exp $");
42 
43 #include <stdio.h>
44 
45 #include "indent.h"
46 
47 struct buffer inp;
48 struct output_state out;
49 static unsigned wrote_newlines = 2;	/* 0 in the middle of a line, 1 after a
50 					 * single '\n', > 1 means there were (n
51 					 * - 1) blank lines above */
52 static int paren_indent;
53 
54 
55 void
56 inp_skip(void)
57 {
58 	inp.st++;
59 	if ((size_t)(inp.st - inp.mem) >= inp.len)
60 		inp_read_line();
61 }
62 
63 char
64 inp_next(void)
65 {
66 	char ch = inp.st[0];
67 	inp_skip();
68 	return ch;
69 }
70 
71 static void
72 inp_read_next_line(FILE *f)
73 {
74 	inp.st = inp.mem;
75 	inp.len = 0;
76 
77 	for (;;) {
78 		int ch = getc(f);
79 		if (ch == EOF) {
80 			if (indent_enabled == indent_on) {
81 				buf_add_char(&inp, ' ');
82 				buf_add_char(&inp, '\n');
83 			}
84 			had_eof = true;
85 			break;
86 		}
87 
88 		if (ch != '\0')
89 			buf_add_char(&inp, (char)ch);
90 		if (ch == '\n')
91 			break;
92 	}
93 }
94 
95 static void
96 output_newline(void)
97 {
98 	fputc('\n', output);
99 	debug_println("output_newline");
100 	wrote_newlines++;
101 }
102 
103 static void
104 output_range(const char *s, size_t len)
105 {
106 	fwrite(s, 1, len, output);
107 	debug_vis_range("output_range \"", s, len, "\"\n");
108 	for (size_t i = 0; i < len; i++)
109 		wrote_newlines = s[i] == '\n' ? wrote_newlines + 1 : 0;
110 }
111 
112 static int
113 output_indent(int old_ind, int new_ind)
114 {
115 	int ind = old_ind;
116 
117 	if (opt.use_tabs) {
118 		int tabsize = opt.tabsize;
119 		int n = new_ind / tabsize - ind / tabsize;
120 		if (n > 0)
121 			ind -= ind % tabsize;
122 		for (int i = 0; i < n; i++) {
123 			fputc('\t', output);
124 			ind += tabsize;
125 			wrote_newlines = 0;
126 		}
127 	}
128 
129 	for (; ind < new_ind; ind++) {
130 		fputc(' ', output);
131 		wrote_newlines = 0;
132 	}
133 
134 	debug_println("output_indent %d", ind);
135 	return ind;
136 }
137 
138 static bool
139 want_blank_line(void)
140 {
141 	debug_println("%s: %s -> %s", __func__,
142 	    line_kind_name[out.prev_line_kind], line_kind_name[out.line_kind]);
143 
144 	if (ps.blank_line_after_decl && ps.declaration == decl_no) {
145 		ps.blank_line_after_decl = false;
146 		return true;
147 	}
148 	if (opt.blanklines_around_conditional_compilation) {
149 		if (out.prev_line_kind != lk_if && out.line_kind == lk_if)
150 			return true;
151 		if (out.prev_line_kind == lk_endif
152 		    && out.line_kind != lk_endif)
153 			return true;
154 	}
155 	if (opt.blanklines_after_procs && out.prev_line_kind == lk_func_end
156 	    && out.line_kind != lk_endif)
157 		return true;
158 	if (opt.blanklines_before_block_comments
159 	    && out.line_kind == lk_block_comment)
160 		return true;
161 	return false;
162 }
163 
164 static bool
165 is_blank_line_optional(void)
166 {
167 	if (out.prev_line_kind == lk_stmt_head)
168 		return wrote_newlines >= 1;
169 	if (ps.tos >= 2)
170 		return wrote_newlines >= 2;
171 	return wrote_newlines >= 3;
172 }
173 
174 static int
175 output_line_label(void)
176 {
177 	int ind;
178 
179 	while (lab.len > 0 && ch_isblank(lab.mem[lab.len - 1]))
180 		lab.len--;
181 
182 	ind = output_indent(0, compute_label_indent());
183 	output_range(lab.st, lab.len);
184 	ind = ind_add(ind, lab.st, lab.len);
185 
186 	ps.is_case_label = false;
187 	return ind;
188 }
189 
190 static int
191 output_line_code(int ind)
192 {
193 
194 	int target_ind = compute_code_indent();
195 	for (int i = 0; i < ps.nparen; i++) {
196 		int paren_ind = ps.paren[i].indent;
197 		if (paren_ind >= 0) {
198 			ps.paren[i].indent = -1 - (paren_ind + target_ind);
199 			debug_println(
200 			    "setting paren_indents[%d] from %d to %d "
201 			    "for column %d",
202 			    i, paren_ind, ps.paren[i].indent, target_ind + 1);
203 		}
204 	}
205 
206 	ind = output_indent(ind, target_ind);
207 	output_range(code.st, code.len);
208 	return ind_add(ind, code.st, code.len);
209 }
210 
211 static void
212 output_line_comment(int ind)
213 {
214 	int target_ind = ps.com_ind;
215 	const char *p = com.st;
216 
217 	target_ind += ps.comment_delta;
218 
219 	/* consider original indentation in case this is a box comment */
220 	for (; *p == '\t'; p++)
221 		target_ind += opt.tabsize;
222 
223 	for (; target_ind < 0; p++) {
224 		if (*p == ' ')
225 			target_ind++;
226 		else if (*p == '\t')
227 			target_ind = next_tab(target_ind);
228 		else {
229 			target_ind = 0;
230 			break;
231 		}
232 	}
233 
234 	/* if comment can't fit on this line, put it on the next line */
235 	if (ind > target_ind) {
236 		output_newline();
237 		ind = 0;
238 	}
239 
240 	while (com.mem + com.len > p && ch_isspace(com.mem[com.len - 1]))
241 		com.len--;
242 
243 	(void)output_indent(ind, target_ind);
244 	output_range(p, com.len - (size_t)(p - com.mem));
245 
246 	ps.comment_delta = ps.n_comment_delta;
247 }
248 
249 /*
250  * Write a line of formatted source to the output file. The line consists of
251  * the label, the code and the comment.
252  *
253  * Comments are written directly, bypassing this function.
254  */
255 void
256 output_line(void)
257 {
258 	debug_blank_line();
259 	debug_printf("%s", __func__);
260 	debug_buffers();
261 
262 	ps.is_function_definition = false;
263 
264 	if (indent_enabled == indent_on) {
265 		if (lab.len == 0 && code.len == 0 && com.len == 0)
266 			out.line_kind = lk_blank;
267 
268 		if (want_blank_line() && wrote_newlines < 2
269 		    && out.line_kind != lk_blank)
270 			output_newline();
271 
272 		if (ps.ind_level == 0)
273 			ps.in_stmt_cont = false;	/* this is a class A
274 							 * kludge */
275 
276 		if (opt.blank_line_after_decl && ps.declaration == decl_end
277 		    && ps.tos > 1) {
278 			ps.declaration = decl_no;
279 			ps.blank_line_after_decl = true;
280 		}
281 
282 		if (opt.swallow_optional_blanklines
283 		    && out.line_kind == lk_blank
284 		    && is_blank_line_optional())
285 			goto dont_write_line;
286 
287 		int ind = 0;
288 		if (lab.len > 0)
289 			ind = output_line_label();
290 		if (code.len > 0)
291 			ind = output_line_code(ind);
292 		if (com.len > 0)
293 			output_line_comment(ind);
294 
295 		output_newline();
296 		out.prev_line_kind = out.line_kind;
297 	}
298 
299 	if (indent_enabled == indent_last_off_line) {
300 		indent_enabled = indent_on;
301 		output_range(out.indent_off_text.st, out.indent_off_text.len);
302 		out.indent_off_text.len = 0;
303 	}
304 
305 dont_write_line:
306 	ps.decl_on_line = ps.in_decl;	/* for proper comment indentation */
307 	ps.in_stmt_cont = ps.in_stmt_or_decl && !ps.in_decl;
308 	ps.decl_indent_done = false;
309 	if (ps.extra_expr_indent == eei_last)
310 		ps.extra_expr_indent = eei_no;
311 
312 	lab.len = 0;
313 	code.len = 0;
314 	com.len = 0;
315 
316 	ps.ind_level = ps.ind_level_follow;
317 	ps.line_start_nparen = ps.nparen;
318 
319 	if (ps.nparen > 0) {
320 		/* TODO: explain what negative indentation means */
321 		paren_indent = -1 - ps.paren[ps.nparen - 1].indent;
322 		debug_println("paren_indent is now %d", paren_indent);
323 	}
324 
325 	ps.want_blank = false;
326 	out.line_kind = lk_other;
327 }
328 
329 static int
330 compute_code_indent_lineup(int base_ind)
331 {
332 	int ind = paren_indent;
333 	int overflow = ind_add(ind, code.st, code.len) - opt.max_line_length;
334 	if (overflow < 0)
335 		return ind;
336 
337 	if (ind_add(base_ind, code.st, code.len) < opt.max_line_length) {
338 		ind -= overflow + 2;
339 		if (ind > base_ind)
340 			return ind;
341 		return base_ind;
342 	}
343 
344 	return ind;
345 }
346 
347 int
348 compute_code_indent(void)
349 {
350 	int base_ind = ps.ind_level * opt.indent_size;
351 
352 	if (ps.line_start_nparen == 0) {
353 		if (ps.in_enum == in_enum_brace)
354 			return base_ind;
355 		if (ps.in_stmt_cont)
356 			return base_ind + opt.continuation_indent;
357 		return base_ind;
358 	}
359 
360 	if (opt.lineup_to_parens) {
361 		if (opt.lineup_to_parens_always)
362 			return paren_indent;
363 		return compute_code_indent_lineup(base_ind);
364 	}
365 
366 	if (ps.extra_expr_indent != eei_no)
367 		return base_ind + 2 * opt.continuation_indent;
368 
369 	if (2 * opt.continuation_indent == opt.indent_size)
370 		return base_ind + opt.continuation_indent;
371 	else
372 		return base_ind +
373 		    opt.continuation_indent * ps.line_start_nparen;
374 }
375 
376 int
377 compute_label_indent(void)
378 {
379 	if (ps.is_case_label)
380 		return (int)(case_ind * (float)opt.indent_size);
381 	if (lab.st[0] == '#')
382 		return 0;
383 	return opt.indent_size * (ps.ind_level - 2);
384 }
385 
386 void
387 inp_read_line(void)
388 {
389 	if (indent_enabled == indent_on)
390 		out.indent_off_text.len = 0;
391 	buf_add_chars(&out.indent_off_text, inp.mem, inp.len);
392 	inp_read_next_line(input);
393 }
394