- Compiler: PP → Lexer → Parser → Analyzer → Gengo pipeline - Parser: 232/236 (98%) Harbour compatibility, registry-based dispatch - RTL: 351 Harbour-compatible functions - RDD: DBF/NTX/CDX engines with Rushmore bitmap optimization - Go Interop: IMPORT + pkg.Func() + obj:Method() with FastPath (15M calls/sec) - HB_FUNC API: Full Harbour C API compatible Go bridge - Concurrency: SPAWN/LAUNCH/GOROUTINE, <-, WATCH, PARALLEL FOR, ASYNC/AWAIT - Extensions: Multi-return, DEFER, Slice, f-string, Nil-safe ?:, CONST - Macro Compiler: Runtime AST parsing and evaluation - Debugger: TUI debugger with source display, breakpoints, stepping - FRB: Native + Pcode dual mode runtime binary - Tests: 13 packages ALL PASS Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
396 lines
8.7 KiB
Go
396 lines
8.7 KiB
Go
// Copyright (c) 2026 Charles KWON OhJun (charleskwonohjun@gmail.com)
|
|
// All rights reserved.
|
|
|
|
// Random, OS info, process, UTF-8, memo line functions.
|
|
|
|
package hbrtl
|
|
|
|
import (
|
|
"five/hbrt"
|
|
"math/rand"
|
|
"os"
|
|
"os/exec"
|
|
"path/filepath"
|
|
"runtime"
|
|
"strings"
|
|
"time"
|
|
"unicode/utf8"
|
|
)
|
|
|
|
// === Random ===
|
|
|
|
func init() {
|
|
rand.Seed(time.Now().UnixNano())
|
|
}
|
|
|
|
// HB_RANDOM([nMax]) → nRandom (0.0 to 1.0, or 0 to nMax)
|
|
func HbRandom(t *hbrt.Thread) {
|
|
nParams := t.ParamCount()
|
|
t.Frame(nParams, 0)
|
|
defer t.EndProc()
|
|
if nParams >= 1 && !t.Local(1).IsNil() {
|
|
max := t.Local(1).AsNumDouble()
|
|
t.RetDouble(rand.Float64()*max, 0, 0)
|
|
} else {
|
|
t.RetDouble(rand.Float64(), 0, 0)
|
|
}
|
|
}
|
|
|
|
// HB_RANDOMINT(nMin, nMax) → nRandom
|
|
func HbRandomInt(t *hbrt.Thread) {
|
|
t.Frame(2, 0)
|
|
defer t.EndProc()
|
|
min := int(t.Local(1).AsNumInt())
|
|
max := int(t.Local(2).AsNumInt())
|
|
if max <= min {
|
|
t.RetInt(int64(min))
|
|
return
|
|
}
|
|
t.RetInt(int64(min + rand.Intn(max-min+1)))
|
|
}
|
|
|
|
// HB_RANDOMSEED(nSeed) → NIL
|
|
func HbRandomSeed(t *hbrt.Thread) {
|
|
t.Frame(1, 0)
|
|
defer t.EndProc()
|
|
rand.Seed(t.Local(1).AsLong())
|
|
t.RetNil()
|
|
}
|
|
|
|
// HB_RANDSTR(nLen) → cRandomString
|
|
func HbRandStr(t *hbrt.Thread) {
|
|
t.Frame(1, 0)
|
|
defer t.EndProc()
|
|
n := t.Local(1).AsInt()
|
|
b := make([]byte, n)
|
|
for i := range b {
|
|
b[i] = byte(rand.Intn(256))
|
|
}
|
|
t.RetString(string(b))
|
|
}
|
|
|
|
// === OS Info ===
|
|
|
|
// HB_VERSION() → cVersion
|
|
func HbVersion(t *hbrt.Thread) {
|
|
t.Frame(0, 0)
|
|
defer t.EndProc()
|
|
t.RetString("Five 1.0.0 (" + runtime.Version() + " " + runtime.GOOS + "/" + runtime.GOARCH + ")")
|
|
}
|
|
|
|
// HB_COMPILER() → cCompiler
|
|
func HbCompiler(t *hbrt.Thread) {
|
|
t.Frame(0, 0)
|
|
defer t.EndProc()
|
|
t.RetString("Five gengo + " + runtime.Version())
|
|
}
|
|
|
|
// HB_OSNEWLINE() → cNewline
|
|
func HbOsNewLine(t *hbrt.Thread) {
|
|
t.Frame(0, 0)
|
|
defer t.EndProc()
|
|
if runtime.GOOS == "windows" {
|
|
t.RetString("\r\n")
|
|
} else {
|
|
t.RetString("\n")
|
|
}
|
|
}
|
|
|
|
// HB_OSPATHSEPARATOR() → cSep
|
|
func HbOsPathSeparator(t *hbrt.Thread) {
|
|
t.Frame(0, 0)
|
|
defer t.EndProc()
|
|
t.RetString(string(filepath.Separator))
|
|
}
|
|
|
|
// HB_CWD() → cCurrentDir
|
|
func HbCwd(t *hbrt.Thread) {
|
|
t.Frame(0, 0)
|
|
defer t.EndProc()
|
|
dir, _ := os.Getwd()
|
|
t.RetString(dir)
|
|
}
|
|
|
|
// HB_DIRBASE() → cExeDir
|
|
func HbDirBase(t *hbrt.Thread) {
|
|
t.Frame(0, 0)
|
|
defer t.EndProc()
|
|
exe, _ := os.Executable()
|
|
t.RetString(filepath.Dir(exe) + string(filepath.Separator))
|
|
}
|
|
|
|
// HB_PROGNAME() → cProgramName
|
|
func HbProgName(t *hbrt.Thread) {
|
|
t.Frame(0, 0)
|
|
defer t.EndProc()
|
|
if len(os.Args) > 0 {
|
|
t.RetString(filepath.Base(os.Args[0]))
|
|
} else {
|
|
t.RetString("")
|
|
}
|
|
}
|
|
|
|
// HB_USERNAME() → cUsername
|
|
func HbUserName(t *hbrt.Thread) {
|
|
t.Frame(0, 0)
|
|
defer t.EndProc()
|
|
user := os.Getenv("USER")
|
|
if user == "" {
|
|
user = os.Getenv("USERNAME")
|
|
}
|
|
t.RetString(user)
|
|
}
|
|
|
|
// HB_GETHOSTNAME() → cHostname
|
|
func HbGetHostName(t *hbrt.Thread) {
|
|
t.Frame(0, 0)
|
|
defer t.EndProc()
|
|
h, _ := os.Hostname()
|
|
t.RetString(h)
|
|
}
|
|
|
|
// === Process ===
|
|
|
|
// HB_PROCESSRUN(cCommand [, @cStdOut [, @cStdErr]]) → nExitCode
|
|
func HbProcessRun(t *hbrt.Thread) {
|
|
nParams := t.ParamCount()
|
|
t.Frame(nParams, 0)
|
|
defer t.EndProc()
|
|
cmdStr := t.Local(1).AsString()
|
|
var cmd *exec.Cmd
|
|
if runtime.GOOS == "windows" {
|
|
cmd = exec.Command("cmd", "/c", cmdStr)
|
|
} else {
|
|
cmd = exec.Command("sh", "-c", cmdStr)
|
|
}
|
|
output, err := cmd.CombinedOutput()
|
|
if nParams >= 2 {
|
|
// Store stdout in param 2 (simplified — no byref yet)
|
|
_ = output
|
|
}
|
|
if err != nil {
|
|
if exitErr, ok := err.(*exec.ExitError); ok {
|
|
t.RetInt(int64(exitErr.ExitCode()))
|
|
return
|
|
}
|
|
t.RetInt(-1)
|
|
return
|
|
}
|
|
t.RetInt(0)
|
|
}
|
|
|
|
// WAIT [cPrompt] [TO var] — simplified: just wait for keypress
|
|
func WaitCmd(t *hbrt.Thread) {
|
|
nParams := t.ParamCount()
|
|
t.Frame(nParams, 0)
|
|
defer t.EndProc()
|
|
if nParams >= 1 && !t.Local(1).IsNil() {
|
|
s := t.Local(1).AsString()
|
|
os.Stdout.WriteString(s)
|
|
}
|
|
key := ReadKey()
|
|
t.RetString(string(rune(key)))
|
|
}
|
|
|
|
// === UTF-8 String Functions ===
|
|
|
|
// HB_UTF8LEN(cString) → nLen
|
|
func HbUtf8Len(t *hbrt.Thread) {
|
|
t.Frame(1, 0)
|
|
defer t.EndProc()
|
|
t.RetInt(int64(utf8.RuneCountInString(t.Local(1).AsString())))
|
|
}
|
|
|
|
// HB_UTF8SUBSTR(cString, nStart [, nLen]) → cSubString
|
|
func HbUtf8Substr(t *hbrt.Thread) {
|
|
nParams := t.ParamCount()
|
|
t.Frame(nParams, 0)
|
|
defer t.EndProc()
|
|
s := []rune(t.Local(1).AsString())
|
|
start := int(t.Local(2).AsNumInt()) - 1
|
|
if start < 0 { start = 0 }
|
|
if start >= len(s) { t.RetString(""); return }
|
|
length := len(s) - start
|
|
if nParams >= 3 && !t.Local(3).IsNil() {
|
|
length = int(t.Local(3).AsNumInt())
|
|
}
|
|
if start+length > len(s) { length = len(s) - start }
|
|
t.RetString(string(s[start : start+length]))
|
|
}
|
|
|
|
// HB_UTF8LEFT(cString, nLen) → cLeft
|
|
func HbUtf8Left(t *hbrt.Thread) {
|
|
t.Frame(2, 0)
|
|
defer t.EndProc()
|
|
s := []rune(t.Local(1).AsString())
|
|
n := int(t.Local(2).AsNumInt())
|
|
if n > len(s) { n = len(s) }
|
|
t.RetString(string(s[:n]))
|
|
}
|
|
|
|
// HB_UTF8RIGHT(cString, nLen) → cRight
|
|
func HbUtf8Right(t *hbrt.Thread) {
|
|
t.Frame(2, 0)
|
|
defer t.EndProc()
|
|
s := []rune(t.Local(1).AsString())
|
|
n := int(t.Local(2).AsNumInt())
|
|
if n > len(s) { n = len(s) }
|
|
t.RetString(string(s[len(s)-n:]))
|
|
}
|
|
|
|
// HB_UTF8AT(cSearch, cString) → nPos
|
|
func HbUtf8At(t *hbrt.Thread) {
|
|
t.Frame(2, 0)
|
|
defer t.EndProc()
|
|
search := t.Local(1).AsString()
|
|
str := t.Local(2).AsString()
|
|
bytePos := strings.Index(str, search)
|
|
if bytePos < 0 {
|
|
t.RetInt(0)
|
|
return
|
|
}
|
|
t.RetInt(int64(utf8.RuneCountInString(str[:bytePos]) + 1))
|
|
}
|
|
|
|
// HB_STRTOHEX(cString [, cSep]) → cHex
|
|
func HbStrToHex(t *hbrt.Thread) {
|
|
nParams := t.ParamCount()
|
|
t.Frame(nParams, 0)
|
|
defer t.EndProc()
|
|
s := t.Local(1).AsString()
|
|
sep := ""
|
|
if nParams >= 2 && !t.Local(2).IsNil() {
|
|
sep = t.Local(2).AsString()
|
|
}
|
|
var buf strings.Builder
|
|
for i, b := range []byte(s) {
|
|
if i > 0 && sep != "" {
|
|
buf.WriteString(sep)
|
|
}
|
|
buf.WriteByte("0123456789ABCDEF"[b>>4])
|
|
buf.WriteByte("0123456789ABCDEF"[b&0x0F])
|
|
}
|
|
t.RetString(buf.String())
|
|
}
|
|
|
|
// HB_HEXTOSTR(cHex) → cString
|
|
func HbHexToStr(t *hbrt.Thread) {
|
|
t.Frame(1, 0)
|
|
defer t.EndProc()
|
|
hex := t.Local(1).AsString()
|
|
var buf []byte
|
|
for i := 0; i+1 < len(hex); i += 2 {
|
|
hi := hexVal(hex[i])
|
|
lo := hexVal(hex[i+1])
|
|
buf = append(buf, (hi<<4)|lo)
|
|
}
|
|
t.RetString(string(buf))
|
|
}
|
|
|
|
func hexVal(c byte) byte {
|
|
switch {
|
|
case c >= '0' && c <= '9': return c - '0'
|
|
case c >= 'A' && c <= 'F': return c - 'A' + 10
|
|
case c >= 'a' && c <= 'f': return c - 'a' + 10
|
|
}
|
|
return 0
|
|
}
|
|
|
|
// HB_STRFORMAT(cFormat, ...) → cResult — simplified printf
|
|
func HbStrFormat(t *hbrt.Thread) {
|
|
nParams := t.ParamCount()
|
|
t.Frame(nParams, 0)
|
|
defer t.EndProc()
|
|
format := t.Local(1).AsString()
|
|
// Replace %1, %2, %3... with params
|
|
for i := 2; i <= nParams; i++ {
|
|
placeholder := "%" + strings.Repeat("", 0) + string(rune('0'+i-1))
|
|
format = strings.ReplaceAll(format, placeholder, valueToDisplay(t.Local(i)))
|
|
}
|
|
t.RetString(format)
|
|
}
|
|
|
|
// === Memo Line Functions ===
|
|
|
|
// MEMOLINE(cMemo, nLineLen, nLine, nTabSize, lWrap) → cLine
|
|
func MemoLine(t *hbrt.Thread) {
|
|
nParams := t.ParamCount()
|
|
t.Frame(nParams, 0)
|
|
defer t.EndProc()
|
|
memo := t.Local(1).AsString()
|
|
lineLen := 79
|
|
if nParams >= 2 && !t.Local(2).IsNil() { lineLen = t.Local(2).AsInt() }
|
|
nLine := 1
|
|
if nParams >= 3 && !t.Local(3).IsNil() { nLine = t.Local(3).AsInt() }
|
|
lines := wrapMemo(memo, lineLen)
|
|
if nLine >= 1 && nLine <= len(lines) {
|
|
t.RetString(lines[nLine-1])
|
|
} else {
|
|
t.RetString("")
|
|
}
|
|
}
|
|
|
|
// MLCOUNT(cMemo, nLineLen, nTabSize, lWrap) → nLines
|
|
func MlCount(t *hbrt.Thread) {
|
|
nParams := t.ParamCount()
|
|
t.Frame(nParams, 0)
|
|
defer t.EndProc()
|
|
memo := t.Local(1).AsString()
|
|
lineLen := 79
|
|
if nParams >= 2 && !t.Local(2).IsNil() { lineLen = t.Local(2).AsInt() }
|
|
t.RetInt(int64(len(wrapMemo(memo, lineLen))))
|
|
}
|
|
|
|
func wrapMemo(memo string, lineLen int) []string {
|
|
if lineLen <= 0 { lineLen = 79 }
|
|
rawLines := strings.Split(strings.ReplaceAll(memo, "\r\n", "\n"), "\n")
|
|
var result []string
|
|
for _, line := range rawLines {
|
|
if len(line) <= lineLen {
|
|
result = append(result, line)
|
|
} else {
|
|
for len(line) > lineLen {
|
|
result = append(result, line[:lineLen])
|
|
line = line[lineLen:]
|
|
}
|
|
result = append(result, line)
|
|
}
|
|
}
|
|
if len(result) == 0 {
|
|
result = []string{""}
|
|
}
|
|
return result
|
|
}
|
|
|
|
// SOUNDEX(cString) → cSoundex
|
|
func Soundex(t *hbrt.Thread) {
|
|
t.Frame(1, 0)
|
|
defer t.EndProc()
|
|
s := strings.ToUpper(strings.TrimSpace(t.Local(1).AsString()))
|
|
if s == "" { t.RetString("0000"); return }
|
|
code := []byte{s[0], '0', '0', '0'}
|
|
ci := 1
|
|
prev := soundexCode(s[0])
|
|
for i := 1; i < len(s) && ci < 4; i++ {
|
|
c := soundexCode(s[i])
|
|
if c != '0' && c != prev {
|
|
code[ci] = c
|
|
ci++
|
|
}
|
|
prev = c
|
|
}
|
|
t.RetString(string(code))
|
|
}
|
|
|
|
func soundexCode(c byte) byte {
|
|
switch c {
|
|
case 'B','F','P','V': return '1'
|
|
case 'C','G','J','K','Q','S','X','Z': return '2'
|
|
case 'D','T': return '3'
|
|
case 'L': return '4'
|
|
case 'M','N': return '5'
|
|
case 'R': return '6'
|
|
}
|
|
return '0'
|
|
}
|