home *** CD-ROM | disk | FTP | other *** search
Text File | 1994-06-30 | 101.7 KB | 2,564 lines |
- 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.
-