Files
five/hbrtl/random.go
Charles KWON OhJun 59568f3301 Five v0.9 — Harbour + Go fusion language
- 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>
2026-03-31 09:41:50 +09:00

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'
}