Files
five/hbrt/hbfunc.go
CharlesKWON cde86730b8 fix(compiler,hbrt,hbrdd,cli): pre-1.0 audit — 13 critical fixes
Senior-engineer / QA audit landed 13 silent-miscompile and data-
integrity fixes spanning the whole compiler+runtime+storage stack.
Each fix is paired with either an integration test in the suite or
a focused regression check; all 6 release gates stay green:
go test ./..., FiveSql2 43/43, Harbour compat 56/56, std.ch 17/17,
FRB 7/7, examples 65/71.

Compiler
--------

* genpc IF/ELSEIF jumpEnd2 patching (compiler/genpc/genpc.go).
  Per-ELSEIF branch terminators were stashed into `_ = jumpEnd2`
  and never patched — the relative offset stayed 0 and the runtime
  walked the next ELSEIF's PcOpJumpFalse opcode as if it were
  jump-offset data. Bytecode-level corruption in pcode mode. Now
  collected into a slice and patched at end-of-IF. Verified via
  Grade(95..50) cases 11a-e added to tests/frb/test_frb_pcode_sweep.

* countLocalsInStmts / scanBodyLocals missing bodies
  (compiler/gengo/gen_util.go, compiler/gengo/gengo.go). Frame-size
  counter skipped WATCH/TIMEOUT/PARALLEL FOR bodies, so a LOCAL
  declared inside one of those constructs got a slot index past
  the runtime's allocated count — silent NIL reads or out-of-range
  stomps.

* emitMethodDeclStandalone nested LOCAL (compiler/gengo/gen_class.go).
  Same bug class but on the *method* side. Pre-fix repro:

      METHOD Stomp(n) CLASS T
         LOCAL a := 1, b := 2
         IF n > 0
            LOCAL c := 30, d := 40, e := 50, f := 60
            Inner( n )
            IF c != 30 .OR. d != 40 .OR. e != 50 .OR. f != 60 ...

  printed `c, d, e, f = 5, NIL, NIL, NIL` because Inner's frame
  collided with Stomp's underallocated slot range. Now counts
  body-nested LOCALs into the frame and pre-allocates indices via
  scanBodyLocals.

* genpc unsupported-AST diagnostic surface (compiler/genpc/genpc.go,
  hbrt/pcode.go, cmd/five/main.go, hbrtl/frb.go). The `default`
  cases in emitStmt / emitExpr silently emitted PushNil / no-op
  for nodes the pcode generator doesn't implement (ClassDecl,
  MethodDecl, xBase commands, concurrency primitives, …). Added
  `PcodeModule.Warnings []string` populated by noteUnsupported,
  surfaced on stderr from the build pipeline. Users now see
  "pcode: AST node not supported in --pcode/FRB-pcode mode: stmt
  *ast.GoBlockStmt" instead of getting a silently broken module.

Runtime
-------

* class.go Send/tryBinaryOp t.self defer-restore (hbrt/class.go).
  Restoration was a plain `t.self = oldSelf` after `fn(t)`. Any
  panic in the method body skipped the line, so the next BEGIN
  SEQUENCE / RECOVER handler ran with the THROWING object's Self
  — `::field` resolved against the wrong receiver. Wrapped both
  restore sites in `defer func() { t.self = oldSelf }()`.
  Verified: pre-fix RECOVER saw "THROWER", post-fix "OUTER".

* hbfunc.go HB_FUNC parameter Frame() (hbrt/hbfunc.go). The
  RegisterDynamicFunc wrapper called `fn(ctx)` without ever
  calling Frame, so `ctx.ParC(1)` / `ctx.Local(n)` read through
  `t.curFrame.localBase + n - 1` against the *caller's* frame.
  Every #pragma BEGINDUMP HB_FUNC taking parameters silently
  returned "" / 0 / "" for them — masked by ParNIDef-style
  defaults. Wrapper now does `t.Frame(t.pendingParams, 0); defer
  t.EndProc()` before dispatch.

* pcode codeblock closure capture (hbrt/pcinterp.go, hbrt/pcode.go,
  hbrt/thread.go, compiler/genpc/genpc.go). PcOpPushBlock recorded
  `nDetached` but never copied enclosing locals; free vars in the
  block body fell through to memvar lookup → NIL. Wired full
  capture pipeline:
  - New opcodes PcOpPushDetached (0x59) / PcOpPopDetached (0x5A).
  - PushBlock now reads per-slot source-local indices and
    snapshots into bb.Detached at construction time.
  - New detachedMap in genpc auto-promotes any free var that
    resolves to an enclosing-frame local into a capture slot.
  - emitAssignAsExpr leaves the assigned value on the eval stack
    so SeqExpr items like `{|v| acc += v, acc }` work.
  - Thread tracks curBlock with paired Set/restore in the block's
    Fn wrapper for nested-block evaluation.
  Mutating capture (acc += v across successive Evals) now works.

* vm.NewThread statics + waFactory propagation (hbrt/vm.go).
  GoLaunch / GoLaunchBlock call NewThread directly. Previously
  the statics map and WA factory were applied only in Run(), so
  goroutine-spawned PRG code panicked on STATIC access ("static
  index out of range") and crashed dereferencing nil WA on any
  DB call. Both now happen inside NewThread under the same lock
  as TID assignment.

Data layer
----------

* dbf concurrent Append lock (hbrdd/dbf/dbf.go,
  hbrdd/dbf/locks_posix.go, hbrdd/dbf/locks_windows.go). Append
  bumped a local recCount with no file-system serialization. Two
  shared-mode processes both wrote at the same RecordOffset; one
  record silently overwrote the other. Added an append-intent
  byte-range lock at offset 0x7FFFFFFE + bounded retry, on-disk
  header refresh inside the locked region, and immediate header
  write so peers refresh past our slot.

* indexer negative numeric key encoding (hbrdd/dbf/indexer.go +
  new hbrdd/dbf/encode_numeric_test.go). `%20.10f` formats `-100`
  as `"     -100.0000000000"` and `99` as `"        99.0000000000"`.
  ASCII ' ' (0x20) < '-' (0x2D), so `99` lex-compared LESS than
  `-100` — every NTX/CDX index over a column that ever held a
  negative number returned wrong rows for SEEK / range scans.
  Replaced with a 1-byte sign prefix + 21-byte zero-padded
  magnitude (negatives use digit-complement) so byte order
  matches numeric order across signs and magnitudes. Format
  change: existing indexes built with the old encoding must be
  REINDEXed. Three unit tests pin the order.

* dbf Append index maintenance hooks (hbrdd/dbf/dbf.go,
  hbrdd/dbf/indexer.go). Append never inserted into open NTX/CDX
  indexes — the audit's canonical scenario `SET INDEX TO …;
  APPEND BLANK; REPLACE …; dbSeek …` silently missed the new
  record. Added optional IndexWriter interface, queue the new
  recNo in pendingIdxInserts, drain after flushRecord by calling
  InsertKey on every open writer-supporting engine. NTX
  participates (its existing rebuild-on-insert is correct);
  CDX online maintenance is deferred to a follow-up — those
  indexes still need REINDEX. Verified: post-fix SEEK("Charlie")
  after APPEND BLANK + REPLACE finds the new record.

* dbf PACK crash-safety (hbrdd/dbf/dbf.go). The old in-place
  rewrite read record N, overwrote slot M<N, then truncated.
  Power loss after partial loop left a file with overwritten
  prefix and no original copies of the records already advanced
  past — silent data loss. Rewrote to:
    1) drop mmap, build `<file>.pack.tmp` with all surviving
       records,
    2) Sync(),
    3) close original handle + os.Rename(tmp, orig) (atomic on
       same FS),
    4) reopen + re-mmap.
  TestComp_Pack passes; readers always see either the pre-PACK
  or post-PACK contents, never a half-state.

* mem RDD torn reads (hbrdd/mem/memrdd.go). The comment claimed
  in-place PutValue was safe because hbrt.Value "fits in a
  single machine word + pointer". hbrt.Value is 24 bytes (3
  words) — a concurrent reader could observe new type tag with
  stale scalar/ptr and type-confuse on the next AsXxx() call.
  Switched mu to sync.RWMutex; GetValue takes RLock,
  Append/PutValue/Delete/Recall take Lock. `go test -race
  ./hbrdd/mem/` clean.

Files touched
-------------

  compiler/gengo/gen_class.go, gen_util.go, gengo.go
  compiler/genpc/genpc.go
  hbrt/class.go, hbfunc.go, pcinterp.go, pcode.go, thread.go, vm.go
  hbrdd/dbf/dbf.go, indexer.go, locks_posix.go, locks_windows.go
  hbrdd/dbf/encode_numeric_test.go  (new)
  hbrdd/mem/memrdd.go
  cmd/five/main.go
  hbrtl/frb.go
  tests/frb/test_frb_pcode_sweep.prg

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-13 05:29:56 +09:00

677 lines
18 KiB
Go

// 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
}
}