The mov
, add
, and sub
instructions, while valuable, aren't sufficient to let you write meaningful programs. You will need to complement these instructions with the ability to make decisions and create loops in your HLA programs before you can write anything other than a simple program. HLA provides several high-level control structures that are very similar to control structures found in high-level languages. These include if..then..elseif..else..endif
, while..endwhile
, repeat..until
, and so on. By learning these statements you will be armed and ready to write some real programs.
Before discussing these high-level control structures, it's important to point out that these are not real 80x86 assembly language statements. HLA compiles these statements into a sequence of one or more real assembly language statements for you. In Chapter 7, you'll learn how HLA compiles the statements, and you'll learn how to write pure assembly language code that doesn't use them. However, there is a lot to learn before you get to that point, so we'll stick with these high-level language statements for now.
Another important fact to mention is that HLA's high-level control structures are not as high level as they first appear. The purpose behind HLA's high-level control structures is to let you start writing assembly language programs as quickly as possible, not to let you avoid the use of assembly language altogether. You will soon discover that these statements have some severe restrictions associated with them, and you will quickly outgrow their capabilities. This is intentional. Once you reach a certain level of comfort with HLA's high-level control structures and decide you need more power than they have to offer, it's time to move on and learn the real 80x86 instructions behind these statements.
Do not let the presence of high-level-like statements in HLA confuse you. Many people, after learning about the presence of these statements in the HLA language, erroneously come to the conclusion that HLA is just some special high-level language and not a true assembly language. This isn't true. HLA is a full low-level assembly language. HLA supports all the same machine instructions as any other 80x86 assembler. The difference is that HLA has some extra statements that allow you to do more than is possible with those other 80x86 assemblers. Once you learn 80x86 assembly language with HLA, you may elect to ignore all these extra (high-level) statements and write only low-level 80x86 assembly language code if this is your desire.
The following sections assume that you're familiar with at least one high-level language. They present the HLA control statements from that perspective without bothering to explain how you actually use these statements to accomplish something in a program. One prerequisite this text assumes is that you already know how to use these generic control statements in a high-level language; you'll use them in HLA programs in an identical manner.
Several HLA statements require a boolean (true or false) expression to control their execution. Examples include the if
, while
, and repeat..until
statements. The syntax for these boolean expressions represents the greatest limitation of the HLA high-level control structures. This is one area where your familiarity with a high-level language will work against you—you'll want to use the fancy expressions you use in a high-level language, yet HLA supports only some basic forms.
HLA boolean expressions take the following forms:[9]
flag_specification !flag_specification register !register Boolean_variable !Boolean_variable mem_reg relop mem_reg_const register in LowConst..HiConst register not in LowConst..HiConst
A flag_specification
may be one of the symbols that are described in Table 1-2.
Table 1-2. Symbols for flag_specification
Symbol | Meaning | Explanation |
---|---|---|
Carry | True if the carry is set (1); false if the carry is clear (0). | |
No carry | True if the carry is clear (0); false if the carry is set (1). | |
Zero | True if the zero flag is set; false if it is clear. | |
Not zero | True if the zero flag is clear; false if it is set. | |
Overflow | True if the overflow flag is set; false if it is clear. | |
No overflow | True if the overflow flag is clear; false if it is set. | |
Sign | True if the sign flag is set; false if it is clear. | |
No sign | True if the sign flag is clear; false if it is set. |
The use of the flag values in a boolean expression is somewhat advanced. You will begin to see how to use these boolean expression operands in the next chapter.
A register operand can be any of the 8-bit, 16-bit, or 32-bit general-purpose registers. The expression evaluates false if the register contains a zero; it evaluates true if the register contains a nonzero value.
If you specify a boolean variable as the expression, the program tests it for zero (false) or nonzero (true). Because HLA uses the values zero and one to represent false and true, respectively, the test works in an intuitive fashion. Note that HLA requires such variables be of type boolean
. HLA rejects other data types. If you want to test some other type against zero/not zero, then use the general boolean expression discussed next.
The most general form of an HLA boolean expression has two operands and a relational operator. Table 1-3 lists the legal combinations.
Table 1-3. Legal Boolean Expressions
Left Operand | Relational Operator | Right Operand |
---|---|---|
Memory variable or register | = or == <> or != < <= > >= | Variable, register, or constant |
Note that both operands cannot be memory operands. In fact, if you think of the right operand as the source operand and the left operand as the destination operand, then the two operands must be the same that add
and sub
allow.
Also like the add
and sub
instructions, the two operands must be the same size. That is, they must both be byte operands, they must both be word operands, or they must both be double-word operands. If the right operand is a constant, its value must be in the range that is compatible with the left operand.
There is one other issue: if the left operand is a register and the right operand is a positive constant or another register, HLA uses an unsigned comparison. The next chapter will discuss the ramifications of this; for the time being, do not compare negative values in a register against a constant or another register. You may not get an intuitive result.
The in
and not in
operators let you test a register to see if it is within a specified range. For example, the expression eax in 2000..2099
evaluates true if the value in the EAX register is between 2,000 and 2,099 (inclusive). The not in
(two words) operator checks to see if the value in a register is outside the specified range. For example, al not in 'a'..'z'
evaluates true if the character in the AL register is not a lowercase alphabetic character.
Here are some examples of legal boolean expressions in HLA:
@c Bool_var al esi eax < ebx ebx > 5 i32 < −2 i8 > 128 al < i8 eax in 1..100 ch not in 'a'..'z'
The HLA if
statement uses the syntax shown in Figure 1-10.
The expressions appearing in an if
statement must take one of the forms from the previous section. If the boolean expression is true, the code after the then
executes; otherwise control transfers to the next elseif
or else
clause in the statement.
Because the elseif
and else
clauses are optional, an if
statement could take the form of a single if..then
clause, followed by a sequence of statements and a closing endif
clause. The following is such a statement:
if( eax = 0 ) then stdout.put( "error: NULL value", nl ); endif;
If, during program execution, the expression evaluates true, then the code between the then
and the endif
executes. If the expression evaluates false, then the program skips over the code between the then
and the endif
.
Another common form of the if
statement has a single else
clause. The following is an example of an if
statement with an optional else
clause:
if( eax = 0 ) then stdout.put( "error: NULL pointer encountered", nl ); else stdout.put( "Pointer is valid", nl ); endif;
If the expression evaluates true, the code between the then
and the else
executes; otherwise the code between the else
and the endif
clauses executes.
You can create sophisticated decision-making logic by incorporating the elseif
clause into an if
statement. For example, if the CH register contains a character value, you can select from a menu of items using code like the following:
if( ch = 'a' ) then stdout.put( "You selected the 'a' menu item", nl ); elseif( ch = 'b' ) then stdout.put( "You selected the 'b' menu item", nl ); elseif( ch = 'c' ) then stdout.put( "You selected the 'c' menu item", nl ); else stdout.put( "Error: illegal menu item selection", nl ); endif;
Although this simple example doesn't demonstrate it, HLA does not require an else
clause at the end of a sequence of elseif
clauses. However, when making multiway decisions, it's always a good idea to provide an else
clause just in case an error arises. Even if you think it's impossible for the else
clause to execute, just keep in mind that future modifications to the code could void this assertion, so it's a good idea to have error-reporting statements in your code.
Some obvious omissions in the list of operators in the previous sections are the conjunction (logical and
), disjunction (logical or
), and negation (logical not
) operators. This section describes their use in boolean expressions (the discussion had to wait until after describing the if
statement in order to present realistic examples).
HLA uses the &&
operator to denote logical and
in a runtime boolean expression. This is a dyadic (two-operand) operator, and the two operands must be legal runtime boolean expressions. This operator evaluates to true if both operands evaluate to true. For example:
if( eax > 0 && ch = 'a' ) then mov( eax, ebx ); mov( ' ', ch ); endif;
The two mov
statements above execute only if EAX is greater than zero and CH is equal to the character a. If either of these conditions is false, then program execution skips over these mov
instructions.
Note that the expressions on either side of the &&
operator may be any legal boolean expressions; these expressions don't have to be comparisons using the relational operators. For example, the following are all legal expressions:
@z && al in 5..10 al in 'a'..'z' && ebx boolVar && !eax
HLA uses short-circuit evaluation when compiling the &&
operator. If the leftmost operand evaluates false, then the code that HLA generates does not bother evaluating the second operand (because the whole expression must be false at that point). Therefore, in the last expression above, the code will not check EAX against zero if boolVar
evaluates false.
Note that an expression like eax < 10 && ebx <> eax
is itself a legal boolean expression and, therefore, may appear as the left or right operand of the &&
operator. Therefore, expressions like the following are perfectly legal:
eax < 0 && ebx <> eax && !ecx
The &&
operator is left associative, so the code that HLA generates evaluates the expression above in a left-to-right fashion. If EAX is less than zero, the CPU will not test either of the remaining expressions. Likewise, if EAX is not less than zero but EBX is equal to EAX, this code will not evaluate the third expression because the whole expression is false regardless of ECX's value.
HLA uses the ||
operator to denote disjunction (logical or
) in a runtime boolean expression. Like the &&
operator, this operator expects two legal runtime boolean expressions as operands. This operator evaluates true if either (or both) operands evaluate true. Like the &&
operator, the disjunction operator uses short-circuit evaluation. If the left operand evaluates true, then the code that HLA generates doesn't bother to test the value of the second operand. Instead, the code will transfer to the location that handles the situation when the boolean expression evaluates true. Here are some examples of legal expressions using the ||
operator:
@z || al = 10 al in 'a'..'z' || ebx !boolVar || eax
Like the &&
operator, the disjunction operator is left associative, so multiple instances of the ||
operator may appear within the same expression. Should this be the case, the code that HLA generates will evaluate the expressions from left to right. For example:
eax < 0 || ebx <> eax || !ecx
The code above evaluates to true if EAX is less than zero, EBX does not equal EAX, or ECX is zero. Note that if the first comparison is true, the code doesn't bother testing the other conditions. Likewise, if the first comparison is false and the second is true, the code doesn't bother checking to see if ECX is zero. The check for ECX equal to zero occurs only if the first two comparisons are false.
If both the conjunction and disjunction operators appear in the same expression, then the &&
operator takes precedence over the ||
operator. Consider the following expression:
eax < 0 || ebx <> eax && !ecx
The machine code HLA generates evaluates this as
eax < 0 || (ebx <> eax && !ecx)
If EAX is less than zero, then the code HLA generates does not bother to check the remainder of the expression, and the entire expression evaluates true. However, if EAX is not less than zero, then both of the following conditions must evaluate true in order for the overall expression to evaluate true.
HLA allows you to use parentheses to surround subexpressions involving &&
and ||
if you need to adjust the precedence of the operators. Consider the following expression:
(eax < 0 || ebx <> eax) && !ecx
For this expression to evaluate true, ECX must contain zero and either EAX must be less than zero or EBX must not equal EAX. Contrast this to the result the expression produces without the parentheses.
HLA uses the !
operator to denote logical negation. However, the !
operator may only prefix a register or boolean variable; you may not use it as part of a larger expression (e.g., !eax < 0
). To achieve logical negative of an existing boolean expression, you must surround that expression with parentheses and prefix the parentheses with the !
operator. For example:
!( eax < 0 )
This expression evaluates true if EAX is not less than zero.
The logical not
operator is primarily useful for surrounding complex expressions involving the conjunction and disjunction operators. While it is occasionally useful for short expressions like the one above, it's usually easier (and more readable) to simply state the logic directly rather than convolute it with the logical not
operator.
Note that HLA also provides the |
and &
operators, but they are distinct from ||
and &&
and have completely different meanings. See the HLA reference manual for more details on these (compile-time) operators.
The while
statement uses the basic syntax shown in Figure 1-11.
This statement evaluates the boolean expression. If it is false, control immediately transfers to the first statement following the endwhile
clause. If the value of the expression is true, then the CPU executes the body of the loop. After the loop body executes, control transfers back to the top of the loop, where the while
statement retests the loop control expression. This process repeats until the expression evaluates false.
Note that the while
loop, like its high-level-language counterpart, tests for loop termination at the top of the loop. Therefore, it is quite possible that the statements in the body of the loop will not execute (if the expression is false when the code first executes the while
statement). Also note that the body of the while
loop must, at some point, modify the value of the boolean expression or an infinite loop will result.
Here's an example of an HLA while
loop:
mov( 0, i ); while( i < 10 ) do stdout.put( "i=", i, nl ); add( 1, i ); endwhile;
The HLA for
loop takes the following general form:
for(Initial_Stmt
;Termination_Expression
;Post_Body_Statement
) do << Loop body >> endfor;
This is equivalent to the following while
statement:
Initial_Stmt
; while(Termination_Expression
) do << Loop body >>Post_Body_Statement
; endwhile;
Initial_Stmt
can be any single HLA/80x86 instruction. Generally this statement initializes a register or memory location (the loop counter) with zero or some other initial value. Termination_Expression
is an HLA boolean expression (same format that while
allows). This expression determines whether the loop body executes. Post_Body_Statement
executes at the bottom of the loop (as shown in the while
example above). This is a single HLA statement. Usually an instruction like add
modifies the value of the loop control variable.
The following gives a complete example:
for( mov( 0, i ); i < 10; add(1, i )) do stdout.put( "i=", i, nl ); endfor;
The above, rewritten as a while loop, becomes:
mov( 0, i ); while( i < 10 ) do stdout.put( "i=", i, nl ); add( 1, i ); endwhile;
The HLA repeat..until
statement uses the syntax shown in Figure 1-12. C/C++/C# and Java users should note that the repeat..until
statement is very similar to the do..while
statement.
The HLA repeat..until
statement tests for loop termination at the bottom of the loop. Therefore, the statements in the loop body always execute at least once. Upon encountering the until
clause, the program will evaluate the expression and repeat the loop if the expression is false (that is, it repeats while false). If the expression evaluates true, the control transfers to the first statement following the until
clause.
The following simple example demonstrates the repeat..until
statement:
mov( 10, ecx ); repeat stdout.put( "ecx = ", ecx, nl ); sub( 1, ecx ); until( ecx = 0 );
If the loop body will always execute at least once, then it is usually more efficient to use a repeat..until
loop rather than a while
loop.
The break
and breakif
statements provide the ability to prematurely exit from a loop. Figure 1-13 shows the syntax for these two statements.
The break
statement exits the loop that immediately contains the break
. The breakif
statement evaluates the boolean expression and exits the containing loop if the expression evaluates true.
Note that the break
and breakif
statements do not allow you to break out of more than one nested loop. HLA does provide statements that do this, the begin..end
block and the exit
/exitif
statements. Please consult the HLA reference manual for more details. HLA also provides the continue
/continueif
pair that lets you repeat a loop body. Again, see the HLA reference manual for more details.
Figure 1-14 shows the syntax for the forever
statement.
This statement creates an infinite loop. You may also use the break
and breakif
statements along with forever..endfor
to create a loop that tests for loop termination in the middle of the loop. Indeed, this is probably the most common use of this loop, as the following example demonstrates:
forever stdout.put( "Enter an integer less than 10: "); stdin.get( i ); breakif( i < 10 ); stdout.put( "The value needs to be less than 10!", nl ); endfor;
The HLA try..exception..endtry
statement provides very powerful exception handling capabilities. The syntax for this statement appears in Figure 1-15.
The try..endtry
statement protects a block of statements during execution. If the statements between the try
clause and the first exception
clause (the protected block), execute without incident, control transfers to the first statement after the endtry
immediately after executing the last statement in the protected block. If an error (exception) occurs, then the program interrupts control at the point of the exception (that is, the program raises an exception). Each exception has an unsigned integer constant associated with it, known as the exception ID. The excepts.hhf header file in the HLA Standard Library predefines several exception IDs, although you may create new ones for your own purposes. When an exception occurs, the system compares the exception ID against the values appearing in each of the exception clauses following the protected code. If the current exception ID matches one of the exception values, control continues with the block of statements immediately following that exception. After the exception-handling code completes execution, control transfers to the first statement following the endtry
.
If an exception occurs and there is no active try..endtry
statement, or the active try..endtry
statements do not handle the specific exception, the program will abort with an error message.
The following code fragment demonstrates how to use the try..endtry
statement to protect the program from bad user input:
repeat mov( false, GoodInteger ); // Note: GoodInteger must be a boolean var. try stdout.put( "Enter an integer: " ); stdin.get( i ); mov( true, GoodInteger ); exception( ex.ConversionError ); stdout.put( "Illegal numeric value, please re-enter", nl ); exception( ex.ValueOutOfRange ); stdout.put( "Value is out of range, please re-enter", nl ); endtry; until( GoodInteger );
The repeat..until
loop repeats this code as long as there is an error during input. Should an exception occur because of bad input, control transfers to the exception clauses to see if a conversion error (e.g., illegal characters in the number) or a numeric overflow occurs. If either of these exceptions occur, then they print the appropriate message, control falls out of the try..endtry
statement, and the repeat..until
loop repeats because the code will not have set GoodInteger
to true. If a different exception occurs (one that is not handled in this code), then the program aborts with the specified error message.[10]
Table 1-4 lists the exceptions provided in the excepts.hhf header file at the time this was being written. See the excepts.hhf header file provided with HLA for the most current list of exceptions.
Table 1-4. Exceptions Provided in excepts.hhf
Exception | Description |
---|---|
Attempt to store a string that is too large into a string variable. | |
Attempt to access a character that is not present in a string. | |
Attempt to copy a string onto itself. | |
Corrupted string value. | |
Attempt to store a string an at unaligned address. | |
Attempt to extract "negative" characters from a string. | |
Operation not permitted on string data. | |
Value is too large for the current operation. | |
Operation encountered a character code whose ASCII code is not in the range 0..127. | |
Command line contains too many program parameters. | |
Pointer to class object is illegal. | |
Argument was not aligned on a proper memory address. | |
Function call (generally OS API call) contains an invalid argument value. | |
Buffer or blob object exceeded declared size. | |
Attempt to retrieve nonexistent data from a blob or buffer. | |
Argument's data size is incorrect. | |
String-to-numeric conversion operation contains illegal (nonnumeric) characters. | |
Program attempted a file access using an invalid file handle value. | |
Program attempted to access a nonexistent file. | |
Operating system could not open the file (file not found). | |
Operating system could not close the file. | |
Error writing data to a file. | |
Error reading data from a file. | |
Attempted to seek to a nonexistent position in a file. | |
Attempted to write data to a full disk. | |
User does not have sufficient priviledges to access file data. | |
Program attempted to read beyond the end of file. | |
Attempt to create a directory failed. | |
Attempt to delete a directory failed. | |
Attempt to delete a file failed. | |
Attempt to change to a new directory failed. | |
Attempt to rename a file failed. | |
Insufficient system memory for allocation request. | |
Could not free the specified memory block (corrupted memory management system). | |
Corrupted memory management system. | |
Caller attempted to free a NULL pointer. | |
Program attempted to access data indirectly using a NULL pointer. | |
Caller attempted to free a block that was already freed. | |
Memory free operation failure. | |
Caller attempted to free a block of memory that was not allocated on the heap. | |
Format width for numeric to string conversion was too large. | |
Format size for fractional portion in floating-point-to-string conversion was too large. | |
Attempted operation on two arrays whose dimensions don't match. | |
Attempted to access an element of an array, but the index was out of bounds. | |
Attempted date operation with an illegal date. | |
Conversion from string to date contains illegal characters. | |
Overflow during time arithmetic. | |
Attempted time operation with an illegal time. | |
Conversion from string to time contains illegal characters. | |
Network communication failure. | |
Generic thread (multitasking) error. | |
| |
Attempt to execute an abstract class method. | |
Attempt to access an illegal memory location. | |
OS memory access error. | |
OS memory failure. | |
Bad handle passed to OS API call. | |
ctrl-C was pressed on system console (functionality is OS specific). | |
Program executed a breakpoint instruction (INT 3). | |
Program is operating with the trace flag set. | |
Program attempted to execute a kernel-only instruction. | |
Program attempted to execute an illegal machine instruction. | |
Bound instruction execution with "out of bounds" value. | |
Into instruction execution with the overflow flag set. | |
Program attempted division by zero or other divide error. | |
Floating point exception (see Chapter 6). | |
Floating point exception (see Chapter 6). | |
Floating point exception (see Chapter 6). | |
Floating point exception (see Chapter 6). | |
Floating point exception (see Chapter 6). | |
Floating point exception (see Chapter 6). | |
Floating point exception (see Chapter 6). | |
| OS reported an invalid handle for some operation. |
Most of these exceptions occur in situations that are well beyond the scope of this chapter. Their appearance here is strictly for completeness. See the HLA reference manual, the HLA Standard Library documentation, and the HLA Standard Library source code for more details concerning these exceptions. The ex.ConversionError
, ex.ValueOutOfRange
, and ex.StringOverflow
exceptions are the ones you'll most commonly use.
We'll return to the discussion of the try..endtry
statement in 1.11 Additional Details About try..endtry. First, however, we need to cover a little more material.
[10] An experienced programmer may wonder why this code uses a boolean variable rather than a breakif
statement to exit the repeat..until
loop. There are some technical reasons for this that you will learn about in 1.11 Additional Details About try..endtry.