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