Record Manager

JMP $FCE2
MarcWalters
Member
Member
Posts: 45
Joined: Wed Jun 11, 2014 2:33 pm
Location: Lake Macquarie, NSW, Australia

Record Manager

Post by MarcWalters » Sun Oct 26, 2014 4:50 pm

This is a work in progress, but complete and bug-free as it is. Format is CBM Prg Studio.

Description
Record Manager provides a way to coerce a block of memory within a 24-bit address space to a Pascal-style Record.
A Record can be 1 to 65536 bytes in length, and consist of 1-127 Fields.
A Field can be 1 to 65536 bytes in length, and has a user-defined Datatype associated with it.

Why?
It can be useful to keep related data grouped together if speed is not a concern. General-purpose re-useable code can be written to take advantage of the record structure in a way easier than if the record were serialised into tables for indexed lookups.

Integration
Although presented here in isolation, the Record manager slots neatly into a suite of Record-aware tools I've written, including a 24-bit Array Manager. It can also be wrapped in the Function Manager presented last week.

Design Overview
The RecordType data can be created in a static block by the user in order to retain direct access to its elements. It maintains an external reference to itself, which allows other programs to access its information via a vector/index system. This minimises the amount of memory the Record manager reserves to just the handle list and a table of addresses.

Some of the naming conventions in the code are still to be fixed. Basically, the block of memory to which the Record Type is applied becomes a Record of that type. There are some areas in which the code refers to a "Record" but it's actually the "Type". I'll clean it all up over time.

Final Notes
The code is spread across several include-files, and so there may be a few hiccoughs in the way its been presented here. Once the suite of tools is finished I'll make the full set of project files available for download.

Subroutines
The table below, plus the examples elsewhere, should be self-explanatory.

Code: Select all

; RECORD OPERATIONS
; Init Record Manager: Rec_Init$ ()
; Create a Record Type: Rec_Create$ (W rAX Record Descriptor, B rY Flag 0 = Generate Offset Table) :  B, rSC
; Delete a Record Type: Rec_Delete$ ( Handle ) : rSC

; FIELD OPERATIONS
; Get Field Count: Rec_GetFldCnt$ ( Hdl ) : B
; Get Field Offset: Rec_GetFldOs$ ( Hdl, Field ) : W
; Get Field Length: Rec_GetFldLen$ (Hdl, Field ) : W
; Get Field Address: Rec_GetFldAdr$ (Hdl, Field, ZP Long Address ) : L rAXY Address
; Get Short Field Adr and Len: Rec_GetFldAdrLenS$ (Hdl, Field, ZP-W Adr) : W rAX Address, B rY Field Length 
; Get Len and Datatype, Long Field Adr in Rec_zRecAdr$: Rec_GetFldLenDt$ (Hdl, Field, ZP-W Adr) 
;   : W rA Field Datatype, rYX Field Length, L Rec_zRecAdr$. NOTE: rY has Lo-byte of Length.
Example: Create A Record Type

Code: Select all

Start    jsr Rec_Init$

         ; Create a Record Id
         lda #<MyRecId
         ldx #>MyRecId
         ldy #0            ; 0 = Generate Offset Table
         jmp Rec_Create$

MyRecId  byte 255          ; ID
         byte 2            ; Number of fields
         word @FldDtyp     ; Address of Datatype table
         word @FldLen      ; Address of Field Length table
         word @FldOs       ; Address of Field Offset table (calculated by default)
@FldDtyp byte 0,0,0         
@FldLen  word 12,3,1         
@FldOs   word 0,0,0,0      ; Normally undefined, but can be hardcoded to create gaps between fields

; This is a block of memory that will be a record
RecMem   text 'hello world '
         text 'abc'
         text '0'
Example: Get Some Information About A Record

Code: Select all

         ; Setup Long Address to Memory
         lda #<RecMem
         ldx #>RecMem
         ldy #0
         sta $FB
         stx $FB + 1
         sty $FB + 2

         ; Point to a Record Id, its second Field, and a block of memory via a Zero-Page vector
         lda MyRecId
         ldx #1
         ldy #$FB

         ; Calculate and store the 24-bit address of the Field in a global vector
         ; Fetch the 16-bit Field Length in .Y/X and the Datatype in .A
         jsr Rec_GetFldLenDt$

         ; If Address is 16-bit, Field Length 8-bit and Datatype Binary then
         ; the field content can be accessed using ZP indirect.
         lda (Rec_zRecVal$),Y
 
Record Manager Code

Code: Select all

; --------------------------
; Record Manager
; --------------------------

zRecordMan$ = $A0; If isolated

; -----------
; ZERO-PAGE 
zRecDesVec = zRecordMan$            ; 2 bytes
zRecDesVal = zRecDesVec + 2         ; 3 bytes 
zRecAdr    = zRecDesVal + 3         ; 3 bytes 
; -----------
; PUBLIC
Rec_Init$         = Init
Rec_Create$       = Create
Rec_Delete$       = Delete
Rec_GetFldCnt$    = GetFldCnt
Rec_GetFldOs$     = GetFldOs 
Rec_GetFldLen$    = GetFldLen
Rec_GetFldAdr$    = GetFldAdr
Rec_GetFldAdrLenS$ = GetFldAdrLenS
Rec_GetFldLenDt$  = GetFldLenDt
; COMPILER BUG? If this following declaration occurs before that of zRecAdr the 
;   labels in this file are generated 2 bytes forward of their correct location
Rec_zRecAdr$      = zRecAdr         
; -----------
; LOCALS
MaxRecords = 8
RecDescAdr
         words MaxRecords
RecHdl   byte 0,0
         bytes MaxRecords 
CurRecord         byte 0   ; Current record. Negates need to pass in Record Handle for some operations.
CurField          byte 0   ; Current field. Internal temporary storage.
CurFieldLength    word 0   ; Current field length. Internal temporary storage.
FieldOsCalcF      byte 0   ; Flag for field offset calculation. 0 = Calculate, 1 = Suppress.
; -----------

#Region ; Description

; Up to 127 Record Types can be created. 
; NOTE: A maximum of 256 Record Types could be achieved with a slight code change.

; Up to 127 fields can be created for each Record Type. 
; Must have at least one field, numbering begins at 0. If Fields=1 then RecFldCount=0.

; A record type defines a chunk of contiguous 16-bit memory indexes
; Max 128 fields. 2-byte field position. 
; Pos+1 is used to calculate length of last field.
; Field size: 16 bit. Field size is 1-65536
; Position 1 is always 0. Calculated during Init, once only.
; Field Pos: <Pos2, >Pos2[ ,Pos3, ... ]
; NOTE: Field Pos can be statically assigned in the Descriptor - if so, suppress pos calculation.

; RECORD OPERATIONS
; Init Record Manager: Init ()
; Create a Record Type: Create (W rAX Record Descriptor, B rY Flag 0 = Generate Offset Table) :  B, rSC
; Delete a Record Type: Delete ( Handle ) : rSC

; FIELD OPERATIONS
; Get Field Count: GetFldCnt ( Hdl ) : B
; Get Field Offset: GetFldOs ( Hdl, Field ) : W
; Get Field Length: GetFldLen (Hdl, Field ) : W
; Get Field Address: GetFldAdr (Hdl, Field, ZP Long Address ) : L rAXY Address
; Get Short Field Adr and Len: GetFldAdrLenS (Hdl, Field, ZP-W Adr) : W rAX Address, B rY Field Length 
; Get Len and Datatype, Long Field Adr in Rec_zRecAdr$: GetFldLenDt (Hdl, Field, ZP-W Adr) 
;   : W rA Field Datatype, rYX Field Length, L Rec_zRecAdr$. NOTE: rY has Lo-byte of Length.

#EndRegion

; Record Descriptor Indices: Use an index with zRecDesVec to fetch Record info.
RecordId       = 0                  ; B
RecFldCount    = RecordId + 1       ; B
RecFldDtypeAdr = RecFldCount + 1    ; W
RecFldLenAdr   = RecFldDtypeAdr + 2 ; W 
RecFldOsAdr    = RecFldLenAdr + 2   ; W 

#Region ; Record Operations

; RECORD OPERATIONS

         ; Create a Record Handle List
Init     ldx #<RecHdl
         ldy #>RecHdl
         lda #MaxRecords
         jsr Hdl_Create$

         ; Add default Record Types
@C1      lda DefRectypCnt
         sta DefRectypCtr
         jmp @C2

@L1      asl
         tay
         lda DefRectypAdr,Y
         ldx DefRectypAdr + 1,Y
         jsr Create

@C2      dec DefRectypCtr
         lda DefRectypCtr
         bpl @L1
         rts
         
; Store RecordType (.A.X) and get its Handle. Optionally calculate field lengths.     
Create   pha
         txa
         pha
         sty FieldOsCalcF

         ldx #<RecHdl
         ldy #>RecHdl
         jsr Hdl_Allocate$
         bcc @C1           ; OKAY
         
         pla               ; ERROR
         tax
         pla
         rts

@C1      asl
         tay
         pla
         sta RecDescAdr + 1,Y
         sta zRecDesVec + 1
         pla
         sta RecDescAdr,Y
         sta zRecDesVec
                  
         ; Store valid Record Id (Handle). It's useful for user code to fetch the handle from here.
         ; A value of 255 means it is unallocated.
         tya
         lsr
         ldy #RecordId
         sta (zRecDesVec),Y

; Calculate field offsets. First is always 0, next is previous + Field Length
         
         ldy FieldOsCalcF
         bne @Exit
 
         ; Set up vector to Field Length data
         ldy #RecFldLenAdr
         lda (zRecDesVec),Y
         sta zTW1$
         iny
         lda (zRecDesVec),Y
         sta zTW1$ + 1
         
         ; Set up vector to Field Offset data
         ldy #RecFldOsAdr
         lda (zRecDesVec),Y
         sta zTW2$
         iny
         lda (zRecDesVec),Y
         sta zTW2$ + 1

         ; Set zero-term counter
         ldy #RecFldCount  
         lda (zRecDesVec),Y
         clc
         adc #1
         sta zTB1$         

         lda #0
         tay
         sta (zTW2$),Y
         iny
         sta (zTW2$),Y

         ; Put Length + Offset into Offset + 1
@L1      dey               
         clc
         lda (zTW1$),Y     ; Get Lo-byte of current field
         adc (zTW2$),Y     ; Add Lo-byte of current offset
         iny
         iny
         sta (zTW2$),Y     ; Set Lo-byte of current offset + 1
         dey               
         lda (zTW1$),Y     ; Get Hi-byte of current field
         adc (zTW2$),Y     ; Add Hi-byte of current offset
         iny
         iny
         sta (zTW2$),y     ; Set Hi-byte of current offset + 1

         dec zTB1$
         bne @L1
@Exit    rts

; Deallocate a Record Type
Delete   ldx #<RecHdl
         ldy #>RecHdl
         jsr Hdl_Deallocate$
         bcc @C1           ; OKAY
         rts               ; ERROR

         ; Set RecordId to 255
@C1      jsr PIniRecDesVec ; NOTE: This sets Current Record to the deallocated ID.   
         lda #255
         ldy #RecordId
         sta (zRecDesVec),Y
         rts

#EndRegion

#Region ; Field Operations

; FIELD OPERATIONS

GetFldCnt jsr PIniRecDesVec
IGetFldCnt ldy #RecFldCount
         jmp GetRecDesBA

GetFldDt jsr PIniRecDesVec
IGetFldDt stx CurField
         ldy #RecFldDtypeAdr
         jsr GetRecDesWX
         lda CurField
         jmp GetRecDesSubBA 

GetFldOs jsr PIniRecDesVec
IGetFldOs stx CurField
         ldy #RecFldOsAdr
         jsr GetRecDesWX
         lda CurField
         jmp GetRecDesSubWA 

GetFldLen jsr PIniRecDesVec
IGetFldLen stx CurField
         ldy #RecFldLenAdr
         jsr GetRecDesWX
         lda CurField
         jmp GetRecDesSubWA 

; Hdl, Fld, ZP LAdr)
GetFldAdr jsr PIniRecDesVec
IGetFldAdr tya
         pha                        ; Preserve ZP address of Base Long Address
         stx CurField               ; Preserve Field
         ldy #RecFldOsAdr
         jsr GetRecDesWX            ; rXY has addr of the offset table
         
         lda CurField               ; Restore Field
         jsr GetRecDesSubWA         ; Fetch W rAX from address at (rXY),rA 

         ; Store word for Addition
         sta zTW1$
         stx zTW1$ + 1
         
         ; Set X with ZP index of Long to add to Offset
         pla
         tax
                  
         ; Add Word with Ext 0 to ZP Long, result in .A.X.Y.
         clc
         lda zTW1$
         adc $00,X
         pha               ; Low byte
         lda zTW1$ + 1
         adc $01,X
         pha               ; High byte
         lda #0            ; Field Offset Ext byte is always 0
         adc $02,X
         tay               ; Ext byte
         pla
         tax
         pla
         rts

GetRecDesSubBA
         stx zTW1$
         sty zTW1$ + 1
         tay
         lda (zTW1$),Y
         rts
GetRecDesSubWA
         stx zTW1$
         sty zTW1$ + 1
         asl
         tay
         lda (zTW1$),Y
         pha
         iny
         lda (zTW1$),Y
         tax
         pla
         rts

; A convenient way to get a 16-bit address and its 8-bit field length. 
GetFldAdrLenS jsr PIniRecDesVec
         
         ; Preserve Field Index
         stx CurField
         
         jsr IGetFldAdr
         sta zA1$                   ; Store safely in Accu #1
         stx zA1$ + 1

         ;Recover Field Index
         ldx CurField
         lda CurRecord
         jsr IGetFldLen 

         tay      ; Use only .A's low byte

         lda zA1$
         ldx zA1$ + 1
         rts

GetFldLenDt jsr PIniRecDesVec

         ; Preserve Field Index
         stx CurField
         
         ; Get Long Address of Field
         jsr IGetFldAdr

         ; For access by the user
         sta zRecAdr
         stx zRecAdr + 1
         sty zRecAdr + 2

         ; Get Field Length
         lda CurRecord
         ldx CurField
         jsr IGetFldLen 

         sta CurFieldLength
         stx CurFieldLength + 1

         ; Get Field Datatype
         lda CurRecord
         ldx CurField
         jsr IGetFldDt 
         
         ; Convenient to user to have Lo-byte of Length in rY, eg instant index if rX=0. 
         ldy CurFieldLength
         ldx CurFieldLength + 1
         rts

#EndRegion

#Region ; Helper Subroutines

; HELPER SUBROUTINES

; The following routines are general get and set 
; for Long, Word or Byte from Array Descriptor lookup.
; zRecDesVal values are stored byte-order backwards

GetRecDes stx zTB1$
         tya
         clc
         adc zTB1$
         tay
@Loop    lda (zRecDesVec),Y
         sta zRecDesVal,X
         dey
         dex
         bpl @Loop
         rts

; .A Handle, .Y index to Info item
GetRecDesLA ldx #2
         jsr GetRecDes
         lda zRecDesVal + 0
         ldx zRecDesVal + 1
         ldy zRecDesVal + 2
         rts

GetRecDesWA ldx #1
         jsr GetRecDes
         lda zRecDesVal + 0
         ldx zRecDesVal + 1
         rts
GetRecDesWX ldx #1
         jsr GetRecDes
         ldx zRecDesVal + 0
         ldy zRecDesVal + 1
         rts

GetRecDesBA ldx #0
         jsr GetRecDes
         lda zRecDesVal + 0
         rts

; Assign Record's Descriptor to the vector. AXY preserved. Current Record used. 
AIniRecDesVec pha
         txa
         pha
         tya
         pha
         lda CurRecord
         jmp PInReDeVeC

; Assign Record's Descriptor to the vector. AXY preserved. Record handle in .A. 
PIniRecDesVec sta CurRecord
         pha
         txa
         pha
         tya
         pha
         lda CurRecord
         
PInReDeVeC jsr UIniRecDesVec
         pla
         tay
         pla
         tax
         pla
         rts

UIniRecDesVec asl          ; Record Handle
         tay
         lda RecDescAdr,Y
         sta zRecDesVec,Y
         lda RecDescAdr + 1,Y
         sta zRecDesVec + 1,Y
         rts

#EndRegion

#Region ; Default Records

; DEFAULT RECORDS

DefRectypCnt byte 2 
DefRectypAdr  word RecType1, RecType2

RecType1 byte 255          ; ID
         byte 0            ; FieldCount: 1 field size 40 bytes
         word @FldDtyp
         word @FldLen
         word @FldOs
@FldDtyp byte 0            ; Field Datatypes
@FldLen  word 40           ; Field lengths
@FldOs   word 0,0          ; Field Offsets. Last OS is used to calc len of last position

RecType2 byte 255
         byte 2
         word @FldDtyp
         word @FldLen
         word @FldOs
@FldDtyp byte 0,0,0         
@FldLen  word 32,1,1
@FldOs   word 0,0,0,0


DefRectypCtr   byte 0

#EndRegion

; --------------------
; HANDLE MANAGER
; ----------
; PUBLIC
Hdl_Create$       = Hdl_Create
Hdl_Allocate$     = Hdl_Allocate
Hdl_Deallocate$   = Hdl_Deallocate
; ----------
; LOCAL
TermFlag$         byte 0
Profile$          byte 0
; ----------

; Create Handle
;  Address = <.X >.Y
;  Max Amount = .A (1..253)

Hdl_Create stx zTW1$
         sty zTW1$ + 1
         ldy #0
         sta (zTW1$),Y       ; Write Max
         iny
         sta (zTW1$),Y       ; Write 
         tax
         dex
@L1      iny               ; Write sequence 0..n
         txa
         sta (zTW1$),Y
         dex
         cpx #255
         bne @L1
         rts


; Allocate Handle
;  Address = <.X >.Y
;  Return = .A

Hdl_Allocate stx zTW1$
         sty zTW1$ + 1
         ldy #1
         lda (zTW1$),Y
         bne @C1           
         sec
         bcs @C2           ; ERROR
@C1      sec
         sbc #1
         sta (zTW1$),Y
         tay               ; Inc index to correct position
         iny
         iny
         lda (zTW1$),Y

         clc
@C2      rts


; Deallocate Handle
;  Address = <.X >.Y
;  Handle = .A

Hdl_Deallocate stx zTW1$
         sty zTW1$ + 1
         tax
         ldy #1
         lda (zTW1$),Y
         dey
         cmp (zTW1$),Y
         bne @C1 
         sec               ; ERROR
         bcs @C2
@C1      clc
         adc #1            ; Inc pointer
         iny
         sta (zTW1$),Y
         tay
         iny
         txa               ; Handle to return
         sta (zTW1$),Y

         clc
@C2      rts



Prime

Re: Record Manager

Post by Prime » Tue Oct 28, 2014 1:02 am

I haven't time to study your code right now but I will as soon as I've some free time thanks for sharing Marc.
Just taking a quick peek your coding style is nice and clean easy to follow program flow

Post Reply Previous topicNext topic

Who is online

Users browsing this forum: No registered users and 1 guest