// Copyright (c) 2026 Charles KWON OhJun. All rights reserved. // Extended string RTL functions. package hbrtl import ( "five/hbrt" "path/filepath" "regexp" "strings" "unicode" ) // HB_AT(cSub, cStr [, nFrom [, nEnd]]) → nPos (extended AT with start position) func HbAt(t *hbrt.Thread) { nParams := t.ParamCount() t.Frame(nParams, 0); defer t.EndProc() sub := t.Local(1).AsString() str := t.Local(2).AsString() from := 0 if nParams >= 3 && !t.Local(3).IsNil() { from = t.Local(3).AsInt() - 1 } if from < 0 { from = 0 } if from >= len(str) { t.RetInt(0); return } idx := strings.Index(str[from:], sub) if idx < 0 { t.RetInt(0) } else { t.RetInt(int64(idx + from + 1)) } } // HB_RAT(cSub, cStr [, nEnd]) → nPos func HbRat(t *hbrt.Thread) { nParams := t.ParamCount() t.Frame(nParams, 0); defer t.EndProc() sub := t.Local(1).AsString() str := t.Local(2).AsString() if nParams >= 3 && !t.Local(3).IsNil() { end := t.Local(3).AsInt() if end > 0 && end < len(str) { str = str[:end] } } idx := strings.LastIndex(str, sub) if idx < 0 { t.RetInt(0) } else { t.RetInt(int64(idx + 1)) } } // HB_ATI(cSub, cStr [, nFrom]) → nPos (case-insensitive AT) func HbAtI(t *hbrt.Thread) { nParams := t.ParamCount() t.Frame(nParams, 0); defer t.EndProc() sub := strings.ToUpper(t.Local(1).AsString()) str := strings.ToUpper(t.Local(2).AsString()) from := 0 if nParams >= 3 && !t.Local(3).IsNil() { from = t.Local(3).AsInt() - 1 } if from < 0 { from = 0 } if from >= len(str) { t.RetInt(0); return } idx := strings.Index(str[from:], sub) if idx < 0 { t.RetInt(0) } else { t.RetInt(int64(idx + from + 1)) } } // HB_ATX(cRegex, cStr) → cMatch (first regex match) func HbAtX(t *hbrt.Thread) { t.Frame(2, 0); defer t.EndProc() re, err := regexp.Compile(t.Local(1).AsString()) if err != nil { t.RetString(""); return } m := re.FindString(t.Local(2).AsString()) t.RetString(m) } // HB_LEFTEQI(cStr, cPrefix) → lMatch (case-insensitive left compare) func HbLeftEqI(t *hbrt.Thread) { t.Frame(2, 0); defer t.EndProc() str := t.Local(1).AsString() prefix := t.Local(2).AsString() if len(str) < len(prefix) { t.RetBool(false); return } t.RetBool(strings.EqualFold(str[:len(prefix)], prefix)) } // HB_ASCIIISALPHA(n) → lAlpha func HbAsciiIsAlpha(t *hbrt.Thread) { t.Frame(1, 0); defer t.EndProc() t.RetBool(unicode.IsLetter(rune(t.Local(1).AsInt()))) } // HB_ASCIIISDIGIT(n) → lDigit func HbAsciiIsDigit(t *hbrt.Thread) { t.Frame(1, 0); defer t.EndProc() t.RetBool(unicode.IsDigit(rune(t.Local(1).AsInt()))) } // HB_ASCIIISLOWER(n) → lLower func HbAsciiIsLower(t *hbrt.Thread) { t.Frame(1, 0); defer t.EndProc() t.RetBool(unicode.IsLower(rune(t.Local(1).AsInt()))) } // HB_ASCIIISUPPER(n) → lUpper func HbAsciiIsUpper(t *hbrt.Thread) { t.Frame(1, 0); defer t.EndProc() t.RetBool(unicode.IsUpper(rune(t.Local(1).AsInt()))) } // HB_STRISUTF8(cStr) → lValid func HbStrIsUtf8(t *hbrt.Thread) { t.Frame(1, 0); defer t.EndProc() s := t.Local(1).AsString() // Go strings are UTF-8 by default; check for invalid sequences for i := 0; i < len(s); { r, size := []rune(s[i:])[0], len(string([]rune(s[i:])[0])) if r == 0xFFFD && size == 1 { t.RetBool(false); return } i += size } t.RetBool(true) } // HB_STRDECODESCAPE(cStr) → cDecoded (\n, \t, \r, \\, \xHH) func HbStrDecodeEscape(t *hbrt.Thread) { t.Frame(1, 0); defer t.EndProc() s := t.Local(1).AsString() var b strings.Builder for i := 0; i < len(s); i++ { if s[i] == '\\' && i+1 < len(s) { switch s[i+1] { case 'n': b.WriteByte('\n'); i++ case 't': b.WriteByte('\t'); i++ case 'r': b.WriteByte('\r'); i++ case '\\': b.WriteByte('\\'); i++ case '0': b.WriteByte(0); i++ default: b.WriteByte(s[i]) } } else { b.WriteByte(s[i]) } } t.RetString(b.String()) } // HB_STRXOR(cStr, cKey) → cResult (XOR each byte with key) func HbStrXor(t *hbrt.Thread) { t.Frame(2, 0); defer t.EndProc() s := []byte(t.Local(1).AsString()) k := []byte(t.Local(2).AsString()) if len(k) == 0 { t.RetString(string(s)); return } for i := range s { s[i] ^= k[i%len(k)] } t.RetString(string(s)) } // HB_WILDMATCH(cPattern, cString [, lCase]) → lMatch func HbWildMatch(t *hbrt.Thread) { nParams := t.ParamCount() t.Frame(nParams, 0); defer t.EndProc() pattern := t.Local(1).AsString() str := t.Local(2).AsString() matched, _ := filepath.Match(pattern, str) t.RetBool(matched) } // HB_WILDMATCHI(cPattern, cString) → lMatch (case-insensitive) func HbWildMatchI(t *hbrt.Thread) { t.Frame(2, 0); defer t.EndProc() pattern := strings.ToUpper(t.Local(1).AsString()) str := strings.ToUpper(t.Local(2).AsString()) matched, _ := filepath.Match(pattern, str) t.RetBool(matched) } // HARDCR(cStr) → cStr (replace soft CR 141 with hard CR 13) func HardCR(t *hbrt.Thread) { t.Frame(1, 0); defer t.EndProc() s := t.Local(1).AsString() t.RetString(strings.ReplaceAll(s, string([]byte{141}), string([]byte{13}))) }