/* readsrc.c - read source files for assembler */

#include "syshead.h"
#include "const.h"
#include "type.h"
#include "flag.h"
#include "file.h"
#include "globvar.h"
#include "macro.h"
#include "scan.h"
#undef EXTERN
#define EXTERN
#include "source.h"

/*
 * Ok, lots of hack & slash here.
 * 1) Added BIG buffer to load entire _primary_ file into memory.
 * 2) This means the primary file can be standard input.
 * 3) Fixed so 'get/include' processing now works.
 * 4) Altered for a 'normal' style buffer otherwise (MINIBUF)
 * 5) Have the option of completely unbuffered if you need the last Kb.
 *
 * RDB.
 */

#ifndef __AS386_16__
#ifndef BIGBUFFER
#define BIGBUFFER	1	/* For most machines we have the memory */
#endif
#endif

#ifdef MSDOS
#define off_t		long	/* Not a typedef! */
#endif

#ifndef MINIBUF
#define MINIBUF		1	/* Add in a reasonable buffer */
#endif

struct get_s			/* to record included files */
{
    fd_t fd;
    unsigned line;
    off_t position;
};

PRIVATE char hid_filnambuf[FILNAMLEN + 1];	/* buffer for file name */

PRIVATE struct get_s hid_getstak[MAXGET];	/* GET stack */
PRIVATE struct get_s *getstak;	/* ptr */

#if BIGBUFFER == 1
PRIVATE char *mem_start = 0, *mem_end;
#endif

PRIVATE char hid_linebuf[LINLEN];	/* line buffer */
PRIVATE char *eol_ptr;

PRIVATE char *maclinebuf;
PRIVATE char *maclineptr;

#if MINIBUF == 1
PRIVATE void inp_seek P((int fd, long posn));
PRIVATE long inp_tell P((int fd));
PRIVATE int inp_line P((int fd, char * buf, int size));
#endif

FORWARD void clearsource P((void));
FORWARD void line_too_long P((void));

PRIVATE void clearsource()
{
}

PRIVATE void line_too_long()
{
    symname = linebuf + (LINLEN - 1);	/* spot for the error */
    error(LINLONG);		/* so error is shown in column LINLEN - 1 */
}

/* initialise private variables */

PUBLIC void initsource()
{
    filnamptr = hid_filnambuf;
    getstak = hid_getstak;
    clearsource();		/* sentinel to invoke blank skipping */
}

PUBLIC fd_t open_input(name)
char *name;
{
    fd_t fd;
#if BIGBUFFER == 1
    off_t filelength = -1;

    if( mem_start == 0 && strcmp(name, "-") == 0 )
       fd = 0;
    else
#endif
#ifdef O_BINARY
    if ((unsigned) (fd = open(name, O_RDONLY|O_BINARY)) > 255)
	as_abort("error opening input file");
#else
    if ((unsigned) (fd = open(name, O_RDONLY)) > 255)
	as_abort("error opening input file");
#endif

#if BIGBUFFER == 1
    if( mem_start == 0 )
    {
	size_t memsize = 0;
	int cc;

	if(fd)
	{
	   struct stat st;
	   if( fstat(fd, &st) >= 0 )
	      filelength = st.st_size;
	   if( filelength > (((unsigned)-1)>>1)-3 )
	   {
	      mem_end = mem_start = "\n\n";
	      goto cant_do_this;
 	   }
	}

	if (filelength > 0) {
	   if( (mem_start = malloc(filelength+4)) == 0 )
	   {
	      mem_end = mem_start = "\n\n";
	      goto cant_do_this;
	   }
	   memsize = filelength;

	   filelength = read(fd, mem_start, filelength);
	} else
	   filelength = 0;

	for(;;)
	{
	    if( filelength >= memsize )
	    {
		if (memsize > 16000)
		    mem_start = asrealloc(mem_start, (memsize+=16384)+4);
		else
		    mem_start = asrealloc(mem_start, (memsize+=memsize+32)+4);
	    }
	    cc = read(fd, mem_start+filelength,
			  (size_t)(memsize-filelength));
	    if( cc <= 0 ) break;
	    filelength+=cc;
	}

	*(mem_end=mem_start+filelength) = '\n';
	mem_end[1] = '\0';

	infiln = infil0 = 0;	/* Assemble from memory */
	if(fd) close(fd);
	fd = -1;
    }
cant_do_this:
#endif

    clearsource();
    return fd;
}

/*
  handle GET pseudo_op
  stack state of current file, open new file and reset global state vars
  file must be seekable for the buffer discard/restore method to work
*/

PUBLIC void pget()
{
    if (infiln >= MAXGET)
	error(GETOV);
    else
    {
	char save;

	skipline();
	listline();

	getstak->fd = infil;
	getstak->line = linum;
	if (infiln != 0)
#if MINIBUF == 1
	    getstak->position = inp_tell(infil);
#else
	    getstak->position = lseek(infil, 0L, 1);
#endif
	else
	    getstak->position = (off_t)eol_ptr;
	++getstak;
	++infiln;
	linum = 0;

	for(lineptr=symname; *lineptr != EOLCHAR; lineptr++)
	   if( *lineptr <= ' ' ) break;
	save = *lineptr; *lineptr = '\0';
	infil = open_input(symname);
	*lineptr = save;
	getsym();
    }
}

/* process end of file */
/* close file, unstack old file if current one is included */
/* otherwise switch pass 0 to pass 1 or exit on pass 2 */
/* end of file may be from phyical end of file or an END statement */

PUBLIC void pproceof()
{
    if (infiln != 0)
	close(infil);
    if (infiln == infil0)
	/* all conditionals must be closed before end of main file (not GETs) */
    {
	if (blocklevel != 0)
	    error(EOFBLOCK);
	if (iflevel != 0)
	    error(EOFIF);
	if (pass && (lcdata & UNDBIT))
	    error(EOFLC);
	lcptr->data = lcdata;
	lcptr->lc = lc;
    }
    /* macros must be closed before end of all files */
    if (macload)
	error(EOFMAC);
    if (linebuf != lineptr)
        listline();		/* last line or line after last if error */
    if (infiln != infil0)
    {
	--getstak;
	infil = getstak->fd;
	linum = getstak->line;
	if (--infiln == 0)
	    eol_ptr = (void*)getstak->position;
	else
#if MINIBUF == 1
	    inp_seek(infil, getstak->position);
#else
	    lseek(infil, getstak->position, 0);
#endif
    }
    else if (pass!=last_pass)
    {
	pass++;
	if( last_pass>1 && last_pass<30 && dirty_pass && pass==last_pass )
	   last_pass++;

	if( pass==last_pass )
	   objheader();		/* while pass 1 data all valid */
	binmbuf = 0;		/* reset zero variables */
	maclevel = iflevel = blocklevel =
	    totwarn = toterr = linum = macnum = 0;
	initp1p2();		/* reset other varaiables */
	if(pass==last_pass)
	   binaryc = binaryg;
#ifdef I80386
	defsize = idefsize;
	cpuid = origcpuid;
#endif
	if(pass==last_pass)
	{
	   list.current = list.global;
	   maclist.current = maclist.global;
	   as_warn.current = TRUE;
	   if (as_warn.semaphore < 0)
	       as_warn.current = FALSE;
	}

	if (infiln != 0)
	    infil = open_input(filnamptr);
        else
	    eol_ptr=0;

	if(pass==last_pass)
	   binheader();

	line_zero();
    }
    else
	finishup();
}

/*
  read 1 line of source.
  Source line ends with '\n', line returned is null terminated without '\n'.
  Control characters other than blank, tab and newline are discarded.
  Long lines (length > LINLEN) are truncated, and an error is generated.
  On EOF, calls pproceof(), and gets next line unless loading a macro.
  This is where macro lines are recursively expanded.
*/

PUBLIC void readline()
{
    int cc = 0;

    listpre = FALSE;		/* not listed yet */
    if (maclevel != 0)
    {
      register char *bufptr;	/* hold *bufptr in a reg char variable */
      register char *reglineptr;	/* if possible (not done here) */
      char *oldbufptr;
      struct schain_s *parameters;
      char paramnum;
      unsigned int remaining;	/* space remaining in line + 2 */
				/* value 0 not used except for temp predec */
				/* value 1 means error already gen */
				/* values 1 and 2 mean no space */

      for (; maclevel != 0;
	     macpar = macstak->parameters, ++macstak, --maclevel)
	if (*(bufptr = macstak->text) != ETB)
 /* nonempty macro, process it and return without continuing the for loop */
	{
	    if (!macflag)
	    {
		maclinebuf = linebuf;
		maclineptr = lineptr;
		macflag = TRUE;
	    }
	    remaining = LINLEN + 2;
	    lineptr = linebuf = reglineptr = hid_linebuf;
	    while (*bufptr++ != EOLCHAR)
	    {
		if (bufptr[-1] == MACROCHAR && *bufptr >= '0' && *bufptr <= '9')
		{
		    parameters = macstak->parameters;
		    for (paramnum = *bufptr++; paramnum-- != '0';)
			if ((parameters = parameters->next) == NUL_PTR)
			    break;
		    if (parameters != NUL_PTR)
		    {
			for (oldbufptr = bufptr, bufptr = parameters->string;
			     *bufptr++ != 0;)
			{
			    if (--remaining <= 1)
			    {
				if (remaining != 0)
				    line_too_long();
				remaining = 1;
				break;	/* forget rest, param on 1 line */
			    }
			    *reglineptr++ = bufptr[-1];
			}
			bufptr = oldbufptr;
		    }
		}
		else
		{
		    if (--remaining <= 1)
		    {
			if (remaining != 0)
			    line_too_long();
			remaining = 1;
		    }
		    else
			*reglineptr++ = bufptr[-1];
		}
	    }
	    macstak->text = bufptr;
#if 0
            *reglineptr = 0;
            printf("MLINE:%s.\n", lineptr);
#endif
	    *reglineptr = EOLCHAR;
	    return;
	}
    }
    if (macflag)
    {
	linebuf = maclinebuf;
	lineptr = maclineptr;
	macflag = FALSE;
    }
    /* End of macro expansion processing */

again:	/* On EOF for main or included files */
    ++linum;

#if BIGBUFFER == 1
    if( infiln == 0 )
    {
       if( eol_ptr == 0 ) eol_ptr = mem_start-1;
       else *eol_ptr = '\n';
       linebuf = lineptr = eol_ptr + 1;
       cc = (mem_end - linebuf);

       /* memchr not strchr 'cause some implementations of strchr are like:
	  memchr(x,y,strlen(x)); this is _BAD_ with BIGBUFFER
	*/
       if((eol_ptr = memchr(linebuf, '\n', cc)) == 0 && cc > 0)
          cc = -1;
    }
    else
#endif
    {
       lineptr = linebuf = hid_linebuf;
       *(hid_linebuf+sizeof(hid_linebuf)-2) = '\0';	/* Term */

#if MINIBUF == 1
       cc = inp_line(infil, linebuf, sizeof(hid_linebuf)-2);
       if( cc >= 0 )
          eol_ptr = linebuf+cc-1;
#else
       cc = read(infil, linebuf, sizeof(hid_linebuf)-2);
       if( cc > 0 )
       {
          eol_ptr = memchr(linebuf, '\n', cc);
	  if( eol_ptr == 0 )
	     eol_ptr = hid_linebuf+sizeof(hid_linebuf)-2;
	  else
	     lseek(infil, (long)(eol_ptr+1-hid_linebuf)-cc, 1);
       }
#endif
    }

    if( cc <= 0 )
    {
        if( cc < 0 ) as_abort("error reading input");

        clearsource();
        pproceof();
	listpre = FALSE;
        if (macload)
        {
	    symname = lineptr;
	    return;		/* macro not allowed across eof */
        }
        goto again;
    }

#if 0
    *eol_ptr = 0;
    printf("LINE:%s.\n", lineptr);
#endif
    *eol_ptr = EOLCHAR;
}

PUBLIC void skipline()
{
    if(macflag)
        lineptr = strchr(hid_linebuf, EOLCHAR);
    else
        lineptr = eol_ptr;
}

#if MINIBUF == 1
PRIVATE char input_buf[1024];		/* input buffer */
PRIVATE int  in_start=0, in_end=0;
PRIVATE long ftpos = 0;
PRIVATE int  lastfd = -1;

PRIVATE int inp_line(fd, buf, size)
int fd;
char * buf;
int size;
{
   int offt = 0;
   if( fd!=lastfd ) inp_seek(-1, 0L);
   for(;;)
   {
      if(in_start >= in_end)
      {
	 lastfd = -1;
         ftpos = lseek(fd, 0L, 1);
	 in_start = 0;
         in_end = read(fd, input_buf, sizeof(input_buf));
	 if( in_end <=0 ) return in_end;
	 lastfd = fd;
      }
      if( (buf[offt++] = input_buf[in_start++]) == '\n' || offt >= size )
         break;
   }
   return offt;
}

PRIVATE long inp_tell(fd)
int fd;
{
   if( fd != lastfd )
      return lseek(fd, 0L, 1);
   else
      return ftpos + in_start;
}

PRIVATE void inp_seek(fd, posn)
int fd;
long posn;
{
   if( lastfd != -1 )
      lseek(lastfd, ftpos+in_start, 0);
   lastfd = -1;
   in_end = 0;
   if( fd >= 0 )
      lseek(fd, posn, 0);
}

#endif
