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>
217 lines
6.0 KiB
Go
217 lines
6.0 KiB
Go
// Copyright (c) 2026 Charles KWON OhJun. All rights reserved.
|
|
// Extended file/directory RTL functions.
|
|
|
|
package hbrtl
|
|
|
|
import (
|
|
"five/hbrt"
|
|
"io"
|
|
"os"
|
|
"path/filepath"
|
|
"runtime"
|
|
"strings"
|
|
"syscall"
|
|
"time"
|
|
)
|
|
|
|
// HB_FSIZE(cFile) → nBytes
|
|
func HbFSize(t *hbrt.Thread) {
|
|
t.Frame(1, 0); defer t.EndProc()
|
|
info, err := os.Stat(t.Local(1).AsString())
|
|
if err != nil { t.RetInt(0); return }
|
|
t.RetInt(info.Size())
|
|
}
|
|
|
|
// HB_FCOPY(cSrc, cDst) → nError (0=ok, -1=fail)
|
|
func HbFCopy(t *hbrt.Thread) {
|
|
t.Frame(2, 0); defer t.EndProc()
|
|
src, err := os.Open(t.Local(1).AsString())
|
|
if err != nil { t.RetInt(-1); return }
|
|
defer src.Close()
|
|
dst, err := os.Create(t.Local(2).AsString())
|
|
if err != nil { t.RetInt(-1); return }
|
|
defer dst.Close()
|
|
if _, err := io.Copy(dst, src); err != nil { t.RetInt(-1); return }
|
|
t.RetInt(0)
|
|
}
|
|
|
|
// HB_FEOF(nHandle) → lEOF
|
|
func HbFEof(t *hbrt.Thread) {
|
|
t.Frame(1, 0); defer t.EndProc()
|
|
h := t.Local(1).AsInt()
|
|
f, ok := getHandle(h)
|
|
if !ok { t.RetBool(true); return }
|
|
cur, _ := f.Seek(0, io.SeekCurrent)
|
|
end, _ := f.Seek(0, io.SeekEnd)
|
|
f.Seek(cur, io.SeekStart)
|
|
t.RetBool(cur >= end)
|
|
}
|
|
|
|
// HB_FCOMMIT(nHandle) → NIL
|
|
func HbFCommit(t *hbrt.Thread) {
|
|
t.Frame(1, 0); defer t.EndProc()
|
|
if f, ok := getHandle(t.Local(1).AsInt()); ok { f.Sync() }
|
|
t.RetNil()
|
|
}
|
|
|
|
// HB_FREADLEN(nHandle, nLen) → cData
|
|
func HbFReadLen(t *hbrt.Thread) {
|
|
t.Frame(2, 0); defer t.EndProc()
|
|
f, ok := getHandle(t.Local(1).AsInt())
|
|
if !ok { t.RetString(""); return }
|
|
buf := make([]byte, t.Local(2).AsInt())
|
|
n, _ := f.Read(buf)
|
|
t.RetString(string(buf[:n]))
|
|
}
|
|
|
|
// HB_FGETATTR(cFile) → nAttr (Unix mode bits)
|
|
func HbFGetAttr(t *hbrt.Thread) {
|
|
t.Frame(1, 0); defer t.EndProc()
|
|
info, err := os.Stat(t.Local(1).AsString())
|
|
if err != nil { t.RetInt(-1); return }
|
|
t.RetInt(int64(info.Mode().Perm()))
|
|
}
|
|
|
|
// HB_FSETATTR(cFile, nAttr) → nError
|
|
func HbFSetAttr(t *hbrt.Thread) {
|
|
t.Frame(2, 0); defer t.EndProc()
|
|
err := os.Chmod(t.Local(1).AsString(), os.FileMode(t.Local(2).AsInt()))
|
|
if err != nil { t.RetInt(-1) } else { t.RetInt(0) }
|
|
}
|
|
|
|
// HB_FGETDATETIME(cFile) → tTimestamp
|
|
func HbFGetDateTime(t *hbrt.Thread) {
|
|
t.Frame(1, 0); defer t.EndProc()
|
|
info, err := os.Stat(t.Local(1).AsString())
|
|
if err != nil { t.RetNil(); return }
|
|
mt := info.ModTime()
|
|
t.RetVal(goTimeToTimestamp(mt))
|
|
}
|
|
|
|
// HB_FSETDATETIME(cFile, dDate [, cTime]) → nError
|
|
func HbFSetDateTime(t *hbrt.Thread) {
|
|
nParams := t.ParamCount()
|
|
t.Frame(nParams, 0); defer t.EndProc()
|
|
path := t.Local(1).AsString()
|
|
// Simple: set to current time if no date given, else use date
|
|
mt := time.Now()
|
|
if nParams >= 2 && !t.Local(2).IsNil() {
|
|
mt = julianToGoTime(t.Local(2).AsJulian())
|
|
}
|
|
err := os.Chtimes(path, mt, mt)
|
|
if err != nil { t.RetInt(-1) } else { t.RetInt(0) }
|
|
}
|
|
|
|
// HB_FLOCK(nHandle [, nOffset, nLength]) → lSuccess (stub — always true on non-Windows)
|
|
func HbFLock(t *hbrt.Thread) {
|
|
nParams := t.ParamCount()
|
|
t.Frame(nParams, 0); defer t.EndProc()
|
|
t.RetBool(true) // stub
|
|
}
|
|
|
|
// HB_FUNLOCK(nHandle [, nOffset, nLength]) → lSuccess
|
|
func HbFUnlock(t *hbrt.Thread) {
|
|
nParams := t.ParamCount()
|
|
t.Frame(nParams, 0); defer t.EndProc()
|
|
t.RetBool(true) // stub
|
|
}
|
|
|
|
// HB_FILEDELETE(cMask) → lSuccess
|
|
func HbFileDelete(t *hbrt.Thread) {
|
|
t.Frame(1, 0); defer t.EndProc()
|
|
matches, err := filepath.Glob(t.Local(1).AsString())
|
|
if err != nil || len(matches) == 0 { t.RetBool(false); return }
|
|
ok := true
|
|
for _, m := range matches { if os.Remove(m) != nil { ok = false } }
|
|
t.RetBool(ok)
|
|
}
|
|
|
|
// HB_FILEMATCH(cFile, cMask) → lMatch
|
|
func HbFileMatch(t *hbrt.Thread) {
|
|
t.Frame(2, 0); defer t.EndProc()
|
|
matched, _ := filepath.Match(t.Local(2).AsString(), filepath.Base(t.Local(1).AsString()))
|
|
t.RetBool(matched)
|
|
}
|
|
|
|
// HB_FNAMEEXISTS(cFile) → lExists
|
|
func HbFNameExists(t *hbrt.Thread) {
|
|
t.Frame(1, 0); defer t.EndProc()
|
|
_, err := os.Stat(t.Local(1).AsString())
|
|
t.RetBool(err == nil)
|
|
}
|
|
|
|
// HB_FNAMEEXTSET(cFile, cNewExt) → cNewFile
|
|
func HbFNameExtSet(t *hbrt.Thread) {
|
|
t.Frame(2, 0); defer t.EndProc()
|
|
f := t.Local(1).AsString()
|
|
ext := t.Local(2).AsString()
|
|
base := strings.TrimSuffix(f, filepath.Ext(f))
|
|
if ext != "" && !strings.HasPrefix(ext, ".") { ext = "." + ext }
|
|
t.RetString(base + ext)
|
|
}
|
|
|
|
// HB_FNAMENAMEEXT(cFile) → cNameExt (filename + ext, no path)
|
|
func HbFNameNameExt(t *hbrt.Thread) {
|
|
t.Frame(1, 0); defer t.EndProc()
|
|
t.RetString(filepath.Base(t.Local(1).AsString()))
|
|
}
|
|
|
|
// HB_MEMOREAD(cFile) → cContents (alias for MEMOREAD)
|
|
func HbMemoRead(t *hbrt.Thread) {
|
|
t.Frame(1, 0); defer t.EndProc()
|
|
data, err := os.ReadFile(t.Local(1).AsString())
|
|
if err != nil { t.RetString(""); return }
|
|
t.RetString(string(data))
|
|
}
|
|
|
|
// HB_MEMOWRIT(cFile, cData [, lAddEOF]) → lSuccess (alias for MEMOWRIT)
|
|
func HbMemoWrit(t *hbrt.Thread) {
|
|
nParams := t.ParamCount()
|
|
t.Frame(nParams, 0); defer t.EndProc()
|
|
err := os.WriteFile(t.Local(1).AsString(), []byte(t.Local(2).AsString()), 0644)
|
|
t.RetBool(err == nil)
|
|
}
|
|
|
|
// HB_DIRTEMP() → cTempDir
|
|
func HbDirTemp(t *hbrt.Thread) {
|
|
t.Frame(0, 0); defer t.EndProc()
|
|
t.RetString(os.TempDir())
|
|
}
|
|
|
|
// HB_DISKSPACE(cDrive [, nType]) → nFreeBytes
|
|
func HbDiskSpace(t *hbrt.Thread) {
|
|
nParams := t.ParamCount()
|
|
t.Frame(nParams, 0); defer t.EndProc()
|
|
path := "/"
|
|
if nParams >= 1 && !t.Local(1).IsNil() {
|
|
path = t.Local(1).AsString()
|
|
}
|
|
if path == "" { path = "/" }
|
|
if runtime.GOOS == "windows" {
|
|
t.RetInt(0) // unsupported
|
|
return
|
|
}
|
|
var stat syscall.Statfs_t
|
|
if syscall.Statfs(path, &stat) != nil { t.RetInt(0); return }
|
|
t.RetInt(int64(stat.Bavail) * int64(stat.Bsize))
|
|
}
|
|
|
|
// DISKSPACE([nDrive]) → nFreeBytes (Clipper-compatible)
|
|
func DiskSpaceFunc(t *hbrt.Thread) { HbDiskSpace(t) }
|
|
|
|
// --- helpers ---
|
|
|
|
func goTimeToTimestamp(gt time.Time) hbrt.Value {
|
|
y, m, d := gt.Date()
|
|
julian := dateToJulian(y, int(m), d)
|
|
ms := int32(gt.Hour()*3600000 + gt.Minute()*60000 + gt.Second()*1000 + gt.Nanosecond()/1000000)
|
|
return hbrt.MakeTimestamp(julian, ms)
|
|
}
|
|
|
|
func julianToGoTime(j int64) time.Time {
|
|
y, m, d := julianToDate(j)
|
|
return time.Date(y, time.Month(m), d, 0, 0, 0, 0, time.Local)
|
|
}
|
|
|
|
// dateToJulian and julianToDate are in datetime.go
|