1 /* Copyright 2010-2019 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 
23 /* Possibly print an error message, and return CURRENT->parent. */
24 static ELEMENT *
close_brace_command(ELEMENT * current,enum command_id closed_command,enum command_id interrupting_command)25 close_brace_command (ELEMENT *current,
26                      enum command_id closed_command,
27                      enum command_id interrupting_command)
28 {
29 
30   KEY_PAIR *k;
31 
32   if (current->cmd != CM_verb)
33     goto yes;
34   k = lookup_extra (current, "delimiter");
35   if (!k || !*(char *)k->value)
36     goto yes;
37   if (0)
38     {
39 yes:
40       if (closed_command)
41         command_error (current,
42                         "@end %s seen before @%s closing brace",
43                         command_name(closed_command),
44                         command_name(current->cmd));
45       else if (interrupting_command)
46         command_error (current,
47                         "@%s seen before @%s closing brace",
48                         command_name(interrupting_command),
49                         command_name(current->cmd));
50       else
51         command_error (current,
52                         "@%s missing closing brace",
53                         command_name(current->cmd));
54     }
55   else
56     {
57       command_error (current,
58                       "@%s missing closing delimiter sequence: %s}",
59                       command_name(current->cmd),
60                       (char *)k->value);
61     }
62   current = current->parent;
63   return current;
64 }
65 
66 /* Close out any brace commands that mark text, not allowing multiple
67    paragraphs. */
68 ELEMENT *
close_all_style_commands(ELEMENT * current,enum command_id closed_command,enum command_id interrupting_command)69 close_all_style_commands (ELEMENT *current,
70                           enum command_id closed_command,
71                           enum command_id interrupting_command)
72 {
73   while (current->parent
74          && (command_flags(current->parent) & CF_brace)
75          && !(command_data(current->parent->cmd).data == BRACE_context))
76     current = close_brace_command (current->parent,
77                                    closed_command, interrupting_command);
78 
79   return current;
80 }
81 
82 void
close_command_cleanup(ELEMENT * current)83 close_command_cleanup (ELEMENT *current)
84 {
85   if (!current->cmd)
86     return;
87 
88   if (current->cmd == CM_multitable)
89     {
90       int in_head_or_rows = -1, i;
91       ELEMENT_LIST old_contents = current->contents;
92 
93       /* Clear current contents. */
94       memset (&current->contents, 0, sizeof (ELEMENT_LIST));
95 
96       /* Rearrange the contents of the multitable to collect rows into
97          ET_multitable_head and ET_multitable_body elements. */
98       for (i = 0; i < old_contents.number; i++)
99         {
100           ELEMENT *row = old_contents.list[i];
101 
102           if (counter_value (&count_cells, row) != -1)
103             counter_pop (&count_cells);
104 
105           if (row->type == ET_row)
106             {
107               /* Check if we need to open a new container. */
108               if (contents_child_by_index (row, 0)->cmd == CM_headitem)
109                 {
110                   if (in_head_or_rows <= 0)
111                     {
112                       add_to_element_contents (current,
113                                         new_element (ET_multitable_head));
114                       in_head_or_rows = 1;
115                     }
116                 }
117               else if (contents_child_by_index (row, 0)->cmd == CM_item)
118                 {
119                   if (in_head_or_rows == 1 || in_head_or_rows == -1)
120                     {
121                       add_to_element_contents (current,
122                                         new_element (ET_multitable_body));
123                       in_head_or_rows = 0;
124                     }
125                 }
126 
127               add_to_element_contents (last_contents_child(current), row);
128             }
129           else
130             {
131               add_to_element_contents (current, row);
132               in_head_or_rows = -1;
133             }
134         }
135       free (old_contents.list);
136 
137     }
138   else if (current->cmd == CM_itemize || current->cmd == CM_enumerate)
139     {
140       counter_pop (&count_items);
141     }
142 
143   /* Put everything after the last @def*x command in a def_item type
144      container. */
145   if (command_data(current->cmd).flags & CF_def)
146     {
147       gather_def_item (current, 0);
148     }
149 
150   if (current->cmd == CM_table
151       || current->cmd == CM_ftable
152       || current->cmd == CM_vtable)
153     {
154       if (current->contents.number > 0)
155         gather_previous_item (current, 0);
156     }
157 
158   /* Block commands that contain @item's - e.g. @multitable, @table,
159      @itemize. */
160   if (command_data(current->cmd).flags & CF_blockitem
161       && current->contents.number > 0)
162     {
163       int have_leading_spaces = 0;
164       ELEMENT *before_item = 0;
165       if (current->contents.number >= 2
166           && current->contents.list[0]->type == ET_empty_line_after_command
167           && current->contents.list[1]->type == ET_before_item)
168         {
169           have_leading_spaces = 1;
170           before_item = current->contents.list[1];
171         }
172       else if (current->contents.number >= 1
173           && current->contents.list[0]->type == ET_before_item)
174         {
175           before_item = current->contents.list[0];
176         }
177 
178       if (before_item)
179         {
180           /* Reparent @end from a ET_before_item to the block command */
181           KEY_PAIR *k = lookup_extra (current, "end_command");
182           ELEMENT *e = k ? k->value : 0;
183           if (k && last_contents_child (before_item)
184               && last_contents_child (before_item) == e)
185             {
186               add_to_element_contents (current,
187                                      pop_element_from_contents (before_item));
188             }
189 
190           /* Now if the ET_before_item is empty, remove it. */
191           if (before_item->contents.number == 0)
192             {
193               destroy_element (remove_from_contents (current,
194                                                 have_leading_spaces ? 1 : 0));
195             }
196           else /* Non-empty ET_before_item */
197             {
198               int empty_before_item = 1, i;
199               /* Check if contents consist soley of @comment's. */
200               for (i = 0; i < before_item->contents.number; i++)
201                 {
202                   enum command_id c = before_item->contents.list[i]->cmd;
203                   if (c != CM_c && c != CM_comment)
204                     {
205                       empty_before_item = 0;
206                     }
207                 }
208 
209               if (!empty_before_item)
210                 {
211                   int empty_format = 1;
212                   /* Check for an element that could represent an @item in the
213                      block.  The type of this element will depend on the block
214                      command we are in. */
215                   for (i = 0; i < current->contents.number; i++)
216                     {
217                       ELEMENT *e = current->contents.list[i];
218                       if (e == before_item)
219                         continue;
220                       if (e->cmd != CM_NONE
221                           && (e->cmd != CM_c && e->cmd != CM_comment
222                               && e->cmd != CM_end)
223                           || e->type != ET_NONE
224                           && e->type != ET_empty_line_after_command)
225                         {
226                           empty_format = 0;
227                           break;
228                         }
229                     }
230 
231                   if (empty_format)
232                     command_warn (current, "@%s has text but no @item",
233                                   command_name(current->cmd));
234                 }
235             }
236         }
237     }
238 }
239 
240 ELEMENT *
close_current(ELEMENT * current,enum command_id closed_command,enum command_id interrupting_command)241 close_current (ELEMENT *current,
242                enum command_id closed_command,
243                enum command_id interrupting_command)
244 {
245   /* Element is a command */
246   if (current->cmd)
247     {
248       debug ("CLOSING (close_current) %s", command_name(current->cmd));
249       if (command_flags(current) & CF_brace)
250         {
251           if (command_data(current->cmd).data == BRACE_context)
252             pop_context ();
253           current = close_brace_command (current,
254                                          closed_command, interrupting_command);
255         }
256       else if (command_flags(current) & CF_block)
257         {
258           enum command_id cmd = current->cmd;
259           ELEMENT *parent = 0;
260           if (closed_command)
261             {
262               line_error ("`@end' expected `%s', but saw `%s'",
263                           command_name(current->cmd),
264                           command_name(closed_command));
265             }
266           else if (interrupting_command)
267             {
268               line_error ("@%s seen before @end %s",
269                           command_name(interrupting_command),
270                           command_name(current->cmd));
271             }
272           else
273             {
274               line_error ("no matching `@end %s'",
275                           command_name(current->cmd));
276 
277               /* Ignored conditional. */
278               if (command_data(current->cmd).data == BLOCK_conditional)
279                 {
280                   parent = current->parent;
281                   destroy_element_and_children (pop_element_from_contents
282                                                           (parent));
283                 }
284             }
285           if (command_data(cmd).flags
286               & (CF_preformatted | CF_menu | CF_format_raw))
287             {
288               pop_context ();
289             }
290           if (command_data(cmd).data == BLOCK_region)
291             {
292               pop_region ();
293             }
294           if (!parent)
295             parent = current->parent;
296           current = parent;
297         }
298       else
299         {
300           /* @item and @tab commands are closed here, as well as line commands
301              with invalid content. */
302           current = current->parent;
303         }
304     }
305   else if (current->type != ET_NONE)
306     {
307       enum context c;
308       debug ("CLOSING type %s", element_type_names[current->type]);
309       switch (current->type)
310         {
311         case ET_bracketed:
312           command_error (current, "misplaced {");
313           if (current->contents.number > 0
314               && current->contents.list[0]->type
315                  == ET_empty_spaces_before_argument)
316             {
317               /* remove spaces element from tree and update extra values */
318               abort_empty_line (&current, 0);
319            }
320           current = current->parent;
321 
322           break;
323         case ET_menu_comment:
324         case ET_menu_entry_description:
325           c = pop_context ();
326           if (c != ct_preformatted)
327             fatal ("preformatted context expected");
328 
329           /* Remove empty menu_comment */
330           if (current->type == ET_menu_comment
331               && current->contents.number == 0)
332             {
333               current = current->parent;
334               destroy_element (pop_element_from_contents (current));
335             }
336           else
337             current = current->parent;
338 
339           break;
340         case ET_line_arg:
341         case ET_block_line_arg:
342           c = pop_context ();
343           if (c != ct_line && c != ct_def)
344             {
345               /* error */
346               fatal ("line or def context expected");
347             }
348           current = current->parent;
349           break;
350         default:
351           current = current->parent;
352           break;
353         }
354     }
355   else
356     {
357       /* should never get here */
358       if (current->parent)
359         current = current->parent;
360     }
361 
362   return current;
363 }
364 
365 /* Return lowest level ancestor of CURRENT containing a CLOSED_COMMAND
366    element.  Set CLOSED_ELEMENT to the element itself.  INTERRUPTING is used in
367    close_brace_command to display an error message.  Remove a context from
368    context stack if it was added by this command. */
369 ELEMENT *
close_commands(ELEMENT * current,enum command_id closed_command,ELEMENT ** closed_element,enum command_id interrupting)370 close_commands (ELEMENT *current, enum command_id closed_command,
371                 ELEMENT **closed_element, enum command_id interrupting)
372 {
373   *closed_element = 0;
374   current = end_paragraph (current, closed_command, interrupting);
375   current = end_preformatted (current, closed_command, interrupting);
376 
377   while (current->parent
378          && (!closed_command || current->cmd != closed_command)
379      /* Stop if in a root command. */
380          && !(current->cmd && command_flags(current) & CF_root))
381     {
382       close_command_cleanup (current);
383       current = close_current (current, closed_command, interrupting);
384     }
385 
386   if (closed_command && current->cmd == closed_command)
387     {
388       if (command_data(current->cmd).flags & CF_preformatted)
389         {
390           if (pop_context () != ct_preformatted)
391             fatal ("preformatted context expected");
392         }
393       else if (command_data(current->cmd).flags & CF_format_raw)
394         {
395           if (pop_context () != ct_rawpreformatted)
396             fatal ("rawpreformatted context expected");
397           // TODO: pop expanded formats stack
398         }
399       else if (command_data(current->cmd).flags & CF_menu)
400         {
401           enum context c;
402           c = pop_context ();
403           if (c != ct_menu && c != ct_preformatted)
404             fatal ("menu or preformatted context expected");
405         }
406       else if (current->cmd == CM_math || current->cmd == CM_displaymath)
407         {
408           enum context c;
409           c = pop_context ();
410           if (c != ct_math)
411             fatal ("math context expected");
412         }
413 
414       if (command_data(current->cmd).data == BLOCK_region)
415         pop_region ();
416 
417       *closed_element = current;
418       current = current->parent;
419     }
420   else if (closed_command)
421     {
422       line_error ("unmatched `@end %s'", command_name(closed_command));
423     }
424 
425   return current;
426 }
427