/***************************************************************
  
        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                            */
/*                                                               */
/* Version 3.20 by Howard Wulf, AF5NE                            */
/*                                                               */
/*---------------------------------------------------------------*/



#include "bwbasic.h"

static int buff_read_using (char *buffer, int *position, char *format_string,
                            int format_length);
static LineType *bwb_mat_dump (LineType * l, int IsWrite);
static int bwb_print_at (LineType * l);
static void CleanNumericString (char *prnbuf, int RemoveDot);
static int CountDigits (char *Buffer);
static LineType *D71_PUT (LineType * l);
static LineType *file_write_matrix (LineType * l, char delimit);
static LineType *H14_PUT (LineType * Line);
static void internal_print (LineType * l, int IsCSV);
static int is_magic_number (char *buffer);
static int is_magic_string (char *buffer);
static int line_read_using (LineType * l, char *format_string,
                            int format_length);
static void next_zone (void);
static int parse_file_number (LineType * l);
static void print_using_number (char *buffer, int *position, VariantType * e);
static void print_using_string (char *buffer, int *position, VariantType * e);
static void print_using_variant (char *buffer, int *position, VariantType * e,
                                 int IsCSV);
static LineType *S70_PUT (LineType * l);
static void xputc1 (char c);
static void xputc2 (char c);
static void xputs (char *buffer);


/*
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->OptionUsingDigit        /* Digit placeholder, usually '#' */
#define PrintUsingNumberComma    My->CurrentVersion->OptionUsingComma        /* Comma, such as thousands, usually ',' */
#define PrintUsingNumberPeriod   My->CurrentVersion->OptionUsingPeriod        /* Period, such as dollars and cents, usually '.' */
#define PrintUsingNumberPlus     My->CurrentVersion->OptionUsingPlus        /* Plus  sign, positive value, usually '+' */
#define PrintUsingNumberMinus    My->CurrentVersion->OptionUsingMinus        /* Minus sign, negative value, usually '-' */
#define PrintUsingNumberExponent My->CurrentVersion->OptionUsingExrad        /* Exponential format, usually '^' */
#define PrintUsingNumberDollar   My->CurrentVersion->OptionUsingDollar        /* Currency symbol, usually '$' */
#define PrintUsingNumberFiller   My->CurrentVersion->OptionUsingFiller        /* Print filler, such as checks, usually '*' */
#define PrintUsingLiteral        My->CurrentVersion->OptionUsingLiteral        /* The next char is a literal, usually '_' */
#define PrintUsingStringFirst    My->CurrentVersion->OptionUsingFirst        /* The first character of the string, usually '!' */
#define PrintUsingStringAll      My->CurrentVersion->OptionUsingAll        /* Print the entire string, usually '&' */
#define PrintUsingStringLength   My->CurrentVersion->OptionUsingLength        /* Print a substring, usually '%' */


/*
**
** ZoneChar is a MAGIC character code used by file_write_matrix() to request printing by zones.
** ZoneChar can be any character, other than NulChar, that the user will not use as a literal delimiter.
** The user is allowed to specify CHR$(9), '\t', as a literal delimiter.
**
*/
#define ZoneChar 0x01                /* an unlikely literal delimiter */


int
is_empty_string (char *Buffer)
{
   

  if (Buffer == NULL)
  {
    return TRUE;
  }
  while (*Buffer == ' ')
  {
    Buffer++;
  }
  if (*Buffer == NulChar)
  {
    return TRUE;
  }
  return FALSE;
}


FileType *
find_file_by_name (char *FileName)
{
  FileType *F;
   
  if (is_empty_string (FileName))
  {
    /* the rules for Console and Printer vary by command */
    return NULL;
  }
  /* search the list of OPEN files */
  assert( My != NULL );
  for (F = My->FileHead; F != NULL; F = F->next)
  {
    assert( F != NULL );
    if (F->DevMode == DEVMODE_CLOSED)
    {
    }
    else if (F->FileName == NULL)
    {
    }
    else 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 */
  assert( My != NULL );
  for (F = My->FileHead; F != NULL; F = F->next)
  {
    assert( F != NULL );
    if (F->DevMode != 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;
   
  assert( My != NULL );
  for (F = My->FileHead; F != NULL; F = F->next)
  {
    assert( F != NULL );
    if (F->DevMode == DEVMODE_CLOSED)
    {
      /* FOUND */
      return F;
    }
  }
  /* NOT FOUND */
  if ((F = (FileType *) calloc (1, sizeof (FileType))) == NULL)
  {
    WARN_OUT_OF_MEMORY;
    return NULL;
  }
  assert( F != NULL );
  F->next = My->FileHead;
  My->FileHead = F;
  return F;
}


void
file_clear (FileType * F)
{
  /* clean up a file slot that is no longer needed */
   
  assert (F != NULL);

  clear_virtual_by_file (F->FileNumber);
  F->FileNumber = 0;
  F->DevMode = 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 = NulChar;                /* DELIMIT for READ and WRITE */
  if (F->FileName != NULL)
  {
    free (F->FileName);
    F->FileName = NULL;
  }
  if (F->cfp != NULL)
  {
    bwb_fclose (F->cfp);
    F->cfp = NULL;
  }
  if (F->buffer != NULL)
  {
    free (F->buffer);
    F->buffer = NULL;
  }

}

int
file_next_number (void)
{
  int FileNumber;
  FileType *F;
   

  FileNumber = 0;
  assert( My != NULL );
  for (F = My->FileHead; F != NULL; F = F->next)
  {
    assert( F != NULL );
    if (F->DevMode != 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.
  
***************************************************************/

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

  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 = NulChar;                /* for bwb_strlen()  */
  }
  D = bwb_strchr (prnbuf, '.');
  if (D)
  {
    int N;

    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 == NulChar */
        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
CountDigits (char *Buffer)
{
  int NumDigits;
  char *P;
   
  assert (Buffer != NULL);


  /* 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;
}

extern void
FormatBasicNumber (DoubleType Input, char *Output /* [ NUMLEN ] */ )
{
   /*******************************************************************************
   
   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;
   
  assert (Output != NULL);

  assert( My != NULL );
  if (My->OptionScaleInteger >= 1
      && My->OptionScaleInteger <= My->OptionDigitsInteger)
  {
    /* round */
    DoubleType Scale;
    Scale = pow (10, My->OptionScaleInteger);
    assert( Scale != 0 );
    Input = bwb_rint (Input * Scale) / Scale;
  }
  /* print in scientific form first, to determine exponent and significant digits */
  sprintf (Output, "% 1.*E", My->OptionDigitsInteger - 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, FALSE);
    NumDigits = CountDigits (Output);
    DisplayDigits = MIN (NumDigits, My->OptionDigitsInteger);
    zz = MAX (Exponent, DisplayDigits - Exponent - 2);
    if (zz >= My->OptionDigitsInteger)
    {
      /* SCIENTIFIC */
      sprintf (Output, "%# 1.*E", DisplayDigits - 1, Input);
    }
    else if (Input == (int) Input)
    {
      /* INTEGER */
      sprintf (Output, "% *d", DisplayDigits, (int) Input);
    }
    else
    {
      /* FLOAT */
      int Before;                /* number of digits before the '.' */
      int After;                /* number of digits after  the '.' */

      Before = Exponent + 1;
      if (Before < 0)
      {
        Before = 0;
      }
      After = My->OptionDigitsInteger - Before;
      if (After < 0)
      {
        After = 0;
      }
      sprintf (Output, "%# *.*f", Before, After, Input);
    }
    CleanNumericString (Output, FALSE);
  }
  else
  {
    /* ERROR, NAN, INFINITY, ETC. */
  }
}



LineType *
bwb_LPRINT (LineType * l)
{
  int IsCSV;
   
  assert (l != NULL);

  assert( My != NULL );
  assert( My->SYSPRN != NULL );
  My->CurrentFile = My->SYSPRN;
  IsCSV = FALSE;
  internal_print (l, IsCSV);
  return (l);
}


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


static int
bwb_print_at (LineType * l)
{
  int position;
  int r;
  int c;
   
  assert (l != NULL);


  position = 0;
  r = 0;
  c = 0;
  if (line_read_integer_expression (l, &position))
  {
    /* OK */
  }
  else
  {
    WARN_SYNTAX_ERROR;
    return FALSE;
  }

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

  if (position < 0)
  {
    WARN_SYNTAX_ERROR;
    return FALSE;
  }


  assert( My != NULL );
  assert( My->SYSOUT != NULL );
  if (My->SYSOUT->width <= 0)
  {
    WARN_SYNTAX_ERROR;
    return FALSE;
  }
  if (My->SCREEN_ROWS <= 0)
  {
    WARN_SYNTAX_ERROR;
    return FALSE;
  }
  assert( My->CurrentFile == My->SYSOUT );
  /* position is 0-based.  0 is top left, */
  assert( My->CurrentFile != NULL );
  assert( My->CurrentFile->width != 0 );
  r = position / My->CurrentFile->width;
  c = position - r * My->CurrentFile->width;
  while (r >= My->SCREEN_ROWS)
  {
    r -= My->SCREEN_ROWS;
  }
  r++;                                /* 0-based to 1-based */
  c++;                                /* 0-based to 1-based */
  bwx_LOCATE (r, c);
  return TRUE;
}


static int
parse_file_number (LineType * l)
{
  /* ... # FileNumber , ... */
  int FileNumber;
   
  assert (l != NULL);


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

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

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


    if (line_skip_CommaChar (l) /* comma specific */ )
    {
      /* 
         PRINT # file_number , record_number ; expression [, expression] ' RANDOM write
       */
      /* get the RecordNumber */
      int RecordNumber;

      if ((My->CurrentFile->DevMode & 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_SemicolonChar (l) /* semicolon specific */ )
    {
      /* PRINT # filenum          ; */
      /* PRINT # filenum , recnum ; */
    }
    else
    {
      WARN_SYNTAX_ERROR;
      return FALSE;
    }
    return TRUE;
  }
  /* 
     SERIAL file writes:
     PRINT # file_number   
     PRINT # file_number [, expression]
   */
  if (FileNumber < 0)
  {
    My->CurrentFile = My->SYSPRN;
  }
  else if (FileNumber == 0)
  {
    My->CurrentFile = My->SYSOUT;
  }
  else
  {
    /* normal file */
    My->CurrentFile = find_file_by_number (FileNumber);
  }
  if (My->CurrentFile == NULL)
  {
    WARN_BAD_FILE_NUMBER;
    return FALSE;
  }
  if ((My->CurrentFile->DevMode & DEVMODE_WRITE) == 0)
  {
    WARN_BAD_FILE_NUMBER;
    return FALSE;
  }
  if (line_is_eol (l))
  {
    /* PRINT # 2 */
  }
  else if (line_skip_seperator (l))
  {
    /* PRINT # 2 , ... */
  }
  else
  {
    WARN_SYNTAX_ERROR;
    return FALSE;
  }
  return TRUE;
}

LineType *
bwb_PRINT (LineType * l)
{
  int IsCSV;
   
  assert (l != NULL);

  IsCSV = FALSE;
  assert( My != NULL );
  if (My->IsPrinter == TRUE)
  {
    My->CurrentFile = My->SYSPRN;
  }
  else
  {
    My->CurrentFile = My->SYSOUT;
  }
  internal_print (l, IsCSV);
  return (l);
}

/***************************************************************
  
        FUNCTION:       internal_print()
  
   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 format_length)
{
  int p;
   
  assert (buffer != NULL);
  assert (position != NULL);
  assert (format_string != NULL);

  p = *position;

  if (buff_skip_word (buffer, &p, "USING"))
  {
    buff_skip_spaces (buffer, &p);        /* keep this */
    if (bwb_isdigit (buffer[p]))
    {
      /* PRINT USING ### */
      int n;
      int LineNumber;
      LineType *x;
      char *C;
      char *F;

      n = 0;
      LineNumber = 0;
      x = NULL;
      if (buff_read_line_number (buffer, &p, &LineNumber) == FALSE)
      {
        WARN_SYNTAX_ERROR;
        return FALSE;
      }
      /* check for target label */
      x = find_line_number (LineNumber);        /* USING 100 */
      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++;
      }
      assert( My != NULL );
      assert( My->CurrentVersion != NULL );
      if (*C == My->CurrentVersion->OptionQuoteChar)
      {
        /* QUOTED */
        /* skip leading quote */
        C++;
        while (*C != NulChar && *C != My->CurrentVersion->OptionQuoteChar)
        {
          /* copy format string, but not the trailing quote */
          if (n == format_length)
          {
            WARN_STRING_TOO_LONG;
            break;
          }
          *F = *C;
          C++;
          F++;
          n++;
        }
        /* skip trailing quote */
      }
      else
      {
        /* UNQUOTED */
        while (*C)
        {
          /* copy format string verbatim */
          if (n == format_length)
          {
            WARN_STRING_TOO_LONG;
            break;
          }
          *F = *C;
          C++;
          F++;
          n++;
        }
      }
      /* terminate format string */
      *F = NulChar;
      if (buff_skip_seperator (buffer, &p) == FALSE)
      {
        WARN_SYNTAX_ERROR;
        return FALSE;
      }
    }
    else
    {
      {
        char *Value;

        Value = NULL;
        if (buff_read_string_expression (buffer, &p, &Value) == FALSE)
        {
          WARN_SYNTAX_ERROR;
          return FALSE;
        }
        if (Value == NULL)
        {
          WARN_SYNTAX_ERROR;
          return FALSE;
        }
        if (bwb_strlen (Value) > format_length)
        {
          WARN_STRING_TOO_LONG;
          Value[format_length] = NulChar;
        }
        bwb_strcpy (format_string, Value);
        free (Value);
        Value = NULL;
      }
      if (buff_skip_seperator (buffer, &p) == FALSE)
      {
        WARN_SYNTAX_ERROR;
        return FALSE;
      }
    }
    *position = p;
    return TRUE;
  }
  return FALSE;
}

static int
line_read_using (LineType * l, char *format_string, int format_length)
{
  assert (l != NULL);
  assert (format_string != NULL);
  return buff_read_using (l->buffer, &(l->position), format_string,
                          format_length);
}

static void
internal_print (LineType * l, int IsCSV)
{
  /* if no arguments, simply print CR and return */
  /* 1980 PRINT  , , ,"A" */
  int OutputCR;
  char *format_string;
  int format_length;
  int format_position;
   
  assert (l != NULL);


  OutputCR = TRUE;
  assert( My != NULL );
  assert( My->ConsoleOutput != NULL );
  assert( MAX_LINE_LENGTH > 1 );
  format_string = My->ConsoleOutput;
  format_length = MAX_LINE_LENGTH;
  format_position = 0;
  format_string[0] = NulChar;

  if (line_skip_FilenumChar (l))
  {
    /* PRINT # file, ... */
    if (parse_file_number (l) == FALSE)
    {
      return;
    }
    assert( My->CurrentVersion != NULL );
    if (My->CurrentVersion->OptionVersionValue & (C77)
        && My->CurrentFile->FileNumber > 0)
    {
      /* 
       **
       ** CBASIC-II files are CSV files.
       **
       ** Strings are quoted other than PRINT USING.
       ** Comma seperator writes a literal comma.
       ** Semicolon seperator writes a literal comma.
       ** Numbers do NOT have leading or trailing spaces.
       **
       */
      IsCSV = TRUE;
    }
    OutputCR = TRUE;
  }
  else if (line_skip_AtChar (l))
  {
    /* PRINT @ position, ... */
    assert( My->SYSOUT != NULL );
    My->CurrentFile = My->SYSOUT;
    if (bwb_print_at (l) == FALSE)
    {
      return;
    }
    OutputCR = TRUE;
  }
  else if (My->CurrentVersion->OptionVersionValue & (B15|T80|HB1|HB2) 
  &&  line_skip_word (l, "AT"))
  {
    /* PRINT AT position, ... */
    assert( My->SYSOUT != NULL );
    My->CurrentFile = My->SYSOUT;
    if (bwb_print_at (l) == FALSE)
    {
      return;
    }
    OutputCR = TRUE;
  }
  assert( My->CurrentFile != NULL );

  while (line_is_eol (l) == FALSE)
  {
    /* LOOP THROUGH PRINT ELEMENTS */
    VariantType e;
    VariantType *E;

    E = &e;
    CLEAR_VARIANT (E);
    if (line_skip_CommaChar (l) /* comma-specific */ )
    {
      if (format_string[0])
      {
        /* PRINT USING active */
      }
      else if (IsCSV)
      {
        xputc1 (',');
      }
      else
      {
        /* tab over */
        next_zone ();
      }
      OutputCR = FALSE;
    }
    else if (line_skip_SemicolonChar (l) /* semicolon-specific */ )
    {
      if (format_string[0])
      {
        /* PRINT USING active */
      }
      else if (IsCSV)
      {
        xputc1 (',');
      }
      else
      {
        /* concatenate strings */
      }
      OutputCR = FALSE;
    }
    else if (line_read_using (l, format_string, format_length))
    {
      format_position = 0;
      OutputCR = TRUE;
    }
    else if (line_read_expression (l, E))        /* internal_print */
    {
      /* resolve the string */
      if (My->IsErrorPending /* 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 error should immediately halt further evaluation.
         **
         */
        RELEASE_VARIANT (E);
        return;
      }
      print_using_variant (format_string, &format_position, E, IsCSV);
      RELEASE_VARIANT (E);
      OutputCR = TRUE;
    }
    else
    {
      WARN_SYNTAX_ERROR;
      return;
    }
  }

  if (OutputCR == TRUE)
  {
    /* did not end with ',' or ';' */
    xputc1 ('\n');
  }
  if (My->CurrentFile == My->SYSOUT)
  {
    /* FOR I = 1 TO 1000: PRINT "."; : NEXT I : PRINT */
    fflush (My->SYSOUT->cfp);
  }
}


/***************************************************************
  
        FUNCTION:       print_using_variant()
  
   DESCRIPTION:    This function gets the PRINT USING
         format string, returning a structure
         to the format.
  
***************************************************************/
static void
print_using_number (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;
  int precision;
  int exponent;
  char HeadChar;
  char FillChar;
  char CurrChar;
  char ComaChar;
  char TailChar;
  int p;
  char *tbuf;
   
  assert (buffer != NULL);
  assert (position != NULL);
  assert (e != NULL);



  width = 0;
  precision = 0;
  exponent = 0;
  HeadChar = ' ';
  FillChar = ' ';
  CurrChar = ' ';
  ComaChar = ' ';
  TailChar = ' ';
  assert( My != NULL );
  assert( My->ConsoleInput != NULL );
  tbuf = My->ConsoleInput;


  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;
  }


  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;
  }

  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] = NulChar;
    }
  }
  else
  {
    WARN_INTERNAL_ERROR;
    return;
  }


  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;
  }

  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;
  }

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

    dig_pos = -1;
    dec_pos = -1;
    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 && 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))
        {
          xputc1 (PrintUsingNumberComma);
        }
        xputc1 (tbuf[i]);
      }
    }
  }
  else
  {
    WARN_INTERNAL_ERROR;
    return;
  }
  *position = p;
}

static void
print_using_string (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;
   
  assert (buffer != NULL);
  assert (position != NULL);
  assert (e != NULL);
  assert( My != NULL );
  assert( My->NumLenBuffer != NULL );

  p = *position;

  if (e->VariantTypeCode == StringTypeCode)
  {
    tbuf = e->Buffer;
  }
  else
  {
    tbuf = My->NumLenBuffer;
    FormatBasicNumber (e->Number, tbuf);
  }

  if (IS_CHAR (buffer[p], PrintUsingStringFirst))
  {
    /* print first character only */
    int i;

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

    i = 0;
    if (tbuf[i] == NulChar)
    {
      xputc1 (' ');
    }
    else
    {
      xputc1 (tbuf[i]);
      i++;
    }
    p++;

    while (buffer[p] != NulChar && buffer[p] != PrintUsingStringLength)
    {
      if (tbuf[i] == NulChar)
      {
        xputc1 (' ');
      }
      else
      {
        xputc1 (tbuf[i]);
        i++;
      }
      p++;
    }
    if (IS_CHAR (buffer[p], PrintUsingStringLength))
    {
      if (tbuf[i] == NulChar)
      {
        xputc1 (' ');
      }
      else
      {
        xputc1 (tbuf[i]);
        i++;
      }
      p++;
    }
  }
  *position = p;
}

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.
   */
   
  assert (buffer != NULL);


  /* 1 character sequences */
  if (IS_CHAR (buffer[0], PrintUsingStringFirst))
  {
    /* "!" */
    return TRUE;
  }
  if (IS_CHAR (buffer[0], PrintUsingStringAll))
  {
    /* "&" */
    return TRUE;
  }
  if (IS_CHAR (buffer[0], PrintUsingStringLength))
  {
    /* "%...%" */
    return TRUE;
  }

  /* 2 character sequences */

  /* 3 character sequences */

  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.
   */
   
  assert (buffer != NULL);

  /* 1 character sequences */
  if (IS_CHAR (buffer[0], PrintUsingNumberDigit))
  {
    /* "#" */
    return TRUE;
  }

  /* 2 character sequences */
  if (IS_CHAR (buffer[0], PrintUsingNumberFiller))
    if (IS_CHAR (buffer[1], PrintUsingNumberFiller))
    {
      /* "**" */
      return TRUE;
    }
  if (IS_CHAR (buffer[0], PrintUsingNumberDollar))
    if (IS_CHAR (buffer[1], PrintUsingNumberDollar))
    {
      /* "$$" */
      return TRUE;
    }

  if (IS_CHAR (buffer[0], PrintUsingNumberPlus))
    if (IS_CHAR (buffer[1], PrintUsingNumberDigit))
    {
      /* "+#" */
      return TRUE;
    }
  if (IS_CHAR (buffer[0], PrintUsingNumberMinus))
    if (IS_CHAR (buffer[1], PrintUsingNumberDigit))
    {
      /* "-#" */
      return TRUE;
    }

  /* 3 character sequences */
  if (IS_CHAR (buffer[0], PrintUsingNumberPlus))
    if (IS_CHAR (buffer[1], PrintUsingNumberFiller))
      if (IS_CHAR (buffer[2], PrintUsingNumberFiller))
      {
        /* "+**" */
        return TRUE;
      }
  if (IS_CHAR (buffer[0], PrintUsingNumberPlus))
    if (IS_CHAR (buffer[1], PrintUsingNumberDollar))
      if (IS_CHAR (buffer[2], PrintUsingNumberDollar))
      {
        /* "+$$" */
        return TRUE;
      }
  if (IS_CHAR (buffer[0], PrintUsingNumberMinus))
    if (IS_CHAR (buffer[1], PrintUsingNumberFiller))
      if (IS_CHAR (buffer[2], PrintUsingNumberFiller))
      {
        /* "-**" */
        return TRUE;
      }
  if (IS_CHAR (buffer[0], PrintUsingNumberMinus))
    if (IS_CHAR (buffer[1], PrintUsingNumberDollar))
      if (IS_CHAR (buffer[2], PrintUsingNumberDollar))
      {
        /* "-$$" */
        return TRUE;
      }

  return FALSE;
}

static void
print_using_variant (char *buffer, int *position, VariantType * e, int IsCSV)
{
  /*
     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 IsUsed;
   
  assert (buffer != NULL);
  assert (position != NULL);
  assert (e != NULL);
  assert( My != NULL );
  assert( My->NumLenBuffer != NULL );

  /* PRINT A, B, C */
  /* PRINT USING "", A, B, C */
  /* PRINT USING "#", A, B, C */

  IsUsed = FALSE;
  if (buffer[0])
  {
    /* we have a format string */
    int p;
    p = *position;

    if (p > 0 && buffer[p] == NulChar)
    {
      /* recycle the format string */
      p = 0;
    }
    while (buffer[p])
    {
      if (is_magic_string (&buffer[p]))
      {
        if (IsUsed)
        {
          /* stop here, ready for next string value */
          break;
        }
        if (e->VariantTypeCode != StringTypeCode)
        {
          /* we are a number value, so we cannot match a magic string */
          break;
        }
        /* magic and value are both string */
        print_using_string (buffer, &p, e);
        IsUsed = TRUE;
      }
      else if (is_magic_number (&buffer[p]))
      {
        if (IsUsed)
        {
          /* stop here, ready for next number value */
          break;
        }
        if (e->VariantTypeCode == StringTypeCode)
        {
          /* we are a string value, so we cannot match a magic number */
          break;
        }
        /* magic and value are both number */
        print_using_number (buffer, &p, e);
        IsUsed = TRUE;
      }
      else if (IS_CHAR (buffer[p], PrintUsingLiteral))
      {
        /* print next character as literal */
        p++;
        if (buffer[p] == NulChar)
        {
          /* PRINT USING "_" */
          xputc1 (' ');
        }
        else
        {
          /* PRINT USING "_%" */
          xputc1 (buffer[p]);
          p++;
        }
      }
      else
      {
        /* print this character as literal */
        /* PRINT USING "A" */
        xputc1 (buffer[p]);
        p++;
      }
    }
    *position = p;
  }

  if (IsUsed == FALSE)
  {
    /* we did not actually print the vlue */
    if (e->VariantTypeCode == StringTypeCode)
    {
      /*
       **
       ** PRINT A$    
       ** PRINT USING "";A$    
       ** PRINT USING "ABC";A$ 
       **
       */
      if (IsCSV)
      {
        xputc1 ('\"');
        xputs (e->Buffer);
        xputc1 ('\"');
      }
      else
      {
        xputs (e->Buffer);
      }
    }
    else
    {
      /*
       **
       ** PRINT X     
       ** PRINT USING "";X     
       ** PRINT USING "ABC";X  
       **
       ** [space]number[space]   POSITIVE or ZERO
       ** [minus]number[space]   NEGATIVE 
       **
       **/
      char *tbuf;

      tbuf = My->NumLenBuffer;

      FormatBasicNumber (e->Number, tbuf);

      if (IsCSV)
      {
        char *P;
        P = tbuf;
        while (*P == ' ')
        {
          P++;
        }
        xputs (P);
      }
      else
      {
        xputs (tbuf);
        xputc1 (' ');
      }
    }
  }
}

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

static void
xputs (char *buffer)
{
   
  assert (buffer != NULL);
  assert( My != NULL );  
  assert (My->CurrentFile != NULL);

  if (My->CurrentFile->width > 0)
  {
    /* check to see if the width will be exceeded */
    int n;
    n = My->CurrentFile->col + bwb_strlen (buffer) - 1;
    if (n > My->CurrentFile->width)
    {
      xputc1 ('\n');
    }
  }
  /* output the string */
  while (*buffer)
  {
    xputc1 (*buffer);
    buffer++;
  }
}


/***************************************************************
  
        FUNCTION:       next_zone()
  
   DESCRIPTION:    Advance to the next print zone.
  
***************************************************************/
static void
next_zone (void)
{
  assert( My != NULL );
  assert (My->CurrentFile != NULL);

  if (My->CurrentFile->width > 0)
  {
    /*
     **
     ** check to see if width will be exceeded 
     **
     */
    int LastZoneColumn;

    LastZoneColumn = 1;
    while (LastZoneColumn < My->CurrentFile->width)
    {
      LastZoneColumn += My->OptionZoneInteger;
    }
    LastZoneColumn -= My->OptionZoneInteger;

    if (My->CurrentFile->col >= LastZoneColumn)
    {
      /*
       **
       ** width will be exceeded, so advance to a new line
       **
       */
      xputc1 ('\n');
      return;
    }
  }
  /*
   **
   ** advance to the next print zone
   **
   */
  if ((My->CurrentFile->col % My->OptionZoneInteger) == 1)
  {
    xputc1 (' ');
  }
  while ((My->CurrentFile->col % My->OptionZoneInteger) != 1)
  {
    xputc1 (' ');
  }
}

/***************************************************************
  
        FUNCTION:       xputc1()
  
   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 void
xputc1 (char c)
{
  assert( My != NULL );
  assert (My->CurrentFile != NULL);

  if (My->CurrentFile->width > 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)
      {
        xputc2 ('\n');                /* output LF */
      }
    }
  }
  /*
   **
   ** output the character
   **
   */
  xputc2 (c);
}

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


static void
xputc2 (char c)
{
  assert( My != NULL );
  assert (My->CurrentFile != NULL);
  assert (My->CurrentFile->cfp != NULL);
  assert( My->CurrentVersion != NULL );

  if (c == '\n')
  {
    /*
     **
     ** CBASIC-II: RANDOM files are padded on the right with spaces 
     **
     */
    if (My->CurrentVersion->OptionVersionValue & (C77))
      if (My->CurrentFile->DevMode & DEVMODE_RANDOM)
        if (My->CurrentFile->width > 0)
        {
#if HAVE_MSDOS
          /* "\n" is converted to "\r\n" */
          while (My->CurrentFile->col < (My->CurrentFile->width - 1))
#else /*  ! HAVE_MSDOS */
          while (My->CurrentFile->col < My->CurrentFile->width)
#endif /* ! HAVE_MSDOS */
          {
            fputc (' ', My->CurrentFile->cfp);
            My->CurrentFile->col++;
          }
        }
    /*
     **
     ** output the character 
     **
     */
    fputc (c, My->CurrentFile->cfp);
    /*
     **
     ** NULLS 
     **
     */
    if (My->LPRINT_NULLS > 0)
      if (My->CurrentFile == My->SYSPRN)
        if (My->CurrentFile->width > 0)
        {
          int i;
          for (i = 0; i < My->LPRINT_NULLS; i++)
          {
            fputc (NulChar, My->SYSPRN->cfp);
          }
        }
    /*
     **
     ** update current column position 
     **
     */
    My->CurrentFile->col = 1;
    My->CurrentFile->row++;
    return;
  }
  /*
   **
   ** output the character 
   **
   */
  fputc (c, My->CurrentFile->cfp);
  /*
   **
   ** update current column position 
   **
   */
  My->CurrentFile->col++;
}


extern void
ResetConsoleColumn (void)
{
  assert( My != NULL );
  assert (My->SYSOUT != NULL);

  My->SYSOUT->col = 1;
}

static LineType *
S70_PUT (LineType * l)
{
  /* PUT filename$ , value [, ...] */
  VariantType e;
  VariantType *E;
   
  assert (l != NULL);
  assert( My != NULL );
  assert( My->CurrentVersion != NULL );
  assert( My->NumLenBuffer != NULL );

  E = &e;
  CLEAR_VARIANT (E);
  if (line_read_expression (l, E) == FALSE)        /* bwb_PUT */
  {
    goto EXIT;
  }
  if (E->VariantTypeCode == StringTypeCode)
  {
    /* STRING */
    /* PUT filename$ ... */
    if (is_empty_string (E->Buffer))
    {
      /* PUT "" ... is an error */
      WARN_BAD_FILE_NAME;
      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)
      {
        WARN_BAD_FILE_NAME;
        goto EXIT;
      }
      My->CurrentFile->FileNumber = file_next_number ();
      My->CurrentFile->DevMode = DEVMODE_OUTPUT;
      My->CurrentFile->width = 0;
      /* WIDTH == RECLEN */
      My->CurrentFile->col = 1;
      My->CurrentFile->row = 1;
      My->CurrentFile->delimit = ',';
      My->CurrentFile->buffer = NULL;
      if (My->CurrentFile->FileName != NULL)
      {
        free (My->CurrentFile->FileName);
        My->CurrentFile->FileName = NULL;
      }
      My->CurrentFile->FileName = E->Buffer;
      E->Buffer = NULL;
    }
  }
  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;
    }
  }
  if (My->CurrentFile == NULL)
  {
    WARN_BAD_FILE_NUMBER;
    goto EXIT;
  }
  if ((My->CurrentFile->DevMode & DEVMODE_WRITE) == 0)
  {
    WARN_BAD_FILE_NUMBER;
    goto EXIT;
  }
  if (line_is_eol (l))
  {
    /* PUT F$ */
    /* PUT #1 */
    xputc1 ('\n');
    goto EXIT;
  }
  else if (line_skip_seperator (l))
  {
    /* OK */
  }
  else
  {
    WARN_SYNTAX_ERROR;
    goto EXIT;
  }

  /* loop through elements */

  while (line_is_eol (l) == FALSE)
  {
    while (line_skip_seperator (l))
    {
      /* PUT F$, ,,,A,,,B,,, */
      /* PUT #1, ,,,A,,,B,,, */
      xputc1 (My->CurrentFile->delimit);
    }

    if (line_is_eol (l) == FALSE)
    {
      /* print this item */

      CLEAR_VARIANT (E);
      if (line_read_expression (l, E) == FALSE)        /* bwb_PUT */
      {
        goto EXIT;
      }
      if (E->VariantTypeCode == StringTypeCode)
      {
        /* STRING */
        xputc1 (My->CurrentVersion->OptionQuoteChar);
        xputs (E->Buffer);
        xputc1 (My->CurrentVersion->OptionQuoteChar);
      }
      else
      {
        /* NUMBER */
        char *tbuf;

        tbuf = My->NumLenBuffer;
        FormatBasicNumber (E->Number, tbuf);
        xputs (tbuf);
      }
      RELEASE_VARIANT (E);
    }
  }
  /* print LF */
  xputc1 ('\n');
  /* OK */
EXIT:
  RELEASE_VARIANT (E);
  return (l);
}


static LineType *
D71_PUT (LineType * l)
{
  /* PUT # file_number [ , RECORD record_number ] */
  int file_number;
   
  assert (l != NULL);
  assert( My != NULL );

  file_number = 0;
  if (line_skip_FilenumChar (l))
  {
    /* OPTIONAL */
  }
  if (line_read_integer_expression (l, &file_number) == FALSE)
  {
    WARN_BAD_FILE_NUMBER;
    return (l);
  }
  if (file_number < 1)
  {
    WARN_BAD_FILE_NUMBER;
    return (l);
  }
  My->CurrentFile = find_file_by_number (file_number);
  if (My->CurrentFile == NULL)
  {
    WARN_BAD_FILE_NUMBER;
    return (l);
  }
  if (My->CurrentFile->DevMode != DEVMODE_RANDOM)
  {
    WARN_BAD_FILE_NUMBER;
    return (l);
  }
  if (My->CurrentFile->width <= 0)
  {
    WARN_BAD_FILE_NUMBER;
    return (l);
  }
  if (line_is_eol (l))
  {
    /* PUT # file_number */
  }
  else
  {
    /* PUT # file_number , RECORD record_number */
    int record_number;
    long offset;

    record_number = 0;
    offset = 0;
    if (line_skip_seperator (l) == FALSE)
    {
      WARN_SYNTAX_ERROR;
      return (l);
    }
    if (line_skip_word (l, "RECORD") == FALSE)
    {
      WARN_SYNTAX_ERROR;
      return (l);
    }
    if (line_read_integer_expression (l, &record_number) == FALSE)
    {
      WARN_BAD_RECORD_NUMBER;
      return (l);
    }
    if (record_number <= 0)
    {
      WARN_BAD_RECORD_NUMBER;
      return (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 (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 (l);
}

static LineType *
H14_PUT (LineType * Line)
{
  /* PUT # FileNumber [ , RecordNumber ]                   ' RANDOM */
  /* PUT # FileNumber   , [ BytePosition ] , scalar [,...] ' BINARY */
  int file_number;
   
  assert (Line != NULL);
  assert( My != NULL );

  file_number = 0;
  if (line_skip_FilenumChar (Line))
  {
    /* OPTIONAL */
  }
  if (line_read_integer_expression (Line, &file_number) == FALSE)
  {
    WARN_BAD_FILE_NUMBER;
    return (Line);
  }
  if (file_number < 1)
  {
    WARN_BAD_FILE_NUMBER;
    return (Line);
  }
  My->CurrentFile = find_file_by_number (file_number);
  if (My->CurrentFile == NULL)
  {
    WARN_BAD_FILE_NUMBER;
    return (Line);
  }
  if (My->CurrentFile->DevMode == DEVMODE_RANDOM)
  {
    /* PUT # FileNumber [ , RecordNumber ]                   ' RANDOM */
    if (My->CurrentFile->width <= 0)
    {
      WARN_BAD_FILE_NUMBER;
      return (Line);
    }
    if (line_is_eol (Line))
    {
      /* PUT # file_number */
    }
    else
    {
      /* PUT # FileNumber , RecordNumber                   ' RANDOM */
      int record_number;
      long offset;

      record_number = 0;
      offset = 0;
      if (line_skip_seperator (Line) == FALSE)
      {
        WARN_SYNTAX_ERROR;
        return (Line);
      }
      if (line_read_integer_expression (Line, &record_number) == FALSE)
      {
        WARN_BAD_RECORD_NUMBER;
        return (Line);
      }
      if (record_number <= 0)
      {
        WARN_BAD_RECORD_NUMBER;
        return (Line);
      }
      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 (Line);
      }
    }
    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 (Line);
  }
  else if (My->CurrentFile->DevMode == DEVMODE_BINARY)
  {
    /* PUT # FileNumber   , [ BytePosition ] , scalar [,...] ' BINARY */
    if (line_skip_seperator (Line) == FALSE)
    {
      WARN_SYNTAX_ERROR;
      return (Line);
    }
    if (line_skip_seperator (Line))
    {
      /* BytePosition not provided */
    }
    else
    {
      int RecordNumber;
      long offset;

      RecordNumber = 0;
      offset = 0;
      if (line_read_integer_expression (Line, &RecordNumber) == FALSE)
      {
        WARN_BAD_RECORD_NUMBER;
        return (Line);
      }
      if (RecordNumber <= 0)
      {
        WARN_BAD_RECORD_NUMBER;
        return (Line);
      }
      RecordNumber--;                /* BASIC to C */
      offset = RecordNumber;
      if (fseek (My->CurrentFile->cfp, offset, SEEK_SET) != 0)
      {
        WARN_BAD_RECORD_NUMBER;
        return (Line);
      }
      if (line_skip_seperator (Line) == FALSE)
      {
        WARN_SYNTAX_ERROR;
        return (Line);
      }
    }
    do
    {
      VariableType *v;

      if ((v = line_read_scalar (Line)) == NULL)
      {
        WARN_SYNTAX_ERROR;
        return (Line);
      }
      if (binary_get_put (v, TRUE) == FALSE)
      {
        WARN_SYNTAX_ERROR;
        return (Line);
      }
    }
    while (line_skip_seperator (Line));
    /* OK */
    return (Line);
  }
  WARN_BAD_FILE_MODE;
  return (Line);
}


extern LineType *
bwb_PUT (LineType * Line)
{
   
  assert (Line != NULL);
  assert( My != NULL );
  assert( My->CurrentVersion != NULL );

  if (My->CurrentVersion->OptionVersionValue & (S70 | I70 | I73))
  {
    return S70_PUT (Line);
  }
  if (My->CurrentVersion->OptionVersionValue & (D71 | R86))
  {
    return D71_PUT (Line);
  }
  if (My->CurrentVersion->OptionVersionValue & (H14))
  {
    return H14_PUT (Line);
  }
  WARN_INTERNAL_ERROR;
  return (Line);
}


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


extern LineType *
bwb_WRITE (LineType * l)
{
  int IsCSV;
   
  assert (l != NULL);

  IsCSV = TRUE;
  assert( My != NULL );
  assert( My->SYSOUT != NULL );
  My->CurrentFile = My->SYSOUT;
  internal_print (l, IsCSV);
  return (l);
}

static LineType *
file_write_matrix (LineType * l, char delimit)
{
  /* MAT PRINT  [ # filenumber , ] matrix [;|,] ... */
  /* MAT WRITE  [ # filenumber , ] matrix [;|,] ... */
  /* MAT PUT    filename$      ,   matrix [;|,] ... */
  /* MAT PUT    filenumber     ,   matrix [;|,] ... */
  /* Array must be 1, 2 or 3 dimensions    */
  /* Array may be either NUMBER or STRING  */
   
  assert (l != NULL);

  do
  {
    VariableType *v;
    char ItemSeperator;

    /* get matrix name */
    if ((v = line_read_matrix (l)) == NULL)
    {
      WARN_SUBSCRIPT_OUT_OF_RANGE;
      return (l);
    }

    /* variable MUST be an array of 1, 2 or 3 dimensions */
    if (v->dimensions < 1)
    {
      WARN_SUBSCRIPT_OUT_OF_RANGE;
      return (l);
    }
    if (v->dimensions > 3)
    {
      WARN_SUBSCRIPT_OUT_OF_RANGE;
      return (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
     **
     */
    ItemSeperator = NulChar;        /* concatenate the columns */
    if (line_skip_CommaChar (l) /* comma-specific */ )
    {
      /*
       **
       ** force printing with the specified delimiter,
       ** which is usually a Comma but can be any character.
       **
       */
      ItemSeperator = delimit;        /* for MAT PRINT this is forced to be a ZoneChar */
    }
    else if (line_skip_SemicolonChar (l) /* semicolon-specific */ )
    {
      /*
       **
       ** force concatenating the columns,
       ** ignoring the specified delimiter.
       **
       */
      ItemSeperator = NulChar;
    }
    else
    {
      /*
       **
       ** default the item seperator based upon variable's dimensions
       **
       */
      switch (v->dimensions)
      {
      case 1:
        /* by default, a one dimension array is printed row-by-row */
        ItemSeperator = '\n';
        break;
      case 2:
        /* by default, a two dimension array is printed col-by-col */
        ItemSeperator = delimit;
        break;
      case 3:
        /* by default, a three dimension array is printed col-by-col */
        ItemSeperator = delimit;
        break;
      }
    }
    /* 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->VINDEX[0] = v->LBOUND[0]; v->VINDEX[0] <= v->UBOUND[0];
             v->VINDEX[0]++)
        {
          VariantType variant;
          CLEAR_VARIANT (&variant);

          if (v->VINDEX[0] > v->LBOUND[0])
          {
            switch (ItemSeperator)
            {
            case NulChar:
              break;
            case ZoneChar:
              next_zone ();
              break;
            default:
              xputc1 (ItemSeperator);
            }
          }
          if (var_get (v, &variant) == FALSE)
          {
            WARN_VARIABLE_NOT_DECLARED;
            return (l);
          }
          if (variant.VariantTypeCode == StringTypeCode)
          {
            xputs (variant.Buffer);
          }
          else
          {
            char *tbuf;

            tbuf = My->NumLenBuffer;
            FormatBasicNumber (variant.Number, tbuf);
            xputs (tbuf);
          }
        }
        xputc1 ('\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->VINDEX[0] = v->LBOUND[0]; v->VINDEX[0] <= v->UBOUND[0];
             v->VINDEX[0]++)
        {
          for (v->VINDEX[1] = v->LBOUND[1]; v->VINDEX[1] <= v->UBOUND[1];
               v->VINDEX[1]++)
          {
            VariantType variant;
            CLEAR_VARIANT (&variant);

            if (v->VINDEX[1] > v->LBOUND[1])
            {
              switch (ItemSeperator)
              {
              case NulChar:
                break;
              case ZoneChar:
                next_zone ();
                break;
              default:
                xputc1 (ItemSeperator);
              }
            }
            if (var_get (v, &variant) == FALSE)
            {
              WARN_VARIABLE_NOT_DECLARED;
              return (l);
            }
            if (variant.VariantTypeCode == StringTypeCode)
            {
              xputs (variant.Buffer);
            }
            else
            {
              char *tbuf;

              tbuf = My->NumLenBuffer;
              FormatBasicNumber (variant.Number, tbuf);
              xputs (tbuf);
            }
          }
          xputc1 ('\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->VINDEX[0] = v->LBOUND[0]; v->VINDEX[0] <= v->UBOUND[0];
             v->VINDEX[0]++)
        {
          for (v->VINDEX[1] = v->LBOUND[1]; v->VINDEX[1] <= v->UBOUND[1];
               v->VINDEX[1]++)
          {
            for (v->VINDEX[2] = v->LBOUND[2]; v->VINDEX[2] <= v->UBOUND[2];
                 v->VINDEX[2]++)
            {
              VariantType variant;
              CLEAR_VARIANT (&variant);

              if (v->VINDEX[2] > v->LBOUND[2])
              {
                switch (ItemSeperator)
                {
                case NulChar:
                  break;
                case ZoneChar:
                  next_zone ();
                  break;
                default:
                  xputc1 (ItemSeperator);
                }
              }
              if (var_get (v, &variant) == FALSE)
              {
                WARN_VARIABLE_NOT_DECLARED;
                return (l);
              }
              if (variant.VariantTypeCode == StringTypeCode)
              {
                xputs (variant.Buffer);
              }
              else
              {
                char *tbuf;

                tbuf = My->NumLenBuffer;
                FormatBasicNumber (variant.Number, tbuf);
                xputs (tbuf);
              }
            }
            xputc1 ('\n');
          }
          xputc1 ('\n');
        }
      }
      break;
    }
    /* process the next variable, if any  */
  }
  while (line_is_eol (l) == FALSE);
  return (l);
}

extern LineType *
bwb_MAT_PUT (LineType * l)
{
  /* MAT PUT filename$  , matrix [;|,] ... */
  /* MAT PUT filenumber , matrix [;|,] ... */
  /* Array must be 1, 2 or 3 dimensions    */
  /* Array may be either NUMBER or STRING  */
  VariantType x;
  VariantType *X;
   
  assert (l != NULL);
  assert( My != NULL );
  assert( My->SYSOUT != NULL );

  My->CurrentFile = My->SYSOUT;
  X = &x;
  CLEAR_VARIANT (X);
  if (line_read_expression (l, X) == FALSE)        /* bwb_MAT_PUT */
  {
    goto EXIT;
  }
  if (X->VariantTypeCode == StringTypeCode)
  {
    /* STRING */
    /* MAT PUT filename$ ... */
    if (is_empty_string (X->Buffer))
    {
      /* MAT PUT "" ... is an error */
      WARN_BAD_FILE_NAME;
      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)
      {
        WARN_BAD_FILE_NAME;
        goto EXIT;
      }
      My->CurrentFile->FileNumber = file_next_number ();
      My->CurrentFile->DevMode = DEVMODE_OUTPUT;
      My->CurrentFile->width = 0;
      /* WIDTH == RECLEN */
      My->CurrentFile->col = 1;
      My->CurrentFile->row = 1;
      My->CurrentFile->delimit = ',';
      My->CurrentFile->buffer = NULL;
      if (My->CurrentFile->FileName != NULL)
      {
        free (My->CurrentFile->FileName);
        My->CurrentFile->FileName = NULL;
      }
      My->CurrentFile->FileName = X->Buffer;
      X->Buffer = NULL;
    }
  }
  else
  {
    /* NUMBER -- file must already be OPEN */
    /* MAT 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_VARIANT (X);
  if (My->CurrentFile == NULL)
  {
    WARN_BAD_FILE_NUMBER;
    goto EXIT;
  }
  if ((My->CurrentFile->DevMode & DEVMODE_WRITE) == 0)
  {
    WARN_BAD_FILE_NUMBER;
    goto EXIT;
  }
  if (line_skip_seperator (l))
  {
    /* OK */
  }
  else
  {
    WARN_SYNTAX_ERROR;
    goto EXIT;
  }
  return file_write_matrix (l, My->CurrentFile->delimit);
EXIT:
  RELEASE_VARIANT (X);
  return (l);
}

static LineType *
bwb_mat_dump (LineType * l, int IsWrite)
{
  /* MAT PRINT  [ # filenumber , ] matrix [;|,] ... */
  /* MAT WRITE  [ # filenumber , ] matrix [;|,] ... */
  /* Array must be 1, 2 or 3 dimensions    */
  /* Array may be either NUMBER or STRING  */
  char delimit;
   
  assert (l != NULL);
  assert( My != NULL );
  assert( My->SYSOUT != NULL );

  My->CurrentFile = My->SYSOUT;
  if (line_skip_FilenumChar (l))
  {
    /* ... # file, ... */
    if (parse_file_number (l) == FALSE)
    {
      return (l);
    }
    if (line_is_eol (l))
    {
      WARN_SYNTAX_ERROR;
      return (l);
    }
  }

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

extern LineType *
bwb_MAT_WRITE (LineType * l)
{
   
  assert (l != NULL);

  return bwb_mat_dump (l, TRUE);
}

extern LineType *
bwb_MAT_PRINT (LineType * l)
{
   
  assert (l != NULL);

  return bwb_mat_dump (l, FALSE);
}



/*  EOF  */
