<HTML>
<HEAD>
<TITLE> Rlab2 Reference Manual: Programmer's Reference</TITLE>
</HEAD>
<BODY>
<A HREF="rlab-ref-7.html"><IMG SRC="prev.gif" ALT="Previous"></A>
<A HREF="rlab-ref-9.html"><IMG SRC="next.gif" ALT="Next"></A>
<A HREF="rlab-ref.html#toc8"><IMG SRC="toc.gif" ALT="Contents"></A>
<HR>
<H2><A NAME="s8">8. Programmer's Reference</A></H2>




<H2><A NAME="ss8.1">8.1 Introduction</A></H2>


<P>Rlab2 is not at present an externally visible departure from
Rlab1. However, internally, there are substantial, changes. It is
not really necessary to document the changes, since no programmer's
reference existed for Rlab1. However, documenting the inner-workings
of Rlab2 (here-after referred to simply as Rlab) will not only
benefit the author, but allow advanced users to modify and extend
Rlab2.</P>
<P>In addition to explaining the inner architecture, some justification
will also be provided along the way. I do this mostly so I will
remember why I made certain choices years from now. Additionally, it
may be of some benefit to users.</P>
<P>If you are interested only in writing new builtin, or dynamically
linked functions, you can skip to Section 
<A HREF="#dl-functions">Writing Builtin Functions</A></P>



<H2><A NAME="ss8.2">8.2 Interpreter Operation</A></H2>

<P> </P>

<H3>Overview</H3>


<P>In front of the interpreter sit the scanner and the parser. I will
not spend any time discussing the implementation of these pieces,
since they are constructed with fairly common tools: yacc, and
flex. There are many excellent references that cover these
tools.</P>
<P>The interpreter is a stack-based machine. The machine instructions
are called ``op-codes''. Op-codes are to byte codes what RISC
instructions are to CISC instructions. Each op-code is an
integer. Operation information is <EM>not</EM> packed into
words. Instead each op-code is an integer, and any necessary
information occupies its own word. Although this is not space
efficient, it is easier to work with, and usually faster, since the
program does not have to spend any time dissassembling
words. Instructions are aligned on double-word boundaries, further
speeding operation on most architectures.</P>
<P>It is easiest to present the rest of the interpreter in terms of the
data structures:</P>
<P>
<UL>
<LI> The stack data structure is the <CODE>Datum</CODE>. The <CODE>Datum</CODE>
looks like:

<BLOCKQUOTE><CODE>
<PRE>
typedef struct _datum Datum;

struct _datum
{
  int type;
  union
  {
    double val;
    void *ptr;
  }
  u;
};
</PRE>
</CODE></BLOCKQUOTE>


The <CODE>type</CODE> element tells the programmer what the <CODE>Datum</CODE>
is carrying, a <CODE>val</CODE>, or a <CODE>ptr</CODE> of some type. <CODE>val</CODE>
is a simple double value used to hold numeric constants. <CODE>ptr</CODE>
is a void pointer to a variable or an entity. Rlab's variables are
contained in the <CODE>ListNode</CODE> structure, since each variable must
be associated with some sort of list, tree, or hash-table.
</LI>
<LI> The <CODE>ListNode</CODE> structure is simple:

<BLOCKQUOTE><CODE>
<PRE>
typedef struct _listNode ListNode;

struct _listNode
{
  char *key;
  void *ent;
  int scope;
  ListNode *next;
  ListNode *prev;
};
</PRE>
</CODE></BLOCKQUOTE>


<CODE>ListNode</CODE>s only carry the bare essentials: <CODE>key</CODE> is the
variables name, and is used for identification/lookup. <CODE>Ent</CODE> is
the entity pointer (the thing that actually holds the
data). <CODE>next</CODE>, and <CODE>prev</CODE> are pointers to other
<CODE>ListNodes</CODE>; these are only used when a variable
(<CODE>ListNode</CODE>) is actually installed in a tree or hash-table.
</LI>
<LI> <CODE>Ent</CODE> is short for entity. An entity can contain any
sort of data: matrices, lists, and functions, to name a few. Each
entity can contain only <EM>one</EM> item of data. The <CODE>Ent</CODE>
looks like:

<BLOCKQUOTE><CODE>
<PRE>
typedef struct _ent Ent;

struct _ent
{
  int refc;
  int type;
  double d;
  void *data;
};
</PRE>
</CODE></BLOCKQUOTE>


<CODE>refc</CODE> is the reference counter. In Rlab, any number of
variables can point to the same entity. We need to keep track of the
number of variables that actually point to an entitiy, so that we
know when it is safe to delete, or change the entity. Any function
that tries to delete an entity, should really only decrement the
reference count. The entity should not be deleted until the
reference count is zero.

The <CODE>type</CODE> element tells functions operating on the entity,
what type of data the entity contains.

The <CODE>d</CODE> element is for storing simple numeric scalars, and may
go away in the future.

The <CODE>data</CODE> element is a pointer to the actual data. The data
can be anything, in addition to the pre-defined data classes that
Rlab comes with.  I will not discuss the <CODE>data</CODE> structure
because it is arbitrary.
</LI>
</UL>
</P>
<P>The overall operation of the interpreter is easily viewed with an
outline of the steps needed to perform an operation:</P>
<P>
<UL>
<LI> The interpreter makes it way through the instruction
array. For each defined instruction there is a
corresponding block of code, or function that performs the
operation. </LI>
<LI> The operation-function pops data off the data-stack (an array of
Datums) as needed.</LI>
<LI> If a operation-function needs to operate on the data, it
does so through the class-interface.</LI>
<LI> The operation-function pushes data back on the data-stack
as necessary.</LI>
<LI> Interpreter execution continues to the next operation.</LI>
</UL>
</P>


<H3>Scanner and Parser</H3>




<H3>Stack Machine</H3>




<H3>Memory Management</H3>


<P>A consverative garbage collector is used to do the bulk of memory
managemant functions. All data structures are allocated and free'ed
through the garbage-collector functions. Normally, one wouldn't have
to free any objects when a garbage-collector is in use. However,
users can create quite large data objects, and it helps the
garbage-collector (sometimes) to free these objects when we know
they aren't needed.</P>


<H3>Class Management / Interface</H3>






<H2><A NAME="dl-functions"></A> <A NAME="ss8.3">8.3 Writing Builtin Functions </A></H2>




<H3>Introduction</H3>


<P>This section describes the process for building and linking your
own function(s) into RLaB. There are two ways to do this: </P>
<P>
<OL>
<LI> Compile and link your function with the rlab source to
build a new executable. 
</LI>
<LI> Compile your function as a separate shared-object, and
dynamically  link it with rlab using the <CODE>dlopen</CODE>
function. Assuming your platform properly supports runtime
dynamic linking.</LI>
</OL>
</P>
<P>Either method requires that you write an interface function so that
your function can communicate arguments and a return value with
rlab. The interface function is the same whether you compile your
function with rlab to make a new executable or compile your function
as a shared object.  The end result of this process is the
incorporation of your function into rlab as a builtin function.</P>
<P>This subject will be presented almost entirely with
examples. Writing builting functions is <EM>not</EM> difficult</P>



<H3>Example 1</H3>


<P>All builtin functions are functions, which return an entity pointer,
and have the same argument list. The following exampes is a trivial,
but simple builtin function that multiplies its argument by 2, and
returns the result. The basic steps in this (and most) function are:</P>
<P>
<OL>
<LI> Perform argument checking. The interpreter does not perform
any argument checking. Argument checking is entirely up to
the builtin function. In this example argument checking
consists of checking the number of arguments.
</LI>
<LI> Extracting the required data from the arguments list.
</LI>
<LI> Performing the desired operations.
</LI>
<LI> Creating and configuring the return entity.</LI>
</OL>
</P>
<P>
<HR>
<PRE>
  1: /* simplest.c: A simple builtin function example. */
  2: 
  3: /*
  4:    Compile this file with (in this directory):
  5:    cc -fPIC -c simplest.c -I../../ -I../../gc
  6: */
  7: 
  8: /* Necessary header files. */
  9: #include &quot;rlab.h&quot;
 10: #include &quot;ent.h&quot;
 11: #include &quot;class.h&quot;
 12: #include &quot;mem.h&quot;
 13: #include &quot;bltin.h&quot;
 14: #include &quot;util.h&quot;
 15: 
 16: #include &lt;stdio.h&gt;
 17: #include &lt;string.h&gt;
 18: 
 19: Ent *
 20: Simplest (int nargs, Datum args[])
 21: {
 22:   double dtmp;
 23:   Ent *e, *rent;
 24:   MDR *m;
 25: 
 26:   /* Check the number of arguments. */
 27:   if (nargs != 1)
 28:   {
 29:     rerror (&quot;simplest: only 1 argument allowed&quot;);
 30:   }
 31: 
 32:   /* Get the first (only) argument. */
 33:   e = bltin_get_ent (args[0]);
 34: 
 35:   /* Perform the simplest operation. */
 36:   dtmp = 2.0 * class_double (e);
 37: 
 38:   /* Create a new matrix containing the result. */
 39:   m = mdr_CreateScalar (dtmp);
 40: 
 41:   /* Create the return entity. */
 42:   rent = ent_Create ();
 43: 
 44:   /* Set the entity's data. */
 45:   ent_data (rent) = m;
 46: 
 47:   /* Set the entity's data type. */
 48:   ent_type (rent) = MATRIX_DENSE_REAL;
 49: 
 50:   /* Clean up the argument if possible. */
 51:   ent_Clean (e);
 52: 
 53:   return (rent);
 54: }
</PRE>
<HR>
</P>
<P>Now we will examine this function in more detail:</P>
<P>
<DL>
<DT><B> Lines 1-17 </B><DD><P>Comments and header files.</P>
<DT><B> Lines 19-24 </B><DD><P>The function and automatic variables
declarations. <CODE>nargs</CODE> is the number of arguments the
function was invoked with. <CODE>args</CODE> is the array of Datums
that contains each argument.</P>
<DT><B> Lines 26-30</B><DD><P>Checking the function argument(s). In this
case we merely check that the function was called with only
one argument. In some instances you might wish to do more, or
less. </P>
<DT><B> Line 33</B><DD><P>The function argument is extraced from the array
of Datums. <CODE>bltin_get_ent</CODE> takes a single Datum, and
converts (reduces) it to an entity.</P>
<DT><B> Line 36</B><DD><P><CODE>class_double</CODE> takes a single entity as an
argument, and returns a double value. If the argument to
<CODE>class_double</CODE> is not recognized, and error message is
created, and program control returns to the interpreter.</P>
<P>The return value from <CODE>class_double</CODE> is multiplied by 2.</P>
<DT><B> Line 39</B><DD><P>The return matrix is created, and its value is
set to two.</P>
<DT><B> Line 42</B><DD><P>The return entity (which will carry the return
matrix) is created.</P>
<DT><B> Line 45</B><DD><P>The return entity's data pointer is set to point
at the return matrix.</P>
<DT><B> Line 49</B><DD><P>The return entity's data type is set. The
available types are listed at the bottom of <CODE>rlab.h</CODE>.</P>
<DT><B> Line 51</B><DD><P>The argument entity is cleaned (the memory is
free'ed) if possible.</P>
</DL>
</P>
<P>The new builtin can be compiled on a Linux/ELF system like:</P>
<P>
<BLOCKQUOTE><CODE>
<PRE>
examples&gt; gcc -fPIC -g -c simplest.c -I../../ -I../../gc
examples&gt; gcc -shared -o simplest.so  simplest.o
</PRE>
</CODE></BLOCKQUOTE>
</P>
<P>And tested like:</P>
<P>
<BLOCKQUOTE><CODE>
<PRE>
examples&gt; ../../rlab -rmp
&gt; simplest = dlopen(&quot;./simplest.so&quot;, &quot;Simplest&quot;)
        &lt;bltin-function&gt;
&gt; i = 0;
&gt; while (i &lt; 10000) { x=simplest(i); i++; }
&gt; i
    1e+04  
&gt; x
    2e+04  
</PRE>
</CODE></BLOCKQUOTE>
</P>



<H3>Example 2, Portable Gray Map File I/O</H3>


<P>This example may be a little more meaningful than the last. The file
<CODE>pgm.c</CODE> contains builtin functions for loading and saving
Portable Gray May (PGM) files. The <CODE>RSavepgm</CODE> function takes a
real matrix, and writes it to a file using the PGM
format. <CODE>RLoadpgm</CODE> reads a PGM files, and returns a matrix of
the pixel values. An outline of the these functions follows:</P>
<P>For <CODE>RSavepgm</CODE>:</P>
<P>
<OL>
<LI> Check the number of arguments. Two or three arguments allowed.
<OL>
<LI> Image-Matrix. The matrix of pixel values.</LI>
<LI> File-Name. The file to write.</LI>
<LI> Maximum-Gray-Level. </LI>
</OL>
</LI>
<LI> Get each of the arguments.</LI>
<LI> Open the specified file for binary-write.</LI>
<LI> Use the PGM API to write the matrix as a PGM-file.</LI>
<LI> Clean up, and return.</LI>
</OL>
</P>
<P>There are several sections of code that warrant explanation:</P>
<P>
<DL>
<DT><B> Lines 53-71</B><DD><P>Here is where the arguments are extracted
from the <CODE>args</CODE> array. The function <CODE>bltin_get_ent</CODE>
is used extensively, as before. However, this time,
<CODE>class_matrix_rea</CODE>, and <CODE>class_char_pointer</CODE> are
used in addition to <CODE>class_double</CODE>.</P>
<DT><B> Lines 73-88</B><DD><P>Here is a nice example of file I/O. The
functions <CODE>get_file_ds</CODE>, and <CODE>close_file_ds</CODE>, are
Rlab's interface to file I/O. In this instance a simple file
is opened for writing. But, <CODE>get_file_ds</CODE> works equally
well with sub-processes.</P>
<DT><B> Lines 146, 147</B><DD><P>It is not necessary to use the function
<CODE>ent_clean</CODE> since Rlab uses a generational garbage
collector. However, using <CODE>ent_clean</CODE> can help in some
cases, so it is a good idea to use it whenever possible.</P>
<DT><B> Lines 152-155</B><DD><P>This is where the return-value entity is
created, and configured. In this instance a scalar success
value is all that is returned. </P>
<DT><B> Lines 235-238</B><DD><P><CODE>RLoadpgm</CODE> returns a matrix. Note
that the return entity configuration is similar to that in
<CODE>RSavepgm</CODE>.</P>
</DL>
</P>
<P>
<HR>
<PRE>
  1: /* **************************************************************
  2:  * pgm.c: Routines to load and save a matrix containing the pixels 
  3:  *        of a gray scale image to or from a Portable Gray Map 
  4:  *        file, using the libpgm library from the netpbm 
  5:  *        distribution.
  6:  *
  7:  * To compile on a Linux/ELF system:
  8:  *        gcc -g -fPIC -c pgm.c -I../../ -I../../gc
  9:  *        gcc -shared -o pgm.so pgm.o -lpgm -lpbm 
 10:  * ************************************************************** */
 11: 
 12: #include &quot;rlab.h&quot;
 13: #include &quot;ent.h&quot;
 14: #include &quot;class.h&quot;
 15: #include &quot;mem.h&quot;
 16: #include &quot;bltin.h&quot;
 17: #include &quot;rfileio.h&quot;
 18: #include &quot;util.h&quot;
 19: 
 20: #include &lt;stdio.h&gt;
 21: #include &lt;string.h&gt;
 22: #include &lt;errno.h&gt;
 23: #include &lt;math.h&gt;
 24: 
 25: #include &quot;pgm.h&quot;
 26: 
 27: /* **************************************************************
 28:  * RSavepgm: Save a PGM matrix to a file.
 29: 
 30:  * savepgm ( img_matrix, file_name, maximum_gray_level )
 31:  * ************************************************************** */
 32: 
 33: Ent *
 34: RSavepgm (int nargs, Datum args[])
 35: {
 36:   int gl, max_gl = 0, i, j;
 37:   char *string; 
 38:   double dgl;
 39:   FILE *fn;
 40:   Ent *FN, *GL, *IMG, *rent;
 41:   MDR *img;
 42:   gray **new;
 43:   gray *row_new;
 44: 
 45:   char *kluge_argv[1];        /* we need to provide the pgm lib a dummy argv */
 46: 
 47:   kluge_argv[0]=&quot;savepgm&quot;; /* initialize the dummy argv */
 48: 
 49:   /* Check nargs */
 50:   if ((nargs &lt; 2) || (nargs &gt; 3))
 51:     rerror (&quot;savepgm: requires 2 or 3 arguments&quot;);
 52: 
 53:   /* Get the image. */
 54:   /* First the image entity. */
 55:   IMG = bltin_get_ent (args[0]);
 56: 
 57:   /* Next, get the image matrix from within the entity. */
 58:   img = class_matrix_real (IMG);
 59: 
 60:   /* Then the filename for output. */
 61:   FN = bltin_get_ent (args[1]);
 62:   string = class_char_pointer (FN);
 63: 
 64:   /* If the third argument is present, get it for use as the maximum gray level */
 65:   gl = -1;
 66:   if (nargs == 3)
 67:   {
 68:     GL = bltin_get_ent (args[2]);
 69:     dgl = class_double (GL);
 70:     gl = dgl;
 71:   }
 72: 
 73:   /* Open with file for binary write. */
 74:   if ((fn = get_file_ds (string, &quot;wb&quot;, 0)) == 0) 
 75:   {
 76:     fprintf (stderr, &quot;savepgm: %s: cannot open for write\n&quot;, string);
 77: 
 78:     /* Clean up the arguments when we error out. */
 79:     ent_Clean (IMG);
 80:     ent_Clean (FN);
 81:     if (nargs == 3) ent_Clean (GL);
 82: 
 83:     /* Return 0 to indicate failure. */
 84:     rent = ent_Create ();
 85:     ent_data (rent) = mdr_CreateScalar (0.0);
 86:     ent_type (rent) = MATRIX_DENSE_REAL;
 87:     return (rent);
 88:   }
 89: 
 90:   /*
 91:    * First we need to call pgm_init to initialize the pgm library.  
 92:    * Normally this is called with argc and argv, but here we want to
 93:    * just dummy it up.
 94:    */
 95:   i=1;
 96:   pgm_init (&amp;i, kluge_argv); 
 97: 
 98:   /* Allocate a PGM image array of the correct size */
 99:   new = pgm_allocarray (MNC (img), MNR (img));
100: 
101:   /*
102:    * Now for each row of the image we want to store the pixel values
103:    * for each column.  Of course PGM differs from RLaB in the choice
104:    * of column-major and row-major order.
105:    */
106: 
107:   for (j = 0; j &lt; MNR (img);j++)
108:   {
109:     row_new = *(new+j);
110:     for (i = 0; i &lt; MNC (img); i++)
111:     {
112:       *(row_new+i) = (gray) MdrV0 (img, i*MNR(img)+j);
113: 
114:       /* Keep track of the maximum pixel value in the image */
115:       if(*(row_new+i) &gt; max_gl)
116:       {
117:    max_gl=*(row_new+i);
118:       }
119:     }
120:   } 
121:   
122:   /*
123:    * If no maximum gray level was given as an argument, use the maximum
124:    * pixel value detected above. If the detected maximum pixel value is
125:    * greater than the one specified in argument 3, give a warning, and use
126:    * the maximum detected value.
127:    */
128: 
129:   if(gl == -1)
130:   {
131:     gl = max_gl;
132:   }
133:   else if(max_gl &gt; gl)
134:   {
135:     fprintf (stderr,
136:         &quot;savepgm: image contains pixel values greater than specified maximum&quot;);
137:     fprintf (stderr, &quot;\nusing maximum pixel value instead\n&quot;);
138:     gl = max_gl;
139:   }
140: 
141:   /* Now the array new contains the PGM image, so write it out */
142:   pgm_writepgm (fn, new, MNC (img), MNR (img),(gray)gl, 0);
143:   pgm_freearray (new, MNR (img));
144:   
145:   /* Clean up before returning. */
146:   ent_Clean (FN);
147:   ent_Clean (IMG);
148:   if (nargs == 3) ent_Clean (GL);
149:   close_file_ds (string);
150:   
151:   /* Everything OK, return 1 to indicate success. */
152:   rent = ent_Create ();
153:   ent_data (rent) = mdr_CreateScalar (1.0);
154:   ent_type (rent) = MATRIX_DENSE_REAL;
155:   return (rent);
156: }
157: 
158: /* **************************************************************
159:  * RLavepgm: Load a PGM into a matrix.
160: 
161:  * loadpgm ( file_name )
162:  * ************************************************************** */
163: 
164: Ent *
165: RLoadpgm (int nargs, Datum args[])
166: {
167:   int i, j, rows, cols;
168:   char *string;
169:   FILE *fn;
170:   Ent *FN, *rent;
171:   MDR *img;
172:   gray **new;
173:   gray *row_new;
174:   gray gl;
175:   char *kluge_argv[1];     /* we need to provide the pgm lib a dummy argv */
176: 
177:   kluge_argv[0]=&quot;savepgm&quot;; /* initialize the dummy argv */
178: 
179:   /* Check nargs */
180:   if (nargs != 1)
181:     rerror (&quot;loadpgm: requires 1 argument&quot;);
182: 
183:   /* The the filename for input. */
184:   FN = bltin_get_ent (args[0]);
185:   string = class_char_pointer (FN);
186: 
187:   /* Open with file for binary read. */
188:   if ((fn = get_file_ds (string, &quot;rb&quot;, 0)) == 0) 
189:   {
190:     fprintf (stderr, &quot;loadpgm: %s: cannot open for write\n&quot;, string);
191: 
192:     /* Clean up the arguments when we error out. */
193:     ent_Clean (FN);
194: 
195:     /* Return 0 to indicate failure. */
196:     rent = ent_Create ();
197:     ent_data (rent) = mdr_CreateScalar (0.0);
198:     ent_type (rent) = MATRIX_DENSE_REAL;
199:     return (rent);
200:   } 
201: 
202:   /*
203:    * First we need to call pgm_init to initialize the pgm library.  
204:    * Normally this is called with argc and argv, but here we want to
205:    * just dummy it up.
206:    */
207: 
208:   i = 1;
209:   pgm_init (&amp;i, kluge_argv); 
210: 
211:   /* Allocate a PGM image array of the correct size */
212:   new = pgm_readpgm (fn, &amp;cols, &amp;rows, &amp;gl);
213:   img = mdr_Create (rows, cols);
214: 
215:   /*
216:    * Now for each row of the image we want to store the pixel values
217:    * for each column.  Of course PGM differs from RLaB in the choice
218:    * of column-major and row-major order.
219:    */
220:   for (j = 0; j &lt; rows;j++)
221:   {
222:     row_new = *(new+j);
223:     for (i = 0; i &lt; cols; i++)
224:     {
225:        MdrV0 (img, i*MNR(img)+j) = *(row_new+i);
226:     }
227:   }  
228: 
229:   /* Clean up before returning. */
230:   pgm_freearray(new, MNR (img));
231:   ent_Clean (FN);
232:   close_file_ds (string);
233:   
234:   /* Everything OK, return the image. */
235:   rent = ent_Create ();
236:   ent_data (rent) = img;
237:   ent_type (rent) = MATRIX_DENSE_REAL;
238:   return (rent);
239: }
</PRE>
<HR>
</P>
<P>A simple example showing one possible usage of these two new builtin
functions is provided. Both <CODE>savepgm</CODE>, and <CODE>loadpgm</CODE> are
created via the <CODE>dlopen</CODE> function. The Linux logo is read in,
and some white-noise is added to the picture. The new graphic is
written to a file, which can then be viewed with any PGM compatible
viewer (xv for example).</P>
<P>
<BLOCKQUOTE><CODE>
<PRE>
examples&gt; ../../rlab -mpr
&gt; savepgm = dlopen(&quot;./pgm.so&quot;,&quot;RSavepgm&quot;)
        &lt;bltin-function&gt;
&gt; loadpgm = dlopen(&quot;./pgm.so&quot;,&quot;RLoadpgm&quot;)
        &lt;bltin-function&gt;
&gt; logo = loadpgm(&quot;Logo.pgm&quot;);
&gt; show(logo);
        nr                  :   303
        nc                  :   257
        n                   :   77871
        class               :   num
        type                :   real
        storage             :   dense
&gt; max(max(logo))
      248  
&gt; min(min(logo))
        0  
&gt; rand(&quot;uniform&quot;, 0,50);
&gt; logo = logo + rand(size(logo));
&gt; savepgm(logo,&quot;Logo_noisy.pgm&quot;);
&gt; close(&quot;Logo_noisy.pgm&quot;);
</PRE>
</CODE></BLOCKQUOTE>
</P>



<H3>Dynamic Linking</H3>


<P><B> Not Finished Yet ! </B></P>




<HR>
<A HREF="rlab-ref-7.html"><IMG SRC="prev.gif" ALT="Previous"></A>
<A HREF="rlab-ref-9.html"><IMG SRC="next.gif" ALT="Next"></A>
<A HREF="rlab-ref.html#toc8"><IMG SRC="toc.gif" ALT="Contents"></A>
</BODY>
</HTML>
