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