Common Definitions
The files comdefs.h and
comdefs.cpp contain code used
in all of the baseline interpreters in the development kit. This common
code consists of classes which encapsulate the data upon which the
interpreters operate and low level support classes and functions.
The classes support arithmetic on integer and real data types. In addition,
there is limited support for strings and string concatenation.
The comdefs.h file also declares the interpret() function which is the general
interpreter interface.
All interpreters in the Interpreter Development Kit share a common
interface. All are invoked by calling the interpret() function. All report
errors by throwing an instance of the ErrorDiagnostic class.
Value interpret(const char *scriptText,
Dataset &dataset);
-
The interpret function is the common interface for all the
interpreters in this series. It is not implemented in
comdefs.cpp. Instead, in each interpreter, it is implemented
in the principal module of the interpreter. It is declared in
comdefs.h to make the declaration generally available.
scriptText is a null terminated string containing the text
to be executed.
dataset contains the collection of variables
upon which the script executes. A Dataset object contains both the names of
variables and their corresponding values; it functions as a symbol table.
The script may return a Value object as the value of the interpret()
function. If there is no return statement in the script, or the return
statement does not provide a value, the Value object will be uninitialized.
class ErrorDiagnostic;
-
The ErrorDiagnostic class is used to report errors encountered by the
interpreter. The ErrorDiagnostic class is a wrapper for an AgString object which
contains a diagnostic message explaining the error. The diagnostic
message is accessed by the function:
const char *message();
All arithmetic in the primary interpreters is performed on objects of the
Value class. The complete collection of data used
by an interpreter is encapsulated in the Dataset class, which consists of an array of Value
class objects and a reference to an AgDictionary<
AgString>
object which associates values with variable names.
The Value class encapsulates arithmetic operations for interpreters in the
development kit. It supports both integer, double precision, string and
pointer data types. The type member
field specifies whether a value has been properly initialized and, if so,
the type of data contained in the object. Member functions of the Value
class throw an ErrorMessage exception when they
encounter an error.
By doing little more than redefining the Value class suitably, the nature
of the operations carried out by the interpreters can be changed quite
dramatically. For instance, the Value class can be easily redefined to do
complex arithmetic. The files complex\comdefs.h and
complex\comdefs.cpp contain such a redefined Value
class. To give any of the interpreters support for complex
arithmetic, simply replace comdefs.h and comdefs.cpp with these files. For
a description of the changes required for complex arithmetic, see
complex.htm.
enum Type;
- Defines five data types that can be contained in a Value object:
- uninitType
-
Uninitialized data. This type is created by the null constructor for the class.
- integerType
-
This type is created by constructing a Value
object with an int or long, by explicitly
specifying the type as integerType or by an explicit call to
makeInteger(). Even though the
value is specified as integer, it is nevertheless stored as a
double.
- realType
-
This type is created by
constructing a Value object with a double or by an explicit
call to makeReal().
- stringType
-
AgString.
Created by constructing a Value object with an AgString parameter.
- pointerType
-
Value *. The pointer type is used to represent lvalues. No pointer
arithmetic is supported.
Type type
-
Identifies the type of data stored in this object. The type is initially
set by the constructor and can only be modified by member functions of
the class.
- Anonymous Union
-
The anonymous union provides space for storing the actual value of the
object, depending on type. Since
C++ does not allow classes that have constructors to be members of a
union, the ValueWrapper template class has
been provided to allow use of such classes, although the clarity of the
resulting code is less than ideal.
The fields contained within the anonymous union are as follows:
double value;
-
Contains the actual data value for this value object if type is
integerType or
realType. Integer operators cast the
value to long before performing the relevant operation and then cast
the result back to double for storage.
Value *pointer
-
Used to implement lvalues. This field is valid when type is pointerType.
char
stringSpace[sizeof(ValueWrapper<AgString>)];
-
This field guarantees that there is adequate space available for the AgString object that is
stored in the union when type is
set to stringType. Note that to
store a string the placement new operator of the ValueWrapper class must be used.
Value();
-
Creates an uninitialized value.
Value(int x);
-
Creates a Value object of type integerType.
Value(long x);
-
Creates a Value object of type integerType.
Value(double x, Type t = realType);
-
Creates a Value object of the specified type,
either realType or integerType. Throws an ErrorMessage exception if
t is anything but integerType or
realType.
Value(const AgString &s);
-
Constructs a stringType Value object
using the placement new operator of the ValueWrapper template class to construct the
object in the space allocated for the anonymous
union.
Value(Value *);
-
Constructs a pointerType Value object.
Value(const Value &) ;
- Copy constructor.
~Value();
-
If type is stringType the destructor deletes
the ValueWrapper object that wraps the
string. Since the AgString object is
reference counted, the string storage will be housekept only if this is
the last reference to the storage. Otherwise, there isn't anything for
the destructor to do.
void assertInitialized() const;
-
Throws an ErrorMessage exception if type
is uninitType.
void assertInteger() const;
-
Throws an ErrorMessage exception if type
is not integerType.
void assertScalar() const;
-
Throws an ErrorMessage exception if type
is neither integerType nor
realType.
void assertString() const;
-
Throws an ErrorMessage exception if type
is not stringType.
void assertPointer() const;
-
Throws an ErrorMessage exception if type
is not pointerType.
double getDouble() const;
-
Checks to verify the Value object is either integerType or realType and returns the value. Throws
an ErrorMessage exception otherwise.
long getLong() const;
-
Checks to verify the Value object is integerType returns the value. Throws
an ErrorMessage exception otherwise.
AgString &getString() const;
-
Verifies that the Value object is stringType and returns a reference to
the string. The C++ compiler will select the appropriate function
depending on whether the Value object has been declared const
or not.
Value &deref();
-
Verifies that the Value object is pointerType, replaces the
value of the object with the value of the object pointed to and returns
*this.
AgString asString();
-
This function returns a string representation of the Value object.
AgString asLiteral();
-
This function returns a string representation of the Value object,
formatted as a literal. That is, numeric values are represented the same
as for the asString() function, but strings are
represented as quoted strings with escape sequences replacing control
characters.
int isDefined() const;
-
Returns one if the object has been initialized. Returns zero otherwise.
int isTrue() const;
-
Returns one if the object has been initialized and the value is not
zero. Returns zero otherwise.
-
int isFalse() const;
-
Returns one if the object has not been initialized or the value is
zero. Returns zero otherwise.
Value &makeInteger();
-
Changes the type of the Value object and converts the
value to an integer value. Throws an exception,
ErrorMessage, if the Value
object is neither integerType or
realType.
The return value is *this.
Value &makeReal();
-
Changes the type of the Value
object. Throws an exception, ErrorMessage, if the Value object
is neither integerType or realType.
The return value is *this.
Miscellaneous Functions
- Value &setValue(const Value &v)
-
This function sets the value of the object to the value given by v.
It is used by constructor and assignment operator functions.
The return value is *this.
int hash(int startValue) const;
-
This function creates a
hashcode for a
Value instance, thus providing the capability of making
AgSet,
AgMap
and AgDictionary
classes that contain Value instances.
The externally defined function agABTHash() serves
as an interface between this function and the container classes.
Value idiv(const Value &divisor);
-
This function performs an integer divide, discarding any remainder.
It divides the value of the object by the specified divisor. If either
the dividend or divisor is not integer, it throws an ErrorMessage
exception.
Value rdiv(const Value &divisor);
-
This function performs a floating point divide, even if both dividend
and divisor are integer.
Value &operator = (const Value &argument);
-
The plain assignment operator sets the type and value of the object to
the type and value of argument. It is not necessary to check
whether either the object or argument has been initialized.
The return value is *this.
Value &operator += (const Value &argument);
-
This operator supports both ordinary arithmetic and string concatenation,
depending on the type of *this.
If it is integerType or
realType, the operator adds the value
of argument to the value. Note that double
arithmetic works for both integer and real types. If
argument has type realType,
*this is promoted to realType if it isn't already
realType. Otherwise an ErrorMessage exception is thrown.
If *this has type stringType, argument is
concatenated.
The return value is *this.
Value &operator -= (const Value &argument);
-
Subtracts the value of argument from the object value. Type handling
is the same as for addition.
The return value is *this.
Value &operator *= (const Value &argument);
-
Multiplies the object value by the value of argument. Type
handling is the same as for addition.
The return value is *this.
Value &operator /= (const Value &argument);
-
Divides the object value by the value of argument. Note that
integer arithmetic is performed if both the object and argument
are integerType. Otherwise the arithmetic is floating point. The operator
throws an instance of ErrorMessage if the value of argument
is zero.
The return value is *this.
Value &operator %= (const Value
&argument);
-
Replaces the value of the object with the remainder after division by the
value of argument. As in C/C++, this operation is defined only
if both operands are integerType. The operator throws an
instance of ErrorMessage
if the value of argument is zero.
The return value is *this.
Value &operator &=
(const Value &argument);
-
Ands the value of the object with the value of argument.
only if both operands are
The return value is *this.
Value &operator |= (const Value &argument);
-
Ors the value of the object with the value of argument.
Defined only for integer values.
The return value is *this.
Value &operator ^= (const Value &argument);
-
Exclusive ors the value of the object with the value of argument.
Defined only for integer values.
The return value is *this.
Value &operator <<= (const Value &argument);
-
Shifts the value of the object left by the number of bits specified by
the value of argument. Defined only for integer values.
The return value is *this.
Value &operator >>= (const Value &argument);
-
Shifts the value of the object right by the number of bits specified by
the value of argument. Defined only for integer values.
The return value is *this.
Binary operators are implemented in terms of the corresponding assignment operators. Thus if modifying
the behavior of an operator it is only necessary to change the assignment
operator.
Value operator -() const;
- Returns an object of the same type and with the negative value.
Value operator !() const ;
- Returns an object of opposite truth value.
Value operator ~() const ;
- Returns the bitwise complement. Throws an exception,
ErrorMessage, if the object
is not of integer type.
Only the less than operator and equality operators are implemented
explicitly. The remaining inequalities are defined in terms of the
less than operator and the not equal operator is simply defined as
the logical negation of the equality operator. All comparison operators
will throw an exception, ErrorMessage, if either operand is undefined.
Value operator < (const Value &argument) const;
- Returns an integer value of one if the value of the object is less than the value of
argument, zero otherwise.
Value operator > (const Value &argument) const;
- Returns an integer value of one if the value of the object is greater than the value of
argument, zero otherwise.
Value operator <= (const Value &argument) const;
- Returns an integer value of one if the value of the object is less than or equal
to the value of argument, zero otherwise.
Value operator >= (const Value &argument) const ;
- Returns an integer value of one if the value of the object is greater than or equal
to the value of argument, zero otherwise.
Value operator == (const Value &argument) const;
- Returns an integer value of one if the value of the object is equal
to the value of argument, zero otherwise.
Value operator != (const Value &argument) const;
- Returns an integer value of one if the value of the object is not equal
to the value of argument, zero otherwise.
Value operator ++();
-
The value of the object is preincremented, that is, it is incremented by one
and the incremented value is returned.
Value operator --();
-
The value of the object is predecremented, that is, it is decremented by one
and the decremented value is returned.
Value operator ++(int);
-
The value of the object is postincremented, that is the object is
incremented by one and the original value of the object is returned.
Value operator --(int);
-
The value of the object is postdecremented, that is, the object is decremented
by one and the original value of the object is returned.
These functions are not members of the Value class. They are defined
to take Value objects as arguments.
Value pow(const Value &base, const Value &exponent)
-
The pow function returns the value of base raised to the
exponent power. It is used to implement the Fortran exponent
operator, **. The pow() function is a wrapper for the
corresponding C library function.
int agABTHash(const Value &, int startValue = 0);
-
This function invokes the Value class member function hash to
calculate a
hashcode for a
Value instance, thus providing the capability of making
AgSet,
AgMap
and AgDictionary
classes that contain Value instances.
The Dataset class contains a reference to an AgDictionary
<AgString>
object and an array of Value objects which hold
the values corresponding to the variable names contained in the dictionary.
Because the Dataset objects are completely independent of the interpreters,
any instance of any of the baseline interpreters in the kit can be used to
operate on any instance of a Dataset class.
All interpreters in the kit implement the function
void interpret(char *script, Dataset &dataset);
which executes the specified script using the given set of data. The
function throws an exception, ErrorMessage,
if errors are encountered at any stage of
the script interpretation process.
AgDictionary<AgString> &dictionary;
-
The dictionary identifies the variables to be used by the interpreter.
The dictionary assigns a unique integer, beginning with zero, to each
variable name.
AgStack<Value> data;
-
The data stack contains a Value object for each
variable named in the dictionary. If no value has actually been assigned
to a variable, the type is uninitType.
Use of a stack instead of a simple array allows the number of variables
used to increase without invalidating existing pointers to the values
of previously defined variables.
Dataset(AgDictionary<AgString> &d) ;
-
The data array is constructed large enough to provide an uninitialized
value for each name in the dictionary.
Dataset(const Dataset &d) ;
-
The new Dataset object will refer to the same dictionary object as the
original Dataset object. On the other hand, it will have its own data
array, where the value of each variable is initialized to the value given
by the data array in the original object.
~Dataset();
-
There is nothing for the destructor to do, so it is completely empty.
int size();
-
Returns the number of items in the dataset.
Value &value(const char *variableName)
-
The Value function returns a non-constant reference to the value of the
specified variable so that the value can be read or written as necessary.
Value &operator[] (int k);
-
Returns a non-constant reference to the k'th element in the data array.
The value can be read or written as necessary.
One of the limitations of C++ is that a union cannot contain any
data type that requires a constructor. Thus it would appear impossible to
include a datatype in a Value object that
requires a constructor. Fortunately, there is an easy way around this
limitation. The solution is to create a simple wrapper class which uses the
placement new operator to create an instance of the problematic
data type at the required location.
By making the wrapper class a template class, it can be used for any data
type. Ideally, the wrapper class definition would be nested inside the
definition of the Value class. Unfortunately,
even though such nesting of a template class definition within another class
definition is provided for in the ANSI spec for C++, few compilers support
it adequately. The ValueWrapper template class, therefore, has been defined
externally to the Value class.
Since the ValueWrapper class is used only internally to Value, all the member fields and functions of
ValueWrapper are private and the Value class is declared to be a friend.
The ValueWrapper class can therefore only be used by member functions of
the Value class.
In order to guarantee adequate space within a Value object for an object
wrapped with a ValueWrapper, it is wise to declare within the anonymous
union of the Value class a character array equal to the size of the wrapped
object.
When adding a new data type to the Value class, be sure to make sure that
the Value class constructor for the object uses the placement
new operator to create the wrapper object. The Value class destructor
should also delete the wrapper object. Look at the implementation of the constructor for
stringType Value objects as an indication of
the way to proceed.
The ErrorDiagnostic and ErrorMessage classes are used to throw exceptions
reporting errors encountered by the interpreter. There are no
implementation differences between the two classes. The only difference
is in usage. ErrorMessage is used when the message does not
contain location information.
ErrorDiagnostic is used when the message identifies the location of the
error. ErrorMessage, thus, is used by functions that do not have access
to location information. The intent is for ErrorMessage exceptions to be
caught at program levels where location information is available. Location
information is attached and the exception is thrown again, the second time
as an ErrorDiagnostic exception.
The ErrorDiagnostic and ErrorMessage classes are wrappers for an
AgString object which
contains a diagnostic message explaining the error. The diagnostic
message is accessed by the function:
const char *message();
The only data contained in either class is an AgString object. The only
method returns a pointer to the text of the error message.
const
AgString msg;
-
This publicly accessible field contains the reason for the exception.
ErrorDiagnostic(const
AgString &m);
ErrorMessage(const AgString &m);
-
Sets the msg field to the specified string.
const char *message();
-
Returns a pointer to the text of the message contained in the
exception.
The FileLocation class is used by parsers to record the location of
entities in the source file.
const unsigned char *pointer;
-
This is the pointer into the script corresponding to the line
and column numbers.
int line, column;
-
The line and column values mark significant locations in the input script
file for an interpreter. These may be the locations of rules or the
locations of errors.
FileLocation(const unsigned char * = NULL, int l = 0, int c = 0);
-
Sets the pointer and the line and column numbers. Default values for
parameters are zero.
Although it is quite simple to recognize function calls in a parser it is
another matter altogether to implement an interface between an interpreter
and C library functions. The technique described here is moderately general
and quite robust.
The functions implemented are a subset of the standard C math library. They
are primarily intended as examples. Other functions can readily be added.
The central element of this technique is a function table. The function table contains
triples consisting of a name string, the number of arguments the function
takes, and a function object that implements
the named function.
The function objects are instances of classes derived from FunctionObject. Note that FunctionObject
struct does not overload the function call operator as is customary
with function objects. Rather, it implements separate functions for
calls to functions with one or two arguments. This stratagem was required
to avoid warnings from some compilers about hidden virtual functions.
As implemented, this scheme can
handle functions of one or two variables. To add a new function to the
table the following steps are necessary:
-
If the number of arguments the function requires is not one or two, add a
new virtual function definition to FunctionObject,
specifying the appropriate number of arguments. Also, add a new case to
the switch statement in callFunction to
handle the specified number of arguments.
-
In comdefs.cpp, define a function object using an unnamed struct derived
from FunctionObject.
-
Add a new entry to functionTable, specifying the name by which the
function will be invoked in scripts (this is not necessarily the same as
the library function name), the number of arguments, and the address of
the function object.
The FunctionObject class contains no data, but simply provides a base for
function objects that provide access to standard C library math functions
that have one or two arguments.
The methods are provided for invoking functions of one or two arguments,
where the arguments and return value are all Value objects. The default implementation throws an
ErrorMessage exception indicating an incorrect
number of parameters has been supplied. If functions with a
different number of arguments are required, additional methods should be
defined.
For each math function supported, an unnamed struct is used to define a
function object which implements the appropriate function call operator.
Note that it is possible to overload more than one function call operator
as is done with the atanObject.
functionTable is an array of FunctionDescriptor objects.
The last element in the array is set to all zeroes, to serve as a delimiter.
The FunctionDescriptor class contains three fields:
const char *name;
-
This is the name by which the function is known to the interpreter. It is
not necessarily the same as the actual name of the function it invokes.
int argCount;
-
This is the number of arguments that the function takes. If this is
neither one or two, an appropriate virtual function call operator should
be defined in FunctionObject.
FunctionObject *function;
-
A pointer to a function object that implements the function.
Three functions are available for identifying and calling functions:
int idFunction(const AgString &name, int argCount);
-
This function scans the function table for a
function with the given name and argument count. If a match is not found,
an exception, ErrorMessage, is
thrown. The return value is the index of the function in
the function table.
Value callFunction(const AgString &name, AgStack<Value> &args);
-
This function calls the external function with the given name and the number of
arguments given by the size of the args stack. An exception,
ErrorMessage, is
thrown if there is no such function in the function table. The arguments are popped from
the stack.
Value callFunction(int k, AgStack<Value> &args);
-
This function calls the k'th function in the function table. The appropriate number of
arguments are popped from the stack and supplied to the named function.
long makeDecimal(long octal);
-
One of the unpleasant syntactic conventions of C/C++ is that you can't
distinguish an octal integer from a floating point number that happens to
begin with zero. There are two options:
-
Scan numbers without converting them until the entire number has been
found and then call a conversion routine which has to rescan the number
to carry out the conversion.
-
Convert numeric values on the fly while parsing, but resort to an ugly
expedient in the case that what initially looked like an octal number
is really the beginning of a decimal number.
This function is the ugly expedient. It works by undoing the octal
conversion to make a stack of digits. Then it recalculates the numeric
value by treating the stacked digits as decimal digits rather than as
octal digits.
|