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 #include <stdlib.h>
18 #include <string.h>
19 #include <stdio.h>
20 
21 #include "parser.h"
22 #include "text.h"
23 #include "convert.h"
24 #include "input.h"
25 #include "labels.h"
26 
27 ELEMENT *
handle_open_brace(ELEMENT * current,char ** line_inout)28 handle_open_brace (ELEMENT *current, char **line_inout)
29 {
30   char *line = *line_inout;
31 
32   abort_empty_line (&current, NULL);
33   if (command_flags(current) & CF_brace)
34     {
35       enum command_id command;
36       ELEMENT *arg;
37 
38       command = current->cmd;
39       counter_push (&count_remaining_args, current,
40                     command_data(current->cmd).data);
41       counter_dec (&count_remaining_args);
42 
43       arg = new_element (ET_NONE);
44       add_to_element_args (current, arg);
45       current = arg;
46 
47       if (command == CM_verb)
48         {
49           current->type = ET_brace_command_arg;
50           /* Save the deliminating character in 'type'. */
51           if (!*line || *line == '\n')
52             {
53               line_error ("@verb without associated character");
54               add_extra_string_dup (current->parent, "delimiter", "");
55               current->parent->type = 0;
56             }
57           else
58             {
59               static char c[2];
60               c[0] = *line++;
61               add_extra_string_dup (current->parent, "delimiter", c);
62             }
63         }
64       else if (command_data(command).data == BRACE_context)
65         {
66           if (command == CM_caption || command == CM_shortcaption)
67             {
68 #define float floatxx
69               ELEMENT *float;
70               if (!current->parent->parent
71                   || current->parent->parent->cmd != CM_float)
72                 {
73                   float = current->parent;
74                   while (float->parent && float->cmd != CM_float)
75                     float = float->parent;
76                   if (float->cmd != CM_float)
77                     {
78                       line_error ("@%s is not meaningful outside "
79                                   "`@float' environment",
80                                   command_name(command));
81                       float = 0;
82                     }
83                   else
84                     line_warn ("@%s should be right below `@float'",
85                                command_name(command));
86                 }
87               else
88                 float = current->parent->parent;
89               if (float)
90                 {
91                   if (lookup_extra (float, command_name(command)))
92                     line_warn ("ignoring multiple @%s",
93                                command_name(command));
94                   else
95                     {
96                       add_extra_element (current->parent, "float", float);
97                       add_extra_element (float, command_name(command),
98                                              current->parent);
99                     }
100                 }
101 #undef float
102         }
103 
104           /* Add to context stack. */
105           switch (command)
106             {
107             case CM_footnote:
108               push_context (ct_footnote);
109               break;
110             case CM_caption:
111               push_context (ct_caption);
112               break;
113             case CM_shortcaption:
114               push_context (ct_shortcaption);
115               break;
116             case CM_math:
117               push_context (ct_math);
118               break;
119             default:
120               fatal ("no context for command");
121             }
122 
123           {
124             ELEMENT *e;
125             int n;
126             n = strspn (line, whitespace_chars_except_newline);
127             e = new_element (ET_empty_spaces_before_argument);
128             text_append_n (&e->text, line, n);
129             add_to_element_contents (current, e);
130             add_extra_element (e, "command", current->parent);
131             line += n;
132           }
133           current->type = ET_brace_command_context;
134         }
135       else /* not context brace */
136         {
137           current->type = ET_brace_command_arg;
138 
139           /* Commands which are said to take a positive number of arguments
140              disregard leading and trailing whitespace. */
141           if (command_data(command).data > 0)
142             {
143               ELEMENT *e;
144               e = new_element (ET_empty_spaces_before_argument);
145               /* See comment in parser.c:merge_text */
146               text_append (&e->text, "");
147               add_to_element_contents (current, e);
148               add_extra_element (e, "command", current);
149 
150               if (command == CM_inlineraw)
151                 push_context (ct_inlineraw);
152             }
153         }
154       debug ("OPENED");
155     }
156   else if (current->parent && (current->parent->cmd == CM_multitable
157            || current->parent->type == ET_def_line))
158     {
159       ELEMENT *b, *e;
160       b = new_element (ET_bracketed);
161       add_to_element_contents (current, b);
162       current = b;
163 
164       /* We need the line number here in case @ protects the
165          end of the line.  */
166       if (current->parent->parent->type == ET_def_line)
167         current->line_nr = line_nr;
168 
169       e = new_element (ET_empty_spaces_before_argument);
170       text_append (&e->text, ""); /* See comment in parser.c:merge_text */
171       add_to_element_contents (current, e);
172       debug ("BRACKETED in def/multitable");
173       add_extra_element (e, "command", current);
174     }
175   else if (current->type == ET_rawpreformatted)
176     {
177       ELEMENT *e = new_element (ET_NONE);
178       text_append (&e->text, "{");
179       add_to_element_contents (current, e);
180     }
181   else if (current_context() == ct_math
182            || current_context() == ct_rawpreformatted
183            || current_context() == ct_inlineraw)
184     {
185       ELEMENT *b = new_element (ET_bracketed);
186       b->line_nr = line_nr;
187       add_to_element_contents (current, b);
188       current = b;
189       debug ("BRACKETED in math");
190     }
191   else
192     {
193       line_error ("misplaced {");
194       if (current->contents.number > 0
195           && last_contents_child(current)->type
196                == ET_empty_spaces_before_argument)
197         {
198           /* FIXME: Is this right? */
199           remove_from_contents (current, 0);
200         }
201     }
202 
203   *line_inout = line;
204   return current;
205 }
206 
207 /* Return 1 if an element is all whitespace.
208    Note that this function isn't completely reliable because it
209    doesn't look deep into the element tree.
210    In the perl code it calls
211    Texinfo::Convert::NodeNameNormalization::normalize_node,
212    and checks that the result isn't all hyphens.
213  */
214 int
check_empty_expansion(ELEMENT * e)215 check_empty_expansion (ELEMENT *e)
216 {
217   int i;
218   for (i = 0; i < e->contents.number; i++)
219     {
220       ELEMENT *f = e->contents.list[i];
221       if (!(
222                f->cmd == CM_SPACE
223             || f->cmd == CM_TAB
224             || f->cmd == CM_NEWLINE
225             || f->cmd == CM_c
226             || f->cmd == CM_comment
227             || f->cmd == CM_COLON
228             || f->type == ET_empty_spaces_before_argument
229             || f->type == ET_spaces_at_end
230             || (!f->cmd && !f->type && f->text.end == 0)
231             || (f->text.end > 0
232                 && !*(f->text.text + strspn (f->text.text, whitespace_chars)))
233          ))
234         {
235           return 0;
236         }
237     }
238   return 1;
239 }
240 
241 ELEMENT *
handle_close_brace(ELEMENT * current,char ** line_inout)242 handle_close_brace (ELEMENT *current, char **line_inout)
243 {
244   char *line = *line_inout;
245 
246   abort_empty_line (&current, NULL);
247 
248   if (current->type == ET_bracketed)
249     {
250       /* Used in @math */
251       current = current->parent;
252       goto funexit;
253     }
254   else if (command_flags(current->parent) & CF_brace)
255     {
256       enum command_id closed_command;
257       if (command_data(current->parent->cmd).data == BRACE_context)
258         {
259           (void) pop_context ();
260           /* The Perl code here checks that the popped context and the
261              parent command match as strings. */
262         }
263       else if (command_data(current->parent->cmd).data > 0)
264         {
265           /* @inline* always have end spaces considered as normal text */
266           if (!(command_flags(current->parent) & CF_inline))
267             isolate_last_space (current);
268         }
269 
270       closed_command = current->parent->cmd;
271       debug ("CLOSING(brace) %s", command_data(closed_command).cmdname);
272       counter_pop (&count_remaining_args);
273 
274       if (current->contents.number > 0
275           && command_data(closed_command).data == 0)
276         line_warn ("command @%s does not accept arguments",
277                    command_name(closed_command));
278 
279       if (closed_command == CM_anchor)
280         {
281           NODE_SPEC_EXTRA *parsed_anchor;
282           current->parent->line_nr = line_nr;
283           parsed_anchor = parse_node_manual (current);
284           if (check_node_label (parsed_anchor, CM_anchor))
285             {
286               register_label (current->parent, parsed_anchor->node_content);
287               if (current_region ())
288                 add_extra_element (current, "region", current_region ());
289               if (parsed_anchor->manual_content)
290                 destroy_element (parsed_anchor->manual_content);
291             }
292           free (parsed_anchor);
293         }
294       else if (command_data(closed_command).flags & CF_ref)
295         {
296           ELEMENT *ref = current->parent;
297           if (ref->args.number > 0)
298             {
299               if ((closed_command == CM_inforef
300                    && (ref->args.number <= 0
301                        || ref->args.list[0]->contents.number == 0)
302                    && (ref->args.number <= 2
303                        || ref->args.list[2]->contents.number == 0))
304                   || (closed_command != CM_inforef
305                        && (ref->args.number <= 0
306                            || ref->args.list[0]->contents.number == 0)
307                        && (ref->args.number <= 3
308                            || ref->args.list[3]->contents.number == 0)
309                        && (ref->args.number <= 4
310                            || ref->args.list[4]->contents.number == 0)))
311                 {
312                   line_warn ("command @%s missing a node or external manual "
313                              "argument", command_name(closed_command));
314                 }
315               else
316                 {
317                   NODE_SPEC_EXTRA *nse;
318                   nse = parse_node_manual (args_child_by_index (ref, 0));
319                   if (nse && (nse->manual_content || nse->node_content))
320                     add_extra_node_spec (ref, "node_argument", nse);
321                   else
322                     {
323                       if (nse->manual_content)
324                         destroy_element (nse->manual_content);
325                       if (nse->node_content)
326                         destroy_element (nse->node_content);
327                       free (nse);
328                     }
329                   if (closed_command != CM_inforef
330                       && (ref->args.number <= 3
331                           || ref->args.number <= 4
332                              && ref->args.list[3]->contents.number == 0
333                           || (ref->args.list[3]->contents.number == 0
334                                && ref->args.list[4]->contents.number == 0))
335                       && !nse->manual_content)
336                     {
337                       remember_internal_xref (ref);
338                     }
339                 }
340 
341               if (ref->args.number > 1
342                   && ref->args.list[1]->contents.number > 0)
343                 {
344                   if (check_empty_expansion (ref->args.list[1]))
345                     {
346                       char *texi = 0;
347                       if (ref->args.list[1])
348                         texi = convert_to_texinfo (ref->args.list[1]);
349 
350                       line_warn ("in @%s empty cross reference name "
351                                  "after expansion `%s'",
352                                  command_name(closed_command),
353                                  ref->args.list[1] ? texi : "");
354                       free (texi);
355                     }
356                 }
357 
358               if (closed_command != CM_inforef
359                   && ref->args.number > 2
360                   && ref->args.list[2]->contents.number > 0)
361                 {
362                   if (check_empty_expansion (ref->args.list[2]))
363                     {
364                       char *texi = 0;
365                       if (ref->args.list[2])
366                         texi = convert_to_texinfo (ref->args.list[2]);
367 
368                       line_warn ("in @%s empty cross reference title "
369                                  "after expansion `%s'",
370                                  command_name(closed_command),
371                                  ref->args.list[2] ? texi : "");
372                       free (texi);
373                     }
374                 }
375             }
376         }
377       else if (closed_command == CM_image)
378         {
379           ELEMENT *image = current->parent;
380           if (image->args.number == 0
381               || image->args.list[0]->contents.number == 0)
382             {
383               line_error ("@image missing filename argument");
384             }
385           if (global_info.input_perl_encoding)
386             add_extra_string_dup (image, "input_perl_encoding",
387                                   global_info.input_perl_encoding);
388         }
389       else if (closed_command == CM_dotless)
390         {
391           if (current->contents.number > 0)
392             {
393               char *text = current->contents.list[0]->text.text;
394               if (!text || (strcmp (text, "i") && strcmp (text, "j")))
395                 {
396                   line_error ("@dotless expects `i' or `j' as argument, "
397                               "not `%s'", text);
398                 }
399             }
400         }
401       else if ((command_data(closed_command).flags & CF_inline)
402                || closed_command == CM_abbr
403                || closed_command == CM_acronym)
404         {
405           if (current->parent->cmd == CM_inlineraw)
406             {
407               if (ct_inlineraw != pop_context ())
408                 fatal ("expected inlineraw context");
409             }
410           if (current->parent->args.number == 0
411               || current->parent->args.list[0]->contents.number == 0)
412             {
413               line_warn ("@%s missing first argument",
414                          command_name(current->parent->cmd));
415             }
416         }
417       else if (closed_command == CM_errormsg)
418         {
419           char *arg = current->contents.list[0]->text.text;
420           if (arg)
421             line_error (arg);
422         }
423       else if (closed_command == CM_U)
424         {
425           if (current->contents.number == 0)
426             {
427               line_warn ("no argument specified for @U");
428             }
429           else
430             {
431               char *arg = current->contents.list[0]->text.text;
432               int n = strspn (arg, "0123456789ABCDEFabcdef");
433               if (arg[n])
434                 {
435                   line_error ("non-hex digits in argument for @U: %s", arg);
436                 }
437               else if (n < 4)
438                 {
439                   line_warn
440                     ("fewer than four hex digits in argument for @U: %s", arg);
441                 }
442               else
443                 {
444                   unsigned long int val;
445                   int ret = sscanf (arg, "%lx", &val);
446                   if (ret != 1)
447                     {
448                       debug ("hex sscanf failed %s", arg);
449                       /* unknown error.  possibly argument is too large
450                          for an int. */
451                     }
452                   if (ret != 1 || val > 0x10FFFF)
453                     {
454                       line_error
455                        ("argument for @U exceeds Unicode maximum 0x10FFFF: %s",
456                         arg);
457                     }
458                 }
459 
460             }
461         }
462       else if (command_with_command_as_argument (current->parent->parent)
463                && current->contents.number == 0)
464         {
465           debug ("FOR PARENT ... command_as_argument_braces ...");
466           if (!current->parent->type)
467             current->parent->type = ET_command_as_argument;
468           add_extra_element (current->parent->parent->parent,
469                              "command_as_argument", current->parent);
470         }
471       else if (current->parent->cmd == CM_sortas
472                || current->parent->cmd == CM_seeentry
473                || current->parent->cmd == CM_seealso)
474         {
475           ELEMENT *index_elt;
476           if (current->parent->parent
477               && current->parent->parent->parent
478               && ((command_flags(current->parent->parent->parent)
479                     & CF_index_entry_command)
480                   || current->parent->parent->parent->cmd == CM_subentry))
481             {
482               index_elt = current->parent->parent->parent;
483               if (current->parent->cmd == CM_sortas)
484                 {
485                   int superfluous_arg;
486                   char *arg = convert_to_text (current, &superfluous_arg);
487                   if (arg && *arg)
488                     {
489                       add_extra_string (index_elt,
490                                         command_name(current->parent->cmd),
491                                         arg);
492                     }
493                 }
494               else
495                 {
496                   add_extra_element (index_elt,
497                                      command_name(current->parent->cmd),
498                                      current->parent);
499                 }
500             }
501         }
502       register_global_command (current->parent);
503 
504       if (current->parent->cmd == CM_anchor
505           || current->parent->cmd == CM_hyphenation
506           || current->parent->cmd == CM_caption
507           || current->parent->cmd == CM_shortcaption
508           || current->parent->cmd == CM_sortas
509           || current->parent->cmd == CM_seeentry
510           || current->parent->cmd == CM_seealso)
511         {
512           ELEMENT *e;
513           e = new_element (ET_empty_spaces_after_close_brace);
514           text_append (&e->text, "");
515           add_to_element_contents (current->parent->parent, e);
516         }
517 
518       current = current->parent->parent;
519       if (close_preformatted_command(closed_command))
520         current = begin_preformatted (current);
521     } /* CF_brace */
522   else if (current->type == ET_rawpreformatted)
523     {
524       /* lone right braces are accepted in a rawpreformatted */
525       ELEMENT *e = new_element (ET_NONE);
526       text_append_n (&e->text, "}", 1);
527       add_to_element_contents (current, e);
528       goto funexit;
529     }
530   /* context brace command (e.g. @footnote) when there is a paragraph inside */
531   else if (current_context() == ct_footnote
532            || current_context() == ct_caption
533            || current_context() == ct_shortcaption
534            || current_context() == ct_math)
535     {
536       current = end_paragraph (current, 0, 0);
537       if (current->parent
538           && (command_flags(current->parent) & CF_brace)
539           && (command_data(current->parent->cmd).data == BRACE_context))
540         {
541           enum command_id closed_command;
542           (void) pop_context ();
543           debug ("CLOSING(context command)");
544           closed_command = current->parent->cmd;
545           counter_pop (&count_remaining_args);
546 
547           register_global_command (current->parent);
548           current = current->parent->parent;
549           if (close_preformatted_command(closed_command))
550             current = begin_preformatted (current);
551         }
552     }
553   else
554     {
555       line_error ("misplaced }");
556       goto funexit;
557     }
558 
559 funexit:
560   *line_inout = line;
561   return current;
562 }
563 
564 
565 /* Handle a comma separating arguments to a Texinfo command. */
566 ELEMENT *
handle_comma(ELEMENT * current,char ** line_inout)567 handle_comma (ELEMENT *current, char **line_inout)
568 {
569   char *line = *line_inout;
570   enum element_type type;
571   ELEMENT *new_arg, *e;
572 
573   abort_empty_line (&current, NULL);
574   isolate_last_space (current);
575 
576   type = current->type;
577   current = current->parent;
578 
579   if (command_flags(current) & CF_inline)
580     {
581       KEY_PAIR *k;
582       int expandp = 0;
583       debug ("THE INLINE PART");
584       k = lookup_extra (current, "format");
585       if (!k)
586         {
587           ELEMENT *arg = 0;
588           char *inline_type = 0;
589           if (current->args.number > 0
590               && current->args.list[0]->contents.number > 0
591               && (arg = current->args.list[0]->contents.list[0]))
592             {
593               if (arg->text.end > 0)
594                 inline_type = arg->text.text;
595             }
596 
597           debug ("INLINE <%s>", inline_type);
598           if (!inline_type)
599             {
600               /* Condition is missing */
601               debug ("INLINE COND MISSING");
602             }
603           else if (current->cmd == CM_inlineraw
604               || current->cmd == CM_inlinefmt
605               || current->cmd == CM_inlinefmtifelse)
606             {
607               if (format_expanded_p (inline_type))
608                 {
609                   expandp = 1;
610                   add_extra_integer (current, "expand_index", 1);
611                 }
612               else
613                 expandp = 0;
614             }
615           else if (current->cmd == CM_inlineifset
616                    || current->cmd == CM_inlineifclear)
617             {
618               expandp = 0;
619               if (fetch_value (inline_type))
620                 expandp = 1;
621               if (current->cmd == CM_inlineifclear)
622                 expandp = !expandp;
623               if (expandp)
624                 add_extra_integer (current, "expand_index", 1);
625             }
626           else
627             expandp = 0;
628 
629           if (inline_type)
630             add_extra_string_dup (current, "format", inline_type);
631           else
632             add_extra_string (current, "format", 0);
633 
634           /* Skip first argument for a false @inlinefmtifelse */
635           if (!expandp && current->cmd == CM_inlinefmtifelse)
636             {
637               ELEMENT *e;
638               int brace_count = 1;
639 
640               add_extra_integer (current, "expand_index", 2);
641 
642               /* Add a dummy argument for the first argument. */
643               e = new_element (ET_elided);
644               add_to_element_args (current, e);
645 
646               /* Scan forward to get the next argument. */
647               while (brace_count > 0)
648                 {
649                   line += strcspn (line, "{},");
650                   switch (*line)
651                     {
652                     case ',':
653                       if (brace_count == 1)
654                         {
655                           line++;
656                           goto inlinefmtifelse_done;
657                         }
658                       break;
659                     case '{':
660                       brace_count++;
661                       break;
662                     case '}':
663                       brace_count--;
664                       break;
665                     default:
666                       line = next_text ();
667                       if (!line)
668                         goto funexit;
669                       continue;
670                     }
671                   line++;
672                 }
673 inlinefmtifelse_done:
674               /* Check if the second argument is missing. */
675               if (brace_count == 0)
676                 {
677                   line--; /* on '}' */
678                 }
679 
680               counter_dec (&count_remaining_args);
681               expandp = 1;
682             }
683         }
684       else if (current->cmd == CM_inlinefmtifelse)
685         {
686           /* Second art of @inlinefmtifelse when condition is true.  Discard
687              second argument. */
688           expandp = 0;
689         }
690 
691       /* If this command is not being expanded, add a dummy argument, and
692          scan forward to the closing brace. */
693       if (!expandp)
694         {
695           static char *alloc_line;
696           ELEMENT *e;
697           int brace_count = 1;
698           e = new_element (ET_elided);
699           add_to_element_args (current, e);
700           while (brace_count > 0)
701             {
702               line += strcspn (line, "{}");
703               switch (*line)
704                 {
705                 case '{':
706                   brace_count++;
707                   break;
708                 case '}':
709                   brace_count--;
710                   break;
711                 default:
712                   free (alloc_line);
713                   alloc_line = next_text ();
714                   if (!alloc_line)
715                     goto funexit;
716                   line = alloc_line;
717                   continue;
718                 }
719               line++;
720             }
721           current = last_args_child (current);
722           line--;  /* on '}' */
723           goto funexit;
724         }
725     }
726 
727   if (counter_value (&count_remaining_args, current) != COUNTER_VARIADIC)
728     counter_dec (&count_remaining_args);
729   new_arg = new_element (type);
730   add_to_element_args (current, new_arg);
731   current = new_arg;
732   e = new_element (ET_empty_spaces_before_argument);
733   text_append (&e->text, ""); /* See comment in parser.c:merge_text */
734   add_to_element_contents (current, e);
735   add_extra_element (e, "command", current);
736 
737 funexit:
738   *line_inout = line;
739   return current;
740 }
741 
742 /* Actions to be taken when a special character appears in the input. */
743 ELEMENT *
handle_separator(ELEMENT * current,char separator,char ** line_inout)744 handle_separator (ELEMENT *current, char separator, char **line_inout)
745 {
746   char *line = *line_inout;
747 
748   if (separator == '{')
749     {
750       current = handle_open_brace (current, &line);
751     }
752   else if (separator == '}')
753     {
754       current = handle_close_brace (current, &line);
755     }
756   /* If a comma is seen after all the arguments for the command have been
757      read, it is included in the last argument. */
758   else if (separator == ','
759            && counter_value (&count_remaining_args, current->parent) > 0)
760     {
761       current = handle_comma (current, &line);
762     }
763   else if (separator == ',' && current->type == ET_line_arg
764            && current->parent->cmd == CM_node)
765     {
766       line_warn ("superfluous arguments for node");
767     }
768   /* After a separator in a menu. */
769   else if ((separator == ','
770             || separator == '\t'
771             || separator == '.')
772            && current->type == ET_menu_entry_node
773            || separator == ':' && current->type == ET_menu_entry_name)
774     {
775       ELEMENT *e;
776 
777       current = current->parent;
778       e = new_element (ET_menu_entry_separator);
779       text_append_n (&e->text, &separator, 1);
780       add_to_element_args (current, e);
781 
782       /* Note in 'handle_menu' in menus.c, if a '.' is not followed by
783          whitespace, we revert was was done here. */
784     }
785   else if (separator == '\f' && current->type == ET_paragraph)
786     {
787       ELEMENT *e;
788 
789       /* A form feed stops and restarts a paragraph. */
790       current = end_paragraph (current, 0, 0);
791       e = new_element (ET_empty_line);
792       text_append_n (&e->text, "\f", 1);
793       add_to_element_contents (current, e);
794       e = new_element (ET_empty_line);
795       add_to_element_contents (current, e);
796     }
797   else
798     {
799       /* Default - merge the character as usual. */
800       char t[2];
801       t[0] = separator;
802       t[1] = '\0';
803       current = merge_text (current, t);
804     }
805 
806   *line_inout = line;
807   return current;
808 }
809