# PxPlus Programming Language Reference

PxPlus (formerly ProvideX) is a BASIC-derived business application language. It is procedural with object-oriented extensions, and heavily oriented toward file I/O and record-based data access. This document is derived from reading real source files in the PxSource library.

---

## Table of Contents

1. [File Types](#file-types)
2. [Program Structure](#program-structure)
3. [Variables and Types](#variables-and-types)
4. [Operators](#operators)
5. [Control Structures](#control-structures)
6. [Subroutines and Functions](#subroutines-and-functions)
7. [Object-Oriented Programming](#object-oriented-programming)
8. [File I/O](#file-io)
9. [Error Handling](#error-handling)
10. [String Operations](#string-operations)
11. [Arrays](#arrays)
12. [Date and Time](#date-and-time)
13. [Built-in Functions Reference](#built-in-functions-reference)
14. [Directives Reference](#directives-reference)
15. [Special Patterns and Idioms](#special-patterns-and-idioms)
16. [Framework Patterns](#framework-patterns)
17. [Debugging](#debugging)

---

## File Types

| Extension | Purpose |
|-----------|---------|
| `.pxprg` | Standard program (non-tokenized, plain-text source) |
| `.pvc.pxprg` | Class definition file (also plain-text) |
| `.pxpnl` | Nomads UI panel definition |
| `.pxdta` | Data configuration file |
| `.ini` | Configuration file (INI format) |
| `*memory*` | In-memory file (pseudo-filename, used at runtime) |

Class files are named by path/convention: `*FW/Entity`, `*obj/ArrayList`, etc. The `*` prefix means the file is resolved via the PxPlus search path.

---

## Program Structure

### Traditional (Procedural) Style

```pxplus
! Program comment - ! starts a comment
! Copyright notice etc.

      PRECISION 6
      SET_PARAM 'EZ'=1

! --- Entry point ---
MAIN:
      LET variable$ = "value"
      GOSUB PROCESS_DATA
      EXIT

! --- Subroutine ---
PROCESS_DATA:
      IF condition THEN {
          statement1
          statement2
      }
      RETURN
```

Key rules:
- **Never include line numbers** in new code (legacy files may have them, e.g. `0110 INIT:`)
- Labels end with `:` and are at the start of a line
- `EXIT` exits the program; `RETURN` returns from a GOSUB
- `END` ends the current program
- `!` starts a comment; `REM` is the alternative

### Multi-statement Lines

Use `;` to put multiple statements on one line:

```pxplus
mesg$="",extended=1,failed=0,doauth=0
open (filelist)"*memory*"; fileno=lfo
```

### Line Continuation

Use `\` at end of line to continue on the next:

```pxplus
if _HeaderTable$="*" \
 then _HeaderFake=1

function GetNextKey$()=try(tbl(_isLocked<>0,key(_HeaderChannel,kno=0), \
      ken(_HeaderChannel,kno=0)),"")
```

### Multi-line Blocks

Use `{ }` after `THEN`/`ELSE` for multi-line blocks:

```pxplus
if condition \
 then \
 {    write record (smtp,err=ConnectWriteErr)"EHLO "+machine$+$0D0A$;
      response$=fnGetData$(smtp,$0D0A$,servertime);
      if fnGetStatus(response$)=failed \
       then extended=0 \
       else connected=1
 }
```

---

## Variables and Types

### Naming Conventions

| Convention | Meaning |
|-----------|---------|
| `name$` | String variable (dollar suffix required) |
| `count` | Numeric variable (no suffix) |
| `%varname$` | Global/system variable (percent prefix) |
| `_name$` | Private/internal variable (underscore prefix, in classes) |
| `LOCAL var` | Locally scoped variable |

```pxplus
! String variables
customer_name$ = "Acme Corp"
_LoadedKey$ = ""          ! private internal string
%sy_obj                   ! global system object

! Numeric variables
count = 0
total = 123.45
_HeaderChannel = 0        ! numeric, private

! Global variables (percent prefix)
%POOL_DIR$                ! global string
%inomads                  ! global numeric flag
%trace_email              ! global flag
```

### Variable Declaration

Variables do not need to be declared to be used, but `LOCAL` scopes them to the current method/subroutine:

```pxplus
LOCAL result$             ! local string
LOCAL count               ! local numeric
LOCAL result$, count, flag  ! multiple on one line
```

### Assignment

```pxplus
LET variable$ = "value"
LET a=1, b=2, c$="test"  ! comma-separated in single LET
variable$ = "value"       ! LET is optional
```

### Assignment Operators

```pxplus
count = 10
count += 5    ! count = count + 5
count -= 2
count *= 3
count /= 4
count |= 8    ! bitwise OR
count ^= 2    ! exponentiation
count++       ! post-increment (works)
++count       ! pre-increment (works)
count--       ! decrement
```

---

## Operators

### Comparison

```
=    equal
<>   not equal
<    less than
>    greater than
<=   less than or equal  (also =<)
>=   greater than or equal  (also =>)
```

### Arithmetic

```
+    addition / string concatenation
-    subtraction
*    multiplication
/    division
|    bitwise OR (also exponent context-dependent)
^    exponentiation
```

### Logical

```pxplus
AND    logical and
OR     logical or
NOT(expr)    logical not — must use parentheses with properties!
```

**IMPORTANT**: `NOT` requires parentheses when used with properties:

```pxplus
! WRONG:
IF NOT RatesAvailable THEN ...

! CORRECT:
IF NOT(RatesAvailable) THEN ...
IF RatesAvailable = 0 THEN ...   ! also correct
```

### String Operator

```pxplus
full$ = first$ + " " + last$    ! concatenation
```

---

## Control Structures

### IF / THEN / ELSE

Single-line:

```pxplus
IF condition THEN statement
IF condition THEN statement ELSE other_statement
```

Multi-line with continuation:

```pxplus
IF condition \
 THEN statement1 \
 ELSE statement2

IF _HdrTBl$<>"" \
 THEN _HeaderTable$=_HdrTBl$
```

Multi-line block:

```pxplus
IF condition THEN {
    statement1
    statement2
} ELSE {
    statement3
}
```

Nested:

```pxplus
IF fileno \
 THEN IF isFW=1 \
       THEN RETURN \
       ELSE CLOSE (fileno)
```

### SWITCH / CASE

```pxplus
SWITCH expression$
    CASE "value1"
        ! code
        BREAK
    CASE "value2", "value3"
        ! code
        BREAK
    CASE <"0414"          ! comparison operator in CASE
        ! code
        BREAK
    DEFAULT
        ! code
END SWITCH
```

### FOR / NEXT

**CRITICAL**: FOR-NEXT loops **always execute at least once**. Guard with IF when the count might be zero:

```pxplus
! Safe guard for possibly-empty loops:
IF count > 0 THEN {
    FOR i = 1 TO count
        ! code
    NEXT i
}

! Loop with STEP:
FOR i = 1 TO 10 STEP 2
    ! code
NEXT i

! Inline local variable:
FOR local i = 1 TO count
    ! code
NEXT i

! Loop with zero-based index:
FOR local i = 0 TO array'size()-1
    ! code
NEXT i
```

**CRITICAL**: **Never use RETURN inside a FOR loop.** Use a flag variable and BREAK instead:

```pxplus
! WRONG:
FOR i = 1 TO count
    IF condition THEN RETURN value
NEXT i

! CORRECT:
LOCAL result_found, result_value
result_found = 0
FOR i = 1 TO count
    IF condition THEN {
        result_value = value
        result_found = 1
        BREAK
    }
NEXT i
IF result_found THEN RETURN result_value
```

### WHILE / WEND

```pxplus
WHILE condition
    ! code
    IF special_case THEN BREAK
    IF skip_iteration THEN CONTINUE
WEND

! Infinite loop with manual break:
WHILE 1
    READ (channel, REC=rec$, END=*NEXT)
    IF condition THEN BREAK
WEND
```

### REPEAT / UNTIL

```pxplus
REPEAT
    ! code
UNTIL condition
```

### Loop Control

```pxplus
BREAK          ! exit loop
CONTINUE       ! skip to next iteration
EXITTO label   ! exit to a named label
EXITTO *CONTINUE  ! continue current loop iteration
```

### TBL — Inline Conditional (Ternary)

`TBL(condition, value_if_true, value_if_false)`:

```pxplus
result$ = TBL(condition=1, "true_value", "false_value")
SECURITY_MSG$ = TBL(SECURITY_EXISTS, "No security to remove.", "Security files will be removed.")
INPUTLENGTH = TBL(PRINT_FORMAT$<>"", LENGTH+TBL(TYPE$="N",0,2), LEN(PRINT_FORMAT$))
```

---

## Subroutines and Functions

### GOSUB / RETURN

```pxplus
GOSUB PROCESS_DATA
GOSUB CheckOutputDirectory

PROCESS_DATA:
    ! code
    RETURN    ! return to point after GOSUB
```

### GOSUB WITH Parameters

```pxplus
GOSUB Check_table WITH Tablename$=name$
GOSUB Copy_file WITH input_file$="*fw/pxpwiki.dat", output_file$="pxpwiki.dat"
```

### ENTER — Parameter Binding

`ENTER` binds incoming parameters to local variable names:

```pxplus
ENTER param1$, param2, param3$          ! basic parameter binding
ENTER (param1$), (param2)               ! read-only (by value)
ENTER param1$, (optional$="default")    ! optional with default
ENTER _HdrTBl$="", _DtlTBl$="", _DtlObj$=""  ! all optional with defaults
ENTER filename$, (smtp$), (servertime), (eraseit), mesg$  ! mixed
```

**IMPORTANT**: Parentheses in ENTER make parameters **read-only (pass by value)**, NOT optional. To make a parameter optional, give it a default value after `=`.

### RETURN — Returning Values

```pxplus
RETURN             ! return nothing (from GOSUB)
RETURN result$     ! return a value (from FUNCTION)
RETURN 1           ! return numeric
```

### EXIT — Return with Error Code

```pxplus
EXIT               ! exit program
EXIT 11            ! exit with error code 11
EXIT 42            ! exit with specific error code
EXIT -status       ! exit with negated status
```

### User-defined Functions (inline in class)

Functions can be defined inline (one-liners) directly in the class definition:

```pxplus
FUNCTION GetHeaderTbl$() = _HeaderTable$
FUNCTION CanRemove() = 1
FUNCTION ITEM(X) = TBL(X<1, TBL(X>COUNT, ITEMTBL[X], -1), -1)
FUNCTION GetSystemParam(ParamKey$, DefaultValue) = NUM(_obj'GetSystemParam$(ParamKey$, STR(DefaultValue)))
```

---

## Object-Oriented Programming

### Class Definition

```pxplus
DEF CLASS "*namespace/ClassName" CREATE REQUIRED DELETE REQUIRED
    LIKE "*obj/group"          ! inherit from group
    LIKE "*FW/Property"        ! multiple inheritance

    ! Properties
    PROPERTY name$                     ! basic read/write property
    PROPERTY _isLocked SET ERR         ! read-only externally
    PROPERTY _bReadOnly                ! read/write
    PROPERTY HIDE _internal SET ERR    ! hidden (not visible outside class)
    PROPERTY COUNT WRITE ERR           ! Write returns error -- read only)
    PROPERTY Company OBJECT SET ERR    ! object property

    ! Properties with default values:
    PROPERTY SystemDate$ = DTE(0:"YYYYMMDD")

    ! Local (private) variables -- not exposed as properties
    LOCAL _HdrKeyDef$
    LOCAL fileno
    LOCAL itemKeys$

    ! Function declarations (mapped to implementation labels)
    FUNCTION Initialize() Do_Initialize
    FUNCTION Load(Key$) Do_Load
    FUNCTION Load() Do_LoadNoKey          ! overloaded (different signature)
    FUNCTION Save() Do_Save

    ! Inline function (one-liner, no separate label needed)
    FUNCTION GetHeaderTbl$() = _HeaderTable$
    FUNCTION CanRemove() = 1
    FUNCTION ITEM(X) = TBL(X<1, TBL(X>COUNT, ITEMTBL[X],-1), -1)

    ! String function ($ suffix on name means returns string)
    FUNCTION GetRecord$() = REC(IOL(_HeaderChannel))
    FUNCTION ShowDate$(_date$) = TBL(LEN(_date$)=8, _date$, DTE(...))

    ! Access modifiers
    FUNCTION LOCAL private_method() PRIVATE_LABEL   ! private
    FUNCTION HIDE internal() INTERNAL_LABEL         ! hidden

END DEF
```

### Unique Class

```pxplus
DEF CLASS "*FW/System" UNIQUE CREATE REQUIRED DELETE REQUIRED
```

`UNIQUE` means only one instance can exist at a time.

### Class Modifiers

| Modifier | Meaning |
|----------|---------|
| `CREATE REQUIRED` | `ON_CREATE:` must be defined |
| `DELETE REQUIRED` | `ON_DELETE:` must be defined |
| `UNIQUE` | Only one instance allowed |
| `LIKE "class"` | Inherit from another class |
| `SET ERR` on PROPERTY | Property is read-only from outside |
| `WRITE ERR` on PROPERTY | Property cannot be written from outside (read-only) |
| `HIDE` on PROPERTY/FUNCTION | Not accessible outside class |
| `LOCAL` on FUNCTION | Private method |

### ON_CREATE and ON_DELETE

```pxplus
ON_CREATE:
    ENTER _HdrTBl$="", _DtlTBl$="", _DtlObj$=""

    IF _HdrTBl$<>"" \
     THEN _HeaderTable$=_HdrTBl$

    OPEN (HFN, IOL=*, ERR=open_fail) TABLE _HeaderTable$
    _HeaderChannel=LFO

    RETURN

ON_DELETE:
    _Obj'deleteAll()

    IF _DetailChannel \
     THEN CLOSE (_DetailChannel, ERR=*NEXT)
    IF _HeaderChannel \
     THEN CLOSE (_HeaderChannel, ERR=*NEXT)

    _DetailChannel=0
    _HeaderChannel=0

    RETURN
```

### Method Implementation

Method implementations go **outside** the class definition block, at labels matching the declared label names:

```pxplus
DEF CLASS "*obj/ArrayList"
    FUNCTION append(value$) APPEND_STR
    FUNCTION find(value$, caseInsensitive) FIND_STR
END DEF

ON_CREATE:
    OPEN (HFN)"*memory*"
    memFileHandle=LFO
    RETURN

ON_DELETE:
    CLOSE (memFileHandle)
    RETURN

APPEND_STR:
    ENTER value$

    LOCAL index = _OBJ'size()+1
    RETURN _OBJ'insert(index, value$)

FIND_STR:
    ENTER value$, (caseInsensitive=0)

    LOCAL index=0, numElements=_OBJ'size(), searchVal$=value$

    IF numElements=0 \
     THEN RETURN 0

    FOR local i=0 TO numElements-1
        LOCAL indexVal$ = RCD(memFileHandle, IND=i)
        IF caseInsensitive \
         THEN indexVal$=LCS(indexVal$);
              searchVal$=LCS(searchVal$)
        IF indexVal$=searchVal$ \
         THEN index=i+1;
              BREAK
    NEXT i

    RETURN index
```

### Accessing Properties and Calling Methods

```pxplus
! Property access
obj'propertyname$ = "value"
x = obj'count
obj'_isLocked = 1

! Method call (no CALL keyword needed)
obj'Initialize()
result$ = obj'Load(key$)
new_item = obj'AddNew()

! Chained:
result$ = %sy_obj'Company'currencyCode$
```

### Self-reference Inside a Class

Inside a class method, refer to the object itself as `_obj` (not `THIS`):

```pxplus
! Inside a method of *obj/ArrayList:
_OBJ'size()          ! call own method
_OBJ'insert(i, v$)   ! call own method
_OBJ'clear()

! Inside a method of *FW/Entity:
_Obj'Initialize()
_Obj'deleteAll()
_Obj'AddNew()
_Obj'LoadData()
```

Properties are accessed **directly by name** inside the class (no prefix):

```pxplus
! Inside class method - access own properties directly:
_HeaderChannel = LFO       ! CORRECT
_LoadedKey$ = ""           ! CORRECT

! NOT: THIS'_HeaderChannel = LFO   (WRONG)
```

### Creating and Deleting Objects

```pxplus
! Create
obj = NEW("*obj/ArrayList")
system = NEW("*FW/System")
detail = NEW("*FW/EntityDetail", header_table$, detail_table$)

! Create with automatic cleanup when program ends
obj = NEW("*obj/ArrayList" FOR PROGRAM)

! Create with automatic cleanup when parent object deleted
child = NEW("*obj/ChildClass" FOR OBJECT)

! Delete
DROP OBJECT obj
DELETE OBJECT obj

! Safe delete pattern:
IF resManager \
 THEN DROP OBJECT resManager;
      resManager=0

! Reference counting (for group/collection classes)
X = REF(ADD obj_id)    ! increment ref count
X = REF(DROP obj_id, ERR=*NEXT)   ! decrement ref count
```

### Object Reference Safety

Object variables hold **numeric reference numbers**. After `DROP OBJECT`, the number remains in the variable but accessing it causes **silent program termination** (no error).

```pxplus
! WRONG - _obj will be stale after drop:
result_list'Append(_obj)
RETURN result_list

! CORRECT - create a new object:
new_obj = NEW("ClassName", params...)
result_list'Append(new_obj)
RETURN result_list
```

Symptoms of stale object references:
- Program exits silently with no error message
- Works for first item, fails on subsequent ones

### Dynamic Property Access

Using `EXECUTE` and `EVS`/`EVN` for dynamic property access (from `*FW/Property`):

```pxplus
! Get property by name:
FUNCTION getProperty$(_propName$) = EVS("_Obj'"+_propName$)
FUNCTION getProperty(_propName$)  = EVN("_Obj'"+_propName$)

! Set property by name:
Do_SetProperty_STR:
    ENTER _propName$, _Value$
    EXECUTE "_obj'"+_propName$+"=_value$"
    RETURN 1
```

### ADD PROPERTY

Dynamically add IOL-defined properties to a class at runtime:

```pxplus
ADD PROPERTY IOL=IOL(_HeaderChannel)   ! add fields from file's IOL
ADD PROPERTY IOL=dcl_iolist            ! add fields from a named IOL
```

---

## File I/O

### Opening Files

```pxplus
! Basic open (use HFN for next available channel)
OPEN (HFN) "filename.dat"
chan = LFO    ! save the channel number immediately after OPEN

! Open with IOL auto-detection
OPEN (HFN, IOL=*) "file.dat"; chan=LFO

! Open for program lifetime (auto-close when program ends)
OPEN (HFN, IOL=*) "file.dat" FOR PROGRAM; chan=LFO

! Open read-only
OPEN INPUT (chan) "file.txt"; chan=LFO

! Open write-only
OPEN OUTPUT (chan) "file.txt"

! Open with error handling
OPEN (HFN, IOL=*, ERR=open_fail) TABLE _HeaderTable$

! Open with timeout
OPEN (1, ERR=*NEXT, TIM=10) "file.dat"

! Open in-memory file
OPEN (HFN) "*memory*"; chan=LFO

! Open with purge (truncate/create)
OPEN PURGE (HFN, ISZ=-1) output_file$

! Open table (database table)
OPEN (HFN, IOL=*) TABLE "FW_Currency"
OPEN OBJECT (HFN, IOL=dcl_iolist) "*fw/providex.dcl"

! Open TCP connection
OPEN (HFN, ERR=ConnectOpenErr) "[tcp]"+server$+";"+port$
OPEN (HFN, TIM=servertime) "[tcp]"+smtp$+";secure"

! Open lock (exclusive)
OPEN LOCK (ch, ERR=ERROR_HANDLER) "output.html"

! LFO = Last File Opened (save immediately, changes with every OPEN)
open (hfn) "file.dat"
chan = LFO    ! capture before anything else
```

### Reading Records

```pxplus
! Sequential read
READ (chan) var1$, var2

! Keyed read
READ (chan, KEY=key$) record$
READ (chan, KEY=key$, KNO=1) record$   ! key number 1

! Read by index
READ (chan, IND=index) record$

! Read full record
READ RECORD (chan, KEY=key$) rec$

! Read into IOL
READ DATA FROM rec$ TO IOL=IOL(chan)

! Read into IOL from empty (initialize to defaults)
READ DATA FROM "" TO IOL=IOL(_HeaderChannel)

! Extract with lock
EXTRACT (chan, KEY=key$) record$
EXTRACT (chan, KEY=key$, BSY=*NEXT) record$   ! skip if busy

! Read with error handling
READ (chan, KEY=key$, DOM=*NEXT) record$       ! skip if not found
READ (chan, KEY=key$, ERR=handler) record$
READ RECORD (chan, TIM=10, ERR=timeout_label) R$

! Read with separator
READ DATA FROM smtp$, SEP=_SEP$ TO serv$, port$, user$, pass$

! Low-level record read (by index, zero-based)
value$ = RCD(memFileHandle, IND=i)
```

### Writing Records

```pxplus
! Sequential write
WRITE (chan) var1$, var2

! Keyed write
WRITE (chan, KEY=key$) record$

! Write full record
WRITE RECORD (chan) content$
WRITE RECORD (chan, KEY=key$) rec$

! Insert new record
INSERT (chan, KEY=key$) record$

! Write by index (memory file)
WRITE RECORD (memFileHandle, IND=index-1) value$

! Remove record
REMOVE (chan, KEY=key$)
REMOVE (chan, IND=i)

! Release lock (after EXTRACT)
EXTRACT RELEASE (chan)
```

### File Positioning and Range Queries

```pxplus
! Select range and iterate
SELECT * FROM chan BEGIN key_start$ END key_end$
NEXT RECORD    ! move to next in SELECT

! Key navigation functions
KEF(chan, KNO=0)    ! first key
KEN(chan, KNO=0)    ! next key
KEP(chan, KNO=0)    ! prior key
KEL(chan, KNO=0)    ! last key
KEC(chan, KNO=0)    ! current key
KEY(chan)           ! current key (simpler form)
```

### File Information

```pxplus
LFO                        ! last file opened (channel number)
PTH(channel)               ! full path of open file
FIN(LFO, "NUMREC")         ! number of records
FIN(LFO, "FILE_CREATE")    ! file creation info
FIB(chan)                  ! file information block (raw)
IOL(chan)                  ! iolist string for open file
```

### Closing Files

```pxplus
CLOSE (chan)
CLOSE (chan1), (chan2), (chan3)    ! close multiple
CLOSE (chan, ERR=*NEXT)           ! ignore close errors
```

### Creating Files

```pxplus
! Create keyed file
SERIAL output_file$
ch = UNT    ! or HFN
OPEN LOCK (ch, ERR=ERROR_HANDLER) "output.html"
WRITE (ch) content$
CLOSE (ch)

! Create from scratch
SERIAL "filename.dat"

! Erase file
ERASE "filename.dat", ERR=*NEXT
```

### IOLIST Declaration

Named IOLISTs define the field layout for a record:

```pxplus
dcl_iolist: \
    IOLIST name$, length$, type$, description$

Dummy_IOL: \
    IOLIST _DummyKey$
```

---

## Error Handling

### Error Options on Statements

These modifiers are appended to I/O and other statements:

| Option | Meaning |
|--------|---------|
| `ERR=*NEXT` | Continue to next statement on error |
| `ERR=*RETURN` | Return from current subroutine on error |
| `ERR=*BREAK` | Break from current loop on error |
| `ERR=LABEL` | Jump to named label on error |
| `DOM=*NEXT` | Continue if record not found (domain error) |
| `DOM=LABEL` | Jump to label if record not found |
| `BSY=*SAME` | Retry same operation if file/record busy |
| `BSY=*NEXT` | Continue if busy |
| `TIM=n` | Timeout after n seconds |
| `END=*NEXT` | Continue if end of file |
| `END=*BREAK` | Break loop at end of file |
| `END=LABEL` | Jump to label at end of file |

```pxplus
OPEN (HFN, IOL=*, ERR=open_fail) TABLE _HeaderTable$
READ (chan, KEY=key$, DOM=*NEXT) record$
EXTRACT (chan, KEY=key$, BSY=*NEXT) record$
READ RECORD (X, TIM=10, ERR=NOHELO) R$
CLOSE (chan, ERR=*NEXT)
```

### SETERR / SETESC

```pxplus
SETERR ERROR_HANDLER    ! set global error handler
SETESC ESCAPE_HANDLER   ! set global escape handler
```

### TRY / CATCH / FINALLY / END_TRY

Modern structured error handling:

```pxplus
TRY
    ! Code that might fail
    OPEN (HFN)"*memory*"
    memFileHandle=LFO
    status=1
CATCH e
    ! Error handling - e is error number
    error_msg$ = MSG(e)
    status=0
FINALLY
    ! Cleanup - always executes
    IF obj > 0 THEN DROP OBJECT obj
END_TRY
```

**IMPORTANT rules for TRY blocks**:
1. Declare ALL local variables **before** the TRY block
2. Do NOT use `EXIT` or `EXITTO` inside a TRY block — set a result variable instead
3. `RETURN` goes after `END_TRY`

```pxplus
METHOD_LABEL:
    ENTER param1$, (param2$="")

    ! Declare ALL local variables BEFORE TRY
    LOCAL result$
    LET result$ = ""

    TRY
        ! ... logic ...
        result$ = "success value"
    CATCH
        result$ = ""
    FINALLY
        IF obj > 0 THEN DROP OBJECT obj
    END_TRY

    RETURN result$   ! RETURN is AFTER END_TRY
```

### Error Information

```pxplus
ERR         ! last error number
MSG(ERR)    ! error message for last error
MSG(n)      ! error message for error n
TCB(num)    ! task control block value
```

### Traditional Error Handler Pattern

```pxplus
OPEN (HFN, IOL=*, ERR=Make_Table) TABLE Tablename$
CLOSE (LFO)
RETURN

Make_Table:
    ! handle error - create the table
    RETURN

Create_DDF:
    CALL "*pvxfiles;Check", ERR=Create_failed, "TableDefinition", "providex.ddf", EMSG$, "S"
    GOTO Install_tables

Create_failed:
    MSGBOX EMSG$, "Error", "!"
    END
```

---

## String Operations

### Concatenation

```pxplus
full$ = first$ + " " + last$
smtp$ = serv$ + ";" + port$
```

### Special Character Literals

```pxplus
$0D0A$   ! carriage return + line feed (CRLF)
$0A$     ! line feed only
$0D$     ! carriage return only
$09$     ! tab
$22$     ! double quote
$00$     ! null byte
$7F$     ! DEL character
SEP      ! system field separator character (variable, no $)
QUO      ! quote character (variable)
```

### String Functions

```pxplus
LEN(string$)              ! length
MID(string$, start, len)  ! substring (1-based)
MID(string$, -1)          ! last character
POS("search" = string$)   ! find substring position (0 if not found)
POS("search" = string$, -1)  ! find from end
UCS(string$)              ! uppercase
LCS(string$)              ! lowercase
STP(string$)              ! strip leading/trailing spaces
PAD(string$, 20)          ! pad to length 20 (left-align)
PAD(string$, -20)         ! right-align pad
STR(number)               ! numeric to string
STR(value:"000000")       ! format with zero padding
STR(i:"00")               ! two-digit zero-padded
NUM(string$)              ! string to numeric
NUM(string$, ERR=*NEXT)   ! safe conversion
NUL(string$)              ! test if null/empty (returns 1 if null)
SUB(string$, old$, new$)  ! substitute/replace
CVS(string$, flags)       ! convert string (e.g., "ASCII:BASE64")
```

### Substring Access by Position

```pxplus
string$(1, 6)        ! characters 1-6 (start, length)
string$(start, len)  ! substring
mid(string$, -1)     ! last character
```

### Pattern Matching (MSK)

```pxplus
match = MSK(string$, pattern$)   ! returns position of match (0 if no match)
IF MSK(string$, "^[A-Z]+$") THEN ...
IF MSK(response$, "250.[Tt][Ll][Ss]") <> 0 THEN ...

! After a MSK call:
match_start = MSK(1)   ! get match position
match_length = MSL     ! get match length
```

### String Formatting

```pxplus
STR(num:"000000")        ! zero-padded 6 digits
STR(value:"0.00")        ! decimal format
JST(string$, format$)    ! justify string
```

---

## Arrays

### Fixed Arrays

```pxplus
DIM array$[10]          ! string array, 1-10
DIM matrix[5,5]         ! 2D numeric array
DIM array[1:ITEMALLOC]  ! explicit range start:end
```

### Dynamic Arrays (Resize)

```pxplus
DIM array[*]            ! dynamic, size at runtime
REDIM array[1:new_size] ! resize

! Grow pattern:
IF count >= ITEMALLOC THEN {
    IF count = 0 THEN {
        ITEMALLOC = 50
        DIM ITEMTBL[1:ITEMALLOC]
    } ELSE {
        ITEMALLOC += 50
        REDIM ITEMTBL[1:ITEMALLOC]
    }
}
```

### Associative Arrays

```pxplus
! No DIM needed — just use with string key:
rates$["USD"] = "1.0"
rates$["EUR"] = "1.12"

! Declare local associative array:
LOCAL rates$
DIM rates$     ! declares as associative (no size = associative)

! Access:
value$ = rates$[key$]

! Array copy with range:
DIM X[1:count]
X{ALL} = ItemTbl{ALL}   ! copy all elements
ItemTbl{1:count} = X{ALL}
```

### Array Iteration

```pxplus
! Iterate associative array (INDEX syntax):
FOR currency$ INDEX rates${ALL}
    rate$ = rates$[currency$]
    ! use currency$ and rate$
NEXT

! Array slice access:
array{ALL}              ! all elements
array{1:5}              ! elements 1 to 5
array[*] = value        ! assign all elements
```

### LOCAL Arrays

```pxplus
! Must be two statements:
LOCAL var$
LOCAL DIM array$[10]

! Or in a class LOCAL section:
LOCAL itemKeys$
LOCAL ItemTbl, ItemAlloc
```

---

## Date and Time

Always use `DTE` for date/time operations:

```pxplus
! Get today's date formatted
today$ = DTE(0:"YYYYMMDD")      ! e.g. "20260515"
today$ = DTE(0:"YYYY-MM-DD")    ! e.g. "2026-05-15"

! Convert JUL date to formatted string
date$ = DTE(JUL(year, month, day):format$)

! Get current time
time_str$ = DTE(JUL(0,0,0)+(TIM/24)+(TCB(44)/60/60/24), *:"%Hz:%mz:%sz")

! JUL — convert date to Julian day number
j = JUL(2026, 5, 15)
j = JUL(0, 0, 0)   ! today

! DAY — current day number
d = DAY

! TIM — current time (seconds since midnight)
t = TIM

! SETDAY — set system date
SETDAY date_value
```

---

## Built-in Functions Reference

### Numeric / Math

| Function | Description |
|----------|-------------|
| `ABS(n)` | Absolute value |
| `INT(n)` | Integer part (truncate) |
| `ROUND(n, d)` | Round to d decimal places |
| `SQR(n)` | Square root |
| `MOD(n, m)` | Modulo |
| `MAX(a, b)` | Maximum |
| `MIN(a, b)` | Minimum |
| `SGN(n)` | Sign (-1, 0, 1) |
| `LOG(n)` | Natural logarithm |
| `EXP(n)` | e^n |
| `SIN(n)` | Sine |
| `COS(n)` | Cosine |
| `TAN(n)` | Tangent |
| `RND(n)` | Random number |

### String

| Function | Description |
|----------|-------------|
| `LEN(s$)` | String length |
| `MID(s$, start, len)` | Substring |
| `POS(sub$ = s$)` | Find substring position |
| `UCS(s$)` | Uppercase |
| `LCS(s$)` | Lowercase |
| `STP(s$)` | Strip whitespace |
| `PAD(s$, n)` | Pad to length n |
| `STR(n)` | Number to string |
| `NUM(s$)` | String to number |
| `NUL(s$)` | Test if null/empty |
| `SUB(s$, old$, new$)` | Replace substring |
| `CVS(s$, flags)` | Convert string encoding |
| `MSK(s$, pattern$)` | Pattern match |
| `JST(s$, fmt$)` | Justify string |
| `CHR(n)` | ASCII character |
| `ASC(s$)` | ASCII value of first char |
| `HTA(s$)` | Hex to ASCII |
| `ATH(s$)` | ASCII to hex |
| `CMP(a$, b$)` | Compare strings |
| `SRT(s$)` | Sort string |
| `DEC(s$)` | Decode binary string |
| `CPL(expr$)` | Compile/evaluate expression |

### File / Channel

| Function | Description |
|----------|-------------|
| `LFO` | Last file opened (channel number) |
| `HFN` | Next available channel (also `UNT`) |
| `UNT` | Next available channel (same as HFN) |
| `PTH(chan)` | Path of open file |
| `IOL(chan)` | IOLIST string of open file |
| `FIN(chan, info$)` | File information |
| `FIB(chan)` | File information block |
| `REC(iol_var)` | Build record string from IOL values |
| `RCD(chan, IND=i)` | Read record by index (returns string) |
| `KEY(chan)` | Current key of open file |
| `KEF(chan, KNO=n)` | First key |
| `KEN(chan, KNO=n)` | Next key |
| `KEP(chan, KNO=n)` | Prior key |
| `KEL(chan, KNO=n)` | Last key |
| `KEC(chan, KNO=n)` | Current key |
| `KGN(rec$, keydef$, kno)` | Generate key from record |

### System / Environment

| Function | Description |
|----------|-------------|
| `TCB(n)` | Task control block value |
| `PRM('XX')` | Read/set system parameter |
| `SET_PARAM 'XX'=n` | Set system parameter |
| `SYS` | System information |
| `ENV(var$)` | Environment variable |
| `DIR(path$)` | Directory listing |
| `DSK(drive$)` | Disk information |
| `MEM(n)` | Memory information |
| `ERR` | Last error number |
| `MSG(n)` | Error message text |
| `DAY` | Current day number |
| `TIM` | Current time (seconds since midnight) |
| `SSN` | System serial/version number |

### Object / Reference

| Function | Description |
|----------|-------------|
| `NEW("class")` | Create new object |
| `REF(ADD obj)` | Increment reference count |
| `REF(DROP obj)` | Decrement reference count |
| `OBJ(n)` | Object information |
| `NUL(obj)` | Test if null/zero |
| `EVS(expr$)` | Evaluate expression, return string |
| `EVN(expr$)` | Evaluate expression, return numeric |

### Conditional / Selection

| Function | Description |
|----------|-------------|
| `TBL(cond, a, b)` | If cond then a else b |
| `TRY(expr, default)` | Return expr, or default on error |
| `IND(n)` | Index value |
| `OPT(n)` | Option value |

### Date / Time

| Function | Description |
|----------|-------------|
| `DTE(n:format$)` | Format date/time |
| `JUL(y, m, d)` | Convert to Julian day number |
| `DAY` | Today's day number |
| `TIM` | Current time in seconds |
| `TMR` | Timer value |

---

## Directives Reference

Complete list of PxPlus directives (keywords):

`ACCEPT`, `ADD`, `ADDR`, `AUTO`, `BEGIN`, `BREAK`, `BYE`, `CALL`, `CASE`, `CATCH`, `CHART`, `CLEAR`, `CLIP_BOARD`, `CLOSE`, `CONTINUE`, `CONTROL`, `CREATE`, `CTL`, `CWDIR`, `DATA`, `DAY_FORMAT`, `DEF`, `DEFAULT`, `DEFCTL`, `DEFPRT`, `DEFTTY`, `DELETE`, `DICTIONARY`, `DIM`, `DIRECT`, `DIRECTORY`, `DISABLE`, `DROP`, `DUMP`, `EDIT`, `ELSE`, `ENABLE`, `END`, `END_IF`, `END_TRY`, `ENDTRACE`, `ENTER`, `ERASE`, `ERROR_HANDLER`, `ESCAPE`, `EXCEPT`, `EXECUTE`, `EXIT`, `EXITTO`, `EXTRACT`, `FILE`, `FINALLY`, `FIND`, `FLUSH`, `FOR`, `FROM`, `FUNCTION`, `GET`, `GOSUB`, `GOTO`, `HIDE`, `IF`, `INDEX`, `INDEXED`, `INPUT`, `INSERT`, `INVOKE`, `IOLIST`, `KEY`, `KEYED`, `LET`, `LIKE`, `LINE_SWITCH`, `LIST`, `LOAD`, `LOCAL`, `LOCK`, `LONG_FORM`, `MERGE`, `MESSAGE_LIB`, `MNEMONIC`, `MSGBOX`, `NEXT`, `NEXT RECORD`, `OBJECT`, `OBTAIN`, `OFF`, `ON`, `OPEN`, `PERFORM`, `POP`, `POPUP_MENU`, `PRECISION`, `PREFIX`, `PREINPUT`, `PRINT`, `PROCESS`, `PROGRAM`, `PROPERTY`, `PURGE`, `QUIT`, `RANDOMIZE`, `READ`, `RECORD`, `REDIM`, `REFILE`, `RELEASE`, `REMOVE`, `RENAME`, `REPEAT`, `REQUIRED`, `RESET`, `RESTORE`, `RETRY`, `RETURN`, `ROUND`, `RUN`, `SAME`, `SAVE`, `SELECT`, `SERIAL`, `SERVER`, `SET`, `SET_PARAM`, `SETCTL`, `SETDAY`, `SETDRIVE`, `SETERR`, `SETESC`, `SETFID`, `SETMOUSE`, `SETTIME`, `SETTRACE`, `SHORT_FORM`, `SHOW`, `SORT`, `START`, `STATIC`, `STEP`, `STOP`, `SWAP`, `SWITCH`, `TABLE`, `THEN`, `TIME`, `TO`, `TRANSLATE`, `TRY`, `UNIQUE`, `UNLOCK`, `UNTIL`, `UPDATE`, `VIA`, `WAIT`, `WEND`, `WHERE`, `WHILE`, `WINDOW`, `WITH`, `WRITE`

---

## Special Patterns and Idioms

### CALL — Calling External Programs

```pxplus
CALL "*pvxfiles;Check", ERR=Create_failed, "TableDefinition", "providex.ddf", EMSG$, "S"
CALL "*dict/dd_updt;Update_Physical", Tablename$, "", "", E$
```

Format: `CALL "program;entrypoint", params...`

### EXECUTE — Dynamic Code Execution

```pxplus
EXECUTE "_obj'"+_propName$+"=_value$"
EXECUTE SUB(X$, "*fw/", "")
```

### VIA — Indirect Variable Assignment

```pxplus
VIA var$ = value    ! set variable named by var$
```

### PROCESS — Run Nomads Panel

```pxplus
PROCESS "panel_name"
PROCESS EVENT "SentEMail", TO$, subject$
```

### MESSAGE_LIB — Localization

```pxplus
MESSAGE_LIB ADD "*web/messages"    ! add message library
```

### SETTRACE — Debug Tracing

```pxplus
IF %trace_email \
 THEN SETTRACE PRINT "*web/smtp: Connected to SMTP server"
```

### SET_PARAM / PRM

```pxplus
PRECISION 6               ! set numeric precision
SET_PARAM 'EZ'=1          ! set system parameter
SET_PARAM -'OP'           ! clear system parameter
svprm_op = PRM('OP')      ! save parameter value
```

### HFN vs UNT

Both return the next available file channel number. `HFN` is the older form; `UNT` is more common in newer code:

```pxplus
OPEN (HFN) "file.dat"; chan=LFO    ! HFN style
chan = UNT                          ! UNT style (get channel before open)
OPEN (chan) "file.dat"
```

### GOSUB Helper Routines (vs FUNCTION declarations)

Use plain labels with GOSUB for internal helpers. Do **not** declare them as `FUNCTION HIDE`:

```pxplus
! CORRECT - just a label:
SOME_METHOD:
    GOSUB CLEAR_STATE
    RETURN result$

CLEAR_STATE:
    last_error$ = ""
    last_response$ = ""
    RETURN

! WRONG - don't declare internal GOSUBs as FUNCTIONs:
! FUNCTION HIDE __ClearError() CLEAR_ERROR_LABEL   (wrong)
```

### SWITCH with Comparison Operators in CASE

```pxplus
SWITCH MID(ssn, 1, 4)
    CASE <"0414"        ! less than comparison
        ! code
        BREAK
    DEFAULT
        ! code
END SWITCH
```

### FNxxx$ — User-defined Functions

PxPlus supports user-defined functions prefixed with `FN`:

```pxplus
machine$ = fnGetLocalMachineName$
response$ = fnGetData$(smtp, $0D0A$, servertime)
status = fnGetStatus(response$)
```

These are defined as function labels earlier in the program.

### FOR local — Inline LOCAL Declaration

```pxplus
FOR local i = 1 TO count
    ! i is local to the loop
NEXT i

FOR local index = 1 TO array'size()
    ! ...
NEXT index
```

### LOCAL inline initialization

```pxplus
LOCAL index = 0, numElements = _OBJ'size(), searchVal$ = value$
LOCAL result$ = ""
LOCAL status = 0
```

### Dynamic Class with DEF OBJECT

```pxplus
DEF OBJECT resManager, "[wdx]com.pvx.ooadide.eventmanager.ResourceManager", ERR=*NEXT
```

---

## Framework Patterns

### Entity / Detail Pattern

`*FW/Entity` + `*FW/EntityDetail` model a header/lines document (like invoice + line items).

```pxplus
! Create entity
entity = NEW("*FW/Entity", "FW_Invoice", "FW_InvoiceLine", "*FW/InvoiceDetail")

! Load by key
IF entity'Load(invoice_key$) THEN {
    ! Access header fields directly (added via ADD PROPERTY)
    entity'invoiceDate$ = DTE(0:"YYYYMMDD")

    ! Add new detail line
    line = entity'AddNew()
    line'itemCode$ = "PROD001"
    line'quantity = 5

    ! Save
    entity'Save()
}

! Clean up
DROP OBJECT entity
```

### PRECISION Saving Pattern

```pxplus
SAVEPRC = PRC
PRECISION 8
! ... high-precision calculations ...
PRECISION SAVEPRC
```

### Record field access via IOL

After opening a file with `IOL=*`, field names from the file definition become directly accessible:

```pxplus
OPEN (HFN, IOL=*) TABLE "FW_Currency"; chan=LFO

! Field names are now accessible:
READ (chan, KEY=currency_code$)
! Now can access: name$, symbol$, exchangeRate, etc.
name$ = "US Dollar"
WRITE (chan, KEY=currency_code$)
```

### Checking Object Existence

```pxplus
IF obj THEN ...          ! truthy if non-zero (object exists)
IF obj = 0 THEN ...      ! object was not created or was dropped
IF NOT(NUL(obj)) THEN ... ! same as IF obj
```

### Framework Error Handling

```pxplus
! Exit with framework error code:
EXIT %sy_OBJ'exit_with_error("FW_HDRTBL_OPEN_FAIL")
EXIT %sy_OBJ'exit_with_error("FW_NO_DTLOBJ")
```

---

## Debugging

### Common Error Numbers

| Error | Description |
|-------|-------------|
| `#12` | File not found |
| `#20` | Syntax error |
| `#21` | Invalid label/line number |
| `#26` | Variable name error |
| `#27` | Subscript/index error |
| `#42` | Invalid index / out of range |
| `#46` | String too long |
| `#48` | Invalid data / unknown character |

### Error Scanner

From within PxPlus:

```
RUN "*tools/errscn"
```

### Debug Techniques

```pxplus
! Enable error logging
SET_PARAM 'NE'=1    ! show native errors
SET_PARAM 'PC'=0    ! disable program cache

! Set global error handler
SETERR ERROR_HANDLER

! Trace printing
SETTRACE PRINT "Debug: value="+STR(value)

! Print to check values
PRINT "DEBUG: chan=", chan, " key=", key$
```

### Silent Exit Diagnosis

If a program exits silently with no error, suspect a stale object reference. Verify:

```pxplus
! Print object ref before use:
PRINT "obj ref=", obj
result = obj'SomeMethod()  ! crashes here if stale
```

---

## Common Pitfalls

1. **String suffix**: Every string variable must end with `$`
2. **FOR-NEXT always runs once**: Guard with `IF count > 0` when loop might not need to execute
3. **No RETURN inside FOR loops**: Use flag + BREAK pattern
4. **LFO changes with each OPEN**: Save it immediately: `chan = LFO`
5. **EXTRACT locks records**: Always have a plan to unlock (`EXTRACT RELEASE`)
6. **Object refs after DROP**: Accessing a dropped object causes silent program termination
7. **NOT with properties**: Must use `NOT(expr)`, not `NOT expr`
8. **Associative array iteration**: Use `FOR key$ INDEX array${ALL}`, not `array[ALL]`
9. **LOCAL arrays**: Requires two statements: `LOCAL arr$` then `LOCAL DIM arr$[10]`
10. **Variables in TRY**: Declare all LOCAL variables before the TRY block, RETURN after END_TRY
11. **Comments**: Use `!` not `rem` (though both work)
12. **GOSUB vs FUNCTION**: Internal helpers should use plain GOSUB labels, not FUNCTION declarations
13. **1-based arrays**: PxPlus arrays default to 1-based indexing
14. **Case insensitive**: All keywords are case-insensitive; conventionally written in UPPERCASE
15. **Partial key reads**: Use SELECT + NEXT RECORD for partial key searches, not READ with partial key
