PBXA64 Help Index

PBXA64 Compiler Help

Getting Started

Overview | Project introduction, pipeline, supported languages, copyright
Command-Line Options | All compiler flags and switches

BASIC Language - Statements

PRINT | Console output with WinAPI WriteFile
Assignment & FUNCTION = expr | Variable assignment and return values
IF / ELSE / ELSEIF / END IF | Conditional branching with CMP + Jcc
FOR / NEXT | Counted loops with STEP
WHILE / WEND | Conditional loops
GOTO | Unconditional jump
GOSUB / RETURN | Local subroutines with stack-based return address
CALL (External WinAPI) | x64 calling convention, argument staging
DECLARE | External DLL function declarations
SELECT CASE | Multi-way branching with value/ranges
DO / LOOP | Flexible loop with WHILE/UNTIL conditions
EXIT / ITERATE | Early loop exit and iteration

BASIC Language - Data & I/O

String Operations | Concatenation, LEN, STRPTR, VARPTR, comparison
String Functions | LEFT$, RIGHT$, MID$, TRIM$, UCASE$, LCASE$
Arrays & REDIM | Fixed/dynamic arrays, bounds, element access
LBOUND / UBOUND | Array bounds queries
File I/O | OPEN, PRINT#, INPUT#, CLOSE, SEEK, GET, PUT
Console INPUT | Reading from stdin via ReadFile
Expressions & Operators | Arithmetic, comparison, boolean, string comparison

BASIC Language - Core

Language Overview | Module structure, supported feature matrix
Preprocessor Directives | #COMPILE, #DIM ALL, #INCLUDE, #PCODE
Types & Variables | Data types, suffixes, literals, declarations
Function Prolog/Epilog | Stack frame setup, parameter capture, return

PILOT Language

PILOT Overview | Command reference, variables, pattern matching
PILOT Commands Detail | T:, A:, M:, Y:, N:, C:, J:, U:, E:, R:, S:, *LABEL

Examples

Example Programs PB, PILOT, and MGC code samples


Command-Line Options

Usage

pbxa64 [options] <input files>

Options Reference

Option | Description
-o <path> | Output file path. Default depends on mode: a.exe (link), a.obj (compile-only), a.asm (assembly).
-c | Compile only — produce a .o COFF object file without linking.
-S | Emit assembly only — output .asm text file instead of binary. Includes comment header with compiler version and command line.
--asm:<path> | Save generated assembly to file, then continue compiling and linking (unlike -S which stops). Assembly includes comment header.
--dump-tokens | Dump the lexer token stream to stdout. Useful for debugging lexer behavior.
--dump-ast, --emit-ast | Dump the abstract syntax tree to stdout after parsing.
--dump-ir, --emit-ir | Dump the intermediate representation to stdout after IR lowering.
-O0 | No optimization (default). Fastest compilation, no transforms applied.
-O1 | Basic optimization — enables simple peephole and local transforms.
-O2 | Aggressive optimization — enables all available optimization passes.
-O3 | Maximum optimization — highest optimization level.
-v, --verbose | Verbose output — prints additional diagnostic information during compilation.
-h, --help | Show the help message and exit.
--version | Show version banner and exit.

Input Files

Extension | Frontend | Description
.pb | PowerBASIC | Primary PowerBASIC source file
.bas | PowerBASIC | PowerBASIC source file (alternate)
.pbi | PowerBASIC | PowerBASIC include file
.mgc | MGC | C-like intermediate language source

Examples

# Compile and link to executable
pbxa64 hello.pb -o hello.exe

# Compile to object file only
pbxa64 -c program.bas -o program.obj

# Generate assembly listing
pbxa64 -S program.pb -o program.asm

# Debug token stream
pbxa64 --dump-tokens hello.pb

# Debug AST
pbxa64 --dump-ast hello.pb

# Debug IR with optimization
pbxa64 --dump-ir -O1 program.mgc


PBXA64 vs PowerBASIC Compatibility

PBXA64 V22 vs PowerBASIC Compatibility

YES = Fully supported PART = Partial NO = Not implemented

Compiler Directives

Directive | PBXA64 | Notes
#COMPILE EXE/DLL | YES | DLL/GUI modes rejected with diagnostic (V22)
#DIM ALL / NONE | YES | #DIM ALL only
#INCLUDE / ONCE / INCLUDELONCE | YES | Recursive resolution with depth tracking (V22)
#PCODE / #ENDP | YES | PILOT embedded blocks
#IF / #ELSEIF / #ELSE / #ENDIF / $IF | YES | Conditional compilation (V21)
#DEFINE / #UNDEF / %DEF() | YES | Symbol definition and detection (V21/V22)
MACRO / MACROFUNCTION / MACROTEMP | YES | Full macro expansion engine (V22)
#RESOURCE | NO | PE resources
#DEBUG / #TOOLS / #OPTIMIZE | NO |
#REGISTER / #ALIGN / #STACK | NO |

Declarations & Procedures

Feature | PBXA64 | Notes
FUNCTION name(...) AS type | YES |
SUB name(...) | YES |
FUNCTION PBMAIN() AS LONG | YES | Entry point
DECLARE FUNCTION/SUB ... LIB ... ALIAS ... | YES |
DECLARE ... ALIAS "#ordinal" | YES | V13+
DECLARE ... VARARG | YES | V17+
BYVAL / BYREF | YES |
OPTIONAL / OPT | NO |
CALLBACK FUNCTION | YES | V17+
MACRO / MACROFUNCTION | YES | Full expansion engine (V22)
CLASS / INTERFACE / METHOD / PROPERTY | NO | COM objects

Variable Declarations

Feature | PBXA64 | Notes
LOCAL | YES |
DIM | YES |
STATIC | YES |
GLOBAL | YES | Limited support
THREADED | NO |
REGISTER | NO |

Data Types

Type | Suffix | PBXA64 | Notes
BYTE | ? | YES |
WORD | ?? | YES |
DWORD | ??? | YES |
INTEGER | % | YES |
LONG | & | YES |
QUAD | && | YES |
SINGLE | ! | PART | Partial SSE lowering
DOUBLE | # | PART | Partial SSE lowering
EXT | ## | PART | Partial x87
CURRENCY | @ | YES |
STRING | $ | PART | Fixed buffers; heap runtime in V19
WSTRING | $$ | PART | Scalar only, heap runtime in V19
ASCIIZ / PTR |  | YES |
VARIANT |  | NO |
OBJECT / GUID |  | NO |

User-Defined Types

Feature | PBXA64 | Notes
TYPE ... END TYPE | PART | Numeric/STRING/nested fields, member access, deep copy (V15)
UNION ... END UNION | NO |
ENUM ... END ENUM | NO |

Control Flow Statements

Statement | PBXA64 | Notes
IF / ELSEIF / ELSE / END IF | YES |
FOR var = start TO end [STEP n] / NEXT | YES |
WHILE / WEND | YES |
DO [WHILE|UNTIL] / LOOP [WHILE|UNTIL] | YES |
SELECT CASE / CASE / CASE ELSE / END SELECT | YES |
GOTO label | YES |
GOSUB label / RETURN | YES |
EXIT FOR|WHILE|DO|FUNCTION|SUB | YES |
ITERATE FOR|WHILE|DO | YES |
CALL name(args) | YES |

I/O Statements

Statement | PBXA64 | Notes
PRINT expr | YES |
INPUT [prompt;] var | YES | Console ReadFile
PRINT #n, expr | YES |
INPUT #n, var | YES |
LINE INPUT #n, var$ | YES | CR/LF-aware (V13)
LINE INPUT prompt; var$ | NO |
OPEN FOR INPUT|OUTPUT|APPEND|BINARY|RANDOM | YES | RANDOM/BINARY in V19
OPEN ... ENCODING UTF16LE_BOM|AUTO|UTF8|ANSI | YES | V17-V19
CLOSE #n | YES |
GET #n, [pos,] var | YES |
PUT #n, [pos,] var | YES |
SEEK #n, pos | YES |
PUT #n UDT with STRING/WSTRING | YES | V20: descriptor-safe serialization
WRITE #n | NO |
LPRINT | NO |

File Functions

Function | PBXA64 | Notes
EOF(#n) | YES | V12+
LOF(#n) | YES | V12+
LOC(#n) | YES | V19+
SEEK(#n) | YES | V19+
FREEFILE | NO |
FILEATTR | NO |
DIR$ / FILECOPY / KILL / NAME / MKDIR / CHDIR | NO |

String Functions

Function | PBXA64 | Notes
LEFT$(s$, n) | YES |
RIGHT$(s$, n) | YES |
MID$(s$, start [, n]) | YES |
TRIM$ / LTRIM$ / RTRIM$ | YES |
UCASE$ / LCASE$ | YES |
LEN(s$) | YES |
STRPTR(s$) / VARPTR(v) | YES |
FORMAT$(n) | PART | Numeric only, no mask
SPACE$ / STRING$ | PART | Runtime exists (V12/V19)
REMOVE$ / EXTRACT$ / PARSE$ | PART | Runtime exists (V12/V19)
INSTR / TALLY / VERIFY | PART | INSTR (V21); TALLY/VERIFY not yet
STR$ / VAL / CHR$ / ASC / HEX$ / BIN$ / OCT$ | PART | STR$/VAL/CHR$/ASC/HEX$ (V21); BIN$/OCT$ no
USING$ / REPEAT$ / BUILD$ / WRAP$ | NO |
MKL$ / CVI / MKI$ / CVS / MKD$ / CVD etc. | NO |

Math Functions

Function | PBXA64 | Notes
ABS / SGN / INT / FIX / FRAC / ROUND | PART | ABS/SGN/INT (V21); FIX/FRAC/ROUND no
SIN / COS / TAN / ATN | PART | SIN/COS (V21); TAN/ATN no
EXP / LOG / SQR | PART | SQR (V21); EXP/LOG no
RND / RANDOMIZE | YES | V21+
MIN / MAX / SWAP | PART | SWAP (V21); MIN/MAX no

Arrays

Feature | PBXA64 | Notes
Fixed arrays: DIM a(upper) | YES |
Fixed arrays: DIM a(lower TO upper) | YES |
REDIM a(bounds) AS type | YES | Stack-backed
REDIM PRESERVE | PART | Basic support (V15)
LBOUND / UBOUND | YES |
ARRAYATTR | NO |
ERASE | NO |
ARRAY SCAN / SORT / INSERT / DELETE | NO |

Operators

Operator | PBXA64 | Notes
+ - * / \ MOD ^ | YES |
= <> < > <= >= | YES |
AND OR NOT | YES |
+ (string concat) | YES |
& (string concat) | YES | V21+
XOR EQV IMP | YES | V21+
IS ISFALSE ISTRUE | NO | Object operators

Miscellaneous

Feature | PBXA64
SLEEP / WAITKEY$ / STDOUT / STDIN | PART | SLEEP (V21); others no
LSET / RSET / LET / SWAP / INCR / DECR | YES | V21+
WRITE #n | YES | V21+
POKE / PEEK / POKE$ / PEEK$ / MEMORY | NO |
DATE$ / TIME$ / NOW / TIMER | YES | V21+
CODEPTR / FUNCPTR | YES | V21+
KILL / NAME / MKDIR / CHDIR / DIR$ / FREEFILE | YES | V21+
SHELL | YES | V21+
PILOT (T: A: M: Y: N: C: J: U: E: R: S: D: GR: SO:) | YES
Standalone .pilot/.pil/.pcode files | YES
#ASM inline assembly blocks | NO

Summary

Category | YES | PART | NO
Directives | 7 | 0 | 3
Declarations & Procedures | 9 | 0 | 3
Variable Declarations | 4 | 0 | 2
Data Types | 8 | 4 | 2
User-Defined Types | 0 | 1 | 2
Control Flow | 10 | 0 | 0
I/O Statements | 11 | 0 | 3
File Functions | 4 | 0 | 2
String Functions | 9 | 6 | 10
Math Functions | 1 | 4 | 10
Arrays | 4 | 1 | 4
Operators | 17 | 0 | 2
Other | 22 | 1 | 11
TOTAL | 106 | 17 | 54

106 fully supported, 17 partial, 54 not yet implemented. PBXA64 V22 covers the core PB language plus macros, conditional compilation, string functions, file I/O, math functions, arrays, WinAPI, and PILOT. Missing: COM/objects, full graphics, PE resources, UNION, and advanced string packing.


Example Programs

PowerBASIC Examples

Hello World

#COMPILE EXE
#DIM ALL

FUNCTION PBMAIN() AS LONG
    PRINT "Hello, PBXA64!"
    FUNCTION = 0
END FUNCTION

Variables and Expressions

#COMPILE EXE
#DIM ALL

FUNCTION PBMAIN() AS LONG
    LOCAL x AS LONG
    LOCAL y AS LONG
    LOCAL name$ AS STRING

    x = 10
    y = 20
    name$ = "Result"

    PRINT name$; ": "; x + y
    FUNCTION = 0
END FUNCTION

Strings and Arrays

#COMPILE EXE "strings_arrays_winapi.exe"
#DIM ALL

DECLARE FUNCTION MessageBoxA LIB "user32.dll" ALIAS "MessageBoxA" _
    (BYVAL hWnd AS QUAD, BYVAL lpText AS ASCIIZ PTR, _
     BYVAL lpCaption AS ASCIIZ PTR, BYVAL uType AS LONG) AS LONG

FUNCTION PBMAIN() AS LONG
    LOCAL s$ AS STRING
    LOCAL t$ AS STRING
    LOCAL a(1 TO 3) AS LONG

    s$ = "Hello"
    t$ = s$ + ", PBXA64"
    a(1) = LEN(t$)
    a(2) = 42
    a(3) = a(1) + a(2)

    PRINT t$
    PRINT a(3)
    CALL MessageBoxA(0, STRPTR(t$), "PBXA64", 0)
    FUNCTION = 0
END FUNCTION

File I/O

#COMPILE EXE
#DIM ALL

FUNCTION PBMAIN() AS LONG
    LOCAL line$ AS STRING

    OPEN "output.txt" FOR OUTPUT AS #1
    PRINT #1, "Hello, File!"
    CLOSE #1

    OPEN "output.txt" FOR INPUT AS #1
    INPUT #1, line$
    CLOSE #1

    PRINT "Read: "; line$
    FUNCTION = 0
END FUNCTION

Control Flow

#COMPILE EXE
#DIM ALL

FUNCTION PBMAIN() AS LONG
    LOCAL i AS LONG

    FOR i = 1 TO 5
        IF i MOD 2 = 0 THEN
            PRINT i; " is even"
        ELSE
            PRINT i; " is odd"
        END IF
    NEXT i

    FUNCTION = 0
END FUNCTION

WinAPI Message Box

#COMPILE EXE
#DIM ALL

DECLARE FUNCTION MessageBoxA LIB "user32.dll" ALIAS "MessageBoxA" _
    (BYVAL hWnd AS QUAD, BYVAL lpText AS ASCIIZ PTR, _
     BYVAL lpCaption AS ASCIIZ PTR, BYVAL uType AS LONG) AS LONG

FUNCTION PBMAIN() AS LONG
    CALL MessageBoxA(0, "Hello from PBXA64!", "Greeting", 0)
    FUNCTION = 0
END FUNCTION

PILOT Examples

Simple Quiz

#COMPILE EXE
#DIM ALL

FUNCTION PBMAIN() AS LONG
    #PCODE
    T: Welcome to the Quiz!
    T: What is 2 + 2?
    A:
    M: 4
    Y: Correct!
    N: Wrong, the answer is 4.
    E:
    #ENDP
    FUNCTION = 0
END FUNCTION

Multi-Block PILOT

#COMPILE EXE
#DIM ALL

FUNCTION PBMAIN() AS LONG
    #PCODE
    C: score = 0
    *ASK
    T: What is the capital of France?
    A:
    M: PARIS
    Y: Correct! $score = $score + 1
    N: Wrong! It is Paris.
    T: Your score: #score
    T: Try again? (YES/NO)
    A:
    M: Y*
    Y: *ASK
    E:
    #ENDP
    FUNCTION = 0
END FUNCTION

MGC Examples

Basic MGC Program

fn main() -> i32 {
    var x: i32 = 42;
    return x - 42;
}

MGC with Control Flow

fn main() -> i32 {
    var i: i32 = 0;
    var sum: i32 = 0;
    while i < 10 {
        sum = sum + i;
        i = i + 1;
    }
    return sum - 45;
}
More examples: See the examples/ directory in the PBXA64 installation for additional sample programs including: real_usable.pb, feature_pass.pb, gosub_trim.pb, v10_pilot_features.pb, and more.


PBXA64 Compiler - Overview

PBXA64 Compiler V25

PBXA64 is a native x86-64 compiler for BASIC source code, producing Windows PE32+ console executables with direct machine code generation, COFF object emission, and PE linking. V25 adds memory commands, binary string packing, and string-array utilities.

Version: 0.25.0-memory-string-commands-v25
Target: Windows x64 PE32+ console executables
Tests: 83/83 passing
Copyright: © 2026 Theo Gottwald / www.smart-package.com

Supported Language Frontends

Language | Extension | Description
BASIC | .pb, .bas, .pbi | BASIC dialect compatible with popular Windows BASIC compilers
MGC | .mgc | C-like intermediate language for IR testing
PILOT | .pilot, .pil, .pcode, #PCODE | Educational language (Atari-style), standalone + embedded

Compiler Pipeline

Source -> Preprocessor -> Lexer -> Parser -> AST
    -> Semantic Analysis -> IR Lowering -> Optimization
    -> x64 CodeGen -> Assembler -> COFF -> Linker -> PE32+

Quick Start

pbxa64 --help                           Show all options
pbxa64 hello.pb -o hello.exe            Compile to executable
pbxa64 -S program.pb -o program.asm     Emit assembly listing
pbxa64 --dump-tokens program.pb         Debug token stream

Documentation Sections

  • Command-Line Options — All compiler flags and switches
  • BASIC Language Reference — Supported syntax
  • Preprocessor Directives — #COMPILE, #INCLUDE, #DIM ALL
  • Statements — Control flow, I/O, assignments
  • Types & Variables — Data types, arrays, strings
  • PILOT Language Reference — #PCODE commands
  • Compatibility Table — Feature comparison
  • Example Programs — Sample code


Preprocessor Directives

#COMPILE

Syntax: #COMPILE EXE ["filename"]

Specifies the output target type. EXE produces a console executable. DLL and GUI/WINDOWS modes are recognized but explicitly rejected with a clear diagnostic (V22) until PE export/subsystem writing is implemented.

#COMPILE EXE "myapp.exe"

#DIM ALL

Syntax: #DIM ALL

Requires explicit declaration of all variables. Variables used without a LOCAL, DIM, STATIC, or GLOBAL declaration will produce an error.

#DIM ALL    ' Best practice: always declare variables

#INCLUDE / #INCLUDE ONCE / #INCLUDEONCE

Syntax: #INCLUDE "filename" | #INCLUDE ONCE "filename" | #INCLUDEONCE "filename"

Inserts the contents of another source file at the directive location. #INCLUDE ONCE and #INCLUDEONCE (V22) ensure the file is included only once, preventing duplicate declarations. Missing-file diagnostics are emitted when include-once paths fail.

Include paths are resolved relative to the current source file, then against paths specified with -I on the command line.

#INCLUDE ONCE "win32api.inc"
#INCLUDEONCE "mytypes.pbi"
#INCLUDE "helpers.inc"

Conditional Compilation (#IF / #ELSEIF / #ELSE / #ENDIF)

Syntax: #IF condition ... #ELSEIF condition ... #ELSE ... #ENDIF

Conditionally include or exclude blocks of source code at compile time (V21). Supports %DEF(name), NOT, AND, and OR in conditions. Dollar-branch variants $IF, $ELSEIF, $ELSE, $ENDIF are also supported.

#IF %DEF(PBXA64_V22)
    PRINT "V22 macros available"
#ELSEIF %DEF(DEBUG)
    PRINT "Debug build"
#ELSE
    PRINT "Release build"
#ENDIF

#DEFINE / #UNDEF

Syntax: #DEFINE name value | #UNDEF name

#DEFINE (V22) creates an object-like text-replacement macro. Every occurrence of name in subsequent source is replaced with value. #UNDEF (V21) removes a previously defined symbol. Duplicate definitions produce a diagnostic error.

#DEFINE MAX_COUNT 100
#DEFINE APP_NAME "MyApp"
LOCAL i AS LONG
FOR i = 1 TO MAX_COUNT : PRINT APP_NAME : NEXT i
#UNDEF MAX_COUNT

MACRO / END MACRO

Syntax: MACRO name(args) = expr or MACRO name(args) ... END MACRO

Macro expansion engine (V22). Supports:

  • Single-line expression macros: MACRO Add1(x) = (x + 1)
  • Multi-line statement macros: MACRO name(args) ... END MACRO
  • MACROFUNCTION: MACROFUNCTION name(args) ... END MACRO = expr with MACROTEMP unique-name generation

Expansion is source-text based with recursion guards, depth limits, and arity diagnostics.

MACRO SAY(text)
    PRINT text
END MACRO

MACROFUNCTION Twice(x)
    MACROTEMP t
    LOCAL t AS LONG
    t = x * 2
END MACRO = t

See V22 Macro Preprocessor for full details.

Note: PBXA64 supports recursive #INCLUDE resolution with depth tracking to prevent infinite recursion. Include files use the .pbi extension by convention.

Language Block Directives

Directive | Language | Description
#PCODE ... #ENDP | PILOT | Embedded PILOT educational language block. Commands are lowered to PB statements.
FUNCTION PBMAIN() AS LONG
    #PCODE
    T: Hello from PILOT!
    A: What is your name?
    T: Nice to meet you, $A!
    E:
    #ENDP
    FUNCTION = 0
END FUNCTION


PowerBASIC Language Reference

Note: PBXA64 is a prototype. Only the documented subset of PowerBASIC is supported. Full PBWin/PBCC compatibility is not yet achieved.

Module Structure

#COMPILE EXE "output.exe"
#DIM ALL

' Declarations
DECLARE FUNCTION MsgBox LIB "user32.dll" ALIAS "MessageBoxA" (...)
GLOBAL g_count AS LONG

' Types
TYPE Point2D
    x AS DOUBLE
    y AS DOUBLE
END TYPE

' Main entry
FUNCTION PBMAIN() AS LONG
    LOCAL name AS STRING
    name = "PBXA64"
    PRINT "Hello, "; name
    FUNCTION = 0
END FUNCTION

Supported Features (V22)

Category | Supported | Limitations
Program entry | FUNCTION PBMAIN() AS LONG | Required entry point
Procedures | FUNCTION, SUB, DECLARE, CALLBACK | Full WinAPI support
Variables | LOCAL, DIM, STATIC, GLOBAL | GLOBAL (limited), no THREADED
Types | INTEGER, LONG, QUAD, BYTE, WORD, DWORD, SINGLE, DOUBLE, STRING | No VARIANT, OBJECT, GUID
Arrays | Fixed arrays, stack-backed REDIM | No REDIM PRESERVE, no heap arrays
Strings | Local ASCIIZ buffers, concat (+), LEN, STRPTR, VARPTR | Fixed-length only, no dynamic
Control flow | IF/ELSE/END IF, FOR/NEXT, WHILE/WEND, SELECT CASE, DO/LOOP, GOTO, GOSUB | Fully supported
I/O | PRINT, OPEN/PRINT#/INPUT#/CLOSE, LINE INPUT#, GET/PUT, BINARY | Fully supported via WinAPI
Macros | MACRO, MACROFUNCTION, MACROTEMP, #DEFINE | Full V22 expansion engine
External calls | WinAPI DECLARE with >4 args | Varargs, struct args supported

See Also

  • Preprocessor Directives
  • V22 Macro Preprocessor
  • PB Types & Variables


PB Statements

PowerBASIC Statements

DECLARE (External Function)

Syntax: DECLARE FUNCTION|SUB name LIB "dll" ALIAS "export" (params) AS type

Declares an external function imported from a Windows DLL. Supports BYVAL and BYREF parameters. Multiline parameter lists with _ continuation are supported.

DECLARE FUNCTION MessageBoxA LIB "user32.dll" _
    ALIAS "MessageBoxA" ( _
    BYVAL hWnd AS QUAD, _
    BYVAL lpText AS ASCIIZ PTR, _
    BYVAL lpCaption AS ASCIIZ PTR, _
    BYVAL uType AS LONG _
) AS LONG

Assignment

Syntax: variable = expression | FUNCTION = expression

Assigns a value to a variable. FUNCTION = expr sets the return value of the current function.

x = 42
name$ = "Hello"
FUNCTION = 0

PRINT

Syntax: PRINT expr [; expr ...]

Outputs text to the console. Multiple expressions separated by ; are concatenated. A trailing expression adds a newline.

PRINT "The value is "; x
PRINT "Hello, "; name$

IF / ELSE / END IF

Syntax: IF condition THEN statements [ELSE statements] END IF

Conditional execution. Supports ELSEIF for multi-way branching.

IF x > 10 THEN
    PRINT "Large"
ELSEIF x > 0 THEN
    PRINT "Small"
ELSE
    PRINT "Zero or negative"
END IF

FOR / NEXT

Syntax: FOR var = start TO end [STEP n] : statements : NEXT [var]

Iterates a variable from start to end with optional STEP increment.

FOR i = 1 TO 10
    PRINT i
NEXT i

FOR j = 10 TO 1 STEP -2
    PRINT j
NEXT

WHILE / WEND

Syntax: WHILE condition : statements : WEND

Repeats a block of statements while the condition is true.

WHILE x > 0
    PRINT x
    x = x - 1
WEND

GOTO

Syntax: GOTO label

Unconditional jump to a label. Labels are plain identifiers followed by :.

IF error THEN GOTO Cleanup
PRINT "All OK"
Cleanup:
PRINT "Done"

GOSUB / RETURN

Syntax: GOSUB label ... label: ... RETURN

Calls a local subroutine and returns when RETURN is encountered.

GOSUB PrintHeader
...
PrintHeader:
    PRINT "=== Header ==="
    RETURN

CALL

Syntax: CALL procedure(args)

Calls an external or user-defined procedure. For external WinAPI calls, arguments are staged according to the Windows x64 calling convention (first 4 in registers, rest on stack).

CALL MessageBoxA(0, STRPTR(msg$), "Title", 0)

File I/O Statements

OPEN

Syntax: OPEN "file" FOR OUTPUT|APPEND|INPUT AS #n

Opens a file for output (write), append, or input (read). The file number #n is used in subsequent I/O statements.

OPEN "data.txt" FOR OUTPUT AS #1
OPEN "data.txt" FOR APPEND AS #2
OPEN "data.txt" FOR INPUT AS #3

PRINT #

Syntax: PRINT #n, expr [; expr ...]

Writes text to an open file.

PRINT #1, "Line of text"
PRINT #1, "Value: "; x

INPUT #

Syntax: INPUT #n, variable

Reads data from an open file into a variable.

INPUT #3, line$

CLOSE

Syntax: CLOSE #n

Closes an open file.

CLOSE #1


PB Types & Variables

Data Types

Type | Suffix | Size | Range
BYTE | ? | 8-bit | 0 to 255
WORD | ?? | 16-bit | 0 to 65535
DWORD | ??? | 32-bit | 0 to 4,294,967,295
INTEGER | % | 16-bit | -32,768 to 32,767
LONG | & | 32-bit | -2,147,483,648 to 2,147,483,647
QUAD | && | 64-bit | Very large integers
SINGLE | ! | 32-bit | IEEE 754 float (partial support)
DOUBLE | # | 64-bit | IEEE 754 double (partial support)
EXT | ## | 80-bit | x87 extended (partial support)
STRING | $ | Variable | ASCIIZ fixed-length buffer
WSTRING | $$ | Variable | UTF-16 (partial support)
CURRENCY | @ | 64-bit | Fixed-point (4 decimals)
Note: STRING variables are implemented as fixed-length local ASCIIZ buffers. Full dynamic string runtime (heap allocation, automatic cleanup, capacity growth) is not yet implemented.

Variable Declarations

LOCAL

Syntax: LOCAL name[(bounds)] AS type

Declares a procedure-local variable. Must appear at the top of a FUNCTION or SUB before executable statements.

LOCAL x AS LONG
LOCAL name$ AS STRING
LOCAL price AS SINGLE

DIM

Syntax: DIM name[(bounds)] AS type

Declares a variable (equivalent to LOCAL in the current implementation).

DIM count AS LONG

STATIC

Syntax: STATIC name AS type

Declares a variable that retains its value between calls to the procedure.

STATIC callCount AS LONG

GLOBAL

Syntax: GLOBAL name AS type

Declares a module-level global variable. Partial support only.

GLOBAL g_counter AS LONG

Arrays

Fixed Arrays

Arrays with a compile-time constant size:

LOCAL a(10) AS LONG          ' 0 to 10 (11 elements)
LOCAL b(1 TO 5) AS LONG      ' 1 to 5 (5 elements)
LOCAL names$(20) AS STRING   ' String array (limited)

Dynamic Arrays (REDIM)

Stack-backed dynamic arrays; bounds established at runtime:

LOCAL arr() AS LONG
REDIM arr(1 TO n) AS LONG    ' Allocate n elements
arr(3) = 42                  ' Element assignment
x = arr(3)                   ' Element read
Note: REDIM PRESERVE (preserving existing data on resize) is not yet supported. Arrays are stack-backed, not heap-allocated.

Array Functions

Function | Description | Example
LBOUND(a) | Lower bound of array | PRINT LBOUND(arr)
UBOUND(a) | Upper bound of array | PRINT UBOUND(arr)

String Functions

Function | Description
LEN(s$) | Returns the length of a string
STRPTR(s$) | Returns a pointer to the string data (for WinAPI calls)
VARPTR(v) | Returns a pointer to a variable
LEFT$(s$, n) | Returns the leftmost n characters
RIGHT$(s$, n) | Returns the rightmost n characters
MID$(s$, start, n) | Returns n characters from position start
TRIM$(s$) | Removes leading and trailing spaces
LTRIM$(s$) | Removes leading spaces
RTRIM$(s$) | Removes trailing spaces
UCASE$(s$) | Converts to uppercase
LCASE$(s$) | Converts to lowercase

String Operations

s$ = "Hello"
t$ = s$ + ", World"          ' Concatenation with +
IF s$ = t$ THEN ...          ' Equality comparison
IF s$ <> t$ THEN ...         ' Inequality comparison
IF s$ < t$ THEN ...          ' Relational comparison

TYPE (User-Defined Type)

Partial support for structured types with numeric, STRING, and nested TYPE fields:

TYPE Point
    x AS DOUBLE
    y AS DOUBLE
END TYPE

LOCAL p AS Point
p.x = 3.0
p.y = 4.0

Numeric Literals

Base | Prefix | Example
Decimal | (none) | 42, -100
Hexadecimal | &H | &HFF, &H0A2B
Octal | &O | &O77, &O377
Binary | &B | &B1010, &B11110000

Expressions & Operators

Category | Operators
Arithmetic | + - * / \ (integer) MOD ^
Comparison | = <> < > <= >=
String | + (concatenation), = <> < >


PILOT Commands Detail

Description

PILOT is a lightweight educational language embedded within PowerBASIC via #PCODE/#END PCODE blocks. Each PILOT command is lowered to PowerBASIC statements and then compiled to native x64 machine code. PILOT provides a simple command set for text-based interactive programs: type output (T:), accept input (A:), pattern matching (M:), conditional branching (Y:/N:, J:, U:), computation (C:), and flow control (J:, E:).

Syntax

Block: #PCODE ... #END PCODE
Standalone (V11): program.pilot — top-level PILOT source file
Commands: T: A: M: Y: N: C: J: U: E: R: S:

V11 New Features

NEW in V11: The PILOT subsystem has been significantly extended with standalone file support, conditional suffixes, parenthesized conditions, multi-pattern matching, and advanced variable support.

Standalone .pilot Files

In V11, a .pilot file can be compiled directly without wrapping in #PCODE/#END PCODE blocks. The compiler auto-generates the necessary PBMAIN wrapper. This makes PILOT a first-class compile target.

Conditional Suffixes

Every command that produces a match result (T:, Y:, N:, J:, U:) now supports conditional suffixes that test the match flag before executing:

Suffix | Meaning | Example
TY: | Execute if match is TRUE | TY: Correct! — print only if last M: matched
TN: | Execute if match is FALSE | TN: Wrong, try again — print only if last M: failed
JY: | Jump if match is TRUE | JY: *CORRECT — jump to CORRECT if matched
JN: | Jump if match is FALSE | JN: *WRONG — jump to WRONG if not matched
UY: | Conditional use if match TRUE | UY: score>=3: *WIN — test condition only if matched
UN: | Conditional use if match FALSE | UN: score>=10: *QUIT — test condition only if not matched

Parenthesized Conditions (T: command)

The T: (Type) command now supports parenthesized conditional expressions. If the expression evaluates to FALSE, the text output is suppressed:

T(#x>=0): Score is non-negative         ' only prints if #x >= 0
T(#count=1): There is 1 item            ' only prints if #count = 1

Multi-Pattern M: Command

The M: command now accepts multiple patterns separated by commas. The match result is TRUE if any pattern matches. Each matched group is captured:

M: YES, Y, OK, SURE   ' matches any of these four responses

Match Variables: $MATCH, $LEFT, $RIGHT

After a successful M: with wildcards, three special variables contain the matched portions:

Variable | Content
$MATCH | The full substring that matched the pattern
$LEFT | The text to the left of the first wildcard match
$RIGHT | The text to the right of the first wildcard match
M: *IS*
A: Enter something with IS:
T: Matched: $MATCH, Left: $LEFT, Right: $RIGHT

U: *LABEL as GOSUB

The U: (Use) command with a label target (*LABEL) now performs a GOSUB (call with return) instead of a GOTO:

U: score>=3: *BONUS         ' calls subroutine BONUS, returns here
T: Back from bonus round

Multiple A: Targets

The A: (Accept) command now accepts multiple comma-separated variables in a single command:

A: $NAME, #AGE, $CITY       ' reads three values on one line

Command-to-PowerBASIC Lowering Table

PILOT | Generated PowerBASIC | Generated ASM Pattern
T: text $var #num | PRINT "text "; var$; " "; num# | See PRINT (WriteFile)
A: | INPUT a$ | See Console INPUT (ReadFile)
A: $NAME | INPUT name$ | ReadFile + assign to named string variable
A: #COUNT | INPUT tmp$ : count# = VAL(tmp$) | ReadFile + string-to-number conversion
A: $NAME, #AGE, $CITY | INPUT name$ : INPUT age# : INPUT city$ | V11 Sequential ReadFile for multiple targets
M: YES | match = (TRIM$(UCASE$(a$)) = "YES") | String comparison via memcmp
M: Y* | match = (LEFT$(TRIM$(UCASE$(a$)), 1) = "Y") | Prefix check after case normalization
M: *ING | match = (RIGHT$(TRIM$(UCASE$(a$)), 3) = "ING") | Suffix check
M: *ORL* | match = (INSTR(TRIM$(UCASE$(a$)), "ORL") > 0) | Substring search
M: * | match = 1 | Always true
M: 42 | match = (#A = 42) | CMP + SETcc for numeric equality
M: #EXPECTED | match = (#A = #EXPECTED) | CMP with numeric variables
M: $EXPECTED | match = (TRIM$(UCASE$(a$)) = TRIM$(UCASE$(expected$))) | String comparison
M: PAT1, PAT2, PAT3 | match = (match1 OR match2 OR match3) | V11 Multi-pattern OR combination
Y: text $var #num | IF match THEN PRINT "text "; var$; " "; num# | Conditional jump around PRINT code
Y: *LABEL | IF match THEN GOTO LABEL | Conditional JMP
TY: text | IF match THEN PRINT "text" | V11 Y: alias (match-true type)
TN: text | IF NOT match THEN PRINT "text" | V11 N: alias (match-false type)
JY: *LABEL | IF match THEN GOTO LABEL | V11 Conditional JMP on match TRUE
JN: *LABEL | IF NOT match THEN GOTO LABEL | V11 Conditional JMP on match FALSE
N: text $var #num | IF NOT match THEN PRINT "text ... | Inverted conditional jump
N: *LABEL | IF NOT match THEN GOTO LABEL | Inverted conditional JMP
C: score = score + 1 | score# = score# + 1 | Standard assignment + arithmetic
C: name = $A | name$ = a$ | String assignment
J: *LABEL | GOTO LABEL | jmp __pb_label_xxx_LABEL
U: score>=3: *WIN | IF score# >= 3 THEN GOTO WIN | CMP + conditional JMP
U: score>=3: You win! | IF score# >= 3 THEN PRINT "You win!" | CMP + conditional around PRINT
U: score>=3: *BONUS | IF score# >= 3 THEN GOSUB BONUS | V11 CMP + CALL (not JMP)
UY: score>=3: *WIN | IF match AND score# >= 3 THEN GOTO WIN | V11 Match AND condition
UN: score>=10: *QUIT | IF NOT match AND score# >= 10 THEN GOTO QUIT | V11 NOT match AND condition
E: | GOTO EndOfPilotBlock_N | jmp __pb_pilot_end_N
R: comment | PB comment | No ASM generated
S: DRAW 50 | PB comment | No ASM generated (no-op)
*LABEL | __pb_pilot_blockN_LABEL: | ASM label definition

PILOT Variable Mapping

; PILOT variables are prefixed internally to prevent collisions:
$A     -> a$                        (string variable)
#A     -> a#                        (numeric variable from VAL)
$NAME  -> pilot_block1_NAME$        (block-scoped string)
#SCORE -> pilot_block1_SCORE#       (block-scoped numeric)
*LOOP  -> __pb_pilot_block1_LOOP    (block-scoped label)

; V11 match capture variables:
$MATCH -> pilot_block1_MATCH$       (matched substring)
$LEFT  -> pilot_block1_LEFT$        (text left of wildcard)
$RIGHT -> pilot_block1_RIGHT$       (text right of wildcard)

Block Structure

; Each #PCODE block generates:
; 1. LOCAL declarations for all PILOT variables
; 2. Lowered PB statements for each PILOT command
; 3. An end-of-block label for E: command target
; 4. Unique prefix ensures no name clashes between blocks
;
; V11: Standalone .pilot files auto-generate the #PCODE wrapper
; and PBMAIN entry point. Variables are scoped per file.
;
; V11: T(#expr): conditions emit CMP/TEST before the PRINT code
; with a conditional jump to skip output if the expression is FALSE.
;
; V11: U:*LABEL emits CALL instead of JMP for subroutine semantics.

; V13: PILOT restoration brings back D:, GR:, SO: (no-op comments
; for Atari-style display/graphic/sound commands) and #POS state
; variable tracking. All V10/V11 features retained.
;
; V13: Standalone .pilot/.pil/.plt/.pcode files compile via
; auto-generated PB wrapper with PBMAIN entry point.

V13 PILOT Restoration NEW in V13

V13 restores the full PILOT layer that was introduced in V10/V11, including newly covered commands and state variables:

D: (Display/Graphics No-Op)

Syntax: D: x, y, "text" | D: CLEAR

Atari-style display positioning command. Accepted as a no-op comment for compatibility. Future graphics runtime will render text at screen coordinates.

GR: (Graphic Mode No-Op)

Syntax: GR: mode | GR: DRAW x1,y1 TO x2,y2

Atari-style graphics mode and drawing commands. Accepted as no-op comments. Turtle graphics and screen rendering are deferred to a future runtime.

SO: (Sound No-Op)

Syntax: SO: freq, duration | SO: NOTE "C4", 100

Atari-style sound/music command. Accepted as a no-op comment. Audio output is deferred to a future runtime.

#POS (Match Position Variable)

Syntax: #POS (read-only numeric variable)

After a successful M: pattern match, #POS contains the 1-based character position where the match was found in the input string. Returns 0 if no match.

M: *HELLO*
Y: Found at position #POS!

V13 PILOT Command Summary

Command | V10 | V11 | V13 | Description
T: | Yes | Yes | Yes | Type (print)
A: | Yes | Yes | Yes | Accept (input)
M: | Yes | Yes | Yes | Match pattern
Y: | Yes | Yes | Yes | Yes (match success)
N: | Yes | Yes | Yes | No (match failure)
C: | Yes | Yes | Yes | Compute (assign)
J: | Yes | Yes | Yes | Jump (goto)
U: | Yes | Yes | Yes | Use (conditional)
E: | Yes | Yes | Yes | End block
R: | Yes | Yes | Yes | Remark (comment)
S: | Yes | Yes | Yes | Screen (no-op)
D: | -- | -- | Yes | Display (no-op)
GR: | -- | -- | Yes | Graphic (no-op)
SO: | -- | -- | Yes | Sound (no-op)
TY:/TN: | -- | Yes | Yes | Conditional type
JY:/JN: | -- | Yes | Yes | Conditional jump
UY:/UN: | -- | Yes | Yes | Conditional use
T(#expr): | -- | Yes | Yes | Parenthesized condition
$MATCH | -- | Yes | Yes | Matched text variable
$LEFT | -- | Yes | Yes | Left of match
$RIGHT | -- | Yes | Yes | Right of match
#POS | -- | -- | Yes | Match position


PILOT Language Reference

PILOT (Programmed Inquiry, Learning Or Teaching) is an educational language from the 1960s, popular on Atari computers. PBXA64 embeds PILOT blocks inside PowerBASIC functions using #PCODE / #ENDP directives.

V10 Feature: PILOT #PCODE blocks are fully lowered to working PowerBASIC code by the compiler. Multiple blocks are supported with block-scoped labels and variables. All PILOT variables are automatically declared as LOCAL for #DIM ALL compatibility.

Command Reference

Command | Description | Example
T: | Type (print text) | T: Hello, World!
A: | Accept (read input) | A: What is your name?
M: | Match (pattern test) | M: YES
Y: | Yes branch (match success) | Y: Correct!
N: | No branch (match failure) | N: Try again!
C: | Compute (assignment) | C: score = score + 1
J: | Jump (goto label) | J: *START
U: | Use (conditional jump/text) | U: score>=3: *WIN
E: | End (exit PILOT block) | E:
R: | Remark (comment) | R: This is a comment
S: | Screen (no-op, accepted) | S: DRAW 50
*LABEL | Label (jump target) | *START

PILOT Variables

Variable | Type | Description
$A | String | Input from the last A: command
#A | Numeric | Numeric value of the last A: input (via VAL)
$name | String | Named string variable (e.g., $NAME)
#count | Numeric | Named numeric variable (e.g., #SCORE)

Pattern Matching

Pattern | Match Type | Example
Exact text | Exact match after TRIM$/UCASE$ | M: YES matches "YES", " yes "
Y* | Prefix match | M: Y* matches "YES", "YELLOW"
*ING | Suffix match | M: *ING matches "RUNNING"
*ORL* | Substring match | M: *ORL* matches "WORLD"
* | Wildcard (any input) | M: * matches anything
Numeric | Exact number match | M: 42 matches #A = 42
#VAR | Numeric variable match | M: #EXPECTED matches #A = #EXPECTED
$VAR | String variable match | M: $ANSWER matches $A = $ANSWER

Example: Quiz Program

FUNCTION PBMAIN() AS LONG
    #PCODE
    C: score = 0
    *LOOP
    T: What is 5 + 3?
    A:
    M: 8
    Y: Correct! $score = $score + 1
    N: Wrong! The answer is 8.
    T: Play again? (YES/NO)
    A:
    M: Y*
    Y: *LOOP
    T: You got #score correct!
    E:
    #ENDP
    FUNCTION = 0
END FUNCTION

How PILOT Maps to PB

PILOT | Generated PowerBASIC
T: Hello $name | PRINT "Hello "; name$
A: | INPUT a$ : a# = VAL(a$)
M: YES | match = (UCASE$(TRIM$(a$)) = "YES")
Y: *LABEL | IF match THEN GOTO LABEL
C: a = b + c | a# = b# + c#
J: *LABEL | GOTO LABEL
U: x>=5: *WIN | IF x# >= 5 THEN GOTO WIN
E: | GOTO EndOfPilotBlock
*LABEL | LABEL:
Multiple Blocks: Multiple #PCODE / #ENDP blocks can appear in a single PB function. Each block gets unique internal prefixes for labels and variables to prevent name collisions.


Arrays & REDIM

Description

PowerBASIC supports both fixed-size arrays (allocated on the stack) and dynamic arrays (allocated on the heap via REDIM). Fixed arrays are declared with a constant size at compile time. Dynamic arrays use REDIM to allocate and resize at runtime. Both support custom lower bounds via the lower TO upper syntax.

Fixed Arrays

Syntax: LOCAL a(size) AS type | LOCAL a(lower TO upper) AS type

Description

Fixed arrays are allocated inline in the stack frame. The size must be a compile-time constant. Element access uses direct index arithmetic: base + (index - lbound) * stride.

Parameters

Parameter | Description
a | Array variable name
size | Number of elements (0-based: 0 TO size-1)
lower | Lower bound (any integer constant)
upper | Upper bound (any integer constant, >= lower)
type | Element type: LONG, STRING, etc.

Example

LOCAL a(10) AS LONG       ' 0 TO 10 (11 elements)
LOCAL b(1 TO 5) AS LONG   ' 5 elements
a(5) = 42

Generated Assembly

; Fixed array: allocated inline in stack frame
; Element access: base + (index - lbound) * stride
; Example: a(5) = 42
; pb_cg_emit_array_address computes [rbp - base + (5-lbound)*stride]
mov    [rbp - array_base + (5-lbound)*stride], 42

REDIM (Dynamic Arrays)

Syntax: REDIM array(lower TO upper) AS type
Source: pb_cg_emit_redim (pb_codegen.c:5975)

Description

REDIM allocates (or reallocates) a dynamic array on the heap via malloc. The lower and upper bounds can be runtime expressions. If the array was previously allocated, the old heap memory is freed before the new allocation.

Note: REDIM PRESERVE is partially supported. Heap-backed arrays and STRING/UDT arrays are not yet implemented.

Parameters

Parameter | Description
array | Dynamic array variable name
lower | Lower bound (integer expression, evaluated at runtime)
upper | Upper bound (integer expression, evaluated at runtime)
type | Element type

Example

LOCAL n AS LONG
n = 100
REDIM myArray(1 TO n) AS LONG
myArray(50) = 999

Allocation

; Compute bounds
mov    rax, upper_bound
sub    rax, lower_bound
add    rax, 1                          ; count = upper - lower + 1
cmp    rax, 1
jl     __pb_redim_fail                 ; count < 1 -> error
mov    [rbp - count_offset], rax
mov    rcx, rax
imul   rcx, element_size               ; bytes = count * stride
call   [__imp_malloc]                  ; allocate
test   rax, rax
je     __pb_redim_fail                 ; OOM -> exit
mov    [rbp - new_ptr_offset], rax
; Zero-initialize
mov    rcx, rax
xor    rdx, rdx
mov    r8, [rbp - bytes_offset]
call   [__imp_memset]                  ; memset(ptr, 0, bytes)

PRESERVE Copy (overlapping elements)

; For each element that exists in both old and new arrays:
;   source offset = (i - old_lower) * stride + old_ptr
;   dest offset   = (i - new_lower) * stride + new_ptr
mov    rcx, dest_ptr
mov    rdx, src_ptr
mov    r8, stride
call   [__imp_memcpy]                  ; copy element
; For STRING elements: copy descriptor

Finalize (swap pointers)

; Free old array if exists
mov    rcx, [rbp - old_ptr_offset]
test   rcx, rcx
je     __pb_redim_old_null
call   [__imp_free]
__pb_redim_old_null:
; Update local descriptor
mov    rax, [rbp - new_ptr_offset]
mov    [rbp - local_ptr_offset], rax   ; ptr = new_ptr
mov    rax, [rbp - new_lower]
mov    [rbp - local_lower_offset], rax ; lbound = new_lower
mov    rax, [rbp - new_upper]
mov    [rbp - local_upper_offset], rax ; ubound = new_upper
mov    rax, [rbp - new_count]
mov    [rbp - local_count_offset], rax ; count = new_count

Array Element Access

Syntax: array(index) (read or write)

Example

LOCAL a(10) AS LONG
a(3) = 42              ' write
LOCAL x AS LONG
x = a(3)               ' read -> x = 42
; Read:  x = a(index)
; Write: a(index) = expr
; Both compute: r11 = base + (index - lbound) * stride
; Then: mov rax, [r11] (read) or mov [r11], rax (write)


Assignment & FUNCTION = expr

Syntax

Variable Assignment: variable = expression
Function Return: FUNCTION = expression
Source: pb_cg_emit_store_numeric_expr (pb_codegen.c:2251), pb_cg_emit_stmt (pb_codegen.c:~6630)

Description

The assignment operator (=) stores the result of an expression into a variable. The compiler handles different variable types (LOCAL, STATIC, GLOBAL, BYREF parameters, TYPE fields, and array elements) and automatically generates the correct addressing mode for each. For functions, FUNCTION = expression sets the return value, which is loaded into RAX (integer), XMM0 (float), or RAX:RDX (string) during the function epilogue.

Parameters

Parameter | Description
variable | The target variable name. Can be a LOCAL, STATIC, GLOBAL, BYREF parameter, TYPE field, or array element.
expression | Any numeric, string, or pointer expression whose result is stored into the variable.
FUNCTION = value | Sets the return value of a FUNCTION. The value is stored in a dedicated return slot in the stack frame and loaded during the epilogue.

Example

FUNCTION PBMAIN() AS LONG
    LOCAL x AS LONG
    LOCAL y AS LONG
    LOCAL name AS STRING

    ' Simple assignment
    x = 10
    y = x * 2 + 5

    ' String assignment
    name = "Hello"

    ' Function return via FUNCTION =
    FUNCTION = x + y
END FUNCTION

Generated Assembly

Variable = expr

Syntax: variable = expression
Source: pb_cg_emit_store_numeric_expr (pb_codegen.c:2251)

Stack Local (numeric)

; Evaluate expression -> RAX
mov    [rbp - offset], rax           ; store to stack frame

BYREF Parameter (pointer dereference)

mov    r11, [rbp - offset]           ; load pointer from param slot
mov    [r11], rax                    ; store through pointer

STATIC / GLOBAL variable

mov    [label], rax                  ; direct static storage

TYPE Field

mov    [rbp - written_offset], r11   ; save field address
mov    r11, [rbp - written_offset]
mov    [r11], rax                    ; store value at field address

Array Element

; pb_cg_emit_array_address -> r11 = element address
mov    [rbp - written_offset], r11
mov    r11, [rbp - written_offset]
mov    [r11], rax                    ; a(i) = expr

FUNCTION = expr (Return Value)

Syntax: FUNCTION = expression
Source: pb_cg_emit_stmt (pb_codegen.c:~6630)

Integer Return

; Evaluate expression -> RAX
mov    [rbp - return_offset], rax    ; store to return slot
; On function exit:
mov    rax, [rbp - return_offset]    ; load return value
add    rsp, frame_size
pop    rbp
ret

Float Return

movsd  [rbp - return_offset], xmm0   ; store float to return slot
; On function exit:
movsd  xmm0, [rbp - return_offset]   ; load to XMM0 (Win64 FP return)
add    rsp, frame_size
pop    rbp
ret

String Return

lea    r11, [rbp - return_offset]    ; descriptor address
; pb_cg_emit_string_expr_to_descriptor (complex string copy)
; On exit:
mov    rax, [rbp - return_offset]          ; string data pointer
mov    rdx, [rbp - return_length_offset]   ; string length
add    rsp, frame_size
pop    rbp
ret

PBMAIN Return

mov    rcx, [rbp - return_offset]    ; exit code
call   [__imp_ExitProcess]
xor    rax, rax                      ; unreachable
add    rsp, frame_size
pop    rbp
ret


CALL (External WinAPI)

Syntax

Syntax: CALL FunctionName(args)
Source: pb_cg_emit_call (pb_codegen.c:4719)

Description

Invokes an external function declared with DECLARE, such as a Windows API or DLL export. The compiler follows the Windows x64 calling convention: the first four integer/pointer arguments go in RCX, RDX, R8, R9; remaining arguments are pushed to the stack. Float arguments go to XMM0-XMM3. BYREF arguments pass a pointer to the variable. String arguments are converted to null-terminated ASCIIZ pointers before the call.

Parameters

Parameter | Description
FunctionName | The name of the external function as declared with DECLARE.
args | Comma-separated list of arguments. Each argument is evaluated and passed according to the Win64 ABI. Use BYVAL or BYREF as specified in the declaration.

Example

DECLARE FUNCTION MessageBoxA LIB "user32.dll" _
    ALIAS "MessageBoxA" ( _
    BYVAL hWnd AS QUAD, _
    BYVAL lpText AS ASCIIZ PTR, _
    BYVAL lpCaption AS ASCIIZ PTR, _
    BYVAL uType AS LONG _
) AS LONG

FUNCTION PBMAIN() AS LONG
    LOCAL result AS LONG

    ' Call Windows API MessageBox
    CALL MessageBoxA(0, "Hello from PowerBASIC!", _
                     "PB x64", 0)

    FUNCTION = 0
END FUNCTION

Generated Assembly

Integer/Pointer Arguments

; Evaluate each argument -> temp slot
mov    [rbp - arg_offset_0], rax       ; arg 1 value
mov    [rbp - arg_offset_1], rax       ; arg 2 value
; ... etc for each arg

; Stage first 4 to registers (Win64 ABI):
mov    rcx, [rbp - arg_offset_0]       ; arg 1 -> RCX
mov    rdx, [rbp - arg_offset_1]       ; arg 2 -> RDX
mov    r8,  [rbp - arg_offset_2]       ; arg 3 -> R8
mov    r9,  [rbp - arg_offset_3]       ; arg 4 -> R9

; Stage args 5+ to stack (above shadow space):
mov    rax, [rbp - arg_offset_4]
mov    [rsp + 32], rax                 ; arg 5 -> [rsp+32]
mov    rax, [rbp - arg_offset_5]
mov    [rsp + 40], rax                 ; arg 6 -> [rsp+40]
; ... offset = 32 + (i-4)*8 ...

call   [__imp_<FunctionName>]

Float Arguments

movsd  xmm0, [rbp - arg_offset_0]      ; FP arg 1 -> XMM0
movsd  xmm1, [rbp - arg_offset_1]      ; FP arg 2 -> XMM1
movsd  xmm2, [rbp - arg_offset_2]      ; FP arg 3 -> XMM2
movsd  xmm3, [rbp - arg_offset_3]      ; FP arg 4 -> XMM3

BYREF Arguments

; Get address of variable (LEA for stack locals)
lea    rax, [rbp - var_offset]
mov    [rbp - arg_offset], rax          ; store address in arg temp
; Then staged normally: mov rcx, [rbp - arg_offset]

STRING Arguments (to ASCIIZ PTR)

; pb_cg_emit_asciiz_pointer -> RAX = null-terminated string pointer
mov    [rbp - arg_offset], rax

Win64 Argument Layout

Arg | Integer/Ptr | Float | Stack (if >4)
1 | RCX | XMM0 | -
2 | RDX | XMM1 | -
3 | R8 | XMM2 | -
4 | R9 | XMM3 | -
5 | [rsp+32] | [rsp+32] | Stack slot 0
6 | [rsp+40] | [rsp+40] | Stack slot 1
N | [rsp+32+(N-5)*8] | - | Stack slot N-5


Console INPUT

Syntax

Syntax: INPUT [prompt$;] variable
Source: pb_cg_emit_read_console_line (pb_codegen.c:5266)

Description

Reads a line of text from the console (stdin) into a variable. If a prompt string followed by a semicolon is provided, it is displayed to the console before reading input. The input is read via ReadFile from the standard input handle, NUL-terminated, and trailing CR/LF characters are stripped. If the target variable is numeric, the input string is automatically converted with VAL().

Parameters

Parameter | Description
prompt$ | (Optional) A string prompt displayed before reading. Follow with ; to suppress newline, or , to add a question mark.
variable | The variable to receive input. Can be STRING or numeric (LONG, SINGLE, DOUBLE, etc.). Numeric variables trigger automatic VAL() conversion.

Example

LOCAL name$ AS STRING
LOCAL age AS LONG

INPUT "Enter your name: "; name$
INPUT "Enter your age: "; age

PRINT "Hello "; name$; ", you are "; age; " years old."

Interactive Session

Enter your name: Alice
Enter your age: 30
Hello Alice, you are 30 years old.

PILOT A: (Accept) Lowering

The PILOT A: command compiles to console INPUT. The compiler maps PILOT variables to BASIC variables before lowering:

PILOT-to-BASIC Mapping

PILOT Command | Generated PowerBASIC
A: | INPUT a$
A: $NAME | INPUT name$
A: #COUNT | INPUT temp$ : count# = VAL(temp$)

Generated Assembly

Console Input via ReadFile

; 1. Optional prompt via PRINT
; (if prompt present)

; 2. Read from stdin
mov    rcx, -10                         ; STD_INPUT_HANDLE
call   [__imp_GetStdHandle]
mov    rcx, rax                         ; console input handle
lea    rdx, [rbp - buffer_offset]       ; buffer to read into
mov    r8, 1024                         ; max bytes (typically 256-1024)
lea    r9, [rbp - input_count_offset]   ; &bytesRead
mov    [rsp + 32], 0                    ; lpOverlapped = NULL
call   [__imp_ReadFile]

; 3. NUL-terminate the input
mov    r10, [rbp - input_count_offset]  ; bytes actually read
lea    r11, [rbp - buffer_offset]
add    r11, r10
mov    byte [r11], 0                    ; buffer[bytesRead] = 0

; 4. Strip trailing CR/LF
; (if present in the buffer)

; 5. Assign to target variable
; For STRING: copy to local string buffer
; For numeric: VAL() conversion

VAL() Implementation Details

; When INPUT targets a numeric variable, the compiler generates:
; 1. Read string into temporary buffer
; 2. Call strtod (for SINGLE/DOUBLE) or strtol (for LONG/INTEGER)

; Example: INPUT age (LONG)
lea    rcx, [rbp - temp_buffer]         ; string to parse
xor    rdx, rdx                         ; endptr = NULL
mov    r8, 10                           ; base = 10
call   [__imp_strtol]                   ; RAX = parsed integer
mov    [rbp - age_offset], rax          ; store into target variable

; For floating point (SINGLE/DOUBLE):
lea    rcx, [rbp - temp_buffer]
xor    rdx, rdx
call   [__imp_strtod]                   ; XMM0 = parsed double
movsd  [rbp - double_offset], xmm0      ; store into target

Imported WinAPI Functions

Function | DLL | Purpose
GetStdHandle | kernel32.dll | Obtain handle to standard input stream
ReadFile | kernel32.dll | Read raw bytes from the console input buffer
strtod | msvcrt.dll | Convert string to double precision float
strtol | msvcrt.dll | Convert string to signed long integer


DECLARE

DECLARE (External Functions)

Syntax

Syntax: DECLARE FUNCTION|SUB name LIB "dll" ALIAS "export" ([BYVAL|BYREF] param AS type, ...) AS rettype
Source: PB parser + import table generator

Description

Declares an external function or subroutine from a DLL so it can be called with CALL. The compiler generates entries in the PE32+ import table (.idata section) for each declared function. At load time, the Windows PE loader resolves these entries to actual DLL addresses. Multiline parameter lists with _ continuation are supported for readability.

Parameters

Parameter | Description
FUNCTION|SUB | Use FUNCTION if the DLL export returns a value, SUB if it returns void.
name | The name used in your PowerBASIC code to call this function.
LIB "dll" | The DLL filename (e.g., "user32.dll"). The PE loader searches standard system paths.
ALIAS "export" | The actual exported function name in the DLL. Use this when the PB name differs from the DLL export name.
BYVAL param AS type | A parameter passed by value. The value itself is placed in the register or stack slot.
BYREF param AS type | A parameter passed by reference. A pointer to the variable is placed in the register or stack slot.
AS rettype | The return type for FUNCTIONS. Omitted for SUBs. Common types: LONG, QUAD, STRING, SINGLE, DOUBLE.

Example

DECLARE FUNCTION MessageBoxA LIB "user32.dll" _
    ALIAS "MessageBoxA" ( _
    BYVAL hWnd AS QUAD, _
    BYVAL lpText AS ASCIIZ PTR, _
    BYVAL lpCaption AS ASCIIZ PTR, _
    BYVAL uType AS LONG _
) AS LONG

DECLARE FUNCTION GetTickCount LIB "kernel32.dll" _
    ALIAS "GetTickCount" () AS DWORD

FUNCTION PBMAIN() AS LONG
    LOCAL ticks AS DWORD

    ticks = GetTickCount()
    PRINT "System uptime: "; ticks; " ms"

    CALL MessageBoxA(0, "Hello World", "PB x64", 0)

    FUNCTION = 0
END FUNCTION

Generated Assembly

Import Table (.idata section)

; Import Directory Table entry for user32.dll:
dq    __imp_MessageBoxA

; Import Address Table (IAT) entry:
__imp_MessageBoxA:
    dq    0                           ; resolved at load time by Windows PE loader

; Import Name Table (INT) entry:
    dw    0                           ; hint
    db    "MessageBoxA", 0            ; function name

Calling a Declared Function

; Evaluate arguments and stage them according to Win64 ABI:
mov    rcx, 0                         ; hWnd -> RCX
lea    rdx, [__pb_str_hello]          ; lpText -> RDX
lea    r8,  [__pb_str_cap]            ; lpCaption -> R8
mov    r9, 0                          ; uType -> R9

; Call the function via the IAT pointer:
call   [__imp_MessageBoxA]            ; indirect call through import table

; Return value is in RAX (integer) or XMM0 (float)
mov    rax, rax                       ; result is already in RAX

Supported Parameter Types

PB Type | C Equivalent | Size | Register/Stack
LONG | int32_t | 4 | Integer register
QUAD | int64_t / void* | 8 | Integer register
BYTE | uint8_t | 1 | Integer register
WORD | uint16_t | 2 | Integer register
DWORD | uint32_t | 4 | Integer register
SINGLE | float | 4 | XMM register
DOUBLE | double | 8 | XMM register
STRING | char* (ASCIIZ) | 8 | Integer register (ptr)
ASCIIZ PTR | const char* | 8 | Integer register (ptr)

Limitations

  • No CALLBACK FUNCTION support yet
  • No varargs (...) declarations
  • No ordinal aliases (ordinal-only imports)
  • No structure/UDT parameter passing
  • BYREF only works for supported variable types


DO / LOOP

Syntax

Syntax: DO [WHILE|UNTIL cond] : ... : LOOP [WHILE|UNTIL cond]
Source: pb_cg_emit_do (pb_codegen.c:5639)

Description

Creates a loop with flexible condition placement. Conditions can be tested at the top (DO WHILE / DO UNTIL) or at the bottom (LOOP WHILE / LOOP UNTIL). A bare DO...LOOP with no condition creates an infinite loop (use EXIT DO to break). WHILE continues while the condition is true; UNTIL exits once the condition becomes true. ITERATE DO jumps to the condition check at the bottom of the loop.

Parameters

Parameter | Description
WHILE condition | Loop continues while the condition is TRUE (non-zero). Can appear at DO (pre-test) or LOOP (post-test).
UNTIL condition | Loop exits when the condition becomes TRUE. Can appear at DO (pre-test) or LOOP (post-test).
(no condition) | A bare DO...LOOP loops forever. Must use EXIT DO to break out.

Example

FUNCTION PBMAIN() AS LONG
    LOCAL x AS LONG
    LOCAL input AS STRING

    ' Pre-test: DO WHILE x > 0
    x = 5
    DO WHILE x > 0
        PRINT "Countdown: "; x
        x = x - 1
    LOOP
    PRINT "Done!"

    ' Post-test: loop at least once
    x = 0
    DO
        PRINT "x = "; x
        x = x + 1
    LOOP UNTIL x > 3

    ' Infinite loop with EXIT
    x = 0
    DO
        x = x + 1
        IF x > 10 THEN EXIT DO
        ' Skip even numbers
        IF (x AND 1) = 0 THEN ITERATE DO
        PRINT "Odd: "; x
    LOOP

    FUNCTION = 0
END FUNCTION

Generated Assembly

DO WHILE (pre-test)

__pb_do_N:                            ; LOOP TOP
; Evaluate condition
; Condition-false-jump -> __pb_do_end_N
; ... body ...
jmp    __pb_do_N                       ; loop back
__pb_do_end_N:

DO UNTIL (pre-test, exits when true)

__pb_do_N:
; Evaluate condition
; Condition-true-jump -> __pb_do_end_N (inverted from WHILE)
; ... body ...
jmp    __pb_do_N
__pb_do_end_N:

LOOP WHILE (post-test)

__pb_do_N:
__pb_do_iterate_N:                    ; ITERATE target
; ... body ...
; LOOP WHILE condition:
; Evaluate condition -> RAX
test   rax, rax
jne    __pb_do_N                       ; loop if true
__pb_do_end_N:

LOOP UNTIL (post-test)

__pb_do_N:
__pb_do_iterate_N:
; ... body ...
; LOOP UNTIL condition:
test   rax, rax
je     __pb_do_N                       ; loop if false
__pb_do_end_N:

Infinite DO...LOOP

__pb_do_N:
; ... body ...
jmp    __pb_do_N                       ; unconditional loop
; (no __pb_do_end_N generated)

EXIT DO

jmp    __pb_do_end_N                   ; exits the loop

ITERATE DO

jmp    __pb_do_iterate_N               ; skips to condition check


EXIT / ITERATE

Syntax

EXIT: EXIT FOR | WHILE | DO | FUNCTION | SUB
ITERATE: ITERATE FOR | WHILE | DO

Description

EXIT immediately terminates the specified loop or function. EXIT FOR, EXIT WHILE, and EXIT DO break out of their respective loops. EXIT FUNCTION and EXIT SUB perform an early return from a procedure, jumping directly to the epilogue.

ITERATE skips the remainder of the current loop iteration and jumps to the next condition check. For FOR loops, it jumps to the STEP increment and comparison. For WHILE and DO, it jumps directly to the condition test.

Parameters

Parameter | Description
FOR | Targets the innermost enclosing FOR/NEXT loop.
WHILE | Targets the innermost enclosing WHILE/WEND loop.
DO | Targets the innermost enclosing DO/LOOP.
FUNCTION | EXIT only. Jumps to the function epilogue, returning the current FUNCTION = value.
SUB | EXIT only. Jumps to the subroutine epilogue.

Example

FUNCTION PBMAIN() AS LONG
    LOCAL i AS LONG

    ' EXIT FOR: stop loop early
    FOR i = 1 TO 100
        PRINT "Checking: "; i
        IF i = 5 THEN EXIT FOR
    NEXT i
    PRINT "Stopped at: "; i

    ' ITERATE FOR: skip even numbers
    FOR i = 1 TO 10
        IF (i AND 1) = 0 THEN ITERATE FOR
        PRINT "Odd: "; i
    NEXT i

    ' EXIT FUNCTION: early return
    ' IF someError THEN EXIT FUNCTION

    FUNCTION = 0
END FUNCTION

EXIT

Syntax: EXIT FOR | EXIT WHILE | EXIT DO | EXIT FUNCTION | EXIT SUB
Statement | Generated ASM | Jumps To
EXIT FOR | jmp __pb_for_end_N | Past NEXT
EXIT WHILE | jmp __pb_while_end_N | Past WEND
EXIT DO | jmp __pb_do_end_N | Past LOOP
EXIT FUNCTION | jmp __pb_routine_epilogue_N | Function epilogue
EXIT SUB | jmp __pb_routine_epilogue_N | Subroutine epilogue

ITERATE

Syntax: ITERATE FOR | ITERATE WHILE | ITERATE DO
Statement | Generated ASM | Jumps To
ITERATE FOR | jmp __pb_for_iter_N | STEP + condition check
ITERATE WHILE | jmp __pb_while_N | WHILE condition check
ITERATE DO | jmp __pb_do_iterate_N | LOOP condition check

EXIT FUNCTION (Early Return)

; When EXIT FUNCTION is encountered:
jmp    __pb_routine_epilogue_N       ; skip to epilogue

; Epilogue (always at function end):
__pb_routine_epilogue_N:
mov    rax, [rbp - return_offset]    ; load FUNCTION = value
add    rsp, frame_size
pop    rbp
ret


Expressions & Operators

Source: pb_cg_emit_expr (pb_codegen.c:5091)

Integer Literal

mov    rax, <value>                    ; immediate constant

Variable Load

; Stack local:
mov    rax, [rbp - offset]

; BYREF parameter (pointer dereference):
mov    r11, [rbp - offset]             ; get pointer
mov    rax, [r11]                      ; dereference

; STATIC/GLOBAL:
mov    rax, [label]

Arithmetic Operators

; Evaluate left -> RAX, save to temp
; Evaluate right -> RAX
mov    r10, rax                         ; right operand
mov    rax, [rbp - left_offset]         ; reload left

; ADD (+)
add    rax, r10

; SUB (-)
sub    rax, r10

; MUL (*)
imul   r10                              ; RDX:RAX = RAX * r10

; DIV (/)
cqo                                     ; sign-extend RAX -> RDX:RAX
idiv   r10                              ; RAX = quotient

; Integer division (\)
; Same as DIV but uses signed division

; MOD
cqo
idiv   r10
mov    rax, rdx                         ; remainder in RDX

; AND (bitwise)
and    rax, r10

; OR (bitwise)
or     rax, r10

Unary Operators

; Negation (-)
mov    r10, 0
sub    r10, rax
mov    rax, r10

; NOT (logical)
test   rax, rax
je     __pb_not_true_N
xor    rax, rax                         ; 0 (was true -> false)
jmp    __pb_not_end_N
__pb_not_true_N:
mov    rax, 1                           ; 1 (was false -> true)
__pb_not_end_N:

Comparison Operators

; Compare left and right, set RAX to 0 or 1
mov    r10, rax                         ; right
mov    rax, [rbp - left_offset]         ; left
cmp    rax, r10
; then conditional set:
sete   al                               ; EQ
setne  al                               ; NE
setl   al                               ; LT
setle  al                               ; LE
setg   al                               ; GT
setge  al                               ; GE
movzx  rax, al                          ; extend to 64-bit

String Comparison

String comparisons (=, <>, <, >) are implemented using memcmp for performance. The result is normalized to 0 or 1.

; s1$ = "Hello", s2$ = "World"
; IF s1$ = s2$ THEN

; Load string pointers
mov    rcx, [rbp - s1_ptr]
mov    rdx, [rbp - s2_ptr]

; Compare lengths first (optimization)
mov    r8, [rbp - s1_len]
mov    r9, [rbp - s2_len]
cmp    r8, r9
jne    __pb_str_cmp_len_fail

; Compare content via memcmp
mov    r10, r8                          ; length
call   [__imp_memcmp]
test   eax, eax
sete   al                               ; 0 if equal, else non-zero
movzx  rax, al                          ; normalize to 0 or 1

Operator Precedence

Precedence | Operators
1 (highest) | () parentheses, ^ exponent
2 | Unary -, NOT
3 | * / \ MOD
4 | + - (binary), & (string concat)
5 | = <> < > <= >=
6 | AND (bitwise)
7 (lowest) | OR (bitwise)


File I/O

Description

PowerBASIC file I/O uses numbered file handles (#n) and supports four access modes: sequential text (OUTPUT/APPEND/INPUT) and binary (BINARY). All operations ultimately map to the Windows API functions CreateFileA, ReadFile, WriteFile, SetFilePointer, and CloseHandle.

OPEN

Syntax: OPEN "filename" FOR OUTPUT|APPEND|INPUT|BINARY AS #n
Source: pb_cg_emit_open (pb_codegen.c:6072)

Description

Opens a file and assigns it a file number #n. The mode determines the Win32 access flags and creation disposition.

Parameters

Parameter | Description
filename | Path to the file (STRING literal or variable)
mode | OUTPUT (write, create new), APPEND (write, keep existing), INPUT (read), BINARY (read+write)
n | Integer file number (1-255)

Example

OPEN "data.txt" FOR OUTPUT AS #1
OPEN "log.txt" FOR APPEND AS #2
OPEN "input.txt" FOR INPUT AS #3
OPEN "data.bin" FOR BINARY AS #4

Generated Assembly

mov    rcx, filename_ptr               ; lpFileName
mov    rdx, <desired_access>            ; see table below
mov    r8, 0                            ; dwShareMode = 0
mov    r9, 0                            ; lpSecurity = NULL
mov    [rsp + 32], <creation>           ; dwCreationDisposition
mov    [rsp + 40], 128                  ; FILE_ATTRIBUTE_NORMAL
mov    [rsp + 48], 0                    ; hTemplateFile = NULL
call   [__imp_CreateFileA]
mov    [rbp - handle_offset], rax       ; save file handle

; FOR APPEND: seek to end additionally
mov    rcx, [rbp - handle_offset]
xor    rdx, rdx                         ; distance = 0
mov    r8, 0                            ; unused
mov    r9, 2                            ; FILE_END
call   [__imp_SetFilePointer]

Access Modes

Mode | dwDesiredAccess | dwCreationDisposition
INPUT | 0x80000000 (GENERIC_READ) | 3 (OPEN_EXISTING)
OUTPUT | 0x40000000 (GENERIC_WRITE) | 2 (CREATE_ALWAYS)
APPEND | 0x40000000 (GENERIC_WRITE) | 4 (OPEN_ALWAYS)
BINARY | 0xC0000000 (GENERIC_READ|WRITE) | 4 (OPEN_ALWAYS)

PRINT #n

Syntax: PRINT #n, expr
Source: pb_cg_emit_writefile_handle (pb_codegen.c:5182)

Description

Writes formatted text to file #n using WriteFile with the stored file handle. The expression is formatted identically to console PRINT.

Parameters

Parameter | Description
n | The file number to write to
expr | Any printable expression (STRING, numeric, or comma/semicolon separated list)

Example

OPEN "output.txt" FOR OUTPUT AS #1
PRINT #1, "Hello World"
PRINT #1, "Answer: "; 42
CLOSE #1

Generated Assembly

; Format expression (same as console PRINT)
; Then WriteFile using saved file handle:
mov    rcx, [rbp - handle_offset]        ; file handle (NOT GetStdHandle)
mov    rdx, [rbp - print_ptr_offset]     ; buffer
mov    r8, [rbp - print_len_offset]      ; byte count
lea    r9, [rbp - written_offset]        ; &bytesWritten
mov    [rsp + 32], 0                     ; lpOverlapped = NULL
call   [__imp_WriteFile]

INPUT #n

Syntax: INPUT #n, variable$
Source: pb_cg_emit_read_file_line (pb_codegen.c:5291)

Description

Reads a line of text from file #n into a STRING variable. The file must be opened for INPUT or BINARY.

Parameters

Parameter | Description
n | The file number to read from
variable$ | A STRING variable to receive the line

Example

OPEN "input.txt" FOR INPUT AS #1
LOCAL line$ AS STRING
INPUT #1, line$
PRINT line$
CLOSE #1

Generated Assembly

mov    rcx, [rbp - handle_offset]        ; file handle
lea    rdx, [rbp - buffer_offset]         ; read buffer
mov    r8, <max_count>                   ; max bytes to read
lea    r9, [rbp - file_read_count]        ; &bytesRead
mov    [rsp + 32], 0
call   [__imp_ReadFile]
; NUL-terminate:
mov    r10, [rbp - file_read_count]       ; bytes read
lea    r11, [rbp - buffer_offset]
add    r11, r10
mov    byte [r11], 0                      ; buffer[bytesRead] = 0

GET (Binary Read)

Syntax: GET #n, [position,] variable
Source: pb_cg_emit_get (pb_codegen.c:780)

Description

Reads binary data from file #n into a variable. If position is specified, the file pointer is first set to that 1-based record position.

Parameters

Parameter | Description
n | The file number (must be opened for BINARY)
position | (Optional) 1-based record position
variable | Variable to read into (size determines bytes read)

Example

LOCAL val AS LONG
OPEN "data.bin" FOR BINARY AS #1
GET #1, 3, val          ' read 4-byte LONG at record 3
CLOSE #1

Generated Assembly

; Optional SEEK for record position
; Get target address -> RDX
mov    rcx, [rbp - handle_offset]
mov    rdx, target_ptr                   ; buffer
mov    r8, <element_size>               ; bytes to read
lea    r9, [rbp - file_read_count]
mov    [rsp + 32], 0
call   [__imp_ReadFile]

PUT (Binary Write)

Syntax: PUT #n, [position,] variable
Source: pb_cg_emit_put (pb_codegen.c:880)

Description

Writes binary data from a variable to file #n. If position is specified, the file pointer is first set to that 1-based record position.

Parameters

Parameter | Description
n | The file number (must be opened for BINARY)
position | (Optional) 1-based record position
variable | Variable whose data is written

Example

LOCAL val AS LONG
val = 12345
OPEN "data.bin" FOR BINARY AS #1
PUT #1, 1, val          ' write 4-byte LONG at record 1
CLOSE #1

Generated Assembly

mov    rcx, [rbp - handle_offset]
lea    rdx, [rbp - buffer_offset]        ; source data
mov    r8, <element_size>               ; bytes to write
lea    r9, [rbp - written_offset]
mov    [rsp + 32], 0
call   [__imp_WriteFile]

SEEK

Syntax: SEEK #n, position
Source: pb_cg_emit_seek (pb_codegen.c:6132)

Description

Sets the file pointer to a specific 1-based record position for subsequent GET/PUT operations. Internally converts to a 0-based byte offset and calls SetFilePointer.

Parameters

Parameter | Description
n | The file number
position | 1-based record position (numeric expression)

Example

OPEN "data.bin" FOR BINARY AS #1
SEEK #1, 10             ' jump to record 10
GET #1, rec             ' read record 10
CLOSE #1

Generated Assembly

; Convert 1-based position to 0-based
cmp    rax, 1
jge    __pb_seek_positive
xor    rax, rax                         ; clamp to 0
jmp    __pb_seek_ready
__pb_seek_positive:
sub    rax, 1                           ; 1-based -> 0-based
__pb_seek_ready:
mov    rcx, [rbp - handle_offset]
mov    rdx, rax                         ; distance to move
xor    r8, r8
xor    r9, r9
call   [__imp_SetFilePointer]

CLOSE #n

Syntax: CLOSE #n
Source: pb_cg_emit_close (pb_codegen.c:6115)

Description

Closes the file associated with #n by calling CloseHandle and zeroing out the stored handle. Safe to call on an already-closed file (no-op).

Parameters

Parameter | Description
n | The file number to close

Example

CLOSE #1                ' close file #1

Generated Assembly

mov    rcx, [rbp - handle_offset]
test   rcx, rcx
je     __pb_close_skip                  ; already closed
call   [__imp_CloseHandle]
mov    qword [rbp - handle_offset], 0   ; zero out handle
__pb_close_skip:


FOR / NEXT

Syntax

Syntax: FOR var = start TO end [STEP n] : ... : NEXT [var]
Source: PB_STMT_FOR handler (pb_codegen.c:6560)

Description

Executes a block of code a specific number of times. The loop counter variable starts at start and increments by STEP (default 1) on each iteration until it passes end. The compiler checks the loop condition at the top of each iteration. Supports both positive and negative STEP values. EXIT FOR can prematurely terminate the loop, and ITERATE FOR skips the remainder of the current iteration.

Parameters

Parameter | Description
var | A numeric variable (typically LOCAL LONG) used as the loop counter. Its value changes each iteration.
start | The initial value assigned to the counter variable before the first iteration.
end | The final (boundary) value. The loop exits when the counter passes this value.
STEP n | Optional increment value. Defaults to 1. Can be negative for counting down. Can be an expression.

Example

FUNCTION PBMAIN() AS LONG
    LOCAL i AS LONG

    ' Loop 1 to 10
    FOR i = 1 TO 10
        PRINT "Count: "; i
    NEXT i

    ' Loop with STEP 2 (odd numbers)
    FOR i = 1 TO 9 STEP 2
        PRINT "Odd: "; i
    NEXT i

    ' Countdown loop
    FOR i = 10 TO 1 STEP -1
        PRINT "Countdown: "; i
    NEXT i

    FUNCTION = 0
END FUNCTION

Generated Assembly (Positive STEP)

; Evaluate start -> RAX
mov    [rbp - var_offset], rax        ; FOR var = start

__pb_for_N:                           ; LOOP TOP
; Evaluate end -> RAX
mov    r10, rax                       ; r10 = end value
mov    rax, [rbp - var_offset]        ; reload loop variable
cmp    rax, r10                       ; compare var, end
jg     __pb_for_end_N                 ; exit if var > end

; ... loop body ...
; EXIT FOR jumps here: __pb_for_end_N
; ITERATE FOR jumps here: __pb_for_iter_N

__pb_for_iter_N:                      ; ITERATE label
mov    rax, 1                         ; default step = 1
add    [rbp - var_offset], rax        ; var = var + step
jmp    __pb_for_N                     ; loop back

__pb_for_end_N:                       ; EXIT point

Negative STEP

; Same structure but exit check reverses:
jl     __pb_for_end_N                 ; exit if var < end (negative step)
; ADD still used (negative step value subtracts)

EXIT FOR

jmp    __pb_for_end_N                 ; jump to end label

ITERATE FOR

jmp    __pb_for_iter_N                ; jump to increment + check


Function Prolog / Epilog

Source: pb_cg_emit_function_prolog (pb_codegen.c:1272), pb_cg_emit_routine epilogue (pb_codegen.c:1400)

Standard Prolog (frame <= 4096 bytes)

push   rbp
mov    rbp, rsp
sub    rsp, <frame_size>

Large Frame Prolog (with stack probing)

; Required when frame > 4096 to touch each guard page
push   rbp
mov    rbp, rsp
mov    r11, <frame_size>
__pb_stack_probe_loop_N:
cmp    r11, 4096
jle    __pb_stack_probe_last_N
sub    rsp, 4096
mov    r10, [rsp]                       ; touch page
sub    r11, 4096
jmp    __pb_stack_probe_loop_N
__pb_stack_probe_last_N:
sub    rsp, r11
mov    r10, [rsp]                       ; touch final page

Parameter Capture (Win64 ABI)

; First 4 integer/pointer args:
mov    [rbp - param0_offset], rcx       ; arg 1
mov    [rbp - param1_offset], rdx       ; arg 2
mov    [rbp - param2_offset], r8        ; arg 3
mov    [rbp - param3_offset], r9        ; arg 4

; Args 5+ from caller's stack frame:
mov    rax, [rbp + 48]                  ; arg 5 (above shadow space + return addr)
mov    [rbp - param4_offset], rax
mov    rax, [rbp + 56]                  ; arg 6
mov    [rbp - param5_offset], rax

; First 4 float args:
movsd  [rbp - fparam0_offset], xmm0     ; FP arg 1
movsd  [rbp - fparam1_offset], xmm1     ; FP arg 2
movsd  [rbp - fparam2_offset], xmm2     ; FP arg 3
movsd  [rbp - fparam3_offset], xmm3     ; FP arg 4

Standard Epilogue

; Load return value from [rbp - return_offset]
mov    rax, [rbp - return_offset]        ; integer return
; or: movsd xmm0, [rbp - return_offset]  ; float return
add    rsp, <frame_size>
pop    rbp
ret

PBMAIN Epilogue

mov    rcx, [rbp - return_offset]        ; exit code
call   [__imp_ExitProcess]
xor    rax, rax                           ; unreachable
add    rsp, <frame_size>
pop    rbp
ret

Stack Frame Layout

; RBP-based frame (grows downward):
[rbp + 16]    ; return address (from CALL)
[rbp + 8]     ; saved RBP
[rbp]         ; <- RBP points here
[rbp - 8]     ; return value slot
[rbp - 16]    ; GOSUB depth counter
[rbp - 24]    ; GOSUB stack base (32 entries * 8 bytes)
[rbp - N]     ; local variables and temporaries
; ... variable offsets ...
[rbp - frame_size] ; <- RSP after prolog


GOSUB / RETURN

Syntax

Syntax: GOSUB label ... label: ... RETURN
Source: pb_cg_emit_gosub_return (pb_codegen.c:1738), pb_cg_emit_gosub_pop_return (pb_codegen.c:1768)

Description

Calls a local subroutine (defined by a label) within the same function and returns to the statement immediately following GOSUB. Unlike CALL (which invokes external DLL functions), GOSUB is an intra-procedure mechanism. The compiler maintains a dedicated GOSUB return address stack in the stack frame (max 32 entries). Each GOSUB pushes the return address; each RETURN pops and jumps to it.

Parameters

Parameter | Description
label | The name of the subroutine label to jump to. The subroutine must be in the same FUNCTION or SUB and must end with RETURN.
RETURN | Ends a GOSUB subroutine and returns execution to the statement after the calling GOSUB.

Example

FUNCTION PBMAIN() AS LONG
    PRINT "Main start"

    ' Call local subroutine
    GOSUB PrintBanner
    GOSUB PrintBanner

    PRINT "Main end"
    FUNCTION = 0

PrintBanner:
    PRINT "===================="
    PRINT "  PowerBASIC x64"
    PRINT "===================="
    PRINT
    RETURN
END FUNCTION

GOSUB - Push Return Address

mov    r10, [rbp - gosub_depth_offset]     ; current depth
cmp    r10, 32                             ; PB_GOSUB_STACK_MAX
jl     __pb_gosub_push_ok_N
mov    rcx, 1
call   [__imp_ExitProcess]                 ; overflow -> abort
__pb_gosub_push_ok_N:
lea    r11, [rbp - gosub_stack_base]       ; stack base
mov    rax, r10
imul   rax, 8                              ; depth * 8
sub    r11, rax                            ; r11 = &stack[depth]
lea    rax, [__pb_gosub_return_N]          ; return address
mov    [r11], rax                          ; store on stack
add    r10, 1
mov    [rbp - gosub_depth_offset], r10     ; depth++
jmp    __pb_label_<routine>_<label>        ; jump to subroutine

RETURN - Pop Return Address

mov    r10, [rbp - gosub_depth_offset]     ; current depth
sub    r10, 1
mov    [rbp - gosub_depth_offset], r10     ; depth--
lea    r11, [rbp - gosub_stack_base]
mov    rax, r10
imul   rax, 8
sub    r11, rax                            ; r11 = &stack[depth-1]
mov    rax, [r11]                          ; load return address
jmp    rax                                 ; indirect jump to return point
__pb_gosub_return_N:                       ; return target

Example Output

GOSUB SayHello
PRINT "Back from GOSUB"
FUNCTION = 0
SayHello:
PRINT "Hello!"
RETURN


GOTO

Syntax

Syntax: GOTO label
Source: PB_STMT_GOTO handler (pb_codegen.c:6625)

Description

Transfers execution unconditionally to a specified label within the same function or subroutine. GOTO is commonly used for error handling, cleanup code, and skipping sections of code. Labels are defined by an identifier followed by a colon. The compiler generates a direct jmp instruction to the label's address.

Parameters

Parameter | Description
label | The name of the target label (without the trailing colon). The label must be defined in the same FUNCTION or SUB.

Example

FUNCTION PBMAIN() AS LONG
    LOCAL x AS LONG

    x = -5

    ' Error handling with GOTO
    IF x < 0 THEN GOTO ErrorHandler

    PRINT "Value is OK: "; x
    GOTO Cleanup

ErrorHandler:
    PRINT "Error: negative value detected!"
    x = 0

Cleanup:
    PRINT "Finished with x = "; x
    FUNCTION = 0
END FUNCTION

Generated Assembly

; Direct GOTO:
jmp    __pb_label_<routine>_<labelname>

; Label definition:
__pb_label_<routine>_<labelname>:

Example Assembly Output

IF x < 0 THEN GOTO ErrorHandler
PRINT "OK"
GOTO Done
ErrorHandler:
PRINT "Error!"
Done:
PRINT "Finished"
; Generated assembly:
mov    rax, [rbp - x_offset]
cmp    rax, 0
jge    __pb_skip_N                    ; skip if x >= 0
jmp    __pb_label_PBMAIN_ErrorHandler
__pb_skip_N:
; PRINT "OK" code
jmp    __pb_label_PBMAIN_Done
__pb_label_PBMAIN_ErrorHandler:
; PRINT "Error!" code
__pb_label_PBMAIN_Done:
; PRINT "Finished" code


IF / ELSE / END IF

IF / ELSE / ELSEIF / END IF

Syntax

Syntax: IF condition THEN stmts [ELSEIF cond THEN stmts] [ELSE stmts] END IF
Source: pb_cg_emit_condition_false_jump (pb_codegen.c:305)

Description

Conditionally executes blocks of code based on a boolean or comparison expression. The IF statement is the primary branching construct. The compiler evaluates the condition and generates a conditional jump to skip the THEN block when the condition is false. ELSEIF chains multiple conditions efficiently, and ELSE provides a default path. Supports both integer and floating-point comparisons with all standard relational operators.

Parameters

Parameter | Description
condition | A comparison (x < 10) or boolean expression. Non-zero evaluates as TRUE, zero as FALSE.
THEN | Introduces the block executed when the condition is true. Can be followed by a single statement on the same line or a multi-line block.
ELSEIF condition THEN | Tests an additional condition if all previous conditions were false. Multiple ELSEIF blocks are allowed.
ELSE | Optional block executed when no condition is true.
END IF | Terminates the IF block. Required for multi-line IF statements.

Example

FUNCTION PBMAIN() AS LONG
    LOCAL score AS LONG
    score = 85

    IF score >= 90 THEN
        PRINT "Grade: A"
    ELSEIF score >= 80 THEN
        PRINT "Grade: B"
    ELSEIF score >= 70 THEN
        PRINT "Grade: C"
    ELSE
        PRINT "Grade: F"
    END IF

    FUNCTION = 0
END FUNCTION

Generated Assembly - Integer Comparison

; Evaluate left expression -> RAX, save to temp
; Evaluate right expression -> RAX
mov    r10, rax
mov    rax, [rbp - left_offset]       ; reload left
cmp    rax, r10                       ; compare left, right
je     __pb_else_N                    ; jump to else if condition FALSE
; ... THEN body ...
jmp    __pb_endif_N
__pb_else_N:
; ... ELSE body ...
__pb_endif_N:

Integer Jump Mappings

Operator | Jump to ELSE (condition FALSE) | Meaning
= (EQ) | jne | Jump if not equal
<> (NE) | je | Jump if equal
< (LT) | jge | Jump if greater or equal
<= (LE) | jg | Jump if greater
> (GT) | jle | Jump if less or equal
>= (GE) | jl | Jump if less

Float Comparison

; Evaluate left -> XMM0, save to [rbp - temp]
movsd  xmm1, [rbp - temp]             ; reload left
ucomisd xmm1, xmm0                    ; unordered compare
ja     __pb_else_N                    ; jump based on flags

Float Jump Mappings

Operator | Jump to ELSE
= | jne
<> | je
< | jae
<= | ja
> | jbe
>= | jb

Boolean Truthiness Test

; Evaluate expression -> RAX (0 = false, non-0 = true)
test   rax, rax
je     __pb_else_N                    ; jump if 0 (false)

ELSEIF Expansion

; IF a THEN      -> cmp + jne __pb_elseif1_N
; ELSEIF b THEN  -> __pb_elseif1_N: cmp + jne __pb_else_N
; ELSE           -> __pb_else_N:
; END IF         -> __pb_endif_N:


LBOUND / UBOUND

Syntax

Syntax: LBOUND(array) | UBOUND(array)
Source: pb_cg_emit_bound_builtin (pb_codegen.c:4176)

Description

LBOUND returns the lowest valid index (lower bound) of an array. UBOUND returns the highest valid index (upper bound). These are essential for writing loops that iterate over arrays without hard-coding index ranges.

  • For fixed arrays the bounds are compile-time constants. The compiler emits an immediate MOV.
  • For dynamic arrays (REDIM) the bounds are stored in the array descriptor at runtime and are loaded from the stack frame.

Parameters

Parameter | Description
array | Any array variable (fixed or dynamic)

Example

LOCAL a(1 TO 10) AS LONG
LOCAL i AS LONG
FOR i = LBOUND(a) TO UBOUND(a)
    a(i) = i * 2
NEXT i
REDIM d(5 TO 15) AS LONG
LOCAL lo, hi AS LONG
lo = LBOUND(d)          ' lo = 5
hi = UBOUND(d)          ' hi = 15

Dynamic Array (REDIM)

; LBOUND: load from descriptor in stack frame
mov    rax, [rbp - lower_offset]         ; runtime lower bound

; UBOUND: load from descriptor
mov    rax, [rbp - upper_offset]         ; runtime upper bound

Fixed Array

; LBOUND: compile-time constant
mov    rax, <constant_lower>             ; e.g., 0 or 1

; UBOUND: compile-time constant
mov    rax, <constant_upper>             ; e.g., 10

Array Descriptor Layout (per array in stack frame)

; Dynamic array descriptor:
[rbp - ptr_offset]      ; pointer to heap data (from malloc)
[rbp - lower_offset]    ; lower bound (from REDIM)
[rbp - upper_offset]    ; upper bound (from REDIM)
[rbp - count_offset]    ; element count
[rbp - capacity_offset] ; allocated capacity
[rbp - stride_offset]   ; bytes per element

; Fixed array: just the data inline
[rbp - base + i*stride]  ; elements laid out contiguously


WinAPI Import by Ordinal

WinAPI Import by Ordinal V13

Syntax: DECLARE FUNCTION name LIB "dll" ALIAS "#ordinal" (params) AS type
Linker: MGC_IMPORT_THUNK_BY_ORDINAL (import_table.c)

Some Windows DLLs export functions by ordinal number rather than by name. V13 adds support for importing these via the # prefix in the ALIAS clause.

Parameters

Parameter | Description
name | PB name used to call the function in your code
"dll" | DLL filename (e.g., "kernel32.dll")
"#ordinal" | Ordinal number prefixed with # (e.g., "#42")
params | Parameter list (BYVAL/BYREF name AS type)
type | Return type (LONG, QUAD, etc.)

Example

#COMPILE EXE
#DIM ALL

' Import by ordinal from kernel32.dll
DECLARE FUNCTION GetProcessHeapOrd LIB "kernel32.dll" _
    ALIAS "#123" () AS QUAD

FUNCTION PBMAIN() AS LONG
    LOCAL hHeap AS QUAD
    hHeap = GetProcessHeapOrd()
    PRINT "Heap handle: "; hHeap
    FUNCTION = 0
END FUNCTION

Generated Import Table

; In .idata section:
; The PB declare name becomes the IAT symbol
; Linker registers it as MGC_IMPORT_THUNK_BY_ORDINAL
__imp_GetProcessHeapOrd:
    dq    IAT_GetProcessHeapOrd        ; Import Address Table entry

; At link time, the PE import directory records:
;   DLL: kernel32.dll
;   Import by ordinal: 123
;   Thunk: __imp_GetProcessHeapOrd

How It Works

  1. Parser detects ALIAS "#123" and marks the declaration as ordinal import
  2. Codegen emits call [__imp_<PBname>] as usual
  3. Linker creates a PE import directory entry with the IMAGE_ORDINAL_FLAG set
  4. Windows loader resolves the ordinal to the actual function address at load time


PRINT Statement

Syntax

Syntax: PRINT expr [; expr ...]
Source: pb_cg_emit_print_expr (pb_codegen.c:5674)

Description

Outputs text, numbers, or string expressions to the console using the Windows API WriteFile. Handles integers, floating-point values, and string data. Each expression is formatted and written in sequence. The PRINT statement is the primary means of console I/O and is essential for debugging output, status logging, and interactive programs.

Parameters

Parameter | Description
expr | Any expression to output (numeric literal, variable, string, or expression). Multiple expressions are supported.
; (semicolon separator) | Placed between expressions to suppress a newline. The next PRINT or expression continues on the same line.
(no trailing semicolon) | A PRINT statement ending without ; automatically appends a carriage return + line feed (CRLF).
PRINT (alone) | A bare PRINT with no expression outputs a blank line (just CRLF).

Example

FUNCTION PBMAIN() AS LONG
    LOCAL x AS LONG
    LOCAL name AS STRING

    x = 42
    name = "PowerBASIC"

    ' Print a variable with text
    PRINT "The value of x is "; x

    ' Print multiple items on one line
    PRINT "Name: "; name; "  Value: "; x

    ' Print blank line
    PRINT

    ' Print without newline (trailing ;)
    PRINT "Loading";
    PRINT "..."
    PRINT "Done!"

    FUNCTION = 0
END FUNCTION

Generated Assembly

Console Output Path

; 1. Format integer via wsprintfA
lea    rcx, [rbp - intbuf_offset]   ; buffer
lea    rdx, [__pb_fmt_i64]          ; format "%I64d"
mov    r8, rax                      ; value
call   [__imp_wsprintfA]            ; wsprintfA(buf, fmt, val)
mov    [rbp - print_len_offset], rax ; save length

; 2. Write to console via WriteFile
mov    rcx, -11                     ; STD_OUTPUT_HANDLE
call   [__imp_GetStdHandle]
mov    [rbp - written_offset], rax
mov    rcx, [rbp - written_offset]  ; hFile
mov    rdx, [rbp - print_ptr_offset] ; lpBuffer
mov    r8, [rbp - print_len_offset]  ; nBytesToWrite
lea    r9, [rbp - written_offset]    ; lpBytesWritten
mov    [rsp + 32], 0                 ; lpOverlapped = NULL
call   [__imp_WriteFile]

String Output

; String address -> RDX, length -> R8 via pb_cg_emit_string_address_len
; Then same WriteFile path as above

Newline

lea    rdx, [__pb_crlf]            ; "\r\n" in .rdata
mov    r8, 2                        ; 2 bytes
; then WriteFile

Float Output

; Format float to string first
lea    r8, [rbp - intbuf_offset]    ; buffer
mov    rdx, 15                      ; precision
call   [__imp__gcvt]                ; _gcvt(value, 15, buf)
mov    [rbp - print_ptr_offset], rax
mov    rcx, rax
call   [__imp_lstrlenA]
mov    [rbp - print_len_offset], rax
; then WriteFile

Imported WinAPI Functions

Function | DLL | Purpose
GetStdHandle | kernel32.dll | Get console output handle (STD_OUTPUT_HANDLE = -11)
WriteFile | kernel32.dll | Write bytes to console/file
wsprintfA | user32.dll | Format integer to string buffer
lstrlenA | kernel32.dll | Get ANSI string length
_gcvt | msvcrt.dll | Convert float to string


SELECT CASE

Syntax

Syntax: SELECT CASE expr : CASE value [, value] [, min TO max] : ... : [CASE ELSE : ...] : END SELECT
Source: pb_cg_emit_select (pb_codegen.c:5550)

Description

Branches execution based on the value of a selector expression. Each CASE clause tests the selector against one or more values. Unlike a chain of IF/ELSEIF statements, the selector expression is evaluated only once. Supports single value matches, comma-separated multiple values, and range matching with min TO max. CASE ELSE provides a default path when no other case matches.

Parameters

Parameter | Description
expr | The selector expression, evaluated once at the start. Must be a numeric or string type.
CASE value | A single value to match against the selector. Multiple values can be comma-separated: CASE 1, 3, 5.
CASE min TO max | A range of values (inclusive). Matches when min <= expr <= max. Example: CASE 80 TO 89.
CASE ELSE | Optional catch-all. Executes when no other CASE matches.
END SELECT | Terminates the SELECT CASE block.

Example

FUNCTION PBMAIN() AS LONG
    LOCAL grade AS LONG
    grade = 85

    ' Grade classification
    SELECT CASE grade
        CASE 90 TO 100
            PRINT "Grade: A"
        CASE 80 TO 89
            PRINT "Grade: B"
        CASE 70 TO 79
            PRINT "Grade: C"
        CASE 60 TO 69
            PRINT "Grade: D"
        CASE 0 TO 59
            PRINT "Grade: F"
        CASE ELSE
            PRINT "Invalid grade!"
    END SELECT

    FUNCTION = 0
END FUNCTION

Generated Assembly

; Evaluate selector expression -> RAX
mov    [rbp - select_value_offset], rax  ; save selector

; For each CASE branch:
mov    rax, [rbp - select_value_offset]  ; reload selector
mov    r10, <case_value>                 ; first case value
cmp    rax, r10
jne    __pb_select_next1_N              ; skip if no match
; ... CASE body ...
jmp    __pb_select_end_N                ; done

__pb_select_next1_N:                    ; next case label
mov    rax, [rbp - select_value_offset]
mov    r10, <case_value2>
cmp    rax, r10
jne    __pb_select_next2_N
; ... body ...
jmp    __pb_select_end_N

; For range CASE (min TO max):
__pb_select_next2_N:
mov    rax, [rbp - select_value_offset]
cmp    rax, <range_min>
jl     __pb_select_next3_N              ; below range
cmp    rax, <range_max>
jg     __pb_select_next3_N              ; above range
; ... range body ...
jmp    __pb_select_end_N

; CASE ELSE:
__pb_select_next3_N:
; ... default body ...

__pb_select_end_N:


String Functions

Description

PowerBASIC provides standard string manipulation functions that operate on STRING variables and return a new STRING. These functions are case-sensitive for character content but preserve the original case (except UCASE$/LCASE$). All functions return a new string; the original is unchanged.

LEFT$

Syntax: LEFT$(string$, n)

Description

Returns the leftmost n characters of string$. If n is less than 1, an empty string is returned. If n exceeds the string length, the entire string is returned.

Parameters

Parameter | Description
string$ | The source STRING expression
n | Number of characters to extract (numeric expression)

Example

LOCAL s$, r$ AS STRING
s$ = "Hello World"
r$ = LEFT$(s$, 5)       ' r$ = "Hello"

Generated Assembly

Source: pb_cg_emit_left_builtin (pb_codegen.c:2820)
; Clamp n to [0, buffer_capacity-1]
cmp    rax, 0
jge    __pb_left_count_nonneg_N
xor    rax, rax
__pb_left_count_nonneg_N:
cmp    rax, <capacity-1>
jle    __pb_left_count_cap_N
mov    rax, <capacity-1>
__pb_left_count_cap_N:
mov    [rbp - print_len_offset], rax   ; effective count

lea    rcx, [rbp - asciiz_scratch]     ; dest buffer
mov    rdx, [rbp - print_ptr_offset]   ; source
mov    r8, [rbp - print_len_offset]
add    r8, 1                           ; count + 1 for NUL
call   [__imp_lstrcpynA]               ; lstrcpynA(dest, src, n+1)
lea    rax, [rbp - asciiz_scratch]     ; return buffer ptr

RIGHT$

Syntax: RIGHT$(string$, n)

Description

Returns the rightmost n characters of string$. If n is less than 1, an empty string is returned. If n exceeds the string length, the entire string is returned.

Parameters

Parameter | Description
string$ | The source STRING expression
n | Number of characters to extract (numeric expression)

Example

LOCAL s$, r$ AS STRING
s$ = "Hello World"
r$ = RIGHT$(s$, 5)      ' r$ = "World"

Generated Assembly

Source: pb_cg_emit_right_builtin (pb_codegen.c:2863)
; Get source length via lstrlenA
mov    rcx, [rbp - print_ptr_offset]
call   [__imp_lstrlenA]
mov    [rbp - written_offset], rax     ; source length
; Clamp n to [0, min(source_len, buffer_capacity)]
; Compute start offset = source_len - n
mov    rdx, [rbp - print_ptr_offset]
add    rdx, [rbp - written_offset]     ; src + source_len
sub    rdx, [rbp - print_len_offset]   ; src + source_len - n
; Copy n bytes + NUL
lea    rcx, [rbp - asciiz_scratch]
mov    r8, [rbp - print_len_offset]
call   [__imp_memcpy]
; NUL-terminate at buffer[n]

MID$

Syntax: MID$(string$, start [, count])

Description

Returns a substring of string$ starting at position start (1-based). If count is omitted, returns all characters from start to the end. If start is less than 1, it is clamped to 1. If start exceeds the string length, an empty string is returned.

Parameters

Parameter | Description
string$ | The source STRING expression
start | 1-based start position (numeric expression)
count | (Optional) Number of characters to extract; defaults to remaining length

Example

LOCAL s$, r$ AS STRING
s$ = "Hello World"
r$ = MID$(s$, 7, 5)     ' r$ = "World"
r$ = MID$(s$, 7)        ' r$ = "World" (rest of string)

Generated Assembly

Source: pb_cg_emit_mid_builtin (pb_codegen.c:2932)
; Clamp start >= 1, convert to 0-based
cmp    rax, 1
jge    __pb_mid_start_ok_N
mov    rax, 1
__pb_mid_start_ok_N:
sub    rax, 1                           ; 0-based index
; If start >= source_len -> empty result
cmp    rax, [rbp - written_offset]
jae    __pb_mid_empty_N
; Compute count (or remaining length), clamp to buffer
; Copy from source[start] for count+1 bytes
mov    rdx, [rbp - print_ptr_offset]
add    rdx, rax                         ; source + start
lea    rcx, [rbp - asciiz_scratch]
mov    r8, [rbp - print_len_offset]
add    r8, 1
call   [__imp_lstrcpynA]
jmp    __pb_mid_done_N
__pb_mid_empty_N:
mov    byte [rbp - asciiz_scratch], 0   ; empty string
__pb_mid_done_N:

TRIM$ / LTRIM$ / RTRIM$

Syntax: TRIM$(string$) LTRIM$(string$) RTRIM$(string$)

Description

Removes whitespace characters (space, tab, CR, LF) from a string.

  • TRIM$ - removes both leading and trailing whitespace
  • LTRIM$ - removes leading whitespace only
  • RTRIM$ - removes trailing whitespace only

Parameters

Parameter | Description
string$ | The STRING expression to trim

Example

LOCAL s$, r$ AS STRING
s$ = "   Hello World   "
r$ = TRIM$(s$)          ' r$ = "Hello World"
r$ = LTRIM$(s$)         ' r$ = "Hello World   "
r$ = RTRIM$(s$)         ' r$ = "   Hello World"

Generated Assembly

Source: pb_cg_emit_trim_builtin (pb_codegen.c:3066) + pb_cg_emit_trim_temp (pb_codegen.c:1930)
; Copy source to scratch buffer
lea    rcx, [rbp - asciiz_scratch]
mov    rdx, [rbp - print_ptr_offset]
mov    r8, capacity
call   [__imp_lstrcpynA]

; Find first non-whitespace (leading)
xor    rax, rax                        ; index = 0
__pb_trim_lead_loop:
cmp    rax, [rbp - len_offset]
jae    __pb_trim_all_ws_N              ; all whitespace -> empty
mov    r9b, byte [r11 + rax]           ; buffer[index]
cmp    r9b, 32                         ; space
je     __pb_trim_lead_next
cmp    r9b, 9                          ; tab
je     __pb_trim_lead_next
cmp    r9b, 10                         ; LF
je     __pb_trim_lead_next
cmp    r9b, 13                         ; CR
je     __pb_trim_lead_next
jmp    __pb_trim_lead_done
__pb_trim_lead_next:
inc    rax
jmp    __pb_trim_lead_loop
__pb_trim_lead_done:

; Find last non-whitespace (trailing)
mov    rdx, [rbp - len_offset]
__pb_trim_trail_loop:
cmp    rdx, rax
jbe    __pb_trim_bounds_done           ; crossed over
; Check buffer[rdx-1] for whitespace, dec if so
; ...
; Shift left if leading whitespace removed
; NUL-terminate at new position

UCASE$ / LCASE$

Syntax: UCASE$(string$) LCASE$(string$)

Description

Converts all alphabetic characters in string$ to uppercase or lowercase respectively. Non-alphabetic characters are unchanged. Only ASCII range [A-Za-z] is handled.

Parameters

Parameter | Description
string$ | The STRING expression to convert

Example

LOCAL s$, r$ AS STRING
s$ = "Hello World"
r$ = UCASE$(s$)         ' r$ = "HELLO WORLD"
r$ = LCASE$(s$)         ' r$ = "hello world"

Generated Assembly

Source: pb_cg_emit_string_builtin_to_heap_temp (pb_codegen.c:3397)
; Copy source to heap, iterate byte-by-byte:
; UCASE: if 'a' <= byte <= 'z': sub byte, 32
; LCASE: if 'A' <= byte <= 'Z': add byte, 32
; Then point descriptor to result


String Operations

Description

PowerBASIC provides built-in string operations for concatenation, assignment, length retrieval, pointer access, and comparison. All strings are NUL-terminated (C-compatible) and stored in stack-local or heap-allocated buffers.

Syntax

Concatenation: result$ = a$ + b$ [+ c$ ...]
Assignment: dest$ = expr$
Length: LEN(string$)
String pointer: STRPTR(string$)
Variable pointer: VARPTR(variable)
Comparison: a$ = b$ a$ < b$ a$ > b$

Parameters

Parameter | Description
string$ | Any valid STRING expression or variable
variable | Any variable (STRING or numeric); VARPTR returns its address
a$, b$, c$ | STRING operands to concatenate

String Concatenation (+)

Syntax: result$ = a$ + b$ [+ c$ ...]
Source: pb_cg_emit_string_concat_to_heap_temp (pb_codegen.c:3648)

Example

LOCAL a$, b$, c$ AS STRING
a$ = "Hello"
b$ = " World"
c$ = a$ + b$          ' c$ = "Hello World"

Phase 1 - Compute Total Length

xor    rax, rax
; For each part: get address -> RAX, length -> R10
mov    [rbp - ptr_offset_i], rax       ; save part pointer
mov    [rbp - len_offset_i], r10       ; save part length
add    [rbp - total_bytes], r10        ; total += len

Phase 2 - Allocate + Copy

mov    rcx, [rbp - total_bytes]
add    rcx, 8                          ; + padding for NUL
call   [__imp_malloc]                  ; allocate buffer
mov    [rbp - out_ptr], rax
; For each part:
mov    rcx, [rbp - out_ptr]
add    rcx, [rbp - write_pos]          ; dest = base + pos
mov    rdx, [rbp - ptr_offset_i]       ; src = part data
mov    r8, [rbp - len_offset_i]        ; count = part length
call   [__imp_memcpy]                  ; copy
add    [rbp - write_pos], r8           ; pos += len
; NUL-terminate:
mov    r11, [rbp - out_ptr]
add    r11, [rbp - total_bytes]
mov    byte [r11], 0

String Assignment (to local STRING)

Source: pb_cg_emit_string_expr_to_descriptor (pb_codegen.c:3910)

Example

LOCAL s AS STRING
s = "Hello World"       ' literal assignment
s = someFunction$()     ' expression result assignment
; String literal assignment: lstrcpynA
lea    rcx, [rbp - dest_offset]        ; dest buffer
mov    rdx, [rbp - print_ptr_offset]   ; source ptr
mov    r8, capacity                    ; max chars
call   [__imp_lstrcpynA]

; String expression assignment: malloc + memcpy + descriptor update

LEN()

Syntax: LEN(string$)
Source: pb_cg_emit_len_builtin (pb_codegen.c:4108)

Description

Returns the number of characters in string$ (excluding NUL terminator). For a string literal the length is a compile-time constant.

Example

LOCAL s, n AS STRING
s = "Hello"
n = LEN(s)              ' n = 5
; String literal: constant
mov    rax, <literal_length>

; Local STRING: load from descriptor
mov    rax, [rbp - length_offset]       ; length slot in frame

; STATIC/GLOBAL STRING: load from static data
mov    rax, [<label>_len]

STRPTR()

Syntax: STRPTR(string$)
Source: pb_cg_emit_strptr_builtin (pb_codegen.c:4130)

Description

Returns the memory address of the string data (a pointer to the first character). Useful when calling external DLL functions that expect a char*.

Example

LOCAL s AS STRING, p AS DWORD
s = "Hello"
p = STRPTR(s)           ' p = address of 'H'
; Local STRING: load data pointer from descriptor
mov    rax, [rbp - offset]              ; string data pointer

; BYREF STRING: dereference pointer
mov    r11, [rbp - offset]              ; get pointer-address
mov    rax, [r11]                       ; deref to data pointer

; Concatenation expression: scratch buffer address
lea    rax, [rbp - asciiz_scratch_offset]

VARPTR()

Syntax: VARPTR(variable)
Source: pb_cg_emit_varptr_builtin (pb_codegen.c:4140)

Description

Returns the memory address of any variable. For a STRING variable this returns the address of the string descriptor, not the string data (use STRPTR for that).

Example

LOCAL x AS LONG, p AS DWORD
x = 42
p = VARPTR(x)           ' p = stack address of x
; Stack local: LEA for address
lea    rax, [rbp - var_offset]

; BYREF parameter: already an address
mov    rax, [rbp - param_offset]

; STATIC/GLOBAL: LEA for static label
lea    rax, [static_label]

; Array element: compute element address
mov    rax, r11                         ; from pb_cg_emit_array_address

String Comparison

Source: pb_cg_emit_string_comparison_expr (pb_codegen.c:4930)

Description

String comparison is case-sensitive and lexicographic. It first compares the common prefix via memcmp, then falls back to length comparison if the prefix is identical. Supports all relational operators: =, <>, <, >, <=, >=.

Example

LOCAL a$, b$ AS STRING
a$ = "Apple"
b$ = "Banana"
IF a$ < b$ THEN PRINT "Apple comes first"   ' true
; Compare first min(lenL, lenR) bytes with memcmp
mov    rcx, [rbp - left_ptr]
mov    rdx, [rbp - right_ptr]
mov    r8, min_len
call   [__imp_memcmp]
movsxd rax, eax                        ; sign-extend 32-bit result
cmp    rax, 0
jne    __pb_str_cmp_ready_N            ; different -> use memcmp result
; Prefix equal, compare lengths:
mov    rax, [rbp - left_len]
cmp    rax, [rbp - right_len]
; 0 if equal, -1 if left shorter, 1 if left longer
; Then comparison jump: je/jne/jl/jg/jle/jge __pb_str_true_N


LINE INPUT & FORMAT$

LINE INPUT & FORMAT$ V13

LINE INPUT #n, var$ V13 rework

Syntax: LINE INPUT #n, stringvar$
V13 change: Reworked from bulk ReadFile to CR/LF-aware one-byte loop

Reads an entire line from an open file into a string variable, discarding the trailing CR/LF. In V13, the implementation was reworked from a single bulk ReadFile call to a character-by-character read loop that correctly handles CR, LF, and CRLF line endings.

Example

#COMPILE EXE
#DIM ALL

FUNCTION PBMAIN() AS LONG
    LOCAL ln AS STRING
    OPEN "data.txt" FOR INPUT AS #1
    WHILE NOT EOF(1)
        LINE INPUT #1, ln
        PRINT ln
    WEND
    CLOSE #1
    FUNCTION = 0
END FUNCTION

LINE INPUT Behavior

File Content | Result in var$
Hello\r\n | "Hello"
World\n | "World"
Line\r | "Line"
NoNewline | "NoNewline"

V13 Implementation (One-Byte Loop)

; Read one byte at a time until CR, LF, or EOF
__pb_liread_loop:
mov    rcx, [rbp - handle_offset]
lea    rdx, [rbp - one_byte_temp]
mov    r8, 1                              ; read 1 byte
lea    r9, [rbp - bytes_read]
mov    [rsp + 32], 0
call   [__imp_ReadFile]
cmp    rax, 0
je     __pb_liread_eof
mov    al, byte [rbp - one_byte_temp]
cmp    al, 13                             ; CR
je     __pb_liread_check_lf
cmp    al, 10                             ; LF
je     __pb_liread_done
; Store byte in buffer, increment position
jmp    __pb_liread_loop
__pb_liread_check_lf:
; Peek ahead for LF after CR (CRLF handling)
; If LF follows, consume it
__pb_liread_done:
; NUL-terminate buffer

FORMAT$ (Numeric) V13 helpers

Syntax: result$ = FORMAT$(numeric)
Runtime: pb_str64_format_i64, pb_str64_format_d (pb_string64.c)

V13 adds runtime helper functions for numeric formatting (pb_str64_format_i64 for integers, pb_str64_format_d for doubles). These provide a practical numeric-to-string conversion subset. Full PB picture/mask formatting remains future work.

Example

LOCAL s AS STRING
LOCAL n AS LONG
n = 12345
s = FORMAT$(n)        ' "12345"

LOF() / EOF() V12+ carry-forward

Syntax: LOF(#n) | EOF(#n)

Both use GetFileSize internally. EOF compares current file pointer position against total size. File number must be a constant 1..15.


Heap Strings & File I/O V19

Heap-Backed Dynamic Strings

V19 introduces genuine heap-managed dynamic string runtimes: pb_string64 (ANSI) and pb_wstring64 (UTF-16). Previous versions used fixed-length ASCIIZ stack buffers.

STRING64 Runtime (591 lines)

File: src/runtime/pb_string64.c, include/compiler/runtime/pb_string64.h
Function | Description
pb_str64_init | Allocate and initialize STRING descriptor
pb_str64_assign | Assign from C string
pb_str64_concat | Concatenate multiple STRING operands
pb_str64_compare | Relational comparison (<, =, >)
pb_str64_instr | Find substring position
pb_str64_left/right/mid | Substring extraction
pb_str64_trim/ucase/lcase | Whitespace/case operations
pb_str64_space/string | Generate filled strings
pb_str64_remove/extract | Remove substring / extract field
pb_str64_format_i64/_d | Numeric formatting
UTF-8 conversion | Convert from UTF-16LE to narrow UTF-8

WSTRING64 Runtime (388 lines)

File: src/runtime/pb_wstring64.c, include/compiler/runtime/pb_wstring64.h

Mirror API of STRING64 operating on uint16_t* (UTF-16 code units). Includes: init, assign, concat, compare, instr, left, right, mid, trim, ucase, lcase, space, string, remove, extract, parse, WCHR, ASCW, from_ascii, to_ascii, plus UTF-8/UTF-16LE conversion helpers.

OPEN FOR RANDOM / BINARY

Syntax: OPEN "file" FOR RANDOM AS #n LEN = recLen | OPEN "file" FOR BINARY AS #n

V19 adds full RANDOM and BINARY file modes. Both open handles for simultaneous read+write using the Win32 file pipeline.

RANDOM Mode

' Open random-access file with 128-byte records
OPEN "data.dat" FOR RANDOM AS #1 LEN = 128

' Write record 5
PUT #1, 5, myRecord

' Read record 3
GET #1, 3, myRecord

' Query position
PRINT LOC(1)     ' record number (RANDOM mode)
PRINT SEEK(1)    ' 1-based byte position

' Seek to record
SEEK #1, 10

BINARY Mode

OPEN "data.bin" FOR BINARY AS #2
GET #2, 100, value     ' read at byte offset 100
PUT #2, 200, value     ' write at byte offset 200
SEEK #2, 50            ' seek to byte 50
PRINT LOC(2)           ' byte position (BINARY mode)

Scalar Size Hardening

Numeric GET/PUT now uses the packed scalar byte size instead of always 8 bytes. UDTs with dynamic STRING/WSTRING fields are rejected with a diagnostic in BINARY/RANDOM mode.

Encoding-Aware LINE INPUT for STRING

Syntax: LINE INPUT #n, s$ with encoding-aware files

V19 adds UTF-16LE-to-narrow conversion: LINE INPUT from UTF16LE/UTF16LE_BOM files now reads UTF-16 code units and converts to UTF-8-compatible heap strings for STRING targets.

OPEN "unicode.txt" FOR INPUT ENCODING UTF16LE_BOM AS #1
LINE INPUT #1, s$     ' reads UTF-16LE, converts to STRING
CLOSE #1


V22 Macro Preprocessor

V22 introduces the full macro expansion engine. Macros are expanded before the lexer runs, so they can generate any PB code including declarations, statements, and expressions.

Object-Like Macros: #DEFINE

Syntax: #DEFINE name value

Simple text-replacement macros. Every occurrence of name in subsequent source is replaced with value.

#DEFINE MAX_COUNT 100
#DEFINE APP_NAME "MyApp"

LOCAL i AS LONG
FOR i = 1 TO MAX_COUNT
    PRINT APP_NAME
NEXT i
Duplicates: Redefining an existing #DEFINE symbol produces a diagnostic error.

Single-Line Function Macros

Syntax: MACRO name(args) = expression

Expression macros that expand inline with argument substitution.

MACRO Add1(x) = (x + 1)
MACRO Square(n) = (n * n)
MACRO Max(a, b) = IIF((a) > (b), (a), (b))

LOCAL v AS LONG
v = Add1(10)       ' Expands to: v = (10 + 1)
v = Square(v)      ' Expands to: v = (v * v)

Multi-Line Statement Macros

Syntax: MACRO name(args) ... END MACRO

Block macros that expand to multiple statements. Arguments are substituted by name.

MACRO SAY(text)
    PRINT text
END MACRO

MACRO SwapVars(a, b)
    LOCAL t AS LONG
    t = a
    a = b
    b = t
END MACRO

FUNCTION PBMAIN() AS LONG
    LOCAL x, y AS LONG
    x = 10 : y = 20
    SwapVars(x, y)
    SAY("Hello from macro!")
END FUNCTION

MACROFUNCTION with MACROTEMP

Syntax: MACROFUNCTION name(args) ... END MACRO = expr

Function-like macros that can declare temporary variables with MACROTEMP. MACROTEMP generates a unique name for each expansion, preventing collisions.

MACROFUNCTION Twice(x)
    MACROTEMP t
    LOCAL t AS LONG
    t = x * 2
END MACRO = t

FUNCTION PBMAIN() AS LONG
    LOCAL a, b AS LONG
    a = Twice(5)     ' a = 10, uses unique temporary
    b = Twice(a)     ' b = 20, uses different unique temporary
END FUNCTION
Known limit: MACROFUNCTION does not yet implement every PowerBASIC macro edge case. Basic expression macros with MACROTEMP work well for common patterns.

Recursive Macro Protection

The preprocessor detects and rejects recursive macro invocations and excessive expansion depth (default limit: 256 levels).

' This produces a diagnostic error, not infinite expansion
' #DEFINE X Y
' #DEFINE Y X
' PRINT X    ' Error: recursive macro expansion

#INCLUDE ONCE and #INCLUDEONCE

Syntax: #INCLUDE ONCE "file.inc" or #INCLUDEONCE "file.inc"

Prevents duplicate inclusion of the same file. Useful for header files included from multiple locations. Missing-file diagnostics are emitted when the included file cannot be found.

#INCLUDE ONCE "win32api.inc"
#INCLUDEONCE "mytypes.pbi"

#COMPILE DLL and GUI Mode Rejection

V22 explicitly rejects unsupported compile modes with a clear diagnostic instead of silently producing a wrong EXE type:

' #COMPILE DLL "mylib.dll"      ' Error: DLL output not yet implemented
' #COMPILE EXE "app.exe" GUI     ' Error: GUI subsystem not yet implemented

Predefined V22 Symbol

V22 defines PBXA64_V22 as a conditional symbol, allowing version-gated code:

#IF %DEF(PBXA64_V22)
    PRINT "Running under PBXA64 V22 or later"
    ' Use V22 macro features
#ELSE
    PRINT "Older version - macros not available"
#ENDIF

#UNDEF

Syntax: #UNDEF name

Removes a previously defined conditional symbol or #DEFINE macro.

#DEFINE DEBUG 1
' ... debug code here ...
#UNDEF DEBUG
' Debug code no longer active

Diagnostics

Error | Cause
Duplicate #DEFINE | Symbol already defined with a value
Recursive macro expansion | Macro expansion led to itself
Expansion depth exceeded | Too many nested macro invocations
Missing END MACRO | MACRO block not closed
MACROFUNCTION arity mismatch | Wrong number of arguments provided
#INCLUDE ONCE file not found | Included file does not exist
DLL/GUI mode not supported | #COMPILE target not yet implemented


V25 Memory & String Commands

V25 Memory & String-Packing Commands

V25 extends V24 with low-level memory access, binary string packing/unpacking, and string-array manipulation.

Memory Access

POKE

Syntax: POKE address, value

Writes a single byte to a memory address.

LOCAL x AS LONG, p AS QUAD
p = VARPTR(x)
POKE p, 66    ' Write byte 66 to x

POKE$

Syntax: POKE$ address, string$

Writes a block of bytes from a string to a memory address.

LOCAL buf AS STRING * 32
POKE$ STRPTR(buf), "ABCD"

PEEK

Syntax: PEEK(address)

Reads a single byte from a memory address. Returns a numeric value.

x = PEEK(VARPTR(var))

Binary String Packing (V25)

Pack numeric values into fixed-size binary strings for random file I/O.

Function | Packs To | Bytes
MKBYT$(n) | BYTE | 1
MKI$(n) | INTEGER | 2
MKL$(n) | LONG | 4
MKDWD$(n) | DWORD | 4
MKQ$(n) | QUAD | 8
MKS$(n) | SINGLE | 4
MKD$(n) | DOUBLE | 8
MKE$(n) | EXT | 10
MKCUR$(n) | CURRENCY | 8
MKCUX$(n) | CUX | 8
packed = MKL$(123456) + MKS$(1.5)
PUT #1, 1, packed

Binary String Unpacking (V25)

Function | Unpacks From | Bytes
CVBYT(s$) | BYTE | 1
CVI(s$) | INTEGER | 2
CVL(s$) | LONG | 4
CVDWD(s$) | DWORD | 4
CVQ(s$) | QUAD | 8
CVS(s$) | SINGLE | 4
CVD(s$) | DOUBLE | 8
CVE(s$) | EXT | 10
CVCUR(s$) | CURRENCY | 8
CVCUX(s$) | CUX | 8
GET #1, 1, packed
x = CVL(LEFT$(packed, 4))

String Helpers (V25)

NUL$

Syntax: NUL$(n)

Returns a string of n zero (null) bytes.

DEC$

Syntax: DEC$(n)

Returns the decimal string representation of a number (like STR$ but without leading space).

String-Array Commands (V25)

JOIN$

Syntax: JOIN$(array$(), delimiter$)

Joins all elements of a dynamic STRING array into a single string, separated by the delimiter.

REDIM arr(1 TO 3) AS STRING
arr(1) = "a" : arr(2) = "b" : arr(3) = "c"
PRINT JOIN$(arr(), ",")    ' "a,b,c"

SPLIT

Syntax: SPLIT source$, delimiter$ INTO array$()

Splits a source string by delimiter into a dynamic STRING array. Array is automatically REDIM'd.

REDIM parts(1 TO 1) AS STRING
SPLIT "one,two,three", "," INTO parts()
' parts(1)="one", parts(2)="two", parts(3)="three"

MID$ Statement

Syntax: MID$(target$, pos [, n]) = value$

In-place substring assignment. Replaces characters in the target string without changing its length.

s = "abcdef"
MID$(s, 2, 3) = "XYZ"    ' s = "aXYZef"
Note: MID$ assignment performs in-place replacement and does not grow the target string. If value$ is shorter than the replacement range, remaining characters keep their original values.

V24 String Commands (Included in V25)

V25 includes all V24 string-completion commands: REPEAT$, STRDELETE$, STRINSERT$, STRREVERSE$, BIN$, OCT$, LTRIM$, RTRIM$, REPLACE$, SCAN$, TALLY, VERIFY, COMMAND$, ENVIRON$, CURDIR$, TAB$, PEEK$, INPUT$, INKEY$, WAITKEY$.


PBXA64 - Array Commands (V26/V27)

Array Commands (V26/V27)

V26 introduced array operations for one-dimensional dynamic numeric arrays. V27 extended with DELETE and INSERT.

ARRAY ASSIGN

Syntax: ARRAY ASSIGN a(), value

Fills all elements of a dynamic array with a single value.

REDIM nums(1 TO 5) AS LONG
ARRAY ASSIGN nums(), 7     ' All elements = 7

ARRAY REVERSE

Syntax: ARRAY REVERSE a()

Reverses the order of elements in a dynamic array.

nums(1)=1: nums(2)=2: nums(3)=3
ARRAY REVERSE nums()       ' Now: 3,2,1

ARRAY SORT

Syntax: ARRAY SORT a()

Sorts a dynamic numeric array in ascending order.

nums(1)=7: nums(2)=3: nums(3)=5
ARRAY SORT nums()          ' Now: 3,5,7

ARRAY COPY

Syntax: ARRAY COPY src() TO dst()

Copies all elements from source array to destination array. Destination is automatically REDIM'd.

REDIM src(1 TO 3) AS LONG
REDIM dst(1 TO 1) AS LONG
ARRAY ASSIGN src(), 42
ARRAY COPY src() TO dst()

ARRAY SCAN

Syntax: ARRAY SCAN a(), = value TO result

Searches for an element matching a value. Result receives the 1-based index, or 0 if not found.

ARRAY SCAN nums(), = 7 TO found
IF found > 0 THEN PRINT "at index "; found

ARRAY DELETE (V27)

Syntax: ARRAY DELETE a(index)

Removes an element from a dynamic array. Following elements shift down.

ARRAY INSERT (V27)

Syntax: ARRAY INSERT a(index), value

Inserts a value at the given position. Following elements shift up.

Current limit: These commands target one-dimensional dynamic numeric arrays. Multi-dimensional and STRING array support is future work.


PBXA64 - DATA, READ, RESTORE (V26)

DATA, READ, RESTORE (V26)

DATA

Syntax: DATA value1, value2, ...

Stores literal values at compile time for use with READ. DATA statements typically appear at the end of the source file. Supports labelled DATA blocks.

FUNCTION PBMAIN() AS LONG
    LOCAL a AS LONG, b AS LONG, s AS STRING
    READ a, b, s
    PRINT a; b; s
    FUNCTION = 0
END FUNCTION

DATA 12, 34, "MASTER"

myData:
DATA 100, 200

READ

Syntax: READ var1 [, var2, ...]

Reads values sequentially from DATA statements into variables. Supports numeric and string variables. The DATA pointer advances after each READ.

LOCAL a AS LONG, s AS STRING
READ a, s
READ a

RESTORE

Syntax: RESTORE [label]

Resets the DATA pointer to the beginning of DATA statements. With a label, points to a specific labelled DATA block.

RESTORE          ' Reset to first DATA statement
RESTORE myData   ' Point to myData: block

Full Example

#COMPILE EXE "data_demo.exe"
#DIM ALL

FUNCTION PBMAIN() AS LONG
    LOCAL a AS LONG, b AS LONG, s AS STRING
    READ a, b, s
    PRINT a; b; " "; s
    RESTORE myData
    READ a, b
    PRINT a; b
    FUNCTION = 0
END FUNCTION

DATA 12, 34, "MASTER"

myData:
DATA 100, 200


PBXA64 - Error Handling (V26)

Error Handling (V26)

ON ERROR GOTO

Syntax: ON ERROR GOTO label

Redirects runtime errors to a label. Use ERR and ERL to inspect the error.

ON ERROR GOTO failed
' ... code that may error ...
EXIT FUNCTION
failed:
    PRINT "Error "; ERR; " at line "; ERL
    ERRCLEAR
    RESUME NEXT

ON ERROR RESUME NEXT

Syntax: ON ERROR RESUME NEXT

Ignores errors and continues execution at the next statement.

ON ERROR RESUME NEXT
x = 1 / 0     ' Division by zero ignored
PRINT "continues"

ON ERROR GOTO 0

Syntax: ON ERROR GOTO 0

Disables error trapping. Errors terminate the program.

ERR

Syntax: ERR

Returns the last runtime error number.

ERL

Syntax: ERL

Returns the line number of the last runtime error.

ERRCLEAR

Syntax: ERRCLEAR

Clears the current error state. Sets ERR to zero and clears the error flag.

ERROR n

Syntax: ERROR n

Triggers a runtime error with code n. If no error handler is active, the program terminates.

ERROR 9    ' Trigger subscript error

RESUME / RESUME NEXT

Syntax: RESUME | RESUME NEXT

RESUME retries the statement that caused the error. RESUME NEXT continues after it. Must be called from within an error handler.

failed:
    PRINT "Error handled"
    ERRCLEAR
    RESUME NEXT


PBXA64 - File & Directory Commands (V26)

File & Directory Commands (V26)

FILECOPY

Syntax: FILECOPY source$, dest$

Copies a file from source to destination. Returns nonzero on success.

IF FILECOPY("src.txt", "dst.txt") THEN PRINT "OK"

RMDIR

Syntax: RMDIR path$

Removes an empty directory.

RMDIR "C:\\Temp\\MyFolder"

GETATTR

Syntax: GETATTR(path$)

Returns file/directory attributes as a bitmask.

LOCAL attr AS LONG
attr = GETATTR("myfile.txt")
IF attr AND 1 THEN PRINT "Read-only"

SETATTR

Syntax: SETATTR path$, attrs

Sets file/directory attributes.

SETATTR "myfile.txt", &H01    ' Set read-only

RESET

Syntax: RESET

Closes all open files.

OPEN "data.txt" FOR OUTPUT AS #1
OPEN "log.txt" FOR OUTPUT AS #2
RESET     ' Closes both #1 and #2

FLUSH

Syntax: FLUSH #n

Flushes the file buffer for the given file handle, ensuring all buffered data is written to disk.

PRINT #1, "Important data"
FLUSH #1    ' Force write to disk

SETEOF

Syntax: SETEOF #n

Sets the end-of-file marker at the current file position. Truncates the file beyond this point.

SEEK #1, 100
SETEOF #1    ' Truncate file at position 100

REMAIN$

Syntax: REMAIN$(#n)

Returns the number of remaining characters in an open file after the last GET operation.

LOCAL remaining AS STRING
remaining = REMAIN$(1)
PRINT remaining; " bytes left"


PBXA64 - Math, Selection & Format (V26)

Math, Selection & Format Commands (V26/V27)

MIN / MAX

Syntax: MIN(a, b) | MAX(a, b)

Return the smaller or larger of two numeric values.

LOCAL x AS LONG
x = MIN(10, 20)    ' x = 10
x = MAX(10, 20)    ' x = 20

IIF

Syntax: IIF(condition, trueExpr, falseExpr)

Inline conditional: returns trueExpr if condition is true, otherwise falseExpr.

x = IIF(a > b, a, b)     ' Like MAX(a,b)
PRINT IIF(x = 0, "zero", "non-zero")

CHOOSE

Syntax: CHOOSE(index, value1, value2, ...)

Returns the value at the given 1-based index.

s = CHOOSE(2, "red", "green", "blue")   ' s = "green"
n = CHOOSE(month, 31, 28, 31, 30, 31)   ' days in month

SWITCH

Syntax: SWITCH(cond1, value1, cond2, value2, ...)

Evaluates conditions left-to-right, returns the value of the first true condition.

grade = SWITCH(score >= 90, "A", score >= 80, "B", score >= 70, "C", TRUE, "F")

ROUND

Syntax: ROUND(x)

Rounds a floating-point number to the nearest integer.

x = ROUND(3.7)      ' x = 4
x = ROUND(3.2)      ' x = 3

LIKE Operator (V27)

Syntax: stringExpr LIKE pattern$

Wildcard pattern matching using Windows PathMatchSpecA. Returns TRUE if the string matches the pattern.

IF filename LIKE "*.pb" THEN PRINT "BASIC file"
IF name LIKE "test*" THEN PRINT "starts with test"

PRINT USING (V27)

Syntax: PRINT USING mask$; expr

Formatted output using the FORMAT$ path. The mask is applied to the expression before output.

PRINT USING "000"; 42        ' Prints "042"
PRINT USING "###.##"; 3.5    ' Future: prints "  3.50"

FORMAT$ with Masks (V26/V27)

Syntax: FORMAT$(n [, "mask"])

Returns a formatted string. With a mask, formats the number according to the mask pattern.

s = FORMAT$(42, "000")      ' s = "042"
s = FORMAT$(1234)            ' s = "1234" (basic numeric)
V26 adds first-pass picture mask support. Full PowerBASIC mask parity remains future work.

BUILD$

Syntax: BUILD$(expr1, expr2, ...)

Builds a string by concatenating all arguments.

s = BUILD$("x=", FORMAT$(x, "000"), ", y=", FORMAT$(y, "000"))

CLIP$

Syntax: CLIP$(s$)

Removes leading and trailing whitespace. Similar to LTRIM$(RTRIM$(s$)).

s = CLIP$("  hello  ")      ' s = "hello"

RETAIN$

Syntax: RETAIN$(s$, chars$)

Keeps only characters from s$ that appear in chars$.

s = RETAIN$("a1b2c3", "0123456789")   ' s = "123"

SHRINK$

Syntax: SHRINK$(s$)

Collapses multiple consecutive spaces into a single space.

s = SHRINK$("a    b  c")    ' s = "a b c"


PBXA64 - Inline Assembler (V27/V28)

Inline Assembler (V27/V28)

V27 hardened the inline assembler infrastructure. V28 added structured #ASM handling with PB slot binding, stack safety, and privilege gates.

#ASM Block

Syntax: #ASM [options] ... #ENDASM

Embeds x64 assembly directly in PB source. Nonvolatile registers (RBX, RSI, RDI, R12-R15) are automatically saved and restored. Stack alignment is maintained.

#ASM
    mov rax, [rbp - 16]
    add rax, 5
#ENDASM

PB Slot Binding

Reference PB variables by name inside ASM blocks:

Syntax | Meaning
%name | PB storage operand (value at address)
%udt.field | UDT field storage operand
{name} | Address of PB storage (usable with LEA)
{udt.field} | Address of UDT field (usable with LEA)
LOCAL a AS LONG, p AS Pair
a = 5: p.x = 7
#ASM
    mov rax, %a          ' Load value of a
    add rax, %p.x        ' Add value of p.x
    popcnt rbx, rax
    mov qword ptr %a, rbx  ' Store back to a
    pxor xmm0, xmm0
    movaps xmm1, xmm0
    addps xmm1, xmm0
#ENDASM

Single-Line ASM

Syntax: ASM instruction

Convenience syntax for a single assembly instruction. PB slot binding works the same way.

ASM mov rax, %a

#ASM Options

CLOBBER

Syntax: #ASM CLOBBER reg1 [, reg2, ...]

Informs the compiler that the listed registers are modified by the ASM block. The compiler skips save/restore for these registers (you are responsible).

#ASM CLOBBER RBX
    ' RBX not saved/restored
    mov rbx, rax
#ENDASM

UNSAFE

Syntax: #ASM UNSAFE

Expert mode. Disables RSP balance validation, nonvolatile preservation, and RET rejection. You take full responsibility for stack and register correctness.

#ASM UNSAFE
    ' Expert code here -- no safety checks
#ENDASM

ALLOW_PRIVILEGED

Syntax: #ASM ALLOW_PRIVILEGED

Gates privileged/system mnemonics (CPUID, RDTS C, SYSCALL). Without this flag, privileged instructions are rejected with a diagnostic.

#ASM ALLOW_PRIVILEGED
    cpuid
    rdtsc
#ENDASM

ASMDATA Blocks

Syntax: ASMDATA name ... END ASMDATA

Emits data into the assembly output. Supports db, dw, dd, dq, ascii, asciz directives.

ASMDATA V28Table
    db 1, 2, 3, 4
    dq 1234
END ASMDATA

LINE Graphics (V27)

Syntax: LINE x1, y1, x2, y2 [, color] [, B|BF]

Draws a line on the desktop using GDI calls (MoveToEx, LineTo, Rectangle).

LINE 0, 0, 100, 100       ' Diagonal line
LINE 10, 10, 50, 50, , B  ' Box (future)
V27 uses a desktop DC fallback. Full GRAPHIC WINDOW / DDT-targeted drawing is future work.

Supported x64 Mnemonics

~108 base mnemonics including: mov, lea, add, sub, and, or, xor, cmp, test, imul, div, idiv, push, pop, call, jmp, jcc (30 variants), setcc (30), cmovcc (30), shl/shr/sar/rol/ror/rcl/rcr, inc/dec/not/neg, xchg, movzx/movsx, bsf/bsr, popcnt/tzcnt/lzcnt, bt/bts/btr/btc, nop, ret, int3, leave, cqo, pushfq/popfq, clc/stc/cld/std, cpuid, rdtsc, syscall, ud2.

SSE: cvtsi2sd/cvtsd2si, movsd/addsd/mulsd/subsd/divsd/ucomisd, movaps/movups/movapd/movupd/movdqa/movdqu, addps/subps/mulps/divps/maxps/minps/sqrtps (and PD variants), pxor/pand/por, paddb/w/d/q, psubb/w/d/q.


WHILE / WEND

Syntax

Syntax: WHILE condition : ... : WEND
Source: PB_STMT_WHILE handler (pb_codegen.c:6597)

Description

Repeatedly executes a block of code as long as a condition remains true. The condition is checked at the top of each iteration; if false on first entry, the body never executes. EXIT WHILE jumps out of the loop immediately. ITERATE WHILE skips the remainder of the current iteration and returns to the condition check at the top.

Parameters

Parameter | Description
condition | A boolean or comparison expression evaluated before each iteration. Non-zero = TRUE (continue), zero = FALSE (exit).
WEND | Marks the end of the WHILE loop block. Execution jumps back to the WHILE condition check.

Example

FUNCTION PBMAIN() AS LONG
    LOCAL count AS LONG

    ' Countdown from 5 to 1
    count = 5
    WHILE count > 0
        PRINT "Count: "; count
        count = count - 1
    WEND
    PRINT "Liftoff!"

    ' Read until EOF (pseudocode)
    ' WHILE NOT EOF(1)
    '     LINE INPUT #1, line$
    '     PRINT line$
    ' WEND

    FUNCTION = 0
END FUNCTION

Generated Assembly

__pb_while_N:                         ; LOOP TOP
; Evaluate condition -> RAX
; pb_cg_emit_condition_false_jump -> jump if false
test   rax, rax
je     __pb_while_end_N               ; exit if condition false

; ... loop body ...
; EXIT WHILE jumps here: __pb_while_end_N
; ITERATE WHILE jumps here: __pb_while_N

jmp    __pb_while_N                   ; loop back
__pb_while_end_N:                     ; EXIT point

EXIT WHILE

jmp    __pb_while_end_N               ; jump out of loop

ITERATE WHILE

jmp    __pb_while_N                   ; jump to condition check


WSTRING64 Runtime

WSTRING64 & WinAPI V14 compiler V16 WinAPI

V13: WSTRING64 runtime library created (20+ helper functions). V14: Compiler now lowers WSTRING expressions through the full pipeline: lexer -> parser -> semantic -> codegen -> PE.

WSTRING Variable Declarations

Syntax: LOCAL w AS WSTRING | STATIC w AS WSTRING | GLOBAL w AS WSTRING

V14 allocates WSTRING descriptors in the stack frame or static storage. The descriptor contains: data pointer, length (UTF-16 code units), capacity, and flags.

STRING / WSTRING Cross-Assignment

Syntax: w$ = s$ (STRING to WSTRING) | s$ = w$ (WSTRING to STRING)
LOCAL s AS STRING, w AS WSTRING
s = "Hello"
w = s          ' MultiByteToWideChar (CP_ACP -> UTF-16)
s = w          ' WideCharToMultiByte (UTF-16 -> CP_ACP)

Generated Assembly (STRING -> WSTRING)

; Get source STRING pointer -> RCX
; WideCharToMultiByte or MultiByteToWideChar
mov    rcx, 65001                       ; CP_UTF8 / CP_ACP
xor    rdx, rdx                         ; dwFlags = 0
mov    r8, [rbp - src_ptr]              ; source
mov    r9, -1                           ; null-terminated
call   [__imp_MultiByteToWideChar]      ; returns required buffer size

PRINT WSTRING

Syntax: PRINT w$

Lowered through a temporary narrow conversion buffer (WideCharToMultiByte), then output via the existing WriteFile console path.

LEN(WSTRING) / STRPTR(WSTRING)

Syntax: LEN(w$) | STRPTR(w$)

LEN returns UTF-16 code-unit count from the descriptor. STRPTR returns the raw WCHAR* data pointer.

LOCAL w AS WSTRING
w = "Hello"
PRINT LEN(w)       ' 5 (code units)
PRINT STRPTR(w)    ' address of wchar_t data

WSTRING Comparison

Syntax: w1$ = w2$ | w1$ <> w2$ | w1$ < w2$

Mixed STRING/WSTRING expressions auto-promote to WSTRING. Comparison uses memcmp on UTF-16 byte spans.

WSTRING Concatenation

Syntax: w$ = w1$ + w2$ | w$ = s$ + w$ (mixed)

Uses pb_wstr64_concat runtime. Mixed STRING/WSTRING operands are promoted to WSTRING before concatenation.

FUNCTION AS WSTRING

Syntax: FUNCTION name(BYVAL w AS WSTRING) AS WSTRING

Full scalar return path. Parameters can be BYVAL or default BYREF WSTRING.

FUNCTION Greet(BYVAL name AS WSTRING) AS WSTRING
    LOCAL w AS WSTRING
    w = "Hello, " + name
    FUNCTION = w
END FUNCTION

Still Pending

  • Dynamic STRING/WSTRING fields inside UDTs (recursive init/copy/free)
  • UDT arrays and REDIM PRESERVE with dynamic string fields
  • Full Unicode console/file I/O (WriteConsoleW, UTF-16 file modes)
  • WSTRING arrays

WSTRING64 Functions

WSPACE$(count)

Syntax: w$ = WSPACE$(n)
Runtime: pb_wstr64_space (pb_wstring64.c)

Returns a WSTRING containing n space characters.

LOCAL w AS WSTRING
w = WSPACE$(10)    ' 10 spaces

WSTRING$(count, fill)

Syntax: w$ = WSTRING$(n, fill$)
Runtime: pb_wstr64_string (pb_wstring64.c)

Returns a WSTRING by repeating the fill character or string n times.

LOCAL w AS WSTRING
w = WSTRING$(5, "*")     ' "*****"
w = WSTRING$(3, "AB")    ' "ABABAB"

WREMOVE$(source, substring)

Syntax: result$ = WREMOVE$(w$, remove$)
Runtime: pb_wstr64_remove (pb_wstring64.c)

Removes all occurrences of remove$ from w$.

LOCAL w AS WSTRING, r AS WSTRING
w = "Hello World"
r = WREMOVE$(w, "o")    ' "Hell Wrld"

WEXTRACT$(source, delimiter, index)

Syntax: result$ = WEXTRACT$(w$, delim$, n)
Runtime: pb_wstr64_extract (pb_wstring64.c)

Extracts the nth field from w$ split by delim$. Index is 1-based.

LOCAL w AS WSTRING, r AS WSTRING
w = "one,two,three"
r = WEXTRACT$(w, ",", 2)    ' "two"

ASCII/WSTRING Bridge Functions

WSTR_FROM_ASCII(ascii$)

Runtime: pb_wstr64_from_ascii (pb_wstring64.c)

Converts an ASCII/ANSI STRING to a WSTRING (UTF-16). Each byte maps to a wide character.

WSTR_TO_ASCII_LOSSY(w$)

Runtime: pb_wstr64_to_ascii_lossy (pb_wstring64.c)

Converts a WSTRING back to ASCII, truncating characters above U+00FF to '?'.

WinAPI WSTRING Interop V16

BYVAL WSTRING in DECLARE

Syntax: DECLARE FUNCTION MsgBoxW LIB "user32.dll" ALIAS "MessageBoxW" (BYVAL hWnd AS QUAD, BYVAL lpText AS WSTRING, BYVAL lpCaption AS WSTRING, BYVAL uType AS LONG) AS LONG

V16 fixes BYVAL WSTRING to pass LPWSTR data pointers (not descriptors) to external DLLs. STRING literals are auto-widened via MultiByteToWideChar into per-call temporaries, freed after the call.

DECLARE FUNCTION MessageBoxW LIB "user32.dll" ALIAS "MessageBoxW" ( _
    BYVAL hWnd AS QUAD, BYVAL lpText AS WSTRING, _
    BYVAL lpCaption AS WSTRING, BYVAL uType AS LONG) AS LONG

CALL MessageBoxW(0, "Hello", "Title", 0)   ' STRING literal auto-widened

Console PRINT WSTRING

Syntax: PRINT w$ uses WriteConsoleW
LOCAL w AS WSTRING
w = "Hello Unicode"
PRINT w       ' WriteConsoleW with UTF-16 code-unit length

File I/O with WSTRING

Syntax: OPEN w$ FOR ... AS #n uses CreateFileW

WSTRING filenames call CreateFileW (narrow filenames keep CreateFileA). INPUT/LINE INPUT into WSTRING reads the byte stream and widens into the descriptor.

LOCAL w AS WSTRING
w = "unicode_file.txt"
OPEN w FOR OUTPUT AS #1
PRINT #1, "data"
CLOSE #1

VARPTR(WSTRING) Fix

Syntax: VARPTR(w$) | VARPTR(type.wstringField)

V16 fixes VARPTR to return the descriptor-slot address (matching PowerBASIC behavior). Previously returned the string-data pointer.

Limitations

  • WSTRING expressions are not yet a full first-class lowering path in codegen
  • Runtime functions exist but codegen integration is future work
  • Full Unicode/codepage conversion not yet implemented