This is still the header! Main site

AS/400: terminals

This is post no. 99 for Kev Quirk's #100DaysToOffload challenge. The point is to write many things, not to write good ones. Please adjust quality expectations accordingly :)

... earlier in the AS/400 series of posts: intro, libraries.

Remember how an AS/400 terminal looks like?

screenshot of an as/400 battleship game

screenshot: configuring as/400 libraries to use

One of the interesting things about the platform is how programs actually interact with this. As in... sure, it's a "screen-oriented" terminal, so the contents of the screen will be downloaded to it in one go and then fields edited locally... but... you'd assume that at some point, your program will be printing these lines, right?

So then... look at the source code of that Battleship app! (This one is actually GPL!)

       ctl-opt copyright('This program is free software, you can redistribute +
       it and/or modify it under the terms of the GNU General Public License +
       as published by the Free Software Foundation. See GNU General Public +
       License for detail.    Craig Rutledge      < > ');
       // JCRGMBTL - BattleShip
       /define ControlStatements
       /define ApiErrDS
       /define Dspatr
       /define FunctionKeys
       /define QsnGetCsrAdr
       /define f_GetRandom
       /define f_GetDayName

       dcl-f JCRGMBTLD workstn infds(Infds);
       dcl-ds Infds;
        InfdsFkey char(1) pos(369);

       dcl-s col uns(3);
       dcl-s ForCount uns(3);
       dcl-s HashCol uns(3) dim(51);
       dcl-s HashRow uns(3) dim(51);
       dcl-s row uns(3);
       dcl-s TimesHit2 uns(3);
       dcl-s TimesHit3 uns(3);
       dcl-s TimesHit4 uns(3);

... so... yeah. It's written in RPG. This language started out being used on punch cards, so the fact that you don't have to put certain parts of lines on certain fixed columns is already an improvement. What you're looking at here is... um... OK, we'll figure out that one later. We can find the main loop of the game though!

       scDow = f_GetDayName();
       exsr srSetupUserShips;

 1b    dou 1 = 2;
          exfmt screen2;
          // get cursor Row and Column
          QsnGetCsrAdr(QsnCursorRow: QsnCursorCol: 0: ApiErrDS);
          csrRow = QsnCursorRow;
          cSrCol = QsnCursorCol;

          // F5 = Restart
 2b       if InfdsFkey = f05;
             exsr srSetupUserShips;
 1i          iter;
 2e       endif;
 2b       if InfdsFkey = f03 or InfdsFkey = f12;
 1v          leave;
 2e       endif;

          // Process users attack, then let computer have shot at it!
          exsr srUserAttack;

          // Check and see if ALL enemy ships are sunk
 2b       if UserxHit2 = 9
             and UserxHit3 = 9
             and UserxHit4 = 9
             and UserxHit5 = 9;
             GameOver = 'CONGRATULATIONS! YOU WIN!';
             aGameover = %bitor(Green: RI);
 2x       else;
             exsr srComputerAttack;
 2e       endif;

 1e    enddo;
       *inlr = *on;


First of all: there is no asynchronous web weirdness here. Your game is multiple rounds of ships shooting at each other? Well, you'll write a loop then! Just as I described in that article about Web Push article, there is an actual server process paying attention to this terminal; even though communication is screen by screen, you don't have to save everything in a session and return, just you would need to do with HTTP.

But then... where are all the print statements? And what the hell is that "exfmt screen2;" doing?

Well... as it turns out, we do have something called "screen2":

     A          R SCREEN2                   CSRLOC(CSRROW CSRCOL)
     A            CSRROW         3S 0H
     A            CSRCOL         3S 0H
     A            ATR0101        1A  P
     A            ATR0102        1A  P
     A            ATR0103        1A  P
     A            ATR0104        1A  P
     A            ATR0105        1A  P
     A            ATR0106        1A  P
     A            ATR0107        1A  P
     A            ATR0108        1A  P
     A            ATR0109        1A  P
     A            UDSPATR5       1A  P
     A            AGAMEOVER      1A  P
     A                                  1  3'JCRGMBTL' COLOR(BLU)
     A                                  1 14'BATTLE SHIP!' COLOR(BLU)
     A            SCDOW          9A  O  1 62COLOR(BLU)
     A                                  1 72DATE EDTCDE(Y) COLOR(BLU)
     A                                  2  8'ATTACK              '
     A                                      DSPATR(HI UL)
     A                                  2 42'DEFEND              '
     A                                      DSPATR(HI UL)
     A                                  2 72SYSNAME COLOR(BLU)
     A                                  3  8'1'
     A                                  3 10'2'
     A                                  3 12'3'
     A                                  3 14'4'
     A                                  3 16'5'
     A                                  3 18'6'
     A                                  3 20'7'
     A                                  3 22'8'
     A                                  3 24'9'
     A                                  3 26'0'
     A                                  3 42'1'
     A                                  3 44'2'
     A                                  3 46'3'
     A                                  3 48'4'
     A                                  3 50'5'
     A                                  3 52'6'
     A                                  3 54'7'
     A                                  3 56'8'
     A                                  3 58'9'
     A                                  3 60'0'
     A            R01C01         1A  B  5  8DSPATR(&ATR0101)
     A            R01C02         1A  B  5 10DSPATR(&ATR0102)
     A            R01C03         1A  B  5 12DSPATR(&ATR0103)
     A            B10C08         1A  O 14 56DSPATR(&BTR1008)
     A            B10C09         1A  O 14 58DSPATR(&BTR1009)
     A            B10C10         1A  O 14 60DSPATR(&BTR1010)
     A                                 14 64'0'
     A                                 16  8'Enemy Ship Status' DSPATR(UL HI)
     A                                 16 42'Your Ship Status' DSPATR(UL HI)
     A                                 17  8'Cruiser 2' DSPATR(&EDSPATR2)
     A                                 17 42'Cruiser 2' DSPATR(&UDSPATR2)
     A                                 18  8'Destroyer 3' DSPATR(&EDSPATR3)
     A                                 18 42'Destroyer 3' DSPATR(&UDSPATR3)
     A                                 19  8'BattleShip 4' DSPATR(&EDSPATR4)
     A                                 19 42'BattleShip 4' DSPATR(&UDSPATR4)
     A                                 20  8'Aircraft Carrier 5'
     A                                      DSPATR(&EDSPATR5)
     A                                 20 42'Aircraft Carrier 5'
     A                                      DSPATR(&UDSPATR5)
     A            GAMEOVER      27A    21 20DSPATR(&AGAMEOVER)
     A                                 23  7'Key X, press Enter to Fire!'
     A                                      COLOR(BLU)
     A                                 24  7'F3=Exit' COLOR(BLU)
     A                                 24 41'F5=Restart' COLOR(BLU)     

I did skip a lot of repetitive parts. But... yes, this is a screen. A template.

It has row and column numbers for each line! and actual text. Plus attributes (e.g. making some of the text blue). Now, there is this part of the code above:

       dcl-ds GridDS qualified template;
        col char(1) dim(10);

       dcl-ds Deployed dim(10) likeds(GridDS);
       dcl-ds Attack dim(10) likeds(GridDS) based(ptr);  // enemy screen fields
       dcl-ds AttackA dim(10) likeds(GridDS) based(ptr2); // enemy attrib array
       dcl-ds Defend dim(10) likeds(GridDS) based(ptr3);  // defend screen fields
       dcl-ds DefendA dim(10) likeds(GridDS) based(ptr4); // defend attrib array
       dcl-ds DefendSave dim(10) likeds(GridDS);

       dcl-s ptr pointer inz(%addr(r01c01));
       dcl-s ptr2 pointer inz(%addr(atr0101));
       dcl-s ptr3 pointer inz(%addr(b01c01));
       dcl-s ptr4 pointer inz(%addr(btr0101));

       // map screen fields into DS so arrays can manipulate values
       dcl-ds *n;

r01c01 etc. are just fields of a data structure, which... we then also map to an array of arrays, so that we can manipulate their contents with an actual loop in the program. But... essentially, what's happening is:

... we have a record type. With a bunch of fields in them.

By calling exmft screen2;, we print this data structure onto the screen, in a nicely formatted way, described by our screen template. Then we wait for user input (... the user editing the fields at this point), we read contents back into the variables, and we continue.

We also get to query what function key led to the submission of the form (... not entirely unlike HTTP forms). So... that's why there are no print statements: essentially, our global namespace gets plotted onto the screen, with everything being taken care of!

Punch cards and record formats

It kinda makes more sense if you imagine where all this is coming from. It was all punch cards!

a punch card; link to wikipedia. Photo by Pete Birkinshaw, CC-BY 2.0

Imagine a computer with a punch card reader and a printer attached. A program could be written that:

Actually, "RPG" used to stand for "Report Program Generator"; its main purpose was doing something like this.

Given how everything was record-oriented, it made sense not to embed the formatting of the printed records or the input punch cards into the program logic itself (... these might change, after all); thus, these were defined separately, so that the actual program could focus on the data processing part itself. In fact, in early versions of RPG, it might even have been impossible to even express something like "record parsing / formatting"; in any case, you wouldn't want to perform such a complex operation in a high-level language anyway (vs. assembly code operating on templates).

Eventually, punch cards got upgraded into magnetic tapes... and, eventually, fancy terminals! But then... just the way UNIX still treats screens as teletypes with a serial input, a bell and a carriage that can be "returned", AS/400 and RPG are still thinking in terms of records that can come either from a database... or from a user typing them in one by one.

As with many things, it's a tradeoff. If you want to implement Battleship... it's nontrivial to make it fit into this "records" model, but, apparently, it can be done! On the other hand, if you do have records to work with, the resulting code is probably a lot simpler than an equivalent UNIX tool, busily parsing arguments and formatting output line by line. It's also a lot easier to modify how the output looks like.

... comments welcome, either in email or on the (eventual) Mastodon post on Fosstodon.