An Introduction to ACTION!#

by Clinton Parker

ANALOG Computing

PDF: An Introduction to ACTION/introinaction.PDF(info)

This is the first of a 2-part series that will introduce you to the Action! programming language, using a short example program that draws kaleidoscopic patterns on the screen. There's an old saying about fooling people which, unfortunately, holds true for trying to please people as well. The problem in my case is that different readers have different levels of experience. I hope this series will please all of you at least some of the time.

Action! is a true compiled language, whereas Atari BASIC is an interactive interpreter. In both cases, the ultimate goal is to translate programs from a human­ readable form into something that the computer can understand. The difference is that Action! only performs this translation once, whereas BASIC does it repeatedly. The process is similar to having a speech translated from German to English once and then reading it many times in English (Action!), as opposed to having someone translate the speech to English every time it is read (BASIC). Because Action! statements don't have to be translated each time, they execute much faster.

Action! has three types of numeric variables (BYTEs, CARDinals and INTegers), which are easier for the computer to deal with than the floating-point numbers always used by Atari BASIC. This also contributes to faster program execution, but costs you in terms of flexibility (no fractions or very large numbers) and simplicity (you must declare variables so that the compiler will know what type they are).

BYTE variables can represent numbers from 0 to 255. CARDs can represent numbers from 0 to 65535, and INTs can represent numbers from -32768 to 32767.

Referring to Listing 1, the lines:

CARD period, npts 
BYTE x0, y0, x1 , y1 , ATRACT=77 
BYTE CH=764

are called variable declarations. Note that the BYTE variable ATRACT is defined to reference location 77 in memory, and that variable CH references location 764. More on these later.

In addition to the three basic types described above, Action! allows ARRAYs, POINTERs and user defined TYPEs (records). The following line:

TYPE REC=~[CARD cnt,ax,bx,cx,ay,by,cy]

is a TYPE declaration named REC, and:

REC p, e

is a declaration of two variables (p and e) of type REC. Each of these variables contain all of the variable fields specified in the declaration of REC. Fields of record variables are referenced by first giving the record variable name, then a '.' (period), followed by the field name. The lines:

p.ax = 5221   p.bx = 64449  p.cx = 3
p.ay = 57669  p.by = 64489  p.cy = 3

are examples of assignment statements using record fields.

[scan unreadable]

Second, Action! makes it possible to execute a list of statements if the condition following an IF is false. This is done by placing the keyword ELSE where the FI would normally go, followed by the list of statements for the ELSE, and finally an FI to terminate the structure. ELSE is not used in Listing 1, so don't be concerned if you don't see one.

Action! loops are used to execute a group of statements repeatedly. A simple loop is specified by the keyword DO, followed by a list of statements and ending with the keyword OD (DO spelled backwards). The effect is similar to a group of BASIC statements with a GOTO ( first statement) as the last statement in the group. You can provide control information to specify how many times an Action! loop is to be repeated. One loop control structure — FOR/TO — is very similar to the FOR structure in Atari BASIC. The differences are that, in Action!, the end condition is always tested before the statements within the loop are executed, which means that the loop may never be executed. BASIC always executes a FOR/NEXT loop at least once. Additionally, the STEP increment may only be positive in Action!, whereas BASIC allows both positive and negative STEPs. The other two Action! control structures, WHILE and UNTIL, will be discussed later.

PROCedures.#

An Action! PROCedure is roughly the same as an Atari BASIC subroutine, One distinction is that it' s possible to pass arguments to an Action! PROCedure. If you've ever called a function in BASIC, then you have already used argument passing without even realizing it. In the BASIC line:

A=SIN(X)
X is the argument to the function call SIN().

The Listing 1 lines:

MoveBlock (e, P, REC)
Gen (P)

are examples of PROC calls. Note that the Action! compiler makes no distinction between user-defined PROCs and system subroutines. Thus, the PROC calls:

Graphics (24) 
SetCo1or(1,8,14) : SetColor(2,8,8)
are simiiar to the BASIC statements:
GRAPHICS 24 
SETCOLOR 1,8,14:SETCOLOR 2,8,8

This gives us a nice, uniform PROCedure-calling mechanism and provides an easy method for users to provide their own versions of system routines. PROCedure declarations tell the Action! compiler the name by which the PROC can be called, the arguments and variables which are unique to that PROC, and which statements are to be executed when the PROC is called. In our Listing 1 example, everything between:

PROC Gen (REC POIHTER r)
and
PROC Kal()
constitutes the declaration for the PROCedure Gen().

Gen() has one argument, r, which is a POINTER variable of type REC (a userdefined TYPE). The line:

BYTE x0, y0, x1 , y1 , ATRACT=77

declares a number of local variables that are only used in Gen(). They can not be accessed by any other PROCedure in the program (Kal() in this case). However, the global variable period (which was declared at the beginning of the program) can be used by either PROCedure.

The RETURN statement at the end of the declaration for Gen() is the same as a RETURN statement in BASIC, and causes execution to jump back to the point from which the PROCedure was called. The last procedure declared in a program is the one which will be called first when the program is started (Kal() in this example). If you don't quite follow all of this, don't worry; things should get clearer as we walk through the example.

Walking through. #

As stated earlier, Listing 1 draws kaleidoscopic patterns on the screen. This is done by repeatedly calling the PROCedure Gen(). The Gen() statements:

r.ax = (r.ax + r.bx) ! r.bx 
r.ay = (r.ay + r.by) ! r.by
generate new values for ax and ay (fields of record r, passed to the Gen() PROCedure). These values are used to calculate xO and yO as follows:
x0 = r.ax RSH 9 
y0 = r.ay RSH 9
Without going into details about bit arithmetic and operations, the RSH 9 statements have the effect of dividing r.ax and r.ay by 512 (but do it much faster than a "real" divide). The reason for dividing by 512 is to get values in the range 0-127, so that they can be plotted in graphics mode 24.

The IF statement:

IF x0 <= y0 AND y0 < 96 THEN FI
determines if any points are to be plotted. The check for y0 < 96 assures that the points won't overlap when we calculate x1 and y1:
x1 = 191 - x0
y1 = 191 - y0
The value of 191 was chosen since it is the maximum y-value you can plot in graphics mode 24.

The Plot calls following these two statements display all eight combinations of x0, y0, x1, and y1. The +64 in each call centers the display on the screen, since there are 128 more points in the X direction than there are in the Y direction.

If you're curious about how this plotting algorithm works, choose your own values for x0 and y0 (21 and 55, for example). Calculate x1 and y1 from the formula above (170,136). Finally, calculate all of the points that will be plotted (don't add in the 64; it makes things easier to see). Our example would yield coordinates' of (21,55), (21,136), (55,21), (55,170), (170,55), (170,136), (136,21) and (136,170). If you plot these on a piece of graph paper with 0,0 in the upper left corner and 191,191 in the lower right, you' ll see that they are symmetric about the center.

The only part of Gen() not explained yet is:

r.cnt == -1 
IF r.cnt = 0 THEN
 .
 . 
FI

The first statement decrements the cnt field of r, and the IF statement body is executed when cnt reaches zero. The statements:

r.bx = (r.bx + r.cx) ! r.cx 
r.by = (r.by + r.cy) ! r.cy
calculate new values for bx and by, which cause the ax and ay calculations to change in the future as well.

The line:

r.cnt = period
resets cnt so that it can count down to zero again. Finally,
ATRACT = 0
keeps the screen from going into attract mode. Note that [ATRACT]] was declared to be at location 77. This is the memory location used by the OS to determine if attract mode is on or off.

A look at Kal(). Now you understand (I hope) how the Gen() procedure works. So let's look at Kal() and see how it uses Gen().