Going into Action! with Atari XL/XE – Part 5 – Modularization and Distributing your program

Going into Action! with Atari XL/XE – Part 5 – Modularization and Distributing your program

Except for early dialects of BASIC found in the 8-bit computers, the programmer can split an application source code into multiple files in almost all languages. Doing so helps organize the code and allows the programmer to reuse a generic code (library) in different programs.

One could create a specialized library to handle joystick input or display a score with fancy fonts. This practice saves the programmer from repeating the same code over and over across many different applications. As a bonus, using modules like that makes it easier to fix a bug on that code without replicating the fix everywhere.

Another advantage in splitting the source code into multiple files is related to the retro computers we use, usually with limited memory. If your program grows too much, it might not fit in memory, which can be even more limited in an integrated environment like Action!

Action! allows modularization using the keyword INCLUDE:

INCLUDE "D:MYLIB.ACT"

The idea is straightforward. When the compiler finds the keyword include, it will stop the compilation of the current source code, and it will start compiling the file indicated by the parameter passed by the command.

Let’s check this small example. First, we will create a file called BASICMATH.ACT containing very complex math functions:

CARD FUNC SUM_BYTE(BYTE A,B)
  CARD TOTAL
  TOTAL=A+B
RETURN (TOTAL)

If you are typing along, make sure to save this as “D:BASICMATH.ACT” and clear your editor buffer with the command <SHIFT><CLEAR>, and enter the following:

INCLUDE "D:BASICMATH.ACT"

PROC MAIN()
  CARD TOTAL

  PRINTE("COMPLEX MATH DEMO")

  TOTAL = SUM_BYTE(43,123)

  PRINT("TOTAL:")
  PRINTCE(TOTAL)

RETURN    

Notice that the INCLUDE command is at the top of my source code, but it doesn’t have to be there. The only two rules are that the INCLUDE must be placed before the functions that it provides and outside a FUNCTION or PROC block.

If you compile the program above and run it, it should show the output:

MODULE keyword

If you are creating modules to be used in many different programs it is a good practice (thanks @zbyti on Atariage) to enclose your library with the keyword MODULE.

The MODULE keyword is used to indicate that everything declared after this keyword until the next PROC/FUNC will be considered a global variable. Usually, you don’t need to do that when you have a single file program because Action assumes that by default from the beginning of your file (This is why global variables should be declared at the top). See this example:

PROC DOSOMETHING()
  PRINTCE("DOING SOMETHING")
RETURN

CARD THIS_IS_GLOBAL=[10]

PROC MAIN()
  PRINTCE(THIS_IS_GLOBAL)
RETURN    

If you try to compile this, Action will show you an error 11 for the CARD THIS_IS_GLOBAL=[10]. That is because you cannot declare a variable there. However, if you add the MODULE keyword before its declaration, Action will compile just fine because you are telling it that THIS_IS_GLOBAL is a global variable. This is the program version that will work:

PROC DOSOMETHING()
  PRINTCE("DOING SOMETHING")
RETURN

MODULE

CARD THIS_IS_GLOBAL=[10]

PROC MAIN()
  PRINTCE(THIS_IS_GLOBAL)
RETURN 

The example above doesn’t make much sense, but if we go back to the original BASICMATH example and we add a global variable to it like this:

INCLUDE "D:BASICMATH.ACT"

; GLOBAL VARIABLES
CARD THIS_IS_GLOBAL=[10]
CARD THIS_IS_ALSO_GLOBAL=[11]

PROC MAIN()
  CARD TOTAL

  PRINTE("COMPLEX MATH DEMO")

  TOTAL = BYTE_SUM(43,123)

  PRINT("TOTAL:")
  PRINTCE(TOTAL)

RETURN   

You will see that, although it looks perfectible acceptable, this will cause the same error 11 when trying to compile the code because of the BASICMATH.ACT file contains variables and functions in it, and therefore the variable declarations in the code above are not really at the top. You should add a MODULE keyword before them so it will compile.

To avoid such complexity, a good practice is always enclose your library files with the MODULE keyword. The BASICMATH.ACT file will look like this:

MODULE
CARD FUNC SUM_BYTE(BYTE A,B)
  CARD TOTAL
  TOTAL=A+B
RETURN (TOTAL)
MODULE                       

If BASICMATH.ACT has that, the programmer won’t need to care about adding modules before global variables ever, since what the compiler will see is this:

MODULE
CARD FUNC SUM_BYTE(BYTE A,B)
  CARD TOTAL
  TOTAL=A+B
RETURN (TOTAL)
MODULE

; GLOBAL VARIABLES
CARD THIS_IS_GLOBAL=[10]
CARD THIS_IS_ALSO_GLOBAL=[11]

PROC MAIN()
  CARD TOTAL

  PRINTE("COMPLEX MATH DEMO")

  TOTAL = BYTE_SUM(43,123)

  PRINT("TOTAL:")
  PRINTCE(TOTAL)

RETURN        

With this technique, you can create a nice set of reusable libraries to be included in your next Action! game.

The Action! Run Time Package

Action! was originally distributed in a cartridge. Its users need the OSS cart connected to run their programs in the same way the Atari 400/800 users need the BASIC cart to run BASIC programs. This is necessary because when executing commands from the standard library, the Action compiler would include in your program a reference to the library’s resident in the cartridge, so when such a command was about to be executed, the resident code in the cartridge would be called.

Even the simple PRINT command in Action depends on the cartridge to work. For example, if you create a program with one single PRINT statement, the program will display the message just fine if the cartridge is inserted. However, if you remove the cartridge and try to run the program, nothing will show up on the screen because the code to run the PRINT command is nowhere to be found.

To allow Action! programs to be redistributed, OSS created the Runtime package. The Action! Run Time Package is designed to aid users of the OSS ACTION! cartridge-based language. Specifically, by using RunTime, you can compile an ACTION! program in such a way that the Action cartridge is no longer needed when running the compiled program.

The Run Time package is nothing more than all standard library functions source code available for you to INCLUDE in your program. The original image file is available at the AtariWiki Action! page here.

Preparing the Distribution

To illustrate the process, we will assume the work disk is in drive 1 (D1), and the Runtime disk is inserted in disk 2 (D2), so make sure you follow the same configuration or adapt the programs below accordingly.

For this example, we will use the following Action program:

PROC MAIN()
  CARD SECRET
  BYTE I

  PRINTE("HERE IS THE MAGIC OF RANDOM NUMBERS!")

  FOR I=1 TO 5 DO
    SECRET=RAND(10)
    PRINT("THE SECRET NUMBER IS: ")
    PRINTCE(SECRET)
  OD

RETURN                           

After you type (or copy) the program above, save it as “RNDTST.ACT” in the work disk, using CTRL+SHIFT+W. To make sure the program works, open the monitor and compile it using the command “C” and then run with “R”. You should see the message “The secret number is” five times, with different (hopefully) numbers.

The program above utilizes two functions from the standard Action! library: PRINT() (and its variations) and RAND(). Since the two functions are defined in the inserted cartridge, everything will work nicely if executed from the Action editor.

Now, if you want to distribute this amazing program among your friends, you can do so simply by adding at the beginning of it the command INCLUDE pointing to the Action runtime file called SYS.ACT. The program will look like this:

INCLUDE "D2:SYS.ACT"

PROC MAIN()
  CARD SECRET
  BYTE I

  PRINTE("HERE IS THE MAGIC OF RANDOM NUMBERS!")

  FOR I=1 TO 5 DO
    SECRET=RAND(10)
    PRINT("THE SECRET NUMBER IS: ")
    PRINTCE(SECRET)
  OD

RETURN   

Save, compile and rerun it. At this point, the only difference you might notice is that the “INCLUDE “D2:SYS.ACT” message will be displayed, and the compilation will take longer. To distribute the program, you will need first to save the binary, using the command W (while in the monitor, not in the editor), like this:

After the binary is saved, go back to the DOS (“D” command in the monitor) and check if the file RNDTST.COM is saved properly. Next, remove the Action cartridge and go back to DOS once more.

If you try to run the program now, you should see the same output, with the five random numbers! Notice that the output can disappear quickly depending on the DOS you are using.

Optimizing the File Size

The file SYS.ACT contains the source code of all standard functions provided by Action. Because the Action compiler is incapable of determining which function you are using and include only them, your distributable file will contain ALL functions, even if they are not being used. If you are playing around with small programs, using SYS.ACT won’t hurt, but if your program grows bigger, it would be nice if you can keep it smaller as possible.

To help with that task, the Runtime package also provides multiple files containing groups of functions. This way, instead of adding the SYS.ACT file you can add only the files that include the function(s) you are using. In our example above, we are using only the PRINT and RAND to trim down the program size by only including what is necessary.

The PRINT is defined in the SYSIO.ACT file while the RAND in the SYSMISC.ACT, so instead of adding SYS.ACT, you could include these two instead. When not using the complete SYS.ACT, the only mandatory file you must include is SYSLIB.ACT, which contains primitive runtime support routines plus a few declarations used by the
rest of the INCLUDE files.

Preview in new tab(opens in a new tab)

INCLUDE "D2:SYSLIB.ACT"
INCLUDE "D2:SYSIO.ACT"
INCLUDE "D2:SYSRAND.ACT"

PROC MAIN()
  CARD SECRET
  BYTE I

  PRINTE("HERE IS THE MAGIC OF RANDOM NUMBERS!")

  FOR I=1 TO 5 DO
    SECRET=RAND(10)
    PRINT("THE SECRET NUMBER IS: ")
    PRINTCE(SECRET)
  OD

RETURN     

To help you decide what includes to use, here is the list of files and their contents, for your reference:


SYSLIB.ACT
Clos – close channel
Output – output string, no EOL
In – input string
XIOstr – do CIO call
Opn – open file
Prt – output string with EOL
SYSIO.ASCT
All PRINT functions
All INPUT functions
All PUT functions
ChkErr – internal
Break1 – internal
Open – (ChkErr)
Close – (ChkErr)
InS – internal
GetD – (CCIO)
CCIO – internal
XIO – (ChkErr)
CToStr – internal
PNum – internal (ChkErr)
StrB – (StrC)
StrC – (CToStr)
StrI – (CToStr)
ValB – (ValC)
ValC – (ValI)
ValI
PF2 – internal (Put, Print,
PrintI, PrintH, PrintC)
Note – (ChkErr)
Point – (ChkErr)
SYSGR.ACT
Graphics – (Close, Open)
Position – (Pos1)
Pos1 – internal
GrIO – internal (Pos1)
DrawTo – (GrIO, XIO)
Locate – (Position, GetD)
Plot – (Pos1, PutD)
SetColor
Fill – (GrIO, XIO)
SYSMISC.ACT
Rand
Sound
SndRst
Paddle
PTrig
Stick
STrig
Peek – (PeekC)
PeekC
Poke
PokeC – (Poke)
SYSBLK.ACT
Zero – (SetBlock)
SetBlock
MoveBlock
SYSSTR.ACT
SCompare
SCopy
SCopyS – (SCopy)
SAssign – (SCopy)

Alternative Runtimes

Since Action is very flexible, nothing prevents you from adding customized runtimes, which would work the same way as adding your own modules. If someone has created a more efficient way to execute the function, let’s say, PRINTCE, you can add it to a SYSMYRUNTIME.ACT file and include that file when you need it.

The AtariWiki Action page contains some alternative or optimized functions that you can try it on.