Files
five/hbrtl/missing.go
Charles KWON OhJun 827adeeb99 feat: SET commands + ErrorBlock/Break error handling
SET commands (setcmd.go):
  - SetDateFunc: __SetDateFormat([cNew]) → cOld
  - SetDecimalsFunc: SET DECIMALS TO n
  - SetEpochFunc: SET EPOCH TO n
  - 11 toggle functions: SetExact, SetDeleted, SetSoftSeek, SetExclusive,
    SetFixed, SetCancel, SetBell, SetConfirm, SetInsert, SetEscape, SetWrap
  - SET constants: _SET_EXACT, _SET_DELETED, etc. for PRG code
  - GetSetDateFormat(), GetSetDecimals(), GetSetEpoch() helpers
  - Default: DATE="mm/dd/yy", EPOCH=1900, DECIMALS=2

Error handling (error.go):
  - Break(xValue): panics with BreakValue, caught by BEGIN SEQUENCE
  - BreakBlock(): returns {|e| Break(e)} code block
  - LaunchError(): dispatches error through ErrorBlock handler
  - RuntimeError(): creates + launches standard runtime error
  - IsBreak(): checks if recovered panic is a BreakValue
  - createErrorHash(): builds Harbour-compatible error hash

Registration: ErrorBlock, ErrorNew, DosError, FError, Break + all SET functions

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-02 15:33:07 +09:00

451 lines
9.0 KiB
Go

// Copyright (c) 2026 Charles KWON OhJun (charleskwonohjun@gmail.com)
// All rights reserved.
// Missing RTL functions — filling the gap between Five and Harbour.
// Reference: /mnt/d/harbour-core/include/hbcompdf.h (HB_F_* list)
package hbrtl
import (
"five/hbrt"
"math"
"strings"
)
// --- String functions ---
// At returns position of cSearch in cTarget (1-based, 0 if not found).
func At(t *hbrt.Thread) {
t.Frame(2, 0)
defer t.EndProc()
search := t.Local(1).AsString()
target := t.Local(2).AsString()
idx := strings.Index(target, search)
if idx >= 0 {
t.RetInt(int64(idx + 1))
} else {
t.RetInt(0)
}
}
// Left returns leftmost n characters.
func Left(t *hbrt.Thread) {
t.Frame(2, 0)
defer t.EndProc()
s := t.Local(1).AsString()
n := int(t.Local(2).AsNumInt())
if n >= len(s) {
t.PushString(s)
} else if n <= 0 {
t.PushString("")
} else {
t.PushString(s[:n])
}
t.RetValue()
}
// Right returns rightmost n characters.
func Right(t *hbrt.Thread) {
t.Frame(2, 0)
defer t.EndProc()
s := t.Local(1).AsString()
n := int(t.Local(2).AsNumInt())
if n >= len(s) {
t.PushString(s)
} else if n <= 0 {
t.PushString("")
} else {
t.PushString(s[len(s)-n:])
}
t.RetValue()
}
// Asc returns ASCII code of first character.
func Asc(t *hbrt.Thread) {
t.Frame(1, 0)
defer t.EndProc()
s := t.Local(1).AsString()
if len(s) > 0 {
t.RetInt(int64(s[0]))
} else {
t.RetInt(0)
}
}
// Chr returns character from ASCII code.
func Chr(t *hbrt.Thread) {
t.Frame(1, 0)
defer t.EndProc()
n := int(t.Local(1).AsNumInt())
t.PushString(string([]byte{byte(n)}))
t.RetValue()
}
// StrTran replaces occurrences in string.
func StrTran(t *hbrt.Thread) {
nParams := t.ParamCount()
t.Frame(nParams, 0)
defer t.EndProc()
s := t.Local(1).AsString()
search := t.Local(2).AsString()
replace := ""
if nParams >= 3 {
replace = t.Local(3).AsString()
}
t.PushString(strings.ReplaceAll(s, search, replace))
t.RetValue()
}
// Stuff inserts/replaces characters in string.
func Stuff(t *hbrt.Thread) {
t.Frame(4, 0)
defer t.EndProc()
s := t.Local(1).AsString()
start := int(t.Local(2).AsNumInt()) - 1 // 1-based
nDel := int(t.Local(3).AsNumInt())
insert := t.Local(4).AsString()
if start < 0 {
start = 0
}
if start > len(s) {
start = len(s)
}
end := start + nDel
if end > len(s) {
end = len(s)
}
t.PushString(s[:start] + insert + s[end:])
t.RetValue()
}
// PadC pads string centered.
func PadC(t *hbrt.Thread) {
t.Frame(2, 0)
defer t.EndProc()
s := valueToDisplay(t.Local(1))
n := int(t.Local(2).AsNumInt())
if len(s) >= n {
t.PushString(s[:n])
} else {
leftPad := (n - len(s)) / 2
rightPad := n - len(s) - leftPad
t.PushString(strings.Repeat(" ", leftPad) + s + strings.Repeat(" ", rightPad))
}
t.RetValue()
}
// --- Math functions ---
// Round rounds a number to specified decimal places.
func Round(t *hbrt.Thread) {
t.Frame(2, 0)
defer t.EndProc()
val := t.Local(1).AsNumDouble()
dec := int(t.Local(2).AsNumInt())
mult := math.Pow(10, float64(dec))
result := math.Round(val*mult) / mult
t.PushValue(hbrt.MakeDouble(result, 255, uint16(dec)))
t.RetValue()
}
// Max returns larger of two values.
func Max(t *hbrt.Thread) {
t.Frame(2, 0)
defer t.EndProc()
a := t.Local(1)
b := t.Local(2)
if a.IsNumeric() && b.IsNumeric() {
if a.AsNumDouble() >= b.AsNumDouble() {
t.PushValue(a)
} else {
t.PushValue(b)
}
} else if a.IsDateTime() && b.IsDateTime() {
if a.AsJulian() >= b.AsJulian() {
t.PushValue(a)
} else {
t.PushValue(b)
}
} else {
t.PushValue(a)
}
t.RetValue()
}
// Min returns smaller of two values.
func Min(t *hbrt.Thread) {
t.Frame(2, 0)
defer t.EndProc()
a := t.Local(1)
b := t.Local(2)
if a.IsNumeric() && b.IsNumeric() {
if a.AsNumDouble() <= b.AsNumDouble() {
t.PushValue(a)
} else {
t.PushValue(b)
}
} else if a.IsDateTime() && b.IsDateTime() {
if a.AsJulian() <= b.AsJulian() {
t.PushValue(a)
} else {
t.PushValue(b)
}
} else {
t.PushValue(a)
}
t.RetValue()
}
// Sqrt returns square root.
func Sqrt(t *hbrt.Thread) {
t.Frame(1, 0)
defer t.EndProc()
t.PushValue(hbrt.MakeDoubleAuto(math.Sqrt(t.Local(1).AsNumDouble())))
t.RetValue()
}
// Log returns natural logarithm.
func Log(t *hbrt.Thread) {
t.Frame(1, 0)
defer t.EndProc()
t.PushValue(hbrt.MakeDoubleAuto(math.Log(t.Local(1).AsNumDouble())))
t.RetValue()
}
// Exp returns e^x.
func Exp(t *hbrt.Thread) {
t.Frame(1, 0)
defer t.EndProc()
t.PushValue(hbrt.MakeDoubleAuto(math.Exp(t.Local(1).AsNumDouble())))
t.RetValue()
}
// Mod returns modulus (same as %).
func Mod(t *hbrt.Thread) {
t.Frame(2, 0)
defer t.EndProc()
a := t.Local(1).AsNumDouble()
b := t.Local(2).AsNumDouble()
if b == 0 {
t.PushValue(hbrt.MakeDoubleAuto(0))
} else {
t.PushValue(hbrt.MakeDoubleAuto(math.Mod(a, b)))
}
t.RetValue()
}
// CToD, CDoW, CMonth moved to datetime.go
// --- Type / Misc ---
// Type returns type of an expression (as string).
func TypeFunc(t *hbrt.Thread) {
t.Frame(1, 0)
defer t.EndProc()
v := t.Local(1)
var c string
switch {
case v.IsNil():
c = "U"
case v.IsLogical():
c = "L"
case v.IsNumeric():
c = "N"
case v.IsString():
c = "C"
case v.IsDate(), v.IsTimestamp():
c = "D"
case v.IsArray():
c = "A"
case v.IsHash():
c = "H"
case v.IsBlock():
c = "B"
case v.IsObject():
c = "O"
default:
c = "U"
}
t.PushString(c)
t.RetValue()
}
// PCount returns number of parameters passed.
func PCount(t *hbrt.Thread) {
t.Frame(0, 0)
defer t.EndProc()
t.RetInt(int64(t.ParamCount()))
}
// Break moved to error.go — full implementation with BreakValue type.
// Array creates array of given size.
func ArrayFunc(t *hbrt.Thread) {
t.Frame(1, 0)
defer t.EndProc()
n := int(t.Local(1).AsNumInt())
t.PushValue(hbrt.MakeArray(n))
t.RetValue()
}
// FCount returns number of fields in current workarea.
func FCount(t *hbrt.Thread) {
t.Frame(0, 0)
defer t.EndProc()
wam := getWA(t)
if wam != nil {
if area := wam.Current(); area != nil {
t.RetInt(int64(area.FieldCount()))
return
}
}
t.RetInt(0)
}
// FieldName returns field name by position (1-based).
func FieldName(t *hbrt.Thread) {
t.Frame(1, 0)
defer t.EndProc()
nField := int(t.Local(1).AsNumInt())
wam := getWA(t)
if wam != nil {
if area := wam.Current(); area != nil {
if nField >= 1 && nField <= area.FieldCount() {
fi := area.GetFieldInfo(nField - 1) // 1-based to 0-based
t.RetString(fi.Name)
return
}
}
}
t.RetString("")
}
// Select returns current workarea number.
func SelectFunc(t *hbrt.Thread) {
t.Frame(0, 0)
defer t.EndProc()
// TODO: integrate with RDD
t.RetInt(0)
}
// File checks if file exists.
func FileFunc(t *hbrt.Thread) {
t.Frame(1, 0)
defer t.EndProc()
// Simple implementation
t.PushBool(false)
t.RetValue()
}
// Inkey waits for keypress and returns key code.
// Harbour: Inkey(nSeconds) — 0 = wait forever
// Uses shared raw terminal from rawtty.go
func Inkey(t *hbrt.Thread) {
nParams := t.ParamCount()
t.Frame(nParams, 0)
defer t.EndProc()
// Check keyboard buffer first
if k := PopKeyBuffer(); k >= 0 {
SetLastKey(k)
t.RetInt(int64(k))
return
}
// Check timeout param: Inkey(0) = wait forever, Inkey(n) = wait n seconds
nWait := float64(0)
if nParams >= 1 && !t.Local(1).IsNil() {
nWait = t.Local(1).AsNumDouble()
}
_ = nWait // TODO: implement timeout for non-zero
key := ReadKey() // from rawtty.go (auto-inits raw mode)
var result int
switch key {
case 'A':
result = 5 // K_UP
case 'B':
result = 24 // K_DOWN
case 'C':
result = 4 // K_RIGHT
case 'D':
result = 19 // K_LEFT
case '5':
result = 18 // K_PGUP
case '6':
result = 3 // K_PGDN
case 'H':
result = 1 // K_HOME
case 'F':
result = 6 // K_END
case kESC:
result = 27
default:
result = key
}
SetLastKey(result)
t.RetInt(int64(result))
}
// Transform formats a value with picture string.
func Transform(t *hbrt.Thread) {
nParams := t.ParamCount()
t.Frame(nParams, 0)
defer t.EndProc()
val := t.Local(1)
pic := ""
if nParams >= 2 && !t.Local(2).IsNil() {
pic = t.Local(2).AsString()
}
t.RetString(transformHbValue(val, pic))
}
// hb_StrReplace replaces multiple substrings.
func HbStrReplace(t *hbrt.Thread) {
t.Frame(3, 0)
defer t.EndProc()
s := t.Local(1).AsString()
search := t.Local(2) // array of search strings
replace := t.Local(3) // array of replace strings
if search.IsArray() && replace.IsArray() {
sa := search.AsArray()
ra := replace.AsArray()
for i := 0; i < len(sa.Items) && i < len(ra.Items); i++ {
s = strings.ReplaceAll(s, sa.Items[i].AsString(), ra.Items[i].AsString())
}
}
t.PushString(s)
t.RetValue()
}
// hb_NToS converts number to string without leading spaces.
func HbNToS(t *hbrt.Thread) {
t.Frame(1, 0)
defer t.EndProc()
v := t.Local(1)
if v.IsNumInt() {
t.PushString(fmt_int64(v.AsNumInt()))
} else {
t.PushString(strings.TrimSpace(valueToDisplay(v)))
}
t.RetValue()
}
func fmt_int64(n int64) string {
if n == 0 {
return "0"
}
neg := n < 0
if neg {
n = -n
}
buf := make([]byte, 0, 20)
for n > 0 {
buf = append([]byte{byte('0' + n%10)}, buf...)
n /= 10
}
if neg {
buf = append([]byte{'-'}, buf...)
}
return string(buf)
}