- 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>
431 lines
10 KiB
Go
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())
|
|
}
|
|
}
|