feat(rtl): add hb_HGetDef and PValue / hb_PValue

Two standard Harbour functions that fivenode-style PRG code (bridge_*.prg
and downstream apps) calls frequently. Without them, every reference
emits an analyzer WARN and resolves to NIL at runtime.

* hb_HGetDef(hHash, xKey, xDefault) — hash lookup with fallback.
* PValue(nIndex[, xDefault]) — read the nth parameter of the calling
  PRG function. Mirrors the PCount pattern: needs the caller frame's
  paramCount and locals, exposed via new hbrt.Thread.CallerLocal helper
  that pairs with the existing CallerParamCount.

Registered under PVALUE and HB_PVALUE (Harbour accepts both forms).

Verified: hb_HGetDef / PValue / HB_PVALUE all return expected values for
present-key, missing-key-with-default, missing-key-no-default, and
out-of-range-param cases. Full regression: go test (18 packages) +
Compat 56/56 + std.ch 17/17 + FRB 7/7 + FiveSql2 43/43 all green.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-05-27 09:55:19 +09:00
parent 5daba8beec
commit ad6cc0bcee
4 changed files with 53 additions and 0 deletions

View File

@@ -670,6 +670,19 @@ func (t *Thread) CallerParamCount() int {
return 0 return 0
} }
// CallerLocal returns the n-th parameter of the calling PRG function
// (1-based). Returns NIL if no caller frame exists or n is out of range.
// Pairs with CallerParamCount for implementing the PValue() RTL.
func (t *Thread) CallerLocal(n int) Value {
if t.callSP >= 2 {
caller := &t.calls[t.callSP-2]
if n >= 1 && n <= caller.paramCount {
return caller.GetLocal(n, t.locals)
}
}
return MakeNil()
}
// PendingParams2 sets pending param count for direct block calls (AEval, ASort etc.) // PendingParams2 sets pending param count for direct block calls (AEval, ASort etc.)
func (t *Thread) PendingParams2(n int) { func (t *Thread) PendingParams2(n int) {
t.pendingParams = n t.pendingParams = n

View File

@@ -40,6 +40,22 @@ func HbHGet(t *hbrt.Thread) {
t.RetValue() t.RetValue()
} }
// HbHGetDef gets a value from a hash by key, returning a default if missing.
// Harbour: hb_HGetDef(hHash, xKey, xDefault) → xValue
func HbHGetDef(t *hbrt.Thread) {
t.Frame(3, 0)
defer t.EndProc()
if hh := t.Local(1).AsHash(); hh != nil {
if i := hh.Lookup(t.Local(2)); i >= 0 {
t.PushValue(hh.Values[i])
t.RetValue()
return
}
}
t.PushValue(t.Local(3))
t.RetValue()
}
// HbHSet sets a value in hash by key. // HbHSet sets a value in hash by key.
// Harbour: hb_HSet(hHash, xKey, xValue) → hHash // Harbour: hb_HSet(hHash, xKey, xValue) → hHash
func HbHSet(t *hbrt.Thread) { func HbHSet(t *hbrt.Thread) {

View File

@@ -278,6 +278,27 @@ func PCount(t *hbrt.Thread) {
t.RetInt(int64(t.CallerParamCount())) t.RetInt(int64(t.CallerParamCount()))
} }
// PValue returns the nth parameter of the calling PRG function.
// Harbour: PValue(nIndex[, xDefault]) → xValue
// Returns xDefault when n is out of range, or NIL if no default was given.
func PValue(t *hbrt.Thread) {
nParams := t.ParamCount()
t.Frame(nParams, 0)
defer t.EndProc()
n := int(t.Local(1).AsNumInt())
if n >= 1 && n <= t.CallerParamCount() {
t.PushValue(t.CallerLocal(n))
t.RetValue()
return
}
if nParams >= 2 {
t.PushValue(t.Local(2))
} else {
t.PushNil()
}
t.RetValue()
}
// Break moved to error.go — full implementation with BreakValue type. // Break moved to error.go — full implementation with BreakValue type.
// Array creates array of given size. // Array creates array of given size.

View File

@@ -69,6 +69,7 @@ func RegisterRTL(vm *hbrt.VM) {
// Hash // Hash
hbrt.Sym("HB_HASH", hbrt.FsPublic, HbHash), hbrt.Sym("HB_HASH", hbrt.FsPublic, HbHash),
hbrt.Sym("HB_HGET", hbrt.FsPublic, HbHGet), hbrt.Sym("HB_HGET", hbrt.FsPublic, HbHGet),
hbrt.Sym("HB_HGETDEF", hbrt.FsPublic, HbHGetDef),
hbrt.Sym("HB_HSET", hbrt.FsPublic, HbHSet), hbrt.Sym("HB_HSET", hbrt.FsPublic, HbHSet),
hbrt.Sym("HB_HDEL", hbrt.FsPublic, HbHDel), hbrt.Sym("HB_HDEL", hbrt.FsPublic, HbHDel),
hbrt.Sym("HB_HHASKEY", hbrt.FsPublic, HbHHasKey), hbrt.Sym("HB_HHASKEY", hbrt.FsPublic, HbHHasKey),
@@ -120,6 +121,8 @@ func RegisterRTL(vm *hbrt.VM) {
// Misc (new) // Misc (new)
hbrt.Sym("TYPE", hbrt.FsPublic, TypeFunc), hbrt.Sym("TYPE", hbrt.FsPublic, TypeFunc),
hbrt.Sym("PCOUNT", hbrt.FsPublic, PCount), hbrt.Sym("PCOUNT", hbrt.FsPublic, PCount),
hbrt.Sym("PVALUE", hbrt.FsPublic, PValue),
hbrt.Sym("HB_PVALUE", hbrt.FsPublic, PValue),
hbrt.Sym("BREAK", hbrt.FsPublic, Break), hbrt.Sym("BREAK", hbrt.FsPublic, Break),
hbrt.Sym("ARRAY", hbrt.FsPublic, ArrayFunc), hbrt.Sym("ARRAY", hbrt.FsPublic, ArrayFunc),
hbrt.Sym("FCOUNT", hbrt.FsPublic, FCount), hbrt.Sym("FCOUNT", hbrt.FsPublic, FCount),