[net.micro.amiga] ansiecho reposting + other asms

dewi@druca.UUCP (WilliamsD) (04/14/86)

Well, it's just been pointed out to me that the ansiecho source that I
issued to the net a few weeks back was scrogged (I missed the comment to
that effect on this newsgroup).
It turned out that the public domain xmodem I *was* using dropped blocks on
a NAK (!) so it never even made it unscathed to the local UNIX box.
Long live kermit...

So here it is, reposted. Also thrown in are two other CLI routines. A "touch"
command, useful for taming a rogue make and getting rid of those "Future"
dates. WARNING: due to a bug in the RAM: handler, it won't update the datestamp
for ramdisk-resident files. It'll handle zero length files and checks for its
argument being a directory.

I've also added an assembler version of "list" called "alist", just in case
anybody wants to build on it. It's the bare-bones list, it just prints out
the file names. I originally wrote it as a response to a BBS discussion
that claimed that list and dir were slow because they were written in a
high-level language. In case you're curious, alist only turned out to be
around 2-3 seconds faster than list on my C: directory.

About the only advantage of this list command is its size, around 660
bytes (list is 8664 bytes long). Still, it shouldn't be that much work
to add "ls -l" functionality. If somebody does it, please send me a copy!

You'll need V1.1 of the Macro Assembler for these.

Finally, for amusement only, here's a small CLI mis-feature:

	makedir junk
	echo >junk blow away the directory!

Yes. If you do a list on junk, you'll find that it's changed to a text
file! Before you panic, this only works with *empty* directories. But
still...
		Dewi.

------------------------- snip it, snip it good -----------------------------
echo "creating source files:"
echo "\ttouch.asm"
cat <<"FUNKYSTUFF" >touch.asm
* TOUCH.ASM:    A recreation in assembler of the UN*X command of the same name.
*		Works by read & write rather than attempting to muck directly
*		with filesystem datestamps.
* Perpetrator:  Dewi Williams  ..!ihnp4!druca!dewi
*               Unconditionally placed in the public domain.

* Included equate files.
* -----------------------------------------------------------------------
      	NOLIST 
      	INCLUDE  "exec/types.i"
      	INCLUDE  "exec/libraries.i"
      	INCLUDE  "libraries/dos.i"
	INCLUDE	 "libraries/dosextens.i"
	INCLUDE  "exec/memory.i"
      	LIST

* External references
* -----------------------------------------------------------------------

	EXTERN_LIB	OpenLibrary
	EXTERN_LIB	Read
	EXTERN_LIB	Write
	EXTERN_LIB	Output
	EXTERN_LIB	Open
	EXTERN_LIB	Close
	EXTERN_LIB	Lock
	EXTERN_LIB	UnLock
	EXTERN_LIB	IoErr
	EXTERN_LIB	Seek
	EXTERN_LIB	AllocMem
	EXTERN_LIB	FreeMem
	EXTERN_LIB	Examine
	EXTERN_LIB	DeleteFile

* Local Equates
* ------------------------------------------------------------------------
SysBase  EQU   4        ; The one known address in the system.
TRUE     EQU   -1
FALSE    EQU   0
SEEKFAIL EQU   -1

* Macros
* ------------------------------------------------------------------------

* Calls to dos.library and exec library. Differing calls scrog different
* registers. I chose to take no chances...
callsys  MACRO
      MOVEM.L  D1-D7/A0-A6,-(SP)	; save registers
      CALLLIB      _LVO\1
      MOVEM.L  (SP)+,D1-D7/A0-A6        ; and restore (AmigaDOS scrogs!).
      ENDM
      
callexec MACRO
      MOVEM.L  D1-D7/A0-A6,-(SP)	; save registers
      LINKLIB	   _LVO\1,SysBase
      MOVEM.L  (SP)+,D1-D7/A0-A6        ; and restore (AmigaDOS scrogs!).      
      ENDM

* The print routine and argument setup
PRINT   MACRO
	lea.l	\1(PC),A0
	move.l  D1,-(SP)
	move.l	d5,d1
	jsr	print
	move.l  (SP)+,D1
	ENDM

* General purpose close, print, and quit macro
QUIT    MACRO
   callsys IoErr		; return from IoErr will be exit code
   move.l  D0,D6		; save error return. Could use stack...
   PRINT   \1
   callsys Close		; assumes handle in D1
   move.l  d6,d0		; restore error return
   bra	   FINISHED
   ENDM

* The code segment
* -------------------------------------------------------------------------
   RORG     0                 ; Relocatable code.

   jsr      argtrim           ; clean up command line

   ;------ get Exec's library base pointer:
   LEA.L    DOSName(PC),A1    ; name of dos library
   move.l   #LIBRARY_VERSION,d0

   callexec OpenLibrary,SysBase
   MOVE.L   D0,A6             ; Save library pointer (always in A6).
   BNE.S    gotdos

   ; Should really issue an alert here...
   moveq    #RETURN_FAIL,D0   ; give up
   bra      FINISHED

gotdos:

*  Obtain the output handle (needed for error prints).
   callsys   Output
   MOVE.L   D0,D5             ; Save for the write

*  Now check if we have an argument. 
   cmpi.b   #0,(A0)	      	; a null string for an argument ?
   beq	    noarg	      	; yes

   ; Actions differ depending on whether or not the file exists. If it
   ; does, we read and write the first byte (if it isn't empty!). If it
   ; doesn't, we create it as a zero length file. So we start off by 
   ; attempting to lock the given name.
   move.l   a0,a5		; safe copy
   move.l   a0,d1		; supply the name
   move.l   #ACCESS_READ,d2	; only checking for existence
   callsys  Lock
   move.l   d0,d1		; remember for later   
   beq	    nolock	      	; did we get a lock?

   ; It exists: let's Examine it and see what it is. Need to deny a touch
   ; of a directory and do special actions for a zero length file.
   jsr	    CheckFile
   cmpi.l   #0,D0
   beq      proceed
   cmpi.l   #1,D0
   beq      unlinkit
   cmpi.l   #2,D0
   beq      isadir		; touch a directory?!

   ; Get here if we encountered an error in CheckFile
   bra      examerror

;  It's a zero length file, so unlink it and re-create.
unlinkit:   
   callsys  UnLock		; lock's still in D1
   move.l   a5,d1		; the filename
   callsys  DeleteFile		; blow it away
   bra	    nolock		; create a new zero length file
	
   ; It exists and is not empty, so unlock it and proceed with open read and 
   ; write.
proceed:
   callsys  UnLock
   move.l  a5,d1		; the filename
   move.l  #MODE_OLDFILE,d2	; allows reading & writing, no creation.
   callsys Open
   move.l  d0,d1		; for read, write, seek and close
   beq	   noopen		; couldn't open, but it exists!

   lea.l   char(pc),a4		; where the single char read goes
   move.l  a4,d2
   moveq   #1,d3		; single character read
   callsys Read
   cmpi.l  #1,d0		; did it work?
   bne     noread

   ; Seek back to the beginning.
   moveq   #0,d2
   move.l  #OFFSET_BEGINNING,d3 ; seek mode -- 0 bytes from beginning
   callsys Seek
   cmpi.l  #SEEKFAIL,D0
   beq     noseek		; seek failed for some reason
   
   move.l  a4,d2		; the character that was read
   moveq   #1,d3		; the count of characters
   callsys Write
   cmpi.l  #1,d0		; did it work?
   bne     nowrite		; no

   callsys Close		; clean up
   move.l  #RETURN_OK,D0
   bra     FINISHED		; finally
   
nolock:
   ; It doesn't exist, so just create a zero length file.
   move.l  a5,d1		; the filename
   move.l  #MODE_NEWFILE,d2	; create
   callsys Open
   move.l  d0,d1		; for close
   beq	   nocreate

   callsys Close		; this should leave us with a zero length file
   move.l  #RETURN_OK,D0
   bra	   FINISHED

* Error traps.
isadir:
   callsys UnLock		; lock's still in D1
   PRINT   S_ISADIR
   move.l  #RETURN_FAIL,D0
   bra	   FINISHED
examerror:
   PRINT   S_EXAMERR
   move.l  #RETURN_FAIL,D0
   bra	   FINISHED
noseek:
   QUIT    S_NOSEEK
nowrite:
   QUIT    S_NOWRITE
noread:
   QUIT    S_NOREAD
noopen:
   PRINT   S_NOOPEN
   callsys IoErr		; return from IoErr will be exit code
   bra	   FINISHED
nocreate:
   PRINT   S_NOCREATE
   callsys IoErr		; return from IoErr will be exit code
   bra	   FINISHED
noarg:
   PRINT   S_NOARG
   move.l  #RETURN_ERROR,D0
   ; Fall through
FINISHED:
   rts
	
* Subroutines
* ------------------------------------------------------------------------

* argtrim:  a routine to trim the end of a command line and null terminate
*           it. This is achieved in the following manner:
*           Start at the end and run back until a non-whitespace (nl/blank)
*           character is encountered. If this is a quote, toss it.
*           If the *first* character is a quote, bump the start address by
*           one. No pretense of quote matching here.
*                 Inputs:     address in A0, length in D0
*                 Outputs:    address in A0.

argtrim:
   movem.l  d1-d7/a1-a6,-(sp)       ; save registers

   cmpi.b   #'"',(a0)		   ; starts with a quote?
   bne.s    checkline		   ; no
   subq     #1,d0		   ; yes - decrement count
   addq     #1,a0		   ; and bump start address

checkline:
   cmpi.b   #1,D0                   ; length includes the newline
   bne.s    isline		    ; yes - there is something there
   move.b   #0,(a0)                 ; create null string
   bra.s    argfini		    ; done

isline:
;  strip off any run of blanks on the end...   
   move.l   a0,a1                   ; computing end address of line
   add.l    d0,a1		    ; 
   subq     #2,a1		    ;

toploop:
   cmp.l    a0,a1		
   beq.s    empty                   ; single char or run of blanks

   cmpi.b   #' ',(a1)
   bne.s    endloop                 ; finished the scan
   subq     #1,a1                   ; else back one more
   bra.s    toploop                 ; and try again

endloop:
   cmpi.b   #'"',(a1)
   beq.s    nullit
nullnext:
   addq     #1,a1
nullit:
   move.b   #0,(a1)
   bra.s    argfini
empty:                              ; could be blanks or a single char
   cmpi.b   #' ',(a1)
   bne.s    endloop		    ; or maybe a single quote!
   move.b   #0,(a0)

argfini:
   movem.l  (sp)+,d1-d7/a1-a6       ; restore registers
   rts

* PRINT:    Passed an EA in A0, and a handle in D1, print the string.

print:
   MOVEM.L D0-D7/A0-A6,-(SP)

   ; Let's find out how long this string is...
   JSR      strlen                     ; Addr in A0 returns len in D0
   MOVE.L   D0,D3                      ; length
   MOVE.L   A0,D2                      ; Address
   callsys  Write                      ; Handle already in D1

   ; We don't check error returns from write, because there isn't a whole
   ; lot we can do if console writes fail.
   MOVEM.L  (SP)+,D0-D7/A0-A6          ; Restore registers
   rts

* STRLEN:   How long is a piece of string? Pass a string in A0, the
*           result comes back in D0. 

strlen:
   MOVEM.L  D1-D7/A0-A6,-(SP)          ; Save registers A0, D1 on stack.

   MOVEQ    #0,D0                      ; Initialize count

STRLOOP:
   ; Investigate current character.
   cmpi.b   #0,(A0)+                   ; Is it a NUL?
   beq.s    STRFINI
   ADDQ     #1,D0                      ; Up the count
   bra	    STRLOOP

STRFINI:
   MOVEM.L  (SP)+,D1-D7/A0-A6          ; Restore registers
   rts

;  Passed a lock in register D1 this routine checks out the file attributes.
;  Returns 0 to proceed, 1 if empty, 2 if a directory, and 3 for error.

CheckFile:
   MOVEM.L  D1-D7/A0-A6,-(SP)   ; Save registers on stack.
   move.l  d1,d4		; save it for future use

   ; A lock in D1 and a FileInfoBlock address in D2 are all that are needed.
   ; We allocmem the FileInfoBlock to assure alignment. 

   move.l  #fib_SIZEOF,D0	; size of the block
   move.l  #MEMF_PUBLIC,D1	; memory requirement

   callexec AllocMem		; args D0, D1 returns into D0

   move.l  d0,a5		; save it for later
   beq	   examnomem		; but did it work?

   move.l  d0,d2		; fileinfoblock address
   move.l  d4,d1		; the lock

   callsys Examine		; see what the lock corresponds to.
   cmpi.l  #FALSE,d0		; it should work...
   beq     noexam		; but it didn't!

   ; We have an Examine structure. Now check out if it's a directory or not.
   ; fib_DirEntryType is >0 for a directory and < 0 for a file
   tst.l   fib_DirEntryType(A5)
   bgt	   directory		; it's a directory

   ; It's just a filename. Let's see how large it is.
   tst.l   fib_Size(A5)
   beq     isempty		; nothing in it -- so we can't read

   moveq   #0,D6                ; the flag character
   bra	   examfree

* Error & cleanup for this routine				
examnomem:			; couldn't get memory
   moveq   #3,D6
   bra     examfini   
noexam:                         ; couldn't Examine
   moveq   #3,D6
   bra     examfree   
directory:                      ; it's a directory
   moveq   #2,D6		; set return code
   bra     examfree		; release memory
isempty:                        ; it's empty
   moveq   #1,D6		; fall through
examfree:                       ; free the memory here
   move.l  #fib_SIZEOF,D0   	; Address in A1, size in D0
   move.l  a5,a1
   callexec FreeMem             ; restore memory
examfini:
   move.l  d6,d0		; set return code
   MOVEM.L  (SP)+,D1-D7/A0-A6   ; Restore registers
   rts

* Data declarations
* -------------------------------------------------------------------------
DOSName		DOSNAME
char		DS.B	1				; scratch
S_NOARG		DC.B	'Usage: touch file',10,0
S_NOCREATE	DC.B	'Failed to create file',10,0
S_NOOPEN	DC.B	'Cannot open file',10,0
S_NOREAD	DC.B	'Cannot read from file',10,0
S_NOWRITE	DC.B	'Cannot write to file',10,0
S_NOSEEK	DC.B	'Cannot rewind file',10,0
S_EXAMERR	DC.B	'Error in examining file',10,0
S_ISADIR	DC.B	'Cannot touch a directory!',10,0
     END
FUNKYSTUFF
echo "\talist.asm"
cat <<"FUNKYSTUFF" >alist.asm
* ALIST.ASM:	An assembler list command. Does the bare minimum directory
*		listing. Of no real utility, it was mainly written to compare
*		assembler and C implementations of "trivial" CLI programs.
*		It's merely the framework with which an assembler variant of
*		full-blown list could be written (but not by me!).
*		In case you're interested, it really isn't much faster than
*		list. I suspect the difference (2-3s on a large c: directory)
*		is due to list's formatting of the FileInfoBlock and
*		associated greater console driver use.
*		However, alist is ~660 bytes long, instead of 8664 !!
* Perpetrator:  Dewi Williams  ..!ihnp4!druca!dewi
*               Unconditionally placed in the public domain.
*		(I could make money off of this?!)

* Included equate files.
* -----------------------------------------------------------------------
      	NOLIST 
      	INCLUDE  "exec/types.i"
      	INCLUDE  "exec/libraries.i"
      	INCLUDE  "libraries/dos.i"
	INCLUDE	 "libraries/dosextens.i"
	INCLUDE  "exec/memory.i"
      	LIST

* External references
* -----------------------------------------------------------------------

	EXTERN_LIB	OpenLibrary
	EXTERN_LIB	Write
	EXTERN_LIB	Output
	EXTERN_LIB	Examine
	EXTERN_LIB	ExNext
	EXTERN_LIB	Lock
	EXTERN_LIB	UnLock
	EXTERN_LIB	CurrentDir
	EXTERN_LIB	AllocMem
	EXTERN_LIB	FreeMem

* Local Equates
* ------------------------------------------------------------------------
SysBase  EQU   4        ; The one known address in the system.
TRUE     EQU   -1
FALSE    EQU   0

* Macros
* ------------------------------------------------------------------------

* Calls to dos.library and exec library
callsys  MACRO
      CALLLIB      _LVO\1
      ENDM
callexec MACRO
      LINKLIB	   _LVO\1,SysBase
      ENDM

* The print routine and argument setup
PRINT   MACRO
	lea.l	\1(PC),A0
	move.l	d5,d1
	jsr	print
	ENDM

* The newline print macro -- called after PRINT so D1 always set up.
NL	MACRO
	lea.l   NLCHAR(PC),A0
	jsr	print
	ENDM

* The code segment
* -------------------------------------------------------------------------
   RORG     0                 ; Relocatable code.

   jsr      argtrim           ; clean up command line
   MOVEM.L  D0/A0-A2,-(SP)    ; Save command line (OpenLibrary trashes).

   ;------ get Exec's library base pointer:
   LEA.L    DOSName(PC),A1    ; name of dos library
   move.l   #LIBRARY_VERSION,d0

   callexec OpenLibrary,SysBase
   MOVE.L   D0,A6             ; Save library pointer (always in A6).
   BNE.S    gotdos

   ; Should really issue an alert here...
   moveq    #RETURN_FAIL,D0   ; give up
   bra      FINISHED

gotdos:

   ; REGISTER USAGE: 
   ;			A0   = scratch after command line used.
   ;			A5   = FileInfoBlock address
   ;			A6   = dos library base pointer
   ;			D1-3 = AmigaDOS scratch argument registers
   ;			D4   = current directory lock
   ;			D5   = output handle
   ;			D6   = root lock flag

*  Obtain the output handle (needed for write)
   callsys   Output
   MOVE.L   D0,D5             ; Save for the write

   MOVEM.L  (SP)+,D0/A0-A2    ; restore command line etc.

*  Setup D6 as the restore directory flag
   moveq    #FALSE,D6

*  Now check if we have an argument. If we have, attempt to lock it.
*  Otherwise, go for current directory.
   cmpi.b   #0,(A0)	      	; a null string for an argument ?
   beq	    CURDIR	      	; yes

   ; Lock to specified directory. Place name in D1, access mode in D2
   move.l   a0,d1
   move.l   #ACCESS_READ,d2
   callsys  Lock
   move.l   d0,d4		; remember for later   
   beq	    nolock	      	; did we get a lock?
   bra 	    uselock		; for the Examine

CURDIR:
   ; Get lock to current directory. The most bulletproof way of doing this
   ; (due to bugs in the V1.1 RAM: handler) is to make the root directory the
   ; current one, because CurrentDir will return the old lock value.
   moveq   #0,D1
   callsys CurrentDir	       ; got the lock
   move.l  d0,d4	       ; for later
   moveq   #TRUE,d6	       ; remember to cd

uselock:
   ; A lock in D1 and a FileInfoBlock address in D2 are all that are needed.
   ; We allocmem the FileInfoBlock to assure alignment. 

   move.l  #fib_SIZEOF,D0	; size of the block
   move.l  #MEMF_PUBLIC,D1	; memory requirement

   MOVEM.L  D2-D7/A0-A6,-(SP)   ; in case AllocMem trashes (it does...)
   callexec AllocMem		; args D0, D1 returns into D0
   MOVEM.L  (SP)+,D2-D7/A0-A6   ; restore registers
   move.l  d0,a5		; save it for later
   beq	   nomem		; but did it work?

   move.l  d0,d2		; fileinfoblock address
   move.l  d4,d1		; the lock

   callsys Examine		; see what the lock corresponds to.
      
   cmpi.l  #FALSE,d0		; don't need to cd in cleanup code
   beq     noexam

   ; We have an Examine structure. Now check out if it's a directory or not.
   ; Incredibly silly things happen if you call ExNext for a file.
   ; fib_DirEntryType is >0 for a directory and < 0 for a file

   lea.l   fib_DirEntryType(A5),A0
   tst.l   (a0)
   bgt	   again

   ; It's just a filename. Emit it's name and finish.
   move.l  d5,d1
   lea.l   fib_FileName(A5),A0
   jsr	   print
   NL
   bra	   cleanup		; get next file
 
again:
   move.l  a5,d2		; restore fileinfoblock address
   move.l  d4,d1   		; and the lock
   callsys ExNext

   cmpi.l  #FALSE,d0
   beq	   cleanup		; Should really check with IoErr

   move.l  d5,d1		; restore output handle
   lea.l   fib_FileName(A5),A0
   jsr	   print
   NL
   bra	   again		; get next file
   
nomem:
   PRINT   S_NOMEM
   bra	   afterfree

noexam:
   PRINT   S_NOEXAM
   bra	   cleanup

nolock:
   PRINT   S_NLOCK
   bra     FINISHED
   
cleanup:
   ; start off by freeing allocated memory.
   move.l   a5,a1
   MOVEM.L  D1-D7/A0-A6,-(SP)   ; in case FreeMem trashes 
   move.l  #fib_SIZEOF,D0
   callexec FreeMem
   MOVEM.L  (SP)+,D1-D7/A0-A6   ; restore registers
      
afterfree:

   ; May have to throw away two locks and do a cd here
   cmpi.l  #FALSE,d6
   beq     nocd

   move.l  d4,d1
   callsys CurrentDir			; get back to where we were
   move.l  d0,d1
   callsys UnLock
   move.l  #RETURN_OK,D0
   bra	   FINISHED
nocd:
   move.l  d4,d1
   callsys UnLock   
   move.l  #RETURN_OK,D0   
FINISHED:
   rts
	
* Subroutines
* ------------------------------------------------------------------------

* argtrim:  a routine to trim the end of a command line and null terminate
*           it. This is achieved in the following manner:
*           Start at the end and run back until a non-whitespace (nl/blank)
*           character is encountered. If this is a quote, toss it.
*           If the *first* character is a quote, bump the start address by
*           one. No pretense of quote matching here.
*                 Inputs:     address in A0, length in D0
*                 Outputs:    address in A0.
*	    This routine is probably overkill for flist.

argtrim:
   movem.l  d1-d7/a1-a6,-(sp)       ; save registers

   cmpi.b   #'"',(a0)		   ; starts with a quote?
   bne.s    checkline		   ; no
   subq     #1,d0		   ; yes - decrement count
   addq     #1,a0		   ; and bump start address

checkline:
   cmpi.b   #1,D0                   ; length includes the newline
   bne.s    isline		    ; yes - there is something there
   move.b   #0,(a0)                 ; create null string
   bra.s    argfini		    ; done

isline:
;  strip off any run of blanks on the end...   
   move.l   a0,a1                   ; computing end address of line
   add.l    d0,a1		    ; 
   subq     #2,a1		    ;

toploop:
   cmp.l    a0,a1		
   beq.s    empty                   ; single char or run of blanks

   cmpi.b   #' ',(a1)
   bne.s    endloop                 ; finished the scan
   subq     #1,a1                   ; else back one more
   bra.s    toploop                 ; and try again

endloop:
   cmpi.b   #'"',(a1)
   beq.s    nullit
nullnext:
   addq     #1,a1
nullit:
   move.b   #0,(a1)
   bra.s    argfini
empty:                              ; could be blanks or a single char
   cmpi.b   #' ',(a1)
   bne.s    endloop		    ; or maybe a single quote!
   move.b   #0,(a0)

argfini:
   movem.l  (sp)+,d1-d7/a1-a6       ; restore registers
   rts

* PRINT:    Passed an EA in A0, and a handle in D1, print the string.

print:
   MOVEM.L D0-D7/A0-A6,-(SP)

   ; Let's find out how long this string is...
   JSR      strlen                     ; Addr in A0 returns len in D0
   MOVE.L   D0,D3                      ; length
   MOVE.L   A0,D2                      ; Address
   callsys  Write                      ; Handle already in D1

   ; We don't check error returns from write, because there isn't a whole
   ; lot we can do if console writes fail.
   MOVEM.L  (SP)+,D0-D7/A0-A6          ; Restore registers
   rts

* STRLEN:   How long is a piece of string? Pass a string in A0, the
*           result comes back in D0. 
strlen:
   MOVEM.L  D1-D7/A0-A6,-(SP)          ; Save registers A0, D1 on stack.

   MOVEQ    #0,D0                      ; Initialize count

STRLOOP:
   ; Investigate current character.
   cmpi.b   #0,(A0)+                   ; Is it a NUL?
   beq.s    STRFINI
   ADDQ     #1,D0                      ; Up the count
   bra	    STRLOOP

STRFINI:
   MOVEM.L  (SP)+,D1-D7/A0-A6          ; Restore registers
   rts

* Data declarations
* -------------------------------------------------------------------------
DOSName		DOSNAME
NLCHAR		DC.B	10,0
S_NLOCK    	DC.B	'directory or file name not found',10,0
S_NOEXAM	DC.B	'Could not examine directory',10,0
S_NOMEM		DC.B	'No memory',10,0
    END
FUNKYSTUFF
echo "\techo.asm"
cat <<"FUNKYSTUFF" >echo.asm
* ANSIECHO.ASM:    Extended echo that handles backslash constructs such as \t
*                  etc. Unlike UNIX echo, it takes \3A hex constructs instead
*                  of octal. I'd rather grow 6 new fingers than lose 2!
*
*              The following backslash constructs are supported:
*                 \t    tab
*                 \n    newline
*                 \f    formfeed (clear screen)
*                 \e    escape character
*                 \c    on end of string, suppresses newline
*                 \b    backspace
*                 \\    single backslash
*                 \"    explicit string quote, otherwise eaten.
*                 \NN   hexadecimal characters (*must* be upper case).
*
*              As an example, try echo \e[37mhello world\e[0m
*
* Perpetrator: Dewi Williams  ..!ihnp4!druca!dewi
*              Unconditionally placed in the public domain.

* Included equate files.
* -----------------------------------------------------------------------
      	NOLIST 
      	INCLUDE  "exec/types.i"
      	INCLUDE  "exec/libraries.i"
      	INCLUDE  "libraries/dos.i"
      	LIST

* External references
* -----------------------------------------------------------------------

	EXTERN_LIB	OpenLibrary
	EXTERN_LIB	Write
	EXTERN_LIB	Output

* Local Equates
* ------------------------------------------------------------------------
SysBase  EQU   4        ; The one known address in the system.
TRUE     EQU   -1
FALSE    EQU   0
CMD_SIZE EQU   300      ; size of expansion area on stack

* Macros
* ------------------------------------------------------------------------

* Calls to dos.library and exec library
callsys  MACRO
      CALLLIB      _LVO\1
      ENDM

* The code segment
* -------------------------------------------------------------------------
   RORG     0                 ; Relocatable code.

   LINK     A2,#(-CMD_SIZE)   ; expand onto stack
   LEA.L    -CMD_SIZE(A2),A1  ; start of expansion buffer

   jsr      argtrim           ; clean up command line
   MOVEM.L  D0/A0-A2,-(SP)    ; Save command line (OpenLibrary trashes).

   ;------ get Exec's library base pointer:
   move.l   SysBase,a6
   LEA.L    DOSName(PC),A1    ; name of dos library
   move.l   #LIBRARY_VERSION,d0

   callsys  OpenLibrary
   MOVE.L   D0,A6             ; Save library pointer (always in A6).
   BNE.S    gotdos

   ; Should really issue an alert here...
   moveq    #RETURN_FAIL,D0   ; give up
   bra      FINISHED

*  Set up loop addressing: A0 goes through command line, A1 references
*  stack build area, A2 is the frame pointer, D4 is the nonl flag and
*  D3 is a counter. The command line is terminated by a newline character.

gotdos:

*  Obtain the output handle (needed for write)
   callsys   Output
   MOVE.L   D0,D5             ; Save for the write

   MOVEM.L  (SP)+,D0/A0-A2    ; restore command line etc.

   MOVEQ    #FALSE,D4         ; nonl assumed false as default
   CLR.L    D3                ; zero characters so far

mainloop:
   CMPI.B   #0,(A0)           ; Reached end of string yet?
   BEQ      eos               ; yes
   CMPI.B   #'\',(A0)         ; is it escaped?
   BNE      notesc            ; no
   CMPI.B   #0,1(A0)          ; at the end of the string?
   BEQ      notesc            ; not escaping anything, is itself

   ; Check out the \t \f etc. cases first
   ADDQ     #1,A0             ; move to next character
   CMPI.B   #'t',(A0)         ; a tab?
   BNE      next1             ; no
   MOVE.B   #9,(A1)+          ; yes
   bra      gotit
next1:
   cmpi.b   #'b',(A0)         ; a backspace?
   bne      next2             ; no
   move.b   #8,(A1)+          ; yes
   bra      gotit
next2:
   cmpi.b   #'n',(A0)         ; a newline?
   bne      next3             ; no
   move.b   #10,(A1)+         ; yes
   bra      gotit
next3:
   cmpi.b   #'e',(a0)         ; an escape char (not backslash!)
   bne      next4             ; no
   move.b   #27,(A1)+         ; yes
   bra      gotit
next4:
   cmpi.b   #'\',(a0)         ; an escaped backslash?
   bne      next5             ; no
   bra      notesc            ; yes
next5:
   cmpi.b   #'f',(a0)         ; formfeed
   bne      next6             ; no
   move.b   #12,(A1)+         ; yes
   bra      gotit
next6:
   cmpi.b   #'c',(a0)         ; no newline construct
   bne      next7             ; no
   cmpi.b   #0,1(A0)          ; A \c at the end of the string?
   bne      notesc            ; no
   moveq    #TRUE,D4          ; set up the nonl flag
   bra      eos               ; know we've finished
next7:
   cmpi.b   #'"',(A0)         ; a string quote?
   bne      maybehex          ; no
   move.b   #'"',(A1)+        ; yes
   bra      gotit

   ; get here -- maybe it's 2 hex digits. Call ishexdig to find out.
maybehex:
   move.b   (a0),D1
   jsr      ishexdig          ; Check the first character
   beq      notesc            ; failed the test
   move.b   1(a0),D1          ; Now check the second
   jsr      ishexdig
   beq      notesc            ; second one failed
   jsr      hexstr            ; translate it into a real character
   addq     #1,a0             ; lose both characters
   move.b   d0,(A1)+          ; and fall through to gotit.

gotit:
   addq     #1,a0             ; next input character
   addq     #1,d3             ; bump output count
   bra      mainloop          ; round again
notesc:

   ; A normal character
   move.b   (a0)+,(a1)+       ; copy it over
   addq     #1,d3             ; bump output count
   bra      mainloop          ; round again

   ; Left the loop.

eos:
   TST.L    D4                ; Check out the nonl flag
   BNE      skipnl            ; set
   move.b   #10,(a1)+         ; add the newline.
   addq     #1,d3

skipnl:

   ; All set up for printing out the gathered string. Start by recalculating
   ; the start address of the output buffer.
   lea.l    -CMD_SIZE(A2),A0
   move.l   a0,d2             ; start address
   move.l   d5,d1             ; restore handle

   callsys  Write		

   MOVEQ    #0,D0             ; success
FINISHED:
   UNLK     A2
   RTS

* Subroutines
* ------------------------------------------------------------------------

* ishexdig: passed a character in D1, will report as a boolean in D0.
* Passes 0..9 and A..F (upper case).
ishexdig:
   cmpi.b   #'0',D1
   blt      hexlab
   cmpi.b   #'9',D1
   ble      yes
hexlab:
   cmpi.b   #'A',D1
   blt      no
   cmpi.b   #'F',D1
   bgt      no
yes:
   moveq    #TRUE,D0
   bra      endhex
no:
   moveq    #FALSE,D0
endhex:
   rts

* hexstr:   passed a hex string in A0, return its value in D0.
*           Stop either at length 2 or invalid hex character.
*           In this case, the string has already been validated.
* CREDITS:  Slightly hacked version of p430 of "Programming the 68000"
*           by Steve Williams. Sybex books. Recommended.

hexstr:
   movem.l  d1-d3/a0,-(sp)    ; save starting registers
   clr.l    d0                ; clear accumulator
   clr.l    d3                ; count of digits processed
loop:
   clr.l    d1                ; zero out D1
   cmpi.b   #'9',(a0)         ; upper bound
   bhi      notdec            ; not a decimal digit
   cmpi.b   #'0',(a0)         ; lower bound
   blt      nothex            ; not a hex digit
   move.b   #'0',d1           ; correction factor
   bra      gotdig            ; accumulate
notdec:
   cmpi.b   #'A',(a0)         ; check letters
   blt      nothex            ; not a hex digit
   cmpi.b   #'F',(a0)         ; upper case hex?
   bhi      nothex            ; no, and lower case is not allowed
   move.b   #'A'-10,d1        ; correction factor / fall through to gotdig
gotdig:
   clr.l    d2                ; zero high byte
   move.b   (a0)+,d2          ; get next digit
   sub.l    d1,d2             ; convert to binary
   lsl.l    #4,d0             ; multiply by 16
   add.l    d2,d0             ; add in digit
   cmpi.l   #1,d3             ; reached the end yet?
   beq      hexdone           ; yes
   addq.l   #1,d3             ; bump the count
   bra      loop              ; try another digit
hexdone:
nothex:
   movem.l  (sp)+,d1-d3/a0    ; unsave registers
   rts                        ; return to caller

* argtrim:  a routine to trim the end of a command line and null terminate
*           it. This is achieved in the following manner:
*           Start at the end and run back until a non-whitespace (nl/blank)
*           character is encountered. If this is a quote, toss it.
*           If the *first* character is a quote, bump the start address by
*           one. No pretense of quote matching here.
*                 Inputs:     address in A0, length in D0
*                 Outputs:    address in A0.

argtrim:
   movem.l  d1-d7/a1-a6,-(sp)      ; save registers

   cmpi.b   #'"',(a0)		   ; starts with a quote?
   bne.s    checkline		   ; no
   subq     #1,d0		   ; yes - decrement count
   addq     #1,a0		   ; and bump start address

checkline:
   cmpi.b   #1,D0                   ; length includes the newline
   bne.s    isline		    ; yes - there is something there
   move.b   #0,(a0)                 ; create null string
   bra.s    argfini		    ; done

isline:
;  strip off any run of blanks on the end...   
   move.l   a0,a1                   ; computing end address of line
   add.l    d0,a1		    ; 
   subq     #2,a1		    ;

toploop:
   cmp.l    a0,a1		
   beq.s    empty                   ; single char or run of blanks

   cmpi.b   #' ',(a1)
   bne.s    endloop                 ; finished the scan
   subq     #1,a1                   ; else back one more
   bra.s    toploop                 ; and try again

endloop:
   cmpi.b   #'"',(a1)
   beq.s    nullit
nullnext:
   addq     #1,a1
nullit:
   move.b   #0,(a1)
   bra.s    argfini
empty:                              ; could be blanks or a single char
   cmpi.b   #' ',(a1)
   bne.s    endloop		    ; or maybe a single quote!
   move.b   #0,(a0)

argfini:
   movem.l  (sp)+,d1-d7/a1-a6       ; restore registers
   rts
   
* Data declarations
* -------------------------------------------------------------------------
DOSName		DOSNAME
   END

FUNKYSTUFF
echo "source file creation complete"
exit 0
-- 


----------------------------------------------------------------------
	Dewi Williams.
				uucp:	   ..!ihnp4!druca!dewi
				phone:     (303) 538 4884