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