// Copyright (c) 2026 Charles KWON OhJun (charleskwonohjun@gmail.com) // All rights reserved. // Collection operations for the Five runtime. // Array generation, indexing, hash generation, and code block evaluation. // Harbour: HB_P_ARRAYGEN, HB_P_ARRAYPUSH, HB_P_ARRAYPOP, etc. package hbrt import "unsafe" // ArrayGen pops n items from stack and creates an array. // Harbour: HB_P_ARRAYGEN / hb_vmArrayGen func (t *Thread) ArrayGen(n int) { if n <= 0 { t.push(MakeArrayFrom(nil)) return } items := make([]Value, n) base := t.sp - n copy(items, t.stack[base:t.sp]) // Clear the consumed slots so the popped values can be GC'd. // The first slot gets overwritten by the push below; clear the // rest (base+1 … sp-1). for i := base + 1; i < t.sp; i++ { t.stack[i] = cachedNil } t.sp = base t.push(MakeArrayFrom(items)) } // HashGen pops n key-value pairs and creates a hash. // Stack: [key1] [val1] [key2] [val2] ... → Hash // // Duplicate keys follow Harbour hash-literal semantics: the last // assignment wins and no second slot is created. Lookup/Set invoked // inside the reverse-scan pop loop would be order-inverted, so we // first materialize all N pairs in stack order and then feed them // forward into the hash via Set. func (t *Thread) HashGen(n int) { keys := make([]Value, n) vals := make([]Value, n) for i := n - 1; i >= 0; i-- { vals[i] = t.pop() keys[i] = t.pop() } hh := &HbHash{} for i := 0; i < n; i++ { hh.Set(keys[i], vals[i]) } t.push(Value{ info: makeInfo(tHash, 0, 0), ptr: unsafe.Pointer(hh), }) } // ArrayPush pops index and array/hash, pushes array[index] or hash[key]. // Harbour: HB_P_ARRAYPUSH func (t *Thread) ArrayPush() { idx := t.pop() arr := t.pop() // Hash: h[key] → value if arr.IsHash() { hh := arr.AsHash() if i := hh.Lookup(idx); i >= 0 { t.push(hh.Values[i]) return } t.push(MakeNil()) return } if !arr.IsArray() { // String indexing: cString[n] → single char if arr.IsString() { s := arr.AsString() n := int(idx.AsNumInt()) if n >= 1 && n <= len(s) { t.push(MakeString(string(s[n-1]))) return } t.push(MakeString("")) return } panic(t.argError("[]", arr, idx)) } ha := arr.AsArray() n := int(idx.AsNumInt()) // Harbour: 1-based indexing if n < 1 || n > len(ha.Items) { panic(t.runtimeError("array index out of bounds")) } t.push(ha.Items[n-1]) } // ArrayPop pops value, index, array/hash and sets array[index]=value or hash[key]=value. // Harbour: HB_P_ARRAYPOP func (t *Thread) ArrayPop() { val := t.pop() idx := t.pop() arr := t.pop() // Hash: h[key] := value if arr.IsHash() { arr.AsHash().Set(idx, val) return } if !arr.IsArray() { panic(t.argError("[]=", arr, idx)) } ha := arr.AsArray() n := int(idx.AsNumInt()) if n < 1 || n > len(ha.Items) { panic(t.runtimeError("array index out of bounds")) } ha.Items[n-1] = val } // EvalBlock evaluates a code block on the stack with nArgs arguments. // Stack: [block] [arg1] ... [argN] → [result] // // Same pop-push-dance removal as call.Function(): shift the args down // by one to overwrite the block slot instead of popping and pushing // round-trip. The block Value itself is read before the shift. func (t *Thread) EvalBlock(nArgs int) { blockIdx := t.sp - nArgs - 1 if blockIdx < 0 { panic(t.runtimeError("stack underflow (EvalBlock)")) } blockVal := t.stack[blockIdx] if !blockVal.IsBlock() { panic(t.argError("Eval", blockVal)) } blk := blockVal.AsBlock() // Shift args down by one to overwrite the block slot. if nArgs > 0 { copy(t.stack[blockIdx:blockIdx+nArgs], t.stack[blockIdx+1:t.sp]) } t.stack[t.sp-1] = cachedNil t.sp-- t.pendingParams = nArgs blk.Fn(t) t.push(t.retVal) } // PushBlock creates a code block and pushes it onto the stack. func (t *Thread) PushBlock(fn func(*Thread), detachedLocals int) { t.push(MakeBlock(fn, detachedLocals)) } // PushSelf pushes the current Self object (for :: access in methods). func (t *Thread) PushSelf() { t.push(t.self) }