12. Packages, Libraries, and Modules

Introduction

/* ------------------------------------------------------------------ */
/* 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 ...
  ...

*/

Defining a Module's Interface

/* ------------------------------------------------------------------ */
/* 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 *** */

Trapping Errors in require or use

/* ------------------------------------------------------------------ */
/* 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

Delaying use Until Run Time

/* ------------------------------------------------------------------ */
/* 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 *** */

Making Variables Private to a Module

/* ------------------------------------------------------------------ */
/* 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")

Determining the Caller's Package

/* ------------------------------------------------------------------ */
/* 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 *** */

Automating Module Clean-Up

/* ------------------------------------------------------------------ */
/* 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

/* ------------------------------------------------------------------ */
/* Keeping your own module directory                                  */
/* ------------------------------------------------------------------ */

@@INCOMPLETE@@
@@INCOMPLETE@@

Preparing a Module for Distribution

/* ------------------------------------------------------------------ */
/* 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
*/

Speeding Module Loading with SelfLoader

/* ------------------------------------------------------------------ */
/* REXX does not implement a facility like Perl's Self Loader. Hence  */
/* this concept is inapplicable in REXX.                              */
/* ------------------------------------------------------------------ */

/* *** Translation skipped *** */

Speeding Up Module Loading with Autoloader

/* ------------------------------------------------------------------ */
/* REXX does not implement a facility like Perl's Auto Loader. Hence  */
/* this concept is inapplicable in REXX.                              */
/* ------------------------------------------------------------------ */

/* *** Translation skipped *** */

Overriding Built-In Functions

/* ------------------------------------------------------------------ */
/* 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]]"

Reporting Errors and Warnings Like Built-Ins

/* ------------------------------------------------------------------ */
/* 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

Referring to Packages Indirectly

/* ------------------------------------------------------------------ */
/* Since REXX does not support modules this issue is moot. Examples,  */
/* are therefore not translatable.                                    */
/* ------------------------------------------------------------------ */

/* *** Translation skipped *** */

Using h2ph to Translate C #include Files

/* ------------------------------------------------------------------ */
/* There is, AFAIK, no publically-available REXX translation tool that*/
/* is similar in functionality to Perl's 'h2ph' utility.              */
*/
/* ------------------------------------------------------------------ */

@@INCOMPLETE@@
@@INCOMPLETE@@

Using h2xs to Make a Module with C Code

/* ------------------------------------------------------------------ */
/* There is, AFAIK, no publically-available REXX translation tool that*/
/* is similar in functionality to Perl's 'h2xs' utility.              */
*/
/* ------------------------------------------------------------------ */

/* *** Translation skipped *** */

Documenting Your Module with Pod

/* ------------------------------------------------------------------ */
/* 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 *** */

Building and Installing a CPAN Module

/* ------------------------------------------------------------------ */
/* 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

/* ------------------------------------------------------------------ */
/* 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

Program: Finding Versions and Descriptions of Installed Modules