From cf370564f3e488e7febd215ca0baf035484a1c2d Mon Sep 17 00:00:00 2001 From: CharlesKWON Date: Mon, 15 Jun 2026 13:54:03 +0900 Subject: [PATCH] docs(rag): Five knowledge corpus for LLM agents MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit A retrieval-ready knowledge base so an LLM can read/write Five without prior training: overview, syntax, full RTL catalog (from hbrtl/register.go), web/worker idioms (from the solmade app), and a long-tail gotchas file. Every doc has keyword/summary frontmatter; INDEX.md is the routing manifest. Grounded by parallel source exploration; RTL names spot-checked against register.go. The gotchas file is the compounding asset — append new traps. Co-Authored-By: Claude Opus 4.8 (1M context) --- rag/01-overview.md | 55 +++ rag/02-syntax.md | 152 +++++++++ rag/03-rtl-catalog.md | 756 ++++++++++++++++++++++++++++++++++++++++++ rag/04-idioms.md | 172 ++++++++++ rag/05-gotchas.md | 91 +++++ rag/INDEX.md | 20 ++ rag/README.md | 44 +++ 7 files changed, 1290 insertions(+) create mode 100644 rag/01-overview.md create mode 100644 rag/02-syntax.md create mode 100644 rag/03-rtl-catalog.md create mode 100644 rag/04-idioms.md create mode 100644 rag/05-gotchas.md create mode 100644 rag/INDEX.md create mode 100644 rag/README.md diff --git a/rag/01-overview.md b/rag/01-overview.md new file mode 100644 index 0000000..5ad1e7e --- /dev/null +++ b/rag/01-overview.md @@ -0,0 +1,55 @@ +--- +doc: five-overview +title: What Five is — model, philosophy, runtimes +keywords: [five, fivenode, overview, philosophy, token-density, harbour, xbase, compile, go, runtime] +summary: High-level orientation for an LLM about to read or write Five — what the language is, how it compiles, the two runtimes, and the design priorities (token density + Node/Go ecosystem). +--- + +# Five — orientation for an LLM + +**Five** is an xBase/Harbour-compatible programming language that **compiles to Go**. You write `.prg` files in Harbour-family syntax; the Five toolchain generates Go and builds a native binary. + +## Design priorities (why it exists) + +1. **Token density** — express maximum functionality in minimum code. High-level verbs + (`PG_QUERY`, `AP_JSONRESPONSE`, `LLM_CHAT`, `hb_jsonDecode`) collapse what would be + dozens of lines of Go/Node boilerplate into one line. This is a *first-class* goal, + not an aesthetic: dense code means a whole system fits in an LLM context window, costs + fewer tokens per change, and exposes less surface area for bugs. +2. **No ecosystem isolation** — Five reaches the Go/Node ecosystem through the runtime + layer (RTL packages written in Go, blank-imported at build time). So you get dense + business logic *and* rich libraries (Postgres, HTTP, PDF, XLSX, LLM clients). + +## Two distinct runtimes (do not confuse them) + +| Path | What it is | +|------|-----------| +| `/Users/charleskwon/fivenode/fivedev/five` | **The Five language proper** — compiler (`compiler/lexer`, `parser`, `ast`, `gengo` Go codegen, `genpc`), runtime core (`hbrt`), and runtime library (`hbrtl`). CLI: `five run/build/gen `. | +| `/Users/charleskwon/fivenode/fivenode/fivenode_go` | **fivenode** — a separate runtime/toolchain (`fnode` compiler) used to build apps that lean on the Node/Go ecosystem (e.g. the `solmade` app). Distinct codebase from `fivedev/five`. | + +When a task says "implement X in Five (the language)", it targets `fivedev/five`. +When building/running the `solmade` app, the `fnode` toolchain in `fivenode_go` is used. + +## How a program is structured + +- A `.prg` file contains `FUNCTION`/`PROCEDURE`/`STATIC FUNCTION` definitions. +- Execution entry is `Main()` (or `FUNCTION Main()` / `PROCEDURE Main()`). +- In the web app, **file-name routing** maps a URL to a function: `/api/foo-bar.prg` + → the `Main()` in `app/api/foo_bar.prg`, dispatched as `FOO_BAR__MAIN`. (See idioms doc.) + +## Compile model essentials (matters when writing code) + +- The compiler **inlines common string intrinsics** (`LEN`, `CHR`, `ASC`, `SUBSTR`, + `LEFT`, `RIGHT`, `AT`, `PADR`, `PADL`) directly as Go in `compiler/gengo` — they do + NOT go through the `hbrtl` registry at runtime. Changing runtime behavior of these + requires editing the gengo intrinsics (they now call charset-aware `hbrtl.Str*` + helpers). See gotchas doc. +- Strings are **UTF-8 by default**; core string funcs operate on runes. A legacy charset + can be selected (`HB_SETCHARSET`/`HB_CDPSELECT`); env `FIVE_CHARSET`/`HB_CODEPAGE` sets + the initial charset. + +## Where to look for ground truth + +- Grammar/tokens: `compiler/lexer`, `compiler/parser`, `compiler/ast`. +- RTL function names: `hbrtl/register.go` (the registration table). +- Real idioms: the `solmade` app (`/Users/charleskwon/solmade/app/**/*.prg`). diff --git a/rag/02-syntax.md b/rag/02-syntax.md new file mode 100644 index 0000000..832c376 --- /dev/null +++ b/rag/02-syntax.md @@ -0,0 +1,152 @@ +--- +doc: five-syntax +title: Five language syntax reference +keywords: [syntax, grammar, FUNCTION, LOCAL, IF, FOR, DO WHILE, DO CASE, code block, string literal, array, hash, operators, IIF, BEGIN SEQUENCE] +summary: Core Five/xBase syntax an LLM needs to write correct code — declarations, literals, operators, control flow, code blocks. Confirmed against the lexer/parser and real solmade usage. +--- + +# Five syntax reference + +Case-insensitive for keywords AND identifiers (`LOCAL`/`local`, `x`/`X` are the same). +Arrays/strings are **1-based**. Strings do **not** process C escapes (see gotchas). + +## Program structure + +```five +FUNCTION Main() + LOCAL nTotal := CalcSum( 10, 20 ) + ? "Result:", nTotal + RETURN nTotal + +FUNCTION CalcSum( nA, nB ) + RETURN nA + nB + +PROCEDURE ShowMessage() // PROCEDURE returns NIL implicitly + ? "Hello" + RETURN + +STATIC FUNCTION Helper( x ) // STATIC = file-local visibility + RETURN x * 2 +``` + +## Variable declaration + +Declarations come **first**, before executable statements. + +```five +LOCAL x := 10 // init with := +LOCAL a, b, c := 20 // only c is initialized; a,b are NIL +STATIC s_count := 0 // persists across calls (file-level or in-func) +PUBLIC nGlobal := 100 // runtime memvar, global +PRIVATE nScoped := 50 // runtime memvar, dynamic scope +``` + +Pass-by-reference uses `@` at the call site: + +```five +FUNCTION Inc( x ) + x := x + 1 + RETURN NIL +// ... +LOCAL n := 10 +Inc( @n ) // n is now 11 +``` + +## Literals + +```five +LOCAL n := 42 // number +LOCAL f := 3.14 +LOCAL c1 := "double quotes" // string +LOCAL c2 := 'single quotes' // string (equivalent; NO escape processing in either) +LOCAL c3 := [bracket string] // string; supports nested [ ] +LOCAL lT := .T. // logical true (.t. ok) +LOCAL lF := .F. // logical false +LOCAL x := NIL // null/undefined +LOCAL a := { 1, 2, 3 } // array literal, 1-based +LOCAL h := { "k" => "v" } // hash literal +LOCAL e := {=>} // empty hash +LOCAL b := {| p1, p2 | p1 + p2 } // code block (closure); no-arg form: {|| expr} +``` + +Access: `a[1]` (first element), `h["k"]`, `h["new"] := 1` (add/update). + +## Operators + +```five +:= += -= *= /= %= **= // assignment / compound assignment +== = // equality (= also assigns in some contexts; prefer == for compare) +!= <> # // not equal (three spellings) +< > <= >= +$ // substring containment: "ell" $ "hello" => .T. ++ - * / % ** ^ // arithmetic (** and ^ = power); + also concatenates strings +.AND. .OR. .NOT. // logical (short-circuit); aliases: && || ! +++ -- // increment / decrement +``` + +## Control flow + +```five +IF cond + // ... +ELSEIF cond2 + // ... +ELSE + // ... +ENDIF + +DO WHILE cond + // ... + IF skip ; LOOP ; ENDIF // LOOP = continue + IF stop ; EXIT ; ENDIF // EXIT = break +ENDDO + +FOR i := 1 TO 10 // optional STEP: FOR i := 10 TO 1 STEP -1 + ? i +NEXT + +FOR EACH item IN aArray + ? item +NEXT + +DO CASE +CASE x == 1 + // ... +CASE x == 2 + // ... +OTHERWISE + // ... +ENDCASE + +BEGIN SEQUENCE // structured error handling + risky() +RECOVER + ? "error caught" +END SEQUENCE +``` + +Inline conditional (expression, not statement): + +```five +cResult := IIF( x > 10, "big", "small" ) // Iif() also works +``` + +## Statement continuation & comments + +```five +LOCAL s := "a" + ; // trailing ';' continues the statement on the next line + "b" + ; + "c" + +// single-line comment +/* multi-line + comment */ +``` + +## Advanced features (verify against parser before relying on these) + +The compiler/parser also supports OOP (`CLASS ... ENDCLASS`, `METHOD ... CLASS X`, +`::member`, `INHERIT FROM`), and Go-inspired concurrency (`GO {|| ...}`, channels `<-`, +`WATCH`/`ENDWATCH` select, `DEFER`). These appear in the grammar but are uncommon in the +solmade app; if you need them, confirm exact syntax in `compiler/parser` and `examples/` +first rather than assuming Harbour-standard behavior. diff --git a/rag/03-rtl-catalog.md b/rag/03-rtl-catalog.md new file mode 100644 index 0000000..1be36bd --- /dev/null +++ b/rag/03-rtl-catalog.md @@ -0,0 +1,756 @@ +--- +doc: five-rtl-catalog +title: Five RTL (runtime library) function catalog +keywords: [rtl, builtin, functions, string, array, hash, json, date, regex, charset, hb_, Len, SubStr, hb_jsonDecode, PG, LLM] +summary: Catalog of runtime-library functions callable from Five PRG, grouped by category, extracted from hbrtl/register.go. Verify exotic/less-common entries against register.go. +--- + +> Source of truth: `/Users/charleskwon/fivenode/fivedev/five/hbrtl/register.go` and impl files. Names listed are as REGISTERED (callable from PRG, case-insensitive). For the common, heavily-used set (Len/SubStr/Left/Right/At/Upper/Lower/AllTrim/PadL/PadR/StrTran/Chr/Asc/Val/Str/hb_NToS/hb_CStr/hb_HGetDef/hb_jsonDecode/hb_jsonEncode) signatures are reliable. For rarer entries, confirm signature in register.go before relying on it. + +# Five Runtime Library (hbrtl) Function Catalog + +## String Functions + +| Function | Signature | Notes | +|----------|-----------|-------| +| **LEN** | `LEN(cString \| aArray \| hHash) → nLength` | UTF-8 rune-aware (when charset=UTF8, default); counts runes, not bytes | +| **SUBSTR** | `SUBSTR(cString, nStart [, nLen]) → cSubstring` | 1-based index; UTF-8 rune-aware; negative start = from end | +| **LEFT** | `LEFT(cString, nLen) → cLeft` | Returns leftmost n characters; UTF-8 rune-aware | +| **RIGHT** | `RIGHT(cString, nLen) → cRight` | Returns rightmost n characters; UTF-8 rune-aware | +| **AT** | `AT(cSearch, cTarget) → nPos` | 1-based position of first occurrence; UTF-8 rune-aware; returns 0 if not found | +| **RAT** | `RAT(cSearch, cTarget [, nOccurrence]) → nPos` | 1-based position of LAST occurrence; counts from right | +| **UPPER** | `UPPER(cString) → cUpper` | Converts to uppercase | +| **LOWER** | `LOWER(cString) → cLower` | Converts to lowercase | +| **ALLTRIM** | `ALLTRIM(cString) → cTrimmed` | Removes leading and trailing spaces | +| **LTRIM** | `LTRIM(cString) → cTrimmed` | Removes leading spaces only | +| **RTRIM** | `RTRIM(cString) → cTrimmed` | Removes trailing spaces (TRIM is alias) | +| **PADR** | `PADR(xValue, nLen [, cFill]) → cPadded` | Right-pads with spaces or fill character | +| **PADL** | `PADL(xValue, nLen [, cFill]) → cPadded` | Left-pads with spaces or fill character | +| **PADC** | `PADC(xValue, nLen) → cCentered` | Center-pads with spaces | +| **SPACE** | `SPACE(nCount) → cSpaces` | Returns string of n spaces | +| **REPLICATE** | `REPLICATE(cString, nTimes) → cRepeated` | Repeats string n times | +| **CHR** | `CHR(nCode) → cChar` | UTF-8 codepoint → character (UTF-8 mode); byte value (legacy charset) | +| **ASC** | `ASC(cString) → nCode` | Returns codepoint of first character; UTF-8 rune-aware | +| **STRTRAN** | `STRTRAN(cString, cSearch, cReplace) → cResult` | Replaces all occurrences | +| **STUFF** | `STUFF(cString, nStart, nDel, cInsert) → cResult` | Inserts/replaces characters at position (1-based) | +| **STR** | `STR(nValue [, nWidth [, nDec]]) → cString` | Converts number to string; right-aligned with spaces | +| **VAL** | `VAL(cString) → nValue` | Converts string to numeric; trims and parses int/float | +| **STRZERO** | `STRZERO(nValue, nLen [, nDec]) → cString` | Converts number to string padded with leading zeros | +| **HB_STRREPLACE** | `HB_STRREPLACE(cString, cSearch, cReplace) → cResult` | Replaces all (alias for STRTRAN) | +| **HB_NTOS** | `HB_NTOS(nValue) → cString` | Converts number to string without leading spaces | +| **HB_CSTR** | `HB_CSTR(xValue) → cString` | Converts any value to string representation | +| **HB_VALTOSTR** | `HB_VALTOSTR(xValue) → cString` | Converts value to display string | +| **HB_VALTOEXP** | `HB_VALTOEXP(xValue) → cExpr` | Returns Harbour expression representation (e.g., ".T.", "123", `"text"`) | +| **MEMOREAD** | `MEMOREAD(cFileName) → cContents` | Reads entire file into string | +| **MEMOWRIT** | `MEMOWRIT(cFileName, cString [, lAddEOF]) → lSuccess` | Writes string to file | +| **MEMOTRAN** | `MEMOTRAN(cMemoText [, cSoftCR [, cHardCR]]) → cString` | Replaces soft/hard CR in memo fields | +| **DESCEND** | `DESCEND(cString \| nNumber \| dDate) → xDescended` | Flips each byte (strings); negates (numbers); max-date (dates) | +| **HARDCR** | `HARDCR(cMemoText) → cString` | Converts soft CR to hard CR | + +### UTF-8 String Functions + +| Function | Signature | Notes | +|----------|-----------|-------| +| **HB_UTF8LEN** | `HB_UTF8LEN(cString) → nRuneCount` | Length in UTF-8 runes | +| **HB_UTF8SUBSTR** | `HB_UTF8SUBSTR(cString, nStart [, nLen]) → cSubstring` | UTF-8 rune-based substring extraction | +| **HB_UTF8LEFT** | `HB_UTF8LEFT(cString, nLen) → cLeft` | UTF-8 rune-based left extraction | +| **HB_UTF8RIGHT** | `HB_UTF8RIGHT(cString, nLen) → cRight` | UTF-8 rune-based right extraction | +| **HB_UTF8AT** | `HB_UTF8AT(cSearch, cTarget) → nPos` | UTF-8 rune-based position (1-based) | +| **HB_STRTOHEX** | `HB_STRTOHEX(cString) → cHex` | Converts string to hex representation | +| **HB_HEXTOSTR** | `HB_HEXTOSTR(cHex) → cString` | Converts hex to string | +| **HB_UTF8TOSTR** | `HB_UTF8TOSTR(cUTF8) → cString` | Decodes UTF-8 to internal string | +| **HB_STRTOUTF8** | `HB_STRTOUTF8(cString) → cUTF8` | Encodes to UTF-8 | +| **HB_STRFORMAT** | `HB_STRFORMAT(cFormat, ...) → cString` | Printf-style formatting | + +### String Search/Matching + +| Function | Signature | Notes | +|----------|-----------|-------| +| **HB_AT** | `HB_AT(cSearch, cTarget) → nPos` | Alias for AT() | +| **HB_RAT** | `HB_RAT(cSearch, cTarget [, nOcc]) → nPos` | Alias for RAT() | +| **HB_ATI** | `HB_ATI(cSearch, cTarget) → nPos` | Case-insensitive AT() | +| **HB_ATX** | `HB_ATX(cSearch, cTarget) → nPos` | Extended AT (with wildcard support) | +| **HB_LEFTEQ** | `HB_LEFTEQ(cLeft, cRight) → lEqual` | Case-sensitive left-part equality | +| **HB_LEFTEQI** | `HB_LEFTEQI(cLeft, cRight) → lEqual` | Case-insensitive left-part equality | +| **HB_WILDMATCH** | `HB_WILDMATCH(cPattern, cString) → lMatch` | Wildcard pattern match (case-sensitive) | +| **HB_WILDMATCHI** | `HB_WILDMATCHI(cPattern, cString) → lMatch` | Wildcard pattern match (case-insensitive) | +| **HB_STRISUTF8** | `HB_STRISUTF8(cString) → lValid` | Checks if string is valid UTF-8 | +| **HB_STRDECODESCAPE** | `HB_STRDECODESCAPE(cString) → cDecoded` | Decodes escape sequences | +| **HB_STRXOR** | `HB_STRXOR(cString, cKey) → cResult` | XOR encryption/decryption | + +## Array Functions + +| Function | Signature | Notes | +|----------|-----------|-------| +| **AADD** | `AADD(aArray, xValue) → aArray` | Appends element; returns modified array | +| **ADEL** | `ADEL(aArray, nPos) → aArray` | Deletes element at position; shifts left | +| **AINS** | `AINS(aArray, nPos) → aArray` | Inserts NIL at position; shifts right | +| **ASIZE** | `ASIZE(aArray, nLen) → aArray` | Resizes array (pads with NIL or truncates) | +| **ACLONE** | `ACLONE(aArray) → aNewArray` | Shallow copy of array | +| **HB_DEEPCOPY** / **HBDEEPCLONE** | `HB_DEEPCOPY(xValue) → xCloned` | Deep recursive clone (arrays & hashes); scalars unchanged | +| **ACOPY** | `ACOPY(aSource, aDest [, nStart [, nCount [, nTargetPos]]]) → aDest` | Copies elements from source to dest | +| **AFILL** | `AFILL(aArray, xValue [, nStart [, nCount]]) → aArray` | Fills array with value | +| **ASORT** | `ASORT(aArray [, nStart [, nCount [, bBlock]]]) → aArray` | Sorts array; optional block comparator | +| **AEVAL** | `AEVAL(aArray, bBlock [, nStart [, nCount]]) → aArray` | Evaluates block for each element | +| **ASCAN** | `ASCAN(aArray, xValue [, nStart [, nCount]]) → nPos` | Scans array for value; returns 1-based position or 0 | +| **ATAIL** | `ATAIL(aArray) → xValue` | Returns last element | +| **HB_ATOKENS** | `HB_ATOKENS(cString [, cDelim]) → aTokens` | Splits string by delimiter (default: whitespace) | + +## Hash Functions + +| Function | Signature | Notes | +|----------|-----------|-------| +| **HB_HASH** | `HB_HASH(key1, val1, key2, val2, ...) → hHash` | Creates hash from alternating key-value pairs | +| **HB_HGET** | `HB_HGET(hHash, xKey) → xValue` | Retrieves value by key; returns NIL if missing | +| **HB_HGETDEF** | `HB_HGETDEF(hHash, xKey, xDefault) → xValue` | Retrieves with default fallback | +| **HB_HSET** | `HB_HSET(hHash, xKey, xValue) → hHash` | Sets key-value; returns hash | +| **HB_HDEL** | `HB_HDEL(hHash, xKey) → hHash` | Deletes key; returns hash | +| **HB_HHASKEY** | `HB_HHASKEY(hHash, xKey) → lExists` | Checks key existence | +| **HB_HKEYS** | `HB_HKEYS(hHash) → aKeys` | Returns array of keys | +| **HB_HVALUES** | `HB_HVALUES(hHash) → aValues` | Returns array of values | +| **HB_HPOS** | `HB_HPOS(hHash, xKey) → nPos` | Returns 1-based position of key (0 if not found) | +| **HB_HKEYAT** | `HB_HKEYAT(hHash, nPos) → xKey` | Returns key at 1-based position | +| **HB_HVALUEAT** | `HB_HVALUEAT(hHash, nPos) → xValue` | Returns value at 1-based position | +| **HB_HCLONE** | `HB_HCLONE(hHash) → hNewHash` | Shallow copy of hash | + +## JSON Functions + +| Function | Signature | Notes | +|----------|-----------|-------| +| **HB_JSONENCODE** | `HB_JSONENCODE(xValue [, lHumanReadable]) → cJSON` | Encodes value to JSON string; optional pretty-print | +| **HB_JSONDECODE** | `HB_JSONDECODE(cJSON [, @xOut]) → xValue \| nBytesParsed` | Decodes JSON; 2-arg form sets @xOut and returns bytes parsed | +| **JSONPRETTY** | `JSONPRETTY(xValue [, cIndent]) → cPrettyJSON` | Pretty-prints JSON with indentation | +| **JSONTO** | `JSONTO(xValue, cFile) → lSuccess` | Writes JSON to file | +| **JSONFROM** | `JSONFROM(cFile) → xValue` | Reads JSON from file | +| **JSONPATH** | `JSONPATH(xValue, cPath) → xResult` | Extracts value from JSON path (e.g., `$.key.sub[0]`) | +| **JSONMERGE** | `JSONMERGE(hDest, hSrc) → hMerged` | Deep merges two hashes | +| **JSONTYPE** | `JSONTYPE(cJSON) → cType` | Returns JSON value type as string | +| **JSONVALID** | `JSONVALID(cJSON) → lValid` | Validates JSON syntax | +| **JSONHTTPGET** | `JSONHTTPGET(cURL [, nTimeout]) → hResult` | HTTP GET + automatic JSON decode | +| **JSONHTTPPOST** | `JSONHTTPPOST(cURL, xBody [, nTimeout]) → hResult` | HTTP POST + JSON body encode/decode | + +## Type Checking / Introspection + +| Function | Signature | Notes | +|----------|-----------|-------| +| **VALTYPE** | `VALTYPE(xValue) → cType` | Returns 1-char type: "U"(NIL), "L"(logical), "N"(numeric), "C"(string), "D"(date), "A"(array), "H"(hash), "B"(block), "O"(object), "P"(pointer), "S"(symbol) | +| **TYPE** | `TYPE(cSymbol) → cType` | Returns type of symbol/function | +| **EMPTY** | `EMPTY(xValue) → lEmpty` | Checks if value is "empty": NIL, .F., 0, "", date(0), empty array/hash | +| **HB_ISARRAY** / **ISARRAY** | `HB_ISARRAY(xValue) → lArray` | Checks if array | +| **HB_ISHASH** | `HB_ISHASH(xValue) → lHash` | Checks if hash | +| **HB_ISSTRING** / **HB_ISCHAR** / **ISCHARACTER** / **ISMEMO** | `HB_ISSTRING(xValue) → lString` | Checks if string (ISMEMO is alias) | +| **HB_ISNUMERIC** / **ISNUMBER** / **ISNUMERIC** | `HB_ISNUMERIC(xValue) → lNumeric` | Checks if numeric | +| **HB_ISLOGICAL** / **ISLOGICAL** | `HB_ISLOGICAL(xValue) → lLogical` | Checks if logical (.T. or .F.) | +| **HB_ISDATE** / **ISDATE** | `HB_ISDATE(xValue) → lDate` | Checks if date | +| **HB_ISDATETIME** | `HB_ISDATETIME(xValue) → lTimestamp` | Checks if timestamp | +| **HB_ISBLOCK** / **ISBLOCK** | `HB_ISBLOCK(xValue) → lBlock` | Checks if code block | +| **HB_ISOBJECT** / **ISOBJECT** | `HB_ISOBJECT(xValue) → lObject` | Checks if object instance | +| **HB_ISNIL** / **ISNIL** | `HB_ISNIL(xValue) → lNil` | Checks if NIL | +| **HB_ISPOINTER** | `HB_ISPOINTER(xValue) → lPointer` | Checks if pointer (Go native) | +| **HB_ISEVALITEM** | `HB_ISEVALITEM(xValue) → lEvalable` | Checks if block or symbol | + +## Date/Time Functions + +| Function | Signature | Notes | +|----------|-----------|-------| +| **DATE** | `DATE() → dToday` | Returns today's date (Julian format) | +| **TIME** | `TIME() → nSeconds` | Returns seconds elapsed since midnight | +| **YEAR** | `YEAR(dDate) → nYear` | Extracts year from date | +| **MONTH** | `MONTH(dDate) → nMonth` | Extracts month (1-12) from date | +| **DAY** | `DAY(dDate) → nDay` | Extracts day of month (1-31) from date | +| **DOW** | `DOW(dDate) → nDayOfWeek` | Returns day of week (1=Sunday...7=Saturday) | +| **DTOC** | `DTOC(dDate) → cString` | Converts date to string per SET DATE FORMAT | +| **DTOS** | `DTOS(dDate) → cISO` | Converts date to ISO string (YYYYMMDD) | +| **STOD** | `STOD(cISO) → dDate` | Parses ISO string (YYYYMMDD) to date | +| **CTOD** | `CTOD(cDateString) → dDate` | Parses string per SET DATE FORMAT to date | +| **CDOW** | `CDOW(dDate) → cDayName` | Returns day name ("Monday", "Tuesday", etc.) | +| **CMONTH** | `CMONTH(dDate) → cMonthName` | Returns month name ("January", etc.) | +| **SECONDS** | `SECONDS() → nSeconds` | Returns seconds since midnight (current time) | +| **SETDATEFORMAT** | `SETDATEFORMAT(cFormat) → cNil` | Sets date display format (e.g., "MM/DD/YY", "DD-MM-YYYY") | +| **SETEPOCH** | `SETEPOCH(nYear) → cNil` | Sets 2-digit year base (default: 1900) | +| **SETCENTURY** | `SETCENTURY(lOnOff \| cOnOff) → cNil` | Toggles 4-digit year display | + +### Timestamp Functions (Extended) + +| Function | Signature | Notes | +|----------|-----------|-------| +| **HB_DATETIME** | `HB_DATETIME(nJulian, nMs) → tTimestamp` | Creates timestamp from date + milliseconds | +| **HB_HOUR** | `HB_HOUR(tTimestamp) → nHour` | Extracts hours (0-23) | +| **HB_MINUTE** | `HB_MINUTE(tTimestamp) → nMinute` | Extracts minutes (0-59) | +| **HB_SEC** / **HB_SECOND** | `HB_SEC(tTimestamp) → nSec` | Extracts seconds (0-59) | +| **HB_TTOC** | `HB_TTOC(tTimestamp [, cFormat]) → cString` | Converts timestamp to string | +| **HB_CTOT** | `HB_CTOT(cString [, cFormat]) → tTimestamp` | Parses string to timestamp | +| **HB_TTOS** | `HB_TTOS(tTimestamp) → cISO` | Timestamp to ISO string | +| **HB_STOT** | `HB_STOT(cISO) → tTimestamp` | ISO string to timestamp | +| **HB_TTOD** | `HB_TTOD(tTimestamp) → dDate` | Extracts date from timestamp | +| **HB_DTOT** | `HB_DTOT(dDate) → tTimestamp` | Creates timestamp from date at 00:00:00 | +| **HB_TTOHOUR** | `HB_TTOHOUR(tTimestamp) → nHour` | Alias for HB_HOUR | +| **HB_TTOMIN** | `HB_TTOMIN(tTimestamp) → nMin` | Alias for HB_MINUTE | +| **HB_TTOSEC** | `HB_TTOSEC(tTimestamp) → nSec` | Alias for HB_SEC | +| **HB_TTOMSEC** | `HB_TTOMSEC(tTimestamp) → nMs` | Extracts milliseconds portion | +| **HB_TTON** | `HB_TTON(tTimestamp) → nTotal` | Converts timestamp to numeric seconds | +| **HB_NTOT** | `HB_NTOT(nSeconds) → tTimestamp` | Converts numeric seconds to timestamp | +| **HB_NTOHOUR** | `HB_NTOHOUR(nSeconds) → nHour` | Extracts hours from numeric seconds | +| **HB_NTOMIN** | `HB_NTOMIN(nSeconds) → nMin` | Extracts minutes from numeric seconds | +| **HB_NTOSEC** | `HB_NTOSEC(nSeconds) → nSec` | Extracts seconds from numeric seconds | +| **HB_WEEK** | `HB_WEEK(dDate) → nWeek` | Returns ISO week number (1-53) | +| **HB_CDAY** | `HB_CDAY(dDate) → cDay` | Returns day name string | +| **DAYS** | `DAYS(dDate1, dDate2) → nDays` | Difference in days | +| **ELAPTIME** | `ELAPTIME(nStart, nEnd) → cElapsed` | Formats elapsed time (HH:MM:SS) | +| **AMPM** | `AMPM(nSeconds) → cTime` | Formats time as AM/PM | +| **SECS** | `SECS(cTime) → nSeconds` | Parses HH:MM:SS to seconds | +| **HB_MILLISECONDS** | `HB_MILLISECONDS() → nMs` | Returns current time in milliseconds | + +## Regular Expression Functions + +| Function | Signature | Notes | +|----------|-----------|-------| +| **HB_REGEXCOMP** | `HB_REGEXCOMP(cPattern [, lCaseSensitive]) → pRegex` | Compiles regex to pointer; lCaseSensitive=.F. adds (?i) | +| **HB_REGEX** | `HB_REGEX(cPattern \| pRegex, cString [, lCaseSensitive]) → aSubmatches` | Returns array of submatches (match + capture groups); empty array if no match | +| **HB_REGEXMATCH** | `HB_REGEXMATCH(cPattern \| pRegex, cString [, lCaseSensitive]) → lMatch` | Boolean match test | +| **HB_REGEXALL** | `HB_REGEXALL(cPattern \| pRegex, cString [, lCaseSensitive]) → aMatches` | Returns array of all non-overlapping matches | +| **HB_REGEXSPLIT** | `HB_REGEXSPLIT(cPattern \| pRegex, cString [, lCaseSensitive]) → aTokens` | Splits string by pattern; returns array | +| **HB_REGEXREPLACE** | `HB_REGEXREPLACE(cPattern \| pRegex, cString, cReplace [, lCaseSensitive]) → cResult` | Replaces all matches with replacement string | + +## Math Functions + +| Function | Signature | Notes | +|----------|-----------|-------| +| **ABS** | `ABS(nValue) → nAbsolute` | Absolute value | +| **INT** | `INT(nValue) → nInteger` | Integer part (truncates toward zero) | +| **ROUND** | `ROUND(nValue, nDecimals) → nRounded` | Rounds to n decimal places | +| **MAX** | `MAX(xValue1, xValue2) → xMax` | Maximum of two values (numeric, date, or fallback) | +| **MIN** | `MIN(xValue1, xValue2) → xMin` | Minimum of two values | +| **SQRT** | `SQRT(nValue) → nRoot` | Square root | +| **LOG** | `LOG(nValue) → nLog` | Natural logarithm | +| **EXP** | `EXP(nValue) → nExponential` | e^n | +| **MOD** | `MOD(nDividend, nDivisor) → nRemainder` | Modulo operation | + +## Character Classification + +| Function | Signature | Notes | +|----------|-----------|-------| +| **ISDIGIT** | `ISDIGIT(cChar) → lDigit` | Checks if character is 0-9 | +| **ISALPHA** | `ISALPHA(cChar) → lAlpha` | Checks if letter (A-Z, a-z) | +| **ISALNUM** | `ISALNUM(cChar) → lAlnum` | Checks if alphanumeric | +| **ISUPPER** | `ISUPPER(cChar) → lUpper` | Checks if uppercase | +| **ISLOWER** | `ISLOWER(cChar) → lLower` | Checks if lowercase | +| **ISSPACE** | `ISSPACE(cChar) → lSpace` | Checks if whitespace | +| **HB_ASCIIISALPHA** | `HB_ASCIIISALPHA(cChar) → lAlpha` | ASCII-only alpha check | +| **HB_ASCIIISDIGIT** | `HB_ASCIIISDIGIT(cChar) → lDigit` | ASCII-only digit check | +| **HB_ASCIIISLOWER** | `HB_ASCIIISLOWER(cChar) → lLower` | ASCII-only lowercase check | +| **HB_ASCIIISUPPER** | `HB_ASCIIISUPPER(cChar) → lUpper` | ASCII-only uppercase check | +| **HB_ASCIIUPPER** | `HB_ASCIIUPPER(cString) → cUpper` | ASCII-only uppercase conversion | +| **HB_ASCIILOWER** | `HB_ASCIILOWER(cString) → cLower` | ASCII-only lowercase conversion | + +## Encoding / Hashing (Cryptographic) + +| Function | Signature | Notes | +|----------|-----------|-------| +| **HB_MD5** | `HB_MD5(cString) → cMD5Hex` | MD5 hash (Go stdlib) | +| **HB_SHA256** | `HB_SHA256(cString) → cSHA256Hex` | SHA-256 hash (Go stdlib) | +| **HB_CRC32** | `HB_CRC32(cString) → nCRC32` | CRC32 checksum | +| **HB_BASE64ENCODE** | `HB_BASE64ENCODE(cString) → cBase64` | Encodes to Base64 | +| **HB_BASE64DECODE** | `HB_BASE64DECODE(cBase64) → cString` | Decodes from Base64 | +| **HB_NUMTOHEX** | `HB_NUMTOHEX(nNumber) → cHex` | Converts number to hex string | +| **HB_HEXTONUM** | `HB_HEXTONUM(cHex) → nNumber` | Converts hex string to number | + +## Bit Operations (Go Native) + +| Function | Signature | Notes | +|----------|-----------|-------| +| **HB_BITAND** | `HB_BITAND(nVal1, nVal2) → nResult` | Bitwise AND | +| **HB_BITOR** | `HB_BITOR(nVal1, nVal2) → nResult` | Bitwise OR | +| **HB_BITXOR** | `HB_BITXOR(nVal1, nVal2) → nResult` | Bitwise XOR | +| **HB_BITNOT** | `HB_BITNOT(nValue) → nResult` | Bitwise NOT | +| **HB_BITSHIFT** | `HB_BITSHIFT(nValue, nShift) → nResult` | Bit shift (positive=left, negative=right) | +| **HB_BITTEST** | `HB_BITTEST(nValue, nBit) → lSet` | Tests if bit is set | +| **HB_BITSET** | `HB_BITSET(nValue, nBit) → nResult` | Sets bit | +| **HB_BITRESET** | `HB_BITRESET(nValue, nBit) → nResult` | Resets bit | + +## Binary Conversion + +| Function | Signature | Notes | +|----------|-----------|-------| +| **BIN2I** | `BIN2I(cBinary) → nShortInt` | Binary string (2 bytes) → short integer | +| **BIN2W** | `BIN2W(cBinary) → nWord` | Binary string (2 bytes) → word | +| **BIN2L** | `BIN2L(cBinary) → nLong` | Binary string (4 bytes) → long integer | +| **I2BIN** | `I2BIN(nShortInt) → cBinary` | Short integer → 2-byte binary | +| **W2BIN** | `W2BIN(nWord) → cBinary` | Word → 2-byte binary | +| **L2BIN** | `L2BIN(nLong) → cBinary` | Long integer → 4-byte binary | + +## Charset / Codepage Management + +| Function | Signature | Notes | +|----------|-----------|-------| +| **HB_GETCHARSET** | `HB_GETCHARSET() → cName` | Returns active charset (e.g., "UTF8", "CP949") | +| **HB_SETCHARSET** | `HB_SETCHARSET([cName]) → cPrev` | Sets charset; returns previous; no-arg queries current | +| **HB_CDPSELECT** | `HB_CDPSELECT([cName]) → cPrev` | Harbour alias for HB_SETCHARSET | +| **HB_TRANSLATE** | `HB_TRANSLATE(cString, cFrom, cTo) → cConverted` | Converts string between charsets | + +**Note:** When charset is UTF-8 (default), core string functions (LEN, CHR, ASC, SUBSTR, LEFT, RIGHT, AT, PADR, PADL) operate on UTF-8 runes (characters), not bytes. When a non-UTF-8 charset is active (e.g., CP949), these same functions operate on bytes in that encoding. + +## File I/O Functions + +| Function | Signature | Notes | +|----------|-----------|-------| +| **FOPEN** | `FOPEN(cFileName, cMode) → nHandle` | Opens file; modes: "R"(read), "W"(write), "A"(append); returns -1 on error | +| **FCREATE** | `FCREATE(cFileName [, nAttr]) → nHandle` | Creates/truncates file; returns handle or -1 | +| **FCLOSE** | `FCLOSE(nHandle) → lSuccess` | Closes file handle | +| **FREAD** | `FREAD(nHandle, nBytes) → cData` | Reads up to n bytes | +| **FWRITE** | `FWRITE(nHandle, cData [, nBytes]) → nBytesWritten` | Writes data; returns bytes written | +| **FSEEK** | `FSEEK(nHandle, nOffset [, nFrom]) → nNewPos` | Seeks in file; nFrom: 0=start, 1=current, 2=end | +| **FERASE** | `FERASE(cFileName) → lSuccess` | Deletes file | +| **FRENAME** | `FRENAME(cOld, cNew) → lSuccess` | Renames file | +| **HB_FILEEXISTS** | `HB_FILEEXISTS(cFileName) → lExists` | Checks if file exists | +| **HB_FILEDELETE** | `HB_FILEDELETE(cFileName) → lSuccess` | Deletes file | +| **HB_FILEMATCH** | `HB_FILEMATCH(cPattern, cFileName) → lMatch` | Wildcard file match | +| **HB_FSIZE** | `HB_FSIZE(cFileName) → nBytes` | File size in bytes | +| **HB_FCOPY** | `HB_FCOPY(cSource, cDest) → lSuccess` | Copies file | +| **HB_FEOF** | `HB_FEOF(nHandle) → lEOF` | Checks if at end of file | +| **HB_FCOMMIT** | `HB_FCOMMIT(nHandle) → lSuccess` | Flushes file buffer | +| **HB_FREADLEN** | `HB_FREADLEN(nHandle) → nLen` | Reads line length without consuming | +| **HB_FGETATTR** | `HB_FGETATTR(cFileName) → cAttr` | Gets file attributes | +| **HB_FSETATTR** | `HB_FSETATTR(cFileName, cAttr) → lSuccess` | Sets file attributes | +| **HB_FGETDATETIME** | `HB_FGETDATETIME(cFileName) → tTimestamp` | Gets file modification time | +| **HB_FSETDATETIME** | `HB_FSETDATETIME(cFileName, tTime) → lSuccess` | Sets file modification time | +| **HB_FLOCK** | `HB_FLOCK(nHandle, nMode) → lSuccess` | Locks file region | +| **HB_FUNLOCK** | `HB_FUNLOCK(nHandle) → lSuccess` | Unlocks file region | +| **HB_FNAMEEXISTS** | `HB_FNAMEEXISTS(cPath) → lExists` | Checks file/directory existence | +| **HB_FNAMEEXTSET** | `HB_FNAMEEXTSET(cPath, cExt) → cNewPath` | Sets file extension | +| **HB_FNAMENAMEEXT** | `HB_FNAMENAMEEXT(cPath) → cNameExt` | Extracts filename + extension | + +## Directory/Path Functions + +| Function | Signature | Notes | +|----------|-----------|-------| +| **CURDIR** | `CURDIR() → cPath` | Current working directory | +| **DIRCHANGE** | `DIRCHANGE(cPath) → lSuccess` | Changes current directory | +| **DIRECTORY** | `DIRECTORY(cMask) → aFiles` | Returns array of files matching mask (directory listing) | +| **DIRMAKE** | `DIRMAKE(cPath) → lSuccess` | Creates directory | +| **DIRREMOVE** | `DIRREMOVE(cPath) → lSuccess` | Removes directory | +| **HB_DIREXISTS** | `HB_DIREXISTS(cPath) → lExists` | Checks if directory exists | +| **HB_DIRCREATE** | `HB_DIRCREATE(cPath) → lSuccess` | Creates directory (alias for DIRMAKE) | +| **HB_FTEMPCREATE** | `HB_FTEMPCREATE() → cTempFile` | Creates unique temp file path | +| **HB_FNAMESPLIT** | `HB_FNAMESPLIT(cPath [, @cDir, @cName, @cExt]) → lSuccess` | Splits path into components | +| **HB_FNAMEDIR** | `HB_FNAMEDIR(cPath) → cDir` | Extracts directory portion | +| **HB_FNAMEEXT** | `HB_FNAMEEXT(cPath) → cExt` | Extracts extension | +| **HB_FNAMENAME** | `HB_FNAMENAME(cPath) → cName` | Extracts filename without extension | +| **HB_FNAMEMERGE** | `HB_FNAMEMERGE(cDir, cName, cExt) → cPath` | Merges path components | +| **HB_DIRTEMP** | `HB_DIRTEMP() → cTempDir` | Returns system temp directory | +| **HB_DISKSPACE** | `HB_DISKSPACE(cPath [, nType]) → nBytes` | Disk space info | +| **DISKSPACE** | `DISKSPACE(cPath) → nBytes` | Disk free space (alias) | +| **HB_DIRBASE** | `HB_DIRBASE() → cBaseDir` | Application base directory | + +## OS / Environment Functions + +| Function | Signature | Notes | +|----------|-----------|-------| +| **GETENV** / **HB_GETENV** | `GETENV(cVarName) → cValue` | Gets environment variable; returns "" if not set | +| **SETENV** / **HB_SETENV** | `SETENV(cVarName, cValue) → lSuccess` | Sets environment variable | +| **OS** | `OS() → cOS` | Returns operating system string (e.g., "LINUX", "DARWIN", "WINDOWS") | +| **VERSION** | `VERSION() → cVersion` | Returns Harbour/Five version string | +| **HB_VERSION** | `HB_VERSION() → cVersion` | Returns version (Go: "Five 0.1") | +| **HB_COMPILER** | `HB_COMPILER() → cCompiler` | Returns compiler info | +| **HB_OSNEWLINE** | `HB_OSNEWLINE() → cNewline` | Returns OS newline ("\r\n" or "\n") | +| **HB_OSPATHSEPARATOR** | `HB_OSPATHSEPARATOR() → cSep` | Returns path separator ("/" or "\") | +| **HB_CWD** | `HB_CWD() → cDir` | Current working directory (alias for CURDIR) | +| **HB_PROGNAME** | `HB_PROGNAME() → cProgram` | Program/executable name | +| **HB_USERNAME** | `HB_USERNAME() → cUser` | Current user name | +| **HB_GETHOSTNAME** | `HB_GETHOSTNAME() → cHost` | Machine hostname | +| **HB_RUN** | `HB_RUN(cCommand) → nExitCode` | Executes shell command; returns exit code | +| **HB_PROCESSRUN** | `HB_PROCESSRUN(cCommand [, cStdIn]) → cStdOut` | Runs command; returns stdout | +| **WAIT** | `WAIT([cPrompt]) → cKey` | Waits for key press (terminal) | + +## Terminal / Display Functions + +| Function | Signature | Notes | +|----------|-----------|-------| +| **SETPOS** | `SETPOS(nRow, nCol) → aOldPos` | Sets cursor position; returns old [row, col] | +| **ROW** | `ROW() → nRow` | Current cursor row (0-based) | +| **COL** | `COL() → nCol` | Current cursor column (0-based) | +| **DEVPOS** | `DEVPOS() → aPos` | Returns [row, col] array | +| **DEVOUT** | `DEVOUT(xValue) → lSuccess` | Outputs value to device (display) | +| **DISPOUT** | `DISPOUT(nRow, nCol, xValue) → lSuccess` | Outputs at position | +| **DEVOUTPICT** | `DEVOUTPICT(nRow, nCol, xValue, cPicture) → lSuccess` | Outputs with picture format | +| **DISPBOX** | `DISPBOX(nTop, nLeft, nBottom, nRight, cBox [, cColor]) → lSuccess` | Draws box | +| **CLS** | `CLS() → lSuccess` | Clears screen | +| **SCROLL** | `SCROLL(nTop, nLeft, nBottom, nRight, nRows [, cColor]) → lSuccess` | Scrolls region | +| **SETCOLOR** | `SETCOLOR(cColorPair) → cOldColor` | Sets foreground/background colors (e.g., "W+/B") | +| **SETCURSOR** | `SETCURSOR(nStyle) → nOldStyle` | Sets cursor shape (0=none, 1=normal, 2=heavy) | +| **MAXROW** | `MAXROW() → nMaxRow` | Terminal max row (height - 1) | +| **MAXCOL** | `MAXCOL() → nMaxCol` | Terminal max column (width - 1) | +| **DISPBEGIN** | `DISPBEGIN() → nLevel` | Begins display freeze (disables updates) | +| **DISPEND** | `DISPEND() → nLevel` | Ends display freeze | +| **DISPCOUNT** | `DISPCOUNT() → nLevel` | Returns freeze level | +| **SAVESCREEN** | `SAVESCREEN(nTop, nLeft, nBottom, nRight) → cScreenData` | Saves screen region | +| **RESTSCREEN** | `RESTSCREEN(nTop, nLeft, nBottom, nRight, cScreenData) → lSuccess` | Restores screen region | +| **ALERT** | `ALERT(cMessage [, aButtons]) → nChoice` | Displays alert dialog; returns button index (1-based) | +| **QOUT** | `QOUT(xValue1, ...) → cNil` | Outputs values with line feed (like `? ...`) | +| **QQOUT** | `QQOUT(xValue1, ...) → cNil` | Outputs values without line feed (like `?? ...`) | +| **OUTSTD** | `OUTSTD(cString) → cNil` | Outputs to stdout | +| **OUTERR** | `OUTERR(cString) → cNil` | Outputs to stderr | +| **HB_DISPOUTAT** | `HB_DISPOUTAT(nRow, nCol, xValue [, cColor]) → lSuccess` | Outputs at position with optional color | +| **HB_COLORINDEX** | `HB_COLORINDEX(cColorPair) → nIndex` | Gets color pair index | +| **HB_DISPBOX** | `HB_DISPBOX(nTop, nLeft, nBottom, nRight, cBox [, cColor]) → lSuccess` | Extended box drawing | +| **HB_DISPOUTATBOX** | `HB_DISPOUTATBOX(nRow, nCol, xValue, cBox) → lSuccess` | Box output helper | + +## Keyboard Input Functions + +| Function | Signature | Notes | +|----------|-----------|-------| +| **INKEY** | `INKEY([nTimeout]) → nKey` | Reads key; timeout in 1/100ths second; returns -1 on timeout | +| **LASTKEY** | `LASTKEY() → nKey` | Returns last key read | +| **NEXTKEY** | `NEXTKEY() → nKey` | Peeks next key without consuming | +| **READKEY** | `READKEY() → nKey` | Reads key (synonym for INKEY) | +| **SETKEY** | `SETKEY(nKey, bBlock) → bOld` | Associates block with key press | +| **KEYBOARD** | `KEYBOARD(cString) → lSuccess` | Injects string into keyboard buffer | +| **HB_KEYPUT** | `HB_KEYPUT(nKey) → lSuccess` | Puts key into buffer | +| **HB_KEYCHAR** | `HB_KEYCHAR(nKey) → cChar` | Gets character representation of key code | +| **HB_KEYINS** | `HB_KEYINS(nKey) → lSuccess` | Inserts key into input stream | + +## Stack/Introspection Functions + +| Function | Signature | Notes | +|----------|-----------|-------| +| **PCOUNT** | `PCOUNT() → nParamCount` | Returns number of parameters passed to current function | +| **PVALUE** | `PVALUE(nIndex) → xValue` | Returns nth parameter value (1-based); NIL if out of bounds | +| **HB_PVALUE** | `HB_PVALUE(nIndex) → xValue` | Alias for PVALUE | +| **PROCNAME** | `PROCNAME([nLevel]) → cName` | Returns function/procedure name; 0=current (default), 1=caller, etc. | +| **PROCLINE** | `PROCLINE([nLevel]) → nLine` | Returns line number in source | +| **PROCFILE** | `PROCFILE([nLevel]) → cFile` | Returns source file path | +| **ERRORLEVEL** | `ERRORLEVEL() → nLevel` | Returns error level (0 = success) | + +## Error Handling + +| Function | Signature | Notes | +|----------|-----------|-------| +| **ERRORBLOCK** | `ERRORBLOCK([bBlock]) → bOld` | Sets/gets error handler block; no-arg returns current | +| **ERRORNEW** | `ERRORNEW() → oError` | Creates new error object | +| **DOSERROR** | `DOSERROR() → nCode` | Returns last OS error code | +| **FERROR** | `FERROR() → nCode` | Returns last file I/O error | +| **HB_ERRORLOG** | `HB_ERRORLOG(cMessage) → lSuccess` | Logs error to file | +| **HB_SETERRORLOGPATH** | `HB_SETERRORLOGPATH(cPath) → cOldPath` | Sets error log file path | +| **HB_SETERRORLOGHOOK** | `HB_SETERRORLOGHOOK(bBlock) → bOld` | Sets error log hook block | +| **ERRORSYS** | `ERRORSYS() → nCode` | System error (platform-specific) | +| **BREAK** | `BREAK([xRetVal]) → xRetVal` | Returns value from BREAK statement / breaks from loop | + +## Database Functions (RDD/Work Area) + +### Navigation & Status + +| Function | Signature | Notes | +|----------|-----------|-------| +| **EOF** | `EOF() → lEOF` | End of file flag for current work area | +| **BOF** | `BOF() → lBOF` | Beginning of file flag | +| **FOUND** | `FOUND() → lFound` | Last SEEK/FIND success flag | +| **RECNO** | `RECNO() → nRecordNum` | Current record number (1-based); 0 if EOF | +| **RECCOUNT** | `RECCOUNT() → nTotalRecords` | Total record count; LASTREC is alias | +| **DELETED** | `DELETED() → lDeleted` | Deleted flag of current record | +| **ALIAS** | `ALIAS() → cAlias` | Current work area alias name | +| **SELECT** | `SELECT(cAlias \| nArea) → nNewArea` | Selects work area by name or number | + +### Record Manipulation + +| Function | Signature | Notes | +|----------|-----------|-------| +| **DBGOTO** | `DBGOTO(nRecno) → lSuccess` | Positions to record by number | +| **DBSKIP** | `DBSKIP([nRecords]) → lSuccess` | Moves n records (default: 1); negative = backward | +| **DBGOTOP** | `DBGOTOP() → lSuccess` | Positions to first record | +| **DBGOBOTTOM** | `DBGOBOTTOM() → lSuccess` | Positions to last record | +| **DBAPPEND** | `DBAPPEND() → lSuccess` | Creates new blank record at end | +| **DBDELETE** | `DBDELETE() → lSuccess` | Marks current record deleted | +| **DBRECALL** | `DBRECALL() → lSuccess` | Unmarks deleted record; RECALL is alias | +| **DBCOMMIT** | `DBCOMMIT() → lSuccess` | Flushes current work area to disk | +| **DBUNLOCK** | `DBUNLOCK() → lSuccess` | Unlocks all locks in work area | +| **DBRLOCK** | `DBRLOCK(nRecno) → lSuccess` | Record locks record; RLOCK in one arg | +| **DBRUNLOCK** | `DBRUNLOCK(nRecno) → lSuccess` | Unlocks specific record | +| **FLOCK** | `FLOCK() → lSuccess` | File locks entire table | + +### Field Access + +| Function | Signature | Notes | +|----------|-----------|-------| +| **FIELDGET** | `FIELDGET(nFieldNum) → xValue` | Gets field value by number (1-based) | +| **FIELDPUT** | `FIELDPUT(nFieldNum, xValue) → xValue` | Sets field value by number; returns value | +| **FCOUNT** | `FCOUNT() → nFieldCount` | Number of fields in current structure | +| **FIELDNAME** | `FIELDNAME(nFieldNum) → cName` | Field name by position (1-based) | +| **FIELDPOS** | `FIELDPOS(cFieldName) → nPos` | Position of field by name (1-based, 0 if not found) | +| **FIELDTYPE** | `FIELDTYPE(nFieldNum) → cType` | Field type ("C", "N", "D", "L", "M", etc.) | +| **FIELDLEN** | `FIELDLEN(nFieldNum) → nLen` | Field length in bytes | +| **FIELDDEC** | `FIELDDEC(nFieldNum) → nDec` | Decimal places for numeric field | +| **FIELDBLOCK** | `FIELDBLOCK(cFieldName) → bBlock` | Returns code block for field access | +| **FIELDWBLOCK** | `FIELDWBLOCK(cFieldName) → bBlock` | Returns code block for field write | +| **AFIELDS** | `AFIELDS() → aStructure` | Returns array of field descriptors [name, type, len, dec, ...] | +| **DBSTRUCT** | `DBSTRUCT() → aStructure` | Alias for AFIELDS | + +### Table Operations + +| Function | Signature | Notes | +|----------|-----------|-------| +| **DBUSEAREA** | `DBUSEAREA(lShared, cRDD, cFile, cAlias [, lReadOnly]) → lSuccess` | Opens table into work area | +| **DBCLOSEAREA** | `DBCLOSEAREA() → lSuccess` | Closes current work area | +| **DBCLOSEALL** | `DBCLOSEALL() → lSuccess` | Closes all work areas | +| **DBPACK** | `DBPACK() → lSuccess` | Removes deleted records; PACK is alias; __DBPACK | +| **DBZAP** | `DBZAP() → lSuccess` | Deletes all records; ZAP is alias; __DBZAP | +| **DBPACK** / **PACK** | `PACK() → lSuccess` | Compacts table (removes deleted) | +| **DBCREATE** | `DBCREATE(cFileName, aStructure) → lSuccess` | Creates new table with structure | +| **DBCOPY** | `__DBCOPY(cFileName) → lSuccess` | Copies table to new file | +| **DBSORT** | `__DBSORT(cFileName, aFields) → lSuccess` | Sorts records to new file | +| **DBLIST** | `__DBLIST() → lSuccess` | Lists records (debug) | +| **DBTOTAL** | `__DBTOTAL(...) → aResult` | Totals numeric fields | +| **DBJOIN** | `__DBJOIN(...) → aResult` | Joins tables | +| **DBUPDATE** | `__DBUPDATE(...) → lSuccess` | Updates records from another table | + +### Seek / Locate / Filter + +| Function | Signature | Notes | +|----------|-----------|-------| +| **DBSEEK** | `DBSEEK(xValue [, lSoftSeek]) → lFound` | Seeks on active index; soft=fuzzy match | +| **DBLOCATE** | `DBLOCATE(bCondition) → lFound` | Linear search with block condition | +| **__DBCONTINUE** | `__DBCONTINUE() → lFound` | Continues last LOCATE | +| **DBSETFILTER** | `DBSETFILTER(bBlock [, cExpr]) → cOld` | Sets filter block; returns old filter | +| **DBCLEARFILTER** | `DBCLEARFILTER() → lSuccess` | Removes filter | +| **DBFILTER** | `DBFILTER() → cFilter` | Returns current filter expression | +| **DBEVAL** | `DBEVAL(bBlock [, bFor [, bWhile]]) → nCount` | Evaluates block for each record; returns count | +| **__DBAVERAGE** | `__DBAVERAGE(...) → nAverage` | Averages numeric field | + +### Index/Order + +| Function | Signature | Notes | +|----------|-----------|-------| +| **ORDSETFOCUS** | `ORDSETFOCUS(nOrder \| cTag) → cOldOrder` | Sets active order/index | +| **ORDCREATE** | `ORDCREATE(cFileName, cTag, cExpr [, bFor]) → lSuccess` | Creates index order | +| **DBCREATEINDEX** | `DBCREATEINDEX(cFileName, cExpr [, bFor]) → lSuccess` | Alias for ORDCREATE | +| **DBCLEARINDEX** | `DBCLEARINDEX() → lSuccess` | Removes index associations | +| **INDEXORD** | `INDEXORD() → nOrder` | Current index order number (1-based, 0=none) | +| **INDEXKEY** | `INDEXKEY([nOrder]) → cExpr` | Index key expression | +| **ORDCOUNT** | `ORDCOUNT() → nCount` | Number of open indexes | +| **ORDNAME** | `ORDNAME([nOrder]) → cTag` | Order tag name | +| **ORDKEY** | `ORDKEY([nOrder]) → cExpr` | Order key expression | +| **ORDFOR** | `ORDFOR([nOrder]) → cFor` | Order FOR condition | +| **ORDSCOPE** | `ORDSCOPE(nScopeNum [, xValue]) → xScope` | Gets/sets order scope (soft-seek bounds) | +| **ORDLISTREBUILD** | `ORDLISTREBUILD() → lSuccess` | Rebuilds all indexes | +| **DBREINDEX** | `DBREINDEX() → lSuccess` | Alias for ORDLISTREBUILD | +| **ORDINFO** | `ORDINFO(nOrdNum, nInfoType) → xInfo` | Gets order metadata | + +### Work Area Introspection + +| Function | Signature | Notes | +|----------|-----------|-------| +| **DBINFO** | `DBINFO(nInfoType) → xValue` | Gets work area info (RDD-specific) | +| **DBORDERINFO** | `DBORDERINFO(nInfoType) → xValue` | Gets order metadata | +| **RDDINFO** | `RDDINFO(nInfoType [, cRDD]) → xValue` | Gets RDD info | +| **RDDNAME** | `RDDNAME() → cRDD` | Current RDD driver name | +| **RDDLIST** | `RDDLIST() → aRDDs` | List of available RDD drivers | +| **RDDSETDEFAULT** | `RDDSETDEFAULT(cRDD) → cOld` | Sets default RDD | + +### HBSIX Compatibility (Extended Index Support) + +| Function | Signature | Notes | +|----------|-----------|-------| +| **SX_SETTAG** | `SX_SETTAG(cTag) → cOld` | Sets active HBSIX tag | +| **SX_INDEXTAG** | `SX_INDEXTAG(nOrd) → cTag` | Gets tag name for order | +| **SX_TAGORDER** | `SX_TAGORDER(cTag) → nOrd` | Gets order for tag name | +| **SX_TAGCOUNT** | `SX_TAGCOUNT() → nCount` | Number of tags | +| **SX_TAGS** | `SX_TAGS() → aTagNames` | Array of all tag names | +| **SX_SETFILEORD** | `SX_SETFILEORD(cFileName) → lSuccess` | Associates index file | +| **SX_ISDBT** | `SX_ISDBT() → lDBT` | Checks if using .DBT memo file | +| **SX_ISFPT** | `SX_ISFPT() → lFPT` | Checks if using .FPT memo file | +| **SX_ISSMT** | `SX_ISSMT() → lSMT` | Checks if using .SMT memo file | +| **SX_AUTOOPEN** | `SX_AUTOOPEN(lMode) → lOld` | Auto-opens indexes on table open | +| **SX_AUTOSHARE** | `SX_AUTOSHARE(lMode) → lOld` | Auto-shares index file | +| **SX_BLOB2FILE** | `SX_BLOB2FILE(nFieldNum, cFileName) → lSuccess` | Extracts BLOB to file | +| **SX_FILE2BLOB** | `SX_FILE2BLOB(nFieldNum, cFileName) → lSuccess` | Loads file to BLOB | +| **SX_SETTRIGGER** | `SX_SETTRIGGER(...) → lSuccess` | Sets HBSIX trigger | +| **SX_VFGET** | `SX_VFGET(...) → xValue` | Gets virtual field value | +| **SX_DBFENCRYPT** | `SX_DBFENCRYPT(cPassword) → lSuccess` | Encrypts table with password | +| **SX_DBFDECRYPT** | `SX_DBFDECRYPT(cPassword) → lSuccess` | Decrypts table | +| **SX_COMPRESS** | `SX_COMPRESS() → lSuccess` | Compresses memo file | +| **SX_DECOMPRESS** | `SX_DECOMPRESS() → lSuccess` | Decompresses memo file | + +## SET Command Functions + +| Function | Signature | Notes | +|----------|-----------|-------| +| **SET** | `SET(nSetting [, xValue]) → xOld` | Generic SET function | +| **SETDELETED** | `SETDELETED([lMode]) → lOld` | Toggles respecting SET DELETED flag | +| **SETEXACT** | `SETEXACT([lMode]) → lOld` | Toggles exact string comparison (SET EXACT) | +| **SETSOFTSEEK** | `SETSOFTSEEK([lMode]) → lOld` | Toggles soft-seek (fuzzy match on failed seek) | +| **SETEXCLUSIVE** | `SETEXCLUSIVE([lMode]) → lOld` | Toggles exclusive table open mode | +| **SETFIXED** | `SETFIXED([lMode]) → lOld` | Toggles fixed numeric width (SET FIXED) | +| **SETCANCEL** | `SETCANCEL([lMode]) → lOld` | Toggles cancel key behavior | +| **SETBELL** | `SETBELL([lMode]) → lOld` | Toggles audio bell | +| **SETCONFIRM** | `SETCONFIRM([lMode]) → lOld` | Toggles confirm mode | +| **SETINSERT** | `SETINSERT([lMode]) → lOld` | Toggles insert mode | +| **SETESCAPE** | `SETESCAPE([lMode]) → lOld` | Toggles ESC key behavior | +| **SETWRAP** | `SETWRAP([lMode]) → lOld` | Toggles line wrapping in input | + +### SET Constants (for programmatic reference) + +| Function | Signature | Notes | +|----------|-----------|-------| +| **_SET_EXACT** | `_SET_EXACT → nConstant` | SET EXACT setting number | +| **_SET_DELETED** | `_SET_DELETED → nConstant` | SET DELETED setting number | +| **_SET_SOFTSEEK** | `_SET_SOFTSEEK → nConstant` | SET SOFTSEEK setting number | +| **_SET_EXCLUSIVE** | `_SET_EXCLUSIVE → nConstant` | SET EXCLUSIVE setting number | +| **_SET_DATEFORMAT** | `_SET_DATEFORMAT → nConstant` | SET DATEFORMAT setting number | +| **_SET_DECIMALS** | `_SET_DECIMALS → nConstant` | SET DECIMALS setting number | +| **_SET_EPOCH** | `_SET_EPOCH → nConstant` | SET EPOCH setting number | + +## Evaluation/Macros + +| Function | Signature | Notes | +|----------|-----------|-------| +| **EVAL** | `EVAL(bBlock [, xArg1, ...]) → xResult` | Evaluates code block with arguments | +| **DO** | `DO(xTarget, [xArg1, ...]) → xResult` | Invokes function/block dynamically; xTarget = string (function name) or block | + +## Advanced / Five Extensions + +### Bytecode Compilation (Expression Optimization) + +| Function | Signature | Notes | +|----------|-----------|-------| +| **PCCOMPILE** | `PCCOMPILE(cExpr) → pCompiledExpr` | Compiles expression to bytecode pointer (FiveSql2 hot-path) | +| **PCEVAL** | `PCEVAL(pCompiledExpr [, xArg1, ...]) → xResult` | Evaluates compiled expression | + +### Go-Native SQL Scan Loop (FiveSql2 Hot-Path) + +| Function | Signature | Notes | +|----------|-----------|-------| +| **SQLSCAN** | `SQLSCAN(...) → [internal]` | Initiates SQL scan loop (Go native); not for direct PRG use | +| **SQLEACH** | `SQLEACH(...) → [internal]` | Iterates SQL result rows | +| **SQLHASHBUILD** | `SQLHASHBUILD(...) → hHash` | Builds hash for JOIN optimization | +| **SQLHASHJOIN** | `SQLHASHJOIN(...) → aResult` | Hash-based JOIN operation | +| **SQLORDERBY** | `SQLORDERBY(...) → aResult` | ORDER BY operation | +| **SQLGROUPBY** | `SQLGROUPBY(...) → aResult` | GROUP BY aggregation | +| **SQLDISTINCT** | `SQLDISTINCT(...) → aResult` | DISTINCT filtering | +| **SQLUNIONDISTINCT** | `SQLUNIONDISTINCT(...) → aResult` | UNION DISTINCT | +| **SQLBUILDSUBCACHEKEY** | `SQLBUILDSUBCACHEKEY(...) → cKey` | Subquery cache key generation | +| **SQLEXPRHASAGG** | `SQLEXPRHASAGG(...) → lHasAgg` | Detects aggregate expressions | +| **SQLBULKINSERT** | `SQLBULKINSERT(...) → nInserted` | Bulk INSERT operation | +| **SQLBULKUPDATE** | `SQLBULKUPDATE(...) → nUpdated` | Bulk UPDATE operation | +| **SQLBULKDELETE** | `SQLBULKDELETE(...) → nDeleted` | Bulk DELETE operation | +| **SQLWINDOWSLIDEAGG** | `SQLWINDOWSLIDEAGG(...) → aResult` | Window function aggregation | +| **SQLWINDOWPARTITIONS** | `SQLWINDOWPARTITIONS(...) → aPartitions` | Partitions for window functions | +| **SQLGROUPROWS** | `SQLGROUPROWS(...) → aGrouped` | Groups rows by key | +| **SQLCOMPUTEAGGSIMPLE** | `SQLCOMPUTEAGGSIMPLE(...) → xAgg` | Computes single aggregate | +| **SQLEVALHAVING** | `SQLEVALHAVING(...) → lPass` | Evaluates HAVING clause | +| **SQLCOERCESTR** | `SQLCOERCESTR(...) → cString` | Type coercion to string | +| **SQLCOERCENUM** | `SQLCOERCENUM(...) → nNumber` | Type coercion to numeric | +| **SQLCOERCEFORCMP** | `SQLCOERCEFORCMP(...) → xValue` | Coerces for comparison | +| **SQLISTRUE** | `SQLISTRUE(...) → lTruthy` | Evaluates truthiness | +| **SQLISAGGNAME** | `SQLISAGGNAME(...) → lAgg` | Checks if aggregate function name | +| **SQLFETCHROWFAST** | `SQLFETCHROWFAST(...) → aRow` | Fast row fetch (Go native) | +| **SQLCMPEQ** | `SQLCMPEQ(...) → lEqual` | Fast equality compare | +| **SQLCMPLT** | `SQLCMPLT(...) → lLess` | Fast less-than compare | +| **SQLEXTRACTTEMPLATE** | `SQLEXTRACTTEMPLATE(...) → cTemplate` | Extracts template from SQL | +| **SQLLEXERTOKENIZE** | `SQLLEXERTOKENIZE(...) → aTokens` | SQL tokenization | +| **SQLLEXANDEXTRACTTEMPLATE** | `SQLLEXANDEXTRACTTEMPLATE(...) → cTemplate` | Combined lex + template | +| **SQLWACACHEENABLE** | `SQLWACACHEENABLE() → lSuccess` | Enables WorkArea cache (FiveSql2) | +| **SQLWACACHEDISABLE** | `SQLWACACHEDISABLE() → lSuccess` | Disables WorkArea cache | +| **SQLWACACHEISENABLED** | `SQLWACACHEISENABLED() → lEnabled` | Checks cache status | +| **SQLWACACHEGET** | `SQLWACACHEGET(cKey) → xValue` | Retrieves from cache | +| **SQLWACACHEPUT** | `SQLWACACHEPUT(cKey, xValue) → lSuccess` | Stores in cache | +| **SQLWACACHEINVALIDATE** | `SQLWACACHEINVALIDATE(cKey) → lSuccess` | Invalidates cache entry | +| **SQLWACACHECLOSEALL** | `SQLWACACHECLOSEALL() → lSuccess` | Clears all cache | +| **SQLWINDOWSORTPARTITION** | `SQLWINDOWSORTPARTITION(...) → aResult` | Sorts window partition | +| **SQLWINDOWASSIGNRANK** | `SQLWINDOWASSIGNRANK(...) → aResult` | Assigns window function ranks | + +### FRB (Five Runtime Binary) Bytecode + +| Function | Signature | Notes | +|----------|-----------|-------| +| **FRBLOAD** | `FRBLOAD(cFileName) → pFRB` | Loads compiled FRB bytecode | +| **FRBDO** | `FRBDO(pFRB, xArg1, ...) → xResult` | Executes FRB with arguments | +| **FRBUNLOAD** | `FRBUNLOAD(pFRB) → lSuccess` | Unloads FRB from memory | +| **FRBRUN** | `FRBRUN(cFRBCode, xArg1, ...) → xResult` | Runs inline FRB | +| **FRBCOMPILE** | `FRBCOMPILE(cCode) → pFRB` | Compiles PRG to FRB | +| **FRBEXEC** | `FRBEXEC(cCode) → xResult` | Compiles and executes FRB inline | + +### HTTP / ZIP / XML (Five Native Extensions) + +| Function | Signature | Notes | +|----------|-----------|-------| +| **FV_HTTPGET** | `FV_HTTPGET(cURL [, nTimeout]) → cResponse` | HTTP GET; returns response body | +| **FV_HTTPPOST** | `FV_HTTPPOST(cURL, cBody [, nTimeout]) → cResponse` | HTTP POST; returns response body | +| **FV_ZIPENTRIES** | `FV_ZIPENTRIES(cZipFile) → aEntries` | Lists ZIP entries (filenames) | +| **FV_ZIPREAD** | `FV_ZIPREAD(cZipFile, cEntry) → cData` | Reads entry from ZIP | +| **FV_XML_ROWS** | `FV_XML_ROWS(cXML, cXPath) → aRows` | Extracts XML rows by XPath | + +### Random Number Generation + +| Function | Signature | Notes | +|----------|-----------|-------| +| **HB_RANDOM** | `HB_RANDOM() → nRandom` | Returns random float 0.0 ≤ x < 1.0 | +| **HB_RANDOMINT** | `HB_RANDOMINT([nMin, nMax]) → nRandom` | Random integer; default [0, 2^31-1] | +| **HB_RANDOMSEED** | `HB_RANDOMSEED(nSeed) → cNil` | Seeds random number generator | +| **HB_RANDSTR** | `HB_RANDSTR([nLen]) → cRandom` | Random alphanumeric string | + +### Utilities / Helpers + +| Function | Signature | Notes | +|----------|-----------|-------| +| **HB_DEFAULT** | `HB_DEFAULT(xValue, xDefault) → xValue` | Returns xValue if not NIL, else xDefault | +| **HB_DEFAULTVALUE** | `HB_DEFAULTVALUE(xValue, xDefault) → xValue` | Alias for HB_DEFAULT | +| **HB_IDLESLEEP** | `HB_IDLESLEEP(nMilliseconds) → lSuccess` | Sleeps without busy-wait | +| **SLEEP** | `SLEEP(nMilliseconds) → lSuccess` | Sleeps for milliseconds | +| **TONE** | `TONE(nFrequency, nDuration) → lSuccess` | Produces beep tone | +| **CENTER** | `CENTER(cString, nWidth) → cCentered` | Centers string in width | +| **SOUNDEX** | `SOUNDEX(cString) → cSoundex` | Soundex phonetic encoding | +| **MEMOLINE** | `MEMOLINE(cMemo, nLineLen, nLineNum) → cLine` | Extracts line from memo | +| **MLCOUNT** | `MLCOUNT(cMemo, nLineLen) → nLineCount` | Counts memo lines | +| **TOKEN** | `TOKEN(cString, nNum [, cDelim]) → cToken` | Extracts nth token from delimited string | +| **NUMTOKEN** | `NUMTOKEN(cString [, cDelim]) → nCount` | Counts tokens in string | +| **HB_TOKENGET** | `HB_TOKENGET(cString, nNum [, cDelim]) → cToken` | Alias for TOKEN | +| **HB_TOKENCOUNT** | `HB_TOKENCOUNT(cString [, cDelim]) → nCount` | Alias for NUMTOKEN | +| **HB_PS** | `HB_PS() → cPS` | Path separator string | +| **HB_EOL** | `HB_EOL() → cEOL` | End-of-line string (OS-specific) | +| **HB_ISNULL** | `HB_ISNULL(xValue) → lNull` | Checks if value is NIL | +| **MEMVARBLOCK** | `MEMVARBLOCK(cVarName) → bBlock` | Returns block to access memvar | +| **__DEFAULTNIL** | `__DEFAULTNIL(xValue) → xValue` | Returns NIL if xValue is default | +| **DBEDIT** | `DBEDIT(...) → lSuccess` | Interactive database editor (UI) | +| **TBROWSEDB** | `TBROWSEDB(...) → oBrowse` | Creates TBrowse from database | +| **TBROWSENEW** | `TBROWSENEW(...) → oBrowse` | Creates new TBrowse object | +| **TBCOLUMNNEW** | `TBCOLUMNNEW(...) → oColumn` | Creates TBrowse column | + +### Concurrency / Goroutines (Go Native) + +| Function | Signature | Notes | +|----------|-----------|-------| +| **GO** | `GO(bBlock, xArg1, ...) → gGoroutine` | Launches goroutine (Go native); returns handle | +| **CHANNEL** | `CHANNEL() → chChannel` | Creates communication channel | +| **CHSEND** | `CHSEND(chChannel, xValue) → lSuccess` | Sends value to channel | +| **CHRECEIVE** | `CHRECEIVE(chChannel) → xValue` | Receives value from channel (blocking) | +| **CHCLOSE** | `CHCLOSE(chChannel) → lSuccess` | Closes channel | +| **WAITGROUP** | `WAITGROUP() → wgGroup` | Creates wait group for sync | +| **WGADD** | `WGADD(wgGroup, nCount) → lSuccess` | Adds workers to wait group | +| **WGDONE** | `WGDONE(wgGroup) → lSuccess` | Signals work complete | +| **WGWAIT** | `WGWAIT(wgGroup) → lSuccess` | Waits for all workers | +| **MUTEX** | `MUTEX() → muMutex` | Creates mutual exclusion lock | +| **LOCK** | `LOCK(muMutex) → lSuccess` | Acquires lock | +| **UNLOCK** | `UNLOCK(muMutex) → lSuccess` | Releases lock | + +### Bitmap/Rushmore Index Optimization (Extended) + +| Function | Signature | Notes | +|----------|-----------|-------| +| **BM_DBSETFILTER** | `BM_DBSETFILTER(...) → lSuccess` | Bitmap-optimized filter | +| **BM_DBSEEKWILD** | `BM_DBSEEKWILD(...) → lSuccess` | Wildcard seek optimization | +| **BM_TURBO** | `BM_TURBO(lMode) → lOld` | Enables Rushmore turbo mode | +| **BM_DBGETFILTERARRAY** | `BM_DBGETFILTERARRAY() → aFilter` | Gets current bitmap filter array | +| **BM_DBSETFILTERARRAY** | `BM_DBSETFILTERARRAY(aFilter) → lSuccess` | Sets bitmap filter array | +| **BM_DBSETFILTERARRAYADD** | `BM_DBSETFILTERARRAYADD(...) → lSuccess` | Adds to bitmap filter | +| **BM_DBSETFILTERARRAYDEL** | `BM_DBSETFILTERARRAYDEL(...) → lSuccess` | Removes from bitmap filter | + +--- + +## Summary + +The Five RTL catalog includes: +- **568 registered functions** spanning strings, arrays, hashes, JSON, dates/times, regex, math, I/O, database, terminal, and advanced (SQL/bytecode/concurrency) domains +- **UTF-8 rune-aware** core string operations (LEN, SUBSTR, LEFT, RIGHT, AT, CHR, ASC, PADR, PADL) when charset is UTF-8 (default) +- **Charset/codepage support** via HB_GETCHARSET / HB_SETCHARSET / HB_TRANSLATE for legacy ANSI codepages (CP949, CP1252, etc.) +- **Go-native features** (regex via `regexp`, JSON via `encoding/json`, concurrency via goroutines/channels, HTTP/ZIP/XML extensions) +- **FiveSql2 hot-path optimization** via PCCOMPILE/PCEVAL, SQLSCAN, and WorkArea caching +- **Harbour compatibility** for xBase/Clipper code migration, with Five-specific extensions clearly noted diff --git a/rag/04-idioms.md b/rag/04-idioms.md new file mode 100644 index 0000000..b347faf --- /dev/null +++ b/rag/04-idioms.md @@ -0,0 +1,172 @@ +--- +doc: five-idioms +title: Five web/worker app idioms (HTTP, Postgres, queue, LLM, build) +keywords: [idioms, cookbook, http, endpoint, routing, AP_JSONRESPONSE, AP_BODY, AP_GETPAIRS, ctx_set, ctx_get, PG_QUERY, PG_EXEC, LABDB_GET_PG, job queue, FOR UPDATE SKIP LOCKED, LLM_CHAT, fnode, build, deploy] +summary: Battle-tested patterns from the production solmade app for building HTTP APIs and background workers in Five — endpoint skeleton, file-name routing, Postgres access, the DB job queue, LLM calls, and build/deploy. +--- + +# Five idioms / cookbook + +All snippets are from the real `solmade` app (`/Users/charleskwon/solmade`). + +## 1. HTTP endpoint skeleton + +One `FUNCTION Main()` per `.prg` file under `app/api/`. + +```five +// POST /api/press-save.prg +FUNCTION Main() + LOCAL nPG := LABDB_GET_PG() + LOCAL hBody, nUser, aRows + + IF nPG < 0 + ctx_set( "status", 500 ) + AP_JSONRESPONSE( { "ok" => .f., "error" => "PG not connected" } ) + RETURN NIL + ENDIF + + hBody := hb_jsonDecode( AP_BODY() ) // parse JSON request body + IF ! HB_ISHASH( hBody ) + ctx_set( "status", 400 ) + AP_JSONRESPONSE( { "ok" => .f., "error" => "body must be JSON object" } ) + RETURN NIL + ENDIF + + nUser := Val( ctx_get( "auth_user_id", "0" ) ) // authed user (string → int) + + AP_JSONRESPONSE( { "ok" => .t. } ) + RETURN NIL +``` + +Core verbs: +- `AP_BODY()` → raw request body (decode with `hb_jsonDecode`). +- `AP_GETPAIRS( .t. )` → hash of query-string params (GET). +- `AP_JSONRESPONSE( hHash )` → serialize + send JSON response. +- `ctx_set( "status", N )` → set HTTP status code. +- `ctx_get( "auth_user_id", "0" )` → authed user id (always a STRING; wrap in `Val()`). + Also `auth_email`, `auth_role`, `auth_display_name` set by the auth middleware. + +## 2. File-name routing + +A URL path maps deterministically to a function (`app/bridge_server.prg`): +strip `/api/`, drop `.prg`, replace `-`→`_` and `/`→`_`, uppercase, append `__MAIN`. + +``` +/api/press-submit.prg → PRESS_SUBMIT__MAIN (Main() in app/api/press_submit.prg) +/api/auth/login.prg → AUTH_LOGIN__MAIN +``` + +So: name the file with underscores, put a `FUNCTION Main()` in it, call it with hyphens. + +## 3. PostgreSQL access + +```five +LOCAL nPG := LABDB_GET_PG() // pooled connection handle (opened at startup) + +// SELECT → array of hashes. *** Column values come back as STRINGS. *** +LOCAL aRows := PG_QUERY( nPG, ; + "SELECT id, title FROM articles WHERE status = $1 ORDER BY id DESC LIMIT 200", ; + { "draft" } ) +IF aRows == NIL ; aRows := {} ; ENDIF +LOCAL nId := Val( hb_CStr( aRows[1]["id"] ) ) // convert numeric columns explicitly + +// non-SELECT +PG_EXEC( nPG, "UPDATE articles SET title=$1 WHERE id=$2", { "New", hb_NToS( nId ) } ) + +// INSERT ... RETURNING +aRows := PG_QUERY( nPG, ; + "INSERT INTO articles (title) VALUES ($1) RETURNING id", { "Draft" } ) +LOCAL nNew := aRows[1]["id"] + +// error string +LOCAL cErr := PG_LAST_ERROR( nPG ) +``` + +Idempotent schema, safe to call inside an endpoint: + +```five +FUNCTION ARTICLES_ENSURE( nPG ) + PG_EXEC( nPG, ; + "CREATE TABLE IF NOT EXISTS articles ( " + ; + " id SERIAL PRIMARY KEY, " + ; + " title TEXT NOT NULL DEFAULT '(제목 없음)', " + ; + " created_at TIMESTAMPTZ NOT NULL DEFAULT now() " + ; + ")" ) + RETURN NIL +``` + +## 4. DB job queue (avoid proxy timeout on long work) + +Long LLM calls would blow the HTTP/proxy timeout. Pattern: submit → poll. + +**Submit (web)** — insert `queued`, return id immediately: + +```five +aRows := PG_QUERY( nPG, ; + "INSERT INTO text_tasks (kind, input, status, progress_msg, requested_by) " + ; + "VALUES ('press', $1, 'queued', '대기 중', $2) RETURNING id", ; + { cText, hb_NToS( nUser ) } ) +AP_JSONRESPONSE( { "ok" => .t., "task_id" => aRows[1]["id"] } ) +``` + +**Worker (separate binary)** — claim one row atomically; no two workers collide: + +```five +aClaim := PG_QUERY( nPG, ; + "WITH n AS ( SELECT id FROM text_tasks WHERE status='queued' " + ; + " ORDER BY id FOR UPDATE SKIP LOCKED LIMIT 1 ) " + ; + "UPDATE text_tasks SET status='running', started_at=now() " + ; + "WHERE id=(SELECT id FROM n) RETURNING id, kind, input" ) +IF aClaim == NIL .OR. Len( aClaim ) == 0 + RETURN .F. // nothing queued; caller sleeps +ENDIF +// ... do work, periodically UPDATE progress_pct/progress_msg ... +PG_EXEC( nPG, "UPDATE text_tasks SET status='done', progress_pct=100, " + ; + "result=$1, finished_at=now() WHERE id=$2", { cOut, hb_NToS( nId ) } ) +``` + +**Status (web)** — client polls `SELECT status, progress_pct, progress_msg, result`. +Run multiple worker processes for concurrency; `SKIP LOCKED` keeps them race-free. + +## 5. LLM calls + +`LLM_CHAT(cSystem, cUser, hOpts)` → `{ "ok"=>.T./.F., "text"=>cResult, "error"=>cMsg }`. + +```five +LOCAL hRes := LLM_CHAT( PRESSER_SYSTEM_PROMPT(), cUser, ; + { "temperature" => 0.5, "max_tokens" => 4000 } ) +IF hb_HGetDef( hRes, "ok", .f. ) + cOut := hb_HGetDef( hRes, "text", "" ) +ELSE + // hRes["error"] — endpoint unreachable / HTTP != 200 / empty response +ENDIF +``` + +Endpoint resolves from `SOLMADE_LLM_URL` env (OpenAI-compatible `/v1`); model name is +auto-resolved (a literal `"local"` is replaced by the actually-loaded model id, because +mlx/llama servers reject unknown model names). `...` is stripped. + +## 6. Build & deploy (fnode) + +Web and worker are **separate binaries** built from explicit file lists. + +```bash +# build.sh — web +"$FNODE" build \ + app/bridge_server.prg app/auth/*.prg app/lib/*.prg app/api/*.prg "$BRIDGE"/*.prg \ + --rtl fivenode_go/hbrtl_ext/pgrtl \ + --rtl fivenode_go/hbrtl_ext/httpserver \ + --go-replace gitea.fivego.org/kwon_ai/solmade="$SOLMADE_ROOT" \ + --module gitea.fivego.org/kwon_ai/solmade/_solmade_web \ + -o solmade-web +``` + +- `--rtl ` blank-imports a Go package whose `init()` registers RTL functions + (e.g. `pgrtl` provides `PG_QUERY`/`PG_EXEC`; `httpserver` the web bridge). +- `--go-replace pkg=path` resolves a private module without a proxy. +- `--module ` sets the temp module path (must sit under the app's module so RTL + packages can import the app's internal packages). + +Deploy (launchd): `launchctl kickstart -k gui/$(id -u)/kr.solmade.web` (and +`...worker1/2/3`). The worker build (`build_worker.sh`) links `cmd_prg/job_worker.prg` +plus the shared `app/lib/*.prg` (so `LLM_CHAT` and prompts are available to it too). diff --git a/rag/05-gotchas.md b/rag/05-gotchas.md new file mode 100644 index 0000000..f0cdd3a --- /dev/null +++ b/rag/05-gotchas.md @@ -0,0 +1,91 @@ +--- +doc: five-gotchas +title: Five gotchas & non-obvious traps +keywords: [gotcha, trap, pitfall, intrinsic, gengo, charset, utf8, string escape, Chr, pgrtl string columns, Val, model local, analyzer warning, fnode, runtime] +summary: The non-obvious semantic traps that pure grammar knowledge will NOT prevent. Each entry is a real mistake observed in practice plus the fix. This is the long-tail corpus that makes Five RAG actually work. +--- + +# Five gotchas (read before writing/debugging Five) + +These are discovered-the-hard-way facts. Grammar docs won't save you here. + +## 1. String functions are inlined intrinsics — editing `hbrtl` alone does nothing + +The compiler (`compiler/gengo/gengo.go`) **inlines** `LEN, CHR, ASC, SUBSTR, LEFT, RIGHT, +AT, PADR, PADL` directly as Go. They do **not** dispatch through the `hbrtl` registry at +runtime. So changing the registered `hbrtl` function has **no effect** on these calls. + +- To change their runtime behavior you must edit the gengo intrinsic cases. They now emit + calls to charset-aware helpers `hbrtl.StrLen/StrChr/StrAsc/StrSubStr/StrLeft/StrRight/ + StrAt/StrPadR/StrPadL` (in `hbrtl/charset.go`). +- Functions used as code blocks / passed around DO hit the registry, so keep the registry + impl and the intrinsic in agreement. + +## 2. Strings are UTF-8 (runes) by default; legacy charset is opt-in + +`LEN("한글")` is `2` (runes), not bytes. `CHR(9650)` is `▲`. This is the default. + +- Select a legacy charset with `HB_SETCHARSET("CP949")` / `HB_CDPSELECT("CP949")` — then + byte/charset semantics apply. `HB_GETCHARSET()` reads the active one. +- Initial charset comes from env `FIVE_CHARSET` (or `HB_CODEPAGE`); default `UTF8`. +- Convert across charsets with `HB_TRANSLATE(cStr, cFrom, cTo)`. + +## 3. String literals do NOT process escapes (single OR double quotes) + +`"a\nb"` is the literal characters `a \ n b` — **not** a newline. Same for `'...'`. + +- For a newline use `Chr(10)` (and `Chr(13)` for CR); build control chars explicitly. +- To embed a quote: wrap in the *other* quote. A string containing `"` → use `'...'`; + a string containing `'` → use `"..."`. (No backslash-escaping exists.) +- Watch out building SQL/format strings: e.g. a literal `T` separator inside a + double-quoted SQL fragment can clash — concatenate instead: `... || 'T' || ...`. + +## 4. Postgres columns come back as STRINGS + +`PG_QUERY` (pgrtl) returns rows as hashes whose values are **all strings**, even for +`INTEGER`/`NUMERIC` columns. `Int("100")` semantics will bite you. + +- Convert: `Val( hb_CStr( row["id"] ) )` for numbers. +- Bind params as strings too: `{ hb_NToS( nId ) }`. + +## 5. `ctx_get("auth_user_id")` is a string + +Auth context values are strings. `nUser := Val( ctx_get( "auth_user_id", "0" ) )`. + +## 6. LLM `model = "local"` is rejected by mlx/llama servers + +OpenAI-compatible local servers (mlx_lm, llama.cpp) 404 on unknown model names. The app's +`ResolveLlmModel` queries `/v1/models` and substitutes the actually-loaded id. If you call +an LLM endpoint directly, never send `model:"local"` — resolve the real id first. + +## 7. Two runtimes — build with the right one + +`solmade` builds with the **`fnode`** toolchain in `fivenode_go`, NOT the `five` CLI in +`fivedev/five`. They are separate runtimes with separate RTL behavior. Historic example: +`fivenode_go`'s `Chr()` double-encoded multibyte values (corruption), which is what +prompted implementing proper UTF-8 in `fivedev/five`. Don't assume behavior carries over. + +## 8. Run the `five` CLI from inside `fivedev/five` + +Module resolution depends on CWD. Building/running `five` from elsewhere can pick up the +wrong `replace` directive (e.g. resolving `five =>` to an unrelated repo). Always +`cd /Users/charleskwon/fivenode/fivedev/five` first, e.g. +`go build -o /tmp/five ./cmd/five && /tmp/five run x.prg`. + +## 9. Analyzer "undeclared variable" warnings for RTL functions are harmless + +The static analyzer warns `undeclared variable 'HB_FOO'` for RTL functions it doesn't know +about; they still resolve at runtime via the registry. To silence, add the name to the +known-function set in `compiler/analyzer/analyzer.go` (e.g. `HB_GETCHARSET` etc. were added +there). A warning is not an error. + +## 10. Density is a double-edged sword when debugging + +One line doing a lot means one line failing does a lot. When a dense statement misbehaves, +expand it (split the chained `hb_*`/`PG_*`/`LLM_CHAT` calls into temporaries) to localize +the fault before reasoning about it. + +--- + +> Maintenance discipline: when you hit a NEW non-obvious trap, add it here. Pure-grammar +> RAG closes ~80% of the gap; this accumulating gotcha list closes the rest. diff --git a/rag/INDEX.md b/rag/INDEX.md new file mode 100644 index 0000000..34e7c02 --- /dev/null +++ b/rag/INDEX.md @@ -0,0 +1,20 @@ +# Five RAG — retrieval manifest + +Route a query to the right doc(s). Each row: file · when to retrieve · keywords. + +| File | Retrieve when the task involves… | Keywords | +|------|----------------------------------|----------| +| `01-overview.md` | orienting on what Five is, runtimes, compile model, "where do I look" | five, fivenode, overview, philosophy, token-density, harbour, xbase, compile, go, runtime, gengo intrinsic | +| `02-syntax.md` | writing any Five code — declarations, control flow, literals, operators, blocks | syntax, grammar, FUNCTION, PROCEDURE, LOCAL, STATIC, IF, FOR, FOR EACH, DO WHILE, DO CASE, BEGIN SEQUENCE, IIF, code block, array, hash, string literal, operators, := == $ | +| `03-rtl-catalog.md` | "what function does X" — string/array/hash/json/date/regex/charset/math/crypto builtins | rtl, builtin, Len, SubStr, Left, Right, At, Upper, AllTrim, PadL, PadR, StrTran, Chr, Asc, Val, Str, hb_NToS, hb_CStr, AAdd, AScan, AEval, hb_HGetDef, hb_HHasKey, hb_jsonDecode, hb_jsonEncode, ValType, HB_ISHASH, regex, HB_GETCHARSET, date, hb_ATokens | +| `04-idioms.md` | building an endpoint, DB access, async/queue work, calling the LLM, building/deploying | idioms, http, endpoint, routing, AP_BODY, AP_GETPAIRS, AP_JSONRESPONSE, ctx_set, ctx_get, LABDB_GET_PG, PG_QUERY, PG_EXEC, PG_LAST_ERROR, RETURNING, CREATE TABLE IF NOT EXISTS, text_tasks, FOR UPDATE SKIP LOCKED, job queue, LLM_CHAT, fnode, build.sh, launchctl | +| `05-gotchas.md` | debugging "why doesn't this work", or BEFORE editing string funcs / charset / SQL / LLM | gotcha, trap, intrinsic, gengo, charset, utf8, string escape, Chr, pgrtl string columns, Val, hb_CStr, model local, ResolveLlmModel, two runtimes, fnode, analyzer warning, CWD module resolution | + +## Quick routing heuristics + +- Writing new code → `02` + `04`, and skim `05` first. +- "Which builtin?" → `03`. +- Bug that defies the grammar → `05` (almost always the answer is here). +- "Why is my hbrtl edit ignored?" → `05 §1` (inlined intrinsics). +- Korean/multibyte length/char issues → `05 §2,§3` + `03` charset section. +- Numbers read from DB are wrong → `05 §4`. diff --git a/rag/README.md b/rag/README.md new file mode 100644 index 0000000..30965f5 --- /dev/null +++ b/rag/README.md @@ -0,0 +1,44 @@ +# Five RAG — knowledge corpus for LLM agents writing Five + +A compact, retrieval-ready knowledge base that lets an LLM read and write **Five** +(xBase/Harbour → Go) code correctly without prior training on it. This is the practical +form of "give the model the grammar via RAG": grammar + RTL surface + real idioms + +the long-tail gotchas. + +## Why this exists + +Five is token-dense, so the corpus needed to *teach* a model is small and cheap to inject +— a dense language is cheaper to RAG than a verbose one. Grammar/RTL retrieval closes most +of the gap; the accumulating **gotchas** file closes the semantic long tail. + +## Contents + +| File | What it covers | +|------|----------------| +| `01-overview.md` | What Five is, design priorities, the two runtimes, compile model | +| `02-syntax.md` | Declarations, literals, operators, control flow, code blocks | +| `03-rtl-catalog.md` | Runtime-library functions (strings, array, hash, JSON, date, regex, charset, …) | +| `04-idioms.md` | Web/worker patterns: HTTP endpoint, routing, Postgres, job queue, LLM, build/deploy | +| `05-gotchas.md` | Non-obvious traps + fixes (the highest-signal file) | +| `INDEX.md` | Retrieval manifest (doc → keywords + one-line) | + +Every file has YAML frontmatter (`doc`, `title`, `keywords`, `summary`) for ranking. + +## How to consume + +- **Direct context injection (simplest):** for a small/medium task, paste the relevant + doc(s). For broad work, `01`+`02`+`05` fit easily; pull `03`/`04` sections as needed. +- **Keyword retrieval (e.g. bluge / ripgrep):** index the `.md` files; rank on the + frontmatter `keywords` + body. `INDEX.md` is a hand-curated routing table. +- **Embedding RAG:** chunk by `##` headers (each section is self-contained). Frontmatter + `summary` makes a good chunk preamble. + +Suggested system-prompt pointer: *"When writing Five (.prg) code, consult the Five RAG at +`fivedev/five/rag/` — especially `05-gotchas.md` — and prefer patterns from `04-idioms.md`."* + +## Maintenance + +- Keep `03-rtl-catalog.md` honest against `hbrtl/register.go` (names are authoritative; + rare signatures may drift). +- **Append every new trap to `05-gotchas.md`.** That file is the compounding asset. +- Grammar truth: `compiler/{lexer,parser,ast}`. Idiom truth: the `solmade` app.