;
; string.s
;
; String handling routines (control terminated)
;
;  1994-1998 Straylight
;

;----- Licensing note -------------------------------------------------------
;
; This file is part of Straylight's BASIC Assembler Supplement.
;
; BAS 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.
;
; BAS 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 BAS.  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	sh.workspace

;----- Main code ------------------------------------------------------------
;
; No string routine corrupts the scratchpad.

		AREA	|BAS$$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_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	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 (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_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
		LDR	R14,str__buffNum	;Get the current buffer
		ADD	R14,R14,#1		;Bump the buffer count
		CMP	R14,#3			;Have we gone too far?
		MOVCS	R14,#0			;Yes -- go back then
		STR	R14,str__buffNum	;Store the new one back
		ADRL	R1,str__buffer		;Point to the actual buffer
		ADD	R1,R1,R14,LSL #8	;Point to correct buffer
		LDMFD	R13!,{PC}^		;Return to caller

		LTORG

;----- That's all folks -----------------------------------------------------

		END
