/* CLIBIO.C : includes environment dependent routines (e.g, argument list
/*	      preparation, I/O).  This code is not ROMable and must
/*	      be replaced if a ROMable object module is to be generated.
/* IMPORTANT: CLIBIO must be loaded last in the object module.
*/

/* C/80 3.0 runtime library. (c) 1983 Walter Bilofsky.	All rights
   reserved.  This library in C source and assembler source form may
   not be copied or transmitted to other than the original purchaser
   of C/80.  Executable object modules including code produced by
   C/80 and by this library may be distributed without restriction;
   acknowledgement that C/80 is used would be appreciated. */

#include "config"		/* config just defines MAC (for M80 code
				   generation) or not (for AS code), and
				   CP/M, or not (for HDOS) */

/* NFILES = number of files open at one time. */
#define NFILES	6
#define NULL	0

#ifdef CPM		/* CP/M definitions */

#define EXIT 0
#define SCIN 1
#define SCOUT 2
#define PRINT 9
#define READC 10
#define CONSTAT 11
#define OPENR 15
#define CLOSE 16
#define DELETE 19
#define READ 20
#define WRITE 21
#define MAKE 22
#define SETDMA 26
/* Following locations are relative to BOOT; change for different ORG CP/M */
#define BDOS 5		/* System entry point */
#define MAXMEM 6	/* Maximum memory address */
#define ARGLIN 128	/* Argument Line */
#define DEFFCB 92	/* Default FCB location */
#define PROGFWA 256	/* FWA of program */

#define EOL 13		/* End of line = CR, */
#define LF 10		/* LF. */
#define EOF 26		/* ASCII End of file */
#define FCBLEN 36	/* Length of an FCB */
#define PUNIT 32	/* Lowest physical I/O channel */

#else			/* HDOS definitions */

#define EXIT	0
#define SCIN	1
#define SCOUT	2
#define READ	4
#define WRITE	5
#define CLRCO	7
#define CTLC	33
#define OPENR	34
#define CLOSE	38
#define SETTOP	42
#define NAME	44
#define ERR	47
#define SSYSM	20320Q
#define SOMAX	20324Q
#define TICCNT	20033Q
#define STK	21200Q
#define PROGFWA 21200Q
#endif

/* Entry for all C programs.  Initialize I/O channel array.
/* Get all possible memory and put stack up at the top. */

#asm
; CLIBRARY.ASM 3.0 (7/18/83) - (c) 1982, 1983 Walter Bilofsky
; Multiply and divide routines (c)1981 UltiMeth Corp. Permission is gran-
; ted to reproduce them without charge, provided this notice is included.

#ifndef MAC
	ORG	PROGFWA
#else
	EXT	main,c.ugt,c.uge,c.sxt,s.1,h.,q.
	ENTRY	exit,fin,fout,.PC2,CP_M,$END
#ifdef CPM
x?fcb	EQU	x_fcb
NAM@U	EQU	NAM.U
@PC2	EQU	.PC2
	ENTRY	Cmode,CtlCk,x_fcb,CtlB,x?fcb,NAM@U,@PC2
#endif
#endif

#endasm

C_lib () {	/* this just to cause generation of code in CODE section,
		/* although it has to be RAM as we will overlay this area
		/* with arguments */
#asm

#ifdef CPM
CP_M	EQU	1
$AS	EQU	$+2		/* CP/M- use this code area for argv arg list */
$AG	EQU	$

$INIT:	DW	0		/* No-op; initializes $AG */
	LHLD	MAXMEM		/* Max mem address */
	DCX	H
#else
CP_M	EQU	0
$AS	EQU	STK-200 	/* HDOS - use stack area for temps */
$AT	EQU	$AS-2
$AG	EQU	$AS-4
$INIT:	LXI	H,exic	
	MVI	A,3
	DB	255,CTLC
	LXI	H,1
	SHLD	$AG
	DCX	H		/* Zero clock for profiler */
	SHLD	TICCNT
	DAD	SP		/* Save stack ptr to arg line */
	SHLD	$AT
	LHLD	SSYSM	/* Get memory and set up stack. 40320=S.SYSM. */
	LXI	H,-1
	DB	255,SETTOP
	XCHG			/* To use system overlay for stack, */
	LHLD	SOMAX		/* comment out from here through CALL s.1 */
	LXI	B,10
	DAD	B
	CALL	s.1
	PUSH	H
	DB	255,SETTOP
	POP	H
#endif
	DCR	H		/* At this point, HL contains max usable */
	SHLD	IObuf+6 	/* memory address.  Allocate 3 buffers; */
	DCR	H		/* use likely HDOS overlay area at top memory */
	SHLD	IObuf+4
	DCR	H
	SHLD	IObuf+2
#ifdef CPM
	LXI	B,-FCBLEN	/* Allocate 3 FCBs */
	DAD	B
	SHLD	IOfcb+4
	DAD	B
	SHLD	IOfcb+2
	DAD	B
	SHLD	IOfcb
	LXI	B,-135		/* and console buffer */
	DAD	B
	MVI	M,0
	SHLD	Cbuf
	DCX	H
	MVI	M,132
#endif
	DCX	H
	SPHL		/* Set up stack */
#ifdef CPM			/* Parse command line; build args from $AS */
	LXI	H,ARGLIN	/* Get address of arg line */
	MOV	E,M		/* And char count */
	MVI	M,' '		/* Add on space we might or might not take */
	MVI	D,0
	DAD	D		/* Get end of string */
	INR	E		/* Fix char count */
	LXI	B,0		/* Push 2 0 bytes to start */
$A8:	PUSH	B
	MOV	B,M		/* Move string onto stack 2 bytes at a time */
	DCX	H		/* (necessary because CP/M 1.4 clobbers 80H) */
	MOV	C,M
	DCX	H
	DCR	E
	DCR	E
	JP	$A8
	LXI	H,202AH 	/* Push first arg, which is always * */
	PUSH	H
	LXI	H,$AS		/* Push fwa of arg stack */
	PUSH	H
	LXI	H,2		/* Leave arg line address in HL */
	DAD	SP
#else
	LXI	H,STK-120	/* HDOS - first arg is file name. */
	SHLD	$AS	/* Get from .NAME scall. */
	LXI	D,$AS+2
	PUSH	D
	MVI	A,-1
	DB	255,NAME
	LHLD	$AT	/* Now to arg list.  Address of arg string */
#endif				/* line in HL, arg array atop stack. */
$A2:	DS	0		/* Scan next argument. */
#ifndef CPM
	LXI	D,STK		/* HDOS - check for end of stack area */
	MOV	A,E
	CMP	L
	JNZ	$A7
	MOV	A,D
	CMP	H
	JZ	$A6		/* (CP/M always terminates on 0) */
#endif
$A7:	MOV	A,M		/* Skip over blanks between args. */
	INX	H		
	ORA	A		
	JZ	$A6		/* Terminate on 0 byte */
	CPI	' '
	JZ	$A7
	MOV	C,A		/* Save first character of arg in C */
	CPI	'"'		/* If it's a quote character, leave it */
	JZ	$A3		/*  (so C contains terminator char) */
	CPI	047Q
	JZ	$A3
	MVI	C,' '		/* Otherwise terminate on blank */
	DCX	H		/*  and include first char in arg */
$A3:	POP	D		/* Store address of arg in arg list */
	MOV	A,L
	STAX	D
	INX	D
	MOV	A,H
	STAX	D
	INX	D
	PUSH	D
	PUSH	H		/* Save address of arg on stack */
	DCX	H
$A9:	INX	H		/* Skip to end of argument */
	MOV	A,M
	ORA	A
	JZ	$A5
	CMP	C
	JNZ	$A9
	MVI	M,0		/* Terminate arg with 0 byte */
	INX	H
$A5:	XTHL		/* Check for > or < arg for I/O redirection */
	MOV	A,M
	INX	H
	CPI	'<'
	JZ	$B1
	CPI	'>'
	JZ	$B2
	LXI	H,$AG		/* Bump arg count */
	INR	M
	POP	H		/* Restore command line pointer */
	JMP	$A2		/* Go do next arg */
$A6:	POP	H		/* Done with command line.  Put -1 at end */
	MVI	M,-1		/* of array, push argv and argc. */
	INX	H
	MVI	M,-1
	LHLD	$AG
	PUSH	H
	LXI	H,$AS
	PUSH	H
	CALL	main
exit:	LHLD	fout
	MOV	A,H
	ORA	L
	JZ	$B4
	PUSH	H
	CALL	fclose
#ifdef CPM
$B4:	JMP	-5+BDOS
exic	EQU	exit
#else
$B4:	MVI	A,0
	DB	255,EXIT
exic:	DB	255,CLRCO
	JMP	exit
#endif

/* I/O redirection: open a file */
$B1:	PUSH	H
	LXI	H,$BR
	PUSH	H
	CALL	fopen
	SHLD	fin
	JMP	$B3
$BR:	DB	'r',0
fin:	DW	0
$B2:	PUSH	H
	LXI	H,$BW
	PUSH	H
	CALL	fopen
	SHLD	fout
$B3:	POP	B
	POP	B
	JC	$B0

/* Restore command line pointer; discard the > or < arg */

	POP	H
	POP	D
	DCX	D
	DCX	D
	PUSH	D
	JMP	$A2
#ifdef CPM
$B0:	LXI	D,$BMS
	MVI	C,PRINT
	CALL	BDOS
	MVI	C,EXIT
	CALL	BDOS
$BMS:	DB	'Can''t open > or < file.$'
CtlB	EQU	$+1	/* Patch here for ctl-c action */
	JMP	exic	/* Patch here for ctl-c action */
#else
$B0:	DB	255,ERR,255,CLRCO,255,EXIT
#endif
$BW:	DB	'w',0
fout:	DW	0
#endasm
}

/* Allocate memory; return -1 if none available (with 500 byte threshold) */

/* WARNING : upon compiler execution, $LM MUST point to the very end
	     of the compiler. Because the last section loaded is the
	     DATA section, $LM must be defined in there.
*/
sbrk (n) int n; {
#asm
	POP	D
	POP	B
	PUSH	B
	PUSH	D
	LHLD	$LM
	PUSH	H
	PUSH	H
	POP	D
	DAD	B
	PUSH	H
	PUSH	H
	CALL	c.ugt
	POP	D
	JNZ	al.1
	LXI	H,-500
	DAD	SP
	CALL	c.uge
al.1:	POP	D
	POP	B
	LXI	H,-1
	RNZ
	XCHG
	SHLD	$LM
	PUSH	B
	POP	H
	RET
$LM:	DW	$END
#endasm
}

/* Get a character from the console */
getchar() {
/*	return getc(fin);	*/
#asm
	LHLD	fin
	PUSH	H
	CALL	getc
	POP	B
	RET
#ifdef CPM
Ccnt:	DB	0
Cbuf:	DS	2
Cmode:	DB	1		/* Set to 0 for raw console input */
$RLIN:	LDA	Ccnt	/* Check if console buffer needs more chars */
	INR	A		/* Returns with chars in buffer, */
	LHLD	Cbuf	/* new count in A and Cbuf in HL */
	CMP	M		/* Past last char? */
	RC		/* No - return */
	RZ
	XCHG			/* Yes - read another line */
	MVI	C,READC
	DCX	D
	CALL	BDOS
	XRA	A
	STA	Ccnt
	MVI	E,LF
	CALL	$RADD		/* Add Line Feed */
	JMP	$RLIN
CtlCk:	MVI	C,CONSTAT	/* Check for console input (maybe ctrl-c) */
	CALL	BDOS
	ORA	A
	RZ
	MVI	C,SCIN
	CALL	BDOS		/* Char waiting - read it */
	CPI	3		/* Check ctrl-C */
	JZ	0		/* and reboot if there */
	CPI	2		/* Check ctrl-B */
	JZ	CtlB-1
	CPI	EOL
	JNZ	$CC
	MVI	A,LF
$CC:	MOV	E,A
$RADD:	LHLD	Cbuf	/* Add character fm E to console line buffer */
	MOV	A,M
	DCX	H
	CMP	M
	RNC			/* Return if no room */
	INX	H
	INR	M		/* Inc count */
	MOV	C,M
	MVI	B,0
	DAD	B
	MOV	M,E		/* Store char */
	MVI	A,LF
	CMP	E
	MVI	C,SCOUT
	CZ	BDOS		/* If CR, echo line feed */
	RET		/* CHARACTER INPUT ROUTINE */
$B8Z:	CALL	CtlB-1		/* Enter here to process ^B and reread */
$B8:	MVI	A,PUNIT 	/* Enter here to read from console */
$GCH:	LXI	H,IOpread	/* Enter here for CHAR IN; A = unit */
	CALL	$PU
	RC		/* Return if error */
	PUSH	B		/* Save sys fn */
	LDA	Cmode	/* Check if line mode & console input(both 1)*/
	CMP	C
	JNZ	$GC1		/* If so, */
	CALL	$RLIN		/* read a char from console in line mode */
	STA	Ccnt
	MOV	E,A
	MVI	D,0
	DAD	D
	MOV	A,M
	JMP	$GC2
$GC1:	CALL	BDOS		/* Otherwise, get char the regular way. */
$GC2:	POP	B
	CPI	EOL		/* If char is CR */
	JNZ	$B8P
	DCR	C		/* If fn is SCIN = 1 */
	JNZ	$B8M
	MVI	E,10		/* echo LF */
	MVI	C,SCOUT
	CALL	BDOS
$B8M:	MVI	A,10		/* Return LF. */
	JMP	c.sxt
$B8P:	DCR	C		/* If fn is SCIN = 1, */
	JNZ	$B8G
	CPI	2		/* Check for ctl-b */
	JZ	$B8Z
$B8G:	CPI	EOF		/* Ctl-z? */
#else
$B8:	DB	255,SCIN
	JC	$B8	
	CPI	4		/* ctl-d? */
#endif
	JZ	$B9
	JMP	c.sxt

/* Check for ^d = EOF; clear buffer because line count messes up */

#ifdef CPM
$B9:	DS 0
#else
$B9:	DB	255,CLRCO
#endif
$RM:	LXI	H,-1
#endasm
}

/* Write a character to the console */
putchar(c) char c; {
  /*	return putc(c,fout);  */
#asm
	POP	B
	POP	D
	PUSH	D
	PUSH	B
	LHLD	fout
	PUSH	D
	PUSH	H
	CALL	putc
	POP	B
	POP	B
	RET
#ifdef CPM
$B7:	XCHG
	MVI	A,PUNIT
$PCH:	PUSH	H		/* Char out; A = unit, HL = char */
	LXI	H,IOpwrit
	CALL	$PU
	POP	D		/* Restore char */
	RC		/* Return if error */
	PUSH	D
	XCHG
	MOV	A,L
	CPI	LF		/* If outputting LF */
	JNZ	$B7A
	MVI	E,EOL		/* First put CR */
	PUSH	B		/* Save scall number */
	CALL	BDOS
	POP	B
	MVI	A,LF		/* Get LF into A */
$B7A:	MOV	E,A
	CALL	BDOS
#else
$B7:	PUSH	D
	MOV	A,E
	DB	255,SCOUT
#endif
	POP	H
	RET

#ifdef CPM
	/* Check for physical unit - return carry if no good */
	/* Call with IOpread or IOpwrit in HL, log unit in A */
	/* Return sys call in C, HL points to it in table. */
IOdev:	DB	'con:rdr:pun:lst:'
	DB	0

$PU:	SUI	PUNIT		/* Check channel in bounds */
	JC	$PU1
	CPI	4
	JP	$PU1
	MOV	E,A
	MVI	D,0
	DAD	D
	MOV	A,M
	MOV	C,A		/* Get call number */
	ORA	A
	RNZ		/* Check can do operation */
$PU1:	STC			/* error ret */
	JMP	$RM
#endif
#endasm
}

/* io storage */
int  IObuf[7] {0,0,0,0,0,0,0};	/* Start of buffer for file */
int  IOsect[7] {0,0,0,0,0,0,0}; /* Current sector, for seek */
char IOrw[7] {0,0,0,0,0,0,0};	/* # sectors read on last op on chan */
char *IOtmp;			/* Holds buffer start within routines */
char IOch[7] {-1,-1,-1,-1,-1,-1,-1}; /* Channel for open files */
int  IOind[7];			/* Index of next character in current rec */
char IOmode[7]; 		/* Mode in which opened */
char IObin[7];			/* Binary? */

#ifdef CPM
int  IOfcb[7] {0,0,0,0,0,0,0};	/* FCB addresses for files.  In CP/M, */
				/*  channel is index into this array. */
char IOnch[7] {0,0,0,0,0,0,0};	/* Nr chars in buffer: 0 or 128 */
char IOpchan[] {32,33,34,35};	  /* Logical channels */
char IOpread[] { 1, 3, 0, 0};	  /* System call for read */
char IOpwrit[] { 2, 0, 4, 5};	  /* System call for write */
char IOpeof[]  { 0,EOF,EOF,12};  /* EOF char: FF for LST */
#endif


/* Open file sname with indicated mode - "w", "r" or "u". */
fopen(sname,mode)
char *sname,*mode;
{
/*	char i;
	char scall;	*/
#asm
#ifdef CPM
   /* For CP/M, check if device is any of the logical hardware devices */
	POP	B
	POP	H
	POP	D		/* Get name in D, table in H */
	PUSH	D
	PUSH	H
	PUSH	B
	LXI	H,IOdev
	MVI	C,32	/* Loop count */
Z1$:	PUSH	D
	PUSH	H
	MVI	B,4
Z2$:	LDAX	D
	ORI	' '	/* lower case */
	CMP	M
	JNZ	Z3$
	INX	D
	INX	H
	DCR	B
	JNZ	Z2$
	POP	D	/* Match succeeds */
	POP	H
	MOV	L,C	/* Return channel */
	MVI	H,0
	RET
Z3$:	LXI	D,4	/* Match fails - inc IOdev ptr */
	POP	H
	DAD	D
	POP	D
	INR	C
	MOV	A,M
	ORA	A
	JNZ	Z1$	/* Check next channel if not done */
#endif
   /*	i = 1;			/* Find an available channel */
   /*	while (i <= NFILES)	*/
	/*	if (IOch[i] == -1) break;
		else i++;	*/

	MVI	B,NFILES
	LXI	H,IOch+1
O1$:	MOV	A,M
	CPI	-1
	JZ	O2$
	INX	H
	DCR	B
	JNZ	O1$

   /*	if (i>NFILES) return NULL;	*/
	JMP	RN$

   /*	scall = 34;		/* Compute system call - .OPENR, W or U. */
   /*	if (*mode == 'w') scall = 35;
	if (*mode == 'u') scall = 36;	*/

O2$:	POP	B
	POP	D
	PUSH	D
	PUSH	B
	XCHG

   /*	IOmode[i] = *mode++;
	IObin[i] = *mode;
	IOrw[i] = 0;	       */

	MOV	A,M
	INX	H
	MOV	B,M
	XCHG
	LXI	D,IOrw-IOch
	DAD	D
	MVI	M,0
	LXI	D,IObin-IOrw
	DAD	D
	MOV	M,B
	LXI	D,IOmode-IObin
	DAD	D
	MOV	M,A
#ifdef CPM
#define IOguy IOnch
	LXI	D,IOnch-IOmode	/* CP/M - set chars in buffer to 0 (= 256) */
	DAD	D
	MVI	M,0
#else
#define IOguy IOmode
	MVI	C,OPENR+2	/* HDOS - compute system call to use */
	CPI	'u'		/* Changed to default to r 7/83 */
	JZ	O4$
	DCR	C
	CPI	'w'
	JZ	O4$
	DCR	C
O4$:	MOV	A,C
#endif
	STA	O6$+1
	LXI	D,IOguy 	/* Macro-80 can't relocate neg. addr. */
	XCHG
	CALL	s.1
	PUSH	H
	LXI	H,6
	DAD	SP
	CALL	h.
	POP	B
	PUSH	B
	MOV	A,C
	DCR	A
#ifdef CPM			/* Note that O6$+1 gets plugged in either OS */
	PUSH	H			/* Save file name */
	CALL	.CPC		/* Get FCB from "channel" */
	POP	H
	JZ	RN$			/* Return if can't alloc */
	PUSH	D			/* Save FCB */
	XCHG
	CALL	x_fcb		/* Put name in it */
O6$:	MVI	A,0		/* Gets plugged with mode */
	MVI	C,OPENR
	CPI	'w'		/* If not open for write, */
	JNZ	O6$B		/* just do OPEN. */
	POP	D
	PUSH	D
	MVI	C,DELETE	/* Open for write - delete file */
	CALL	BDOS
	MVI	C,MAKE		/* Then make new one. */
O6$B:	POP	D			/* Restore FCB */
	CALL	BDOS
	POP	D
	INR	A			/* Check for error */
	JZ	RN$
#else
	LXI	D,O7$	
O6$:	DB	255,0
	POP	D
	JC	RN$
#endif

  /*	IOch[i] = i;		/* Initialize index into buffer */

	LXI	H,IOch
	DAD	D
	MOV	M,E

  /*	IOind[i] = 256; 	/* to point to LWA + 1 */

	LXI	B,IOind-IOch
	DAD	B
	DAD	D
	MVI	M,0
	INX	H
	MVI	M,1

  /* See if buffer already allocated */

	LXI	B,IObuf-IOind
	DAD	B
	MOV	A,M
	DCX	H
	ORA	M
	JNZ	O8$

  /* No - allocate 256 bytes for it. */

	PUSH	D
	PUSH	H
	LXI	B,256
	PUSH	B
	CALL	sbrk
	POP	B
	XCHG
	POP	H
	MOV	M,E
	INX	H
	MOV	M,D

  /* Return NULL if sbrk returned -1 */

	INX	D
	MOV	A,E
	ORA	D
	POP	D
	JZ	RN$

  /*	return i;		(Clear carry for internal call) */

O8$:	XCHG
	XRA	A
#endasm
}

#ifndef CPM
#asm
O7$:	DB	'SY0'
	DB	0,0,0
#endasm
#endif

fclose(unit)
int unit;
{
  /*	int i;	*/
  /*	if (IOch[unit] == -1) return NULL;   */
#asm
	POP	B	
	POP	D
	PUSH	D
	PUSH	B
#ifdef CPM
	MOV	A,E				/* Check if physical unit */
	CPI	PUNIT
	JC	F$1
	LXI	H,IOpwrit			/* Yes - check if can write */
	CALL	$PU
	INX	H				/* If not, just return */
	RC
	LXI	D,IOpeof-IOpwrit-1		/* Else write EOF */
	DAD	D
	MOV	E,M
	MOV	A,E
	ORA	A
	CNZ	BDOS
	LXI	H,0
	RET			/* and return */
#endif
F$1:	LXI	H,IOsect			/* Zero IOsect entry */
	DAD	D
	DAD	D
	XRA	A
	MOV	M,A
	INX	H
	MOV	M,A
	LXI	H,IOch
	DAD	D
	MOV	A,M
	INR	A
	JZ	RN$	
	MVI	M,-1	/*	  IOch[unit] = -1;	  /* Release channel. */
	CALL	TM$			/* Put buffer address in IOtmp */
	LXI	H,IOmode
		/*if (IOmode[unit] != 'r') { /* If file being written, */
	DAD	D
	MOV	A,M
	CPI	'r'
	JZ	F2$
	LXI	H,IObin 	/*  and binary char*/
	DAD	D
	MOV	C,M
	LXI	H,IOind   /*   i = IOind[unit];     */
	DAD	D
	DAD	D
	PUSH	D
	CALL	h.
	MOV	A,L	 /*	   if (i & 255) {	   */
	ORA	A
	JZ	F3$
	XCHG
#ifdef CPM
	PUSH	PSW	/* CP/M - save whether >= 128 bytes */
#endif
	LHLD	IOtmp
	MOV	A,C	 /*	       if (IObin[unit] != 'b')		  */
	CPI	'b'
	JZ	F5$
	DAD	D	 /*	       while (i & 255) IObuf[i++] = 0;	  */
#ifdef CPM
	MVI	A,EOF		/* Fill last record with ctrl-Z for CP/M */
#else
	XRA	A			/* Fill with 0 for HDOS */
#endif
F4$:	MOV	M,A
	INX	H
	INR	E
	JNZ	F4$
	DCR	H  /*		write(unit,&IObuf[i-256],256); /* dump buffer */
#ifdef CPM
F5$:	POP	PSW		/* CP/M - might want to write 128 */
	PUSH	H
	LXI	H,128
	DCR	A		/* or 256 if index > 128 */
	JP	F5A$
	DAD	H
#else
F5$:	PUSH	H
	LXI	H,256
#endif
F5A$:	PUSH	H
	CALL	write
	POP	B
	POP	B
F3$:	POP	D

  /*		}  }		*/

F2$:	MOV	A,E
	DCR	A
	PUSH	D
#ifdef CPM
	CALL	.CPC
	MVI	C,CLOSE
	CALL	BDOS
	INR	A
	POP	H
	RNZ
#else
	DB	255,CLOSE
	POP	H
	RNC		/* Return unit */
#endif

  /*	return NULL;		*/

RN$:	LXI	H,NULL
	STC
	RET

  /* Subroutine to put buffer address in IOtmp */
TM$:	LXI	H,IObuf
	DAD	D
	DAD	D
	MOV	C,M
	INX	H
	MOV	B,M
	PUSH	B
	POP	H
	SHLD	IOtmp
#endasm
}

getc(unit)
int unit;
{
   /*	int c;	*/
   /*	while (1) {	*/
   /*		if ((IOind[unit] & 255) == 0) { 	*/
#asm
.G3:	CALL	.GPC	
	ORA	A	/* If unit 0 */
	JZ	$B8	/* Read from console */
#ifdef CPM
	CPI	PUNIT	/* Check if phys device */
	JNC	$GCH
#endif
	XRA	A
	CMP	C
	DCX	H
	JNZ	.G1
	PUSH	H	 /*  IOind[unit] = IOind[unit] - 256; */
	PUSH	D	 /*  if (read(unit,&IObuf[IOind[unit]],256) == 0)  */
	LHLD	IOtmp
	PUSH	H
	LXI	H,256
	PUSH	H
	CALL	read
	POP	B
	POP	B
	POP	D
#ifdef CPM
	PUSH	D
	XCHG	/* Store amount read in IOnch[unit] */
	LXI	B,IOnch
	DAD	B
	MOV	M,E
	MOV	A,E
	ORA	D
	POP	D
#else
	MOV	A,L
	ORA	H
#endif
	POP	H
	JZ	$RM	   /*	     return -1; }    */

   /*		c = 255 & IObuf[IOind[unit]++]; */
   /* (IOind+unit is still in (SP)). */

.G1:	PUSH	D
	PUSH	H
	CALL	h.
	INX	H
	CALL	q.
	DCX	H
	XCHG
	LHLD	IOtmp
	DAD	D
	MOV	B,M

   /*		if (IObin[unit] == 'b') return c;	*/

	LXI	H,IObin
	POP	D
	DAD	D
	MVI	A,'b'
	CMP	M
	JZ	.G2
	MOV	A,B
#ifdef CPM
	CPI	EOL	/* In CP/M, ignore CRs */
	JZ	.G3
	CPI	EOF	/* and return EOF for ctrl-Z */
	JZ	$RM
#endif

   /*		if (c) return (c); }	/* Ignore nulls in ascii file */

	ORA	A
	JZ	.G3
.G2:	MOV	L,B
	MVI	H,0
	RET

/* Subroutine for putc and getc to increment buffer pointers */
/* Does: if ((IOind[unit]&255)==0) IOind[unit]=IOind[unit]-256; 
   returns IOind[unit] in BC, address+1 in HL, and unit in DE and A.
   Also sets up IOtmp to point to buffer. */

.GPC:	POP	H
	POP	B
	POP	D
	PUSH	D
	PUSH	B
	PUSH	H
	MOV	A,E	/* Return if unit 0 */
	ORA	A
	RZ
#ifdef CPM
	CPI	PUNIT	/* Check if phys channel */
	RNC
#endif
	CALL	TM$
#ifdef CPM
	LXI	H,IOnch /* Get nr chars in buffer */
	DAD	D
	MOV	A,M
#else
	XRA	A
#endif
	LXI	H,IOind
	DAD	D
	DAD	D
	MOV	C,M
	INX	H
	MOV	B,M
	CMP	C
	MOV	A,E	/* Leave unit in A */
	RNZ
	MVI	B,0
	MOV	M,B
#ifdef CPM
	DCX	H	/* On CP/M, C might be 00 or 80.  HDOS: only 00 */
	MOV	M,B
	MOV	C,B
	INX	H
#endif
#endasm
}	

putc(c,unit)
char c; int unit;
{
  /*	if ((IOind[unit] & 255) == 0) IOind[unit] = IOind[unit] - 256;	*/
#asm
	CALL	.GPC
	ORA	A	/* If to console */
	JNZ	PC.8
	POP	B
	POP	H
	POP	D	/* Get character in D */
	PUSH	D
	PUSH	H
	PUSH	B
	JMP	$B7	/* Go output it */
PC.8:	DS	0
#ifdef CPM
	CPI	PUNIT	/* Check if phys device */
	JC	PC.9
	POP	B		/* Get char in HL */
	POP	D
	POP	H
	PUSH	H
	PUSH	D
	PUSH	B
	JMP	$PCH	/* Go do it */
#endif
PC.9:	DCX	H
	PUSH	H

   /*	IObuf[IOind[unit]++] = c;	*/

	LXI	H,6
	DAD	SP
	MOV	A,M
#ifdef CPM
	CPI	LF		/* LF on non-bin file? */
	JNZ	PC.1
	PUSH	H
	LXI	H,IObin
	DAD	D
	MOV	A,M
	CPI	'b'
	JZ	PC.3
	LXI	H,C.LFD  /* Yes - check flag to see if CR output already */
	DCR	M
	JNZ	PC.2
	MVI	A,EOL
	POP	H
	JMP	PC.1
C.LFD:	DB 1
PC.2:	MVI	M,1
PC.3:	POP	H
	MVI	A,LF
#endif
PC.1:	LHLD	IOtmp
	DAD	B
	MOV	M,A
	XTHL
	PUSH	H
	PUSH	H
	MOV	H,B
	MOV	L,C
	INX	H
	CALL	q.
	POP	H

   /*	if ((IOind[unit] & 255) == 0)	*/

	XRA	A
	CMP	M
	POP	H
	JNZ	.PC1

   /*		if (write(unit,&IObuf[IOind[unit]-256],256) <= 0) return -1; */

	POP	B
	POP	D
	PUSH	D
	PUSH	B
	PUSH	D
	DCR	H
	INR	L
	PUSH	H
	LXI	H,256
	PUSH	H
.PC2:	CALL	write		/* This location may be modified by seek */
	POP	B
	POP	B
	POP	B
	MOV	A,H
	ORA	L
	LXI	H,-1
	RZ
	RM

   /*	return (c & 255);	*/

.PC1:	LXI	H,4
	DAD	SP
	MOV	L,M
	MVI	H,0
#ifdef CPM
	LDA	C.LFD
	ORA	A
	JZ	putc	/* If output CR, follow with LF */
#endif
#endasm
}

/* Routines to write, read one block */
write(unit,buf,count)
int unit;
char *buf;
int count;
{				/* Set up channel and addresses */
#asm
	XRA	A		/* Remember whether write */
	STA	RD.RW
#ifdef CPM
	MVI	A,WRITE
	JMP	RD.0
RD.T:	DS	1
#else
	CALL	$RW.
	PUSH	B
	DB	255,WRITE
	POP	H
	JC	$RW.3		/* Fix double error reporting 7/83 */
#endif
$RW.2:	PUSH	H		/* Increment sector count */
	LXI	H,8
	DAD	SP
	CALL	h.
	XCHG
	POP	H
	PUSH	H		/* Retrieve sectors read/written */
#ifdef CPM
	DAD	H		/* (CP/M - Multiple of 128 bytes) */
#endif
	LDA	RD.RW
	ANA	H		/* R/W flag is number of sectors read */
	PUSH	PSW		/* Save it on stack */
	MOV	A,H
	LXI	H,IOsect
	DAD	D
	DAD	D
	ADD	M		/* Add to sector count */
	MOV	M,A
	JNC	.PC5
	INX	H		/* And carry if necessary */
	INR	M
.PC5:	LXI	H,IOrw		/* Store R/W flag */
	DAD	D
	POP	PSW		/* Get nr sectors read back */
	MOV	M,A
	POP	H
#endasm
}

read(unit,buf,count)
int unit;
char *buf;
int count;
{				/* Set up channel and addresses */
#asm
	MVI	A,-1
	STA	RD.RW
#ifdef CPM
	MVI	A,READ
RD.0:	STA	RD.T
	LDA	Cmode	/* If not in raw mode, give ^c a shot */
	ORA	A
	CNZ	CtlCk
	CALL	$RW.
	PUSH	B		/* Save original count */
RD.1:	MOV	A,B		/* Done reading? */
	ORA	C
	JZ	RD.4		/* Yes - quit */
	PUSH	D		/* Save FCB */
	PUSH	H		/* Save address */
	PUSH	B		/* Save remaining count */
	PUSH	D		/* Save FCB */
	XCHG
	MVI	C,SETDMA
	CALL	BDOS
	POP	D		/* Get FCB */
	LDA	RD.T	/* Get read/write code */
	MOV	C,A
	CALL	BDOS
	POP	B		/* Get count */
	ORA	A		/* If EOF */
	JNZ	RD.2		/* Quit */
	LXI	H,-128
	DAD	B
	MOV	B,H
	MOV	C,L
	POP	H		/* Bump address */
	LXI	D,128
	DAD	D
	POP	D		/* Get FCB back */
	JMP	RD.1
RD.2:	POP	H		/* Error - Pop adr, FCB off stack */
	POP	D		/* saving adr in H */
	LDA	RD.T	/* Check read or write */
	CPI	WRITE
	JNZ	RD.4		/* If read, EOF. Just return count */
	POP	H
	LXI	D,RD.M		/* Write - it's an error. */
	MVI	C,PRINT 	/* Print message */
	CALL	BDOS
	LXI	H,-1		/* Return -1 */
	JMP	RD.3
RD.M:	DB	'Write error - Disk full'
	DB	0AH,0DH,'$'
RD.4:	POP	D		/* Restore orig. count */
	MOV	H,B
	MOV	L,C
	CALL	s.1		/* Return amount read */
RD.3:	PUSH	H
	MVI	C,SETDMA	/* Restore DMA address */
	LXI	D,ARGLIN	/* (because CDOS uses it) */
	CALL	BDOS
	POP	H
	JMP	$RW.2		/* Return */
#else
	CALL	$RW.
	PUSH	B		/* Save original count */
	DB	255,READ
	POP	H
	JNC	$RW.2
	DCR	A
	JNZ	$RW.1
	MOV	A,H
	SUB	B
	MOV	H,A
	RET
$RW.1:	INR	A
$RW.3:	DB	255,ERR
	JMP	$RM
#endif
RD.RW:	DS	1

/* Subroutine to set up channel and transfer addresses.
	CALL	with stack = [ret.ad; ret.ad; xfer count; buffer adr; channel].
	RETurns with addresses set up for read/write call.
   HDOS: A=channel, BC=count, DE=address.
   CP/M: BC=count, DE=FCB, HL=address. */

$RW.:	LXI	H,8
	DAD	SP
	MOV	A,M
	DCR	A
	DCX	H
#ifdef CPM
	PUSH	H		/* Save address */
	CALL	.CPC		/* Get FCB in DE */
	POP	H
	PUSH	D		/* Save it */
#endif
	MOV	D,M
	DCX	H
	MOV	E,M
	DCX	H
	MOV	B,M
	DCX	H
	MOV	C,M
#ifdef CPM
	POP	H		/* Restore FCB */
	XCHG
#endif
	RET

#ifdef CPM
/* CP/M name->FCB routine.  Call with file name address in DE, FCB IN HL. */

x_fcb:	LXI	B,11	/* Erase the name area with blanks. */
	DAD	B
	SHLD	.XTMP	/* Save FCB+11 */
	MVI	A,' '
nam.1:	MOV	M,A
	DCX	H
	DCR	C
	JNZ	nam.1
	XRA	A		
	MOV	M,A	/* Erase drive number */
	LXI	B,12
	DAD	B
	MOV	M,A	/* Erase EX, S2 and CR */
	INX	H
	INX	H
	MOV	M,A
	LXI	B,18
	DAD	B
	MOV	M,A
	LXI	B,-32	/* Get start of FCB */
	DAD	B
	XCHG
	INX	H
	MOV	A,M	/* IS : IN NAME */
	DCX	H
	CPI	':'
	JNZ	nam.2
	MOV	A,M	/* YES - GET DRIVE. */
	INX	H
	INX	H		/* STEP PAST DRIVE: */
	CALL	NAM.U
	SUI	'A'-1
	STAX	D
nam.2:	INX	D		/* GET NAME*/
	MOV	A,M
	INX	H
	ORA	A		/* DONE? */
	RZ	/* YES - RETURN. */
	CPI	'.'	 /* START TYPE? */
	JZ	nam.3
	CALL	NAM.U	/* NO. STORE CHAR. */
	STAX	D
	JMP	nam.2
nam.3:	XCHG		/* START TYPE */
	LHLD	.XTMP
	DCX	H		/* Restore FCB+11 */
	DCX	H
nam.4:	LDAX	D
	ORA	A		/* DONE? */
	RZ	/* RETURN. */
	CALL	NAM.U
	MOV	M,A
	INX	D
	INX	H
	JMP	nam.4
.XTMP:	DS 2
NAM.U:	ANI	177Q	/* CAPITALIZE (A). */
	CPI	'a'
	RM
	SUI	'a'-'A'
	RET
.CPC:	MOV	C,A	/* Get FCB location in DE from channel number in A. */
	MVI	B,0	/* Return zero flag if error. */
	LXI	H,IOfcb
	DAD	B
	DAD	B
	MOV	A,M	/* Allocated yet? */
	MOV	E,A
	INX	H
	MOV	D,M
	ORA	D
	RNZ		/* Yes - return */
	PUSH	H		/* No - allocate it */
	LXI	H,FCBLEN
	PUSH	H
	CALL	sbrk
	XCHG
	POP	H
	POP	H
	MOV	M,D	/* Store block address */
	DCX	H
	MOV	M,E
	MOV	H,D
	MOV	L,E	/* Get value to return */
	INX	H	/* Set Z flag if can't alloc */
	MOV	A,H
	ORA	L
	RET
#endif

#ifdef MAC
$END:	DS	0
	END	$INIT
#endasm
#else

#endasm
#include "clibmath.c"
#endif

}
m */
	INX	H
	INR	M		/* Inc count */
	MOV	C,M
	MVI	B,0
	DAD	B
	MOV	M,E		/* Store char */
	MVI	A,LF
	CMP	E
	MV