Files
five/hbrt/value.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

431 lines
10 KiB
Go

// Copyright (c) 2026 Charles KWON OhJun (charleskwonohjun@gmail.com)
// All rights reserved.
// Package hbrt provides the core runtime for the Five language.
// Tagged Value 24B: the fundamental value representation.
//
// Layout:
// scalar (uint64): numeric/date/bool raw bits
// info (uint64): [type:8][meta:24][aux:32]
// ptr (unsafe.Pointer): GC-traced pointer for string/array/hash/block
//
// Design rationale:
// - 24B vs Harbour's 32B HB_ITEM = 25% smaller
// - Scalar types (55% of runtime values) use scalar+info only, ptr=nil
// - Pointer types use ptr field, which Go's GC can trace directly
// - No global pointer store, no mutex, no memory leaks
// - Inspired by typescript-go (tsgo): "don't fight the GC, design around it"
//
// See docs/harbour-type-system-analysis.md for full analysis.
package hbrt
import (
"fmt"
"math"
"unsafe"
)
// Value is the fundamental value type in Five (24 bytes).
// Scalar types use scalar+info fields (ptr is nil).
// Pointer types use ptr field (GC-traced) + info for metadata.
type Value struct {
scalar uint64 // numeric/date/bool raw bits
info uint64 // [type:8 bits][meta:24 bits][aux:32 bits]
ptr unsafe.Pointer // GC-traced pointer (nil for scalar types)
}
// --- Type constants (upper 8 bits of info) ---
const (
tNil byte = 0
tLogical byte = 1
tInt byte = 2
tLong byte = 3
tDouble byte = 4
tDate byte = 5
tTimestamp byte = 6
tString byte = 7
tArray byte = 8
tHash byte = 9
tBlock byte = 10
tSymbol byte = 11
tByref byte = 12
tPointer byte = 13
tObject byte = 14
)
// info field bit layout
const (
typeShift = 56
metaMask = 0x00FFFFFF00000000
metaShift = 32
auxMask = 0x00000000FFFFFFFF
)
func makeInfo(typ byte, meta uint32, aux uint32) uint64 {
return uint64(typ)<<typeShift | uint64(meta&0x00FFFFFF)<<metaShift | uint64(aux)
}
// --- Type checking ---
func (v Value) Type() byte { return byte(v.info >> typeShift) }
func (v Value) IsNil() bool { return v.Type() == tNil }
func (v Value) IsLogical() bool { return v.Type() == tLogical }
func (v Value) IsInt() bool { return v.Type() == tInt }
func (v Value) IsLong() bool { return v.Type() == tLong }
func (v Value) IsDouble() bool { return v.Type() == tDouble }
func (v Value) IsDate() bool { return v.Type() == tDate }
func (v Value) IsTimestamp() bool { return v.Type() == tTimestamp }
func (v Value) IsString() bool { return v.Type() == tString }
func (v Value) IsArray() bool { t := v.Type(); return t == tArray || t == tObject }
func (v Value) IsHash() bool { return v.Type() == tHash }
func (v Value) IsBlock() bool { return v.Type() == tBlock }
func (v Value) IsSymbol() bool { return v.Type() == tSymbol }
func (v Value) IsByref() bool { return v.Type() == tByref }
func (v Value) IsPointer() bool { return v.Type() == tPointer }
func (v Value) IsObject() bool { return v.Type() == tObject }
// Composite type checks (matching Harbour's HB_IT_* groups)
func (v Value) IsNumeric() bool { t := v.Type(); return t == tInt || t == tLong || t == tDouble }
func (v Value) IsNumInt() bool { t := v.Type(); return t == tInt || t == tLong }
func (v Value) IsDateTime() bool { t := v.Type(); return t == tDate || t == tTimestamp }
// --- Scalar constructors (no heap allocation) ---
func MakeNil() Value {
return Value{info: makeInfo(tNil, 0, 0)}
}
func MakeBool(b bool) Value {
var d uint64
if b {
d = 1
}
return Value{scalar: d, info: makeInfo(tLogical, 0, 0)}
}
// MakeInt creates an integer Value with display width.
func MakeInt(v int) Value {
return Value{
scalar: uint64(int64(v)),
info: makeInfo(tInt, uint32(intExpLen(int64(v))), 0),
}
}
// MakeLong creates a 64-bit integer Value.
func MakeLong(v int64) Value {
return Value{
scalar: uint64(v),
info: makeInfo(tLong, uint32(longExpLen(v)), 0),
}
}
// MakeDouble creates a double Value with display width and decimal places.
func MakeDouble(v float64, length, decimal uint16) Value {
meta := uint32(length)<<8 | uint32(decimal)
return Value{
scalar: math.Float64bits(v),
info: makeInfo(tDouble, meta, 0),
}
}
// MakeDoubleAuto creates a double with default display format.
func MakeDoubleAuto(v float64) Value {
return MakeDouble(v, 255, 255)
}
// MakeDate creates a date Value from Julian day number.
func MakeDate(julian int64) Value {
return Value{scalar: uint64(julian), info: makeInfo(tDate, 0, 0)}
}
// MakeTimestamp creates a timestamp Value from Julian day + milliseconds.
func MakeTimestamp(julian int64, timeMs int32) Value {
return Value{
scalar: uint64(julian),
info: makeInfo(tTimestamp, 0, uint32(timeMs)),
}
}
// --- Value extraction ---
func (v Value) AsBool() bool { return v.scalar != 0 }
func (v Value) AsInt() int { return int(int64(v.scalar)) }
func (v Value) AsLong() int64 { return int64(v.scalar) }
func (v Value) AsDouble() float64 { return math.Float64frombits(v.scalar) }
func (v Value) AsJulian() int64 { return int64(v.scalar) }
func (v Value) AsTimeMs() int32 { return int32(v.info & auxMask) }
func (v Value) AsNumInt() int64 { return int64(v.scalar) }
// AsNumDouble returns a double value from any numeric type.
func (v Value) AsNumDouble() float64 {
switch v.Type() {
case tInt, tLong:
return float64(int64(v.scalar))
case tDouble:
return math.Float64frombits(v.scalar)
default:
return 0
}
}
// Display metadata
func (v Value) Length() uint16 {
switch v.Type() {
case tInt, tLong:
return uint16((v.info & metaMask) >> metaShift)
case tDouble:
return uint16((v.info & metaMask) >> (metaShift + 8))
default:
return 0
}
}
func (v Value) Decimal() uint16 {
if v.Type() == tDouble {
return uint16((v.info & metaMask) >> metaShift & 0xFF)
}
return 0
}
// --- Pointer type backing stores ---
// HbString is the string backing store.
type HbString struct {
Data string // Go immutable string (primary storage)
Bytes []byte // mutable buffer (for in-place edits, nil if immutable)
}
// HbArray is the array/object backing store.
type HbArray struct {
Items []Value
Class uint16
PrevCls uint16
}
// HbHash is the hash table backing store.
type HbHash struct {
Keys []Value
Values []Value
Order []int
Flags int32
}
// HbBlock is the code block backing store.
type HbBlock struct {
Fn func(*Thread)
DetachedLen int
Detached []Value
}
// --- Pointer type constructors ---
// These store Go pointers in Value.ptr, which the GC can trace.
// No global store, no mutex, no memory leaks.
// MakeString creates a string Value.
func MakeString(s string) Value {
hs := &HbString{Data: s}
return Value{
info: makeInfo(tString, 0, uint32(len(s))),
ptr: unsafe.Pointer(hs),
}
}
// MakeArray creates an array Value.
func MakeArray(size int) Value {
ha := &HbArray{Items: make([]Value, size)}
return Value{
info: makeInfo(tArray, 0, 0),
ptr: unsafe.Pointer(ha),
}
}
// MakeArrayFrom creates an array Value from existing items.
func MakeArrayFrom(items []Value) Value {
ha := &HbArray{Items: items}
return Value{
info: makeInfo(tArray, 0, 0),
ptr: unsafe.Pointer(ha),
}
}
// MakeObject creates an object Value (array with class).
func MakeObject(classID uint16, fieldCount int) Value {
ha := &HbArray{Items: make([]Value, fieldCount), Class: classID}
return Value{
info: makeInfo(tObject, uint32(classID), 0),
ptr: unsafe.Pointer(ha),
}
}
// MakeHash creates an empty hash Value.
func MakeHash() Value {
hh := &HbHash{}
return Value{
info: makeInfo(tHash, 0, 0),
ptr: unsafe.Pointer(hh),
}
}
func MakeHashFrom(hh *HbHash) Value {
return Value{
info: makeInfo(tHash, 0, 0),
ptr: unsafe.Pointer(hh),
}
}
// MakeBlock creates a code block Value.
func MakeBlock(fn func(*Thread), detachedLocals int) Value {
hb := &HbBlock{
Fn: fn,
DetachedLen: detachedLocals,
Detached: make([]Value, detachedLocals),
}
return Value{
info: makeInfo(tBlock, 0, 0),
ptr: unsafe.Pointer(hb),
}
}
// --- Pointer type accessors ---
func (v Value) AsString() string {
if v.ptr == nil {
return ""
}
hs := (*HbString)(v.ptr)
if hs.Bytes != nil {
return string(hs.Bytes)
}
return hs.Data
}
func (v Value) StringLen() int {
return int(v.info & auxMask)
}
func (v Value) AsArray() *HbArray {
if v.ptr == nil {
return nil
}
return (*HbArray)(v.ptr)
}
func (v Value) AsHash() *HbHash {
if v.ptr == nil {
return nil
}
return (*HbHash)(v.ptr)
}
func (v Value) AsBlock() *HbBlock {
if v.ptr == nil {
return nil
}
return (*HbBlock)(v.ptr)
}
// AsPointer returns the Go interface{} stored in a Pointer value.
func (v Value) AsPointer() interface{} {
if v.ptr == nil {
return nil
}
return *(*interface{})(v.ptr)
}
// MakePointer wraps an arbitrary Go value as a Harbour Pointer type.
func MakePointer(val interface{}) Value {
p := new(interface{})
*p = val
return Value{
info: makeInfo(tPointer, 0, 0),
ptr: unsafe.Pointer(p),
}
}
// --- Numeric auto-promotion ---
// MakeNumInt creates an Int or Long depending on value range.
func MakeNumInt(v int64) Value {
if v >= math.MinInt32 && v <= math.MaxInt32 {
return MakeInt(int(v))
}
return MakeLong(v)
}
// --- Display length helpers ---
func intExpLen(v int64) int {
if v == 0 {
return 1
}
n := 0
if v < 0 {
n = 1
v = -v
}
for v > 0 {
n++
v /= 10
}
return n
}
func longExpLen(v int64) int {
return intExpLen(v)
}
// --- Stringer ---
func (v Value) String() string {
switch v.Type() {
case tNil:
return "NIL"
case tLogical:
if v.AsBool() {
return ".T."
}
return ".F."
case tInt:
return fmt.Sprintf("%d", v.AsInt())
case tLong:
return fmt.Sprintf("%d", v.AsLong())
case tDouble:
return fmt.Sprintf("%g", v.AsDouble())
case tDate:
return fmt.Sprintf("Date(%d)", v.AsJulian())
case tTimestamp:
return fmt.Sprintf("Timestamp(%d,%d)", v.AsJulian(), v.AsTimeMs())
case tString:
return fmt.Sprintf("%q", v.AsString())
case tArray:
arr := v.AsArray()
if arr == nil {
return "Array(nil)"
}
return fmt.Sprintf("Array(%d)", len(arr.Items))
case tObject:
arr := v.AsArray()
if arr == nil {
return "Object(nil)"
}
return fmt.Sprintf("Object(class=%d, fields=%d)", arr.Class, len(arr.Items))
case tHash:
hh := v.AsHash()
if hh == nil {
return "Hash(nil)"
}
return fmt.Sprintf("Hash(%d)", len(hh.Keys))
case tBlock:
return "Block{...}"
case tSymbol:
return "Symbol"
case tByref:
return "Byref"
case tPointer:
return fmt.Sprintf("Pointer(%x)", v.scalar)
default:
return fmt.Sprintf("Unknown(type=%d)", v.Type())
}
}