|
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 trivial 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. Later in this text, 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.
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 only supports some basic forms.
HLA boolean expressions always 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.
Symbol | Meaning | Explanation |
---|---|---|
| ||
@c | Carry | True if the carry is set (1), false if the carry is clear (0). |
@nc | No carry | True if the carry is clear (0), false if the carry is set (1). |
@z | Zero | True if the zero flag is set, false if it is clear. |
@nz | No zero | True if the zero flag is clear, false if it is set. |
@o | Overflow | True if the overflow flag is set, false if it is clear. |
@no | No overflow | True if the overflow flag is clear, false if it is set. |
@s | Sign | True if the sign flag is set, false if it is clear. |
@ns | 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 non-zero value.
If you specify a boolean variable as the expression, the program tests it for zero (false) or non-zero (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.
Left Operand | Relational Operator | Right Operand |
---|---|---|
| ||
Memory variable | = or == | Memory variable, |
<> or != | ||
or | < | Register |
<= | ||
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 2000 and 2099 (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 lower case 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.
Figure 1-10: HLA IF Statement Syntax.
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 run-time boolean expression. This is a dyadic (two-operand) operator and the two operands must be legal run-time boolean expressions. This operator evaluates true if both operands evaluate to true. 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 expression; 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 contains 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 run-time 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. Examples of legal expressions using the "||" operator:
@z || al = 10 al in 'a'..'z' || ebx !boolVar || eax
As for 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, e.g.,
eax < 0 || ebx <> eax || !ecx
The code above executes if either 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 only occurs 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, 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 sub expressions 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 the logical negative of an existing boolean expression you must surround that expression with parentheses and prefix the parentheses with the "!" operator, e.g.,
!( 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 these 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.
Figure 1-11: HLA WHILE Statement Syntax.
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.
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. The Post_Body_Statement executes at the bottom of the loop (as shown in the while example above). This is a single HLA statement. Usually it is an instruction like add that 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.
Figure 1-12: HLA REPEAT..UNTIL Statement Syntax.
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[10] (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 provides the syntax for these two statements.
Figure 1-13: HLA BREAK and BREAKIF Syntax.
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 let you repeat a loop body. Again, see the HLA reference manual for more details.
Figure 1-14 shows the syntax for the forever statement.
Figure 1-14: HLA FOREVER Loop Syntax.
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.
Figure 1-15: HLA TRY..EXCEPTION..ENDTRY Statement Syntax.
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 and 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.[11]
Table 1-4 on the following page lists the exceptions provided in the excepts.hhf header file as this was being written. Please see the excepts.hhf header file provided with HLA for the most current list of exceptions.
Exception | Description |
---|---|
| |
ex.StringOverflow | Attempt to store a string that is too large into a string variable. |
ex.StringIndexError | Attempt to access a character that is not present in a string. |
ex.ValueOutOfRange | Value is too large for the current operation. |
ex.IllegalChar | Operation encountered a character code whose ASCII code is not in the range 0..127. |
ex.ConversionError | String-to-numeric conversion operation contains illegal (non-numeric) characters. |
ex.BadFileHandle | Program attempted a file access using an invalid file handle value. |
ex.FileOpenFailure | Operating system could not open file (file not found). |
ex.FileCloseError | Operating system could not close file. |
ex.FileWriteError | Error writing data to a file. |
ex.FileReadError | Error reading data from a file. |
ex.DiskFullError | Attempted to write data to a full disk. |
ex.EndOfFile | Program attempted to read beyond the end of file. |
ex.MemoryAllocationFailure | Insufficient system memory for allocation request. |
ex.AttemptToDerefNULL | Program attempted to access data indirectly using a NULL pointer. |
ex.CannotFreeMemory | Memory free operation failure. |
ex.WidthTooBig | Format width for numeric to string conversion was too large. |
ex.TooManyCmdLnParms | Command line contains too many arguments for processing by arg.c and arg.v. |
ex.ArrayShapeViolation | Attempted operation on two arrays whose dimensions don't match. |
ex.ArrayBounds | Attempt to access an element of an array, but the index was out of bounds. |
ex.InvalidDate | Attempted date operation with an illegal date. |
ex.InvalidDateFormat | Conversion from string to date contains illegal characters. |
ex.TimeOverflow | Overflow during time arithmetic. |
ex.AssertionFailed | ASSERT statement encountered a failed assertion. |
ex.ExecutedAbstract | Attempt to execute an abstract class method. |
ex.AccessViolation | Attempt to access an illegal memory location. |
ex.Breakpoint | Program executed a breakpoint instruction (INT 3). |
ex.SingleStep | Program is operating with the trace flag set. |
ex.PrivInstr | Program attempted to execute a kernel-only instruction. |
ex.IllegalInstr | Program attempted to execute an illegal machine instruction. |
ex.BoundInstr | Bound instruction execution with "out of bounds" value. |
ex.IntoInstr | Into instruction execution with the overflow flag set. |
ex.DivideError | Program attempted division by zero or other divide error. |
ex.fDenormal | Floating point exception (see the chapter on arithmetic). |
ex.fDivByZero | Floating point exception (see the chapter on arithmetic). |
ex.fInexactResult | Floating point exception (see the chapter on arithmetic). |
ex.fInvalidOperation | Floating point exception (see the chapter on arithmetic). |
ex.fOverflow | Floating point exception (see the chapter on arithmetic). |
ex.fStackCheck | Floating point exception (see the chapter on arithmetic). |
ex.fUnderflow | Floating point exception (see the chapter on arithmetic). |
ex.InvalidHandle | OS reported an invalid handle for some operation. |
ex.StackOverflow | OS reported a stack overflow. |
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 a little bit later in this chapter. First, however, we need to cover a little more material.
[9]There are a few additional forms that we'll cover in later sctions and later chapters.
[10]Note that this condition is the opposite of the do..while loop found in C/C++ and Java.
[11]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 later in this text.
|