#ifndef lint
static char *RCSid = "$Id: stack.c,v 1.13 1993/05/10 06:06:43 anders Exp anders $";
#endif

/*
 *  The Regina Rexx Interpreter
 *  Copyright (C) 1992-1994  Anders Christensen <anders@pvv.unit.no>
 *
 *  This library is free software; you can redistribute it and/or
 *  modify it under the terms of the GNU Library General Public
 *  License as published by the Free Software Foundation; either
 *  version 2 of the License, or (at your option) any later version.
 *
 *  This library 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
 *  Library General Public License for more details.
 *
 *  You should have received a copy of the GNU Library General Public
 *  License along with this library; if not, write to the Free
 *  Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 */

/* Consept of implementation: 

 * In this description, the stack is described as growing upwards

 * The stack has two pointers to the ends of a double-linked list,
 * which contains entries consisting of just a pointer to the text (in
 * a struct streng) in addition to pointers to next and previous
 * entry.  Last box' next, and first box' prev is both NIL. The text
 * pointer points to the text of that line in the stack.

 * If the text pointer is NIL, then that box in the stack-structure is
 * just symbolizing a stack buffer mark. The integer variable
 * 'buffers' contains the current number of buffermarks in the stack.
 * A buffer is the lines between two buffermarks. The first buffer mark
 * (for buffer 0) is not marked, but is implicit the bottom of the
 * stack. The last buffer is the lines between the uppermost
 * buffermark and the top of the stack.

 * Initialy all lines are put in buffer 0. When a new buffer is
 * created, lines pushed lifo is put on top of that buffer, and lines
 * queued fifo are put in the bottom of that buffer (i.e. not in the
 * bottom of buffer 0.) Lines pushed on the stack are 'eaten' and
 * there must not be any referances to them in other parts of the
 * program after they have been pushed.

 * When reading lines from the stack, and all lines in the current
 * buffer has been read, the buffer-mark will be removed, and lines
 * are read from the buffer underneath. Lines read from the stack must
 * be free'ed by the routine that popped them from the stack.

 * When the whole stack is empty, lines are read from standard input.

 * Buffer 0 is contains the lines between the bottom of the stack, and
 * the first buffermark on the stack, the first buffer, is the lines
 * between buffermark 1 and buffer mark 2.

 * When creating a buffer, the value returned will be the number of
 * buffermarks in the stack after the new buffermark is created.
 * When destroying buffers, the parameter given will equal the number
 * of the lowest buffermark to be destroyed (i.e dropbuf 4) will leave
 * 3 buffermarks on the stack).

 * Possible extentions:
 *  o A new 'hard-buffer', that is not removed when a line is popped
 *    from it while it is empty
 */


#include "rexx.h"
#include <stdio.h>
#include <assert.h>
#include <ctype.h>

/*
 * Hide the definition of the stack from the rest of the program
 */
typedef struct stacklinestruct *stacklineptr ;

typedef struct stacklinestruct 
{ 
   stacklineptr next, prev ;
   streng *contents ;
} stackline ;


/*
 * Pointers to first and last line in the stack.
 */
static stacklineptr lastline=NULL ;
static stacklineptr firstline=NULL ;


/*
 * The number of buffermarks in the stack
 */
static int buffers=0 ;


/* 
 * Pointers to first and last entry in the temporary stack
 */
static stacklineptr firstbox=NULL, lastbox=NULL ;

/* 
 * This routines accumulates stackentries, but without pushing them
 * on the actual stack, used when stacking the output of an external
 * command. If such a command also reads from the stack, the output
 * can not be flushed to the stack before the command has finished 
 * reading (else, the command might read its own output.)
 *
 * Parameter is the streng to stack.
 */
void tmp_stack( streng *value, int is_fifo ) 
{
   stacklineptr ptr ;

   assert( ((lastbox)&&(firstbox)) || ((!lastbox)&&(!firstbox)) ) ;
   assert( (value) && (Str_max(value) >= Str_len(value)) ) ;

   if (currlevel->u.options.flushstack)
   {
      if (is_fifo)
         stack_fifo( value ) ;
      else
         stack_lifo( value ) ;
   }
   else
   {
      ptr = Malloc( sizeof(struct stacklinestruct) ) ;
      ptr->contents = value ;
      ptr->next = NULL ;
      ptr->prev = lastbox ;

 
      if (firstbox)
         lastbox->next = ptr ;
      else
         firstbox = ptr ;
      lastbox = ptr ;
   }
}


/*
 * Flushes the content of the temporary stack created by (possibly 
 * multiple) calls to tmp_stack(). 
 *
 * If parameter is true, lines are stacked fifo, which requires their
 * order to be reversed first.
 */
void flush_stack( int is_fifo ) 
{
   stacklineptr ptr, tptr ;

   if (firstbox==NULL) 
   {
      /* nothing in temporary stack to flush */
      assert(lastbox==NULL) ;
      return ;
   }

   /* stack it either fifo or lifo */
   if (is_fifo)
   {
      /* if fifo, temporary stack must be reversed first */
      /* travelling in 'prev' direction, since stack is reversed */
      for (ptr=firstbox; ptr; ptr=ptr->prev )  
      {
         tptr = ptr->prev ;
         ptr->prev = ptr->next ;
         ptr->next = tptr ;
      }
    
      /* temporary stack now in right order, link to top of real stack */
      firstbox->next = firstline ;
      if (firstline)
         firstline->prev = firstbox ;
      else
         lastline = firstbox ;
      firstline = lastbox ;
   }
   else
   {
      /* everything is in right order, just link them together */   
      firstbox->prev = lastline ;
      if (lastline)
         lastline->next = firstbox ;
      else
         firstline = firstbox ;
      lastline = lastbox ;   
   }

   /* reset the pointers, to signify that temporary stack is empty */
   lastbox = firstbox = NULL ;
}



/*
 * Pushes 'line' onto the REXX stack, lifo, and sets 'lastline' to
 *    point to the new line. The line is put on top of the current
 *    buffer. 
 */
void stack_lifo( streng *line ) 
{
   stacklineptr newbox ;

   newbox = (stacklineptr)Malloc(sizeof(stackline)) ;
   if (lastline) 
   {
      lastline->next = newbox ;
      newbox->prev = lastline ; 
   }
   else 
   {
      newbox->prev = NULL ;
      firstline = newbox ; 
   }
   if (!line)
      buffers++ ;

   newbox->next = NULL ;
   newbox->contents = line ;
   lastline = newbox ; 
}


/* 
 * Puts 'line' on the REXX stack, fifo. This routine is similar to
 *    stack_lifo but the differences are big enough to have a separate
 *    routine. The line is put in the bottom of the current buffer, 
 *    that is just above the uppermost buffer mark, or at the bottom 
 *    of the stack, if there are no buffer marks.
 */
void stack_fifo( streng *line ) 
{
   stacklineptr newbox, ptr ;

   if (!line) 
      buffers++ ;

   /* Bug: inserts into bottom of stack, not bottom of current buffer */
   newbox = (stacklineptr)Malloc(sizeof(stackline)) ;
   newbox->prev = newbox->next = NULL ;
   newbox->contents = line ;

   for (ptr=lastline;(ptr)&&(ptr->contents);ptr=ptr->prev) ;

   if (ptr) 
   {
      newbox->prev = ptr ;
      newbox->next = ptr->next ;
      if (ptr->next)
         ptr->next->prev = newbox ;
      else 
         lastline = newbox ;
      ptr->next = newbox ; 
   }
   else 
   {
      newbox->next = firstline ;
      firstline = newbox ; 
      if (newbox->next)
	 newbox->next->prev = newbox ;
      if (!lastline) 
         lastline = newbox ; 
   }
}


/* 
 * Removes one (or several) buffers from the stack. The number of
 *    buffers to remove is decided by 'number', which is the number of
 *    buffers to remain on the stack. The global variable buffer
 *    contains the number of buffermarks currently in the stack.
 * 
 * When number==2, buffer 0 (the implisit), and buffer 1 (the first 
 *    buffer created by the user) are the only that remain. All lines
 *    from buffermark 2 (inclusive) and above are removed. Remember
 *    that buffer N is the lines following the N'th buffermark, until
 *    end-of-stack or another buffermark is reached.
 *
 * If number is less than zero, then abs(number) buffers is removed
 *    from the top of the stack (or until the stack is empty). 

 * If the number is zero, the stack is emptied. If a buffer with a
 *    number that is higher than the current buffer is spesified,
 *    errorcode is retured.  
 */

int drop_buffer( int number ) 
{
   stacklineptr ptr, nptr ;

   if (number<0) 
      number = (buffers + number + 1) ;

   assert(buffers>=0) ;
   for (ptr=lastline; (ptr) && (number<=buffers); ptr=nptr) 
   {
      nptr = ptr->prev ; 
      if (ptr->contents)
         Free(ptr->contents) ; 
      else
         buffers-- ;
      Free(ptr) ; 
      if (nptr)
         nptr->next = NULL ;
   }
   if (!(lastline=ptr))
      firstline = NULL ;
   
   if (buffers<0)
      buffers = 0 ;

   return buffers ;
}


/* 
 * Fetches a line from the top of the stack. If the current buffer
 *    does not contain any lines, it is removed and the second current
 *    buffer is search for a line etc. If there isn't any lines in the
 *    stack, a line is read from the standard input.
 */
streng *popline( void ) 
{
   extern sysinfo systeminfo ;
   streng *contents ;
   stacklineptr line ;

   if ((line=lastline))
   {
      contents = line->contents ;
      lastline = line->prev ; 
      if (!line->prev) 
         firstline = NULL ;
      else
         line->prev->next = NULL ; 

      Free(line) ;
      if (!contents) 
      {
         buffers-- ;
         contents = popline() ; 
      }  
   }
   else 
   {
      int rc = HOOK_GO_ON ;
      if (systeminfo->hooks & HOOK_MASK(HOOK_PULL))
         rc = hookup( HOOK_PULL, &contents ) ;

      if (rc==HOOK_GO_ON)
         contents = readkbdline() ;   

      assert( contents ) ;
   }
    
   return contents ;
}


/* 
 * Counts the lines in the stack.
 */
int lines_in_stack( void ) 
{
   stacklineptr ptr ;
   int lines ;

   ptr = firstline ;
   for (lines=0;ptr;ptr=ptr->next) 
      if (ptr->contents)
         lines++ ;

   return lines ;
}


/* 
 * Returns boolean expression: is the stack empty?
 */
int stack_empty( void )
{
   stacklineptr ptr ;

   for (ptr=firstline;ptr;ptr=ptr->next) 
      if (ptr->contents)
         return 0 ;

   return 1 ;
}


#ifdef TRACEMEM
/* 
 * Marks all chunks of dynamic allocated memory that are allocated 
 *   to the stack subsystem.
 */
void mark_stack( void )
{
   stacklineptr ptr ;

   for (ptr=firstline;ptr;ptr=ptr->next) 
   {
      markmemory(ptr,TRC_STACKBOX) ;
      if (ptr->contents)
         markmemory(ptr->contents,TRC_STACKLINE) ; 
   }
}
#endif


/* 
 * Creates a new buffer, by putting a buffer mark at the top of the
 *    stack. (This could be implemented as {push_line(NULL)} and a 
 *    special test for (line==NULL) in push_line())
 */
int make_buffer( void ) 
{
   stack_lifo( NULL ) ;
   return buffers ;
}



/* 
 * Dumps the contents of the stack to standard error. Buffer marks are
 *    indicated in the printout.
 */
void type_buffer( void )
{
   stacklineptr ptr ;
   char *cptr, *stop ;
   int counter ;
 
   fprintf(stddump,"==> Lines: %d\n", lines_in_stack()) ;
   fprintf(stddump,"==> Buffer: %d\n", counter=buffers) ;
   for (ptr=lastline; ptr; ptr=ptr->prev) 
   {
      if (ptr->contents)
      {
         putc( '"', stddump ) ;
         stop = Str_end(ptr->contents) ;
	 for (cptr=ptr->contents->value; cptr<stop; cptr++)
            putc( (isprint(*cptr) ? (*cptr) : '?'), stddump ) ;

         putc( '"', stddump ) ;
         putc( 0x0a, stddump ) ;
      }  
      else
         fprintf(stddump,"==> Buffer: %d\n",--counter) ; }

   fprintf(stddump,"==> End of Stack\n") ;
   fflush(stddump) ;
}





