Contents
Manual for CExpr 1.0: a flexible, ASCII I/O library for MFC

Julian Smart

January 1997

Contents

Introduction

CExpr compilation

Bugs and future developments

Tutorial

Class reference

Change log


Introduction

CExpr is a C++ library for reading and writing ASCII files of objects containing string, word, numerical, or list attributes. The file format happens to be a subset of Prolog syntax.

This document describes the CExpr API (Application Programming Interface). It can be used to develop programs with readable, robust and (a rather specialised requirement) Prolog-compatible data files.

During the development of the Hardy diagramming tool within AIAI at the University of Edinburgh, the need arose for a data file format for C++ that was easy for both humans and programs to read, and was robust in the face of fast-moving software development.

The result was the PROLOGIO library, bundled with the wxWindows GUI toolkit, and now in MFC-compatible form with a slightly modified API as the CExpr library.  The advantages of using this library over using binary or custom formats include:

1.	The data files are readable by humans;

2.	I/O routines are easier to write and debug compared with using binary files;

3.	the files are robust: unrecognised attributes and objects will just be ignored by the application;

4.	inbuilt hashing gives a random access capability, useful for when linking up C++ objects as data is read in;

5.	Prolog programs can load the files using a single command.

As the demo shows, MFC programs can easily use the file format in documents, instead of using the serialisation function.

CExpr is perhaps of most use in the development or prototyping phase of a project, when file formats change, and backward compatibility needs to be maintained. This is where the attribute-value file format is most useful, since attributes can often be added and removed without requiring a lot of intricate logic to test the file and application version. When the application is finished, the file format can be recoded using binary serialisation if required, or simply left in CExpr format. A future release may allow binary serialisation for when the application file format has reached a stable state - or you could write it and send it to me for future inclusion!

The next section outlines the way the library is used.

Data file manipulation

Availability and location of CExpr


CExpr compilation

Use the provided project file in the src directory.

If creating your own project file or makefile, define USE_DEFINE and YY_USE_PROTOS.

To test out CExpr, compile the demo program in demo, loading and saving exp files. The object functors are displayed in a list for each file opened or created.


Bugs and future developments

Bugs

Future developments


Tutorial

This chapter is a brief introduction to using the CExpr package.

First, some terminology.  A CExpr database is a list of clauses, each of which represents an object or record which needs to be saved to a file. A clause has a functor (name), and a list of attributes, each of which has a value.  Attributes may take the following types of value: string, word, integer, floating point number, and list.  A list can itself contain any type, allowing for nested data structures.

Consider the following code.

  CExprDatabase db;

  CExpr *my_clause = new PrologExpr("object");

  my_clause->AddAttributeValue("id", (long)1);

  my_clause->AddAttributeValueString("name", "Julian Smart");

  db.Append(my_clause);

  db.Write("myfile.dat");

This creates a database, constructs a clause, adds it to the database, and writes the whole database to a file.  The file it produces looks like this:

object(id = 1,

  name = "Julian Smart").

To read the database back in, the following will work:

  CExprDatabase db;

  db.Read("my_file");

  db.BeginFind();

  CExpr *my_clause = db.FindClauseByFunctor("object");

  int id = 0;

  CString name("None found");

  my_clause->GetAttributeValue("id", &id);

  my_clause->GetAttributeValue("name", name);

  cout << "Id is " << id << ", name is " << name << "\n";


Class reference

These are the main CExpr classes.

CExpr

CExprDatabase: public wxList

Macros


Change log

Version 1.0, 3rd January 1997

First release, based on PROLOGIO from wxWindows.


Data file manipulation

Typical output looks like this:

diagram_definition(type = "Spirit Belief Network").

node_definition(type = "Model",

  image_type = "Diamond",

  attribute_for_label = "name",

  attribute_for_status_line = "label",

  colour = "CYAN",

  default_width = 120,

  default_height = 80,

  text_size = 10,

  can_resize = 1,

  has_hypertext_item = 1,

  attributes = ["name", "combining_function", "level_of_belief"]).

arc_definition(type = "Potentially Confirming",

  image_type = "Spline",

  arrow_type = "End",

  line_style = "Solid",

  width = 1,

  segmentable = 0,

  attribute_for_label = "label",

  attribute_for_status_line = "label",

  colour = "BLACK",

  text_size = 10,

  has_hypertext_item = 1,

  can_connect_to = ["Evidence", "Cluster", "Model", "Evidence", "Evidence", "Cluster"],

  can_connect_from = ["Data", "Evidence", "Cluster", "Evidence", "Data", "Cluster"]).

This is substantially easier to read and debug than a series of numbers and strings.

Note the object-oriented style: a file comprises a series of clauses. Each clause is an object with a functor or object name, followed by a list of attribute-value pairs enclosed in parentheses, and finished with a full stop.  Each attribute value may be a string, a word (no quotes), an integer, a real number, or a list with potentially recursive elements.

The way that the facility is used by an application to read in a file is as follows:

1.	The application creates a CExprDatabase instance.

2.	The application tells the database to read in the entire file.

3.	The application searches the database for objects it requires, decomposing the objects using the CExpr API. The database may be hashed, allowing rapid linking-up of application data.

4.	The application deletes or clears the CExprDatabase.

Writing a file is just as easy:

1.	The application creates a CExprDatabase instance.

2.	The application adds objects to the database using the API.

3.	The application tells the database to write out the entire database.

4.	The application deletes or clears the CExprDatabase.

To use the library, include "cexpr.h" and link with cexpr.lib.


Availability and location of CExpr

CExpr source and documentation is freely distributed on Julian Smart's Web site:

http://web.ukonline.co.uk/Members/julian.smart


Bugs

These are the known bugs:

1.	Functors are permissable only in the main clause (object). Therefore nesting of structures must be done using lists, not predicates as in Prolog.

2.	There is a limit to the size of strings read in (about 5000 bytes).


Future developments

It would be nice to be able to use MFC's serialisation facilities for when the application file format settles down and fast binary I/O is required.


CExpr

The CExpr class is the building brick of CExpr expressions. It can represent both a clause, and any subexpression (long integer, float, string, word, or list).

CExpr::CExpr

CExpr::~CExpr

CExpr::AddAttributeValue

CExpr::AddAttributeValueString

CExpr::AddAttributeValueStringList

CExpr::AddAttributeValueWord

CExpr::Append

CExpr::Arg

CExpr::AttributeValue

CExpr::Copy

CExpr::DeleteAttributeValue

CExpr::Functor

CExpr::GetAttributeValue

CExpr::GetAttributeValueStringList

CExpr::GetClientData

CExpr::GetFirst

CExpr::GetLast

CExpr::GetNext

CExpr::Insert

CExpr::IntegerValue

CExpr::Nth

CExpr::RealValue

CExpr::SetClientData

CExpr::StringValue

CExpr::Type

CExpr::WordValue

CExpr::WriteLispExpr

CExpr::WriteClause

CExpr::WriteExpr


CExprDatabase: public wxList

The CExprDatabase class represents a database, or list, of expressions.  Instances of this class are used for reading, writing and creating data files.

CExprDatabase::CExprDatabase

CExprDatabase::~CExprDatabase

CExprDatabase::Append

CExprDatabase::BeginFind

CExprDatabase::ClearDatabase

CExprDatabase::FindClause

CExprDatabase::FindClauseByFunctor

CExprDatabase::GetErrorCount

CExprDatabase::HashFind

CExprDatabase::Read

CExprDatabase::ReadFromString

CExprDatabase::Write

CExprDatabase::WriteLisp


Macros

The following macros have been defined to make constructing CExpr objects slightly easier:

#define CExprMakeInteger(x) (new CExpr((long)x))

#define CExprMakeReal(x)    (new CExpr((float)x))

#define CExprMakeString(x)  (new CExpr(CExprString, x))

#define CExprMakeWord(x)    (new CExpr(CExprWord, x))

#define CExprMake(x)        (new CExpr(x))


CExpr::CExpr

void CExpr(const wxString& functor)

Construct a new clause with this form, supplying the functor name.

void CExpr(CExprType type, const wxString& word_or_string,
BOOL allocate = TRUE)

Construct a new empty list, word (will be output with no quotes) or a string, depending on the value of type (CExprList, CExprWord, CExprString).

If a word or string, the allocate parameter determines whether a new copy of the value is allocated, the default being TRUE.

void CExpr(long the_int)

Construct an integer expression.

void CExpr(float the_float)

Construct a floating point expression.

void CExpr(wxList *the_list)

Construct a list expression. The list's nodes' data should themselves be CExprs.

The current version of this library no longer uses the wxListinternally, so this constructor turns the list into its internal format (assuming a non-nested list) and then deletes the supplied list.


CExpr::~CExpr

void ~CExpr(void)

Destructor.


CExpr::AddAttributeValue

Use these on clauses ONLY. Note that the functions for adding strings and words must be differentiated by function name which is why they are missing from this group (see AddAttributeValueString andAddAttributeValueWord).

void AddAttributeValue(const wxString& attribute, float value)

Adds an attribute and floating point value pair to the clause.

void AddAttributeValue(const wxString& attribute, long value)

Adds an attribute and long integer value pair to the clause.

void AddAttributeValue(const wxString& attribute, wxList *value)

Adds an attribute and list value pair to the clause, converting the list into internal form and then deleting value. Note that the list should not contain nested lists (except if in internal CExpr form.)

void AddAttributeValue(const wxString& attribute, CExpr **value)

Adds an attribute and CExpr value pair to the clause. Do not delete value once this function has been called.


CExpr::AddAttributeValueString

void AddAttributeValueString(const wxString& attribute, const wxString& value)

Adds an attribute and string value pair to the clause.


CExpr::AddAttributeValueStringList

void AddAttributeValueStringList(const wxString& attribute, wxList *value)

Adds an attribute and string list value pair to the clause.

Note that the list passed to this function is a list of strings, NOT a list of CExprs; it gets turned into a list of CExprs automatically. This is a convenience function, since lists of strings are often manipulated in C++.


CExpr::AddAttributeValueWord

void AddAttributeValueWord(const wxString& attribute, const wxString& value)

Adds an attribute and word value pair to the clause.


CExpr::Append

void Append(CExpr *value)

Append the value to the end of the list. The CExpr must be a list.


CExpr::Arg

CExpr * Arg(CExprType typ, int n)

Get nth arg of the given clause (starting from 1). NULL is returned if the expression is not a clause, or n is invalid, or the given type does not match the actual type. See also Nth.


CExpr::AttributeValue

CExpr * AttributeValue(const wxString& word)

Use this on clauses ONLY. Searches the clause for an attribute matching word, and returns the value associated with it.


CExpr::Copy

CExpr * Copy(void)

Recursively copies the expression, allocating new storage space.


CExpr::DeleteAttributeValue

void DeleteAttributeValue(const wxString& attribute)

Use this on clauses ONLY. Deletes the attribute and its value (if any) from the clause.


CExpr::Functor

wxString Functor(void)

Use this on clauses ONLY. Returns the clause's functor (object name).


CExpr::GetAttributeValue

These functions are the easiest way to retrieve attribute values, by passing a reference or pointer to variable. If the attribute is present, the variable will be filled with the appropriate value.  If not, the existing value is left alone.  This style of retrieving attributes makes it easy to set variables to default values before calling these functions; no code is necessary to check whether the attribute is present or not.

BOOL GetAttributeValue(const wxString& attribute, wxString& value)

Retrieve a string (or word) value.

BOOL GetAttributeValue(const wxString& attribute, float *value)

Retrieve a floating point value.

BOOL GetAttributeValue(const wxString& attribute, int *value)

Retrieve an integer value.

BOOL GetAttributeValue(const wxString& attribute, long *value)

Retrieve a long integer value.

BOOL GetAttributeValue(const wxString& attribute, CExpr **value)

Retrieve a CExpr pointer.


CExpr::GetAttributeValueStringList

BOOL GetAttributeValueStringList(const wxString& attribute, wxList *value)

Use this on clauses ONLY. See above for comments on this style of attribute value retrieval. This function expects to receive a pointer to a new list (created by the calling application); it will append strings to the list if the attribute is present in the clause.


CExpr::GetClientData

CObject * GetClientData(void)

Retrieve arbitrary data stored with this clause. This can be useful when reading in data for storing a pointer to the C++ object, so when another clause makes a reference to this clause, its C++ object can be retrieved. See SetClientData.


CExpr::GetFirst

CExpr * GetFirst(void)

If this is a list expression (or clause), gets the first element in the list.

See also GetLast, GetNext, Nth.


CExpr::GetLast

CExpr * GetLast(void)

If this is a list expression (or clause), gets the last element in the list.

See also GetFirst, GetNext, Nth.


CExpr::GetNext

CExpr * GetNext(void)

If this is a node in a list (any CExpr may be a node in a list), gets the next element in the list.

See also GetFirst, GetLast, Nth.


CExpr::Insert

void Insert(CExpr *value)

Insert the value at the start of the list. The CExpr   must be a list.


CExpr::IntegerValue

long IntegerValue(void)

Returns the integer value of the expression.


CExpr::Nth

CExpr * Nth(int n)

Get nth arg of the given list expression (starting from 0). NULL is returned if the expression is not a list expression, or n is invalid. See also Arg.

Normally, you would use attribute-value pairs to add and retrieve data from objects (clauses) in a data file. However, if the data gets complex, you may need to store attribute values as lists, and pick them apart yourself.

Here is an example of using lists.

  int regionNo = 1;

  CString regionNameStr;

  CExpr *regionExpr = NULL;

  regionNameStr.Format("region%d", regionNo);

  // Keep getting attribute values until no more regions.

  while (regionExpr = clause->AttributeValue(regionNameStr))

  {

    /*

     * Get the region information

     *

     */

    CString regionName;

    CString formatString;

    int formatMode = FORMAT_NONE;

    int fontSize = 10;

    int fontFamily = wxSWISS;

    int fontStyle = wxNORMAL;

    int fontWeight = wxNORMAL;

    CString textColour;

    if (regionExpr->Type() == CExprList)

    {

      CExpr *nameExpr = regionExpr->Nth(0);

      CExpr *stringExpr = regionExpr->Nth(1);

      CExpr *formatExpr = regionExpr->Nth(2);

      CExpr *sizeExpr = regionExpr->Nth(3);

      CExpr *familyExpr = regionExpr->Nth(4);

      CExpr *styleExpr = regionExpr->Nth(5);

      CExpr *weightExpr = regionExpr->Nth(6);

      CExpr *colourExpr = regionExpr->Nth(7);

      regionName = nameExpr->StringValue();

      formatString = stringExpr->StringValue();

      formatMode = (int)formatExpr->IntegerValue();

      fontSize = (int)sizeExpr->IntegerValue();

      fontFamily = (int)familyExpr->IntegerValue();

      fontStyle = (int)styleExpr->IntegerValue();

      fontWeight = (int)weightExpr->IntegerValue();

      textColour = colourExpr->StringValue();

    }

    ...

    regionNo ++;

    regionNameStr.Format("region%d", regionNo);

  }


CExpr::RealValue

float RealValue(void)

Returns the floating point value of the expression.


CExpr::SetClientData

void SetClientData(CObject *data)

Associate arbitrary data with this clause. This can be useful when reading in data for storing a pointer to the C++ object, so when another clause makes a reference to this clause, its C++ object can be retrieved. See GetClientData.


CExpr::StringValue

wxString StringValue(void)

Returns the string value of the expression.


CExpr::Type

CExprType Type(void)

Returns the type of the expression. CExprType is defined as follows:

typedef enum {

    CExprNull,

    CExprInteger,

    CExprReal,

    CExprWord,

    CExprString,

    CExprList

} CExprType;


CExpr::WordValue

wxString WordValue(void)

Returns the word value of the expression.


CExpr::WriteLispExpr

void WriteLispExpr(ostream& stream)

Writes the expression or clause to the given stream in LISP format. Not normally needed, since the whole CExprDatabase will usually be written at once. Lists are enclosed in parentheses will no commas.


CExpr::WriteClause

void WriteClause(ostream& stream)

Writes the clause to the given stream in CExpr format. Not normally needed, since the whole CExprDatabase will usually be written at once. The format is: functor, open parenthesis, list of comma-separated expressions, close parenthesis, full stop.


CExpr::WriteExpr

void WriteExpr(ostream& stream)

Writes the expression (not clause) to the given stream in CExpr format. Not normally needed, since the whole CExprDatabase will usually be written at once. Lists are written in square bracketed, comma-delimited format.


CExprDatabase::CExprDatabase

void CExprDatabase(CExprErrorHandler handler = 0)

Construct a new, unhashed database, with an optional error handler. The error handler must be a function returning a BOOL and taking an integer and a string argument. When an error occurs when reading or writing a database, this function is called. The error is given as the first argument (currently one of CEXPR_ERROR_GENERAL, CEXPR_ERROR_SYNTAX) and an error message is given as the second argument. If FALSE is returned by the error handler, processing of the CExpr operation stops.

Another way of handling errors is simply to call GetErrorCount after the operation, to check whether errors have occurred, instead of installing an error handler. If the error count is more than zero, Write and Read will return FALSE to the application.

For example:

BOOL myErrorHandler(int err, chat *msg)

{

  if (err == CEXPR_ERROR_SYNTAX)

  {

    AfxMessageBox(msg);

  }

  return FALSE;

}

CExprDatabase database(myErrorHandler);

void CExprDatabase(CExprType type, const wxString& attribute,
 int size = 500, CExprErrorHandler handler = 0)

Construct a new database hashed on a combination of the clause functor and a named attribute (often an integer identification).

See above for an explanation of the error handler.


CExprDatabase::~CExprDatabase

void ~CExprDatabase(void)

Delete the database and contents.


CExprDatabase::Append

void Append(CExpr *clause)

Append a clause to the end of the database. If the database is hashing, the functor and a user-specified attribute will be hashed upon, giving the option of random access in addition to linear traversal of the database.


CExprDatabase::BeginFind

void BeginFind(void)

Reset the current position to the start of the database. Subsequent FindClause calls will move the pointer.


CExprDatabase::ClearDatabase

void ClearDatabase(void)

Clears the contents of the database.


CExprDatabase::FindClause

Various ways of retrieving clauses from the database. A return value of NULL indicates no (more) clauses matching the given criteria. Calling the functions repeatedly retrieves more matching clauses, if any.

CExpr * FindClause(long id)

Find a clause based on the special "id'' attribute.

CExpr * FindClause(const wxString& attribute, const wxString& value)

Find a clause which has the given attribute set to the given string or word value.

CExpr * FindClause(const wxString& attribute, long value)

Find a clause which has the given attribute set to the given integer value.

CExpr * FindClause(const wxString& attribute, float value)

Find a clause which has the given attribute set to the given floating point value.


CExprDatabase::FindClauseByFunctor

CExpr * FindClauseByFunctor(const wxString&  functor)

Find the next clause with the specified functor.


CExprDatabase::GetErrorCount

int GetErrorCount(void)

Returns the number of errors encountered during the last read or write operation.


CExprDatabase::HashFind

CExpr * HashFind(const wxString&  functor, long value)

Finds the clause with the given functor and with the attribute specified in the database constructor having the given integer value.

For example,

// Hash on a combination of functor and integer "id" attribute when reading in

CExprDatabase db(CExprInteger, "id");

// Read it in

db.Read("data.doc");

// Retrieve a clause with specified functor and id

CExpr *clause = db.HashFind("node", 24);

This would retrieve a clause which is written: node(id = 24, ..., ).

CExpr * HashFind(const wxString&  functor, const wxString& value)

Finds the clause with the given functor and with the attribute specified in the database constructor having the given string value.


CExprDatabase::Read

BOOL Read(const wxString&  filename)

Reads in the given file, returning TRUE if successful.


CExprDatabase::ReadFromString

BOOL ReadFromString(const wxString&  buffer)

Reads a database from the given string buffer, returning TRUE if successful.


CExprDatabase::Write

BOOL Write(const wxString& filename)

BOOL Write(ostream& stream)

Writes the database as a CExpr-format file, return FALSE if there were errors, TRUE otherwise.


CExprDatabase::WriteLisp

void WriteLisp(ostream& stream)

Writes the database as a LISP-format file.