Author Topic: REXX tutorials -- start developing applets under ArcaOS out-of-the-box  (Read 97142 times)

Jan-Erik Lärka

  • Global Moderator
  • Sr. Member
  • *****
  • Posts: 378
  • Karma: +15/-0
Re: REXX tutorials -- start developing applets under ArcaOS out-of-the-box
« Reply #30 on: March 30, 2026, 05:22:55 pm »
Included with ArcaOS you'll find some tools and rexx scripts by Martin Lafaix, updated by Chuck McKinnis, to extract text and images for example.

This is a function I've written for the rexx script that create a translation database of OS/2.
With a .rc file and the images you can redesign the visual appearance of the application!
See what one can do with the chess game in OS/2.

Since the function below is a part of a larger tool it use variables and functions defined there.
The purpose is however to list resources with
RESMGR -L file -q
and then extract them with
RESMGR -x file *.* output.res
and get the rc-file with
RDC output.res output.rc

rxRCXtrct: PROCEDURE EXPOSE this.
    CALL DIRECTORY this.path
    variables = 'chk.'
    rc = 1
    retval = rxRunRetVal( 'CMD /c RESMGR -L' ARG(1) '-q' )
    IF 0 = POS( 'No resources found in', retval ) & 0 = POS( 'Return checkmz', retval ) THEN
    DO
        tgt = SUBSTR( ARG(1), ARG(2), LASTPOS( '.', ARG(1) ) - ARG(2) )
        CALL rxMkDir ARG(3)||tgt
        rc = rxRun( 'CMD /c RESMGR -x '||ARG(1) '*.* '||ARG(3)||tgt||'.RES' )
        IF rc = 0 & STREAM( ARG(3)||tgt||'.RES', 'C', 'QUERY EXISTS' ) <> '' THEN
        DO
            CALL rxStdOut SysGetMessage( 73, this.msg, FILESPEC( 'N', ARG(1) ), FILESPEC( 'D', ARG(1) ) )
            dest = FILESPEC( 'N', ARG(1) )
            dest = '\'||LEFT( dest, LENGTH( dest ) - 4 )
            CALL rxMkDir ARG(4)||tgt
            IF SysCopyObject( ARG(3)||tgt||'.RES', ARG(4)||tgt ) THEN
            DO
                CALL DIRECTORY ARG(4)||tgt
                rc = rxRun( 'CMD /c RDC' ARG(4)||tgt||dest||'.RES' ARG(4)||tgt||dest||'.RC' )
                IF 0 < POS( 'DIALOG', rxRunRetVal( 'CMD /c RESMGR -L' ARG(4)||tgt||dest||'.RES *.Dialog' ) ) THEN
                    rc = rxRun( 'res2dlg' ARG(4)||tgt||dest||'.RES' ARG(4)||tgt||dest||'.DLG' )
                CALL SysFileDelete ARG(4)||tgt||dest||'.RES'
                CALL SysFileDelete ARG(4)||tgt||dest||'.DL2'
            END
            CALL SysFileDelete ARG(1)
        END
    END
    CALL SysFileDelete ARG(3)||tgt||'.SAV'
RETURN rc

Alfredo Fernández Díaz

  • Full Member
  • ***
  • Posts: 108
  • Karma: +7/-0
Re: REXX tutorials -- start developing applets under ArcaOS out-of-the-box
« Reply #31 on: April 01, 2026, 09:38:49 pm »
Yes, I am familiar with RDC, ResMgr and co. ; )

Your code is good as an example of putting things together, which is essential and a central point to tutorials and my aim here. However, and although it will be interesting in itself to discuss maybe on some other thread, it is not adapted to work on its own but relies on external stuff to do the real work 'off-screen', and thus not something I would include as an example in a tutorial.

OK, while we wait for others to chime in regarding my previous proposal, let's have another working, short code snippet on that same theme of putting things together:

Code: [Select]
/*RexxUtil.dll contains many Sys*Object functions to manipulate WPS objects, but none
to verify whether specific ones, as may be installed along OS/2 or with specific
applications, are present. OS/2 keeps a record of existing object IDs in its user INI
file, from where they can be listed using the SysIni function, also from RexxUtil.dll: */

if FindObject('<WP_SYSTEM>') then
  say '"System" object found! A vanilla OS/2 system, I suppose.'
else do
  if FindObject('<XWP_WPS>') then
    say '"WorkPlace Shell" object found: clearly an XWP-enhanced system.'
  else do
    say 'No "System" or "XWorkplace" objects found. Did the dog ate them?'
    say 'Unknown type of OS/2 system. Pre-Warp?'
   end
 end

exit

/* FindObject - checks the presence of a specified WPS object
   Argument:  an object ID (in the form <WP_DEKSTOP> f.e.)
   Returns: 1 if the object exists, 0 otherwise
*/
FindObject: procedure
parse arg object
found = 0
if RxFuncQuery('SysIni') then call RxFuncAdd 'SysIni','RexxUtil','SysIni'
call SysIni 'USER','PM_Workplace:Location','ALL:','ids'
do i=1 to ids.0
  found = (object = ids.i)
  if found then
    leave
 end
return found
« Last Edit: April 02, 2026, 11:51:11 am by Alfredo Fernández Díaz »

Jan-Erik Lärka

  • Global Moderator
  • Sr. Member
  • *****
  • Posts: 378
  • Karma: +15/-0
Re: REXX tutorials -- start developing applets under ArcaOS out-of-the-box
« Reply #32 on: April 03, 2026, 09:17:38 am »
One can do some other interesting stuff as well.
While I designed the download tool for Dosbox-X/2 and the free DOS game Red Alert I found out that it's possible to recover code from DrDialog applications along side the resources.
Ok, we've known that for a long time, but how to calculate the length of each resource and extract them. A problem with resmgr/rdc is the extra bytes added that cause certain files to be unusable.

Anyway I reduced the code to extract images from the executable itself (specifically as icons) down to:

GetRes: PROCEDURE
    PARSE SOURCE . . this                      /* where is this executable located */
    CALL STREAM this, 'C', 'OPEN READ'   /* open a stream to read itself while running */
    C = 0                                               /* keep track of read position */
    CALL SKP 60                                     /* skip over 60 characters (x 8 bit) from start */
    lxofs = L2D(SKP(4))                          /* find the offset for the exe lx data */
    CALL SKP lxofs + 80 - C                    /* skip over data we're not looking for */
    jmp = L2D(SKP(4)); jps = L2D(SKP(4)) /* find out resource position information and number of resources */
    CALL SEEK 1 + lxofs + jmp               /* reposition to location with info about resources */
    DO i = 1 TO jps                               /* go through the resources */
        CALL SKP 4                                 /* skip, just usable info we're not interested in here */
        cb.i = L2D(SKP(4))                      /* get resource start */
        CALL SKP 2                                 /* skip, just more usable info we're not interested in here */
        of.i = L2D(SKP(4))                      /* get resource length */
    END
    DO i = 1 TO 8                                 /* The 8 icons (placed first with resources) */
        CALL SEEK 135681 + of.i             /* skip over the actual drrex.exe code (135681) */
        CALL CHAROUT 'IMG'||( i + 990 )||'.ICO', CHARIN( this,, cb.i ) /* read resource data and write it out with a generic file name */
        CALL STREAM 'IMG'||( i + 990 )||'.ICO', 'C', 'CLOSE' /* close the file afterward */
    END
    CALL STREAM this, 'C', 'CLOSE'         /* and close the file we're reading from, this executable running */
RETURN 0

L2D: RETURN C2D( REVERSE( ARG(1) ) ) /* Helper function, C2D convert character to numeric value, while data must be read backwards */
SKP: C = C + ARG(1); RETURN CHARIN( this,, ARG(1) ) /* keep track of read position and reposition/skip relative read position */
SEEK: C = ARG(1) - 1; RETURN STREAM( this, 'C', 'SEEK ='||ARG(1) ) /* move read position to absolute position */

Tame/2, Inieditor and other software written with DrDialog where source code may only exist in the executable may be recovered if one want to rewrite/imporve/fix them.

Alfredo Fernández Díaz

  • Full Member
  • ***
  • Posts: 108
  • Karma: +7/-0
Re: REXX tutorials -- start developing applets under ArcaOS out-of-the-box
« Reply #33 on: April 22, 2026, 03:08:09 am »
Recovering REXX code from DrDialog applications? You definitely have a penchant for mining useful stuff from interesting places : )

OK, we can try, but I don't think any code for that is really interesting in itself as an example to other people trying to code their first REXX applets, so we better get this thread back to the tutorial stuff, and open a new thread instead. Give me a couple of days to have a look at the question.

Anton Monroe

  • Newbie
  • *
  • Posts: 27
  • Karma: +3/-0
Re: REXX tutorials -- start developing applets under ArcaOS out-of-the-box
« Reply #34 on: April 23, 2026, 01:39:03 am »
Alfredo,

Attached are some Rexx scripts I wrote for my own purposes. Maybe some of them
would fit what you have in mind, or could be adapted for a tutorial.

pstat.cmd is a front end for pstat.exe that makes the output more readable.
Uses several variations of the 'parse var' command.

pstree.cmd and pidof.cmd were inspired by Linux commands of the same names.
pstree.cmd shows a tree diagram of all running processes. pidof.cmd returns the
process ID(s) of a specified process. Uses the RxQProcStatus function from
RXU.DLL.

EAset.cmd adds an extended attribute to a file. It is only for EAs that consist
of text (type ASCII, like .subject or .longname). EAfind.cmd searches for files
that have the specified EA(s). EAremove.cmd removes specified EA(s). EAlist.cmd
lists EAs that files have. They show the use of SysGetEA/SysPutEA/SysQueryEAList.

A tedious part of writing a script is parsing the command line switches, so
I wrote a generic GetOpts routine that will automatically generate a set of
variables. So reading the command line can be done simply with
    parse arg args
    interpret GetOpts(args)
ShowGetOpts.cmd is a demo for it. In spite of the name, it is not much like
the Unix getopts.

WhatWPIpkg.cmd determines which WarpIn package a given file came from, by
querying the WarpIn ini file with SysIni(). Unfortunately I didn't comment it
as well as I should have and I no longer remember how SysIni works.  Maybe
someone who knows SysIni() can add better comments. It is normally called from
a 4OS2 batchfile that identifies other packages also, but the WarpIn part is
the only part that needed Rexx.

wordwrap.cmd indents text and wraps long lines. As in
    type old.txt | wordwrap.cmd > new.txt



OS2World has a Rexx collection on Github that might be a source. A couple of
years ago I downloaded a bunch of files from it and they all came through with
Unix line-endings. I wonder if that has been fixed.

Dave Yeo

  • Hero Member
  • *****
  • Posts: 6052
  • Karma: +167/-1
Re: REXX tutorials -- start developing applets under ArcaOS out-of-the-box
« Reply #35 on: April 23, 2026, 02:31:21 am »
OS2World has a Rexx collection on Github that might be a source. A couple of
years ago I downloaded a bunch of files from it and they all came through with
Unix line-endings. I wonder if that has been fixed.

The trick with git is to have a .gitattribute file in the root of the repository containing something like,
Code: [Select]
# CRLF is crucial for OS/2 REXX files (also fine for Windows .cmd)
*.cmd eol=crlf

Anton Monroe

  • Newbie
  • *
  • Posts: 27
  • Karma: +3/-0
This might fit into a Rexx tutorial somewhere:


An external procedure is convenient because it is one file that can be called from any script you write. But it is slow, because Rexx must search the %path to find it, then read it from the disk, then tokenize it. You can speed things up by using a pre-tokenized or "compiled" library of external procedures that are kept in memory. Those are called macros.

To make a new macro procedure called Foo()--
    * the Foo() procedure must be in a separate file called 'Foo.cmd'
    * unlike an external procedure, the file does not need to be in your %path
    * like an external procedure, it must =not= use 'procedure' after the
       Foo: label. The file can contain private procedures using 'procedure'
    * like an external procedure, the procedure cannot share variables; it only
      sees its arguments and the caller only sees the value it returns.

The attached zip file contains a few sample macro files, and a script called makerexxlib.cmd to compile them. Make a directory for your macros and copy the macro files into it. Put makerexxlib.cmd somewhere in your %path, not in the macro directory.

You will need to edit makerexxlib.cmd to set the macro directory and the name of the library. Then run it.

To use the macro library, add a line like this to your Rexx script

    call SysLoadRexxMacroSpace 'C:\rexx\rexx.macro'

but using the real library name that you chose

Makerexxlib.cmd does not compile the macros directly into a file; it works by adding each macro to the "macrospace" and then saves the macrospace to a file.  Like other Rexx libraries, the macrospace is available to all sessions and stays in memory until removed.  So you would not want to change the macrospace while a Rexx script in another session is using it.

Whenever you edit or add a macro file, update the library by running makerexxlib.cmd again.

If any macro file cannot be tokenized, makerexxlib.cmd will clear the macrospace and restore it from the old version of the library, if there is one.  A tokenizing error usually indicates something like an unbalanced comment or quoted string. Successful tokenizing does not mean there are no mistakes in the file-- most Rexx errors will not be detected until the code is executed.

I have seen conflicting information about whether you can load more than one macro library using multiple calls to SysLoadRexxMacroSpace. I'm not sure why you would need to do that anyway.

In Rexx Tips and Tricks (filename RXTT36.inf) is a discussion about measuring the overhead of procedure calls. Briefly, internal procedures are fastest, external procedures are slowest, and compiled macros are a reasonable compromise between speed and convenience. So if speed is really important, you might want to copy your macro procedure into your main script, making it an internal procedure. An internal procedure will take precedence over a macro of the same name.

makerexxlib.cmd adds macros using SysAddRexxMacro(). RexxUtil.inf describes the optional third argument to it by saying that it determines the "position in the macrospace search order where the function is to be added, relative to other functions in the macrospace", which is not clear. The explanation in RXTT36.inf suggests that it should be "relative to non-macro external procedures". That is, before or after looking for an external procedure of that name. Makerexxlib.cmd uses the default 'before'. There may be some reason for using 'after' but I don't know what it would be.

You might also look at RXTT36.inf's description of LoadMac.cmd which has routines for managing macrospace. I find it hard to understand.


Anton Monroe

  • Newbie
  • *
  • Posts: 27
  • Karma: +3/-0
When I started learning Rexx I wanted a convenient reference to all the operators. I couldn't find one so I started my own. I'm attaching the plain-text version of it because I may not get the formatting quite right when I paste it into this message.




    \   reverses the True/False value of whatever comes after it
        0   is False
        1   is True
        \1  is 0
        \0  is 1
        some languages use 0 for True and any number greater than 0 for False
        some languages use -1 for True and 0 for False
        and even many Rexx functions reverse the Rexx logic and return 0 for success

        ¬ (decimal 170 , hex AA) is the same as \

        the \ may have a space after it:
            \ 0 is the same as \0


    Comparison operators:
        compare numbers as their value
        compare strings as if they were stripped of space characters
        The way I think of it is that Rexx compares the "meaning" of the symbols, not their literal value

        when comparing strings, the comparison uses ascii order, not alphabetical order,
        which means it is case-sensitive

        1.0 = 1             is True
        "abc" = " abc "     is True
        "abc" = "ABC"       is False
        " " > "0A"x         is True

        =     equal to
        <     less than
        >     greater than
        <=    less than or equal
        >=    greater than or equal
        <>    greater or less, ie, not equal
        \=    not equal
        \>    not greater
        \<    not less

        =< and =>   can NOT be used for  <= and >=
                    to remember the correct form, think as in English: "less than or equal"


    Strict comparison operators:
        a simple way to think of them is that they consider everything as a string,
        and spaces are not stripped:

        1.0  == 1            is False
        1.23 == '1.23'       is True
        "abc" == " abc "     is False

        But remember that numbers and expressions are evaluated before making the comparison:

        -1 == - 1            is True   because both sides evaluate to -1
        (0 - 1) == -1        is True
        (0 - 1) == '-1'      is True
        (0 - 1) == '- 1'     is False
        (0 - 1.0) == - 1.0   is True   because both sides evaluate to -1.0
        (0 - 1.0) == -1      is False  because (0 - 1.0) evaluates to -1.0

        ==     strictly equal to
        <<     strictly less than
        >>     strictly greater than
        <<=    strictly less or equal
        >>=    strictly greater or equal
        \==    not strictly equal
        \>>    not strictly greater
        \<<    not strictly less


    Boolean (logical) operators:

        &      AND   both expressions are True
        |      OR    one or both expressions are True
        &&     XOR   one expression is True, but not both
                     (&& seems to be missing from the OS/2 Rexx documentation)

        (1=1) & (2=2)       is True
        (1=1) | (2=2)       is True
        (1=1) && (2=2)      is False


    Math operators:
        +   addition
        -   subtraction
        *   multiplication
        /   division
        //  division, returns only the remainder
        %   division, returns a whole number, discarding the remainder
        **  exponential (power)

        Example:
        say 'elapsed time:' sec / 60 'minutes'                        yields "2.08333333 minutes"
        say 'elapsed time:' sec % 60 'minutes' sec // 60 'seconds'    yields "2 minutes 5 seconds"


    Signs:
        - and + can also indicate negative and positive numbers
        spaces are not significant:

        say - 1             yields -1
        say 0 - -1          yields 1
        say 0-+1            yields -1


    Concatenation operator:
        || joins two strings

        say 'abc'||'def'        yields "abcdef"
        say 'abc' || 'def'      yields "abcdef"; the spaces are not significant

        but you can also join strings by juxtaposition, without the || operator:
        say 'abc' 'def'         yields "abc def" ; the space is needed
        say 'abc'     'def'     yields "abc def" ; but only one space is significant!
        say 'abc'"def"          yields "abcdef"  ; two strings without a space
        say 'abc''def'          yields "abc'def" ; because Rexx does not interpret that as two strings.
                                                   It is one string containing an escaped single quote
        say 'abc',
        'def'                   yields "abc def" ; even though there is no space. That's odd.
                                                   The continuation comma prevents the adjacent single
                                                   quotes from being interpreted as an escaped quote.

        Conclusion:
        Juxtaposition is more convenient when you want to insert spaces, because
            say varA varB
        is easier to type than
            say varA || ' ' || varB
        but if in doubt, use the || operator




Anton Monroe

  • Newbie
  • *
  • Posts: 27
  • Karma: +3/-0
about SIGL:

Whenever an internal routine is called, the built-in variable SIGL is set to the line it was called from, But SIGL is not a global variable.  If a procedure is called, SIGL is set in the =calling= environment, not in the procedure's local environment (which is the only place where it would be very useful). So if your procedure needs to know where it was called from, you must expose SIGL:

              ProcName: procedure expose SIGL

But do not think of SIGL as "the line where the current procedure was called from". It is the line where the most recent internal CALL or function() happened. It frequently changes.  If you want to know where your procedure was called from, you might want to save the value of SIGL to a local variable right away. For example:

/**/
say 'starting sigl is' sigl', which means it has not yet been initialized'
call ProcA
return

ProcA: procedure expose sigl
orig_sigl = sigl
call ProcB
say 'ProcA was called from line' orig_sigl', which is'
say '   'sourceline(orig_sigl)
say 'the current value of sigl is' sigl
say 'so the last subroutine call was at line' sigl', which is'
say '   'sourceline(sigl)
return

ProcB: procedure
nop
return



SIGL can be useful when combined with sourceline() for error messages, like in a NoValue or error handler. You can also use it to identify the current line, as in

/**/
say 'now executing line' LineNo()
return

LineNo:
return sigl


Anton Monroe

  • Newbie
  • *
  • Posts: 27
  • Karma: +3/-0
Re: REXX tutorials -- start developing applets under ArcaOS out-of-the-box
« Reply #39 on: August 05, 2026, 06:59:12 am »
This might fit into a tutorial.

About conditionals:

IF <expression> THEN <statement 1> ELSE <statement 2>
means  "test whether <expression> evaluates to 1 (True)",
       "if it is True then execute <statement 1>"
       "if it is False then execute <statement 2>"

ELSE
does not need THEN, because unlike IF, ELSE has no <expression> to test. It is a fall-through for times when all the tests before it fail. Unlike OTHERWISE, ELSE =does not= imply DO; it only executes one command.

SELECT...END
is for when you want to do only one of several actions. It marks the start of a series of tests each introduced by WHEN. The tests are performed in order and the first one that tests True is executed. All remaining tests are then skipped. If all tests fail, the statements under OTHERWISE are executed.

WHEN <expression> THEN <statement>"
means  "test whether <expression> evaluates to 1 (True)"; "if it is True then execute <statement>"
if you want to execute several statements, you must surround them with DO...END

OTHERWISE
does not need THEN, because unlike WHEN, it has no <expression> to test, It is a fall-through for times when all the tests before it fail.

unlike ELSE, it implies DO. Therefore it does not execute just one statement, it executes all statements from OTHERWISE until the END that closes the SELECT block. (and if you use "OTHERWISE DO", you must add another END) It isn't quite consistent, but that is how Rexx works.

A SELECT block does not necessarily need an OTHERWISE, so long as your WHENs account for all the possibilities. But if all the WHENs test False and there is no OTHERWISE, Rexx will report an error.

Note that THEN executes the next Rexx statement
Only a single statement
Not the next line
Not multiple statements after THEN, even if they are all on the same line as THEN
A comment is not a statement
A blank line is not a statement
So this will execute SAY 'Yes'
Code: [Select]
        IF 1 == 1 THEN
            /*  comment     */


            SAY 'Yes'
In fact, a null statement is not a statement. This will execute SAY 'Yes'
Code: [Select]
    IF 1 == 1 THEN ;;;;; SAY 'Yes' the single statement may be "DO" which will execute everything until "END"
the single statement may be "CALL Subroutine"

ELSE IF
is a construction you might see, but it doesn't really exist in Rexx. That is, Rexx does not have a concept like the 'elseiff' in 4OS2 batch files or 'elif' in Unix shell scripts. The Rexx documentation says "ELSE binds to the nearest IF at the same level". In other words, each IF can have only one ELSE. In other other words, IF is not a multiple-choice test.

A series of ELSE IF's can often do what you want; the only real problem is that it can cause new Rexx users to misunderstand how Rexx works and can lead to errors. Here is a example. It does not do what the indenting implies:
Code: [Select]
           a = 3
           b = 2
           if a == 0 then
              say 'a is 0'
           else if a == 1 then
              say 'a is 1'
           else if a == 2 then
              if b == 2 then
                 say 'a is 2, b is 2'
           else if a == 3 then
              say 'a is 3'            /* this line can never execute */
           else
              say 'a > 3'

inserting that nested IF that says "if b == 2 then" broke the next test, because "else if a == 3 then" now binds to the nested if. If you split the ELSE IFs and correct the indenting it is easier to see what is really happening:
Code: [Select]
          a = 3
          b = 2
          if a == 0 then
             say 'a is 0'
          else
             if a == 1 then
                say 'a is 1'
             else
                if a == 2 then
                   if b == 2 then
                      say 'a is 2, b is 2'
                   else
                      if a == 3 then
                         say 'a is 3'        /* this line can never execute */
                      else
                         say 'a > 3'
This is the kind of thing that the SELECT construction is intended for. Replace the ELSE IFs with WHEN and ELSE with OTHERWISE. Or you could fix it by inserting "else nop" after the nested IF test. I admit, ELSE IF is a pet peeve of mine.