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

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