COMMENT *

			     CLUBware  (tm)

	  COMPRINT - Enhancement to the Basic Compiler Print statement
				(for routines compiled without /O option)

		   Copyright 1984 Rayhawk Automation N.W. Inc
				  P.O. Box 1427
				  Beaverton, Oregon   97075
									      *

SCRDSEG   SEGMENT

	  ASSUME  CS:SCRDSEG,DS:NOTHING,ES:NOTHING,SS:STACK

ORIG_INT10	  LABEL  WORD	       ; original int 10
	  DW	  0
	  DW	  0


;______________________________________________________________________________

;   Swap the code used by BASIC to print strings.  Swap must be made
;    after BASIC has been loaded and initialized its environment.  When the
;    screen is cleared INTSWAP intercepts the BASIC print statement.

ORIG_2841	  LABEL  WORD	       ; original subroutine 2841
	  DW	  2846h
BASIC_SEGMENT	  LABEL  WORD
	  DW	  0

CALLERS_SEG	  EQU  WORD PTR [BP+4]	; location of callers segment on stack


INTSWAP   PROC	  FAR

	  CMP	  AX,0600h	       ; clear screen request?
	  JE	  DO_SWAP
	  JMP	  DWORD PTR ORIG_INT10 ; pass call to BIOS

DO_SWAP:
	  PUSH	  BP		       ; address the stack
	  MOV	  BP,SP
	  PUSH	  DS
	  PUSH	  AX

	  MOV	  AX,CALLERS_SEG       ; address basic space
	  MOV	  DS,AX
	  MOV	  BASIC_SEGMENT,AX     ; save basic's code segment

	  CLI			       ; disable interrupts for a moment

;   have we already taken over this subroutine once before?

	  CMP	  WORD PTR DS:[2844h],SEG PRINTER
	  JE	  ALREADY_SAVED


;   replace the original code with a call to PRINTER to intercept prints

	  CMP	  WORD PTR DS:[2842h],OFFSET PRINTER  ; two copies of INTSWAP?
	  JE	  SECOND_COPY
	  CMP	  WORD PTR DS:[2841h],8B56h	      ; basic loaded yet?
	  JNE	  ALREADY_SAVED 		      ; jump if not

SECOND_COPY:
	  MOV	  BYTE PTR DS:[2841h],9Ah	      ; opcode is far call
	  MOV	  WORD PTR DS:[2842h],OFFSET PRINTER  ; target for call
	  MOV	  WORD PTR DS:[2844h],SEG    PRINTER

ALREADY_SAVED:

	  STI

	  POP	  AX
	  POP	  DS
	  POP	  BP
	  JMP	  DWORD PTR ORIG_INT10 ; pass clear screen to BIOS now



INTSWAP   ENDP

;______________________________________________________________________________

COMMENT * PRINTER prints a character string on the screen starting
		  at the current cursor position.  After the string is
		  written to the screen the cursor position is updated
		  to just after the string.

	  To use this routine, a clear screen must occur
		  to swap in the PRINTER routine for basic.


	  The COMPRINT module will take over the INT 10h and make
		  this module permanently resident.  The user from inside
		  his BASIC program will clear the screen with a CLS
		  which will perform the vector swap.

	  Algorithm:

		 check context for simple text to screen or possibly something
		   more involved that basic should handle

		  1) backup to start of string and store character count
		  2) load current page from DS:[0085]
		  3) load current position on page from DS:[0087]
		  4) load attribute for string from DS:[00B5]
		  5) move character count to CX for use in loop
		  6) load type of crt display from 0000:463
		  7) address the screen segment
		  8) move string to screen while synchronizing
		      with horizontal retrace
		  9) update cursor position on screen
		 10) update cursor position in database at DS:[0087]
		 11) leave registers in manner BASIC expects

	  On entry
		  DS:[SI] points to string to write
		  CX	  contains count of characters to write
		  DS:[0085] contains the page to which we write
		  DS:[00B5] contains the attribute for the text
		  DS:[0087] contains the current screen location and must
			       be updated after print is complete


									      *
;______________________________________________________________________________

;  Local data addressable through CS register


CHAR_COUNT	  LABEL  WORD
	  DW	  0


LINE_START	  LABEL  WORD
	  DW	  0		       ;  0
	  DW	  160		       ;  1
	  DW	  320		       ;  2
	  DW	  480		       ;  3
	  DW	  640		       ;  4
	  DW	  800		       ;  5
	  DW	  960		       ;  6
	  DW	  1120		       ;  7
	  DW	  1280		       ;  8
	  DW	  1440		       ;  9

	  DW	  1600		       ;  10
	  DW	  1760		       ;  11
	  DW	  1920		       ;  12
	  DW	  2080		       ;  13
	  DW	  2240		       ;  14
	  DW	  2400		       ;  15
	  DW	  2560		       ;  16
	  DW	  2720		       ;  17
	  DW	  2880		       ;  18
	  DW	  3040		       ;  19
	  DW	  3200		       ;  20
	  DW	  3360		       ;  21
	  DW	  3520		       ;  22
	  DW	  3680		       ;  23
	  DW	  3840		       ;  24

PAGE_START	  LABEL  WORD
	  DW	  0
	  DW	  4000
	  DW	  8000
	  DW	  12000

STARTING_PAGE	  LABEL  WORD
	  DW	  0

END_OF_PAGE	  LABEL  WORD
	  DW	  0

STARTING_LINE	  LABEL  WORD
	  DW	  0

RESIDUAL	  LABEL  WORD
	  DW	  0


LOCAL_FLAG	  LABEL  BYTE
		  DB   00000000b
JUST_BLANKS	  EQU  00000001b       ; basic wants just blanks displayed
ZERO_TAGGED	  EQU  00000010b       ; string length unknown, tagged by 0


;______________________________________________________________________________

COMMENT *

   New PRINT service routine for BASIC

     Calls to routine XXXX:2841 are rerouted to this piece of code
     These calls come from several places within the BASIC rutime library
     We service calls from three locations: 4A07 - call to display a string
					    4ECE - call to display blanks
					    612F - call to display number
     Calls from anywhere else are serviced by the original basic code.
     The three labels corresponding to the three types of calls we service
     are  STRING_IO
	  REPEAT_BLANKS
	  NUMBER_STRING
     The code labeled BASIC_IO passes the interrupt back to the basic code
     for service.
									      *


PRINTER   PROC	  FAR

	  PUSH	  BP
	  MOV	  BP,SP 	       ; address the stack
	  MOV	  LOCAL_FLAG,0	       ; clear the flag for string i/o


;    check context for simple text to screen or possibly something
;      more involved that basic should handle

	  CMP	  WORD PTR DS:[079Eh],0
	  JNE	  BASIC_IO
	  CMP	  BYTE PTR DS:[07CCh],50h  ; 80 bytes per line?
	  JNE	  BASIC_IO
;	  CMP	  BYTE PTR DS:[0056h],25   ; on bottom line of screen?
;	  JE	  BASIC_IO		   ; if so, let basic handle it
	  CMP	  WORD PTR [BP+6],4A07h  ; just regular i/o?
	  JE	  STRING_IO
	  CMP	  WORD PTR [BP+6],4ECEh  ; just repeated blanks?
	  JE	  REPEAT_BLANKS
	  CMP	  WORD PTR [BP+6],612Fh  ; number string?
	  JE	  NUMBER_STRING

;    allow original service routine to perform I/O

BASIC_IO:
	  POP	  BP
	  ADD	  SP,4		       ; clear our far call from stack
	  PUSH	  SI		       ; original first two statements
	  MOV	  SI,WORD PTR DS:[079Eh] ;   of the basic subroutine
	  JMP	  DWORD PTR ORIG_2841  ; pass control to original routine



; ----------


;    send a number string to screen
;		  string length is not known but end is tagged by a zero

NUMBER_STRING:

	  PUSH	  BX		       ; save registers used
	  PUSH	  DX
	  PUSH	  DI
	  PUSH	  ES


;	  ...	  1) store character count and mark the flag for type of i/o

	  DEC	  SI		       ; backup to start of string
	  MOV	  CHAR_COUNT,80        ; always less than 80 characters
	  OR	  LOCAL_FLAG,ZERO_TAGGED
	  MOV	  RESIDUAL,CX	       ; restore original cx contents when done
	  JMP	  SETUP_START


; ----------


;    repeated blanks sent to screen

REPEAT_BLANKS:

	  PUSH	  BX		       ; save registers used
	  PUSH	  DX
	  PUSH	  DI
	  PUSH	  ES


;	  ...	  1) store character count and mark the flag for type of i/o

	  MOV	  CHAR_COUNT,CX        ; save character count
	  OR	  LOCAL_FLAG,JUST_BLANKS
	  MOV	  RESIDUAL,1	       ; leave one in cx register when done
	  JMP	  SETUP_START


; ----------


;    write the string for basic

STRING_IO:

	  PUSH	  BX		       ; save registers used,
	  PUSH	  DX
	  PUSH	  DI
	  PUSH	  ES



;	  ...	  1) backup to start of string and store character count

	  DEC	  SI		       ; backup to start of string
	  MOV	  CHAR_COUNT,CX        ; save character count
	  MOV	  RESIDUAL,1	       ; leave one in cx register when done


;	  ...	  2) load current page from DS:[0085]

SETUP_START:
	  MOV	  BL,BYTE PTR DS:[0085h]    ; load current page
	  SUB	  BH,BH
	  SHL	  BX,1		       ; convert to a table offset
	  MOV	  DI,PAGE_START[BX]    ; load start of page
	  MOV	  STARTING_PAGE,DI     ; save the page
	  MOV	  END_OF_PAGE,DI       ; save ending page pointer
	  ADD	  END_OF_PAGE,4000



;	  ...	  3) load current position on page from DS:[0087]

	  MOV	  DX,WORD PTR DS:[0087h]    ; load current position
	  DEC	  DH		       ; basic counts from 1 instead of 0
	  DEC	  DL		       ; basic counts from 1 instead of 0
	  MOV	  BL,DL 	       ; load row number
	  SHL	  BX,1		       ; two bytes per table entry
	  ADD	  DI,LINE_START[BX]    ; add in start of line
	  MOV	  STARTING_LINE,DI     ; store this for later


	  MOV	  DL,DH 	       ; bring down column number
	  SUB	  DH,DH
	  ADD	  DI,DX 	       ; add in column position
	  ADD	  DI,DX 	       ; account for attribute bytes



;	  ...	  4) load attribute for string from DS:[00B5]

	  MOV	  BH,BYTE PTR DS:[00B5h]  ; load attribute for string



;	  ...	  5) move character count to CX for use in loop

	  MOV	  CX,CHAR_COUNT        ; load the character count



;	  ...	  6) load type of crt display from 0000:463

	  SUB	  AX,AX 	       ; address system memory
	  MOV	  ES,AX

	  MOV	  DX,WORD PTR ES:[463h]      ; load address of display adapter
	  ADD	  DX,6			     ; address crt status port


;	  ...	  7) address the screen segment

	  MOV	  AX,0B000h	       ; screen seg for monochrome card
	  CMP	  DX,03DAh	       ; is this a graphic card?
	  JNE	  MONOCHROME

	  MOV	  AX,0B800h	       ; load screen seg for graphic card

MONOCHROME:

	  MOV	  ES,AX 	       ; address the screen buffer



;	  ...	  8) move string to screen while synchronizing
;		      with horizontal retrace

	  TEST	  LOCAL_FLAG,JUST_BLANKS
	  JZ	  DISPLAY_LOOP

	  MOV	  BL,20h	       ; load a blank
BLANK_LOOP:
	  CALL	  DISPLAY_CHAR	       ; display a line of blanks for basic
	  LOOP	  BLANK_LOOP
	  JMP	  UPDATE_POSITION


DISPLAY_LOOP:
	  LODSB 		       ; load next character

	  CMP	  AL,20h	       ; special character?
	  JGE	  NOT_SPECIAL
	  CALL	  SPECIAL
	  JZ	  ANOTHER_CHAR	       ; if flag set, go for another character

NOT_SPECIAL:

	  MOV	  BL,AL

	  CLI
HSYNC_WAIT1:
	  IN	  AL,DX 	       ; check for horizontal retrace
	  TEST	  AL,1
	  JNZ	  HSYNC_WAIT1	       ; wait for retrace
HSYNC_WAIT2:
	  IN	  AL,DX 	       ; check for horizontal retrace
	  TEST	  AL,1
	  JZ	  HSYNC_WAIT2	       ; wait for retrace

	  MOV	  AX,BX
	  STOSW 		       ; store character and attribute

ANOTHER_CHAR:
	  STI

	  CMP	  DI,END_OF_PAGE
	  JL	  NOT_SCROLLED

	  MOV	  AL,0Dh	       ; force a carriage return
	  CALL	  SPECIAL

NOT_SCROLLED:

	  LOOP	  DISPLAY_LOOP	       ; repeat cx times



;	  ...	 9) update cursor position on screen


UPDATE_POSITION:
	  MOV	  AX,DI
	  SUB	  AX,STARTING_PAGE
	  SHR	  AX,1		       ; eliminate attribute bytes
	  SUB	  DX,DX
	  MOV	  BX,80 	       ; divide by bytes per line
	  DIV	  BX		       ;   quotient in AL (ROW)
				       ;   remainder in DL (COLUMN)
	  MOV	  DH,AL
	  MOV	  BH,BYTE PTR DS:[0085h]    ; load page number
	  MOV	  AH,2		       ; request new position
	  INT	  10h


;	  ...	  10) update cursor position in database at DS:[0087]

	  XCHG	  DH,DL 	       ; basic likes this reversed
	  INC	  DH		       ; basic counts from 1 instead of 0
	  INC	  DL		       ; basic counts from 1 instead of 0
	  MOV	  WORD PTR DS:[0087h],DX


;	  ...	  11) leave registers in manner BASIC expects

	  MOV	  CX,RESIDUAL	       ; let decrement instr take this to zero
				       ;  inside basic
	  POP	  ES
	  POP	  DI
	  POP	  DX

	  POP	  BX
	  POP	  BP


	  ADD	  SP,4		       ; throw away offset and code segment
				       ;  from our call

	  POP	  AX		       ; throw away near call on stack
	  PUSH	  BASIC_SEGMENT        ; and convert to a far call
	  PUSH	  AX
	  RET			       ; return to basic


PRINTER   ENDP

;______________________________________________________________________________

;  subroutine to handle special control characters

SPECIAL   PROC	  NEAR

; ----------

	  CMP	  AL,0Ah	       ; line feed?
	  JE	  NEW_LINE


; ----------

	  CMP	  AL,0Bh	       ; home?
	  JNE	  NOT_HOME
	  MOV	  DI,STARTING_PAGE     ; start over at top of screen
	  MOV	  STARTING_LINE,DI
	  SUB	  AL,AL
	  RET

NOT_HOME:

; ----------

	  CMP	  AL,0Ch	       ; clear screen?
	  JNE	  NOT_CLEAR
	  MOV	  DI,STARTING_PAGE
	  MOV	  STARTING_LINE,DI
	  MOV	  AL,0		       ; clear whole window
	  JMP	  SHORT SCROLL_SCREEN


NOT_CLEAR:

; ----------

	  CMP	  AL,0Dh	       ; carriage return?
	  JNE	  NOT_CR

NEW_LINE:
	  MOV	  DI,STARTING_LINE
	  ADD	  DI,160
	  JMP	  SHORT TEST_RIGHT

NOT_CR:

; ----------

	  CMP	  AL,1Ch	       ; move right?
	  JNE	  NOT_RIGHT
	  ADD	  DI,2
	  JMP	  SHORT TEST_RIGHT

NOT_RIGHT:

; ----------

	  CMP	  AL,1Dh	       ; move left?
	  JNE	  NOT_LEFT
	  SUB	  DI,2
	  JMP	  SHORT TEST_LEFT

NOT_LEFT:

; ----------

	  CMP	  AL,1Eh	       ; move up?
	  JNE	  NOT_UP

	  SUB	  DI,160
	  JMP	  SHORT TEST_LEFT

NOT_UP:

; ----------

	  CMP	  AL,1Fh	       ; move down?
	  JNE	  NOT_DOWN

	  ADD	  DI,160
	  JMP	  SHORT TEST_RIGHT

NOT_DOWN:
	  JMP	  SHORT TEST_FOR_TAB

; ----------

TEST_RIGHT:
	  CMP	  DI,END_OF_PAGE       ; are we past line 25?
	  JL	  VALID_RIGHT
	  MOV	  DI,STARTING_PAGE     ; back at start of last line
	  ADD	  DI,3840
	  MOV	  STARTING_LINE,DI

	  MOV	  AL,1		       ; scroll one line
	  JMP	  SHORT SCROLL_SCREEN

VALID_RIGHT:
	  SUB	  AL,AL
	  RET

; ----------

SCROLL_SCREEN:
	  PUSH	  CX
	  MOV	  CX,0		       ; start in upper left corner
	  PUSH	  DX
	  MOV	  DX,184Fh	       ; end in lower right
	  MOV	  AH,6
	  PUSHF 		       ; simulate an INT 10
	  CALL	  DWORD PTR ORIG_INT10
	  POP	  DX
	  POP	  CX
	  SUB	  AL,AL
	  RET

; ----------

TEST_LEFT:
	  CMP	  DI,STARTING_PAGE
	  JGE	  VALID_LEFT

	  MOV	  DI,STARTING_PAGE

VALID_LEFT:
	  SUB	  AL,AL
	  RET

; ----------

TEST_FOR_TAB:

	  CMP	  AL,09h	       ; tab?
	  JNE	  NOT_TAB

	  PUSH	  CX
	  PUSH	  DX
	  MOV	  AX,DI
	  SUB	  AX,STARTING_LINE
	  SHR	  AX,1		       ; discount attribute bytes
	  SUB	  DX,DX
	  MOV	  CX,8
	  DIV	  CX
	  MOV	  CX,8		       ; tab positions are every 8 charactes
	  SUB	  CX,DX 	       ; subtract off remainder
	  MOV	  BL,' '               ; write some blanks
	  POP	  DX
TAB_LOOP:
	  CALL	  DISPLAY_CHAR
	  LOOP	  TAB_LOOP

	  POP	  CX
	  SUB	  AL,AL
	  RET

NOT_TAB:

; ----------

	  CMP	  AL,08h	       ; backspace?
	  JNE	  NOT_BACKSPACE

	  CMP	  DI,STARTING_LINE
	  JE	  AT_START
	  SUB	  DI,2		       ; back up a space

AT_START:
	  MOV	  BL,' '               ; write a blank
	  CALL	  DISPLAY_CHAR

	  SUB	  DI,2		       ; back up a space
	  SUB	  AL,AL
	  RET

NOT_BACKSPACE:

; ----------

	  CMP	  AL,00h	       ; null?
	  JNE	  NOT_NULL

	  TEST	  LOCAL_FLAG,ZERO_TAGGED  ; does this mean end of string?
	  JZ	  NOT_NULL		  ;  branch if null meaningless
	  MOV	  CX,1		       ; terminate the display loop
	  DEC	  SI		       ; backup so basic can see null too
	  SUB	  AL,AL
	  RET

NOT_NULL:

; ----------

	  MOV	  AH,2
	  SUB	  AH,1		       ; set flag to display the char
	  RET

SPECIAL   ENDP

;______________________________________________________________________________

;  routine to display a character in BL - used only for special characters
;     attribute is in BH


DISPLAY_CHAR	  PROC	NEAR

	  CLI
HSYNC_WAIT3:
	  IN	  AL,DX 	       ; check for horizontal retrace
	  TEST	  AL,1
	  JNZ	  HSYNC_WAIT3	       ; wait for retrace
HSYNC_WAIT4:
	  IN	  AL,DX 	       ; check for horizontal retrace
	  TEST	  AL,1
	  JZ	  HSYNC_WAIT4	       ; wait for retrace

	  MOV	  AX,BX
	  STOSW 		       ; store character and attribute

	  STI
	  RET

DISPLAY_CHAR	  ENDP


;______________________________________________________________________________

LASTADDR  LABEL   BYTE


COPYRIGHT LABEL   BYTE
	  DB	  10,13
	  DB  '         CLUBware  (tm)',10,13,10,13
	  DB  'COMPRINT - Enhancement to the Basic Compiler Print statement'
	  DB	  10,13
	  DB  '                 (for routines compiled without /O option)'
	  DB	  10,13,10,13
	  DB  '         Copyright 1984 Rayhawk Automation N.W. Inc',10,13
	  DB  '                        P.O. Box 1427',10,13
	  DB  '                        Beaverton, Oregon   97075',10,13,'$'


;______________________________________________________________________________

COMPRINT  PROC	  FAR


	  PUSH	  DS		       ; Push addr of Program Segment Prefix
	  SUB	  AX,AX 	       ; Zero AX
	  PUSH	  AX		       ; Push zero onto stack
;					 (offset of INT 20 within PSP)


;		      | - - - - - - - - - - - - - - - - - - - - - - - - -|
;		      | 						 |
;		      | 	   take over the INT 10h		 |
;		      | 	   interrupt if not already done	 |
;		      | 						 |
;		      | - - - - - - - - - - - - - - - - - - - - - - - - -|

	  MOV	  DS,AX 	       ; address low memory

;	  CMP	  WORD PTR DS:[40h],OFFSET INTSWAP
;	  JE	  ALREADY_DONE

	  MOV	  AX,WORD PTR DS:[40h] ; save original int10
	  MOV	  ORIG_INT10,AX
	  MOV	  AX,WORD PTR DS:[42h]
	  MOV	  ORIG_INT10+2,AX

	  MOV	  AX,SEG COMPRINT
	  MOV	  DS,AX
	  MOV	  DX,OFFSET INTSWAP    ; Load offset of interrupt service mod
	  MOV	  AX,2510h	       ; Prepare for DOS service call type 25
;					 to establish service for INT 10
	  INT	  21h		       ; Ask DOS to establish service

;		      | - - - - - - - - - - - - - - - - - - - - - - - - -|
;		      | 						 |
;		      | 	   issue copyright message		 |
;		      | 						 |
;		      | - - - - - - - - - - - - - - - - - - - - - - - - -|

	  MOV	  DX,OFFSET COPYRIGHT
	  MOV	  AH,9
	  INT	  21h


;		      | - - - - - - - - - - - - - - - - - - - - - - - - -|
;		      | 						 |
;		      | 	   modify INT 20 into INT 27 in the	 |
;		      | 	   program segment prefix		 |
;		      | 						 |
;		      | - - - - - - - - - - - - - - - - - - - - - - - - -|

	  MOV	  BYTE PTR ES:[01],27h ; Change INT 20h to INT 27h

;		      | - - - - - - - - - - - - - - - - - - - - - - - - -|
;		      | 						 |
;		      | 	6) load address of ending tag into DX	 |
;		      | 						 |
;		      | - - - - - - - - - - - - - - - - - - - - - - - - -|

	  MOV	  AX,SEG LASTADDR
	  SUB	  AX,SEG COMPRINT
	  MOV	  CL,4		       ; prepare for 4 bit shift
	  SHL	  AX,CL 	       ; shift up (convert from seg to abs)
	  ADD	  AX,OFFSET LASTADDR   ; add address of bottom location
	  ADD	  AX,0103h	       ; Pad offset because DOS measures
;					  offset relative to Program
;					  Segment Prefix
	  MOV	  DX,AX 	       ; leave where DOS will find it

;		      | - - - - - - - - - - - - - - - - - - - - - - - - -|
;		      | 						 |
;		      | 	7) use RET FAR to return to DOS and	 |
;		      | 	   leave service routine resident	 |
;		      | 						 |
;		      | - - - - - - - - - - - - - - - - - - - - - - - - -|

ALREADY_DONE:

	  RET

COMPRINT  ENDP

SCRDSEG   ENDS

;______________________________________________________________________________

STACK	  SEGMENT PARA STACK 'STACK'
	  DB	  24 DUP('STACK***')
TOPSTACK  DB	  0
STACK	  ENDS

	  END	  COMPRINT
