/*
 * GNU m4 -- A simple macro processor
 * Copyright (C) 1989, 1990 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 1, 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, write to the Free Software
 * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 */

/*
 * MS-DOS port (c) 1990 by Thorsten Ohl, ohl@gnu.ai.mit.edu
 * This port is also distributed under the terms of the
 * GNU General Public License as published by the
 * Free Software Foundation.
 *
 * Please note that this file is not identical to the
 * original GNU release, you should have received this
 * code as patch to the official release.
 *
 * $Header: e:/gnu/m4/RCS/output.c 0.5.1.0 90/09/28 18:36:42 tho Exp $
 */

#include "m4.h"

#ifdef MSDOS
#include <io.h>
#include <malloc.h>
int mkstemp (char *tmpl);
#endif /* MSDOS */

/* 
 * Output functions.  Most of the complexity is for handling cpp like
 * sync lines.
 *
 * This code is fairly entangled with the code in input.c, and maybe it
 * belongs there?
 */

/* number of output lines for current input line */
int output_lines;

/* number of input line we are generating output for */
int output_current_line;


/* current output stream */
static FILE *output;

/* table of diversion files */
static FILE **divtab;

/* number of diversions allocated */
static int ndivertion;


/* 
 * Output initialisation.  It handles allocation of memory for
 * diversions.  This is done dynamically, in case we want to expand it
 * later.
 */
void 
output_init()
{
    int i;

    output = stdout;

    ndivertion = 10;
    divtab = (FILE **)xmalloc(ndivertion * sizeof(FILE *));
    for (i = 0; i < ndivertion; i++)
	divtab[i] = nil;
    divtab[0] = stdout;
}


/* 
 * Output a sync line for line number LINE, with an optional file name
 * FILE specified.
 */

void 
sync_line(line, file)
    int line;
    char *file;
{
    if (output == nil)
	return;

    fprintf(output, "#line %d", line);
    if (file != nil)
	fprintf(output, " \"%s\"", file);
    putc('\n', output);
}

/* 
 * Output TEXT to either an obstack or a file.  If OBS is nil, and there
 * is no output file, the text is discarded.
 *
 * If we are generating sync lines, the output have to be examined,
 * because we need to know how much output each input line generates.
 * In general, sync lines are output whenever a single input lines
 * generates several output lines, or when several input lines does not
 * generate any output.
 */
void 
shipout_text(obs, text)
    struct obstack *obs;
    char *text;
{
    static boolean start_of_output_line = true;

    if (obs != nil) {			/* output to obstack OBS */
	obstack_grow(obs, text, strlen(text));
	return;
    }
    if (output == nil)			/* discard TEXT */
	return;

    if (!sync_output)
	fputs(text, output);
    else {
	for (; *text != '\0'; text++) {
	    if (start_of_output_line) {
		start_of_output_line = false;

#ifdef DEBUG_OUTPUT
		printf("DEBUG: cur %d, cur out %d, out %d\n",
		       current_line, output_current_line, output_lines);
#endif

		if (current_line - output_current_line > 1 || output_lines > 1)
		    sync_line(current_line, nil);
		
		output_current_line = current_line;
	    }
	    putc(*text, output);
	    if (*text == '\n') {
		output_lines++;
		start_of_output_line = true;
	    }
	}
    }
}

/* 
 * Functions for use by diversions.
 */

#if defined(USG) || defined(ultrix)
/* 
 * This does not avoid any races, but its there.  Poor bastards.
 */
#include <fcntl.h>

int 
mkstemp(tmpl)
    char *tmpl;
{
    mktemp(tmpl);
    return open(tmpl, O_RDWR|O_TRUNC|O_CREAT, 0600);
}
#endif /* USG */


#ifdef MSDOS
char template_base[] = "/m4%02dXXXXXX";
char *template;
#endif /* MSDOS */

/* 
 * Make a file for diversion DIVNUM, and install it in the diversion
 * table "divtab".  The file is opened read-write, so we can unlink it
 * immediately.
 */
void 
make_divertion(divnum)
    int divnum;
{
    char buf[256];
    int fd;

    if (output != nil)
	fflush(output);

    if (divnum < 0 || divnum > ndivertion) {
	output = nil;
	return;
    }

    if (divtab[divnum] == nil) {
#ifdef MSDOS
	  {
	    char *p;

	    if ((p = getenv ("TMP")) || (p = getenv ("TEMP")))
	      {
		int len = strlen (p);
		template = (char *) alloca (sizeof (template_base) + len + 1);
		strcpy (template, p);
		p = template + len - 1;
		if (*p == '/' || *p == '\\')	/*  strip trailing slash */
		  *p = '\0';
	      }
	    else
	      {
		template = (char *) alloca (sizeof (template_base) + 2);
		strcpy (template, ".");
	      }
	    strcat (template, template_base);
	  }
        sprintf(buf, template, divnum);
#else /* not MSDOS */
	sprintf(buf, "/tmp/m4.%02d.XXXXXX", divnum);
#endif /* not MSDOS */
	fd = mkstemp(buf);
	if (fd < 0)
	    fatal("can't create file for diversion: %s", syserr());
	divtab[divnum] = fdopen(fd, "w+");
	unlink(buf);
    }
    output = divtab[divnum];

    if (sync_output)
	sync_line(current_line, current_file);
}

/* 
 * Insert diversion number DIVNUM into the current output file.  The
 * diversion is NOT placed on the expansion obstack, because it must not
 * be rescanned.  When the file is closed, it is deleted by the system.
 */
void 
insert_divertion(divnum)
    int divnum;
{
    FILE *div;
    int ch;

    if (divnum < 0 || divnum > ndivertion)
	return;

    div  = divtab[divnum];
    if (div == nil || div == output)
	return;

    if (output != nil) {
	rewind(div);
	while ((ch = getc(div)) != EOF)
	    putc(ch, output);
	
	if (sync_output)
	    sync_line(current_line, current_file); /* BUG HERE -- undivert in the middle of line*/
    }
    fclose(div);
}


/* 
 * Get back all diversions.  This is done just before exiting from
 * main(), and from m4_undivert(), if called without arguments.
 */
void 
undivert_all()
{
    int divnum;

    for (divnum = 1; divnum < ndivertion; divnum++)
	insert_divertion(divnum);
}
