REXX Language implementation
This facility allows for the compilation, linking, and execution of additional functionality (developed in C) alongside REXX code. It enables the decoupling of native modules (plugins), which can be developed and packaged either as separate entities or linked statically to the main \crexx{} core solution.
The architecture is designed to completely decouple the plugins from the rest of \crexx{}, and the plugin client library only consists of a single header file (rexxpa.h)
Plugin developers have the flexibility to structure their code in a way that allows for both dynamic or static linking. While the source code can remain the same, it needs to be built differently based on the desired linking approach, as it relies on macro expansion.
During the REXX compilation process, the REXX compiler inspects the plugin to determine the type and argument of any provided functions. This inspection helps ensure type safety. It is recommended for plugin developers to load or initialise dependencies only when an explicitly exposed initialisation REXX function is called or when the first function is invoked (lazy initialisation) to avoid overhead during build (rather than run). In addition, for the static builds, there is macro definition DECL_ONLY which allows definition / implementation code to be excluded from the static library designed to be linked to the compiler only.
For dynamically packaged plugins (with the extension *.rxplugin), the search process for these plugins mirrors how \crexx{} locates REXX modules (extensions such as *.rexx, *.rxas, or *.rxbin). In contrast, for static builds, the provided functions are loaded before the execution of the main() function in the core crexx solution, however these functions are placed at the end of the search order, meaning users can override static function definitions with local native or crexx modules.
The Plugin Architecture offers a comprehensive set of resources for developers, including a header file, macros, and a cmake configuration. These tools aim to create a convenient and efficient development environment for plugin creation.
User-provided plugins are recommended to be provided as dynamic .rxplugin files. Dynamic plugins offer several advantages: easy site-wide distribution by placing them in the same directory as \crexx{} binaries, project-specific customization by locating them in the project REXX files directory, and flexible placement in any desired location using “rxc” and “rxvm” options.
In contrast, static plugin packaging is more complex in terms of linking and is intended for core \crexx{} components that are part of every \crexx{} release. Static plugins are shipped within the \crexx{} binaries, ensuring their availability and consistency across distributions.
The choice between dynamic and static plugin packaging depends on the specific requirements and use cases: dynamic plugins provide flexibility and customization for users, while static plugins are designed to provide a robust solution for core \crexx{} components.
The following code demonstrates a decryption plugin.
The PROCEDURE macro starts the function, and argument access is via macros like ARG().
Each argument is a \crexx{} register. This means it holds multiple types or values and these are accesses by macros like GETSTRING() and SETSTRING().
Errors are handled by defining and returning a SIGNAL via the RETURNSIGNAL macro.
/*------------------------------------------------------------------*/
/* */
/* Name: rxdes.C */
/* */
/* Copyright René Jansen june 1st 1993 */
/* */
/* Function: crexx external functions RxDesEncrypt and RxDesDecrypt */
/* */
/* dependencies/calls: desbase.c CREXX/rxpa */
/* */
/* Ported to CREXX by Adrian Sutherland 2024 */
/*------------------------------------------------------------------*/
#include <stdio.h>
#include <string.h>
#include "crexxpa.h" // crexx/pa - Plugin Architecture header file
#include "desbase.h" // DES encryption/decryption functions
// Encrypt a string using DES - the first argument is the key, the second the data
PROCEDURE(RxDesEncrypt)
{
char des_out[8];
char des_in[8];
char key[8];
char result[17];
if( NUM_ARGS != 2)
RETURNSIGNAL(SIGNAL_INVALID_ARGUMENTS, "2 arguments expected")
if (strlen(GETSTRING(ARG(0))) != 16 || strlen(GETSTRING(ARG(1))) != 16)
RETURNSIGNAL(SIGNAL_INVALID_ARGUMENTS, "Both arguments must be 16 hex digits")
if( hex2bin(GETSTRING(ARG(0)), key) < 0)
RETURNSIGNAL(SIGNAL_INVALID_ARGUMENTS, "Key is not valid hex")
if( hex2bin(GETSTRING(ARG(1)), des_in) < 0)
RETURNSIGNAL(SIGNAL_INVALID_ARGUMENTS, "Data is not valid hex")
// Encrypt
desinit(key);
endes(des_in, des_out);
// Set return and make sure the signal is reset/ok
bin2hex(des_out, result);
SETSTRING(RETURN, result);
RESETSIGNAL
}
// Decrypt a string using DES - the first argument is the key, the second the data
PROCEDURE(RxDesDecrypt)
{
char des_out[8];
char des_in[8];
char key[8];
char result[17];
if( NUM_ARGS != 2)
RETURNSIGNAL(SIGNAL_INVALID_ARGUMENTS, "2 arguments expected")
if (strlen(GETSTRING(ARG(0))) != 16 || strlen(GETSTRING(ARG(1))) != 16)
RETURNSIGNAL(SIGNAL_INVALID_ARGUMENTS, "Both arguments must be 16 hex digits")
if( hex2bin(GETSTRING(ARG(0)), key) < 0)
RETURNSIGNAL(SIGNAL_INVALID_ARGUMENTS, "Key is not valid hex")
if( hex2bin(GETSTRING(ARG(1)), des_in) < 0)
RETURNSIGNAL(SIGNAL_INVALID_ARGUMENTS, "Data is not valid hex")
// Decrypt
desinit(key);
dedes(des_in, des_out);
// Set return and make sure the signal is reset/ok
bin2hex(des_out, result);
SETSTRING(RETURN, result);
RESETSIGNAL
}
// Functions to be provided to rexx - these are loaded either when the plugin is loaded (dynamic) or
// before main() is called (static)
LOADFUNCS
// C Function__, REXX namespace & name, Option_, Return Type_, Arguments
ADDPROC(RxDesDecrypt, "rxdes.decrypt", "b", ".string", "key=.string,data=.string");
ADDPROC(RxDesEncrypt, "rxdes.encrypt", "b", ".string", "key=.string,data=.string");
ENDLOADFUNCS
// End of file
The following shows how the defined functions are published to \crexx{}.
// Functions to be provided to rexx \- these are loaded either when the*
// plugin is loaded (dynamic) or before main() is called (static)*
LOADFUNCS
// C Function\_\_, REXX namespace & name, Option\_, Return Type\_,*
// Arguments*
ADDPROC (RxDesDecrypt, "rxdes.decrypt", "b", ".string",
"key=.string,data=.string");
ADDPROC (RxDesEncrypt, "rxdes.encrypt", "b", ".string",
"key=.string,data=.string");
ENDLOADFUNCS**
The ADDPROC Macro published the function to \crexx{}. This has the namespace and name, options/level (should be b), the function return type (in level b format) and the arguments (again in level b format). This allows the Compiler and VM to search and “fix-up” procedure calls using the same search order and syntax as for procedures developed in REXX.
The following MACROs are provided for plugin developers (defined in rexxpa.h)
| Macro | Purpose |
|---|---|
| LOADFUNCS | Starts and finishes the block of ADDFUNC()’s |
| ADDFUNC() | Published a function to \crexx{} |
| NUM_ARGS | Is the number of arguments passed the the function |
| ARG() | Returns the nth argument (which is an opaque pointer to the \crexx{} register) |
| RETURN | Returns the register used to pass the function’s returned value. |
| GETSTRING() | Gets the String value of a register |
| SETSTRING() | Sets the String value of a register |
| GETINT() | Gets the Integer value of a register |
| SETINT() | Sets the Integer value of a register |
| GETFLOAT() | Gets the float (double) value of a register |
| SETFLOAT() | Sets the float (double) value of a register |
| SIGNAL | Returns the registers used to pass and Signal Information. |
| RESETSIGNAL | Ensures that the signal register is set no “no signal” |
| RETURNSIGNAL() | Sets the signal register and returns from the function. Used for error conditions. |
| ENDLOADFUNCS | Starts and finishes the block of ADDFUNC()’s |
The Signal values are:
| Signal | Purpose | ||
|---|---|---|---|
| SIGNAL_NONE | |||
| SIGNAL_ERROR | Syntax error | SIGNAL_OVERFLOW_UNDERFLOW | numeric overflow or underflow |
| SIGNAL_CONVERSION_ERROR | conversion error between types | ||
| SIGNAL_UNKNOWN_INSTRUCTION | unknown RSAS instruction | ||
| SIGNAL_FUNCTION_NOT_FOUND | attempt to execute an unknown function | ||
| SIGNAL_OUT_OF_RANGE | attempt to access an array element that is out of range | ||
| SIGNAL_FAILURE | error in an external function or subroutine | ||
| SIGNAL_HALT | an external request to halt execution | ||
| SIGNAL_NOTREADY | IO error, such as a file not being ready | ||
| SIGNAL_INVALID_ARGUMENTS | invalid arguments are passed to a function | ||
| SIGNAL_OTHER | Other (or unknown) error condition |
In addition
The following example shows a CMake script (CMakeLists.txt) to build a plugin. It uses a provided CMake function add_plugin_target from RXPluginFunction.cmake.
cmake_minimum_required(VERSION 3.5)
project(rxdes C)
set(CMAKE_C_STANDARD 90)
# Including RXPlugin Build System
include(${CMAKE_SOURCE_DIR}/rxpa/RXPluginFunction.cmake)
# Create module
add_dynamic_plugin_target(des rxdes.c desbase.c desbase.h)
This script builds a dynamic version of the plugin, and supports Windows (mingw and VS), OSX and Linux.
The \crexx{} Cmake build configuration should be referenced for static build support. Building the static library is simple, but proper linking is crucial to guarantee that the linker recognizes the need to link in the plugin and that the plugin’s initialization function is called at program load across various platforms.
The following is a REXX Level B example using the plugin.
Note that there is no manual loading of the dynamic or static plugin, instead \crexx{} loads the plugins using the same search rules as it uses for other REXX modules. This means that the REXX program (or programmer) does not need to be concerned about how the external function is provided - REXX, Native, Dynamic, Static - it all has the same calling syntax. This is designed to meet the objective to simplify programming. ```rexx options levelb /* This is a rexx level b program */
import rxfnsb /* Import the crexx level B functions */
import rxdes /* Import the rxdes plugin functions */
/* Note that the input and output to the des functions are in hex strings */
Plaintext = “0000000000000000”
key = “08192A3B4C5D6E7F”
Ciphertext = Encrypt(key,Plaintext) ``` In line 4, the import statement loads the namespace meaning that any REXX modules or native plugins in the rxdes namespace will be loaded as needed; the function call, encrypt(), follows REXX syntax, and the compiler can check parameters and return types as normal.