.TITLE UTILITY .IDENT "V1.2" ; ; Author: D. Mischler 10-JUN-87 ; ; This module contains utility routines for string ; operations, etc. ; .MCALL EXTK$S .ASECT ; ; Keyword dispatch table entry format. ; .=0 K.NAME: .BLKW 1 ; Address of keyword text. K.DISP: .BLKW 1 ; Dispatch address. .PAGE .PSECT CODE,I,RO ; ; Subroutine to buffer an address as preface to a line of data. ; On entry: R5 contains address to be displayed. ; On exit: R0 points after buffered address. ; U$ADDR:: JSR R5,.SAVR1 ; Save R1 - R5. MOV R0,R4 ; Copy incoming output pointer. MOV R5,R1 ; Copy address. CALL C$VALU ; Buffer address. MOVB #':,(R0)+ ; Buffer delimiter. MOV NCFLGS,R1 ; Get numeric conversion flags. ASH #-11.,R1 ; Position field width. BIC #^C<37>,R1 ; Mask to field width. ADD #3,R1 ; Adjust spacing. CMP #C$SYMB,CNMODE ; Is symbolic mode enabled? BNE 10$ ; No, current spacing will suffice. ADD #7,R1 ; Adjust for symbol name and plus sign. 10$: ADD R4,R1 ; Add incoming buffer address. SUB R0,R1 ; Calculate number of blanks to buffer. 20$: MOVB #' ,(R0)+ ; Buffer all required blanks. SOB R1,20$ RETURN .PAGE ; ; Subroutine to "fix" a null-terminated string. Whitespace is ; compressed and unquoted alphabetics are forced to upper case. ; On entry: R0 points to string. ; On exit: R1 points to end of string. ; U$CLFX:: JSR R5,$SAVRG ; Save registers R3 - R5. MOV R0,R1 ; Copy buffer address MOV R0,R4 ; a couple of times. MOV SP,R5 ; Set whitespace flag. 10$: MOVB (R1)+,R3 ; Get a character, terminator? BEQ 100$ ; Yup, check results. CMPB #HT,R3 ; Is it a horizontal tab? BEQ 15$ ; Yes, treat as whitespace. CMPB #' ,R3 ; Is it a blank? BNE 20$ ; No, check for lower case. ; Process whitespace. 15$: TST R5 ; Was last character whitespace? BNE 10$ ; Yes, ignore consecutive whitespace. DEC R5 ; Set whitespace flag. MOVB #' ,(R0)+ ; Buffer a blank. BR 10$ ; Get next character. ; Convert lower case alphabetics to upper case. 20$: CMPB R3,#'a ; Is the character too low for lowercase? BLO 30$ ; Yes, just pass it through. CMPB R3,#'z ; Is it too high for lowercase? BHI 30$ ; Yes, just pass it through. BIC #'a-'A,R3 ; Convert to upper case. 30$: MOVB R3,(R0)+ ; Buffer character. CLR R5 ; Character is not whitespace. CMPB #'",R3 ; Is character a double quote? BEQ 40$ ; Yes, don't compress or change case. CMPB #'',R3 ; Is character a single quote? BNE 10$ ; No, get next character. INC R5 ; Set character counter. BR 50$ ; Process quoted character. ; Handle two quoted characters. 40$: MOV #2,R5 ; Set up character counter. 50$: MOVB (R1)+,(R0)+ ; Buffer a character, terminator? BEQ 110$ ; Yes, just end it. SOB R5,50$ ; Stay down for the count. BR 10$ ; Get next character. ; Hit end of buffer. 100$: CALL U$RMTB ; Get rid of trailing blanks. CLRB (R0) ; Terminate buffer. 110$: MOV R0,R1 ; Copy end pointer. MOV R4,R0 ; Restore original input pointer. RETURN .PAGE ; ; Subroutine to look up a command component in a keyword table. ; The command component terminator will be found by U$FTRM. ; If any error is detected then the carry will be set on exit and ; R1 will point to a null-terminated error message. ; ; On entry: R0 points to command line component. ; R1 points to keyword dispatch table. ; ; On exit: R1 is destroyed. ; R2 points to keyword dispatch routine. ; U$DCOD:: JSR R5,$SAVRG ; Save supposedly non-volatile registers. MOV R0,R5 ; Save command line component address. CALL U$FTRM ; Find command element terminator. MOV R0,R4 ; Save terminator address for later. CLR R2 ; Indicate no keyword match yet. 10$: MOV (R1)+,R3 ; Get keyword entry string address, zero? BEQ 60$ ; Yes, end of table. MOV R5,R0 ; Get command element address. 20$: CMP R0,R4 ; End of command element string? BEQ 40$ ; Yes, table entry matches. TSTB (R3) ; Keyword string terminator? BEQ 30$ ; Yes, table entry does not match. CMPB (R0)+,(R3)+ ; Does this character match? BEQ 20$ ; Yes, keep checking. ; Keyword table entry doesn't match. 30$: TST (R1)+ ; Skip over dispatch address. BR 10$ ; Keep looking. ; Matching table entry found. 40$: TST R2 ; Is command element ambiguous? BNE 50$ ; Yes, complain. MOV (R1)+,R2 ; Pick up keyword dispatch address. BR 10$ ; Make sure keyword is not ambiguous. ; Command element is ambiguous. 50$: MOV #E.AMBG,R1 ; Point to ambiguous command element message. BR 65$ ; Take error exit. ; End of table reached. 60$: TST R2 ; Was a matching keyword found? BNE 70$ ; Yes, return with a clear carry. MOV #E.KWNF,R1 ; Point to keyword not found message. 65$: SEC ; Indicate failure. 70$: MOV R5,R0 ; Restore command element pointer. RETURN .PAGE ; ; Subroutine to find the start of the next command element. ; On entry: R0 points to current command element. ; On exit: R0 points to next command element. ; The carry will be set if there are no more command elements. ; U$FNXT:: CALL U$FTRM ; Find terminator of current element. TSTB (R0)+ ; End of command line? BNE 10$ ; No, return with carry clear. DEC R0 ; Point to command line terminator. SEC ; Indicate failure. 10$: RETURN ; ; Subroutine to find the terminator for a command element. ; On entry: R0 points to command element. ; On exit: R0 points to terminator. ; U$FTRM:: CMPB #' ,(R0) ; Is character a blank? BEQ 10$ ; Yes, it's a terminator. CMPB #'/,(R0) ; Is character a slash? BEQ 10$ ; Yes, it's sort of a terminator. TSTB (R0)+ ; Null byte? BNE U$FTRM ; No, keep looking. DEC R0 ; Fix up pointer. 10$: RETURN ; ; Subroutine to remove trailing blanks from a string. ; The string must contain at least one non-blank character. ; On entry: R0 points after string. ; U$RMTB:: CMPB #' ,-(R0) ; Is last character a blank? BEQ U$RMTB ; Yes, do that trick again. INC R0 ; Fix string pointer. RETURN .PAGE ; ; Subroutine to pack a symbol name into R2, R3. ; On entry: R0 points to symbol name. ; On exit: R0 points past symbol name, R2 & R3 contain name. ; The carry will be set if no symbol is found. ; U$SYMN:: MOV #6,R2 ; Get maximum allowed symbol length. SUB R2,SP ; Allocate a symbol name area. MOV SP,R1 ; Point to symbol name area. 10$: MOVB (R0)+,R3 ; Get a character, high bit set? BMI 30$ ; Yes, it's obviously illegal. TSTB SYMCHR(R3) ; Is character a legal symbol constituent? BPL 30$ ; No, found symbol terminator. MOVB R3,(R1)+ ; Buffer character. SOB R2,10$ ; Continue up to maximum length. ; Symbol is longer than maximum length. Ignore the excess. 20$: MOVB (R0)+,R3 ; Get a character, high bit set? BMI 30$ ; Yes, it's obviously illegal. TSTB SYMCHR(R3) ; Is character a legal symbol constituent? BMI 20$ ; Yes, keep scanning. ; Symbol terminator has been located. 30$: DEC R0 ; Point back at terminator. CMP R1,SP ; Any legal characters buffered? BEQ 100$ ; No, complain. TST R2 ; Any blanks needed? BEQ 50$ ; No, just encode symbol name. 40$: MOVB #' ,(R1)+ ; Buffer a pad character. SOB R2,40$ ; Continue to maximum symbol length. ; Encode symbol name into RAD50. 50$: MOV SP,R1 ; Point to symbol name. MOV R0,-(SP) ; Save scan pointer. MOV R1,R0 ; Put name pointer where it belongs. MOV #1,R1 ; Accept periods. CALL $CAT5B ; Convert first three characters to RAD50. MOV R1,-(SP) ; Save result. MOV #1,R1 ; Accept periods. CALL $CAT5B ; Convert the remainder. MOV (SP)+,R2 ; Position RAD50 name appropriately. MOV R1,R3 MOV (SP)+,R0 ; Recover scan pointer. ADD #6,SP ; Clean up stack. RETURN ; Symbol name required but not found. 100$: ADD #6,SP ; Clean up stack. MOV #E.SNRQ,R1 ; Point to error message. SEC ; Indicate failure. RETURN .PAGE ; ; Subroutine to allocate a block of memory. ; ; On entry: R0 contains desired block size. ; ; On exit: R0 points to allocated block. ; The carry will be set if allocation fails. ; U$RQCB:: MOV R2,-(SP) ; Save volatile registers. MOV R1,-(SP) MOV R0,-(SP) ; Save desired allocation size. 10$: MOV #FREMEM,R0 ; Point to free memory list header. MOV (SP),R1 ; Get desired allocation size. CALL $RQCB ; Is a suitable block available? BCC 40$ ; Yes, return with it. ; Allocation failed: attempt task extension. MOV #FREMEM,R0 ; Point back to free memory list head. MOV 2(R0),R2 ; Has extension already failed? BEQ 30$ ; Yes, give it up. EXTK$S #4 ; Extend task by 256 bytes, OK? BCS 20$ ; No, forget it. MOV #256.,R1 ; Get size of new area. ADD R1,2(R0) ; Indicate new highest address. CALL $RLCB ; Make new area available. BR 10$ ; Retry allocation. ; Task extension failed. 20$: CLR 2(R0) ; Don't try further extensions. 30$: SEC ; Indicate failure. ; Exit with or without desired block. 40$: ROR R0 ; Save carry bit. TST (SP)+ ; Pop desired allocation size. MOV (SP)+,R1 ; Rcover volatile registers. MOV (SP)+,R2 ASL R0 ; Restore carry bit. RETURN .PAGE ; ; Subroutine to convert a RAD50 word to ASCII. ; On entry: R0 points to buffer, R1 contains RAD50 word. ; On exit: R0 points after characters, R1 & R2 are destroyed. ; $C5TA:: MOV R0,R2 ; Save buffer pointer. CLR R0 ; Zero-extend RAD50 word to 32 bits. DIV #50,R0 ; Get last RAD50 character in R1. MOV R1,-(SP) ; Save it. MOV R0,R1 ; Position quotient. CLR R0 ; Zero-extend it. DIV #50,R0 ; Get middle RAD50 character in R1. MOVB R50TBL(R0),(R2)+; Buffer first RAD50 character. MOV R2,R0 ; Put buffer pointer where it belongs. MOVB R50TBL(R1),(R0)+; Buffer second RAD50 character. MOV (SP)+,R1 ; Recover final RAD50 character. MOVB R50TBL(R1),(R0)+; Buffer it. RETURN .SAVE .PSECT RODATA,D,RO ; ; Table of RAD50 characters. ; R50TBL: .ASCII " ABCDEFGHIJKLMNOPQRSTUVWXYZ$.?0123456789" .RESTORE .PAGE ; ; Subroutine to perform unsigned division. ; On entry: R0 contains dividend, R1 contains divisor. ; On exit: R0 contains quotient, R1 contains remainder. ; $DIV:: MOV R1,-(SP) ; Save divisor. MOV R0,R1 ; Put dividend in place. CLR R0 ; Zero-extend it to 32 bits. CMP (SP),#1 ; Will DIV instruction work right? BLE 10$ ; No, handle the perverse cases. DIV (SP)+,R0 ; Perform signed division. RETURN ; Here if divisor would cause problems for the DIV instruction. 10$: TST (SP) ; Is the divisor >= 32768? BPL 30$ ; No, handle trivial cases. 20$: CMP R1,(SP) ; Is remainder >= divisor? BLO 40$ ; No, clean up stack and exit. SUB (SP),R1 ; Subtract divisor. INC R0 ; Count subtraction in quotient. BR 40$ ; Clean up the stack and exit. ; Here for divisors of 0 and 1. 30$: MOV R1,R0 ; Return dividend as quotient. CLR R1 ; Zero remainder. 40$: TST (SP)+ ; Clean up the stack. RETURN ; ; Subroutine to perform unsigned multiplication. ; On entry: R0 contains the multiplicand, R1 contains multiplier. ; On exit: R0 and R1 contain product. ; $MUL:: MOV R0,-(SP) ; Copy multiplicand. BIS R1,(SP)+ ; Are both factors <= 32767? BMI 10$ ; No, do it the hard way. MUL R1,R0 ; Perform signed multiplication. RETURN ; At least one factor is >= 32768. 10$: MOV R3,-(SP) ; Free up a couple of registers. MOV R2,-(SP) MOV R0,R2 ; Copy multiplicand. MOV #17.,R3 ; Set up shift counter. CLR R0 ; Zero-extend the multiplier (clears carry). 20$: ROR R0 ; Shift down multiplier (pick up carry). ROR R1 ; Should multiplicand be added? BCC 30$ ; No, count shift and loop. ADD R2,R0 ; Add multiplicand. 30$: SOB R3,20$ ; Perform all shifts. MOV (SP)+,R2 ; Pop saved registers. MOV (SP)+,R3 RETURN .END