NEW AUTO 3,1 .LIST OFF .OP 65C02 .OR $2000 .TF bin/seq */------------------------------------- * # SEQ * Prints sequences of numbers. * * ## Arguments * **** * Starting number for the sequence of numbers. * * **** * Count in increments of . Default is 1 if omitted. * * **** * Last number to count to. If is less than , then the default is -1. * * ## Return Value * N/A * * ### Author * 2021-11-17, Brian J. Bernstein . *\------------------------------------- .INB inc/macros.i .INB inc/a2osx.i *-------------------------------------- * Defines / Consts *-------------------------------------- DIR_INCREMENT .EQ 1 DIR_DECREMENT .EQ 0 *-------------------------------------- * Zero Page Segment, up to 32 bytes *-------------------------------------- .DUMMY .OR ZPBIN ZS.START ZPPtr1 .BS 2 ; address pointer (used in arg parsing) ArgIndex .BS 1 ; index offset for argument parsing NumArgIndex .BS 1 ; used for numerical argument indexing Direction .BS 1 ; direction of counting, 0=backwards, 1=forwards wFirst .BS 2 ; arg variable - starting count wIncr .BS 2 ; arg variable - increment wLast .BS 2 ; arg variable - ending count bFormat .BS 1 ; flag that the format -f option was specified bHelp .BS 1 ; flag that the help -h option was specified ZPPtrFormat .BS 2 ; pointer to format -f string bString .BS 1 ; flag that the string -s option was specified ZPPtrString .BS 2 ; pointer to string -s string bTerminating .BS 1 ; flag that the terminating -t option was specified ZPPtrTerm .BS 2 ; pointer to terminating -t string *-------------------------------------- * X.STRESC *-------------------------------------- *ZPPtr1 .BS 2 ZPPtr2 .BS 2 ZPTmpByte .BS 1 *-------------------------------------- ZS.END .ED *-------------------------------------- * File Header (16 Bytes) *-------------------------------------- CS.START cld jmp (.1,x) .DA #$61 ; 6502,Level 1 (65c02) .DA #1 ; BIN Layout Version 1 .DA #0 ; Events disabled (enable with S.PS.F.EVENT) .DA #0 .DA CS.END-CS.START ; Code Size (without Constants) .DA DS.END-DS.START ; Data SegmentSize .DA #32 ; Stack Size .DA #ZS.END-ZS.START ; Zero Page Size .DA 0 *-------------------------------------- * Relocation Table *-------------------------------------- .1 .DA CS.INIT .DA CS.RUN .DA CS.DOEVENT .DA CS.QUIT L.MSG.USAGE .DA MSG.USAGE ; msg for usage / help text L.MSG.NEWLINE .DA MSG.MSG.NEWLINE L.FMT.FORMAT .DA FMT.FORMAT L.FMT.STRING .DA FMT.STRING L.FMT.TERM .DA FMT.TERM .DA 0 *-------------------------------------- * Called once at process creation * Put code for loading LIB here *-------------------------------------- CS.INIT clc lda L.FMT.FORMAT ; set default format sta ZPPtrFormat lda L.FMT.FORMAT+1 sta ZPPtrFormat+1 lda L.FMT.STRING ; set default string separator sta ZPPtrString lda L.FMT.STRING+1 sta ZPPtrString+1 lda L.FMT.TERM ; set default seq terminator string sta ZPPtrTerm lda L.FMT.TERM+1 sta ZPPtrTerm+1 rts *-------------------------------------- * Called until exit with CS * if RUN exits with CC, RUN entered again *-------------------------------------- CS.RUN .1 inc ArgIndex ; Check next argument lda ArgIndex >SYSCALL ArgV ; check for an arg at index in A bcc .10 ; if it exists, keep checking jmp .8 ; otherwise, we're done with args .10 >STYA ZPPtr1 ; ArgV pointer was in Y,A so stick into ZPPtr1 lda (ZPPtr1) cmp #'-' ; does arg have a hyphen? bne .11 ; if not, check for string/numeric args jsr CS.RUN.CheckOpt ; if it had a hyphen, check and set arg if recognized bcs .9 ; if we didn't recognize the arg, go to usage and exit bit bHelp ; was the arg 'Help'? bpl .1 ; no, loop again to check next jmp .9 ; it was, so display usage and exit *--- Checking of argument -F ---------- .11 bit bFormat ; did we just see the -f option? bpl .12 ; no, jump to next arg flag lda ArgIndex ; yes, then get the pointer to the arg string >SYSCALL ArgV ; and set it to the pointer for the -f >STYA ZPPtrFormat ; string storage lda #0 ; and clear out that we processed the -f sta bFormat ; argument so that we don't try it again jmp .1 ; and then go process the next arg *--- Checking of argument -S ---------- .12 bit bString ; did we just see the -s option? bpl .13 ; no, jump to next arg flag lda ArgIndex ; yes, then get the pointer to the next arg string >SYSCALL ArgV ; and set it to the pointer for the -s >STYA ZPPtrString ; string storage lda #0 ; and clear out that we processed the -s sta bString ; argument so that we don't try to do it again jmp .1 ; and then go process the next arg *--- Checking of argument -T ---------- .13 bit bTerminating ; did we just see the -t option? bpl .2 ; no, jump to numeric processing lda ArgIndex ; yes, then get the pointer to the arg string >SYSCALL ArgV ; and set it to the pointer for the -t >STYA ZPPtrTerm ; string storage lda #0 ; and clear out that we processed the -t sta bTerminating ; argument so that we don't try it again jmp .1 ; and then go process the next arg *--- Display usage and error out ------ .9 >PUSHW L.MSG.USAGE ; push address for usage text >PUSHBI 0 >SYSCALL PrintF ; print usage message lda #E.SYN ; set OS return code as Syntax Error sec ; indicate we don't want CS.RUN called again rts ; return to OS *--- Successful exit ------------------ .99 lda #0 ; set OS return code to success sec ; indicate we don't want CS.RUN called again rts ; return to OS *--- Processing numerical args -------- .2 lda ArgIndex >SYSCALL ArgV ; check for an arg at index in A .20 >SYSCALL AToI ; get the next value on the command line >STYA wIncr ; temporarily store it as the increment value inc NumArgIndex lda NumArgIndex ; check what argument value we were looking at cmp #1 ; because if it was the first, then it is probably wFirst bne .3 >LDYA wIncr ; copy the value to wFirst >STYA wFirst .3 cmp #2 ; but if we were looking at second value, then bne .4 ; we assume it is 'wLast' for the moment >LDYA wIncr ; and copy it over >STYA wLast .4 cmp #3 ; if we're looking at third value, then we must have had bne .5 ; an increment value, so we need to swap what we recorded ldx wLast ; as wLast and swap it with the just-read wIncr ldy wIncr stx wIncr sty wLast ldx wLast+1 ; as wLast and swap it with the just-read wIncr ldy wIncr+1 stx wIncr+1 sty wLast+1 .5 jmp .1 ; go check for another argument *--- Done with args so figure out ----- *--- what numerical args are for ------ .8 lda NumArgIndex ; check that we got 1 to 3 numeric arguments. cmp #1 bmi .9 ; otherwise, display help and error out cmp #4 bpl .9 cmp #1 ; if we only got one, then it was wLast bne .81 lda wFirst ; copy what we thought was wFirst to wLast sta wLast lda wFirst+1 sta wLast+1 ldy #1 ; set 1 as wFirst lda #0 >STYA wFirst >STYA wIncr ; and set 1 as wIncr jmp .82 .81 cmp #2 ; check to see if we didn't get an increment bne .82 ldy #1 ; if not, then just store +1 as the increment lda #0 >STYA wIncr .82 jsr CS.RUN.Seq ; everything is set, go 'seq' fame and fortune jmp .99 *-------------------------------------- * Called if option S.PS.F.EVENT enabled in Header * Timer Event : every 10th seconds *-------------------------------------- CS.DOEVENT sec ; we don't use this since we don't have timer events rts *-------------------------------------- * Called once, when RUN exited with CS * Put code for unloading LIB here *-------------------------------------- CS.QUIT clc ; nothing to do on exit except clear carry and return rts *-------------------------------------- * CheckOpt assumes a set ZPPtr1 which is the address of the command line argument being examined. * We start at 1 to look past the '-' as position 0 since that was checked by the caller. * OptionList is a list of possible options and each character correlates with a memory offset * in OptionVars, which are only one byte since they are in ZP but this also allows for us to * simply use indexed addressing to reference them easily as well instead of doing 16-bit * address juggling. * The options are checked in reverse from end-to-start and indexed by X. *-------------------------------------- CS.RUN.CheckOpt ldy #1 ; set up y to look at second character of passed in arg lda (ZPPtr1),y ; check second character of passed in argument into A ldx #OptionVars-OptionList-1 ; clever way to put size of OptionList into X .2 cmp OptionList,x ; compare the arg we got to the OptionList at X beq .3 ; if it is a match, go handle it. dex ; if not, decrement so we can check next OptionList bpl .2 ; if we haven't reached end of OptionList, go check next sec ; set carry if we didn't find a match rts ; return to caller .3 ldy OptionVars,x ; since we matched, find ZP addr of matching option into Y lda #$ff ; we will set this ZP option to $FF sta 0,y ; store A into the ZP address we have in Y clc ; clear carry since we found a match rts ; return to caller *-------------------------------------- *-------------------------------------- * CS.RUN.Seq - Entry point for when args are handled and we're ready to 'seq'. * This is anything we need to do before actually 'seq'encing. *-------------------------------------- CS.RUN.Seq jsr CS.DetermineDir ; determine if we're doing increment or decrement *-------------------------------------- * CS.DoSeq - the actual 'SEQ' work once everything is set up. *-------------------------------------- CS.DoSeq *--- Print the number in ZPPtrFormat -- .1 >PUSHW ZPPtrFormat ; set up the format that we're printing sequence with >PUSHW wFirst ; current seq value is kept in wFirst >PUSHBI 2 >SYSCALL PrintF ; print the current seq count. *--- Print the string sepators -------- >LDYA ZPPtrString jsr CS.PrintFEscYA ; print string separator lda Direction ; check which direction we're counting cmp #DIR_INCREMENT ; going up? bne .2 ; nope, go to decrement code *--- Do INCREMENTAL math on the seq --- clc ; ADDING wIncr to wFirst lda wFirst ; do 16-bit addition of wFirst + wIncr adc wIncr sta wFirst lda wFirst+1 adc wIncr+1 sta wFirst+1 jsr CS.CmpFirstLast ; is wFirst >= wLast? bcs .1 ; no, so keep going lda wFirst ; check to see if wFirst == wLast cmp wLast bne .9 lda wFirst+1 cmp wLast+1 bne .9 jmp .1 ; wFirst == wLast, so go around one more time *--- Do DECREMENTAL math on the seq --- .2 sec ; SUBTRACTING wIncr from wFirst lda wFirst sbc wIncr sta wFirst lda wFirst+1 sbc wIncr+1 sta wFirst+1 jsr CS.CmpFirstLast ; is wFirst still >= wLast? bcc .1 ; yes, keep going. lda wFirst ; check to see if wFirst == wLast cmp wLast bne .9 lda wFirst+1 cmp wLast+1 bne .9 jmp .1 ; wFirst == wLast, so go around one more time *--- Done with sequence, finish up ---- .9 >LDYA ZPPtrTerm ; set up terminating string jsr CS.PrintFEscYA ; print terminating string rts ; done with SEQ! * TODO: negatives in incr shouldn't be allowed. *-------------------------------------- CS.PrintFEscYA jsr X.STRESC >PUSHYA >PUSHBI 0 >SYSCALL PrintF rts *-------------------------------------- * CS.DetermineDir - checks that increment value in relation +/- to * wFirst / wLast, and then sets the Direction flag. *-------------------------------------- CS.DetermineDir jsr CS.CmpFirstLast ; compare first/last values bcc .1 ; if first < last, then we're counting forward lda #DIR_INCREMENT jmp .2 .1 lda #DIR_DECREMENT .2 sta Direction ; set the direction rts ; and return to caller. *-------------------------------------- * CS.CmpFirstLast - compares wFirst to wLast value and sets carry based on * if wFirst greater than or equal to wLast, or clear carry * if wFirst is less than wLast. * * IN: n/a (uses global wFirst/wLast) * OUT: carry flag; set if wFirst >= wLast, clear if wFirst < wLast. *-------------------------------------- CS.CmpFirstLast >PUSHW wFirst ; using FPU macro, so push first, >PUSHW wLast ; and call the macro >FPU iGE lda (pStack) ; get result from stack bne .1 ; yes, wFirst is >= wLast >POP 2 ; wFirst < wLast sec ; return to caller with carry set rts .1 >POP 2 ; wFirst is >= wLast clc ; return to caller with carry clear rts *-------------------------------------- .INB usr/src/shared/x.stresc.s *-------------------------------------- CS.END *-------------------------------------- MSG.USAGE .CS "Usage : SEQ [options] [first [incr]] last\r\n" .CS " -F fmt : numeric format\r\n" .CS " -S sep : string separator\r\n" .CZ " -T trm : terminating string\r\n" MSG.MSG.NEWLINE .CZ "\r\n" *-------------------------------------- FMT.FORMAT .AZ "%I" FMT.STRING .CZ "\r\n" FMT.TERM .AZ "" *-------------------------------------- OptionList .AS "FfSsTtHh" OptionVars .DA #bFormat,#bFormat,#bString,#bString,#bTerminating,#bTerminating,#bHelp,#bHelp *-------------------------------------- .INB usr/src/shared/x.stresc.g *-------------------------------------- * Per Process DATA segment (0 filled before INIT) *-------------------------------------- .DUMMY .OR 0 DS.START DS.END .ED *-------------------------------------- MAN SAVE usr/src/bin/seq.s ASM