REXX Language implementation
ADDRESS sends commands or function requests to a named external environment.
It is implemented through the current compiler-exit and VM environment protocol.
Basic command form:
address system "echo hello"
Command output and error streams can be captured:
address command "echo #42" output out error err
say out
The built-in COMMAND/CMD/SYSTEM/SHELL and PATH environments execute a
program with parsed arguments; they are not an interactive shell parser. Simple
quoted arguments are supported, but complex nested shell quoting should be
passed to an explicit shell through stdin:
command_lines = .string[]
command_lines[1] = "printf '%s\n' alpha beta"
address command "sh" input command_lines output out error err
ADDRESS host-variable anchors such as :name and ${name} are compiler
auto-expose syntax. Their command meaning belongs to the selected environment
handler; the VM carries binding values and write-back updates.
The current native registration API is environment based:
rxvml_address_register_callback_environment(ctx, name, id,
command_cb, function_cb, userdata);
The old command-only callback registration form is retired.
See Procedures and Arguments Section
CALL routine [ parameter ] [, [ parameter ] … ]
DO [ repetitor ] [ conditional ] ; [ clauses ]
expr ::= DO ; [ clauses ] END
END [ symbol ] ;
repetitor : = symbol = expri [ TO exprt ] [ BY exprb ] [ FOR exprf ]
conditional : = WHILE exprw UNTIL expru
The DO/END statement is the command employed to iterate and group multiple statements into a singular block. This instruction consists of multiple clauses.
When DO ... END appears where an expression is expected, it is parsed as a block expression. In that form the body must yield a value using LEAVE WITH expr.
Simple DO ... END groups may also carry block-scoped signal handlers using
ON SIGNAL clauses. The handler clauses are valid only on a simple DO group,
not on counted, conditional, forever, or expression-form DO. To protect code
inside a loop, nest a simple signal-handling DO ... END group inside the loop
body.
EXIT [ expr ] ;
Causes the Rexx program to cease execution and, optionally, returns the expression expr to the calling program.
IF expr [;] THEN [;] statement
[ ELSE [;] statement ]
This provides the standard conditional statement structure.
ITERATE [ symbol ] ;
The ITERATE instruction will execute the innermost, active loop in which the ITERATE instruction is situated repeatedly. If a symbol is specified, it will execute the innermost, active loop having the symbol as the control variable repeatedly.
LEAVE [ symbol ] ;
LEAVE WITH expr ;
This statement terminates the innermost, active loop. If symbol is specified, it terminates the innermost, active loop having symbol as control variable.
LEAVE WITH expr is distinct from loop-control LEAVE. It exits the innermost enclosing expression-form DO ... END block and returns the value of expr to the parent expression.
NOP ;
The NOP instruction is the “null operation” directive; it executes without performing any operation.
OPTIONS expr ;
The OPTIONS instruction is used to set various interpreter-specific options. See Language Level and Options Section
PARSE [ option ] [ CASELESS ] type [ template ] ;
Current implementation status:
PARSE VALUE ..., PARSE VAR ..., and PARSE ARG ... are implemented through the certified PARSE exit.PARSE ARG uses the current procedure’s arg() compatibility view.main, that means command-line arguments.... tail if present, or an empty source string if there is no ... tail.PROCEDURE starts a named local routine and optionally declares its return
type and the module-global variables that routine can see.
Common forms:
name: procedure
name: procedure = .int
name: procedure = .void expose state count
Procedure-level expose is local to that procedure declaration. The listed
names are bound to module-global storage shared with other procedures that
also expose the same names. A procedure that does not list the name does not
see that exposed storage unless the name is also exposed by the file-level
namespace ... expose ... declaration.
proc1: procedure = .void expose var
var = "Hello World"
return
proc2: procedure = .void expose var
say "var is" var
return
This is distinct from ARG expose, which exposes a call argument by reference.
SAY [ expr ] ;
Evaluates the expression expr and prints the resulting string onto the standard output stream.1
SELECT [expression] [;] WHEN expression [, expression …] [;] THEN [;] instruction [;] [WHEN expression [, expression …] [;] THEN [;] instruction [;]] … [OTHERWISE [;] [instruction] [;] …] END [;]
The SELECT statement allows you to conditionally evaluate multiple expressions and execute corresponding instructions based on the first expression that evaluates to true (1).
There are two styles of the SELECT statement in cRexx:
Classic SELECT: Does not include an initial expression after the SELECT keyword. Each WHEN expression is evaluated as a standalone boolean condition.
C-Style SELECT (SWITCH): Includes an initial expression after the SELECT keyword. The expression is evaluated once, and its result is implicitly compared for equality (=) against each WHEN expression.
If a WHEN condition is met, its associated THEN instruction is executed, and control exits the SELECT block. If no WHEN condition is met, the OTHERWISE block (if present) is executed. If no WHEN condition is met and an OTHERWISE block is absent, the SELECT statement acts as a NOP (null operation) and does nothing.
Signals are Level B error/condition objects implementing .signal.
Rexx-created signals can be raised with a signal object or with the compact
named forms:
signal .signal("error", "message")
signal error
signal error "message"
Procedure-scoped handlers are installed with SIGNAL ON and removed with
SIGNAL OFF:
signal on conversion_error call handle_conversion
signal on error, syntax call handle_problem
signal off conversion_error
handle_conversion: procedure = .signalaction
arg problem = .signal
say problem.source()
return .signalaction.skip()
The handler procedure receives one .signal argument and returns a
.signalaction: .signalaction.skip(), .signalaction.retry(), or
.signalaction.fail().
Block-scoped handlers use ON SIGNAL clauses on a simple DO ... END group:
do
risky_work()
on signal conversion_error as problem
say problem.source()
on signal error, syntax
call cleanup()
on signal
call log_unhandled_signal()
end
The statements before the first ON SIGNAL clause are the protected body.
Normal completion skips the handlers. A handler that completes normally leaves
the DO block. ON SIGNAL with no names catches all maskable signals.
AS name binds the current .signal object; if AS is omitted, no signal
object is available to that handler.
Only a simple DO ... END group can carry ON SIGNAL clauses. Counted,
conditional, forever, and expression-form DO loops do not carry handlers
directly. To protect code inside a loop, put a simple signal-handling
DO ... END group inside the loop body.
TRACE enables or disables VM breakpoint-backed tracing for the current call
frame and procedures called from it.
Supported forms are:
trace off
trace normal
trace results
trace rexx
trace asm
trace as
trace llm
trace ll
trace env
trace value expr
trace suppress namespace name
trace unsuppress namespace name
trace add suppressed namespace name
trace remove suppressed namespace name
trace reset namespaces
trace results to stderr
trace llm to file "trace.jsonl"
The standard Rexx option letters A, C, E, F, I, L, N, O, and
R are accepted, including a leading ? prefix and signed integer settings.
Options use a minimum-abbreviation rule: the spelling must be a left-prefix of
the full option word. For example, TRACE R, TRACE RE, TRACE RES,
TRACE RESULT, and TRACE RESULTS all select Results, while TRACE RAS is
invalid. The cRexx extensions use AS as the minimum abbreviation for ASM
and LL as the minimum abbreviation for LLM; ENV is an exact cRexx
extension spelling. TRACE REXX remains supported as an exact legacy cRexx
source-trace spelling; it is not abbreviated because R and RE... belong to
Results.
This is not yet full semantic compatibility, but the noninteractive output
shape follows the standard prefix vocabulary for implemented events:
> > escaped-source-file
5 *-* escaped-source
>=> "escaped-assignment-result"
+++ RC=-3 ENVIRONMENT escaped-command
TRACE N is the quiet/default mode: it does not trace ordinary statements and
emits +++ only for failing ADDRESS commands. TRACE C, TRACE E, and
TRACE F are ADDRESS-command driven. TRACE A traces source clauses.
Classic TRACE R traces source clauses, variable substitutions, and assignment
or expression results. cRexx emits source clauses from .srcstep metadata and
semantic value records from .traceevent metadata, including initial >=>
assignment, >V> variable, >L> literal, and >O>/>P> operation coverage
where the compiler can point at an available register or constant. TRACE I
uses the same metadata path and adds intermediate-event visibility as coverage
grows. TRACE L is accepted, but label-pass events are not emitted yet.
O/OFF disables breakpoint tracing. TRACE ASM traces VM/RXAS instruction
information and includes source text where metadata is available.
When traced execution moves between source files, cRexx emits a source-file transition line:
> > helper.crexx
The first visible source file is not printed, so single-file traces keep their
classic shape. Later file changes are printed before the next *-* source
line, including when execution returns to the original file. This is a cRexx
extension; classic Regina-style output does not provide an equivalent filename
record.
This TRACE implementation is still beta. Source reporting now uses self-contained source-step metadata, and text TRACE no longer guesses assignment results from source text. Result coverage is still deliberately partial: optimized-away or folded values may have no trace event, and some compound-variable details such as final resolved-name reporting remain a compiler/runtime coverage task.
TRACE LLM is a cRexx extension that emits one JSON-lines-style trace record
per event. It is intended for debugger automation and for validating emitted
.srcstep metadata; source text is escaped so control characters and
backslashes remain visible. TRACE VALUE expr evaluates expr at runtime and
normalizes it using the same trace option rules.
Trace output defaults to stdout. Add TO STDERR, TO STDOUT, TO FILE expr,
or TO expr to choose a sink. TO FILE opens the selected file in append mode
for each trace record.
TRACE normally hides events from system library and debugger namespaces so a
user trace follows the program being debugged instead of the machinery that
implements tracing. The default suppressed namespaces are rxfnsb, _rxsysb,
rxfnsg, _rxsysg, rxfnsl, _rxsysl, rxfnsc, _rxsysc, rxcp,
rxcpexits, rxcptest, rxdb, rxdbgui, runtime_signal, signalaction,
and library. The TRACE runtime internals themselves are always hidden.
Use namespace controls when you need to include or exclude a library while debugging:
trace results
trace unsuppress namespace rxfnsg
trace suppress namespace myframework
trace reset namespaces
TRACE SUPPRESS NAMESPACE name and TRACE ADD SUPPRESSED NAMESPACE name
suppress a namespace. TRACE UNSUPPRESS NAMESPACE name and
TRACE REMOVE SUPPRESSED NAMESPACE name make that namespace visible again.
TRACE RESET NAMESPACES restores the default suppression list and clears
per-session changes. Namespace names may be bare identifiers or string
literals; matching is by namespace or path component, not by arbitrary
substring, so suppressing rxfnsg does not suppress myrxfnsghelper.
TRACE ENV explicitly checks two environment variables at that point in the
program. CREXX_TRACE supplies the mode using the same option rules as
TRACE VALUE, and CREXX_TRACE_TO supplies the sink using the same rules as
TO. For example, CREXX_TRACE=results CREXX_TRACE_TO=stderr can switch
tracing on at a compiled TRACE ENV marker without editing the source. If
CREXX_TRACE is unset or empty, TRACE ENV turns tracing off. If CREXX_TRACE
has an invalid value, TRACE ENV turns tracing off and emits a +++ trace
message naming the invalid value.
TRACE is implemented as a certified compiler exit. It requires normal compiler exit loading; compiling with exits disabled rejects the statement rather than treating it as an implicit command.
The cRexx standard-library/BIF build deliberately compiles most
lib/rxfnsb/rexx/*.crexx files with compiler exits disabled (rxc -x) to avoid
bootstrap and circular-dependency problems while building the library that the
exits themselves use. Adding TRACE RESULTS, TRACE R, or another explicit
TRACE instruction directly to a BIF source file such as abs.crexx therefore
produces #CERTIFIED_EXIT_DISABLED. Debug BIF or library behavior from a
normal test program instead: call the BIF from a fixture that compiles with
exits enabled, use TRACE UNSUPPRESS NAMESPACE rxfnsb if you need to see
library frames, and keep linked/native images unstripped with
--link-keep-source when source-level TRACE metadata is needed.
Implementation status and compatibility requirements are tracked in
docs/ai-context/CREXX_TRACE_REQUIREMENTS.md.
procedure expose is the local procedure form for sharing module-global state:
main: procedure
call proc1
call proc2
say "but var in main is" var
return
proc1: procedure = .void expose var
var = "Hello World"
return
proc2: procedure = .void expose var
say "var is" var
return
Here proc1 and proc2 share the exposed global var. main does not list
var, so its bare var reference is not the same exposed variable. To let
main read or write the shared value, declare main: procedure expose var as
well.
The expose list follows the return type if a return type is present. The
items in the procedure-level list are bare variable names:
worker: procedure = .int expose state errors
For globally published module variables, prefer file-level
namespace name expose var; those namespace-exposed globals auto-bind into
procedures in the same source file.
Arguments can be passed to a procedure by reference or by value. When an argument is passed by reference, the procedure can modify the original variable that was passed to it. When an argument is passed by value, a copy of the variable is passed to the procedure, and any changes made to the copy do not affect the original variable.
The user-visible rules are:
ARG name = type is pass by value.ARG expose name = type is pass by reference.expose.By example:
ARG a1 = 0, a2 = .int, expose a3 = .aclass, ?a4 = .aclass, a5 = .string[]
.aclass, not a factory callOptional defaults evaluate exactly as written. Use ?x = .SomeClass for the
bare typed class value and ?x = .SomeClass() to call the default factory.
Examples:
bump: procedure = .int
arg value = .int
value = value + 1
return value
x = 10
say bump(x)
say x /* still 10 */
bumpref: procedure = .void
arg expose value = .int
value = value + 1
return
x = 10
call bumpref(x)
say x /* now 11 */
The last arguments declaration can be an ellipsis (‘…’), this is used to show that 0 or more arguments can be provided. For example:
ARG a1 = 0, a2 = .int, … = .string
Pseudo Array arg allows access to the ‘…’ arguments. Also see the Arrays section.
... tail, the count forms return 0The type of this Pseudo is the type of the ‘…’ argument
The compatibility arg() operator is designed to provide some compatibility with Classic Rexx; by example:
... tail, arg() returns 0 and the E/O probe forms operate on that empty tailIn the event that a module file contains instructions preceding a PROCEDURE instruction, an implicit procedure named main() is automatically generated within the namespace of the module file. The arguments for this procedure can be accessed through the pseudo array arg or arg() operator. This implicit main() case is the compatibility bridge that maps classic arg(n) access onto command-line arguments when no explicit signature is present. The return type of the implicitly defined main() procedure is automatically set to either int or void.
with an added newline. For cases where no newline is wanted, the conventional way of using call lineout can be used, or the sayx assembler instruction. ↩