Major changes since last commit: - FiveSql2 SQL:1999 engine (10,458 LOC) — 43/43 ALL PASS - 21 compiler/runtime bugs fixed (short-circuit AND/OR, FOR LOOP, etc.) - @byref pass-by-reference via RefCell pattern - Mutable closure capture (EnsureLocalRef + RefCell sharing) - RTL: 400 → 479 functions (+79: file, string, datetime, hash, UTF-8) - DateTime/Timestamp fully working (hb_DateTime, hb_Hour/Min/Sec, display) - Reserved word guard (39 keywords blocked from function calls) - AEval arg order fix (element before index) - Closure capture redecl fix (unique _cap_ names per block) - Hash/string indexing in ArrayPush/ArrayPop - Harbour compat test suite: 51/51 - 4 docs: Porting Report, Implementation Plan, Optimization Plan, Commercialization Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
271 lines
6.2 KiB
Go
271 lines
6.2 KiB
Go
// 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
|
|
}
|
|
}
|