;
; 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:
;
;			`%' [<type>] <digit>
;
;		(spaces are for clarity -- in fact you must not include
;		spaces in the format string.)
;
;		<digit> 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 <type>.
;
;		<type> 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
