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>
168 lines
4.8 KiB
Go
168 lines
4.8 KiB
Go
// 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})))
|
|
}
|