VTPIO   TITLE   "Z80 BCPL Run Time System  -  Section VTPIO"
        GET     "HEADER"
        GET     "SCBHDR"
        LAYOUT
;  This section contains all the routines to handle Virtual Terminal I/O.
;  It requires the section "BSPIO" to handle the raw byte stream interface.
        LAYOUT
        REF     FREEVEC

        DEF     V.FNDI
        DEF     V.FNDO
        LAYOUT
        RELOCATABLE

        ORG     0

VTPIO   DEFB    'BCPL'                  ;  Entry flag for BCPL module
        DEFW    VTPIOE-VTPIO            ;  Length of module in bytes
        LAYOUT
;  FINDINPUT
;  ---------
;
;    VTP "findinput" routine.  Load the address of the prototype SCB,
;    and join the common code.


V.FNDI  LD      DE,ISCB                 ;  Load address of SCB
        LD      BC,ISCBL                ;  Length of SCB
        JP      FIND                    ;  Join common code
        LAYOUT
;  FINDOUTPUT
;  ----------
;
;    VTP "findoutput" routine.  Load the address of the prototype SCB,
;    and join the common code


V.FNDO  LD      DE,OSCB                 ;  Load address of SCB
        LD      BC,OSCBL                ;  Length of SCB
        JP      FIND                    ;  Join common code
        LAYOUT
;  Prototype SCB layouts for input and output
;  ------------------------------------------


ISCB    DEFB    B.IN+B.BUF+B.INT        ;  Flags
        DEFB    0                       ;  POS field
        DEFB    0                       ;  END field
        DEFW    BUFR                    ;  Buffer READ routine
        DEFW    ERR                     ;  Buffer WRITE routine
        DEFW    ERR                     ;  Binary READ routine
        DEFW    ERR                     ;  Binary WRITE routine
        DEFW    CLOSE                   ;  CLOSE routine
        DEFW    0                       ;  Arg1
        DEFW    0                       ;  Arg2
ISCBL   EQU     $-ISCB                  ;  Length of SCB


OSCB    DEFB    B.OUT+B.BUF+B.INT       ;  Flags
        DEFB    0                       ;  POS field
        DEFB    S.SIZE                  ;  END field
        DEFW    ERR                     ;  Buffer READ routine
        DEFW    BUFW                    ;  Buffer WRITE routine
        DEFW    ERR                     ;  Binary READ routine
        DEFW    ERR                     ;  Binary WRITE routine
        DEFW    CLOSE                   ;  CLOSE routine
        DEFW    0                       ;  Arg1
        DEFW    0                       ;  Arg2
OSCBL   EQU     $-OSCB                  ;  Length of SCB
        LAYOUT
;  FIND
;  ----
;
;    Common code to execute a stream open.  We enter here with HL pointing
;    to the prototype SCB and BC containing its length.  IX points to the
;    store allocated for the SCB.


FIND    EX      DE,HL                   ;  Swap source and destination pointers
        LDIR                            ;  Copy values across

        PUSH    IX                      ;  Save pointer to SCB
        POP     HL                      ;  Set result from function
        RET                             ;  And return
        LAYOUT
;  BUFREAD
;  -------
;
;    Replenish the buffer of the scb pointed to by IX.  This means issuing
;    a line request, and waiting for the characters to arrive.


BUFR    PUSH    IX                      ;  Save pointer to SCB
        POP     HL                      ;  And store it in HL
        LD      DE,S.BUFF               ;  Offset of buffer
        ADD     HL,DE                   ;  Get pointer to buffer
        PUSH    HL                      ;  Save pointer to buffer

        LD      E,(IX+S.ARG1+0)         ;  Low byte of BSCB
        LD      D,(IX+S.ARG1+1)         ;  High byte of BSCB
        PUSH    DE                      ;  Save pointer to BSCB

;  Now, issue the line request to the terminal, and wait for characters to be
;  forthcoming.

        LD      HL,V.LNGTH+3            ;  Length of request
        CALL    BSPW                    ;  Write to the stream
        LD      HL,V.LRQST              ;  Line request code
        CALL    BSPW                    ;  Write to the stream
        LD      HL,S.SIZE               ;  Maximum length of line
        CALL    BSPW                    ;  Write to the stream
        LD      HL,#B0000               ;  Reflect, full interpretation
        CALL    BSPW                    ;  Write to the stream
        LD      HL,CH.PSH               ;  Character to push the data
        CALL    BSPW                    ;  Write to the stream

;  We are now in a state to start reading characters from the VTP stream.
;  Resets are handled at a lower level, but we must handle end of file here.

        LD      (IX+S.POS),0            ;  Set character count

BUFR0   CALL    BSPR                    ;  Read a character from byte stream
        LD      A,H                     ;  Look at the top byte
        OR      A                       ;  Is it zero ?
        JP      NZ,BUFR2                ;  No, so handle error character

;  If we drop through here, then this is a normal character from the byte stream.
;  Check the top bit, which will tell us if this is a data character, or
;  a VTP control code.

        BIT     7,L                     ;  Check the state of the bit
        JP      Z,BUFR3                 ;  Zero, so normal data character.

;  If we drop through here, then this is not a normal data character, and
;  so we should interpret it for what it is.  The length code is in "L".

        RES     7,L                     ;  Reset the top bit
        LD      (IX+S.END),L            ;  Save the length in the SCB

;  Look at the length, and if zero, we should not even wait round for the
;  next byte.

        LD      A,L                     ;  Length count
        OR      A                       ;  Is it zero ?
        JP      Z,BUFR0                 ;  Yes, so back to beginning again

;  Otherwise, there is at least one character to read, and so we should read
;  it and see what it asks us to do.

        CALL    BSPR                    ;  Read the next character
        LD      A,H                     ;  Check top byte of character
        OR      A                       ;  Is it zero ?
        JP      NZ,BUFR2                ;  No, so error return

;  If we come here, then this should be the VTP control code which we are
;  expecting.  The ones which we handle are:
;
;      EOIL         End of input line.  Next character is terminator
;      IESC         Input escape character.  Next character is data character.
;      TABMK        Tab mark.  Translate to "tab" character.

        LD      A,L                     ;  Load character into A

        CP      V.EOIL                  ;  End of input line ?
        JP      Z,BUFR6                 ;  Yes, so handle it
        CP      V.IESC                  ;  Escaped character ?
        JP      Z,BUFR7                 ;  Yes, so handle it
        CP      V.TABMK                 ;  Tab mark ?
        JP      Z,BUFR8                 ;  Yes, so handle it

;  If we drop through here, then we don't understand the character given to us,
;  but no matter, since we have the length of the control sequence, and can
;  hence can ignore the rest of the characters in the command.

BUFR9   LD      A,(IX+S.END)            ;  Load length of sequence
        DEC     A                       ;  Reached zero yet ?
        JP      Z,BUFR0                 ;  Yes, so return to main loop

        LD      (IX+S.END),A            ;  Save count again
        CALL    BSPR                    ;  Read character from byte stream

        LD      A,H                     ;  High byte of character
        OR      A                       ;  Is it zero ?
        JP      Z,BUFR9                 ;  Yes, so continue looping

;  If we drop through here, then we have received a special character which
;  we are not expecting.  Return an error.

        JP      BUFR2                   ;  Error return

;  If we come here, then we have received VTP "end of input line", and the
;  next character is the terminator.  Read the character, and interpret it.

BUFR6   CALL    BSPR                    ;  Read the next character
        LD      A,H                     ;  Top byte of character
        OR      A                       ;  Is it zero ?
        JP      NZ,BUFR2                ;  No, so handle error

        LD      A,L                     ;  Load character into A
        POP     HL                      ;  Restore BSCB
        POP     HL                      ;  Restore pointer to buffer

        CP      0                       ;  No reason code ?
        JP      Z,BUFR10                ;  Yes, so return
        CP      1                       ;  Requested number of characters ?
        JP      Z,BUFR10                ;  Yes, so return
        CP      2                       ;  Logical end of file ?
        JP      Z,BUFR11                ;  Yes, so set "EOF" flag

;  If we drop through here, then the character given should be added to the
;  buffer.

        LD      (HL),A                  ;  Store the character
        LD      A,(IX+S.POS)            ;  Load the character count
        INC     A                       ;  Increment character count
        RET                             ;  And return

;  If we come here, then we are handling a VTP escape character.  The next
;  character through is the data character.

BUFR7   CALL    BSPR                    ;  Read the next character
        LD      A,H                     ;  High byte of character
        OR      A                       ;  Is it zero ?
        JP      NZ,BUFR2                ;  No, so error return
        JP      BUFR3                   ;  Join common code

;  If we come here, then we have been given a VTP tab character.  Translate
;  this into a real tab character.

BUFR8   LD      HL,START                ;  Load real character code
        JP      BUFR3                   ;  Join common code

;  If we come here, then we have been given logical end of file.  Set the
;  "EOF" bit, and return the characters given so far.

BUFR11  SET     F.EOF,(IX+S.FLAG)       ;  Set the "end of file" flag

;  When we come here, then all has finished, and we should just load the
;  character count from the SCB, and return this as a result.

BUFR10  LD      A,(IX+S.POS)            ;  Load character count
        RET                             ;  And return

;  If we come here, then we have been given a normal data character, which
;  is currently in "L".  Store this character in the buffer, and go back
;  for more.

BUFR3   LD      A,(IX+S.POS)            ;  Load character count
        INC     A                       ;  And increment it
        LD      (IX+S.POS),A            ;  Store it again
        POP     DE                      ;  Restore BSCB

        EX      (SP),HL                 ;  Swap character and buffer pointer
        POP     BC                      ;  Restore character
        LD      (HL),C                  ;  Store the character
        INC     HL                      ;  Increment buffer pointer

        PUSH    HL                      ;  Save buffer pointer
        PUSH    DE                      ;  Save BSCB

        JP      BUFR0                   ;  Get the next character

;  If we come here, then we have received what amounts to "end of file" from
;  the other end.  We return a "zero" character count, passing the state
;  to the higher level.

BUFR2   POP     HL                      ;  Restore BSCB
        POP     HL                      ;  Restore buffer pointer

        XOR     A                       ;  Set return code
        RET                             ;  And return
        LAYOUT
;  BUFWRITE
;  --------
;
;    Flush the buffer given down the VTP stream.  This means reflecting each
;    character in turn, adding control codes where necessary.


BUFW    PUSH    IX                      ;  Save pointer to SCB
        POP     HL                      ;  And store it in HL
        LD      DE,S.BUFF               ;  Offset of buffer
        ADD     HL,DE                   ;  Get pointer to buffer
        PUSH    HL                      ;  Save pointer to buffer

        LD      E,(IX+S.ARG1+0)         ;  Low byte of BSCB
        LD      D,(IX+S.ARG1+1)         ;  High byte of BSCB
        PUSH    DE                      ;  Save pointer to BSCB

;  Now, pick up each character in turn from the buffer, and write it down the
;  VTP stream.  Characters *C, *E and *N are preceded by "end of output line"
;  directives.

BUFW0   OR      A                       ;  Character count reached zero ?
        JP      Z,BUFW1                 ;  Yes, so finish

        LD      (IX+S.POS),A            ;  Save character count

        POP     DE                      ;  Restore BSCB pointer
        POP     HL                      ;  Restore buffer pointer
        LD      A,(HL)                  ;  Load next character
        INC     HL                      ;  Increment buffer pointer
        PUSH    HL                      ;  Save buffer pointer
        PUSH    DE                      ;  Save BSCB pointer

        CP      STARC                   ;  Terminator ?
        JP      Z,BUFW2                 ;  Yes, so handle it
        CP      STARE                   ;  Terminator ?
        JP      Z,BUFW2                 ;  Yes, so handle it
        CP      STARN                   ;  Terminator ?
        JP      Z,BUFW2                 ;  Yes, so handle it

;  If we drop through here, then this is a bog standard character, which we
;  can just write straight down the stream.

        RES     7,A                     ;  Reset parity
        LD      L,A                     ;  Low byte of character
        LD      H,0                     ;  High byte of character
        CALL    BSPW                    ;  Write the character out
        JP      BUFW3                   ;  Get the next character

;  If we come here, then the character in our hand is one which must terminate
;  an output line.

BUFW2   LD      (IX+S.END),A            ;  Save character

        LD      HL,V.LNGTH+2            ;  VTP length byte
        CALL    BSPW                    ;  Write to the stream
        LD      HL,V.EOOL               ;  End of output line
        CALL    BSPW                    ;  Write to the stream

        LD      L,(IX+S.END)            ;  Low byte of character
        LD      H,0                     ;  High byte of character
        CALL    BSPW                    ;  Write the character out

;  When we come here, we have finished with this character, and so we must
;  decrement the counter, and go back for more.

BUFW3   LD      A,(IX+S.POS)            ;  Restore character count
        DEC     A                       ;  Decrement count
        JP      BUFW0                   ;  Loop until finished

;  If we come here, then we have finished with the buffer given to us, and so
;  should push the data down the byte stream, and return.

BUFW1   LD      HL,CH.PSH               ;  Character to push data
        CALL    BSPW                    ;  Push the byte stream

        POP     HL                      ;  Restore BSCB
        POP     HL                      ;  Restore buffer pointer
        RET                               ;  And return
        LAYOUT
;  BSPREAD
;  -------
;
;    Read the next character from the byte stream, and return the value in HL.
;    Byte stream resets are handled here, and whenever a reset happens, a
;    "deny reset" code is sent, and the "bufread" routine is reentered.


BSPR    POP     DE                      ;  Restore return address
        EX      (SP),IX                 ;  Swap VTP and BSP control blocks
        PUSH    DE                      ;  Save return address again

        LD      C,(IX+S.BINR+0)         ;  Low byte of BINREAD
        LD      B,(IX+S.BINR+1)         ;  High byte of BINREAD
        CALL    CALLBC                  ;  Call the routine

        POP     DE                      ;  Restore return address
        EX      (SP),IX                 ;  Swap VTP and BSP control blocks
        PUSH    DE                      ;  Save return address again

;  We should at this point check to see whether we have had a byte stream
;  reset.  If we have, then we should handle it properly.

        LD      DE,CH.RST               ;  Reset character
        EX      DE,HL                   ;  Swap registers
        OR      A                       ;  Clear carry flag
        SBC     HL,DE                   ;  Do we have a reset ?
        JP      Z,BSPR0                 ;  Yes, so handle it

;  If we drop through here, then we do not have a reset, and so we should
;  return the character back to the user.

        EX      DE,HL                   ;  Swap registers again
        RET                             ;  And return

;  If we come here, then this is a byte stream "reset" character, which we
;  should acknowledge.  After that, we should get rid of the stuff on the
;  stack, and reenter the "BUFREAD" routine.

BSPR0   POP     HL                      ;  Restore return address

        LD      HL,V.LNGTH+1            ;  Length code
        CALL    BSPW                    ;  Write to the stream
        LD      HL,V.DENRS              ;  Deny reset control code
        CALL    BSPW                    ;  Write to the stream
        LD      HL,CH.PSH               ;  Character to push data
        CALL    BSPW                    ;  Write to the stream

        SET     F.BRK,(IX+S.FLAG)       ;  Set the break flag

        POP     HL                      ;  Restore BSCB
        POP     HL                      ;  Restore buffer pointer
        JP      BUFR                    ;  Restart the whole operation
        LAYOUT
;  BSPWRITE
;  --------
;
;    Write the character given down the byte stream.  Don't worry here about
;    whether the byte stream has been reset or not, but just write the
;    character regardless.


BSPW    POP     DE                      ;  Restore return address
        EX      (SP),IX                 ;  Swap VTP and BSP control blocks
        PUSH    DE                      ;  Save return address again

        LD      C,(IX+S.BINW+0)         ;  Low byte of BINREAD
        LD      B,(IX+S.BINW+1)         ;  High byte of BINWRITE
        CALL    CALLBC                  ;  Call the routine

        POP     DE                      ;  Restore return address
        EX      (SP),IX                 ;  Swap VTP and BSP control blocks
        PUSH    DE                      ;  Save return address again

        RET                             ;  And return
        LAYOUT
;  CLOSE
;  -----
;
;    Close the current stream, which is pointed to by IX.  We should also
;    close the byte stream associated with the VTP stream, and free the
;    opposite half of the VTP stream.


CLOSE   LD      E,(IX+S.ARG1+0)         ;  Low byte of BSCB
        LD      D,(IX+S.ARG1+1)         ;  High byte of BSCB
        LD      L,(IX+S.ARG2+0)         ;  Low byte of other SCB
        LD      H,(IX+S.ARG2+1)         ;  High byte of other SCB

        PUSH    DE                      ;  Save pointer to BSCB
        CALL    FREEVEC                 ;  Free the other VTP stream

        EX      (SP),IX                 ;  Get the BSCB in IX

        LD      C,(IX+S.BINW+0)         ;  Low byte of BINWRITE
        LD      B,(IX+S.BINW+1)         ;  High byte of BINWRITE
        LD      HL,CH.CRQ               ;  Close request
        CALL    CALLBC                  ;  Attempt to close the stream

CLOSE0  LD      C,(IX+S.BINR+0)         ;  Low byte of BINREAD routine
        LD      B,(IX+S.BINR+1)         ;  High byte of BINREAD routine
        CALL    CALLBC                  ;  Read a character

        INC     HL                      ;  Increment result
        LD      A,H                     ;  Zero yet ?
        OR      L                       ;  i.e. have we been given endstreamch
        JP      NZ,CLOSE0               ;  No, so continue looping

        POP     IX                      ;  Restore SCB pointer
        RET                             ;  And return
        LAYOUT
;  ERROR
;  -----
;
;    Enter here if the user attempts to muck around with the routines in the
;    stream control block.


ERR     ERRTRAP                         ;  Enter the debugger
        RET                             ;  And return
        LAYOUT
;  CALLBC
;  ------
;
;    CALL the routine which BC points to.


CALLBC  PUSH    BC                      ;  Save address on the stack
        RET                             ;  And enter the routine
        LAYOUT
        ALIGN

        DEFW    0                       ;  End of global list
        DEFW    0                       ;  HRG
        LAYOUT
VTPIOE  END


