/*************************************************************************** Stems as mock data structures in REXX ============================================ An experimental "Tutorial as Code" (c) 2021 Carl Svensson | www.datagubbe.se Tested in ARexx 1.15, Regina REXX 3.6, BRexx 2.1.9 The REXX language was invented at IBM by Mike Cowlishaw and has since been ported from IBM's mainframes to many various platforms, including DOS and OS/2. In 1987 it was ported to the Amiga range of computers (as ARexx) by William S. Hawes. Today, REXX is available on a multitude of platforms thanks to the portable interpreters Regina and BRexx. REXX itself has no particular data structures as supplied in modern scripting languages such as JavaScript and Python. It provides variables and a single stack, but by using what could be described as "syntactic sugar" for manipulating the internal symbol table, it enables something called stems and compound variables. The aim of this tutorial is not to introduce the basics of the REXX language, but rather to explain a few concepts that are, AFAIK, unique to the REXX family of languages. Even though the tutorial has a stupendously pretentious name, the hope is that it will be instructive enough to explain how combining stems, subroutines and evaluation of strings as code can substitute the need for many other language constructs that are not immediately available in REXX. This tutorial comes with ABSOLUTELY NO WARRANTY and is licensed under the CC BY-NC-ND 4.0 license. For details, see https://creativecommons.org/licenses/by-nc-nd/4.0/ ***************************************************************************/ /* ====================================================================== */ /* Variables and the symbol table. */ /* ====================================================================== */ say foobar /* An uninitialized symbol will evaluate */ /* to an uppercase string. This will print */ /* FOOBAR, as will henceforth be denoted */ /* by the arrow "--->" in this tutorial: */ /* ---> FOOBAR */ foobar = 42 /* Initialize the variable foobar to */ /* create an entry in the symbol table. */ say foobar /* ---> 42 */ drop foobar /* Remove the variable from the */ /* symbol table. */ say foobar /* ---> FOOBAR */ /* ====================================================================== */ /* Stems, compounds and the symbol table. */ /* ====================================================================== */ mystem. = 31337 /* The stem mystem. is initialized */ /* with the default value 31337. */ /* Stems are not like lists or arrays, but */ /* rather syntactic sugar for working with */ /* the REXX symbol table - I.E., declaring */ /* variables. */ /* As such, they can be used to implement */ /* or at least approximate the behavior of */ /* common structures in other languages. */ say mystem. /* ---> 31337 */ /* The value of mystem. itself is its */ /* default value. */ say mystem.1 /* ---> 31337 */ /* The compound variable mystem.1 */ /* is now initialized to the stem's */ /* default value. The stem's name is */ /* mystem. and the 'index', 1, is called */ /* a 'tail' in REXX. */ mystem.2 = 3 /* Initialize compound variable mystem.2 */ /* with the value 3. All new compounds */ /* get new individual entries in REXX's */ /* symbol table. */ say mystem.2 /* ---> 3 */ say mystem.1 + mystem.2 /* ---> 31340 */ drop mystem.1 /* Drop the compound mystem.1 */ /* from the symbol table. */ say mystem.1 /* In ARexx and Regina REXX, this will */ /* result in a completely dropped symbol, */ /* which will evaluate to an uppercase */ /* string: */ /* ---> MYSTEM.1 */ /* In BRexx it will result in reverting to */ /* the stem's default value: */ /* ---> 31337 */ /* This tutorial remains agnostic as to */ /* which behavior is "correct". */ mystem.foo = "Hello" /* Compounds can be of any type */ /* both in name and value, regardless */ /* of the stem default. We can write */ /* foo in lowercase for convenience, */ /* but resolution will really be in */ /* uppercase. */ say mystem.foo /* ---> Hello */ tail = "FOO" /* Initialize a normal string variable */ /* intended for tail substitution. */ /* Note the uppercase! */ say mystem.tail /* ---> Hello */ /* REXX resolves the value of the */ /* tail before resolving the compound, */ /* meaning we can dynamically access */ /* arbitrary tails using a variable. */ /* This is called tail substitution. */ tail = foo /* Since uninitialized symbols evaluate */ /* to uppercase strings, we can assign an */ /* unquoted value to a variable we want to */ /* use for tail substitution. */ say tail /* ---> FOO */ say mystem.tail /* ---> Hello */ mystem.bar = 12 /* Here we create a clash: */ bar = "FOO" /* The tail BAR contains the value 12, */ /* but we assign the value FOO to the */ /* variable BAR. */ say mystem.bar /* ---> Hello */ /* REXX tail substitutes BAR with 'FOO', */ /* giving us the value for MYSTEM.FOO. */ drop bar /* Let's remove BAR from the symbol table. */ say mystem.bar /* ---> 12 */ /* Since we dropped BAR, MYSTEM.BAR now */ /* resolves to the intended value. */ newstem. = mystem. /* It's not possible to create a copy of */ /* or reference to a stem by assignment. */ /* In this assignment, we simply create a */ /* newstem. stem with the default value of */ /* 31337, since that's the actual value of */ /* mystem. */ say newstem.foo /* ---> 31337 */ newstem.foo = "Baz!" /* This will only affect newstem. */ say newstem.foo /* ---> Baz! */ say mystem.foo /* ---> Hello */ newstem.foo = mystem.foo /* All values we wish to retain must be */ /* copied explicitly. Remember, a compound */ /* will get a unique entry in the symbol */ /* table! */ say newstem.foo /* --> Hello */ x = 31; y = 13; z = 73 /* Tails can be chained to create multi- */ coords.x.y.z = 23 /* dimensional stems. */ say coords.x.y.z /* ---> 23 */ say coords.31.13.73 /* ---> 23 */ array.0 = 0 /* A common pattern in REXX is to store */ /* the number of stem elements in tail 0. */ do i = 1 to 5 /* When populating a stem intended for use */ array.i = "I am "i"!" /* as an array, we must keep track of the */ array.0 = i /* <=== element count */ end /* ourselves. */ say array.0 /* ---> 5 */ do i = 1 to array.0 say array.i /* ---> I am 1! ... I am 5! */ end /* ====================================================================== */ /* Interpret. */ /* ====================================================================== */ interpret "say 'Hello!'" /* ---> Hello! */ /* INTERPRET allows for the interpretation */ /* of an arbitrary string as REXX code, */ /* much like eval() in JavaScript. */ interpret "say array.1" /* ---> I am 1! */ /* The global scope is accessible. */ interpret "mystem.baz = 10" /* The global scope can be manipulated. */ say mystem.baz /* ---> 10 */ anum = 1 /* As evident from these lines and the ones */ interpret "bnum = 2" /* just above, we can manipulate both */ /* normal and compound variables. */ interpret "say anum + bnum" /* ---> 3 */ say bnum /* ---> 2 */ stem = "mystem" /* We can also use INTERPRET to dynamically */ tail = "foo" /* access compounds based on the values of */ /* variables: */ interpret "say "stem"."tail /* ---> Hello */ /* ====================================================================== */ /* Labels and procedures. */ /* ====================================================================== */ my_label: /* A label. Without any special control */ /* flow, this will simply be a no-op and */ /* the code following it will execute */ /* normally, just like a label in C. */ say "Below my_label." /* ---> Below my_label. */ call skip_here /* Here we call the label skip_here, as */ /* declared below. */ say "Between call and label." /* This line will not be executed. */ skip_here: /* The label called above. */ say "We are now here!" /* ---> We are now here! */ call subroutine /* Call the label "subroutine" defined */ /* below. */ /* ---> I can access mystem: */ /* Hello */ call myproc /* Call the label "myproc" defined below. */ /* ---> I can access array: */ /* I am 1! */ /* But not mystem: */ /* MYSTEM.FOO */ say myfunc("a string") /* Invoke the label "myfunc" defined below */ /* as a function. */ /* ---> I received a string */ idx = arrayfunc("New item") /* Invoke the function "arrayfunc" defined */ /* below and store the return value. */ say array.idx /* ---> New item */ idx = arrpush(array, "Pushed") /* Invoke the function "arrpush" defined */ /* below. */ /* We use the fact that REXX will treat */ /* uninitialized variables as uppercase */ /* strings to mock the use of a stem as a */ /* function argument. */ say array.idx /* ---> Pushed */ call arrcopy array, new_array /* Call the label "arrcopy" defined */ /* below, to copy the contents of array to */ /* new_array. Note that there's no need to */ /* first declare the target stem, it will */ /* be initialized by our function if it */ /* doesn't exist. */ do i = 1 to new_array.0 /* Here we loop over the contents of our */ say "New: "new_array.i /* new_array stem and print the values. */ end /* ---> New: I am 1! */ /* New: I am 2! */ /* New: I am 3! */ /* New: I am 4! */ /* New: I am 5! */ /* New: New item */ /* New: Pushed */ exit /* Labels can also be used to declare */ /* subroutines and procedures, but since */ /* we don't want to execute them within */ /* the normal program flow, we must EXIT */ /* the program flow before declaring them, */ /* or the interpreter will execute them */ /* just like with "my_label" above. */ /* ====================================================================== */ /* subroutine: A label as a subroutine */ /* ====================================================================== */ subroutine: /* This label can be described as a */ /* subroutine and will have access to the */ /* full symbol scope of the script. */ say "I can access mystem:" say mystem.foo return /* Return to the call statement */ /* ====================================================================== */ /* myproc: A locally scoped procedure */ /* ====================================================================== */ myproc: /* This label will be treated as a */ /* procedure, a subroutine with a local */ /* scope that can be augmented using the */ /* EXPOSE keyword, to expose arbitrary */ /* values from the global scope. */ procedure expose array. /* PROCEDURE tells REXX that we want a */ /* local scope. EXPOSE is used to "expose" */ /* desired values from the global scope, */ /* which lets us access them from our */ /* local scope. */ say "I can access array:" say array.1 /* ---> I am 1! */ say "But not mystem:" say mystem.foo /* ---> MYSTEM.FOO */ return /* ====================================================================== */ /* myfunc: Returning a value */ /* ====================================================================== */ myfunc: /* To create a function, we can accept */ /* arguments using PARSE ARG and return */ /* a value to the caller. */ parse arg str_a new_str = "I received "str_a return new_str /* ====================================================================== */ /* arrayfunc: Add a value to a stem and return the new item count */ /* ====================================================================== */ arrayfunc: /* Let's create a locally scoped function. */ procedure expose array. /* Because stems are a kind of sugar for */ /* working with the symbol table, we */ /* cannot pass a stem as an argument to a */ /* function. To be able to work with it, */ /* we must access it in the global symbol */ /* table just like any other variable. */ parse arg new_val /* Accept one argument */ new_count = array.0 + 1 /* Calculate our new item count. */ array.new_count = new_val /* Add our new value to the array. */ array.0 = new_count /* Update the item count. */ return new_count /* Return the new item count. */ /* ====================================================================== */ /* arrpush: Pushing an item to a stem-as-array */ /* ====================================================================== */ arrpush: /* Now things are getting interesting! */ /* In this function with access to the */ /* global scope, we accept the name of */ /* an arbitrary stem intended for use as */ /* an array, and push values onto it using */ /* the INTERPRET instruction. */ parse arg stem_name, push_val /* Our arguments: the stem's name and */ /* the value to push. */ interpret stem_name".0 = "stem_name".0 + 1" /* Assume the item count */ /* is in tail 0 and */ /* increment it. */ interpret "new_count = "stem_name".0" /* Store the new item count in a */ /* variable for use below. */ interpret stem_name"."new_count" = '"push_val"'" /* Assign a value to */ /* the new item. */ return new_count /* Return the new item count. */ /* ====================================================================== */ /* arrcopy: Create a copy of a stem-as-array */ /* ====================================================================== */ arrcopy: /* To copy an arbitrary array stem, we */ /* create a subroutine that accepts two */ /* stem names, the source and the target. */ parse arg arr_source, arr_target /* Our args, source and target. */ interpret "drop "arr_target"." /* Drop the target to ensure */ /* it's empty. If it doesn't */ /* exist, this will not */ /* cause an error. */ interpret "source_count = "arr_source".0" /* Assume the item count is */ /* in tail 0 and store it in */ /* a variable for use below. */ do i = 1 to source_count /* Loop over the source. */ interpret arr_target"."i" = "arr_source"."i /* Copy the value from */ /* source to target. */ end interpret arr_target".0 = "source_count /* Set the target's item */ /* counter. */ return /* ========================== End of tutorial. ========================== */