#include "m4.h"
#include <sys/stat.h>
#define INITIAL_BUFFER_SIZE 512
#define MAXIMUM_TOTAL_SIZE (512 * 1024)
#define COPY_BUFFER_SIZE (32 * 512)
#ifdef HAVE_TMPFILE
extern FILE *tmpfile ();
#endif
struct diversion
{
FILE *file;
char *buffer;
int size;
int used;
};
static struct diversion *diversion_table;
static int diversions;
static int total_buffer_size;
int current_diversion;
static struct diversion *output_diversion;
static FILE *output_file;
static char *output_cursor;
static int output_unused;
int output_current_line;
void
output_init (void)
{
diversion_table = (struct diversion *) xmalloc (sizeof (struct diversion));
diversions = 1;
diversion_table[0].file = stdout;
diversion_table[0].buffer = NULL;
diversion_table[0].size = 0;
diversion_table[0].used = 0;
total_buffer_size = 0;
current_diversion = 0;
output_diversion = diversion_table;
output_file = stdout;
output_cursor = NULL;
output_unused = 0;
}
#ifndef HAVE_TMPFILE
#ifndef HAVE_MKSTEMP
#include <fcntl.h>
static int
mkstemp (const char *tmpl)
{
mktemp (tmpl);
return open (tmpl, O_RDWR | O_TRUNC | O_CREAT, 0600);
}
#endif
static FILE *
tmpfile (void)
{
char buf[32];
int fd;
strcpy (buf, "/tmp/m4XXXXXX");
fd = mkstemp (buf);
if (fd < 0)
return NULL;
unlink (buf);
return fdopen (fd, "w+");
}
#endif
static void
make_room_for (int length)
{
int wanted_size;
output_diversion->used = output_diversion->size - output_unused;
for (wanted_size = output_diversion->size;
wanted_size < output_diversion->used + length;
wanted_size = wanted_size == 0 ? INITIAL_BUFFER_SIZE : wanted_size * 2)
;
if (total_buffer_size - output_diversion->size + wanted_size
> MAXIMUM_TOTAL_SIZE)
{
struct diversion *selected_diversion;
int selected_used;
struct diversion *diversion;
int count;
selected_diversion = output_diversion;
selected_used = output_diversion->used + length;
for (diversion = diversion_table + 1;
diversion < diversion_table + diversions;
diversion++)
if (diversion->used > selected_used)
{
selected_diversion = diversion;
selected_used = diversion->used;
}
selected_diversion->file = tmpfile ();
if (selected_diversion->file == NULL)
M4ERROR ((EXIT_FAILURE, errno,
"ERROR: Cannot create temporary file for diversion"));
if (selected_diversion->used > 0)
{
count = fwrite (selected_diversion->buffer,
(size_t) selected_diversion->used,
1,
selected_diversion->file);
if (count != 1)
M4ERROR ((EXIT_FAILURE, errno,
"ERROR: Cannot flush diversion to temporary file"));
}
free (selected_diversion->buffer);
total_buffer_size -= selected_diversion->size;
selected_diversion->buffer = NULL;
selected_diversion->size = 0;
selected_diversion->used = 0;
}
output_file = output_diversion->file;
if (output_file)
{
output_cursor = NULL;
output_unused = 0;
}
else
{
output_diversion->buffer
= xrealloc (output_diversion->buffer, (size_t) wanted_size);
total_buffer_size += wanted_size - output_diversion->size;
output_diversion->size = wanted_size;
output_cursor = output_diversion->buffer + output_diversion->used;
output_unused = wanted_size - output_diversion->used;
}
}
#define OUTPUT_CHARACTER(Char) \
if (output_file) \
putc ((Char), output_file); \
else if (output_unused == 0) \
output_character_helper ((Char)); \
else \
(output_unused--, *output_cursor++ = (Char))
static void
output_character_helper (int character)
{
make_room_for (1);
if (output_file)
putc (character, output_file);
else
{
*output_cursor++ = character;
output_unused--;
}
}
static void
output_text (const char *text, int length)
{
int count;
if (!output_file && length > output_unused)
make_room_for (length);
if (output_file)
{
count = fwrite (text, length, 1, output_file);
if (count != 1)
M4ERROR ((EXIT_FAILURE, errno, "ERROR: Copying inserted file"));
}
else
{
memcpy (output_cursor, text, (size_t) length);
output_cursor += length;
output_unused -= length;
}
}
void
shipout_text (struct obstack *obs, const char *text, int length)
{
static boolean start_of_output_line = TRUE;
char line[20];
const char *cursor;
if (obs != NULL)
{
obstack_grow (obs, text, length);
return;
}
if (output_diversion == NULL)
return;
if (!sync_output)
switch (length)
{
case 8: OUTPUT_CHARACTER (*text); text++;
case 7: OUTPUT_CHARACTER (*text); text++;
case 6: OUTPUT_CHARACTER (*text); text++;
case 5: OUTPUT_CHARACTER (*text); text++;
case 4: OUTPUT_CHARACTER (*text); text++;
case 3: OUTPUT_CHARACTER (*text); text++;
case 2: OUTPUT_CHARACTER (*text); text++;
case 1: OUTPUT_CHARACTER (*text);
case 0:
return;
default:
output_text (text, length);
}
else
for (; length-- > 0; text++)
{
if (start_of_output_line)
{
start_of_output_line = FALSE;
output_current_line++;
#ifdef DEBUG_OUTPUT
printf ("DEBUG: cur %d, cur out %d\n",
current_line, output_current_line);
#endif
if (output_current_line != current_line)
{
sprintf (line, "#line %d", current_line);
for (cursor = line; *cursor; cursor++)
OUTPUT_CHARACTER (*cursor);
if (output_current_line < 1)
{
OUTPUT_CHARACTER (' ');
OUTPUT_CHARACTER ('"');
for (cursor = current_file; *cursor; cursor++)
OUTPUT_CHARACTER (*cursor);
OUTPUT_CHARACTER ('"');
}
OUTPUT_CHARACTER ('\n');
output_current_line = current_line;
}
}
OUTPUT_CHARACTER (*text);
if (*text == '\n')
start_of_output_line = TRUE;
}
}
void
make_diversion (int divnum)
{
struct diversion *diversion;
if (output_diversion)
{
output_diversion->file = output_file;
output_diversion->used = output_diversion->size - output_unused;
output_diversion = NULL;
output_file = NULL;
output_cursor = NULL;
output_unused = 0;
}
current_diversion = divnum;
if (divnum < 0)
return;
if (divnum >= diversions)
{
diversion_table = (struct diversion *)
xrealloc (diversion_table, (divnum + 1) * sizeof (struct diversion));
for (diversion = diversion_table + diversions;
diversion <= diversion_table + divnum;
diversion++)
{
diversion->file = NULL;
diversion->buffer = NULL;
diversion->size = 0;
diversion->used = 0;
}
diversions = divnum + 1;
}
output_diversion = diversion_table + divnum;
output_file = output_diversion->file;
output_cursor = output_diversion->buffer + output_diversion->used;
output_unused = output_diversion->size - output_diversion->used;
output_current_line = -1;
}
void
insert_file (FILE *file)
{
char buffer[COPY_BUFFER_SIZE];
size_t length;
if (!output_diversion)
return;
for (;;)
{
length = fread (buffer, 1, COPY_BUFFER_SIZE, file);
if (ferror (file))
M4ERROR ((EXIT_FAILURE, errno, "ERROR: Reading inserted file"));
if (length == 0)
break;
output_text (buffer, length);
}
}
void
insert_diversion (int divnum)
{
struct diversion *diversion;
if (divnum < 0 || divnum >= diversions)
return;
diversion = diversion_table + divnum;
if (diversion == output_diversion)
return;
if (output_diversion)
{
if (diversion->file)
{
rewind (diversion->file);
insert_file (diversion->file);
}
else if (diversion->buffer)
output_text (diversion->buffer, diversion->used);
output_current_line = -1;
}
if (diversion->file)
{
fclose (diversion->file);
diversion->file = NULL;
}
else if (diversion->buffer)
{
free (diversion->buffer);
diversion->buffer = NULL;
diversion->size = 0;
diversion->used = 0;
}
}
void
undivert_all (void)
{
int divnum;
for (divnum = 1; divnum < diversions; divnum++)
insert_diversion (divnum);
}
void
freeze_diversions (FILE *file)
{
int saved_number;
int last_inserted;
int divnum;
struct diversion *diversion;
struct stat file_stat;
saved_number = current_diversion;
last_inserted = 0;
make_diversion (0);
output_file = file;
for (divnum = 1; divnum < diversions; divnum++)
{
diversion = diversion_table + divnum;
if (diversion->file || diversion->buffer)
{
if (diversion->file)
{
fflush (diversion->file);
if (fstat (fileno (diversion->file), &file_stat) < 0)
M4ERROR ((EXIT_FAILURE, errno, "Cannot stat diversion"));
fprintf (file, "D%d,%d", divnum, (int) file_stat.st_size);
}
else
fprintf (file, "D%d,%d\n", divnum, diversion->used);
insert_diversion (divnum);
putc ('\n', file);
last_inserted = divnum;
}
}
if (saved_number != last_inserted)
fprintf (file, "D%d,0\n\n", saved_number);
}