Files
five/hbrdd/dbf/field.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

363 lines
8.6 KiB
Go

// Copyright (c) 2026 Charles KWON OhJun (charleskwonohjun@gmail.com)
// All rights reserved.
// DBF field type conversion: raw bytes ↔ Five Value.
// Each field type (C, N, L, D, M, I, B, @, etc.) has exact byte format.
//
// Reference: /mnt/d/harbour-core/src/rdd/dbf1.c (getValue/putValue)
// docs/dbf-engine-spec.md Section 3
package dbf
import (
"encoding/binary"
"five/hbrt"
"fmt"
"math"
"strconv"
"strings"
)
// GetFieldValue converts raw record bytes to a Five Value.
// Harbour: hb_dbfGetValue in dbf1.c
func GetFieldValue(recBuf []byte, offset uint16, field *FieldDesc) hbrt.Value {
raw := recBuf[offset : offset+uint16(field.Len)]
switch field.Type {
case 'C', 'c': // Character
return hbrt.MakeString(string(raw))
case 'N', 'n': // Numeric (ASCII)
return parseNumericField(raw, field.Dec)
case 'L', 'l': // Logical
return parseLogicalField(raw[0])
case 'D', 'd': // Date
return parseDateField(raw, field.Len)
case 'M', 'm': // Memo (block reference)
return parseMemoRef(raw, field.Len)
case 'I', 'i': // Integer (binary LE)
return parseIntegerField(raw, field.Len)
case 'B', 'b': // Double (IEEE 754 LE)
if field.Len == 8 {
bits := binary.LittleEndian.Uint64(raw)
return hbrt.MakeDoubleAuto(math.Float64frombits(bits))
}
return hbrt.MakeNil()
case '@': // Timestamp (4 bytes date + 4 bytes time, LE)
if field.Len >= 8 {
julian := int64(binary.LittleEndian.Uint32(raw[0:4]))
timeMs := int32(binary.LittleEndian.Uint32(raw[4:8]))
return hbrt.MakeTimestamp(julian, timeMs)
}
return hbrt.MakeNil()
case '+': // Autoincrement (binary LE integer)
return parseIntegerField(raw, field.Len)
case '=': // Modtime (same as Timestamp)
if field.Len >= 8 {
julian := int64(binary.LittleEndian.Uint32(raw[0:4]))
timeMs := int32(binary.LittleEndian.Uint32(raw[4:8]))
return hbrt.MakeTimestamp(julian, timeMs)
}
return hbrt.MakeNil()
case '^': // RowVersion (uint64 LE)
if field.Len == 8 {
return hbrt.MakeLong(int64(binary.LittleEndian.Uint64(raw)))
}
return hbrt.MakeNil()
case 'Y', 'y': // Currency (int64 LE, implicit 4 decimal places)
if field.Len == 8 {
cents := int64(binary.LittleEndian.Uint64(raw))
return hbrt.MakeDouble(float64(cents)/10000.0, 20, 4)
}
return hbrt.MakeNil()
case 'T', 't': // Timestamp (Harbour extension)
if field.Len >= 8 {
julian := int64(binary.LittleEndian.Uint32(raw[0:4]))
timeMs := int32(binary.LittleEndian.Uint32(raw[4:8]))
return hbrt.MakeTimestamp(julian, timeMs)
}
if field.Len == 4 {
// Time only
timeMs := int32(binary.LittleEndian.Uint32(raw[0:4]))
return hbrt.MakeTimestamp(0, timeMs)
}
return hbrt.MakeNil()
default:
// Unknown type: return as string
return hbrt.MakeString(string(raw))
}
}
// PutFieldValue converts a Five Value to raw record bytes.
// Harbour: hb_dbfPutValue in dbf1.c
func PutFieldValue(recBuf []byte, offset uint16, field *FieldDesc, val hbrt.Value) {
raw := recBuf[offset : offset+uint16(field.Len)]
switch field.Type {
case 'C', 'c': // Character
s := val.AsString()
copy(raw, s)
// Pad with spaces
if len(s) < int(field.Len) {
for i := len(s); i < int(field.Len); i++ {
raw[i] = ' '
}
}
case 'N', 'n': // Numeric (ASCII, right-aligned, space-padded)
formatNumericField(raw, field.Len, field.Dec, val)
case 'L', 'l': // Logical
if val.IsNil() {
raw[0] = ' '
} else if val.AsBool() {
raw[0] = 'T'
} else {
raw[0] = 'F'
}
case 'D', 'd': // Date
putDateField(raw, field.Len, val)
case 'M', 'm': // Memo (block reference)
// Memo writes handled by MemoHandler
// Here just store block number
if val.IsNumInt() {
putMemoRef(raw, field.Len, uint32(val.AsNumInt()))
}
case 'I', 'i', '+': // Integer / Autoincrement
putIntegerField(raw, field.Len, val)
case 'B', 'b': // Double (IEEE 754 LE)
if field.Len == 8 {
binary.LittleEndian.PutUint64(raw, math.Float64bits(val.AsNumDouble()))
}
case '@', '=', 'T', 't': // Timestamp / Modtime
if field.Len >= 8 {
binary.LittleEndian.PutUint32(raw[0:4], uint32(val.AsJulian()))
binary.LittleEndian.PutUint32(raw[4:8], uint32(val.AsTimeMs()))
}
case 'Y', 'y': // Currency
if field.Len == 8 {
cents := int64(val.AsNumDouble() * 10000.0)
binary.LittleEndian.PutUint64(raw, uint64(cents))
}
case '^': // RowVersion
if field.Len == 8 {
binary.LittleEndian.PutUint64(raw, uint64(val.AsLong()))
}
default:
// Unknown: write as string
s := val.AsString()
copy(raw, s)
}
}
// --- Internal parsers ---
func parseNumericField(raw []byte, dec byte) hbrt.Value {
s := strings.TrimSpace(string(raw))
if s == "" {
return hbrt.MakeInt(0)
}
if dec == 0 && !strings.Contains(s, ".") {
n, err := strconv.ParseInt(s, 10, 64)
if err == nil {
return hbrt.MakeNumInt(n)
}
}
f, err := strconv.ParseFloat(s, 64)
if err == nil {
return hbrt.MakeDouble(f, uint16(len(raw)), uint16(dec))
}
return hbrt.MakeInt(0)
}
func parseLogicalField(b byte) hbrt.Value {
switch b {
case 'T', 't', 'Y', 'y':
return hbrt.MakeBool(true)
case 'F', 'f', 'N', 'n':
return hbrt.MakeBool(false)
default:
return hbrt.MakeNil() // space = uninitialized
}
}
func parseDateField(raw []byte, fieldLen byte) hbrt.Value {
if fieldLen == 8 {
// Standard: YYYYMMDD ASCII
s := string(raw)
if strings.TrimSpace(s) == "" {
return hbrt.MakeDate(0) // empty date
}
y := parseInt(s[0:4])
m := parseInt(s[4:6])
d := parseInt(s[6:8])
if y > 0 {
return hbrt.MakeDate(dateToJulian(y, m, d))
}
return hbrt.MakeDate(0)
}
if fieldLen == 3 {
// Short: LE uint24
julian := int64(raw[0]) | int64(raw[1])<<8 | int64(raw[2])<<16
return hbrt.MakeDate(julian)
}
if fieldLen == 4 {
// VFP: LE uint32 Julian
return hbrt.MakeDate(int64(binary.LittleEndian.Uint32(raw)))
}
return hbrt.MakeDate(0)
}
func parseMemoRef(raw []byte, fieldLen byte) hbrt.Value {
if fieldLen == 4 {
blockNo := binary.LittleEndian.Uint32(raw)
return hbrt.MakeLong(int64(blockNo))
}
if fieldLen == 10 {
s := strings.TrimSpace(string(raw))
if s == "" {
return hbrt.MakeLong(0)
}
n, _ := strconv.ParseInt(s, 10, 64)
return hbrt.MakeLong(n)
}
return hbrt.MakeLong(0)
}
func parseIntegerField(raw []byte, fieldLen byte) hbrt.Value {
switch fieldLen {
case 1:
return hbrt.MakeInt(int(int8(raw[0])))
case 2:
return hbrt.MakeInt(int(int16(binary.LittleEndian.Uint16(raw))))
case 3:
v := int32(raw[0]) | int32(raw[1])<<8 | int32(raw[2])<<16
if v&0x800000 != 0 {
v |= ^0xFFFFFF // sign extend
}
return hbrt.MakeInt(int(v))
case 4:
return hbrt.MakeInt(int(int32(binary.LittleEndian.Uint32(raw))))
case 8:
return hbrt.MakeLong(int64(binary.LittleEndian.Uint64(raw)))
default:
return hbrt.MakeInt(0)
}
}
// --- Internal formatters ---
func formatNumericField(raw []byte, fieldLen, dec byte, val hbrt.Value) {
d := val.AsNumDouble()
format := "%" + strconv.Itoa(int(fieldLen)) + "." + strconv.Itoa(int(dec)) + "f"
s := []byte(fmt.Sprintf(format, d))
// If too wide, fill with asterisks (Harbour behavior)
if len(s) > int(fieldLen) {
for i := range raw {
raw[i] = '*'
}
return
}
// Right-align, space-pad left
copy(raw, s)
}
func putDateField(raw []byte, fieldLen byte, val hbrt.Value) {
if fieldLen == 8 {
if !val.IsDateTime() || val.AsJulian() == 0 {
copy(raw, " ")
return
}
y, m, d := julianToDate(val.AsJulian())
s := fmt.Sprintf("%04d%02d%02d", y, m, d)
copy(raw, s)
} else if fieldLen == 4 {
binary.LittleEndian.PutUint32(raw, uint32(val.AsJulian()))
}
}
func putMemoRef(raw []byte, fieldLen byte, blockNo uint32) {
if fieldLen == 4 {
binary.LittleEndian.PutUint32(raw, blockNo)
} else if fieldLen == 10 {
s := fmt.Sprintf("%10d", blockNo)
copy(raw, s)
}
}
func putIntegerField(raw []byte, fieldLen byte, val hbrt.Value) {
n := val.AsNumInt()
switch fieldLen {
case 1:
raw[0] = byte(int8(n))
case 2:
binary.LittleEndian.PutUint16(raw, uint16(int16(n)))
case 4:
binary.LittleEndian.PutUint32(raw, uint32(int32(n)))
case 8:
binary.LittleEndian.PutUint64(raw, uint64(n))
}
}
// --- Julian date helpers ---
func dateToJulian(y, m, d int) int64 {
if m <= 2 {
y--
m += 12
}
a := y / 100
b := 2 - a + a/4
return int64(365.25*float64(y+4716)) + int64(30.6001*float64(m+1)) + int64(d+b) - 1524
}
func julianToDate(julian int64) (y, m, d int) {
if julian <= 0 {
return 0, 0, 0
}
l := julian + 68569
n := 4 * l / 146097
l = l - (146097*n+3)/4
i := 4000 * (l + 1) / 1461001
l = l - 1461*i/4 + 31
j := 80 * l / 2447
d = int(l - 2447*j/80)
l = j / 11
m = int(j + 2 - 12*l)
y = int(100*(n-49) + i + l)
return
}
func parseInt(s string) int {
n := 0
for _, c := range s {
if c >= '0' && c <= '9' {
n = n*10 + int(c-'0')
}
}
return n
}