Message ID | 20230531180630.3127108-3-dmalcolm@redhat.com |
---|---|
State | New |
Headers | show |
Series | Add diagram support to gcc diagnostics | expand |
Hi David, On 31/05/2023 14:06, David Malcolm via Gcc-patches wrote: > Existing text output in GCC has to be implemented by writing > sequentially to a pretty_printer instance. This makes it > hard to implement some kinds of diagnostic output (see e.g. > diagnostic-show-locus.cc). > > This patch adds more flexible ways of creating text output: > - a canvas class, which can be "painted" to via random-access (rather > that sequentially) > - a table class for 2D grid layout, supporting items that span > multiple rows/columns > - a widget class for organizing diagrams hierarchically. > > The patch also expands GCC's diagnostics subsystem so that diagnostics > can have "text art" diagrams - think ASCII art, but potentially > including some Unicode characters, such as box-drawing chars. > > The new code is in a new "gcc/text-art" subdirectory and "text_art" > namespace. It looks like this patch breaks bootstrap on Darwin. I tried a bootstrap on x86_64-apple-darwin and got errors building selftest-run-tests.cc: In file included from /Users/alecop01/toolchain/src/gcc/gcc/selftest-run-tests.cc:31: In file included from /Users/alecop01/toolchain/src/gcc/gcc/text-art/selftests.h:25: In file included from /Users/alecop01/toolchain/src/gcc/gcc/text-art/types.h:26: In file included from /Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX.sdk/usr/include/c++/v1/vector:276: In file included from /Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX.sdk/usr/include/c++/v1/__bit_reference:15: In file included from /Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX.sdk/usr/include/c++/v1/algorithm:653: In file included from /Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX.sdk/usr/include/c++/v1/memory:670: /Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX.sdk/usr/include/c++/v1/typeinfo:377:5: error: no member named 'fancy_abort' in namespace 'std::__1'; did you mean simply 'fancy_abort'? _VSTD::abort(); ^~~~~~~ /Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX.sdk/usr/include/c++/v1/__config:856:15: note: expanded from macro '_VSTD' #define _VSTD std::_LIBCPP_ABI_NAMESPACE ^ /Users/alecop01/toolchain/src/gcc/gcc/system.h:811:13: note: 'fancy_abort' declared here extern void fancy_abort (const char *, int, const char *) ^ Please could you take a look? Thanks, Alex > > The patch adds a new "-fdiagnostics-text-art-charset=VAL" option, with > values: > - "none": don't emit diagrams (added to -fdiagnostics-plain-output) > - "ascii": use pure ASCII in diagrams > - "unicode": allow for conservative use of unicode drawing characters > (such as box-drawing characters). > - "emoji" (the default): as "unicode", but potentially allow for > conservative use of emoji in the output (such as U+26A0 WARNING SIGN). > I made it possible to disable emoji separately from unicode as I believe > there's a generation gap in acceptance of these characters (some older > programmers have a visceral reaction against them, whereas younger > programmers may have no problem with them). > > Diagrams are emitted to stderr by default. With SARIF output they are > captured as a location in "relatedLocations", with the diagram as a > code block in Markdown within a "markdown" property of a message. > > This patch doesn't add any such diagram usage to GCC, saving that for > followups, apart from adding a plugin to the test suite to exercise the > functionality. > > One issue is that in various places in the seltftests I've embedded > UTF-8 encoded characters in the source code. Is that acceptable, or > do I need to e.g. move those tests to DejaGnu? > > contrib/ChangeLog: > * unicode/gen-box-drawing-chars.py: New file. > * unicode/gen-combining-chars.py: New file. > * unicode/gen-printable-chars.py: New file. > > gcc/ChangeLog: > * Makefile.in (OBJS-libcommon): Add text-art/box-drawing.o, > text-art/canvas.o, text-art/ruler.o, text-art/selftests.o, > text-art/style.o, text-art/styled-string.o, text-art/table.o, > text-art/theme.o, and text-art/widget.o. > * color-macros.h (COLOR_FG_BRIGHT_BLACK): New. > (COLOR_FG_BRIGHT_RED): New. > (COLOR_FG_BRIGHT_GREEN): New. > (COLOR_FG_BRIGHT_YELLOW): New. > (COLOR_FG_BRIGHT_BLUE): New. > (COLOR_FG_BRIGHT_MAGENTA): New. > (COLOR_FG_BRIGHT_CYAN): New. > (COLOR_FG_BRIGHT_WHITE): New. > (COLOR_BG_BRIGHT_BLACK): New. > (COLOR_BG_BRIGHT_RED): New. > (COLOR_BG_BRIGHT_GREEN): New. > (COLOR_BG_BRIGHT_YELLOW): New. > (COLOR_BG_BRIGHT_BLUE): New. > (COLOR_BG_BRIGHT_MAGENTA): New. > (COLOR_BG_BRIGHT_CYAN): New. > (COLOR_BG_BRIGHT_WHITE): New. > * common.opt (fdiagnostics-text-art-charset=): New option. > (diagnostic-text-art.h): New SourceInclude. > (diagnostic_text_art_charset) New Enum and EnumValues. > * configure: Regenerate. > * configure.ac (gccdepdir): Add text-art to loop. > * diagnostic-diagram.h: New file. > * diagnostic-format-json.cc (json_emit_diagram): New. > (diagnostic_output_format_init_json): Wire it up to > context->m_diagrams.m_emission_cb. > * diagnostic-format-sarif.cc: Include "diagnostic-diagram.h" and > "text-art/canvas.h". > (sarif_result::on_nested_diagnostic): Move code to... > (sarif_result::add_related_location): ...this new function. > (sarif_result::on_diagram): New. > (sarif_builder::emit_diagram): New. > (sarif_builder::make_message_object_for_diagram): New. > (sarif_emit_diagram): New. > (diagnostic_output_format_init_sarif): Set > context->m_diagrams.m_emission_cb to sarif_emit_diagram. > * diagnostic-text-art.h: New file. > * diagnostic.cc: Include "diagnostic-text-art.h", > "diagnostic-diagram.h", and "text-art/theme.h". > (diagnostic_initialize): Initialize context->m_diagrams and > call diagnostics_text_art_charset_init. > (diagnostic_finish): Clean up context->m_diagrams.m_theme. > (diagnostic_emit_diagram): New. > (diagnostics_text_art_charset_init): New. > * diagnostic.h (text_art::theme): New forward decl. > (class diagnostic_diagram): Likewise. > (diagnostic_context::m_diagrams): New field. > (diagnostic_emit_diagram): New decl. > * doc/invoke.texi (Diagnostic Message Formatting Options): Add > -fdiagnostics-text-art-charset=. > (-fdiagnostics-plain-output): Add > -fdiagnostics-text-art-charset=none. > * gcc.cc: Include "diagnostic-text-art.h". > (driver_handle_option): Handle OPT_fdiagnostics_text_art_charset_. > * opts-common.cc (decode_cmdline_options_to_array): Add > "-fdiagnostics-text-art-charset=none" to expanded_args for > -fdiagnostics-plain-output. > * opts.cc: Include "diagnostic-text-art.h". > (common_handle_option): Handle OPT_fdiagnostics_text_art_charset_. > * pretty-print.cc (pp_unicode_character): New. > * pretty-print.h (pp_unicode_character): New decl. > * selftest-run-tests.cc: Include "text-art/selftests.h". > (selftest::run_tests): Call text_art_tests. > * text-art/box-drawing-chars.inc: New file, generated by > contrib/unicode/gen-box-drawing-chars.py. > * text-art/box-drawing.cc: New file. > * text-art/box-drawing.h: New file. > * text-art/canvas.cc: New file. > * text-art/canvas.h: New file. > * text-art/ruler.cc: New file. > * text-art/ruler.h: New file. > * text-art/selftests.cc: New file. > * text-art/selftests.h: New file. > * text-art/style.cc: New file. > * text-art/styled-string.cc: New file. > * text-art/table.cc: New file. > * text-art/table.h: New file. > * text-art/theme.cc: New file. > * text-art/theme.h: New file. > * text-art/types.h: New file. > * text-art/widget.cc: New file. > * text-art/widget.h: New file. > > gcc/testsuite/ChangeLog: > * gcc.dg/plugin/diagnostic-test-text-art-ascii-bw.c: New test. > * gcc.dg/plugin/diagnostic-test-text-art-ascii-color.c: New test. > * gcc.dg/plugin/diagnostic-test-text-art-none.c: New test. > * gcc.dg/plugin/diagnostic-test-text-art-unicode-bw.c: New test. > * gcc.dg/plugin/diagnostic-test-text-art-unicode-color.c: New test. > * gcc.dg/plugin/diagnostic_plugin_test_text_art.c: New test plugin. > * gcc.dg/plugin/plugin.exp (plugin_test_list): Add them. > > libcpp/ChangeLog: > * charset.cc (get_cppchar_property): New function template, based > on... > (cpp_wcwidth): ...this function. Rework to use the above. > Include "combining-chars.inc". > (cpp_is_combining_char): New function > Include "printable-chars.inc". > (cpp_is_printable_char): New function > * combining-chars.inc: New file, generated by > contrib/unicode/gen-combining-chars.py. > * include/cpplib.h (cpp_is_combining_char): New function decl. > (cpp_is_printable_char): New function decl. > * printable-chars.inc: New file, generated by > contrib/unicode/gen-printable-chars.py. > --- > contrib/unicode/gen-box-drawing-chars.py | 94 ++ > contrib/unicode/gen-combining-chars.py | 75 + > contrib/unicode/gen-printable-chars.py | 77 + > gcc/Makefile.in | 11 +- > gcc/color-macros.h | 16 + > gcc/common.opt | 23 + > gcc/configure | 2 +- > gcc/configure.ac | 2 +- > gcc/diagnostic-diagram.h | 51 + > gcc/diagnostic-format-json.cc | 10 + > gcc/diagnostic-format-sarif.cc | 106 +- > gcc/diagnostic-text-art.h | 49 + > gcc/diagnostic.cc | 72 + > gcc/diagnostic.h | 21 + > gcc/doc/invoke.texi | 25 +- > gcc/gcc.cc | 6 + > gcc/opts-common.cc | 1 + > gcc/opts.cc | 6 + > gcc/pretty-print.cc | 29 + > gcc/pretty-print.h | 1 + > gcc/selftest-run-tests.cc | 3 + > .../diagnostic-test-text-art-ascii-bw.c | 57 + > .../diagnostic-test-text-art-ascii-color.c | 58 + > .../plugin/diagnostic-test-text-art-none.c | 5 + > .../diagnostic-test-text-art-unicode-bw.c | 58 + > .../diagnostic-test-text-art-unicode-color.c | 59 + > .../plugin/diagnostic_plugin_test_text_art.c | 257 ++++ > gcc/testsuite/gcc.dg/plugin/plugin.exp | 6 + > gcc/text-art/box-drawing-chars.inc | 18 + > gcc/text-art/box-drawing.cc | 72 + > gcc/text-art/box-drawing.h | 32 + > gcc/text-art/canvas.cc | 437 ++++++ > gcc/text-art/canvas.h | 74 + > gcc/text-art/ruler.cc | 723 ++++++++++ > gcc/text-art/ruler.h | 125 ++ > gcc/text-art/selftests.cc | 77 + > gcc/text-art/selftests.h | 60 + > gcc/text-art/style.cc | 632 ++++++++ > gcc/text-art/styled-string.cc | 1107 ++++++++++++++ > gcc/text-art/table.cc | 1272 +++++++++++++++++ > gcc/text-art/table.h | 262 ++++ > gcc/text-art/theme.cc | 183 +++ > gcc/text-art/theme.h | 123 ++ > gcc/text-art/types.h | 504 +++++++ > gcc/text-art/widget.cc | 275 ++++ > gcc/text-art/widget.h | 246 ++++ > libcpp/charset.cc | 89 +- > libcpp/combining-chars.inc | 68 + > libcpp/include/cpplib.h | 3 + > libcpp/printable-chars.inc | 231 +++ > 50 files changed, 7760 insertions(+), 33 deletions(-) > create mode 100755 contrib/unicode/gen-box-drawing-chars.py > create mode 100755 contrib/unicode/gen-combining-chars.py > create mode 100755 contrib/unicode/gen-printable-chars.py > create mode 100644 gcc/diagnostic-diagram.h > create mode 100644 gcc/diagnostic-text-art.h > create mode 100644 gcc/testsuite/gcc.dg/plugin/diagnostic-test-text-art-ascii-bw.c > create mode 100644 gcc/testsuite/gcc.dg/plugin/diagnostic-test-text-art-ascii-color.c > create mode 100644 gcc/testsuite/gcc.dg/plugin/diagnostic-test-text-art-none.c > create mode 100644 gcc/testsuite/gcc.dg/plugin/diagnostic-test-text-art-unicode-bw.c > create mode 100644 gcc/testsuite/gcc.dg/plugin/diagnostic-test-text-art-unicode-color.c > create mode 100644 gcc/testsuite/gcc.dg/plugin/diagnostic_plugin_test_text_art.c > create mode 100644 gcc/text-art/box-drawing-chars.inc > create mode 100644 gcc/text-art/box-drawing.cc > create mode 100644 gcc/text-art/box-drawing.h > create mode 100644 gcc/text-art/canvas.cc > create mode 100644 gcc/text-art/canvas.h > create mode 100644 gcc/text-art/ruler.cc > create mode 100644 gcc/text-art/ruler.h > create mode 100644 gcc/text-art/selftests.cc > create mode 100644 gcc/text-art/selftests.h > create mode 100644 gcc/text-art/style.cc > create mode 100644 gcc/text-art/styled-string.cc > create mode 100644 gcc/text-art/table.cc > create mode 100644 gcc/text-art/table.h > create mode 100644 gcc/text-art/theme.cc > create mode 100644 gcc/text-art/theme.h > create mode 100644 gcc/text-art/types.h > create mode 100644 gcc/text-art/widget.cc > create mode 100644 gcc/text-art/widget.h > create mode 100644 libcpp/combining-chars.inc > create mode 100644 libcpp/printable-chars.inc > > diff --git a/contrib/unicode/gen-box-drawing-chars.py b/contrib/unicode/gen-box-drawing-chars.py > new file mode 100755 > index 00000000000..9a55266ab84 > --- /dev/null > +++ b/contrib/unicode/gen-box-drawing-chars.py > @@ -0,0 +1,94 @@ > +#!/usr/bin/env python3 > +# > +# Script to generate gcc/text-art/box-drawing-chars.inc > +# > +# This file is part of GCC. > +# > +# GCC is free software; you can redistribute it and/or modify it under > +# the terms of the GNU General Public License as published by the Free > +# Software Foundation; either version 3, or (at your option) any later > +# version. > +# > +# GCC is distributed in the hope that it will be useful, but WITHOUT ANY > +# WARRANTY; without even the implied warranty of MERCHANTABILITY or > +# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License > +# for more details. > +# > +# You should have received a copy of the GNU General Public License > +# along with GCC; see the file COPYING3. If not see > +# <http://www.gnu.org/licenses/>. */ > + > +import unicodedata > + > +def get_box_drawing_char_name(up: bool, > + down: bool, > + left: bool, > + right: bool) -> str: > + if 0: > + print(f'{locals()=}') > + if up and down: > + vertical = True > + up = False > + down = False > + else: > + vertical = False > + > + if left and right: > + horizontal = True > + left = False > + right = False > + else: > + horizontal = False > + > + weights = [] > + heavy = [] > + light = [] > + dirs = [] > + for dir_name in ('up', 'down', 'vertical', 'left', 'right', 'horizontal'): > + val = locals()[dir_name] > + if val: > + dirs.append(dir_name.upper()) > + > + if not dirs: > + return 'SPACE' > + > + name = 'BOX DRAWINGS' > + #print(f'{light=} {heavy=}') > + > + if 0: > + print(dirs) > + > + def weights_frag(weight: str, dirs: list, prefix: bool): > + """ > + Generate a fragment where all directions share the same weight, e.g.: > + 'HEAVY HORIZONTAL' > + 'DOWN LIGHT' > + 'LEFT DOWN HEAVY' > + 'HEAVY DOWN AND RIGHT' > + """ > + assert len(dirs) >= 1 > + assert len(dirs) <= 2 > + if prefix: > + return f' {weight} ' + (' AND '.join(dirs)) > + else: > + return ' ' + (' '.join(dirs)) + f' {weight}' > + > + assert(len(dirs) >= 1 and len(dirs) <= 2) > + name += weights_frag('LIGHT', dirs, True) > + > + return name > + > +print('/* Generated by contrib/unicode/gen-box-drawing-chars.py. */') > +print() > +for i in range(16): > + up = (i & 8) > + down = (i & 4) > + left = (i & 2) > + right = (i & 1) > + name = get_box_drawing_char_name(up, down, left, right) > + if i < 15: > + trailing_comma = ',' > + else: > + trailing_comma = ' ' > + unichar = unicodedata.lookup(name) > + print(f'0x{ord(unichar):04X}{trailing_comma} /* "{unichar}": U+{ord(unichar):04X}: {name} */') > diff --git a/contrib/unicode/gen-combining-chars.py b/contrib/unicode/gen-combining-chars.py > new file mode 100755 > index 00000000000..fb5ef50ba4c > --- /dev/null > +++ b/contrib/unicode/gen-combining-chars.py > @@ -0,0 +1,75 @@ > +#!/usr/bin/env python3 > +# > +# Script to generate libcpp/combining-chars.inc > +# > +# This file is part of GCC. > +# > +# GCC is free software; you can redistribute it and/or modify it under > +# the terms of the GNU General Public License as published by the Free > +# Software Foundation; either version 3, or (at your option) any later > +# version. > +# > +# GCC is distributed in the hope that it will be useful, but WITHOUT ANY > +# WARRANTY; without even the implied warranty of MERCHANTABILITY or > +# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License > +# for more details. > +# > +# You should have received a copy of the GNU General Public License > +# along with GCC; see the file COPYING3. If not see > +# <http://www.gnu.org/licenses/>. */ > + > +from pprint import pprint > +import unicodedata > + > +def is_combining_char(code_point) -> bool: > + return unicodedata.combining(chr(code_point)) != 0 > + > +class Range: > + def __init__(self, start, end, value): > + self.start = start > + self.end = end > + self.value = value > + > + def __repr__(self): > + return f'Range({self.start:x}, {self.end:x}, {self.value})' > + > +def make_ranges(value_callback): > + ranges = [] > + for code_point in range(0x10FFFF): > + value = is_combining_char(code_point) > + if 0: > + print(f'{code_point=:x} {value=}') > + if ranges and ranges[-1].value == value: > + # Extend current range > + ranges[-1].end = code_point > + else: > + # Start a new range > + ranges.append(Range(code_point, code_point, value)) > + return ranges > + > +ranges = make_ranges(is_combining_char) > +if 0: > + pprint(ranges) > + > +print(f"/* Generated by contrib/unicode/gen-combining-chars.py") > +print(f" using version {unicodedata.unidata_version}" > + " of the Unicode standard. */") > +print("\nstatic const cppchar_t combining_range_ends[] = {", end="") > +for i, r in enumerate(ranges): > + if i % 8: > + print(" ", end="") > + else: > + print("\n ", end="") > + print("0x%x," % r.end, end="") > +print("\n};\n") > +print("static const bool is_combining[] = {", end="") > +for i, r in enumerate(ranges): > + if i % 24: > + print(" ", end="") > + else: > + print("\n ", end="") > + if r.value: > + print("1,", end="") > + else: > + print("0,", end="") > +print("\n};") > diff --git a/contrib/unicode/gen-printable-chars.py b/contrib/unicode/gen-printable-chars.py > new file mode 100755 > index 00000000000..7684c086638 > --- /dev/null > +++ b/contrib/unicode/gen-printable-chars.py > @@ -0,0 +1,77 @@ > +#!/usr/bin/env python3 > +# > +# Script to generate libcpp/printable-chars.inc > +# > +# This file is part of GCC. > +# > +# GCC is free software; you can redistribute it and/or modify it under > +# the terms of the GNU General Public License as published by the Free > +# Software Foundation; either version 3, or (at your option) any later > +# version. > +# > +# GCC is distributed in the hope that it will be useful, but WITHOUT ANY > +# WARRANTY; without even the implied warranty of MERCHANTABILITY or > +# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License > +# for more details. > +# > +# You should have received a copy of the GNU General Public License > +# along with GCC; see the file COPYING3. If not see > +# <http://www.gnu.org/licenses/>. */ > + > +from pprint import pprint > +import unicodedata > + > +def is_printable_char(code_point) -> bool: > + category = unicodedata.category(chr(code_point)) > + # "Cc" is "control" and "Cf" is "format" > + return category[0] != 'C' > + > +class Range: > + def __init__(self, start, end, value): > + self.start = start > + self.end = end > + self.value = value > + > + def __repr__(self): > + return f'Range({self.start:x}, {self.end:x}, {self.value})' > + > +def make_ranges(value_callback): > + ranges = [] > + for code_point in range(0x10FFFF): > + value = is_printable_char(code_point) > + if 0: > + print(f'{code_point=:x} {value=}') > + if ranges and ranges[-1].value == value: > + # Extend current range > + ranges[-1].end = code_point > + else: > + # Start a new range > + ranges.append(Range(code_point, code_point, value)) > + return ranges > + > +ranges = make_ranges(is_printable_char) > +if 0: > + pprint(ranges) > + > +print(f"/* Generated by contrib/unicode/gen-printable-chars.py") > +print(f" using version {unicodedata.unidata_version}" > + " of the Unicode standard. */") > +print("\nstatic const cppchar_t printable_range_ends[] = {", end="") > +for i, r in enumerate(ranges): > + if i % 8: > + print(" ", end="") > + else: > + print("\n ", end="") > + print("0x%x," % r.end, end="") > +print("\n};\n") > +print("static const bool is_printable[] = {", end="") > +for i, r in enumerate(ranges): > + if i % 24: > + print(" ", end="") > + else: > + print("\n ", end="") > + if r.value: > + print("1,", end="") > + else: > + print("0,", end="") > +print("\n};") > diff --git a/gcc/Makefile.in b/gcc/Makefile.in > index 1d39e6dd3f8..c1e7257ed24 100644 > --- a/gcc/Makefile.in > +++ b/gcc/Makefile.in > @@ -1781,7 +1781,16 @@ OBJS-libcommon = diagnostic-spec.o diagnostic.o diagnostic-color.o \ > json.o \ > sbitmap.o \ > vec.o input.o hash-table.o ggc-none.o memory-block.o \ > - selftest.o selftest-diagnostic.o sort.o > + selftest.o selftest-diagnostic.o sort.o \ > + text-art/box-drawing.o \ > + text-art/canvas.o \ > + text-art/ruler.o \ > + text-art/selftests.o \ > + text-art/style.o \ > + text-art/styled-string.o \ > + text-art/table.o \ > + text-art/theme.o \ > + text-art/widget.o > > # Objects in libcommon-target.a, used by drivers and by the core > # compiler and containing target-dependent code. > diff --git a/gcc/color-macros.h b/gcc/color-macros.h > index fcd79d09c01..9688f92110a 100644 > --- a/gcc/color-macros.h > +++ b/gcc/color-macros.h > @@ -92,6 +92,14 @@ along with GCC; see the file COPYING3. If not see > #define COLOR_FG_MAGENTA "35" > #define COLOR_FG_CYAN "36" > #define COLOR_FG_WHITE "37" > +#define COLOR_FG_BRIGHT_BLACK "90" > +#define COLOR_FG_BRIGHT_RED "91" > +#define COLOR_FG_BRIGHT_GREEN "92" > +#define COLOR_FG_BRIGHT_YELLOW "93" > +#define COLOR_FG_BRIGHT_BLUE "94" > +#define COLOR_FG_BRIGHT_MAGENTA "95" > +#define COLOR_FG_BRIGHT_CYAN "96" > +#define COLOR_FG_BRIGHT_WHITE "97" > #define COLOR_BG_BLACK "40" > #define COLOR_BG_RED "41" > #define COLOR_BG_GREEN "42" > @@ -100,6 +108,14 @@ along with GCC; see the file COPYING3. If not see > #define COLOR_BG_MAGENTA "45" > #define COLOR_BG_CYAN "46" > #define COLOR_BG_WHITE "47" > +#define COLOR_BG_BRIGHT_BLACK "100" > +#define COLOR_BG_BRIGHT_RED "101" > +#define COLOR_BG_BRIGHT_GREEN "102" > +#define COLOR_BG_BRIGHT_YELLOW "103" > +#define COLOR_BG_BRIGHT_BLUE "104" > +#define COLOR_BG_BRIGHT_MAGENTA "105" > +#define COLOR_BG_BRIGHT_CYAN "106" > +#define COLOR_BG_BRIGHT_WHITE "107" > #define SGR_START "\33[" > #define SGR_END "m\33[K" > #define SGR_SEQ(str) SGR_START str SGR_END > diff --git a/gcc/common.opt b/gcc/common.opt > index a28ca13385a..b3c82b8607c 100644 > --- a/gcc/common.opt > +++ b/gcc/common.opt > @@ -1502,6 +1502,29 @@ fdiagnostics-show-path-depths > Common Var(flag_diagnostics_show_path_depths) Init(0) > Show stack depths of events in paths. > > +fdiagnostics-text-art-charset= > +Driver Common Joined RejectNegative Var(flag_diagnostics_text_art_charset) Enum(diagnostic_text_art_charset) Init(DIAGNOSTICS_TEXT_ART_CHARSET_EMOJI) > +-fdiagnostics-text-art-charset=[none|ascii|unicode|emoji] Determine which characters to use in text arg diagrams. > + > +; Required for these enum values. > +SourceInclude > +diagnostic-text-art.h > + > +Enum > +Name(diagnostic_text_art_charset) Type(int) > + > +EnumValue > +Enum(diagnostic_text_art_charset) String(none) Value(DIAGNOSTICS_TEXT_ART_CHARSET_NONE) > + > +EnumValue > +Enum(diagnostic_text_art_charset) String(ascii) Value(DIAGNOSTICS_TEXT_ART_CHARSET_ASCII) > + > +EnumValue > +Enum(diagnostic_text_art_charset) String(unicode) Value(DIAGNOSTICS_TEXT_ART_CHARSET_UNICODE) > + > +EnumValue > +Enum(diagnostic_text_art_charset) String(emoji) Value(DIAGNOSTICS_TEXT_ART_CHARSET_EMOJI) > + > fdiagnostics-minimum-margin-width= > Common Joined UInteger Var(diagnostics_minimum_margin_width) Init(6) > Set minimum width of left margin of source code when showing source. > diff --git a/gcc/configure b/gcc/configure > index 5f67808b774..e061d2b1949 100755 > --- a/gcc/configure > +++ b/gcc/configure > @@ -33995,7 +33995,7 @@ $as_echo "$as_me: executing $ac_file commands" >&6;} > "depdir":C) $SHELL $ac_aux_dir/mkinstalldirs $DEPDIR ;; > "gccdepdir":C) > ${CONFIG_SHELL-/bin/sh} $ac_aux_dir/mkinstalldirs build/$DEPDIR > - for lang in $subdirs c-family common analyzer rtl-ssa > + for lang in $subdirs c-family common analyzer text-art rtl-ssa > do > ${CONFIG_SHELL-/bin/sh} $ac_aux_dir/mkinstalldirs $lang/$DEPDIR > done ;; > diff --git a/gcc/configure.ac b/gcc/configure.ac > index cc8dd9e20bf..350d245c89f 100644 > --- a/gcc/configure.ac > +++ b/gcc/configure.ac > @@ -1384,7 +1384,7 @@ AC_CHECK_HEADERS(ext/hash_map) > ZW_CREATE_DEPDIR > AC_CONFIG_COMMANDS([gccdepdir],[ > ${CONFIG_SHELL-/bin/sh} $ac_aux_dir/mkinstalldirs build/$DEPDIR > - for lang in $subdirs c-family common analyzer rtl-ssa > + for lang in $subdirs c-family common analyzer text-art rtl-ssa > do > ${CONFIG_SHELL-/bin/sh} $ac_aux_dir/mkinstalldirs $lang/$DEPDIR > done], [subdirs="$subdirs" ac_aux_dir=$ac_aux_dir DEPDIR=$DEPDIR]) > diff --git a/gcc/diagnostic-diagram.h b/gcc/diagnostic-diagram.h > new file mode 100644 > index 00000000000..fc923c512ed > --- /dev/null > +++ b/gcc/diagnostic-diagram.h > @@ -0,0 +1,51 @@ > +/* Support for diagrams within diagnostics. > + Copyright (C) 2023 Free Software Foundation, Inc. > + Contributed by David Malcolm <dmalcolm@redhat.com> > + > +This file is part of GCC. > + > +GCC is free software; you can redistribute it and/or modify it under > +the terms of the GNU General Public License as published by the Free > +Software Foundation; either version 3, or (at your option) any later > +version. > + > +GCC is distributed in the hope that it will be useful, but WITHOUT ANY > +WARRANTY; without even the implied warranty of MERCHANTABILITY or > +FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License > +for more details. > + > +You should have received a copy of the GNU General Public License > +along with GCC; see the file COPYING3. If not see > +<http://www.gnu.org/licenses/>. */ > + > +#ifndef GCC_DIAGNOSTIC_DIAGRAM_H > +#define GCC_DIAGNOSTIC_DIAGRAM_H > + > +namespace text_art > +{ > + class canvas; > +} // namespace text_art > + > +/* A text art diagram, along with an "alternative text" string > + describing it. */ > + > +class diagnostic_diagram > +{ > + public: > + diagnostic_diagram (const text_art::canvas &canvas, > + const char *alt_text) > + : m_canvas (canvas), > + m_alt_text (alt_text) > + { > + gcc_assert (alt_text); > + } > + > + const text_art::canvas &get_canvas () const { return m_canvas; } > + const char *get_alt_text () const { return m_alt_text; } > + > + private: > + const text_art::canvas &m_canvas; > + const char *const m_alt_text; > +}; > + > +#endif /* ! GCC_DIAGNOSTIC_DIAGRAM_H */ > diff --git a/gcc/diagnostic-format-json.cc b/gcc/diagnostic-format-json.cc > index 694dddca9e8..539b98b5e74 100644 > --- a/gcc/diagnostic-format-json.cc > +++ b/gcc/diagnostic-format-json.cc > @@ -324,6 +324,15 @@ json_file_final_cb (diagnostic_context *) > free (filename); > } > > +/* Callback for diagnostic_context::m_diagrams.m_emission_cb. */ > + > +static void > +json_emit_diagram (diagnostic_context *, > + const diagnostic_diagram &) > +{ > + /* No-op. */ > +} > + > /* Populate CONTEXT in preparation for JSON output (either to stderr, or > to a file). */ > > @@ -340,6 +349,7 @@ diagnostic_output_format_init_json (diagnostic_context *context) > context->begin_group_cb = json_begin_group; > context->end_group_cb = json_end_group; > context->print_path = NULL; /* handled in json_end_diagnostic. */ > + context->m_diagrams.m_emission_cb = json_emit_diagram; > > /* The metadata is handled in JSON format, rather than as text. */ > context->show_cwe = false; > diff --git a/gcc/diagnostic-format-sarif.cc b/gcc/diagnostic-format-sarif.cc > index fd29ac2ca3b..ac2f5b844e3 100644 > --- a/gcc/diagnostic-format-sarif.cc > +++ b/gcc/diagnostic-format-sarif.cc > @@ -29,6 +29,8 @@ along with GCC; see the file COPYING3. If not see > #include "cpplib.h" > #include "logical-location.h" > #include "diagnostic-client-data-hooks.h" > +#include "diagnostic-diagram.h" > +#include "text-art/canvas.h" > > class sarif_builder; > > @@ -66,8 +68,13 @@ public: > diagnostic_info *diagnostic, > diagnostic_t orig_diag_kind, > sarif_builder *builder); > + void on_diagram (diagnostic_context *context, > + const diagnostic_diagram &diagram, > + sarif_builder *builder); > > private: > + void add_related_location (json::object *location_obj); > + > json::array *m_related_locations_arr; > }; > > @@ -135,7 +142,8 @@ public: > > void end_diagnostic (diagnostic_context *context, diagnostic_info *diagnostic, > diagnostic_t orig_diag_kind); > - > + void emit_diagram (diagnostic_context *context, > + const diagnostic_diagram &diagram); > void end_group (); > > void flush_to_file (FILE *outf); > @@ -144,6 +152,9 @@ public: > json::object *make_location_object (const rich_location &rich_loc, > const logical_location *logical_loc); > json::object *make_message_object (const char *msg) const; > + json::object * > + make_message_object_for_diagram (diagnostic_context *context, > + const diagnostic_diagram &diagram); > > private: > sarif_result *make_result_object (diagnostic_context *context, > @@ -261,12 +272,6 @@ sarif_result::on_nested_diagnostic (diagnostic_context *context, > diagnostic_t /*orig_diag_kind*/, > sarif_builder *builder) > { > - if (!m_related_locations_arr) > - { > - m_related_locations_arr = new json::array (); > - set ("relatedLocations", m_related_locations_arr); > - } > - > /* We don't yet generate meaningful logical locations for notes; > sometimes these will related to current_function_decl, but > often they won't. */ > @@ -277,6 +282,39 @@ sarif_result::on_nested_diagnostic (diagnostic_context *context, > pp_clear_output_area (context->printer); > location_obj->set ("message", message_obj); > > + add_related_location (location_obj); > +} > + > +/* Handle diagrams that occur within a diagnostic group. > + The closest thing in SARIF seems to be to add a location to the > + "releatedLocations" property (SARIF v2.1.0 section 3.27.22), > + and to put the diagram into the "message" property of that location > + (SARIF v2.1.0 section 3.28.5). */ > + > +void > +sarif_result::on_diagram (diagnostic_context *context, > + const diagnostic_diagram &diagram, > + sarif_builder *builder) > +{ > + json::object *location_obj = new json::object (); > + json::object *message_obj > + = builder->make_message_object_for_diagram (context, diagram); > + location_obj->set ("message", message_obj); > + > + add_related_location (location_obj); > +} > + > +/* Add LOCATION_OBJ to this result's "relatedLocations" array, > + creating it if it doesn't yet exist. */ > + > +void > +sarif_result::add_related_location (json::object *location_obj) > +{ > + if (!m_related_locations_arr) > + { > + m_related_locations_arr = new json::array (); > + set ("relatedLocations", m_related_locations_arr); > + } > m_related_locations_arr->append (location_obj); > } > > @@ -348,6 +386,18 @@ sarif_builder::end_diagnostic (diagnostic_context *context, > } > } > > +/* Implementation of diagnostic_context::m_diagrams.m_emission_cb > + for SARIF output. */ > + > +void > +sarif_builder::emit_diagram (diagnostic_context *context, > + const diagnostic_diagram &diagram) > +{ > + /* We must be within the emission of a top-level diagnostic. */ > + gcc_assert (m_cur_group_result); > + m_cur_group_result->on_diagram (context, diagram, this); > +} > + > /* Implementation of "end_group_cb" for SARIF output. */ > > void > @@ -1115,6 +1165,37 @@ sarif_builder::make_message_object (const char *msg) const > return message_obj; > } > > +/* Make a message object (SARIF v2.1.0 section 3.11) for DIAGRAM. > + We emit the diagram as a code block within the Markdown part > + of the message. */ > + > +json::object * > +sarif_builder::make_message_object_for_diagram (diagnostic_context *context, > + const diagnostic_diagram &diagram) > +{ > + json::object *message_obj = new json::object (); > + > + /* "text" property (SARIF v2.1.0 section 3.11.8). */ > + message_obj->set ("text", new json::string (diagram.get_alt_text ())); > + > + char *saved_prefix = pp_take_prefix (context->printer); > + pp_set_prefix (context->printer, NULL); > + > + /* "To produce a code block in Markdown, simply indent every line of > + the block by at least 4 spaces or 1 tab." > + Here we use 4 spaces. */ > + diagram.get_canvas ().print_to_pp (context->printer, " "); > + pp_set_prefix (context->printer, saved_prefix); > + > + /* "markdown" property (SARIF v2.1.0 section 3.11.9). */ > + message_obj->set ("markdown", > + new json::string (pp_formatted_text (context->printer))); > + > + pp_clear_output_area (context->printer); > + > + return message_obj; > +} > + > /* Make a multiformatMessageString object (SARIF v2.1.0 section 3.12) > for MSG. */ > > @@ -1630,6 +1711,16 @@ sarif_ice_handler (diagnostic_context *context) > fnotice (stderr, "Internal compiler error:\n"); > } > > +/* Callback for diagnostic_context::m_diagrams.m_emission_cb. */ > + > +static void > +sarif_emit_diagram (diagnostic_context *context, > + const diagnostic_diagram &diagram) > +{ > + gcc_assert (the_builder); > + the_builder->emit_diagram (context, diagram); > +} > + > /* Populate CONTEXT in preparation for SARIF output (either to stderr, or > to a file). */ > > @@ -1645,6 +1736,7 @@ diagnostic_output_format_init_sarif (diagnostic_context *context) > context->end_group_cb = sarif_end_group; > context->print_path = NULL; /* handled in sarif_end_diagnostic. */ > context->ice_handler_cb = sarif_ice_handler; > + context->m_diagrams.m_emission_cb = sarif_emit_diagram; > > /* The metadata is handled in SARIF format, rather than as text. */ > context->show_cwe = false; > diff --git a/gcc/diagnostic-text-art.h b/gcc/diagnostic-text-art.h > new file mode 100644 > index 00000000000..a0d8a78f52a > --- /dev/null > +++ b/gcc/diagnostic-text-art.h > @@ -0,0 +1,49 @@ > +/* Copyright (C) 2023 Free Software Foundation, Inc. > + Contributed by David Malcolm <dmalcolm@redhat.com>. > + > +This file is part of GCC. > + > +GCC is free software; you can redistribute it and/or modify it under > +the terms of the GNU General Public License as published by the Free > +Software Foundation; either version 3, or (at your option) any later > +version. > + > +GCC is distributed in the hope that it will be useful, but WITHOUT ANY > +WARRANTY; without even the implied warranty of MERCHANTABILITY or > +FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License > +for more details. > + > +You should have received a copy of the GNU General Public License > +along with GCC; see the file COPYING3. If not see > +<http://www.gnu.org/licenses/>. */ > + > +#ifndef GCC_DIAGNOSTIC_TEXT_ART_H > +#define GCC_DIAGNOSTIC_TEXT_ART_H > + > +/* Values for -fdiagnostics-text-art-charset=. */ > + > +enum diagnostic_text_art_charset > +{ > + /* No text art diagrams shall be emitted. */ > + DIAGNOSTICS_TEXT_ART_CHARSET_NONE, > + > + /* Use pure ASCII for text art diagrams. */ > + DIAGNOSTICS_TEXT_ART_CHARSET_ASCII, > + > + /* Use ASCII + conservative use of other unicode characters > + in text art diagrams. */ > + DIAGNOSTICS_TEXT_ART_CHARSET_UNICODE, > + > + /* Use Emoji. */ > + DIAGNOSTICS_TEXT_ART_CHARSET_EMOJI > +}; > + > +const enum diagnostic_text_art_charset DIAGNOSTICS_TEXT_ART_CHARSET_DEFAULT > + = DIAGNOSTICS_TEXT_ART_CHARSET_EMOJI; > + > +extern void > +diagnostics_text_art_charset_init (diagnostic_context *context, > + enum diagnostic_text_art_charset charset); > + > + > +#endif /* ! GCC_DIAGNOSTIC_TEXT_ART_H */ > diff --git a/gcc/diagnostic.cc b/gcc/diagnostic.cc > index 0f093081161..7c2289f0634 100644 > --- a/gcc/diagnostic.cc > +++ b/gcc/diagnostic.cc > @@ -35,11 +35,14 @@ along with GCC; see the file COPYING3. If not see > #include "diagnostic-metadata.h" > #include "diagnostic-path.h" > #include "diagnostic-client-data-hooks.h" > +#include "diagnostic-text-art.h" > +#include "diagnostic-diagram.h" > #include "edit-context.h" > #include "selftest.h" > #include "selftest-diagnostic.h" > #include "opts.h" > #include "cpplib.h" > +#include "text-art/theme.h" > > #ifdef HAVE_TERMIOS_H > # include <termios.h> > @@ -244,6 +247,10 @@ diagnostic_initialize (diagnostic_context *context, int n_opts) > context->ice_handler_cb = NULL; > context->includes_seen = NULL; > context->m_client_data_hooks = NULL; > + context->m_diagrams.m_theme = NULL; > + context->m_diagrams.m_emission_cb = NULL; > + diagnostics_text_art_charset_init (context, > + DIAGNOSTICS_TEXT_ART_CHARSET_DEFAULT); > } > > /* Maybe initialize the color support. We require clients to do this > @@ -320,6 +327,12 @@ diagnostic_finish (diagnostic_context *context) > if (context->final_cb) > context->final_cb (context); > > + if (context->m_diagrams.m_theme) > + { > + delete context->m_diagrams.m_theme; > + context->m_diagrams.m_theme = NULL; > + } > + > diagnostic_file_cache_fini (); > > XDELETEVEC (context->classify_diagnostic); > @@ -2174,6 +2187,33 @@ internal_error_no_backtrace (const char *gmsgid, ...) > > gcc_unreachable (); > } > + > +/* Emit DIAGRAM to CONTEXT, respecting the output format. */ > + > +void > +diagnostic_emit_diagram (diagnostic_context *context, > + const diagnostic_diagram &diagram) > +{ > + if (context->m_diagrams.m_theme == nullptr) > + return; > + > + if (context->m_diagrams.m_emission_cb) > + { > + context->m_diagrams.m_emission_cb (context, diagram); > + return; > + } > + > + /* Default implementation. */ > + char *saved_prefix = pp_take_prefix (context->printer); > + pp_set_prefix (context->printer, NULL); > + /* Use a newline before and after and a two-space indent > + to make the diagram stand out a little from the wall of text. */ > + pp_newline (context->printer); > + diagram.get_canvas ().print_to_pp (context->printer, " "); > + pp_newline (context->printer); > + pp_set_prefix (context->printer, saved_prefix); > + pp_flush (context->printer); > +} > > /* Special case error functions. Most are implemented in terms of the > above, or should be. */ > @@ -2316,6 +2356,38 @@ diagnostic_output_format_init (diagnostic_context *context, > } > } > > +/* Initialize CONTEXT->m_diagrams based on CHARSET. > + Specifically, make a text_art::theme object for m_diagrams.m_theme, > + (or NULL for "no diagrams"). */ > + > +void > +diagnostics_text_art_charset_init (diagnostic_context *context, > + enum diagnostic_text_art_charset charset) > +{ > + delete context->m_diagrams.m_theme; > + switch (charset) > + { > + default: > + gcc_unreachable (); > + > + case DIAGNOSTICS_TEXT_ART_CHARSET_NONE: > + context->m_diagrams.m_theme = NULL; > + break; > + > + case DIAGNOSTICS_TEXT_ART_CHARSET_ASCII: > + context->m_diagrams.m_theme = new text_art::ascii_theme (); > + break; > + > + case DIAGNOSTICS_TEXT_ART_CHARSET_UNICODE: > + context->m_diagrams.m_theme = new text_art::unicode_theme (); > + break; > + > + case DIAGNOSTICS_TEXT_ART_CHARSET_EMOJI: > + context->m_diagrams.m_theme = new text_art::emoji_theme (); > + break; > + } > +} > + > /* Implementation of diagnostic_path::num_events vfunc for > simple_diagnostic_path: simply get the number of events in the vec. */ > > diff --git a/gcc/diagnostic.h b/gcc/diagnostic.h > index 9a51097f146..00b828f230d 100644 > --- a/gcc/diagnostic.h > +++ b/gcc/diagnostic.h > @@ -24,6 +24,11 @@ along with GCC; see the file COPYING3. If not see > #include "pretty-print.h" > #include "diagnostic-core.h" > > +namespace text_art > +{ > + class theme; > +} // namespace text_art > + > /* An enum for controlling what units to use for the column number > when diagnostics are output, used by the -fdiagnostics-column-unit option. > Tabs will be expanded or not according to the value of -ftabstop. The origin > @@ -170,6 +175,7 @@ class edit_context; > namespace json { class value; } > class diagnostic_client_data_hooks; > class logical_location; > +class diagnostic_diagram; > > /* This data structure bundles altogether any information relevant to > the context of a diagnostic message. */ > @@ -417,6 +423,18 @@ struct diagnostic_context > Used by SARIF output to give metadata about the client that's > producing diagnostics. */ > diagnostic_client_data_hooks *m_client_data_hooks; > + > + /* Support for diagrams. */ > + struct > + { > + /* Theme to use when generating diagrams. > + Can be NULL (if text art is disabled). */ > + text_art::theme *m_theme; > + > + /* Callback for emitting diagrams. */ > + void (*m_emission_cb) (diagnostic_context *context, > + const diagnostic_diagram &diagram); > + } m_diagrams; > }; > > inline void > @@ -619,4 +637,7 @@ extern bool warning_enabled_at (location_t, int); > > extern char *get_cwe_url (int cwe); > > +extern void diagnostic_emit_diagram (diagnostic_context *context, > + const diagnostic_diagram &diagram); > + > #endif /* ! GCC_DIAGNOSTIC_H */ > diff --git a/gcc/doc/invoke.texi b/gcc/doc/invoke.texi > index 898a88ce33e..023a56a647e 100644 > --- a/gcc/doc/invoke.texi > +++ b/gcc/doc/invoke.texi > @@ -316,7 +316,8 @@ Objective-C and Objective-C++ Dialects}. > -fno-show-column > -fdiagnostics-column-unit=@r{[}display@r{|}byte@r{]} > -fdiagnostics-column-origin=@var{origin} > --fdiagnostics-escape-format=@r{[}unicode@r{|}bytes@r{]}} > +-fdiagnostics-escape-format=@r{[}unicode@r{|}bytes@r{]} > +-fdiagnostics-text-art-charset=@r{[}none@r{|}ascii@r{|}unicode@r{|}emoji@r{]}} > > @item Warning Options > @xref{Warning Options,,Options to Request or Suppress Warnings}. > @@ -5066,7 +5067,8 @@ options: > -fno-diagnostics-show-line-numbers > -fdiagnostics-color=never > -fdiagnostics-urls=never > --fdiagnostics-path-format=separate-events} > +-fdiagnostics-path-format=separate-events > +-fdiagnostics-text-art-charset=none} > In the future, if GCC changes the default appearance of its diagnostics, the > corresponding option to disable the new behavior will be added to this list. > > @@ -5592,6 +5594,25 @@ Unicode characters. For the example above, the following will be printed: > before<CF><80><BF>after > @end smallexample > > +@opindex fdiagnostics-text-art-charset > +@item -fdiagnostics-text-art-charset=@var{CHARSET} > +Some diagnostics can contain ``text art'' diagrams: visualizations created > +from text, intended to be viewed in a monospaced font. > + > +This option selects which characters should be used for printing such > +diagrams, if any. @var{CHARSET} is @samp{none}, @samp{ascii}, @samp{unicode}, > +or @samp{emoji}. > + > +The @samp{none} value suppresses the printing of such diagrams. > +The @samp{ascii} value will ensure that such diagrams are pure ASCII > +(``ASCII art''). The @samp{unicode} value will allow for conservative use of > +unicode drawing characters (such as box-drawing characters). The @samp{emoji} > +value further adds the possibility of emoji in the output (such as emitting > +U+26A0 WARNING SIGN followed by U+FE0F VARIATION SELECTOR-16 to select the > +emoji variant of the character). > + > +The default is @samp{emoji}. > + > @opindex fdiagnostics-format > @item -fdiagnostics-format=@var{FORMAT} > Select a different format for printing diagnostics. > diff --git a/gcc/gcc.cc b/gcc/gcc.cc > index 2ccca00d603..f9f0a7eaad4 100644 > --- a/gcc/gcc.cc > +++ b/gcc/gcc.cc > @@ -46,6 +46,7 @@ compilation is specified by a string called a "spec". */ > #include "spellcheck.h" > #include "opts-jobserver.h" > #include "common/common-target.h" > +#include "diagnostic-text-art.h" > > > > @@ -4299,6 +4300,11 @@ driver_handle_option (struct gcc_options *opts, > break; > } > > + case OPT_fdiagnostics_text_art_charset_: > + diagnostics_text_art_charset_init (dc, > + (enum diagnostic_text_art_charset)value); > + break; > + > case OPT_Wa_: > { > int prev, j; > diff --git a/gcc/opts-common.cc b/gcc/opts-common.cc > index 23ddcaa3b55..f0c5f483665 100644 > --- a/gcc/opts-common.cc > +++ b/gcc/opts-common.cc > @@ -1068,6 +1068,7 @@ decode_cmdline_options_to_array (unsigned int argc, const char **argv, > "-fdiagnostics-color=never", > "-fdiagnostics-urls=never", > "-fdiagnostics-path-format=separate-events", > + "-fdiagnostics-text-art-charset=none" > }; > const int num_expanded = ARRAY_SIZE (expanded_args); > opt_array_len += num_expanded - 1; > diff --git a/gcc/opts.cc b/gcc/opts.cc > index 86b94d62b58..3087bdac2c6 100644 > --- a/gcc/opts.cc > +++ b/gcc/opts.cc > @@ -35,6 +35,7 @@ along with GCC; see the file COPYING3. If not see > #include "version.h" > #include "selftest.h" > #include "file-prefix-map.h" > +#include "diagnostic-text-art.h" > > /* In this file all option sets are explicit. */ > #undef OPTION_SET_P > @@ -2887,6 +2888,11 @@ common_handle_option (struct gcc_options *opts, > break; > } > > + case OPT_fdiagnostics_text_art_charset_: > + diagnostics_text_art_charset_init (dc, > + (enum diagnostic_text_art_charset)value); > + break; > + > case OPT_fdiagnostics_parseable_fixits: > dc->extra_output_kind = (value > ? EXTRA_DIAGNOSTIC_OUTPUT_fixits_v1 > diff --git a/gcc/pretty-print.cc b/gcc/pretty-print.cc > index 7d294717f50..3d789a23812 100644 > --- a/gcc/pretty-print.cc > +++ b/gcc/pretty-print.cc > @@ -1828,6 +1828,35 @@ pp_string (pretty_printer *pp, const char *str) > pp_maybe_wrap_text (pp, str, str + strlen (str)); > } > > +/* Append code point C to the output area of PRETTY-PRINTER, encoding it > + as UTF-8. */ > + > +void > +pp_unicode_character (pretty_printer *pp, unsigned c) > +{ > + static const uchar masks[6] = { 0x00, 0xC0, 0xE0, 0xF0, 0xF8, 0xFC }; > + static const uchar limits[6] = { 0x80, 0xE0, 0xF0, 0xF8, 0xFC, 0xFE }; > + size_t nbytes; > + uchar buf[6], *p = &buf[6]; > + > + nbytes = 1; > + if (c < 0x80) > + *--p = c; > + else > + { > + do > + { > + *--p = ((c & 0x3F) | 0x80); > + c >>= 6; > + nbytes++; > + } > + while (c >= 0x3F || (c & limits[nbytes-1])); > + *--p = (c | masks[nbytes-1]); > + } > + > + pp_append_r (pp, (const char *)p, nbytes); > +} > + > /* Append the leading N characters of STRING to the output area of > PRETTY-PRINTER, quoting in hexadecimal non-printable characters. > Setting N = -1 is as if N were set to strlen (STRING). The STRING > diff --git a/gcc/pretty-print.h b/gcc/pretty-print.h > index 0230a289df5..369be6e7ba7 100644 > --- a/gcc/pretty-print.h > +++ b/gcc/pretty-print.h > @@ -401,6 +401,7 @@ extern void pp_indent (pretty_printer *); > extern void pp_newline (pretty_printer *); > extern void pp_character (pretty_printer *, int); > extern void pp_string (pretty_printer *, const char *); > +extern void pp_unicode_character (pretty_printer *, unsigned); > > extern void pp_write_text_to_stream (pretty_printer *); > extern void pp_write_text_as_dot_label_to_stream (pretty_printer *, bool); > diff --git a/gcc/selftest-run-tests.cc b/gcc/selftest-run-tests.cc > index 915f2129702..e2fc8f84b1b 100644 > --- a/gcc/selftest-run-tests.cc > +++ b/gcc/selftest-run-tests.cc > @@ -28,6 +28,7 @@ along with GCC; see the file COPYING3. If not see > #include "stringpool.h" > #include "attribs.h" > #include "analyzer/analyzer-selftests.h" > +#include "text-art/selftests.h" > > /* This function needed to be split out from selftest.cc as it references > tests from the whole source tree, and so is within > @@ -118,6 +119,8 @@ selftest::run_tests () > /* Run any lang-specific selftests. */ > lang_hooks.run_lang_selftests (); > > + text_art_tests (); > + > /* Run the analyzer selftests (if enabled). */ > ana::selftest::run_analyzer_selftests (); > > diff --git a/gcc/testsuite/gcc.dg/plugin/diagnostic-test-text-art-ascii-bw.c b/gcc/testsuite/gcc.dg/plugin/diagnostic-test-text-art-ascii-bw.c > new file mode 100644 > index 00000000000..e4239aab032 > --- /dev/null > +++ b/gcc/testsuite/gcc.dg/plugin/diagnostic-test-text-art-ascii-bw.c > @@ -0,0 +1,57 @@ > +/* { dg-additional-options "-fdiagnostics-text-art-charset=ascii -fdiagnostics-color=never" } */ > + > +int non_empty; > + > +/* { dg-begin-multiline-output "" } > + > + A > + B > + C > + > + { dg-end-multiline-output "" } */ > + > +/* { dg-begin-multiline-output "" } > + > + ♜ ♞ ♝ ♛ ♚ ♝ ♞ ♜ > + ♟ ♟ ♟ ♟ ♟ ♟ ♟ ♟ > + > + > + > + > + ♙ ♙ ♙ ♙ ♙ ♙ ♙ ♙ > + ♖ ♘ ♗ ♕ ♔ ♗ ♘ ♖ > + > + { dg-end-multiline-output "" } */ > + > +/* { dg-begin-multiline-output "" } > + > + +--+ > + |🙂| > + +--+ > + > + { dg-end-multiline-output "" } */ > +/* { dg-begin-multiline-output "" } > + > + +-------+-----+---------------+---------------------+-----------------------+-----------------------+ > + |Offsets|Octet| 0 | 1 | 2 | 3 | > + +-------+-----+-+-+-+-+-+-+-+-+-+-+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ > + | Octet | Bit |0|1|2|3|4|5|6|7|8|9|10|11|12|13|14|15|16|17|18|19|20|21|22|23|24|25|26|27|28|29|30|31| > + +-------+-----+-+-+-+-+-+-+-+-+-+-+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ > + | 0 | 0 |Version| IHL | DSCP | ECN | Total Length | > + +-------+-----+-------+-------+---------------+-----+--------+--------------------------------------+ > + | 4 | 32 | Identification | Flags | Fragment Offset | > + +-------+-----+---------------+---------------------+--------+--------------------------------------+ > + | 8 | 64 | Time To Live | Protocol | Header Checksum | > + +-------+-----+---------------+---------------------+-----------------------------------------------+ > + | 12 | 96 | Source IP Address | > + +-------+-----+-------------------------------------------------------------------------------------+ > + | 16 | 128 | Destination IP Address | > + +-------+-----+-------------------------------------------------------------------------------------+ > + | 20 | 160 | | > + +-------+-----+ | > + | ... | ... | Options | > + +-------+-----+ | > + | 56 | 448 | | > + +-------+-----+-------------------------------------------------------------------------------------+ > + > + { dg-end-multiline-output "" } */ > diff --git a/gcc/testsuite/gcc.dg/plugin/diagnostic-test-text-art-ascii-color.c b/gcc/testsuite/gcc.dg/plugin/diagnostic-test-text-art-ascii-color.c > new file mode 100644 > index 00000000000..0650428b1ce > --- /dev/null > +++ b/gcc/testsuite/gcc.dg/plugin/diagnostic-test-text-art-ascii-color.c > @@ -0,0 +1,58 @@ > +/* { dg-additional-options "-fdiagnostics-text-art-charset=ascii -fdiagnostics-color=always" } */ > + > +int non_empty; > + > +/* { dg-begin-multiline-output "" } > + > + A > + B > + C > + > + { dg-end-multiline-output "" } */ > + > +/* { dg-begin-multiline-output "" } > + > + [38;2;0;0;0;48;2;240;217;181m[K♜ [38;2;0;0;0;48;2;181;136;99m[K♞ [38;2;0;0;0;48;2;240;217;181m[K♝ [38;2;0;0;0;48;2;181;136;99m[K♛ [38;2;0;0;0;48;2;240;217;181m[K♚ [38;2;0;0;0;48;2;181;136;99m[K♝ [38;2;0;0;0;48;2;240;217;181m[K♞ [38;2;0;0;0;48;2;181;136;99m[K♜ [m[K > + [38;2;0;0;0;48;2;181;136;99m[K♟ [38;2;0;0;0;48;2;240;217;181m[K♟ [38;2;0;0;0;48;2;181;136;99m[K♟ [38;2;0;0;0;48;2;240;217;181m[K♟ [38;2;0;0;0;48;2;181;136;99m[K♟ [38;2;0;0;0;48;2;240;217;181m[K♟ [38;2;0;0;0;48;2;181;136;99m[K♟ [38;2;0;0;0;48;2;240;217;181m[K♟ [m[K > + [48;2;240;217;181m[K [48;2;181;136;99m[K [48;2;240;217;181m[K [48;2;181;136;99m[K [48;2;240;217;181m[K [48;2;181;136;99m[K [48;2;240;217;181m[K [48;2;181;136;99m[K [m[K > + [48;2;181;136;99m[K [48;2;240;217;181m[K [48;2;181;136;99m[K [48;2;240;217;181m[K [48;2;181;136;99m[K [48;2;240;217;181m[K [48;2;181;136;99m[K [48;2;240;217;181m[K [m[K > + [48;2;240;217;181m[K [48;2;181;136;99m[K [48;2;240;217;181m[K [48;2;181;136;99m[K [48;2;240;217;181m[K [48;2;181;136;99m[K [48;2;240;217;181m[K [48;2;181;136;99m[K [m[K > + [48;2;181;136;99m[K [48;2;240;217;181m[K [48;2;181;136;99m[K [48;2;240;217;181m[K [48;2;181;136;99m[K [48;2;240;217;181m[K [48;2;181;136;99m[K [48;2;240;217;181m[K [m[K > + [38;2;255;255;255;48;2;240;217;181m[K♙ [38;2;255;255;255;48;2;181;136;99m[K♙ [38;2;255;255;255;48;2;240;217;181m[K♙ [38;2;255;255;255;48;2;181;136;99m[K♙ [38;2;255;255;255;48;2;240;217;181m[K♙ [38;2;255;255;255;48;2;181;136;99m[K♙ [38;2;255;255;255;48;2;240;217;181m[K♙ [38;2;255;255;255;48;2;181;136;99m[K♙ [m[K > + [38;2;255;255;255;48;2;181;136;99m[K♖ [38;2;255;255;255;48;2;240;217;181m[K♘ [38;2;255;255;255;48;2;181;136;99m[K♗ [38;2;255;255;255;48;2;240;217;181m[K♕ [38;2;255;255;255;48;2;181;136;99m[K♔ [38;2;255;255;255;48;2;240;217;181m[K♗ [38;2;255;255;255;48;2;181;136;99m[K♘ [38;2;255;255;255;48;2;240;217;181m[K♖ [m[K > + > + { dg-end-multiline-output "" } */ > + > +/* { dg-begin-multiline-output "" } > + > + +--+ > + |🙂| > + +--+ > + > + { dg-end-multiline-output "" } */ > + > +/* { dg-begin-multiline-output "" } > + > + +-------+-----+---------------+---------------------+-----------------------+-----------------------+ > + |Offsets|Octet| 0 | 1 | 2 | 3 | > + +-------+-----+-+-+-+-+-+-+-+-+-+-+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ > + | Octet | Bit |0|1|2|3|4|5|6|7|8|9|10|11|12|13|14|15|16|17|18|19|20|21|22|23|24|25|26|27|28|29|30|31| > + +-------+-----+-+-+-+-+-+-+-+-+-+-+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ > + | 0 | 0 |Version| IHL | DSCP | ECN | Total Length | > + +-------+-----+-------+-------+---------------+-----+--------+--------------------------------------+ > + | 4 | 32 | Identification | Flags | Fragment Offset | > + +-------+-----+---------------+---------------------+--------+--------------------------------------+ > + | 8 | 64 | Time To Live | Protocol | Header Checksum | > + +-------+-----+---------------+---------------------+-----------------------------------------------+ > + | 12 | 96 | Source IP Address | > + +-------+-----+-------------------------------------------------------------------------------------+ > + | 16 | 128 | Destination IP Address | > + +-------+-----+-------------------------------------------------------------------------------------+ > + | 20 | 160 | | > + +-------+-----+ | > + | ... | ... | Options | > + +-------+-----+ | > + | 56 | 448 | | > + +-------+-----+-------------------------------------------------------------------------------------+ > + > + { dg-end-multiline-output "" } */ > diff --git a/gcc/testsuite/gcc.dg/plugin/diagnostic-test-text-art-none.c b/gcc/testsuite/gcc.dg/plugin/diagnostic-test-text-art-none.c > new file mode 100644 > index 00000000000..c8118b46759 > --- /dev/null > +++ b/gcc/testsuite/gcc.dg/plugin/diagnostic-test-text-art-none.c > @@ -0,0 +1,5 @@ > +/* { dg-additional-options "-fdiagnostics-text-art-charset=none" } */ > + > +int non_empty; > + > +/* We expect no output. */ > diff --git a/gcc/testsuite/gcc.dg/plugin/diagnostic-test-text-art-unicode-bw.c b/gcc/testsuite/gcc.dg/plugin/diagnostic-test-text-art-unicode-bw.c > new file mode 100644 > index 00000000000..c9f5b36571a > --- /dev/null > +++ b/gcc/testsuite/gcc.dg/plugin/diagnostic-test-text-art-unicode-bw.c > @@ -0,0 +1,58 @@ > +/* { dg-additional-options "-fdiagnostics-text-art-charset=unicode -fdiagnostics-color=never" } */ > + > +int non_empty; > + > +/* { dg-begin-multiline-output "" } > + > + A > + B > + C > + > + { dg-end-multiline-output "" } */ > + > +/* { dg-begin-multiline-output "" } > + > + ♜ ♞ ♝ ♛ ♚ ♝ ♞ ♜ > + ♟ ♟ ♟ ♟ ♟ ♟ ♟ ♟ > + > + > + > + > + ♙ ♙ ♙ ♙ ♙ ♙ ♙ ♙ > + ♖ ♘ ♗ ♕ ♔ ♗ ♘ ♖ > + > + { dg-end-multiline-output "" } */ > + > +/* { dg-begin-multiline-output "" } > + > + ┌──┐ > + │🙂│ > + └──┘ > + > + { dg-end-multiline-output "" } */ > + > +/* { dg-begin-multiline-output "" } > + > + ┌───────┬─────┬───────────────┬─────────────────────┬───────────────────────┬───────────────────────┐ > + │Offsets│Octet│ 0 │ 1 │ 2 │ 3 │ > + ├───────┼─────┼─┬─┬─┬─┬─┬─┬─┬─┼─┬─┬──┬──┬──┬──┬──┬──┼──┬──┬──┬──┬──┬──┬──┬──┼──┬──┬──┬──┬──┬──┬──┬──┤ > + │ Octet │ Bit │0│1│2│3│4│5│6│7│8│9│10│11│12│13│14│15│16│17│18│19│20│21│22│23│24│25│26│27│28│29│30│31│ > + ├───────┼─────┼─┴─┴─┴─┼─┴─┴─┴─┼─┴─┴──┴──┴──┴──┼──┴──┼──┴──┴──┴──┴──┴──┴──┴──┴──┴──┴──┴──┴──┴──┴──┴──┤ > + │ 0 │ 0 │Version│ IHL │ DSCP │ ECN │ Total Length │ > + ├───────┼─────┼───────┴───────┴───────────────┴─────┼────────┬──────────────────────────────────────┤ > + │ 4 │ 32 │ Identification │ Flags │ Fragment Offset │ > + ├───────┼─────┼───────────────┬─────────────────────┼────────┴──────────────────────────────────────┤ > + │ 8 │ 64 │ Time To Live │ Protocol │ Header Checksum │ > + ├───────┼─────┼───────────────┴─────────────────────┴───────────────────────────────────────────────┤ > + │ 12 │ 96 │ Source IP Address │ > + ├───────┼─────┼─────────────────────────────────────────────────────────────────────────────────────┤ > + │ 16 │ 128 │ Destination IP Address │ > + ├───────┼─────┼─────────────────────────────────────────────────────────────────────────────────────┤ > + │ 20 │ 160 │ │ > + ├───────┼─────┤ │ > + │ ... │ ... │ Options │ > + ├───────┼─────┤ │ > + │ 56 │ 448 │ │ > + └───────┴─────┴─────────────────────────────────────────────────────────────────────────────────────┘ > + > + { dg-end-multiline-output "" } */ > diff --git a/gcc/testsuite/gcc.dg/plugin/diagnostic-test-text-art-unicode-color.c b/gcc/testsuite/gcc.dg/plugin/diagnostic-test-text-art-unicode-color.c > new file mode 100644 > index 00000000000..f402836f889 > --- /dev/null > +++ b/gcc/testsuite/gcc.dg/plugin/diagnostic-test-text-art-unicode-color.c > @@ -0,0 +1,59 @@ > +/* { dg-additional-options "-fdiagnostics-text-art-charset=unicode -fdiagnostics-color=always" } */ > + > +int non_empty; > + > + > +/* { dg-begin-multiline-output "" } > + > + A > + B > + C > + > + { dg-end-multiline-output "" } */ > + > +/* { dg-begin-multiline-output "" } > + > + [38;2;0;0;0;48;2;240;217;181m[K♜ [38;2;0;0;0;48;2;181;136;99m[K♞ [38;2;0;0;0;48;2;240;217;181m[K♝ [38;2;0;0;0;48;2;181;136;99m[K♛ [38;2;0;0;0;48;2;240;217;181m[K♚ [38;2;0;0;0;48;2;181;136;99m[K♝ [38;2;0;0;0;48;2;240;217;181m[K♞ [38;2;0;0;0;48;2;181;136;99m[K♜ [m[K > + [38;2;0;0;0;48;2;181;136;99m[K♟ [38;2;0;0;0;48;2;240;217;181m[K♟ [38;2;0;0;0;48;2;181;136;99m[K♟ [38;2;0;0;0;48;2;240;217;181m[K♟ [38;2;0;0;0;48;2;181;136;99m[K♟ [38;2;0;0;0;48;2;240;217;181m[K♟ [38;2;0;0;0;48;2;181;136;99m[K♟ [38;2;0;0;0;48;2;240;217;181m[K♟ [m[K > + [48;2;240;217;181m[K [48;2;181;136;99m[K [48;2;240;217;181m[K [48;2;181;136;99m[K [48;2;240;217;181m[K [48;2;181;136;99m[K [48;2;240;217;181m[K [48;2;181;136;99m[K [m[K > + [48;2;181;136;99m[K [48;2;240;217;181m[K [48;2;181;136;99m[K [48;2;240;217;181m[K [48;2;181;136;99m[K [48;2;240;217;181m[K [48;2;181;136;99m[K [48;2;240;217;181m[K [m[K > + [48;2;240;217;181m[K [48;2;181;136;99m[K [48;2;240;217;181m[K [48;2;181;136;99m[K [48;2;240;217;181m[K [48;2;181;136;99m[K [48;2;240;217;181m[K [48;2;181;136;99m[K [m[K > + [48;2;181;136;99m[K [48;2;240;217;181m[K [48;2;181;136;99m[K [48;2;240;217;181m[K [48;2;181;136;99m[K [48;2;240;217;181m[K [48;2;181;136;99m[K [48;2;240;217;181m[K [m[K > + [38;2;255;255;255;48;2;240;217;181m[K♙ [38;2;255;255;255;48;2;181;136;99m[K♙ [38;2;255;255;255;48;2;240;217;181m[K♙ [38;2;255;255;255;48;2;181;136;99m[K♙ [38;2;255;255;255;48;2;240;217;181m[K♙ [38;2;255;255;255;48;2;181;136;99m[K♙ [38;2;255;255;255;48;2;240;217;181m[K♙ [38;2;255;255;255;48;2;181;136;99m[K♙ [m[K > + [38;2;255;255;255;48;2;181;136;99m[K♖ [38;2;255;255;255;48;2;240;217;181m[K♘ [38;2;255;255;255;48;2;181;136;99m[K♗ [38;2;255;255;255;48;2;240;217;181m[K♕ [38;2;255;255;255;48;2;181;136;99m[K♔ [38;2;255;255;255;48;2;240;217;181m[K♗ [38;2;255;255;255;48;2;181;136;99m[K♘ [38;2;255;255;255;48;2;240;217;181m[K♖ [m[K > + > + { dg-end-multiline-output "" } */ > + > +/* { dg-begin-multiline-output "" } > + > + ┌──┐ > + │🙂│ > + └──┘ > + > + { dg-end-multiline-output "" } */ > + > +/* { dg-begin-multiline-output "" } > + > + ┌───────┬─────┬───────────────┬─────────────────────┬───────────────────────┬───────────────────────┐ > + │Offsets│Octet│ 0 │ 1 │ 2 │ 3 │ > + ├───────┼─────┼─┬─┬─┬─┬─┬─┬─┬─┼─┬─┬──┬──┬──┬──┬──┬──┼──┬──┬──┬──┬──┬──┬──┬──┼──┬──┬──┬──┬──┬──┬──┬──┤ > + │ Octet │ Bit │0│1│2│3│4│5│6│7│8│9│10│11│12│13│14│15│16│17│18│19│20│21│22│23│24│25│26│27│28│29│30│31│ > + ├───────┼─────┼─┴─┴─┴─┼─┴─┴─┴─┼─┴─┴──┴──┴──┴──┼──┴──┼──┴──┴──┴──┴──┴──┴──┴──┴──┴──┴──┴──┴──┴──┴──┴──┤ > + │ 0 │ 0 │Version│ IHL │ DSCP │ ECN │ Total Length │ > + ├───────┼─────┼───────┴───────┴───────────────┴─────┼────────┬──────────────────────────────────────┤ > + │ 4 │ 32 │ Identification │ Flags │ Fragment Offset │ > + ├───────┼─────┼───────────────┬─────────────────────┼────────┴──────────────────────────────────────┤ > + │ 8 │ 64 │ Time To Live │ Protocol │ Header Checksum │ > + ├───────┼─────┼───────────────┴─────────────────────┴───────────────────────────────────────────────┤ > + │ 12 │ 96 │ Source IP Address │ > + ├───────┼─────┼─────────────────────────────────────────────────────────────────────────────────────┤ > + │ 16 │ 128 │ Destination IP Address │ > + ├───────┼─────┼─────────────────────────────────────────────────────────────────────────────────────┤ > + │ 20 │ 160 │ │ > + ├───────┼─────┤ │ > + │ ... │ ... │ Options │ > + ├───────┼─────┤ │ > + │ 56 │ 448 │ │ > + └───────┴─────┴─────────────────────────────────────────────────────────────────────────────────────┘ > + > + { dg-end-multiline-output "" } */ > diff --git a/gcc/testsuite/gcc.dg/plugin/diagnostic_plugin_test_text_art.c b/gcc/testsuite/gcc.dg/plugin/diagnostic_plugin_test_text_art.c > new file mode 100644 > index 00000000000..27c341b9f2f > --- /dev/null > +++ b/gcc/testsuite/gcc.dg/plugin/diagnostic_plugin_test_text_art.c > @@ -0,0 +1,257 @@ > +/* { dg-options "-O" } */ > + > +/* This plugin exercises the text_art code. */ > + > +#include "gcc-plugin.h" > +#include "config.h" > +#include "system.h" > +#include "coretypes.h" > +#include "plugin-version.h" > +#include "diagnostic.h" > +#include "diagnostic-diagram.h" > +#include "text-art/canvas.h" > +#include "text-art/table.h" > + > +int plugin_is_GPL_compatible; > + > +using namespace text_art; > + > +/* Canvas tests. */ > + > +static void > +emit_canvas (const canvas &c, const char *alt_text) > +{ > + diagnostic_diagram diagram (c, alt_text); > + diagnostic_emit_diagram (global_dc, diagram); > +} > + > +static void > +test_abc () > +{ > + style_manager sm; > + canvas c (canvas::size_t (3, 3), sm); > + c.paint (canvas::coord_t (0, 0), styled_unichar ('A')); > + c.paint (canvas::coord_t (1, 1), styled_unichar ('B')); > + c.paint (canvas::coord_t (2, 2), styled_unichar ('C')); > + emit_canvas (c, "test_abc"); > +} > + > +/* Test of procedural art using 24-bit color: chess starting position. */ > + > +static void > +test_chessboard () > +{ > + /* With the exception of NONE, these are in order of the chess symbols > + in the Unicode Miscellaneous Symbols block. */ > + enum class piece { KING, QUEEN, ROOK, BISHOP, KNIGHT, PAWN, NONE }; > + enum class color { BLACK, WHITE, NONE }; > + > + style_manager sm; > + > + /* We assume double-column chars for the pieces, so allow two canvas > + columns per square. */ > + canvas canvas (canvas::size_t (16, 8), sm); > + > + for (int x = 0; x < 8; x++) > + for (int y = 0; y < 8; y++) > + { > + enum piece piece_kind; > + enum color piece_color; > + switch (y) > + { > + case 0: > + case 7: > + switch (x) > + { > + default: > + gcc_unreachable (); > + case 0: > + piece_kind = piece::ROOK; > + break; > + case 1: > + piece_kind = piece::KNIGHT; > + break; > + case 2: > + piece_kind = piece::BISHOP; > + break; > + case 3: > + piece_kind = piece::QUEEN; > + break; > + case 4: > + piece_kind = piece::KING; > + break; > + case 5: > + piece_kind = piece::BISHOP; > + break; > + case 6: > + piece_kind = piece::KNIGHT; > + break; > + case 7: > + piece_kind = piece::ROOK; > + break; > + } > + piece_color = (y == 0) ? color::BLACK : color::WHITE; > + break; > + case 1: > + case 6: > + piece_kind = piece::PAWN; > + piece_color = (y == 1) ? color::BLACK : color::WHITE; > + break; > + default: > + piece_kind = piece::NONE; > + piece_color = color::NONE; > + break; > + } > + > + style s; > + const bool white_square = (x + y) % 2 == 0; > + if (white_square) > + s.m_bg_color = style::color (0xf0, 0xd9, 0xb5); > + else > + s.m_bg_color = style::color (0xb5, 0x88, 0x63); > + switch (piece_color) > + { > + default: > + gcc_unreachable (); > + case color::WHITE: > + s.m_fg_color = style::color (0xff, 0xff, 0xff); > + break; > + case color::BLACK: > + s.m_fg_color = style::color (0x00, 0x00, 0x00); > + break; > + case color::NONE: > + break; > + } > + style::id_t style_id = sm.get_or_create_id (s); > + > + cppchar_t ch; > + if (piece_kind == piece::NONE) > + ch = ' '; > + else > + { > + const cppchar_t WHITE_KING = 0x2654; > + const cppchar_t BLACK_KING = 0x265A; > + cppchar_t base ((piece_color == color::WHITE) > + ? WHITE_KING : BLACK_KING); > + ch = base + ((int)piece_kind - (int)piece::KING); > + } > + canvas.paint (canvas::coord_t (x * 2, y), > + canvas::cell_t (ch, false, style_id)); > + canvas.paint (canvas::coord_t (x * 2 + 1, y), > + canvas::cell_t (' ', false, style_id)); > + } > + emit_canvas (canvas, "test_chessboard"); > +} > + > +/* Table tests. */ > + > +static void > +emit_table (const table &table, const style_manager &sm, const char *alt_text) > +{ > + const text_art::theme *theme = global_dc->m_diagrams.m_theme; > + if (!theme) > + return; > + canvas c (table.to_canvas (*theme, sm)); > + emit_canvas (c, alt_text); > +} > + > +static void > +test_double_width_chars () > +{ > + style_manager sm; > + table table (table::size_t (1, 1)); > + table.set_cell (table::coord_t (0,0), > + styled_string ((cppchar_t)0x1f642)); > + > + emit_table (table, sm, "test_double_width_chars"); > +} > + > +static void > +test_ipv4_header () > +{ > + style_manager sm; > + table table (table::size_t (34, 10)); > + table.set_cell (table::coord_t (0, 0), styled_string (sm, "Offsets")); > + table.set_cell (table::coord_t (1, 0), styled_string (sm, "Octet")); > + table.set_cell (table::coord_t (0, 1), styled_string (sm, "Octet")); > + for (int octet = 0; octet < 4; octet++) > + table.set_cell_span (table::rect_t (table::coord_t (2 + (octet * 8), 0), > + table::size_t (8, 1)), > + styled_string::from_fmt (sm, nullptr, "%i", octet)); > + table.set_cell (table::coord_t (1, 1), styled_string (sm, "Bit")); > + for (int bit = 0; bit < 32; bit++) > + table.set_cell (table::coord_t (bit + 2, 1), > + styled_string::from_fmt (sm, nullptr, "%i", bit)); > + for (int word = 0; word < 6; word++) > + { > + table.set_cell (table::coord_t (0, word + 2), > + styled_string::from_fmt (sm, nullptr, "%i", word * 4)); > + table.set_cell (table::coord_t (1, word + 2), > + styled_string::from_fmt (sm, nullptr, "%i", word * 32)); > + } > + > + table.set_cell (table::coord_t (0, 8), styled_string (sm, "...")); > + table.set_cell (table::coord_t (1, 8), styled_string (sm, "...")); > + table.set_cell (table::coord_t (0, 9), styled_string (sm, "56")); > + table.set_cell (table::coord_t (1, 9), styled_string (sm, "448")); > + > +#define SET_BITS(FIRST, LAST, NAME) \ > + do { \ > + const int first = (FIRST); \ > + const int last = (LAST); \ > + const char *name = (NAME); \ > + const int row = first / 32; \ > + gcc_assert (last / 32 == row); \ > + table::rect_t rect (table::coord_t ((first % 32) + 2, row + 2), \ > + table::size_t (last + 1 - first , 1)); \ > + table.set_cell_span (rect, styled_string (sm, name)); \ > + } while (0) > + > + SET_BITS (0, 3, "Version"); > + SET_BITS (4, 7, "IHL"); > + SET_BITS (8, 13, "DSCP"); > + SET_BITS (14, 15, "ECN"); > + SET_BITS (16, 31, "Total Length"); > + > + SET_BITS (32 + 0, 32 + 15, "Identification"); > + SET_BITS (32 + 16, 32 + 18, "Flags"); > + SET_BITS (32 + 19, 32 + 31, "Fragment Offset"); > + > + SET_BITS (64 + 0, 64 + 7, "Time To Live"); > + SET_BITS (64 + 8, 64 + 15, "Protocol"); > + SET_BITS (64 + 16, 64 + 31, "Header Checksum"); > + > + SET_BITS (96 + 0, 96 + 31, "Source IP Address"); > + SET_BITS (128 + 0, 128 + 31, "Destination IP Address"); > + > + table.set_cell_span(table::rect_t (table::coord_t (2, 7), > + table::size_t (32, 3)), > + styled_string (sm, "Options")); > + > + emit_table (table, sm, "test_ipv4_header"); > +} > + > +static void > +show_diagrams () > +{ > + test_abc (); > + test_chessboard (); > + test_double_width_chars (); > + test_ipv4_header (); > +} > + > +int > +plugin_init (struct plugin_name_args *plugin_info, > + struct plugin_gcc_version *version) > +{ > + const char *plugin_name = plugin_info->base_name; > + int argc = plugin_info->argc; > + struct plugin_argument *argv = plugin_info->argv; > + > + if (!plugin_default_version_check (version, &gcc_version)) > + return 1; > + > + show_diagrams (); > + > + return 0; > +} > diff --git a/gcc/testsuite/gcc.dg/plugin/plugin.exp b/gcc/testsuite/gcc.dg/plugin/plugin.exp > index 4d6304cd100..60723a20eda 100644 > --- a/gcc/testsuite/gcc.dg/plugin/plugin.exp > +++ b/gcc/testsuite/gcc.dg/plugin/plugin.exp > @@ -114,6 +114,12 @@ set plugin_test_list [list \ > diagnostic-path-format-inline-events-1.c \ > diagnostic-path-format-inline-events-2.c \ > diagnostic-path-format-inline-events-3.c } \ > + { diagnostic_plugin_test_text_art.c \ > + diagnostic-test-text-art-none.c \ > + diagnostic-test-text-art-ascii-bw.c \ > + diagnostic-test-text-art-ascii-color.c \ > + diagnostic-test-text-art-unicode-bw.c \ > + diagnostic-test-text-art-unicode-color.c } \ > { location_overflow_plugin.c \ > location-overflow-test-1.c \ > location-overflow-test-2.c \ > diff --git a/gcc/text-art/box-drawing-chars.inc b/gcc/text-art/box-drawing-chars.inc > new file mode 100644 > index 00000000000..a370255d56d > --- /dev/null > +++ b/gcc/text-art/box-drawing-chars.inc > @@ -0,0 +1,18 @@ > +/* Generated by contrib/unicode/gen-box-drawing-chars.py. */ > + > +0x0020, /* " ": U+0020: SPACE */ > +0x2576, /* "╶": U+2576: BOX DRAWINGS LIGHT RIGHT */ > +0x2574, /* "╴": U+2574: BOX DRAWINGS LIGHT LEFT */ > +0x2500, /* "─": U+2500: BOX DRAWINGS LIGHT HORIZONTAL */ > +0x2577, /* "╷": U+2577: BOX DRAWINGS LIGHT DOWN */ > +0x250C, /* "┌": U+250C: BOX DRAWINGS LIGHT DOWN AND RIGHT */ > +0x2510, /* "┐": U+2510: BOX DRAWINGS LIGHT DOWN AND LEFT */ > +0x252C, /* "┬": U+252C: BOX DRAWINGS LIGHT DOWN AND HORIZONTAL */ > +0x2575, /* "╵": U+2575: BOX DRAWINGS LIGHT UP */ > +0x2514, /* "└": U+2514: BOX DRAWINGS LIGHT UP AND RIGHT */ > +0x2518, /* "┘": U+2518: BOX DRAWINGS LIGHT UP AND LEFT */ > +0x2534, /* "┴": U+2534: BOX DRAWINGS LIGHT UP AND HORIZONTAL */ > +0x2502, /* "│": U+2502: BOX DRAWINGS LIGHT VERTICAL */ > +0x251C, /* "├": U+251C: BOX DRAWINGS LIGHT VERTICAL AND RIGHT */ > +0x2524, /* "┤": U+2524: BOX DRAWINGS LIGHT VERTICAL AND LEFT */ > +0x253C /* "┼": U+253C: BOX DRAWINGS LIGHT VERTICAL AND HORIZONTAL */ > diff --git a/gcc/text-art/box-drawing.cc b/gcc/text-art/box-drawing.cc > new file mode 100644 > index 00000000000..981d0b095cf > --- /dev/null > +++ b/gcc/text-art/box-drawing.cc > @@ -0,0 +1,72 @@ > +/* Procedural lookup of box drawing characters. > + Copyright (C) 2023 Free Software Foundation, Inc. > + Contributed by David Malcolm <dmalcolm@redhat.com>. > + > +This file is part of GCC. > + > +GCC is free software; you can redistribute it and/or modify it under > +the terms of the GNU General Public License as published by the Free > +Software Foundation; either version 3, or (at your option) any later > +version. > + > +GCC is distributed in the hope that it will be useful, but WITHOUT ANY > +WARRANTY; without even the implied warranty of MERCHANTABILITY or > +FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License > +for more details. > + > +You should have received a copy of the GNU General Public License > +along with GCC; see the file COPYING3. If not see > +<http://www.gnu.org/licenses/>. */ > + > +#include "config.h" > +#include "system.h" > +#include "coretypes.h" > +#include "text-art/box-drawing.h" > +#include "selftest.h" > +#include "text-art/selftests.h" > + > + > +/* According to > + https://en.wikipedia.org/wiki/Box-drawing_character#Character_code > + "DOS line- and box-drawing characters are not ordered in any programmatic > + manner, so calculating a particular character shape needs to use a look-up > + table. " > + Hence this array. */ > +static const cppchar_t box_drawing_chars[] = { > +#include "text-art/box-drawing-chars.inc" > +}; > + > +cppchar_t > +text_art::get_box_drawing_char (directions line_dirs) > +{ > + const size_t idx = line_dirs.as_index (); > + gcc_assert (idx < 16); > + return box_drawing_chars[idx]; > +} > + > +#if CHECKING_P > + > +namespace selftest { > + > +/* Run all selftests in this file. */ > + > +void > +text_art_box_drawing_cc_tests () > +{ > + ASSERT_EQ (text_art::get_box_drawing_char > + (text_art::directions (false, false, false, false)), > + ' '); > + ASSERT_EQ (text_art::get_box_drawing_char > + (text_art::directions (false, false, true, true)), > + 0x2500); /* BOX DRAWINGS LIGHT HORIZONTAL */ > + ASSERT_EQ (text_art::get_box_drawing_char > + (text_art::directions (true, true, false, false)), > + 0x2502); /* BOX DRAWINGS LIGHT VERTICAL */ > + ASSERT_EQ (text_art::get_box_drawing_char > + (text_art::directions (true, false, true, false)), > + 0x2518); /* BOX DRAWINGS LIGHT UP AND LEFT */ > +} > + > +} // namespace selftest > + > +#endif /* #if CHECKING_P */ > diff --git a/gcc/text-art/box-drawing.h b/gcc/text-art/box-drawing.h > new file mode 100644 > index 00000000000..29f4d9921b3 > --- /dev/null > +++ b/gcc/text-art/box-drawing.h > @@ -0,0 +1,32 @@ > +/* Procedural lookup of box drawing characters. > + Copyright (C) 2023 Free Software Foundation, Inc. > + Contributed by David Malcolm <dmalcolm@redhat.com>. > + > +This file is part of GCC. > + > +GCC is free software; you can redistribute it and/or modify it > +under the terms of the GNU General Public License as published by > +the Free Software Foundation; either version 3, or (at your option) > +any later version. > + > +GCC is distributed in the hope that it will be useful, but > +WITHOUT ANY WARRANTY; without even the implied warranty of > +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU > +General Public License for more details. > + > +You should have received a copy of the GNU General Public License > +along with GCC; see the file COPYING3. If not see > +<http://www.gnu.org/licenses/>. */ > + > +#ifndef GCC_TEXT_ART_BOX_DRAWING_H > +#define GCC_TEXT_ART_BOX_DRAWING_H > + > +#include "text-art/types.h" > + > +namespace text_art { > + > +extern cppchar_t get_box_drawing_char (directions line_dirs); > + > +} // namespace text_art > + > +#endif /* GCC_TEXT_ART_BOX_DRAWING_H */ > diff --git a/gcc/text-art/canvas.cc b/gcc/text-art/canvas.cc > new file mode 100644 > index 00000000000..f229612c919 > --- /dev/null > +++ b/gcc/text-art/canvas.cc > @@ -0,0 +1,437 @@ > +/* Canvas for random-access procedural text art. > + Copyright (C) 2023 Free Software Foundation, Inc. > + Contributed by David Malcolm <dmalcolm@redhat.com>. > + > +This file is part of GCC. > + > +GCC is free software; you can redistribute it and/or modify it under > +the terms of the GNU General Public License as published by the Free > +Software Foundation; either version 3, or (at your option) any later > +version. > + > +GCC is distributed in the hope that it will be useful, but WITHOUT ANY > +WARRANTY; without even the implied warranty of MERCHANTABILITY or > +FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License > +for more details. > + > +You should have received a copy of the GNU General Public License > +along with GCC; see the file COPYING3. If not see > +<http://www.gnu.org/licenses/>. */ > + > +#include "config.h" > +#include "system.h" > +#include "coretypes.h" > +#include "pretty-print.h" > +#include "selftest.h" > +#include "text-art/selftests.h" > +#include "text-art/canvas.h" > + > +using namespace text_art; > + > +canvas::canvas (size_t size, const style_manager &style_mgr) > +: m_cells (size_t (size.w, size.h)), > + m_style_mgr (style_mgr) > +{ > + m_cells.fill (cell_t (' ')); > +} > + > +void > +canvas::paint (coord_t coord, styled_unichar ch) > +{ > + m_cells.set (coord, std::move (ch)); > +} > + > +void > +canvas::paint_text (coord_t coord, const styled_string &text) > +{ > + for (auto ch : text) > + { > + paint (coord, ch); > + if (ch.double_width_p ()) > + coord.x += 2; > + else > + coord.x++; > + } > +} > + > +void > +canvas::fill (rect_t rect, cell_t c) > +{ > + for (int y = rect.get_min_y (); y < rect.get_next_y (); y++) > + for (int x = rect.get_min_x (); x < rect.get_next_x (); x++) > + paint(coord_t (x, y), c); > +} > + > +void > +canvas::debug_fill () > +{ > + fill (rect_t (coord_t (0, 0), get_size ()), cell_t ('*')); > +} > + > +void > +canvas::print_to_pp (pretty_printer *pp, > + const char *per_line_prefix) const > +{ > + for (int y = 0; y < m_cells.get_size ().h; y++) > + { > + style::id_t curr_style_id = 0; > + if (per_line_prefix) > + pp_string (pp, per_line_prefix); > + > + pretty_printer line_pp; > + line_pp.show_color = pp->show_color; > + line_pp.url_format = pp->url_format; > + const int final_x_in_row = get_final_x_in_row (y); > + for (int x = 0; x <= final_x_in_row; x++) > + { > + if (x > 0) > + { > + const cell_t prev_cell = m_cells.get (coord_t (x - 1, y)); > + if (prev_cell.double_width_p ()) > + /* This cell is just a placeholder for the > + 2nd column of a double width cell; skip it. */ > + continue; > + } > + const cell_t cell = m_cells.get (coord_t (x, y)); > + if (cell.get_style_id () != curr_style_id) > + { > + m_style_mgr.print_any_style_changes (&line_pp, > + curr_style_id, > + cell.get_style_id ()); > + curr_style_id = cell.get_style_id (); > + } > + pp_unicode_character (&line_pp, cell.get_code ()); > + if (cell.emoji_variant_p ()) > + /* Append U+FE0F VARIATION SELECTOR-16 to select the emoji > + variation of the char. */ > + pp_unicode_character (&line_pp, 0xFE0F); > + } > + /* Reset the style at the end of each line. */ > + m_style_mgr.print_any_style_changes (&line_pp, curr_style_id, 0); > + > + /* Print from line_pp to pp, stripping trailing whitespace from > + the line. */ > + const char *line_buf = pp_formatted_text (&line_pp); > + ::size_t len = strlen (line_buf); > + while (len > 0) > + { > + if (line_buf[len - 1] == ' ') > + len--; > + else > + break; > + } > + pp_append_text (pp, line_buf, line_buf + len); > + pp_newline (pp); > + } > +} > + > +DEBUG_FUNCTION void > +canvas::debug (bool styled) const > +{ > + pretty_printer pp; > + if (styled) > + { > + pp_show_color (&pp) = true; > + pp.url_format = determine_url_format (DIAGNOSTICS_URL_AUTO); > + } > + print_to_pp (&pp); > + fprintf (stderr, "%s\n", pp_formatted_text (&pp)); > +} > + > +/* Find right-most non-default cell in this row, > + or -1 if all are default. */ > + > +int > +canvas::get_final_x_in_row (int y) const > +{ > + for (int x = m_cells.get_size ().w - 1; x >= 0; x--) > + { > + cell_t cell = m_cells.get (coord_t (x, y)); > + if (cell.get_code () != ' ' > + || cell.get_style_id () != style::id_plain) > + return x; > + } > + return -1; > +} > + > +#if CHECKING_P > + > +namespace selftest { > + > +static void > +test_blank () > +{ > + style_manager sm; > + canvas c (canvas::size_t (5, 5), sm); > + ASSERT_CANVAS_STREQ (c, false, > + ("\n" > + "\n" > + "\n" > + "\n" > + "\n")); > +} > + > +static void > +test_abc () > +{ > + style_manager sm; > + canvas c (canvas::size_t (3, 3), sm); > + c.paint (canvas::coord_t (0, 0), styled_unichar ('A')); > + c.paint (canvas::coord_t (1, 1), styled_unichar ('B')); > + c.paint (canvas::coord_t (2, 2), styled_unichar ('C')); > + > + ASSERT_CANVAS_STREQ (c, false, > + "A\n B\n C\n"); > +} > + > +static void > +test_debug_fill () > +{ > + style_manager sm; > + canvas c (canvas::size_t (5, 3), sm); > + c.debug_fill(); > + ASSERT_CANVAS_STREQ (c, false, > + ("*****\n" > + "*****\n" > + "*****\n")); > +} > + > +static void > +test_text () > +{ > + style_manager sm; > + canvas c (canvas::size_t (6, 1), sm); > + c.paint_text (canvas::coord_t (0, 0), styled_string (sm, "012345")); > + ASSERT_CANVAS_STREQ (c, false, > + ("012345\n")); > + > + /* Paint an emoji character that should occupy two canvas columns when > + printed. */ > + c.paint_text (canvas::coord_t (2, 0), styled_string ((cppchar_t)0x1f642)); > + ASSERT_CANVAS_STREQ (c, false, > + ("01🙂45\n")); > +} > + > +static void > +test_circle () > +{ > + canvas::size_t sz (30, 30); > + style_manager sm; > + canvas canvas (sz, sm); > + canvas::coord_t center (sz.w / 2, sz.h / 2); > + const int radius = 12; > + const int radius_squared = radius * radius; > + for (int x = 0; x < sz.w; x++) > + for (int y = 0; y < sz.h; y++) > + { > + int dx = x - center.x; > + int dy = y - center.y; > + char ch = "AB"[(x + y) % 2]; > + if (dx * dx + dy * dy < radius_squared) > + canvas.paint (canvas::coord_t (x, y), styled_unichar (ch)); > + } > + ASSERT_CANVAS_STREQ > + (canvas, false, > + ("\n" > + "\n" > + "\n" > + "\n" > + " BABABABAB\n" > + " ABABABABABABA\n" > + " ABABABABABABABA\n" > + " ABABABABABABABABA\n" > + " ABABABABABABABABABA\n" > + " ABABABABABABABABABABA\n" > + " BABABABABABABABABABAB\n" > + " BABABABABABABABABABABAB\n" > + " ABABABABABABABABABABABA\n" > + " BABABABABABABABABABABAB\n" > + " ABABABABABABABABABABABA\n" > + " BABABABABABABABABABABAB\n" > + " ABABABABABABABABABABABA\n" > + " BABABABABABABABABABABAB\n" > + " ABABABABABABABABABABABA\n" > + " BABABABABABABABABABABAB\n" > + " BABABABABABABABABABAB\n" > + " ABABABABABABABABABABA\n" > + " ABABABABABABABABABA\n" > + " ABABABABABABABABA\n" > + " ABABABABABABABA\n" > + " ABABABABABABA\n" > + " BABABABAB\n" > + "\n" > + "\n" > + "\n")); > +} > + > +static void > +test_color_circle () > +{ > + const canvas::size_t sz (10, 10); > + const canvas::coord_t center (sz.w / 2, sz.h / 2); > + const int outer_r2 = 25; > + const int inner_r2 = 10; > + style_manager sm; > + canvas c (sz, sm); > + for (int x = 0; x < sz.w; x++) > + for (int y = 0; y < sz.h; y++) > + { > + const int dist_from_center_squared > + = ((x - center.x) * (x - center.x) + (y - center.y) * (y - center.y)); > + if (dist_from_center_squared < outer_r2) > + { > + style s; > + if (dist_from_center_squared < inner_r2) > + s.m_fg_color = style::named_color::RED; > + else > + s.m_fg_color = style::named_color::GREEN; > + c.paint (canvas::coord_t (x, y), > + styled_unichar ('*', false, sm.get_or_create_id (s))); > + } > + } > + ASSERT_EQ (sm.get_num_styles (), 3); > + ASSERT_CANVAS_STREQ > + (c, false, > + ("\n" > + " *****\n" > + " *******\n" > + " *********\n" > + " *********\n" > + " *********\n" > + " *********\n" > + " *********\n" > + " *******\n" > + " *****\n")); > + ASSERT_CANVAS_STREQ > + (c, true, > + ("\n" > + " [32m[K*****[m[K\n" > + " [32m[K***[31m[K*[32m[K***[m[K\n" > + " [32m[K**[31m[K*****[32m[K**[m[K\n" > + " [32m[K**[31m[K*****[32m[K**[m[K\n" > + " [32m[K*[31m[K*******[32m[K*[m[K\n" > + " [32m[K**[31m[K*****[32m[K**[m[K\n" > + " [32m[K**[31m[K*****[32m[K**[m[K\n" > + " [32m[K***[31m[K*[32m[K***[m[K\n" > + " [32m[K*****[m[K\n")); > +} > + > +static void > +test_bold () > +{ > + auto_fix_quotes fix_quotes; > + style_manager sm; > + styled_string s (styled_string::from_fmt (sm, nullptr, > + "before %qs after", "foo")); > + canvas c (canvas::size_t (s.calc_canvas_width (), 1), sm); > + c.paint_text (canvas::coord_t (0, 0), s); > + ASSERT_CANVAS_STREQ (c, false, > + "before `foo' after\n"); > + ASSERT_CANVAS_STREQ (c, true, > + "before `[00;01m[Kfoo[00m[K' after\n"); > +} > + > +static void > +test_emoji () > +{ > + style_manager sm; > + styled_string s (0x26A0, /* U+26A0 WARNING SIGN. */ > + true); > + canvas c (canvas::size_t (s.calc_canvas_width (), 1), sm); > + c.paint_text (canvas::coord_t (0, 0), s); > + ASSERT_CANVAS_STREQ (c, false, "⚠️\n"); > + ASSERT_CANVAS_STREQ (c, true, "⚠️\n"); > +} > + > +static void > +test_emoji_2 () > +{ > + style_manager sm; > + styled_string s; > + s.append (styled_string (0x26A0, /* U+26A0 WARNING SIGN. */ > + true)); > + s.append (styled_string (sm, "test")); > + ASSERT_EQ (s.size (), 5); > + ASSERT_EQ (s.calc_canvas_width (), 5); > + canvas c (canvas::size_t (s.calc_canvas_width (), 1), sm); > + c.paint_text (canvas::coord_t (0, 0), s); > + ASSERT_CANVAS_STREQ (c, false, > + /* U+26A0 WARNING SIGN, as UTF-8: 0xE2 0x9A 0xA0. */ > + "\xE2\x9A\xA0" > + /* U+FE0F VARIATION SELECTOR-16, as UTF-8: 0xEF 0xB8 0x8F. */ > + "\xEF\xB8\x8F" > + "test\n"); > +} > + > +static void > +test_canvas_urls () > +{ > + style_manager sm; > + canvas canvas (canvas::size_t (9, 3), sm); > + styled_string foo_ss (sm, "foo"); > + foo_ss.set_url (sm, "https://www.example.com/foo"); > + styled_string bar_ss (sm, "bar"); > + bar_ss.set_url (sm, "https://www.example.com/bar"); > + canvas.paint_text(canvas::coord_t (1, 1), foo_ss); > + canvas.paint_text(canvas::coord_t (5, 1), bar_ss); > + > + ASSERT_CANVAS_STREQ (canvas, false, > + ("\n" > + " foo bar\n" > + "\n")); > + { > + pretty_printer pp; > + pp_show_color (&pp) = true; > + pp.url_format = URL_FORMAT_ST; > + assert_canvas_streq (SELFTEST_LOCATION, canvas, &pp, > + (/* Line 1. */ > + "\n" > + /* Line 2. */ > + " " > + "\33]8;;https://www.example.com/foo\33\\foo\33]8;;\33\\" > + " " > + "\33]8;;https://www.example.com/bar\33\\bar\33]8;;\33\\" > + "\n" > + /* Line 3. */ > + "\n")); > + } > + > + { > + pretty_printer pp; > + pp_show_color (&pp) = true; > + pp.url_format = URL_FORMAT_BEL; > + assert_canvas_streq (SELFTEST_LOCATION, canvas, &pp, > + (/* Line 1. */ > + "\n" > + /* Line 2. */ > + " " > + "\33]8;;https://www.example.com/foo\afoo\33]8;;\a" > + " " > + "\33]8;;https://www.example.com/bar\abar\33]8;;\a" > + "\n" > + /* Line 3. */ > + "\n")); > + } > +} > + > +/* Run all selftests in this file. */ > + > +void > +text_art_canvas_cc_tests () > +{ > + test_blank (); > + test_abc (); > + test_debug_fill (); > + test_text (); > + test_circle (); > + test_color_circle (); > + test_bold (); > + test_emoji (); > + test_emoji_2 (); > + test_canvas_urls (); > +} > + > +} // namespace selftest > + > + > +#endif /* #if CHECKING_P */ > diff --git a/gcc/text-art/canvas.h b/gcc/text-art/canvas.h > new file mode 100644 > index 00000000000..495497754f5 > --- /dev/null > +++ b/gcc/text-art/canvas.h > @@ -0,0 +1,74 @@ > +/* Canvas for random-access procedural text art. > + Copyright (C) 2023 Free Software Foundation, Inc. > + Contributed by David Malcolm <dmalcolm@redhat.com>. > + > +This file is part of GCC. > + > +GCC is free software; you can redistribute it and/or modify it > +under the terms of the GNU General Public License as published by > +the Free Software Foundation; either version 3, or (at your option) > +any later version. > + > +GCC is distributed in the hope that it will be useful, but > +WITHOUT ANY WARRANTY; without even the implied warranty of > +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU > +General Public License for more details. > + > +You should have received a copy of the GNU General Public License > +along with GCC; see the file COPYING3. If not see > +<http://www.gnu.org/licenses/>. */ > + > +#ifndef GCC_TEXT_ART_CANVAS_H > +#define GCC_TEXT_ART_CANVAS_H > + > +#include "text-art/types.h" > + > +namespace text_art { > + > +class canvas; > + > +/* A 2 dimensional grid of text cells (a "canvas"), which > + can be written to ("painted") via random access, and then > + written out to a pretty_printer once the picture is complete. > + > + Each text cell can be styled independently (colorization, > + URLs, etc). */ > + > +class canvas > +{ > + public: > + typedef styled_unichar cell_t; > + typedef size<class canvas> size_t; > + typedef coord<class canvas> coord_t; > + typedef range<class canvas> range_t; > + typedef rect<class canvas> rect_t; > + > + canvas (size_t size, const style_manager &style_mgr); > + > + size_t get_size () const { return m_cells.get_size (); } > + > + void paint (coord_t coord, cell_t c); > + void paint_text (coord_t coord, const styled_string &text); > + > + void fill (rect_t rect, cell_t c); > + void debug_fill (); > + > + void print_to_pp (pretty_printer *pp, > + const char *per_line_prefix = NULL) const; > + void debug (bool styled) const; > + > + const cell_t &get (coord_t coord) const > + { > + return m_cells.get (coord); > + } > + > + private: > + int get_final_x_in_row (int y) const; > + > + array2<cell_t, size_t, coord_t> m_cells; > + const style_manager &m_style_mgr; > +}; > + > +} // namespace text_art > + > +#endif /* GCC_TEXT_ART_CANVAS_H */ > diff --git a/gcc/text-art/ruler.cc b/gcc/text-art/ruler.cc > new file mode 100644 > index 00000000000..80c623f77ba > --- /dev/null > +++ b/gcc/text-art/ruler.cc > @@ -0,0 +1,723 @@ > +/* Classes for printing labelled rulers. > + Copyright (C) 2023 Free Software Foundation, Inc. > + Contributed by David Malcolm <dmalcolm@redhat.com>. > + > +This file is part of GCC. > + > +GCC is free software; you can redistribute it and/or modify it under > +the terms of the GNU General Public License as published by the Free > +Software Foundation; either version 3, or (at your option) any later > +version. > + > +GCC is distributed in the hope that it will be useful, but WITHOUT ANY > +WARRANTY; without even the implied warranty of MERCHANTABILITY or > +FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License > +for more details. > + > +You should have received a copy of the GNU General Public License > +along with GCC; see the file COPYING3. If not see > +<http://www.gnu.org/licenses/>. */ > + > +#include "config.h" > +#define INCLUDE_ALGORITHM > +#include "system.h" > +#include "coretypes.h" > +#include "pretty-print.h" > +#include "selftest.h" > +#include "text-art/selftests.h" > +#include "text-art/ruler.h" > +#include "text-art/theme.h" > + > +using namespace text_art; > + > +void > +x_ruler::add_label (const canvas::range_t &r, > + styled_string text, > + style::id_t style_id, > + label_kind kind) > +{ > + m_labels.push_back (label (r, std::move (text), style_id, kind)); > + m_has_layout = false; > +} > + > +int > +x_ruler::get_canvas_y (int rel_y) const > +{ > + gcc_assert (rel_y >= 0); > + gcc_assert (rel_y < m_size.h); > + switch (m_label_dir) > + { > + default: > + gcc_unreachable (); > + case label_dir::ABOVE: > + return m_size.h - (rel_y + 1); > + case label_dir::BELOW: > + return rel_y; > + } > +} > + > +void > +x_ruler::paint_to_canvas (canvas &canvas, > + canvas::coord_t offset, > + const theme &theme) > +{ > + ensure_layout (); > + > + if (0) > + canvas.fill (canvas::rect_t (offset, m_size), > + canvas::cell_t ('*')); > + > + for (size_t idx = 0; idx < m_labels.size (); idx++) > + { > + const label &iter_label = m_labels[idx]; > + > + /* Paint the ruler itself. */ > + const int ruler_rel_y = get_canvas_y (0); > + for (int rel_x = iter_label.m_range.start; > + rel_x < iter_label.m_range.next; > + rel_x++) > + { > + enum theme::cell_kind kind = theme::cell_kind::X_RULER_MIDDLE; > + > + if (rel_x == iter_label.m_range.start) > + { > + kind = theme::cell_kind::X_RULER_LEFT_EDGE; > + if (idx > 0) > + { > + const label &prev_label = m_labels[idx - 1]; > + if (prev_label.m_range.get_max () == iter_label.m_range.start) > + kind = theme::cell_kind::X_RULER_INTERNAL_EDGE; > + } > + } > + else if (rel_x == iter_label.m_range.get_max ()) > + kind = theme::cell_kind::X_RULER_RIGHT_EDGE; > + else if (rel_x == iter_label.m_connector_x) > + { > + switch (m_label_dir) > + { > + default: > + gcc_unreachable (); > + case label_dir::ABOVE: > + kind = theme::cell_kind::X_RULER_CONNECTOR_TO_LABEL_ABOVE; > + break; > + case label_dir::BELOW: > + kind = theme::cell_kind::X_RULER_CONNECTOR_TO_LABEL_BELOW; > + break; > + } > + } > + canvas.paint (canvas::coord_t (rel_x, ruler_rel_y) + offset, > + theme.get_cell (kind, iter_label.m_style_id)); > + } > + > + /* Paint the connector to the text. */ > + for (int connector_rel_y = 1; > + connector_rel_y < iter_label.m_text_rect.get_min_y (); > + connector_rel_y++) > + { > + canvas.paint > + ((canvas::coord_t (iter_label.m_connector_x, > + get_canvas_y (connector_rel_y)) > + + offset), > + theme.get_cell (theme::cell_kind::X_RULER_VERTICAL_CONNECTOR, > + iter_label.m_style_id)); > + } > + > + /* Paint the text. */ > + switch (iter_label.m_kind) > + { > + default: > + gcc_unreachable (); > + case x_ruler::label_kind::TEXT: > + canvas.paint_text > + ((canvas::coord_t (iter_label.m_text_rect.get_min_x (), > + get_canvas_y (iter_label.m_text_rect.get_min_y ())) > + + offset), > + iter_label.m_text); > + break; > + > + case x_ruler::label_kind::TEXT_WITH_BORDER: > + { > + const canvas::range_t rel_x_range > + (iter_label.m_text_rect.get_x_range ()); > + > + enum theme::cell_kind inner_left_kind; > + enum theme::cell_kind inner_connector_kind; > + enum theme::cell_kind inner_right_kind; > + enum theme::cell_kind outer_left_kind; > + enum theme::cell_kind outer_right_kind; > + > + switch (m_label_dir) > + { > + default: > + gcc_unreachable (); > + case label_dir::ABOVE: > + outer_left_kind = theme::cell_kind::TEXT_BORDER_TOP_LEFT; > + outer_right_kind = theme::cell_kind::TEXT_BORDER_TOP_RIGHT; > + inner_left_kind = theme::cell_kind::TEXT_BORDER_BOTTOM_LEFT; > + inner_connector_kind = theme::cell_kind::X_RULER_CONNECTOR_TO_LABEL_BELOW; > + inner_right_kind = theme::cell_kind::TEXT_BORDER_BOTTOM_RIGHT; > + break; > + case label_dir::BELOW: > + inner_left_kind = theme::cell_kind::TEXT_BORDER_TOP_LEFT; > + inner_connector_kind = theme::cell_kind::X_RULER_CONNECTOR_TO_LABEL_ABOVE; > + inner_right_kind = theme::cell_kind::TEXT_BORDER_TOP_RIGHT; > + outer_left_kind = theme::cell_kind::TEXT_BORDER_BOTTOM_LEFT; > + outer_right_kind = theme::cell_kind::TEXT_BORDER_BOTTOM_RIGHT; > + break; > + } > + /* Inner border. */ > + { > + const int rel_canvas_y > + = get_canvas_y (iter_label.m_text_rect.get_min_y ()); > + /* Left corner. */ > + canvas.paint ((canvas::coord_t (rel_x_range.get_min (), > + rel_canvas_y) > + + offset), > + theme.get_cell (inner_left_kind, > + iter_label.m_style_id)); > + /* Edge. */ > + const canvas::cell_t edge_border_cell > + = theme.get_cell (theme::cell_kind::TEXT_BORDER_HORIZONTAL, > + iter_label.m_style_id); > + const canvas::cell_t connector_border_cell > + = theme.get_cell (inner_connector_kind, > + iter_label.m_style_id); > + for (int rel_x = rel_x_range.get_min () + 1; > + rel_x < rel_x_range.get_max (); > + rel_x++) > + if (rel_x == iter_label.m_connector_x) > + canvas.paint ((canvas::coord_t (rel_x, rel_canvas_y) > + + offset), > + connector_border_cell); > + else > + canvas.paint ((canvas::coord_t (rel_x, rel_canvas_y) > + + offset), > + edge_border_cell); > + > + /* Right corner. */ > + canvas.paint ((canvas::coord_t (rel_x_range.get_max (), > + rel_canvas_y) > + + offset), > + theme.get_cell (inner_right_kind, > + iter_label.m_style_id)); > + } > + > + { > + const int rel_canvas_y > + = get_canvas_y (iter_label.m_text_rect.get_min_y () + 1); > + const canvas::cell_t border_cell > + = theme.get_cell (theme::cell_kind::TEXT_BORDER_VERTICAL, > + iter_label.m_style_id); > + > + /* Left border. */ > + canvas.paint ((canvas::coord_t (rel_x_range.get_min (), > + rel_canvas_y) > + + offset), > + border_cell); > + /* Text. */ > + canvas.paint_text ((canvas::coord_t (rel_x_range.get_min () + 1, > + rel_canvas_y) > + + offset), > + iter_label.m_text); > + /* Right border. */ > + canvas.paint ((canvas::coord_t (rel_x_range.get_max (), > + rel_canvas_y) > + + offset), > + border_cell); > + } > + > + /* Outer border. */ > + { > + const int rel_canvas_y > + = get_canvas_y (iter_label.m_text_rect.get_max_y ()); > + /* Left corner. */ > + canvas.paint ((canvas::coord_t (rel_x_range.get_min (), > + rel_canvas_y) > + + offset), > + theme.get_cell (outer_left_kind, > + iter_label.m_style_id)); > + /* Edge. */ > + const canvas::cell_t border_cell > + = theme.get_cell (theme::cell_kind::TEXT_BORDER_HORIZONTAL, > + iter_label.m_style_id); > + for (int rel_x = rel_x_range.get_min () + 1; > + rel_x < rel_x_range.get_max (); > + rel_x++) > + canvas.paint ((canvas::coord_t (rel_x, rel_canvas_y) > + + offset), > + border_cell); > + > + /* Right corner. */ > + canvas.paint ((canvas::coord_t (rel_x_range.get_max (), > + rel_canvas_y) > + + offset), > + theme.get_cell (outer_right_kind, > + iter_label.m_style_id)); > + } > + } > + break; > + } > + } > +} > + > +DEBUG_FUNCTION void > +x_ruler::debug (const style_manager &sm) > +{ > + canvas c (get_size (), sm); > + paint_to_canvas (c, canvas::coord_t (0, 0), unicode_theme ()); > + c.debug (true); > +} > + > +x_ruler::label::label (const canvas::range_t &range, > + styled_string text, > + style::id_t style_id, > + label_kind kind) > +: m_range (range), > + m_text (std::move (text)), > + m_style_id (style_id), > + m_kind (kind), > + m_text_rect (canvas::coord_t (0, 0), > + canvas::size_t (m_text.calc_canvas_width (), 1)), > + m_connector_x ((m_range.get_min () + m_range.get_max ()) / 2) > +{ > + if (kind == label_kind::TEXT_WITH_BORDER) > + { > + m_text_rect.m_size.w += 2; > + m_text_rect.m_size.h += 2; > + } > +} > + > +bool > +x_ruler::label::operator< (const label &other) const > +{ > + int cmp = m_range.start - other.m_range.start; > + if (cmp) > + return cmp < 0; > + return m_range.next < other.m_range.next; > +} > + > +void > +x_ruler::ensure_layout () > +{ > + if (m_has_layout) > + return; > + update_layout (); > + m_has_layout = true; > +} > + > +void > +x_ruler::update_layout () > +{ > + if (m_labels.empty ()) > + return; > + > + std::sort (m_labels.begin (), m_labels.end ()); > + > + /* Place labels. */ > + int ruler_width = m_labels.back ().m_range.get_next (); > + int width_with_labels = ruler_width; > + > + /* Get x coordinates of text parts of each label > + (m_text_rect.m_top_left.x for each label). */ > + for (size_t idx = 0; idx < m_labels.size (); idx++) > + { > + label &iter_label = m_labels[idx]; > + /* Attempt to center the text label. */ > + int min_x; > + if (idx > 0) > + { > + /* ...but don't overlap with the connector to the left. */ > + int left_neighbor_connector_x = m_labels[idx - 1].m_connector_x; > + min_x = left_neighbor_connector_x + 1; > + } > + else > + { > + /* ...or go beyond the leftmost column. */ > + min_x = 0; > + } > + int connector_x = iter_label.m_connector_x; > + int centered_x > + = connector_x - ((int)iter_label.m_text_rect.get_width () / 2); > + int text_x = std::max (min_x, centered_x); > + iter_label.m_text_rect.m_top_left.x = text_x; > + } > + > + /* Now walk backwards trying to place them vertically, > + setting m_text_rect.m_top_left.y for each label, > + consolidating the rows where possible. > + The y cooordinates are stored with respect to label_dir::BELOW. */ > + int label_y = 2; > + for (int idx = m_labels.size () - 1; idx >= 0; idx--) > + { > + label &iter_label = m_labels[idx]; > + /* Does it fit on the same row as the text label to the right? */ > + size_t text_len = iter_label.m_text_rect.get_width (); > + /* Get the x-coord of immediately beyond iter_label's text. */ > + int next_x = iter_label.m_text_rect.get_min_x () + text_len; > + if (idx < (int)m_labels.size () - 1) > + { > + if (next_x >= m_labels[idx + 1].m_text_rect.get_min_x ()) > + { > + /* If not, start a new row. */ > + label_y += m_labels[idx + 1].m_text_rect.get_height (); > + } > + } > + iter_label.m_text_rect.m_top_left.y = label_y; > + width_with_labels = std::max (width_with_labels, next_x); > + } > + > + m_size = canvas::size_t (width_with_labels, > + label_y + m_labels[0].m_text_rect.get_height ()); > +} > + > +#if CHECKING_P > + > +namespace selftest { > + > +static void > +assert_x_ruler_streq (const location &loc, > + x_ruler &ruler, > + const theme &theme, > + const style_manager &sm, > + bool styled, > + const char *expected_str) > +{ > + canvas c (ruler.get_size (), sm); > + ruler.paint_to_canvas (c, canvas::coord_t (0, 0), theme); > + if (0) > + c.debug (styled); > + assert_canvas_streq (loc, c, styled, expected_str); > +} > + > +#define ASSERT_X_RULER_STREQ(RULER, THEME, SM, STYLED, EXPECTED_STR) \ > + SELFTEST_BEGIN_STMT \ > + assert_x_ruler_streq ((SELFTEST_LOCATION), \ > + (RULER), \ > + (THEME), \ > + (SM), \ > + (STYLED), \ > + (EXPECTED_STR)); \ > + SELFTEST_END_STMT > + > +static void > +test_single () > +{ > + style_manager sm; > + x_ruler r (x_ruler::label_dir::BELOW); > + r.add_label (canvas::range_t (0, 11), styled_string (sm, "foo"), > + style::id_plain, x_ruler::label_kind::TEXT); > + ASSERT_X_RULER_STREQ > + (r, ascii_theme (), sm, true, > + ("|~~~~+~~~~|\n" > + " |\n" > + " foo\n")); > + ASSERT_X_RULER_STREQ > + (r, unicode_theme (), sm, true, > + ("├────┬────┤\n" > + " │\n" > + " foo\n")); > +} > + > +static void > +test_single_above () > +{ > + style_manager sm; > + x_ruler r (x_ruler::label_dir::ABOVE); > + r.add_label (canvas::range_t (0, 11), styled_string (sm, "hello world"), > + style::id_plain); > + ASSERT_X_RULER_STREQ > + (r, ascii_theme (), sm, true, > + ("hello world\n" > + " |\n" > + "|~~~~+~~~~|\n")); > + ASSERT_X_RULER_STREQ > + (r, unicode_theme (), sm, true, > + ("hello world\n" > + " │\n" > + "├────┴────┤\n")); > +} > + > +static void > +test_multiple_contiguous () > +{ > + style_manager sm; > + x_ruler r (x_ruler::label_dir::BELOW); > + r.add_label (canvas::range_t (0, 11), styled_string (sm, "foo"), > + style::id_plain); > + r.add_label (canvas::range_t (10, 16), styled_string (sm, "bar"), > + style::id_plain); > + ASSERT_X_RULER_STREQ > + (r, ascii_theme (), sm, true, > + ("|~~~~+~~~~|~+~~|\n" > + " | |\n" > + " foo bar\n")); > + ASSERT_X_RULER_STREQ > + (r, unicode_theme (), sm, true, > + ("├────┬────┼─┬──┤\n" > + " │ │\n" > + " foo bar\n")); > +} > + > +static void > +test_multiple_contiguous_above () > +{ > + style_manager sm; > + x_ruler r (x_ruler::label_dir::ABOVE); > + r.add_label (canvas::range_t (0, 11), styled_string (sm, "foo"), > + style::id_plain); > + r.add_label (canvas::range_t (10, 16), styled_string (sm, "bar"), > + style::id_plain); > + ASSERT_X_RULER_STREQ > + (r, ascii_theme (), sm, true, > + (" foo bar\n" > + " | |\n" > + "|~~~~+~~~~|~+~~|\n")); > + ASSERT_X_RULER_STREQ > + (r, unicode_theme (), sm, true, > + (" foo bar\n" > + " │ │\n" > + "├────┴────┼─┴──┤\n")); > +} > + > +static void > +test_multiple_contiguous_abutting_labels () > +{ > + style_manager sm; > + x_ruler r (x_ruler::label_dir::BELOW); > + r.add_label (canvas::range_t (0, 11), styled_string (sm, "12345678"), > + style::id_plain); > + r.add_label (canvas::range_t (10, 16), styled_string (sm, "1234678"), > + style::id_plain); > + ASSERT_X_RULER_STREQ > + (r, unicode_theme (), sm, true, > + ("├────┬────┼─┬──┤\n" > + " │ │\n" > + " │ 1234678\n" > + " 12345678\n")); > +} > + > +static void > +test_multiple_contiguous_overlapping_labels () > +{ > + style_manager sm; > + x_ruler r (x_ruler::label_dir::BELOW); > + r.add_label (canvas::range_t (0, 11), styled_string (sm, "123456789"), > + style::id_plain); > + r.add_label (canvas::range_t (10, 16), styled_string (sm, "12346789"), > + style::id_plain); > + ASSERT_X_RULER_STREQ > + (r, unicode_theme (), sm, true, > + ("├────┬────┼─┬──┤\n" > + " │ │\n" > + " │ 12346789\n" > + " 123456789\n")); > +} > +static void > +test_abutting_left_border () > +{ > + style_manager sm; > + x_ruler r (x_ruler::label_dir::BELOW); > + r.add_label (canvas::range_t (0, 6), > + styled_string (sm, "this is a long label"), > + style::id_plain); > + ASSERT_X_RULER_STREQ > + (r, unicode_theme (), sm, true, > + ("├─┬──┤\n" > + " │\n" > + "this is a long label\n")); > +} > + > +static void > +test_too_long_to_consolidate_vertically () > +{ > + style_manager sm; > + x_ruler r (x_ruler::label_dir::BELOW); > + r.add_label (canvas::range_t (0, 11), > + styled_string (sm, "long string A"), > + style::id_plain); > + r.add_label (canvas::range_t (10, 16), > + styled_string (sm, "long string B"), > + style::id_plain); > + ASSERT_X_RULER_STREQ > + (r, unicode_theme (), sm, true, > + ("├────┬────┼─┬──┤\n" > + " │ │\n" > + " │long string B\n" > + "long string A\n")); > +} > + > +static void > +test_abutting_neighbor () > +{ > + style_manager sm; > + x_ruler r (x_ruler::label_dir::BELOW); > + r.add_label (canvas::range_t (0, 11), > + styled_string (sm, "very long string A"), > + style::id_plain); > + r.add_label (canvas::range_t (10, 16), > + styled_string (sm, "very long string B"), > + style::id_plain); > + ASSERT_X_RULER_STREQ > + (r, unicode_theme (), sm, true, > + ("├────┬────┼─┬──┤\n" > + " │ │\n" > + " │very long string B\n" > + "very long string A\n")); > +} > + > +static void > +test_gaps () > +{ > + style_manager sm; > + x_ruler r (x_ruler::label_dir::BELOW); > + r.add_label (canvas::range_t (0, 5), > + styled_string (sm, "foo"), > + style::id_plain); > + r.add_label (canvas::range_t (10, 15), > + styled_string (sm, "bar"), > + style::id_plain); > + ASSERT_X_RULER_STREQ > + (r, ascii_theme (), sm, true, > + ("|~+~| |~+~|\n" > + " | |\n" > + " foo bar\n")); > +} > + > +static void > +test_styled () > +{ > + style_manager sm; > + style s1, s2; > + s1.m_bold = true; > + s1.m_fg_color = style::named_color::YELLOW; > + s2.m_bold = true; > + s2.m_fg_color = style::named_color::BLUE; > + style::id_t sid1 = sm.get_or_create_id (s1); > + style::id_t sid2 = sm.get_or_create_id (s2); > + > + x_ruler r (x_ruler::label_dir::BELOW); > + r.add_label (canvas::range_t (0, 5), styled_string (sm, "foo"), sid1); > + r.add_label (canvas::range_t (10, 15), styled_string (sm, "bar"), sid2); > + ASSERT_X_RULER_STREQ > + (r, ascii_theme (), sm, true, > + ("[00;01;33m[K|~+~|[00m[K [00;01;34m[K|~+~|[00m[K\n" > + " [00;01;33m[K|[00m[K [00;01;34m[K|[00m[K\n" > + " foo bar\n")); > +} > + > +static void > +test_borders () > +{ > + style_manager sm; > + { > + x_ruler r (x_ruler::label_dir::BELOW); > + r.add_label (canvas::range_t (0, 5), > + styled_string (sm, "label 1"), > + style::id_plain, > + x_ruler::label_kind::TEXT_WITH_BORDER); > + r.add_label (canvas::range_t (10, 15), > + styled_string (sm, "label 2"), > + style::id_plain); > + r.add_label (canvas::range_t (20, 25), > + styled_string (sm, "label 3"), > + style::id_plain, > + x_ruler::label_kind::TEXT_WITH_BORDER); > + ASSERT_X_RULER_STREQ > + (r, ascii_theme (), sm, true, > + "|~+~| |~+~| |~+~|\n" > + " | | |\n" > + " | label 2 +---+---+\n" > + "+-+-----+ |label 3|\n" > + "|label 1| +-------+\n" > + "+-------+\n"); > + ASSERT_X_RULER_STREQ > + (r, unicode_theme (), sm, true, > + "├─┬─┤ ├─┬─┤ ├─┬─┤\n" > + " │ │ │\n" > + " │ label 2 ╭───┴───╮\n" > + "╭─┴─────╮ │label 3│\n" > + "│label 1│ ╰───────╯\n" > + "╰───────╯\n"); > + } > + { > + x_ruler r (x_ruler::label_dir::ABOVE); > + r.add_label (canvas::range_t (0, 5), > + styled_string (sm, "label 1"), > + style::id_plain, > + x_ruler::label_kind::TEXT_WITH_BORDER); > + r.add_label (canvas::range_t (10, 15), > + styled_string (sm, "label 2"), > + style::id_plain); > + r.add_label (canvas::range_t (20, 25), > + styled_string (sm, "label 3"), > + style::id_plain, > + x_ruler::label_kind::TEXT_WITH_BORDER); > + ASSERT_X_RULER_STREQ > + (r, ascii_theme (), sm, true, > + "+-------+\n" > + "|label 1| +-------+\n" > + "+-+-----+ |label 3|\n" > + " | label 2 +---+---+\n" > + " | | |\n" > + "|~+~| |~+~| |~+~|\n"); > + ASSERT_X_RULER_STREQ > + (r, unicode_theme (), sm, true, > + "╭───────╮\n" > + "│label 1│ ╭───────╮\n" > + "╰─┬─────╯ │label 3│\n" > + " │ label 2 ╰───┬───╯\n" > + " │ │ │\n" > + "├─┴─┤ ├─┴─┤ ├─┴─┤\n"); > + } > +} > + > +static void > +test_emoji () > +{ > + style_manager sm; > + > + styled_string s; > + s.append (styled_string (0x26A0, /* U+26A0 WARNING SIGN. */ > + true)); > + s.append (styled_string (sm, " ")); > + s.append (styled_string (sm, "this is a warning")); > + > + x_ruler r (x_ruler::label_dir::BELOW); > + r.add_label (canvas::range_t (0, 5), > + std::move (s), > + style::id_plain, > + x_ruler::label_kind::TEXT_WITH_BORDER); > + > + ASSERT_X_RULER_STREQ > + (r, ascii_theme (), sm, true, > + "|~+~|\n" > + " |\n" > + "+-+------------------+\n" > + "|⚠️ this is a warning|\n" > + "+--------------------+\n"); > +} > + > +/* Run all selftests in this file. */ > + > +void > +text_art_ruler_cc_tests () > +{ > + test_single (); > + test_single_above (); > + test_multiple_contiguous (); > + test_multiple_contiguous_above (); > + test_multiple_contiguous_abutting_labels (); > + test_multiple_contiguous_overlapping_labels (); > + test_abutting_left_border (); > + test_too_long_to_consolidate_vertically (); > + test_abutting_neighbor (); > + test_gaps (); > + test_styled (); > + test_borders (); > + test_emoji (); > +} > + > +} // namespace selftest > + > + > +#endif /* #if CHECKING_P */ > diff --git a/gcc/text-art/ruler.h b/gcc/text-art/ruler.h > new file mode 100644 > index 00000000000..31f53549836 > --- /dev/null > +++ b/gcc/text-art/ruler.h > @@ -0,0 +1,125 @@ > +/* Classes for printing labelled rulers. > + Copyright (C) 2023 Free Software Foundation, Inc. > + Contributed by David Malcolm <dmalcolm@redhat.com>. > + > +This file is part of GCC. > + > +GCC is free software; you can redistribute it and/or modify it > +under the terms of the GNU General Public License as published by > +the Free Software Foundation; either version 3, or (at your option) > +any later version. > + > +GCC is distributed in the hope that it will be useful, but > +WITHOUT ANY WARRANTY; without even the implied warranty of > +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU > +General Public License for more details. > + > +You should have received a copy of the GNU General Public License > +along with GCC; see the file COPYING3. If not see > +<http://www.gnu.org/licenses/>. */ > + > +#ifndef GCC_TEXT_ART_RULER_H > +#define GCC_TEXT_ART_RULER_H > + > +#include "text-art/canvas.h" > + > +namespace text_art { > + > +/* A way to annotate a series of ranges of canvas coordinates > + with text labels either above or, in this example, below: > + ├───────┬───────┼───────┬───────┼───────┬───────┤ > + │ │ │ > + label A label B label C > + with logic to ensure that the text labels don't overlap > + when printed. */ > + > +class x_ruler > +{ > + public: > + enum class label_dir { ABOVE, BELOW }; > + enum class label_kind > + { > + TEXT, > + TEXT_WITH_BORDER > + }; > + > + x_ruler (label_dir dir) > + : m_label_dir (dir), > + m_size (canvas::size_t (0, 0)), > + m_has_layout (false) > + {} > + > + void add_label (const canvas::range_t &r, > + styled_string text, > + style::id_t style_id, > + label_kind kind = label_kind::TEXT); > + > + canvas::size_t get_size () > + { > + ensure_layout (); > + return m_size; > + } > + > + void paint_to_canvas (canvas &canvas, > + canvas::coord_t offset, > + const theme &theme); > + > + void debug (const style_manager &sm); > + > + private: > + /* A particular label within an x_ruler. > + Consider e.g.: > + > + # x: 01234567890123456789012345678901234567890123456789 > + # y: 0: ├───────┬───────┼───────┬───────┼───────┬───────┤ > + # 1: │ │ │ > + # 2: label A label B label C > + # > + > + Then "label A" is: > + > + # m_connector_x == 8 > + # V > + # x: 0123456789012 > + # y: 0: ┬ > + # 1: │ > + # 2: label A > + # x: 0123456789012 > + # ^ > + # m_text_coord.x == 6 > + > + and m_text_coord is (2, 6). > + The y cooordinates are stored with respect to label_dir::BELOW; > + for label_dir::ABOVE we flip them when painting the ruler. */ > + class label > + { > + friend class x_ruler; > + public: > + label (const canvas::range_t &range, styled_string text, style::id_t style_id, > + label_kind kind); > + > + bool operator< (const label &other) const; > + > + private: > + canvas::range_t m_range; > + styled_string m_text; > + style::id_t m_style_id; > + label_kind m_kind; > + canvas::rect_t m_text_rect; // includes any border > + int m_connector_x; > + }; > + > + void ensure_layout (); > + void update_layout (); > + int get_canvas_y (int rel_y) const; > + > + label_dir m_label_dir; > + std::vector<label> m_labels; > + canvas::size_t m_size; > + bool m_has_layout = false; > + > +}; > + > +} // namespace text_art > + > +#endif /* GCC_TEXT_ART_RULER_H */ > diff --git a/gcc/text-art/selftests.cc b/gcc/text-art/selftests.cc > new file mode 100644 > index 00000000000..60ad003b549 > --- /dev/null > +++ b/gcc/text-art/selftests.cc > @@ -0,0 +1,77 @@ > +/* Selftests for text art. > + Copyright (C) 2023 Free Software Foundation, Inc. > + Contributed by David Malcolm <dmalcolm@redhat.com>. > + > +This file is part of GCC. > + > +GCC is free software; you can redistribute it and/or modify it under > +the terms of the GNU General Public License as published by the Free > +Software Foundation; either version 3, or (at your option) any later > +version. > + > +GCC is distributed in the hope that it will be useful, but WITHOUT ANY > +WARRANTY; without even the implied warranty of MERCHANTABILITY or > +FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License > +for more details. > + > +You should have received a copy of the GNU General Public License > +along with GCC; see the file COPYING3. If not see > +<http://www.gnu.org/licenses/>. */ > + > +#include "config.h" > +#include "system.h" > +#include "coretypes.h" > +#include "selftest.h" > +#include "pretty-print.h" > +#include "text-art/selftests.h" > +#include "text-art/canvas.h" > + > +#if CHECKING_P > + > +/* Run all tests, aborting if any fail. */ > + > +void > +selftest::text_art_tests () > +{ > + text_art_style_cc_tests (); > + text_art_styled_string_cc_tests (); > + > + text_art_box_drawing_cc_tests (); > + text_art_canvas_cc_tests (); > + text_art_ruler_cc_tests (); > + text_art_table_cc_tests (); > + text_art_widget_cc_tests (); > +} > + > +/* Implementation detail of ASSERT_CANVAS_STREQ. */ > + > +void > +selftest::assert_canvas_streq (const location &loc, > + const text_art::canvas &canvas, > + pretty_printer *pp, > + const char *expected_str) > +{ > + canvas.print_to_pp (pp); > + if (0) > + fprintf (stderr, "%s\n", pp_formatted_text (pp)); > + ASSERT_STREQ_AT (loc, pp_formatted_text (pp), expected_str); > +} > + > +/* Implementation detail of ASSERT_CANVAS_STREQ. */ > + > +void > +selftest::assert_canvas_streq (const location &loc, > + const text_art::canvas &canvas, > + bool styled, > + const char *expected_str) > +{ > + pretty_printer pp; > + if (styled) > + { > + pp_show_color (&pp) = true; > + pp.url_format = URL_FORMAT_DEFAULT; > + } > + assert_canvas_streq (loc, canvas, &pp, expected_str); > +} > + > +#endif /* #if CHECKING_P */ > diff --git a/gcc/text-art/selftests.h b/gcc/text-art/selftests.h > new file mode 100644 > index 00000000000..706a1d8b5d6 > --- /dev/null > +++ b/gcc/text-art/selftests.h > @@ -0,0 +1,60 @@ > +/* Copyright (C) 2023 Free Software Foundation, Inc. > + Contributed by David Malcolm <dmalcolm@redhat.com>. > + > +This file is part of GCC. > + > +GCC is free software; you can redistribute it and/or modify it under > +the terms of the GNU General Public License as published by the Free > +Software Foundation; either version 3, or (at your option) any later > +version. > + > +GCC is distributed in the hope that it will be useful, but WITHOUT ANY > +WARRANTY; without even the implied warranty of MERCHANTABILITY or > +FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License > +for more details. > + > +You should have received a copy of the GNU General Public License > +along with GCC; see the file COPYING3. If not see > +<http://www.gnu.org/licenses/>. */ > + > +#ifndef GCC_TEXT_ART_SELFTESTS_H > +#define GCC_TEXT_ART_SELFTESTS_H > + > +#if CHECKING_P > + > +#include "text-art/types.h" > + > +namespace selftest { > + > +extern void text_art_box_drawing_cc_tests (); > +extern void text_art_canvas_cc_tests (); > +extern void text_art_ruler_cc_tests (); > +extern void text_art_style_cc_tests (); > +extern void text_art_styled_string_cc_tests (); > +extern void text_art_table_cc_tests (); > +extern void text_art_widget_cc_tests (); > + > +extern void text_art_tests (); > + > +extern void assert_canvas_streq (const location &loc, > + const text_art::canvas &canvas, > + pretty_printer *pp, > + const char *expected_str); > +extern void assert_canvas_streq (const location &loc, > + const text_art::canvas &canvas, > + bool styled, > + const char *expected_str); > + > +#define ASSERT_CANVAS_STREQ(CANVAS, STYLED, EXPECTED_STR) \ > + SELFTEST_BEGIN_STMT \ > + assert_canvas_streq ((SELFTEST_LOCATION), \ > + (CANVAS), \ > + (STYLED), \ > + (EXPECTED_STR)); \ > + SELFTEST_END_STMT > + > +} /* end of namespace selftest. */ > + > +#endif /* #if CHECKING_P */ > + > +#endif /* GCC_TEXT_ART_SELFTESTS_H */ > diff --git a/gcc/text-art/style.cc b/gcc/text-art/style.cc > new file mode 100644 > index 00000000000..00b056336fc > --- /dev/null > +++ b/gcc/text-art/style.cc > @@ -0,0 +1,632 @@ > +/* Classes for styling text cells (color, URLs). > + Copyright (C) 2023 Free Software Foundation, Inc. > + Contributed by David Malcolm <dmalcolm@redhat.com>. > + > +This file is part of GCC. > + > +GCC is free software; you can redistribute it and/or modify it under > +the terms of the GNU General Public License as published by the Free > +Software Foundation; either version 3, or (at your option) any later > +version. > + > +GCC is distributed in the hope that it will be useful, but WITHOUT ANY > +WARRANTY; without even the implied warranty of MERCHANTABILITY or > +FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License > +for more details. > + > +You should have received a copy of the GNU General Public License > +along with GCC; see the file COPYING3. If not see > +<http://www.gnu.org/licenses/>. */ > + > +#include "config.h" > +#define INCLUDE_ALGORITHM > +#define INCLUDE_MEMORY > +#include "system.h" > +#include "coretypes.h" > +#include "make-unique.h" > +#include "pretty-print.h" > +#include "intl.h" > +#include "selftest.h" > +#include "text-art/selftests.h" > +#include "text-art/types.h" > +#include "color-macros.h" > + > +using namespace text_art; > + > +/* class text_art::style. */ > + > +style & > +style::set_style_url (const char *url) > +{ > + m_url.clear (); > + while (*url) > + m_url.push_back (*(url++)); > + return *this; > +} > + > +/* class text_art::style::color. */ > + > +bool > +style::color::operator== (const style::color &other) const > +{ > + if (m_kind != other.m_kind) > + return false; > + switch (m_kind) > + { > + default: > + gcc_unreachable (); > + case kind::NAMED: > + return (u.m_named.m_name == other.u.m_named.m_name > + && u.m_named.m_bright == other.u.m_named.m_bright); > + case kind::BITS_8: > + return u.m_8bit == other.u.m_8bit; > + case kind::BITS_24: > + return (u.m_24bit.r == other.u.m_24bit.r > + && u.m_24bit.g == other.u.m_24bit.g > + && u.m_24bit.b == other.u.m_24bit.b); > + } > +} > + > +static void > +ensure_separator (pretty_printer *pp, bool &need_separator) > +{ > + if (need_separator) > + pp_string (pp, COLOR_SEPARATOR); > + need_separator = true; > +} > + > +void > +style::color::print_sgr (pretty_printer *pp, > + bool fg, > + bool &need_separator) const > +{ > + switch (m_kind) > + { > + default: > + gcc_unreachable (); > + case kind::NAMED: > + { > + static const char * const fg_normal[] = {"", // reset, for DEFAULT > + COLOR_FG_BLACK, > + COLOR_FG_RED, > + COLOR_FG_GREEN, > + COLOR_FG_YELLOW, > + COLOR_FG_BLUE, > + COLOR_FG_MAGENTA, > + COLOR_FG_CYAN, > + COLOR_FG_WHITE}; > + static const char * const fg_bright[] = {"", // reset, for DEFAULT > + COLOR_FG_BRIGHT_BLACK, > + COLOR_FG_BRIGHT_RED, > + COLOR_FG_BRIGHT_GREEN, > + COLOR_FG_BRIGHT_YELLOW, > + COLOR_FG_BRIGHT_BLUE, > + COLOR_FG_BRIGHT_MAGENTA, > + COLOR_FG_BRIGHT_CYAN, > + COLOR_FG_BRIGHT_WHITE}; > + static const char * const bg_normal[] = {"", // reset, for DEFAULT > + COLOR_BG_BLACK, > + COLOR_BG_RED, > + COLOR_BG_GREEN, > + COLOR_BG_YELLOW, > + COLOR_BG_BLUE, > + COLOR_BG_MAGENTA, > + COLOR_BG_CYAN, > + COLOR_BG_WHITE}; > + static const char * const bg_bright[] = {"", // reset, for DEFAULT > + COLOR_BG_BRIGHT_BLACK, > + COLOR_BG_BRIGHT_RED, > + COLOR_BG_BRIGHT_GREEN, > + COLOR_BG_BRIGHT_YELLOW, > + COLOR_BG_BRIGHT_BLUE, > + COLOR_BG_BRIGHT_MAGENTA, > + COLOR_BG_BRIGHT_CYAN, > + COLOR_BG_BRIGHT_WHITE}; > + STATIC_ASSERT (ARRAY_SIZE (fg_normal) == ARRAY_SIZE (fg_bright)); > + STATIC_ASSERT (ARRAY_SIZE (fg_normal) == ARRAY_SIZE (bg_normal)); > + STATIC_ASSERT (ARRAY_SIZE (fg_normal) == ARRAY_SIZE (bg_bright)); > + gcc_assert ((size_t)u.m_named.m_name < ARRAY_SIZE (fg_normal)); > + const char *const *arr; > + if (fg) > + arr = u.m_named.m_bright ? fg_bright : fg_normal; > + else > + arr = u.m_named.m_bright ? bg_bright : bg_normal; > + const char *str = arr[(size_t)u.m_named.m_name]; > + if (strlen (str) > 0) > + { > + ensure_separator (pp, need_separator); > + pp_string (pp, str); > + } > + } > + break; > + case kind::BITS_8: > + { > + ensure_separator (pp, need_separator); > + if (fg) > + pp_string (pp, "38"); > + else > + pp_string (pp, "48"); > + pp_printf (pp, ";5;%i", (int)u.m_8bit); > + } > + break; > + case kind::BITS_24: > + { > + ensure_separator (pp, need_separator); > + if (fg) > + pp_string (pp, "38"); > + else > + pp_string (pp, "48"); > + pp_printf (pp, ";2;%i;%i;%i", > + (int)u.m_24bit.r, > + (int)u.m_24bit.g, > + (int)u.m_24bit.b); > + } > + break; > + } > +} > + > +/* class text_art::style. */ > + > +/* See https://www.ecma-international.org/wp-content/uploads/ECMA-48_5th_edition_june_1991.pdf > + GRCM - GRAPHIC RENDITION COMBINATION MODE can be "REPLACING" or > + "CUMULATIVE", which affects whether we need to respecify all attributes > + at each SGR, or can accumulate them. Looks like we can't rely on the value > + of this, so we have to emit a single SGR for all changes, with a "0" reset > + at the front, forcing it to be effectively replacing. */ > + > +void > +style::print_changes (pretty_printer *pp, > + const style &old_style, > + const style &new_style) > +{ > + if (pp_show_color (pp)) > + { > + bool needs_sgr = ((old_style.m_bold != new_style.m_bold) > + || (old_style.m_underscore != new_style.m_underscore) > + || (old_style.m_blink != new_style.m_blink) > + || (old_style.m_fg_color != new_style.m_fg_color) > + || (old_style.m_bg_color != new_style.m_bg_color)); > + if (needs_sgr) > + { > + bool emit_reset = (old_style.m_bold > + || new_style.m_bold > + || old_style.m_underscore > + || new_style.m_underscore > + || old_style.m_blink > + || new_style.m_blink); > + bool need_separator = false; > + > + pp_string (pp, SGR_START); > + if (emit_reset) > + { > + pp_string (pp, COLOR_NONE); > + need_separator = true; > + } > + if (new_style.m_bold) > + { > + gcc_assert (emit_reset); > + ensure_separator (pp, need_separator); > + pp_string (pp, COLOR_BOLD); > + } > + if (new_style.m_underscore) > + { > + gcc_assert (emit_reset); > + ensure_separator (pp, need_separator); > + pp_string (pp, COLOR_UNDERSCORE); > + } > + if (new_style.m_blink) > + { > + gcc_assert (emit_reset); > + ensure_separator (pp, need_separator); > + pp_string (pp, COLOR_BLINK); > + } > + new_style.m_fg_color.print_sgr (pp, true, need_separator); > + new_style.m_bg_color.print_sgr (pp, false, need_separator); > + pp_string (pp, SGR_END); > + } > + } > + > + if (old_style.m_url != new_style.m_url) > + { > + if (!old_style.m_url.empty ()) > + pp_end_url (pp); > + if (pp->url_format != URL_FORMAT_NONE > + && !new_style.m_url.empty ()) > + { > + /* Adapted from pp_begin_url, but encoding the > + chars to UTF-8 on the fly, rather than converting > + to a buffer. */ > + pp_string (pp, "\33]8;;"); > + for (auto ch : new_style.m_url) > + pp_unicode_character (pp, ch); > + switch (pp->url_format) > + { > + default: > + case URL_FORMAT_NONE: > + gcc_unreachable (); > + case URL_FORMAT_ST: > + pp_string (pp, "\33\\"); > + break; > + case URL_FORMAT_BEL: > + pp_string (pp, "\a"); > + break; > + } > + } > + } > +} > + > +/* class text_art::style_manager. */ > + > +style_manager::style_manager () > +{ > + // index 0 will be the default style > + m_styles.push_back (style ()); > +} > + > +style::id_t > +style_manager::get_or_create_id (const style &s) > +{ > + // For now, linear search > + std::vector<style>::iterator existing > + (std::find (m_styles.begin (), m_styles.end (), s)); > + > + /* If found, return index of slot. */ > + if (existing != m_styles.end ()) > + return std::distance (m_styles.begin (), existing); > + > + /* Not found. */ > + > + /* styled_str uses 7 bits for style information, so we can only support > + up to 128 different style combinations. > + Gracefully fail by turning off styling when this limit is reached. */ > + if (m_styles.size () >= 127) > + return 0; > + > + m_styles.push_back (s); > + return m_styles.size () - 1; > +} > + > +void > +style_manager::print_any_style_changes (pretty_printer *pp, > + style::id_t old_id, > + style::id_t new_id) const > +{ > + gcc_assert (pp); > + if (old_id == new_id) > + return; > + > + const style &old_style = m_styles[old_id]; > + const style &new_style = m_styles[new_id]; > + gcc_assert (!(old_style == new_style)); > + style::print_changes (pp, old_style, new_style); > +} > + > +#if CHECKING_P > + > +namespace selftest { > + > +void > +assert_style_change_streq (const location &loc, > + const style &old_style, > + const style &new_style, > + const char *expected_str) > +{ > + pretty_printer pp; > + pp_show_color (&pp) = true; > + style::print_changes (&pp, old_style, new_style); > + ASSERT_STREQ_AT (loc, pp_formatted_text (&pp), expected_str); > +} > + > +#define ASSERT_STYLE_CHANGE_STREQ(OLD_STYLE, NEW_STYLE, EXPECTED_STR) \ > + SELFTEST_BEGIN_STMT \ > + assert_style_change_streq ((SELFTEST_LOCATION), \ > + (OLD_STYLE), \ > + (NEW_STYLE), \ > + (EXPECTED_STR)); \ > + SELFTEST_END_STMT > + > +static void > +test_bold () > +{ > + style_manager sm; > + ASSERT_EQ (sm.get_num_styles (), 1); > + > + style plain; > + ASSERT_EQ (sm.get_or_create_id (plain), 0); > + ASSERT_EQ (sm.get_num_styles (), 1); > + > + style bold; > + bold.m_bold = true; > + > + ASSERT_EQ (sm.get_or_create_id (bold), 1); > + ASSERT_EQ (sm.get_num_styles (), 2); > + ASSERT_EQ (sm.get_or_create_id (bold), 1); > + ASSERT_EQ (sm.get_num_styles (), 2); > + > + ASSERT_STYLE_CHANGE_STREQ (plain, bold, "\33[00;01m\33[K"); > + ASSERT_STYLE_CHANGE_STREQ (bold, plain, "\33[00m\33[K"); > +} > + > +static void > +test_underscore () > +{ > + style_manager sm; > + ASSERT_EQ (sm.get_num_styles (), 1); > + > + style plain; > + ASSERT_EQ (sm.get_or_create_id (plain), 0); > + ASSERT_EQ (sm.get_num_styles (), 1); > + > + style underscore; > + underscore.m_underscore = true; > + > + ASSERT_EQ (sm.get_or_create_id (underscore), 1); > + ASSERT_EQ (sm.get_num_styles (), 2); > + ASSERT_EQ (sm.get_or_create_id (underscore), 1); > + ASSERT_EQ (sm.get_num_styles (), 2); > + > + ASSERT_STYLE_CHANGE_STREQ (plain, underscore, "\33[00;04m\33[K"); > + ASSERT_STYLE_CHANGE_STREQ (underscore, plain, "\33[00m\33[K"); > +} > + > +static void > +test_blink () > +{ > + style_manager sm; > + ASSERT_EQ (sm.get_num_styles (), 1); > + > + style plain; > + ASSERT_EQ (sm.get_or_create_id (plain), 0); > + ASSERT_EQ (sm.get_num_styles (), 1); > + > + style blink; > + blink.m_blink = true; > + > + ASSERT_EQ (sm.get_or_create_id (blink), 1); > + ASSERT_EQ (sm.get_num_styles (), 2); > + ASSERT_EQ (sm.get_or_create_id (blink), 1); > + ASSERT_EQ (sm.get_num_styles (), 2); > + > + ASSERT_STYLE_CHANGE_STREQ (plain, blink, "\33[00;05m\33[K"); > + ASSERT_STYLE_CHANGE_STREQ (blink, plain, "\33[00m\33[K"); > +} > + > +#define ASSERT_NAMED_COL_STREQ(NAMED_COLOR, FG, BRIGHT, EXPECTED_STR) \ > + SELFTEST_BEGIN_STMT \ > + { \ > + style plain; \ > + style s; \ > + if (FG) \ > + s.m_fg_color = style::color ((NAMED_COLOR), (BRIGHT)); \ > + else \ > + s.m_bg_color = style::color ((NAMED_COLOR), (BRIGHT)); \ > + assert_style_change_streq ((SELFTEST_LOCATION), \ > + plain, \ > + s, \ > + (EXPECTED_STR)); \ > + } \ > + SELFTEST_END_STMT > + > +static void > +test_named_colors () > +{ > + /* Foreground colors. */ > + { > + const bool fg = true; > + { > + const bool bright = false; > + ASSERT_NAMED_COL_STREQ (style::named_color::DEFAULT, fg, bright, ""); > + ASSERT_NAMED_COL_STREQ (style::named_color::BLACK, fg, bright, > + "[30m[K"); > + ASSERT_NAMED_COL_STREQ (style::named_color::RED, fg, bright, > + "[31m[K"); > + ASSERT_NAMED_COL_STREQ (style::named_color::GREEN, fg, bright, > + "[32m[K"); > + ASSERT_NAMED_COL_STREQ (style::named_color::YELLOW, fg, bright, > + "[33m[K"); > + ASSERT_NAMED_COL_STREQ (style::named_color::BLUE, fg, bright, > + "[34m[K"); > + ASSERT_NAMED_COL_STREQ (style::named_color::MAGENTA, fg, bright, > + "[35m[K"); > + ASSERT_NAMED_COL_STREQ (style::named_color::CYAN, fg, bright, > + "[36m[K"); > + ASSERT_NAMED_COL_STREQ (style::named_color::WHITE, fg, bright, > + "[37m[K"); > + } > + { > + const bool bright = true; > + ASSERT_NAMED_COL_STREQ (style::named_color::DEFAULT, fg, bright, > + "[m[K"); > + ASSERT_NAMED_COL_STREQ (style::named_color::BLACK, fg, bright, > + "[90m[K"); > + ASSERT_NAMED_COL_STREQ (style::named_color::RED, fg, bright, > + "[91m[K"); > + ASSERT_NAMED_COL_STREQ (style::named_color::GREEN, fg, bright, > + "[92m[K"); > + ASSERT_NAMED_COL_STREQ (style::named_color::YELLOW, fg, bright, > + "[93m[K"); > + ASSERT_NAMED_COL_STREQ (style::named_color::BLUE, fg, bright, > + "[94m[K"); > + ASSERT_NAMED_COL_STREQ (style::named_color::MAGENTA, fg, bright, > + "[95m[K"); > + ASSERT_NAMED_COL_STREQ (style::named_color::CYAN, fg, bright, > + "[96m[K"); > + ASSERT_NAMED_COL_STREQ (style::named_color::WHITE, fg, bright, > + "[97m[K"); > + } > + } > + > + /* Background colors. */ > + { > + const bool fg = false; > + { > + const bool bright = false; > + ASSERT_NAMED_COL_STREQ (style::named_color::DEFAULT, fg, bright, ""); > + ASSERT_NAMED_COL_STREQ (style::named_color::BLACK, fg, bright, > + "[40m[K"); > + ASSERT_NAMED_COL_STREQ (style::named_color::RED, fg, bright, > + "[41m[K"); > + ASSERT_NAMED_COL_STREQ (style::named_color::GREEN, fg, bright, > + "[42m[K"); > + ASSERT_NAMED_COL_STREQ (style::named_color::YELLOW, fg, bright, > + "[43m[K"); > + ASSERT_NAMED_COL_STREQ (style::named_color::BLUE, fg, bright, > + "[44m[K"); > + ASSERT_NAMED_COL_STREQ (style::named_color::MAGENTA, fg, bright, > + "[45m[K"); > + ASSERT_NAMED_COL_STREQ (style::named_color::CYAN, fg, bright, > + "[46m[K"); > + ASSERT_NAMED_COL_STREQ (style::named_color::WHITE, fg, bright, > + "[47m[K"); > + } > + { > + const bool bright = true; > + ASSERT_NAMED_COL_STREQ (style::named_color::DEFAULT, fg, bright, > + "[m[K"); > + ASSERT_NAMED_COL_STREQ (style::named_color::BLACK, fg, bright, > + "[100m[K"); > + ASSERT_NAMED_COL_STREQ (style::named_color::RED, fg, bright, > + "[101m[K"); > + ASSERT_NAMED_COL_STREQ (style::named_color::GREEN, fg, bright, > + "[102m[K"); > + ASSERT_NAMED_COL_STREQ (style::named_color::YELLOW, fg, bright, > + "[103m[K"); > + ASSERT_NAMED_COL_STREQ (style::named_color::BLUE, fg, bright, > + "[104m[K"); > + ASSERT_NAMED_COL_STREQ (style::named_color::MAGENTA, fg, bright, > + "[105m[K"); > + ASSERT_NAMED_COL_STREQ (style::named_color::CYAN, fg, bright, > + "[106m[K"); > + ASSERT_NAMED_COL_STREQ (style::named_color::WHITE, fg, bright, > + "[107m[K"); > + } > + } > +} > + > +#define ASSERT_8_BIT_COL_STREQ(COL_VAL, FG, EXPECTED_STR) \ > + SELFTEST_BEGIN_STMT \ > + { \ > + style plain; \ > + style s; \ > + if (FG) \ > + s.m_fg_color = style::color (COL_VAL); \ > + else \ > + s.m_bg_color = style::color (COL_VAL); \ > + assert_style_change_streq ((SELFTEST_LOCATION), \ > + plain, \ > + s, \ > + (EXPECTED_STR)); \ > + } \ > + SELFTEST_END_STMT > + > +static void > +test_8_bit_colors () > +{ > + /* Foreground colors. */ > + { > + const bool fg = true; > + /* 0-15: standard and high-intensity standard colors. */ > + ASSERT_8_BIT_COL_STREQ (0, fg, "[38;5;0m[K"); > + ASSERT_8_BIT_COL_STREQ (15, fg, "[38;5;15m[K"); > + /* 16-231: 6x6x6 color cube. */ > + ASSERT_8_BIT_COL_STREQ (16, fg, "[38;5;16m[K"); > + ASSERT_8_BIT_COL_STREQ (231, fg, "[38;5;231m[K"); > + /* 232-255: grayscale. */ > + ASSERT_8_BIT_COL_STREQ (232, fg, "[38;5;232m[K"); > + ASSERT_8_BIT_COL_STREQ (255, fg, "[38;5;255m[K"); > + } > + /* Background colors. */ > + { > + const bool fg = false; > + /* 0-15: standard and high-intensity standard colors. */ > + ASSERT_8_BIT_COL_STREQ (0, fg, "[48;5;0m[K"); > + ASSERT_8_BIT_COL_STREQ (15, fg, "[48;5;15m[K"); > + /* 16-231: 6x6x6 color cube. */ > + ASSERT_8_BIT_COL_STREQ (16, fg, "[48;5;16m[K"); > + ASSERT_8_BIT_COL_STREQ (231, fg, "[48;5;231m[K"); > + /* 232-255: grayscale. */ > + ASSERT_8_BIT_COL_STREQ (232, fg, "[48;5;232m[K"); > + ASSERT_8_BIT_COL_STREQ (255, fg, "[48;5;255m[K"); > + } > +} > + > +#define ASSERT_24_BIT_COL_STREQ(R, G, B, FG, EXPECTED_STR) \ > + SELFTEST_BEGIN_STMT \ > + { \ > + style plain; \ > + style s; \ > + if (FG) \ > + s.m_fg_color = style::color ((R), (G), (B)); \ > + else \ > + s.m_bg_color = style::color ((R), (G), (B)); \ > + assert_style_change_streq ((SELFTEST_LOCATION), \ > + plain, \ > + s, \ > + (EXPECTED_STR)); \ > + } \ > + SELFTEST_END_STMT > + > +static void > +test_24_bit_colors () > +{ > + /* Foreground colors. */ > + { > + const bool fg = true; > + // #F3FAF2: > + ASSERT_24_BIT_COL_STREQ (0xf3, 0xfa, 0xf2, fg, > + "[38;2;243;250;242m[K"); > + } > + /* Background colors. */ > + { > + const bool fg = false; > + // #FDF7E7 > + ASSERT_24_BIT_COL_STREQ (0xfd, 0xf7, 0xe7, fg, > + "[48;2;253;247;231m[K"); > + } > +} > + > +static void > +test_style_combinations () > +{ > + style_manager sm; > + ASSERT_EQ (sm.get_num_styles (), 1); > + > + style plain; > + ASSERT_EQ (sm.get_or_create_id (plain), 0); > + ASSERT_EQ (sm.get_num_styles (), 1); > + > + style bold; > + bold.m_bold = true; > + > + ASSERT_EQ (sm.get_or_create_id (bold), 1); > + ASSERT_EQ (sm.get_num_styles (), 2); > + ASSERT_EQ (sm.get_or_create_id (bold), 1); > + ASSERT_EQ (sm.get_num_styles (), 2); > + > + style magenta_on_blue; > + magenta_on_blue.m_fg_color = style::named_color::MAGENTA; > + magenta_on_blue.m_bg_color = style::named_color::BLUE; > + ASSERT_EQ (sm.get_or_create_id (magenta_on_blue), 2); > + ASSERT_EQ (sm.get_num_styles (), 3); > + ASSERT_EQ (sm.get_or_create_id (magenta_on_blue), 2); > + ASSERT_EQ (sm.get_num_styles (), 3); > +} > + > +/* Run all selftests in this file. */ > + > +void > +text_art_style_cc_tests () > +{ > + test_bold (); > + test_underscore (); > + test_blink (); > + test_named_colors (); > + test_8_bit_colors (); > + test_24_bit_colors (); > + test_style_combinations (); > +} > + > +} // namespace selftest > + > + > +#endif /* #if CHECKING_P */ > diff --git a/gcc/text-art/styled-string.cc b/gcc/text-art/styled-string.cc > new file mode 100644 > index 00000000000..cd176b2313f > --- /dev/null > +++ b/gcc/text-art/styled-string.cc > @@ -0,0 +1,1107 @@ > +/* Implementation of text_art::styled_string. > + Copyright (C) 2023 Free Software Foundation, Inc. > + Contributed by David Malcolm <dmalcolm@redhat.com>. > + > +This file is part of GCC. > + > +GCC is free software; you can redistribute it and/or modify it under > +the terms of the GNU General Public License as published by the Free > +Software Foundation; either version 3, or (at your option) any later > +version. > + > +GCC is distributed in the hope that it will be useful, but WITHOUT ANY > +WARRANTY; without even the implied warranty of MERCHANTABILITY or > +FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License > +for more details. > + > +You should have received a copy of the GNU General Public License > +along with GCC; see the file COPYING3. If not see > +<http://www.gnu.org/licenses/>. */ > + > +#include "config.h" > +#define INCLUDE_MEMORY > +#include "system.h" > +#include "coretypes.h" > +#include "make-unique.h" > +#include "pretty-print.h" > +#include "intl.h" > +#include "diagnostic.h" > +#include "selftest.h" > +#include "text-art/selftests.h" > +#include "text-art/types.h" > +#include "color-macros.h" > + > +using namespace text_art; > + > +namespace { > + > +/* Support class for parsing text containing escape codes. > + See e.g. https://en.wikipedia.org/wiki/ANSI_escape_code > + We only support the codes that pretty-print.cc can generate. */ > + > +class escape_code_parser > +{ > +public: > + escape_code_parser (style_manager &sm, > + std::vector<styled_unichar> &out) > + : m_sm (sm), > + m_out (out), > + m_cur_style_obj (), > + m_cur_style_id (style::id_plain), > + m_state (state::START) > + { > + } > + > + void on_char (cppchar_t ch) > + { > + switch (m_state) > + { > + default: > + gcc_unreachable (); > + case state::START: > + if (ch == '\033') > + { > + /* The start of an escape sequence. */ > + m_state = state::AFTER_ESC; > + return; > + } > + break; > + case state::AFTER_ESC: > + if (ch == '[') > + { > + /* ESC [ is a Control Sequence Introducer. */ > + m_state = state::CS_PARAMETER_BYTES; > + return; > + } > + else if (ch == ']') > + { > + /* ESC ] is an Operating System Command. */ > + m_state = state::WITHIN_OSC; > + return; > + } > + break; > + case state::CS_PARAMETER_BYTES: > + if (parameter_byte_p (ch)) > + { > + m_parameter_bytes.push_back ((char)ch); > + return; > + } > + else if (intermediate_byte_p (ch)) > + { > + m_intermediate_bytes.push_back ((char)ch); > + m_state = state::CS_INTERMEDIATE_BYTES; > + return; > + } > + else if (final_byte_p (ch)) > + { > + on_final_csi_char (ch); > + return; > + } > + break; > + case state::CS_INTERMEDIATE_BYTES: > + /* Expect zero or more intermediate bytes. */ > + if (intermediate_byte_p (ch)) > + { > + m_intermediate_bytes.push_back ((char)ch); > + return; > + } > + else if (final_byte_p (ch)) > + { > + on_final_csi_char (ch); > + return; > + } > + break; > + case state::WITHIN_OSC: > + /* Accumulate chars into m_osc_string, until we see an ST or a BEL. */ > + { > + /* Check for ESC \, the String Terminator (aka "ST"). */ > + if (ch == '\\' > + && m_osc_string.size () > 0 > + && m_osc_string.back () == '\033') > + { > + m_osc_string.pop_back (); > + on_final_osc_char (); > + return; > + } > + else if (ch == '\a') > + { > + // BEL > + on_final_osc_char (); > + return; > + } > + m_osc_string.push_back (ch); > + return; > + } > + break; > + } > + > + /* Test of handling U+FE0F VARIATION SELECTOR-16 to select the emoji > + variation for the previous character. */ > + if (ch == 0xFE0F) > + { > + if (m_out.size () > 0) > + m_out.back ().set_emoji_variant (); > + return; > + } > + > + if (cpp_is_combining_char (ch)) > + { > + if (m_out.size () > 0) > + { > + m_out.back ().add_combining_char (ch); > + return; > + } > + } > + /* By default, add the char. */ > + m_out.push_back (styled_unichar (ch, false, m_cur_style_id)); > + } > + > +private: > + void on_final_csi_char (cppchar_t ch) > + { > + switch (ch) > + { > + default: > + /* Unrecognized. */ > + break; > + case 'm': > + { > + /* SGR control sequence. */ > + if (m_parameter_bytes.empty ()) > + reset_style (); > + std::vector<int> params (params_from_decimal ()); > + for (auto iter = params.begin (); iter != params.end (); ) > + { > + const int param = *iter; > + switch (param) > + { > + default: > + /* Unrecognized SGR parameter. */ > + break; > + case 0: > + reset_style (); > + break; > + case 1: > + set_style_bold (); > + break; > + case 4: > + set_style_underscore (); > + break; > + case 5: > + set_style_blink (); > + break; > + > + /* Named foreground colors. */ > + case 30: > + set_style_fg_color (style::named_color::BLACK); > + break; > + case 31: > + set_style_fg_color (style::named_color::RED); > + break; > + case 32: > + set_style_fg_color (style::named_color::GREEN); > + break; > + case 33: > + set_style_fg_color (style::named_color::YELLOW); > + break; > + case 34: > + set_style_fg_color (style::named_color::BLUE); > + break; > + case 35: > + set_style_fg_color (style::named_color::MAGENTA); > + break; > + case 36: > + set_style_fg_color (style::named_color::CYAN); > + break; > + case 37: > + set_style_fg_color (style::named_color::WHITE); > + break; > + > + /* 8-bit and 24-bit color */ > + case 38: > + case 48: > + { > + const bool fg = (param == 38); > + iter++; > + if (iter != params.end ()) > + switch (*(iter++)) > + { > + default: > + break; > + case 5: > + /* 8-bit color. */ > + if (iter != params.end ()) > + { > + const uint8_t col = *(iter++); > + if (fg) > + set_style_fg_color (style::color (col)); > + else > + set_style_bg_color (style::color (col)); > + } > + continue; > + case 2: > + /* 24-bit color. */ > + if (iter != params.end ()) > + { > + const uint8_t r = *(iter++); > + if (iter != params.end ()) > + { > + const uint8_t g = *(iter++); > + if (iter != params.end ()) > + { > + const uint8_t b = *(iter++); > + if (fg) > + set_style_fg_color (style::color (r, > + g, > + b)); > + else > + set_style_bg_color (style::color (r, > + g, > + b)); > + } > + } > + } > + continue; > + } > + continue; > + } > + break; > + > + /* Named background colors. */ > + case 40: > + set_style_bg_color (style::named_color::BLACK); > + break; > + case 41: > + set_style_bg_color (style::named_color::RED); > + break; > + case 42: > + set_style_bg_color (style::named_color::GREEN); > + break; > + case 43: > + set_style_bg_color (style::named_color::YELLOW); > + break; > + case 44: > + set_style_bg_color (style::named_color::BLUE); > + break; > + case 45: > + set_style_bg_color (style::named_color::MAGENTA); > + break; > + case 46: > + set_style_bg_color (style::named_color::CYAN); > + break; > + case 47: > + set_style_bg_color (style::named_color::WHITE); > + break; > + > + /* Named foreground colors, bright. */ > + case 90: > + set_style_fg_color (style::color (style::named_color::BLACK, > + true)); > + break; > + case 91: > + set_style_fg_color (style::color (style::named_color::RED, > + true)); > + break; > + case 92: > + set_style_fg_color (style::color (style::named_color::GREEN, > + true)); > + break; > + case 93: > + set_style_fg_color (style::color (style::named_color::YELLOW, > + true)); > + break; > + case 94: > + set_style_fg_color (style::color (style::named_color::BLUE, > + true)); > + break; > + case 95: > + set_style_fg_color (style::color (style::named_color::MAGENTA, > + true)); > + break; > + case 96: > + set_style_fg_color (style::color (style::named_color::CYAN, > + true)); > + break; > + case 97: > + set_style_fg_color (style::color (style::named_color::WHITE, > + true)); > + break; > + > + /* Named foreground colors, bright. */ > + case 100: > + set_style_bg_color (style::color (style::named_color::BLACK, > + true)); > + break; > + case 101: > + set_style_bg_color (style::color (style::named_color::RED, > + true)); > + break; > + case 102: > + set_style_bg_color (style::color (style::named_color::GREEN, > + true)); > + break; > + case 103: > + set_style_bg_color (style::color (style::named_color::YELLOW, > + true)); > + break; > + case 104: > + set_style_bg_color (style::color (style::named_color::BLUE, > + true)); > + break; > + case 105: > + set_style_bg_color (style::color (style::named_color::MAGENTA, > + true)); > + break; > + case 106: > + set_style_bg_color (style::color (style::named_color::CYAN, > + true)); > + break; > + case 107: > + set_style_bg_color (style::color (style::named_color::WHITE, > + true)); > + break; > + } > + ++iter; > + } > + } > + break; > + } > + m_parameter_bytes.clear (); > + m_intermediate_bytes.clear (); > + m_state = state::START; > + } > + > + void on_final_osc_char () > + { > + if (!m_osc_string.empty ()) > + { > + switch (m_osc_string[0]) > + { > + default: > + break; > + case '8': > + /* Hyperlink support; see: > + https://gist.github.com/egmontkob/eb114294efbcd5adb1944c9f3cb5feda > + We don't support params, so we expect either: > + (a) "8;;URL" to begin a url (see pp_begin_url), or > + (b) "8;;" to end a URL (see pp_end_url). */ > + if (m_osc_string.size () >= 3 > + && m_osc_string[1] == ';' > + && m_osc_string[2] == ';') > + { > + set_style_url (m_osc_string.begin () + 3, > + m_osc_string.end ()); > + } > + break; > + } > + } > + m_osc_string.clear (); > + m_state = state::START; > + } > + > + std::vector<int> params_from_decimal () const > + { > + std::vector<int> result; > + > + int curr_int = -1; > + for (auto param_ch : m_parameter_bytes) > + { > + if (param_ch >= '0' && param_ch <= '9') > + { > + if (curr_int == -1) > + curr_int = 0; > + else > + curr_int *= 10; > + curr_int += param_ch - '0'; > + } > + else > + { > + if (curr_int != -1) > + { > + result.push_back (curr_int); > + curr_int = -1; > + } > + } > + } > + if (curr_int != -1) > + result.push_back (curr_int); > + return result; > + } > + > + void refresh_style_id () > + { > + m_cur_style_id = m_sm.get_or_create_id (m_cur_style_obj); > + } > + void reset_style () > + { > + m_cur_style_obj = style (); > + refresh_style_id (); > + } > + void set_style_bold () > + { > + m_cur_style_obj.m_bold = true; > + refresh_style_id (); > + } > + void set_style_underscore () > + { > + m_cur_style_obj.m_underscore = true; > + refresh_style_id (); > + } > + void set_style_blink () > + { > + m_cur_style_obj.m_blink = true; > + refresh_style_id (); > + } > + void set_style_fg_color (style::color color) > + { > + m_cur_style_obj.m_fg_color = color; > + refresh_style_id (); > + } > + void set_style_bg_color (style::color color) > + { > + m_cur_style_obj.m_bg_color = color; > + refresh_style_id (); > + } > + void set_style_url (std::vector<cppchar_t>::iterator begin, > + std::vector<cppchar_t>::iterator end) > + { > + // The empty string means "no URL" > + m_cur_style_obj.m_url = std::vector<cppchar_t> (begin, end); > + refresh_style_id (); > + } > + > + static bool parameter_byte_p (cppchar_t ch) > + { > + return ch >= 0x30 && ch <= 0x3F; > + } > + > + static bool intermediate_byte_p (cppchar_t ch) > + { > + return ch >= 0x20 && ch <= 0x2F; > + } > + > + static bool final_byte_p (cppchar_t ch) > + { > + return ch >= 0x40 && ch <= 0x7E; > + } > + > + style_manager &m_sm; > + std::vector<styled_unichar> &m_out; > + > + style m_cur_style_obj; > + style::id_t m_cur_style_id; > + > + /* Handling of control sequences. */ > + enum class state > + { > + START, > + > + /* After ESC, expecting '['. */ > + AFTER_ESC, > + > + /* Expecting zero or more parameter bytes, an > + intermediate byte, or a final byte. */ > + CS_PARAMETER_BYTES, > + > + /* Expecting zero or more intermediate bytes, or a final byte. */ > + CS_INTERMEDIATE_BYTES, > + > + /* Within OSC. */ > + WITHIN_OSC > + > + } m_state; > + std::vector<char> m_parameter_bytes; > + std::vector<char> m_intermediate_bytes; > + std::vector<cppchar_t> m_osc_string; > +}; > + > +} // anon namespace > + > +/* class text_art::styled_string. */ > + > +/* Construct a styled_string from STR. > + STR is assumed to be UTF-8 encoded and 0-terminated. > + > + Parse SGR formatting chars from being in-band (within in the sequence > + of chars) to being out-of-band, as style elements. > + We only support parsing the subset of SGR chars that can be emitted > + by pretty-print.cc */ > + > +styled_string::styled_string (style_manager &sm, const char *str) > +: m_chars () > +{ > + escape_code_parser parser (sm, m_chars); > + > + /* We don't actually want the display widths here, but > + it's an easy way to decode UTF-8. */ > + cpp_char_column_policy policy (8, cpp_wcwidth); > + cpp_display_width_computation dw (str, strlen (str), policy); > + while (!dw.done ()) > + { > + cpp_decoded_char decoded_char; > + dw.process_next_codepoint (&decoded_char); > + > + if (!decoded_char.m_valid_ch) > + /* Skip bytes that aren't valid UTF-8. */ > + continue; > + > + /* Decode SGR formatting. */ > + cppchar_t ch = decoded_char.m_ch; > + parser.on_char (ch); > + } > +} > + > +styled_string::styled_string (cppchar_t cppchar, bool emoji) > +{ > + m_chars.push_back (styled_unichar (cppchar, emoji, style::id_plain)); > +} > + > +styled_string > +styled_string::from_fmt_va (style_manager &sm, > + printer_fn format_decoder, > + const char *fmt, > + va_list *args) > +{ > + text_info text; > + text.err_no = errno; > + text.args_ptr = args; > + text.format_spec = fmt; > + pretty_printer pp; > + pp_show_color (&pp) = true; > + pp.url_format = URL_FORMAT_DEFAULT; > + pp_format_decoder (&pp) = format_decoder; > + pp_format (&pp, &text); > + pp_output_formatted_text (&pp); > + styled_string result (sm, pp_formatted_text (&pp)); > + return result; > +} > + > +styled_string > +styled_string::from_fmt (style_manager &sm, > + printer_fn format_decoder, > + const char *fmt, ...) > +{ > + va_list ap; > + va_start (ap, fmt); > + styled_string result = from_fmt_va (sm, format_decoder, fmt, &ap); > + va_end (ap); > + return result; > +} > + > +int > +styled_string::calc_canvas_width () const > +{ > + int result = 0; > + for (auto ch : m_chars) > + result += ch.get_canvas_width (); > + return result; > +} > + > +void > +styled_string::append (const styled_string &suffix) > +{ > + m_chars.insert<std::vector<styled_unichar>::const_iterator> (m_chars.end (), > + suffix.begin (), > + suffix.end ()); > +} > + > +void > +styled_string::set_url (style_manager &sm, const char *url) > +{ > + for (auto& ch : m_chars) > + { > + const style &existing_style = sm.get_style (ch.get_style_id ()); > + style with_url (existing_style); > + with_url.set_style_url (url); > + ch.m_style_id = sm.get_or_create_id (with_url); > + } > +} > + > +#if CHECKING_P > + > +namespace selftest { > + > +static void > +test_combining_chars () > +{ > + /* This really ought to be in libcpp, but we don't have > + selftests there. */ > + ASSERT_FALSE (cpp_is_combining_char (0)); > + ASSERT_FALSE (cpp_is_combining_char ('a')); > + > + /* COMBINING BREVE (U+0306). */ > + ASSERT_TRUE (cpp_is_combining_char (0x0306)); > + > + /* U+5B57 CJK UNIFIED IDEOGRAPH-5B57. */ > + ASSERT_FALSE (cpp_is_combining_char (0x5B57)); > + > + /* U+FE0F VARIATION SELECTOR-16. */ > + ASSERT_FALSE (cpp_is_combining_char (0xFE0F)); > +} > + > +static void > +test_empty () > +{ > + style_manager sm; > + styled_string s (sm, ""); > + ASSERT_EQ (s.size (), 0); > + ASSERT_EQ (s.calc_canvas_width (), 0); > +} > + > +/* Test of a pure ASCII string with no escape codes. */ > + > +static void > +test_simple () > +{ > + const char *c_str = "hello world!"; > + style_manager sm; > + styled_string s (sm, c_str); > + ASSERT_EQ (s.size (), strlen (c_str)); > + ASSERT_EQ (s.calc_canvas_width (), (int)strlen (c_str)); > + for (size_t i = 0; i < strlen (c_str); i++) > + { > + ASSERT_EQ (s[i].get_code (), (cppchar_t)c_str[i]); > + ASSERT_EQ (s[i].get_style_id (), 0); > + } > +} > + > +/* Test of decoding UTF-8. */ > + > +static void > +test_pi_from_utf8 () > +{ > + /* U+03C0 "GREEK SMALL LETTER PI". */ > + const char * const pi_utf8 = "\xCF\x80"; > + > + style_manager sm; > + styled_string s (sm, pi_utf8); > + ASSERT_EQ (s.size (), 1); > + ASSERT_EQ (s.calc_canvas_width (), 1); > + ASSERT_EQ (s[0].get_code (), 0x03c0); > + ASSERT_EQ (s[0].emoji_variant_p (), false); > + ASSERT_EQ (s[0].double_width_p (), false); > + ASSERT_EQ (s[0].get_style_id (), 0); > +} > + > +/* Test of double-width character. */ > + > +static void > +test_emoji_from_utf8 () > +{ > + /* U+1F642 "SLIGHTLY SMILING FACE". */ > + const char * const emoji_utf8 = "\xF0\x9F\x99\x82"; > + > + style_manager sm; > + styled_string s (sm, emoji_utf8); > + ASSERT_EQ (s.size (), 1); > + ASSERT_EQ (s.calc_canvas_width (), 2); > + ASSERT_EQ (s[0].get_code (), 0x1f642); > + ASSERT_EQ (s[0].double_width_p (), true); > + ASSERT_EQ (s[0].get_style_id (), 0); > +} > + > +/* Test of handling U+FE0F VARIATION SELECTOR-16 to select the emoji > + variation for the previous character. */ > + > +static void > +test_emoji_variant_from_utf8 () > +{ > + const char * const emoji_utf8 > + = (/* U+26A0 WARNING SIGN. */ > + "\xE2\x9A\xA0" > + /* U+FE0F VARIATION SELECTOR-16 (emoji variation selector). */ > + "\xEF\xB8\x8F"); > + > + style_manager sm; > + styled_string s (sm, emoji_utf8); > + ASSERT_EQ (s.size (), 1); > + ASSERT_EQ (s.calc_canvas_width (), 1); > + ASSERT_EQ (s[0].get_code (), 0x26a0); > + ASSERT_EQ (s[0].emoji_variant_p (), true); > + ASSERT_EQ (s[0].double_width_p (), false); > + ASSERT_EQ (s[0].get_style_id (), 0); > +} > + > +static void > +test_emoji_from_codepoint () > +{ > + styled_string s ((cppchar_t)0x1f642); > + ASSERT_EQ (s.size (), 1); > + ASSERT_EQ (s.calc_canvas_width (), 2); > + ASSERT_EQ (s[0].get_code (), 0x1f642); > + ASSERT_EQ (s[0].double_width_p (), true); > + ASSERT_EQ (s[0].get_style_id (), 0); > +} > + > +static void > +test_from_mixed_width_utf8 () > +{ > + /* This UTF-8 string literal is of the form > + before mojibake after > + where the Japanese word "mojibake" is written as the following > + four unicode code points: > + U+6587 CJK UNIFIED IDEOGRAPH-6587 > + U+5B57 CJK UNIFIED IDEOGRAPH-5B57 > + U+5316 CJK UNIFIED IDEOGRAPH-5316 > + U+3051 HIRAGANA LETTER KE. > + Each of these is 3 bytes wide when encoded in UTF-8, whereas the > + "before" and "after" are 1 byte per unicode character. */ > + const char * const mixed_width_utf8 > + = ("before " > + > + /* U+6587 CJK UNIFIED IDEOGRAPH-6587 > + UTF-8: 0xE6 0x96 0x87 > + C octal escaped UTF-8: \346\226\207. */ > + "\346\226\207" > + > + /* U+5B57 CJK UNIFIED IDEOGRAPH-5B57 > + UTF-8: 0xE5 0xAD 0x97 > + C octal escaped UTF-8: \345\255\227. */ > + "\345\255\227" > + > + /* U+5316 CJK UNIFIED IDEOGRAPH-5316 > + UTF-8: 0xE5 0x8C 0x96 > + C octal escaped UTF-8: \345\214\226. */ > + "\345\214\226" > + > + /* U+3051 HIRAGANA LETTER KE > + UTF-8: 0xE3 0x81 0x91 > + C octal escaped UTF-8: \343\201\221. */ > + "\343\201\221" > + > + " after"); > + > + style_manager sm; > + styled_string s (sm, mixed_width_utf8); > + ASSERT_EQ (s.size (), 6 + 1 + 4 + 1 + 5); > + ASSERT_EQ (sm.get_num_styles (), 1); > + > + // We expect the Japanese characters to be double width. > + ASSERT_EQ (s.calc_canvas_width (), 6 + 1 + (2 * 4) + 1 + 5); > + > + ASSERT_EQ (s[0].get_code (), 'b'); > + ASSERT_EQ (s[0].double_width_p (), false); > + ASSERT_EQ (s[1].get_code (), 'e'); > + ASSERT_EQ (s[2].get_code (), 'f'); > + ASSERT_EQ (s[3].get_code (), 'o'); > + ASSERT_EQ (s[4].get_code (), 'r'); > + ASSERT_EQ (s[5].get_code (), 'e'); > + ASSERT_EQ (s[6].get_code (), ' '); > + ASSERT_EQ (s[7].get_code (), 0x6587); > + ASSERT_EQ (s[7].double_width_p (), true); > + ASSERT_EQ (s[8].get_code (), 0x5B57); > + ASSERT_EQ (s[9].get_code (), 0x5316); > + ASSERT_EQ (s[10].get_code (), 0x3051); > + ASSERT_EQ (s[11].get_code (), ' '); > + ASSERT_EQ (s[12].get_code (), 'a'); > + ASSERT_EQ (s[13].get_code (), 'f'); > + ASSERT_EQ (s[14].get_code (), 't'); > + ASSERT_EQ (s[15].get_code (), 'e'); > + ASSERT_EQ (s[16].get_code (), 'r'); > + > + ASSERT_EQ (s[0].get_style_id (), 0); > +} > + > +static void > +assert_style_urleq (const location &loc, > + const style &s, > + const char *expected_str) > +{ > + ASSERT_EQ_AT (loc, s.m_url.size (), strlen (expected_str)); > + for (size_t i = 0; i < s.m_url.size (); i++) > + ASSERT_EQ_AT (loc, s.m_url[i], (cppchar_t)expected_str[i]); > +} > + > +#define ASSERT_STYLE_URLEQ(STYLE, EXPECTED_STR) \ > + assert_style_urleq ((SELFTEST_LOCATION), (STYLE), (EXPECTED_STR)) > + > +static void > +test_url () > +{ > + // URL_FORMAT_ST > + { > + style_manager sm; > + styled_string s > + (sm, "\33]8;;http://example.com\33\\This is a link\33]8;;\33\\"); > + const char *expected = "This is a link"; > + ASSERT_EQ (s.size (), strlen (expected)); > + ASSERT_EQ (s.calc_canvas_width (), (int)strlen (expected)); > + ASSERT_EQ (sm.get_num_styles (), 2); > + for (size_t i = 0; i < strlen (expected); i++) > + { > + ASSERT_EQ (s[i].get_code (), (cppchar_t)expected[i]); > + ASSERT_EQ (s[i].get_style_id (), 1); > + } > + ASSERT_STYLE_URLEQ (sm.get_style (1), "http://example.com"); > + } > + > + // URL_FORMAT_BEL > + { > + style_manager sm; > + styled_string s > + (sm, "\33]8;;http://example.com\aThis is a link\33]8;;\a"); > + const char *expected = "This is a link"; > + ASSERT_EQ (s.size (), strlen (expected)); > + ASSERT_EQ (s.calc_canvas_width (), (int)strlen (expected)); > + ASSERT_EQ (sm.get_num_styles (), 2); > + for (size_t i = 0; i < strlen (expected); i++) > + { > + ASSERT_EQ (s[i].get_code (), (cppchar_t)expected[i]); > + ASSERT_EQ (s[i].get_style_id (), 1); > + } > + ASSERT_STYLE_URLEQ (sm.get_style (1), "http://example.com"); > + } > +} > + > +static void > +test_from_fmt () > +{ > + style_manager sm; > + styled_string s (styled_string::from_fmt (sm, NULL, "%%i: %i", 42)); > + ASSERT_EQ (s[0].get_code (), '%'); > + ASSERT_EQ (s[1].get_code (), 'i'); > + ASSERT_EQ (s[2].get_code (), ':'); > + ASSERT_EQ (s[3].get_code (), ' '); > + ASSERT_EQ (s[4].get_code (), '4'); > + ASSERT_EQ (s[5].get_code (), '2'); > + ASSERT_EQ (s.size (), 6); > + ASSERT_EQ (s.calc_canvas_width (), 6); > +} > + > +static void > +test_from_fmt_qs () > +{ > + auto_fix_quotes fix_quotes; > + open_quote = "\xe2\x80\x98"; > + close_quote = "\xe2\x80\x99"; > + > + style_manager sm; > + styled_string s (styled_string::from_fmt (sm, NULL, "%qs", "msg")); > + ASSERT_EQ (sm.get_num_styles (), 2); > + ASSERT_EQ (s[0].get_code (), 0x2018); > + ASSERT_EQ (s[0].get_style_id (), 0); > + ASSERT_EQ (s[1].get_code (), 'm'); > + ASSERT_EQ (s[1].get_style_id (), 1); > + ASSERT_EQ (s[2].get_code (), 's'); > + ASSERT_EQ (s[2].get_style_id (), 1); > + ASSERT_EQ (s[3].get_code (), 'g'); > + ASSERT_EQ (s[3].get_style_id (), 1); > + ASSERT_EQ (s[4].get_code (), 0x2019); > + ASSERT_EQ (s[4].get_style_id (), 0); > + ASSERT_EQ (s.size (), 5); > +} > + > +// Test of parsing SGR codes. > + > +static void > +test_from_str_with_bold () > +{ > + style_manager sm; > + /* This is the result of pp_printf (pp, "%qs", "foo") > + with auto_fix_quotes. */ > + styled_string s (sm, "`\33[01m\33[Kfoo\33[m\33[K'"); > + ASSERT_EQ (s[0].get_code (), '`'); > + ASSERT_EQ (s[0].get_style_id (), 0); > + ASSERT_EQ (s[1].get_code (), 'f'); > + ASSERT_EQ (s[1].get_style_id (), 1); > + ASSERT_EQ (s[2].get_code (), 'o'); > + ASSERT_EQ (s[2].get_style_id (), 1); > + ASSERT_EQ (s[3].get_code (), 'o'); > + ASSERT_EQ (s[3].get_style_id (), 1); > + ASSERT_EQ (s[4].get_code (), '\''); > + ASSERT_EQ (s[4].get_style_id (), 0); > + ASSERT_EQ (s.size (), 5); > + ASSERT_TRUE (sm.get_style (1).m_bold); > +} > + > +static void > +test_from_str_with_underscore () > +{ > + style_manager sm; > + styled_string s (sm, "\33[04m\33[KA"); > + ASSERT_EQ (s[0].get_code (), 'A'); > + ASSERT_EQ (s[0].get_style_id (), 1); > + ASSERT_TRUE (sm.get_style (1).m_underscore); > +} > + > +static void > +test_from_str_with_blink () > +{ > + style_manager sm; > + styled_string s (sm, "\33[05m\33[KA"); > + ASSERT_EQ (s[0].get_code (), 'A'); > + ASSERT_EQ (s[0].get_style_id (), 1); > + ASSERT_TRUE (sm.get_style (1).m_blink); > +} > + > +// Test of parsing SGR codes. > + > +static void > +test_from_str_with_color () > +{ > + style_manager sm; > + > + styled_string s (sm, > + ("0" > + SGR_SEQ (COLOR_FG_RED) > + "R" > + SGR_RESET > + "2" > + SGR_SEQ (COLOR_FG_GREEN) > + "G" > + SGR_RESET > + "4")); > + ASSERT_EQ (s.size (), 5); > + ASSERT_EQ (sm.get_num_styles (), 3); > + ASSERT_EQ (s[0].get_code (), '0'); > + ASSERT_EQ (s[0].get_style_id (), 0); > + ASSERT_EQ (s[1].get_code (), 'R'); > + ASSERT_EQ (s[1].get_style_id (), 1); > + ASSERT_EQ (s[2].get_code (), '2'); > + ASSERT_EQ (s[2].get_style_id (), 0); > + ASSERT_EQ (s[3].get_code (), 'G'); > + ASSERT_EQ (s[3].get_style_id (), 2); > + ASSERT_EQ (s[4].get_code (), '4'); > + ASSERT_EQ (s[4].get_style_id (), 0); > + ASSERT_EQ (sm.get_style (1).m_fg_color, style::named_color::RED); > + ASSERT_EQ (sm.get_style (2).m_fg_color, style::named_color::GREEN); > +} > + > +static void > +test_from_str_with_named_color () > +{ > + style_manager sm; > + styled_string s (sm, > + ("F" > + SGR_SEQ (COLOR_FG_BLACK) "F" > + SGR_SEQ (COLOR_FG_RED) "F" > + SGR_SEQ (COLOR_FG_GREEN) "F" > + SGR_SEQ (COLOR_FG_YELLOW) "F" > + SGR_SEQ (COLOR_FG_BLUE) "F" > + SGR_SEQ (COLOR_FG_MAGENTA) "F" > + SGR_SEQ (COLOR_FG_CYAN) "F" > + SGR_SEQ (COLOR_FG_WHITE) "F" > + SGR_SEQ (COLOR_FG_BRIGHT_BLACK) "F" > + SGR_SEQ (COLOR_FG_BRIGHT_RED) "F" > + SGR_SEQ (COLOR_FG_BRIGHT_GREEN) "F" > + SGR_SEQ (COLOR_FG_BRIGHT_YELLOW) "F" > + SGR_SEQ (COLOR_FG_BRIGHT_BLUE) "F" > + SGR_SEQ (COLOR_FG_BRIGHT_MAGENTA) "F" > + SGR_SEQ (COLOR_FG_BRIGHT_CYAN) "F" > + SGR_SEQ (COLOR_FG_BRIGHT_WHITE) "F" > + SGR_SEQ (COLOR_BG_BLACK) "B" > + SGR_SEQ (COLOR_BG_RED) "B" > + SGR_SEQ (COLOR_BG_GREEN) "B" > + SGR_SEQ (COLOR_BG_YELLOW) "B" > + SGR_SEQ (COLOR_BG_BLUE) "B" > + SGR_SEQ (COLOR_BG_MAGENTA) "B" > + SGR_SEQ (COLOR_BG_CYAN) "B" > + SGR_SEQ (COLOR_BG_WHITE) "B" > + SGR_SEQ (COLOR_BG_BRIGHT_BLACK) "B" > + SGR_SEQ (COLOR_BG_BRIGHT_RED) "B" > + SGR_SEQ (COLOR_BG_BRIGHT_GREEN) "B" > + SGR_SEQ (COLOR_BG_BRIGHT_YELLOW) "B" > + SGR_SEQ (COLOR_BG_BRIGHT_BLUE) "B" > + SGR_SEQ (COLOR_BG_BRIGHT_MAGENTA) "B" > + SGR_SEQ (COLOR_BG_BRIGHT_CYAN) "B" > + SGR_SEQ (COLOR_BG_BRIGHT_WHITE) "B")); > + ASSERT_EQ (s.size (), 33); > + for (size_t i = 0; i < s.size (); i++) > + ASSERT_EQ (s[i].get_style_id (), i); > + for (size_t i = 0; i < 17; i++) > + ASSERT_EQ (s[i].get_code (), 'F'); > + for (size_t i = 17; i < 33; i++) > + ASSERT_EQ (s[i].get_code (), 'B'); > +} > + > +static void > +test_from_str_with_8_bit_color () > +{ > + { > + style_manager sm; > + styled_string s (sm, > + ("[38;5;232m[KF")); > + ASSERT_EQ (s.size (), 1); > + ASSERT_EQ (s[0].get_code (), 'F'); > + ASSERT_EQ (s[0].get_style_id (), 1); > + ASSERT_EQ (sm.get_style (1).m_fg_color, style::color (232)); > + } > + { > + style_manager sm; > + styled_string s (sm, > + ("[48;5;231m[KB")); > + ASSERT_EQ (s.size (), 1); > + ASSERT_EQ (s[0].get_code (), 'B'); > + ASSERT_EQ (s[0].get_style_id (), 1); > + ASSERT_EQ (sm.get_style (1).m_bg_color, style::color (231)); > + } > +} > + > +static void > +test_from_str_with_24_bit_color () > +{ > + { > + style_manager sm; > + styled_string s (sm, > + ("[38;2;243;250;242m[KF")); > + ASSERT_EQ (s.size (), 1); > + ASSERT_EQ (s[0].get_code (), 'F'); > + ASSERT_EQ (s[0].get_style_id (), 1); > + ASSERT_EQ (sm.get_style (1).m_fg_color, style::color (243, 250, 242)); > + } > + { > + style_manager sm; > + styled_string s (sm, > + ("[48;2;253;247;231m[KB")); > + ASSERT_EQ (s.size (), 1); > + ASSERT_EQ (s[0].get_code (), 'B'); > + ASSERT_EQ (s[0].get_style_id (), 1); > + ASSERT_EQ (sm.get_style (1).m_bg_color, style::color (253, 247, 231)); > + } > +} > + > +static void > +test_from_str_combining_characters () > +{ > + style_manager sm; > + styled_string s (sm, > + /* CYRILLIC CAPITAL LETTER U (U+0423). */ > + "\xD0\xA3" > + /* COMBINING BREVE (U+0306). */ > + "\xCC\x86"); > + ASSERT_EQ (s.size (), 1); > + ASSERT_EQ (s[0].get_code (), 0x423); > + ASSERT_EQ (s[0].get_combining_chars ().size (), 1); > + ASSERT_EQ (s[0].get_combining_chars ()[0], 0x306); > +} > + > +/* Run all selftests in this file. */ > + > +void > +text_art_styled_string_cc_tests () > +{ > + test_combining_chars (); > + test_empty (); > + test_simple (); > + test_pi_from_utf8 (); > + test_emoji_from_utf8 (); > + test_emoji_variant_from_utf8 (); > + test_emoji_from_codepoint (); > + test_from_mixed_width_utf8 (); > + test_url (); > + test_from_fmt (); > + test_from_fmt_qs (); > + test_from_str_with_bold (); > + test_from_str_with_underscore (); > + test_from_str_with_blink (); > + test_from_str_with_color (); > + test_from_str_with_named_color (); > + test_from_str_with_8_bit_color (); > + test_from_str_with_24_bit_color (); > + test_from_str_combining_characters (); > +} > + > +} // namespace selftest > + > + > +#endif /* #if CHECKING_P */ > diff --git a/gcc/text-art/table.cc b/gcc/text-art/table.cc > new file mode 100644 > index 00000000000..42cc4228ea6 > --- /dev/null > +++ b/gcc/text-art/table.cc > @@ -0,0 +1,1272 @@ > +/* Support for tabular/grid-based content. > + Copyright (C) 2023 Free Software Foundation, Inc. > + Contributed by David Malcolm <dmalcolm@redhat.com>. > + > +This file is part of GCC. > + > +GCC is free software; you can redistribute it and/or modify it under > +the terms of the GNU General Public License as published by the Free > +Software Foundation; either version 3, or (at your option) any later > +version. > + > +GCC is distributed in the hope that it will be useful, but WITHOUT ANY > +WARRANTY; without even the implied warranty of MERCHANTABILITY or > +FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License > +for more details. > + > +You should have received a copy of the GNU General Public License > +along with GCC; see the file COPYING3. If not see > +<http://www.gnu.org/licenses/>. */ > + > +#include "config.h" > +#define INCLUDE_MEMORY > +#include "system.h" > +#include "coretypes.h" > +#include "make-unique.h" > +#include "pretty-print.h" > +#include "diagnostic.h" > +#include "selftest.h" > +#include "text-art/selftests.h" > +#include "text-art/table.h" > + > +using namespace text_art; > + > +/* class text_art::table_cell_content. */ > + > +table_cell_content::table_cell_content (styled_string &&s) > +: m_str (std::move (s)), > + /* We assume here that the content occupies a single canvas row. */ > + m_size (m_str.calc_canvas_width (), 1) > +{ > +} > + > +void > +table_cell_content::paint_to_canvas (canvas &canvas, > + canvas::coord_t top_left) const > +{ > + canvas.paint_text (top_left, m_str); > +} > + > +/* struct text_art::table_dimension_sizes. */ > + > +table_dimension_sizes::table_dimension_sizes (unsigned num) > +: m_requirements (num, 0) > +{ > +} > + > +/* class text_art::table::cell_placement. */ > + > +void > +table::cell_placement::paint_cell_contents_to_canvas(canvas &canvas, > + canvas::coord_t offset, > + const table_geometry &tg) const > +{ > + const canvas::size_t req_canvas_size = get_min_canvas_size (); > + const canvas::size_t alloc_canvas_size = tg.get_canvas_size (m_rect); > + gcc_assert (req_canvas_size.w <= alloc_canvas_size.w); > + gcc_assert (req_canvas_size.h <= alloc_canvas_size.h); > + const int x_padding = alloc_canvas_size.w - req_canvas_size.w; > + const int y_padding = alloc_canvas_size.h - req_canvas_size.h; > + const table::coord_t table_top_left = m_rect.m_top_left; > + const canvas::coord_t canvas_top_left = tg.table_to_canvas (table_top_left); > + > + gcc_assert (x_padding >= 0); > + int x_align_offset; > + switch (m_x_align) > + { > + default: > + gcc_unreachable (); > + case x_align::LEFT: > + x_align_offset = 0; > + break; > + case x_align::CENTER: > + x_align_offset = x_padding / 2; > + break; > + case x_align::RIGHT: > + x_align_offset = x_padding; > + break; > + } > + > + gcc_assert (y_padding >= 0); > + int y_align_offset; > + switch (m_y_align) > + { > + default: > + gcc_unreachable (); > + case y_align::TOP: > + y_align_offset = 0; > + break; > + case y_align::CENTER: > + y_align_offset = y_padding / 2; > + break; > + case y_align::BOTTOM: > + y_align_offset = y_padding; > + break; > + } > + const canvas::coord_t content_rel_coord > + (canvas_top_left.x + 1 + x_align_offset, > + canvas_top_left.y + 1 + y_align_offset); > + m_content.paint_to_canvas (canvas, offset + content_rel_coord); > +} > + > +/* class text_art::table. */ > + > + > +table::table (size_t size) > +: m_size (size), > + m_placements (), > + m_occupancy (size) > +{ > + m_occupancy.fill (-1); > +} > + > +void > +table::set_cell (coord_t coord, > + table_cell_content &&content, > + enum x_align x_align, > + enum y_align y_align) > +{ > + set_cell_span (rect_t (coord, table::size_t (1, 1)), > + std::move (content), x_align, y_align); > +} > + > +void > +table::set_cell_span (rect_t span, > + table_cell_content &&content, > + enum x_align x_align, > + enum y_align y_align) > +{ > + gcc_assert (span.m_size.w > 0); > + gcc_assert (span.m_size.h > 0); > + int placement_idx = m_placements.size (); > + m_placements.emplace_back (cell_placement (span, std::move (content), > + x_align, y_align)); > + for (int y = span.get_min_y (); y < span.get_next_y (); y++) > + for (int x = span.get_min_x (); x < span.get_next_x (); x++) > + { > + gcc_assert (m_occupancy.get (coord_t (x, y)) == -1); > + m_occupancy.set (coord_t (x, y), placement_idx); > + } > +} > + > +canvas > +table::to_canvas (const theme &theme, const style_manager &sm) const > +{ > + table_dimension_sizes col_widths (m_size.w); > + table_dimension_sizes row_heights (m_size.h); > + table_cell_sizes cell_sizes (col_widths, row_heights); > + cell_sizes.pass_1 (*this); > + cell_sizes.pass_2 (*this); > + table_geometry tg (*this, cell_sizes); > + canvas canvas (tg.get_canvas_size (), sm); > + paint_to_canvas (canvas, canvas::coord_t (0, 0), tg, theme); > + return canvas; > +} > + > +void > +table::paint_to_canvas (canvas &canvas, > + canvas::coord_t offset, > + const table_geometry &tg, > + const theme &theme) const > +{ > + canvas.fill (canvas::rect_t (offset, tg.get_canvas_size ()), > + styled_unichar (' ')); > + paint_cell_borders_to_canvas (canvas, offset, tg, theme); > + paint_cell_contents_to_canvas (canvas, offset, tg); > +} > + > +/* Print this table to stderr. */ > + > +DEBUG_FUNCTION void > +table::debug () const > +{ > + /* Use a temporary style manager. > + Styles in the table will be meaningless, so > + print the canvas with styling disabled. */ > + style_manager sm; > + canvas canvas (to_canvas (unicode_theme (), sm)); > + canvas.debug (false); > +} > + > +const table::cell_placement * > +table::get_placement_at (coord_t coord) const > +{ > + const int placement_idx = m_occupancy.get (coord); > + if (placement_idx == -1) > + return nullptr; > + return &m_placements[placement_idx]; > +} > + > +int > +table::get_occupancy_safe (coord_t coord) const > +{ > + if (coord.x < 0) > + return -1; > + if (coord.x >= m_size.w) > + return -1; > + if (coord.y < 0) > + return -1; > + if (coord.y >= m_size.h) > + return -1; > + return m_occupancy.get (coord); > +} > + > +/* Determine if the "?" edges need borders for table cell D > + in the following, for the directions relative to "X", based > + on whether each of table cell boundaries AB, CD, AC, and BD > + are boundaries between cell spans: > + > + # up? > + # +-----+-----+ > + # | | > + # | ? | > + # | A ? B | > + # | ? | > + # | | > + # left?+ ??? X ??? + right? > + # | | > + # | ? | > + # | C ? D | > + # | ? | > + # | | > + # +-----+-----+ > + # down? > +*/ > + > +directions > +table::get_connections (int table_x, int table_y) const > +{ > + int cell_a = get_occupancy_safe (coord_t (table_x - 1, table_y - 1)); > + int cell_b = get_occupancy_safe (coord_t (table_x, table_y - 1)); > + int cell_c = get_occupancy_safe (coord_t (table_x - 1, table_y)); > + int cell_d = get_occupancy_safe (coord_t (table_x, table_y)); > + const bool up = (cell_a != cell_b); > + const bool down = (cell_c != cell_d); > + const bool left = (cell_a != cell_c); > + const bool right = (cell_b != cell_d); > + return directions (up, down, left, right); > +} > + > +/* Paint the grid lines. > + > + Consider painting > + - a grid of cells, > + - plus a right-hand border > + - and a bottom border > + > + Then we need to paint to the canvas like this: > + > + # PER-TABLE-COLUMN R BORDER > + # +-------------------+ +-----+ > + # > + # TABLE CELL WIDTH (in canvas units) > + # +-------------+ > + # . . . . . . . > + # ...+-----+-----+.+-----+...+-----+ + > + # | U | |.| | | U | | > + # | U | |.| | | U | | > + # |LL+RR|RRRRR|.|RRRRR| |LL+ | | > + # | D | |.| | | D | | > + # | D | |.| | | D | | > + # ...+-----+-----+.+-----+...+-----+ | > + # ..................... ...... +-- PER-TABLE-ROW > + # ...+-----+-----+.+-----+...+-----+ | + > + # | D | |.| | | D | | | > + # | D | |.| | | D | | | > + # | D | |.| | | D | | +---- TABLE CELL HEIGHT (in canvas units) > + # | D | |.| | | D | | | > + # | D | |.| | | D | | | > + # ...+-----+-----+.+-----+...+-----+ + + > + # . . . . . . > + # ...+-----+-----+.+-----+...+-----+ + > + # | D | |.| | | U | | > + # | D | |.| | | U | | > + # |LL+RR|RRRRR|.|RRRRR| |LL+ | | BOTTOM BORDER > + # | | |.| | | | | > + # | | |.| | | | | > + # ...+-----+-----+.+-----+...+-----+ + > + > + where each: > + > + # +-----+ > + # | | > + # | | > + # | | > + # | | > + # | | > + # +-----+ > + > + is a canvas cell, and the U, L, R, D express the connections > + that are present with neighboring table cells. These affect > + the kinds of borders that we draw for a particular table cell. */ > + > +void > +table::paint_cell_borders_to_canvas (canvas &canvas, > + canvas::coord_t offset, > + const table_geometry &tg, > + const theme &theme) const > +{ > + /* The per-table-cell left and top borders are either drawn or not, > + but if they are, they aren't affected by per-table-cell connections. */ > + const canvas::cell_t left_border > + = theme.get_line_art (directions (true, /* up */ > + true, /* down */ > + false, /* left */ > + false /* right */)); > + const canvas::cell_t top_border > + = theme.get_line_art (directions (false, /* up */ > + false, /* down */ > + true, /* left */ > + true)); /* right */ > + for (int table_y = 0; table_y < m_size.h; table_y++) > + { > + const int canvas_y = tg.table_y_to_canvas_y (table_y); > + for (int table_x = 0; table_x < m_size.w; table_x++) > + { > + canvas::coord_t canvas_top_left > + = tg.table_to_canvas(table::coord_t (table_x, table_y)); > + > + const directions c (get_connections (table_x, table_y)); > + > + /* Paint top-left corner of border, if any. */ > + canvas.paint (offset + canvas_top_left, > + theme.get_line_art (c)); > + > + /* Paint remainder of left border of cell, if any. > + We assume here that the content occupies a single canvas row. */ > + if (c.m_down) > + canvas.paint (offset + canvas::coord_t (canvas_top_left.x, > + canvas_y + 1), > + left_border); > + > + /* Paint remainder of top border of cell, if any. */ > + if (c.m_right) > + { > + const int col_width = tg.get_col_width (table_x); > + for (int x_offset = 0; x_offset < col_width; x_offset++) > + { > + const int canvas_x = canvas_top_left.x + 1 + x_offset; > + canvas.paint (offset + canvas::coord_t (canvas_x, canvas_y), > + top_border); > + } > + } > + } > + > + /* Paint right-hand border of row. */ > + const int table_x = m_size.w; > + const int canvas_x = tg.table_x_to_canvas_x (table_x); > + const directions c (get_connections (m_size.w, table_y)); > + canvas.paint(offset + canvas::coord_t (canvas_x, canvas_y), > + theme.get_line_art (directions (c.m_up, > + c.m_down, > + c.m_left, > + false))); /* right */ > + /* We assume here that the content occupies a single canvas row. */ > + canvas.paint(offset + canvas::coord_t (canvas_x, canvas_y + 1), > + theme.get_line_art (directions (c.m_down, /* up */ > + c.m_down, /* down */ > + false, /* left */ > + false))); /* right */ > + } > + > + /* Draw bottom border of table. */ > + { > + const int canvas_y = tg.get_canvas_size ().h - 1; > + for (int table_x = 0; table_x < m_size.w; table_x++) > + { > + const directions c (get_connections (table_x, m_size.h)); > + const int left_canvas_x = tg.table_x_to_canvas_x (table_x); > + canvas.paint (offset + canvas::coord_t (left_canvas_x, canvas_y), > + theme.get_line_art (directions (c.m_up, > + false, /* down */ > + c.m_left, > + c.m_right))); > + const int col_width = tg.get_col_width (table_x); > + for (int x_offset = 0; x_offset < col_width; x_offset++) > + { > + const int canvas_x = left_canvas_x + 1 + x_offset; > + canvas.paint(offset + canvas::coord_t (canvas_x, canvas_y), > + theme.get_line_art (directions (false, // up > + false, // down > + c.m_right, // left > + c.m_right))); // right > + } > + } > + > + /* Bottom-right corner of table. */ > + const int table_x = m_size.w; > + const int canvas_x = tg.table_x_to_canvas_x (table_x); > + const directions c (get_connections (m_size.w, m_size.h)); > + canvas.paint (offset + canvas::coord_t (canvas_x, canvas_y), > + theme.get_line_art (directions (c.m_up, // up > + false, // down > + c.m_left, // left > + false))); // right > + } > +} > + > +void > +table::paint_cell_contents_to_canvas(canvas &canvas, > + canvas::coord_t offset, > + const table_geometry &tg) const > +{ > + for (auto &placement : m_placements) > + placement.paint_cell_contents_to_canvas (canvas, offset, tg); > +} > + > +/* class table_cell_sizes. */ > + > +/* Consider 1x1 cells. */ > + > +void > +table_cell_sizes::pass_1 (const table &table) > +{ > + for (auto &placement : table.m_placements) > + if (placement.one_by_one_p ()) > + { > + canvas::size_t canvas_size (placement.get_min_canvas_size ()); > + table::coord_t table_coord (placement.m_rect.m_top_left); > + m_col_widths.require (table_coord.x, canvas_size.w); > + m_row_heights.require (table_coord.y, canvas_size.h); > + } > +} > + > +/* Consider cells that span more than one row or column. */ > + > +void > +table_cell_sizes::pass_2 (const table &table) > +{ > + for (auto &placement : table.m_placements) > + if (!placement.one_by_one_p ()) > + { > + const canvas::size_t req_canvas_size (placement.get_min_canvas_size ()); > + const canvas::size_t current_canvas_size > + = get_canvas_size (placement.m_rect); > + /* Grow columns as necessary. */ > + if (req_canvas_size.w > current_canvas_size.w) > + { > + /* Spread the deficit amongst the columns. */ > + int deficit = req_canvas_size.w - current_canvas_size.w; > + const int per_col = deficit / placement.m_rect.m_size.w; > + for (int table_x = placement.m_rect.get_min_x (); > + table_x < placement.m_rect.get_next_x (); > + table_x++) > + { > + m_col_widths.m_requirements[table_x] += per_col; > + deficit -= per_col; > + } > + /* Make sure we allocate all of the deficit. */ > + if (deficit > 0) > + { > + const int table_x = placement.m_rect.get_max_x (); > + m_col_widths.m_requirements[table_x] += deficit; > + } > + } > + /* Grow rows as necessary. */ > + if (req_canvas_size.h > current_canvas_size.h) > + { > + /* Spread the deficit amongst the rows. */ > + int deficit = req_canvas_size.h - current_canvas_size.h; > + const int per_row = deficit / placement.m_rect.m_size.h; > + for (int table_y = placement.m_rect.get_min_y (); > + table_y < placement.m_rect.get_next_y (); > + table_y++) > + { > + m_row_heights.m_requirements[table_y] += per_row; > + deficit -= per_row; > + } > + /* Make sure we allocate all of the deficit. */ > + if (deficit > 0) > + { > + const int table_y = placement.m_rect.get_max_y (); > + m_row_heights.m_requirements[table_y] += deficit; > + } > + } > + } > +} > + > +canvas::size_t > +table_cell_sizes::get_canvas_size (const table::rect_t &rect) const > +{ > + canvas::size_t result (0, 0); > + for (int table_x = rect.get_min_x (); > + table_x < rect.get_next_x (); > + table_x ++) > + result.w += m_col_widths.m_requirements[table_x]; > + for (int table_y = rect.get_min_y (); > + table_y < rect.get_next_y (); > + table_y ++) > + result.h += m_row_heights.m_requirements[table_y]; > + /* Allow space for the borders. */ > + result.w += rect.m_size.w - 1; > + result.h += rect.m_size.h - 1; > + return result; > +} > + > +/* class text_art::table_geometry. */ > + > +table_geometry::table_geometry (const table &table, table_cell_sizes &cell_sizes) > +: m_table (table), > + m_cell_sizes (cell_sizes), > + m_canvas_size (canvas::size_t (0, 0)), > + m_col_start_x (table.get_size ().w), > + m_row_start_y (table.get_size ().h) > +{ > + recalc_coords (); > +} > + > +void > +table_geometry::recalc_coords () > +{ > + /* Start canvas column of table cell, including leading border. */ > + m_col_start_x.clear (); > + int iter_canvas_x = 0; > + for (auto w : m_cell_sizes.m_col_widths.m_requirements) > + { > + m_col_start_x.push_back (iter_canvas_x); > + iter_canvas_x += w + 1; > + } > + > + /* Start canvas row of table cell, including leading border. */ > + m_row_start_y.clear (); > + int iter_canvas_y = 0; > + for (auto h : m_cell_sizes.m_row_heights.m_requirements) > + { > + m_row_start_y.push_back (iter_canvas_y); > + iter_canvas_y += h + 1; > + } > + > + m_canvas_size = canvas::size_t (iter_canvas_x + 1, > + iter_canvas_y + 1); > +} > + > +/* Get the TL corner of the table cell at TABLE_COORD > + in canvas coords (including the border). */ > + > +canvas::coord_t > +table_geometry::table_to_canvas (table::coord_t table_coord) const > +{ > + return canvas::coord_t (table_x_to_canvas_x (table_coord.x), > + table_y_to_canvas_y (table_coord.y)); > +} > + > +/* Get the left border of the table cell at column TABLE_X > + in canvas coords (including the border). */ > + > +int > +table_geometry::table_x_to_canvas_x (int table_x) const > +{ > + /* Allow one beyond the end, for the right-hand border of the table. */ > + if (table_x == m_col_start_x.size ()) > + return m_canvas_size.w - 1; > + return m_col_start_x[table_x]; > +} > + > +/* Get the top border of the table cell at column TABLE_Y > + in canvas coords (including the border). */ > + > +int > +table_geometry::table_y_to_canvas_y (int table_y) const > +{ > + /* Allow one beyond the end, for the right-hand border of the table. */ > + if (table_y == m_row_start_y.size ()) > + return m_canvas_size.h - 1; > + return m_row_start_y[table_y]; > +} > + > +/* class text_art::simple_table_geometry. */ > + > +simple_table_geometry::simple_table_geometry (const table &table) > +: m_col_widths (table.get_size ().w), > + m_row_heights (table.get_size ().h), > + m_cell_sizes (m_col_widths, m_row_heights), > + m_tg (table, m_cell_sizes) > +{ > + m_cell_sizes.pass_1 (table); > + m_cell_sizes.pass_2 (table); > + m_tg.recalc_coords (); > +} > + > +#if CHECKING_P > + > +namespace selftest { > + > +static void > +test_tic_tac_toe () > +{ > + style_manager sm; > + table t (table::size_t (3, 3)); > + t.set_cell (table::coord_t (0, 0), styled_string (sm, "X")); > + t.set_cell (table::coord_t (1, 0), styled_string (sm, "")); > + t.set_cell (table::coord_t (2, 0), styled_string (sm, "")); > + t.set_cell (table::coord_t (0, 1), styled_string (sm, "O")); > + t.set_cell (table::coord_t (1, 1), styled_string (sm, "O")); > + t.set_cell (table::coord_t (2, 1), styled_string (sm, "")); > + t.set_cell (table::coord_t (0, 2), styled_string (sm, "X")); > + t.set_cell (table::coord_t (1, 2), styled_string (sm, "")); > + t.set_cell (table::coord_t (2, 2), styled_string (sm, "O")); > + > + { > + canvas canvas (t.to_canvas (ascii_theme (), sm)); > + ASSERT_CANVAS_STREQ > + (canvas, false, > + ("+-+-+-+\n" > + "|X| | |\n" > + "+-+-+-+\n" > + "|O|O| |\n" > + "+-+-+-+\n" > + "|X| |O|\n" > + "+-+-+-+\n")); > + } > + > + { > + canvas canvas (t.to_canvas (unicode_theme (), sm)); > + ASSERT_CANVAS_STREQ > + (canvas, false, > + // FIXME: are we allowed unicode chars in string literals in our source? > + ("┌─┬─┬─┐\n" > + "│X│ │ │\n" > + "├─┼─┼─┤\n" > + "│O│O│ │\n" > + "├─┼─┼─┤\n" > + "│X│ │O│\n" > + "└─┴─┴─┘\n")); > + } > +} > + > +static table > +make_text_table () > +{ > + style_manager sm; > + table t (table::size_t (3, 3)); > + t.set_cell (table::coord_t (0, 0), styled_string (sm, "top left")); > + t.set_cell (table::coord_t (1, 0), styled_string (sm, "top middle")); > + t.set_cell (table::coord_t (2, 0), styled_string (sm, "top right")); > + t.set_cell (table::coord_t (0, 1), styled_string (sm, "middle left")); > + t.set_cell (table::coord_t (1, 1), styled_string (sm, "middle middle")); > + t.set_cell (table::coord_t (2, 1), styled_string (sm, "middle right")); > + t.set_cell (table::coord_t (0, 2), styled_string (sm, "bottom left")); > + t.set_cell (table::coord_t (1, 2), styled_string (sm, "bottom middle")); > + t.set_cell (table::coord_t (2, 2), styled_string (sm, "bottom right")); > + return t; > +} > + > +static void > +test_text_table () > +{ > + style_manager sm; > + table table = make_text_table (); > + { > + canvas canvas (table.to_canvas (ascii_theme(), sm)); > + ASSERT_CANVAS_STREQ > + (canvas, false, > + ("+-----------+-------------+------------+\n" > + "| top left | top middle | top right |\n" > + "+-----------+-------------+------------+\n" > + "|middle left|middle middle|middle right|\n" > + "+-----------+-------------+------------+\n" > + "|bottom left|bottom middle|bottom right|\n" > + "+-----------+-------------+------------+\n")); > + } > + { > + canvas canvas (table.to_canvas (unicode_theme(), sm)); > + ASSERT_CANVAS_STREQ > + (canvas, false, > + // FIXME: are we allowed unicode chars in string literals in our source? > + ("┌───────────┬─────────────┬────────────┐\n" > + "│ top left │ top middle │ top right │\n" > + "├───────────┼─────────────┼────────────┤\n" > + "│middle left│middle middle│middle right│\n" > + "├───────────┼─────────────┼────────────┤\n" > + "│bottom left│bottom middle│bottom right│\n" > + "└───────────┴─────────────┴────────────┘\n")); > + } > +} > + > +static void > +test_offset_table () > +{ > + style_manager sm; > + table table = make_text_table (); > + simple_table_geometry stg (table); > + const canvas::size_t tcs = stg.m_tg.get_canvas_size(); > + { > + canvas canvas (canvas::size_t (tcs.w + 5, tcs.h + 5), sm); > + canvas.debug_fill (); > + table.paint_to_canvas (canvas, canvas::coord_t (3, 3), > + stg.m_tg, > + ascii_theme()); > + ASSERT_CANVAS_STREQ > + (canvas, false, > + ("*********************************************\n" > + "*********************************************\n" > + "*********************************************\n" > + "***+-----------+-------------+------------+**\n" > + "***| top left | top middle | top right |**\n" > + "***+-----------+-------------+------------+**\n" > + "***|middle left|middle middle|middle right|**\n" > + "***+-----------+-------------+------------+**\n" > + "***|bottom left|bottom middle|bottom right|**\n" > + "***+-----------+-------------+------------+**\n" > + "*********************************************\n" > + "*********************************************\n")); > + } > + { > + canvas canvas (canvas::size_t (tcs.w + 5, tcs.h + 5), sm); > + canvas.debug_fill (); > + table.paint_to_canvas (canvas, canvas::coord_t (3, 3), > + stg.m_tg, > + unicode_theme()); > + ASSERT_CANVAS_STREQ > + (canvas, false, > + // FIXME: are we allowed unicode chars in string literals in our source? > + ("*********************************************\n" > + "*********************************************\n" > + "*********************************************\n" > + "***┌───────────┬─────────────┬────────────┐**\n" > + "***│ top left │ top middle │ top right │**\n" > + "***├───────────┼─────────────┼────────────┤**\n" > + "***│middle left│middle middle│middle right│**\n" > + "***├───────────┼─────────────┼────────────┤**\n" > + "***│bottom left│bottom middle│bottom right│**\n" > + "***└───────────┴─────────────┴────────────┘**\n" > + "*********************************************\n" > + "*********************************************\n")); > + } > +} > + > +#define ASSERT_TABLE_CELL_STREQ(TABLE, TABLE_X, TABLE_Y, EXPECTED_STR) \ > + SELFTEST_BEGIN_STMT \ > + table::coord_t coord ((TABLE_X), (TABLE_Y)); \ > + const table::cell_placement *cp = (TABLE).get_placement_at (coord); \ > + ASSERT_NE (cp, nullptr); \ > + ASSERT_EQ (cp->get_content (), styled_string (sm, EXPECTED_STR)); \ > + SELFTEST_END_STMT > + > +#define ASSERT_TABLE_NULL_CELL(TABLE, TABLE_X, TABLE_Y) \ > + SELFTEST_BEGIN_STMT \ > + table::coord_t coord ((TABLE_X), (TABLE_Y)); \ > + const table::cell_placement *cp = (TABLE).get_placement_at (coord); \ > + ASSERT_EQ (cp, nullptr); \ > + SELFTEST_END_STMT > + > +static void > +test_spans () > +{ > + style_manager sm; > + table table (table::size_t (3, 3)); > + table.set_cell_span (table::rect_t (table::coord_t (0, 0), > + table::size_t (3, 1)), > + styled_string (sm, "ABC")); > + table.set_cell_span (table::rect_t (table::coord_t (0, 1), > + table::size_t (2, 1)), > + styled_string (sm, "DE")); > + table.set_cell_span (table::rect_t (table::coord_t (2, 1), > + table::size_t (1, 1)), > + styled_string (sm, "F")); > + table.set_cell (table::coord_t (0, 2), styled_string (sm, "G")); > + table.set_cell (table::coord_t (1, 2), styled_string (sm, "H")); > + table.set_cell (table::coord_t (2, 2), styled_string (sm, "I")); > + { > + canvas canvas (table.to_canvas (ascii_theme(), sm)); > + ASSERT_CANVAS_STREQ > + (canvas, false, > + ("+-----+\n" > + "| ABC |\n" > + "+---+-+\n" > + "|DE |F|\n" > + "+-+-+-+\n" > + "|G|H|I|\n" > + "+-+-+-+\n")); > + } > + { > + canvas canvas (table.to_canvas (unicode_theme(), sm)); > + ASSERT_CANVAS_STREQ > + (canvas, false, > + // FIXME: are we allowed unicode chars in string literals in our source? > + ("┌─────┐\n" > + "│ ABC │\n" > + "├───┬─┤\n" > + "│DE │F│\n" > + "├─┬─┼─┤\n" > + "│G│H│I│\n" > + "└─┴─┴─┘\n")); > + } > +} > + > +/* Verify building this 5x5 table with spans: > + |0|1|2|3|4| > + +-+-+-+-+-+ > + 0|A A A|B|C|0 > + + +-+ + > + 1|A A A|D|C|1 > + + +-+-+ > + 2|A A A|E|F|2 > + +-+-+-+-+-+ > + 3|G G|H|I I|3 > + | | +-+-+ > + 4|G G|H|J J|4 > + +-+-+-+-+-+ > + |0|1|2|3|4| > +*/ > + > +static void > +test_spans_2 () > +{ > + style_manager sm; > + table table (table::size_t (5, 5)); > + table.set_cell_span (table::rect_t (table::coord_t (0, 0), > + table::size_t (3, 3)), > + styled_string (sm, "A")); > + table.set_cell_span (table::rect_t (table::coord_t (3, 0), > + table::size_t (1, 1)), > + styled_string (sm, "B")); > + table.set_cell_span (table::rect_t (table::coord_t (4, 0), > + table::size_t (1, 2)), > + styled_string (sm, "C")); > + table.set_cell_span (table::rect_t (table::coord_t (3, 1), > + table::size_t (1, 1)), > + styled_string (sm, "D")); > + table.set_cell_span (table::rect_t (table::coord_t (3, 2), > + table::size_t (1, 1)), > + styled_string (sm, "E")); > + table.set_cell_span (table::rect_t (table::coord_t (4, 2), > + table::size_t (1, 1)), > + styled_string (sm, "F")); > + table.set_cell_span (table::rect_t (table::coord_t (0, 3), > + table::size_t (2, 2)), > + styled_string (sm, "G")); > + table.set_cell_span (table::rect_t (table::coord_t (2, 3), > + table::size_t (1, 2)), > + styled_string (sm, "H")); > + table.set_cell_span (table::rect_t (table::coord_t (3, 3), > + table::size_t (2, 1)), > + styled_string (sm, "I")); > + table.set_cell_span (table::rect_t (table::coord_t (3, 4), > + table::size_t (2, 1)), > + styled_string (sm, "J")); > + > + /* Check occupancy at each table coordinate. */ > + ASSERT_TABLE_CELL_STREQ (table, 0, 0, "A"); > + ASSERT_TABLE_CELL_STREQ (table, 1, 0, "A"); > + ASSERT_TABLE_CELL_STREQ (table, 2, 0, "A"); > + ASSERT_TABLE_CELL_STREQ (table, 3, 0, "B"); > + ASSERT_TABLE_CELL_STREQ (table, 4, 0, "C"); > + > + ASSERT_TABLE_CELL_STREQ (table, 0, 1, "A"); > + ASSERT_TABLE_CELL_STREQ (table, 1, 1, "A"); > + ASSERT_TABLE_CELL_STREQ (table, 2, 1, "A"); > + ASSERT_TABLE_CELL_STREQ (table, 3, 1, "D"); > + ASSERT_TABLE_CELL_STREQ (table, 4, 1, "C"); > + > + ASSERT_TABLE_CELL_STREQ (table, 0, 2, "A"); > + ASSERT_TABLE_CELL_STREQ (table, 1, 2, "A"); > + ASSERT_TABLE_CELL_STREQ (table, 2, 2, "A"); > + ASSERT_TABLE_CELL_STREQ (table, 3, 2, "E"); > + ASSERT_TABLE_CELL_STREQ (table, 4, 2, "F"); > + > + ASSERT_TABLE_CELL_STREQ (table, 0, 3, "G"); > + ASSERT_TABLE_CELL_STREQ (table, 1, 3, "G"); > + ASSERT_TABLE_CELL_STREQ (table, 2, 3, "H"); > + ASSERT_TABLE_CELL_STREQ (table, 3, 3, "I"); > + ASSERT_TABLE_CELL_STREQ (table, 4, 3, "I"); > + > + ASSERT_TABLE_CELL_STREQ (table, 0, 4, "G"); > + ASSERT_TABLE_CELL_STREQ (table, 1, 4, "G"); > + ASSERT_TABLE_CELL_STREQ (table, 2, 4, "H"); > + ASSERT_TABLE_CELL_STREQ (table, 3, 4, "J"); > + ASSERT_TABLE_CELL_STREQ (table, 4, 4, "J"); > + > + { > + canvas canvas (table.to_canvas (ascii_theme(), sm)); > + ASSERT_CANVAS_STREQ > + (canvas, false, > + ("+---+-+-+\n" > + "| |B| |\n" > + "| +-+C|\n" > + "| A |D| |\n" > + "| +-+-+\n" > + "| |E|F|\n" > + "+-+-+-+-+\n" > + "| | | I |\n" > + "|G|H+---+\n" > + "| | | J |\n" > + "+-+-+---+\n")); > + } > + { > + canvas canvas (table.to_canvas (unicode_theme(), sm)); > + ASSERT_CANVAS_STREQ > + (canvas, false, > + // FIXME: are we allowed unicode chars in string literals in our source? > + ("┌───┬─┬─┐\n" > + "│ │B│ │\n" > + "│ ├─┤C│\n" > + "│ A │D│ │\n" > + "│ ├─┼─┤\n" > + "│ │E│F│\n" > + "├─┬─┼─┴─┤\n" > + "│ │ │ I │\n" > + "│G│H├───┤\n" > + "│ │ │ J │\n" > + "└─┴─┴───┘\n")); > + } > +} > + > +/* Experiment with adding a 1-table-column gap at the boundary between > + valid vs invalid for visualizing a buffer overflow. */ > +static void > +test_spans_3 () > +{ > + const char * const str = "hello world!"; > + const size_t buf_size = 10; > + const size_t str_size = strlen (str) + 1; > + > + style_manager sm; > + table table (table::size_t (str_size + 1, 3)); > + > + table.set_cell_span (table::rect_t (table::coord_t (0, 0), > + table::size_t (str_size + 1, 1)), > + styled_string (sm, "String literal")); > + > + for (size_t i = 0; i < str_size; i++) > + { > + table::coord_t c (i, 1); > + if (i >= buf_size) > + c.x++; > + if (str[i] == '\0') > + table.set_cell (c, styled_string (sm, "NUL")); > + else > + table.set_cell (c, styled_string ((cppchar_t)str[i])); > + } > + > + table.set_cell_span (table::rect_t (table::coord_t (0, 2), > + table::size_t (buf_size, 1)), > + styled_string::from_fmt (sm, > + nullptr, > + "'buf' (char[%i])", > + (int)buf_size)); > + table.set_cell_span (table::rect_t (table::coord_t (buf_size + 1, 2), > + table::size_t (str_size - buf_size, 1)), > + styled_string (sm, "overflow")); > + > + { > + canvas canvas (table.to_canvas (ascii_theme (), sm)); > + ASSERT_CANVAS_STREQ > + (canvas, false, > + "+-----------------------------+\n" > + "| String literal |\n" > + "+-+-+-+-+-+-+-+-+-+-++-+-+----+\n" > + "|h|e|l|l|o| |w|o|r|l||d|!|NUL |\n" > + "+-+-+-+-+-+-+-+-+-+-++-+-+----+\n" > + "| 'buf' (char[10]) ||overflow|\n" > + "+-------------------++--------+\n"); > + } > + { > + canvas canvas (table.to_canvas (unicode_theme (), sm)); > + ASSERT_CANVAS_STREQ > + (canvas, false, > + // FIXME: are we allowed unicode chars in string literals in our source? > + ("┌─────────────────────────────┐\n" > + "│ String literal │\n" > + "├─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬┬─┬─┬────┤\n" > + "│h│e│l│l│o│ │w│o│r│l││d│!│NUL │\n" > + "├─┴─┴─┴─┴─┴─┴─┴─┴─┴─┤├─┴─┴────┤\n" > + "│ 'buf' (char[10]) ││overflow│\n" > + "└───────────────────┘└────────┘\n")); > + } > +} > + > +static void > +test_double_width_chars () > +{ > + table_cell_content tcc (styled_string ((cppchar_t)0x1f642)); > + ASSERT_EQ (tcc.get_canvas_size ().w, 2); > + ASSERT_EQ (tcc.get_canvas_size ().h, 1); > + > + style_manager sm; > + table table (table::size_t (1, 1)); > + table.set_cell (table::coord_t (0,0), > + styled_string ((cppchar_t)0x1f642)); > + > + canvas canvas (table.to_canvas (unicode_theme(), sm)); > + ASSERT_CANVAS_STREQ > + (canvas, false, > + // FIXME: are we allowed unicode chars in string literals in our source? > + ("┌──┐\n" > + "│🙂│\n" > + "└──┘\n")); > +} > + > +static void > +test_ipv4_header () > +{ > + style_manager sm; > + table table (table::size_t (34, 10)); > + table.set_cell (table::coord_t (0, 0), styled_string (sm, "Offsets")); > + table.set_cell (table::coord_t (1, 0), styled_string (sm, "Octet")); > + table.set_cell (table::coord_t (0, 1), styled_string (sm, "Octet")); > + for (int octet = 0; octet < 4; octet++) > + table.set_cell_span (table::rect_t (table::coord_t (2 + (octet * 8), 0), > + table::size_t (8, 1)), > + styled_string::from_fmt (sm, nullptr, "%i", octet)); > + table.set_cell (table::coord_t (1, 1), styled_string (sm, "Bit")); > + for (int bit = 0; bit < 32; bit++) > + table.set_cell (table::coord_t (bit + 2, 1), > + styled_string::from_fmt (sm, nullptr, "%i", bit)); > + for (int word = 0; word < 6; word++) > + { > + table.set_cell (table::coord_t (0, word + 2), > + styled_string::from_fmt (sm, nullptr, "%i", word * 4)); > + table.set_cell (table::coord_t (1, word + 2), > + styled_string::from_fmt (sm, nullptr, "%i", word * 32)); > + } > + > + table.set_cell (table::coord_t (0, 8), styled_string (sm, "...")); > + table.set_cell (table::coord_t (1, 8), styled_string (sm, "...")); > + table.set_cell (table::coord_t (0, 9), styled_string (sm, "56")); > + table.set_cell (table::coord_t (1, 9), styled_string (sm, "448")); > + > +#define SET_BITS(FIRST, LAST, NAME) \ > + do { \ > + const int first = (FIRST); \ > + const int last = (LAST); \ > + const char *name = (NAME); \ > + const int row = first / 32; \ > + gcc_assert (last / 32 == row); \ > + table::rect_t rect (table::coord_t ((first % 32) + 2, row + 2), \ > + table::size_t (last + 1 - first , 1)); \ > + table.set_cell_span (rect, styled_string (sm, name)); \ > + } while (0) > + > + SET_BITS (0, 3, "Version"); > + SET_BITS (4, 7, "IHL"); > + SET_BITS (8, 13, "DSCP"); > + SET_BITS (14, 15, "ECN"); > + SET_BITS (16, 31, "Total Length"); > + > + SET_BITS (32 + 0, 32 + 15, "Identification"); > + SET_BITS (32 + 16, 32 + 18, "Flags"); > + SET_BITS (32 + 19, 32 + 31, "Fragment Offset"); > + > + SET_BITS (64 + 0, 64 + 7, "Time To Live"); > + SET_BITS (64 + 8, 64 + 15, "Protocol"); > + SET_BITS (64 + 16, 64 + 31, "Header Checksum"); > + > + SET_BITS (96 + 0, 96 + 31, "Source IP Address"); > + SET_BITS (128 + 0, 128 + 31, "Destination IP Address"); > + > + table.set_cell_span(table::rect_t (table::coord_t (2, 7), > + table::size_t (32, 3)), > + styled_string (sm, "Options")); > + { > + canvas canvas (table.to_canvas (ascii_theme(), sm)); > + ASSERT_CANVAS_STREQ > + (canvas, false, > + ("+-------+-----+---------------+---------------------+-----------------------+-----------------------+\n" > + "|Offsets|Octet| 0 | 1 | 2 | 3 |\n" > + "+-------+-----+-+-+-+-+-+-+-+-+-+-+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+\n" > + "| Octet | Bit |0|1|2|3|4|5|6|7|8|9|10|11|12|13|14|15|16|17|18|19|20|21|22|23|24|25|26|27|28|29|30|31|\n" > + "+-------+-----+-+-+-+-+-+-+-+-+-+-+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+\n" > + "| 0 | 0 |Version| IHL | DSCP | ECN | Total Length |\n" > + "+-------+-----+-------+-------+---------------+-----+--------+--------------------------------------+\n" > + "| 4 | 32 | Identification | Flags | Fragment Offset |\n" > + "+-------+-----+---------------+---------------------+--------+--------------------------------------+\n" > + "| 8 | 64 | Time To Live | Protocol | Header Checksum |\n" > + "+-------+-----+---------------+---------------------+-----------------------------------------------+\n" > + "| 12 | 96 | Source IP Address |\n" > + "+-------+-----+-------------------------------------------------------------------------------------+\n" > + "| 16 | 128 | Destination IP Address |\n" > + "+-------+-----+-------------------------------------------------------------------------------------+\n" > + "| 20 | 160 | |\n" > + "+-------+-----+ |\n" > + "| ... | ... | Options |\n" > + "+-------+-----+ |\n" > + "| 56 | 448 | |\n" > + "+-------+-----+-------------------------------------------------------------------------------------+\n")); > + } > + { > + canvas canvas (table.to_canvas (unicode_theme(), sm)); > + ASSERT_CANVAS_STREQ > + (canvas, false, > + // FIXME: are we allowed unicode chars in string literals in our source? > + ("┌───────┬─────┬───────────────┬─────────────────────┬───────────────────────┬───────────────────────┐\n" > + "│Offsets│Octet│ 0 │ 1 │ 2 │ 3 │\n" > + "├───────┼─────┼─┬─┬─┬─┬─┬─┬─┬─┼─┬─┬──┬──┬──┬──┬──┬──┼──┬──┬──┬──┬──┬──┬──┬──┼──┬──┬──┬──┬──┬──┬──┬──┤\n" > + "│ Octet │ Bit │0│1│2│3│4│5│6│7│8│9│10│11│12│13│14│15│16│17│18│19│20│21│22│23│24│25│26│27│28│29│30│31│\n" > + "├───────┼─────┼─┴─┴─┴─┼─┴─┴─┴─┼─┴─┴──┴──┴──┴──┼──┴──┼──┴──┴──┴──┴──┴──┴──┴──┴──┴──┴──┴──┴──┴──┴──┴──┤\n" > + "│ 0 │ 0 │Version│ IHL │ DSCP │ ECN │ Total Length │\n" > + "├───────┼─────┼───────┴───────┴───────────────┴─────┼────────┬──────────────────────────────────────┤\n" > + "│ 4 │ 32 │ Identification │ Flags │ Fragment Offset │\n" > + "├───────┼─────┼───────────────┬─────────────────────┼────────┴──────────────────────────────────────┤\n" > + "│ 8 │ 64 │ Time To Live │ Protocol │ Header Checksum │\n" > + "├───────┼─────┼───────────────┴─────────────────────┴───────────────────────────────────────────────┤\n" > + "│ 12 │ 96 │ Source IP Address │\n" > + "├───────┼─────┼─────────────────────────────────────────────────────────────────────────────────────┤\n" > + "│ 16 │ 128 │ Destination IP Address │\n" > + "├───────┼─────┼─────────────────────────────────────────────────────────────────────────────────────┤\n" > + "│ 20 │ 160 │ │\n" > + "├───────┼─────┤ │\n" > + "│ ... │ ... │ Options │\n" > + "├───────┼─────┤ │\n" > + "│ 56 │ 448 │ │\n" > + "└───────┴─────┴─────────────────────────────────────────────────────────────────────────────────────┘\n")); > + } > +} > + > +static void > +test_missing_cells () > +{ > + style_manager sm; > + table table (table::size_t (3, 3)); > + table.set_cell (table::coord_t (1, 0), styled_string (sm, "A")); > + table.set_cell (table::coord_t (0, 1), styled_string (sm, "B")); > + table.set_cell (table::coord_t (1, 1), styled_string (sm, "C")); > + table.set_cell (table::coord_t (2, 1), styled_string (sm, "D")); > + table.set_cell (table::coord_t (1, 2), styled_string (sm, "E")); > + > + ASSERT_TABLE_NULL_CELL (table, 0, 0); > + ASSERT_TABLE_CELL_STREQ (table, 1, 0, "A"); > + ASSERT_TABLE_NULL_CELL (table, 2, 0); > + > + ASSERT_TABLE_CELL_STREQ (table, 0, 1, "B"); > + ASSERT_TABLE_CELL_STREQ (table, 1, 1, "C"); > + ASSERT_TABLE_CELL_STREQ (table, 2, 1, "D"); > + > + ASSERT_TABLE_NULL_CELL (table, 0, 2); > + ASSERT_TABLE_CELL_STREQ (table, 1, 2, "E"); > + ASSERT_TABLE_NULL_CELL (table, 2, 2); > + > + { > + canvas canvas (table.to_canvas (ascii_theme(), sm)); > + ASSERT_CANVAS_STREQ > + (canvas, false, > + (" +-+\n" > + " |A|\n" > + "+-+-+-+\n" > + "|B|C|D|\n" > + "+-+-+-+\n" > + " |E|\n" > + " +-+\n")); > + } > + { > + canvas canvas (table.to_canvas (unicode_theme(), sm)); > + ASSERT_CANVAS_STREQ > + (canvas, false, > + (" ┌─┐\n" > + " │A│\n" > + "┌─┼─┼─┐\n" > + "│B│C│D│\n" > + "└─┼─┼─┘\n" > + " │E│\n" > + " └─┘\n")); > + } > +} > + > +static void > +test_add_row () > +{ > + style_manager sm; > + table table (table::size_t (3, 0)); > + for (int i = 0; i < 5; i++) > + { > + const int y = table.add_row (); > + for (int x = 0; x < 3; x++) > + table.set_cell (table::coord_t (x, y), > + styled_string::from_fmt (sm, nullptr, > + "%i, %i", x, y)); > + } > + canvas canvas (table.to_canvas (ascii_theme(), sm)); > + ASSERT_CANVAS_STREQ > + (canvas, false, > + ("+----+----+----+\n" > + "|0, 0|1, 0|2, 0|\n" > + "+----+----+----+\n" > + "|0, 1|1, 1|2, 1|\n" > + "+----+----+----+\n" > + "|0, 2|1, 2|2, 2|\n" > + "+----+----+----+\n" > + "|0, 3|1, 3|2, 3|\n" > + "+----+----+----+\n" > + "|0, 4|1, 4|2, 4|\n" > + "+----+----+----+\n")); > +} > + > +static void > +test_alignment () > +{ > + style_manager sm; > + table table (table::size_t (9, 9)); > + table.set_cell_span (table::rect_t (table::coord_t (0, 0), > + table::size_t (3, 3)), > + styled_string (sm, "left top"), > + x_align::LEFT, y_align::TOP); > + table.set_cell_span (table::rect_t (table::coord_t (3, 0), > + table::size_t (3, 3)), > + styled_string (sm, "center top"), > + x_align::CENTER, y_align::TOP); > + table.set_cell_span (table::rect_t (table::coord_t (6, 0), > + table::size_t (3, 3)), > + styled_string (sm, "right top"), > + x_align::RIGHT, y_align::TOP); > + table.set_cell_span (table::rect_t (table::coord_t (0, 3), > + table::size_t (3, 3)), > + styled_string (sm, "left center"), > + x_align::LEFT, y_align::CENTER); > + table.set_cell_span (table::rect_t (table::coord_t (3, 3), > + table::size_t (3, 3)), > + styled_string (sm, "center center"), > + x_align::CENTER, y_align::CENTER); > + table.set_cell_span (table::rect_t (table::coord_t (6, 3), > + table::size_t (3, 3)), > + styled_string (sm, "right center"), > + x_align::RIGHT, y_align::CENTER); > + table.set_cell_span (table::rect_t (table::coord_t (0, 6), > + table::size_t (3, 3)), > + styled_string (sm, "left bottom"), > + x_align::LEFT, y_align::BOTTOM); > + table.set_cell_span (table::rect_t (table::coord_t (3, 6), > + table::size_t (3, 3)), > + styled_string (sm, "center bottom"), > + x_align::CENTER, y_align::BOTTOM); > + table.set_cell_span (table::rect_t (table::coord_t (6, 6), > + table::size_t (3, 3)), > + styled_string (sm, "right bottom"), > + x_align::RIGHT, y_align::BOTTOM); > + > + canvas canvas (table.to_canvas (ascii_theme(), sm)); > + ASSERT_CANVAS_STREQ > + (canvas, false, > + ("+-----------+-------------+------------+\n" > + "|left top | center top | right top|\n" > + "| | | |\n" > + "+-----------+-------------+------------+\n" > + "|left center|center center|right center|\n" > + "| | | |\n" > + "+-----------+-------------+------------+\n" > + "| | | |\n" > + "|left bottom|center bottom|right bottom|\n" > + "+-----------+-------------+------------+\n")); > +} > + > +/* Run all selftests in this file. */ > + > +void > +text_art_table_cc_tests () > +{ > + test_tic_tac_toe (); > + test_text_table (); > + test_offset_table (); > + test_spans (); > + test_spans_2 (); > + test_spans_3 (); > + test_double_width_chars (); > + test_ipv4_header (); > + test_missing_cells (); > + test_add_row (); > + test_alignment (); > +} > + > +} // namespace selftest > + > + > +#endif /* #if CHECKING_P */ > diff --git a/gcc/text-art/table.h b/gcc/text-art/table.h > new file mode 100644 > index 00000000000..5e6c8ffb836 > --- /dev/null > +++ b/gcc/text-art/table.h > @@ -0,0 +1,262 @@ > +/* Support for tabular/grid-based content. > + Copyright (C) 2023 Free Software Foundation, Inc. > + Contributed by David Malcolm <dmalcolm@redhat.com>. > + > +This file is part of GCC. > + > +GCC is free software; you can redistribute it and/or modify it > +under the terms of the GNU General Public License as published by > +the Free Software Foundation; either version 3, or (at your option) > +any later version. > + > +GCC is distributed in the hope that it will be useful, but > +WITHOUT ANY WARRANTY; without even the implied warranty of > +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU > +General Public License for more details. > + > +You should have received a copy of the GNU General Public License > +along with GCC; see the file COPYING3. If not see > +<http://www.gnu.org/licenses/>. */ > + > +#ifndef GCC_TEXT_ART_TABLE_H > +#define GCC_TEXT_ART_TABLE_H > + > +#include "text-art/canvas.h" > +#include "text-art/theme.h" > +#include <vector> > + > +namespace text_art { > + > +class table; > +class table_geometry; > + > +/* A class representing the content of a particular table cell, > + or of a span of table cells. */ > + > +class table_cell_content > +{ > + public: > + table_cell_content () : m_str (), m_size (0, 0) {} > + table_cell_content (styled_string &&s); > + > + bool operator== (const table_cell_content &other) const > + { > + return m_str == other.m_str; > + } > + > + canvas::size_t get_canvas_size () const { return m_size; } > + > + void paint_to_canvas (canvas &canvas, > + canvas::coord_t top_left) const; > + > + private: > + styled_string m_str; > + canvas::size_t m_size; > +}; > + > +/* A list of required sizes of table rows or columns > + in canvas units (row heights or column widths). */ > + > +struct table_dimension_sizes > +{ > + table_dimension_sizes (unsigned num); > + > + void require (unsigned idx, int amount) > + { > + m_requirements[idx] = std::max (m_requirements[idx], amount); > + } > + > + std::vector<int> m_requirements; > +}; > + > +/* A 2D grid of cells. Instances of table_cell_content can be assigned > + to individual table cells, and to rectangular spans of cells. Such > + assignments do not have to fully cover the 2D grid, but they must not > + overlap. */ > + > +class table > +{ > + public: > + typedef size<class table> size_t; > + typedef coord<class table> coord_t; > + typedef range<class table> range_t; > + typedef rect<class table> rect_t; > + > + /* A record of how a table_cell_content was placed at a table::rect_t > + with a certain alignment. */ > + class cell_placement > + { > + public: > + cell_placement (rect_t rect, > + table_cell_content &&content, > + x_align x_align, > + y_align y_align) > + : m_rect (rect), > + m_content (std::move (content)), > + m_x_align (x_align), > + m_y_align (y_align) > + { > + } > + > + bool one_by_one_p () const > + { > + return m_rect.m_size.w == 1 && m_rect.m_size.h == 1; > + } > + > + canvas::size_t get_min_canvas_size () const > + { > + // Doesn't include border > + return m_content.get_canvas_size (); > + } > + > + void paint_cell_contents_to_canvas(canvas &canvas, > + canvas::coord_t offset, > + const table_geometry &tg) const; > + > + const table_cell_content &get_content () const { return m_content; } > + > + private: > + friend class table_cell_sizes; > + rect_t m_rect; > + table_cell_content m_content; > + x_align m_x_align; > + y_align m_y_align; > + }; > + > + table (size_t size); > + ~table () = default; > + table (table &&) = default; > + table (const table &) = delete; > + table &operator= (const table &) = delete; > + > + const size_t &get_size () const { return m_size; } > + > + int add_row () > + { > + m_size.h++; > + m_occupancy.add_row (-1); > + return m_size.h - 1; // return the table_y of the newly-added row > + } > + > + void set_cell (coord_t coord, > + table_cell_content &&content, > + enum x_align x_align = x_align::CENTER, > + enum y_align y_align = y_align::CENTER); > + > + void set_cell_span (rect_t span, > + table_cell_content &&content, > + enum x_align x_align = x_align::CENTER, > + enum y_align y_align = y_align::CENTER); > + > + canvas to_canvas (const theme &theme, const style_manager &sm) const; > + > + void paint_to_canvas(canvas &canvas, > + canvas::coord_t offset, > + const table_geometry &tg, > + const theme &theme) const; > + > + void debug () const; > + > + /* Self-test support. */ > + const cell_placement *get_placement_at (coord_t coord) const; > + > + private: > + int get_occupancy_safe (coord_t coord) const; > + directions get_connections (int table_x, int table_y) const; > + void paint_cell_borders_to_canvas(canvas &canvas, > + canvas::coord_t offset, > + const table_geometry &tg, > + const theme &theme) const; > + void paint_cell_contents_to_canvas(canvas &canvas, > + canvas::coord_t offset, > + const table_geometry &tg) const; > + > + friend class table_cell_sizes; > + > + size_t m_size; > + std::vector<cell_placement> m_placements; > + array2<int, size_t, coord_t> m_occupancy; /* indices into the m_placements vec. */ > +}; > + > +/* A workspace for computing the row heights and column widths > + of a table (in canvas units). > + The col_widths and row_heights could be shared between multiple > + instances, for aligning multiple tables vertically or horizontally. */ > + > +class table_cell_sizes > +{ > + public: > + table_cell_sizes (table_dimension_sizes &col_widths, > + table_dimension_sizes &row_heights) > + : m_col_widths (col_widths), > + m_row_heights (row_heights) > + { > + } > + > + void pass_1 (const table &table); > + void pass_2 (const table &table); > + > + canvas::size_t get_canvas_size (const table::rect_t &rect) const; > + > + table_dimension_sizes &m_col_widths; > + table_dimension_sizes &m_row_heights; > +}; > + > +/* A class responsible for mapping from table cell coords > + to canvas coords, handling column widths. > + It's the result of solving "how big are all the table cells and where > + do they go?" > + The cell_sizes are passed in, for handling aligning multiple tables, > + sharing column widths or row heights. */ > + > +class table_geometry > +{ > + public: > + table_geometry (const table &table, table_cell_sizes &cell_sizes); > + > + void recalc_coords (); > + > + const canvas::size_t get_canvas_size () const { return m_canvas_size; } > + > + canvas::coord_t table_to_canvas (table::coord_t table_coord) const; > + int table_x_to_canvas_x (int table_x) const; > + int table_y_to_canvas_y (int table_y) const; > + > + int get_col_width (int table_x) const > + { > + return m_cell_sizes.m_col_widths.m_requirements[table_x]; > + } > + > + canvas::size_t get_canvas_size (const table::rect_t &rect) const > + { > + return m_cell_sizes.get_canvas_size (rect); > + } > + > + private: > + const table &m_table; > + table_cell_sizes &m_cell_sizes; > + canvas::size_t m_canvas_size; > + > + /* Start canvas column of table cell, including leading border. */ > + std::vector<int> m_col_start_x; > + > + /* Start canvas row of table cell, including leading border. */ > + std::vector<int> m_row_start_y; > +}; > + > +/* Helper class for handling the simple case of a single table > + that doesn't need to be aligned with respect to anything else. */ > + > +struct simple_table_geometry > +{ > + simple_table_geometry (const table &table); > + > + table_dimension_sizes m_col_widths; > + table_dimension_sizes m_row_heights; > + table_cell_sizes m_cell_sizes; > + table_geometry m_tg; > +}; > + > +} // namespace text_art > + > +#endif /* GCC_TEXT_ART_TABLE_H */ > diff --git a/gcc/text-art/theme.cc b/gcc/text-art/theme.cc > new file mode 100644 > index 00000000000..54dfe7c9213 > --- /dev/null > +++ b/gcc/text-art/theme.cc > @@ -0,0 +1,183 @@ > +/* Classes for abstracting ascii vs unicode output. > + Copyright (C) 2023 Free Software Foundation, Inc. > + Contributed by David Malcolm <dmalcolm@redhat.com>. > + > +This file is part of GCC. > + > +GCC is free software; you can redistribute it and/or modify it under > +the terms of the GNU General Public License as published by the Free > +Software Foundation; either version 3, or (at your option) any later > +version. > + > +GCC is distributed in the hope that it will be useful, but WITHOUT ANY > +WARRANTY; without even the implied warranty of MERCHANTABILITY or > +FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License > +for more details. > + > +You should have received a copy of the GNU General Public License > +along with GCC; see the file COPYING3. If not see > +<http://www.gnu.org/licenses/>. */ > + > +#include "config.h" > +#include "system.h" > +#include "coretypes.h" > +#include "pretty-print.h" > +#include "selftest.h" > +#include "text-art/selftests.h" > +#include "text-art/ruler.h" > +#include "text-art/theme.h" > + > +using namespace text_art; > + > +/* class theme. */ > + > +void > +theme::paint_y_arrow (canvas &canvas, > + int canvas_x, > + canvas::range_t y_range, > + y_arrow_dir dir, > + style::id_t style_id) const > +{ > + int canvas_y; > + int delta_y; > + const canvas::cell_t head (get_cppchar (dir == y_arrow_dir::UP > + ? cell_kind::Y_ARROW_UP_HEAD > + : cell_kind::Y_ARROW_DOWN_HEAD), > + false, style_id); > + const canvas::cell_t tail (get_cppchar (dir == y_arrow_dir::UP > + ? cell_kind::Y_ARROW_UP_TAIL > + : cell_kind::Y_ARROW_DOWN_TAIL), > + false, style_id); > + if (dir == y_arrow_dir::UP) > + { > + canvas_y = y_range.get_max (); > + delta_y = -1; > + } > + else > + { > + canvas_y = y_range.get_min (); > + delta_y = 1; > + } > + for (int len = y_range.get_size (); len; len--) > + { > + const canvas::cell_t cell = (len > 1) ? tail : head; > + canvas.paint (canvas::coord_t (canvas_x, canvas_y), cell); > + canvas_y += delta_y; > + } > +} > + > +/* class ascii_theme : public theme. */ > + > +canvas::cell_t > +ascii_theme::get_line_art (directions line_dirs) const > +{ > + if (line_dirs.m_up > + && line_dirs.m_down > + && !(line_dirs.m_left || line_dirs.m_right)) > + return canvas::cell_t ('|'); > + if (line_dirs.m_left > + && line_dirs.m_right > + && !(line_dirs.m_up || line_dirs.m_down)) > + return canvas::cell_t ('-'); > + if (line_dirs.m_up > + || line_dirs.m_down > + || line_dirs.m_left > + || line_dirs.m_right) > + return canvas::cell_t ('+'); > + return canvas::cell_t (' '); > +} > + > +cppchar_t > +ascii_theme::get_cppchar (enum cell_kind kind) const > +{ > + switch (kind) > + { > + default: > + gcc_unreachable (); > + case cell_kind::X_RULER_LEFT_EDGE: > + return '|'; > + case cell_kind::X_RULER_MIDDLE: > + return '~'; > + case cell_kind::X_RULER_INTERNAL_EDGE: > + return '|'; > + case cell_kind::X_RULER_CONNECTOR_TO_LABEL_BELOW: > + case cell_kind::X_RULER_CONNECTOR_TO_LABEL_ABOVE: > + return '+'; > + case cell_kind::X_RULER_RIGHT_EDGE: > + return '|'; > + case cell_kind::X_RULER_VERTICAL_CONNECTOR: > + return '|'; > + > + case cell_kind::TEXT_BORDER_HORIZONTAL: > + return '-'; > + case cell_kind::TEXT_BORDER_VERTICAL: > + return '|'; > + case cell_kind::TEXT_BORDER_TOP_LEFT: > + case cell_kind::TEXT_BORDER_TOP_RIGHT: > + case cell_kind::TEXT_BORDER_BOTTOM_LEFT: > + case cell_kind::TEXT_BORDER_BOTTOM_RIGHT: > + return '+'; > + > + case cell_kind::Y_ARROW_UP_HEAD: return '^'; > + case cell_kind::Y_ARROW_DOWN_HEAD: return 'v'; > + > + case cell_kind::Y_ARROW_UP_TAIL: > + case cell_kind::Y_ARROW_DOWN_TAIL: > + return '|'; > + } > +} > + > +/* class unicode_theme : public theme. */ > + > +canvas::cell_t > +unicode_theme::get_line_art (directions line_dirs) const > +{ > + return canvas::cell_t (get_box_drawing_char (line_dirs)); > +} > + > +cppchar_t > +unicode_theme::get_cppchar (enum cell_kind kind) const > +{ > + switch (kind) > + { > + default: > + gcc_unreachable (); > + case cell_kind::X_RULER_LEFT_EDGE: > + return 0x251C; /* "├": U+251C: BOX DRAWINGS LIGHT VERTICAL AND RIGHT */ > + case cell_kind::X_RULER_MIDDLE: > + return 0x2500; /* "─": U+2500: BOX DRAWINGS LIGHT HORIZONTAL */ > + case cell_kind::X_RULER_INTERNAL_EDGE: > + return 0x253C; /* "┼": U+253C: BOX DRAWINGS LIGHT VERTICAL AND HORIZONTAL */ > + case cell_kind::X_RULER_CONNECTOR_TO_LABEL_BELOW: > + return 0x252C; /* "┬": U+252C: BOX DRAWINGS LIGHT DOWN AND HORIZONTAL */ > + case cell_kind::X_RULER_CONNECTOR_TO_LABEL_ABOVE: > + return 0x2534; /* "┴": U+2534: BOX DRAWINGS LIGHT UP AND HORIZONTAL */ > + case cell_kind::X_RULER_RIGHT_EDGE: > + return 0x2524; /* "┤": U+2524: BOX DRAWINGS LIGHT VERTICAL AND LEFT */ > + case cell_kind::X_RULER_VERTICAL_CONNECTOR: > + return 0x2502; /* "│": U+2502: BOX DRAWINGS LIGHT VERTICAL */ > + > + case cell_kind::TEXT_BORDER_HORIZONTAL: > + return 0x2500; /* "─": U+2500: BOX DRAWINGS LIGHT HORIZONTAL */ > + case cell_kind::TEXT_BORDER_VERTICAL: > + return 0x2502; /* "│": U+2502: BOX DRAWINGS LIGHT VERTICAL */ > + > + /* Round corners. */ > + case cell_kind::TEXT_BORDER_TOP_LEFT: > + return 0x256D; /* "╭": U+256D BOX DRAWINGS LIGHT ARC DOWN AND RIGHT. */ > + case cell_kind::TEXT_BORDER_TOP_RIGHT: > + return 0x256E; /* "╮": U+256E BOX DRAWINGS LIGHT ARC DOWN AND LEFT. */ > + case cell_kind::TEXT_BORDER_BOTTOM_LEFT: > + return 0x2570; /* "╰": U+2570 BOX DRAWINGS LIGHT ARC UP AND RIGHT. */ > + case cell_kind::TEXT_BORDER_BOTTOM_RIGHT: > + return 0x256F; /* "╯": U+256F BOX DRAWINGS LIGHT ARC UP AND LEFT. */ > + > + case cell_kind::Y_ARROW_UP_HEAD: > + return '^'; > + case cell_kind::Y_ARROW_DOWN_HEAD: > + return 'v'; > + case cell_kind::Y_ARROW_UP_TAIL: > + case cell_kind::Y_ARROW_DOWN_TAIL: > + return 0x2502; /* "│": U+2502: BOX DRAWINGS LIGHT VERTICAL */ > + } > +} > diff --git a/gcc/text-art/theme.h b/gcc/text-art/theme.h > new file mode 100644 > index 00000000000..8edbc6efc76 > --- /dev/null > +++ b/gcc/text-art/theme.h > @@ -0,0 +1,123 @@ > +/* Classes for abstracting ascii vs unicode output. > + Copyright (C) 2023 Free Software Foundation, Inc. > + Contributed by David Malcolm <dmalcolm@redhat.com>. > + > +This file is part of GCC. > + > +GCC is free software; you can redistribute it and/or modify it > +under the terms of the GNU General Public License as published by > +the Free Software Foundation; either version 3, or (at your option) > +any later version. > + > +GCC is distributed in the hope that it will be useful, but > +WITHOUT ANY WARRANTY; without even the implied warranty of > +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU > +General Public License for more details. > + > +You should have received a copy of the GNU General Public License > +along with GCC; see the file COPYING3. If not see > +<http://www.gnu.org/licenses/>. */ > + > +#ifndef GCC_TEXT_ART_THEME_H > +#define GCC_TEXT_ART_THEME_H > + > +#include "text-art/canvas.h" > +#include "text-art/box-drawing.h" > + > +namespace text_art { > + > +class theme > +{ > + public: > + enum class cell_kind > + { > + /* A left-hand edge of a range e.g. "├". */ > + X_RULER_LEFT_EDGE, > + > + /* Within a range e.g. "─". */ > + X_RULER_MIDDLE, > + > + /* A border between two neighboring ranges e.g. "┼". */ > + X_RULER_INTERNAL_EDGE, > + > + /* The connector with the text label within a range e.g. "┬". */ > + X_RULER_CONNECTOR_TO_LABEL_BELOW, > + > + /* As above, but when the text label is above the ruler. */ > + X_RULER_CONNECTOR_TO_LABEL_ABOVE, > + > + /* The vertical connection to a text label. */ > + X_RULER_VERTICAL_CONNECTOR, > + > + /* A right-hand edge of a range e.g. "┤". */ > + X_RULER_RIGHT_EDGE, > + > + TEXT_BORDER_HORIZONTAL, > + TEXT_BORDER_VERTICAL, > + TEXT_BORDER_TOP_LEFT, > + TEXT_BORDER_TOP_RIGHT, > + TEXT_BORDER_BOTTOM_LEFT, > + TEXT_BORDER_BOTTOM_RIGHT, > + > + Y_ARROW_UP_HEAD, > + Y_ARROW_UP_TAIL, > + Y_ARROW_DOWN_HEAD, > + Y_ARROW_DOWN_TAIL, > + }; > + > + virtual ~theme () = default; > + > + virtual bool unicode_p () const = 0; > + virtual bool emojis_p () const = 0; > + > + virtual canvas::cell_t > + get_line_art (directions line_dirs) const = 0; > + > + canvas::cell_t get_cell (enum cell_kind kind, unsigned style_idx) const > + { > + return canvas::cell_t (get_cppchar (kind), false, style_idx); > + } > + > + virtual cppchar_t get_cppchar (enum cell_kind kind) const = 0; > + > + enum class y_arrow_dir { UP, DOWN }; > + void paint_y_arrow (canvas &canvas, > + int x, > + canvas::range_t y_range, > + y_arrow_dir dir, > + style::id_t style_id) const; > +}; > + > +class ascii_theme : public theme > +{ > + public: > + bool unicode_p () const final override { return false; } > + bool emojis_p () const final override { return false; } > + > + canvas::cell_t > + get_line_art (directions line_dirs) const final override; > + > + cppchar_t get_cppchar (enum cell_kind kind) const final override; > +}; > + > +class unicode_theme : public theme > +{ > + public: > + bool unicode_p () const final override { return true; } > + bool emojis_p () const override { return false; } > + > + canvas::cell_t > + get_line_art (directions line_dirs) const final override; > + > + cppchar_t get_cppchar (enum cell_kind kind) const final override; > +}; > + > +class emoji_theme : public unicode_theme > +{ > +public: > + bool emojis_p () const final override { return true; } > +}; > + > +} // namespace text_art > + > +#endif /* GCC_TEXT_ART_THEME_H */ > diff --git a/gcc/text-art/types.h b/gcc/text-art/types.h > new file mode 100644 > index 00000000000..b66188ae19c > --- /dev/null > +++ b/gcc/text-art/types.h > @@ -0,0 +1,504 @@ > +/* Types for drawing 2d "text art". > + Copyright (C) 2023 Free Software Foundation, Inc. > + Contributed by David Malcolm <dmalcolm@redhat.com>. > + > +This file is part of GCC. > + > +GCC is free software; you can redistribute it and/or modify it > +under the terms of the GNU General Public License as published by > +the Free Software Foundation; either version 3, or (at your option) > +any later version. > + > +GCC is distributed in the hope that it will be useful, but > +WITHOUT ANY WARRANTY; without even the implied warranty of > +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU > +General Public License for more details. > + > +You should have received a copy of the GNU General Public License > +along with GCC; see the file COPYING3. If not see > +<http://www.gnu.org/licenses/>. */ > + > +#ifndef GCC_TEXT_ART_TYPES_H > +#define GCC_TEXT_ART_TYPES_H > + > +#include "cpplib.h" > +#include "pretty-print.h" > +#include <vector> > +#include <string> > + > +namespace text_art { > + > +/* Forward decls. */ > + > +class canvas; > +class table; > +class theme; > + > +/* Classes for geometry. > + We use templates to avoid mixing up e.g. canvas coordinates > + with table coordinates. */ > + > +template <typename CoordinateSystem> > +struct size > +{ > + size (int w_, int h_) : w (w_), h (h_) {} > + int w; > + int h; > +}; > + > +template <typename CoordinateSystem> > +struct coord > +{ > + coord (int x_, int y_) : x (x_), y (y_) {} > + int x; > + int y; > +}; > + > +template <typename CoordinateSystem> > +coord<CoordinateSystem> operator+ (coord<CoordinateSystem> a, > + coord<CoordinateSystem> b) > +{ > + return coord<CoordinateSystem> (a.x + b.x, a.y + b.y); > +} > + > +/* A half-open range [start, next) of int. */ > + > +template <typename CoordinateSystem> > +struct range > +{ > + range (int start_, int next_) > + : start (start_), next (next_) > + {} > + > + int get_min () const { return start; } > + int get_max () const { return next - 1; } > + int get_next () const { return next; } > + int get_size () const { return next - start; } > + > + int get_midpoint () const { return get_min () + get_size () / 2; } > + > + int start; > + int next; > +}; > + > +/* A rectangle area within CoordinateSystem. */ > + > +template <typename CoordinateSystem> > +struct rect > +{ > + rect (coord<CoordinateSystem> top_left, > + size<CoordinateSystem> size) > + : m_top_left (top_left), > + m_size (size) > + { > + } > + > + rect (range<CoordinateSystem> x_range, > + range<CoordinateSystem> y_range) > + : m_top_left (x_range.get_min (), y_range.get_min ()), > + m_size (x_range.get_size (), y_range.get_size ()) > + { > + } > + > + int get_min_x () const { return m_top_left.x; } > + int get_min_y () const { return m_top_left.y; } > + int get_max_x () const { return m_top_left.x + m_size.w - 1; } > + int get_max_y () const { return m_top_left.y + m_size.h - 1; } > + int get_next_x () const { return m_top_left.x + m_size.w; } > + int get_next_y () const { return m_top_left.y + m_size.h; } > + > + range<CoordinateSystem> get_x_range () const > + { > + return range<CoordinateSystem> (get_min_x (), get_next_x ()); > + } > + range<CoordinateSystem> get_y_range () const > + { > + return range<CoordinateSystem> (get_min_y (), get_next_y ()); > + } > + > + int get_width () const { return m_size.w; } > + int get_height () const { return m_size.h; } > + > + coord<CoordinateSystem> m_top_left; > + size<CoordinateSystem> m_size; > +}; > + > +template <typename ElementType, typename SizeType, typename CoordType> > +class array2 > +{ > + public: > + typedef ElementType element_t; > + typedef SizeType size_t; > + typedef CoordType coord_t; > + > + array2 (size_t sz) > + : m_size (sz), > + m_elements (sz.w * sz.h) > + { > + } > + array2 (array2 &&other) > + : m_size (other.m_size), > + m_elements (std::move (other.m_elements)) > + { > + } > + > + /* Move assignment not implemented or used. */ > + array2 &operator== (array2 &&other) = delete; > + > + /* No copy ctor or assignment op. */ > + array2 (const array2 &other) = delete; > + array2 &operator= (const array2 &other) = delete; > + > + > + const size_t &get_size () const { return m_size; } > + > + void add_row (const element_t &element) > + { > + m_size.h++; > + m_elements.insert (m_elements.end (), m_size.w, element); > + } > + > + const element_t &get (const coord_t &coord) const > + { > + ::size_t idx = get_idx (coord); > + return m_elements[idx]; > + } > + > + void set (const coord_t &coord, const element_t &element) > + { > + ::size_t idx = get_idx (coord); > + m_elements[idx] = element; > + } > + > + void fill (element_t element) > + { > + for (int y = 0; y < m_size.h; y++) > + for (int x = 0; x < m_size.w; x++) > + set (coord_t (x, y), element); > + } > + > + private: > + ::size_t get_idx (const coord_t &coord) const > + { > + gcc_assert (coord.x >= 0); > + gcc_assert (coord.x < m_size.w); > + gcc_assert (coord.y >= 0); > + gcc_assert (coord.y < m_size.h); > + return (coord.y * m_size.w) + coord.x; > + } > + > + size_t m_size; > + std::vector<element_t> m_elements; > +}; > + > +/* A combination of attributes describing how to style a text cell. > + We only support those attributes mentioned in invoke.texi: > + - bold, > + - underscore, > + - blink, > + - inverse, > + - colors for foreground and background: > + - default color > + - named colors > + - 16-color mode colors (the "bright" variants) > + - 88-color mode > + - 256-color mode > + plus URLs. */ > + > +struct style > +{ > + typedef unsigned char id_t; > + static const id_t id_plain = 0; > + > + /* Colors. */ > + enum class named_color > + { > + DEFAULT, > + // ANSI order > + BLACK, > + RED, > + GREEN, > + YELLOW, > + BLUE, > + MAGENTA, > + CYAN, > + WHITE > + }; > + > + > + struct color > + { > + enum class kind > + { > + NAMED, > + BITS_8, > + BITS_24, > + } m_kind; > + > + union > + { > + struct { > + enum named_color m_name; > + bool m_bright; > + } m_named; > + uint8_t m_8bit; > + struct { > + uint8_t r; > + uint8_t g; > + uint8_t b; > + } m_24bit; > + } u; > + > + /* Constructor for named colors. */ > + color (enum named_color name = named_color::DEFAULT, > + bool bright = false) > + : m_kind (kind::NAMED) > + { > + u.m_named.m_name = name; > + u.m_named.m_bright = bright; > + } > + > + /* Constructor for 8-bit colors. */ > + color (uint8_t col_val) > + : m_kind (kind::BITS_8) > + { > + u.m_8bit = col_val; > + } > + > + /* Constructor for 24-bit colors. */ > + color (uint8_t r, uint8_t g, uint8_t b) > + : m_kind (kind::BITS_24) > + { > + u.m_24bit.r = r; > + u.m_24bit.g = g; > + u.m_24bit.b = b; > + } > + > + bool operator== (const color &other) const; > + bool operator!= (const color &other) const > + { > + return !(*this == other); > + } > + > + void print_sgr (pretty_printer *pp, bool fg, bool &need_separator) const; > + }; > + > + style () > + : m_bold (false), > + m_underscore (false), > + m_blink (false), > + m_reverse (false), > + m_fg_color (named_color::DEFAULT), > + m_bg_color (named_color::DEFAULT), > + m_url () > + {} > + > + bool operator== (const style &other) const > + { > + return (m_bold == other.m_bold > + && m_underscore == other.m_underscore > + && m_blink == other.m_blink > + && m_reverse == other.m_reverse > + && m_fg_color == other.m_fg_color > + && m_bg_color == other.m_bg_color > + && m_url == other.m_url); > + } > + > + style &set_style_url (const char *url); > + > + static void print_changes (pretty_printer *pp, > + const style &old_style, > + const style &new_style); > + > + bool m_bold; > + bool m_underscore; > + bool m_blink; > + bool m_reverse; > + color m_fg_color; > + color m_bg_color; > + std::vector<cppchar_t> m_url; // empty = no URL > +}; > + > +/* A class to keep track of all the styles in use in a drawing, so that > + we can refer to them via the compact style::id_t type, rather than > + via e.g. pointers. */ > + > +class style_manager > +{ > + public: > + style_manager (); > + style::id_t get_or_create_id (const style &style); > + const style &get_style (style::id_t id) const > + { > + return m_styles[id]; > + } > + void print_any_style_changes (pretty_printer *pp, > + style::id_t old_id, > + style::id_t new_id) const; > + unsigned get_num_styles () const { return m_styles.size (); } > + > +private: > + std::vector<style> m_styles; > +}; > + > +class styled_unichar > +{ > + public: > + friend class styled_string; > + > + explicit styled_unichar () > + : m_code (0), > + m_style_id (style::id_plain) > + {} > + explicit styled_unichar (cppchar_t ch) > + : m_code (ch), > + m_emoji_variant_p (false), > + m_style_id (style::id_plain) > + {} > + explicit styled_unichar (cppchar_t ch, bool emoji, style::id_t style_id) > + : m_code (ch), > + m_emoji_variant_p (emoji), > + m_style_id (style_id) > + { > + gcc_assert (style_id <= 0x7f); > + } > + > + cppchar_t get_code () const { return m_code; } > + bool emoji_variant_p () const { return m_emoji_variant_p; } > + style::id_t get_style_id () const { return m_style_id; } > + > + bool double_width_p () const > + { > + int width = cpp_wcwidth (get_code ()); > + gcc_assert (width == 1 || width == 2); > + return width == 2; > + } > + > + bool operator== (const styled_unichar &other) const > + { > + return (m_code == other.m_code > + && m_emoji_variant_p == other.m_emoji_variant_p > + && m_style_id == other.m_style_id); > + } > + > + void set_emoji_variant () { m_emoji_variant_p = true; } > + > + int get_canvas_width () const > + { > + return cpp_wcwidth (m_code); > + } > + > + void add_combining_char (cppchar_t ch) > + { > + m_combining_chars.push_back (ch); > + } > + > + const std::vector<cppchar_t> get_combining_chars () const > + { > + return m_combining_chars; > + } > + > +private: > + cppchar_t m_code : 24; > + bool m_emoji_variant_p : 1; > + style::id_t m_style_id : 7; > + std::vector<cppchar_t> m_combining_chars; > +}; > + > +class styled_string > +{ > + public: > + explicit styled_string () = default; > + explicit styled_string (style_manager &sm, const char *str); > + explicit styled_string (cppchar_t cppchar, bool emoji = false); > + > + styled_string (styled_string &&) = default; > + styled_string &operator= (styled_string &&) = default; > + > + /* No copy ctor or assignment op. */ > + styled_string (const styled_string &) = delete; > + styled_string &operator= (const styled_string &) = delete; > + > + /* For the few cases where copying is required, spell it out explicitly. */ > + styled_string copy () const > + { > + styled_string result; > + result.m_chars = m_chars; > + return result; > + } > + > + bool operator== (const styled_string &other) const > + { > + return m_chars == other.m_chars; > + } > + > + static styled_string from_fmt (style_manager &sm, > + printer_fn format_decoder, > + const char *fmt, ...) > + ATTRIBUTE_GCC_PPDIAG(3, 4); > + static styled_string from_fmt_va (style_manager &sm, > + printer_fn format_decoder, > + const char *fmt, > + va_list *args) > + ATTRIBUTE_GCC_PPDIAG(3, 0); > + > + size_t size () const { return m_chars.size (); } > + styled_unichar operator[] (size_t idx) const { return m_chars[idx]; } > + > + std::vector<styled_unichar>::const_iterator begin () const > + { > + return m_chars.begin (); > + } > + std::vector<styled_unichar>::const_iterator end () const > + { > + return m_chars.end (); > + } > + > + int calc_canvas_width () const; > + > + void append (const styled_string &suffix); > + > + void set_url (style_manager &sm, const char *url); > + > +private: > + std::vector<styled_unichar> m_chars; > +}; > + > +enum class x_align > +{ > + LEFT, > + CENTER, > + RIGHT > +}; > + > +enum class y_align > +{ > + TOP, > + CENTER, > + BOTTOM > +}; > + > +/* A set of cardinal directions within a canvas or table. */ > + > +struct directions > +{ > +public: > + directions (bool up, bool down, bool left, bool right) > + : m_up (up), m_down (down), m_left (left), m_right (right) > + { > + } > + > + size_t as_index () const > + { > + return (m_up << 3) | (m_down << 2) | (m_left << 1) | m_right; > + } > + > + bool m_up: 1; > + bool m_down: 1; > + bool m_left: 1; > + bool m_right: 1; > +}; > + > +} // namespace text_art > + > +#endif /* GCC_TEXT_ART_TYPES_H */ > diff --git a/gcc/text-art/widget.cc b/gcc/text-art/widget.cc > new file mode 100644 > index 00000000000..e6e544d5035 > --- /dev/null > +++ b/gcc/text-art/widget.cc > @@ -0,0 +1,275 @@ > +/* Hierarchical diagram elements. > + Copyright (C) 2023 Free Software Foundation, Inc. > + Contributed by David Malcolm <dmalcolm@redhat.com>. > + > +This file is part of GCC. > + > +GCC is free software; you can redistribute it and/or modify it under > +the terms of the GNU General Public License as published by the Free > +Software Foundation; either version 3, or (at your option) any later > +version. > + > +GCC is distributed in the hope that it will be useful, but WITHOUT ANY > +WARRANTY; without even the implied warranty of MERCHANTABILITY or > +FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License > +for more details. > + > +You should have received a copy of the GNU General Public License > +along with GCC; see the file COPYING3. If not see > +<http://www.gnu.org/licenses/>. */ > + > +#include "config.h" > +#define INCLUDE_MEMORY > +#include "system.h" > +#include "coretypes.h" > +#include "pretty-print.h" > +#include "selftest.h" > +#include "make-unique.h" > +#include "text-art/selftests.h" > +#include "text-art/widget.h" > + > +using namespace text_art; > + > +/* class text_art::widget. */ > + > +canvas > +widget::to_canvas (const style_manager &style_mgr) > +{ > + const canvas::size_t req_size = get_req_size (); > + > + /* For now we don't constrain the allocation; we give > + the widget the full size it requested, and widgets > + assume they got their full size request. */ > + const canvas::size_t alloc_size = req_size; > + > + set_alloc_rect (canvas::rect_t (canvas::coord_t (0, 0), alloc_size)); > + canvas c (alloc_size, style_mgr); > + paint_to_canvas (c); > + return c; > +} > + > +/* class text_art::vbox_widget : public text_art::container_widget. */ > + > +const char * > +vbox_widget::get_desc () const > +{ > + return "vbox_widget"; > +} > + > +canvas::size_t > +vbox_widget::calc_req_size () > +{ > + canvas::size_t result (0, 0); > + for (auto &child : m_children) > + { > + canvas::size_t child_req_size = child->get_req_size(); > + result.h += child_req_size.h; > + result.w = std::max (result.w, child_req_size.w); > + } > + return result; > +} > + > +void > +vbox_widget::update_child_alloc_rects () > +{ > + const int x = get_min_x (); > + int y = get_min_y (); > + for (auto &child : m_children) > + { > + child->set_alloc_rect > + (canvas::rect_t (canvas::coord_t (x, y), > + canvas::size_t (get_alloc_w (), > + child->get_req_h ()))); > + y += child->get_req_h (); > + } > +} > + > +/* class text_art::text_widget : public text_art::leaf_widget. */ > + > +const char * > +text_widget::get_desc () const > +{ > + return "text_widget"; > +} > + > +canvas::size_t > +text_widget::calc_req_size () > +{ > + return canvas::size_t (m_str.size (), 1); > +} > + > +void > +text_widget::paint_to_canvas (canvas &canvas) > +{ > + canvas.paint_text (get_top_left (), m_str); > +} > + > +/* class text_art::canvas_widget : public text_art::leaf_widget. */ > + > +const char * > +canvas_widget::get_desc () const > +{ > + return "canvas_widget"; > +} > + > +canvas::size_t > +canvas_widget::calc_req_size () > +{ > + return m_canvas.get_size (); > +} > + > +void > +canvas_widget::paint_to_canvas (canvas &canvas) > +{ > + for (int y = 0; y < m_canvas.get_size ().h; y++) > + for (int x = 0; x < m_canvas.get_size ().w; x++) > + { > + canvas::coord_t rel_xy (x, y); > + canvas.paint (get_top_left () + rel_xy, > + m_canvas.get (rel_xy)); > + } > +} > + > +#if CHECKING_P > + > +namespace selftest { > + > +/* Concrete widget subclass for writing selftests. > + Requests a hard-coded size, and fills its allocated rectangle > + with a specific character. */ > + > +class test_widget : public leaf_widget > +{ > +public: > + test_widget (canvas::size_t size, char ch) > + : m_test_size (size), m_ch (ch) > + {} > + > + const char *get_desc () const final override > + { > + return "test_widget"; > + } > + canvas::size_t calc_req_size () final override > + { > + return m_test_size; > + } > + void paint_to_canvas (canvas &canvas) final override > + { > + canvas.fill (get_alloc_rect (), canvas::cell_t (m_ch)); > + } > + > +private: > + canvas::size_t m_test_size; > + char m_ch; > +}; > + > +static void > +test_test_widget () > +{ > + style_manager sm; > + test_widget w (canvas::size_t (3, 3), 'A'); > + canvas c (w.to_canvas (sm)); > + ASSERT_CANVAS_STREQ > + (c, false, > + ("AAA\n" > + "AAA\n" > + "AAA\n")); > +} > + > +static void > +test_text_widget () > +{ > + style_manager sm; > + text_widget w (styled_string (sm, "hello world")); > + canvas c (w.to_canvas (sm)); > + ASSERT_CANVAS_STREQ > + (c, false, > + ("hello world\n")); > +} > + > +static void > +test_wrapper_widget () > +{ > + style_manager sm; > + wrapper_widget w (::make_unique<test_widget> (canvas::size_t (3, 3), 'B')); > + canvas c (w.to_canvas (sm)); > + ASSERT_CANVAS_STREQ > + (c, false, > + ("BBB\n" > + "BBB\n" > + "BBB\n")); > +} > + > +static void > +test_vbox_1 () > +{ > + style_manager sm; > + vbox_widget w; > + for (int i = 0; i < 5; i++) > + w.add_child > + (::make_unique <text_widget> > + (styled_string::from_fmt (sm, nullptr, > + "this is line %i", i))); > + canvas c (w.to_canvas (sm)); > + ASSERT_CANVAS_STREQ > + (c, false, > + ("this is line 0\n" > + "this is line 1\n" > + "this is line 2\n" > + "this is line 3\n" > + "this is line 4\n")); > +} > + > +static void > +test_vbox_2 () > +{ > + style_manager sm; > + vbox_widget w; > + w.add_child (::make_unique<test_widget> (canvas::size_t (1, 3), 'A')); > + w.add_child (::make_unique<test_widget> (canvas::size_t (4, 1), 'B')); > + w.add_child (::make_unique<test_widget> (canvas::size_t (1, 2), 'C')); > + canvas c (w.to_canvas (sm)); > + ASSERT_CANVAS_STREQ > + (c, false, > + ("AAAA\n" > + "AAAA\n" > + "AAAA\n" > + "BBBB\n" > + "CCCC\n" > + "CCCC\n")); > +} > + > +static void > +test_canvas_widget () > +{ > + style_manager sm; > + canvas inner_canvas (canvas::size_t (5, 3), sm); > + inner_canvas.fill (canvas::rect_t (canvas::coord_t (0, 0), > + canvas::size_t (5, 3)), > + canvas::cell_t ('a')); > + canvas_widget cw (std::move (inner_canvas)); > + canvas c (cw.to_canvas (sm)); > + ASSERT_CANVAS_STREQ > + (c, false, > + ("aaaaa\n" > + "aaaaa\n" > + "aaaaa\n")); > +} > + > +/* Run all selftests in this file. */ > + > +void > +text_art_widget_cc_tests () > +{ > + test_test_widget (); > + test_text_widget (); > + test_wrapper_widget (); > + test_vbox_1 (); > + test_vbox_2 (); > + test_canvas_widget (); > +} > + > +} // namespace selftest > + > + > +#endif /* #if CHECKING_P */ > diff --git a/gcc/text-art/widget.h b/gcc/text-art/widget.h > new file mode 100644 > index 00000000000..91209444bf7 > --- /dev/null > +++ b/gcc/text-art/widget.h > @@ -0,0 +1,246 @@ > +/* Hierarchical diagram elements. > + Copyright (C) 2023 Free Software Foundation, Inc. > + Contributed by David Malcolm <dmalcolm@redhat.com>. > + > +This file is part of GCC. > + > +GCC is free software; you can redistribute it and/or modify it > +under the terms of the GNU General Public License as published by > +the Free Software Foundation; either version 3, or (at your option) > +any later version. > + > +GCC is distributed in the hope that it will be useful, but > +WITHOUT ANY WARRANTY; without even the implied warranty of > +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU > +General Public License for more details. > + > +You should have received a copy of the GNU General Public License > +along with GCC; see the file COPYING3. If not see > +<http://www.gnu.org/licenses/>. */ > + > +#ifndef GCC_TEXT_ART_WIDGET_H > +#define GCC_TEXT_ART_WIDGET_H > + > +#include <vector> > +#include "text-art/canvas.h" > +#include "text-art/table.h" > + > +namespace text_art { > + > +/* Abstract base class: something that knows how to size itself and > + how to paint itself to a canvas, potentially with children, with > + support for hierarchical sizing and positioning. > + > + Widgets have a two-phase sizing/positioning algorithm. > + > + Step 1: size requests: the root widget is asked for its size request i.e > + how big it wants to be. This is handled by recursively asking child > + widgets for their requested sizes. Each widget subclass can implement > + their own logic for this in the "calc_req_size" vfunc, and the result > + is cached in m_req_size. > + > + Step 2: rect allocation: the root widget is set a canvas::rect_t as > + its "allocated" rectangle. Each widget subclass can then place its > + children recursively using the "update_child_alloc_rects" vfunc. > + For simplicity, all coordinates in the hierarchy are within the same > + coordinate system (rather than attempting to store per-child offsets). > + > + Widget subclasses are responsible for managing their own children. */ > + > +/* Subclasses in this header, with indentation indicating inheritance. */ > + > +class widget; /* Abstract base class. */ > + class wrapper_widget; /* Concrete subclass: a widget with a single child. */ > + class container_widget; /* Abstract subclass: widgets with an arbitrary > + number of children. */ > + class vbox_widget; /* Concrete widget subclass: lay out children > + vertically. */ > + class leaf_widget; /* Abstract subclass: a widget with no children. */ > + class text_widget; /* Concrete subclass: a text string. */ > + class canvas_widget; /* Concrete subclass: a pre-rendered canvas. */ > + > +class widget > +{ > + public: > + /* This can be very useful for debugging when implementing new > + widget subclasses. */ > + static const bool DEBUG_GEOMETRY = false; > + > + virtual ~widget () {} > + > + canvas to_canvas (const style_manager &style_mgr); > + > + canvas::size_t get_req_size () > + { > + m_req_size = calc_req_size(); > + if (DEBUG_GEOMETRY) > + fprintf (stderr, "calc_req_size (%s) -> (w:%i, h:%i)\n", > + get_desc (), > + m_req_size.w, m_req_size.h); > + return m_req_size; > + } > + > + void set_alloc_rect (const canvas::rect_t &rect) > + { > + if (DEBUG_GEOMETRY) > + fprintf (stderr, "set_alloc_rect (%s): ((x:%i, y:%i), (w:%i, h:%i))\n", > + get_desc (), > + rect.m_top_left.x, rect.m_top_left.y, > + rect.m_size.w, rect.m_size.h); > + m_alloc_rect = rect; > + update_child_alloc_rects (); > + } > + > + virtual const char *get_desc () const = 0; > + virtual canvas::size_t calc_req_size () = 0; > + virtual void update_child_alloc_rects () = 0; > + virtual void paint_to_canvas (canvas &canvas) = 0; > + > + /* Access to the cached size request of this widget. */ > + const canvas::size_t get_req_size () const { return m_req_size; } > + int get_req_w () const { return m_req_size.w; } > + int get_req_h () const { return m_req_size.h; } > + > + /* Access to the allocated canvas coordinates of this widget. */ > + const canvas::rect_t &get_alloc_rect () const { return m_alloc_rect; } > + int get_alloc_w () const { return m_alloc_rect.get_width (); } > + int get_alloc_h () const { return m_alloc_rect.get_height (); } > + int get_min_x () const { return m_alloc_rect.get_min_x (); } > + int get_max_x () const { return m_alloc_rect.get_max_x (); } > + int get_next_x () const { return m_alloc_rect.get_next_x (); } > + int get_min_y () const { return m_alloc_rect.get_min_y (); } > + int get_max_y () const { return m_alloc_rect.get_max_y (); } > + int get_next_y () const { return m_alloc_rect.get_max_y (); } > + canvas::range_t get_x_range () const { return m_alloc_rect.get_x_range (); } > + canvas::range_t get_y_range () const { return m_alloc_rect.get_y_range (); } > + const canvas::coord_t &get_top_left () const > + { > + return m_alloc_rect.m_top_left; > + } > + > + protected: > + widget () > + : m_req_size (0, 0), > + m_alloc_rect (canvas::coord_t (0, 0), > + canvas::size_t (0, 0)) > + {} > + > +private: > + /* How much size this widget requested. */ > + canvas::size_t m_req_size; > + /* Where (and how big) this widget was allocated. */ > + canvas::rect_t m_alloc_rect; > +}; > + > +/* Concrete subclass for a widget with a single child. */ > + > +class wrapper_widget : public widget > +{ > + public: > + wrapper_widget (std::unique_ptr<widget> child) > + : m_child (std::move (child)) > + {} > + > + const char *get_desc () const override > + { > + return "wrapper_widget"; > + } > + canvas::size_t calc_req_size () override > + { > + return m_child->get_req_size (); > + } > + void update_child_alloc_rects () > + { > + m_child->set_alloc_rect (get_alloc_rect ()); > + } > + void paint_to_canvas (canvas &canvas) override > + { > + m_child->paint_to_canvas (canvas); > + } > + private: > + std::unique_ptr<widget> m_child; > +}; > + > +/* Abstract subclass for widgets with an arbitrary number of children. */ > + > +class container_widget : public widget > +{ > + public: > + void add_child (std::unique_ptr<widget> child) > + { > + m_children.push_back (std::move (child)); > + } > + > + void paint_to_canvas (canvas &canvas) final override > + { > + for (auto &child : m_children) > + child->paint_to_canvas (canvas); > + } > + > + protected: > + std::vector<std::unique_ptr<widget>> m_children; > +}; > + > +/* Concrete widget subclass: lay out children vertically. */ > + > +class vbox_widget : public container_widget > +{ > + public: > + const char *get_desc () const override; > + canvas::size_t calc_req_size () override; > + void update_child_alloc_rects () final override; > +}; > + > +/* Abstract subclass for widgets with no children. */ > + > +class leaf_widget : public widget > +{ > + public: > + void update_child_alloc_rects () final override > + { > + /* no-op. */ > + } > + > + protected: > + leaf_widget () : widget () {} > +}; > + > +/* Concrete widget subclass for a text string. */ > + > +class text_widget : public leaf_widget > +{ > + public: > + text_widget (styled_string str) > + : leaf_widget (), m_str (std::move (str)) > + { > + } > + > + const char *get_desc () const override; > + canvas::size_t calc_req_size () final override; > + void paint_to_canvas (canvas &canvas) final override; > + > +private: > + styled_string m_str; > +}; > + > +/* Concrete widget subclass for a pre-rendered canvas. */ > + > +class canvas_widget : public leaf_widget > +{ > + public: > + canvas_widget (canvas &&c) > + : leaf_widget (), m_canvas (std::move (c)) > + { > + } > + > + const char *get_desc () const override; > + canvas::size_t calc_req_size () final override; > + void paint_to_canvas (canvas &canvas) final override; > + > +private: > + canvas m_canvas; > +}; > + > +} // namespace text_art > + > +#endif /* GCC_TEXT_ART_WIDGET_H */ > diff --git a/libcpp/charset.cc b/libcpp/charset.cc > index d7f323b2cd5..a0bd2ede11c 100644 > --- a/libcpp/charset.cc > +++ b/libcpp/charset.cc > @@ -3147,34 +3147,26 @@ cpp_display_column_to_byte_column (const char *data, int data_length, > return dw.bytes_processed () + MAX (0, display_col - avail_display); > } > > -/* Our own version of wcwidth(). We don't use the actual wcwidth() in glibc, > - because that will inspect the user's locale, and in particular in an ASCII > - locale, it will not return anything useful for extended characters. But GCC > - in other respects (see e.g. _cpp_default_encoding()) behaves as if > - everything is UTF-8. We also make some tweaks that are useful for the way > - GCC needs to use this data, e.g. tabs and other control characters should be > - treated as having width 1. The lookup tables are generated from > - contrib/unicode/gen_wcwidth.py and were made by simply calling glibc > - wcwidth() on all codepoints, then applying the small tweaks. These tables > - are not highly optimized, but for the present purpose of outputting > - diagnostics, they are sufficient. */ > - > -#include "generated_cpp_wcwidth.h" > -int cpp_wcwidth (cppchar_t c) > +template <typename PropertyType> > +PropertyType > +get_cppchar_property (cppchar_t c, > + const cppchar_t *range_ends, > + const PropertyType *range_values, > + size_t num_ranges, > + PropertyType default_value) > { > - if (__builtin_expect (c <= wcwidth_range_ends[0], true)) > - return wcwidth_widths[0]; > + if (__builtin_expect (c <= range_ends[0], true)) > + return range_values[0]; > > /* Binary search the tables. */ > int begin = 1; > - static const int end > - = sizeof wcwidth_range_ends / sizeof (*wcwidth_range_ends); > + static const int end = num_ranges; > int len = end - begin; > do > { > int half = len/2; > int middle = begin + half; > - if (c > wcwidth_range_ends[middle]) > + if (c > range_ends[middle]) > { > begin = middle + 1; > len -= half + 1; > @@ -3184,6 +3176,61 @@ int cpp_wcwidth (cppchar_t c) > } while (len); > > if (__builtin_expect (begin != end, true)) > - return wcwidth_widths[begin]; > - return 1; > + return range_values[begin]; > + > + return default_value; > +} > + > +/* Our own version of wcwidth(). We don't use the actual wcwidth() in glibc, > + because that will inspect the user's locale, and in particular in an ASCII > + locale, it will not return anything useful for extended characters. But GCC > + in other respects (see e.g. _cpp_default_encoding()) behaves as if > + everything is UTF-8. We also make some tweaks that are useful for the way > + GCC needs to use this data, e.g. tabs and other control characters should be > + treated as having width 1. The lookup tables are generated from > + contrib/unicode/gen_wcwidth.py and were made by simply calling glibc > + wcwidth() on all codepoints, then applying the small tweaks. These tables > + are not highly optimized, but for the present purpose of outputting > + diagnostics, they are sufficient. */ > + > +#include "generated_cpp_wcwidth.h" > + > +int > +cpp_wcwidth (cppchar_t c) > +{ > + const size_t num_ranges > + = sizeof wcwidth_range_ends / sizeof (*wcwidth_range_ends); > + return get_cppchar_property<unsigned char > (c, > + &wcwidth_range_ends[0], > + &wcwidth_widths[0], > + num_ranges, > + 1); > +} > + > +#include "combining-chars.inc" > + > +bool > +cpp_is_combining_char (cppchar_t c) > +{ > + const size_t num_ranges > + = sizeof combining_range_ends / sizeof (*combining_range_ends); > + return get_cppchar_property<bool> (c, > + &combining_range_ends[0], > + &is_combining[0], > + num_ranges, > + false); > +} > + > +#include "printable-chars.inc" > + > +bool > +cpp_is_printable_char (cppchar_t c) > +{ > + const size_t num_ranges > + = sizeof printable_range_ends / sizeof (*printable_range_ends); > + return get_cppchar_property<bool> (c, > + &printable_range_ends[0], > + &is_printable[0], > + num_ranges, > + false); > } > diff --git a/libcpp/combining-chars.inc b/libcpp/combining-chars.inc > new file mode 100644 > index 00000000000..dfec966970a > --- /dev/null > +++ b/libcpp/combining-chars.inc > @@ -0,0 +1,68 @@ > +/* Generated by contrib/unicode/gen-combining-chars.py > + using version 12.1.0 of the Unicode standard. */ > + > +static const cppchar_t combining_range_ends[] = { > + 0x2ff, 0x34e, 0x34f, 0x36f, 0x482, 0x487, 0x590, 0x5bd, > + 0x5be, 0x5bf, 0x5c0, 0x5c2, 0x5c3, 0x5c5, 0x5c6, 0x5c7, > + 0x60f, 0x61a, 0x64a, 0x65f, 0x66f, 0x670, 0x6d5, 0x6dc, > + 0x6de, 0x6e4, 0x6e6, 0x6e8, 0x6e9, 0x6ed, 0x710, 0x711, > + 0x72f, 0x74a, 0x7ea, 0x7f3, 0x7fc, 0x7fd, 0x815, 0x819, > + 0x81a, 0x823, 0x824, 0x827, 0x828, 0x82d, 0x858, 0x85b, > + 0x8d2, 0x8e1, 0x8e2, 0x8ff, 0x93b, 0x93c, 0x94c, 0x94d, > + 0x950, 0x954, 0x9bb, 0x9bc, 0x9cc, 0x9cd, 0x9fd, 0x9fe, > + 0xa3b, 0xa3c, 0xa4c, 0xa4d, 0xabb, 0xabc, 0xacc, 0xacd, > + 0xb3b, 0xb3c, 0xb4c, 0xb4d, 0xbcc, 0xbcd, 0xc4c, 0xc4d, > + 0xc54, 0xc56, 0xcbb, 0xcbc, 0xccc, 0xccd, 0xd3a, 0xd3c, > + 0xd4c, 0xd4d, 0xdc9, 0xdca, 0xe37, 0xe3a, 0xe47, 0xe4b, > + 0xeb7, 0xeba, 0xec7, 0xecb, 0xf17, 0xf19, 0xf34, 0xf35, > + 0xf36, 0xf37, 0xf38, 0xf39, 0xf70, 0xf72, 0xf73, 0xf74, > + 0xf79, 0xf7d, 0xf7f, 0xf80, 0xf81, 0xf84, 0xf85, 0xf87, > + 0xfc5, 0xfc6, 0x1036, 0x1037, 0x1038, 0x103a, 0x108c, 0x108d, > + 0x135c, 0x135f, 0x1713, 0x1714, 0x1733, 0x1734, 0x17d1, 0x17d2, > + 0x17dc, 0x17dd, 0x18a8, 0x18a9, 0x1938, 0x193b, 0x1a16, 0x1a18, > + 0x1a5f, 0x1a60, 0x1a74, 0x1a7c, 0x1a7e, 0x1a7f, 0x1aaf, 0x1abd, > + 0x1b33, 0x1b34, 0x1b43, 0x1b44, 0x1b6a, 0x1b73, 0x1ba9, 0x1bab, > + 0x1be5, 0x1be6, 0x1bf1, 0x1bf3, 0x1c36, 0x1c37, 0x1ccf, 0x1cd2, > + 0x1cd3, 0x1ce0, 0x1ce1, 0x1ce8, 0x1cec, 0x1ced, 0x1cf3, 0x1cf4, > + 0x1cf7, 0x1cf9, 0x1dbf, 0x1df9, 0x1dfa, 0x1dff, 0x20cf, 0x20dc, > + 0x20e0, 0x20e1, 0x20e4, 0x20f0, 0x2cee, 0x2cf1, 0x2d7e, 0x2d7f, > + 0x2ddf, 0x2dff, 0x3029, 0x302f, 0x3098, 0x309a, 0xa66e, 0xa66f, > + 0xa673, 0xa67d, 0xa69d, 0xa69f, 0xa6ef, 0xa6f1, 0xa805, 0xa806, > + 0xa8c3, 0xa8c4, 0xa8df, 0xa8f1, 0xa92a, 0xa92d, 0xa952, 0xa953, > + 0xa9b2, 0xa9b3, 0xa9bf, 0xa9c0, 0xaaaf, 0xaab0, 0xaab1, 0xaab4, > + 0xaab6, 0xaab8, 0xaabd, 0xaabf, 0xaac0, 0xaac1, 0xaaf5, 0xaaf6, > + 0xabec, 0xabed, 0xfb1d, 0xfb1e, 0xfe1f, 0xfe2f, 0x101fc, 0x101fd, > + 0x102df, 0x102e0, 0x10375, 0x1037a, 0x10a0c, 0x10a0d, 0x10a0e, 0x10a0f, > + 0x10a37, 0x10a3a, 0x10a3e, 0x10a3f, 0x10ae4, 0x10ae6, 0x10d23, 0x10d27, > + 0x10f45, 0x10f50, 0x11045, 0x11046, 0x1107e, 0x1107f, 0x110b8, 0x110ba, > + 0x110ff, 0x11102, 0x11132, 0x11134, 0x11172, 0x11173, 0x111bf, 0x111c0, > + 0x111c9, 0x111ca, 0x11234, 0x11236, 0x112e8, 0x112ea, 0x1133a, 0x1133c, > + 0x1134c, 0x1134d, 0x11365, 0x1136c, 0x1136f, 0x11374, 0x11441, 0x11442, > + 0x11445, 0x11446, 0x1145d, 0x1145e, 0x114c1, 0x114c3, 0x115be, 0x115c0, > + 0x1163e, 0x1163f, 0x116b5, 0x116b7, 0x1172a, 0x1172b, 0x11838, 0x1183a, > + 0x119df, 0x119e0, 0x11a33, 0x11a34, 0x11a46, 0x11a47, 0x11a98, 0x11a99, > + 0x11c3e, 0x11c3f, 0x11d41, 0x11d42, 0x11d43, 0x11d45, 0x11d96, 0x11d97, > + 0x16aef, 0x16af4, 0x16b2f, 0x16b36, 0x1bc9d, 0x1bc9e, 0x1d164, 0x1d169, > + 0x1d16c, 0x1d172, 0x1d17a, 0x1d182, 0x1d184, 0x1d18b, 0x1d1a9, 0x1d1ad, > + 0x1d241, 0x1d244, 0x1dfff, 0x1e006, 0x1e007, 0x1e018, 0x1e01a, 0x1e021, > + 0x1e022, 0x1e024, 0x1e025, 0x1e02a, 0x1e12f, 0x1e136, 0x1e2eb, 0x1e2ef, > + 0x1e8cf, 0x1e8d6, 0x1e943, 0x1e94a, 0x10fffe, > +}; > + > +static const bool is_combining[] = { > + 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, > + 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, > + 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, > + 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, > + 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, > + 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, > + 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, > + 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, > + 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, > + 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, > + 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, > + 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, > + 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, > + 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, > + 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, > +}; > diff --git a/libcpp/include/cpplib.h b/libcpp/include/cpplib.h > index a6f0abd894c..d326f5aa316 100644 > --- a/libcpp/include/cpplib.h > +++ b/libcpp/include/cpplib.h > @@ -1602,4 +1602,7 @@ bool cpp_input_conversion_is_trivial (const char *input_charset); > int cpp_check_utf8_bom (const char *data, size_t data_length); > bool cpp_valid_utf8_p (const char *data, size_t num_bytes); > > +bool cpp_is_combining_char (cppchar_t c); > +bool cpp_is_printable_char (cppchar_t c); > + > #endif /* ! LIBCPP_CPPLIB_H */ > diff --git a/libcpp/printable-chars.inc b/libcpp/printable-chars.inc > new file mode 100644 > index 00000000000..470b1eef331 > --- /dev/null > +++ b/libcpp/printable-chars.inc > @@ -0,0 +1,231 @@ > +/* Generated by contrib/unicode/gen-printable-chars.py > + using version 12.1.0 of the Unicode standard. */ > + > +static const cppchar_t printable_range_ends[] = { > + 0x1f, 0x7e, 0x9f, 0xac, 0xad, 0x377, 0x379, 0x37f, > + 0x383, 0x38a, 0x38b, 0x38c, 0x38d, 0x3a1, 0x3a2, 0x52f, > + 0x530, 0x556, 0x558, 0x58a, 0x58c, 0x58f, 0x590, 0x5c7, > + 0x5cf, 0x5ea, 0x5ee, 0x5f4, 0x605, 0x61b, 0x61d, 0x6dc, > + 0x6dd, 0x70d, 0x70f, 0x74a, 0x74c, 0x7b1, 0x7bf, 0x7fa, > + 0x7fc, 0x82d, 0x82f, 0x83e, 0x83f, 0x85b, 0x85d, 0x85e, > + 0x85f, 0x86a, 0x89f, 0x8b4, 0x8b5, 0x8bd, 0x8d2, 0x8e1, > + 0x8e2, 0x983, 0x984, 0x98c, 0x98e, 0x990, 0x992, 0x9a8, > + 0x9a9, 0x9b0, 0x9b1, 0x9b2, 0x9b5, 0x9b9, 0x9bb, 0x9c4, > + 0x9c6, 0x9c8, 0x9ca, 0x9ce, 0x9d6, 0x9d7, 0x9db, 0x9dd, > + 0x9de, 0x9e3, 0x9e5, 0x9fe, 0xa00, 0xa03, 0xa04, 0xa0a, > + 0xa0e, 0xa10, 0xa12, 0xa28, 0xa29, 0xa30, 0xa31, 0xa33, > + 0xa34, 0xa36, 0xa37, 0xa39, 0xa3b, 0xa3c, 0xa3d, 0xa42, > + 0xa46, 0xa48, 0xa4a, 0xa4d, 0xa50, 0xa51, 0xa58, 0xa5c, > + 0xa5d, 0xa5e, 0xa65, 0xa76, 0xa80, 0xa83, 0xa84, 0xa8d, > + 0xa8e, 0xa91, 0xa92, 0xaa8, 0xaa9, 0xab0, 0xab1, 0xab3, > + 0xab4, 0xab9, 0xabb, 0xac5, 0xac6, 0xac9, 0xaca, 0xacd, > + 0xacf, 0xad0, 0xadf, 0xae3, 0xae5, 0xaf1, 0xaf8, 0xaff, > + 0xb00, 0xb03, 0xb04, 0xb0c, 0xb0e, 0xb10, 0xb12, 0xb28, > + 0xb29, 0xb30, 0xb31, 0xb33, 0xb34, 0xb39, 0xb3b, 0xb44, > + 0xb46, 0xb48, 0xb4a, 0xb4d, 0xb55, 0xb57, 0xb5b, 0xb5d, > + 0xb5e, 0xb63, 0xb65, 0xb77, 0xb81, 0xb83, 0xb84, 0xb8a, > + 0xb8d, 0xb90, 0xb91, 0xb95, 0xb98, 0xb9a, 0xb9b, 0xb9c, > + 0xb9d, 0xb9f, 0xba2, 0xba4, 0xba7, 0xbaa, 0xbad, 0xbb9, > + 0xbbd, 0xbc2, 0xbc5, 0xbc8, 0xbc9, 0xbcd, 0xbcf, 0xbd0, > + 0xbd6, 0xbd7, 0xbe5, 0xbfa, 0xbff, 0xc0c, 0xc0d, 0xc10, > + 0xc11, 0xc28, 0xc29, 0xc39, 0xc3c, 0xc44, 0xc45, 0xc48, > + 0xc49, 0xc4d, 0xc54, 0xc56, 0xc57, 0xc5a, 0xc5f, 0xc63, > + 0xc65, 0xc6f, 0xc76, 0xc8c, 0xc8d, 0xc90, 0xc91, 0xca8, > + 0xca9, 0xcb3, 0xcb4, 0xcb9, 0xcbb, 0xcc4, 0xcc5, 0xcc8, > + 0xcc9, 0xccd, 0xcd4, 0xcd6, 0xcdd, 0xcde, 0xcdf, 0xce3, > + 0xce5, 0xcef, 0xcf0, 0xcf2, 0xcff, 0xd03, 0xd04, 0xd0c, > + 0xd0d, 0xd10, 0xd11, 0xd44, 0xd45, 0xd48, 0xd49, 0xd4f, > + 0xd53, 0xd63, 0xd65, 0xd7f, 0xd81, 0xd83, 0xd84, 0xd96, > + 0xd99, 0xdb1, 0xdb2, 0xdbb, 0xdbc, 0xdbd, 0xdbf, 0xdc6, > + 0xdc9, 0xdca, 0xdce, 0xdd4, 0xdd5, 0xdd6, 0xdd7, 0xddf, > + 0xde5, 0xdef, 0xdf1, 0xdf4, 0xe00, 0xe3a, 0xe3e, 0xe5b, > + 0xe80, 0xe82, 0xe83, 0xe84, 0xe85, 0xe8a, 0xe8b, 0xea3, > + 0xea4, 0xea5, 0xea6, 0xebd, 0xebf, 0xec4, 0xec5, 0xec6, > + 0xec7, 0xecd, 0xecf, 0xed9, 0xedb, 0xedf, 0xeff, 0xf47, > + 0xf48, 0xf6c, 0xf70, 0xf97, 0xf98, 0xfbc, 0xfbd, 0xfcc, > + 0xfcd, 0xfda, 0xfff, 0x10c5, 0x10c6, 0x10c7, 0x10cc, 0x10cd, > + 0x10cf, 0x1248, 0x1249, 0x124d, 0x124f, 0x1256, 0x1257, 0x1258, > + 0x1259, 0x125d, 0x125f, 0x1288, 0x1289, 0x128d, 0x128f, 0x12b0, > + 0x12b1, 0x12b5, 0x12b7, 0x12be, 0x12bf, 0x12c0, 0x12c1, 0x12c5, > + 0x12c7, 0x12d6, 0x12d7, 0x1310, 0x1311, 0x1315, 0x1317, 0x135a, > + 0x135c, 0x137c, 0x137f, 0x1399, 0x139f, 0x13f5, 0x13f7, 0x13fd, > + 0x13ff, 0x169c, 0x169f, 0x16f8, 0x16ff, 0x170c, 0x170d, 0x1714, > + 0x171f, 0x1736, 0x173f, 0x1753, 0x175f, 0x176c, 0x176d, 0x1770, > + 0x1771, 0x1773, 0x177f, 0x17dd, 0x17df, 0x17e9, 0x17ef, 0x17f9, > + 0x17ff, 0x180d, 0x180f, 0x1819, 0x181f, 0x1878, 0x187f, 0x18aa, > + 0x18af, 0x18f5, 0x18ff, 0x191e, 0x191f, 0x192b, 0x192f, 0x193b, > + 0x193f, 0x1940, 0x1943, 0x196d, 0x196f, 0x1974, 0x197f, 0x19ab, > + 0x19af, 0x19c9, 0x19cf, 0x19da, 0x19dd, 0x1a1b, 0x1a1d, 0x1a5e, > + 0x1a5f, 0x1a7c, 0x1a7e, 0x1a89, 0x1a8f, 0x1a99, 0x1a9f, 0x1aad, > + 0x1aaf, 0x1abe, 0x1aff, 0x1b4b, 0x1b4f, 0x1b7c, 0x1b7f, 0x1bf3, > + 0x1bfb, 0x1c37, 0x1c3a, 0x1c49, 0x1c4c, 0x1c88, 0x1c8f, 0x1cba, > + 0x1cbc, 0x1cc7, 0x1ccf, 0x1cfa, 0x1cff, 0x1df9, 0x1dfa, 0x1f15, > + 0x1f17, 0x1f1d, 0x1f1f, 0x1f45, 0x1f47, 0x1f4d, 0x1f4f, 0x1f57, > + 0x1f58, 0x1f59, 0x1f5a, 0x1f5b, 0x1f5c, 0x1f5d, 0x1f5e, 0x1f7d, > + 0x1f7f, 0x1fb4, 0x1fb5, 0x1fc4, 0x1fc5, 0x1fd3, 0x1fd5, 0x1fdb, > + 0x1fdc, 0x1fef, 0x1ff1, 0x1ff4, 0x1ff5, 0x1ffe, 0x1fff, 0x200a, > + 0x200f, 0x2029, 0x202e, 0x205f, 0x206f, 0x2071, 0x2073, 0x208e, > + 0x208f, 0x209c, 0x209f, 0x20bf, 0x20cf, 0x20f0, 0x20ff, 0x218b, > + 0x218f, 0x2426, 0x243f, 0x244a, 0x245f, 0x2b73, 0x2b75, 0x2b95, > + 0x2b97, 0x2c2e, 0x2c2f, 0x2c5e, 0x2c5f, 0x2cf3, 0x2cf8, 0x2d25, > + 0x2d26, 0x2d27, 0x2d2c, 0x2d2d, 0x2d2f, 0x2d67, 0x2d6e, 0x2d70, > + 0x2d7e, 0x2d96, 0x2d9f, 0x2da6, 0x2da7, 0x2dae, 0x2daf, 0x2db6, > + 0x2db7, 0x2dbe, 0x2dbf, 0x2dc6, 0x2dc7, 0x2dce, 0x2dcf, 0x2dd6, > + 0x2dd7, 0x2dde, 0x2ddf, 0x2e4f, 0x2e7f, 0x2e99, 0x2e9a, 0x2ef3, > + 0x2eff, 0x2fd5, 0x2fef, 0x2ffb, 0x2fff, 0x303f, 0x3040, 0x3096, > + 0x3098, 0x30ff, 0x3104, 0x312f, 0x3130, 0x318e, 0x318f, 0x31ba, > + 0x31bf, 0x31e3, 0x31ef, 0x321e, 0x321f, 0x4db5, 0x4dbf, 0x9fef, > + 0x9fff, 0xa48c, 0xa48f, 0xa4c6, 0xa4cf, 0xa62b, 0xa63f, 0xa6f7, > + 0xa6ff, 0xa7bf, 0xa7c1, 0xa7c6, 0xa7f6, 0xa82b, 0xa82f, 0xa839, > + 0xa83f, 0xa877, 0xa87f, 0xa8c5, 0xa8cd, 0xa8d9, 0xa8df, 0xa953, > + 0xa95e, 0xa97c, 0xa97f, 0xa9cd, 0xa9ce, 0xa9d9, 0xa9dd, 0xa9fe, > + 0xa9ff, 0xaa36, 0xaa3f, 0xaa4d, 0xaa4f, 0xaa59, 0xaa5b, 0xaac2, > + 0xaada, 0xaaf6, 0xab00, 0xab06, 0xab08, 0xab0e, 0xab10, 0xab16, > + 0xab1f, 0xab26, 0xab27, 0xab2e, 0xab2f, 0xab67, 0xab6f, 0xabed, > + 0xabef, 0xabf9, 0xabff, 0xd7a3, 0xd7af, 0xd7c6, 0xd7ca, 0xd7fb, > + 0xf8ff, 0xfa6d, 0xfa6f, 0xfad9, 0xfaff, 0xfb06, 0xfb12, 0xfb17, > + 0xfb1c, 0xfb36, 0xfb37, 0xfb3c, 0xfb3d, 0xfb3e, 0xfb3f, 0xfb41, > + 0xfb42, 0xfb44, 0xfb45, 0xfbc1, 0xfbd2, 0xfd3f, 0xfd4f, 0xfd8f, > + 0xfd91, 0xfdc7, 0xfdef, 0xfdfd, 0xfdff, 0xfe19, 0xfe1f, 0xfe52, > + 0xfe53, 0xfe66, 0xfe67, 0xfe6b, 0xfe6f, 0xfe74, 0xfe75, 0xfefc, > + 0xff00, 0xffbe, 0xffc1, 0xffc7, 0xffc9, 0xffcf, 0xffd1, 0xffd7, > + 0xffd9, 0xffdc, 0xffdf, 0xffe6, 0xffe7, 0xffee, 0xfffb, 0xfffd, > + 0xffff, 0x1000b, 0x1000c, 0x10026, 0x10027, 0x1003a, 0x1003b, 0x1003d, > + 0x1003e, 0x1004d, 0x1004f, 0x1005d, 0x1007f, 0x100fa, 0x100ff, 0x10102, > + 0x10106, 0x10133, 0x10136, 0x1018e, 0x1018f, 0x1019b, 0x1019f, 0x101a0, > + 0x101cf, 0x101fd, 0x1027f, 0x1029c, 0x1029f, 0x102d0, 0x102df, 0x102fb, > + 0x102ff, 0x10323, 0x1032c, 0x1034a, 0x1034f, 0x1037a, 0x1037f, 0x1039d, > + 0x1039e, 0x103c3, 0x103c7, 0x103d5, 0x103ff, 0x1049d, 0x1049f, 0x104a9, > + 0x104af, 0x104d3, 0x104d7, 0x104fb, 0x104ff, 0x10527, 0x1052f, 0x10563, > + 0x1056e, 0x1056f, 0x105ff, 0x10736, 0x1073f, 0x10755, 0x1075f, 0x10767, > + 0x107ff, 0x10805, 0x10807, 0x10808, 0x10809, 0x10835, 0x10836, 0x10838, > + 0x1083b, 0x1083c, 0x1083e, 0x10855, 0x10856, 0x1089e, 0x108a6, 0x108af, > + 0x108df, 0x108f2, 0x108f3, 0x108f5, 0x108fa, 0x1091b, 0x1091e, 0x10939, > + 0x1093e, 0x1093f, 0x1097f, 0x109b7, 0x109bb, 0x109cf, 0x109d1, 0x10a03, > + 0x10a04, 0x10a06, 0x10a0b, 0x10a13, 0x10a14, 0x10a17, 0x10a18, 0x10a35, > + 0x10a37, 0x10a3a, 0x10a3e, 0x10a48, 0x10a4f, 0x10a58, 0x10a5f, 0x10a9f, > + 0x10abf, 0x10ae6, 0x10aea, 0x10af6, 0x10aff, 0x10b35, 0x10b38, 0x10b55, > + 0x10b57, 0x10b72, 0x10b77, 0x10b91, 0x10b98, 0x10b9c, 0x10ba8, 0x10baf, > + 0x10bff, 0x10c48, 0x10c7f, 0x10cb2, 0x10cbf, 0x10cf2, 0x10cf9, 0x10d27, > + 0x10d2f, 0x10d39, 0x10e5f, 0x10e7e, 0x10eff, 0x10f27, 0x10f2f, 0x10f59, > + 0x10fdf, 0x10ff6, 0x10fff, 0x1104d, 0x11051, 0x1106f, 0x1107e, 0x110bc, > + 0x110bd, 0x110c1, 0x110cf, 0x110e8, 0x110ef, 0x110f9, 0x110ff, 0x11134, > + 0x11135, 0x11146, 0x1114f, 0x11176, 0x1117f, 0x111cd, 0x111cf, 0x111df, > + 0x111e0, 0x111f4, 0x111ff, 0x11211, 0x11212, 0x1123e, 0x1127f, 0x11286, > + 0x11287, 0x11288, 0x11289, 0x1128d, 0x1128e, 0x1129d, 0x1129e, 0x112a9, > + 0x112af, 0x112ea, 0x112ef, 0x112f9, 0x112ff, 0x11303, 0x11304, 0x1130c, > + 0x1130e, 0x11310, 0x11312, 0x11328, 0x11329, 0x11330, 0x11331, 0x11333, > + 0x11334, 0x11339, 0x1133a, 0x11344, 0x11346, 0x11348, 0x1134a, 0x1134d, > + 0x1134f, 0x11350, 0x11356, 0x11357, 0x1135c, 0x11363, 0x11365, 0x1136c, > + 0x1136f, 0x11374, 0x113ff, 0x11459, 0x1145a, 0x1145b, 0x1145c, 0x1145f, > + 0x1147f, 0x114c7, 0x114cf, 0x114d9, 0x1157f, 0x115b5, 0x115b7, 0x115dd, > + 0x115ff, 0x11644, 0x1164f, 0x11659, 0x1165f, 0x1166c, 0x1167f, 0x116b8, > + 0x116bf, 0x116c9, 0x116ff, 0x1171a, 0x1171c, 0x1172b, 0x1172f, 0x1173f, > + 0x117ff, 0x1183b, 0x1189f, 0x118f2, 0x118fe, 0x118ff, 0x1199f, 0x119a7, > + 0x119a9, 0x119d7, 0x119d9, 0x119e4, 0x119ff, 0x11a47, 0x11a4f, 0x11aa2, > + 0x11abf, 0x11af8, 0x11bff, 0x11c08, 0x11c09, 0x11c36, 0x11c37, 0x11c45, > + 0x11c4f, 0x11c6c, 0x11c6f, 0x11c8f, 0x11c91, 0x11ca7, 0x11ca8, 0x11cb6, > + 0x11cff, 0x11d06, 0x11d07, 0x11d09, 0x11d0a, 0x11d36, 0x11d39, 0x11d3a, > + 0x11d3b, 0x11d3d, 0x11d3e, 0x11d47, 0x11d4f, 0x11d59, 0x11d5f, 0x11d65, > + 0x11d66, 0x11d68, 0x11d69, 0x11d8e, 0x11d8f, 0x11d91, 0x11d92, 0x11d98, > + 0x11d9f, 0x11da9, 0x11edf, 0x11ef8, 0x11fbf, 0x11ff1, 0x11ffe, 0x12399, > + 0x123ff, 0x1246e, 0x1246f, 0x12474, 0x1247f, 0x12543, 0x12fff, 0x1342e, > + 0x143ff, 0x14646, 0x167ff, 0x16a38, 0x16a3f, 0x16a5e, 0x16a5f, 0x16a69, > + 0x16a6d, 0x16a6f, 0x16acf, 0x16aed, 0x16aef, 0x16af5, 0x16aff, 0x16b45, > + 0x16b4f, 0x16b59, 0x16b5a, 0x16b61, 0x16b62, 0x16b77, 0x16b7c, 0x16b8f, > + 0x16e3f, 0x16e9a, 0x16eff, 0x16f4a, 0x16f4e, 0x16f87, 0x16f8e, 0x16f9f, > + 0x16fdf, 0x16fe3, 0x16fff, 0x187f7, 0x187ff, 0x18af2, 0x1afff, 0x1b11e, > + 0x1b14f, 0x1b152, 0x1b163, 0x1b167, 0x1b16f, 0x1b2fb, 0x1bbff, 0x1bc6a, > + 0x1bc6f, 0x1bc7c, 0x1bc7f, 0x1bc88, 0x1bc8f, 0x1bc99, 0x1bc9b, 0x1bc9f, > + 0x1cfff, 0x1d0f5, 0x1d0ff, 0x1d126, 0x1d128, 0x1d172, 0x1d17a, 0x1d1e8, > + 0x1d1ff, 0x1d245, 0x1d2df, 0x1d2f3, 0x1d2ff, 0x1d356, 0x1d35f, 0x1d378, > + 0x1d3ff, 0x1d454, 0x1d455, 0x1d49c, 0x1d49d, 0x1d49f, 0x1d4a1, 0x1d4a2, > + 0x1d4a4, 0x1d4a6, 0x1d4a8, 0x1d4ac, 0x1d4ad, 0x1d4b9, 0x1d4ba, 0x1d4bb, > + 0x1d4bc, 0x1d4c3, 0x1d4c4, 0x1d505, 0x1d506, 0x1d50a, 0x1d50c, 0x1d514, > + 0x1d515, 0x1d51c, 0x1d51d, 0x1d539, 0x1d53a, 0x1d53e, 0x1d53f, 0x1d544, > + 0x1d545, 0x1d546, 0x1d549, 0x1d550, 0x1d551, 0x1d6a5, 0x1d6a7, 0x1d7cb, > + 0x1d7cd, 0x1da8b, 0x1da9a, 0x1da9f, 0x1daa0, 0x1daaf, 0x1dfff, 0x1e006, > + 0x1e007, 0x1e018, 0x1e01a, 0x1e021, 0x1e022, 0x1e024, 0x1e025, 0x1e02a, > + 0x1e0ff, 0x1e12c, 0x1e12f, 0x1e13d, 0x1e13f, 0x1e149, 0x1e14d, 0x1e14f, > + 0x1e2bf, 0x1e2f9, 0x1e2fe, 0x1e2ff, 0x1e7ff, 0x1e8c4, 0x1e8c6, 0x1e8d6, > + 0x1e8ff, 0x1e94b, 0x1e94f, 0x1e959, 0x1e95d, 0x1e95f, 0x1ec70, 0x1ecb4, > + 0x1ed00, 0x1ed3d, 0x1edff, 0x1ee03, 0x1ee04, 0x1ee1f, 0x1ee20, 0x1ee22, > + 0x1ee23, 0x1ee24, 0x1ee26, 0x1ee27, 0x1ee28, 0x1ee32, 0x1ee33, 0x1ee37, > + 0x1ee38, 0x1ee39, 0x1ee3a, 0x1ee3b, 0x1ee41, 0x1ee42, 0x1ee46, 0x1ee47, > + 0x1ee48, 0x1ee49, 0x1ee4a, 0x1ee4b, 0x1ee4c, 0x1ee4f, 0x1ee50, 0x1ee52, > + 0x1ee53, 0x1ee54, 0x1ee56, 0x1ee57, 0x1ee58, 0x1ee59, 0x1ee5a, 0x1ee5b, > + 0x1ee5c, 0x1ee5d, 0x1ee5e, 0x1ee5f, 0x1ee60, 0x1ee62, 0x1ee63, 0x1ee64, > + 0x1ee66, 0x1ee6a, 0x1ee6b, 0x1ee72, 0x1ee73, 0x1ee77, 0x1ee78, 0x1ee7c, > + 0x1ee7d, 0x1ee7e, 0x1ee7f, 0x1ee89, 0x1ee8a, 0x1ee9b, 0x1eea0, 0x1eea3, > + 0x1eea4, 0x1eea9, 0x1eeaa, 0x1eebb, 0x1eeef, 0x1eef1, 0x1efff, 0x1f02b, > + 0x1f02f, 0x1f093, 0x1f09f, 0x1f0ae, 0x1f0b0, 0x1f0bf, 0x1f0c0, 0x1f0cf, > + 0x1f0d0, 0x1f0f5, 0x1f0ff, 0x1f10c, 0x1f10f, 0x1f16c, 0x1f16f, 0x1f1ac, > + 0x1f1e5, 0x1f202, 0x1f20f, 0x1f23b, 0x1f23f, 0x1f248, 0x1f24f, 0x1f251, > + 0x1f25f, 0x1f265, 0x1f2ff, 0x1f6d5, 0x1f6df, 0x1f6ec, 0x1f6ef, 0x1f6fa, > + 0x1f6ff, 0x1f773, 0x1f77f, 0x1f7d8, 0x1f7df, 0x1f7eb, 0x1f7ff, 0x1f80b, > + 0x1f80f, 0x1f847, 0x1f84f, 0x1f859, 0x1f85f, 0x1f887, 0x1f88f, 0x1f8ad, > + 0x1f8ff, 0x1f90b, 0x1f90c, 0x1f971, 0x1f972, 0x1f976, 0x1f979, 0x1f9a2, > + 0x1f9a4, 0x1f9aa, 0x1f9ad, 0x1f9ca, 0x1f9cc, 0x1fa53, 0x1fa5f, 0x1fa6d, > + 0x1fa6f, 0x1fa73, 0x1fa77, 0x1fa7a, 0x1fa7f, 0x1fa82, 0x1fa8f, 0x1fa95, > + 0x1ffff, 0x2a6d6, 0x2a6ff, 0x2b734, 0x2b73f, 0x2b81d, 0x2b81f, 0x2cea1, > + 0x2ceaf, 0x2ebe0, 0x2f7ff, 0x2fa1d, 0xe00ff, 0xe01ef, 0x10fffe, > +}; > + > +static const bool is_printable[] = { > + 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, > + 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, > + 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, > + 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, > + 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, > + 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, > + 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, > + 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, > + 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, > + 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, > + 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, > + 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, > + 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, > + 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, > + 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, > + 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, > + 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, > + 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, > + 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, > + 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, > + 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, > + 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, > + 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, > + 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, > + 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, > + 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, > + 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, > + 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, > + 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, > + 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, > + 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, > + 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, > + 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, > + 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, > + 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, > + 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, > + 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, > + 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, > + 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, > + 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, > + 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, > + 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, > + 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, > + 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, > + 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, > + 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, > + 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, > + 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, > + 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, > + 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, > + 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, > + 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, > + 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, > + 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, > + 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, > + 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, > +}; > -- > 2.26.3 >
diff --git a/contrib/unicode/gen-box-drawing-chars.py b/contrib/unicode/gen-box-drawing-chars.py new file mode 100755 index 00000000000..9a55266ab84 --- /dev/null +++ b/contrib/unicode/gen-box-drawing-chars.py @@ -0,0 +1,94 @@ +#!/usr/bin/env python3 +# +# Script to generate gcc/text-art/box-drawing-chars.inc +# +# This file is part of GCC. +# +# GCC is free software; you can redistribute it and/or modify it under +# the terms of the GNU General Public License as published by the Free +# Software Foundation; either version 3, or (at your option) any later +# version. +# +# GCC is distributed in the hope that it will be useful, but WITHOUT ANY +# WARRANTY; without even the implied warranty of MERCHANTABILITY or +# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License +# for more details. +# +# You should have received a copy of the GNU General Public License +# along with GCC; see the file COPYING3. If not see +# <http://www.gnu.org/licenses/>. */ + +import unicodedata + +def get_box_drawing_char_name(up: bool, + down: bool, + left: bool, + right: bool) -> str: + if 0: + print(f'{locals()=}') + if up and down: + vertical = True + up = False + down = False + else: + vertical = False + + if left and right: + horizontal = True + left = False + right = False + else: + horizontal = False + + weights = [] + heavy = [] + light = [] + dirs = [] + for dir_name in ('up', 'down', 'vertical', 'left', 'right', 'horizontal'): + val = locals()[dir_name] + if val: + dirs.append(dir_name.upper()) + + if not dirs: + return 'SPACE' + + name = 'BOX DRAWINGS' + #print(f'{light=} {heavy=}') + + if 0: + print(dirs) + + def weights_frag(weight: str, dirs: list, prefix: bool): + """ + Generate a fragment where all directions share the same weight, e.g.: + 'HEAVY HORIZONTAL' + 'DOWN LIGHT' + 'LEFT DOWN HEAVY' + 'HEAVY DOWN AND RIGHT' + """ + assert len(dirs) >= 1 + assert len(dirs) <= 2 + if prefix: + return f' {weight} ' + (' AND '.join(dirs)) + else: + return ' ' + (' '.join(dirs)) + f' {weight}' + + assert(len(dirs) >= 1 and len(dirs) <= 2) + name += weights_frag('LIGHT', dirs, True) + + return name + +print('/* Generated by contrib/unicode/gen-box-drawing-chars.py. */') +print() +for i in range(16): + up = (i & 8) + down = (i & 4) + left = (i & 2) + right = (i & 1) + name = get_box_drawing_char_name(up, down, left, right) + if i < 15: + trailing_comma = ',' + else: + trailing_comma = ' ' + unichar = unicodedata.lookup(name) + print(f'0x{ord(unichar):04X}{trailing_comma} /* "{unichar}": U+{ord(unichar):04X}: {name} */') diff --git a/contrib/unicode/gen-combining-chars.py b/contrib/unicode/gen-combining-chars.py new file mode 100755 index 00000000000..fb5ef50ba4c --- /dev/null +++ b/contrib/unicode/gen-combining-chars.py @@ -0,0 +1,75 @@ +#!/usr/bin/env python3 +# +# Script to generate libcpp/combining-chars.inc +# +# This file is part of GCC. +# +# GCC is free software; you can redistribute it and/or modify it under +# the terms of the GNU General Public License as published by the Free +# Software Foundation; either version 3, or (at your option) any later +# version. +# +# GCC is distributed in the hope that it will be useful, but WITHOUT ANY +# WARRANTY; without even the implied warranty of MERCHANTABILITY or +# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License +# for more details. +# +# You should have received a copy of the GNU General Public License +# along with GCC; see the file COPYING3. If not see +# <http://www.gnu.org/licenses/>. */ + +from pprint import pprint +import unicodedata + +def is_combining_char(code_point) -> bool: + return unicodedata.combining(chr(code_point)) != 0 + +class Range: + def __init__(self, start, end, value): + self.start = start + self.end = end + self.value = value + + def __repr__(self): + return f'Range({self.start:x}, {self.end:x}, {self.value})' + +def make_ranges(value_callback): + ranges = [] + for code_point in range(0x10FFFF): + value = is_combining_char(code_point) + if 0: + print(f'{code_point=:x} {value=}') + if ranges and ranges[-1].value == value: + # Extend current range + ranges[-1].end = code_point + else: + # Start a new range + ranges.append(Range(code_point, code_point, value)) + return ranges + +ranges = make_ranges(is_combining_char) +if 0: + pprint(ranges) + +print(f"/* Generated by contrib/unicode/gen-combining-chars.py") +print(f" using version {unicodedata.unidata_version}" + " of the Unicode standard. */") +print("\nstatic const cppchar_t combining_range_ends[] = {", end="") +for i, r in enumerate(ranges): + if i % 8: + print(" ", end="") + else: + print("\n ", end="") + print("0x%x," % r.end, end="") +print("\n};\n") +print("static const bool is_combining[] = {", end="") +for i, r in enumerate(ranges): + if i % 24: + print(" ", end="") + else: + print("\n ", end="") + if r.value: + print("1,", end="") + else: + print("0,", end="") +print("\n};") diff --git a/contrib/unicode/gen-printable-chars.py b/contrib/unicode/gen-printable-chars.py new file mode 100755 index 00000000000..7684c086638 --- /dev/null +++ b/contrib/unicode/gen-printable-chars.py @@ -0,0 +1,77 @@ +#!/usr/bin/env python3 +# +# Script to generate libcpp/printable-chars.inc +# +# This file is part of GCC. +# +# GCC is free software; you can redistribute it and/or modify it under +# the terms of the GNU General Public License as published by the Free +# Software Foundation; either version 3, or (at your option) any later +# version. +# +# GCC is distributed in the hope that it will be useful, but WITHOUT ANY +# WARRANTY; without even the implied warranty of MERCHANTABILITY or +# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License +# for more details. +# +# You should have received a copy of the GNU General Public License +# along with GCC; see the file COPYING3. If not see +# <http://www.gnu.org/licenses/>. */ + +from pprint import pprint +import unicodedata + +def is_printable_char(code_point) -> bool: + category = unicodedata.category(chr(code_point)) + # "Cc" is "control" and "Cf" is "format" + return category[0] != 'C' + +class Range: + def __init__(self, start, end, value): + self.start = start + self.end = end + self.value = value + + def __repr__(self): + return f'Range({self.start:x}, {self.end:x}, {self.value})' + +def make_ranges(value_callback): + ranges = [] + for code_point in range(0x10FFFF): + value = is_printable_char(code_point) + if 0: + print(f'{code_point=:x} {value=}') + if ranges and ranges[-1].value == value: + # Extend current range + ranges[-1].end = code_point + else: + # Start a new range + ranges.append(Range(code_point, code_point, value)) + return ranges + +ranges = make_ranges(is_printable_char) +if 0: + pprint(ranges) + +print(f"/* Generated by contrib/unicode/gen-printable-chars.py") +print(f" using version {unicodedata.unidata_version}" + " of the Unicode standard. */") +print("\nstatic const cppchar_t printable_range_ends[] = {", end="") +for i, r in enumerate(ranges): + if i % 8: + print(" ", end="") + else: + print("\n ", end="") + print("0x%x," % r.end, end="") +print("\n};\n") +print("static const bool is_printable[] = {", end="") +for i, r in enumerate(ranges): + if i % 24: + print(" ", end="") + else: + print("\n ", end="") + if r.value: + print("1,", end="") + else: + print("0,", end="") +print("\n};") diff --git a/gcc/Makefile.in b/gcc/Makefile.in index 1d39e6dd3f8..c1e7257ed24 100644 --- a/gcc/Makefile.in +++ b/gcc/Makefile.in @@ -1781,7 +1781,16 @@ OBJS-libcommon = diagnostic-spec.o diagnostic.o diagnostic-color.o \ json.o \ sbitmap.o \ vec.o input.o hash-table.o ggc-none.o memory-block.o \ - selftest.o selftest-diagnostic.o sort.o + selftest.o selftest-diagnostic.o sort.o \ + text-art/box-drawing.o \ + text-art/canvas.o \ + text-art/ruler.o \ + text-art/selftests.o \ + text-art/style.o \ + text-art/styled-string.o \ + text-art/table.o \ + text-art/theme.o \ + text-art/widget.o # Objects in libcommon-target.a, used by drivers and by the core # compiler and containing target-dependent code. diff --git a/gcc/color-macros.h b/gcc/color-macros.h index fcd79d09c01..9688f92110a 100644 --- a/gcc/color-macros.h +++ b/gcc/color-macros.h @@ -92,6 +92,14 @@ along with GCC; see the file COPYING3. If not see #define COLOR_FG_MAGENTA "35" #define COLOR_FG_CYAN "36" #define COLOR_FG_WHITE "37" +#define COLOR_FG_BRIGHT_BLACK "90" +#define COLOR_FG_BRIGHT_RED "91" +#define COLOR_FG_BRIGHT_GREEN "92" +#define COLOR_FG_BRIGHT_YELLOW "93" +#define COLOR_FG_BRIGHT_BLUE "94" +#define COLOR_FG_BRIGHT_MAGENTA "95" +#define COLOR_FG_BRIGHT_CYAN "96" +#define COLOR_FG_BRIGHT_WHITE "97" #define COLOR_BG_BLACK "40" #define COLOR_BG_RED "41" #define COLOR_BG_GREEN "42" @@ -100,6 +108,14 @@ along with GCC; see the file COPYING3. If not see #define COLOR_BG_MAGENTA "45" #define COLOR_BG_CYAN "46" #define COLOR_BG_WHITE "47" +#define COLOR_BG_BRIGHT_BLACK "100" +#define COLOR_BG_BRIGHT_RED "101" +#define COLOR_BG_BRIGHT_GREEN "102" +#define COLOR_BG_BRIGHT_YELLOW "103" +#define COLOR_BG_BRIGHT_BLUE "104" +#define COLOR_BG_BRIGHT_MAGENTA "105" +#define COLOR_BG_BRIGHT_CYAN "106" +#define COLOR_BG_BRIGHT_WHITE "107" #define SGR_START "\33[" #define SGR_END "m\33[K" #define SGR_SEQ(str) SGR_START str SGR_END diff --git a/gcc/common.opt b/gcc/common.opt index a28ca13385a..b3c82b8607c 100644 --- a/gcc/common.opt +++ b/gcc/common.opt @@ -1502,6 +1502,29 @@ fdiagnostics-show-path-depths Common Var(flag_diagnostics_show_path_depths) Init(0) Show stack depths of events in paths. +fdiagnostics-text-art-charset= +Driver Common Joined RejectNegative Var(flag_diagnostics_text_art_charset) Enum(diagnostic_text_art_charset) Init(DIAGNOSTICS_TEXT_ART_CHARSET_EMOJI) +-fdiagnostics-text-art-charset=[none|ascii|unicode|emoji] Determine which characters to use in text arg diagrams. + +; Required for these enum values. +SourceInclude +diagnostic-text-art.h + +Enum +Name(diagnostic_text_art_charset) Type(int) + +EnumValue +Enum(diagnostic_text_art_charset) String(none) Value(DIAGNOSTICS_TEXT_ART_CHARSET_NONE) + +EnumValue +Enum(diagnostic_text_art_charset) String(ascii) Value(DIAGNOSTICS_TEXT_ART_CHARSET_ASCII) + +EnumValue +Enum(diagnostic_text_art_charset) String(unicode) Value(DIAGNOSTICS_TEXT_ART_CHARSET_UNICODE) + +EnumValue +Enum(diagnostic_text_art_charset) String(emoji) Value(DIAGNOSTICS_TEXT_ART_CHARSET_EMOJI) + fdiagnostics-minimum-margin-width= Common Joined UInteger Var(diagnostics_minimum_margin_width) Init(6) Set minimum width of left margin of source code when showing source. diff --git a/gcc/configure b/gcc/configure index 5f67808b774..e061d2b1949 100755 --- a/gcc/configure +++ b/gcc/configure @@ -33995,7 +33995,7 @@ $as_echo "$as_me: executing $ac_file commands" >&6;} "depdir":C) $SHELL $ac_aux_dir/mkinstalldirs $DEPDIR ;; "gccdepdir":C) ${CONFIG_SHELL-/bin/sh} $ac_aux_dir/mkinstalldirs build/$DEPDIR - for lang in $subdirs c-family common analyzer rtl-ssa + for lang in $subdirs c-family common analyzer text-art rtl-ssa do ${CONFIG_SHELL-/bin/sh} $ac_aux_dir/mkinstalldirs $lang/$DEPDIR done ;; diff --git a/gcc/configure.ac b/gcc/configure.ac index cc8dd9e20bf..350d245c89f 100644 --- a/gcc/configure.ac +++ b/gcc/configure.ac @@ -1384,7 +1384,7 @@ AC_CHECK_HEADERS(ext/hash_map) ZW_CREATE_DEPDIR AC_CONFIG_COMMANDS([gccdepdir],[ ${CONFIG_SHELL-/bin/sh} $ac_aux_dir/mkinstalldirs build/$DEPDIR - for lang in $subdirs c-family common analyzer rtl-ssa + for lang in $subdirs c-family common analyzer text-art rtl-ssa do ${CONFIG_SHELL-/bin/sh} $ac_aux_dir/mkinstalldirs $lang/$DEPDIR done], [subdirs="$subdirs" ac_aux_dir=$ac_aux_dir DEPDIR=$DEPDIR]) diff --git a/gcc/diagnostic-diagram.h b/gcc/diagnostic-diagram.h new file mode 100644 index 00000000000..fc923c512ed --- /dev/null +++ b/gcc/diagnostic-diagram.h @@ -0,0 +1,51 @@ +/* Support for diagrams within diagnostics. + Copyright (C) 2023 Free Software Foundation, Inc. + Contributed by David Malcolm <dmalcolm@redhat.com> + +This file is part of GCC. + +GCC is free software; you can redistribute it and/or modify it under +the terms of the GNU General Public License as published by the Free +Software Foundation; either version 3, or (at your option) any later +version. + +GCC is distributed in the hope that it will be useful, but WITHOUT ANY +WARRANTY; without even the implied warranty of MERCHANTABILITY or +FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License +for more details. + +You should have received a copy of the GNU General Public License +along with GCC; see the file COPYING3. If not see +<http://www.gnu.org/licenses/>. */ + +#ifndef GCC_DIAGNOSTIC_DIAGRAM_H +#define GCC_DIAGNOSTIC_DIAGRAM_H + +namespace text_art +{ + class canvas; +} // namespace text_art + +/* A text art diagram, along with an "alternative text" string + describing it. */ + +class diagnostic_diagram +{ + public: + diagnostic_diagram (const text_art::canvas &canvas, + const char *alt_text) + : m_canvas (canvas), + m_alt_text (alt_text) + { + gcc_assert (alt_text); + } + + const text_art::canvas &get_canvas () const { return m_canvas; } + const char *get_alt_text () const { return m_alt_text; } + + private: + const text_art::canvas &m_canvas; + const char *const m_alt_text; +}; + +#endif /* ! GCC_DIAGNOSTIC_DIAGRAM_H */ diff --git a/gcc/diagnostic-format-json.cc b/gcc/diagnostic-format-json.cc index 694dddca9e8..539b98b5e74 100644 --- a/gcc/diagnostic-format-json.cc +++ b/gcc/diagnostic-format-json.cc @@ -324,6 +324,15 @@ json_file_final_cb (diagnostic_context *) free (filename); } +/* Callback for diagnostic_context::m_diagrams.m_emission_cb. */ + +static void +json_emit_diagram (diagnostic_context *, + const diagnostic_diagram &) +{ + /* No-op. */ +} + /* Populate CONTEXT in preparation for JSON output (either to stderr, or to a file). */ @@ -340,6 +349,7 @@ diagnostic_output_format_init_json (diagnostic_context *context) context->begin_group_cb = json_begin_group; context->end_group_cb = json_end_group; context->print_path = NULL; /* handled in json_end_diagnostic. */ + context->m_diagrams.m_emission_cb = json_emit_diagram; /* The metadata is handled in JSON format, rather than as text. */ context->show_cwe = false; diff --git a/gcc/diagnostic-format-sarif.cc b/gcc/diagnostic-format-sarif.cc index fd29ac2ca3b..ac2f5b844e3 100644 --- a/gcc/diagnostic-format-sarif.cc +++ b/gcc/diagnostic-format-sarif.cc @@ -29,6 +29,8 @@ along with GCC; see the file COPYING3. If not see #include "cpplib.h" #include "logical-location.h" #include "diagnostic-client-data-hooks.h" +#include "diagnostic-diagram.h" +#include "text-art/canvas.h" class sarif_builder; @@ -66,8 +68,13 @@ public: diagnostic_info *diagnostic, diagnostic_t orig_diag_kind, sarif_builder *builder); + void on_diagram (diagnostic_context *context, + const diagnostic_diagram &diagram, + sarif_builder *builder); private: + void add_related_location (json::object *location_obj); + json::array *m_related_locations_arr; }; @@ -135,7 +142,8 @@ public: void end_diagnostic (diagnostic_context *context, diagnostic_info *diagnostic, diagnostic_t orig_diag_kind); - + void emit_diagram (diagnostic_context *context, + const diagnostic_diagram &diagram); void end_group (); void flush_to_file (FILE *outf); @@ -144,6 +152,9 @@ public: json::object *make_location_object (const rich_location &rich_loc, const logical_location *logical_loc); json::object *make_message_object (const char *msg) const; + json::object * + make_message_object_for_diagram (diagnostic_context *context, + const diagnostic_diagram &diagram); private: sarif_result *make_result_object (diagnostic_context *context, @@ -261,12 +272,6 @@ sarif_result::on_nested_diagnostic (diagnostic_context *context, diagnostic_t /*orig_diag_kind*/, sarif_builder *builder) { - if (!m_related_locations_arr) - { - m_related_locations_arr = new json::array (); - set ("relatedLocations", m_related_locations_arr); - } - /* We don't yet generate meaningful logical locations for notes; sometimes these will related to current_function_decl, but often they won't. */ @@ -277,6 +282,39 @@ sarif_result::on_nested_diagnostic (diagnostic_context *context, pp_clear_output_area (context->printer); location_obj->set ("message", message_obj); + add_related_location (location_obj); +} + +/* Handle diagrams that occur within a diagnostic group. + The closest thing in SARIF seems to be to add a location to the + "releatedLocations" property (SARIF v2.1.0 section 3.27.22), + and to put the diagram into the "message" property of that location + (SARIF v2.1.0 section 3.28.5). */ + +void +sarif_result::on_diagram (diagnostic_context *context, + const diagnostic_diagram &diagram, + sarif_builder *builder) +{ + json::object *location_obj = new json::object (); + json::object *message_obj + = builder->make_message_object_for_diagram (context, diagram); + location_obj->set ("message", message_obj); + + add_related_location (location_obj); +} + +/* Add LOCATION_OBJ to this result's "relatedLocations" array, + creating it if it doesn't yet exist. */ + +void +sarif_result::add_related_location (json::object *location_obj) +{ + if (!m_related_locations_arr) + { + m_related_locations_arr = new json::array (); + set ("relatedLocations", m_related_locations_arr); + } m_related_locations_arr->append (location_obj); } @@ -348,6 +386,18 @@ sarif_builder::end_diagnostic (diagnostic_context *context, } } +/* Implementation of diagnostic_context::m_diagrams.m_emission_cb + for SARIF output. */ + +void +sarif_builder::emit_diagram (diagnostic_context *context, + const diagnostic_diagram &diagram) +{ + /* We must be within the emission of a top-level diagnostic. */ + gcc_assert (m_cur_group_result); + m_cur_group_result->on_diagram (context, diagram, this); +} + /* Implementation of "end_group_cb" for SARIF output. */ void @@ -1115,6 +1165,37 @@ sarif_builder::make_message_object (const char *msg) const return message_obj; } +/* Make a message object (SARIF v2.1.0 section 3.11) for DIAGRAM. + We emit the diagram as a code block within the Markdown part + of the message. */ + +json::object * +sarif_builder::make_message_object_for_diagram (diagnostic_context *context, + const diagnostic_diagram &diagram) +{ + json::object *message_obj = new json::object (); + + /* "text" property (SARIF v2.1.0 section 3.11.8). */ + message_obj->set ("text", new json::string (diagram.get_alt_text ())); + + char *saved_prefix = pp_take_prefix (context->printer); + pp_set_prefix (context->printer, NULL); + + /* "To produce a code block in Markdown, simply indent every line of + the block by at least 4 spaces or 1 tab." + Here we use 4 spaces. */ + diagram.get_canvas ().print_to_pp (context->printer, " "); + pp_set_prefix (context->printer, saved_prefix); + + /* "markdown" property (SARIF v2.1.0 section 3.11.9). */ + message_obj->set ("markdown", + new json::string (pp_formatted_text (context->printer))); + + pp_clear_output_area (context->printer); + + return message_obj; +} + /* Make a multiformatMessageString object (SARIF v2.1.0 section 3.12) for MSG. */ @@ -1630,6 +1711,16 @@ sarif_ice_handler (diagnostic_context *context) fnotice (stderr, "Internal compiler error:\n"); } +/* Callback for diagnostic_context::m_diagrams.m_emission_cb. */ + +static void +sarif_emit_diagram (diagnostic_context *context, + const diagnostic_diagram &diagram) +{ + gcc_assert (the_builder); + the_builder->emit_diagram (context, diagram); +} + /* Populate CONTEXT in preparation for SARIF output (either to stderr, or to a file). */ @@ -1645,6 +1736,7 @@ diagnostic_output_format_init_sarif (diagnostic_context *context) context->end_group_cb = sarif_end_group; context->print_path = NULL; /* handled in sarif_end_diagnostic. */ context->ice_handler_cb = sarif_ice_handler; + context->m_diagrams.m_emission_cb = sarif_emit_diagram; /* The metadata is handled in SARIF format, rather than as text. */ context->show_cwe = false; diff --git a/gcc/diagnostic-text-art.h b/gcc/diagnostic-text-art.h new file mode 100644 index 00000000000..a0d8a78f52a --- /dev/null +++ b/gcc/diagnostic-text-art.h @@ -0,0 +1,49 @@ +/* Copyright (C) 2023 Free Software Foundation, Inc. + Contributed by David Malcolm <dmalcolm@redhat.com>. + +This file is part of GCC. + +GCC is free software; you can redistribute it and/or modify it under +the terms of the GNU General Public License as published by the Free +Software Foundation; either version 3, or (at your option) any later +version. + +GCC is distributed in the hope that it will be useful, but WITHOUT ANY +WARRANTY; without even the implied warranty of MERCHANTABILITY or +FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License +for more details. + +You should have received a copy of the GNU General Public License +along with GCC; see the file COPYING3. If not see +<http://www.gnu.org/licenses/>. */ + +#ifndef GCC_DIAGNOSTIC_TEXT_ART_H +#define GCC_DIAGNOSTIC_TEXT_ART_H + +/* Values for -fdiagnostics-text-art-charset=. */ + +enum diagnostic_text_art_charset +{ + /* No text art diagrams shall be emitted. */ + DIAGNOSTICS_TEXT_ART_CHARSET_NONE, + + /* Use pure ASCII for text art diagrams. */ + DIAGNOSTICS_TEXT_ART_CHARSET_ASCII, + + /* Use ASCII + conservative use of other unicode characters + in text art diagrams. */ + DIAGNOSTICS_TEXT_ART_CHARSET_UNICODE, + + /* Use Emoji. */ + DIAGNOSTICS_TEXT_ART_CHARSET_EMOJI +}; + +const enum diagnostic_text_art_charset DIAGNOSTICS_TEXT_ART_CHARSET_DEFAULT + = DIAGNOSTICS_TEXT_ART_CHARSET_EMOJI; + +extern void +diagnostics_text_art_charset_init (diagnostic_context *context, + enum diagnostic_text_art_charset charset); + + +#endif /* ! GCC_DIAGNOSTIC_TEXT_ART_H */ diff --git a/gcc/diagnostic.cc b/gcc/diagnostic.cc index 0f093081161..7c2289f0634 100644 --- a/gcc/diagnostic.cc +++ b/gcc/diagnostic.cc @@ -35,11 +35,14 @@ along with GCC; see the file COPYING3. If not see #include "diagnostic-metadata.h" #include "diagnostic-path.h" #include "diagnostic-client-data-hooks.h" +#include "diagnostic-text-art.h" +#include "diagnostic-diagram.h" #include "edit-context.h" #include "selftest.h" #include "selftest-diagnostic.h" #include "opts.h" #include "cpplib.h" +#include "text-art/theme.h" #ifdef HAVE_TERMIOS_H # include <termios.h> @@ -244,6 +247,10 @@ diagnostic_initialize (diagnostic_context *context, int n_opts) context->ice_handler_cb = NULL; context->includes_seen = NULL; context->m_client_data_hooks = NULL; + context->m_diagrams.m_theme = NULL; + context->m_diagrams.m_emission_cb = NULL; + diagnostics_text_art_charset_init (context, + DIAGNOSTICS_TEXT_ART_CHARSET_DEFAULT); } /* Maybe initialize the color support. We require clients to do this @@ -320,6 +327,12 @@ diagnostic_finish (diagnostic_context *context) if (context->final_cb) context->final_cb (context); + if (context->m_diagrams.m_theme) + { + delete context->m_diagrams.m_theme; + context->m_diagrams.m_theme = NULL; + } + diagnostic_file_cache_fini (); XDELETEVEC (context->classify_diagnostic); @@ -2174,6 +2187,33 @@ internal_error_no_backtrace (const char *gmsgid, ...) gcc_unreachable (); } + +/* Emit DIAGRAM to CONTEXT, respecting the output format. */ + +void +diagnostic_emit_diagram (diagnostic_context *context, + const diagnostic_diagram &diagram) +{ + if (context->m_diagrams.m_theme == nullptr) + return; + + if (context->m_diagrams.m_emission_cb) + { + context->m_diagrams.m_emission_cb (context, diagram); + return; + } + + /* Default implementation. */ + char *saved_prefix = pp_take_prefix (context->printer); + pp_set_prefix (context->printer, NULL); + /* Use a newline before and after and a two-space indent + to make the diagram stand out a little from the wall of text. */ + pp_newline (context->printer); + diagram.get_canvas ().print_to_pp (context->printer, " "); + pp_newline (context->printer); + pp_set_prefix (context->printer, saved_prefix); + pp_flush (context->printer); +} /* Special case error functions. Most are implemented in terms of the above, or should be. */ @@ -2316,6 +2356,38 @@ diagnostic_output_format_init (diagnostic_context *context, } } +/* Initialize CONTEXT->m_diagrams based on CHARSET. + Specifically, make a text_art::theme object for m_diagrams.m_theme, + (or NULL for "no diagrams"). */ + +void +diagnostics_text_art_charset_init (diagnostic_context *context, + enum diagnostic_text_art_charset charset) +{ + delete context->m_diagrams.m_theme; + switch (charset) + { + default: + gcc_unreachable (); + + case DIAGNOSTICS_TEXT_ART_CHARSET_NONE: + context->m_diagrams.m_theme = NULL; + break; + + case DIAGNOSTICS_TEXT_ART_CHARSET_ASCII: + context->m_diagrams.m_theme = new text_art::ascii_theme (); + break; + + case DIAGNOSTICS_TEXT_ART_CHARSET_UNICODE: + context->m_diagrams.m_theme = new text_art::unicode_theme (); + break; + + case DIAGNOSTICS_TEXT_ART_CHARSET_EMOJI: + context->m_diagrams.m_theme = new text_art::emoji_theme (); + break; + } +} + /* Implementation of diagnostic_path::num_events vfunc for simple_diagnostic_path: simply get the number of events in the vec. */ diff --git a/gcc/diagnostic.h b/gcc/diagnostic.h index 9a51097f146..00b828f230d 100644 --- a/gcc/diagnostic.h +++ b/gcc/diagnostic.h @@ -24,6 +24,11 @@ along with GCC; see the file COPYING3. If not see #include "pretty-print.h" #include "diagnostic-core.h" +namespace text_art +{ + class theme; +} // namespace text_art + /* An enum for controlling what units to use for the column number when diagnostics are output, used by the -fdiagnostics-column-unit option. Tabs will be expanded or not according to the value of -ftabstop. The origin @@ -170,6 +175,7 @@ class edit_context; namespace json { class value; } class diagnostic_client_data_hooks; class logical_location; +class diagnostic_diagram; /* This data structure bundles altogether any information relevant to the context of a diagnostic message. */ @@ -417,6 +423,18 @@ struct diagnostic_context Used by SARIF output to give metadata about the client that's producing diagnostics. */ diagnostic_client_data_hooks *m_client_data_hooks; + + /* Support for diagrams. */ + struct + { + /* Theme to use when generating diagrams. + Can be NULL (if text art is disabled). */ + text_art::theme *m_theme; + + /* Callback for emitting diagrams. */ + void (*m_emission_cb) (diagnostic_context *context, + const diagnostic_diagram &diagram); + } m_diagrams; }; inline void @@ -619,4 +637,7 @@ extern bool warning_enabled_at (location_t, int); extern char *get_cwe_url (int cwe); +extern void diagnostic_emit_diagram (diagnostic_context *context, + const diagnostic_diagram &diagram); + #endif /* ! GCC_DIAGNOSTIC_H */ diff --git a/gcc/doc/invoke.texi b/gcc/doc/invoke.texi index 898a88ce33e..023a56a647e 100644 --- a/gcc/doc/invoke.texi +++ b/gcc/doc/invoke.texi @@ -316,7 +316,8 @@ Objective-C and Objective-C++ Dialects}. -fno-show-column -fdiagnostics-column-unit=@r{[}display@r{|}byte@r{]} -fdiagnostics-column-origin=@var{origin} --fdiagnostics-escape-format=@r{[}unicode@r{|}bytes@r{]}} +-fdiagnostics-escape-format=@r{[}unicode@r{|}bytes@r{]} +-fdiagnostics-text-art-charset=@r{[}none@r{|}ascii@r{|}unicode@r{|}emoji@r{]}} @item Warning Options @xref{Warning Options,,Options to Request or Suppress Warnings}. @@ -5066,7 +5067,8 @@ options: -fno-diagnostics-show-line-numbers -fdiagnostics-color=never -fdiagnostics-urls=never --fdiagnostics-path-format=separate-events} +-fdiagnostics-path-format=separate-events +-fdiagnostics-text-art-charset=none} In the future, if GCC changes the default appearance of its diagnostics, the corresponding option to disable the new behavior will be added to this list. @@ -5592,6 +5594,25 @@ Unicode characters. For the example above, the following will be printed: before<CF><80><BF>after @end smallexample +@opindex fdiagnostics-text-art-charset +@item -fdiagnostics-text-art-charset=@var{CHARSET} +Some diagnostics can contain ``text art'' diagrams: visualizations created +from text, intended to be viewed in a monospaced font. + +This option selects which characters should be used for printing such +diagrams, if any. @var{CHARSET} is @samp{none}, @samp{ascii}, @samp{unicode}, +or @samp{emoji}. + +The @samp{none} value suppresses the printing of such diagrams. +The @samp{ascii} value will ensure that such diagrams are pure ASCII +(``ASCII art''). The @samp{unicode} value will allow for conservative use of +unicode drawing characters (such as box-drawing characters). The @samp{emoji} +value further adds the possibility of emoji in the output (such as emitting +U+26A0 WARNING SIGN followed by U+FE0F VARIATION SELECTOR-16 to select the +emoji variant of the character). + +The default is @samp{emoji}. + @opindex fdiagnostics-format @item -fdiagnostics-format=@var{FORMAT} Select a different format for printing diagnostics. diff --git a/gcc/gcc.cc b/gcc/gcc.cc index 2ccca00d603..f9f0a7eaad4 100644 --- a/gcc/gcc.cc +++ b/gcc/gcc.cc @@ -46,6 +46,7 @@ compilation is specified by a string called a "spec". */ #include "spellcheck.h" #include "opts-jobserver.h" #include "common/common-target.h" +#include "diagnostic-text-art.h" @@ -4299,6 +4300,11 @@ driver_handle_option (struct gcc_options *opts, break; } + case OPT_fdiagnostics_text_art_charset_: + diagnostics_text_art_charset_init (dc, + (enum diagnostic_text_art_charset)value); + break; + case OPT_Wa_: { int prev, j; diff --git a/gcc/opts-common.cc b/gcc/opts-common.cc index 23ddcaa3b55..f0c5f483665 100644 --- a/gcc/opts-common.cc +++ b/gcc/opts-common.cc @@ -1068,6 +1068,7 @@ decode_cmdline_options_to_array (unsigned int argc, const char **argv, "-fdiagnostics-color=never", "-fdiagnostics-urls=never", "-fdiagnostics-path-format=separate-events", + "-fdiagnostics-text-art-charset=none" }; const int num_expanded = ARRAY_SIZE (expanded_args); opt_array_len += num_expanded - 1; diff --git a/gcc/opts.cc b/gcc/opts.cc index 86b94d62b58..3087bdac2c6 100644 --- a/gcc/opts.cc +++ b/gcc/opts.cc @@ -35,6 +35,7 @@ along with GCC; see the file COPYING3. If not see #include "version.h" #include "selftest.h" #include "file-prefix-map.h" +#include "diagnostic-text-art.h" /* In this file all option sets are explicit. */ #undef OPTION_SET_P @@ -2887,6 +2888,11 @@ common_handle_option (struct gcc_options *opts, break; } + case OPT_fdiagnostics_text_art_charset_: + diagnostics_text_art_charset_init (dc, + (enum diagnostic_text_art_charset)value); + break; + case OPT_fdiagnostics_parseable_fixits: dc->extra_output_kind = (value ? EXTRA_DIAGNOSTIC_OUTPUT_fixits_v1 diff --git a/gcc/pretty-print.cc b/gcc/pretty-print.cc index 7d294717f50..3d789a23812 100644 --- a/gcc/pretty-print.cc +++ b/gcc/pretty-print.cc @@ -1828,6 +1828,35 @@ pp_string (pretty_printer *pp, const char *str) pp_maybe_wrap_text (pp, str, str + strlen (str)); } +/* Append code point C to the output area of PRETTY-PRINTER, encoding it + as UTF-8. */ + +void +pp_unicode_character (pretty_printer *pp, unsigned c) +{ + static const uchar masks[6] = { 0x00, 0xC0, 0xE0, 0xF0, 0xF8, 0xFC }; + static const uchar limits[6] = { 0x80, 0xE0, 0xF0, 0xF8, 0xFC, 0xFE }; + size_t nbytes; + uchar buf[6], *p = &buf[6]; + + nbytes = 1; + if (c < 0x80) + *--p = c; + else + { + do + { + *--p = ((c & 0x3F) | 0x80); + c >>= 6; + nbytes++; + } + while (c >= 0x3F || (c & limits[nbytes-1])); + *--p = (c | masks[nbytes-1]); + } + + pp_append_r (pp, (const char *)p, nbytes); +} + /* Append the leading N characters of STRING to the output area of PRETTY-PRINTER, quoting in hexadecimal non-printable characters. Setting N = -1 is as if N were set to strlen (STRING). The STRING diff --git a/gcc/pretty-print.h b/gcc/pretty-print.h index 0230a289df5..369be6e7ba7 100644 --- a/gcc/pretty-print.h +++ b/gcc/pretty-print.h @@ -401,6 +401,7 @@ extern void pp_indent (pretty_printer *); extern void pp_newline (pretty_printer *); extern void pp_character (pretty_printer *, int); extern void pp_string (pretty_printer *, const char *); +extern void pp_unicode_character (pretty_printer *, unsigned); extern void pp_write_text_to_stream (pretty_printer *); extern void pp_write_text_as_dot_label_to_stream (pretty_printer *, bool); diff --git a/gcc/selftest-run-tests.cc b/gcc/selftest-run-tests.cc index 915f2129702..e2fc8f84b1b 100644 --- a/gcc/selftest-run-tests.cc +++ b/gcc/selftest-run-tests.cc @@ -28,6 +28,7 @@ along with GCC; see the file COPYING3. If not see #include "stringpool.h" #include "attribs.h" #include "analyzer/analyzer-selftests.h" +#include "text-art/selftests.h" /* This function needed to be split out from selftest.cc as it references tests from the whole source tree, and so is within @@ -118,6 +119,8 @@ selftest::run_tests () /* Run any lang-specific selftests. */ lang_hooks.run_lang_selftests (); + text_art_tests (); + /* Run the analyzer selftests (if enabled). */ ana::selftest::run_analyzer_selftests (); diff --git a/gcc/testsuite/gcc.dg/plugin/diagnostic-test-text-art-ascii-bw.c b/gcc/testsuite/gcc.dg/plugin/diagnostic-test-text-art-ascii-bw.c new file mode 100644 index 00000000000..e4239aab032 --- /dev/null +++ b/gcc/testsuite/gcc.dg/plugin/diagnostic-test-text-art-ascii-bw.c @@ -0,0 +1,57 @@ +/* { dg-additional-options "-fdiagnostics-text-art-charset=ascii -fdiagnostics-color=never" } */ + +int non_empty; + +/* { dg-begin-multiline-output "" } + + A + B + C + + { dg-end-multiline-output "" } */ + +/* { dg-begin-multiline-output "" } + + ♜ ♞ ♝ ♛ ♚ ♝ ♞ ♜ + ♟ ♟ ♟ ♟ ♟ ♟ ♟ ♟ + + + + + ♙ ♙ ♙ ♙ ♙ ♙ ♙ ♙ + ♖ ♘ ♗ ♕ ♔ ♗ ♘ ♖ + + { dg-end-multiline-output "" } */ + +/* { dg-begin-multiline-output "" } + + +--+ + |🙂| + +--+ + + { dg-end-multiline-output "" } */ +/* { dg-begin-multiline-output "" } + + +-------+-----+---------------+---------------------+-----------------------+-----------------------+ + |Offsets|Octet| 0 | 1 | 2 | 3 | + +-------+-----+-+-+-+-+-+-+-+-+-+-+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ + | Octet | Bit |0|1|2|3|4|5|6|7|8|9|10|11|12|13|14|15|16|17|18|19|20|21|22|23|24|25|26|27|28|29|30|31| + +-------+-----+-+-+-+-+-+-+-+-+-+-+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ + | 0 | 0 |Version| IHL | DSCP | ECN | Total Length | + +-------+-----+-------+-------+---------------+-----+--------+--------------------------------------+ + | 4 | 32 | Identification | Flags | Fragment Offset | + +-------+-----+---------------+---------------------+--------+--------------------------------------+ + | 8 | 64 | Time To Live | Protocol | Header Checksum | + +-------+-----+---------------+---------------------+-----------------------------------------------+ + | 12 | 96 | Source IP Address | + +-------+-----+-------------------------------------------------------------------------------------+ + | 16 | 128 | Destination IP Address | + +-------+-----+-------------------------------------------------------------------------------------+ + | 20 | 160 | | + +-------+-----+ | + | ... | ... | Options | + +-------+-----+ | + | 56 | 448 | | + +-------+-----+-------------------------------------------------------------------------------------+ + + { dg-end-multiline-output "" } */ diff --git a/gcc/testsuite/gcc.dg/plugin/diagnostic-test-text-art-ascii-color.c b/gcc/testsuite/gcc.dg/plugin/diagnostic-test-text-art-ascii-color.c new file mode 100644 index 00000000000..0650428b1ce --- /dev/null +++ b/gcc/testsuite/gcc.dg/plugin/diagnostic-test-text-art-ascii-color.c @@ -0,0 +1,58 @@ +/* { dg-additional-options "-fdiagnostics-text-art-charset=ascii -fdiagnostics-color=always" } */ + +int non_empty; + +/* { dg-begin-multiline-output "" } + + A + B + C + + { dg-end-multiline-output "" } */ + +/* { dg-begin-multiline-output "" } + + [38;2;0;0;0;48;2;240;217;181m[K♜ [38;2;0;0;0;48;2;181;136;99m[K♞ [38;2;0;0;0;48;2;240;217;181m[K♝ [38;2;0;0;0;48;2;181;136;99m[K♛ [38;2;0;0;0;48;2;240;217;181m[K♚ [38;2;0;0;0;48;2;181;136;99m[K♝ [38;2;0;0;0;48;2;240;217;181m[K♞ [38;2;0;0;0;48;2;181;136;99m[K♜ [m[K + [38;2;0;0;0;48;2;181;136;99m[K♟ [38;2;0;0;0;48;2;240;217;181m[K♟ [38;2;0;0;0;48;2;181;136;99m[K♟ [38;2;0;0;0;48;2;240;217;181m[K♟ [38;2;0;0;0;48;2;181;136;99m[K♟ [38;2;0;0;0;48;2;240;217;181m[K♟ [38;2;0;0;0;48;2;181;136;99m[K♟ [38;2;0;0;0;48;2;240;217;181m[K♟ [m[K + [48;2;240;217;181m[K [48;2;181;136;99m[K [48;2;240;217;181m[K [48;2;181;136;99m[K [48;2;240;217;181m[K [48;2;181;136;99m[K [48;2;240;217;181m[K [48;2;181;136;99m[K [m[K + [48;2;181;136;99m[K [48;2;240;217;181m[K [48;2;181;136;99m[K [48;2;240;217;181m[K [48;2;181;136;99m[K [48;2;240;217;181m[K [48;2;181;136;99m[K [48;2;240;217;181m[K [m[K + [48;2;240;217;181m[K [48;2;181;136;99m[K [48;2;240;217;181m[K [48;2;181;136;99m[K [48;2;240;217;181m[K [48;2;181;136;99m[K [48;2;240;217;181m[K [48;2;181;136;99m[K [m[K + [48;2;181;136;99m[K [48;2;240;217;181m[K [48;2;181;136;99m[K [48;2;240;217;181m[K [48;2;181;136;99m[K [48;2;240;217;181m[K [48;2;181;136;99m[K [48;2;240;217;181m[K [m[K + [38;2;255;255;255;48;2;240;217;181m[K♙ [38;2;255;255;255;48;2;181;136;99m[K♙ [38;2;255;255;255;48;2;240;217;181m[K♙ [38;2;255;255;255;48;2;181;136;99m[K♙ [38;2;255;255;255;48;2;240;217;181m[K♙ [38;2;255;255;255;48;2;181;136;99m[K♙ [38;2;255;255;255;48;2;240;217;181m[K♙ [38;2;255;255;255;48;2;181;136;99m[K♙ [m[K + [38;2;255;255;255;48;2;181;136;99m[K♖ [38;2;255;255;255;48;2;240;217;181m[K♘ [38;2;255;255;255;48;2;181;136;99m[K♗ [38;2;255;255;255;48;2;240;217;181m[K♕ [38;2;255;255;255;48;2;181;136;99m[K♔ [38;2;255;255;255;48;2;240;217;181m[K♗ [38;2;255;255;255;48;2;181;136;99m[K♘ [38;2;255;255;255;48;2;240;217;181m[K♖ [m[K + + { dg-end-multiline-output "" } */ + +/* { dg-begin-multiline-output "" } + + +--+ + |🙂| + +--+ + + { dg-end-multiline-output "" } */ + +/* { dg-begin-multiline-output "" } + + +-------+-----+---------------+---------------------+-----------------------+-----------------------+ + |Offsets|Octet| 0 | 1 | 2 | 3 | + +-------+-----+-+-+-+-+-+-+-+-+-+-+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ + | Octet | Bit |0|1|2|3|4|5|6|7|8|9|10|11|12|13|14|15|16|17|18|19|20|21|22|23|24|25|26|27|28|29|30|31| + +-------+-----+-+-+-+-+-+-+-+-+-+-+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ + | 0 | 0 |Version| IHL | DSCP | ECN | Total Length | + +-------+-----+-------+-------+---------------+-----+--------+--------------------------------------+ + | 4 | 32 | Identification | Flags | Fragment Offset | + +-------+-----+---------------+---------------------+--------+--------------------------------------+ + | 8 | 64 | Time To Live | Protocol | Header Checksum | + +-------+-----+---------------+---------------------+-----------------------------------------------+ + | 12 | 96 | Source IP Address | + +-------+-----+-------------------------------------------------------------------------------------+ + | 16 | 128 | Destination IP Address | + +-------+-----+-------------------------------------------------------------------------------------+ + | 20 | 160 | | + +-------+-----+ | + | ... | ... | Options | + +-------+-----+ | + | 56 | 448 | | + +-------+-----+-------------------------------------------------------------------------------------+ + + { dg-end-multiline-output "" } */ diff --git a/gcc/testsuite/gcc.dg/plugin/diagnostic-test-text-art-none.c b/gcc/testsuite/gcc.dg/plugin/diagnostic-test-text-art-none.c new file mode 100644 index 00000000000..c8118b46759 --- /dev/null +++ b/gcc/testsuite/gcc.dg/plugin/diagnostic-test-text-art-none.c @@ -0,0 +1,5 @@ +/* { dg-additional-options "-fdiagnostics-text-art-charset=none" } */ + +int non_empty; + +/* We expect no output. */ diff --git a/gcc/testsuite/gcc.dg/plugin/diagnostic-test-text-art-unicode-bw.c b/gcc/testsuite/gcc.dg/plugin/diagnostic-test-text-art-unicode-bw.c new file mode 100644 index 00000000000..c9f5b36571a --- /dev/null +++ b/gcc/testsuite/gcc.dg/plugin/diagnostic-test-text-art-unicode-bw.c @@ -0,0 +1,58 @@ +/* { dg-additional-options "-fdiagnostics-text-art-charset=unicode -fdiagnostics-color=never" } */ + +int non_empty; + +/* { dg-begin-multiline-output "" } + + A + B + C + + { dg-end-multiline-output "" } */ + +/* { dg-begin-multiline-output "" } + + ♜ ♞ ♝ ♛ ♚ ♝ ♞ ♜ + ♟ ♟ ♟ ♟ ♟ ♟ ♟ ♟ + + + + + ♙ ♙ ♙ ♙ ♙ ♙ ♙ ♙ + ♖ ♘ ♗ ♕ ♔ ♗ ♘ ♖ + + { dg-end-multiline-output "" } */ + +/* { dg-begin-multiline-output "" } + + ┌──┐ + │🙂│ + └──┘ + + { dg-end-multiline-output "" } */ + +/* { dg-begin-multiline-output "" } + + ┌───────┬─────┬───────────────┬─────────────────────┬───────────────────────┬───────────────────────┐ + │Offsets│Octet│ 0 │ 1 │ 2 │ 3 │ + ├───────┼─────┼─┬─┬─┬─┬─┬─┬─┬─┼─┬─┬──┬──┬──┬──┬──┬──┼──┬──┬──┬──┬──┬──┬──┬──┼──┬──┬──┬──┬──┬──┬──┬──┤ + │ Octet │ Bit │0│1│2│3│4│5│6│7│8│9│10│11│12│13│14│15│16│17│18│19│20│21│22│23│24│25│26│27│28│29│30│31│ + ├───────┼─────┼─┴─┴─┴─┼─┴─┴─┴─┼─┴─┴──┴──┴──┴──┼──┴──┼──┴──┴──┴──┴──┴──┴──┴──┴──┴──┴──┴──┴──┴──┴──┴──┤ + │ 0 │ 0 │Version│ IHL │ DSCP │ ECN │ Total Length │ + ├───────┼─────┼───────┴───────┴───────────────┴─────┼────────┬──────────────────────────────────────┤ + │ 4 │ 32 │ Identification │ Flags │ Fragment Offset │ + ├───────┼─────┼───────────────┬─────────────────────┼────────┴──────────────────────────────────────┤ + │ 8 │ 64 │ Time To Live │ Protocol │ Header Checksum │ + ├───────┼─────┼───────────────┴─────────────────────┴───────────────────────────────────────────────┤ + │ 12 │ 96 │ Source IP Address │ + ├───────┼─────┼─────────────────────────────────────────────────────────────────────────────────────┤ + │ 16 │ 128 │ Destination IP Address │ + ├───────┼─────┼─────────────────────────────────────────────────────────────────────────────────────┤ + │ 20 │ 160 │ │ + ├───────┼─────┤ │ + │ ... │ ... │ Options │ + ├───────┼─────┤ │ + │ 56 │ 448 │ │ + └───────┴─────┴─────────────────────────────────────────────────────────────────────────────────────┘ + + { dg-end-multiline-output "" } */ diff --git a/gcc/testsuite/gcc.dg/plugin/diagnostic-test-text-art-unicode-color.c b/gcc/testsuite/gcc.dg/plugin/diagnostic-test-text-art-unicode-color.c new file mode 100644 index 00000000000..f402836f889 --- /dev/null +++ b/gcc/testsuite/gcc.dg/plugin/diagnostic-test-text-art-unicode-color.c @@ -0,0 +1,59 @@ +/* { dg-additional-options "-fdiagnostics-text-art-charset=unicode -fdiagnostics-color=always" } */ + +int non_empty; + + +/* { dg-begin-multiline-output "" } + + A + B + C + + { dg-end-multiline-output "" } */ + +/* { dg-begin-multiline-output "" } + + [38;2;0;0;0;48;2;240;217;181m[K♜ [38;2;0;0;0;48;2;181;136;99m[K♞ [38;2;0;0;0;48;2;240;217;181m[K♝ [38;2;0;0;0;48;2;181;136;99m[K♛ [38;2;0;0;0;48;2;240;217;181m[K♚ [38;2;0;0;0;48;2;181;136;99m[K♝ [38;2;0;0;0;48;2;240;217;181m[K♞ [38;2;0;0;0;48;2;181;136;99m[K♜ [m[K + [38;2;0;0;0;48;2;181;136;99m[K♟ [38;2;0;0;0;48;2;240;217;181m[K♟ [38;2;0;0;0;48;2;181;136;99m[K♟ [38;2;0;0;0;48;2;240;217;181m[K♟ [38;2;0;0;0;48;2;181;136;99m[K♟ [38;2;0;0;0;48;2;240;217;181m[K♟ [38;2;0;0;0;48;2;181;136;99m[K♟ [38;2;0;0;0;48;2;240;217;181m[K♟ [m[K + [48;2;240;217;181m[K [48;2;181;136;99m[K [48;2;240;217;181m[K [48;2;181;136;99m[K [48;2;240;217;181m[K [48;2;181;136;99m[K [48;2;240;217;181m[K [48;2;181;136;99m[K [m[K + [48;2;181;136;99m[K [48;2;240;217;181m[K [48;2;181;136;99m[K [48;2;240;217;181m[K [48;2;181;136;99m[K [48;2;240;217;181m[K [48;2;181;136;99m[K [48;2;240;217;181m[K [m[K + [48;2;240;217;181m[K [48;2;181;136;99m[K [48;2;240;217;181m[K [48;2;181;136;99m[K [48;2;240;217;181m[K [48;2;181;136;99m[K [48;2;240;217;181m[K [48;2;181;136;99m[K [m[K + [48;2;181;136;99m[K [48;2;240;217;181m[K [48;2;181;136;99m[K [48;2;240;217;181m[K [48;2;181;136;99m[K [48;2;240;217;181m[K [48;2;181;136;99m[K [48;2;240;217;181m[K [m[K + [38;2;255;255;255;48;2;240;217;181m[K♙ [38;2;255;255;255;48;2;181;136;99m[K♙ [38;2;255;255;255;48;2;240;217;181m[K♙ [38;2;255;255;255;48;2;181;136;99m[K♙ [38;2;255;255;255;48;2;240;217;181m[K♙ [38;2;255;255;255;48;2;181;136;99m[K♙ [38;2;255;255;255;48;2;240;217;181m[K♙ [38;2;255;255;255;48;2;181;136;99m[K♙ [m[K + [38;2;255;255;255;48;2;181;136;99m[K♖ [38;2;255;255;255;48;2;240;217;181m[K♘ [38;2;255;255;255;48;2;181;136;99m[K♗ [38;2;255;255;255;48;2;240;217;181m[K♕ [38;2;255;255;255;48;2;181;136;99m[K♔ [38;2;255;255;255;48;2;240;217;181m[K♗ [38;2;255;255;255;48;2;181;136;99m[K♘ [38;2;255;255;255;48;2;240;217;181m[K♖ [m[K + + { dg-end-multiline-output "" } */ + +/* { dg-begin-multiline-output "" } + + ┌──┐ + │🙂│ + └──┘ + + { dg-end-multiline-output "" } */ + +/* { dg-begin-multiline-output "" } + + ┌───────┬─────┬───────────────┬─────────────────────┬───────────────────────┬───────────────────────┐ + │Offsets│Octet│ 0 │ 1 │ 2 │ 3 │ + ├───────┼─────┼─┬─┬─┬─┬─┬─┬─┬─┼─┬─┬──┬──┬──┬──┬──┬──┼──┬──┬──┬──┬──┬──┬──┬──┼──┬──┬──┬──┬──┬──┬──┬──┤ + │ Octet │ Bit │0│1│2│3│4│5│6│7│8│9│10│11│12│13│14│15│16│17│18│19│20│21│22│23│24│25│26│27│28│29│30│31│ + ├───────┼─────┼─┴─┴─┴─┼─┴─┴─┴─┼─┴─┴──┴──┴──┴──┼──┴──┼──┴──┴──┴──┴──┴──┴──┴──┴──┴──┴──┴──┴──┴──┴──┴──┤ + │ 0 │ 0 │Version│ IHL │ DSCP │ ECN │ Total Length │ + ├───────┼─────┼───────┴───────┴───────────────┴─────┼────────┬──────────────────────────────────────┤ + │ 4 │ 32 │ Identification │ Flags │ Fragment Offset │ + ├───────┼─────┼───────────────┬─────────────────────┼────────┴──────────────────────────────────────┤ + │ 8 │ 64 │ Time To Live │ Protocol │ Header Checksum │ + ├───────┼─────┼───────────────┴─────────────────────┴───────────────────────────────────────────────┤ + │ 12 │ 96 │ Source IP Address │ + ├───────┼─────┼─────────────────────────────────────────────────────────────────────────────────────┤ + │ 16 │ 128 │ Destination IP Address │ + ├───────┼─────┼─────────────────────────────────────────────────────────────────────────────────────┤ + │ 20 │ 160 │ │ + ├───────┼─────┤ │ + │ ... │ ... │ Options │ + ├───────┼─────┤ │ + │ 56 │ 448 │ │ + └───────┴─────┴─────────────────────────────────────────────────────────────────────────────────────┘ + + { dg-end-multiline-output "" } */ diff --git a/gcc/testsuite/gcc.dg/plugin/diagnostic_plugin_test_text_art.c b/gcc/testsuite/gcc.dg/plugin/diagnostic_plugin_test_text_art.c new file mode 100644 index 00000000000..27c341b9f2f --- /dev/null +++ b/gcc/testsuite/gcc.dg/plugin/diagnostic_plugin_test_text_art.c @@ -0,0 +1,257 @@ +/* { dg-options "-O" } */ + +/* This plugin exercises the text_art code. */ + +#include "gcc-plugin.h" +#include "config.h" +#include "system.h" +#include "coretypes.h" +#include "plugin-version.h" +#include "diagnostic.h" +#include "diagnostic-diagram.h" +#include "text-art/canvas.h" +#include "text-art/table.h" + +int plugin_is_GPL_compatible; + +using namespace text_art; + +/* Canvas tests. */ + +static void +emit_canvas (const canvas &c, const char *alt_text) +{ + diagnostic_diagram diagram (c, alt_text); + diagnostic_emit_diagram (global_dc, diagram); +} + +static void +test_abc () +{ + style_manager sm; + canvas c (canvas::size_t (3, 3), sm); + c.paint (canvas::coord_t (0, 0), styled_unichar ('A')); + c.paint (canvas::coord_t (1, 1), styled_unichar ('B')); + c.paint (canvas::coord_t (2, 2), styled_unichar ('C')); + emit_canvas (c, "test_abc"); +} + +/* Test of procedural art using 24-bit color: chess starting position. */ + +static void +test_chessboard () +{ + /* With the exception of NONE, these are in order of the chess symbols + in the Unicode Miscellaneous Symbols block. */ + enum class piece { KING, QUEEN, ROOK, BISHOP, KNIGHT, PAWN, NONE }; + enum class color { BLACK, WHITE, NONE }; + + style_manager sm; + + /* We assume double-column chars for the pieces, so allow two canvas + columns per square. */ + canvas canvas (canvas::size_t (16, 8), sm); + + for (int x = 0; x < 8; x++) + for (int y = 0; y < 8; y++) + { + enum piece piece_kind; + enum color piece_color; + switch (y) + { + case 0: + case 7: + switch (x) + { + default: + gcc_unreachable (); + case 0: + piece_kind = piece::ROOK; + break; + case 1: + piece_kind = piece::KNIGHT; + break; + case 2: + piece_kind = piece::BISHOP; + break; + case 3: + piece_kind = piece::QUEEN; + break; + case 4: + piece_kind = piece::KING; + break; + case 5: + piece_kind = piece::BISHOP; + break; + case 6: + piece_kind = piece::KNIGHT; + break; + case 7: + piece_kind = piece::ROOK; + break; + } + piece_color = (y == 0) ? color::BLACK : color::WHITE; + break; + case 1: + case 6: + piece_kind = piece::PAWN; + piece_color = (y == 1) ? color::BLACK : color::WHITE; + break; + default: + piece_kind = piece::NONE; + piece_color = color::NONE; + break; + } + + style s; + const bool white_square = (x + y) % 2 == 0; + if (white_square) + s.m_bg_color = style::color (0xf0, 0xd9, 0xb5); + else + s.m_bg_color = style::color (0xb5, 0x88, 0x63); + switch (piece_color) + { + default: + gcc_unreachable (); + case color::WHITE: + s.m_fg_color = style::color (0xff, 0xff, 0xff); + break; + case color::BLACK: + s.m_fg_color = style::color (0x00, 0x00, 0x00); + break; + case color::NONE: + break; + } + style::id_t style_id = sm.get_or_create_id (s); + + cppchar_t ch; + if (piece_kind == piece::NONE) + ch = ' '; + else + { + const cppchar_t WHITE_KING = 0x2654; + const cppchar_t BLACK_KING = 0x265A; + cppchar_t base ((piece_color == color::WHITE) + ? WHITE_KING : BLACK_KING); + ch = base + ((int)piece_kind - (int)piece::KING); + } + canvas.paint (canvas::coord_t (x * 2, y), + canvas::cell_t (ch, false, style_id)); + canvas.paint (canvas::coord_t (x * 2 + 1, y), + canvas::cell_t (' ', false, style_id)); + } + emit_canvas (canvas, "test_chessboard"); +} + +/* Table tests. */ + +static void +emit_table (const table &table, const style_manager &sm, const char *alt_text) +{ + const text_art::theme *theme = global_dc->m_diagrams.m_theme; + if (!theme) + return; + canvas c (table.to_canvas (*theme, sm)); + emit_canvas (c, alt_text); +} + +static void +test_double_width_chars () +{ + style_manager sm; + table table (table::size_t (1, 1)); + table.set_cell (table::coord_t (0,0), + styled_string ((cppchar_t)0x1f642)); + + emit_table (table, sm, "test_double_width_chars"); +} + +static void +test_ipv4_header () +{ + style_manager sm; + table table (table::size_t (34, 10)); + table.set_cell (table::coord_t (0, 0), styled_string (sm, "Offsets")); + table.set_cell (table::coord_t (1, 0), styled_string (sm, "Octet")); + table.set_cell (table::coord_t (0, 1), styled_string (sm, "Octet")); + for (int octet = 0; octet < 4; octet++) + table.set_cell_span (table::rect_t (table::coord_t (2 + (octet * 8), 0), + table::size_t (8, 1)), + styled_string::from_fmt (sm, nullptr, "%i", octet)); + table.set_cell (table::coord_t (1, 1), styled_string (sm, "Bit")); + for (int bit = 0; bit < 32; bit++) + table.set_cell (table::coord_t (bit + 2, 1), + styled_string::from_fmt (sm, nullptr, "%i", bit)); + for (int word = 0; word < 6; word++) + { + table.set_cell (table::coord_t (0, word + 2), + styled_string::from_fmt (sm, nullptr, "%i", word * 4)); + table.set_cell (table::coord_t (1, word + 2), + styled_string::from_fmt (sm, nullptr, "%i", word * 32)); + } + + table.set_cell (table::coord_t (0, 8), styled_string (sm, "...")); + table.set_cell (table::coord_t (1, 8), styled_string (sm, "...")); + table.set_cell (table::coord_t (0, 9), styled_string (sm, "56")); + table.set_cell (table::coord_t (1, 9), styled_string (sm, "448")); + +#define SET_BITS(FIRST, LAST, NAME) \ + do { \ + const int first = (FIRST); \ + const int last = (LAST); \ + const char *name = (NAME); \ + const int row = first / 32; \ + gcc_assert (last / 32 == row); \ + table::rect_t rect (table::coord_t ((first % 32) + 2, row + 2), \ + table::size_t (last + 1 - first , 1)); \ + table.set_cell_span (rect, styled_string (sm, name)); \ + } while (0) + + SET_BITS (0, 3, "Version"); + SET_BITS (4, 7, "IHL"); + SET_BITS (8, 13, "DSCP"); + SET_BITS (14, 15, "ECN"); + SET_BITS (16, 31, "Total Length"); + + SET_BITS (32 + 0, 32 + 15, "Identification"); + SET_BITS (32 + 16, 32 + 18, "Flags"); + SET_BITS (32 + 19, 32 + 31, "Fragment Offset"); + + SET_BITS (64 + 0, 64 + 7, "Time To Live"); + SET_BITS (64 + 8, 64 + 15, "Protocol"); + SET_BITS (64 + 16, 64 + 31, "Header Checksum"); + + SET_BITS (96 + 0, 96 + 31, "Source IP Address"); + SET_BITS (128 + 0, 128 + 31, "Destination IP Address"); + + table.set_cell_span(table::rect_t (table::coord_t (2, 7), + table::size_t (32, 3)), + styled_string (sm, "Options")); + + emit_table (table, sm, "test_ipv4_header"); +} + +static void +show_diagrams () +{ + test_abc (); + test_chessboard (); + test_double_width_chars (); + test_ipv4_header (); +} + +int +plugin_init (struct plugin_name_args *plugin_info, + struct plugin_gcc_version *version) +{ + const char *plugin_name = plugin_info->base_name; + int argc = plugin_info->argc; + struct plugin_argument *argv = plugin_info->argv; + + if (!plugin_default_version_check (version, &gcc_version)) + return 1; + + show_diagrams (); + + return 0; +} diff --git a/gcc/testsuite/gcc.dg/plugin/plugin.exp b/gcc/testsuite/gcc.dg/plugin/plugin.exp index 4d6304cd100..60723a20eda 100644 --- a/gcc/testsuite/gcc.dg/plugin/plugin.exp +++ b/gcc/testsuite/gcc.dg/plugin/plugin.exp @@ -114,6 +114,12 @@ set plugin_test_list [list \ diagnostic-path-format-inline-events-1.c \ diagnostic-path-format-inline-events-2.c \ diagnostic-path-format-inline-events-3.c } \ + { diagnostic_plugin_test_text_art.c \ + diagnostic-test-text-art-none.c \ + diagnostic-test-text-art-ascii-bw.c \ + diagnostic-test-text-art-ascii-color.c \ + diagnostic-test-text-art-unicode-bw.c \ + diagnostic-test-text-art-unicode-color.c } \ { location_overflow_plugin.c \ location-overflow-test-1.c \ location-overflow-test-2.c \ diff --git a/gcc/text-art/box-drawing-chars.inc b/gcc/text-art/box-drawing-chars.inc new file mode 100644 index 00000000000..a370255d56d --- /dev/null +++ b/gcc/text-art/box-drawing-chars.inc @@ -0,0 +1,18 @@ +/* Generated by contrib/unicode/gen-box-drawing-chars.py. */ + +0x0020, /* " ": U+0020: SPACE */ +0x2576, /* "╶": U+2576: BOX DRAWINGS LIGHT RIGHT */ +0x2574, /* "╴": U+2574: BOX DRAWINGS LIGHT LEFT */ +0x2500, /* "─": U+2500: BOX DRAWINGS LIGHT HORIZONTAL */ +0x2577, /* "╷": U+2577: BOX DRAWINGS LIGHT DOWN */ +0x250C, /* "┌": U+250C: BOX DRAWINGS LIGHT DOWN AND RIGHT */ +0x2510, /* "┐": U+2510: BOX DRAWINGS LIGHT DOWN AND LEFT */ +0x252C, /* "┬": U+252C: BOX DRAWINGS LIGHT DOWN AND HORIZONTAL */ +0x2575, /* "╵": U+2575: BOX DRAWINGS LIGHT UP */ +0x2514, /* "└": U+2514: BOX DRAWINGS LIGHT UP AND RIGHT */ +0x2518, /* "┘": U+2518: BOX DRAWINGS LIGHT UP AND LEFT */ +0x2534, /* "┴": U+2534: BOX DRAWINGS LIGHT UP AND HORIZONTAL */ +0x2502, /* "│": U+2502: BOX DRAWINGS LIGHT VERTICAL */ +0x251C, /* "├": U+251C: BOX DRAWINGS LIGHT VERTICAL AND RIGHT */ +0x2524, /* "┤": U+2524: BOX DRAWINGS LIGHT VERTICAL AND LEFT */ +0x253C /* "┼": U+253C: BOX DRAWINGS LIGHT VERTICAL AND HORIZONTAL */ diff --git a/gcc/text-art/box-drawing.cc b/gcc/text-art/box-drawing.cc new file mode 100644 index 00000000000..981d0b095cf --- /dev/null +++ b/gcc/text-art/box-drawing.cc @@ -0,0 +1,72 @@ +/* Procedural lookup of box drawing characters. + Copyright (C) 2023 Free Software Foundation, Inc. + Contributed by David Malcolm <dmalcolm@redhat.com>. + +This file is part of GCC. + +GCC is free software; you can redistribute it and/or modify it under +the terms of the GNU General Public License as published by the Free +Software Foundation; either version 3, or (at your option) any later +version. + +GCC is distributed in the hope that it will be useful, but WITHOUT ANY +WARRANTY; without even the implied warranty of MERCHANTABILITY or +FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License +for more details. + +You should have received a copy of the GNU General Public License +along with GCC; see the file COPYING3. If not see +<http://www.gnu.org/licenses/>. */ + +#include "config.h" +#include "system.h" +#include "coretypes.h" +#include "text-art/box-drawing.h" +#include "selftest.h" +#include "text-art/selftests.h" + + +/* According to + https://en.wikipedia.org/wiki/Box-drawing_character#Character_code + "DOS line- and box-drawing characters are not ordered in any programmatic + manner, so calculating a particular character shape needs to use a look-up + table. " + Hence this array. */ +static const cppchar_t box_drawing_chars[] = { +#include "text-art/box-drawing-chars.inc" +}; + +cppchar_t +text_art::get_box_drawing_char (directions line_dirs) +{ + const size_t idx = line_dirs.as_index (); + gcc_assert (idx < 16); + return box_drawing_chars[idx]; +} + +#if CHECKING_P + +namespace selftest { + +/* Run all selftests in this file. */ + +void +text_art_box_drawing_cc_tests () +{ + ASSERT_EQ (text_art::get_box_drawing_char + (text_art::directions (false, false, false, false)), + ' '); + ASSERT_EQ (text_art::get_box_drawing_char + (text_art::directions (false, false, true, true)), + 0x2500); /* BOX DRAWINGS LIGHT HORIZONTAL */ + ASSERT_EQ (text_art::get_box_drawing_char + (text_art::directions (true, true, false, false)), + 0x2502); /* BOX DRAWINGS LIGHT VERTICAL */ + ASSERT_EQ (text_art::get_box_drawing_char + (text_art::directions (true, false, true, false)), + 0x2518); /* BOX DRAWINGS LIGHT UP AND LEFT */ +} + +} // namespace selftest + +#endif /* #if CHECKING_P */ diff --git a/gcc/text-art/box-drawing.h b/gcc/text-art/box-drawing.h new file mode 100644 index 00000000000..29f4d9921b3 --- /dev/null +++ b/gcc/text-art/box-drawing.h @@ -0,0 +1,32 @@ +/* Procedural lookup of box drawing characters. + Copyright (C) 2023 Free Software Foundation, Inc. + Contributed by David Malcolm <dmalcolm@redhat.com>. + +This file is part of GCC. + +GCC is free software; you can redistribute it and/or modify it +under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 3, or (at your option) +any later version. + +GCC is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GCC; see the file COPYING3. If not see +<http://www.gnu.org/licenses/>. */ + +#ifndef GCC_TEXT_ART_BOX_DRAWING_H +#define GCC_TEXT_ART_BOX_DRAWING_H + +#include "text-art/types.h" + +namespace text_art { + +extern cppchar_t get_box_drawing_char (directions line_dirs); + +} // namespace text_art + +#endif /* GCC_TEXT_ART_BOX_DRAWING_H */ diff --git a/gcc/text-art/canvas.cc b/gcc/text-art/canvas.cc new file mode 100644 index 00000000000..f229612c919 --- /dev/null +++ b/gcc/text-art/canvas.cc @@ -0,0 +1,437 @@ +/* Canvas for random-access procedural text art. + Copyright (C) 2023 Free Software Foundation, Inc. + Contributed by David Malcolm <dmalcolm@redhat.com>. + +This file is part of GCC. + +GCC is free software; you can redistribute it and/or modify it under +the terms of the GNU General Public License as published by the Free +Software Foundation; either version 3, or (at your option) any later +version. + +GCC is distributed in the hope that it will be useful, but WITHOUT ANY +WARRANTY; without even the implied warranty of MERCHANTABILITY or +FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License +for more details. + +You should have received a copy of the GNU General Public License +along with GCC; see the file COPYING3. If not see +<http://www.gnu.org/licenses/>. */ + +#include "config.h" +#include "system.h" +#include "coretypes.h" +#include "pretty-print.h" +#include "selftest.h" +#include "text-art/selftests.h" +#include "text-art/canvas.h" + +using namespace text_art; + +canvas::canvas (size_t size, const style_manager &style_mgr) +: m_cells (size_t (size.w, size.h)), + m_style_mgr (style_mgr) +{ + m_cells.fill (cell_t (' ')); +} + +void +canvas::paint (coord_t coord, styled_unichar ch) +{ + m_cells.set (coord, std::move (ch)); +} + +void +canvas::paint_text (coord_t coord, const styled_string &text) +{ + for (auto ch : text) + { + paint (coord, ch); + if (ch.double_width_p ()) + coord.x += 2; + else + coord.x++; + } +} + +void +canvas::fill (rect_t rect, cell_t c) +{ + for (int y = rect.get_min_y (); y < rect.get_next_y (); y++) + for (int x = rect.get_min_x (); x < rect.get_next_x (); x++) + paint(coord_t (x, y), c); +} + +void +canvas::debug_fill () +{ + fill (rect_t (coord_t (0, 0), get_size ()), cell_t ('*')); +} + +void +canvas::print_to_pp (pretty_printer *pp, + const char *per_line_prefix) const +{ + for (int y = 0; y < m_cells.get_size ().h; y++) + { + style::id_t curr_style_id = 0; + if (per_line_prefix) + pp_string (pp, per_line_prefix); + + pretty_printer line_pp; + line_pp.show_color = pp->show_color; + line_pp.url_format = pp->url_format; + const int final_x_in_row = get_final_x_in_row (y); + for (int x = 0; x <= final_x_in_row; x++) + { + if (x > 0) + { + const cell_t prev_cell = m_cells.get (coord_t (x - 1, y)); + if (prev_cell.double_width_p ()) + /* This cell is just a placeholder for the + 2nd column of a double width cell; skip it. */ + continue; + } + const cell_t cell = m_cells.get (coord_t (x, y)); + if (cell.get_style_id () != curr_style_id) + { + m_style_mgr.print_any_style_changes (&line_pp, + curr_style_id, + cell.get_style_id ()); + curr_style_id = cell.get_style_id (); + } + pp_unicode_character (&line_pp, cell.get_code ()); + if (cell.emoji_variant_p ()) + /* Append U+FE0F VARIATION SELECTOR-16 to select the emoji + variation of the char. */ + pp_unicode_character (&line_pp, 0xFE0F); + } + /* Reset the style at the end of each line. */ + m_style_mgr.print_any_style_changes (&line_pp, curr_style_id, 0); + + /* Print from line_pp to pp, stripping trailing whitespace from + the line. */ + const char *line_buf = pp_formatted_text (&line_pp); + ::size_t len = strlen (line_buf); + while (len > 0) + { + if (line_buf[len - 1] == ' ') + len--; + else + break; + } + pp_append_text (pp, line_buf, line_buf + len); + pp_newline (pp); + } +} + +DEBUG_FUNCTION void +canvas::debug (bool styled) const +{ + pretty_printer pp; + if (styled) + { + pp_show_color (&pp) = true; + pp.url_format = determine_url_format (DIAGNOSTICS_URL_AUTO); + } + print_to_pp (&pp); + fprintf (stderr, "%s\n", pp_formatted_text (&pp)); +} + +/* Find right-most non-default cell in this row, + or -1 if all are default. */ + +int +canvas::get_final_x_in_row (int y) const +{ + for (int x = m_cells.get_size ().w - 1; x >= 0; x--) + { + cell_t cell = m_cells.get (coord_t (x, y)); + if (cell.get_code () != ' ' + || cell.get_style_id () != style::id_plain) + return x; + } + return -1; +} + +#if CHECKING_P + +namespace selftest { + +static void +test_blank () +{ + style_manager sm; + canvas c (canvas::size_t (5, 5), sm); + ASSERT_CANVAS_STREQ (c, false, + ("\n" + "\n" + "\n" + "\n" + "\n")); +} + +static void +test_abc () +{ + style_manager sm; + canvas c (canvas::size_t (3, 3), sm); + c.paint (canvas::coord_t (0, 0), styled_unichar ('A')); + c.paint (canvas::coord_t (1, 1), styled_unichar ('B')); + c.paint (canvas::coord_t (2, 2), styled_unichar ('C')); + + ASSERT_CANVAS_STREQ (c, false, + "A\n B\n C\n"); +} + +static void +test_debug_fill () +{ + style_manager sm; + canvas c (canvas::size_t (5, 3), sm); + c.debug_fill(); + ASSERT_CANVAS_STREQ (c, false, + ("*****\n" + "*****\n" + "*****\n")); +} + +static void +test_text () +{ + style_manager sm; + canvas c (canvas::size_t (6, 1), sm); + c.paint_text (canvas::coord_t (0, 0), styled_string (sm, "012345")); + ASSERT_CANVAS_STREQ (c, false, + ("012345\n")); + + /* Paint an emoji character that should occupy two canvas columns when + printed. */ + c.paint_text (canvas::coord_t (2, 0), styled_string ((cppchar_t)0x1f642)); + ASSERT_CANVAS_STREQ (c, false, + ("01🙂45\n")); +} + +static void +test_circle () +{ + canvas::size_t sz (30, 30); + style_manager sm; + canvas canvas (sz, sm); + canvas::coord_t center (sz.w / 2, sz.h / 2); + const int radius = 12; + const int radius_squared = radius * radius; + for (int x = 0; x < sz.w; x++) + for (int y = 0; y < sz.h; y++) + { + int dx = x - center.x; + int dy = y - center.y; + char ch = "AB"[(x + y) % 2]; + if (dx * dx + dy * dy < radius_squared) + canvas.paint (canvas::coord_t (x, y), styled_unichar (ch)); + } + ASSERT_CANVAS_STREQ + (canvas, false, + ("\n" + "\n" + "\n" + "\n" + " BABABABAB\n" + " ABABABABABABA\n" + " ABABABABABABABA\n" + " ABABABABABABABABA\n" + " ABABABABABABABABABA\n" + " ABABABABABABABABABABA\n" + " BABABABABABABABABABAB\n" + " BABABABABABABABABABABAB\n" + " ABABABABABABABABABABABA\n" + " BABABABABABABABABABABAB\n" + " ABABABABABABABABABABABA\n" + " BABABABABABABABABABABAB\n" + " ABABABABABABABABABABABA\n" + " BABABABABABABABABABABAB\n" + " ABABABABABABABABABABABA\n" + " BABABABABABABABABABABAB\n" + " BABABABABABABABABABAB\n" + " ABABABABABABABABABABA\n" + " ABABABABABABABABABA\n" + " ABABABABABABABABA\n" + " ABABABABABABABA\n" + " ABABABABABABA\n" + " BABABABAB\n" + "\n" + "\n" + "\n")); +} + +static void +test_color_circle () +{ + const canvas::size_t sz (10, 10); + const canvas::coord_t center (sz.w / 2, sz.h / 2); + const int outer_r2 = 25; + const int inner_r2 = 10; + style_manager sm; + canvas c (sz, sm); + for (int x = 0; x < sz.w; x++) + for (int y = 0; y < sz.h; y++) + { + const int dist_from_center_squared + = ((x - center.x) * (x - center.x) + (y - center.y) * (y - center.y)); + if (dist_from_center_squared < outer_r2) + { + style s; + if (dist_from_center_squared < inner_r2) + s.m_fg_color = style::named_color::RED; + else + s.m_fg_color = style::named_color::GREEN; + c.paint (canvas::coord_t (x, y), + styled_unichar ('*', false, sm.get_or_create_id (s))); + } + } + ASSERT_EQ (sm.get_num_styles (), 3); + ASSERT_CANVAS_STREQ + (c, false, + ("\n" + " *****\n" + " *******\n" + " *********\n" + " *********\n" + " *********\n" + " *********\n" + " *********\n" + " *******\n" + " *****\n")); + ASSERT_CANVAS_STREQ + (c, true, + ("\n" + " [32m[K*****[m[K\n" + " [32m[K***[31m[K*[32m[K***[m[K\n" + " [32m[K**[31m[K*****[32m[K**[m[K\n" + " [32m[K**[31m[K*****[32m[K**[m[K\n" + " [32m[K*[31m[K*******[32m[K*[m[K\n" + " [32m[K**[31m[K*****[32m[K**[m[K\n" + " [32m[K**[31m[K*****[32m[K**[m[K\n" + " [32m[K***[31m[K*[32m[K***[m[K\n" + " [32m[K*****[m[K\n")); +} + +static void +test_bold () +{ + auto_fix_quotes fix_quotes; + style_manager sm; + styled_string s (styled_string::from_fmt (sm, nullptr, + "before %qs after", "foo")); + canvas c (canvas::size_t (s.calc_canvas_width (), 1), sm); + c.paint_text (canvas::coord_t (0, 0), s); + ASSERT_CANVAS_STREQ (c, false, + "before `foo' after\n"); + ASSERT_CANVAS_STREQ (c, true, + "before `[00;01m[Kfoo[00m[K' after\n"); +} + +static void +test_emoji () +{ + style_manager sm; + styled_string s (0x26A0, /* U+26A0 WARNING SIGN. */ + true); + canvas c (canvas::size_t (s.calc_canvas_width (), 1), sm); + c.paint_text (canvas::coord_t (0, 0), s); + ASSERT_CANVAS_STREQ (c, false, "⚠️\n"); + ASSERT_CANVAS_STREQ (c, true, "⚠️\n"); +} + +static void +test_emoji_2 () +{ + style_manager sm; + styled_string s; + s.append (styled_string (0x26A0, /* U+26A0 WARNING SIGN. */ + true)); + s.append (styled_string (sm, "test")); + ASSERT_EQ (s.size (), 5); + ASSERT_EQ (s.calc_canvas_width (), 5); + canvas c (canvas::size_t (s.calc_canvas_width (), 1), sm); + c.paint_text (canvas::coord_t (0, 0), s); + ASSERT_CANVAS_STREQ (c, false, + /* U+26A0 WARNING SIGN, as UTF-8: 0xE2 0x9A 0xA0. */ + "\xE2\x9A\xA0" + /* U+FE0F VARIATION SELECTOR-16, as UTF-8: 0xEF 0xB8 0x8F. */ + "\xEF\xB8\x8F" + "test\n"); +} + +static void +test_canvas_urls () +{ + style_manager sm; + canvas canvas (canvas::size_t (9, 3), sm); + styled_string foo_ss (sm, "foo"); + foo_ss.set_url (sm, "https://www.example.com/foo"); + styled_string bar_ss (sm, "bar"); + bar_ss.set_url (sm, "https://www.example.com/bar"); + canvas.paint_text(canvas::coord_t (1, 1), foo_ss); + canvas.paint_text(canvas::coord_t (5, 1), bar_ss); + + ASSERT_CANVAS_STREQ (canvas, false, + ("\n" + " foo bar\n" + "\n")); + { + pretty_printer pp; + pp_show_color (&pp) = true; + pp.url_format = URL_FORMAT_ST; + assert_canvas_streq (SELFTEST_LOCATION, canvas, &pp, + (/* Line 1. */ + "\n" + /* Line 2. */ + " " + "\33]8;;https://www.example.com/foo\33\\foo\33]8;;\33\\" + " " + "\33]8;;https://www.example.com/bar\33\\bar\33]8;;\33\\" + "\n" + /* Line 3. */ + "\n")); + } + + { + pretty_printer pp; + pp_show_color (&pp) = true; + pp.url_format = URL_FORMAT_BEL; + assert_canvas_streq (SELFTEST_LOCATION, canvas, &pp, + (/* Line 1. */ + "\n" + /* Line 2. */ + " " + "\33]8;;https://www.example.com/foo\afoo\33]8;;\a" + " " + "\33]8;;https://www.example.com/bar\abar\33]8;;\a" + "\n" + /* Line 3. */ + "\n")); + } +} + +/* Run all selftests in this file. */ + +void +text_art_canvas_cc_tests () +{ + test_blank (); + test_abc (); + test_debug_fill (); + test_text (); + test_circle (); + test_color_circle (); + test_bold (); + test_emoji (); + test_emoji_2 (); + test_canvas_urls (); +} + +} // namespace selftest + + +#endif /* #if CHECKING_P */ diff --git a/gcc/text-art/canvas.h b/gcc/text-art/canvas.h new file mode 100644 index 00000000000..495497754f5 --- /dev/null +++ b/gcc/text-art/canvas.h @@ -0,0 +1,74 @@ +/* Canvas for random-access procedural text art. + Copyright (C) 2023 Free Software Foundation, Inc. + Contributed by David Malcolm <dmalcolm@redhat.com>. + +This file is part of GCC. + +GCC is free software; you can redistribute it and/or modify it +under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 3, or (at your option) +any later version. + +GCC is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GCC; see the file COPYING3. If not see +<http://www.gnu.org/licenses/>. */ + +#ifndef GCC_TEXT_ART_CANVAS_H +#define GCC_TEXT_ART_CANVAS_H + +#include "text-art/types.h" + +namespace text_art { + +class canvas; + +/* A 2 dimensional grid of text cells (a "canvas"), which + can be written to ("painted") via random access, and then + written out to a pretty_printer once the picture is complete. + + Each text cell can be styled independently (colorization, + URLs, etc). */ + +class canvas +{ + public: + typedef styled_unichar cell_t; + typedef size<class canvas> size_t; + typedef coord<class canvas> coord_t; + typedef range<class canvas> range_t; + typedef rect<class canvas> rect_t; + + canvas (size_t size, const style_manager &style_mgr); + + size_t get_size () const { return m_cells.get_size (); } + + void paint (coord_t coord, cell_t c); + void paint_text (coord_t coord, const styled_string &text); + + void fill (rect_t rect, cell_t c); + void debug_fill (); + + void print_to_pp (pretty_printer *pp, + const char *per_line_prefix = NULL) const; + void debug (bool styled) const; + + const cell_t &get (coord_t coord) const + { + return m_cells.get (coord); + } + + private: + int get_final_x_in_row (int y) const; + + array2<cell_t, size_t, coord_t> m_cells; + const style_manager &m_style_mgr; +}; + +} // namespace text_art + +#endif /* GCC_TEXT_ART_CANVAS_H */ diff --git a/gcc/text-art/ruler.cc b/gcc/text-art/ruler.cc new file mode 100644 index 00000000000..80c623f77ba --- /dev/null +++ b/gcc/text-art/ruler.cc @@ -0,0 +1,723 @@ +/* Classes for printing labelled rulers. + Copyright (C) 2023 Free Software Foundation, Inc. + Contributed by David Malcolm <dmalcolm@redhat.com>. + +This file is part of GCC. + +GCC is free software; you can redistribute it and/or modify it under +the terms of the GNU General Public License as published by the Free +Software Foundation; either version 3, or (at your option) any later +version. + +GCC is distributed in the hope that it will be useful, but WITHOUT ANY +WARRANTY; without even the implied warranty of MERCHANTABILITY or +FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License +for more details. + +You should have received a copy of the GNU General Public License +along with GCC; see the file COPYING3. If not see +<http://www.gnu.org/licenses/>. */ + +#include "config.h" +#define INCLUDE_ALGORITHM +#include "system.h" +#include "coretypes.h" +#include "pretty-print.h" +#include "selftest.h" +#include "text-art/selftests.h" +#include "text-art/ruler.h" +#include "text-art/theme.h" + +using namespace text_art; + +void +x_ruler::add_label (const canvas::range_t &r, + styled_string text, + style::id_t style_id, + label_kind kind) +{ + m_labels.push_back (label (r, std::move (text), style_id, kind)); + m_has_layout = false; +} + +int +x_ruler::get_canvas_y (int rel_y) const +{ + gcc_assert (rel_y >= 0); + gcc_assert (rel_y < m_size.h); + switch (m_label_dir) + { + default: + gcc_unreachable (); + case label_dir::ABOVE: + return m_size.h - (rel_y + 1); + case label_dir::BELOW: + return rel_y; + } +} + +void +x_ruler::paint_to_canvas (canvas &canvas, + canvas::coord_t offset, + const theme &theme) +{ + ensure_layout (); + + if (0) + canvas.fill (canvas::rect_t (offset, m_size), + canvas::cell_t ('*')); + + for (size_t idx = 0; idx < m_labels.size (); idx++) + { + const label &iter_label = m_labels[idx]; + + /* Paint the ruler itself. */ + const int ruler_rel_y = get_canvas_y (0); + for (int rel_x = iter_label.m_range.start; + rel_x < iter_label.m_range.next; + rel_x++) + { + enum theme::cell_kind kind = theme::cell_kind::X_RULER_MIDDLE; + + if (rel_x == iter_label.m_range.start) + { + kind = theme::cell_kind::X_RULER_LEFT_EDGE; + if (idx > 0) + { + const label &prev_label = m_labels[idx - 1]; + if (prev_label.m_range.get_max () == iter_label.m_range.start) + kind = theme::cell_kind::X_RULER_INTERNAL_EDGE; + } + } + else if (rel_x == iter_label.m_range.get_max ()) + kind = theme::cell_kind::X_RULER_RIGHT_EDGE; + else if (rel_x == iter_label.m_connector_x) + { + switch (m_label_dir) + { + default: + gcc_unreachable (); + case label_dir::ABOVE: + kind = theme::cell_kind::X_RULER_CONNECTOR_TO_LABEL_ABOVE; + break; + case label_dir::BELOW: + kind = theme::cell_kind::X_RULER_CONNECTOR_TO_LABEL_BELOW; + break; + } + } + canvas.paint (canvas::coord_t (rel_x, ruler_rel_y) + offset, + theme.get_cell (kind, iter_label.m_style_id)); + } + + /* Paint the connector to the text. */ + for (int connector_rel_y = 1; + connector_rel_y < iter_label.m_text_rect.get_min_y (); + connector_rel_y++) + { + canvas.paint + ((canvas::coord_t (iter_label.m_connector_x, + get_canvas_y (connector_rel_y)) + + offset), + theme.get_cell (theme::cell_kind::X_RULER_VERTICAL_CONNECTOR, + iter_label.m_style_id)); + } + + /* Paint the text. */ + switch (iter_label.m_kind) + { + default: + gcc_unreachable (); + case x_ruler::label_kind::TEXT: + canvas.paint_text + ((canvas::coord_t (iter_label.m_text_rect.get_min_x (), + get_canvas_y (iter_label.m_text_rect.get_min_y ())) + + offset), + iter_label.m_text); + break; + + case x_ruler::label_kind::TEXT_WITH_BORDER: + { + const canvas::range_t rel_x_range + (iter_label.m_text_rect.get_x_range ()); + + enum theme::cell_kind inner_left_kind; + enum theme::cell_kind inner_connector_kind; + enum theme::cell_kind inner_right_kind; + enum theme::cell_kind outer_left_kind; + enum theme::cell_kind outer_right_kind; + + switch (m_label_dir) + { + default: + gcc_unreachable (); + case label_dir::ABOVE: + outer_left_kind = theme::cell_kind::TEXT_BORDER_TOP_LEFT; + outer_right_kind = theme::cell_kind::TEXT_BORDER_TOP_RIGHT; + inner_left_kind = theme::cell_kind::TEXT_BORDER_BOTTOM_LEFT; + inner_connector_kind = theme::cell_kind::X_RULER_CONNECTOR_TO_LABEL_BELOW; + inner_right_kind = theme::cell_kind::TEXT_BORDER_BOTTOM_RIGHT; + break; + case label_dir::BELOW: + inner_left_kind = theme::cell_kind::TEXT_BORDER_TOP_LEFT; + inner_connector_kind = theme::cell_kind::X_RULER_CONNECTOR_TO_LABEL_ABOVE; + inner_right_kind = theme::cell_kind::TEXT_BORDER_TOP_RIGHT; + outer_left_kind = theme::cell_kind::TEXT_BORDER_BOTTOM_LEFT; + outer_right_kind = theme::cell_kind::TEXT_BORDER_BOTTOM_RIGHT; + break; + } + /* Inner border. */ + { + const int rel_canvas_y + = get_canvas_y (iter_label.m_text_rect.get_min_y ()); + /* Left corner. */ + canvas.paint ((canvas::coord_t (rel_x_range.get_min (), + rel_canvas_y) + + offset), + theme.get_cell (inner_left_kind, + iter_label.m_style_id)); + /* Edge. */ + const canvas::cell_t edge_border_cell + = theme.get_cell (theme::cell_kind::TEXT_BORDER_HORIZONTAL, + iter_label.m_style_id); + const canvas::cell_t connector_border_cell + = theme.get_cell (inner_connector_kind, + iter_label.m_style_id); + for (int rel_x = rel_x_range.get_min () + 1; + rel_x < rel_x_range.get_max (); + rel_x++) + if (rel_x == iter_label.m_connector_x) + canvas.paint ((canvas::coord_t (rel_x, rel_canvas_y) + + offset), + connector_border_cell); + else + canvas.paint ((canvas::coord_t (rel_x, rel_canvas_y) + + offset), + edge_border_cell); + + /* Right corner. */ + canvas.paint ((canvas::coord_t (rel_x_range.get_max (), + rel_canvas_y) + + offset), + theme.get_cell (inner_right_kind, + iter_label.m_style_id)); + } + + { + const int rel_canvas_y + = get_canvas_y (iter_label.m_text_rect.get_min_y () + 1); + const canvas::cell_t border_cell + = theme.get_cell (theme::cell_kind::TEXT_BORDER_VERTICAL, + iter_label.m_style_id); + + /* Left border. */ + canvas.paint ((canvas::coord_t (rel_x_range.get_min (), + rel_canvas_y) + + offset), + border_cell); + /* Text. */ + canvas.paint_text ((canvas::coord_t (rel_x_range.get_min () + 1, + rel_canvas_y) + + offset), + iter_label.m_text); + /* Right border. */ + canvas.paint ((canvas::coord_t (rel_x_range.get_max (), + rel_canvas_y) + + offset), + border_cell); + } + + /* Outer border. */ + { + const int rel_canvas_y + = get_canvas_y (iter_label.m_text_rect.get_max_y ()); + /* Left corner. */ + canvas.paint ((canvas::coord_t (rel_x_range.get_min (), + rel_canvas_y) + + offset), + theme.get_cell (outer_left_kind, + iter_label.m_style_id)); + /* Edge. */ + const canvas::cell_t border_cell + = theme.get_cell (theme::cell_kind::TEXT_BORDER_HORIZONTAL, + iter_label.m_style_id); + for (int rel_x = rel_x_range.get_min () + 1; + rel_x < rel_x_range.get_max (); + rel_x++) + canvas.paint ((canvas::coord_t (rel_x, rel_canvas_y) + + offset), + border_cell); + + /* Right corner. */ + canvas.paint ((canvas::coord_t (rel_x_range.get_max (), + rel_canvas_y) + + offset), + theme.get_cell (outer_right_kind, + iter_label.m_style_id)); + } + } + break; + } + } +} + +DEBUG_FUNCTION void +x_ruler::debug (const style_manager &sm) +{ + canvas c (get_size (), sm); + paint_to_canvas (c, canvas::coord_t (0, 0), unicode_theme ()); + c.debug (true); +} + +x_ruler::label::label (const canvas::range_t &range, + styled_string text, + style::id_t style_id, + label_kind kind) +: m_range (range), + m_text (std::move (text)), + m_style_id (style_id), + m_kind (kind), + m_text_rect (canvas::coord_t (0, 0), + canvas::size_t (m_text.calc_canvas_width (), 1)), + m_connector_x ((m_range.get_min () + m_range.get_max ()) / 2) +{ + if (kind == label_kind::TEXT_WITH_BORDER) + { + m_text_rect.m_size.w += 2; + m_text_rect.m_size.h += 2; + } +} + +bool +x_ruler::label::operator< (const label &other) const +{ + int cmp = m_range.start - other.m_range.start; + if (cmp) + return cmp < 0; + return m_range.next < other.m_range.next; +} + +void +x_ruler::ensure_layout () +{ + if (m_has_layout) + return; + update_layout (); + m_has_layout = true; +} + +void +x_ruler::update_layout () +{ + if (m_labels.empty ()) + return; + + std::sort (m_labels.begin (), m_labels.end ()); + + /* Place labels. */ + int ruler_width = m_labels.back ().m_range.get_next (); + int width_with_labels = ruler_width; + + /* Get x coordinates of text parts of each label + (m_text_rect.m_top_left.x for each label). */ + for (size_t idx = 0; idx < m_labels.size (); idx++) + { + label &iter_label = m_labels[idx]; + /* Attempt to center the text label. */ + int min_x; + if (idx > 0) + { + /* ...but don't overlap with the connector to the left. */ + int left_neighbor_connector_x = m_labels[idx - 1].m_connector_x; + min_x = left_neighbor_connector_x + 1; + } + else + { + /* ...or go beyond the leftmost column. */ + min_x = 0; + } + int connector_x = iter_label.m_connector_x; + int centered_x + = connector_x - ((int)iter_label.m_text_rect.get_width () / 2); + int text_x = std::max (min_x, centered_x); + iter_label.m_text_rect.m_top_left.x = text_x; + } + + /* Now walk backwards trying to place them vertically, + setting m_text_rect.m_top_left.y for each label, + consolidating the rows where possible. + The y cooordinates are stored with respect to label_dir::BELOW. */ + int label_y = 2; + for (int idx = m_labels.size () - 1; idx >= 0; idx--) + { + label &iter_label = m_labels[idx]; + /* Does it fit on the same row as the text label to the right? */ + size_t text_len = iter_label.m_text_rect.get_width (); + /* Get the x-coord of immediately beyond iter_label's text. */ + int next_x = iter_label.m_text_rect.get_min_x () + text_len; + if (idx < (int)m_labels.size () - 1) + { + if (next_x >= m_labels[idx + 1].m_text_rect.get_min_x ()) + { + /* If not, start a new row. */ + label_y += m_labels[idx + 1].m_text_rect.get_height (); + } + } + iter_label.m_text_rect.m_top_left.y = label_y; + width_with_labels = std::max (width_with_labels, next_x); + } + + m_size = canvas::size_t (width_with_labels, + label_y + m_labels[0].m_text_rect.get_height ()); +} + +#if CHECKING_P + +namespace selftest { + +static void +assert_x_ruler_streq (const location &loc, + x_ruler &ruler, + const theme &theme, + const style_manager &sm, + bool styled, + const char *expected_str) +{ + canvas c (ruler.get_size (), sm); + ruler.paint_to_canvas (c, canvas::coord_t (0, 0), theme); + if (0) + c.debug (styled); + assert_canvas_streq (loc, c, styled, expected_str); +} + +#define ASSERT_X_RULER_STREQ(RULER, THEME, SM, STYLED, EXPECTED_STR) \ + SELFTEST_BEGIN_STMT \ + assert_x_ruler_streq ((SELFTEST_LOCATION), \ + (RULER), \ + (THEME), \ + (SM), \ + (STYLED), \ + (EXPECTED_STR)); \ + SELFTEST_END_STMT + +static void +test_single () +{ + style_manager sm; + x_ruler r (x_ruler::label_dir::BELOW); + r.add_label (canvas::range_t (0, 11), styled_string (sm, "foo"), + style::id_plain, x_ruler::label_kind::TEXT); + ASSERT_X_RULER_STREQ + (r, ascii_theme (), sm, true, + ("|~~~~+~~~~|\n" + " |\n" + " foo\n")); + ASSERT_X_RULER_STREQ + (r, unicode_theme (), sm, true, + ("├────┬────┤\n" + " │\n" + " foo\n")); +} + +static void +test_single_above () +{ + style_manager sm; + x_ruler r (x_ruler::label_dir::ABOVE); + r.add_label (canvas::range_t (0, 11), styled_string (sm, "hello world"), + style::id_plain); + ASSERT_X_RULER_STREQ + (r, ascii_theme (), sm, true, + ("hello world\n" + " |\n" + "|~~~~+~~~~|\n")); + ASSERT_X_RULER_STREQ + (r, unicode_theme (), sm, true, + ("hello world\n" + " │\n" + "├────┴────┤\n")); +} + +static void +test_multiple_contiguous () +{ + style_manager sm; + x_ruler r (x_ruler::label_dir::BELOW); + r.add_label (canvas::range_t (0, 11), styled_string (sm, "foo"), + style::id_plain); + r.add_label (canvas::range_t (10, 16), styled_string (sm, "bar"), + style::id_plain); + ASSERT_X_RULER_STREQ + (r, ascii_theme (), sm, true, + ("|~~~~+~~~~|~+~~|\n" + " | |\n" + " foo bar\n")); + ASSERT_X_RULER_STREQ + (r, unicode_theme (), sm, true, + ("├────┬────┼─┬──┤\n" + " │ │\n" + " foo bar\n")); +} + +static void +test_multiple_contiguous_above () +{ + style_manager sm; + x_ruler r (x_ruler::label_dir::ABOVE); + r.add_label (canvas::range_t (0, 11), styled_string (sm, "foo"), + style::id_plain); + r.add_label (canvas::range_t (10, 16), styled_string (sm, "bar"), + style::id_plain); + ASSERT_X_RULER_STREQ + (r, ascii_theme (), sm, true, + (" foo bar\n" + " | |\n" + "|~~~~+~~~~|~+~~|\n")); + ASSERT_X_RULER_STREQ + (r, unicode_theme (), sm, true, + (" foo bar\n" + " │ │\n" + "├────┴────┼─┴──┤\n")); +} + +static void +test_multiple_contiguous_abutting_labels () +{ + style_manager sm; + x_ruler r (x_ruler::label_dir::BELOW); + r.add_label (canvas::range_t (0, 11), styled_string (sm, "12345678"), + style::id_plain); + r.add_label (canvas::range_t (10, 16), styled_string (sm, "1234678"), + style::id_plain); + ASSERT_X_RULER_STREQ + (r, unicode_theme (), sm, true, + ("├────┬────┼─┬──┤\n" + " │ │\n" + " │ 1234678\n" + " 12345678\n")); +} + +static void +test_multiple_contiguous_overlapping_labels () +{ + style_manager sm; + x_ruler r (x_ruler::label_dir::BELOW); + r.add_label (canvas::range_t (0, 11), styled_string (sm, "123456789"), + style::id_plain); + r.add_label (canvas::range_t (10, 16), styled_string (sm, "12346789"), + style::id_plain); + ASSERT_X_RULER_STREQ + (r, unicode_theme (), sm, true, + ("├────┬────┼─┬──┤\n" + " │ │\n" + " │ 12346789\n" + " 123456789\n")); +} +static void +test_abutting_left_border () +{ + style_manager sm; + x_ruler r (x_ruler::label_dir::BELOW); + r.add_label (canvas::range_t (0, 6), + styled_string (sm, "this is a long label"), + style::id_plain); + ASSERT_X_RULER_STREQ + (r, unicode_theme (), sm, true, + ("├─┬──┤\n" + " │\n" + "this is a long label\n")); +} + +static void +test_too_long_to_consolidate_vertically () +{ + style_manager sm; + x_ruler r (x_ruler::label_dir::BELOW); + r.add_label (canvas::range_t (0, 11), + styled_string (sm, "long string A"), + style::id_plain); + r.add_label (canvas::range_t (10, 16), + styled_string (sm, "long string B"), + style::id_plain); + ASSERT_X_RULER_STREQ + (r, unicode_theme (), sm, true, + ("├────┬────┼─┬──┤\n" + " │ │\n" + " │long string B\n" + "long string A\n")); +} + +static void +test_abutting_neighbor () +{ + style_manager sm; + x_ruler r (x_ruler::label_dir::BELOW); + r.add_label (canvas::range_t (0, 11), + styled_string (sm, "very long string A"), + style::id_plain); + r.add_label (canvas::range_t (10, 16), + styled_string (sm, "very long string B"), + style::id_plain); + ASSERT_X_RULER_STREQ + (r, unicode_theme (), sm, true, + ("├────┬────┼─┬──┤\n" + " │ │\n" + " │very long string B\n" + "very long string A\n")); +} + +static void +test_gaps () +{ + style_manager sm; + x_ruler r (x_ruler::label_dir::BELOW); + r.add_label (canvas::range_t (0, 5), + styled_string (sm, "foo"), + style::id_plain); + r.add_label (canvas::range_t (10, 15), + styled_string (sm, "bar"), + style::id_plain); + ASSERT_X_RULER_STREQ + (r, ascii_theme (), sm, true, + ("|~+~| |~+~|\n" + " | |\n" + " foo bar\n")); +} + +static void +test_styled () +{ + style_manager sm; + style s1, s2; + s1.m_bold = true; + s1.m_fg_color = style::named_color::YELLOW; + s2.m_bold = true; + s2.m_fg_color = style::named_color::BLUE; + style::id_t sid1 = sm.get_or_create_id (s1); + style::id_t sid2 = sm.get_or_create_id (s2); + + x_ruler r (x_ruler::label_dir::BELOW); + r.add_label (canvas::range_t (0, 5), styled_string (sm, "foo"), sid1); + r.add_label (canvas::range_t (10, 15), styled_string (sm, "bar"), sid2); + ASSERT_X_RULER_STREQ + (r, ascii_theme (), sm, true, + ("[00;01;33m[K|~+~|[00m[K [00;01;34m[K|~+~|[00m[K\n" + " [00;01;33m[K|[00m[K [00;01;34m[K|[00m[K\n" + " foo bar\n")); +} + +static void +test_borders () +{ + style_manager sm; + { + x_ruler r (x_ruler::label_dir::BELOW); + r.add_label (canvas::range_t (0, 5), + styled_string (sm, "label 1"), + style::id_plain, + x_ruler::label_kind::TEXT_WITH_BORDER); + r.add_label (canvas::range_t (10, 15), + styled_string (sm, "label 2"), + style::id_plain); + r.add_label (canvas::range_t (20, 25), + styled_string (sm, "label 3"), + style::id_plain, + x_ruler::label_kind::TEXT_WITH_BORDER); + ASSERT_X_RULER_STREQ + (r, ascii_theme (), sm, true, + "|~+~| |~+~| |~+~|\n" + " | | |\n" + " | label 2 +---+---+\n" + "+-+-----+ |label 3|\n" + "|label 1| +-------+\n" + "+-------+\n"); + ASSERT_X_RULER_STREQ + (r, unicode_theme (), sm, true, + "├─┬─┤ ├─┬─┤ ├─┬─┤\n" + " │ │ │\n" + " │ label 2 ╭───┴───╮\n" + "╭─┴─────╮ │label 3│\n" + "│label 1│ ╰───────╯\n" + "╰───────╯\n"); + } + { + x_ruler r (x_ruler::label_dir::ABOVE); + r.add_label (canvas::range_t (0, 5), + styled_string (sm, "label 1"), + style::id_plain, + x_ruler::label_kind::TEXT_WITH_BORDER); + r.add_label (canvas::range_t (10, 15), + styled_string (sm, "label 2"), + style::id_plain); + r.add_label (canvas::range_t (20, 25), + styled_string (sm, "label 3"), + style::id_plain, + x_ruler::label_kind::TEXT_WITH_BORDER); + ASSERT_X_RULER_STREQ + (r, ascii_theme (), sm, true, + "+-------+\n" + "|label 1| +-------+\n" + "+-+-----+ |label 3|\n" + " | label 2 +---+---+\n" + " | | |\n" + "|~+~| |~+~| |~+~|\n"); + ASSERT_X_RULER_STREQ + (r, unicode_theme (), sm, true, + "╭───────╮\n" + "│label 1│ ╭───────╮\n" + "╰─┬─────╯ │label 3│\n" + " │ label 2 ╰───┬───╯\n" + " │ │ │\n" + "├─┴─┤ ├─┴─┤ ├─┴─┤\n"); + } +} + +static void +test_emoji () +{ + style_manager sm; + + styled_string s; + s.append (styled_string (0x26A0, /* U+26A0 WARNING SIGN. */ + true)); + s.append (styled_string (sm, " ")); + s.append (styled_string (sm, "this is a warning")); + + x_ruler r (x_ruler::label_dir::BELOW); + r.add_label (canvas::range_t (0, 5), + std::move (s), + style::id_plain, + x_ruler::label_kind::TEXT_WITH_BORDER); + + ASSERT_X_RULER_STREQ + (r, ascii_theme (), sm, true, + "|~+~|\n" + " |\n" + "+-+------------------+\n" + "|⚠️ this is a warning|\n" + "+--------------------+\n"); +} + +/* Run all selftests in this file. */ + +void +text_art_ruler_cc_tests () +{ + test_single (); + test_single_above (); + test_multiple_contiguous (); + test_multiple_contiguous_above (); + test_multiple_contiguous_abutting_labels (); + test_multiple_contiguous_overlapping_labels (); + test_abutting_left_border (); + test_too_long_to_consolidate_vertically (); + test_abutting_neighbor (); + test_gaps (); + test_styled (); + test_borders (); + test_emoji (); +} + +} // namespace selftest + + +#endif /* #if CHECKING_P */ diff --git a/gcc/text-art/ruler.h b/gcc/text-art/ruler.h new file mode 100644 index 00000000000..31f53549836 --- /dev/null +++ b/gcc/text-art/ruler.h @@ -0,0 +1,125 @@ +/* Classes for printing labelled rulers. + Copyright (C) 2023 Free Software Foundation, Inc. + Contributed by David Malcolm <dmalcolm@redhat.com>. + +This file is part of GCC. + +GCC is free software; you can redistribute it and/or modify it +under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 3, or (at your option) +any later version. + +GCC is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GCC; see the file COPYING3. If not see +<http://www.gnu.org/licenses/>. */ + +#ifndef GCC_TEXT_ART_RULER_H +#define GCC_TEXT_ART_RULER_H + +#include "text-art/canvas.h" + +namespace text_art { + +/* A way to annotate a series of ranges of canvas coordinates + with text labels either above or, in this example, below: + ├───────┬───────┼───────┬───────┼───────┬───────┤ + │ │ │ + label A label B label C + with logic to ensure that the text labels don't overlap + when printed. */ + +class x_ruler +{ + public: + enum class label_dir { ABOVE, BELOW }; + enum class label_kind + { + TEXT, + TEXT_WITH_BORDER + }; + + x_ruler (label_dir dir) + : m_label_dir (dir), + m_size (canvas::size_t (0, 0)), + m_has_layout (false) + {} + + void add_label (const canvas::range_t &r, + styled_string text, + style::id_t style_id, + label_kind kind = label_kind::TEXT); + + canvas::size_t get_size () + { + ensure_layout (); + return m_size; + } + + void paint_to_canvas (canvas &canvas, + canvas::coord_t offset, + const theme &theme); + + void debug (const style_manager &sm); + + private: + /* A particular label within an x_ruler. + Consider e.g.: + + # x: 01234567890123456789012345678901234567890123456789 + # y: 0: ├───────┬───────┼───────┬───────┼───────┬───────┤ + # 1: │ │ │ + # 2: label A label B label C + # + + Then "label A" is: + + # m_connector_x == 8 + # V + # x: 0123456789012 + # y: 0: ┬ + # 1: │ + # 2: label A + # x: 0123456789012 + # ^ + # m_text_coord.x == 6 + + and m_text_coord is (2, 6). + The y cooordinates are stored with respect to label_dir::BELOW; + for label_dir::ABOVE we flip them when painting the ruler. */ + class label + { + friend class x_ruler; + public: + label (const canvas::range_t &range, styled_string text, style::id_t style_id, + label_kind kind); + + bool operator< (const label &other) const; + + private: + canvas::range_t m_range; + styled_string m_text; + style::id_t m_style_id; + label_kind m_kind; + canvas::rect_t m_text_rect; // includes any border + int m_connector_x; + }; + + void ensure_layout (); + void update_layout (); + int get_canvas_y (int rel_y) const; + + label_dir m_label_dir; + std::vector<label> m_labels; + canvas::size_t m_size; + bool m_has_layout = false; + +}; + +} // namespace text_art + +#endif /* GCC_TEXT_ART_RULER_H */ diff --git a/gcc/text-art/selftests.cc b/gcc/text-art/selftests.cc new file mode 100644 index 00000000000..60ad003b549 --- /dev/null +++ b/gcc/text-art/selftests.cc @@ -0,0 +1,77 @@ +/* Selftests for text art. + Copyright (C) 2023 Free Software Foundation, Inc. + Contributed by David Malcolm <dmalcolm@redhat.com>. + +This file is part of GCC. + +GCC is free software; you can redistribute it and/or modify it under +the terms of the GNU General Public License as published by the Free +Software Foundation; either version 3, or (at your option) any later +version. + +GCC is distributed in the hope that it will be useful, but WITHOUT ANY +WARRANTY; without even the implied warranty of MERCHANTABILITY or +FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License +for more details. + +You should have received a copy of the GNU General Public License +along with GCC; see the file COPYING3. If not see +<http://www.gnu.org/licenses/>. */ + +#include "config.h" +#include "system.h" +#include "coretypes.h" +#include "selftest.h" +#include "pretty-print.h" +#include "text-art/selftests.h" +#include "text-art/canvas.h" + +#if CHECKING_P + +/* Run all tests, aborting if any fail. */ + +void +selftest::text_art_tests () +{ + text_art_style_cc_tests (); + text_art_styled_string_cc_tests (); + + text_art_box_drawing_cc_tests (); + text_art_canvas_cc_tests (); + text_art_ruler_cc_tests (); + text_art_table_cc_tests (); + text_art_widget_cc_tests (); +} + +/* Implementation detail of ASSERT_CANVAS_STREQ. */ + +void +selftest::assert_canvas_streq (const location &loc, + const text_art::canvas &canvas, + pretty_printer *pp, + const char *expected_str) +{ + canvas.print_to_pp (pp); + if (0) + fprintf (stderr, "%s\n", pp_formatted_text (pp)); + ASSERT_STREQ_AT (loc, pp_formatted_text (pp), expected_str); +} + +/* Implementation detail of ASSERT_CANVAS_STREQ. */ + +void +selftest::assert_canvas_streq (const location &loc, + const text_art::canvas &canvas, + bool styled, + const char *expected_str) +{ + pretty_printer pp; + if (styled) + { + pp_show_color (&pp) = true; + pp.url_format = URL_FORMAT_DEFAULT; + } + assert_canvas_streq (loc, canvas, &pp, expected_str); +} + +#endif /* #if CHECKING_P */ diff --git a/gcc/text-art/selftests.h b/gcc/text-art/selftests.h new file mode 100644 index 00000000000..706a1d8b5d6 --- /dev/null +++ b/gcc/text-art/selftests.h @@ -0,0 +1,60 @@ +/* Copyright (C) 2023 Free Software Foundation, Inc. + Contributed by David Malcolm <dmalcolm@redhat.com>. + +This file is part of GCC. + +GCC is free software; you can redistribute it and/or modify it under +the terms of the GNU General Public License as published by the Free +Software Foundation; either version 3, or (at your option) any later +version. + +GCC is distributed in the hope that it will be useful, but WITHOUT ANY +WARRANTY; without even the implied warranty of MERCHANTABILITY or +FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License +for more details. + +You should have received a copy of the GNU General Public License +along with GCC; see the file COPYING3. If not see +<http://www.gnu.org/licenses/>. */ + +#ifndef GCC_TEXT_ART_SELFTESTS_H +#define GCC_TEXT_ART_SELFTESTS_H + +#if CHECKING_P + +#include "text-art/types.h" + +namespace selftest { + +extern void text_art_box_drawing_cc_tests (); +extern void text_art_canvas_cc_tests (); +extern void text_art_ruler_cc_tests (); +extern void text_art_style_cc_tests (); +extern void text_art_styled_string_cc_tests (); +extern void text_art_table_cc_tests (); +extern void text_art_widget_cc_tests (); + +extern void text_art_tests (); + +extern void assert_canvas_streq (const location &loc, + const text_art::canvas &canvas, + pretty_printer *pp, + const char *expected_str); +extern void assert_canvas_streq (const location &loc, + const text_art::canvas &canvas, + bool styled, + const char *expected_str); + +#define ASSERT_CANVAS_STREQ(CANVAS, STYLED, EXPECTED_STR) \ + SELFTEST_BEGIN_STMT \ + assert_canvas_streq ((SELFTEST_LOCATION), \ + (CANVAS), \ + (STYLED), \ + (EXPECTED_STR)); \ + SELFTEST_END_STMT + +} /* end of namespace selftest. */ + +#endif /* #if CHECKING_P */ + +#endif /* GCC_TEXT_ART_SELFTESTS_H */ diff --git a/gcc/text-art/style.cc b/gcc/text-art/style.cc new file mode 100644 index 00000000000..00b056336fc --- /dev/null +++ b/gcc/text-art/style.cc @@ -0,0 +1,632 @@ +/* Classes for styling text cells (color, URLs). + Copyright (C) 2023 Free Software Foundation, Inc. + Contributed by David Malcolm <dmalcolm@redhat.com>. + +This file is part of GCC. + +GCC is free software; you can redistribute it and/or modify it under +the terms of the GNU General Public License as published by the Free +Software Foundation; either version 3, or (at your option) any later +version. + +GCC is distributed in the hope that it will be useful, but WITHOUT ANY +WARRANTY; without even the implied warranty of MERCHANTABILITY or +FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License +for more details. + +You should have received a copy of the GNU General Public License +along with GCC; see the file COPYING3. If not see +<http://www.gnu.org/licenses/>. */ + +#include "config.h" +#define INCLUDE_ALGORITHM +#define INCLUDE_MEMORY +#include "system.h" +#include "coretypes.h" +#include "make-unique.h" +#include "pretty-print.h" +#include "intl.h" +#include "selftest.h" +#include "text-art/selftests.h" +#include "text-art/types.h" +#include "color-macros.h" + +using namespace text_art; + +/* class text_art::style. */ + +style & +style::set_style_url (const char *url) +{ + m_url.clear (); + while (*url) + m_url.push_back (*(url++)); + return *this; +} + +/* class text_art::style::color. */ + +bool +style::color::operator== (const style::color &other) const +{ + if (m_kind != other.m_kind) + return false; + switch (m_kind) + { + default: + gcc_unreachable (); + case kind::NAMED: + return (u.m_named.m_name == other.u.m_named.m_name + && u.m_named.m_bright == other.u.m_named.m_bright); + case kind::BITS_8: + return u.m_8bit == other.u.m_8bit; + case kind::BITS_24: + return (u.m_24bit.r == other.u.m_24bit.r + && u.m_24bit.g == other.u.m_24bit.g + && u.m_24bit.b == other.u.m_24bit.b); + } +} + +static void +ensure_separator (pretty_printer *pp, bool &need_separator) +{ + if (need_separator) + pp_string (pp, COLOR_SEPARATOR); + need_separator = true; +} + +void +style::color::print_sgr (pretty_printer *pp, + bool fg, + bool &need_separator) const +{ + switch (m_kind) + { + default: + gcc_unreachable (); + case kind::NAMED: + { + static const char * const fg_normal[] = {"", // reset, for DEFAULT + COLOR_FG_BLACK, + COLOR_FG_RED, + COLOR_FG_GREEN, + COLOR_FG_YELLOW, + COLOR_FG_BLUE, + COLOR_FG_MAGENTA, + COLOR_FG_CYAN, + COLOR_FG_WHITE}; + static const char * const fg_bright[] = {"", // reset, for DEFAULT + COLOR_FG_BRIGHT_BLACK, + COLOR_FG_BRIGHT_RED, + COLOR_FG_BRIGHT_GREEN, + COLOR_FG_BRIGHT_YELLOW, + COLOR_FG_BRIGHT_BLUE, + COLOR_FG_BRIGHT_MAGENTA, + COLOR_FG_BRIGHT_CYAN, + COLOR_FG_BRIGHT_WHITE}; + static const char * const bg_normal[] = {"", // reset, for DEFAULT + COLOR_BG_BLACK, + COLOR_BG_RED, + COLOR_BG_GREEN, + COLOR_BG_YELLOW, + COLOR_BG_BLUE, + COLOR_BG_MAGENTA, + COLOR_BG_CYAN, + COLOR_BG_WHITE}; + static const char * const bg_bright[] = {"", // reset, for DEFAULT + COLOR_BG_BRIGHT_BLACK, + COLOR_BG_BRIGHT_RED, + COLOR_BG_BRIGHT_GREEN, + COLOR_BG_BRIGHT_YELLOW, + COLOR_BG_BRIGHT_BLUE, + COLOR_BG_BRIGHT_MAGENTA, + COLOR_BG_BRIGHT_CYAN, + COLOR_BG_BRIGHT_WHITE}; + STATIC_ASSERT (ARRAY_SIZE (fg_normal) == ARRAY_SIZE (fg_bright)); + STATIC_ASSERT (ARRAY_SIZE (fg_normal) == ARRAY_SIZE (bg_normal)); + STATIC_ASSERT (ARRAY_SIZE (fg_normal) == ARRAY_SIZE (bg_bright)); + gcc_assert ((size_t)u.m_named.m_name < ARRAY_SIZE (fg_normal)); + const char *const *arr; + if (fg) + arr = u.m_named.m_bright ? fg_bright : fg_normal; + else + arr = u.m_named.m_bright ? bg_bright : bg_normal; + const char *str = arr[(size_t)u.m_named.m_name]; + if (strlen (str) > 0) + { + ensure_separator (pp, need_separator); + pp_string (pp, str); + } + } + break; + case kind::BITS_8: + { + ensure_separator (pp, need_separator); + if (fg) + pp_string (pp, "38"); + else + pp_string (pp, "48"); + pp_printf (pp, ";5;%i", (int)u.m_8bit); + } + break; + case kind::BITS_24: + { + ensure_separator (pp, need_separator); + if (fg) + pp_string (pp, "38"); + else + pp_string (pp, "48"); + pp_printf (pp, ";2;%i;%i;%i", + (int)u.m_24bit.r, + (int)u.m_24bit.g, + (int)u.m_24bit.b); + } + break; + } +} + +/* class text_art::style. */ + +/* See https://www.ecma-international.org/wp-content/uploads/ECMA-48_5th_edition_june_1991.pdf + GRCM - GRAPHIC RENDITION COMBINATION MODE can be "REPLACING" or + "CUMULATIVE", which affects whether we need to respecify all attributes + at each SGR, or can accumulate them. Looks like we can't rely on the value + of this, so we have to emit a single SGR for all changes, with a "0" reset + at the front, forcing it to be effectively replacing. */ + +void +style::print_changes (pretty_printer *pp, + const style &old_style, + const style &new_style) +{ + if (pp_show_color (pp)) + { + bool needs_sgr = ((old_style.m_bold != new_style.m_bold) + || (old_style.m_underscore != new_style.m_underscore) + || (old_style.m_blink != new_style.m_blink) + || (old_style.m_fg_color != new_style.m_fg_color) + || (old_style.m_bg_color != new_style.m_bg_color)); + if (needs_sgr) + { + bool emit_reset = (old_style.m_bold + || new_style.m_bold + || old_style.m_underscore + || new_style.m_underscore + || old_style.m_blink + || new_style.m_blink); + bool need_separator = false; + + pp_string (pp, SGR_START); + if (emit_reset) + { + pp_string (pp, COLOR_NONE); + need_separator = true; + } + if (new_style.m_bold) + { + gcc_assert (emit_reset); + ensure_separator (pp, need_separator); + pp_string (pp, COLOR_BOLD); + } + if (new_style.m_underscore) + { + gcc_assert (emit_reset); + ensure_separator (pp, need_separator); + pp_string (pp, COLOR_UNDERSCORE); + } + if (new_style.m_blink) + { + gcc_assert (emit_reset); + ensure_separator (pp, need_separator); + pp_string (pp, COLOR_BLINK); + } + new_style.m_fg_color.print_sgr (pp, true, need_separator); + new_style.m_bg_color.print_sgr (pp, false, need_separator); + pp_string (pp, SGR_END); + } + } + + if (old_style.m_url != new_style.m_url) + { + if (!old_style.m_url.empty ()) + pp_end_url (pp); + if (pp->url_format != URL_FORMAT_NONE + && !new_style.m_url.empty ()) + { + /* Adapted from pp_begin_url, but encoding the + chars to UTF-8 on the fly, rather than converting + to a buffer. */ + pp_string (pp, "\33]8;;"); + for (auto ch : new_style.m_url) + pp_unicode_character (pp, ch); + switch (pp->url_format) + { + default: + case URL_FORMAT_NONE: + gcc_unreachable (); + case URL_FORMAT_ST: + pp_string (pp, "\33\\"); + break; + case URL_FORMAT_BEL: + pp_string (pp, "\a"); + break; + } + } + } +} + +/* class text_art::style_manager. */ + +style_manager::style_manager () +{ + // index 0 will be the default style + m_styles.push_back (style ()); +} + +style::id_t +style_manager::get_or_create_id (const style &s) +{ + // For now, linear search + std::vector<style>::iterator existing + (std::find (m_styles.begin (), m_styles.end (), s)); + + /* If found, return index of slot. */ + if (existing != m_styles.end ()) + return std::distance (m_styles.begin (), existing); + + /* Not found. */ + + /* styled_str uses 7 bits for style information, so we can only support + up to 128 different style combinations. + Gracefully fail by turning off styling when this limit is reached. */ + if (m_styles.size () >= 127) + return 0; + + m_styles.push_back (s); + return m_styles.size () - 1; +} + +void +style_manager::print_any_style_changes (pretty_printer *pp, + style::id_t old_id, + style::id_t new_id) const +{ + gcc_assert (pp); + if (old_id == new_id) + return; + + const style &old_style = m_styles[old_id]; + const style &new_style = m_styles[new_id]; + gcc_assert (!(old_style == new_style)); + style::print_changes (pp, old_style, new_style); +} + +#if CHECKING_P + +namespace selftest { + +void +assert_style_change_streq (const location &loc, + const style &old_style, + const style &new_style, + const char *expected_str) +{ + pretty_printer pp; + pp_show_color (&pp) = true; + style::print_changes (&pp, old_style, new_style); + ASSERT_STREQ_AT (loc, pp_formatted_text (&pp), expected_str); +} + +#define ASSERT_STYLE_CHANGE_STREQ(OLD_STYLE, NEW_STYLE, EXPECTED_STR) \ + SELFTEST_BEGIN_STMT \ + assert_style_change_streq ((SELFTEST_LOCATION), \ + (OLD_STYLE), \ + (NEW_STYLE), \ + (EXPECTED_STR)); \ + SELFTEST_END_STMT + +static void +test_bold () +{ + style_manager sm; + ASSERT_EQ (sm.get_num_styles (), 1); + + style plain; + ASSERT_EQ (sm.get_or_create_id (plain), 0); + ASSERT_EQ (sm.get_num_styles (), 1); + + style bold; + bold.m_bold = true; + + ASSERT_EQ (sm.get_or_create_id (bold), 1); + ASSERT_EQ (sm.get_num_styles (), 2); + ASSERT_EQ (sm.get_or_create_id (bold), 1); + ASSERT_EQ (sm.get_num_styles (), 2); + + ASSERT_STYLE_CHANGE_STREQ (plain, bold, "\33[00;01m\33[K"); + ASSERT_STYLE_CHANGE_STREQ (bold, plain, "\33[00m\33[K"); +} + +static void +test_underscore () +{ + style_manager sm; + ASSERT_EQ (sm.get_num_styles (), 1); + + style plain; + ASSERT_EQ (sm.get_or_create_id (plain), 0); + ASSERT_EQ (sm.get_num_styles (), 1); + + style underscore; + underscore.m_underscore = true; + + ASSERT_EQ (sm.get_or_create_id (underscore), 1); + ASSERT_EQ (sm.get_num_styles (), 2); + ASSERT_EQ (sm.get_or_create_id (underscore), 1); + ASSERT_EQ (sm.get_num_styles (), 2); + + ASSERT_STYLE_CHANGE_STREQ (plain, underscore, "\33[00;04m\33[K"); + ASSERT_STYLE_CHANGE_STREQ (underscore, plain, "\33[00m\33[K"); +} + +static void +test_blink () +{ + style_manager sm; + ASSERT_EQ (sm.get_num_styles (), 1); + + style plain; + ASSERT_EQ (sm.get_or_create_id (plain), 0); + ASSERT_EQ (sm.get_num_styles (), 1); + + style blink; + blink.m_blink = true; + + ASSERT_EQ (sm.get_or_create_id (blink), 1); + ASSERT_EQ (sm.get_num_styles (), 2); + ASSERT_EQ (sm.get_or_create_id (blink), 1); + ASSERT_EQ (sm.get_num_styles (), 2); + + ASSERT_STYLE_CHANGE_STREQ (plain, blink, "\33[00;05m\33[K"); + ASSERT_STYLE_CHANGE_STREQ (blink, plain, "\33[00m\33[K"); +} + +#define ASSERT_NAMED_COL_STREQ(NAMED_COLOR, FG, BRIGHT, EXPECTED_STR) \ + SELFTEST_BEGIN_STMT \ + { \ + style plain; \ + style s; \ + if (FG) \ + s.m_fg_color = style::color ((NAMED_COLOR), (BRIGHT)); \ + else \ + s.m_bg_color = style::color ((NAMED_COLOR), (BRIGHT)); \ + assert_style_change_streq ((SELFTEST_LOCATION), \ + plain, \ + s, \ + (EXPECTED_STR)); \ + } \ + SELFTEST_END_STMT + +static void +test_named_colors () +{ + /* Foreground colors. */ + { + const bool fg = true; + { + const bool bright = false; + ASSERT_NAMED_COL_STREQ (style::named_color::DEFAULT, fg, bright, ""); + ASSERT_NAMED_COL_STREQ (style::named_color::BLACK, fg, bright, + "[30m[K"); + ASSERT_NAMED_COL_STREQ (style::named_color::RED, fg, bright, + "[31m[K"); + ASSERT_NAMED_COL_STREQ (style::named_color::GREEN, fg, bright, + "[32m[K"); + ASSERT_NAMED_COL_STREQ (style::named_color::YELLOW, fg, bright, + "[33m[K"); + ASSERT_NAMED_COL_STREQ (style::named_color::BLUE, fg, bright, + "[34m[K"); + ASSERT_NAMED_COL_STREQ (style::named_color::MAGENTA, fg, bright, + "[35m[K"); + ASSERT_NAMED_COL_STREQ (style::named_color::CYAN, fg, bright, + "[36m[K"); + ASSERT_NAMED_COL_STREQ (style::named_color::WHITE, fg, bright, + "[37m[K"); + } + { + const bool bright = true; + ASSERT_NAMED_COL_STREQ (style::named_color::DEFAULT, fg, bright, + "[m[K"); + ASSERT_NAMED_COL_STREQ (style::named_color::BLACK, fg, bright, + "[90m[K"); + ASSERT_NAMED_COL_STREQ (style::named_color::RED, fg, bright, + "[91m[K"); + ASSERT_NAMED_COL_STREQ (style::named_color::GREEN, fg, bright, + "[92m[K"); + ASSERT_NAMED_COL_STREQ (style::named_color::YELLOW, fg, bright, + "[93m[K"); + ASSERT_NAMED_COL_STREQ (style::named_color::BLUE, fg, bright, + "[94m[K"); + ASSERT_NAMED_COL_STREQ (style::named_color::MAGENTA, fg, bright, + "[95m[K"); + ASSERT_NAMED_COL_STREQ (style::named_color::CYAN, fg, bright, + "[96m[K"); + ASSERT_NAMED_COL_STREQ (style::named_color::WHITE, fg, bright, + "[97m[K"); + } + } + + /* Background colors. */ + { + const bool fg = false; + { + const bool bright = false; + ASSERT_NAMED_COL_STREQ (style::named_color::DEFAULT, fg, bright, ""); + ASSERT_NAMED_COL_STREQ (style::named_color::BLACK, fg, bright, + "[40m[K"); + ASSERT_NAMED_COL_STREQ (style::named_color::RED, fg, bright, + "[41m[K"); + ASSERT_NAMED_COL_STREQ (style::named_color::GREEN, fg, bright, + "[42m[K"); + ASSERT_NAMED_COL_STREQ (style::named_color::YELLOW, fg, bright, + "[43m[K"); + ASSERT_NAMED_COL_STREQ (style::named_color::BLUE, fg, bright, + "[44m[K"); + ASSERT_NAMED_COL_STREQ (style::named_color::MAGENTA, fg, bright, + "[45m[K"); + ASSERT_NAMED_COL_STREQ (style::named_color::CYAN, fg, bright, + "[46m[K"); + ASSERT_NAMED_COL_STREQ (style::named_color::WHITE, fg, bright, + "[47m[K"); + } + { + const bool bright = true; + ASSERT_NAMED_COL_STREQ (style::named_color::DEFAULT, fg, bright, + "[m[K"); + ASSERT_NAMED_COL_STREQ (style::named_color::BLACK, fg, bright, + "[100m[K"); + ASSERT_NAMED_COL_STREQ (style::named_color::RED, fg, bright, + "[101m[K"); + ASSERT_NAMED_COL_STREQ (style::named_color::GREEN, fg, bright, + "[102m[K"); + ASSERT_NAMED_COL_STREQ (style::named_color::YELLOW, fg, bright, + "[103m[K"); + ASSERT_NAMED_COL_STREQ (style::named_color::BLUE, fg, bright, + "[104m[K"); + ASSERT_NAMED_COL_STREQ (style::named_color::MAGENTA, fg, bright, + "[105m[K"); + ASSERT_NAMED_COL_STREQ (style::named_color::CYAN, fg, bright, + "[106m[K"); + ASSERT_NAMED_COL_STREQ (style::named_color::WHITE, fg, bright, + "[107m[K"); + } + } +} + +#define ASSERT_8_BIT_COL_STREQ(COL_VAL, FG, EXPECTED_STR) \ + SELFTEST_BEGIN_STMT \ + { \ + style plain; \ + style s; \ + if (FG) \ + s.m_fg_color = style::color (COL_VAL); \ + else \ + s.m_bg_color = style::color (COL_VAL); \ + assert_style_change_streq ((SELFTEST_LOCATION), \ + plain, \ + s, \ + (EXPECTED_STR)); \ + } \ + SELFTEST_END_STMT + +static void +test_8_bit_colors () +{ + /* Foreground colors. */ + { + const bool fg = true; + /* 0-15: standard and high-intensity standard colors. */ + ASSERT_8_BIT_COL_STREQ (0, fg, "[38;5;0m[K"); + ASSERT_8_BIT_COL_STREQ (15, fg, "[38;5;15m[K"); + /* 16-231: 6x6x6 color cube. */ + ASSERT_8_BIT_COL_STREQ (16, fg, "[38;5;16m[K"); + ASSERT_8_BIT_COL_STREQ (231, fg, "[38;5;231m[K"); + /* 232-255: grayscale. */ + ASSERT_8_BIT_COL_STREQ (232, fg, "[38;5;232m[K"); + ASSERT_8_BIT_COL_STREQ (255, fg, "[38;5;255m[K"); + } + /* Background colors. */ + { + const bool fg = false; + /* 0-15: standard and high-intensity standard colors. */ + ASSERT_8_BIT_COL_STREQ (0, fg, "[48;5;0m[K"); + ASSERT_8_BIT_COL_STREQ (15, fg, "[48;5;15m[K"); + /* 16-231: 6x6x6 color cube. */ + ASSERT_8_BIT_COL_STREQ (16, fg, "[48;5;16m[K"); + ASSERT_8_BIT_COL_STREQ (231, fg, "[48;5;231m[K"); + /* 232-255: grayscale. */ + ASSERT_8_BIT_COL_STREQ (232, fg, "[48;5;232m[K"); + ASSERT_8_BIT_COL_STREQ (255, fg, "[48;5;255m[K"); + } +} + +#define ASSERT_24_BIT_COL_STREQ(R, G, B, FG, EXPECTED_STR) \ + SELFTEST_BEGIN_STMT \ + { \ + style plain; \ + style s; \ + if (FG) \ + s.m_fg_color = style::color ((R), (G), (B)); \ + else \ + s.m_bg_color = style::color ((R), (G), (B)); \ + assert_style_change_streq ((SELFTEST_LOCATION), \ + plain, \ + s, \ + (EXPECTED_STR)); \ + } \ + SELFTEST_END_STMT + +static void +test_24_bit_colors () +{ + /* Foreground colors. */ + { + const bool fg = true; + // #F3FAF2: + ASSERT_24_BIT_COL_STREQ (0xf3, 0xfa, 0xf2, fg, + "[38;2;243;250;242m[K"); + } + /* Background colors. */ + { + const bool fg = false; + // #FDF7E7 + ASSERT_24_BIT_COL_STREQ (0xfd, 0xf7, 0xe7, fg, + "[48;2;253;247;231m[K"); + } +} + +static void +test_style_combinations () +{ + style_manager sm; + ASSERT_EQ (sm.get_num_styles (), 1); + + style plain; + ASSERT_EQ (sm.get_or_create_id (plain), 0); + ASSERT_EQ (sm.get_num_styles (), 1); + + style bold; + bold.m_bold = true; + + ASSERT_EQ (sm.get_or_create_id (bold), 1); + ASSERT_EQ (sm.get_num_styles (), 2); + ASSERT_EQ (sm.get_or_create_id (bold), 1); + ASSERT_EQ (sm.get_num_styles (), 2); + + style magenta_on_blue; + magenta_on_blue.m_fg_color = style::named_color::MAGENTA; + magenta_on_blue.m_bg_color = style::named_color::BLUE; + ASSERT_EQ (sm.get_or_create_id (magenta_on_blue), 2); + ASSERT_EQ (sm.get_num_styles (), 3); + ASSERT_EQ (sm.get_or_create_id (magenta_on_blue), 2); + ASSERT_EQ (sm.get_num_styles (), 3); +} + +/* Run all selftests in this file. */ + +void +text_art_style_cc_tests () +{ + test_bold (); + test_underscore (); + test_blink (); + test_named_colors (); + test_8_bit_colors (); + test_24_bit_colors (); + test_style_combinations (); +} + +} // namespace selftest + + +#endif /* #if CHECKING_P */ diff --git a/gcc/text-art/styled-string.cc b/gcc/text-art/styled-string.cc new file mode 100644 index 00000000000..cd176b2313f --- /dev/null +++ b/gcc/text-art/styled-string.cc @@ -0,0 +1,1107 @@ +/* Implementation of text_art::styled_string. + Copyright (C) 2023 Free Software Foundation, Inc. + Contributed by David Malcolm <dmalcolm@redhat.com>. + +This file is part of GCC. + +GCC is free software; you can redistribute it and/or modify it under +the terms of the GNU General Public License as published by the Free +Software Foundation; either version 3, or (at your option) any later +version. + +GCC is distributed in the hope that it will be useful, but WITHOUT ANY +WARRANTY; without even the implied warranty of MERCHANTABILITY or +FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License +for more details. + +You should have received a copy of the GNU General Public License +along with GCC; see the file COPYING3. If not see +<http://www.gnu.org/licenses/>. */ + +#include "config.h" +#define INCLUDE_MEMORY +#include "system.h" +#include "coretypes.h" +#include "make-unique.h" +#include "pretty-print.h" +#include "intl.h" +#include "diagnostic.h" +#include "selftest.h" +#include "text-art/selftests.h" +#include "text-art/types.h" +#include "color-macros.h" + +using namespace text_art; + +namespace { + +/* Support class for parsing text containing escape codes. + See e.g. https://en.wikipedia.org/wiki/ANSI_escape_code + We only support the codes that pretty-print.cc can generate. */ + +class escape_code_parser +{ +public: + escape_code_parser (style_manager &sm, + std::vector<styled_unichar> &out) + : m_sm (sm), + m_out (out), + m_cur_style_obj (), + m_cur_style_id (style::id_plain), + m_state (state::START) + { + } + + void on_char (cppchar_t ch) + { + switch (m_state) + { + default: + gcc_unreachable (); + case state::START: + if (ch == '\033') + { + /* The start of an escape sequence. */ + m_state = state::AFTER_ESC; + return; + } + break; + case state::AFTER_ESC: + if (ch == '[') + { + /* ESC [ is a Control Sequence Introducer. */ + m_state = state::CS_PARAMETER_BYTES; + return; + } + else if (ch == ']') + { + /* ESC ] is an Operating System Command. */ + m_state = state::WITHIN_OSC; + return; + } + break; + case state::CS_PARAMETER_BYTES: + if (parameter_byte_p (ch)) + { + m_parameter_bytes.push_back ((char)ch); + return; + } + else if (intermediate_byte_p (ch)) + { + m_intermediate_bytes.push_back ((char)ch); + m_state = state::CS_INTERMEDIATE_BYTES; + return; + } + else if (final_byte_p (ch)) + { + on_final_csi_char (ch); + return; + } + break; + case state::CS_INTERMEDIATE_BYTES: + /* Expect zero or more intermediate bytes. */ + if (intermediate_byte_p (ch)) + { + m_intermediate_bytes.push_back ((char)ch); + return; + } + else if (final_byte_p (ch)) + { + on_final_csi_char (ch); + return; + } + break; + case state::WITHIN_OSC: + /* Accumulate chars into m_osc_string, until we see an ST or a BEL. */ + { + /* Check for ESC \, the String Terminator (aka "ST"). */ + if (ch == '\\' + && m_osc_string.size () > 0 + && m_osc_string.back () == '\033') + { + m_osc_string.pop_back (); + on_final_osc_char (); + return; + } + else if (ch == '\a') + { + // BEL + on_final_osc_char (); + return; + } + m_osc_string.push_back (ch); + return; + } + break; + } + + /* Test of handling U+FE0F VARIATION SELECTOR-16 to select the emoji + variation for the previous character. */ + if (ch == 0xFE0F) + { + if (m_out.size () > 0) + m_out.back ().set_emoji_variant (); + return; + } + + if (cpp_is_combining_char (ch)) + { + if (m_out.size () > 0) + { + m_out.back ().add_combining_char (ch); + return; + } + } + /* By default, add the char. */ + m_out.push_back (styled_unichar (ch, false, m_cur_style_id)); + } + +private: + void on_final_csi_char (cppchar_t ch) + { + switch (ch) + { + default: + /* Unrecognized. */ + break; + case 'm': + { + /* SGR control sequence. */ + if (m_parameter_bytes.empty ()) + reset_style (); + std::vector<int> params (params_from_decimal ()); + for (auto iter = params.begin (); iter != params.end (); ) + { + const int param = *iter; + switch (param) + { + default: + /* Unrecognized SGR parameter. */ + break; + case 0: + reset_style (); + break; + case 1: + set_style_bold (); + break; + case 4: + set_style_underscore (); + break; + case 5: + set_style_blink (); + break; + + /* Named foreground colors. */ + case 30: + set_style_fg_color (style::named_color::BLACK); + break; + case 31: + set_style_fg_color (style::named_color::RED); + break; + case 32: + set_style_fg_color (style::named_color::GREEN); + break; + case 33: + set_style_fg_color (style::named_color::YELLOW); + break; + case 34: + set_style_fg_color (style::named_color::BLUE); + break; + case 35: + set_style_fg_color (style::named_color::MAGENTA); + break; + case 36: + set_style_fg_color (style::named_color::CYAN); + break; + case 37: + set_style_fg_color (style::named_color::WHITE); + break; + + /* 8-bit and 24-bit color */ + case 38: + case 48: + { + const bool fg = (param == 38); + iter++; + if (iter != params.end ()) + switch (*(iter++)) + { + default: + break; + case 5: + /* 8-bit color. */ + if (iter != params.end ()) + { + const uint8_t col = *(iter++); + if (fg) + set_style_fg_color (style::color (col)); + else + set_style_bg_color (style::color (col)); + } + continue; + case 2: + /* 24-bit color. */ + if (iter != params.end ()) + { + const uint8_t r = *(iter++); + if (iter != params.end ()) + { + const uint8_t g = *(iter++); + if (iter != params.end ()) + { + const uint8_t b = *(iter++); + if (fg) + set_style_fg_color (style::color (r, + g, + b)); + else + set_style_bg_color (style::color (r, + g, + b)); + } + } + } + continue; + } + continue; + } + break; + + /* Named background colors. */ + case 40: + set_style_bg_color (style::named_color::BLACK); + break; + case 41: + set_style_bg_color (style::named_color::RED); + break; + case 42: + set_style_bg_color (style::named_color::GREEN); + break; + case 43: + set_style_bg_color (style::named_color::YELLOW); + break; + case 44: + set_style_bg_color (style::named_color::BLUE); + break; + case 45: + set_style_bg_color (style::named_color::MAGENTA); + break; + case 46: + set_style_bg_color (style::named_color::CYAN); + break; + case 47: + set_style_bg_color (style::named_color::WHITE); + break; + + /* Named foreground colors, bright. */ + case 90: + set_style_fg_color (style::color (style::named_color::BLACK, + true)); + break; + case 91: + set_style_fg_color (style::color (style::named_color::RED, + true)); + break; + case 92: + set_style_fg_color (style::color (style::named_color::GREEN, + true)); + break; + case 93: + set_style_fg_color (style::color (style::named_color::YELLOW, + true)); + break; + case 94: + set_style_fg_color (style::color (style::named_color::BLUE, + true)); + break; + case 95: + set_style_fg_color (style::color (style::named_color::MAGENTA, + true)); + break; + case 96: + set_style_fg_color (style::color (style::named_color::CYAN, + true)); + break; + case 97: + set_style_fg_color (style::color (style::named_color::WHITE, + true)); + break; + + /* Named foreground colors, bright. */ + case 100: + set_style_bg_color (style::color (style::named_color::BLACK, + true)); + break; + case 101: + set_style_bg_color (style::color (style::named_color::RED, + true)); + break; + case 102: + set_style_bg_color (style::color (style::named_color::GREEN, + true)); + break; + case 103: + set_style_bg_color (style::color (style::named_color::YELLOW, + true)); + break; + case 104: + set_style_bg_color (style::color (style::named_color::BLUE, + true)); + break; + case 105: + set_style_bg_color (style::color (style::named_color::MAGENTA, + true)); + break; + case 106: + set_style_bg_color (style::color (style::named_color::CYAN, + true)); + break; + case 107: + set_style_bg_color (style::color (style::named_color::WHITE, + true)); + break; + } + ++iter; + } + } + break; + } + m_parameter_bytes.clear (); + m_intermediate_bytes.clear (); + m_state = state::START; + } + + void on_final_osc_char () + { + if (!m_osc_string.empty ()) + { + switch (m_osc_string[0]) + { + default: + break; + case '8': + /* Hyperlink support; see: + https://gist.github.com/egmontkob/eb114294efbcd5adb1944c9f3cb5feda + We don't support params, so we expect either: + (a) "8;;URL" to begin a url (see pp_begin_url), or + (b) "8;;" to end a URL (see pp_end_url). */ + if (m_osc_string.size () >= 3 + && m_osc_string[1] == ';' + && m_osc_string[2] == ';') + { + set_style_url (m_osc_string.begin () + 3, + m_osc_string.end ()); + } + break; + } + } + m_osc_string.clear (); + m_state = state::START; + } + + std::vector<int> params_from_decimal () const + { + std::vector<int> result; + + int curr_int = -1; + for (auto param_ch : m_parameter_bytes) + { + if (param_ch >= '0' && param_ch <= '9') + { + if (curr_int == -1) + curr_int = 0; + else + curr_int *= 10; + curr_int += param_ch - '0'; + } + else + { + if (curr_int != -1) + { + result.push_back (curr_int); + curr_int = -1; + } + } + } + if (curr_int != -1) + result.push_back (curr_int); + return result; + } + + void refresh_style_id () + { + m_cur_style_id = m_sm.get_or_create_id (m_cur_style_obj); + } + void reset_style () + { + m_cur_style_obj = style (); + refresh_style_id (); + } + void set_style_bold () + { + m_cur_style_obj.m_bold = true; + refresh_style_id (); + } + void set_style_underscore () + { + m_cur_style_obj.m_underscore = true; + refresh_style_id (); + } + void set_style_blink () + { + m_cur_style_obj.m_blink = true; + refresh_style_id (); + } + void set_style_fg_color (style::color color) + { + m_cur_style_obj.m_fg_color = color; + refresh_style_id (); + } + void set_style_bg_color (style::color color) + { + m_cur_style_obj.m_bg_color = color; + refresh_style_id (); + } + void set_style_url (std::vector<cppchar_t>::iterator begin, + std::vector<cppchar_t>::iterator end) + { + // The empty string means "no URL" + m_cur_style_obj.m_url = std::vector<cppchar_t> (begin, end); + refresh_style_id (); + } + + static bool parameter_byte_p (cppchar_t ch) + { + return ch >= 0x30 && ch <= 0x3F; + } + + static bool intermediate_byte_p (cppchar_t ch) + { + return ch >= 0x20 && ch <= 0x2F; + } + + static bool final_byte_p (cppchar_t ch) + { + return ch >= 0x40 && ch <= 0x7E; + } + + style_manager &m_sm; + std::vector<styled_unichar> &m_out; + + style m_cur_style_obj; + style::id_t m_cur_style_id; + + /* Handling of control sequences. */ + enum class state + { + START, + + /* After ESC, expecting '['. */ + AFTER_ESC, + + /* Expecting zero or more parameter bytes, an + intermediate byte, or a final byte. */ + CS_PARAMETER_BYTES, + + /* Expecting zero or more intermediate bytes, or a final byte. */ + CS_INTERMEDIATE_BYTES, + + /* Within OSC. */ + WITHIN_OSC + + } m_state; + std::vector<char> m_parameter_bytes; + std::vector<char> m_intermediate_bytes; + std::vector<cppchar_t> m_osc_string; +}; + +} // anon namespace + +/* class text_art::styled_string. */ + +/* Construct a styled_string from STR. + STR is assumed to be UTF-8 encoded and 0-terminated. + + Parse SGR formatting chars from being in-band (within in the sequence + of chars) to being out-of-band, as style elements. + We only support parsing the subset of SGR chars that can be emitted + by pretty-print.cc */ + +styled_string::styled_string (style_manager &sm, const char *str) +: m_chars () +{ + escape_code_parser parser (sm, m_chars); + + /* We don't actually want the display widths here, but + it's an easy way to decode UTF-8. */ + cpp_char_column_policy policy (8, cpp_wcwidth); + cpp_display_width_computation dw (str, strlen (str), policy); + while (!dw.done ()) + { + cpp_decoded_char decoded_char; + dw.process_next_codepoint (&decoded_char); + + if (!decoded_char.m_valid_ch) + /* Skip bytes that aren't valid UTF-8. */ + continue; + + /* Decode SGR formatting. */ + cppchar_t ch = decoded_char.m_ch; + parser.on_char (ch); + } +} + +styled_string::styled_string (cppchar_t cppchar, bool emoji) +{ + m_chars.push_back (styled_unichar (cppchar, emoji, style::id_plain)); +} + +styled_string +styled_string::from_fmt_va (style_manager &sm, + printer_fn format_decoder, + const char *fmt, + va_list *args) +{ + text_info text; + text.err_no = errno; + text.args_ptr = args; + text.format_spec = fmt; + pretty_printer pp; + pp_show_color (&pp) = true; + pp.url_format = URL_FORMAT_DEFAULT; + pp_format_decoder (&pp) = format_decoder; + pp_format (&pp, &text); + pp_output_formatted_text (&pp); + styled_string result (sm, pp_formatted_text (&pp)); + return result; +} + +styled_string +styled_string::from_fmt (style_manager &sm, + printer_fn format_decoder, + const char *fmt, ...) +{ + va_list ap; + va_start (ap, fmt); + styled_string result = from_fmt_va (sm, format_decoder, fmt, &ap); + va_end (ap); + return result; +} + +int +styled_string::calc_canvas_width () const +{ + int result = 0; + for (auto ch : m_chars) + result += ch.get_canvas_width (); + return result; +} + +void +styled_string::append (const styled_string &suffix) +{ + m_chars.insert<std::vector<styled_unichar>::const_iterator> (m_chars.end (), + suffix.begin (), + suffix.end ()); +} + +void +styled_string::set_url (style_manager &sm, const char *url) +{ + for (auto& ch : m_chars) + { + const style &existing_style = sm.get_style (ch.get_style_id ()); + style with_url (existing_style); + with_url.set_style_url (url); + ch.m_style_id = sm.get_or_create_id (with_url); + } +} + +#if CHECKING_P + +namespace selftest { + +static void +test_combining_chars () +{ + /* This really ought to be in libcpp, but we don't have + selftests there. */ + ASSERT_FALSE (cpp_is_combining_char (0)); + ASSERT_FALSE (cpp_is_combining_char ('a')); + + /* COMBINING BREVE (U+0306). */ + ASSERT_TRUE (cpp_is_combining_char (0x0306)); + + /* U+5B57 CJK UNIFIED IDEOGRAPH-5B57. */ + ASSERT_FALSE (cpp_is_combining_char (0x5B57)); + + /* U+FE0F VARIATION SELECTOR-16. */ + ASSERT_FALSE (cpp_is_combining_char (0xFE0F)); +} + +static void +test_empty () +{ + style_manager sm; + styled_string s (sm, ""); + ASSERT_EQ (s.size (), 0); + ASSERT_EQ (s.calc_canvas_width (), 0); +} + +/* Test of a pure ASCII string with no escape codes. */ + +static void +test_simple () +{ + const char *c_str = "hello world!"; + style_manager sm; + styled_string s (sm, c_str); + ASSERT_EQ (s.size (), strlen (c_str)); + ASSERT_EQ (s.calc_canvas_width (), (int)strlen (c_str)); + for (size_t i = 0; i < strlen (c_str); i++) + { + ASSERT_EQ (s[i].get_code (), (cppchar_t)c_str[i]); + ASSERT_EQ (s[i].get_style_id (), 0); + } +} + +/* Test of decoding UTF-8. */ + +static void +test_pi_from_utf8 () +{ + /* U+03C0 "GREEK SMALL LETTER PI". */ + const char * const pi_utf8 = "\xCF\x80"; + + style_manager sm; + styled_string s (sm, pi_utf8); + ASSERT_EQ (s.size (), 1); + ASSERT_EQ (s.calc_canvas_width (), 1); + ASSERT_EQ (s[0].get_code (), 0x03c0); + ASSERT_EQ (s[0].emoji_variant_p (), false); + ASSERT_EQ (s[0].double_width_p (), false); + ASSERT_EQ (s[0].get_style_id (), 0); +} + +/* Test of double-width character. */ + +static void +test_emoji_from_utf8 () +{ + /* U+1F642 "SLIGHTLY SMILING FACE". */ + const char * const emoji_utf8 = "\xF0\x9F\x99\x82"; + + style_manager sm; + styled_string s (sm, emoji_utf8); + ASSERT_EQ (s.size (), 1); + ASSERT_EQ (s.calc_canvas_width (), 2); + ASSERT_EQ (s[0].get_code (), 0x1f642); + ASSERT_EQ (s[0].double_width_p (), true); + ASSERT_EQ (s[0].get_style_id (), 0); +} + +/* Test of handling U+FE0F VARIATION SELECTOR-16 to select the emoji + variation for the previous character. */ + +static void +test_emoji_variant_from_utf8 () +{ + const char * const emoji_utf8 + = (/* U+26A0 WARNING SIGN. */ + "\xE2\x9A\xA0" + /* U+FE0F VARIATION SELECTOR-16 (emoji variation selector). */ + "\xEF\xB8\x8F"); + + style_manager sm; + styled_string s (sm, emoji_utf8); + ASSERT_EQ (s.size (), 1); + ASSERT_EQ (s.calc_canvas_width (), 1); + ASSERT_EQ (s[0].get_code (), 0x26a0); + ASSERT_EQ (s[0].emoji_variant_p (), true); + ASSERT_EQ (s[0].double_width_p (), false); + ASSERT_EQ (s[0].get_style_id (), 0); +} + +static void +test_emoji_from_codepoint () +{ + styled_string s ((cppchar_t)0x1f642); + ASSERT_EQ (s.size (), 1); + ASSERT_EQ (s.calc_canvas_width (), 2); + ASSERT_EQ (s[0].get_code (), 0x1f642); + ASSERT_EQ (s[0].double_width_p (), true); + ASSERT_EQ (s[0].get_style_id (), 0); +} + +static void +test_from_mixed_width_utf8 () +{ + /* This UTF-8 string literal is of the form + before mojibake after + where the Japanese word "mojibake" is written as the following + four unicode code points: + U+6587 CJK UNIFIED IDEOGRAPH-6587 + U+5B57 CJK UNIFIED IDEOGRAPH-5B57 + U+5316 CJK UNIFIED IDEOGRAPH-5316 + U+3051 HIRAGANA LETTER KE. + Each of these is 3 bytes wide when encoded in UTF-8, whereas the + "before" and "after" are 1 byte per unicode character. */ + const char * const mixed_width_utf8 + = ("before " + + /* U+6587 CJK UNIFIED IDEOGRAPH-6587 + UTF-8: 0xE6 0x96 0x87 + C octal escaped UTF-8: \346\226\207. */ + "\346\226\207" + + /* U+5B57 CJK UNIFIED IDEOGRAPH-5B57 + UTF-8: 0xE5 0xAD 0x97 + C octal escaped UTF-8: \345\255\227. */ + "\345\255\227" + + /* U+5316 CJK UNIFIED IDEOGRAPH-5316 + UTF-8: 0xE5 0x8C 0x96 + C octal escaped UTF-8: \345\214\226. */ + "\345\214\226" + + /* U+3051 HIRAGANA LETTER KE + UTF-8: 0xE3 0x81 0x91 + C octal escaped UTF-8: \343\201\221. */ + "\343\201\221" + + " after"); + + style_manager sm; + styled_string s (sm, mixed_width_utf8); + ASSERT_EQ (s.size (), 6 + 1 + 4 + 1 + 5); + ASSERT_EQ (sm.get_num_styles (), 1); + + // We expect the Japanese characters to be double width. + ASSERT_EQ (s.calc_canvas_width (), 6 + 1 + (2 * 4) + 1 + 5); + + ASSERT_EQ (s[0].get_code (), 'b'); + ASSERT_EQ (s[0].double_width_p (), false); + ASSERT_EQ (s[1].get_code (), 'e'); + ASSERT_EQ (s[2].get_code (), 'f'); + ASSERT_EQ (s[3].get_code (), 'o'); + ASSERT_EQ (s[4].get_code (), 'r'); + ASSERT_EQ (s[5].get_code (), 'e'); + ASSERT_EQ (s[6].get_code (), ' '); + ASSERT_EQ (s[7].get_code (), 0x6587); + ASSERT_EQ (s[7].double_width_p (), true); + ASSERT_EQ (s[8].get_code (), 0x5B57); + ASSERT_EQ (s[9].get_code (), 0x5316); + ASSERT_EQ (s[10].get_code (), 0x3051); + ASSERT_EQ (s[11].get_code (), ' '); + ASSERT_EQ (s[12].get_code (), 'a'); + ASSERT_EQ (s[13].get_code (), 'f'); + ASSERT_EQ (s[14].get_code (), 't'); + ASSERT_EQ (s[15].get_code (), 'e'); + ASSERT_EQ (s[16].get_code (), 'r'); + + ASSERT_EQ (s[0].get_style_id (), 0); +} + +static void +assert_style_urleq (const location &loc, + const style &s, + const char *expected_str) +{ + ASSERT_EQ_AT (loc, s.m_url.size (), strlen (expected_str)); + for (size_t i = 0; i < s.m_url.size (); i++) + ASSERT_EQ_AT (loc, s.m_url[i], (cppchar_t)expected_str[i]); +} + +#define ASSERT_STYLE_URLEQ(STYLE, EXPECTED_STR) \ + assert_style_urleq ((SELFTEST_LOCATION), (STYLE), (EXPECTED_STR)) + +static void +test_url () +{ + // URL_FORMAT_ST + { + style_manager sm; + styled_string s + (sm, "\33]8;;http://example.com\33\\This is a link\33]8;;\33\\"); + const char *expected = "This is a link"; + ASSERT_EQ (s.size (), strlen (expected)); + ASSERT_EQ (s.calc_canvas_width (), (int)strlen (expected)); + ASSERT_EQ (sm.get_num_styles (), 2); + for (size_t i = 0; i < strlen (expected); i++) + { + ASSERT_EQ (s[i].get_code (), (cppchar_t)expected[i]); + ASSERT_EQ (s[i].get_style_id (), 1); + } + ASSERT_STYLE_URLEQ (sm.get_style (1), "http://example.com"); + } + + // URL_FORMAT_BEL + { + style_manager sm; + styled_string s + (sm, "\33]8;;http://example.com\aThis is a link\33]8;;\a"); + const char *expected = "This is a link"; + ASSERT_EQ (s.size (), strlen (expected)); + ASSERT_EQ (s.calc_canvas_width (), (int)strlen (expected)); + ASSERT_EQ (sm.get_num_styles (), 2); + for (size_t i = 0; i < strlen (expected); i++) + { + ASSERT_EQ (s[i].get_code (), (cppchar_t)expected[i]); + ASSERT_EQ (s[i].get_style_id (), 1); + } + ASSERT_STYLE_URLEQ (sm.get_style (1), "http://example.com"); + } +} + +static void +test_from_fmt () +{ + style_manager sm; + styled_string s (styled_string::from_fmt (sm, NULL, "%%i: %i", 42)); + ASSERT_EQ (s[0].get_code (), '%'); + ASSERT_EQ (s[1].get_code (), 'i'); + ASSERT_EQ (s[2].get_code (), ':'); + ASSERT_EQ (s[3].get_code (), ' '); + ASSERT_EQ (s[4].get_code (), '4'); + ASSERT_EQ (s[5].get_code (), '2'); + ASSERT_EQ (s.size (), 6); + ASSERT_EQ (s.calc_canvas_width (), 6); +} + +static void +test_from_fmt_qs () +{ + auto_fix_quotes fix_quotes; + open_quote = "\xe2\x80\x98"; + close_quote = "\xe2\x80\x99"; + + style_manager sm; + styled_string s (styled_string::from_fmt (sm, NULL, "%qs", "msg")); + ASSERT_EQ (sm.get_num_styles (), 2); + ASSERT_EQ (s[0].get_code (), 0x2018); + ASSERT_EQ (s[0].get_style_id (), 0); + ASSERT_EQ (s[1].get_code (), 'm'); + ASSERT_EQ (s[1].get_style_id (), 1); + ASSERT_EQ (s[2].get_code (), 's'); + ASSERT_EQ (s[2].get_style_id (), 1); + ASSERT_EQ (s[3].get_code (), 'g'); + ASSERT_EQ (s[3].get_style_id (), 1); + ASSERT_EQ (s[4].get_code (), 0x2019); + ASSERT_EQ (s[4].get_style_id (), 0); + ASSERT_EQ (s.size (), 5); +} + +// Test of parsing SGR codes. + +static void +test_from_str_with_bold () +{ + style_manager sm; + /* This is the result of pp_printf (pp, "%qs", "foo") + with auto_fix_quotes. */ + styled_string s (sm, "`\33[01m\33[Kfoo\33[m\33[K'"); + ASSERT_EQ (s[0].get_code (), '`'); + ASSERT_EQ (s[0].get_style_id (), 0); + ASSERT_EQ (s[1].get_code (), 'f'); + ASSERT_EQ (s[1].get_style_id (), 1); + ASSERT_EQ (s[2].get_code (), 'o'); + ASSERT_EQ (s[2].get_style_id (), 1); + ASSERT_EQ (s[3].get_code (), 'o'); + ASSERT_EQ (s[3].get_style_id (), 1); + ASSERT_EQ (s[4].get_code (), '\''); + ASSERT_EQ (s[4].get_style_id (), 0); + ASSERT_EQ (s.size (), 5); + ASSERT_TRUE (sm.get_style (1).m_bold); +} + +static void +test_from_str_with_underscore () +{ + style_manager sm; + styled_string s (sm, "\33[04m\33[KA"); + ASSERT_EQ (s[0].get_code (), 'A'); + ASSERT_EQ (s[0].get_style_id (), 1); + ASSERT_TRUE (sm.get_style (1).m_underscore); +} + +static void +test_from_str_with_blink () +{ + style_manager sm; + styled_string s (sm, "\33[05m\33[KA"); + ASSERT_EQ (s[0].get_code (), 'A'); + ASSERT_EQ (s[0].get_style_id (), 1); + ASSERT_TRUE (sm.get_style (1).m_blink); +} + +// Test of parsing SGR codes. + +static void +test_from_str_with_color () +{ + style_manager sm; + + styled_string s (sm, + ("0" + SGR_SEQ (COLOR_FG_RED) + "R" + SGR_RESET + "2" + SGR_SEQ (COLOR_FG_GREEN) + "G" + SGR_RESET + "4")); + ASSERT_EQ (s.size (), 5); + ASSERT_EQ (sm.get_num_styles (), 3); + ASSERT_EQ (s[0].get_code (), '0'); + ASSERT_EQ (s[0].get_style_id (), 0); + ASSERT_EQ (s[1].get_code (), 'R'); + ASSERT_EQ (s[1].get_style_id (), 1); + ASSERT_EQ (s[2].get_code (), '2'); + ASSERT_EQ (s[2].get_style_id (), 0); + ASSERT_EQ (s[3].get_code (), 'G'); + ASSERT_EQ (s[3].get_style_id (), 2); + ASSERT_EQ (s[4].get_code (), '4'); + ASSERT_EQ (s[4].get_style_id (), 0); + ASSERT_EQ (sm.get_style (1).m_fg_color, style::named_color::RED); + ASSERT_EQ (sm.get_style (2).m_fg_color, style::named_color::GREEN); +} + +static void +test_from_str_with_named_color () +{ + style_manager sm; + styled_string s (sm, + ("F" + SGR_SEQ (COLOR_FG_BLACK) "F" + SGR_SEQ (COLOR_FG_RED) "F" + SGR_SEQ (COLOR_FG_GREEN) "F" + SGR_SEQ (COLOR_FG_YELLOW) "F" + SGR_SEQ (COLOR_FG_BLUE) "F" + SGR_SEQ (COLOR_FG_MAGENTA) "F" + SGR_SEQ (COLOR_FG_CYAN) "F" + SGR_SEQ (COLOR_FG_WHITE) "F" + SGR_SEQ (COLOR_FG_BRIGHT_BLACK) "F" + SGR_SEQ (COLOR_FG_BRIGHT_RED) "F" + SGR_SEQ (COLOR_FG_BRIGHT_GREEN) "F" + SGR_SEQ (COLOR_FG_BRIGHT_YELLOW) "F" + SGR_SEQ (COLOR_FG_BRIGHT_BLUE) "F" + SGR_SEQ (COLOR_FG_BRIGHT_MAGENTA) "F" + SGR_SEQ (COLOR_FG_BRIGHT_CYAN) "F" + SGR_SEQ (COLOR_FG_BRIGHT_WHITE) "F" + SGR_SEQ (COLOR_BG_BLACK) "B" + SGR_SEQ (COLOR_BG_RED) "B" + SGR_SEQ (COLOR_BG_GREEN) "B" + SGR_SEQ (COLOR_BG_YELLOW) "B" + SGR_SEQ (COLOR_BG_BLUE) "B" + SGR_SEQ (COLOR_BG_MAGENTA) "B" + SGR_SEQ (COLOR_BG_CYAN) "B" + SGR_SEQ (COLOR_BG_WHITE) "B" + SGR_SEQ (COLOR_BG_BRIGHT_BLACK) "B" + SGR_SEQ (COLOR_BG_BRIGHT_RED) "B" + SGR_SEQ (COLOR_BG_BRIGHT_GREEN) "B" + SGR_SEQ (COLOR_BG_BRIGHT_YELLOW) "B" + SGR_SEQ (COLOR_BG_BRIGHT_BLUE) "B" + SGR_SEQ (COLOR_BG_BRIGHT_MAGENTA) "B" + SGR_SEQ (COLOR_BG_BRIGHT_CYAN) "B" + SGR_SEQ (COLOR_BG_BRIGHT_WHITE) "B")); + ASSERT_EQ (s.size (), 33); + for (size_t i = 0; i < s.size (); i++) + ASSERT_EQ (s[i].get_style_id (), i); + for (size_t i = 0; i < 17; i++) + ASSERT_EQ (s[i].get_code (), 'F'); + for (size_t i = 17; i < 33; i++) + ASSERT_EQ (s[i].get_code (), 'B'); +} + +static void +test_from_str_with_8_bit_color () +{ + { + style_manager sm; + styled_string s (sm, + ("[38;5;232m[KF")); + ASSERT_EQ (s.size (), 1); + ASSERT_EQ (s[0].get_code (), 'F'); + ASSERT_EQ (s[0].get_style_id (), 1); + ASSERT_EQ (sm.get_style (1).m_fg_color, style::color (232)); + } + { + style_manager sm; + styled_string s (sm, + ("[48;5;231m[KB")); + ASSERT_EQ (s.size (), 1); + ASSERT_EQ (s[0].get_code (), 'B'); + ASSERT_EQ (s[0].get_style_id (), 1); + ASSERT_EQ (sm.get_style (1).m_bg_color, style::color (231)); + } +} + +static void +test_from_str_with_24_bit_color () +{ + { + style_manager sm; + styled_string s (sm, + ("[38;2;243;250;242m[KF")); + ASSERT_EQ (s.size (), 1); + ASSERT_EQ (s[0].get_code (), 'F'); + ASSERT_EQ (s[0].get_style_id (), 1); + ASSERT_EQ (sm.get_style (1).m_fg_color, style::color (243, 250, 242)); + } + { + style_manager sm; + styled_string s (sm, + ("[48;2;253;247;231m[KB")); + ASSERT_EQ (s.size (), 1); + ASSERT_EQ (s[0].get_code (), 'B'); + ASSERT_EQ (s[0].get_style_id (), 1); + ASSERT_EQ (sm.get_style (1).m_bg_color, style::color (253, 247, 231)); + } +} + +static void +test_from_str_combining_characters () +{ + style_manager sm; + styled_string s (sm, + /* CYRILLIC CAPITAL LETTER U (U+0423). */ + "\xD0\xA3" + /* COMBINING BREVE (U+0306). */ + "\xCC\x86"); + ASSERT_EQ (s.size (), 1); + ASSERT_EQ (s[0].get_code (), 0x423); + ASSERT_EQ (s[0].get_combining_chars ().size (), 1); + ASSERT_EQ (s[0].get_combining_chars ()[0], 0x306); +} + +/* Run all selftests in this file. */ + +void +text_art_styled_string_cc_tests () +{ + test_combining_chars (); + test_empty (); + test_simple (); + test_pi_from_utf8 (); + test_emoji_from_utf8 (); + test_emoji_variant_from_utf8 (); + test_emoji_from_codepoint (); + test_from_mixed_width_utf8 (); + test_url (); + test_from_fmt (); + test_from_fmt_qs (); + test_from_str_with_bold (); + test_from_str_with_underscore (); + test_from_str_with_blink (); + test_from_str_with_color (); + test_from_str_with_named_color (); + test_from_str_with_8_bit_color (); + test_from_str_with_24_bit_color (); + test_from_str_combining_characters (); +} + +} // namespace selftest + + +#endif /* #if CHECKING_P */ diff --git a/gcc/text-art/table.cc b/gcc/text-art/table.cc new file mode 100644 index 00000000000..42cc4228ea6 --- /dev/null +++ b/gcc/text-art/table.cc @@ -0,0 +1,1272 @@ +/* Support for tabular/grid-based content. + Copyright (C) 2023 Free Software Foundation, Inc. + Contributed by David Malcolm <dmalcolm@redhat.com>. + +This file is part of GCC. + +GCC is free software; you can redistribute it and/or modify it under +the terms of the GNU General Public License as published by the Free +Software Foundation; either version 3, or (at your option) any later +version. + +GCC is distributed in the hope that it will be useful, but WITHOUT ANY +WARRANTY; without even the implied warranty of MERCHANTABILITY or +FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License +for more details. + +You should have received a copy of the GNU General Public License +along with GCC; see the file COPYING3. If not see +<http://www.gnu.org/licenses/>. */ + +#include "config.h" +#define INCLUDE_MEMORY +#include "system.h" +#include "coretypes.h" +#include "make-unique.h" +#include "pretty-print.h" +#include "diagnostic.h" +#include "selftest.h" +#include "text-art/selftests.h" +#include "text-art/table.h" + +using namespace text_art; + +/* class text_art::table_cell_content. */ + +table_cell_content::table_cell_content (styled_string &&s) +: m_str (std::move (s)), + /* We assume here that the content occupies a single canvas row. */ + m_size (m_str.calc_canvas_width (), 1) +{ +} + +void +table_cell_content::paint_to_canvas (canvas &canvas, + canvas::coord_t top_left) const +{ + canvas.paint_text (top_left, m_str); +} + +/* struct text_art::table_dimension_sizes. */ + +table_dimension_sizes::table_dimension_sizes (unsigned num) +: m_requirements (num, 0) +{ +} + +/* class text_art::table::cell_placement. */ + +void +table::cell_placement::paint_cell_contents_to_canvas(canvas &canvas, + canvas::coord_t offset, + const table_geometry &tg) const +{ + const canvas::size_t req_canvas_size = get_min_canvas_size (); + const canvas::size_t alloc_canvas_size = tg.get_canvas_size (m_rect); + gcc_assert (req_canvas_size.w <= alloc_canvas_size.w); + gcc_assert (req_canvas_size.h <= alloc_canvas_size.h); + const int x_padding = alloc_canvas_size.w - req_canvas_size.w; + const int y_padding = alloc_canvas_size.h - req_canvas_size.h; + const table::coord_t table_top_left = m_rect.m_top_left; + const canvas::coord_t canvas_top_left = tg.table_to_canvas (table_top_left); + + gcc_assert (x_padding >= 0); + int x_align_offset; + switch (m_x_align) + { + default: + gcc_unreachable (); + case x_align::LEFT: + x_align_offset = 0; + break; + case x_align::CENTER: + x_align_offset = x_padding / 2; + break; + case x_align::RIGHT: + x_align_offset = x_padding; + break; + } + + gcc_assert (y_padding >= 0); + int y_align_offset; + switch (m_y_align) + { + default: + gcc_unreachable (); + case y_align::TOP: + y_align_offset = 0; + break; + case y_align::CENTER: + y_align_offset = y_padding / 2; + break; + case y_align::BOTTOM: + y_align_offset = y_padding; + break; + } + const canvas::coord_t content_rel_coord + (canvas_top_left.x + 1 + x_align_offset, + canvas_top_left.y + 1 + y_align_offset); + m_content.paint_to_canvas (canvas, offset + content_rel_coord); +} + +/* class text_art::table. */ + + +table::table (size_t size) +: m_size (size), + m_placements (), + m_occupancy (size) +{ + m_occupancy.fill (-1); +} + +void +table::set_cell (coord_t coord, + table_cell_content &&content, + enum x_align x_align, + enum y_align y_align) +{ + set_cell_span (rect_t (coord, table::size_t (1, 1)), + std::move (content), x_align, y_align); +} + +void +table::set_cell_span (rect_t span, + table_cell_content &&content, + enum x_align x_align, + enum y_align y_align) +{ + gcc_assert (span.m_size.w > 0); + gcc_assert (span.m_size.h > 0); + int placement_idx = m_placements.size (); + m_placements.emplace_back (cell_placement (span, std::move (content), + x_align, y_align)); + for (int y = span.get_min_y (); y < span.get_next_y (); y++) + for (int x = span.get_min_x (); x < span.get_next_x (); x++) + { + gcc_assert (m_occupancy.get (coord_t (x, y)) == -1); + m_occupancy.set (coord_t (x, y), placement_idx); + } +} + +canvas +table::to_canvas (const theme &theme, const style_manager &sm) const +{ + table_dimension_sizes col_widths (m_size.w); + table_dimension_sizes row_heights (m_size.h); + table_cell_sizes cell_sizes (col_widths, row_heights); + cell_sizes.pass_1 (*this); + cell_sizes.pass_2 (*this); + table_geometry tg (*this, cell_sizes); + canvas canvas (tg.get_canvas_size (), sm); + paint_to_canvas (canvas, canvas::coord_t (0, 0), tg, theme); + return canvas; +} + +void +table::paint_to_canvas (canvas &canvas, + canvas::coord_t offset, + const table_geometry &tg, + const theme &theme) const +{ + canvas.fill (canvas::rect_t (offset, tg.get_canvas_size ()), + styled_unichar (' ')); + paint_cell_borders_to_canvas (canvas, offset, tg, theme); + paint_cell_contents_to_canvas (canvas, offset, tg); +} + +/* Print this table to stderr. */ + +DEBUG_FUNCTION void +table::debug () const +{ + /* Use a temporary style manager. + Styles in the table will be meaningless, so + print the canvas with styling disabled. */ + style_manager sm; + canvas canvas (to_canvas (unicode_theme (), sm)); + canvas.debug (false); +} + +const table::cell_placement * +table::get_placement_at (coord_t coord) const +{ + const int placement_idx = m_occupancy.get (coord); + if (placement_idx == -1) + return nullptr; + return &m_placements[placement_idx]; +} + +int +table::get_occupancy_safe (coord_t coord) const +{ + if (coord.x < 0) + return -1; + if (coord.x >= m_size.w) + return -1; + if (coord.y < 0) + return -1; + if (coord.y >= m_size.h) + return -1; + return m_occupancy.get (coord); +} + +/* Determine if the "?" edges need borders for table cell D + in the following, for the directions relative to "X", based + on whether each of table cell boundaries AB, CD, AC, and BD + are boundaries between cell spans: + + # up? + # +-----+-----+ + # | | + # | ? | + # | A ? B | + # | ? | + # | | + # left?+ ??? X ??? + right? + # | | + # | ? | + # | C ? D | + # | ? | + # | | + # +-----+-----+ + # down? +*/ + +directions +table::get_connections (int table_x, int table_y) const +{ + int cell_a = get_occupancy_safe (coord_t (table_x - 1, table_y - 1)); + int cell_b = get_occupancy_safe (coord_t (table_x, table_y - 1)); + int cell_c = get_occupancy_safe (coord_t (table_x - 1, table_y)); + int cell_d = get_occupancy_safe (coord_t (table_x, table_y)); + const bool up = (cell_a != cell_b); + const bool down = (cell_c != cell_d); + const bool left = (cell_a != cell_c); + const bool right = (cell_b != cell_d); + return directions (up, down, left, right); +} + +/* Paint the grid lines. + + Consider painting + - a grid of cells, + - plus a right-hand border + - and a bottom border + + Then we need to paint to the canvas like this: + + # PER-TABLE-COLUMN R BORDER + # +-------------------+ +-----+ + # + # TABLE CELL WIDTH (in canvas units) + # +-------------+ + # . . . . . . . + # ...+-----+-----+.+-----+...+-----+ + + # | U | |.| | | U | | + # | U | |.| | | U | | + # |LL+RR|RRRRR|.|RRRRR| |LL+ | | + # | D | |.| | | D | | + # | D | |.| | | D | | + # ...+-----+-----+.+-----+...+-----+ | + # ..................... ...... +-- PER-TABLE-ROW + # ...+-----+-----+.+-----+...+-----+ | + + # | D | |.| | | D | | | + # | D | |.| | | D | | | + # | D | |.| | | D | | +---- TABLE CELL HEIGHT (in canvas units) + # | D | |.| | | D | | | + # | D | |.| | | D | | | + # ...+-----+-----+.+-----+...+-----+ + + + # . . . . . . + # ...+-----+-----+.+-----+...+-----+ + + # | D | |.| | | U | | + # | D | |.| | | U | | + # |LL+RR|RRRRR|.|RRRRR| |LL+ | | BOTTOM BORDER + # | | |.| | | | | + # | | |.| | | | | + # ...+-----+-----+.+-----+...+-----+ + + + where each: + + # +-----+ + # | | + # | | + # | | + # | | + # | | + # +-----+ + + is a canvas cell, and the U, L, R, D express the connections + that are present with neighboring table cells. These affect + the kinds of borders that we draw for a particular table cell. */ + +void +table::paint_cell_borders_to_canvas (canvas &canvas, + canvas::coord_t offset, + const table_geometry &tg, + const theme &theme) const +{ + /* The per-table-cell left and top borders are either drawn or not, + but if they are, they aren't affected by per-table-cell connections. */ + const canvas::cell_t left_border + = theme.get_line_art (directions (true, /* up */ + true, /* down */ + false, /* left */ + false /* right */)); + const canvas::cell_t top_border + = theme.get_line_art (directions (false, /* up */ + false, /* down */ + true, /* left */ + true)); /* right */ + for (int table_y = 0; table_y < m_size.h; table_y++) + { + const int canvas_y = tg.table_y_to_canvas_y (table_y); + for (int table_x = 0; table_x < m_size.w; table_x++) + { + canvas::coord_t canvas_top_left + = tg.table_to_canvas(table::coord_t (table_x, table_y)); + + const directions c (get_connections (table_x, table_y)); + + /* Paint top-left corner of border, if any. */ + canvas.paint (offset + canvas_top_left, + theme.get_line_art (c)); + + /* Paint remainder of left border of cell, if any. + We assume here that the content occupies a single canvas row. */ + if (c.m_down) + canvas.paint (offset + canvas::coord_t (canvas_top_left.x, + canvas_y + 1), + left_border); + + /* Paint remainder of top border of cell, if any. */ + if (c.m_right) + { + const int col_width = tg.get_col_width (table_x); + for (int x_offset = 0; x_offset < col_width; x_offset++) + { + const int canvas_x = canvas_top_left.x + 1 + x_offset; + canvas.paint (offset + canvas::coord_t (canvas_x, canvas_y), + top_border); + } + } + } + + /* Paint right-hand border of row. */ + const int table_x = m_size.w; + const int canvas_x = tg.table_x_to_canvas_x (table_x); + const directions c (get_connections (m_size.w, table_y)); + canvas.paint(offset + canvas::coord_t (canvas_x, canvas_y), + theme.get_line_art (directions (c.m_up, + c.m_down, + c.m_left, + false))); /* right */ + /* We assume here that the content occupies a single canvas row. */ + canvas.paint(offset + canvas::coord_t (canvas_x, canvas_y + 1), + theme.get_line_art (directions (c.m_down, /* up */ + c.m_down, /* down */ + false, /* left */ + false))); /* right */ + } + + /* Draw bottom border of table. */ + { + const int canvas_y = tg.get_canvas_size ().h - 1; + for (int table_x = 0; table_x < m_size.w; table_x++) + { + const directions c (get_connections (table_x, m_size.h)); + const int left_canvas_x = tg.table_x_to_canvas_x (table_x); + canvas.paint (offset + canvas::coord_t (left_canvas_x, canvas_y), + theme.get_line_art (directions (c.m_up, + false, /* down */ + c.m_left, + c.m_right))); + const int col_width = tg.get_col_width (table_x); + for (int x_offset = 0; x_offset < col_width; x_offset++) + { + const int canvas_x = left_canvas_x + 1 + x_offset; + canvas.paint(offset + canvas::coord_t (canvas_x, canvas_y), + theme.get_line_art (directions (false, // up + false, // down + c.m_right, // left + c.m_right))); // right + } + } + + /* Bottom-right corner of table. */ + const int table_x = m_size.w; + const int canvas_x = tg.table_x_to_canvas_x (table_x); + const directions c (get_connections (m_size.w, m_size.h)); + canvas.paint (offset + canvas::coord_t (canvas_x, canvas_y), + theme.get_line_art (directions (c.m_up, // up + false, // down + c.m_left, // left + false))); // right + } +} + +void +table::paint_cell_contents_to_canvas(canvas &canvas, + canvas::coord_t offset, + const table_geometry &tg) const +{ + for (auto &placement : m_placements) + placement.paint_cell_contents_to_canvas (canvas, offset, tg); +} + +/* class table_cell_sizes. */ + +/* Consider 1x1 cells. */ + +void +table_cell_sizes::pass_1 (const table &table) +{ + for (auto &placement : table.m_placements) + if (placement.one_by_one_p ()) + { + canvas::size_t canvas_size (placement.get_min_canvas_size ()); + table::coord_t table_coord (placement.m_rect.m_top_left); + m_col_widths.require (table_coord.x, canvas_size.w); + m_row_heights.require (table_coord.y, canvas_size.h); + } +} + +/* Consider cells that span more than one row or column. */ + +void +table_cell_sizes::pass_2 (const table &table) +{ + for (auto &placement : table.m_placements) + if (!placement.one_by_one_p ()) + { + const canvas::size_t req_canvas_size (placement.get_min_canvas_size ()); + const canvas::size_t current_canvas_size + = get_canvas_size (placement.m_rect); + /* Grow columns as necessary. */ + if (req_canvas_size.w > current_canvas_size.w) + { + /* Spread the deficit amongst the columns. */ + int deficit = req_canvas_size.w - current_canvas_size.w; + const int per_col = deficit / placement.m_rect.m_size.w; + for (int table_x = placement.m_rect.get_min_x (); + table_x < placement.m_rect.get_next_x (); + table_x++) + { + m_col_widths.m_requirements[table_x] += per_col; + deficit -= per_col; + } + /* Make sure we allocate all of the deficit. */ + if (deficit > 0) + { + const int table_x = placement.m_rect.get_max_x (); + m_col_widths.m_requirements[table_x] += deficit; + } + } + /* Grow rows as necessary. */ + if (req_canvas_size.h > current_canvas_size.h) + { + /* Spread the deficit amongst the rows. */ + int deficit = req_canvas_size.h - current_canvas_size.h; + const int per_row = deficit / placement.m_rect.m_size.h; + for (int table_y = placement.m_rect.get_min_y (); + table_y < placement.m_rect.get_next_y (); + table_y++) + { + m_row_heights.m_requirements[table_y] += per_row; + deficit -= per_row; + } + /* Make sure we allocate all of the deficit. */ + if (deficit > 0) + { + const int table_y = placement.m_rect.get_max_y (); + m_row_heights.m_requirements[table_y] += deficit; + } + } + } +} + +canvas::size_t +table_cell_sizes::get_canvas_size (const table::rect_t &rect) const +{ + canvas::size_t result (0, 0); + for (int table_x = rect.get_min_x (); + table_x < rect.get_next_x (); + table_x ++) + result.w += m_col_widths.m_requirements[table_x]; + for (int table_y = rect.get_min_y (); + table_y < rect.get_next_y (); + table_y ++) + result.h += m_row_heights.m_requirements[table_y]; + /* Allow space for the borders. */ + result.w += rect.m_size.w - 1; + result.h += rect.m_size.h - 1; + return result; +} + +/* class text_art::table_geometry. */ + +table_geometry::table_geometry (const table &table, table_cell_sizes &cell_sizes) +: m_table (table), + m_cell_sizes (cell_sizes), + m_canvas_size (canvas::size_t (0, 0)), + m_col_start_x (table.get_size ().w), + m_row_start_y (table.get_size ().h) +{ + recalc_coords (); +} + +void +table_geometry::recalc_coords () +{ + /* Start canvas column of table cell, including leading border. */ + m_col_start_x.clear (); + int iter_canvas_x = 0; + for (auto w : m_cell_sizes.m_col_widths.m_requirements) + { + m_col_start_x.push_back (iter_canvas_x); + iter_canvas_x += w + 1; + } + + /* Start canvas row of table cell, including leading border. */ + m_row_start_y.clear (); + int iter_canvas_y = 0; + for (auto h : m_cell_sizes.m_row_heights.m_requirements) + { + m_row_start_y.push_back (iter_canvas_y); + iter_canvas_y += h + 1; + } + + m_canvas_size = canvas::size_t (iter_canvas_x + 1, + iter_canvas_y + 1); +} + +/* Get the TL corner of the table cell at TABLE_COORD + in canvas coords (including the border). */ + +canvas::coord_t +table_geometry::table_to_canvas (table::coord_t table_coord) const +{ + return canvas::coord_t (table_x_to_canvas_x (table_coord.x), + table_y_to_canvas_y (table_coord.y)); +} + +/* Get the left border of the table cell at column TABLE_X + in canvas coords (including the border). */ + +int +table_geometry::table_x_to_canvas_x (int table_x) const +{ + /* Allow one beyond the end, for the right-hand border of the table. */ + if (table_x == m_col_start_x.size ()) + return m_canvas_size.w - 1; + return m_col_start_x[table_x]; +} + +/* Get the top border of the table cell at column TABLE_Y + in canvas coords (including the border). */ + +int +table_geometry::table_y_to_canvas_y (int table_y) const +{ + /* Allow one beyond the end, for the right-hand border of the table. */ + if (table_y == m_row_start_y.size ()) + return m_canvas_size.h - 1; + return m_row_start_y[table_y]; +} + +/* class text_art::simple_table_geometry. */ + +simple_table_geometry::simple_table_geometry (const table &table) +: m_col_widths (table.get_size ().w), + m_row_heights (table.get_size ().h), + m_cell_sizes (m_col_widths, m_row_heights), + m_tg (table, m_cell_sizes) +{ + m_cell_sizes.pass_1 (table); + m_cell_sizes.pass_2 (table); + m_tg.recalc_coords (); +} + +#if CHECKING_P + +namespace selftest { + +static void +test_tic_tac_toe () +{ + style_manager sm; + table t (table::size_t (3, 3)); + t.set_cell (table::coord_t (0, 0), styled_string (sm, "X")); + t.set_cell (table::coord_t (1, 0), styled_string (sm, "")); + t.set_cell (table::coord_t (2, 0), styled_string (sm, "")); + t.set_cell (table::coord_t (0, 1), styled_string (sm, "O")); + t.set_cell (table::coord_t (1, 1), styled_string (sm, "O")); + t.set_cell (table::coord_t (2, 1), styled_string (sm, "")); + t.set_cell (table::coord_t (0, 2), styled_string (sm, "X")); + t.set_cell (table::coord_t (1, 2), styled_string (sm, "")); + t.set_cell (table::coord_t (2, 2), styled_string (sm, "O")); + + { + canvas canvas (t.to_canvas (ascii_theme (), sm)); + ASSERT_CANVAS_STREQ + (canvas, false, + ("+-+-+-+\n" + "|X| | |\n" + "+-+-+-+\n" + "|O|O| |\n" + "+-+-+-+\n" + "|X| |O|\n" + "+-+-+-+\n")); + } + + { + canvas canvas (t.to_canvas (unicode_theme (), sm)); + ASSERT_CANVAS_STREQ + (canvas, false, + // FIXME: are we allowed unicode chars in string literals in our source? + ("┌─┬─┬─┐\n" + "│X│ │ │\n" + "├─┼─┼─┤\n" + "│O│O│ │\n" + "├─┼─┼─┤\n" + "│X│ │O│\n" + "└─┴─┴─┘\n")); + } +} + +static table +make_text_table () +{ + style_manager sm; + table t (table::size_t (3, 3)); + t.set_cell (table::coord_t (0, 0), styled_string (sm, "top left")); + t.set_cell (table::coord_t (1, 0), styled_string (sm, "top middle")); + t.set_cell (table::coord_t (2, 0), styled_string (sm, "top right")); + t.set_cell (table::coord_t (0, 1), styled_string (sm, "middle left")); + t.set_cell (table::coord_t (1, 1), styled_string (sm, "middle middle")); + t.set_cell (table::coord_t (2, 1), styled_string (sm, "middle right")); + t.set_cell (table::coord_t (0, 2), styled_string (sm, "bottom left")); + t.set_cell (table::coord_t (1, 2), styled_string (sm, "bottom middle")); + t.set_cell (table::coord_t (2, 2), styled_string (sm, "bottom right")); + return t; +} + +static void +test_text_table () +{ + style_manager sm; + table table = make_text_table (); + { + canvas canvas (table.to_canvas (ascii_theme(), sm)); + ASSERT_CANVAS_STREQ + (canvas, false, + ("+-----------+-------------+------------+\n" + "| top left | top middle | top right |\n" + "+-----------+-------------+------------+\n" + "|middle left|middle middle|middle right|\n" + "+-----------+-------------+------------+\n" + "|bottom left|bottom middle|bottom right|\n" + "+-----------+-------------+------------+\n")); + } + { + canvas canvas (table.to_canvas (unicode_theme(), sm)); + ASSERT_CANVAS_STREQ + (canvas, false, + // FIXME: are we allowed unicode chars in string literals in our source? + ("┌───────────┬─────────────┬────────────┐\n" + "│ top left │ top middle │ top right │\n" + "├───────────┼─────────────┼────────────┤\n" + "│middle left│middle middle│middle right│\n" + "├───────────┼─────────────┼────────────┤\n" + "│bottom left│bottom middle│bottom right│\n" + "└───────────┴─────────────┴────────────┘\n")); + } +} + +static void +test_offset_table () +{ + style_manager sm; + table table = make_text_table (); + simple_table_geometry stg (table); + const canvas::size_t tcs = stg.m_tg.get_canvas_size(); + { + canvas canvas (canvas::size_t (tcs.w + 5, tcs.h + 5), sm); + canvas.debug_fill (); + table.paint_to_canvas (canvas, canvas::coord_t (3, 3), + stg.m_tg, + ascii_theme()); + ASSERT_CANVAS_STREQ + (canvas, false, + ("*********************************************\n" + "*********************************************\n" + "*********************************************\n" + "***+-----------+-------------+------------+**\n" + "***| top left | top middle | top right |**\n" + "***+-----------+-------------+------------+**\n" + "***|middle left|middle middle|middle right|**\n" + "***+-----------+-------------+------------+**\n" + "***|bottom left|bottom middle|bottom right|**\n" + "***+-----------+-------------+------------+**\n" + "*********************************************\n" + "*********************************************\n")); + } + { + canvas canvas (canvas::size_t (tcs.w + 5, tcs.h + 5), sm); + canvas.debug_fill (); + table.paint_to_canvas (canvas, canvas::coord_t (3, 3), + stg.m_tg, + unicode_theme()); + ASSERT_CANVAS_STREQ + (canvas, false, + // FIXME: are we allowed unicode chars in string literals in our source? + ("*********************************************\n" + "*********************************************\n" + "*********************************************\n" + "***┌───────────┬─────────────┬────────────┐**\n" + "***│ top left │ top middle │ top right │**\n" + "***├───────────┼─────────────┼────────────┤**\n" + "***│middle left│middle middle│middle right│**\n" + "***├───────────┼─────────────┼────────────┤**\n" + "***│bottom left│bottom middle│bottom right│**\n" + "***└───────────┴─────────────┴────────────┘**\n" + "*********************************************\n" + "*********************************************\n")); + } +} + +#define ASSERT_TABLE_CELL_STREQ(TABLE, TABLE_X, TABLE_Y, EXPECTED_STR) \ + SELFTEST_BEGIN_STMT \ + table::coord_t coord ((TABLE_X), (TABLE_Y)); \ + const table::cell_placement *cp = (TABLE).get_placement_at (coord); \ + ASSERT_NE (cp, nullptr); \ + ASSERT_EQ (cp->get_content (), styled_string (sm, EXPECTED_STR)); \ + SELFTEST_END_STMT + +#define ASSERT_TABLE_NULL_CELL(TABLE, TABLE_X, TABLE_Y) \ + SELFTEST_BEGIN_STMT \ + table::coord_t coord ((TABLE_X), (TABLE_Y)); \ + const table::cell_placement *cp = (TABLE).get_placement_at (coord); \ + ASSERT_EQ (cp, nullptr); \ + SELFTEST_END_STMT + +static void +test_spans () +{ + style_manager sm; + table table (table::size_t (3, 3)); + table.set_cell_span (table::rect_t (table::coord_t (0, 0), + table::size_t (3, 1)), + styled_string (sm, "ABC")); + table.set_cell_span (table::rect_t (table::coord_t (0, 1), + table::size_t (2, 1)), + styled_string (sm, "DE")); + table.set_cell_span (table::rect_t (table::coord_t (2, 1), + table::size_t (1, 1)), + styled_string (sm, "F")); + table.set_cell (table::coord_t (0, 2), styled_string (sm, "G")); + table.set_cell (table::coord_t (1, 2), styled_string (sm, "H")); + table.set_cell (table::coord_t (2, 2), styled_string (sm, "I")); + { + canvas canvas (table.to_canvas (ascii_theme(), sm)); + ASSERT_CANVAS_STREQ + (canvas, false, + ("+-----+\n" + "| ABC |\n" + "+---+-+\n" + "|DE |F|\n" + "+-+-+-+\n" + "|G|H|I|\n" + "+-+-+-+\n")); + } + { + canvas canvas (table.to_canvas (unicode_theme(), sm)); + ASSERT_CANVAS_STREQ + (canvas, false, + // FIXME: are we allowed unicode chars in string literals in our source? + ("┌─────┐\n" + "│ ABC │\n" + "├───┬─┤\n" + "│DE │F│\n" + "├─┬─┼─┤\n" + "│G│H│I│\n" + "└─┴─┴─┘\n")); + } +} + +/* Verify building this 5x5 table with spans: + |0|1|2|3|4| + +-+-+-+-+-+ + 0|A A A|B|C|0 + + +-+ + + 1|A A A|D|C|1 + + +-+-+ + 2|A A A|E|F|2 + +-+-+-+-+-+ + 3|G G|H|I I|3 + | | +-+-+ + 4|G G|H|J J|4 + +-+-+-+-+-+ + |0|1|2|3|4| +*/ + +static void +test_spans_2 () +{ + style_manager sm; + table table (table::size_t (5, 5)); + table.set_cell_span (table::rect_t (table::coord_t (0, 0), + table::size_t (3, 3)), + styled_string (sm, "A")); + table.set_cell_span (table::rect_t (table::coord_t (3, 0), + table::size_t (1, 1)), + styled_string (sm, "B")); + table.set_cell_span (table::rect_t (table::coord_t (4, 0), + table::size_t (1, 2)), + styled_string (sm, "C")); + table.set_cell_span (table::rect_t (table::coord_t (3, 1), + table::size_t (1, 1)), + styled_string (sm, "D")); + table.set_cell_span (table::rect_t (table::coord_t (3, 2), + table::size_t (1, 1)), + styled_string (sm, "E")); + table.set_cell_span (table::rect_t (table::coord_t (4, 2), + table::size_t (1, 1)), + styled_string (sm, "F")); + table.set_cell_span (table::rect_t (table::coord_t (0, 3), + table::size_t (2, 2)), + styled_string (sm, "G")); + table.set_cell_span (table::rect_t (table::coord_t (2, 3), + table::size_t (1, 2)), + styled_string (sm, "H")); + table.set_cell_span (table::rect_t (table::coord_t (3, 3), + table::size_t (2, 1)), + styled_string (sm, "I")); + table.set_cell_span (table::rect_t (table::coord_t (3, 4), + table::size_t (2, 1)), + styled_string (sm, "J")); + + /* Check occupancy at each table coordinate. */ + ASSERT_TABLE_CELL_STREQ (table, 0, 0, "A"); + ASSERT_TABLE_CELL_STREQ (table, 1, 0, "A"); + ASSERT_TABLE_CELL_STREQ (table, 2, 0, "A"); + ASSERT_TABLE_CELL_STREQ (table, 3, 0, "B"); + ASSERT_TABLE_CELL_STREQ (table, 4, 0, "C"); + + ASSERT_TABLE_CELL_STREQ (table, 0, 1, "A"); + ASSERT_TABLE_CELL_STREQ (table, 1, 1, "A"); + ASSERT_TABLE_CELL_STREQ (table, 2, 1, "A"); + ASSERT_TABLE_CELL_STREQ (table, 3, 1, "D"); + ASSERT_TABLE_CELL_STREQ (table, 4, 1, "C"); + + ASSERT_TABLE_CELL_STREQ (table, 0, 2, "A"); + ASSERT_TABLE_CELL_STREQ (table, 1, 2, "A"); + ASSERT_TABLE_CELL_STREQ (table, 2, 2, "A"); + ASSERT_TABLE_CELL_STREQ (table, 3, 2, "E"); + ASSERT_TABLE_CELL_STREQ (table, 4, 2, "F"); + + ASSERT_TABLE_CELL_STREQ (table, 0, 3, "G"); + ASSERT_TABLE_CELL_STREQ (table, 1, 3, "G"); + ASSERT_TABLE_CELL_STREQ (table, 2, 3, "H"); + ASSERT_TABLE_CELL_STREQ (table, 3, 3, "I"); + ASSERT_TABLE_CELL_STREQ (table, 4, 3, "I"); + + ASSERT_TABLE_CELL_STREQ (table, 0, 4, "G"); + ASSERT_TABLE_CELL_STREQ (table, 1, 4, "G"); + ASSERT_TABLE_CELL_STREQ (table, 2, 4, "H"); + ASSERT_TABLE_CELL_STREQ (table, 3, 4, "J"); + ASSERT_TABLE_CELL_STREQ (table, 4, 4, "J"); + + { + canvas canvas (table.to_canvas (ascii_theme(), sm)); + ASSERT_CANVAS_STREQ + (canvas, false, + ("+---+-+-+\n" + "| |B| |\n" + "| +-+C|\n" + "| A |D| |\n" + "| +-+-+\n" + "| |E|F|\n" + "+-+-+-+-+\n" + "| | | I |\n" + "|G|H+---+\n" + "| | | J |\n" + "+-+-+---+\n")); + } + { + canvas canvas (table.to_canvas (unicode_theme(), sm)); + ASSERT_CANVAS_STREQ + (canvas, false, + // FIXME: are we allowed unicode chars in string literals in our source? + ("┌───┬─┬─┐\n" + "│ │B│ │\n" + "│ ├─┤C│\n" + "│ A │D│ │\n" + "│ ├─┼─┤\n" + "│ │E│F│\n" + "├─┬─┼─┴─┤\n" + "│ │ │ I │\n" + "│G│H├───┤\n" + "│ │ │ J │\n" + "└─┴─┴───┘\n")); + } +} + +/* Experiment with adding a 1-table-column gap at the boundary between + valid vs invalid for visualizing a buffer overflow. */ +static void +test_spans_3 () +{ + const char * const str = "hello world!"; + const size_t buf_size = 10; + const size_t str_size = strlen (str) + 1; + + style_manager sm; + table table (table::size_t (str_size + 1, 3)); + + table.set_cell_span (table::rect_t (table::coord_t (0, 0), + table::size_t (str_size + 1, 1)), + styled_string (sm, "String literal")); + + for (size_t i = 0; i < str_size; i++) + { + table::coord_t c (i, 1); + if (i >= buf_size) + c.x++; + if (str[i] == '\0') + table.set_cell (c, styled_string (sm, "NUL")); + else + table.set_cell (c, styled_string ((cppchar_t)str[i])); + } + + table.set_cell_span (table::rect_t (table::coord_t (0, 2), + table::size_t (buf_size, 1)), + styled_string::from_fmt (sm, + nullptr, + "'buf' (char[%i])", + (int)buf_size)); + table.set_cell_span (table::rect_t (table::coord_t (buf_size + 1, 2), + table::size_t (str_size - buf_size, 1)), + styled_string (sm, "overflow")); + + { + canvas canvas (table.to_canvas (ascii_theme (), sm)); + ASSERT_CANVAS_STREQ + (canvas, false, + "+-----------------------------+\n" + "| String literal |\n" + "+-+-+-+-+-+-+-+-+-+-++-+-+----+\n" + "|h|e|l|l|o| |w|o|r|l||d|!|NUL |\n" + "+-+-+-+-+-+-+-+-+-+-++-+-+----+\n" + "| 'buf' (char[10]) ||overflow|\n" + "+-------------------++--------+\n"); + } + { + canvas canvas (table.to_canvas (unicode_theme (), sm)); + ASSERT_CANVAS_STREQ + (canvas, false, + // FIXME: are we allowed unicode chars in string literals in our source? + ("┌─────────────────────────────┐\n" + "│ String literal │\n" + "├─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬┬─┬─┬────┤\n" + "│h│e│l│l│o│ │w│o│r│l││d│!│NUL │\n" + "├─┴─┴─┴─┴─┴─┴─┴─┴─┴─┤├─┴─┴────┤\n" + "│ 'buf' (char[10]) ││overflow│\n" + "└───────────────────┘└────────┘\n")); + } +} + +static void +test_double_width_chars () +{ + table_cell_content tcc (styled_string ((cppchar_t)0x1f642)); + ASSERT_EQ (tcc.get_canvas_size ().w, 2); + ASSERT_EQ (tcc.get_canvas_size ().h, 1); + + style_manager sm; + table table (table::size_t (1, 1)); + table.set_cell (table::coord_t (0,0), + styled_string ((cppchar_t)0x1f642)); + + canvas canvas (table.to_canvas (unicode_theme(), sm)); + ASSERT_CANVAS_STREQ + (canvas, false, + // FIXME: are we allowed unicode chars in string literals in our source? + ("┌──┐\n" + "│🙂│\n" + "└──┘\n")); +} + +static void +test_ipv4_header () +{ + style_manager sm; + table table (table::size_t (34, 10)); + table.set_cell (table::coord_t (0, 0), styled_string (sm, "Offsets")); + table.set_cell (table::coord_t (1, 0), styled_string (sm, "Octet")); + table.set_cell (table::coord_t (0, 1), styled_string (sm, "Octet")); + for (int octet = 0; octet < 4; octet++) + table.set_cell_span (table::rect_t (table::coord_t (2 + (octet * 8), 0), + table::size_t (8, 1)), + styled_string::from_fmt (sm, nullptr, "%i", octet)); + table.set_cell (table::coord_t (1, 1), styled_string (sm, "Bit")); + for (int bit = 0; bit < 32; bit++) + table.set_cell (table::coord_t (bit + 2, 1), + styled_string::from_fmt (sm, nullptr, "%i", bit)); + for (int word = 0; word < 6; word++) + { + table.set_cell (table::coord_t (0, word + 2), + styled_string::from_fmt (sm, nullptr, "%i", word * 4)); + table.set_cell (table::coord_t (1, word + 2), + styled_string::from_fmt (sm, nullptr, "%i", word * 32)); + } + + table.set_cell (table::coord_t (0, 8), styled_string (sm, "...")); + table.set_cell (table::coord_t (1, 8), styled_string (sm, "...")); + table.set_cell (table::coord_t (0, 9), styled_string (sm, "56")); + table.set_cell (table::coord_t (1, 9), styled_string (sm, "448")); + +#define SET_BITS(FIRST, LAST, NAME) \ + do { \ + const int first = (FIRST); \ + const int last = (LAST); \ + const char *name = (NAME); \ + const int row = first / 32; \ + gcc_assert (last / 32 == row); \ + table::rect_t rect (table::coord_t ((first % 32) + 2, row + 2), \ + table::size_t (last + 1 - first , 1)); \ + table.set_cell_span (rect, styled_string (sm, name)); \ + } while (0) + + SET_BITS (0, 3, "Version"); + SET_BITS (4, 7, "IHL"); + SET_BITS (8, 13, "DSCP"); + SET_BITS (14, 15, "ECN"); + SET_BITS (16, 31, "Total Length"); + + SET_BITS (32 + 0, 32 + 15, "Identification"); + SET_BITS (32 + 16, 32 + 18, "Flags"); + SET_BITS (32 + 19, 32 + 31, "Fragment Offset"); + + SET_BITS (64 + 0, 64 + 7, "Time To Live"); + SET_BITS (64 + 8, 64 + 15, "Protocol"); + SET_BITS (64 + 16, 64 + 31, "Header Checksum"); + + SET_BITS (96 + 0, 96 + 31, "Source IP Address"); + SET_BITS (128 + 0, 128 + 31, "Destination IP Address"); + + table.set_cell_span(table::rect_t (table::coord_t (2, 7), + table::size_t (32, 3)), + styled_string (sm, "Options")); + { + canvas canvas (table.to_canvas (ascii_theme(), sm)); + ASSERT_CANVAS_STREQ + (canvas, false, + ("+-------+-----+---------------+---------------------+-----------------------+-----------------------+\n" + "|Offsets|Octet| 0 | 1 | 2 | 3 |\n" + "+-------+-----+-+-+-+-+-+-+-+-+-+-+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+\n" + "| Octet | Bit |0|1|2|3|4|5|6|7|8|9|10|11|12|13|14|15|16|17|18|19|20|21|22|23|24|25|26|27|28|29|30|31|\n" + "+-------+-----+-+-+-+-+-+-+-+-+-+-+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+\n" + "| 0 | 0 |Version| IHL | DSCP | ECN | Total Length |\n" + "+-------+-----+-------+-------+---------------+-----+--------+--------------------------------------+\n" + "| 4 | 32 | Identification | Flags | Fragment Offset |\n" + "+-------+-----+---------------+---------------------+--------+--------------------------------------+\n" + "| 8 | 64 | Time To Live | Protocol | Header Checksum |\n" + "+-------+-----+---------------+---------------------+-----------------------------------------------+\n" + "| 12 | 96 | Source IP Address |\n" + "+-------+-----+-------------------------------------------------------------------------------------+\n" + "| 16 | 128 | Destination IP Address |\n" + "+-------+-----+-------------------------------------------------------------------------------------+\n" + "| 20 | 160 | |\n" + "+-------+-----+ |\n" + "| ... | ... | Options |\n" + "+-------+-----+ |\n" + "| 56 | 448 | |\n" + "+-------+-----+-------------------------------------------------------------------------------------+\n")); + } + { + canvas canvas (table.to_canvas (unicode_theme(), sm)); + ASSERT_CANVAS_STREQ + (canvas, false, + // FIXME: are we allowed unicode chars in string literals in our source? + ("┌───────┬─────┬───────────────┬─────────────────────┬───────────────────────┬───────────────────────┐\n" + "│Offsets│Octet│ 0 │ 1 │ 2 │ 3 │\n" + "├───────┼─────┼─┬─┬─┬─┬─┬─┬─┬─┼─┬─┬──┬──┬──┬──┬──┬──┼──┬──┬──┬──┬──┬──┬──┬──┼──┬──┬──┬──┬──┬──┬──┬──┤\n" + "│ Octet │ Bit │0│1│2│3│4│5│6│7│8│9│10│11│12│13│14│15│16│17│18│19│20│21│22│23│24│25│26│27│28│29│30│31│\n" + "├───────┼─────┼─┴─┴─┴─┼─┴─┴─┴─┼─┴─┴──┴──┴──┴──┼──┴──┼──┴──┴──┴──┴──┴──┴──┴──┴──┴──┴──┴──┴──┴──┴──┴──┤\n" + "│ 0 │ 0 │Version│ IHL │ DSCP │ ECN │ Total Length │\n" + "├───────┼─────┼───────┴───────┴───────────────┴─────┼────────┬──────────────────────────────────────┤\n" + "│ 4 │ 32 │ Identification │ Flags │ Fragment Offset │\n" + "├───────┼─────┼───────────────┬─────────────────────┼────────┴──────────────────────────────────────┤\n" + "│ 8 │ 64 │ Time To Live │ Protocol │ Header Checksum │\n" + "├───────┼─────┼───────────────┴─────────────────────┴───────────────────────────────────────────────┤\n" + "│ 12 │ 96 │ Source IP Address │\n" + "├───────┼─────┼─────────────────────────────────────────────────────────────────────────────────────┤\n" + "│ 16 │ 128 │ Destination IP Address │\n" + "├───────┼─────┼─────────────────────────────────────────────────────────────────────────────────────┤\n" + "│ 20 │ 160 │ │\n" + "├───────┼─────┤ │\n" + "│ ... │ ... │ Options │\n" + "├───────┼─────┤ │\n" + "│ 56 │ 448 │ │\n" + "└───────┴─────┴─────────────────────────────────────────────────────────────────────────────────────┘\n")); + } +} + +static void +test_missing_cells () +{ + style_manager sm; + table table (table::size_t (3, 3)); + table.set_cell (table::coord_t (1, 0), styled_string (sm, "A")); + table.set_cell (table::coord_t (0, 1), styled_string (sm, "B")); + table.set_cell (table::coord_t (1, 1), styled_string (sm, "C")); + table.set_cell (table::coord_t (2, 1), styled_string (sm, "D")); + table.set_cell (table::coord_t (1, 2), styled_string (sm, "E")); + + ASSERT_TABLE_NULL_CELL (table, 0, 0); + ASSERT_TABLE_CELL_STREQ (table, 1, 0, "A"); + ASSERT_TABLE_NULL_CELL (table, 2, 0); + + ASSERT_TABLE_CELL_STREQ (table, 0, 1, "B"); + ASSERT_TABLE_CELL_STREQ (table, 1, 1, "C"); + ASSERT_TABLE_CELL_STREQ (table, 2, 1, "D"); + + ASSERT_TABLE_NULL_CELL (table, 0, 2); + ASSERT_TABLE_CELL_STREQ (table, 1, 2, "E"); + ASSERT_TABLE_NULL_CELL (table, 2, 2); + + { + canvas canvas (table.to_canvas (ascii_theme(), sm)); + ASSERT_CANVAS_STREQ + (canvas, false, + (" +-+\n" + " |A|\n" + "+-+-+-+\n" + "|B|C|D|\n" + "+-+-+-+\n" + " |E|\n" + " +-+\n")); + } + { + canvas canvas (table.to_canvas (unicode_theme(), sm)); + ASSERT_CANVAS_STREQ + (canvas, false, + (" ┌─┐\n" + " │A│\n" + "┌─┼─┼─┐\n" + "│B│C│D│\n" + "└─┼─┼─┘\n" + " │E│\n" + " └─┘\n")); + } +} + +static void +test_add_row () +{ + style_manager sm; + table table (table::size_t (3, 0)); + for (int i = 0; i < 5; i++) + { + const int y = table.add_row (); + for (int x = 0; x < 3; x++) + table.set_cell (table::coord_t (x, y), + styled_string::from_fmt (sm, nullptr, + "%i, %i", x, y)); + } + canvas canvas (table.to_canvas (ascii_theme(), sm)); + ASSERT_CANVAS_STREQ + (canvas, false, + ("+----+----+----+\n" + "|0, 0|1, 0|2, 0|\n" + "+----+----+----+\n" + "|0, 1|1, 1|2, 1|\n" + "+----+----+----+\n" + "|0, 2|1, 2|2, 2|\n" + "+----+----+----+\n" + "|0, 3|1, 3|2, 3|\n" + "+----+----+----+\n" + "|0, 4|1, 4|2, 4|\n" + "+----+----+----+\n")); +} + +static void +test_alignment () +{ + style_manager sm; + table table (table::size_t (9, 9)); + table.set_cell_span (table::rect_t (table::coord_t (0, 0), + table::size_t (3, 3)), + styled_string (sm, "left top"), + x_align::LEFT, y_align::TOP); + table.set_cell_span (table::rect_t (table::coord_t (3, 0), + table::size_t (3, 3)), + styled_string (sm, "center top"), + x_align::CENTER, y_align::TOP); + table.set_cell_span (table::rect_t (table::coord_t (6, 0), + table::size_t (3, 3)), + styled_string (sm, "right top"), + x_align::RIGHT, y_align::TOP); + table.set_cell_span (table::rect_t (table::coord_t (0, 3), + table::size_t (3, 3)), + styled_string (sm, "left center"), + x_align::LEFT, y_align::CENTER); + table.set_cell_span (table::rect_t (table::coord_t (3, 3), + table::size_t (3, 3)), + styled_string (sm, "center center"), + x_align::CENTER, y_align::CENTER); + table.set_cell_span (table::rect_t (table::coord_t (6, 3), + table::size_t (3, 3)), + styled_string (sm, "right center"), + x_align::RIGHT, y_align::CENTER); + table.set_cell_span (table::rect_t (table::coord_t (0, 6), + table::size_t (3, 3)), + styled_string (sm, "left bottom"), + x_align::LEFT, y_align::BOTTOM); + table.set_cell_span (table::rect_t (table::coord_t (3, 6), + table::size_t (3, 3)), + styled_string (sm, "center bottom"), + x_align::CENTER, y_align::BOTTOM); + table.set_cell_span (table::rect_t (table::coord_t (6, 6), + table::size_t (3, 3)), + styled_string (sm, "right bottom"), + x_align::RIGHT, y_align::BOTTOM); + + canvas canvas (table.to_canvas (ascii_theme(), sm)); + ASSERT_CANVAS_STREQ + (canvas, false, + ("+-----------+-------------+------------+\n" + "|left top | center top | right top|\n" + "| | | |\n" + "+-----------+-------------+------------+\n" + "|left center|center center|right center|\n" + "| | | |\n" + "+-----------+-------------+------------+\n" + "| | | |\n" + "|left bottom|center bottom|right bottom|\n" + "+-----------+-------------+------------+\n")); +} + +/* Run all selftests in this file. */ + +void +text_art_table_cc_tests () +{ + test_tic_tac_toe (); + test_text_table (); + test_offset_table (); + test_spans (); + test_spans_2 (); + test_spans_3 (); + test_double_width_chars (); + test_ipv4_header (); + test_missing_cells (); + test_add_row (); + test_alignment (); +} + +} // namespace selftest + + +#endif /* #if CHECKING_P */ diff --git a/gcc/text-art/table.h b/gcc/text-art/table.h new file mode 100644 index 00000000000..5e6c8ffb836 --- /dev/null +++ b/gcc/text-art/table.h @@ -0,0 +1,262 @@ +/* Support for tabular/grid-based content. + Copyright (C) 2023 Free Software Foundation, Inc. + Contributed by David Malcolm <dmalcolm@redhat.com>. + +This file is part of GCC. + +GCC is free software; you can redistribute it and/or modify it +under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 3, or (at your option) +any later version. + +GCC is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GCC; see the file COPYING3. If not see +<http://www.gnu.org/licenses/>. */ + +#ifndef GCC_TEXT_ART_TABLE_H +#define GCC_TEXT_ART_TABLE_H + +#include "text-art/canvas.h" +#include "text-art/theme.h" +#include <vector> + +namespace text_art { + +class table; +class table_geometry; + +/* A class representing the content of a particular table cell, + or of a span of table cells. */ + +class table_cell_content +{ + public: + table_cell_content () : m_str (), m_size (0, 0) {} + table_cell_content (styled_string &&s); + + bool operator== (const table_cell_content &other) const + { + return m_str == other.m_str; + } + + canvas::size_t get_canvas_size () const { return m_size; } + + void paint_to_canvas (canvas &canvas, + canvas::coord_t top_left) const; + + private: + styled_string m_str; + canvas::size_t m_size; +}; + +/* A list of required sizes of table rows or columns + in canvas units (row heights or column widths). */ + +struct table_dimension_sizes +{ + table_dimension_sizes (unsigned num); + + void require (unsigned idx, int amount) + { + m_requirements[idx] = std::max (m_requirements[idx], amount); + } + + std::vector<int> m_requirements; +}; + +/* A 2D grid of cells. Instances of table_cell_content can be assigned + to individual table cells, and to rectangular spans of cells. Such + assignments do not have to fully cover the 2D grid, but they must not + overlap. */ + +class table +{ + public: + typedef size<class table> size_t; + typedef coord<class table> coord_t; + typedef range<class table> range_t; + typedef rect<class table> rect_t; + + /* A record of how a table_cell_content was placed at a table::rect_t + with a certain alignment. */ + class cell_placement + { + public: + cell_placement (rect_t rect, + table_cell_content &&content, + x_align x_align, + y_align y_align) + : m_rect (rect), + m_content (std::move (content)), + m_x_align (x_align), + m_y_align (y_align) + { + } + + bool one_by_one_p () const + { + return m_rect.m_size.w == 1 && m_rect.m_size.h == 1; + } + + canvas::size_t get_min_canvas_size () const + { + // Doesn't include border + return m_content.get_canvas_size (); + } + + void paint_cell_contents_to_canvas(canvas &canvas, + canvas::coord_t offset, + const table_geometry &tg) const; + + const table_cell_content &get_content () const { return m_content; } + + private: + friend class table_cell_sizes; + rect_t m_rect; + table_cell_content m_content; + x_align m_x_align; + y_align m_y_align; + }; + + table (size_t size); + ~table () = default; + table (table &&) = default; + table (const table &) = delete; + table &operator= (const table &) = delete; + + const size_t &get_size () const { return m_size; } + + int add_row () + { + m_size.h++; + m_occupancy.add_row (-1); + return m_size.h - 1; // return the table_y of the newly-added row + } + + void set_cell (coord_t coord, + table_cell_content &&content, + enum x_align x_align = x_align::CENTER, + enum y_align y_align = y_align::CENTER); + + void set_cell_span (rect_t span, + table_cell_content &&content, + enum x_align x_align = x_align::CENTER, + enum y_align y_align = y_align::CENTER); + + canvas to_canvas (const theme &theme, const style_manager &sm) const; + + void paint_to_canvas(canvas &canvas, + canvas::coord_t offset, + const table_geometry &tg, + const theme &theme) const; + + void debug () const; + + /* Self-test support. */ + const cell_placement *get_placement_at (coord_t coord) const; + + private: + int get_occupancy_safe (coord_t coord) const; + directions get_connections (int table_x, int table_y) const; + void paint_cell_borders_to_canvas(canvas &canvas, + canvas::coord_t offset, + const table_geometry &tg, + const theme &theme) const; + void paint_cell_contents_to_canvas(canvas &canvas, + canvas::coord_t offset, + const table_geometry &tg) const; + + friend class table_cell_sizes; + + size_t m_size; + std::vector<cell_placement> m_placements; + array2<int, size_t, coord_t> m_occupancy; /* indices into the m_placements vec. */ +}; + +/* A workspace for computing the row heights and column widths + of a table (in canvas units). + The col_widths and row_heights could be shared between multiple + instances, for aligning multiple tables vertically or horizontally. */ + +class table_cell_sizes +{ + public: + table_cell_sizes (table_dimension_sizes &col_widths, + table_dimension_sizes &row_heights) + : m_col_widths (col_widths), + m_row_heights (row_heights) + { + } + + void pass_1 (const table &table); + void pass_2 (const table &table); + + canvas::size_t get_canvas_size (const table::rect_t &rect) const; + + table_dimension_sizes &m_col_widths; + table_dimension_sizes &m_row_heights; +}; + +/* A class responsible for mapping from table cell coords + to canvas coords, handling column widths. + It's the result of solving "how big are all the table cells and where + do they go?" + The cell_sizes are passed in, for handling aligning multiple tables, + sharing column widths or row heights. */ + +class table_geometry +{ + public: + table_geometry (const table &table, table_cell_sizes &cell_sizes); + + void recalc_coords (); + + const canvas::size_t get_canvas_size () const { return m_canvas_size; } + + canvas::coord_t table_to_canvas (table::coord_t table_coord) const; + int table_x_to_canvas_x (int table_x) const; + int table_y_to_canvas_y (int table_y) const; + + int get_col_width (int table_x) const + { + return m_cell_sizes.m_col_widths.m_requirements[table_x]; + } + + canvas::size_t get_canvas_size (const table::rect_t &rect) const + { + return m_cell_sizes.get_canvas_size (rect); + } + + private: + const table &m_table; + table_cell_sizes &m_cell_sizes; + canvas::size_t m_canvas_size; + + /* Start canvas column of table cell, including leading border. */ + std::vector<int> m_col_start_x; + + /* Start canvas row of table cell, including leading border. */ + std::vector<int> m_row_start_y; +}; + +/* Helper class for handling the simple case of a single table + that doesn't need to be aligned with respect to anything else. */ + +struct simple_table_geometry +{ + simple_table_geometry (const table &table); + + table_dimension_sizes m_col_widths; + table_dimension_sizes m_row_heights; + table_cell_sizes m_cell_sizes; + table_geometry m_tg; +}; + +} // namespace text_art + +#endif /* GCC_TEXT_ART_TABLE_H */ diff --git a/gcc/text-art/theme.cc b/gcc/text-art/theme.cc new file mode 100644 index 00000000000..54dfe7c9213 --- /dev/null +++ b/gcc/text-art/theme.cc @@ -0,0 +1,183 @@ +/* Classes for abstracting ascii vs unicode output. + Copyright (C) 2023 Free Software Foundation, Inc. + Contributed by David Malcolm <dmalcolm@redhat.com>. + +This file is part of GCC. + +GCC is free software; you can redistribute it and/or modify it under +the terms of the GNU General Public License as published by the Free +Software Foundation; either version 3, or (at your option) any later +version. + +GCC is distributed in the hope that it will be useful, but WITHOUT ANY +WARRANTY; without even the implied warranty of MERCHANTABILITY or +FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License +for more details. + +You should have received a copy of the GNU General Public License +along with GCC; see the file COPYING3. If not see +<http://www.gnu.org/licenses/>. */ + +#include "config.h" +#include "system.h" +#include "coretypes.h" +#include "pretty-print.h" +#include "selftest.h" +#include "text-art/selftests.h" +#include "text-art/ruler.h" +#include "text-art/theme.h" + +using namespace text_art; + +/* class theme. */ + +void +theme::paint_y_arrow (canvas &canvas, + int canvas_x, + canvas::range_t y_range, + y_arrow_dir dir, + style::id_t style_id) const +{ + int canvas_y; + int delta_y; + const canvas::cell_t head (get_cppchar (dir == y_arrow_dir::UP + ? cell_kind::Y_ARROW_UP_HEAD + : cell_kind::Y_ARROW_DOWN_HEAD), + false, style_id); + const canvas::cell_t tail (get_cppchar (dir == y_arrow_dir::UP + ? cell_kind::Y_ARROW_UP_TAIL + : cell_kind::Y_ARROW_DOWN_TAIL), + false, style_id); + if (dir == y_arrow_dir::UP) + { + canvas_y = y_range.get_max (); + delta_y = -1; + } + else + { + canvas_y = y_range.get_min (); + delta_y = 1; + } + for (int len = y_range.get_size (); len; len--) + { + const canvas::cell_t cell = (len > 1) ? tail : head; + canvas.paint (canvas::coord_t (canvas_x, canvas_y), cell); + canvas_y += delta_y; + } +} + +/* class ascii_theme : public theme. */ + +canvas::cell_t +ascii_theme::get_line_art (directions line_dirs) const +{ + if (line_dirs.m_up + && line_dirs.m_down + && !(line_dirs.m_left || line_dirs.m_right)) + return canvas::cell_t ('|'); + if (line_dirs.m_left + && line_dirs.m_right + && !(line_dirs.m_up || line_dirs.m_down)) + return canvas::cell_t ('-'); + if (line_dirs.m_up + || line_dirs.m_down + || line_dirs.m_left + || line_dirs.m_right) + return canvas::cell_t ('+'); + return canvas::cell_t (' '); +} + +cppchar_t +ascii_theme::get_cppchar (enum cell_kind kind) const +{ + switch (kind) + { + default: + gcc_unreachable (); + case cell_kind::X_RULER_LEFT_EDGE: + return '|'; + case cell_kind::X_RULER_MIDDLE: + return '~'; + case cell_kind::X_RULER_INTERNAL_EDGE: + return '|'; + case cell_kind::X_RULER_CONNECTOR_TO_LABEL_BELOW: + case cell_kind::X_RULER_CONNECTOR_TO_LABEL_ABOVE: + return '+'; + case cell_kind::X_RULER_RIGHT_EDGE: + return '|'; + case cell_kind::X_RULER_VERTICAL_CONNECTOR: + return '|'; + + case cell_kind::TEXT_BORDER_HORIZONTAL: + return '-'; + case cell_kind::TEXT_BORDER_VERTICAL: + return '|'; + case cell_kind::TEXT_BORDER_TOP_LEFT: + case cell_kind::TEXT_BORDER_TOP_RIGHT: + case cell_kind::TEXT_BORDER_BOTTOM_LEFT: + case cell_kind::TEXT_BORDER_BOTTOM_RIGHT: + return '+'; + + case cell_kind::Y_ARROW_UP_HEAD: return '^'; + case cell_kind::Y_ARROW_DOWN_HEAD: return 'v'; + + case cell_kind::Y_ARROW_UP_TAIL: + case cell_kind::Y_ARROW_DOWN_TAIL: + return '|'; + } +} + +/* class unicode_theme : public theme. */ + +canvas::cell_t +unicode_theme::get_line_art (directions line_dirs) const +{ + return canvas::cell_t (get_box_drawing_char (line_dirs)); +} + +cppchar_t +unicode_theme::get_cppchar (enum cell_kind kind) const +{ + switch (kind) + { + default: + gcc_unreachable (); + case cell_kind::X_RULER_LEFT_EDGE: + return 0x251C; /* "├": U+251C: BOX DRAWINGS LIGHT VERTICAL AND RIGHT */ + case cell_kind::X_RULER_MIDDLE: + return 0x2500; /* "─": U+2500: BOX DRAWINGS LIGHT HORIZONTAL */ + case cell_kind::X_RULER_INTERNAL_EDGE: + return 0x253C; /* "┼": U+253C: BOX DRAWINGS LIGHT VERTICAL AND HORIZONTAL */ + case cell_kind::X_RULER_CONNECTOR_TO_LABEL_BELOW: + return 0x252C; /* "┬": U+252C: BOX DRAWINGS LIGHT DOWN AND HORIZONTAL */ + case cell_kind::X_RULER_CONNECTOR_TO_LABEL_ABOVE: + return 0x2534; /* "┴": U+2534: BOX DRAWINGS LIGHT UP AND HORIZONTAL */ + case cell_kind::X_RULER_RIGHT_EDGE: + return 0x2524; /* "┤": U+2524: BOX DRAWINGS LIGHT VERTICAL AND LEFT */ + case cell_kind::X_RULER_VERTICAL_CONNECTOR: + return 0x2502; /* "│": U+2502: BOX DRAWINGS LIGHT VERTICAL */ + + case cell_kind::TEXT_BORDER_HORIZONTAL: + return 0x2500; /* "─": U+2500: BOX DRAWINGS LIGHT HORIZONTAL */ + case cell_kind::TEXT_BORDER_VERTICAL: + return 0x2502; /* "│": U+2502: BOX DRAWINGS LIGHT VERTICAL */ + + /* Round corners. */ + case cell_kind::TEXT_BORDER_TOP_LEFT: + return 0x256D; /* "╭": U+256D BOX DRAWINGS LIGHT ARC DOWN AND RIGHT. */ + case cell_kind::TEXT_BORDER_TOP_RIGHT: + return 0x256E; /* "╮": U+256E BOX DRAWINGS LIGHT ARC DOWN AND LEFT. */ + case cell_kind::TEXT_BORDER_BOTTOM_LEFT: + return 0x2570; /* "╰": U+2570 BOX DRAWINGS LIGHT ARC UP AND RIGHT. */ + case cell_kind::TEXT_BORDER_BOTTOM_RIGHT: + return 0x256F; /* "╯": U+256F BOX DRAWINGS LIGHT ARC UP AND LEFT. */ + + case cell_kind::Y_ARROW_UP_HEAD: + return '^'; + case cell_kind::Y_ARROW_DOWN_HEAD: + return 'v'; + case cell_kind::Y_ARROW_UP_TAIL: + case cell_kind::Y_ARROW_DOWN_TAIL: + return 0x2502; /* "│": U+2502: BOX DRAWINGS LIGHT VERTICAL */ + } +} diff --git a/gcc/text-art/theme.h b/gcc/text-art/theme.h new file mode 100644 index 00000000000..8edbc6efc76 --- /dev/null +++ b/gcc/text-art/theme.h @@ -0,0 +1,123 @@ +/* Classes for abstracting ascii vs unicode output. + Copyright (C) 2023 Free Software Foundation, Inc. + Contributed by David Malcolm <dmalcolm@redhat.com>. + +This file is part of GCC. + +GCC is free software; you can redistribute it and/or modify it +under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 3, or (at your option) +any later version. + +GCC is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GCC; see the file COPYING3. If not see +<http://www.gnu.org/licenses/>. */ + +#ifndef GCC_TEXT_ART_THEME_H +#define GCC_TEXT_ART_THEME_H + +#include "text-art/canvas.h" +#include "text-art/box-drawing.h" + +namespace text_art { + +class theme +{ + public: + enum class cell_kind + { + /* A left-hand edge of a range e.g. "├". */ + X_RULER_LEFT_EDGE, + + /* Within a range e.g. "─". */ + X_RULER_MIDDLE, + + /* A border between two neighboring ranges e.g. "┼". */ + X_RULER_INTERNAL_EDGE, + + /* The connector with the text label within a range e.g. "┬". */ + X_RULER_CONNECTOR_TO_LABEL_BELOW, + + /* As above, but when the text label is above the ruler. */ + X_RULER_CONNECTOR_TO_LABEL_ABOVE, + + /* The vertical connection to a text label. */ + X_RULER_VERTICAL_CONNECTOR, + + /* A right-hand edge of a range e.g. "┤". */ + X_RULER_RIGHT_EDGE, + + TEXT_BORDER_HORIZONTAL, + TEXT_BORDER_VERTICAL, + TEXT_BORDER_TOP_LEFT, + TEXT_BORDER_TOP_RIGHT, + TEXT_BORDER_BOTTOM_LEFT, + TEXT_BORDER_BOTTOM_RIGHT, + + Y_ARROW_UP_HEAD, + Y_ARROW_UP_TAIL, + Y_ARROW_DOWN_HEAD, + Y_ARROW_DOWN_TAIL, + }; + + virtual ~theme () = default; + + virtual bool unicode_p () const = 0; + virtual bool emojis_p () const = 0; + + virtual canvas::cell_t + get_line_art (directions line_dirs) const = 0; + + canvas::cell_t get_cell (enum cell_kind kind, unsigned style_idx) const + { + return canvas::cell_t (get_cppchar (kind), false, style_idx); + } + + virtual cppchar_t get_cppchar (enum cell_kind kind) const = 0; + + enum class y_arrow_dir { UP, DOWN }; + void paint_y_arrow (canvas &canvas, + int x, + canvas::range_t y_range, + y_arrow_dir dir, + style::id_t style_id) const; +}; + +class ascii_theme : public theme +{ + public: + bool unicode_p () const final override { return false; } + bool emojis_p () const final override { return false; } + + canvas::cell_t + get_line_art (directions line_dirs) const final override; + + cppchar_t get_cppchar (enum cell_kind kind) const final override; +}; + +class unicode_theme : public theme +{ + public: + bool unicode_p () const final override { return true; } + bool emojis_p () const override { return false; } + + canvas::cell_t + get_line_art (directions line_dirs) const final override; + + cppchar_t get_cppchar (enum cell_kind kind) const final override; +}; + +class emoji_theme : public unicode_theme +{ +public: + bool emojis_p () const final override { return true; } +}; + +} // namespace text_art + +#endif /* GCC_TEXT_ART_THEME_H */ diff --git a/gcc/text-art/types.h b/gcc/text-art/types.h new file mode 100644 index 00000000000..b66188ae19c --- /dev/null +++ b/gcc/text-art/types.h @@ -0,0 +1,504 @@ +/* Types for drawing 2d "text art". + Copyright (C) 2023 Free Software Foundation, Inc. + Contributed by David Malcolm <dmalcolm@redhat.com>. + +This file is part of GCC. + +GCC is free software; you can redistribute it and/or modify it +under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 3, or (at your option) +any later version. + +GCC is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GCC; see the file COPYING3. If not see +<http://www.gnu.org/licenses/>. */ + +#ifndef GCC_TEXT_ART_TYPES_H +#define GCC_TEXT_ART_TYPES_H + +#include "cpplib.h" +#include "pretty-print.h" +#include <vector> +#include <string> + +namespace text_art { + +/* Forward decls. */ + +class canvas; +class table; +class theme; + +/* Classes for geometry. + We use templates to avoid mixing up e.g. canvas coordinates + with table coordinates. */ + +template <typename CoordinateSystem> +struct size +{ + size (int w_, int h_) : w (w_), h (h_) {} + int w; + int h; +}; + +template <typename CoordinateSystem> +struct coord +{ + coord (int x_, int y_) : x (x_), y (y_) {} + int x; + int y; +}; + +template <typename CoordinateSystem> +coord<CoordinateSystem> operator+ (coord<CoordinateSystem> a, + coord<CoordinateSystem> b) +{ + return coord<CoordinateSystem> (a.x + b.x, a.y + b.y); +} + +/* A half-open range [start, next) of int. */ + +template <typename CoordinateSystem> +struct range +{ + range (int start_, int next_) + : start (start_), next (next_) + {} + + int get_min () const { return start; } + int get_max () const { return next - 1; } + int get_next () const { return next; } + int get_size () const { return next - start; } + + int get_midpoint () const { return get_min () + get_size () / 2; } + + int start; + int next; +}; + +/* A rectangle area within CoordinateSystem. */ + +template <typename CoordinateSystem> +struct rect +{ + rect (coord<CoordinateSystem> top_left, + size<CoordinateSystem> size) + : m_top_left (top_left), + m_size (size) + { + } + + rect (range<CoordinateSystem> x_range, + range<CoordinateSystem> y_range) + : m_top_left (x_range.get_min (), y_range.get_min ()), + m_size (x_range.get_size (), y_range.get_size ()) + { + } + + int get_min_x () const { return m_top_left.x; } + int get_min_y () const { return m_top_left.y; } + int get_max_x () const { return m_top_left.x + m_size.w - 1; } + int get_max_y () const { return m_top_left.y + m_size.h - 1; } + int get_next_x () const { return m_top_left.x + m_size.w; } + int get_next_y () const { return m_top_left.y + m_size.h; } + + range<CoordinateSystem> get_x_range () const + { + return range<CoordinateSystem> (get_min_x (), get_next_x ()); + } + range<CoordinateSystem> get_y_range () const + { + return range<CoordinateSystem> (get_min_y (), get_next_y ()); + } + + int get_width () const { return m_size.w; } + int get_height () const { return m_size.h; } + + coord<CoordinateSystem> m_top_left; + size<CoordinateSystem> m_size; +}; + +template <typename ElementType, typename SizeType, typename CoordType> +class array2 +{ + public: + typedef ElementType element_t; + typedef SizeType size_t; + typedef CoordType coord_t; + + array2 (size_t sz) + : m_size (sz), + m_elements (sz.w * sz.h) + { + } + array2 (array2 &&other) + : m_size (other.m_size), + m_elements (std::move (other.m_elements)) + { + } + + /* Move assignment not implemented or used. */ + array2 &operator== (array2 &&other) = delete; + + /* No copy ctor or assignment op. */ + array2 (const array2 &other) = delete; + array2 &operator= (const array2 &other) = delete; + + + const size_t &get_size () const { return m_size; } + + void add_row (const element_t &element) + { + m_size.h++; + m_elements.insert (m_elements.end (), m_size.w, element); + } + + const element_t &get (const coord_t &coord) const + { + ::size_t idx = get_idx (coord); + return m_elements[idx]; + } + + void set (const coord_t &coord, const element_t &element) + { + ::size_t idx = get_idx (coord); + m_elements[idx] = element; + } + + void fill (element_t element) + { + for (int y = 0; y < m_size.h; y++) + for (int x = 0; x < m_size.w; x++) + set (coord_t (x, y), element); + } + + private: + ::size_t get_idx (const coord_t &coord) const + { + gcc_assert (coord.x >= 0); + gcc_assert (coord.x < m_size.w); + gcc_assert (coord.y >= 0); + gcc_assert (coord.y < m_size.h); + return (coord.y * m_size.w) + coord.x; + } + + size_t m_size; + std::vector<element_t> m_elements; +}; + +/* A combination of attributes describing how to style a text cell. + We only support those attributes mentioned in invoke.texi: + - bold, + - underscore, + - blink, + - inverse, + - colors for foreground and background: + - default color + - named colors + - 16-color mode colors (the "bright" variants) + - 88-color mode + - 256-color mode + plus URLs. */ + +struct style +{ + typedef unsigned char id_t; + static const id_t id_plain = 0; + + /* Colors. */ + enum class named_color + { + DEFAULT, + // ANSI order + BLACK, + RED, + GREEN, + YELLOW, + BLUE, + MAGENTA, + CYAN, + WHITE + }; + + + struct color + { + enum class kind + { + NAMED, + BITS_8, + BITS_24, + } m_kind; + + union + { + struct { + enum named_color m_name; + bool m_bright; + } m_named; + uint8_t m_8bit; + struct { + uint8_t r; + uint8_t g; + uint8_t b; + } m_24bit; + } u; + + /* Constructor for named colors. */ + color (enum named_color name = named_color::DEFAULT, + bool bright = false) + : m_kind (kind::NAMED) + { + u.m_named.m_name = name; + u.m_named.m_bright = bright; + } + + /* Constructor for 8-bit colors. */ + color (uint8_t col_val) + : m_kind (kind::BITS_8) + { + u.m_8bit = col_val; + } + + /* Constructor for 24-bit colors. */ + color (uint8_t r, uint8_t g, uint8_t b) + : m_kind (kind::BITS_24) + { + u.m_24bit.r = r; + u.m_24bit.g = g; + u.m_24bit.b = b; + } + + bool operator== (const color &other) const; + bool operator!= (const color &other) const + { + return !(*this == other); + } + + void print_sgr (pretty_printer *pp, bool fg, bool &need_separator) const; + }; + + style () + : m_bold (false), + m_underscore (false), + m_blink (false), + m_reverse (false), + m_fg_color (named_color::DEFAULT), + m_bg_color (named_color::DEFAULT), + m_url () + {} + + bool operator== (const style &other) const + { + return (m_bold == other.m_bold + && m_underscore == other.m_underscore + && m_blink == other.m_blink + && m_reverse == other.m_reverse + && m_fg_color == other.m_fg_color + && m_bg_color == other.m_bg_color + && m_url == other.m_url); + } + + style &set_style_url (const char *url); + + static void print_changes (pretty_printer *pp, + const style &old_style, + const style &new_style); + + bool m_bold; + bool m_underscore; + bool m_blink; + bool m_reverse; + color m_fg_color; + color m_bg_color; + std::vector<cppchar_t> m_url; // empty = no URL +}; + +/* A class to keep track of all the styles in use in a drawing, so that + we can refer to them via the compact style::id_t type, rather than + via e.g. pointers. */ + +class style_manager +{ + public: + style_manager (); + style::id_t get_or_create_id (const style &style); + const style &get_style (style::id_t id) const + { + return m_styles[id]; + } + void print_any_style_changes (pretty_printer *pp, + style::id_t old_id, + style::id_t new_id) const; + unsigned get_num_styles () const { return m_styles.size (); } + +private: + std::vector<style> m_styles; +}; + +class styled_unichar +{ + public: + friend class styled_string; + + explicit styled_unichar () + : m_code (0), + m_style_id (style::id_plain) + {} + explicit styled_unichar (cppchar_t ch) + : m_code (ch), + m_emoji_variant_p (false), + m_style_id (style::id_plain) + {} + explicit styled_unichar (cppchar_t ch, bool emoji, style::id_t style_id) + : m_code (ch), + m_emoji_variant_p (emoji), + m_style_id (style_id) + { + gcc_assert (style_id <= 0x7f); + } + + cppchar_t get_code () const { return m_code; } + bool emoji_variant_p () const { return m_emoji_variant_p; } + style::id_t get_style_id () const { return m_style_id; } + + bool double_width_p () const + { + int width = cpp_wcwidth (get_code ()); + gcc_assert (width == 1 || width == 2); + return width == 2; + } + + bool operator== (const styled_unichar &other) const + { + return (m_code == other.m_code + && m_emoji_variant_p == other.m_emoji_variant_p + && m_style_id == other.m_style_id); + } + + void set_emoji_variant () { m_emoji_variant_p = true; } + + int get_canvas_width () const + { + return cpp_wcwidth (m_code); + } + + void add_combining_char (cppchar_t ch) + { + m_combining_chars.push_back (ch); + } + + const std::vector<cppchar_t> get_combining_chars () const + { + return m_combining_chars; + } + +private: + cppchar_t m_code : 24; + bool m_emoji_variant_p : 1; + style::id_t m_style_id : 7; + std::vector<cppchar_t> m_combining_chars; +}; + +class styled_string +{ + public: + explicit styled_string () = default; + explicit styled_string (style_manager &sm, const char *str); + explicit styled_string (cppchar_t cppchar, bool emoji = false); + + styled_string (styled_string &&) = default; + styled_string &operator= (styled_string &&) = default; + + /* No copy ctor or assignment op. */ + styled_string (const styled_string &) = delete; + styled_string &operator= (const styled_string &) = delete; + + /* For the few cases where copying is required, spell it out explicitly. */ + styled_string copy () const + { + styled_string result; + result.m_chars = m_chars; + return result; + } + + bool operator== (const styled_string &other) const + { + return m_chars == other.m_chars; + } + + static styled_string from_fmt (style_manager &sm, + printer_fn format_decoder, + const char *fmt, ...) + ATTRIBUTE_GCC_PPDIAG(3, 4); + static styled_string from_fmt_va (style_manager &sm, + printer_fn format_decoder, + const char *fmt, + va_list *args) + ATTRIBUTE_GCC_PPDIAG(3, 0); + + size_t size () const { return m_chars.size (); } + styled_unichar operator[] (size_t idx) const { return m_chars[idx]; } + + std::vector<styled_unichar>::const_iterator begin () const + { + return m_chars.begin (); + } + std::vector<styled_unichar>::const_iterator end () const + { + return m_chars.end (); + } + + int calc_canvas_width () const; + + void append (const styled_string &suffix); + + void set_url (style_manager &sm, const char *url); + +private: + std::vector<styled_unichar> m_chars; +}; + +enum class x_align +{ + LEFT, + CENTER, + RIGHT +}; + +enum class y_align +{ + TOP, + CENTER, + BOTTOM +}; + +/* A set of cardinal directions within a canvas or table. */ + +struct directions +{ +public: + directions (bool up, bool down, bool left, bool right) + : m_up (up), m_down (down), m_left (left), m_right (right) + { + } + + size_t as_index () const + { + return (m_up << 3) | (m_down << 2) | (m_left << 1) | m_right; + } + + bool m_up: 1; + bool m_down: 1; + bool m_left: 1; + bool m_right: 1; +}; + +} // namespace text_art + +#endif /* GCC_TEXT_ART_TYPES_H */ diff --git a/gcc/text-art/widget.cc b/gcc/text-art/widget.cc new file mode 100644 index 00000000000..e6e544d5035 --- /dev/null +++ b/gcc/text-art/widget.cc @@ -0,0 +1,275 @@ +/* Hierarchical diagram elements. + Copyright (C) 2023 Free Software Foundation, Inc. + Contributed by David Malcolm <dmalcolm@redhat.com>. + +This file is part of GCC. + +GCC is free software; you can redistribute it and/or modify it under +the terms of the GNU General Public License as published by the Free +Software Foundation; either version 3, or (at your option) any later +version. + +GCC is distributed in the hope that it will be useful, but WITHOUT ANY +WARRANTY; without even the implied warranty of MERCHANTABILITY or +FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License +for more details. + +You should have received a copy of the GNU General Public License +along with GCC; see the file COPYING3. If not see +<http://www.gnu.org/licenses/>. */ + +#include "config.h" +#define INCLUDE_MEMORY +#include "system.h" +#include "coretypes.h" +#include "pretty-print.h" +#include "selftest.h" +#include "make-unique.h" +#include "text-art/selftests.h" +#include "text-art/widget.h" + +using namespace text_art; + +/* class text_art::widget. */ + +canvas +widget::to_canvas (const style_manager &style_mgr) +{ + const canvas::size_t req_size = get_req_size (); + + /* For now we don't constrain the allocation; we give + the widget the full size it requested, and widgets + assume they got their full size request. */ + const canvas::size_t alloc_size = req_size; + + set_alloc_rect (canvas::rect_t (canvas::coord_t (0, 0), alloc_size)); + canvas c (alloc_size, style_mgr); + paint_to_canvas (c); + return c; +} + +/* class text_art::vbox_widget : public text_art::container_widget. */ + +const char * +vbox_widget::get_desc () const +{ + return "vbox_widget"; +} + +canvas::size_t +vbox_widget::calc_req_size () +{ + canvas::size_t result (0, 0); + for (auto &child : m_children) + { + canvas::size_t child_req_size = child->get_req_size(); + result.h += child_req_size.h; + result.w = std::max (result.w, child_req_size.w); + } + return result; +} + +void +vbox_widget::update_child_alloc_rects () +{ + const int x = get_min_x (); + int y = get_min_y (); + for (auto &child : m_children) + { + child->set_alloc_rect + (canvas::rect_t (canvas::coord_t (x, y), + canvas::size_t (get_alloc_w (), + child->get_req_h ()))); + y += child->get_req_h (); + } +} + +/* class text_art::text_widget : public text_art::leaf_widget. */ + +const char * +text_widget::get_desc () const +{ + return "text_widget"; +} + +canvas::size_t +text_widget::calc_req_size () +{ + return canvas::size_t (m_str.size (), 1); +} + +void +text_widget::paint_to_canvas (canvas &canvas) +{ + canvas.paint_text (get_top_left (), m_str); +} + +/* class text_art::canvas_widget : public text_art::leaf_widget. */ + +const char * +canvas_widget::get_desc () const +{ + return "canvas_widget"; +} + +canvas::size_t +canvas_widget::calc_req_size () +{ + return m_canvas.get_size (); +} + +void +canvas_widget::paint_to_canvas (canvas &canvas) +{ + for (int y = 0; y < m_canvas.get_size ().h; y++) + for (int x = 0; x < m_canvas.get_size ().w; x++) + { + canvas::coord_t rel_xy (x, y); + canvas.paint (get_top_left () + rel_xy, + m_canvas.get (rel_xy)); + } +} + +#if CHECKING_P + +namespace selftest { + +/* Concrete widget subclass for writing selftests. + Requests a hard-coded size, and fills its allocated rectangle + with a specific character. */ + +class test_widget : public leaf_widget +{ +public: + test_widget (canvas::size_t size, char ch) + : m_test_size (size), m_ch (ch) + {} + + const char *get_desc () const final override + { + return "test_widget"; + } + canvas::size_t calc_req_size () final override + { + return m_test_size; + } + void paint_to_canvas (canvas &canvas) final override + { + canvas.fill (get_alloc_rect (), canvas::cell_t (m_ch)); + } + +private: + canvas::size_t m_test_size; + char m_ch; +}; + +static void +test_test_widget () +{ + style_manager sm; + test_widget w (canvas::size_t (3, 3), 'A'); + canvas c (w.to_canvas (sm)); + ASSERT_CANVAS_STREQ + (c, false, + ("AAA\n" + "AAA\n" + "AAA\n")); +} + +static void +test_text_widget () +{ + style_manager sm; + text_widget w (styled_string (sm, "hello world")); + canvas c (w.to_canvas (sm)); + ASSERT_CANVAS_STREQ + (c, false, + ("hello world\n")); +} + +static void +test_wrapper_widget () +{ + style_manager sm; + wrapper_widget w (::make_unique<test_widget> (canvas::size_t (3, 3), 'B')); + canvas c (w.to_canvas (sm)); + ASSERT_CANVAS_STREQ + (c, false, + ("BBB\n" + "BBB\n" + "BBB\n")); +} + +static void +test_vbox_1 () +{ + style_manager sm; + vbox_widget w; + for (int i = 0; i < 5; i++) + w.add_child + (::make_unique <text_widget> + (styled_string::from_fmt (sm, nullptr, + "this is line %i", i))); + canvas c (w.to_canvas (sm)); + ASSERT_CANVAS_STREQ + (c, false, + ("this is line 0\n" + "this is line 1\n" + "this is line 2\n" + "this is line 3\n" + "this is line 4\n")); +} + +static void +test_vbox_2 () +{ + style_manager sm; + vbox_widget w; + w.add_child (::make_unique<test_widget> (canvas::size_t (1, 3), 'A')); + w.add_child (::make_unique<test_widget> (canvas::size_t (4, 1), 'B')); + w.add_child (::make_unique<test_widget> (canvas::size_t (1, 2), 'C')); + canvas c (w.to_canvas (sm)); + ASSERT_CANVAS_STREQ + (c, false, + ("AAAA\n" + "AAAA\n" + "AAAA\n" + "BBBB\n" + "CCCC\n" + "CCCC\n")); +} + +static void +test_canvas_widget () +{ + style_manager sm; + canvas inner_canvas (canvas::size_t (5, 3), sm); + inner_canvas.fill (canvas::rect_t (canvas::coord_t (0, 0), + canvas::size_t (5, 3)), + canvas::cell_t ('a')); + canvas_widget cw (std::move (inner_canvas)); + canvas c (cw.to_canvas (sm)); + ASSERT_CANVAS_STREQ + (c, false, + ("aaaaa\n" + "aaaaa\n" + "aaaaa\n")); +} + +/* Run all selftests in this file. */ + +void +text_art_widget_cc_tests () +{ + test_test_widget (); + test_text_widget (); + test_wrapper_widget (); + test_vbox_1 (); + test_vbox_2 (); + test_canvas_widget (); +} + +} // namespace selftest + + +#endif /* #if CHECKING_P */ diff --git a/gcc/text-art/widget.h b/gcc/text-art/widget.h new file mode 100644 index 00000000000..91209444bf7 --- /dev/null +++ b/gcc/text-art/widget.h @@ -0,0 +1,246 @@ +/* Hierarchical diagram elements. + Copyright (C) 2023 Free Software Foundation, Inc. + Contributed by David Malcolm <dmalcolm@redhat.com>. + +This file is part of GCC. + +GCC is free software; you can redistribute it and/or modify it +under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 3, or (at your option) +any later version. + +GCC is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GCC; see the file COPYING3. If not see +<http://www.gnu.org/licenses/>. */ + +#ifndef GCC_TEXT_ART_WIDGET_H +#define GCC_TEXT_ART_WIDGET_H + +#include <vector> +#include "text-art/canvas.h" +#include "text-art/table.h" + +namespace text_art { + +/* Abstract base class: something that knows how to size itself and + how to paint itself to a canvas, potentially with children, with + support for hierarchical sizing and positioning. + + Widgets have a two-phase sizing/positioning algorithm. + + Step 1: size requests: the root widget is asked for its size request i.e + how big it wants to be. This is handled by recursively asking child + widgets for their requested sizes. Each widget subclass can implement + their own logic for this in the "calc_req_size" vfunc, and the result + is cached in m_req_size. + + Step 2: rect allocation: the root widget is set a canvas::rect_t as + its "allocated" rectangle. Each widget subclass can then place its + children recursively using the "update_child_alloc_rects" vfunc. + For simplicity, all coordinates in the hierarchy are within the same + coordinate system (rather than attempting to store per-child offsets). + + Widget subclasses are responsible for managing their own children. */ + +/* Subclasses in this header, with indentation indicating inheritance. */ + +class widget; /* Abstract base class. */ + class wrapper_widget; /* Concrete subclass: a widget with a single child. */ + class container_widget; /* Abstract subclass: widgets with an arbitrary + number of children. */ + class vbox_widget; /* Concrete widget subclass: lay out children + vertically. */ + class leaf_widget; /* Abstract subclass: a widget with no children. */ + class text_widget; /* Concrete subclass: a text string. */ + class canvas_widget; /* Concrete subclass: a pre-rendered canvas. */ + +class widget +{ + public: + /* This can be very useful for debugging when implementing new + widget subclasses. */ + static const bool DEBUG_GEOMETRY = false; + + virtual ~widget () {} + + canvas to_canvas (const style_manager &style_mgr); + + canvas::size_t get_req_size () + { + m_req_size = calc_req_size(); + if (DEBUG_GEOMETRY) + fprintf (stderr, "calc_req_size (%s) -> (w:%i, h:%i)\n", + get_desc (), + m_req_size.w, m_req_size.h); + return m_req_size; + } + + void set_alloc_rect (const canvas::rect_t &rect) + { + if (DEBUG_GEOMETRY) + fprintf (stderr, "set_alloc_rect (%s): ((x:%i, y:%i), (w:%i, h:%i))\n", + get_desc (), + rect.m_top_left.x, rect.m_top_left.y, + rect.m_size.w, rect.m_size.h); + m_alloc_rect = rect; + update_child_alloc_rects (); + } + + virtual const char *get_desc () const = 0; + virtual canvas::size_t calc_req_size () = 0; + virtual void update_child_alloc_rects () = 0; + virtual void paint_to_canvas (canvas &canvas) = 0; + + /* Access to the cached size request of this widget. */ + const canvas::size_t get_req_size () const { return m_req_size; } + int get_req_w () const { return m_req_size.w; } + int get_req_h () const { return m_req_size.h; } + + /* Access to the allocated canvas coordinates of this widget. */ + const canvas::rect_t &get_alloc_rect () const { return m_alloc_rect; } + int get_alloc_w () const { return m_alloc_rect.get_width (); } + int get_alloc_h () const { return m_alloc_rect.get_height (); } + int get_min_x () const { return m_alloc_rect.get_min_x (); } + int get_max_x () const { return m_alloc_rect.get_max_x (); } + int get_next_x () const { return m_alloc_rect.get_next_x (); } + int get_min_y () const { return m_alloc_rect.get_min_y (); } + int get_max_y () const { return m_alloc_rect.get_max_y (); } + int get_next_y () const { return m_alloc_rect.get_max_y (); } + canvas::range_t get_x_range () const { return m_alloc_rect.get_x_range (); } + canvas::range_t get_y_range () const { return m_alloc_rect.get_y_range (); } + const canvas::coord_t &get_top_left () const + { + return m_alloc_rect.m_top_left; + } + + protected: + widget () + : m_req_size (0, 0), + m_alloc_rect (canvas::coord_t (0, 0), + canvas::size_t (0, 0)) + {} + +private: + /* How much size this widget requested. */ + canvas::size_t m_req_size; + /* Where (and how big) this widget was allocated. */ + canvas::rect_t m_alloc_rect; +}; + +/* Concrete subclass for a widget with a single child. */ + +class wrapper_widget : public widget +{ + public: + wrapper_widget (std::unique_ptr<widget> child) + : m_child (std::move (child)) + {} + + const char *get_desc () const override + { + return "wrapper_widget"; + } + canvas::size_t calc_req_size () override + { + return m_child->get_req_size (); + } + void update_child_alloc_rects () + { + m_child->set_alloc_rect (get_alloc_rect ()); + } + void paint_to_canvas (canvas &canvas) override + { + m_child->paint_to_canvas (canvas); + } + private: + std::unique_ptr<widget> m_child; +}; + +/* Abstract subclass for widgets with an arbitrary number of children. */ + +class container_widget : public widget +{ + public: + void add_child (std::unique_ptr<widget> child) + { + m_children.push_back (std::move (child)); + } + + void paint_to_canvas (canvas &canvas) final override + { + for (auto &child : m_children) + child->paint_to_canvas (canvas); + } + + protected: + std::vector<std::unique_ptr<widget>> m_children; +}; + +/* Concrete widget subclass: lay out children vertically. */ + +class vbox_widget : public container_widget +{ + public: + const char *get_desc () const override; + canvas::size_t calc_req_size () override; + void update_child_alloc_rects () final override; +}; + +/* Abstract subclass for widgets with no children. */ + +class leaf_widget : public widget +{ + public: + void update_child_alloc_rects () final override + { + /* no-op. */ + } + + protected: + leaf_widget () : widget () {} +}; + +/* Concrete widget subclass for a text string. */ + +class text_widget : public leaf_widget +{ + public: + text_widget (styled_string str) + : leaf_widget (), m_str (std::move (str)) + { + } + + const char *get_desc () const override; + canvas::size_t calc_req_size () final override; + void paint_to_canvas (canvas &canvas) final override; + +private: + styled_string m_str; +}; + +/* Concrete widget subclass for a pre-rendered canvas. */ + +class canvas_widget : public leaf_widget +{ + public: + canvas_widget (canvas &&c) + : leaf_widget (), m_canvas (std::move (c)) + { + } + + const char *get_desc () const override; + canvas::size_t calc_req_size () final override; + void paint_to_canvas (canvas &canvas) final override; + +private: + canvas m_canvas; +}; + +} // namespace text_art + +#endif /* GCC_TEXT_ART_WIDGET_H */ diff --git a/libcpp/charset.cc b/libcpp/charset.cc index d7f323b2cd5..a0bd2ede11c 100644 --- a/libcpp/charset.cc +++ b/libcpp/charset.cc @@ -3147,34 +3147,26 @@ cpp_display_column_to_byte_column (const char *data, int data_length, return dw.bytes_processed () + MAX (0, display_col - avail_display); } -/* Our own version of wcwidth(). We don't use the actual wcwidth() in glibc, - because that will inspect the user's locale, and in particular in an ASCII - locale, it will not return anything useful for extended characters. But GCC - in other respects (see e.g. _cpp_default_encoding()) behaves as if - everything is UTF-8. We also make some tweaks that are useful for the way - GCC needs to use this data, e.g. tabs and other control characters should be - treated as having width 1. The lookup tables are generated from - contrib/unicode/gen_wcwidth.py and were made by simply calling glibc - wcwidth() on all codepoints, then applying the small tweaks. These tables - are not highly optimized, but for the present purpose of outputting - diagnostics, they are sufficient. */ - -#include "generated_cpp_wcwidth.h" -int cpp_wcwidth (cppchar_t c) +template <typename PropertyType> +PropertyType +get_cppchar_property (cppchar_t c, + const cppchar_t *range_ends, + const PropertyType *range_values, + size_t num_ranges, + PropertyType default_value) { - if (__builtin_expect (c <= wcwidth_range_ends[0], true)) - return wcwidth_widths[0]; + if (__builtin_expect (c <= range_ends[0], true)) + return range_values[0]; /* Binary search the tables. */ int begin = 1; - static const int end - = sizeof wcwidth_range_ends / sizeof (*wcwidth_range_ends); + static const int end = num_ranges; int len = end - begin; do { int half = len/2; int middle = begin + half; - if (c > wcwidth_range_ends[middle]) + if (c > range_ends[middle]) { begin = middle + 1; len -= half + 1; @@ -3184,6 +3176,61 @@ int cpp_wcwidth (cppchar_t c) } while (len); if (__builtin_expect (begin != end, true)) - return wcwidth_widths[begin]; - return 1; + return range_values[begin]; + + return default_value; +} + +/* Our own version of wcwidth(). We don't use the actual wcwidth() in glibc, + because that will inspect the user's locale, and in particular in an ASCII + locale, it will not return anything useful for extended characters. But GCC + in other respects (see e.g. _cpp_default_encoding()) behaves as if + everything is UTF-8. We also make some tweaks that are useful for the way + GCC needs to use this data, e.g. tabs and other control characters should be + treated as having width 1. The lookup tables are generated from + contrib/unicode/gen_wcwidth.py and were made by simply calling glibc + wcwidth() on all codepoints, then applying the small tweaks. These tables + are not highly optimized, but for the present purpose of outputting + diagnostics, they are sufficient. */ + +#include "generated_cpp_wcwidth.h" + +int +cpp_wcwidth (cppchar_t c) +{ + const size_t num_ranges + = sizeof wcwidth_range_ends / sizeof (*wcwidth_range_ends); + return get_cppchar_property<unsigned char > (c, + &wcwidth_range_ends[0], + &wcwidth_widths[0], + num_ranges, + 1); +} + +#include "combining-chars.inc" + +bool +cpp_is_combining_char (cppchar_t c) +{ + const size_t num_ranges + = sizeof combining_range_ends / sizeof (*combining_range_ends); + return get_cppchar_property<bool> (c, + &combining_range_ends[0], + &is_combining[0], + num_ranges, + false); +} + +#include "printable-chars.inc" + +bool +cpp_is_printable_char (cppchar_t c) +{ + const size_t num_ranges + = sizeof printable_range_ends / sizeof (*printable_range_ends); + return get_cppchar_property<bool> (c, + &printable_range_ends[0], + &is_printable[0], + num_ranges, + false); } diff --git a/libcpp/combining-chars.inc b/libcpp/combining-chars.inc new file mode 100644 index 00000000000..dfec966970a --- /dev/null +++ b/libcpp/combining-chars.inc @@ -0,0 +1,68 @@ +/* Generated by contrib/unicode/gen-combining-chars.py + using version 12.1.0 of the Unicode standard. */ + +static const cppchar_t combining_range_ends[] = { + 0x2ff, 0x34e, 0x34f, 0x36f, 0x482, 0x487, 0x590, 0x5bd, + 0x5be, 0x5bf, 0x5c0, 0x5c2, 0x5c3, 0x5c5, 0x5c6, 0x5c7, + 0x60f, 0x61a, 0x64a, 0x65f, 0x66f, 0x670, 0x6d5, 0x6dc, + 0x6de, 0x6e4, 0x6e6, 0x6e8, 0x6e9, 0x6ed, 0x710, 0x711, + 0x72f, 0x74a, 0x7ea, 0x7f3, 0x7fc, 0x7fd, 0x815, 0x819, + 0x81a, 0x823, 0x824, 0x827, 0x828, 0x82d, 0x858, 0x85b, + 0x8d2, 0x8e1, 0x8e2, 0x8ff, 0x93b, 0x93c, 0x94c, 0x94d, + 0x950, 0x954, 0x9bb, 0x9bc, 0x9cc, 0x9cd, 0x9fd, 0x9fe, + 0xa3b, 0xa3c, 0xa4c, 0xa4d, 0xabb, 0xabc, 0xacc, 0xacd, + 0xb3b, 0xb3c, 0xb4c, 0xb4d, 0xbcc, 0xbcd, 0xc4c, 0xc4d, + 0xc54, 0xc56, 0xcbb, 0xcbc, 0xccc, 0xccd, 0xd3a, 0xd3c, + 0xd4c, 0xd4d, 0xdc9, 0xdca, 0xe37, 0xe3a, 0xe47, 0xe4b, + 0xeb7, 0xeba, 0xec7, 0xecb, 0xf17, 0xf19, 0xf34, 0xf35, + 0xf36, 0xf37, 0xf38, 0xf39, 0xf70, 0xf72, 0xf73, 0xf74, + 0xf79, 0xf7d, 0xf7f, 0xf80, 0xf81, 0xf84, 0xf85, 0xf87, + 0xfc5, 0xfc6, 0x1036, 0x1037, 0x1038, 0x103a, 0x108c, 0x108d, + 0x135c, 0x135f, 0x1713, 0x1714, 0x1733, 0x1734, 0x17d1, 0x17d2, + 0x17dc, 0x17dd, 0x18a8, 0x18a9, 0x1938, 0x193b, 0x1a16, 0x1a18, + 0x1a5f, 0x1a60, 0x1a74, 0x1a7c, 0x1a7e, 0x1a7f, 0x1aaf, 0x1abd, + 0x1b33, 0x1b34, 0x1b43, 0x1b44, 0x1b6a, 0x1b73, 0x1ba9, 0x1bab, + 0x1be5, 0x1be6, 0x1bf1, 0x1bf3, 0x1c36, 0x1c37, 0x1ccf, 0x1cd2, + 0x1cd3, 0x1ce0, 0x1ce1, 0x1ce8, 0x1cec, 0x1ced, 0x1cf3, 0x1cf4, + 0x1cf7, 0x1cf9, 0x1dbf, 0x1df9, 0x1dfa, 0x1dff, 0x20cf, 0x20dc, + 0x20e0, 0x20e1, 0x20e4, 0x20f0, 0x2cee, 0x2cf1, 0x2d7e, 0x2d7f, + 0x2ddf, 0x2dff, 0x3029, 0x302f, 0x3098, 0x309a, 0xa66e, 0xa66f, + 0xa673, 0xa67d, 0xa69d, 0xa69f, 0xa6ef, 0xa6f1, 0xa805, 0xa806, + 0xa8c3, 0xa8c4, 0xa8df, 0xa8f1, 0xa92a, 0xa92d, 0xa952, 0xa953, + 0xa9b2, 0xa9b3, 0xa9bf, 0xa9c0, 0xaaaf, 0xaab0, 0xaab1, 0xaab4, + 0xaab6, 0xaab8, 0xaabd, 0xaabf, 0xaac0, 0xaac1, 0xaaf5, 0xaaf6, + 0xabec, 0xabed, 0xfb1d, 0xfb1e, 0xfe1f, 0xfe2f, 0x101fc, 0x101fd, + 0x102df, 0x102e0, 0x10375, 0x1037a, 0x10a0c, 0x10a0d, 0x10a0e, 0x10a0f, + 0x10a37, 0x10a3a, 0x10a3e, 0x10a3f, 0x10ae4, 0x10ae6, 0x10d23, 0x10d27, + 0x10f45, 0x10f50, 0x11045, 0x11046, 0x1107e, 0x1107f, 0x110b8, 0x110ba, + 0x110ff, 0x11102, 0x11132, 0x11134, 0x11172, 0x11173, 0x111bf, 0x111c0, + 0x111c9, 0x111ca, 0x11234, 0x11236, 0x112e8, 0x112ea, 0x1133a, 0x1133c, + 0x1134c, 0x1134d, 0x11365, 0x1136c, 0x1136f, 0x11374, 0x11441, 0x11442, + 0x11445, 0x11446, 0x1145d, 0x1145e, 0x114c1, 0x114c3, 0x115be, 0x115c0, + 0x1163e, 0x1163f, 0x116b5, 0x116b7, 0x1172a, 0x1172b, 0x11838, 0x1183a, + 0x119df, 0x119e0, 0x11a33, 0x11a34, 0x11a46, 0x11a47, 0x11a98, 0x11a99, + 0x11c3e, 0x11c3f, 0x11d41, 0x11d42, 0x11d43, 0x11d45, 0x11d96, 0x11d97, + 0x16aef, 0x16af4, 0x16b2f, 0x16b36, 0x1bc9d, 0x1bc9e, 0x1d164, 0x1d169, + 0x1d16c, 0x1d172, 0x1d17a, 0x1d182, 0x1d184, 0x1d18b, 0x1d1a9, 0x1d1ad, + 0x1d241, 0x1d244, 0x1dfff, 0x1e006, 0x1e007, 0x1e018, 0x1e01a, 0x1e021, + 0x1e022, 0x1e024, 0x1e025, 0x1e02a, 0x1e12f, 0x1e136, 0x1e2eb, 0x1e2ef, + 0x1e8cf, 0x1e8d6, 0x1e943, 0x1e94a, 0x10fffe, +}; + +static const bool is_combining[] = { + 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, + 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, + 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, + 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, + 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, + 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, + 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, + 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, + 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, + 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, + 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, + 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, + 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, + 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, + 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, +}; diff --git a/libcpp/include/cpplib.h b/libcpp/include/cpplib.h index a6f0abd894c..d326f5aa316 100644 --- a/libcpp/include/cpplib.h +++ b/libcpp/include/cpplib.h @@ -1602,4 +1602,7 @@ bool cpp_input_conversion_is_trivial (const char *input_charset); int cpp_check_utf8_bom (const char *data, size_t data_length); bool cpp_valid_utf8_p (const char *data, size_t num_bytes); +bool cpp_is_combining_char (cppchar_t c); +bool cpp_is_printable_char (cppchar_t c); + #endif /* ! LIBCPP_CPPLIB_H */ diff --git a/libcpp/printable-chars.inc b/libcpp/printable-chars.inc new file mode 100644 index 00000000000..470b1eef331 --- /dev/null +++ b/libcpp/printable-chars.inc @@ -0,0 +1,231 @@ +/* Generated by contrib/unicode/gen-printable-chars.py + using version 12.1.0 of the Unicode standard. */ + +static const cppchar_t printable_range_ends[] = { + 0x1f, 0x7e, 0x9f, 0xac, 0xad, 0x377, 0x379, 0x37f, + 0x383, 0x38a, 0x38b, 0x38c, 0x38d, 0x3a1, 0x3a2, 0x52f, + 0x530, 0x556, 0x558, 0x58a, 0x58c, 0x58f, 0x590, 0x5c7, + 0x5cf, 0x5ea, 0x5ee, 0x5f4, 0x605, 0x61b, 0x61d, 0x6dc, + 0x6dd, 0x70d, 0x70f, 0x74a, 0x74c, 0x7b1, 0x7bf, 0x7fa, + 0x7fc, 0x82d, 0x82f, 0x83e, 0x83f, 0x85b, 0x85d, 0x85e, + 0x85f, 0x86a, 0x89f, 0x8b4, 0x8b5, 0x8bd, 0x8d2, 0x8e1, + 0x8e2, 0x983, 0x984, 0x98c, 0x98e, 0x990, 0x992, 0x9a8, + 0x9a9, 0x9b0, 0x9b1, 0x9b2, 0x9b5, 0x9b9, 0x9bb, 0x9c4, + 0x9c6, 0x9c8, 0x9ca, 0x9ce, 0x9d6, 0x9d7, 0x9db, 0x9dd, + 0x9de, 0x9e3, 0x9e5, 0x9fe, 0xa00, 0xa03, 0xa04, 0xa0a, + 0xa0e, 0xa10, 0xa12, 0xa28, 0xa29, 0xa30, 0xa31, 0xa33, + 0xa34, 0xa36, 0xa37, 0xa39, 0xa3b, 0xa3c, 0xa3d, 0xa42, + 0xa46, 0xa48, 0xa4a, 0xa4d, 0xa50, 0xa51, 0xa58, 0xa5c, + 0xa5d, 0xa5e, 0xa65, 0xa76, 0xa80, 0xa83, 0xa84, 0xa8d, + 0xa8e, 0xa91, 0xa92, 0xaa8, 0xaa9, 0xab0, 0xab1, 0xab3, + 0xab4, 0xab9, 0xabb, 0xac5, 0xac6, 0xac9, 0xaca, 0xacd, + 0xacf, 0xad0, 0xadf, 0xae3, 0xae5, 0xaf1, 0xaf8, 0xaff, + 0xb00, 0xb03, 0xb04, 0xb0c, 0xb0e, 0xb10, 0xb12, 0xb28, + 0xb29, 0xb30, 0xb31, 0xb33, 0xb34, 0xb39, 0xb3b, 0xb44, + 0xb46, 0xb48, 0xb4a, 0xb4d, 0xb55, 0xb57, 0xb5b, 0xb5d, + 0xb5e, 0xb63, 0xb65, 0xb77, 0xb81, 0xb83, 0xb84, 0xb8a, + 0xb8d, 0xb90, 0xb91, 0xb95, 0xb98, 0xb9a, 0xb9b, 0xb9c, + 0xb9d, 0xb9f, 0xba2, 0xba4, 0xba7, 0xbaa, 0xbad, 0xbb9, + 0xbbd, 0xbc2, 0xbc5, 0xbc8, 0xbc9, 0xbcd, 0xbcf, 0xbd0, + 0xbd6, 0xbd7, 0xbe5, 0xbfa, 0xbff, 0xc0c, 0xc0d, 0xc10, + 0xc11, 0xc28, 0xc29, 0xc39, 0xc3c, 0xc44, 0xc45, 0xc48, + 0xc49, 0xc4d, 0xc54, 0xc56, 0xc57, 0xc5a, 0xc5f, 0xc63, + 0xc65, 0xc6f, 0xc76, 0xc8c, 0xc8d, 0xc90, 0xc91, 0xca8, + 0xca9, 0xcb3, 0xcb4, 0xcb9, 0xcbb, 0xcc4, 0xcc5, 0xcc8, + 0xcc9, 0xccd, 0xcd4, 0xcd6, 0xcdd, 0xcde, 0xcdf, 0xce3, + 0xce5, 0xcef, 0xcf0, 0xcf2, 0xcff, 0xd03, 0xd04, 0xd0c, + 0xd0d, 0xd10, 0xd11, 0xd44, 0xd45, 0xd48, 0xd49, 0xd4f, + 0xd53, 0xd63, 0xd65, 0xd7f, 0xd81, 0xd83, 0xd84, 0xd96, + 0xd99, 0xdb1, 0xdb2, 0xdbb, 0xdbc, 0xdbd, 0xdbf, 0xdc6, + 0xdc9, 0xdca, 0xdce, 0xdd4, 0xdd5, 0xdd6, 0xdd7, 0xddf, + 0xde5, 0xdef, 0xdf1, 0xdf4, 0xe00, 0xe3a, 0xe3e, 0xe5b, + 0xe80, 0xe82, 0xe83, 0xe84, 0xe85, 0xe8a, 0xe8b, 0xea3, + 0xea4, 0xea5, 0xea6, 0xebd, 0xebf, 0xec4, 0xec5, 0xec6, + 0xec7, 0xecd, 0xecf, 0xed9, 0xedb, 0xedf, 0xeff, 0xf47, + 0xf48, 0xf6c, 0xf70, 0xf97, 0xf98, 0xfbc, 0xfbd, 0xfcc, + 0xfcd, 0xfda, 0xfff, 0x10c5, 0x10c6, 0x10c7, 0x10cc, 0x10cd, + 0x10cf, 0x1248, 0x1249, 0x124d, 0x124f, 0x1256, 0x1257, 0x1258, + 0x1259, 0x125d, 0x125f, 0x1288, 0x1289, 0x128d, 0x128f, 0x12b0, + 0x12b1, 0x12b5, 0x12b7, 0x12be, 0x12bf, 0x12c0, 0x12c1, 0x12c5, + 0x12c7, 0x12d6, 0x12d7, 0x1310, 0x1311, 0x1315, 0x1317, 0x135a, + 0x135c, 0x137c, 0x137f, 0x1399, 0x139f, 0x13f5, 0x13f7, 0x13fd, + 0x13ff, 0x169c, 0x169f, 0x16f8, 0x16ff, 0x170c, 0x170d, 0x1714, + 0x171f, 0x1736, 0x173f, 0x1753, 0x175f, 0x176c, 0x176d, 0x1770, + 0x1771, 0x1773, 0x177f, 0x17dd, 0x17df, 0x17e9, 0x17ef, 0x17f9, + 0x17ff, 0x180d, 0x180f, 0x1819, 0x181f, 0x1878, 0x187f, 0x18aa, + 0x18af, 0x18f5, 0x18ff, 0x191e, 0x191f, 0x192b, 0x192f, 0x193b, + 0x193f, 0x1940, 0x1943, 0x196d, 0x196f, 0x1974, 0x197f, 0x19ab, + 0x19af, 0x19c9, 0x19cf, 0x19da, 0x19dd, 0x1a1b, 0x1a1d, 0x1a5e, + 0x1a5f, 0x1a7c, 0x1a7e, 0x1a89, 0x1a8f, 0x1a99, 0x1a9f, 0x1aad, + 0x1aaf, 0x1abe, 0x1aff, 0x1b4b, 0x1b4f, 0x1b7c, 0x1b7f, 0x1bf3, + 0x1bfb, 0x1c37, 0x1c3a, 0x1c49, 0x1c4c, 0x1c88, 0x1c8f, 0x1cba, + 0x1cbc, 0x1cc7, 0x1ccf, 0x1cfa, 0x1cff, 0x1df9, 0x1dfa, 0x1f15, + 0x1f17, 0x1f1d, 0x1f1f, 0x1f45, 0x1f47, 0x1f4d, 0x1f4f, 0x1f57, + 0x1f58, 0x1f59, 0x1f5a, 0x1f5b, 0x1f5c, 0x1f5d, 0x1f5e, 0x1f7d, + 0x1f7f, 0x1fb4, 0x1fb5, 0x1fc4, 0x1fc5, 0x1fd3, 0x1fd5, 0x1fdb, + 0x1fdc, 0x1fef, 0x1ff1, 0x1ff4, 0x1ff5, 0x1ffe, 0x1fff, 0x200a, + 0x200f, 0x2029, 0x202e, 0x205f, 0x206f, 0x2071, 0x2073, 0x208e, + 0x208f, 0x209c, 0x209f, 0x20bf, 0x20cf, 0x20f0, 0x20ff, 0x218b, + 0x218f, 0x2426, 0x243f, 0x244a, 0x245f, 0x2b73, 0x2b75, 0x2b95, + 0x2b97, 0x2c2e, 0x2c2f, 0x2c5e, 0x2c5f, 0x2cf3, 0x2cf8, 0x2d25, + 0x2d26, 0x2d27, 0x2d2c, 0x2d2d, 0x2d2f, 0x2d67, 0x2d6e, 0x2d70, + 0x2d7e, 0x2d96, 0x2d9f, 0x2da6, 0x2da7, 0x2dae, 0x2daf, 0x2db6, + 0x2db7, 0x2dbe, 0x2dbf, 0x2dc6, 0x2dc7, 0x2dce, 0x2dcf, 0x2dd6, + 0x2dd7, 0x2dde, 0x2ddf, 0x2e4f, 0x2e7f, 0x2e99, 0x2e9a, 0x2ef3, + 0x2eff, 0x2fd5, 0x2fef, 0x2ffb, 0x2fff, 0x303f, 0x3040, 0x3096, + 0x3098, 0x30ff, 0x3104, 0x312f, 0x3130, 0x318e, 0x318f, 0x31ba, + 0x31bf, 0x31e3, 0x31ef, 0x321e, 0x321f, 0x4db5, 0x4dbf, 0x9fef, + 0x9fff, 0xa48c, 0xa48f, 0xa4c6, 0xa4cf, 0xa62b, 0xa63f, 0xa6f7, + 0xa6ff, 0xa7bf, 0xa7c1, 0xa7c6, 0xa7f6, 0xa82b, 0xa82f, 0xa839, + 0xa83f, 0xa877, 0xa87f, 0xa8c5, 0xa8cd, 0xa8d9, 0xa8df, 0xa953, + 0xa95e, 0xa97c, 0xa97f, 0xa9cd, 0xa9ce, 0xa9d9, 0xa9dd, 0xa9fe, + 0xa9ff, 0xaa36, 0xaa3f, 0xaa4d, 0xaa4f, 0xaa59, 0xaa5b, 0xaac2, + 0xaada, 0xaaf6, 0xab00, 0xab06, 0xab08, 0xab0e, 0xab10, 0xab16, + 0xab1f, 0xab26, 0xab27, 0xab2e, 0xab2f, 0xab67, 0xab6f, 0xabed, + 0xabef, 0xabf9, 0xabff, 0xd7a3, 0xd7af, 0xd7c6, 0xd7ca, 0xd7fb, + 0xf8ff, 0xfa6d, 0xfa6f, 0xfad9, 0xfaff, 0xfb06, 0xfb12, 0xfb17, + 0xfb1c, 0xfb36, 0xfb37, 0xfb3c, 0xfb3d, 0xfb3e, 0xfb3f, 0xfb41, + 0xfb42, 0xfb44, 0xfb45, 0xfbc1, 0xfbd2, 0xfd3f, 0xfd4f, 0xfd8f, + 0xfd91, 0xfdc7, 0xfdef, 0xfdfd, 0xfdff, 0xfe19, 0xfe1f, 0xfe52, + 0xfe53, 0xfe66, 0xfe67, 0xfe6b, 0xfe6f, 0xfe74, 0xfe75, 0xfefc, + 0xff00, 0xffbe, 0xffc1, 0xffc7, 0xffc9, 0xffcf, 0xffd1, 0xffd7, + 0xffd9, 0xffdc, 0xffdf, 0xffe6, 0xffe7, 0xffee, 0xfffb, 0xfffd, + 0xffff, 0x1000b, 0x1000c, 0x10026, 0x10027, 0x1003a, 0x1003b, 0x1003d, + 0x1003e, 0x1004d, 0x1004f, 0x1005d, 0x1007f, 0x100fa, 0x100ff, 0x10102, + 0x10106, 0x10133, 0x10136, 0x1018e, 0x1018f, 0x1019b, 0x1019f, 0x101a0, + 0x101cf, 0x101fd, 0x1027f, 0x1029c, 0x1029f, 0x102d0, 0x102df, 0x102fb, + 0x102ff, 0x10323, 0x1032c, 0x1034a, 0x1034f, 0x1037a, 0x1037f, 0x1039d, + 0x1039e, 0x103c3, 0x103c7, 0x103d5, 0x103ff, 0x1049d, 0x1049f, 0x104a9, + 0x104af, 0x104d3, 0x104d7, 0x104fb, 0x104ff, 0x10527, 0x1052f, 0x10563, + 0x1056e, 0x1056f, 0x105ff, 0x10736, 0x1073f, 0x10755, 0x1075f, 0x10767, + 0x107ff, 0x10805, 0x10807, 0x10808, 0x10809, 0x10835, 0x10836, 0x10838, + 0x1083b, 0x1083c, 0x1083e, 0x10855, 0x10856, 0x1089e, 0x108a6, 0x108af, + 0x108df, 0x108f2, 0x108f3, 0x108f5, 0x108fa, 0x1091b, 0x1091e, 0x10939, + 0x1093e, 0x1093f, 0x1097f, 0x109b7, 0x109bb, 0x109cf, 0x109d1, 0x10a03, + 0x10a04, 0x10a06, 0x10a0b, 0x10a13, 0x10a14, 0x10a17, 0x10a18, 0x10a35, + 0x10a37, 0x10a3a, 0x10a3e, 0x10a48, 0x10a4f, 0x10a58, 0x10a5f, 0x10a9f, + 0x10abf, 0x10ae6, 0x10aea, 0x10af6, 0x10aff, 0x10b35, 0x10b38, 0x10b55, + 0x10b57, 0x10b72, 0x10b77, 0x10b91, 0x10b98, 0x10b9c, 0x10ba8, 0x10baf, + 0x10bff, 0x10c48, 0x10c7f, 0x10cb2, 0x10cbf, 0x10cf2, 0x10cf9, 0x10d27, + 0x10d2f, 0x10d39, 0x10e5f, 0x10e7e, 0x10eff, 0x10f27, 0x10f2f, 0x10f59, + 0x10fdf, 0x10ff6, 0x10fff, 0x1104d, 0x11051, 0x1106f, 0x1107e, 0x110bc, + 0x110bd, 0x110c1, 0x110cf, 0x110e8, 0x110ef, 0x110f9, 0x110ff, 0x11134, + 0x11135, 0x11146, 0x1114f, 0x11176, 0x1117f, 0x111cd, 0x111cf, 0x111df, + 0x111e0, 0x111f4, 0x111ff, 0x11211, 0x11212, 0x1123e, 0x1127f, 0x11286, + 0x11287, 0x11288, 0x11289, 0x1128d, 0x1128e, 0x1129d, 0x1129e, 0x112a9, + 0x112af, 0x112ea, 0x112ef, 0x112f9, 0x112ff, 0x11303, 0x11304, 0x1130c, + 0x1130e, 0x11310, 0x11312, 0x11328, 0x11329, 0x11330, 0x11331, 0x11333, + 0x11334, 0x11339, 0x1133a, 0x11344, 0x11346, 0x11348, 0x1134a, 0x1134d, + 0x1134f, 0x11350, 0x11356, 0x11357, 0x1135c, 0x11363, 0x11365, 0x1136c, + 0x1136f, 0x11374, 0x113ff, 0x11459, 0x1145a, 0x1145b, 0x1145c, 0x1145f, + 0x1147f, 0x114c7, 0x114cf, 0x114d9, 0x1157f, 0x115b5, 0x115b7, 0x115dd, + 0x115ff, 0x11644, 0x1164f, 0x11659, 0x1165f, 0x1166c, 0x1167f, 0x116b8, + 0x116bf, 0x116c9, 0x116ff, 0x1171a, 0x1171c, 0x1172b, 0x1172f, 0x1173f, + 0x117ff, 0x1183b, 0x1189f, 0x118f2, 0x118fe, 0x118ff, 0x1199f, 0x119a7, + 0x119a9, 0x119d7, 0x119d9, 0x119e4, 0x119ff, 0x11a47, 0x11a4f, 0x11aa2, + 0x11abf, 0x11af8, 0x11bff, 0x11c08, 0x11c09, 0x11c36, 0x11c37, 0x11c45, + 0x11c4f, 0x11c6c, 0x11c6f, 0x11c8f, 0x11c91, 0x11ca7, 0x11ca8, 0x11cb6, + 0x11cff, 0x11d06, 0x11d07, 0x11d09, 0x11d0a, 0x11d36, 0x11d39, 0x11d3a, + 0x11d3b, 0x11d3d, 0x11d3e, 0x11d47, 0x11d4f, 0x11d59, 0x11d5f, 0x11d65, + 0x11d66, 0x11d68, 0x11d69, 0x11d8e, 0x11d8f, 0x11d91, 0x11d92, 0x11d98, + 0x11d9f, 0x11da9, 0x11edf, 0x11ef8, 0x11fbf, 0x11ff1, 0x11ffe, 0x12399, + 0x123ff, 0x1246e, 0x1246f, 0x12474, 0x1247f, 0x12543, 0x12fff, 0x1342e, + 0x143ff, 0x14646, 0x167ff, 0x16a38, 0x16a3f, 0x16a5e, 0x16a5f, 0x16a69, + 0x16a6d, 0x16a6f, 0x16acf, 0x16aed, 0x16aef, 0x16af5, 0x16aff, 0x16b45, + 0x16b4f, 0x16b59, 0x16b5a, 0x16b61, 0x16b62, 0x16b77, 0x16b7c, 0x16b8f, + 0x16e3f, 0x16e9a, 0x16eff, 0x16f4a, 0x16f4e, 0x16f87, 0x16f8e, 0x16f9f, + 0x16fdf, 0x16fe3, 0x16fff, 0x187f7, 0x187ff, 0x18af2, 0x1afff, 0x1b11e, + 0x1b14f, 0x1b152, 0x1b163, 0x1b167, 0x1b16f, 0x1b2fb, 0x1bbff, 0x1bc6a, + 0x1bc6f, 0x1bc7c, 0x1bc7f, 0x1bc88, 0x1bc8f, 0x1bc99, 0x1bc9b, 0x1bc9f, + 0x1cfff, 0x1d0f5, 0x1d0ff, 0x1d126, 0x1d128, 0x1d172, 0x1d17a, 0x1d1e8, + 0x1d1ff, 0x1d245, 0x1d2df, 0x1d2f3, 0x1d2ff, 0x1d356, 0x1d35f, 0x1d378, + 0x1d3ff, 0x1d454, 0x1d455, 0x1d49c, 0x1d49d, 0x1d49f, 0x1d4a1, 0x1d4a2, + 0x1d4a4, 0x1d4a6, 0x1d4a8, 0x1d4ac, 0x1d4ad, 0x1d4b9, 0x1d4ba, 0x1d4bb, + 0x1d4bc, 0x1d4c3, 0x1d4c4, 0x1d505, 0x1d506, 0x1d50a, 0x1d50c, 0x1d514, + 0x1d515, 0x1d51c, 0x1d51d, 0x1d539, 0x1d53a, 0x1d53e, 0x1d53f, 0x1d544, + 0x1d545, 0x1d546, 0x1d549, 0x1d550, 0x1d551, 0x1d6a5, 0x1d6a7, 0x1d7cb, + 0x1d7cd, 0x1da8b, 0x1da9a, 0x1da9f, 0x1daa0, 0x1daaf, 0x1dfff, 0x1e006, + 0x1e007, 0x1e018, 0x1e01a, 0x1e021, 0x1e022, 0x1e024, 0x1e025, 0x1e02a, + 0x1e0ff, 0x1e12c, 0x1e12f, 0x1e13d, 0x1e13f, 0x1e149, 0x1e14d, 0x1e14f, + 0x1e2bf, 0x1e2f9, 0x1e2fe, 0x1e2ff, 0x1e7ff, 0x1e8c4, 0x1e8c6, 0x1e8d6, + 0x1e8ff, 0x1e94b, 0x1e94f, 0x1e959, 0x1e95d, 0x1e95f, 0x1ec70, 0x1ecb4, + 0x1ed00, 0x1ed3d, 0x1edff, 0x1ee03, 0x1ee04, 0x1ee1f, 0x1ee20, 0x1ee22, + 0x1ee23, 0x1ee24, 0x1ee26, 0x1ee27, 0x1ee28, 0x1ee32, 0x1ee33, 0x1ee37, + 0x1ee38, 0x1ee39, 0x1ee3a, 0x1ee3b, 0x1ee41, 0x1ee42, 0x1ee46, 0x1ee47, + 0x1ee48, 0x1ee49, 0x1ee4a, 0x1ee4b, 0x1ee4c, 0x1ee4f, 0x1ee50, 0x1ee52, + 0x1ee53, 0x1ee54, 0x1ee56, 0x1ee57, 0x1ee58, 0x1ee59, 0x1ee5a, 0x1ee5b, + 0x1ee5c, 0x1ee5d, 0x1ee5e, 0x1ee5f, 0x1ee60, 0x1ee62, 0x1ee63, 0x1ee64, + 0x1ee66, 0x1ee6a, 0x1ee6b, 0x1ee72, 0x1ee73, 0x1ee77, 0x1ee78, 0x1ee7c, + 0x1ee7d, 0x1ee7e, 0x1ee7f, 0x1ee89, 0x1ee8a, 0x1ee9b, 0x1eea0, 0x1eea3, + 0x1eea4, 0x1eea9, 0x1eeaa, 0x1eebb, 0x1eeef, 0x1eef1, 0x1efff, 0x1f02b, + 0x1f02f, 0x1f093, 0x1f09f, 0x1f0ae, 0x1f0b0, 0x1f0bf, 0x1f0c0, 0x1f0cf, + 0x1f0d0, 0x1f0f5, 0x1f0ff, 0x1f10c, 0x1f10f, 0x1f16c, 0x1f16f, 0x1f1ac, + 0x1f1e5, 0x1f202, 0x1f20f, 0x1f23b, 0x1f23f, 0x1f248, 0x1f24f, 0x1f251, + 0x1f25f, 0x1f265, 0x1f2ff, 0x1f6d5, 0x1f6df, 0x1f6ec, 0x1f6ef, 0x1f6fa, + 0x1f6ff, 0x1f773, 0x1f77f, 0x1f7d8, 0x1f7df, 0x1f7eb, 0x1f7ff, 0x1f80b, + 0x1f80f, 0x1f847, 0x1f84f, 0x1f859, 0x1f85f, 0x1f887, 0x1f88f, 0x1f8ad, + 0x1f8ff, 0x1f90b, 0x1f90c, 0x1f971, 0x1f972, 0x1f976, 0x1f979, 0x1f9a2, + 0x1f9a4, 0x1f9aa, 0x1f9ad, 0x1f9ca, 0x1f9cc, 0x1fa53, 0x1fa5f, 0x1fa6d, + 0x1fa6f, 0x1fa73, 0x1fa77, 0x1fa7a, 0x1fa7f, 0x1fa82, 0x1fa8f, 0x1fa95, + 0x1ffff, 0x2a6d6, 0x2a6ff, 0x2b734, 0x2b73f, 0x2b81d, 0x2b81f, 0x2cea1, + 0x2ceaf, 0x2ebe0, 0x2f7ff, 0x2fa1d, 0xe00ff, 0xe01ef, 0x10fffe, +}; + +static const bool is_printable[] = { + 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, + 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, + 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, + 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, + 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, + 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, + 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, + 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, + 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, + 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, + 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, + 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, + 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, + 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, + 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, + 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, + 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, + 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, + 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, + 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, + 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, + 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, + 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, + 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, + 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, + 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, + 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, + 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, + 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, + 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, + 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, + 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, + 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, + 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, + 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, + 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, + 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, + 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, + 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, + 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, + 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, + 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, + 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, + 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, + 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, + 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, + 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, + 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, + 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, + 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, + 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, + 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, + 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, + 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, + 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, + 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, +};