/* Copyright 2010-2023 Free Software Foundation, Inc. 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 . */ #include #include #include #include #include "parser.h" #include "debug.h" #include "text.h" #include "convert.h" #include "input.h" #include "labels.h" ELEMENT * handle_open_brace (ELEMENT *current, char **line_inout) { char *line = *line_inout; if (command_flags(current) & CF_brace) { enum command_id command; ELEMENT *arg; command = current->cmd; /* if there is already content it is for spaces_after_cmd_before_arg */ if (current->contents.number > 0) gather_spaces_after_cmd_before_arg (current); counter_push (&count_remaining_args, current, command_data(current->cmd).args_number); counter_dec (&count_remaining_args); arg = new_element (ET_NONE); add_to_element_args (current, arg); current = arg; if (command_data(command).flags & CF_contain_basic_inline) push_command (&nesting_context.basic_inline_stack, command); if (command == CM_verb) { current->type = ET_brace_command_arg; /* the delimiter may be in macro expansion */ if (!*line) line = new_line (current); /* Save the deliminating character in 'type'. */ if (!*line || *line == '\n') { line_error ("@verb without associated character"); add_info_string_dup (current->parent, "delimiter", ""); current->parent->type = 0; } else { /* Count any UTF-8 continuation bytes. */ int char_len = 1; char *delimiter_character; while ((line[char_len] & 0xC0) == 0x80) char_len++; delimiter_character = strndup (line, char_len); add_info_string (current->parent, "delimiter", delimiter_character); line += char_len; } } else if (command_data(command).data == BRACE_context) { if (command == CM_caption || command == CM_shortcaption) { #define float floatxx ELEMENT *float; nesting_context.caption++; if (!current->parent->parent || current->parent->parent->cmd != CM_float) { float = current->parent; while (float->parent && float->cmd != CM_float) float = float->parent; if (float->cmd != CM_float) { line_error ("@%s is not meaningful outside " "`@float' environment", command_name(command)); float = 0; } else line_warn ("@%s should be right below `@float'", command_name(command)); } else float = current->parent->parent; if (float) { if (lookup_extra (float, command_name(command))) line_warn ("ignoring multiple @%s", command_name(command)); else { add_extra_element (current->parent, "float", float); add_extra_element (float, command_name(command), current->parent); } } #undef float } else if (command == CM_footnote) { nesting_context.footnote++; } /* Add to context stack. */ switch (command) { case CM_footnote: push_context (ct_brace_command, command); break; case CM_caption: push_context (ct_brace_command, command); break; case CM_shortcaption: push_context (ct_brace_command, command); break; case CM_math: push_context (ct_math, command); break; default: fatal ("no context for command"); } { ELEMENT *e; int n; n = strspn (line, whitespace_chars_except_newline); e = new_element (ET_internal_spaces_before_argument); text_append_n (&e->text, line, n); add_to_element_contents (current, e); add_extra_element (e, "spaces_associated_command", current->parent); line += n; } current->type = ET_brace_command_context; } else /* not context brace */ { current->type = ET_brace_command_arg; /* Commands that disregard leading whitespace. */ if (command_data(command).data == BRACE_arguments || command_data(command).data == BRACE_inline) { ELEMENT *e; e = new_element (ET_internal_spaces_before_argument); /* See comment in parser.c:merge_text */ text_append (&e->text, ""); add_to_element_contents (current, e); add_extra_element (e, "spaces_associated_command", current); if (command == CM_inlineraw) push_context (ct_inlineraw, command); } } debug_nonl ("OPENED @%s, remaining: %d ", command_name (current->parent->cmd), counter_value (&count_remaining_args, current->parent) > 0 ? counter_value (&count_remaining_args, current->parent) : 0); debug_print_element (current, 0); debug (""); } else if (current->parent && (current->parent->cmd == CM_multitable || current->parent->type == ET_def_line || current->parent->type == ET_linemacro_call)) { ELEMENT *b, *e; abort_empty_line (¤t, NULL); b = new_element (ET_bracketed_arg); add_to_element_contents (current, b); current = b; /* We need the line number here in case @ protects the end of the line. */ if (current->parent->parent->type == ET_def_line) current->source_info = current_source_info; e = new_element (ET_internal_spaces_before_argument); text_append (&e->text, ""); /* See comment in parser.c:merge_text */ add_to_element_contents (current, e); debug ("BRACKETED in def/multitable"); add_extra_element (e, "spaces_associated_command", current); } else if (current->type == ET_rawpreformatted) { debug ("LONE OPEN BRACE in rawpreformatted"); current = merge_text (current, "{", 0); } /* matching braces accepted in a rawpreformatted, inline raw or math. Note that for rawpreformatted, it can only happen within an @-command as { is simply added as seen just above. */ else if (current_context() == ct_math || current_context() == ct_rawpreformatted || current_context() == ct_inlineraw) { ELEMENT *b = new_element (ET_balanced_braces); ELEMENT *open_brace = new_element (ET_NONE); abort_empty_line (¤t, NULL); b->source_info = current_source_info; add_to_element_contents (current, b); current = b; text_append (&open_brace->text, "{"); add_to_element_contents (current, open_brace); debug ("BALANCED BRACES in math/rawpreformatted/inlineraw"); } else { line_error ("misplaced {"); } *line_inout = line; return current; } /* Return 1 if an element is all whitespace. Note that this function isn't completely reliable because it doesn't look deep into the element tree. */ int check_empty_expansion (ELEMENT *e) { int i; for (i = 0; i < e->contents.number; i++) { ELEMENT *f = e->contents.list[i]; if (!check_space_element(f)) { return 0; } } return 1; } ELEMENT * handle_close_brace (ELEMENT *current, char **line_inout) { char *line = *line_inout; debug ("CLOSE BRACE"); /* For footnote and caption closing, when there is a paragraph inside. This makes the brace command the parent element. */ if (current->parent && current->parent->type == ET_brace_command_context && current->type == ET_paragraph) { abort_empty_line (¤t, NULL); debug ("IN BRACE_COMMAND_CONTEXT end paragraph"); current = end_paragraph (current, 0, 0); } if (current->type == ET_balanced_braces) { current = merge_text (current, "}", 0); current = current->parent; } else if (current->type == ET_bracketed_arg) { abort_empty_line (¤t, NULL); current = current->parent; } else if (command_flags(current->parent) & CF_brace) { enum command_id closed_command; abort_empty_line (¤t, NULL); /* determine if trailing spaces are ignored */ if (command_data(current->parent->cmd).data == BRACE_arguments) isolate_last_space (current); closed_command = current->parent->cmd; debug ("CLOSING(brace) @%s", command_data(closed_command).cmdname); counter_pop (&count_remaining_args); if (current->contents.number > 0 && command_data(closed_command).data == BRACE_noarg) line_warn ("command @%s does not accept arguments", command_name(closed_command)); if (closed_command == CM_anchor) { current->parent->source_info = current_source_info; if (current->contents.number == 0) line_error ("empty argument in @%s", command_name(current->parent->cmd)); else { check_register_target_element_label (current, current->parent); if (nesting_context.regions_stack.top > 0) { add_extra_string_dup (current, "element_region", command_name(top_command(&nesting_context.regions_stack))); } } } else if (command_data(closed_command).flags & CF_ref) { ELEMENT *ref = current->parent; if (ref->args.number > 0) { int link_or_inforef = (closed_command == CM_link || closed_command == CM_inforef); if ((link_or_inforef && (ref->args.number <= 0 || ref->args.list[0]->contents.number == 0) && (ref->args.number <= 2 || ref->args.list[2]->contents.number == 0)) || (!link_or_inforef && (ref->args.number <= 0 || ref->args.list[0]->contents.number == 0) && (ref->args.number <= 3 || ref->args.list[3]->contents.number == 0) && (ref->args.number <= 4 || ref->args.list[4]->contents.number == 0))) { line_warn ("command @%s missing a node or external manual " "argument", command_name(closed_command)); } else { ELEMENT *arg_label = args_child_by_index (ref, 0); NODE_SPEC_EXTRA *ref_label_info = parse_node_manual (arg_label, 1); if (ref_label_info && (ref_label_info->manual_content || ref_label_info->node_content)) { if (ref_label_info->node_content) add_extra_contents (arg_label, "node_content", ref_label_info->node_content); if (ref_label_info->manual_content) add_extra_contents (arg_label, "manual_content", ref_label_info->manual_content); } else { if (ref_label_info->manual_content) destroy_element (ref_label_info->manual_content); if (ref_label_info->node_content) destroy_element (ref_label_info->node_content); } if ((!link_or_inforef && (ref->args.number <= 3 || (ref->args.number <= 4 && ref->args.list[3]->contents.number == 0) || (ref->args.list[3]->contents.number == 0 && ref->args.list[4]->contents.number == 0)) && !ref_label_info->manual_content) || (link_or_inforef && (ref->args.number <= 2 || ref->args.list[2]->contents.number == 0))) { /* we use the @*ref command here and not the label command to have more information for messages */ remember_internal_xref (ref); } free (ref_label_info); } if (ref->args.number > 1 && ref->args.list[1]->contents.number > 0) { if (check_empty_expansion (ref->args.list[1])) { char *texi = 0; if (ref->args.list[1]) texi = convert_contents_to_texinfo (ref->args.list[1]); line_warn ("in @%s empty cross reference name " "after expansion `%s'", command_name(closed_command), ref->args.list[1] ? texi : ""); free (texi); } } if (!link_or_inforef && ref->args.number > 2 && ref->args.list[2]->contents.number > 0) { if (check_empty_expansion (ref->args.list[2])) { char *texi = 0; if (ref->args.list[2]) texi = convert_contents_to_texinfo (ref->args.list[2]); line_warn ("in @%s empty cross reference title " "after expansion `%s'", command_name(closed_command), ref->args.list[2] ? texi : ""); free (texi); } } } } else if (closed_command == CM_image) { ELEMENT *image = current->parent; if (image->args.number == 0 || image->args.list[0]->contents.number == 0) { line_error ("@image missing filename argument"); } if (global_input_encoding_name) add_extra_string_dup (image, "input_encoding_name", global_input_encoding_name); } else if (closed_command == CM_dotless) { if (current->contents.number > 0) { char *text = current->contents.list[0]->text.text; if (!text || (strcmp (text, "i") && strcmp (text, "j"))) { line_error ("@dotless expects `i' or `j' as argument, " "not `%s'", text); } } } else if ((command_data(closed_command).data == BRACE_inline) || closed_command == CM_abbr || closed_command == CM_acronym) { if (current->parent->cmd == CM_inlineraw) { if (ct_inlineraw != pop_context ()) fatal ("inlineraw context expected"); } if (current->parent->args.number == 0 || current->parent->args.list[0]->contents.number == 0) { line_warn ("@%s missing first argument", command_name(current->parent->cmd)); } } else if (closed_command == CM_errormsg) { char *arg = current->contents.list[0]->text.text; if (arg) line_error (arg); } else if (closed_command == CM_U) { if (current->contents.number == 0) { line_warn ("no argument specified for @U"); } else { char *arg = current->contents.list[0]->text.text; int n = strspn (arg, "0123456789ABCDEFabcdef"); if (arg[n]) { line_error ("non-hex digits in argument for @U: %s", arg); } else if (n < 4) { line_warn ("fewer than four hex digits in argument for @U: %s", arg); } else { unsigned long int val; int ret = sscanf (arg, "%lx", &val); if (ret != 1) { debug ("hex sscanf failed %s", arg); /* unknown error. possibly argument is too large for an int. */ } if (ret != 1 || val > 0x10FFFF) { line_error ("argument for @U exceeds Unicode maximum 0x10FFFF: %s", arg); } } } } else if (parent_of_command_as_argument (current->parent->parent) && current->contents.number == 0) { register_command_as_argument (current->parent); } else if (current->parent->cmd == CM_sortas || current->parent->cmd == CM_seeentry || current->parent->cmd == CM_seealso) { ELEMENT *index_elt; if (current->parent->parent && current->parent->parent->parent && ((command_flags(current->parent->parent->parent) & CF_index_entry_command) || current->parent->parent->parent->cmd == CM_subentry)) { index_elt = current->parent->parent->parent; if (current->parent->cmd == CM_sortas) { int superfluous_arg; char *arg = convert_to_text (current, &superfluous_arg); if (arg && *arg) { add_extra_string (index_elt, command_name(current->parent->cmd), arg); } } else { add_extra_element (index_elt, command_name(current->parent->cmd), current->parent); } } } register_global_command (current->parent); if (current->parent->cmd == CM_anchor || current->parent->cmd == CM_hyphenation || current->parent->cmd == CM_caption || current->parent->cmd == CM_shortcaption || current->parent->cmd == CM_sortas || current->parent->cmd == CM_seeentry || current->parent->cmd == CM_seealso) { ELEMENT *e; e = new_element (ET_spaces_after_close_brace); text_append (&e->text, ""); add_to_element_contents (current->parent->parent, e); } current = close_brace_command (current->parent, 0, 0, 0); if (close_preformatted_command(closed_command)) current = begin_preformatted (current); } /* CF_brace */ else if (current->type == ET_rawpreformatted) { /* lone right braces are accepted in a rawpreformatted */ current = merge_text (current, "}", 0); } else { line_error ("misplaced }"); } *line_inout = line; return current; } /* Handle a comma separating arguments to a Texinfo command. */ ELEMENT * handle_comma (ELEMENT *current, char **line_inout) { char *line = *line_inout; enum element_type type; ELEMENT *new_arg, *e; abort_empty_line (¤t, NULL); isolate_last_space (current); type = current->type; current = current->parent; if (command_data(current->cmd).data == BRACE_inline) { KEY_PAIR *k; int expandp = 0; k = lookup_extra (current, "format"); if (!k) { ELEMENT *arg = 0; char *inline_type = 0; if (current->args.number > 0 && current->args.list[0]->contents.number > 0 && (arg = current->args.list[0]->contents.list[0])) { if (arg->text.end > 0) inline_type = arg->text.text; } if (!inline_type) { /* Condition is missing */ debug ("INLINE COND MISSING"); add_extra_string (current, "format", 0); } else { debug ("INLINE: %s", inline_type); if (current->cmd == CM_inlineraw || current->cmd == CM_inlinefmt || current->cmd == CM_inlinefmtifelse) { if (format_expanded_p (inline_type)) { expandp = 1; add_extra_integer (current, "expand_index", 1); } else expandp = 0; } else if (current->cmd == CM_inlineifset || current->cmd == CM_inlineifclear) { expandp = 0; if (fetch_value (inline_type)) expandp = 1; if (current->cmd == CM_inlineifclear) expandp = !expandp; if (expandp) add_extra_integer (current, "expand_index", 1); } else expandp = 0; add_extra_string_dup (current, "format", inline_type); } /* Skip first argument for a false @inlinefmtifelse */ if (!expandp && current->cmd == CM_inlinefmtifelse) { ELEMENT *e; ELEMENT *arg; int brace_count = 1; add_extra_integer (current, "expand_index", 2); e = new_element (ET_elided_brace_command_arg); add_to_element_args (current, e); arg = new_element (ET_raw); text_append (&arg->text, ""); add_to_element_contents (e, arg); /* Scan forward to get the next argument. */ while (brace_count > 0) { static char *alloc_line; size_t non_separator_len = strcspn (line, "{},"); if (non_separator_len > 0) text_append_n (&arg->text, line, non_separator_len); line += non_separator_len; switch (*line) { case ',': if (brace_count == 1) { line++; goto inlinefmtifelse_done; } text_append_n (&arg->text, line, 1); break; case '{': brace_count++; text_append_n (&arg->text, line, 1); break; case '}': brace_count--; if (brace_count > 0) text_append_n (&arg->text, line, 1); break; default: /* at the end of line */ free (alloc_line); line = alloc_line = next_text (e); if (!line) goto funexit; continue; } line++; } inlinefmtifelse_done: /* Second argument is missing. */ if (brace_count == 0) { current = last_args_child (current); line--; /* on '}' */ goto funexit; } else counter_dec (&count_remaining_args); expandp = 1; } } else if (current->cmd == CM_inlinefmtifelse) { /* Second part of @inlinefmtifelse when condition is true. Discard second argument. */ expandp = 0; } /* If this command is not being expanded, add an elided argument, and scan forward to the closing brace. */ if (!expandp) { static char *alloc_line; ELEMENT *e; ELEMENT *arg; int brace_count = 1; e = new_element (ET_elided_brace_command_arg); add_to_element_args (current, e); arg = new_element (ET_raw); text_append (&arg->text, ""); add_to_element_contents (e, arg); while (brace_count > 0) { size_t non_separator_len = strcspn (line, "{}"); if (non_separator_len > 0) text_append_n (&arg->text, line, non_separator_len); line += non_separator_len; switch (*line) { case '{': brace_count++; text_append_n (&arg->text, line, 1); break; case '}': brace_count--; if (brace_count > 0) text_append_n (&arg->text, line, 1); break; default: /* at the end of line */ free (alloc_line); line = alloc_line = next_text (e); if (!alloc_line) goto funexit; continue; } line++; } counter_dec (&count_remaining_args); current = last_args_child (current); line--; /* on '}' */ goto funexit; } } if (counter_value (&count_remaining_args, current) != COUNTER_VARIADIC) counter_dec (&count_remaining_args); new_arg = new_element (type); add_to_element_args (current, new_arg); current = new_arg; e = new_element (ET_internal_spaces_before_argument); text_append (&e->text, ""); /* See comment in parser.c:merge_text */ add_to_element_contents (current, e); add_extra_element (e, "spaces_associated_command", current); funexit: *line_inout = line; return current; }