Flags
User’s Guide
Version 2.0
Flags
provide a mechanism for supporting bit flags and bitwise operations beyond the
limitations of the normal TADS 3 32-bit infrastructure.
Copyright
© 2005 by Kevin Forchione. All
rights reserved.
Contents
Working
with Flags and Bit Flags
Converting
Between Bit Strings and Flags
Setting
and Clearing Flag Bits
Flags provide a compact way
of storing, evaluating, and manipulating object characteristics.
The relationship between
bit number and bit value is as follows:
bit0 => 20
= 1
bit1 => 21
= 2
bit2 => 22
= 4
bit3 => 23
= 8
. . .
For n bits:
The low
order bit is indexed at 0
The high
order bit is indexed at n - 1.
The usual approach to defining bit flags is to use
#defines and assign integer value powers of 2:
#define TAKEBIT 1 // 20
#define TRYTAKEBIT 2 // 21
#define CONTBIT 4 // 22
#define DOORBIT 8 // 23
#define OPENBIT 16 // 24
. . .
And so on until we reach
the limitations of an integer value.
We could then model the
state of an open door like this:
door: object
{
state = (DOORBIT|OPENBIT)
}
We’ve assigned the state by
combining two bit flags using the OR bitwise operator. We could then
interrogate the door’s state to determine if it were open by using the AND
bitwise operator, as follows:
If ((door.state
& OPENBIT) != 0)
do-something;
As we noted, up to 31 bit
flags can be defined in this way before the maximum integer value is reached.
It sounds like this ought to be more than enough for any definition. But when
using flags to keep track of object characteristics we can quickly bump up against
the 32-bit limit. For instance, ZIL employed at least 34 flags as documented in
the ZIL.pdf.
The TADS 3 Flags library
extension provides the means for an author to work with bit flags beyond the
32-bit limitation.
The BitFlag
class derives from the Flag class, specialized to allow ease of definition. In
this implementation a bit flag is an instance of Flag that has 1 and only 1 bit
turned “on”.
There are two ways to
create instances of BitFlag. You can either statically
define the flag, assigning it a bitNum value, like
this:
TakeBit: BitFlag
bitNum = 0
;
or you can create one dynamically by using the new
operator and passing the class a bitNum value, like
this:
new BitNum(3)
Because static definitions
of bit flags are so common and useful, the library extension provides a macro
for compactly defining static bit flags. Using the macro, the equivalent of the
bit flag #defines above would be:
TakeBit: DefBitFlag(0); // 20
TryTakeBit: DefBitFlag(1); // 21
ContBit: DefBitFlag(2); // 22
DoorBit: DefBitFlag(3); // 23
OpenBit: DefBitFlag(4); // 24
. . .
The DefBitFlag() macro defines
the superclass of each bit flag, and indicates which bit is to be turned “on”
in the bit flag. In the above examples the #define for TAKEBIT has a value of
1, while the DefBitFlag() macro for TakeBit passes ‘0’ as
its argument.
In addition, the macro will
throw a compile-time exception if the bitNum is
greater than that allowed by the particular implementation of Flags (the
default is 63).
The Flag class
encapsulates a collection used to store bit values. This allows the Flag class
to overcome the 32-bit limitation. Although the default implementation allows
for only 64 flags, this limit is modifiable, and since the collection has an
upper limit in the millions of bytes, for
practical purposes there is no limitation to the number of bit flags an author
can define.
For the sake of efficiency,
Flags doesn’t validate the ranges for bit numbers, bit strings, or element
numbers in its computations unless you tell it to do so. The DefBitFlag macro will automatically throw a compile-time
error if the bit number is greater than the maximum allowed by the
implementation. But this is the only range-checking done by the implementation
unless it is specifically told to do so.
If you want this further
safeguard, you must specifically tell Flags to validate ranges for bit numbers
and elements by defining __FLAG_VALIDATE__
in your compile.
In the majority of cases
this won’t be necessary, since most of the referencing by bit number and
element number are internal to the extension’s computation methods. In most of
the cases where you could reference an invalid bit number or element number
value, the system will throw an exception error at the lower level of its
implementation.
To create an instance of
Flag class you use the new operator, and pass its constructor either a single-quote
string of the binary representation of its value or a series of flags. For
instance, suppose we have the following bit flags defined:
TakeBit: DefBitFlag(0); // 20
TryTakeBit: DefBitFlag(1); // 21
ContBit: DefBitFlag(2); // 22
DoorBit: DefBitFlag(3); // 23
OpenBit: DefBitFlag(4); // 24
We could represent the
state of an open door with the following flag:
state = new Flag(DoorBit, OpenBit);
Or we could create this
flag using a “bit string” representation. If we use the “bit string” approach
we don’t need to supply high-order zeros.
state = new Flag(‘11000’);
We can create a “bit
string” representation for any existing flag simply by passing it the toBitString()
message, indicating whether or not we want a space separating each byte:
str = new Flag(‘11000’).toBitString(true);
The length of the string
will be determined by the Flag class’ collSize
attribute, which is set to a default of 2. This will allow Flag to accommodate up
to 64 bits of information (The maximum number of bits in an implementation can
be determined by adding 1 to the Flag class’ maxBitNum
attribute).
Creating flags from “bit
strings” and converting flags to “bit strings” is a handy convenience, both in making
a flag humanly readable, and in providing a useful shortcut in designating the
bits of a flag.
There is no native
representation of the series of bits that represent a flag. Instead the Flags
library extension makes use of single-quoted strings consisting of binary
values, separating bytes, if desired, by spaces.
So, for instance, this is a
“bit string”:
‘111’
and so is this:
‘00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000111’
A flag can be created from
a “bit string” representation, such as our new Flag(‘111’)
example. The method that actually performs this conversion is the cvtBitString()
method.
This method returns a new
flag.
It ignores whitespace within the bit string, and counts only non-whitespace characters to determine the bit number to set.
The method only sets those bits whose corresponding “bit string” values are
‘1’.
An example of the syntax
is:
flag = Flag.cvtBitString(‘1110 0111’)
This method converts the
bit values of a flag into a “bit string” representation. The “bit string” consists
of all the bits of the flag (represented by maxBitNum
+1). The method takes one optional argument: byteSep,
which if true will separate each byte of bits by a single whitespace
character in the string.
For example, for the
default (64-bit) implementation:
new flag(‘111’).toBitString(true)
will return a “bit string” that looks like this:
‘00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000111’
When this method is called
it will convert the flag to a “bit string” value and display it. This method is
intended for debugging purposes.
Both setBits() and clearBits() apply their results to the flag receiving those
messages and return self. Both methods can take multiple flag arguments,
applying the results recursively to the flag receiving the message.
Because these methods
return self, they can be used like the bitwise methods and we can chain
messages, as in the following example:
new Flag('111').clearBits(new
Flag('10')).disp();
This method sets the
corresponding bits of the receiving flag “on” if they are “on” for the flag
argument. No action is taken for those bits of the argument flag that are not
turned “on”.
flag1.setBits(flag2)
is equivalent to
flag1 = flag1.or(flag2)
This method sets the
corresponding bits of the receiving flag “off” if they are “on” for the flag argument.
No action is taken for those bits of the argument flag that are not turned
“on”. This allows us to specify with bit flags, those bits of the receiving
flag that we want to clear.
flag1.clearBits(flag2)
is equivalent to
flag1 = flag1.and(flag2.not())
One caveat: suppose we
wanted to clear the DoorBit and OpenBit
values for flag. Using flag.clearBits(DoorBit, OpenBit)
wouldn’t work properly because it would clear all the bits, regardless of the
flag’s bit values. To clear only the DoorBit and OpenBit we need to do the following: flag.clearBits(new
Flag(DoorBit, OpenBit)).
Because flags and bit flags
are objects, we can’t use bitwise operators on instances of the Flag class. We
have to use the equivalent Flag methods.
Flag class provides methods
that allow an author to perform bitwise and shift operations. Like BigNumber objects, the Flag object has specialized bitwise
methods that replace the native bitwise operators.
These bitwise methods of
Flag class don’t return integer values like bitwise operators do, they return
Flag instances.
Bitwise
Operator |
Flag
Method |
Meaning |
>> |
rsh() |
Shifts RIGHT by the
number of bits specified; zero
padded left. |
<< |
lsh() |
Shifts LEFT by the number
of bits specified; zero padded
right. |
! |
not() |
Bitwise NEGATION |
& |
and() |
Bitwise AND |
| |
or() |
Bitwise OR |
^ |
xor() |
Bitwise XOR |
The bitwise methods of Flag class don’t
return integer values like bitwise operators do, they return Flag instances.
Messages to the Flag class can be
chained. Chained messages are executed from left to right.
For example:
new Flag(‘111’).and(bit1).or(bit3).disp()
is equivalent to the following:
flag = new Flag(‘111’);
flag = flag.and(bit1);
flag = flag.or(bit3);
flag.disp();
The Flag bitwise methods not(), and(), or(), and xor() all
take flag arguments. The and(), or(), and xor() methods can take multiple flag arguments, which are recursively
applied to the results of the previous argument. For instance:
flag.and(DoorBit, OpenBit).lval() would return nil in every case because it is
equivalent to ((flag & DoorBit) & OpenBit). If you want to check if the flag has both DoorBit and OpenBit set you would
code it as flag.and(new Flag(DoorBit, OpenBit)).lval().
The not()
bitwise method takes no argument. It returns the complement of the flag. For
instance, new Flag(‘1010’).toBitString(true)
would return:
‘11111111 11111111 11111111 11111111 11111111 11111111 11111111 11110101’.
The shift methods return a
flag that has its bits shifted either left or right the indicated distance. For
instance new Flag(‘1111’).lsh(4).toBitString(true)
will return a string value ‘00000000 00000000 00000000
00000000 00000000 00000000 00000000 11110000’. Both
left and right shifts pad with zeros.
Much of the time we want to
evaluate the results of a bitwise operation to an integer or logical value. A
common example is to interrogate a flag for a certain bit value:
if ((flag & TakeBit) != 0)
do-something;
We can’t use bitwise
operators on the Flag class, we have to use the
equivalent method. But the bitwise methods in Flag class don’t return integer
values, they return Flag instances.
Method |
Purpose |
ival([bitNum]) |
Returns 1 to indicate
that the flag has at least one bit turned “on” otherwise returns 0. If a bitNum is passed then it evaluates the specified bit in
the flag. |
lval([bitNum]) |
Returns true to indicate
that the flag has at least one bit turned “on” otherwise returns nil. If a bitNum is passed then it evaluates the specified bit in
the flag. |
The ival() method examines the bits
of an instance of Flag and returns an integer value (1/0) indicating whether or
not at least one of the bits of the flag is “on”.
So, one way to code the
equivalent interrogation of flag for TakeBit using
the Flag ival method would be:
if (flag.and(TakeBit).ival() != 0)
do-something;
The lval() method examines the
bits of an instance of Flag and returns a logical value (true/nil) indicating
whether or not at least one of the bits of the flag is “on”.
So, one way to code the equivalent
interrogation of flag for TakeBit using the Flag lval method would be:
If (flag.and(TakeBit).lval())
do-something;
Sometimes we want to
compare a flag, or the results of a bitwise method to another flag. A typical
example is as follows:
if ((flag & TakeBit) == TakeBit)
do-something;
As we can’t use bitwise
operators on the Flag class, we have to use the equivalent method. But the
bitwise methods in Flag class don’t return integer values, they return Flag
instances.
If you wanted to compare
the result of a bitwise method to a flag, you could simply use the equals() method, like this:
if (flag.and(TakeBit).equals(TakeBit))
do-something;
The equals()
method returns a logical value (true/nil) to indicate whether the two flags
have equal bit values.
This method returns the
following integer values:
Return
Value |
Meaning |
-1 |
The bit values of the
flag receiving the &comp message are less than the bit values of the flag
in the argument |
0 |
The bit values of the
flag receiving the &comp message are equal to the bit values of the flag
in the argument |
1 |
The bit values of the
flag receiving the &comp message are greater than the bit values of the
flag in the argument |
You would code the
comparison similarly to the following:
if (flag.and(TakeBit).comp(TakeBit) == 0)
do-something;
You can count the number of
bits in a Flag, or in an element of a flag, by using the bitCount() method. This
method will return an integer value of the number of “on” bits for the Flag or
the element in question.
For instance:
new Flag(‘101’).countBits()
would return 2; while
new Flag(bit33, bit32, bit31).countBits(1)
would return 2.