Files
five/hbrtl/missing.go
Charles KWON OhJun d451b836a6 perf: inline Str/PadR/PadL/SubStr/Left/Right/At/IIF in gengo
13 more RTL functions inlined — no Frame/EndProc, no VM dispatch:
- Str(n,w,d) → fmt.Sprintf("%*.*f", w, d, n)
- PadR(s,n) → s + hbrtl.Spaces(n-len(s))
- PadL(s,n[,fill]) → Spaces(pad) + s or Repeat(fill, pad) + s
- SubStr(s,p,l) → s[p:p+l] with bounds check
- Left(s,n) → s[:n], Right(s,n) → s[len-n:]
- At(search,target) → strings.Index + 1
- IIF(cond,a,b) → if/else without function call

Also: Spaces() exported for generated code access.

50K SEEK random: 62ms (Harbour 67ms — Five FASTER!)
82/82 stress PASS. 14 packages ALL PASS.

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

454 lines
9.1 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.EndProcFast()
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.EndProcFast()
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.EndProcFast()
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.EndProcFast()
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.EndProcFast()
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.EndProcFast()
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.EndProcFast()
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.EndProcFast()
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(Spaces(leftPad) + s + Spaces(rightPad))
}
t.RetValue()
}
// --- Math functions ---
// Round rounds a number to specified decimal places.
func Round(t *hbrt.Thread) {
t.Frame(2, 0)
defer t.EndProcFast()
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.EndProcFast()
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.EndProcFast()
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.EndProcFast()
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.EndProcFast()
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.EndProcFast()
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.EndProcFast()
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.EndProcFast()
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.EndProcFast()
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.EndProcFast()
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.EndProcFast()
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.EndProcFast()
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.EndProcFast()
// TODO: integrate with RDD
t.RetInt(0)
}
// File checks if file exists.
func FileFunc(t *hbrt.Thread) {
t.Frame(1, 0)
defer t.EndProcFast()
// 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.EndProcFast()
// 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.EndProcFast()
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.EndProcFast()
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.EndProcFast()
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
}
var buf [20]byte // stack allocation, no heap
i := len(buf)
for n > 0 {
i--
buf[i] = byte('0' + n%10)
n /= 10
}
if neg {
i--
buf[i] = '-'
}
return string(buf[i:])
}