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>
386 lines
7.7 KiB
Go
386 lines
7.7 KiB
Go
// Copyright (c) 2026 Charles KWON OhJun (charleskwonohjun@gmail.com)
|
|
// All rights reserved.
|
|
|
|
// New RTL functions: directory, temp file, token, hex, hash position, sleep, UTF-8.
|
|
|
|
package hbrtl
|
|
|
|
import (
|
|
"five/hbrt"
|
|
"fmt"
|
|
"os"
|
|
"path/filepath"
|
|
"strconv"
|
|
"strings"
|
|
"time"
|
|
)
|
|
|
|
// --- Directory / File ---
|
|
|
|
// HB_DIREXISTS(cPath) → lExists
|
|
func HbDirExists(t *hbrt.Thread) {
|
|
t.Frame(1, 0)
|
|
defer t.EndProc()
|
|
path := t.Local(1).AsString()
|
|
info, err := os.Stat(path)
|
|
t.RetBool(err == nil && info.IsDir())
|
|
}
|
|
|
|
// HB_DIRCREATE(cPath) → nError (0=success)
|
|
func HbDirCreate(t *hbrt.Thread) {
|
|
t.Frame(1, 0)
|
|
defer t.EndProc()
|
|
path := t.Local(1).AsString()
|
|
err := os.MkdirAll(path, 0755)
|
|
if err != nil {
|
|
t.RetInt(1)
|
|
} else {
|
|
t.RetInt(0)
|
|
}
|
|
}
|
|
|
|
// HB_FTEMPCREATE([cDir], [cPrefix], [cExt]) → nHandle
|
|
func HbFTempCreate(t *hbrt.Thread) {
|
|
nParams := t.ParamCount()
|
|
t.Frame(nParams, 0)
|
|
defer t.EndProc()
|
|
|
|
dir := ""
|
|
prefix := "hb"
|
|
ext := ".tmp"
|
|
if nParams >= 1 && !t.Local(1).IsNil() {
|
|
dir = t.Local(1).AsString()
|
|
}
|
|
if nParams >= 2 && !t.Local(2).IsNil() {
|
|
prefix = t.Local(2).AsString()
|
|
}
|
|
if nParams >= 3 && !t.Local(3).IsNil() {
|
|
ext = t.Local(3).AsString()
|
|
}
|
|
|
|
f, err := os.CreateTemp(dir, prefix+"*"+ext)
|
|
if err != nil {
|
|
t.RetInt(-1)
|
|
return
|
|
}
|
|
h := allocHandle(f)
|
|
t.RetInt(int64(h))
|
|
}
|
|
|
|
// HB_FNAMESPLIT(cFile) → {cPath, cName, cExt}
|
|
// Returns array instead of @byref params (Five workaround).
|
|
func HbFNameSplit(t *hbrt.Thread) {
|
|
t.Frame(1, 0)
|
|
defer t.EndProc()
|
|
|
|
fullPath := t.Local(1).AsString()
|
|
dir := filepath.Dir(fullPath)
|
|
base := filepath.Base(fullPath)
|
|
ext := filepath.Ext(base)
|
|
name := strings.TrimSuffix(base, ext)
|
|
|
|
if dir == "." {
|
|
dir = ""
|
|
} else if !strings.HasSuffix(dir, string(os.PathSeparator)) {
|
|
dir += string(os.PathSeparator)
|
|
}
|
|
|
|
items := []hbrt.Value{
|
|
hbrt.MakeString(dir),
|
|
hbrt.MakeString(name),
|
|
hbrt.MakeString(ext),
|
|
}
|
|
t.RetVal(hbrt.MakeArrayFrom(items))
|
|
}
|
|
|
|
// --- String Token ---
|
|
|
|
// TOKEN(cString [, cDelim [, nToken]]) → cToken
|
|
// Extract nth token from string. Default delim = " ,;\t"
|
|
func Token(t *hbrt.Thread) {
|
|
nParams := t.ParamCount()
|
|
t.Frame(nParams, 0)
|
|
defer t.EndProc()
|
|
|
|
s := t.Local(1).AsString()
|
|
delim := " ,;\t"
|
|
nToken := 1
|
|
|
|
if nParams >= 2 && !t.Local(2).IsNil() {
|
|
delim = t.Local(2).AsString()
|
|
}
|
|
if nParams >= 3 && !t.Local(3).IsNil() {
|
|
nToken = t.Local(3).AsInt()
|
|
}
|
|
|
|
tokens := tokenize(s, delim)
|
|
if nToken >= 1 && nToken <= len(tokens) {
|
|
t.RetString(tokens[nToken-1])
|
|
} else {
|
|
t.RetString("")
|
|
}
|
|
}
|
|
|
|
// NUMTOKEN(cString [, cDelim]) → nCount
|
|
func NumToken(t *hbrt.Thread) {
|
|
nParams := t.ParamCount()
|
|
t.Frame(nParams, 0)
|
|
defer t.EndProc()
|
|
|
|
s := t.Local(1).AsString()
|
|
delim := " ,;\t"
|
|
if nParams >= 2 && !t.Local(2).IsNil() {
|
|
delim = t.Local(2).AsString()
|
|
}
|
|
|
|
t.RetInt(int64(len(tokenize(s, delim))))
|
|
}
|
|
|
|
func tokenize(s, delim string) []string {
|
|
var tokens []string
|
|
current := ""
|
|
for _, ch := range s {
|
|
if strings.ContainsRune(delim, ch) {
|
|
if current != "" {
|
|
tokens = append(tokens, current)
|
|
current = ""
|
|
}
|
|
} else {
|
|
current += string(ch)
|
|
}
|
|
}
|
|
if current != "" {
|
|
tokens = append(tokens, current)
|
|
}
|
|
return tokens
|
|
}
|
|
|
|
// --- Hex Conversion ---
|
|
|
|
// HB_NUMTOHEX(nNumber [, nDigits]) → cHex
|
|
func HbNumToHex(t *hbrt.Thread) {
|
|
nParams := t.ParamCount()
|
|
t.Frame(nParams, 0)
|
|
defer t.EndProc()
|
|
|
|
n := t.Local(1).AsLong()
|
|
digits := 0
|
|
if nParams >= 2 && !t.Local(2).IsNil() {
|
|
digits = t.Local(2).AsInt()
|
|
}
|
|
|
|
var hex string
|
|
if n < 0 {
|
|
hex = fmt.Sprintf("%X", uint64(n))
|
|
} else {
|
|
hex = fmt.Sprintf("%X", n)
|
|
}
|
|
|
|
if digits > 0 && len(hex) < digits {
|
|
hex = strings.Repeat("0", digits-len(hex)) + hex
|
|
}
|
|
t.RetString(hex)
|
|
}
|
|
|
|
// HB_HEXTONUM(cHex) → nNumber
|
|
func HbHexToNum(t *hbrt.Thread) {
|
|
t.Frame(1, 0)
|
|
defer t.EndProc()
|
|
s := strings.TrimSpace(t.Local(1).AsString())
|
|
s = strings.TrimPrefix(s, "0x")
|
|
s = strings.TrimPrefix(s, "0X")
|
|
n, err := strconv.ParseInt(s, 16, 64)
|
|
if err != nil {
|
|
t.RetInt(0)
|
|
} else {
|
|
t.RetInt(n)
|
|
}
|
|
}
|
|
|
|
// --- Hash Position Access ---
|
|
|
|
// HB_HPOS(hHash, xKey) → nPos (1-based, 0 if not found)
|
|
func HbHPos(t *hbrt.Thread) {
|
|
t.Frame(2, 0)
|
|
defer t.EndProc()
|
|
h := t.Local(1)
|
|
key := t.Local(2)
|
|
if !h.IsHash() {
|
|
t.RetInt(0)
|
|
return
|
|
}
|
|
hh := h.AsHash()
|
|
for i, k := range hh.Keys {
|
|
if k.AsString() == key.AsString() {
|
|
t.RetInt(int64(i + 1))
|
|
return
|
|
}
|
|
}
|
|
t.RetInt(0)
|
|
}
|
|
|
|
// HB_HKEYAT(hHash, nPos) → xKey
|
|
func HbHKeyAt(t *hbrt.Thread) {
|
|
t.Frame(2, 0)
|
|
defer t.EndProc()
|
|
h := t.Local(1)
|
|
n := t.Local(2).AsInt()
|
|
if !h.IsHash() || n < 1 || n > len(h.AsHash().Keys) {
|
|
t.RetNil()
|
|
return
|
|
}
|
|
t.RetVal(h.AsHash().Keys[n-1])
|
|
}
|
|
|
|
// HB_HVALUEAT(hHash, nPos [, xNewVal]) → xValue
|
|
func HbHValueAt(t *hbrt.Thread) {
|
|
nParams := t.ParamCount()
|
|
t.Frame(nParams, 0)
|
|
defer t.EndProc()
|
|
h := t.Local(1)
|
|
n := t.Local(2).AsInt()
|
|
if !h.IsHash() || n < 1 || n > len(h.AsHash().Values) {
|
|
t.RetNil()
|
|
return
|
|
}
|
|
hh := h.AsHash()
|
|
if nParams >= 3 && !t.Local(3).IsNil() {
|
|
hh.Values[n-1] = t.Local(3)
|
|
}
|
|
t.RetVal(hh.Values[n-1])
|
|
}
|
|
|
|
// HB_HCLONE(hHash) → hNewHash (deep copy)
|
|
func HbHClone(t *hbrt.Thread) {
|
|
t.Frame(1, 0)
|
|
defer t.EndProc()
|
|
h := t.Local(1)
|
|
if !h.IsHash() {
|
|
t.RetVal(h)
|
|
return
|
|
}
|
|
src := h.AsHash()
|
|
dst := &hbrt.HbHash{
|
|
Keys: make([]hbrt.Value, len(src.Keys)),
|
|
Values: make([]hbrt.Value, len(src.Values)),
|
|
}
|
|
copy(dst.Keys, src.Keys)
|
|
copy(dst.Values, src.Values)
|
|
if src.Order != nil {
|
|
dst.Order = make([]int, len(src.Order))
|
|
copy(dst.Order, src.Order)
|
|
}
|
|
t.RetVal(hbrt.MakeHashFrom(dst))
|
|
}
|
|
|
|
// --- Sleep ---
|
|
|
|
// HB_IDLESLEEP(nSeconds) → NIL
|
|
func HbIdleSleep(t *hbrt.Thread) {
|
|
t.Frame(1, 0)
|
|
defer t.EndProc()
|
|
n := t.Local(1).AsNumDouble()
|
|
if n > 0 {
|
|
time.Sleep(time.Duration(n * float64(time.Second)))
|
|
}
|
|
t.RetNil()
|
|
}
|
|
|
|
// --- UTF-8 (identity in Go) ---
|
|
|
|
// HB_UTF8TOSTR(cUTF8) → cString
|
|
func HbUTF8ToStr(t *hbrt.Thread) {
|
|
t.Frame(1, 0)
|
|
defer t.EndProc()
|
|
t.RetString(t.Local(1).AsString())
|
|
}
|
|
|
|
// HB_STRTOUTF8(cString) → cUTF8
|
|
func HbStrToUTF8(t *hbrt.Thread) {
|
|
t.Frame(1, 0)
|
|
defer t.EndProc()
|
|
t.RetString(t.Local(1).AsString())
|
|
}
|
|
|
|
// HB_UTF8LEN(cUTF8) → nChars
|
|
func HbUTF8Len(t *hbrt.Thread) {
|
|
t.Frame(1, 0)
|
|
defer t.EndProc()
|
|
s := t.Local(1).AsString()
|
|
t.RetInt(int64(len([]rune(s))))
|
|
}
|
|
|
|
// HB_UTF8SUBSTR(cUTF8, nFrom [, nCount]) → cSub
|
|
func HbUTF8SubStr(t *hbrt.Thread) {
|
|
nParams := t.ParamCount()
|
|
t.Frame(nParams, 0)
|
|
defer t.EndProc()
|
|
|
|
runes := []rune(t.Local(1).AsString())
|
|
nFrom := t.Local(2).AsInt()
|
|
if nFrom < 1 {
|
|
nFrom = 1
|
|
}
|
|
nFrom-- // 0-based
|
|
|
|
nCount := len(runes) - nFrom
|
|
if nParams >= 3 && !t.Local(3).IsNil() {
|
|
nCount = t.Local(3).AsInt()
|
|
}
|
|
|
|
if nFrom >= len(runes) {
|
|
t.RetString("")
|
|
return
|
|
}
|
|
end := nFrom + nCount
|
|
if end > len(runes) {
|
|
end = len(runes)
|
|
}
|
|
t.RetString(string(runes[nFrom:end]))
|
|
}
|
|
|
|
// --- Utilities ---
|
|
|
|
// HB_SETENV(cVar [, cValue]) → lSuccess
|
|
func HbSetEnv(t *hbrt.Thread) {
|
|
nParams := t.ParamCount()
|
|
t.Frame(nParams, 0); defer t.EndProc()
|
|
name := t.Local(1).AsString()
|
|
if nParams >= 2 && !t.Local(2).IsNil() {
|
|
t.RetBool(os.Setenv(name, t.Local(2).AsString()) == nil)
|
|
} else {
|
|
t.RetBool(os.Unsetenv(name) == nil)
|
|
}
|
|
}
|
|
|
|
// HB_PS() → cPathSeparator
|
|
func HbPS(t *hbrt.Thread) {
|
|
t.Frame(0, 0); defer t.EndProc()
|
|
t.RetString(string(os.PathSeparator))
|
|
}
|
|
|
|
// HB_EOL() → cEOL
|
|
func HbEOL(t *hbrt.Thread) {
|
|
t.Frame(0, 0); defer t.EndProc()
|
|
if os.PathSeparator == '\\' {
|
|
t.RetString("\r\n")
|
|
} else {
|
|
t.RetString("\n")
|
|
}
|
|
}
|
|
|
|
// HB_ISNULL(x) → lNull (NIL or empty string)
|
|
func HbIsNull(t *hbrt.Thread) {
|
|
t.Frame(1, 0); defer t.EndProc()
|
|
v := t.Local(1)
|
|
if v.IsNil() { t.RetBool(true); return }
|
|
if v.IsString() { t.RetBool(len(v.AsString()) == 0); return }
|
|
t.RetBool(false)
|
|
}
|
|
|
|
// ERRORSYS() → NIL (stub — default error handler setup)
|
|
func ErrorSys(t *hbrt.Thread) {
|
|
t.Frame(0, 0); defer t.EndProc()
|
|
t.RetNil()
|
|
}
|