pr_comment.c revision 1.135 1 /* $NetBSD: pr_comment.c,v 1.135 2023/05/14 16:47:06 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.135 2023/05/14 16:47:06 rillig Exp $");
42
43 #include <string.h>
44
45 #include "indent.h"
46
47 static void
48 com_add_char(char ch)
49 {
50 if (1 >= com.limit - com.e)
51 buf_expand(&com, 1);
52 *com.e++ = ch;
53 }
54
55 static void
56 com_add_delim(void)
57 {
58 if (!opt.star_comment_cont)
59 return;
60 const char *delim = " * ";
61 buf_add_range(&com, delim, delim + 3);
62 }
63
64 static void
65 com_terminate(void)
66 {
67 if (1 >= com.limit - com.e)
68 buf_expand(&com, 1);
69 *com.e = '\0';
70 }
71
72 static bool
73 fits_in_one_line(int com_ind, int max_line_length)
74 {
75 for (const char *start = inp_p(), *p = start; *p != '\n'; p++) {
76 if (p[0] == '*' && p[1] == '/') {
77 int len = ind_add(com_ind + 3, start, p);
78 len += ch_isblank(p[-1]) ? 2 : 3;
79 return len <= max_line_length;
80 }
81 }
82 return false;
83 }
84
85 static void
86 analyze_comment(bool *p_may_wrap, bool *p_break_delim,
87 int *p_adj_max_line_length)
88 {
89 int adj_max_line_length; /* Adjusted max_line_length for comments that
90 * spill over the right margin */
91 bool break_delim = opt.comment_delimiter_on_blankline;
92 int com_ind;
93
94 adj_max_line_length = opt.max_line_length;
95 bool may_wrap = true;
96
97 if (ps.curr_col_1 && !opt.format_col1_comments) {
98 may_wrap = false;
99 break_delim = false;
100 com_ind = 0;
101
102 } else {
103 if (inp_peek() == '-' || inp_peek() == '*' ||
104 token.e[-1] == '/' ||
105 (inp_peek() == '\n' && !opt.format_block_comments)) {
106 may_wrap = false;
107 break_delim = false;
108 }
109
110 /*
111 * XXX: This condition looks suspicious since it ignores the case
112 * where the end of the previous comment is still in 'com'.
113 *
114 * See test lsym_comment.c, keyword 'analyze_comment'.
115 */
116 if (lab.s == lab.e && code.s == code.e) {
117 adj_max_line_length = opt.block_comment_max_line_length;
118 com_ind = (ps.ind_level - opt.unindent_displace) * opt.indent_size;
119 if (com_ind <= 0)
120 com_ind = opt.format_col1_comments ? 0 : 1;
121
122 } else {
123 break_delim = false;
124
125 int target_ind = code.s != code.e
126 ? ind_add(compute_code_indent(), code.s, code.e)
127 : ind_add(compute_label_indent(), lab.s, lab.e);
128
129 com_ind = ps.decl_on_line || ps.ind_level == 0
130 ? opt.decl_comment_column - 1 : opt.comment_column - 1;
131 if (com_ind <= target_ind)
132 com_ind = next_tab(target_ind);
133 if (com_ind + 25 > adj_max_line_length)
134 adj_max_line_length = com_ind + 25;
135 }
136 }
137
138 ps.com_ind = com_ind;
139
140 if (!may_wrap) {
141 /*
142 * Find out how much indentation there was originally, because that
143 * much will have to be ignored by output_complete_line.
144 */
145 ps.n_comment_delta = -ind_add(0, inp_line_start(), inp_p() - 2);
146 } else {
147 ps.n_comment_delta = 0;
148 while (ch_isblank(inp_peek()))
149 inp_skip();
150 }
151
152 ps.comment_delta = 0;
153 com_add_char('/');
154 com_add_char(token.e[-1]); /* either '*' or '/' */
155
156 /* TODO: Maybe preserve a single '\t' as well. */
157 if (inp_peek() != ' ' && may_wrap)
158 com_add_char(' ');
159
160 if (break_delim && fits_in_one_line(com_ind, adj_max_line_length))
161 break_delim = false;
162
163 if (break_delim) {
164 output_line();
165 com_add_delim();
166 }
167
168 *p_adj_max_line_length = adj_max_line_length;
169 *p_break_delim = break_delim;
170 *p_may_wrap = may_wrap;
171 }
172
173 /*
174 * Copy characters from 'inp' to 'com'. Try to keep comments from going over
175 * the maximum line length. To do that, remember where the last blank, tab, or
176 * newline was. When a line is filled, print up to the last blank and continue
177 * copying.
178 */
179 static void
180 copy_comment_wrap(int adj_max_line_length, bool break_delim)
181 {
182 ssize_t last_blank = -1; /* index of the last blank in com.mem */
183
184 for (;;) {
185 switch (inp_peek()) {
186 case '\f':
187 output_line_ff();
188 last_blank = -1;
189 com_add_delim();
190 inp_skip();
191 while (ch_isblank(inp_peek()))
192 inp_skip();
193 break;
194
195 case '\n':
196 if (had_eof) {
197 diag(1, "Unterminated comment");
198 output_line();
199 return;
200 }
201
202 last_blank = -1;
203 if (ps.next_col_1) {
204 if (com.s == com.e)
205 com_add_char(' '); /* force empty line of output */
206 if (com.e - com.s > 3) {
207 output_line();
208 com_add_delim();
209 }
210 output_line();
211 com_add_delim();
212
213 } else {
214 ps.next_col_1 = true;
215 if (!(com.e > com.s && ch_isblank(com.e[-1])))
216 com_add_char(' ');
217 last_blank = com.e - 1 - com.mem;
218 }
219 ++line_no;
220
221 bool skip_asterisk = true;
222 do { /* flush any blanks and/or tabs at start of
223 * next line */
224 inp_skip();
225 if (inp_peek() == '*' && skip_asterisk) {
226 skip_asterisk = false;
227 inp_skip();
228 if (inp_peek() == '/')
229 goto end_of_comment;
230 }
231 } while (ch_isblank(inp_peek()));
232
233 break; /* end of case for newline */
234
235 case '*':
236 inp_skip();
237 if (inp_peek() == '/') {
238 end_of_comment:
239 inp_skip();
240
241 if (break_delim) {
242 if (com.e - com.s > 3)
243 output_line();
244 else
245 com.e = com.s;
246 com_add_char(' ');
247 }
248
249 if (!(com.e > com.s && ch_isblank(com.e[-1])))
250 com_add_char(' ');
251 com_add_char('*');
252 com_add_char('/');
253 com_terminate();
254 return;
255
256 } else /* handle isolated '*' */
257 com_add_char('*');
258 break;
259
260 default: /* we have a random char */
261 ;
262 int now_len = ind_add(ps.com_ind, com.s, com.e);
263 for (;;) {
264 char ch = inp_next();
265 if (ch_isblank(ch))
266 last_blank = com.e - com.mem;
267 com_add_char(ch);
268 now_len++;
269 if (memchr("*\n\r\b\t", inp_peek(), 6) != NULL)
270 break;
271 if (now_len >= adj_max_line_length && last_blank != -1)
272 break;
273 }
274
275 ps.next_col_1 = false;
276
277 if (now_len <= adj_max_line_length)
278 break;
279 if (ch_isspace(com.e[-1]))
280 break;
281
282 if (last_blank == -1) { /* only a single word in this line */
283 output_line();
284 com_add_delim();
285 break;
286 }
287
288 const char *last_word_s = com.mem + last_blank + 1;
289 size_t last_word_len = (size_t)(com.e - last_word_s);
290 com.e = com.mem + last_blank;
291 output_line();
292 com_add_delim();
293
294 memcpy(com.e, last_word_s, last_word_len);
295 com.e += last_word_len;
296 last_blank = -1;
297 }
298 }
299 }
300
301 static void
302 copy_comment_nowrap(void)
303 {
304 for (;;) {
305 if (inp_peek() == '\n') {
306 if (token.e[-1] == '/') {
307 com_terminate();
308 return;
309 }
310
311 if (had_eof) {
312 diag(1, "Unterminated comment");
313 output_line();
314 return;
315 }
316
317 if (com.s == com.e)
318 com_add_char(' '); /* force output of an empty line */
319 output_line();
320 ++line_no;
321 inp_skip();
322 continue;
323 }
324
325 com_add_char(inp_next());
326 if (com.e[-2] == '*' && com.e[-1] == '/' && token.e[-1] == '*') {
327 com_terminate();
328 return;
329 }
330 }
331 }
332
333 /*
334 * Scan, reformat and output a single comment, which is either a block comment
335 * starting with '/' '*' or an end-of-line comment starting with '//'.
336 */
337 void
338 process_comment(void)
339 {
340 int adj_max_line_length;
341 bool may_wrap, break_delim;
342
343 analyze_comment(&may_wrap, &break_delim, &adj_max_line_length);
344 if (may_wrap)
345 copy_comment_wrap(adj_max_line_length, break_delim);
346 else
347 copy_comment_nowrap();
348 }
349