| /* Counterexample derivation trees |
| |
| Copyright (C) 2020-2021 Free Software Foundation, Inc. |
| |
| This file is part of Bison, the GNU Compiler Compiler. |
| |
| This program 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 of the License, or |
| (at your option) any later version. |
| |
| This program 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 this program. If not, see <https://www.gnu.org/licenses/>. */ |
| |
| #include <config.h> |
| |
| #include "derivation.h" |
| #include "glyphs.h" |
| |
| #include <c-ctype.h> |
| #include <gl_linked_list.h> |
| #include <mbswidth.h> |
| #include <vasnprintf.h> |
| |
| #include "system.h" |
| #include "complain.h" |
| |
| struct derivation |
| { |
| symbol_number sym; |
| derivation_list children; |
| int reference_count; |
| // The rule SYM -> CHILDREN. |
| const rule *rule; |
| // Color assigned for styling. Guarantees that the derivation is |
| // always displayed with the same color, independently of the order |
| // in which the derivations are traversed. |
| int color; |
| }; |
| |
| static derivation d_dot = { -1, NULL, -1, NULL, -1 }; |
| |
| derivation * |
| derivation_dot (void) |
| { |
| return &d_dot; |
| } |
| |
| void |
| derivation_list_append (derivation_list dl, derivation *d) |
| { |
| derivation_retain (d); |
| gl_list_add_last (dl, d); |
| } |
| |
| void |
| derivation_list_prepend (derivation_list dl, derivation *d) |
| { |
| derivation_retain (d); |
| gl_list_add_first (dl, d); |
| } |
| |
| void derivation_list_free (derivation_list dl) |
| { |
| derivation *d = NULL; |
| for (gl_list_iterator_t it = gl_list_iterator (dl); |
| derivation_list_next (&it, &d); |
| ) |
| if (d != &d_dot) |
| derivation_free (d); |
| gl_list_free (dl); |
| } |
| |
| derivation * |
| derivation_new (symbol_number sym, derivation_list children, |
| const rule *r) |
| { |
| derivation *res = xmalloc (sizeof *res); |
| res->sym = sym; |
| res->children = children; |
| res->reference_count = 0; |
| res->rule = r; |
| res->color = -1; |
| return res; |
| } |
| |
| void |
| derivation_retain (derivation *d) |
| { |
| ++d->reference_count; |
| } |
| |
| void |
| derivation_free (derivation *d) |
| { |
| if (!d) |
| return; |
| derivation_list free_queue = |
| gl_list_create (GL_LINKED_LIST, NULL, NULL, NULL, true, |
| 1, (const void **)&d); |
| while (gl_list_size (free_queue) > 0) |
| { |
| derivation *deriv = (derivation *) gl_list_get_at (free_queue, 0); |
| if (--deriv->reference_count == 0) |
| { |
| if (deriv->children) |
| { |
| derivation *child = NULL; |
| for (gl_list_iterator_t it = gl_list_iterator (deriv->children); |
| derivation_list_next (&it, &child); |
| ) |
| if (child != &d_dot) |
| gl_list_add_last (free_queue, child); |
| gl_list_free (deriv->children); |
| } |
| free (deriv); |
| } |
| gl_list_remove_at (free_queue, 0); |
| } |
| gl_list_free (free_queue); |
| } |
| |
| size_t |
| derivation_size (const derivation *deriv) |
| { |
| if (!deriv->children) |
| return 1; |
| int size = 1; |
| derivation *child = NULL; |
| for (gl_list_iterator_t it = gl_list_iterator (deriv->children); |
| derivation_list_next (&it, &child); |
| ) |
| size += derivation_size (child); |
| return size; |
| } |
| |
| |
| // Longest distance from root to leaf. |
| static int |
| derivation_depth (const derivation *deriv) |
| { |
| if (deriv->children) |
| { |
| // Children's depth cannot be 0, even if there are no children |
| // (the case of a derivation with an empty RHS). |
| int res = 1; |
| derivation *child; |
| for (gl_list_iterator_t it = gl_list_iterator (deriv->children); |
| derivation_list_next (&it, &child); |
| ) |
| res = max_int (res, derivation_depth (child)); |
| return res + 1; |
| } |
| else |
| return 1; |
| } |
| |
| static bool |
| all_spaces (const char *s) |
| { |
| while (c_isspace (*s)) |
| s++; |
| return *s == '\0'; |
| } |
| |
| // Printing the derivation as trees without trailing spaces is |
| // painful: we cannot simply pad one "column" before moving to the |
| // next: |
| // |
| // exp |
| // ↳ x1 e1 foo1 x1 |
| // ↳ x2 ↳ ε ↳ foo2 ↳ x2 |
| // ↳ x3 ↳ foo3 ↳ x3 |
| // ↳ "X" • ↳ x1 foo4 ↳ "X" |
| // ↳ x2 ↳ "quuux" |
| // ↳ x3 |
| // ↳ "X" |
| // |
| // It's hard for a column to know that it's "last" to decide whether |
| // to output the right-padding or not. So when we need to pad on the |
| // right to complete a column, we don't output the spaces, we |
| // accumulate the width of padding in *PADDING. |
| // |
| // Each time we actually print something (non space), we flush that |
| // padding. When we _don't_ print something, its width is added to |
| // the current padding. |
| // |
| // This function implements this. |
| // |
| // When COND is true, put S on OUT, preceded by *PADDING white spaces. |
| // Otherwise add the width to *PADDING. Return the width of S. |
| static int |
| fputs_if (bool cond, FILE *out, int *padding, const char *s) |
| { |
| int res = mbswidth (s, 0); |
| if (cond && !all_spaces (s)) |
| { |
| fprintf (out, "%*s%s", *padding, "", s); |
| *padding = 0; |
| } |
| else |
| { |
| *padding += res; |
| } |
| return res; |
| } |
| |
| static int |
| fprintf_if (bool cond, FILE *out, int *padding, const char *fmt, ...) |
| { |
| char buf[256]; |
| size_t len = sizeof (buf); |
| va_list args; |
| va_start (args, fmt); |
| char *cp = vasnprintf (buf, &len, fmt, args); |
| va_end (args); |
| if (!cp) |
| xalloc_die (); |
| int res = fputs_if (cond, out, padding, cp); |
| if (cp != buf) |
| free (cp); |
| return res; |
| } |
| |
| // The width taken to report this derivation recursively down to its |
| // leaves. |
| static int |
| derivation_width (const derivation *deriv) |
| { |
| if (deriv->children) |
| { |
| const symbol *sym = symbols[deriv->sym]; |
| int self_width = mbswidth (sym->tag, 0); |
| |
| // Arrow and space. |
| int children_width = down_arrow_width; |
| children_width += snprintf (NULL, 0, "%d: ", deriv->rule->number); |
| if (gl_list_size (deriv->children) == 0) |
| // Empty rhs. |
| children_width += empty_width; |
| else |
| { |
| if (gl_list_size (deriv->children) == 1 |
| && gl_list_get_first (deriv->children) == &d_dot) |
| { |
| children_width += empty_width; |
| children_width += derivation_separator_width; |
| } |
| |
| derivation *child; |
| for (gl_list_iterator_t it = gl_list_iterator (deriv->children); |
| derivation_list_next (&it, &child); |
| ) |
| children_width |
| += derivation_separator_width + derivation_width (child); |
| // No separator at the beginning. |
| children_width -= derivation_separator_width; |
| } |
| return max_int (self_width, children_width); |
| } |
| else if (deriv == &d_dot) |
| { |
| return dot_width; |
| } |
| else // leaf. |
| { |
| const symbol *sym = symbols[deriv->sym]; |
| return mbswidth (sym->tag, 0); |
| } |
| } |
| |
| |
| // Print DERIV for DEPTH. |
| // |
| // The tree is printed from top to bottom with DEPTH ranging from 0 to |
| // the total depth of the tree. DERIV should only printed when we |
| // reach its depth, i.e., then DEPTH is 0. |
| // |
| // When DEPTH is 1 and we're on a subderivation, then we print the RHS |
| // of the derivation (in DEPTH 0 we printed its LHS). |
| // |
| // Return the "logical printed" width. We might have not have reached |
| // that width, in which case the missing spaces are in *PADDING. |
| static int |
| derivation_print_tree_impl (const derivation *deriv, FILE *out, |
| int depth, int *padding) |
| { |
| const int width = derivation_width (deriv); |
| |
| int res = 0; |
| if (deriv->children) |
| { |
| const symbol *sym = symbols[deriv->sym]; |
| char style[20]; |
| snprintf (style, 20, "cex-%d", deriv->color); |
| |
| if (depth == 0 || depth == 1) |
| { |
| begin_use_class (style, out); |
| begin_use_class ("cex-step", out); |
| } |
| if (depth == 0) |
| { |
| res += fputs_if (true, out, padding, sym->tag); |
| } |
| else |
| { |
| res += fputs_if (depth == 1, out, padding, down_arrow); |
| res += fprintf_if (depth == 1, out, padding, "%d: ", deriv->rule->number); |
| if (gl_list_size (deriv->children) == 0) |
| // Empty rhs. |
| res += fputs_if (depth == 1, out, padding, empty); |
| else |
| { |
| if (gl_list_size (deriv->children) == 1 |
| && gl_list_get_first (deriv->children) == &d_dot) |
| { |
| res += fputs_if (depth == 1, out, padding, empty); |
| res += fputs_if (depth == 1, out, padding, derivation_separator); |
| } |
| |
| bool first = true; |
| derivation *child; |
| for (gl_list_iterator_t it = gl_list_iterator (deriv->children); |
| derivation_list_next (&it, &child); |
| ) |
| { |
| if (!first) |
| res += fputs_if (depth == 1, out, padding, derivation_separator); |
| res += derivation_print_tree_impl (child, out, depth - 1, padding); |
| first = false; |
| } |
| } |
| } |
| if (depth == 0 || depth == 1) |
| { |
| end_use_class ("cex-step", out); |
| end_use_class (style, out); |
| } |
| *padding += width - res; |
| res = width; |
| } |
| else if (deriv == &d_dot) |
| { |
| if (depth == 0) |
| begin_use_class ("cex-dot", out); |
| res += fputs_if (depth == 0, out, padding, dot); |
| if (depth == 0) |
| end_use_class ("cex-dot", out); |
| } |
| else // leaf. |
| { |
| const symbol *sym = symbols[deriv->sym]; |
| if (depth == 0) |
| begin_use_class ("cex-leaf", out); |
| res += fputs_if (depth == 0, out, padding, sym->tag); |
| if (depth == 0) |
| end_use_class ("cex-leaf", out); |
| } |
| return res; |
| } |
| |
| static void |
| derivation_print_tree (const derivation *deriv, FILE *out, const char *prefix) |
| { |
| fputc ('\n', out); |
| for (int depth = 0, max_depth = derivation_depth (deriv); |
| depth < max_depth; ++depth) |
| { |
| int padding = 0; |
| fprintf (out, " %s", prefix); |
| derivation_print_tree_impl (deriv, out, depth, &padding); |
| fputc ('\n', out); |
| } |
| } |
| |
| |
| /* Print DERIV, colored according to COUNTER. |
| Return false if nothing is printed. */ |
| static bool |
| derivation_print_flat_impl (derivation *deriv, FILE *out, |
| bool leaves_only, |
| int *counter, const char *prefix) |
| { |
| if (deriv->children) |
| { |
| const symbol *sym = symbols[deriv->sym]; |
| deriv->color = *counter; |
| ++*counter; |
| char style[20]; |
| snprintf (style, 20, "cex-%d", deriv->color); |
| begin_use_class (style, out); |
| |
| if (!leaves_only) |
| { |
| fputs (prefix, out); |
| begin_use_class ("cex-step", out); |
| fprintf (out, "%s %s [ ", sym->tag, arrow); |
| end_use_class ("cex-step", out); |
| prefix = ""; |
| } |
| bool res = false; |
| derivation *child; |
| for (gl_list_iterator_t it = gl_list_iterator (deriv->children); |
| derivation_list_next (&it, &child); |
| ) |
| { |
| if (derivation_print_flat_impl (child, out, |
| leaves_only, counter, prefix)) |
| { |
| prefix = " "; |
| res = true; |
| } |
| else if (!leaves_only) |
| prefix = " "; |
| } |
| if (!leaves_only) |
| { |
| begin_use_class ("cex-step", out); |
| if (res) |
| fputs (" ]", out); |
| else |
| fputs ("]", out); |
| end_use_class ("cex-step", out); |
| } |
| end_use_class (style, out); |
| return res; |
| } |
| else if (deriv == &d_dot) |
| { |
| fputs (prefix, out); |
| begin_use_class ("cex-dot", out); |
| fputs (dot, out); |
| end_use_class ("cex-dot", out); |
| } |
| else // leaf. |
| { |
| fputs (prefix, out); |
| const symbol *sym = symbols[deriv->sym]; |
| begin_use_class ("cex-leaf", out); |
| fprintf (out, "%s", sym->tag); |
| end_use_class ("cex-leaf", out); |
| } |
| return true; |
| } |
| |
| static void |
| derivation_print_flat (const derivation *deriv, FILE *out, const char *prefix) |
| { |
| int counter = 0; |
| fputs (prefix, out); |
| derivation_print_flat_impl ((derivation *)deriv, out, false, &counter, ""); |
| fputc ('\n', out); |
| } |
| |
| void |
| derivation_print_leaves (const derivation *deriv, FILE *out) |
| { |
| int counter = 0; |
| derivation_print_flat_impl ((derivation *)deriv, out, true, &counter, ""); |
| fputc ('\n', out); |
| } |
| |
| void |
| derivation_print (const derivation *deriv, FILE *out, const char *prefix) |
| { |
| if (getenv ("YYFLAT")) |
| derivation_print_flat (deriv, out, prefix); |
| else |
| derivation_print_tree (deriv, out, prefix); |
| } |