; IfsDirCompareRecords.asm
; Copyright Xerox Corporation 1981

; Last modified May 3, 1981  1:07 PM by Taft

.ent CompareRecords
.bext IFSError

; The following must agree with the DR structure in IfsDirs.decl.
DR.header = 0.
DR.pathName = 6.
drHeaderMask = 171600

.dmr GetNextChar = 66400

.srel

CompareRecords: .CompareRecords

.nrel

; CompareRecords(r1, r2)
; Compares two B-Tree records in the same manner as DirCompareKey.
; This differs from DirCompareKey in that it compares two records
; rather than a key and a record.
; Note: this procedure is not reentrant.

.CompareRecords:
	sta 3 1 2
	sta 0 2 2		; r1
	sta 1 3 2		; r2

; Find position of last "!" in r1.
	mov 0 3			; r1
	lda 0 DR.header 3	; minimal check that this is really a DR
	lda 1 cdrHeaderMask
	and# 0 1 szr
	 jmp BadDR
	lda 0 DR.pathName 3	; extract string length
	lda 1 c177400
	ands 1 0
	sta 0 lenVerString	; save temporarily
	lda 1 cDR.pathName
	add 3 1			; address of string in record
	lda 3 csdR1
	snz 3 3			; CSD pointers already set up?
	 jsr SetupCSDs		; no, do so first
	sta 1 1 3		; save string address
FindLastExcl:
	jsr @363		; load ac0'th byte from array pointed to
				; by ac1, and return byte in ac1.
				; does not clobber ac0!
	lda 3 cExcl
	sne 1 3
	 jmp FoundExcl
	neg 0 0			; decrement string index
	com 0 0
	lda 3 csdR1
	lda 1 1 3
	jmp FindLastExcl

; CompareRecords (cont'd)

; Set up CSD for r1
FoundExcl:
	lda 1 lenVerString	; compute length of version string
	sub 0 1			; = entire string length - position of "!"
	sta 1 lenVerString
	subzr 1 1		; 100000B
	add 1 0			; begin at right-hand byte
	sta 0 @csdR1

; Set up CSD for r2
	lda 3 3 2		; r2
	lda 0 DR.header 3	; minimal check that this is really a DR
	lda 1 cdrHeaderMask
	and# 0 1 szr
	 jmp BadDR
	lda 0 DR.pathName 3	; extract string length
	lda 1 c177400
	ands 1 0
	subzr 1 1		; 100000B
	add 1 0			; begin at right-hand byte
	lda 1 cDR.pathName
	add 3 1			; address of string in record
	lda 3 csdR2
	sta 0 0 3
	sta 1 1 3

; Compare characters in the "<dir>name!" portion
CompareBodyLoop:
	lda 0 csdR1
	GetNextChar 140		; get char from r1, convert to upper case
	 jmp R1Exhausted
	mov 1 3
	lda 0 csdR2
	GetNextChar 140		; get char from r2, convert to upper case
	 jmp RetGr		; exhausted: r1 is greater than r2
	sne 1 3
	 jmp CompareBodyLoop	; equal, continue comparing

; First mismatch.  If all remaining characters of r2 are digits
; then r2's body is an initial substring of r1 and we declare
; r1 to be greater.  Otherwise we return the result of comparing
; the mismatching character codes.  Note that the only possible effect
; of looking at the rest of r2 is to return "greater" where we
; otherwise would return "less".  But if r1's char > r2's char anyway, then
; just say so now without bothering to check.
	slt 3 1
	 jmp RetGr

CheckDigitsLoop:
	lda 0 cDigit0		; r2 char a digit?
	sub 0 1
	lda 0 d10
	sltu 1 0
	 jmp RetLs		; no, r1 is less than r2
	lda 0 csdR2
	GetNextChar 0		; get next char from r2
	 jmp RetGr		; exhausted: r1 is greater than r2
	jmp CheckDigitsLoop

; Still inside CompareRecords; this stuff is here for addressing reasons.

; Internal procedure to set up csdR1 and csdR2.
; Returns with AC3 = csdR1.
; Does not disturb other ACs.

SetupCSDs:
	sta 3 csdBlk
	jsr AfterCSDs

csdBlk:	.blk 5

AfterCSDs:
	skeven 3 3		; force CSDs to be even
	 inc 3 3
	sta 3 csdR2
	inc 3 3
	inc 3 3
	sta 3 csdR1
	jmp @csdBlk

csdR1:	.blk 1
csdR2:	.blk 1
lenVerString: .blk 1

c177400: 177400
cDigit0: 60
d10:	10.
cDR.pathName: DR.pathName
cExcl:	41			; "!"

BadDR1:	lda 3 2 2
BadDR:	sta 3 lenVerString
	lda 0 2 2
	lda 1 3 2
	jsr @370
	 6
	 77400
	lda 0 .ecMalformedDR
	lda 1 lenVerString
	jsrii lvIFSError	; IFSError(ecMalformedDR, record)
	 2

cdrHeaderMask: drHeaderMask

.ecMalformedDR: 36.
lvIFSError: IFSError

; CompareRecords (cont'd)

; r1 is exhausted before r2: bodies seem to match.
; Now parse the version strings and compare them numerically.
; It must be possible to parse r1's version; if not, the record is malformed.
; If a non-digit is encountered in r2 then r1's body is an initial substring
; of r2 and is therefore "less".
R1Exhausted:
	lda 0 @csdR1		; get r1 byte flag (count is zero)
	lda 1 lenVerString	; merge with length of version string
	add 1 0
	sta 0 @csdR1
	mkzero 3 3		; init parsed version
ParseV1Loop:
	lda 0 csdR1
	GetNextChar 0		; get next char from r1
	 jmp V1Exhausted	; exhausted: go begin work on v2
	lda 0 cDigit0		; char a digit?
	sub 0 1
	lda 0 d10
	sltu 1 0
	 jmp BadDR1		; no, r1 is malformed
	mov 3 0			; yes, multiply parsed version by 10
	addzl 3 3
	addzl 0 3
	add 1 3			; add in new digit
	jmp ParseV1Loop

V1Exhausted:
	sta 3 lenVerString	; store r1's version here temporarily
	mkzero 3 3		; init parsed version
ParseV2Loop:
	lda 0 csdR2
	GetNextChar 0		; get next char from r2
	 jmp V2Exhausted	; exhausted: go compare version numbers
	lda 0 cDigit0		; char a digit?
	sub 0 1
	lda 0 d10
	sltu 1 0
	 jmp RetLs		; no, r1 is less than r2
	mov 3 0			; yes, multiply parsed version by 10
	addzl 3 3
	addzl 0 3
	add 1 3			; add in new digit
	jmp ParseV2Loop

; Reached end of version number.  Compare versions.
V2Exhausted:
	lda 0 lenVerString	; r1's version
	sne 0 3
	 jmp RetEq
	sltu 0 3
RetGr:	 mkone 0 0 skp		; r1 > r2, return 1
RetLs:	  mkminusone 0 0	; r1 < r2, return -1
	lda 3 1 2
	jmp 1 3

RetEq:	mkzero 0 0		; r1 = r2, return 0
	lda 3 1 2
	jmp 1 3

.end

// The following procedure is equivalent to the one in this module:

//---------------------------------------------------------------------------
let CompareRecords(r1, r2) = valof
//---------------------------------------------------------------------------
// Compares two B-Tree records in the same manner as DirCompareKey.
// This differs from DirCompareKey in that it compares two records
// rather than a key and a record.
[
if (r1>>DR.header & drHeaderMask) ne 0 then IFSError(ecMalformedDR, r1)
if (r2>>DR.header & drHeaderMask) ne 0 then IFSError(ecMalformedDR, r2)

// Find position of last "!" in first record
let lenBodyString = nil
for i = r1>>DR.pathName.length to 1 by -1 do
   if r1>>DR.pathName.char↑i eq $! then [ lenBodyString = i; break ]

// Compare chars in the "<dir>name!" (string) portion
let lenR2 = r2>>DR.pathName.length
for i = 1 to lenBodyString do
   [
   // If we run off the end of r2 then r1 is greater.
   if i gr lenR2 resultis 1

   let c1 = r1>>DR.pathName.char↑i
   let c2 = r2>>DR.pathName.char↑i
   if c1 ne c2 then
      [
      // Lower-case alphabetics collate with upper-case
      if c1 ge $a & c1 le $z then c1 = c1-($a-$A)
      if c2 ge $a & c2 le $z then c2 = c2-($a-$A)
      if c1 ne c2 then
         [
         // Definitely a mismatch.  If all remaining characters of r2
         // are digits then r2's body is an initial substring
         // of r1 and we declare r1 to be greater.  Otherwise we
         // return the result of comparing the mismatching character codes.
         // Note that the only possible effect of looking at the rest of r2
         // is to return "greater" where we otherwise would return
         // "less".  But if r1's char > r2's char anyway, then just say so now
         // without bothering to check.
         if c1 ls c2 then
            for j = i to lenR2 do
               [
               let digit = r2>>DR.pathName.char↑j - $0
               if digit ls 0 % digit gr 9 resultis -1
               ]
         resultis 1
         ]
      ]
   ]

// Bodies equal, now parse the version strings and compare them numerically.
// It must be possible to parse r1's version; if not, the record is malformed.
// If a non-digit is encountered in r2 then r1's body is an initial substring
// of r2 and we return "less than"
let v1 = 0
for i = lenBodyString+1 to r1>>DR.pathName.length do
   [
   let digit = r1>>DR.pathName.char↑i - $0
   if digit ls 0 % digit gr 9 then IFSError(ecMalformedDR, r1)
   v1 = 10*v1+digit
   ]
let v2 = 0
for i = lenBodyString+1 to lenR2 do
   [
   let digit = r2>>DR.pathName.char↑i - $0
   if digit ls 0 % digit gr 9 resultis -1  //non-digit encountered
   v2 = 10*v2+digit
   ]
resultis Usc(v1, v2)
]