

                     rpc Design Description

                          Version 0.93

                  Copyright (C) 2001 Paul Pelzl



INTRODUCTION

As of version 0.90, nearly all of rpc has been rewritten.  The new
codebase is far better structured, and should be much easier to
expand as a result.  This document is intended to be an aid to anyone
interested in poking around with the code.  Hope it helps.



STRUCTURE

Here is a diagram of the fundamental classes that make up rpc:


                           Complex     RealMatrix
                              |            |
                              V            V
RpcObject  :  RpcDouble, RpcComplex, RpcRealMatrix, RpcError, ...
   |
   V
RpcStack
   |
   V
RpcCalc    RpcInterface
   |            |
   V            |
RpcCommander  <--



RpcStack defines a stack capable of handling virtually any data
type.  The basic calculator object (class RpcCalc) owns two such 
stacks--one acts as a backup to allow undo operations.  Each RpcStack 
is composed of a dynamically-allocated array of type RpcObject*.
The actual data is not of type RpcObject, however; each array
element is one of the derived types:  RpcDouble, RpcComplex,
RpcRealMatrix, RpcError, etc.

class Complex is a wrapper for the C-style "struct complex" and
corresponding complex arithmetic functions defined in CCMATH.
class RealMatrix is a wrapper for the corresponding real matrix
arithmetic.

RpcCalc is the underlying calculator object.  It has public
functions that handle all available operations, such as add(),
exp(), drop(), dup(), etc.  These operations act on the
stack data, and return results to the stack.

RpcInterface is the basic UI class.  It takes input, decides
what RpcCalc member functions to call, and creates the display.

RpcCommander is the top-level class.  It is arguably not doing
much at the moment, but it could be useful if other "interface"
modules are added (e.g. an editor).  It owns an RpcCalc,
and generates an RpcInterface to act on the calc. 



ADDING NEW DATA TYPES

New data types should be derived from RpcObject.  They should be
given a unique number for the private variable "type".  The 
number can be defined via macro in RpcObject.h along with
DOUBLE_TYPE and COMPLEX_TYPE.

To make the new object useful, simply override a bunch of the virtual
functions declared in RpcObject.h.  RpcComplex.h and RpcComplex.cpp should
be suitable guides for this procedure.  At minimum, your object should
be able to create a display string for itself via getDisplayString().
To permit the object to be saved in the stack data file, you will
also need to create a getData() function to generate a copy of
your object's data.

Any of the (virtual) operations which are not overloaded will be handled
by the RpcObject code, which will generate a "bad argument" error.  So
you should only define the operators for data types where they make
sense, and everything will come out okay. 

Now comes the tedious part.  In RpcStack.cpp, every time you see
a switch statement along the lines of:

  switch( newStack.element[i] -> getType() )
  {
      case DOUBLE_TYPE:
          element[i] = new RpcDouble( *((RpcDouble*)(newStack.element[i])) );
          break;
      
      case COMPLEX_TYPE:
          element[i] = new RpcComplex( *((RpcComplex*)(newStack.element[i])) );
          break;
              
      default:
          break;
  }
   

you will need to add a case for YOUR_OBJECT_TYPE.  This is necessary
to ensure that the proper constructor is called at all times.  We
can freely refer to an RpcDouble* by an RpcObject*, but the actual
object had better be created using RpcDouble::RpcDouble().

You will also need to go through the member functions for all the other data 
types derived from RpcObject.  Whenever you see something like this:

    switch( rhs -> getType() )
    {
        // Add two doubles
        case DOUBLE_TYPE:
            temp = new RpcDouble( data + ((RpcDouble*)rhs)->getData() );
            break;
        
        // Add a complex to a double
        case COMPLEX_TYPE:
            temp = new RpcComplex( ((RpcComplex*)rhs)->getData() + data );
            break;
            
        // Default is an error.    
        default:
            temp = new RpcError;
            break;
    }


again you will need to add a case for YOUR_OBJECT_TYPE, provided
it makes sense in that context.  For example, if (when) I create a 
data type MATRIX_TYPE, then I would need to add a case for MATRIX_TYPE 
within RpcDouble::operator *( const RpcObject* ), because multiplying
an RpcDouble by an RpcMatrix should produce a scaled RpcMatrix (rather
than a bad argument error).



ADDING NEW FUNCTIONS

New functions should be added as virtual functions in RpcObject,
then overridden in all derived classes for which that function
makes sense.  Then a wrapper needs to be added in RpcCalc.
Finally, RpcInterface needs to be updated with a new "functString"
for the function, as well as new code within specialFunction() that
will call the new RpcCalc member function.



QUESTIONS/COMMENTS

Feel free to drop me a line at <pelzlpj@eecs.umich.edu>.



