// Copyright (c) 2026 Charles KWON OhJun (charleskwonohjun@gmail.com) // All rights reserved. // Array functions for the Five runtime library. // Implements Harbour-compatible array manipulation functions. // Reference: /mnt/d/harbour-core/src/vm/arrays.c package hbrtl import ( "five/hbrt" "sort" ) // AAdd appends an element to an array and returns the array. // Harbour: AAdd(aArray, xValue) → aArray func AAdd(t *hbrt.Thread) { t.Frame(2, 0) defer t.EndProc() arrVal := t.Local(1) val := t.Local(2) if !arrVal.IsArray() { panic("AAdd: argument is not an array") } arr := arrVal.AsArray() arr.Items = append(arr.Items, val) t.PushValue(arrVal) t.RetValue() } // ADel deletes an element from array at position, shifts remaining left. // Harbour: ADel(aArray, nPos) → aArray func ADel(t *hbrt.Thread) { t.Frame(2, 0) defer t.EndProc() arrVal := t.Local(1) pos := int(t.Local(2).AsNumInt()) arr := arrVal.AsArray() if pos >= 1 && pos <= len(arr.Items) { copy(arr.Items[pos-1:], arr.Items[pos:]) arr.Items[len(arr.Items)-1] = hbrt.MakeNil() } t.PushValue(arrVal) t.RetValue() } // AIns inserts NIL at position, shifts elements right. // Harbour: AIns(aArray, nPos) → aArray func AIns(t *hbrt.Thread) { t.Frame(2, 0) defer t.EndProc() arrVal := t.Local(1) pos := int(t.Local(2).AsNumInt()) arr := arrVal.AsArray() if pos >= 1 && pos <= len(arr.Items) { copy(arr.Items[pos:], arr.Items[pos-1:len(arr.Items)-1]) arr.Items[pos-1] = hbrt.MakeNil() } t.PushValue(arrVal) t.RetValue() } // ASize resizes an array. // Harbour: ASize(aArray, nLen) → aArray func ASize(t *hbrt.Thread) { t.Frame(2, 0) defer t.EndProc() arrVal := t.Local(1) newLen := int(t.Local(2).AsNumInt()) arr := arrVal.AsArray() if newLen < 0 { newLen = 0 } if newLen > len(arr.Items) { ext := make([]hbrt.Value, newLen-len(arr.Items)) arr.Items = append(arr.Items, ext...) } else { arr.Items = arr.Items[:newLen] } t.PushValue(arrVal) t.RetValue() } // AClone creates a shallow copy of an array. // Harbour: AClone(aArray) → aNewArray func AClone(t *hbrt.Thread) { t.Frame(1, 0) defer t.EndProc() arrVal := t.Local(1) if !arrVal.IsArray() { t.PushValue(arrVal) t.RetValue() return } src := arrVal.AsArray() items := make([]hbrt.Value, len(src.Items)) copy(items, src.Items) t.PushValue(hbrt.MakeArrayFrom(items)) t.RetValue() } // ACopy copies elements from one array to another. // Harbour: ACopy(aSource, aDest [, nStart [, nCount [, nTargetPos]]]) → aDest func ACopy(t *hbrt.Thread) { t.Frame(2, 0) // simplified: just source and dest defer t.EndProc() srcVal := t.Local(1) dstVal := t.Local(2) src := srcVal.AsArray() dst := dstVal.AsArray() n := len(src.Items) if n > len(dst.Items) { n = len(dst.Items) } copy(dst.Items[:n], src.Items[:n]) t.PushValue(dstVal) t.RetValue() } // AFill fills an array with a value. // Harbour: AFill(aArray, xValue [, nStart [, nCount]]) → aArray func AFill(t *hbrt.Thread) { t.Frame(2, 0) // simplified: array and value defer t.EndProc() arrVal := t.Local(1) val := t.Local(2) arr := arrVal.AsArray() for i := range arr.Items { arr.Items[i] = val } t.PushValue(arrVal) t.RetValue() } // ASort sorts an array using an optional comparison block. // Harbour: ASort(aArray [, nStart [, nCount [, bBlock]]]) → aArray func ASort(t *hbrt.Thread) { nParams := t.ParamCount() t.Frame(nParams, 0) defer t.EndProc() arrVal := t.Local(1) arr := arrVal.AsArray() if nParams >= 4 && t.Local(4).IsBlock() { // Sort with code block comparator blk := t.Local(4).AsBlock() sort.SliceStable(arr.Items, func(i, j int) bool { t.PushValue(arr.Items[i]) t.PushValue(arr.Items[j]) t.PendingParams2(2) blk.Fn(t) return t.GetRetValue().AsBool() }) } else { // Default sort: by value comparison sort.SliceStable(arr.Items, func(i, j int) bool { a, b := arr.Items[i], arr.Items[j] if a.IsString() && b.IsString() { return a.AsString() < b.AsString() } if a.IsNumeric() && b.IsNumeric() { return a.AsNumDouble() < b.AsNumDouble() } return false }) } t.PushValue(arrVal) t.RetValue() } // AEval evaluates a block for each element in array. // Harbour: AEval(aArray, bBlock [, nStart [, nCount]]) → aArray func AEval(t *hbrt.Thread) { nParams := t.ParamCount() t.Frame(nParams, 0) defer t.EndProc() arrVal := t.Local(1) arr := arrVal.AsArray() blkVal := t.Local(2) if !blkVal.IsBlock() { t.PushValue(arrVal) t.RetValue() return } blk := blkVal.AsBlock() for i, item := range arr.Items { // Harbour: AEval callback receives (element, index). // Stack order: index first, element on top — Frame picks top-N. t.PushValue(hbrt.MakeInt(i + 1)) // arg2: 1-based index t.PushValue(item) // arg1: element value t.PendingParams2(2) blk.Fn(t) } t.PushValue(arrVal) t.RetValue() } // AScan searches for a value in array, returns position (0 if not found). // Harbour: AScan(aArray, xValue|bBlock [, nStart [, nCount]]) → nPos func AScan(t *hbrt.Thread) { nParams := t.ParamCount() t.Frame(nParams, 0) defer t.EndProc() arrVal := t.Local(1) arr := arrVal.AsArray() search := t.Local(2) if search.IsBlock() { blk := search.AsBlock() for i, item := range arr.Items { t.PushValue(item) t.PendingParams2(1) blk.Fn(t) if t.GetRetValue().AsBool() { t.RetInt(int64(i + 1)) return } } } else { for i, item := range arr.Items { if valuesEqual(item, search) { t.RetInt(int64(i + 1)) return } } } t.RetInt(0) } // ATail returns the last element of an array. // Harbour: ATail(aArray) → xValue func ATail(t *hbrt.Thread) { t.Frame(1, 0) defer t.EndProc() arr := t.Local(1).AsArray() if arr != nil && len(arr.Items) > 0 { t.PushValue(arr.Items[len(arr.Items)-1]) } else { t.PushNil() } t.RetValue() } // valuesEqual compares two values for equality (simplified for AScan). func valuesEqual(a, b hbrt.Value) bool { if a.Type() != b.Type() { if a.IsNumeric() && b.IsNumeric() { return a.AsNumDouble() == b.AsNumDouble() } return false } switch { case a.IsNumInt(): return a.AsNumInt() == b.AsNumInt() case a.IsDouble(): return a.AsDouble() == b.AsDouble() case a.IsString(): return a.AsString() == b.AsString() case a.IsLogical(): return a.AsBool() == b.AsBool() default: return false } }