On-Line was a column by Russ Wetmore in ANALOG Computing that ran for five issues. The third and fourth columns explored the Action! language.

A.N.A.L.O.G. ISSUE 32 / JULY 1985 / PAGE 23

ON-LINE

Getting in on the Action!

by Russ Wetmore

Action! is an Atari programmer’s dream come true. It is a language not too unlike C or Pascal, but which compiles to very “tight” 6502 machine language. Clint Parker, the author of Action!, has fashioned a remarkable programming environment, where editor, compiler and monitor are all resident at once.

Write your program, compile it, run to test it, then dump right back into the editor with your source code intact, to start making corrections. I’ve done a couple of major projects using Action! in the past year and can recommend it without hesitation to any serious (or casual) Atari programmer.

There are several caveats in creating really big programs (larger than 16K), because of the integrated environment. If you’re planning to write such programs, it’s necessary to know how Action! creates object code from your source, in order to maximize memory usage. There are also a few bugs that need to be noted.

In this article (and the one next month), I’ll show you some tricks I’ve learned to optimize Action!’s output. These comments all apply to version 3.6—they may work on other versions of the compiler, but have not been tested. They also assume a working knowledge of Action!

Variable allocation.

Allocating free memory.

There isn’t a function in Action! that approximates BASIC’s FRE(0) command. It isn’t as simple as checking the monitor to see where the end of your program is, because Action! tries to help you out by placing some non-initialized arrays beyond the end of your program code, instead of inside your program, where they’re declared (specifically, CARD ARRAYs and, generally, BYTE ARRAYs over a page in length).

Luckily, there’s an easy method for determining where the end of the program and variable space actually is. The first CARD ARRAY declared in a program is the last actually allocated during compilation.

MODULE ; sample 1

CARD
  MEMTOP=$2E5, freemem=[0]
CARD ARRAY
  EndOfProgram(1)

PROC Main()
  freemem=MEMTOP - EndOfProgram
  PrintF("Total free Memory=%U%E",
         freemem)
RETURN

Static ARRAY variables.

Action! allows you a lot of choices when it comes to variable declaration. For example, ARRAY variable names are actually pointers to the ARRAY space. This allows you to do such esoterics as:

MODULE ; sample 2

CHAR ARRAY
  str1="This is a test", str2

PROC Main()
  str2=str1
  PrintF(str2)
RETURN

When you run the program, you’ll find that str1 and str2 both “equal” the same string. This is possible because Action! also allocates a pointer to the ARRAY, in addition to the ARRAY data itself. When you assign str2 to str1, you’re actually just assigning str2’s pointer equal to str1’s, which is pointing to the ARRAY data.

In many cases, though, this overhead costs memory for arrays that you’re never going to reassign, such as string constants. Also, if you were to reference the ARRAY name in a code block, you’d have to go through contortions in order to get to the actual data, because the ARRAY name equals a pointer to the data, which you’d have to access indirectly. Clint very thoughtfully put in a construct that allows you to declare ARRAY variables without the associated pointer. Declare the ARRAY with a predefined length of 0. For example:

CHAR ARRAY
  str1(0)="This is a test."

You won’t be able to reassign str1 (you’ll get an error if you try), but you will have saved 2 bytes you probably never would have used, anyway. You’ll also save 2 bytes every time you reference the ARRAY, because Action! will compile the reference as immediate loads of registers, as opposed to indirect fetches from memory. For example:

MODULE ; sample 3a

CHAR ARRAY
  str1="This is a test."

PROC Main()
  PrintE(str1)
RETURN

compiles to:

MAIN    LDA str1
        LDK str1+1
        JSR PrintE
        RTS

whereas the following:

MODULE ; sample 3b

CHAR ARRAY
  str1(0)="This is a test."

PROC Main()
  PrintE(str1)
RETURN

compiles to:

MAIN    LDA #<str1
        LDX #>str1
        JSR PrintE
        RTS

For similar reasons, you may save memory if you predeclare all your variables, ARRAYs or otherwise. For example, when you declare a BYTE variable, you can set its memory address in the declaration. Any variables that follow it in the same statement, though, have extra overhead associated with them. (You can see this effect in the following example.) To test all of these constructs, you can compile a test program then execute the command ?$493 from the monitor, to see the program’s length. Try this with the following two examples:

MODULE ; Long example

BYTE
  COLOR1=$2C4, i, j, k
CARD
  MEMTOP=$2E5, c, d, e
CHAR ARRAY
  str1="Test1", str2="Test2"

PROC Main()
RETURN


MODULE ; Shorter example

BYTE
  COLOR1=$2C4, i=[0], j=[0], k=[0]
CARD
  MEMTOP=$2E5, c=[0], d=[0], e=[0]
CHAR ARRAY
  str1(0)="Test1", str2(0)="Test2"

PROC Main()
RETURN

You’ll find that the second example ends up being 19 bytes shorter than the first.

A string shortcut.

If you work with strings at all. you probably know that the length of a declared string is always the first (“zeroth”) byte of the ARRAY. As such, you probably use a construct similar to:

MODULE ; Sample 4a

CHAR ARRAY
  str1="Test"

PROC Main()
  PrintF("Length of XS is %U%E",
         str1, str1(0))
RETURN

You can save considerable memory (11 bytes each occurrence!) by declaring a separate BYTE variable:

MODULE ; Sample 4b

CHAR ARRAY
  str1="Test"
BYTE
  str1len=str1

PROC Main()
  PrintF("Length of %S is %U%E",
        str1, str1len)
RETURN

By making the declaration str1len = str1, we’re setting str1len’s memory location equal to the “zeroth” byte of str1, hence str1len will always be equal to the length of str1 (if you don’t point str1 elsewhere). The reason for the memory savings is simple. In the first example, the compiler is given the address of the start of the ARRAY and an offset to the actual byte desired. This compiles to something similar to:

LDA str1    ;Fetch address of array
STA $AE     ;Save for indirect ref
LDA str1+1  ;Fetch high byte
STA $AF     ;Save, . .
LDY #0      ;Me want 0'th element
LDA ($AE),U ;Fetch String length

If we declare a BYTE variable outright, though, it will already be pointing to the proper memory location, and no calculation is needed to find it. Thus, the compiler produces something like:

LDA str1len ;Fetch string length

which, I think you’ll agree, is much cleaner. You can apply this principle to any portion of a declared ARRRAY that isn’t going to move, that you need to access.

PROC and FUNC addressing.

In the Action! manual, reference is made to “addressing routines.” Besides the example given, there’s little said about how useful this construct can be.

Forward references.

Action! is a one-pass compiler. Most compilers use a two-pass method, where the entire source program is scanned first to build a symbol table of variable addresses. Thus, on the second pass, if a variable is used before it is declared, the compiler can look it up in the symbol table to find its address.

Action!, however, only makes one pass through a program for speed reasons. This means that every procedure or function is supposed to be previously declared before you reference it. Sometimes this isn’t feasible, but how do you get around it?

One other feature of Action! is the ability to reassign PROCs and FUNCs to different memory locations from where they are first compiled. If you run the following example:

MODULE ; Sample 5

PROC NUM1() PrintE("ONE") RETURN

PROC NUM2() PrintE("TWO") RETURN

PROC Main()
  Num2=Num1
RETURN

you’ll get the result one printed to the screen, because we’ve “pointed” Num2 to Numl’s address. Using this same concept, we can forward reference a PROC or FUNC before it is declared!

MODULE ; Sample 6

PROC DUMMY()

PROC Num1() DUMMY () RETURN

PROC Num2 PrintE("TWO") RETURN

PROC Main()
  DUMMY=Num2 Num1()
RETURN

In Num1, we’ve actually forward referenced Num2 indirectly, by setting DUMMY to be equal to Num2.

An indirect detriment.

Unfortunately, as in the case of non-initialized ARRAYs, the overhead for such indirection is the default case. I have very rarely used the addressing feature and, even then, only in cases where I was too lazy to redo the necessary routines properly.

Action! compiles normal PROC references in a manner similar to this example:

MODULE ; Sample 7

BYTE
  test

PROC DUMMY()
RETURN

PROC Main()
  PrintBE(test)
RETURN

test     .DS 1

DUMMYvec JMP DUMMY
DUMMY    RTS

Mainvec  JMP Main
Main     LDA test
         JSR PrintBE
         RTS

If you were to do the assignment DUMMY=Main, what the compiler would actually produce is:

LDA #<Main
STA DUMMYvec+1
LDA #>Main
STA DUMMYvec+2

so that the resulting code at DUMMYvec would actually become JMP Main. If you don’t ever use this feature, though, every time you declare a PROC or FUNC, you’re actually throwing in a JMP to the next instruction.

The way to avoid this automatic inclusion of the JMP command is to use the construct:

PROC procname=*()

You save three bytes and a little overhead in speed when you declare routines this way. One important note—this construct will not work if you’re passing variables to a routine, unless the first thing encountered in the routine is a code block. This is because of the way that Action! handles saving its zero-page working variables.

Modularizing programs.

You can also use this construct to “modularize” your programs. This is important if you’re trying to compile large programs. Frequently, you’ll run out of symbol table space or, worse yet, run out of memory to compile to because the cartridge eats 8K of space itself, in addition to other overhead.

You can compile all of your constant strings and low level routines, for example, separately from your main program and reference them in your program through equates and routine addressing. You can then use the SET command in the second module to compile the second module above the first, then append your files together to get the final object file. I’ll go more into detail on how to do this next issue.

Also, next time I’ll cover ARRAYs of ARRAYs (string ARRAYs, for example), an Action! version of BASIC’s “ON x GOSUB” and “ON x GOTO” commands, plus other surprises.

Russ Wetmore has been involved in the home computer industry for over six years. He’s probably most widely known for his best-selling, award-winning Atari game program Preppie! He has also shown his talent as a composer/arranger whose work has been heard on national TV. Russ is President of Star Systems Software, Inc., a research and development firm specializing in entertainment and home productivity programs for a host of computers.

A.N.A.L.O.G. ISSUE 35 / OCTOBER 1985 / PAGE 97

ON-LINE

Getting in on the Action!

by Russ Wetmore

This article, both part one (ANALOG Computing, issue 32) and this month’s segment, was written for advanced programmers. Don’t feel badly if you’ve dabbled a little in Action! and can’t make any sense out of the examples in this article. Some of the concepts are quite advanced and are mainly aimed at the experienced programmer who wants to squeeze more functionality out of the Action! cartridge.

Modularizing.

I recently completed a major undertaking in Action!—an integrated three-program package called HomePak. All together, these three programs take up about 64K of disk space, not counting the various global subprograms required, like an RS232 handler, character sets, etc.

Two of the programs were too large to compile using standard methods. I faced an interesting decision: recede substantial portions of the program in assembly language (avoiding such being one major reason I did it in a high-level language to begin with) or leave out possible features in order to save space.

I hit upon another option: compiling the program in pieces. In fact, this saved me time, as I didn’t have to compile the whole program every time. Let’s face it. Many portions of an Action! program are static variables and arrays that almost never change. Why compile them every time, just to find out their addresses so that the rest of the program can tell where they reside?

There’s an “undocumented” feature of the Action! cart you need to know before you can do this. I’ll describe it first.

Compilation offset.

In page 0, $B5-$B6 is used by the compiler as a compilation offset value. The three HomePak programs reside at $3400, which is well above the $2404 address that the cart tells me is my LOMEM value. The manual tells you that you can do the following:

SET $E = $3400
SET $491 = $3480

to set the base address to $3400, but this throws away a good 4K(!) of memory I need to compile to. A better way of handling it is to compile the program to the LOMEM address, but specify an offset to the compiler. That way, when the program gets written out to disk, it loads at the proper address. You can do this by putting a value in $B5-$B6 (using the set command), which is your base address minus the LOMEM address found at $491. Thus, if your LOMEM value is $2404, and you want your program to load at $4000, you’d put:

SET $B5=$1BFC ;(which is $4000-$2404)

at the very beginning of your program. The program, when compiled, would reside in memory during compilation at the $2404 LOMEM address, but when written to disk, will appear to load at $4000.

In order for this to work properly, check the value at $491 while the edit buffer is empty. Since any program in memory pushes up the LOMEM value, you’ll have to do your compiling from disk, rather than from memory. It’s either that, or check the value every time you want to compile, and alter the program accordingly.

Note: There are a couple of bugs in the current version of the cart that effect the offset value. Negative offsets don’t work, so you can’t use this trick to compile below the LOMEM address. Also, there is a subtle bug involving type definitions. If you use the $B5-$B6 offset, and yom- program uses the type construct, you must set the offset to before any type definition—and set it back to its original value afterwards. Example:

MODULE ; Example 1
SET $B5 = $1BFC
  ; (compile to $4000,
  ;from LOMEM of $2404)

BYTE
  i, j, k  ;some variable definitions

SET $B5 = 0
SET $B6 = 0 ; account for bug
            ; involving TYPE statewents
TYPE DISK = [ CARD sector BYTE pos ]
SET $B5 = $1BFC ;return offset to what
                ;it used to be
...

Notice that I had to do two set statements, because the Action! compiler will always try to make a set value a byte, if it can. We need to set the card at $B5, so we need to set each byte of the card value.

Getting down to it.

Now we know how to tell Action! where we want our modules to reside. I generally have a file named GLOBALS.H, which is my header file with seldom-changed global variables. I compile this separately, to the desired base address of my whole program.

Once the compilation is finished and I’ve written the program to a disk file, I use the debugging portion of the monitor to find the end addresses of those variables. (Once a program is compiled—and before any system errors occur—use the program variables in the monitor as you would constants.)

Let’s take an example. Type this in and save it to disk as EXAMPLE2.ACT:

MODULE ; Example 2
; This is my global variable file

SET $B5 = $1BFC
; so program compiles to $4000 from
; $2404 LOMEM. Note: your system
; probably has a different address
; for LOMEM than mine. The value for
; LOMEM will differ depending on what
; DOS you're using, how many drives
; and file buffers you have allocated,
; etc. Do a ?$491 at the Monitor
; with an empty edit buffer to find
; your LOMEM, and subtract it from
; $4000 to get the proper SET value
; for your computer.

BYTE
  two = [2], three = [3], four = [4]

Okay, okay, it’s short. But, then, this is just an example, right?

After you’ve written the file to disk, be sure to clear the source from memory, so your LOMEM value is correct. Compile the module using the command C D:EXAMPLE2.ACT. Once it’s compiled, type in W EXAMPLE2.OBJ at the monitor, to write your object file to disk. Now, type this in:

?two
?three
?four

This tells us the addresses of our byte variables, two, three and four. (You should get the values $4000, $4001 and $4002, respectively.) The last step is to type in ?$E to get the address of the end of the program, which should return the value $2407 (or whatever your LOMEM value is, plus 3).

Some of you are ahead of me, I can tell—the value returned is the proper value, all right, but relative to the object file as it currently exists in memory. You have to add your set value to it to get the final address, so $2407 + $1BFC (or whatever your values are) = $4003—which is what we expect it to be.

Now we can start with our second module. Type this in:

MODULE ; Example 3
SET $B5 = $1BFF

; Note that this value is $4003 (the
; address of the byte following the
; first module) Minus $2404, My LOMEM
; address.  Again, as in Example 2,
; adjust your values accordingly.

; First off, we have to tell this
; Module where our globals are:

BYTE
  two = $4000, three = $4001,
  four = $4002

; Now, for this Module's code:

PROC Main()
BYTE
  i, j, k

  i = two + three
  j = three + four
  k = two + three + four
  PrintF("i=%U, j=%U, k=%U%E",
         i, j, k)
RETURN

Save this source file to disk as EXAMPLES.ACT. Clear the source code from memory, then go to the monitor and type C EXAMPLES.ACT to compile it. Type W EXAMPLE3.OBJ to write the object code to disk.

We now have two object files on disk. Exit Action! to DOS and type in the following at the DUP.SYS menu:

C [RETURN]
EXAMPLE3.OBJ, EXAMPLE2.OBJ/A [RETURN]

This appends the second module onto the first. You can now run EXAMPLE2.OBJ, and the result:

i=5, j=7, k=9

should be printed to your screen. Using variations of this procedure, you can create programs that are much larger than can be physically compiled. You’ll save time, since you won’t have to recompile everything, every time.

ON X GOSUB/GOTO.

There’s a C language construct whereby you can pass the address of a function to a function. (For those of you who don’t know C, you might want to skip over this section; I’m using C here because the examples will serve as a basis for its emulation in Action!) Here’s a short example:

/* Example 4 */
static void PrtNum(num)
unsigned char num;
{
  printf("We want to print ");
  printf("the number %u here", num);
}

static void PrintANumber(routine, num)
void (*routine)();
unsigned char num;
{
  (*routine)(num);
}

void main()
{
  PrintANumber(PrtNun, 5);
}

PrintANumber in the above example takes the address of a function as its argument, and executes it directly. Since the PrtNum routine (actually, the address of Prt Num) is passed, it is executed at the PrintANumber call in the main function.

We can carry this concept a little further—by using arrays of addresses to fmrctions. This gives us the tools we need to do our emulation of BASIC’s ON x GOSUB function:

/* Example 5 */

/* Global declarations */

/*
 FUNCPTR is typed as a pointer to a
 function returning void (no value)
*/
typedef void (*FUNCPTR)();

/*
 Here, we have to tell the conpiler
 ahead of tine what we’re up to:
 we’re using these names as functions
 returning void
*/
void Print1(), Print2(), Print3();

/*
 routines is an array of pointers
 to functions returning void
 (n’est-ce pas?)
*/
FUNCPTR routines[] =
{ Print1, Print2, Print3 };

static void Print1()
{
  puts("Subroutine number 1\n");
}

static void Print2()
{
  puts("Subroutine number 2\n");
}

static void Print3()
{
  puts("Subroutine number 3\n");
}

void main()
{
  unsigned char i;
  for (i = 0; i <= 2; ++i)
    (*routines[i])();
}

This little program does a lot. First, it executes a “for” loop for the values between and 2. The “pointer” to the desired function is fetched (routines[i]), which is then executed directly. Routines[] is an “array of pointers” to functions, with three elements (numbered to 2).

This example has the same function as BASIC’S ON x GOSUB. The equivalent BASIC would be:

0 REM BASIC version of C code
10 FOR X=l TO 3
20 ON K GOSUB 100,200,300
30 NEXT X
40 END
100 PRINT "Subroutine #1":RETURN
200 PRINT "Subroutine #2":RETURN
300 PRINT "Subroutine #3":RETURN

Translating to Action!

We can carry these same basic concepts over to Action! There’s an eccentricity of the compiler that we need to know first. We can’t declare an array of procs or funcs, because such a declaration requires a constant at compile time.

We can, however, declare a code block that includes proc and func addresses, and point an array name to it. For example, to emulate the C example above in Action!, we’d do the following:

MODULE ; Action! version of Example 5

; First, let's define the PROC's
; to be called:

PROC Print1=*()
  PrintE("Number 1")
RETURN

PROC Print2=*()
  PrintE("Number 2")
RETURN

PROC Print3=*()
  PrintE("Number 3")
RETURN

; Next, we define a dummy PROC which
; holds the addresses of the PROC's
; we want to execute:
;
; (We can't define these in a
; CARD ARRAY because they're NOT
; constants and Action! would choke
; on them.)

PROC dummy=*() [
  Print1 Print2 Print3 ]

; Now, a MODULE stateHent because
; we have to declare a variable:

MODULE  ; for CARD ARRAY declaration

; This declares a CARD ARRAY that
; points (suprize!) to "dummy"

CARD ARRAY
  ptrary = dummy

; This routine does a JMP indirect
;to the address passed to it:

PROC Indirect=*(CARD address) [

  ; ("address" is passed in the A and
  ; X registers)

  $85 $AE ;STA $AE save low byte
  $86 $AF ;STX $AF save high byte

; NOTE! To change this to enulate ON x GOTO rather than
;   ON X GOSUB, add this line here:
; $66 $68   ;PLA/PLA pull Off
            ;        return address
  $6C $AE $00 ] ;JMP ($AE)
; jump indirect to routine, which
; RTS's itself to the calling PROC

; Now, our version of the
; C "Main" function:
PROC main()
BYTE i

  FOR i = 0 TO 2 DO
    ; Fetch address of routine to
    ; call ( ptrary(i) ) and execute
    ; it (via "Indirect" PROC)
    Indirect(ptrary(i))
  OD
RETURN

Notice the indirect procedure. We have to do this, because there we have to jump “indirectly” to the routine address. Another way of handling this would be to jump indirect directly into the card array, but this would require self-modifying code (which is a no-no).

To convert the above to emulate BASIC’s ON x GOTO, we just insert two PLAs in the indirect procedure, to pull the return address off the stack.

Arrays of arrays.

The last foray we’re going to make right now into extending Action!’s functionality is the concept of “arrays of arrays.” Action! arrays want to be only one-dimensional, which is prohibitive in a lot of real world programming needs.

Let’s take a simple two-dimension byte array. An array of arrays can basically be considered to be an array of pointers to arrays. Since pointers are actually cards in disguise, it follows that, to create an array of arrays, we need to do the following: (1) declare the individual byte arrays; and (2) declare a card array of the addresses of the individual arrays.

We have the same problem we had before—we can’t declare an array using values which aren’t constants. But we know how to get around that now, right? Here’s an example:

MODULE ; Example 6

; Declare our individual arrays:

BYTE ARRAY
  one()   = [ 1, 2, 3 ],
  two()   = [ 4, 5, 6 ],
  three() = [ 7, 8, 9 ]

; Declare a dunny PROC with the
; addresses of the BYTE arrays:

PROC dummy=*() [ one two three ]

; MODULE statement because we're
; declaring a variable:

CARD ARRAY
  ary_of_arys = dummy

; Now, our Main procedure, which
; illustrates how to access our
; doubly subscripted arrays:

PROC main()
BYTE i, j
BYTE ARRAY bary

  ; loop for first subscript:
  FOR i = TO 2 DO

    ; fetch address of array:
    bary = ary_of_arys(i)

    ; loop for second subscript:
    FOR j=0 TO 2 DO
      PrintF("Array(%U)(%U) = %U%E",
             i, j, bary(j))
    OD
    PutE()
  OD
RETURN

You should get the following output when you run this example:

Array(0)(0) = 1
Array(0)(1) = 2
Array(0)(2) = 3
Array(1)(0) = 4
Array(1)(1) = 5
Array(1)(2) = 6
Array(2)(0) = 7
Array(2)(1) = 8
Array(2)(2) = 9

You can, of course, carry this out ad infinitum—as many layers as you like—by declaring card arrays for each layer of arrays.

Another typical use of arrays of arrays in programming is “string arrays,” where strings are considered to be arrays of characters (as in C and Action!). I’ll give a more useful example here:

MODULE  ; Example 7

; This subroutine prints out an
; English explanation for the user
; when a system error occurs (Only
; errors 128 through 144 are given
; for space reasons)

DEFINE LASTERR = "144"

CHAR ARRAY
  s128() = "BREAK key abort",
  s129() = "IOCB already open",
  s130() = "Nonexistent device",
  s131() = "IOCB Write only",
  s132() = "Illegal handler command",
  s133() = "IOCB not Open",
  s134() = "Illegal IOCB nunber",
  s135() = "IOCB Read only",
  s136() = "End of file",
  s137() = "Truncated record",
  s138() = "Device timeout",
  s139() = "Device NAK",
  s140() = "Serial frame error",
  s141() = "Cursor out of range",
  s142() = "Serial bus overrun",
  s143() = "Checksum error",
  s144() = "Device done error",
  generic() = "Error %U!%E."

PROC dummy=*() [
  s128 s129 s130 s131 s132 s133 s134
  s135 s136 s137 s138 s139 s140 s141
  s142 s143 s144 ]

MODULE  ; for variable declaration

CARD ARRAY
  errstrs = dummy

PROC PrintError(BYTE errnum)
  IF errnum > 128 THEN
    IF errnum > LASTERR THEN
      PrintF(generic, errnuM)
    ELSE
      PrintE(errstrs(errnuM - 128))
    FI
  FI
RETURN

I’ll leave it as an exercise to you, to figure out how this last example works. It’s much like the preceding example, if that’s any help.

That’s it for this journey into esoterica. The Action! language has many capabilities that most people will never see or use. I hope I’ve at least sparked some of you to do more investigative work.

Next month is letters/feedback time. I’ve gotten a lot of response to my articles on piracy—some pro and a surprisingly high number on the con side. I’ll share some of the more representative ones with you next month.

Russ Wetmore has been involved in the computer industry for over six years. He’s probably best known for his game Preppie! and is president of Star Systems Software, Inc., a research and development firm specializing in entertainment and home productivity programs.