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>
216 lines
4.7 KiB
Go
216 lines
4.7 KiB
Go
// Copyright (c) 2026 Charles KWON OhJun (charleskwonohjun@gmail.com)
|
|
// All rights reserved.
|
|
|
|
// Additional string functions: RAT, STRZERO, DESCEND, HB_VALTOSTR,
|
|
// MEMOREAD, MEMOWRIT, MEMOTRAN
|
|
|
|
package hbrtl
|
|
|
|
import (
|
|
"five/hbrt"
|
|
"fmt"
|
|
"os"
|
|
"strings"
|
|
)
|
|
|
|
// RAT(cSearch, cTarget [, nOccurrence]) → nPos
|
|
// Returns position of LAST occurrence of cSearch in cTarget.
|
|
func Rat(t *hbrt.Thread) {
|
|
nParams := t.ParamCount()
|
|
t.Frame(nParams, 0)
|
|
defer t.EndProc()
|
|
|
|
search := t.Local(1).AsString()
|
|
target := t.Local(2).AsString()
|
|
|
|
if search == "" || target == "" {
|
|
t.RetInt(0)
|
|
return
|
|
}
|
|
|
|
nOccurrence := 1
|
|
if nParams >= 3 && !t.Local(3).IsNil() {
|
|
nOccurrence = t.Local(3).AsInt()
|
|
if nOccurrence < 1 {
|
|
nOccurrence = 1
|
|
}
|
|
}
|
|
|
|
// Find nth occurrence from the right
|
|
pos := -1
|
|
from := len(target)
|
|
for i := 0; i < nOccurrence; i++ {
|
|
pos = strings.LastIndex(target[:from], search)
|
|
if pos < 0 {
|
|
t.RetInt(0)
|
|
return
|
|
}
|
|
from = pos
|
|
}
|
|
t.RetInt(int64(pos + 1)) // 1-based
|
|
}
|
|
|
|
// STRZERO(nValue, nLen [, nDec]) → cString
|
|
// Converts number to string padded with leading zeros.
|
|
func StrZero(t *hbrt.Thread) {
|
|
nParams := t.ParamCount()
|
|
t.Frame(nParams, 0)
|
|
defer t.EndProc()
|
|
|
|
val := t.Local(1)
|
|
nLen := 10
|
|
nDec := 0
|
|
|
|
if nParams >= 2 && !t.Local(2).IsNil() {
|
|
nLen = t.Local(2).AsInt()
|
|
}
|
|
if nParams >= 3 && !t.Local(3).IsNil() {
|
|
nDec = t.Local(3).AsInt()
|
|
}
|
|
|
|
var s string
|
|
if nDec > 0 {
|
|
fmtStr := fmt.Sprintf("%%0%d.%df", nLen, nDec)
|
|
s = fmt.Sprintf(fmtStr, val.AsNumDouble())
|
|
} else {
|
|
fmtStr := fmt.Sprintf("%%0%dd", nLen)
|
|
s = fmt.Sprintf(fmtStr, val.AsLong())
|
|
}
|
|
|
|
// Ensure exact length
|
|
if len(s) > nLen {
|
|
s = strings.Repeat("*", nLen)
|
|
}
|
|
t.RetString(s)
|
|
}
|
|
|
|
// DESCEND(xValue) → xDescended
|
|
// For strings: flips each byte (255-byte). For numbers: negates. For dates: max-date.
|
|
func Descend(t *hbrt.Thread) {
|
|
t.Frame(1, 0)
|
|
defer t.EndProc()
|
|
|
|
v := t.Local(1)
|
|
switch {
|
|
case v.IsString():
|
|
s := v.AsString()
|
|
buf := make([]byte, len(s))
|
|
for i := 0; i < len(s); i++ {
|
|
buf[i] = 255 - s[i]
|
|
}
|
|
t.RetString(string(buf))
|
|
case v.IsNumeric():
|
|
t.RetVal(hbrt.MakeNumInt(-v.AsLong()))
|
|
case v.IsDate():
|
|
// Max Julian (2^24) minus date
|
|
t.RetVal(hbrt.MakeNumInt(5373484 - v.AsJulian()))
|
|
default:
|
|
t.RetNil()
|
|
}
|
|
}
|
|
|
|
// HB_VALTOSTR(xValue) → cString
|
|
// Converts any value to its string representation.
|
|
func HbValToStr(t *hbrt.Thread) {
|
|
t.Frame(1, 0)
|
|
defer t.EndProc()
|
|
t.RetString(valueToDisplay(t.Local(1)))
|
|
}
|
|
|
|
// HB_CSTR(xVal) → cString — converts any value to string representation
|
|
func HbCStr(t *hbrt.Thread) {
|
|
t.Frame(1, 0)
|
|
defer t.EndProc()
|
|
t.RetString(valueToDisplay(t.Local(1)))
|
|
}
|
|
|
|
// HB_NTOS(nVal) → cString — converts numeric to string without leading spaces
|
|
func HbNtos(t *hbrt.Thread) {
|
|
t.Frame(1, 0)
|
|
defer t.EndProc()
|
|
v := t.Local(1)
|
|
if v.IsNumInt() {
|
|
t.RetString(fmt.Sprintf("%d", v.AsNumInt()))
|
|
} else if v.IsNumeric() {
|
|
t.RetString(fmt.Sprintf("%g", v.AsNumDouble()))
|
|
} else {
|
|
t.RetString("0")
|
|
}
|
|
}
|
|
|
|
// HB_VALTOEXP(xVal) → cExpr — returns Harbour expression representation
|
|
func HbValToExp(t *hbrt.Thread) {
|
|
t.Frame(1, 0)
|
|
defer t.EndProc()
|
|
v := t.Local(1)
|
|
switch {
|
|
case v.IsNil():
|
|
t.RetString("NIL")
|
|
case v.IsString():
|
|
t.RetString(`"` + v.AsString() + `"`)
|
|
case v.IsNumInt():
|
|
t.RetString(fmt.Sprintf("%d", v.AsNumInt()))
|
|
case v.IsNumeric():
|
|
t.RetString(fmt.Sprintf("%g", v.AsNumDouble()))
|
|
case v.IsLogical():
|
|
if v.AsBool() {
|
|
t.RetString(".T.")
|
|
} else {
|
|
t.RetString(".F.")
|
|
}
|
|
case v.IsDate():
|
|
t.RetString(fmt.Sprintf("0d%08d", v.AsJulian()))
|
|
default:
|
|
t.RetString(valueToDisplay(v))
|
|
}
|
|
}
|
|
|
|
// MEMOREAD(cFileName) → cContents
|
|
// Reads entire file into a string.
|
|
func MemoRead(t *hbrt.Thread) {
|
|
t.Frame(1, 0)
|
|
defer t.EndProc()
|
|
fname := t.Local(1).AsString()
|
|
data, err := os.ReadFile(fname)
|
|
if err != nil {
|
|
t.RetString("")
|
|
return
|
|
}
|
|
t.RetString(string(data))
|
|
}
|
|
|
|
// MEMOWRIT(cFileName, cString [, lAddEOF]) → lSuccess
|
|
// Writes string to file.
|
|
func MemoWrit(t *hbrt.Thread) {
|
|
nParams := t.ParamCount()
|
|
t.Frame(nParams, 0)
|
|
defer t.EndProc()
|
|
fname := t.Local(1).AsString()
|
|
content := t.Local(2).AsString()
|
|
err := os.WriteFile(fname, []byte(content), 0644)
|
|
t.RetBool(err == nil)
|
|
}
|
|
|
|
// MEMOTRAN(cMemoText [, cSoftCR [, cHardCR]]) → cString
|
|
// Replaces soft/hard CR in memo fields.
|
|
func MemoTran(t *hbrt.Thread) {
|
|
nParams := t.ParamCount()
|
|
t.Frame(nParams, 0)
|
|
defer t.EndProc()
|
|
|
|
text := t.Local(1).AsString()
|
|
softCR := ";"
|
|
hardCR := ";"
|
|
if nParams >= 2 && !t.Local(2).IsNil() {
|
|
softCR = t.Local(2).AsString()
|
|
}
|
|
if nParams >= 3 && !t.Local(3).IsNil() {
|
|
hardCR = t.Local(3).AsString()
|
|
}
|
|
|
|
// Replace soft CR (141+10) then hard CR (13+10)
|
|
text = strings.ReplaceAll(text, "\x8d\n", softCR)
|
|
text = strings.ReplaceAll(text, "\r\n", hardCR)
|
|
t.RetString(text)
|
|
}
|