(* PushBackInput.mod provides a method for pushing back and consuming input.

Copyright (C) 2001-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 PushBackInput ;


FROM FIO IMPORT ReadChar, IsNoError, EOF, OpenToRead, WriteChar, StdErr ;
FROM DynamicStrings IMPORT string, Length, char ;
FROM ASCII IMPORT nul, cr, lf ;
FROM Debug IMPORT Halt ;
FROM StrLib IMPORT StrCopy, StrLen ;
FROM NumberIO IMPORT WriteCard ;
FROM StrIO IMPORT WriteString, WriteLn ;
FROM StdIO IMPORT Write, PushOutput, PopOutput ;
FROM libc IMPORT exit ;

IMPORT FIO ;


CONST
   MaxPushBackStack =  8192 ;
   MaxFileName      =  4096 ;

VAR
   FileName  : ARRAY [0..MaxFileName] OF CHAR ;
   CharStack : ARRAY [0..MaxPushBackStack] OF CHAR ;
   ExitStatus: CARDINAL ;
   Column,
   StackPtr,
   LineNo    : CARDINAL ;
   Debugging : BOOLEAN ;


(*
   GetCh - gets a character from either the push back stack or
           from file, f.
*)

PROCEDURE GetCh (f: File) : CHAR ;
VAR
   ch: CHAR ;
BEGIN
   IF StackPtr>0
   THEN
      DEC(StackPtr) ;
      IF Debugging
      THEN
         Write(CharStack[StackPtr])
      END ;
      RETURN( CharStack[StackPtr] )
   ELSE
      IF EOF(f) OR (NOT IsNoError(f))
      THEN
         ch := nul
      ELSE
         REPEAT
            ch := ReadChar(f)
         UNTIL (ch#cr) OR EOF(f) OR (NOT IsNoError(f)) ;
         IF ch=lf
         THEN
            Column := 0 ;
            INC(LineNo)
         ELSE
            INC(Column)
         END
      END ;
      IF Debugging
      THEN
         Write(ch)
      END ;
      RETURN( ch )
   END
END GetCh ;


(*
   PutStr - pushes a dynamic string onto the push back stack.
            The string, s, is not deallocated.
*)

PROCEDURE PutStr (s: String) ;
VAR
   i: CARDINAL ;
BEGIN
   i := Length (s) ;
   WHILE i > 0 DO
      DEC (i) ;
      IF PutCh (char (s, i)) # char (s, i)
      THEN
         Halt('assert failed', __FILE__, __FUNCTION__, __LINE__)
      END
   END
END PutStr ;


(*
   PutString - pushes a string onto the push back stack.
*)

PROCEDURE PutString (a: ARRAY OF CHAR) ;
VAR
   l: CARDINAL ;
BEGIN
   l := StrLen (a) ;
   WHILE l > 0 DO
      DEC (l) ;
      IF PutCh (a[l]) # a[l]
      THEN
         Halt ('assert failed', __FILE__, __FUNCTION__, __LINE__)
      END
   END
END PutString ;


(*
   PutCh - pushes a character onto the push back stack, it also
           returns the character which has been pushed.
*)

PROCEDURE PutCh (ch: CHAR) : CHAR ;
BEGIN
   IF StackPtr<MaxPushBackStack
   THEN
      CharStack[StackPtr] := ch ;
      INC(StackPtr)
   ELSE
      Halt('max push back stack exceeded, increase MaxPushBackStack',
           __FILE__, __FUNCTION__, __LINE__)
   END ;
   RETURN( ch )
END PutCh ;


(*
   Open - opens a file for reading.
*)

PROCEDURE Open (a: ARRAY OF CHAR) : File ;
BEGIN
   Init ;
   StrCopy(a, FileName) ;
   RETURN( OpenToRead(a) )
END Open ;


(*
   Close - closes the opened file.
*)

PROCEDURE Close (f: File) ;
BEGIN
   FIO.Close(f)
END Close ;


(*
   ErrChar - writes a char, ch, to stderr.
*)

PROCEDURE ErrChar (ch: CHAR) ;
BEGIN
   WriteChar(StdErr, ch)
END ErrChar ;


(*
   Error - emits an error message with the appropriate file, line combination.
*)

PROCEDURE Error (a: ARRAY OF CHAR) ;
BEGIN
   PushOutput(ErrChar) ;
   WriteString(FileName) ; Write(':') ; WriteCard(LineNo, 0) ; Write(':') ; WriteString(a) ; WriteLn ;
   PopOutput ;
   FIO.Close(StdErr) ;
   exit(1)
END Error ;


(*
   WarnError - emits an error message with the appropriate file, line combination.
               It does not terminate but when the program finishes an exit status of
               1 will be issued.
*)

PROCEDURE WarnError (a: ARRAY OF CHAR) ;
BEGIN
   PushOutput(ErrChar) ;
   WriteString(FileName) ; Write(':') ; WriteCard(LineNo, 0) ; Write(':') ; WriteString(a) ; WriteLn ;
   PopOutput ;
   ExitStatus := 1
END WarnError ;


(*
   WarnString - emits an error message with the appropriate file, line combination.
                It does not terminate but when the program finishes an exit status of
                1 will be issued.
*)

PROCEDURE WarnString (s: String) ;
VAR
   p : POINTER TO CHAR ;
BEGIN
   p := string(s) ;
   WriteString(FileName) ; Write(':') ; WriteCard(LineNo, 0) ; Write(':') ;
   REPEAT
      IF p#NIL
      THEN
         IF p^=lf
         THEN
            WriteLn ;
            WriteString(FileName) ; Write(':') ; WriteCard(LineNo, 0) ; Write(':')
         ELSE
            Write(p^)
         END ;
         INC(p)
      END ;
   UNTIL (p=NIL) OR (p^=nul) ;
   ExitStatus := 1
END WarnString ;


(*
   GetExitStatus - returns the exit status which will be 1 if any warnings were issued.
*)

PROCEDURE GetExitStatus () : CARDINAL ;
BEGIN
   RETURN( ExitStatus )
END GetExitStatus ;


(*
   SetDebug - sets the debug flag on or off.
*)

PROCEDURE SetDebug (d: BOOLEAN) ;
BEGIN
   Debugging := d
END SetDebug ;


(*
   GetColumnPosition - returns the column position of the current character.
*)

PROCEDURE GetColumnPosition () : CARDINAL ;
BEGIN
   IF StackPtr>Column
   THEN
      RETURN( 0 )
   ELSE
      RETURN( Column-StackPtr )
   END
END GetColumnPosition ;


(*
   GetCurrentLine - returns the current line number.
*)

PROCEDURE GetCurrentLine () : CARDINAL ;
BEGIN
   RETURN( LineNo )
END GetCurrentLine ;


(*
   Init - initialize global variables.
*)

PROCEDURE Init ;
BEGIN
   ExitStatus := 0 ;
   StackPtr   := 0 ;
   LineNo     := 1 ;
   Column     := 0
END Init ;


BEGIN
   SetDebug(FALSE) ;
   Init
END PushBackInput.