TOC PREV NEXT INDEX

Chapter Seven The HLA Compile-Time Language


7.1 Chapter Overview

Now we come to the fun part. For the past nine chapters this text has been molding and conforming you to deal with the HLA language and assembly language programming in general. In this chapter you get to turn the tables; you'll learn how to force HLA to conform to your desires. This chapter will teach you how to extend the HLA language using HLA's compile-time language. By the time you are through with this chapter, you should have a healthy appreciation for the power of the HLA compile-time language. You will be able to write short compile-time programs. You will also be able to add new statements, of your own choosing, to the HLA language.

7.2 Introduction to the Compile-Time Language (CTL)

HLA is actually two languages rolled into a single program. The run-time language is the standard 80x86/HLA assembly language you've been reading about in all the past chapters. This is called the run-time language because the programs you write execute when you run the executable file. HLA contains an interpreter for a second language, the HLA Compile-Time Language (or CTL) that executes programs while HLA is compiling a program. The source code for the CTL program is embedded in an HLA assembly language source file; that is, HLA source files contain instructions for both the HLA CTL and the run-time program. HLA executes the CTL program during compilation. Once HLA completes compilation, the CTL program terminates; the CTL application is not a part of the run-time executable that HLA emits, although the CTL application can write part of the run-time program for you and, in fact, this is the major purpose of the CTL.



Figure 7.1 Compile-Time vs. Run-Time Execution

It may seem confusing to have two separate languages built into the same compiler. Perhaps you're even questioning why anyone would need a compile time language. To understand the benefits of a compile time language, consider the following statement that you should be very comfortable with at this point:

stdout.put( "i32=", i32, " strVar=", strVar, " charVar=", charVar, nl );
 

 

This statement is neither a statement in the HLA language nor a call to some HLA Standard Library procedure. Instead, stdout.put is actually a statement in a CTL application provided by the HLA Standard Library. The stdout.put "application" processes a list of objects (the parameter list) and makes calls to various other Standard Library procedures; it chooses the procedure to call based on the type of the object it is currently processing. For example, the stdout.put "application" above will emit the following statements to the run-time executable:

stdout.puts( "i32=" );
 
stdout.puti32( i32 );
 
stdout.puts( " strVar=" );
 
stdout.puts( strVar );
 
stdout.puts( " charVar=" );
 
stdout.putc( charVar );
 
stdout.newln();
 

 

Clearly the stdout.put statement is much easier to read and write than the sequence of statements that stdout.put emits in response to its parameter list. This is one of the more powerful capabilities of the HLA programming language: the ability to modify the language to simplify common programming tasks. Printing lots of different data objects in a sequential fashion is a common task; the stdout.put "application" greatly simplifies this process.

The HLA Standard Library is loaded with lots of HLA CTL examples. In addition to standard library usage, the HLA CTL is quite adept at handling "one-off" or "one-use" applications. A classic example is filling in the data for a lookup table. An earlier chapter in this text noted that it is possible to construct look-up tables using the HLA CTL (see "Tables" on page 647 and "Generating Tables" on page 651). Not only is this possible, but it is often far less work to use the HLA CTL to construct these look-up tables. This chapter abounds with examples of exactly this application of the CTL.

Although the CTL itself is relatively inefficient and you would not use it to write end-user applications, it does maximize the use of that one precious commodity of which there is so little available: your time. By learning how to use the HLA CTL, and applying it properly, you can develop assembly language applications as rapidly as high level language applications (even faster since HLA's CTL lets you create very high level language constructs).

7.3 The #PRINT and #ERROR Statements

Chapter One of this textbook began with the typical first program most people write when learning a new language; the "Hello World" program. It is only fitting for this chapter to present that same program when discussing the second language of this text. So here it is, the basic "Hello World" program written in the HLA compile time language:


 
program ctlHelloWorld;
 
begin ctlHelloWorld;
 

 
    #print( "Hello, World of HLA/CTL" )
 

 
end ctlHelloWorld;
 

 
Program 7.1	 The CTL "Hello World" Program
 

The only CTL statement in this program is the "#print" statement. The remaining lines are needed just to keep the compiler happy (though we could have reduced the overhead to two lines by using a UNIT rather than a PROGRAM declaration).

The #PRINT statement displays the textual representation of its argument list during the compilation of an HLA program. Therefore, if you compile the program above with the command "hla ctlHW.hla" the HLA compiler will immediately print, before returning control to the command line, the text:

Hello, World of HLA/CTL
 

 

Note that there is a big difference between the following two statements in an HLA source file:

#print( "Hello World" )
 
stdout.puts( "Hello World" nl );
 

 

The first statement prints "Hello World" (and a newline) during the compilation process. This first statement does not have any effect on the executable program. The second line doesn't affect the compilation process (other than the emission of code to the executable file). However, when you run the executable file, the second statement prints the string "Hello World" followed by a new line sequence.

The HLA/CTL #PRINT statement uses the following basic syntax:

#print( list_of_comma_separated_constants )
 

 

Note that a semicolon does not terminate this statement. Semicolons terminate run-time statements, they generally do not terminate compile-time statements (there is one big exception, as you will see a little later).

The #PRINT statement must have at least one operand; if multiple operands appear in the parameter list, you must separate each operand with a comma (just like stdout.put). If a particular operand is not a string constant, HLA will translate that constant to its corresponding string representation and print that string. Example:

#print( "A string Constant ", 45, ' ', 54.9, ' ', true )
 

 

You may specify named symbolic constants and constant expressions. However, all #PRINT operands must be constants (either literal constants or constants you define in the CONST or VAL sections) and those constants must be defined before you use them in the #PRINT statement. Example:

const
 
	pi := 3.14159;
 
	charConst := 'c';
 

 
#print( "PI = ", pi, "  CharVal=", CharConst )
 

 

The HLA #PRINT statement is particularly invaluable for debugging CTL programs (since there is no debugger available for CTL code). This statement is also useful for displaying the progress of the compilation and displaying assumptions and default actions that take place during compilation. Other than displaying the text associated with the #PRINT parameter list, the #PRINT statement does not have any affect on the compilation of the program.

The #ERROR statement allows a single string constant operand. Like #PRINT this statement will display the string to the console during compilation. However, the #ERROR statement treats the string as a error message and displays the string as part of an HLA error diagnostic. Further, the #ERROR statement increments the error count and this will cause HLA to stop the compilation (without assembly or linking) at the conclusion of the source file. You would normally use the #ERROR statement to display an error message during compilation if your CTL code discovers something that prevents it from creating valid code. Example:

#error( "Statement must have exactly one operand" )
 

 

Like the #PRINT statement, the #ERROR statement does not end with a semicolon. Although #ERROR only allows a string operand, it's very easy to print other values by using the string (constant) concatenation operator and several of the HLA built-in compile-time functions (see "Compile-Time Constants and Variables" on page 952 and "Compile-Time Functions" on page 956) for more details).

7.4 Compile-Time Constants and Variables

Just as the run-time language supports constants and variables, so does the compile-time language. You declare compile-time constants in the CONST section, the same as for the run-time language. You declare compile-time variables in the VAL section. Objects you declare in the VAL section are constants as far as the run-time language is concerned, but remember that you can change the value of an object you declare in the VAL section throughout the source file. Hence the term "compile-time variable." See "HLA Constant and Value Declarations" on page 397 for more details.

The CTL assignment statement ("?") computes the value of the constant expression to the right of the assignment operator (":=") and stores the result into the VAL object name appearing immediately to the left of the assignment operator1. The following example is a rework of the example above; this example, however, may appear anywhere in your HLA source file, not just in the VAL section of the program.

	?ConstToPrint := 25;
 
	#print( "ConstToPrint = ", ConstToPrint )
 
	?ConstToPrint := ConstToPrint + 5;
 
	#print( "Now ConstToPrint = ", ConstToPrint )
 

 

Note that HLA's CTL ignores the distinction between the different sizes of numeric objects. HLA always reserves storage for the largest possible object of a given type, so HLA merges the following types:

byte, word, dword -> dword
 
uns8, uns16, uns32 -> uns32
 
int8, int16, int32 -> int32
 
real32, real64, real80 -> real80
 

 

For most practical applications of the CTL, this shouldn't make a difference in the operation of the program.

7.5 Compile-Time Expressions and Operators

As the previous section states, the HLA CTL supports constant expressions in the CTL assignment statement. Unlike the run-time language (where you have to translate algebraic notation into a sequence of machine instructions), the HLA CTL allows a full set of arithmetic operations using familiar expression syntax. This gives the HLA CTL considerable power, especially when combined with the built-in compile-time functions the next section discusses.

HLA's CTL supports the following operators in compile-time expressions:

Table 1: Compile-Time Operators
Operator(s)
Operand Types1
Description

- (unary)
numeric
Negates the specific numeric value (int, uns, real).
cset
Returns the complement of the specified character set.

! (unary)
integer
Inverts all the bits in the operand (bitwise not).
boolean
Boolean NOT of the operand.

*
numericL * numericR
Multiplies the two operands.
csetL * csetR
Computes the intersection of the two sets
div
integerL div integerR
Computes the integer quotient of the two integer (int/uns/dword) operands.
mod
integerL mod integerR
Computes the remainder of the division of the two integer (int/uns/dword) operands.
/
numericL / numericR
Computes the real quotient of the two numeric operands. Returns a real result even if both operands are integers.
<<
integerL << integerR
Shifts integerL operand to the left the number of bits specified by the integerR operand.
>>
integerL >> integerR
Shifts integerL operand to the right the number of bits specified by the integerR operand.


+
numericL + numericR
Adds the two numeric operands.
csetL + csetR
Computes the union of the two sets.
strL + strR
Concatenates the two strings.

-
numericL - numericR
Computes the difference between numericL and numericR.
csetL - csetR
Computes the set difference of csetL-csetR.



= or ==
numericL = numericR
Returns true if the two operands have the same value.
csetL = csetR
Returns true if the two sets are equal.
strL = strR
Returns true if the two strings/chars are equal.
typeL = typeR
Returns true if the two values are equal. They must be the same type.
<> or !=
typeL <> typeR
(Same as =)
Returns false if the two (compatible) operands are not equal to one another.




<
numericL < numericR
Returns true if numericL is less than numericR.
csetL < csetR
Returns true if csetL is a proper subset of csetR.
strL < strR
Returns true if strL is less than strR
booleanL < booleanR
Returns true if left operand is less than right operand (note: false < true).
enumL < enumR
Returns true if enumL appears in the same enum list as enumR and enumL appears first.
<=
Same as <
Returns true if the left operand is less than or equal to the right operand. For character sets, this means that the left operand is a subset of the right operand.
>
Same as <
Returns true if the left operand is greater than the right operand. For character sets, this means that the left operand is a proper superset of the right operand.
>=
Same as <=
Returns true if the left operand is greater than or equal to the right operand. For character sets, this means that the left operand is a superset of the right operand.

&
integerL & integerR
Computes the bitwise AND of the two operands.
booleanL & booleanR
Computes the logical AND of the two operands.

|
integerL | integerR
Computes the bitwise OR of the two operands.
booleanL | booleanR
Computes the logical OR of the two operands.

^
integerL ^ integerR
Computes the bitwise XOR of the two operands.
booleanL ^ booleanR
Computes the logical XOR of the two operands. Note that this is equivalent to "booleanL <> booleanR".
in
charL in csetR
Returns true if charL is a member of csetR.
1Numeric is {intXX, unsXX, byte, word, dword, and realXX} values. Cset is a character set operand. Type integer is { intXX, unsXX, byte, word, dword }. Type str is any string or character value. "TYPE" indicates an arbitrary HLA type. Other types specify an explicit HLA data type.

Table 2: Operator Precedence and Associativity
Associativity
Precedence (Highest to Lowest)
Operator
Right-to-left

6
! (unary)
- (unary)




Left to right




5
*
div
mod
/
>>
<<

Left to right

4
+
-




Left to right




3
= or ==
<> or !=
<
<=
>
>=

Left to right


2
&
|
^
Nonassociative
1
in

Of course, you can always override the default precedence and associativity of an operator by using parentheses in an expression.

1If the identifier to the left of the assignment operator is undefined, HLA will automatically declare this object at the current scope level.


TOC PREV NEXT INDEX