I always considered to have a nice batch language extension for my Smalltalk applications.
One of the best batch languages I’ve seen is REXX. Perhaps due to the fact, that I’ve OS/2 background knowledge I was always fascinated what is possible with such a greate language within an operating language.
One of the implementation of this language was OORexx from IBM. After IBM opened their OORexx under their “Common Public License”, the interpreter was opened source and available for everyone.
To make this wrapper work, you need at least two files from that distribution: “rexx.dll” and “rexxapi.dll”, which should be located in your Smalltalk bin directory. When writing more complex REXX programs (e.g. IP related stuff), you may also need additional stuff. Some of this stuff is also included in the download.
Please notice, this is a snapshot. Not finished, but at least with ObjectRexx it can be used to call external REXX scripts, even with parameters and you can return values to Smalltalk back again.
The version 0.36 seems to be ok to show how the things are working. Some memory related stuff has to be done – but one can work with this library.
This solution presented here is only working under Windows, but OORexx is also available for OS/2, Linux and Solaris and therefore the solution should be portable accross different platforms.
* Download OORexx 3.x from http://www.oorexx.org and extract dynamic libraries named “rexx.dll” and “rexxapi.dll”.
* Copy both libraries to the bin directory of your VASmalltalk application.
* Download the latest MSKKernel package from this site.
* Import the application map MSKKErnel and MSKREXXSupport from the downloaded archive
* You may look at the simple SUnit test in application “MSKRexxInterfaceTestsApp”. Please change method “baseTestPath” according to your needs.
As a simple example, please consider the following little REXX script located in an external text file:
/* */ parse arg one, two, three Return(one||two||three||one||two||three )
It accepts three parameters and returns a two times concatenation of these parameters. Very simple.
To call this external script file you write Smalltalk code like:
aRexxInterface := MSKOORexxInterface new. aRexxInterface rexxStart: (Array with: '1' with: '2' with: '3' ) fromFile: 'c:\simple-return.rexx'. aRexxInterface release.
From version 0.28 it is possible to execute REXX source code, which is stored in Smalltalk memory.
aRexxInterface := MSKOORexxInterface new. aRexxInterface rexxStart: (Array with: '1' with: '2' with: '3' ) source: self simpleRexxScriptSource. aRexxInterface release.
where simpleRexxScriptSource looks like:
| sourceStream | sourceStream := WriteStream on: String new. sourceStream nextPutAll: 'parse arg one, two, three'; cr ; nextPutAll: 'Return(one||two||three||one||two||three )' ; cr. ^sourceStream contents
Calling REXX scripts in this way can be done in two different way:
When doing asynchronous call the call is done using a static future call. Due to the fact, that a REXX script may have concurrency within itself, the rexxStart: returns to the calling Smalltalk, but some parts of the REXX script is still running and may run asynchron compared to the running Smalltalk image.
Communication between Smalltalk and still running REXX scripts can be done using queues.
Simple Queue Example
The following code is not a runnable code, but it shows via examples how it works.
aRexxInterface := MSKOORexxInterface new. "Before starting the rexx script, we create a data queues for data exchange !" "The first one is uses for transfer Smalltalk -> REXX" queueNameToRexx := aRexxInterface rexxCreateQueueNamed: 'toRexx'. "The second one is used for transfer REXX -> Smalltalk" queueNameToSmalltalk := aRexxInterface rexxCreateQueueNamed: 'toSmalltalk'. "We start the REXX script" rcAnswer := aRexxInterface rexxStart: (Array with: '1' with: '2' with: '3' ) fromFile: 'c:\sample.rexx'. "Just as an example - transfer a string to a running REXX script via queue" aRexxInterface rexxAddFIFOQueueNamed: queueNameToRexx value: 'aString for Rexx'. "or as an example: retrieve a string from a running REXX script via a queue" "value may be nil or an two element array like #( value timestamp)" valueArray := aRexxInterface rexxPullQueueNamed: queueNameToSmalltalk wait: false. aRexxInterface release.
What is a subcommand handler ? Via a Subcommand Handler REXX is enabled to make call-backs into the starting program to force the program to evaluate a command sequence within the REXX script ….
| aHandler aRexxInterface rcAnswer rexxScriptPath | aRexxInterface := MSKRexxInterface new. "We create a subcommand handler instance" aHandler := MSKRexxSubcommandHandler new. aHandler commandHandlerName: 'MSKTEST' ; "we define the handler block !" subCommandBlock: [ :sc | " We expect a simple number .... and return the number multiplied with 10 ! " sc subCommandReturnString: (sc subCommandString trimBlanks asNumber * 10) asString ; subCommandFlags: RxSubcomOk ; subCommandReturnCode: 0. ]. "We register the subcommand handler within REXX ..." aRexxInterface addSubCom: aHandler ; rexxRegisterSubcomExe. "And we start the test script" rexxScriptPath := self baseTestPath append: 'mskTestSubComExe00.rexx'. rcAnswer := aRexxInterface rexxStart: #( '1' ) codeLocation: rexxScriptPath asString. self assert: (rcAnswer returnString = '10').
What happens here ? A REXXInterface is opened and a subcommand handler is created and added to the RexxInterface. Via “rexxRegisterSubcomExe” we informed REXX about the new environment named “MSKTEST”. Internally within Smalltalk we created EsEntryPoints to handle the callbacks from Rexx to Smalltalk.
The core subcommandBlock is defined using a Smalltalk block. The main work is done in
“sc subCommandString trimBlanls asNumber * 10) asString”.
Ok, the REXX script is started:
/* */ parse arg aNumber ADDRESS MSKTEST aNumber return(rc)
and as everyone can see, it does nothing but calls the environment MSKTEST for evaluation of the term “aNumber”, which a “1” or “2” (example above) and REXX internally looks for the suitable environment and calls the evaluation handler and turns over the action to Smalltalk.
More or less the handler block in Smalltalk is executed and returns a result to the REXX program (via variable rc).
Of course the subcommand handler can be much more complex than the one above.
Some words about Exit Handler
What are exit handlers ? Well there are several of them in the system, but the library has only code for the SIO exit handler and also only for subfunction SAY and TRACE.
What happens, when the rexx script you have called from Smalltalk does not work as you expected ? You put “say …” statements to show, what is going on or you simple trace through the code.
The SIO exit handler allows you to receive the output from trace and say within Smalltalk and e.g. output the result to the transcript window.
What is still missing ?
* Further testing to catch memory leaks
* Perhaps some more exit handler support for the not supported handlers ?
* Perhaps variable pool access support ?
* Perhaps external function support ?