* 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