* 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
