Files
five/hbrtl/array.go
Charles KWON OhJun 486e466592 feat: FiveSql2 43/43, @byref, mutable closure, RTL 479, DateTime fix
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>
2026-04-11 11:35:37 +09:00

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