- 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>
201 lines
5.0 KiB
Go
201 lines
5.0 KiB
Go
// Copyright (c) 2026 Charles KWON OhJun (charleskwonohjun@gmail.com)
|
|
// All rights reserved.
|
|
|
|
// FPT memo file handler for Five.
|
|
// Byte-compatible with Harbour/FoxPro FPT memo files.
|
|
//
|
|
// FPT format:
|
|
// Header (512 bytes): nextBlock(4 BE), reserved(2), blockSize(2 BE), reserved(504)
|
|
// Blocks: type(4 BE) + size(4 BE) + data(variable)
|
|
//
|
|
// DBFCDX uses FPT memo (not DBT). DBFNTX traditionally uses DBT but
|
|
// Five supports FPT for both via this handler.
|
|
//
|
|
// Reference:
|
|
// /mnt/d/harbour-core/src/rdd/dbffpt/dbffpt1.c
|
|
// docs/dbf-engine-spec.md Section 6
|
|
package dbf
|
|
|
|
import (
|
|
"encoding/binary"
|
|
"fmt"
|
|
"os"
|
|
)
|
|
|
|
// FPT constants
|
|
const (
|
|
FPTHeaderSize = 512
|
|
FPTDefaultBlock = 64 // FoxPro default
|
|
FPTBlockTypeMemo = 1 // memo text
|
|
)
|
|
|
|
// FPTHeader is the FPT memo file header (512 bytes on disk).
|
|
// All multi-byte fields are BIG-ENDIAN (unlike DBF which is LE).
|
|
type FPTHeader struct {
|
|
NextBlock uint32 // offset 0: next free block (BE)
|
|
Reserved1 uint16 // offset 4
|
|
BlockSize uint16 // offset 6: block size in bytes (BE)
|
|
// offset 8-511: reserved
|
|
}
|
|
|
|
// FPTFile handles FPT memo file operations.
|
|
type FPTFile struct {
|
|
file *os.File
|
|
header FPTHeader
|
|
blockSize int
|
|
}
|
|
|
|
// OpenFPT opens an existing FPT memo file.
|
|
func OpenFPT(path string) (*FPTFile, error) {
|
|
f, err := os.OpenFile(path, os.O_RDWR, 0)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("open FPT %s: %w", path, err)
|
|
}
|
|
|
|
// Read header (first 8 bytes, big-endian)
|
|
buf := make([]byte, 8)
|
|
if _, err := f.ReadAt(buf, 0); err != nil {
|
|
f.Close()
|
|
return nil, fmt.Errorf("read FPT header: %w", err)
|
|
}
|
|
|
|
hdr := FPTHeader{
|
|
NextBlock: binary.BigEndian.Uint32(buf[0:4]),
|
|
Reserved1: binary.BigEndian.Uint16(buf[4:6]),
|
|
BlockSize: binary.BigEndian.Uint16(buf[6:8]),
|
|
}
|
|
|
|
blockSize := int(hdr.BlockSize)
|
|
if blockSize == 0 {
|
|
blockSize = FPTDefaultBlock
|
|
}
|
|
|
|
return &FPTFile{
|
|
file: f,
|
|
header: hdr,
|
|
blockSize: blockSize,
|
|
}, nil
|
|
}
|
|
|
|
// CreateFPT creates a new FPT memo file.
|
|
func CreateFPT(path string, blockSize int) (*FPTFile, error) {
|
|
if blockSize <= 0 {
|
|
blockSize = FPTDefaultBlock
|
|
}
|
|
|
|
f, err := os.Create(path)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("create FPT %s: %w", path, err)
|
|
}
|
|
|
|
// Header occupies enough blocks to fill FPTHeaderSize
|
|
headerBlocks := uint32(FPTHeaderSize / blockSize)
|
|
if FPTHeaderSize%blockSize != 0 {
|
|
headerBlocks++
|
|
}
|
|
|
|
hdr := FPTHeader{
|
|
NextBlock: headerBlocks,
|
|
BlockSize: uint16(blockSize),
|
|
}
|
|
|
|
// Write header
|
|
buf := make([]byte, FPTHeaderSize)
|
|
binary.BigEndian.PutUint32(buf[0:4], hdr.NextBlock)
|
|
binary.BigEndian.PutUint16(buf[4:6], hdr.Reserved1)
|
|
binary.BigEndian.PutUint16(buf[6:8], hdr.BlockSize)
|
|
if _, err := f.Write(buf); err != nil {
|
|
f.Close()
|
|
return nil, err
|
|
}
|
|
|
|
return &FPTFile{
|
|
file: f,
|
|
header: hdr,
|
|
blockSize: blockSize,
|
|
}, nil
|
|
}
|
|
|
|
// Close closes the FPT file.
|
|
func (fpt *FPTFile) Close() error {
|
|
return fpt.file.Close()
|
|
}
|
|
|
|
// ReadMemo reads memo data from a block number.
|
|
// Returns nil for block 0 (empty memo).
|
|
func (fpt *FPTFile) ReadMemo(blockNo uint32) ([]byte, error) {
|
|
if blockNo == 0 {
|
|
return nil, nil
|
|
}
|
|
|
|
offset := int64(blockNo) * int64(fpt.blockSize)
|
|
|
|
// Read block header: type(4 BE) + size(4 BE)
|
|
hdr := make([]byte, 8)
|
|
if _, err := fpt.file.ReadAt(hdr, offset); err != nil {
|
|
return nil, fmt.Errorf("read memo block %d header: %w", blockNo, err)
|
|
}
|
|
|
|
// blockType := binary.BigEndian.Uint32(hdr[0:4]) // 0=picture, 1=memo, 2=object
|
|
dataSize := binary.BigEndian.Uint32(hdr[4:8])
|
|
|
|
if dataSize == 0 {
|
|
return nil, nil
|
|
}
|
|
|
|
// Read data
|
|
data := make([]byte, dataSize)
|
|
if _, err := fpt.file.ReadAt(data, offset+8); err != nil {
|
|
return nil, fmt.Errorf("read memo block %d data: %w", blockNo, err)
|
|
}
|
|
|
|
return data, nil
|
|
}
|
|
|
|
// WriteMemo writes memo data and returns the block number.
|
|
// Appends at the next available block.
|
|
func (fpt *FPTFile) WriteMemo(data []byte) (uint32, error) {
|
|
if len(data) == 0 {
|
|
return 0, nil // empty memo = block 0
|
|
}
|
|
|
|
blockNo := fpt.header.NextBlock
|
|
offset := int64(blockNo) * int64(fpt.blockSize)
|
|
|
|
// Write block header: type(4 BE) + size(4 BE)
|
|
hdr := make([]byte, 8)
|
|
binary.BigEndian.PutUint32(hdr[0:4], FPTBlockTypeMemo)
|
|
binary.BigEndian.PutUint32(hdr[4:8], uint32(len(data)))
|
|
|
|
if _, err := fpt.file.WriteAt(hdr, offset); err != nil {
|
|
return 0, fmt.Errorf("write memo block header: %w", err)
|
|
}
|
|
|
|
// Write data
|
|
if _, err := fpt.file.WriteAt(data, offset+8); err != nil {
|
|
return 0, fmt.Errorf("write memo block data: %w", err)
|
|
}
|
|
|
|
// Calculate blocks used
|
|
totalBytes := 8 + len(data) // header + data
|
|
blocksUsed := totalBytes / fpt.blockSize
|
|
if totalBytes%fpt.blockSize != 0 {
|
|
blocksUsed++
|
|
}
|
|
|
|
// Update next block pointer
|
|
fpt.header.NextBlock = blockNo + uint32(blocksUsed)
|
|
fpt.updateHeader()
|
|
|
|
return blockNo, nil
|
|
}
|
|
|
|
// updateHeader writes the header back to file.
|
|
func (fpt *FPTFile) updateHeader() {
|
|
buf := make([]byte, 8)
|
|
binary.BigEndian.PutUint32(buf[0:4], fpt.header.NextBlock)
|
|
binary.BigEndian.PutUint16(buf[4:6], fpt.header.Reserved1)
|
|
binary.BigEndian.PutUint16(buf[6:8], fpt.header.BlockSize)
|
|
fpt.file.WriteAt(buf, 0)
|
|
}
|