1  /* Copyright 2010-2023 Free Software Foundation, Inc.
       2  
       3     This program is free software: you can redistribute it and/or modify
       4     it under the terms of the GNU General Public License as published by
       5     the Free Software Foundation, either version 3 of the License, or
       6     (at your option) any later version.
       7  
       8     This program is distributed in the hope that it will be useful,
       9     but WITHOUT ANY WARRANTY; without even the implied warranty of
      10     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
      11     GNU General Public License for more details.
      12  
      13     You should have received a copy of the GNU General Public License
      14     along with this program.  If not, see <http://www.gnu.org/licenses/>. */
      15  
      16  #include <config.h>
      17  
      18  #include <stdlib.h>
      19  #include <string.h>
      20  
      21  #include "parser.h"
      22  #include "def.h"
      23  #include "debug.h"
      24  #include "source_marks.h"
      25  
      26  /* Return CURRENT->parent.  The other arguments are used if an error message
      27     should be printed. */
      28  ELEMENT *
      29  close_brace_command (ELEMENT *current,
      30                       enum command_id closed_block_command,
      31                       enum command_id interrupting_command,
      32                       int missing_brace)
      33  {
      34  
      35    KEY_PAIR *k;
      36  
      37    if (command_data(current->cmd).data == BRACE_context)
      38      {
      39        if (current->cmd == CM_math)
      40          {
      41            if (pop_context () != ct_math)
      42              fatal ("math context expected");
      43          }
      44        else if (pop_context () != ct_brace_command)
      45          fatal ("context brace command context expected");
      46        if (current->cmd == CM_footnote)
      47          nesting_context.footnote--;
      48        if (current->cmd == CM_caption || current->cmd == CM_shortcaption)
      49          nesting_context.caption--;
      50      }
      51  
      52    if (command_flags(current) & CF_contain_basic_inline)
      53      (void) pop_command (&nesting_context.basic_inline_stack);
      54  
      55    if (current->cmd != CM_verb)
      56      goto yes;
      57    k = lookup_info (current, "delimiter");
      58    if (!k || !*(char *)k->value)
      59      goto yes;
      60    if (0)
      61      {
      62    yes:
      63        if (closed_block_command)
      64          command_error (current,
      65                          "@end %s seen before @%s closing brace",
      66                          command_name(closed_block_command),
      67                          command_name(current->cmd));
      68        else if (interrupting_command)
      69          command_error (current,
      70                          "@%s seen before @%s closing brace",
      71                          command_name(interrupting_command),
      72                          command_name(current->cmd));
      73        else if (missing_brace)
      74           command_error (current,
      75                          "@%s missing closing brace",
      76                          command_name(current->cmd));
      77      }
      78    else if (missing_brace)
      79      {
      80        command_error (current,
      81                        "@%s missing closing delimiter sequence: %s}",
      82                        command_name(current->cmd),
      83                        (char *)k->value);
      84      }
      85    current = current->parent;
      86    return current;
      87  }
      88  
      89  /* Close out any brace commands that mark text, not allowing multiple
      90     paragraphs. */
      91  ELEMENT *
      92  close_all_style_commands (ELEMENT *current,
      93                            enum command_id closed_block_command,
      94                            enum command_id interrupting_command)
      95  {
      96    while (current->parent
      97           && (command_flags(current->parent) & CF_brace)
      98           && !(command_data(current->parent->cmd).data == BRACE_context))
      99      {
     100        debug ("CLOSING(all_style_commands) @%s",
     101               command_name(current->parent->cmd));
     102        current = close_brace_command (current->parent,
     103                             closed_block_command, interrupting_command, 1);
     104      }
     105  
     106    return current;
     107  }
     108  
     109  int
     110  is_container_empty (ELEMENT *current)
     111  {
     112    if (current->contents.number == 0
     113        && current->args.number == 0
     114        && current->text.end == 0
     115        && current->info_info.info_number == 0)
     116      return 1;
     117    return 0;
     118  }
     119  
     120  /* remove an empty content that only holds source marks */
     121  void
     122  remove_empty_content (ELEMENT *current)
     123  {
     124    if (current->contents.number == 1)
     125      {
     126        ELEMENT *child_element = last_contents_child (current);
     127        if ((!child_element->cmd) && is_container_empty (child_element))
     128          {
     129            transfer_source_marks (child_element, current);
     130  
     131            debug_nonl ("REMOVE empty child ");
     132            debug_print_element (child_element, 0); debug_nonl (" from ");
     133            debug_print_element (current, 0); debug ("");
     134            destroy_element (pop_element_from_contents (current));
     135          }
     136      }
     137  }
     138  
     139  /* this should only be called for non @-command elements otherwise
     140     empty command elements will be removed */
     141  ELEMENT *
     142  close_container (ELEMENT *current)
     143  {
     144    ELEMENT *element_to_remove = 0;
     145  
     146    remove_empty_content (current);
     147  
     148    /* remove element without contents nor associated information */
     149    if (is_container_empty (current))
     150      {
     151        debug_nonl ("CONTAINER EMPTY ");
     152        debug_print_element (current, 1);
     153        debug_nonl (" (%d source marks)",
     154                    current->source_mark_list.number); debug ("");
     155        if (current->source_mark_list.number > 0)
     156          {
     157            /* Keep the element to keep the source mark, but remove some types.
     158              Keep before_item in order not to add empty table definition in
     159              gather_previous_item. */
     160            if (current->type != ET_before_item)
     161              current->type = ET_NONE;
     162          }
     163        else
     164          element_to_remove = current;
     165      }
     166  
     167    current = current->parent;
     168    if (element_to_remove)
     169      {
     170        ELEMENT *last_child = last_contents_child (current);
     171        /* this is to avoid removing empty containers in args,
     172           happens with brace commands not closed at the end of
     173           a manual */
     174        if (last_child == element_to_remove)
     175          {
     176            debug_nonl ("REMOVE empty type ");
     177            debug_print_element (last_child, 1); debug ("");
     178            destroy_element (pop_element_from_contents (current));
     179          }
     180      }
     181    return current;
     182  }
     183  
     184  void
     185  close_command_cleanup (ELEMENT *current)
     186  {
     187    if (!current->cmd)
     188      return;
     189  
     190    if (current->cmd == CM_multitable)
     191      {
     192        int in_head_or_rows = -1, i;
     193        ELEMENT_LIST old_contents = current->contents;
     194  
     195        /* Clear current contents. */
     196        memset (¤t->contents, 0, sizeof (ELEMENT_LIST));
     197  
     198        /* Rearrange the contents of the multitable to collect rows into
     199           ET_multitable_head and ET_multitable_body elements. */
     200        for (i = 0; i < old_contents.number; i++)
     201          {
     202            ELEMENT *row = old_contents.list[i];
     203  
     204            if (counter_value (&count_cells, row) != -1)
     205              counter_pop (&count_cells);
     206  
     207            if (row->type == ET_row)
     208              {
     209                /* Check if we need to open a new container. */
     210                if (contents_child_by_index (row, 0)->cmd == CM_headitem)
     211                  {
     212                    if (in_head_or_rows <= 0)
     213                      {
     214                        add_to_element_contents (current,
     215                                          new_element (ET_multitable_head));
     216                        in_head_or_rows = 1;
     217                      }
     218                  }
     219                else if (contents_child_by_index (row, 0)->cmd == CM_item)
     220                  {
     221                    if (in_head_or_rows == 1 || in_head_or_rows == -1)
     222                      {
     223                        add_to_element_contents (current,
     224                                          new_element (ET_multitable_body));
     225                        in_head_or_rows = 0;
     226                      }
     227                  }
     228  
     229                add_to_element_contents (last_contents_child(current), row);
     230              }
     231            else
     232              {
     233                add_to_element_contents (current, row);
     234                in_head_or_rows = -1;
     235              }
     236          }
     237        free (old_contents.list);
     238  
     239      }
     240    else if (current->cmd == CM_itemize || current->cmd == CM_enumerate)
     241      {
     242        counter_pop (&count_items);
     243      }
     244  
     245    /* Put everything after the last @def*x command in a def_item type
     246       container. */
     247    if (command_data(current->cmd).flags & CF_def
     248        || current->cmd == CM_defblock)
     249      {
     250        gather_def_item (current, 0);
     251      }
     252  
     253    if (current->cmd == CM_table
     254        || current->cmd == CM_ftable
     255        || current->cmd == CM_vtable)
     256      {
     257        if (current->contents.number > 0)
     258          gather_previous_item (current, 0);
     259      }
     260  
     261    /* Block commands that contain @item's - e.g. @multitable, @table,
     262       @itemize. */
     263    if (command_data(current->cmd).flags & CF_blockitem
     264        && current->contents.number > 0)
     265      {
     266        int have_leading_spaces = 0;
     267        ELEMENT *before_item = 0;
     268        if (current->contents.number >= 2
     269            && current->contents.list[0]->type == ET_ignorable_spaces_after_command
     270            && current->contents.list[1]->type == ET_before_item)
     271          {
     272            have_leading_spaces = 1;
     273            before_item = current->contents.list[1];
     274          }
     275        else if (current->contents.number >= 1
     276            && current->contents.list[0]->type == ET_before_item)
     277          {
     278            before_item = current->contents.list[0];
     279          }
     280  
     281        if (before_item)
     282          {
     283            /* Reparent @end from a ET_before_item to the block command */
     284            ELEMENT *e = last_contents_child (before_item);
     285            if (e && e->cmd == CM_end)
     286              {
     287                add_to_element_contents (current,
     288                                       pop_element_from_contents (before_item));
     289              }
     290  
     291            /* Now if the ET_before_item is empty, remove it. */
     292            if (is_container_empty (before_item)
     293                && before_item->source_mark_list.number == 0)
     294              {
     295                destroy_element (remove_from_contents (current,
     296                                                  have_leading_spaces ? 1 : 0));
     297              }
     298            else /* Non-empty ET_before_item */
     299              {
     300                int empty_before_item = 1, i;
     301                /* Check if contents consist soley of @comment's. */
     302                for (i = 0; i < before_item->contents.number; i++)
     303                  {
     304                    enum command_id c = before_item->contents.list[i]->cmd;
     305                    if (c != CM_c && c != CM_comment)
     306                      {
     307                        empty_before_item = 0;
     308                      }
     309                  }
     310  
     311                if (!empty_before_item)
     312                  {
     313                    int empty_format = 1;
     314                    /* Check for an element that could represent an @item in the
     315                       block.  The type of this element will depend on the block 
     316                       command we are in. */
     317                    for (i = 0; i < current->contents.number; i++)
     318                      {
     319                        ELEMENT *e = current->contents.list[i];
     320                        if (e == before_item)
     321                          continue;
     322                        if ((e->cmd != CM_NONE
     323                             && (e->cmd != CM_c && e->cmd != CM_comment
     324                                 && e->cmd != CM_end))
     325                            || (e->type != ET_NONE
     326                                && e->type != ET_ignorable_spaces_after_command))
     327                          {
     328                            empty_format = 0;
     329                            break;
     330                          }
     331                      }
     332  
     333                    if (empty_format)
     334                      command_warn (current, "@%s has text but no @item",
     335                                    command_name(current->cmd));
     336                  }
     337              }
     338          }
     339      }
     340  }
     341  
     342  void
     343  pop_block_command_contexts (enum command_id cmd)
     344  {
     345    if (command_data(cmd).flags & CF_preformatted
     346         || command_data(cmd).data == BLOCK_menu)
     347      {
     348        if (pop_context () != ct_preformatted)
     349          fatal ("preformatted context expected");
     350      }
     351    else if (command_data(cmd).data == BLOCK_format_raw)
     352      {
     353        if (pop_context () != ct_rawpreformatted)
     354          fatal ("rawpreformatted context expected");
     355      }
     356    else if (cmd == CM_displaymath)
     357      {
     358        if (pop_context () != ct_math)
     359          fatal ("math context expected");
     360      }
     361    else if (command_data(cmd).data == BLOCK_region)
     362      {
     363        (void) pop_command (&nesting_context.regions_stack);
     364      }
     365  }
     366  
     367  void
     368  close_ignored_block_conditional (ELEMENT *current)
     369  {
     370    SOURCE_MARK *source_mark
     371      = new_source_mark (SM_type_ignored_conditional_block);
     372    ELEMENT *conditional = pop_element_from_contents (current);
     373  
     374    conditional->parent = 0;
     375    source_mark->element = conditional;
     376    register_source_mark (current, source_mark);
     377  }
     378  
     379  ELEMENT *
     380  close_current (ELEMENT *current,
     381                 enum command_id closed_block_command,
     382                 enum command_id interrupting_command)
     383  {
     384    /* Element is a command */
     385    if (current->cmd)
     386      {
     387        enum command_id cmd = current->cmd;
     388        debug ("CLOSING(close_current) @%s", command_name(cmd));
     389        if (command_flags(current) & CF_brace)
     390          {
     391            current = close_brace_command (current, closed_block_command,
     392                                           interrupting_command, 1);
     393          }
     394        else if (command_flags(current) & CF_block)
     395          {
     396            if (closed_block_command)
     397              {
     398                line_error ("`@end' expected `%s', but saw `%s'",
     399                            command_name(cmd),
     400                            command_name(closed_block_command));
     401              }
     402            else if (interrupting_command)
     403              {
     404                line_error ("@%s seen before @end %s",
     405                            command_name(interrupting_command),
     406                            command_name(cmd));
     407              }
     408            else
     409              {
     410                line_error ("no matching `@end %s'",
     411                            command_name(cmd));
     412  
     413              }
     414            pop_block_command_contexts (cmd);
     415            current = current->parent;
     416            /* In ignored conditional. */
     417            if (command_data(cmd).data == BLOCK_conditional)
     418              close_ignored_block_conditional (current);
     419          }
     420        else
     421          {
     422            /* @item and @tab commands are closed here, as well as line commands 
     423               with invalid content. */
     424            current = current->parent;
     425          }
     426      }
     427    else if (current->type != ET_NONE)
     428      {
     429        ELEMENT *close_brace;
     430  
     431        debug ("CLOSING type %s", element_type_name (current));
     432  
     433        switch (current->type)
     434          {
     435          case ET_balanced_braces:
     436            close_brace = new_element (ET_NONE);
     437            command_error (current, "misplaced {");
     438            /* We prefer adding an element to merging because we may
     439               be at the end of the document after an empty line we
     440               do not want to modify */
     441            /* current = merge_text (current, "}", 0); */
     442            text_append (&close_brace->text, "}");
     443            add_to_element_contents (current, close_brace);
     444            current = current->parent;
     445            break;
     446          case ET_bracketed_arg:
     447            command_error (current, "misplaced {");
     448            if (current->contents.number > 0
     449                && current->contents.list[0]->type
     450                   == ET_internal_spaces_before_argument)
     451              {
     452                /* remove spaces element from tree and update extra values */
     453                abort_empty_line (¤t, 0);
     454              }
     455            current = current->parent;
     456            break;
     457          case ET_line_arg:
     458            current = end_line_misc_line (current);
     459            break;
     460          case ET_block_line_arg:
     461            current = end_line_starting_block (current);
     462            break;
     463          default:
     464            current = close_container (current);
     465            break;
     466          }
     467      }
     468    else
     469      {
     470        /* should never get here */
     471        if (current->parent)
     472          current = current->parent;
     473      }
     474  
     475    return current;
     476  }
     477  
     478  /* Return lowest level ancestor of CURRENT containing a CLOSED_BLOCK_COMMAND
     479     element, or the lowest level ancestor if CLOSED_BLOCK_COMMAND is 0.
     480     Set CLOSED_BLOCK_ELEMENT to the last closed element.  INTERRUPTING is used in 
     481     close_brace_command to display an error message.  Remove a context from 
     482     context stack if CLOSED_BLOCK_COMMAND is not 0 and a context was added
     483     by the CLOSED_BLOCK_COMMAND.
     484     CLOSED_BLOCK_COMMAND should be the id of a block command.
     485   */
     486  ELEMENT *
     487  close_commands (ELEMENT *current, enum command_id closed_block_command,
     488                  ELEMENT **closed_element, enum command_id interrupting)
     489  {
     490    *closed_element = 0;
     491    current = end_paragraph (current, closed_block_command, interrupting);
     492    current = end_preformatted (current, closed_block_command, interrupting);
     493  
     494    while (current->parent
     495           && (!closed_block_command || current->cmd != closed_block_command)
     496       /* Stop if in a root command. */
     497           && !(current->cmd && command_flags(current) & CF_root)
     498       /* Stop if at a type at the root */
     499           && !(current->type == ET_before_node_section))
     500      {
     501        close_command_cleanup (current);
     502        current = close_current (current, closed_block_command, interrupting);
     503      }
     504  
     505    if (closed_block_command && current->cmd == closed_block_command)
     506      {
     507        pop_block_command_contexts (current->cmd);
     508        *closed_element = current;
     509        current = current->parent;
     510  
     511        if (command_data((*closed_element)->cmd).data == BLOCK_conditional)
     512          /* In ignored conditional. */
     513          close_ignored_block_conditional (current);
     514      }
     515    else
     516      {
     517        if (closed_block_command)
     518          line_error ("unmatched `@end %s'", command_name(closed_block_command));
     519        if (! ((current->cmd && command_flags(current) & CF_root)
     520               || (current->type == ET_before_node_section)
     521               || (current->type == ET_root_line)
     522               || (current->type == ET_document_root)))
     523          {
     524            debug_nonl ("close_commands unexpectedly stopped ");
     525            debug_print_element (current, 1); debug ("");
     526          }
     527      }
     528  
     529    return current;
     530  }