/* GNU m4 -- A simple macro processor Copyright (C) 1989-1994, 2006-2014, 2016-2017, 2020-2021 Free Software Foundation, Inc. This file is part of GNU M4. GNU M4 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. GNU M4 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 . */ /* This module handles frozen files. */ #include "m4.h" /*-------------------------------------------------------------------. | Destructively reverse a symbol list and return the reversed list. | `-------------------------------------------------------------------*/ static symbol * reverse_symbol_list (symbol *sym) { symbol *result; symbol *next; result = NULL; while (sym) { next = SYMBOL_STACK (sym); SYMBOL_STACK (sym) = result; result = sym; sym = next; } return result; } static void freeze_symbol (symbol *sym, void *arg) { symbol *s = sym; FILE *file = arg; const builtin *bp; /* Process all entries in one stack, from the last to the first. This order ensures that, at reload time, pushdef's will be executed with the oldest definitions first. */ s = reverse_symbol_list (s); for (sym = s; sym; sym = SYMBOL_STACK (sym)) { switch (SYMBOL_TYPE (sym)) { case TOKEN_TEXT: xfprintf (file, "T%d,%d\n", (int) strlen (SYMBOL_NAME (sym)), (int) strlen (SYMBOL_TEXT (sym))); fputs (SYMBOL_NAME (sym), file); fputs (SYMBOL_TEXT (sym), file); fputc ('\n', file); break; case TOKEN_FUNC: bp = find_builtin_by_addr (SYMBOL_FUNC (sym)); if (bp == NULL) { M4ERROR ((warning_status, 0, "\ INTERNAL ERROR: builtin not found in builtin table!")); abort (); } xfprintf (file, "F%d,%d\n", (int) strlen (SYMBOL_NAME (sym)), (int) strlen (bp->name)); fputs (SYMBOL_NAME (sym), file); fputs (bp->name, file); fputc ('\n', file); break; case TOKEN_VOID: /* Ignore placeholder tokens that exist due to traceon. */ break; default: M4ERROR ((warning_status, 0, "\ INTERNAL ERROR: bad token data type in freeze_symbol ()")); abort (); break; } } /* Reverse the stack once more, putting it back as it was. */ reverse_symbol_list (s); } /*------------------------------------------------. | Produce a frozen state to the given file NAME. | `------------------------------------------------*/ void produce_frozen_state (const char *name) { FILE *file; file = fopen (name, O_BINARY ? "wbN" : "wN"); if (!file) m4_failure (errno, _("cannot open `%s'"), name); /* Write a recognizable header. */ xfprintf (file, "# This is a frozen state file generated by %s\n", PACKAGE_STRING); xfprintf (file, "V1\n"); /* Dump quote delimiters. */ if (strcmp (lquote.string, DEF_LQUOTE) || strcmp (rquote.string, DEF_RQUOTE)) { xfprintf (file, "Q%d,%d\n", (int) lquote.length, (int) rquote.length); fputs (lquote.string, file); fputs (rquote.string, file); fputc ('\n', file); } /* Dump comment delimiters. */ if (strcmp (bcomm.string, DEF_BCOMM) || strcmp (ecomm.string, DEF_ECOMM)) { xfprintf (file, "C%d,%d\n", (int) bcomm.length, (int) ecomm.length); fputs (bcomm.string, file); fputs (ecomm.string, file); fputc ('\n', file); } /* Dump all symbols. */ hack_all_symbols (freeze_symbol, file); /* Let diversions be issued from output.c module, its cleaner to have this piece of code there. */ freeze_diversions (file); /* All done. */ fputs ("# End of frozen state file\n", file); if (close_stream (file) != 0) m4_failure (errno, _("unable to create frozen state")); } /*----------------------------------------------------------------------. | Issue a message saying that some character is an EXPECTED character. | `----------------------------------------------------------------------*/ static void issue_expect_message (int expected) { if (expected == '\n') m4_failure (0, _("expecting line feed in frozen file")); else m4_failure (0, _("expecting character `%c' in frozen file"), expected); } /*-------------------------------------------------. | Reload a frozen state from the given file NAME. | `-------------------------------------------------*/ /* We are seeking speed, here. */ void reload_frozen_state (const char *name) { FILE *file; int character; int operation; char *string[2]; int allocated[2]; int number[2]; const builtin *bp; bool advance_line = true; #define GET_CHARACTER \ do \ { \ if (advance_line) \ { \ current_line++; \ advance_line = false; \ } \ (character = getc (file)); \ if (character == '\n') \ advance_line = true; \ } \ while (0) #define GET_NUMBER(Number, AllowNeg) \ do \ { \ unsigned int n = 0; \ while (c_isdigit (character) && n <= INT_MAX / 10U) \ { \ n = 10 * n + character - '0'; \ GET_CHARACTER; \ } \ if (((AllowNeg) ? INT_MIN : INT_MAX) + 0U < n \ || c_isdigit (character)) \ m4_failure (0, _("integer overflow in frozen file")); \ (Number) = n; \ } \ while (0) #define VALIDATE(Expected) \ do \ { \ if (character != (Expected)) \ issue_expect_message (Expected); \ } \ while (0) /* Skip comments (`#' at beginning of line) and blank lines, setting character to the next directive or to EOF. */ #define GET_DIRECTIVE \ do \ { \ GET_CHARACTER; \ if (character == '#') \ { \ while (character != EOF && character != '\n') \ GET_CHARACTER; \ VALIDATE ('\n'); \ } \ } \ while (character == '\n') #define GET_STRING(i) \ do \ { \ void *tmp; \ char *p; \ if (number[(i)] + 1 > allocated[(i)]) \ { \ free (string[(i)]); \ allocated[(i)] = number[(i)] + 1; \ string[(i)] = xcharalloc ((size_t) allocated[(i)]); \ } \ if (number[(i)] > 0 \ && !fread (string[(i)], (size_t) number[(i)], 1, file)) \ m4_failure (0, _("premature end of frozen file")); \ string[(i)][number[(i)]] = '\0'; \ p = string[(i)]; \ while ((tmp = memchr(p, '\n', number[(i)] - (p - string[(i)])))) \ { \ current_line++; \ p = (char *) tmp + 1; \ } \ } \ while (0) file = m4_path_search (name, NULL); if (file == NULL) m4_failure (errno, _("cannot open %s"), name); current_file = name; allocated[0] = 100; string[0] = xcharalloc ((size_t) allocated[0]); allocated[1] = 100; string[1] = xcharalloc ((size_t) allocated[1]); /* Validate format version. Only `1' is acceptable for now. */ GET_DIRECTIVE; VALIDATE ('V'); GET_CHARACTER; GET_NUMBER (number[0], false); if (number[0] > 1) M4ERROR ((EXIT_MISMATCH, 0, _("frozen file version %d greater than max supported of 1"), number[0])); else if (number[0] < 1) m4_failure (0, _("ill-formed frozen file, version directive expected")); VALIDATE ('\n'); GET_DIRECTIVE; while (character != EOF) { switch (character) { default: m4_failure (0, _("ill-formed frozen file")); case 'C': case 'D': case 'F': case 'T': case 'Q': operation = character; GET_CHARACTER; /* Get string lengths. Accept a negative diversion number. */ if (operation == 'D' && character == '-') { GET_CHARACTER; GET_NUMBER (number[0], true); number[0] = -number[0]; } else GET_NUMBER (number[0], false); VALIDATE (','); GET_CHARACTER; GET_NUMBER (number[1], false); VALIDATE ('\n'); if (operation != 'D') GET_STRING (0); GET_STRING (1); GET_CHARACTER; VALIDATE ('\n'); /* Act according to operation letter. */ switch (operation) { case 'C': /* Change comment strings. */ set_comment (string[0], string[1]); break; case 'D': /* Select a diversion and add a string to it. */ make_diversion (number[0]); if (number[1] > 0) output_text (string[1], number[1]); break; case 'F': /* Enter a macro having a builtin function as a definition. */ bp = find_builtin_by_name (string[1]); define_builtin (string[0], bp, SYMBOL_PUSHDEF); break; case 'T': /* Enter a macro having an expansion text as a definition. */ define_user_macro (string[0], string[1], SYMBOL_PUSHDEF); break; case 'Q': /* Change quote strings. */ set_quotes (string[0], string[1]); break; default: /* Cannot happen. */ break; } break; } GET_DIRECTIVE; } free (string[0]); free (string[1]); if (close_stream (file) != 0) m4_failure (errno, _("unable to read frozen state")); current_file = NULL; current_line = 0; #undef GET_CHARACTER #undef GET_DIRECTIVE #undef GET_NUMBER #undef VALIDATE #undef GET_STRING }