Files
five/hbrtl/strings2.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

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