
(********************************************************************)
(*                                                                  *)
(*  strifile.s7i  Implementation type for files stored in a string  *)
(*  Copyright (C) 2008  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.                       *)
(*                                                                  *)
(********************************************************************)


(**
 *  [[file|File]] implementation type for files stored in a [[string]].
 *)
const type: strifile is sub null_file struct
    var string: content is "";
    var integer: position is 1;
  end struct;

type_implements_interface(strifile, file);


(**
 *  Open a ''strifile'' with the given string content.
 *  @return the file opened.
 *)
const func file: openStrifile (in string: content) is func
  result
    var file: newFile is STD_NULL;
  local
    var strifile: new_strifile is strifile.value;
  begin
    new_strifile.content := content;
    newFile := toInterface(new_strifile);
  end func;


(**
 *  Open a ''strifile'' with an empty string content.
 *  @return the file opened.
 *)
const func file: openStrifile is func
  result
    var file: newFile is STD_NULL;
  local
    var strifile: new_strifile is strifile.value;
  begin
    newFile := toInterface(new_strifile);
  end func;


(**
 *  Write the [[string]] ''stri'' to ''outStrifile''.
 *)
const proc: write (inout strifile: outStrifile, in string: stri) is func
  begin
    if outStrifile.position = succ(length(outStrifile.content)) then
      outStrifile.content &:= stri;
    elsif outStrifile.position <= length(outStrifile.content) then
      if pred(outStrifile.position) <= length(outStrifile.content) - length(stri) then
        outStrifile.content @:= [outStrifile.position] stri;
      else
        outStrifile.content := outStrifile.content[.. pred(outStrifile.position)];
        outStrifile.content &:= stri;
      end if;
    elsif outStrifile.position > length(outStrifile.content) then
      outStrifile.content &:=
          "\0;" mult pred(outStrifile.position - length(outStrifile.content)) &
          stri;
    end if;
    outStrifile.position +:= length(stri);
  end func;


const proc: moveLeft (inout strifile: outStrifile, in string: stri) is func
  begin
    if outStrifile.position > length(stri) then
      outStrifile.position -:= length(stri);
    else
      outStrifile.position := 1;
    end if;
  end func;


(**
 *  Read a character from ''inStrifile''.
 *  @return the character read, or [[char#EOF|EOF]] at the end of the file.
 *)
const func char: getc (inout strifile: inStrifile) is func
  result
    var char: charRead is ' ';
  begin
    if inStrifile.position <= length(inStrifile.content) then
      charRead := inStrifile.content[inStrifile.position];
      incr(inStrifile.position);
    else
      charRead := EOF;
    end if;
  end func;


(**
 *  Read a [[string]] with maximum length from ''inStrifile''.
 *  @return the string read.
 *  @exception RANGE_ERROR The parameter ''maxLength'' is negative.
 *)
const func string: gets (inout strifile: inStrifile, in integer: maxLength) is func
  result
    var string: striRead is "";
  begin
    if maxLength < 0 then
      raise RANGE_ERROR;
    elsif maxLength <= succ(length(inStrifile.content) - inStrifile.position) then
      striRead := inStrifile.content[inStrifile.position len maxLength];
      inStrifile.position +:= maxLength;
    else
      striRead := inStrifile.content[inStrifile.position ..];
      inStrifile.position := succ(length(inStrifile.content));
    end if;
  end func;


(**
 *  Read a word from ''inStrifile''.
 *  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 inStrifile.bufferChar contains ' ',
 *  '\t', '\n' or [[char#EOF|EOF]].
 *  @return the word read.
 *)
const func string: getwd (inout strifile: inStrifile) is func
  result
    var string: stri is "";
  local
    const set of char: space_or_tab is {' ', '\t'};
    const set of char: space_tab_or_nl is {' ', '\t', '\n'};
    var integer: wordStartPos is 0;
    var integer: wordEndPos is 0;
  begin
    wordStartPos := inStrifile.position;
    while wordStartPos <= length(inStrifile.content) and
        inStrifile.content[wordStartPos] in space_or_tab do
      incr(wordStartPos);
    end while;
    if wordStartPos > length(inStrifile.content) then
      inStrifile.position := wordStartPos;
      inStrifile.bufferChar := EOF;
    else
      wordEndPos := succ(wordStartPos);
      while wordEndPos <= length(inStrifile.content) and
          inStrifile.content[wordEndPos] not in space_tab_or_nl do
        incr(wordEndPos);
      end while;
      if wordEndPos <= length(inStrifile.content) then
        stri := inStrifile.content[wordStartPos .. pred(wordEndPos)];
        inStrifile.position := succ(wordEndPos);
        inStrifile.bufferChar := inStrifile.content[wordEndPos];
      else
        stri := inStrifile.content[wordStartPos ..];
        inStrifile.position := succ(length(inStrifile.content));
        inStrifile.bufferChar := EOF;
      end if;
    end if;
  end func;


(**
 *  Read a line from ''inStrifile''.
 *  A strifile works as if all lines end with '\n'.
 *  The line ending character is not copied into the string. When
 *  the function is left inStrifile.bufferChar contains '\n' or
 *  [[char#EOF|EOF]].
 *  @return the line read.
 *)
const func string: getln (inout strifile: inStrifile) is func
  result
    var string: stri is "";
  local
    var integer: newlinePos is 0;
  begin
    newlinePos := pos(inStrifile.content, '\n', inStrifile.position);
    if newlinePos <> 0 then
      stri := inStrifile.content[inStrifile.position .. pred(newlinePos)];
      inStrifile.position := succ(newlinePos);
      inStrifile.bufferChar := '\n';
    else
      stri := inStrifile.content[inStrifile.position ..];
      inStrifile.position := succ(length(inStrifile.content));
      inStrifile.bufferChar := EOF;
    end if;
  end func;


(**
 *  Determine the end-of-file indicator.
 *  The end-of-file indicator is set if at least one request to read
 *  from the file failed.
 *  @return TRUE if the end-of-file indicator is set, FALSE otherwise.
 *)
const func boolean: eof (in strifile: inStrifile) is
  return inStrifile.position > length(inStrifile.content);


(**
 *  Determine if at least one character can be read successfully.
 *  This function allows a file to be handled like an iterator.
 *  @return FALSE if ''getc'' would return [[char#EOF|EOF]],
 *          TRUE otherwise.
 *)
const func boolean: hasNext (in strifile: inStrifile) is
  return inStrifile.position <= length(inStrifile.content);


(**
 *  Obtain the length of a ''aStrifile''.
 *  The file length is measured in characters.
 *  @return the length of a file.
 *)
const func integer: length (in strifile: aStrifile) is
  return length(aStrifile.content);


(**
 *  Truncate ''aStrifile'' to the given ''length''.
 *  If the file previously was larger than ''length'', the extra data is lost.
 *  If the file previously was shorter, it is extended, and the extended
 *  part is filled with null bytes ('\0;').
 *  The file length is measured in characters.
 *)
const proc: truncate (inout strifile: aStrifile, in integer: length) is func
  begin
    if length < 0 then
      raise RANGE_ERROR;
    elsif length(aStrifile.content) > length then
      aStrifile.content := aStrifile.content[.. length];
    elsif length(aStrifile.content) < length then
      aStrifile.content := aStrifile.content &
          "\0;" mult (length - length(aStrifile.content));
    end if;
  end func;


(**
 *  Set the current file position.
 *  The file position is measured in characters from the start of the file.
 *  The first character in the file has the position 1.
 *)
const proc: seek (inout strifile: aStrifile, in integer: position) is func
  begin
    aStrifile.position := position;
  end func;


(**
 *  Obtain the current file position of ''aStrifile''.
 *  The file position is measured in characters from the start of the file.
 *  The first character in the file has the position 1.
 *  @return the current file position.
 *)
const func integer: tell (in strifile: aStrifile) is
  return aStrifile.position;
