8. Run-Time Library

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.

8.1 IBitstream Interface

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).

Methods
nextbits(int n)
Obtains the next 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)
This method is the same as the above method except that it obtains the bits from the bitstream using the little-endian method. That is, the least significant byte is obtained first
 
snextbits(int n)
little_snextbits(int n)
Obtains the next 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)
Reads the next n bits from the bitstream and returns them as an unsigned integer. Only supported for input bitstreams.
 
sgetbits(int n)
little_sgetbits(int n)
Reads the next 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)
Outputs the unsigned integer value 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)
This is equivalent to the *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)
This is equivalent to the *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)
This is equivalent to the *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)
Skips the next n bits from the bitstream. Supported in both input and output bitstreams.
 
align(int n)
Aligns a bitstream to the closest following bitstream position that is a multiple of 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)
Probes the next n bits (input) or returns 0 (output); in either case, the bitstream is (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)
Skips all bits upto, but excluding, the specified code. The code is represented using n bits and it is alen-bit aligned.
 
getpos(void)
Returns the current position of a bitstream. Supported in both input and output bitstreams.
 

8.2 Bitstream Class

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.

C++ Bitstream Class Construction/Destruction

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 an unitialized bistream. Further initialization can be done using one of the Create() methods indicated below.
 
Bitstream(const char *filename, Bitstream_t mode)
Create(const char *filename, Bitstream_t mode)
Create a bistream using the file name 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)
Create a bistream using the file descriptor 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();
This method can be called to close a bitstream, and all pending data will be flushed. For both input and output bitstreams, if the file has been opened by the class, it will be closed before the function returns. This function is also called by the Bitstream class destructor.
Java Bitstream Class Construction
Bitstream(String filename, int type, int bufferlength);
Create a bistream using the file name 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);
Same as above except using default buffer length.
Public Methods
int atend(void);
Returns non-zero value of the input bitstream has reached and end-of-file condition. Not supported for output bitstreams.
 
int geterror(void);
Return the last error recorded by the bitstream. See Error Handling below for more information on the return values.
 
char* const geterror(void);
Return a string describing the last error recorded by the bitstream. See Error Handling below for more information on the return values.
C++ Error Handling

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
No error has been detected.
 
E_END_OF_DATA
An end-of-file condition has been detected.
 
E_INVALID_ALIGNMENT
An invalid alignment argument (not a multiple of 8) has been given to the align() method.
 
E_READ_FAILED
A reading operation on the input file has failed.
 
E_WRITE_FAILED
An output operation on the output file has 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
This is the base exception type. You can use the methods 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
An end-of-date condition has been detected (E_END_OF_DATA).
 
InvalidAlignment
An invalid argument (not a multiple of 8) has been given to the align() method (E_INVALID_ALIGNMENT).
 
ReadFailed
A reading operation on the input file has failed (E_READ_FAILED).
 
WriteFailed
An output operation on the output file has failed (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.

Java Error Handling

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.

8.3 Error Function

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.

8.4 Trace Function

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.

 

Copyright Notice