/* Copyright 2010-2019 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 . */ #define _GNU_SOURCE #include #include #include #include #include "parser.h" #include "tree.h" #include "text.h" #include "input.h" #include "convert.h" static MACRO *macro_list; static size_t macro_number; static size_t macro_space; /* Macro definition. */ void new_macro (char *name, ELEMENT *macro) { enum command_id new; MACRO *m = 0; ELEMENT tmp; /* Check for an existing definition first for us to overwrite. */ new = lookup_command (name); if (new) m = lookup_macro (new); if (!m) { if (macro_number == macro_space) { macro_list = realloc (macro_list, (macro_space += 5) * sizeof (MACRO)); if (!macro_list) fatal ("realloc failed"); } new = add_texinfo_command (name); m = ¯o_list[macro_number]; m->cmd = new; macro_number++; new &= ~USER_COMMAND_BIT; user_defined_command_data[new].flags |= CF_MACRO; } else { free (m->macro_name); } m->macro_name = strdup (name); m->element = macro; memset (&tmp, 0, sizeof (ELEMENT)); tmp.contents = macro->contents; m->macrobody = convert_to_texinfo (&tmp); } /* CMD will be either CM_macro or CM_rmacro. Read the line defining a macro's name and the arguments it takes, and return this information in a new ELEMENT. */ ELEMENT * parse_macro_command_line (enum command_id cmd, char **line_inout, ELEMENT *parent) { char *line = *line_inout; ELEMENT *macro, *macro_name; char *name, *args_ptr; int index; macro = new_element (ET_NONE); macro->cmd = cmd; macro->line_nr = line_nr; add_extra_string (macro, "arg_line", strdup (line)); /* Note this extra value isn't used much, so it might be possible to get rid of it. */ line += strspn (line, whitespace_chars); name = read_command_name (&line); if (*line && *line != '{' && !strchr (whitespace_chars, *line)) { line_error ("bad name for @%s", command_name (cmd)); add_extra_integer (macro, "invalid_syntax", 1); return macro; } else if (!name) { line_error ("@%s requires a name", command_name (cmd)); add_extra_integer (macro, "invalid_syntax", 1); return macro; } macro_name = new_element (ET_macro_name); text_append (¯o_name->text, name); free (name); add_to_element_args (macro, macro_name); args_ptr = line; args_ptr += strspn (args_ptr, whitespace_chars); if (*args_ptr != '{') { /* Either error or no args. */ goto check_trailing; } args_ptr++; index = 0; while (1) { /* args_ptr is after a '{' or ','. INDEX holds the number of the macro argument */ char *q, *q2; ELEMENT *arg; args_ptr += strspn (args_ptr, whitespace_chars); /* Find end of current argument. */ q = args_ptr; while (*q != '\0' && *q != ',' && *q != '}') q++; if (!*q) { /* End of string reached before closing brace. */ goto check_trailing; } /* Disregard trailing whitespace. */ q2 = q; while (q2 > args_ptr && strchr (whitespace_chars, q2[-1])) q2--; if (q2 == args_ptr) { /* argument is completely whitespace */ if (*q == ',') { line_error ("bad or empty @%s formal argument: ", command_name(cmd)); arg = new_element (ET_macro_arg); add_to_element_args (macro, arg); text_append_n (&arg->text, "", 0); add_extra_integer (macro, "invalid_syntax", 1); } } else { arg = new_element (ET_macro_arg); text_append_n (&arg->text, args_ptr, q2 - args_ptr); add_to_element_args (macro, arg); /* Check the argument name. */ { char *p; for (p = args_ptr; p < q2; p++) { if (!isalnum (*p) && *p != '_' && *p != '-') { char c = *q2; *q2 = 0; line_error ("bad or empty @%s formal argument: %s", command_name(cmd), args_ptr); *q2 = c; add_extra_integer (macro, "invalid_syntax", 1); break; } } } } args_ptr = q + 1; if (*q == '}') break; index++; } check_trailing: line = args_ptr; line += strspn (line, whitespace_chars); if (*line && *line != '@') { line_error ("bad syntax for @%s argument: %s", command_name(cmd), line); add_extra_integer (macro, "invalid_syntax", 1); } //line += strlen (line); /* Discard rest of line. */ *line_inout = line; return macro; } /* Macro use. */ /* Return index into given arguments to look for the value of NAME. Return -1 if not found. */ int lookup_macro_parameter (char *name, ELEMENT *macro) { int i, pos; ELEMENT **args; /* Find 'arg' in MACRO parameters. */ args = macro->args.list; pos = 0; for (i = 0; i < macro->args.number; i++) { if (args[i]->type == ET_macro_arg) { if (!strcmp (args[i]->text.text, name)) return pos; pos++; } } return -1; } /* LINE points the first non-whitespace character after the opening brace in a macro invocation. CMD is the command identifier of the macro command. Return array of the arguments. Return value to be freed by caller. */ char ** expand_macro_arguments (ELEMENT *macro, char **line_inout, enum command_id cmd) { char *line = *line_inout; char *pline = line; TEXT arg; int braces_level = 1; int args_total; char **arg_list = 0; size_t arg_number = 0; size_t arg_space = 0; arg_list = malloc (sizeof (char *)); args_total = macro->args.number - 1; text_init (&arg); while (braces_level > 0) { /* At the beginning of this loop pline is at the start of an argument. */ char *sep; sep = pline + strcspn (pline, "\\,{}"); if (!*sep) { debug ("MACRO ARG end of line"); text_append (&arg, pline); line = new_line (); if (!line) { line_error ("@%s missing closing brace", command_name(cmd)); line = "\n"; free (arg.text); goto funexit; } pline = line; continue; } text_append_n (&arg, pline, sep - pline); switch (*sep) { case '\\': if (!strchr ("\\{},", sep[1])) text_append_n (&arg, sep, 1); if (sep[1]) { text_append_n (&arg, &sep[1], 1); pline = sep + 2; } else pline = sep + 1; break; case '{': braces_level++; text_append_n (&arg, sep, 1); pline = sep + 1; break; case '}': braces_level--; if (braces_level > 0) { text_append_n (&arg, sep, 1); pline = sep + 1; break; } /* Fall through to add argument. */ case ',': if (braces_level > 1) { text_append_n (&arg, sep, 1); pline = sep + 1; break; } // check for too many args if (*sep == '}' || arg_number < args_total - 1) { /* Add the last argument read to the list. */ if (arg_number == arg_space) { arg_list = realloc (arg_list, (1+(arg_space += 5)) * sizeof (char *)); /* Include space for terminating null element. */ if (!arg_list) fatal ("realloc failed"); } if (arg.space > 0) arg_list[arg_number++] = arg.text; else arg_list[arg_number++] = strdup (""); text_init (&arg); debug ("MACRO NEW ARG"); pline = sep + 1; if (*sep == ',') pline += strspn (pline, whitespace_chars); } else { if (args_total != 1) line_error ("macro `%s' called with too many args", command_name(cmd)); text_append_n (&arg, ",", 1); pline = sep + 1; } break; } } debug ("END MACRO ARGS EXPANSION"); line = pline; if (args_total == 0 && arg_number > 0 && arg_list[0] && *arg_list[0]) { line_error ("macro `%s' declared without argument called with an argument", command_name(cmd)); } funexit: *line_inout = line; arg_list[arg_number] = 0; return arg_list; } /* ARGUMENTS are the arguments used in the macro invocation. EXPANDED gets the result of the expansion. */ static void expand_macro_body (MACRO *macro_record, char *arguments[], TEXT *expanded) { int pos; /* Index into arguments. */ ELEMENT *macro; char *macrobody; char *ptext; macro = macro_record->element; macrobody = macro_record->macrobody; /* Initialize TEXT object. */ expanded->end = 0; if (!macrobody) return; ptext = macrobody; while (1) { /* At the start of this loop ptext is at the beginning or just after the last backslash sequence. */ char *bs; /* Pointer to next backslash. */ bs = strchrnul (ptext, '\\'); text_append_n (expanded, ptext, bs - ptext); if (!*bs) break; /* End of line. */ ptext = bs + 1; if (*ptext == '\\') { text_append_n (expanded, "\\", 1); /* Escaped backslash (\\). */ ptext++; } else { bs = strchr (ptext, '\\'); if (!bs) { /* malformed input - unpaired backslash */ return; } *bs = '\0'; pos = lookup_macro_parameter (ptext, macro); if (pos == -1) { line_error ("\\ in @%s expansion followed `%s' instead of " "parameter name or \\", macro->args.list[0]->text.text, ptext); text_append (expanded, "\\"); text_append (expanded, ptext); } else { if (arguments && arguments[pos]) text_append (expanded, arguments[pos]); } *bs = '\\'; ptext = bs + 1; } } } MACRO * lookup_macro (enum command_id cmd) { int i; for (i = 0; i < macro_number; i++) { if (macro_list[i].cmd == cmd) return ¯o_list[i]; } return 0; } void delete_macro (char *name) { enum command_id cmd; MACRO *m; cmd = lookup_command (name); if (!cmd) return; m = lookup_macro (cmd); if (!m) return; m->cmd = 0; free (m->macro_name); m->macro_name = strdup (""); free (m->macrobody); m->macrobody = 0; m->element = 0; remove_texinfo_command (cmd); } void wipe_macros (void) { int i; for (i = 0; i < macro_number; i++) { free (macro_list[i].macro_name); free (macro_list[i].macrobody); } macro_number = 0; } /* Handle macro expansion. CMD is the macro command. */ ELEMENT * handle_macro (ELEMENT *current, char **line_inout, enum command_id cmd) { char *line, *p; MACRO *macro_record; ELEMENT *macro; TEXT expanded; char **arguments = 0; int args_number; line = *line_inout; text_init (&expanded); macro_record = lookup_macro (cmd); if (!macro_record) fatal ("no macro record"); macro = macro_record->element; /* Get number of args. - 1 for the macro name. */ args_number = macro->args.number - 1; p = line + strspn (line, whitespace_chars); if (*p == '{') { line = p; line++; line += strspn (line, whitespace_chars); arguments = expand_macro_arguments (macro, &line, cmd); } /* Warning depending on the number of arguments this macro is supposed to take. */ else if (args_number != 1) { if (args_number > 1) line_warn ("@%s defined with zero or more than one argument should " "be invoked with {}", command_name(cmd)); /* As agreed on the bug-texinfo mailing list, no warn when zero arg and not called with {}. */ } else { char *p; /* If it takes a single line of input, and we don't have a full line of input already, call new_line. */ if (!strchr (line, '\n')) { line = new_line (); if (!line) line = ""; } line += strspn (line, whitespace_chars_except_newline); arguments = malloc (sizeof (char *) * 2); arguments[0] = strdup (line); arguments[1] = 0; p = strchr (arguments[0], '\n'); if (p) { *p = '\0'; line = "\n"; } } expand_macro_body (macro_record, arguments, &expanded); debug ("MACROBODY: %s||||||", expanded.text); if (expanded.end > 0 && expanded.text[expanded.end - 1] == '\n') expanded.text[--expanded.end] = '\0'; if (input_number >= 1000) { line_warn ( "macro call nested too deeply " "(set MAX_NESTED_MACROS to override; current value %d)", 1000); goto funexit; /* TODO: actually check MAX_NESTED_MACROS? */ } if (macro->cmd == CM_macro) { if (expanding_macro (command_name(cmd))) { line_error ("recursive call of macro %s is not allowed; " "use @rmacro if needed", command_name(cmd)); expanded.text[0] = '\0'; expanded.end = 0; } } /* Free arguments. */ if (arguments) { char **s = arguments; while (*s) { free (*s); s++; } free (arguments); } // 3958 Pop macro stack /* Put expansion in front of the current line. */ input_push_text (strdup (line), 0); line = strchr (line, '\0'); input_push_text (expanded.text, command_name(cmd)); funexit: *line_inout = line; return current; } /* @set and @value */ typedef struct { char *name; char *value; } VALUE; static VALUE *value_list; static size_t value_number; static size_t value_space; void wipe_values (void) { size_t i; for (i = 0; i < value_number; i++) { free (value_list[i].name); free (value_list[i].value); } value_number = 0; } void store_value (char *name, char *value) { int i; VALUE *v = 0; int len; len = strlen (name); /* Check if already defined. */ for (i = 0; i < value_number; i++) { if (!strncmp (value_list[i].name, name, len) && !value_list[i].name[len]) { v = &value_list[i]; free (v->name); free (v->value); break; } } if (!v) { if (value_number == value_space) { value_list = realloc (value_list, (value_space += 5) * sizeof (VALUE)); } v = &value_list[value_number++]; } v->name = strdup (name); v->value = strdup (value); } void clear_value (char *name) { int i; for (i = 0; i < value_number; i++) { if (!strcmp (value_list[i].name, name)) { value_list[i].name[0] = '\0'; value_list[i].value[0] = '\0'; } } } char * fetch_value (char *name) { int i; for (i = 0; i < value_number; i++) { if (!strcmp (value_list[i].name, name)) return value_list[i].value; } if (!strcmp (name, "txicommandconditionals")) return "1"; return 0; } static INFO_ENCLOSE *infoencl_list; static size_t infoencl_number; static size_t infoencl_space; INFO_ENCLOSE * lookup_infoenclose (enum command_id cmd) { int i; for (i = 0; i < infoencl_number; i++) { if (infoencl_list[i].cmd == cmd) return &infoencl_list[i]; } return 0; } void add_infoenclose (enum command_id cmd, char *begin, char *end) { int i; INFO_ENCLOSE *ie = 0; /* Check if already defined. */ for (i = 0; i < infoencl_number; i++) { if (infoencl_list[i].cmd == cmd) { ie = &infoencl_list[i]; free (ie->begin); free (ie->end); break; } } if (!ie) { if (infoencl_number == infoencl_space) { infoencl_list = realloc (infoencl_list, (infoencl_space += 5) * sizeof (INFO_ENCLOSE)); } ie = &infoencl_list[infoencl_number++]; } ie->cmd = cmd; ie->begin = strdup (begin); ie->end = strdup (end); }