The run-time library provides low-level bitstream I/O facilities, as well as functions for reporting bitstream syntax errors and generating trace information. Although the Flavor translator could directly output the required code, using a thin library provides significant benefits in terms of flexibility. For example, it is impossible to anticipate all possible I/O structures that might be needed by applications (network-based, multi-threaded, multiple buffers, etc.). Attempting to provide a universal solution would be futile. By separating this layer from the core translator, programmers have the option of replacing parts of, or the entire layer with their own code. The only requirement is that this custom code provides an identical interface to the one needed by the translator.
This interface is defined in a pure virtual class called IBitstream
. This class
contains all the methods that the translator expects from its supported underlying bitstream
I/O class.
Based on the IBitstream
class, we provide a complete run-time library that supports
file-based input and output, as well as error reporting and tracing functions. As the source
code for the library is included in its entirety, customization can be performed quite easily.
Note that the library is just about 600 lines of code, demonstrating the simplicity of the
interface. We also provide information on how to rebuild the library, if needed. Custom library
can be built simply by deriving from the IBitstream
class. This will ensure
compatibility with the translator.
In the following, we document both the abtstract interface expected by the translator, as well
as the components of the run-time library. Note that only the abstract interface is used by the
translator. For example, the constructor for the bitstream I/O class is irrelevant for the
translated code, but of course an implementation is required in order to have a fully functional
class. We thus indicate the interfaces required by the translator in
red, while the rest are shown in green. These interfaces
(in red) need to be provided by any custom code that interfaces to
the flavorc
-generated code.
The IBitstream
interface defines the methods that the translator expects from the
underlying class that performs bitstream I/O. Its definition is as follows (the flavori.h
file contains the definition for the C++ library and the IBitstream.java
file for the Java library).
nextbits(int n)
n
bits from the bitstream and returns them
as an unsigned integer, but does not advance the bitstream pointer. Only supported
for input bitstreams.little_nextbits(int n)
snextbits(int n)
little_snextbits(int n)
n
bits from the bitstream and returns them
as a signed integer, but does not advance the bitstream pointer. Only supported
for input bitstreams.getbits(int n)
little_getbits(int n)
n
bits from the bitstream and returns them as an unsigned
integer. Only supported for input bitstreams.sgetbits(int n)
little_sgetbits(int n)
n
bits from the bitstream and returns them as a signed
integer. Only supported for input bitstreams.putbits(unsigned int bits, int n)
little_putbits(unsigned int bits, int n)
bits
using n
bits to the bitstream. Returns the value of bits
. Only supported
for output bitstreams.nextfloat(void)
little_nextfloat(void)
getfloat(void)
little_getfloat(void)
putfloat(void)
little_putfloat(void)
*nextbits
, *getbits
, and
*putbits
methods, but for float
quantities. In this implementation,
floats are assumed to be represented by 32 bits.nextdouble(void)
little_nextdouble(void)
getdouble(void)
little_getdouble(void)
putdouble(void)
little_putdouble(void)
*nextbits
, *getbits
, and
*putbits
methods, but for double
quantities. In this implementation,
doubles are assumed to be represented by 64 bits.nextldouble(void)
little_nextldouble(void)
getldouble(void)
little_getldouble(void)
putldouble(void)
little_putldouble(void)
*nextbits
, *getbits
, and
*putbits
methods, but for long double
quantities. In this implementation,
long doubles are assumed to be represented by 64 bits.skipbits(int n)
n
bits from the bitstream. Supported in both input and output bitstreams.align(int n)
n
.
Note that n
must be a multiple of 8. Supported in both input and output
bitstreams.next(int n, int big, int sign, int alen)
alen
-bit) aligned. If big
=0, then the number is represented using the
little-endian method; otherwise, big-endian byte ordering is used. If sign
=0, then no sign
extension is used; otherwise, sign extension is used.nextcode(unsigned int code, int n, int alen)
code
. The code is represented using
n
bits and it is alen
-bit aligned.getpos(void)
The Bitstream
class is derived from the IBitstream
interface, and
provides basic bitstream I/O facilities in terms of reading or writing bits from/to a file.
Note that this class only supports bistream I/O for quantities of length up to 32 bits (except
for double
or long double
quantities which have length 64 bits). This
ensures that all bitstream manipulation is performed using integers, and is thus very fast.
The following documents the interface provided by the Bitstream
class. We encourage
the interested reader to review the actual definitions as given in the bitstream.h
file of the run-time library, as additional methods may have become available since this text
has been prepared.
To facilitate the Bitstream
class management, in addition to a set of constructors
we also provide corresponding Create()/Destroy()
methods. This allows the same
instance of a Bitstream
class to be reused many times.
Bitstream()
Create()
methods indicated below.Bitstream(const char *filename, Bitstream_t mode)
Create(const char *filename, Bitstream_t mode)
filename
. mode
determines if it is an input (BS_INPUT
) or output (BS_OUTPUT
)
bitstream. A Bitstream
object cannot simultaneously support input and output
modes. If the constructor fails, an error message will be printed to stderr
and the program will terminate.Bitstream(int fd, Bitstream_t mode)
Create(int fd, Bitstream_t mode)
fd
. mode
determines if it is an input (BS_INPUT
) or output (BS_OUTPUT
)
bitstream. A Bitstream
object cannot simultaneously support input and output
modes. If the constructor fails, an error message will be printed to stderr
and the program will terminate.~Bitstream();
Destroy();
Bitstream
class
destructor.Bitstream(String filename, int type, int bufferlength);
filename
. type
determines if it is an input (BS_INPUT
) or output (BS_OUTPUT
)
bitstream. A Bitstream
object cannot simultaneously support input
and output modes. If the constructor fails, an IOException will be thrown.
BS_INPUT
and BS_OUTPUT
are defined in the IBistream
interface. The length of internal buffer can also be specified with bufferlength
parameter.Bitstream(String filename, int type);
int atend(void);
int geterror(void);
char* const geterror(void);
A Bitstream
object maintains as state information the value of the last error message
detected. This information is described by an Error_t
enumeration. The following values are defined.
E_NONE
E_END_OF_DATA
E_INVALID_ALIGNMENT
align()
method.E_READ_FAILED
E_WRITE_FAILED
A user can query the value of the last error recorded using the geterror()
method. A text
message describing the error can be obtained using the getmsg()
method.
In addition to the above interface, the Bitstream
class can also support C++ exceptions
for error handling. Support for exceptions is controlled by the USE_EXCEPTION
defined
constant, which can be found in the file port.h
of the source code distribution. By
default, only the Win32 distribution supports exceptions as almost all UNIX C++ compilers do not yet
support them.
The following exception classes are defined. All support the geterror()
and
getmsg()
interfaces described above.
Error
geterror()
and getmsg()
to identify the particular error signalled. These methods can be used on both the
exception object or the original Bitstream
object.EndOfData
E_END_OF_DATA
).InvalidAlignment
align()
method (E_INVALID_ALIGNMENT
).ReadFailed
E_READ_FAILED)
.WriteFailed
E_WRITE_FAILED)
.Exceptions is the preferred mechanism for error handling as the traditional error reporting mechanism will delay error reporting to the user's code.
All the error methods are reported with standard Java exception mechanism. Currently we are using
IOException
of Java API for handling all errors, mainly because all the possible errors are related
to underlying I/O failure. Since Java allows for the programmer to extend excpetion type to handle more specific
error, a programmer who is extending IBistream
methods can define their own exception by extending
IOException
class. The FlIOException
exception class, derived from
IOException
, is provided to handle error condition. Almost all the methods in IBitstream
throws this exception when there is an error condition.
The flerror()
function is used by the translator to report bitstream syntax errors. Currently,
these errors refer to parsable variables with expected values that do not match the value that was read from
the bitstream. In C++, the function is declared as follows.
void flerror(char* fmt, ...)
The fmt
argument is a string containing a text description of the error, including formatting
information similar to a printf()
statement. The ellipses (...
) at the end of the
declaration indicate a variable number of arguments, which describes potential additional values needed by
the fmt
description.
Similarly in Java, a flerror()
method is defined as a part of the Util
class and below is its declaration.
public static void flerror(String msg)
If an alternate implementation is provided in the user's code, the version present in the library will be
ignored by the linker. The user can also change the name of the function using the -E
command
line option of the translator. The interface, however, must be the same.
In order to produce bitstream traces, the translator inserts calls to a set of trace()
functions
in the get()
method. Two such functions are used; one for tracing quantities that are compatible
with integer types, and one for quantities that are compatible with double types.
Here are the declarations used in the C++ code generated by the translator.
void trace(int pos, int size, unsigned int val, char* fmt, ...)
void trace(int pos, int size, double val, char* fmt, ...)
The first argument is the position of the first bit of the traced quantity in the bitstream. The second
argument is the length of the quantity in bits. Next, we have the actual value, either as an integer or
a double. Finally, the fmt
argument is a string containing a text description of the traced
quantity, including formatting information similar to a printf()
statement. The ellipses
(...
) indicate a variable number of arguments, which describes potential additional values
needed by the fmt
description.
Similarly in Java, a set of trace()
methods are defined as a part of the Util
class and below are their declarations.
public static void trace(int pos, int size, int val, String desc)
public static void trace(int pos, int size, long val, String desc)
public static void trace(int pos, int size, double val, String desc)
If an alternate implementation is provided in the user's code, the version present in the library will be
ignored by the linker. The user can also change the name of the function using the -T
command
line option of the translator. The interface, however, must be the same.