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

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