; ; string.s ; ; String handling routines (control terminated) (MDW) ; ; © 1994-1998 Straylight ; ;----- Licensing note ------------------------------------------------------- ; ; This file is part of Straylight's Sapphire library. ; ; Sapphire is free software; you can redistribute it and/or modify ; it under the terms of the GNU General Public License as published by ; the Free Software Foundation; either version 2, or (at your option) ; any later version. ; ; Sapphire is distributed in the hope that it will be useful, ; but WITHOUT ANY WARRANTY; without even the implied warranty of ; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ; GNU General Public License for more details. ; ; You should have received a copy of the GNU General Public License ; along with Sapphire. If not, write to the Free Software Foundation, ; 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. ;----- Standard header ------------------------------------------------------ GET libs:swis GET libs:header ;----- External dependencies ------------------------------------------------ GET sapphire:sapphire ;----- Main code ------------------------------------------------------------ ; ; No string routine corrupts the scratchpad. AREA |Sapphire$$Code|,CODE,READONLY ; --- str_cpy --- ; ; On entry: R0 == destination string ; R1 == source string ; ; On exit: R0 == pointer to terminator of destination ; ; Use: Copies a string from one block to another. It leaves the ; destination pointer at the end of the string so that any ; subsequent copies concatenate other bits on the same string. ; Single characters can of course be appended with ; ; MOV Rx,#&cc ; STRB Rx,[R0],#1 EXPORT str_cpy str_cpy ROUT STMFD R13!,{R1,R14} ;Keep return address safe 00str_cpy LDRB R14,[R1],#1 ;Get a byte from source CMP R14,#' ' ;Is it a control character MOVLT R14,#0 ;Yes -- translate to a 0 STRB R14,[R0],#1 ;Store in destination BGE %00str_cpy ;No -- copy another byte SUB R0,R0,#1 ;Point back at terminator LDMFD R13!,{R1,PC}^ ;Return to caller LTORG ; --- str_len --- ; ; On entry: R0 == pointer to string ; ; On exit: R0 == length of the string ; ; Use: Calculates the length of a string. EXPORT str_len str_len ROUT STMFD R13!,{R1,R14} ;Save some registers MOV R14,R0 ;Point to the string MOV R0,#0 ;Current length is 0 00str_len LDRB R1,[R14],#1 ;Get a byte from the string CMP R1,#' ' ;Is it the end yet? LDMLTFD R13!,{R1,PC}^ ;Yes -- return ADD R0,R0,#1 ;Bump the length counter B %00str_len ;And go back for more LTORG ; --- str_cmp --- ; ; On entry: R0 == pointer to string A ; R1 == pointer to string B ; ; On exit: Flags as appropriate ; ; Use: Case-sensitively compares two strings. You can use the ; normal ARM condition codes after the compare, so you can ; treat this fairly much like a normal CMP. EXPORT str_cmp str_cmp ROUT STMFD R13!,{R0,R1,R3,R4,R14} 00str_cmp LDRB R3,[R0],#1 ;Get a character from A LDRB R4,[R1],#1 ;And one from B CMP R3,#&20 ;Is that the end of A? MOVLT R3,#0 ;Yes -- pretend it's null CMP R4,#&20 ;Is that the end of B? MOVLT R4,#0 ;Yes -- pretend it's null CMP R3,R4 ;How do they match up? LDMNEFD R13!,{R0,R1,R3,R4,PC} ;If NE, return condition CMP R3,#0 ;Is this the end? BNE %00str_cmp ;No -- loop again LDMFD R13!,{R0,R1,R3,R4,PC} ;Return to caller ; --- str_icmp --- ; ; On entry: R0 == pointer to string A ; R1 == pointer to string B ; ; On exit: Flags as appropriate ; ; Use: As for str_cmp above, but case-insensitive. EXPORT str_icmp str_icmp ROUT STMFD R13!,{R0,R1,R3,R4,R14} 00str_icmp LDRB R3,[R0],#1 ;Get a character from A LDRB R4,[R1],#1 ;And one from B SUB R14,R3,#'a' ;Subtract the bottom limit CMP R14,#26 ;Is it a lower case letter? BICLO R3,R3,#&20 ;Yes -- convert to upper SUB R14,R4,#'a' ;Subtract the bottom limit CMP R14,#26 ;Is it a lower case letter? BICLO R4,R4,#&20 ;Yes -- convert to upper CMP R3,#&20 ;Is that the end of A? MOVLT R3,#0 ;Yes -- pretend it's null CMP R4,#&20 ;Is that the end of B? MOVLT R4,#0 ;Yes -- pretend it's null CMP R3,R4 ;How do they match up? LDMNEFD R13!,{R0,R1,R3,R4,PC} ;If NE, return condition CMP R3,#0 ;Is this the end? BNE %00str_icmp ;No -- loop again LDMFD R13!,{R0,R1,R3,R4,PC} ;Return to caller LTORG ; --- str_index --- ; ; On entry: R0 == pointer to name table ; R1 == index into name table ; ; On exit: CS if index good, and ; R0 == address of R0th string in table ; else CC and ; R0 corrupted ; ; Use: Finds an indexed string in a table. The table consists of ; ctrl-terminated strings, with no separation. The table is ; terminated by a zero-length entry. EXPORT str_index str_index ROUT ORRS R14,R14,#C_flag ;Set C initially STMFD R13!,{R1,R14} ;Save a register or two 05 SUBS R1,R1,#1 ;Decrement the counter LDMCCFD R13!,{R1,PC}^ ;And return to caller LDRB R14,[R0],#1 ;Load byte from string CMP R14,#&20 ;Is this the end? BCC %10str_index ;Yes -- ooops 00 LDRB R14,[R0],#1 ;No -- load another CMP R14,#&20 ;Is this the end? BCS %b00 ;No -- loop then B %b05 ;Go back to main loop 10str_index LDMFD R13!,{R1,R14} ;And return to caller BICS PC,R14,#C_flag ;With a bad result LTORG ; --- str_match --- ; ; On entry: R0 == pointer to name table ; R1 == string to match in table ; ; On exit: CS if match found, and ; R0 == index of string matched ; else CC and ; R0 corrupted ; ; Use: Looks up a string in a table. The table consists of ; ctrl-terminated strings, with no separation. The table is ; terminated by a zero-length entry. EXPORT str_match str_match ROUT STMFD R13!,{R1-R5,R14} ;Save some registers MOV R2,#0 ;Index of the current item LDRB R14,[R1,#0] ;Load the first byte CMP R14,#0 ;Is it a null string? BEQ %90str_match ;Yes -- no match then ; --- The main loop --- 00str_match MOV R3,R1 ;Point to argument start LDRB R4,[R0],#1 ;Load a byte from the table LDRB R5,[R3],#1 ;Load a byte from the arg CMP R4,#&20 ;Is this an empty string? BCC %90str_match ;Yes -- no match then ; --- Try to match a word --- 10str_match CMP R5,#&20 ;End of argument string? BCC %80str_match ;Yes -- that's a match then SUB R14,R4,#'a' ;Subtract the bottom limit CMP R14,#26 ;Is it a lower case letter? BICLO R4,R4,#&20 ;Yes -- convert to upper SUB R14,R5,#'a' ;Subtract the bottom limit CMP R14,#26 ;Is it a lower case letter? BICLO R5,R5,#&20 ;Yes -- convert to upper CMP R4,R5 ;Do characters match up? LDREQB R4,[R0],#1 ;Load a byte from the table LDREQB R5,[R3],#1 ;Load a byte from the arg BEQ %10str_match ;Yes -- go round for more ; --- Failed -- find end of table entry --- 20str_match CMP R4,#&20 ;End of entry string? LDRCSB R4,[R0],#1 ;No -- load byte from table BCS %20str_match ;And go round again ADD R2,R2,#1 ;Increment item index B %00str_match ;Loop round for next entry ; --- Found a match --- 80str_match MOV R0,R2 ;Get the item index LDMFD R13!,{R1-R5,R14} ;Unstack the registers ORRS PC,R14,#C_flag ;And return with C set ; --- No match found --- 90str_match LDMFD R13!,{R1-R5,R14} ;Unstack the registers BICS PC,R14,#C_flag ;And return with C clear LTORG ; --- str_subst --- ; ; On entry: R0 == Pointer to skeleton ; R1 == Pointer to output buffer ; R2-R11 == Pointer to filler strings (optional) ; ; On exit: R0 == Pointer to start of buffer ; R1 == Pointer to terminating null ; ; Use: Performs string substitution, filling in a skeleton string ; containing placeholders with `filler' strings. The ; placeholders are actually rather powerful. The syntax of ; these is as follows: ; ; `%' [] ; ; (spaces are for clarity -- in fact you must not include ; spaces in the format string.) ; ; is any charater between `0' and `9'. It refers to ; registers R2-R11 (so `0' means R2, `5' is R7 etc.) How the ; value is interpreted is determined by . ; ; is one of: ; ; s String. This is the default. The register is ; considered to be a pointer to an ASCII string ; (control terminated). ; ; i Integer. The (signed) decimal representation is ; inserted. Leading zeros are suppressed. ; ; x Hex fullword. The hexadecimal representation of the ; register is inserted. Leading zeros are included. ; ; b Hex byte. The hexadecimal representation of the ; least significant byte is inserted. Leading zeros ; are included. ; ; c Character. The ASCII character corresponding to the ; least significant byte is inserted. EXPORT str_subst str_subst ROUT STMFD R13!,{R1-R11,R14} ; --- Move arguments into more amenable registers --- MOV R10,R0 ;Pointer to skeleton string ; --- Main `get a character' loop --- 00str_subst LDRB R14,[R10],#1 ;Get an input character CMP R14,#'%' ;Is it a `%' sign? BEQ %01str_subst ;Yes -- deal with it 02str_subst CMP R14,#&20 ;Is it the end of input? MOVLT R14,#0 ;Yes -- null terminate it STRB R14,[R1],#1 ;Not special, so store it BGE %00str_subst ;No -- get another one SUB R1,R1,#1 ;Point to null terminator LDMFD R13!,{R0,R2-R11,PC}^ ;And return to caller ; --- Found a `%' sign, so find out what to substitute --- 01str_subst LDRB R14,[R10],#1 ;Get the next character ; --- Now find out what we're substituting --- ORR R9,R14,#&20 ;Convert it to lowercase CMP R9,#'s' ;Is it a string? CMPNE R9,#'i' ;Or an integer? CMPNE R9,#'x' ;Or a fullword hex number? CMPNE R9,#'b' ;Or a single byte in hex? CMPNE R9,#'c' ;Or an ASCII character? LDREQB R14,[R10],#1 ;And get another character ; --- Now find which filler it is --- CMP R14,#'0' ;Is it a digit? BLT %02str_subst ;No -- just ignore the `%' CMP R14,#'9' ;Make sure it's small enough BGT %02str_subst ;No -- just ignore the `%' SUB R14,R14,#'0'-1 ;Convert to binary (1..10) LDR R0,[R13,R14,LSL #2] ;Load appropriate register ; --- Now find out how to substitute this argument --- MOV R2,#256 ;Buffer size -- saves space CMP R9,#'s' ;Is it meant to be a string? BEQ %03str_subst ;Yes -- a quick copy loop CMP R9,#'i' ;A decimal integer? BEQ %04str_subst ;Yes -- go ahead to convert CMP R9,#'x' ;A hex fullword? BEQ %05str_subst ;Yes -- convert that CMP R9,#'b' ;A hex byte? BEQ %06str_subst ;Yes -- convert that CMP R9,#'c' ;A character? BEQ %07str_subst ;Yes -- convert that ; --- String substitution copy-loop --- 03str_subst LDRB R14,[R0],#1 ;Get an input byte CMP R14,#&20 ;Is it the end of the string? BLT %00str_subst ;Yes -- read main string STRB R14,[R1],#1 ;No -- store it in output B %03str_subst ;... and get another one ; --- Decimal integer conversion --- 04str_subst SWI OS_ConvertInteger4 ;Convert and update nicely B %00str_subst ;And rejoin the main loop ; --- Hexadecimal fullword conversion --- 05str_subst SWI OS_ConvertHex8 ;Convert and update nicely B %00str_subst ;And rejoin the main loop ; --- Hexadecimal byte conversion --- 06str_subst SWI OS_ConvertHex2 ;Convert and update nicely B %00str_subst ;And rejoin the main loop ; --- ASCII character conversion --- 07str_subst STRB R0,[R1],#1 ;Store the byte in B %00str_subst ;And rejoin the main loop LTORG ; --- str_error --- ; ; On entry: R0 == Pointer to skeleton ; R2-R11 == Pointers to fillin strings ; ; On exit: R0 == Pointer to error in buffer ; R1 == Pointer to terminator ; ; Use: Fills in an error skeleton (containing a 4 byte error number ; and a control terminated skeleton string as for str_subst) ; and returns the address of the filled in error block. The ; error block is stored in a buffer obtained from str_buffer. ; ; Filler strings may be held in the scratchpad. EXPORT str_error str_error ROUT STMFD R13!,{R14} ;Store the link register BL str_buffer ;Find a spare buffer LDR R14,[R0],#4 ;Get the error number STR R14,[R1],#4 ;Output it too BL str_subst ;Do the string substitution SUB R0,R0,#4 ;Point to the error start LDMFD R13!,{PC}^ ;Return to caller LTORG str__wSpace DCD 0 ;Pointer to error buffer ; ---- str_buffer --- ; ; On entry: -- ; ; On exit: R1 == pointer to the next free buffer ; ; Use: Returns a pointer to a 256-byte buffer. There are at present ; 2 buffers, which are returned alternately. EXPORT str_buffer str_buffer ROUT STMFD R13!,{R14} ;Save a work register ; --- Work out which buffer to use --- ; ; This uses some vaguely clever tricks, so watch out [mdw] ; In fact, the C compiler used exactly the same tricks when ; tried `return (buffer+256*(count^=1))'. WSPACE str__wSpace,R1 ;Find workspace address LDR R14,[R1,#0] ;Get the current buffer EOR R14,R14,#1 ;Toggle the buffer number STR R14,[R1],#4 ;Store the new one back ADD R1,R1,R14,LSL #8 ;Point to correct buffer LDMFD R13!,{PC}^ ;Return to caller LTORG ;----- Workspace ------------------------------------------------------------ str__buffers EQU 2 ;Use two buffers for now ^ 0 ;Don't tie it to R12 str__wStart # 0 str__buffNum # 4 str__buffer # 256*str__buffers ;The number of buffers I want str__wSize EQU {VAR}-str__wStart AREA |Sapphire$$LibData|,CODE,READONLY DCD str__wSize ;For the error buffer DCD str__wSpace ;Pointer to the pointer DCD 0 ;Don't use the scratchpad DCD 0 ;No initialisation reqd. ;----- That's all folks ----------------------------------------------------- END