(*  VERSION 0019 *)
(* COPYRIGHT 1981,1982,1983 BY DIGITAL RESEARCH, INC. *)
(* ALL RIGHTS RESERVED *)


MODULE IOMODULE;

(* INTERFACE TO CP/M-86 FOR PASCAL/MT+86 *)

(*$I FIBDEF.LIB*)
const
  maxfcbs = 9;

TYPE
  FPTR = ^FIB;
  FCBLK = PACKED ARRAY [0..36] OF CHAR;
  SECTOR = PACKED ARRAY [0..127] OF CHAR;
  DUMMY = PACKED ARRAY[0..0] OF CHAR;
  PTR = ^DUMMY;

  FCBREC = RECORD
             ACTIVE : BOOLEAN;
             FCB    : FCBLK;
             BUFIDX : INTEGER;
             BUFFER : SECTOR;
             ENDFILE: BOOLEAN
           END;

  PTRIX = RECORD
            CASE BOOLEAN OF
                TRUE : (LO_VAL:INTEGER;
                        HI_VAL:INTEGER);
                FALSE: (P:PTR)
          END;


VAR
  @LFB : FPTR;
  RESULTI : INTEGER;
  @FCBS : ARRAY [0..maxfcbs] OF FCBREC;

(* ALLOWS 10 SIMULTANEOUSLY OPEN FILES *)
(* THE CONSOLE TAKES ONE FILE SLOT *)
(* FOR CON: AS INPUT AND CON: AS OUTPUT *)

EXTERNAL FUNCTION @BDOS86(FUNC:INTEGER; PARM:PTR):BYTE;
EXTERNAL FUNCTION @BDOS86A(FUNC:INTEGER; FIRST,SECOND:INTEGER):BYTE;
(* @BDOS86A WILL RESOLVE TO @BDOS86 AT LINK TIME BUT USE DIFFERENT PARMS *)
EXTERNAL PROCEDURE @BDOSX(FUNC:INTEGER; CH:CHAR);
EXTERNAL PROCEDURE @CHN(P:PTR);
EXTERNAL PROCEDURE @HLT;

(*$E-*)
FUNCTION GET_AN_FCB:INTEGER;
VAR
  I : INTEGER;
BEGIN
  I := 0;
  WHILE I <= maxfcbs DO
    BEGIN
      IF NOT(@FCBS[I].ACTIVE) THEN (* WE FOUND ONE! *)
        BEGIN
          GET_AN_FCB := I;
          @FCBS[I].ACTIVE := TRUE;
          EXIT
        END
      ELSE
        I := I + 1
    END;
  I := -1;
  WRITELN('FCB Table Exhausted!');
  @HLT;
END;

PROCEDURE FREE_AN_FCB(FCBNUM:INTEGER);
BEGIN
  @FCBS[FCBNUM].ACTIVE := FALSE
END;

PROCEDURE PUTSECTOR(I:INTEGER);
BEGIN
  RESULTI := @BDOS86(26,ADDR(@FCBS[I].BUFFER));
  RESULTI := @BDOS86(21,ADDR(@FCBS[I].FCB));
END;

FUNCTION  GETSECTOR(I:INTEGER):BOOLEAN;
BEGIN
  GETSECTOR := TRUE;    (* FALSE MEANS EOF *)
  RESULTI := @BDOS86(26,ADDR(@FCBS[I].BUFFER));
  RESULTI := @BDOS86(20,ADDR(@FCBS[I].FCB));
  IF RESULTI <> 0 THEN
    GETSECTOR := FALSE;
END;



FUNCTION @SPN(VAR F:FIB):BOOLEAN;
BEGIN
  @SPN := FALSE;
  IF F.FNAME = 'CON:' THEN
    BEGIN
      F.OPTION := FCONIO;
      @SPN := TRUE
    END
  ELSE
    IF F.FNAME = 'LST:' THEN
      BEGIN
        F.OPTION := FLSTOUT;
        @SPN := TRUE
      END
    ELSE
      IF (F.FNAME = 'KBD:') OR (F.FNAME = 'TRM:') THEN
        BEGIN
          F.OPTION := FTRMIO;
          @SPN := TRUE
        END
      ELSE
        IF (F.FNAME = 'RDR:') OR (F.FNAME = 'PUN:') THEN
          BEGIN
            F.OPTION := FAUXIO;
            @SPN := TRUE
          END
END;

FUNCTION @NOK(VAR S:STRING):BOOLEAN;
VAR
  I : INTEGER;
  ST: SET OF CHAR;
BEGIN
  @NOK := FALSE;
  ST := [' '..CHR($7E)];
  IF (LENGTH(S) > 14) OR (LENGTH(S) < 1) THEN
    EXIT;

  FOR I := 1 TO LENGTH(S) DO
    IF NOT(S[I] IN ST) THEN
      EXIT;
  @NOK := TRUE
END;


FUNCTION UPPERCASE(CH:CHAR):CHAR;
BEGIN
  IF (CH >= 'a') AND (CH <= 'z') THEN
    CH := CHR(CH & $DF);
  UPPERCASE := CH
END;



(*$E+*)
PROCEDURE @PARSE(VAR F:FCBLK;VAR S:STRING);
VAR
  DISK : CHAR;
  NAME : PACKED ARRAY [1..8] OF CHAR;
  EXT  : PACKED ARRAY [1..3] OF CHAR;
  I,J,MAX: INTEGER;

BEGIN
  (* PARSE CP/M FILE NAME *)

  WHILE (LENGTH(S) <> 0) AND (S[1] = ' ') DO
    DELETE(S,1,1);              (* REMOVE LEADING BLANKS *)

  IF LENGTH(S) <> 0 THEN
    BEGIN
      DISK := '@';      (* DEFAULT *)
      NAME := '        ';
      EXT  := '   ';

      IF S[2] = ':' THEN
        BEGIN
          I := 3;
          DISK := UPPERCASE(S[1])
        END
      ELSE
        I := 1;
      MAX := I + 8;
      J := 1;

      WHILE (NOT(S[I] IN ['.',':'])) AND (I < MAX) 
             AND (I <= LENGTH(S)) DO
        BEGIN
          NAME[J] := UPPERCASE(S[I]);
          J := J + 1;
          I := I + 1
        END; (* WHILE *)

      IF (S[I] = '.') AND (I <= LENGTH(S)) THEN
        BEGIN
          I := I + 1;
          J := 1;
          WHILE (J < 4) AND (I <= LENGTH(S)) DO
            BEGIN
              EXT[J] := UPPERCASE(S[I]);
              J := J + 1;
              I := I + 1
            END (* WHILE *)
        END; (* IF *)

      FILLCHAR(F,SIZEOF(FCBLK)-18,CHR(0));
      F[0] := CHR(ORD(DISK) - ORD('@'));
      MOVE(NAME,F[1],8);
      MOVE(EXT,F[9],3);
    END (* IF *)
  
END;



PROCEDURE @INI2;        (* INIT @FCBS *)
BEGIN
  FILLCHAR(@FCBS,SIZEOF(@FCBS),CHR(0))
END;


FUNCTION @OPEN(VAR F:FIB; MODE:INTEGER):INTEGER;

(* NOTE: THIS CODE IS DEPENDENT UPON THE FACT THAT THE FIRST FIELD *)
(* OF THE FIB DEFINITION IS FNAME! *)

VAR
  I : INTEGER;
BEGIN
  I := GET_AN_FCB;
  @OPEN := I;
  IF I <> -1 THEN
    BEGIN
      FILLCHAR(@FCBS[I].FCB,36,CHR(0));
      @PARSE(@FCBS[I].FCB,F.FNAME);
      IF NOT @NOK(F.FNAME) THEN
        BEGIN
          @OPEN := -1;
          RESULTI := 255;
          FREE_AN_FCB(I);       (* DONT NEED FCB IF BAD NAME *)
          EXIT
        END;

      IF @SPN(F) THEN
        BEGIN
          RESULTI := 0;
          @FCBS[I].FCB[0] := CHR($FF); {MARK SPECIAL FILE}
          FREE_AN_FCB(I);       
(* fix for micropro bug. using LST: as file causes fcb overflow. NOT TESTED*)
          {since on 1/16/82 we implemented i/o redirection }
          {special files now need an fcb allocated to them! }
          EXIT
        END;

      RESULTI := @BDOS86(15,ADDR(@FCBS[I].FCB));
      IF RESULTI = 255 THEN
        BEGIN
          @OPEN := -1;
          FREE_AN_FCB(I);       (* DONT NEED FCB IF NOT FOUND *)
        END
      ELSE
        BEGIN
          @FCBS[I].BUFIDX := SIZEOF(SECTOR);
          @FCBS[I].ENDFILE:= FALSE
        END
    END
  ELSE
    RESULTI := 255
END; (* @OPEN *)

FUNCTION @CREAT(VAR F:FIB; MODE:INTEGER):INTEGER;
VAR
  I : INTEGER;
BEGIN
  I := GET_AN_FCB;
  @CREAT := I;
  IF I <> -1 THEN
    BEGIN
      FILLCHAR(@FCBS[I].FCB,36,CHR(0));
      @PARSE(@FCBS[I].FCB,F.FNAME);
      IF NOT @NOK(F.FNAME) THEN
        BEGIN
          @CREAT := -1;
          RESULTI := 255;
          FREE_AN_FCB(I);       (* DONT NEED FCB IF BAD NAME *)
          EXIT
        END;

      IF @SPN(F) THEN
        BEGIN
          RESULTI := 0;
          @FCBS[I].FCB[0] := CHR($FF); {MARK SPECIAL FILE}
          {FREE_AN_FCB(I);       (* DONT NEED AN FCB ON SPECIAL FILES *)}
          {since on 1/16/82 we implemented i/o redirection }
          {special files now need an fcb allocated to them! }
          EXIT
        END;

      RESULTI := @BDOS86(19,ADDR(@FCBS[I].FCB)); (* DELETE ANY OLD ONES *)
      RESULTI := @BDOS86(22,ADDR(@FCBS[I].FCB)); (* AND CREATE A NEW ONE *)
      IF RESULTI = 255 THEN
        BEGIN
          @CREAT := -1;
          FREE_AN_FCB(I);       (* DONT NEED FCB IF ERROR *)
        END;
      @FCBS[I].BUFIDX := 0;
    END
  ELSE
    RESULTI := 255
END; (* @CREAT *)


FUNCTION @UNLINK(VAR F:FIB):INTEGER;
BEGIN
  IF F.SYSID = 0 THEN (* WE MUST ALLOCATE AN FCB FIRST *)
    F.SYSID := @OPEN(F,2);
  IF F.SYSID <> -1 THEN (* VALID FILE *)
    BEGIN
      IF F.OPTION <= FRANDOM THEN (* IT IS A DISK FILE *)
        RESULTI := @BDOS86(19,ADDR(@FCBS[F.SYSID].FCB));
      @UNLINK := 0;
      FREE_AN_FCB(F.SYSID)
    END;
END;

PROCEDURE @CLOSE(I:INTEGER; an_infile:boolean);
VAR
  J : INTEGER;
BEGIN
  if (not an_infile) and (@FCBS[I].FCB[0] <> CHR($FF)) then
    begin (* check to see if stuff to flush *)
      IF (@FCBS[I].BUFIDX <> 0) THEN
        BEGIN
          IF  (@FCBS[I].BUFIDX <> SIZEOF(SECTOR)) THEN
          (* STILL SPACE LEFT TO FILL WITH CTRL/Z'S *)
            WITH @FCBS[I] DO
              FILLCHAR(BUFFER[BUFIDX],SIZEOF(SECTOR)-BUFIDX,CHR($1A));
          PUTSECTOR(I)  (* ALWAYS OUTPUT BUFFER IF IDX <> 0 *)
        END;
      RESULTI := @BDOS86(16,ADDR(@FCBS[I].FCB))
    end;
  FREE_AN_FCB(I); (* WE ALWAYS DO THIS! *)
END;

PROCEDURE @SFB(P:FPTR);
BEGIN
  @LFB := P
END;

(*$E-*)
FUNCTION GETBYTE(I:INTEGER; VAR ENDFIL : BOOLEAN):BYTE;
BEGIN
  WITH @FCBS[I] DO
    BEGIN
      IF BUFIDX >= SIZEOF(SECTOR) THEN (* GOT TO GO READ SOME DATA *)
        BEGIN
          ENDFIL := NOT GETSECTOR(I);
          BUFIDX := 0
        END;
      GETBYTE := BUFFER[BUFIDX];
      BUFIDX := BUFIDX + 1
    END
END;

PROCEDURE PUTBYTE(B:BYTE; I:INTEGER);
BEGIN
  WITH @FCBS[I] DO
    BEGIN
      IF BUFIDX >= SIZEOF(SECTOR) THEN
        BEGIN
          PUTSECTOR(I);
          BUFIDX := 0
        END;
      BUFFER[BUFIDX] := B;
      BUFIDX := BUFIDX + 1
    END
END;

(*$E+*)
PROCEDURE @RNB;
VAR
  I : INTEGER;
  J : INTEGER;
  CH: CHAR;
  ENDFILE:BOOLEAN;
BEGIN
  RESULTI := 0;
  IF @LFB^.OPTION = FCONIO THEN (* READ CONSOLE NOT A DISK FILE *)
    BEGIN
      CH := @BDOS86(1,ADDR(I)); (* SECOND PARM IS A DUMMY *)
      IF CH = CHR(8) THEN
        BEGIN
          @BDOSX(2,' ');
          @BDOSX(2,CHR(8))
        END
      ELSE
        IF CH = CHR($0D) THEN
          @BDOSX(2,CHR($0A));
      @LFB^.FBUFFER[0] := CH;
      @LFB^.FEOF := (CH = CHR($1A));
      EXIT
    END;

  IF @LFB^.OPTION = FTRMIO THEN
    BEGIN
      repeat
        CH := @BDOS86A(6,$FFFF,$FFFF);
      until ch <> chr(0);
      @LFB^.FBUFFER[0] := CH;
      EXIT
    END;

  IF @LFB^.OPTION = FAUXIO THEN
    BEGIN
      CH := @BDOS86(3,ADDR(I));
      @LFB^.FBUFFER[0] := CH;
      EXIT
    END;


  (* ELSE NON-CONSOLE, READ USING GETBYTE *)

  I := @LFB^.SYSID;
  ENDFILE := @LFB^.FEOF;
  J := 1;
  WHILE (J <= @LFB^.BUFLEN) AND (NOT ENDFILE) DO
    BEGIN
      WITH @LFB^ DO
        FBUFFER[J-1] := GETBYTE(I,ENDFILE);
      J := J + 1
    END;
  @LFB^.FEOF := ENDFILE;
  @LFB^.IOSIZE := J-1;  (* THIS IS SO GNB CAN TELL THE DIFFERENCE *)
                        (* BETWEEN A PARTIALLY FULL BUFFER AND    *)
                        (* TRUE EOF                               *)
END;

PROCEDURE @WNB;
VAR
  I : INTEGER;
  J : INTEGER;
  CH: CHAR;
BEGIN
  RESULTI := 0;
  IF @LFB^.OPTION = FCONIO THEN (* WRITE TO THE CONSOLE *)
    BEGIN
      @BDOSX(2,@LFB^.FBUFFER[0]);
      EXIT
    END;

  IF @LFB^.OPTION = FTRMIO THEN (* USE FUNCTION 6 *)
    BEGIN
      @BDOSX(6,@LFB^.FBUFFER[0]);
      EXIT
    END;
    
  if @lfb^.option = flstout then (* use function 5 *)
    begin
      @bdosx(5,@lfb^.fbuffer[0]);
      exit
    end;

  if @lfb^.option = fauxio then (* use function 4 *)
    begin
      @bdosx(4,@lfb^.fbuffer[0]);
      exit
    end;

  (* ELSE NON-CONSOLE, WRITE USING PUTBYTE *)
  I := @LFB^.SYSID;
  FOR J := 1 TO @LFB^.BUFLEN DO
    WITH @LFB^ DO
      PUTBYTE(FBUFFER[J-1],I);
  @LFB^.BUFIDX := 0; (* SO CLOSE ON A WNB FILE WORKS PROPERLY *)
END;


PROCEDURE CHAIN(VAR F:FIB; SZ:INTEGER);
BEGIN
  @CHN(ADDR(@FCBS[F.SYSID].FCB))
END;


MODEND.
