
			The DOS Library
			===============

 Last month I introduced a program that could be run from the CLI and would
convert all characters in a text file to upper case. The biggest problem
with this program is that the name of the file to be converted is hard
coded into the program. This makes the program next to useless since you
would have to rename any file you wished to convert to the name coded into
the program. The program would be more useful if it were possible to enter
the name of the file to convert as a parameter at the CLI. For example, if
the program was called UCASE and the file to be converted was called
letter.doc on drive 1, then entering

	UCASE	df1:letter.doc

                                     at the CLI would convert the desired
file letter.doc to upper case. This is not as difficult as it may seem.
Read on...

 Reading CLI Parameters.
 -----------------------

 Any  program  that  is  launched  from  the  CLI  has the address of the
parameters following its name passed to it in register A0. The number of
characters in the parameter list, including the terminating line-feed
generated by pressing the RETURN key, is passed in the register D0. So
there will always be a parameter list, even if it is only a single
character, the line-feed.

 The parameter list is always terminated by the line-feed byte, $0A. As you
are now aware DOS ( and Intuition, when we get there ) functions require a
0 terminated string of characters, so the first task to accomplish is to 0
terminate the parameter list. This can be done by converting the $0a byte
to a $00 byte. To achieve this the following line can be used:

		move.b		#0,-1(a0,d0)

 Let's check this. Register a0 holds the address of the start of the
parameter list and register d0 the number of characters. So adding d0 to a0
will form the address of the byte immediately following the parameter list.
Eg. The parameter list contains the four characters 'RAM:', and the
terminating line-feed:

     char 1  char 2  char 3  char 4   char 5     char 6
	R	A	M	:	$0a	next byte
	^	^	^	^	 ^	    ^
       a0     a0+1    a0+2    a0+3     a0+4       a0+5

 As you will recall d0 contains the number of characters in the parameter
list including the line-feed, in this case 5. The address formed by adding
d0 to a0 ( a0+d0 ) is a0+5. This is the byte after the parameter list. In
fact a0+d0 will always be the address of the byte following the parameter
list. So it is necessary to subtract 1 from the address formed by adding a0
and d0. This is done by using address register indirect with a 16 bit
displacement and 8 bit index value, or simply -1(a0,d0). This forms the
address a0+d0.w-1 which is what we require.

 That's enough theory. Once you have 0 terminated the parameter list, you
can use it as the filename of the file to convert. See dos_eg1.s for the
complete program and compare this to the original program given last month 
( you never copied over it did you ).

; DOS_eg1.s

; Here is the program that will convert any file ( of 10000 bytes or less )
;from upper case to lower case. All you have to do is enter the files name
;as a parameter at the CLI.


;--------------	To start with, the INCLUDE files we require.

		incdir		sys:include/		specify directory
		include		exec/exec_lib.i
		include		exec/exec.i
		include		libraries/dos_lib.i
		include		libraries/dos.i

;--------------	First then, 0 terminate parameter list.

		move.l		#0,-1(a0,d0)

;--------------	Now save start of parameter list as filename pointer

		move.l		a0,filename

;--------------	Open the DOS library

		lea		dosname,a1		a1->library name
		moveq.l		#0,d0			d0=0,any version
		CALLEXEC	OpenLibrary		open the library
		move.l		d0,_DOSBase		save base ptr
		beq		error			leave if error

;--------------	Allocate memory for our text buffer

		move.l		#10000,d0		d0=size in bytes
		move.l		#MEMF_PUBLIC!MEMF_CLEAR,d1 d1=requirements
		CALLEXEC	AllocMem		ask for memory
		move.l		d0,buffer		save mem addr
		beq		error1			leave if error

;--------------	Open file for reading

		move.l		filename,d1		d1=addr of filename
		move.l		#MODE_OLDFILE,d2	d2=access mode
		CALLDOS		Open			open the file
		move.l		d0,filehd		save handle
		beq		error2			leave if error

;--------------	Read data from file into buffer

		move.l		filehd,d1		d1=file handle
		move.l		buffer,d2		d2=addr of buffer
		move.l		#10000,d3		d3=max num of chars
		CALLDOS		Read			read data from file
		move.l		d0,file_len		save num of chars

;--------------	Close the file

		move.l		filehd,d1		d1=file handle
		CALLDOS		Close			close the file

;--------------	Call subroutine to do conversion. Note I have used the 
;		routine detailed above, but as a subroutine. If you wanted
;		to process the file in some other way, you just change the
;		subroutine.

		bsr		convert

;--------------	Open the file using MODE_NEWFILE to erase old contents

		move.l		filename,d1		d1=filename
		move.l		#MODE_NEWFILE,d2	d2=access mode
		CALLDOS		Open			and open file
		move.l		d0,filehd		save handle
		beq		error2			leave if error

;--------------	Write contents of buffer to the file

		move.l		filehd,d1		d1=files handle
		move.l		buffer,d2		d2=addr of buffer
		move.l		file_len,d3		d3=size of buffer
		CALLDOS		Write			write buffer

;--------------	Close the file

		move.l		filehd,d1		d1=file handle
		CALLDOS		Close			and close it

;--------------	Release buffer memory

error2		move.l		buffer,a1		a1=addr of buffer
		move.l		#10000,d0		d0=size allocated
		CALLEXEC	FreeMem			and release it

;--------------	Close the DOS library

error1		move.l		_DOSBase,a1		a1=lib base ptr
		CALLEXEC	CloseLibrary		and close it

error		rts					all done so quit

;--------------
;--------------	SUBROUTINE AREA
;--------------

;-------------- Subroutine to convert chars in buffer from lower case to
;		upper case.

; Initialise the data counter

convert		move.l		file_len,d0	d0 is counter
		subq.l		#1,d0		adjust for dbra
		
; Get address of start of buffer into register a0.

		move.l		buffer,a0	a0=start addr of buffer

; Move codes for 'a' and 'z' into data registers. This will speed loop up.

		move.b		#'a',d1		d1=code of char 'a'
		move.b		#'z',d2		d2=code of char 'z'

; If char is less than 'a' it is not lower case, so don't convert it.

char_loop	cmp.b		(a0)+,d1
		bgt.s		not_lower_case

; If char is greater than 'z' it is not lower case, so don't convert it.

		cmp.b		-1(a0),d2
		blt.s		not_lower_case 

; Char must be lower case so convert it.

		sub.b		#$20,-1(a0)	convert byte

; Test for end of data. Loop back if not there yet.

not_lower_case	dbra		d0,char_loop	loop until end of data

		rts

;--------------
;--------------	DATA AREA
;--------------

dosname		dc.b		'dos.library',0
		even
_DOSBase	dc.l		0

filename	dc.l		0
		even
filehd		dc.l		0
buffer		dc.l		0
file_len	dc.l		0

 At this point it is worth considering another option. As you are probably
aware, most of the commands in the C directory and also most PD utilities
will display usage instructions if a ? is entered as the first or only
parameter. You can test for the ? very simply:

		cmpi.b		#'?',(a0)
		beq		usage_routine

 Your usage routine could now display some text in the CLI window. Wait a
minute, you can't do that yet .......

 CLI Input and Output.
 ---------------------

 You can probably guess what's coming next. DOS treats the CLI just like a
disc file. You can write characters to it using Write and read keystrokes
from it using Read.

 What you need first is a 'handle' for the CLI in order to use Read and
Write. This is where the CLI differs from disc files in that it has one
'handle ' for input ( Read ) and one for outputc ( Write ). All you need do
is ask DOS for these handles, you do this by using the subroutines 

	Input ()  and   Output ()

 These subroutines require no parameters to be set prior to calling and
return the relevant 'handle' in register d0. Once you have obtained the
'handle' for input or output you can store it away for use in your program.
There is no need to repeatedly call Input and Output. Here is a very basic
example of Output to demonstrate the point. This is dos_eg2.s:

; DOS_eg2.s

; Writing to the CLI window

;--------------	The INCLUDES

		incdir		sys:include/
		include		exec/exec_lib.i
		include		libraries/dos_lib.i

;--------------	Open the DOS library

Start		lea		dosname,a1	a1-->lib name
		moveq.l		#0,d0		d0=0; any version
		CALLEXEC	OpenLibrary	open it
		move.l		d0,_DOSBase	save base pointer
		beq.s		error		quit if error

;--------------	Get CLI output handle

		CALLDOS		Output		get output handle
		move.l		d0,CLI_out	and store it
		beq.s		error_no_out	quit if no handle

;--------------	Write some text into CLI window

		move.l		CLI_out,d1	d1=file handle
		move.l		#message,d2	d2=addr of message
		moveq.l		#msg_len,d3	d3=length of message
		CALLDOS		Write		write text into CLI

;--------------	Close DOS library

error_no_out	move.l		_DOSBase,a1	a1=lib base address
		CALLEXEC	CloseLibrary	close the library

;--------------	Finish

error		rts				and quit

;--------------	Variables and Strings 

dosname		dc.b		'dos.library',0
		even
_DOSBase	dc.l		0

CLI_out		dc.l		0

message		dc.b		$0a,'Hi world and all that.......',$0a
msg_len		equ		*-message
		even
-----------------------------------------------------------------------

 So back to our original program, lets add some usage instructions:



; DOS_eg3.s

; Here is the program that will convert any file ( of 10000 bytes or less )
;from upper case to lower case. All you have to do is enter the files name
;as a parameter at the CLI.


;--------------	To start with, the INCLUDE files we require.

		incdir		sys:include/		specify directory
		include		exec/exec_lib.i
		include		exec/exec.i
		include		libraries/dos_lib.i
		include		libraries/dos.i

;--------------	First then, 0 terminate parameter list.

		move.b		#0,-1(a0,d0)

;--------------	Now save start of parameter list as filename pointer

		move.l		a0,filename

;--------------	Open the DOS library

		lea		dosname,a1		a1->library name
		moveq.l		#0,d0			d0=0,any version
		CALLEXEC	OpenLibrary		open the library
		move.l		d0,_DOSBase		save base ptr
		beq		error			leave if error

;--------------	Now the DOS lib is open, check for usage instructions.

		move.l		filename,a0		a0-->parameter list
		cmpi.b		#'?',(a0)		is 1st param a ?
		beq		usage_msg		if so brach

;--------------	Allocate memory for our text buffer

		move.l		#10000,d0		d0=size in bytes
		move.l		#MEMF_PUBLIC!MEMF_CLEAR,d1 d1=requirements
		CALLEXEC	AllocMem		ask for memory
		move.l		d0,buffer		save mem addr
		beq		error1			leave if error

;--------------	Open file for reading

		move.l		filename,d1		d1=addr of filename
		move.l		#MODE_OLDFILE,d2	d2=access mode
		CALLDOS		Open			open the file
		move.l		d0,filehd		save handle
		beq		error2			leave if error

;--------------	Read data from file into buffer

		move.l		filehd,d1		d1=file handle
		move.l		buffer,d2		d2=addr of buffer
		move.l		#10000,d3		d3=max num of chars
		CALLDOS		Read			read data from file
		move.l		d0,file_len		save num of chars

;--------------	Close the file

		move.l		filehd,d1		d1=file handle
		CALLDOS		Close			close the file

;--------------	Call subroutine to do conversion. Note I have used the 
;		routine detailed above, but as a subroutine. If you wanted
;		to process the file in some other way, you just change the
;		subroutine.

		bsr		convert

;--------------	Open the file using MODE_NEWFILE to erase old contents

		move.l		filename,d1		d1=filename
		move.l		#MODE_NEWFILE,d2	d2=access mode
		CALLDOS		Open			and open file
		move.l		d0,filehd		save handle
		beq		error2			leave if error

;--------------	Write contents of buffer to the file

		move.l		filehd,d1		d1=files handle
		move.l		buffer,d2		d2=addr of buffer
		move.l		file_len,d3		d3=size of buffer
		CALLDOS		Write			write buffer

;--------------	Close the file

		move.l		filehd,d1		d1=file handle
		CALLDOS		Close			and close it

;--------------	Release buffer memory

error2		move.l		buffer,a1		a1=addr of buffer
		move.l		#10000,d0		d0=size allocated
		CALLEXEC	FreeMem			and release it

;--------------	Close the DOS library

error1		move.l		_DOSBase,a1		a1=lib base ptr
		CALLEXEC	CloseLibrary		and close it

error		rts					all done so quit

;--------------
;--------------	SUBROUTINE AREA
;--------------

;--------------	The Usage routine. Not a subroutine proper, but I put it
;		here anyhow.

usage_msg	CALLDOS		Output			get CLI handle
		move.l		d0,d1			put it in d1
		beq.s		no_cli			whoops ! no CLI

		move.l		#usage,d2		d2=addr of message
		move.l		#usage_len,d3		d3=len of message
		CALLDOS		Write			write the message

no_cli		bra		error1			close DOS and finish


;-------------- Subroutine to convert chars in buffer from lower case to
;		upper case.

; Initialise the data counter

convert		move.l		file_len,d0	d0 is counter
		subq.l		#1,d0		adjust for dbra
		
; Get address of start of buffer into register a0.

		move.l		buffer,a0	a0=start addr of buffer

; Move codes for 'a' and 'z' into data registers. This will speed loop up.

		move.b		#'a',d1		d1=code of char 'a'
		move.b		#'z',d2		d2=code of char 'z'

; If char is less than 'a' it is not lower case, so don't convert it.

char_loop	cmp.b		(a0)+,d1
		bgt.s		not_lower_case

; If char is greater than 'z' it is not lower case, so don't convert it.

		cmp.b		-1(a0),d2
		blt.s		not_lower_case 

; Char must be lower case so convert it.

		sub.b		#$20,-1(a0)	convert byte

; Test for end of data. Loop back if not there yet.

not_lower_case	dbra		d0,char_loop	loop until end of data

		rts

;--------------
;--------------	DATA AREA
;--------------

dosname		dc.b		'dos.library',0
		even
_DOSBase	dc.l		0

filename	dc.l		0
		even
filehd		dc.l		0
buffer		dc.l		0
file_len	dc.l		0

usage		dc.b		$0a,'UCASE, a utility to convert all characters'
		dc.b		' in a file',$0a,' from lower case to upper case.',$0a,$0a
		dc.b		'From the CLI :',$0a
		dc.b		'              UCASE <filename>',$0a,$0a
		dc.b		'Where <filename> is the name of the file to convert.'
		dc.b		$0a,$0a,'     © M.Meany, March 91',$0a,$0a
usage_len	equ		*-usage
		even
------------------------------------------------------------------------------

 This ability to write into the CLI window opens up new areas for you to
consider when programming. Usage text is just the start. For instance, have
you ever run a program and nothing has happened ? anoying isn't it. Things
would be so much better if the program told you why nothing had happened, for
instance 'Unable to open file for reading' or 'Unable to open file for 
Writing'. Well all this is now possible.

 What is required is a subroutine that will display any message in the CLI
window, given the start address of the message. This one subroutine could
then be used to display usage instructions, error messages or just general
progress of the program. The more user-friendly you make a program, the more
likley it will be used.

 So lets write this general message printing routine. Firstly, recall that 
the DOS command Write requires a file handle, address of message and length
of message. To make using this routine as easy as possible only the address
of the message will be required. The subroutine will calculate the length of
the message and assume the file handle is stored at STD_OUT ( for all you C
lovers out there ). If there is no handle stored at STD_OUT the subroutine
will make a quick exit. The last problem then is determaning the length of 
the message to be printed. DOS messages do not have to be 0 terminated, but
in order to be able to locate the end of a message we need terminator, so
this routine will assume all messages are 0 terminated, but the 0 byte will
not be displayed. Here then is the required source:

;--------------	Subroutine to display any message in the CLI window

; Entry		a0 must hold address of 0 terminated message.
;		STD_OUT should hold handle of file to be written to.
;		DOS library must be open

PrintMsg	move.l		a0,a1		get a working copy

;--------------	Determine length of message

		moveq.l		#-1,d3		reset counter
.loop		addq.l		#1,d3		bump counter
		tst.b		(a1)+		is this byte a 0
		bne.s		.loop		if not loop back

;--------------	Make sure there was a message

		tst.l		d3		was there a message ?
		beq.s		.error		if not, graceful exit

;--------------	Get handle of output file

		move.l		STD_OUT,d1	d1=file handle
		beq.s		.error		leave if no handle

;--------------	Now print the message
;		At this point, d3 already holds length of message
;		and d1 holds the file handle.

		move.l		a0,d2		d2=address of message
		CALLDOS		Write		and print it

;--------------	All done so finish

.error		rts

----------------------------------------------------------------------

 Before implementing this in the Ucase program lets write a short program
just to make sure it works. The following program uses the subroutine to
write three seperate messages to the CLI window.


; DOS_eg4.s

; Testing the PrintMsg subroutine

;--------------	The INCLUDES

		incdir		sys:include/
		include		exec/exec_lib.i
		include		libraries/dos_lib.i

;--------------	Open the DOS library

Start		lea		dosname,a1	a1-->lib name
		moveq.l		#0,d0		d0=0; any version
		CALLEXEC	OpenLibrary	open it
		move.l		d0,_DOSBase	save base pointer
		beq.s		error		quit if error

;--------------	Get CLI output handle

		CALLDOS		Output		get output handle
		move.l		d0,STD_OUT	and store it
		beq.s		error_no_out	quit if no handle

;--------------	Write some text into CLI window using PrintMsg

		lea		message,a0
		bsr		PrintMsg

		lea		msg1,a0
		bsr		PrintMsg

		lea		msg2,a0
		bsr		PrintMsg


;--------------	Close DOS library

error_no_out	move.l		_DOSBase,a1	a1=lib base address
		CALLEXEC	CloseLibrary	close the library

;--------------	Finish

error		rts				and quit

;--------------
;-------------- Subroutine Area
;--------------

;--------------	Subroutine to display any message in the CLI window

; Entry		a0 must hold address of 0 terminated message.
;		STD_OUT should hold handle of file to be written to.
;		DOS library must be open

PrintMsg	move.l		a0,a1		get a working copy

;--------------	Determine length of message

		moveq.l		#-1,d3		reset counter
.loop		addq.l		#1,d3		bump counter
		tst.b		(a1)+		is this byte a 0
		bne.s		.loop		if not loop back

;--------------	Make sure there was a message

		tst.l		d3		was there a message ?
		beq.s		.error		if not, graceful exit

;--------------	Get handle of output file

		move.l		STD_OUT,d1	d1=file handle
		beq.s		.error		leave if no handle

;--------------	Now print the message
;		At this point, d3 already holds length of message
;		and d1 holds the file handle.

		move.l		a0,d2		d2=address of message
		CALLDOS		Write		and print it

;--------------	All done so finish

.error		rts



;--------------
;--------------	Variables and Strings 
;--------------

dosname		dc.b		'dos.library',0
		even
_DOSBase	dc.l		0

STD_OUT		dc.l		0

message		dc.b		$0a,'Hi world and all that.......',$0a,0
		even
msg1		dc.b		'Lets just make sure',0
		even
msg2		dc.b		' that this works',$0a,$0a,0
		even

-------------------------------------------------------------------------

 Before going on to add this subroutine to the Ucase program I will just
point out that you could put any file handle into STD_OUT and use the
PrintMsg routine to write to any file. If your program had several files
open, by copying the handle into STD_OUT prior to calling PrintMsg, you
only need the one routine to write to all the files.

 Now for the almost complete version of Ucase. Messages are displayed in
abandon now so the user always knows what is going on. Nothing major has
changed, so I will not explain the listing in full:


; DOS_eg5.s

; Here is the program that will convert any file ( of 10000 bytes or less )
;from upper case to lower case. All you have to do is enter the files name
;as a parameter at the CLI.

; Useful messages are now printed all over the show, taking user-friendlieness
;to the extreme.


;--------------	To start with, the INCLUDE files we require.

		incdir		sys:include/		specify directory
		include		exec/exec_lib.i
		include		exec/exec.i
		include		libraries/dos_lib.i
		include		libraries/dos.i

;--------------	First then, 0 terminate parameter list.

Start		move.b		#0,-1(a0,d0)

;--------------	Now save start of parameter list as filename pointer

		move.l		a0,filename

;--------------	Open the DOS library

		lea		dosname,a1		a1->library name
		moveq.l		#0,d0			d0=0,any version
		CALLEXEC	OpenLibrary		open the library
		move.l		d0,_DOSBase		save base ptr
		beq		error			leave if error

;--------------	Get CLI output handle

		CALLDOS		Output			get handle
		move.l		d0,STD_OUT		save it
		beq		error1		leave if no handle

;--------------	Time for the first message, an introduction to the prog.

		lea		intro_msg,a0		a0=addr of msg
		bsr		PrintMsg		print it


;--------------	Check for usage instructions.

		move.l		filename,a0		a0-->parameter list
		cmpi.b		#'?',(a0)		is 1st param a ?
		bne		.continue		if not branch
		lea		usage,a0		a0=addr of msg
		bsr		PrintMsg		print it
		bra		error1			and quit

;--------------	Now display a message telling user what file is bieng
;		converted.

.continue	lea		convrt_msg,a0	a0=addr of msg
		bsr		PrintMsg	print it

		move.l		filename,a0	a0=address of filename
		bsr		PrintMsg	print it

		lea		convrt_msg1,a0	a0=addr of msg
		bsr		PrintMsg	print it

;--------------	Allocate memory for our text buffer

		move.l		#10000,d0		d0=size in bytes
		move.l		#MEMF_PUBLIC!MEMF_CLEAR,d1 d1=requirements
		CALLEXEC	AllocMem		ask for memory
		move.l		d0,buffer		save mem addr
		bne.s		.cont1		branch if all ok

		lea		no_mem_msg,a0	a0=addr of error msg
		bsr		PrintMsg	print it
		bra		error1		leave if error

;--------------	Open file for reading

.cont1		move.l		filename,d1		d1=addr of filename
		move.l		#MODE_OLDFILE,d2	d2=access mode
		CALLDOS		Open			open the file
		move.l		d0,filehd		save handle
		bne.s		.cont2		branch if ok

		lea		no_file_msg,a0	a0=addr of error msg
		bsr		PrintMsg	print it
		bra		error2		leave if error

;--------------	Read data from file into buffer

.cont2		move.l		filehd,d1		d1=file handle
		move.l		buffer,d2		d2=addr of buffer
		move.l		#10000,d3		d3=max num of chars
		CALLDOS		Read			read data from file
		move.l		d0,file_len		save num of chars

;--------------	Close the file

		move.l		filehd,d1		d1=file handle
		CALLDOS		Close			close the file

;--------------	Call subroutine to do conversion. Note I have used the 
;		routine detailed above, but as a subroutine. If you wanted
;		to process the file in some other way, you just change the
;		subroutine.

		bsr		convert

;--------------	Open the file using MODE_NEWFILE to erase old contents

		move.l		filename,d1		d1=filename
		move.l		#MODE_NEWFILE,d2	d2=access mode
		CALLDOS		Open			and open file
		move.l		d0,filehd		save handle
		bne.s		.cont3		branch if OK

		lea		no_out_file,a0	a0=addr of error msg
		bsr		PrintMsg	print it		
		bra		error2		leave if error

;--------------	Write contents of buffer to the file

.cont3		move.l		filehd,d1		d1=files handle
		move.l		buffer,d2		d2=addr of buffer
		move.l		file_len,d3		d3=size of buffer
		CALLDOS		Write			write buffer

;--------------	Close the file

		move.l		filehd,d1		d1=file handle
		CALLDOS		Close			and close it

;--------------	Release buffer memory

error2		move.l		buffer,a1		a1=addr of buffer
		move.l		#10000,d0		d0=size allocated
		CALLEXEC	FreeMem			and release it

;--------------	Close the DOS library

error1		lea		all_done_msg,a0	a0=addr of message
		bsr		PrintMsg	print it

		move.l		_DOSBase,a1		a1=lib base ptr
		CALLEXEC	CloseLibrary		and close it

error		rts					all done so quit

;--------------
;--------------	SUBROUTINE AREA
;--------------


;--------------	Subroutine to display any message in the CLI window

; Entry		a0 must hold address of 0 terminated message.
;		STD_OUT should hold handle of file to be written to.
;		DOS library must be open

PrintMsg	move.l		a0,a1		get a working copy

;--------------	Determine length of message

		moveq.l		#-1,d3		reset counter
.loop		addq.l		#1,d3		bump counter
		tst.b		(a1)+		is this byte a 0
		bne.s		.loop		if not loop back

;--------------	Make sure there was a message

		tst.l		d3		was there a message ?
		beq.s		.error		if not, graceful exit

;--------------	Get handle of output file

		move.l		STD_OUT,d1	d1=file handle
		beq.s		.error		leave if no handle

;--------------	Now print the message
;		At this point, d3 already holds length of message
;		and d1 holds the file handle.

		move.l		a0,d2		d2=address of message
		CALLDOS		Write		and print it

;--------------	All done so finish

.error		rts



;-------------- Subroutine to convert chars in buffer from lower case to
;		upper case.

; Initialise the data counter

convert		move.l		file_len,d0	d0 is counter
		subq.l		#1,d0		adjust for dbra
		
; Get address of start of buffer into register a0.

		move.l		buffer,a0	a0=start addr of buffer

; Move codes for 'a' and 'z' into data registers. This will speed loop up.

		move.b		#'a',d1		d1=code of char 'a'
		move.b		#'z',d2		d2=code of char 'z'

; If char is less than 'a' it is not lower case, so don't convert it.

char_loop	cmp.b		(a0)+,d1
		bgt.s		not_lower_case

; If char is greater than 'z' it is not lower case, so don't convert it.

		cmp.b		-1(a0),d2
		blt.s		not_lower_case 

; Char must be lower case so convert it.

		sub.b		#$20,-1(a0)	convert byte

; Test for end of data. Loop back if not there yet.

not_lower_case	dbra		d0,char_loop	loop until end of data

		rts

;--------------
;--------------	DATA AREA
;--------------

dosname		dc.b		'dos.library',0
		even
_DOSBase	dc.l		0

STD_OUT		dc.l		0

filename	dc.l		0
		even
filehd		dc.l		0
buffer		dc.l		0
file_len	dc.l		0

;--------------	Messages that will be displayed in CLI window

intro_msg	dc.b		$0a,'Ucase, by M.Meany.',$0a,$0a,0
		even

usage		dc.b		$0a,'UCASE, a utility to convert all characters'
		dc.b		' in a file',$0a,' from lower case to upper case.',$0a,$0a
		dc.b		'From the CLI :',$0a
		dc.b		'              UCASE <filename>',$0a,$0a
		dc.b		'Where <filename> is the name of the file to convert.'
		dc.b		$0a,$0a,'     © M.Meany, March 91',$0a,$0a,0
		even

convrt_msg	dc.b		'Converting file : ',0
		even

convrt_msg1	dc.b		'  to upper case.',$0a,0
		even

no_mem_msg	dc.b		"ABORTED......Can't allocate memory for buffer.",$0a,0
		even

no_file_msg	dc.b		"ABORTED......Can't open file to read data.",$0a,0
		even

no_out_file	dc.b		"ABORTED......Can't open file to save data.",$0a,0
		even

all_done_msg	dc.b		"Thank you for using this utility. M.Meany.",$0a,$0a,0
		even

--------------------------------------------------------------------------

 Well that's it. Assemble it and give it a try. You will have to assemble it
to disc and give it a name as the Run option from Devpac does not allow you
to pass any parameters to the program.

 Try entering a ? for the usage instructions, then the name of a file for it
to convert. Lastly, give the name of a file that does not exsist.

 I am sure you will agree that this version is a vast improvement on the
original that we started with. This program may seem trivial, but it is the
concept that is important. Start writing user friendly programs from the
very beginning. Not only are they easier to use, but the comments may help
you debug a program that is not working correctly.

 Well earlier I said that this was almost the finished version, so what's
missing. Consider what happens if the program is run with no parameters at
all. It plunders on trying to open a non exsistant file and aborts when
the file fails to open. At least it does not crash, you may think. What is
needed is a routine that will ask the user to enter a filename if none is
passed from the CLI. For this we need to be able to Read from the keyboard.

 To read characters from the keyboard, as you may have guessed, we use the
Read command. If you remember, this subroutine requires three parameters:

d1	file handle
d2	address of buffer
d3	length of buffer

 Well when using Read to get input from the user, these can be thought of as

d1	handle of keyboard ( CLI Input handle )
d2	address of buffer to store key codes ( ASCII chars ) in
d3	maximum number of chars that can fit in buffer

 When you call Read in this fashion, the system waits until the user presses
the Return key. It then passes the characters read into your buffer until
either the buffer is full or the line-feed byte is passed. This means that
if your buffer is not large enough you may not get all the characters the
user typed. Don't panic ! The system remembers these and the next time you
use read, the remaining bytes are read. This can be a real pain in the neck
at times, so I tend to use a large buffer and hopr the user has a little 
common sense ( something you should never do as the user is bound to make a 
hash of things, your program will crash or malfunction and you will get 
complaints for being such a naff coder ). In truth, I hate using the Read
function to get characters from the keyboard, but other methods require far
to much explanation to go into just yet ( we will get there though ). It is
worth knowing that Read returns the number of characters read in register
d0, you can make use of this as shown in the following example.

 So, to get the CLI input handle, call the subroutine Input (). This requires
no parameters and returns the desired handle in register D0 in the same way
as Output. Lets look at a simple example first. This asks the user to enter
his ( /her ) name ( note the message that saves face, max 32 chars he he ).
Once the user has enterd their name a small greeting is displayed.


; DOS_eg6.s

; Reading the keyboard.

;--------------	The INCLUDES

		incdir		sys:include/
		include		exec/exec_lib.i
		include		libraries/dos_lib.i

;--------------	Open the DOS library

Start		lea		dosname,a1	a1-->lib name
		moveq.l		#0,d0		d0=0; any version
		CALLEXEC	OpenLibrary	open it
		move.l		d0,_DOSBase	save base pointer
		beq		error		quit if error

;--------------	Get CLI output handle

		CALLDOS		Output		get output handle
		move.l		d0,CLI_out	and store it
		beq		error_no_out	quit if no handle

;--------------	Get CLI input handle ( keyboard )

		CALLDOS		Input		get input handle
		move.l		d0,CLI_in	and store it
		beq.s		error_no_out	quit if no handle

;--------------	Write some text into CLI window

		move.l		CLI_out,d1	d1=file handle
		move.l		#message,d2	d2=addr of message
		moveq.l		#msg_len,d3	d3=length of message
		CALLDOS		Write		write text into CLI

;--------------	Get users reply

		move.l		CLI_in,d1	d1=file handle (keyboard)
		move.l		#buffer,d2	d2=addr of buffer
		move.l		#buf_len,d3	d3=max num of chars
		CALLDOS		Read		get user reply
		move.l		d0,reply_len	save reply length

;--------------	Write greeting intoo CLI window

		move.l		CLI_out,d1	d1=file handle
		move.l		#message1,d2	d2=addr of message
		move.l		#msg1_len,d3	d3=length of message
		CALLDOS		Write		write text into CLI

;--------------	Echo users name into CLI window

		move.l		CLI_out,d1	d1=file handle
		move.l		#buffer,d2	d2=addr of message
		move.l		reply_len,d3	d3=length of message
		CALLDOS		Write		write text into CLI

;--------------	Close DOS library

error_no_out	move.l		_DOSBase,a1	a1=lib base address
		CALLEXEC	CloseLibrary	close the library

;--------------	Finish

error		rts				and quit

;--------------	Variables and Strings 

dosname		dc.b		'dos.library',0
		even
_DOSBase	dc.l		0

CLI_out		dc.l		0
CLI_in		dc.l		0

reply_len	dc.l		0

message		dc.b		$0a,'Please enter your name ( max 32 chars )',$0a
msg_len		equ		*-message
		even

buffer		ds.b		34		leave a little extra !
buf_len

message1	dc.b		$0a,'Good-day to you '
msg1_len	equ		*-message1

--------------------------------------------------------------------------

 As an exercise, modify this program to use the PrintMsg subroutine.

 Now we need to develop a subroutine that will display a message asking the
user to enter a filename and then get the filename. The filename will have to
be 0 terminated and its address stored at label filename to maintain 
compatability with the program so far. I am also giving the user a chance to
quit the program by just pressing return, always useful for people just
browsing a utility disc or running a program in error.


;--------------	Subroutine to get a filename from user

;Entry		none

;Exit		d0=0 if user wishes to quit or if no keyboard handle
;		   could be obtained.

;--------------	Get keyboard handle

GetFileName	CALLDOS		Input		get keyboard handle
		move.l		d0,STD_IN	store it
		beq.s		.error		leave if no handle

;--------------	Display a prompt to the user

		lea		get_file,a0	a0=addr of prompt message
		bsr		PrintMsg	print it

;--------------	Get filename

		move.l		STD_IN,d1	d1=handle
		move.l		#key_buffer,d2	d2=addr of buffer
		move.l		#buf_len,d3	d3=max num of chars to read
		CALLDOS		Read		get user input

;--------------	Save addr of filename and 0 terminate it

		lea		key_buffer,a0	a0=addr of filename
		move.l		a0,filename	save addr of filename
		move.b		#0,-1(a0,d0)	0 terminate

;--------------	Get first character of filename into register d0. If this is
;		a 0 byte then the user pressed return and wants to quit. This
;		value will be passed back to the calling program.

		moveq.l		#0,d0		clear d0
		move.b		(a0),d0		get 1st char into d0

;--------------	And return

.error		rts

--------------------------------------------------------------------------

 If Input fails to get a handle for the keyboard, d0 will contain zero. Also
if the user presses only return, the first byte in the buffer will be $0a.
This is converted to a 0 byte and then loaded into register d0 before the
routine finishes. So no keyboard handle or no user input will mean d0=0 on
return. If the user did enter a file name then d0=code of 1st character. The
calling program only has to test d0, if it is non-zero a filename was enterd,
if not the program can quit.

 So here is the final version ( for this month anyway ):



; DOS_eg7.s

; Here is the program that will convert any file ( of 10000 bytes or less )
;from upper case to lower case. All you have to do is enter the files name
;as a parameter at the CLI.

; Useful messages are now printed all over the show, taking user-friendlieness
;to the extreme.

; Added a routine to get filename from user.

;--------------	To start with, the INCLUDE files we require.

		incdir		sys:include/		specify directory
		include		exec/exec_lib.i
		include		exec/exec.i
		include		libraries/dos_lib.i
		include		libraries/dos.i

;--------------	First then, 0 terminate parameter list.

Start		move.b		#0,-1(a0,d0)

;--------------	Now save start of parameter list as filename pointer

		move.l		a0,filename

;--------------	Open the DOS library

		lea		dosname,a1		a1->library name
		moveq.l		#0,d0			d0=0,any version
		CALLEXEC	OpenLibrary		open the library
		move.l		d0,_DOSBase		save base ptr
		beq		error			leave if error

;--------------	Get CLI output handle

		CALLDOS		Output			get handle
		move.l		d0,STD_OUT		save it
		beq		error1		leave if no handle

;--------------	Time for the first message, an introduction to the prog.

		lea		intro_msg,a0		a0=addr of msg
		bsr		PrintMsg		print it


;--------------	Check for usage instructions.

		move.l		filename,a0		a0-->parameter list
		cmpi.b		#'?',(a0)		is 1st param a ?
		bne		.continue		if not branch
		lea		usage,a0		a0=addr of msg
		bsr		PrintMsg		print it
		bra		error1			and quit

;--------------	Check that a file name has been given

.continue	move.l		filename,a0		a0-->parameter list
		tst.b		(a0)			is 1st byte a 0
		bne.s		.ok			if not branch

		bsr		GetFileName	else call subroutine
						;to get a filename from user

		tst.l		d0		does user want to quit?
		beq		error1		if so leave

;--------------	Now display a message telling user what file is bieng
;		converted.

.ok		lea		convrt_msg,a0	a0=addr of msg
		bsr		PrintMsg	print it

		move.l		filename,a0	a0=address of filename
		bsr		PrintMsg	print it

		lea		convrt_msg1,a0	a0=addr of msg
		bsr		PrintMsg	print it

;--------------	Allocate memory for our text buffer

		move.l		#10000,d0		d0=size in bytes
		move.l		#MEMF_PUBLIC!MEMF_CLEAR,d1 d1=requirements
		CALLEXEC	AllocMem		ask for memory
		move.l		d0,buffer		save mem addr
		bne.s		.cont1		branch if all ok

		lea		no_mem_msg,a0	a0=addr of error msg
		bsr		PrintMsg	print it
		bra		error1		leave if error

;--------------	Open file for reading

.cont1		move.l		filename,d1		d1=addr of filename
		move.l		#MODE_OLDFILE,d2	d2=access mode
		CALLDOS		Open			open the file
		move.l		d0,filehd		save handle
		bne.s		.cont2		branch if ok

		lea		no_file_msg,a0	a0=addr of error msg
		bsr		PrintMsg	print it
		bra		error2		leave if error

;--------------	Read data from file into buffer

.cont2		move.l		filehd,d1		d1=file handle
		move.l		buffer,d2		d2=addr of buffer
		move.l		#10000,d3		d3=max num of chars
		CALLDOS		Read			read data from file
		move.l		d0,file_len		save num of chars

;--------------	Close the file

		move.l		filehd,d1		d1=file handle
		CALLDOS		Close			close the file

;--------------	Call subroutine to do conversion. Note I have used the 
;		routine detailed above, but as a subroutine. If you wanted
;		to process the file in some other way, you just change the
;		subroutine.

		bsr		convert

;--------------	Open the file using MODE_NEWFILE to erase old contents

		move.l		filename,d1		d1=filename
		move.l		#MODE_NEWFILE,d2	d2=access mode
		CALLDOS		Open			and open file
		move.l		d0,filehd		save handle
		bne.s		.cont3		branch if OK

		lea		no_out_file,a0	a0=addr of error msg
		bsr		PrintMsg	print it		
		bra		error2		leave if error

;--------------	Write contents of buffer to the file

.cont3		move.l		filehd,d1		d1=files handle
		move.l		buffer,d2		d2=addr of buffer
		move.l		file_len,d3		d3=size of buffer
		CALLDOS		Write			write buffer

;--------------	Close the file

		move.l		filehd,d1		d1=file handle
		CALLDOS		Close			and close it

;--------------	Release buffer memory

error2		move.l		buffer,a1		a1=addr of buffer
		move.l		#10000,d0		d0=size allocated
		CALLEXEC	FreeMem			and release it

;--------------	Close the DOS library

error1		lea		all_done_msg,a0	a0=addr of message
		bsr		PrintMsg	print it

		move.l		_DOSBase,a1		a1=lib base ptr
		CALLEXEC	CloseLibrary		and close it

error		rts					all done so quit

;--------------
;--------------	SUBROUTINE AREA
;--------------

;--------------	Subroutine to get a filename from user

;Entry		none

;Exit		d0=0 if user wishes to quit or if no keyboard handle
;		   could be obtained.

;--------------	Get keyboard handle

GetFileName	CALLDOS		Input		get keyboard handle
		move.l		d0,STD_IN	store it
		beq.s		.error		leave if no handle

;--------------	Display a prompt to the user

		lea		get_file,a0	a0=addr of prompt message
		bsr		PrintMsg	print it

;--------------	Get filename

		move.l		STD_IN,d1	d1=handle
		move.l		#key_buffer,d2	d2=addr of buffer
		move.l		#buf_len,d3	d3=max num of chars to read
		CALLDOS		Read		get user input

;--------------	Save addr of filename and 0 terminate it

		lea		key_buffer,a0	a0=addr of filename
		move.l		a0,filename	save addr of filename
		move.b		#0,-1(a0,d0)	0 terminate

;--------------	Get first character of filename into register d0. If this is
;		a 0 byte then the user pressed return and wants to quit. This
;		value will be passed back to the calling program.

		moveq.l		#0,d0		clear d0
		move.b		(a0),d0		get 1st char into d0

;--------------	And return

.error		rts


;--------------	Subroutine to display any message in the CLI window

; Entry		a0 must hold address of 0 terminated message.
;		STD_OUT should hold handle of file to be written to.
;		DOS library must be open

PrintMsg	move.l		a0,a1		get a working copy

;--------------	Determine length of message

		moveq.l		#-1,d3		reset counter
.loop		addq.l		#1,d3		bump counter
		tst.b		(a1)+		is this byte a 0
		bne.s		.loop		if not loop back

;--------------	Make sure there was a message

		tst.l		d3		was there a message ?
		beq.s		.error		if not, graceful exit

;--------------	Get handle of output file

		move.l		STD_OUT,d1	d1=file handle
		beq.s		.error		leave if no handle

;--------------	Now print the message
;		At this point, d3 already holds length of message
;		and d1 holds the file handle.

		move.l		a0,d2		d2=address of message
		CALLDOS		Write		and print it

;--------------	All done so finish

.error		rts



;-------------- Subroutine to convert chars in buffer from lower case to
;		upper case.

; Initialise the data counter

convert		move.l		file_len,d0	d0 is counter
		subq.l		#1,d0		adjust for dbra
		
; Get address of start of buffer into register a0.

		move.l		buffer,a0	a0=start addr of buffer

; Move codes for 'a' and 'z' into data registers. This will speed loop up.

		move.b		#'a',d1		d1=code of char 'a'
		move.b		#'z',d2		d2=code of char 'z'

; If char is less than 'a' it is not lower case, so don't convert it.

char_loop	cmp.b		(a0)+,d1
		bgt.s		not_lower_case

; If char is greater than 'z' it is not lower case, so don't convert it.

		cmp.b		-1(a0),d2
		blt.s		not_lower_case 

; Char must be lower case so convert it.

		sub.b		#$20,-1(a0)	convert byte

; Test for end of data. Loop back if not there yet.

not_lower_case	dbra		d0,char_loop	loop until end of data

		rts

;--------------
;--------------	DATA AREA
;--------------

dosname		dc.b		'dos.library',0
		even
_DOSBase	dc.l		0

STD_OUT		dc.l		0
STD_IN		dc.l		0

filename	dc.l		0
		even
filehd		dc.l		0
buffer		dc.l		0
file_len	dc.l		0

;-------------- Buffer for keyboard entry

key_buffer		ds.b		200
buf_len		equ		*-buffer

;--------------	Messages that will be displayed in CLI window

intro_msg	dc.b		$0a,'Ucase, by M.Meany.',$0a,$0a,0
		even

usage		dc.b		$0a,'UCASE, a utility to convert all characters'
		dc.b		' in a file',$0a,' from lower case to upper case.',$0a,$0a
		dc.b		'From the CLI :',$0a
		dc.b		'              UCASE <filename>',$0a,$0a
		dc.b		'Where <filename> is the name of the file to convert.'
		dc.b		$0a,$0a,'     © M.Meany, March 91',$0a,$0a,0
		even

convrt_msg	dc.b		'Converting file : ',0
		even

convrt_msg1	dc.b		'  to upper case.',$0a,0
		even

no_mem_msg	dc.b		"ABORTED......Can't allocate memory for buffer.",$0a,0
		even

no_file_msg	dc.b		"ABORTED......Can't open file to read data.",$0a,0
		even

no_out_file	dc.b		"ABORTED......Can't open file to save data.",$0a,0
		even

all_done_msg	dc.b		"Thank you for using this utility. M.Meany.",$0a,$0a,0
		even

get_file	dc.b		"You must specify a file name !",$0a
		dc.b		"Please enter a file name or press return to quit.",$0a
		dc.b		0
		even

--------------------------------------------------------------------------

 Well we now have completed a utility that is worthy of showing off. Even
if it doesn't do anything remarkable, it is user friendly and almost bug
free. 


 So what can we possibly modify now. Well, the program is still restricted 
to files of 10000 bytes or less. What is required is a routine that first
examines the file, determines its length and then allocates memory for the
buffer, but that's another story to be taken up later ( next month or the
one after ).

						Mark.

 If you have any comments on this series of tutorials, please rush them to
me on disc. Constructive critisism always welcomed.

