(********************************************************************)
(*                                                                  *)
(*  null_file.s7i  Base implementation type for all files           *)
(*  Copyright (C) 1989 - 2013, 2021, 2022  Thomas Mertes            *)
(*                                                                  *)
(*  This file is part of the Seed7 Runtime Library.                 *)
(*                                                                  *)
(*  The Seed7 Runtime Library is free software; you can             *)
(*  redistribute it and/or modify it under the terms of the GNU     *)
(*  Lesser General Public License as published by the Free Software *)
(*  Foundation; either version 2.1 of the License, or (at your      *)
(*  option) any later version.                                      *)
(*                                                                  *)
(*  The Seed7 Runtime Library is distributed in the hope that it    *)
(*  will be useful, but WITHOUT ANY WARRANTY; without even the      *)
(*  implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR *)
(*  PURPOSE.  See the GNU Lesser General Public License for more    *)
(*  details.                                                        *)
(*                                                                  *)
(*  You should have received a copy of the GNU Lesser General       *)
(*  Public License along with this program; if not, write to the    *)
(*  Free Software Foundation, Inc., 51 Franklin Street,             *)
(*  Fifth Floor, Boston, MA  02110-1301, USA.                       *)
(*                                                                  *)
(********************************************************************)


include "file.s7i";


(**
 *  Base implementation type for the [[file]] interface.
 *  A ''null_file'' discards all data written to it. Reads from a
 *  ''null_file'' always yield [[char#EOF|EOF]] immediately. A ''null_file''
 *  is not seekable. The functions [[#length(in_null_file)|length]],
 *  [[#seek(in_null_file,in_integer)|seek]] and [[#tell(in_null_file)|tell]]
 *  raise FILE_ERROR. The functions [[#getc(inout_null_file)|getc]],
 *  [[#getwd(inout_null_file)|getwd]] and [[#getln(inout_null_file)|getln]]
 *  are written such that a derived type can use them. Derived
 *  types of ''null_file'' need to override the functions
 *  [[#write(in_null_file,in_string)|write]], [[#gets(in_null_file,in_integer)|gets]],
 *  [[#eof(in_null_file)|eof]] and [[#hasNext(in_null_file)|hasNext]].
 *  A derived type, which represents a seekable file, needs to
 *  override the functions [[#length(in_null_file)|length]],
 *  [[#seek(in_null_file,in_integer)|seek]] and [[#tell(in_null_file)|tell]].
 *)
const type: null_file is new struct
    var char: bufferChar is '\n';
  end struct;

type_implements_interface(null_file, file);

const func boolean: (in null_file: aFile1) = (in null_file: aFile2)   is action "ENU_EQ";
const func boolean: (in null_file: aFile1) <> (in null_file: aFile2)  is action "ENU_NE";


(**
 *  Standard null file.
 *  Anything written to STD_NULL is ignored. Reading from STD_NULL does not
 *  deliver data.
 *   eof(STD_NULL)      returns TRUE
 *   getc(STD_NULL)     returns EOF
 *   gets(STD_NULL, 1)  returns ""
 *  The file STD_NULL is used to initialize [[file]] variables
 *  and as result of [[external_file#open(in_string,in_string)|open]],
 *  if a ''file'' cannot be opened.
 *)
var null_file: STD_NULL is null_file.value;


(**
 *  Default value of ''file'' (STD_NULL).
 *)
const file: (attr file) . value is STD_NULL;


(**
 *  Write a [[string]] to a ''null_file''.
 *  The parameter ''stri'' is just ignored. Derived types of
 *  ''null_file'' need to override this function.
 *)
const proc: write (in null_file: outFile, in string: stri) is noop;


(**
 *  Write end-of-line to ''outFile''.
 *  This function writes the end-of-line marker '\n'. Derived types
 *  can use this function. If a derived type does not use '\n'
 *  as end-of-line marker it needs to override this function.
 *)
const proc: writeln (inout null_file: outFile) is func
  begin
    write(outFile, "\n");
  end func;


(**
 *  Write a [[string]] followed by end-of-line to ''outFile''.
 *  This function is based on write and writeln. Derived types
 *  can use this function. This function must be overridden, if
 *  it is necessary to write ''stri'' and '\n' together.
 *)
const proc: writeln (inout null_file: outFile, in string: stri) is func
  begin
    write(outFile, stri);
    writeln(outFile);
  end func;


(**
 *  Read a string with a maximum length from a ''null_file''.
 *  Derived types of ''null_file'' need to override this function.
 *  @return the empty string ("").
 *  @exception RANGE_ERROR The parameter ''maxLength'' is negative.
 *)
const func string: gets (in null_file: inFile, in integer: maxLength) is func
  result
    var string: striRead is "";
  begin
    if maxLength < 0 then
      raise RANGE_ERROR;
    end if;
  end func;


(**
 *  Read a character from a ''null_file''.
 *  This function is based on the gets function. Therefore it is
 *  useable for derived types of ''null_file''. For the ''null_file''
 *  itself it always returns [[char#EOF|EOF]].
 *  @return the character read, or [[char#EOF|EOF]] at the end of the file.
 *)
const func char: getc (inout null_file: inFile) is func
  result
    var char: charRead is ' ';
  local
    var string: buffer is "";
  begin
    buffer := gets(inFile, 1);
    if buffer = "" then
      charRead := EOF;
    else
      charRead := buffer[1];
    end if;
  end func;


(**
 *  Read a [[string]] from ''inFile'' until the ''terminator'' character is found.
 *  If a ''terminator'' is found the string before the ''terminator'' is
 *  returned and the ''terminator'' character is assigned to inFile.bufferChar.
 *  The file position is advanced after the ''terminator'' character.
 *  If no ''terminator'' is found the rest of ''inFile'' is returned and
 *  [[char#EOF|EOF]] is assigned to the inFile.bufferChar. When the function
 *  is left inFile.bufferChar contains either ''terminator'' or [[char#EOF|EOF]].
 *  This function is based on the gets function.
 *  Therefore it is useable for derived types of ''null_file''.
 *  For the ''null_file'' itself it always returns "" and assigns
 *  [[char#EOF|EOF]] to inFile.bufferChar.
 *  @param inFile File from which the string is read.
 *  @param terminator Character which terminates the string.
 *  @return the string read without the ''terminator'' or the rest of the
 *          file if no ''terminator'' is found.
 *)
const func string: getTerminatedString (inout null_file: inFile, in char: terminator) is func
  result
    var string: stri is "";
  local
    var string: buffer is "";
  begin
    buffer := gets(inFile, 1);
    while buffer <> "" and buffer[1] <> terminator do
      stri &:= buffer;
      buffer := gets(inFile, 1);
    end while;
    if buffer = "" then
      inFile.bufferChar := EOF;
    else
      inFile.bufferChar := terminator;
    end if;
  end func;


(**
 *  Read a word from a ''null_file''.
 *  Before reading the word it skips spaces and tabs. The function
 *  accepts words ending with ' ', '\t', '\n' or [[char#EOF|EOF]]. The
 *  word ending characters are not copied into the string. When the
 *  function is left inFile.bufferChar contains ' ', '\t', '\n' or
 *  [[char#EOF|EOF]]. This function is based on the gets function.
 *  Therefore it is useable for derived types of ''null_file''.
 *  For the ''null_file'' itself it always returns "" and assigns
 *  [[char#EOF|EOF]] to inFile.bufferChar.
 *  @return the word read.
 *)
const func string: getwd (inout null_file: inFile) is func
  result
    var string: word is "";
  local
    var string: buffer is "";
  begin
    repeat
      buffer := gets(inFile, 1);
    until buffer <> " " and buffer <> "\t";
    while buffer <> " " and buffer <> "\t" and
        buffer <> "\n" and buffer <> "" do
      word &:= buffer;
      buffer := gets(inFile, 1);
    end while;
    if buffer = "" then
      inFile.bufferChar := EOF;
    else
      inFile.bufferChar := buffer[1];
    end if;
  end func;


(**
 *  Read a line from a ''null_file''.
 *  The function accepts lines ending with '\n' or [[char#EOF|EOF]].
 *  The line ending characters are not copied into the string. When
 *  the function is left inFile.bufferChar contains '\n' or
 *  [[char#EOF|EOF]]. This function is based on the gets function.
 *  Therefore it is useable for derived types of ''null_file''.
 *  For the ''null_file'' itself it always returns "" and assigns
 *  [[char#EOF|EOF]] to inFile.bufferChar.
 *  @return the line read.
 *)
const func string: getln (inout null_file: inFile) is func
  result
    var string: line is "";
  local
    var string: buffer is "";
  begin
    buffer := gets(inFile, 1);
    while buffer <> "\n" and buffer <> "" do
      line &:= buffer;
      buffer := gets(inFile, 1);
    end while;
    if buffer = "" then
      inFile.bufferChar := EOF;
    else
      inFile.bufferChar := '\n';
    end if;
  end func;


(**
 *  Determine the end-of-file indicator.
 *  Derived types of ''null_file'' need to override this function.
 *  @return TRUE, since a ''null_file'' is always at end-of-file.
 *)
const boolean: eof (in null_file: inFile) is TRUE;


(**
 *  Determine if at least one character can be read successfully.
 *  Derived types of ''null_file'' need to override this function.
 *  @return FALSE, since the next ''getc'' will always return
 *          [[char#EOF|EOF]].
 *)
const boolean: hasNext (in null_file: inFile) is FALSE;


const func boolean: eoln (in null_file: inFile) is
  return inFile.bufferChar = '\n';


(**
 *  Obtain the length of a file.
 *  A ''null_file'' is not seekable, therefore FILE_ERROR is raised.
 *  Derived types of ''null_file'' need to override this function.
 *  @return nothing, because FILE_ERROR is always raised.
 *  @exception FILE_ERROR Is always raised, because a ''null_file'' is
 *             not seekable.
 *)
const func integer: length (in null_file: aFile) is func
  result
    var integer: length is 0;
  begin
    raise FILE_ERROR;
  end func;


(**
 *  Determine if the file ''aFile'' is seekable.
 *  If a file is seekable the functions ''seek'' and ''tell''
 *  can be used to set and and obtain the current file position.
 *  @return FALSE, since a ''null_file'' is not seekable.
 *)
const boolean: seekable (in null_file: aFile) is FALSE;


(**
 *  Set the current file position.
 *  A ''null_file'' is not seekable, therefore FILE_ERROR is raised.
 *  If a derived type is seekable it needs to override this function.
 *  @exception FILE_ERROR Is always raised, because a ''null_file'' is
 *             not seekable.
 *)
const proc: seek (in null_file: aFile, in integer: position) is func
  begin
    raise FILE_ERROR;
  end func;


(**
 *  Obtain the current file position.
 *  A ''null_file'' is not seekable, therefore FILE_ERROR is raised.
 *  If a derived type is seekable it needs to override this function.
 *  @return nothing, because FILE_ERROR is always raised.
 *  @exception FILE_ERROR Is always raised, because a ''null_file'' is
 *             not seekable.
 *)
const func integer: tell (in null_file: aFile) is func
  result
    var integer: position is 0;
  begin
    raise FILE_ERROR;
  end func;


const proc: moveLeft (in null_file: outFile, in string: stri) is func
  begin
    raise FILE_ERROR;
  end func;


const proc: erase (in null_file: outFile, in string: stri) is func
  begin
    raise FILE_ERROR;
  end func;


(**
 *  Close a ''null_file''.
 *  Closing a ''null_file'' has no effect.
 *)
const proc: close (in null_file: aFile) is noop;


(**
 *  Forces that all buffered data is sent to its destination.
 *  Flushing a ''null_file'' has no effect.
 *)
const proc: flush (in null_file: aFile) is noop;