/***************************************************************
  
        bwb_prn.c       Print and Error-Handling Commands
                        for Bywater BASIC Interpreter
  
                        Copyright (c) 1993, Ted A. Campbell
                        Bywater Software
  
                        email: tcamp@delphi.com
  
        Copyright and Permissions Information:
  
        All U.S. and international rights are claimed by the author,
        Ted A. Campbell.
  
   This software is released under the terms of the GNU General
   Public License (GPL), which is distributed with this software
   in the file "COPYING".  The GPL specifies the terms under
   which users may copy and use the software in this distribution.
  
   A separate license is available for commercial distribution,
   for information on which you should contact the author.
  
***************************************************************/

/*---------------------------------------------------------------*/
/* NOTE: Modifications marked "JBV" were made by Jon B. Volkoff, */
/* 11/1995 (eidetics@cerf.net).                                  */
/*                                                               */
/* Those additionally marked with "DD" were at the suggestion of */
/* Dale DePriest (daled@cadence.com).                            */
/*                                                               */
/* Version 3.00 by Howard Wulf, AF5NE                            */
/*                                                               */
/* Version 3.10 by Howard Wulf, AF5NE                            */
/*                                                               */
/*---------------------------------------------------------------*/



#include "bwbasic.h"

/*
We try to allow as many legacy PRINT USING formats as reasonable.
Many legacy PRINT USING formats are incompatible with one another.
For example:
1) some use '%' for strings, others use '%' for numbers, others consider '%' as a lieral.
2) some count a leading or traling signs in the width, while others do not.
3) when a value requires more digits than the assigned width:
   a) some truncate the displayed value to the width, 
   b) some expand the width, 
   c) some print a number of '%' or '*', and 
   d) some halt processing.
There is no perfect solution that will work for all possible dialects.
*/


#define PrintUsingNumberDigit    My->CurrentVersion->UsingDigit   /* Digit placeholder, usually '#' */
#define PrintUsingNumberComma    My->CurrentVersion->UsingComma   /* Comma, such as thousands, usually ',' */
#define PrintUsingNumberPeriod   My->CurrentVersion->UsingPeriod  /* Period, such as dollars and cents, usually '.' */
#define PrintUsingNumberPlus     My->CurrentVersion->UsingPlus    /* Plus  sign, positive value, usually '+' */
#define PrintUsingNumberMinus    My->CurrentVersion->UsingMinus   /* Minus sign, negative value, usually '-' */
#define PrintUsingNumberExponent My->CurrentVersion->UsingExrad   /* Exponential format, usually '^' */
#define PrintUsingNumberDollar   My->CurrentVersion->UsingDollar  /* Currency symbol, usually '$' */
#define PrintUsingNumberFiller   My->CurrentVersion->UsingFiller  /* Print filler, such as checks, usually '*' */
#define PrintUsingLiteral        My->CurrentVersion->UsingLiteral /* The next char is a literal, usually '_' */
#define PrintUsingStringFirst    My->CurrentVersion->UsingFirst   /* The first character of the string, usually '!' */
#define PrintUsingStringAll      My->CurrentVersion->UsingAll     /* Print the entire string, usually '&' */
#define PrintUsingStringLength   My->CurrentVersion->UsingLength  /* Print a substring, usually '%' */





/* Prototypes for functions visible only to this file */


static int get_prnfmt(char *buffer, int *position, VariantType *e);
static int xputc(char c);
static int xxputc(char c);
static int xxxputc(char c);
static int bwb_xprint(LineType * l);
static int prn_xxprintf(char *buffer);


int is_empty_filename( char * Buffer )
{
   while( *Buffer == ' ' )
   {
      Buffer++;
   }
   if( *Buffer == BasicNulChar )
   {
      return TRUE;
   }
   return FALSE;
}


FileType * find_file_by_name( char * FileName )
{
   FileType * F;

   if( is_empty_filename( FileName ) )
   {
      /* the rules for Console and Printer vary by command */
      return NULL;
   }
   /* search the list of OPEN files */
   for( F = My->file_head; F != NULL; F = F->next )
   {
      if( F->mode != DEVMODE_CLOSED )
      {
         if( bwb_stricmp( F->filename, FileName ) == 0 )
         {
            /* FOUND */
            return F;
         }
      }
   }
   /* NOT FOUND */
   return NULL;
}


FileType * find_file_by_number( int FileNumber )
{
   FileType * F;
   /* handle MAGIC file numbers */
   if( FileNumber <= 0 )
   {
      /* the rules for Console and Printer vary by command */
      return NULL;
   }
   /* search the list of OPEN files */
   for( F = My->file_head; F != NULL; F = F->next )
   {
      if( F->mode != DEVMODE_CLOSED )
      {
         if( F->FileNumber == FileNumber )
         {
            /* FOUND */
            return F;
         }
      }
   }
   /* NOT FOUND */
   return NULL;
}


FileType * file_new( void )
{
   /* search for an empty slot.  If not found, add a new slot. */
   FileType * F;
   
   for( F = My->file_head; F != NULL; F = F->next )
   {
      if( F->mode == DEVMODE_CLOSED )
      {
         /* FOUND */
         return F;
      }
   }
   /* NOT FOUND */
   F = calloc( 1, sizeof( FileType ) );
   F->next = My->file_head;
   My->file_head = F;
   return F;
}


void file_clear( FileType * F )
{
   /* clean up a file slot that is no longer needed */

#if NEW_VIRTUAL
   clear_virtual_by_file( F->FileNumber );
#endif /* NEW_VIRTUAL */
   F->FileNumber = 0;
   F->mode = DEVMODE_CLOSED;   /* DEVMODE_ item */
   F->width = 0;  /* width for OUTPUT and APPEND; reclen for RANDOM; not used for INPUT or BINARY */
   F->col = 0; /* current column for OUTPUT and APPEND */
   F->row = 0; /* current row for OUTPUT and APPEND */
   F->EOF_LineNumber = 0; /* CBASIC-II: IF END # filenumber THEN linenumber */
   F->delimit = BasicNulChar; /* DELIMIT for READ and WRITE */
   F->filename[0] = BasicNulChar;  /* filename */
   if( F->cfp != NULL )
   {
      fclose( F->cfp ); /* F->cfp != NULL */
      F->cfp = NULL;
   }
   if( F->buffer != NULL ) /* pointer to character buffer for RANDOM */
   {
      free( F->buffer );
      F->buffer = NULL;
   }

}

int file_next_number( void )
{
   int        FileNumber;
   FileType * F;
   FileNumber = 0;
   for (F = My->file_head; F != NULL; F = F->next)
   {
      if (F->mode != DEVMODE_CLOSED)
      {
         if( F->FileNumber > FileNumber )
         {
            FileNumber = F->FileNumber;
         }
      }
   }
   /* 'FileNumber' is the highest FileNumber that is currently open */
   FileNumber++;
   return FileNumber;
}         



/***************************************************************
  
      FUNCTION:       bwx_putc()
  
   DESCRIPTION:    This function outputs a single character
         to the default output device.
  
***************************************************************/

int
bwx_PRINT(char c)
{
   bwx_DEBUG(__FUNCTION__);

   /* send character to console */
   return fputc(c, My->SYSOUT->cfp);
}

int
bwx_LPRINT(char c)
{
   bwx_DEBUG(__FUNCTION__);

   /* send character to printer */
   return fputc(c, My->SYSPRN->cfp);
}

int
prn_lprintf(char *buffer)
{
   bwx_DEBUG(__FUNCTION__);

   while (*buffer != BasicNulChar)
   {
      bwx_LPRINT(*buffer);
      buffer++;
   }
   return 0;
}

int
prn_xprintf(char *buffer)
{
   /* Catch-22: an error has occurred before the devicce table is loaded */
   int             n;
   bwx_DEBUG(__FUNCTION__);
   
   
   n = My->SYSOUT->width;

   if (n > 0)
   {
      int i;
      i = 0;
      while (*buffer)
      {
         fputc(*buffer, My->SYSOUT->cfp);
         buffer++;
         if (*buffer == '\n')
         {
            i = 0;
         }
         i++;
         if (i >= n)
         {
            fputc('\n', My->SYSOUT->cfp);
            i = 0;
         }
      }
   }
   else
   {
      /* raw */
      while (*buffer)
      {
         fputc(*buffer, My->SYSOUT->cfp);
         buffer++;
      }
   }
   fflush(My->SYSOUT->cfp);
   return 0;
}

static void CleanNumericString(char *prnbuf, int RemoveDot)
{
   /* remove trailing zeroes */
   char           *E;
   char           *D;

   bwx_DEBUG(__FUNCTION__);

   E = bwb_strchr(prnbuf, 'E');
   if (E == NULL)
   {
      E = bwb_strchr(prnbuf, 'e');
   }
   if (E)
   {
      /* SCIENTIFIC == SCALED notation */
      /* trim leading zeroes in exponent */
      char           *F;
      char           *G;
      F = E;
      while (bwb_isalpha(*F))
      {
         F++;
      }
      while (*F == '+' || *F == '-')
      {
         /* skip sign */
         F++;
      }
      G = F;
      while (*G == '0' || *G == ' ')
      {
         /* skip leading zeroes or spaces */
         G++;
      }
      if (G > F)
      {
         bwb_strcpy(F, G);
      }
      G = NULL;   /* no longer valid */
      *E = BasicNulChar;  /* for bwb_strlen()  */
   }
   D = bwb_strchr(prnbuf, '.');
   if (D)
   {
      int             N = bwb_strlen(D);
      if (N > 1)
      {
         int             M;
         N--;
         M = N;
         while (D[N] == '0')
         {
            /* remove trailing zeroes */
            D[N] = '_';
            N--;
         }
         if (RemoveDot)
         {
            if (E)
            {
               /* SCIENTIFIC == SCALED notation */
               /* do NOT remove '.' */
            }
            else
            {
               /* NORMAL  == UNSCALED notation */
               /* remove trailing '.' */
               /* this will only occur for integer
                * values */
               while (D[N] == '.')
               {
                  /* _###. POSITIVE INTEGER */
                  /* -###. NEGATIVE INTEGER */
                  D[N] = '_';
                  N--;
               }
            }
         }
         if (N < M)
         {
            if (E)
            {
               /* SCIENTIFIC == SCALED notation */
               *E = 'E';
               E = NULL;
            }
            N++;
            /* if INTEGER, then N == 0, else N > 0 */
            M++;
            /* if SCIENTIFIC, then  *M == 'E' else *M == BasicNulChar */
            bwb_strcpy(&(D[N]), &(D[M]));
         }
      }
   }
   if (E)
   {
      /* SCIENTIFIC == SCALED notation */
      *E = 'E';
      E = NULL;
   }
   if (prnbuf[1] == '0' && prnbuf[2] == '.')
   {
      /* _0.### POSITIVE FRACTION ==> _.### */
      /* -0.### NEGATIVE FRACTION ==> -.### */
      bwb_strcpy(&(prnbuf[1]), &(prnbuf[2]));
   }
   if (prnbuf[1] == '.' && prnbuf[2] == 'E')
   {
      /* _.E POSITIVE ZERO ==> _0 */
      /* -.E NEGATIVE ZERO ==> _0 */
      bwb_strcpy(prnbuf, " 0");
   }
}

static int SignificantDigits(char *Buffer)
{
   int             NumDigits;
   char           *P;

   bwx_DEBUG(__FUNCTION__);


   /* determine the number of significant digits */
   NumDigits = 0;
   P = Buffer;
   while (*P)
   {
      if (bwb_isalpha(*P))
      {
         /* 'E', 'e', and so on. */
         break;
      }
      if (bwb_isdigit(*P))
      {
         NumDigits++;
      }
      P++;
   }
   return NumDigits;
}

void
BasicNumerc(BasicNumberType Input, char *Output)
{
   /*
   ********************************************************************************
   
   This is essentially sprintf( Output, "%g", Input ), 
   except the rules for selecting between "%e", "%f", and "%d" are different.

   The C rules depend upon the value of the exponent.
   The BASIC rules depend upon the number of significant digits.

   The results of this routine have been verified by the NBS2 test suite, so   
   THINK VERY CAREFULLY BEFORE MAKING ANY CHANGES TO THIS ROUTINE.   
   
   ********************************************************************************
   */

   char           *E;

   bwx_DEBUG(__FUNCTION__);

   /* print in scientific form first, to determine exponent and significant digits */
   sprintf(Output, "% 1.*E", SIGNIFICANT_DIGITS - 1, Input);
   E = bwb_strchr(Output, 'E');
   if (E == NULL)
   {
      E = bwb_strchr(Output, 'e');
   }
   if (E)
   {
      /* valid */
      int             Exponent;
      int             NumDigits;
      int             DisplayDigits;
      int             zz;
      char           *F;   /* pointer to the exponent's value */
      F = E;
      while (bwb_isalpha(*F))
      {
         F++;
      }
      Exponent = atoi(F);
      CleanNumericString(Output, 0);
      NumDigits = SignificantDigits(Output);
      DisplayDigits = MIN( NumDigits, SIGNIFICANT_DIGITS );
      zz = MAX(Exponent,DisplayDigits - Exponent - 2);
      if (zz >= SIGNIFICANT_DIGITS)
      {
         /* SCIENTIFIC */
         sprintf(Output, "%# 1.*E", DisplayDigits - 1, Input);
      }
      else
      if (Input == (int) Input)
      {
         /* INTEGER */
         sprintf(Output, "% *d", DisplayDigits, (int) Input);
      }
      else
      {
         /* FLOAT */
         int             N;
         /* number of digits before the '.' */
         int             M;
         /* number of digits after  the '.' */
         N = Exponent + 1;
         if (N < 0)
         {
            N = 0;
         }
         M = SIGNIFICANT_DIGITS - N;
         if (M < 0)
         {
            M = 0;
         }
         
         sprintf(Output, "%# *.*f", N, M, Input);
      }
      CleanNumericString(Output, 0);
   }
   else
   {
      /* ERROR, NAN, INFINITY, ETC. */
   }
   return;
}



LineType *
bwb_LPRINT(LineType * l)
{
   bwx_DEBUG(__FUNCTION__);
   My->CurrentFile = My->SYSPRN;
   bwb_xprint(l);
   return bwb_zline(l);
}


/***************************************************************
  
        FUNCTION:       bwb_print()
  
        DESCRIPTION:    This function implements the BASIC PRINT
                        command.
  
   SYNTAX:     PRINT [# device-number,][USING format-string$;] expressions...
  
***************************************************************/

LineType *
bwb_QUEST(LineType * l)
{
   bwx_DEBUG(__FUNCTION__);

   return bwb_PRINT(l);
}

static int bwb_print_at(LineType * l)
{
   int             position;
   int             r;
   int             c;
   

   position = 0;
   r = 0;
   c = 0;

   if( line_read_integer_expression(l, &position) == FALSE )
   {
      WARN_SYNTAX_ERROR;
      return FALSE;
   }
   

   if ( line_skip_comma(l))
   {
      /* OK */
   }
   else
   {
      WARN_SYNTAX_ERROR;
      return FALSE;
   }

   if( position < 0 )
   {
      WARN_SYNTAX_ERROR;
      return FALSE;
   }
   if( My->CurrentFile->width <= 0 )
   {
      WARN_SYNTAX_ERROR;
      return FALSE;
   }
   if( My->SCREEN_ROWS <= 0 )
   {
      WARN_SYNTAX_ERROR;
      return FALSE;
   }
   r = position / My->CurrentFile->width;
   c = position - r * My->CurrentFile->width;
   while( r >= My->SCREEN_ROWS )
   {
      r -= My->SCREEN_ROWS;
   }
   r++;
   c++;

   switch (My->OptionTerminalType)
   {
   case C_OPTION_TERMINAL_NONE:
      break;
   case C_OPTION_TERMINAL_ADM:
      fprintf(My->CurrentFile->cfp, "%c=%c%c", 27, r + 32, c + 32);
      break;
   case C_OPTION_TERMINAL_ANSI:
      fprintf(My->CurrentFile->cfp, "%c[%d;%dH", 27, r, c);
      break;
   default:
      WARN_SYNTAX_ERROR;
      return FALSE;
      /* break; */
   }      
   My->CurrentFile->row = r;
   My->CurrentFile->col = c;
   return TRUE;
}

static int bwb_print_num(LineType * l)
{
   int             UserFileNumber;


   if( line_read_integer_expression(l, &UserFileNumber) == FALSE )
   {
      WARN_SYNTAX_ERROR;
      return FALSE;
   }

   if( My->CurrentVersion->OptionVersionBitmask & ( C77 ) )
   {
      /* 
      CBASIC-II: SERIAL & RANDOM file writes
      PRINT # file_number                 ; expression [, expression] ' SERIAL write
      PRINT # file_number , record_number ; expression [, expression] ' RANDOM write
      */

      if( UserFileNumber <= 0 )
      {
         WARN_BAD_FILE_NUMBER;
         return FALSE;
      }
      /* normal file */
      My->CurrentFile = find_file_by_number( UserFileNumber );
      if( My->CurrentFile == NULL )
      {
         WARN_BAD_FILE_NUMBER;
         return FALSE;
      }


      if( line_skip_char( l, ',' ) )
      {
         /* 
         PRINT # file_number , record_number ; expression [, expression] ' RANDOM write
         */
         /* get the RecordNumber */
         int RecordNumber;
         
         if( (My->CurrentFile->mode & DEVMODE_RANDOM) == 0 )
         {
            WARN_BAD_FILE_MODE;
            return FALSE;
         }
         if( My->CurrentFile->width <= 0 )
         {
            WARN_FIELD_OVERFLOW;
            return FALSE;
         }
         if( line_read_integer_expression( l, &RecordNumber ) == FALSE )
         {
            WARN_SYNTAX_ERROR;
            return FALSE;
         }
         if( RecordNumber <= 0 )
         {
            WARN_BAD_RECORD_NUMBER;
            return FALSE;
         }
         RecordNumber--; /* BASIC to C */
         /* if( TRUE ) */
         {
            long offset;
            offset = RecordNumber;
            offset *= My->CurrentFile->width;
            fseek( My->CurrentFile->cfp, offset, SEEK_SET );
         }
      }
      if( line_is_eol( l ) )
      {
         /* PRINT # filenum          */
         /* PRINT # filenum , recnum */
      }
      else
      if( line_skip_char( l, ';' ) )
      {
         /* PRINT # filenum          ; */
         /* PRINT # filenum , recnum ; */
      }
      else
      {
         WARN_SYNTAX_ERROR;
         return FALSE;
      }
      return TRUE;
   }
   /* 
   SERIAL file writes:
   PRINT # file_number   
   PRINT # file_number [, expression]
   */
   if( UserFileNumber < 0 )
   {
      My->CurrentFile = My->SYSPRN;
   }
   else
   if( UserFileNumber == 0 )
   {
      My->CurrentFile = My->SYSOUT;
   }
   else
   {
      /* normal file */
      My->CurrentFile = find_file_by_number( UserFileNumber );
   }
   if( My->CurrentFile == NULL )
   {
      WARN_BAD_FILE_NUMBER;
      return FALSE;
   }
   if( ( My->CurrentFile->mode & DEVMODE_WRITE ) == 0 )
   {
      WARN_BAD_FILE_NUMBER;
      return FALSE;
   }
   if( line_is_eol( l ) )
   {
      /* PRINT # 2 */
   }
   else
   if( line_skip_comma( l ) )
   {
      /* PRINT # 2 , ... */
   }
   else
   {
      WARN_SYNTAX_ERROR;
      return FALSE;
   }
   return TRUE;
}

LineType *
bwb_PRINT(LineType * l)
{
   bwx_DEBUG(__FUNCTION__);

   My->CurrentFile = My->SYSOUT;
   line_skip_spaces(l);
   if ( line_skip_char(l,'@'))
   {
      /* PRINT @ position, ... */
      if( bwb_print_at(l) == FALSE )
      {
         return bwb_zline(l);
      }
   }
   else
   if ( line_skip_word( l, "AT")  )
   {
      /* PRINT AT position, ... */
      if( bwb_print_at(l) == FALSE )
      {
         return bwb_zline(l);
      }
   }
   else
   if ( line_skip_char(l,BasicFileNumberPrefix))
   {
      /* PRINT # file, ... */
      if( bwb_print_num(l) == FALSE )
      {
         return bwb_zline(l);
      }
   }
   bwb_xprint(l);
   if( My->CurrentFile == My->SYSOUT )
   {
      /* FOR I = 1 TO 1000: PRINT "."; : NEXT I : PRINT */
      fflush(My->SYSOUT->cfp);
   }
   return bwb_zline(l);
}

/***************************************************************
  
        FUNCTION:       bwb_xprint()
  
   DESCRIPTION:    This function implements the PRINT
         command, utilizing a specified file our
         output device.
  
***************************************************************/

static int buff_read_using( char * buffer, int * position, char * format_string )
{
   int p;
   
   p = *position;
   
   buff_skip_spaces( buffer, &p );
   if( buff_skip_word( buffer, &p, "USING" ) )
   {
      buff_skip_spaces( buffer, &p );
      if( bwb_isdigit( buffer[p] ) )
      {
          /* PRINT USING ### */
          int LineNumber;
          LineType *x = NULL;
          char *C;
          char *F;

           if( buff_read_line_number(buffer, &p, &LineNumber) == FALSE )
            {
               WARN_SYNTAX_ERROR;
               return FALSE;
            }
           /* check for target label */
           x = find_line_number( LineNumber, TRUE );
           if (x == NULL)
           {
               WARN_UNDEFINED_LINE;
               return FALSE;
           }
           /* line exists */
           if( x->cmdnum != C_IMAGE )
           {
               WARN_UNDEFINED_LINE;
               return FALSE;
           }
           /* line contains IMAGE command */
          C = x->buffer;
          C += x->Startpos;
          F = format_string;
          /* look for leading quote in IMAGE "..." */
          while( *C == ' ' )
          {
             C++;
          }
          if( *C == BasicQuoteChar )
          {
             /* QUOTED */
              /* skip leading quote */
              C++;
              while( *C != BasicQuoteChar && *C != BasicNulChar )
              {
                  /* copy format string, but not the trailing quote */
                  *F = *C;
                  C++;
                  F++;
              }
              /* skip trailing quote */
          }
          else
          {
             /* UNQUOTED */
              while( *C != BasicNulChar )
              {
                  /* copy format string verbatim */
                  *F = *C;
                  C++;
                  F++;
              }
          }
          /* terminate format string */
          *F = BasicNulChar;
          
          buff_skip_spaces(buffer, &p);
          if ( buff_skip_comma(buffer, &p ) == FALSE)
          {
             WARN_SYNTAX_ERROR;
             return FALSE;
          }
          buff_skip_spaces(buffer, &p);
      }
      else
      {
         {
            char * Value = NULL;
            
            if( buff_read_string_expression( buffer, &p, &Value ) == FALSE )
            {
               WARN_SYNTAX_ERROR; /* HERE-Here-here BUG? */
               return FALSE;
            }
            if( Value == NULL )
            {
               WARN_SYNTAX_ERROR;
               return FALSE;
            }
            bwb_strcpy( format_string, Value );
            free( Value );
         }
         buff_skip_spaces(buffer, &p);
         if ( buff_skip_comma(buffer, &p) == FALSE)
         {
            WARN_SYNTAX_ERROR;
            return FALSE;
         }
         buff_skip_spaces(buffer, &p);
      }
      *position = p;
      return TRUE;
   }
   return FALSE;
}

static int bwb_xprint(LineType * l)
{
   int Success = FALSE;
   VariantType e;       /* no leaks */
   VariantType *E = &e; /* no leaks */
   static int      fs_pos;
   int             OutputCR;
   char    format_string[BasicStringLengthMax + 1];

   bwx_DEBUG(__FUNCTION__);
   CLEAR_VARIANT( E );


   /* Detect USING Here */

   format_string[0] = BasicNulChar;
   fs_pos = 0;

   /* get "USING" in format_string */
   if( buff_read_using( l->buffer, &(l->position), format_string ) == TRUE )
   {
       fs_pos = 0;
   }
   
   /* if no arguments, simply print CR and return */


   /* LOOP THROUGH PRINT ELEMENTS */

   OutputCR = TRUE;

   /* 1980 PRINT  , , ,"A" */
   line_skip_spaces(l);
   while( line_is_eol(l) == FALSE )
   {
      /* 1980 PRINT  , , ,"A" */
      if ( line_skip_char( l, ',' /* comma-specific */ ) )
      {
         /* tab over */
         OutputCR = FALSE;
         if( format_string[0] == BasicNulChar )
         {
            /* Tab only if there's no format specification! (JBV) */

            if( My->CurrentVersion->OptionVersionBitmask & ( C77 ) && My->CurrentFile->FileNumber > 0 )
            {
               /* CBASIC-II: files use commas between values */
               xputc(',');
            }
            else
            {
               xputc('\t');
            }
         }
         if( buff_read_using( l->buffer, &(l->position), format_string ) == TRUE )
         {
             fs_pos = 0;
            OutputCR = TRUE;
         }
      }
      else
      if ( line_skip_char( l, ';' /* semicolon-specific */ ) )
      {
         /* concatenate strings */
         OutputCR = FALSE;
         if( buff_read_using( l->buffer, &(l->position), format_string ) == TRUE )
         {
             fs_pos = 0;
            OutputCR = TRUE;
         }
         if( My->CurrentVersion->OptionVersionBitmask & ( C77 ) && My->CurrentFile->FileNumber > 0 )
         {
            /* CBASIC-II: files cannot use semicolon */
            WARN_SYNTAX_ERROR;
            goto EXIT;
         }
      }
      else
      {
         /* resolve the string */
         OutputCR = TRUE;
         if( line_read_expression( l, E ) == FALSE )
         {
            WARN_SYNTAX_ERROR;
            goto EXIT;
         }
         if( bwb_Warning_Pending() /* Keep This */ )
         {
            /* 
            this might look odd... 
            but we want to abort printing on the first warning.
            The expression list could include a function with side-effects,
            so any warning should immediately halt further evaluation.
            */
            goto EXIT;
         }
         if( My->CurrentVersion->OptionVersionBitmask & ( C77 ) && My->CurrentFile->FileNumber > 0 )
         {
            /* CBASIC-II: files have quoted strings */
            if( E->TypeChar == BasicStringSuffix )
            {
               xputc('\"');
            }
         }
         if( get_prnfmt(format_string, &fs_pos, E ) == FALSE )
         {
            WARN_SYNTAX_ERROR;
            goto EXIT;
         }
         if( My->CurrentVersion->OptionVersionBitmask & ( C77 ) && My->CurrentFile->FileNumber > 0 )
         {
            /* CBASIC-II: files have quoted strings */
            if( E->TypeChar == BasicStringSuffix )
            {
               xputc('\"');
            }
         }
         RELEASE( E );
      }
      line_skip_spaces(l);
   }        /* end of loop through print elements */

   if (OutputCR == TRUE)
   {
      /* did not end with ',' or ';' */
      xputc('\n');
   }
   Success = TRUE;
EXIT:
   RELEASE( E );
   return Success;   
}


/***************************************************************
  
        FUNCTION:       get_prnfmt()
  
   DESCRIPTION:    This function gets the PRINT USING
         format string, returning a structure
         to the format.
  
***************************************************************/
static int num_prnfmt(char *buffer, int *position, VariantType *e)
{
   /*
   Format a NUMBER.
   'buffer' points to the beginning of a PRINT USING format string, such as "###.##".
   'position' is the current offset in 'buffer'.
   'e' is the current expression to print.
   */
   int  width     =   0;
   int  precision =   0;
   int  exponent  =   0;
   char HeadChar  = ' ';
   char FillChar  = ' ';
   char CurrChar  = ' ';
   char ComaChar  = ' ';
   char TailChar  = ' ';
   int  p;
   char tbuf[BasicStringLengthMax + 1];
   
   p = *position;

   while( IS_CHAR(buffer[p], PrintUsingNumberPlus) || IS_CHAR( buffer[p], PrintUsingNumberMinus ) )
   {
       HeadChar = buffer[p];
       width++;
       p++;
   }
   while( IS_CHAR(buffer[p], PrintUsingNumberFiller) || IS_CHAR(buffer[p], PrintUsingNumberDollar ) )
   {
      if( IS_CHAR( buffer[p], PrintUsingNumberFiller ) )
      {
         FillChar = PrintUsingNumberFiller;
      }
      else
      if( IS_CHAR( buffer[p], PrintUsingNumberDollar ) )
      {
         CurrChar = PrintUsingNumberDollar;
      }
      width++;
      p++;
   }
   while( IS_CHAR(buffer[p], PrintUsingNumberDigit) || IS_CHAR(buffer[p], PrintUsingNumberComma ) )
   {
      if( IS_CHAR( buffer[p], PrintUsingNumberComma ) )
      {
         ComaChar = PrintUsingNumberComma;
      }
      width++;
      p++;
   }
   if( IS_CHAR( buffer[p], PrintUsingNumberPeriod ) )
   {
      while( IS_CHAR( buffer[p], PrintUsingNumberPeriod ) )
      {
         width++;
         p++;
      }
      while( IS_CHAR( buffer[p], PrintUsingNumberDigit ) )
      {
         precision++;
         width++;
         p++;
      }
   }
   while( IS_CHAR( buffer[p], PrintUsingNumberExponent ) )
   {
      exponent++;
      precision++;
      width++;
      p++;
   }
   while( IS_CHAR( buffer[p], PrintUsingNumberPlus ) || IS_CHAR( buffer[p], PrintUsingNumberMinus ) )
   {
      TailChar = buffer[p];
      width++;
      p++;
   }
   /* format the number */


   /* displaying both a Heading and a Trailing sign is NOT supported */
   if( TailChar == ' ' )
   {
      /* do nothing */
   }
   else
   if( IS_CHAR( TailChar, PrintUsingNumberPlus ) || IS_CHAR( TailChar, PrintUsingNumberMinus ) )
   {
      /* force the sign to be printed, so we can move it */
      HeadChar = TailChar;
   }
   else
   {
      WARN_INTERNAL_ERROR;
      return FALSE;
   }


   if( HeadChar == ' ' )
   {
      /* only display a '-' sign */
      if( exponent > 0 )
      {
         sprintf(tbuf, "%*.*e", width, precision, e->Number);
      }
      else
      {
         sprintf(tbuf, "%*.*f", width, precision, e->Number);
      }
   }
   else
   if( IS_CHAR( HeadChar, PrintUsingNumberPlus ) || IS_CHAR( HeadChar, PrintUsingNumberMinus ) )
   {
      /* force a leading sign '+' or '-' */
      if( exponent > 0 )
      {
         sprintf(tbuf, "%+*.*e", width, precision, e->Number);
      }
      else
      {
         sprintf(tbuf, "%+*.*f", width, precision, e->Number);
      }
   }
   else
   {
      WARN_INTERNAL_ERROR;
      return FALSE;
   }
   
   if( TailChar == ' ' )
   {
      /* do nothing */
   }
   else
   if( IS_CHAR( TailChar, PrintUsingNumberPlus ) || IS_CHAR( TailChar, PrintUsingNumberMinus ) )
   {
      /* move sign '+' or '-' to end */
      int i;
      int n;

      n = bwb_strlen(tbuf);

      for (i = 0; i < n; i++)
      {
         if( tbuf[i] != ' ' )
         {
            if( IS_CHAR( tbuf[i], PrintUsingNumberPlus ) )
            {
               tbuf[i] = ' ';
               if( IS_CHAR( TailChar, PrintUsingNumberPlus ) )
               {
                  /* TailChar of '+' does print a '+' */
                  bwb_strcat(tbuf,"+");
               }
               else
               if( IS_CHAR( TailChar, PrintUsingNumberMinus ) )
               {
                  /* TailChar of '-' does NOT print a '+' */
                  bwb_strcat(tbuf," ");
               }
            }
            else
            if( IS_CHAR( tbuf[i], PrintUsingNumberMinus ) )
            {
               tbuf[i] = ' ';
               bwb_strcat(tbuf,"-");
            }
            break;
         }
      }
      if( tbuf[0] == ' ' )
      {
         n = bwb_strlen(tbuf); 
         /* n > 0 */
         for( i = 1; i < n; i++ )
         {
            tbuf[i-1] = tbuf[i];
         }
         tbuf[n-1] = BasicNulChar;
      }
   }
   else
   {
      WARN_INTERNAL_ERROR;
      return FALSE;
   }


   if( CurrChar == ' ' )
   {
      /* do nothing */
   }
   else
   if( IS_CHAR( CurrChar, PrintUsingNumberDollar ) )
   {
      int i;
      int n;

      n = bwb_strlen(tbuf);

      for (i = 0; i < n; i++)
      {
         if (tbuf[i] != ' ')
         {
            if (i > 0)
            {
               if (bwb_isdigit(tbuf[i]))
               {
                  tbuf[i - 1] = CurrChar;
               }
               else
               {
                  /* sign char */
                  tbuf[i - 1] = tbuf[i];
                  tbuf[i] = CurrChar;
               }
            }
            break;
         }
      }
   }
   else
   {
      WARN_INTERNAL_ERROR;
      return FALSE;
   }

   if( FillChar == ' ' )
   {
      /* do nothing */
   }
   else
   if( IS_CHAR( FillChar, PrintUsingNumberFiller ) )
   {
      int i;
      int n;

      n = bwb_strlen(tbuf);

      for (i = 0; i < n; i++)
      {
         if (tbuf[i] != ' ')
         {
            break;
         }
         tbuf[i] = PrintUsingNumberFiller;
      }
   }
   else
   {
      WARN_INTERNAL_ERROR;
      return FALSE;
   }

   if( ComaChar == ' ' )
   {
      prn_xxprintf(tbuf);
   }
   else
   if( IS_CHAR( ComaChar, PrintUsingNumberComma ) )
   {
      int  dig_pos = -1;
      int  dec_pos = -1;
      int  i;
      int  n;
      int  commas;

      n = bwb_strlen(tbuf);

      for (i = 0; i < n; i++)
      {
        if ((bwb_isdigit(tbuf[i]) != 0) && (dig_pos == -1))
        {
           dig_pos = i;
        }
        if ((tbuf[i] == PrintUsingNumberPeriod) && (dec_pos == -1))
        {
           dec_pos = i;
        }
        if ((dig_pos != -1) && (dec_pos != -1))
        {
           break;
        }
      }
      if (dig_pos == -1)
      {
         dec_pos = n;
      }
      if (dec_pos == -1)
      {
         dec_pos = n;
      }
      /* count the number of commas */
      commas = 0;
      for (i = 0; i < n; ++i)
      {
         if (((dec_pos - i) % 3 == 0) && (i > dig_pos) && (i < dec_pos))
         {
            commas++;
         }
      }
      /* now, actually print */
      for( i = 0; i < n; i++ )
      {
         if( i < commas && IS_CHAR( tbuf[i], FillChar ) )
         {
            /* 
            Ignore the same number of leading spaces as there are commas.
            While not perfect for all possible cases, 
            it is usually good enough for practical purposes.
            */
         }
         else
         {
            if (((dec_pos - i) % 3 == 0) && (i > dig_pos) && (i < dec_pos))
            {
              xxputc(PrintUsingNumberComma);
            }
            xxputc(tbuf[i]);
         }
      }
   }
   else
   {
      WARN_INTERNAL_ERROR;
      return FALSE;
   }
   *position = p;
   return TRUE;
}

static int str_prnfmt(char *buffer, int *position, VariantType *e)
{
   /*
   Format a STRING.
   'buffer' points to the beginning of a PRINT USING format string, such as "###.##".
   'position' is the current offset in 'buffer'.
   'e' is the current expression to print.
   */
   int p;
   char tbuf[BasicStringLengthMax + 1];
   
   p = *position;
   
   if( e->TypeChar == BasicStringSuffix )
   {
      bwb_strcpy( tbuf, e->Buffer );
   }
   else
   {
      BasicNumerc( e->Number, tbuf );
   }
   
   if( IS_CHAR( buffer[ p ], PrintUsingStringFirst ) )
   {
      /* print first character only */
      int i = 0;

      if( tbuf[i] == BasicNulChar )
      {
         xxputc(' ');
      }
      else
      {
         xxputc(tbuf[i]);
         i++;
      }
      p++;
   }
   else
   if( IS_CHAR( buffer[ p ], PrintUsingStringAll ) )
   {
      /* print entire string */
      p++;
      prn_xxprintf(tbuf);
   }
   else
   if( IS_CHAR( buffer[ p ], PrintUsingStringLength ) )
   {
      /* print N characters or spaces */
      int i = 0;

      if( tbuf[i] == BasicNulChar )
      {
         xxputc(' ');
      }
      else
      {
         xxputc(tbuf[i]);
         i++;
      }
      p++;

      while( buffer[p] != BasicNulChar && buffer[p] != PrintUsingStringLength )
      {
         if( tbuf[i] == BasicNulChar )
         {
            xxputc(' ');
         }
         else
         {
            xxputc(tbuf[i]);
            i++;
         }
         p++;
      }
      if( buffer[p] == PrintUsingStringLength )
      {
         if( tbuf[i] == BasicNulChar )
         {
            xxputc(' ');
         }
         else
         {
            xxputc(tbuf[i]);
            i++;
         }
         p++;
      }
   }
   *position = p;
   return TRUE;
}

static int is_magic_string( char * buffer )
{
   /* 
   for the character string pointed to 'buffer':
   return TRUE if it is a MagicString sequence,
   return FALSE otherwise.
   */
   char *P;

   /* "!" */
   P = buffer;
   if( IS_CHAR( *P, PrintUsingStringFirst ) )
   {
      return TRUE;
   }

   /* "&" */
   P = buffer;
   if( IS_CHAR( *P, PrintUsingStringAll ) )
   {
      return TRUE;
   }

   /* "%...%" */
   P = buffer;
   if( IS_CHAR( *P, PrintUsingStringLength ) )
   {
      return TRUE;
   }

   return FALSE;
}

static int is_magic_number( char * buffer )
{
   /* 
   for the character string pointed to 'buffer':
   return TRUE if it is a MagicNumber sequence,
   return FALSE otherwise.
   */
   char *P;

   /* "+**" */
   P = buffer;
   if( IS_CHAR( *P, PrintUsingNumberPlus ) )
   {
      P++;
      if( IS_CHAR( *P, PrintUsingNumberFiller ) )
      {
         P++;
         if( IS_CHAR( *P, PrintUsingNumberFiller ) )
         {
            /* "+**" */
            return TRUE;
         }
      }
   }

   /* "+$$" */
   P = buffer;
   if( IS_CHAR( *P, PrintUsingNumberPlus ) )
   {
      P++;
      if( IS_CHAR( *P, PrintUsingNumberDollar ) )
      {
         P++;
         if( IS_CHAR( *P, PrintUsingNumberDollar ) )
         {
            /* "+$$" */
            return TRUE;
         }
      }
   }

   /* "+#" */
   P = buffer;
   if( IS_CHAR( *P, PrintUsingNumberPlus ) )
   {
      P++;
      if( IS_CHAR( *P, PrintUsingNumberDigit ) )
      {
         /* "+#" */
         return TRUE;
      }
   }

   /* "-**" */
   P = buffer;
   if( IS_CHAR( *P, PrintUsingNumberMinus ) )
   {
      P++;
      if( IS_CHAR( *P, PrintUsingNumberFiller ) )
      {
         P++;
         if( IS_CHAR( *P, PrintUsingNumberFiller ) )
         {
            /* "-**" */
            return TRUE;
         }
      }
   }

   /* "-$$" */
   P = buffer;
   if( IS_CHAR( *P, PrintUsingNumberMinus ) )
   {
      P++;
      if( IS_CHAR( *P, PrintUsingNumberDollar ) )
      {
         P++;
         if( IS_CHAR( *P, PrintUsingNumberDollar ) )
         {
            /* "-$$" */
            return TRUE;
         }
      }
   }

   /* "-#" */
   P = buffer;
   if( IS_CHAR( *P, PrintUsingNumberMinus ) )
   {
      P++;
      if( IS_CHAR( *P, PrintUsingNumberDigit ) )
      {
         /* "-#" */
         return TRUE;
      }
   }

   /* "**" */
   P = buffer;
   if( IS_CHAR( *P, PrintUsingNumberFiller ) )
   {
      P++;
      if( IS_CHAR( *P, PrintUsingNumberFiller ) )
      {
         /* "**" */
         return TRUE;
      }
   }
   
   /* "$$" */
   P = buffer;
   if( IS_CHAR( *P, PrintUsingNumberDollar ) )
   {
      P++;
      if( IS_CHAR( *P, PrintUsingNumberDollar ) )
      {
         /* "$$" */
         return TRUE;
      }
   }

   /* "#" */
   P = buffer;
   if( IS_CHAR( *P, PrintUsingNumberDigit ) )
   {
      /* "#" */
      return TRUE;
   }

   return FALSE;
}

static int get_prnfmt(char *buffer, int *position, VariantType *e)
{
   /*
   Format an EXPRESSION.
   'buffer' points to the beginning of a PRINT USING format string, such as "###.##".
   'position' is the current offset in 'buffer'.
   'e' is the current expression to print.
   */
   


   int p;
   int  IsLoop = TRUE;
   int  IsUsed = FALSE;

   bwx_DEBUG(__FUNCTION__);


   p = *position;
   if( p < 0 )
   {
      p = 0;
   }
   else
   if( p > 0 )
   {
      if( buffer[p] == BasicNulChar )
      {
         p = 0;
      }
   }
   while( IsLoop == TRUE )
   {
      if( buffer[p] == BasicNulChar )
      {
         IsLoop = FALSE;
      }
      else
      {
         int IsLiteral = TRUE;
         
         
         if( IsLiteral == TRUE )
         {
            char * S;

            S = buffer;
            S += p;

            if( is_magic_string( S ) )
            {
               /* MagicString Value */
               if( IsUsed == TRUE )
               {
                  IsLoop = FALSE;
               }
               else
               if( e->TypeChar == BasicStringSuffix )
               {
                  str_prnfmt( buffer, &p, e );
                  IsUsed = TRUE;
               }
               else
               {
                  IsLoop = FALSE;
               }
               IsLiteral = FALSE;
            }
         }
         if( IsLiteral == TRUE )
         {
            char * S;

            S = buffer;
            S += p;

            if( is_magic_number( S ) )
            {
               /* MagicNumber Value */
               if( IsUsed == TRUE )
               {
                  IsLoop = FALSE;
               }
               else
               if( e->TypeChar == BasicStringSuffix )
               {
                  IsLoop = FALSE;
               }
               else
               {
                  num_prnfmt( buffer, &p, e );
                  IsUsed = TRUE;
               }
               IsLiteral = FALSE;
            }
         }
         if( IsLiteral == TRUE )
         {
            if( PrintUsingLiteral != BasicNulChar && buffer[p] == PrintUsingLiteral )
            {
               /* print next character as literal */
               p++;
               if( buffer[p] == BasicNulChar )
               {
                 /* PRINT USING "_" */
                 xxputc(' ');
               }
               else
               {
                 xxputc(buffer[p]);
                 p++;
               }
            }
            else
            {
                 xxputc(buffer[p]);
                 p++;
            }
         }
         
      }
   }

   if( IsUsed == FALSE )
   {

      if( e->TypeChar == BasicStringSuffix )
      {
         /* PRINT USING "";A$    */
         /* PRINT USING "ABC";A$ */
         prn_iprintf(e->Buffer);
      }
      else
      {
         /* PRINT USING "";X     */
         /* PRINT USING "ABC";X  */
         /* [space]number[space]   POSITIVE or ZERO
          * [minus]number[space]   NEGATIVE */
         char tbuf[ 32 ];

         BasicNumerc(e->Number, tbuf);

         if( My->CurrentVersion->OptionVersionBitmask & ( C77 ) && My->CurrentFile->FileNumber > 0 )
         {
            /* CBASIC-II: numbers in files do NOT have leading or trailing spaces */
            char * P;
            P = tbuf;
            while( *P == ' ' )
            {
               P++;
            }
            prn_iprintf( P );
         }
         else
         {
            prn_iprintf(tbuf);
            xputc(' ');
         }
      }
   }
   *position = p;
   return TRUE;
}

/***************************************************************
  
        FUNCTION:       prn_iprintf()
  
   DESCRIPTION:    This function outputs a null-terminated
         string to a specified file or output
         device.
  
***************************************************************/

int
prn_iprintf(char *buffer)
{
   int             n;

   bwx_DEBUG(__FUNCTION__);

   if (My->CurrentFile->width == 0)
   {
      /* ignore when WIDTH == 0 -- BINARY output */
      while (*buffer)
      {
         xxxputc(*buffer);
         buffer++;
      }
      return 0;
   }
   /* check to see if width will be exceeded */
   n = My->CurrentFile->col + bwb_strlen(buffer) - 1;
   if (n > My->CurrentFile->width)
   {
      xputc('\n');
   }
   /* output the string */
   while (*buffer)
   {
      xputc(*buffer);
      buffer++;
   }
   return 0;
}

/***************************************************************
  
        FUNCTION:       prn_xxprintf()
  
   DESCRIPTION:    This function outputs a null-terminated
         string to a specified file or output
         device without expanding tabs.
         Added by JBV 10/95
  
***************************************************************/

static int prn_xxprintf(char *buffer)
{
   int             n;

   bwx_DEBUG(__FUNCTION__);

   if (My->CurrentFile->width == 0)
   {
      /* ignore when WIDTH == 0 -- BINARY output */
      while (*buffer)
      {
         xxxputc(*buffer);
         buffer++;
      }
      return 0;
   }
   /* check to see if width will be exceeded */
   n = My->CurrentFile->col + bwb_strlen(buffer) - 1;
   if (n > My->CurrentFile->width)
   {
      xxputc('\n');
   }
   /* output the string */
   while (*buffer)
   {
      xxputc(*buffer);
      buffer++;
   }
   return 0;
}

/***************************************************************
  
        FUNCTION:       xputc()
  
   DESCRIPTION:    This function outputs a character to a
         specified file or output device, expanding
         TABbed output approriately.
  
***************************************************************/

static int xputc(char c)
{
   static char     CHR_pending = FALSE;

   bwx_DEBUG(__FUNCTION__);

   if (My->CurrentFile->width == 0)
   {
      /* ignore when WIDTH == 0 -- BINARY output */
      xxxputc(c);
      return 0;
   }
   /* check for pending SPC */
   if (CHR_pending == PRN_SPC)
   {
      /* 190 PRINT SPC(A);"X" ' A = 0...255 */
      int             i;
      for (i = 0; i < c; i++)
      {
         xxputc(' ');
      }
      CHR_pending = FALSE;
      return TRUE;
   }
   /* check for pending TAB */
   if (CHR_pending == PRN_TAB)
   {
      /* WIDTH 80 */
      while (c > My->CurrentFile->width)
      {
         /* If n is greater than the margin m, then n is
          * reduced by an integral multiple of m so that it is
          * in the range 1 <= n <= m; */
         c -= My->CurrentFile->width;
      }
      /* 190 PRINT TAB(A);"X" ' A = 0 */
      if (c == 0)
      {
         /* use the value of one */
         c = 1;
         /* continue processing */
      }
      if ((int) c < My->CurrentFile->col)
      {
         xxputc('\n');
      }
      while (My->CurrentFile->col < (int) c)
      {
         xxputc(' ');
      }
      CHR_pending = FALSE;
      return TRUE;
   }
   /* check for specific output options */
   switch (c)
   {
   case PRN_SPC:
   case PRN_TAB:
      CHR_pending = c;
      break;
   case '\t':
      {
         int             LastZoneColumn;
         LastZoneColumn = 1;
         while (LastZoneColumn < My->CurrentFile->width)
         {
            LastZoneColumn += ZONE_WIDTH;
         }
         LastZoneColumn -= ZONE_WIDTH;

         if (My->CurrentFile->col >= LastZoneColumn)
         {
            /* advance to a new line */
            xxputc('\n');
         }
         else
         {
            /* advance to the next print zone */
            if ((My->CurrentFile->col % ZONE_WIDTH) == 1)
            {
               xxputc(' ');
            }
            while ((My->CurrentFile->col % ZONE_WIDTH) != 1)
            {
               xxputc(' ');
            }
         }
      }
      break;
   default:
      xxputc(c);
      break;
   }
   return 0;
}

/***************************************************************
  
        FUNCTION:       xxputc()
  
   DESCRIPTION:    This function outputs a character to a
         specified file or output device, checking
         to be sure the PRINT width is within
         the bounds specified for that device.
  
***************************************************************/

static int xxputc(char c)
{
   bwx_DEBUG(__FUNCTION__);

   if (My->CurrentFile->width == 0)
   {
      /* ignore when WIDTH == 0 -- BINARY output */
      xxxputc(c);
      return 0;
   }
   /* check to see if width has been exceeded */
   if (c != '\n')
   {
      /* REM this should print one line, not two lines WIDTH 80
       * PRINT SPACE$( 80 ) */
      if (My->CurrentFile->col > My->CurrentFile->width)
      {
         xxxputc('\n'); /* output LF */
      }
   }
   /* output the character */
   xxxputc(c);

   return 0;
}

/***************************************************************
  
   FUNCTION:       xxxputc()
  
   DESCRIPTION:    This function sends a character to a
         specified file or output device.
  
***************************************************************/


static int xxxputc(char c)
{
   bwx_DEBUG(__FUNCTION__);

   if (My->CurrentFile == My->SYSPRN)
   {
      bwx_LPRINT(c);
      if (c == '\n' && My->SYSPRN->width > 0 && My->LPRINT_NULLS > 0)
      {
         int             i;
         for (i = 0; i < My->LPRINT_NULLS; i++)
         {
            bwx_LPRINT(0);
         }
      }
   }
   else
   {
      if( My->CurrentVersion->OptionVersionBitmask & ( C77 ) && c == '\n' )
      {
         if( My->CurrentFile->mode & DEVMODE_RANDOM && My->CurrentFile->width > 0 )
         {
            /* CBASIC-II: RANDOM files are padded on the right with spaces */
            while( My->CurrentFile->col < My->CurrentFile->width )
            {
               fputc(' ', My->CurrentFile->cfp);
               My->CurrentFile->col++;
            }
         }
      }
      fputc(c, My->CurrentFile->cfp);
   }
   /* update current column position */
   if (My->CurrentFile->width == 0)
   {
      /* ignore when WIDTH == 0 -- BINARY output */
      My->CurrentFile->col = 1;
      My->CurrentFile->row = 1;
   }
   else
   if (c == '\n')
   {
      My->CurrentFile->col = 1;
      My->CurrentFile->row ++;
   }
   else
   {
      My->CurrentFile->col ++;
   }
   return 0;
}


void 
ResetConsoleColumn( void )
{
   bwx_DEBUG(__FUNCTION__);

   My->SYSOUT->col = 1;
}


LineType *
bwb_PUT(LineType * l)
{
   bwx_DEBUG(__FUNCTION__);

   if( My->CurrentVersion->OptionVersionBitmask & ( I70 | I73 ) )
   {
      /* PUT filename$ , value [, ...] */
      VariantType e;       /* no leaks */
      VariantType *E = &e; /* no leaks */

      CLEAR_VARIANT( E );
      line_skip_spaces(l);
      if( line_read_expression( l, E ) == FALSE )
      {
         goto EXIT;
      }
      if( E->TypeChar == BasicStringSuffix )
      {
         /* STRING */
         /* PUT filename$ ... */
         if( is_empty_filename( E->Buffer ) )
         {
            /* "PUT # 0" is an error */
            WARN_BAD_FILE_NUMBER;
            goto EXIT;
         }
         My->CurrentFile = find_file_by_name( E->Buffer );
         if( My->CurrentFile == NULL )
         {
            /* implicitly OPEN for writing */
            My->CurrentFile = file_new();
            My->CurrentFile->cfp = fopen(E->Buffer, "w");
            if( My->CurrentFile->cfp == NULL )
            {
               /* bad file name */
               WARN_BAD_FILE_NUMBER;
               goto EXIT;
            }
            My->CurrentFile->FileNumber = file_next_number();
            My->CurrentFile->mode = DEVMODE_OUTPUT;
            My->CurrentFile->width = 0;
            /* WIDTH == RECLEN */
            My->CurrentFile->col = 1;
            My->CurrentFile->row = 1;
            My->CurrentFile->delimit = ',';
            My->CurrentFile->buffer = NULL;
            bwb_strcpy(My->CurrentFile->filename, E->Buffer);
         }
      }
      else
      {
         /* NUMBER -- file must already be OPEN */
         /* PUT filenumber ... */
         if( E->Number < 0 )
         {
            /* "PUT # -1" is an error */
            WARN_BAD_FILE_NUMBER;
            goto EXIT;
         }
         if( E->Number == 0 )
         {
            /* "PUT # 0" is an error */
            WARN_BAD_FILE_NUMBER;
            goto EXIT;
         }
         /* normal file */
         My->CurrentFile = find_file_by_number( (int) bwb_rint( E->Number ) );
         if( My->CurrentFile == NULL )
         {
            /* file not OPEN */
            WARN_BAD_FILE_NUMBER;
            goto EXIT;
         }
      }  
      RELEASE( E );  
      if( My->CurrentFile == NULL )
      {
         WARN_BAD_FILE_NUMBER;
         goto EXIT;
      }
      if (( My->CurrentFile->mode & DEVMODE_WRITE) == 0)
      {
         WARN_BAD_FILE_NUMBER;
         goto EXIT;
      }
      if( line_is_eol(l) )
      {
         /* PUT F$ */
         /* PUT #1 */
         xputc('\n');
         goto EXIT;
      }
      else
      if (line_skip_comma(l))
      {
         /* OK */
      }
      else
      {
         WARN_SYNTAX_ERROR;
         goto EXIT;
      }
   
      /* loop through elements */
   
      while( ! line_is_eol(l) )
      {
         while (line_skip_comma(l))
         {
            /* PUT F$, ,,,A,,,B,,, */
            /* PUT #1, ,,,A,,,B,,, */
            xputc( My->CurrentFile->delimit );
         }
   
         if ( ! line_is_eol(l) )
         {
            /* print this item */

            /* get the next element */
            line_skip_spaces(l);
            if( line_read_expression( l, E ) == FALSE )
            {
               goto EXIT;
            }
            if( E->TypeChar == BasicStringSuffix )
            {
               /* STRING */
               xputc(BasicQuoteChar);
               prn_iprintf(E->Buffer);
               xputc(BasicQuoteChar);
            }
            else
            {
               /* NUMBER */
               char tbuf[ 32 ];
               BasicNumerc(E->Number, tbuf);
               prn_iprintf(tbuf);
            }  
            RELEASE( E );   
         }
      }
      /* print LF */
      xputc('\n');
      /* OK */
EXIT:
      RELEASE( E );
      return bwb_zline(l);
   }
   else
   if( My->CurrentVersion->OptionVersionBitmask & ( D71 ) )
   {
      /* PUT # file_number [ , RECORD record_number ] */
      int file_number = 0;
      if( line_skip_char( l, BasicFileNumberPrefix ) == FALSE )
      {
         /* OPTIONAL */
      }
      if( line_read_integer_expression( l, &file_number ) == FALSE )
      {
         WARN_BAD_FILE_NUMBER;
         return bwb_zline( l );
      }
      if( file_number < 1 )
      {
         WARN_BAD_FILE_NUMBER;
         return bwb_zline( l );
      }
      My->CurrentFile = find_file_by_number( file_number );
      if( My->CurrentFile == NULL )
      {
         WARN_BAD_FILE_NUMBER;
         return bwb_zline( l );
      }
      if( My->CurrentFile->mode != DEVMODE_RANDOM )
      {
         WARN_BAD_FILE_NUMBER;
         return bwb_zline( l );
      }
      if( My->CurrentFile->width <= 0 )
      {
         WARN_BAD_FILE_NUMBER;
         return bwb_zline( l );
      }
      if( line_is_eol( l ) )
      {
         /* PUT # file_number */
      }
      else
      {
         /* PUT # file_number , RECORD record_number */
         int record_number = 0;
         long offset = 0;
         if( line_skip_comma( l ) == FALSE )
         {
            WARN_SYNTAX_ERROR;
            return bwb_zline( l );
         }
         if( line_skip_word( l, "RECORD" ) == FALSE )
         {
            WARN_SYNTAX_ERROR;
            return bwb_zline( l );
         }
         if( line_read_integer_expression( l, &record_number ) == FALSE )
         {
            WARN_BAD_RECORD_NUMBER;
            return bwb_zline( l );
         }
         if( record_number <= 0 )
         {
            WARN_BAD_RECORD_NUMBER;
            return bwb_zline( l );
         }
         record_number--; /* BASIC to C */
         offset = record_number;
         offset *= My->CurrentFile->width;
         if (fseek(My->CurrentFile->cfp, offset, SEEK_SET) != 0)
         {
            WARN_BAD_RECORD_NUMBER;
            return bwb_zline( l );
         }
      }
      field_put( My->CurrentFile );
      /* if( TRUE ) */
      {
         int i;
         for (i = 0; i < My->CurrentFile->width; i++)
         {
            char c;
            c = My->CurrentFile->buffer[i];
            fputc(c, My->CurrentFile->cfp);
         }
      }
      /* OK */
      return bwb_zline( l );
   }
   WARN_SYNTAX_ERROR;
   return bwb_zline(l);
}


/***************************************************************
  
        FUNCTION:       bwb_write()
  
   DESCRIPTION:    This C function implements the BASIC WRITE
         command.
  
   SYNTAX:     WRITE [# device-number,] element [, element ]....
  
***************************************************************/


LineType *
bwb_WRITE(LineType * l)
{
   int OutputCR;
   VariantType x;       /* no leaks */
   VariantType *X = &x; /* no leaks */

   bwx_DEBUG(__FUNCTION__);
   CLEAR_VARIANT( X );     
   My->CurrentFile = My->SYSOUT;
   if ( line_skip_char( l, BasicFileNumberPrefix ) )
   {
      int             UserFileNumber;

      if( line_read_integer_expression(l, &UserFileNumber) == FALSE )
      {
         WARN_SYNTAX_ERROR;
         return bwb_zline(l);
      }

      /* check the requested device number */
      if( UserFileNumber < 0 )
      {
         My->CurrentFile = My->SYSPRN;
      }
      else
      if( UserFileNumber == 0 )
      {
         My->CurrentFile = My->SYSOUT;
      }
      else
      {
         /* normal file */
         My->CurrentFile = find_file_by_number( UserFileNumber );
      }
      if( My->CurrentFile == NULL )
      {
         WARN_BAD_FILE_NUMBER;
         return bwb_zline(l);
      }
      if (( My->CurrentFile->mode & DEVMODE_WRITE) == 0)
      {
         WARN_BAD_FILE_NUMBER;
         return bwb_zline(l);
      }
      if( line_is_eol(l) )
      {
         /* WRITE #1 */
         xputc('\n');
         return bwb_zline(l);
      }
      else
      if (line_skip_comma(l))
      {
         /* OK */
      }
      else
      {
         WARN_SYNTAX_ERROR;
         return bwb_zline(l);
      }
   }


   /* loop through elements */
   OutputCR = TRUE;
   line_skip_spaces( l );
   while( line_is_eol(l) == FALSE )
   {
      if( line_skip_comma(l) )
      {
         /* WRITE     ,,,A,,,B,,, */
         /* WRITE #1, ,,,A,,,B,,, */
         OutputCR = FALSE;
         xputc( My->CurrentFile->delimit );
      }
      else
      {
         /* print the expression */
         OutputCR = TRUE;
         if( line_read_expression( l, X ) == FALSE )
         {
            goto EXIT;
         }
         if( bwb_Warning_Pending() /* Keep This */ )
         {
            /* 
            this might look odd... 
            but we want to abort printing on the first error.
            The expression list could include a function with side-effects,
            so any kind of error should immediately halt further evaluation.
            */
            goto EXIT;
         }
         if( X->TypeChar == BasicStringSuffix )
         {
            /* STRING */
            xputc(BasicQuoteChar);
            prn_iprintf(X->Buffer);
            xputc(BasicQuoteChar);
         }
         else
         {
            /* NUMBER */
            char tbuf[ 32 ];
            BasicNumerc(X->Number, tbuf);
            prn_iprintf(tbuf);
         }  
         RELEASE( X );   
      }
      line_skip_spaces( l );
   }
   /* print LF */
   if( OutputCR == TRUE )
   {
      xputc('\n');
   }
EXIT:
   RELEASE( X );      
   return bwb_zline(l);
}

static LineType * file_write_matrix( LineType * l, char delimit )
{
   /* MAT PRINT arrayname [;|,] */
   /* Array must be 1, 2 or 3 dimensions */
   /* Array may be either NUMBER or STRING */
   VariableType *v;
   char            ItemSeperator[2];

   bwx_DEBUG(__FUNCTION__);

   /* get the variable name */
   line_skip_spaces(l);
   while( bwb_isalpha( l->buffer[l->position] ) )
   {
      /* get matrix name */
       if((v = line_read_matrix( l )) == NULL)
       {
          WARN_SUBSCRIPT_OUT_OF_RANGE;
          return bwb_zline(l);
       }

       /* variable MUST be an array of 1, 2 or 3 dimensions */
       if (v->dimensions < 1)
       {
          WARN_SUBSCRIPT_OUT_OF_RANGE;
          return bwb_zline(l);
       }
       if(v->dimensions > 3)
       {
          WARN_SUBSCRIPT_OUT_OF_RANGE;
          return bwb_zline(l);
       }


       /* 
       This may look odd, but MAT PRINT is special.
       The variable seperator AFTER the variable determines how 
       the variable's values are printed.
       The number of dimension determines:
       a) the meaning of comma (,) and semicolon (;)
       b) the default of row-by-row or col-by-col
       */
       /* default the item seperator based upon variable's dimensions */
       ItemSeperator[0] = BasicNulChar;
       ItemSeperator[1] = BasicNulChar;
       switch( v->dimensions )
       {
       case 1:
           /* by default, a one dimension array is printed row-by-row */
           ItemSeperator[0] = '\n';
           break;
       case 2:
           /* by default, a two dimension array is printed col-by-col */
           ItemSeperator[0] = delimit;
           break;
       case 3:
           /* by default, a three dimension array is printed col-by-col */
           ItemSeperator[0] = delimit;
           break;
       }
       /* allow user to assign the item seperator */
       if( line_skip_char(l, ',' /* comma-specific */ ))
       {
           /* force printing col-by-col */
           ItemSeperator[0] = delimit;
       }
       else
       if( line_skip_char(l, ';' /* semicolon-specific */ ))
       {
           /* force concatenating the columns */
           ItemSeperator[0] = BasicNulChar;
       }
       /* print array */
       switch( v->dimensions )
       {
       case 1:
           {
               /*
               OPTION BASE 0
               DIM A(5)
               ...
               MAT PRINT A 
               ...
               FOR I = 0 TO 5
                 PRINT A(I)
               NEXT I
               ...
               */
               for( v->array_pos[0] = v->LBOUND[0]; v->array_pos[0] <= v->UBOUND[0]; v->array_pos[0]++ )
               {
                  char tbuf[BasicStringLengthMax + 1];
                  
                   if( ItemSeperator[0] != BasicNulChar && v->array_pos[0] > v->LBOUND[0] )
                   {
                       prn_iprintf(ItemSeperator);
                   }
                  /* if( TRUE ) */
                  {
                     VariantType variant;
                     if( var_get( v, &variant) == FALSE )
                     {
                        WARN_VARIABLE_NOT_DECLARED;
                        return bwb_zline(l);
                     }
                     if( variant.TypeChar == '$' )
                     {
                        bwb_strcpy( tbuf, variant.Buffer );
                     }
                     else
                     {
                        BasicNumerc( variant.Number, tbuf );
                     }
                  }
                   prn_iprintf(tbuf);
               }
               prn_iprintf("\n");
           }
           break;
       case 2:
           {
               /*
               OPTION BASE 0
               DIM B(2,3)
               ...
               MAT PRINT B 
               ...
               FOR I = 0 TO 2
                   FOR J = 0 TO 3
                       PRINT B(I,J),
                   NEXT J
                   PRINT
               NEXT I
               ...
               */
               for( v->array_pos[0] = v->LBOUND[0]; v->array_pos[0] <= v->UBOUND[0]; v->array_pos[0]++ )
               {
               for( v->array_pos[1] = v->LBOUND[1]; v->array_pos[1] <= v->UBOUND[1]; v->array_pos[1]++ )
               {
                  char tbuf[BasicStringLengthMax + 1];

                   if( ItemSeperator[0] != BasicNulChar && v->array_pos[1] > v->LBOUND[1] )
                   {
                       prn_iprintf(ItemSeperator);
                   }
                  /* if( TRUE ) */
                  {
                     VariantType variant;
                     if( var_get( v, &variant) == FALSE )
                     {
                        WARN_VARIABLE_NOT_DECLARED;
                        return bwb_zline(l);
                     }
                     if( variant.TypeChar == '$' )
                     {
                        bwb_strcpy( tbuf, variant.Buffer );
                     }
                     else
                     {
                        BasicNumerc( variant.Number, tbuf );
                     }
                  }
                   prn_iprintf(tbuf);
               }
               prn_iprintf("\n");
               }
           }
           break;
       case 3:
           {
               /*
               OPTION BASE 0
               DIM C(2,3,4)
               ...
               MAT PRINT C 
               ...
               FOR I = 0 TO 2
                   FOR J = 0 TO 3
                       FOR K = 0 TO 4
                           PRINT C(I,J,K),
                       NEXT K
                       PRINT
                   NEXT J
                   PRINT
               NEXT I
               ...
               */
               for( v->array_pos[0] = v->LBOUND[0]; v->array_pos[0] <= v->UBOUND[0]; v->array_pos[0]++ )
               {
               for( v->array_pos[1] = v->LBOUND[1]; v->array_pos[1] <= v->UBOUND[1]; v->array_pos[1]++ )
               {
               for( v->array_pos[2] = v->LBOUND[2]; v->array_pos[2] <= v->UBOUND[2]; v->array_pos[2]++ )
               {
                  char tbuf[BasicStringLengthMax + 1];

                   if( ItemSeperator[0] != BasicNulChar && v->array_pos[2] > v->LBOUND[2] )
                   {
                       prn_iprintf(ItemSeperator);
                   }
                  /* if( TRUE ) */
                  {
                     VariantType variant;
                     if( var_get( v, &variant) == FALSE )
                     {
                        WARN_VARIABLE_NOT_DECLARED;
                        return bwb_zline(l);
                     }
                     if( variant.TypeChar == '$' )
                     {
                        bwb_strcpy( tbuf, variant.Buffer );
                     }
                     else
                     {
                        BasicNumerc( variant.Number, tbuf );
                     }
                  }
                   prn_iprintf(tbuf);
               }
               prn_iprintf("\n");
               }
               prn_iprintf("\n");
               }
           }
           break;
       }
       /* skip spaces */
       line_skip_spaces(l);
       /* process the next variable, if any  */
   }
   return bwb_zline(l);
}


static LineType * bwb_mat_dump(LineType * l, int IsWrite )
{
   /* MAT PRINT arrayname [;|,] */
   /* Array must be 1, 2 or 3 dimensions */
   /* Array may be either NUMBER or STRING */
   char delimit;

   bwx_DEBUG(__FUNCTION__);

   My->CurrentFile = My->SYSOUT;
   if ( line_skip_char( l, BasicFileNumberPrefix ) )
   {
      int             UserFileNumber;

      if( line_read_integer_expression(l, &UserFileNumber) == FALSE )
      {
         WARN_SYNTAX_ERROR;
         return bwb_zline(l);
      }

      /* check the requested device number */
      if( UserFileNumber < 0 )
      {
         My->CurrentFile = My->SYSPRN;
      }
      else
      if( UserFileNumber == 0 )
      {
         My->CurrentFile = My->SYSOUT;
      }
      else
      {
         /* normal file */
         My->CurrentFile = find_file_by_number( UserFileNumber );
      }
      if( My->CurrentFile == NULL )
      {
         WARN_BAD_FILE_NUMBER;
         return bwb_zline(l);
      }
      if ((My->CurrentFile->mode & DEVMODE_WRITE) == 0)
      {
         WARN_BAD_FILE_NUMBER;
         return bwb_zline(l);
      }
      if (line_skip_comma(l))
      {
         /* OK */
      }
      else
      {
         WARN_SYNTAX_ERROR;
         return bwb_zline(l);
      }
   }

   if( IsWrite )
   {
      /* MAT WRITE */
      delimit = My->CurrentFile->delimit;
   }
   else
   {
      /* MAT PRINT */
      delimit = '\t'; 
   }
   return file_write_matrix( l, delimit );
}


LineType *
bwb_MAT_PUT(LineType * l)
{
   /* MAT PUT filename$ , matrix [, ...] */

   VariantType x;       /* no leaks */
   VariantType *X = &x; /* no leaks */

   bwx_DEBUG(__FUNCTION__);
   CLEAR_VARIANT( X );

   My->CurrentFile = My->SYSOUT;

   line_skip_spaces(l);
   if( line_read_expression( l, X ) == FALSE )
   {
      goto EXIT;
   }
   if( X->TypeChar == BasicStringSuffix )
   {
      /* STRING */
      /* MAT PUT filename$ ... */
      if( is_empty_filename( X->Buffer ) )
      {
         /* "MAT PUT # 0" is an error */
         WARN_BAD_FILE_NUMBER;
         goto EXIT;
      }
      My->CurrentFile = find_file_by_name( X->Buffer );
      if( My->CurrentFile == NULL )
      {
         /* implicitly OPEN for writing */
         My->CurrentFile = file_new();
         My->CurrentFile->cfp = fopen( X->Buffer, "w" );
         if( My->CurrentFile->cfp == NULL )
         {
            /* bad file name */
            WARN_BAD_FILE_NUMBER;
            goto EXIT;
         }
         My->CurrentFile->FileNumber = file_next_number();
         My->CurrentFile->mode = DEVMODE_OUTPUT;
         My->CurrentFile->width = 0;
         /* WIDTH == RECLEN */
         My->CurrentFile->col = 1;
         My->CurrentFile->row = 1;
         My->CurrentFile->delimit = ',';
         My->CurrentFile->buffer = NULL;
         bwb_strcpy( My->CurrentFile->filename, X->Buffer );
      }
   }
   else
   {
      /* NUMBER -- file must already be OPEN */
      /* PUT filenumber ... */
      if( X->Number < 0 )
      {
         /* "MAT PUT # -1" is an error */
         WARN_BAD_FILE_NUMBER;
         goto EXIT;
      }
      if( X->Number == 0 )
      {
         /* "MAT PUT # 0" is an error */
         WARN_BAD_FILE_NUMBER;
         goto EXIT;
      }
      /* normal file */
      My->CurrentFile = find_file_by_number( (int) bwb_rint( X->Number ) );
      if( My->CurrentFile == NULL )
      {
         /* file not OPEN */
         WARN_BAD_FILE_NUMBER;
         goto EXIT;
      }
   }  
   RELEASE( X );  
   if( My->CurrentFile == NULL )
   {
      WARN_BAD_FILE_NUMBER;
      goto EXIT;
   }
   if (( My->CurrentFile->mode & DEVMODE_WRITE) == 0)
   {
      WARN_BAD_FILE_NUMBER;
      goto EXIT;
   }
   if ( line_skip_comma(l) )
   {
      /* OK */
   }
   else
   {
      WARN_SYNTAX_ERROR;
      goto EXIT;
   }
   return file_write_matrix( l, My->CurrentFile->delimit );
EXIT:
   RELEASE( X );  
   return bwb_zline(l);
}


LineType *
bwb_MAT_WRITE(LineType * l)
{
   return bwb_mat_dump( l, TRUE );
}

LineType *
bwb_MAT_PRINT(LineType * l)
{
   return bwb_mat_dump( l, FALSE );
}



/*  EOF  */
