Files
five/hbrdd/dbf/header.go
Charles KWON OhJun 59568f3301 Five v0.9 — Harbour + Go fusion language
- Compiler: PP → Lexer → Parser → Analyzer → Gengo pipeline
- Parser: 232/236 (98%) Harbour compatibility, registry-based dispatch
- RTL: 351 Harbour-compatible functions
- RDD: DBF/NTX/CDX engines with Rushmore bitmap optimization
- Go Interop: IMPORT + pkg.Func() + obj:Method() with FastPath (15M calls/sec)
- HB_FUNC API: Full Harbour C API compatible Go bridge
- Concurrency: SPAWN/LAUNCH/GOROUTINE, <-, WATCH, PARALLEL FOR, ASYNC/AWAIT
- Extensions: Multi-return, DEFER, Slice, f-string, Nil-safe ?:, CONST
- Macro Compiler: Runtime AST parsing and evaluation
- Debugger: TUI debugger with source display, breakpoints, stepping
- FRB: Native + Pcode dual mode runtime binary
- Tests: 13 packages ALL PASS

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-31 09:41:50 +09:00

176 lines
4.9 KiB
Go

// Copyright (c) 2026 Charles KWON OhJun (charleskwonohjun@gmail.com)
// All rights reserved.
// DBF file header and field descriptor structures.
// EXACT byte-compatible with Harbour's DBFHEADER and DBFFIELD.
//
// Reference: /mnt/d/harbour-core/include/hbdbf.h
// docs/dbf-engine-spec.md
package dbf
import (
"encoding/binary"
"fmt"
"io"
"time"
)
// HeaderSize is the fixed size of the DBF file header.
const HeaderSize = 32
// FieldDescSize is the fixed size of each field descriptor.
const FieldDescSize = 32
// Header terminator byte.
const HeaderTerminator = 0x0D
// EOF marker byte.
const EOFMarker = 0x1A
// Deletion flag values.
const (
RecordActive = ' ' // 0x20
RecordDeleted = '*' // 0x2A
)
// Version byte values.
const (
VersionDBF3 = 0x03 // Standard dBASE III
VersionVFP = 0x30 // Visual FoxPro
VersionVFPAuto = 0x31 // VFP + autoincrement
VersionVFPVar = 0x32 // VFP + varchar/varbinary
VersionDBT = 0x83 // dBASE III + DBT memo
VersionFPT = 0xF5 // DBF + FPT memo
)
// Header represents the 32-byte DBF file header.
// Layout is byte-identical to Harbour's DBFHEADER.
type Header struct {
Version byte // offset 0
Year byte // offset 1 (year - 1900)
Month byte // offset 2
Day byte // offset 3
RecCount uint32 // offset 4 (LE)
HeaderLen uint16 // offset 8 (LE)
RecordLen uint16 // offset 10 (LE)
Reserved1 [2]byte
Transaction byte // offset 14
Encrypted byte // offset 15
Reserved2 [12]byte
HasTags byte // offset 28
CodePage byte // offset 29
Reserved3 [2]byte
}
// ReadHeader reads the 32-byte header from a reader.
func ReadHeader(r io.Reader) (*Header, error) {
h := &Header{}
if err := binary.Read(r, binary.LittleEndian, h); err != nil {
return nil, fmt.Errorf("read DBF header: %w", err)
}
return h, nil
}
// WriteHeader writes the 32-byte header to a writer.
func WriteHeader(w io.Writer, h *Header) error {
return binary.Write(w, binary.LittleEndian, h)
}
// UpdateDate sets the header's last-update date to now.
func (h *Header) UpdateDate() {
now := time.Now()
h.Year = byte(now.Year() - 1900)
h.Month = byte(now.Month())
h.Day = byte(now.Day())
}
// FieldCount calculates the number of fields from header length.
// Harbour: fieldCount = (headerLen - 32 - 1) / 32
func (h *Header) FieldCount() int {
if h.HeaderLen < 33 {
return 0
}
return int(h.HeaderLen-32-1) / FieldDescSize
}
// HasMemo returns true if the version byte indicates a memo file.
func (h *Header) HasMemo() bool {
return h.Version == VersionDBT || h.Version == VersionFPT ||
h.Version == 0x8B || h.Version == 0xE6 || h.Version == 0xF6
}
// RecordOffset calculates the file offset for a record (1-based).
// Harbour: headerLen + (recNo - 1) * recordLen
func (h *Header) RecordOffset(recNo uint32) int64 {
return int64(h.HeaderLen) + int64(recNo-1)*int64(h.RecordLen)
}
// EOFOffset returns the file offset where EOF marker should be written.
func (h *Header) EOFOffset() int64 {
return int64(h.HeaderLen) + int64(h.RecCount)*int64(h.RecordLen)
}
// FieldDesc represents a 32-byte field descriptor.
// Layout is byte-identical to Harbour's DBFFIELD.
type FieldDesc struct {
Name [11]byte // offset 0 (null-terminated)
Type byte // offset 11
Reserved1 [4]byte // offset 12
Len byte // offset 16
Dec byte // offset 17
Flags byte // offset 18
Counter [4]byte // offset 19 (autoincrement, LE)
Step byte // offset 23
Reserved2 [7]byte // offset 24
HasTag byte // offset 31
}
// ReadFieldDescs reads n field descriptors from a reader.
func ReadFieldDescs(r io.Reader, n int) ([]FieldDesc, error) {
fields := make([]FieldDesc, n)
for i := 0; i < n; i++ {
if err := binary.Read(r, binary.LittleEndian, &fields[i]); err != nil {
return nil, fmt.Errorf("read field descriptor %d: %w", i, err)
}
}
return fields, nil
}
// WriteFieldDescs writes field descriptors to a writer.
func WriteFieldDescs(w io.Writer, fields []FieldDesc) error {
for i := range fields {
if err := binary.Write(w, binary.LittleEndian, &fields[i]); err != nil {
return fmt.Errorf("write field descriptor %d: %w", i, err)
}
}
return nil
}
// GetName extracts the field name as a trimmed string.
func (f *FieldDesc) GetName() string {
for i, b := range f.Name {
if b == 0 {
return string(f.Name[:i])
}
}
return string(f.Name[:])
}
// SetName sets the field name (max 10 chars, null-terminated, space-padded).
func (f *FieldDesc) SetName(name string) {
f.Name = [11]byte{}
copy(f.Name[:], name)
}
// BuildFieldOffsets calculates the byte offset of each field within a record.
// offset[0] = 1 (after deletion flag), offset[i+1] = offset[i] + field[i].Len
// Harbour: pFieldOffset array built during OPEN.
func BuildFieldOffsets(fields []FieldDesc) []uint16 {
offsets := make([]uint16, len(fields)+1)
offsets[0] = 1 // skip deletion flag byte
for i, f := range fields {
offsets[i+1] = offsets[i] + uint16(f.Len)
}
return offsets
}