/* ------------------------------------------------------------------ */ /* A REXX program is just a sequence of instructions residing in a */ /* file. It may pass control to labelled blocks of code residing in */ /* the same file [internal subroutines], as well as to other REXX */ /* programs which, in this context, are known as external subroutines.*/ /* Things worth noting: */ /* */ /* * REXX possesses no formal notion of the module / package concept; */ /* each program is a standalone, totally independant entity with no */ /* program being able to - directly - access the contents of another*/ /* program */ /* */ /* * The name of a REXX program plays no part in naming or otherwise */ /* identifying any of that program's contents. Thus, there is also */ /* no notion of namespace, nor is it possible to create an alias for*/ /* an existing entity. Each subroutine used in a program must be */ /* named so as to be uniquely identifiable */ /* */ /* * REXX possesses no pre-processing facility, thus conditional code */ /* inclusion [e.g. debugging code, code from other source files] */ /* is not possible */ /* */ /* The lack of a module/package system may be of concern to some, just*/ /* as a lack of pre-processing facility may be to others. However, it */ /* should be remembered that REXX was designed to be an easy-to-use, */ /* general purpose, end-user tool, one which would facilitate the */ /* creation of simple scripts such as those for one-off tasks or for */ /* tying together several applications. Key to ensuring its achieving */ /* this goal is to keep the language simple, and facilities offered */ /* quite minimal. Put simply, REXX was not intended for large-scale, */ /* team-based development, so does not offer facilities which cater */ /* to this. */ /* */ /* Of course it is possible to implement such functionality, though of*/ /* course it would not be as 'clean', and sophisticated as would the */ /* equivalent native facility: */ /* */ /* * A compilation step could be introduced using an an external pre- */ /* processor [e.g. third-party package like 'm4' or a script written*/ /* in REXX or other tool like 'awk']. This would allow conditional */ /* code inclusion in the same way it is achieved in C using #define */ /* and #include. */ /* */ /* * Use the filesystem [e.g. directories / folders] to act as module */ /* or packages in much the same way it is done in Perl and Java */ /* */ /* * Package a REXX file as a module [i.e. collection of subroutines],*/ /* and adopt the convention of invoking a particular subroutine when*/ /* the 'module' [invoked as an external subroutine] is accessed */ /* */ /* These techniques will be described in the relevant sections. The */ /* third approach - REXX file as module - will be used in all the */ /* sections where Perl package / module use is made. However, given */ /* the very significant differences between REXX and Perl, these, and */ /* other examples will differ from the original cookbook code. */ /* */ /* Finally, related to the idea of modules, is REXX support for ext- */ /* ernal libraries [i.e. collections of machine code routines]. These */ /* cannot be considered true modules because they are not part of the */ /* core language. However, their use is key to extending REXX funct- */ /* ionality, so it is important to understand how they are used. As */ /* the examples will show they are managed in a manner quite like */ /* Perl modules. */ /* ------------------------------------------------------------------ */ /* The first Perl example ['Alpha / Omega'] illustrates how a 'package' can be created 'on the fly'. Inapplicable to REXX. */ /* ----------------------------- */ /* The second Perl example illustrates both the compile-time and run- tie loading of packages / modules. Neither applies to REXX; the closest equivalent is to check for the availability of a REXX file */ /* Run-time availability check i.e. ensure 'FileHandle.rexx' exists */ available = require("FileHandle.rexx") available = require("FileHandle") if available then ; say "'FileHandle' package is available" else ; say "'FileHandle' package *NOT* available" /* ----------- */ available = require("Cards/Poker.rexx") if available then ; say "'Cards/Poker' package is available" else ; say "'Cards/Poker' package *NOT* available" /* ----------------------------- */ /* Rough outline of how a REXX file might be stuctured to play the role of a 'module'. Example is roughly functionally equivalent to the Perl example */ /* Contents of file, 'Poker.rexx' located in 'Cards' directory */ /* /* Module Name */ _modname = "Poker" ... /* Method List [i.e. exports] */ _methods = "shuffle getCardDeck setCardDeck" ... /* Method Implementations */ shuffle : procedure ... getCardDeck : procedure ... setCardDeck : procedure ... ... */ |
/* ------------------------------------------------------------------ */ /* Since REXX does not support modules this issue is moot. However an */ /* outline of how to implement a 'REXX file as module' is shown; in */ /* the current section an outline of how to structure, then use, one */ /* is provided, whilst a complete implementation [and example of use] */ /* appears in the second last section of this chapter. */ /* */ /* Implementing a 'module' system in REXX is actually quite simple. It*/ /* makes use of some key REXX features: */ /* */ /* * A source file is self-contained: all contents are private to that*/ /* file, thus making it an ideal vehicle for acting as a 'module' */ /* */ /* * A source file is callable as an external subroutine, and is able */ /* to accept arguments, and return result(s) to the caller */ /* */ /* * The INTERPRET instruction allows for the evaluation of arbitrary */ /* strings as code. Therefore, it is possible to pass the name of a */ /* subroutine, and any arguments it might require, to a subroutine */ /* and have *that* subroutine execute */ /* */ /* as well as requiring an adherence to certain conventions: */ /* */ /* * Module data access must be via 'accessor' subroutines and updates*/ /* via 'mutator' subroutines */ /* */ /* * Module subroutine calls are via argument passing to module file */ /* calls */ /* */ /* * Module data is stored via some 'persistence' mechanism [since a */ /* module is really a REXX subroutine, data is destroyed on exit; */ /* any data needing to be retained needs to be externally stored] */ /* */ /* * Metadata such as the module name, version, and perhaps list of */ /* subroutines [and optionally associated descriptions] be kept as */ /* module data [and suitable accessors provided] */ /* */ /* All these ideas appear in the module example in the second last */ /* section of this chapter. */ /* ------------------------------------------------------------------ */ /* *** Translation skipped *** */ |
/* ------------------------------------------------------------------ */ /* REXX lack of module support makes it unnecessary for it to offer */ /* facilities / keywords like Perl's 'require' or 'use'. If a native */ /* REXX subroutine is to be invoked [particularly an internal routine]*/ /* it is simply assumed to exist. Calling a non-existent routine will */ /* raise a SYNTAX condition which can, of course, be trapped, but this*/ /* approach is rarely worth the trouble as it merely complicates the */ /* application design [an extensive example is in <<PLEAC>>_10.15]. */ /* */ /* Invoking an external subroutine assumes that it resides in a known */ /* location, thus a check for its presence can be made prior to the */ /* call, and appropriate recovery steps taken [i.e. look for it else- */ /* where, generate the required code, etc ...]. This is, in effect, */ /* an implementation of 'require'-like functionality. The example in */ /* the previous section well-illustrates the use of the custom routine*/ /* 'require', for this task, as well as how module information may be */ /* obtained [though a simple example also appears below]. */ /* */ /* A run-time facility akin to module use is the loading / registering*/ /* of external library functions [i.e. machine code routines residing */ /* in shared library files]. REXX sports a complete API for handling */ /* such entities, including the ability to test whether any such have */ /* been correctly loaded. */ /* */ /* Library loading is actually a two-step process: */ /* */ /* * Loading the library file [i.e. shared library / DLL] */ /* * Loading the desired function [a step repeated for each function] */ /* */ /* A convention has been adopted for such libraries in which both a */ /* a loader [of all functions] and an unloader function are provided */ /* to facilitate library function handling. The example below shows */ /* their use. */ /* */ /* Another means of trapping 'module' errors is to introduce a kind of*/ /* 'compilation step', something easily achieved via the use of a pre-*/ /* processor. For example, one could adopt the convention that a line */ /* such as: */ /* */ /* #include "myModule.rexx" */ /* */ /* would see a search for the relevant file made, and its contents */ /* inlined starting at that location. Failure to locate and include */ /* the 'module' would see a 'complation' error signalled, and remedial*/ /* steps taken. Needless to say, the pre-processor could be something */ /* like a small REXX or awk script, or a sophisticated application */ /* such as 'm4'. */ /* ------------------------------------------------------------------ */ if \require("modulename") then ; say "Couldn't load 'modulename'" /* ----------- */ modulelist = "Giant/Eanie Giant/Meanie Mouse/Mynie Moe" do while modulelist <> NULL parse var modulelist mod modulelist if \require(mod) then ; say "Couldn't load" mod end /* ----------------------------- */ /* *** Regina-specific Examples *** */ /* Dynamically adding / removing external library functions */ /* Load general-purpose functions from external library */ /* [1] Load / register the 'library loader' function */ if \rxFuncAdd('sysLoadFuncs', 'rexxUtil', 'sysLoadFuncs') then say "Error loading ..." /* [2] Call the 'library loader' function to load *all* functions */ call sysLoadFuncs /* Use some of these general-purpose function(s) */ call sysCls /* Invoke 'unloader' function to remove all functions from memory */ call sysDropFuncs |
/* ------------------------------------------------------------------ */ /* Since REXX does not support modules the issue of delaying their use*/ /* until runtime is moot. Additionally, using the 'external subroutine*/ /* as module' approach [as has been extensively done] sees all module */ /* contents unavailable until it is actually needed. Thus, the issue */ /* delaying module loading does not arise. */ /* ------------------------------------------------------------------ */ /* *** Translation skipped *** */ |
/* ------------------------------------------------------------------ */ /* Assuming the conceit of having a REXX file / external subroutine */ /* mimic a 'module', it should be noted that all variables declared */ /* within that file are local to that file. Therefore, the issue of */ /* making variables private to a module is not one applicable in REXX.*/ /* */ /* Whilst on this matter, it should be pointed out that it is not */ /* possible to make those variables externally accessable, nor is it */ /* possible to make those variables persistent. Both tasks *can* be */ /* accomplished, albeit indirectly, through the use of an external */ /* storage system together with a set of 'accessor' methods. */ /* */ /* The example below - based on the first Perl example in this section*/ /* - illustrates the above except that variables are non-persistent */ /* [persistent variables are illustarted elsewhere]. */ /* ------------------------------------------------------------------ */ /* Module Name */ _modname = "Alpha" ... /* Method List [i.e. exports] */ _methods = "getAA setAA getX setX" ... /* Data [private, non-persistent, set to initial values] */ aa = NULL ; x = NULL ... /* Method Implementations */ getAA : procedure expose aa ; return aa setAA : procedure expose aa ; aa = ARG(1) ; return aa getX : procedure expose x ; return x setX : procedure expose x ; x = ARG(1) ; return x ... /* ----------- */ /* As above [with appropriate name changes] for package, 'Beta' */ /* ----------- */ if requires("Alpha") then do call Alpha "setAA", 10 call Alpha "setX", "azure" end if requires("Beta") then do call Beta "setBB", 20 call Beta "setX", "blue" end /* In current package */ say Alpha("getAA") Beta("getBB") Alpha("getX") Beta("getX") |
/* ------------------------------------------------------------------ */ /* The only information obtainable about a caller is information */ /* actually passed to the callee such as, for example, the caller's */ /* name. The Perl examples are, therefore, not translatable. */ /* ------------------------------------------------------------------ */ /* *** Translation skipped *** */ |
/* ------------------------------------------------------------------ */ /* Since REXX does not support modules the issue of automating their */ /* cleanup is moot. However, it does help highlight the issue of */ /* application cleanup, something of equal importance especially where*/ /* there are external resources which must be properly released prior */ /* to application exit. */ /* */ /* REXX does not implement the equivalent of Perl's END block, so it */ /* not possible to specify code blocks that must *always* execute e.g.*/ /* like a C++ destructor. It does, however, allow the trapping of */ /* certain CONDITIONS [roughly the same as Perl signals], and the */ /* specifying of handlers for those conditions. */ /* */ /* A condition may be raised by the interpreter, or by the program */ /* [via an explicit 'signal CONDITION' instruction]. An easy way to */ /* ensure a block of code is executed both at the end of normal exe- */ /* cution, and when a condition is raised is to place such code at the*/ /* end of the application. The Perl-equivalent example below uses this*/ /* approach. */ /* ------------------------------------------------------------------ */ /* Outline of a 'cleanup' subroutine triggered by HALT and SYNTAX */ /* Set 'cleanup' routine to trigger specified conditions */ signal on SYNTAX name cleanup signal on HALT name cleanup /* ... application main body ... */ exit 0 /* ----------- */ /* Application 'cleanup' routine */ cleanup : /* ... cleanup tasks ... */ exit 0 /* ----------------------------- */ /* As per Perl example */ LOG = "mylogfile.txt" /* Control jumps to 'cleanup' in the event of a raised condition */ signal on SYNTAX name cleanup signal on HALT name cleanup call logmsg LOG, "startup" /* ... application main body ... */ /* Control falls through to this block under normal execution */ cleanup : call logmsg LOG, "shutdown" exit 0 /* ----------- */ logmsg : procedure expose (globals) logfile = ARG(1) ; message = ARG(2) call LINEOUT logfile, message return |
/* ------------------------------------------------------------------ */ /* Keeping your own module directory */ /* ------------------------------------------------------------------ */ @@INCOMPLETE@@ @@INCOMPLETE@@ |
/* ------------------------------------------------------------------ */ /* REXX is an interpreted language, therefore, a REXX application is */ /* normally distributed as a set of source files [it is assumed that */ /* intended client / user possesses a suitable interpreter; refer to */ /* later section on interpreter installation / distribution]. */ /* */ /* Considerations: */ /* */ /* * Any general purpose distribution tool may be used; packaging up */ /* applications as .zip or .tgz files is common and easy to do, as */ /* is the use of utilities such as InstallShield to largely automate*/ /* application installation */ /* */ /* * REXX compilers are available. Aside from the performance benefits*/ /* obtainable via such tools, it avoids the need for source code */ /* distribution */ /* */ /* * A particularly useful tool is Rexx/Wrapper, available from: */ /* */ /* http://rexxwrapper.sourceforge.net/doc/index.html */ /* */ /* This utility bundles up source code [optionally encrypted] into */ /* executable form, simplifying and helping secure the application */ /* distribution process. */ /* ------------------------------------------------------------------ */ /* Sample Rexx/Wrapper Session [not all required options shown] */ /* /* Command-line use [may also be used interactively] rexx rexxwrap.cmd -options ... /* Create 'Planets' module distribution package [incomplete] */ rexx rexxwrap.cmd -program Planets -rexxfiles /home/Planets.rexx /* Create 'Planets' module distribution package [incomplete] */ rexx rexxwrap.cmd -program Orbits -rexxfiles /home/Orbits.rexx */ |
/* ------------------------------------------------------------------ */ /* REXX does not implement a facility like Perl's Self Loader. Hence */ /* this concept is inapplicable in REXX. */ /* ------------------------------------------------------------------ */ /* *** Translation skipped *** */ |
/* ------------------------------------------------------------------ */ /* REXX does not implement a facility like Perl's Auto Loader. Hence */ /* this concept is inapplicable in REXX. */ /* ------------------------------------------------------------------ */ /* *** Translation skipped *** */ |
/* ------------------------------------------------------------------ */ /* Implementing a subroutine having the same name as a built-in [BIF] */ /* sees the replacement subroutine invoked whenever that name is used */ /* in a call. Unfortunately, it also renders the BIF inaccessable, and*/ /* it is not even possible to call the BIF from within the replacement*/ /* subroutine. Hence, this is a practice best avoided in REXX. */ /* */ /* The lack of native module support renders this concept otherwise */ /* inapplicable in REXX. */ /* ------------------------------------------------------------------ */ /* Call the built-in 'TIME' function; displays the actual HH:MM:SS */ say TIME('N') /* ----------------------------- */ /* Call 'TIME' function override; displays the string "[[HH:MM:SS]]" */ say TIME('N') /* ----------- */ TIME : procedure expose (globals) return "[[HH:MM:SS]]" |
/* ------------------------------------------------------------------ */ /* REXX BIF's will generally raise SYNTAX conditions to signal errors */ /* such as, for example, the passing of invalid arguments. It is quite*/ /* possible to both: */ /* */ /* * Override the default SYNTAX condition handler so as to customise */ /* the handling of BIF errors */ /* */ /* * Mimic this error trapping / handling strategy in custom code */ /* */ /* An example of each is shown; only the second is based on the Perl */ /* cookbook code. */ /* ------------------------------------------------------------------ */ /* Customised BIF Error Handling */ /* Install handler [default name is 'SYNTAX'] for SYNTAX condition */ signal on SYNTAX /* ... */ /* Force SYNTAX condition: invoke 'TIME' BIF with erroneous argument */ say TIME('Z') /* ... */ /* SYNTAX condition handler */ SYNTAX : /* Display error information, and exit interprter */ say makeErrorMsg(40, SIGL) exit 1 /* ----------- */ /* Displays error information in same format as default handler */ makeErrorMsg : n = ARG(1) ; lineno = ARG(2) ; parse source . . name return "Error" n "running" '"' || name || '", line', lineno || ":" ERRORTEXT(n) /* ----------------------------- */ /* Custom Subroutine Error Handling */ call even_only 2 /* Executes ok */ call even_only 3 /* Error trapped and reported */ /* ----------- */ even_only : procedure expose (globals) n = ARG(1) ; signal on SYNTAX name eo_error if (n // 2) > 0 then ; signal SYNTAX /* ... */ return TRUE eo_error : say "Error in 'even_only' subroutine: is not even" return FALSE |
/* ------------------------------------------------------------------ */ /* Since REXX does not support modules this issue is moot. Examples, */ /* are therefore not translatable. */ /* ------------------------------------------------------------------ */ /* *** Translation skipped *** */ |
/* ------------------------------------------------------------------ */ /* There is, AFAIK, no publically-available REXX translation tool that*/ /* is similar in functionality to Perl's 'h2ph' utility. */ */ /* ------------------------------------------------------------------ */ @@INCOMPLETE@@ @@INCOMPLETE@@ |
/* ------------------------------------------------------------------ */ /* There is, AFAIK, no publically-available REXX translation tool that*/ /* is similar in functionality to Perl's 'h2xs' utility. */ */ /* ------------------------------------------------------------------ */ /* *** Translation skipped *** */ |
/* ------------------------------------------------------------------ */ /* There is, AFAIK, no publically-available documentation generation */ /* tool for REXX [though, doubtless, a significant number do exist, */ /* though as proprietary products]. */ /* */ /* However, there are a number of general purpose documentation tools */ /* available. A particularly useful one is ROBODoc, available from: */ /* */ /* http://www.xs4all.nl/~rfsber/Robo/robodoc.html */ /* */ /* This product is language-neutral, and works something like the very*/ /* widely used javadoc tool [Java Documentation] in that it scans the */ /* source code it is fed looking for specially-formatted comments from*/ /* which it extracts information and assembles it into one of several */ /* formats including HTML and PDF. */ /* */ /* ROBODoc is fully configurable, but does recognise several comment */ /* types by default, including that for the C language. Since REXX */ /* utilises the same comment type it is possible to use it 'out of the*/ /* box' by using the C language commentary conventions. What could be */ /* easier :) ? */ /* ------------------------------------------------------------------ */ /* *** Translation skipped *** */ |
/* ------------------------------------------------------------------ */ /* Since REXX does not support modules the issue of installing such is*/ /* moot. However, it is worth discussing the issue of REXX interpreter*/ /* distribution / installation [REXX application distribution and */ /* installation was discussed in an earliuer section]. */ /* */ /* Quite obviously this is both an interpreter-specific and platform- */ /* specific issue. In the case of Regina, a popular implementation */ /* which emphasises cross-platform workability, installation options */ /* for several platforms are available. In fact, the one interpreter */ /* package may be used to generate interpreter executables for several*/ /* platforms. See: http://regina-rexx.sf.net */ /* ------------------------------------------------------------------ */ /* Sample Regina REXX Installation Session for *NIX / Linux --- % ./configure % make install */ /* ----------------------------- */ /* Sample Regina REXX Installation Session for OpenVMS --- % @BUILD */ /* ----------------------------- */ /* Sample Regina REXX Installation Session for Win32 --- 1. Unzip archive file 2. Copy the files: * regina.exe * regina.dll into a directory specified in your PATH environment variable 3. [Optional] configuration steps outlined in documentation */ |
/* ------------------------------------------------------------------ */ /* Example: Module Template */ /* */ /* The following example consists of: */ /* */ /* * A complete 'module' implementation */ /* * A complete module usage example */ /* */ /* The only assumption made is that the module reside in a REXX source*/ /* file having the same name as the module [in this case, 'modtime']. */ /* More details on module structure is in the earlier section on */ /* module interfaces. */ /* ------------------------------------------------------------------ */ /* *** Module name and version *** */ _modname = "modtime" ; _modversion = 1.0 /* ----------------------------- */ /* *** Module Constants *** */ FALSE = 0 ; TRUE = 1 ; NULL = "" ; NEWLINE = "0A"X ; SPACE = ' ' globals = "sys. env. args. $. FALSE TRUE NULL NEWLINE SPACE" /* ----------------------------- */ /* *** Module Non-persistent Storage *** */ /* ... */ /* ----------------------------- */ /* *** Module routine table *** */ /* [1] Housekeeping routines list */ _code = "init cleanup getModuleName getModuleVersion getMethodList" /* [2] User methods list */ _method = "getTime setTime" /* ----------------------------- */ /* *** Module Entry Point / Method Dispatcher *** */ parse value ARG(1) with _proc "," . ; _args = ARG() ; _arglist = NULL /* Extract arguments and construct callable routine */ if _args > 1 then do do i = 2 to _args ; _arglist = _arglist ARG(i) ; end _cmd = "_result =" _proc || "(" || STRIP(_arglist) || ")" end ; else do _cmd = "_result =" _proc || "()" end /* Ensure constructed routine is actually a module routine */ if \hasCode(_proc) & \hasMethod(_proc) then ; return NULL /* Invoke routine and return its result to caller */ interpret _cmd ; return _result /* ----------------------------- */ /* *** Module Code *** */ /* [1] Housekeeping Routines */ init : /* Module setup routine [i.e. module 'constructor'] */ call setTime DATE() return NULL /* ----------- */ cleanup : /* Module cleanup rotuine [i.e. module 'destructor'] */ return NULL /* ----------- */ /* Module Information Accessors */ getModuleName : ; return _modname getModuleVersion : ; return _modversion getMethodList : ; return _method /* ----------- */ /* Module Validation Routines */ hasCode : ; return POS(ARG(1), _code) > 0 hasMethod : ; return POS(ARG(1), _method) > 0 hasGlobal : ; return LENGTH(VALUE(ARG(1),, 'SYSTEM')) > 0 /* ----------- */ /* External Persistent Storage */ updateGlobal : ; call VALUE ARG(1), ARG(2), 'SYSTEM' ; return NULL extractGlobal : ; return VALUE(ARG(1),, 'SYSTEM') /* ----------------------------- */ /* [2] User Methods */ getTime : procedure expose (globals) /* Extract value of "TIME" from external storage */ return extractGlobal("TIME") /* ----------- */ setTime : procedure expose (globals) /* Set "TIME" in external storage to specified value */ call updateGlobal "TIME", ARG(1) return ARG(1) /* ----------------------------- */ /* ----------------------------- */ /* Module Usage Example */ /* Application Options */ options 'NO_STRICT_ANSI' trace 'OFF' signal on NOVALUE /* Global Constants */ FALSE = 0 ; TRUE = 1 ; NULL = "" ; NEWLINE = "0A"X ; SPACE = ' ' /* Global Roots and 'expose' list */ globals = "sys. env. args. $. FALSE TRUE NULL NEWLINE SPACE" /* ----------- */ /* Check module availability */ available = require("modtime") if available then do /* Initialise module */ call modtime "init" /* Extract and print module information */ name = modtime("getModuleName") version = modtime("getModuleVersion") methods = modtime("getMethodList") say "Module 'modtime' is available" say "Details:" say " Name:" name say " Version:" version say " Methods:" methods /* Invoke user-available module routine(s) */ say modtime("getTime") /* Cleanup module */ call modtime "cleanup" end ; else do say "Module 'modtime' *NOT* available" end /* ----------------------------- */ /* Current implementation is Win32 / *NIX specific */ require : procedure expose (globals) /* Extract PATH components */ parse value ARG(1) with name "." extension ; version = ARG(2) if extension == NULL then ; extension = "rexx" path = name || "." || extension /* Check file / module existence, and its version [if required] */ if LENGTH(STREAM(path,'C',"QUERY EXISTS")) > 0 then do if version == NULL then ; return TRUE _cmd = "_result =" name || '("getModuleVersion")' interpret _cmd ; return _result == version end return FALSE |