; ; CCC.ASM (C.CCC) v1.45 11/22/81 ; ; NOTE: If you are running under MP/M II, be sure to set the MPM2 ; equate to 1. ; ; This is the BDS C run-time package. Normally, it resides at ; the start of the TPA (at address BASE+100h, where BASE is either ; 0000h or 4200h depending on CP/M implementation.) The code ; generated by the compiler ALWAYS sits immediately after the end of ; this run-time package code. ; ; Equate statements in CAPITAL letters may be customized by the ; user in order to change a) the origin of the run-time package, ; and b) the origin of the run-time RAM area. If you will be ; generating code to run in a non-CP/M environment, set the CPM ; equate to zero and make sure to set the ORIGIN, RAM and ; EXITAD equates to fit your custom run-time configuration. ; ; The "lxi sp,0" instruction at the start is replaced by the sequence: ; ; lhld base+6 ; sphl ; ; by CLINK at link time, unless the -t option is used with CLINK, ; in which case the "lxi sp" remains there and the value used to ; initialize the SP is the argument given to the "-t" option. ; page 76 title 'BDS C Run-Time Module (c.ccc) v1.45 11/22/81' CPM: EQU 1 ;True if to be run under CP/M or MP/M MPM2: EQU 0 ;True ONLY if running under MP/M II DMAVIO: EQU 1 ;True if using DMA video library routines and ;need parameters initialized IF CPM base: equ 0 ;start of ram in system (either 0 or 4200h for CP/M) bdos: equ base+5 ;rest of these used by CP/M-based configurations. tpa: equ base+100h nfcbs: equ 8 ;maximum # of files open at one time tbuff: equ base+80h origin: equ tpa exitad: equ base ;warm boot location ENDIF IF NOT CPM ;fill in the appropriate values... ORIGIN: EQU NEWBASE ;Address at which programs are to run RAM: EQU WHATEVER ;Read-write memory area for non-CP/M configurations ; (defaults to immediately after C.CCC under CP/M) EXITAD: EQU WHENDONE ;where to go when done executing ENDIF ; ; The location of the jump vectors and utility routines must remain ; constant relative to the beginning of this run-time module. ; ; Do NOT change ANYTHING between here and the start of the ; "init" routine!!!!!!!! ; org origin lxi sp,0 ;this is changed by CLINK to lhld base+5h nop ;this first is usually turned into sphl by CLINK nop! nop ;Simple initialization or patches may be nop! nop! nop ;inserted here, but better to do all that nop! nop! nop ;in the "init" routine call init ;do ARGC & ARGV processing, plus misc. initializations call main ;go crunch!!!! jmp vexit ;close open files and reboot extrns: ds 2 ;set by CLINK to external data base address cccsiz: dw main-origin ;size of this code (for use by CLINK) codend: ds 2 ;set by CLINK to (last addr of code + 1) freram: ds 2 ;set by CLINK to (last addr of externals + 1) ; ; Jump vectors to some file i/o utility routines: ; error: jmp verror ;loads -1 into HL and returns exit: jmp vexit ;close all open files and reboot IF CPM close: jmp vclose ;close a file setfcb: jmp vsetfcb ;set up fcb at HL given filename at DE fgfd: jmp vfgfd ;return C set if file fd in A not open fgfcb: jmp vfgfcb ;compute address of internal fcb for fd in A ENDIF IF NOT CPM ;if not under CP/M, file I/O routines close: jmp verror ;are not used. setfcb: jmp verror fgfd: jmp verror fgfcb: jmp verror ENDIF ds 16 ;reserved IF CPM setfcb3: mov m,a ;this is a patch from the "vsetfcb" routine, inx h ;which causes the random record bytes of the mov m,a ;fcb being initialized to be zeroed. (Former inx h ;versions had a "ds 30" above, so this keeps mov m,a ;all the addresses consistent between this pop d ;and earlier 1.4's) pop b ret patchnm: call setnm ;another patch from "vsetfcb" jmp setnm3 ENDIF IF NOT CPM ds 14 ;keep addresses the same for non-CP/M implementations ENDIF ; ; The following routines fetch a variable value from either ; the local stack frame or the external area, given the relative ; offset of the datum required immediately following the call; ; for the "long displacement" routines, the offset must be 16 bits, ; for the "short displacement" routines, the offset must be 8 bits. ; ; ; long-displacement, double-byte external indirection: ; ; format: call ldei ; get 16-bit value in HL ; dw offset_from_extrns ; >= 256 ; ldei: pop h ;get address of offset mov e,m ;put offset in DE inx h mov d,m inx h push h ;save return address lhld extrns ;add offset to external area base dad d mov a,m ;and get the value into HL inx h mov h,m mov l,a ret ; ; short-displacement, double-byte external indirection: ; ; format: call sdei ; get 16-bit value in L ; db offset_from_extrns ; < 256 ; sdei: pop h mov e,m inx h push h mvi d,0 lhld extrns dad d mov a,m inx h mov h,m mov l,a ret ; ; long-displacement, single-byte external indirection: ; ; format: call lsei ; get 8-bit value in L ; dw offset_from_extrns ; >= 256 ; lsei: pop h mov e,m inx h mov d,m inx h push h lhld extrns dad d mov l,m ret ; ; short-displacement, single-byte external indirection: ; ; format: call ssei ; get 8-bit value in L ; db offset_from_externs ; < 256 ; ssei: pop h mov e,m inx h push h mvi d,0 lhld extrns dad d mov l,m ret ; ; long-displacement, double-byte local indirection: ; ; format: call ldli ; get 16-bit value in HL ; dw offset_from_BC ; >= 256 ; ldli: pop h mov e,m inx h mov d,m inx h push h xchg dad b mov a,m inx h mov h,m mov l,a ret ; ; short-displacement, double-byte local indirection: ; ; format: call sdli ; get 16-bit value in HL ; db offset_from_BC ; < 256 ; sdli: pop h mov e,m inx h push h xchg mvi h,0 dad b mov a,m inx h mov h,m mov l,a ret ; ; Flag conversion routines: ; pzinh: lxi h,1 ;return HL = true if Z set rz dcx h ret pnzinh: lxi h,0 ;return HL = false if Z set rz inx h ret pcinh: lxi h,1 ;return HL = true if C set rc dcx h ret pncinh: lxi h,0 ;return HL = false if C set rc inx h ret ppinh: lxi h,1 ;return HL = true if P (plus) flag set rp dcx h ret pminh: lxi h,1 ;return HL = true if M (minus) flag set rm dcx h ret pzind: lxi d,1 ;return DE = true if Z set rz dcx d ret pnzind: lxi d,0 ;return DE = false if Z set rz inx d ret pcind: lxi d,1 ;return DE = true if C set rc dcx d ret pncind: lxi d,0 ;return DE = false if C set rc inx d ret ppind: lxi d,1 ;return DE = true if P (plus) flag set rp dcx d ret pmind: lxi d,1 ;return DE = true if M (minus) flag set rm dcx d ret ; ; Relational operator routines: take args in DE and HL, ; and return a flag bit either set or reset. ; ; ==, >, < : ; eqwel: mov a,l ;return Z if HL == DE, else NZ cmp e rnz ;if L <> E, then HL <> DE mov a,h ;else HL == DE only if H == D cmp d ret blau: xchg ;return C if HL < DE, unsigned albu: mov a,d ;return C if DE < HL, unsigned cmp h rnz ;if D <> H, C is set correctly mov a,e ;else compare E with L cmp l ret bgau: xchg ;return C if HL > DE, unsigned agbu: mov a,h ;return C if DE > HL, unsigned cmp d rnz ;if H <> D, C is set correctly mov a,l ;else compare L with E cmp e ret blas: xchg ;return C if HL < DE, signed albs: mov a,h ;return C if DE < HL, signed xra d jp albu ;if same sign, do unsigned compare mov a,d ora a rp ;else return NC if DE is positive and HL is negative stc ;else set carry, since DE is negative and HL is pos. ret bgas: xchg ;return C if HL > DE, signed agbs: mov a,h ;return C if DE > HL, signed xra d jp agbu ;if same sign, go do unsigned compare mov a,h ora a rp ;else return NC is HL is positive and DE is negative stc ret ;else return C, since HL is neg and DE is pos ; ; Multiplicative operators: *, /, and %: ; smod: mov a,d ;signed MOD routine: return (DE % HL) in HL push psw ;save high bit of DE as sign of result call tstn ;get absolute value of args xchg call tstn xchg call usmod ;do unsigned mod pop psw ;was DE negative? ora a ;if not, rp ; all done mov a,h ;else make result negative cma mov h,a mov a,l cma mov l,a inx h ret nop ;maintain address compatibility with some nop ; pre-release v1.4's. usmod: mov a,h ;unsigned MOD: return (DE % HL) in HL ora l rz push d push h call usdiv pop d call usmul mov a,h cma mov h,a mov a,l cma mov l,a inx h pop d dad d ret smul: xra a ;signed multiply: return (DE * HL) in HL sta tmp call tstn xchg call tstn call usmul smul2: lda tmp rar rnc mov a,h cma mov h,a mov a,l cma mov l,a inx h ret tstn: mov a,h ora a rp cma mov h,a mov a,l cma mov l,a inx h lda tmp inr a sta tmp ret usmul: push b ;unsigned multiply: return (DE * HL) in HL call usm2 pop b ret usm2: mov b,h mov c,l lxi h,0 usm3: mov a,b ora c rz mov a,b rar mov b,a mov a,c rar mov c,a jnc usm4 dad d usm4: xchg dad h xchg jmp usm3 usdiv: mov a,h ;unsigned divide: return (DE / HL) in HL ora l ;return 0 if HL is 0 rz push b call usd1 mov h,b mov l,c pop b ret usd1: mvi b,1 usd2: mov a,h ora a jm usd3 dad h inr b jmp usd2 usd3: xchg usd4: mov a,b lxi b,0 usd5: push psw usd6: call cmphd jc usd7 inx b push d mov a,d cma mov d,a mov a,e cma mov e,a inx d dad d pop d usd7: xra a mov a,d rar mov d,a mov a,e rar mov e,a pop psw dcr a rz push psw mov a,c ral mov c,a mov a,b ral mov b,a jmp usd6 sdiv: xra a ;signed divide: return (DE / HL) in HL sta tmp call tstn xchg call tstn xchg call usdiv jmp smul2 cmphd: mov a,h ;this returns C if HL < DE cmp d ; (unsigned compare only used rc ; within C.CCC, not from C) rnz mov a,l cmp e ret ; ; Shift operators << and >>: ; sderbl: xchg ;shift DE right by L bits shlrbe: inr e ;shift HL right by E bits shrbe2: dcr e rz xra a mov a,h rar mov h,a mov a,l rar mov l,a jmp shrbe2 sdelbl: xchg ;shift DE left by L bits shllbe: inr e ;shift HL left by E bits shlbe2: dcr e rz dad h jmp shlbe2 ; ; Routines to 2's complement HL and DE: ; cmh: mov a,h cma mov h,a mov a,l cma mov l,a inx h ret cmd: mov a,d cma mov d,a mov a,e cma mov e,a inx d ret ; ; The following routines yank a formal parameter value off the stack ; and place it in both HL and A (low byte), assuming the caller ; hasn't done anything to its stack pointer since IT was called. ; ; The mnemonics are "Move Arg #n To HL", ; where arg #1 is the third thing on the stack (where the first ; and second things are, respectively, the return address of the ; routine making the call to here, and the previous return ; address to the routine which actually pushed the args on the ; stack.) Thus, a call to "ma1toh" would return with the first ; passed parameter in HL and A; "ma2toh" would return the second, ; etc. Note that if the caller has pushed [n] items on the stack ; before calling "ma [x] toh", then the [x-n]th formal parameter ; value will be returned, not the [x]th. ; ma1toh: lxi h,4 ;get first arg ma0toh: dad sp mov a,m inx h mov h,m mov l,a ret ma2toh: lxi h,6 ;get 2nd arg jmp ma0toh ma3toh: lxi h,8 ;get 3rd arg jmp ma0toh ma4toh: lxi h,10 ;get 4th arg jmp ma0toh ma5toh: lxi h,12 ;get 5th arg jmp ma0toh ma6toh: lxi h,14 ;get 6th arg jmp ma0toh ma7toh: lxi h,16 ;get 7th arg jmp ma0toh ; ; This routine takes the first 7 args on the stack ; and places them contiguously at the "args" ram area. ; This allows a library routine to make one call to arghak ; and henceforth have all it's args available directly ; through lhld's instead of having to hack the stack as it ; grows and shrinks. Note that arghak should be called as the ; VERY FIRST THING a function does, before even pushing BC. ; arghak: lxi d,args ;destination for block move in DE lxi h,4 ;pass over two return address dad sp ;source for block move in HL push b ;save BC mvi b,14 ;countdown in B arghk2: mov a,m ;copy loop stax d inx h inx d dcr b jnz arghk2 pop b ;restore BC ret ; ; UP TO THIS POINT, ABSOLUTELY NO CHANGES SHOULD EVER BE MADE ; TO THIS SOURCE FILE (except for customizing the EQU statements ; at the beginning of the file). ; ; ; This routine is called first to do argc & argv processing (if ; running under CP/M) and some odds and ends initializations: ; init: pop h ;store return address shld tmp2 ; somewhere safe for the time being IF CPM lxi h,arglst-2 ;set the "argv" that the C main program ENDIF IF NOT CPM lxi h,0 ENDIF push h ; will get. ;Initialize storage allocation pointers: lhld freram ;get address after end of externals shld allocp ;store at allocation pointer (for "sbrk.") lxi h,1000 ;default safety space between stack and shld alocmx ; highest allocatable address in memory ; (for use by "sbrk".). ;Initialize random seed: lxi h,59dch ;let's stick something wierd into the shld rseed ;first 16 bits of the random-number seed ;Initialize I/O hack locations: mvi a,0dbh ;"in" op, for "in xx; ret" subroutine sta iohack mvi a,0d3h ;"out" op for "out xx; ret" subroutine sta iohack+3 mvi a,0c9h ;"ret" for above sobroutines sta iohack+2 ;the port number is filled in by the sta iohack+5 ;"inp" and "outp" library routines. ;Initialize DMA video parameters: IF DMAVIO ;if we're using DMA video routines, lxi h,0cc00h ;set up default values (may be changed shld pbase ;to whatever suits). Video board address, lxi h,16 shld xsize ;# of lines, lxi h,64 shld ysize ;# of columns, lxi h,1024 shld psize ;and total # of characters on screen ENDIF IF CPM ;under CP/M: clear console, process ARGC & ARGV: mvi c,11 ;interrogate console status to see if there call bdos ; happens to be a stray character there... ora a ;(used to be `ani 1'...they tell me this works nop ; better for certain bizarre CP/M-"like" systems) jz initzz mvi c,1 ;if input present, clear it call bdos initzz: lxi h,tbuff ;if arguments given, process them. lxi d,comlin ;get ready to copy command line mov b,m ;first get length of it from loc. base+80h inx h mov a,b ora a ;if no arguments, don't parse for argv jnz initl lxi d,1 ;set argc to 1 in such a case. jmp i5 initl: mov a,m ;ok, there are arguments. parse... stax d ;first copy command line to comlin inx h inx d dcr b jnz initl xra a ;place zero following line stax d lxi h,comlin ;now compute pointers to each arg lxi d,1 ;arg count lxi b,arglst ;where pointers will all go xra a ;clear "in a string" flag sta tmp1 i2: mov a,m ;between args... inx h cpi ' ' jz i2 ora a jz i5 ;if null byte, done with list cpi '"' jnz i2a ;quote? sta tmp1 ;yes. set "in a string" flag jmp i2b i2a: dcx h i2b: mov a,l ;ok, HL is a pointer to the start stax b ;of an arg string. store it. inx b mov a,h stax b inx b inx d ;bump arg count i3: mov a,m inx h ;pass over text of this arg ora a ;if at end, all done jz i5 push b ;if tmp1 set, in a string mov b,a ; (so we have to ignore spaces) lda tmp1 ora a mov a,b pop b jz i3a cpi '"' ;we are in a string. jnz i3 ;check for terminating quote xra a ;if found, reset "in string" flag sta tmp1 dcx h mov m,a ;and stick a zero byte after the string inx h ;and go on to next arg i3a: cpi ' ' ;now find the space between args jnz i3 dcx h ;found it. stick in a zero byte mvi m,0 inx h jmp i2 ;and go on to next arg i5: push d ;all done finding args. Set argc. mvi b,nfcbs ;now initialize all the file info lxi h,fdt ;(just zero the fd table) i6: mvi m,0 inx h dcr b jnz i6 ENDIF IF NOT CPM ;if not under CP/M, force ARGC value lxi h,1 ; of one. push h ENDIF xra a sta ungetl ;clear the push-back byte sta lastc ;and last character byte lhld tmp2 pchl ;all done initializing. ; ; General purpose error value return routine: ; verror: lxi h,-1 ;general error handler...just ret ;returns -1 in HL ; ; Here are file I/O handling routines, only needed under CP/M: ; ; ; Close any open files and reboot: ; vexit: IF CPM ;if under CP/M, close all open files mvi a,7+nfcbs ;start with largest possible fd exit1: push psw ;and scan all fd's for open files call vfgfd ;is file whose fd is in A open? jc exit2 ;if not, go on to next fd mov l,a ;else close the associated file mvi h,0 push h call vclose pop h exit2: pop psw dcr a ;and go on to next one cpi 7 jnz exit1 ENDIF jmp exitad ;done closing; now reboot CP/M or whatever. ; ; Close the file whose fd is 1st arg: ; IF CPM ;here comes a lot of CP/M stuff... vclose: call setdma ;library function just jumps here. call ma1toh ;get fd in A call vfgfd ;see if it is open jc verror ;if not, complain mov a,m ani 4 IF NOT MPM2 ;if not MP/M, and jz close2 ;the file isn't open for write, don't bother to close ENDIF IF MPM2 ;always close all files under MP/M nop nop nop ENDIF push h ;save fd table entry addr call ma2toh ;move arg1 to A push b call vfgfcb ;get the appropriate fcb address xchg ;put it in DE mvi c,16 ;get BDOS function # for close call bdos ;and do it! pop b pop h close2: mvi m,0 ;close logically cpi 255 ;if 255 comes back, we got problems lxi h,0 rnz ;return 0 if OK dcx h ;return -1 on error ret ; ; Determine status of file whose fd is in A...if the file ; is not open, return C flag set, else clear C flag: vfgfd: call setdma mov d,a sui 8 rc ;if fd < 8, error cpi nfcbs cmc ;don't allow too big an fd either rc push d mov e,a ;OK, we have a value in range. Now mvi d,0 ; see if the file is open or not lxi h,fdt dad d mov a,m ani 1 ;bit 0 is high if file is open stc pop d mov a,d rz ;return C set if not open cmc ret ;else reset C and return ; ; Set up a CP/M file control block at HL with the file whose ; simple null-terminated name is pointed to by DE: ; Format for filename must be: "[white space][d:]filename.ext" ; vsetfcb: call setdma ;set up an fcb at HL for filename at DE push b call igwsp ;ignore blanks and tabs mvi b,8 push h inx d ldax d dcx d cpi ':' ;default disk byte value is 0 mvi a,0 ; (for currently logged disk) jnz setf1 ldax d ;oh oh...we have a disk designator call mapuc ;make it upper case sui '@' ;and fudge it a bit inx d inx d setf1: mov m,a inx h call patchnm ;now set filename and pad with blanks ldax d cpi '.' ;and if an extension is given, jnz setfcb2 inx d setfcb2 mvi b,3 ;set the extension and pad with blanks call setnm xra a ;and zero the appropriate fields of the fcb mov m,a lxi d,20 dad d mov m,a inx h jmp setfcb3 ;finish up elsewhere to keep addresses consistent ;with prior releases ; ; This routine copes up to B characters from memory at DE to ; memory at HL and pads with blanks on the right: ; setnm: push b setnm1: ldax d cpi '*' ;wild card? mvi a,'?' ;if so, pad with ? characters jz pad2 setnm2: ldax d call legfc ;next char legal filename char? jc pad ;if not, go pad for total of B characters mov m,a ;else store inx h inx d dcr b jnz setnm1 ;and go for more if B not yet zero pop b setnm3: ldax d ;skip rest of filename if B chars already found call legfc rc inx d jmp setnm3 pad: mvi a,' ' ;pad with B blanks pad2: mov m,a ;pad with B instances of char in A inx h dcr b jnz pad2 pop b ret ; ; Test if char in A is legal character to be in a filename: ; legfc: call mapuc cpi '.' ; '.' is illegal in a filename or extension stc rz cpi ':' ;so is ':' stc rz cpi 7fh ;delete is no good stc rz cpi '!' ;if less than exclamation pt, not legal char ret ;else good enough ; ; Map character in A to upper case if it is lower case: ; mapuc: cpi 'a' rc cpi 'z'+1 rnc sui 32 ;if lower case, map to upper ret ; ; Ignore blanks and tabs at text pointed to by DE: ; igwsp: dcx d igwsp1: inx d ldax d cpi ' ' jz igwsp1 cpi 9 jz igwsp1 ret ; ; This routine does one of two things, depending ; on the value passed in A. ; ; If A is zero, then it finds a free file slot ; (if possible), else returns C set. ; ; If A is non-zero, then it returns the address ; of the fcb corresponding to an open file whose ; fd happens to be the value in A, or C set if there ; is no file associated with fd. ; vfgfcb: push b call setdma ora a ;look for free slot? mov c,a jnz fgfc2 ;if not, go away mvi b,nfcbs ;yes. do it... lxi d,fdt lxi h,fcbt mvi c,8 fgfc1: ldax d ani 1 mov a,c jnz fgfc1a ;found free slot? pop b ;yes. all done. ret fgfc1a: push d lxi d,36 ;fcb length to accommodate random I/O dad d pop d inx d inr c dcr b jnz fgfc1 fgfc1b: stc pop b ret ;return C if no more free slots fgfc2: call vfgfd ;compute fcb address for fd in A: jc fgfc1b ;return C if file isn't open sui 8 mov l,a ;put (fd-8) in HL mvi h,0 dad h ;double it dad h ;4*a mov d,h ;save 4*a in DE mov e,l dad h ;8*a dad h ;16*a dad h ;32*a dad d ;36*a xchg ;put 36*a in DE lxi h,fcbt ;add to base of table dad d ;result in HL mov a,c ;and return original fd in A pop b ret setdma: push d ;just a preventative measure, push b ;since the default I/O buffer push psw ;tends to magically change push h ;around by itself when left mvi c,26 ;in CP/M's hands !! lxi d,tbuff call bdos pop h pop psw pop b pop d ret ENDIF ;end of CP/M-related file I/O routines IF NOT CPM main: equ $ ;where main program resides when not under CP/M ;(under CP/M, the data area comes first) ENDIF ; ; Ram area: ; IF NOT CPM ;if not under CP/M, use custom ram area address org ram ENDIF room: ds 30 ;room for random stuff pbase: ds 2 ;screen-DMA address ysize: ds 2 ;screen width xsize: ds 2 ;screen height psize: ds 2 ;screen length rseed: ds 8 ;the random generator seed args: ds 14 ;"arghak" puts args passed on stack here. iohack: ds 6 ;room for I/O subroutines for use by "inp" ;and "outp" library routines allocp: ds 2 ;pointer to free storage for use by "sbrk" func alocmx: ds 2 ;highest location to be made available to the ;storage allocator tmp: equ room ;this is misc. garbage space tmp1: equ room+1 tmp2: equ room+2 tmp2a: equ room+4 ungetl: equ room+6 ;where characters are "ungotten" lastc: equ room+7 ;last char typed ; ;-------------------------------------------------------------------------- ; The following data areas are needed only if running under CP/M: ; IF CPM ; ; The fcb table (fcbt): 36 bytes per file control block ; fcbt: ds 36*nfcbs ;reserve room for fcb's (extra byte for IMDOS) ; ; The fd table: one byte per file specifying r/w/open as follows: ; bit 0 is high if open, low if closed ; bit 1 is high if open for read ; bit 2 is high if open for write ; (both b1 and b2 may be high) ; fdt: ds nfcbs ;one byte per fcb tells if it is active, r/w, etc. ; ; The command line is copied here by init: ; comlin: ds 131 ;copy of the command line pointed to by entries ;in arglst ; ; This is where "init" places the array of argument pointers: ; arglst: ds 60 ;the "argv" paramater points here (well, ;actually to 2 bytes before arglst). Thus, ;up to 30 parameters may be passed to "main" ENDIF ;(enough for you, Andy?) ; ; End of CP/M-only data area ;--------------------------------------------------------------------------- IF CPM main: equ $ ;where "main" program will be loaded under CP/M ENDIF end