// Copyright (c) 2026 Charles KWON OhJun (charleskwonohjun@gmail.com) // All rights reserved. // hbfunc.go — Harbour HB_FUNC compatible Go API for #pragma BEGINDUMP. // // This provides the complete Harbour C extension API in Go, // allowing PRG code to call inline Go functions seamlessly. // // Usage in PRG: // // #pragma BEGINDUMP // func init() { // hbrt.HB_FUNC("MYFUNC", MyFunc) // } // func MyFunc(ctx *hbrt.HBContext) { // name := ctx.ParC(1) // age := ctx.ParNI(2) // ctx.RetC("Hello " + name) // } // #pragma ENDDUMP // // Then in PRG: // // ? MyFunc("Charles", 30) // → "Hello Charles" package hbrt import ( "fmt" "strings" "time" ) // HBContext wraps Thread for Harbour-compatible C API access. // Maps 1:1 to Harbour's hb_par*/hb_ret*/hb_stor* functions. type HBContext struct { T *Thread } // --------------------------------------------------------------------------- // HB_FUNC registration — called from init() in #pragma BEGINDUMP // --------------------------------------------------------------------------- // HB_FUNC registers a Go function as a Harbour-callable function. // Equivalent to Harbour's HB_FUNC(name) macro. // // The wrapper sets up a runtime Frame so `ctx.ParC(n)` / `ctx.Local(n)` // resolves through *this* function's locals instead of leaking into // the caller's frame. Without Frame the body's `t.Local(n)` indexed // `t.curFrame.localBase + n - 1` against whatever frame the caller // happened to be on — silently reading the caller's locals, and // any default-value path (`ParNIDef`) hid the bug by masking NIL. func HB_FUNC(name string, fn func(ctx *HBContext)) { RegisterDynamicFunc(strings.ToUpper(name), func(t *Thread) { // Snapshot pendingParams before Frame consumes it; the body // expects to declare nParams=actual so each positional arg // lands in a fresh slot, but EndProc/EndProcFast cleanup // must run regardless of how the body returns. nArgs := t.pendingParams t.Frame(nArgs, 0) defer t.EndProc() // retains panic propagation for RECOVER ctx := &HBContext{T: t} fn(ctx) }) } // HB_FUNC_STATIC is same as HB_FUNC but marks as static scope. func HB_FUNC_STATIC(name string, fn func(ctx *HBContext)) { HB_FUNC(name, fn) } // --------------------------------------------------------------------------- // Parameter count — Harbour: hb_pcount() // --------------------------------------------------------------------------- func (c *HBContext) PCount() int { return c.T.ParamCount() } // --------------------------------------------------------------------------- // Parameter access (1-based index) // --------------------------------------------------------------------------- func (c *HBContext) param(n int) Value { if n < 1 || n > c.PCount() { return MakeNil() } return c.T.Local(n) } // Param returns raw Value of parameter n. func (c *HBContext) Param(n int) Value { return c.param(n) } // --------------------------------------------------------------------------- // Type checking — Harbour: HB_IS*(n) macros // --------------------------------------------------------------------------- func (c *HBContext) IsNil(n int) bool { return c.param(n).IsNil() } func (c *HBContext) IsChar(n int) bool { return c.param(n).IsString() } func (c *HBContext) IsString(n int) bool { return c.param(n).IsString() } func (c *HBContext) IsNum(n int) bool { return c.param(n).IsNumeric() } func (c *HBContext) IsNumeric(n int) bool { return c.param(n).IsNumeric() } func (c *HBContext) IsLog(n int) bool { return c.param(n).IsLogical() } func (c *HBContext) IsLogical(n int) bool { return c.param(n).IsLogical() } func (c *HBContext) IsDate(n int) bool { return c.param(n).IsDate() } func (c *HBContext) IsDateTime(n int) bool { return c.param(n).IsDateTime() } func (c *HBContext) IsArray(n int) bool { return c.param(n).IsArray() } func (c *HBContext) IsHash(n int) bool { return c.param(n).IsHash() } func (c *HBContext) IsBlock(n int) bool { return c.param(n).IsBlock() } func (c *HBContext) IsObject(n int) bool { return c.param(n).IsObject() } func (c *HBContext) IsPointer(n int) bool { return c.param(n).IsPointer() } // --------------------------------------------------------------------------- // String parameters — Harbour: hb_parc, hb_parclen // --------------------------------------------------------------------------- // ParC returns string parameter n. Harbour: hb_parc(n) func (c *HBContext) ParC(n int) string { v := c.param(n) if v.IsString() { return v.AsString() } return "" } // ParCLen returns length of string parameter n. Harbour: hb_parclen(n) func (c *HBContext) ParCLen(n int) int { v := c.param(n) if v.IsString() { return len(v.AsString()) } return 0 } // --------------------------------------------------------------------------- // Numeric parameters — Harbour: hb_parni, hb_parnl, hb_parnd // --------------------------------------------------------------------------- // ParNI returns int parameter. Harbour: hb_parni(n) func (c *HBContext) ParNI(n int) int { v := c.param(n) if v.IsNumeric() { return v.AsInt() } return 0 } // ParNIDef returns int parameter with default. Harbour: hb_parnidef(n, def) func (c *HBContext) ParNIDef(n int, def int) int { v := c.param(n) if v.IsNumeric() { return v.AsInt() } return def } // ParNL returns int64 parameter. Harbour: hb_parnl(n) func (c *HBContext) ParNL(n int) int64 { v := c.param(n) if v.IsNumeric() { return v.AsLong() } return 0 } // ParNLDef returns int64 parameter with default. Harbour: hb_parnldef(n, def) func (c *HBContext) ParNLDef(n int, def int64) int64 { v := c.param(n) if v.IsNumeric() { return v.AsLong() } return def } // ParND returns float64 parameter. Harbour: hb_parnd(n) func (c *HBContext) ParND(n int) float64 { v := c.param(n) if v.IsNumeric() { return v.AsNumDouble() } return 0 } // ParNDDef returns float64 parameter with default. func (c *HBContext) ParNDDef(n int, def float64) float64 { v := c.param(n) if v.IsNumeric() { return v.AsNumDouble() } return def } // ParNInt returns HB_MAXINT parameter. Harbour: hb_parnint(n) func (c *HBContext) ParNInt(n int) int64 { return c.ParNL(n) } // --------------------------------------------------------------------------- // Logical parameters — Harbour: hb_parl // --------------------------------------------------------------------------- // ParL returns bool parameter. Harbour: hb_parl(n) func (c *HBContext) ParL(n int) bool { v := c.param(n) if v.IsLogical() { return v.AsBool() } return false } // ParLDef returns bool parameter with default. Harbour: hb_parldef(n, def) func (c *HBContext) ParLDef(n int, def bool) bool { v := c.param(n) if v.IsLogical() { return v.AsBool() } return def } // --------------------------------------------------------------------------- // Date parameters — Harbour: hb_pards, hb_pardl // --------------------------------------------------------------------------- // julianToYMD converts Julian day to year, month, day. func julianToYMD(julian int64) (int, int, 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 := l - 2447*j/80 l = j / 11 m := j + 2 - 12*l y := 100*(n-49) + i + l return int(y), int(m), int(d) } // ymdToJulian converts year, month, day to Julian day number. func ymdToJulian(y, m, d int) int64 { if y == 0 && m == 0 && d == 0 { return 0 } mm := int64(m) yy := int64(y) dd := int64(d) return dd - 32075 + 1461*(yy+4800+(mm-14)/12)/4 + 367*(mm-2-(mm-14)/12*12)/12 - 3*((yy+4900+(mm-14)/12)/100)/4 } // ParDS returns date as "YYYYMMDD" string. Harbour: hb_pards(n) func (c *HBContext) ParDS(n int) string { v := c.param(n) if v.IsDate() { y, m, d := julianToYMD(v.AsJulian()) return fmt.Sprintf("%04d%02d%02d", y, m, d) } return " " } // ParDL returns date as Julian day number. Harbour: hb_pardl(n) func (c *HBContext) ParDL(n int) int64 { v := c.param(n) if v.IsDate() { return v.AsJulian() } return 0 } // ParDate returns date as Go time.Time (Five extension). func (c *HBContext) ParDate(n int) time.Time { v := c.param(n) if v.IsDate() { y, m, d := julianToYMD(v.AsJulian()) return time.Date(y, time.Month(m), d, 0, 0, 0, 0, time.Local) } return time.Time{} } // --------------------------------------------------------------------------- // Array parameters // --------------------------------------------------------------------------- // ParArray returns array items. Five extension. func (c *HBContext) ParArray(n int) []Value { v := c.param(n) if v.IsArray() { return v.AsArray().Items } return nil } // ParArrayLen returns array length. Harbour: hb_parinfa(n, 0) func (c *HBContext) ParArrayLen(n int) int { v := c.param(n) if v.IsArray() { return len(v.AsArray().Items) } return 0 } // ParHash returns hash. Five extension. func (c *HBContext) ParHash(n int) *HbHash { v := c.param(n) if v.IsHash() { return v.AsHash() } return nil } // --------------------------------------------------------------------------- // Return values — Harbour: hb_ret* // --------------------------------------------------------------------------- // Ret returns NIL. Harbour: hb_ret() func (c *HBContext) Ret() { c.T.PushNil() c.T.RetValue() } // RetNil returns NIL explicitly. func (c *HBContext) RetNil() { c.T.PushNil() c.T.RetValue() } // RetC returns string. Harbour: hb_retc(s) func (c *HBContext) RetC(s string) { c.T.PushString(s) c.T.RetValue() } // RetCLen returns string of specific length. Harbour: hb_retclen(s, n) func (c *HBContext) RetCLen(s string, n int) { if n < len(s) { s = s[:n] } c.T.PushString(s) c.T.RetValue() } // RetNI returns integer. Harbour: hb_retni(n) func (c *HBContext) RetNI(n int) { c.T.PushInt(n) c.T.RetValue() } // RetNL returns long. Harbour: hb_retnl(n) func (c *HBContext) RetNL(n int64) { c.T.PushLong(n) c.T.RetValue() } // RetND returns double. Harbour: hb_retnd(d) func (c *HBContext) RetND(d float64) { c.T.PushDouble(d, 0, 0) c.T.RetValue() } // RetNDLen returns double with width/decimals. Harbour: hb_retndlen(d, w, dec) func (c *HBContext) RetNDLen(d float64, width, dec int) { c.T.PushDouble(d, uint16(width), uint16(dec)) c.T.RetValue() } // RetL returns logical. Harbour: hb_retl(b) func (c *HBContext) RetL(b bool) { c.T.PushBool(b) c.T.RetValue() } // RetDS returns date from "YYYYMMDD". Harbour: hb_retds(s) func (c *HBContext) RetDS(s string) { if len(s) >= 8 { y, m, d := 0, 0, 0 fmt.Sscanf(s, "%04d%02d%02d", &y, &m, &d) c.T.PushValue(MakeDate(ymdToJulian(y, m, d))) } else { c.T.PushValue(MakeDate(0)) } c.T.RetValue() } // RetDL returns date from Julian. Harbour: hb_retdl(n) func (c *HBContext) RetDL(julian int64) { c.T.PushValue(MakeDate(julian)) c.T.RetValue() } // RetD returns date from y/m/d. Harbour: hb_retd(y, m, d) func (c *HBContext) RetD(y, m, d int) { c.T.PushValue(MakeDate(ymdToJulian(y, m, d))) c.T.RetValue() } // RetValue returns raw Value. Five extension. func (c *HBContext) RetVal(v Value) { c.T.PushValue(v) c.T.RetValue() } // RetA returns empty array of size n. Harbour: hb_reta(n) func (c *HBContext) RetA(size int) { c.T.PushValue(MakeArray(size)) c.T.RetValue() } // RetArray returns populated array. Five extension. func (c *HBContext) RetArray(items []Value) { c.T.PushValue(MakeArrayFrom(items)) c.T.RetValue() } // RetHash returns hash. Five extension. func (c *HBContext) RetHash(h *HbHash) { c.T.PushValue(MakeHashFrom(h)) c.T.RetValue() } // --------------------------------------------------------------------------- // By-reference storage — Harbour: hb_stor* // --------------------------------------------------------------------------- // StorNil stores NIL into by-ref param. Harbour: hb_stor(n) func (c *HBContext) StorNil(n int) { if n >= 1 && n <= c.PCount() { c.T.SetLocal(n, MakeNil()) } } // StorC stores string into by-ref param. Harbour: hb_storc(s, n) func (c *HBContext) StorC(s string, n int) { if n >= 1 && n <= c.PCount() { c.T.SetLocal(n, MakeString(s)) } } // StorNI stores int into by-ref param. Harbour: hb_storni(v, n) func (c *HBContext) StorNI(v int, n int) { if n >= 1 && n <= c.PCount() { c.T.SetLocal(n, MakeInt(v)) } } // StorNL stores int64 into by-ref param. Harbour: hb_stornl(v, n) func (c *HBContext) StorNL(v int64, n int) { if n >= 1 && n <= c.PCount() { c.T.SetLocal(n, MakeLong(v)) } } // StorND stores float64 into by-ref param. Harbour: hb_stornd(v, n) func (c *HBContext) StorND(v float64, n int) { if n >= 1 && n <= c.PCount() { c.T.SetLocal(n, MakeDouble(v, 0, 0)) } } // StorL stores bool into by-ref param. Harbour: hb_storl(v, n) func (c *HBContext) StorL(v bool, n int) { if n >= 1 && n <= c.PCount() { c.T.SetLocal(n, MakeBool(v)) } } // StorDS stores date string into by-ref param. Harbour: hb_stords(s, n) func (c *HBContext) StorDS(s string, n int) { if n >= 1 && n <= c.PCount() && len(s) >= 8 { y, m, d := 0, 0, 0 fmt.Sscanf(s, "%04d%02d%02d", &y, &m, &d) c.T.SetLocal(n, MakeDate(ymdToJulian(y, m, d))) } } // StorDL stores Julian date into by-ref param. Harbour: hb_stordl(v, n) func (c *HBContext) StorDL(v int64, n int) { if n >= 1 && n <= c.PCount() { c.T.SetLocal(n, MakeDate(v)) } } // --------------------------------------------------------------------------- // Array manipulation — Harbour: hb_array* // --------------------------------------------------------------------------- // ArrayNew creates empty array. Harbour: hb_arrayNew() func (c *HBContext) ArrayNew(size int) Value { return MakeArray(size) } // ArrayLen returns array length. Harbour: hb_arrayLen() func (c *HBContext) ArrayLen(v Value) int { if v.IsArray() { return len(v.AsArray().Items) } return 0 } // ArrayGet gets element at 1-based index. Harbour: hb_arrayGet() func (c *HBContext) ArrayGet(v Value, index int) Value { if v.IsArray() { items := v.AsArray().Items if index >= 1 && index <= len(items) { return items[index-1] } } return MakeNil() } // ArrayGetC gets string at index. Harbour: hb_arrayGetC() func (c *HBContext) ArrayGetC(v Value, index int) string { return c.ArrayGet(v, index).AsString() } // ArrayGetNI gets int at index. Harbour: hb_arrayGetNI() func (c *HBContext) ArrayGetNI(v Value, index int) int { return c.ArrayGet(v, index).AsInt() } // ArrayGetND gets double at index. Harbour: hb_arrayGetND() func (c *HBContext) ArrayGetND(v Value, index int) float64 { return c.ArrayGet(v, index).AsNumDouble() } // ArrayGetL gets bool at index. Harbour: hb_arrayGetL() func (c *HBContext) ArrayGetL(v Value, index int) bool { return c.ArrayGet(v, index).AsBool() } // ArraySet sets element at 1-based index. Harbour: hb_arraySet() func (c *HBContext) ArraySet(v Value, index int, item Value) { if v.IsArray() { items := v.AsArray().Items if index >= 1 && index <= len(items) { items[index-1] = item } } } // ArraySetC sets string at index. Harbour: hb_arraySetC() func (c *HBContext) ArraySetC(v Value, index int, s string) { c.ArraySet(v, index, MakeString(s)) } // ArraySetNI sets int at index. Harbour: hb_arraySetNI() func (c *HBContext) ArraySetNI(v Value, index int, n int) { c.ArraySet(v, index, MakeInt(n)) } // ArraySetND sets double at index. Harbour: hb_arraySetND() func (c *HBContext) ArraySetND(v Value, index int, d float64) { c.ArraySet(v, index, MakeDouble(d, 0, 0)) } // ArraySetL sets bool at index. Harbour: hb_arraySetL() func (c *HBContext) ArraySetL(v Value, index int, b bool) { c.ArraySet(v, index, MakeBool(b)) } // ArrayAdd appends to array. Harbour: hb_arrayAdd() func (c *HBContext) ArrayAdd(v Value, item Value) { if v.IsArray() { arr := v.AsArray() arr.Items = append(arr.Items, item) } } // --------------------------------------------------------------------------- // Hash manipulation — Harbour: hb_hash* // --------------------------------------------------------------------------- // HashNew creates empty hash. func (c *HBContext) HashNew() Value { return MakeHash() } // HashLen returns hash size. func (c *HBContext) HashLen(v Value) int { if v.IsHash() { return len(v.AsHash().Keys) } return 0 } // HashAdd adds key-value pair. Harbour: hb_hashAdd() func (c *HBContext) HashAdd(v Value, key, val Value) { if v.IsHash() { v.AsHash().Set(key, val) } } // HashGetC gets value by string key. Five extension. // Hits the Index directly with the "S"+key serialization so we skip // allocating a Value wrapper for the lookup. func (c *HBContext) HashGetC(v Value, key string) Value { if v.IsHash() { h := v.AsHash() h.ensureIndex() if i, ok := h.Index["S"+key]; ok { return h.Values[i] } } return MakeNil() } // --------------------------------------------------------------------------- // Error handling — Harbour: hb_errRT_BASE // --------------------------------------------------------------------------- // ErrRT_BASE raises a BASE runtime error. func (c *HBContext) ErrRT_BASE(subCode int, description, operation string) { panic(fmt.Sprintf("BASE/%04d: %s: %s", subCode, description, operation)) } // ErrRT_BASE_SubstR raises a substitution error. func (c *HBContext) ErrRT_BASE_SubstR(subCode int, description, operation string) { c.ErrRT_BASE(subCode, description, operation) } // --------------------------------------------------------------------------- // ParInfo — Harbour: hb_parinfo(n) // --------------------------------------------------------------------------- const ( HB_IT_NIL = 0x00001 HB_IT_INTEGER = 0x00002 HB_IT_LONG = 0x00008 HB_IT_DOUBLE = 0x00010 HB_IT_DATE = 0x00020 HB_IT_TIMESTAMP = 0x00040 HB_IT_LOGICAL = 0x00080 HB_IT_SYMBOL = 0x00100 HB_IT_POINTER = 0x00200 HB_IT_STRING = 0x00400 HB_IT_MEMO = 0x00800 HB_IT_BLOCK = 0x01000 HB_IT_BYREF = 0x02000 HB_IT_ARRAY = 0x04000 HB_IT_HASH = 0x08000 HB_IT_OBJECT = 0x10000 HB_IT_NUMERIC = HB_IT_INTEGER | HB_IT_LONG | HB_IT_DOUBLE ) // ParInfo returns type flags for parameter n. Harbour: hb_parinfo(n) func (c *HBContext) ParInfo(n int) int { v := c.param(n) switch { case v.IsNil(): return HB_IT_NIL case v.IsString(): return HB_IT_STRING case v.IsLogical(): return HB_IT_LOGICAL case v.IsDate(): return HB_IT_DATE case v.IsTimestamp(): return HB_IT_TIMESTAMP case v.IsArray(): if v.IsObject() { return HB_IT_OBJECT } return HB_IT_ARRAY case v.IsHash(): return HB_IT_HASH case v.IsBlock(): return HB_IT_BLOCK case v.IsPointer(): return HB_IT_POINTER case v.IsNumeric(): return HB_IT_NUMERIC default: return HB_IT_NIL } }