Parsifal Software


XIDEK
Extensible Interpreter Development Kit
Reference documentation


Common Definitions

Introduction

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.


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();


Data Encapsulation Classes

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.


Value Class

Introduction

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.


Nested Type Definitions

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.

Protected Member Fields

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.

Constructors/Destructor

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.

Member Functions

Error Checking Functions
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.

Data Access Functions
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.

Type Conversion Functions
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.

Overloaded Operators

Assignment Operators
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 Arithmetic Operators
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.

Unary Arithmetic Operators
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.

Comparison Operators
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.

Autoincrement and Autodecrement Operators
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.

External Functions Defined on Value Objects
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.


Dataset Class

Introduction

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.


Member Fields

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.

Constructor/Destructor

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.


Member Functions

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.


Overloaded Operators

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.


Support Classes

ValueWrapper<Object> Template Class

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.


ErrorDiagnostic and ErrorMessage Classes

Introduction

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.

Member Field

const AgString msg;
This publicly accessible field contains the reason for the exception.

Constructor

ErrorDiagnostic(const AgString &m);
ErrorMessage(const AgString &m);
Sets the msg field to the specified string.

Member Function

const char *message();
Returns a pointer to the text of the message contained in the exception.


FileLocation Class

Introduction

The FileLocation class is used by parsers to record the location of entities in the source file.

Member Fields

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.

Constructor

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.


Library Function Support

Introduction

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.


FunctionObject Class

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.


Function Table

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.


Calling Functions

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.


Global Functions

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.



Table of Contents | Parsifal Software Home Page


Interpreter Development Kit
Copyright © 1997-2002, Parsifal Software.
All Rights Reserved.