Technical Notes
OverviewThe table functions are fast, low-level routines. They are most commonly used to associate key codes with function addresses or to identify labels to be jumped to when an input stream contains specific characters. However, these table functions may be used to automate almost any high-level task where one set of values must be mapped into another set of values on a one-to-one or a many-to-one basis. Tables can be thought of as definitions of rules, constraints, or associations, and the table functions may be thought of as the interpreters of those rules.
Index tables are structured as follows:
struct {
int/char near *next; /* Offset of next table in chain (optional) */
int startidx; /* Starting index */
int num; /* Number of entries in table */
int/char tbl_start[NUM]; /* Start of the table */
} table;
Reference tables are structured as follows:
struct {
int/char near *next; /* Offset of next table in chain (optional) */
int/long/void near *array; /* Starting index */
int num; /* Number of entries in table */
int/char tbl_start[NUM]; /* Start of the table */
} table;
WARNING! Tables should always be created using structures. This ensures that table data is always in the proper format for the table functions. Some compilers reserve the right to rearrange data that is not encapsulated in structures.
As an example, these functions may be used to validate input data. The following code sample accepts a character from the keyboard and verifies that the character is acceptable before processing it.
/* ---- table of valid keyboard input characters */
struct {
int entries; /* number of entries in the table */
char tbl_start[9]; /* list of allowed input characters */
} char_table = {8, "ABC123+-"}; /* NULL at end is not necessary */
...
/* ---- accept and process only valid keystrokes */
char chr = _get_chr ();
while (chr == 0) /* if an extended keystroke */
{
chr = _get_chr (); /* flush extended code */
chr = _get_chr ();
}
if (!_is_inctbl (char_table.tbl_start, chr))
_put_str ("Invalid key pressed");
These functions may be used any time non-contiguous char or int values must be mapped into a set of values which lie within a fixed range. The following example uses this functionality to convert a string of lowercase characters, including IBM PC international characters, to a string of chars which indicate the lexical (sorting) order of each character in the original string. The resulting string is suitable for sorting in the specified lexical order. In the code sample below, the lexical order of the characters is defined in SORT_TABLE.
/* ---- table which defines lexical order to be used */
struct {
int start; /* lexical number sequence starts at 0 */
int entries; /* number of chars in lexical sequence */
char tbl_start[45]; /* 44 chars + unneeded NULL (from string) */
} table = {0,44,"abcdefghijklmnopqrstuvwxyz"};
...
/* ---- convert an IBM ASCII string to a lexical string */
int num = 0;
int len;
char string[50];
...
len = _str_len (string);
while (num < len)
{
string[num] = (char) _index_char (table.tbl_start, string[num]);
num++;
} /* repeat for each char in string */
/* ---- string is ready for sorting */
The _index_... and _xlat_... functions are exact inverses of each other as are the _xindex_... and _xxlat_... functions.
These functions may be used any time a set of values within a fixed range must be mapped to non-contiguous values or addresses.
The _xlat_... and _index_... functions are exact inverses of each other as are the _xxlat_... and _xindex_... functions.
These functions may be used any time one non-contiguous set of values must be mapped into another non-contiguous set of values. The following code takes advantage of this functionality to execute keystroke handlers when keystrokes are pressed. Keystrokes are automatically validated at the same time.
/* ---- external functions for processing keystrokes */
void (*functions1[10])(void) = /* array of function pointers */
{ do_new, do_save, do_retrieve, do_list, do_examine,
do_copy, do_delete, do_copy, do_delete, do_quit };
/* ---- table for mapping keystrokes to keystroke handlers */
struct {
void (near * *funcs)(void); /* address of the associated array */
int num; /* number of entries in table */
char tbl_start[10]; /* list of valid keys */
} table1 = {functions1, 10, {'N','S','R','L','X','C','D','+','-','Q'}};
{
void (near * *func)(void);
int size;
char chr;
#if __LARGE_CODE__
size = 4;
#else
size = 2;
#endif
chr = _get_chr ();
/* ---- keystroke function dispatcher */
while (chr != 0)
{
func = (void (near * *)()) _ctbl_to_ptr (table1.tbl_start, chr, size);
if (func != NULL)
(**func)(); /* call the keystroke handler */
chr = _get_chr ();
}
}
The _is_in..., _index_... and ...tbl_to_... functions are not as fast as the _xlat_... functions. The _is_in... functions represent the fastest possible way to check a value against a non-contiguous list of values (with the exception of using the code from these functions in-line instead of calling them). _is_inbtbl is two to three times faster than the _is_instr function.
The speed of the _is_in..., _index_... and ...tbl_to_... functions depends on the number of entries that must be examined before a specified entry is found. Input values which do not match any table entry take the greatest amount of processing time. As a result, these functions can be sped up dramatically by eliminating input values which are obviously not in the table and defining the table entries in most-frequently used order.
Reducing Table Size with Extended Tables
The extended table functions (_is_inx..., _xindex_..., _xxlat_..., and _x?tbl_to_...) allow multiple tables to be chained together as a single extended table. This allows memory space for tables to be minimized by separating tables and chaining them so redundancies or unneeded ranges are eliminated.
For example, it is common to use two tables which are only slightly different from each other. One table might be used to process keyboard input for a standard menu, and a second table might be used to process keyboard input for the same menu when additional options are available. Rather than create two distinct tables to handle these two cases (which would result in duplicated entries), two extended tables may be constructed. The first table would contain only the entries for the standard menu, and it would specify -1 in the chaining offset (next field) to signify end of chain. The second table would contain only the additional entries required to handle the special menu case, and it would chain to the first table. The two menu cases could then be handled by using an extended table function and pointing to the appropriate table. This slows down execution speed only slightly and conserves space in the default data segment.
/* ---- external functions for processing keystrokes */
void (*functions[8])(void) = /* array of function pointers */
{ do_new, do_save, do_retrieve, do_list,
do_examine, do_copy, do_delete, do_quit };
void (*functions_a[2])(void) = /* array of function pointers */
{ do_plus, do_minus };
/* ---- tables for mapping keystrokes to keystroke handlers */
struct {
char near *next; /* offset of next table in chain */
void (near * *funcs)(void); /* address of the associated array */
int num; /* number of entries in table */
char tbl_start[9]; /* list of valid keys (NULL not required) */
} table = {(char near *)0xffff,functions, 8, "NSRLXCDQ"};
struct {
char near *next; /* offset of next table in chain */
void (near * *funcs)(void); /* address of the associated array */
int num; /* number of entries in table */
char tbl_start[2]; /* list of valid keys */
} table_a = {(char near *)table.tbl_start, functions_a, 2, {'+','-'}};
{
void (near * *func)(void);
int size, standard;
char chr;
#if __LARGE_CODE__
size = 4;
#else
size = 2;
#endif
chr = _get_chr ();
while (chr != 0)
{
if (standard) /* ---- process standard menu input */
func = (void (near * *)()) _xctbl_to_ptr (table.tbl_start, chr, size);
else /* ---- process menu input when additional options are available */
func = (void (near * *)()) _xctbl_to_ptr (table_a.tbl_start, chr, size);
if (func != NULL)
(**func)();
chr = _get_chr ();
}
}
Note that extended tables can always be used as if they were normal tables just by calling the appropriate function (e.g., by calling _btbl_to_ptr instead of _xbtbl_to_ptr). The table pointer passed to the function is the same in either case; the only difference is whether or not the function takes advantage of any chaining information present in the table header. Note that all extended functions require the chaining offset to be present. The results are undefined if the address of a non-extended table is passed to an extended function.