; ; string.s ; ; String handling routines (control terminated) (TMA) ; ; © 1994-1998 Straylight ; ;----- Licensing note ------------------------------------------------------- ; ; This file is part of Straylight's Quartz library. ; ; Quartz 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. ; ; Quartz 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 Quartz. 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 ------------------------------------------------ ; ; None. ;----- Main code ------------------------------------------------------------ ; ; No string routine corrupts the scratchpad. AREA |Quartz$$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_qcpy --- ; ; On entry: R0 == destination pointer ; R1 == source pointer ; ; On exit: R0 == pointer to terminator of destination ; ; Use: This copies the source string to the destination. It is ; much faster than str_cpy if boths the string are word ; aligned. Note that it only works with NULL terminated ; strings. EXPORT str_qcpy str_qcpy ROUT STMFD R13!,{R1-R5,R14} ;Stack some registers ; --- Are both strings word aligned? --- ORR R2,R0,R1 ;OR them together ANDS R2,R2,#3 ;Is bit 0 or 1 set? BNE %10str_qcpy ;Yes, do non-word aligned cpy ; --- Set up bit structures --- MOV R3,#&01 ;Set bit 0 ADD R3,R3,#&100 ;Set bits 0 and 8 ADD R3,R3,R3,ROR #16 ;Set LSB of each byte in word MOV R4,#&80 ;Set bit 7 ADD R4,R4,#&8000 ;Set bit 15 ADD R4,R4,R4,ROR #16 ;Set MSB of each byte in word ; --- Branch to word loading routine --- B %02str_qcpy ;Branch ahead ; --- Copy the loaded word --- 01str_qcpy STR R2,[R0],#4 ;Store word ; --- Load a word, and check if any byte in it is 0 --- ; ; The algorithm is actually quite simple. By looking at ; each byte in the word being loaded individually, ; if we subtract 1 from it, the MSB of that byte is set ; if the byte was 0 to start with (ie. it becomes -1). ; However, the MSB may also be set if it was set to ; start with. If this is the case then we clear it ; after the subtract operation. ; ; This is why R3 is set up as it is. R4 can then be used ; to see if any of MSB is set -- if so, then at least ; one of the bytes was initially 0 :-) 02str_qcpy LDR R2,[R1],#4 ;Load a word SUB R5,R2,R3 ;Subtract 1 from each byte BIC R5,R5,R2 ;Clear MSB(s) if set in R2 ANDS R5,R5,R4 ;Are any MSBs set BEQ %02str_qcpy ;No, try another 4 bytes ; --- Now do the remaining bytes --- 03str_qcpy STRB R2,[R0],#1 ;Store a byte ANDS R2,R2,#&FF ;Clear the first byte SUBEQ R0,R0,#1 ;Point to NULL byte if finshd LDMEQIA R13!,{R1-R5,PC}^ ;And return to caller MOV R2,R2,ASR #8 ;Prepare next byte B %03str_qcpy ;Copy what's left ; --- Do a non-word aligned copy --- 10str_qcpy LDRB R2,[R1],#1 ;Get a byte STRB R2,[R0],#1 ;Store it in destination CMP R2,#0 ;Is it a terminator BNE %10str_qcpy ;No -- keep copying SUB R0,R0,#1 ;Point to NULL LDMFD R13!,{R1-R5,PC}^ 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 as a normal CMP type thing (except the arguments ; must be in R0 and R1, and it mangles R14). EXPORT str_cmp str_cmp ROUT STMFD R13!,{R0,R1,R3-R5,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-R5,PC} ;If NE, return condition CMP R3,#0 ;Is this the end? BNE %00str_cmp ;No -- loop again LDMFD R13!,{R0,R1,R3-R5,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-R5,R14} ADR R5,str__caseTable ;Point to upper-case table 00str_icmp LDRB R3,[R0],#1 ;Get a character from A LDRB R4,[R1],#1 ;And one from B LDRB R3,[R5,R3] ;If so, convert using table LDRB R4,[R5,R4] ;(both characters) CMP R3,R4 ;How do they match up? LDMNEFD R13!,{R0,R1,R3-R5,PC} ;If NE, return condition CMP R3,#0 ;Is this the end? BNE %00str_icmp ;No -- loop again LDMFD R13!,{R0,R1,R3-R5,PC} ;Return to caller str__caseTable DCB &00,&00,&00,&00,&00,&00,&00,&00 DCB &00,&00,&00,&00,&00,&00,&00,&00 DCB &00,&00,&00,&00,&00,&00,&00,&00 DCB &00,&00,&00,&00,&00,&00,&00,&00 DCB &20,&21,&22,&23,&24,&25,&26,&27 DCB &28,&29,&2A,&2B,&2C,&2D,&2E,&2F DCB &30,&31,&32,&33,&34,&35,&36,&37 DCB &38,&39,&3A,&3B,&3C,&3D,&3E,&3F DCB &40,&41,&42,&43,&44,&45,&46,&47 DCB &48,&49,&4A,&4B,&4C,&4D,&4E,&4F DCB &50,&51,&52,&53,&54,&55,&56,&57 DCB &58,&59,&5A,&5B,&5C,&5D,&5E,&5F DCB &60,&41,&42,&43,&44,&45,&46,&47 DCB &48,&49,&4A,&4B,&4C,&4D,&4E,&4F DCB &50,&51,&52,&53,&54,&55,&56,&57 DCB &58,&59,&5A,&7B,&7C,&7D,&7E,&7F DCB &80,&81,&82,&83,&84,&85,&86,&87 DCB &88,&89,&8A,&8B,&8C,&8D,&8E,&8F DCB &90,&91,&92,&93,&94,&95,&96,&97 DCB &98,&99,&9A,&9B,&9C,&9D,&9E,&9F DCB &A0,&A1,&A2,&A3,&A4,&A5,&A6,&A7 DCB &A8,&A9,&AA,&AB,&AC,&AD,&AE,&AF DCB &B0,&B1,&B2,&B3,&B4,&B5,&B6,&B7 DCB &B8,&B9,&BA,&BB,&BC,&BD,&BE,&BF DCB &C0,&C1,&C2,&C3,&C4,&C5,&C6,&C7 DCB &C8,&C9,&CA,&CB,&CC,&CD,&CE,&CF DCB &D0,&D1,&D2,&D3,&D4,&D5,&D6,&D7 DCB &D8,&D9,&DA,&DB,&DC,&DD,&DE,&DF DCB &E0,&E1,&E2,&E3,&E4,&E5,&E6,&E7 DCB &E8,&E9,&EA,&EB,&EC,&ED,&EE,&EF DCB &F0,&F1,&F2,&F3,&F4,&F5,&F6,&F7 DCB &F8,&F9,&FA,&FB,&FC,&FD,&FE,&FF 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 R11,R0 ;Pointer to skeleton string ; --- Main `get a character' loop --- 00str_subst LDRB R14,[R11],#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,[R11],#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,[R11],#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 (like a substitution skeleton in ; str_subst above with an error number on the front) and ; returns a pointer to it. The buffer used is found by calling ; str_buffer (see below), so you can safely use the result of ; one call as a filler string for the next. ; ; 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))'. LDR R14,str__wSpace ;Find the workspace LDR R1,[R14,#0] ;Get the current buffer EOR R1,R1,#1 ;Toggle the buffer number STR R1,[R14],#4 ;Store the new one back ADD R1,R14,R1,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 |Quartz$$Table|,CODE,READONLY DCD str__wSize ;For the error buffer DCD str__wSpace ;Pointer to the pointer DCD 0 ;No initialisation DCD 0 ;No finalisation ;----- That's all folks ----------------------------------------------------- END