!fnbits.bsi - Various bit field functions (set, clr, toggle, test)
!-------------------------------------------------------------------------------------
!VEDIT=103
![103] 05-Jul-18 / jdm / If runtime < 6.5.1639, auto-init bit field to a length of 64
!                           on first bit set to avoid failure prior to 1639 to auto
!                           extend the field during assignment to a slice of it
![102] 23-Jul-15 / jdm / add :inputonly, chg Fn'IsBitSet return to be -1 for TRUE 
!                           (allowing use of not Fn'IsBitSet())
![101] 23-Oct-14 / jdm / Expand from x16 to x0 (variable width), add ++ifndef
![100] 21-Apr-09 / jdm / Created 
!	
!-------------------------------------------------------------------------------------
!Function List:
!   Fn'BitSet$(bitfield,bitno)     - Set a bit
!   Fn'BitClr$(bitfield,bitno)     - Clr a bit
!   Fn'BitToggle$(bitfield,bitno)  - Toggle a bit
!   Fn'BitIsSet(bitfield,bitno)   - Test a bit
!-------------------------------------------------------------------------------------
!Notes:
! All of these functions work on "bit fields", which are just unformatted (X)
! variables.  The bit numbers are just the linear it position (1..n where n=8x, where
! x is the size of the unformatted var in bytes).
! Most functions (those with $) return the updated bit field.
!
! [101] There is no inherent length to these bit fields, so we'use here x0 to allow 
! an infinitely variable size. Capability was introduced in 5.1.1159
!
! [103] Prior to 6.5.1639, an assignment to a slice of an x0 variable did not auto
! expand the variable, which could lead to a problem here if the caller used an x0
! variable and didn't pre-fill it to a suitable size. In such a case, Fn'BitSet$()
! and Fn'BitToggle$() will now pre-size the field to 64 bytes if < 6.5.1639. To 
! avoid this issue, caller can either use a fixed size bit field (it presumably 
! know how big it needs to be), or it can pre-size it using bitfield = fill$(chr(0),size)
!
!Keywords: xor bitops
!-------------------------------------------------------------------------------------
!{end history}

++ifndef MAX_BIT_FIELD

++include'once sosfunc:fnminasver.bsi   ! [103]

  define MAX_BIT_FIELD = 0              ! [101] unlimited (was 16)  (See [103])
  define DEFAULT_BIT_FIELD_SIZE = 64    ! [103] initial default size if < 6.5.1639
                                        ! [103] and caller passes an uninited x0
  define ALL_BITS = 0                   ! bit # symbol representing all bits
!-------------------------------------------------------------------------------------
! Function :    Fn'BitSet$(bitfield,bitno)
!   Set a bit in a bit field
! Params:
!   bitfield [x, in]    bit field
!   bitno    [b2, in]   bit # to set (1 is first, 8 x sizeof bitfield is last)
!                       if omitted, or ALL_BITS (0), set them all
! Returns:
!   Updated bit field
! Notes:
!   [103] Prior to 6.5.1639.0:
!       a) initial call passing an uninitialized x0 bit field will 
!           pre-clear the field to a size of 64 bytes.  
!       b) after that, attempt to set bit beyond end of field will do nothing
!   After 6.5.1639.0, field will be auto-expanded as needed.
!-------------------------------------------------------------------------------------
Function Fn'BitSet$(bitfield as x0:inputonly, bitno as b2:inputonly) as x0

    map1 locals
        map2 byteno,b,2

    if len(bitfield) = 0 then                   ! [103] incoming bit field an uninitialized x0?
        if Fn'MinAshVer(6,5,1639,0) < 0 then    ! if so and if < 1639 pre-init it to a suitable size
            debug.print "Auto-initializing bit field to "+DEFAULT_BIT_FIELD_SIZE+" bytes"
            bitfield = fill$(chr(0),DEFAULT_BIT_FIELD_SIZE)
        endif
    endif
    
    if bitno = ALL_BITS then
        ! set all bits...
        Fn'BitSet$ = fill$(chr(255),len(bitfield))
    else
        ! (determine byte # that bitno appears in)
        byteno = (bitno-1)/8 + 1 ! assuming bit #1 is first

        ! long but clear approach to extracting/setting bit ...

            ! (convert bitno into a bit mask)
            ! bitmask = 2 ^((bitno-1) mod 8)  ! bitmask = 0,1,2,4,8,16,32,64,128

            ! (extract the byte from the field,)
            ! btemp = asc(bitfield[byteno;1])

            ! (set the bit,)
            ! btemp = btemp or bitmask

            ! (put it back)
            ! bitfield[byteno;1] = chr(btemp)

        ! short way... (see above for clarification of logic)
        bitfield[byteno;1] = chr(asc(bitfield[byteno;1]) or (2 ^((bitno-1) mod 8)))

        ! and return new bit field as func value
        Fn'BitSet$ = bitfield
    endif

End Function

!-------------------------------------------------------------------------------------
! Function :    Fn'BitClr$(bitfield,bitno)
!   Clear a bit in a bit field
! Params:
!   bitfield [x, in]    bit field
!   bitno    [b2, in]   bit # to clear (1 is first, 8 x sizeof bitfield is last)
!                       if omitted, or ALL_BITS (0), clear them all
! Returns:
!   Updated bit field
! Notes:
!   If bit # out of range do nothing
!-------------------------------------------------------------------------------------
Function Fn'BitClr$(bitfield as x0:inputonly, bitno as b2:inputonly) as x0

    map1 locals
        map2 byteno,b,2

    if bitno = ALL_BITS then
        ! nothing to do since return value starts all clear
    else
        ! determine byte # that bitno appears in
        byteno = (bitno-1)/8 + 1 ! assuming bit #1 is first

        ! clear the bit (see Fn'SetBit$() above for clarification of logic)
        bitfield[byteno;1] = chr(asc(bitfield[byteno;1]) and not (2 ^((bitno-1) mod 8)))

        ! and return new bit field as func value
        Fn'BitClr$ = bitfield
    endif

End Function

!-------------------------------------------------------------------------------------
! Function :    Fn'BitToggle$(bitfield,bitno)
!   Toggle a bit in a bit field
! Params:
!   bitfield [x, in]    bit field
!   bitno    [b2, in]   bit # to toggle (1 is first, 8 x sizeof bitfield is last)
!                       if omitted, or ALL_BITS (0), toggle them all
! Returns:
!   Updated bit field
! Notes:
!   [103] see note for Fn'BitSet$()
!-------------------------------------------------------------------------------------
Function Fn'BitToggle$(bitfield as x0:inputonly, bitno as b2:inputonly) as x0

    map1 locals
        map2 btemp,b,1
        map2 byteno,b,2
        map2 i,i,2

    if len(bitfield) = 0 then                   ! [103] incoming bit field an uninitialized x0?
        if Fn'MinAshVer(6,5,1639,0) < 0 then    ! if so and if < 1639 pre-init it to a suitable size
            bitfield = fill$(chr(0),DEFAULT_BIT_FIELD_SIZE)
        endif
    endif

    if bitno = ALL_BITS then
        ! toggle every bit using XOR on byte-by-byte basis
        for i = 1 to sizeof(bitfield)
            btemp = asc(bitfield[i;1]) xor 255
            bitfield[i;1] = chr(btemp)
        next i
    else
        ! determine byte # that bitno appears in
        byteno = (bitno-1)/8 + 1 ! assuming bit #1 is first

        ! toggle the bit (see Fn'SetBit$() above for clarification of logic)
        bitfield[byteno;1] = chr(asc(bitfield[byteno;1]) xor (2 ^((bitno-1) mod 8)))

    endif

    ! and return new bit field as func value
    Fn'BitToggle$ = bitfield

End Function

!-------------------------------------------------------------------------------------
! Function : Test if a bit in a bitfield is set
! Params:
!   bitfield [x, in]    bit field
!   bitno    [b2, in]   bit # to test (1 is first, 8 x sizeof bitfield is last)
!                       if omitted, or ALL_BITS (0), test if any are set
! Returns:
!   0 (bit is not set, or bit out of range); -1 (bit is set)
! Notes:
!   If bit # out of range return 0
!   [102] return value now -1 for true so you can use not, e.g.
!       if not Fn'BitIsSet(...) then...
!-------------------------------------------------------------------------------------
Function Fn'BitIsSet(bitfield as x0:inputonly, bitno as b2:inputonly) as i2

    map1 locals
        map2 byteno,b,2
        map2 i,i,2

    if bitno = ALL_BITS then
        ! scan bytes until end or first non-zero
        for i = 1 to sizeof(bitfield)
            if asc(bitfield[i;1]) # 0 then
                Fn'BitIsSet = 1
                exit
            endif
        next i
    else
        ! determine byte # that bitno appears in
        byteno = (bitno-1)/8 + 1 ! assuming bit #1 is first

        ! test it and return result (see Fn'SetBit$() above for clarification of logic)
        trace.print "bitfield[1] = "+asc(bitfield[1;1]), "bitfield[2] = "+asc(bitfield[2;1])
        Fn'BitIsSet = asc(bitfield[((bitno-1)/8 + 1);1]) and (2 ^((bitno-1) mod 8))
    endif

    if Fn'BitIsSet then         ! [102] convert all true results to .TRUE (-1)
        Fn'BitIsSet = .TRUE     ! [102]
    endif                       ! [102] 
End Function

++endif