Part 3: Intelligible Forms

Created 22 September 2022 - Updated 26 September 2022

The documentation for DPS 2200, OS 2200's block-oriented form system, is not particularly approachable. Additionally, starting from the DPS 2200 runtime programming manual, as might seem prudent, is a bad idea; instead, true enlightenment is to be found in the Application Development Programming Guide. The first stop on the Road to DPS 2200, after reading the documentation, is to run @FORMGN - the interactive form generator, FORMGEN.

A complex menu-based system for generating source code output for a form. The title reads DPS 2200 FORMGEN PROCESSOR.

FORMGEN is actually a pretty cool piece of software. It allows definition of custom fields, layout, testing of forms, and a host of other features I didn't even scratch the surface of. It's also a unique mixture of extremely intimidating and extremely helpful. FORMGEN prompts you, as part of its main form, for an expertise level, from 1 to 4. Running at expertise level 1 enables walkthrough mode, where a page of description text is displayed before entering any new section of the application. There's also deep online help, accessed by entering a question mark in almost any field. Unfortunately, other aspects of FORMGEN are decidedly arcane, especially to someone with an attention span as limited as that of your author; in particular, the tool for creating multiple fields copied from a single template - which would have been useful for my game board fields - is something I bounced off of very quickly. Other parts of FORMGEN are simply a little clunky; for instance, instead of supporting directional scrolling or similar, the Image/Field Attributes editor requires you to specify a starting point to display (whether as an offset or an absolute value) for both lines and form fields.

The FORMGEN attribute editor.

A form contains a form name and a form number, which I assume is used to have an application's entire form set in a single filename, with forms differentiated by number; I admit that in practice, I mostly used it as a versioning system. I had difficulties early on getting forms to correctly save; I realized, after consulting the DPS 2200 Administration Guide, that I needed to initialize the DPS 2200 password file and form library. A quick trip to @DPSIF resolved that issue, and soon I was saving and loading forms with the best of them. The other important function of FORMGEN is generating what OS 2200 refers to as working storage for the form - an output file of C, COBOL, or FORTRAN source containing metadata structures and, importantly to the programmer, a data model for the form's field values.

How DPS 2200 Works

The DPS 2200 communication model was initially fairly unclear to me - but a brief dive into the documentation, and some trial and error, resulted in a very familiar system. Here's how it works.

  • The C program, running on the Dorado mainframe, runs d_init1() and d_open() to initialize the form and its working storage. After this you can set default values in the working storage's screen_formname_s[version]_data struct; for instance, if I want to set a default value of 'a' for the single-character field 'statustxt' in the form RFKX, form number 5, i'd run screen_rfkx_5.screen_rfkx_5_data.s5_statustxt = 'a';
  • Once working storage is set to your liking, you run d_send() to send the form down to the user terminal. They now see the form and whatever values you set in working storage!
  • You call d_read(), which blocks until the user transmits the form back. You then receive updated working storage, including transparently updated field values in your model. If you really want to, you can have DPS 2200 perform field validation at this stage using the _valid member of the form tag struct, which lurks within working storage.
  • You do whatever server side manipulation you need to, then run d_send() again. It is important not to run d_read() more than once consecutively without a d_send() in between - as I learned, this leads to a frozen form!

For those following along at home, this is basically the same flow many modern web apps use - a model is passed between the client and the server, being mutated at both ends, and potentially with validation as it crosses a boundary. The more things change, the more they stay the same.

Our First DPS 2200 Program

The Robot Finds Kitten gameboard.

This is the Robot Finds Kitten game board. There are many like it, but this one is ours. It displays the app name, the username, and the current time - the latter are made easy with FORMGEN's attribute support - as well as a number of fields in the middle where the player icon, Kitten, and Non-Kitten Items will eventually be displayed. It also has single-char alphanumeric field for the user to enter commands, and a currently-invisible field for message text - like descriptions of non-kitten items - to appear.

This is the start of our Robot Finds Kitten app.

	
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <dpsfunc-def.h>
#include <dps-stat-def.h>
#include <info-buf-def.h>
#include <get-fld-def.h>
#include <senderr-def.h>
#include "rfkx.h"

int main(void)
{
	d_init1(&dps_status, &info_buffer);
	if (dps_status.status_word.status_indicator == STATUS_FATAL)
	{
		exit_error();
	}

	d_open(&dps_status, &screen_rfkx2_5);
	if (dps_status.status_word.status_indicator == STATUS_FATAL)
	{
		exit_error();
	}

	d_send(&dps_status, &screen_rfkx2_5);
	if (dps_status.status_word.status_indicator == STATUS_FATAL)
	{
		exit_error();
	}

	while (1)
	{
		d_read(&dps_status, &screen_rfkx2_5);
		if (dps_status.status_word.status_indicator == STATUS_FATAL)
		{
			exit_errors();
		}
		else if (screen_rfkx2_5.screen_rfkx2_5_data.s5_cmdtxt == 'X' || screen_rfkx2_5.screen_rfkx2_5_data.s5_cmdtxt == 'x')
		{
			exit_normal();
		}
		screen_rfkx2_5.screen_rfkx2_5_data.s5_cmdtxt = ' ';
		d_send(&dps_status, &screen_rfkx2_5);
		if (dps_status.status_word.status_indicator == STATUS_FATAL)
		{
			exit_error();
		}
	}
}

void exit_normal(void)
{
	d_close(&dps_status);
	if (dps_status.status_word.status_indicator == STATUS_FATAL)
	{
		exit_error();
	}
	exit(0);
}

void exit_error(void)
{
	d_dump();
	d_errmsg(&dps_status);
	d_term(&dps_status);
	exit(1);
}

This obviously depends on the FORMGEN-generated headerfile, rfkx.h, which I'll link at the bottom; it also depends on having a form in the forms library that matches that file. Its behavior is pretty self-explanatory and draws heavily from Unisys's samples; we initialize the form, open the form, and send the form, checking for errors throughout. Then we read the form until the user transmits a command consisting of the letter X, which instructs the application to cleanly terminate. It's simple and does almost nothing, but is a functional DPS 2200 application. I keep this project's source as members of a program file (for the UNIX users out there, read 'directory') named sunset*rfkx. - so I'd compile and run this like so:

@uc sunset*rfkx.rfkx/c,sunset*rfkx.rfkx/out
@xqt sunset*rfkx.rfkx/out

This tells the system to compile the C source in sunset*rfkx.rfkx/c to the object module named sunset*rfkx.rfkx/out, then execute it directly.

Time to get a cup of tea, I'll be back in ten minutes! Okay, I'm back... wait, what the heck?

Robot Finds Kitten crashing!

Oh no! A contingency! Our application has crashed due to a terminal timeout, and the OS 2200 system has helpfully dropped us into the PADS debugger. Thankfully, there's a way to resolve this.

Next time, we'll build a User Contingency Handler, and take a dive into OS 2200's fancy set of storage mechanisms!

Generated header file for the test program

FDLP definition to generate the form

Back to Let's Play OS 2200!