Translator Declarations
A translator is a collection of D assignment statements provided by the
supplier of an interface that can be used to translate an input
expression into an object of struct type. To understand the need for
and use of translators, we'll consider as an example the ANSI-C standard
library routines defined in stdio.h. These routines operate on a data structure
named FILE whose implementation artifacts are abstracted away from C programmers. A
standard technique for creating a data structure abstraction is to provide only
a forward declaration of a data structure in public header files, while
keeping the corresponding struct definition in a separate private header file.
If you are writing a C program and wish to know the
file descriptor corresponding to a FILE struct, you can use the fileno(3C)
function to obtain the descriptor rather than dereferencing a member of the
FILE struct directly. The Solaris header files enforce this rule by defining
FILE as an opaque forward declaration tag so it cannot be dereferenced
directly by C programs that include <stdio.h>. Inside the libc.so.1 library, you
can imagine that fileno() is implemented in C something like this:
int
fileno(FILE *fp)
{
struct file_impl *ip = (struct file_impl *)fp;
return (ip->fd);
}
Our hypothetical fileno() takes a FILE pointer as an argument and casts
it to a pointer to a corresponding internal libc structure, struct file_impl, and
then returns the value of the fd member of the implementation structure.
Why does Solaris implement interfaces like this? By abstracting the details of
the current libc implementation away from client programs, Sun is able to
maintain a commitment to strong binary compatibility while continuing to evolve and
change the internal implementation details of libc. In our example, the fd
member could change size or position within struct file_impl, even in a patch,
and existing binaries calling fileno(3C) would not be affected by this change
because they do not depend on these artifacts.
Unfortunately, observability software such as DTrace has the need to peer inside
the implementation in order to provide useful results, and does not have
the luxury of calling arbitrary C functions defined in Solaris libraries or
in the kernel. You could declare a copy of struct file_impl in your
D program in order to instrument the routines declared in stdio.h, but
then your D program would rely on Private implementation artifacts of the
library that might break in a future micro or minor release, or
even in a patch. Ideally, we want to provide a construct for
use in D programs that is bound to the implementation of the
library and is updated accordingly, but still provides an additional layer of
abstraction associated with greater stability.
A new translator is created using a declaration of the form:
translator output-type < input-type input-identifier > {
member-name = expression ;
member-name = expression ;
...
};
The output-type names a struct that will be the result type for
the translation. The input-type specifies the type of the input expression, and
is surrounded in angle brackets < > and followed by an input-identifier that
can be used in the translator expressions as an alias for the
input expression. The body of the translator is surrounded in braces { }
and terminated with a semicolon (;), and consists of a list of
member-name and identifiers corresponding translation expressions. Each member declaration must name a
unique member of the output-type and must be assigned an expression of
a type compatible with the member type, according to the rules for
the D assignment (=) operator.
For example, we could define a struct of stable information about stdio
files based on some of the available libc interfaces:
struct file_info {
int file_fd; /* file descriptor from fileno(3C) */
int file_eof; /* eof flag from feof(3C) */
};
A hypothetical D translator from FILE to file_info could then be declared
in D as follows:
translator struct file_info < FILE *F > {
file_fd = ((struct file_impl *)F)->fd;
file_eof = ((struct file_impl *)F)->eof;
};
In our hypothetical translator, the input expression is of type FILE * and
is assigned the input-identifier F. The identifier F can then be used
in the translator member expressions as a variable of type FILE * that
is only visible within the body of the translator declaration. To determine
the value of the output file_fd member, the translator performs a cast
and dereference similar to the hypothetical implementation of fileno(3C) shown above. A
similar translation is performed to obtain the value of the EOF indicator.
Sun provides a set of translators for use with Solaris interfaces that
you can invoke from your D programs, and promises to maintain these
translators according to the rules for interface stability defined earlier as the
implementation of the corresponding interface changes. We'll learn about these translators later
in the chapter, after we learn how to invoke translators from D.
The translator facility itself is also provided for use by application and
library developers who wish to offer their own translators that D programmers
can use to observe the state of their software packages.