diagnostics: add support for "text art" diagrams

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.

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.

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.

Signed-off-by: David Malcolm <dmalcolm@redhat.com>
This commit is contained in:
David Malcolm 2023-06-21 21:49:00 -04:00
parent 985d6480fe
commit 4f01ae3761
50 changed files with 7764 additions and 37 deletions

View File

@ -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} */')

View File

@ -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};")

View File

@ -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};")

View File

@ -1788,7 +1788,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.

View File

@ -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

View File

@ -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.

2
gcc/configure vendored
View File

@ -34009,7 +34009,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 ;;

View File

@ -1382,7 +1382,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])

51
gcc/diagnostic-diagram.h Normal file
View File

@ -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 */

View File

@ -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;

View File

@ -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;

49
gcc/diagnostic-text-art.h Normal file
View File

@ -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 */

View File

@ -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. */

View File

@ -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 */

View File

@ -317,7 +317,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}.
@ -5078,7 +5079,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.
@ -5604,6 +5606,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.

View File

@ -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"
#ifndef MATH_LIBRARY
#define MATH_LIBRARY "m"
@ -4344,6 +4345,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;

View File

@ -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;

View File

@ -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

View File

@ -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

View File

@ -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);

View File

@ -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 ();

View File

@ -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 "" } */

View File

@ -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 "" }
        
        
        
        
        
        
        
        
{ 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 "" } */

View File

@ -0,0 +1,5 @@
/* { dg-additional-options "-fdiagnostics-text-art-charset=none" } */
int non_empty;
/* We expect no output. */

View File

@ -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 "" }
OffsetsOctet 0 1 2 3
Octet Bit 012345678910111213141516171819202122232425262728293031
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 "" } */

View File

@ -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 "" }
        
        
        
        
        
        
        
        
{ dg-end-multiline-output "" } */
/* { dg-begin-multiline-output "" }
🙂
{ dg-end-multiline-output "" } */
/* { dg-begin-multiline-output "" }
OffsetsOctet 0 1 2 3
Octet Bit 012345678910111213141516171819202122232425262728293031
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 "" } */

View File

@ -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;
}

View File

@ -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 \

View File

@ -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 */

View File

@ -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 */

View File

@ -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 */

437
gcc/text-art/canvas.cc Normal file
View File

@ -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"
" *****\n"
" *******\n"
" *********\n"
" *********\n"
" *********\n"
" *********\n"
" *********\n"
" *******\n"
" *****\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 `foo' 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 */

74
gcc/text-art/canvas.h Normal file
View File

@ -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 */

723
gcc/text-art/ruler.cc Normal file
View File

@ -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,
("|~+~| |~+~|\n"
" | |\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 */

125
gcc/text-art/ruler.h Normal file
View File

@ -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 */

77
gcc/text-art/selftests.cc Normal file
View File

@ -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 */

60
gcc/text-art/selftests.h Normal file
View File

@ -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 */

632
gcc/text-art/style.cc Normal file
View File

@ -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,
"");
ASSERT_NAMED_COL_STREQ (style::named_color::RED, fg, bright,
"");
ASSERT_NAMED_COL_STREQ (style::named_color::GREEN, fg, bright,
"");
ASSERT_NAMED_COL_STREQ (style::named_color::YELLOW, fg, bright,
"");
ASSERT_NAMED_COL_STREQ (style::named_color::BLUE, fg, bright,
"");
ASSERT_NAMED_COL_STREQ (style::named_color::MAGENTA, fg, bright,
"");
ASSERT_NAMED_COL_STREQ (style::named_color::CYAN, fg, bright,
"");
ASSERT_NAMED_COL_STREQ (style::named_color::WHITE, fg, bright,
"");
}
{
const bool bright = true;
ASSERT_NAMED_COL_STREQ (style::named_color::DEFAULT, fg, bright,
"");
ASSERT_NAMED_COL_STREQ (style::named_color::BLACK, fg, bright,
"");
ASSERT_NAMED_COL_STREQ (style::named_color::RED, fg, bright,
"");
ASSERT_NAMED_COL_STREQ (style::named_color::GREEN, fg, bright,
"");
ASSERT_NAMED_COL_STREQ (style::named_color::YELLOW, fg, bright,
"");
ASSERT_NAMED_COL_STREQ (style::named_color::BLUE, fg, bright,
"");
ASSERT_NAMED_COL_STREQ (style::named_color::MAGENTA, fg, bright,
"");
ASSERT_NAMED_COL_STREQ (style::named_color::CYAN, fg, bright,
"");
ASSERT_NAMED_COL_STREQ (style::named_color::WHITE, fg, bright,
"");
}
}
/* 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,
"");
ASSERT_NAMED_COL_STREQ (style::named_color::RED, fg, bright,
"");
ASSERT_NAMED_COL_STREQ (style::named_color::GREEN, fg, bright,
"");
ASSERT_NAMED_COL_STREQ (style::named_color::YELLOW, fg, bright,
"");
ASSERT_NAMED_COL_STREQ (style::named_color::BLUE, fg, bright,
"");
ASSERT_NAMED_COL_STREQ (style::named_color::MAGENTA, fg, bright,
"");
ASSERT_NAMED_COL_STREQ (style::named_color::CYAN, fg, bright,
"");
ASSERT_NAMED_COL_STREQ (style::named_color::WHITE, fg, bright,
"");
}
{
const bool bright = true;
ASSERT_NAMED_COL_STREQ (style::named_color::DEFAULT, fg, bright,
"");
ASSERT_NAMED_COL_STREQ (style::named_color::BLACK, fg, bright,
"");
ASSERT_NAMED_COL_STREQ (style::named_color::RED, fg, bright,
"");
ASSERT_NAMED_COL_STREQ (style::named_color::GREEN, fg, bright,
"");
ASSERT_NAMED_COL_STREQ (style::named_color::YELLOW, fg, bright,
"");
ASSERT_NAMED_COL_STREQ (style::named_color::BLUE, fg, bright,
"");
ASSERT_NAMED_COL_STREQ (style::named_color::MAGENTA, fg, bright,
"");
ASSERT_NAMED_COL_STREQ (style::named_color::CYAN, fg, bright,
"");
ASSERT_NAMED_COL_STREQ (style::named_color::WHITE, fg, bright,
"");
}
}
}
#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, "");
ASSERT_8_BIT_COL_STREQ (15, fg, "");
/* 16-231: 6x6x6 color cube. */
ASSERT_8_BIT_COL_STREQ (16, fg, "");
ASSERT_8_BIT_COL_STREQ (231, fg, "");
/* 232-255: grayscale. */
ASSERT_8_BIT_COL_STREQ (232, fg, "");
ASSERT_8_BIT_COL_STREQ (255, fg, "");
}
/* Background colors. */
{
const bool fg = false;
/* 0-15: standard and high-intensity standard colors. */
ASSERT_8_BIT_COL_STREQ (0, fg, "");
ASSERT_8_BIT_COL_STREQ (15, fg, "");
/* 16-231: 6x6x6 color cube. */
ASSERT_8_BIT_COL_STREQ (16, fg, "");
ASSERT_8_BIT_COL_STREQ (231, fg, "");
/* 232-255: grayscale. */
ASSERT_8_BIT_COL_STREQ (232, fg, "");
ASSERT_8_BIT_COL_STREQ (255, fg, "");
}
}
#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,
"");
}
/* Background colors. */
{
const bool fg = false;
// #FDF7E7
ASSERT_24_BIT_COL_STREQ (0xfd, 0xf7, 0xe7, fg,
"");
}
}
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 */

File diff suppressed because it is too large Load Diff

1272
gcc/text-art/table.cc Normal file

File diff suppressed because it is too large Load Diff

262
gcc/text-art/table.h Normal file
View File

@ -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 */

183
gcc/text-art/theme.cc Normal file
View File

@ -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 */
}
}

123
gcc/text-art/theme.h Normal file
View File

@ -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 */

504
gcc/text-art/types.h Normal file
View File

@ -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 */

275
gcc/text-art/widget.cc Normal file
View File

@ -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 */

246
gcc/text-art/widget.h Normal file
View File

@ -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 */

View File

@ -3154,6 +3154,40 @@ cpp_display_column_to_byte_column (const char *data, int data_length,
return dw.bytes_processed () + MAX (0, display_col - avail_display);
}
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 <= range_ends[0], true))
return range_values[0];
/* Binary search the tables. */
int begin = 1;
static const int end = num_ranges;
int len = end - begin;
do
{
int half = len/2;
int middle = begin + half;
if (c > range_ends[middle])
{
begin = middle + 1;
len -= half + 1;
}
else
len = half;
} while (len);
if (__builtin_expect (begin != end, true))
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
@ -3167,30 +3201,43 @@ cpp_display_column_to_byte_column (const char *data, int data_length,
diagnostics, they are sufficient. */
#include "generated_cpp_wcwidth.h"
int cpp_wcwidth (cppchar_t c)
int
cpp_wcwidth (cppchar_t c)
{
if (__builtin_expect (c <= wcwidth_range_ends[0], true))
return wcwidth_widths[0];
/* Binary search the tables. */
int begin = 1;
static const int end
const size_t num_ranges
= sizeof wcwidth_range_ends / sizeof (*wcwidth_range_ends);
int len = end - begin;
do
{
int half = len/2;
int middle = begin + half;
if (c > wcwidth_range_ends[middle])
{
begin = middle + 1;
len -= half + 1;
}
else
len = half;
} while (len);
if (__builtin_expect (begin != end, true))
return wcwidth_widths[begin];
return 1;
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);
}

View File

@ -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,
};

View File

@ -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 */

231
libcpp/printable-chars.inc Normal file
View File

@ -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,
};