// Copyright (c) 2026 Charles KWON OhJun (charleskwonohjun@gmail.com) // All rights reserved. // gobridge.go — Bridge between Harbour Values and native Go objects. // // Allows PRG code to hold and manipulate Go objects (sql.DB, http.Client, etc.) // using Harbour's object syntax: obj:Method(args) // // Architecture: // PRG: db := sql.Open("sqlite", ":memory:") → Value wrapping *sql.DB // PRG: db:Exec("CREATE TABLE ...") → reflect.Call on *sql.DB.Exec // PRG: db:Close() → reflect.Call on *sql.DB.Close // // Type coercion: Value ↔ Go types automatic conversion // string ↔ Value.AsString() // int ↔ Value.AsInt() // float64 ↔ Value.AsNumDouble() // bool ↔ Value.AsBool() // error ↔ Value (string of error message, or NIL) // nil ↔ Value.IsNil() package hbrt import ( "fmt" "reflect" ) // --------------------------------------------------------------------------- // GoValue — wraps any Go object inside a Harbour Value // --------------------------------------------------------------------------- // WrapGo wraps any Go value (pointer, interface, etc.) as a Harbour Value. // The Go object is stored in Value.ptr as interface{}. func WrapGo(obj interface{}) Value { if obj == nil { return MakeNil() } return MakePointer(obj) } // WrapGoError wraps a Go error as a Harbour Value. // nil error → Harbour NIL, non-nil → Harbour string. func WrapGoError(err error) Value { if err == nil { return MakeNil() } return MakeString(err.Error()) } // UnwrapGo extracts the Go object from a Harbour Value. func UnwrapGo(v Value) interface{} { if v.IsPointer() { return v.AsPointer() } return nil } // --------------------------------------------------------------------------- // GoCall — call a Go method on a wrapped object using reflection // --------------------------------------------------------------------------- // GoCall calls method `name` on Go object wrapped in `receiver` with args. // Returns array of Harbour Values (one per Go return value). // // PRG: result := obj:Method(arg1, arg2) // Go: GoCall(objValue, "Method", arg1Value, arg2Value) func GoCall(receiver Value, method string, args ...Value) []Value { obj := UnwrapGo(receiver) if obj == nil { return []Value{MakeNil(), MakeString("nil receiver")} } rv := reflect.ValueOf(obj) m := rv.MethodByName(method) if !m.IsValid() { // Try pointer receiver if rv.Kind() != reflect.Ptr { pv := reflect.New(rv.Type()) pv.Elem().Set(rv) m = pv.MethodByName(method) } if !m.IsValid() { return []Value{MakeNil(), MakeString("method not found: " + method)} } } mt := m.Type() // Convert Harbour args → Go args goArgs := make([]reflect.Value, len(args)) for i, arg := range args { if i < mt.NumIn() { goArgs[i] = valueToReflect(arg, mt.In(i)) } else if mt.IsVariadic() && i >= mt.NumIn()-1 { // Variadic: convert to slice element type elemType := mt.In(mt.NumIn() - 1).Elem() goArgs[i] = valueToReflect(arg, elemType) } else { goArgs[i] = reflect.ValueOf(valueToInterface(arg)) } } // Call — m.Call handles both variadic and non-variadic correctly results := m.Call(goArgs) // Convert Go results → Harbour Values hbResults := make([]Value, len(results)) for i, r := range results { hbResults[i] = reflectToValue(r) } return hbResults } // GoCallFunc calls a package-level Go function. // fn must be a reflect.Value of the function. func GoCallFunc(fn interface{}, args ...Value) []Value { rv := reflect.ValueOf(fn) if rv.Kind() != reflect.Func { return []Value{MakeNil(), MakeString("not a function")} } ft := rv.Type() goArgs := make([]reflect.Value, len(args)) isVariadic := ft.IsVariadic() fixedCount := ft.NumIn() if isVariadic { fixedCount-- // last param is the variadic slice } for i, arg := range args { if i < fixedCount { goArgs[i] = valueToReflect(arg, ft.In(i)) } else if isVariadic { // Variadic: convert to the slice's element type elemType := ft.In(ft.NumIn() - 1).Elem() goArgs[i] = valueToReflect(arg, elemType) } else { goArgs[i] = reflect.ValueOf(valueToInterface(arg)) } } results := rv.Call(goArgs) hbResults := make([]Value, len(results)) for i, r := range results { hbResults[i] = reflectToValue(r) } return hbResults } // --------------------------------------------------------------------------- // GoGet / GoSet — field access on Go structs // --------------------------------------------------------------------------- // GoGet gets a field value from a Go struct. func GoGet(receiver Value, field string) Value { obj := UnwrapGo(receiver) if obj == nil { return MakeNil() } rv := reflect.ValueOf(obj) if rv.Kind() == reflect.Ptr { rv = rv.Elem() } if rv.Kind() != reflect.Struct { return MakeNil() } f := rv.FieldByName(field) if !f.IsValid() { return MakeNil() } return reflectToValue(f) } // GoSet sets a field value on a Go struct. func GoSet(receiver Value, field string, val Value) { obj := UnwrapGo(receiver) if obj == nil { return } rv := reflect.ValueOf(obj) if rv.Kind() == reflect.Ptr { rv = rv.Elem() } if rv.Kind() != reflect.Struct { return } f := rv.FieldByName(field) if !f.IsValid() || !f.CanSet() { return } f.Set(valueToReflect(val, f.Type())) } // --------------------------------------------------------------------------- // Type coercion: Value → reflect.Value // --------------------------------------------------------------------------- func valueToReflect(v Value, targetType reflect.Type) reflect.Value { // Handle interface{} target if targetType.Kind() == reflect.Interface { return reflect.ValueOf(valueToInterface(v)) } switch targetType.Kind() { case reflect.String: return reflect.ValueOf(v.AsString()) case reflect.Int: return reflect.ValueOf(int(valToInt64(v))) case reflect.Int8: return reflect.ValueOf(int8(valToInt64(v))) case reflect.Int16: return reflect.ValueOf(int16(valToInt64(v))) case reflect.Int32: return reflect.ValueOf(int32(valToInt64(v))) case reflect.Int64: return reflect.ValueOf(valToInt64(v)) case reflect.Uint: return reflect.ValueOf(uint(valToInt64(v))) case reflect.Uint8: return reflect.ValueOf(uint8(valToInt64(v))) case reflect.Uint16: return reflect.ValueOf(uint16(valToInt64(v))) case reflect.Uint32: return reflect.ValueOf(uint32(valToInt64(v))) case reflect.Uint64: return reflect.ValueOf(uint64(valToInt64(v))) case reflect.Float32: return reflect.ValueOf(float32(v.AsNumDouble())) case reflect.Float64: return reflect.ValueOf(v.AsNumDouble()) case reflect.Bool: return reflect.ValueOf(v.AsBool()) case reflect.Ptr: // Unwrap Go pointer from Value if v.IsPointer() { obj := v.AsPointer() if obj != nil { rv := reflect.ValueOf(obj) if rv.Type().AssignableTo(targetType) { return rv } } } return reflect.Zero(targetType) case reflect.Slice: if targetType.Elem().Kind() == reflect.Uint8 && v.IsString() { // []byte from string return reflect.ValueOf([]byte(v.AsString())) } if v.IsArray() { return arrayToSlice(v, targetType) } return reflect.Zero(targetType) default: // Try interface{} unwrap if v.IsPointer() { obj := v.AsPointer() if obj != nil { rv := reflect.ValueOf(obj) if rv.Type().AssignableTo(targetType) { return rv } } } return reflect.Zero(targetType) } } // valToInt64 safely converts any numeric Value to int64. func valToInt64(v Value) int64 { if v.IsInt() || v.IsLong() { return v.AsLong() } // Double → truncate return int64(v.AsNumDouble()) } func valueToInterface(v Value) interface{} { switch { case v.IsNil(): return nil case v.IsString(): return v.AsString() case v.IsLogical(): return v.AsBool() case v.IsDate(): return v.AsLong() // Julian case v.IsNumeric(): if v.IsInt() { return v.AsInt() } return v.AsNumDouble() case v.IsPointer(): return v.AsPointer() case v.IsArray(): arr := v.AsArray() result := make([]interface{}, len(arr.Items)) for i, item := range arr.Items { result[i] = valueToInterface(item) } return result default: return nil } } func arrayToSlice(v Value, sliceType reflect.Type) reflect.Value { arr := v.AsArray() if arr == nil { return reflect.Zero(sliceType) } elemType := sliceType.Elem() slice := reflect.MakeSlice(sliceType, len(arr.Items), len(arr.Items)) for i, item := range arr.Items { slice.Index(i).Set(valueToReflect(item, elemType)) } return slice } // --------------------------------------------------------------------------- // Type coercion: reflect.Value → Value // --------------------------------------------------------------------------- func reflectToValue(rv reflect.Value) Value { if !rv.IsValid() { return MakeNil() } // Check error interface BEFORE unwrapping errorType := reflect.TypeOf((*error)(nil)).Elem() if rv.Type().Implements(errorType) { if rv.IsNil() { return MakeNil() } return MakeString(rv.Interface().(error).Error()) } // Handle interface — unwrap if rv.Kind() == reflect.Interface || rv.Kind() == reflect.Ptr { if rv.IsNil() { return MakeNil() } if rv.Kind() == reflect.Interface { rv = rv.Elem() } } switch rv.Kind() { case reflect.String: return MakeString(rv.String()) case reflect.Bool: return MakeBool(rv.Bool()) case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: n := rv.Int() if n >= -2147483648 && n <= 2147483647 { return MakeInt(int(n)) } return MakeLong(n) case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: return MakeLong(int64(rv.Uint())) case reflect.Float32, reflect.Float64: return MakeDouble(rv.Float(), 0, 0) case reflect.Slice: if rv.Type().Elem().Kind() == reflect.Uint8 { // []byte → string return MakeString(string(rv.Bytes())) } items := make([]Value, rv.Len()) for i := 0; i < rv.Len(); i++ { items[i] = reflectToValue(rv.Index(i)) } return MakeArrayFrom(items) case reflect.Map: h := &HbHash{} iter := rv.MapRange() for iter.Next() { // Go maps guarantee unique keys; Append skips the lookup. h.Append(reflectToValue(iter.Key()), reflectToValue(iter.Value())) } return MakeHashFrom(h) case reflect.Ptr, reflect.Struct, reflect.Func, reflect.Chan: // Wrap as Go object pointer return WrapGo(rv.Interface()) default: // Wrap anything else as Go object if rv.CanInterface() { return WrapGo(rv.Interface()) } return MakeNil() } } // --------------------------------------------------------------------------- // GoMultiReturn — unpack Go multi-return into Harbour locals // --------------------------------------------------------------------------- // GoMultiAssign assigns multiple Go return values to Harbour locals. // PRG: a, b, c := GoFunc(...) // Generated: results := GoCallFunc(fn, args...); GoMultiAssign(t, results, 1, 2, 3) func GoMultiAssign(t *Thread, results []Value, localIndices ...int) { for i, idx := range localIndices { if i < len(results) { t.SetLocal(idx, results[i]) } else { t.SetLocal(idx, MakeNil()) } } } // --------------------------------------------------------------------------- // IsGoObject checks if a Value contains a wrapped Go object // --------------------------------------------------------------------------- func IsGoObject(v Value) bool { if !v.IsPointer() { return false } obj := v.AsPointer() if obj == nil { return false } rv := reflect.ValueOf(obj) k := rv.Kind() return k == reflect.Ptr || k == reflect.Struct || k == reflect.Interface || k == reflect.Map || k == reflect.Slice || k == reflect.Chan || k == reflect.Func } // --------------------------------------------------------------------------- // GoTypeName returns the Go type name of a wrapped object // --------------------------------------------------------------------------- func GoTypeName(v Value) string { if !v.IsPointer() { return "Value" } obj := v.AsPointer() if obj == nil { return "nil" } return fmt.Sprintf("%T", obj) }