NEW
  AUTO 3,1
                .LIST OFF
                .OP 65C02
                .OR $2000
                .TF bin/seq
*/-------------------------------------
* # SEQ
* Prints sequences of numbers.
*
* ## Arguments
* **<first>**
* Starting number for the sequence of numbers.
*
* **<incr>**
* Count in increments of <incr>. Default is 1 if omitted.
*
* **<last>**
* Last number to count to. If <last> is less than <first>, then the default <incr> is -1.
*
* ## Return Value
* N/A
*
* ### Author
* 2021-11-17, Brian J. Bernstein <brian@dronefone.com>.
*\-------------------------------------
                .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
