    NAME CpQueue

; This is cp.queue.asm~text~
; This contains all the queueing routines

$NOLIST
$INCLUDE (cp.constant.asm.inc~text~)
$LIST

DGROUP GROUP DATA
CGROUP GROUP CODE

    PUBLIC OsNewQElement, OsInitQcb, OsElementcheckSum, OsVerifyCheckSum
    PUBLIC OsInsertIntoQ, OsRemoveFromQ, OsReplaceInQ
    PUBLIC OsSearchInQ, OsElementInQ

    EXTRN IntAllocate:FAR, MakeCheckSum:FAR, CompareChars:FAR
    EXTRN Exception:FAR

DATA SEGMENT PUBLIC 'DATA'
DATA ENDS

CODE SEGMENT PUBLIC 'CODE'
     ASSUME CS:CGROUP, DS:DGROUP

$EJ

;    OsNewQElement : PROCEDURE (pQcb) EidType
;        DCL pQcb PTR;
;        DCL error WORD;
;
; This will allocate a new queue element using
; the length stored in the Qcb.  This uses IntAllocate
; and uses the system pid.

error EQU WORD PTR [BP-2]        ; first local
pQcb EQU DWORD PTR [BP+6]        ; first parameter

OsNewQElement PROC FAR
    PUSH BP
    MOV BP,SP
    SUB SP, 2                    ; 1 local word

    MOV AX, systemPid            ; pid to use
    PUSH AX

    LES BX,pQcb                  ; length to allocate
    PUSH ES:[BX].elementLength

    LEA AX,error 
    PUSH SS                      ; pError
    PUSH AX

    CALL IntAllocate

    MOV AX, nullWord
    CMP error, eOK               ; if error <> eOK THEN
    JNE OsNewQDone               ;     null

    MOV AX, ES                   ; else return selector from IntAllocate

OsNewQDone:
    MOV SP,BP
    POP BP
    RET 4
OsNewQElement ENDP

PURGE error, pQcb

$EJ

;    OsInitQcb : PROCEDURE (pQcb, usesChecksum, length)
;        DCL pQcb PTR;
;        DCL usesCheckSum BOOLEAN;
;        DCL length WORD;
;
; This will initialize the fields of a Qcb

len EQU WORD PTR [BP+6]                ; third param
usesCheck EQU BYTE PTR [BP+8]          ; second param
pQcb EQU DWORD PTR [BP+10]             ; first param

OsInitQcb PROC FAR
    PUSH BP
    MOV BP,SP

    MOV AX, nullWord
    LES BX, pQcb

    MOV ES:[BX].headOfQ, AX
    MOV ES:[BX].tailOfQ, AX
    MOV AL, usesCheck
    MOV ES:[BX].usescheckSum, AL
    MOV AX,len
    MOV ES:[BX].elementLength, AX
    MOV ES:[BX].qcbCount, 0

    MOV SP, BP
    POP BP
    RET 8
OsInitQcb ENDP

PURGE len, usesCheck, pQcb

$EJ

;    OsElementChecksum : PROCEDURE (pQcb, eid) WORD
;        DCL pQcb PTR;
;        DCL eid SELECTOR
;
; This will return the checksum of the given
; element in the queue

eid EQU WORD PTR [BP+6]           ; second param
pQcb EQU DWORD PTR [BP+8]         ; first param

OsElementChecksum PROC FAR
    PUSH BP
    MOV BP,SP

    MOV ES, eid                   ; get selector

    PUSH ES                       ; pointer to element.next
    MOV AX, nextOffset
    PUSH AX

    LES BX, pQcb 
    MOV AX, ES:[BX].elementLength
    SUB AX,2                      ; get length -2
    PUSH AX

    CALL MakeCheckSum             ; AX contains checksum

    MOV SP,BP
    POP BP
    RET 6
OsElementChecksum ENDP

PURGE eid, pQcb

$EJ

;    OsVerifyChecksum : PROCEDURE (pQcb, eid) BOOLEAN;
;        DCL pQcb PTR;
;        DCL eidt SELECTOR;
;
; This will return a boolean indicating if the checksum
; of an element is okay

eid EQU WORD PTR [BP+6]           ; second param
pQcb EQU DWORD PTR [BP+8]         ; first param

OsVerifyChecksum PROC FAR
    PUSH BP
    MOV BP,SP

    LES AX, pQcb
    PUSH ES                       ; pQcb
    PUSH AX

    PUSH eid                      ; eid

    CALL OsElementChecksum        ; AX = checksum

    MOV ES, eid 
    CMP AX, ES:checkSum           ; equal to stored checksum
    MOV AL, TRUE
    JE OsVerifyDone

    MOV AL, FALSE

OsVerifyDone:
    MOV SP,BP
    POP BP
    RET 6
OsVerifyChecksum ENDP

PURGE eid, pQcb

$EJ

;    ReplaceOp : PROCEDURE (pQcb, prevEid, newEid, nextEid);
;        DCL pQcb PTR;
;        DCL (prevEid, newEid, nextEid) EidType;
;
;    This general routine handles adding, replacing, and
;    remove from queues.  If newEid = nullWord then the operation
;    is really a delete, otherwise it is an insert or replace

nextEid EQU WORD PTR [BP+6]        ; fourth param
newEid EQU WORD PTR [BP+8]         ; third param
prevEid EQU WORD PTR [BP+10]       ; second param
pQcb EQU DWORD PTR [BP+12]         ; first param

len EQU WORD PTR [BP-2]            ; local variable

ReplaceOp PROC NEAR
    PUSH DS                        ; this saves ds and makes offsets of params
                                   ; look as if it was a long call
    PUSH BP
    MOV BP, SP
    SUB SP, 2                      ; 1 local word

    LES BX, pQcb                   ; initial pointer (ES:BX) to Qcb

    CMP newEid, nullWord           ; is this a delete ?
    JNE ReplaceOther               ; if not then jump

    CMP prevEid, nullWord          ; IF (prevEid = nullWord) AND
    JNE Replace10
    CMP nextEid, nullWord          ;    (nextEid = nullWord) THEN DO;
    JNE Replace10

    MOV ES:[BX].headOfQ, nullWord  ;    head and tail := nullWord
    MOV ES:[BX].tailOfQ, nullWord
    JMP ReplaceCont

Replace10:
    CMP prevEid, nullWord          ; IF (prevEid = nullWord) THEN DO;
    JNE Replace20

    MOV AX, nextEid
    MOV ES:[BX].headOfQ, AX        ;     head := nextEid
    MOV DS, nextEid
    MOV DS:prev, nullWord          ;     next.prev := nullWord
    JMP SHORT ReplaceCont

Replace20:
    CMP nextEid, nullWord          ; IF (nextEid = nullWord THEN DO;
    JNE Replace30

    MOV AX, prevEid
    MOV ES:[BX].tailOfQ, AX        ;     tail := prevEid
    MOV DS, prevEid
    MOV DS:next, nullWord          ;     next.prev := nullWord
    JMP SHORT ReplaceCont

Replace30:
    MOV DS, prevEid
    MOV AX, nextEid
    MOV DS:next, AX                ; prev.next := nextEid
    MOV DS, AX
    MOV AX, prevEid
    MOV DS:prev, AX                ; next.prev := prevEid
    JMP SHORT ReplaceCont

ReplaceOther:
    MOV DS, newEid
    MOV AX, prevEid
    MOV DS:prev, AX                ; new.prev := prevEid
    MOV AX, nextEid
    MOV DS:next, AX                ; new.next := nextEid

    MOV AX, newEid
    CMP prevEid, nullWord          ; IF prevEid <> nullWord THEN
    JE Replace40

    MOV DS, prevEid
    MOV DS:next, AX                ;     prev.next := newEid
    JMP SHORT Replace50

Replace40:                         ; ELSE
    MOV ES:[BX].headOfQ, AX        ;     headOfQ := newEid

Replace50:
;   MOV AX, newEid                 ; AX already = newEid10
    CMP nextEid, nullWord          ; IF nextEid <> nullWord THEN
    JE Replace60

    MOV DS, nextEid
    MOV DS:prev, AX                ;     next.prev := newEid
    JMP SHORT Replace70

Replace60:                         ; ELSE
    MOV ES:[BX].tailOfQ, AX        ;     tailOfQ := nweEid

Replace70:
ReplaceCont:
    MOV AL, BYTE PTR ES:[BX].usesCheckSum
    RCR AL, 1                      ; IF NOT(usesCheckSum) THEN RETURN
    JNC ReplaceDone

    MOV AX,WORD PTR ES:[BX].elementLength 
    SUB AX, 2                      ; length := Qcb.length - 2;
    MOV len, AX

    CMP prevEid, nullWord        ; IF prevEid <> nullWord THEN DO;
    JE Replace80

    PUSH prevEid
    MOV AX, nextOffset             ; @prev.next
    PUSH AX
    
    PUSH len                       ; length
    
    CALL MakeCheckSum              ; AX := CheckSum

    MOV ES, prevEid
    MOV ES:checkSum, AX            ; prev.checkSum := AX
     
Replace80:
    CMP newEid, nullWord           ; IF newEid <> nullWord THEN DO;
    JE Replace90

    PUSH newEid
    MOV AX, nextOffset             ; @new.next
    PUSH AX
    
    PUSH len                       ; length
    
    CALL MakeCheckSum              ; AX := CheckSum

    MOV ES, newEid
    MOV ES:checkSum, AX            ; new.checkSum := AX
     
Replace90:
    CMP nextEid, nullWord          ; IF nextEid <> nullWord THEN DO;
    JE ReplaceDone

    PUSH nextEid
    MOV AX, nextOffset             ; next.next
    PUSH AX
    
    PUSH len                       ; length
    
    CALL MakeCheckSum              ; AX := CheckSum

    MOV ES, nextEid
    MOV ES:checkSum, AX            ; next.checkSum := AX
     
ReplaceDone:
    MOV SP, BP
    POP BP
    POP DS                         ; restore DS
    RET 10
ReplaceOp ENDP

PURGE len, nextEid, newEid, prevEid, pQcb

$EJ

;    OsInsertIntoQ : PROCEDURE (pQcb, curEid, newEid)
;        DCL pQcb PTR;
;        DCL (curEid, newEid) SELECTOR;
;
; This will insert the newEid element after curEid
; If curEid = nullWord then it will be put at the
; head of the queue.

newEid EQU WORD PTR [BP+6]        ; third param
curEid EQU WORD PTR [BP+8]        ; second param
pQcb EQU DWORD PTR [BP+10]        ; first param

OsInsertIntoQ PROC FAR
    PUSH BP
    MOV BP,SP

    CMP curEid, nullWord          ; curEid = nullWord ?
    JE OsInsertNull

    MOV ES, curEid
    MOV DX, ES:next               ; DX = nextEid := next
    JMP SHORT OsInsertCont

OsInsertNull:
    LES BX, pQcb
    MOV DX, ES:[BX].headOfQ       ; DX = nextEid := headOfQ

OsInsertCont:

    LES AX, pQcb
    PUSH ES                       ; pQcb
    PUSH AX
    
    PUSH curEid                   ; curEid
    
    PUSH newEid                   ; newEid
    
    PUSH DX                       ; next Eid
    
    CALL ReplaceOp
    
    LES BX, pQcb
    INC ES:[BX].qcbCount             ; increment count
    
    MOV SP,BP
    POP BP
    RET 8
OsInsertIntoQ ENDP

PURGE newEid, curEid, pQcb

$EJ          

;    OsRemoveFromQ : PROCEDURE (pQcb, curEid) CLEAN;
;        DCL pQcb PTR;
;        DCL curEid EidType;
;
;    This will remove the element from the specified q

curEid EQU WORD PTR [BP+6]       ; second param
pQcb EQU DWORD PTR [BP+8]        ; first parameter

OsRemoveFromQ PROC FAR
    PUSH BP
    MOV BP,SP

    LES AX, pQcb                 ; pQcb
    PUSH ES
    PUSH AX

    MOV ES, curEid
    PUSH ES:prev                 ; cur.prev

    MOV AX, nullWord
    PUSH AX

    PUSH ES:next                 ; cur.next

    CALL ReplaceOp

    LES BX, pQcb
    DEC ES:[BX].qcbCount            ; decrement count

    MOV ES, curEid
    DEC ES:checkSum              ; undo checksum

    MOV SP,BP
    POP BP
    RET 6
OsRemoveFromQ ENDP

PURGE curEid, pQcb

$EJ

;    OsReplaceInQ : PROCEDURE (pQCB, oldEid, newEid) CLEAN;
;        DCL pQcb PTR;
;        DCL (oldEid, newEid) EidType;
;
;    This will replace one element in a q with another

newEid EQU WORD PTR [BP+6]       ; third param
oldEid EQU WORD PTR [BP+8]       ; second param
pQcb EQU DWORD PTR [BP+10]       ; first param

OsReplaceInQ PROC FAR
    PUSH BP
    MOV BP,SP

    LES AX, pQcb                 ; pQcb
    PUSH ES
    PUSH AX

    MOV ES, oldEid
    PUSH ES:prev                 ; oldEid.prev

    PUSH newEid                  ; newEid

    PUSH ES:next                 ; oldEid.next

    CALL ReplaceOp

    MOV SP,BP
    POP BP
    RET 8
OsReplaceInQ ENDP

PURGE newEid, oldEid, pQcb

$EJ
;   OsElementInQ : PROCEDURE (pQcb, eid) BOOLEAN CLEAN;
;        DCL pQcb PTR;
;        DCL eid EidType;
;
;    This will check to see if an element really belongs
;    in the given q.  If a previous and back pointer don't
;    match then an exception will be raised.

eid EQU WORD PTR [BP+6]          ; second param
pQcb EQU DWORD PTR [BP+8]        ; first param

; in this routine let ES be the current Eid and
; DX be the previous eid

OsElementInQ PROC FAR
    PUSH BP
    MOV BP,SP
    
    LES BX,pQcb
    MOV ES, ES:[BX].headOfQ      ; current := first element
    MOV DX, nullWord             ; previous := null

OsElementTopOfLoop:
    MOV AX, ES
    CMP AX, nullWord             ; at end of queue ?
    JE OsElementFalse

    CMP ES:prev, DX              ; links match up ?
    JE OsElementCont

    MOV AX, memException         ; if not then raise exception
    PUSH AX
    MOV AX, eBadPointer
    PUSH AX
    CALL Exception

OsElementCont:
    MOV AL, TRUE                 ; assume true
    MOV BX, ES
    CMP BX, eid                  ; if element in Q ?
    JE OsElementDone

    MOV DX, ES                   ; previous := current
    MOV ES, ES:next              ; current := next
    JMP SHORT OsElementTopOfLoop

OsElementFalse:
    MOV AL, FALSE

OsElementDone:
    MOV SP, BP
    POP BP
    RET 6
OsElementInQ ENDP

PURGE eid, pQcb

$EJ

;    OsSearchInQ : PROCEDURE (pQcb, offset, pKey, length, mode) EidType BOOLEAN;
;        DCL (pQcb, pKey) PTR;
;        DCL (offset, length) WORD;
;        DCL mode BYTE;

;    This will search in a queue for an ascii string which would
;    be in an element offset bytes from the start and length
;    bytes long.  The element needs to match pKey.  Mode determines
;    whether case is significant. (0 -> caseless)

mode EQU BYTE PTR [BP+6]          ; fifth param
len EQU WORD PTR [BP+8]           ; fourth param
pKey EQU DWORD PTR [BP+10]        ; third param
offst EQU WORD PTR [BP+14]        ; second param
pQcb EQU DWORD PTR [BP+16]        ; first param

;    In this routine, ES will usually be the current element and
;    DX will usually be the previous element
 
OsSearchInQ PROC FAR
    PUSH BP
    MOV BP,SP

    LES BX, pQcb
    MOV ES, ES:[BX].headOfQ       ; current := headOfQ

    MOV DX, nullWord              ; previous = null

OsSearchTopOfLoop:
    MOV AX, ES
    CMP AX, nullWord              ; at end of queue ?
    JE OsSearchDone               ; return with ax = nullWord

    CMP ES:prev, DX               ; links match up ?
    JE OsSearchCont

    MOV AX, memException          ; if not then raise exception
    PUSH AX
    MOV AX, eBadPointer
    PUSH AX
    CALL Exception

OsSearchCont:
    PUSH ES
    PUSH DX                       ; save current and previous

    CMP mode, asciiMode          ; caseless compare ?
    JNE OsSearchCase              ; if not then jump


    PUSH ES                       ; current(offset)
    PUSH offst

    LES AX, pKey
    PUSH ES                       ; pKey
    PUSH AX

    PUSH len                      ; length

    CALL CompareChars

    RCR AL, 1                     ; equal (c -> true) ?
    JNC OsSearchBottomOfLoop      ; if not equal, then loop again

    JMP SHORT OsSearchTrue

OsSearchCase:
    MOV DI, offst                 ; ES:DI is now ready

    PUSH DS                       ; save DS
    LDS SI, pKey                  ; DS:SI is now ready

    MOV CX, len

    CLD
    REPZ CMPSB                    ; compare them byte for byte
    POP DS                        ; restore DS
    JNE OsSearchBottomOfLoop      ; if not equal, then loop again

    JMP SHORT OsSearchTrue

OsSearchBottomOfLoop:
    POP DX
    POP ES                        ; restore current and previous

    MOV DX, ES                    ; previous := current
    MOV ES, ES:next               ; current := next
    JMP SHORT OsSearchTopOfLoop

OsSearchTrue:
    POP DX                        ; restore these
    POP AX                        ; return eid in AX (from pushed es)

OsSearchDone:
    MOV SP, BP
    POP BP
    RET 14
OsSearchInQ ENDP

PURGE mode, len, pKey, offst, pQcb

CODE ENDS

    END
