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 (¤t->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 (¤t, 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