REXX Language implementation
Use this guide when you need to write or edit Level B/Level G .crexx in this
repository.
Generic training data about “REXX” is often too vague, too classic-Rexx
oriented, or simply wrong for cREXX Level B as it exists in this tree.
This guide is intentionally grounded in code that already compiles here. When in doubt, copy the nearest repo pattern instead of inventing syntax.
For Level B authoring, these sources are more reliable than model memory:
docs/ai-context/CREXX_ARCHITECTURE.mddocs/ai-context/CREXX_LIBS.mddocs/books/crexx_language_reference/classes_and_interfaces.mddocs/books/crexx_language_reference/data_types.mddocs/books/crexx_language_reference/statements.md.crexx files in lib/, bin/, compiler/exits/,
debugger/, and tests/Use explicit options levelb, a namespace, and an exposed symbol:
options levelb
namespace rxfnsb expose abs
abs: procedure = .string
arg number = .string
if left(number,1) = '-' then number = substr(number,2)
return number
Reference:
lib/rxfnsb/rexx/abs.crexxSmall scripts can use top-level arg directly instead of a main: routine:
options levelb
import rxfnsb
arg searches = .string[]
if searches.0 < 1 then searches.1 = ".crexx"
ordered_searches = .string[]
address cmd "sort" input searches output ordered_searches
References:
tests/demo/countlines.crexxcompiler/exits/address/test_address.crexxLonger tools often use main: plus procedure-level expose for module state:
options levelb
namespace rxdb expose stephandler
import rxfnsb
import globals
main: procedure = .int expose next_instruction last_instruction mode
arg cmd_line = .string[]
The expose list belongs to that one procedure. Other local procedures see
the same module-global storage only if they also list the variable:
proc1: procedure = .void expose var
var = "Hello World"
return
proc2: procedure = .void expose var
say "var is" var
return
Reference:
debugger/rxdb.crexxWhen you really need caller-owned state, use arg expose ...:
pushidentifier: procedure = .int
arg expose tokens = .token[], text = .string, value_type = ".unknown"
index = tokens[0] + 1
tokens[index] = .token(...)
return index
Reference:
compiler/exits/ExitTestSupport.crexxIn this repo, typed procedures and typed arg declarations are the normal
Level B style. Do not default to untyped classic-Rexx-looking code when you
are editing standard libraries, tools, exits, or tests.
Common examples in-tree:
procedure = .intprocedure = .stringprocedure = .token[]arg name = .stringarg cmd_line = .string[]arg expose tokens = .token[]References:
lib/rxfnsb/rexx/abs.crexxdebugger/rxdb.crexxcompiler/exits/ExitTestSupport.crexxtests/levelbfunc.crexxDo not treat namespace use as documentation sugar. It is part of how source is validated, imported, and linked.
Important points:
namespace and import clauses are real inputs to compilationnamespace..symbol is the canonical qualified-reference formnamespace::symbol is accepted as a compatibility alias, but do not prefer it.. must be an imported namespaceReferences:
docs/ai-context/CREXX_ARCHITECTURE.mddocs/books/crexx_programming_guide/intralanguage.mddocs/books/crexx_language_reference/classes_and_interfaces.mdThe compiler does a lightweight pre-scan of the leading options,
namespace, and import clauses before full parsing. Preserve that structure
when editing existing files.
Practical guidance:
options levelb near the topnamespace and import in the leading header blockReference:
docs/ai-context/CREXX_ARCHITECTURE.mddocs/books/crexx_programming_guide/rxc.mdThere are two ways a Level B procedure can intentionally bind a module-global variable:
namespace name expose var exposes the symbol from the module and
automatically binds it into local procedure scopes in that source filename: procedure [= .type] expose var ... binds the listed
variables only for that procedure; every local procedure that needs the same
storage must list the variable itselfUse procedure-level expose for private module state that several local
procedures share, or when you are following an existing stateful tool/library
pattern. Do not add procedure expose ... just out of habit when a
namespace-exposed module global is what you actually want.
Use procedure expose or arg expose when:
rxdbReferences:
docs/ai-context/CREXX_ARCHITECTURE.mddocs/books/crexx_programming_guide/global_variables.mddebugger/rxdb.crexxDo not reason about them as loose classic stem variables only.
Patterns used in-tree:
items = .string[]items[0] for count/cardinality reads onlyitems[1] for first elementitems.1 is also used in older codearg expose items = .type[]; otherwise they work on a local value copyDo not initialise or resize an array by assigning to index 0. The cardinality
slot is read-only source syntax, and writes such as items[0] = "3" are
rejected with OUT_OF_RANGE. Use ordinary element assignment or the standard
array helpers instead:
items = .string[]
call arrayappend items, "alpha"
call arrayappend items, "beta"
say items[0]
When a test needs delete/insert semantics, prefer library helpers such as
arraydelete, arrayinsert, and arrayappend over assembler unless the test
is specifically about RXAS.
Use the objectarray* helper family for mutating .object[] arrays:
objectarrayinsert, objectarraydelete, objectarrayappend,
objectarrayprepend, objectarraydrop, and objectarraymove. Store concrete
class instances with an explicit as .object upcast.
References:
tests/demo/countlines.crexxcompiler/exits/ExitTestSupport.crexxdocs/books/crexx_language_reference/data_types.mdUse *: factory or name: factory; do not write = .type on a factory. The
factory result is inferred from the owning contract: an interface factory
returns that interface and a class factory returns that concrete class. In a
class factory, bare return returns the constructed object, so there is no
source-level this value to mention.
When creating an object value, include the factory call brackets. .SomeClass()
returns an initialized instance. Bare .SomeClass is a typed default object
value and is intentionally uninitialized; it is useful for type defaults and
can be probed with initialized(value), but calling methods on it raises
OBJECT_NOT_INITIALIZED.
vehicle: interface
*: factory
arg name = .string
describe: method = .string
car: class implements .vehicle
_name = .string
*: factory
arg name = .string
_name = name
return
describe: method = .string
return _name
Reference:
docs/books/crexx_language_reference/classes_and_interfaces.mdaddress command is the standard shell-out patternWhen Level B code shells out, copy the repo pattern instead of inventing a new API shape:
out = .string[]
err = .string[]
address command "echo #42" output out error err
if rc <> 0 then say "command failed"
address command is argv-parsed by the built-in provider; it is not a full
shell-quoting contract. For multi-line shell scripts or nested quoting, send
the script to an explicit shell through an input array:
script = .string[]
script[1] = "printf '%s\n' alpha beta"
address command "sh" input script output out error err
References:
compiler/exits/address/test_address.crexxbin/crexx.crexxtests/demo/countlines.crexxdo groupsBlock-scoped signal handling is written with on signal clauses on a simple
do ... end group:
do
risky_work()
on signal conversion_error as problem
say problem.source()
on signal
call cleanup()
end
Do not attach on signal directly to counted, conditional, forever, or
expression-form do loops. To protect part of a loop, nest a simple
signal-handling do ... end group inside the loop body.
If as name is omitted, the handler has no local signal object. That is fine
for fixed cleanup/logging handlers.
References:
docs/books/crexx_language_reference/statements.mddocs/ai-context/LEVELB_SIGNALS_TRACE_WORKING.mdcompiler/exits/signal/test_signal_block.crexxThe crexx driver can compile simple headerless top-level scripts with
synthetic defaults (--level levelb --import rxfnsb). That is useful for end
users, but repo code should usually stay explicit with options levelb and
imports unless there is a strong reason not to.
Reference:
docs/books/crexx_language_reference/tools.mddocs/books/crexx_programming_guide/crexx.mdLevel B programs receive command-line arguments through arg, and when the
compiler synthesizes the file-level main() wrapper, the VM argv payload is
available there as a .string[].
Program-side guidance:
arg fn = .string[] (or a typed equivalent)fn.0 / fn[0] for the countfn.1..fn.fn.0main, model it on in-tree patterns such as arg cmd_line = .string[]Launcher-side guidance:
crexx or rxvm
invocation syntaxReferences:
docs/books/crexx_language_reference/arguments.mddocs/ai-context/CREXX_ARCHITECTURE.mddebugger/rxdb.crexxtests/demo/countlines.crexxlib/rxfnsb/rexx/abs.crexxlib/rxfnsb/rexx/fileio.crexxlib/rxfnsb/rexx/regex.crexxtests/demo/countlines.crexxbin/crexx.crexxcompiler/exits/address/test_address.crexxdebugger/rxdb.crexxcompiler/exits/ExitTestSupport.crexxcompiler/exits/address/Address.crexxcompiler/exits/parse/Parse.crexxtests/levelbfunc.crexxcompiler/exits/ExitTestSupport.crexxdocs/books/crexx_programming_guide/global_variables.mddocs/books/crexx_programming_guide/global_procedures.mddocs/books/crexx_language_reference/arguments.mddebugger/rxdb.crexxtests/demo/countlines.crexxWhen writing Level B code:
.crexx examples before editing.namespace..symbol qualification.If a proposed snippet does not resemble existing repo code in lib/,
debugger/, compiler/exits/, bin/, or tests/, stop and verify it before
committing it.