397 lines
13 KiB
C
397 lines
13 KiB
C
/* GNU m4 -- A simple macro processor
|
|
|
|
Copyright (C) 1989-1994, 2006-2014, 2016-2017, 2020-2026 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 <https://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
/* 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 ? "wbe" : "we");
|
|
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, !!O_BINARY, 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], strlen (string[0]), string[1],
|
|
strlen (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
|
|
}
|