;
; test.s
;
; Tests a condition
;
;  1995-1998 Straylight
;

;----- Licensing note -------------------------------------------------------
;
; This file is part of Straylight's core utilities (coreutils).
;
; Coreutils 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.
;
; Coreutils 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 Coreutils.  If not, write to the Free Software Foundation,
; 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.

;----- Standard header ------------------------------------------------------

		GET	libs:header
		GET	libs:swis

		GET	libs:stream

;----- External dependencies ------------------------------------------------

		IMPORT	version

;----- Main code ------------------------------------------------------------

		AREA	|!!!Utility$$Code|,CODE,READONLY

; --- main ---
;
; On entry:	R0 == pointer to command string
;		R1 == pointer to command tail
;
; On exit:	May return an error
;
; Use:		Performs a test, and does things appropriately.

main		ROUT

		STR	R14,test_return		;Save the return address

		; --- Read the command line arguments ---

		ADR	R0,test_syntaxDef	;Find the definitions
		ADR	R2,test_args		;Point to argument buffer
		MOV	R3,#252			;Get the buffer size
		SWI	XOS_ReadArgs		;Read the arguments
		BVS	test_error		;If it failed, return now

		; --- Check for some easy cases ---

		LDR	R14,tArg_help		;Load the help switch
		CMP	R14,#0			;Is it enabled?
		BNE	test_help		;Yes -- give some help then

		; --- Sort out the test ---

		ADR	R0,tArg_file		;Point to file arg
		ADR	R1,tArg_exists		;And to the limit
		BL	test_which		;Which one is set?
		ADRCC	R0,test_noTest		;None -- point to error
		BCC	test_error		;And complain then
		MOV	R2,R0			;Look after this

		ADR	R0,tArg_exists		;Point to file qualifiers
		ADR	R1,tArg_limit		;And to the very end
		BL	test_which		;Which one is set?
		CMP	R2,#0			;Is this a file?
		CMPNE	R0,#-1			;No -- make sure no quals
		ADRNE	R0,test_badQuals	;If there are, find error
		BNE	test_error		;And complain
		MOV	R10,R0			;Look after this

		; --- Dispatch the test ---

		MOV	R14,PC			;Set up return address
		ADD	PC,PC,R2,LSL #2		;Dispatch
		B	%f00			;And continue when done

		B	test_file		;Handle a file test
		B	test_expr		;Handle an expression test
		B	test_key		;Handle a key test
		B	test_riscOs		;Handle an OS version test
		B	test_command		;Handle a command test
		B	test_true		;Handle a true test
		B	test_false		;Handle a false test

		; --- Now handle the results ---

00		LDR	R14,tArg_multiLine	;Is this a multiline?
		CMP	R14,#0			;Is the switch there?
		BNE	%50main			;Yes -- handle that then
		CMP	R0,#0			;Was the condition true?
		LDRNE	R0,tArg_then		;Yes -- find then clause
		LDREQ	R0,tArg_else		;No -- find else clause
		CMP	R0,#0			;Is the clause defined?
		SWINE	XOS_CLI			;Yes -- do it then
		BVS	test_error		;If it failed, die horribly
		B	test_end		;Otherwise end nicely

		; --- Deal with a multiline ---

50		CMP	R0,#0			;Did the test succeed?
		ADR	R0,test_uScore		;Point to variable name
		ADRNE	R1,test_vTrue		;Yes -- use true value
		ADREQ	R1,test_vFalse		;No -- use false value
		BL	test_setVar		;Set that up

		ADR	R0,test_else		;Point to variable name
		ADRNE	R1,test_eTrue		;Yes -- use true value
		ADREQ	R1,test_eFalse		;No -- use false value
		BL	test_setVar		;Set that up

		ADR	R0,test_endif		;Point to variable name
		ADR	R1,test_endVal		;Point to value
		BL	test_setVar		;Set that up
		B	test_end		;And return when done

test_uScore	DCB	"Alias$_",0
test_vTrue	DCB	"%*0",0
test_vFalse	DCB	"||",0
test_else	DCB	"Alias$Else",0
test_eTrue	DCB	"Set Alias$_ ||||",0
test_eFalse	DCB	"Set Alias$_ %%*0",0
test_endif	DCB	"Alias$EndIf",0
test_endVal	DCB	"Unset Alias$_|mUnset Alias$Else|m"
		DCB	"Unset Alias$EndIf",0

test_noTest	DCD	1
		DCB	"No condition to test",0

test_badQuals	DCD	1
		DCB	"File qualifier found, but not -file test",0
		ALIGN

test_syntaxDef	DCB	"help/s,"

		DCB	"then/k,"
		DCB	"else/k,"
		DCB	"multiline/s,"

		DCB	"file/k,"
		DCB	"expr/e/k,"
		DCB	"key/e/k,"
		DCB	"riscOs/k,"
		DCB	"command/k,"
		DCB	"true/s,"
		DCB	"false/s,"

		DCB	"exists/s,"
		DCB	"isDir/s,"
		DCB	"isFile/s,"
		DCB	"isType/k",0

		LTORG

; --- test_which ---
;
; On entry:	R0 == pointer to base argument
;		R1 == pointer to limit argument
;
; On exit:	CS if an argument matched, and
;		  R0 == index of argument chosen
;		else CC and
;		  R0 == -1
;
; Use:		Works out which argument in a collection is actually chosen.
;		an error is raised if more than one is chosen.

test_which	ROUT

		STMFD	R13!,{R1-R3,R14}	;Save some registers
		MOV	R2,R0			;Look after base pointer
		MOV	R0,#-1			;Initially, we have no choice
		MOV	R3,#-1			;Initialise a counter
00		ADD	R3,R3,#1		;Increment the counter
		CMP	R2,R1			;Finished scanning?
		BEQ	%10test_which		;Yes -- deal with that
		LDR	R14,[R2],#4		;Load the argument
		CMP	R14,#0			;Is this one enabled?
		BEQ	%b00			;No -- ignore it then
		CMP	R0,#-1			;Do we have a choice yet?
		MOVEQ	R0,R3			;No -- we do now
		BEQ	%b00			;So skip back again

		ADR	R0,test_badRadio	;Point to the error
		B	test_error		;And report it

10test_which	CMP	R0,#-1			;Did we get an answer?
		LDMFD	R13!,{R1-R3,R14}	;Restore registers
		ORRNES	PC,R14,#C_flag		;Yes -- return C set
		BICEQS	PC,R14,#C_flag		;No -- return C clear

test_badRadio	DCD	1
		DCB	"Bad options",0

		LTORG

; --- test_help ---
;
; On entry:	--
;
; On exit:	Doesn't
;
; Use:		Gives the user some help.

test_help	ROUT

		ADR	R0,test_helpText	;Point to the help text
		MOV	R1,#0			;Use the system dictionary
		LDR	R2,=version		;Find the version string
		ADRL	R14,main		;Point to the utility base
		ADD	R2,R14,R2		;Relocate the pointer
		SWI	XOS_PrettyPrint		;Write the text out
		B	test_end		;And finish the program

test_helpText	DCB	"test ",27,0,13
		DCB	13
		DCB	"Syntax: test <condition> <target>",13
		DCB	13
		DCB	"Tests a condition and then performs an operation "
		DCB	"based on the result.  Possible targets are:",13
		DCB	13
		DCB	"[-then <command>] [-else <command>]",13
		DCB	"-multiline",13
		DCB	13
		DCB	"-then ... -else ... works as expected.  "
		DCB	"-multiline defines a collection of alias "
		DCB	"commands, as follows:",13
		DCB	13
		DCB	"_",9,9,"prefix normal commands with this",13
		DCB	"else",9,9,"toggles whether `_' executes commands",13
		DCB	"endif",9,9,"undefines `_', `else' and `endif'",13
		DCB	13
		DCB	"Permitted tests are:",13
		DCB	13
		DCB	"-file <file> <qualifier>",13
		DCB	"-expr <expression>",13
		DCB	"-key <key number>",13
		DCB	"-riscos <version number>",13
		DCB	"-command <*command>",13
		DCB	"-true | -false",13
		DCB	13
		DCB	"The file qualifiers test various things about a "
		DCB	"file:",13
		DCB	13
		DCB	"-exists",9,9,"Ensure the file exists",13
		DCB	"-isDir",9,9,"Ensure it's a directory",13
		DCB	"-isFile",9,9,"Ensure it's a file",13
		DCB	"-isType <type>",9,"Ensure it's a file of given "
		DCB	"type",13
		DCB	13
		DCB	"Key numbers are given either as internal key "
		DCB	"numbers (see sapphire:intKeys for bindings) or as "
		DCB	"BASIC-style -ve INKEY numbers",13
		DCB	13
		DCB	"The -riscos test ensures that the current version "
		DCB	"of the OS is the same or later than that given.",13
		DCB	13
		DCB	"The -command test runs a command and ensures that "
		DCB	"it didn't return an error.  This may be useful "
		DCB	"just to suppress the error.",13
		DCB	0

		LTORG

; --- test_file ---
;
; On entry:	R10 == reason code
;
; On exit:	R0 == 0 if false, non-0 if true
;		R1-R11 corrupted
;
; Use:		Performs a file operation and returns its truth.

test_file	ROUT

		; --- Find the file information ---

		MOV	R0,#17			;Read file information
		LDR	R1,tArg_file		;Get the filename
		SWI	XOS_File		;Try to get this information
		MOVVS	R0,#0			;If failed, say not there
		CMP	R10,#0			;Just checking existance?
		CMPNE	R0,#0			;Or is the file not there?
		MOVEQS	PC,R14			;Yes -- return now

		CMP	R10,#-1			;Was one chosen?
		ADDNE	PC,PC,R10,LSL #2	;Yes -- dispatch it
		MOVS	PC,R14			;No op -- assume existance

		MOVS	PC,R14			;Dealt with this already
		B	test_fDir		;Check it's a directory
		B	test_fFile		;Check it's a file
		B	test_fType		;Check its type

test_fDir	AND	R0,R0,#2		;Leave only the dir bit
		MOVS	PC,R14			;And return

test_fFile	AND	R0,R0,#1		;Leave only the file bit
		MOVS	PC,R14			;And return

test_fType	ANDS	R0,R0,#1		;Test the file bit
		MOVEQS	PC,R14			;If clear, skip on
		MOV	R3,R2,LSL #12		;Look after the filetype
		MOV	R0,#31			;Convert filetype name
		LDR	R1,tArg_isType		;Find the type string
		SWI	XOS_FSControl		;Try to convert it
		BVS	test_error		;Fail if we couldn't
		CMP	R2,R3,LSR #20		;Compare the filetypes
		MOVEQ	R0,#1			;If match, say true
		MOVNE	R0,#0			;Otherwise say false
		MOVS	PC,R14			;And return

		LTORG

; --- test_expr ---
;
; On entry:	--
;
; On exit:	R0 == 0 if false, non-0 if true
;		R1-R11 corrupted
;
; Use:		Evaluates an expression and returns the result.

test_expr	ROUT

		LDR	R0,tArg_expr		;Find the expression result
		B	test_getValue		;Read the value

		LTORG

; --- test_key ---
;
; On entry:	--
;
; On exit:	R0 == 0 if false, non-0 if true
;		R1-R11 corrupted
;
; Use:		Tests a key on the keyboard.

test_key	STMFD	R13!,{R14}		;Save the link register
		LDR	R0,tArg_key		;Find the expression result
		BL	test_getValue		;Find the value
		TST	R0,#&80000000		;Is the result negative?
		EOREQ	R0,R0,#&FF		;No -- then do that then
		MOV	R2,#&FF			;Scan for the key
		AND	R1,R0,#&FF		;Only have lowest byte
		MOV	R0,#&81			;Get the OS_Byte code
		SWI	XOS_Byte		;Read the key pressedness
		BVS	test_error		;If it failed, return error
		MOV	R0,R1			;Get the result
		LDMFD	R13!,{PC}^		;And return

		LTORG

; --- test_riscOs ---
;
; On entry:	--
;
; On exit:	R0 == 0 if false, and non-0 if true
;		R1-R11 corrupted
;
; Use:		Returns the result of testing the OS version.

test_riscOs	ROUT

		STMFD	R13!,{R14}		;Save a register
		LDR	R0,tArg_riscOs		;Find the version string
		BL	test_getVersion		;Translate it for me
		MOV	R3,R0			;Look after this result

		MOV	R0,#129			;Read the OS version
		MOV	R1,#0			;Want the OS version
		MOV	R2,#255			;Still want it, dammit
		SWI	XOS_Byte		;Try to read it then
		BVS	test_error		;If it failed, report error

		ADR	R2,test_verTable	;Point to version table
		MOV	R0,#-1			;Start off with bad value
00		LDMIA	R2!,{R4,R5}		;Load the values out
		CMP	R4,R1			;Does this version match?
		MOVLE	R0,R5			;Yes -- use it then
		BGT	%b00			;Otherwise loop back

		CMP	R0,R3			;Compare with his version
		MOVGE	R0,#1			;If later or same, return ok
		MOVLT	R0,#0			;Otherwise return false
		LDMFD	R13!,{PC}^		;And return to caller

test_verTable	DCD	&A5,350
		DCD	&A4,310
		DCD	&A3,300
		DCD	&A2,201
		DCD	&A1,200
		DCD	&A0,120
		DCD	0,0

		LTORG

; --- test_command ---
;
; On entry:	--
;
; On exit:	R0 == 0 if false, non-0 if true
;
; Use:		Runs a command, and returns true if the command didn't
;		make an error.

test_command	ROUT

		LDR	R0,tArg_command		;Load the command string
		SWI	XOS_CLI			;Run the command
		MOVVC	R0,#1			;If OK, return true
		MOVVS	R0,#0			;Otherwise return false
		MOVS	PC,R14			;And return to caller

		LTORG

; --- test_true and test_false ---
;
; On entry:	--
;
; On exit:	R0 == 0 if false, non-0 if true
;		R1-R11 corrupted
;
; Use:		Return particular values.

test_true	MOV	R0,#1			;Return true
		MOVS	PC,R14			;And return

test_false	MOV	R0,#0			;Return false
		MOVS	PC,R14			;And return

		LTORG

; --- test_getValue ---
;
; On entry:	R0 == pointer to expression result
;
; On exit:	R0 == value read
;
; Use:		Reads the value of an expression from the OS_ReadArgs block.

test_getValue	ROUT

		STMFD	R13!,{R1,R2,R14}	;Save some registers
		LDRB	R14,[R0],#1		;Load the first byte
		CMP	R14,#0			;Is this an integer?
		ADRNE	R0,test_notAnInt	;No -- then find error
		BNE	test_error		;And complain
		AND	R2,R0,#3		;Find non-alignedness
		BIC	R0,R0,#3		;Round down a little
		LDMIA	R0,{R0,R1}		;Load the value out
		MOV	R2,R2,LSL #3		;Convert bits to bytes
		RSB	R14,R2,#32		;And find the other shift
		MOV	R0,R0,LSR R2		;Get the lower bits
		ORR	R0,R0,R1,LSL R14	;And the upper bits
		LDMFD	R13!,{R1,R2,PC}^	;And return to caller

test_notAnInt	DCD	1
		DCB	"Integer expected",0

		LTORG

; --- test_getVersion ---
;
; On entry:	R0 == pointer to version string
;
; On exit:	R0 == value of version number-
;
; Use:		Reads a version umber

test_getVersion	ROUT

		STMFD	R13!,{R1,R2,R14}	;Save some registers
		MOV	R2,#0			;Clear version accumulator

00		LDRB	R1,[R0],#1		;Load next byte from thing
		SUB	R14,R1,#'0'		;Convert digit to integer
		CMP	R14,#10			;Is it in range?
		ADDCC	R2,R2,R2,LSL #2		;Yes -- accumulate
		ADDCC	R2,R14,R2,LSL #1	;Multiply by 10 and add
		BCC	%b00			;And loop back round

		ADD	R2,R2,R2,LSL #2		;Multiply version by 100
		ADD	R2,R2,R2,LSL #2
		MOV	R2,R2,LSL #2

		CMP	R1,#'.'			;Is the character a dot?
		BNE	%f00			;No -- skip on then
		LDRB	R1,[R0],#1		;Load next byte from thing
		SUB	R14,R1,#'0'		;Convert digit to integer
		CMP	R14,#10			;Is it in range?
		ADDCC	R14,R14,R14,LSL #2	;Yes -- accumulate
		ADDCC	R2,R2,R14,LSL #1	;Do it the other way round
		LDRCCB	R1,[R0],#1		;Load next byte from thing
		SUB	R14,R1,#'0'		;Convert digit to integer
		CMP	R14,#10			;Is it in range?
		ADDCC	R2,R2,R14		;Yes -- add that on
		LDRCCB	R1,[R0],#1		;Load next byte from thing

00		CMP	R1,#&20			;Is this the very end?
		ADRCS	R0,test_badVer		;Point to error message
		BCS	test_error		;And complain bitterly
		MOV	R0,R2			;Get the version number
		LDMFD	R13!,{R1,R2,PC}^	;And return to caller

test_badVer	DCD	1
		DCB	"Bad version number",0

		LTORG

; --- test_setVar ---
;
; On entry:	R0 == variable name
;		R1 == pointer to value
;
; On exit:	--
;
; Use:		Sets the variable given to the given value.

test_setVar	ROUT

		STMFD	R13!,{R0-R4,R14}	;Save some registers
		MOV	R2,R1			;Point to the value
00		LDRB	R14,[R2],#1		;Load the next byte
		CMP	R14,#&20		;Is this the end yet?
		BCS	%b00			;No -- keep going
		SUB	R2,R2,R1		;Find the string length
		SUB	R2,R2,#1		;Don't count the terminator
		MOV	R3,#0			;Start at the beginning
		MOV	R4,#0			;Normal GS type variable
		SWI	XOS_SetVarVal		;Set the variable
		BVS	test_error		;If failed, abort
		LDMFD	R13!,{R0-R4,PC}^	;And return to caller

		LTORG

; --- test_end ---
;
; On entry:	--
;
; On exit:	--
;
; Use:		Returns to the operating system.

test_end	ROUT

		LDR	R14,test_return		;Find the return address
		BICS	PC,R14,#V_flag		;And return with no error

		LTORG

; --- test_error ---
;
; On entry:	R0 == pointer to error block
;
; On exit:	--
;
; Use:		Reports an error to the operating system.

test_error	ROUT

		LDR	R14,test_return		;Find the return address
		ORRS	PC,R14,#V_flag		;And return the error

		LTORG

;----- Workspace ------------------------------------------------------------

		^	0,R12
test_wStart	#	0

test_return	#	4			;Return address

test_args	#	0			;Argument output buffer
tArg_help	#	4			;Help switch
tArg_then	#	4			;Then command
tArg_else	#	4			;Else command
tArg_multiLine	#	4			;Multiline switch
tArg_file	#	4			;File condition
tArg_expr	#	4			;Expression condition
tArg_key	#	4			;Key condition
tArg_riscOs	#	4			;OS version condition
tArg_command	#	4			;Run a *command
tArg_true	#	4			;True condition
tArg_false	#	4			;False condition
tArg_exists	#	4			;Exists filetest
tArg_isDir	#	4			;IsDir filetest
tArg_isFile	#	4			;IsFile filetest
tArg_isType	#	4			;IsType filetest
tArg_limit	#	4			;End of the arguments

test_buffer	EQU	test_wStart+256		;Misc buffer for things

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

		END
