Home | History | Annotate | Line # | Download | only in indent
      1 /*	$NetBSD: pr_comment.c,v 1.174 2025/01/04 10:28:08 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: pr_comment.c,v 1.174 2025/01/04 10:28:08 rillig Exp $");
     42 
     43 #include <string.h>
     44 
     45 #include "indent.h"
     46 
     47 static void
     48 com_add_char(char ch)
     49 {
     50 	buf_add_char(&com, ch);
     51 }
     52 
     53 static void
     54 com_add_star(void)
     55 {
     56 	if (opt.star_comment_cont)
     57 		buf_add_str(&com, " * ");
     58 }
     59 
     60 static bool
     61 fits_in_one_line(int max_line_length)
     62 {
     63 	for (const char *start = in.p, *p = start; *p != '\n'; p++) {
     64 		if (p[0] == '*' && p[1] == '/') {
     65 			while (p - in.p >= 2
     66 			    && ch_isblank(p[-1])
     67 			    && ch_isblank(p[-2]))
     68 				p--;
     69 			int ind = ind_add(ps.comment_ind + 3,
     70 			    start, (size_t)(p - start));
     71 			ind += p == start || ch_isblank(p[-1]) ? 2 : 3;
     72 			return ind <= max_line_length;
     73 		}
     74 	}
     75 	return false;
     76 }
     77 
     78 static bool
     79 is_block_comment(void)
     80 {
     81 	const char *p = in.p;
     82 	while (*p == '*')
     83 		p++;
     84 	return *p == '\n';
     85 }
     86 
     87 static void
     88 analyze_comment(bool *p_may_wrap, bool *p_delim, int *p_line_length)
     89 {
     90 	bool may_wrap = true;
     91 	bool delim = false;	// only relevant if may_wrap
     92 	int ind;
     93 	int line_length = opt.max_line_length;
     94 
     95 	if (in.p - in.line.s == 2 && !opt.format_col1_comments) {
     96 		may_wrap = false;
     97 		ind = 0;
     98 	} else {
     99 		if (in.p[0] == '-' || in.p[0] == '*' ||
    100 		    token.s[token.len - 1] == '/' ||
    101 		    (in.p[0] == '\n' && !opt.format_block_comments))
    102 			may_wrap = false;
    103 
    104 		if (com.len > 0)
    105 			output_line();
    106 		if (lab.len == 0 && code.len == 0) {
    107 			if (is_block_comment())
    108 				out.line_kind = lk_block_comment;
    109 			ind = (ps.ind_level - opt.unindent_displace)
    110 			    * opt.indent_size;
    111 			if (ind <= 0)
    112 				ind = opt.format_col1_comments ? 0 : 1;
    113 			line_length = opt.block_comment_max_line_length;
    114 			if (may_wrap && in.p[0] == '\n')
    115 				delim = true;
    116 			if (may_wrap && opt.comment_delimiter_on_blank_line)
    117 				delim = true;
    118 		} else {
    119 			int min_ind = code.len > 0
    120 			    ? ind_add(compute_code_indent(), code.s, code.len)
    121 			    : ind_add(compute_label_indent(), lab.s, lab.len);
    122 
    123 			ind = ps.line_has_decl || ps.ind_level == 0
    124 			    ? opt.decl_comment_column - 1
    125 			    : opt.comment_column - 1;
    126 			if (ind <= min_ind)
    127 				ind = next_tab(min_ind);
    128 			if (ind + 25 > line_length)
    129 				line_length = ind + 25;
    130 		}
    131 	}
    132 
    133 	if (!may_wrap) {
    134 		/* Find out how much indentation there was originally, because
    135 		 * that much will have to be ignored by output_line. */
    136 		size_t len = (size_t)(in.p - 2 - in.line.s);
    137 		ps.comment_shift = -ind_add(0, in.line.s, len);
    138 	} else {
    139 		ps.comment_shift = 0;
    140 		if (!(in.p[0] == '\t' && !ch_isblank(in.p[1])))
    141 			while (ch_isblank(in.p[0]))
    142 				in.p++;
    143 	}
    144 
    145 	ps.comment_ind = ind;
    146 	*p_may_wrap = may_wrap;
    147 	*p_delim = delim;
    148 	*p_line_length = line_length;
    149 }
    150 
    151 static void
    152 copy_comment_start(bool may_wrap, bool *delim, int line_length)
    153 {
    154 	ps.comment_cont = false;
    155 	buf_add_chars(&com, token.s, token.len);	// "/*" or "//"
    156 
    157 	if (may_wrap) {
    158 		if (!ch_isblank(in.p[0]))
    159 			com_add_char(' ');
    160 
    161 		if (*delim && fits_in_one_line(line_length))
    162 			*delim = false;
    163 		if (*delim) {
    164 			output_line();
    165 			com_add_star();
    166 		}
    167 	}
    168 }
    169 
    170 static void
    171 copy_comment_wrap_text(int line_length, ssize_t *last_blank)
    172 {
    173 	int ind = ind_add(ps.comment_ind, com.s, com.len);
    174 	for (;;) {
    175 		char ch = inp_next();
    176 		if (ch_isblank(ch))
    177 			*last_blank = (ssize_t)com.len;
    178 		com_add_char(ch);
    179 		ind++;
    180 		if (memchr("*\n\r\t", in.p[0], 5) != NULL)
    181 			break;
    182 		if (ind >= line_length && *last_blank != -1)
    183 			break;
    184 	}
    185 
    186 	if (ind <= line_length)
    187 		return;
    188 	if (ch_isspace(com.s[com.len - 1]))
    189 		return;
    190 
    191 	if (*last_blank == -1) {	/* only a single word in this line */
    192 		output_line();
    193 		com_add_star();
    194 		return;
    195 	}
    196 
    197 	// Move the overlong word to the next line.
    198 	const char *last_word = com.s + *last_blank + 1;
    199 	size_t last_word_len = com.len - (size_t)(*last_blank + 1);
    200 	com.len = (size_t)*last_blank;
    201 	buf_terminate(&com);
    202 	output_line();
    203 	com_add_star();
    204 
    205 	/* Assume that output_line and com_add_delim left the "unused" part of
    206 	 * the now truncated buffer beyond com.s + com.len as-is. */
    207 	memmove(com.s + com.len, last_word, last_word_len);
    208 	com.len += last_word_len;
    209 	buf_terminate(&com);
    210 	*last_blank = -1;
    211 }
    212 
    213 /* In a comment that is re-wrapped, handle a single newline character. */
    214 static bool
    215 copy_comment_wrap_newline(ssize_t *last_blank, bool seen_newline)
    216 {
    217 	*last_blank = -1;
    218 	if (seen_newline) {
    219 		if (com.len > 3) {
    220 			output_line();
    221 			com_add_star();
    222 		}
    223 		output_line();
    224 		com_add_star();
    225 	} else {
    226 		if (!(com.len > 0 && ch_isblank(com.s[com.len - 1])))
    227 			com_add_char(' ');
    228 		*last_blank = (int)com.len - 1;
    229 	}
    230 	in.token_end_line++;
    231 
    232 	/* flush any blanks and/or tabs at start of next line */
    233 	inp_skip();		/* '\n' */
    234 	while (ch_isblank(in.p[0]))
    235 		in.p++;
    236 	if (in.p[0] == '*' && in.p[1] == '/')
    237 		return false;
    238 	if (in.p[0] == '*') {
    239 		in.p++;
    240 		while (ch_isblank(in.p[0]))
    241 			in.p++;
    242 	}
    243 
    244 	return true;
    245 }
    246 
    247 static void
    248 copy_comment_wrap_finish(int line_length, bool delim)
    249 {
    250 	if (delim) {
    251 		if (com.len > 3)
    252 			output_line();
    253 		buf_clear(&com);
    254 	} else {
    255 		size_t len = com.len;
    256 		// XXX: This loop differs from the one below.
    257 		while (ch_isblank(com.s[len - 1]))
    258 			len--;
    259 		if (ind_add(ps.comment_ind, com.s, len) + 3 > line_length)
    260 			output_line();
    261 	}
    262 
    263 	while (com.len >= 2
    264 	    && ch_isblank(com.s[com.len - 1])
    265 	    && ch_isblank(com.s[com.len - 2]))
    266 		com.len--;
    267 	buf_terminate(&com);
    268 
    269 	in.p += 2;
    270 	if (com.len > 0 && ch_isblank(com.s[com.len - 1]))
    271 		buf_add_str(&com, "*/");
    272 	else
    273 		buf_add_str(&com, " */");
    274 }
    275 
    276 static void
    277 copy_comment_wrap(int line_length, bool delim)
    278 {
    279 	ssize_t last_blank = -1;	/* index of the last blank in 'com' */
    280 	bool seen_newline = false;
    281 
    282 	for (;;) {
    283 		if (in.p[0] == '\n') {
    284 			if (had_eof)
    285 				goto unterminated_comment;
    286 			if (!copy_comment_wrap_newline(&last_blank,
    287 				seen_newline))
    288 				break;
    289 			seen_newline = true;
    290 		} else if (in.p[0] == '*' && in.p[1] == '/')
    291 			break;
    292 		else {
    293 			copy_comment_wrap_text(line_length, &last_blank);
    294 			seen_newline = false;
    295 		}
    296 	}
    297 
    298 	copy_comment_wrap_finish(line_length, delim);
    299 	return;
    300 
    301 unterminated_comment:
    302 	in.token_start_line = in.token_end_line;
    303 	diag(1, "Unterminated comment");
    304 	output_line();
    305 }
    306 
    307 static void
    308 copy_comment_nowrap(void)
    309 {
    310 	char kind = token.s[token.len - 1];
    311 
    312 	for (;;) {
    313 		if (in.p[0] == '\n') {
    314 			if (kind == '/')
    315 				return;
    316 
    317 			if (had_eof) {
    318 				in.token_start_line = in.token_end_line;
    319 				diag(1, "Unterminated comment");
    320 				output_line();
    321 				return;
    322 			}
    323 
    324 			output_line();
    325 			in.token_end_line++;
    326 			inp_skip();
    327 			continue;
    328 		}
    329 
    330 		if (kind == '*' && in.p[0] == '*' && in.p[1] == '/') {
    331 			com_add_char(*in.p++);
    332 			com_add_char(*in.p++);
    333 			return;
    334 		}
    335 
    336 		com_add_char(*in.p++);
    337 	}
    338 }
    339 
    340 void
    341 process_comment(void)
    342 {
    343 	bool may_wrap, delim;
    344 	int line_length;
    345 
    346 	analyze_comment(&may_wrap, &delim, &line_length);
    347 	copy_comment_start(may_wrap, &delim, line_length);
    348 	if (may_wrap)
    349 		copy_comment_wrap(line_length, delim);
    350 	else
    351 		copy_comment_nowrap();
    352 }
    353