REXX Language implementation
RXAS is the textual assembly form for cREXX VM bytecode. It is normally emitted
by rxc, but it is also intended to remain readable enough for tests, focused
runtime examples, and low-level diagnostic work.
An RXAS source file is line-oriented. Blank lines are allowed, comments are ignored, and each non-comment line is one directive, one procedure header, one instruction, or metadata attached to the current scope. A source file has three logical regions:
Header directives must appear before the first procedure. Once a procedure header has been seen, later lines are parsed as procedure declarations, procedure definitions, instructions, labels, or procedure metadata.
Whitespace separates tokens and is otherwise not significant. Newlines are significant because they terminate directives and instructions.
A line comment begins with * when * is the first non-whitespace character on
the line:
* This is a comment
* This is also a comment
Block comments use /* ... */ and may be nested:
/*
* Outer comment.
* /* Nested comment. */
*/
Module headers describe global registers, exposed symbols, module metadata, and named constants. The common forms are:
.globals=0
g0 .expose=sharedValue
.const bytes binary 0x01020304
.const name string "lookup-key"
.globals=<integer> declares the module global-register count. Global registers
are written as g0, g1, and so on. A global register may be exposed with
gN .expose=<name> so other modules can link to it. Local registers rN and
argument registers aN cannot be exposed with this header form.
.const creates a module-scoped read-only alias for a pooled constant. Release
1 supports binary and string constant aliases:
.const table binary 0x0001020304050607
.const marker string "index"
The alias itself is an identifier operand. Each instruction form decides whether that identifier is legal as a string constant, binary constant, label, procedure name, or other symbolic operand.
A procedure definition names the callable and declares how many local registers the procedure uses:
main() .locals=4
The local register declaration must cover the rN registers referenced by the
procedure body. A definition can also expose the procedure under a link-visible
name:
worker() .locals=8 .expose=package.worker
A procedure declaration names an external callable without defining a normal instruction body:
externalCall() .expose=package.externalCall
Declarations accept only the restricted metadata forms used by the compiler and linker. They do not contain normal RXAS instructions.
Instructions use a mnemonic followed by the number of operands declared by its opcode form. The first operand follows the mnemonic directly; later operands are separated by commas. The grammar does not impose a small fixed operand limit:
ret
load r1,"hello"
bgetu8 r2,table,r0
add r3,r1,r2
The assembler checks the mnemonic, operand count, and operand types against the instruction table. Unknown mnemonics, mismatched counts, missing operands, and illegal operand forms are syntax or assembly errors.
Labels are written as name:. A label may stand on its own line or immediately
precede another instruction. Branch and jump instructions refer to labels by
identifier:
loop:
add r1,r1,r2
br loop
Jump-table case labels use a label followed by .jcase on the same line. The
.jcase directive decorates the label and does not emit an instruction:
keyword_if: .jcase keywords "if"
RXAS recognizes these operand classes:
r0, r1, r2, …g0, g1, g2, …a0, a1, a2, …name().Registers are untyped storage locations at the RXAS syntax level. The selected instruction determines how the VM interprets each register operand at runtime.
Integer literals are signed or unsigned decimal digits:
load r1,42
load r2,-1
Floating-point literals use a decimal point or exponent:
load r1,1.5
load r2,1e6
Decimal literals use the same numeric spelling with a lowercase d suffix:
load r1,10d
load r2,1.25d
String literals are double quoted. A literal newline is not allowed inside a
string. An escaped double quote is written as \":
load r1,"hello"
load r2,"quoted \"text\""
Character literals are single quoted:
load r1,'A'
Binary literals use byte-paired hexadecimal after 0x or 0X. The empty binary
literal 0x is valid:
load r1,0x
load r2,0x000102ff
Binary literals and .const ... binary ... aliases are stored as binary
constant-pool values, not as integer or string values.
.jtable declares a procedure-local static dispatch table. .jcase decorates
target labels with literal keys, and jumps, jumpr, jumpn, jumpb,
jumpbs, or jumpi
branch through the assembled table. A miss falls through to the next
instruction, so write an explicit default branch after the jump instruction.
String table modes are deliberately distinct. jumps compares exact UTF-8
bytes, jumpr applies blank-padded nonnumeric Rexx equality by ignoring
trailing ASCII spaces, and jumpn canonicalizes numeric strings. One table may
not mix these modes.
main() .locals=4
.jtable keywords linear
br after_cases
keyword_if: .jcase keywords "if"
load r1,1
br done
keyword_else: .jcase keywords "else"
load r1,2
br done
after_cases:
load r0,"else"
jumps r0,keywords
br not_found
done:
ret
not_found:
load r1,0
ret
Release 1 supports auto, linear, openhash, and acph. Explicit algorithm
names are useful for measurement and repeatable tuning. auto selects linear
for a one-case table. Larger tables use openhash for average key lengths up to
two bytes; tables of at least 256 cases also use it for average lengths up to
four bytes. The remaining longer-key tables use acph. This policy is based on
Release VM profiling and may be retuned without changing the RXAS surface.
.meta, .srcstep, and .traceevent records carry compiler, linker, debug,
interface, and trace information. Hand-written RXAS usually does not need them,
but generated RXAS relies on them for source mapping, exposed callable metadata,
classes, interfaces, attributes, inline bodies, and TRACE output.
The source-step and trace-event forms are:
.srcstep <step-id> <clause-id> <flags> "<file>" <line> <start-col> <end-col> "<source line>"
.traceevent "<kind>" <mode-mask> "<value-source>" "<value-type>" "<register-type>" <value-ref> <source-step-id> <clause-id> <flags> "<symbol>" "<resolved-name>"
.meta has several scoped forms. The compiler should be treated as the owner of
most .meta payload formats unless an instruction or linking feature documents
the record explicitly.
One documented generated form declares a native provider dependency without duplicating the adjacent callable signature:
.meta "namespace.callable"=".provider" "provider-id"
Use .provider.optional for a best-effort dependency;
.provider.required is an explicit alias of the default required form.
.globals=0
.const table binary 0x01020304
main() .locals=4
load r0,0
load r1,"hello, rxas"
say r1
bgetu8 r2,table,r0
itos r2
say r2
ret
This module declares no global registers, defines one binary constant alias, loads and prints a string, reads the first byte of the binary constant, converts that integer to a string, prints it, and returns.