c++: Prevent overwriting arguments when merging duplicates [PR112588]
[official-gcc.git] / gcc / text-art / style.cc
blobe74efe23014c5b4056cd38c72ba118d9cb0c7646
1 /* Classes for styling text cells (color, URLs).
2 Copyright (C) 2023-2024 Free Software Foundation, Inc.
3 Contributed by David Malcolm <dmalcolm@redhat.com>.
5 This file is part of GCC.
7 GCC is free software; you can redistribute it and/or modify it under
8 the terms of the GNU General Public License as published by the Free
9 Software Foundation; either version 3, or (at your option) any later
10 version.
12 GCC is distributed in the hope that it will be useful, but WITHOUT ANY
13 WARRANTY; without even the implied warranty of MERCHANTABILITY or
14 FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
15 for more details.
17 You should have received a copy of the GNU General Public License
18 along with GCC; see the file COPYING3. If not see
19 <http://www.gnu.org/licenses/>. */
21 #include "config.h"
22 #define INCLUDE_ALGORITHM
23 #define INCLUDE_MEMORY
24 #define INCLUDE_VECTOR
25 #include "system.h"
26 #include "coretypes.h"
27 #include "make-unique.h"
28 #include "pretty-print.h"
29 #include "intl.h"
30 #include "selftest.h"
31 #include "text-art/selftests.h"
32 #include "text-art/types.h"
33 #include "color-macros.h"
35 using namespace text_art;
37 /* class text_art::style. */
39 style &
40 style::set_style_url (const char *url)
42 m_url.clear ();
43 while (*url)
44 m_url.push_back (*(url++));
45 return *this;
48 /* class text_art::style::color. */
50 bool
51 style::color::operator== (const style::color &other) const
53 if (m_kind != other.m_kind)
54 return false;
55 switch (m_kind)
57 default:
58 gcc_unreachable ();
59 case kind::NAMED:
60 return (u.m_named.m_name == other.u.m_named.m_name
61 && u.m_named.m_bright == other.u.m_named.m_bright);
62 case kind::BITS_8:
63 return u.m_8bit == other.u.m_8bit;
64 case kind::BITS_24:
65 return (u.m_24bit.r == other.u.m_24bit.r
66 && u.m_24bit.g == other.u.m_24bit.g
67 && u.m_24bit.b == other.u.m_24bit.b);
71 static void
72 ensure_separator (pretty_printer *pp, bool &need_separator)
74 if (need_separator)
75 pp_string (pp, COLOR_SEPARATOR);
76 need_separator = true;
79 void
80 style::color::print_sgr (pretty_printer *pp,
81 bool fg,
82 bool &need_separator) const
84 switch (m_kind)
86 default:
87 gcc_unreachable ();
88 case kind::NAMED:
90 static const char * const fg_normal[] = {"", // reset, for DEFAULT
91 COLOR_FG_BLACK,
92 COLOR_FG_RED,
93 COLOR_FG_GREEN,
94 COLOR_FG_YELLOW,
95 COLOR_FG_BLUE,
96 COLOR_FG_MAGENTA,
97 COLOR_FG_CYAN,
98 COLOR_FG_WHITE};
99 static const char * const fg_bright[] = {"", // reset, for DEFAULT
100 COLOR_FG_BRIGHT_BLACK,
101 COLOR_FG_BRIGHT_RED,
102 COLOR_FG_BRIGHT_GREEN,
103 COLOR_FG_BRIGHT_YELLOW,
104 COLOR_FG_BRIGHT_BLUE,
105 COLOR_FG_BRIGHT_MAGENTA,
106 COLOR_FG_BRIGHT_CYAN,
107 COLOR_FG_BRIGHT_WHITE};
108 static const char * const bg_normal[] = {"", // reset, for DEFAULT
109 COLOR_BG_BLACK,
110 COLOR_BG_RED,
111 COLOR_BG_GREEN,
112 COLOR_BG_YELLOW,
113 COLOR_BG_BLUE,
114 COLOR_BG_MAGENTA,
115 COLOR_BG_CYAN,
116 COLOR_BG_WHITE};
117 static const char * const bg_bright[] = {"", // reset, for DEFAULT
118 COLOR_BG_BRIGHT_BLACK,
119 COLOR_BG_BRIGHT_RED,
120 COLOR_BG_BRIGHT_GREEN,
121 COLOR_BG_BRIGHT_YELLOW,
122 COLOR_BG_BRIGHT_BLUE,
123 COLOR_BG_BRIGHT_MAGENTA,
124 COLOR_BG_BRIGHT_CYAN,
125 COLOR_BG_BRIGHT_WHITE};
126 STATIC_ASSERT (ARRAY_SIZE (fg_normal) == ARRAY_SIZE (fg_bright));
127 STATIC_ASSERT (ARRAY_SIZE (fg_normal) == ARRAY_SIZE (bg_normal));
128 STATIC_ASSERT (ARRAY_SIZE (fg_normal) == ARRAY_SIZE (bg_bright));
129 gcc_assert ((size_t)u.m_named.m_name < ARRAY_SIZE (fg_normal));
130 const char *const *arr;
131 if (fg)
132 arr = u.m_named.m_bright ? fg_bright : fg_normal;
133 else
134 arr = u.m_named.m_bright ? bg_bright : bg_normal;
135 const char *str = arr[(size_t)u.m_named.m_name];
136 if (strlen (str) > 0)
138 ensure_separator (pp, need_separator);
139 pp_string (pp, str);
142 break;
143 case kind::BITS_8:
145 ensure_separator (pp, need_separator);
146 if (fg)
147 pp_string (pp, "38");
148 else
149 pp_string (pp, "48");
150 pp_printf (pp, ";5;%i", (int)u.m_8bit);
152 break;
153 case kind::BITS_24:
155 ensure_separator (pp, need_separator);
156 if (fg)
157 pp_string (pp, "38");
158 else
159 pp_string (pp, "48");
160 pp_printf (pp, ";2;%i;%i;%i",
161 (int)u.m_24bit.r,
162 (int)u.m_24bit.g,
163 (int)u.m_24bit.b);
165 break;
169 /* class text_art::style. */
171 /* See https://www.ecma-international.org/wp-content/uploads/ECMA-48_5th_edition_june_1991.pdf
172 GRCM - GRAPHIC RENDITION COMBINATION MODE can be "REPLACING" or
173 "CUMULATIVE", which affects whether we need to respecify all attributes
174 at each SGR, or can accumulate them. Looks like we can't rely on the value
175 of this, so we have to emit a single SGR for all changes, with a "0" reset
176 at the front, forcing it to be effectively replacing. */
178 void
179 style::print_changes (pretty_printer *pp,
180 const style &old_style,
181 const style &new_style)
183 if (pp_show_color (pp))
185 bool needs_sgr = ((old_style.m_bold != new_style.m_bold)
186 || (old_style.m_underscore != new_style.m_underscore)
187 || (old_style.m_blink != new_style.m_blink)
188 || (old_style.m_fg_color != new_style.m_fg_color)
189 || (old_style.m_bg_color != new_style.m_bg_color));
190 if (needs_sgr)
192 bool emit_reset = (old_style.m_bold
193 || new_style.m_bold
194 || old_style.m_underscore
195 || new_style.m_underscore
196 || old_style.m_blink
197 || new_style.m_blink);
198 bool need_separator = false;
200 pp_string (pp, SGR_START);
201 if (emit_reset)
203 pp_string (pp, COLOR_NONE);
204 need_separator = true;
206 if (new_style.m_bold)
208 gcc_assert (emit_reset);
209 ensure_separator (pp, need_separator);
210 pp_string (pp, COLOR_BOLD);
212 if (new_style.m_underscore)
214 gcc_assert (emit_reset);
215 ensure_separator (pp, need_separator);
216 pp_string (pp, COLOR_UNDERSCORE);
218 if (new_style.m_blink)
220 gcc_assert (emit_reset);
221 ensure_separator (pp, need_separator);
222 pp_string (pp, COLOR_BLINK);
224 new_style.m_fg_color.print_sgr (pp, true, need_separator);
225 new_style.m_bg_color.print_sgr (pp, false, need_separator);
226 pp_string (pp, SGR_END);
230 if (old_style.m_url != new_style.m_url)
232 if (!old_style.m_url.empty ())
233 pp_end_url (pp);
234 if (pp->url_format != URL_FORMAT_NONE
235 && !new_style.m_url.empty ())
237 /* Adapted from pp_begin_url, but encoding the
238 chars to UTF-8 on the fly, rather than converting
239 to a buffer. */
240 pp_string (pp, "\33]8;;");
241 for (auto ch : new_style.m_url)
242 pp_unicode_character (pp, ch);
243 switch (pp->url_format)
245 default:
246 case URL_FORMAT_NONE:
247 gcc_unreachable ();
248 case URL_FORMAT_ST:
249 pp_string (pp, "\33\\");
250 break;
251 case URL_FORMAT_BEL:
252 pp_string (pp, "\a");
253 break;
259 /* class text_art::style_manager. */
261 style_manager::style_manager ()
263 // index 0 will be the default style
264 m_styles.push_back (style ());
267 style::id_t
268 style_manager::get_or_create_id (const style &s)
270 // For now, linear search
271 std::vector<style>::iterator existing
272 (std::find (m_styles.begin (), m_styles.end (), s));
274 /* If found, return index of slot. */
275 if (existing != m_styles.end ())
276 return std::distance (m_styles.begin (), existing);
278 /* Not found. */
280 /* styled_str uses 7 bits for style information, so we can only support
281 up to 128 different style combinations.
282 Gracefully fail by turning off styling when this limit is reached. */
283 if (m_styles.size () >= 127)
284 return 0;
286 m_styles.push_back (s);
287 return m_styles.size () - 1;
290 void
291 style_manager::print_any_style_changes (pretty_printer *pp,
292 style::id_t old_id,
293 style::id_t new_id) const
295 gcc_assert (pp);
296 if (old_id == new_id)
297 return;
299 const style &old_style = m_styles[old_id];
300 const style &new_style = m_styles[new_id];
301 gcc_assert (!(old_style == new_style));
302 style::print_changes (pp, old_style, new_style);
305 #if CHECKING_P
307 namespace selftest {
309 void
310 assert_style_change_streq (const location &loc,
311 const style &old_style,
312 const style &new_style,
313 const char *expected_str)
315 pretty_printer pp;
316 pp_show_color (&pp) = true;
317 style::print_changes (&pp, old_style, new_style);
318 ASSERT_STREQ_AT (loc, pp_formatted_text (&pp), expected_str);
321 #define ASSERT_STYLE_CHANGE_STREQ(OLD_STYLE, NEW_STYLE, EXPECTED_STR) \
322 SELFTEST_BEGIN_STMT \
323 assert_style_change_streq ((SELFTEST_LOCATION), \
324 (OLD_STYLE), \
325 (NEW_STYLE), \
326 (EXPECTED_STR)); \
327 SELFTEST_END_STMT
329 static void
330 test_bold ()
332 style_manager sm;
333 ASSERT_EQ (sm.get_num_styles (), 1);
335 style plain;
336 ASSERT_EQ (sm.get_or_create_id (plain), 0);
337 ASSERT_EQ (sm.get_num_styles (), 1);
339 style bold;
340 bold.m_bold = true;
342 ASSERT_EQ (sm.get_or_create_id (bold), 1);
343 ASSERT_EQ (sm.get_num_styles (), 2);
344 ASSERT_EQ (sm.get_or_create_id (bold), 1);
345 ASSERT_EQ (sm.get_num_styles (), 2);
347 ASSERT_STYLE_CHANGE_STREQ (plain, bold, "\33[00;01m\33[K");
348 ASSERT_STYLE_CHANGE_STREQ (bold, plain, "\33[00m\33[K");
351 static void
352 test_underscore ()
354 style_manager sm;
355 ASSERT_EQ (sm.get_num_styles (), 1);
357 style plain;
358 ASSERT_EQ (sm.get_or_create_id (plain), 0);
359 ASSERT_EQ (sm.get_num_styles (), 1);
361 style underscore;
362 underscore.m_underscore = true;
364 ASSERT_EQ (sm.get_or_create_id (underscore), 1);
365 ASSERT_EQ (sm.get_num_styles (), 2);
366 ASSERT_EQ (sm.get_or_create_id (underscore), 1);
367 ASSERT_EQ (sm.get_num_styles (), 2);
369 ASSERT_STYLE_CHANGE_STREQ (plain, underscore, "\33[00;04m\33[K");
370 ASSERT_STYLE_CHANGE_STREQ (underscore, plain, "\33[00m\33[K");
373 static void
374 test_blink ()
376 style_manager sm;
377 ASSERT_EQ (sm.get_num_styles (), 1);
379 style plain;
380 ASSERT_EQ (sm.get_or_create_id (plain), 0);
381 ASSERT_EQ (sm.get_num_styles (), 1);
383 style blink;
384 blink.m_blink = true;
386 ASSERT_EQ (sm.get_or_create_id (blink), 1);
387 ASSERT_EQ (sm.get_num_styles (), 2);
388 ASSERT_EQ (sm.get_or_create_id (blink), 1);
389 ASSERT_EQ (sm.get_num_styles (), 2);
391 ASSERT_STYLE_CHANGE_STREQ (plain, blink, "\33[00;05m\33[K");
392 ASSERT_STYLE_CHANGE_STREQ (blink, plain, "\33[00m\33[K");
395 #define ASSERT_NAMED_COL_STREQ(NAMED_COLOR, FG, BRIGHT, EXPECTED_STR) \
396 SELFTEST_BEGIN_STMT \
398 style plain; \
399 style s; \
400 if (FG) \
401 s.m_fg_color = style::color ((NAMED_COLOR), (BRIGHT)); \
402 else \
403 s.m_bg_color = style::color ((NAMED_COLOR), (BRIGHT)); \
404 assert_style_change_streq ((SELFTEST_LOCATION), \
405 plain, \
406 s, \
407 (EXPECTED_STR)); \
409 SELFTEST_END_STMT
411 static void
412 test_named_colors ()
414 /* Foreground colors. */
416 const bool fg = true;
418 const bool bright = false;
419 ASSERT_NAMED_COL_STREQ (style::named_color::DEFAULT, fg, bright, "");
420 ASSERT_NAMED_COL_STREQ (style::named_color::BLACK, fg, bright,
421 "\e[30m\e[K");
422 ASSERT_NAMED_COL_STREQ (style::named_color::RED, fg, bright,
423 "\e[31m\e[K");
424 ASSERT_NAMED_COL_STREQ (style::named_color::GREEN, fg, bright,
425 "\e[32m\e[K");
426 ASSERT_NAMED_COL_STREQ (style::named_color::YELLOW, fg, bright,
427 "\e[33m\e[K");
428 ASSERT_NAMED_COL_STREQ (style::named_color::BLUE, fg, bright,
429 "\e[34m\e[K");
430 ASSERT_NAMED_COL_STREQ (style::named_color::MAGENTA, fg, bright,
431 "\e[35m\e[K");
432 ASSERT_NAMED_COL_STREQ (style::named_color::CYAN, fg, bright,
433 "\e[36m\e[K");
434 ASSERT_NAMED_COL_STREQ (style::named_color::WHITE, fg, bright,
435 "\e[37m\e[K");
438 const bool bright = true;
439 ASSERT_NAMED_COL_STREQ (style::named_color::DEFAULT, fg, bright,
440 "\e[m\e[K");
441 ASSERT_NAMED_COL_STREQ (style::named_color::BLACK, fg, bright,
442 "\e[90m\e[K");
443 ASSERT_NAMED_COL_STREQ (style::named_color::RED, fg, bright,
444 "\e[91m\e[K");
445 ASSERT_NAMED_COL_STREQ (style::named_color::GREEN, fg, bright,
446 "\e[92m\e[K");
447 ASSERT_NAMED_COL_STREQ (style::named_color::YELLOW, fg, bright,
448 "\e[93m\e[K");
449 ASSERT_NAMED_COL_STREQ (style::named_color::BLUE, fg, bright,
450 "\e[94m\e[K");
451 ASSERT_NAMED_COL_STREQ (style::named_color::MAGENTA, fg, bright,
452 "\e[95m\e[K");
453 ASSERT_NAMED_COL_STREQ (style::named_color::CYAN, fg, bright,
454 "\e[96m\e[K");
455 ASSERT_NAMED_COL_STREQ (style::named_color::WHITE, fg, bright,
456 "\e[97m\e[K");
460 /* Background colors. */
462 const bool fg = false;
464 const bool bright = false;
465 ASSERT_NAMED_COL_STREQ (style::named_color::DEFAULT, fg, bright, "");
466 ASSERT_NAMED_COL_STREQ (style::named_color::BLACK, fg, bright,
467 "\e[40m\e[K");
468 ASSERT_NAMED_COL_STREQ (style::named_color::RED, fg, bright,
469 "\e[41m\e[K");
470 ASSERT_NAMED_COL_STREQ (style::named_color::GREEN, fg, bright,
471 "\e[42m\e[K");
472 ASSERT_NAMED_COL_STREQ (style::named_color::YELLOW, fg, bright,
473 "\e[43m\e[K");
474 ASSERT_NAMED_COL_STREQ (style::named_color::BLUE, fg, bright,
475 "\e[44m\e[K");
476 ASSERT_NAMED_COL_STREQ (style::named_color::MAGENTA, fg, bright,
477 "\e[45m\e[K");
478 ASSERT_NAMED_COL_STREQ (style::named_color::CYAN, fg, bright,
479 "\e[46m\e[K");
480 ASSERT_NAMED_COL_STREQ (style::named_color::WHITE, fg, bright,
481 "\e[47m\e[K");
484 const bool bright = true;
485 ASSERT_NAMED_COL_STREQ (style::named_color::DEFAULT, fg, bright,
486 "\e[m\e[K");
487 ASSERT_NAMED_COL_STREQ (style::named_color::BLACK, fg, bright,
488 "\e[100m\e[K");
489 ASSERT_NAMED_COL_STREQ (style::named_color::RED, fg, bright,
490 "\e[101m\e[K");
491 ASSERT_NAMED_COL_STREQ (style::named_color::GREEN, fg, bright,
492 "\e[102m\e[K");
493 ASSERT_NAMED_COL_STREQ (style::named_color::YELLOW, fg, bright,
494 "\e[103m\e[K");
495 ASSERT_NAMED_COL_STREQ (style::named_color::BLUE, fg, bright,
496 "\e[104m\e[K");
497 ASSERT_NAMED_COL_STREQ (style::named_color::MAGENTA, fg, bright,
498 "\e[105m\e[K");
499 ASSERT_NAMED_COL_STREQ (style::named_color::CYAN, fg, bright,
500 "\e[106m\e[K");
501 ASSERT_NAMED_COL_STREQ (style::named_color::WHITE, fg, bright,
502 "\e[107m\e[K");
507 #define ASSERT_8_BIT_COL_STREQ(COL_VAL, FG, EXPECTED_STR) \
508 SELFTEST_BEGIN_STMT \
510 style plain; \
511 style s; \
512 if (FG) \
513 s.m_fg_color = style::color (COL_VAL); \
514 else \
515 s.m_bg_color = style::color (COL_VAL); \
516 assert_style_change_streq ((SELFTEST_LOCATION), \
517 plain, \
518 s, \
519 (EXPECTED_STR)); \
521 SELFTEST_END_STMT
523 static void
524 test_8_bit_colors ()
526 /* Foreground colors. */
528 const bool fg = true;
529 /* 0-15: standard and high-intensity standard colors. */
530 ASSERT_8_BIT_COL_STREQ (0, fg, "\e[38;5;0m\e[K");
531 ASSERT_8_BIT_COL_STREQ (15, fg, "\e[38;5;15m\e[K");
532 /* 16-231: 6x6x6 color cube. */
533 ASSERT_8_BIT_COL_STREQ (16, fg, "\e[38;5;16m\e[K");
534 ASSERT_8_BIT_COL_STREQ (231, fg, "\e[38;5;231m\e[K");
535 /* 232-255: grayscale. */
536 ASSERT_8_BIT_COL_STREQ (232, fg, "\e[38;5;232m\e[K");
537 ASSERT_8_BIT_COL_STREQ (255, fg, "\e[38;5;255m\e[K");
539 /* Background colors. */
541 const bool fg = false;
542 /* 0-15: standard and high-intensity standard colors. */
543 ASSERT_8_BIT_COL_STREQ (0, fg, "\e[48;5;0m\e[K");
544 ASSERT_8_BIT_COL_STREQ (15, fg, "\e[48;5;15m\e[K");
545 /* 16-231: 6x6x6 color cube. */
546 ASSERT_8_BIT_COL_STREQ (16, fg, "\e[48;5;16m\e[K");
547 ASSERT_8_BIT_COL_STREQ (231, fg, "\e[48;5;231m\e[K");
548 /* 232-255: grayscale. */
549 ASSERT_8_BIT_COL_STREQ (232, fg, "\e[48;5;232m\e[K");
550 ASSERT_8_BIT_COL_STREQ (255, fg, "\e[48;5;255m\e[K");
554 #define ASSERT_24_BIT_COL_STREQ(R, G, B, FG, EXPECTED_STR) \
555 SELFTEST_BEGIN_STMT \
557 style plain; \
558 style s; \
559 if (FG) \
560 s.m_fg_color = style::color ((R), (G), (B)); \
561 else \
562 s.m_bg_color = style::color ((R), (G), (B)); \
563 assert_style_change_streq ((SELFTEST_LOCATION), \
564 plain, \
565 s, \
566 (EXPECTED_STR)); \
568 SELFTEST_END_STMT
570 static void
571 test_24_bit_colors ()
573 /* Foreground colors. */
575 const bool fg = true;
576 // #F3FAF2:
577 ASSERT_24_BIT_COL_STREQ (0xf3, 0xfa, 0xf2, fg,
578 "\e[38;2;243;250;242m\e[K");
580 /* Background colors. */
582 const bool fg = false;
583 // #FDF7E7
584 ASSERT_24_BIT_COL_STREQ (0xfd, 0xf7, 0xe7, fg,
585 "\e[48;2;253;247;231m\e[K");
589 static void
590 test_style_combinations ()
592 style_manager sm;
593 ASSERT_EQ (sm.get_num_styles (), 1);
595 style plain;
596 ASSERT_EQ (sm.get_or_create_id (plain), 0);
597 ASSERT_EQ (sm.get_num_styles (), 1);
599 style bold;
600 bold.m_bold = true;
602 ASSERT_EQ (sm.get_or_create_id (bold), 1);
603 ASSERT_EQ (sm.get_num_styles (), 2);
604 ASSERT_EQ (sm.get_or_create_id (bold), 1);
605 ASSERT_EQ (sm.get_num_styles (), 2);
607 style magenta_on_blue;
608 magenta_on_blue.m_fg_color = style::named_color::MAGENTA;
609 magenta_on_blue.m_bg_color = style::named_color::BLUE;
610 ASSERT_EQ (sm.get_or_create_id (magenta_on_blue), 2);
611 ASSERT_EQ (sm.get_num_styles (), 3);
612 ASSERT_EQ (sm.get_or_create_id (magenta_on_blue), 2);
613 ASSERT_EQ (sm.get_num_styles (), 3);
616 /* Run all selftests in this file. */
618 void
619 text_art_style_cc_tests ()
621 test_bold ();
622 test_underscore ();
623 test_blink ();
624 test_named_colors ();
625 test_8_bit_colors ();
626 test_24_bit_colors ();
627 test_style_combinations ();
630 } // namespace selftest
633 #endif /* #if CHECKING_P */