DEV Community

Tomasz Wegrzanowski
Tomasz Wegrzanowski

Posted on

100 Languages Speedrun: Episode 98: Rexx

Rexx is a language from the olden days, vaguely similar to Python, or Tcl, but doing a lot of things in a weird way because back then people didn't know better.

Rexx saw modest use back in the 1980s, and while it never got big, it still survives somewhat in the IBM mainframe world. You can install an Open Source Rexx interpreter with brew install regina-rexx.

Hello, World!

I'm a bit surprised it uses say for print, I thought it's a new thing Raku did, but apparently it has much deeper roots.

#!/usr/bin/env rexx say "Hello, World!" 
Enter fullscreen mode Exit fullscreen mode
$ ./hello.rexx Hello, World! 
Enter fullscreen mode Exit fullscreen mode

Variables

Rexx is case-insensitive. Variables by default contain their name, in upper case, so variable world starts with WORLD.

#!/usr/bin/env rexx a = 40 b = 19 say a + b * 20 say A B C say "Hello, " || world || "!" 
Enter fullscreen mode Exit fullscreen mode
./variables.rexx 420 40 19 C Hello, WORLD! 
Enter fullscreen mode Exit fullscreen mode

This is definitely not the direction where the world of programming ended up going. || is string concatenation.

Types

Everything is a string. If we use a string in numeric context, and it looks like a number, it's treated as a number. Otherwise, it raises exception:

#!/usr/bin/env rexx say "2" + "67" say "hello" + "world" 
Enter fullscreen mode Exit fullscreen mode
$ ./types.rexx 69 4 +++ say "hello" + "world" Error 41 running "types.rexx", line 4: Bad arithmetic conversion 
Enter fullscreen mode Exit fullscreen mode

FizzBuzz

In a baffling reversal of the usual convention, / is float division, % is integer division, and // is modulo.

#!/usr/bin/env rexx do i = 1 to 100 if i // 15 == 0 then say "FizzBuzz" else if i // 5 == 0 then say "Buzz" else if i // 3 == 0 then say "Fizz" else say i end 
Enter fullscreen mode Exit fullscreen mode

Fibonacci

Let's do an easy Fibonacci sequence:

#!/usr/bin/env rexx do i = 1 to 20 say "fib(" || i || ")=" || fib(i) end fib: parse arg n if n <= 2 then return 1 else return fib(n - 1) + fib(n - 2) 
Enter fullscreen mode Exit fullscreen mode

Something's not right here...

$ ./fib.rexx fib(1)=1 fib(2)=1 fib(3)=2 fib(4)=3 fib(5)=4 fib(6)=5 fib(7)=6 fib(8)=7 fib(9)=8 fib(10)=9 fib(11)=10 fib(12)=11 fib(13)=12 fib(14)=13 fib(15)=14 fib(16)=15 fib(17)=16 fib(18)=17 fib(19)=18 fib(20)=19 
Enter fullscreen mode Exit fullscreen mode

Rexx by default makes everything global, so that n is getting overwritten by recursive calls, and crazy things happen.

Let's do a second try:

$ ./fib2.rexx fib(1)=1 fib(2)=1 fib(3)=2 fib(4)=3 fib(5)=5 fib(6)=8 fib(7)=13 fib(8)=21 fib(9)=34 fib(10)=55 fib(11)=89 fib(12)=144 fib(13)=233 fib(14)=377 fib(15)=610 fib(16)=987 fib(17)=1597 fib(18)=2584 fib(19)=4181 fib(20)=6765 7 +++ procedure Error 17 running "fib2.rexx", line 7: Unexpected PROCEDURE Error 17.1: PROCEDURE is valid only when it is the first instruction executed after an internal CALL or function invocation 
Enter fullscreen mode Exit fullscreen mode

That was closer, but why did it suddenly crash? Well, what if we add an exit?

#!/usr/bin/env rexx do i = 1 to 20 say "fib(" || i || ")=" || fib(i) end exit fib: procedure parse arg n if n <= 2 then return 1 else return fib(n - 1) + fib(n - 2) 
Enter fullscreen mode Exit fullscreen mode
$ ./fib3.rexx fib(1)=1 fib(2)=1 fib(3)=2 fib(4)=3 fib(5)=5 fib(6)=8 fib(7)=13 fib(8)=21 fib(9)=34 fib(10)=55 fib(11)=89 fib(12)=144 fib(13)=233 fib(14)=377 fib(15)=610 fib(16)=987 fib(17)=1597 fib(18)=2584 fib(19)=4181 fib(20)=6765 
Enter fullscreen mode Exit fullscreen mode

Now it works. There's also a few different versions of parse arg with somewhat different semantics.

Of course, we can work with all this, but it's annoying such a basic thing is already causing issues.

Arrays and Hashes

There aren't any. But we get a weird alternative instead - composite variables.

Let's try to implement very simple records this way:

#!/usr/bin/env rexx a.name = "Alice" a.surname = "Smith" a.age = 25 b.name = "Bob" b.surname = "Nilson" b.age = 30 c.name = "Charlie" call print_person a call print_person "B" call print_person c exit print_person: person = arg(1) name_ = value(person || ".name") surname_ = value(person || ".surname") age_ = value(person || ".age") say name_ || " " || surname_ || " is " || age_ || " years old" 
Enter fullscreen mode Exit fullscreen mode
$ ./person.rexx Alice Smith is 25 years old Bob Nilson is 30 years old Charlie C.SURNAME is C.AGE years old 
Enter fullscreen mode Exit fullscreen mode

There's a lot going on here:

  • there's separate syntax for function calls which must return values (foo(a,b)) - and procedure calls which don't (call foo a b)
  • we cannot actually pass composite variable as argument, what we're passing is their names A, B, C
  • value of a (by default A) is completely unrelated to what's in a.name etc.
  • call print_person b is just call print_person "B" - as that's the default value
  • we cannot mark print_person as procedure, because it needs full access to variables in the caller
  • to get actual values we need value or interpret - that is essentially an eval
  • default fields are uppercase of the whole thing so Charlie's default surname is C.SURNAME not even just SURNAME
  • because print_person doesn't have its own local variables (name_ etc. are all global) we need to be really careful here

Compared to languages which came after, this is all unbelievably primitive. I don't know if any other language tries to take this "composite variables" concept somewhere good, this definitely isn't it.

Composite Arrays

OK, so given all the limitations of composite arrays, how do we even do anything with them?

The first problem is that we cannot even know the length, and we cannot iterate to see when array ends as accessing beyond its end will give us prefilled ARRAY.7, ARRAY.8 etc.

So by convention we put the length of the array in ARRAY.0, and then its elements in ARRAY.1, ARRAY.2, etc. You can program this way, but it is quite ugly:

#!/usr/bin/env rexx a.0 = 20 do i = 1 to 20 a.i = 2 * i end b.0 = 3 do i = 1 to 3 b.i = 21 + i end say sum(a) say sum(b) exit sum: array_name = arg(1) array_size = value(array_name || ".0") result = 0 do i = 1 to array_size result = result + value(array_name || "." || i) end return result 
Enter fullscreen mode Exit fullscreen mode
$ ./sum.rexx 420 69 
Enter fullscreen mode Exit fullscreen mode

Should you use Rexx?

Not unless you're a time traveler who needs to go back to the 1980s to code on an IBM mainframe. Even then, I'm not sure it was such a great choice.

Rexx seems like a dead-end language. I don't think it had any influence on the languages which followed it.

Code

All code examples for the series will be in this repository.

Code for the Rexx episode is available here.

Top comments (1)

Collapse
 
robole profile image
Rob OLeary • Edited

As a junior dev, I was tasked with working with a mainframe codebase where I needed to write some code to process some data and I could choose the language to use. The choices were: a COBOL-variant, JCL, Eastytrieve, and Rexx; Rexx was the easiest to use. I am glad that I do not have to use any of those now, but given the circumstances, it was better than I anticipated