; ======================================================= ; ; REC module for the operators and predicates pertaining ; to the pushdown list, other than the most important ; ones already contained in the REC nucleus. These ; comprise: ; ; comparison ; ; = equality ; ; modification of arguments ; ; H hex ASCII string to binary ; [exclm] binary to hex ASCII string ; & exchange top arguments ; | concatinate top arguments ; ; block movements ; ; G fetch a block from memory ; g address fetch ; r replace address by contents ; u incrementing byte fetch ; y incrementing word fetch ; P put buffer block in memory ; S store block in memory ; s store into buffer ; v incrementing byte store ; m move arg to end of PDL space ; n recover arg from end of PDL ; h store/restore machine state ; ; generate pointers ; ; c reserve block, generate pointer ; p put px, py-px on PDL ; l put pz on PDL ; $ form addr of variable cell ; ; ------------------------------------------------------- ; Version of REC released during the summer school, 1980 ; ------------------------------------------------------- ; 8086 version with separate segments for code, PDL, WS. ; ------------------------------------------------------- ; ; PDL86 - Copyright (C) 1982, 1984 ; Universidad Autonoma de Puebla ; All Rights Reserved ; ; [Harold V. McIntosh, 25 April 1982] ; [Gerardo Cisneros, 8 February 1984] ; ; May 29, 1983 - & exchanges arbitrary arguments ; May 29, 1983 - ~ discontinued; use m&n instead ; May 29, 1983 - ~ Complement or Negate top element ; May 29, 1983 - N for Numerical comparison on PDL ; July 7, 1983 - $ with char arg gets subroutine addr ; January 23, 1984 - at comp:, is ; February 8, 1984 - separate segments (GCS) ; May 9, 1984 - h stores/restores machine state (GCS) ; May 22, 1984 - arithmetic operators moved to ATH and FLT (GCS) ; May 23, 1984 - H and exclm handle any length (GCS) ; May 31, 1984 - PDL becomes 1st module of REC86F.CMD -GCS ; 18 June 1984 - n quits on empty PDL complement - GCS ; 3 July 1984 - entry points for pG, $S and $r - GCS ; 15 Aug 1984 - E.P.s for nL and &S - GCS ; ======================================================= ; ============= org 0000H ; ============= jmp MAIN ;<===============================<<< ; ======================================================= ; A collection of subroutines for two-byte arithmetic, ; including loading and storage of the 8080 registers ; from the pushdown list. ; ======================================================= ; ------------------------------------------------------- ; Load and store subroutines for 2-byte arithmetic. ; ------------------------------------------------------- ; Push a two-byte value onto the PDL. The value to be ; pushed should be placed on the 8080's stack before ; calling PUTW. PUTW: mov cx,2 ;two bytes are required call NARG ;close old variable, reserve space mov bp,sp mov ax,2[bp] mov [bx],ax ;store low order byte inc bx ;on to high order destination inc bx ;always leave pointer in good condition mov PY,bx ;close top argument ret 2 ; (&) Exchange top two arguments, assumed two-byte. EXCH: mov ax,PY mov bx,PX ;org1 sub ax,bx ;siz1 mov dx,-2[bx] ;org2 lea cx,-2[bx] sub cx,dx ;siz2 cmp ax,cx jnz XTNE jcxz XTRE XTEQ: mov al,[bx] xchg bx,dx mov ah,[bx] mov [bx],al xchg bx,dx mov [bx],ah inc bx inc dx loop XTEQ XTRE: ret XTNE: push cx push dx push bx push ax push dx call NARG mov ax,ds mov es,ax cld mov di,bx pop si repnz movs byte [di],[si] pop cx pop si pop di push di repnz movs byte [di],[si] pop word ptr [di] lea di,2[di] mov si,PX mov PX,di pop cx repnz movs byte [di],[si] mov PY,di ret ; Load top two arguments into the machine's stack. In ; reality so many permutations exist for places to put ; the arguments as they are taken off the REC stack that ; they are simply transferred to the 8080 stack, to be ; popped into the desired registers on return from the ; corresponding call. It is assumed that all quantities ; involved in these transactions are of two bytes. A ; sequence of entry points is provided so as to pop off ; one or two arguments. TWOL: mov bx,PX pop bp ;entry for two args push word ptr [bx] push bp call UCL ;pop argument, put px in (bx) ONEL: pop bp ;continue, or entry for one argument push word ptr [bx] push bp jmp UCL ;pop the last argument, quit ; ------------------------------------------------------- ; Conversion between binary and hexadecimal ASCII strings ; ------------------------------------------------------- ; Return if not hexadecimal. A unchanged if not hex, else ; reduced to binary. RNH: cmp al,'G' ;no hex characters beyond F jnb RH2 cmp al,'A' ;hex letters equal A or beyond jb RH1 sub al,'7' ;compensate the gap between 9 and A ret RH1: jmp RND RH2: inc sp inc sp ret ; (H) Convert a hex ASCII string on the PDL into binary. ; If the length of the string is n, the result will have ; int((n+1)/2) bytes, stored in Intel form: the least ; significant byte in the lowest addressed location. HE: mov cx,PY ;compute length of string mov si,PX ;while saving pointers sub cx,si jz H4 ;leave null strings alone mov bp,cx ;save byte count cld H0: lods byte ptr [si] ;check that all characters call RNH ;are hex digits, return false if not loop H0 mov cx,bp inc cx ;compute length of final number shr cx,1 mov si,PX ;reload PX to process the hex string mov bp,si ;copy of PX to be used later mov bx,si ;another copy as pointer for result mov di,cx ;copy of byte count to be used later mov dl,0 jnc H2 ;start in the middle if length odd H1: lods byte ptr [si] call RNH ;reduce to binary shl al,1 ;multiply by 16 shl al,1 shl al,1 shl al,1 mov dl,al ;save it while getting next nibble H2: lods byte ptr [si] call RNH or al,dl ;put nibbles together mov [bx],al ;and store on PDL inc bx ;keep pointing to the next byte loop H1 mov PY,bx ;update PY call HXC ;turn the string around H4: jmp SKP ;return TRUE ; Turn around string of (DI) bytes starting at (BP) and ; ending at (BX)-1 HXC: dec bx ;highest byte mov cx,di ;total byte count shr cx,1 ;half of number of bytes jcxz H5 H3: mov al,[bx] xchg ds:[bp],al mov [bx],al dec bx inc bp loop H3 H5: ret ; ([exclm]) Convert an n-byte binary number into an ASCII ; string of length 2n. The high order byte is assumed to be ; in the highest-addressed location. HX: mov cx,PY ;compute lenght of number mov si,cx mov bp,PX ;while saving pointers in other sub cx,bp ;registers jnz HX0 ret ;leave null strings alone HX0: mov di,cx shl cx,1 ;twice as many bytes will be made call OARG ;verify availability of space mov PY,bx ;update PY right away mov cx,di ;restore old count std ;conversion proceeds backwards dec si ;last byte is one below original PY HX1: lods byte ptr [si] ;get the byte mov ah,al ;make a copy call HSA ;produce digit from high nibble mov al,ah ;back to AL call HSB ;produce digit from low nibble loop HX1 shl di,1 ;prepare to turn string around mov bx,PY jmps HXC HSA: ror al,1 ;shift byte right four bits ror al,1 ; ror al,1 ; ror al,1 ; HSB: and al,0FH ;mask in right nibble add al,90H ;prepare for some carries from daa ;create gap if nibble beyond 10 adc al,40H ;code for @ if we have a letter daa ;decide 3 for digit, 4 for letter dec bx ;get pointer ready for deposit mov [bx],al ;record the ASCII digit ret ; ------------------------------------------------------- ; Fetch and store bytes, addresses, and blocks to and fro ; between the PDL and the memory. The following chart ; shows the relation between all the different operators ; which are available. ; ; byte word block ; ---- ---- ----- ; ; replace - r G ; fetch, nonincrement g - - ; fetch, increment u y - ; ; store - - S ; store, increment - - v ; store w.r.t. limit - - s ; store into buffer - - P ; ; variable head cell - $ - ; ; The main operators for saving and fetching variables ; are G and S. The remainder were especially chosen ; on the one hand to scrutinize the memory under REC ; control, and on the other to give the widest possible ; latitude in defining variables in applications of REC. ; ; The following chart shows how to employ variables: ; ; 'data' [var#] $ S define 2-byte variable ; [var#] $ r fetch 2-byte variable ; 'data' ml [var#] $ S save fixed variable ; [var#] $ ryG fetch fixed variable ; 'data' [var#] $rs redefine existing fixed var ; kc Lml [var#] $ S create k-byte buffered variable ; kc [var#] $ S alternative k-byte buffered var ; 'data' [var#] $r P redefine buffered variable ; [var#] $ ryLyG fetch buffered variable ; ; Memory can be examined bytewise with the following ; combinations: ; ; org g fetch a byte, keep origin ; org u autoincrementing byte fetch ; org v autoincrementing byte store ; org (g ... v:;) read, modify, store, ready next ; o1 o2 (u~...v&:;) move from o1 to o2 ; ; ------------------------------------------------------- ; (g) (u) Fetch a byte from memory and leave on PDL. The ; sequence leaves on PDL. ; The sequence leaves on ; PDL. GB: mov bx,PX ;/g/ pointer to top argument push word ptr [bx] ;fetch low byte of origin jmp GBJ ;if the origin is not to be incremented GBI: mov bx,PX ;/u/ pointer to arg, which is org push word ptr [bx] ;fetch low byte of origin inc word ptr [bx] GBJ: call ESLD ;get segment address mov cx,1 ;require space for one byte call NARG ;close old arg, check space, open new pop dx ;here's the origin we saved xchg bx,dx mov al,es:[bx] xchg bx,dx ;fetch the byte there mov [bx],al ;store on the PDL inc bx ;pointer always ready for next byte mov PY,bx ;right deliniter of argument ret ; (y) Fetch two bytes from memory and leave on PDL. ; The sequence leaves ; on PDL. GW: mov bx,PX ;/ / pointer to the argument push word ptr [bx] ;low byte of origin jmp GWJ ;common continuation of gw, gwi GWI: mov bx,PX ;/y/ pointer to the argument push word ptr [bx] ;place low byte in A add word ptr [bx],2 ;origin to be incremented by 2 GWJ: call ESLD ;get segment address mov cx,2 ;require space for two bytes call NARG ;close old arg, check space, open new pop dx ;now we're ready for that origin xchg bx,dx mov ax,es:[bx] xchg bx,dx ;fetch the byte sitting there mov [bx],ax ;and store it on PDL inc bx inc bx ;keep the pointer moving along mov PY,bx ;value's finished, store its end ret ; (G) Fetch a block from memory, leave on PDL. ; leaves (org, ...) on PDL. GA: call CXLD ;load siz into (cx) call OARG ;reuse the argument, but with siz bytes call ESLD ;get segment base mov si,[bx] ;pick up source address too cld mov di,bx mov bp,ds mov ax,es mov ds,ax mov es,bp repnz movs byte [di],[si] mov ds,bp ;restore data segment mov PY,di ;(bx) holds the destination terminator ret ; (pG) Make a copy of the last argument, a combination ; which is compiled as a single operator DUP: mov cx,PY ;compute size of current top sub cx,PX call NARG ;check availability of space mov ax,ds mov es,ax ;prepare for move cld mov si,dx ;old PX is in DX mov di,bx ;new PX is in BX repnz movs byte [di],[si] ;count still in CX mov PY,di ;note new argument end ret ; (S) Store a block forward from the designated memory ; location. <'data' org S> stores 'data' starting at ; org; leaves no residue on the PDL. XSTO: call EXCH ;entry point for &S jmps SA VSTO: call VBLE ;entry pt for combination $S SA: call CXLD ;fetch destination origin mov di,cx ;save it for a while mov si,PX mov cx,PY sub cx,si cld repnz movs byte [di],[si] jmp UCL ;pop the second argument too ; (v) Store a block, leaving incremented address. ; leaves org+size['data'] on PDL, stores ; 'data' starting from org. SAI: mov si,PX mov cx,PY sub cx,si ;determine length of data call UCL ;pop top argument, exposing second call ESLD ;get segment address mov di,[bx] ;(bx) has px, which is destn address mov ax,si add ax,cx cmp di,ax jb LVB cld repnz movs byte [di],[si] mov [bx],di ret LVB: std add si,cx add di,cx mov [bx],di dec si dec di repnz movs byte [di],[si] ret ; (s) Store into an area of limited size. The sequence ; <'data' org s> will store 'data' beginning at org+2, ; supposing that siz('data') is less than or equal to ; (org, org+1). In either event no residue is left, but ; an error notation is generated if the data doesn't fit. ; No data at all is stored if all will not fit. If it ; matters to know how much of the space was used, the ; operator P should probably be used instead. LCS: call CXLD ;fetch destination origin mov bx,cx ;save it while calling psiz mov si,PX mov cx,PY sub cx,si ;determine length of data mov ax,es:[bx] ;low byte of capacity cmp ax,cx jnb LST call UCL call RER ;note error, return if it won't fit LST: cld inc bx inc bx mov di,bx repnz movs byte [di],[si] jmp UCL ;pop second argument ; (P) Store into a buffer and note length. Used to ; store data of variable length into an area whose ; maximum length is fixed. The buffer has the form ; ; /available/used/data/data/.../data/.../end/ ; ; The sequence <'data' org P> will store the data ; in the buffer beginning at org. (org, org+1) holds ; the maximum length of data that may be stored in the ; buffer, (org+2, org+3) is siz('data'), and 'data' is ; stored from org+4 onward if it will fit. If it will ; not, P is a noop and error is set. UCP: call CXLD ;pointer to destination mov bx,cx ;save destination while calling psiz mov si,PX mov cx,PY sub cx,si ;load (cx) with length of data inc cx ;data has to appear two bytes larger inc cx ;to include cell showing its size mov ax,es:[bx] ;low byte of destination capacity inc bx ; inc bx ; cmp ax,cx jnb UP1 call RER ;capacity exceeded: mark error, return UP1: dec cx ;we want to store the true size dec cx ;subtract out the two byte margin mov es:[bx],cx ;low byte into usage cell inc bx ;just keep moving along inc bx ;ready to start moving data cld mov di,bx mov ax,ds mov es,ax repnz movs byte [di],[si] jmp UCL ;lift second argument, leave nothing ; (r) Replace address on top of pdl by its contents. VREP: call VBLE ;entry point for combination $r IND: call ESLD ;get segment address mov dx,[bx] ;load word xchg bx,dx ;(bx) now has top argument mov ax,es:[bx] ;indirect address xchg bx,dx ;address of top argument again mov [bx],ax ;store low indirect byte add bx,2 ;set PY in case old arg had 4 bytes mov PY,bx ret ; ($) Generate the address of the nth cell in the array ; of variables, which is a block of four-byte addresses. ; These cells may be used to store data directly - for ; example counters or addresses - or indirectly through ; pointers to the actual location of the data. By giving ; a one-byte character argument, <'x'$>, the location where ; the address of subroutine x is stored may be obtained. VBLE: mov bx,PX ;pointer to argument mov cx,PY sub cx,bx cmp cx,2 jz VBLF mov cx,2 call OARG ;reuse old arg with size 2 mov bx,PX mov al,[bx] mov ah,0 jmp VBLG VBLF: mov ax,[bx] VBLG: add ax,ax add ax,ax add ax,VRT mov [bx],ax add bx,2 mov PY,bx ret ; (l) Load pz onto PDL. LCL: push PZ ;putw requires arg on 8080 stack call PUTW ;record two-byte argument ret ;can't use simply ; (m) Set aside top argument on PDL. It is moved to the ; other end of the array reserved for the PDL, which can ; be used as a temporary storage stack without name. The ; mechanism by which pz is moved and the block size is ; recorded makes this an attractive mechanism to create ; storage space for REC variables. LCM: mov si,PY mov cx,si sub cx,PX ;get length of top argument push cx call UCL ;pop top argument mov di,PZ ;load destination origin std dec si dec di mov ax,ds mov es,ax repnz movs byte [di],[si] lea bx,-1[di] mov PZ,bx pop word ptr [bx] ;recover length ret ; (n) Recover segment which was set aside. LCN: mov cx,ZE ;there won't be any net length change call NARG ;close old argument, ready for new mov di,bx ;place destination origin in (dx) mov PY,di ;leave null string in case of error mov bx,PZ ;place source origin in (bx) mov cx,[bx] ;place length in cx cmp cx,0FFFFH ;see whether PDL complement is empty jnz lcn1 call RER ;it is, quit after recording error lcn1: lea si,2[bx] cld mov ax,ds mov es,ax repnz movs byte [di],[si] mov PY,di ;end of destination is end of argument mov PZ,si ;update pz ret ; (nL) Lift from PDL complement ENLF: mov bx,PZ ;place source origin in (bx) mov cx,[bx] ;place length in cx cmp cx,0FFFFH ;check for top of PDL flag jnz enlf1 call RER ;quit if PDL complement empty enlf1: add bx,cx ;compute start of next compl. arg. inc bx inc bx mov PZ,bx ;store new upper limit of PDL ret ; (|) Concatinate the top arguments on the PDL. CONC: mov si,PX mov cx,PY sub cx,si ;get length of top argument call UCL ;pop top argument, set up pntrs to next mov di,dx ;new py is destination cld mov ax,ds mov es,ax repnz movs byte [di],[si] mov PY,di ;record new terminal address ret ; (p) Put px and siz on the pushdown list. GXS: mov dx,PX mov bx,PY mov cx,bx sub cx,dx ;calculate length of top argument push cx ;put length on 8080 stack push dx ;put origin on 8080 stack call PUTW ;put top of 8080 stack on REC PDL call PUTW ;put the next item there too ret ;can't combine into ; (c) Reserve a block on the pushdown list. creates ; a block of length n, and puts n-2 at the front of the ; block as a size indicator. Then, if n .ge. 2, it will ; be there as a length indicator for a buffer. <=====maybe change this? BLOK: mov bx,PX ;pointer to argument mov cx,[bx] ;fetch the argument mov [bx],cx ;store header sub word ptr [bx],2 call OARG ;is there enough space to reuse arg? mov PY,bx ;increment in (bx), it goes into py push PX ;px has origin of block just formed call PUTW ;record block origin as new argument ret ;can't replace by jmp ; (h) Save the state of the machine and leave the SP value on ; the PDL, if arg is null; otherwise restore the state of the ; machine from values at the stack pointed to by the address ; on the PDL. MST: pop rtaddr ;put return address aside pushf ;Flags to the stack push ax ;Accumulator to stack push bx ;BX to stack push cx ;CX to stack push dx ;DX to stack push bp ;Base pointer to stack push si ;Source index to stack push di ;Destination index to stack push ds ;Data segment to stack push es ;Extra segment to stack mov ax,PY ;compute argument length sub ax,PX ;and lift top call UCL ;before pushing pointers ; push PX ;PDL pointers ; push PY ; push PZ ; push P0 ;WS pointers ; push P1 ; push P2 ; push P3 ; push P4 ; push WSEG test ax,ax jnz mrst ;restore if arg nonnull mov cx,4 ;need 4 bytes for SS and SP call NARG ;A ROYAL MESS WILL ENSUE IF NARG FAILS mov [bx],ss ;record stack segment value mov 2[bx],sp ;record stack pointer value add bx,cx ;compute new PY mov PY,bx ;and update it jmps mrst2 mrst: xchg bx,dx ;get previous PY back in BX cmp al,2 ;see if top arg has size 2 jnz mrst1 call UCL ;yes, clean up stack xchg dx,bx ;get PY back into BX mov ss,2[bx] ;restoring pointers should be below mov sp,4[bx] ;the 2-byte argument add sp,20 ;get rid of stored stuff (10 regs) ; add sp,38 ;get rid of stored stuff (10 regs + 9 ptrs) jmps mrst2 mrst1: mov ss,2[bx] ;fetch SS from the PDL mov sp,4[bx] ;SP too, now it points to previous store ; pop WSEG ;so we can pop everything ; pop P4 ;we pushed in reverse ; pop P3 ; pop P2 ; pop P1 ; pop P0 ; pop PZ ; pop PY ; pop PX pop es pop ds pop di pop si pop bp pop dx pop cx pop bx pop ax popf mrst2: push rtaddr ;restore return address ret ;and exit ; Load a single variable into (cx) from the pushdown ; list. No register is sure to be preserved. CXLD: mov bx,PX ;pointer to argument mov cx,[bx] ;fetch low order byte call ESLD ;get segment address jmp UCL ;erase argument [(cx) is unchanged] ; Load register pair (dx) from the pushdown list. ; (cx) will be preserved, (bx) not. DXLD: mov bx,PX ;pointer to argument push word ptr [bx] ;fetch word call UCL ;erase argument pop dx ;restore (dx) since UCL modified it ret ; (=) Test the two top arguments on the pushdown list ; for equality. The arguments may be of any length, but ; will be equal only when of the same length and composed ; of the same sequence of bytes. The top argument will be ; popped whatever the outcome, but when equality is true ; both will be popped. EQL: mov di,PX ;under argument mov cx,PY sub cx,di ;obtain length of top argument call UCL ;lift top argument mov si,PX mov bx,PY sub bx,si cmp bx,cx ;compare lengths jnz EQF cld mov ax,ds mov es,ax repz cmps byte [di],[si] jnz EQF jmp CUCL ;both agree, erase second arg, TRUE EQF: ret ;disagree so FALSE ; Load (es) with (ds) if PDL argument length is 2 bytes ; Load!(es) from upper two bytes if length is 4 bytes ESLD: mov ax,PY mov bx,PX sub ax,bx cmp ax,2 jnz esl1 mov ax,ds ;two-byte argument mov es,ax ret esl1: cmp ax,4 jnz esl2 mov ax,2[bx] ;four-byte argument mov es,ax ret esl2: call RR1 ;error record with one lift ; ------------------------------------------------------- ; ; Some of the service routines which are likely to be ; external references in other modules are: ; ; puon push one byte on PDL ; putw push address on PDL ; thrl load three arguments onto 8080 stack ; twol load two arguments onto 8080 stack ; onel load one argument onto 8080 stack ; bcld load (cx) from PDL, pop PDL ; deld load (dx) from PDL, pop PDL ; ; ------------------------------------------------------- END