(* InOut.mod provides a compatible PIM [234] InOut module.

Copyright (C) 2004-2025 Free Software Foundation, Inc.
Contributed by Gaius Mulley <gaius.mulley@southwales.ac.uk>.

This file is part of GNU Modula-2.

GNU Modula-2 is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 3, or (at your option)
any later version.

GNU Modula-2 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
General Public License for more details.

Under Section 7 of GPL version 3, you are granted additional
permissions described in the GCC Runtime Library Exception, version
3.1, as published by the Free Software Foundation.

You should have received a copy of the GNU General Public License and
a copy of the GCC Runtime Library Exception along with this program;
see the files COPYING3 and COPYING.RUNTIME respectively.  If not, see
<http://www.gnu.org/licenses/>.  *)

IMPLEMENTATION MODULE InOut ;

IMPORT FIO, SFIO, Terminal ;
FROM FIO IMPORT File, StdIn, StdOut ;

FROM DynamicStrings IMPORT InitString, Mark, KillString, ConCat,
                           RemoveWhitePrefix, char, ConCatChar, Length ;

FROM StringConvert IMPORT CardinalToString, stoc, stoi, ctos, itos ;
FROM ASCII IMPORT nul ;
FROM SYSTEM IMPORT ADR ;
FROM libc IMPORT read, write ;
FROM Termbase IMPORT AssignRead, AssignWrite ;
IMPORT Keyboard ;


CONST
   stdin = 0 ;
   stdout = 1 ;

TYPE
   CharSet = SET OF CHAR ;

VAR
   inFile, outFile: File ;
   inUsed, outUsed: BOOLEAN ;


(*
   OpenInput - reads a string from stdin as the filename for reading.
               If the filename ends with `.' then it appends the defext
               extension. The global variable Done is set if all
               was successful.
*)

PROCEDURE OpenInput (defext: ARRAY OF CHAR) ;
VAR
   s: String ;
BEGIN
   s := ReadS() ;
   IF char(s, -1)='.'
   THEN
      s := ConCat(s, Mark(InitString(defext)))
   END ;
   IF SFIO.Exists(s)
   THEN
      inFile := SFIO.OpenToRead(s) ;
      Done := FIO.IsNoError(inFile) ;
      inUsed := TRUE
   ELSE
      Done := FALSE ;
      inUsed := FALSE
   END ;
   s := KillString(s)
END OpenInput ;


(*
   CloseInput - closes an opened input file and returns input back to
                StdIn.
*)

PROCEDURE CloseInput ;
BEGIN
   IF inUsed
   THEN
      FIO.Close(inFile) ;
      inFile := StdIn ;
      inUsed := FALSE
   END
END CloseInput ;


(*
   OpenOutput - reads a string from stdin as the filename for writing.
                If the filename ends with `.' then it appends the defext
                extension. The global variable Done is set if all
                was successful.
*)

PROCEDURE OpenOutput (defext: ARRAY OF CHAR) ;
VAR
   s: String ;
BEGIN
   s := ReadS() ;
   IF char(s, -1)='.'
   THEN
      s := ConCat(s, Mark(InitString(defext)))
   END ;
   IF SFIO.Exists(s)
   THEN
      outFile := SFIO.OpenToWrite(s) ;
      Done := FIO.IsNoError(outFile) ;
      outUsed := TRUE
   ELSE
      Done := FALSE ;
      outUsed := FALSE
   END ;
   s := KillString(s)
END OpenOutput ;


(*
   CloseOutput - closes an opened output file and returns output back to
                 StdOut.
*)

PROCEDURE CloseOutput ;
BEGIN
   IF outUsed
   THEN
      FIO.Close(outFile) ;
      outFile := StdOut ;
      outUsed := FALSE
   END
END CloseOutput ;


(*
   LocalRead -
*)

PROCEDURE LocalRead (VAR ch: CHAR) ;
BEGIN
   ch := FIO.ReadChar(inFile) ;
   Done := FIO.IsNoError(inFile) AND (NOT FIO.EOF(inFile))
END LocalRead ;


(*
   LocalStatus - returns TRUE if more characters may be read.
*)

PROCEDURE LocalStatus () : BOOLEAN ;
BEGIN
   IF inUsed
   THEN
      RETURN Done
   ELSE
      RETURN Keyboard.KeyPressed ()
   END
END LocalStatus ;


(*
   ReadS - returns a string which has is a sequence of characters.
           Leading white space is ignored and string is terminated
           with a character <= ' '.
*)

PROCEDURE ReadS () : String ;
VAR
   s : String ;
   ch: CHAR ;
BEGIN
   s := InitString('') ;
   REPEAT
      Read(ch)
   UNTIL ch>' ' ;
   WHILE ch>' ' DO
      s := ConCatChar(s, ch) ;
      Read(ch)
   END ;
   (* successful *)
   RETURN( s )
END ReadS ;


(*
   Read - reads a single character from the current input file.
          Done is set to FALSE if end of file is reached or an
          error occurs.
*)

PROCEDURE Read (VAR ch: CHAR) ;
BEGIN
   Terminal.Read(ch)
END Read ;


(*
   ReadString - reads a sequence of characters. Leading white space
                is ignored and the string is terminated with a character
                <= ' '
*)

PROCEDURE ReadString (VAR s: ARRAY OF CHAR) ;
VAR
   h, i: CARDINAL ;
BEGIN
   (* skip leading spaces *)
   REPEAT
      Read(termCH)
   UNTIL termCH>' ' ;
   s[0] := termCH ;
   i := 1 ;
   h := HIGH(s) ;
   IF i<=h
   THEN
      REPEAT
         Read(termCH) ;
         IF termCH<=' '
         THEN
            s[i] := nul ;
            (* successful *)
            RETURN
         END ;
         s[i] := termCH ;
         INC(i)
      UNTIL i>h ;
   END ;
   Done := FALSE   (* out of space *)
END ReadString ;


(*
   WriteString - writes a string to the output file.
*)

PROCEDURE WriteString (s: ARRAY OF CHAR) ;
BEGIN
   FIO.WriteString(outFile, s) ;
   Done := FIO.IsNoError(outFile)
END WriteString ;


(*
   LocalWrite -
*)

PROCEDURE LocalWrite (ch: CHAR) ;
BEGIN
   FIO.WriteChar(outFile, ch) ;
   Done := FIO.IsNoError(outFile)
(*
   IF outUsed
   THEN
      FIO.WriteChar(outFile, ch) ;
      Done := FIO.IsNoError(outFile)
   ELSE
      Done := (write(stdout, ADR(ch), 1) = 1)
   END
*)
END LocalWrite ;


(*
   Write - writes out a single character, ch, to the current output file.
*)

PROCEDURE Write (ch: CHAR) ;
BEGIN
   Terminal.Write(ch)
END Write ;


(*
   WriteS - writes a String to the output device.
            It returns the string, s.
*)

PROCEDURE WriteS (s: String) : String ;
VAR
   i, h: CARDINAL ;
BEGIN
   i := 0 ;
   h := Length(s) ;
   WHILE i<h DO
      Write(char(s, i)) ;
      INC(i)
   END ;
   RETURN( s )
END WriteS ;


(*
   WriteLn - writes a newline to the output file.
*)

PROCEDURE WriteLn ;
BEGIN
   IF outUsed
   THEN
      FIO.WriteLine(outFile) ;
      Done := FIO.IsNoError(outFile)
   ELSE
      Terminal.WriteLn
   END
END WriteLn ;


(*
   ReadInt - reads a string and converts it into an INTEGER, x.
             Done is set if an INTEGER is read.
*)

PROCEDURE ReadInt (VAR x: INTEGER) ;
VAR
   s: String ;
BEGIN
   s := RemoveWhitePrefix(ReadS()) ;
   IF char(s, 0) IN CharSet{'-', '+', '0'..'9'}
   THEN
      x := stoi(s) ;
      Done := TRUE
   ELSE
      Done := FALSE
   END ;
   s := KillString(s)
END ReadInt ;


(*
   ReadInt - reads a string and converts it into an INTEGER, x.
             Done is set if an INTEGER is read.
*)

PROCEDURE ReadCard (VAR x: CARDINAL) ;
VAR
   s: String ;
BEGIN
   s := RemoveWhitePrefix(ReadS()) ;
   IF char(s, 0) IN CharSet{'+', '0'..'9'}
   THEN
      x := stoc(s) ;
      Done := TRUE
   ELSE
      Done := FALSE
   END ;
   s := KillString(s)
END ReadCard ;


(*
   WriteCard - writes the CARDINAL, x, to the output file. It ensures
               that the number occupies, n, characters. Leading spaces
               are added if required.
*)

PROCEDURE WriteCard (x, n: CARDINAL) ;
BEGIN
   IF KillString(SFIO.WriteS(outFile, ctos(x, n, ' ')))=NIL
   THEN
   END
END WriteCard ;


(*
   WriteInt - writes the INTEGER, x, to the output file. It ensures
              that the number occupies, n, characters. Leading spaces
              are added if required.
*)

PROCEDURE WriteInt (x: INTEGER; n: CARDINAL) ;
BEGIN
   IF KillString(SFIO.WriteS(outFile, itos(x, n, ' ', FALSE)))=NIL
   THEN
   END
END WriteInt ;


(*
   WriteOct - writes the CARDINAL, x, to the output file in octal.
              It ensures that the number occupies, n, characters.
              Leading spaces are added if required.
*)

PROCEDURE WriteOct (x, n: CARDINAL) ;
BEGIN
   IF KillString(SFIO.WriteS(outFile, CardinalToString(x, n, ' ', 8, FALSE)))=NIL
   THEN
   END
END WriteOct ;


(*
   WriteHex - writes the CARDINAL, x, to the output file in hexadecimal.
              It ensures that the number occupies, n, characters.
              Leading spaces are added if required.
*)

PROCEDURE WriteHex (x, n: CARDINAL) ;
BEGIN
   IF KillString(SFIO.WriteS(outFile, CardinalToString(x, n, ' ', 16, TRUE)))=NIL
   THEN
   END
END WriteHex ;


(*
   Init -
*)

PROCEDURE Init ;
BEGIN
   inFile := FIO.StdIn ;
   outFile := FIO.StdOut ;
   inUsed := FALSE ;
   outUsed := FALSE ;
   AssignRead(LocalRead, LocalStatus, Done) ;
   AssignWrite(LocalWrite, Done)
END Init ;


BEGIN
   Init
END InOut.