; Listing11-3.asm
;
; A program that demonstrates classes
; for creating generators (no fibers).

            option     casemap:none
            .nolist

            include    aoalib.inc
            include    aoaProcs.inc
            include    aoaTypes.inc
            include    aoaMacros.inc
            include    aoaExcepts.inc
            include    aoaClasses.inc
            includelib aoalib.lib
            
            .list
            
            
; Program title:

            .const
             align  word
ttlStr       byte   "Listing 11-3", 0

; Class names:

range_tName  byte   "range_t", 0

    
    
; Define the generator_t class.
; This is an abstract base class from which
; other generator classes will be defined.

            class   generator_t

; className- pointer to zString containing class name.
            
className   qword   ?

; onHeap- True if generator object was allocated on the heap.

onHeap      byte    ?

; Methods:
; All methods except create expect RSI to contain the THIS
; pointer and RDI to contain the address of the VMT.

            ;static create
            
; start parameters will vary by derived class.

            virtual start
            
; Generate returns the generator result in:
;           128-bit: RDX:RAX
;           64-bit:  RAX
;           32-bit:  EAX
;           16-bit:  AX
;           8-bit:   AL
;
; HO accumulator bits are undefined. Generator will
; not modify RDX if the generator returns 64 bits or
; less.

            virtual generate
            
; hasData will return true/false (1/0) in AL

            virtual hasData
            
; Delete returns nothing.

            virtual delete
            
            endc    generator_t

; Still in .const section, emit the VMT:

            emitVMT generator_t

; The "this" pointer for generator_t:

generator_t@   textequ <(generator_t ptr [rsi])>


;--------
;
; Simple demonstration of a derived generator class: range_t
;
; Passed a pair of integers (startingVal and endingVal).
; Generates the starting integers from startingVal to 
; endingVal.

            class   range_t, inherits( generator_t )
            
; Private data:

startingVal dword   ?       ;Store generator parameters
endingVal   dword   ?       ; in these two locations

            override start,         range_t$start
            override generate,      range_t$generate
            override hasData,       range_t$hasData
            override delete,        range_t$delete
            
            endc    range_t


; Still in .const section, emit the VMT:

            emitVMT range_t

; The "this" pointer for generator_t:

range_t@    textequ <(range_t ptr [rsi])>
            
            
;-------------------------------------------------------------

            .data

rangePtr    qword   ?
range2Ptr   qword   ?

;-------------------------------------------------------------
; Class methods and procedures



            .code

;------
;
; Constructor and methods for the abstract generator_t class:
;
; Just raise an exception if anyone tries to call any of
; these procedures or methods.

generator_t$create  proc
                    int     3
generator_t$create  endp
generator_t$start   textequ <generator_t$create>
generator_t$hasData textequ <generator_t$create>
generator_t$delete  textequ <generator_t$create>
generator_t$generate textequ <generator_t$create>




;**********************************************************
;
; Constructor for the range_t class.
;
; On entry, RSI contains the address of the range_t
; object to initialize, or NULL if this function
; is supposed to allocate storage for a new object.
;
; On return, RSI points at the object and RDI
; points at the VMT. This construction returns
; NULL in RSI if there was an error allocating or
; initializing the object.

; locals maps rsiSave, and rdiSave to the same offsets
; that xproc uses to preserve their values. Need to so this
; so we can change the return values of RSI and RDI later.

                    locals  range_t$create, \
                            rsiSave:qword,  \
                            rdiSave:qword

range_t$create      xproc   (frame, <rsi, rdi>)

; Note: this function makes Windows API calls. Would really
; like to preserve all registers except RSI and RDI across
; this call, so we must save the volatile registers ourselves.
;
; Note that volatile registers don't get restored if an
; exception occurs.

                    local   raxSave:qword,  \
                            rcxSave:qword,  \
                            thisPtr:qword,  \
                            shadowStorage[64]:byte

; Preserve the volatile registers:
                            
                    mov     raxSave, rax
                    mov     rcxSave, rcx
                    
; Save THIS so we can check for NULL later.

                    mov     thisPtr, rsi
                    
                    mov     ecx, sizeof range_t
                    lea     rdi, vmtAdrs(range_t)
                    call    generic$Class$Constructor
                    mov     rcx, thisPtr    ;Original RSI value
                    test    rsi, rsi        ;Did allocation fail?
                    jz      failure
                    
; Check to see if object was allocated on heap. Set the
; onHeap field to true if it was:

                    test    rcx, rcx
                    jnz     dontSetHeap
                    mov     range_t@.onHeap, 1
                    
; Initialize pointer to field name:

dontSetHeap:        lea     rcx, range_tName
                    mov     range_t@.className, rcx
                                    
; endxp is going to restore RSI and RDI as per the
; Windows' ABI. However, we really need to return
; THIS and VMT in them. Overwrite data on the stack:

                    mov     range_t$create$rsiSave, rsi
                    mov     range_t$create$rdiSave, rdi

; Need to return THIS in RAX:
                    
failure:            mov     rax, raxSave
                    mov     rcx, rcxSave
range_t$create      endxp()
                    

; The following class methods for range_t override
; the abstract methods from the generator_t class.
; Note that range_t is an exceeding simple generator
; and really shouldn't be written using fibers; there
; is many times more work involved in invoking the
; fiber as there would be in just doing the range_t
; actions directly in the following methods. Because
; range_t is so trivial, however, it makes and easy-
; to-understand generator. So the following methods
; invoke the fiber procedure to demonstrate how
; it's done.


; start( startingValue, endingValue );
;
; (Re)Sets the object's starting and ending values.
                    
range_t$start       proc
                    mov     range_t@.startingVal, ecx
                    mov     range_t@.endingVal, edx
                    ret
range_t$start       endp

; generate- If a value is available, returns the next generated
;           value in EAX; if no data is available, it returns
;           0 in EAX. 
;
;           If data was avaiable, this function returns with
;           the carry flag clear.
;
;           If data was not available, this function returns
;           with the carry flag set.

                            
range_t$generate    proc
                    mov     eax, range_t@.startingVal
                    cmp     eax, range_t@.endingVal
                    ja      genError
                    inc     range_t@.startingVal
                    clc     ;Return no error
                    ret

genError:           xor     rax, rax
                    stc     ;Return error 
                    ret             
range_t$generate    endp

; hasData returns (in AL) true/false depending on
; whether the generator can generate additional
; data.

range_t$hasData     proc
                    mov     eax, range_t@.startingVal
                    cmp     eax, range_t@.endingVal
                    setbe   al
                    movzx   eax, al
                    ret             
range_t$hasData     endp

; range_t$delete:
;
; The delete method shuts down the generator and
; deletes the fiber.

range_t$delete      proc
                    
; If object was allocated on the heap, delete it here:

                    cmp     range_t@.onHeap, 0
                    je      noFree
                    
                    mov     rax, rsi        ;Free object storage
                    call    mem$free
noFree:             ret
range_t$delete      endp

;--------------------------------------------------
;
; Here is the main assembly language function.
            
            public  asmMain
asmMain     xproc   (frame, <rbx, rsi, rdi, r15>)
            local   shadowStorage[80]:byte


; Create a pair of range generator objects

            xor     rsi, rsi        ;Allocate on heap
            call    range_t$create
            mov     rangePtr, rsi   ;Return value in RSI!
            
            xor     rsi, rsi        ;Allocate on heap
            call    range_t$create
            mov     range2Ptr, rsi
            
; Start the outer generator with the values 1..2:

            mov     ecx, 1
            mov     edx, 2
            inv     range_t, start, range2Ptr               

; Invoke the generators until they fail:

outerLoop:  inv     range_t, hasData, range2Ptr
            cmp     al, 0
            je      noMoreData

            inv     range_t, generate, range2Ptr
            mov     r15d, eax       ;Save for later
            
; Start the inner generator with the values 1..5:

            mov     ecx, 1
            mov     edx, 5
            inv     range_t, start, rangePtr                

innerLoop:  inv     range_t, hasData, rangePtr
            cmp     al, 0
            je      outerLoop
            
            inv     range_t, generate, rangePtr
            
            mov     edx, eax
            mov     r8d, r15d
            call    print
            byte    "Generated: inner=%d, outer=%d", nl, 0
            jmp     innerLoop
            
noMoreData: 
            call    print
            byte    "Generation done, cleaning up", nl, 0           

; Clean up after ourselves:

            inv     range_t, delete, range2Ptr
            inv     range_t, delete, rangePtr
            
asmMain     endxp()
            end