/* name... iolib purpose... to provide a "standard" interface between c programs and the CPM I/O system. notes... Compile using -M option. history... 31 Jul 84 fbuf is now a pointer rather than an array, and the disk buffer is allocated from the heap. 28 Jul 84 Prepended '_' to all global variables that shouldn't be visible elsewhere. 27 Jul 84 Permitting I/O redirection. err() temporarily resets STDOUT to 1 so error messages go to console. 26 Jul 84 ccgo is initializing fmode[] at run time, so compiled code need not be changed before assembly. 14 Jul 84 gets() emitting LF after reading input. 1 Jul 84 Declaring _dfltdsk in c rather than assembly, so references from c don't have to be corrected. 27 Jun 84 Changed ENDDATA to _END in alloc(). 17 Jun 84 including iolib.h, so output can be separately assembled. 14 Jun 84 Several bugs fixed, 'A' file mode supported, putb() and getb() installed, getc(1) comes from keyboard & putc(c,2) goes to console. 29 Oct 83 Saving return addr in cpm(). 13 Oct 83 QERR added from clibv.asm, reformatted for ZMAC. 18 Sep 83 written (jrvz). */ #include iolib.h #asm BDOS = 5 CR = 13 ; cpm(bc,de) int bc,de; BDOS call */ QCPM: POP HL POP DE POP BC PUSH BC PUSH DE PUSH HL CALL BDOS JP CCSXT ;move A to HL & sign extend ; ; /* return address of a block of memory */ ; alloc(b) ; int b; /* # bytes desired */ ; QALLOC: POP HL ;return addr POP DE ;block size PUSH DE PUSH HL LD HL,(HEAPTOP) ;current top of heap EX DE,HL ADD HL,DE ;hl=new top of heap LD (HEAPTOP),HL EX DE,HL ;hl=old top of heap RET HEAPTOP: DEFW _END ; ; /* reset the top of heap pointer to addr* */ ; ; free(addr) ; int addr; ; QFREE: POP DE POP HL ;addr PUSH HL PUSH DE LD (HEAPTOP),HL RET ; ; /* return number of bytes between top of heap ; and end of TPA. Remember that this includes ; the stack! */ ; ; avail() ; QAVAIL: LD HL,(6) ;end of TPA PUSH HL LD HL,(HEAPTOP) ;top of heap JP CCSUB ;find (6)-HEAPTOP ; ; error...print message & walkback trace (if available) ; ; err(s) char *s; ; { int str; ; puts("\nERROR "); QERR: LD HL,(QSTDOUT) LD (ERR6),HL LD HL,1 LD (QSTDOUT),HL LD HL,MSGE PUSH HL CALL QPUTS POP HL ; puts(s); POP DE POP HL PUSH HL PUSH DE PUSH HL CALL QPUTS POP HL ; str=current; LD HL,(CURRENT) ; while(str) ERR4: LD (ERR2),HL LD A,H OR L JR Z,ERR5 ; {puts("\ncalled by "); LD HL,MSGE2 PUSH HL CALL QPUTS POP HL ; puts(*(str+1)); LD HL,(ERR2) INC HL INC HL CALL CCGINT PUSH HL CALL QPUTS POP HL ; str=*str; LD HL,(ERR2) CALL CCGINT ; } JR ERR4 ; } ERR5: LD HL,(ERR6) LD (QSTDOUT),HL RET ; MSGE: DB CR,'ERROR: ',0 MSGE2: DB CR,'CALLED BY ',0 ERR2: DW 0 ERR6: DW 0 ;temporary storage for STDOUT CURRENT: DW 0 #endasm #define LF 10 int _dfltdsk, /* "current disk" at beginning of execution */ stdin, /* 0 initially, or unit number for input file if input has been redirected by args() */ stdout; /* 1 initially, or unit number for output file if output has been redirected by args() */ getchar() { return getc(stdin); } putchar(c) char c; { putc(c,stdout); return c; } gets(buf) char *buf; /* input a string (editing permitted) */ { char s1,s2; int i; if(stdin) /* input has been redirected */ {i=80; while(i--) {s1=getc(stdin); if((s1==-1)|(s1=='\n')) break; *buf++=s1; } *buf=0; } else {s2=buf[-2]; s1=buf[-1]; /* save 2 bytes */ buf[-2]=80; /* assumed string length */ cpm(10,buf-2); buf[buf[-1]]=0; /* mark end using count left by cpm */ buf[-1]=s1; buf[-2]=s2; /* restore the bytes */ putchar('\l'); /* LF */ } } puts(buf) char *buf; /* print a null-terminated string */ { char c; while(c=*buf++) putchar(c); } #define NBUFS 3 /* = number of files which can be open at once */ /* NOTE: ccgo() must initialize this many */ /* elements of fmode[]. */ #define LGH 1024 /* length of each file buffer */ /* = some multiple of 128: 128, 256, 384, 512... */ #define BUFLGH 3171 /* =NBUFS*(LGH+33) */ /* used in ccgo() to allocate the disk buffer */ #define MFREE 11387 #define MREAD 22489 #define MWRITE 17325 #define MEOF -8734 char *_fbuf; /* for fcb's and disk buffers */ int _ffcb[NBUFS], /* pointers to the fcb's */ _fnext[NBUFS], /* pointers to the next char to be fed to the program (for an input file) or the next free byte in the buffer (for an output file) */ _ffirst[NBUFS], /* ptrs to the starts of the buffers */ _flast[NBUFS], /* ptrs to the ends of the buffers */ _fmode[NBUFS], /* MFREE => buffer is free MREAD => open for reading MWRITE => open for writing MEOF => was open for reading, but EOF encountered = MFREE initially */ _ex,_cr; /* extent & current record at beginning of this buffer full (used for "A" access) */ fopen(name,mode) /* open file in fmode "r", "w", or "a" (upper or lower case) */ char *name,*mode; { char c,*fcb; int index,i,unit; index=NBUFS; while(index--) /* search for free buffer */ {if(_fmode[index]==MFREE)break; } if(index==-1) {err("OUT OF DISK BUFFERS"); exit(); } unit=index+5; _ffcb[index]=_fbuf+index*(33+LGH); _ffirst[index]=_ffcb[index]+33; _flast[index]=_ffirst[index]+LGH; fcb=_ffcb[index]; i=11; while(i) fcb[i--]=' '; /* clear file name */ fcb[12]=fcb[32]=0; /* clear ex & cr */ if (name[1]==':') /* transfer disk */ {fcb[0]= (*name&15) +1; /* either case */ name=name+2; } else fcb[0]=_dfltdsk; while(c=upper(*name++)) /* transfer name */ {if(c=='.')break; fcb[++i]=c; } if(c=='.') /* transfer extension */ {i=8; while(c=upper(*name++)) {fcb[++i]=c;} } c=upper(*mode); /* puts("OPENING FILE "); puts(fcb+1); putchar('\n'); puts("fcb at "); hex(fcb); puts("\nbuffer "); hex(_ffirst[index]); puts("through "); hex(_flast[index]); */ if((c=='R')|(c=='A')) {if(cpm(15,fcb)<0) {/* err("INPUT FILE DOESN\'T EXIST");*/ return 0; /* file not found */ } _fmode[index]=MREAD; /* open for reading */ _fnext[index]=_flast[index]; /* forces immed. read */ if(c=='A') /* append mode requested? */ {while(getc(unit)!=-1) {} /* read to EOF */ fcb[12]=_ex; /* reset to values at...*/ cpm(15,fcb); /* ...beginning of buffer */ fcb[32]=_cr; _fmode[index]=MWRITE; } /* puts("\nnext char at "); hex(_fnext[index]); */ return unit; } else if(c=='W') {cpm(19,fcb); /* delete file */ i=cpm(22,fcb); /* create file */ if(i<0) return 0; /* creation failure */ _fmode[index]=MWRITE; /* open for writing */ _fnext[index]=_ffirst[index]; /* buffer is empty */ /* puts("\nnext char at "); hex(_fnext[index]);*/ return unit; } else return 0; } fclose(unit) int unit; /* close a file */ { int index,i,werror; /* puts("\nfclose: closing unit "); hexb(unit); */ index=unit-5; i=fchk(index); if((i==MREAD)|(i==MEOF)) /* don't close read files */ {_fmode[index]=MFREE; return 1; /* success */ } putb(26,unit); /* append ^Z (CP/M EOF) */ werror=fflush(unit); _fmode[index]=MFREE; if((cpm(16,_ffcb[index])<0)|werror) return 0; /* failure */ return 1; /* success */ } fchk(index) int index; /* check for legal index */ { int i; if((index>=0)&(index<=NBUFS)) {i=_fmode[index]; if((i==MREAD)|(i==MWRITE)|(i==MEOF)) return i; } err("INVALID UNIT NUMBER"); exit(); } getc(unit) int unit; /* get character from file (return -1 at EOF) */ { int c; while((c=getb(unit))==LF){} /* discard LF */ if(c==26) /* CP/M EOF? */ {if(unit>=5) /* leave _fnext[index] pointing at the ^Z */ {_fmode[unit-5]=MEOF; --_fnext[unit-5]; } return -1; } return c; } getb(unit) int unit; /* get byte from file (return -1 at EOF) */ { int index,mode,i; char *next,*last,c,*fcb; if(unit==0) /* STDIN */ {c=cpm(1,0); if(c=='\n')cpm(2,LF); /* add LF after CR */ return c; } index=unit-5; mode=fchk(index); if(mode==MEOF) return -1; /* already found eof */ if(mode!=MREAD) {err("CAN\'T READ OUTFILE"); exit();} next=_fnext[index]; if(next==_flast[index]) /* empty buffer? */ {fcb=_ffcb[index]; _ex=fcb[12]; _cr=fcb[32]; /* save for fopen() */ next=_ffirst[index]; last=next+LGH; while(next='a')return c-32; return c; } exit() { if(stdout>=5)fclose(stdout); #asm JP 0 #endasm } #asm ; ; Runtime library initialization. ; Set up default drive for CP/M. CCGO: LD C,25 ;get current disk CALL BDOS INC A ;now in range 1...16 LD (Q_DFLTDSK),A LD HL,11387 ;initialize the elements of LD (Q_FMODE),HL ;_fmode[] (# entries = NBUFS) LD (Q_FMODE+2),HL ;to reflect free buffers LD (Q_FMODE+4),HL LD HL,(HEAPTOP) ;current top of heap LD (Q_FBUF),HL ;initialize buffer pointer LD DE,3171 ;= BUFLGH ADD HL,DE ;hl=new top of heap LD (HEAPTOP),HL LD HL,0 LD (QSTDIN),HL ;initialize input unit INC HL LD (QSTDOUT),HL ;initialize output unit RET ;Fetch a single byte from the address in HL and ; sign extend into HL CCGCHAR: LD A,(HL) QARGC: CCSXT: LD L,A RLCA SBC A,A LD H,A RET ;Fetch integer from (HL+2) CCCDR: INC HL INC HL ;Fetch a full 16-bit integer from the address in HL CCCAR: CCGINT: LD A,(HL) INC HL LD H,(HL) LD L,A RET ;Store a 16-bit integer in HL at the address in TOS CCPINT: POP BC POP DE PUSH BC LD A,L LD (DE),A INC DE LD A,H LD (DE),A RET ;Inclusive "or" HL and TOS into HL CCOR: POP BC POP DE PUSH BC LD A,L OR E LD L,A LD A,H OR D LD H,A RET ;Exclusive "or" HL and TOS into HL CCXOR: POP BC POP DE PUSH BC LD A,L XOR E LD L,A LD A,H XOR D LD H,A RET ;"And" HL and TOS into HL CCAND: POP BC POP DE PUSH BC LD A,L AND E LD L,A LD A,H AND D LD H,A RET ;Test if HL = TOS and set HL = 1 if true else 0 CCEQ: POP BC POP DE PUSH BC CALL CCCMP RET Z DEC HL RET ;Test if TOS ~= HL CCNE: POP BC POP DE PUSH BC CALL CCCMP RET NZ DEC HL RET ;Test if TOS > HL (signed) CCGT: POP BC POP DE PUSH BC EX DE,HL CALL CCCMP RET C DEC HL RET ;Test if TOS <= HL (signed) CCLE: POP BC POP DE PUSH BC CALL CCCMP RET Z RET C DEC HL RET ;Test if TOS >= HL (signed) CCGE: POP BC POP DE PUSH BC CALL CCCMP RET NC DEC HL RET ;Test if TOS < HL (signed) CCLT: POP BC POP DE PUSH BC CALL CCCMP RET C DEC HL RET ;Common routine to perform a signed compare ; of DE and HL ;This routine performs DE - HL and sets the conditions: ; Carry reflects sign of difference (set means DE < HL) ; Zero/non-zero set according to equality. CCCMP: LD A,E SUB L LD E,A LD A,D SBC A,H LD HL,1 ;preset true condition JP M,CCCMP1 OR E ;"OR" resets carry RET CCCMP1: OR E SCF ;set carry to signal minus RET ; ;Test if TOS >= HL (unsigned) CCUGE: POP BC POP DE PUSH BC CALL CCUCMP RET NC DEC HL RET ; ;Test if TOS < HL (unsigned) CCULT: POP BC POP DE PUSH BC CALL CCUCMP RET C DEC HL RET ; ;Test if TOS > HL (unsigned) CCUGT: POP BC POP DE PUSH BC EX DE,HL CALL CCUCMP RET C DEC HL RET ; ;Test if TOS <= HL (unsigned) CCULE: POP BC POP DE PUSH BC CALL CCUCMP RET Z RET C DEC HL RET ; ;Common routine to perform unsigned compare ;carry set if DE < HL ;zero/nonzero set accordingly CCUCMP: LD A,D CP H JP NZ,CUCMP1 LD A,E CP L CUCMP1: LD HL,1 RET ; ;Shift DE arithmetically right by HL and return in HL CCASR: EX DE,HL DEC E RET M ; 7/2/82 jrvz LD A,H RLA LD A,H RRA LD H,A LD A,L RRA LD L,A JP CCASR+1 ;Shift TOS arithmetically left by HL and return in HL CCASL: POP BC POP DE PUSH BC EX DE,HL CCASL4: DEC E RET M ; jrvz 7/2/82 ADD HL,HL JP CCASL4 ;Subtract HL from TOS and return in HL CCSUB: POP BC POP DE PUSH BC LD A,E SUB L LD L,A LD A,D SBC A,H LD H,A RET ;Form the two's complement of HL CCNEG: CALL CCCOM INC HL RET ;Form the one's complement of HL CCCOM: LD A,H CPL LD H,A LD A,L CPL LD L,A RET ;Multiply TOS by HL and return in HL CCMULT: POP BC POP DE PUSH BC LD B,H LD C,L LD HL,0 CCMLT1: LD A,C RRCA JP NC,CMLT2 ADD HL,DE CMLT2: XOR A LD A,B RRA LD B,A LD A,C RRA LD C,A OR B RET Z XOR A LD A,E RLA LD E,A LD A,D RLA LD D,A OR E RET Z JP CCMLT1 ;Divide DE by HL and return quotient in HL, remainder in DE CCDIV: LD B,H LD C,L LD A,D XOR B PUSH AF LD A,D OR A CALL M,CCDENEG LD A,B OR A CALL M,CCBCNEG LD A,16 PUSH AF EX DE,HL LD DE,0 CCDIV1: ADD HL,HL CALL CCRDEL JP Z,CCDIV2 CALL CCPBCDE JP M,CCDIV2 LD A,L OR 1 LD L,A LD A,E SUB C LD E,A LD A,D SBC A,B LD D,A CCDIV2: POP AF DEC A JP Z,CCDIV3 PUSH AF JP CCDIV1 CCDIV3: POP AF RET P CALL CCDENEG EX DE,HL CALL CCDENEG EX DE,HL RET CCDENEG: LD A,D CPL LD D,A LD A,E CPL LD E,A INC DE RET CCBCNEG: LD A,B CPL LD B,A LD A,C CPL LD C,A INC BC RET CCRDEL: LD A,E RLA LD E,A LD A,D RLA LD D,A OR E RET CCPBCDE: LD A,E SUB C LD A,D SBC A,B RET #endasm /* hex(x) int x; { hexb(x>>8); hexb(x); putchar(' '); } hexb(x) int x; { hexn(x>>4); hexn(x); } hexn(x) int x; { x=x&15; if(x<10) putchar(x+'0'); else putchar(x-10+'a'); } */  .