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.
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'
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
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