720 lines
27 KiB
Plaintext
720 lines
27 KiB
Plaintext
CHAPTER 8 NUMBERS AND EXPRESSIONS
|
||
|
||
|
||
Numbers and Bases
|
||
|
||
A86 supports a variety of formats for numbers. In non-computer
|
||
life, we write numbers in a decimal format. There are ten
|
||
digits, 0 through 9, that we use to describe numbers; and each
|
||
digit position is ten times as significant as the position to its
|
||
right. The number ten is called the "base" of the decimal
|
||
format. Computer programmers often find it convenient to use
|
||
other bases to specify numbers used in their programs. The most
|
||
commonly-used bases are two (binary format), sixteen (hexadecimal
|
||
format), and eight (octal format).
|
||
|
||
The hexadecimal format requires sixteen digits. The extra six
|
||
digits beyond 0 through 9 are denoted by the first six letters of
|
||
the alphabet: A for ten, B for eleven, C for twelve, D for
|
||
thirteen, E for fourteen, and F for fifteen.
|
||
|
||
In A86, a number must always begin with a digit from 0 through 9,
|
||
even if the base is hexadecimal. This is so that A86 can
|
||
distinguish between a number and a symbol that happens to have
|
||
digits in its name. If a hexadecimal number would begin with a
|
||
letter, you precede the letter with a zero. For example, hex A0,
|
||
which is the same as decimal 160, would be written 0A0.
|
||
|
||
Because it is necessary for you to append leading zeroes to many
|
||
hex numbers, and because you never have to do so for decimal
|
||
numbers, I decided to make hexadecimal the default base for
|
||
numbers with leading zeroes. Decimal is still the default base
|
||
for numbers beginning with 1 through 9.
|
||
|
||
Large numbers can be given as the operands to DD, DQ, or DT
|
||
directives. For readability, you may freely intersperse
|
||
underscore characters anywhere with your numbers.
|
||
|
||
The default base can be overridden, with a letter or letters at
|
||
the end of the number: B or xB for binary, O or Q for octal, H
|
||
for hexadecimal, and D or xD for decimal. Examples:
|
||
|
||
077Q octal, value is 8*7 + 7 = 63 in decimal notation
|
||
123O octal if the "O" is a letter: 64 + 2*8 + 3 = 83 decimal
|
||
1230 decimal 1230: shows why you should use "Q" for octal!!
|
||
01234567H large constant
|
||
0001_0000_0000_0000_0003R real number specified in hexadecimal
|
||
100D superfluous D indicates decimal base
|
||
0100D hex number 100D, which is 4096 + 13 = 5009 in decimal
|
||
0100xD decimal 100, since xD overrides the default hex format
|
||
0110B hex 110B, which is 4096 + 256 + 11 = 4363 in decimal
|
||
0110xB binary 4+2 = 6 in decimal notation
|
||
110B also binary 4+2 = 6, since "B" is not a decimal digit
|
||
8-2
|
||
|
||
The last five examples above illustrate why an "x" is sometimes
|
||
necessary before the base-override letter "B" or "D". If that
|
||
letter can be interpreted as a hex digit, it is; the "x" forces
|
||
an override interpretation for the "B" or "D". By the way, the
|
||
usage of lower case for x and upper case for the following
|
||
override letter is simply a recommendation; A86 treats upper-and
|
||
lower-case letters equivalently.
|
||
|
||
|
||
The RADIX Directive
|
||
|
||
The above-mentioned set of defaults (hex if leading zero, decimal
|
||
otherwise) can be overridden with the RADIX directive. The RADIX
|
||
directive consists of the word RADIX followed by a number from 2
|
||
to 16. The default base for the number is ALWAYS decimal,
|
||
regardless of any (or no) previous RADIX commands. The number
|
||
gives the default base for ALL subsequent numbers, up to (but not
|
||
including) the next RADIX command. If there is no number
|
||
following RADIX, then A86 returns to its initial mixed default of
|
||
hex for leading zeroes, decimal for other leading digits.
|
||
|
||
For compatibility with IBM's assembler, RADIX can appear with a
|
||
leading period; although I curse the pinhead designer who put
|
||
that period into IBM's language.
|
||
|
||
As an alternative to the RADIX directive, I provide the D switch,
|
||
which causes A86 to start with decimal defaults. You can put +D
|
||
into the A86 command invocation, or into the A86 environment
|
||
variable. The first RADIX command in the program will override
|
||
the D switch setting.
|
||
|
||
Following are examples of radix usage. The numbers in the
|
||
comments are all in decimal notation.
|
||
|
||
DB 10,010 ; produces 10,16 if RADIX was not seen yet
|
||
; and +D switch was not specified
|
||
RADIX 10
|
||
DB 10,010 ; produces 10,10
|
||
RADIX 16
|
||
DB 10,010 ; produces 16,16
|
||
RADIX 2
|
||
DB 10,01010 ; produces 2,10
|
||
RADIX 3 ; for Martian programmers in Heinlein novels
|
||
DB 10,100 ; produces 3,9
|
||
RADIX
|
||
DB 10,010 ; produces 10,16
|
||
8-3
|
||
|
||
Floating Point Initializations
|
||
|
||
A86 allows floating point numbers as the operands to DD, DQ, and
|
||
DT directives. The numbers are encoded according to the IEEE
|
||
standard, followed by the 8087 and 287 coprocessors. The format
|
||
for floating point constants is as follows: First, there is a
|
||
decimal number containing a decimal point. There must be a
|
||
decimal point, or else the number is interpreted as an integer.
|
||
There must also be at least one decimal digit, either to the left
|
||
or right of the decimal point, or else the decimal point is
|
||
interpreted as an addition (structure element) operator.
|
||
Optionally, there may follow immediately after the decimal number
|
||
the letter E followed by a decimal number. The E stands for
|
||
"exponent", and means "times 10 raised to the power of". You may
|
||
provide a + or - between the E and its number. Examples:
|
||
|
||
0.1 constant one-tenth
|
||
.1 the same
|
||
300. floating point three hundred
|
||
30.E1 30 * 10**1; i.e., three hundred
|
||
30.E+1 the same
|
||
30.E-1 30 * 10**-1; i.e., three
|
||
30E1 not floating point: hex integer 030E1
|
||
1.234E20 scientific notation: 1.234 times 10 to the 20th
|
||
1.234E-20 a tiny number: 1.234 divided by 10 to the 20th
|
||
|
||
|
||
|
||
Overview of Expressions
|
||
|
||
Most of the operands that you code into your instructions and
|
||
data initializations will be simple register names, variable
|
||
names, or constants. However, you will regularly wish to code
|
||
operands that are the results of arithmetic calculations,
|
||
performed either by the machine when the program is running (for
|
||
indexing), or by the assembler (to determine the value to
|
||
assemble into the program). A86 has a full set of operators that
|
||
you can use to create expressions to cover these cases:
|
||
|
||
* Arithmetic Operators
|
||
byte isolation and combination (HIGH, LOW, BY)
|
||
addition and subtraction (+,-)
|
||
multiplication and division (* , /, MOD)
|
||
shifting operators (SHR, SHL, BIT)
|
||
|
||
* Logical Operators
|
||
(AND, OR, XOR, NOT)
|
||
|
||
* Boolean Negation Operator
|
||
(!)
|
||
|
||
* Relational Operators
|
||
(EQ, LE, LT, GE, GT, NE)
|
||
|
||
* String Comparison Operators
|
||
(EQ, NE, =)
|
||
8-4
|
||
|
||
* Attribute Operators/Specifiers
|
||
size specifiers (B=BYTE,W=WORD,F=FAR,SHORT,LONG)
|
||
attribute specifiers (OFFSET,NEAR,brackets)
|
||
segment addressing specifier (:)
|
||
compatibility operators (PTR,ST)
|
||
built-in value specifiers (TYPE,THIS,$)
|
||
|
||
* Special Data Duplication Operator
|
||
(DUP) --see Chapter 9 for a description
|
||
|
||
|
||
Types of Expression Operands
|
||
|
||
Numbers and Label Addresses
|
||
|
||
A number or constant (16-bit number) can be used in most
|
||
expressions. A label (defined with a colon) is also treated as
|
||
a constant and so can be used in expressions, except when it is a
|
||
forward reference.
|
||
|
||
Variables
|
||
|
||
A variable stands for a byte- or word-memory location. You may
|
||
add or subtract constants from variables; when you do so, the
|
||
constant is added to the address of the variable. You typically
|
||
do this when the variable is the name of a memory array.
|
||
|
||
Index Expressions
|
||
|
||
An index expression consists of a combination of a base register
|
||
[BX] or [BP], and/or an index register [SI] or [DI], with an
|
||
optional constant added or subtracted. You will usually want to
|
||
precede the bracketed expression with B, W, or F; to specify the
|
||
kind of memory unit (byte, word, or far pointer) you are
|
||
referring to. The expression stands for the memory unit whose
|
||
address is the run-time value(s) of the base and/or index
|
||
registers added to the constant. See the Effective Address
|
||
section and the beginning of this chapter for more details on
|
||
indexed memory.
|
||
|
||
|
||
Arithmetic Operators
|
||
|
||
|
||
HIGH/LOW
|
||
|
||
Syntax: HIGH operand
|
||
LOW operand
|
||
|
||
These operators are called the "byte isolation" operators. The
|
||
operand must evaluate to a 16-bit number. HIGH returns the
|
||
high order byte of the number; LOW the low order byte.
|
||
|
||
For example,
|
||
|
||
MOV AL,HIGH(01234) ; AL = 012
|
||
TENHEX EQU LOW(0FF10) ; TENHEX = 010
|
||
8-5
|
||
|
||
These operators can be applied to each other. The following
|
||
identities apply:
|
||
|
||
LOW LOW Q = LOW Q
|
||
LOW HIGH Q = HIGH Q
|
||
HIGH LOW Q = 0
|
||
HIGH HIGH Q = 0
|
||
|
||
|
||
BY
|
||
|
||
Syntax: operand BY operand
|
||
|
||
This operator is a "byte combination" operator. It returns the
|
||
word whose high byte is the left operand, and whose low byte is
|
||
the right operand. For example, the expression 3 BY 5 is the
|
||
same as hexadecimal 0305. The BY operator is exclusive to A86. I
|
||
added it to cover the following situation: Suppose you are
|
||
initializing your registers to immediate values. Suppose you
|
||
want to initialize AH to the ASCII value 'A', and AL to decimal
|
||
10. You could code this as two instructions MOV AH,'A' and MOV
|
||
AL,10; but you realize that a single load into the AX register
|
||
would save both program space and execution time. Without the BY
|
||
operator, you would have to code MOV AX,0410A, which disguises
|
||
the types of the individual byte operands you were thinking
|
||
about. With BY, you can code it properly: MOV AX,'A' BY 10.
|
||
|
||
|
||
Addition (combination)
|
||
|
||
Syntax: operand + operand
|
||
operand.operand
|
||
operand PTR operand
|
||
operand operand
|
||
|
||
As shown in the above syntax, addition can be accomplished in
|
||
four ways: with a plus sign, with a dot operator, with a PTR
|
||
operator, and simply by juxtaposing two operands next to each
|
||
other. The dot and PTR operators are provided for compatibility
|
||
with Intel/IBM assemblers. The dot is used in structure field
|
||
notation; PTR is used in expressions such as BYTE PTR 0. (See
|
||
Chapter 12 for recommendations concerning PTR.)
|
||
|
||
If either operand is a constant, the answer is an expression with
|
||
the typing of the other operand, with the offsets added. For
|
||
example, if BVAR is a byte variable, then BVAR + 100 is the byte
|
||
variable 100 bytes beyond BVAR.
|
||
|
||
Other examples:
|
||
|
||
DB 100+17 ; simple addition
|
||
CTRL EQU -040
|
||
MOV AL,CTRL'D' ; a nice notation for control-D!
|
||
MOV DX,[BP].SMEM ; --where SMEM was in an unindexed structure
|
||
DQ 10.0 + 7.0 ; floating point addition
|
||
8-6
|
||
|
||
Subtraction
|
||
|
||
Syntax: operand - operand
|
||
|
||
The subtraction operator may have operands that are:
|
||
|
||
a. both absolute numbers
|
||
|
||
b. variable names that have the same type
|
||
|
||
The result is an absolute number; the difference between the two
|
||
operands.
|
||
|
||
Subtraction is also allowed between floating point numbers; the
|
||
answer is the floating point difference.
|
||
|
||
|
||
Multiplication and Division
|
||
|
||
Syntax: operand * operand (multiplication)
|
||
operand / operand (division)
|
||
operand MOD operand (modulo)
|
||
|
||
You may only use these operators with absolute or floating point
|
||
numbers, and the result is always the same type. Either operand
|
||
may be a numeric expression, as long as the expression evaluates
|
||
to an absolute or floating point number. Examples:
|
||
|
||
CMP AL,2 * 4 ; compare AL to 8
|
||
MOV BX,0123/16 ; BX = 012
|
||
DT 1.0 / 7.0
|
||
|
||
|
||
|
||
Shifting Operators
|
||
|
||
Syntax: operand SHR count (shift right)
|
||
operand SHL count (shift left)
|
||
BIT count (bit number)
|
||
|
||
The shift operators will perform a "bit-wise" shift of the
|
||
operand. The operand will be shifted "count" bits either to the
|
||
right or the left. Bits shifted into the operand will be set to
|
||
0.
|
||
|
||
The expression "BIT count" is equivalent to "1 SHL count"; i.e.,
|
||
BIT returns the mask of the single bit whose number is "count".
|
||
The operands must be numeric expressions that evaluate to
|
||
absolute numbers. Examples:
|
||
|
||
MOV BX, 0FACBH SHR 4 ; BX = 0FACH
|
||
OR AL,BIT 6 ; AL = AL OR 040; 040 is the mask for bit 6
|
||
8-7
|
||
|
||
Logical Operators
|
||
|
||
Syntax: operand OR operand
|
||
operand XOR operand
|
||
operand AND operand
|
||
NOT operand
|
||
|
||
The logical operators may only be used with absolute numbers.
|
||
They always return an absolute number.
|
||
|
||
Logical operators operate on individual bits. Each bit of the
|
||
answer depends only on the corresponding bit in the operand(s).
|
||
|
||
The functions performed are as follows:
|
||
|
||
1. OR: An answer bit is 1 if either or both of the operand bits
|
||
is 1. An answer bit is 0 only if both operand bits are 0.
|
||
|
||
Example:
|
||
|
||
11110000xB OR 00110011xB = 11110011xB
|
||
|
||
|
||
2. XOR: This is "exclusive OR." An answer bit is 1 if the
|
||
operand bits are different; an answer bit is 0 if the operand
|
||
bits are the same. Example:
|
||
|
||
11110000xB XOR 00110011xB = 11000011xB
|
||
|
||
|
||
3. AND: An answer bit is 1 only if both operand bits are 1. An
|
||
answer bit is 0 if either or both operand bits are 0.
|
||
Example:
|
||
|
||
11110000xB AND 00110011xB = 00110000xB
|
||
|
||
4. NOT: An answer bit is the opposite of the operand bit. It
|
||
is 1 if the operand bit is 0; 0 if the operand bit is 1.
|
||
Example:
|
||
|
||
NOT 00110011xB = 11001100xB
|
||
|
||
|
||
Boolean Negation Operator
|
||
|
||
Syntax: ! operand
|
||
|
||
The exclamation-point operator, rather than reversing each
|
||
individual bit of the operand, considers the entire operand as a
|
||
boolean variable to be negated. If the operand is non-zero (any
|
||
of the bits are 1), the answer is 0. If the operand is zero, the
|
||
answer is 0FFFF.
|
||
8-8
|
||
|
||
Because ! is intended to be used in conditional assembly
|
||
expressions (described in Chapter 11), there is also a special
|
||
action when ! is applied to an undefined name: the answer is the
|
||
defined value 0FFFF, meaning it is TRUE that the symbol is
|
||
undefined. Similarly, when ! is applied to some defined quantity
|
||
other than an absolute constant, the answer is 0, meaning it is
|
||
FALSE that the operand is undefined.
|
||
|
||
|
||
Relational Operators
|
||
|
||
Syntax: operand EQ operand (equal)
|
||
operand NE operand (not equal)
|
||
operand LT operand (less than)
|
||
operand LE operand (less or equal)
|
||
operand GT operand (greater than)
|
||
operand GE operand (greater or equal)
|
||
|
||
The relational operators may have operands that are:
|
||
|
||
a. both absolute numbers
|
||
|
||
b. variable names that have the same type
|
||
|
||
The result of a relational operation is always an absolute
|
||
number. They return an 8-or 16-bit result of all 1's for TRUE
|
||
and all 0's for FALSE. Examples:
|
||
|
||
MOV AL, 3 EQ 0 ; AL = 0 (false)
|
||
MOV AX, 2 LE 15 ; AX = 0FFFFH (true)
|
||
|
||
|
||
String Comparison Operators
|
||
|
||
Syntax: string EQ string (equal)
|
||
string NE string (not equal)
|
||
string = string (equal ignoring case)
|
||
|
||
In order to subsume the string comparison facilities offered by
|
||
That Other Assembler's special conditional-assembly directives
|
||
IFIDN and IFDIF, A86 allows the relational operators EQ and NE to
|
||
accept string arguments. For this syntax to be accepted by A86,
|
||
both strings must be bounded using the same delimiter (either
|
||
single quotes for both strings, or double quotes for both
|
||
strings). For a match (EQ returns TRUE or NE returns FALSE), the
|
||
strings must be the same length, and every character must match
|
||
exactly.
|
||
8-9
|
||
|
||
An additional A86-exclusive feature is the = operator, which
|
||
returns TRUE if the characters of the strings differ only in the
|
||
bit masked by the value 020. Thus you may use = to compare a
|
||
macro parameter to a string containing nothing but letters. The
|
||
comparison will be TRUE whether the macro parameter is upper-case
|
||
or lower-case. No checking is made to detect non-letters, so if
|
||
you use = on strings containing non-letters, you may get some
|
||
false TRUE results. Also, = is accepted when it is applied to
|
||
non-strings as well-- the corresponding values are interpreted as
|
||
two-byte strings, with the 020 bits masked away before
|
||
comparison.
|
||
|
||
|
||
|
||
Attribute Operators/Specifiers
|
||
|
||
|
||
B,W,D,Q,T memory variable specifiers
|
||
|
||
Syntax: B operand Q operand
|
||
operand B operand Q
|
||
W operand T operand
|
||
operand W operand T
|
||
D operand
|
||
operand D
|
||
|
||
B, W, D, F, Q, and T convert the operand into a byte, word,
|
||
doubleword, far, quadword, and ten-byte variable, respectively.
|
||
The operand can be a constant, or a variable of the other type.
|
||
Examples:
|
||
|
||
ARRAY_PTR:
|
||
DB 100 DUP (?)
|
||
WVAR DW ?
|
||
MOV AL,ARRAY_PTR B ; load first byte of ARRAY_PTR array into AL
|
||
MOV AL,WVAR B ; load the low byte of WVAR into AL
|
||
MOV AX,W[01000] ; load AX with the memory word at loc. 01000
|
||
LDS BX,D[01000] ; load DS:BX with the doubleword at loc. 01000
|
||
JMP F[01000] ; jump far to the 4-byte location at 01000
|
||
FLD T[BX] ; load ten-byte number at [BX] to 87 stack
|
||
|
||
|
||
For compatibility with Intel/IBM assemblers, A86 accepts the more
|
||
verbose synonyms BYTE, WORD, DWORD, FAR, QWORD, and TBYTE for
|
||
B,W,D,F,Q,T, respectively.
|
||
|
||
|
||
SHORT and LONG Operators
|
||
|
||
Syntax: SHORT label
|
||
LONG label
|
||
8-10
|
||
|
||
The SHORT operator is used to specify that the label referenced
|
||
by a JMP instruction is within 127 bytes of the end of the
|
||
instruction. The LONG operator specifies the opposite: that the
|
||
label is not within 127 bytes. The appropriate operator can (and
|
||
sometimes must) be used if the label is forward referenced in the
|
||
instruction.
|
||
|
||
When a non-local label is forward referenced, the assembler
|
||
assumes that it will require two bytes to represent the relative
|
||
offset of the label (so the instruction including the opcode byte
|
||
will be three bytes). By correctly using the SHORT operator, you
|
||
can save a byte of code when you use a forward reference. If the
|
||
label is not within the specified range, an error will occur. The
|
||
following example illustrates the use of the SHORT operator.
|
||
|
||
JMP FWDLAB ; three byte instruction
|
||
JMP SHORT FWDLAB ; two byte instruction
|
||
JMP >L1 ; two byte instruction assumed for a local label
|
||
|
||
Because the assembler assumes that a forward reference local
|
||
label is SHORT, you may sometimes be forced to override this
|
||
assumption if the label is in fact not within 127 bytes of the
|
||
JMP. This is why LONG is provided:
|
||
|
||
JMP LONG >L9 ; three byte instruction
|
||
|
||
If you are bothered by this possibility, you can specify the +L
|
||
switch, which causes A86 to pessimistically generate the three
|
||
byte JMP for all forward references, unless specifically told not
|
||
to with SHORT.
|
||
|
||
NOTE that LONG will have effect only on the operand to an
|
||
unconditional JMP instruction; not to conditional jumps. This is
|
||
because the conditional jumps don't have 3-byte forms; the only
|
||
conditional jumps are short ones. If you run into this problem,
|
||
then chances are your code is getting out of control--time to
|
||
rearrange, or to break off some of the intervening code into
|
||
separate procedures. If you insist upon leaving the code intact,
|
||
you can replace the conditional jump with an "IF cond JMP".
|
||
|
||
|
||
OFFSET Operator
|
||
|
||
Syntax: OFFSET var-name
|
||
|
||
OFFSET is used to convert a variable into the constant pointer to
|
||
the variable. For example, if you have declared XX DW ?, and
|
||
you want to load SI with the pointer to the variable XX, you can
|
||
code: MOV SI,OFFSET XX. The simpler instruction MOV SI,XX moves
|
||
the variable contents of XX into SI, not the constant pointer to
|
||
XX.
|
||
8-11
|
||
|
||
NEAR Operator
|
||
|
||
Syntax: NEAR operand
|
||
|
||
NEAR converts the operand to have the type of a code label, as if
|
||
it were defined by appearing at the beginning of a program line
|
||
with a colon after it. NEAR is provided mainly for compatibility
|
||
with Intel/IBM assemblers.
|
||
|
||
|
||
Square Brackets Operator
|
||
|
||
Syntax: [operand]
|
||
|
||
Square brackets around an operand give the operand a memory
|
||
variable type. Square brackets are generally used to enclose the
|
||
names of base and index registers: BX, BP, SI, and DI. When the
|
||
size of the memory variable can be deduced from the context of
|
||
the expression, square brackets are also used to turn numeric
|
||
constants into memory variables. Examples:
|
||
|
||
MOV B[BX+50],047 ; move imm value 047 into mem byte at BX+50
|
||
MOV AL,[050] ; move byte at memory location 050 into AL
|
||
MOV AL,050 ; move immediate value 050 into AL
|
||
|
||
|
||
Colon Operator
|
||
|
||
Syntax: constant:operand
|
||
segreg:operand
|
||
seg_or_group_name:operand
|
||
|
||
The colon operator is used to attach a segment register value to
|
||
an operand. The segment register value appears to the left of
|
||
the colon; the rest of the operand appears to the right of the
|
||
colon.
|
||
|
||
There are three forms to the colon operator. The first form has
|
||
a constant as the segment register value. This form is used to
|
||
create an operand to a long (inter-segment) JMP or CALL
|
||
instruction. An example of this is the instruction JMP 0FFFF:0,
|
||
which jumps to the cold-boot reset location of the 86 processor.
|
||
|
||
The only context other than JMP or CALL in which this first form
|
||
is legal, is as the operand to a DD directive or an EQU
|
||
directive. The EQU case has a further restriction: the offset
|
||
(the part to the right of the colon) must have a value less than
|
||
256. This is because there simply isn't room in a symbol table
|
||
entry for a segment register value AND a 2-byte offset. I don't
|
||
think you will be hurt by this restriction, since references to
|
||
other segments are usually to jump tables at the beginning of
|
||
those segments.
|
||
8-12
|
||
|
||
The second form has a segment register name to the left of the
|
||
colon. This is the segment override form, provided for
|
||
compatibility with Intel/IBM assemblers. A86 will generate a
|
||
segment override byte when it sees this form, unless the operand
|
||
to the right of the colon already has a default segment register
|
||
that is the same as the given override.
|
||
|
||
I prefer the more explicit method of overrides, exclusive to A86:
|
||
simply place the segment register name before the instruction
|
||
mnemonic. For example, I prefer ES MOV AL,[BX] to MOV
|
||
AL,ES:[BX].
|
||
|
||
The third form has a segment or group name before the colon. This
|
||
form is ignored by A86; it is provided for compatibility with
|
||
Turbo C, which likes to include spurious DGROUP: overrides, to
|
||
satisfy MASM's ASSUME-checking.
|
||
|
||
|
||
ST Operator
|
||
|
||
ST is ignored whenever it occurs in an expression. It is
|
||
provided for compatibility with Intel and IBM assemblers. For
|
||
example, you can code FLD ST(0),ST(1), which will be taken by A86
|
||
as FLD 0,1.
|
||
|
||
|
||
TYPE Operator
|
||
|
||
Syntax: TYPE operand
|
||
|
||
The TYPE operator returns 1 if the operand is a byte variable; 2
|
||
if the operand is a word variable; 4 if the operand is a
|
||
doubleword variable; 8 if the operand is a quadword variable; 10
|
||
if the operand is a ten-byte variable; and the number of bytes
|
||
allocated by the structure if the operand is a structure name
|
||
(see STRUC in the next chapter).
|
||
|
||
A common usage of the TYPE operator is to represent the number of
|
||
bytes of a named structure. For example, if you have declared a
|
||
structure named LINE (as described in the next chapter) that
|
||
defines 82 bytes of storage, then two ways you might refer to the
|
||
value symbolically are as follows:
|
||
|
||
MOV CX,TYPE LINE ; loads the size of LINE into CX
|
||
DB TYPE LINE DUP ? ; allocates an area of memory for a LINE
|
||
|
||
|
||
|
||
THIS and $ Specifiers
|
||
|
||
THIS returns the value of the current location counter. It is
|
||
provided for compatibility with Intel/IBM assemblers. The dollar
|
||
sign $ is the more standard and familiar specifier for this
|
||
purpose; it is equivalent to THIS NEAR. THIS is typically used
|
||
with the BYTE and WORD specifiers to create alternate-typed
|
||
symbols at the same memory location:
|
||
8-13
|
||
|
||
BVAR EQU THIS BYTE
|
||
WVAR DW ?
|
||
|
||
I don't recommend the use of THIS. If you wish to retain Intel
|
||
compatibility, you can use the less verbose LABEL directive:
|
||
|
||
BVAR LABEL BYTE
|
||
WVAR DW ?
|
||
|
||
If you are not concerned with compatibility to lesser assemblers,
|
||
A86 offers a variety of less verbose forms. The most concise is
|
||
DB without an operand:
|
||
|
||
BVAR DB
|
||
WVAR DW ?
|
||
|
||
If this is too cryptic for you, there is always BVAR EQU B[$].
|
||
|
||
|
||
Operator Precedence
|
||
|
||
Consider the expression 1 + 2 * 3. When A86 sees this
|
||
expression, it could perform the multiplication first, giving an
|
||
answer of 1+6 = 7; or it could do the addition first, giving an
|
||
answer of 3*3 = 9. In fact, A86 does the multiplication first,
|
||
because A86 assigns a higher precedence to multiplication than it
|
||
does addition.
|
||
|
||
The following list specifies the order of precedence A86 assigns
|
||
to expression operators. All expressions are evaluated from left
|
||
to right following the precedence rules. You may override this
|
||
order of evaluation and precedence through the use of parentheses
|
||
( ). In the example above, you could override the precedence by
|
||
parenthesizing the addition: (1+2) * 3.
|
||
|
||
Some symbols that we have referred to as operators, are treated
|
||
by the assembler as operands having built-in values. These
|
||
include B, W, F, $, and ST. In a similar vein, a segment
|
||
override term (a segment register name followed by a colon) is
|
||
recorded when it is scanned, but not acted upon until the entire
|
||
containing expression is scanned and evaluated.
|
||
|
||
If two operators are adjacent, the rightmost operator must have
|
||
precedence; otherwise, parentheses must be used. For example,
|
||
the expression BIT ! 1 is illegal because the leftmost operator
|
||
BIT has the higher precedence of the two adjacent operators BIT
|
||
and "!". You can code BIT (! 1).
|
||
|
||
--Highest Precedence--
|
||
8-14
|
||
|
||
1. Parenthesized expressions
|
||
2. Period
|
||
3. OFFSET, SEG, TYPE, and PTR
|
||
4. HIGH, LOW, and BIT
|
||
5. Multiplication and division: *, /, MOD, SHR, SHL
|
||
6. Addition and subtraction: +,-
|
||
a. unary
|
||
b. binary
|
||
7. Relational: EQ, NE, LT, LE, GT, GE =
|
||
8. Logical NOT and !
|
||
9. Logical AND
|
||
10. Logical OR and XOR
|
||
11. Colon for long pointer, SHORT, LONG, and BY
|
||
12. DUP
|
||
|
||
--Lowest Precedence--
|
||
|
||
|