From 1c1dd39625719d92b0ab7afc690f0aada5951072 Mon Sep 17 00:00:00 2001 From: Alexander Monakov Date: Thu, 18 May 2023 23:47:47 +0300 Subject: c-family: implement -ffp-contract=on Implement -ffp-contract=on for C and C++ without changing default behavior (=off for -std=cNN, =fast for C++ and -std=gnuNN). gcc/c-family/ChangeLog: * c-gimplify.cc (fma_supported_p): New helper. (c_gimplify_expr) [PLUS_EXPR, MINUS_EXPR]: Implement FMA contraction. gcc/ChangeLog: * common.opt (fp_contract_mode) [on]: Remove fallback. * config/sh/sh.md (*fmasf4): Correct flag_fp_contract_mode test. * doc/invoke.texi (-ffp-contract): Update. * trans-mem.cc (diagnose_tm_1): Skip internal function calls. --- gcc/c-family/c-gimplify.cc | 79 ++++++++++++++++++++++++++++++++++++++++++++++ gcc/common.opt | 3 +- gcc/config/sh/sh.md | 2 +- gcc/doc/invoke.texi | 8 +++-- gcc/trans-mem.cc | 3 ++ 5 files changed, 89 insertions(+), 6 deletions(-) diff --git a/gcc/c-family/c-gimplify.cc b/gcc/c-family/c-gimplify.cc index ef5c7d9..17b0610 100644 --- a/gcc/c-family/c-gimplify.cc +++ b/gcc/c-family/c-gimplify.cc @@ -41,6 +41,8 @@ along with GCC; see the file COPYING3. If not see #include "c-ubsan.h" #include "tree-nested.h" #include "context.h" +#include "tree-pass.h" +#include "internal-fn.h" /* The gimplification pass converts the language-dependent trees (ld-trees) emitted by the parser into language-independent trees @@ -686,6 +688,14 @@ c_build_bind_expr (location_t loc, tree block, tree body) return bind; } +/* Helper for c_gimplify_expr: test if target supports fma-like FN. */ + +static bool +fma_supported_p (enum internal_fn fn, tree type) +{ + return direct_internal_fn_supported_p (fn, type, OPTIMIZE_FOR_BOTH); +} + /* Gimplification of expression trees. */ /* Do C-specific gimplification on *EXPR_P. PRE_P and POST_P are as in @@ -739,6 +749,75 @@ c_gimplify_expr (tree *expr_p, gimple_seq *pre_p ATTRIBUTE_UNUSED, break; } + case PLUS_EXPR: + case MINUS_EXPR: + { + tree type = TREE_TYPE (*expr_p); + /* For -ffp-contract=on we need to attempt FMA contraction only + during initial gimplification. Late contraction across statement + boundaries would violate language semantics. */ + if (SCALAR_FLOAT_TYPE_P (type) + && flag_fp_contract_mode == FP_CONTRACT_ON + && cfun && !(cfun->curr_properties & PROP_gimple_any) + && fma_supported_p (IFN_FMA, type)) + { + bool neg_mul = false, neg_add = code == MINUS_EXPR; + + tree *op0_p = &TREE_OPERAND (*expr_p, 0); + tree *op1_p = &TREE_OPERAND (*expr_p, 1); + + /* Look for ±(x * y) ± z, swapping operands if necessary. */ + if (TREE_CODE (*op0_p) == NEGATE_EXPR + && TREE_CODE (TREE_OPERAND (*op0_p, 0)) == MULT_EXPR) + /* '*EXPR_P' is '-(x * y) ± z'. This is fine. */; + else if (TREE_CODE (*op0_p) != MULT_EXPR) + { + std::swap (op0_p, op1_p); + std::swap (neg_mul, neg_add); + } + if (TREE_CODE (*op0_p) == NEGATE_EXPR) + { + op0_p = &TREE_OPERAND (*op0_p, 0); + neg_mul = !neg_mul; + } + if (TREE_CODE (*op0_p) != MULT_EXPR) + break; + auto_vec ops (3); + ops.quick_push (TREE_OPERAND (*op0_p, 0)); + ops.quick_push (TREE_OPERAND (*op0_p, 1)); + ops.quick_push (*op1_p); + + enum internal_fn ifn = IFN_FMA; + if (neg_mul) + { + if (fma_supported_p (IFN_FNMA, type)) + ifn = IFN_FNMA; + else + ops[0] = build1 (NEGATE_EXPR, type, ops[0]); + } + if (neg_add) + { + enum internal_fn ifn2 = ifn == IFN_FMA ? IFN_FMS : IFN_FNMS; + if (fma_supported_p (ifn2, type)) + ifn = ifn2; + else + ops[2] = build1 (NEGATE_EXPR, type, ops[2]); + } + /* Avoid gimplify_arg: it emits all side effects into *PRE_P. */ + for (auto &&op : ops) + if (gimplify_expr (&op, pre_p, post_p, is_gimple_val, fb_rvalue) + == GS_ERROR) + return GS_ERROR; + + gcall *call = gimple_build_call_internal_vec (ifn, ops); + gimple_seq_add_stmt_without_update (pre_p, call); + *expr_p = create_tmp_var (type); + gimple_call_set_lhs (call, *expr_p); + return GS_ALL_DONE; + } + break; + } + default:; } diff --git a/gcc/common.opt b/gcc/common.opt index a28ca13..3daec85 100644 --- a/gcc/common.opt +++ b/gcc/common.opt @@ -1662,9 +1662,8 @@ Name(fp_contract_mode) Type(enum fp_contract_mode) UnknownError(unknown floating EnumValue Enum(fp_contract_mode) String(off) Value(FP_CONTRACT_OFF) -; Not implemented, fall back to conservative FP_CONTRACT_OFF. EnumValue -Enum(fp_contract_mode) String(on) Value(FP_CONTRACT_OFF) +Enum(fp_contract_mode) String(on) Value(FP_CONTRACT_ON) EnumValue Enum(fp_contract_mode) String(fast) Value(FP_CONTRACT_FAST) diff --git a/gcc/config/sh/sh.md b/gcc/config/sh/sh.md index 4622dba..5cb1795 100644 --- a/gcc/config/sh/sh.md +++ b/gcc/config/sh/sh.md @@ -9269,7 +9269,7 @@ (match_operand:SF 3 "arith_reg_operand" "0"))) (clobber (reg:SI FPSCR_STAT_REG)) (use (reg:SI FPSCR_MODES_REG))] - "TARGET_SH2E && flag_fp_contract_mode != FP_CONTRACT_OFF" + "TARGET_SH2E && flag_fp_contract_mode == FP_CONTRACT_FAST" "fmac %1,%2,%0" "&& can_create_pseudo_p ()" [(parallel [(set (match_dup 0) diff --git a/gcc/doc/invoke.texi b/gcc/doc/invoke.texi index 215ab0d..8c17a81 100644 --- a/gcc/doc/invoke.texi +++ b/gcc/doc/invoke.texi @@ -12077,10 +12077,12 @@ This option is enabled by default at optimization levels @option{-O1}, such as forming of fused multiply-add operations if the target has native support for them. @option{-ffp-contract=on} enables floating-point expression contraction -if allowed by the language standard. This is currently not implemented -and treated equal to @option{-ffp-contract=off}. +if allowed by the language standard. This is implemented for C and C++, +where it enables contraction within one expression, but not across +different statements. -The default is @option{-ffp-contract=fast}. +The default is @option{-ffp-contract=off} for C in a standards compliant mode +(@option{-std=c11} or similar), @option{-ffp-contract=fast} otherwise. @opindex fomit-frame-pointer @item -fomit-frame-pointer diff --git a/gcc/trans-mem.cc b/gcc/trans-mem.cc index d036e43..9c3d112 100644 --- a/gcc/trans-mem.cc +++ b/gcc/trans-mem.cc @@ -637,6 +637,9 @@ diagnose_tm_1 (gimple_stmt_iterator *gsi, bool *handled_ops_p, { case GIMPLE_CALL: { + if (gimple_call_internal_p (stmt)) + break; + tree fn = gimple_call_fn (stmt); if ((d->summary_flags & DIAG_TM_OUTER) == 0 -- cgit v1.1 From ce47d3c2cf59bb2cc94afc4bbef88b0e4950f086 Mon Sep 17 00:00:00 2001 From: Uros Bizjak Date: Wed, 21 Jun 2023 21:55:30 +0200 Subject: function: Change return type of predicate function from int to bool Also change some internal variables to bool and some functions to void. gcc/ChangeLog: * function.h (emit_initial_value_sets): Change return type from int to void. (aggregate_value_p): Change return type from int to bool. (prologue_contains): Ditto. (epilogue_contains): Ditto. (prologue_epilogue_contains): Ditto. * function.cc (temp_slot): Make "in_use" variable bool. (make_slot_available): Update for changed "in_use" variable. (assign_stack_temp_for_type): Ditto. (emit_initial_value_sets): Change return type from int to void and update function body accordingly. (instantiate_virtual_regs): Ditto. (rest_of_handle_thread_prologue_and_epilogue): Ditto. (safe_insn_predicate): Change return type from int to bool. (aggregate_value_p): Change return type from int to bool and update function body accordingly. (prologue_contains): Change return type from int to bool. (prologue_epilogue_contains): Ditto. --- gcc/function.cc | 77 +++++++++++++++++++++++++++------------------------------ gcc/function.h | 10 ++++---- 2 files changed, 42 insertions(+), 45 deletions(-) diff --git a/gcc/function.cc b/gcc/function.cc index 82102ed..6a79a82 100644 --- a/gcc/function.cc +++ b/gcc/function.cc @@ -578,8 +578,8 @@ public: tree type; /* The alignment (in bits) of the slot. */ unsigned int align; - /* Nonzero if this temporary is currently in use. */ - char in_use; + /* True if this temporary is currently in use. */ + bool in_use; /* Nesting level at which this slot is being used. */ int level; /* The offset of the slot from the frame_pointer, including extra space @@ -674,7 +674,7 @@ make_slot_available (class temp_slot *temp) { cut_slot_from_list (temp, temp_slots_at_level (temp->level)); insert_slot_to_list (temp, &avail_temp_slots); - temp->in_use = 0; + temp->in_use = false; temp->level = -1; n_temp_slots_in_use--; } @@ -848,7 +848,7 @@ assign_stack_temp_for_type (machine_mode mode, poly_int64 size, tree type) if (known_ge (best_p->size - rounded_size, alignment)) { p = ggc_alloc (); - p->in_use = 0; + p->in_use = false; p->size = best_p->size - rounded_size; p->base_offset = best_p->base_offset + rounded_size; p->full_size = best_p->full_size - rounded_size; @@ -918,7 +918,7 @@ assign_stack_temp_for_type (machine_mode mode, poly_int64 size, tree type) } p = selected; - p->in_use = 1; + p->in_use = true; p->type = type; p->level = temp_slot_level; n_temp_slots_in_use++; @@ -1340,7 +1340,7 @@ has_hard_reg_initial_val (machine_mode mode, unsigned int regno) return NULL_RTX; } -unsigned int +void emit_initial_value_sets (void) { struct initial_value_struct *ivs = crtl->hard_reg_initial_vals; @@ -1348,7 +1348,7 @@ emit_initial_value_sets (void) rtx_insn *seq; if (ivs == 0) - return 0; + return; start_sequence (); for (i = 0; i < ivs->num_entries; i++) @@ -1357,7 +1357,6 @@ emit_initial_value_sets (void) end_sequence (); emit_insn_at_entry (seq); - return 0; } /* Return the hardreg-pseudoreg initial values pair entry I and @@ -1535,7 +1534,7 @@ instantiate_virtual_regs_in_rtx (rtx *loc) /* A subroutine of instantiate_virtual_regs_in_insn. Return true if X matches the predicate for insn CODE operand OPERAND. */ -static int +static bool safe_insn_predicate (int code, int operand, rtx x) { return code < 0 || insn_operand_matches ((enum insn_code) code, operand, x); @@ -1947,7 +1946,7 @@ instantiate_decls (tree fndecl) /* Pass through the INSNS of function FNDECL and convert virtual register references to hard register references. */ -static unsigned int +static void instantiate_virtual_regs (void) { rtx_insn *insn; @@ -2001,8 +2000,6 @@ instantiate_virtual_regs (void) /* Indicate that, from now on, assign_stack_local should use frame_pointer_rtx. */ virtuals_instantiated = 1; - - return 0; } namespace { @@ -2030,7 +2027,8 @@ public: /* opt_pass methods: */ unsigned int execute (function *) final override { - return instantiate_virtual_regs (); + instantiate_virtual_regs (); + return 0; } }; // class pass_instantiate_virtual_regs @@ -2044,12 +2042,12 @@ make_pass_instantiate_virtual_regs (gcc::context *ctxt) } -/* Return 1 if EXP is an aggregate type (or a value with aggregate type). +/* Return true if EXP is an aggregate type (or a value with aggregate type). This means a type for which function calls must pass an address to the function or get an address back from the function. EXP may be a type node or an expression (whose type is tested). */ -int +bool aggregate_value_p (const_tree exp, const_tree fntype) { const_tree type = (TYPE_P (exp)) ? exp : TREE_TYPE (exp); @@ -2069,7 +2067,7 @@ aggregate_value_p (const_tree exp, const_tree fntype) else /* For internal functions, assume nothing needs to be returned in memory. */ - return 0; + return false; } break; case FUNCTION_DECL: @@ -2087,10 +2085,10 @@ aggregate_value_p (const_tree exp, const_tree fntype) } if (VOID_TYPE_P (type)) - return 0; + return false; if (error_operand_p (fntype)) - return 0; + return false; /* If a record should be passed the same as its first (and only) member don't pass it as an aggregate. */ @@ -2101,25 +2099,25 @@ aggregate_value_p (const_tree exp, const_tree fntype) reference, do so. */ if ((TREE_CODE (exp) == PARM_DECL || TREE_CODE (exp) == RESULT_DECL) && DECL_BY_REFERENCE (exp)) - return 1; + return true; /* Function types that are TREE_ADDRESSABLE force return in memory. */ if (fntype && TREE_ADDRESSABLE (fntype)) - return 1; + return true; /* Types that are TREE_ADDRESSABLE must be constructed in memory, and thus can't be returned in registers. */ if (TREE_ADDRESSABLE (type)) - return 1; + return true; if (TYPE_EMPTY_P (type)) - return 0; + return false; if (flag_pcc_struct_return && AGGREGATE_TYPE_P (type)) - return 1; + return true; if (targetm.calls.return_in_memory (type, fntype)) - return 1; + return true; /* Make sure we have suitable call-clobbered regs to return the value in; if not, we must return it in memory. */ @@ -2128,7 +2126,7 @@ aggregate_value_p (const_tree exp, const_tree fntype) /* If we have something other than a REG (e.g. a PARALLEL), then assume it is OK. */ if (!REG_P (reg)) - return 0; + return false; /* Use the default ABI if the type of the function isn't known. The scheme for handling interoperability between different ABIs @@ -2141,9 +2139,9 @@ aggregate_value_p (const_tree exp, const_tree fntype) nregs = hard_regno_nregs (regno, TYPE_MODE (type)); for (i = 0; i < nregs; i++) if (!fixed_regs[regno + i] && !abi.clobbers_full_reg_p (regno + i)) - return 1; + return true; - return 0; + return false; } /* Return true if we should assign DECL a pseudo register; false if it @@ -5733,26 +5731,26 @@ contains (const rtx_insn *insn, hash_table *hash) return hash->find (const_cast (insn)) != NULL; } -int +bool prologue_contains (const rtx_insn *insn) { return contains (insn, prologue_insn_hash); } -int +bool epilogue_contains (const rtx_insn *insn) { return contains (insn, epilogue_insn_hash); } -int +bool prologue_epilogue_contains (const rtx_insn *insn) { if (contains (insn, prologue_insn_hash)) - return 1; + return true; if (contains (insn, epilogue_insn_hash)) - return 1; - return 0; + return true; + return false; } void @@ -6386,14 +6384,13 @@ current_function_name (void) } -static unsigned int +static void rest_of_handle_check_leaf_regs (void) { #ifdef LEAF_REGISTERS crtl->uses_only_leaf_regs = optimize > 0 && only_leaf_regs_used () && leaf_function_p (); #endif - return 0; } /* Insert a TYPE into the used types hash table of CFUN. */ @@ -6518,7 +6515,8 @@ public: /* opt_pass methods: */ unsigned int execute (function *) final override { - return rest_of_handle_check_leaf_regs (); + rest_of_handle_check_leaf_regs (); + return 0; } }; // class pass_leaf_regs @@ -6531,7 +6529,7 @@ make_pass_leaf_regs (gcc::context *ctxt) return new pass_leaf_regs (ctxt); } -static unsigned int +static void rest_of_handle_thread_prologue_and_epilogue (function *fun) { /* prepare_shrink_wrap is sensitive to the block structure of the control @@ -6563,8 +6561,6 @@ rest_of_handle_thread_prologue_and_epilogue (function *fun) /* The stack usage info is finalized during prologue expansion. */ if (flag_stack_usage_info || flag_callgraph_info) output_stack_usage (); - - return 0; } /* Record a final call to CALLEE at LOCATION. */ @@ -6626,7 +6622,8 @@ public: /* opt_pass methods: */ unsigned int execute (function * fun) final override { - return rest_of_handle_thread_prologue_and_epilogue (fun); + rest_of_handle_thread_prologue_and_epilogue (fun); + return 0; } }; // class pass_thread_prologue_and_epilogue diff --git a/gcc/function.h b/gcc/function.h index d4ce8a7..e290ff5 100644 --- a/gcc/function.h +++ b/gcc/function.h @@ -657,11 +657,11 @@ extern rtx get_hard_reg_initial_val (machine_mode, unsigned int); extern rtx has_hard_reg_initial_val (machine_mode, unsigned int); /* Called from gimple_expand_cfg. */ -extern unsigned int emit_initial_value_sets (void); +extern void emit_initial_value_sets (void); extern bool initial_value_entry (int i, rtx *, rtx *); extern void instantiate_decl_rtl (rtx x); -extern int aggregate_value_p (const_tree, const_tree); +extern bool aggregate_value_p (const_tree, const_tree); extern bool use_register_for_decl (const_tree); extern gimple_seq gimplify_parameters (gimple_seq *); extern void locate_and_pad_parm (machine_mode, tree, int, int, int, @@ -702,9 +702,9 @@ extern void clobber_return_register (void); extern void expand_function_end (void); extern rtx get_arg_pointer_save_area (void); extern void maybe_copy_prologue_epilogue_insn (rtx, rtx); -extern int prologue_contains (const rtx_insn *); -extern int epilogue_contains (const rtx_insn *); -extern int prologue_epilogue_contains (const rtx_insn *); +extern bool prologue_contains (const rtx_insn *); +extern bool epilogue_contains (const rtx_insn *); +extern bool prologue_epilogue_contains (const rtx_insn *); extern void record_prologue_seq (rtx_insn *); extern void record_epilogue_seq (rtx_insn *); extern void emit_return_into_block (bool simple_p, basic_block bb); -- cgit v1.1 From 80e9ca0e36cc3ec1153c16764fa922875750c17a Mon Sep 17 00:00:00 2001 From: GCC Administrator Date: Thu, 22 Jun 2023 00:17:09 +0000 Subject: Daily bump. --- gcc/ChangeLog | 227 ++++++++++++++++++++++++++++++++++++++++++++++++ gcc/DATESTAMP | 2 +- gcc/c-family/ChangeLog | 6 ++ gcc/fortran/ChangeLog | 34 ++++++++ gcc/testsuite/ChangeLog | 49 +++++++++++ 5 files changed, 317 insertions(+), 1 deletion(-) diff --git a/gcc/ChangeLog b/gcc/ChangeLog index 2d9e425..a82f00f 100644 --- a/gcc/ChangeLog +++ b/gcc/ChangeLog @@ -1,3 +1,230 @@ +2023-06-21 Uros Bizjak + + * function.h (emit_initial_value_sets): + Change return type from int to void. + (aggregate_value_p): Change return type from int to bool. + (prologue_contains): Ditto. + (epilogue_contains): Ditto. + (prologue_epilogue_contains): Ditto. + * function.cc (temp_slot): Make "in_use" variable bool. + (make_slot_available): Update for changed "in_use" variable. + (assign_stack_temp_for_type): Ditto. + (emit_initial_value_sets): Change return type from int to void + and update function body accordingly. + (instantiate_virtual_regs): Ditto. + (rest_of_handle_thread_prologue_and_epilogue): Ditto. + (safe_insn_predicate): Change return type from int to bool. + (aggregate_value_p): Change return type from int to bool + and update function body accordingly. + (prologue_contains): Change return type from int to bool. + (prologue_epilogue_contains): Ditto. + +2023-06-21 Alexander Monakov + + * common.opt (fp_contract_mode) [on]: Remove fallback. + * config/sh/sh.md (*fmasf4): Correct flag_fp_contract_mode test. + * doc/invoke.texi (-ffp-contract): Update. + * trans-mem.cc (diagnose_tm_1): Skip internal function calls. + +2023-06-21 Kyrylo Tkachov + + * config/aarch64/aarch64-sve.md (mask_gather_load): + Add alternatives to prefer to avoid same input and output Z register. + (mask_gather_load): Likewise. + (*mask_gather_load_xtw_unpacked): Likewise. + (*mask_gather_load_sxtw): Likewise. + (*mask_gather_load_uxtw): Likewise. + (@aarch64_gather_load_): + Likewise. + (@aarch64_gather_load_): + Likewise. + (*aarch64_gather_load_ + _xtw_unpacked): Likewise. + (*aarch64_gather_load_ + _sxtw): Likewise. + (*aarch64_gather_load_ + _uxtw): Likewise. + (@aarch64_ldff1_gather): Likewise. + (@aarch64_ldff1_gather): Likewise. + (*aarch64_ldff1_gather_sxtw): Likewise. + (*aarch64_ldff1_gather_uxtw): Likewise. + (@aarch64_ldff1_gather_ + ): Likewise. + (@aarch64_ldff1_gather_ + ): Likewise. + (*aarch64_ldff1_gather_ + _sxtw): Likewise. + (*aarch64_ldff1_gather_ + _uxtw): Likewise. + * config/aarch64/aarch64-sve2.md (@aarch64_gather_ldnt): Likewise. + (@aarch64_gather_ldnt_ + ): Likewise. + +2023-06-21 Kyrylo Tkachov + + * config/aarch64/aarch64-sve.md (mask_gather_load): + Convert to compact alternatives syntax. + (mask_gather_load): Likewise. + (*mask_gather_load_xtw_unpacked): Likewise. + (*mask_gather_load_sxtw): Likewise. + (*mask_gather_load_uxtw): Likewise. + (@aarch64_gather_load_): + Likewise. + (@aarch64_gather_load_): + Likewise. + (*aarch64_gather_load_ + _xtw_unpacked): Likewise. + (*aarch64_gather_load_ + _sxtw): Likewise. + (*aarch64_gather_load_ + _uxtw): Likewise. + (@aarch64_ldff1_gather): Likewise. + (@aarch64_ldff1_gather): Likewise. + (*aarch64_ldff1_gather_sxtw): Likewise. + (*aarch64_ldff1_gather_uxtw): Likewise. + (@aarch64_ldff1_gather_ + ): Likewise. + (@aarch64_ldff1_gather_ + ): Likewise. + (*aarch64_ldff1_gather_ + _sxtw): Likewise. + (*aarch64_ldff1_gather_ + _uxtw): Likewise. + * config/aarch64/aarch64-sve2.md (@aarch64_gather_ldnt): Likewise. + (@aarch64_gather_ldnt_ + ): Likewise. + +2023-06-21 Kyrylo Tkachov + + Revert: + 2023-06-21 Kyrylo Tkachov + + * config/aarch64/aarch64-sve.md (mask_gather_load): + Convert to compact alternatives syntax. + (mask_gather_load): Likewise. + (*mask_gather_load_xtw_unpacked): Likewise. + (*mask_gather_load_sxtw): Likewise. + (*mask_gather_load_uxtw): Likewise. + (@aarch64_gather_load_): + Likewise. + (@aarch64_gather_load_): + Likewise. + (*aarch64_gather_load_ + _xtw_unpacked): Likewise. + (*aarch64_gather_load_ + _sxtw): Likewise. + (*aarch64_gather_load_ + _uxtw): Likewise. + (@aarch64_ldff1_gather): Likewise. + (@aarch64_ldff1_gather): Likewise. + (*aarch64_ldff1_gather_sxtw): Likewise. + (*aarch64_ldff1_gather_uxtw): Likewise. + (@aarch64_ldff1_gather_ + ): Likewise. + (@aarch64_ldff1_gather_ + ): Likewise. + (*aarch64_ldff1_gather_ + _sxtw): Likewise. + (*aarch64_ldff1_gather_ + _uxtw): Likewise. + * config/aarch64/aarch64-sve2.md (@aarch64_gather_ldnt): Likewise. + (@aarch64_gather_ldnt_ + ): Likewise. + +2023-06-21 Ju-Zhe Zhong + + * optabs-query.cc (can_vec_mask_load_store_p): Move to optabs-tree.cc. + (get_len_load_store_mode): Ditto. + * optabs-query.h (can_vec_mask_load_store_p): Move to optabs-tree.h. + (get_len_load_store_mode): Ditto. + * optabs-tree.cc (can_vec_mask_load_store_p): New function. + (get_len_load_store_mode): Ditto. + * optabs-tree.h (can_vec_mask_load_store_p): Ditto. + (get_len_load_store_mode): Ditto. + * tree-if-conv.cc: include optabs-tree instead of optabs-query + +2023-06-21 Richard Biener + + * tree-ssa-loop-ivopts.cc (add_iv_candidate_for_use): Use + split_constant_offset for the POINTER_PLUS_EXPR case. + +2023-06-21 Richard Biener + + * tree-ssa-loop-ivopts.cc (record_group_use): Use + split_constant_offset. + +2023-06-21 Richard Biener + + * tree-loop-distribution.cc (classify_builtin_st): Use + split_constant_offset. + * tree-ssa-loop-ivopts.h (strip_offset): Remove. + * tree-ssa-loop-ivopts.cc (strip_offset): Make static. + +2023-06-21 Kyrylo Tkachov + + * config/aarch64/aarch64-sve.md (mask_gather_load): + Convert to compact alternatives syntax. + (mask_gather_load): Likewise. + (*mask_gather_load_xtw_unpacked): Likewise. + (*mask_gather_load_sxtw): Likewise. + (*mask_gather_load_uxtw): Likewise. + (@aarch64_gather_load_): + Likewise. + (@aarch64_gather_load_): + Likewise. + (*aarch64_gather_load_ + _xtw_unpacked): Likewise. + (*aarch64_gather_load_ + _sxtw): Likewise. + (*aarch64_gather_load_ + _uxtw): Likewise. + (@aarch64_ldff1_gather): Likewise. + (@aarch64_ldff1_gather): Likewise. + (*aarch64_ldff1_gather_sxtw): Likewise. + (*aarch64_ldff1_gather_uxtw): Likewise. + (@aarch64_ldff1_gather_ + ): Likewise. + (@aarch64_ldff1_gather_ + ): Likewise. + (*aarch64_ldff1_gather_ + _sxtw): Likewise. + (*aarch64_ldff1_gather_ + _uxtw): Likewise. + * config/aarch64/aarch64-sve2.md (@aarch64_gather_ldnt): Likewise. + (@aarch64_gather_ldnt_ + ): Likewise. + +2023-06-21 Tamar Christina + + PR other/110329 + * doc/md.texi: Replace backslashchar. + +2023-06-21 Richard Biener + + * config/i386/i386.cc (ix86_vector_costs::finish_cost): + Overload. For masked main loops make sure the vectorization + factor isn't more than double the number of iterations. + +2023-06-21 Jan Beulich + + * config/i386/i386-expand.cc (ix86_expand_copysign): Request + value duplication by ix86_build_signbit_mask() when AVX512F and + not HFmode. + * config/i386/sse.md (*_vternlog_all): Convert to + 2-alternative form. Adjust "mode" attribute. Add "enabled" + attribute. + (*_vpternlog_1): Also permit when TARGET_AVX512F + && !TARGET_PREFER_AVX256. + (*_vpternlog_2): Likewise. + (*_vpternlog_3): Likewise. + +2023-06-21 liuhongt + + PR target/110018 + * tree-vect-stmts.cc (vectorizable_conversion): Use + intermiediate integer type for float_expr/fix_trunc_expr when + direct optab is not existed. + 2023-06-20 Tamar Christina PR bootstrap/110324 diff --git a/gcc/DATESTAMP b/gcc/DATESTAMP index 047f836..2fa3911 100644 --- a/gcc/DATESTAMP +++ b/gcc/DATESTAMP @@ -1 +1 @@ -20230621 +20230622 diff --git a/gcc/c-family/ChangeLog b/gcc/c-family/ChangeLog index c326324..c78ad9c 100644 --- a/gcc/c-family/ChangeLog +++ b/gcc/c-family/ChangeLog @@ -1,3 +1,9 @@ +2023-06-21 Alexander Monakov + + * c-gimplify.cc (fma_supported_p): New helper. + (c_gimplify_expr) [PLUS_EXPR, MINUS_EXPR]: Implement FMA + contraction. + 2023-06-16 Alex Coplan * c.opt (Welaborated-enum-base): New. diff --git a/gcc/fortran/ChangeLog b/gcc/fortran/ChangeLog index 147fb1d..f0424c6 100644 --- a/gcc/fortran/ChangeLog +++ b/gcc/fortran/ChangeLog @@ -1,3 +1,37 @@ +2023-06-21 Paul Thomas + + PR fortran/87477 + PR fortran/88688 + PR fortran/94380 + PR fortran/107900 + PR fortran/110224 + * decl.cc (char_len_param_value): Fix memory leak. + (resolve_block_construct): Remove unnecessary static decls. + * expr.cc (gfc_is_ptr_fcn): New function. + (gfc_check_vardef_context): Use it to permit pointer function + result selectors to be used for associate names in variable + definition context. + * gfortran.h: Prototype for gfc_is_ptr_fcn. + * match.cc (build_associate_name): New function. + (gfc_match_select_type): Use the new function to replace inline + version and to build a new associate name for the case where + the supplied associate name is already used for that purpose. + * resolve.cc (resolve_assoc_var): Call gfc_is_ptr_fcn to allow + associate names with pointer function targets to be used in + variable definition context. + * trans-decl.cc (gfc_get_symbol_decl): Unlimited polymorphic + variables need deferred initialisation of the vptr. + (gfc_trans_deferred_vars): Do the vptr initialisation. + * trans-stmt.cc (trans_associate_var): Ensure that a pointer + associate name points to the target of the selector and not + the selector itself. + +2023-06-21 Paul Thomas + + PR fortran/108961 + * trans-expr.cc (gfc_conv_procedure_call): The hidden string + length must not be passed to a formal arg of type(cptr). + 2023-06-20 Tobias Burnus * dump-parse-tree.cc (show_omp_namelist): Fix dump of the allocator diff --git a/gcc/testsuite/ChangeLog b/gcc/testsuite/ChangeLog index 3e3f0ad..6ff4c0d 100644 --- a/gcc/testsuite/ChangeLog +++ b/gcc/testsuite/ChangeLog @@ -1,3 +1,52 @@ +2023-06-21 Paul Thomas + + PR fortran/87477 + PR fortran/107900 + PR fortran/110224 + PR fortran/88688 + PR fortran/94380 + PR fortran/95398 + * gfortran.dg/pr107900.f90 : New test + * gfortran.dg/pr110224.f90 : New test + * gfortran.dg/pr88688.f90 : New test + * gfortran.dg/pr94380.f90 : New test + * gfortran.dg/pr95398.f90 : Set -std=f2008, bump the line + numbers in the error tests by two and change the text in two. + +2023-06-21 Paul Thomas + + PR fortran/108961 + * gfortran.dg/pr108961.f90: New test. + +2023-06-21 Uros Bizjak + + PR target/110018 + * gcc.target/i386/pr110018-1.c: Use explicit signed types. + * gcc.target/i386/pr110018-2.c: New test. + +2023-06-21 Kyrylo Tkachov + + * gcc.target/aarch64/sve/gather_earlyclobber.c: New test. + * gcc.target/aarch64/sve2/gather_earlyclobber.c: New test. + +2023-06-21 Richard Biener + + * gcc.target/i386/vect-partial-vectors-1.c: New testcase. + * gcc.target/i386/vect-partial-vectors-2.c: Likewise. + +2023-06-21 Jan Beulich + + * gcc.target/i386/avx512f-copysign.c: New test. + +2023-06-21 Jan Beulich + + * gcc.target/i386/avx512f-dupv2di.c: Add + -mprefer-vector-width=512. + +2023-06-21 liuhongt + + * gcc.target/i386/pr110018-1.c: New test. + 2023-06-20 Lewis Hyatt PR c++/66290 -- cgit v1.1 From cb760f66e0b29f09af5cfa0cd6aebc02aaaa0f7f Mon Sep 17 00:00:00 2001 From: Ian Lance Taylor Date: Wed, 21 Jun 2023 14:27:02 -0700 Subject: compiler: determine types of Slice_{value,info} expressions This fixes an accidental omission in the determine types pass. Test case is https://go.dev/cl/505015. Reviewed-on: https://go-review.googlesource.com/c/gofrontend/+/504797 --- gcc/go/gofrontend/MERGE | 2 +- gcc/go/gofrontend/expressions.cc | 10 ++++++++++ gcc/go/gofrontend/expressions.h | 5 ++--- 3 files changed, 13 insertions(+), 4 deletions(-) diff --git a/gcc/go/gofrontend/MERGE b/gcc/go/gofrontend/MERGE index dbb2d68..a028350 100644 --- a/gcc/go/gofrontend/MERGE +++ b/gcc/go/gofrontend/MERGE @@ -1,4 +1,4 @@ -6a1d165c2218cd127ee937a1f45599075762f716 +195060166e6045408a2cb95e6aa88c6f0b98f20b The first line of this file holds the git revision number of the last merge done from the gofrontend repository. diff --git a/gcc/go/gofrontend/expressions.cc b/gcc/go/gofrontend/expressions.cc index 4ac55af..2112de6 100644 --- a/gcc/go/gofrontend/expressions.cc +++ b/gcc/go/gofrontend/expressions.cc @@ -18307,6 +18307,16 @@ Slice_value_expression::do_traverse(Traverse* traverse) return TRAVERSE_CONTINUE; } +// Determine type of a slice value. + +void +Slice_value_expression::do_determine_type(const Type_context*) +{ + this->valmem_->determine_type_no_context(); + this->len_->determine_type_no_context(); + this->cap_->determine_type_no_context(); +} + Expression* Slice_value_expression::do_copy() { diff --git a/gcc/go/gofrontend/expressions.h b/gcc/go/gofrontend/expressions.h index 3d7e787..bdb7ccd 100644 --- a/gcc/go/gofrontend/expressions.h +++ b/gcc/go/gofrontend/expressions.h @@ -4364,8 +4364,7 @@ class Slice_value_expression : public Expression { return this->type_; } void - do_determine_type(const Type_context*) - { } + do_determine_type(const Type_context*); Expression* do_copy(); @@ -4419,7 +4418,7 @@ class Slice_info_expression : public Expression void do_determine_type(const Type_context*) - { } + { this->slice_->determine_type_no_context(); } Expression* do_copy() -- cgit v1.1 From 985d6480fe52a5b109960117ba6a876dd875157e Mon Sep 17 00:00:00 2001 From: David Malcolm Date: Wed, 21 Jun 2023 21:48:59 -0400 Subject: testsuite: move handle-multiline-outputs to before check for blank lines I have followup patches that require checking for multiline patterns that have blank lines within them, so this moves the handling of multiline patterns before the check for blank lines, allowing for such multiline patterns. Doing so uncovers some issues with existing multiline directives, which the patch fixes. gcc/testsuite/ChangeLog: * c-c++-common/Wlogical-not-parentheses-2.c: Split up the multiline directive. * gcc.dg/analyzer/malloc-macro-inline-events.c: Remove redundant dg-regexp directives. * gcc.dg/missing-header-fixit-5.c: Split up the multiline directives. * lib/gcc-dg.exp (gcc-dg-prune): Move call to handle-multiline-outputs from prune_gcc_output to here. * lib/multiline.exp (dg-end-multiline-output): Move call to maybe-handle-nn-line-numbers from prune_gcc_output to here. * lib/prune.exp (prune_gcc_output): Move calls to maybe-handle-nn-line-numbers and handle-multiline-outputs from here to the above. Signed-off-by: David Malcolm --- gcc/testsuite/c-c++-common/Wlogical-not-parentheses-2.c | 2 ++ gcc/testsuite/gcc.dg/analyzer/malloc-macro-inline-events.c | 5 ----- gcc/testsuite/gcc.dg/missing-header-fixit-5.c | 10 ++++++++-- gcc/testsuite/lib/gcc-dg.exp | 5 +++++ gcc/testsuite/lib/multiline.exp | 7 ++++++- gcc/testsuite/lib/prune.exp | 7 ------- 6 files changed, 21 insertions(+), 15 deletions(-) diff --git a/gcc/testsuite/c-c++-common/Wlogical-not-parentheses-2.c b/gcc/testsuite/c-c++-common/Wlogical-not-parentheses-2.c index ba8dce8..2d93820 100644 --- a/gcc/testsuite/c-c++-common/Wlogical-not-parentheses-2.c +++ b/gcc/testsuite/c-c++-common/Wlogical-not-parentheses-2.c @@ -12,6 +12,8 @@ foo (int aaa, int bbb) /* { dg-begin-multiline-output "" } r += !aaa == bbb; ^~ + { dg-end-multiline-output "" } */ +/* { dg-begin-multiline-output "" } r += !aaa == bbb; ^~~~ ( ) diff --git a/gcc/testsuite/gcc.dg/analyzer/malloc-macro-inline-events.c b/gcc/testsuite/gcc.dg/analyzer/malloc-macro-inline-events.c index f08aee6..9134bb4 100644 --- a/gcc/testsuite/gcc.dg/analyzer/malloc-macro-inline-events.c +++ b/gcc/testsuite/gcc.dg/analyzer/malloc-macro-inline-events.c @@ -12,11 +12,6 @@ int test (void *ptr) WRAPPED_FREE (ptr); /* { dg-message "in expansion of macro 'WRAPPED_FREE'" } */ WRAPPED_FREE (ptr); /* { dg-message "in expansion of macro 'WRAPPED_FREE'" } */ - /* Erase the spans indicating the header file - (to avoid embedding path assumptions). */ - /* { dg-regexp "\[^|\]+/malloc-macro.h:\[0-9\]+:\[0-9\]+:" } */ - /* { dg-regexp "\[^|\]+/malloc-macro.h:\[0-9\]+:\[0-9\]+:" } */ - /* { dg-begin-multiline-output "" } NN | #define WRAPPED_FREE(PTR) free(PTR) | ^~~~~~~~~ diff --git a/gcc/testsuite/gcc.dg/missing-header-fixit-5.c b/gcc/testsuite/gcc.dg/missing-header-fixit-5.c index 916033c..bf44feb 100644 --- a/gcc/testsuite/gcc.dg/missing-header-fixit-5.c +++ b/gcc/testsuite/gcc.dg/missing-header-fixit-5.c @@ -12,14 +12,18 @@ foo (char *m, int i) /* { dg-begin-multiline-output "" } 11 | if (isdigit (m[0])) | ^~~~~~~ + { dg-end-multiline-output "" } */ + /* { dg-begin-multiline-output "" } +++ |+#include 1 | { dg-end-multiline-output "" } */ { return abs (i); /* { dg-warning "implicit declaration of function" } */ /* { dg-begin-multiline-output "" } - 19 | return abs (i); + 21 | return abs (i); | ^~~ + { dg-end-multiline-output "" } */ + /* { dg-begin-multiline-output "" } +++ |+#include 1 | { dg-end-multiline-output "" } */ @@ -27,8 +31,10 @@ foo (char *m, int i) else putchar (m[0]); /* { dg-warning "implicit declaration of function" } */ /* { dg-begin-multiline-output "" } - 28 | putchar (m[0]); + 32 | putchar (m[0]); | ^~~~~~~ + { dg-end-multiline-output "" } */ + /* { dg-begin-multiline-output "" } +++ |+#include 1 | { dg-end-multiline-output "" } */ diff --git a/gcc/testsuite/lib/gcc-dg.exp b/gcc/testsuite/lib/gcc-dg.exp index 01c8c02..28529f5 100644 --- a/gcc/testsuite/lib/gcc-dg.exp +++ b/gcc/testsuite/lib/gcc-dg.exp @@ -364,6 +364,11 @@ proc gcc-dg-prune { system text } { # Always remember to clear it in .exp file after executed all tests. global dg_runtest_extra_prunes + # Call into multiline.exp to handle any multiline output directives. + # This is done before the check for blank lines so that multiline + # output directives can have blank lines within them. + set text [handle-multiline-outputs $text] + # Complain about blank lines in the output (PR other/69006) global allow_blank_lines if { !$allow_blank_lines } { diff --git a/gcc/testsuite/lib/multiline.exp b/gcc/testsuite/lib/multiline.exp index 73621a0..4c25bb7 100644 --- a/gcc/testsuite/lib/multiline.exp +++ b/gcc/testsuite/lib/multiline.exp @@ -139,7 +139,7 @@ proc dg-end-multiline-output { args } { verbose "within dg-end-multiline-output: multiline_expected_outputs: $multiline_expected_outputs" 3 } -# Hook to be called by prune.exp's prune_gcc_output to +# Hook to be called by gcc-dg.exp's gcc-dg-prune to # look for the expected multiline outputs, pruning them, # reporting PASS for those that are found, and FAIL for # those that weren't found. @@ -149,6 +149,11 @@ proc dg-end-multiline-output { args } { proc handle-multiline-outputs { text } { global multiline_expected_outputs global testname_with_flags + + # If dg-enable-nn-line-numbers was provided, then obscure source-margin + # line numbers by converting them to "NN" form. + set text [maybe-handle-nn-line-numbers $text] + set index 0 foreach entry $multiline_expected_outputs { verbose " entry: $entry" 3 diff --git a/gcc/testsuite/lib/prune.exp b/gcc/testsuite/lib/prune.exp index cfe427c..8d37b24 100644 --- a/gcc/testsuite/lib/prune.exp +++ b/gcc/testsuite/lib/prune.exp @@ -108,13 +108,6 @@ proc prune_gcc_output { text } { # Many tests that use visibility will still pass on platforms that don't support it. regsub -all "(^|\n)\[^\n\]*lto1: warning: visibility attribute not supported in this configuration; ignored\[^\n\]*" $text "" text - # If dg-enable-nn-line-numbers was provided, then obscure source-margin - # line numbers by converting them to "NN" form. - set text [maybe-handle-nn-line-numbers $text] - - # Call into multiline.exp to handle any multiline output directives. - set text [handle-multiline-outputs $text] - #send_user "After:$text\n" return $text -- cgit v1.1 From 4f01ae3761ca1f8dd7a33b833ae30624f047ac9c Mon Sep 17 00:00:00 2001 From: David Malcolm Date: Wed, 21 Jun 2023 21:49:00 -0400 Subject: 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 --- contrib/unicode/gen-box-drawing-chars.py | 94 ++ contrib/unicode/gen-combining-chars.py | 75 ++ contrib/unicode/gen-printable-chars.py | 77 ++ gcc/Makefile.in | 11 +- gcc/color-macros.h | 16 + gcc/common.opt | 23 + gcc/configure | 2 +- gcc/configure.ac | 2 +- gcc/diagnostic-diagram.h | 51 + gcc/diagnostic-format-json.cc | 10 + gcc/diagnostic-format-sarif.cc | 106 +- gcc/diagnostic-text-art.h | 49 + gcc/diagnostic.cc | 72 ++ gcc/diagnostic.h | 21 + gcc/doc/invoke.texi | 25 +- gcc/gcc.cc | 6 + gcc/opts-common.cc | 1 + gcc/opts.cc | 6 + gcc/pretty-print.cc | 29 + gcc/pretty-print.h | 1 + gcc/selftest-run-tests.cc | 3 + .../plugin/diagnostic-test-text-art-ascii-bw.c | 57 + .../plugin/diagnostic-test-text-art-ascii-color.c | 58 + .../gcc.dg/plugin/diagnostic-test-text-art-none.c | 5 + .../plugin/diagnostic-test-text-art-unicode-bw.c | 58 + .../diagnostic-test-text-art-unicode-color.c | 59 + .../plugin/diagnostic_plugin_test_text_art.c | 257 ++++ gcc/testsuite/gcc.dg/plugin/plugin.exp | 6 + gcc/text-art/box-drawing-chars.inc | 18 + gcc/text-art/box-drawing.cc | 72 ++ gcc/text-art/box-drawing.h | 32 + gcc/text-art/canvas.cc | 437 +++++++ gcc/text-art/canvas.h | 74 ++ gcc/text-art/ruler.cc | 723 +++++++++++ gcc/text-art/ruler.h | 125 ++ gcc/text-art/selftests.cc | 77 ++ gcc/text-art/selftests.h | 60 + gcc/text-art/style.cc | 632 ++++++++++ gcc/text-art/styled-string.cc | 1107 +++++++++++++++++ gcc/text-art/table.cc | 1272 ++++++++++++++++++++ gcc/text-art/table.h | 262 ++++ gcc/text-art/theme.cc | 183 +++ gcc/text-art/theme.h | 123 ++ gcc/text-art/types.h | 504 ++++++++ gcc/text-art/widget.cc | 275 +++++ gcc/text-art/widget.h | 246 ++++ libcpp/charset.cc | 89 +- libcpp/combining-chars.inc | 68 ++ libcpp/include/cpplib.h | 3 + libcpp/printable-chars.inc | 231 ++++ 50 files changed, 7760 insertions(+), 33 deletions(-) create mode 100755 contrib/unicode/gen-box-drawing-chars.py create mode 100755 contrib/unicode/gen-combining-chars.py create mode 100755 contrib/unicode/gen-printable-chars.py create mode 100644 gcc/diagnostic-diagram.h create mode 100644 gcc/diagnostic-text-art.h create mode 100644 gcc/testsuite/gcc.dg/plugin/diagnostic-test-text-art-ascii-bw.c create mode 100644 gcc/testsuite/gcc.dg/plugin/diagnostic-test-text-art-ascii-color.c create mode 100644 gcc/testsuite/gcc.dg/plugin/diagnostic-test-text-art-none.c create mode 100644 gcc/testsuite/gcc.dg/plugin/diagnostic-test-text-art-unicode-bw.c create mode 100644 gcc/testsuite/gcc.dg/plugin/diagnostic-test-text-art-unicode-color.c create mode 100644 gcc/testsuite/gcc.dg/plugin/diagnostic_plugin_test_text_art.c create mode 100644 gcc/text-art/box-drawing-chars.inc create mode 100644 gcc/text-art/box-drawing.cc create mode 100644 gcc/text-art/box-drawing.h create mode 100644 gcc/text-art/canvas.cc create mode 100644 gcc/text-art/canvas.h create mode 100644 gcc/text-art/ruler.cc create mode 100644 gcc/text-art/ruler.h create mode 100644 gcc/text-art/selftests.cc create mode 100644 gcc/text-art/selftests.h create mode 100644 gcc/text-art/style.cc create mode 100644 gcc/text-art/styled-string.cc create mode 100644 gcc/text-art/table.cc create mode 100644 gcc/text-art/table.h create mode 100644 gcc/text-art/theme.cc create mode 100644 gcc/text-art/theme.h create mode 100644 gcc/text-art/types.h create mode 100644 gcc/text-art/widget.cc create mode 100644 gcc/text-art/widget.h create mode 100644 libcpp/combining-chars.inc create mode 100644 libcpp/printable-chars.inc diff --git a/contrib/unicode/gen-box-drawing-chars.py b/contrib/unicode/gen-box-drawing-chars.py new file mode 100755 index 0000000..9a55266 --- /dev/null +++ b/contrib/unicode/gen-box-drawing-chars.py @@ -0,0 +1,94 @@ +#!/usr/bin/env python3 +# +# Script to generate gcc/text-art/box-drawing-chars.inc +# +# This file is part of GCC. +# +# GCC is free software; you can redistribute it and/or modify it under +# the terms of the GNU General Public License as published by the Free +# Software Foundation; either version 3, or (at your option) any later +# version. +# +# GCC is distributed in the hope that it will be useful, but WITHOUT ANY +# WARRANTY; without even the implied warranty of MERCHANTABILITY or +# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License +# for more details. +# +# You should have received a copy of the GNU General Public License +# along with GCC; see the file COPYING3. If not see +# . */ + +import unicodedata + +def get_box_drawing_char_name(up: bool, + down: bool, + left: bool, + right: bool) -> str: + if 0: + print(f'{locals()=}') + if up and down: + vertical = True + up = False + down = False + else: + vertical = False + + if left and right: + horizontal = True + left = False + right = False + else: + horizontal = False + + weights = [] + heavy = [] + light = [] + dirs = [] + for dir_name in ('up', 'down', 'vertical', 'left', 'right', 'horizontal'): + val = locals()[dir_name] + if val: + dirs.append(dir_name.upper()) + + if not dirs: + return 'SPACE' + + name = 'BOX DRAWINGS' + #print(f'{light=} {heavy=}') + + if 0: + print(dirs) + + def weights_frag(weight: str, dirs: list, prefix: bool): + """ + Generate a fragment where all directions share the same weight, e.g.: + 'HEAVY HORIZONTAL' + 'DOWN LIGHT' + 'LEFT DOWN HEAVY' + 'HEAVY DOWN AND RIGHT' + """ + assert len(dirs) >= 1 + assert len(dirs) <= 2 + if prefix: + return f' {weight} ' + (' AND '.join(dirs)) + else: + return ' ' + (' '.join(dirs)) + f' {weight}' + + assert(len(dirs) >= 1 and len(dirs) <= 2) + name += weights_frag('LIGHT', dirs, True) + + return name + +print('/* Generated by contrib/unicode/gen-box-drawing-chars.py. */') +print() +for i in range(16): + up = (i & 8) + down = (i & 4) + left = (i & 2) + right = (i & 1) + name = get_box_drawing_char_name(up, down, left, right) + if i < 15: + trailing_comma = ',' + else: + trailing_comma = ' ' + unichar = unicodedata.lookup(name) + print(f'0x{ord(unichar):04X}{trailing_comma} /* "{unichar}": U+{ord(unichar):04X}: {name} */') diff --git a/contrib/unicode/gen-combining-chars.py b/contrib/unicode/gen-combining-chars.py new file mode 100755 index 0000000..fb5ef50 --- /dev/null +++ b/contrib/unicode/gen-combining-chars.py @@ -0,0 +1,75 @@ +#!/usr/bin/env python3 +# +# Script to generate libcpp/combining-chars.inc +# +# This file is part of GCC. +# +# GCC is free software; you can redistribute it and/or modify it under +# the terms of the GNU General Public License as published by the Free +# Software Foundation; either version 3, or (at your option) any later +# version. +# +# GCC is distributed in the hope that it will be useful, but WITHOUT ANY +# WARRANTY; without even the implied warranty of MERCHANTABILITY or +# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License +# for more details. +# +# You should have received a copy of the GNU General Public License +# along with GCC; see the file COPYING3. If not see +# . */ + +from pprint import pprint +import unicodedata + +def is_combining_char(code_point) -> bool: + return unicodedata.combining(chr(code_point)) != 0 + +class Range: + def __init__(self, start, end, value): + self.start = start + self.end = end + self.value = value + + def __repr__(self): + return f'Range({self.start:x}, {self.end:x}, {self.value})' + +def make_ranges(value_callback): + ranges = [] + for code_point in range(0x10FFFF): + value = is_combining_char(code_point) + if 0: + print(f'{code_point=:x} {value=}') + if ranges and ranges[-1].value == value: + # Extend current range + ranges[-1].end = code_point + else: + # Start a new range + ranges.append(Range(code_point, code_point, value)) + return ranges + +ranges = make_ranges(is_combining_char) +if 0: + pprint(ranges) + +print(f"/* Generated by contrib/unicode/gen-combining-chars.py") +print(f" using version {unicodedata.unidata_version}" + " of the Unicode standard. */") +print("\nstatic const cppchar_t combining_range_ends[] = {", end="") +for i, r in enumerate(ranges): + if i % 8: + print(" ", end="") + else: + print("\n ", end="") + print("0x%x," % r.end, end="") +print("\n};\n") +print("static const bool is_combining[] = {", end="") +for i, r in enumerate(ranges): + if i % 24: + print(" ", end="") + else: + print("\n ", end="") + if r.value: + print("1,", end="") + else: + print("0,", end="") +print("\n};") diff --git a/contrib/unicode/gen-printable-chars.py b/contrib/unicode/gen-printable-chars.py new file mode 100755 index 0000000..7684c08 --- /dev/null +++ b/contrib/unicode/gen-printable-chars.py @@ -0,0 +1,77 @@ +#!/usr/bin/env python3 +# +# Script to generate libcpp/printable-chars.inc +# +# This file is part of GCC. +# +# GCC is free software; you can redistribute it and/or modify it under +# the terms of the GNU General Public License as published by the Free +# Software Foundation; either version 3, or (at your option) any later +# version. +# +# GCC is distributed in the hope that it will be useful, but WITHOUT ANY +# WARRANTY; without even the implied warranty of MERCHANTABILITY or +# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License +# for more details. +# +# You should have received a copy of the GNU General Public License +# along with GCC; see the file COPYING3. If not see +# . */ + +from pprint import pprint +import unicodedata + +def is_printable_char(code_point) -> bool: + category = unicodedata.category(chr(code_point)) + # "Cc" is "control" and "Cf" is "format" + return category[0] != 'C' + +class Range: + def __init__(self, start, end, value): + self.start = start + self.end = end + self.value = value + + def __repr__(self): + return f'Range({self.start:x}, {self.end:x}, {self.value})' + +def make_ranges(value_callback): + ranges = [] + for code_point in range(0x10FFFF): + value = is_printable_char(code_point) + if 0: + print(f'{code_point=:x} {value=}') + if ranges and ranges[-1].value == value: + # Extend current range + ranges[-1].end = code_point + else: + # Start a new range + ranges.append(Range(code_point, code_point, value)) + return ranges + +ranges = make_ranges(is_printable_char) +if 0: + pprint(ranges) + +print(f"/* Generated by contrib/unicode/gen-printable-chars.py") +print(f" using version {unicodedata.unidata_version}" + " of the Unicode standard. */") +print("\nstatic const cppchar_t printable_range_ends[] = {", end="") +for i, r in enumerate(ranges): + if i % 8: + print(" ", end="") + else: + print("\n ", end="") + print("0x%x," % r.end, end="") +print("\n};\n") +print("static const bool is_printable[] = {", end="") +for i, r in enumerate(ranges): + if i % 24: + print(" ", end="") + else: + print("\n ", end="") + if r.value: + print("1,", end="") + else: + print("0,", end="") +print("\n};") diff --git a/gcc/Makefile.in b/gcc/Makefile.in index 669a2a0..8a7dbf7 100644 --- a/gcc/Makefile.in +++ b/gcc/Makefile.in @@ -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. diff --git a/gcc/color-macros.h b/gcc/color-macros.h index fcd79d0..9688f92 100644 --- a/gcc/color-macros.h +++ b/gcc/color-macros.h @@ -92,6 +92,14 @@ along with GCC; see the file COPYING3. If not see #define COLOR_FG_MAGENTA "35" #define COLOR_FG_CYAN "36" #define COLOR_FG_WHITE "37" +#define COLOR_FG_BRIGHT_BLACK "90" +#define COLOR_FG_BRIGHT_RED "91" +#define COLOR_FG_BRIGHT_GREEN "92" +#define COLOR_FG_BRIGHT_YELLOW "93" +#define COLOR_FG_BRIGHT_BLUE "94" +#define COLOR_FG_BRIGHT_MAGENTA "95" +#define COLOR_FG_BRIGHT_CYAN "96" +#define COLOR_FG_BRIGHT_WHITE "97" #define COLOR_BG_BLACK "40" #define COLOR_BG_RED "41" #define COLOR_BG_GREEN "42" @@ -100,6 +108,14 @@ along with GCC; see the file COPYING3. If not see #define COLOR_BG_MAGENTA "45" #define COLOR_BG_CYAN "46" #define COLOR_BG_WHITE "47" +#define COLOR_BG_BRIGHT_BLACK "100" +#define COLOR_BG_BRIGHT_RED "101" +#define COLOR_BG_BRIGHT_GREEN "102" +#define COLOR_BG_BRIGHT_YELLOW "103" +#define COLOR_BG_BRIGHT_BLUE "104" +#define COLOR_BG_BRIGHT_MAGENTA "105" +#define COLOR_BG_BRIGHT_CYAN "106" +#define COLOR_BG_BRIGHT_WHITE "107" #define SGR_START "\33[" #define SGR_END "m\33[K" #define SGR_SEQ(str) SGR_START str SGR_END diff --git a/gcc/common.opt b/gcc/common.opt index 3daec85..25f650e 100644 --- a/gcc/common.opt +++ b/gcc/common.opt @@ -1502,6 +1502,29 @@ fdiagnostics-show-path-depths Common Var(flag_diagnostics_show_path_depths) Init(0) Show stack depths of events in paths. +fdiagnostics-text-art-charset= +Driver Common Joined RejectNegative Var(flag_diagnostics_text_art_charset) Enum(diagnostic_text_art_charset) Init(DIAGNOSTICS_TEXT_ART_CHARSET_EMOJI) +-fdiagnostics-text-art-charset=[none|ascii|unicode|emoji] Determine which characters to use in text arg diagrams. + +; Required for these enum values. +SourceInclude +diagnostic-text-art.h + +Enum +Name(diagnostic_text_art_charset) Type(int) + +EnumValue +Enum(diagnostic_text_art_charset) String(none) Value(DIAGNOSTICS_TEXT_ART_CHARSET_NONE) + +EnumValue +Enum(diagnostic_text_art_charset) String(ascii) Value(DIAGNOSTICS_TEXT_ART_CHARSET_ASCII) + +EnumValue +Enum(diagnostic_text_art_charset) String(unicode) Value(DIAGNOSTICS_TEXT_ART_CHARSET_UNICODE) + +EnumValue +Enum(diagnostic_text_art_charset) String(emoji) Value(DIAGNOSTICS_TEXT_ART_CHARSET_EMOJI) + fdiagnostics-minimum-margin-width= Common Joined UInteger Var(diagnostics_minimum_margin_width) Init(6) Set minimum width of left margin of source code when showing source. diff --git a/gcc/configure b/gcc/configure index f7b4b28..c99105f 100755 --- a/gcc/configure +++ b/gcc/configure @@ -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 ;; diff --git a/gcc/configure.ac b/gcc/configure.ac index 9c680ec..0428ee4 100644 --- a/gcc/configure.ac +++ b/gcc/configure.ac @@ -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]) diff --git a/gcc/diagnostic-diagram.h b/gcc/diagnostic-diagram.h new file mode 100644 index 0000000..fc923c5 --- /dev/null +++ b/gcc/diagnostic-diagram.h @@ -0,0 +1,51 @@ +/* Support for diagrams within diagnostics. + Copyright (C) 2023 Free Software Foundation, Inc. + Contributed by David Malcolm + +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 +. */ + +#ifndef GCC_DIAGNOSTIC_DIAGRAM_H +#define GCC_DIAGNOSTIC_DIAGRAM_H + +namespace text_art +{ + class canvas; +} // namespace text_art + +/* A text art diagram, along with an "alternative text" string + describing it. */ + +class diagnostic_diagram +{ + public: + diagnostic_diagram (const text_art::canvas &canvas, + const char *alt_text) + : m_canvas (canvas), + m_alt_text (alt_text) + { + gcc_assert (alt_text); + } + + const text_art::canvas &get_canvas () const { return m_canvas; } + const char *get_alt_text () const { return m_alt_text; } + + private: + const text_art::canvas &m_canvas; + const char *const m_alt_text; +}; + +#endif /* ! GCC_DIAGNOSTIC_DIAGRAM_H */ diff --git a/gcc/diagnostic-format-json.cc b/gcc/diagnostic-format-json.cc index 694dddc..539b98b 100644 --- a/gcc/diagnostic-format-json.cc +++ b/gcc/diagnostic-format-json.cc @@ -324,6 +324,15 @@ json_file_final_cb (diagnostic_context *) free (filename); } +/* Callback for diagnostic_context::m_diagrams.m_emission_cb. */ + +static void +json_emit_diagram (diagnostic_context *, + const diagnostic_diagram &) +{ + /* No-op. */ +} + /* Populate CONTEXT in preparation for JSON output (either to stderr, or to a file). */ @@ -340,6 +349,7 @@ diagnostic_output_format_init_json (diagnostic_context *context) context->begin_group_cb = json_begin_group; context->end_group_cb = json_end_group; context->print_path = NULL; /* handled in json_end_diagnostic. */ + context->m_diagrams.m_emission_cb = json_emit_diagram; /* The metadata is handled in JSON format, rather than as text. */ context->show_cwe = false; diff --git a/gcc/diagnostic-format-sarif.cc b/gcc/diagnostic-format-sarif.cc index fd29ac2..ac2f5b8 100644 --- a/gcc/diagnostic-format-sarif.cc +++ b/gcc/diagnostic-format-sarif.cc @@ -29,6 +29,8 @@ along with GCC; see the file COPYING3. If not see #include "cpplib.h" #include "logical-location.h" #include "diagnostic-client-data-hooks.h" +#include "diagnostic-diagram.h" +#include "text-art/canvas.h" class sarif_builder; @@ -66,8 +68,13 @@ public: diagnostic_info *diagnostic, diagnostic_t orig_diag_kind, sarif_builder *builder); + void on_diagram (diagnostic_context *context, + const diagnostic_diagram &diagram, + sarif_builder *builder); private: + void add_related_location (json::object *location_obj); + json::array *m_related_locations_arr; }; @@ -135,7 +142,8 @@ public: void end_diagnostic (diagnostic_context *context, diagnostic_info *diagnostic, diagnostic_t orig_diag_kind); - + void emit_diagram (diagnostic_context *context, + const diagnostic_diagram &diagram); void end_group (); void flush_to_file (FILE *outf); @@ -144,6 +152,9 @@ public: json::object *make_location_object (const rich_location &rich_loc, const logical_location *logical_loc); json::object *make_message_object (const char *msg) const; + json::object * + make_message_object_for_diagram (diagnostic_context *context, + const diagnostic_diagram &diagram); private: sarif_result *make_result_object (diagnostic_context *context, @@ -261,12 +272,6 @@ sarif_result::on_nested_diagnostic (diagnostic_context *context, diagnostic_t /*orig_diag_kind*/, sarif_builder *builder) { - if (!m_related_locations_arr) - { - m_related_locations_arr = new json::array (); - set ("relatedLocations", m_related_locations_arr); - } - /* We don't yet generate meaningful logical locations for notes; sometimes these will related to current_function_decl, but often they won't. */ @@ -277,6 +282,39 @@ sarif_result::on_nested_diagnostic (diagnostic_context *context, pp_clear_output_area (context->printer); location_obj->set ("message", message_obj); + add_related_location (location_obj); +} + +/* Handle diagrams that occur within a diagnostic group. + The closest thing in SARIF seems to be to add a location to the + "releatedLocations" property (SARIF v2.1.0 section 3.27.22), + and to put the diagram into the "message" property of that location + (SARIF v2.1.0 section 3.28.5). */ + +void +sarif_result::on_diagram (diagnostic_context *context, + const diagnostic_diagram &diagram, + sarif_builder *builder) +{ + json::object *location_obj = new json::object (); + json::object *message_obj + = builder->make_message_object_for_diagram (context, diagram); + location_obj->set ("message", message_obj); + + add_related_location (location_obj); +} + +/* Add LOCATION_OBJ to this result's "relatedLocations" array, + creating it if it doesn't yet exist. */ + +void +sarif_result::add_related_location (json::object *location_obj) +{ + if (!m_related_locations_arr) + { + m_related_locations_arr = new json::array (); + set ("relatedLocations", m_related_locations_arr); + } m_related_locations_arr->append (location_obj); } @@ -348,6 +386,18 @@ sarif_builder::end_diagnostic (diagnostic_context *context, } } +/* Implementation of diagnostic_context::m_diagrams.m_emission_cb + for SARIF output. */ + +void +sarif_builder::emit_diagram (diagnostic_context *context, + const diagnostic_diagram &diagram) +{ + /* We must be within the emission of a top-level diagnostic. */ + gcc_assert (m_cur_group_result); + m_cur_group_result->on_diagram (context, diagram, this); +} + /* Implementation of "end_group_cb" for SARIF output. */ void @@ -1115,6 +1165,37 @@ sarif_builder::make_message_object (const char *msg) const return message_obj; } +/* Make a message object (SARIF v2.1.0 section 3.11) for DIAGRAM. + We emit the diagram as a code block within the Markdown part + of the message. */ + +json::object * +sarif_builder::make_message_object_for_diagram (diagnostic_context *context, + const diagnostic_diagram &diagram) +{ + json::object *message_obj = new json::object (); + + /* "text" property (SARIF v2.1.0 section 3.11.8). */ + message_obj->set ("text", new json::string (diagram.get_alt_text ())); + + char *saved_prefix = pp_take_prefix (context->printer); + pp_set_prefix (context->printer, NULL); + + /* "To produce a code block in Markdown, simply indent every line of + the block by at least 4 spaces or 1 tab." + Here we use 4 spaces. */ + diagram.get_canvas ().print_to_pp (context->printer, " "); + pp_set_prefix (context->printer, saved_prefix); + + /* "markdown" property (SARIF v2.1.0 section 3.11.9). */ + message_obj->set ("markdown", + new json::string (pp_formatted_text (context->printer))); + + pp_clear_output_area (context->printer); + + return message_obj; +} + /* Make a multiformatMessageString object (SARIF v2.1.0 section 3.12) for MSG. */ @@ -1630,6 +1711,16 @@ sarif_ice_handler (diagnostic_context *context) fnotice (stderr, "Internal compiler error:\n"); } +/* Callback for diagnostic_context::m_diagrams.m_emission_cb. */ + +static void +sarif_emit_diagram (diagnostic_context *context, + const diagnostic_diagram &diagram) +{ + gcc_assert (the_builder); + the_builder->emit_diagram (context, diagram); +} + /* Populate CONTEXT in preparation for SARIF output (either to stderr, or to a file). */ @@ -1645,6 +1736,7 @@ diagnostic_output_format_init_sarif (diagnostic_context *context) context->end_group_cb = sarif_end_group; context->print_path = NULL; /* handled in sarif_end_diagnostic. */ context->ice_handler_cb = sarif_ice_handler; + context->m_diagrams.m_emission_cb = sarif_emit_diagram; /* The metadata is handled in SARIF format, rather than as text. */ context->show_cwe = false; diff --git a/gcc/diagnostic-text-art.h b/gcc/diagnostic-text-art.h new file mode 100644 index 0000000..a0d8a78 --- /dev/null +++ b/gcc/diagnostic-text-art.h @@ -0,0 +1,49 @@ +/* Copyright (C) 2023 Free Software Foundation, Inc. + Contributed by David Malcolm . + +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 +. */ + +#ifndef GCC_DIAGNOSTIC_TEXT_ART_H +#define GCC_DIAGNOSTIC_TEXT_ART_H + +/* Values for -fdiagnostics-text-art-charset=. */ + +enum diagnostic_text_art_charset +{ + /* No text art diagrams shall be emitted. */ + DIAGNOSTICS_TEXT_ART_CHARSET_NONE, + + /* Use pure ASCII for text art diagrams. */ + DIAGNOSTICS_TEXT_ART_CHARSET_ASCII, + + /* Use ASCII + conservative use of other unicode characters + in text art diagrams. */ + DIAGNOSTICS_TEXT_ART_CHARSET_UNICODE, + + /* Use Emoji. */ + DIAGNOSTICS_TEXT_ART_CHARSET_EMOJI +}; + +const enum diagnostic_text_art_charset DIAGNOSTICS_TEXT_ART_CHARSET_DEFAULT + = DIAGNOSTICS_TEXT_ART_CHARSET_EMOJI; + +extern void +diagnostics_text_art_charset_init (diagnostic_context *context, + enum diagnostic_text_art_charset charset); + + +#endif /* ! GCC_DIAGNOSTIC_TEXT_ART_H */ diff --git a/gcc/diagnostic.cc b/gcc/diagnostic.cc index 0f09308..7c2289f 100644 --- a/gcc/diagnostic.cc +++ b/gcc/diagnostic.cc @@ -35,11 +35,14 @@ along with GCC; see the file COPYING3. If not see #include "diagnostic-metadata.h" #include "diagnostic-path.h" #include "diagnostic-client-data-hooks.h" +#include "diagnostic-text-art.h" +#include "diagnostic-diagram.h" #include "edit-context.h" #include "selftest.h" #include "selftest-diagnostic.h" #include "opts.h" #include "cpplib.h" +#include "text-art/theme.h" #ifdef HAVE_TERMIOS_H # include @@ -244,6 +247,10 @@ diagnostic_initialize (diagnostic_context *context, int n_opts) context->ice_handler_cb = NULL; context->includes_seen = NULL; context->m_client_data_hooks = NULL; + context->m_diagrams.m_theme = NULL; + context->m_diagrams.m_emission_cb = NULL; + diagnostics_text_art_charset_init (context, + DIAGNOSTICS_TEXT_ART_CHARSET_DEFAULT); } /* Maybe initialize the color support. We require clients to do this @@ -320,6 +327,12 @@ diagnostic_finish (diagnostic_context *context) if (context->final_cb) context->final_cb (context); + if (context->m_diagrams.m_theme) + { + delete context->m_diagrams.m_theme; + context->m_diagrams.m_theme = NULL; + } + diagnostic_file_cache_fini (); XDELETEVEC (context->classify_diagnostic); @@ -2174,6 +2187,33 @@ internal_error_no_backtrace (const char *gmsgid, ...) gcc_unreachable (); } + +/* Emit DIAGRAM to CONTEXT, respecting the output format. */ + +void +diagnostic_emit_diagram (diagnostic_context *context, + const diagnostic_diagram &diagram) +{ + if (context->m_diagrams.m_theme == nullptr) + return; + + if (context->m_diagrams.m_emission_cb) + { + context->m_diagrams.m_emission_cb (context, diagram); + return; + } + + /* Default implementation. */ + char *saved_prefix = pp_take_prefix (context->printer); + pp_set_prefix (context->printer, NULL); + /* Use a newline before and after and a two-space indent + to make the diagram stand out a little from the wall of text. */ + pp_newline (context->printer); + diagram.get_canvas ().print_to_pp (context->printer, " "); + pp_newline (context->printer); + pp_set_prefix (context->printer, saved_prefix); + pp_flush (context->printer); +} /* Special case error functions. Most are implemented in terms of the above, or should be. */ @@ -2316,6 +2356,38 @@ diagnostic_output_format_init (diagnostic_context *context, } } +/* Initialize CONTEXT->m_diagrams based on CHARSET. + Specifically, make a text_art::theme object for m_diagrams.m_theme, + (or NULL for "no diagrams"). */ + +void +diagnostics_text_art_charset_init (diagnostic_context *context, + enum diagnostic_text_art_charset charset) +{ + delete context->m_diagrams.m_theme; + switch (charset) + { + default: + gcc_unreachable (); + + case DIAGNOSTICS_TEXT_ART_CHARSET_NONE: + context->m_diagrams.m_theme = NULL; + break; + + case DIAGNOSTICS_TEXT_ART_CHARSET_ASCII: + context->m_diagrams.m_theme = new text_art::ascii_theme (); + break; + + case DIAGNOSTICS_TEXT_ART_CHARSET_UNICODE: + context->m_diagrams.m_theme = new text_art::unicode_theme (); + break; + + case DIAGNOSTICS_TEXT_ART_CHARSET_EMOJI: + context->m_diagrams.m_theme = new text_art::emoji_theme (); + break; + } +} + /* Implementation of diagnostic_path::num_events vfunc for simple_diagnostic_path: simply get the number of events in the vec. */ diff --git a/gcc/diagnostic.h b/gcc/diagnostic.h index 9a51097..00b828f 100644 --- a/gcc/diagnostic.h +++ b/gcc/diagnostic.h @@ -24,6 +24,11 @@ along with GCC; see the file COPYING3. If not see #include "pretty-print.h" #include "diagnostic-core.h" +namespace text_art +{ + class theme; +} // namespace text_art + /* An enum for controlling what units to use for the column number when diagnostics are output, used by the -fdiagnostics-column-unit option. Tabs will be expanded or not according to the value of -ftabstop. The origin @@ -170,6 +175,7 @@ class edit_context; namespace json { class value; } class diagnostic_client_data_hooks; class logical_location; +class diagnostic_diagram; /* This data structure bundles altogether any information relevant to the context of a diagnostic message. */ @@ -417,6 +423,18 @@ struct diagnostic_context Used by SARIF output to give metadata about the client that's producing diagnostics. */ diagnostic_client_data_hooks *m_client_data_hooks; + + /* Support for diagrams. */ + struct + { + /* Theme to use when generating diagrams. + Can be NULL (if text art is disabled). */ + text_art::theme *m_theme; + + /* Callback for emitting diagrams. */ + void (*m_emission_cb) (diagnostic_context *context, + const diagnostic_diagram &diagram); + } m_diagrams; }; inline void @@ -619,4 +637,7 @@ extern bool warning_enabled_at (location_t, int); extern char *get_cwe_url (int cwe); +extern void diagnostic_emit_diagram (diagnostic_context *context, + const diagnostic_diagram &diagram); + #endif /* ! GCC_DIAGNOSTIC_H */ diff --git a/gcc/doc/invoke.texi b/gcc/doc/invoke.texi index 8c17a81..7f76337 100644 --- a/gcc/doc/invoke.texi +++ b/gcc/doc/invoke.texi @@ -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<80>after @end smallexample +@opindex fdiagnostics-text-art-charset +@item -fdiagnostics-text-art-charset=@var{CHARSET} +Some diagnostics can contain ``text art'' diagrams: visualizations created +from text, intended to be viewed in a monospaced font. + +This option selects which characters should be used for printing such +diagrams, if any. @var{CHARSET} is @samp{none}, @samp{ascii}, @samp{unicode}, +or @samp{emoji}. + +The @samp{none} value suppresses the printing of such diagrams. +The @samp{ascii} value will ensure that such diagrams are pure ASCII +(``ASCII art''). The @samp{unicode} value will allow for conservative use of +unicode drawing characters (such as box-drawing characters). The @samp{emoji} +value further adds the possibility of emoji in the output (such as emitting +U+26A0 WARNING SIGN followed by U+FE0F VARIATION SELECTOR-16 to select the +emoji variant of the character). + +The default is @samp{emoji}. + @opindex fdiagnostics-format @item -fdiagnostics-format=@var{FORMAT} Select a different format for printing diagnostics. diff --git a/gcc/gcc.cc b/gcc/gcc.cc index 08bdf28..fdfac0b 100644 --- a/gcc/gcc.cc +++ b/gcc/gcc.cc @@ -46,6 +46,7 @@ compilation is specified by a string called a "spec". */ #include "spellcheck.h" #include "opts-jobserver.h" #include "common/common-target.h" +#include "diagnostic-text-art.h" #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; diff --git a/gcc/opts-common.cc b/gcc/opts-common.cc index 23ddcaa..f0c5f48 100644 --- a/gcc/opts-common.cc +++ b/gcc/opts-common.cc @@ -1068,6 +1068,7 @@ decode_cmdline_options_to_array (unsigned int argc, const char **argv, "-fdiagnostics-color=never", "-fdiagnostics-urls=never", "-fdiagnostics-path-format=separate-events", + "-fdiagnostics-text-art-charset=none" }; const int num_expanded = ARRAY_SIZE (expanded_args); opt_array_len += num_expanded - 1; diff --git a/gcc/opts.cc b/gcc/opts.cc index 86b94d6..3087bda 100644 --- a/gcc/opts.cc +++ b/gcc/opts.cc @@ -35,6 +35,7 @@ along with GCC; see the file COPYING3. If not see #include "version.h" #include "selftest.h" #include "file-prefix-map.h" +#include "diagnostic-text-art.h" /* In this file all option sets are explicit. */ #undef OPTION_SET_P @@ -2887,6 +2888,11 @@ common_handle_option (struct gcc_options *opts, break; } + case OPT_fdiagnostics_text_art_charset_: + diagnostics_text_art_charset_init (dc, + (enum diagnostic_text_art_charset)value); + break; + case OPT_fdiagnostics_parseable_fixits: dc->extra_output_kind = (value ? EXTRA_DIAGNOSTIC_OUTPUT_fixits_v1 diff --git a/gcc/pretty-print.cc b/gcc/pretty-print.cc index 7d29471..3d789a2 100644 --- a/gcc/pretty-print.cc +++ b/gcc/pretty-print.cc @@ -1828,6 +1828,35 @@ pp_string (pretty_printer *pp, const char *str) pp_maybe_wrap_text (pp, str, str + strlen (str)); } +/* Append code point C to the output area of PRETTY-PRINTER, encoding it + as UTF-8. */ + +void +pp_unicode_character (pretty_printer *pp, unsigned c) +{ + static const uchar masks[6] = { 0x00, 0xC0, 0xE0, 0xF0, 0xF8, 0xFC }; + static const uchar limits[6] = { 0x80, 0xE0, 0xF0, 0xF8, 0xFC, 0xFE }; + size_t nbytes; + uchar buf[6], *p = &buf[6]; + + nbytes = 1; + if (c < 0x80) + *--p = c; + else + { + do + { + *--p = ((c & 0x3F) | 0x80); + c >>= 6; + nbytes++; + } + while (c >= 0x3F || (c & limits[nbytes-1])); + *--p = (c | masks[nbytes-1]); + } + + pp_append_r (pp, (const char *)p, nbytes); +} + /* Append the leading N characters of STRING to the output area of PRETTY-PRINTER, quoting in hexadecimal non-printable characters. Setting N = -1 is as if N were set to strlen (STRING). The STRING diff --git a/gcc/pretty-print.h b/gcc/pretty-print.h index 0230a28..369be6e 100644 --- a/gcc/pretty-print.h +++ b/gcc/pretty-print.h @@ -401,6 +401,7 @@ extern void pp_indent (pretty_printer *); extern void pp_newline (pretty_printer *); extern void pp_character (pretty_printer *, int); extern void pp_string (pretty_printer *, const char *); +extern void pp_unicode_character (pretty_printer *, unsigned); extern void pp_write_text_to_stream (pretty_printer *); extern void pp_write_text_as_dot_label_to_stream (pretty_printer *, bool); diff --git a/gcc/selftest-run-tests.cc b/gcc/selftest-run-tests.cc index 915f212..e2fc8f8 100644 --- a/gcc/selftest-run-tests.cc +++ b/gcc/selftest-run-tests.cc @@ -28,6 +28,7 @@ along with GCC; see the file COPYING3. If not see #include "stringpool.h" #include "attribs.h" #include "analyzer/analyzer-selftests.h" +#include "text-art/selftests.h" /* This function needed to be split out from selftest.cc as it references tests from the whole source tree, and so is within @@ -118,6 +119,8 @@ selftest::run_tests () /* Run any lang-specific selftests. */ lang_hooks.run_lang_selftests (); + text_art_tests (); + /* Run the analyzer selftests (if enabled). */ ana::selftest::run_analyzer_selftests (); diff --git a/gcc/testsuite/gcc.dg/plugin/diagnostic-test-text-art-ascii-bw.c b/gcc/testsuite/gcc.dg/plugin/diagnostic-test-text-art-ascii-bw.c new file mode 100644 index 0000000..e4239aa --- /dev/null +++ b/gcc/testsuite/gcc.dg/plugin/diagnostic-test-text-art-ascii-bw.c @@ -0,0 +1,57 @@ +/* { dg-additional-options "-fdiagnostics-text-art-charset=ascii -fdiagnostics-color=never" } */ + +int non_empty; + +/* { dg-begin-multiline-output "" } + + A + B + C + + { dg-end-multiline-output "" } */ + +/* { dg-begin-multiline-output "" } + + ♜ ♞ ♝ ♛ ♚ ♝ ♞ ♜ + ♟ ♟ ♟ ♟ ♟ ♟ ♟ ♟ + + + + + ♙ ♙ ♙ ♙ ♙ ♙ ♙ ♙ + ♖ ♘ ♗ ♕ ♔ ♗ ♘ ♖ + + { dg-end-multiline-output "" } */ + +/* { dg-begin-multiline-output "" } + + +--+ + |🙂| + +--+ + + { dg-end-multiline-output "" } */ +/* { dg-begin-multiline-output "" } + + +-------+-----+---------------+---------------------+-----------------------+-----------------------+ + |Offsets|Octet| 0 | 1 | 2 | 3 | + +-------+-----+-+-+-+-+-+-+-+-+-+-+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ + | Octet | Bit |0|1|2|3|4|5|6|7|8|9|10|11|12|13|14|15|16|17|18|19|20|21|22|23|24|25|26|27|28|29|30|31| + +-------+-----+-+-+-+-+-+-+-+-+-+-+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ + | 0 | 0 |Version| IHL | DSCP | ECN | Total Length | + +-------+-----+-------+-------+---------------+-----+--------+--------------------------------------+ + | 4 | 32 | Identification | Flags | Fragment Offset | + +-------+-----+---------------+---------------------+--------+--------------------------------------+ + | 8 | 64 | Time To Live | Protocol | Header Checksum | + +-------+-----+---------------+---------------------+-----------------------------------------------+ + | 12 | 96 | Source IP Address | + +-------+-----+-------------------------------------------------------------------------------------+ + | 16 | 128 | Destination IP Address | + +-------+-----+-------------------------------------------------------------------------------------+ + | 20 | 160 | | + +-------+-----+ | + | ... | ... | Options | + +-------+-----+ | + | 56 | 448 | | + +-------+-----+-------------------------------------------------------------------------------------+ + + { dg-end-multiline-output "" } */ diff --git a/gcc/testsuite/gcc.dg/plugin/diagnostic-test-text-art-ascii-color.c b/gcc/testsuite/gcc.dg/plugin/diagnostic-test-text-art-ascii-color.c new file mode 100644 index 0000000..0650428 --- /dev/null +++ b/gcc/testsuite/gcc.dg/plugin/diagnostic-test-text-art-ascii-color.c @@ -0,0 +1,58 @@ +/* { dg-additional-options "-fdiagnostics-text-art-charset=ascii -fdiagnostics-color=always" } */ + +int non_empty; + +/* { dg-begin-multiline-output "" } + + A + B + C + + { dg-end-multiline-output "" } */ + +/* { dg-begin-multiline-output "" } + + ♜ ♞ ♝ ♛ ♚ ♝ ♞ ♜  + ♟ ♟ ♟ ♟ ♟ ♟ ♟ ♟  +          +          +          +          + ♙ ♙ ♙ ♙ ♙ ♙ ♙ ♙  + ♖ ♘ ♗ ♕ ♔ ♗ ♘ ♖  + + { dg-end-multiline-output "" } */ + +/* { dg-begin-multiline-output "" } + + +--+ + |🙂| + +--+ + + { dg-end-multiline-output "" } */ + +/* { dg-begin-multiline-output "" } + + +-------+-----+---------------+---------------------+-----------------------+-----------------------+ + |Offsets|Octet| 0 | 1 | 2 | 3 | + +-------+-----+-+-+-+-+-+-+-+-+-+-+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ + | Octet | Bit |0|1|2|3|4|5|6|7|8|9|10|11|12|13|14|15|16|17|18|19|20|21|22|23|24|25|26|27|28|29|30|31| + +-------+-----+-+-+-+-+-+-+-+-+-+-+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ + | 0 | 0 |Version| IHL | DSCP | ECN | Total Length | + +-------+-----+-------+-------+---------------+-----+--------+--------------------------------------+ + | 4 | 32 | Identification | Flags | Fragment Offset | + +-------+-----+---------------+---------------------+--------+--------------------------------------+ + | 8 | 64 | Time To Live | Protocol | Header Checksum | + +-------+-----+---------------+---------------------+-----------------------------------------------+ + | 12 | 96 | Source IP Address | + +-------+-----+-------------------------------------------------------------------------------------+ + | 16 | 128 | Destination IP Address | + +-------+-----+-------------------------------------------------------------------------------------+ + | 20 | 160 | | + +-------+-----+ | + | ... | ... | Options | + +-------+-----+ | + | 56 | 448 | | + +-------+-----+-------------------------------------------------------------------------------------+ + + { dg-end-multiline-output "" } */ diff --git a/gcc/testsuite/gcc.dg/plugin/diagnostic-test-text-art-none.c b/gcc/testsuite/gcc.dg/plugin/diagnostic-test-text-art-none.c new file mode 100644 index 0000000..c8118b4 --- /dev/null +++ b/gcc/testsuite/gcc.dg/plugin/diagnostic-test-text-art-none.c @@ -0,0 +1,5 @@ +/* { dg-additional-options "-fdiagnostics-text-art-charset=none" } */ + +int non_empty; + +/* We expect no output. */ diff --git a/gcc/testsuite/gcc.dg/plugin/diagnostic-test-text-art-unicode-bw.c b/gcc/testsuite/gcc.dg/plugin/diagnostic-test-text-art-unicode-bw.c new file mode 100644 index 0000000..c9f5b36 --- /dev/null +++ b/gcc/testsuite/gcc.dg/plugin/diagnostic-test-text-art-unicode-bw.c @@ -0,0 +1,58 @@ +/* { dg-additional-options "-fdiagnostics-text-art-charset=unicode -fdiagnostics-color=never" } */ + +int non_empty; + +/* { dg-begin-multiline-output "" } + + A + B + C + + { dg-end-multiline-output "" } */ + +/* { dg-begin-multiline-output "" } + + ♜ ♞ ♝ ♛ ♚ ♝ ♞ ♜ + ♟ ♟ ♟ ♟ ♟ ♟ ♟ ♟ + + + + + ♙ ♙ ♙ ♙ ♙ ♙ ♙ ♙ + ♖ ♘ ♗ ♕ ♔ ♗ ♘ ♖ + + { dg-end-multiline-output "" } */ + +/* { dg-begin-multiline-output "" } + + ┌──┐ + │🙂│ + └──┘ + + { dg-end-multiline-output "" } */ + +/* { dg-begin-multiline-output "" } + + ┌───────┬─────┬───────────────┬─────────────────────┬───────────────────────┬───────────────────────┐ + │Offsets│Octet│ 0 │ 1 │ 2 │ 3 │ + ├───────┼─────┼─┬─┬─┬─┬─┬─┬─┬─┼─┬─┬──┬──┬──┬──┬──┬──┼──┬──┬──┬──┬──┬──┬──┬──┼──┬──┬──┬──┬──┬──┬──┬──┤ + │ Octet │ Bit │0│1│2│3│4│5│6│7│8│9│10│11│12│13│14│15│16│17│18│19│20│21│22│23│24│25│26│27│28│29│30│31│ + ├───────┼─────┼─┴─┴─┴─┼─┴─┴─┴─┼─┴─┴──┴──┴──┴──┼──┴──┼──┴──┴──┴──┴──┴──┴──┴──┴──┴──┴──┴──┴──┴──┴──┴──┤ + │ 0 │ 0 │Version│ IHL │ DSCP │ ECN │ Total Length │ + ├───────┼─────┼───────┴───────┴───────────────┴─────┼────────┬──────────────────────────────────────┤ + │ 4 │ 32 │ Identification │ Flags │ Fragment Offset │ + ├───────┼─────┼───────────────┬─────────────────────┼────────┴──────────────────────────────────────┤ + │ 8 │ 64 │ Time To Live │ Protocol │ Header Checksum │ + ├───────┼─────┼───────────────┴─────────────────────┴───────────────────────────────────────────────┤ + │ 12 │ 96 │ Source IP Address │ + ├───────┼─────┼─────────────────────────────────────────────────────────────────────────────────────┤ + │ 16 │ 128 │ Destination IP Address │ + ├───────┼─────┼─────────────────────────────────────────────────────────────────────────────────────┤ + │ 20 │ 160 │ │ + ├───────┼─────┤ │ + │ ... │ ... │ Options │ + ├───────┼─────┤ │ + │ 56 │ 448 │ │ + └───────┴─────┴─────────────────────────────────────────────────────────────────────────────────────┘ + + { dg-end-multiline-output "" } */ diff --git a/gcc/testsuite/gcc.dg/plugin/diagnostic-test-text-art-unicode-color.c b/gcc/testsuite/gcc.dg/plugin/diagnostic-test-text-art-unicode-color.c new file mode 100644 index 0000000..f402836 --- /dev/null +++ b/gcc/testsuite/gcc.dg/plugin/diagnostic-test-text-art-unicode-color.c @@ -0,0 +1,59 @@ +/* { dg-additional-options "-fdiagnostics-text-art-charset=unicode -fdiagnostics-color=always" } */ + +int non_empty; + + +/* { dg-begin-multiline-output "" } + + A + B + C + + { dg-end-multiline-output "" } */ + +/* { dg-begin-multiline-output "" } + + ♜ ♞ ♝ ♛ ♚ ♝ ♞ ♜  + ♟ ♟ ♟ ♟ ♟ ♟ ♟ ♟  +          +          +          +          + ♙ ♙ ♙ ♙ ♙ ♙ ♙ ♙  + ♖ ♘ ♗ ♕ ♔ ♗ ♘ ♖  + + { dg-end-multiline-output "" } */ + +/* { dg-begin-multiline-output "" } + + ┌──┐ + │🙂│ + └──┘ + + { dg-end-multiline-output "" } */ + +/* { dg-begin-multiline-output "" } + + ┌───────┬─────┬───────────────┬─────────────────────┬───────────────────────┬───────────────────────┐ + │Offsets│Octet│ 0 │ 1 │ 2 │ 3 │ + ├───────┼─────┼─┬─┬─┬─┬─┬─┬─┬─┼─┬─┬──┬──┬──┬──┬──┬──┼──┬──┬──┬──┬──┬──┬──┬──┼──┬──┬──┬──┬──┬──┬──┬──┤ + │ Octet │ Bit │0│1│2│3│4│5│6│7│8│9│10│11│12│13│14│15│16│17│18│19│20│21│22│23│24│25│26│27│28│29│30│31│ + ├───────┼─────┼─┴─┴─┴─┼─┴─┴─┴─┼─┴─┴──┴──┴──┴──┼──┴──┼──┴──┴──┴──┴──┴──┴──┴──┴──┴──┴──┴──┴──┴──┴──┴──┤ + │ 0 │ 0 │Version│ IHL │ DSCP │ ECN │ Total Length │ + ├───────┼─────┼───────┴───────┴───────────────┴─────┼────────┬──────────────────────────────────────┤ + │ 4 │ 32 │ Identification │ Flags │ Fragment Offset │ + ├───────┼─────┼───────────────┬─────────────────────┼────────┴──────────────────────────────────────┤ + │ 8 │ 64 │ Time To Live │ Protocol │ Header Checksum │ + ├───────┼─────┼───────────────┴─────────────────────┴───────────────────────────────────────────────┤ + │ 12 │ 96 │ Source IP Address │ + ├───────┼─────┼─────────────────────────────────────────────────────────────────────────────────────┤ + │ 16 │ 128 │ Destination IP Address │ + ├───────┼─────┼─────────────────────────────────────────────────────────────────────────────────────┤ + │ 20 │ 160 │ │ + ├───────┼─────┤ │ + │ ... │ ... │ Options │ + ├───────┼─────┤ │ + │ 56 │ 448 │ │ + └───────┴─────┴─────────────────────────────────────────────────────────────────────────────────────┘ + + { dg-end-multiline-output "" } */ diff --git a/gcc/testsuite/gcc.dg/plugin/diagnostic_plugin_test_text_art.c b/gcc/testsuite/gcc.dg/plugin/diagnostic_plugin_test_text_art.c new file mode 100644 index 0000000..27c341b --- /dev/null +++ b/gcc/testsuite/gcc.dg/plugin/diagnostic_plugin_test_text_art.c @@ -0,0 +1,257 @@ +/* { dg-options "-O" } */ + +/* This plugin exercises the text_art code. */ + +#include "gcc-plugin.h" +#include "config.h" +#include "system.h" +#include "coretypes.h" +#include "plugin-version.h" +#include "diagnostic.h" +#include "diagnostic-diagram.h" +#include "text-art/canvas.h" +#include "text-art/table.h" + +int plugin_is_GPL_compatible; + +using namespace text_art; + +/* Canvas tests. */ + +static void +emit_canvas (const canvas &c, const char *alt_text) +{ + diagnostic_diagram diagram (c, alt_text); + diagnostic_emit_diagram (global_dc, diagram); +} + +static void +test_abc () +{ + style_manager sm; + canvas c (canvas::size_t (3, 3), sm); + c.paint (canvas::coord_t (0, 0), styled_unichar ('A')); + c.paint (canvas::coord_t (1, 1), styled_unichar ('B')); + c.paint (canvas::coord_t (2, 2), styled_unichar ('C')); + emit_canvas (c, "test_abc"); +} + +/* Test of procedural art using 24-bit color: chess starting position. */ + +static void +test_chessboard () +{ + /* With the exception of NONE, these are in order of the chess symbols + in the Unicode Miscellaneous Symbols block. */ + enum class piece { KING, QUEEN, ROOK, BISHOP, KNIGHT, PAWN, NONE }; + enum class color { BLACK, WHITE, NONE }; + + style_manager sm; + + /* We assume double-column chars for the pieces, so allow two canvas + columns per square. */ + canvas canvas (canvas::size_t (16, 8), sm); + + for (int x = 0; x < 8; x++) + for (int y = 0; y < 8; y++) + { + enum piece piece_kind; + enum color piece_color; + switch (y) + { + case 0: + case 7: + switch (x) + { + default: + gcc_unreachable (); + case 0: + piece_kind = piece::ROOK; + break; + case 1: + piece_kind = piece::KNIGHT; + break; + case 2: + piece_kind = piece::BISHOP; + break; + case 3: + piece_kind = piece::QUEEN; + break; + case 4: + piece_kind = piece::KING; + break; + case 5: + piece_kind = piece::BISHOP; + break; + case 6: + piece_kind = piece::KNIGHT; + break; + case 7: + piece_kind = piece::ROOK; + break; + } + piece_color = (y == 0) ? color::BLACK : color::WHITE; + break; + case 1: + case 6: + piece_kind = piece::PAWN; + piece_color = (y == 1) ? color::BLACK : color::WHITE; + break; + default: + piece_kind = piece::NONE; + piece_color = color::NONE; + break; + } + + style s; + const bool white_square = (x + y) % 2 == 0; + if (white_square) + s.m_bg_color = style::color (0xf0, 0xd9, 0xb5); + else + s.m_bg_color = style::color (0xb5, 0x88, 0x63); + switch (piece_color) + { + default: + gcc_unreachable (); + case color::WHITE: + s.m_fg_color = style::color (0xff, 0xff, 0xff); + break; + case color::BLACK: + s.m_fg_color = style::color (0x00, 0x00, 0x00); + break; + case color::NONE: + break; + } + style::id_t style_id = sm.get_or_create_id (s); + + cppchar_t ch; + if (piece_kind == piece::NONE) + ch = ' '; + else + { + const cppchar_t WHITE_KING = 0x2654; + const cppchar_t BLACK_KING = 0x265A; + cppchar_t base ((piece_color == color::WHITE) + ? WHITE_KING : BLACK_KING); + ch = base + ((int)piece_kind - (int)piece::KING); + } + canvas.paint (canvas::coord_t (x * 2, y), + canvas::cell_t (ch, false, style_id)); + canvas.paint (canvas::coord_t (x * 2 + 1, y), + canvas::cell_t (' ', false, style_id)); + } + emit_canvas (canvas, "test_chessboard"); +} + +/* Table tests. */ + +static void +emit_table (const table &table, const style_manager &sm, const char *alt_text) +{ + const text_art::theme *theme = global_dc->m_diagrams.m_theme; + if (!theme) + return; + canvas c (table.to_canvas (*theme, sm)); + emit_canvas (c, alt_text); +} + +static void +test_double_width_chars () +{ + style_manager sm; + table table (table::size_t (1, 1)); + table.set_cell (table::coord_t (0,0), + styled_string ((cppchar_t)0x1f642)); + + emit_table (table, sm, "test_double_width_chars"); +} + +static void +test_ipv4_header () +{ + style_manager sm; + table table (table::size_t (34, 10)); + table.set_cell (table::coord_t (0, 0), styled_string (sm, "Offsets")); + table.set_cell (table::coord_t (1, 0), styled_string (sm, "Octet")); + table.set_cell (table::coord_t (0, 1), styled_string (sm, "Octet")); + for (int octet = 0; octet < 4; octet++) + table.set_cell_span (table::rect_t (table::coord_t (2 + (octet * 8), 0), + table::size_t (8, 1)), + styled_string::from_fmt (sm, nullptr, "%i", octet)); + table.set_cell (table::coord_t (1, 1), styled_string (sm, "Bit")); + for (int bit = 0; bit < 32; bit++) + table.set_cell (table::coord_t (bit + 2, 1), + styled_string::from_fmt (sm, nullptr, "%i", bit)); + for (int word = 0; word < 6; word++) + { + table.set_cell (table::coord_t (0, word + 2), + styled_string::from_fmt (sm, nullptr, "%i", word * 4)); + table.set_cell (table::coord_t (1, word + 2), + styled_string::from_fmt (sm, nullptr, "%i", word * 32)); + } + + table.set_cell (table::coord_t (0, 8), styled_string (sm, "...")); + table.set_cell (table::coord_t (1, 8), styled_string (sm, "...")); + table.set_cell (table::coord_t (0, 9), styled_string (sm, "56")); + table.set_cell (table::coord_t (1, 9), styled_string (sm, "448")); + +#define SET_BITS(FIRST, LAST, NAME) \ + do { \ + const int first = (FIRST); \ + const int last = (LAST); \ + const char *name = (NAME); \ + const int row = first / 32; \ + gcc_assert (last / 32 == row); \ + table::rect_t rect (table::coord_t ((first % 32) + 2, row + 2), \ + table::size_t (last + 1 - first , 1)); \ + table.set_cell_span (rect, styled_string (sm, name)); \ + } while (0) + + SET_BITS (0, 3, "Version"); + SET_BITS (4, 7, "IHL"); + SET_BITS (8, 13, "DSCP"); + SET_BITS (14, 15, "ECN"); + SET_BITS (16, 31, "Total Length"); + + SET_BITS (32 + 0, 32 + 15, "Identification"); + SET_BITS (32 + 16, 32 + 18, "Flags"); + SET_BITS (32 + 19, 32 + 31, "Fragment Offset"); + + SET_BITS (64 + 0, 64 + 7, "Time To Live"); + SET_BITS (64 + 8, 64 + 15, "Protocol"); + SET_BITS (64 + 16, 64 + 31, "Header Checksum"); + + SET_BITS (96 + 0, 96 + 31, "Source IP Address"); + SET_BITS (128 + 0, 128 + 31, "Destination IP Address"); + + table.set_cell_span(table::rect_t (table::coord_t (2, 7), + table::size_t (32, 3)), + styled_string (sm, "Options")); + + emit_table (table, sm, "test_ipv4_header"); +} + +static void +show_diagrams () +{ + test_abc (); + test_chessboard (); + test_double_width_chars (); + test_ipv4_header (); +} + +int +plugin_init (struct plugin_name_args *plugin_info, + struct plugin_gcc_version *version) +{ + const char *plugin_name = plugin_info->base_name; + int argc = plugin_info->argc; + struct plugin_argument *argv = plugin_info->argv; + + if (!plugin_default_version_check (version, &gcc_version)) + return 1; + + show_diagrams (); + + return 0; +} diff --git a/gcc/testsuite/gcc.dg/plugin/plugin.exp b/gcc/testsuite/gcc.dg/plugin/plugin.exp index 4d6304c..60723a2 100644 --- a/gcc/testsuite/gcc.dg/plugin/plugin.exp +++ b/gcc/testsuite/gcc.dg/plugin/plugin.exp @@ -114,6 +114,12 @@ set plugin_test_list [list \ diagnostic-path-format-inline-events-1.c \ diagnostic-path-format-inline-events-2.c \ diagnostic-path-format-inline-events-3.c } \ + { diagnostic_plugin_test_text_art.c \ + diagnostic-test-text-art-none.c \ + diagnostic-test-text-art-ascii-bw.c \ + diagnostic-test-text-art-ascii-color.c \ + diagnostic-test-text-art-unicode-bw.c \ + diagnostic-test-text-art-unicode-color.c } \ { location_overflow_plugin.c \ location-overflow-test-1.c \ location-overflow-test-2.c \ diff --git a/gcc/text-art/box-drawing-chars.inc b/gcc/text-art/box-drawing-chars.inc new file mode 100644 index 0000000..a370255 --- /dev/null +++ b/gcc/text-art/box-drawing-chars.inc @@ -0,0 +1,18 @@ +/* Generated by contrib/unicode/gen-box-drawing-chars.py. */ + +0x0020, /* " ": U+0020: SPACE */ +0x2576, /* "╶": U+2576: BOX DRAWINGS LIGHT RIGHT */ +0x2574, /* "╴": U+2574: BOX DRAWINGS LIGHT LEFT */ +0x2500, /* "─": U+2500: BOX DRAWINGS LIGHT HORIZONTAL */ +0x2577, /* "╷": U+2577: BOX DRAWINGS LIGHT DOWN */ +0x250C, /* "┌": U+250C: BOX DRAWINGS LIGHT DOWN AND RIGHT */ +0x2510, /* "┐": U+2510: BOX DRAWINGS LIGHT DOWN AND LEFT */ +0x252C, /* "┬": U+252C: BOX DRAWINGS LIGHT DOWN AND HORIZONTAL */ +0x2575, /* "╵": U+2575: BOX DRAWINGS LIGHT UP */ +0x2514, /* "└": U+2514: BOX DRAWINGS LIGHT UP AND RIGHT */ +0x2518, /* "┘": U+2518: BOX DRAWINGS LIGHT UP AND LEFT */ +0x2534, /* "┴": U+2534: BOX DRAWINGS LIGHT UP AND HORIZONTAL */ +0x2502, /* "│": U+2502: BOX DRAWINGS LIGHT VERTICAL */ +0x251C, /* "├": U+251C: BOX DRAWINGS LIGHT VERTICAL AND RIGHT */ +0x2524, /* "┤": U+2524: BOX DRAWINGS LIGHT VERTICAL AND LEFT */ +0x253C /* "┼": U+253C: BOX DRAWINGS LIGHT VERTICAL AND HORIZONTAL */ diff --git a/gcc/text-art/box-drawing.cc b/gcc/text-art/box-drawing.cc new file mode 100644 index 0000000..981d0b0 --- /dev/null +++ b/gcc/text-art/box-drawing.cc @@ -0,0 +1,72 @@ +/* Procedural lookup of box drawing characters. + Copyright (C) 2023 Free Software Foundation, Inc. + Contributed by David Malcolm . + +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 +. */ + +#include "config.h" +#include "system.h" +#include "coretypes.h" +#include "text-art/box-drawing.h" +#include "selftest.h" +#include "text-art/selftests.h" + + +/* According to + https://en.wikipedia.org/wiki/Box-drawing_character#Character_code + "DOS line- and box-drawing characters are not ordered in any programmatic + manner, so calculating a particular character shape needs to use a look-up + table. " + Hence this array. */ +static const cppchar_t box_drawing_chars[] = { +#include "text-art/box-drawing-chars.inc" +}; + +cppchar_t +text_art::get_box_drawing_char (directions line_dirs) +{ + const size_t idx = line_dirs.as_index (); + gcc_assert (idx < 16); + return box_drawing_chars[idx]; +} + +#if CHECKING_P + +namespace selftest { + +/* Run all selftests in this file. */ + +void +text_art_box_drawing_cc_tests () +{ + ASSERT_EQ (text_art::get_box_drawing_char + (text_art::directions (false, false, false, false)), + ' '); + ASSERT_EQ (text_art::get_box_drawing_char + (text_art::directions (false, false, true, true)), + 0x2500); /* BOX DRAWINGS LIGHT HORIZONTAL */ + ASSERT_EQ (text_art::get_box_drawing_char + (text_art::directions (true, true, false, false)), + 0x2502); /* BOX DRAWINGS LIGHT VERTICAL */ + ASSERT_EQ (text_art::get_box_drawing_char + (text_art::directions (true, false, true, false)), + 0x2518); /* BOX DRAWINGS LIGHT UP AND LEFT */ +} + +} // namespace selftest + +#endif /* #if CHECKING_P */ diff --git a/gcc/text-art/box-drawing.h b/gcc/text-art/box-drawing.h new file mode 100644 index 0000000..29f4d99 --- /dev/null +++ b/gcc/text-art/box-drawing.h @@ -0,0 +1,32 @@ +/* Procedural lookup of box drawing characters. + Copyright (C) 2023 Free Software Foundation, Inc. + Contributed by David Malcolm . + +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 +. */ + +#ifndef GCC_TEXT_ART_BOX_DRAWING_H +#define GCC_TEXT_ART_BOX_DRAWING_H + +#include "text-art/types.h" + +namespace text_art { + +extern cppchar_t get_box_drawing_char (directions line_dirs); + +} // namespace text_art + +#endif /* GCC_TEXT_ART_BOX_DRAWING_H */ diff --git a/gcc/text-art/canvas.cc b/gcc/text-art/canvas.cc new file mode 100644 index 0000000..f229612 --- /dev/null +++ b/gcc/text-art/canvas.cc @@ -0,0 +1,437 @@ +/* Canvas for random-access procedural text art. + Copyright (C) 2023 Free Software Foundation, Inc. + Contributed by David Malcolm . + +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 +. */ + +#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 */ diff --git a/gcc/text-art/canvas.h b/gcc/text-art/canvas.h new file mode 100644 index 0000000..4954977 --- /dev/null +++ b/gcc/text-art/canvas.h @@ -0,0 +1,74 @@ +/* Canvas for random-access procedural text art. + Copyright (C) 2023 Free Software Foundation, Inc. + Contributed by David Malcolm . + +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 +. */ + +#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 size_t; + typedef coord coord_t; + typedef range range_t; + typedef rect 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 m_cells; + const style_manager &m_style_mgr; +}; + +} // namespace text_art + +#endif /* GCC_TEXT_ART_CANVAS_H */ diff --git a/gcc/text-art/ruler.cc b/gcc/text-art/ruler.cc new file mode 100644 index 0000000..80c623f --- /dev/null +++ b/gcc/text-art/ruler.cc @@ -0,0 +1,723 @@ +/* Classes for printing labelled rulers. + Copyright (C) 2023 Free Software Foundation, Inc. + Contributed by David Malcolm . + +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 +. */ + +#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 */ diff --git a/gcc/text-art/ruler.h b/gcc/text-art/ruler.h new file mode 100644 index 0000000..31f5354 --- /dev/null +++ b/gcc/text-art/ruler.h @@ -0,0 +1,125 @@ +/* Classes for printing labelled rulers. + Copyright (C) 2023 Free Software Foundation, Inc. + Contributed by David Malcolm . + +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 +. */ + +#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