                             Struct 1.0
                           --------------

                 An Amiga compiler by Roland Acton

                           --- Manual ---



Contents
--------
1. Introduction
2. Setup And Usage
3. Basic Concepts
4. Language Summary
5. Tutorial
6. Optimization
7. Q & A
8. Registration
9. Legalese

Appendices
----------
A. How to alter the library definition file
B. Possible errors
C. Theoretical limits
D. Speed tests



* Parts of this manual have asterisks along their left border. These
* sections are optional reading material. Most of them deal with the
* internal workings of the compiler and efficiency related issues.
* Reading them is helpful, but not strictly necessary.



1. Introduction
---------------
  It was the summer of '92, and I was irritated. The cause of this
condition was very simple: I didn't have any good languages for my
Amiga.
  Assembly was out. Oh, I knew assembly, and had a decent assembler.
But writing assembly on the 68000 was a nightmare. It hadn't been so
hard on the old C-64 where I had started programming - A, X, and Y
were all you really needed to remember, and the occasional BNE. But
the 68000...sixteen registers (more if you counted the SR and such),
indirect addressing, library pointers... I quickly grew tired of
looking back over my program to figure out what register I'd left a
number in. I grew even more tired of spending hours in a monitor
program because I'd made a typo.
  It wasn't that hard if you wrote your code very carefully, but
that usually meant passing up optimizations because you wouldn't be
able to figure out what you had been doing later. What was the point
of using assembly if you weren't going to squeeze every last bit of
speed out of it?
  C? Well, I'll admit it: I didn't know C at the time. I do now.
Even if I had known it, though, I wouldn't have been very positive
about it. C compilers for the Amiga tend to be expensive and/or
take large amounts of memory. The ones that aren't probably don't
produce very good object code. C was not an option.
  AMOS Basic? It had bugs. (It still has bugs.) One of them was that
programs written in it didn't work very well with each other. This
was because they each tried to open up their own (exclusive) View
and install their own (exclusive) input handler. This meant that
writing a "serious" program in AMOS could be rather dangerous for
the end user, particularly if it hung around on the system for
awhile.
  The other languages that I knew (Pascal, Fortran, Ada) hadn't made
much of a mark on the Amiga. It looked like I would have to go with
assembly - and writing a large program in THAT language was a scary
thought indeed. Still, it was the only option, unless I felt like
writing my OWN language.
  Now that was an interesting idea. I still had nearly all of
summer vacation left, and that gave me quite a bit of time to play
around with. Could I do it? I thought so, but the only way to know
for sure was to try. It was better than sleeping my summer vacation
away. I ought to be able to do it in three months. No problem.
  I write this a year later, as version 0.95 of Struct is about to
be released to beta testers. It wasn't quite as easy as I thought,
but it was always interesting. Almost always.
  Although Struct is a considerable step above assembly, I still
consider it to be a low-level language. This is because many
features which have come to be expected in high-level languages are
not present in Struct - features such as strings, arrays, and
floating point variables. All the "real" high-level languages that
I have seen have these features automagically. In Struct they must
be built from scratch.
  In my opinion, right now Struct has the following serious
limitations:

1. It's not possible to have the parameter of a command or function
be another command or function. I can't recall what the formal name
of this ability is, but Struct doesn't have it. This means more use
of temporary variables, which can be very annoying.

2. The compiler does not support "modular" programs. The user can
split his program into multiple files with the Include Struct
command, but the compiler always sees it as one big source file. A
trivial change to a very large program could take quite a while to
compile. Also, many assemblers do not like a source file that is
more than a few hundred K long - and Struct would generate such a
file with a sufficiently large program.

3. You can have only a certain number of constants. This could cause
"problems" for users wanting to include very large external constant
definition files. I can set this limit quite high, but cannot
eliminate it, so it still exists, waiting to wreak havoc on the
unsuspecting user.

  I believe that I can get around all of these limitations, but the
solutions require careful thought, and their implementation is a
ways down the road.
  Despite these problems, though, the compiler is quite usable. It
will serve to make stand-alone programs or fast subroutines for
programs written in other languages. It is my hope that Struct will
be as useful to you as I intend to make it to me.
  After all, any other language would be inefficient.



2. Setup And Usage
------------------
  Copy the file "StructLibraryDefinitions" into your S: directory.
This file is the equivalent of the Commodore .fds and contains the
library offsets and registers for all of the Amiga's OS functions.
Struct will not run without it.
  Struct itself is used from the command line, and should be copied
into your C: directory to facilitate this. The compiler is invoked
with the command:

Struct <source-file> <destination-file>

  Struct will not append any suffixes to either of the filenames.
  Struct generates output files containing assembly code. This code
must be assembled and linked before it can be run. The Phx directory
contains the optimizing assembler PhxAss and linker PhxLnk; these
should go into your C: directory.
  There are a number of optimizations to the assembly code that
Struct does not make because it knows that PhxAss will do them for
it. For the fastest executables, an optimizing assembler should
always be used.
  Shortly before releasing version 0.99, I discovered to my great
surprise that PhxAss does not work under Kickstart 3.0!
Unfortunately, I know of no other optimizing assemblers that you
don't have to pay for. For those people who have OS 3.0, I have
included the A68k assembler and Blink linker in the archive. These
programs don't optimize, but are extremely stable.
  You can use the batch file included in the archive as a model for
a script that will invoke the compiler, assembler, and linker with
one command.
  When Struct reads a line from a source file, it first REMOVES ALL
SPACES and then CONVERTS THE LINE TO UPPERCASE. Thus, the line:

GLOBALVALUE=5

is the same as:

Global VALUE=5

  When you define variables or constants, Struct will check them to
make sure that they don't begin with keywords. Thus, the situation
just presented would probably end up causing a compilation error.
  Struct's error reports always show you the line as the compiler
sees it - uppercase and without spaces. This allows you to more
easily put yourself in the compiler's position and find your
mistake.
  Besides errors, Struct can also generate warnings. If a
compilation results in warnings but no errors it is considered a
success. However, depending upon the situation, the object code may
not assemble/work.
  If there are errors, object code will still be generated, but it
should NEVER be used! It may be missing vital sections that will
cause the program to fail or crash the computer.



3. Basic concepts
-----------------
  As mentioned before, Struct is a low-level language. There are no
strings, pointers, or arrays as such - only those which you create
yourself. The three basic data types are BYTE, WORD, and LONG. All
variables are assumed to contain signed integers, though this really
only makes a difference for multiplication and division. Floating
point is not supported.
  If a variable of one data type is assigned to a variable of
another data type, Struct will do one of two things. If the
assignment is one of these types:

  <byte-type>=<word-type>
  <byte-type>=<long-type>
  <word-type>=<long-type>

that is, if the contents of a variable are being assigned to one
with lower precision, Struct will simply take the low byte or word,
as appropriate, from the longer variable and place it in the shorter
variable. Because of the way signed numbers are represented in the
computer, this will work so long as the longer variable does not
contain a larger number than the shorter variable can hold. There is
no checking for this kind of situation. If you think it may happen,
you must check for it explicitly in your program.
  If the assignment is one of these types:

  <long-type>=<word-type>
  <long-type>=<byte-type>
  <word-type>=<byte-type>

that is, if the contents of a variable are being assigned to one
with higher precision, Struct will perform an operation called
"sign-extension" on the contents of the shorter variable and assign
it to the longer variable. This will always work properly.

* Sign-extension is a pretty trivial and quick operation, but too
* much of it will start to add up. It's best to structure your
* programs so that variables are only assigned to variables of like
* type; this will avoid sign-extension and the risk of truncating a
* value.

  If an actual mathematical operation is performed (as opposed to
the simple assignments shown above) it will be done in 32 bits. As
much of the result as can fit will be placed in the destination
variable after the math is complete.
  For math operations, only the four basic operators are supported:
+ (plus), - (minus), * (multiplication), and / (division). Further,
NO parentheses are supported and mathematical expressions are ALWAYS
evaluated strictly left-to-right - there is no "operator
precedence". Most other compilers use an internal system called
"Reverse Polish Notation" (RPN) to provide these features. Struct
does not. The disadvantage of this is that writing complex math
expressions can be irritating and require the use of temporary
variables. The significant advantage is that math under Struct does
not require the use of the stack, and so can be much faster than
other languages.
  Be warned that the hardware instructions for multiplication and
division are used, and their support for 32 bits is incomplete.
Multiplication generates a 32-bit result, but only uses the low
word of the multiplier (if it is a LONG) and of the intermediate
value. Division uses the entire 32-bit intermediate value as the
dividend, but only the low word of the divisor is used if it is a
LONG, and the result will be only 16 bits. It will be sign-extended
to 32 bits before becoming the new intermediate value.
  The AND, OR, NOT, and NEGATE operations cannot be used in
mathematical expressions. These operations have their own separate
commands.

* Struct's parsing of math expressions is very thorough. Each
* command has a chance to make local optimizations (the PEEK and
* POKE commands, for example, can see a memory access to RANMA+GENMA+2
* as 2(RANMA,GENMA.L) if appropriate). If the local optimizations
* fail, or the operation was just an assignment in the first place,
* the expression is passed through to an internal routine called
* MATHEVALUATE. This routine knows the difference between situations
* such as:
*
* X=Y ("Level 1", simple assignment)
* X=X+Y ("Level 2", incrementation)
* X=A+B+C ("Level 3", complex expression)
*
* and will act accordingly. It also knows that addition and
* subtraction are commutative, and that numeric literals can be
* concatenated.

  The tilde character (~) has a special meaning to Struct. It is the
line continuation character. When Struct's line parsing routine
encounters the tilde character, it will skip all characters after it
until it reaches an end-of-line character. It will then resume
reading characters as normal. Thus, if a line such as this appears
in your source code:

Data Register Catty, Rabby, Lu~
fy, Rumy, Patty

Struct will see it as:

Data Register Catty, Rabby, Lufy, Rumy, Patty

  Although this feature was intended to allow you to break up huge
lines into sections that will fit on your text editor's screen, it
can also be used to include comments in the source code:

~ This routine opens up a Viewport, initializes a copper list, and
~ loads a backdrop picture onto the screen.

  The presence of the tilde will, as a side effect, cause the
compiler to ignore these lines.
  Labels can be placed at any point in the source code by the
following method:

<label>:

  For example:

Eluza:

  These labels can be used with such commands as Load Address and
Goto. A label that appears within a procedure is a local label, and
can only be referenced by code within that procedure. A label that
appears outside a procedure is a global label, and can be referenced
by code in any procedure. If a global and local label both have the
same name, all references to that label within that procedure are
assumed to refer to the local label.



4. Language Summary
-------------------
Address Absolute <[<variable1>][,][<variable2>][,][...]]>

  Summary: Assigns LONG variables to address registers A3-A5. The
first variable specified is assigned to A3, the second to A4, and
the third to A5.

Address Register [<variable1>[,<variable2>[,...]]]

  Summary: Assigns LONG variables to address registers A3-A5. Struct
decides which variables are assigned to which registers.

And <mask-expression>,<variable>

  Summary: Performs a mathematical AND between the mask-expression
and the variable. The result is left in the variable.

Asm

  Summary: Marks the beginning of a section of inline assembly.

Back If

  Summary: Changes the flow of control back to the beginning of the
most recent If statement. The If statement is re-evaluated.

Byte Data [(label)] <data>

  Summary: The specified data is placed in the object file as
byte-length values. If the command is given while in a procedure,
the data is held until the end of the procedure, then output. The
optional local label will point to the beginning of the data.

Byte Peek <variable>=<memory-location-expression>

  Summary: The memory-location-expression is evaluated, and the
byte-length value found at that location is placed in the receiving
variable.

Byte Poke <memory-location-expression>,<value-expression>

  Summary: The memory-location-expression is evaluated, and the
result of the value-expression is written to that place as a byte.

Call <memory-location-expression>

  Summary: The memory-location-expression is evaluated, and a
machine language subroutine located at that address is called.

Constant <constant1>[=<value>][,<constant2>[=<value>][,...]]

  Summary: Defines one or more constants. An optional initial value
can be specified.

Constant Evaluate <ON/OFF>

  Summary: Turns ON or OFF an internal compiler switch that governs
whether or not a constant assignment is evaluated immediately.

Data Absolute <[<variable1>][,][<variable2>][,][...]]]>

  Summary: Assigns variables to data registers D1-D7. The first
variable specified is assigned to D1, the second to D2, and so
forth.

Data Register [<variable1>[,<variable2>[,...]]]

  Summary: Assigns variables to data registers D1-D7. Struct
decides which variables are assigned to which registers.

Define Procedure [<return-type>=]<procedure-name> [(<variable1>:
  <type>[=default-parameter],<variable2>:<type>[=default-parameter]
  [,...]>)]

  Summary: Tells Struct the names, types, and defaults of the
parameters a procedure will accept, if any, and the type of value
that it returns, if it does. A procedure must be defined before it
can be either implemented or called.

Else

  Summary: Marks the beginning of a section of code that will be
executed if its associated If is false.

End Asm

  Summary: Marks the end of a section of inline assembly.

End If

  Summary: Marks the end if an If statement.

End Loop

  Summary: Marks the end of a Loop structure.

End Procedure

  Summary: Marks the end of the implementation of a procedure.

Eor <mask-expression>,<variable>

  Summary: Performs a mathematical EOR between the mask-expression
and the variable. The result is left in the variable.

Exit

  Summary: Exits prematurely from a loop structure. Program
execution will continue immediately after the loop's terminating
statement.

For <variable>=<start-expression>;<end-expression>
  [;<step-expression>]

  Summary: Initiates a For/Next loop structure. The section of code
in the loop will be repeated as the value of the variable ranges
from the start-expression to the end-expression. An optional
step-expression can be specified which is used in place of the
default step of 1.

Global <variable1>:<type>[=<initial-value>][,<variable2>:<type>
  [=<initial-value>][,...]]

  Summary: Defines the name, type, and possibly initial value of
one or more global variables. The variables will be accessible from
any procedure.

Goto <label>

  Summary: Changes program flow to the specified label. The label
must be defined within the current procedure.

If <condition1> [<&/|> <condition2> [<&/|> <condition3> [...]]]

  Summary: Marks the beginning of an If statement. If the specified
condition is determined to be true, the code immediately following
the If will be executed. Otherwise, the program flow will jump to
the End If (or an Else if one has been used).

Include Constant <filename>

  Summary: Causes inclusion of an external file of constants. The
file must be in a similar format to that of the Commodore-Amiga
assembly include files.

Include Struct <filename>

  Summary: Causes inclusion of an external file of Struct code. The
code is compiled as if it had appeared at that place in the source
program.

Lib Call [<result-variable>=]<function-name>[(<parameter1>
  [,<parameter2>[,...]])]

  Summary: Calls an external function defined in the
StructLibraryDefinitions file. The function's library must have been
opened with the Open Library command.

Load Address <variable>=<program-element>

  Summary: Loads the memory address of the program-element (label,
procedure, or variable) into the specified variable. Local elements
have precedence over global elements of the same type.

Local <variable1>:<type>[=<initial-value>][,<variable2>:<type>
  [=<initial-value>][,...]]

  Summary: Defines the name, type, and possibly initial value of
one or more local variables. The variables will only be accessible
from within the procedure in which they are defined.

Long Data [(label)] <data>

  Summary: The specified data is placed in the object file as
longword values. If the command is given while in a procedure, the
data is held until the end of the procedure, then output. The
optional local label will point to the beginning of the data.

Long Peek <variable>=<memory-location-expression>

  Summary: The memory-location-expression is evaluated, and the
longword value found at that location is placed in the receiving
variable.

Long Poke <memory-location-expression>,<value-expression>

  Summary: The memory-location-expression is evaluated, and the
result of the value-expression is written to that place as a
longword.

Loop [start-assignment];[truth-expression];[step-assignment]

  Summary: Marks the start of a Loop structure. The
start-assignment, if specified, will be performed and the block of
code in the loop will be executed. Then the step-assignment, if
specified, will be performed and the truth-expression will be
checked. If the truth-expression is true then the block of code will
be executed again. If no truth-expression is specified it will cause
an infinite loop.

Negate <variable>

  Summary: A mathematical negation will be performed on the
specified variable. This will have the effect of reversing the sign
of the value contained in the variable.

Next

  Summary: Marks the end of a For/Next loop.

Not <variable>

  Summary: A mathematical NOT will be performed on the specified
variable. This will have the effect of reversing the states of the
bits that make up the value contained in the variable.

Open Library <library>

  Summary: Tells Struct to open the specified library during the
program's startup code. An external function's parent library must
be opened before the function can be used.

Or <mask-expression>,<variable>

  Summary: Performs a mathematical OR between the mask-expression
and the variable. The result is left in the variable.

Peek <variable>=<memory-location-expression>

  Summary: The memory-location-expression is evaluated, and the
value found at that location is placed in the receiving variable.
Struct will determine the size of the memory access based upon the
sizes of the variables and numeric literals in the
memory-location-expression.

Poke <memory-location-expression>,<value-expression>

  Summary: The memory-location-expression is evaluated, and the
result of the value-expression is written to that place. Struct will
determine the size of the memory access based upon the sizes of the
variables and numeric literals in the memory-location-expression.

Print <memory-location-expression>

  Summary: The memory-location-expression is evaluated, and the
null-terminated string found at that location is printed to standard
output. Dos library must be opened with the Open Library command
before the Print command can be used.

Proc Call [<result-variable>=]<procedure-name>[([<parameter1>],
  [<parameter2>][,...]>)]

  Summary: Calls the specified procedure. The optional parameters,
if given, will be passed to the procedure. If a result-variable is
given, the procedure's return value will be placed into it. The
procedure must have been defined at some point before the Proc Call
command.

Procedure [<return-type>=]<procedure-name> [(<variable1>:<type>
  [=default-parameter],<variable2>:<type>[=default-parameter]
  [,...]>)]

  Summary: Marks the beginning of the implementation of a procedure.
The optional variables, if specified, will be used to accept
parameters from other procedures calling this one. The return-type,
if given, indicates that the procedure returns a value of the
specified type.

Recursive <procedure-name> [([<parameter1>],[<parameter2>][,...]>)]

  Summary:  Calls the specified procedure. The optional parameters,
if given, will be passed to the procedure. If a result-variable is
given, the procedure's return value will be placed into it. The
procedure must have been defined at some point before the Recursive
command. Before the call is made, all of the local variables will
be saved onto the stack.

Regload [<variable1>[,<variable2>[,...]]]

  Summary: Loads the specified register variables onto their
registers. If no variables are specified, all register variables
will be loaded.

Regsave [<variable1>[,<variable2>[,...]]]

  Summary: Saves the specified register variables off to memory. If
no variables are specified, all register variables wil be saved.

Remainder <variable>

  Summary: Places the remainder from a previous hardware division
into the specified variable.

Repeat

  Summary: Marks the beginning of a Repeat/Until loop.

Set Startup <procedure-name>

  Summary: Sets which procedure will be executed when the program is
run. The specified procedure must already have been defined.

String [(label)] <data>

  Summary: Works exactly like the Byte Data command, except that a
null character is appended to the end of the data.

Until <expression>

  Summary: Marks the end of a Repeat/Until loop. The code in the
loop will be continually executed until the specified expression is
determined to be true.

Version <version-number>

  Summary: Specifies which version of Struct a program was written
for. Never produces any code, but may cause Struct to generate a
warning.

Wend

  Summary: Marks the end of a While/Wend loop.

While <expression>

  Summary: Marks the beginning of a While/Wend loop. The code within
the loop will be repeatedly executed until the expression is
determined to be false. If the expression is already false before
the first iteration, the code will be skipped.

Word Data [(label)] <data>

  Summary: The specified data is placed in the object file as
word-length values. If the command is given while in a procedure,
the data is held until the end of the procedure, then output. The
optional local label will point to the beginning of the data.

Word Peek <variable>=<memory-location-expression>

  Summary: The memory-location-expression is evaluated, and the
word-length value found at that location is placed in the receiving
variable.

Word Poke <memory-location-expression>,<value-expression>

  Summary: The memory-location-expression is evaluated, and the
result of the value-expression is written to that place as a word.

Zend

  Summary: Marks the end of a Zeroloop structure.

Zeroloop <variable>

  Summary: Marks the beginning of a Zeroloop structure. The code
within the loop will be repeatedly executed as the variable ranges
from its starting value to zero.



5. Tutorial
-----------

------------------------------
Define Procedure, Set Startup,
Procedure, End Procedure
Proc Call, Recursive
------------------------------

  All code you write is organized into procedures. First, a
procedure is DEFINED as follows:

Define Procedure [<return-type>=]<procedure-name> [(<variable1>:
  <type>[=default-parameter],<variable2>:<type>[=default-parameter]
  [,...]>)]

  For example:

Define Procedure WORD=Video (Ai:BYTE=30, Youta:WORD=6, Moemi:LONG)

  Modern programming standards would require you to define all
procedures at the start of your program; however, Struct will permit
you to define a procedure at any point before it is actually
implemented.
  Next, a procedure is IMPLEMENTED as follows:

Procedure [<return-type>=]<procedure-name> [(<variable1>:<type>
  [=default-parameter],<variable2>:<type>[=default-parameter]
  [,...]>)]
        [...]
[program statements]
        [...]
End Procedure

  Within the procedure, the variables given will be defined as local
variables of the type specified as if the Local command had been
used.
  The implementation for the above example would be:

Procedure WORD=Video (Ai:BYTE=30, Youta:WORD=6, Moemi:LONG)
        [...]
[program statements]
        [...]
End Procedure

  At some point in your program, you must use the Set Startup
command, which takes the following form:

Set Startup <procedure-name>

  The named procedure must have been already defined. When your
compiled and assembled program is run, it will first perform some
set-up operations and then call the procedure named with Set
Startup. The startup procedure may call other procedures, but when
it ends the program terminates.
  Procedures normally call other procedures by using the Proc Call
command, which looks like this:

Proc Call [<result-variable>=]<procedure-name> [([<parameter1>],
  [<parameter2>][,...]>)]

  When a procedure call occurs, all register variables (see the set
of "Register" commands) are first saved off. Then the variables
listed are loaded into the hardware registers and the specified
procedure is called. When the called procedure takes control, it
immediately saves off the passed values into the variables specified
in its Define Procedure statement. The first parameter specified in
the Proc Call command is placed into the first variable listed in
the Define Procedure statement, and so forth.
  For example:

Proc Call Len=Video (,Takashi,)

  Note that only the second parameter has been specified; the first
and third have been omitted. Because the receiving variable Ai was
given a default parameter in Video's Define Procedure statement, the
compiler will pass that value as the first parameter. When
procedure Video takes control, Ai will contain 30 and Youta will
contain the value of Takashi, but Moemi will be undefined. A call
of this type is legal, but must be used with care. If some
variables are going to be undefined, the called procedure must have
a way of detecting this.
  If one or more of your parameters is of a different type than its
receiving variable, Struct will warn you. The reason for this is
that Struct does not perform any sign-extension on the parameters.
If you pass a WORD parameter to a LONG variable, the contents of the
upper word of the receiving variable will be uncertain. On the other
hand, if you pass a WORD to a BYTE, you will face the truncation
problem described under the Basic Concepts chapter.
  If you indicate that a procedure returns a value, a local variable
with the same name as the procedure, having the type you specified,
will automatically be defined within it. In the above example, a
WORD-length variable named VIDEO would be defined. When the
procedure ends, the contents of this variable will be passed to the
result-variable specified by the calling procedure, LEN in this
example. Note that VIDEO is a normal local variable in all
respects, and can have its value changed at whim during the
procedure's execution. Only its value at the end of the procedure
matters.
  Although simple procedure calls will be fine for most situations,
you may occasionally need to use recursion. You can't use Proc Call,
as each procedure has only one section of memory for variables, and
the original contents of that section would be overwritten during
the call. You must instead use the Recursive command, which takes
the following form:

Recursive [<result-variable>=]<procedure-name> [([<parameter1>],
  [<parameter2>][,...]>)]

  The only difference between Recursive and Proc Call is that,
before making the call, Recursive saves all of the procedure's local
variables onto the stack. The variables are restored after the call
is complete. Recursive should be used in any situation where a
procedure calls itself, or where a chain of calls among procedures
could form a loop that would cause a procedure to call itself.
  Be warned that if a procedure has too many local variables and
there are too many Recursive calls outstanding at one time, you may
overflow the stack.

* Many languages use the stack for ALL local variables. This is why
* only a few procedure calls in such languages can rapidly exhaust
* the stack space. Struct, on the other hand, avoids mucking about
* with the stack and instead keeps all of its variables in a
* separate memory area. ANY Struct program which does not use
* recursion should be able to run in only a few K of stack space.
* The disadvantage to this approach is that if recursion IS used,
* Struct must laboriously copy all local variables onto the stack,
* whereas other languages must only change the stack pointer to
* allocate more space. The decision to use the copy-method was made
* because recursion shows up most often in situations that Struct is
* not designed to handle - i.e. mathematical simulations. Another
* important factor was that in programs conforming to modern style
* guidelines, procedures will have relatively few variables. Making
* procedures smaller tends to reduce the "clutter" within each of
* them.

-------------
GLOBAL, LOCAL
-------------

  These commands allow you to define variables for use in your
program. Their formats are:

Global <variable1>:<type>[=<initial-value>][,<variable2>:<type>
  [=<initial-value>][,...]]
Local <variable1>:<type>[=<initial-value>][,<variable2>:<type>
  [=<initial-value>][,...]]

where the type of the variable is either BYTE, WORD, or LONG.
  For example:

Local Farfoe:BYTE, Nearfoe:WORD, Zzgo:LONG

* It's a bad idea to use BYTE length variables unless there is a
* specific need to deal with byte-length values (such as using the
* variable with the Peek or Poke set of commands). It takes the
* 68000 no longer to read a word from memory than it does to read a
* byte, and some of the 68000's hardware operations cannot deal with
* byte-length values. In these cases Struct must sign-extend the
* variable in question. It's often faster just to use a WORD
* variable in the first place.

  Global variables can be accessed from any procedure in your
program. Local variables can be accessed only from the procedure in
which they are defined. Variables can be defined at any time before
they are used, but good programming style would demand that all
global variables are defined at the top of the program, and all
local variables are defined at the beginning of the procedure to
which they belong.
  It is possible for a global and a local variable to both have the
same name. If so, all accesses within that procedure are assumed to
be directed at the local variable. This allows procedures included
with the Include Struct command to work properly no matter what
global variables have been defined.
  If an initial-value is specified for a global variable, it will be
assigned to the variable at the beginning of program execution.
An initial-value for a local variable is assigned each time the
procedure is called. Initial values must be resolvable at compile
time.

* There's no efficiency advantage or disadvantage to using an initial
* value for a variable, as opposed to simply assigning it at the
* beginning of the procedure or program. The initial value feature is
* provided simply as an aid to program clarity.

  One additional variable type, STACK, is being provided on an
experimental basis. See the Q & A chapter for more information.

---------------------------
CONSTANT, CONSTANT EVALUATE
---------------------------

  Constants are an important part of any language. They allow values
which might change to be represented by a string of characters. This
string of characters can be used in the code instead of the number.
Changing the number becomes a matter of changing the constant
assignment, rather than the search-and-replace horror there would be
if the value was "hard-wired" into the code. In Struct, constants
can be used in any situation where a numeric literal could legally
appear.
  A constant is defined in Struct like this:

Constant <constant1>[=<value>][,<constant2>[=<value>][,...]]

  For example:

Constant Dendybar, Eldulac=10, Kessel=17

  If a value is specified, it is assigned to the constant. An
initial assignment is optional; at any time after the constant has
been defined, it can be assigned a value as if it was a normal
variable. For example:

Dendybar=17+Errtu

  In the above example, "Errtu" could be either a variable or another
constant. It is permitted to be a variable because, by default,
Struct constants are actually replacement strings. When the constant
Dendybar is used in a variable assignment such as:

Zzgo=12+Dendybar-19

the assignment will be evaluated as if it was:

Zzgo=12+17+Errtu-19

  One implication of this is that a constant's value is not
necessarily stable. If Dendybar is set to equal a math expression
which contains the constant Eldulac, and Eldulac is later changed,
then the effective value of Dendybar will probably change as well.
  Struct has a switch, controlled by the command Constant Evaluate,
which can be used to change this. The format of the command is:

Constant Evaluate <ON/OFF>

  When Constant Evaluate is set to OFF (the default), constants will
operate as described above. When it is set to ON, however, all
constants will be evaluated at the time they are assigned a value,
instead of the time when they are used in an equation. This value
can still be composed of numeric literals and other constants, but
it must reduce to a numeric literal - variables cannot be used,
because their values are not known at compile time. In the situation
described above, since Dendybar would be evaluated at the time of
assignment, its value would be fixed and would not change if
Eldulac's did.
  Constant Evaluate can be switched ON or OFF as desired at any time
in your program.

* If you don't care what Constant Evaluate is set to, turn it ON.
* This will speed up the compiler marginally since a constant's
* value will only have to be determined once, rather than every time
* it is used in an equation.

  If you're careless in assigning values to constants, you can
accidentally cause a loop situation. If, for example, your program
contained the following:

Local A:WORD
Constant B, C
B=C
C=B
A=B

Struct's constant evaluation subroutine would replace the B with C,
notice that there were still constants to replace, replace the C
with B, notice that there were still constants to replace, replace
the B with C...
  If constant replacement seems to be happening too many times, the
subroutine will eventually give up and report an error.

--------------------------------
DATA REGISTER, ADDRESS REGISTER,
DATA ABSOLUTE, ADDRESS ABSOLUTE,
REGLOAD, REGSAVE
--------------------------------

  The "Register" commands are Struct's most powerful optimization
features. They allow variables to be assigned to the 68000's
hardware registers, which speeds up nearly all operations on those
variables. These assignments can be changed at any point in your
program. The formats of the Register commands are:

Data Register [<variable1>[,<variable2>[,...]]]
Address Register [<variable1>[,<variable2>[,...]]]

  For example:

Address Register I-1, I-2, I-3, Atros

  Data Register will cause each of the specified variables to be
assigned to one of 7 data registers (specifically, registers D1
through D7), and Address Register will assign each variable to one
of 3 address registers (A3 through A5). Thus, in the example shown
above, Struct would report an error because too many variables have
been specified.
  Once a variable has been assigned to a register, it is still "off"
the register, in memory. It will stay there until the variable is
used in an operation. At that time the variable will be loaded onto
the register it was assigned to and the operation will be performed.
  Each Data Register command overrides all previous Data Register
commands. If a variable which is currently assigned to a data
register does not appear in the new Data Register command, its
contents are saved off to memory (if appropriate) and it is no
longer considered to be a register variable. The same is true of the
Address Register command.

* The best variables to assign to data registers are loop variables.
* "Pointer" variables (for use with the Poke and Peek sets of
* commands) are best assigned to address registers.
* Many of Struct's commands will not work very well if their
* parameters are address register variables. For example, the And
* command would have to move its parameter to a scratch data
* register, perform the AND, and then move it back to the address
* register. This is because the 68000's hardware AND operation
* cannot be used on an address register. Even so, performing a
* command on an address register variable is usually faster than
* performing it on a variable stored in memory.

  Although the Register commands are fine for most situations,
there are times when it is important to have a variable assigned to
a particular register (usually due to inclusion of inline assembly
with the Asm command). You cannot control which registers receive
which variables with the Register commands - they will be assigned
in the way that Struct believes is most efficient. In order to force
variables onto certain registers, you must use the "Absolute"
commands, which look like this:

Data Absolute <[<variable1>][,][<variable2>][,][...]]]>
Address Absolute <[<variable1>][,][<variable2>][,][...]]>

  For Data Absolute the variables listed will be assigned, in
order, to D1, D2, D3, and so on. Address Absolute will perform
similarly, starting with A3.

  For example:

Data Absolute I-2, , NG, Fiber

would assign I-2 to D1, NG to D3, and Fiber to D4. Note that the
extra comma causes D2 to be skipped. Unlike the Register commands,
the Absolute commands will not affect registers that are not
specified; whatever variable was assigned to the register will
remain assigned after the command. Thus, in the above example, D2
and D5 through D7 would not be altered (unless they happened to
contain one of the three variables that WERE specified).
  As previously mentioned, the Register and Absolute commands only
assign variables to registers; the variable is not actually brought
onto the register until it is used. There are times when it is
necessary to make sure that a variable is either on or off of a
register. Inline assembly is a good example; another important
situation is the flow-of-control pitfall described below. The
solution to this is the commands Regload and Regsave:

Regload [<variable1>[,<variable2>[,...]]]
Regsave [<variable1>[,<variable2>[,...]]]

  Regload can either be used with parameters to load specific
variables onto registers, or without parameters to load all
variables currently assigned to registers. In either case, the
variables in question are immediately brought onto the registers
they have been assigned to. If all of the variables are already on
their registers, nothing will happen.
  Regsave performs identically, except that it causes variables to
be saved back to memory. The variables are still considered to be
assigned to their registers, and will be reloaded the next time they
are accessed.

  For example:

Regsave AB, CB, UY

will cause variables AB, CB, and UY to be taken off their registers.
No other register variables will be affected.

* A problem with allowing register assignments to be changed at any
* time during the program can be illustrated by this example:
*
* Data Register A
* Repeat
* A=A+B
* Until A>1000
*
* If variable A was not a register variable before this code was
* reached, it will be assigned to a register but remain in memory.
* After the Repeat loop has started, Struct will process the
* statement A=A+B. At this point it loads A onto a register and
* performs the math. Now Struct hits the Until which terminates the
* loop, and it has a problem: the flow of control may well lead back
* to the beginning of the loop, but A was not on a register at the
* beginning of the loop! The only thing that Struct can do is to set
* A and all of the other register variables back to the way they
* were at the beginning of the loop. This means that as the loop is
* executed, A will be repeatedly loaded and saved to memory, making
* the loop take longer than if no register variables had been used
* at all! But by simply adding an extra line, like so:
*
* Data Register A
* Regload A
* Repeat
* A=A+B
* Until A>1000
*
* this problem can be avoided and the loop will run very fast. This
* pitfall can occur in conjunction with all of the commands and
* statements that change the flow of control within a program. This
* includes all of the loops, the If set of statements, and the Goto
* command. So be careful to Regload what a loop needs ahead of time,
* and don't change register assignments within a loop without a very
* good reason.

-------------------------
IF, ELSE, END IF, BACK IF
-------------------------

  An If statement in Struct takes the following form:

If <condition1> [<&/|> <condition2> [<&/|> <condition3> [...]]]
        [...]
[program statements]
        [...]
[Else]
        [...]
[program statements]
        [...]
End If

where a <condition> is of the form:

<math-expression1><relational-operators><math-expression2>

  For example:

If A>B | A=<B & A=C | A<>B & A<>C

  To Struct, the ampersand (&) is a logical AND and the vertical bar
(|) is a logical OR. Unlike other languages, Struct does not allow
parentheses to be used to alter the order of comparisons. The reason
for this is the same as the reason, described in the Basic Concepts
section, for not using RPN in math expressions. Instead, in an If
statement the AND operator ALWAYS has a higher priority than the OR
operator. That means that the example given above would be
considered to be true if any of the following were true:

1. A>B
2. A=<B and A=C
3. A<>B and A<>C

  Evaluating the conditions in this way can make it difficult for
certain situations to be tested for. Consider, for example, the
following If statement as it might appear in another language:

If (X=Y or Y=Z) and (X<>Y or Y<>Z)

  It would be possible to write this as an If statement in Struct,
but it would be a rather long one, as it would be necessary to check
independently for four possible situations. A simpler solution under
Struct (and one which will run faster, too) is to split the If into
two Ifs:

If X=Y | Y=Z
   If X<>Y | Y<>Z
        [...]
[program statements]
        [...]
   End If
End If

* During program execution, the checks in an If statement are
* carried out in the same order that they appear in the statement.
* This means that the OR sections most likely to be true should be
* placed at the beginning of the If statement, and the conditions
* related by ANDs should be arranged so that the ones most likely to
* fail are at the beginning of that OR section.

  Struct includes a statement, unique to it, called Back If. The
syntax of it is simply:

Back If

  If the program flow reaches a Back If, it will be directed back to
the beginning of the most recent If statement. The condition(s) in
the If statement will be re-evaluated, and the program flow will be
altered accordingly. This may result in the path to the Back If
being taken again, which will eventually cause execution to return
to the If again.
  An If statement which includes a Back If in one of its blocks is
very similar to a While/Wend loop: as long as the condition is true
(or, perhaps, as long as it is false) the same sequence of program
code will be executed over and over again. In many situations,
however, Back If is more intuitive than While/Wend. This is
especially true in cases where taking the "loop" path is considered
to be an unusual event.

----------------------------
FOR, NEXT, REPEAT, UNTIL,
WHILE, WEND, LOOP, END LOOP,
ZEROLOOP, ZEND, EXIT
----------------------------

  The format of a For/Next loop is:

For <variable>=<start-expression>;<end-expression>
  [;<step-expression>]
        [...]
[program statements]
        [...]
Next

  For example:

For Millions=1;1000000;17

  The For loop structure repeats a block of code over and over,
altering the loop variable on each iteration, until the loop
variable has passed the value of the end-expression. The loop
variable is set to the start-expression before the first iteration
begins, and is altered by the value of the step-expression at the
end of each iteration. If no step-expression is specified, the
variable is incremented by 1. A step-expression which resolves to a
negative number is legal.
  The loop's to-value and step-value are computed and stored in
memory before the first iteration of the loop. If they are based
upon a variable, changing the value of that variable during the loop
will have no effect on the number of iterations performed.

* You should try to avoid having to use a step-expression. If you
* use a step-expression, the executable code must do additional work
* at the end of each iteration because of the possibility that the
* step will resolve to a negative number.

  The format of a Repeat/Until loop is:

Repeat
        [...]
[program statements]
        [...]
Until <expression>

  For example:

Until Cinemaware=Dead

  The Repeat/Until loop repeats a block of code, over and over,
until the specified expression is determined to be true. This check
is carried out at the end of the block of code, where the Until
appears. Normally, the expression will initially be false. During
one of the iterations one of the expression's components will be
altered so that the expression becomes true. However, even if the
expression is initially true, the block of code will always be
executed at least once.

  The format of a While/Wend loop is:

While <expression>
        [...]
[program statements]
        [...]
Wend

  For example:

While Streamline=<Macek

  In a While/Wend loop, the specified expression is first checked
for truth. If it is false, the block of code is skipped and never
executed. If it is true, the block is executed once. The expression
is again checked for truth, and if it is still true then the block
will be executed again. A check for truth will again be made, and
this will continue until the expression is finally determined to be
false.

  The format of a Loop/End Loop structure is:

Loop [start-assignment];[truth-expression];[step-assignment]
        [...]
[program statements]
        [...]
End Loop

  For example:

Loop X=0;X<10;X=X+1

  When the Loop statement is reached the assignment in the
start-expression, if specified, is performed. The block of code is
then executed. At the end of the block the assignment in the
step-expression, if one is provided, is performed. The
truth-expression is then evaluated. If it is determined to be true,
the block is executed again. The step-expression and
truth-expression are then used once again.
  The example above is the same as a For/Next loop from zero to ten.
  Note that the truth-expression is optional. If none is given, it
will result in an infinite loop. The only way to break out of the
loop is with the Exit statement.

* In the compiled code, the start-expression is treated as if it had
* appeared right before the Loop statement, and the step-expression
* is treated as if it appeared right before the End Loop. There's no
* efficiency advantage or disadvantage to including them in the Loop
* statement, so simply do whatever seems most intuitive.

  The format of a Zeroloop structure is:

Zeroloop <variable>
        [...]
[program statements]
        [...]
Zend

  For example:

Zeroloop Chalker

  The Zeroloop construct is unique to Struct.
  A Zeroloop is like a For/Next loop from the variable's current
value to zero, inclusive. The code within the loop will initially be
performed without changing the value of the loop variable. When the
Zend statement is reached, the value of the variable will be reduced
by one. If the variable was equal to zero during the previous
iteration, the loop terminates. Otherwise, the program flow branches
back to the Zeroloop statement and the block is executed again.
  A Zeroloop has a potential problem, because it is built around a
68000 instruction called DBRA. The variable is considered to be an
UNSIGNED number. This means that, because of the way signed numbers
are implemented, a variable with an initially negative value will
keep being decremented until it finally "turns over" to a positive
value. The loop will continue to repeat until that positive number
is finally reduced to zero.

* It is very important to make sure that a Zeroloop's variable is a
* WORD or LONG. If you use a BYTE, it will work, but Struct will
* have to sign-extend the variable on each iteration, which wastes
* time. Furthermore, the variable should ALWAYS be assigned to a data
* register, for the DBRA instruction is only designed to work with
* data registers. If the variable is in memory, Struct must load it
* onto a register to perform the DBRA. There is little point in using
* Zeroloop without having the variable on a data register.

  The format of the Exit statement is simply:

Exit

  When the Exit statement is encountered, the program flow will
immediately jump to the end of the most recently defined loop
structure, as if the loop had terminated normally. A loop variable,
if there was one, will remain set to whatever it was when the Exit
statement was encountered.

----------------------------
OPEN LIBRARY, LIB CALL, CALL
----------------------------

  A very important feature of any language is the ability to make
calls to external functions. Struct implements this feature with the
Lib Call command and a text file, "StructLibraryDefinitions", that
contains all necessary information on those functions. Using the
procedure described in Appendix A, any function which is part of a
library conforming to standard Amiga library specifications can be
added to the library definition file and then used via the Lib Call
command.
  Before any function can be accessed, its parent library must be
opened via the Open Library command:

Open Library <library>

  Each library only needs to be opened once per program. The Open
Library command does not itself generate any code - it just tells
the compiler that you're going to be using functions contained in
that library. The library will be opened during the program's
startup phase.
  The pointer to the library will be placed in a global LONG
variable called "LIBRARY<library>BASE". For example, if you've just
opened the Graphics library, its pointer will be in a variable
called LIBRARYGRAPHICSBASE. If necessary, you can use this pointer
to access the library's data structures.
  Once the library has been opened, an actual function call is made
with the Lib Call command:

Lib Call [<result-variable>=]<function-name>[(<parameter1>
  [,<parameter2>[,...]])]

  For example:

Lib Call CopyBuffer1=AllocMem(8192,0)

  Using the information found in the library definition file, the
parameters, if any, are loaded onto their proper registers and the
function call is made. Specifying a result-variable is optional; if
one is provided then the function's return value is placed into it.
The function is assumed to return a LONG, so if the result-variable
is smaller than that the return value will be truncated.
  At times you may need to call functions which are not part of a
library. This can be done with the Call command, which looks like
this:

Call <memory-location-expression>

  The memory-location-expression will be resolved, and the 68000
instruction JSR will be used to call the subroutine at that address.
The address is assumed to point to valid machine language code; if
it does not, a guru or crash is likely.
  The Call command does not allow parameters or return variables to
be specified. If you need them, you can use the Absolute and
Regload commands to assign variables to specific registers. You can
use this method to pass parameters, and the called routine can
change the contents of one of the registers before returning to pass
a value back to the main program.

-------------------------
POKE, BYTEPOKE, WORDPOKE,
LONGPOKE, PEEK, BYTEPEEK,
WORDPEEK, LONGPEEK
-------------------------

  The "Poke" set of commands is designed to alter memory. The
formats of these commands are:

Poke <memory-location-expression>,<value-expression>
Byte Poke <memory-location-expression>,<value-expression>
Word Poke <memory-location-expression>,<value-expression>
Long Poke <memory-location-expression>,<value-expression>

  For example:

Word Poke YPos*8+XPos, Traverser+3

  The Poke commands all work very much the same: the
value-expression is computed, and then moved into the memory
location which is determined by computing the
memory-location-expression. The difference is in the size of the
memory access. The Byte, Word, and Long versions of the Poke command
force a byte, word, or longword at the specified address to be
affected.
  The general version of Poke, however, forces Struct to determine
the size of the memory access. Struct will examine the
value-expression and set the size of the memory access to the
type of the largest variable or constant it finds there. This, of
course, is very error-prone, so the general Poke is only provided as
a shorthand for programmers who know what they're doing. You should
never use the general Poke unless you're SURE that the types of the
variables involved will never be changed.

  The "Peek" set of commands reads memory into variables. Their
formats are:

Peek <variable>=<memory-location-expression>
Byte Peek <variable>=<memory-location-expression>
Word Peek <variable>=<memory-location-expression>
Long Peek <variable>=<memory-location-expression>
 
  For example:

Byte Peek BeamPos=Screenbase+Offset

  The Peek commands determine the value of the
memory-location-expression, then move the value found at that memory
location into the specified variable. For the general Peek, the size
of the memory access is the size of the receiving variable. In most
situations this is appropriate, but if it is not then the Byte,
Word, and Long versions can be used to cause a byte, word, or long
memory access instead. If necessary, the value found at the memory
location will be sign-extended or truncated as appropriate to make
it fit properly in the receiving variable.

---------------------
BYTE DATA, WORD DATA,
LONG DATA, STRING
---------------------

  This set of commands allows the inclusion of predefined data in
the object code. Since it is much easier to include large amounts of
data by using inline assembly and sending an assembler inclusion
directive, these commands are normally used for small amounts of
data, such as lookup tables and ASCII text. Their formats are:

Byte Data [(label)] <data>
Word Data [(label)] <data>
Long Data [(label)] <data>
String [(label)] <data>

  For example:

String (PrintString1) "IQ Stripper",10,"by Ren Hoek",10

  Normally, the data will consist of either numbers or strings of
text delimited by quotes, separated by commas. As seen in the
example, both can be used in the same line. If a label is provided,
it will point to the beginning of the given data.
  These commands can be used either inside or outside of a
procedure. If they are given inside a procedure, Struct will "hang
on" to the data until it reaches the end of the procedure, at which
point it will be output to the object file. Thus, there is no
problem with the data appearing in the middle of blocks of code.
  If a label is specified with the command, and the command is used
from within a procedure, the label will be defined as a local label.
If the command is used outside of a procedure, it will be defined as
a global label.
  Struct does not actually parse any of the given data; it simply
passes it on to the assembler. Thus, errors in the data will not be
flagged by Struct and will cause the assembler to report an error.
  The difference between the Byte, Word, and Long versions is that
each numerical value will be output as a byte, word, or longword
respectively. Strings of text normally cannot be used as part of the
data in the Word Data or Long Data commands; since text is assumed
to be part of the ASCII character set, in which all characters are
byte-length by definition, assemblers will usually refuse to output
it in a larger format.
  The String command is identical to the Byte Data command, except
that a null character is appended to the end of the data. This is
convenient if the data is going to be used with the Print command or
some operating system functions, as they require null-terminated
strings. If the example shown above was used with the Print command,
the result would be:

IQ Stripper
by Ren Hoek

  This is because each of the 10s is a linefeed, which is the
Amiga's end-of-line character. Another useful number to know about
is 34, which the the ASCII code for a quote.

-----------------------
INCLUDE STRUCT, INCLUDE
CONSTANT
-----------------------

  The format of Include Struct is:

Include Struct <filename>

  The filename should not have quotes around it. If a file with that
name exists, Struct will immediately compile it as if it was a part
of the original source file. When the end of the included file has
been reached, Struct will continue compiling the original file where
it left off. Included files can themselves contain the Include
Struct command.
  This command, used in conjunction with local variables, allows the
creation of a "library" of external procedures which can be included
in larger projects as necessary.
  In order to deal with external functions in general, and the Amiga
operating system routines in particular, it is important to be able
to use external files containing constant definitions. Struct's
support for these files is far from perfect, but should be
sufficient for most situations. Support is provided through the
Include Constant command, which looks like this:

Include Constant <filename>

  Most assembly language include files written to Commodore-Amiga
specifications should work with this command. Others may require
some alterations. Struct will look for the following things within
the constant file:

<constant> EQU <value>
<constant>=<value>
<constant> EQU <value> << <shift-amount>
<constant>=<value> << <shift-amount>

  The first two forms will cause a constant to be defined and
assigned the specified value. The second two forms cause the value
to be bit-shifted to the left by the shift-amount. This is
equivalent to multiplying the variable by 2 to the power of the
shift-amount. The value can be represented in hexadecimal if it is
preceeded by a dollar sign.
  The name of the constant must start at the left margin.

STRUCTURE <structure-name>,<start-value>

  This will cause an internal variable to be set to the start-value,
which is normally zero. The structure-name is provided only for
human benefit, and is ignored by Struct. The STRUCTURE directive
must not start at the left margin.

BYTE <constant>
UBYTE <constant>
WORD <constant>
UWORD <constant>
SHORT <constant>
USHORT <constant>
LONG <constant>
ULONG <constant>
APTR <constant>
BOOL <constant>
FLOAT <constant>
CPTR <constant>
RPTR <constant>

  All of these directives will cause the specified constant to be
defined and assigned the current value of the internal variable. The
internal variable's value will then be increased by a certain amount,
which depends on the directive used. APTR (Address PoinTeR), for
example, will raise the value by 4. The directive must not start at
the left margin.

STRUCT <constant>,<value>

  The specified constant will be defined and assigned the current
value of the internal variable. The variable will then be
incremented by the specified value. The STRUCT directive must not
start at the left margin.

LABEL <constant>

  The specified constant will be defined and assigned the current
value of the internal variable. The variable will not be altered.
The LABEL directive must not start at the left margin.

IFND <constant>
ENDIF

  If the specified constant has not been defined, the data between
the IFND and the ENDIF will be processed. Otherwise, it will be
ignored. This directive must not start at the left margin.

INCLUDE "<filename>"

  Struct will stop reading the current constant file and start
reading a different one with the specified filename. The quotes
around the filename are required. When Struct reaches the end of the
new constant file, it will continue reading the old one where it
left off. Included files can also contain the INCLUDE directive. It
must not start at the left margin.
  It is important to remember that Struct's mathematical order of
operations is not the same as other languages, and so some
expressions will need to be altered to still achieve the desired
result. If Constant Evaluate is set to ON before the constant file
is included, "scratch constants" can be used as temporary variables
to help rewrite expressions. This is better than simply doing the
math yourself and replacing the value, because people looking at the
constant file later on can more easily see how the result was
obtained.

------------
ASM, END ASM
------------

  Occasionally, you'll need to do things that can't be done in
Struct, or can't be done RIGHT. For these situations, you need Asm
and End Asm, which are as follows:

Asm
     [...]
[assembly code]
     [...]
End Asm

  Everything between Asm and End Asm will be passed directly through
to the assembler, whether or not it is actually valid assembly code.
This means that any mistakes in the assembly code will be reported
by the assembler, not by Struct.
  In order to use inline assembly properly, you need to understand
how Struct manages its variables and the 68000 hardware registers.
During a program's startup code, enough space is allocated via the
Exec AllocMem function to hold all of the program's variables. The
pointer to this memory area is permanently placed in register A2,
and A2 is used with an index for all accesses to the variable base.
Currently, the way to access a global variable is:

StructGlobalvar<variable-name>(A2)

and the way to access a local variable is:

StructP<current-procedure>var<variable-name>(A2)

  For example:

StructPFaramirvarSimarillion(A2)

  At some point in the assembly file there will be an equate such
as:

StructPFaramirvarSimarillion EQU 58

which will make everything work out all right. This method may
change at some point in the future, so it is a much better idea to
use one of the Absolute commands and a Regload command to bring the
variable onto a known register.
  Struct uses data registers D1-D7 and address registers A3-A5 to
hold register variables. You should make use of these registers only
if you have made SURE that they are empty. Register A6, by Amiga
convention, is used to hold library bases. Struct keeps track of
which library pointer is on A6 at any particular time, so if you
need to call library functions you must save the contents of A6 and
restore them after you're done. A7 is always used as the hardware
stack, so that leaves registers D0, A0, and A1. Struct uses these
only as scratch registers, so they can be safely used by your
assembly code.
  When accessing the variable base, remember to make sure that the
variable in question is not on a register. Struct saves the contents
of register variables to memory only when they are un-assigned or a
Regsave command is used. The variable's value in memory will almost
certainly not match its value on the register.

----
GOTO
----

  Goto causes a jump in program flow to the specified label. Its
format is:

Goto <label>

  For example:

Goto Iczer-4

  The label must be a local label, defined at some point within that
procedure. It is not legal to jump to global labels, or to labels
in other procedures. You cannot jump to labels defined with the
"Data" set of commands, as those labels actually point to locations
outside of the procedure.

-----
PRINT
-----

  The format of the Print command is:

Print <memory-location-expression>

  The memory-location-expression can include labels, variables, and
constants. After it is resolved, a search will be performed for the
first null character after the computed address. The address is
assumed to be that of a string, and the null character is assumed to
mark the end of it. The null character is not considered to be part
of the string. The address of the string, and its length, will be
passed to the Dos Write function with instructions to print it to
standard output. Normally, this is the CLI from which the program
was run.
  Before the Print command can be used, the Dos library must have
been opened with an Open Library command. Print does not actually
use the Output or Write offsets in the library definition file; it
simply requires the Dos library pointer that Open Library provides.
  Struct's Print is very limited compared to other languages, but
is enough for simple tasks.

------------------
AND, OR, EOR, NOT,
NEGATE
------------------

  This set of commands deals with bit operations.
  The And command looks like this:

And <mask-expression>,<variable>

  The mask-expression will be evaluated, and a mathematical AND will
be performed between it and the contents of the variable. The result
will be left in the variable.
  The format of the Or command is:

Or <mask-expression>,<variable>

  A mathematical OR will be performed between the mask-expression
and the value held in the variable. The result will be left in the
variable.
  The Eor command is used with:

Eor <mask-expression>,<variable>

  The result of the mask-expression will be determined, and a
mathematical EOR will be done between it and the contents of the
variable. The result will be left in the variable.

* And, Or, and Eor will usually work much faster if the variable is
* on a data register.

  The Not command is:

Not <variable>

  An operation called "one's complement" will be performed on the
value of the variable. This "inverts" the bits that make up the
number, turning all ones to zeros and vice versa. The result is left
in the variable

  The Negate command looks like this:

Negate <variable>

  Negate replaces the value in the variable by its "two's
complement". This is done by taking the one's complement and adding
one to it. Because of the way signed numbers are represented inside
the computer, this simply reverses the sign of the number. It is
exactly the same as subtracting the value in the variable from zero,
but faster.

------------------------
REMAINDER, LOAD ADDRESS,
VERSION
------------------------

  Remainder is a "dirty" command that should be used with caution.
Its syntax is:

Remainder <variable>

  For example:

Remainder ShipExcess

  Whenever hardware division is performed, the result is both a
quotient and a remainder, both of which are left in the 68000
register D0. Struct normally takes only the quotient for the
result, and ignores the remainder. The Remainder command, used
immediately after a hardware division, tells Struct to take the
remainder from D0 and place in the specified variable, possibly
sign-extending or truncating.
  This command is unreliable for several reasons. First, anything
that affects register D0, which Struct uses regularly as a scratch
register, will erase the remainder. This could be a multiplication
or another division in the same math expression as the original
divison. Second, there will only be a remainder if actual HARDWARE
division is performed. For example, this assignment:

TRZ=5+16/8

will be completely evaluated at compile time, and TRZ will be
assigned a value of 2. The 68000 division instruction is never used,
so there is no remainder on D0, and the Remainder command will not
work properly.
  Further, using the Remainder command affects the contents of D0,
so it cannot be used twice in a row.
  Often, it is better to do something like this:

Quot=Satori/3
Remain=Quot*3
Remain=Satori-Remain

or

Quot=Satori/3
Remain=Quot*3-Satori
Negate Remain

both of which will work. Remainder, of course, is faster, but
sometimes that just isn't a good enough reason.
  The format of Load Address is:

Load Address <variable>=<program-element>

  For example:

Load Address Caller=PrintString1

  Load Address places the memory location of the program-element in
the specified variable, which must be of type LONG. The
program-element must be either a label, a procedure, or a variable.
If two or more program-elements have the same name - for example, a
local variable and a global label - the compiler will give labels
priority over procedures, procedures priority over variables, and
local elements priority over same-type global elements. In the
example mentioned above, the compiler would load the address of the
global label into whatever variable was given.
  This command is normally used for examining data included with the
"Data" set of commands. Be aware that making any changes to that
data will destroy the program's reentrant property, as you will have
a self-modifying program.
  It is possible to have an external program call a Struct procedure
by using the Load Address command and passing the result to the
other program. Keep in mind, though, that the external program will
need to have the Struct program's variable base pointer on register
A2 at the time of the call. If this is not done, the computer will
almost certainly crash.
  The Version command looks like this:

Version <version-number>

For example:

Version 1.10

  Version generates no code. It is simply a way of saying, "This
program was made with this version of Struct". If the version number
given is higher than the version of Struct that is being used to
compile the program, Struct will generate a warning during the
compilation process. There will be no other direct effects.



6. Optimization
---------------
  Since the whole point behind Struct is speed, it seems appropriate
that there be a separate chapter on optimization.
  Struct tries very hard to make good object code out of what you
give it. But it's only a computer program, and computer programs
aren't as smart as humans. The way in which you write your code
will have an effect - possibly a very great one - on how fast it
runs.
  Most optimization is simply common sense.  Use WORD variables
whenever reasonable. BYTEs are no faster (and may be slower) and
LONGs are slower. Don't split your program into lots of procedures
unless there's a need to do so (normally either clarity or re-use of
code). Procedure calls take time. The example programs are
modularized much more than they need to be, simply because they're
example programs. If you've been reading this manual carefully you
should already have picked up on these, as well as others.
  The greatest enigma is the Register commands. How best to use
them? You know that variables used as pointers belong on address
registers, and variables used as indexes, counters, or holding areas
belong on data registers. But that leaves a lot of leeway. How do
you know when to put a variable on a register? When to change the
register assignments? How to tell if a variable shouldn't be on a
register at all?
  I've given this a great deal of thought myself, and I've come up
with some guidelines. These three principles don't apply to all
situations - they're only generalities - but they should work well
most of the time. As always, use your head before you use your
keyboard.

Principle #1: The time required to execute a 68000 instruction is
the time it takes to perform the memory accesses.

Justification: This is far more true than it first appears. The time
it takes the 68000 to do something is measured in what are called
"clock cycles". It takes 4 cycles to read/write a word to/from
memory. It takes 8 cycles to access a longword (as the 68000 does
this by reading two words). All instructions are one word long (not
counting their parameters) and so take 4 cycles to read.
  Consider this assignment statement:

A = B + C - D

  Assume that all variables are of type LONG. There are four
variables that need to be accessed here, so that's going to take 32
clock cycles. We can see that there is an addition and a subtraction
involved, each of which will take 4 cycles. Also, each instruction
contains a pointer to the variable that is going to be added or
subtracted. Each pointer is one word. Finally, the intermediate
value is going to have to be saved off into variable A. That will
take 4 cycles to read the MOVE instruction, 4 for the pointer to A,
and 8 to actually save intermediate value to A.
  The result: 64 cycles. Is that right? Not quite. We missed an
operation. We counted B, C, and D as being accessed, and mentioned
that C would be added to the intermediate value and D subtracted,
but we forgot about B. The intermediate value is actually going to
be SET to B originally, after which time C is added and D is
subtracted. That setting takes a MOVE instruction (4 cycles) and a
pointer to B (4 cycles). The final total is 72 cycles. The time it
would REALLY take the 68000 to do this is 80 cycles. That's because
the addition and subtraction instructions actually take 8 cycles,
and not the 4 we were assuming. We were very close though. If
they're all register variables, we don't need a pointer or a memory
access to use the variables; we only count the two MOVE instructions
and an ADD and SUBtract. Total time then is only 24 cycles!
  This principle helps you when you're trying to figure out if
something is accessed enough times to make it worth being a register
variable. With a WORD variable, it's going to take 10 cycles to
bring it onto the register and another 10 to save it off eventually.
If the variable is only used once before something makes it get
saved off, it's not worth it to assign it to a register.
  Remember that this principle is only a generalization. Some
instructions do take processing time in addition to their memory
access time - the multiplication instruction, at about 70 cycles, is
a good example. But the simple instructions - moving, adding,
subtracting, sign extending, and others - form the backbone of a
compiled Struct program.

Principle #2: The time spent outside a loop is insignificant
compared to the time spent within it.

Justification: Consider, first, an extreme case. You've just written
a demo. This demo contains a master loop that draws all of the
screen elements, updates them, plays the next fragment of music, and
then does it all again. The part of the demo outside the loop are
the setup and cleanup routines - and if the user spends a few
minutes looking at the demo, the percentage of time spent executing
those outside routines will be very small indeed. Clearly, much more
effort should go into optimizing the code inside the loop than
outside it. In this case, no effort at all should be wasted on the
setup routines, for no one is going to care whether the demo starts
up in one second or three. (The same reasoning was used in the UOut
example program, and no attempts at all were made to optimize the
initialization and finalization routines)
  Now consider a more realistic case, say a For/Next loop from one
to ten. The For/Next loop contains the following line of code:

A = B + C - D

  In the justification of the previous principle, we saw that this
line takes 24 cycles with register variables, and 80 without.
Assuming that we assign register variables before the loop, and
un-assign them afterwards, the total time related to this line of
code is 240+128=368 cycles with register variables, and 800+0=800
without. We had to pay 64 cycles to bring the variables onto the
registers, and another 64 to save them off, but we got a much
quicker assignment statement in return. With a higher loop value
and/or more statements in the loop using these variables, the time
savings would be so great that the register loading and unloading
cost would become trivial.
  Thus, this principle dictates that register assignments should be
changed at the start of each loop, even if those changes force the
un-assignment of variables that were still being used by the code
outside of the loop. (Another Register command could be used at the
end of the loop to set things back the way they were) Remember to
put the Register command(s) and the Regload command OUTSIDE of the
loop, to avoid the flow-of-control pitfall.

Principle #3: In every If-like construct, one block of code will be
executed more often than the other.

Justification: Consider this fragment of code, adapted from the
ReadLine procedure of the UOut example program:

data register length,z,bufferpos,buffercount
address register linepointer,linemem,bufferstart
regload
repeat
  bytepeek z=bufferstart+bufferpos
  bytepoke linemem+length,z
  bufferpos=bufferpos+1
  length=length+1
  if bufferpos=buffercount
    libcall buffercount=read(srcfilehandle,bufferstart,8192)
    bufferpos=0
  end if
until z=10 | buffercount=0 | length=256

  The first question to ask is, "What does this code actually do?"
  This procedure maintains an 8k buffer which contains part of the
file being processed. Whenever the higher-level routines are ready
to process another line of uuencoded data, they call this procedure
to get it. The procedure must copy the next line from the buffer
area, pointed to by BUFFERSTART, into the one-line buffer used by
the rest of the program, pointed to by LINEMEM. BUFFERCOUNT and
LENGTH mark how far the procedure has gone into each memory area.
  Notice that every single variable used in this code has been
assigned to a register - except for SRCFILEHANDLE. Why not
SRCFILEHANDLE?
  We can apply principle #3 to the If statement in this code
fragment if we consider it to have a blank Else block. Thus, either
the If block will be executed most of the time, or nothing will
happen most of the time.
  But the point of this If statement is to check to see if the
buffer is empty. How often will that happen? Well, we have an 8k
buffer, and we process one character per iteration, so it will take
8,192 iterations before we need the code in the If block. Looking at
the purpose of the UOut program, we see that all lines should be
under 80 columns, and thus this procedure will likely be called over
100 times before the If-block code is used. Clearly, loading and
unloading SRCFILEHANDLE at the end of each iteration is not
worthwhile.
  This is an example of a situation in which our human intelligence
is more powerful than a computer's number-crunching ability. A
computer program using principle #2 can run circles around you. It
doesn't need principle #1 because it can be programmed to know
exactly how long each operation will take. But it has no idea what
the program actually DOES, so it can't tell which branch of the If
will be taken more often. Principle #3 is usable only by humans.
With care, it can tip the optimization balance in your favor.

  The most important piece of advice that I can give you is that you
should look at your program and THINK carefully about what you're
really asking the computer to do.



7. Q & A
--------
Q: What if the user tries to run my program with parameters? How can
I get ahold of them?

A: Two global variables are automatically defined before your
program begins. CommandPointer, a LONG, holds the address of the
parameter string that the user typed when he ran your program.
CommandLength, a WORD, holds the length of this string. You must
parse the parameter string yourself. If you are programming under
Kickstart 2.0 or higher, the operating system function ReadArgs can
be used to make things easier.

Q: How do I use structures in Struct?

A: Normally, the structure will be defined in an assembly language
include file which will included with the Include Constant command.
Let's say the structure looked like this:

Structure BitNode,0
APTR BN_NextNode
APTR BN_PrevNode
WORD BN_Value
LABEL BN_SizeOf

  The Include Constant command would define the following constants:

BN_NextNode = 0
BN_PrevNode = 4
BN_Value = 8
BN_SizeOf = 10

  To create an instance of this structure, you would normally use
AllocMem like this:

Lib Call BitNodePointer=AllocMem(BN_SizeOf,0)

  Now BitNodePointer points to a 10-byte memory area that you'll use
for the BitNode. If you wanted to set the Value in the BitNode to
42, you would do this:

Word Poke BitNodePointer+BN_Value,42

  If this BitNode had already been set up, and made part of a list,
you could "step" to the next node in the list with:

Long Peek BitNodePointer=BitNodePointer+BN_NextNode

  It's that easy. Doing the sort of multi-structure indirection
that C is famous for may take some loops or temporary variables, but
it's really no more difficult than what's already been shown here.

Q: What's this STACK data type?

A:  Last semester, in one of my classes, I was given a test on which
I was asked the question, "How would you add direct support for
stacks to your favorite language?" I found the question very
interesting. The stack - and its complement, the queue - is an
important basic construct used in a wide variety of programs. Graph
traversals, for example, would be difficult or impossible without
them.
  Stacks are an integral part of assembly language...but what about
high-level languages? In Ada I would allocate some memory from the
OS, assign the pointer to a variable, and use it to step back and
forth through memory. In C I would do...the same thing. The same as
in Basic. And Fortran. And all the other languages that I knew. It
amazed me that NO high-level language I had ever seen had direct
support for stacks.
  After class I asked the teacher about this. He told me that he had
seen one or two - I believe he mentioned Algol and PL/1 - but none
that were in general use nowdays. He pointed out that you could put
the stack-handling code in a function, calling it whenever you
needed to push or pop, just as I'd done in my basic data structures
classes. I agreed, but argued that it would be tremendously
inefficient compared to the simple -(Ax) and (Ax)+ combinations
possible in assembly. And it seemed more like a way out to me than a
real solution.
  I was curious as to whether or not people would actually use such
a feature, if it was available. Being the creator of my own language
gave me the opportunity to find out. So I added a limited form of
stack support into Struct, using the method I had devised on the
test. The code output by the compiler to do stack accesses isn't up
to the standards of the rest of the compiler, but is sufficient for
an experiment.
  First, a variable must be defined as being of type STACK with a
statement like this:

Global <stack-variable):STACK(<stack-size>)

  For example:

Global Pai:STACK(256)

  Pai will now be a stack variable. The value between the
parentheses is the size of the stack, 256 bytes in this case. The
memory for the stack will be allocated automatically by the compiled
program's startup routine. Note that only the Global command can be
used to define a stack; the Local command will not work.
  Values can be pushed onto a stack like this:

<stack-variable>=<math-expression>

and popped off like this:

<normal-variable>=<stack-variable>

  When a value is pushed onto the stack, Struct will look at the
math-expression to determine the size of the pushed value. If it is
an expression, a longword containing the result of the expression
will be pushed onto the stack. If, however, the "expression" is
actually a single variable, the size of the push will be the same as
the size of the variable.
  When values are popped from the stack, the size of the pop will
always be the same as the size of the receiving variable.
  A stack variable cannot appear on both sides of the equals sign.
An assignment like:

<stack-variable>=<stack-variable>

is NOT allowed and WILL NOT WORK.
  Consider the following piece of code:

Global Pai:STACK(1024), A:WORD=17, B:WORD, C:WORD
Pai=A
Pai=5
A=Pai
B=Pai
C=Pai

  When the second line is executed, a word with the value of 17 will
be placed on the stack, as A is a WORD variable by itself. The
third line will place a longword with the value of 5 on the stack.
Even though 5 is a literal, it is considered an expression because
it is not a lone variable. At this point, if you were to view the
stack with a memory editor, it would look like this:

00 00 00 05 00 17

  The next three lines cause three words to be pulled off of the
stack and assigned to A, B, and C. This will make A equal zero, B
equal five, and C equal seventeen.
  Be aware that although Struct's stacks are easier to use than the
ones you would make yourself, they have the same caveats. You must
be careful not to overflow or underflow the stack; Struct does not
check this for you. You must ensure that values are pushed onto and
popped from the stack in a logical order. And when dealing with
byte-length values, you must beware of a subtle pitfall. Consider
the following piece of code:

Global Yakumo:STACK(500), X:BYTE=5, Y:WORD=7
Yakumo=X
Yakumo=Y

  This code will CRASH THE COMPUTER!
  The problem lies in the design of the 68000. The 68000 requires
that all attempts to read (and write) words and longwords occur at
even memory addresses. When the second line is executed, the pointer
to the Yakumo stack will be decremented by one (making it point to
an odd memory address) and a five will be written to that address.
This is allowed, since the memory access is byte-length. But when
the third line is executed, the stack pointer will be decremented by
two (making it still odd), the computer will try to write a word to
that address, and a guru will result.
  At the moment, the STACK variable type is just a hack. That's why
you can only use the simple kinds of references shown above - only
the compiler's high-level parser knows about it. All other parts of
the compiler will see it as a LONG variable. I have not done any of
the detailed study and design that would be necessary to make it a
formal part of the language. There are serious problems involved in
trying to make STACK variables transparent to the rest of the
language - problems related to scratch registers and determinism. It
might be better to have a separate command to deal with them, though
this somewhat defeats their purpose. We'll see what happens.



8. Registration
---------------
  This version of Struct is not "crippled" or "broken" in any way,
and can be used to its full potential without registration. However,
registration has been known to cause feelings of righteousness and
pride in users, as the mail system whisks their offering of love
across the world. It also makes the recipient much more likely to
continue to support the program he has written.
  Registration is $25 and should be sent to:

Roland Acton
8001 Bluebird Lane
La Palma, CA, 90623
U.S.A.

Internet address: xracton@ccvax.fullerton.edu

  There are advantages to being a registered user. Registered users
will be sent every "significant upgrade" (my decision) as soon as it
is available. Those with an Internet address, or on a system
reachable by Internet mail, will likely receive every single update
the day it comes out.
  I will also be MUCH more inclined to answer questions or do favors
for registered users.
  I can make no guarantees about the future, though, even to
registered users. I intend to continue upgrading and enhancing
Struct, but unforseen problems may make this impossible or
unreasonable. I reserve the right to stop supporting Struct at any
time.



9. Legalese
-----------
  Struct is copyrighted 1994 by Roland Acton, but freely
redistributable, so long as the following conditions are met:

1. The archive is distributed intact, with no programs or
documentation changed, removed or destroyed.
2. A person or organization wishing to distribute Struct as part of
a collection of PD, shareware, freeware, or the like must place
Struct and all of its documentation and support programs on the same
disk, if at all possible. No more money may be charged for this disk
than Fred Fish would charge for a similar disk.

  Although I have put my best effort into making Struct work
properly, I cannot be held responsible if it does not work in the
way that I - or you - intend it to. Such are the dangers of using
programs that you don't have to pay for (and many of those that you
do).
  People wishing to use Struct to create programs, or parts of
programs, must abide by the following conditions:

If the program is intended for NON-COMMERCIAL release:
1. It must be mentioned somewhere in either the program or the
documentation, or both, that the program was either partially or
completely written in Struct.
2.  It is requested, but not required, that you send me a copy of
the program. If this is not possible, I would appreciate at least
being notified that the program exists. My address is listed in the
Registration section; my Internet address can also be used if I am
warned beforehand (there's only so much space in that account).
Sending the source code along would be nice, too.  I'm always
interested in seeing how other people deal with Struct.

If the program is intended for COMMERCIAL release:
1. You MUST register, as described in the Registration section. If
Struct is good enough to write a commercial program with, it's good
enough to pay for.
2. It is preferred that somewhere, in either the program or
documentation, it is mentioned that the program was written using
Struct. This is not absolutely required, though, unless the program
or documentation has a section that credits other utilities used to
make the program. In this case Struct must be mentioned there with
the other programs.
3. The author of the program must make a reasonable attempt to send
me a copy of the program, without charge. My address can be found
in the Registration section. You can also use my Internet address
if you warn me beforehand.



                         --- Appendices ---



A. How to alter the library definition file
-------------------------------------------
  The library definition file, called "StructLibraryDefinitions" and
always looked for in the S: directory, contains a list of all of the
functions that can be used with the Lib Call command. Included in
this list is all of the "vital information" about the function: what
offset it is from the library base, what registers its parameters
(if any) go onto, and what register its return value (if any) is
passed on.
  A typical library definition looks like this:

Library <library-name>
            [...]
[list of function definitions]
            [...]
End Library

  Each individual function definition takes this form:

<function-name>(<library-offset>):[<return-register>=]
  [<parameter-register>[,<parameter-register>[,...]]]

  For example:

Library Pop
  Chaser(-8):D0=D0,A1,A2
End Library

  The registers are specified in the same order that the variables
containing values to pass are specified when the Lib Call command is
used.
  All registers except A6 and A7 can be used to pass parameters.
Return values normally come back on register D0. Is is possible to
specify any register except A6 and A7 as the return register, but
there is a problem: Struct only considers registers D0, D1, A0, and
A1 to be "scratch". Register variables assigned to other registers
are not saved off unless that register is needed to pass a
parameter. If the external function actually returned a value on a
non-scratch register, it might overwrite a register variable.
  Note that you might put variables on registers with the Absolute
and Regload commands and then call a function (normally of your own
creation) which deliberately overwrites them. This could be used as
a method of easily returning more than one value per call. It would
have to be done with care, however.
  The line continuation character can be used to place comments in
the library definition file, if desired. Also, after the definition
of a library has been finished with End Library, you can "come back"
to it with another Library.
  Here is a longer example:

Library Exec
  AddTail(-246):A0,A1
  AllocMem(-198):D0=D0,D1
  GetCC(-528):D0=
End Library

Library Dos
  Delay(-198):D1
End Library

Library Exec
  MakeLibrary(-84):D0=A0,A1,A2,D0,D1
End Library

  You can edit the library definition file to add libraries and
functions other than those that are part of the standard Amiga
operating system. As long as these other libraries conform to
standard Commodore-Amiga shared library specifications, they will
work with Struct.



B. Possible errors
------------------
  Following is a list of most of the possible errors and warnings
that Struct can report during compilation. There are a few others
that are not part of the formal error list.

 #   Reported Error
--   --------------
01   A mathematical expression cannot be a variable
02   A mathematical expression cannot be a constant
03   Variable has already been defined
04   Constant has already been defined
05   Already defined as a variable
06   Already defined as a constant
07   Expected assignment or label
08   Two commas in a row
09   Too many equals signs
10   Undefined variable or constant
11   A mathematical expression cannot be a label
12   An expression must appear on each side of an equals sign
13   You cannot assign a value to an expression
14   Type definition expected
15   An equals sign implies an initial assignment
16   Invalid type definition
17   An initial assignment must reduce to a numeric literal
18   Constant has not been assigned a value
19   Variable name begins with a Struct keyword
20   Constant name begins with a Struct keyword
21   A variable or constant must appear on each side of a mathematical operator
22   MATHEVALUATE: Empty expression encountered
23   Not currently a register variable
24   You are limited to 7 data register variables
25   You are limited to 3 address register variables
26   An address register variable must be of type LONG
27   This command requires one or more parameters
28   Undefined variable
29   Constant assignments must reduce to numeric literals when Constant Evaluate is ON
30   A procedure must have a name
31   Already in a procedure
32   Not in a procedure
33   A procedure with that name has already been defined
34   The LOCAL instruction can only be used within a procedure
35   Constant Evaluate can only be set to ON or OFF
36   REPLACECONSTANT: 100 iterations performed
37   Label has already been defined
38   A default assignment must reduce to a numeric literal
39   An expression must appear on each side of the relational operators
40   A relational operator was used more than once
41   You must specify at least one primary relational operator
42   It makes no sense to use all three primary relational operators
43   Else without If
44   End If without If
45   Back If without If
46   A computed Goto is not allowed
47   A right parenthesis is required to denote the end of the label
48   A comma was expected but not found
49   An equals sign was expected but not found
50   A semicolon was expected but not found
51   There must be an expression on each side of a semicolon
52   NEXT without FOR
53   Loop resolved out of order
54   WEND without WHILE
55   UNTIL without REPEAT
56   Two semicolons must appear as separators in the LOOP command
57   Expected an assignment
58   This command does not use a parameter
59   END LOOP without LOOP
60   LIBREADER: Invalid function definition
61   LIBREADER: Register used more than once
62   LIBREADER: A function must be associated with a library
63   LIBREADER: Duplicate function name
64   Unknown library
65   Library has already been opened
66   A right parenthesis must appear at the end of the parameter list
67   Function not found in library definition file
68   Function definition does not specify a return value
69   Function cannot accept this many parameters
70   Library containing function has not been opened
71   <UNUSED>
72   Parameter must be of type LONG
73   DOS library has not been opened
74   Parameters must appear on each side of the equals sign
75   This program was written for a later version of Struct
76   File does not exist
77   Error in included constant file
78   No procedure with that name has been defined
79   Too few parameters passed to procedure
80   Too many parameters passed to procedure
81   Procedure has already been implemented
82   Procedure has not been defined
83   Definition and implementation parameter lists must match
84   Formal and actual parameter lengths do not match
85   A procedure can accept a maximum of 7 parameters
86   Not currently in a loop
87   Startup procedure has already been set
88   Only LONG parameters may be passed on address registers
89   MAKEBRANCH: Blank condition encountered
90   Value of constant exceeds variable capacity
91   Definition and implementation return types must match
92   Procedure returns a value
93   Formal and actual return variable lengths do not match
94   Procedure does not return a value

Notes:
  Errors #22 and #89 should never happen. There is supposed to be
enough high-level checking in the compiler to catch any problems
before they get to these routines.
  Error #36 will occur if you accidentally set up a replacement loop
as mentioned in the tutorial on the Constant command.
  Errors #60-63 refer to the StructLibraryDefinitions file.
  Error #90 will not always be reported, even in some situations
where it seems blatantly obvious. Due to internal pipelining, it can
be very difficult for Struct to tell where a value is going to end
up.



C. Theoretical Limits
---------------------
  The following table shows the maximum number of instances of each
program element. Exceeding these limits will cause the compiler to
halt abruptly, though it shouldn't crash the computer. It is
possible, but unlikely, to overflow the compiler's internal buffer
area (also causing a halt) without exceeding these limits.

Element                             Per Program    Per Procedure
----------------------------------------------------------------
Global variables                        255              -
Local variables                           -            255
Constants                              2000              -
Labels                                  500              *
Ifs                                       -            255
Gotos                                     -            255
Prints                                    -            255
Loops**                                   -            255
"Data" statements***                      -            255
Procedures                              255              -
External libraries                       32              -
External functions                      500              -
Load Address commands                     -            255

* Local and global labels are stored in the same data table. Local
  labels are discarded at the end of each procedure. The total
  number of outstanding labels may not exceed 500 at any time.
** This includes For/Next, While/Wend, Repeat/Until, Loop/End Loop,
   and Zeroloop/Zend.
*** This includes Byte Data, Word Data, Long Data, and String.



D. Speed Tests
--------------
  I wrote some test programs to compare the efficiency of SAS C 6.2
amd Struct. Although I considered C to be Struct's primary
competition, I also ran the speed tests on the other two languages I
had available: AMOS and ACE.
  SAS C 6.2 is a commercial C compiler, published by SAS Institute.
The included assembler and linker were used. The optimization flag
was not used because the optimizer kept throwing the test code away.
(But, looking at the assembly code, I doubt it would have made much
difference)
  Struct is a copyrighted but freely redistributable compiler,
written by Roland Acton. The PhxAss optimizing assembler and PhxLnk
linker, included with Struct, were used.
  AMOS is a commercial BASIC interpreter, written by Francois Lionet
and published by Europress software. The AMOS compiler was used, and
the numbers given are for the compiled versions of the speed tests.
  ACE is a freeware BASIC compiler, written by David Benn. The A68k
assembler and Blink linker, included with ACE, were used.
  The tests were performed on an Amiga 500 with 1 MB chip and 2 MB
fast, running Kickstart 1.3.

Results (numbers represent seconds):

              C     Struct      AMOS      ACE
--------------------------------------------------
Test 1      114        107       662      504
Test 2      230        109      1694     1200
Test 3      441        107      2313      866
Test 4      119        119      1115      660
Test 5      114        114       887      571
Test 6      116         95       660      504
Test 7      130        107       709      538
Test 8      121         76       622      442

  The tests themselves were quite trivial and focused on loop
structures, comparisons, and simple mathematics. They are not
sufficiently general to be called true benchmarks. The tested
operations, however, are important parts of all useful programs.
  TEST 1 was a For/Next loop.
  TEST 2 used the Zeroloop construct, unique to Struct. For/Next
loops with negative steps were used in the other languages.
  TEST 3 was to acquire the remainder from a division and use it in
an If/Then. This was surrounded by a For/Next loop.
  TEST 4 was a Repeat/Until loop. Do/While was substituted in C.
  TEST 5 was a While/Wend loop.
  TEST 6 was a Loop/End Loop construct. For/Next was substituted in
the other languages.
  TEST 7 was a test of literal concatenation. An If/Then, with the
condition containing numeric literals added to a variable, was
surrounded with a For/Next loop.
  TEST 8 was to access memory. A Byte Peek with a computed memory
address was surrounded by For/Next loops. The memory reference
operator with char casting was used in C. Peek was used in AMOS and
ACE.
  The source code to all the tests is included in the SpeedTests
directory in the archive.
